@form8ion/javascript 14.0.0-alpha.8 → 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 +1244 -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,1144 +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
- });
985
-
986
- function write ({projectRoot, config}) {
987
- return write$2({path: projectRoot, name: 'babel', format: fileTypes.JSON, config});
988
- }
967
+ function getInstallationCommand(packageManager) {
968
+ if (packageManagers$1.NPM === packageManager) return 'npm install';
969
+ if (packageManagers$1.YARN === packageManager) return 'yarn add';
989
970
 
990
- function loadConfig ({projectRoot}) {
991
- return loadConfigFile({path: projectRoot, name: '.babelrc', format: fileTypes.JSON});
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
+ );
992
975
  }
993
976
 
994
- async function addIgnore ({projectRoot, ignore}) {
995
- if (ignore) {
996
- const existingConfig = await loadConfig({projectRoot});
997
-
998
- await write({projectRoot, config: {...existingConfig, ignore: [`./${ignore}/`]}});
999
- }
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
+ ` : ''
1000
984
  }
985
+ \`\`\`sh
986
+ $ ${getInstallationCommand(packageManager)} ${packageName}
987
+ \`\`\`${provideExample
988
+ ? `
1001
989
 
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]}});
990
+ ### Example
1008
991
 
1009
- return {
1010
- dependencies: {javascript: {development: ['@babel/register', preset.packageName]}},
1011
- eslint: {}
992
+ run \`${buildDocumentationCommand(packageManager)}\` to inject the usage example`
993
+ : ''
994
+ }`
1012
995
  };
1013
996
  }
1014
997
 
1015
- async function lifter ({results, projectRoot}) {
1016
- await addIgnore({ignore: results.buildDirectory, projectRoot});
1017
-
1018
- return {};
998
+ function determinePackageAccessLevelFromProjectVisibility ({projectVisibility}) {
999
+ return 'Public' === projectVisibility ? 'public' : 'restricted';
1019
1000
  }
1020
1001
 
1021
- function predicate ({projectRoot}) {
1022
- return fileExists(`${projectRoot}/.babelrc.json`);
1002
+ function defineBadges (packageName, accessLevel) {
1003
+ return {
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: {}
1014
+ };
1023
1015
  }
1024
1016
 
1025
- async function scaffoldTypescript ({config, projectType, projectRoot, testFilenamePattern}) {
1026
- const shareableTsConfigPackage = `${config.scope}/tsconfig`;
1027
-
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
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
+ }
1040
1027
  }
1041
- },
1042
- include: ['src/**/*.ts'],
1043
- ...testFilenamePattern && {exclude: [testFilenamePattern]}
1044
- }
1045
- });
1028
+ }
1029
+ };
1030
+ }
1046
1031
 
1047
- return {
1048
- eslint: {configs: ['typescript']},
1049
- dependencies: {javascript: {development: ['typescript', shareableTsConfigPackage]}},
1050
- vcsIgnore: {files: ['tsconfig.tsbuildinfo']}
1051
- };
1032
+ return {};
1052
1033
  }
1053
1034
 
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
- }
1063
- }
1035
+ async function liftProvenance ({projectRoot, packageDetails}) {
1036
+ const {publishConfig: {access}} = packageDetails;
1064
1037
 
1065
- var dialects = /*#__PURE__*/Object.freeze({
1066
- __proto__: null,
1067
- lift: lifter,
1068
- scaffold: scaffoldDialect,
1069
- test: predicate
1070
- });
1038
+ if ('public' === access) {
1039
+ await mergeIntoExistingPackageJson({projectRoot, config: {publishConfig: {provenance: true}}});
1071
1040
 
1072
- function buildPackageName (projectName, scope) {
1073
- const name = `${scope ? `@${scope}/` : ''}${projectName}`;
1041
+ return enhanceSlsa({provenance: true});
1042
+ }
1074
1043
 
1075
- const {validForNewPackages, errors} = validatePackageName(name);
1044
+ return {};
1045
+ }
1076
1046
 
1077
- if (validForNewPackages) return name;
1078
- if (1 === errors.length && errors.includes('name cannot start with a period')) return projectName.slice(1);
1047
+ async function liftPublishable ({projectRoot, packageDetails}) {
1048
+ const {name: packageName, publishConfig: {access: packageAccessLevel}} = packageDetails;
1049
+ const homepage = `https://npm.im/${packageName}`;
1079
1050
 
