create-gasket-app 7.0.1-cli.0 → 7.0.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.
- package/README.md +223 -3
- package/lib/commands/create.js +149 -0
- package/lib/config/default-plugins.js +15 -0
- package/lib/index.d.ts +309 -0
- package/lib/index.js +24 -9
- package/lib/scaffold/action-wrapper.js +37 -0
- package/lib/scaffold/actions/create-hooks.js +27 -0
- package/lib/scaffold/actions/generate-files.js +189 -0
- package/lib/scaffold/actions/global-prompts.js +153 -0
- package/lib/scaffold/actions/index.js +33 -0
- package/lib/scaffold/actions/install-modules.js +15 -0
- package/lib/scaffold/actions/link-modules.js +19 -0
- package/lib/scaffold/actions/load-preset.js +60 -0
- package/lib/scaffold/actions/mkdir.js +30 -0
- package/lib/scaffold/actions/post-create-hooks.js +31 -0
- package/lib/scaffold/actions/preset-config-hooks.js +13 -0
- package/lib/scaffold/actions/preset-prompt-hooks.js +17 -0
- package/lib/scaffold/actions/print-report.js +89 -0
- package/lib/scaffold/actions/prompt-hooks.js +43 -0
- package/lib/scaffold/actions/setup-pkg.js +34 -0
- package/lib/scaffold/actions/write-gasket-config.js +119 -0
- package/lib/scaffold/actions/write-pkg.js +19 -0
- package/lib/scaffold/config-builder.js +526 -0
- package/lib/scaffold/create-context.js +188 -0
- package/lib/scaffold/dump-error-context.js +28 -0
- package/lib/scaffold/files.js +30 -0
- package/lib/scaffold/readme.js +51 -0
- package/lib/scaffold/utils.js +22 -0
- package/lib/utils/create-option.js +18 -0
- package/lib/utils/index.js +5 -0
- package/lib/utils/logo.js +12 -0
- package/lib/utils/process-args.js +37 -0
- package/lib/utils/process-command.js +50 -0
- package/lib/utils/process-options.js +44 -0
- package/package.json +36 -9
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import action from '../action-wrapper.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* What is your app description?
|
|
6
|
+
*
|
|
7
|
+
* @param {CreateContext} context - Create context
|
|
8
|
+
* @param {function} prompt - function to prompt user
|
|
9
|
+
* @returns {Promise} promise
|
|
10
|
+
*/
|
|
11
|
+
async function chooseAppDescription(context, prompt) {
|
|
12
|
+
if (!('appDescription' in context)) {
|
|
13
|
+
const { appDescription } = await prompt([
|
|
14
|
+
{
|
|
15
|
+
name: 'appDescription',
|
|
16
|
+
message: 'What is your app description?',
|
|
17
|
+
type: 'input',
|
|
18
|
+
default: 'A basic gasket app'
|
|
19
|
+
}
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
Object.assign(context, { appDescription });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* What package manager do you want to use?
|
|
28
|
+
*
|
|
29
|
+
* @param {CreateContext} context - Create context
|
|
30
|
+
* @param {function} prompt - function to prompt user
|
|
31
|
+
* @returns {Promise} promise
|
|
32
|
+
*/
|
|
33
|
+
async function choosePackageManager(context, prompt) {
|
|
34
|
+
const packageManager =
|
|
35
|
+
context.packageManager ||
|
|
36
|
+
(
|
|
37
|
+
await prompt([
|
|
38
|
+
{
|
|
39
|
+
name: 'packageManager',
|
|
40
|
+
message: 'Which packager would you like to use?',
|
|
41
|
+
type: 'list',
|
|
42
|
+
choices: [{ name: 'npm' }, { name: 'pnpm' }, { name: 'yarn' }]
|
|
43
|
+
}
|
|
44
|
+
])
|
|
45
|
+
).packageManager;
|
|
46
|
+
|
|
47
|
+
const installCmd = context.installCmd || `${packageManager} install`;
|
|
48
|
+
|
|
49
|
+
const localCmd = context.localCmd || `${packageManager} run local`;
|
|
50
|
+
|
|
51
|
+
Object.assign(context, {
|
|
52
|
+
packageManager,
|
|
53
|
+
installCmd,
|
|
54
|
+
localCmd
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Choose your unit test suite and integration test suite
|
|
60
|
+
*
|
|
61
|
+
* @param {CreateContext} context - Create context
|
|
62
|
+
* @param {function} prompt - function to prompt user
|
|
63
|
+
* @returns {Promise} promise
|
|
64
|
+
*/
|
|
65
|
+
async function chooseTestPlugins(context, prompt) {
|
|
66
|
+
const knownTestPlugins = {
|
|
67
|
+
unit: { mocha: '@gasket/plugin-mocha', jest: '@gasket/plugin-jest' },
|
|
68
|
+
integration: { cypress: '@gasket/plugin-cypress' }
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const testTypes = ['unit', 'integration'];
|
|
72
|
+
const testPlugins = [];
|
|
73
|
+
|
|
74
|
+
if (!('testPlugins' in context)) {
|
|
75
|
+
for (const type of testTypes) {
|
|
76
|
+
if (type + 'TestSuite' in context) {
|
|
77
|
+
const testSuite = knownTestPlugins[type][context[type + 'TestSuite']];
|
|
78
|
+
if (testSuite) testPlugins.push(testSuite);
|
|
79
|
+
} else {
|
|
80
|
+
const plugin = await promptForTestPlugin(
|
|
81
|
+
prompt,
|
|
82
|
+
`Choose your ${type} test suite`,
|
|
83
|
+
Object.entries(knownTestPlugins[type]).map(([name, value]) => ({ name, value }))
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
if (plugin) testPlugins.push(plugin);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (testPlugins.length > 0) {
|
|
91
|
+
Object.assign(context, { testPlugins });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function promptForTestPlugin(prompt, message, choices) {
|
|
97
|
+
const { testPlugin } = await prompt([
|
|
98
|
+
{
|
|
99
|
+
name: 'testPlugin',
|
|
100
|
+
message,
|
|
101
|
+
type: 'list',
|
|
102
|
+
choices: [{ name: 'none', value: 'none' }, ...choices]
|
|
103
|
+
}
|
|
104
|
+
]);
|
|
105
|
+
|
|
106
|
+
return testPlugin !== 'none' ? testPlugin : null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Given that gasket is creating in an already existing directory, it should
|
|
111
|
+
* confirm with the user that it's intentionally overwriting that directory
|
|
112
|
+
*
|
|
113
|
+
* @param {CreateContext} context - Create context
|
|
114
|
+
* @param {function} prompt - function to prompt user
|
|
115
|
+
* @returns {Promise} promise
|
|
116
|
+
*/
|
|
117
|
+
async function allowExtantOverwriting(context, prompt) {
|
|
118
|
+
const { dest, extant } = context;
|
|
119
|
+
if (extant && !('destOverride' in context)) {
|
|
120
|
+
const { destOverride } = await prompt([
|
|
121
|
+
{
|
|
122
|
+
name: 'destOverride',
|
|
123
|
+
type: 'confirm',
|
|
124
|
+
message: `Override contents of ${dest} ?`,
|
|
125
|
+
default: true
|
|
126
|
+
}
|
|
127
|
+
]);
|
|
128
|
+
|
|
129
|
+
Object.assign(context, { destOverride });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export const questions = [
|
|
134
|
+
chooseAppDescription,
|
|
135
|
+
choosePackageManager,
|
|
136
|
+
chooseTestPlugins,
|
|
137
|
+
allowExtantOverwriting
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Fire off prompts for user input
|
|
142
|
+
*
|
|
143
|
+
* @param {CreateContext} context - Create context
|
|
144
|
+
* @returns {Promise} promise
|
|
145
|
+
*/
|
|
146
|
+
async function globalPrompts({ context }) {
|
|
147
|
+
const prompt = context.prompts ? inquirer.createPromptModule() : () => ({});
|
|
148
|
+
for (var fn of questions) {
|
|
149
|
+
await fn(context, prompt);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export default action('Global prompts', globalPrompts, { startSpinner: false });
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import mkDir from './mkdir.js';
|
|
2
|
+
import loadPreset from './load-preset.js';
|
|
3
|
+
import globalPrompts from './global-prompts.js';
|
|
4
|
+
import setupPkg from './setup-pkg.js';
|
|
5
|
+
import writePkg from './write-pkg.js';
|
|
6
|
+
import installModules from './install-modules.js';
|
|
7
|
+
import linkModules from './link-modules.js';
|
|
8
|
+
import writeGasketConfig from './write-gasket-config.js';
|
|
9
|
+
import presetPromptHooks from './preset-prompt-hooks.js';
|
|
10
|
+
import presetConfigHooks from './preset-config-hooks.js';
|
|
11
|
+
import promptHooks from './prompt-hooks.js';
|
|
12
|
+
import createHooks from './create-hooks.js';
|
|
13
|
+
import generateFiles from './generate-files.js';
|
|
14
|
+
import postCreateHooks from './post-create-hooks.js';
|
|
15
|
+
import printReport from './print-report.js';
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
mkDir,
|
|
19
|
+
loadPreset,
|
|
20
|
+
globalPrompts,
|
|
21
|
+
setupPkg,
|
|
22
|
+
writePkg,
|
|
23
|
+
installModules,
|
|
24
|
+
linkModules,
|
|
25
|
+
writeGasketConfig,
|
|
26
|
+
presetPromptHooks,
|
|
27
|
+
presetConfigHooks,
|
|
28
|
+
promptHooks,
|
|
29
|
+
createHooks,
|
|
30
|
+
postCreateHooks,
|
|
31
|
+
generateFiles,
|
|
32
|
+
printReport
|
|
33
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import action from '../action-wrapper.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Installs node_modules using the selected package manager
|
|
5
|
+
*
|
|
6
|
+
* @param {CreateContext} context - Create context
|
|
7
|
+
* @returns {Promise} promise
|
|
8
|
+
*/
|
|
9
|
+
async function installModules({ context }) {
|
|
10
|
+
const { pkgManager } = context;
|
|
11
|
+
|
|
12
|
+
await pkgManager.install();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default action('Install node modules', installModules);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import action from '../action-wrapper.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Links local packages using the selected package manager
|
|
5
|
+
*
|
|
6
|
+
* @param {CreateContext} context - Create context
|
|
7
|
+
* @param {Spinner} spinner - Spinner
|
|
8
|
+
* @returns {Promise} promise
|
|
9
|
+
*/
|
|
10
|
+
async function linkModules({ context, spinner }) {
|
|
11
|
+
const { pkgLinks, pkgManager } = context;
|
|
12
|
+
|
|
13
|
+
if (pkgLinks && pkgLinks.length) {
|
|
14
|
+
spinner.start();
|
|
15
|
+
await pkgManager.link(pkgLinks);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default action('Link node modules', linkModules, { startSpinner: false });
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import action from '../action-wrapper.js';
|
|
4
|
+
import { default as gasketUtils } from '@gasket/utils';
|
|
5
|
+
import { mkdtemp } from 'fs/promises';
|
|
6
|
+
import { createRequire } from 'module';
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
const hasVersionOrTag = /@([\^~]?\d+\.\d+\.\d+(?:-[\d\w.-]+)?|[\^~]?\d+\.\d+\.\d+|[a-zA-Z]+|file:.+)$/;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* loadPresets - Load presets to temp directory
|
|
12
|
+
* @param {CreateContext} context - Create context
|
|
13
|
+
*/
|
|
14
|
+
async function loadPresets({ context }) {
|
|
15
|
+
const tmpDir = await mkdtemp(path.join(os.tmpdir(), `gasket-create-${context.appName}`));
|
|
16
|
+
context.tmpDir = tmpDir;
|
|
17
|
+
|
|
18
|
+
const modPath = path.join(tmpDir, 'node_modules');
|
|
19
|
+
const pkgManager = new gasketUtils.PackageManager({
|
|
20
|
+
packageManager: context.packageManager,
|
|
21
|
+
dest: tmpDir
|
|
22
|
+
});
|
|
23
|
+
const pkgVerb = pkgManager.manager === 'npm' ? 'install' : 'add';
|
|
24
|
+
|
|
25
|
+
const remotePresets = context.rawPresets.map(async preset => {
|
|
26
|
+
const parts = hasVersionOrTag.test(preset) && preset.split('@').filter(Boolean);
|
|
27
|
+
const name = parts ? `@${parts[0]}` : preset;
|
|
28
|
+
const version = parts ? `@${parts[1]}` : '@latest';
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
// Install the preset
|
|
32
|
+
await pkgManager.exec(pkgVerb, [`${name}${version}`]);
|
|
33
|
+
// Get the package file
|
|
34
|
+
const pkgFile = require(path.join(modPath, name, 'package.json'));
|
|
35
|
+
// Import the preset via the package file attrs, name and main
|
|
36
|
+
// We can't specify the cwd for the import, so we need to use the full path
|
|
37
|
+
// expects type:module & "main": "lib/fullpath.js"
|
|
38
|
+
const mod = await import(`${modPath}/${name}/${pkgFile.main}`);
|
|
39
|
+
return mod.default?.default || mod.default || mod;
|
|
40
|
+
} catch (err) {
|
|
41
|
+
throw new Error(`Failed to install preset ${name}${version}: ${err.message}`);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const localPresets = context.localPresets.map(async localPresetPath => {
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
await pkgManager.exec(pkgVerb, [localPresetPath]);
|
|
49
|
+
const pkgFile = require(path.join(localPresetPath, 'package.json'));
|
|
50
|
+
const mod = await import(`${modPath}/${pkgFile.name}/${pkgFile.main}`);
|
|
51
|
+
return mod.default?.default || mod.default || mod;
|
|
52
|
+
} catch (err) {
|
|
53
|
+
throw new Error(`Failed to install local preset ${localPresetPath}`);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
context.presets = await Promise.all([...remotePresets, ...localPresets]);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export default action('Load presets', loadPresets);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { mkdir } from 'fs/promises';
|
|
2
|
+
import action from '../action-wrapper.js';
|
|
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
|
+
export default action('Set up directory', mkDir);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import action from '../action-wrapper.js';
|
|
2
|
+
import { runShellCommand } from '@gasket/utils';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Executes the `postCreate` hook for all registered plugins.
|
|
6
|
+
*
|
|
7
|
+
* @param {GasketEngine} gasket - Gasket API
|
|
8
|
+
* @param {CreateContext} context - Create context
|
|
9
|
+
* @returns {Promise} promise
|
|
10
|
+
*/
|
|
11
|
+
async function postCreateHooks({ gasket, context }) {
|
|
12
|
+
const { dest } = 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
|
+
await gasket.exec('postCreate', context, utils);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default action('Execute postCreate hooks', postCreateHooks);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import action from '../action-wrapper.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* presetConfigHooks - exec `presetConfig` hook
|
|
5
|
+
* @param {Gasket} gasket - Preset gasket instance
|
|
6
|
+
* @param {CreateContext} context - Create context
|
|
7
|
+
*/
|
|
8
|
+
async function presetConfigHooks({ gasket, context }) {
|
|
9
|
+
const config = await gasket.execWaterfall('presetConfig', context);
|
|
10
|
+
Object.assign(context.presetConfig, config);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default action('Preset Config', presetConfigHooks, { startSpinner: false });
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import action from '../action-wrapper.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* presetPromptHooks - exec `presetPrompt` hook
|
|
6
|
+
* @param {Gasket} gasket - Preset gasket instance
|
|
7
|
+
* @param {CreateContext} context - Create context
|
|
8
|
+
*/
|
|
9
|
+
async function presetPromptHooks({ gasket, context }) {
|
|
10
|
+
const prompt = context.prompts ? inquirer.createPromptModule() : () => ({});
|
|
11
|
+
|
|
12
|
+
const nextContext = await gasket.execWaterfall('presetPrompt', context, { prompt });
|
|
13
|
+
|
|
14
|
+
Object.assign(context, nextContext);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default action('Preset prompts', presetPromptHooks, { startSpinner: false });
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import pkg from 'chalk';
|
|
2
|
+
const { bold } = pkg;
|
|
3
|
+
import action from '../action-wrapper.js';
|
|
4
|
+
import { logo } from '../../utils/index.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Logs a new line in the console
|
|
8
|
+
* @private
|
|
9
|
+
*/
|
|
10
|
+
const newline = () => {
|
|
11
|
+
console.log('');
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Converts a camelCase string to Space Case
|
|
16
|
+
* @see: https://stackoverflow.com/questions/4149276/javascript-camelcase-to-regular-form?answertab=active#tab-top
|
|
17
|
+
*
|
|
18
|
+
* @param {string} str - camelCase string to fixup
|
|
19
|
+
* @returns {string} result
|
|
20
|
+
* @private
|
|
21
|
+
*/
|
|
22
|
+
const toSpaceCase = str => str.replace(/([A-Z])/g, ' $1')
|
|
23
|
+
.replace(/^./, s => s.toUpperCase());
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Builds the report object from context
|
|
27
|
+
*
|
|
28
|
+
* @param {CreateContext} context - Create context
|
|
29
|
+
* @returns {Object} report
|
|
30
|
+
* @private
|
|
31
|
+
*/
|
|
32
|
+
function buildReport(context) {
|
|
33
|
+
const {
|
|
34
|
+
appName,
|
|
35
|
+
dest,
|
|
36
|
+
fullPresets,
|
|
37
|
+
plugins,
|
|
38
|
+
generatedFiles: generatedFilesSet,
|
|
39
|
+
messages,
|
|
40
|
+
warnings,
|
|
41
|
+
errors,
|
|
42
|
+
nextSteps
|
|
43
|
+
} = context;
|
|
44
|
+
|
|
45
|
+
const generatedFiles = Array.from(generatedFilesSet);
|
|
46
|
+
generatedFiles.sort();
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
appName,
|
|
50
|
+
output: dest,
|
|
51
|
+
presets: fullPresets,
|
|
52
|
+
plugins,
|
|
53
|
+
generatedFiles,
|
|
54
|
+
messages,
|
|
55
|
+
warnings,
|
|
56
|
+
errors,
|
|
57
|
+
nextSteps
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Outputs create command details to the console
|
|
63
|
+
|
|
64
|
+
* @param {CreateContext} context - Create context
|
|
65
|
+
*/
|
|
66
|
+
async function printReport({ context }) {
|
|
67
|
+
const report = buildReport(context);
|
|
68
|
+
const { warnings, errors } = context;
|
|
69
|
+
|
|
70
|
+
console.log(`✨Success!
|
|
71
|
+
|
|
72
|
+
Finished with ${warnings.length} warnings and ${errors.length} errors using
|
|
73
|
+
` + logo);
|
|
74
|
+
|
|
75
|
+
Object.entries(report).forEach(([k, v]) => {
|
|
76
|
+
if (!v || !v.length) return;
|
|
77
|
+
newline();
|
|
78
|
+
console.log(`${bold(toSpaceCase(k))}`);
|
|
79
|
+
if (Array.isArray(v)) {
|
|
80
|
+
v.map(o => console.log(` ${o}`));
|
|
81
|
+
} else {
|
|
82
|
+
console.log(` ${v}`);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
newline();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export default action('Print report', printReport, { startSpinner: false });
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import action from '../action-wrapper.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Initializes engine with provide preset and plugins
|
|
6
|
+
* to execute their prompt lifecycle hooks.
|
|
7
|
+
*
|
|
8
|
+
* @param {CreateContext} context - Create context
|
|
9
|
+
* @param {String[]} plugins - plugins to load
|
|
10
|
+
* @param {String} [presets] - presets to load
|
|
11
|
+
* @returns {Promise} promise
|
|
12
|
+
* @private
|
|
13
|
+
*/
|
|
14
|
+
async function execPluginPrompts(gasket, context) {
|
|
15
|
+
//
|
|
16
|
+
// @see: https://github.com/SBoudrias/Inquirer.js/#inquirercreatepromptmodule---prompt-function
|
|
17
|
+
//
|
|
18
|
+
const prompt = context.prompts ? inquirer.createPromptModule() : () => ({});
|
|
19
|
+
const nextContext = await gasket.execWaterfall('prompt', context, { prompt });
|
|
20
|
+
//
|
|
21
|
+
// Ensure the original context is transformed in case a prompt returns a new object
|
|
22
|
+
//
|
|
23
|
+
Object.assign(context, nextContext);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Executes the `prompt` hook for all registered plugins.
|
|
28
|
+
* Adds `prompt` util function for prompting features.
|
|
29
|
+
*
|
|
30
|
+
* @param {GasketEngine} gasket - Gasket API
|
|
31
|
+
* @param {CreateContext} context - Create context
|
|
32
|
+
* @returns {Promise} promise
|
|
33
|
+
*/
|
|
34
|
+
async function promptHooks({ gasket, context }) {
|
|
35
|
+
//
|
|
36
|
+
// Because `execPluginPrompts` is recursively, we need to start it
|
|
37
|
+
// with the processPlugins and presets from our initial context
|
|
38
|
+
//
|
|
39
|
+
const { plugins } = context;
|
|
40
|
+
await execPluginPrompts(gasket, context, plugins);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export default action('Plugin prompts', promptHooks, { startSpinner: false });
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import action from '../action-wrapper.js';
|
|
2
|
+
import { ConfigBuilder } from '../config-builder.js';
|
|
3
|
+
import { default as gasketUtils } from '@gasket/utils';
|
|
4
|
+
import { createRequire } from 'module';
|
|
5
|
+
const require = createRequire(import.meta.url);
|
|
6
|
+
const { dependencies } = require('../../../package.json');
|
|
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, warnings } = context;
|
|
16
|
+
|
|
17
|
+
const pkg = ConfigBuilder.createPackageJson({
|
|
18
|
+
name: appName,
|
|
19
|
+
version: '0.0.0',
|
|
20
|
+
description: appDescription,
|
|
21
|
+
type: 'module'
|
|
22
|
+
}, { warnings });
|
|
23
|
+
|
|
24
|
+
// Add critical dependencies
|
|
25
|
+
pkg.add('dependencies', {
|
|
26
|
+
'@gasket/core': dependencies['@gasket/core'],
|
|
27
|
+
'@gasket/utils': dependencies['@gasket/utils']
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const pkgManager = new gasketUtils.PackageManager(context);
|
|
31
|
+
Object.assign(context, { pkg, pkgManager });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default action('Set up package.json', setupPkg, { startSpinner: false });
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { default as JSON5 } from 'json5';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { writeFile } from 'fs/promises';
|
|
4
|
+
import action from '../action-wrapper.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* writePluginImports - Write string imports as value(s)
|
|
8
|
+
* @param {string[]} plugins - Array of plugin import names
|
|
9
|
+
* @returns {string} - Array of plugin imports as string
|
|
10
|
+
*/
|
|
11
|
+
function writePluginImports(plugins) {
|
|
12
|
+
return plugins.reduce((acc, cur, index) => {
|
|
13
|
+
acc += `\t\t${cur}${index < plugins.length - 1 ? ',\n' : ''}`;
|
|
14
|
+
return acc;
|
|
15
|
+
}, '');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* writeImports - Write imports to file using key value pairs
|
|
20
|
+
* @param {{[pluginImport: string]: string}[]} imports - Array of key value pairs for imports
|
|
21
|
+
* @returns {string} - import declarations as string
|
|
22
|
+
*/
|
|
23
|
+
function writeImports(imports) {
|
|
24
|
+
if (!imports) return '';
|
|
25
|
+
return `${Object.entries(imports).map(([importDef, importPath]) => `import ${importDef} from '${importPath}';`).join('\n')}\n`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* createInjectionAssignments - replace object path with a temp string value
|
|
30
|
+
* @param {GasketConfigDefinition} config - gasket config object
|
|
31
|
+
* @param {{ [configKey]: string }} assignments - key value pairs of config keys to replace
|
|
32
|
+
* @returns {string} - string if assignments is falsy
|
|
33
|
+
*/
|
|
34
|
+
function createInjectionAssignments(config, assignments) {
|
|
35
|
+
if (!assignments) return '';
|
|
36
|
+
Object.keys(assignments).forEach((key) => {
|
|
37
|
+
const keys = key.split('.');
|
|
38
|
+
|
|
39
|
+
keys.reduce((acc, cur, index) => {
|
|
40
|
+
if (index === keys.length - 1) {
|
|
41
|
+
acc[cur] = `${key}_INJECTION_ASSIGNMENT_REPLACE`;
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
return acc[cur];
|
|
45
|
+
}, config);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* replaceInjectionAssignments - replace temp string value with actual value
|
|
51
|
+
* @param {string} content
|
|
52
|
+
* @param {{ [configKey]: string }} assignments - key value pairs of config keys to replace
|
|
53
|
+
* @returns {string} - content with replaced values
|
|
54
|
+
*/
|
|
55
|
+
function replaceInjectionAssignments(content, assignments) {
|
|
56
|
+
if (!assignments) return content;
|
|
57
|
+
Object.keys(assignments).forEach((key) => {
|
|
58
|
+
content = content.replace(`'${key}_INJECTION_ASSIGNMENT_REPLACE'`, assignments[key]);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return content;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* writeExpressions - Write expressions to file
|
|
66
|
+
* @param {string} expressions - programmatic expressions as string
|
|
67
|
+
* @returns {string} - expressions as string
|
|
68
|
+
*/
|
|
69
|
+
function writeExpressions(expressions) {
|
|
70
|
+
if (!expressions) return '';
|
|
71
|
+
return `${expressions.map((expression) => `${expression}`).join('\n')}\n`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* cleanupFields - Remove fields from config object
|
|
76
|
+
* @param {GasketConfigDefinition} config - gasket config object
|
|
77
|
+
*/
|
|
78
|
+
function cleanupFields(config) {
|
|
79
|
+
delete config.fields.imports;
|
|
80
|
+
delete config.fields.pluginImports;
|
|
81
|
+
delete config.fields.expressions;
|
|
82
|
+
delete config.fields.injectionAssignments;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Writes the contents of `pkg` to the app's package.json.
|
|
87
|
+
*
|
|
88
|
+
* @param {CreateContext} context - Create context
|
|
89
|
+
* @returns {Promise} promise
|
|
90
|
+
*/
|
|
91
|
+
async function writeGasketConfig({ context }) {
|
|
92
|
+
const { dest, gasketConfig, generatedFiles, typescript } = context;
|
|
93
|
+
const fileName = typescript ? 'gasket.ts' : 'gasket.js';
|
|
94
|
+
const filePath = path.join(dest, fileName);
|
|
95
|
+
const plugins = gasketConfig.fields.plugins;
|
|
96
|
+
const assignments = gasketConfig.fields.injectionAssignments || null;
|
|
97
|
+
const expressions = gasketConfig.fields.expressions || null;
|
|
98
|
+
gasketConfig.fields.plugins = 'PLUGIN_REPLACE';
|
|
99
|
+
|
|
100
|
+
let contents = '';
|
|
101
|
+
if (typescript) contents += `import type { GasketConfigDefinition } from '@gasket/core';\n`;
|
|
102
|
+
contents += `import { makeGasket } from '@gasket/core';\n`;
|
|
103
|
+
contents += writeImports(gasketConfig.fields.pluginImports);
|
|
104
|
+
contents += writeImports(gasketConfig.fields.imports);
|
|
105
|
+
contents += writeExpressions(expressions);
|
|
106
|
+
createInjectionAssignments(gasketConfig.fields, assignments);
|
|
107
|
+
cleanupFields(gasketConfig);
|
|
108
|
+
|
|
109
|
+
const pluginImports = `[\n${writePluginImports(plugins)}\n ]`;
|
|
110
|
+
const typeCoercion = typescript ? ' as GasketConfigDefinition' : '';
|
|
111
|
+
contents += `\nexport default makeGasket(${JSON5.stringify(gasketConfig.fields, null, 2)}${typeCoercion});\n`;
|
|
112
|
+
contents = contents.replace('\'PLUGIN_REPLACE\'', pluginImports);
|
|
113
|
+
contents = replaceInjectionAssignments(contents, assignments);
|
|
114
|
+
|
|
115
|
+
await writeFile(filePath, contents, 'utf8');
|
|
116
|
+
generatedFiles.add(fileName);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export default action('Write gasket config', writeGasketConfig);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { writeFile } from 'fs/promises';
|
|
3
|
+
import action from '../action-wrapper.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Writes the contents of `pkg` to the app's package.json.
|
|
7
|
+
*
|
|
8
|
+
* @param {CreateContext} context - Create context
|
|
9
|
+
* @returns {Promise} promise
|
|
10
|
+
*/
|
|
11
|
+
async function writePkg({ context }) {
|
|
12
|
+
const { dest, pkg, generatedFiles } = context;
|
|
13
|
+
const fileName = 'package.json';
|
|
14
|
+
const filePath = path.join(dest, fileName);
|
|
15
|
+
await writeFile(filePath, JSON.stringify(pkg, null, 2), 'utf8');
|
|
16
|
+
generatedFiles.add(fileName);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default action('Write package.json', writePkg);
|