@codefast/cli 0.8.1 → 0.9.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/CHANGELOG.md +568 -0
- package/LICENSE +1 -1
- package/README.md +35 -9
- package/dist/audit/cli-schema.js +11 -0
- package/dist/audit/command.js +43 -3
- package/dist/audit/domain/comment-dividers.js +48 -20
- package/dist/audit/domain/display-names.js +71 -0
- package/dist/audit/output.js +41 -0
- package/dist/audit/prepare.js +31 -0
- package/dist/audit/run-comments.js +22 -18
- package/dist/audit/run-display-names.js +60 -0
- package/dist/core/config/schema.js +10 -0
- package/dist/core/workspace/source-walk.js +13 -2
- package/dist/pack-slim/command.js +3 -2
- package/dist/pack-slim/domain/transform.js +141 -15
- package/dist/pack-slim/output.js +10 -1
- package/dist/pack-slim/sync.js +10 -4
- package/package.json +5 -33
|
@@ -1,8 +1,23 @@
|
|
|
1
|
-
/** Pure transforms that
|
|
1
|
+
/** Pure transforms that slim a package's manifest and dist for publish. */
|
|
2
2
|
/**
|
|
3
3
|
* The emitted-file extensions that may carry a `sourceMappingURL` directive.
|
|
4
4
|
*/
|
|
5
5
|
const MAP_ANNOTATED_EXTENSIONS = new Set([".js", ".mjs", ".cjs", ".d.ts", ".d.mts", ".d.cts"]);
|
|
6
|
+
/**
|
|
7
|
+
* The script names npm and pnpm run on their own, on a consumer's install or on this package's publish.
|
|
8
|
+
*/
|
|
9
|
+
const LIFECYCLE_SCRIPT_STEMS = new Set([
|
|
10
|
+
"install",
|
|
11
|
+
"prepare",
|
|
12
|
+
"prepublish",
|
|
13
|
+
"prepublishOnly",
|
|
14
|
+
"pack",
|
|
15
|
+
"publish",
|
|
16
|
+
]);
|
|
17
|
+
/**
|
|
18
|
+
* The characters that make a `files` entry a glob rather than a path.
|
|
19
|
+
*/
|
|
20
|
+
const GLOB_CHARACTERS = /[*?[\]{}]/;
|
|
6
21
|
/**
|
|
7
22
|
* Whether a dist entry is a source-map sidecar (`*.map`).
|
|
8
23
|
*
|
|
@@ -25,11 +40,26 @@ export function isMapAnnotatedFile(fileName) {
|
|
|
25
40
|
return false;
|
|
26
41
|
}
|
|
27
42
|
/**
|
|
28
|
-
*
|
|
43
|
+
* Whether a script name is an install or publish lifecycle hook, with or without its `pre`/`post` prefix.
|
|
44
|
+
*
|
|
45
|
+
* @remarks Publish hooks stay because `pnpm publish` runs them right after pack-slim; install hooks stay because a
|
|
46
|
+
* consumer's package manager runs them.
|
|
47
|
+
*
|
|
48
|
+
* @since 0.9.0
|
|
49
|
+
*/
|
|
50
|
+
export function isLifecycleScript(name) {
|
|
51
|
+
if (LIFECYCLE_SCRIPT_STEMS.has(name)) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
const stem = name.replace(/^(?:pre|post)/, "");
|
|
55
|
+
return stem !== name && LIFECYCLE_SCRIPT_STEMS.has(stem);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Produces a publish manifest carrying only what a consumer's `tsc` and Node read.
|
|
29
59
|
*
|
|
30
|
-
* @remarks Operates on a structural clone, so the caller's manifest is left intact. A consumer never enables
|
|
31
|
-
*
|
|
32
|
-
*
|
|
60
|
+
* @remarks Operates on a structural clone, so the caller's manifest is left intact. A consumer never enables `source`
|
|
61
|
+
* and resolves `#/` through `types`/`default` to `dist`, so dropping the source lane, the `imports` left pointing
|
|
62
|
+
* outside `files`, the scripts that are not lifecycle hooks, and `devDependencies` leaves the published surface whole.
|
|
33
63
|
*
|
|
34
64
|
* @since 0.8.1
|
|
35
65
|
*/
|
|
@@ -38,10 +68,21 @@ export function slimPublishManifest(manifest) {
|
|
|
38
68
|
const filesSrcRemoved = removeSrcFromFiles(draft);
|
|
39
69
|
const exportsSourceRemoved = deleteSourceConditions(draft.exports);
|
|
40
70
|
const importsSourceRemoved = deleteSourceConditions(draft.imports);
|
|
41
|
-
const
|
|
71
|
+
const importsUnshippedRemoved = deleteUnshippedImports(draft);
|
|
72
|
+
const scriptsRemoved = deleteDevOnlyScripts(draft);
|
|
73
|
+
const devDependenciesRemoved = deleteDevDependencies(draft);
|
|
74
|
+
const changed = JSON.stringify(draft) !== JSON.stringify(manifest);
|
|
42
75
|
return {
|
|
43
76
|
manifest: draft,
|
|
44
|
-
report: {
|
|
77
|
+
report: {
|
|
78
|
+
filesSrcRemoved,
|
|
79
|
+
exportsSourceRemoved,
|
|
80
|
+
importsSourceRemoved,
|
|
81
|
+
importsUnshippedRemoved,
|
|
82
|
+
scriptsRemoved,
|
|
83
|
+
devDependenciesRemoved,
|
|
84
|
+
changed,
|
|
85
|
+
},
|
|
45
86
|
};
|
|
46
87
|
}
|
|
47
88
|
/**
|
|
@@ -69,8 +110,8 @@ function removeSrcFromFiles(manifest) {
|
|
|
69
110
|
manifest.files = next;
|
|
70
111
|
return true;
|
|
71
112
|
}
|
|
72
|
-
// Walks a conditions tree deleting every `source` key. A subpath key always starts with ".", so only real
|
|
73
|
-
//
|
|
113
|
+
// Walks a conditions tree deleting every `source` key. A subpath key always starts with ".", so only real condition
|
|
114
|
+
// objects match; an `imports` entry this leaves empty is what the unshipped pass removes.
|
|
74
115
|
function deleteSourceConditions(node) {
|
|
75
116
|
if (Array.isArray(node)) {
|
|
76
117
|
let removed = 0;
|
|
@@ -79,17 +120,102 @@ function deleteSourceConditions(node) {
|
|
|
79
120
|
}
|
|
80
121
|
return removed;
|
|
81
122
|
}
|
|
82
|
-
if (node
|
|
123
|
+
if (!isRecord(node)) {
|
|
83
124
|
return 0;
|
|
84
125
|
}
|
|
85
|
-
const record = node;
|
|
86
126
|
let removed = 0;
|
|
87
|
-
if ("source" in
|
|
88
|
-
delete
|
|
127
|
+
if ("source" in node) {
|
|
128
|
+
delete node.source;
|
|
89
129
|
removed += 1;
|
|
90
130
|
}
|
|
91
|
-
for (const key of Object.keys(
|
|
92
|
-
removed += deleteSourceConditions(
|
|
131
|
+
for (const key of Object.keys(node)) {
|
|
132
|
+
removed += deleteSourceConditions(node[key]);
|
|
93
133
|
}
|
|
94
134
|
return removed;
|
|
135
|
+
}
|
|
136
|
+
// Keeps an `imports` entry only while one of its targets lands inside a path `files` ships. `files` is read after the
|
|
137
|
+
// `src` drop, so a lane still pointing at `./src/*` counts as unshipped; with no `files` at all npm ships everything.
|
|
138
|
+
function deleteUnshippedImports(manifest) {
|
|
139
|
+
const imports = manifest.imports;
|
|
140
|
+
const files = manifest.files;
|
|
141
|
+
if (!isRecord(imports) || !Array.isArray(files)) {
|
|
142
|
+
return 0;
|
|
143
|
+
}
|
|
144
|
+
const shippedEntries = files.filter((entry) => typeof entry === "string");
|
|
145
|
+
let removed = 0;
|
|
146
|
+
for (const specifier of Object.keys(imports)) {
|
|
147
|
+
const targets = collectTargets(imports[specifier]);
|
|
148
|
+
if (targets.some((target) => isShippedTarget(target, shippedEntries))) {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
delete imports[specifier];
|
|
152
|
+
removed += 1;
|
|
153
|
+
}
|
|
154
|
+
if (Object.keys(imports).length === 0) {
|
|
155
|
+
delete manifest.imports;
|
|
156
|
+
}
|
|
157
|
+
return removed;
|
|
158
|
+
}
|
|
159
|
+
function collectTargets(node, into = []) {
|
|
160
|
+
if (typeof node === "string") {
|
|
161
|
+
into.push(node);
|
|
162
|
+
}
|
|
163
|
+
else if (Array.isArray(node)) {
|
|
164
|
+
for (const item of node) {
|
|
165
|
+
collectTargets(item, into);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
else if (isRecord(node)) {
|
|
169
|
+
for (const value of Object.values(node)) {
|
|
170
|
+
collectTargets(value, into);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return into;
|
|
174
|
+
}
|
|
175
|
+
// A glob entry is left to npm, so any target counts as shipped under it; a negated entry ships nothing.
|
|
176
|
+
function isShippedTarget(target, shippedEntries) {
|
|
177
|
+
const relativeTarget = stripDotSlash(target);
|
|
178
|
+
return shippedEntries.some((entry) => {
|
|
179
|
+
if (entry.startsWith("!")) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
if (GLOB_CHARACTERS.test(entry)) {
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
const shipped = stripDotSlash(entry).replace(/\/+$/, "");
|
|
186
|
+
return relativeTarget === shipped || relativeTarget.startsWith(`${shipped}/`);
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
function stripDotSlash(filePath) {
|
|
190
|
+
return filePath.startsWith("./") ? filePath.slice(2) : filePath;
|
|
191
|
+
}
|
|
192
|
+
// Drops every script that is not a lifecycle hook, and the field itself once nothing is left in it.
|
|
193
|
+
function deleteDevOnlyScripts(manifest) {
|
|
194
|
+
const scripts = manifest.scripts;
|
|
195
|
+
if (!isRecord(scripts)) {
|
|
196
|
+
return 0;
|
|
197
|
+
}
|
|
198
|
+
let removed = 0;
|
|
199
|
+
for (const name of Object.keys(scripts)) {
|
|
200
|
+
if (isLifecycleScript(name)) {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
delete scripts[name];
|
|
204
|
+
removed += 1;
|
|
205
|
+
}
|
|
206
|
+
if (Object.keys(scripts).length === 0) {
|
|
207
|
+
delete manifest.scripts;
|
|
208
|
+
}
|
|
209
|
+
return removed;
|
|
210
|
+
}
|
|
211
|
+
function deleteDevDependencies(manifest) {
|
|
212
|
+
const devDependencies = manifest.devDependencies;
|
|
213
|
+
if (!isRecord(devDependencies)) {
|
|
214
|
+
return 0;
|
|
215
|
+
}
|
|
216
|
+
delete manifest.devDependencies;
|
|
217
|
+
return Object.keys(devDependencies).length;
|
|
218
|
+
}
|
|
219
|
+
function isRecord(value) {
|
|
220
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
95
221
|
}
|
package/dist/pack-slim/output.js
CHANGED
|
@@ -10,7 +10,7 @@ export class PackSlimProgressPresenter {
|
|
|
10
10
|
this.dryRun = options.dryRun;
|
|
11
11
|
}
|
|
12
12
|
onBanner() {
|
|
13
|
-
logger.out(this.dryRun ? "pack-slim (dry run) — no files will change" : "pack-slim —
|
|
13
|
+
logger.out(this.dryRun ? "pack-slim (dry run) — no files will change" : "pack-slim — slimming publish artifacts");
|
|
14
14
|
}
|
|
15
15
|
onPackageComplete(stats, ordinal, total) {
|
|
16
16
|
const position = `[${ordinal}/${total}]`;
|
|
@@ -37,6 +37,15 @@ export class PackSlimProgressPresenter {
|
|
|
37
37
|
if (stats.importsSourceRemoved > 0) {
|
|
38
38
|
parts.push(`imports:source×${stats.importsSourceRemoved}`);
|
|
39
39
|
}
|
|
40
|
+
if (stats.importsUnshippedRemoved > 0) {
|
|
41
|
+
parts.push(`imports:unshipped×${stats.importsUnshippedRemoved}`);
|
|
42
|
+
}
|
|
43
|
+
if (stats.scriptsRemoved > 0) {
|
|
44
|
+
parts.push(`scripts×${stats.scriptsRemoved}`);
|
|
45
|
+
}
|
|
46
|
+
if (stats.devDependenciesRemoved > 0) {
|
|
47
|
+
parts.push(`devDependencies×${stats.devDependenciesRemoved}`);
|
|
48
|
+
}
|
|
40
49
|
if (stats.mapFilesDeleted > 0) {
|
|
41
50
|
parts.push(`maps×${stats.mapFilesDeleted}`);
|
|
42
51
|
}
|
package/dist/pack-slim/sync.js
CHANGED
|
@@ -5,11 +5,11 @@ import { listWorkspacePackageDirectories } from "#/core/workspace/resolver";
|
|
|
5
5
|
import { DIST_DIR, PACKAGE_JSON } from "#/mirror/domain/constants";
|
|
6
6
|
import { isMapAnnotatedFile, isSourceMapFile, slimPublishManifest, stripSourceMappingComment, } from "#/pack-slim/domain/transform";
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
8
|
+
* Slims every published package's manifest and build output for publish and returns the aggregate stats.
|
|
9
9
|
*
|
|
10
|
-
* @remarks Meant to run on an ephemeral CI checkout right before `changeset publish`: it rewrites `package.json
|
|
11
|
-
* `dist` source maps, and clears their now-dangling `sourceMappingURL` directives, so the
|
|
12
|
-
* runtime and types. Private packages are skipped because `changeset publish` never publishes them.
|
|
10
|
+
* @remarks Meant to run on an ephemeral CI checkout right before `changeset publish`: it rewrites `package.json` down to
|
|
11
|
+
* what a consumer reads, drops `dist` source maps, and clears their now-dangling `sourceMappingURL` directives, so the
|
|
12
|
+
* tarball ships only `dist` runtime and types. Private packages are skipped because `changeset publish` never publishes them.
|
|
13
13
|
*
|
|
14
14
|
* @since 0.8.1
|
|
15
15
|
*/
|
|
@@ -66,6 +66,9 @@ async function slimWorkspacePackage(fs, packageDir, write) {
|
|
|
66
66
|
filesSrcRemoved: false,
|
|
67
67
|
exportsSourceRemoved: 0,
|
|
68
68
|
importsSourceRemoved: 0,
|
|
69
|
+
importsUnshippedRemoved: 0,
|
|
70
|
+
scriptsRemoved: 0,
|
|
71
|
+
devDependenciesRemoved: 0,
|
|
69
72
|
mapFilesDeleted: 0,
|
|
70
73
|
sourceCommentsStripped: 0,
|
|
71
74
|
changed: false,
|
|
@@ -94,6 +97,9 @@ async function slimWorkspacePackage(fs, packageDir, write) {
|
|
|
94
97
|
pkgStats.filesSrcRemoved = report.filesSrcRemoved;
|
|
95
98
|
pkgStats.exportsSourceRemoved = report.exportsSourceRemoved;
|
|
96
99
|
pkgStats.importsSourceRemoved = report.importsSourceRemoved;
|
|
100
|
+
pkgStats.importsUnshippedRemoved = report.importsUnshippedRemoved;
|
|
101
|
+
pkgStats.scriptsRemoved = report.scriptsRemoved;
|
|
102
|
+
pkgStats.devDependenciesRemoved = report.devDependenciesRemoved;
|
|
97
103
|
if (report.changed && write) {
|
|
98
104
|
await fs.writeFile(packageJsonPath, `${JSON.stringify(slimmed, null, 2)}\n`, "utf8");
|
|
99
105
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codefast/cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Developer CLI for the Codefast monorepo (arrange, audit, mirror, tag)",
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"description": "Developer CLI for the Codefast monorepo (arrange, audit, mirror, pack-slim, tag)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
7
7
|
"codefast",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"url": "https://github.com/codefastlabs/codefast/issues"
|
|
16
16
|
},
|
|
17
17
|
"license": "MIT",
|
|
18
|
-
"author": "Vuong Phan <mr.thevuong@gmail.com>",
|
|
18
|
+
"author": "Vuong Phan <mr.thevuong@gmail.com> (https://github.com/thevuong)",
|
|
19
19
|
"repository": {
|
|
20
20
|
"type": "git",
|
|
21
21
|
"url": "git+https://github.com/codefastlabs/codefast.git",
|
|
@@ -26,19 +26,12 @@
|
|
|
26
26
|
},
|
|
27
27
|
"files": [
|
|
28
28
|
"dist",
|
|
29
|
-
"
|
|
29
|
+
"CHANGELOG.md",
|
|
30
30
|
"README.md",
|
|
31
|
-
"
|
|
31
|
+
"LICENSE"
|
|
32
32
|
],
|
|
33
33
|
"type": "module",
|
|
34
34
|
"imports": {
|
|
35
|
-
"#/tests/*": [
|
|
36
|
-
"./tests/*",
|
|
37
|
-
"./tests/*.ts",
|
|
38
|
-
"./tests/*.tsx",
|
|
39
|
-
"./tests/*/index.ts",
|
|
40
|
-
"./tests/*/index.tsx"
|
|
41
|
-
],
|
|
42
35
|
"#/*": {
|
|
43
36
|
"default": "./dist/*.js"
|
|
44
37
|
}
|
|
@@ -55,28 +48,7 @@
|
|
|
55
48
|
"yaml": "^2.9.0",
|
|
56
49
|
"zod": "^4.5.4"
|
|
57
50
|
},
|
|
58
|
-
"devDependencies": {
|
|
59
|
-
"@types/node": "^26.4.1",
|
|
60
|
-
"@types/picomatch": "^4.0.3",
|
|
61
|
-
"@vitest/coverage-v8": "^5.0.0",
|
|
62
|
-
"typescript": "^7.0.2",
|
|
63
|
-
"vite": "^8.2.2",
|
|
64
|
-
"vitest": "^5.0.0",
|
|
65
|
-
"@codefast/typescript-config": "0.9.0"
|
|
66
|
-
},
|
|
67
51
|
"engines": {
|
|
68
52
|
"node": ">=24.0.0"
|
|
69
|
-
},
|
|
70
|
-
"scripts": {
|
|
71
|
-
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
72
|
-
"check-types": "tsc --noEmit",
|
|
73
|
-
"clean": "rm -rf dist",
|
|
74
|
-
"test": "vitest run",
|
|
75
|
-
"test:coverage": "vitest run --coverage",
|
|
76
|
-
"test:e2e": "vitest run tests/e2e",
|
|
77
|
-
"test:integration": "vitest run tests/integration",
|
|
78
|
-
"test:type": "vitest run tests/types",
|
|
79
|
-
"test:unit": "vitest run tests/unit",
|
|
80
|
-
"test:watch": "vitest"
|
|
81
53
|
}
|
|
82
54
|
}
|