1080
- throw new Error(`The package name ${name} is invalid:${EOL}\t* ${errors.join(`${EOL}\t* `)}`);
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)
1060
+ }
1061
+ );
1081
1062
  }
1082
1063
 
1083
- function buildPackageDetails ({
1084
- packageName,
1085
- dialect,
1086
- license,
1087
- author,
1088
- description
1089
- }) {
1064
+ async function scaffoldPublishable ({packageName, packageAccessLevel}) {
1090
1065
  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: {}
1066
+ badges: await defineBadges(packageName, packageAccessLevel)
1097
1067
  };
1098
1068
  }
1099
1069
 
1100
- async function scaffoldPackage ({
1101
- projectRoot,
1102
- projectName,
1103
- scope,
1104
- dialect,
1105
- license,
1106
- author,
1107
- description
1108
- }) {
1109
- info('Configuring package.json');
1110
-
1111
- const packageName = buildPackageName(projectName, scope);
1070
+ async function chooseBundler ({bundlers, decisions}) {
1071
+ if (!Object.keys(bundlers).length) return 'Other';
1112
1072
 
1113
- await writePackageJson({
1114
- projectRoot,
1115
- config: await buildPackageDetails({
1116
- packageName,
1117
- dialect,
1118
- license,
1119
- author,
1120
- description
1121
- })
1122
- });
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);
1123
1079
 
1124
- return {packageName};
1080
+ return answers[questionNames$1.PACKAGE_BUNDLER];
1125
1081
  }
1126
1082
 
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
- }
1083
+ async function scaffoldBundler ({projectRoot, projectType, bundlers, dialect, decisions}) {
1084
+ const chosenBundler = await chooseBundler({bundlers, decisions});
1163
1085
 
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
- };
1086
+ return scaffoldChoice(bundlers, chosenBundler, {projectRoot, projectType, dialect});
1175
1087
  }
1176
1088
 
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
- };
1089
+ function determinePathToTemplateFile (fileName) {
1090
+ const [, __dirname] = filedirname();
1195
1091
 
1196
- function getInstallationCommandFor(manager) {
1197
- return details[manager].installationCommand;
1092
+ return resolve(__dirname, '..', 'templates', fileName);
1198
1093
  }
1199
1094
 
1200
- function getDependencyTypeFlag(manager, type) {
1201
- return details[manager].installationFlags[type];
1202
- }
1095
+ const defaultBuildDirectory$2 = 'lib';
1203
1096
 
1204
- function getExactFlag(manager) {
1205
- return details[manager].installationFlags.exact;
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
+ );
1206
1105
  }
1207
1106
 
