Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
init.js241 linesDownload Raw Back to commands
1const { statSync } = require('node:fs')
2const { relative, resolve } = require('node:path')
3const { mkdir } = require('node:fs/promises')
4const initJson = require('init-package-json')
5const npa = require('npm-package-arg')
6const libexec = require('libnpmexec')
7const mapWorkspaces = require('@npmcli/map-workspaces')
8const PackageJson = require('@npmcli/package-json')
9const { log, output, input } = require('proc-log')
10const updateWorkspaces = require('../utils/update-workspaces.js')
11const BaseCommand = require('../base-cmd.js')
12
13const posixPath = p => p.split('\\').join('/')
14
15class Init extends BaseCommand {
16  static description = 'Create a package.json file'
17  static params = [
18    'init-author-name',
19    'init-author-url',
20    'init-license',
21    'init-module',
22    'init-type',
23    'init-version',
24    'init-private',
25    'yes',
26    'force',
27    'scope',
28    'workspace',
29    'workspaces',
30    'workspaces-update',
31    'include-workspace-root',
32  ]
33
34  static name = 'init'
35  static usage = [
36    '<package-spec> (same as `npx create-<package-spec>`)',
37    '<@scope> (same as `npx <@scope>/create`)',
38  ]
39
40  static workspaces = true
41  static ignoreImplicitWorkspace = false
42
43  async exec (args) {
44    // npm exec style
45    if (args.length) {
46      return await this.execCreate(args)
47    }
48
49    // no args, uses classic init-package-json boilerplate
50    await this.template()
51  }
52
53  async execWorkspaces (args) {
54    // if the root package is uninitiated, take care of it first
55    if (this.npm.flatOptions.includeWorkspaceRoot) {
56      await this.exec(args)
57    }
58
59    // reads package.json for the top-level folder first
60    // by doing this we ensure the command throw if no package.json is found before trying to create a workspace package.json file or its folders
61    const { content: pkg } = await PackageJson.normalize(this.npm.localPrefix).catch(err => {
62      if (err.code === 'ENOENT') {
63        log.warn('init', 'Missing package.json. Try with `--include-workspace-root`.')
64      }
65      throw err
66    })
67
68    // these are workspaces that are being created, so we can't use this.setWorkspaces()
69    const filters = this.npm.config.get('workspace')
70    const wPath = filterArg => resolve(this.npm.localPrefix, filterArg)
71
72    const workspacesPaths = []
73    // npm-exec style, runs in the context of each workspace filter
74    if (args.length) {
75      for (const filterArg of filters) {
76        const path = wPath(filterArg)
77        await mkdir(path, { recursive: true })
78        workspacesPaths.push(path)
79        await this.execCreate(args, path)
80        await this.setWorkspace(pkg, path)
81      }
82      return
83    }
84
85    // no args, uses classic init-package-json boilerplate
86    for (const filterArg of filters) {
87      const path = wPath(filterArg)
88      await mkdir(path, { recursive: true })
89      workspacesPaths.push(path)
90      await this.template(path)
91      await this.setWorkspace(pkg, path)
92    }
93
94    // reify packages once all workspaces have been initialized
95    await this.update(workspacesPaths)
96  }
97
98  async execCreate (args, runPath = process.cwd()) {
99    const [initerName, ...otherArgs] = args
100    let packageName = initerName
101
102    // Only a scope, possibly with a version
103    if (/^@[^/]+$/.test(initerName)) {
104      const [, scope, version] = initerName.split('@')
105      packageName = `@${scope}/create`
106      if (version) {
107        packageName = `${packageName}@${version}`
108      }
109    } else {
110      const req = npa(initerName)
111      if (req.type === 'git' && req.hosted) {
112        const { user, project } = req.hosted
113        packageName = initerName.replace(`${user}/${project}`, `${user}/create-${project}`)
114      } else if (req.registry) {
115        packageName = `${req.name.replace(/^(@[^/]+\/)?/, '$1create-')}@${req.rawSpec}`
116      } else {
117        throw Object.assign(new Error(
118          'Unrecognized initializer: ' + initerName +
119          '\nFor more package binary executing power check out `npx`:' +
120          '\nhttps://docs.npmjs.com/cli/commands/npx'
121        ), { code: 'EUNSUPPORTED' })
122      }
123    }
124
125    const newArgs = [packageName, ...otherArgs]
126    const {
127      flatOptions,
128      localBin,
129      globalBin,
130      chalk,
131    } = this.npm
132    const scriptShell = this.npm.config.get('script-shell') || undefined
133    const yes = this.npm.config.get('yes')
134
135    // only send the init-private flag if it is set
136    const opts = { ...flatOptions }
137    if (this.npm.config.isDefault('init-private')) {
138      delete opts.initPrivate
139    }
140
141    await libexec({
142      ...opts,
143      args: newArgs,
144      localBin,
145      globalBin,
146      output,
147      chalk,
148      path: this.npm.localPrefix,
149      runPath,
150      scriptShell,
151      yes,
152    })
153  }
154
155  async template (path = process.cwd()) {
156    const initFile = this.npm.config.get('init-module')
157    if (!this.npm.config.get('yes') && !this.npm.config.get('force')) {
158      output.standard([
159        'This utility will walk you through creating a package.json file.',
160        'It only covers the most common items, and tries to guess sensible defaults.',
161        '',
162        'See `npm help init` for definitive documentation on these fields and exactly what they do.',
163        '',
164        'Use `npm install <pkg>` afterwards to install a package and save it as a dependency in the package.json file.',
165        '',
166        'Press ^C at any time to quit.',
167      ].join('\n'))
168    }
169
170    try {
171      const data = await input.read(() => initJson(path, initFile, this.npm.config))
172      log.silly('package data', data)
173      return data
174    } catch (er) {
175      if (er.message === 'canceled') {
176        output.flush()
177        log.warn('init', 'canceled')
178      } else {
179        throw er
180      }
181    }
182  }
183
184  async setWorkspace (pkg, workspacePath) {
185    const workspaces = await mapWorkspaces({ cwd: this.npm.localPrefix, pkg })
186
187    // skip setting workspace if current package.json glob already satisfies it
188    for (const wPath of workspaces.values()) {
189      if (wPath === workspacePath) {
190        return
191      }
192    }
193
194    // if a create-pkg didn't generate a package.json at the workspace folder level, it might not be recognized as a workspace by mapWorkspaces, so we're just going to avoid touching the top-level package.json
195    try {
196      statSync(resolve(workspacePath, 'package.json'))
197    } catch {
198      return
199    }
200
201    const pkgJson = await PackageJson.load(this.npm.localPrefix)
202
203    pkgJson.update({
204      workspaces: [
205        ...(pkgJson.content.workspaces || []),
206        posixPath(relative(this.npm.localPrefix, workspacePath)),
207      ],
208    })
209
210    await pkgJson.save()
211  }
212
213  async update (workspacesPaths) {
214    // translate workspaces paths into an array containing workspaces names
215    const workspaces = []
216    for (const path of workspacesPaths) {
217      const { content: { name } } = await PackageJson.normalize(path).catch(() => ({ content: {} }))
218
219      if (name) {
220        workspaces.push(name)
221      }
222    }
223
224    const {
225      config,
226      flatOptions,
227      localPrefix,
228    } = this.npm
229
230    await updateWorkspaces({
231      config,
232      flatOptions,
233      localPrefix,
234      npm: this.npm,
235      workspaces,
236    })
237  }
238}
239
240module.exports = Init
241 
codekingpro/portable-devtools · Team Ai