@cubing/dev-config 0.6.5 → 0.7.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
CHANGED
|
@@ -50,6 +50,12 @@ bun add @biomejs/biome @cubing/dev-config
|
|
|
50
50
|
bun x @biomejs/biome check
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
+
Add to a project using [`repo`](https://github.com/lgarron/repo):
|
|
54
|
+
|
|
55
|
+
```shell
|
|
56
|
+
repo boilerplate biome add # Or invoke using `npx repo …` / `bun x repo …`
|
|
57
|
+
```
|
|
58
|
+
|
|
53
59
|
### TypeScript
|
|
54
60
|
|
|
55
61
|
#### Check types
|
|
@@ -72,6 +78,12 @@ bun add --dev typescript @cubing/dev-config
|
|
|
72
78
|
bun x tsc --noEmit --project .
|
|
73
79
|
```
|
|
74
80
|
|
|
81
|
+
Add to a project using [`repo`](https://github.com/lgarron/repo):
|
|
82
|
+
|
|
83
|
+
```shell
|
|
84
|
+
repo boilerplate tsconfig add # Or invoke using `npx repo …` / `bun x repo …`
|
|
85
|
+
```
|
|
86
|
+
|
|
75
87
|
#### Build types
|
|
76
88
|
|
|
77
89
|
```jsonc
|
|
@@ -95,6 +107,12 @@ bun add --dev typescript @cubing/dev-config
|
|
|
95
107
|
bun x tsc --project .
|
|
96
108
|
```
|
|
97
109
|
|
|
110
|
+
Add to a project using [`repo`](https://github.com/lgarron/repo):
|
|
111
|
+
|
|
112
|
+
```shell
|
|
113
|
+
repo boilerplate tsconfig add # Or invoke using `npx repo …` / `bun x repo …`
|
|
114
|
+
```
|
|
115
|
+
|
|
98
116
|
#### No DOM
|
|
99
117
|
|
|
100
118
|
Use the `no-dom` variant instead:
|
|
@@ -106,6 +124,12 @@ Use the `no-dom` variant instead:
|
|
|
106
124
|
}
|
|
107
125
|
```
|
|
108
126
|
|
|
127
|
+
Add to a project using [`repo`](https://github.com/lgarron/repo):
|
|
128
|
+
|
|
129
|
+
```shell
|
|
130
|
+
repo boilerplate tsconfig add --no-dom # Or invoke using `npx repo …` / `bun x repo …`
|
|
131
|
+
```
|
|
132
|
+
|
|
109
133
|
### `es2024`
|
|
110
134
|
|
|
111
135
|
The following are also available:
|
|
@@ -115,6 +139,13 @@ The following are also available:
|
|
|
115
139
|
|
|
116
140
|
This is useful for features like `Promise.withResolvers(…)`.
|
|
117
141
|
|
|
142
|
+
Add to a project using [`repo`](https://github.com/lgarron/repo):
|
|
143
|
+
|
|
144
|
+
```shell
|
|
145
|
+
repo boilerplate tsconfig add --module es2022 # Or invoke using `npx repo …` / `bun x repo …`
|
|
146
|
+
repo boilerplate tsconfig add --no-dom --module es2022 # Or invoke using `npx repo …` / `bun x repo …`
|
|
147
|
+
```
|
|
148
|
+
|
|
118
149
|
### Checking `package.json` for a project
|
|
119
150
|
|
|
120
151
|
Run as follows:
|
package/bin/package.json.ts
CHANGED
|
@@ -41,7 +41,6 @@ It also assumes certain conventions about package structure and maintenance.
|
|
|
41
41
|
|
|
42
42
|
*/
|
|
43
43
|
|
|
44
|
-
// TODO: support "format" command that corrects some things.
|
|
45
44
|
// TODO: Schema validation.
|
|
46
45
|
|
|
47
46
|
console.log("Parsing `package.json`:");
|
|
@@ -332,7 +331,7 @@ console.log("Checking presence and type of fields:");
|
|
|
332
331
|
field(["name"], "string");
|
|
333
332
|
field(["version"], "string", {
|
|
334
333
|
additionalChecks: {
|
|
335
|
-
"Version
|
|
334
|
+
"Version must parse successfully.": (version: string) =>
|
|
336
335
|
semver.order(version, version) === 0,
|
|
337
336
|
},
|
|
338
337
|
});
|
|
@@ -398,20 +397,24 @@ field(["type"], "string", {
|
|
|
398
397
|
'Type must be `"module"`.': (type: string) => type === "module",
|
|
399
398
|
},
|
|
400
399
|
});
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
400
|
+
const mainOrTypesArePopoulated = (() => {
|
|
401
|
+
if ("main" in packageJSON || "types" in packageJSON) {
|
|
402
|
+
field(["main"], "string", {
|
|
403
|
+
mustBePopulatedMessage: 'Must be populated if "types" is populated.',
|
|
404
|
+
});
|
|
405
|
+
field(["types"], "string", {
|
|
406
|
+
mustBePopulatedMessage: 'Must be populated if "main" is populated.',
|
|
407
|
+
});
|
|
408
|
+
return true;
|
|
409
|
+
} else {
|
|
410
|
+
console.log("☑️ .main");
|
|
411
|
+
console.log("☑️ .types");
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
})();
|
|
412
415
|
mustNotBePopulated(["module"]);
|
|
413
416
|
mustNotBePopulated(["browser"]);
|
|
414
|
-
field(["exports"], "object");
|
|
417
|
+
field(["exports"], "object", { optional: !mainOrTypesArePopoulated });
|
|
415
418
|
field(["bin"], "object", { optional: true });
|
|
416
419
|
field(["dependencies"], "object", { optional: true });
|
|
417
420
|
field(["devDependencies"], "object", { optional: true });
|
|
@@ -429,8 +432,11 @@ field(["scripts", "prepublishOnly"], "string");
|
|
|
429
432
|
console.log("Checking paths of binaries and exports:");
|
|
430
433
|
|
|
431
434
|
const tempDir = await Path.makeTempDir();
|
|
432
|
-
await using
|
|
433
|
-
[Symbol.asyncDispose]: () =>
|
|
435
|
+
await using tempDirDisposable = {
|
|
436
|
+
[Symbol.asyncDispose]: async () => {
|
|
437
|
+
console.log("Disposing…");
|
|
438
|
+
await tempDir.rm_rf();
|
|
439
|
+
},
|
|
434
440
|
};
|
|
435
441
|
const extractionDir = await tempDir.join("extracted").mkdir();
|
|
436
442
|
// TODO: is there a 100% reliable way to test against paths that *will* be packed?
|
|
@@ -514,17 +520,17 @@ function checkPath(
|
|
|
514
520
|
// TODO: allow folders (with a required trailing slash)?
|
|
515
521
|
if (!(await resolvedPath.existsAsFile())) {
|
|
516
522
|
exitCode = 1;
|
|
517
|
-
return `❌ ${breadcrumbString} — Path
|
|
523
|
+
return `❌ ${breadcrumbString} — Path must be present in the package. — ${value}`;
|
|
518
524
|
}
|
|
519
525
|
if (options.mustBeExecutable) {
|
|
520
526
|
if (!((await resolvedPath.stat()).mode ^ constants.X_OK)) {
|
|
521
527
|
// This is not considered fixable because the binary may be the output
|
|
522
528
|
// of a build process. In that case, the build process is responsible
|
|
523
529
|
// for marking it as executable.
|
|
524
|
-
return `❌ ${breadcrumbString} — File at path must be executable — ${value}`;
|
|
530
|
+
return `❌ ${breadcrumbString} — File at path must be executable. — ${value}`;
|
|
525
531
|
}
|
|
526
532
|
}
|
|
527
|
-
return `✅ ${breadcrumbString} —
|
|
533
|
+
return `✅ ${breadcrumbString} — Path must be present in the package. — ${value}`;
|
|
528
534
|
})(),
|
|
529
535
|
);
|
|
530
536
|
}
|
|
@@ -642,7 +648,7 @@ if (exports) {
|
|
|
642
648
|
...fixingLines,
|
|
643
649
|
].join("\n");
|
|
644
650
|
} else {
|
|
645
|
-
return `✅ ${breadcrumbString} — Key set and ordering is
|
|
651
|
+
return `✅ ${breadcrumbString} — Key set and ordering is OK.`;
|
|
646
652
|
}
|
|
647
653
|
}
|
|
648
654
|
})(),
|
|
@@ -689,4 +695,6 @@ if (subcommand === "format") {
|
|
|
689
695
|
console.log();
|
|
690
696
|
}
|
|
691
697
|
|
|
698
|
+
await tempDirDisposable[Symbol.asyncDispose]();
|
|
699
|
+
|
|
692
700
|
exit(exitCode);
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type BuildOptions,
|
|
3
|
+
build,
|
|
4
|
+
type ImportKind,
|
|
5
|
+
type Metafile,
|
|
6
|
+
type Plugin,
|
|
7
|
+
} from "esbuild";
|
|
8
|
+
import { es2022Lib } from "../../src/esbuild/es2022";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Note:
|
|
12
|
+
* - A file may be matched by any parent path scope key.
|
|
13
|
+
* - Files in a given scope key are allowed to import any other within the same scope.
|
|
14
|
+
*/
|
|
15
|
+
export type AllowedImports = {
|
|
16
|
+
[scope: string]: { static?: string[]; dynamic?: string[] };
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const plugin = {
|
|
20
|
+
name: "mark-bare-imports-as-external",
|
|
21
|
+
setup(build) {
|
|
22
|
+
const filter = /^[^./]|^\.[^./]|^\.\.[^/]/; // Must not start with "/" or "./" or "../"
|
|
23
|
+
build.onResolve({ filter }, (args) => ({
|
|
24
|
+
path: args.path,
|
|
25
|
+
external: true,
|
|
26
|
+
}));
|
|
27
|
+
},
|
|
28
|
+
} satisfies Plugin;
|
|
29
|
+
|
|
30
|
+
export async function checkAllowedImports(
|
|
31
|
+
groups: {
|
|
32
|
+
[description: string]: {
|
|
33
|
+
entryPoints: string[];
|
|
34
|
+
allowedImports: AllowedImports;
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
options?: { overrideEsbuildOptions: BuildOptions },
|
|
38
|
+
): Promise<void> {
|
|
39
|
+
let failure = false;
|
|
40
|
+
|
|
41
|
+
console.log("+ means a new file");
|
|
42
|
+
console.log("- means a valid import for that file");
|
|
43
|
+
|
|
44
|
+
for (const [description, { entryPoints, allowedImports }] of Object.entries(
|
|
45
|
+
groups,
|
|
46
|
+
)) {
|
|
47
|
+
console.log(`# ${description}`);
|
|
48
|
+
// From https://github.com/evanw/esbuild/issues/619#issuecomment-1504100390
|
|
49
|
+
|
|
50
|
+
const { metafile } = await build({
|
|
51
|
+
...es2022Lib(),
|
|
52
|
+
entryPoints,
|
|
53
|
+
plugins: [plugin],
|
|
54
|
+
...options?.overrideEsbuildOptions,
|
|
55
|
+
write: false,
|
|
56
|
+
metafile: true,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Starts with the path and then keeps chopping off from the right.
|
|
60
|
+
function* pathPrefixes(path: string) {
|
|
61
|
+
const pathParts = path.split("/");
|
|
62
|
+
for (let n = pathParts.length; n > 0; n--) {
|
|
63
|
+
yield pathParts.slice(0, n).join("/");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function matchingPathPrefix(matchPrefixes: string[], path: string) {
|
|
68
|
+
for (const pathPrefix of pathPrefixes(path)) {
|
|
69
|
+
if (matchPrefixes.includes(pathPrefix)) {
|
|
70
|
+
return pathPrefix;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const importKindMap: Partial<Record<ImportKind, "static" | "dynamic">> = {
|
|
77
|
+
"import-statement": "static",
|
|
78
|
+
"dynamic-import": "dynamic",
|
|
79
|
+
} as const;
|
|
80
|
+
|
|
81
|
+
function checkImport(
|
|
82
|
+
sourcePath: string,
|
|
83
|
+
importInfo: {
|
|
84
|
+
path: string;
|
|
85
|
+
kind: ImportKind;
|
|
86
|
+
external?: boolean;
|
|
87
|
+
original?: string;
|
|
88
|
+
},
|
|
89
|
+
allowedImports: AllowedImports,
|
|
90
|
+
) {
|
|
91
|
+
const importKind = importKindMap[importInfo.kind];
|
|
92
|
+
if (!importKind) {
|
|
93
|
+
throw new Error("Unexpected import kind!");
|
|
94
|
+
}
|
|
95
|
+
for (const sourcePathPrefix of pathPrefixes(sourcePath)) {
|
|
96
|
+
const matchingSourcePathPrefix = matchingPathPrefix(
|
|
97
|
+
Object.keys(allowedImports),
|
|
98
|
+
sourcePathPrefix,
|
|
99
|
+
);
|
|
100
|
+
if (matchingSourcePathPrefix) {
|
|
101
|
+
const allowedImportsForKind =
|
|
102
|
+
allowedImports[matchingSourcePathPrefix][importKind];
|
|
103
|
+
if (
|
|
104
|
+
typeof allowedImportsForKind !== "undefined" &&
|
|
105
|
+
!Array.isArray(allowedImportsForKind)
|
|
106
|
+
) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
`Expected a string list for ${importKind} imports under the scope "${matchingSourcePathPrefix}"`,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
if (
|
|
112
|
+
matchingPathPrefix(
|
|
113
|
+
[
|
|
114
|
+
matchingSourcePathPrefix, // allow importing from any source group to itself.
|
|
115
|
+
...(allowedImportsForKind ?? []),
|
|
116
|
+
],
|
|
117
|
+
importInfo.path,
|
|
118
|
+
)
|
|
119
|
+
) {
|
|
120
|
+
process.stdout.write("-");
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
failure = true;
|
|
126
|
+
console.error(`\n❌ File has disallowed ${importKind} import:`);
|
|
127
|
+
console.error(`From file: ${sourcePath}`);
|
|
128
|
+
console.error(`Importing: ${importInfo.path}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function checkImports(
|
|
132
|
+
metafile: Metafile,
|
|
133
|
+
allowedImports: AllowedImports,
|
|
134
|
+
) {
|
|
135
|
+
for (const [filePath, importInfoList] of Object.entries(
|
|
136
|
+
metafile.inputs,
|
|
137
|
+
)) {
|
|
138
|
+
process.stdout.write("+");
|
|
139
|
+
for (const importInfo of importInfoList.imports) {
|
|
140
|
+
checkImport(filePath, importInfo, allowedImports);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
console.log();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
await checkImports(metafile, allowedImports);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (failure) {
|
|
150
|
+
throw new Error("Failure");
|
|
151
|
+
}
|
|
152
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cubing/dev-config",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Common dev configs for projects.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Lucas Garron",
|
|
@@ -17,6 +17,9 @@
|
|
|
17
17
|
"./esbuild/es2022": {
|
|
18
18
|
"types": "./esbuild/es2022/index.d.ts",
|
|
19
19
|
"import": "./esbuild/es2022/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./check-allowed-imports": {
|
|
22
|
+
"default": "./lib/check-allowed-imports/index.ts"
|
|
20
23
|
}
|
|
21
24
|
},
|
|
22
25
|
"bin": {
|
|
@@ -37,6 +40,7 @@
|
|
|
37
40
|
"files": [
|
|
38
41
|
"./biome/",
|
|
39
42
|
"./esbuild/",
|
|
43
|
+
"./lib",
|
|
40
44
|
"./ts/"
|
|
41
45
|
],
|
|
42
46
|
"scripts": {
|