1208
- async function install$1 (dependencies, dependenciesType, projectRoot, packageManager = packageManagers$1.NPM) {
1209
- if (dependencies.length) {
1210
- info(`Installing ${dependenciesType} dependencies`, {level: 'secondary'});
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
+ ]);
1211
1114
 
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`);
1115
+ return {};
1221
1116
  }
1222
1117
 
1223
- async function processDependencies ({dependencies = {}, devDependencies, projectRoot, packageManager}) {
1224
- info('Processing dependencies');
1225
-
1226
- if (Array.isArray(devDependencies)) {
1227
- throw new Error(
1228
- `devDependencies provided as: ${devDependencies}. Instead, provide under dependencies.javascript.development`
1229
- );
1230
- }
1118
+ async function buildDetails ({
1119
+ projectRoot,
1120
+ projectName,
1121
+ visibility,
1122
+ packageName,
1123
+ packageBundlers,
1124
+ dialect,
1125
+ provideExample,
1126
+ decisions
1127
+ }) {
1128
+ if (dialects$1.COMMON_JS === dialect) return buildDetailsForCommonJsProject({projectRoot, projectName, provideExample});
1231
1129
 
1232
- if (Array.isArray(dependencies)) {
1233
- throw new Error(`Expected dependencies to be an object. Instead received: ${dependencies}`);
1234
- }
1235
-
1236
- const {javascript: {production = [], development = []} = {}} = dependencies;
1237
-
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
- }
1245
- }
1246
-
1247
- function projectWillBeTested(scripts) {
1248
- return Object.keys(scripts).find(scriptName => scriptName.startsWith('test:'));
1249
- }
1250
-
1251
- function projectShouldBeBuiltForVerification(scripts) {
1252
- return 'run-s build' === scripts['pregenerate:md'];
1253
- }
1254
-
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
- }
1265
-
1266
- function liftScripts ({existingScripts, scripts}) {
1267
- return updateTestScript({...existingScripts, ...scripts});
1268
- }
1269
-
1270
- async function liftPackage ({
1271
- projectRoot,
1272
- scripts,
1273
- tags,
1274
- dependencies,
1275
- devDependencies,
1276
- packageManager,
1277
- vcs,
1278
- pathWithinParent
1279
- }) {
1280
- info('Updating `package.json`', {level: 'secondary'});
1281
-
1282
- const existingPackageJsonContents = JSON.parse(await promises$1.readFile(`${projectRoot}/package.json`, 'utf-8'));
1283
-
1284
- await writePackageJson({
1285
- projectRoot,
1286
- config: sortPackageProperties({
1287
- ...existingPackageJsonContents,
1288
- ...defineVcsHostDetails(vcs, pathWithinParent),
1289
- scripts: liftScripts({
1290
- existingScripts: existingPackageJsonContents.scripts,
1291
- scripts
1292
- }),
1293
- ...tags && {
1294
- keywords: existingPackageJsonContents.keywords ? [...existingPackageJsonContents.keywords, ...tags] : tags
1295
- }
1296
- })
1297
- });
1298
-
1299
- await processDependencies({dependencies, devDependencies, projectRoot, packageManager});
1300
- }
1301
-
1302
- async function lift ({projectRoot, vcs, results, pathWithinParent, enhancers = {}, configs = {}}) {
1303
- info('Lifting JavaScript-specific details');
1304
-
1305
- const {
1306
- scripts,
1307
- tags,
1308
- dependencies,
1309
- devDependencies,
1310
- packageManager: manager
1311
- } = results;
1312
-
1313
- const [packageManager, packageContents] = await Promise.all([
1314
- resolvePackageManager({projectRoot, packageManager: manager}),
1315
- promises$1.readFile(`${projectRoot}/package.json`, 'utf8')
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`)
1316
1135
  ]);
1317
1136
 
