create-gasket-app 7.0.3-cli.0 → 7.0.4

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 (35) hide show
  1. package/README.md +223 -3
  2. package/lib/commands/create.js +149 -0
  3. package/lib/config/default-plugins.js +15 -0
  4. package/lib/index.d.ts +310 -0
  5. package/lib/index.js +24 -9
  6. package/lib/scaffold/action-wrapper.js +37 -0
  7. package/lib/scaffold/actions/create-hooks.js +27 -0
  8. package/lib/scaffold/actions/generate-files.js +189 -0
  9. package/lib/scaffold/actions/global-prompts.js +162 -0
  10. package/lib/scaffold/actions/index.js +33 -0
  11. package/lib/scaffold/actions/install-modules.js +15 -0
  12. package/lib/scaffold/actions/link-modules.js +19 -0
  13. package/lib/scaffold/actions/load-preset.js +60 -0
  14. package/lib/scaffold/actions/mkdir.js +30 -0
  15. package/lib/scaffold/actions/post-create-hooks.js +31 -0
  16. package/lib/scaffold/actions/preset-config-hooks.js +13 -0
  17. package/lib/scaffold/actions/preset-prompt-hooks.js +17 -0
  18. package/lib/scaffold/actions/print-report.js +89 -0
  19. package/lib/scaffold/actions/prompt-hooks.js +43 -0
  20. package/lib/scaffold/actions/setup-pkg.js +34 -0
  21. package/lib/scaffold/actions/write-gasket-config.js +119 -0
  22. package/lib/scaffold/actions/write-pkg.js +19 -0
  23. package/lib/scaffold/config-builder.js +526 -0
  24. package/lib/scaffold/create-context.js +188 -0
  25. package/lib/scaffold/dump-error-context.js +28 -0
  26. package/lib/scaffold/files.js +30 -0
  27. package/lib/scaffold/readme.js +51 -0
  28. package/lib/scaffold/utils.js +22 -0
  29. package/lib/utils/create-option.js +18 -0
  30. package/lib/utils/index.js +5 -0
  31. package/lib/utils/logo.js +12 -0
  32. package/lib/utils/process-args.js +37 -0
  33. package/lib/utils/process-command.js +50 -0
  34. package/lib/utils/process-options.js +44 -0
  35. package/package.json +36 -9
