@nifrajs/cli 2.14.1 → 3.2.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 +10 -0
- package/dist/assure-hydration.d.ts.map +1 -1
- package/dist/assure-hydration.js +6 -1
- package/dist/assure-hydration.js.map +1 -1
- package/dist/assure.d.ts.map +1 -1
- package/dist/assure.js +3 -2
- package/dist/assure.js.map +1 -1
- package/dist/check-diagnostics.d.ts +165 -0
- package/dist/check-diagnostics.d.ts.map +1 -0
- package/dist/check-diagnostics.js +1009 -0
- package/dist/check-diagnostics.js.map +1 -0
- package/dist/check-scan.d.ts +245 -0
- package/dist/check-scan.d.ts.map +1 -0
- package/dist/check-scan.js +1599 -0
- package/dist/check-scan.js.map +1 -0
- package/dist/check.d.ts +4 -324
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +8 -2346
- package/dist/check.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +285 -10
- package/dist/cli.js.map +1 -1
- package/dist/command-catalog.d.ts +27 -1
- package/dist/command-catalog.d.ts.map +1 -1
- package/dist/command-catalog.js +142 -2
- package/dist/command-catalog.js.map +1 -1
- package/dist/doctor.d.ts +42 -0
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +64 -7
- package/dist/doctor.js.map +1 -1
- package/dist/env-file.d.ts +17 -0
- package/dist/env-file.d.ts.map +1 -0
- package/dist/env-file.js +113 -0
- package/dist/env-file.js.map +1 -0
- package/dist/fix-recipes.d.ts.map +1 -1
- package/dist/fix-recipes.js +91 -15
- package/dist/fix-recipes.js.map +1 -1
- package/dist/frontend-guidance.d.ts +57 -0
- package/dist/frontend-guidance.d.ts.map +1 -0
- package/dist/frontend-guidance.js +195 -0
- package/dist/frontend-guidance.js.map +1 -0
- package/dist/internal/reserved-segment-codemod.d.ts +36 -0
- package/dist/internal/reserved-segment-codemod.d.ts.map +1 -0
- package/dist/internal/reserved-segment-codemod.js +134 -0
- package/dist/internal/reserved-segment-codemod.js.map +1 -0
- package/dist/internal/typescript-import.d.ts +1 -1
- package/dist/internal/typescript-import.d.ts.map +1 -1
- package/dist/internal/typescript-import.js +62 -11
- package/dist/internal/typescript-import.js.map +1 -1
- package/dist/introspect.d.ts +26 -1
- package/dist/introspect.d.ts.map +1 -1
- package/dist/introspect.js +74 -24
- package/dist/introspect.js.map +1 -1
- package/dist/levels-tool.d.ts.map +1 -1
- package/dist/levels-tool.js +6 -0
- package/dist/levels-tool.js.map +1 -1
- package/dist/load.d.ts.map +1 -1
- package/dist/load.js +4 -1
- package/dist/load.js.map +1 -1
- package/dist/mcp-context.d.ts +48 -0
- package/dist/mcp-context.d.ts.map +1 -0
- package/dist/mcp-context.js +215 -0
- package/dist/mcp-context.js.map +1 -0
- package/dist/mcp-docs-tools.d.ts.map +1 -1
- package/dist/mcp-docs-tools.js +31 -0
- package/dist/mcp-docs-tools.js.map +1 -1
- package/dist/mcp-exec.d.ts +74 -0
- package/dist/mcp-exec.d.ts.map +1 -0
- package/dist/mcp-exec.js +844 -0
- package/dist/mcp-exec.js.map +1 -0
- package/dist/mcp-http.js +1 -1
- package/dist/mcp-http.js.map +1 -1
- package/dist/mcp-io.d.ts +26 -0
- package/dist/mcp-io.d.ts.map +1 -0
- package/dist/mcp-io.js +100 -0
- package/dist/mcp-io.js.map +1 -0
- package/dist/mcp-reflect.d.ts +16 -0
- package/dist/mcp-reflect.d.ts.map +1 -0
- package/dist/mcp-reflect.js +101 -0
- package/dist/mcp-reflect.js.map +1 -0
- package/dist/mcp-root.d.ts +7 -0
- package/dist/mcp-root.d.ts.map +1 -1
- package/dist/mcp-root.js +51 -3
- package/dist/mcp-root.js.map +1 -1
- package/dist/mcp.d.ts +10 -84
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +13 -1170
- package/dist/mcp.js.map +1 -1
- package/dist/openapi-tool.d.ts +5 -10
- package/dist/openapi-tool.d.ts.map +1 -1
- package/dist/openapi-tool.js +21 -2
- package/dist/openapi-tool.js.map +1 -1
- package/dist/openapi-types.d.ts +21 -0
- package/dist/openapi-types.d.ts.map +1 -0
- package/dist/openapi-types.js +214 -0
- package/dist/openapi-types.js.map +1 -0
- package/dist/port.d.ts.map +1 -1
- package/dist/port.js +2 -1
- package/dist/port.js.map +1 -1
- package/dist/project-facts.d.ts +2 -1
- package/dist/project-facts.d.ts.map +1 -1
- package/dist/project-facts.js.map +1 -1
- package/dist/recipes/3.0.0.d.ts +10 -0
- package/dist/recipes/3.0.0.d.ts.map +1 -0
- package/dist/recipes/3.0.0.js +23 -0
- package/dist/recipes/3.0.0.js.map +1 -0
- package/dist/recipes/index.d.ts.map +1 -1
- package/dist/recipes/index.js +2 -0
- package/dist/recipes/index.js.map +1 -1
- package/dist/release-verification.d.ts +4 -1
- package/dist/release-verification.d.ts.map +1 -1
- package/dist/release-verification.js +6 -123
- package/dist/release-verification.js.map +1 -1
- package/dist/rules/codes.d.ts +4 -0
- package/dist/rules/codes.d.ts.map +1 -1
- package/dist/rules/codes.js +4 -0
- package/dist/rules/codes.js.map +1 -1
- package/dist/rules/islands.d.ts +4 -0
- package/dist/rules/islands.d.ts.map +1 -0
- package/dist/rules/islands.js +155 -0
- package/dist/rules/islands.js.map +1 -0
- package/dist/rules/nano.d.ts +6 -0
- package/dist/rules/nano.d.ts.map +1 -0
- package/dist/rules/nano.js +264 -0
- package/dist/rules/nano.js.map +1 -0
- package/dist/rules/routes.d.ts.map +1 -1
- package/dist/rules/routes.js +17 -30
- package/dist/rules/routes.js.map +1 -1
- package/dist/rules/security.d.ts.map +1 -1
- package/dist/rules/security.js +38 -4
- package/dist/rules/security.js.map +1 -1
- package/dist/scaffold.d.ts +12 -6
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +149 -10
- package/dist/scaffold.js.map +1 -1
- package/dist/search-terms.d.ts.map +1 -1
- package/dist/search-terms.js +5 -3
- package/dist/search-terms.js.map +1 -1
- package/dist/stylex-migrate.d.ts +38 -0
- package/dist/stylex-migrate.d.ts.map +1 -0
- package/dist/stylex-migrate.js +1052 -0
- package/dist/stylex-migrate.js.map +1 -0
- package/dist/sync-manifest.d.ts.map +1 -1
- package/dist/sync-manifest.js +2 -1
- package/dist/sync-manifest.js.map +1 -1
- package/dist/verification-plan.d.ts +24 -0
- package/dist/verification-plan.d.ts.map +1 -0
- package/dist/verification-plan.js +116 -0
- package/dist/verification-plan.js.map +1 -0
- package/dist/work-graph.d.ts.map +1 -1
- package/dist/work-graph.js +6 -3
- package/dist/work-graph.js.map +1 -1
- package/dist/workspace-link.d.ts +37 -0
- package/dist/workspace-link.d.ts.map +1 -0
- package/dist/workspace-link.js +72 -0
- package/dist/workspace-link.js.map +1 -0
- package/docs/examples.json +37 -1
- package/docs/llms-full.txt +1598 -44
- package/docs/types.json +5157 -172
- package/package.json +9 -9
- package/src/assure-hydration.ts +6 -1
- package/src/assure.ts +4 -2
- package/src/check-diagnostics.ts +1219 -0
- package/src/check-scan.ts +1825 -0
- package/src/check.ts +47 -2731
- package/src/cli.ts +291 -10
- package/src/command-catalog.ts +177 -2
- package/src/doctor.ts +99 -8
- package/src/env-file.ts +121 -0
- package/src/fix-recipes.ts +101 -15
- package/src/frontend-guidance.ts +259 -0
- package/src/internal/reserved-segment-codemod.ts +150 -0
- package/src/internal/typescript-import.ts +61 -11
- package/src/introspect.ts +82 -21
- package/src/levels-tool.ts +6 -0
- package/src/load.ts +4 -1
- package/src/mcp-context.ts +266 -0
- package/src/mcp-docs-tools.ts +37 -0
- package/src/mcp-exec.ts +1040 -0
- package/src/mcp-http.ts +1 -1
- package/src/mcp-io.ts +118 -0
- package/src/mcp-reflect.ts +140 -0
- package/src/mcp-root.ts +62 -4
- package/src/mcp.ts +61 -1436
- package/src/openapi-tool.ts +32 -2
- package/src/openapi-types.ts +261 -0
- package/src/port.ts +2 -1
- package/src/project-facts.ts +2 -4
- package/src/recipes/3.0.0.ts +24 -0
- package/src/recipes/index.ts +2 -0
- package/src/release-verification.ts +22 -147
- package/src/rules/codes.ts +4 -0
- package/src/rules/islands.ts +171 -0
- package/src/rules/nano.ts +295 -0
- package/src/rules/routes.ts +17 -30
- package/src/rules/security.ts +40 -4
- package/src/scaffold.ts +169 -9
- package/src/search-terms.ts +7 -3
- package/src/stylex-migrate.ts +1139 -0
- package/src/sync-manifest.ts +2 -1
- package/src/verification-plan.ts +242 -0
- package/src/work-graph.ts +6 -3
- package/src/workspace-link.ts +80 -0
package/src/sync-manifest.ts
CHANGED
|
@@ -46,7 +46,8 @@ export async function syncServerManifests(cwd: string): Promise<SyncManifestResu
|
|
|
46
46
|
"@nifrajs/web/build"
|
|
47
47
|
)
|
|
48
48
|
const results: SyncManifestResult[] = []
|
|
49
|
-
for await (const
|
|
49
|
+
for await (const rawRel of new Glob("**/server-manifest.ts").scan({ cwd, dot: false })) {
|
|
50
|
+
const rel = rawRel.replaceAll("\\", "/")
|
|
50
51
|
if (IGNORED.test(rel)) continue
|
|
51
52
|
const abs = join(cwd, rel)
|
|
52
53
|
const source = await Bun.file(abs).text()
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The repository verification plan.
|
|
3
|
+
*
|
|
4
|
+
* Gates are declared once and plans refer to them by stable id. Keeping composition separate from the
|
|
5
|
+
* gate registry is deliberate: adding a gate to one mode cannot silently re-point another mode through
|
|
6
|
+
* an array index. The release runner, its CLI/MCP projections, and the plan tests all consume this seam.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export type VerificationPlanMode = "default" | "release"
|
|
10
|
+
|
|
11
|
+
export interface VerificationGateSpec {
|
|
12
|
+
readonly id: string
|
|
13
|
+
readonly commands: readonly (readonly string[])[]
|
|
14
|
+
readonly remediation: string
|
|
15
|
+
/** Whether the gate must be represented by a command in `.github/workflows/ci.yml`. */
|
|
16
|
+
readonly workflowRequired: boolean
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const gate = (
|
|
20
|
+
id: string,
|
|
21
|
+
commands: readonly (readonly string[])[],
|
|
22
|
+
remediation: string,
|
|
23
|
+
options: { readonly workflowRequired?: boolean } = {},
|
|
24
|
+
): VerificationGateSpec =>
|
|
25
|
+
Object.freeze({
|
|
26
|
+
id,
|
|
27
|
+
commands: Object.freeze(commands.map((args) => Object.freeze([...args]))),
|
|
28
|
+
remediation,
|
|
29
|
+
workflowRequired: options.workflowRequired ?? true,
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
const GATES = Object.freeze([
|
|
33
|
+
gate("lint", [["run", "lint"]], "Run `bun run lint` and fix the reported lint findings."),
|
|
34
|
+
gate(
|
|
35
|
+
"typecheck",
|
|
36
|
+
[["run", "typecheck"]],
|
|
37
|
+
"Run `bun run typecheck` and fix the reported TypeScript errors.",
|
|
38
|
+
),
|
|
39
|
+
gate("tests", [["run", "test"]], "Run `bun run test` and fix the first failing test."),
|
|
40
|
+
gate(
|
|
41
|
+
"docs",
|
|
42
|
+
[["run", "check:docs"]],
|
|
43
|
+
"Run `bun run check:docs` and update the failing documentation example.",
|
|
44
|
+
),
|
|
45
|
+
gate(
|
|
46
|
+
"api-corpus",
|
|
47
|
+
[["run", "check:api"]],
|
|
48
|
+
"Run `bun run gen:api` and review the generated API reference.",
|
|
49
|
+
),
|
|
50
|
+
gate(
|
|
51
|
+
"cards-corpus",
|
|
52
|
+
[["run", "check:cards"]],
|
|
53
|
+
"Run `bun run gen:cards` and review the generated package cards.",
|
|
54
|
+
),
|
|
55
|
+
gate(
|
|
56
|
+
"node-outcome-corpus",
|
|
57
|
+
[["run", "check:node-outcome"]],
|
|
58
|
+
"Run `bun run gen:node-outcome` and review the generated Node outcome contract.",
|
|
59
|
+
),
|
|
60
|
+
gate(
|
|
61
|
+
"sitemap",
|
|
62
|
+
[["run", "check:sitemap"]],
|
|
63
|
+
"Run `bun run gen:sitemap` and review the generated sitemap.",
|
|
64
|
+
),
|
|
65
|
+
gate(
|
|
66
|
+
"public-manifest",
|
|
67
|
+
[["run", "check:public-manifest"]],
|
|
68
|
+
"Run `bun run gen:public` and review the generated public product manifest.",
|
|
69
|
+
),
|
|
70
|
+
gate(
|
|
71
|
+
"public-boundary",
|
|
72
|
+
[["run", "check:public-boundary"]],
|
|
73
|
+
"Run `bun run check:public-boundary` (which includes the agent-boundary scan) and remove the reported boundary violation.",
|
|
74
|
+
),
|
|
75
|
+
gate(
|
|
76
|
+
"size",
|
|
77
|
+
[["run", "check:size"]],
|
|
78
|
+
"Run `bun run check:size` and either reduce the bundle or update the reviewed budget.",
|
|
79
|
+
),
|
|
80
|
+
gate(
|
|
81
|
+
"changesets",
|
|
82
|
+
[["run", "check:changesets"]],
|
|
83
|
+
"Run `bun run changeset` and name every package whose source changed, so the release documents it.",
|
|
84
|
+
),
|
|
85
|
+
gate("build", [["run", "build"]], "Run `bun run build` and fix the first package build failure."),
|
|
86
|
+
gate(
|
|
87
|
+
"coverage",
|
|
88
|
+
[
|
|
89
|
+
["run", "test:coverage"],
|
|
90
|
+
["run", "check:coverage"],
|
|
91
|
+
],
|
|
92
|
+
"Run `bun run test:coverage` first, then `bun run check:coverage`, and fix the reported coverage regression.",
|
|
93
|
+
),
|
|
94
|
+
gate(
|
|
95
|
+
"corpus",
|
|
96
|
+
[["run", "check:corpus"]],
|
|
97
|
+
"Run `bun run gen:llms`, `bun run gen:api`, and `bun run gen:cards`, then rerun the corpus gate.",
|
|
98
|
+
),
|
|
99
|
+
gate(
|
|
100
|
+
"core-performance",
|
|
101
|
+
[["run", "check:core-performance"]],
|
|
102
|
+
"Run `bun run check:core-performance` and investigate the measured performance regression.",
|
|
103
|
+
{ workflowRequired: false },
|
|
104
|
+
),
|
|
105
|
+
gate(
|
|
106
|
+
"publish",
|
|
107
|
+
[["run", "check:publish"]],
|
|
108
|
+
"Run `bun run check:publish` and fix the publish-consumer metadata or type-surface failure.",
|
|
109
|
+
),
|
|
110
|
+
gate(
|
|
111
|
+
"consumer",
|
|
112
|
+
[["run", "check:consumers"]],
|
|
113
|
+
"Run `bun run check:consumers` and fix the isolated consumer failure.",
|
|
114
|
+
),
|
|
115
|
+
gate(
|
|
116
|
+
"cold-start",
|
|
117
|
+
[["run", "check:cold-start"]],
|
|
118
|
+
"Run `bun run check:cold-start` and fix the fresh scaffold install or build failure.",
|
|
119
|
+
),
|
|
120
|
+
gate(
|
|
121
|
+
"cross-runtime-deno",
|
|
122
|
+
[
|
|
123
|
+
["run", "test:deno"],
|
|
124
|
+
["run", "check:deno-tarball"],
|
|
125
|
+
],
|
|
126
|
+
"Run `bun run test:deno` and `bun run check:deno-tarball`, then fix the first Deno compatibility failure.",
|
|
127
|
+
),
|
|
128
|
+
gate(
|
|
129
|
+
"cross-runtime-node",
|
|
130
|
+
[["run", "test:node"]],
|
|
131
|
+
"Run `bun run test:node` and fix the Node runtime adapter failure.",
|
|
132
|
+
),
|
|
133
|
+
gate(
|
|
134
|
+
"workerd",
|
|
135
|
+
[["run", "check:workerd"]],
|
|
136
|
+
"Run `bun run check:workerd` and fix the real Workers runtime contract failure.",
|
|
137
|
+
),
|
|
138
|
+
gate(
|
|
139
|
+
"pipeline-parity",
|
|
140
|
+
[["run", "check:pipeline-parity"]],
|
|
141
|
+
"Run `bun run check:pipeline-parity` and fix the development and production manifest drift.",
|
|
142
|
+
),
|
|
143
|
+
gate(
|
|
144
|
+
"cli-isolation",
|
|
145
|
+
[["run", "check:cli-isolation"]],
|
|
146
|
+
"Run `bun run check:cli-isolation` and fix the first order-dependent CLI test failure.",
|
|
147
|
+
),
|
|
148
|
+
gate(
|
|
149
|
+
"plugin-manifest",
|
|
150
|
+
[["run", "check:plugin"]],
|
|
151
|
+
"Run `bun run check:plugin` and reconcile the skills package, plugin, and marketplace manifests.",
|
|
152
|
+
),
|
|
153
|
+
gate(
|
|
154
|
+
"verification-parity",
|
|
155
|
+
[["run", "check:verification-parity"]],
|
|
156
|
+
"Run `bun run check:verification-parity` and reconcile the release plan with CI.",
|
|
157
|
+
),
|
|
158
|
+
])
|
|
159
|
+
|
|
160
|
+
const DEFAULT_PLAN = Object.freeze([
|
|
161
|
+
"lint",
|
|
162
|
+
"typecheck",
|
|
163
|
+
"tests",
|
|
164
|
+
"docs",
|
|
165
|
+
"api-corpus",
|
|
166
|
+
"cards-corpus",
|
|
167
|
+
"node-outcome-corpus",
|
|
168
|
+
"sitemap",
|
|
169
|
+
"public-boundary",
|
|
170
|
+
"public-manifest",
|
|
171
|
+
"plugin-manifest",
|
|
172
|
+
"size",
|
|
173
|
+
"changesets",
|
|
174
|
+
] as const)
|
|
175
|
+
|
|
176
|
+
const RELEASE_PLAN = Object.freeze([
|
|
177
|
+
"build",
|
|
178
|
+
"lint",
|
|
179
|
+
"typecheck",
|
|
180
|
+
"tests",
|
|
181
|
+
"cli-isolation",
|
|
182
|
+
"coverage",
|
|
183
|
+
"corpus",
|
|
184
|
+
"docs",
|
|
185
|
+
"public-boundary",
|
|
186
|
+
"public-manifest",
|
|
187
|
+
"plugin-manifest",
|
|
188
|
+
"size",
|
|
189
|
+
"core-performance",
|
|
190
|
+
"publish",
|
|
191
|
+
"consumer",
|
|
192
|
+
"cold-start",
|
|
193
|
+
"cross-runtime-deno",
|
|
194
|
+
"cross-runtime-node",
|
|
195
|
+
"workerd",
|
|
196
|
+
"pipeline-parity",
|
|
197
|
+
"verification-parity",
|
|
198
|
+
"changesets",
|
|
199
|
+
] as const)
|
|
200
|
+
|
|
201
|
+
const gateById = new Map(GATES.map((entry) => [entry.id, entry]))
|
|
202
|
+
|
|
203
|
+
const planIds = (mode: VerificationPlanMode): readonly string[] =>
|
|
204
|
+
mode === "release" ? RELEASE_PLAN : DEFAULT_PLAN
|
|
205
|
+
|
|
206
|
+
/** Return the immutable, ordered gate plan for a verification mode. */
|
|
207
|
+
export function verificationPlan(
|
|
208
|
+
mode: VerificationPlanMode = "default",
|
|
209
|
+
): readonly VerificationGateSpec[] {
|
|
210
|
+
return Object.freeze(
|
|
211
|
+
planIds(mode).map((id) => {
|
|
212
|
+
const entry = gateById.get(id)
|
|
213
|
+
if (entry === undefined) throw new Error(`verification plan references unknown gate: ${id}`)
|
|
214
|
+
return entry
|
|
215
|
+
}),
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** Stable IDs are useful to CI and tests that need to compare plans without parsing prose. */
|
|
220
|
+
export function verificationPlanIds(mode: VerificationPlanMode = "default"): readonly string[] {
|
|
221
|
+
return planIds(mode)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Stable IDs in the full plan that are not part of a shorter plan. */
|
|
225
|
+
export function omittedVerificationGateIds(
|
|
226
|
+
mode: VerificationPlanMode = "default",
|
|
227
|
+
fullMode: VerificationPlanMode = "release",
|
|
228
|
+
): readonly string[] {
|
|
229
|
+
const included = new Set(planIds(mode))
|
|
230
|
+
return Object.freeze(planIds(fullMode).filter((id) => !included.has(id)))
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const commandLabel = (args: readonly string[]): string => `bun ${args.join(" ")}`
|
|
234
|
+
|
|
235
|
+
/** Render the declarative plan without running any gate. */
|
|
236
|
+
export function renderVerificationPlan(mode: VerificationPlanMode = "default"): string {
|
|
237
|
+
const lines = [`nifra verification plan --${mode}`, ""]
|
|
238
|
+
for (const [index, entry] of verificationPlan(mode).entries()) {
|
|
239
|
+
lines.push(`${index + 1}. ${entry.id}: ${entry.commands.map(commandLabel).join(" → ")}`)
|
|
240
|
+
}
|
|
241
|
+
return lines.join("\n")
|
|
242
|
+
}
|
package/src/work-graph.ts
CHANGED
|
@@ -173,7 +173,8 @@ export async function inspectBuildFreshness(cwd: string): Promise<BuildFreshness
|
|
|
173
173
|
const path = resolve(cwd, name)
|
|
174
174
|
if (existsSync(path)) sourcePaths.push(path)
|
|
175
175
|
}
|
|
176
|
-
for await (const
|
|
176
|
+
for await (const rawPath of SOURCE_GLOB.scan({ cwd, dot: false })) {
|
|
177
|
+
const path = rawPath.replaceAll("\\", "/")
|
|
177
178
|
if (!IGNORED.test(path) && path.startsWith("routes/")) sourcePaths.push(resolve(cwd, path))
|
|
178
179
|
}
|
|
179
180
|
const sourceTimes = await fileTimes(sourcePaths)
|
|
@@ -181,7 +182,8 @@ export async function inspectBuildFreshness(cwd: string): Promise<BuildFreshness
|
|
|
181
182
|
const buildDir = resolve(cwd, buildDirName)
|
|
182
183
|
if (!existsSync(buildDir)) continue
|
|
183
184
|
const artifacts: string[] = []
|
|
184
|
-
for await (const
|
|
185
|
+
for await (const rawPath of new Glob("**/*.{js,mjs,cjs}").scan({ cwd: buildDir, dot: false })) {
|
|
186
|
+
const path = rawPath.replaceAll("\\", "/")
|
|
185
187
|
artifacts.push(resolve(buildDir, path))
|
|
186
188
|
}
|
|
187
189
|
if (artifacts.length === 0) continue
|
|
@@ -515,7 +517,8 @@ export function renderWorkGraphText(result: ProjectWorkGraphResult): string {
|
|
|
515
517
|
|
|
516
518
|
async function collectProjectFiles(cwd: string): Promise<WorkGraphSourceFile[]> {
|
|
517
519
|
const files: WorkGraphSourceFile[] = []
|
|
518
|
-
for await (const
|
|
520
|
+
for await (const rawPath of SOURCE_GLOB.scan({ cwd, dot: false })) {
|
|
521
|
+
const path = rawPath.replaceAll("\\", "/")
|
|
519
522
|
if (IGNORED.test(path)) continue
|
|
520
523
|
const full = resolve(cwd, path)
|
|
521
524
|
try {
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolving a WORKSPACE-LINKED dependency back to the directory it really lives in.
|
|
3
|
+
*
|
|
4
|
+
* A workspace link is the one install whose realpath deliberately leaves the project: `link:` and
|
|
5
|
+
* `workspaces` entries point at a source checkout, routinely in a sibling repository. That is exactly
|
|
6
|
+
* what makes such a package able to go stale (its `dist/` is nobody's build step) and exactly what a
|
|
7
|
+
* project-contained path check rejects - so the rebuild has to resolve the escape on purpose, under
|
|
8
|
+
* its own rules, rather than reuse `resolveInsideProject`.
|
|
9
|
+
*
|
|
10
|
+
* The rules that keep it safe:
|
|
11
|
+
* - the path is never taken from a diagnostic; it is re-derived from the package NAME by walking the
|
|
12
|
+
* project's own `node_modules` chain, so only a symlink the package manager already created can be
|
|
13
|
+
* reached, and only a package the project actually declares;
|
|
14
|
+
* - a copy that resolves INSIDE `node_modules` is a registry tarball - immutable, never stale, and
|
|
15
|
+
* never rebuilt;
|
|
16
|
+
* - the package must declare the build script itself. nifra runs the project's own script, never a
|
|
17
|
+
* command of its own composition, and never through a shell.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { sep } from "node:path"
|
|
21
|
+
import { resolvedInstalledCopy } from "@nifrajs/web/internal/parity"
|
|
22
|
+
|
|
23
|
+
/** The one script name a rebuild will run. Guessing among `prepare`/`compile`/`build:*` would risk
|
|
24
|
+
* running something that is not the build at all; a package that names it differently is reported as
|
|
25
|
+
* un-automatable instead. */
|
|
26
|
+
const BUILD_SCRIPT = "build"
|
|
27
|
+
|
|
28
|
+
/** A package name npm would accept, with no path segment that could climb out of `node_modules`. */
|
|
29
|
+
const PACKAGE_NAME = /^@?[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]+)?$/
|
|
30
|
+
|
|
31
|
+
export function isPackageName(value: string): boolean {
|
|
32
|
+
return PACKAGE_NAME.test(value) && !value.split("/").some((part) => part === "." || part === "..")
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The package's own build script name, or `undefined` when it declares none. */
|
|
36
|
+
export function buildScriptName(meta: Record<string, unknown> | undefined): string | undefined {
|
|
37
|
+
const scripts = meta?.scripts
|
|
38
|
+
if (typeof scripts !== "object" || scripts === null) return undefined
|
|
39
|
+
const script = (scripts as Record<string, unknown>)[BUILD_SCRIPT]
|
|
40
|
+
return typeof script === "string" && script.trim().length > 0 ? BUILD_SCRIPT : undefined
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type WorkspaceLinkResolution =
|
|
44
|
+
| { readonly ok: true; readonly dir: string; readonly buildScript: string }
|
|
45
|
+
| { readonly ok: false; readonly reason: string }
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Resolve `name` to the workspace-linked checkout the project loads it from, refusing with a stated
|
|
49
|
+
* reason rather than resolving to nothing. The upward walk is unbounded on purpose: it has to reach the
|
|
50
|
+
* same hoisted copy the staleness scan found, which in a monorepo sits above the package being checked.
|
|
51
|
+
* It only ever inspects `node_modules/<name>` directories, so it cannot wander into an arbitrary path.
|
|
52
|
+
*/
|
|
53
|
+
export async function resolveWorkspaceLinkedPackage(
|
|
54
|
+
cwd: string,
|
|
55
|
+
name: string,
|
|
56
|
+
): Promise<WorkspaceLinkResolution> {
|
|
57
|
+
if (!isPackageName(name)) return { ok: false, reason: `not a package name: ${name}` }
|
|
58
|
+
// Boundary "" and the `node_modules` refusal below mirror `collectStaleWorkspaceDists` exactly, so
|
|
59
|
+
// the rebuild always lands on the same copy the staleness scan flagged.
|
|
60
|
+
const copy = await resolvedInstalledCopy(cwd, "", name)
|
|
61
|
+
if (copy === undefined) return { ok: false, reason: `${name} is not installed under ${cwd}` }
|
|
62
|
+
if (copy.path.includes(`${sep}node_modules${sep}`)) {
|
|
63
|
+
return {
|
|
64
|
+
ok: false,
|
|
65
|
+
reason: `${name} resolves to a registry install (${copy.path}), which cannot be stale or rebuilt`,
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const meta = (await Bun.file(`${copy.path}${sep}package.json`)
|
|
69
|
+
.json()
|
|
70
|
+
.catch(() => undefined)) as Record<string, unknown> | undefined
|
|
71
|
+
if (meta === undefined) return { ok: false, reason: `${copy.path} has no readable package.json` }
|
|
72
|
+
const buildScript = buildScriptName(meta)
|
|
73
|
+
if (buildScript === undefined) {
|
|
74
|
+
return {
|
|
75
|
+
ok: false,
|
|
76
|
+
reason: `${name} declares no "${BUILD_SCRIPT}" script in ${copy.path}/package.json, so its artifact cannot be rebuilt automatically - build it the way that package expects`,
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return { ok: true, dir: copy.path, buildScript }
|
|
80
|
+
}
|