1318
- const enhancerResults = await applyEnhancers({
1319
- results,
1320
- enhancers: {
1321
- ...enhancers,
1322
- huskyPlugin,
1323
- enginesEnhancer,
1324
- coveragePlugin,
1325
- commitConventionPlugin,
1326
- dialects,
1327
- codeStylePlugin,
1328
- npmConfigPlugin,
1329
- projectTypes,
1330
- packageManagers,
1331
- registriesPlugin
1332
- },
1333
- options: {packageManager, projectRoot, vcs, packageDetails: JSON.parse(packageContents), configs}
1334
- });
1335
-
1336
- await liftPackage(
1337
- deepmerge.all([
1338
- {projectRoot, scripts, tags, dependencies, devDependencies, packageManager, vcs, pathWithinParent},
1339
- enhancerResults
1340
- ])
1341
- );
1342
-
1343
- return enhancerResults;
1344
- }
1345
-
1346
- const scopeBasedConfigSchema = joi.object({scope: joi.string().regex(/^@[a-z0-9-]+$/i, 'scope').required()});
1347
-
1348
- const nameBasedConfigSchema = joi.object({
1349
- packageName: joi.string().required(),
1350
- name: joi.string().required()
1351
- });
1352
-
1353
- const registriesSchema = joi.object().pattern(joi.string(), joi.string().uri()).default({});
1354
-
1355
- const visibilitySchema = joi.string().valid('Public', 'Private').required();
1356
-
1357
- const projectNameSchema = joi.string().regex(/^@\w*\//, {invert: true}).required();
1358
-
1359
- const vcsSchema = joi.object({
1360
- host: joi.string().required(),
1361
- owner: joi.string().required(),
1362
- name: joi.string().required()
1363
- });
1364
-
1365
- function validate(options) {
1366
- const schema = joi.object({
1367
- projectRoot: joi.string().required(),
1368
- projectName: projectNameSchema,
1369
- visibility: visibilitySchema,
1370
- license: joi.string().required(),
1371
- description: joi.string(),
1372
- pathWithinParent: joi.string(),
1373
- decisions: joi.object(),
1374
- vcs: vcsSchema,
1375
- configs: joi.object({
1376
- eslint: scopeBasedConfigSchema,
1377
- typescript: scopeBasedConfigSchema,
1378
- prettier: scopeBasedConfigSchema,
1379
- commitlint: nameBasedConfigSchema,
1380
- babelPreset: nameBasedConfigSchema,
1381
- remark: joi.string(),
1382
- registries: registriesSchema
1383
- }).default({registries: {}}),
1384
- plugins: {
1385
- unitTestFrameworks: pluginsSchema,
1386
- packageBundlers: pluginsSchema,
1387
- applicationTypes: pluginsSchema,
1388
- packageTypes: pluginsSchema,
1389
- monorepoTypes: pluginsSchema,
1390
- hosts: pluginsSchema,
1391
- ciServices: pluginsSchema
1392
- }
1393
- }).required();
1394
-
1395
- return validateOptions(schema, options);
1396
- }
1397
-
1398
- function buildDialectChoices ({babelPreset, typescript}) {
1399
- return [
1400
- {name: 'Common JS (no transpilation)', value: dialects$1.COMMON_JS, short: 'cjs'},
1401
- ...babelPreset ? [{name: 'Modern JavaScript (transpiled)', value: dialects$1.BABEL, short: 'modern'}] : [],
1402
- {name: 'ESM-only (no transpilation)', value: dialects$1.ESM, short: 'esm'},
1403
- ...typescript ? [{name: 'TypeScript', value: dialects$1.TYPESCRIPT, short: 'ts'}] : []
1404
- ];
1405
- }
1406
-
1407
- function projectIsCLI(answers) {
1408
- return projectTypes$1.CLI === answers[questionNames$1.PROJECT_TYPE];
1409
- }
1410
-
1411
- function projectIsPackage(answers) {
1412
- return projectTypes$1.PACKAGE === answers[questionNames$1.PROJECT_TYPE];
1413
- }
1414
-
1415
- function projectIsApplication(answers) {
1416
- return projectTypes$1.APPLICATION === answers[questionNames$1.PROJECT_TYPE];
1417
- }
1418
-
1419
- function packageShouldBeScoped(visibility, answers) {
1420
- return 'Private' === visibility || answers[questionNames$1.SHOULD_BE_SCOPED];
1421
- }
1422
-
1423
- function willBePublishedToNpm(answers) {
1424
- return projectIsPackage(answers) || projectIsCLI(answers);
1425
- }
1426
-
1427
- function shouldBeScopedPromptShouldBePresented(answers) {
1428
- return willBePublishedToNpm(answers);
1429
- }
1430
-
1431
- function scopePromptShouldBePresentedFactory(visibility) {
1432
- return answers => willBePublishedToNpm(answers) && packageShouldBeScoped(visibility, answers);
1433
- }
1434
-
1435
- function lintingPromptShouldBePresented({
1436
- [questionNames$2.UNIT_TESTS]: unitTested,
1437
- [questionNames$2.INTEGRATION_TESTS]: integrationTested
1438
- }) {
1439
- return !unitTested && !integrationTested;
1440
- }
1441
-
1442
- function scope(visibility) {
1443
- return input => {
1444
- if (!input && 'Private' === visibility) {
1445
- return 'Private packages must be scoped (https://docs.npmjs.com/private-modules/intro#setting-up-your-package)';
1446
- }
1447
-
1448
- return true;
1449
- };
1450
- }
1451
-
1452
- function authorQuestions({name, email, url}) {
1453
- return [
1454
- {
1455
- name: questionNames$1.AUTHOR_NAME,
1456
- message: 'What is the author\'s name?',
1457
- default: name
1458
- },
1459
- {
1460
- name: questionNames$1.AUTHOR_EMAIL,
1461
- message: 'What is the author\'s email?',
1462
- default: email
1463
- },
1137
+ return deepmerge(
1138
+ bundlerResults,
1464
1139
  {
1465
- name: questionNames$1.AUTHOR_URL,
1466
- message: 'What is the author\'s website url?',
1467
- default: url
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
+ }
1468
1161
  }
1469
- ];
1162
+ );
1470
1163
  }
