@adbayb/stack 2.39.0 → 2.40.0-next-7baa8ed
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 +1 -1
- 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 +28 -0
- package/configs/typescript/index.json +1 -7
- package/dist/index.js +204 -162
- package/package.json +26 -44
- 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} +2 -0
- 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} +2 -0
- 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,15 +84,14 @@ 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
|
const request = { async get(url, responseType) {
|
|
96
97
|
const response = await fetch(url);
|
|
@@ -100,26 +101,30 @@ const request = { async get(url, responseType) {
|
|
|
100
101
|
})})`);
|
|
101
102
|
return response[responseType === "text" ? "text" : "json"]();
|
|
102
103
|
} };
|
|
103
|
-
const
|
|
104
|
-
let eslintFiles = [];
|
|
105
|
-
if (files.length === 0) eslintFiles.push(".");
|
|
106
|
-
else {
|
|
107
|
-
eslintFiles = files.filter((file) => {
|
|
108
|
-
return ESLINT_EXTENSIONS.some((extension) => file.endsWith(extension));
|
|
109
|
-
});
|
|
110
|
-
if (eslintFiles.length === 0) return;
|
|
111
|
-
}
|
|
104
|
+
const oxlint = (options) => async (files = []) => {
|
|
112
105
|
const arguments_ = [
|
|
113
|
-
...
|
|
114
|
-
"--
|
|
115
|
-
`--cache-location ${resolveFromProjectDirectory("node_modules/.cache/.eslintcache")}`,
|
|
106
|
+
...files,
|
|
107
|
+
"--disable-nested-config",
|
|
116
108
|
"--no-error-on-unmatched-pattern"
|
|
117
109
|
];
|
|
118
110
|
if (options.isFixMode) arguments_.push("--fix");
|
|
119
111
|
try {
|
|
120
|
-
return await helpers.exec(`
|
|
112
|
+
return await helpers.exec(`oxlint ${arguments_.join(" ")}`);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
throw createError("oxlint", error);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
const oxfmt = (options) => async (files = []) => {
|
|
118
|
+
const arguments_ = [
|
|
119
|
+
...files,
|
|
120
|
+
"--disable-nested-config",
|
|
121
|
+
"--no-error-on-unmatched-pattern",
|
|
122
|
+
options.isFixMode ? "--write" : "--check"
|
|
123
|
+
];
|
|
124
|
+
try {
|
|
125
|
+
return await helpers.exec(`oxfmt ${arguments_.join(" ")}`);
|
|
121
126
|
} catch (error) {
|
|
122
|
-
throw createError("
|
|
127
|
+
throw createError("oxfmt", error);
|
|
123
128
|
}
|
|
124
129
|
};
|
|
125
130
|
const turbo = async (command, options = {}) => {
|
|
@@ -159,18 +164,6 @@ const changeset = async (command) => {
|
|
|
159
164
|
throw createError("changeset", error);
|
|
160
165
|
}
|
|
161
166
|
};
|
|
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
167
|
|
|
175
168
|
//#endregion
|
|
176
169
|
//#region src/commands/build.ts
|
|
@@ -185,7 +178,7 @@ const createBuildCommand = (program) => {
|
|
|
185
178
|
|
|
186
179
|
//#endregion
|
|
187
180
|
//#region src/commands/check/checkCode.ts
|
|
188
|
-
const checkCode =
|
|
181
|
+
const checkCode = oxlint({ isFixMode: false });
|
|
189
182
|
|
|
190
183
|
//#endregion
|
|
191
184
|
//#region src/commands/check/checkCommit.ts
|
|
@@ -294,14 +287,8 @@ const isExcluded = (version) => {
|
|
|
294
287
|
const hasCaret = (version) => version.startsWith("^");
|
|
295
288
|
|
|
296
289
|
//#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
|
-
};
|
|
290
|
+
//#region src/commands/check/checkFormatting.ts
|
|
291
|
+
const checkFormatting = oxfmt({ isFixMode: false });
|
|
305
292
|
|
|
306
293
|
//#endregion
|
|
307
294
|
//#region src/commands/check/check.ts
|
|
@@ -309,7 +296,7 @@ const ONLY_VALUES = [
|
|
|
309
296
|
"commit",
|
|
310
297
|
"code",
|
|
311
298
|
"dependency",
|
|
312
|
-
"
|
|
299
|
+
"formatting"
|
|
313
300
|
];
|
|
314
301
|
const createCheckCommand = (program) => {
|
|
315
302
|
program.command({
|
|
@@ -342,6 +329,13 @@ const createCheckCommand = (program) => {
|
|
|
342
329
|
},
|
|
343
330
|
label: label$4("Check dependency compliance"),
|
|
344
331
|
skip: ifFilterDefinedAndNotEqualTo("dependency")
|
|
332
|
+
}).task({
|
|
333
|
+
async handler(_, argv) {
|
|
334
|
+
const filenames = argv.operands;
|
|
335
|
+
await checkFormatting(filenames);
|
|
336
|
+
},
|
|
337
|
+
label: label$4("Check formatting compliance"),
|
|
338
|
+
skip: ifFilterDefinedAndNotEqualTo("formatting")
|
|
345
339
|
}).task({
|
|
346
340
|
async handler(_, argv) {
|
|
347
341
|
const filenames = argv.operands;
|
|
@@ -349,14 +343,6 @@ const createCheckCommand = (program) => {
|
|
|
349
343
|
},
|
|
350
344
|
label: label$4("Check code compliance"),
|
|
351
345
|
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
346
|
}).task({
|
|
361
347
|
async handler() {
|
|
362
348
|
await checkCommit();
|
|
@@ -382,7 +368,7 @@ const createCleanCommand = (program) => {
|
|
|
382
368
|
async handler() {
|
|
383
369
|
const cachePath = "node_modules/.cache";
|
|
384
370
|
const files = await retrieveIgnoredFiles();
|
|
385
|
-
if (isDirectoryExistAndNotEmpty(
|
|
371
|
+
if (isDirectoryExistAndNotEmpty(resolveFromWorkingDirectory(cachePath))) files.push(cachePath);
|
|
386
372
|
return files;
|
|
387
373
|
},
|
|
388
374
|
key: "files",
|
|
@@ -428,7 +414,7 @@ const PRESERVE_FILES = ["node_modules"];
|
|
|
428
414
|
|
|
429
415
|
//#endregion
|
|
430
416
|
//#region package.json
|
|
431
|
-
var version = "2.
|
|
417
|
+
var version = "2.40.0-next-7baa8ed";
|
|
432
418
|
|
|
433
419
|
//#endregion
|
|
434
420
|
//#region src/commands/create.ts
|
|
@@ -439,12 +425,12 @@ const createCreateCommand = (program) => {
|
|
|
439
425
|
}).task({ handler() {
|
|
440
426
|
botMessage({
|
|
441
427
|
description: "I can guarantee you a project creation in under 1 minute 🚀",
|
|
442
|
-
title: `I'm Stack v${version}
|
|
428
|
+
title: `I'm Stack v${version} 👋`,
|
|
443
429
|
type: "information"
|
|
444
430
|
});
|
|
445
431
|
} }).task({
|
|
446
432
|
async handler() {
|
|
447
|
-
await
|
|
433
|
+
await getPnpmVersion();
|
|
448
434
|
},
|
|
449
435
|
label: label$2("Check pre-requisites")
|
|
450
436
|
}).input({
|
|
@@ -467,61 +453,92 @@ const createCreateCommand = (program) => {
|
|
|
467
453
|
options: ["single-project", "multi-projects"],
|
|
468
454
|
type: "select"
|
|
469
455
|
}).task({
|
|
470
|
-
async handler({ inputDescription, inputName, inputUrl }) {
|
|
456
|
+
async handler({ inputDescription, inputName, inputTemplate, inputUrl }) {
|
|
471
457
|
if (!inputName) throw createError("stack create", "The project name is not optional. Make sure to provide a valid value (non-empty string).");
|
|
472
458
|
const { repoName, repoOwner } = (inputUrl.startsWith("git") ? /^git@.*:(?<repoOwner>.*)\/(?<repoName>.*)\.git$/ : /^https?:\/\/.*\/(?<repoOwner>.*)\/(?<repoName>.*)\.git$/).exec(inputUrl)?.groups ?? {};
|
|
473
459
|
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
460
|
const nodeVersion = await request.get("https://resolve-node.vercel.app/lts", "text");
|
|
475
|
-
const { version:
|
|
461
|
+
const { version: pnpmVersion } = await request.get("https://registry.npmjs.org/pnpm/latest", "json");
|
|
462
|
+
const projectName = slugify(inputName);
|
|
476
463
|
return {
|
|
477
464
|
licenseYear: (/* @__PURE__ */ new Date()).getFullYear().toString(),
|
|
478
465
|
nodeVersion: nodeVersion.replace("v", ""),
|
|
479
|
-
|
|
480
|
-
projectDescription:
|
|
481
|
-
projectName
|
|
466
|
+
pnpmVersion: String(pnpmVersion),
|
|
467
|
+
projectDescription: toCapitalLetter(inputDescription),
|
|
468
|
+
projectName,
|
|
482
469
|
projectUrl: inputUrl,
|
|
483
|
-
repoId: `${repoOwner}/${repoName}
|
|
470
|
+
repoId: `${repoOwner}/${repoName}`,
|
|
471
|
+
templatePath: resolveFromPackageDirectory("templates", inputTemplate),
|
|
472
|
+
workingPath: resolveFromWorkingDirectory(projectName)
|
|
484
473
|
};
|
|
485
474
|
},
|
|
486
475
|
key: "data",
|
|
487
476
|
label: label$2("Check and format input")
|
|
488
|
-
}).
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
477
|
+
}).input({
|
|
478
|
+
defaultValue: true,
|
|
479
|
+
key: "canRemoveExistingDirectoryInput",
|
|
480
|
+
label({ data: { projectName } }) {
|
|
481
|
+
return label$2(`\`${projectName}\` directory already exists, do you want to remove it?`);
|
|
493
482
|
},
|
|
494
|
-
|
|
495
|
-
return
|
|
483
|
+
skip({ data: { workingPath } }) {
|
|
484
|
+
return !existsSync(workingPath);
|
|
485
|
+
},
|
|
486
|
+
type: "confirm",
|
|
487
|
+
validate({ canRemoveExistingDirectoryInput, data: { projectName } }) {
|
|
488
|
+
if (canRemoveExistingDirectoryInput) return;
|
|
489
|
+
return createError("mkdir", `Remove or rename the \`${projectName}\` existing directory to apply the template from a clean state.`);
|
|
496
490
|
}
|
|
497
491
|
}).task({
|
|
498
|
-
async handler({ data }) {
|
|
499
|
-
await
|
|
500
|
-
|
|
492
|
+
async handler({ canRemoveExistingDirectoryInput, data: { licenseYear, nodeVersion, pnpmVersion, projectDescription, projectName, projectUrl, repoId, templatePath, workingPath }, inputTemplate }) {
|
|
493
|
+
if (canRemoveExistingDirectoryInput) await rm(workingPath, {
|
|
494
|
+
force: true,
|
|
495
|
+
recursive: true
|
|
496
|
+
});
|
|
497
|
+
return createTemplateEngine(workingPath, {
|
|
498
|
+
projectName,
|
|
499
|
+
templateModel: {
|
|
500
|
+
licenseYear,
|
|
501
|
+
nodeVersion,
|
|
502
|
+
pnpmVersion,
|
|
503
|
+
projectDescription,
|
|
504
|
+
projectName,
|
|
505
|
+
projectUrl,
|
|
506
|
+
repoId
|
|
507
|
+
},
|
|
508
|
+
templateName: inputTemplate,
|
|
509
|
+
templatePath
|
|
510
|
+
});
|
|
501
511
|
},
|
|
502
|
-
|
|
512
|
+
key: "templateEngine",
|
|
513
|
+
label({ data: { projectName }, inputTemplate }) {
|
|
514
|
+
return label$2(`Copy \`${inputTemplate}\` template to \`${projectName}\` directory`);
|
|
515
|
+
}
|
|
503
516
|
}).task({
|
|
504
|
-
handler({
|
|
505
|
-
|
|
517
|
+
async handler({ templateEngine }) {
|
|
518
|
+
await templateEngine.processContents();
|
|
519
|
+
await templateEngine.processPaths();
|
|
506
520
|
},
|
|
507
|
-
label
|
|
521
|
+
label() {
|
|
522
|
+
return label$2("Process template");
|
|
523
|
+
}
|
|
508
524
|
}).task({
|
|
509
|
-
async handler({ data: {
|
|
510
|
-
await
|
|
525
|
+
async handler({ data: { projectUrl } }) {
|
|
526
|
+
await helpers.exec("git init");
|
|
527
|
+
await helpers.exec(`git remote add origin ${projectUrl}`);
|
|
511
528
|
},
|
|
512
|
-
label: label$2("
|
|
529
|
+
label: label$2("Initialize `git`")
|
|
513
530
|
}).task({
|
|
514
531
|
async handler() {
|
|
515
532
|
await setPackageManager();
|
|
516
533
|
},
|
|
517
534
|
label: label$2("Set up the package manager")
|
|
518
535
|
}).task({
|
|
519
|
-
async handler({ data }) {
|
|
536
|
+
async handler({ data: { projectName } }) {
|
|
520
537
|
const localDevelopmentDependencies = ["quickbundle", "vitest"];
|
|
521
538
|
const globalDevelopmentDependencies = ["@adbayb/stack"];
|
|
522
539
|
try {
|
|
523
540
|
await helpers.exec(`pnpm add ${globalDevelopmentDependencies.join(" ")} --save-dev --ignore-workspace-root-check`);
|
|
524
|
-
await helpers.exec(`pnpm add ${localDevelopmentDependencies.join(" ")} --save-dev --filter ${
|
|
541
|
+
await helpers.exec(`pnpm add ${localDevelopmentDependencies.join(" ")} --save-dev --filter ${projectName}`);
|
|
525
542
|
await helpers.exec("pnpm install");
|
|
526
543
|
} catch (error) {
|
|
527
544
|
throw createError("pnpm", error);
|
|
@@ -539,79 +556,104 @@ const createCreateCommand = (program) => {
|
|
|
539
556
|
await helpers.exec("git commit -m \"chore: initial commit\"");
|
|
540
557
|
},
|
|
541
558
|
label: label$2("Commit")
|
|
542
|
-
}).task({ handler({ data }) {
|
|
559
|
+
}).task({ handler({ data: { projectName } }) {
|
|
543
560
|
botMessage({
|
|
544
|
-
description: `Run \`cd ./${
|
|
561
|
+
description: `Run \`cd ./${projectName}\` and Enjoy 🚀`,
|
|
545
562
|
title: "The project was successfully created",
|
|
546
563
|
type: "success"
|
|
547
564
|
});
|
|
548
565
|
} });
|
|
549
566
|
};
|
|
550
567
|
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, {
|
|
568
|
+
const slugify = (input) => {
|
|
569
|
+
return input.toLowerCase().replaceAll(/[^a-z0-9\s-]/g, "").trim().replaceAll(/\s+/g, "-").replaceAll(/-+/g, "-");
|
|
570
|
+
};
|
|
571
|
+
const toCapitalLetter = (input) => {
|
|
572
|
+
return input.charAt(0).toUpperCase() + input.slice(1);
|
|
573
|
+
};
|
|
574
|
+
const createTemplateEngine = async (workingPath, { projectName, templateModel, templateName, templatePath }) => {
|
|
575
|
+
if (!existsSync(workingPath)) await mkdir(workingPath);
|
|
576
|
+
await cp(templatePath, workingPath, {
|
|
570
577
|
force: true,
|
|
571
578
|
recursive: true
|
|
572
579
|
});
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
580
|
+
const gitignoreFile = join(workingPath, ".gitignore.tmpl");
|
|
581
|
+
if (existsSync(gitignoreFile)) await rename(gitignoreFile, join(workingPath, ".gitignore"));
|
|
582
|
+
const eslintConfigFile = join(workingPath, "eslint.config.js.tmpl");
|
|
583
|
+
if (existsSync(eslintConfigFile)) await rename(eslintConfigFile, join(workingPath, "eslint.config.js"));
|
|
584
|
+
const templateEntries = await getTemplateEntries(workingPath);
|
|
585
|
+
process.chdir(workingPath);
|
|
586
|
+
return {
|
|
587
|
+
async processContents() {
|
|
588
|
+
await Promise.all(templateEntries.filter(({ type }) => type === "content").map(async (entry) => {
|
|
589
|
+
return writeFile(entry.path, setTemplateVariables(entry, templateModel));
|
|
590
|
+
}));
|
|
591
|
+
},
|
|
592
|
+
async processPaths() {
|
|
593
|
+
const sortedDirectoryEntries = templateEntries.filter(({ content, type }) => {
|
|
594
|
+
if (type === "path.directory" || type === "path.file") return hasTemplateVariable(basename(content));
|
|
595
|
+
return false;
|
|
596
|
+
}).toSorted(({ path: pathA }, { path: pathB }) => pathB.length - pathA.length);
|
|
597
|
+
for (const entry of sortedDirectoryEntries) {
|
|
598
|
+
const newPath = setTemplateVariables(entry, templateModel);
|
|
599
|
+
await rename(entry.path, newPath);
|
|
600
|
+
}
|
|
601
|
+
await symlink(join(templateName === "single-project" ? projectName : join("libraries", projectName), "README.md"), "./README.md");
|
|
602
|
+
}
|
|
603
|
+
};
|
|
604
|
+
};
|
|
605
|
+
const getTemplateEntries = async (path) => {
|
|
606
|
+
const entries = await readdir(path, {
|
|
607
|
+
recursive: true,
|
|
608
|
+
withFileTypes: true
|
|
579
609
|
});
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
610
|
+
return (await Promise.all(entries.map(async (entry) => {
|
|
611
|
+
const isDirectory = entry.isDirectory();
|
|
612
|
+
if (!isDirectory && !entry.isFile()) return [];
|
|
613
|
+
const entryPath = resolve(entry.parentPath, entry.name);
|
|
614
|
+
return (isDirectory ? [{
|
|
615
|
+
content: entryPath,
|
|
616
|
+
type: "path.directory"
|
|
617
|
+
}] : [{
|
|
618
|
+
content: entryPath,
|
|
619
|
+
type: "path.file"
|
|
620
|
+
}, {
|
|
621
|
+
content: await readFile(entryPath, "utf8"),
|
|
622
|
+
type: "content"
|
|
623
|
+
}]).map(({ content, type }) => {
|
|
624
|
+
if (!hasTemplateVariable(content)) return void 0;
|
|
625
|
+
return {
|
|
626
|
+
content,
|
|
627
|
+
path: entryPath,
|
|
628
|
+
type
|
|
629
|
+
};
|
|
630
|
+
}).filter((input) => Boolean(input));
|
|
631
|
+
}))).flat();
|
|
632
|
+
};
|
|
633
|
+
const setTemplateVariables = (entry, model) => {
|
|
634
|
+
return entry.content.replaceAll(TEMPLATE_VARIABLE_MATCHER, (match, dataModelKey) => {
|
|
635
|
+
return model[dataModelKey] ?? match;
|
|
587
636
|
});
|
|
588
637
|
};
|
|
638
|
+
const TEMPLATE_VARIABLE_MATCHER = /* @__PURE__ */ new RegExp(/{{(.*?)}}/g, "gi");
|
|
639
|
+
const hasTemplateVariable = (input) => {
|
|
640
|
+
/**
|
|
641
|
+
* TemplateVariableMatcher.test() is not used since the `RegExp` is stateful when the global is
|
|
642
|
+
* used leading to some unstable results (relying on latest `lastIndex` set (lastIndex specifies
|
|
643
|
+
* the index at which to start the next match)). String.search is stateless.
|
|
644
|
+
*
|
|
645
|
+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test MDN documentation}.
|
|
646
|
+
*/
|
|
647
|
+
return input.search(TEMPLATE_VARIABLE_MATCHER) >= 0;
|
|
648
|
+
};
|
|
589
649
|
|
|
590
650
|
//#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
|
-
};
|
|
651
|
+
//#region src/commands/fix/fixCode.ts
|
|
652
|
+
const fixCode = oxlint({ isFixMode: true });
|
|
611
653
|
|
|
612
654
|
//#endregion
|
|
613
|
-
//#region src/commands/fix/
|
|
614
|
-
const
|
|
655
|
+
//#region src/commands/fix/fixFormatting.ts
|
|
656
|
+
const fixFormatting = oxfmt({ isFixMode: true });
|
|
615
657
|
|
|
616
658
|
//#endregion
|
|
617
659
|
//#region src/commands/fix/fix.ts
|
|
@@ -631,14 +673,14 @@ const createFixCommand = (program) => {
|
|
|
631
673
|
label: label$1("Prepare the project")
|
|
632
674
|
}).task({
|
|
633
675
|
async handler(_, argv) {
|
|
634
|
-
await
|
|
676
|
+
await fixFormatting(argv.operands);
|
|
635
677
|
},
|
|
636
|
-
label: label$1("Fix
|
|
678
|
+
label: label$1("Fix formatting issues")
|
|
637
679
|
}).task({
|
|
638
680
|
async handler(_, argv) {
|
|
639
|
-
await
|
|
681
|
+
await fixCode(argv.operands);
|
|
640
682
|
},
|
|
641
|
-
label: label$1("Fix
|
|
683
|
+
label: label$1("Fix code issues")
|
|
642
684
|
});
|
|
643
685
|
};
|
|
644
686
|
const label$1 = (message) => `${message} 🚑`;
|
|
@@ -663,7 +705,7 @@ const createInstallCommand = (program) => {
|
|
|
663
705
|
};
|
|
664
706
|
const label = (message) => `${message} 📲`;
|
|
665
707
|
const installGitHook = async (hook, content) => {
|
|
666
|
-
const filename =
|
|
708
|
+
const filename = resolveFromWorkingDirectory(`.git/hooks/${hook}`);
|
|
667
709
|
await writeFile(filename, content);
|
|
668
710
|
return chmod(filename, "0755");
|
|
669
711
|
};
|
package/package.json
CHANGED
|
@@ -1,33 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adbayb/stack",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.40.0-next-7baa8ed",
|
|
4
4
|
"description": "My opinionated JavaScript-based toolchain",
|
|
5
5
|
"keywords": [
|
|
6
|
-
"
|
|
6
|
+
"development",
|
|
7
7
|
"scripts",
|
|
8
|
-
"
|
|
9
|
-
"
|
|
8
|
+
"stack",
|
|
9
|
+
"toolchain"
|
|
10
10
|
],
|
|
11
|
-
"repository": {
|
|
12
|
-
"type": "git",
|
|
13
|
-
"url": "https://github.com/adbayb/stack.git",
|
|
14
|
-
"directory": "applications/stack"
|
|
15
|
-
},
|
|
16
11
|
"license": "MIT",
|
|
17
12
|
"author": {
|
|
18
13
|
"name": "Ayoub Adib",
|
|
19
14
|
"email": "adbayb@gmail.com",
|
|
20
15
|
"url": "https://twitter.com/adbayb"
|
|
21
16
|
},
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
"default": "./dist/index.js"
|
|
27
|
-
},
|
|
28
|
-
"./eslint": "./configs/eslint/index.js",
|
|
29
|
-
"./prettier": "./configs/prettier/index.js",
|
|
30
|
-
"./typescript": "./configs/typescript/index.json"
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/adbayb/stack.git",
|
|
20
|
+
"directory": "applications/stack"
|
|
31
21
|
},
|
|
32
22
|
"bin": {
|
|
33
23
|
"stack": "./bin/index.js"
|
|
@@ -38,44 +28,36 @@
|
|
|
38
28
|
"dist",
|
|
39
29
|
"templates"
|
|
40
30
|
],
|
|
31
|
+
"type": "module",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"source": "./src/index.ts",
|
|
35
|
+
"default": "./dist/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./oxfmt": "./configs/oxfmt/index.ts",
|
|
38
|
+
"./oxlint": "./configs/oxlint/index.ts",
|
|
39
|
+
"./typescript": "./configs/typescript/index.json"
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public",
|
|
43
|
+
"registry": "https://registry.npmjs.org"
|
|
44
|
+
},
|
|
41
45
|
"dependencies": {
|
|
42
46
|
"@changesets/changelog-github": "^0.7.0",
|
|
43
47
|
"@changesets/cli": "^2.31.0",
|
|
44
48
|
"@commitlint/cli": "^21.0.2",
|
|
45
49
|
"@commitlint/config-conventional": "^21.0.2",
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"@vitest/eslint-plugin": "^1.6.20",
|
|
50
|
-
"eslint": "^10.5.0",
|
|
51
|
-
"eslint-config-prettier": "^10.1.8",
|
|
52
|
-
"eslint-import-resolver-typescript": "^4.4.5",
|
|
53
|
-
"eslint-plugin-depend": "^1.5.0",
|
|
54
|
-
"eslint-plugin-import-x": "^4.16.2",
|
|
55
|
-
"eslint-plugin-jsdoc": "^63.0.6",
|
|
56
|
-
"eslint-plugin-mdx": "^3.8.1",
|
|
57
|
-
"eslint-plugin-n": "^18.1.0",
|
|
58
|
-
"eslint-plugin-perfectionist": "^5.9.1",
|
|
59
|
-
"eslint-plugin-prettier": "^5.5.6",
|
|
60
|
-
"eslint-plugin-sonarjs": "^4.1.0",
|
|
61
|
-
"eslint-plugin-unicorn": "^68.0.0",
|
|
62
|
-
"fdir": "^6.5.0",
|
|
63
|
-
"globals": "^17.6.0",
|
|
64
|
-
"prettier": "^3.8.4",
|
|
65
|
-
"prettier-plugin-packagejson": "^3.0.2",
|
|
50
|
+
"oxfmt": "^0.57.0",
|
|
51
|
+
"oxlint": "^1.72.0",
|
|
52
|
+
"oxlint-tsgolint": "^0.24.0",
|
|
66
53
|
"termost": "^1.9.1",
|
|
67
54
|
"turbo": "^2.9.18",
|
|
68
|
-
"typescript": "^6.0.3"
|
|
69
|
-
"typescript-eslint": "^8.61.1"
|
|
55
|
+
"typescript": "^6.0.3"
|
|
70
56
|
},
|
|
71
57
|
"devDependencies": {
|
|
72
58
|
"@types/node": "24.13.2",
|
|
73
59
|
"quickbundle": "3.0.0"
|
|
74
60
|
},
|
|
75
|
-
"publishConfig": {
|
|
76
|
-
"access": "public",
|
|
77
|
-
"registry": "https://registry.npmjs.org"
|
|
78
|
-
},
|
|
79
61
|
"scripts": {
|
|
80
62
|
"build": "quickbundle build",
|
|
81
63
|
"prestart": "pnpm build",
|