create-gasket-app 7.0.0-cli.7 → 7.0.0-next.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 (40) hide show
  1. package/README.md +222 -2
  2. package/lib/commands/create.js +192 -0
  3. package/lib/config/default-plugins.js +7 -0
  4. package/lib/index.d.ts +205 -0
  5. package/lib/index.js +22 -9
  6. package/lib/main.js +19 -0
  7. package/lib/scaffold/action-wrapper.js +36 -0
  8. package/lib/scaffold/actions/apply-preset-config.js +29 -0
  9. package/lib/scaffold/actions/cli-version.js +84 -0
  10. package/lib/scaffold/actions/create-hooks.js +26 -0
  11. package/lib/scaffold/actions/generate-files.js +183 -0
  12. package/lib/scaffold/actions/global-prompts.js +161 -0
  13. package/lib/scaffold/actions/index.js +36 -0
  14. package/lib/scaffold/actions/install-modules.js +18 -0
  15. package/lib/scaffold/actions/link-modules.js +21 -0
  16. package/lib/scaffold/actions/load-pkg-for-debug.js +24 -0
  17. package/lib/scaffold/actions/load-preset.js +82 -0
  18. package/lib/scaffold/actions/mkdir.js +30 -0
  19. package/lib/scaffold/actions/post-create-hooks.js +33 -0
  20. package/lib/scaffold/actions/print-report.js +88 -0
  21. package/lib/scaffold/actions/prompt-hooks.js +90 -0
  22. package/lib/scaffold/actions/setup-pkg.js +47 -0
  23. package/lib/scaffold/actions/write-gasket-config.js +32 -0
  24. package/lib/scaffold/actions/write-pkg.js +23 -0
  25. package/lib/scaffold/config-builder.js +448 -0
  26. package/lib/scaffold/create-context.js +193 -0
  27. package/lib/scaffold/create-engine.js +21 -0
  28. package/lib/scaffold/dump-error-context.js +28 -0
  29. package/lib/scaffold/fetcher.js +194 -0
  30. package/lib/scaffold/files.js +30 -0
  31. package/lib/scaffold/utils.js +115 -0
  32. package/lib/utils/create-option.js +20 -0
  33. package/lib/utils/index.js +16 -0
  34. package/lib/utils/logo.js +14 -0
  35. package/lib/utils/process-args.js +39 -0
  36. package/lib/utils/process-command.js +52 -0
  37. package/lib/utils/process-options.js +46 -0
  38. package/lib/utils/setup.js +25 -0
  39. package/lib/utils/warn-if-outdated.js +76 -0
  40. package/package.json +23 -3