1471
1164
 
1472
- async function prompt(
1473
- ciServices,
1474
- hosts,
1165
+ async function scaffoldPackageType ({
1166
+ projectRoot,
1167
+ projectName,
1168
+ packageName,
1169
+ packageManager,
1475
1170
  visibility,
1476
- vcs,
1171
+ scope,
1172
+ packageBundlers,
1477
1173
  decisions,
1478
- configs,
1479
- pathWithinParent
1480
- ) {
1481
- const npmConf$1 = npmConf();
1482
-
1483
- let maybeLoggedInNpmUsername;
1484
- try {
1485
- maybeLoggedInNpmUsername = (await execa('npm', ['whoami'])).stdout;
1486
- } catch (failedExecutionResult) {
1487
- if (!decisions[questionNames$1.SCOPE]) {
1488
- warn('No logged in user found with `npm whoami`. Login with `npm login` '
1489
- + 'to use your npm account name as the package scope default.');
1490
- }
1491
- }
1174
+ dialect,
1175
+ provideExample,
1176
+ publishRegistry
1177
+ }) {
1178
+ info('Scaffolding Package Details');
1492
1179
 
1493
- const {
1494
- [questionNames$2.UNIT_TESTS]: unitTested,
1495
- [questionNames$2.INTEGRATION_TESTS]: integrationTested,
1496
- [questionNames$1.PROJECT_TYPE]: projectType,
1497
- [questionNames$2.CI_SERVICE]: ci,
1498
- [questionNames$1.HOST]: chosenHost,
1499
- [questionNames$1.SCOPE]: scope$1,
1500
- [questionNames$1.NODE_VERSION_CATEGORY]: nodeVersionCategory,
1501
- [questionNames$1.AUTHOR_NAME]: authorName,
1502
- [questionNames$1.AUTHOR_EMAIL]: authorEmail,
1503
- [questionNames$1.AUTHOR_URL]: authorUrl,
1504
- [questionNames$1.CONFIGURE_LINTING]: configureLinting,
1505
- [questionNames$1.PROVIDE_EXAMPLE]: provideExample,
1506
- [questionNames$1.PACKAGE_MANAGER]: packageManager,
1507
- [questionNames$1.DIALECT]: dialect
1508
- } = await prompt$1([
1509
- {
1510
- name: questionNames$1.DIALECT,
1511
- message: 'Which JavaScript dialect should this project follow?',
1512
- type: 'list',
1513
- choices: buildDialectChoices(configs),
1514
- default: 'babel'
1515
- },
1516
- ...pathWithinParent ? [] : [{
1517
- name: questionNames$1.NODE_VERSION_CATEGORY,
1518
- message: 'What node.js version should be used?',
1519
- type: 'list',
1520
- choices: ['LTS', 'Latest'],
1521
- default: 'LTS'
1522
- }],
1523
- {
1524
- name: questionNames$1.PACKAGE_MANAGER,
1525
- message: 'Which package manager will be used with this project?',
1526
- type: 'list',
1527
- choices: Object.values(packageManagers$1),
1528
- default: packageManagers$1.NPM
1529
- },
1530
- {
1531
- name: questionNames$1.PROJECT_TYPE,
1532
- message: 'What type of JavaScript project is this?',
1533
- type: 'list',
1534
- choices: [...Object.values(projectTypes$1), 'Other'],
1535
- default: projectTypes$1.PACKAGE
1536
- },
1537
- ...'Private' === visibility ? [] : [{
1538
- name: questionNames$1.SHOULD_BE_SCOPED,
1539
- message: 'Should this package be scoped?',
1540
- type: 'confirm',
1541
- when: shouldBeScopedPromptShouldBePresented,
1542
- default: true
1543
- }],
1544
- {
1545
- name: questionNames$1.SCOPE,
1546
- message: 'What is the scope?',
1547
- when: scopePromptShouldBePresentedFactory(visibility),
1548
- validate: scope(visibility),
1549
- default: maybeLoggedInNpmUsername
1550
- },
1551
- ...authorQuestions({
1552
- name: npmConf$1.get('init.author.name'),
1553
- email: npmConf$1.get('init.author.email'),
1554
- url: npmConf$1.get('init.author.url')
1180
+ const packageAccessLevel = determinePackageAccessLevelFromProjectVisibility({projectVisibility: visibility});
1181
+ const [detailsForBuild, publishableResults] = await Promise.all([
1182
+ buildDetails({
1183
+ projectRoot,
1184
+ projectName,
1185
+ packageBundlers,
1186
+ visibility,
1187
+ packageName,
1188
+ dialect,
1189
+ provideExample,
1190
+ decisions
1555
1191
  }),
1556
- ...questions(({vcs, ciServices, pathWithinParent})),
1557
- {
1558
- name: questionNames$1.CONFIGURE_LINTING,
1559
- message: 'Will there be source code that should be linted?',
1560
- type: 'confirm',
1561
- when: lintingPromptShouldBePresented
1562
- },
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
+ }
1227
+ })
1228
+ ]);
1229
+
1230
+ return deepmerge.all([
1231
+ publishableResults,
1563
1232
  {
1564
- name: questionNames$1.PROVIDE_EXAMPLE,
1565
- message: 'Should an example be provided in the README?',
1566
- type: 'confirm',
1567
- when: projectIsPackage
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
+ ]
1568
1239
  },
1569
- {
1570
- name: questionNames$1.HOST,
1571
- type: 'list',
1572
- message: 'Where will the application be hosted?',
1573
- when: projectIsApplication,
1574
- choices: [...Object.keys(hosts), 'Other']
1575
- }
1576
- ], decisions);
1240
+ detailsForBuild
1241
+ ]);
1242
+ }
1577
1243
 
