@cubing/dev-config 0.6.6 → 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
|
});
|
|
@@ -433,8 +432,11 @@ field(["scripts", "prepublishOnly"], "string");
|
|
|
433
432
|
console.log("Checking paths of binaries and exports:");
|
|
434
433
|
|
|
435
434
|
const tempDir = await Path.makeTempDir();
|
|
436
|
-
await using
|
|
437
|
-
[Symbol.asyncDispose]: () =>
|
|
435
|
+
await using tempDirDisposable = {
|
|
436
|
+
[Symbol.asyncDispose]: async () => {
|
|
437
|
+
console.log("Disposing…");
|
|
438
|
+
await tempDir.rm_rf();
|
|
439
|
+
},
|
|
438
440
|
};
|
|
439
441
|
const extractionDir = await tempDir.join("extracted").mkdir();
|
|
440
442
|
// TODO: is there a 100% reliable way to test against paths that *will* be packed?
|
|
@@ -518,17 +520,17 @@ function checkPath(
|
|
|
518
520
|
// TODO: allow folders (with a required trailing slash)?
|
|
519
521
|
if (!(await resolvedPath.existsAsFile())) {
|
|
520
522
|
exitCode = 1;
|
|
521
|
-
return `❌ ${breadcrumbString} — Path
|
|
523
|
+
return `❌ ${breadcrumbString} — Path must be present in the package. — ${value}`;
|
|
522
524
|
}
|
|
523
525
|
if (options.mustBeExecutable) {
|
|
524
526
|
if (!((await resolvedPath.stat()).mode ^ constants.X_OK)) {
|
|
525
527
|
// This is not considered fixable because the binary may be the output
|
|
526
528
|
// of a build process. In that case, the build process is responsible
|
|
527
529
|
// for marking it as executable.
|
|
528
|
-
return `❌ ${breadcrumbString} — File at path must be executable — ${value}`;
|
|
530
|
+
return `❌ ${breadcrumbString} — File at path must be executable. — ${value}`;
|
|
529
531
|
}
|
|
530
532
|
}
|
|
531
|
-
return `✅ ${breadcrumbString} —
|
|
533
|
+
return `✅ ${breadcrumbString} — Path must be present in the package. — ${value}`;
|
|
532
534
|
})(),
|
|
533
535
|
);
|
|
534
536
|
}
|
|
@@ -693,4 +695,6 @@ if (subcommand === "format") {
|
|
|
693
695
|
console.log();
|
|
694
696
|
}
|
|
695
697
|
|
|
698
|
+
await tempDirDisposable[Symbol.asyncDispose]();
|
|
699
|
+
|
|
696
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": {
|