@form8ion/javascript 14.0.0-alpha.9 → 14.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/lib/index.js +1235 -1249
  2. package/lib/index.js.map +1 -1
  3. package/package.json +3 -3
package/lib/index.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import { questionNames as questionNames$2, questions } from '@travi/language-scaffolder-prompts';
2
2
  import deepmerge from 'deepmerge';
3
- import { fileTypes, fileExists, optionsSchemas, validateOptions, loadConfigFile, applyEnhancers, writeConfigFile } from '@form8ion/core';
4
- import { scaffoldChoice, writePackageJson, packageManagers as packageManagers$1, projectTypes as projectTypes$1, mergeIntoExistingPackageJson, dialects as dialects$1, DEV_DEPENDENCY_TYPE, PROD_DEPENDENCY_TYPE } from '@form8ion/javascript-core';
3
+ import { fileTypes, fileExists, optionsSchemas, validateOptions, loadConfigFile, writeConfigFile, applyEnhancers } from '@form8ion/core';
4
+ import { scaffoldChoice, dialects as dialects$1, projectTypes as projectTypes$1, packageManagers as packageManagers$1, writePackageJson, DEV_DEPENDENCY_TYPE, PROD_DEPENDENCY_TYPE, mergeIntoExistingPackageJson } from '@form8ion/javascript-core';
5
5
  import { prompt as prompt$1 } from '@form8ion/overridable-prompts';
6
6
  import joi from 'joi';
7
7
  import { scaffold, lift as lift$3 } from '@form8ion/codecov';
8
8
  import { write as write$2 } from '@form8ion/config-file';
9
- import { info, warn, error } from '@travi/cli-messages';
9
+ import { warn, info, error } from '@travi/cli-messages';
10
10
  import * as commitConventionPlugin from '@form8ion/commit-convention';
11
11
  import { scaffold as scaffold$4 } from '@form8ion/commit-convention';
12
12
  import { execa } from 'execa';
@@ -21,12 +21,12 @@ import camelcase from 'camelcase';
21
21
  import { resolve } from 'path';
22
22
  import filedirname from 'filedirname';
23
23
  import * as huskyPlugin from '@form8ion/husky';
24
- import { scaffold as scaffold$3 } from '@form8ion/husky';
24
+ import { scaffold as scaffold$1 } from '@form8ion/husky';
25
25
  import { promises as promises$1 } from 'node:fs';
26
26
  import { stringify, parse } from 'ini';
27
- import { scaffold as scaffold$2 } from '@form8ion/prettier';
27
+ import { scaffold as scaffold$3 } from '@form8ion/prettier';
28
28
  import * as eslintPlugin from '@form8ion/eslint';
29
- import { scaffold as scaffold$1, test as test$1 } from '@form8ion/eslint';
29
+ import { scaffold as scaffold$2, test as test$1 } from '@form8ion/eslint';
30
30
  import sortProperties from 'sort-object-keys';
31
31
 
32
32
  const questionNames$1 = {
@@ -157,189 +157,357 @@ async function scaffoldUnitTesting ({projectRoot, frameworks, decisions, visibil
157
157
  ]);
158
158
  }
159
159
 
160
- function tester$4 () {
161
- return true;
162
- }
163
-
164
- function buildAllowedHostsList ({packageManager, registries = {}}) {
165
- return [
166
- ...!registries.registry ? [packageManager] : [],
167
- ...Object.values(Object.fromEntries(Object.entries(registries).filter(([scope]) => 'publish' !== scope)))
168
- ];
169
- }
170
-
171
- async function scaffoldNpm ({projectRoot}) {
172
- const [packageContents, {stdout}] = await Promise.all([
173
- promises$1.readFile(`${projectRoot}/package.json`, 'utf-8'),
174
- execa('npm', ['--version'])
175
- ]);
176
- const existingPackageJsonContents = JSON.parse(packageContents);
177
-
178
- await writePackageJson({
179
- projectRoot,
180
- config: {
181
- ...existingPackageJsonContents,
182
- packageManager: `npm@${stdout}`
183
- }
184
- });
185
- }
160
+ const scopeBasedConfigSchema = joi.object({scope: joi.string().regex(/^@[a-z0-9-]+$/i, 'scope').required()});
186
161
 
187
- function determineLockfilePathFor (packageManager) {
188
- const lockfilePaths = {
189
- [packageManagers$1.NPM]: 'package-lock.json',
190
- [packageManagers$1.YARN]: 'yarn.lock'
191
- };
162
+ const nameBasedConfigSchema = joi.object({
163
+ packageName: joi.string().required(),
164
+ name: joi.string().required()
165
+ });
192
166
 
193
- return lockfilePaths[packageManager];
194
- }
167
+ const registriesSchema = joi.object().pattern(joi.string(), joi.string().uri()).default({});
195
168
 