1578
- return {
1579
- tests: {unit: unitTested, integration: integrationTested},
1580
- projectType,
1581
- ci,
1582
- chosenHost,
1583
- scope: scope$1,
1584
- nodeVersionCategory,
1585
- author: {name: authorName, email: authorEmail, url: authorUrl},
1586
- configureLinting: false !== configureLinting,
1587
- provideExample,
1588
- packageManager,
1589
- dialect
1590
- };
1244
+ function liftPackage ({projectRoot, packageDetails}) {
1245
+ return liftPublishable({projectRoot, packageDetails});
1591
1246
  }
1592
1247
 
1593
- function scaffoldDocumentation ({projectTypeResults, packageManager}) {
1594
- return {
1595
- toc: `Run \`${buildDocumentationCommand(packageManager)}\` to generate a table of contents`,
1596
- ...projectTypeResults.documentation,
1597
- contributing: `### Dependencies
1248
+ async function isPackage ({packageDetails: {exports, publishConfig, bin}}) {
1249
+ return !!exports || (!!publishConfig && !bin);
1250
+ }
1598
1251
 
1599
- \`\`\`sh
1600
- $ nvm install
1601
- $ ${packageManager} install
1602
- \`\`\`
1252
+ const defaultBuildDirectory$1 = 'public';
1603
1253
 
1604
- ### Verification
1254
+ async function scaffoldApplicationType ({projectRoot}) {
1255
+ info('Scaffolding Application Details');
1605
1256
 
1606
- \`\`\`sh
1607
- $ ${packageManager} test
1608
- \`\`\``
1257
+ await mergeIntoExistingPackageJson({projectRoot, config: {private: true}});
1258
+
1259
+ const buildDirectory = defaultBuildDirectory$1;
1260
+
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: []
1609
1271
  };
1610
1272
  }