@@ -0,0 +1,188 @@
1
+ /* eslint-disable max-len, max-statements */
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { readConfig } from '../scaffold/utils.js';
5
+
6
+ /**
7
+ * The CreateRuntime represents a shallow proxy to a CreateContext
8
+ * that automatically adds transactional information for providing
9
+ * CLI users with blame information in the event of conflicts.
10
+ *
11
+ * @param {CreateContext} context - Create context.
12
+ * @param {Plugin} source - Gasket plugin the create context is within.
13
+ * @returns {Proxy} Shallow proxy to the `context` provided that will
14
+ * pass `source` to `context.{files,pkg}.*` methods.
15
+ */
16
+ function makeCreateRuntime(context, source) {
17
+ //
18
+ // Create a set of overrides for our Proxy
19
+ //
20
+ const overrides = {
21
+ source,
22
+ files: {
23
+ add(...globs) {
24
+ context.files.add({ globs, source });
25
+ }
26
+ },
27
+ pkg: {
28
+ extend(fields) {
29
+ context.pkg.extend(fields, source);
30
+ },
31
+
32
+ add(key, value, options) {
33
+ context.pkg.add(key, value, source, options);
34
+ },
35
+
36
+ has(key, value) {
37
+ return context.pkg.has(key, value);
38
+ }
39
+ }
40
+ };
41
+
42
+ return new Proxy(context, {
43
+ get(obj, key) {
44
+ if (overrides[key]) return overrides[key];
45
+ return obj[key];
46
+ },
47
+ set(obj, key, value) {
48
+ if (key !== 'pkg' && key !== 'files' && key !== 'source') {
49
+ obj[key] = value;
50
+ }
51
+ }
52
+ });
53
+ }
54
+
55
+ function flatten(acc, values) {
56
+ return acc.concat(values);
57
+ }
58
+
59
+ /**
60
+ * Create Context
61
+ *
62
+ * @type {CreateContext}
63
+ *
64
+ * -- Added by command args and options
65
+ *
66
+ * @property {String} appName - Short name of the app
67
+ * @property {String} cwd - Current work directory
68
+ * @property {String} dest - Path to the target app (Default: cwd/appName)
69
+ * @property {String} relDest - Relative path to the target app
70
+ * @property {Boolean} extant - Whether or not target directory already exists
71
+ * @property {[String]} localPresets - paths to the local presets packages
72
+ * @property {PresetDesc[]} rawPresets - Raw preset desc from args. Can include version constraint. Added by load-preset if using localPresets.
73
+ * @property {String[]} pkgLinks - Local packages that should be linked
74
+ * @property {String[]} messages - non-error/warning messages to report
75
+ * @property {String[]} warnings - warnings messages to report
76
+ * @property {String[]} errors - error messages to report but do not exit process
77
+ * @property {String[]} nextSteps - any next steps to report for user
78
+ * @property {Set<String>} generatedFiles - any generated files to show in report
79
+ * @property {any[]} presets - Default empty array, populated by load-preset with actual imports
80
+ * @property {Object} presetConfig - Default to object w/empty plugins array to be populated by `presetConfig` hook
81
+ *
82
+ * -- Added by `global-prompts`
83
+ *
84
+ * @property {String} appDescription - Description of app
85
+ * @property {Boolean} gitInit - Should a git repo be initialized and first commit
86
+ * @property {String} testPlugins - Array of testing plugins
87
+ * @property {String} packageManager - Which package manager to use (Default: 'npm')
88
+ * @property {String} installCmd - Derived install command (Default: 'npm install')
89
+ * @property {String} localCmd - Derived local run command (Default: 'npx gasket local')
90
+ * @property {Boolean} destOverride - Whether or not the user wants to override an extant directory
91
+ *
92
+ * -- Added by `load-preset`
93
+ *
94
+ * @property {PresetName[]} presets - Short name of presets
95
+ *
96
+ *
97
+ * -- Added by `setup-pkg`
98
+ *
99
+ * @property {ConfigBuilder} pkg - package.json builder
100
+ * @property {PackageManager} pkgManager - manager to execute npm or yarn commands
101
+ *
102
+ * -- Added by `setup-gasket-config`
103
+ *
104
+ * @property {ConfigBuilder} gasketConfig - gasket.config builder
105
+ *
106
+ * -- Added by `create-hooks`
107
+ *
108
+ * @property {Files} files - Use to add files and templates to generate
109
+ */
110
+ export class CreateContext {
111
+ constructor(initContext = {}) {
112
+ Object.assign(this, initContext);
113
+ }
114
+
115
+ runWith(plugin) {
116
+ return makeCreateRuntime(this, plugin);
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Makes the initial context used through the create command flow.
122
+ *
123
+ * @param {String[]} argv - Commands args
124
+ * @param {Object} options - Command options
125
+ * @returns {CreateContext} context
126
+ */
127
+ export function makeCreateContext(argv = [], options = {}) {
128
+ const appName = argv[0] || 'templated-app';
129
+ const {
130
+ presets = [],
131
+ npmLink = [],
132
+ presetPath = [],
133
+ packageManager,
134
+ prompts,
135
+ config,
136
+ configFile
137
+ } = options;
138
+
139
+ // Flatten the array of array created by the plugins flag – it
140
+ // supports both multiple instances as well as comma-separated lists.
141
+ const rawPresets = presets.reduce(flatten, []);
142
+ const localPresets = presetPath.reduce(flatten, []);
143
+ const pkgLinks = npmLink.reduce(flatten, []);
144
+ const cwd = process.cwd();
145
+ const dest = path.join(cwd, appName);
146
+ const relDest = `.${path.sep}${path.relative(cwd, dest)}`;
147
+ // eslint-disable-next-line no-sync
148
+ const extant = fs.existsSync(dest);
149
+
150
+ /**
151
+ * Input context which will be appended by prompts and passed to create hooks
152
+ *
153
+ * @type {CreateContext}
154
+ */
155
+ const context = new CreateContext({
156
+ destOverride: true,
157
+ cwd,
158
+ dest,
159
+ relDest,
160
+ extant,
161
+ pkgLinks,
162
+ localPresets,
163
+ rawPresets,
164
+ messages: [],
165
+ warnings: [],
166
+ errors: [],
167
+ nextSteps: [],
168
+ generatedFiles: new Set(),
169
+ prompts,
170
+ presets: [],
171
+ presetConfig: {
172
+ plugins: []
173
+ },
174
+ readme: []
175
+ });
176
+
177
+ readConfig(context, { config, configFile });
178
+
179
+ if (packageManager) {
180
+ context.packageManager = packageManager;
181
+ }
182
+ if (appName) {
183
+ context.appName = appName;
184
+ }
185
+
186
+ return context;
187
+ }
188
+
@@ -0,0 +1,28 @@
1
+ import path from 'path';
2
+ import { writeFile } from 'fs/promises';
3
+
4
+ /**
5
+ * If an error occurs during create, dump the context for debugging.
6
+ * The error which cause the exit is also included in the log.
7
+ *
8
+ * @param {CreateContext} context - Create context
9
+ * @param {Error} error - Exiting error
10
+ * @returns {Promise<void>} promise
11
+ */
12
+ export async function dumpErrorContext(context, error) {
13
+ const { cwd } = context;
14
+
15
+ try {
16
+ const report = { exitError: error.stack, ...context };
17
+ const fileName = 'gasket-create-error.log';
18
+
19
+ const filePath = path.join(cwd, fileName);
20
+ await writeFile(filePath, JSON.stringify(report, null, 2), 'utf8');
21
+ console.log(`Error log dumped to: ${filePath}`);
22
+ } catch (err) {
23
+ //
24
+ // eat but print any errors during writing log file
25
+ //
26
+ console.error(`Error writing error log: ${err}`);
27
+ }
28
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Utility for plugins to add files and templates for generating
3
+ *
4
+ * @type {Files}
5
+ */
6
+ export class Files {
7
+ constructor() {
8
+ this.globSets = [];
9
+ }
10
+
11
+ /**
12
+ * Return array of globs
13
+ *
14
+ * @deprecated
15
+ * @returns {string[]} `globby` compatible patterns
16
+ */
17
+ get globs() {
18
+ return this.globSets.reduce((acc, cur) => [...acc, ...cur.globs], []);
19
+ }
20
+
21
+ /**
22
+ * Adds the specified `globby` compatible patterns, `globs`,
23
+ * into the set of all sources for this set of files.
24
+ * @param {string[]} globs - `globby` compatible patterns
25
+ * @param {Object} source - Plugin to blame if conflicts arise from this operation.
26
+ */
27
+ add({ globs, source }) {
28
+ this.globSets.push({ globs, source });
29
+ }
30
+ }
@@ -0,0 +1,51 @@
1
+ import { readFile } from 'fs/promises';
2
+
3
+ /** @type {import('../index').Readme} */
4
+ export default class Readme {
5
+ constructor() {
6
+ this.markdown = [];
7
+ this.links = [];
8
+ }
9
+
10
+ addNewLine() {
11
+ this.markdown.push('');
12
+ return this;
13
+ }
14
+
15
+ heading(content, level = 2) {
16
+ this.markdown.push(`${'#'.repeat(level)} ${content}`);
17
+ return this.addNewLine();
18
+ }
19
+
20
+ subHeading(content) {
21
+ return this.heading(content, 3);
22
+ }
23
+
24
+ content(content) {
25
+ this.markdown.push(content);
26
+ return this.addNewLine();
27
+ }
28
+
29
+ list(items) {
30
+ this.markdown.push(...items.map(item => `- ${item}`));
31
+ return this.addNewLine();
32
+ }
33
+
34
+ link(content, href) {
35
+ this.links.push(`[${content}]: ${href}`);
36
+ return this;
37
+ }
38
+
39
+ codeBlock(content, syntax = '') {
40
+ this.markdown.push(`\`\`\`${syntax}`);
41
+ this.markdown.push(content);
42
+ this.markdown.push('```');
43
+ return this.addNewLine();
44
+ }
45
+
46
+ async markdownFile(path) {
47
+ const content = await readFile(path, 'utf8');
48
+ this.markdown.push(content);
49
+ return this.addNewLine();
50
+ }
51
+ }
@@ -0,0 +1,22 @@
1
+ import path from 'path';
2
+ import { createRequire } from 'module';
3
+ const require = createRequire(import.meta.url);
4
+
5
+ /**
6
+ * Parses JSON file or string to assign to context
7
+ *
8
+ * @param {CreateContext} context - Create context.
9
+ * @param {Object} configFlags - flags to read config from
10
+ * @param {string} configFlags.config - JSON string of config values
11
+ * @param {string} configFlags.configFile - path to JSON file of config values
12
+ */
13
+ export function readConfig(context, { config, configFile }) {
14
+ if (config) {
15
+ const parsedConfig = JSON.parse(config);
16
+ Object.assign(context, parsedConfig);
17
+ } else if (configFile) {
18
+ const parsedConfigFile = require(path.resolve(context.cwd, configFile));
19
+ Object.assign(context, parsedConfigFile);
20
+ }
21
+ }
22
+
@@ -0,0 +1,18 @@
1
+ import { Option } from 'commander';
2
+
3
+ /**
4
+ * createOption - Create a commander option
5
+ * @param {CLICommandOption} definition - The option configuration
6
+ * @returns {CommnaderOption} option
7
+ */
8
+ export function createOption(definition) {
9
+ const option = new Option(...definition.options);
10
+ const { defaultValue, conflicts, parse, hidden, required } = definition;
11
+
12
+ if (conflicts.length) option.conflicts(definition.conflicts);
13
+ if (hidden) option.hideHelp();
14
+ if (typeof defaultValue !== 'undefined') option.default(defaultValue);
15
+ if (parse) option.argParser(parse);
16
+ if (required) option.makeOptionMandatory();
17
+ return option;
18
+ }
@@ -0,0 +1,5 @@
1
+ export { processCommand } from './process-command.js';
2
+ export { processArgs } from './process-args.js';
3
+ export { processOptions } from './process-options.js';
4
+ export { createOption } from './create-option.js';
5
+ export { logo } from './logo.js';
@@ -0,0 +1,12 @@
1
+ export const logo = `
2
+ ▄▄▄▄████▀▀█▄
3
+ ▄████▀▀▀▀▀▀█▄▄██
4
+ ███▀ ▄▄▄▄▄ ▀██▌ ▄▄▄
5
+ ▄███ ███▀▀▀▀██ ██ ██ ██
6
+ █▀▀█ ██▌ ▐█▌ ▄▀▀▀█▄ ▄█▀▀▀▄ ██ ▐██▀ ▄█▀▀█▄ ▀██▀▀
7
+ █▄▄█ ██▌ ▀▀██ ▐█▌ ▄▄▄▄██ ▀▀█▄▄▄ █████ ██▄▄██ ██
8
+ ▀███ ███▄▄▄▄██ ██ ▀▄▄▄▀█▄ ▀▄▄▄█▀ ▄██▄ ██▄ ▀█▄▄▄▄ ▀█▄▄
9
+ ███▄ ▀▀▀ ▀▀ ▄██▌
10
+ ▀████▄▄▄▄▄▄█▀▀██
11
+ ▀▀▀▀████▄▄█▀
12
+ `;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * isValidArg - Validates the argument configuration
3
+ * @param {CLICommandArg} arg The argument configuration
4
+ * @returns {boolean} True if valid, false otherwise
5
+ */
6
+ function isValidArg(arg) {
7
+ const keys = Object.keys(arg);
8
+ if (arg.required && arg.default) return false;
9
+ return keys.length &&
10
+ keys.length <= 4 &&
11
+ arg.name && arg.description;
12
+ }
13
+
14
+ /**
15
+ * processArgs - Process the arguments configuration
16
+ * @param {CLICommandArg[]} args Array of argument configurations
17
+ * @returns {ProccesedCLICommandArg[]} Array of argument definitions
18
+ */
19
+ export function processArgs(args) {
20
+ if (!Array.isArray(args) || !args.every(isValidArg)) throw new Error('Invalid argument(s) configuration');
21
+
22
+ return args.reduce((acc, arg) => {
23
+ const def = [];
24
+ const { name, description, required = false, default: defaultValue } = arg;
25
+ if (required) {
26
+ def.push(`<${name}>`, description);
27
+ } else {
28
+ def.push(`[${name}]`, description);
29
+ }
30
+
31
+ if (defaultValue) def.push(defaultValue);
32
+
33
+ acc.push(def);
34
+ return acc;
35
+
36
+ }, []);
37
+ }
@@ -0,0 +1,50 @@
1
+ import { Command } from 'commander';
2
+ import { processArgs } from './process-args.js';
3
+ import { processOptions } from './process-options.js';
4
+ import { createOption } from './create-option.js';
5
+ const program = new Command();
6
+
7
+ /**
8
+ * isValidCommand - Validates the command configuration
9
+ * @param {CLICommand} command The command configuration
10
+ * @returns {boolean} True if valid, false otherwise
11
+ */
12
+ function isValidCommand(command) {
13
+ const keys = Object.keys(command);
14
+ return keys.length &&
15
+ keys.length <= 7 &&
16
+ command &&
17
+ command.id &&
18
+ command.description &&
19
+ command.action &&
20
+ typeof command.action === 'function';
21
+ }
22
+
23
+ /**
24
+ * processCommand - Process the command configuration
25
+ * @param {CLICommand} command The command configuration
26
+ * @returns {ProccesedCLICommand} The command instance
27
+ */
28
+ export function processCommand(command) {
29
+ if (!isValidCommand(command)) throw new Error('Invalid command configuration');
30
+ const { id, description, action, args, options, hidden = false, default: isDefault = false } = command;
31
+ const cmd = program
32
+ .command(id)
33
+ .description(description)
34
+ .action(action);
35
+
36
+ if (args) {
37
+ const cmdArgs = processArgs(args);
38
+ cmdArgs.forEach(arg => cmd.argument(...arg));
39
+ }
40
+
41
+ if (options) {
42
+ const cmdOpts = processOptions(options);
43
+ cmdOpts.forEach(o => {
44
+ const option = createOption(o);
45
+ cmd.addOption(option);
46
+ });
47
+ }
48
+
49
+ return { command: cmd, hidden, isDefault };
50
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * isValidOption - Validate the option
3
+ * @param {CLICommandOption} option The option configuration
4
+ * @returns {boolean} True if valid, false otherwise
5
+ */
6
+ function isValidOption(option) {
7
+ const keys = Object.keys(option);
8
+ return keys.length &&
9
+ keys.length <= 5 &&
10
+ option.name &&
11
+ option.description;
12
+ }
13
+
14
+ /**
15
+ * processOptions - Process the options configuration
16
+ * @param {CLICommandOption[]} options Array of option configurations
17
+ * @returns {ProccesedCLICommandOption[]} Processed option definitions
18
+ */
19
+ export function processOptions(options) {
20
+ if (!Array.isArray(options) || !options.every(isValidOption)) throw new Error('Invalid option(s) configuration');
21
+
22
+ return options.reduce((acc, option) => {
23
+ const def = [];
24
+ const {
25
+ name,
26
+ description,
27
+ required = false,
28
+ short,
29
+ parse,
30
+ type = 'string',
31
+ conflicts = [],
32
+ hidden = false,
33
+ default: defaultValue
34
+ } = option;
35
+
36
+ const format = required ? `<${name}>` : `[${name}]`;
37
+ const flags = short ? `-${short}, --${name}` : `--${name}`;
38
+
39
+ def.push(`${flags}${type !== 'boolean' ? ` ${format}` : ''}`, description);
40
+ acc.push({ options: def, conflicts, hidden, defaultValue, parse, required });
41
+
42
+ return acc;
43
+ }, []);
44
+ }
package/package.json CHANGED
@@ -1,19 +1,21 @@
1
1
  {
2
2
  "name": "create-gasket-app",
3
- "version": "7.0.3-cli.0",
3
+ "version": "7.0.4",
4
4
  "description": "starter pack for creating a gasket app",
5
- "main": "lib",
5
+ "type": "module",
6
6
  "bin": {
7
7
  "create-gasket-app": "./lib/index.js"
8
8
  },
9
+ "main": "lib/index.js",
10
+ "types": "lib/index.d.ts",
9
11
  "files": [
10
12
  "lib"
11
13
  ],
12
14
  "scripts": {
13
15
  "lint": "eslint .",
14
16
  "lint:fix": "npm run lint -- --fix",
15
- "test": "cross-env NODE_OPTIONS='--unhandled-rejections=strict' jest",
16
- "test:watch": "jest --watch",
17
+ "test": "cross-env NODE_OPTIONS='--unhandled-rejections=strict --experimental-vm-modules' jest",
18
+ "test:watch": "npm run test -- --watch",
17
19
  "test:coverage": "jest --coverage",
18
20
  "posttest": "npm run lint"
19
21
  },
@@ -31,15 +33,37 @@
31
33
  },
32
34
  "homepage": "https://github.com/godaddy/gasket/tree/main/packages/create-gasket-app",
33
35
  "dependencies": {
34
- "@gasket/cli": "^7.0.3-cli.0"
36
+ "@gasket/core": "^7.0.1",
37
+ "@gasket/plugin-command": "^7.0.1",
38
+ "@gasket/plugin-docs": "^7.0.4",
39
+ "@gasket/plugin-docusaurus": "^7.0.3",
40
+ "@gasket/plugin-git": "^7.0.1",
41
+ "@gasket/plugin-logger": "^7.0.4",
42
+ "@gasket/plugin-metadata": "^7.0.4",
43
+ "@gasket/utils": "^7.0.1",
44
+ "commander": "^12.0.0",
45
+ "deepmerge": "^4.3.1",
46
+ "glob": "^8.1.0",
47
+ "handlebars": "^4.7.8",
48
+ "inquirer": "6.5.2",
49
+ "json5": "^2.2.3",
50
+ "mkdirp": "^1.0.4",
51
+ "ora": "3.4.0",
52
+ "pump": "^3.0.0",
53
+ "semver": "^7.5.4",
54
+ "tar-fs": "^2.0.0",
55
+ "untildify": "^4.0.0"
35
56
  },
36
57
  "devDependencies": {
58
+ "@types/inquirer": "^9.0.7",
37
59
  "cross-env": "^7.0.3",
60
+ "diagnostics": "^2.0.2",
61
+ "dotenv": "^16.4.5",
38
62
  "eslint": "^8.56.0",
39
- "eslint-config-godaddy": "^7.0.2",
40
- "eslint-plugin-jest": "^27.6.3",
63
+ "eslint-config-godaddy": "^7.1.1",
64
+ "eslint-plugin-jest": "^28.6.0",
41
65
  "eslint-plugin-json": "^3.1.0",
42
- "eslint-plugin-unicorn": "^44.0.0",
66
+ "eslint-plugin-unicorn": "^55.0.0",
43
67
  "jest": "^29.7.0"
44
68
  },
45
69
  "eslintConfig": {
@@ -57,5 +81,8 @@
57
81
  "eslintIgnore": [
58
82
  "test/fixtures"
59
83
  ],
60
- "gitHead": "ffcc0372192c096dba45d684d9fe3926edd6cc71"
84
+ "jest": {
85
+ "transform": {}
86
+ },
87
+ "gitHead": "af49c0e42e7947e000faa771777b815b90aaffa3"
61
88
  }