@adbayb/stack 2.39.0 → 2.40.0-next-f8ad939
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/bin/index.js +2 -2
- package/configs/{prettier → oxfmt}/README.md +6 -14
- package/configs/{prettier/index.js → oxfmt/index.ts} +13 -9
- package/configs/{eslint → oxlint}/README.md +4 -4
- package/configs/oxlint/index.ts +30 -0
- package/configs/typescript/index.json +1 -7
- package/dist/index.js +229 -185
- package/package.json +30 -48
- package/templates/multi-projects/.vscode/extensions.json +1 -1
- package/templates/multi-projects/.vscode/settings.json +7 -13
- package/templates/{single-project/package.json.tmpl → multi-projects/package.json} +7 -13
- package/templates/multi-projects/{pnpm-workspace.yaml.tmpl → pnpm-workspace.yaml} +4 -2
- package/templates/single-project/.vscode/extensions.json +1 -1
- package/templates/single-project/.vscode/settings.json +7 -13
- package/templates/{multi-projects/package.json.tmpl → single-project/package.json} +7 -13
- package/templates/single-project/{pnpm-workspace.yaml.tmpl → pnpm-workspace.yaml} +4 -2
- 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/templates/multi-projects/.npmrc.tmpl +0 -1
- package/templates/single-project/.npmrc.tmpl +0 -1
- package/templates/multi-projects/.changeset/{config.json.tmpl → config.json} +0 -0
- package/templates/multi-projects/.changeset/{welcome.md.tmpl → welcome.md} +0 -0
- package/templates/multi-projects/.github/{PULL_REQUEST_TEMPLATE.md.tmpl → PULL_REQUEST_TEMPLATE.md} +0 -0
- package/templates/multi-projects/{.nvmrc.tmpl → .nvmrc} +0 -0
- package/templates/multi-projects/{CONTRIBUTING.md.tmpl → CONTRIBUTING.md} +0 -0
- package/templates/multi-projects/{LICENSE.tmpl → LICENSE} +0 -0
- package/templates/multi-projects/examples/default/{package.json.tmpl → package.json} +0 -0
- package/templates/multi-projects/examples/default/src/{index.ts.tmpl → index.ts} +0 -0
- package/templates/multi-projects/libraries/{{projectName}}/{README.md.tmpl → README.md} +0 -0
- package/templates/multi-projects/libraries/{{projectName}}/{package.json.tmpl → package.json} +10 -10
- package/templates/multi-projects/libraries/{{projectName}}/src/{index.test.ts.tmpl → index.test.ts} +0 -0
- package/templates/single-project/.changeset/{config.json.tmpl → config.json} +0 -0
- package/templates/single-project/.changeset/{welcome.md.tmpl → welcome.md} +0 -0
- package/templates/single-project/.github/{PULL_REQUEST_TEMPLATE.md.tmpl → PULL_REQUEST_TEMPLATE.md} +0 -0
- package/templates/single-project/{.nvmrc.tmpl → .nvmrc} +0 -0
- package/templates/single-project/{CONTRIBUTING.md.tmpl → CONTRIBUTING.md} +0 -0
- package/templates/single-project/{LICENSE.tmpl → LICENSE} +0 -0
- package/templates/single-project/examples/default/{package.json.tmpl → package.json} +0 -0
- package/templates/single-project/examples/default/src/{index.ts.tmpl → index.ts} +0 -0
- package/templates/single-project/{{projectName}}/{README.md.tmpl → README.md} +0 -0
- package/templates/single-project/{{projectName}}/{package.json.tmpl → package.json} +10 -10
- /package/templates/single-project/{{projectName}}/src/{index.test.ts.tmpl → index.test.ts} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { helpers, termost } from "termost";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
|
-
import { join, resolve } from "node:path";
|
|
4
|
-
import {
|
|
5
|
-
import { chmod, mkdir, rm, symlink, writeFile } from "node:fs/promises";
|
|
6
|
-
import { fdir } from "fdir";
|
|
3
|
+
import { basename, join, resolve } from "node:path";
|
|
4
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
5
|
+
import { chmod, cp, mkdir, readFile, readdir, rename, rm, symlink, writeFile } from "node:fs/promises";
|
|
7
6
|
|
|
8
7
|
//#region src/helpers.ts
|
|
9
8
|
const require = createRequire(import.meta.url);
|
|
@@ -12,21 +11,20 @@ function assert(expectedCondition, createError) {
|
|
|
12
11
|
}
|
|
13
12
|
/**
|
|
14
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
|
+
*
|
|
15
23
|
* @param input - Message factory.
|
|
16
24
|
* @param input.title - Title input.
|
|
17
25
|
* @param input.description - Description input.
|
|
18
26
|
* @param input.body - Body input.
|
|
19
27
|
* @param input.type - Message type.
|
|
20
|
-
* @example
|
|
21
|
-
* botMessage(
|
|
22
|
-
* {
|
|
23
|
-
* title: "Oops, an error occurred",
|
|
24
|
-
* description:
|
|
25
|
-
* "Keep calm and carry on with some coffee ☕️",
|
|
26
|
-
* body: String(previousTaskError),
|
|
27
|
-
* type: "error",
|
|
28
|
-
* },
|
|
29
|
-
* );
|
|
30
28
|
*/
|
|
31
29
|
const botMessage = (input) => {
|
|
32
30
|
const { type } = input;
|
|
@@ -46,23 +44,27 @@ ${input.body}
|
|
|
46
44
|
};
|
|
47
45
|
/**
|
|
48
46
|
* Resolve a relative path to an absolute one resolved from the generated project root directory.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* resolveFromWorkingDirectory(".gitignore");
|
|
50
|
+
*
|
|
49
51
|
* @param path - The relative path.
|
|
50
52
|
* @returns The resolved absolute path.
|
|
51
|
-
* @example
|
|
52
|
-
* resolveFromProjectDirectory(".gitignore");
|
|
53
53
|
*/
|
|
54
|
-
const
|
|
55
|
-
return resolve(process.cwd(), path);
|
|
54
|
+
const resolveFromWorkingDirectory = (...path) => {
|
|
55
|
+
return resolve(process.cwd(), ...path);
|
|
56
56
|
};
|
|
57
57
|
/**
|
|
58
58
|
* Resolve a relative path to an absolute one resolved from the `stack` node module directory.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* resolveFromPackageDirectory("./templates");
|
|
62
|
+
*
|
|
59
63
|
* @param path - The relative path.
|
|
60
64
|
* @returns The resolved absolute path.
|
|
61
|
-
* @example
|
|
62
|
-
* resolveFromStackDirectory("./templates");
|
|
63
65
|
*/
|
|
64
|
-
const
|
|
65
|
-
return resolve(import.meta.dirname, "../", path);
|
|
66
|
+
const resolveFromPackageDirectory = (...path) => {
|
|
67
|
+
return resolve(import.meta.dirname, "../", ...path);
|
|
66
68
|
};
|
|
67
69
|
const createError = (bin, error) => {
|
|
68
70
|
const errorMessage = `\`${bin}\` command failed.\n${String(error)}`;
|
|
@@ -72,7 +74,7 @@ const createError = (bin, error) => {
|
|
|
72
74
|
}
|
|
73
75
|
return new Error(errorMessage);
|
|
74
76
|
};
|
|
75
|
-
const
|
|
77
|
+
const getPnpmVersion = async () => {
|
|
76
78
|
try {
|
|
77
79
|
return await helpers.exec("pnpm -v");
|
|
78
80
|
} catch {
|
|
@@ -82,44 +84,49 @@ const getNpmVersion = async () => {
|
|
|
82
84
|
const getStackCommand = (command) => {
|
|
83
85
|
return `pnpm stack ${command}`;
|
|
84
86
|
};
|
|
85
|
-
const hasDependency = (packageName) => {
|
|
86
|
-
return Boolean(require.resolve(packageName));
|
|
87
|
-
};
|
|
88
87
|
const setPackageManager = async () => {
|
|
89
88
|
/**
|
|
90
|
-
* 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
|
+
*
|
|
91
92
|
* @see {@link https://github.com/nodejs/corepack/issues/613}
|
|
92
93
|
*/
|
|
93
|
-
return helpers.exec("
|
|
94
|
+
return helpers.exec("pnx corepack enable");
|
|
94
95
|
};
|
|
95
|
-
|
|
96
|
+
async function getRequest(url, responseType) {
|
|
96
97
|
const response = await fetch(url);
|
|
97
98
|
if (!response.ok) throw createError("fetch", `Failed to fetch resources from ${url} (${JSON.stringify({
|
|
98
99
|
status: response.status,
|
|
99
100
|
statusText: response.statusText
|
|
100
101
|
})})`);
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
});
|
|
110
|
-
if (eslintFiles.length === 0) return;
|
|
111
|
-
}
|
|
112
|
-
const arguments_ = [
|
|
113
|
-
...eslintFiles,
|
|
114
|
-
"--cache",
|
|
115
|
-
`--cache-location ${resolveFromProjectDirectory("node_modules/.cache/.eslintcache")}`,
|
|
102
|
+
if (responseType === "text") return response.text();
|
|
103
|
+
return response.json();
|
|
104
|
+
}
|
|
105
|
+
const request = { get: getRequest };
|
|
106
|
+
const oxlint = (options) => async (files = []) => {
|
|
107
|
+
const args = [
|
|
108
|
+
...files,
|
|
109
|
+
"--disable-nested-config",
|
|
116
110
|
"--no-error-on-unmatched-pattern"
|
|
117
111
|
];
|
|
118
|
-
if (options.isFixMode)
|
|
112
|
+
if (options.isFixMode) args.push("--fix");
|
|
113
|
+
try {
|
|
114
|
+
return await helpers.exec(`oxlint ${args.join(" ")}`);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
throw createError("oxlint", error instanceof Error ? error : new Error(String(error)));
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
const oxfmt = (options) => async (files = []) => {
|
|
120
|
+
const args = [
|
|
121
|
+
...files,
|
|
122
|
+
"--disable-nested-config",
|
|
123
|
+
"--no-error-on-unmatched-pattern",
|
|
124
|
+
options.isFixMode ? "--write" : "--check"
|
|
125
|
+
];
|
|
119
126
|
try {
|
|
120
|
-
return await helpers.exec(`
|
|
127
|
+
return await helpers.exec(`oxfmt ${args.join(" ")}`);
|
|
121
128
|
} catch (error) {
|
|
122
|
-
throw createError("
|
|
129
|
+
throw createError("oxfmt", error instanceof Error ? error : new Error(String(error)));
|
|
123
130
|
}
|
|
124
131
|
};
|
|
125
132
|
const turbo = async (command, options = {}) => {
|
|
@@ -130,7 +137,7 @@ const turbo = async (command, options = {}) => {
|
|
|
130
137
|
hasLiveOutput
|
|
131
138
|
});
|
|
132
139
|
} catch (error) {
|
|
133
|
-
throw createError("turbo", error);
|
|
140
|
+
throw createError("turbo", error instanceof Error ? error : new Error(String(error)));
|
|
134
141
|
}
|
|
135
142
|
};
|
|
136
143
|
const logCheckableFiles = (files) => {
|
|
@@ -156,21 +163,9 @@ const changeset = async (command) => {
|
|
|
156
163
|
try {
|
|
157
164
|
return await helpers.exec(command, { hasLiveOutput: true });
|
|
158
165
|
} catch (error) {
|
|
159
|
-
throw createError("changeset", error);
|
|
166
|
+
throw createError("changeset", error instanceof Error ? error : new Error(String(error)));
|
|
160
167
|
}
|
|
161
168
|
};
|
|
162
|
-
const ESLINT_EXTENSIONS = [
|
|
163
|
-
"js",
|
|
164
|
-
"jsx",
|
|
165
|
-
"cjs",
|
|
166
|
-
"mjs",
|
|
167
|
-
"ts",
|
|
168
|
-
"tsx",
|
|
169
|
-
"cts",
|
|
170
|
-
"mts",
|
|
171
|
-
"md",
|
|
172
|
-
"mdx"
|
|
173
|
-
];
|
|
174
169
|
|
|
175
170
|
//#endregion
|
|
176
171
|
//#region src/commands/build.ts
|
|
@@ -185,7 +180,7 @@ const createBuildCommand = (program) => {
|
|
|
185
180
|
|
|
186
181
|
//#endregion
|
|
187
182
|
//#region src/commands/check/checkCode.ts
|
|
188
|
-
const checkCode =
|
|
183
|
+
const checkCode = oxlint({ isFixMode: false });
|
|
189
184
|
|
|
190
185
|
//#endregion
|
|
191
186
|
//#region src/commands/check/checkCommit.ts
|
|
@@ -193,7 +188,7 @@ const checkCommit = async () => {
|
|
|
193
188
|
try {
|
|
194
189
|
return await helpers.exec("commitlint --extends \"@commitlint/config-conventional\" --edit");
|
|
195
190
|
} catch (error) {
|
|
196
|
-
throw createError("commitlint", error);
|
|
191
|
+
throw createError("commitlint", error instanceof Error ? error : new Error(String(error)));
|
|
197
192
|
}
|
|
198
193
|
};
|
|
199
194
|
|
|
@@ -202,22 +197,22 @@ const checkCommit = async () => {
|
|
|
202
197
|
const checkDependency = async () => {
|
|
203
198
|
const stdout = await helpers.exec("pnpm recursive ls --json");
|
|
204
199
|
const checkDependencyVersionMismatch = createPackagesVersionMismatchChecker();
|
|
205
|
-
const packages = JSON.parse(stdout).map((
|
|
206
|
-
const packagePath = join(
|
|
207
|
-
assert(
|
|
200
|
+
const packages = JSON.parse(stdout).map((pkg) => {
|
|
201
|
+
const packagePath = join(pkg.path, "package.json");
|
|
202
|
+
assert(pkg.name, () => createPackageError(`\`${packagePath}\` must have a name field.`));
|
|
208
203
|
const packageContent = require(packagePath);
|
|
209
204
|
const peerDependencies = packageContent.peerDependencies ?? {};
|
|
210
205
|
const devDependencies = packageContent.devDependencies ?? {};
|
|
211
206
|
return {
|
|
212
207
|
dependencies: packageContent.dependencies ?? {},
|
|
213
208
|
devDependencies,
|
|
214
|
-
name:
|
|
209
|
+
name: pkg.name,
|
|
215
210
|
peerDependencies
|
|
216
211
|
};
|
|
217
212
|
});
|
|
218
|
-
for (const
|
|
219
|
-
checkDependencyVersionMismatch(
|
|
220
|
-
checkDependencyVersionRange(
|
|
213
|
+
for (const pkg of packages) {
|
|
214
|
+
checkDependencyVersionMismatch(pkg);
|
|
215
|
+
checkDependencyVersionRange(pkg);
|
|
221
216
|
}
|
|
222
217
|
};
|
|
223
218
|
const checkDependencyVersionRange = ({ dependencies, devDependencies, name, peerDependencies }) => {
|
|
@@ -255,11 +250,11 @@ const checkDependencyVersionRange = ({ dependencies, devDependencies, name, peer
|
|
|
255
250
|
const createPackagesVersionMismatchChecker = () => {
|
|
256
251
|
const monorepoDependencies = /* @__PURE__ */ new Map();
|
|
257
252
|
const monorepoDevelopmentDependencies = /* @__PURE__ */ new Map();
|
|
258
|
-
const lint = (
|
|
259
|
-
const packageName =
|
|
253
|
+
const lint = (pkg, type) => {
|
|
254
|
+
const packageName = pkg.name;
|
|
260
255
|
const isDevelopment = type === "development";
|
|
261
256
|
const store = isDevelopment ? monorepoDevelopmentDependencies : monorepoDependencies;
|
|
262
|
-
const dependencies =
|
|
257
|
+
const dependencies = pkg[isDevelopment ? "devDependencies" : "dependencies"];
|
|
263
258
|
for (const [dependencyName, dependencyVersion] of Object.entries(dependencies)) {
|
|
264
259
|
if (!dependencyVersion) continue;
|
|
265
260
|
const storedVersion = store.get(dependencyName);
|
|
@@ -273,9 +268,9 @@ const createPackagesVersionMismatchChecker = () => {
|
|
|
273
268
|
});
|
|
274
269
|
}
|
|
275
270
|
};
|
|
276
|
-
return (
|
|
277
|
-
lint(
|
|
278
|
-
lint(
|
|
271
|
+
return (pkg) => {
|
|
272
|
+
lint(pkg, "development");
|
|
273
|
+
lint(pkg, "production");
|
|
279
274
|
};
|
|
280
275
|
};
|
|
281
276
|
const createPackageError = (message, context) => {
|
|
@@ -294,14 +289,8 @@ const isExcluded = (version) => {
|
|
|
294
289
|
const hasCaret = (version) => version.startsWith("^");
|
|
295
290
|
|
|
296
291
|
//#endregion
|
|
297
|
-
//#region src/commands/check/
|
|
298
|
-
const
|
|
299
|
-
try {
|
|
300
|
-
return await helpers.exec("pnpm --parallel exec tsc --noEmit");
|
|
301
|
-
} catch (error) {
|
|
302
|
-
throw createError("tsc", error);
|
|
303
|
-
}
|
|
304
|
-
};
|
|
292
|
+
//#region src/commands/check/checkFormatting.ts
|
|
293
|
+
const checkFormatting = oxfmt({ isFixMode: false });
|
|
305
294
|
|
|
306
295
|
//#endregion
|
|
307
296
|
//#region src/commands/check/check.ts
|
|
@@ -309,7 +298,7 @@ const ONLY_VALUES = [
|
|
|
309
298
|
"commit",
|
|
310
299
|
"code",
|
|
311
300
|
"dependency",
|
|
312
|
-
"
|
|
301
|
+
"formatting"
|
|
313
302
|
];
|
|
314
303
|
const createCheckCommand = (program) => {
|
|
315
304
|
program.command({
|
|
@@ -342,6 +331,13 @@ const createCheckCommand = (program) => {
|
|
|
342
331
|
},
|
|
343
332
|
label: label$4("Check dependency compliance"),
|
|
344
333
|
skip: ifFilterDefinedAndNotEqualTo("dependency")
|
|
334
|
+
}).task({
|
|
335
|
+
async handler(_, argv) {
|
|
336
|
+
const filenames = argv.operands;
|
|
337
|
+
await checkFormatting(filenames);
|
|
338
|
+
},
|
|
339
|
+
label: label$4("Check formatting compliance"),
|
|
340
|
+
skip: ifFilterDefinedAndNotEqualTo("formatting")
|
|
345
341
|
}).task({
|
|
346
342
|
async handler(_, argv) {
|
|
347
343
|
const filenames = argv.operands;
|
|
@@ -349,14 +345,6 @@ const createCheckCommand = (program) => {
|
|
|
349
345
|
},
|
|
350
346
|
label: label$4("Check code compliance"),
|
|
351
347
|
skip: ifFilterDefinedAndNotEqualTo("code")
|
|
352
|
-
}).task({
|
|
353
|
-
async handler() {
|
|
354
|
-
await checkType();
|
|
355
|
-
},
|
|
356
|
-
label: label$4("Check type compliance"),
|
|
357
|
-
skip(context, argv) {
|
|
358
|
-
return ifFilterDefinedAndNotEqualTo("type")(context) || !hasDependency("typescript") || argv.operands.length > 0;
|
|
359
|
-
}
|
|
360
348
|
}).task({
|
|
361
349
|
async handler() {
|
|
362
350
|
await checkCommit();
|
|
@@ -382,7 +370,7 @@ const createCleanCommand = (program) => {
|
|
|
382
370
|
async handler() {
|
|
383
371
|
const cachePath = "node_modules/.cache";
|
|
384
372
|
const files = await retrieveIgnoredFiles();
|
|
385
|
-
if (isDirectoryExistAndNotEmpty(
|
|
373
|
+
if (isDirectoryExistAndNotEmpty(resolveFromWorkingDirectory(cachePath))) files.push(cachePath);
|
|
386
374
|
return files;
|
|
387
375
|
},
|
|
388
376
|
key: "files",
|
|
@@ -422,13 +410,13 @@ const isDirectoryExistAndNotEmpty = (path) => {
|
|
|
422
410
|
return existsSync(path) && readdirSync(path).length > 0;
|
|
423
411
|
};
|
|
424
412
|
const retrieveIgnoredFiles = async () => {
|
|
425
|
-
return (await helpers.exec("git clean -fdXn")).split(/\n|\r\n/).filter((cleanOutput) => PRESERVE_FILES.every((excludedFile) => !cleanOutput.includes(excludedFile))).map((cleanOutput) => cleanOutput.split(" ").at(-1));
|
|
413
|
+
return (await helpers.exec("git clean -fdXn")).split(/\n|\r\n/).filter((cleanOutput) => PRESERVE_FILES.every((excludedFile) => !cleanOutput.includes(excludedFile))).map((cleanOutput) => cleanOutput.split(" ").at(-1) ?? "").filter(Boolean);
|
|
426
414
|
};
|
|
427
415
|
const PRESERVE_FILES = ["node_modules"];
|
|
428
416
|
|
|
429
417
|
//#endregion
|
|
430
418
|
//#region package.json
|
|
431
|
-
var version = "2.
|
|
419
|
+
var version = "2.40.0-next-f8ad939";
|
|
432
420
|
|
|
433
421
|
//#endregion
|
|
434
422
|
//#region src/commands/create.ts
|
|
@@ -439,12 +427,12 @@ const createCreateCommand = (program) => {
|
|
|
439
427
|
}).task({ handler() {
|
|
440
428
|
botMessage({
|
|
441
429
|
description: "I can guarantee you a project creation in under 1 minute 🚀",
|
|
442
|
-
title: `I'm Stack v${version}
|
|
430
|
+
title: `I'm Stack v${version} 👋`,
|
|
443
431
|
type: "information"
|
|
444
432
|
});
|
|
445
433
|
} }).task({
|
|
446
434
|
async handler() {
|
|
447
|
-
await
|
|
435
|
+
await getPnpmVersion();
|
|
448
436
|
},
|
|
449
437
|
label: label$2("Check pre-requisites")
|
|
450
438
|
}).input({
|
|
@@ -467,64 +455,95 @@ const createCreateCommand = (program) => {
|
|
|
467
455
|
options: ["single-project", "multi-projects"],
|
|
468
456
|
type: "select"
|
|
469
457
|
}).task({
|
|
470
|
-
async handler({ inputDescription, inputName, inputUrl }) {
|
|
458
|
+
async handler({ inputDescription, inputName, inputTemplate, inputUrl }) {
|
|
471
459
|
if (!inputName) throw createError("stack create", "The project name is not optional. Make sure to provide a valid value (non-empty string).");
|
|
472
460
|
const { repoName, repoOwner } = (inputUrl.startsWith("git") ? /^git@.*:(?<repoOwner>.*)\/(?<repoName>.*)\.git$/ : /^https?:\/\/.*\/(?<repoOwner>.*)\/(?<repoName>.*)\.git$/).exec(inputUrl)?.groups ?? {};
|
|
473
461
|
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.");
|
|
474
462
|
const nodeVersion = await request.get("https://resolve-node.vercel.app/lts", "text");
|
|
475
|
-
const { version:
|
|
463
|
+
const { version: pnpmVersion } = await request.get("https://registry.npmjs.org/pnpm/latest", "json");
|
|
464
|
+
const projectName = slugify(inputName);
|
|
476
465
|
return {
|
|
477
466
|
licenseYear: (/* @__PURE__ */ new Date()).getFullYear().toString(),
|
|
478
467
|
nodeVersion: nodeVersion.replace("v", ""),
|
|
479
|
-
|
|
480
|
-
projectDescription:
|
|
481
|
-
projectName
|
|
468
|
+
pnpmVersion: String(pnpmVersion),
|
|
469
|
+
projectDescription: toCapitalLetter(inputDescription),
|
|
470
|
+
projectName,
|
|
482
471
|
projectUrl: inputUrl,
|
|
483
|
-
repoId: `${repoOwner}/${repoName}
|
|
472
|
+
repoId: `${repoOwner}/${repoName}`,
|
|
473
|
+
templatePath: resolveFromPackageDirectory("templates", inputTemplate),
|
|
474
|
+
workingPath: resolveFromWorkingDirectory(projectName)
|
|
484
475
|
};
|
|
485
476
|
},
|
|
486
477
|
key: "data",
|
|
487
478
|
label: label$2("Check and format input")
|
|
488
|
-
}).
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
479
|
+
}).input({
|
|
480
|
+
defaultValue: true,
|
|
481
|
+
key: "canRemoveExistingDirectoryInput",
|
|
482
|
+
label({ data: { projectName } }) {
|
|
483
|
+
return label$2(`\`${projectName}\` directory already exists, do you want to remove it?`);
|
|
493
484
|
},
|
|
494
|
-
|
|
495
|
-
return
|
|
485
|
+
skip({ data: { workingPath } }) {
|
|
486
|
+
return !existsSync(workingPath);
|
|
487
|
+
},
|
|
488
|
+
type: "confirm",
|
|
489
|
+
validate({ canRemoveExistingDirectoryInput, data: { projectName } }) {
|
|
490
|
+
if (canRemoveExistingDirectoryInput) return void 0;
|
|
491
|
+
return createError("mkdir", `Remove or rename the \`${projectName}\` existing directory to apply the template from a clean state.`);
|
|
496
492
|
}
|
|
497
493
|
}).task({
|
|
498
|
-
async handler({ data }) {
|
|
499
|
-
await
|
|
500
|
-
|
|
494
|
+
async handler({ canRemoveExistingDirectoryInput, data: { licenseYear, nodeVersion, pnpmVersion, projectDescription, projectName, projectUrl, repoId, templatePath, workingPath }, inputTemplate }) {
|
|
495
|
+
if (canRemoveExistingDirectoryInput) await rm(workingPath, {
|
|
496
|
+
force: true,
|
|
497
|
+
recursive: true
|
|
498
|
+
});
|
|
499
|
+
return createTemplateEngine(workingPath, {
|
|
500
|
+
projectName,
|
|
501
|
+
templateModel: {
|
|
502
|
+
licenseYear,
|
|
503
|
+
nodeVersion,
|
|
504
|
+
pnpmVersion,
|
|
505
|
+
projectDescription,
|
|
506
|
+
projectName,
|
|
507
|
+
projectUrl,
|
|
508
|
+
repoId
|
|
509
|
+
},
|
|
510
|
+
templateName: inputTemplate,
|
|
511
|
+
templatePath
|
|
512
|
+
});
|
|
501
513
|
},
|
|
502
|
-
|
|
514
|
+
key: "templateEngine",
|
|
515
|
+
label({ data: { projectName }, inputTemplate }) {
|
|
516
|
+
return label$2(`Copy \`${inputTemplate}\` template to \`${projectName}\` directory`);
|
|
517
|
+
}
|
|
503
518
|
}).task({
|
|
504
|
-
handler({
|
|
505
|
-
|
|
519
|
+
async handler({ templateEngine }) {
|
|
520
|
+
await templateEngine.processContents();
|
|
521
|
+
await templateEngine.processPaths();
|
|
506
522
|
},
|
|
507
|
-
label
|
|
523
|
+
label() {
|
|
524
|
+
return label$2("Process template");
|
|
525
|
+
}
|
|
508
526
|
}).task({
|
|
509
|
-
async handler({ data: {
|
|
510
|
-
await
|
|
527
|
+
async handler({ data: { projectUrl } }) {
|
|
528
|
+
await helpers.exec("git init");
|
|
529
|
+
await helpers.exec(`git remote add origin ${projectUrl}`);
|
|
511
530
|
},
|
|
512
|
-
label: label$2("
|
|
531
|
+
label: label$2("Initialize `git`")
|
|
513
532
|
}).task({
|
|
514
533
|
async handler() {
|
|
515
534
|
await setPackageManager();
|
|
516
535
|
},
|
|
517
536
|
label: label$2("Set up the package manager")
|
|
518
537
|
}).task({
|
|
519
|
-
async handler({ data }) {
|
|
538
|
+
async handler({ data: { projectName } }) {
|
|
520
539
|
const localDevelopmentDependencies = ["quickbundle", "vitest"];
|
|
521
540
|
const globalDevelopmentDependencies = ["@adbayb/stack"];
|
|
522
541
|
try {
|
|
523
542
|
await helpers.exec(`pnpm add ${globalDevelopmentDependencies.join(" ")} --save-dev --ignore-workspace-root-check`);
|
|
524
|
-
await helpers.exec(`pnpm add ${localDevelopmentDependencies.join(" ")} --save-dev --filter ${
|
|
543
|
+
await helpers.exec(`pnpm add ${localDevelopmentDependencies.join(" ")} --save-dev --filter ${projectName}`);
|
|
525
544
|
await helpers.exec("pnpm install");
|
|
526
545
|
} catch (error) {
|
|
527
|
-
throw createError("pnpm", error);
|
|
546
|
+
throw createError("pnpm", error instanceof Error ? error : new Error(String(error)));
|
|
528
547
|
}
|
|
529
548
|
},
|
|
530
549
|
label: label$2("Install dependencies")
|
|
@@ -539,79 +558,104 @@ const createCreateCommand = (program) => {
|
|
|
539
558
|
await helpers.exec("git commit -m \"chore: initial commit\"");
|
|
540
559
|
},
|
|
541
560
|
label: label$2("Commit")
|
|
542
|
-
}).task({ handler({ data }) {
|
|
561
|
+
}).task({ handler({ data: { projectName } }) {
|
|
543
562
|
botMessage({
|
|
544
|
-
description: `Run \`cd ./${
|
|
563
|
+
description: `Run \`cd ./${projectName}\` and Enjoy 🚀`,
|
|
545
564
|
title: "The project was successfully created",
|
|
546
565
|
type: "success"
|
|
547
566
|
});
|
|
548
567
|
} });
|
|
549
568
|
};
|
|
550
569
|
const label$2 = (message) => `${message} 🔨`;
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
const applyTemplate = (template, dataModel) => {
|
|
561
|
-
const templateExtension = ".tmpl";
|
|
562
|
-
const templateRootPath = resolveFromStackDirectory(join("./templates", template));
|
|
563
|
-
const projectRootPath = resolveFromProjectDirectory("./");
|
|
564
|
-
const templateExpressionRegExp = /{{(.*?)}}/g;
|
|
565
|
-
const evaluate = (content) => {
|
|
566
|
-
return content.replaceAll(templateExpressionRegExp, (_, key) => dataModel[key] || "");
|
|
567
|
-
};
|
|
568
|
-
/** Copy the template before mutations. */
|
|
569
|
-
cpSync(templateRootPath, projectRootPath, {
|
|
570
|
+
const slugify = (input) => {
|
|
571
|
+
return input.toLowerCase().replaceAll(/[^a-z0-9\s-]/g, "").trim().replaceAll(/\s+/g, "-").replaceAll(/-+/g, "-");
|
|
572
|
+
};
|
|
573
|
+
const toCapitalLetter = (input) => {
|
|
574
|
+
return input.charAt(0).toUpperCase() + input.slice(1);
|
|
575
|
+
};
|
|
576
|
+
const createTemplateEngine = async (workingPath, { projectName, templateModel, templateName, templatePath }) => {
|
|
577
|
+
if (!existsSync(workingPath)) await mkdir(workingPath);
|
|
578
|
+
await cp(templatePath, workingPath, {
|
|
570
579
|
force: true,
|
|
571
580
|
recursive: true
|
|
572
581
|
});
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
582
|
+
const gitignoreFile = join(workingPath, ".gitignore.tmpl");
|
|
583
|
+
if (existsSync(gitignoreFile)) await rename(gitignoreFile, join(workingPath, ".gitignore"));
|
|
584
|
+
const eslintConfigFile = join(workingPath, "eslint.config.js.tmpl");
|
|
585
|
+
if (existsSync(eslintConfigFile)) await rename(eslintConfigFile, join(workingPath, "eslint.config.js"));
|
|
586
|
+
const templateEntries = await getTemplateEntries(workingPath);
|
|
587
|
+
process.chdir(workingPath);
|
|
588
|
+
return {
|
|
589
|
+
async processContents() {
|
|
590
|
+
await Promise.all(templateEntries.filter(({ type }) => type === "content").map(async (entry) => {
|
|
591
|
+
return writeFile(entry.path, setTemplateVariables(entry, templateModel));
|
|
592
|
+
}));
|
|
593
|
+
},
|
|
594
|
+
async processPaths() {
|
|
595
|
+
const sortedDirectoryEntries = templateEntries.filter(({ content, type }) => {
|
|
596
|
+
if (type === "path.directory" || type === "path.file") return hasTemplateVariable(basename(content));
|
|
597
|
+
return false;
|
|
598
|
+
}).toSorted(({ path: pathA }, { path: pathB }) => pathB.length - pathA.length);
|
|
599
|
+
for (const entry of sortedDirectoryEntries) {
|
|
600
|
+
const newPath = setTemplateVariables(entry, templateModel);
|
|
601
|
+
await rename(entry.path, newPath);
|
|
602
|
+
}
|
|
603
|
+
await symlink(join(templateName === "single-project" ? projectName : join("libraries", projectName), "README.md"), "./README.md");
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
};
|
|
607
|
+
const getTemplateEntries = async (path) => {
|
|
608
|
+
const entries = await readdir(path, {
|
|
609
|
+
recursive: true,
|
|
610
|
+
withFileTypes: true
|
|
579
611
|
});
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
612
|
+
return (await Promise.all(entries.map(async (entry) => {
|
|
613
|
+
const isDirectory = entry.isDirectory();
|
|
614
|
+
if (!isDirectory && !entry.isFile()) return [];
|
|
615
|
+
const entryPath = resolve(entry.parentPath, entry.name);
|
|
616
|
+
return (isDirectory ? [{
|
|
617
|
+
content: entryPath,
|
|
618
|
+
type: "path.directory"
|
|
619
|
+
}] : [{
|
|
620
|
+
content: entryPath,
|
|
621
|
+
type: "path.file"
|
|
622
|
+
}, {
|
|
623
|
+
content: await readFile(entryPath, "utf8"),
|
|
624
|
+
type: "content"
|
|
625
|
+
}]).map(({ content, type }) => {
|
|
626
|
+
if (!hasTemplateVariable(content)) return void 0;
|
|
627
|
+
return {
|
|
628
|
+
content,
|
|
629
|
+
path: entryPath,
|
|
630
|
+
type
|
|
631
|
+
};
|
|
632
|
+
}).filter((input) => Boolean(input));
|
|
633
|
+
}))).flat();
|
|
634
|
+
};
|
|
635
|
+
const setTemplateVariables = (entry, model) => {
|
|
636
|
+
return entry.content.replaceAll(TEMPLATE_VARIABLE_MATCHER, (match, dataModelKey) => {
|
|
637
|
+
return model[dataModelKey] ?? match;
|
|
587
638
|
});
|
|
588
639
|
};
|
|
640
|
+
const TEMPLATE_VARIABLE_MATCHER = /* @__PURE__ */ new RegExp(/{{(.*?)}}/g, "gi");
|
|
641
|
+
const hasTemplateVariable = (input) => {
|
|
642
|
+
/**
|
|
643
|
+
* TemplateVariableMatcher.test() is not used since the `RegExp` is stateful when the global is
|
|
644
|
+
* used leading to some unstable results (relying on latest `lastIndex` set (lastIndex specifies
|
|
645
|
+
* the index at which to start the next match)). String.search is stateless.
|
|
646
|
+
*
|
|
647
|
+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test MDN documentation}.
|
|
648
|
+
*/
|
|
649
|
+
return input.search(TEMPLATE_VARIABLE_MATCHER) >= 0;
|
|
650
|
+
};
|
|
589
651
|
|
|
590
652
|
//#endregion
|
|
591
|
-
//#region src/commands/fix/
|
|
592
|
-
const
|
|
593
|
-
const fixFormatting = async (files) => {
|
|
594
|
-
let prettierFiles = [];
|
|
595
|
-
if (files.length === 0) prettierFiles.push(`"**/!(${PRETTIER_IGNORE_FILES.join("|")})"`);
|
|
596
|
-
else {
|
|
597
|
-
prettierFiles = files.filter((file) => {
|
|
598
|
-
return PRETTIER_IGNORE_FILES.every((filename) => !file.endsWith(filename));
|
|
599
|
-
});
|
|
600
|
-
if (prettierFiles.length === 0) return;
|
|
601
|
-
}
|
|
602
|
-
const arguments_ = [...prettierFiles];
|
|
603
|
-
if (existsSync(resolveFromProjectDirectory(".gitignore"))) arguments_.push("--ignore-path .gitignore");
|
|
604
|
-
arguments_.push("--write", "--ignore-unknown", "--no-error-on-unmatched-pattern");
|
|
605
|
-
try {
|
|
606
|
-
return await helpers.exec(`prettier ${arguments_.join(" ")}`);
|
|
607
|
-
} catch (error) {
|
|
608
|
-
throw createError("prettier", error);
|
|
609
|
-
}
|
|
610
|
-
};
|
|
653
|
+
//#region src/commands/fix/fixCode.ts
|
|
654
|
+
const fixCode = oxlint({ isFixMode: true });
|
|
611
655
|
|
|
612
656
|
//#endregion
|
|
613
|
-
//#region src/commands/fix/
|
|
614
|
-
const
|
|
657
|
+
//#region src/commands/fix/fixFormatting.ts
|
|
658
|
+
const fixFormatting = oxfmt({ isFixMode: true });
|
|
615
659
|
|
|
616
660
|
//#endregion
|
|
617
661
|
//#region src/commands/fix/fix.ts
|
|
@@ -631,14 +675,14 @@ const createFixCommand = (program) => {
|
|
|
631
675
|
label: label$1("Prepare the project")
|
|
632
676
|
}).task({
|
|
633
677
|
async handler(_, argv) {
|
|
634
|
-
await
|
|
678
|
+
await fixFormatting(argv.operands);
|
|
635
679
|
},
|
|
636
|
-
label: label$1("Fix
|
|
680
|
+
label: label$1("Fix formatting issues")
|
|
637
681
|
}).task({
|
|
638
682
|
async handler(_, argv) {
|
|
639
|
-
await
|
|
683
|
+
await fixCode(argv.operands);
|
|
640
684
|
},
|
|
641
|
-
label: label$1("Fix
|
|
685
|
+
label: label$1("Fix code issues")
|
|
642
686
|
});
|
|
643
687
|
};
|
|
644
688
|
const label$1 = (message) => `${message} 🚑`;
|
|
@@ -663,7 +707,7 @@ const createInstallCommand = (program) => {
|
|
|
663
707
|
};
|
|
664
708
|
const label = (message) => `${message} 📲`;
|
|
665
709
|
const installGitHook = async (hook, content) => {
|
|
666
|
-
const filename =
|
|
710
|
+
const filename = resolveFromWorkingDirectory(`.git/hooks/${hook}`);
|
|
667
711
|
await writeFile(filename, content);
|
|
668
712
|
return chmod(filename, "0755");
|
|
669
713
|
};
|