196
- function npmIsUsed ({projectRoot, pinnedPackageManager = ''}) {
197
- const [packageManager] = pinnedPackageManager.split('@');
169
+ const visibilitySchema = joi.string().valid('Public', 'Private').required();
198
170
 
199
- return packageManagers$1.NPM === packageManager
200
- || fileExists(`${projectRoot}/${determineLockfilePathFor(packageManagers$1.NPM)}`);
201
- }
171
+ const projectNameSchema = joi.string().regex(/^@\w*\//, {invert: true}).required();
202
172
 
203
- async function scaffoldYarn ({projectRoot}) {
204
- const [packageContents, {stdout}] = await Promise.all([
205
- promises$1.readFile(`${projectRoot}/package.json`, 'utf-8'),
206
- execa('yarn', ['--version'])
207
- ]);
208
- const existingPackageJsonContents = JSON.parse(packageContents);
173
+ const vcsSchema = joi.object({
174
+ host: joi.string().required(),
175
+ owner: joi.string().required(),
176
+ name: joi.string().required()
177
+ });
209
178
 
210
- await writePackageJson({
211
- projectRoot,
212
- config: {
213
- ...existingPackageJsonContents,
214
- packageManager: `yarn@${stdout}`
179
+ function validate(options) {
180
+ const schema = joi.object({
181
+ projectRoot: joi.string().required(),
182
+ projectName: projectNameSchema,
183
+ visibility: visibilitySchema,
184
+ license: joi.string().required(),
185
+ description: joi.string(),
186
+ pathWithinParent: joi.string(),
187
+ decisions: joi.object(),
188
+ vcs: vcsSchema,
189
+ configs: joi.object({
190
+ eslint: scopeBasedConfigSchema,
191
+ typescript: scopeBasedConfigSchema,
192
+ prettier: scopeBasedConfigSchema,
193
+ commitlint: nameBasedConfigSchema,
194
+ babelPreset: nameBasedConfigSchema,
195
+ remark: joi.string(),
196
+ registries: registriesSchema
197
+ }).default({registries: {}}),
198
+ plugins: {
199
+ unitTestFrameworks: pluginsSchema,
200
+ packageBundlers: pluginsSchema,
201
+ applicationTypes: pluginsSchema,
202
+ packageTypes: pluginsSchema,
203
+ monorepoTypes: pluginsSchema,
204
+ hosts: pluginsSchema,
205
+ ciServices: pluginsSchema
215
206
  }
216
- });
217
- }
218
-
219
- function yarnIsUsed ({projectRoot, pinnedPackageManager = ''}) {
220
- const [packageManager] = pinnedPackageManager.split('@');
207
+ }).required();
221
208
 
222
- return packageManagers$1.YARN === packageManager
223
- || fileExists(`${projectRoot}/${determineLockfilePathFor(packageManagers$1.YARN)}`);
209
+ return validateOptions(schema, options);
224
210
  }
225
211
 
226
- async function jsPackageManagerIsUsed ({projectRoot}) {
227
- const [npmFound, yarnFound] = await Promise.all([
228
- npmIsUsed({projectRoot}),
229
- yarnIsUsed({projectRoot})
230
- ]);
231
-
232
- return npmFound || yarnFound;
212
+ function buildDialectChoices ({babelPreset, typescript}) {
213
+ return [
214
+ {name: 'Common JS (no transpilation)', value: dialects$1.COMMON_JS, short: 'cjs'},
215
+ ...babelPreset ? [{name: 'Modern JavaScript (transpiled)', value: dialects$1.BABEL, short: 'modern'}] : [],
216
+ {name: 'ESM-only (no transpilation)', value: dialects$1.ESM, short: 'esm'},
217
+ ...typescript ? [{name: 'TypeScript', value: dialects$1.TYPESCRIPT, short: 'ts'}] : []
218
+ ];
233
219
  }
234
220
 
235
- async function liftCorepack () {
236
- await execa('corepack', ['use', 'npm@latest']);
221
+ function projectIsCLI(answers) {
222
+ return projectTypes$1.CLI === answers[questionNames$1.PROJECT_TYPE];
237
223
  }
238
224
 
239
- async function lifter$5 () {
240
- await liftCorepack();
241
-
242
- return {};
225
+ function projectIsPackage(answers) {
226
+ return projectTypes$1.PACKAGE === answers[questionNames$1.PROJECT_TYPE];
243
227
  }
244
228
 
245
- const scaffolders = {
246
- npm: scaffoldNpm,
247
- yarn: scaffoldYarn
248
- };
249
-
250
- function scaffoldPackageManager ({projectRoot, packageManager}) {
251
- return scaffolders[packageManager]({projectRoot});
229
+ function projectIsApplication(answers) {
230
+ return projectTypes$1.APPLICATION === answers[questionNames$1.PROJECT_TYPE];
252
231
  }
253
232
 
254
- async function resolvePackageManager ({projectRoot, packageManager}) {
255
- if (packageManager) return packageManager;
256
-
257
- const {packageManager: pinnedPackageManager} = JSON.parse(await promises$1.readFile(`${projectRoot}/package.json`, 'utf-8'));
258
-
259
- if (await npmIsUsed({projectRoot, pinnedPackageManager})) {
260
- return packageManagers$1.NPM;
261
- }
262
-
263
- if (await yarnIsUsed({projectRoot, pinnedPackageManager})) {
264
- return packageManagers$1.YARN;
265
- }
266
-
267
- throw new Error('Package-manager could not be determined');
233
+ function packageShouldBeScoped(visibility, answers) {
234
+ return 'Private' === visibility || answers[questionNames$1.SHOULD_BE_SCOPED];
268
235
  }
269
236
 
270
- var packageManagers = /*#__PURE__*/Object.freeze({
271
- __proto__: null,
272
- defineLockfilePath: determineLockfilePathFor,
273
- determineCurrent: resolvePackageManager,
274
- lift: lifter$5,
275
- scaffold: scaffoldPackageManager,
276
- test: jsPackageManagerIsUsed
277
- });
278
-
279
- const lockfileLintSupportedPackageManagers = [packageManagers$1.NPM, packageManagers$1.YARN];
280
-
281
- function lockfileLintSupports(packageManager) {
282
- return lockfileLintSupportedPackageManagers.includes(packageManager);
237
+ function willBePublishedToNpm(answers) {
238
+ return projectIsPackage(answers) || projectIsCLI(answers);
283
239
  }
284
240
 
285
- async function scaffoldLockfileLint ({projectRoot, packageManager, registries}) {
286
- if (!lockfileLintSupports(packageManager)) {
287
- throw new Error(
288
- `The ${packageManager} package manager is currently not supported by lockfile-lint. `
289
- + `Only ${lockfileLintSupportedPackageManagers.join(' and ')} are currently supported.`
290
- );
291
- }
292
-
293
- await write$2({
294
- name: 'lockfile-lint',
295
- format: fileTypes.JSON,
296
- path: projectRoot,
297
- config: {
298
- path: determineLockfilePathFor(packageManager),
299
- type: packageManager,
300
- 'validate-https': true,
301
- 'allowed-hosts': buildAllowedHostsList({packageManager, registries})
302
- }
303
- });
304
-
305
- return {
306
- dependencies: {javascript: {development: ['lockfile-lint']}},
307
- scripts: {'lint:lockfile': 'lockfile-lint'}
308
- };
241
+ function shouldBeScopedPromptShouldBePresented(answers) {
242
+ return willBePublishedToNpm(answers);
309
243
  }
310
244
 
311
- function lockfileLintIsAlreadyConfigured ({projectRoot}) {
312
- return fileExists(`${projectRoot}/.lockfile-lintrc.json`);
245
+ function scopePromptShouldBePresentedFactory(visibility) {
246
+ return answers => willBePublishedToNpm(answers) && packageShouldBeScoped(visibility, answers);
313
247
  }
314
248
 
315
- const configName = 'lockfile-lint';
316
-
317
- function read({projectRoot}) {
318
- return loadConfigFile({name: `.${configName}rc`, format: fileTypes.JSON, path: projectRoot});
249
+ function lintingPromptShouldBePresented({
250
+ [questionNames$2.UNIT_TESTS]: unitTested,
251
+ [questionNames$2.INTEGRATION_TESTS]: integrationTested
252
+ }) {
253
+ return !unitTested && !integrationTested;
319
254
  }
320
255
 
321
- function write$1({projectRoot, config}) {
322
- return write$2({name: configName, format: fileTypes.JSON, path: projectRoot, config});
323
- }
256
+ function scope(visibility) {
257
+ return input => {
258
+ if (!input && 'Private' === visibility) {
259
+ return 'Private packages must be scoped (https://docs.npmjs.com/private-modules/intro#setting-up-your-package)';
260
+ }
324
261
 
325
- async function writeNpmConfig ({projectRoot, config}) {
326
- await promises$1.writeFile(`${projectRoot}/.npmrc`, stringify(config));
262
+ return true;
263
+ };
327
264
  }
328
265
 
329
- function projectWillNotBeConsumed(projectType) {
330
- return projectTypes$1.APPLICATION === projectType || projectTypes$1.CLI === projectType;
266
+ function authorQuestions({name, email, url}) {
267
+ return [
268
+ {
269
+ name: questionNames$1.AUTHOR_NAME,
270
+ message: 'What is the author\'s name?',
271
+ default: name
272
+ },
273
+ {
274
+ name: questionNames$1.AUTHOR_EMAIL,
275
+ message: 'What is the author\'s email?',
276
+ default: email
277
+ },
278
+ {
279
+ name: questionNames$1.AUTHOR_URL,
280
+ message: 'What is the author\'s website url?',
281
+ default: url
282
+ }
283
+ ];
331
284
  }
332
285
 
333
- async function scaffoldNpmConfig ({projectRoot, projectType}) {
334
- await writeNpmConfig({
335
- projectRoot,
336
- config: {'update-notifier': false, ...projectWillNotBeConsumed(projectType) && {'save-exact': true}}
337
- });
286
+ async function prompt(
287
+ ciServices,
288
+ hosts,
289
+ visibility,
290
+ vcs,
291
+ decisions,
292
+ configs,
293
+ pathWithinParent
294
+ ) {
295
+ const npmConf$1 = npmConf();
338
296
 
339
- return {scripts: {'lint:peer': 'npm ls >/dev/null'}};
340
- }
297
+ let maybeLoggedInNpmUsername;
298
+ try {
299
+ maybeLoggedInNpmUsername = (await execa('npm', ['whoami'])).stdout;
300
+ } catch (failedExecutionResult) {
301
+ if (!decisions[questionNames$1.SCOPE]) {
302
+ warn('No logged in user found with `npm whoami`. Login with `npm login` '
303
+ + 'to use your npm account name as the package scope default.');
304
+ }
305
+ }
341
306
 
342
- function tester$3 ({projectRoot}) {
307
+ const {
308
+ [questionNames$2.UNIT_TESTS]: unitTested,
309
+ [questionNames$2.INTEGRATION_TESTS]: integrationTested,
310
+ [questionNames$1.PROJECT_TYPE]: projectType,
311
+ [questionNames$2.CI_SERVICE]: ci,
312
+ [questionNames$1.HOST]: chosenHost,
313
+ [questionNames$1.SCOPE]: scope$1,
314
+ [questionNames$1.NODE_VERSION_CATEGORY]: nodeVersionCategory,
315
+ [questionNames$1.AUTHOR_NAME]: authorName,
316
+ [questionNames$1.AUTHOR_EMAIL]: authorEmail,
317
+ [questionNames$1.AUTHOR_URL]: authorUrl,
318
+ [questionNames$1.CONFIGURE_LINTING]: configureLinting,
319
+ [questionNames$1.PROVIDE_EXAMPLE]: provideExample,
320
+ [questionNames$1.PACKAGE_MANAGER]: packageManager,
321
+ [questionNames$1.DIALECT]: dialect
322
+ } = await prompt$1([
323
+ {
324
+ name: questionNames$1.DIALECT,
325
+ message: 'Which JavaScript dialect should this project follow?',
326
+ type: 'list',
327
+ choices: buildDialectChoices(configs),
328
+ default: 'babel'
329
+ },
330
+ ...pathWithinParent ? [] : [{
331
+ name: questionNames$1.NODE_VERSION_CATEGORY,
332
+ message: 'What node.js version should be used?',
333
+ type: 'list',
334
+ choices: ['LTS', 'Latest'],
335
+ default: 'LTS'
336
+ }],
337
+ {
338
+ name: questionNames$1.PACKAGE_MANAGER,
339
+ message: 'Which package manager will be used with this project?',
340
+ type: 'list',
341
+ choices: Object.values(packageManagers$1),
342
+ default: packageManagers$1.NPM
343
+ },
344
+ {
345
+ name: questionNames$1.PROJECT_TYPE,
346
+ message: 'What type of JavaScript project is this?',
347
+ type: 'list',
348
+ choices: [...Object.values(projectTypes$1), 'Other'],
349
+ default: projectTypes$1.PACKAGE
350
+ },
351
+ ...'Private' === visibility ? [] : [{
352
+ name: questionNames$1.SHOULD_BE_SCOPED,
353
+ message: 'Should this package be scoped?',
354
+ type: 'confirm',
355
+ when: shouldBeScopedPromptShouldBePresented,
356
+ default: true
357
+ }],
358
+ {
359
+ name: questionNames$1.SCOPE,
360
+ message: 'What is the scope?',
361
+ when: scopePromptShouldBePresentedFactory(visibility),
362
+ validate: scope(visibility),
363
+ default: maybeLoggedInNpmUsername
364
+ },
365
+ ...authorQuestions({
366
+ name: npmConf$1.get('init.author.name'),
367
+ email: npmConf$1.get('init.author.email'),
368
+ url: npmConf$1.get('init.author.url')
369
+ }),
370
+ ...questions(({vcs, ciServices, pathWithinParent})),
371
+ {
372
+ name: questionNames$1.CONFIGURE_LINTING,
373
+ message: 'Will there be source code that should be linted?',
374
+ type: 'confirm',
375
+ when: lintingPromptShouldBePresented
376
+ },
377
+ {
378
+ name: questionNames$1.PROVIDE_EXAMPLE,
379
+ message: 'Should an example be provided in the README?',
380
+ type: 'confirm',
381
+ when: projectIsPackage
382
+ },
383
+ {
384
+ name: questionNames$1.HOST,
385
+ type: 'list',
386
+ message: 'Where will the application be hosted?',
387
+ when: projectIsApplication,
388
+ choices: [...Object.keys(hosts), 'Other']
389
+ }
390
+ ], decisions);
391
+
392
+ return {
393
+ tests: {unit: unitTested, integration: integrationTested},
394
+ projectType,
395
+ ci,
396
+ chosenHost,
397
+ scope: scope$1,
398
+ nodeVersionCategory,
399
+ author: {name: authorName, email: authorEmail, url: authorUrl},
400
+ configureLinting: false !== configureLinting,
401
+ provideExample,
402
+ packageManager,
403
+ dialect
404
+ };
405
+ }
406
+
407
+ function write$1 ({projectRoot, config}) {
408
+ return write$2({path: projectRoot, name: 'babel', format: fileTypes.JSON, config});
409
+ }
410
+
411
+ function loadConfig ({projectRoot}) {
412
+ return loadConfigFile({path: projectRoot, name: '.babelrc', format: fileTypes.JSON});
413
+ }
414
+
415
+ async function addIgnore ({projectRoot, ignore}) {
416
+ if (ignore) {
417
+ const existingConfig = await loadConfig({projectRoot});
418
+
419
+ await write$1({projectRoot, config: {...existingConfig, ignore: [`./${ignore}/`]}});
420
+ }
421
+ }
422
+
423
+ async function scaffoldBabel ({projectRoot, preset}) {
424
+ if (!preset) {
425
+ throw new Error('No babel preset provided. Cannot configure babel transpilation');
426
+ }
427
+
428
+ await write$1({projectRoot, config: {presets: [preset.name]}});
429
+
430
+ return {
431
+ dependencies: {javascript: {development: ['@babel/register', preset.packageName]}},
432
+ eslint: {}
433
+ };
434
+ }
435
+
436
+ async function lifter$5 ({results, projectRoot}) {
437
+ await addIgnore({ignore: results.buildDirectory, projectRoot});
438
+
439
+ return {};
440
+ }
441
+
442
+ function predicate ({projectRoot}) {
443
+ return fileExists(`${projectRoot}/.babelrc.json`);
444
+ }
445
+
446
+ async function scaffoldTypescript ({config, projectType, projectRoot, testFilenamePattern}) {
447
+ const shareableTsConfigPackage = `${config.scope}/tsconfig`;
448
+
449
+ await writeConfigFile({
450
+ path: projectRoot,
451
+ name: 'tsconfig',
452
+ format: fileTypes.JSON,
453
+ config: {
454
+ $schema: 'https://json.schemastore.org/tsconfig',
455
+ extends: shareableTsConfigPackage,
456
+ compilerOptions: {
457
+ rootDir: 'src',
458
+ ...projectTypes$1.PACKAGE === projectType && {
459
+ outDir: 'lib',
460
+ declaration: true
461
+ }
462
+ },
463
+ include: ['src/**/*.ts'],
464
+ ...testFilenamePattern && {exclude: [testFilenamePattern]}
465
+ }
466
+ });
467
+
468
+ return {
469
+ eslint: {configs: ['typescript']},
470
+ dependencies: {javascript: {development: ['typescript', shareableTsConfigPackage]}},
471
+ vcsIgnore: {files: ['tsconfig.tsbuildinfo']}
472
+ };
473
+ }
474
+
475
+ function scaffoldDialect ({dialect, projectType, projectRoot, configs, testFilenamePattern}) {
476
+ switch (dialect) {
477
+ case dialects$1.BABEL:
478
+ return scaffoldBabel({preset: configs.babelPreset, projectRoot});
479
+ case dialects$1.TYPESCRIPT:
480
+ return scaffoldTypescript({config: configs.typescript, projectType, projectRoot, testFilenamePattern});
481
+ default:
482
+ return {};
483
+ }
484
+ }
485
+
486
+ var dialects = /*#__PURE__*/Object.freeze({
487
+ __proto__: null,
488
+ lift: lifter$5,
489
+ scaffold: scaffoldDialect,
490
+ test: predicate
491
+ });
492
+
493
+ async function writeNpmConfig ({projectRoot, config}) {
494
+ await promises$1.writeFile(`${projectRoot}/.npmrc`, stringify(config));
495
+ }
496
+
497
+ function projectWillNotBeConsumed(projectType) {
498
+ return projectTypes$1.APPLICATION === projectType || projectTypes$1.CLI === projectType;
499
+ }
500
+
501
+ async function scaffoldNpmConfig ({projectRoot, projectType}) {
502
+ await writeNpmConfig({
503
+ projectRoot,
504
+ config: {'update-notifier': false, ...projectWillNotBeConsumed(projectType) && {'save-exact': true}}
505
+ });
506
+
507
+ return {scripts: {'lint:peer': 'npm ls >/dev/null'}};
508
+ }
509
+
510
+ function tester$4 ({projectRoot}) {
343
511
  return fileExists(`${projectRoot}/.npmrc`);
344
512
  }
345
513
 
@@ -368,148 +536,116 @@ var npmConfigPlugin = /*#__PURE__*/Object.freeze({
368
536
  lift: lifter$4,
369
537
  read: readNpmConfig,
370
538
  scaffold: scaffoldNpmConfig,
371
- test: tester$3,
539
+ test: tester$4,
372
540
  write: writeNpmConfig
373
541
  });
374
542
 
375
- function buildRegistriesConfig (registries = {}) {
376
- return Object.entries(registries)
377
- .filter(([scope]) => 'publish' !== scope)
378
- .reduce((acc, [scope, url]) => {
379
- if ('registry' === scope) return {...acc, registry: url};
380
-
381
- return {...acc, [`@${scope}:registry`]: url};
382
- }, {registry: 'https://registry.npmjs.org'});
383
- }
384
-
385
- async function updateRegistriesInNpmConfig(registries, projectRoot) {
386
- const registriesForNpmConfig = buildRegistriesConfig(registries);
543
+ async function scaffoldNpm ({projectRoot}) {
544
+ const [packageContents, {stdout}] = await Promise.all([
545
+ promises$1.readFile(`${projectRoot}/package.json`, 'utf-8'),
546
+ execa('npm', ['--version'])
547
+ ]);
548
+ const existingPackageJsonContents = JSON.parse(packageContents);
387
549
 
388
- await writeNpmConfig({
550
+ await writePackageJson({
389
551
  projectRoot,
390
552
  config: {
391
- ...(await readNpmConfig({projectRoot})),
392
- ...registriesForNpmConfig
553
+ ...existingPackageJsonContents,
554
+ packageManager: `npm@${stdout}`
393
555
  }
394
556
  });
395
557
  }
396
558
 
397
- async function updateRegistriesInLockfileLintConfig(projectRoot, packageManager, registries) {
398
- await write$1({
559
+ function determineLockfilePathFor (packageManager) {
560
+ const lockfilePaths = {
561
+ [packageManagers$1.NPM]: 'package-lock.json',
562
+ [packageManagers$1.YARN]: 'yarn.lock'
563
+ };
564
+
565
+ return lockfilePaths[packageManager];
566
+ }
567
+
568
+ function npmIsUsed ({projectRoot, pinnedPackageManager = ''}) {
569
+ const [packageManager] = pinnedPackageManager.split('@');
570
+
571
+ return packageManagers$1.NPM === packageManager
572
+ || fileExists(`${projectRoot}/${determineLockfilePathFor(packageManagers$1.NPM)}`);
573
+ }
574
+
575
+ async function scaffoldYarn ({projectRoot}) {
576
+ const [packageContents, {stdout}] = await Promise.all([
577
+ promises$1.readFile(`${projectRoot}/package.json`, 'utf-8'),
578
+ execa('yarn', ['--version'])
579
+ ]);
580
+ const existingPackageJsonContents = JSON.parse(packageContents);
581
+
582
+ await writePackageJson({
399
583
  projectRoot,
400
584
  config: {
401
- ...await read({projectRoot}),
402
- 'allowed-hosts': buildAllowedHostsList({packageManager, registries})
585
+ ...existingPackageJsonContents,
586
+ packageManager: `yarn@${stdout}`
403
587
  }
404
588
  });
405
589
  }
406
590
 
407
- async function lifter$3 ({projectRoot, packageManager, configs: {registries}}) {
408
- await updateRegistriesInNpmConfig(registries, projectRoot);
591
+ function yarnIsUsed ({projectRoot, pinnedPackageManager = ''}) {
592
+ const [packageManager] = pinnedPackageManager.split('@');
409
593
 
410
- if (!(await lockfileLintIsAlreadyConfigured({projectRoot}))) {
411
- return scaffoldLockfileLint({projectRoot, packageManager, registries});
412
- }
594
+ return packageManagers$1.YARN === packageManager
595
+ || fileExists(`${projectRoot}/${determineLockfilePathFor(packageManagers$1.YARN)}`);
596
+ }
413
597
 
414
- await updateRegistriesInLockfileLintConfig(projectRoot, packageManager, registries);
598
+ async function jsPackageManagerIsUsed ({projectRoot}) {
599
+ const [npmFound, yarnFound] = await Promise.all([
600
+ npmIsUsed({projectRoot}),
601
+ yarnIsUsed({projectRoot})
602
+ ]);
415
603
 
416
- return {};
604
+ return npmFound || yarnFound;
417
605
  }
418
606
 
419
- var registriesPlugin = /*#__PURE__*/Object.freeze({
420
- __proto__: null,
421
- lift: lifter$3,
422
- test: tester$4
423
- });
607
+ async function liftCorepack () {
608
+ await execa('corepack', ['use', 'npm@latest']);
609
+ }
424
610
 
425
- async function scaffoldRemark ({config, projectRoot, projectType, vcs}) {
426
- await write$2({
427
- format: fileTypes.JSON,
428
- path: projectRoot,
429
- name: 'remark',
430
- config: {
431
- settings: {
432
- listItemIndent: 'one',
433
- emphasis: '_',
434
- strong: '_',
435
- bullet: '*',
436
- incrementListMarker: false
437
- },
438
- plugins: [
439
- config,
440
- ['remark-toc', {tight: true}],
441
- ...projectTypes$1.PACKAGE === projectType ? [['remark-usage', {heading: 'example'}]] : [],
442
- ...!vcs ? [['validate-links', {repository: false}]] : []
443
- ]
444
- }
445
- });
611
+ async function lifter$3 () {
612
+ await liftCorepack();
446
613
 
447
- return deepmerge(
448
- {
449
- dependencies: {javascript: {development: [config, 'remark-cli', 'remark-toc']}},
450
- scripts: {
451
- 'lint:md': 'remark . --frail',
452
- 'generate:md': 'remark . --output'
453
- }
454
- },
455
- {...projectTypes$1.PACKAGE === projectType && {dependencies: {javascript: {development: ['remark-usage']}}}}
456
- );
614
+ return {};
457
615
  }
458
616
 
459
- async function scaffoldCodeStyle ({
460
- projectRoot,
461
- projectType,
462
- configs,
463
- vcs,
464
- configureLinting
465
- }) {
466
- return deepmerge.all(await Promise.all([
467
- configs.eslint
468
- && configureLinting
469
- && scaffold$1({projectRoot, config: configs.eslint}),
470
- scaffoldRemark({
471
- projectRoot,
472
- projectType,
473
- vcs,
474
- config: configs.remark || '@form8ion/remark-lint-preset'
475
- }),
476
- scaffold$2({projectRoot, config: configs.prettier})
477
- ].filter(Boolean)));
478
- }
617
+ const scaffolders = {
618
+ npm: scaffoldNpm,
619
+ yarn: scaffoldYarn
620
+ };
479
621
 
480
- function lifter$2 (options) {
481
- return applyEnhancers({options, enhancers: [eslintPlugin]});
622
+ function scaffoldPackageManager ({projectRoot, packageManager}) {
623
+ return scaffolders[packageManager]({projectRoot});
482
624
  }
483
625
 
484
- function tester$2 (options) {
485
- return test$1(options);
486
- }
626
+ async function resolvePackageManager ({projectRoot, packageManager}) {
627
+ if (packageManager) return packageManager;
487
628
 
488
- var codeStylePlugin = /*#__PURE__*/Object.freeze({
489
- __proto__: null,
490
- lift: lifter$2,
491
- scaffold: scaffoldCodeStyle,
492
- test: tester$2
493
- });
629
+ const {packageManager: pinnedPackageManager} = JSON.parse(await promises$1.readFile(`${projectRoot}/package.json`, 'utf-8'));
494
630
 
495
- async function test({projectRoot}) {
496
- const {engines} = JSON.parse(await promises.readFile(`${projectRoot}/package.json`, 'utf8'));
631
+ if (await npmIsUsed({projectRoot, pinnedPackageManager})) {
632
+ return packageManagers$1.NPM;
633
+ }
497
634
 
498
- return !!engines?.node;
499
- }
635
+ if (await yarnIsUsed({projectRoot, pinnedPackageManager})) {
636
+ return packageManagers$1.YARN;
637
+ }
500
638
 
501
- async function lift$1({packageDetails: {name}}) {
502
- return {
503
- dependencies: {javascript: {development: ['ls-engines']}},
504
- scripts: {'lint:engines': 'ls-engines'},
505
- badges: {consumer: {node: {img: `https://img.shields.io/node/v/${name}?logo=node.js`, text: 'node'}}}
506
- };
639
+ throw new Error('Package-manager could not be determined');
507
640
  }
508
641
 
509
- var enginesEnhancer = /*#__PURE__*/Object.freeze({
642
+ var packageManagers = /*#__PURE__*/Object.freeze({
510
643
  __proto__: null,
511
- lift: lift$1,
512
- test: test
644
+ defineLockfilePath: determineLockfilePathFor,
645
+ determineCurrent: resolvePackageManager,
646
+ lift: lifter$3,
647
+ scaffold: scaffoldPackageManager,
648
+ test: jsPackageManagerIsUsed
513
649
  });
514
650
 
515
651
  function buildDocumentationCommand (packageManager) {
@@ -522,1153 +658,772 @@ function buildDocumentationCommand (packageManager) {
522
658
  );
523
659
  }
524
660
 
525
- function getInstallationCommand(packageManager) {
526
- if (packageManagers$1.NPM === packageManager) return 'npm install';
527
- if (packageManagers$1.YARN === packageManager) return 'yarn add';
528
-
529
- throw new Error(
530
- `The ${packageManager} package manager is currently not supported. `
531
- + `Only ${Object.values(packageManagers$1).join(' and ')} are currently supported.`
532
- );
533
- }
534
-
535
- function scaffoldPackageDocumentation ({scope, packageName, packageManager, visibility, provideExample}) {
661
+ function scaffoldDocumentation ({projectTypeResults, packageManager}) {
536
662
  return {
537
- usage: `### Installation
538
- ${'Private' === visibility ? `
539
- :warning: this is a private package, so you will need to use an npm token with
540
- access to private packages under \`@${scope}\`
541
- ` : ''
542
- }
663
+ toc: `Run \`${buildDocumentationCommand(packageManager)}\` to generate a table of contents`,
664
+ ...projectTypeResults.documentation,
665
+ contributing: `### Dependencies
666
+
543
667
  \`\`\`sh
544
- $ ${getInstallationCommand(packageManager)} ${packageName}
545
- \`\`\`${provideExample
546
- ? `
668
+ $ nvm install
669
+ $ ${packageManager} install
670
+ \`\`\`
547
671
 
548
- ### Example
672
+ ### Verification
549
673
 
550
- run \`${buildDocumentationCommand(packageManager)}\` to inject the usage example`
551
- : ''
552
- }`
674
+ \`\`\`sh
675
+ $ ${packageManager} test
676
+ \`\`\``
553
677
  };
554
678
  }
555
679
 
556
- function determinePackageAccessLevelFromProjectVisibility ({projectVisibility}) {
557
- return 'Public' === projectVisibility ? 'public' : 'restricted';
558
- }
680
+ async function determineLatestVersionOf(nodeVersionCategory) {
681
+ info('Determining version of node', {level: 'secondary'});
559
682
 
560
- function defineBadges (packageName, accessLevel) {
561
- return {
562
- consumer: {
563
- ...'public' === accessLevel && {
564
- npm: {
565
- img: `https://img.shields.io/npm/v/${packageName}?logo=npm`,
566
- text: 'npm',
567
- link: `https://www.npmjs.com/package/${packageName}`
568
- }
569
- }
570
- },
571
- status: {}
572
- };
683
+ const {stdout: nvmLsOutput} = await execa(
684
+ `. ~/.nvm/nvm.sh && nvm ls-remote${('LTS' === nodeVersionCategory) ? ' --lts' : ''}`,
685
+ {shell: true}
686
+ );
687
+
688
+ const lsLines = nvmLsOutput.split('\n');
689
+ const lsLine = lsLines[lsLines.length - 2];
690
+
691
+ return lsLine.match(/(v[0-9]+)\.[0-9]+\.[0-9]+/)[1];
573
692
  }
574
693
 
575
- function enhanceSlsa ({provenance}) {
576
- if (provenance) {
577
- return {
578
- badges: {
579
- status: {
580
- slsa: {
581
- img: 'https://slsa.dev/images/gh-badge-level2.svg',
582
- url: 'https://slsa.dev',
583
- text: 'SLSA Level 2'
584
- }
585
- }
586
- }
587
- };
588
- }
694
+ function install$1(nodeVersionCategory) {
695
+ info(`Installing ${nodeVersionCategory} version of node using nvm`, {level: 'secondary'});
589
696
 
590
- return {};
697
+ const subprocess = execa('. ~/.nvm/nvm.sh && nvm install', {shell: true});
698
+ subprocess.stdout.pipe(process.stdout);
699
+ return subprocess;
591
700
  }
592
701
 
593
- async function liftProvenance ({projectRoot, packageDetails}) {
594
- const {publishConfig: {access}} = packageDetails;
702
+ async function scaffoldNodeVersion ({projectRoot, nodeVersionCategory}) {
703
+ if (!nodeVersionCategory) return undefined;
595
704
 
596
- if ('public' === access) {
597
- await mergeIntoExistingPackageJson({projectRoot, config: {publishConfig: {provenance: true}}});
705
+ const lowerCaseCategory = nodeVersionCategory.toLowerCase();
706
+ info(`Configuring ${lowerCaseCategory} version of node`);
598
707
 
599
- return enhanceSlsa({provenance: true});
600
- }
708
+ const version = await determineLatestVersionOf(nodeVersionCategory);
601
709
 
602
- return {};
603
- }
710
+ await promises$1.writeFile(`${projectRoot}/.nvmrc`, version);
604
711
 
605
- async function liftPublishable ({projectRoot, packageDetails}) {
606
- const {name: packageName, publishConfig: {access: packageAccessLevel}} = packageDetails;
607
- const homepage = `https://npm.im/${packageName}`;
712
+ await install$1(nodeVersionCategory);
608
713
 
609
- await mergeIntoExistingPackageJson({projectRoot, config: {homepage}});
714
+ return version;
715
+ }
610
716
 
611
- return deepmerge(
612
- await liftProvenance({packageDetails, projectRoot}),
613
- {
614
- homepage,
615
- dependencies: {javascript: {development: ['publint']}},
616
- scripts: {'lint:publish': 'publint --strict'},
617
- badges: defineBadges(packageName, packageAccessLevel)
618
- }
619
- );
717
+ function nvmIsUsed ({projectRoot}) {
718
+ return fileExists(`${projectRoot}/.nvmrc`);
620
719
  }
621
720
 
622
- async function scaffoldPublishable ({packageName, packageAccessLevel}) {
721
+ function buildVcsIgnoreLists (vcsIgnoreLists = {}) {
623
722
  return {
624
- badges: await defineBadges(packageName, packageAccessLevel)
723
+ files: vcsIgnoreLists.files || [],
724
+ directories: ['/node_modules/', ...vcsIgnoreLists.directories || []]
625
725
  };
626
726
  }
627
727
 
628
- async function chooseBundler ({bundlers, decisions}) {
629
- if (!Object.keys(bundlers).length) return 'Other';
630
-
631
- const answers = await prompt$1([{
632
- name: questionNames$1.PACKAGE_BUNDLER,
633
- type: 'list',
634
- message: 'Which bundler should be used?',
635
- choices: [...Object.keys(bundlers), 'Other']
636
- }], decisions);
728
+ function buildPackageName (projectName, scope) {
729
+ const name = `${scope ? `@${scope}/` : ''}${projectName}`;
637
730
 
638
- return answers[questionNames$1.PACKAGE_BUNDLER];
639
- }
731
+ const {validForNewPackages, errors} = validatePackageName(name);
640
732
 
641
- async function scaffoldBundler ({projectRoot, projectType, bundlers, dialect, decisions}) {
642
- const chosenBundler = await chooseBundler({bundlers, decisions});
733
+ if (validForNewPackages) return name;
734
+ if (1 === errors.length && errors.includes('name cannot start with a period')) return projectName.slice(1);
643
735
 
644
- return scaffoldChoice(bundlers, chosenBundler, {projectRoot, projectType, dialect});
736
+ throw new Error(`The package name ${name} is invalid:${EOL}\t* ${errors.join(`${EOL}\t* `)}`);
645
737
  }
646
738
 
647
- function determinePathToTemplateFile (fileName) {
648
- const [, __dirname] = filedirname();
649
-
650
- return resolve(__dirname, '..', 'templates', fileName);
651
- }
652
-
653
- const defaultBuildDirectory$2 = 'lib';
654
-
655
- async function createExample(projectRoot, projectName, dialect) {
656
- return promises.writeFile(
657
- `${projectRoot}/example.js`,
658
- mustache.render(
659
- await promises.readFile(determinePathToTemplateFile('example.mustache'), 'utf8'),
660
- {projectName: camelcase(projectName), esm: dialect === dialects$1.ESM}
661
- )
662
- );
663
- }
664
-
665
- async function buildDetailsForCommonJsProject({projectRoot, projectName, provideExample}) {
666
- await Promise.all([
667
- touch(`${projectRoot}/index.js`),
668
- provideExample
669
- ? promises.writeFile(`${projectRoot}/example.js`, `const ${camelcase(projectName)} = require('.');\n`)
670
- : Promise.resolve()
671
- ]);
672
-
673
- return {};
674
- }
675
-
676
- async function buildDetails ({
677
- projectRoot,
678
- projectName,
679
- visibility,
739
+ function buildPackageDetails ({
680
740
  packageName,
681
- packageBundlers,
682
741
  dialect,
683
- provideExample,
684
- decisions
742
+ license,
743
+ author,
744
+ description
685
745
  }) {
686
- if (dialects$1.COMMON_JS === dialect) return buildDetailsForCommonJsProject({projectRoot, projectName, provideExample});
687
-
688
- const pathToCreatedSrcDirectory = await mkdir(`${projectRoot}/src`);
689
- const [bundlerResults] = await Promise.all([
690
- scaffoldBundler({bundlers: packageBundlers, projectRoot, dialect, decisions, projectType: projectTypes$1.PACKAGE}),
691
- provideExample ? await createExample(projectRoot, projectName, dialect) : Promise.resolve,
692
- touch(`${pathToCreatedSrcDirectory}/index.js`)
693
- ]);
694
-
695
- return deepmerge(
696
- bundlerResults,
697
- {
698
- dependencies: {javascript: {development: ['rimraf']}},
699
- scripts: {
700
- clean: `rimraf ./${defaultBuildDirectory$2}`,
701
- prebuild: 'run-s clean',
702
- build: 'npm-run-all --print-label --parallel build:*',
703
- prepack: 'run-s build',
704
- ...provideExample && {'pregenerate:md': 'run-s build'}
705
- },
706
- vcsIgnore: {directories: [`/${defaultBuildDirectory$2}/`]},
707
- buildDirectory: defaultBuildDirectory$2,
708
- badges: {
709
- consumer: {
710
- ...'Public' === visibility && {
711
- runkit: {
712
- img: `https://badge.runkitcdn.com/${packageName}.svg`,
713
- text: `Try ${packageName} on RunKit`,
714
- link: `https://npm.runkit.com/${packageName}`
715
- }
716
- }
717
- }
718
- }
719
- }
720
- );
746
+ return {
747
+ name: packageName,
748
+ description,
749
+ license,
750
+ type: dialects$1.ESM === dialect ? 'module' : 'commonjs',
751
+ author: `${author.name}${author.email ? ` <${author.email}>` : ''}${author.url ? ` (${author.url})` : ''}`,
752
+ scripts: {}
753
+ };
721
754
  }
722
755
 
723
- async function scaffoldPackageType ({
756
+ async function scaffoldPackage ({
724
757
  projectRoot,
725
758
  projectName,
726
- packageName,
727
- packageManager,
728
- visibility,
729
759
  scope,
730
- packageBundlers,
731
- decisions,
732
760
  dialect,
733
- provideExample,
734
- publishRegistry
761
+ license,
762
+ author,
763
+ description
735
764
  }) {
736
- info('Scaffolding Package Details');
765
+ info('Configuring package.json');
737
766
 
738
- const packageAccessLevel = determinePackageAccessLevelFromProjectVisibility({projectVisibility: visibility});
739
- const [detailsForBuild, publishableResults] = await Promise.all([
740
- buildDetails({
741
- projectRoot,
742
- projectName,
743
- packageBundlers,
744
- visibility,
767
+ const packageName = buildPackageName(projectName, scope);
768
+
769
+ await writePackageJson({
770
+ projectRoot,
771
+ config: await buildPackageDetails({
745
772
  packageName,
746
773
  dialect,
747
- provideExample,
748
- decisions
749
- }),
750
- scaffoldPublishable({packageName, packageAccessLevel}),
751
- mergeIntoExistingPackageJson({
752
- projectRoot,
753
- config: {
754
- files: ['example.js', ...dialects$1.COMMON_JS === dialect ? ['index.js'] : ['lib/']],
755
- publishConfig: {
756
- access: packageAccessLevel,
757
- ...publishRegistry && {registry: publishRegistry}
758
- },
759
- sideEffects: false,
760
- ...'Public' === visibility && {runkitExampleFilename: './example.js'},
761
- ...dialects$1.BABEL === dialect && {
762
- main: './lib/index.js',
763
- module: './lib/index.mjs',
764
- exports: {
765
- module: './lib/index.mjs',
766
- require: './lib/index.js',
767
- import: './lib/index.mjs'
768
- }
769
- },
770
- ...dialects$1.ESM === dialect && {
771
- main: './lib/index.js',
772
- exports: './lib/index.js'
773
- },
774
- ...dialects$1.TYPESCRIPT === dialect && {
775
- main: './lib/index.js',
776
- module: './lib/index.mjs',
777
- types: './lib/index.d.ts',
778
- exports: {
779
- types: './lib/index.d.ts',
780
- require: './lib/index.js',
781
- import: './lib/index.mjs'
782
- }
783
- }
784
- }
774
+ license,
775
+ author,
776
+ description
785
777
  })
786
- ]);
778
+ });
787
779
 
788
- return deepmerge.all([
789
- publishableResults,
790
- {
791
- documentation: scaffoldPackageDocumentation({packageName, visibility, scope, packageManager, provideExample}),
792
- nextSteps: [
793
- {summary: 'Add the appropriate `save` flag to the installation instructions in the README'},
794
- {summary: 'Define supported node.js versions as `engines.node` in the `package.json` file'},
795
- {summary: 'Publish pre-release versions to npm until package is stable enough to publish v1.0.0'}
796
- ]
797
- },
798
- detailsForBuild
799
- ]);
780
+ return {packageName};
800
781
  }
801
782
 
802
- function liftPackage$1 ({projectRoot, packageDetails}) {
803
- return liftPublishable({projectRoot, packageDetails});
783
+ function sortPackageProperties (packageContents) {
784
+ return sortProperties(
785
+ packageContents,
786
+ [
787
+ 'name',
788
+ 'description',
789
+ 'license',
790
+ 'version',
791
+ 'private',
792
+ 'type',
793
+ 'engines',
794
+ 'author',
795
+ 'contributors',
796
+ 'repository',
797
+ 'bugs',
798
+ 'homepage',
799
+ 'funding',
800
+ 'keywords',
801
+ 'runkitExampleFilename',
802
+ 'exports',
803
+ 'bin',
804
+ 'main',
805
+ 'module',
806
+ 'types',
807
+ 'sideEffects',
808
+ 'scripts',
809
+ 'files',
810
+ 'publishConfig',
811
+ 'packageManager',
812
+ 'config',
813
+ 'dependencies',
814
+ 'devDependencies',
815
+ 'peerDependencies'
816
+ ]
817
+ );
804
818
  }
805
819
 
806
- async function isPackage ({packageDetails: {exports, publishConfig, bin}}) {
807
- return !!exports || (!!publishConfig && !bin);
820
+ function defineVcsHostDetails (vcs, pathWithinParent) {
821
+ return vcs && 'github' === vcs.host && {
822
+ repository: pathWithinParent
823
+ ? {
824
+ type: 'git',
825
+ url: `https://github.com/${vcs.owner}/${vcs.name}.git`,
826
+ directory: pathWithinParent
827
+ }
828
+ : `${vcs.owner}/${vcs.name}`,
829
+ bugs: `https://github.com/${vcs.owner}/${vcs.name}/issues`
830
+ };
808
831
  }
809
832
 
810
- const defaultBuildDirectory$1 = 'public';
811
-
812
- async function scaffoldApplicationType ({projectRoot}) {
813
- info('Scaffolding Application Details');
814
-
815
- await mergeIntoExistingPackageJson({projectRoot, config: {private: true}});
816
-
817
- const buildDirectory = defaultBuildDirectory$1;
833
+ const details = {
834
+ [packageManagers$1.NPM]: {
835
+ installationCommand: 'install',
836
+ installationFlags: {
837
+ [DEV_DEPENDENCY_TYPE]: `save-${DEV_DEPENDENCY_TYPE}`,
838
+ [PROD_DEPENDENCY_TYPE]: `save-${PROD_DEPENDENCY_TYPE}`,
839
+ exact: 'save-exact'
840
+ }
841
+ },
842
+ [packageManagers$1.YARN]: {
843
+ installationCommand: 'add',
844
+ installationFlags: {
845
+ [DEV_DEPENDENCY_TYPE]: DEV_DEPENDENCY_TYPE,
846
+ [PROD_DEPENDENCY_TYPE]: PROD_DEPENDENCY_TYPE,
847
+ exact: 'exact'
848
+ }
849
+ }
850
+ };
818
851
 
819
- return {
820
- scripts: {
821
- clean: `rimraf ./${buildDirectory}`,
822
- start: `node ./${buildDirectory}/index.js`,
823
- prebuild: 'run-s clean'
824
- },
825
- dependencies: {javascript: {development: ['rimraf']}},
826
- vcsIgnore: {files: ['.env'], directories: [`/${buildDirectory}/`]},
827
- buildDirectory,
828
- nextSteps: []
829
- };
852
+ function getInstallationCommandFor(manager) {
853
+ return details[manager].installationCommand;
830
854
  }
831
855
 
832
- function isApplication ({packageDetails}) {
833
- return !!packageDetails.private;
856
+ function getDependencyTypeFlag(manager, type) {
857
+ return details[manager].installationFlags[type];
834
858
  }
835
859
 
836
- async function scaffoldMonorepoType ({projectRoot}) {
837
- info('Scaffolding Monorepo Details');
860
+ function getExactFlag(manager) {
861
+ return details[manager].installationFlags.exact;
862
+ }
838
863
 
839
- await mergeIntoExistingPackageJson({projectRoot, config: {private: true}});
864
+ async function install (dependencies, dependenciesType, projectRoot, packageManager = packageManagers$1.NPM) {
865
+ if (dependencies.length) {
866
+ info(`Installing ${dependenciesType} dependencies`, {level: 'secondary'});
840
867
 
841
- return {
842
- nextSteps: [{
843
- summary: 'Add packages to your new monorepo',
844
- description: 'Leverage [@form8ion/add-package-to-monorepo](https://npm.im/@form8ion/add-package-to-monorepo)'
845
- + ' to scaffold new packages into your new monorepo'
846
- }]
847
- };
868
+ await execa(
869
+ `. ~/.nvm/nvm.sh && nvm use && ${packageManager} ${
870
+ getInstallationCommandFor(packageManager)
871
+ } ${[...new Set(dependencies)].join(' ')} --${getDependencyTypeFlag(packageManager, dependenciesType)}${
872
+ DEV_DEPENDENCY_TYPE === dependenciesType ? ` --${getExactFlag(packageManager)}` : ''
873
+ }`,
874
+ {shell: true, cwd: projectRoot}
875
+ );
876
+ } else warn(`No ${dependenciesType} dependencies to install`);
848
877
  }
849
878
 
850
- const defaultBuildDirectory = 'bin';
879
+ async function processDependencies ({dependencies = {}, devDependencies, projectRoot, packageManager}) {
880
+ info('Processing dependencies');
851
881
 
852
- async function scaffoldCliType ({
853
- packageName,
854
- visibility,
855
- projectRoot,
856
- dialect,
857
- publishRegistry,
858
- decisions,
859
- packageBundlers
860
- }) {
861
- const packageAccessLevel = determinePackageAccessLevelFromProjectVisibility({projectVisibility: visibility});
862
- const [bundlerResults, publishableResults] = await Promise.all([
863
- scaffoldBundler({bundlers: packageBundlers, projectRoot, dialect, decisions, projectType: projectTypes$1.CLI}),
864
- scaffoldPublishable({packageName, packageAccessLevel}),
865
- mergeIntoExistingPackageJson({
866
- projectRoot,
867
- config: {
868
- bin: {},
869
- files: [`${defaultBuildDirectory}/`],
870
- publishConfig: {
871
- access: packageAccessLevel,
872
- ...publishRegistry && {registry: publishRegistry}
873
- }
874
- }
875
- })
876
- ]);
882
+ if (Array.isArray(devDependencies)) {
883
+ throw new Error(
884
+ `devDependencies provided as: ${devDependencies}. Instead, provide under dependencies.javascript.development`
885
+ );
886
+ }
877
887
 
878
- return deepmerge.all([
879
- publishableResults,
880
- bundlerResults,
881
- {
882
- scripts: {
883
- clean: `rimraf ./${defaultBuildDirectory}`,
884
- prebuild: 'run-s clean',
885
- build: 'npm-run-all --print-label --parallel build:*',
886
- prepack: 'run-s build'
887
- },
888
- dependencies: {javascript: {production: ['update-notifier'], development: ['rimraf']}},
889
- vcsIgnore: {files: [], directories: [`/${defaultBuildDirectory}/`]},
890
- buildDirectory: defaultBuildDirectory,
891
- nextSteps: [{summary: 'Define supported node.js versions as `engines.node` in the `package.json` file'}]
892
- }
893
- ]);
894
- }
888
+ if (Array.isArray(dependencies)) {
889
+ throw new Error(`Expected dependencies to be an object. Instead received: ${dependencies}`);
890
+ }
895
891
 
896
- async function isCli ({packageDetails: {bin}}) {
897
- return !!bin;
898
- }
892
+ const {javascript: {production = [], development = []} = {}} = dependencies;
899
893
 
900
- function liftCli ({projectRoot, packageDetails}) {
901
- return liftPublishable({projectRoot, packageDetails});
894
+ try {
895
+ await install(production, PROD_DEPENDENCY_TYPE, projectRoot, packageManager);
896
+ await install(development, DEV_DEPENDENCY_TYPE, projectRoot, packageManager);
897
+ } catch (e) {
898
+ error('Failed to update dependencies');
899
+ error(e, {level: 'secondary'});
900
+ }
902
901
  }
903
902
 
904
- async function scaffoldProjectType ({
905
- projectType,
906
- projectRoot,
907
- projectName,
908
- packageName,
909
- packageManager,
910
- visibility,
911
- packageBundlers,
912
- scope,
913
- decisions,
914
- dialect,
915
- provideExample,
916
- publishRegistry
917
- }) {
918
- switch (projectType) {
919
- case projectTypes$1.PACKAGE:
920
- return scaffoldPackageType({
921
- projectRoot,
922
- projectName,
923
- packageName,
924
- packageManager,
925
- visibility,
926
- scope,
927
- packageBundlers,
928
- decisions,
929
- dialect,
930
- provideExample,
931
- publishRegistry
932
- });
933
- case projectTypes$1.APPLICATION:
934
- return scaffoldApplicationType({projectRoot});
935
- case projectTypes$1.CLI:
936
- return scaffoldCliType({
937
- packageName,
938
- visibility,
939
- projectRoot,
940
- dialect,
941
- publishRegistry,
942
- decisions,
943
- packageBundlers
944
- });
945
- case projectTypes$1.MONOREPO:
946
- return scaffoldMonorepoType({projectRoot});
947
- case 'Other':
948
- return {};
949
- default:
950
- throw new Error(`The project-type of ${projectType} is invalid`);
951
- }
903
+ function projectWillBeTested(scripts) {
904
+ return Object.keys(scripts).find(scriptName => scriptName.startsWith('test:'));
952
905
  }
953
906
 
954
- async function tester$1 ({projectRoot, packageDetails}) {
955
- return await isPackage({projectRoot, packageDetails})
956
- || await isCli({projectRoot, packageDetails})
957
- || isApplication({projectRoot, packageDetails});
907
+ function projectShouldBeBuiltForVerification(scripts) {
908
+ return 'run-s build' === scripts['pregenerate:md'];
958
909
  }
959
910
 
960
- function vcsRepositoryHostedOnGithub(vcs) {
961
- return vcs && 'github' === vcs.host;
911
+ function updateTestScript (scripts) {
912
+ return {
913
+ ...scripts,
914
+ test: `npm-run-all --print-label${
915
+ projectShouldBeBuiltForVerification(scripts) ? ' build' : ''
916
+ } --parallel lint:*${
917
+ projectWillBeTested(scripts) ? ' --parallel test:*' : ''
918
+ }`
919
+ };
962
920
  }
963
921
 
964
- async function lifter$1 ({projectRoot, packageDetails, vcs}) {
965
- if (await isPackage({projectRoot, packageDetails})) return liftPackage$1({projectRoot, packageDetails});
966
- if (await isCli({projectRoot, packageDetails})) return liftCli({projectRoot, packageDetails});
922
+ function liftScripts ({existingScripts, scripts}) {
923
+ return {
924
+ scripts: updateTestScript({...existingScripts, ...scripts}),
925
+ dependencies: {javascript: {development: ['npm-run-all2']}}
926
+ };
927
+ }
967
928
 
968
- let homepage;
929
+ async function liftPackage$1 ({
930
+ projectRoot,
931
+ scripts,
932
+ tags,
933
+ dependencies,
934
+ devDependencies,
935
+ packageManager,
936
+ vcs,
937
+ pathWithinParent
938
+ }) {
939
+ info('Updating `package.json`', {level: 'secondary'});
969
940
 
970
- if (vcsRepositoryHostedOnGithub(vcs)) {
971
- homepage = `https://github.com/${vcs.owner}/${vcs.name}#readme`;
941
+ const existingPackageJsonContents = JSON.parse(await promises$1.readFile(`${projectRoot}/package.json`, 'utf-8'));
942
+ const {scripts: liftedScripts, dependencies: scriptDependencies} = liftScripts({
943
+ existingScripts: existingPackageJsonContents.scripts,
944
+ scripts
945
+ });
972
946
 
973
- await mergeIntoExistingPackageJson({projectRoot, config: {homepage}});
974
- }
947
+ await writePackageJson({
948
+ projectRoot,
949
+ config: sortPackageProperties({
950
+ ...existingPackageJsonContents,
951
+ ...defineVcsHostDetails(vcs, pathWithinParent),
952
+ scripts: liftedScripts,
953
+ ...tags && {
954
+ keywords: existingPackageJsonContents.keywords ? [...existingPackageJsonContents.keywords, ...tags] : tags
955
+ }
956
+ })
957
+ });
975
958
 
976
- return {homepage};
959
+ await processDependencies({
960
+ dependencies: deepmerge(dependencies, scriptDependencies),
961
+ devDependencies,
962
+ projectRoot,
963
+ packageManager
964
+ });
977
965
  }
978
966
 
979
- var projectTypes = /*#__PURE__*/Object.freeze({
980
- __proto__: null,
981
- lift: lifter$1,
982
- scaffold: scaffoldProjectType,
983
- test: tester$1
984
- });
967
+ function getInstallationCommand(packageManager) {
968
+ if (packageManagers$1.NPM === packageManager) return 'npm install';
969
+ if (packageManagers$1.YARN === packageManager) return 'yarn add';
985
970
 
986
- function write ({projectRoot, config}) {
987
- return write$2({path: projectRoot, name: 'babel', format: fileTypes.JSON, config});
971
+ throw new Error(
972
+ `The ${packageManager} package manager is currently not supported. `
973
+ + `Only ${Object.values(packageManagers$1).join(' and ')} are currently supported.`
974
+ );
988
975
  }
989
976
 
990
- function loadConfig ({projectRoot}) {
991
- return loadConfigFile({path: projectRoot, name: '.babelrc', format: fileTypes.JSON});
977
+ function scaffoldPackageDocumentation ({scope, packageName, packageManager, visibility, provideExample}) {
978
+ return {
979
+ usage: `### Installation
980
+ ${'Private' === visibility ? `
981
+ :warning: this is a private package, so you will need to use an npm token with
982
+ access to private packages under \`@${scope}\`
983
+ ` : ''
992
984
  }
985
+ \`\`\`sh
986
+ $ ${getInstallationCommand(packageManager)} ${packageName}
987
+ \`\`\`${provideExample
988
+ ? `
993
989
 
994
- async function addIgnore ({projectRoot, ignore}) {
995
- if (ignore) {
996
- const existingConfig = await loadConfig({projectRoot});
990
+ ### Example
997
991
 
998
- await write({projectRoot, config: {...existingConfig, ignore: [`./${ignore}/`]}});
999
- }
992
+ run \`${buildDocumentationCommand(packageManager)}\` to inject the usage example`
993
+ : ''
994
+ }`
995
+ };
1000
996
  }
1001
997
 
1002
- async function scaffoldBabel ({projectRoot, preset}) {
1003
- if (!preset) {
1004
- throw new Error('No babel preset provided. Cannot configure babel transpilation');
1005
- }
1006
-
1007
- await write({projectRoot, config: {presets: [preset.name]}});
998
+ function determinePackageAccessLevelFromProjectVisibility ({projectVisibility}) {
999
+ return 'Public' === projectVisibility ? 'public' : 'restricted';
1000
+ }
1008
1001
 
1002
+ function defineBadges (packageName, accessLevel) {
1009
1003
  return {
1010
- dependencies: {javascript: {development: ['@babel/register', preset.packageName]}},
1011
- eslint: {}
1004
+ consumer: {
1005
+ ...'public' === accessLevel && {
1006
+ npm: {
1007
+ img: `https://img.shields.io/npm/v/${packageName}?logo=npm`,
1008
+ text: 'npm',
1009
+ link: `https://www.npmjs.com/package/${packageName}`
1010
+ }
1011
+ }
1012
+ },
1013
+ status: {}
1012
1014
  };
1013
1015
  }
1014
1016
 
1015
- async function lifter ({results, projectRoot}) {
1016
- await addIgnore({ignore: results.buildDirectory, projectRoot});
1017
+ function enhanceSlsa ({provenance}) {
1018
+ if (provenance) {
1019
+ return {
1020
+ badges: {
1021
+ status: {
1022
+ slsa: {
1023
+ img: 'https://slsa.dev/images/gh-badge-level2.svg',
1024
+ url: 'https://slsa.dev',
1025
+ text: 'SLSA Level 2'
1026
+ }
1027
+ }
1028
+ }
1029
+ };
1030
+ }
1017
1031
 
1018
1032
  return {};
1019
1033
  }
1020
1034
 
1021
- function predicate ({projectRoot}) {
1022
- return fileExists(`${projectRoot}/.babelrc.json`);
1035
+ async function liftProvenance ({projectRoot, packageDetails}) {
1036
+ const {publishConfig: {access}} = packageDetails;
1037
+
1038
+ if ('public' === access) {
1039
+ await mergeIntoExistingPackageJson({projectRoot, config: {publishConfig: {provenance: true}}});
1040
+
1041
+ return enhanceSlsa({provenance: true});
1042
+ }
1043
+
1044
+ return {};
1023
1045
  }
1024
1046
 
1025
- async function scaffoldTypescript ({config, projectType, projectRoot, testFilenamePattern}) {
1026
- const shareableTsConfigPackage = `${config.scope}/tsconfig`;
1047
+ async function liftPublishable ({projectRoot, packageDetails}) {
1048
+ const {name: packageName, publishConfig: {access: packageAccessLevel}} = packageDetails;
1049
+ const homepage = `https://npm.im/${packageName}`;
1027
1050
 
1028
- await writeConfigFile({
1029
- path: projectRoot,
1030
- name: 'tsconfig',
1031
- format: fileTypes.JSON,
1032
- config: {
1033
- $schema: 'https://json.schemastore.org/tsconfig',
1034
- extends: shareableTsConfigPackage,
1035
- compilerOptions: {
1036
- rootDir: 'src',
1037
- ...projectTypes$1.PACKAGE === projectType && {
1038
- outDir: 'lib',
1039
- declaration: true
1040
- }
1041
- },
1042
- include: ['src/**/*.ts'],
1043
- ...testFilenamePattern && {exclude: [testFilenamePattern]}
1051
+ await mergeIntoExistingPackageJson({projectRoot, config: {homepage}});
1052
+
1053
+ return deepmerge(
1054
+ await liftProvenance({packageDetails, projectRoot}),
1055
+ {
1056
+ homepage,
1057
+ dependencies: {javascript: {development: ['publint']}},
1058
+ scripts: {'lint:publish': 'publint --strict'},
1059
+ badges: defineBadges(packageName, packageAccessLevel)
1044
1060
  }
1045
- });
1061
+ );
1062
+ }
1046
1063
 
1064
+ async function scaffoldPublishable ({packageName, packageAccessLevel}) {
1047
1065
  return {
1048
- eslint: {configs: ['typescript']},
1049
- dependencies: {javascript: {development: ['typescript', shareableTsConfigPackage]}},
1050
- vcsIgnore: {files: ['tsconfig.tsbuildinfo']}
1066
+ badges: await defineBadges(packageName, packageAccessLevel)
1051
1067
  };
1052
1068
  }
1053
1069
 
1054
- function scaffoldDialect ({dialect, projectType, projectRoot, configs, testFilenamePattern}) {
1055
- switch (dialect) {
1056
- case dialects$1.BABEL:
1057
- return scaffoldBabel({preset: configs.babelPreset, projectRoot});
1058
- case dialects$1.TYPESCRIPT:
1059
- return scaffoldTypescript({config: configs.typescript, projectType, projectRoot, testFilenamePattern});
1060
- default:
1061
- return {};
1062
- }
1070
+ async function chooseBundler ({bundlers, decisions}) {
1071
+ if (!Object.keys(bundlers).length) return 'Other';
1072
+
1073
+ const answers = await prompt$1([{
1074
+ name: questionNames$1.PACKAGE_BUNDLER,
1075
+ type: 'list',
1076
+ message: 'Which bundler should be used?',
1077
+ choices: [...Object.keys(bundlers), 'Other']
1078
+ }], decisions);
1079
+
1080
+ return answers[questionNames$1.PACKAGE_BUNDLER];
1063
1081
  }
1064
1082
 
1065
- var dialects = /*#__PURE__*/Object.freeze({
1066
- __proto__: null,
1067
- lift: lifter,
1068
- scaffold: scaffoldDialect,
1069
- test: predicate
1070
- });
1083
+ async function scaffoldBundler ({projectRoot, projectType, bundlers, dialect, decisions}) {
1084
+ const chosenBundler = await chooseBundler({bundlers, decisions});
1071
1085
 
1072
- function buildPackageName (projectName, scope) {
1073
- const name = `${scope ? `@${scope}/` : ''}${projectName}`;
1086
+ return scaffoldChoice(bundlers, chosenBundler, {projectRoot, projectType, dialect});
1087
+ }
1074
1088
 
1075
- const {validForNewPackages, errors} = validatePackageName(name);
1089
+ function determinePathToTemplateFile (fileName) {
1090
+ const [, __dirname] = filedirname();
1076
1091
 
1077
- if (validForNewPackages) return name;
1078
- if (1 === errors.length && errors.includes('name cannot start with a period')) return projectName.slice(1);
1092
+ return resolve(__dirname, '..', 'templates', fileName);
1093
+ }
1079
1094
 
1080
- throw new Error(`The package name ${name} is invalid:${EOL}\t* ${errors.join(`${EOL}\t* `)}`);
1095
+ const defaultBuildDirectory$2 = 'lib';
1096
+
1097
+ async function createExample(projectRoot, projectName, dialect) {
1098
+ return promises.writeFile(
1099
+ `${projectRoot}/example.js`,
1100
+ mustache.render(
1101
+ await promises.readFile(determinePathToTemplateFile('example.mustache'), 'utf8'),
1102
+ {projectName: camelcase(projectName), esm: dialect === dialects$1.ESM}
1103
+ )
1104
+ );
1081
1105
  }
1082
1106
 
1083
- function buildPackageDetails ({
1107
+ async function buildDetailsForCommonJsProject({projectRoot, projectName, provideExample}) {
1108
+ await Promise.all([
1109
+ touch(`${projectRoot}/index.js`),
1110
+ provideExample
1111
+ ? promises.writeFile(`${projectRoot}/example.js`, `const ${camelcase(projectName)} = require('.');\n`)
1112
+ : Promise.resolve()
1113
+ ]);
1114
+
1115
+ return {};
1116
+ }
1117
+
1118
+ async function buildDetails ({
1119
+ projectRoot,
1120
+ projectName,
1121
+ visibility,
1084
1122
  packageName,
1123
+ packageBundlers,
1085
1124
  dialect,
1086
- license,
1087
- author,
1088
- description
1125
+ provideExample,
1126
+ decisions
1089
1127
  }) {
1090
- return {
1091
- name: packageName,
1092
- description,
1093
- license,
1094
- type: dialects$1.ESM === dialect ? 'module' : 'commonjs',
1095
- author: `${author.name}${author.email ? ` <${author.email}>` : ''}${author.url ? ` (${author.url})` : ''}`,
1096
- scripts: {}
1097
- };
1128
+ if (dialects$1.COMMON_JS === dialect) return buildDetailsForCommonJsProject({projectRoot, projectName, provideExample});
1129
+
1130
+ const pathToCreatedSrcDirectory = await mkdir(`${projectRoot}/src`);
1131
+ const [bundlerResults] = await Promise.all([
1132
+ scaffoldBundler({bundlers: packageBundlers, projectRoot, dialect, decisions, projectType: projectTypes$1.PACKAGE}),
1133
+ provideExample ? await createExample(projectRoot, projectName, dialect) : Promise.resolve,
1134
+ touch(`${pathToCreatedSrcDirectory}/index.js`)
1135
+ ]);
1136
+
1137
+ return deepmerge(
1138
+ bundlerResults,
1139
+ {
1140
+ dependencies: {javascript: {development: ['rimraf']}},
1141
+ scripts: {
1142
+ clean: `rimraf ./${defaultBuildDirectory$2}`,
1143
+ prebuild: 'run-s clean',
1144
+ build: 'npm-run-all --print-label --parallel build:*',
1145
+ prepack: 'run-s build',
1146
+ ...provideExample && {'pregenerate:md': 'run-s build'}
1147
+ },
1148
+ vcsIgnore: {directories: [`/${defaultBuildDirectory$2}/`]},
1149
+ buildDirectory: defaultBuildDirectory$2,
1150
+ badges: {
1151
+ consumer: {
1152
+ ...'Public' === visibility && {
1153
+ runkit: {
1154
+ img: `https://badge.runkitcdn.com/${packageName}.svg`,
1155
+ text: `Try ${packageName} on RunKit`,
1156
+ link: `https://npm.runkit.com/${packageName}`
1157
+ }
1158
+ }
1159
+ }
1160
+ }
1161
+ }
1162
+ );
1098
1163
  }
1099
1164
 
1100
- async function scaffoldPackage ({
1165
+ async function scaffoldPackageType ({
1101
1166
  projectRoot,
1102
1167
  projectName,
1168
+ packageName,
1169
+ packageManager,
1170
+ visibility,
1103
1171
  scope,
1172
+ packageBundlers,
1173
+ decisions,
1104
1174
  dialect,
1105
- license,
1106
- author,
1107
- description
1175
+ provideExample,
1176
+ publishRegistry
1108
1177
  }) {
1109
- info('Configuring package.json');
1110
-
1111
- const packageName = buildPackageName(projectName, scope);
1178
+ info('Scaffolding Package Details');
1112
1179
 
1113
- await writePackageJson({
1114
- projectRoot,
1115
- config: await buildPackageDetails({
1180
+ const packageAccessLevel = determinePackageAccessLevelFromProjectVisibility({projectVisibility: visibility});
1181
+ const [detailsForBuild, publishableResults] = await Promise.all([
1182
+ buildDetails({
1183
+ projectRoot,
1184
+ projectName,
1185
+ packageBundlers,
1186
+ visibility,
1116
1187
  packageName,
1117
1188
  dialect,
1118
- license,
1119
- author,
1120
- description
1189
+ provideExample,
1190
+ decisions
1191
+ }),
1192
+ scaffoldPublishable({packageName, packageAccessLevel}),
1193
+ mergeIntoExistingPackageJson({
1194
+ projectRoot,
1195
+ config: {
1196
+ files: ['example.js', ...dialects$1.COMMON_JS === dialect ? ['index.js'] : ['lib/']],
1197
+ publishConfig: {
1198
+ access: packageAccessLevel,
1199
+ ...publishRegistry && {registry: publishRegistry}
1200
+ },
1201
+ sideEffects: false,
1202
+ ...'Public' === visibility && {runkitExampleFilename: './example.js'},
1203
+ ...dialects$1.BABEL === dialect && {
1204
+ main: './lib/index.js',
1205
+ module: './lib/index.mjs',
1206
+ exports: {
1207
+ module: './lib/index.mjs',
1208
+ require: './lib/index.js',
1209
+ import: './lib/index.mjs'
1210
+ }
1211
+ },
1212
+ ...dialects$1.ESM === dialect && {
1213
+ main: './lib/index.js',
1214
+ exports: './lib/index.js'
1215
+ },
1216
+ ...dialects$1.TYPESCRIPT === dialect && {
1217
+ main: './lib/index.js',
1218
+ module: './lib/index.mjs',
1219
+ types: './lib/index.d.ts',
1220
+ exports: {
1221
+ types: './lib/index.d.ts',
1222
+ require: './lib/index.js',
1223
+ import: './lib/index.mjs'
1224
+ }
1225
+ }
1226
+ }
1121
1227
  })
1122
- });
1123
-
1124
- return {packageName};
1125
- }
1228
+ ]);
1126
1229
 
1127
- function sortPackageProperties (packageContents) {
1128
- return sortProperties(
1129
- packageContents,
1130
- [
1131
- 'name',
1132
- 'description',
1133
- 'license',
1134
- 'version',
1135
- 'private',
1136
- 'type',
1137
- 'engines',
1138
- 'author',
1139
- 'contributors',
1140
- 'repository',
1141
- 'bugs',
1142
- 'homepage',
1143
- 'funding',
1144
- 'keywords',
1145
- 'runkitExampleFilename',
1146
- 'exports',
1147
- 'bin',
1148
- 'main',
1149
- 'module',
1150
- 'types',
1151
- 'sideEffects',
1152
- 'scripts',
1153
- 'files',
1154
- 'publishConfig',
1155
- 'packageManager',
1156
- 'config',
1157
- 'dependencies',
1158
- 'devDependencies',
1159
- 'peerDependencies'
1160
- ]
1161
- );
1162
- }
1163
-
1164
- function defineVcsHostDetails (vcs, pathWithinParent) {
1165
- return vcs && 'github' === vcs.host && {
1166
- repository: pathWithinParent
1167
- ? {
1168
- type: 'git',
1169
- url: `https://github.com/${vcs.owner}/${vcs.name}.git`,
1170
- directory: pathWithinParent
1171
- }
1172
- : `${vcs.owner}/${vcs.name}`,
1173
- bugs: `https://github.com/${vcs.owner}/${vcs.name}/issues`
1174
- };
1175
- }
1176
-
1177
- const details = {
1178
- [packageManagers$1.NPM]: {
1179
- installationCommand: 'install',
1180
- installationFlags: {
1181
- [DEV_DEPENDENCY_TYPE]: `save-${DEV_DEPENDENCY_TYPE}`,
1182
- [PROD_DEPENDENCY_TYPE]: `save-${PROD_DEPENDENCY_TYPE}`,
1183
- exact: 'save-exact'
1184
- }
1185
- },
1186
- [packageManagers$1.YARN]: {
1187
- installationCommand: 'add',
1188
- installationFlags: {
1189
- [DEV_DEPENDENCY_TYPE]: DEV_DEPENDENCY_TYPE,
1190
- [PROD_DEPENDENCY_TYPE]: PROD_DEPENDENCY_TYPE,
1191
- exact: 'exact'
1192
- }
1193
- }
1194
- };
1195
-
1196
- function getInstallationCommandFor(manager) {
1197
- return details[manager].installationCommand;
1198
- }
1199
-
1200
- function getDependencyTypeFlag(manager, type) {
1201
- return details[manager].installationFlags[type];
1230
+ return deepmerge.all([
1231
+ publishableResults,
1232
+ {
1233
+ documentation: scaffoldPackageDocumentation({packageName, visibility, scope, packageManager, provideExample}),
1234
+ nextSteps: [
1235
+ {summary: 'Add the appropriate `save` flag to the installation instructions in the README'},
1236
+ {summary: 'Define supported node.js versions as `engines.node` in the `package.json` file'},
1237
+ {summary: 'Publish pre-release versions to npm until package is stable enough to publish v1.0.0'}
1238
+ ]
1239
+ },
1240
+ detailsForBuild
1241
+ ]);
1202
1242
  }
1203
1243
 
1204
- function getExactFlag(manager) {
1205
- return details[manager].installationFlags.exact;
1244
+ function liftPackage ({projectRoot, packageDetails}) {
1245
+ return liftPublishable({projectRoot, packageDetails});
1206
1246
  }
1207
1247
 
1208
- async function install$1 (dependencies, dependenciesType, projectRoot, packageManager = packageManagers$1.NPM) {
1209
- if (dependencies.length) {
1210
- info(`Installing ${dependenciesType} dependencies`, {level: 'secondary'});
1211
-
1212
- await execa(
1213
- `. ~/.nvm/nvm.sh && nvm use && ${packageManager} ${
1214
- getInstallationCommandFor(packageManager)
1215
- } ${[...new Set(dependencies)].join(' ')} --${getDependencyTypeFlag(packageManager, dependenciesType)}${
1216
- DEV_DEPENDENCY_TYPE === dependenciesType ? ` --${getExactFlag(packageManager)}` : ''
1217
- }`,
1218
- {shell: true, cwd: projectRoot}
1219
- );
1220
- } else warn(`No ${dependenciesType} dependencies to install`);
1248
+ async function isPackage ({packageDetails: {exports, publishConfig, bin}}) {
1249
+ return !!exports || (!!publishConfig && !bin);
1221
1250
  }
1222
1251
 
1223
- async function processDependencies ({dependencies = {}, devDependencies, projectRoot, packageManager}) {
1224
- info('Processing dependencies');
1252
+ const defaultBuildDirectory$1 = 'public';
1225
1253
 
1226
- if (Array.isArray(devDependencies)) {
1227
- throw new Error(
1228
- `devDependencies provided as: ${devDependencies}. Instead, provide under dependencies.javascript.development`
1229
- );
1230
- }
1254
+ async function scaffoldApplicationType ({projectRoot}) {
1255
+ info('Scaffolding Application Details');
1231
1256
 
1232
- if (Array.isArray(dependencies)) {
1233
- throw new Error(`Expected dependencies to be an object. Instead received: ${dependencies}`);
1234
- }
1257
+ await mergeIntoExistingPackageJson({projectRoot, config: {private: true}});
1235
1258
 
1236
- const {javascript: {production = [], development = []} = {}} = dependencies;
1259
+ const buildDirectory = defaultBuildDirectory$1;
1237
1260
 
1238
- try {
1239
- await install$1(production, PROD_DEPENDENCY_TYPE, projectRoot, packageManager);
1240
- await install$1(development, DEV_DEPENDENCY_TYPE, projectRoot, packageManager);
1241
- } catch (e) {
1242
- error('Failed to update dependencies');
1243
- error(e, {level: 'secondary'});
1244
- }
1261
+ return {
1262
+ scripts: {
1263
+ clean: `rimraf ./${buildDirectory}`,
1264
+ start: `node ./${buildDirectory}/index.js`,
1265
+ prebuild: 'run-s clean'
1266
+ },
1267
+ dependencies: {javascript: {development: ['rimraf']}},
1268
+ vcsIgnore: {files: ['.env'], directories: [`/${buildDirectory}/`]},
1269
+ buildDirectory,
1270
+ nextSteps: []
1271
+ };
1245
1272
  }
1246
1273
 
1247
- function projectWillBeTested(scripts) {
1248
- return Object.keys(scripts).find(scriptName => scriptName.startsWith('test:'));
1274
+ function isApplication ({packageDetails}) {
1275
+ return !!packageDetails.private;
1249
1276
  }
1250
1277
 
1251
- function projectShouldBeBuiltForVerification(scripts) {
1252
- return 'run-s build' === scripts['pregenerate:md'];
1253
- }
1278
+ async function scaffoldMonorepoType ({projectRoot}) {
1279
+ info('Scaffolding Monorepo Details');
1254
1280
 
1255
- function updateTestScript (scripts) {
1256
- return {
1257
- ...scripts,
1258
- test: `npm-run-all --print-label${
1259
- projectShouldBeBuiltForVerification(scripts) ? ' build' : ''
1260
- } --parallel lint:*${
1261
- projectWillBeTested(scripts) ? ' --parallel test:*' : ''
1262
- }`
1263
- };
1264
- }
1281
+ await mergeIntoExistingPackageJson({projectRoot, config: {private: true}});
1265
1282
 
1266
- function liftScripts ({existingScripts, scripts}) {
1267
1283
  return {
1268
- scripts: updateTestScript({...existingScripts, ...scripts}),
1269
- dependencies: {javascript: {development: ['npm-run-all2']}}
1284
+ nextSteps: [{
1285
+ summary: 'Add packages to your new monorepo',
1286
+ description: 'Leverage [@form8ion/add-package-to-monorepo](https://npm.im/@form8ion/add-package-to-monorepo)'
1287
+ + ' to scaffold new packages into your new monorepo'
1288
+ }]
1270
1289
  };
1271
1290
  }
1272
1291
 
1273
- async function liftPackage ({
1292
+ const defaultBuildDirectory = 'bin';
1293
+
1294
+ async function scaffoldCliType ({
1295
+ packageName,
1296
+ visibility,
1274
1297
  projectRoot,
1275
- scripts,
1276
- tags,
1277
- dependencies,
1278
- devDependencies,
1279
- packageManager,
1280
- vcs,
1281
- pathWithinParent
1298
+ dialect,
1299
+ publishRegistry,
1300
+ decisions,
1301
+ packageBundlers
1282
1302
  }) {
1283
- info('Updating `package.json`', {level: 'secondary'});
1284
-
1285
- const existingPackageJsonContents = JSON.parse(await promises$1.readFile(`${projectRoot}/package.json`, 'utf-8'));
1286
- const {scripts: liftedScripts, dependencies: scriptDependencies} = liftScripts({
1287
- existingScripts: existingPackageJsonContents.scripts,
1288
- scripts
1289
- });
1290
-
1291
- await writePackageJson({
1292
- projectRoot,
1293
- config: sortPackageProperties({
1294
- ...existingPackageJsonContents,
1295
- ...defineVcsHostDetails(vcs, pathWithinParent),
1296
- scripts: liftedScripts,
1297
- ...tags && {
1298
- keywords: existingPackageJsonContents.keywords ? [...existingPackageJsonContents.keywords, ...tags] : tags
1303
+ const packageAccessLevel = determinePackageAccessLevelFromProjectVisibility({projectVisibility: visibility});
1304
+ const [bundlerResults, publishableResults] = await Promise.all([
1305
+ scaffoldBundler({bundlers: packageBundlers, projectRoot, dialect, decisions, projectType: projectTypes$1.CLI}),
1306
+ scaffoldPublishable({packageName, packageAccessLevel}),
1307
+ mergeIntoExistingPackageJson({
1308
+ projectRoot,
1309
+ config: {
1310
+ bin: {},
1311
+ files: [`${defaultBuildDirectory}/`],
1312
+ publishConfig: {
1313
+ access: packageAccessLevel,
1314
+ ...publishRegistry && {registry: publishRegistry}
1315
+ }
1299
1316
  }
1300
1317
  })
1301
- });
1318
+ ]);
1302
1319
 
1303
- await processDependencies({
1304
- dependencies: deepmerge(dependencies, scriptDependencies),
1305
- devDependencies,
1306
- projectRoot,
1307
- packageManager
1308
- });
1320
+ return deepmerge.all([
1321
+ publishableResults,
1322
+ bundlerResults,
1323
+ {
1324
+ scripts: {
1325
+ clean: `rimraf ./${defaultBuildDirectory}`,
1326
+ prebuild: 'run-s clean',
1327
+ build: 'npm-run-all --print-label --parallel build:*',
1328
+ prepack: 'run-s build'
1329
+ },
1330
+ dependencies: {javascript: {production: ['update-notifier'], development: ['rimraf']}},
1331
+ vcsIgnore: {files: [], directories: [`/${defaultBuildDirectory}/`]},
1332
+ buildDirectory: defaultBuildDirectory,
1333
+ nextSteps: [{summary: 'Define supported node.js versions as `engines.node` in the `package.json` file'}]
1334
+ }
1335
+ ]);
1309
1336
  }
1310
1337
 
1311
- async function lift ({projectRoot, vcs, results, pathWithinParent, enhancers = {}, configs = {}}) {
1312
- info('Lifting JavaScript-specific details');
1313
-
1314
- const {
1315
- scripts,
1316
- tags,
1317
- dependencies,
1318
- devDependencies,
1319
- packageManager: manager
1320
- } = results;
1338
+ async function isCli ({packageDetails: {bin}}) {
1339
+ return !!bin;
1340
+ }
1321
1341
 
1322
- const [packageManager, packageContents] = await Promise.all([
1323
- resolvePackageManager({projectRoot, packageManager: manager}),
1324
- promises$1.readFile(`${projectRoot}/package.json`, 'utf8')
1325
- ]);
1342
+ function liftCli ({projectRoot, packageDetails}) {
1343
+ return liftPublishable({projectRoot, packageDetails});
1344
+ }
1326
1345
 
1327
- const enhancerResults = await applyEnhancers({
1328
- results,
1329
- enhancers: {
1330
- ...enhancers,
1331
- huskyPlugin,
1332
- enginesEnhancer,
1333
- coveragePlugin,
1334
- commitConventionPlugin,
1335
- dialects,
1336
- codeStylePlugin,
1337
- npmConfigPlugin,
1338
- projectTypes,
1339
- packageManagers,
1340
- registriesPlugin
1341
- },
1342
- options: {packageManager, projectRoot, vcs, packageDetails: JSON.parse(packageContents), configs}
1343
- });
1344
-
1345
- await liftPackage(
1346
- deepmerge.all([
1347
- {projectRoot, scripts, tags, dependencies, devDependencies, packageManager, vcs, pathWithinParent},
1348
- enhancerResults
1349
- ])
1350
- );
1351
-
1352
- return enhancerResults;
1353
- }
1354
-
1355
- const scopeBasedConfigSchema = joi.object({scope: joi.string().regex(/^@[a-z0-9-]+$/i, 'scope').required()});
1356
-
1357
- const nameBasedConfigSchema = joi.object({
1358
- packageName: joi.string().required(),
1359
- name: joi.string().required()
1360
- });
1361
-
1362
- const registriesSchema = joi.object().pattern(joi.string(), joi.string().uri()).default({});
1363
-
1364
- const visibilitySchema = joi.string().valid('Public', 'Private').required();
1365
-
1366
- const projectNameSchema = joi.string().regex(/^@\w*\//, {invert: true}).required();
1367
-
1368
- const vcsSchema = joi.object({
1369
- host: joi.string().required(),
1370
- owner: joi.string().required(),
1371
- name: joi.string().required()
1372
- });
1373
-
1374
- function validate(options) {
1375
- const schema = joi.object({
1376
- projectRoot: joi.string().required(),
1377
- projectName: projectNameSchema,
1378
- visibility: visibilitySchema,
1379
- license: joi.string().required(),
1380
- description: joi.string(),
1381
- pathWithinParent: joi.string(),
1382
- decisions: joi.object(),
1383
- vcs: vcsSchema,
1384
- configs: joi.object({
1385
- eslint: scopeBasedConfigSchema,
1386
- typescript: scopeBasedConfigSchema,
1387
- prettier: scopeBasedConfigSchema,
1388
- commitlint: nameBasedConfigSchema,
1389
- babelPreset: nameBasedConfigSchema,
1390
- remark: joi.string(),
1391
- registries: registriesSchema
1392
- }).default({registries: {}}),
1393
- plugins: {
1394
- unitTestFrameworks: pluginsSchema,
1395
- packageBundlers: pluginsSchema,
1396
- applicationTypes: pluginsSchema,
1397
- packageTypes: pluginsSchema,
1398
- monorepoTypes: pluginsSchema,
1399
- hosts: pluginsSchema,
1400
- ciServices: pluginsSchema
1401
- }
1402
- }).required();
1403
-
1404
- return validateOptions(schema, options);
1405
- }
1406
-
1407
- function buildDialectChoices ({babelPreset, typescript}) {
1408
- return [
1409
- {name: 'Common JS (no transpilation)', value: dialects$1.COMMON_JS, short: 'cjs'},
1410
- ...babelPreset ? [{name: 'Modern JavaScript (transpiled)', value: dialects$1.BABEL, short: 'modern'}] : [],
1411
- {name: 'ESM-only (no transpilation)', value: dialects$1.ESM, short: 'esm'},
1412
- ...typescript ? [{name: 'TypeScript', value: dialects$1.TYPESCRIPT, short: 'ts'}] : []
1413
- ];
1414
- }
1415
-
1416
- function projectIsCLI(answers) {
1417
- return projectTypes$1.CLI === answers[questionNames$1.PROJECT_TYPE];
1418
- }
1419
-
1420
- function projectIsPackage(answers) {
1421
- return projectTypes$1.PACKAGE === answers[questionNames$1.PROJECT_TYPE];
1422
- }
1423
-
1424
- function projectIsApplication(answers) {
1425
- return projectTypes$1.APPLICATION === answers[questionNames$1.PROJECT_TYPE];
1426
- }
1427
-
1428
- function packageShouldBeScoped(visibility, answers) {
1429
- return 'Private' === visibility || answers[questionNames$1.SHOULD_BE_SCOPED];
1430
- }
1431
-
1432
- function willBePublishedToNpm(answers) {
1433
- return projectIsPackage(answers) || projectIsCLI(answers);
1434
- }
1435
-
1436
- function shouldBeScopedPromptShouldBePresented(answers) {
1437
- return willBePublishedToNpm(answers);
1438
- }
1439
-
1440
- function scopePromptShouldBePresentedFactory(visibility) {
1441
- return answers => willBePublishedToNpm(answers) && packageShouldBeScoped(visibility, answers);
1442
- }
1443
-
1444
- function lintingPromptShouldBePresented({
1445
- [questionNames$2.UNIT_TESTS]: unitTested,
1446
- [questionNames$2.INTEGRATION_TESTS]: integrationTested
1447
- }) {
1448
- return !unitTested && !integrationTested;
1449
- }
1450
-
1451
- function scope(visibility) {
1452
- return input => {
1453
- if (!input && 'Private' === visibility) {
1454
- return 'Private packages must be scoped (https://docs.npmjs.com/private-modules/intro#setting-up-your-package)';
1455
- }
1456
-
1457
- return true;
1458
- };
1459
- }
1460
-
1461
- function authorQuestions({name, email, url}) {
1462
- return [
1463
- {
1464
- name: questionNames$1.AUTHOR_NAME,
1465
- message: 'What is the author\'s name?',
1466
- default: name
1467
- },
1468
- {
1469
- name: questionNames$1.AUTHOR_EMAIL,
1470
- message: 'What is the author\'s email?',
1471
- default: email
1472
- },
1473
- {
1474
- name: questionNames$1.AUTHOR_URL,
1475
- message: 'What is the author\'s website url?',
1476
- default: url
1477
- }
1478
- ];
1479
- }
1480
-
1481
- async function prompt(
1482
- ciServices,
1483
- hosts,
1346
+ async function scaffoldProjectType ({
1347
+ projectType,
1348
+ projectRoot,
1349
+ projectName,
1350
+ packageName,
1351
+ packageManager,
1484
1352
  visibility,
1485
- vcs,
1353
+ packageBundlers,
1354
+ scope,
1486
1355
  decisions,
1487
- configs,
1488
- pathWithinParent
1489
- ) {
1490
- const npmConf$1 = npmConf();
1491
-
1492
- let maybeLoggedInNpmUsername;
1493
- try {
1494
- maybeLoggedInNpmUsername = (await execa('npm', ['whoami'])).stdout;
1495
- } catch (failedExecutionResult) {
1496
- if (!decisions[questionNames$1.SCOPE]) {
1497
- warn('No logged in user found with `npm whoami`. Login with `npm login` '
1498
- + 'to use your npm account name as the package scope default.');
1499
- }
1356
+ dialect,
1357
+ provideExample,
1358
+ publishRegistry
1359
+ }) {
1360
+ switch (projectType) {
1361
+ case projectTypes$1.PACKAGE:
1362
+ return scaffoldPackageType({
1363
+ projectRoot,
1364
+ projectName,
1365
+ packageName,
1366
+ packageManager,
1367
+ visibility,
1368
+ scope,
1369
+ packageBundlers,
1370
+ decisions,
1371
+ dialect,
1372
+ provideExample,
1373
+ publishRegistry
1374
+ });
1375
+ case projectTypes$1.APPLICATION:
1376
+ return scaffoldApplicationType({projectRoot});
1377
+ case projectTypes$1.CLI:
1378
+ return scaffoldCliType({
1379
+ packageName,
1380
+ visibility,
1381
+ projectRoot,
1382
+ dialect,
1383
+ publishRegistry,
1384
+ decisions,
1385
+ packageBundlers
1386
+ });
1387
+ case projectTypes$1.MONOREPO:
1388
+ return scaffoldMonorepoType({projectRoot});
1389
+ case 'Other':
1390
+ return {};
1391
+ default:
1392
+ throw new Error(`The project-type of ${projectType} is invalid`);
1500
1393
  }
1501
-
1502
- const {
1503
- [questionNames$2.UNIT_TESTS]: unitTested,
1504
- [questionNames$2.INTEGRATION_TESTS]: integrationTested,
1505
- [questionNames$1.PROJECT_TYPE]: projectType,
1506
- [questionNames$2.CI_SERVICE]: ci,
1507
- [questionNames$1.HOST]: chosenHost,
1508
- [questionNames$1.SCOPE]: scope$1,
1509
- [questionNames$1.NODE_VERSION_CATEGORY]: nodeVersionCategory,
1510
- [questionNames$1.AUTHOR_NAME]: authorName,
1511
- [questionNames$1.AUTHOR_EMAIL]: authorEmail,
1512
- [questionNames$1.AUTHOR_URL]: authorUrl,
1513
- [questionNames$1.CONFIGURE_LINTING]: configureLinting,
1514
- [questionNames$1.PROVIDE_EXAMPLE]: provideExample,
1515
- [questionNames$1.PACKAGE_MANAGER]: packageManager,
1516
- [questionNames$1.DIALECT]: dialect
1517
- } = await prompt$1([
1518
- {
1519
- name: questionNames$1.DIALECT,
1520
- message: 'Which JavaScript dialect should this project follow?',
1521
- type: 'list',
1522
- choices: buildDialectChoices(configs),
1523
- default: 'babel'
1524
- },
1525
- ...pathWithinParent ? [] : [{
1526
- name: questionNames$1.NODE_VERSION_CATEGORY,
1527
- message: 'What node.js version should be used?',
1528
- type: 'list',
1529
- choices: ['LTS', 'Latest'],
1530
- default: 'LTS'
1531
- }],
1532
- {
1533
- name: questionNames$1.PACKAGE_MANAGER,
1534
- message: 'Which package manager will be used with this project?',
1535
- type: 'list',
1536
- choices: Object.values(packageManagers$1),
1537
- default: packageManagers$1.NPM
1538
- },
1539
- {
1540
- name: questionNames$1.PROJECT_TYPE,
1541
- message: 'What type of JavaScript project is this?',
1542
- type: 'list',
1543
- choices: [...Object.values(projectTypes$1), 'Other'],
1544
- default: projectTypes$1.PACKAGE
1545
- },
1546
- ...'Private' === visibility ? [] : [{
1547
- name: questionNames$1.SHOULD_BE_SCOPED,
1548
- message: 'Should this package be scoped?',
1549
- type: 'confirm',
1550
- when: shouldBeScopedPromptShouldBePresented,
1551
- default: true
1552
- }],
1553
- {
1554
- name: questionNames$1.SCOPE,
1555
- message: 'What is the scope?',
1556
- when: scopePromptShouldBePresentedFactory(visibility),
1557
- validate: scope(visibility),
1558
- default: maybeLoggedInNpmUsername
1559
- },
1560
- ...authorQuestions({
1561
- name: npmConf$1.get('init.author.name'),
1562
- email: npmConf$1.get('init.author.email'),
1563
- url: npmConf$1.get('init.author.url')
1564
- }),
1565
- ...questions(({vcs, ciServices, pathWithinParent})),
1566
- {
1567
- name: questionNames$1.CONFIGURE_LINTING,
1568
- message: 'Will there be source code that should be linted?',
1569
- type: 'confirm',
1570
- when: lintingPromptShouldBePresented
1571
- },
1572
- {
1573
- name: questionNames$1.PROVIDE_EXAMPLE,
1574
- message: 'Should an example be provided in the README?',
1575
- type: 'confirm',
1576
- when: projectIsPackage
1577
- },
1578
- {
1579
- name: questionNames$1.HOST,
1580
- type: 'list',
1581
- message: 'Where will the application be hosted?',
1582
- when: projectIsApplication,
1583
- choices: [...Object.keys(hosts), 'Other']
1584
- }
1585
- ], decisions);
1586
-
1587
- return {
1588
- tests: {unit: unitTested, integration: integrationTested},
1589
- projectType,
1590
- ci,
1591
- chosenHost,
1592
- scope: scope$1,
1593
- nodeVersionCategory,
1594
- author: {name: authorName, email: authorEmail, url: authorUrl},
1595
- configureLinting: false !== configureLinting,
1596
- provideExample,
1597
- packageManager,
1598
- dialect
1599
- };
1600
1394
  }
1601
1395
 
1602
- function scaffoldDocumentation ({projectTypeResults, packageManager}) {
1603
- return {
1604
- toc: `Run \`${buildDocumentationCommand(packageManager)}\` to generate a table of contents`,
1605
- ...projectTypeResults.documentation,
1606
- contributing: `### Dependencies
1607
-
1608
- \`\`\`sh
1609
- $ nvm install
1610
- $ ${packageManager} install
1611
- \`\`\`
1612
-
1613
- ### Verification
1614
-
1615
- \`\`\`sh
1616
- $ ${packageManager} test
1617
- \`\`\``
1618
- };
1619
- }
1620
-
1621
- function buildBadgesDetails (contributors) {
1622
- return deepmerge.all(contributors).badges;
1623
- }
1624
-
1625
- async function determineLatestVersionOf(nodeVersionCategory) {
1626
- info('Determining version of node', {level: 'secondary'});
1627
-
1628
- const {stdout: nvmLsOutput} = await execa(
1629
- `. ~/.nvm/nvm.sh && nvm ls-remote${('LTS' === nodeVersionCategory) ? ' --lts' : ''}`,
1630
- {shell: true}
1631
- );
1632
-
1633
- const lsLines = nvmLsOutput.split('\n');
1634
- const lsLine = lsLines[lsLines.length - 2];
1635
-
1636
- return lsLine.match(/(v[0-9]+)\.[0-9]+\.[0-9]+/)[1];
1396
+ async function tester$3 ({projectRoot, packageDetails}) {
1397
+ return await isPackage({projectRoot, packageDetails})
1398
+ || await isCli({projectRoot, packageDetails})
1399
+ || isApplication({projectRoot, packageDetails});
1637
1400
  }
1638
1401
 
1639
- function install(nodeVersionCategory) {
1640
- info(`Installing ${nodeVersionCategory} version of node using nvm`, {level: 'secondary'});
1641
-
1642
- const subprocess = execa('. ~/.nvm/nvm.sh && nvm install', {shell: true});
1643
- subprocess.stdout.pipe(process.stdout);
1644
- return subprocess;
1402
+ function vcsRepositoryHostedOnGithub(vcs) {
1403
+ return vcs && 'github' === vcs.host;
1645
1404
  }
1646
1405
 
1647
- async function scaffoldNodeVersion ({projectRoot, nodeVersionCategory}) {
1648
- if (!nodeVersionCategory) return undefined;
1649
-
1650
- const lowerCaseCategory = nodeVersionCategory.toLowerCase();
1651
- info(`Configuring ${lowerCaseCategory} version of node`);
1652
-
1653
- const version = await determineLatestVersionOf(nodeVersionCategory);
1406
+ async function lifter$2 ({projectRoot, packageDetails, vcs}) {
1407
+ if (await isPackage({projectRoot, packageDetails})) return liftPackage({projectRoot, packageDetails});
1408
+ if (await isCli({projectRoot, packageDetails})) return liftCli({projectRoot, packageDetails});
1654
1409
 
1655
- await promises$1.writeFile(`${projectRoot}/.nvmrc`, version);
1410
+ let homepage;
1656
1411
 
1657
- await install(nodeVersionCategory);
1412
+ if (vcsRepositoryHostedOnGithub(vcs)) {
1413
+ homepage = `https://github.com/${vcs.owner}/${vcs.name}#readme`;
1658
1414
 
1659
- return version;
1660
- }
1415
+ await mergeIntoExistingPackageJson({projectRoot, config: {homepage}});
1416
+ }
1661
1417
 
1662
- function nvmIsUsed ({projectRoot}) {
1663
- return fileExists(`${projectRoot}/.nvmrc`);
1418
+ return {homepage};
1664
1419
  }
1665
1420
 
1666
- function buildVcsIgnoreLists (vcsIgnoreLists = {}) {
1667
- return {
1668
- files: vcsIgnoreLists.files || [],
1669
- directories: ['/node_modules/', ...vcsIgnoreLists.directories || []]
1670
- };
1671
- }
1421
+ var projectTypes = /*#__PURE__*/Object.freeze({
1422
+ __proto__: null,
1423
+ lift: lifter$2,
1424
+ scaffold: scaffoldProjectType,
1425
+ test: tester$3
1426
+ });
1672
1427
 
1673
1428
  async function chooseProjectTypePlugin ({types, projectType, decisions}) {
1674
1429
  if (!Object.keys(types).length) return 'Other';
@@ -1736,6 +1491,59 @@ async function scaffoldTesting ({
1736
1491
  );
1737
1492
  }
1738
1493
 
1494
+ function buildAllowedHostsList ({packageManager, registries = {}}) {
1495
+ return [
1496
+ ...!registries.registry ? [packageManager] : [],
1497
+ ...Object.values(Object.fromEntries(Object.entries(registries).filter(([scope]) => 'publish' !== scope)))
1498
+ ];
1499
+ }
1500
+
1501
+ const lockfileLintSupportedPackageManagers = [packageManagers$1.NPM, packageManagers$1.YARN];
1502
+
1503
+ function lockfileLintSupports(packageManager) {
1504
+ return lockfileLintSupportedPackageManagers.includes(packageManager);
1505
+ }
1506
+
1507
+ async function scaffoldLockfileLint ({projectRoot, packageManager, registries}) {
1508
+ if (!lockfileLintSupports(packageManager)) {
1509
+ throw new Error(
1510
+ `The ${packageManager} package manager is currently not supported by lockfile-lint. `
1511
+ + `Only ${lockfileLintSupportedPackageManagers.join(' and ')} are currently supported.`
1512
+ );
1513
+ }
1514
+
1515
+ await write$2({
1516
+ name: 'lockfile-lint',
1517
+ format: fileTypes.JSON,
1518
+ path: projectRoot,
1519
+ config: {
1520
+ path: determineLockfilePathFor(packageManager),
1521
+ type: packageManager,
1522
+ 'validate-https': true,
1523
+ 'allowed-hosts': buildAllowedHostsList({packageManager, registries})
1524
+ }
1525
+ });
1526
+
1527
+ return {
1528
+ dependencies: {javascript: {development: ['lockfile-lint']}},
1529
+ scripts: {'lint:lockfile': 'lockfile-lint'}
1530
+ };
1531
+ }
1532
+
1533
+ function lockfileLintIsAlreadyConfigured ({projectRoot}) {
1534
+ return fileExists(`${projectRoot}/.lockfile-lintrc.json`);
1535
+ }
1536
+
1537
+ const configName = 'lockfile-lint';
1538
+
1539
+ function read({projectRoot}) {
1540
+ return loadConfigFile({name: `.${configName}rc`, format: fileTypes.JSON, path: projectRoot});
1541
+ }
1542
+
1543
+ function write({projectRoot, config}) {
1544
+ return write$2({name: configName, format: fileTypes.JSON, path: projectRoot, config});
1545
+ }
1546
+
1739
1547
  async function scaffoldLinting ({projectRoot, packageManager, registries}) {
1740
1548
  return scaffoldLockfileLint({projectRoot, packageManager, registries});
1741
1549
  }
@@ -1764,12 +1572,82 @@ async function scaffoldVerification({
1764
1572
  pathWithinParent
1765
1573
  }),
1766
1574
  scaffoldLinting({projectRoot, packageManager, registries, vcs, pathWithinParent}),
1767
- scaffold$3({projectRoot, packageManager, pathWithinParent})
1575
+ scaffold$1({projectRoot, packageManager, pathWithinParent})
1768
1576
  ]);
1769
1577
 
1770
1578
  return deepmerge.all([testingResults, lintingResults, huskyResults]);
1771
1579
  }
1772
1580
 
1581
+ async function scaffoldRemark ({config, projectRoot, projectType, vcs}) {
1582
+ await write$2({
1583
+ format: fileTypes.JSON,
1584
+ path: projectRoot,
1585
+ name: 'remark',
1586
+ config: {
1587
+ settings: {
1588
+ listItemIndent: 'one',
1589
+ emphasis: '_',
1590
+ strong: '_',
1591
+ bullet: '*',
1592
+ incrementListMarker: false
1593
+ },
1594
+ plugins: [
1595
+ config,
1596
+ ['remark-toc', {tight: true}],
1597
+ ...projectTypes$1.PACKAGE === projectType ? [['remark-usage', {heading: 'example'}]] : [],
1598
+ ...!vcs ? [['validate-links', {repository: false}]] : []
1599
+ ]
1600
+ }
1601
+ });
1602
+
1603
+ return deepmerge(
1604
+ {
1605
+ dependencies: {javascript: {development: [config, 'remark-cli', 'remark-toc']}},
1606
+ scripts: {
1607
+ 'lint:md': 'remark . --frail',
1608
+ 'generate:md': 'remark . --output'
1609
+ }
1610
+ },
1611
+ {...projectTypes$1.PACKAGE === projectType && {dependencies: {javascript: {development: ['remark-usage']}}}}
1612
+ );
1613
+ }
1614
+
1615
+ async function scaffoldCodeStyle ({
1616
+ projectRoot,
1617
+ projectType,
1618
+ configs,
1619
+ vcs,
1620
+ configureLinting
1621
+ }) {
1622
+ return deepmerge.all(await Promise.all([
1623
+ configs.eslint
1624
+ && configureLinting
1625
+ && scaffold$2({projectRoot, config: configs.eslint}),
1626
+ scaffoldRemark({
1627
+ projectRoot,
1628
+ projectType,
1629
+ vcs,
1630
+ config: configs.remark || '@form8ion/remark-lint-preset'
1631
+ }),
1632
+ scaffold$3({projectRoot, config: configs.prettier})
1633
+ ].filter(Boolean)));
1634
+ }
1635
+
1636
+ function lifter$1 (options) {
1637
+ return applyEnhancers({options, enhancers: [eslintPlugin]});
1638
+ }
1639
+
1640
+ function tester$2 (options) {
1641
+ return test$1(options);
1642
+ }
1643
+
1644
+ var codeStylePlugin = /*#__PURE__*/Object.freeze({
1645
+ __proto__: null,
1646
+ lift: lifter$1,
1647
+ scaffold: scaffoldCodeStyle,
1648
+ test: tester$2
1649
+ });
1650
+
1773
1651
  async function scaffolder (options) {
1774
1652
  info('Initializing JavaScript project');
1775
1653
 
@@ -1897,25 +1775,133 @@ async function scaffolder (options) {
1897
1775
  projectTypePluginResults
1898
1776
  ]);
1899
1777
 
1900
- // const liftResults = await lift({
1901
- // results: deepmerge({devDependencies: ['npm-run-all2'], packageManager}, mergedContributions),
1902
- // projectRoot,
1903
- // configs,
1904
- // vcs,
1905
- // pathWithinParent
1906
- // });
1907
-
1908
1778
  return {
1909
1779
  ...mergedContributions,
1910
- badges: buildBadgesDetails([mergedContributions/*, liftResults*/]),
1911
1780
  documentation: scaffoldDocumentation({projectTypeResults, packageManager}),
1912
1781
  tags: projectTypeResults.tags,
1913
1782
  vcsIgnore: buildVcsIgnoreLists(mergedContributions.vcsIgnore),
1914
- verificationCommand: `${buildDocumentationCommand(packageManager)} && ${packageManager} test`,
1915
- // projectDetails: {...liftResults.homepage && {homepage: liftResults.homepage}},
1783
+ verificationCommand: `${buildDocumentationCommand(packageManager)} && ${packageManager} test`
1784
+ };
1785
+ }
1786
+
1787
+ function tester$1 () {
1788
+ return true;
1789
+ }
1790
+
1791
+ function buildRegistriesConfig (registries = {}) {
1792
+ return Object.entries(registries)
1793
+ .filter(([scope]) => 'publish' !== scope)
1794
+ .reduce((acc, [scope, url]) => {
1795
+ if ('registry' === scope) return {...acc, registry: url};
1796
+
1797
+ return {...acc, [`@${scope}:registry`]: url};
1798
+ }, {registry: 'https://registry.npmjs.org'});
1799
+ }
1800
+
1801
+ async function updateRegistriesInNpmConfig(registries, projectRoot) {
1802
+ const registriesForNpmConfig = buildRegistriesConfig(registries);
1803
+
1804
+ await writeNpmConfig({
1805
+ projectRoot,
1806
+ config: {
1807
+ ...(await readNpmConfig({projectRoot})),
1808
+ ...registriesForNpmConfig
1809
+ }
1810
+ });
1811
+ }
1812
+
1813
+ async function updateRegistriesInLockfileLintConfig(projectRoot, packageManager, registries) {
1814
+ await write({
1815
+ projectRoot,
1816
+ config: {
1817
+ ...await read({projectRoot}),
1818
+ 'allowed-hosts': buildAllowedHostsList({packageManager, registries})
1819
+ }
1820
+ });
1821
+ }
1822
+
1823
+ async function lifter ({projectRoot, packageManager, configs: {registries}}) {
1824
+ await updateRegistriesInNpmConfig(registries, projectRoot);
1825
+
1826
+ if (!(await lockfileLintIsAlreadyConfigured({projectRoot}))) {
1827
+ return scaffoldLockfileLint({projectRoot, packageManager, registries});
1828
+ }
1829
+
1830
+ await updateRegistriesInLockfileLintConfig(projectRoot, packageManager, registries);
1831
+
1832
+ return {};
1833
+ }
1834
+
1835
+ var registriesPlugin = /*#__PURE__*/Object.freeze({
1836
+ __proto__: null,
1837
+ lift: lifter,
1838
+ test: tester$1
1839
+ });
1840
+
1841
+ async function test({projectRoot}) {
1842
+ const {engines} = JSON.parse(await promises.readFile(`${projectRoot}/package.json`, 'utf8'));
1843
+
1844
+ return !!engines?.node;
1845
+ }
1846
+
1847
+ async function lift$1({packageDetails: {name}}) {
1848
+ return {
1849
+ dependencies: {javascript: {development: ['ls-engines']}},
1850
+ scripts: {'lint:engines': 'ls-engines'},
1851
+ badges: {consumer: {node: {img: `https://img.shields.io/node/v/${name}?logo=node.js`, text: 'node'}}}
1916
1852
  };
1917
1853
  }
1918
1854
 
1855
+ var enginesEnhancer = /*#__PURE__*/Object.freeze({
1856
+ __proto__: null,
1857
+ lift: lift$1,
1858
+ test: test
1859
+ });
1860
+
1861
+ async function lift ({projectRoot, vcs, results, pathWithinParent, enhancers = {}, configs = {}}) {
1862
+ info('Lifting JavaScript-specific details');
1863
+
1864
+ const {
1865
+ scripts,
1866
+ tags,
1867
+ dependencies,
1868
+ devDependencies,
1869
+ packageManager: manager
1870
+ } = results;
1871
+
1872
+ const [packageManager, packageContents] = await Promise.all([
1873
+ resolvePackageManager({projectRoot, packageManager: manager}),
1874
+ promises$1.readFile(`${projectRoot}/package.json`, 'utf8')
1875
+ ]);
1876
+
1877
+ const enhancerResults = await applyEnhancers({
1878
+ results,
1879
+ enhancers: {
1880
+ ...enhancers,
1881
+ huskyPlugin,
1882
+ enginesEnhancer,
1883
+ coveragePlugin,
1884
+ commitConventionPlugin,
1885
+ dialects,
1886
+ codeStylePlugin,
1887
+ npmConfigPlugin,
1888
+ projectTypes,
1889
+ packageManagers,
1890
+ registriesPlugin
1891
+ },
1892
+ options: {packageManager, projectRoot, vcs, packageDetails: JSON.parse(packageContents), configs}
1893
+ });
1894
+
1895
+ await liftPackage$1(
1896
+ deepmerge.all([
1897
+ {projectRoot, scripts, tags, dependencies, devDependencies, packageManager, vcs, pathWithinParent},
1898
+ enhancerResults
1899
+ ])
1900
+ );
1901
+
1902
+ return enhancerResults;
1903
+ }
1904
+
1919
1905
  async function tester ({projectRoot}) {
1920
1906
  const [nvmFound, jsPackageManagerFound] = await Promise.all([
1921
1907
  nvmIsUsed({projectRoot}),