@@ -0,0 +1,82 @@
1
+ const path = require('path');
2
+ const action = require('../action-wrapper');
3
+ const PackageFetcher = require('../fetcher');
4
+ const { presetIdentifier, Loader } = require('@gasket/resolve');
5
+
6
+ const loader = new Loader();
7
+
8
+ /**
9
+ * Fetches the preset packages and loads PresetInfos
10
+ *
11
+ * @param {[String]} rawPresets - Presets names
12
+ * @param {String} cwd - Root path
13
+ * @param {String} from from
14
+ * @returns {PresetInfo[]} loaded presetInfos
15
+ * @private
16
+ */
17
+ async function remotePresets(rawPresets, cwd, from) {
18
+ if (!rawPresets) { return []; }
19
+
20
+ const allRemotePresets = await Promise.all(rawPresets.map(async rawName => {
21
+ const packageName = presetIdentifier(rawName).full;
22
+ const fetcher = new PackageFetcher({ cwd, packageName });
23
+ const pkgPath = await fetcher.clone();
24
+
25
+ const presetInfo = loader.loadPreset(pkgPath, { from: from, rawName }, { shallow: true });
26
+ const { name: presetName, dependencies } = presetInfo.package;
27
+ if (!dependencies) {
28
+ return presetInfo;
29
+ }
30
+
31
+ const presetDeps = Object.keys(dependencies).filter(k => presetIdentifier.isValidFullName(k));
32
+ const rawPresetsDeps = presetDeps.map(presetDep => `${presetDep}@${dependencies[presetDep]}`);
33
+
34
+ const presetInfoDeps = await remotePresets(rawPresetsDeps, cwd, presetName);
35
+ return {
36
+ ...presetInfo,
37
+ presets: presetInfoDeps
38
+ };
39
+ }));
40
+
41
+ return allRemotePresets;
42
+ }
43
+
44
+ /**
45
+ * Loads PresetInfo from local preset package
46
+ *
47
+ * @param {CreateContext} context - Create context
48
+ * @returns {PresetInfo[]} loaded presetInfos
49
+ * @private
50
+ */
51
+ function localPreset(context) {
52
+ const { cwd, localPresets = [] } = context;
53
+ if (!localPresets) { return []; }
54
+
55
+ const presetInfos = [];
56
+ localPresets.forEach(localPresetPath => {
57
+ const pkgPath = path.resolve(cwd, localPresetPath);
58
+ const presetInfo = loader.loadPreset(pkgPath, { from: 'cli' }, { shallow: false });
59
+ presetInfo.rawName = `${presetInfo.package.name}@file:${pkgPath}`;
60
+ presetInfos.push(presetInfo);
61
+ });
62
+
63
+ return presetInfos;
64
+ }
65
+
66
+ /**
67
+ * Downloads the target preset package and adds the package.json
68
+ * contents and plugins dependencies to context.
69
+ *
70
+ * @param {CreateContext} context - Create context
71
+ * @returns {Promise} promise
72
+ */
73
+ async function loadPreset(context) {
74
+ const { rawPresets = [], cwd } = context;
75
+ let presetInfos = await remotePresets(rawPresets, cwd, 'cli');
76
+ presetInfos = presetInfos.concat(localPreset(context));
77
+
78
+ const presets = presetInfos.map(p => presetIdentifier(p.rawName).shortName);
79
+ Object.assign(context, { presets, presetInfos });
80
+ }
81
+
82
+ module.exports = action('Load presets', loadPreset);
@@ -0,0 +1,30 @@
1
+ const { mkdir } = require('fs').promises;
2
+ const action = require('../action-wrapper');
3
+
4
+ /**
5
+ * Validates this instance can execute without common blockers:
6
+ * - Target destination on disk is available. Validate by acquiring
7
+ * a lock through `mkdir`.
8
+ *
9
+ * @param {CreateContext} context - Create context
10
+ * @param {Spinner} spinner - Spinner
11
+ * @returns {Promise} promise
12
+ */
13
+ async function mkDir(context, spinner) {
14
+ const { dest, relDest, extant, destOverride } = context;
15
+
16
+ // At this point, either the directory does not exist, or the user has
17
+ // confirmed that this directory can be overwritten, so we can proceed.
18
+
19
+ if (!extant) {
20
+ spinner.text = `Make directory: ${relDest}`;
21
+ await mkdir(dest);
22
+ } else {
23
+ spinner.text = `Existing directory: ${relDest}`;
24
+ if (!destOverride) {
25
+ throw new Error(`Directory ${relDest} was not allowed to be overwritten.`);
26
+ }
27
+ }
28
+ }
29
+
30
+ module.exports = action('Set up directory', mkDir);
@@ -0,0 +1,33 @@
1
+ const action = require('../action-wrapper');
2
+ const createEngine = require('../create-engine');
3
+ const { runShellCommand } = require('@gasket/utils');
4
+
5
+ /**
6
+ * Executes the `postCreate` hook for all registered plugins.
7
+ *
8
+ * @param {CreateContext} context - Create context
9
+ * @returns {Promise} promise
10
+ */
11
+ async function postCreateHooks(context) {
12
+ const { dest, presets = [], plugins = [] } = context;
13
+
14
+ /**
15
+ * Run an npm script in the context of the created application
16
+ * @param {String} script name of script
17
+ * @returns {Promise} A promise represents if npm succeeds or fails.
18
+ */
19
+ async function runScript(script) {
20
+ return await runShellCommand('npm', ['run', script], { cwd: dest });
21
+ }
22
+
23
+ /**
24
+ * An object with one value for now, so adding more utilities
25
+ * in future is easy.
26
+ */
27
+ const utils = { runScript };
28
+
29
+ const gasket = await createEngine({ dest, presets, plugins });
30
+ await gasket.exec('postCreate', context, utils);
31
+ }
32
+
33
+ module.exports = action('Execute postCreate hooks', postCreateHooks);
@@ -0,0 +1,88 @@
1
+ const { bold } = require('chalk');
2
+ const action = require('../action-wrapper');
3
+ const asciiLogo = require('../../utils/logo');
4
+
5
+ /**
6
+ * Logs a new line in the console
7
+ * @private
8
+ */
9
+ const newline = () => {
10
+ console.log('');
11
+ };
12
+
13
+ /**
14
+ * Converts a camelCase string to Space Case
15
+ * @see: https://stackoverflow.com/questions/4149276/javascript-camelcase-to-regular-form?answertab=active#tab-top
16
+ *
17
+ * @param {string} str - camelCase string to fixup
18
+ * @returns {string} result
19
+ * @private
20
+ */
21
+ const toSpaceCase = str => str.replace(/([A-Z])/g, ' $1')
22
+ .replace(/^./, s => s.toUpperCase());
23
+
24
+ /**
25
+ * Builds the report object from context
26
+ *
27
+ * @param {CreateContext} context - Create context
28
+ * @returns {Object} report
29
+ * @private
30
+ */
31
+ function buildReport(context) {
32
+ const {
33
+ appName,
34
+ dest,
35
+ fullPresets,
36
+ plugins,
37
+ generatedFiles: generatedFilesSet,
38
+ messages,
39
+ warnings,
40
+ errors,
41
+ nextSteps
42
+ } = context;
43
+
44
+ const generatedFiles = Array.from(generatedFilesSet);
45
+ generatedFiles.sort();
46
+
47
+ return {
48
+ appName,
49
+ output: dest,
50
+ presets: fullPresets,
51
+ plugins,
52
+ generatedFiles,
53
+ messages,
54
+ warnings,
55
+ errors,
56
+ nextSteps
57
+ };
58
+ }
59
+
60
+ /**
61
+ * Outputs create command details to the console
62
+
63
+ * @param {CreateContext} context - Create context
64
+ */
65
+ function printReport(context) {
66
+ const report = buildReport(context);
67
+ const { warnings, errors } = context;
68
+
69
+ console.log(`✨Success!
70
+
71
+ Finished with ${warnings.length} warnings and ${errors.length} errors using
72
+ ` + asciiLogo);
73
+
74
+ Object.entries(report).forEach(([k, v]) => {
75
+ if (!v || !v.length) return;
76
+ newline();
77
+ console.log(`${bold(toSpaceCase(k))}`);
78
+ if (Array.isArray(v)) {
79
+ v.map(o => console.log(` ${o}`));
80
+ } else {
81
+ console.log(` ${v}`);
82
+ }
83
+ });
84
+
85
+ newline();
86
+ }
87
+
88
+ module.exports = action('Print report', printReport, { startSpinner: false });
@@ -0,0 +1,90 @@
1
+ const inquirer = require('inquirer');
2
+ const action = require('../action-wrapper');
3
+ const { addPluginsToContext, addPluginsToPkg, getPluginsWithVersions } = require('../utils');
4
+ const createEngine = require('../create-engine');
5
+
6
+ /**
7
+ * Create the `addPlugins` function with context
8
+ *
9
+ * @param {CreateContext} context - Create context
10
+ * @returns {addPlugins} addPlugins
11
+ */
12
+ const createAddPlugins = context => {
13
+ /**
14
+ * Allows plugins to add plugins based on user input.
15
+ *
16
+ * @typedef {Function} addPlugins
17
+ *
18
+ * @param {...PluginDesc} pluginsToAdd - Additional plugins to add
19
+ * @returns {Promise} promise
20
+ */
21
+ return async function addPlugins(...pluginsToAdd) {
22
+ const { pkg, pkgLinks = [], pkgManager } = context;
23
+
24
+ addPluginsToContext(pluginsToAdd, context);
25
+ const pluginIds = await getPluginsWithVersions(pluginsToAdd, pkgManager);
26
+ addPluginsToPkg(pluginIds, pkg);
27
+
28
+ //
29
+ // Install new plugins if not already linked
30
+ //
31
+ const toInstall = pluginIds.filter(p => !pkgLinks.includes(p.fullName));
32
+ if (toInstall.length) {
33
+ await pkgManager.install(toInstall.map(p => p.full));
34
+ //
35
+ // relink because npm blows them away on installs...
36
+ //
37
+ if (pkgLinks.length) {
38
+ await pkgManager.link(pkgLinks);
39
+ }
40
+ }
41
+
42
+ await execPluginPrompts(context, pluginIds.map(p => p.shortName));
43
+ };
44
+ };
45
+
46
+ /**
47
+ * Initializes engine with provide preset and plugins
48
+ * to execute their prompt lifecycle hooks.
49
+ *
50
+ * @param {CreateContext} context - Create context
51
+ * @param {String[]} plugins - plugins to load
52
+ * @param {String} [presets] - presets to load
53
+ * @returns {Promise} promise
54
+ * @private
55
+ */
56
+ async function execPluginPrompts(context, plugins = [], presets = []) {
57
+ const { dest } = context;
58
+
59
+ const gasket = await createEngine({ dest, presets, plugins });
60
+
61
+ //
62
+ // @see: https://github.com/SBoudrias/Inquirer.js/#inquirercreatepromptmodule---prompt-function
63
+ //
64
+ const prompt = context.prompts ? inquirer.createPromptModule() : () => ({});
65
+ const addPlugins = createAddPlugins(context);
66
+
67
+ const nextContext = await gasket.execWaterfall('prompt', context, { prompt, addPlugins });
68
+ //
69
+ // Ensure the original context is transformed in case a prompt returns a new object
70
+ //
71
+ Object.assign(context, nextContext);
72
+ }
73
+
74
+ /**
75
+ * Executes the `prompt` hook for all registered plugins.
76
+ * Adds `prompt` and `addPlugins` util functions for prompting features.
77
+ *
78
+ * @param {CreateContext} context - Create context
79
+ * @returns {Promise} promise
80
+ */
81
+ async function promptHooks(context) {
82
+ //
83
+ // Because `execPluginPrompts` is recursively, we need to start it
84
+ // with the processPlugins and presets from our initial context
85
+ //
86
+ const { presets, plugins } = context;
87
+ await execPluginPrompts(context, plugins, presets);
88
+ }
89
+
90
+ module.exports = action('Plugin prompts', promptHooks, { startSpinner: false });
@@ -0,0 +1,47 @@
1
+ const action = require('../action-wrapper');
2
+ const ConfigBuilder = require('../config-builder');
3
+ const { addPluginsToPkg, getPluginsWithVersions } = require('../utils');
4
+ const { PackageManager } = require('@gasket/utils');
5
+ const { presetIdentifier } = require('@gasket/resolve');
6
+ const { devDependencies } = require('../../../package');
7
+
8
+ /**
9
+ * Initializes the ConfigBuilder builder and adds to context.
10
+ *
11
+ * @param {CreateContext} context - Create context
12
+ * @returns {Promise} promise
13
+ */
14
+ async function setupPkg(context) {
15
+ const { appName, appDescription, presetInfos = [], rawPlugins = [], cliVersionRequired, warnings } = context;
16
+
17
+ const pkg = ConfigBuilder.createPackageJson({
18
+ name: appName,
19
+ version: '0.0.0',
20
+ description: appDescription
21
+ }, { warnings });
22
+
23
+ // The preset package itself must be included in the dependencies
24
+ // of the `pkg` to be bootstrapped.
25
+ pkg.add('dependencies', presetInfos.reduce((acc, presetInfo) => {
26
+ // Use rawName in case version or file path was set in cli args
27
+ // Otherwise fallback to resolved version
28
+ const { fullName, version } = presetIdentifier(presetInfo.rawName).withVersion(`^${presetInfo.version}`);
29
+
30
+ acc[fullName] = version;
31
+ return acc;
32
+ }, {
33
+ '@gasket/cli': cliVersionRequired
34
+ }));
35
+
36
+ pkg.add('depencencies', {
37
+ dotenv: devDependencies.dotenv
38
+ });
39
+
40
+ const pkgManager = new PackageManager(context);
41
+ const pluginIds = await getPluginsWithVersions(rawPlugins, pkgManager);
42
+ addPluginsToPkg(pluginIds, pkg);
43
+
44
+ Object.assign(context, { pkg, pkgManager });
45
+ }
46
+
47
+ module.exports = action('Set up package.json', setupPkg, { startSpinner: false });
@@ -0,0 +1,32 @@
1
+ const JSON5 = require('json5');
2
+ const path = require('path');
3
+ const { writeFile } = require('fs').promises;
4
+ const action = require('../action-wrapper');
5
+
6
+ /**
7
+ * Writes the contents of `pkg` to the app's package.json.
8
+ *
9
+ * @param {CreateContext} context - Create context
10
+ * @returns {Promise} promise
11
+ */
12
+ async function writeGasketConfig(context) {
13
+ const { dest, gasketConfig, presets, plugins, generatedFiles } = context;
14
+ const fileName = 'gasket.config.js';
15
+ const filePath = path.join(dest, fileName);
16
+
17
+ gasketConfig.add('plugins', {
18
+ presets,
19
+ ...(plugins && plugins.length ? { add: plugins } : {})
20
+ });
21
+
22
+ //
23
+ // Use JSON5 to stringify the config and add export for CommonJS
24
+ //
25
+ const contents = 'module.exports = ' +
26
+ JSON5.stringify(gasketConfig, null, 2) + ';\n';
27
+
28
+ await writeFile(filePath, contents, 'utf8');
29
+ generatedFiles.add(fileName);
30
+ }
31
+
32
+ module.exports = action('Write gasket config', writeGasketConfig);
@@ -0,0 +1,23 @@
1
+ const path = require('path');
2
+ const { writeFile } = require('fs').promises;
3
+ const action = require('../action-wrapper');
4
+
5
+ /**
6
+ * Writes the contents of `pkg` to the app's package.json.
7
+ *
8
+ * @param {CreateContext} context - Create context
9
+ * @param {Boolean} update - Is an update step
10
+ * @returns {Promise} promise
11
+ */
12
+ async function writePkg(context) {
13
+ const { dest, pkg, generatedFiles } = context;
14
+ const fileName = 'package.json';
15
+ const filePath = path.join(dest, fileName);
16
+
17
+ await writeFile(filePath, JSON.stringify(pkg, null, 2), 'utf8');
18
+ generatedFiles.add(fileName);
19
+ }
20
+
21
+ module.exports = action('Write package.json', writePkg);
22
+
23
+ module.exports.update = action('Update package.json', writePkg);