1611
1273
 
1612
- function buildBadgesDetails (contributors) {
1613
- return deepmerge.all(contributors).badges;
1274
+ function isApplication ({packageDetails}) {
1275
+ return !!packageDetails.private;
1614
1276
  }
1615
1277
 
1616
- async function determineLatestVersionOf(nodeVersionCategory) {
1617
- info('Determining version of node', {level: 'secondary'});
1278
+ async function scaffoldMonorepoType ({projectRoot}) {
1279
+ info('Scaffolding Monorepo Details');
1618
1280
 
1619
- const {stdout: nvmLsOutput} = await execa(
1620
- `. ~/.nvm/nvm.sh && nvm ls-remote${('LTS' === nodeVersionCategory) ? ' --lts' : ''}`,
1621
- {shell: true}
1622
- );
1281
+ await mergeIntoExistingPackageJson({projectRoot, config: {private: true}});
1623
1282
 
1624
- const lsLines = nvmLsOutput.split('\n');
1625
- const lsLine = lsLines[lsLines.length - 2];
1283
+ return {
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
+ }]
1289
+ };
1290
+ }
1626
1291
 
1627
- return lsLine.match(/(v[0-9]+)\.[0-9]+\.[0-9]+/)[1];
1292
+ const defaultBuildDirectory = 'bin';
1293
+
1294
+ async function scaffoldCliType ({
1295
+ packageName,
1296
+ visibility,
1297
+ projectRoot,
1298
+ dialect,
1299
+ publishRegistry,
1300
+ decisions,
1301
+ packageBundlers
1302
+ }) {
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
+ }
1316
+ }
1317
+ })
1318
+ ]);
1319
+
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
+ ]);
1628
1336
  }
1629
1337
 
