@adbayb/stack 0.0.0-next-912a585 → 0.0.0-next-ece28ab
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 +20 -16
- package/config/eslint/README.md +1 -1
- package/config/prettier/README.md +1 -1
- package/config/typescript/README.md +1 -1
- package/dist/index.js +213 -95
- package/package.json +1 -2
package/README.md
CHANGED
|
@@ -59,19 +59,13 @@ stack release
|
|
|
59
59
|
|
|
60
60
|
<br>
|
|
61
61
|
|
|
62
|
-
##
|
|
62
|
+
## 🌍 Ecosystem
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
flowchart TD
|
|
66
|
-
N("npm init @adbayb"):::strokeWidth -- Runs --> C("@adbayb/create"):::strokeWidth
|
|
67
|
-
C -- Runs --> S("@adbayb/stack"):::strokeWidth
|
|
68
|
-
S -- "Scaffolds (via template and shared configurations)" --> P("New project"):::strokeWidth
|
|
69
|
-
style C fill:#daf2d7,stroke:#90cf8e
|
|
70
|
-
style S fill:#daf2d7,stroke:#90cf8e
|
|
71
|
-
classDef strokeWidth stroke-width:3px
|
|
72
|
-
```
|
|
64
|
+
### Available configurations
|
|
73
65
|
|
|
74
|
-
|
|
66
|
+
- [@adbayb/stack/eslint](https://github.com/adbayb/stack/blob/main/stack/config/eslint/) to align logical and stylistic rules across projects.
|
|
67
|
+
- [@adbayb/stack/prettier](https://github.com/adbayb/stack/blob/main/stack/config/prettier/) to align code formatting conventions across projects.
|
|
68
|
+
- [@adbayb/stack/typescript](https://github.com/adbayb/stack/blob/main/stack/config/typescript/) to align type checking and processing across projects.
|
|
75
69
|
|
|
76
70
|
### Used technologies
|
|
77
71
|
|
|
@@ -87,15 +81,25 @@ flowchart TD
|
|
|
87
81
|
- [Scripts](https://conventionalscripts.org/)
|
|
88
82
|
- [File Structure](https://nx.dev/concepts/decisions/folder-structure)
|
|
89
83
|
- Monorepo-first structure to:
|
|
90
|
-
- Keep a consistent file structure pattern across templates (ease template familiarity and onboarding)
|
|
91
|
-
- Ease examples setup
|
|
92
|
-
- Not clutter the published package with development-related context, metadata, and artifacts
|
|
84
|
+
- Keep a consistent file structure pattern across templates (ease template familiarity and onboarding).
|
|
85
|
+
- Ease examples setup.
|
|
86
|
+
- Not clutter the published package with development-related context, metadata, and artifacts.
|
|
93
87
|
|
|
94
88
|
<br>
|
|
95
89
|
|
|
96
|
-
##
|
|
90
|
+
## 🏗️ Architecture
|
|
91
|
+
|
|
92
|
+
### Project creation workflow
|
|
97
93
|
|
|
98
|
-
|
|
94
|
+
```mermaid
|
|
95
|
+
flowchart TD
|
|
96
|
+
N("npm init @adbayb"):::strokeWidth -- Runs --> C("@adbayb/create"):::strokeWidth
|
|
97
|
+
C -- Runs --> S("@adbayb/stack create"):::strokeWidth
|
|
98
|
+
S -- "Scaffolds (via template and shared configurations)" --> P("New project"):::strokeWidth
|
|
99
|
+
style C fill:#daf2d7,stroke:#90cf8e
|
|
100
|
+
style S fill:#daf2d7,stroke:#90cf8e
|
|
101
|
+
classDef strokeWidth stroke-width:3px
|
|
102
|
+
```
|
|
99
103
|
|
|
100
104
|
<br>
|
|
101
105
|
|
package/config/eslint/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
## ✨ Features
|
|
10
10
|
|
|
11
|
-
This package offers opinionated but extensible **[ESLint](https://eslint.org/)** configurations to align logical and stylistic rules across
|
|
11
|
+
This package offers opinionated but extensible **[ESLint](https://eslint.org/)** configurations to align logical and stylistic rules across projects.
|
|
12
12
|
|
|
13
13
|
It allows to:
|
|
14
14
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
## ✨ Features
|
|
10
10
|
|
|
11
|
-
This package offers opinionated but extensible **[Prettier](https://prettier.io/)** configurations to align formatting
|
|
11
|
+
This package offers opinionated but extensible **[Prettier](https://prettier.io/)** configurations to align code formatting conventions across projects.
|
|
12
12
|
|
|
13
13
|
It allows to:
|
|
14
14
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
## ✨ Features
|
|
10
10
|
|
|
11
|
-
This package offers opinionated but extensible **[TypeScript](https://www.typescriptlang.org/tsconfig/)** configurations to align type
|
|
11
|
+
This package offers opinionated but extensible **[TypeScript](https://www.typescriptlang.org/tsconfig/)** configurations to align type checking and processing across projects.
|
|
12
12
|
|
|
13
13
|
It allows to:
|
|
14
14
|
|
package/dist/index.js
CHANGED
|
@@ -2,15 +2,20 @@ import { helpers, termost } from 'termost';
|
|
|
2
2
|
import process from 'node:process';
|
|
3
3
|
import { resolve, join } from 'node:path';
|
|
4
4
|
import { createRequire } from 'node:module';
|
|
5
|
-
import { existsSync, cpSync, readFileSync, renameSync, writeFileSync, readdirSync } from 'node:fs';
|
|
6
5
|
import { writeFile, chmod, mkdir, symlink } from 'node:fs/promises';
|
|
6
|
+
import { existsSync, cpSync, readFileSync, renameSync, writeFileSync, readdirSync } from 'node:fs';
|
|
7
7
|
import { fdir } from 'fdir';
|
|
8
8
|
|
|
9
|
-
var version = "0.0.0-next-
|
|
9
|
+
var version = "0.0.0-next-ece28ab";
|
|
10
10
|
|
|
11
11
|
const VERSION = version;
|
|
12
12
|
|
|
13
13
|
const require = createRequire(import.meta.url);
|
|
14
|
+
function assert(expectedCondition, createError) {
|
|
15
|
+
if (!expectedCondition) {
|
|
16
|
+
throw createError();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
14
19
|
/**
|
|
15
20
|
* Helper to format log messages with a welcoming bot.
|
|
16
21
|
* @param input - Message factory.
|
|
@@ -58,6 +63,14 @@ ${input.body}
|
|
|
58
63
|
*/ const resolveFromStackDirectory = (path)=>{
|
|
59
64
|
return resolve(import.meta.dirname, "../", path);
|
|
60
65
|
};
|
|
66
|
+
const createError = (bin, error)=>{
|
|
67
|
+
const errorMessage = `\`${bin}\` command failed.\n${String(error)}`;
|
|
68
|
+
if (error instanceof Error) {
|
|
69
|
+
error.message = errorMessage;
|
|
70
|
+
return error;
|
|
71
|
+
}
|
|
72
|
+
return new Error(errorMessage);
|
|
73
|
+
};
|
|
61
74
|
const getNpmVersion = async ()=>{
|
|
62
75
|
try {
|
|
63
76
|
return await helpers.exec("pnpm -v");
|
|
@@ -121,60 +134,6 @@ const eslint = (options)=>async (files = [])=>{
|
|
|
121
134
|
throw createError("eslint", error);
|
|
122
135
|
}
|
|
123
136
|
};
|
|
124
|
-
const checkLints = eslint({
|
|
125
|
-
isFixMode: false
|
|
126
|
-
});
|
|
127
|
-
const fixLints = eslint({
|
|
128
|
-
isFixMode: true
|
|
129
|
-
});
|
|
130
|
-
const fixFormatting = async (files)=>{
|
|
131
|
-
let prettierFiles = [];
|
|
132
|
-
if (files.length === 0) {
|
|
133
|
-
prettierFiles.push(`"**/!(${PRETTIER_IGNORE_FILES.join("|")})"`);
|
|
134
|
-
} else {
|
|
135
|
-
prettierFiles = files.filter((file)=>{
|
|
136
|
-
return !PRETTIER_IGNORE_FILES.some((filename)=>file.endsWith(filename)) && // The root `README.md` file is ignored to prevent error due to its symbolic link nature when specified explicitly as a file
|
|
137
|
-
file !== "README.md";
|
|
138
|
-
});
|
|
139
|
-
if (prettierFiles.length === 0) return Promise.resolve();
|
|
140
|
-
}
|
|
141
|
-
const args = [
|
|
142
|
-
...prettierFiles
|
|
143
|
-
];
|
|
144
|
-
if (existsSync(resolveFromProjectDirectory(".gitignore"))) {
|
|
145
|
-
args.push("--ignore-path .gitignore");
|
|
146
|
-
}
|
|
147
|
-
args.push("--write");
|
|
148
|
-
args.push("--ignore-unknown");
|
|
149
|
-
args.push("--no-error-on-unmatched-pattern");
|
|
150
|
-
try {
|
|
151
|
-
return await helpers.exec(`prettier ${args.join(" ")}`);
|
|
152
|
-
} catch (error) {
|
|
153
|
-
throw createError("prettier", error);
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
|
-
const checkTypes = async ()=>{
|
|
157
|
-
try {
|
|
158
|
-
return await helpers.exec("pnpm --parallel exec tsc --noEmit");
|
|
159
|
-
} catch (error) {
|
|
160
|
-
throw createError("tsc", error);
|
|
161
|
-
}
|
|
162
|
-
};
|
|
163
|
-
const checkCommit = async ()=>{
|
|
164
|
-
try {
|
|
165
|
-
return await helpers.exec('commitlint --extends "@commitlint/config-conventional" --edit');
|
|
166
|
-
} catch (error) {
|
|
167
|
-
throw createError("commitlint", error);
|
|
168
|
-
}
|
|
169
|
-
};
|
|
170
|
-
const createError = (bin, error)=>{
|
|
171
|
-
const errorMessage = `\`${bin}\` command failed.\n${String(error)}`;
|
|
172
|
-
if (error instanceof Error) {
|
|
173
|
-
error.message = errorMessage;
|
|
174
|
-
return error;
|
|
175
|
-
}
|
|
176
|
-
return new Error(errorMessage);
|
|
177
|
-
};
|
|
178
137
|
const turbo = async (command, options = {
|
|
179
138
|
hasLiveOutput: true
|
|
180
139
|
})=>{
|
|
@@ -193,26 +152,17 @@ const changeset = async (command)=>{
|
|
|
193
152
|
throw createError("changeset", error);
|
|
194
153
|
}
|
|
195
154
|
};
|
|
196
|
-
const
|
|
197
|
-
"ts",
|
|
198
|
-
"tsx",
|
|
199
|
-
"cts",
|
|
200
|
-
"mts"
|
|
201
|
-
];
|
|
202
|
-
/**
|
|
203
|
-
* Extensions supported by ESLint.
|
|
204
|
-
*/ const ESLINT_EXTENSIONS = [
|
|
155
|
+
const ESLINT_EXTENSIONS = [
|
|
205
156
|
"js",
|
|
206
157
|
"jsx",
|
|
207
158
|
"cjs",
|
|
208
159
|
"mjs",
|
|
160
|
+
"ts",
|
|
161
|
+
"tsx",
|
|
162
|
+
"cts",
|
|
163
|
+
"mts",
|
|
209
164
|
"md",
|
|
210
|
-
"mdx"
|
|
211
|
-
...TYPESCRIPT_EXTENSIONS
|
|
212
|
-
];
|
|
213
|
-
const PRETTIER_IGNORE_FILES = [
|
|
214
|
-
"CHANGELOG.md",
|
|
215
|
-
"pnpm-lock.yaml"
|
|
165
|
+
"mdx"
|
|
216
166
|
];
|
|
217
167
|
|
|
218
168
|
const createWatchCommand = (program)=>{
|
|
@@ -266,6 +216,7 @@ const createReleaseCommand = (program)=>{
|
|
|
266
216
|
description: "Publish package(s) to the registry"
|
|
267
217
|
}).task({
|
|
268
218
|
async handler () {
|
|
219
|
+
// TODO: label instead?
|
|
269
220
|
helpers.message("New changelog entry\n");
|
|
270
221
|
await changeset("changeset");
|
|
271
222
|
},
|
|
@@ -293,12 +244,12 @@ const createInstallCommand = (program)=>{
|
|
|
293
244
|
name: "install",
|
|
294
245
|
description: "Setup initial requirements"
|
|
295
246
|
}).task({
|
|
296
|
-
label: label$4("
|
|
247
|
+
label: label$4("Install `git.pre-commit` hook"),
|
|
297
248
|
async handler () {
|
|
298
249
|
await installGitHook("pre-commit", `${getStackCommand("fix $(git status --porcelain | awk 'BEGIN{ ORS=\" \" } { print $2 }')", false)} && git add -A`);
|
|
299
250
|
}
|
|
300
251
|
}).task({
|
|
301
|
-
label: label$4("
|
|
252
|
+
label: label$4("Install `git.commit-msg` hook"),
|
|
302
253
|
async handler () {
|
|
303
254
|
await installGitHook("commit-msg", getStackCommand("check --only commit", false));
|
|
304
255
|
}
|
|
@@ -311,24 +262,59 @@ const installGitHook = async (hook, content)=>{
|
|
|
311
262
|
return chmod(filename, "0755");
|
|
312
263
|
};
|
|
313
264
|
|
|
265
|
+
const fixLinter = eslint({
|
|
266
|
+
isFixMode: true
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
const PRETTIER_IGNORE_FILES = [
|
|
270
|
+
"CHANGELOG.md",
|
|
271
|
+
"pnpm-lock.yaml"
|
|
272
|
+
];
|
|
273
|
+
const fixFormatting = async (files)=>{
|
|
274
|
+
let prettierFiles = [];
|
|
275
|
+
if (files.length === 0) {
|
|
276
|
+
prettierFiles.push(`"**/!(${PRETTIER_IGNORE_FILES.join("|")})"`);
|
|
277
|
+
} else {
|
|
278
|
+
prettierFiles = files.filter((file)=>{
|
|
279
|
+
return !PRETTIER_IGNORE_FILES.some((filename)=>file.endsWith(filename)) && // The root `README.md` file is ignored to prevent error due to its symbolic link nature when specified explicitly as a file
|
|
280
|
+
file !== "README.md";
|
|
281
|
+
});
|
|
282
|
+
if (prettierFiles.length === 0) return Promise.resolve();
|
|
283
|
+
}
|
|
284
|
+
const args = [
|
|
285
|
+
...prettierFiles
|
|
286
|
+
];
|
|
287
|
+
if (existsSync(resolveFromProjectDirectory(".gitignore"))) {
|
|
288
|
+
args.push("--ignore-path .gitignore");
|
|
289
|
+
}
|
|
290
|
+
args.push("--write");
|
|
291
|
+
args.push("--ignore-unknown");
|
|
292
|
+
args.push("--no-error-on-unmatched-pattern");
|
|
293
|
+
try {
|
|
294
|
+
return await helpers.exec(`prettier ${args.join(" ")}`);
|
|
295
|
+
} catch (error) {
|
|
296
|
+
throw createError("prettier", error);
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
|
|
314
300
|
const createFixCommand = (program)=>{
|
|
315
301
|
program.command({
|
|
316
302
|
name: "fix",
|
|
317
303
|
description: "Fix auto-fixable issues"
|
|
318
304
|
}).task({
|
|
319
|
-
label: label$3("
|
|
305
|
+
label: label$3("Prepare the project"),
|
|
320
306
|
async handler () {
|
|
321
307
|
await turbo("build", {
|
|
322
308
|
hasLiveOutput: false
|
|
323
309
|
});
|
|
324
310
|
}
|
|
325
311
|
}).task({
|
|
326
|
-
label: label$3("
|
|
312
|
+
label: label$3("Fix linter issues"),
|
|
327
313
|
async handler (_, argv) {
|
|
328
|
-
await
|
|
314
|
+
await fixLinter(argv.operands);
|
|
329
315
|
}
|
|
330
316
|
}).task({
|
|
331
|
-
label: label$3("
|
|
317
|
+
label: label$3("Fix formatting issues"),
|
|
332
318
|
async handler (_, argv) {
|
|
333
319
|
await fixFormatting(argv.operands);
|
|
334
320
|
}
|
|
@@ -379,7 +365,7 @@ const createCreateCommand = (program)=>{
|
|
|
379
365
|
}
|
|
380
366
|
}).task({
|
|
381
367
|
key: "data",
|
|
382
|
-
label: label$2("
|
|
368
|
+
label: label$2("Evaluate contextual data"),
|
|
383
369
|
async handler ({ inputDescription, inputName, inputUrl }) {
|
|
384
370
|
const nodeVersion = (await request.get("https://resolve-node.vercel.app/lts", "text")).replace("v", "");
|
|
385
371
|
const npmVersion = (await request.get("https://registry.npmjs.org/pnpm/latest", "json")).version;
|
|
@@ -399,7 +385,7 @@ const createCreateCommand = (program)=>{
|
|
|
399
385
|
}
|
|
400
386
|
}).task({
|
|
401
387
|
label ({ data }) {
|
|
402
|
-
return label$2(`
|
|
388
|
+
return label$2(`Create \`${data.projectName}\` folder`);
|
|
403
389
|
},
|
|
404
390
|
async handler ({ data }) {
|
|
405
391
|
const projectPath = resolve(process.cwd(), data.projectName);
|
|
@@ -407,18 +393,18 @@ const createCreateCommand = (program)=>{
|
|
|
407
393
|
process.chdir(projectPath);
|
|
408
394
|
}
|
|
409
395
|
}).task({
|
|
410
|
-
label: label$2("
|
|
396
|
+
label: label$2("Initialize `git`"),
|
|
411
397
|
async handler ({ data }) {
|
|
412
398
|
await helpers.exec("git init");
|
|
413
399
|
await helpers.exec(`git remote add origin ${data.projectUrl}`);
|
|
414
400
|
}
|
|
415
401
|
}).task({
|
|
416
|
-
label: label$2("
|
|
402
|
+
label: label$2("Apply template"),
|
|
417
403
|
handler ({ data, inputTemplate }) {
|
|
418
404
|
applyTemplate(inputTemplate, data);
|
|
419
405
|
}
|
|
420
406
|
}).task({
|
|
421
|
-
label: label$2("
|
|
407
|
+
label: label$2("Install dependencies"),
|
|
422
408
|
async handler ({ data }) {
|
|
423
409
|
const localDevDependencies = [
|
|
424
410
|
"quickbundle",
|
|
@@ -436,7 +422,7 @@ const createCreateCommand = (program)=>{
|
|
|
436
422
|
}
|
|
437
423
|
}).task({
|
|
438
424
|
key: "error",
|
|
439
|
-
label: label$2("
|
|
425
|
+
label: label$2("Clean up"),
|
|
440
426
|
async handler ({ data }) {
|
|
441
427
|
try {
|
|
442
428
|
// Symlink the package `README.md` file to the root project directory
|
|
@@ -518,7 +504,7 @@ const createCleanCommand = (program)=>{
|
|
|
518
504
|
description: "Clean the project"
|
|
519
505
|
}).task({
|
|
520
506
|
key: "files",
|
|
521
|
-
label: label$1("
|
|
507
|
+
label: label$1("Retrieve removable files"),
|
|
522
508
|
async handler () {
|
|
523
509
|
const cachePath = "node_modules/.cache";
|
|
524
510
|
const files = await retrieveIgnoredFiles();
|
|
@@ -562,10 +548,136 @@ const PRESERVE_FILES = [
|
|
|
562
548
|
".turbo"
|
|
563
549
|
];
|
|
564
550
|
|
|
565
|
-
const
|
|
551
|
+
const checkTypes = async ()=>{
|
|
552
|
+
try {
|
|
553
|
+
return await helpers.exec("pnpm --parallel exec tsc --noEmit");
|
|
554
|
+
} catch (error) {
|
|
555
|
+
throw createError("tsc", error);
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
const checkPackages = async ()=>{
|
|
560
|
+
const stdout = await helpers.exec("pnpm recursive ls --json");
|
|
561
|
+
const checkPackagesVersionMismatch = createPackagesVersionMismatchChecker();
|
|
562
|
+
const packages = JSON.parse(stdout).map((pkg)=>{
|
|
563
|
+
const pkgPath = join(pkg.path, "package.json");
|
|
564
|
+
assert(pkg.name, ()=>createPackageError(`\`${pkgPath}\` must have a name field.`));
|
|
565
|
+
const pkgContent = require(pkgPath);
|
|
566
|
+
const peerDependencies = pkgContent.peerDependencies ?? {};
|
|
567
|
+
const devDependencies = pkgContent.devDependencies ?? {};
|
|
568
|
+
const dependencies = pkgContent.dependencies ?? {};
|
|
569
|
+
return {
|
|
570
|
+
name: pkg.name,
|
|
571
|
+
dependencies,
|
|
572
|
+
devDependencies,
|
|
573
|
+
peerDependencies
|
|
574
|
+
};
|
|
575
|
+
});
|
|
576
|
+
for (const pkg of packages){
|
|
577
|
+
// Check version mismatches to guarantee a single copy for a given package in the monorepo (use case: prevent singleton-like issues with React contexts)
|
|
578
|
+
checkPackagesVersionMismatch(pkg);
|
|
579
|
+
// Check version range accordingly to our dependency guidelines (ie. dev dependencies must be pinned and dependencies must have caret)
|
|
580
|
+
checkPackagesVersionRange(pkg);
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
const checkPackagesVersionRange = ({ name, dependencies, devDependencies, peerDependencies })=>{
|
|
584
|
+
for (const dependencyName of Object.keys(devDependencies)){
|
|
585
|
+
const version = devDependencies[dependencyName];
|
|
586
|
+
assertVersion(version, {
|
|
587
|
+
name: dependencyName,
|
|
588
|
+
consumedBy: name
|
|
589
|
+
});
|
|
590
|
+
if (version !== "workspace:*" && !/^\d/.exec(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.`, {
|
|
591
|
+
name: dependencyName,
|
|
592
|
+
consumedBy: name
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
const isPreReleaseVersion = (version)=>/\d+\.\d+\.\d+-(alpha|beta|experimental|next|rc).*/.exec(version);
|
|
596
|
+
const hasNoCaret = (version)=>!isPreReleaseVersion(version) && !/^\^/.exec(version);
|
|
597
|
+
for (const dependencyName of Object.keys(dependencies)){
|
|
598
|
+
const version = dependencies[dependencyName];
|
|
599
|
+
assertVersion(version, {
|
|
600
|
+
name: dependencyName,
|
|
601
|
+
consumedBy: name
|
|
602
|
+
});
|
|
603
|
+
if (version !== "workspace:^" && hasNoCaret(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.`, {
|
|
604
|
+
name: dependencyName,
|
|
605
|
+
consumedBy: name
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
for (const dependencyName of Object.keys(peerDependencies)){
|
|
609
|
+
const version = peerDependencies[dependencyName];
|
|
610
|
+
assertVersion(version, {
|
|
611
|
+
name: dependencyName,
|
|
612
|
+
consumedBy: name
|
|
613
|
+
});
|
|
614
|
+
if (hasNoCaret(version)) /*
|
|
615
|
+
* Why disallowing workspace protocol as a version resolver?
|
|
616
|
+
* To reduce the update frequency needs consumer-side and guarantee on our side the minimum compatible version,
|
|
617
|
+
* the best practice should be to keeping an explicit number version which represents the lowest compatible version from an API perspective.
|
|
618
|
+
*/ 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.`, {
|
|
619
|
+
name: dependencyName,
|
|
620
|
+
consumedBy: name
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
};
|
|
624
|
+
const createPackagesVersionMismatchChecker = ()=>{
|
|
625
|
+
const monorepoDependencies = new Map();
|
|
626
|
+
const monorepoDevDependencies = new Map();
|
|
627
|
+
const lint = (pkg, type)=>{
|
|
628
|
+
const pkgName = pkg.name;
|
|
629
|
+
const isDev = type === "development";
|
|
630
|
+
const store = isDev ? monorepoDevDependencies : monorepoDependencies;
|
|
631
|
+
const dependencies = isDev ? pkg.devDependencies : pkg.dependencies;
|
|
632
|
+
for (const dependencyName of Object.keys(dependencies)){
|
|
633
|
+
const depVersion = dependencies[dependencyName];
|
|
634
|
+
if (!depVersion) continue;
|
|
635
|
+
const storedVersion = store.get(dependencyName);
|
|
636
|
+
if (!storedVersion) {
|
|
637
|
+
store.set(dependencyName, depVersion);
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
const isSameMonorepoVersion = depVersion === storedVersion;
|
|
641
|
+
if (!isSameMonorepoVersion) {
|
|
642
|
+
throw createPackageError(`Mismatched versions: received version \`${depVersion}\` 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 \`${depVersion}\`.`, {
|
|
643
|
+
name: dependencyName,
|
|
644
|
+
consumedBy: pkgName
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
return (pkg)=>{
|
|
650
|
+
lint(pkg, "development");
|
|
651
|
+
lint(pkg, "production");
|
|
652
|
+
};
|
|
653
|
+
};
|
|
654
|
+
const createPackageError = (message, context)=>{
|
|
655
|
+
return createError("stack check", !context ? message : `\`${context.name}\` consumed by \`${context.consumedBy}\` doesn't conform to our package policy.\n${message}`);
|
|
656
|
+
};
|
|
657
|
+
function assertVersion(version, { name, consumedBy }) {
|
|
658
|
+
assert(version, ()=>createPackageError(`\`${name}\` must have a valid version specified (current version equals to \`${String(version)}\`).`, {
|
|
659
|
+
name,
|
|
660
|
+
consumedBy
|
|
661
|
+
}));
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
const checkLinter = eslint({
|
|
665
|
+
isFixMode: false
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
const checkCommit = async ()=>{
|
|
669
|
+
try {
|
|
670
|
+
return await helpers.exec('commitlint --extends "@commitlint/config-conventional" --edit');
|
|
671
|
+
} catch (error) {
|
|
672
|
+
throw createError("commitlint", error);
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
const ONLY_VALUES = [
|
|
566
677
|
"commit",
|
|
567
|
-
"
|
|
568
|
-
"
|
|
678
|
+
"linter",
|
|
679
|
+
"packages",
|
|
680
|
+
"types"
|
|
569
681
|
];
|
|
570
682
|
const createCheckCommand = (program)=>{
|
|
571
683
|
program.command({
|
|
@@ -574,10 +686,10 @@ const createCheckCommand = (program)=>{
|
|
|
574
686
|
}).option({
|
|
575
687
|
key: "only",
|
|
576
688
|
name: "only",
|
|
577
|
-
description: `Run only one specified task (accepted value: ${
|
|
689
|
+
description: `Run only one specified task (accepted value: ${ONLY_VALUES.join(", ")})`,
|
|
578
690
|
defaultValue: undefined
|
|
579
691
|
}).task({
|
|
580
|
-
label: label("
|
|
692
|
+
label: label("Prepare the project"),
|
|
581
693
|
async handler () {
|
|
582
694
|
await turbo("build", {
|
|
583
695
|
hasLiveOutput: false
|
|
@@ -587,25 +699,31 @@ const createCheckCommand = (program)=>{
|
|
|
587
699
|
return only === "commit"; // No need to build if only commitlint is run
|
|
588
700
|
}
|
|
589
701
|
}).task({
|
|
590
|
-
label: label("
|
|
702
|
+
label: label("Check package guidelines"),
|
|
703
|
+
async handler () {
|
|
704
|
+
await checkPackages();
|
|
705
|
+
},
|
|
706
|
+
skip: ifOnlyDefinedAndNotEqualTo("packages")
|
|
707
|
+
}).task({
|
|
708
|
+
label: label("Check linter rules"),
|
|
591
709
|
async handler (_, argv) {
|
|
592
710
|
const filenames = argv.operands;
|
|
593
|
-
await
|
|
711
|
+
await checkLinter(filenames);
|
|
594
712
|
},
|
|
595
|
-
skip: ifOnlyDefinedAndNotEqualTo("
|
|
713
|
+
skip: ifOnlyDefinedAndNotEqualTo("linter")
|
|
596
714
|
}).task({
|
|
597
|
-
label: label("
|
|
715
|
+
label: label("Check types"),
|
|
598
716
|
async handler () {
|
|
599
717
|
await checkTypes();
|
|
600
718
|
},
|
|
601
719
|
skip (context, argv) {
|
|
602
|
-
return ifOnlyDefinedAndNotEqualTo("
|
|
720
|
+
return ifOnlyDefinedAndNotEqualTo("types")(context) || !hasDependency("typescript") || /**
|
|
603
721
|
* For now, skip type-checking if some files are passed down.
|
|
604
722
|
* @see https://github.com/microsoft/TypeScript/issues/27379
|
|
605
723
|
*/ argv.operands.length > 0;
|
|
606
724
|
}
|
|
607
725
|
}).task({
|
|
608
|
-
label: label("
|
|
726
|
+
label: label("Check commit"),
|
|
609
727
|
async handler () {
|
|
610
728
|
await checkCommit();
|
|
611
729
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adbayb/stack",
|
|
3
|
-
"version": "0.0.0-next-
|
|
3
|
+
"version": "0.0.0-next-ece28ab",
|
|
4
4
|
"description": "My opinionated JavaScript-based toolchain",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -42,7 +42,6 @@
|
|
|
42
42
|
},
|
|
43
43
|
"license": "MIT",
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@types/eslint-config-prettier": "6.11.3",
|
|
46
45
|
"@types/node": "22.8.6",
|
|
47
46
|
"quickbundle": "2.6.0"
|
|
48
47
|
},
|