@adbayb/stack 2.40.0 → 3.0.0
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 +4 -4
- package/bin/index.js +2 -4
- package/configs/{prettier → oxfmt}/README.md +6 -14
- package/configs/oxfmt/index.ts +41 -0
- package/configs/{eslint → oxlint}/README.md +4 -4
- package/configs/oxlint/index.ts +317 -0
- package/configs/typescript/index.json +1 -7
- package/dist/configs/oxfmt.d.ts +32 -0
- package/dist/configs/oxfmt.js +37 -0
- package/dist/configs/oxlint.d.ts +271 -0
- package/dist/configs/oxlint.js +323 -0
- package/dist/index.js +261 -249
- package/package.json +42 -48
- package/templates/multi-projects/.github/workflows/continuous_delivery.yml +3 -3
- package/templates/multi-projects/.github/workflows/dependency_changelog.yml +1 -1
- package/templates/multi-projects/.github/workflows/workflow.yml +3 -3
- package/templates/multi-projects/.vscode/extensions.json +1 -1
- package/templates/multi-projects/.vscode/settings.json +7 -13
- package/templates/multi-projects/libraries/{{projectName}}/package.json +10 -10
- package/templates/multi-projects/oxfmt.config.ts +1 -0
- package/templates/multi-projects/oxlint.config.ts +1 -0
- package/templates/multi-projects/package.json +3 -4
- package/templates/multi-projects/pnpm-workspace.yaml +8 -9
- package/templates/single-project/.github/workflows/continuous_delivery.yml +3 -3
- package/templates/single-project/.github/workflows/dependency_changelog.yml +1 -1
- package/templates/single-project/.github/workflows/workflow.yml +3 -3
- package/templates/single-project/.vscode/extensions.json +1 -1
- package/templates/single-project/.vscode/settings.json +7 -13
- package/templates/single-project/oxfmt.config.ts +1 -0
- package/templates/single-project/oxlint.config.ts +1 -0
- package/templates/single-project/package.json +3 -4
- package/templates/single-project/pnpm-workspace.yaml +8 -9
- package/templates/single-project/{{projectName}}/package.json +10 -10
- package/configs/eslint/constants.js +0 -37
- package/configs/eslint/helpers.js +0 -5
- package/configs/eslint/index.js +0 -30
- package/configs/eslint/presets/dependencies.js +0 -14
- package/configs/eslint/presets/eslint.js +0 -133
- package/configs/eslint/presets/import.js +0 -65
- package/configs/eslint/presets/jsdoc.js +0 -78
- package/configs/eslint/presets/markdown.js +0 -1
- package/configs/eslint/presets/node.js +0 -51
- package/configs/eslint/presets/prettier.js +0 -1
- package/configs/eslint/presets/react.js +0 -111
- package/configs/eslint/presets/sonar.js +0 -201
- package/configs/eslint/presets/stylistic.js +0 -79
- package/configs/eslint/presets/test.js +0 -87
- package/configs/eslint/presets/typescript.js +0 -198
- package/configs/eslint/presets/unicorn.js +0 -257
- package/configs/prettier/index.js +0 -25
- package/templates/multi-projects/.nvmrc +0 -1
- package/templates/multi-projects/eslint.config.js.tmpl +0 -1
- package/templates/single-project/.nvmrc +0 -1
- package/templates/single-project/eslint.config.js.tmpl +0 -1
package/dist/index.js
CHANGED
|
@@ -6,26 +6,25 @@ import { chmod, cp, mkdir, readFile, readdir, rename, rm, symlink, writeFile } f
|
|
|
6
6
|
|
|
7
7
|
//#region src/helpers.ts
|
|
8
8
|
const require = createRequire(import.meta.url);
|
|
9
|
-
|
|
9
|
+
const assert = (expectedCondition, createError) => {
|
|
10
10
|
if (!expectedCondition) throw createError();
|
|
11
|
-
}
|
|
11
|
+
};
|
|
12
12
|
/**
|
|
13
13
|
* Helper to format log messages with a welcoming bot.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* botMessage({
|
|
17
|
+
* title: "Oops, an error occurred",
|
|
18
|
+
* description: "Keep calm and carry on with some coffee ☕️",
|
|
19
|
+
* body: String(previousTaskError),
|
|
20
|
+
* type: "error",
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
14
23
|
* @param input - Message factory.
|
|
15
24
|
* @param input.title - Title input.
|
|
16
25
|
* @param input.description - Description input.
|
|
17
26
|
* @param input.body - Body input.
|
|
18
27
|
* @param input.type - Message type.
|
|
19
|
-
* @example
|
|
20
|
-
* botMessage(
|
|
21
|
-
* {
|
|
22
|
-
* title: "Oops, an error occurred",
|
|
23
|
-
* description:
|
|
24
|
-
* "Keep calm and carry on with some coffee ☕️",
|
|
25
|
-
* body: String(previousTaskError),
|
|
26
|
-
* type: "error",
|
|
27
|
-
* },
|
|
28
|
-
* );
|
|
29
28
|
*/
|
|
30
29
|
const botMessage = (input) => {
|
|
31
30
|
const { type } = input;
|
|
@@ -45,20 +44,24 @@ ${input.body}
|
|
|
45
44
|
};
|
|
46
45
|
/**
|
|
47
46
|
* Resolve a relative path to an absolute one resolved from the generated project root directory.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* resolveFromWorkingDirectory(".gitignore");
|
|
50
|
+
*
|
|
48
51
|
* @param path - The relative path.
|
|
49
52
|
* @returns The resolved absolute path.
|
|
50
|
-
* @example
|
|
51
|
-
* resolveFromWorkingDirectory(".gitignore");
|
|
52
53
|
*/
|
|
53
54
|
const resolveFromWorkingDirectory = (...path) => {
|
|
54
55
|
return resolve(process.cwd(), ...path);
|
|
55
56
|
};
|
|
56
57
|
/**
|
|
57
58
|
* Resolve a relative path to an absolute one resolved from the `stack` node module directory.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* resolveFromPackageDirectory("./templates");
|
|
62
|
+
*
|
|
58
63
|
* @param path - The relative path.
|
|
59
64
|
* @returns The resolved absolute path.
|
|
60
|
-
* @example
|
|
61
|
-
* resolveFromPackageDirectory("./templates");
|
|
62
65
|
*/
|
|
63
66
|
const resolveFromPackageDirectory = (...path) => {
|
|
64
67
|
return resolve(import.meta.dirname, "../", ...path);
|
|
@@ -81,15 +84,14 @@ const getPnpmVersion = async () => {
|
|
|
81
84
|
const getStackCommand = (command) => {
|
|
82
85
|
return `pnpm stack ${command}`;
|
|
83
86
|
};
|
|
84
|
-
const hasDependency = (packageName) => {
|
|
85
|
-
return Boolean(require.resolve(packageName));
|
|
86
|
-
};
|
|
87
87
|
const setPackageManager = async () => {
|
|
88
88
|
/**
|
|
89
|
-
* Corepack is downloaded remotely to get always up-to-date npm registry fingerprints since
|
|
89
|
+
* Corepack is downloaded remotely to get always up-to-date npm registry fingerprints since
|
|
90
|
+
* they're hardcoded.
|
|
91
|
+
*
|
|
90
92
|
* @see {@link https://github.com/nodejs/corepack/issues/613}
|
|
91
93
|
*/
|
|
92
|
-
|
|
94
|
+
await helpers.exec("pnx corepack enable");
|
|
93
95
|
};
|
|
94
96
|
const request = { async get(url, responseType) {
|
|
95
97
|
const response = await fetch(url);
|
|
@@ -97,29 +99,37 @@ const request = { async get(url, responseType) {
|
|
|
97
99
|
status: response.status,
|
|
98
100
|
statusText: response.statusText
|
|
99
101
|
})})`);
|
|
100
|
-
return
|
|
102
|
+
return responseType === "text" ? response.text() : response.json();
|
|
101
103
|
} };
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
104
|
+
const oxlint = (options) => {
|
|
105
|
+
return async (files = []) => {
|
|
106
|
+
const args = [
|
|
107
|
+
...files,
|
|
108
|
+
"--disable-nested-config",
|
|
109
|
+
"--no-error-on-unmatched-pattern"
|
|
110
|
+
];
|
|
111
|
+
if (options.isFixMode) args.push("--fix-dangerously");
|
|
112
|
+
try {
|
|
113
|
+
return await helpers.exec(`oxlint ${args.join(" ")}`);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
throw createError("oxlint", error instanceof Error ? error : new Error(String(error)));
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
const oxfmt = (options) => {
|
|
120
|
+
return async (files = []) => {
|
|
121
|
+
const args = [
|
|
122
|
+
...files,
|
|
123
|
+
"--disable-nested-config",
|
|
124
|
+
"--no-error-on-unmatched-pattern",
|
|
125
|
+
options.isFixMode ? "--write" : "--check"
|
|
126
|
+
];
|
|
127
|
+
try {
|
|
128
|
+
return await helpers.exec(`oxfmt ${args.join(" ")}`);
|
|
129
|
+
} catch (error) {
|
|
130
|
+
throw createError("oxfmt", error instanceof Error ? error : new Error(String(error)));
|
|
131
|
+
}
|
|
132
|
+
};
|
|
123
133
|
};
|
|
124
134
|
const turbo = async (command, options = {}) => {
|
|
125
135
|
try {
|
|
@@ -129,7 +139,7 @@ const turbo = async (command, options = {}) => {
|
|
|
129
139
|
hasLiveOutput
|
|
130
140
|
});
|
|
131
141
|
} catch (error) {
|
|
132
|
-
throw createError("turbo", error);
|
|
142
|
+
throw createError("turbo", error instanceof Error ? error : new Error(String(error)));
|
|
133
143
|
}
|
|
134
144
|
};
|
|
135
145
|
const logCheckableFiles = (files) => {
|
|
@@ -155,28 +165,16 @@ const changeset = async (command) => {
|
|
|
155
165
|
try {
|
|
156
166
|
return await helpers.exec(command, { hasLiveOutput: true });
|
|
157
167
|
} catch (error) {
|
|
158
|
-
throw createError("changeset", error);
|
|
168
|
+
throw createError("changeset", error instanceof Error ? error : new Error(String(error)));
|
|
159
169
|
}
|
|
160
170
|
};
|
|
161
|
-
const ESLINT_EXTENSIONS = [
|
|
162
|
-
"js",
|
|
163
|
-
"jsx",
|
|
164
|
-
"cjs",
|
|
165
|
-
"mjs",
|
|
166
|
-
"ts",
|
|
167
|
-
"tsx",
|
|
168
|
-
"cts",
|
|
169
|
-
"mts",
|
|
170
|
-
"md",
|
|
171
|
-
"mdx"
|
|
172
|
-
];
|
|
173
171
|
|
|
174
172
|
//#endregion
|
|
175
173
|
//#region src/commands/build.ts
|
|
176
174
|
const createBuildCommand = (program) => {
|
|
177
175
|
program.command({
|
|
178
|
-
|
|
179
|
-
|
|
176
|
+
name: "build",
|
|
177
|
+
description: "Build the project in production mode"
|
|
180
178
|
}).task({ async handler() {
|
|
181
179
|
await turbo("build");
|
|
182
180
|
} });
|
|
@@ -184,7 +182,7 @@ const createBuildCommand = (program) => {
|
|
|
184
182
|
|
|
185
183
|
//#endregion
|
|
186
184
|
//#region src/commands/check/checkCode.ts
|
|
187
|
-
const checkCode =
|
|
185
|
+
const checkCode = oxlint({ isFixMode: false });
|
|
188
186
|
|
|
189
187
|
//#endregion
|
|
190
188
|
//#region src/commands/check/checkCommit.ts
|
|
@@ -192,7 +190,7 @@ const checkCommit = async () => {
|
|
|
192
190
|
try {
|
|
193
191
|
return await helpers.exec("commitlint --extends \"@commitlint/config-conventional\" --edit");
|
|
194
192
|
} catch (error) {
|
|
195
|
-
throw createError("commitlint", error);
|
|
193
|
+
throw createError("commitlint", error instanceof Error ? error : new Error(String(error)));
|
|
196
194
|
}
|
|
197
195
|
};
|
|
198
196
|
|
|
@@ -201,64 +199,68 @@ const checkCommit = async () => {
|
|
|
201
199
|
const checkDependency = async () => {
|
|
202
200
|
const stdout = await helpers.exec("pnpm recursive ls --json");
|
|
203
201
|
const checkDependencyVersionMismatch = createPackagesVersionMismatchChecker();
|
|
204
|
-
const packages = JSON.parse(stdout).map((
|
|
205
|
-
const packagePath = join(
|
|
206
|
-
assert(
|
|
202
|
+
const packages = JSON.parse(stdout).map((pkg) => {
|
|
203
|
+
const packagePath = join(pkg.path, "package.json");
|
|
204
|
+
assert(pkg.name, () => {
|
|
205
|
+
return createPackageError(`\`${packagePath}\` must have a name field.`);
|
|
206
|
+
});
|
|
207
207
|
const packageContent = require(packagePath);
|
|
208
208
|
const peerDependencies = packageContent.peerDependencies ?? {};
|
|
209
209
|
const devDependencies = packageContent.devDependencies ?? {};
|
|
210
|
+
const dependencies = packageContent.dependencies ?? {};
|
|
210
211
|
return {
|
|
211
|
-
|
|
212
|
+
name: pkg.name,
|
|
213
|
+
dependencies,
|
|
212
214
|
devDependencies,
|
|
213
|
-
name: package_.name,
|
|
214
215
|
peerDependencies
|
|
215
216
|
};
|
|
216
217
|
});
|
|
217
|
-
for (const
|
|
218
|
-
checkDependencyVersionMismatch(
|
|
219
|
-
checkDependencyVersionRange(
|
|
218
|
+
for (const pkg of packages) {
|
|
219
|
+
checkDependencyVersionMismatch(pkg);
|
|
220
|
+
checkDependencyVersionRange(pkg);
|
|
220
221
|
}
|
|
221
222
|
};
|
|
222
|
-
const
|
|
223
|
+
const STARTING_WITH_DIGIT_REGEXP = /^\d/u;
|
|
224
|
+
const checkDependencyVersionRange = ({ name, dependencies, devDependencies, peerDependencies }) => {
|
|
223
225
|
for (const [dependencyName, version] of Object.entries(devDependencies)) {
|
|
224
226
|
assertVersion(version, {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
+
name: dependencyName,
|
|
228
|
+
consumedBy: name
|
|
227
229
|
});
|
|
228
|
-
if (version !== "workspace:*" && !isExcluded(version) &&
|
|
229
|
-
|
|
230
|
-
|
|
230
|
+
if (version !== "workspace:*" && !isExcluded(version) && !STARTING_WITH_DIGIT_REGEXP.test(version)) throw createPackageError(`As a dev dependency, \`${dependencyName}\` version must be fixed (or set as "workspace:*" for local packages) to reduce accidental breaking change risks due to an implicit semver upgrade.`, {
|
|
231
|
+
name: dependencyName,
|
|
232
|
+
consumedBy: name
|
|
231
233
|
});
|
|
232
234
|
}
|
|
233
235
|
for (const [dependencyName, version] of Object.entries(dependencies)) {
|
|
234
236
|
assertVersion(version, {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
+
name: dependencyName,
|
|
238
|
+
consumedBy: name
|
|
237
239
|
});
|
|
238
240
|
if (version !== "workspace:^" && !hasCaret(version) && !isExcluded(version)) throw createPackageError(`As a dependency, \`${dependencyName}\` version must be prefixed with a caret (or set as "workspace:^" for local packages) to optimize the size (whether of installation or bundle output) on the consumer side.`, {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
+
name: dependencyName,
|
|
242
|
+
consumedBy: name
|
|
241
243
|
});
|
|
242
244
|
}
|
|
243
245
|
for (const [dependencyName, version] of Object.entries(peerDependencies)) {
|
|
244
246
|
assertVersion(version, {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
+
name: dependencyName,
|
|
248
|
+
consumedBy: name
|
|
247
249
|
});
|
|
248
250
|
if (!hasCaret(version) && !isExcluded(version)) throw createPackageError(`As a peer dependency, \`${dependencyName}\` version must be explicit (i.e. the "workspace:^" protocol a version resolver is not allowed) and prefixed with a caret to optimize the size (whether of installation or bundle output) on the consumer side.`, {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
+
name: dependencyName,
|
|
252
|
+
consumedBy: name
|
|
251
253
|
});
|
|
252
254
|
}
|
|
253
255
|
};
|
|
254
256
|
const createPackagesVersionMismatchChecker = () => {
|
|
255
257
|
const monorepoDependencies = /* @__PURE__ */ new Map();
|
|
256
258
|
const monorepoDevelopmentDependencies = /* @__PURE__ */ new Map();
|
|
257
|
-
const lint = (
|
|
258
|
-
const packageName =
|
|
259
|
+
const lint = (pkg, type) => {
|
|
260
|
+
const packageName = pkg.name;
|
|
259
261
|
const isDevelopment = type === "development";
|
|
260
262
|
const store = isDevelopment ? monorepoDevelopmentDependencies : monorepoDependencies;
|
|
261
|
-
const dependencies =
|
|
263
|
+
const dependencies = pkg[isDevelopment ? "devDependencies" : "dependencies"];
|
|
262
264
|
for (const [dependencyName, dependencyVersion] of Object.entries(dependencies)) {
|
|
263
265
|
if (!dependencyVersion) continue;
|
|
264
266
|
const storedVersion = store.get(dependencyName);
|
|
@@ -267,40 +269,39 @@ const createPackagesVersionMismatchChecker = () => {
|
|
|
267
269
|
continue;
|
|
268
270
|
}
|
|
269
271
|
if (!(dependencyVersion === storedVersion)) throw createPackageError(`Mismatched versions: received version \`${dependencyVersion}\` while others use \`${storedVersion}\`. To prevent issues with singleton-like code (React contexts, …), please make sure to update all packages to use the same \`${dependencyName}\` version (either \`${storedVersion}\` or \`${dependencyVersion}\`).`, {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
+
name: dependencyName,
|
|
273
|
+
consumedBy: packageName
|
|
272
274
|
});
|
|
273
275
|
}
|
|
274
276
|
};
|
|
275
|
-
return (
|
|
276
|
-
lint(
|
|
277
|
-
lint(
|
|
277
|
+
return (pkg) => {
|
|
278
|
+
lint(pkg, "development");
|
|
279
|
+
lint(pkg, "production");
|
|
278
280
|
};
|
|
279
281
|
};
|
|
280
282
|
const createPackageError = (message, context) => {
|
|
281
283
|
return createError("stack check", context ? `\`${context.name}\` consumed by \`${context.consumedBy}\` doesn't conform to package guidelines.\n${message}` : message);
|
|
282
284
|
};
|
|
283
|
-
|
|
284
|
-
assert(version, () =>
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}
|
|
285
|
+
const assertVersion = (version, { name, consumedBy }) => {
|
|
286
|
+
assert(version, () => {
|
|
287
|
+
return createPackageError(`\`${name}\` must have a valid version specified (current version equals to \`${String(version)}\`).`, {
|
|
288
|
+
name,
|
|
289
|
+
consumedBy
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
};
|
|
293
|
+
const PRERELEASE_VERSION_REGEXP = /\d+\.\d+\.\d+-(alpha|beta|experimental|next|rc).*/u;
|
|
289
294
|
const isExcluded = (version) => {
|
|
290
|
-
const isPreReleaseVersion =
|
|
295
|
+
const isPreReleaseVersion = PRERELEASE_VERSION_REGEXP.exec(version);
|
|
291
296
|
return version.startsWith("npm:") || isPreReleaseVersion;
|
|
292
297
|
};
|
|
293
|
-
const hasCaret = (version) =>
|
|
298
|
+
const hasCaret = (version) => {
|
|
299
|
+
return version.startsWith("^");
|
|
300
|
+
};
|
|
294
301
|
|
|
295
302
|
//#endregion
|
|
296
|
-
//#region src/commands/check/
|
|
297
|
-
const
|
|
298
|
-
try {
|
|
299
|
-
return await helpers.exec("pnpm --parallel exec tsc --noEmit");
|
|
300
|
-
} catch (error) {
|
|
301
|
-
throw createError("tsc", error);
|
|
302
|
-
}
|
|
303
|
-
};
|
|
303
|
+
//#region src/commands/check/checkFormatting.ts
|
|
304
|
+
const checkFormatting = oxfmt({ isFixMode: false });
|
|
304
305
|
|
|
305
306
|
//#endregion
|
|
306
307
|
//#region src/commands/check/check.ts
|
|
@@ -308,91 +309,94 @@ const ONLY_VALUES = [
|
|
|
308
309
|
"commit",
|
|
309
310
|
"code",
|
|
310
311
|
"dependency",
|
|
311
|
-
"
|
|
312
|
+
"formatting"
|
|
312
313
|
];
|
|
313
314
|
const createCheckCommand = (program) => {
|
|
314
315
|
program.command({
|
|
315
|
-
|
|
316
|
-
|
|
316
|
+
name: "check",
|
|
317
|
+
description: "Check code health (static analysis)"
|
|
317
318
|
}).option({
|
|
318
|
-
defaultValue: void 0,
|
|
319
|
-
description: `Filter the compliance check to run (accepted value: ${ONLY_VALUES.join(", ")})`,
|
|
320
319
|
key: "filter",
|
|
321
|
-
name: "filter"
|
|
320
|
+
name: "filter",
|
|
321
|
+
description: `Filter the compliance check to run (accepted value: ${ONLY_VALUES.join(", ")})`,
|
|
322
|
+
defaultValue: void 0
|
|
322
323
|
}).task({
|
|
323
324
|
handler(_, argv) {
|
|
324
325
|
logCheckableFiles(argv.operands);
|
|
325
326
|
},
|
|
326
327
|
skip: ifFilterDefinedAndNotEqualTo("code")
|
|
327
328
|
}).task({
|
|
329
|
+
label: label$4("Prepare the project"),
|
|
328
330
|
async handler() {
|
|
329
331
|
await turbo("build", {
|
|
330
332
|
excludeExamples: true,
|
|
331
333
|
hasLiveOutput: false
|
|
332
334
|
});
|
|
333
335
|
},
|
|
334
|
-
label: label$4("Prepare the project"),
|
|
335
336
|
skip({ filter }) {
|
|
336
337
|
return filter === "commit";
|
|
337
338
|
}
|
|
338
339
|
}).task({
|
|
340
|
+
label: label$4("Check dependency compliance"),
|
|
339
341
|
async handler() {
|
|
340
342
|
await checkDependency();
|
|
341
343
|
},
|
|
342
|
-
label: label$4("Check dependency compliance"),
|
|
343
344
|
skip: ifFilterDefinedAndNotEqualTo("dependency")
|
|
344
345
|
}).task({
|
|
346
|
+
label: label$4("Check formatting compliance"),
|
|
345
347
|
async handler(_, argv) {
|
|
346
348
|
const filenames = argv.operands;
|
|
347
|
-
await
|
|
349
|
+
await checkFormatting(filenames);
|
|
348
350
|
},
|
|
349
|
-
|
|
350
|
-
skip: ifFilterDefinedAndNotEqualTo("code")
|
|
351
|
+
skip: ifFilterDefinedAndNotEqualTo("formatting")
|
|
351
352
|
}).task({
|
|
352
|
-
|
|
353
|
-
|
|
353
|
+
label: label$4("Check code compliance"),
|
|
354
|
+
async handler(_, argv) {
|
|
355
|
+
const filenames = argv.operands;
|
|
356
|
+
await checkCode(filenames);
|
|
354
357
|
},
|
|
355
|
-
|
|
356
|
-
skip(context, argv) {
|
|
357
|
-
return ifFilterDefinedAndNotEqualTo("type")(context) || !hasDependency("typescript") || argv.operands.length > 0;
|
|
358
|
-
}
|
|
358
|
+
skip: ifFilterDefinedAndNotEqualTo("code")
|
|
359
359
|
}).task({
|
|
360
|
+
label: label$4("Check commit compliance"),
|
|
360
361
|
async handler() {
|
|
361
362
|
await checkCommit();
|
|
362
363
|
},
|
|
363
|
-
label: label$4("Check commit compliance"),
|
|
364
364
|
skip(context) {
|
|
365
365
|
return context.filter !== "commit";
|
|
366
366
|
}
|
|
367
367
|
});
|
|
368
368
|
};
|
|
369
|
-
const label$4 = (message) =>
|
|
370
|
-
|
|
371
|
-
|
|
369
|
+
const label$4 = (message) => {
|
|
370
|
+
return `${message} 🧐`;
|
|
371
|
+
};
|
|
372
|
+
const ifFilterDefinedAndNotEqualTo = (filter) => {
|
|
373
|
+
return (context) => {
|
|
374
|
+
return context.filter !== void 0 && context.filter !== filter;
|
|
375
|
+
};
|
|
372
376
|
};
|
|
373
377
|
|
|
374
378
|
//#endregion
|
|
375
379
|
//#region src/commands/clean.ts
|
|
376
380
|
const createCleanCommand = (program) => {
|
|
377
381
|
program.command({
|
|
378
|
-
|
|
379
|
-
|
|
382
|
+
name: "clean",
|
|
383
|
+
description: "Clean the project"
|
|
380
384
|
}).task({
|
|
385
|
+
key: "files",
|
|
386
|
+
label: label$3("Retrieve removable files"),
|
|
381
387
|
async handler() {
|
|
382
388
|
const cachePath = "node_modules/.cache";
|
|
383
389
|
const files = await retrieveIgnoredFiles();
|
|
384
390
|
if (isDirectoryExistAndNotEmpty(resolveFromWorkingDirectory(cachePath))) files.push(cachePath);
|
|
385
391
|
return files;
|
|
386
|
-
}
|
|
387
|
-
key: "files",
|
|
388
|
-
label: label$3("Retrieve removable files")
|
|
392
|
+
}
|
|
389
393
|
}).task({
|
|
394
|
+
label({ files }) {
|
|
395
|
+
return files.length > 0 ? label$3("Clean assets") : "Already clean ✨";
|
|
396
|
+
},
|
|
390
397
|
async handler({ files }) {
|
|
391
398
|
if (files.length === 0) return;
|
|
392
399
|
await cleanFiles(files);
|
|
393
|
-
},
|
|
394
|
-
label({ files }) {
|
|
395
|
-
return files.length > 0 ? label$3("Clean assets") : "Already clean ✨";
|
|
396
400
|
}
|
|
397
401
|
}).task({
|
|
398
402
|
handler({ files }) {
|
|
@@ -410,42 +414,54 @@ const createCleanCommand = (program) => {
|
|
|
410
414
|
}
|
|
411
415
|
});
|
|
412
416
|
};
|
|
413
|
-
const label$3 = (message) =>
|
|
417
|
+
const label$3 = (message) => {
|
|
418
|
+
return `${message} 🧹`;
|
|
419
|
+
};
|
|
414
420
|
const cleanFiles = async (files) => {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
421
|
+
await Promise.all(files.map(async (file) => {
|
|
422
|
+
await rm(file, {
|
|
423
|
+
force: true,
|
|
424
|
+
recursive: true
|
|
425
|
+
});
|
|
426
|
+
}));
|
|
419
427
|
};
|
|
420
428
|
const isDirectoryExistAndNotEmpty = (path) => {
|
|
421
429
|
return existsSync(path) && readdirSync(path).length > 0;
|
|
422
430
|
};
|
|
431
|
+
const LINEBREAK_REGEXP = /\n|\r\n/u;
|
|
423
432
|
const retrieveIgnoredFiles = async () => {
|
|
424
|
-
return (await helpers.exec("git clean -fdXn")).split(
|
|
433
|
+
return (await helpers.exec("git clean -fdXn")).split(LINEBREAK_REGEXP).filter((cleanOutput) => {
|
|
434
|
+
return PRESERVE_FILES.every((excludedFile) => {
|
|
435
|
+
return !cleanOutput.includes(excludedFile);
|
|
436
|
+
});
|
|
437
|
+
}).map((cleanOutput) => {
|
|
438
|
+
return cleanOutput.split(" ").at(-1) ?? "";
|
|
439
|
+
}).filter(Boolean);
|
|
425
440
|
};
|
|
426
441
|
const PRESERVE_FILES = ["node_modules"];
|
|
427
442
|
|
|
428
443
|
//#endregion
|
|
429
444
|
//#region package.json
|
|
430
|
-
var version = "
|
|
445
|
+
var version = "3.0.0";
|
|
431
446
|
|
|
432
447
|
//#endregion
|
|
433
448
|
//#region src/commands/create.ts
|
|
449
|
+
const REPOSITORY_REGEXP = /^(?:git@.*:|https?:\/\/.*\/)(?<repoOwner>[^/]+)\/(?<repoName>[^/]+)\.git$/u;
|
|
434
450
|
const createCreateCommand = (program) => {
|
|
435
451
|
program.command({
|
|
436
|
-
|
|
437
|
-
|
|
452
|
+
name: "create",
|
|
453
|
+
description: "Scaffold a new project"
|
|
438
454
|
}).task({ handler() {
|
|
439
455
|
botMessage({
|
|
440
|
-
description: "I can guarantee you a project creation in under 1 minute 🚀",
|
|
441
456
|
title: `I'm Stack v${version} 👋`,
|
|
457
|
+
description: "I can guarantee you a project creation in under 1 minute 🚀",
|
|
442
458
|
type: "information"
|
|
443
459
|
});
|
|
444
460
|
} }).task({
|
|
461
|
+
label: label$2("Check pre-requisites"),
|
|
445
462
|
async handler() {
|
|
446
463
|
await getPnpmVersion();
|
|
447
|
-
}
|
|
448
|
-
label: label$2("Check pre-requisites")
|
|
464
|
+
}
|
|
449
465
|
}).input({
|
|
450
466
|
key: "inputName",
|
|
451
467
|
label: "What's your project name?",
|
|
@@ -455,27 +471,29 @@ const createCreateCommand = (program) => {
|
|
|
455
471
|
label: "How would you describe it?",
|
|
456
472
|
type: "text"
|
|
457
473
|
}).input({
|
|
458
|
-
defaultValue: "git@github.com:adbayb/xxx.git",
|
|
459
474
|
key: "inputUrl",
|
|
460
475
|
label: "Where will it be stored? (Git remote URL)",
|
|
476
|
+
defaultValue: "git@github.com:adbayb/xxx.git",
|
|
461
477
|
type: "text"
|
|
462
478
|
}).input({
|
|
463
|
-
defaultValue: "single-project",
|
|
464
479
|
key: "inputTemplate",
|
|
465
480
|
label: "Which template you would like to apply?",
|
|
481
|
+
defaultValue: "single-project",
|
|
466
482
|
options: ["single-project", "multi-projects"],
|
|
467
483
|
type: "select"
|
|
468
484
|
}).task({
|
|
485
|
+
key: "data",
|
|
486
|
+
label: label$2("Check and format input"),
|
|
469
487
|
async handler({ inputDescription, inputName, inputTemplate, inputUrl }) {
|
|
470
488
|
if (!inputName) throw createError("stack create", "The project name is not optional. Make sure to provide a valid value (non-empty string).");
|
|
471
|
-
const { repoName, repoOwner } =
|
|
489
|
+
const { repoName, repoOwner } = REPOSITORY_REGEXP.exec(inputUrl)?.groups ?? {};
|
|
472
490
|
if (!repoOwner || !repoName) throw createError("git", "The owner and repository name can not be extracted. Please make sure to follow either `/^git@.*:(?<repoOwner>.*)/(?<repoName>.*).git$/` or `/^https?://.*/(?<repoOwner>.*)/(?<repoName>.*).git$/` pattern.");
|
|
473
491
|
const nodeVersion = await request.get("https://resolve-node.vercel.app/lts", "text");
|
|
474
492
|
const { version: pnpmVersion } = await request.get("https://registry.npmjs.org/pnpm/latest", "json");
|
|
475
493
|
const projectName = slugify(inputName);
|
|
476
494
|
return {
|
|
477
495
|
licenseYear: (/* @__PURE__ */ new Date()).getFullYear().toString(),
|
|
478
|
-
nodeVersion: nodeVersion.replace("v", "")
|
|
496
|
+
nodeVersion: `^${Number(nodeVersion.replace("v", "").split(".")[0])}.0.0`,
|
|
479
497
|
pnpmVersion: String(pnpmVersion),
|
|
480
498
|
projectDescription: toCapitalLetter(inputDescription),
|
|
481
499
|
projectName,
|
|
@@ -484,15 +502,13 @@ const createCreateCommand = (program) => {
|
|
|
484
502
|
templatePath: resolveFromPackageDirectory("templates", inputTemplate),
|
|
485
503
|
workingPath: resolveFromWorkingDirectory(projectName)
|
|
486
504
|
};
|
|
487
|
-
}
|
|
488
|
-
key: "data",
|
|
489
|
-
label: label$2("Check and format input")
|
|
505
|
+
}
|
|
490
506
|
}).input({
|
|
491
|
-
defaultValue: true,
|
|
492
507
|
key: "canRemoveExistingDirectoryInput",
|
|
493
508
|
label({ data: { projectName } }) {
|
|
494
509
|
return label$2(`\`${projectName}\` directory already exists, do you want to remove it?`);
|
|
495
510
|
},
|
|
511
|
+
defaultValue: true,
|
|
496
512
|
skip({ data: { workingPath } }) {
|
|
497
513
|
return !existsSync(workingPath);
|
|
498
514
|
},
|
|
@@ -502,6 +518,10 @@ const createCreateCommand = (program) => {
|
|
|
502
518
|
return createError("mkdir", `Remove or rename the \`${projectName}\` existing directory to apply the template from a clean state.`);
|
|
503
519
|
}
|
|
504
520
|
}).task({
|
|
521
|
+
key: "templateEngine",
|
|
522
|
+
label({ data: { projectName }, inputTemplate }) {
|
|
523
|
+
return label$2(`Copy \`${inputTemplate}\` template to \`${projectName}\` directory`);
|
|
524
|
+
},
|
|
505
525
|
async handler({ canRemoveExistingDirectoryInput, data: { licenseYear, nodeVersion, pnpmVersion, projectDescription, projectName, projectUrl, repoId, templatePath, workingPath }, inputTemplate }) {
|
|
506
526
|
if (canRemoveExistingDirectoryInput) await rm(workingPath, {
|
|
507
527
|
force: true,
|
|
@@ -521,31 +541,28 @@ const createCreateCommand = (program) => {
|
|
|
521
541
|
templateName: inputTemplate,
|
|
522
542
|
templatePath
|
|
523
543
|
});
|
|
524
|
-
},
|
|
525
|
-
key: "templateEngine",
|
|
526
|
-
label({ data: { projectName }, inputTemplate }) {
|
|
527
|
-
return label$2(`Copy \`${inputTemplate}\` template to \`${projectName}\` directory`);
|
|
528
544
|
}
|
|
529
545
|
}).task({
|
|
546
|
+
label() {
|
|
547
|
+
return label$2("Process template");
|
|
548
|
+
},
|
|
530
549
|
async handler({ templateEngine }) {
|
|
531
550
|
await templateEngine.processContents();
|
|
532
551
|
await templateEngine.processPaths();
|
|
533
|
-
},
|
|
534
|
-
label() {
|
|
535
|
-
return label$2("Process template");
|
|
536
552
|
}
|
|
537
553
|
}).task({
|
|
554
|
+
label: label$2("Initialize `git`"),
|
|
538
555
|
async handler({ data: { projectUrl } }) {
|
|
539
556
|
await helpers.exec("git init");
|
|
540
557
|
await helpers.exec(`git remote add origin ${projectUrl}`);
|
|
541
|
-
}
|
|
542
|
-
label: label$2("Initialize `git`")
|
|
558
|
+
}
|
|
543
559
|
}).task({
|
|
560
|
+
label: label$2("Set up the package manager"),
|
|
544
561
|
async handler() {
|
|
545
562
|
await setPackageManager();
|
|
546
|
-
}
|
|
547
|
-
label: label$2("Set up the package manager")
|
|
563
|
+
}
|
|
548
564
|
}).task({
|
|
565
|
+
label: label$2("Install dependencies"),
|
|
549
566
|
async handler({ data: { projectName } }) {
|
|
550
567
|
const localDevelopmentDependencies = ["quickbundle", "vitest"];
|
|
551
568
|
const globalDevelopmentDependencies = ["@adbayb/stack"];
|
|
@@ -554,32 +571,33 @@ const createCreateCommand = (program) => {
|
|
|
554
571
|
await helpers.exec(`pnpm add ${localDevelopmentDependencies.join(" ")} --save-dev --filter ${projectName}`);
|
|
555
572
|
await helpers.exec("pnpm install");
|
|
556
573
|
} catch (error) {
|
|
557
|
-
throw createError("pnpm", error);
|
|
574
|
+
throw createError("pnpm", error instanceof Error ? error : new Error(String(error)));
|
|
558
575
|
}
|
|
559
|
-
}
|
|
560
|
-
label: label$2("Install dependencies")
|
|
576
|
+
}
|
|
561
577
|
}).task({
|
|
578
|
+
label: label$2("Run `stack install`"),
|
|
562
579
|
async handler() {
|
|
563
580
|
await helpers.exec("stack install");
|
|
564
|
-
}
|
|
565
|
-
label: label$2("Run `stack install`")
|
|
581
|
+
}
|
|
566
582
|
}).task({
|
|
583
|
+
label: label$2("Commit"),
|
|
567
584
|
async handler() {
|
|
568
585
|
await helpers.exec("git add -A");
|
|
569
586
|
await helpers.exec("git commit -m \"chore: initial commit\"");
|
|
570
|
-
}
|
|
571
|
-
label: label$2("Commit")
|
|
587
|
+
}
|
|
572
588
|
}).task({ handler({ data: { projectName } }) {
|
|
573
589
|
botMessage({
|
|
574
|
-
description: `Run \`cd ./${projectName}\` and Enjoy 🚀`,
|
|
575
590
|
title: "The project was successfully created",
|
|
591
|
+
description: `Run \`cd ./${projectName}\` and Enjoy 🚀`,
|
|
576
592
|
type: "success"
|
|
577
593
|
});
|
|
578
594
|
} });
|
|
579
595
|
};
|
|
580
|
-
const label$2 = (message) =>
|
|
596
|
+
const label$2 = (message) => {
|
|
597
|
+
return `${message} 🔨`;
|
|
598
|
+
};
|
|
581
599
|
const slugify = (input) => {
|
|
582
|
-
return input.toLowerCase().replaceAll(/[^a-z0-9\s-]/
|
|
600
|
+
return input.toLowerCase().replaceAll(/[^a-z0-9\s-]/gu, "").trim().replaceAll(/\s+/gu, "-").replaceAll(/-+/gu, "-");
|
|
583
601
|
};
|
|
584
602
|
const toCapitalLetter = (input) => {
|
|
585
603
|
return input.charAt(0).toUpperCase() + input.slice(1);
|
|
@@ -592,21 +610,23 @@ const createTemplateEngine = async (workingPath, { projectName, templateModel, t
|
|
|
592
610
|
});
|
|
593
611
|
const gitignoreFile = join(workingPath, ".gitignore.tmpl");
|
|
594
612
|
if (existsSync(gitignoreFile)) await rename(gitignoreFile, join(workingPath, ".gitignore"));
|
|
595
|
-
const eslintConfigFile = join(workingPath, "eslint.config.js.tmpl");
|
|
596
|
-
if (existsSync(eslintConfigFile)) await rename(eslintConfigFile, join(workingPath, "eslint.config.js"));
|
|
597
613
|
const templateEntries = await getTemplateEntries(workingPath);
|
|
598
614
|
process.chdir(workingPath);
|
|
599
615
|
return {
|
|
600
616
|
async processContents() {
|
|
601
|
-
await Promise.all(templateEntries.filter(({ type }) =>
|
|
602
|
-
return
|
|
617
|
+
await Promise.all(templateEntries.filter(({ type }) => {
|
|
618
|
+
return type === "content";
|
|
619
|
+
}).map(async (entry) => {
|
|
620
|
+
await writeFile(entry.path, setTemplateVariables(entry, templateModel));
|
|
603
621
|
}));
|
|
604
622
|
},
|
|
605
623
|
async processPaths() {
|
|
606
624
|
const sortedDirectoryEntries = templateEntries.filter(({ content, type }) => {
|
|
607
625
|
if (type === "path.directory" || type === "path.file") return hasTemplateVariable(basename(content));
|
|
608
626
|
return false;
|
|
609
|
-
}).toSorted(({ path: pathA }, { path: pathB }) =>
|
|
627
|
+
}).toSorted(({ path: pathA }, { path: pathB }) => {
|
|
628
|
+
return pathB.length - pathA.length;
|
|
629
|
+
});
|
|
610
630
|
for (const entry of sortedDirectoryEntries) {
|
|
611
631
|
const newPath = setTemplateVariables(entry, templateModel);
|
|
612
632
|
await rename(entry.path, newPath);
|
|
@@ -634,13 +654,15 @@ const getTemplateEntries = async (path) => {
|
|
|
634
654
|
content: await readFile(entryPath, "utf8"),
|
|
635
655
|
type: "content"
|
|
636
656
|
}]).map(({ content, type }) => {
|
|
637
|
-
if (!hasTemplateVariable(content)) return
|
|
657
|
+
if (!hasTemplateVariable(content)) return;
|
|
638
658
|
return {
|
|
639
659
|
content,
|
|
640
660
|
path: entryPath,
|
|
641
661
|
type
|
|
642
662
|
};
|
|
643
|
-
}).filter((input) =>
|
|
663
|
+
}).filter((input) => {
|
|
664
|
+
return Boolean(input);
|
|
665
|
+
});
|
|
644
666
|
}))).flat();
|
|
645
667
|
};
|
|
646
668
|
const setTemplateVariables = (entry, model) => {
|
|
@@ -648,92 +670,80 @@ const setTemplateVariables = (entry, model) => {
|
|
|
648
670
|
return model[dataModelKey] ?? match;
|
|
649
671
|
});
|
|
650
672
|
};
|
|
651
|
-
const TEMPLATE_VARIABLE_MATCHER =
|
|
673
|
+
const TEMPLATE_VARIABLE_MATCHER = /\{\{(.*?)\}\}/giu;
|
|
652
674
|
const hasTemplateVariable = (input) => {
|
|
653
675
|
/**
|
|
654
|
-
* TemplateVariableMatcher.test() is not used since the `RegExp` is stateful when the global is
|
|
655
|
-
* (relying on latest `lastIndex` set (lastIndex specifies
|
|
656
|
-
* String.search is stateless.
|
|
676
|
+
* TemplateVariableMatcher.test() is not used since the `RegExp` is stateful when the global is
|
|
677
|
+
* used leading to some unstable results (relying on latest `lastIndex` set (lastIndex specifies
|
|
678
|
+
* the index at which to start the next match)). String.search is stateless.
|
|
679
|
+
*
|
|
657
680
|
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test MDN documentation}.
|
|
658
681
|
*/
|
|
659
682
|
return input.search(TEMPLATE_VARIABLE_MATCHER) >= 0;
|
|
660
683
|
};
|
|
661
684
|
|
|
662
685
|
//#endregion
|
|
663
|
-
//#region src/commands/fix/
|
|
664
|
-
const
|
|
665
|
-
const fixFormatting = async (files) => {
|
|
666
|
-
let prettierFiles = [];
|
|
667
|
-
if (files.length === 0) prettierFiles.push(`"**/!(${PRETTIER_IGNORE_FILES.join("|")})"`);
|
|
668
|
-
else {
|
|
669
|
-
prettierFiles = files.filter((file) => {
|
|
670
|
-
return PRETTIER_IGNORE_FILES.every((filename) => !file.endsWith(filename));
|
|
671
|
-
});
|
|
672
|
-
if (prettierFiles.length === 0) return;
|
|
673
|
-
}
|
|
674
|
-
const arguments_ = [...prettierFiles];
|
|
675
|
-
if (existsSync(resolveFromWorkingDirectory(".gitignore"))) arguments_.push("--ignore-path .gitignore");
|
|
676
|
-
arguments_.push("--write", "--ignore-unknown", "--no-error-on-unmatched-pattern");
|
|
677
|
-
try {
|
|
678
|
-
return await helpers.exec(`prettier ${arguments_.join(" ")}`);
|
|
679
|
-
} catch (error) {
|
|
680
|
-
throw createError("prettier", error);
|
|
681
|
-
}
|
|
682
|
-
};
|
|
686
|
+
//#region src/commands/fix/fixCode.ts
|
|
687
|
+
const fixCode = oxlint({ isFixMode: true });
|
|
683
688
|
|
|
684
689
|
//#endregion
|
|
685
|
-
//#region src/commands/fix/
|
|
686
|
-
const
|
|
690
|
+
//#region src/commands/fix/fixFormatting.ts
|
|
691
|
+
const fixFormatting = oxfmt({ isFixMode: true });
|
|
687
692
|
|
|
688
693
|
//#endregion
|
|
689
694
|
//#region src/commands/fix/fix.ts
|
|
690
695
|
const createFixCommand = (program) => {
|
|
691
696
|
program.command({
|
|
692
|
-
|
|
693
|
-
|
|
697
|
+
name: "fix",
|
|
698
|
+
description: "Fix auto-fixable issues"
|
|
694
699
|
}).task({ handler(_, argv) {
|
|
695
700
|
logCheckableFiles(argv.operands);
|
|
696
701
|
} }).task({
|
|
702
|
+
label: label$1("Prepare the project"),
|
|
697
703
|
async handler() {
|
|
698
704
|
await turbo("build", {
|
|
699
705
|
excludeExamples: true,
|
|
700
706
|
hasLiveOutput: false
|
|
701
707
|
});
|
|
702
|
-
}
|
|
703
|
-
label: label$1("Prepare the project")
|
|
708
|
+
}
|
|
704
709
|
}).task({
|
|
710
|
+
label: label$1("Fix formatting issues"),
|
|
705
711
|
async handler(_, argv) {
|
|
706
|
-
await
|
|
707
|
-
}
|
|
708
|
-
label: label$1("Fix linter issues")
|
|
712
|
+
await fixFormatting(argv.operands);
|
|
713
|
+
}
|
|
709
714
|
}).task({
|
|
715
|
+
label: label$1("Fix code issues"),
|
|
710
716
|
async handler(_, argv) {
|
|
711
|
-
await
|
|
712
|
-
}
|
|
713
|
-
label: label$1("Fix formatting issues")
|
|
717
|
+
await fixCode(argv.operands);
|
|
718
|
+
}
|
|
714
719
|
});
|
|
715
720
|
};
|
|
716
|
-
const label$1 = (message) =>
|
|
721
|
+
const label$1 = (message) => {
|
|
722
|
+
return `${message} 🚑`;
|
|
723
|
+
};
|
|
717
724
|
|
|
718
725
|
//#endregion
|
|
719
726
|
//#region src/commands/install.ts
|
|
720
727
|
const createInstallCommand = (program) => {
|
|
721
728
|
program.command({
|
|
722
|
-
|
|
723
|
-
|
|
729
|
+
name: "install",
|
|
730
|
+
description: "Install required setup"
|
|
724
731
|
}).task({
|
|
732
|
+
label: label("Install `git.pre-commit` hook"),
|
|
725
733
|
async handler() {
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
734
|
+
const stackCommand = getStackCommand(`fix $(node -e 'console.log(require("child_process").execSync("git status --porcelain", {encoding: "utf8"}).split(/${String.raw`\n|\r\n`}/).filter(Boolean).map(item => item.split(" ").at(-1)).join(" "))')`);
|
|
735
|
+
await installGitHook("pre-commit", `${stackCommand} && git add -A`);
|
|
736
|
+
}
|
|
729
737
|
}).task({
|
|
738
|
+
label: label("Install `git.commit-msg` hook"),
|
|
730
739
|
async handler() {
|
|
731
740
|
await installGitHook("commit-msg", getStackCommand("check --filter commit"));
|
|
732
|
-
}
|
|
733
|
-
label: label("Install `git.commit-msg` hook")
|
|
741
|
+
}
|
|
734
742
|
});
|
|
735
743
|
};
|
|
736
|
-
const label = (message) =>
|
|
744
|
+
const label = (message) => {
|
|
745
|
+
return `${message} 📲`;
|
|
746
|
+
};
|
|
737
747
|
const installGitHook = async (hook, content) => {
|
|
738
748
|
const filename = resolveFromWorkingDirectory(`.git/hooks/${hook}`);
|
|
739
749
|
await writeFile(filename, content);
|
|
@@ -744,24 +754,24 @@ const installGitHook = async (hook, content) => {
|
|
|
744
754
|
//#region src/commands/release.ts
|
|
745
755
|
const createReleaseCommand = (program) => {
|
|
746
756
|
program.command({
|
|
747
|
-
|
|
748
|
-
|
|
757
|
+
name: "release",
|
|
758
|
+
description: "Log, version, and publish package(s)"
|
|
749
759
|
}).option({
|
|
750
|
-
description: "Add a new changelog entry",
|
|
751
760
|
key: "log",
|
|
752
|
-
name: "log"
|
|
761
|
+
name: "log",
|
|
762
|
+
description: "Add a new changelog entry"
|
|
753
763
|
}).option({
|
|
754
|
-
description: "Add an empty changelog entry",
|
|
755
764
|
key: "emptyLog",
|
|
756
|
-
name: "empty-log"
|
|
765
|
+
name: "empty-log",
|
|
766
|
+
description: "Add an empty changelog entry"
|
|
757
767
|
}).option({
|
|
758
|
-
description: "Bump the package(s) version",
|
|
759
768
|
key: "tag",
|
|
760
|
-
name: "tag"
|
|
769
|
+
name: "tag",
|
|
770
|
+
description: "Bump the package(s) version"
|
|
761
771
|
}).option({
|
|
762
|
-
description: "Publish package(s) to the registry",
|
|
763
772
|
key: "publish",
|
|
764
|
-
name: "publish"
|
|
773
|
+
name: "publish",
|
|
774
|
+
description: "Publish package(s) to the registry"
|
|
765
775
|
}).task({
|
|
766
776
|
async handler() {
|
|
767
777
|
helpers.message("New changelog entry\n");
|
|
@@ -788,16 +798,18 @@ const createReleaseCommand = (program) => {
|
|
|
788
798
|
skip: ifNotEqualTo("publish")
|
|
789
799
|
});
|
|
790
800
|
};
|
|
791
|
-
const ifNotEqualTo = (validOption) =>
|
|
792
|
-
return
|
|
801
|
+
const ifNotEqualTo = (validOption) => {
|
|
802
|
+
return (context) => {
|
|
803
|
+
return !context[validOption];
|
|
804
|
+
};
|
|
793
805
|
};
|
|
794
806
|
|
|
795
807
|
//#endregion
|
|
796
808
|
//#region src/commands/start.ts
|
|
797
809
|
const createStartCommand = (program) => {
|
|
798
810
|
program.command({
|
|
799
|
-
|
|
800
|
-
|
|
811
|
+
name: "start",
|
|
812
|
+
description: "Start the project in production mode"
|
|
801
813
|
}).task({ async handler() {
|
|
802
814
|
await turbo("start");
|
|
803
815
|
} });
|
|
@@ -807,8 +819,8 @@ const createStartCommand = (program) => {
|
|
|
807
819
|
//#region src/commands/test.ts
|
|
808
820
|
const createTestCommand = (program) => {
|
|
809
821
|
program.command({
|
|
810
|
-
|
|
811
|
-
|
|
822
|
+
name: "test",
|
|
823
|
+
description: "Test the code execution"
|
|
812
824
|
}).task({ async handler() {
|
|
813
825
|
await turbo("test");
|
|
814
826
|
} });
|
|
@@ -818,8 +830,8 @@ const createTestCommand = (program) => {
|
|
|
818
830
|
//#region src/commands/watch.ts
|
|
819
831
|
const createWatchCommand = (program) => {
|
|
820
832
|
program.command({
|
|
821
|
-
|
|
822
|
-
|
|
833
|
+
name: "watch",
|
|
834
|
+
description: "Build and start the project in development mode"
|
|
823
835
|
}).task({ async handler() {
|
|
824
836
|
await turbo("watch");
|
|
825
837
|
} });
|
|
@@ -829,12 +841,12 @@ const createWatchCommand = (program) => {
|
|
|
829
841
|
//#region src/index.ts
|
|
830
842
|
const createProgram = (...commandFactories) => {
|
|
831
843
|
const program = termost({
|
|
832
|
-
description: "Toolbox to easily scaffold and maintain a project",
|
|
833
844
|
name: "stack",
|
|
845
|
+
description: "Toolbox to easily scaffold and maintain a project",
|
|
834
846
|
onException() {
|
|
835
847
|
botMessage({
|
|
836
|
-
description: "Keep calm and carry on with some coffee ☕️",
|
|
837
848
|
title: "Oops, an error occurred",
|
|
849
|
+
description: "Keep calm and carry on with some coffee ☕️",
|
|
838
850
|
type: "error"
|
|
839
851
|
});
|
|
840
852
|
},
|