1630
- function install(nodeVersionCategory) {
1631
- info(`Installing ${nodeVersionCategory} version of node using nvm`, {level: 'secondary'});
1338
+ async function isCli ({packageDetails: {bin}}) {
1339
+ return !!bin;
1340
+ }
1632
1341
 
1633
- const subprocess = execa('. ~/.nvm/nvm.sh && nvm install', {shell: true});
1634
- subprocess.stdout.pipe(process.stdout);
1635
- return subprocess;
1342
+ function liftCli ({projectRoot, packageDetails}) {
1343
+ return liftPublishable({projectRoot, packageDetails});
1636
1344
  }
1637
1345
 
1638
- async function scaffoldNodeVersion ({projectRoot, nodeVersionCategory}) {
1639
- if (!nodeVersionCategory) return undefined;
1346
+ async function scaffoldProjectType ({
1347
+ projectType,
1348
+ projectRoot,
1349
+ projectName,
1350
+ packageName,
1351
+ packageManager,
1352
+ visibility,
1353
+ packageBundlers,
1354
+ scope,
1355
+ decisions,
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`);
1393
+ }
1394
+ }
1640
1395
 
1641
- const lowerCaseCategory = nodeVersionCategory.toLowerCase();
1642
- info(`Configuring ${lowerCaseCategory} version of node`);
1396
+ async function tester$3 ({projectRoot, packageDetails}) {
1397
+ return await isPackage({projectRoot, packageDetails})
1398
+ || await isCli({projectRoot, packageDetails})
1399
+ || isApplication({projectRoot, packageDetails});
1400
+ }
1643
1401
 
1644
- const version = await determineLatestVersionOf(nodeVersionCategory);
1402
+ function vcsRepositoryHostedOnGithub(vcs) {
1403
+ return vcs && 'github' === vcs.host;
1404
+ }
1405
+
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});
1645
1409
 
1646
- await promises$1.writeFile(`${projectRoot}/.nvmrc`, version);
1410
+ let homepage;
1647
1411
 
1648
- await install(nodeVersionCategory);
1412
+ if (vcsRepositoryHostedOnGithub(vcs)) {
1413
+ homepage = `https://github.com/${vcs.owner}/${vcs.name}#readme`;
1649
1414
 
1650
- return version;
1651
- }
1415
+ await mergeIntoExistingPackageJson({projectRoot, config: {homepage}});
1416
+ }
1652
1417
 
1653
- function nvmIsUsed ({projectRoot}) {
1654
- return fileExists(`${projectRoot}/.nvmrc`);
1418
+ return {homepage};
1655
1419
  }
1656
1420
 
1657
- function buildVcsIgnoreLists (vcsIgnoreLists = {}) {
1658
- return {
1659
- files: vcsIgnoreLists.files || [],
1660
- directories: ['/node_modules/', ...vcsIgnoreLists.directories || []]
1661
- };
1662
- }
1421
+ var projectTypes = /*#__PURE__*/Object.freeze({
1422
+ __proto__: null,
1423
+ lift: lifter$2,
1424
+ scaffold: scaffoldProjectType,
1425
+ test: tester$3
1426
+ });
1663
1427
 
1664
1428
  async function chooseProjectTypePlugin ({types, projectType, decisions}) {
1665
1429
  if (!Object.keys(types).length) return 'Other';
@@ -1727,6 +1491,59 @@ async function scaffoldTesting ({
1727
1491
  );
1728
1492
  }
1729
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
+
1730
1547
  async function scaffoldLinting ({projectRoot, packageManager, registries}) {
1731
1548
  return scaffoldLockfileLint({projectRoot, packageManager, registries});
1732
1549
  }
@@ -1755,12 +1572,82 @@ async function scaffoldVerification({
1755
1572
  pathWithinParent
1756
1573
  }),
1757
1574
  scaffoldLinting({projectRoot, packageManager, registries, vcs, pathWithinParent}),
1758
- scaffold$3({projectRoot, packageManager, pathWithinParent})
1575
+ scaffold$1({projectRoot, packageManager, pathWithinParent})
1759
1576
  ]);
1760
1577
 
1761
1578
  return deepmerge.all([testingResults, lintingResults, huskyResults]);
1762
1579
  }
1763
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
+
1764
1651
  async function scaffolder (options) {
1765
1652
  info('Initializing JavaScript project');
1766
1653
 
@@ -1888,25 +1775,133 @@ async function scaffolder (options) {
1888
1775
  projectTypePluginResults
1889
1776
  ]);
1890
1777
 
1891
- // const liftResults = await lift({
1892
- // results: deepmerge({devDependencies: ['npm-run-all2'], packageManager}, mergedContributions),
1893
- // projectRoot,
1894
- // configs,
1895
- // vcs,
1896
- // pathWithinParent
1897
- // });
1898
-
1899
1778
  return {
1900
1779
  ...mergedContributions,
1901
- badges: buildBadgesDetails([mergedContributions/*, liftResults*/]),
1902
1780
  documentation: scaffoldDocumentation({projectTypeResults, packageManager}),
1903
1781
  tags: projectTypeResults.tags,
1904
1782
  vcsIgnore: buildVcsIgnoreLists(mergedContributions.vcsIgnore),
1905
- verificationCommand: `${buildDocumentationCommand(packageManager)} && ${packageManager} test`,
1906
- // 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'}}}
1907
1852
  };
1908
1853
  }
1909
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
+
1910
1905
  async function tester ({projectRoot}) {
1911
1906
  const [nvmFound, jsPackageManagerFound] = await Promise.all([
1912
1907
  nvmIsUsed({projectRoot}),