@cubing/dev-config 0.6.6 → 0.7.1
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 +31 -0
- package/bin/package.json/index.js +580 -0
- package/bin/package.json/index.js.map +7 -0
- package/chunks/chunk-NX2753TH.js +28 -0
- package/chunks/chunk-NX2753TH.js.map +7 -0
- package/chunks/chunk-OZDRWEHO.js +51 -0
- package/chunks/chunk-OZDRWEHO.js.map +7 -0
- package/esbuild/es2022/index.js +5 -22
- package/esbuild/es2022/index.js.map +3 -3
- package/lib/check-allowed-imports/checkAllowedImports.d.ts +20 -0
- package/lib/check-allowed-imports/index.d.ts +1 -0
- package/lib/check-allowed-imports/index.js +110 -0
- package/lib/check-allowed-imports/index.js.map +7 -0
- package/package.json +9 -2
- package/bin/package.json.ts +0 -696
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:
|
|
@@ -0,0 +1,580 @@
|
|
|
1
|
+
#!/usr/bin/env -S bun run --
|
|
2
|
+
import {
|
|
3
|
+
__callDispose,
|
|
4
|
+
__using
|
|
5
|
+
} from "../../chunks/chunk-OZDRWEHO.js";
|
|
6
|
+
|
|
7
|
+
// src/bin/package.json/index.ts
|
|
8
|
+
import assert from "node:assert";
|
|
9
|
+
import { constants } from "node:fs/promises";
|
|
10
|
+
import { argv, exit } from "node:process";
|
|
11
|
+
import { semver } from "bun";
|
|
12
|
+
import { Path, ResolutionPrefix, stringifyIfPath } from "path-class";
|
|
13
|
+
import { PrintableShellCommand } from "printable-shell-command";
|
|
14
|
+
function categorize(v) {
|
|
15
|
+
if (Array.isArray(v)) {
|
|
16
|
+
return "array";
|
|
17
|
+
}
|
|
18
|
+
if (v === null) {
|
|
19
|
+
return "null";
|
|
20
|
+
}
|
|
21
|
+
return typeof v;
|
|
22
|
+
}
|
|
23
|
+
function* withOrderingMetadata(iter) {
|
|
24
|
+
let previous;
|
|
25
|
+
for (const value of iter) {
|
|
26
|
+
if (previous) {
|
|
27
|
+
yield previous[0];
|
|
28
|
+
previous = [
|
|
29
|
+
{ value, previous: { value: previous[0].value }, isLast: false }
|
|
30
|
+
];
|
|
31
|
+
} else {
|
|
32
|
+
previous = [{ value, previous: null, isLast: false }];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (previous) {
|
|
36
|
+
yield { ...previous[0], isLast: true };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function traverse(breadcrumbs, options) {
|
|
40
|
+
assert(breadcrumbs.length > 0);
|
|
41
|
+
let maybeValue = [packageJSON];
|
|
42
|
+
let breadcrumbString = "";
|
|
43
|
+
for (let { value: breadcrumb, isLast } of withOrderingMetadata(breadcrumbs)) {
|
|
44
|
+
if (Array.isArray(breadcrumb)) {
|
|
45
|
+
assert(breadcrumb.length === 1);
|
|
46
|
+
assert(typeof breadcrumb[0] === "string");
|
|
47
|
+
breadcrumb = breadcrumb[0];
|
|
48
|
+
breadcrumbString += `[${JSON.stringify(breadcrumb)}]`;
|
|
49
|
+
} else if (typeof breadcrumb === "string") {
|
|
50
|
+
breadcrumbString += `.${breadcrumb}`;
|
|
51
|
+
} else {
|
|
52
|
+
breadcrumbString += `[${breadcrumb}]`;
|
|
53
|
+
}
|
|
54
|
+
if (options && "set" in options && isLast) {
|
|
55
|
+
if (!maybeValue || !["array", "object"].includes(categorize(maybeValue[0]))) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
"Missing (but expected) traversal path while setting a value"
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
maybeValue[0][breadcrumb] = stringifyIfPath(options.set);
|
|
61
|
+
} else if (maybeValue && ["array", "object"].includes(categorize(maybeValue[0])) && breadcrumb in maybeValue[0]) {
|
|
62
|
+
maybeValue = [maybeValue[0][breadcrumb]];
|
|
63
|
+
} else {
|
|
64
|
+
maybeValue = null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return { breadcrumbString, maybeValue };
|
|
68
|
+
}
|
|
69
|
+
function field(breadcrumbs, type, options) {
|
|
70
|
+
const mustBePopulatedMessage = () => options?.mustBePopulatedMessage ?? "Field must be populated.";
|
|
71
|
+
const { breadcrumbString, maybeValue } = traverse(breadcrumbs);
|
|
72
|
+
if (!maybeValue) {
|
|
73
|
+
if (options?.optional) {
|
|
74
|
+
if (!options.skipPrintingSuccess) {
|
|
75
|
+
console.log(`\u2611\uFE0F ${breadcrumbString}`);
|
|
76
|
+
}
|
|
77
|
+
return;
|
|
78
|
+
} else {
|
|
79
|
+
console.log(`\u274C ${breadcrumbString} \u2014 ${mustBePopulatedMessage()}`);
|
|
80
|
+
exitCode = 1;
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const [value] = maybeValue;
|
|
85
|
+
const typeArray = Array.isArray(type) ? type : [type];
|
|
86
|
+
const category = categorize(value);
|
|
87
|
+
if (typeArray.includes(category)) {
|
|
88
|
+
for (const [failureMessage, fn] of Object.entries(
|
|
89
|
+
options?.additionalChecks ?? {}
|
|
90
|
+
)) {
|
|
91
|
+
if (!fn) {
|
|
92
|
+
console.log(`\u274C ${breadcrumbString} | ${failureMessage}`);
|
|
93
|
+
exitCode = 1;
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (!options?.skipPrintingSuccess) {
|
|
98
|
+
console.log(`\u2705 ${breadcrumbString}`);
|
|
99
|
+
}
|
|
100
|
+
} else {
|
|
101
|
+
if (category === "undefined") {
|
|
102
|
+
console.log(`\u274C ${breadcrumbString} \u2014 ${mustBePopulatedMessage()}.`);
|
|
103
|
+
} else if (type === "undefined") {
|
|
104
|
+
console.log(
|
|
105
|
+
`\u274C ${breadcrumbString} \u2014 Field is populated (but must not be).`
|
|
106
|
+
);
|
|
107
|
+
} else {
|
|
108
|
+
if (Array.isArray(type)) {
|
|
109
|
+
console.log(
|
|
110
|
+
`\u274C ${breadcrumbString} \u2014 Does not match an expected type: ${type.join(", ")}`
|
|
111
|
+
);
|
|
112
|
+
} else {
|
|
113
|
+
console.log(
|
|
114
|
+
`\u274C ${breadcrumbString} \u2014 Does not match expected type: ${type}`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
exitCode = 1;
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function mustNotBePopulated(breadcrumbs) {
|
|
123
|
+
const { breadcrumbString, maybeValue } = traverse(breadcrumbs);
|
|
124
|
+
if (maybeValue) {
|
|
125
|
+
console.log(`\u274C ${breadcrumbString} \u2014 Must not be present.`);
|
|
126
|
+
exitCode = 1;
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function checkPath(breadcrumbs, options) {
|
|
131
|
+
const { breadcrumbString, maybeValue } = traverse(breadcrumbs);
|
|
132
|
+
if (!maybeValue) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const [value] = maybeValue;
|
|
136
|
+
checks.push(
|
|
137
|
+
(async () => {
|
|
138
|
+
if (typeof value !== "string") {
|
|
139
|
+
exitCode = 1;
|
|
140
|
+
return `\u274C ${breadcrumbString} \u2014 Non-string value`;
|
|
141
|
+
}
|
|
142
|
+
if (value.includes("*")) {
|
|
143
|
+
return `\u23ED\uFE0F ${breadcrumbString} \u2014 Skipping due to glob (*) \u2014 ${value}`;
|
|
144
|
+
}
|
|
145
|
+
const unresolvedPath = new Path(value);
|
|
146
|
+
if (unresolvedPath.resolutionPrefix !== options.expectPrefix) {
|
|
147
|
+
if (unresolvedPath.resolutionPrefix === ResolutionPrefix.Absolute) {
|
|
148
|
+
exitCode = 1;
|
|
149
|
+
return `\u274C ${breadcrumbString} \u2014 Incorrect resolution prefix (${unresolvedPath.resolutionPrefix}) \u2014 ${value}`;
|
|
150
|
+
} else {
|
|
151
|
+
switch (subcommand) {
|
|
152
|
+
case "check": {
|
|
153
|
+
exitCode = 1;
|
|
154
|
+
foundFixableErrors = true;
|
|
155
|
+
return `\u274C ${breadcrumbString} \u2014 Incorrect resolution prefix (${unresolvedPath.resolutionPrefix}) \u2014 \u{1F4DD} fixable! \u2014 ${value}`;
|
|
156
|
+
}
|
|
157
|
+
case "format": {
|
|
158
|
+
console.log(
|
|
159
|
+
`\u{1F4DD} \u2014 Incorrect resolution prefix (${unresolvedPath.resolutionPrefix}) \u2014 fixing! \u2014 ${value}`
|
|
160
|
+
);
|
|
161
|
+
const newPath = options.expectPrefix === ResolutionPrefix.Bare ? unresolvedPath.asBare() : unresolvedPath.asRelative();
|
|
162
|
+
traverse(breadcrumbs, { set: newPath });
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
default:
|
|
166
|
+
throw new Error("Invalid subcommand.");
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (unresolvedPath.path.startsWith("../") || unresolvedPath.path === "..") {
|
|
171
|
+
exitCode = 1;
|
|
172
|
+
return `\u274C ${breadcrumbString} \u2014 Invalid traversal of parent path. \u2014 ${value}`;
|
|
173
|
+
}
|
|
174
|
+
const resolvedPath = Path.resolve(unresolvedPath, extractedRoot);
|
|
175
|
+
if (!await resolvedPath.existsAsFile()) {
|
|
176
|
+
exitCode = 1;
|
|
177
|
+
return `\u274C ${breadcrumbString} \u2014 Path must be present in the package. \u2014 ${value}`;
|
|
178
|
+
}
|
|
179
|
+
if (options.mustBeExecutable) {
|
|
180
|
+
if (!((await resolvedPath.stat()).mode ^ constants.X_OK)) {
|
|
181
|
+
return `\u274C ${breadcrumbString} \u2014 File at path must be executable. \u2014 ${value}`;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return `\u2705 ${breadcrumbString} \u2014 Path must be present in the package. \u2014 ${value}`;
|
|
185
|
+
})()
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
var _stack = [];
|
|
189
|
+
try {
|
|
190
|
+
var PERMITTED_LICENSES = /* @__PURE__ */ new Set([
|
|
191
|
+
"MPL-2.0",
|
|
192
|
+
"MIT",
|
|
193
|
+
"Unlicense",
|
|
194
|
+
"GPL-3.0-or-later"
|
|
195
|
+
]);
|
|
196
|
+
var subcommand = (() => {
|
|
197
|
+
const subcommand2 = argv[2];
|
|
198
|
+
if (!["check", "format"].includes(subcommand2)) {
|
|
199
|
+
console.error("Must specify subcommand: `check` or `format`");
|
|
200
|
+
exit(1);
|
|
201
|
+
}
|
|
202
|
+
return subcommand2;
|
|
203
|
+
})();
|
|
204
|
+
var exitCode = 0;
|
|
205
|
+
var foundFixableErrors = false;
|
|
206
|
+
var PACKAGE_JSON_PATH = new Path("./package.json");
|
|
207
|
+
console.log("Parsing `package.json`:");
|
|
208
|
+
var packageJSONString = await PACKAGE_JSON_PATH.readText();
|
|
209
|
+
var packageJSON = (() => {
|
|
210
|
+
try {
|
|
211
|
+
const packageJSON2 = JSON.parse(packageJSONString);
|
|
212
|
+
console.log("\u2705 `package.json` is valid JSON.");
|
|
213
|
+
return packageJSON2;
|
|
214
|
+
} catch {
|
|
215
|
+
console.log(
|
|
216
|
+
"\u274C `package.json` must be valid JSON (not JSONC or JSON5 or anything else)."
|
|
217
|
+
);
|
|
218
|
+
exit(1);
|
|
219
|
+
}
|
|
220
|
+
})();
|
|
221
|
+
console.log("Checking field order:");
|
|
222
|
+
var opinionatedFieldOrder = [
|
|
223
|
+
"name",
|
|
224
|
+
"version",
|
|
225
|
+
"homepage",
|
|
226
|
+
"description",
|
|
227
|
+
"author",
|
|
228
|
+
"license",
|
|
229
|
+
"repository",
|
|
230
|
+
"engines",
|
|
231
|
+
"os",
|
|
232
|
+
"cpu",
|
|
233
|
+
"type",
|
|
234
|
+
"main",
|
|
235
|
+
"types",
|
|
236
|
+
"module",
|
|
237
|
+
"browser",
|
|
238
|
+
"exports",
|
|
239
|
+
"bin",
|
|
240
|
+
"dependencies",
|
|
241
|
+
"devDependencies",
|
|
242
|
+
"optionalDependencies",
|
|
243
|
+
"peerDependencies",
|
|
244
|
+
"bundleDependencies",
|
|
245
|
+
"devEngines",
|
|
246
|
+
"files",
|
|
247
|
+
"scripts",
|
|
248
|
+
"keywords",
|
|
249
|
+
"@cubing/deploy",
|
|
250
|
+
"$schema"
|
|
251
|
+
];
|
|
252
|
+
var opinionatedFields = new Set(opinionatedFieldOrder);
|
|
253
|
+
var packageJSONOrder = [];
|
|
254
|
+
for (const key in packageJSON) {
|
|
255
|
+
if (opinionatedFields.has(key)) {
|
|
256
|
+
packageJSONOrder.push(key);
|
|
257
|
+
} else {
|
|
258
|
+
console.warn(`\u26A0\uFE0F [${JSON.stringify(key)}] Unexpected field.`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
var packageJSONByOpinionatedOrder = [];
|
|
262
|
+
for (const field2 of opinionatedFieldOrder) {
|
|
263
|
+
if (field2 in packageJSON) {
|
|
264
|
+
packageJSONByOpinionatedOrder.push(field2);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
try {
|
|
268
|
+
assert.deepEqual(packageJSONOrder, packageJSONByOpinionatedOrder);
|
|
269
|
+
console.log(`\u2705 Field order is good.`);
|
|
270
|
+
} catch {
|
|
271
|
+
switch (subcommand) {
|
|
272
|
+
case "check": {
|
|
273
|
+
console.log(`\u274C Found opinionated fields out of order:`);
|
|
274
|
+
console.log(`\u21A4 ${packageJSONOrder.join(", ")}`);
|
|
275
|
+
console.log("Expected:");
|
|
276
|
+
console.log(`\u21A6 ${packageJSONByOpinionatedOrder.join(", ")}`);
|
|
277
|
+
console.log(
|
|
278
|
+
"\u{1F4DD} Run with the `sort` subcommand to sort. (Additional fields will kept after the field they previously followed.)"
|
|
279
|
+
);
|
|
280
|
+
foundFixableErrors = true;
|
|
281
|
+
exitCode = 1;
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
case "format": {
|
|
285
|
+
console.log("\u{1F4DD} Invalid field order. Formatting\u2026");
|
|
286
|
+
exitCode = 1;
|
|
287
|
+
const newKeyOrder = [];
|
|
288
|
+
for (const key of packageJSONByOpinionatedOrder) {
|
|
289
|
+
newKeyOrder.push(key);
|
|
290
|
+
}
|
|
291
|
+
for (const { value: key, previous } of withOrderingMetadata(
|
|
292
|
+
Object.keys(packageJSON)
|
|
293
|
+
)) {
|
|
294
|
+
if (newKeyOrder.includes(key)) {
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
if (!previous) {
|
|
298
|
+
newKeyOrder.unshift(key);
|
|
299
|
+
} else {
|
|
300
|
+
const { value: previousKey } = previous;
|
|
301
|
+
const idx = newKeyOrder.indexOf(previousKey);
|
|
302
|
+
newKeyOrder.splice(idx + 1, 0, key);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
const newPackageJSON = {};
|
|
306
|
+
for (const key of newKeyOrder) {
|
|
307
|
+
newPackageJSON[key] = packageJSON[key];
|
|
308
|
+
}
|
|
309
|
+
packageJSON = newPackageJSON;
|
|
310
|
+
break;
|
|
311
|
+
}
|
|
312
|
+
default:
|
|
313
|
+
throw new Error("Invalid subcommand.");
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
console.log("Checking presence and type of fields:");
|
|
317
|
+
field(["name"], "string");
|
|
318
|
+
field(["version"], "string", {
|
|
319
|
+
additionalChecks: {
|
|
320
|
+
"Version must parse successfully.": (version) => semver.order(version, version) === 0
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
field(["homepage"], "string", { optional: true });
|
|
324
|
+
field(["description"], "string");
|
|
325
|
+
field(["author"], ["string", "object"]);
|
|
326
|
+
if (categorize(packageJSON["author"]) === "object") {
|
|
327
|
+
field(["author", "name"], "string");
|
|
328
|
+
field(["author", "email"], "string");
|
|
329
|
+
field(["author", "url"], "string", {
|
|
330
|
+
additionalChecks: {
|
|
331
|
+
"URL must parse.": (url) => {
|
|
332
|
+
try {
|
|
333
|
+
new URL(url);
|
|
334
|
+
return true;
|
|
335
|
+
} catch {
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
field(["license"], "string", {
|
|
343
|
+
additionalChecks: {
|
|
344
|
+
"Must contain a non-permitted license.": (license) => {
|
|
345
|
+
for (const licenseEntry of license.split(" OR ")) {
|
|
346
|
+
if (!PERMITTED_LICENSES.has(licenseEntry)) {
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
field(["repository"], "object");
|
|
355
|
+
field(["repository", "type"], "string");
|
|
356
|
+
var GIT_URL_PREFIX = "git+";
|
|
357
|
+
var GIT_URL_SUFFIX = ".";
|
|
358
|
+
field(["repository", "url"], "string", {
|
|
359
|
+
additionalChecks: {
|
|
360
|
+
[`URL must be prefixed with \`${GIT_URL_PREFIX}\`.`]: (url) => url.startsWith(GIT_URL_PREFIX),
|
|
361
|
+
[`URL must end with with \`.${GIT_URL_SUFFIX}\`.`]: (url) => url.endsWith(GIT_URL_SUFFIX),
|
|
362
|
+
"URL must parse.": (url) => {
|
|
363
|
+
try {
|
|
364
|
+
new URL(url.slice());
|
|
365
|
+
return true;
|
|
366
|
+
} catch {
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
field(["engines"], "object", { optional: true });
|
|
373
|
+
field(["os"], "array", { optional: true });
|
|
374
|
+
field(["cpu"], "array", { optional: true });
|
|
375
|
+
field(["type"], "string", {
|
|
376
|
+
additionalChecks: {
|
|
377
|
+
'Type must be `"module"`.': (type) => type === "module"
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
var mainOrTypesArePopoulated = (() => {
|
|
381
|
+
if ("main" in packageJSON || "types" in packageJSON) {
|
|
382
|
+
field(["main"], "string", {
|
|
383
|
+
mustBePopulatedMessage: 'Must be populated if "types" is populated.'
|
|
384
|
+
});
|
|
385
|
+
field(["types"], "string", {
|
|
386
|
+
mustBePopulatedMessage: 'Must be populated if "main" is populated.'
|
|
387
|
+
});
|
|
388
|
+
return true;
|
|
389
|
+
} else {
|
|
390
|
+
console.log("\u2611\uFE0F .main");
|
|
391
|
+
console.log("\u2611\uFE0F .types");
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
394
|
+
})();
|
|
395
|
+
mustNotBePopulated(["module"]);
|
|
396
|
+
mustNotBePopulated(["browser"]);
|
|
397
|
+
field(["exports"], "object", { optional: !mainOrTypesArePopoulated });
|
|
398
|
+
field(["bin"], "object", { optional: true });
|
|
399
|
+
field(["dependencies"], "object", { optional: true });
|
|
400
|
+
field(["devDependencies"], "object", { optional: true });
|
|
401
|
+
field(["optionalDependencies"], "object", { optional: true });
|
|
402
|
+
field(["peerDependencies"], "object", { optional: true });
|
|
403
|
+
field(["bundleDependencies"], "object", { optional: true });
|
|
404
|
+
field(["devEngines"], "object", { optional: true });
|
|
405
|
+
field(["files"], "array");
|
|
406
|
+
field(["scripts"], "object");
|
|
407
|
+
field(["scripts", "prepublishOnly"], "string");
|
|
408
|
+
console.log("Checking paths of binaries and exports:");
|
|
409
|
+
var tempDir = await Path.makeTempDir();
|
|
410
|
+
var tempDirDisposable = __using(_stack, {
|
|
411
|
+
[Symbol.asyncDispose]: async () => {
|
|
412
|
+
console.log("Disposing\u2026");
|
|
413
|
+
await tempDir.rm_rf();
|
|
414
|
+
}
|
|
415
|
+
}, true);
|
|
416
|
+
var extractionDir = await tempDir.join("extracted").mkdir();
|
|
417
|
+
var data = await new PrintableShellCommand("npm", [
|
|
418
|
+
"pack",
|
|
419
|
+
"--json",
|
|
420
|
+
"--ignore-scripts",
|
|
421
|
+
["--pack-destination", tempDir]
|
|
422
|
+
]).print().json();
|
|
423
|
+
var tgzPath = tempDir.join(data[0].filename);
|
|
424
|
+
await new PrintableShellCommand("tar", [
|
|
425
|
+
["-C", extractionDir],
|
|
426
|
+
["-xvzf", tgzPath]
|
|
427
|
+
]).spawn().success;
|
|
428
|
+
var extractedRoot = extractionDir.join("package/");
|
|
429
|
+
assert(await extractedRoot.existsAsDir());
|
|
430
|
+
var checks = [];
|
|
431
|
+
checkPath(["main"], { expectPrefix: ResolutionPrefix.Relative });
|
|
432
|
+
checkPath(["types"], { expectPrefix: ResolutionPrefix.Relative });
|
|
433
|
+
checkPath(["module"], { expectPrefix: ResolutionPrefix.Relative });
|
|
434
|
+
checkPath(["browser"], { expectPrefix: ResolutionPrefix.Relative });
|
|
435
|
+
var { exports } = packageJSON;
|
|
436
|
+
if (exports) {
|
|
437
|
+
for (const [subpath, value] of Object.entries(exports)) {
|
|
438
|
+
if (!value) {
|
|
439
|
+
continue;
|
|
440
|
+
} else if (typeof value === "string") {
|
|
441
|
+
checkPath(["exports", [subpath]], {
|
|
442
|
+
expectPrefix: ResolutionPrefix.Relative
|
|
443
|
+
});
|
|
444
|
+
} else if (value === null) {
|
|
445
|
+
continue;
|
|
446
|
+
} else if (Array.isArray(value)) {
|
|
447
|
+
throw new Error(
|
|
448
|
+
"\u274C .exports \u2014 Must use an object (instead of an array)."
|
|
449
|
+
);
|
|
450
|
+
} else {
|
|
451
|
+
const keys = Object.keys(value);
|
|
452
|
+
checks.push(
|
|
453
|
+
(async () => {
|
|
454
|
+
const { breadcrumbString } = traverse(["exports", [subpath]]);
|
|
455
|
+
const fixingLines = [];
|
|
456
|
+
const orderingErrorLines = [];
|
|
457
|
+
let updateKeys = false;
|
|
458
|
+
if (keys.includes("types")) {
|
|
459
|
+
if (keys[0] !== "types") {
|
|
460
|
+
switch (subcommand) {
|
|
461
|
+
case "check": {
|
|
462
|
+
orderingErrorLines.push(
|
|
463
|
+
` \u21AA "types" must be the first export if present \u2014 \u{1F4DD} fixable!`
|
|
464
|
+
);
|
|
465
|
+
break;
|
|
466
|
+
}
|
|
467
|
+
case "format": {
|
|
468
|
+
fixingLines.push(
|
|
469
|
+
` \u21AA "types" must be the first export if present \u2014 \u{1F4DD} fixing!`
|
|
470
|
+
);
|
|
471
|
+
keys.splice(keys.indexOf("types"), 1);
|
|
472
|
+
keys.splice(0, 0, "types");
|
|
473
|
+
updateKeys = true;
|
|
474
|
+
break;
|
|
475
|
+
}
|
|
476
|
+
default:
|
|
477
|
+
throw new Error("Invalid subcommand.");
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
if (keys.includes("default")) {
|
|
482
|
+
if (keys.at(-1) !== "default") {
|
|
483
|
+
switch (subcommand) {
|
|
484
|
+
case "check": {
|
|
485
|
+
orderingErrorLines.push(
|
|
486
|
+
` \u21AA "default" must be the last export if present \u2014 \u{1F4DD} fixable!`
|
|
487
|
+
);
|
|
488
|
+
break;
|
|
489
|
+
}
|
|
490
|
+
case "format": {
|
|
491
|
+
fixingLines.push(
|
|
492
|
+
` \u21AA "default" must be the last export if present \u2014 \u{1F4DD} fixing!`
|
|
493
|
+
);
|
|
494
|
+
keys.splice(keys.indexOf("default"), 1);
|
|
495
|
+
keys.push("default");
|
|
496
|
+
updateKeys = true;
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
default:
|
|
500
|
+
throw new Error("Invalid subcommand.");
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
if (updateKeys) {
|
|
505
|
+
const newConditionalExports = {};
|
|
506
|
+
for (const key of keys) {
|
|
507
|
+
newConditionalExports[key] = value[key];
|
|
508
|
+
}
|
|
509
|
+
exports[subpath] = newConditionalExports;
|
|
510
|
+
}
|
|
511
|
+
for (const key of keys) {
|
|
512
|
+
if (!["types", "import", "default"].includes(key)) {
|
|
513
|
+
orderingErrorLines.push(
|
|
514
|
+
` \u21AA Key must not be present: ${JSON.stringify(key)}`
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
if (orderingErrorLines.length > 0) {
|
|
519
|
+
exitCode = 1;
|
|
520
|
+
return [
|
|
521
|
+
`\u274C ${breadcrumbString} \u2014 Invalid keys:`,
|
|
522
|
+
...orderingErrorLines
|
|
523
|
+
].join("\n");
|
|
524
|
+
} else {
|
|
525
|
+
if (fixingLines.length > 0) {
|
|
526
|
+
return [
|
|
527
|
+
`\u2705 ${breadcrumbString} \u2014 Fixing key ordering:`,
|
|
528
|
+
...fixingLines
|
|
529
|
+
].join("\n");
|
|
530
|
+
} else {
|
|
531
|
+
return `\u2705 ${breadcrumbString} \u2014 Key set and ordering is OK.`;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
})()
|
|
535
|
+
);
|
|
536
|
+
for (const secondaryKey of keys) {
|
|
537
|
+
checkPath(["exports", [subpath], secondaryKey], {
|
|
538
|
+
expectPrefix: ResolutionPrefix.Relative
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
var { bin } = packageJSON;
|
|
545
|
+
if (bin) {
|
|
546
|
+
for (const binEntry of Object.keys(bin)) {
|
|
547
|
+
checkPath(["bin", [binEntry]], {
|
|
548
|
+
// `npm pkg fix` prefers bare paths for `bin` entries for some reason. 🤷
|
|
549
|
+
expectPrefix: ResolutionPrefix.Bare,
|
|
550
|
+
// `npm` will technically make binary entry points executable, but we want
|
|
551
|
+
// to enforce that the unpackaged path also is. This is particularly
|
|
552
|
+
// important when the package is linked.
|
|
553
|
+
mustBeExecutable: true
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
console.log((await Promise.all(checks)).join("\n"));
|
|
558
|
+
if (subcommand === "format") {
|
|
559
|
+
console.log("\u{1F4DD} Writing formatting fixes.");
|
|
560
|
+
await PACKAGE_JSON_PATH.write(`${JSON.stringify(packageJSON, null, " ")}
|
|
561
|
+
`);
|
|
562
|
+
console.log(PACKAGE_JSON_PATH.path);
|
|
563
|
+
console.log("\u{1F4DD} Running `npm pkg fix`.");
|
|
564
|
+
await new PrintableShellCommand("npm", ["pkg", "fix"]).print({ argumentLineWrapping: "inline" }).spawn().success;
|
|
565
|
+
} else if (foundFixableErrors) {
|
|
566
|
+
console.log();
|
|
567
|
+
console.log(
|
|
568
|
+
"\u{1F4DD} Found fixable errors. Run with the `format` subcommand to fix."
|
|
569
|
+
);
|
|
570
|
+
console.log();
|
|
571
|
+
}
|
|
572
|
+
await tempDirDisposable[Symbol.asyncDispose]();
|
|
573
|
+
exit(exitCode);
|
|
574
|
+
} catch (_) {
|
|
575
|
+
var _error = _, _hasError = true;
|
|
576
|
+
} finally {
|
|
577
|
+
var _promise = __callDispose(_stack, _error, _hasError);
|
|
578
|
+
_promise && await _promise;
|
|
579
|
+
}
|
|
580
|
+
//# sourceMappingURL=index.js.map
|