@animus-ui/extract 0.1.9 → 0.1.12
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 +48 -1
- package/crates/extract-v2/animus-extract-v2.darwin-arm64.node +0 -0
- package/crates/extract-v2/animus-extract-v2.linux-arm64-gnu.node +0 -0
- package/crates/extract-v2/animus-extract-v2.linux-x64-gnu.node +0 -0
- package/crates/extract-v2/index.d.ts +17 -98
- package/dist/index.cjs +78 -2101
- package/dist/index.mjs +2 -2026
- package/dist/{analyze-project-args.d.ts → pipeline/analyze-project-args.d.ts} +0 -12
- package/dist/pipeline/analyze-project-args.d.ts.map +1 -0
- package/dist/pipeline/assemble-stylesheet.d.ts +23 -0
- package/dist/pipeline/assemble-stylesheet.d.ts.map +1 -0
- package/dist/pipeline/asset-placeholders.d.ts +4 -0
- package/dist/pipeline/asset-placeholders.d.ts.map +1 -0
- package/dist/pipeline/content-hash.d.ts +2 -0
- package/dist/pipeline/content-hash.d.ts.map +1 -0
- package/dist/pipeline/core-options.d.ts +96 -0
- package/dist/pipeline/core-options.d.ts.map +1 -0
- package/dist/pipeline/correlate-external-tokens.d.ts +38 -0
- package/dist/pipeline/correlate-external-tokens.d.ts.map +1 -0
- package/dist/pipeline/discover-files.d.ts +4 -0
- package/dist/pipeline/discover-files.d.ts.map +1 -0
- package/dist/pipeline/discover-packages.d.ts +55 -0
- package/dist/pipeline/discover-packages.d.ts.map +1 -0
- package/dist/pipeline/dynamic-prop-config.d.ts +22 -0
- package/dist/pipeline/dynamic-prop-config.d.ts.map +1 -0
- package/dist/pipeline/engine-adapter.d.ts +63 -0
- package/dist/pipeline/engine-adapter.d.ts.map +1 -0
- package/dist/pipeline/engine-retirement.d.ts +3 -0
- package/dist/pipeline/engine-retirement.d.ts.map +1 -0
- package/dist/pipeline/error-diagnostics.d.ts +8 -0
- package/dist/pipeline/error-diagnostics.d.ts.map +1 -0
- package/dist/{index.d.ts → pipeline/index.d.ts} +16 -6
- package/dist/pipeline/index.d.ts.map +1 -0
- package/dist/pipeline/internal-wire.d.ts +2 -0
- package/dist/pipeline/internal-wire.d.ts.map +1 -0
- package/dist/pipeline/manifest-diagnostics.d.ts +57 -0
- package/dist/pipeline/manifest-diagnostics.d.ts.map +1 -0
- package/dist/pipeline/manifest-schema.d.ts +117 -0
- package/dist/pipeline/manifest-schema.d.ts.map +1 -0
- package/dist/pipeline/mdx-preprocessor.d.ts +25 -0
- package/dist/pipeline/mdx-preprocessor.d.ts.map +1 -0
- package/dist/pipeline/path-aliases.d.ts +15 -0
- package/dist/pipeline/path-aliases.d.ts.map +1 -0
- package/dist/{post-process-css.d.ts → pipeline/post-process-css.d.ts} +0 -8
- package/dist/pipeline/post-process-css.d.ts.map +1 -0
- package/dist/pipeline/prefix.d.ts +8 -0
- package/dist/pipeline/prefix.d.ts.map +1 -0
- package/dist/pipeline/replacement-plans.d.ts +22 -0
- package/dist/pipeline/replacement-plans.d.ts.map +1 -0
- package/dist/pipeline/resolve-asset.d.ts +11 -0
- package/dist/pipeline/resolve-asset.d.ts.map +1 -0
- package/dist/pipeline/run-analysis.d.ts +62 -0
- package/dist/pipeline/run-analysis.d.ts.map +1 -0
- package/dist/pipeline/source-corpus.d.ts +25 -0
- package/dist/pipeline/source-corpus.d.ts.map +1 -0
- package/dist/pipeline/source-identity.d.ts +63 -0
- package/dist/pipeline/source-identity.d.ts.map +1 -0
- package/dist/pipeline/source-ingestion.d.ts +168 -0
- package/dist/pipeline/source-ingestion.d.ts.map +1 -0
- package/dist/pipeline/static-css.d.ts +16 -0
- package/dist/pipeline/static-css.d.ts.map +1 -0
- package/dist/pipeline/structural-self-check.d.ts +12 -0
- package/dist/pipeline/structural-self-check.d.ts.map +1 -0
- package/dist/pipeline/svelte-source-adapter.d.ts +81 -0
- package/dist/pipeline/svelte-source-adapter.d.ts.map +1 -0
- package/dist/pipeline/system-config.d.ts +40 -0
- package/dist/pipeline/system-config.d.ts.map +1 -0
- package/dist/pipeline/system-props-module.d.ts +8 -0
- package/dist/pipeline/system-props-module.d.ts.map +1 -0
- package/dist/pipeline/timing-waterfall.d.ts +6 -0
- package/dist/pipeline/timing-waterfall.d.ts.map +1 -0
- package/dist/pipeline/tsconfig-paths.d.ts +7 -0
- package/dist/pipeline/tsconfig-paths.d.ts.map +1 -0
- package/dist/pipeline/unit-fallback.d.ts +2 -0
- package/dist/pipeline/unit-fallback.d.ts.map +1 -0
- package/dist/pipeline/utils.d.ts +3 -0
- package/dist/pipeline/utils.d.ts.map +1 -0
- package/dist/pipeline/watch-keys.d.ts +2 -0
- package/dist/pipeline/watch-keys.d.ts.map +1 -0
- package/dist/pipeline-DeEENy5C.mjs +2905 -0
- package/dist/pipeline-x1yBZ2UP.cjs +3386 -0
- package/dist/session/extraction-session.d.ts +240 -0
- package/dist/session/extraction-session.d.ts.map +1 -0
- package/dist/session/index.d.ts +10 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/published-set.d.ts +52 -0
- package/dist/session/published-set.d.ts.map +1 -0
- package/dist/session/resolve-packages.d.ts +7 -0
- package/dist/session/resolve-packages.d.ts.map +1 -0
- package/dist/session/session-paths.d.ts +97 -0
- package/dist/session/session-paths.d.ts.map +1 -0
- package/dist/session/singleton.d.ts +85 -0
- package/dist/session/singleton.d.ts.map +1 -0
- package/dist/session/timing.d.ts +2 -0
- package/dist/session/timing.d.ts.map +1 -0
- package/dist/session/turbopack-orchestrator.d.ts +27 -0
- package/dist/session/turbopack-orchestrator.d.ts.map +1 -0
- package/dist/session.cjs +1999 -0
- package/dist/session.mjs +1955 -0
- package/index-v2.js +0 -9
- package/package.json +22 -9
- package/dist/analyze-project-args.d.ts.map +0 -1
- package/dist/assemble-stylesheet.d.ts +0 -58
- package/dist/assemble-stylesheet.d.ts.map +0 -1
- package/dist/asset-placeholders.d.ts +0 -29
- package/dist/asset-placeholders.d.ts.map +0 -1
- package/dist/content-hash.d.ts +0 -9
- package/dist/content-hash.d.ts.map +0 -1
- package/dist/correlate-external-tokens.d.ts +0 -46
- package/dist/correlate-external-tokens.d.ts.map +0 -1
- package/dist/discover-files.d.ts +0 -10
- package/dist/discover-files.d.ts.map +0 -1
- package/dist/discover-packages.d.ts +0 -192
- package/dist/discover-packages.d.ts.map +0 -1
- package/dist/dynamic-prop-config.d.ts +0 -37
- package/dist/dynamic-prop-config.d.ts.map +0 -1
- package/dist/engine-adapter.d.ts +0 -97
- package/dist/engine-adapter.d.ts.map +0 -1
- package/dist/engine-retirement.d.ts +0 -22
- package/dist/engine-retirement.d.ts.map +0 -1
- package/dist/error-diagnostics.d.ts +0 -32
- package/dist/error-diagnostics.d.ts.map +0 -1
- package/dist/external-keyframes.d.ts +0 -23
- package/dist/external-keyframes.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/manifest-diagnostics.d.ts +0 -62
- package/dist/manifest-diagnostics.d.ts.map +0 -1
- package/dist/mdx-preprocessor.d.ts +0 -39
- package/dist/mdx-preprocessor.d.ts.map +0 -1
- package/dist/path-aliases.d.ts +0 -34
- package/dist/path-aliases.d.ts.map +0 -1
- package/dist/post-process-css.d.ts.map +0 -1
- package/dist/prefix.d.ts +0 -15
- package/dist/prefix.d.ts.map +0 -1
- package/dist/replacement-plans.d.ts +0 -46
- package/dist/replacement-plans.d.ts.map +0 -1
- package/dist/resolve-asset.d.ts +0 -14
- package/dist/resolve-asset.d.ts.map +0 -1
- package/dist/run-analysis.d.ts +0 -74
- package/dist/run-analysis.d.ts.map +0 -1
- package/dist/source-identity.d.ts +0 -93
- package/dist/source-identity.d.ts.map +0 -1
- package/dist/static-css.d.ts +0 -25
- package/dist/static-css.d.ts.map +0 -1
- package/dist/system-config.d.ts +0 -57
- package/dist/system-config.d.ts.map +0 -1
- package/dist/system-props-module.d.ts +0 -22
- package/dist/system-props-module.d.ts.map +0 -1
- package/dist/timing-waterfall.d.ts +0 -17
- package/dist/timing-waterfall.d.ts.map +0 -1
- package/dist/tsconfig-paths.d.ts +0 -8
- package/dist/tsconfig-paths.d.ts.map +0 -1
- package/dist/unit-fallback.d.ts +0 -7
- package/dist/unit-fallback.d.ts.map +0 -1
- package/dist/utils.d.ts +0 -3
- package/dist/utils.d.ts.map +0 -1
- package/dist/watch-keys.d.ts +0 -12
- package/dist/watch-keys.d.ts.map +0 -1
package/dist/session.cjs
ADDED
|
@@ -0,0 +1,1999 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_pipeline = require("./pipeline-x1yBZ2UP.cjs");
|
|
3
|
+
let crypto = require("crypto");
|
|
4
|
+
let fs = require("fs");
|
|
5
|
+
let path = require("path");
|
|
6
|
+
//#region session/session-paths.ts
|
|
7
|
+
/**
|
|
8
|
+
* Session artifact names, path derivations, and artifact shapes. Imports no
|
|
9
|
+
* session or singleton module: the Turbopack loader graph stays worker-local.
|
|
10
|
+
*/
|
|
11
|
+
/** Session-dir-relative name of the replacement-epoch disk witness. */
|
|
12
|
+
const REPLACEMENT_EPOCH_ARTIFACT = "replacements-epoch";
|
|
13
|
+
/** Virtual module id of the emitted system-props module. */
|
|
14
|
+
const TURBOPACK_SYSTEM_PROPS_ID = "virtual:animus/system-props";
|
|
15
|
+
/** Session-dir-relative name of the analysis-commit transaction artifact. */
|
|
16
|
+
const ANALYSIS_COMMIT_ARTIFACT = "analysis-commit";
|
|
17
|
+
/** Session-dir-relative name of the analysis-status artifact. */
|
|
18
|
+
const ANALYSIS_STATUS_ARTIFACT = "analysis-status.json";
|
|
19
|
+
/** Session-dir-relative name of the manifest payload artifact. */
|
|
20
|
+
const MANIFEST_ARTIFACT = "manifest.json";
|
|
21
|
+
/** Session-dir-relative name of the analysis-inputs payload artifact — the
|
|
22
|
+
* Turbopack loader-worker hydration corpus. */
|
|
23
|
+
const ANALYSIS_INPUTS_ARTIFACT = "analysis-inputs.json";
|
|
24
|
+
/** Session-dir-relative name of the emitted stylesheet artifact. */
|
|
25
|
+
const STYLES_ARTIFACT = "styles.css";
|
|
26
|
+
/** Session-dir-relative name of the emitted system-props module. */
|
|
27
|
+
const SYSTEM_PROPS_ARTIFACT = "system-props.js";
|
|
28
|
+
/** Session-dir-relative directory holding copied asset() files. */
|
|
29
|
+
const SESSION_ASSETS_DIR = "assets";
|
|
30
|
+
/** The standalone CLI's published-set commit record in the flat `.animus/`
|
|
31
|
+
* tree; the session's start hygiene keys on this name before deleting. */
|
|
32
|
+
const CLI_COMMIT_ARTIFACT = "commit.json";
|
|
33
|
+
/** Advisory claim record (pid plus heartbeat) of whichever directory holds
|
|
34
|
+
* it; sessions write it into their own tree too, despite the `CLI_` name. */
|
|
35
|
+
const CLI_LOCK_ARTIFACT = "lock.json";
|
|
36
|
+
/** Project-relative artifact directory: the CLI's default output tree and
|
|
37
|
+
* the parent of every session tree. One spelling, or the tree splits. */
|
|
38
|
+
const ANIMUS_ARTIFACT_DIR = ".animus";
|
|
39
|
+
/** Module id the Rust emitter injects, and the alias key both bundlers
|
|
40
|
+
* register. A literal on purpose: a rename breaks built consumer packages.
|
|
41
|
+
* The Next plugin's webpack alias harvest skips this exact key, so a change
|
|
42
|
+
* here is a change in the emitter, both bundler arms and that harvest. */
|
|
43
|
+
const ANIMUS_CSS_MODULE_ID = ".animus/styles.css";
|
|
44
|
+
function sessionsRootDir(rootDir) {
|
|
45
|
+
return (0, path.join)(rootDir, ANIMUS_ARTIFACT_DIR, "sessions");
|
|
46
|
+
}
|
|
47
|
+
function sessionArtifactDir(rootDir, sessionId) {
|
|
48
|
+
return (0, path.join)(sessionsRootDir(rootDir), sessionId);
|
|
49
|
+
}
|
|
50
|
+
function replacementEpochPath(sessionDir) {
|
|
51
|
+
return (0, path.join)(sessionDir, REPLACEMENT_EPOCH_ARTIFACT);
|
|
52
|
+
}
|
|
53
|
+
function analysisCommitPath(sessionDir) {
|
|
54
|
+
return (0, path.join)(sessionDir, ANALYSIS_COMMIT_ARTIFACT);
|
|
55
|
+
}
|
|
56
|
+
function analysisStatusPath(sessionDir) {
|
|
57
|
+
return (0, path.join)(sessionDir, ANALYSIS_STATUS_ARTIFACT);
|
|
58
|
+
}
|
|
59
|
+
function manifestPath(sessionDir) {
|
|
60
|
+
return (0, path.join)(sessionDir, MANIFEST_ARTIFACT);
|
|
61
|
+
}
|
|
62
|
+
function analysisInputsPath(sessionDir) {
|
|
63
|
+
return (0, path.join)(sessionDir, ANALYSIS_INPUTS_ARTIFACT);
|
|
64
|
+
}
|
|
65
|
+
function stylesPath(sessionDir) {
|
|
66
|
+
return (0, path.join)(sessionDir, STYLES_ARTIFACT);
|
|
67
|
+
}
|
|
68
|
+
function systemPropsPath(sessionDir) {
|
|
69
|
+
return (0, path.join)(sessionDir, SYSTEM_PROPS_ARTIFACT);
|
|
70
|
+
}
|
|
71
|
+
/** Splices the envelope in as a leading `__animusSession` field without
|
|
72
|
+
* re-serializing: parse/re-stringify would reformat engine-emitted JSON. */
|
|
73
|
+
function envelopeJsonArtifact(payload, envelopeJson) {
|
|
74
|
+
const trimmed = payload.trimStart();
|
|
75
|
+
if (!trimmed.startsWith("{")) return payload;
|
|
76
|
+
if (trimmed.slice(1).trimStart().startsWith("}")) return `{"__animusSession":${envelopeJson}}`;
|
|
77
|
+
return `{"__animusSession":${envelopeJson},${trimmed.slice(1)}`;
|
|
78
|
+
}
|
|
79
|
+
/** Trailing-comment side-band: a leading comment would displace the `@layer`
|
|
80
|
+
* declaration consumers pin at offset 0. */
|
|
81
|
+
function envelopeCssArtifact(payload, envelopeJson) {
|
|
82
|
+
return `${payload}\n/* __animusSession ${envelopeJson} */\n`;
|
|
83
|
+
}
|
|
84
|
+
const ENVELOPE_CSS_COMMENT_RE = /\/\* __animusSession (\{.*\}) \*\//;
|
|
85
|
+
/** Envelope of a JSON artifact's bytes, or undefined when it carries none.
|
|
86
|
+
* Throws on unparseable bytes; callers decide torn-artifact policy. */
|
|
87
|
+
function readJsonEnvelope(bytes) {
|
|
88
|
+
return JSON.parse(bytes).__animusSession;
|
|
89
|
+
}
|
|
90
|
+
/** Envelope of the CSS side-band comment, or undefined when it is absent.
|
|
91
|
+
* Throws on an unparseable envelope body. */
|
|
92
|
+
function readCssEnvelope(bytes) {
|
|
93
|
+
const match = bytes.match(ENVELOPE_CSS_COMMENT_RE);
|
|
94
|
+
return match ? JSON.parse(match[1]) : void 0;
|
|
95
|
+
}
|
|
96
|
+
//#endregion
|
|
97
|
+
//#region session/published-set.ts
|
|
98
|
+
function isPublishedSetRecord(value) {
|
|
99
|
+
return Object.prototype.toString.call(value) === "[object Object]";
|
|
100
|
+
}
|
|
101
|
+
function isPublishedSetString(value) {
|
|
102
|
+
return Object.prototype.toString.call(value) === "[object String]";
|
|
103
|
+
}
|
|
104
|
+
function isPublishedSetNumber(value) {
|
|
105
|
+
return Object.prototype.toString.call(value) === "[object Number]";
|
|
106
|
+
}
|
|
107
|
+
function decodeCommitRecord(bytes) {
|
|
108
|
+
let candidate;
|
|
109
|
+
try {
|
|
110
|
+
candidate = JSON.parse(bytes);
|
|
111
|
+
} catch {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
if (!isPublishedSetRecord(candidate) || candidate.schema !== 1) return null;
|
|
115
|
+
const declared = candidate.payloads;
|
|
116
|
+
if (!isPublishedSetRecord(declared)) return null;
|
|
117
|
+
const payloads = {};
|
|
118
|
+
for (const [name, entry] of Object.entries(declared)) {
|
|
119
|
+
if (!isPublishedSetRecord(entry) || !isPublishedSetString(entry.hash)) return null;
|
|
120
|
+
payloads[name] = { hash: entry.hash };
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
schema: 1,
|
|
124
|
+
payloads
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/** Recompute every recorded payload hash over raw bytes — the writer's own
|
|
128
|
+
* hashing domain — and return the mismatches; empty means the set verifies. */
|
|
129
|
+
function verifyCommitRecord(dir) {
|
|
130
|
+
let bytes;
|
|
131
|
+
try {
|
|
132
|
+
bytes = (0, fs.readFileSync)((0, path.join)(dir, CLI_COMMIT_ARTIFACT), "utf-8");
|
|
133
|
+
} catch (error) {
|
|
134
|
+
return [`commit record unreadable: ${String(error)}`];
|
|
135
|
+
}
|
|
136
|
+
const record = decodeCommitRecord(bytes);
|
|
137
|
+
if (record === null) return ["commit record is not a schema-1 payload record"];
|
|
138
|
+
const failures = [];
|
|
139
|
+
for (const [name, entry] of Object.entries(record.payloads)) try {
|
|
140
|
+
if (require_pipeline.contentHash((0, fs.readFileSync)((0, path.join)(dir, name))) !== entry.hash) failures.push(`${name}: bytes do not match the commit record`);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
failures.push(`${name}: unreadable (${String(error)})`);
|
|
143
|
+
}
|
|
144
|
+
return failures;
|
|
145
|
+
}
|
|
146
|
+
const CLI_LOCK_HEARTBEAT_INTERVAL_MS = 6e4;
|
|
147
|
+
/** A claim whose heartbeat is older than this has no live holder. Five missed
|
|
148
|
+
* refreshes: synchronous extraction can block the holder's refresh timer. */
|
|
149
|
+
const CLI_LOCK_STALE_AFTER_MS = 5 * CLI_LOCK_HEARTBEAT_INTERVAL_MS;
|
|
150
|
+
/** Absence of the file — the only read failure that means "no holder".
|
|
151
|
+
* ENOTDIR joins ENOENT: the path cannot exist under a non-directory. */
|
|
152
|
+
function isMissingFile(error) {
|
|
153
|
+
return error instanceof Object && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR");
|
|
154
|
+
}
|
|
155
|
+
function readCliLockRecord(dir) {
|
|
156
|
+
let bytes;
|
|
157
|
+
try {
|
|
158
|
+
bytes = (0, fs.readFileSync)((0, path.join)(dir, CLI_LOCK_ARTIFACT), "utf-8");
|
|
159
|
+
} catch (error) {
|
|
160
|
+
if (isMissingFile(error)) return { kind: "none" };
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
163
|
+
let candidate;
|
|
164
|
+
try {
|
|
165
|
+
candidate = JSON.parse(bytes);
|
|
166
|
+
} catch {
|
|
167
|
+
return { kind: "indeterminate" };
|
|
168
|
+
}
|
|
169
|
+
if (!isPublishedSetRecord(candidate) || !isPublishedSetNumber(candidate.pid)) return { kind: "indeterminate" };
|
|
170
|
+
const startedAt = candidate.startedAt;
|
|
171
|
+
const heartbeatAt = candidate.heartbeatAt;
|
|
172
|
+
const record = { pid: candidate.pid };
|
|
173
|
+
if (isPublishedSetString(startedAt)) record.startedAt = startedAt;
|
|
174
|
+
if (isPublishedSetString(heartbeatAt)) record.heartbeatAt = heartbeatAt;
|
|
175
|
+
return {
|
|
176
|
+
kind: "held",
|
|
177
|
+
record
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function isPermissionDenied(error) {
|
|
181
|
+
return error instanceof Object && "code" in error && error.code === "EPERM";
|
|
182
|
+
}
|
|
183
|
+
function isLockHolderAlive(pid) {
|
|
184
|
+
try {
|
|
185
|
+
process.kill(pid, 0);
|
|
186
|
+
return true;
|
|
187
|
+
} catch (error) {
|
|
188
|
+
return isPermissionDenied(error);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function checkLockLiveness(record) {
|
|
192
|
+
if (!isLockHolderAlive(record.pid)) return {
|
|
193
|
+
live: false,
|
|
194
|
+
reason: `holder pid ${record.pid} is not running`
|
|
195
|
+
};
|
|
196
|
+
const beatAt = record.heartbeatAt === void 0 ? NaN : Date.parse(record.heartbeatAt);
|
|
197
|
+
if (Number.isNaN(beatAt)) return { live: true };
|
|
198
|
+
const ageMs = Date.now() - beatAt;
|
|
199
|
+
if (ageMs <= 3e5) return { live: true };
|
|
200
|
+
return {
|
|
201
|
+
live: false,
|
|
202
|
+
reason: `holder pid ${record.pid} is running but its lock heartbeat (${record.heartbeatAt}) is ${Math.round(ageMs / 1e3)}s old, past the ${CLI_LOCK_STALE_AFTER_MS / 1e3}s staleness window — the pid names a different process now`
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function lockRecordBytes(startedAt) {
|
|
206
|
+
return JSON.stringify({
|
|
207
|
+
pid: process.pid,
|
|
208
|
+
startedAt,
|
|
209
|
+
heartbeatAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
const HEARTBEAT_TEMP_PREFIX = ".lock-heartbeat-";
|
|
213
|
+
function holdDirectoryClaim(dir, options = {}) {
|
|
214
|
+
const startedAt = options.startedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
215
|
+
const path$9 = (0, path.join)(dir, CLI_LOCK_ARTIFACT);
|
|
216
|
+
const tempPath = (0, path.join)(dir, `${HEARTBEAT_TEMP_PREFIX}${process.pid}`);
|
|
217
|
+
const rename = options.write === "rename";
|
|
218
|
+
let refresh = null;
|
|
219
|
+
let stopped = false;
|
|
220
|
+
const stop = () => {
|
|
221
|
+
stopped = true;
|
|
222
|
+
if (refresh !== null) clearInterval(refresh);
|
|
223
|
+
refresh = null;
|
|
224
|
+
};
|
|
225
|
+
const stamp = () => {
|
|
226
|
+
try {
|
|
227
|
+
if (rename) {
|
|
228
|
+
(0, fs.writeFileSync)(tempPath, lockRecordBytes(startedAt));
|
|
229
|
+
(0, fs.renameSync)(tempPath, path$9);
|
|
230
|
+
} else (0, fs.writeFileSync)(path$9, lockRecordBytes(startedAt));
|
|
231
|
+
} catch (error) {
|
|
232
|
+
stop();
|
|
233
|
+
if (isMissingFile(error)) return;
|
|
234
|
+
console.error(`[animus] Could not write the directory claim ${path$9}: ${String(error)}`);
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
stamp();
|
|
238
|
+
if (!stopped) {
|
|
239
|
+
refresh = setInterval(stamp, CLI_LOCK_HEARTBEAT_INTERVAL_MS);
|
|
240
|
+
refresh.unref?.();
|
|
241
|
+
}
|
|
242
|
+
return () => {
|
|
243
|
+
stop();
|
|
244
|
+
if (rename) (0, fs.rmSync)(tempPath, { force: true });
|
|
245
|
+
(0, fs.rmSync)(path$9, { force: true });
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
/** Snapshot the session's copied asset() files, sorted, BEFORE the session
|
|
249
|
+
* tree is disposed. Drivers publish them beside the emitted stylesheet. */
|
|
250
|
+
function collectSessionAssets(sessionDir) {
|
|
251
|
+
if (!sessionDir) return [];
|
|
252
|
+
const dir = (0, path.join)(sessionDir, SESSION_ASSETS_DIR);
|
|
253
|
+
if (!(0, fs.existsSync)(dir)) return [];
|
|
254
|
+
return (0, fs.readdirSync)(dir).sort().map((name) => ({
|
|
255
|
+
name,
|
|
256
|
+
bytes: (0, fs.readFileSync)((0, path.join)(dir, name))
|
|
257
|
+
}));
|
|
258
|
+
}
|
|
259
|
+
//#endregion
|
|
260
|
+
//#region session/resolve-packages.ts
|
|
261
|
+
function resolvePackagesByName(rootDir, names) {
|
|
262
|
+
if (names.length === 0) return {};
|
|
263
|
+
const nameSet = new Set(names);
|
|
264
|
+
const resolved = /* @__PURE__ */ new Set();
|
|
265
|
+
const packageMap = {};
|
|
266
|
+
try {
|
|
267
|
+
const rootPkg = JSON.parse((0, fs.readFileSync)((0, path.join)(rootDir, "package.json"), "utf-8"));
|
|
268
|
+
const workspaces = Array.isArray(rootPkg.workspaces) ? rootPkg.workspaces : rootPkg.workspaces?.packages ?? [];
|
|
269
|
+
for (const ws of workspaces) {
|
|
270
|
+
const wsDir = (0, path.resolve)(rootDir, ws);
|
|
271
|
+
if (!(0, fs.existsSync)(wsDir)) continue;
|
|
272
|
+
try {
|
|
273
|
+
const pkg = JSON.parse((0, fs.readFileSync)((0, path.join)(wsDir, "package.json"), "utf-8"));
|
|
274
|
+
const name = pkg.name || "";
|
|
275
|
+
if (nameSet.has(name)) {
|
|
276
|
+
const main = pkg.main || pkg.module || "index.ts";
|
|
277
|
+
const entryPath = (0, path.resolve)(wsDir, main);
|
|
278
|
+
if ((0, fs.existsSync)(entryPath)) {
|
|
279
|
+
packageMap[name] = (0, path.relative)(rootDir, entryPath);
|
|
280
|
+
resolved.add(name);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
} catch {}
|
|
284
|
+
}
|
|
285
|
+
} catch {}
|
|
286
|
+
for (const name of nameSet) {
|
|
287
|
+
if (resolved.has(name)) continue;
|
|
288
|
+
try {
|
|
289
|
+
const entryPath = require.resolve(name, { paths: [rootDir] });
|
|
290
|
+
packageMap[name] = (0, path.relative)(rootDir, entryPath);
|
|
291
|
+
} catch {}
|
|
292
|
+
}
|
|
293
|
+
return packageMap;
|
|
294
|
+
}
|
|
295
|
+
//#endregion
|
|
296
|
+
//#region session/singleton.ts
|
|
297
|
+
/**
|
|
298
|
+
* globalThis-backed so the ESM plugin copy and the CJS webpack-loader copy
|
|
299
|
+
* share one store; module scope alone gives each copy its own instance.
|
|
300
|
+
*/
|
|
301
|
+
const MANIFEST_KEY = "__animus_manifest_json__";
|
|
302
|
+
const ANALYSIS_STARTED_KEY = "__animus_analysis_started__";
|
|
303
|
+
const SHARED_CSS_KEY = "__animus_shared_css__";
|
|
304
|
+
const SHARED_SYSTEM_PROPS_KEY = "__animus_shared_system_props__";
|
|
305
|
+
const SHARED_EXTERNAL_DIRS_KEY = "__animus_external_pkg_dirs__";
|
|
306
|
+
const SHARED_EXTERNAL_ENTRIES_KEY = "__animus_external_source_entries__";
|
|
307
|
+
const ANALYZED_HASHES_KEY = "__animus_analyzed_hashes__";
|
|
308
|
+
const REPLACEMENT_EPOCH_KEY = "__animus_replacement_epoch__";
|
|
309
|
+
const WATCH_TRANSACTION_KEY = "__animus_watch_transaction__";
|
|
310
|
+
const PROCESS_SESSION_ID_KEY = "__animus_process_session_id__";
|
|
311
|
+
const SESSION_ARTIFACT_DIR_KEY = "__animus_session_artifact_dir__";
|
|
312
|
+
const OWNING_WATCH_SESSION_KEY = "__animus_owning_watch_session__";
|
|
313
|
+
const EXCLUSIVE_SESSION_OWNER_KEY = "__animus_exclusive_session_owner__";
|
|
314
|
+
const ENGINE_KEY = "__animus_engine__";
|
|
315
|
+
const V2_ENGINE_KEY = "__animus_v2_engine__";
|
|
316
|
+
const V2_SENT_SOURCES_KEY = "__animus_v2_sent_sources__";
|
|
317
|
+
const V2_DRIFT_WARNED_KEY = "__animus_v2_drift_warned__";
|
|
318
|
+
const ENGINE_API_OVERRIDE_KEY = "__animus_engine_api_override__";
|
|
319
|
+
const singletonStore = globalThis;
|
|
320
|
+
const isSingletonString = (value) => {
|
|
321
|
+
if (Object(value) === value) return false;
|
|
322
|
+
try {
|
|
323
|
+
String.prototype.valueOf.call(value);
|
|
324
|
+
return true;
|
|
325
|
+
} catch {
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
function getManifestJson() {
|
|
330
|
+
return singletonStore[MANIFEST_KEY];
|
|
331
|
+
}
|
|
332
|
+
function setManifestJson(json) {
|
|
333
|
+
singletonStore[MANIFEST_KEY] = json;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* A latch over the most recently STARTED analysis, never cleared on settle:
|
|
337
|
+
* non-null means one was scheduled. In-flight lives in getWatchTransaction.
|
|
338
|
+
*/
|
|
339
|
+
function getAnalysisStartedPromise() {
|
|
340
|
+
return singletonStore[ANALYSIS_STARTED_KEY];
|
|
341
|
+
}
|
|
342
|
+
function setAnalysisStartedPromise(promise) {
|
|
343
|
+
singletonStore[ANALYSIS_STARTED_KEY] = promise;
|
|
344
|
+
}
|
|
345
|
+
function resetAnalysisStartedPromise() {
|
|
346
|
+
singletonStore[ANALYSIS_STARTED_KEY] = null;
|
|
347
|
+
}
|
|
348
|
+
function getSharedCss() {
|
|
349
|
+
return singletonStore[SHARED_CSS_KEY] || "";
|
|
350
|
+
}
|
|
351
|
+
function setSharedCss(css) {
|
|
352
|
+
singletonStore[SHARED_CSS_KEY] = css;
|
|
353
|
+
}
|
|
354
|
+
function getSharedSystemProps() {
|
|
355
|
+
return singletonStore[SHARED_SYSTEM_PROPS_KEY] || "";
|
|
356
|
+
}
|
|
357
|
+
function setSharedSystemProps(content) {
|
|
358
|
+
singletonStore[SHARED_SYSTEM_PROPS_KEY] = content;
|
|
359
|
+
}
|
|
360
|
+
function getSharedExternalDirs() {
|
|
361
|
+
return singletonStore[SHARED_EXTERNAL_DIRS_KEY] || [];
|
|
362
|
+
}
|
|
363
|
+
function setSharedExternalDirs(dirs) {
|
|
364
|
+
singletonStore[SHARED_EXTERNAL_DIRS_KEY] = dirs;
|
|
365
|
+
}
|
|
366
|
+
function getSharedExternalEntries() {
|
|
367
|
+
return singletonStore[SHARED_EXTERNAL_ENTRIES_KEY] || /* @__PURE__ */ new Map();
|
|
368
|
+
}
|
|
369
|
+
function setSharedExternalEntries(entries) {
|
|
370
|
+
singletonStore[SHARED_EXTERNAL_ENTRIES_KEY] = entries;
|
|
371
|
+
}
|
|
372
|
+
function globalSlot(key) {
|
|
373
|
+
return {
|
|
374
|
+
get: () => singletonStore[key] ?? null,
|
|
375
|
+
set: (value) => {
|
|
376
|
+
singletonStore[key] = value;
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
const analyzedHashesSlot = globalSlot(ANALYZED_HASHES_KEY);
|
|
381
|
+
const replacementEpochSlot = globalSlot(REPLACEMENT_EPOCH_KEY);
|
|
382
|
+
const watchTransactionSlot = globalSlot(WATCH_TRANSACTION_KEY);
|
|
383
|
+
const sessionArtifactDirSlot = globalSlot(SESSION_ARTIFACT_DIR_KEY);
|
|
384
|
+
const owningWatchSessionSlot = globalSlot(OWNING_WATCH_SESSION_KEY);
|
|
385
|
+
/**
|
|
386
|
+
* The only session that can run watch analysis. A non-owning session forwards
|
|
387
|
+
* its batch here; dropping it loses files only that watcher sees.
|
|
388
|
+
*/
|
|
389
|
+
function getOwningWatchSession() {
|
|
390
|
+
return owningWatchSessionSlot.get();
|
|
391
|
+
}
|
|
392
|
+
function setOwningWatchSession(session) {
|
|
393
|
+
owningWatchSessionSlot.set(session);
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Per-file content hashes of the last published analysis (relPath → hash of
|
|
397
|
+
* the exact bytes analyzed), including files that produced zero entries.
|
|
398
|
+
*/
|
|
399
|
+
function getAnalyzedHashes() {
|
|
400
|
+
return analyzedHashesSlot.get();
|
|
401
|
+
}
|
|
402
|
+
function setAnalyzedHashes(hashes) {
|
|
403
|
+
analyzedHashesSlot.set(hashes);
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Session identity is per process: the first caller claims a fresh id and
|
|
407
|
+
* every later one adopts it, so all compilers share one artifact tree.
|
|
408
|
+
*/
|
|
409
|
+
function claimProcessSessionId() {
|
|
410
|
+
const existing = singletonStore[PROCESS_SESSION_ID_KEY];
|
|
411
|
+
if (isSingletonString(existing) && existing.length > 0) return existing;
|
|
412
|
+
const fresh = (0, crypto.randomUUID)();
|
|
413
|
+
singletonStore[PROCESS_SESSION_ID_KEY] = fresh;
|
|
414
|
+
return fresh;
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Two CONCURRENT drive loops in one process would share one session tree and
|
|
418
|
+
* clobber each other; sequential claim/release cycles are legal.
|
|
419
|
+
*/
|
|
420
|
+
function claimExclusiveSessionOwner(label) {
|
|
421
|
+
const active = singletonStore[EXCLUSIVE_SESSION_OWNER_KEY];
|
|
422
|
+
if (isSingletonString(active)) throw new Error(`[animus] a second Animus host ("${label}") started while "${active}" is still active in this process. The extraction session is process-global (one session tree, one manifest), so concurrent hosts would clobber each other's analysis. Run one Animus-enabled config per process, or make the builds sequential.`);
|
|
423
|
+
singletonStore[EXCLUSIVE_SESSION_OWNER_KEY] = label;
|
|
424
|
+
return () => {
|
|
425
|
+
if (singletonStore[EXCLUSIVE_SESSION_OWNER_KEY] === label) delete singletonStore[EXCLUSIVE_SESSION_OWNER_KEY];
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Absolute artifact directory of the OWNING session's last publication. The
|
|
430
|
+
* Turbopack loader runs out of process and receives it via options instead.
|
|
431
|
+
*/
|
|
432
|
+
function getSessionArtifactDir() {
|
|
433
|
+
return sessionArtifactDirSlot.get();
|
|
434
|
+
}
|
|
435
|
+
function setSessionArtifactDir(dir) {
|
|
436
|
+
sessionArtifactDirSlot.set(dir);
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Replacement epoch of the last published analysis, written AFTER the
|
|
440
|
+
* manifest so an epoch reader always sees at least that generation's manifest.
|
|
441
|
+
*/
|
|
442
|
+
function getReplacementEpoch() {
|
|
443
|
+
return replacementEpochSlot.get();
|
|
444
|
+
}
|
|
445
|
+
function setReplacementEpoch(epoch) {
|
|
446
|
+
replacementEpochSlot.set(epoch);
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* The one in-flight watch-analysis transaction: a compiler that did not start
|
|
450
|
+
* it joins this promise rather than read a pre-transaction generation.
|
|
451
|
+
*/
|
|
452
|
+
function getWatchTransaction() {
|
|
453
|
+
return watchTransactionSlot.get();
|
|
454
|
+
}
|
|
455
|
+
function setWatchTransaction(transaction) {
|
|
456
|
+
watchTransactionSlot.set(transaction);
|
|
457
|
+
}
|
|
458
|
+
/** Engine selection travels through the singleton so non-owning compilers
|
|
459
|
+
* and the webpack loader honor the owner's choice. */
|
|
460
|
+
function setSharedEngine(engine) {
|
|
461
|
+
singletonStore[ENGINE_KEY] = engine;
|
|
462
|
+
}
|
|
463
|
+
function getSharedEngine() {
|
|
464
|
+
return singletonStore[ENGINE_KEY] || "v2";
|
|
465
|
+
}
|
|
466
|
+
/** Single choke-point for every native extraction call; the NAPI module's
|
|
467
|
+
* own `.d.ts` is the authoritative surface for what it returns. */
|
|
468
|
+
function requireEngine() {
|
|
469
|
+
return require("@animus-ui/extract");
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* The one engine API for this process: the instance, its analyze-time
|
|
473
|
+
* sources, and the drift flag live on globalThis so both copies share them.
|
|
474
|
+
*/
|
|
475
|
+
const v2EngineApi = require_pipeline.createV2EngineApi({
|
|
476
|
+
label: "animus",
|
|
477
|
+
isV2: () => getSharedEngine() === "v2",
|
|
478
|
+
loadNativeEngine: requireEngine,
|
|
479
|
+
passThroughUnknownPaths: true,
|
|
480
|
+
store: {
|
|
481
|
+
getEngine: () => singletonStore[V2_ENGINE_KEY],
|
|
482
|
+
setEngine: (engine) => {
|
|
483
|
+
singletonStore[V2_ENGINE_KEY] = engine;
|
|
484
|
+
},
|
|
485
|
+
getSentSources: () => singletonStore[V2_SENT_SOURCES_KEY] ?? null,
|
|
486
|
+
setSentSources: (sources) => {
|
|
487
|
+
singletonStore[V2_SENT_SOURCES_KEY] = sources;
|
|
488
|
+
},
|
|
489
|
+
getDriftWarned: () => Boolean(singletonStore[V2_DRIFT_WARNED_KEY]),
|
|
490
|
+
setDriftWarned: (value) => {
|
|
491
|
+
singletonStore[V2_DRIFT_WARNED_KEY] = value;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
function engineApi() {
|
|
496
|
+
const override = singletonStore[ENGINE_API_OVERRIDE_KEY];
|
|
497
|
+
return override ? override() : v2EngineApi();
|
|
498
|
+
}
|
|
499
|
+
//#endregion
|
|
500
|
+
//#region session/timing.ts
|
|
501
|
+
function logBuildTimings(bt, rustTiming, log, verbose) {
|
|
502
|
+
if (!verbose) return;
|
|
503
|
+
const pad = (label, width) => label + " ".repeat(Math.max(0, width - label.length));
|
|
504
|
+
for (const [key, label] of [
|
|
505
|
+
["systemLoad", "system-load"],
|
|
506
|
+
["fileDiscovery", "file-discovery"],
|
|
507
|
+
["fileRead", "file-read+hash"],
|
|
508
|
+
["packageResolve", "pkg-resolve"],
|
|
509
|
+
["analysis", "analysis"]
|
|
510
|
+
]) {
|
|
511
|
+
const ms = bt[key] ?? 0;
|
|
512
|
+
if (ms === 0 && !Object.hasOwn(bt, key)) continue;
|
|
513
|
+
log(` ${pad(label, 17)}${String(ms).padStart(5)}ms`);
|
|
514
|
+
if (key === "analysis") for (const [sk, sl] of [
|
|
515
|
+
["jsonSerialize", "json-serialize"],
|
|
516
|
+
["rustExtract", "rust-extract"],
|
|
517
|
+
["jsonParse", "json-parse"]
|
|
518
|
+
]) {
|
|
519
|
+
const sms = bt[sk] ?? 0;
|
|
520
|
+
log(` ${pad(sl, 15)}${String(sms).padStart(5)}ms`);
|
|
521
|
+
if (sk === "rustExtract" && rustTiming) for (const line of require_pipeline.formatRustTimingWaterfall(rustTiming, {
|
|
522
|
+
indent: " ",
|
|
523
|
+
labelWidth: 13
|
|
524
|
+
})) log(line);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
log(` total ${String(bt.total).padStart(5)}ms`);
|
|
528
|
+
if (process.env.ANIMUS_TIMING_JSON === "1") {
|
|
529
|
+
const merged = {};
|
|
530
|
+
for (const [k, v] of Object.entries(bt)) merged[`buildStart.${k}`] = v;
|
|
531
|
+
if (rustTiming) for (const [k, v] of Object.entries(rustTiming)) merged[`rust.${k}`] = v;
|
|
532
|
+
console.error(`[animus:timing] ${JSON.stringify(merged)}`);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
//#endregion
|
|
536
|
+
//#region session/extraction-session.ts
|
|
537
|
+
const SESSION_DIR_MAX_AGE_MS = 864e5;
|
|
538
|
+
const STATUS_WATCHDOG_MS = 2e3;
|
|
539
|
+
const LEGACY_FLAT_ARTIFACTS = [
|
|
540
|
+
MANIFEST_ARTIFACT,
|
|
541
|
+
ANALYSIS_INPUTS_ARTIFACT,
|
|
542
|
+
STYLES_ARTIFACT,
|
|
543
|
+
SYSTEM_PROPS_ARTIFACT,
|
|
544
|
+
REPLACEMENT_EPOCH_ARTIFACT,
|
|
545
|
+
ANALYSIS_COMMIT_ARTIFACT,
|
|
546
|
+
ANALYSIS_STATUS_ARTIFACT
|
|
547
|
+
];
|
|
548
|
+
/** Brands values read back off DISK. `Object(value) !== value` rejects
|
|
549
|
+
* boxed primitives; the tag is unspoofable by `Symbol.toStringTag`. */
|
|
550
|
+
function isDiskString(value) {
|
|
551
|
+
return Object(value) !== value && Object.prototype.toString.call(value) === "[object String]";
|
|
552
|
+
}
|
|
553
|
+
function isDiskNumber(value) {
|
|
554
|
+
return Object(value) !== value && Object.prototype.toString.call(value) === "[object Number]";
|
|
555
|
+
}
|
|
556
|
+
/** Bundler-agnostic extraction pipeline: system load, source ingestion,
|
|
557
|
+
* analysis, emission, and watch-cycle diffing. Carries no bundler types. */
|
|
558
|
+
var ExtractionSession = class {
|
|
559
|
+
/** Set by the adapter before any pipeline run. */
|
|
560
|
+
rootDir = null;
|
|
561
|
+
/** Serialized path aliases, harvested by the adapter from bundler config. */
|
|
562
|
+
pathAliasesJson = null;
|
|
563
|
+
assetCopyCache = /* @__PURE__ */ new Map();
|
|
564
|
+
/** Physical asset files registered with the host watcher. */
|
|
565
|
+
assetDependencyPaths = /* @__PURE__ */ new Set();
|
|
566
|
+
assetDependencyKeys = /* @__PURE__ */ new Set();
|
|
567
|
+
/** System-props module id override. Turbopack rejects absolute-path
|
|
568
|
+
* imports, so its driver sets a virtual id; null injects the abs path. */
|
|
569
|
+
systemPropsModuleId = null;
|
|
570
|
+
/** Whether the analysis-inputs hydration corpus is written to disk. Only
|
|
571
|
+
* isolated loader workers replay it; an in-process loader reads memory. */
|
|
572
|
+
persistAnalysisInputs = false;
|
|
573
|
+
/** Absolute directory prefixes for external packages (loader allowlist). */
|
|
574
|
+
externalPackageDirs = [];
|
|
575
|
+
/** Absolute package dir → owning specifier (cross-source correlation). */
|
|
576
|
+
externalDirOwners = {};
|
|
577
|
+
/** rootDir-relative external file → owning specifier (correlation join). */
|
|
578
|
+
externalFileOwners = {};
|
|
579
|
+
/** External package specifier → absolute source entry path. */
|
|
580
|
+
externalSourceEntries = /* @__PURE__ */ new Map();
|
|
581
|
+
/** SourceId authority for this generation — roots realpath'd once per
|
|
582
|
+
* pipeline; aliases recorded while files exist so deletions resolve. */
|
|
583
|
+
sourceIdentity = null;
|
|
584
|
+
/** Canonical admitted external root → declared specifiers; duplicate
|
|
585
|
+
* specifiers resolving to one canonical root share an entry. */
|
|
586
|
+
externalRootOwners = /* @__PURE__ */ new Map();
|
|
587
|
+
/** Canonical external root → the extension set its collection walk used
|
|
588
|
+
* (widened for dist-only roots); rewalks and classification share it. */
|
|
589
|
+
externalRootExtensions = /* @__PURE__ */ new Map();
|
|
590
|
+
/** Canonical admitted external watch roots — the engine-side watch
|
|
591
|
+
* surface (webpack contextDependencies / Turbopack watchers). */
|
|
592
|
+
externalWatchRoots = [];
|
|
593
|
+
/** Canonical external root → sourceKey → {raw content hash, recorded abs}:
|
|
594
|
+
* the inventory a dirty-root rewalk diffs against, hashed before MDX. */
|
|
595
|
+
externalInventory = /* @__PURE__ */ new Map();
|
|
596
|
+
/** Stable key → message, re-surfaced on every full pipeline until the
|
|
597
|
+
* underlying condition clears. */
|
|
598
|
+
stickyDiagnostics = /* @__PURE__ */ new Map();
|
|
599
|
+
/** Volume-membership predicate for the cross-volume gate. Injected seam:
|
|
600
|
+
* the real predicate only discriminates on Windows, so tests replace it. */
|
|
601
|
+
sharesProjectVolume = require_pipeline.sharesVolumeRoot;
|
|
602
|
+
/** `onExternalRootResolved` fires per admitted root DURING collection,
|
|
603
|
+
* before its walk; `onExternalRootsCommitted` only after publication. */
|
|
604
|
+
onExternalRootResolved = null;
|
|
605
|
+
onExternalRootsCommitted = null;
|
|
606
|
+
/** Fires exactly once per settled `handleWatchUpdate` — the failure cause,
|
|
607
|
+
* or null when clean. Observation never alters delivery. */
|
|
608
|
+
onCycleSettled = null;
|
|
609
|
+
options;
|
|
610
|
+
staticCssJson;
|
|
611
|
+
system = null;
|
|
612
|
+
/** Vocabulary witness diagnostics from the sealed system's registration
|
|
613
|
+
* record, awaiting the shared surfacing pass. */
|
|
614
|
+
systemVocabularyDiagnostics = [];
|
|
615
|
+
/** Configured external files that could not be read. Incremental passes
|
|
616
|
+
* re-collect nothing, so they replay these instead of passing strict. */
|
|
617
|
+
ingestionFailureDiagnostics = [];
|
|
618
|
+
/** Full package-resolution map from the last full pipeline — replayed by
|
|
619
|
+
* incremental passes (sourceEntries alone omits dist-resolved packages). */
|
|
620
|
+
lastPackageMap = {};
|
|
621
|
+
fileCache = /* @__PURE__ */ new Map();
|
|
622
|
+
corpus = require_pipeline.createSourceCorpus({
|
|
623
|
+
engineApi: () => engineApi(),
|
|
624
|
+
prefix: "[animus-next]",
|
|
625
|
+
strict: () => !!this.options.strict,
|
|
626
|
+
warn: (message) => this.warn(message)
|
|
627
|
+
});
|
|
628
|
+
systemDependencyKeys = /* @__PURE__ */ new Set();
|
|
629
|
+
/** Loader-reported dependency paths for bundler watch registration. */
|
|
630
|
+
systemDependencyPaths = [];
|
|
631
|
+
systemDependencyHashes = /* @__PURE__ */ new Map();
|
|
632
|
+
lastSystemPropsHash = null;
|
|
633
|
+
artifactRecords = {
|
|
634
|
+
manifest: null,
|
|
635
|
+
inputs: null,
|
|
636
|
+
styles: null
|
|
637
|
+
};
|
|
638
|
+
/** Last written analysis-commit; null seeds from disk on first use. Its
|
|
639
|
+
* generation counter is correct only because one session owns the dir. */
|
|
640
|
+
lastCommit = null;
|
|
641
|
+
/** Release handle for the in-process owner slot AND the directory's
|
|
642
|
+
* on-disk owner record, taken and released as one; null when unheld. */
|
|
643
|
+
releasePublicationClaim = null;
|
|
644
|
+
/** Session identity, claimed once per PROCESS and adopted by every later
|
|
645
|
+
* session instance so all compilers share one artifact tree. */
|
|
646
|
+
sessionId = claimProcessSessionId();
|
|
647
|
+
/** Epoch of the last published analysis; null seeds from disk so a
|
|
648
|
+
* same-session restart with unchanged plans rewrites no bytes. */
|
|
649
|
+
lastEpochValue = null;
|
|
650
|
+
/** Watcher debounce ceiling feeding status deadlines. */
|
|
651
|
+
debounceCeilingMs = 75;
|
|
652
|
+
/** Test seam: observes every session-artifact write (name, content)
|
|
653
|
+
* post-rename — write ORDER is part of the transaction contract. */
|
|
654
|
+
onArtifactWrite = null;
|
|
655
|
+
statusAttemptId = 0;
|
|
656
|
+
statusAttemptOpen = false;
|
|
657
|
+
/** Monotonic readiness witness: flips true on the first complete
|
|
658
|
+
* publication and never regresses; every later status write carries it. */
|
|
659
|
+
firstEmissionComplete = false;
|
|
660
|
+
/** Debounce-window observations pending analysis (sourceKey → hash). */
|
|
661
|
+
debouncePending = /* @__PURE__ */ new Map();
|
|
662
|
+
sessionStartHygieneDone = false;
|
|
663
|
+
lcssTargets = null;
|
|
664
|
+
constructor(options) {
|
|
665
|
+
this.options = options;
|
|
666
|
+
this.staticCssJson = require_pipeline.serializeStaticCss(options.staticCss);
|
|
667
|
+
}
|
|
668
|
+
get sessionDir() {
|
|
669
|
+
return sessionArtifactDir(this.rootDir, this.sessionId);
|
|
670
|
+
}
|
|
671
|
+
get verbose() {
|
|
672
|
+
return this.options.verbose === true || process.env.ANIMUS_DEBUG === "1" || process.env.ANIMUS_DEBUG === "true";
|
|
673
|
+
}
|
|
674
|
+
log(msg) {
|
|
675
|
+
if (this.verbose) console.error(`[animus] ${msg}`);
|
|
676
|
+
}
|
|
677
|
+
warn(msg) {
|
|
678
|
+
console.warn(`[animus] ${msg}`);
|
|
679
|
+
}
|
|
680
|
+
now() {
|
|
681
|
+
return this.verbose ? performance.now() : 0;
|
|
682
|
+
}
|
|
683
|
+
elapsed(t) {
|
|
684
|
+
return this.verbose ? Math.round(performance.now() - t) : 0;
|
|
685
|
+
}
|
|
686
|
+
/** Diagnostic prefix label — drivers set their own so one host's errors
|
|
687
|
+
* never masquerade as another driver's. */
|
|
688
|
+
driverLabel = "animus-next";
|
|
689
|
+
/** Per-specifier discovery outcomes from the last full collection —
|
|
690
|
+
* driver-consumed reporting surface (the CLI's summary). */
|
|
691
|
+
lastExternalOutcomes = [];
|
|
692
|
+
/** Component count of the last published analysis, null before the first —
|
|
693
|
+
* read by drivers instead of re-parsing the manifest JSON every cycle. */
|
|
694
|
+
lastComponentCount = null;
|
|
695
|
+
/** When superseded asset copies leave `assets/`: `'full-pipeline'` suits a
|
|
696
|
+
* driver serving the dir in place, `'every-cycle'` one republishing it. */
|
|
697
|
+
staleAssetPruning = "full-pipeline";
|
|
698
|
+
/** Driver-owned STRUCTURAL exclusions, joined to the never-replaceable set
|
|
699
|
+
* — never the user list, whose presence flips the replace semantics. */
|
|
700
|
+
structuralExclude = [];
|
|
701
|
+
scanConfigMemo = null;
|
|
702
|
+
/** Per-pattern exclusion hit counts from this session's matcher —
|
|
703
|
+
* driver-consumed reporting surface (dead-pattern visibility). */
|
|
704
|
+
getExcludeStats() {
|
|
705
|
+
return this.resolveScanConfig().excludeMatcher.stats();
|
|
706
|
+
}
|
|
707
|
+
/** Resolve the scan configuration from options — the single source of the
|
|
708
|
+
* exclude/extension policy shared by the full and incremental pipelines. */
|
|
709
|
+
resolveScanConfig() {
|
|
710
|
+
if (this.scanConfigMemo === null) {
|
|
711
|
+
const extensionsSet = new Set(this.options.extensions ?? require_pipeline.DEFAULT_EXTENSIONS);
|
|
712
|
+
this.scanConfigMemo = {
|
|
713
|
+
excludeMatcher: require_pipeline.createExcludeMatcher(this.options.exclude, this.structuralExclude),
|
|
714
|
+
extensionsSet
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
return this.scanConfigMemo;
|
|
718
|
+
}
|
|
719
|
+
/** One single-flight analysis transaction per event batch: concurrent
|
|
720
|
+
* entries join the in-flight promise; each settled entry reports outcome. */
|
|
721
|
+
async handleWatchUpdate(changes) {
|
|
722
|
+
try {
|
|
723
|
+
await this.routeWatchUpdate(changes);
|
|
724
|
+
} catch (error) {
|
|
725
|
+
this.onCycleSettled?.(error);
|
|
726
|
+
throw error;
|
|
727
|
+
}
|
|
728
|
+
this.onCycleSettled?.(null);
|
|
729
|
+
}
|
|
730
|
+
async routeWatchUpdate(changes) {
|
|
731
|
+
const inflight = getWatchTransaction();
|
|
732
|
+
if (inflight) {
|
|
733
|
+
await inflight;
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
if (!this.system) {
|
|
737
|
+
const owner = getOwningWatchSession();
|
|
738
|
+
const hasBatch = (changes.modifiedFiles?.size ?? 0) > 0 || (changes.removedFiles?.size ?? 0) > 0;
|
|
739
|
+
if (owner && owner !== this && hasBatch) await owner.ingestForwardedBatch(changes);
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
const transaction = this.processWatchUpdate(changes);
|
|
743
|
+
setWatchTransaction(transaction);
|
|
744
|
+
try {
|
|
745
|
+
await transaction;
|
|
746
|
+
} finally {
|
|
747
|
+
setWatchTransaction(null);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
/** Owner-side entry for a batch a non-owning compiler observed. Serializes
|
|
751
|
+
* behind any in-flight transaction — it has no other delivery. */
|
|
752
|
+
async ingestForwardedBatch(changes) {
|
|
753
|
+
if (!this.system) return;
|
|
754
|
+
for (;;) {
|
|
755
|
+
const inflight = getWatchTransaction();
|
|
756
|
+
if (!inflight) break;
|
|
757
|
+
await inflight;
|
|
758
|
+
}
|
|
759
|
+
const transaction = this.processWatchUpdate(changes);
|
|
760
|
+
setWatchTransaction(transaction);
|
|
761
|
+
try {
|
|
762
|
+
await transaction;
|
|
763
|
+
} finally {
|
|
764
|
+
setWatchTransaction(null);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
async processWatchUpdate(changes) {
|
|
768
|
+
const rootDir = this.rootDir;
|
|
769
|
+
try {
|
|
770
|
+
changes = this.reconcileExternalRoots(changes);
|
|
771
|
+
} catch (err) {
|
|
772
|
+
this.warn(`external root reconciliation failed: ${String(err)}`);
|
|
773
|
+
}
|
|
774
|
+
const changed = [...changes.modifiedFiles ?? [], ...changes.removedFiles ?? []];
|
|
775
|
+
let assetChanged = false;
|
|
776
|
+
let systemHit;
|
|
777
|
+
try {
|
|
778
|
+
if (!changes.modifiedFiles && !changes.removedFiles) for (const [dep, hash] of this.systemDependencyHashes) {
|
|
779
|
+
let current = "";
|
|
780
|
+
try {
|
|
781
|
+
current = require_pipeline.contentHash((0, fs.readFileSync)(dep, "utf-8"));
|
|
782
|
+
} catch {}
|
|
783
|
+
if (current !== hash) {
|
|
784
|
+
systemHit = dep;
|
|
785
|
+
break;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
else systemHit = changed.find((path$2) => require_pipeline.toWatchKeys(path$2).some((key) => this.systemDependencyKeys.has(key)));
|
|
789
|
+
} catch (err) {
|
|
790
|
+
this.warn(`HMR system-reload check failed: ${String(err)}`);
|
|
791
|
+
}
|
|
792
|
+
if (systemHit) {
|
|
793
|
+
this.log(`system reload: system dependency changed (${(0, path.relative)(rootDir, systemHit)})`);
|
|
794
|
+
this.resetForHmr();
|
|
795
|
+
try {
|
|
796
|
+
const promise = this.runFullPipeline(this.pendingFromBatch(changes));
|
|
797
|
+
setAnalysisStartedPromise(promise);
|
|
798
|
+
await promise;
|
|
799
|
+
} catch (err) {
|
|
800
|
+
try {
|
|
801
|
+
this.beginStatusAttempt();
|
|
802
|
+
this.writeAnalysisStatus("failed", this.pendingFromBatch(changes), String(err));
|
|
803
|
+
} catch (statusErr) {
|
|
804
|
+
this.warn(`failed-status write failed: ${String(statusErr)}`);
|
|
805
|
+
}
|
|
806
|
+
throw err;
|
|
807
|
+
}
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
if (changed.some((path$3) => require_pipeline.toWatchKeys(path$3).some((key) => this.assetDependencyKeys.has(key)))) {
|
|
811
|
+
this.assetCopyCache.clear();
|
|
812
|
+
assetChanged = true;
|
|
813
|
+
}
|
|
814
|
+
const { excludeMatcher, extensionsSet } = this.resolveScanConfig();
|
|
815
|
+
const priorCacheEntries = /* @__PURE__ */ new Map();
|
|
816
|
+
const priorExternalFileOwners = { ...this.externalFileOwners };
|
|
817
|
+
const recordPrior = (key) => {
|
|
818
|
+
if (!priorCacheEntries.has(key)) priorCacheEntries.set(key, this.fileCache.get(key) ?? null);
|
|
819
|
+
};
|
|
820
|
+
const inventoryUpdates = [];
|
|
821
|
+
const ownerRemovals = [];
|
|
822
|
+
let removedAny = false;
|
|
823
|
+
if (changes.removedFiles) for (const removedPath of changes.removedFiles) {
|
|
824
|
+
let key;
|
|
825
|
+
let owningRoot = null;
|
|
826
|
+
if (require_pipeline.isPathWithinRoot(rootDir, removedPath)) key = (0, path.relative)(rootDir, removedPath);
|
|
827
|
+
else {
|
|
828
|
+
const resolved = this.sourceIdentity?.resolveDeletedSourceId(removedPath);
|
|
829
|
+
if (!resolved) continue;
|
|
830
|
+
key = resolved.sourceKey;
|
|
831
|
+
owningRoot = resolved.owningRoot;
|
|
832
|
+
}
|
|
833
|
+
recordPrior(key);
|
|
834
|
+
if (this.fileCache.delete(key)) {
|
|
835
|
+
removedAny = true;
|
|
836
|
+
if (owningRoot) {
|
|
837
|
+
inventoryUpdates.push({
|
|
838
|
+
root: owningRoot,
|
|
839
|
+
key,
|
|
840
|
+
hash: null,
|
|
841
|
+
abs: removedPath
|
|
842
|
+
});
|
|
843
|
+
ownerRemovals.push(key, key + ".tsx");
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
let targets;
|
|
848
|
+
if (changes.modifiedFiles) {
|
|
849
|
+
targets = [];
|
|
850
|
+
for (const modifiedPath of changes.modifiedFiles) {
|
|
851
|
+
const classified = this.classifyWatchPath(modifiedPath, {
|
|
852
|
+
excludeMatcher,
|
|
853
|
+
extensionsSet
|
|
854
|
+
});
|
|
855
|
+
if (classified) targets.push({
|
|
856
|
+
abs: modifiedPath,
|
|
857
|
+
...classified
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
} else targets = require_pipeline.discoverFiles(rootDir, rootDir, excludeMatcher, extensionsSet).map((abs) => ({
|
|
861
|
+
abs,
|
|
862
|
+
key: (0, path.relative)(rootDir, abs),
|
|
863
|
+
owningRoot: null
|
|
864
|
+
}));
|
|
865
|
+
const changedPaths = [];
|
|
866
|
+
for (const target of targets) {
|
|
867
|
+
const relPath = target.key;
|
|
868
|
+
let source;
|
|
869
|
+
try {
|
|
870
|
+
source = (0, fs.readFileSync)(target.abs, "utf-8");
|
|
871
|
+
} catch {
|
|
872
|
+
continue;
|
|
873
|
+
}
|
|
874
|
+
const rawHash = require_pipeline.contentHash(source);
|
|
875
|
+
const cached = this.fileCache.get(relPath);
|
|
876
|
+
const hash = rawHash;
|
|
877
|
+
if (!cached || cached.hash !== hash) {
|
|
878
|
+
changedPaths.push(relPath);
|
|
879
|
+
recordPrior(relPath);
|
|
880
|
+
this.fileCache.set(relPath, {
|
|
881
|
+
hash,
|
|
882
|
+
source
|
|
883
|
+
});
|
|
884
|
+
if (target.owningRoot) {
|
|
885
|
+
const owner = this.externalRootOwners.get(target.owningRoot)?.values().next().value;
|
|
886
|
+
if (owner !== void 0) this.externalFileOwners[relPath] ??= owner;
|
|
887
|
+
inventoryUpdates.push({
|
|
888
|
+
root: target.owningRoot,
|
|
889
|
+
key: target.key,
|
|
890
|
+
hash: rawHash,
|
|
891
|
+
abs: target.abs
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
if (changedPaths.length > 0 || removedAny || assetChanged) {
|
|
897
|
+
const pending = changedPaths.map((rel) => [rel, this.fileCache.get(rel).hash]);
|
|
898
|
+
let ingested;
|
|
899
|
+
try {
|
|
900
|
+
this.beginStatusAttempt();
|
|
901
|
+
ingested = await this.corpus.prepare({
|
|
902
|
+
fileCache: this.fileCache,
|
|
903
|
+
externalFileOwners: this.externalFileOwners
|
|
904
|
+
});
|
|
905
|
+
this.externalFileOwners = require_pipeline.projectExternalFileOwners(ingested, this.externalFileOwners);
|
|
906
|
+
resetAnalysisStartedPromise();
|
|
907
|
+
const promise = this.runIncrementalPipeline(ingested.analysisEntries, pending);
|
|
908
|
+
setAnalysisStartedPromise(promise);
|
|
909
|
+
await promise;
|
|
910
|
+
} catch (err) {
|
|
911
|
+
for (const [key, prior] of priorCacheEntries) if (prior === null) this.fileCache.delete(key);
|
|
912
|
+
else this.fileCache.set(key, prior);
|
|
913
|
+
this.externalFileOwners = priorExternalFileOwners;
|
|
914
|
+
if (this.statusAttemptOpen) {
|
|
915
|
+
this.debouncePending.clear();
|
|
916
|
+
this.writeAnalysisStatus("failed", pending, String(err));
|
|
917
|
+
}
|
|
918
|
+
throw err;
|
|
919
|
+
}
|
|
920
|
+
this.publishSourceIngestion(ingested);
|
|
921
|
+
for (const update of inventoryUpdates) {
|
|
922
|
+
let inventory = this.externalInventory.get(update.root);
|
|
923
|
+
if (!inventory) {
|
|
924
|
+
inventory = /* @__PURE__ */ new Map();
|
|
925
|
+
this.externalInventory.set(update.root, inventory);
|
|
926
|
+
}
|
|
927
|
+
if (update.hash === null) inventory.delete(update.key);
|
|
928
|
+
else inventory.set(update.key, {
|
|
929
|
+
hash: update.hash,
|
|
930
|
+
abs: update.abs
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
for (const key of ownerRemovals) delete this.externalFileOwners[key];
|
|
934
|
+
} else if (this.statusAttemptOpen) {
|
|
935
|
+
this.debouncePending.clear();
|
|
936
|
+
this.writeAnalysisStatus("idle", []);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
publishSourceIngestion(result) {
|
|
940
|
+
this.fileCache = new Map(result.originalEntries.map((entry) => [entry.path, {
|
|
941
|
+
hash: entry.hash,
|
|
942
|
+
source: entry.source
|
|
943
|
+
}]));
|
|
944
|
+
this.corpus.publish(result);
|
|
945
|
+
}
|
|
946
|
+
/** Full analysis + publication, registered in the one in-flight
|
|
947
|
+
* transaction slot; a nested reload leaves the enclosing registration. */
|
|
948
|
+
async runFullPipeline(pending = []) {
|
|
949
|
+
if (this.releasePublicationClaim === null) {
|
|
950
|
+
const releaseOwner = claimExclusiveSessionOwner(`${this.driverLabel}:${this.sessionDir}`);
|
|
951
|
+
(0, fs.mkdirSync)(this.sessionDir, { recursive: true });
|
|
952
|
+
const releaseClaimRecord = holdDirectoryClaim(this.sessionDir);
|
|
953
|
+
this.releasePublicationClaim = () => {
|
|
954
|
+
releaseOwner();
|
|
955
|
+
releaseClaimRecord();
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
const priorSystem = this.system;
|
|
959
|
+
const nested = getWatchTransaction() !== null;
|
|
960
|
+
const transaction = this.runPipelineTransaction(pending);
|
|
961
|
+
if (!nested) setWatchTransaction(transaction);
|
|
962
|
+
try {
|
|
963
|
+
await transaction;
|
|
964
|
+
} catch (error) {
|
|
965
|
+
this.system = priorSystem;
|
|
966
|
+
throw error;
|
|
967
|
+
} finally {
|
|
968
|
+
if (!nested) setWatchTransaction(null);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
async runPipelineTransaction(pending) {
|
|
972
|
+
const pipelineStart = this.now();
|
|
973
|
+
const bt = {};
|
|
974
|
+
this.runSessionStartHygiene();
|
|
975
|
+
setSessionArtifactDir(this.sessionDir);
|
|
976
|
+
const rootDir = this.rootDir;
|
|
977
|
+
const priorSourceState = this.snapshotSourceState();
|
|
978
|
+
const resolvedSystemPath = (0, path.resolve)(rootDir, this.options.system);
|
|
979
|
+
let t = this.now();
|
|
980
|
+
this.system = require_pipeline.loadSystemConfig(engineApi, {
|
|
981
|
+
systemPath: resolvedSystemPath,
|
|
982
|
+
rootDir,
|
|
983
|
+
prefix: this.options.prefix
|
|
984
|
+
});
|
|
985
|
+
this.systemVocabularyDiagnostics = require_pipeline.vocabularyWitnessDiagnostics(this.system.vocabularyWitnessesJson);
|
|
986
|
+
this.assetCopyCache.clear();
|
|
987
|
+
this.assetDependencyPaths.clear();
|
|
988
|
+
this.assetDependencyKeys.clear();
|
|
989
|
+
{
|
|
990
|
+
const deps = this.system.dependencies ?? [];
|
|
991
|
+
const keys = /* @__PURE__ */ new Set();
|
|
992
|
+
for (const key of require_pipeline.toWatchKeys(resolvedSystemPath)) keys.add(key);
|
|
993
|
+
for (const dep of deps) for (const key of require_pipeline.toWatchKeys(dep)) keys.add(key);
|
|
994
|
+
this.systemDependencyKeys = keys;
|
|
995
|
+
this.systemDependencyPaths = deps;
|
|
996
|
+
const hashes = /* @__PURE__ */ new Map();
|
|
997
|
+
for (const dep of deps.length > 0 ? deps : [resolvedSystemPath]) try {
|
|
998
|
+
hashes.set(dep, require_pipeline.contentHash((0, fs.readFileSync)(dep, "utf-8")));
|
|
999
|
+
} catch {
|
|
1000
|
+
hashes.set(dep, "");
|
|
1001
|
+
}
|
|
1002
|
+
this.systemDependencyHashes = hashes;
|
|
1003
|
+
}
|
|
1004
|
+
bt.systemLoad = this.elapsed(t);
|
|
1005
|
+
t = this.now();
|
|
1006
|
+
const { excludeMatcher, extensionsSet } = this.resolveScanConfig();
|
|
1007
|
+
const files = require_pipeline.discoverFiles(rootDir, rootDir, excludeMatcher, extensionsSet);
|
|
1008
|
+
bt.fileDiscovery = this.elapsed(t);
|
|
1009
|
+
t = this.now();
|
|
1010
|
+
const rawEntries = [];
|
|
1011
|
+
for (const filePath of files) {
|
|
1012
|
+
const source = (0, fs.readFileSync)(filePath, "utf-8");
|
|
1013
|
+
const relPath = (0, path.relative)(rootDir, filePath);
|
|
1014
|
+
const hash = require_pipeline.contentHash(source);
|
|
1015
|
+
rawEntries.push({
|
|
1016
|
+
path: relPath,
|
|
1017
|
+
source,
|
|
1018
|
+
hash
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
bt.fileRead = this.elapsed(t);
|
|
1022
|
+
t = this.now();
|
|
1023
|
+
const packageNames = require_pipeline.extractSystemFilePackages(resolvedSystemPath);
|
|
1024
|
+
const preResolved = resolvePackagesByName(rootDir, packageNames);
|
|
1025
|
+
const rawExternalFiles = /* @__PURE__ */ new Map();
|
|
1026
|
+
const ingestionFailures = [];
|
|
1027
|
+
const collected = await require_pipeline.collectExternalPackageSources({
|
|
1028
|
+
specifiers: packageNames,
|
|
1029
|
+
resolveSpecifier: (name) => preResolved[name] ? (0, path.resolve)(rootDir, preResolved[name]) : null,
|
|
1030
|
+
rootDir,
|
|
1031
|
+
extensionsSet,
|
|
1032
|
+
hasEntry: (relPath) => rawEntries.some((e) => e.path === relPath),
|
|
1033
|
+
onSourceRead: (source, _relPath, absPath) => {
|
|
1034
|
+
rawExternalFiles.set(absPath, require_pipeline.contentHash(source));
|
|
1035
|
+
},
|
|
1036
|
+
onUnreadable: (relPath, err) => ingestionFailures.push(require_pipeline.unreadableSourceDiagnostic(relPath, err)),
|
|
1037
|
+
onPackageResolved: (_specifier, packageDir) => {
|
|
1038
|
+
if (!this.onExternalRootResolved) return;
|
|
1039
|
+
if (!this.sharesProjectVolume(rootDir, packageDir)) return;
|
|
1040
|
+
let canonical = packageDir;
|
|
1041
|
+
try {
|
|
1042
|
+
canonical = (0, fs.realpathSync)(packageDir);
|
|
1043
|
+
} catch {}
|
|
1044
|
+
this.onExternalRootResolved(canonical);
|
|
1045
|
+
}
|
|
1046
|
+
});
|
|
1047
|
+
this.lastExternalOutcomes = collected.outcomes;
|
|
1048
|
+
this.ingestionFailureDiagnostics = ingestionFailures;
|
|
1049
|
+
for (const record of collected.outcomes) if (record.outcome === "empty") this.warn(`include '${record.specifier}' resolved but discovered no component sources`);
|
|
1050
|
+
const unresolvableMessage = require_pipeline.unresolvableIncludesMessage(collected.outcomes);
|
|
1051
|
+
if (unresolvableMessage !== null) {
|
|
1052
|
+
if (this.options.strict) throw new Error(unresolvableMessage);
|
|
1053
|
+
this.warn(unresolvableMessage);
|
|
1054
|
+
}
|
|
1055
|
+
const staleDistMessage = require_pipeline.staleDistIncludesMessage(collected.outcomes);
|
|
1056
|
+
if (staleDistMessage !== null) {
|
|
1057
|
+
if (this.options.strict) throw new Error(staleDistMessage);
|
|
1058
|
+
this.warn(staleDistMessage);
|
|
1059
|
+
}
|
|
1060
|
+
const rejectedSpecifiers = /* @__PURE__ */ new Set();
|
|
1061
|
+
const crossVolumeDetails = [];
|
|
1062
|
+
for (const [dir, specifiers] of Object.entries(collected.dirOwnerSets)) {
|
|
1063
|
+
if (this.sharesProjectVolume(rootDir, dir)) continue;
|
|
1064
|
+
for (const specifier of specifiers) rejectedSpecifiers.add(specifier);
|
|
1065
|
+
crossVolumeDetails.push(`${specifiers.join(", ")} → ${dir}`);
|
|
1066
|
+
}
|
|
1067
|
+
this.stickyDiagnostics.delete("cross-volume");
|
|
1068
|
+
if (rejectedSpecifiers.size > 0) {
|
|
1069
|
+
const message = `ANIMUS_EXTERNAL_CROSS_VOLUME_UNSUPPORTED: external package source root(s) on a different volume than the project root (${rootDir}): ${crossVolumeDetails.join("; ")} — cross-volume workspace sources are unsupported; the package(s) are excluded from extraction and watching`;
|
|
1070
|
+
if (this.options.strict) throw new Error(`[${this.driverLabel}] ${message}`);
|
|
1071
|
+
this.stickyDiagnostics.set("cross-volume", message);
|
|
1072
|
+
}
|
|
1073
|
+
for (const message of this.stickyDiagnostics.values()) this.warn(message);
|
|
1074
|
+
const admitted = require_pipeline.excludeCollectedPackages(collected, rejectedSpecifiers, rootDir);
|
|
1075
|
+
try {
|
|
1076
|
+
const identity = require_pipeline.createSourceIdentity(rootDir);
|
|
1077
|
+
this.externalRootOwners = /* @__PURE__ */ new Map();
|
|
1078
|
+
this.externalRootExtensions = /* @__PURE__ */ new Map();
|
|
1079
|
+
for (const [dir, specifiers] of Object.entries(admitted.dirOwnerSets)) {
|
|
1080
|
+
const canonical = identity.registerExternalRoot(dir);
|
|
1081
|
+
const owners = this.externalRootOwners.get(canonical) ?? /* @__PURE__ */ new Set();
|
|
1082
|
+
for (const specifier of specifiers) owners.add(specifier);
|
|
1083
|
+
this.externalRootOwners.set(canonical, owners);
|
|
1084
|
+
const exts = admitted.dirExtensions[dir];
|
|
1085
|
+
if (exts) this.externalRootExtensions.set(canonical, new Set(exts));
|
|
1086
|
+
}
|
|
1087
|
+
this.sourceIdentity = identity;
|
|
1088
|
+
this.externalWatchRoots = identity.externalRoots();
|
|
1089
|
+
this.externalInventory = /* @__PURE__ */ new Map();
|
|
1090
|
+
for (const [absPath, rawHash] of rawExternalFiles) {
|
|
1091
|
+
const resolved = identity.resolveSourceId(absPath);
|
|
1092
|
+
if (!resolved?.owningRoot) continue;
|
|
1093
|
+
let inventory = this.externalInventory.get(resolved.owningRoot);
|
|
1094
|
+
if (!inventory) {
|
|
1095
|
+
inventory = /* @__PURE__ */ new Map();
|
|
1096
|
+
this.externalInventory.set(resolved.owningRoot, inventory);
|
|
1097
|
+
}
|
|
1098
|
+
inventory.set(resolved.sourceKey, {
|
|
1099
|
+
hash: rawHash,
|
|
1100
|
+
abs: absPath
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
const packageMap = admitted.packageMap;
|
|
1104
|
+
this.lastPackageMap = packageMap;
|
|
1105
|
+
this.externalDirOwners = require_pipeline.firstOwners(admitted.dirOwnerSets);
|
|
1106
|
+
this.externalFileOwners = admitted.fileOwners;
|
|
1107
|
+
this.externalSourceEntries = admitted.sourceEntries;
|
|
1108
|
+
for (const entry of admitted.entries) {
|
|
1109
|
+
const hash = require_pipeline.contentHash(entry.source);
|
|
1110
|
+
rawEntries.push({
|
|
1111
|
+
path: entry.path,
|
|
1112
|
+
source: entry.source,
|
|
1113
|
+
hash
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1116
|
+
this.externalPackageDirs = admitted.packageDirs;
|
|
1117
|
+
bt.packageResolve = this.elapsed(t);
|
|
1118
|
+
this.beginStatusAttempt();
|
|
1119
|
+
let accepted;
|
|
1120
|
+
try {
|
|
1121
|
+
accepted = await this.corpus.prepare(rawEntries);
|
|
1122
|
+
} catch (err) {
|
|
1123
|
+
this.debouncePending.clear();
|
|
1124
|
+
this.writeAnalysisStatus("failed", pending, String(err));
|
|
1125
|
+
throw err;
|
|
1126
|
+
}
|
|
1127
|
+
this.externalFileOwners = require_pipeline.projectExternalFileOwners(accepted, this.externalFileOwners);
|
|
1128
|
+
bt.fileCount = accepted.analysisEntries.length;
|
|
1129
|
+
require_pipeline.clearEngineCache(engineApi);
|
|
1130
|
+
await this.analyzeAndEmit(accepted.analysisEntries, packageMap, false, bt, pipelineStart, pending);
|
|
1131
|
+
this.publishSourceIngestion(accepted);
|
|
1132
|
+
setSharedExternalDirs(admitted.packageDirs);
|
|
1133
|
+
setSharedExternalEntries(admitted.sourceEntries);
|
|
1134
|
+
this.onExternalRootsCommitted?.(this.externalWatchRoots);
|
|
1135
|
+
setOwningWatchSession(this);
|
|
1136
|
+
} catch (err) {
|
|
1137
|
+
this.restoreSourceState(priorSourceState);
|
|
1138
|
+
throw err;
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
/** Per-generation source state captured as ONE object so the restore
|
|
1142
|
+
* cannot omit a member; deep-copied fields are mutated in place. */
|
|
1143
|
+
snapshotSourceState() {
|
|
1144
|
+
return {
|
|
1145
|
+
sourceIdentity: this.sourceIdentity,
|
|
1146
|
+
externalRootOwners: new Map([...this.externalRootOwners].map(([root, owners]) => [root, new Set(owners)])),
|
|
1147
|
+
externalRootExtensions: new Map(this.externalRootExtensions),
|
|
1148
|
+
externalWatchRoots: [...this.externalWatchRoots],
|
|
1149
|
+
externalInventory: new Map([...this.externalInventory].map(([root, inventory]) => [root, new Map(inventory)])),
|
|
1150
|
+
lastPackageMap: this.lastPackageMap,
|
|
1151
|
+
externalDirOwners: this.externalDirOwners,
|
|
1152
|
+
externalFileOwners: this.externalFileOwners,
|
|
1153
|
+
externalSourceEntries: this.externalSourceEntries,
|
|
1154
|
+
externalPackageDirs: this.externalPackageDirs,
|
|
1155
|
+
systemVocabularyDiagnostics: this.systemVocabularyDiagnostics,
|
|
1156
|
+
ingestionFailureDiagnostics: this.ingestionFailureDiagnostics
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
restoreSourceState(snapshot) {
|
|
1160
|
+
Object.assign(this, snapshot);
|
|
1161
|
+
}
|
|
1162
|
+
/** Release this session's publication claim so a successor may publish.
|
|
1163
|
+
* Idempotent, and scoped to this session's own claim. */
|
|
1164
|
+
close() {
|
|
1165
|
+
this.releasePublicationClaim?.();
|
|
1166
|
+
this.releasePublicationClaim = null;
|
|
1167
|
+
}
|
|
1168
|
+
/** Reset analysis state for a system reload: write guards go back to null
|
|
1169
|
+
* and reseed from the disk envelopes on the next publication. */
|
|
1170
|
+
resetForHmr() {
|
|
1171
|
+
resetAnalysisStartedPromise();
|
|
1172
|
+
this.artifactRecords = {
|
|
1173
|
+
manifest: null,
|
|
1174
|
+
inputs: null,
|
|
1175
|
+
styles: null
|
|
1176
|
+
};
|
|
1177
|
+
this.lastSystemPropsHash = null;
|
|
1178
|
+
}
|
|
1179
|
+
/** The (sourceKey, observed hash) pairs of a watch batch — the status
|
|
1180
|
+
* file's pending set; loaders wait only on observed inputs. */
|
|
1181
|
+
pendingFromBatch(changes) {
|
|
1182
|
+
const { excludeMatcher, extensionsSet } = this.resolveScanConfig();
|
|
1183
|
+
const pending = [];
|
|
1184
|
+
for (const path$4 of changes.modifiedFiles ?? []) {
|
|
1185
|
+
const classified = this.classifyWatchPath(path$4, {
|
|
1186
|
+
excludeMatcher,
|
|
1187
|
+
extensionsSet
|
|
1188
|
+
});
|
|
1189
|
+
if (!classified) continue;
|
|
1190
|
+
try {
|
|
1191
|
+
pending.push([classified.key, require_pipeline.contentHash((0, fs.readFileSync)(path$4, "utf-8"))]);
|
|
1192
|
+
} catch {}
|
|
1193
|
+
}
|
|
1194
|
+
return pending;
|
|
1195
|
+
}
|
|
1196
|
+
/** Rewalk dirty external roots and diff the inventory into created,
|
|
1197
|
+
* edited and deleted deltas: a watcher may report only the directory. */
|
|
1198
|
+
reconcileExternalRoots(changes) {
|
|
1199
|
+
const identity = this.sourceIdentity;
|
|
1200
|
+
if (!identity) return changes;
|
|
1201
|
+
if (!changes.modifiedFiles && !changes.removedFiles) return changes;
|
|
1202
|
+
if (this.externalWatchRoots.length === 0) return changes;
|
|
1203
|
+
const { extensionsSet } = this.resolveScanConfig();
|
|
1204
|
+
const dirtyRoots = /* @__PURE__ */ new Set();
|
|
1205
|
+
const modified = /* @__PURE__ */ new Set();
|
|
1206
|
+
const removed = /* @__PURE__ */ new Set();
|
|
1207
|
+
const rootHitFor = (path$5) => {
|
|
1208
|
+
const containing = identity.containingExternalRoot(path$5);
|
|
1209
|
+
if (!containing) return null;
|
|
1210
|
+
try {
|
|
1211
|
+
return (0, fs.statSync)(path$5).isDirectory() ? containing : null;
|
|
1212
|
+
} catch {
|
|
1213
|
+
return identity.resolveDeletedSourceId(path$5) ? null : containing;
|
|
1214
|
+
}
|
|
1215
|
+
};
|
|
1216
|
+
for (const path$6 of changes.modifiedFiles ?? []) {
|
|
1217
|
+
const root = rootHitFor(path$6);
|
|
1218
|
+
if (root) dirtyRoots.add(root);
|
|
1219
|
+
else modified.add(path$6);
|
|
1220
|
+
}
|
|
1221
|
+
for (const path$7 of changes.removedFiles ?? []) {
|
|
1222
|
+
const root = rootHitFor(path$7);
|
|
1223
|
+
if (root) dirtyRoots.add(root);
|
|
1224
|
+
else removed.add(path$7);
|
|
1225
|
+
}
|
|
1226
|
+
for (const root of dirtyRoots) {
|
|
1227
|
+
const previous = this.externalInventory.get(root) ?? /* @__PURE__ */ new Map();
|
|
1228
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1229
|
+
const walked = require_pipeline.walkPackageSources(root, this.externalRootExtensions.get(root) ?? extensionsSet);
|
|
1230
|
+
if (walked.length === 0 && previous.size > 0) this.warn(`external root rewalk found no files under ${root} while its inventory holds ${previous.size} — reconciling as full deletion`);
|
|
1231
|
+
for (const abs of walked) {
|
|
1232
|
+
const resolved = identity.resolveSourceId(abs);
|
|
1233
|
+
if (!resolved || resolved.owningRoot !== root) continue;
|
|
1234
|
+
seen.add(resolved.sourceKey);
|
|
1235
|
+
let rawHash;
|
|
1236
|
+
try {
|
|
1237
|
+
rawHash = require_pipeline.contentHash((0, fs.readFileSync)(abs, "utf-8"));
|
|
1238
|
+
} catch {
|
|
1239
|
+
continue;
|
|
1240
|
+
}
|
|
1241
|
+
if (previous.get(resolved.sourceKey)?.hash !== rawHash) modified.add(abs);
|
|
1242
|
+
}
|
|
1243
|
+
for (const [key, entry] of previous) if (!seen.has(key)) removed.add(entry.abs);
|
|
1244
|
+
}
|
|
1245
|
+
return {
|
|
1246
|
+
modifiedFiles: modified,
|
|
1247
|
+
removedFiles: removed
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1250
|
+
/** Route one watcher path: a project-root member (consumer excludes apply)
|
|
1251
|
+
* or an admitted external member (symlink escapes rejected). */
|
|
1252
|
+
classifyWatchPath(absPath, scan) {
|
|
1253
|
+
const ext = (0, path.extname)(absPath);
|
|
1254
|
+
const rootDir = this.rootDir;
|
|
1255
|
+
if (require_pipeline.isPathWithinRoot(rootDir, absPath)) {
|
|
1256
|
+
if (!scan.extensionsSet.has(ext)) return null;
|
|
1257
|
+
const rel = (0, path.relative)(rootDir, absPath);
|
|
1258
|
+
if (scan.excludeMatcher.matches(absPath, rel)) return null;
|
|
1259
|
+
return {
|
|
1260
|
+
key: rel,
|
|
1261
|
+
owningRoot: null
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
const resolved = this.sourceIdentity?.resolveSourceId(absPath);
|
|
1265
|
+
if (!resolved) return null;
|
|
1266
|
+
if (resolved.owningRoot === null) {
|
|
1267
|
+
if (!scan.extensionsSet.has(ext)) return null;
|
|
1268
|
+
if (scan.excludeMatcher.matches(resolved.sourceKey, resolved.sourceKey)) return null;
|
|
1269
|
+
return {
|
|
1270
|
+
key: resolved.sourceKey,
|
|
1271
|
+
owningRoot: null
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
if (!(this.externalRootExtensions.get(resolved.owningRoot) ?? scan.extensionsSet).has(ext)) return null;
|
|
1275
|
+
if (require_pipeline.isExcludedPackageRelativePath(resolved.pathInRoot)) return null;
|
|
1276
|
+
return {
|
|
1277
|
+
key: resolved.sourceKey,
|
|
1278
|
+
owningRoot: resolved.owningRoot
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
async runIncrementalPipeline(fileEntries, pending = []) {
|
|
1282
|
+
const bt = {};
|
|
1283
|
+
const pipelineStart = this.now();
|
|
1284
|
+
await this.analyzeAndEmit(fileEntries, this.lastPackageMap, true, bt, pipelineStart, pending);
|
|
1285
|
+
}
|
|
1286
|
+
/** Shared analysis + emit core for both pipelines. `devMode` forks only
|
|
1287
|
+
* the report log and the system-props write guard. */
|
|
1288
|
+
async analyzeAndEmit(fileEntries, packageMap, devMode, bt, pipelineStart, pending = []) {
|
|
1289
|
+
this.beginStatusAttempt();
|
|
1290
|
+
this.debouncePending.clear();
|
|
1291
|
+
this.writeAnalysisStatus("starting", pending);
|
|
1292
|
+
try {
|
|
1293
|
+
await this.analyzeAndEmitAttempt(fileEntries, packageMap, devMode, bt, pipelineStart, pending);
|
|
1294
|
+
this.firstEmissionComplete = true;
|
|
1295
|
+
this.writeAnalysisStatus("idle", []);
|
|
1296
|
+
} catch (err) {
|
|
1297
|
+
this.writeAnalysisStatus("failed", pending, String(err));
|
|
1298
|
+
throw err;
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
/** An explicit `mode` wins over the pipeline path: a pinned-production
|
|
1302
|
+
* watch must not emit unpruned CSS on its first incremental republish. */
|
|
1303
|
+
engineDevMode(pipelineDefault) {
|
|
1304
|
+
if (this.options.mode !== void 0) return this.options.mode === "development";
|
|
1305
|
+
return pipelineDefault;
|
|
1306
|
+
}
|
|
1307
|
+
async analyzeAndEmitAttempt(fileEntries, packageMap, devMode, bt, pipelineStart, pending) {
|
|
1308
|
+
const system = this.system;
|
|
1309
|
+
const analysisOptions = {
|
|
1310
|
+
fileEntries,
|
|
1311
|
+
packageMap,
|
|
1312
|
+
system,
|
|
1313
|
+
emitter: {
|
|
1314
|
+
runtimeImport: "@animus-ui/system/runtime",
|
|
1315
|
+
cssModuleId: ANIMUS_CSS_MODULE_ID,
|
|
1316
|
+
systemPropsModuleId: this.systemPropsModuleId ?? systemPropsPath(this.sessionDir)
|
|
1317
|
+
},
|
|
1318
|
+
pathAliasesJson: this.pathAliasesJson,
|
|
1319
|
+
staticCssJson: this.staticCssJson,
|
|
1320
|
+
externalDirs: this.externalPackageDirs.map((dir) => (0, path.relative)(this.rootDir, dir)),
|
|
1321
|
+
devMode: this.engineDevMode(devMode)
|
|
1322
|
+
};
|
|
1323
|
+
this.writeAnalysisStatus("analyzing", pending);
|
|
1324
|
+
const result = require_pipeline.runProjectAnalysis(engineApi, {
|
|
1325
|
+
...analysisOptions,
|
|
1326
|
+
warn: (message) => this.warn(message),
|
|
1327
|
+
strict: this.options.strict,
|
|
1328
|
+
extraDiagnostics: [...this.systemVocabularyDiagnostics, ...this.ingestionFailureDiagnostics]
|
|
1329
|
+
});
|
|
1330
|
+
require_pipeline.assertNoErrorDiagnostics(result.manifest?.diagnostics);
|
|
1331
|
+
require_pipeline.enforceExternalTokenContracts({
|
|
1332
|
+
diagnostics: result.manifest?.diagnostics,
|
|
1333
|
+
fileOwners: this.externalFileOwners,
|
|
1334
|
+
dirOwners: this.externalDirOwners,
|
|
1335
|
+
sourceThemeManifestsJson: system.sourceThemeManifestsJson,
|
|
1336
|
+
strict: this.options.strict,
|
|
1337
|
+
prefix: `[${this.driverLabel}]`,
|
|
1338
|
+
warn: (message) => this.warn(message)
|
|
1339
|
+
});
|
|
1340
|
+
bt.jsonSerialize = result.timings.serializeMs;
|
|
1341
|
+
bt.rustExtract = result.timings.extractMs;
|
|
1342
|
+
bt.jsonParse = result.timings.parseMs;
|
|
1343
|
+
const manifest = result.manifest;
|
|
1344
|
+
this.lastComponentCount = Object.keys(manifest?.components ?? {}).length;
|
|
1345
|
+
if (!devMode) {
|
|
1346
|
+
bt.analysis = (bt.jsonSerialize ?? 0) + (bt.rustExtract ?? 0) + (bt.jsonParse ?? 0);
|
|
1347
|
+
if (manifest?.report) this.log(`Extracted ${manifest.report.components_extracted ?? "?"}/${manifest.report.components_total ?? "?"} components (${bt.analysis}ms)`);
|
|
1348
|
+
}
|
|
1349
|
+
const globalCss = this.substituteAssetReferences(result.globalCss, !devMode || this.staleAssetPruning === "every-cycle");
|
|
1350
|
+
const { declaration, variables, body } = require_pipeline.assembleStylesheet({
|
|
1351
|
+
layers: this.options.layers,
|
|
1352
|
+
variableCss: system.variableCss,
|
|
1353
|
+
globalCss,
|
|
1354
|
+
componentCss: result.componentCss,
|
|
1355
|
+
split: true
|
|
1356
|
+
});
|
|
1357
|
+
if (this.lcssTargets === null) this.lcssTargets = require_pipeline.resolveLightningTargets(this.options.targets, this.rootDir);
|
|
1358
|
+
const fullCss = [
|
|
1359
|
+
declaration,
|
|
1360
|
+
variables,
|
|
1361
|
+
require_pipeline.postProcessCss(body, {
|
|
1362
|
+
minify: this.options.minify ?? require_pipeline.resolveMode(this.options.mode, () => process.env.NODE_ENV === "production" ? "production" : "development").mode === "production",
|
|
1363
|
+
targets: this.lcssTargets,
|
|
1364
|
+
warnFn: (msg) => this.warn(msg)
|
|
1365
|
+
})
|
|
1366
|
+
].filter(Boolean).join("\n");
|
|
1367
|
+
setSharedCss(fullCss);
|
|
1368
|
+
const systemPropsContent = require_pipeline.buildSystemPropsModule({
|
|
1369
|
+
systemPropMapJson: JSON.stringify(manifest?.system_prop_map ?? {}),
|
|
1370
|
+
groupRegistryJson: system.groupRegistryJson,
|
|
1371
|
+
dynamicProps: manifest?.dynamic_props ?? {}
|
|
1372
|
+
});
|
|
1373
|
+
setSharedSystemProps(systemPropsContent);
|
|
1374
|
+
setManifestJson(result.manifestJson);
|
|
1375
|
+
setAnalyzedHashes(new Map(fileEntries.map((entry) => [entry.path, entry.hash])));
|
|
1376
|
+
this.writeAnalysisStatus("committing", pending);
|
|
1377
|
+
const epoch = require_pipeline.hashReplacementPlans(require_pipeline.snapshotFilePlans(manifest), systemPropsContent);
|
|
1378
|
+
if (this.lastCommit === null) this.lastCommit = this.seedCommitFromDisk();
|
|
1379
|
+
const generation = (this.lastCommit?.generation ?? 0) + 1;
|
|
1380
|
+
this.publishPayloadArtifact("manifest", MANIFEST_ARTIFACT, result.manifestJson, epoch, generation, envelopeJsonArtifact);
|
|
1381
|
+
if (this.persistAnalysisInputs) this.publishPayloadArtifact("inputs", ANALYSIS_INPUTS_ARTIFACT, JSON.stringify({
|
|
1382
|
+
analyzedHashes: Object.fromEntries(fileEntries.map((entry) => [entry.path, entry.hash])),
|
|
1383
|
+
...result.inputs
|
|
1384
|
+
}), epoch, generation, envelopeJsonArtifact);
|
|
1385
|
+
this.publishPayloadArtifact("styles", STYLES_ARTIFACT, fullCss, epoch, generation, envelopeCssArtifact);
|
|
1386
|
+
if (devMode) {
|
|
1387
|
+
const systemPropsHash = require_pipeline.contentHash(systemPropsContent);
|
|
1388
|
+
if (systemPropsHash !== this.lastSystemPropsHash) {
|
|
1389
|
+
this.writeSessionArtifact(SYSTEM_PROPS_ARTIFACT, systemPropsContent);
|
|
1390
|
+
this.lastSystemPropsHash = systemPropsHash;
|
|
1391
|
+
}
|
|
1392
|
+
} else this.writeSessionArtifact(SYSTEM_PROPS_ARTIFACT, systemPropsContent);
|
|
1393
|
+
this.publishAnalysisCommit(epoch, generation);
|
|
1394
|
+
this.publishReplacementEpoch(epoch);
|
|
1395
|
+
bt.total = this.elapsed(pipelineStart);
|
|
1396
|
+
logBuildTimings(bt, manifest?.timing, (msg) => this.log(msg), this.verbose);
|
|
1397
|
+
}
|
|
1398
|
+
/** Open a status attempt (attemptId increments once per burst — a
|
|
1399
|
+
* debouncing pre-write and the analysis that follows share one id). */
|
|
1400
|
+
beginStatusAttempt() {
|
|
1401
|
+
if (!this.statusAttemptOpen) {
|
|
1402
|
+
this.statusAttemptId += 1;
|
|
1403
|
+
this.statusAttemptOpen = true;
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
/** Write the session's analysis-status artifact. Terminal states
|
|
1407
|
+
* (idle/failed) close the attempt. */
|
|
1408
|
+
writeAnalysisStatus(state, pending, diagnostic) {
|
|
1409
|
+
const status = {
|
|
1410
|
+
schema: 2,
|
|
1411
|
+
sessionId: this.sessionId,
|
|
1412
|
+
attemptId: this.statusAttemptId,
|
|
1413
|
+
state,
|
|
1414
|
+
pending,
|
|
1415
|
+
deadlineAt: Date.now() + this.debounceCeilingMs + STATUS_WATCHDOG_MS
|
|
1416
|
+
};
|
|
1417
|
+
if (diagnostic !== void 0) status.diagnostic = diagnostic;
|
|
1418
|
+
status.ready = this.firstEmissionComplete;
|
|
1419
|
+
this.writeSessionArtifact(ANALYSIS_STATUS_ARTIFACT, JSON.stringify(status));
|
|
1420
|
+
if (state === "idle" || state === "failed") this.statusAttemptOpen = false;
|
|
1421
|
+
}
|
|
1422
|
+
/** Record watch events observed during the debounce window so a loader
|
|
1423
|
+
* running ahead of the analysis has evidence to wait on. */
|
|
1424
|
+
noteDebouncedWatchEvents(absPaths) {
|
|
1425
|
+
if (!this.rootDir) return;
|
|
1426
|
+
const additions = this.pendingFromBatch({ modifiedFiles: new Set(absPaths) });
|
|
1427
|
+
if (additions.length === 0) return;
|
|
1428
|
+
this.beginStatusAttempt();
|
|
1429
|
+
for (const [key, hash] of additions) this.debouncePending.set(key, hash);
|
|
1430
|
+
if (this.debounceStatusWriteScheduled) return;
|
|
1431
|
+
this.debounceStatusWriteScheduled = true;
|
|
1432
|
+
queueMicrotask(() => {
|
|
1433
|
+
this.debounceStatusWriteScheduled = false;
|
|
1434
|
+
if (this.debouncePending.size === 0) return;
|
|
1435
|
+
try {
|
|
1436
|
+
this.writeAnalysisStatus("debouncing", [...this.debouncePending.entries()]);
|
|
1437
|
+
} catch (err) {
|
|
1438
|
+
this.warn(`debounce status write failed: ${String(err)}`);
|
|
1439
|
+
}
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
/** One pending microtask flushes a burst of debounce observations. */
|
|
1443
|
+
debounceStatusWriteScheduled = false;
|
|
1444
|
+
/** Write one enveloped payload artifact, rewritten only when the PAYLOAD
|
|
1445
|
+
* bytes changed; the recorded diskHash feeds the analysis-commit. */
|
|
1446
|
+
publishPayloadArtifact(key, name, payload, epoch, generation, wrap) {
|
|
1447
|
+
const payloadHash = require_pipeline.contentHash(payload);
|
|
1448
|
+
if (this.artifactRecords[key] === null) this.artifactRecords[key] = this.seedPayloadRecord(key, name);
|
|
1449
|
+
const record = this.artifactRecords[key];
|
|
1450
|
+
if (record !== null && record.payloadHash === payloadHash) return;
|
|
1451
|
+
const envelope = {
|
|
1452
|
+
sessionId: this.sessionId,
|
|
1453
|
+
generation,
|
|
1454
|
+
replacementEpoch: epoch,
|
|
1455
|
+
payloadHash
|
|
1456
|
+
};
|
|
1457
|
+
const bytes = wrap(payload, JSON.stringify(envelope));
|
|
1458
|
+
this.writeSessionArtifact(name, bytes);
|
|
1459
|
+
this.artifactRecords[key] = {
|
|
1460
|
+
payloadHash,
|
|
1461
|
+
diskHash: require_pipeline.contentHash(bytes)
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1464
|
+
/** Reconstruct a payload write guard from the on-disk artifact's envelope
|
|
1465
|
+
* (same-session restart: a byte-identical payload must not rewrite). */
|
|
1466
|
+
seedPayloadRecord(key, name) {
|
|
1467
|
+
let bytes;
|
|
1468
|
+
try {
|
|
1469
|
+
bytes = (0, fs.readFileSync)((0, path.join)(this.sessionDir, name), "utf-8");
|
|
1470
|
+
} catch {
|
|
1471
|
+
return null;
|
|
1472
|
+
}
|
|
1473
|
+
let envelope;
|
|
1474
|
+
try {
|
|
1475
|
+
envelope = key === "styles" ? readCssEnvelope(bytes) : readJsonEnvelope(bytes);
|
|
1476
|
+
} catch {
|
|
1477
|
+
return null;
|
|
1478
|
+
}
|
|
1479
|
+
if (!envelope || !isDiskString(envelope.payloadHash)) return null;
|
|
1480
|
+
return {
|
|
1481
|
+
payloadHash: envelope.payloadHash,
|
|
1482
|
+
diskHash: require_pipeline.contentHash(bytes)
|
|
1483
|
+
};
|
|
1484
|
+
}
|
|
1485
|
+
/** Last analysis-commit persisted in this session's directory, or null. */
|
|
1486
|
+
seedCommitFromDisk() {
|
|
1487
|
+
try {
|
|
1488
|
+
const parsed = JSON.parse((0, fs.readFileSync)(analysisCommitPath(this.sessionDir), "utf-8"));
|
|
1489
|
+
return parsed.schema === 1 && parsed.sessionId === this.sessionId && isDiskNumber(parsed.generation) ? parsed : null;
|
|
1490
|
+
} catch {
|
|
1491
|
+
return null;
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
/** Publish the analysis-commit: written after every payload and before the
|
|
1495
|
+
* epoch, skipped when the payload hashes and epoch are unchanged. */
|
|
1496
|
+
publishAnalysisCommit(epoch, generation) {
|
|
1497
|
+
const manifestHash = this.artifactRecords.manifest?.diskHash ?? "";
|
|
1498
|
+
const inputsHash = this.persistAnalysisInputs ? this.artifactRecords.inputs?.diskHash ?? "" : void 0;
|
|
1499
|
+
const stylesHash = this.artifactRecords.styles?.diskHash ?? "";
|
|
1500
|
+
const prev = this.lastCommit;
|
|
1501
|
+
if (prev !== null && prev.manifestHash === manifestHash && prev.inputsHash === inputsHash && prev.stylesHash === stylesHash && prev.replacementEpoch === epoch) return;
|
|
1502
|
+
const commit = {
|
|
1503
|
+
schema: 1,
|
|
1504
|
+
sessionId: this.sessionId,
|
|
1505
|
+
generation,
|
|
1506
|
+
replacementEpoch: epoch,
|
|
1507
|
+
manifestHash,
|
|
1508
|
+
stylesHash
|
|
1509
|
+
};
|
|
1510
|
+
if (inputsHash !== void 0) commit.inputsHash = inputsHash;
|
|
1511
|
+
this.writeSessionArtifact(ANALYSIS_COMMIT_ARTIFACT, JSON.stringify(commit));
|
|
1512
|
+
this.lastCommit = commit;
|
|
1513
|
+
}
|
|
1514
|
+
/** Maintain the session's epoch witness, rewritten ONLY when the value
|
|
1515
|
+
* changes, then expose it through the singleton. */
|
|
1516
|
+
publishReplacementEpoch(epoch) {
|
|
1517
|
+
const onDisk = this.diskEpochValue();
|
|
1518
|
+
if (this.lastEpochValue === null) this.lastEpochValue = onDisk;
|
|
1519
|
+
if (epoch !== onDisk) this.writeSessionArtifact(REPLACEMENT_EPOCH_ARTIFACT, JSON.stringify({
|
|
1520
|
+
schema: 1,
|
|
1521
|
+
sessionId: this.sessionId,
|
|
1522
|
+
epoch
|
|
1523
|
+
}));
|
|
1524
|
+
if (epoch !== this.lastEpochValue) {
|
|
1525
|
+
this.lastEpochValue = epoch;
|
|
1526
|
+
this.reconcileSiblingEpochs(epoch);
|
|
1527
|
+
}
|
|
1528
|
+
setReplacementEpoch(epoch);
|
|
1529
|
+
}
|
|
1530
|
+
/** sessions-root listing memo, keyed by the root dir's mtime — a new or
|
|
1531
|
+
* pruned sibling DIRECTORY moves it; agreeing siblings stay listed. */
|
|
1532
|
+
siblingListing = null;
|
|
1533
|
+
/** Restored-module snapshots point at the epoch artifact of the session
|
|
1534
|
+
* that built them, so a moved epoch deletes every DISAGREEING sibling
|
|
1535
|
+
* artifact to invalidate them. Agreeing siblings stay byte-untouched so
|
|
1536
|
+
* warm restores keep working; a sibling with a live owner is exempt. */
|
|
1537
|
+
reconcileSiblingEpochs(epoch) {
|
|
1538
|
+
const rootPath = sessionsRootDir(this.rootDir);
|
|
1539
|
+
let entries;
|
|
1540
|
+
try {
|
|
1541
|
+
const mtimeMs = (0, fs.statSync)(rootPath).mtimeMs;
|
|
1542
|
+
if (this.siblingListing?.mtimeMs === mtimeMs) entries = this.siblingListing.entries;
|
|
1543
|
+
else {
|
|
1544
|
+
entries = (0, fs.readdirSync)(rootPath);
|
|
1545
|
+
this.siblingListing = {
|
|
1546
|
+
mtimeMs,
|
|
1547
|
+
entries
|
|
1548
|
+
};
|
|
1549
|
+
}
|
|
1550
|
+
} catch {
|
|
1551
|
+
return;
|
|
1552
|
+
}
|
|
1553
|
+
for (const entry of entries) {
|
|
1554
|
+
if (entry === this.sessionId) continue;
|
|
1555
|
+
const siblingDir = (0, path.join)(rootPath, entry);
|
|
1556
|
+
const siblingEpochPath = (0, path.join)(siblingDir, REPLACEMENT_EPOCH_ARTIFACT);
|
|
1557
|
+
try {
|
|
1558
|
+
if (JSON.parse((0, fs.readFileSync)(siblingEpochPath, "utf-8")).epoch === epoch) continue;
|
|
1559
|
+
} catch (err) {
|
|
1560
|
+
if (err?.code === "ENOENT") continue;
|
|
1561
|
+
}
|
|
1562
|
+
const ownerPid = this.liveSiblingOwnerPid(siblingDir);
|
|
1563
|
+
if (ownerPid !== null) {
|
|
1564
|
+
this.warn(`sibling session ${entry} still has a live owner (pid ${ownerPid}) and its replacement epoch disagrees with this session's (${epoch}) — leaving its epoch artifact in place`);
|
|
1565
|
+
continue;
|
|
1566
|
+
}
|
|
1567
|
+
try {
|
|
1568
|
+
(0, fs.unlinkSync)(siblingEpochPath);
|
|
1569
|
+
} catch {}
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
/** The pid of `siblingDir`'s live owner, or null when nothing proves one.
|
|
1573
|
+
* Our own pid is never one — publication is process-exclusive. A missing,
|
|
1574
|
+
* undecodable or unreadable claim all mean no owner: the opposite default
|
|
1575
|
+
* from the CLI lock check on purpose, so do not unify the two. */
|
|
1576
|
+
liveSiblingOwnerPid(siblingDir) {
|
|
1577
|
+
let claim;
|
|
1578
|
+
try {
|
|
1579
|
+
claim = readCliLockRecord(siblingDir);
|
|
1580
|
+
} catch {
|
|
1581
|
+
return null;
|
|
1582
|
+
}
|
|
1583
|
+
if (claim.kind !== "held") return null;
|
|
1584
|
+
if (claim.record.pid === process.pid) return null;
|
|
1585
|
+
return checkLockLiveness(claim.record).live ? claim.record.pid : null;
|
|
1586
|
+
}
|
|
1587
|
+
/** Epoch value held by this session's on-disk artifact, or null when
|
|
1588
|
+
* absent, unreadable, or not the expected schema. */
|
|
1589
|
+
diskEpochValue() {
|
|
1590
|
+
try {
|
|
1591
|
+
const parsed = JSON.parse((0, fs.readFileSync)(replacementEpochPath(this.sessionDir), "utf-8"));
|
|
1592
|
+
return parsed.schema === 1 && isDiskString(parsed.epoch) ? parsed.epoch : null;
|
|
1593
|
+
} catch {
|
|
1594
|
+
return null;
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
/** True when the flat `.animus/` advisory lock claims the tree. A lock that
|
|
1598
|
+
* exists but cannot be decoded claims it too — unknown is never absent. */
|
|
1599
|
+
cliWriterHoldsLock(animusDir) {
|
|
1600
|
+
const lock = readCliLockRecord(animusDir);
|
|
1601
|
+
if (lock.kind === "none") return false;
|
|
1602
|
+
return lock.kind === "indeterminate" || checkLockLiveness(lock.record).live;
|
|
1603
|
+
}
|
|
1604
|
+
/** Delete legacy flat artifacts and prune sibling session directories past
|
|
1605
|
+
* the retention window — never this session's own dir. */
|
|
1606
|
+
runSessionStartHygiene() {
|
|
1607
|
+
if (this.sessionStartHygieneDone) return;
|
|
1608
|
+
this.sessionStartHygieneDone = true;
|
|
1609
|
+
const animusDir = (0, path.join)(this.rootDir, ANIMUS_ARTIFACT_DIR);
|
|
1610
|
+
const isCliPublishedSet = this.cliWriterHoldsLock(animusDir) || verifyCommitRecord(animusDir).length === 0;
|
|
1611
|
+
const cliPublishedNames = [
|
|
1612
|
+
MANIFEST_ARTIFACT,
|
|
1613
|
+
STYLES_ARTIFACT,
|
|
1614
|
+
SYSTEM_PROPS_ARTIFACT
|
|
1615
|
+
];
|
|
1616
|
+
for (const name of LEGACY_FLAT_ARTIFACTS) {
|
|
1617
|
+
if (isCliPublishedSet && cliPublishedNames.includes(name)) continue;
|
|
1618
|
+
try {
|
|
1619
|
+
(0, fs.unlinkSync)((0, path.join)(animusDir, name));
|
|
1620
|
+
} catch {}
|
|
1621
|
+
}
|
|
1622
|
+
if (!isCliPublishedSet) try {
|
|
1623
|
+
(0, fs.unlinkSync)((0, path.join)(animusDir, CLI_COMMIT_ARTIFACT));
|
|
1624
|
+
} catch {}
|
|
1625
|
+
let entries;
|
|
1626
|
+
try {
|
|
1627
|
+
entries = (0, fs.readdirSync)(sessionsRootDir(this.rootDir));
|
|
1628
|
+
} catch {
|
|
1629
|
+
return;
|
|
1630
|
+
}
|
|
1631
|
+
const cutoff = Date.now() - SESSION_DIR_MAX_AGE_MS;
|
|
1632
|
+
for (const entry of entries) {
|
|
1633
|
+
if (entry === this.sessionId) continue;
|
|
1634
|
+
const dir = (0, path.join)(sessionsRootDir(this.rootDir), entry);
|
|
1635
|
+
try {
|
|
1636
|
+
if ((0, fs.statSync)(dir).mtimeMs >= cutoff) continue;
|
|
1637
|
+
if (this.liveSiblingOwnerPid(dir) !== null) continue;
|
|
1638
|
+
(0, fs.rmSync)(dir, {
|
|
1639
|
+
recursive: true,
|
|
1640
|
+
force: true
|
|
1641
|
+
});
|
|
1642
|
+
} catch {}
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
/** Write one artifact write-then-rename, so a cross-process reader never
|
|
1646
|
+
* sees a torn file; the tmp name carries the pid to keep writers apart. */
|
|
1647
|
+
writeSessionArtifact(name, content) {
|
|
1648
|
+
const dir = this.sessionDir;
|
|
1649
|
+
(0, fs.mkdirSync)(dir, { recursive: true });
|
|
1650
|
+
const tmpPath = (0, path.join)(dir, `.${name}.${process.pid}.tmp`);
|
|
1651
|
+
(0, fs.writeFileSync)(tmpPath, content);
|
|
1652
|
+
(0, fs.renameSync)(tmpPath, (0, path.join)(dir, name));
|
|
1653
|
+
this.onArtifactWrite?.(name, content);
|
|
1654
|
+
}
|
|
1655
|
+
/** Copy each asset() specifier's bytes into `assets/` under a
|
|
1656
|
+
* content-hashed name and substitute a url relative to styles.css. */
|
|
1657
|
+
substituteAssetReferences(globalCss, pruneSuperseded) {
|
|
1658
|
+
const specifiers = require_pipeline.findAssetSpecifiers(globalCss);
|
|
1659
|
+
const assetsDir = (0, path.join)(this.sessionDir, SESSION_ASSETS_DIR);
|
|
1660
|
+
const expected = /* @__PURE__ */ new Set();
|
|
1661
|
+
this.assetDependencyPaths.clear();
|
|
1662
|
+
this.assetDependencyKeys.clear();
|
|
1663
|
+
const urlBySpecifier = /* @__PURE__ */ new Map();
|
|
1664
|
+
for (const specifier of specifiers) {
|
|
1665
|
+
const cached = this.assetCopyCache.get(specifier);
|
|
1666
|
+
if (cached && (0, fs.existsSync)((0, path.join)(assetsDir, cached.fileName))) try {
|
|
1667
|
+
const current = (0, fs.statSync)(cached.sourcePath);
|
|
1668
|
+
if (current.mtimeMs === cached.mtimeMs && current.size === cached.size) {
|
|
1669
|
+
this.trackAssetDependency(cached.sourcePath);
|
|
1670
|
+
expected.add(cached.fileName);
|
|
1671
|
+
urlBySpecifier.set(specifier, cached.url);
|
|
1672
|
+
continue;
|
|
1673
|
+
}
|
|
1674
|
+
} catch {}
|
|
1675
|
+
const resolvedPath = this.resolveAssetSpecifier(specifier);
|
|
1676
|
+
if (!resolvedPath) {
|
|
1677
|
+
const message = `unresolvable asset() specifier: ${specifier}`;
|
|
1678
|
+
if (this.options.strict) throw new Error(`[${this.driverLabel}] ${message}`);
|
|
1679
|
+
this.warn(message);
|
|
1680
|
+
urlBySpecifier.set(specifier, specifier);
|
|
1681
|
+
continue;
|
|
1682
|
+
}
|
|
1683
|
+
const bytes = (0, fs.readFileSync)(resolvedPath);
|
|
1684
|
+
const sourceStat = (0, fs.statSync)(resolvedPath);
|
|
1685
|
+
this.trackAssetDependency(resolvedPath);
|
|
1686
|
+
const ext = (0, path.extname)(resolvedPath);
|
|
1687
|
+
const fileName = `${(0, path.basename)(resolvedPath, ext)}.${require_pipeline.contentHash(bytes).slice(0, 8)}${ext}`;
|
|
1688
|
+
expected.add(fileName);
|
|
1689
|
+
if (!(0, fs.existsSync)(assetsDir)) (0, fs.mkdirSync)(assetsDir, { recursive: true });
|
|
1690
|
+
const assetPath = (0, path.join)(assetsDir, fileName);
|
|
1691
|
+
if (!(0, fs.existsSync)(assetPath)) (0, fs.writeFileSync)(assetPath, bytes);
|
|
1692
|
+
const url = `./${SESSION_ASSETS_DIR}/${fileName}`;
|
|
1693
|
+
urlBySpecifier.set(specifier, url);
|
|
1694
|
+
this.assetCopyCache.set(specifier, {
|
|
1695
|
+
sourcePath: resolvedPath,
|
|
1696
|
+
mtimeMs: sourceStat.mtimeMs,
|
|
1697
|
+
size: sourceStat.size,
|
|
1698
|
+
fileName,
|
|
1699
|
+
url
|
|
1700
|
+
});
|
|
1701
|
+
}
|
|
1702
|
+
if (pruneSuperseded) pruneStaleAssets(assetsDir, expected);
|
|
1703
|
+
return require_pipeline.substituteAssetPlaceholders(globalCss, urlBySpecifier);
|
|
1704
|
+
}
|
|
1705
|
+
trackAssetDependency(path$8) {
|
|
1706
|
+
this.assetDependencyPaths.add(path$8);
|
|
1707
|
+
for (const key of require_pipeline.toWatchKeys(path$8)) this.assetDependencyKeys.add(key);
|
|
1708
|
+
}
|
|
1709
|
+
/** Resolve an asset specifier through the shared resolver, with one
|
|
1710
|
+
* last resort: a discovered source entry's package root. */
|
|
1711
|
+
resolveAssetSpecifier(specifier) {
|
|
1712
|
+
const resolved = require_pipeline.resolveAssetFile(specifier, this.rootDir, this.pathAliasesJson);
|
|
1713
|
+
if (resolved) return resolved;
|
|
1714
|
+
const segments = specifier.split("/");
|
|
1715
|
+
const packageName = specifier.startsWith("@") ? segments.slice(0, 2).join("/") : segments[0];
|
|
1716
|
+
const subpath = specifier.slice(packageName.length + 1);
|
|
1717
|
+
if (!subpath) return null;
|
|
1718
|
+
const sourceEntry = this.externalSourceEntries.get(packageName) ?? [...this.externalSourceEntries].find(([declared]) => declared === packageName || declared.startsWith(`${packageName}/`))?.[1];
|
|
1719
|
+
if (sourceEntry) {
|
|
1720
|
+
const candidate = (0, path.join)(require_pipeline.findPackageRoot(sourceEntry), subpath);
|
|
1721
|
+
if ((0, fs.existsSync)(candidate)) return candidate;
|
|
1722
|
+
}
|
|
1723
|
+
return null;
|
|
1724
|
+
}
|
|
1725
|
+
};
|
|
1726
|
+
/** Sync `assets/` to the current build's file set; per-entry failures are
|
|
1727
|
+
* tolerated — a concurrent session may have removed the same file. */
|
|
1728
|
+
function pruneStaleAssets(assetsDir, expected) {
|
|
1729
|
+
if (!(0, fs.existsSync)(assetsDir)) return;
|
|
1730
|
+
let entries;
|
|
1731
|
+
try {
|
|
1732
|
+
entries = (0, fs.readdirSync)(assetsDir);
|
|
1733
|
+
} catch {
|
|
1734
|
+
return;
|
|
1735
|
+
}
|
|
1736
|
+
for (const entry of entries) {
|
|
1737
|
+
if (expected.has(entry)) continue;
|
|
1738
|
+
try {
|
|
1739
|
+
(0, fs.unlinkSync)((0, path.join)(assetsDir, entry));
|
|
1740
|
+
} catch {}
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
//#endregion
|
|
1744
|
+
//#region session/turbopack-orchestrator.ts
|
|
1745
|
+
async function runSessionPipeline(session) {
|
|
1746
|
+
session.systemPropsModuleId = TURBOPACK_SYSTEM_PROPS_ID;
|
|
1747
|
+
session.persistAnalysisInputs = true;
|
|
1748
|
+
await session.runFullPipeline();
|
|
1749
|
+
}
|
|
1750
|
+
const activeWatcherRoots = /* @__PURE__ */ new Set();
|
|
1751
|
+
const IGNORED_SEGMENTS = /* @__PURE__ */ new Set([
|
|
1752
|
+
ANIMUS_ARTIFACT_DIR,
|
|
1753
|
+
".next",
|
|
1754
|
+
"node_modules"
|
|
1755
|
+
]);
|
|
1756
|
+
/** Registration failures meaning the OS is out of watch capacity: descriptor
|
|
1757
|
+
* and inotify limits surface under all of these. */
|
|
1758
|
+
const CAPACITY_CODES = /* @__PURE__ */ new Set([
|
|
1759
|
+
"EMFILE",
|
|
1760
|
+
"ENFILE",
|
|
1761
|
+
"ENOSPC",
|
|
1762
|
+
"EPERM"
|
|
1763
|
+
]);
|
|
1764
|
+
function errnoCode(error) {
|
|
1765
|
+
if (!(error instanceof Object) || !("code" in error)) return null;
|
|
1766
|
+
const { code } = error;
|
|
1767
|
+
return isIntrinsicString(code) ? code : null;
|
|
1768
|
+
}
|
|
1769
|
+
/** A primitive string, decided by the intrinsic tag. `Object(value) !== value`
|
|
1770
|
+
* rejects boxed strings and a forged `Symbol.toStringTag`. */
|
|
1771
|
+
function isIntrinsicString(value) {
|
|
1772
|
+
return Object(value) !== value && Object.prototype.toString.call(value) === "[object String]";
|
|
1773
|
+
}
|
|
1774
|
+
function startTurbopackWatcher(session, rootDir, options = {}) {
|
|
1775
|
+
const { debounceMs = 75, watchFn = fs.watch, holdEvents = false } = options;
|
|
1776
|
+
if (activeWatcherRoots.has(rootDir)) return { kind: "already-watched" };
|
|
1777
|
+
activeWatcherRoots.add(rootDir);
|
|
1778
|
+
session.debounceCeilingMs = debounceMs;
|
|
1779
|
+
const pendingPaths = /* @__PURE__ */ new Set();
|
|
1780
|
+
const watchers = /* @__PURE__ */ new Map();
|
|
1781
|
+
let timer = null;
|
|
1782
|
+
let updateChain = Promise.resolve();
|
|
1783
|
+
let closed = false;
|
|
1784
|
+
let died = false;
|
|
1785
|
+
let holdingEvents = holdEvents;
|
|
1786
|
+
const externalWatchers = /* @__PURE__ */ new Map();
|
|
1787
|
+
let pendingOpened = /* @__PURE__ */ new Map();
|
|
1788
|
+
let capturedDuringSnapshot = [];
|
|
1789
|
+
const externalDiagnosticKey = (root) => `external-watch:${root}`;
|
|
1790
|
+
const failureReason = (err) => {
|
|
1791
|
+
const code = errnoCode(err);
|
|
1792
|
+
if (code !== null && CAPACITY_CODES.has(code)) return `capacity(${code})`;
|
|
1793
|
+
if (/inotify|too many/i.test(String(err))) return "capacity";
|
|
1794
|
+
return code ?? "error";
|
|
1795
|
+
};
|
|
1796
|
+
const degradeExternalRoot = (root, err) => {
|
|
1797
|
+
externalWatchers.get(root)?.close();
|
|
1798
|
+
externalWatchers.delete(root);
|
|
1799
|
+
pendingOpened.get(root)?.close();
|
|
1800
|
+
pendingOpened.delete(root);
|
|
1801
|
+
const message = `ANIMUS_EXTERNAL_WATCH_UNAVAILABLE root=${(0, path.relative)(rootDir, root)} reason=${failureReason(err)} effect=changes in this workspace source may require restart`;
|
|
1802
|
+
session.stickyDiagnostics?.set(externalDiagnosticKey(root), message);
|
|
1803
|
+
console.warn(`[animus-extract] ${message}`);
|
|
1804
|
+
};
|
|
1805
|
+
const flush = () => {
|
|
1806
|
+
timer = null;
|
|
1807
|
+
const modifiedFiles = /* @__PURE__ */ new Set();
|
|
1808
|
+
const removedFiles = /* @__PURE__ */ new Set();
|
|
1809
|
+
for (const path$1 of pendingPaths) ((0, fs.existsSync)(path$1) ? modifiedFiles : removedFiles).add(path$1);
|
|
1810
|
+
pendingPaths.clear();
|
|
1811
|
+
updateChain = updateChain.then(() => {
|
|
1812
|
+
if (closed) return;
|
|
1813
|
+
return session.handleWatchUpdate({
|
|
1814
|
+
modifiedFiles,
|
|
1815
|
+
removedFiles
|
|
1816
|
+
}).catch((err) => {
|
|
1817
|
+
console.warn(`[animus-extract] watch update failed: ${String(err)}`);
|
|
1818
|
+
}).then(() => {
|
|
1819
|
+
if (pendingOpened.size > 0 || capturedDuringSnapshot.length > 0) rollbackPendingExternal();
|
|
1820
|
+
});
|
|
1821
|
+
});
|
|
1822
|
+
};
|
|
1823
|
+
const rollbackPendingExternal = () => {
|
|
1824
|
+
for (const watcher of pendingOpened.values()) watcher.close();
|
|
1825
|
+
pendingOpened = /* @__PURE__ */ new Map();
|
|
1826
|
+
capturedDuringSnapshot = [];
|
|
1827
|
+
};
|
|
1828
|
+
const closeAll = () => {
|
|
1829
|
+
if (closed) return;
|
|
1830
|
+
closed = true;
|
|
1831
|
+
if (timer) clearTimeout(timer);
|
|
1832
|
+
timer = null;
|
|
1833
|
+
for (const watcher of watchers.values()) watcher.close();
|
|
1834
|
+
watchers.clear();
|
|
1835
|
+
for (const watcher of externalWatchers.values()) watcher.close();
|
|
1836
|
+
externalWatchers.clear();
|
|
1837
|
+
rollbackPendingExternal();
|
|
1838
|
+
session.onExternalRootResolved = null;
|
|
1839
|
+
session.onExternalRootsCommitted = null;
|
|
1840
|
+
activeWatcherRoots.delete(rootDir);
|
|
1841
|
+
};
|
|
1842
|
+
const onWatcherError = (err) => {
|
|
1843
|
+
died = true;
|
|
1844
|
+
closeAll();
|
|
1845
|
+
console.warn(`[animus-extract] Turbopack dev watcher failed (${String(err)}); source edits require a dev-server restart`);
|
|
1846
|
+
handle.onDied?.();
|
|
1847
|
+
};
|
|
1848
|
+
const enqueuePath = (abs) => {
|
|
1849
|
+
pendingPaths.add(abs);
|
|
1850
|
+
if (holdingEvents) return;
|
|
1851
|
+
try {
|
|
1852
|
+
session.noteDebouncedWatchEvents?.([abs]);
|
|
1853
|
+
} catch (err) {
|
|
1854
|
+
console.warn(`[animus-extract] Turbopack debounce status update failed: ${String(err)}`);
|
|
1855
|
+
}
|
|
1856
|
+
if (!timer) {
|
|
1857
|
+
timer = setTimeout(flush, debounceMs);
|
|
1858
|
+
timer.unref?.();
|
|
1859
|
+
}
|
|
1860
|
+
};
|
|
1861
|
+
const onEvent = (baseDir, filename) => {
|
|
1862
|
+
if (closed || !filename) return;
|
|
1863
|
+
const rel = filename.toString();
|
|
1864
|
+
if (rel.split(/[\\/]/).some((segment) => IGNORED_SEGMENTS.has(segment))) return;
|
|
1865
|
+
const abs = (0, path.join)(baseDir, rel);
|
|
1866
|
+
if (baseDir === rootDir && !watchers.has(abs)) try {
|
|
1867
|
+
if ((0, fs.statSync)(abs).isDirectory()) addWatcher(abs, true);
|
|
1868
|
+
} catch {}
|
|
1869
|
+
enqueuePath(abs);
|
|
1870
|
+
};
|
|
1871
|
+
const onExternalEvent = (root, self, filename) => {
|
|
1872
|
+
if (closed) return;
|
|
1873
|
+
const watcher = self();
|
|
1874
|
+
const active = watcher !== void 0 && externalWatchers.get(root) === watcher;
|
|
1875
|
+
const isPending = watcher !== void 0 && pendingOpened.get(root) === watcher;
|
|
1876
|
+
if (!active && !isPending) return;
|
|
1877
|
+
let abs;
|
|
1878
|
+
if (filename == null) abs = root;
|
|
1879
|
+
else {
|
|
1880
|
+
const rel = filename.toString();
|
|
1881
|
+
if (rel.split(/[\\/]/).some((segment) => IGNORED_SEGMENTS.has(segment))) return;
|
|
1882
|
+
abs = rel === "" ? root : (0, path.join)(root, rel);
|
|
1883
|
+
}
|
|
1884
|
+
if (isPending && !active) {
|
|
1885
|
+
capturedDuringSnapshot.push(abs);
|
|
1886
|
+
return;
|
|
1887
|
+
}
|
|
1888
|
+
enqueuePath(abs);
|
|
1889
|
+
};
|
|
1890
|
+
const openExternalWatcher = (root, into) => {
|
|
1891
|
+
if (root.split(/[\\/]/).includes("node_modules")) return;
|
|
1892
|
+
try {
|
|
1893
|
+
let watcher;
|
|
1894
|
+
watcher = watchFn(root, { recursive: true }, (_event, filename) => onExternalEvent(root, () => watcher, filename));
|
|
1895
|
+
watcher.on("error", (err) => degradeExternalRoot(root, err));
|
|
1896
|
+
watcher.unref?.();
|
|
1897
|
+
into.set(root, watcher);
|
|
1898
|
+
session.stickyDiagnostics?.delete(externalDiagnosticKey(root));
|
|
1899
|
+
} catch (err) {
|
|
1900
|
+
degradeExternalRoot(root, err);
|
|
1901
|
+
}
|
|
1902
|
+
};
|
|
1903
|
+
const addWatcher = (dir, recursive) => {
|
|
1904
|
+
const watcher = watchFn(dir, { recursive }, (_event, filename) => onEvent(dir, filename));
|
|
1905
|
+
watcher.on("error", onWatcherError);
|
|
1906
|
+
watcher.unref?.();
|
|
1907
|
+
watchers.set(dir, watcher);
|
|
1908
|
+
};
|
|
1909
|
+
try {
|
|
1910
|
+
addWatcher(rootDir, false);
|
|
1911
|
+
for (const entry of (0, fs.readdirSync)(rootDir, { withFileTypes: true })) {
|
|
1912
|
+
if (!entry.isDirectory() || IGNORED_SEGMENTS.has(entry.name)) continue;
|
|
1913
|
+
addWatcher((0, path.join)(rootDir, entry.name), true);
|
|
1914
|
+
}
|
|
1915
|
+
} catch (err) {
|
|
1916
|
+
closeAll();
|
|
1917
|
+
console.warn(`[animus-extract] Turbopack dev watcher unavailable (${String(err)}); source edits require a dev-server restart`);
|
|
1918
|
+
return { kind: "unavailable" };
|
|
1919
|
+
}
|
|
1920
|
+
for (const root of session.externalWatchRoots ?? []) openExternalWatcher(root, externalWatchers);
|
|
1921
|
+
session.onExternalRootResolved = (root) => {
|
|
1922
|
+
if (closed) return;
|
|
1923
|
+
if (externalWatchers.has(root) || pendingOpened.has(root)) return;
|
|
1924
|
+
openExternalWatcher(root, pendingOpened);
|
|
1925
|
+
};
|
|
1926
|
+
session.onExternalRootsCommitted = (roots) => {
|
|
1927
|
+
if (closed) return;
|
|
1928
|
+
const admitted = new Set(roots);
|
|
1929
|
+
for (const [root, watcher] of pendingOpened) externalWatchers.set(root, watcher);
|
|
1930
|
+
pendingOpened = /* @__PURE__ */ new Map();
|
|
1931
|
+
for (const [root, watcher] of [...externalWatchers]) if (!admitted.has(root)) {
|
|
1932
|
+
watcher.close();
|
|
1933
|
+
externalWatchers.delete(root);
|
|
1934
|
+
}
|
|
1935
|
+
for (const abs of capturedDuringSnapshot.splice(0)) enqueuePath(abs);
|
|
1936
|
+
};
|
|
1937
|
+
const handle = {
|
|
1938
|
+
close: closeAll,
|
|
1939
|
+
get died() {
|
|
1940
|
+
return died;
|
|
1941
|
+
},
|
|
1942
|
+
onDied: null,
|
|
1943
|
+
deliverHeldEvents: () => {
|
|
1944
|
+
holdingEvents = false;
|
|
1945
|
+
if (pendingPaths.size > 0) flush();
|
|
1946
|
+
return updateChain;
|
|
1947
|
+
},
|
|
1948
|
+
settle: () => updateChain
|
|
1949
|
+
};
|
|
1950
|
+
return {
|
|
1951
|
+
kind: "started",
|
|
1952
|
+
handle
|
|
1953
|
+
};
|
|
1954
|
+
}
|
|
1955
|
+
//#endregion
|
|
1956
|
+
exports.ANIMUS_ARTIFACT_DIR = ANIMUS_ARTIFACT_DIR;
|
|
1957
|
+
exports.ANIMUS_CSS_MODULE_ID = ANIMUS_CSS_MODULE_ID;
|
|
1958
|
+
exports.CLI_COMMIT_ARTIFACT = CLI_COMMIT_ARTIFACT;
|
|
1959
|
+
exports.CLI_LOCK_ARTIFACT = CLI_LOCK_ARTIFACT;
|
|
1960
|
+
exports.CLI_LOCK_HEARTBEAT_INTERVAL_MS = CLI_LOCK_HEARTBEAT_INTERVAL_MS;
|
|
1961
|
+
exports.CLI_LOCK_STALE_AFTER_MS = CLI_LOCK_STALE_AFTER_MS;
|
|
1962
|
+
exports.ExtractionSession = ExtractionSession;
|
|
1963
|
+
exports.MANIFEST_ARTIFACT = MANIFEST_ARTIFACT;
|
|
1964
|
+
exports.SESSION_ASSETS_DIR = SESSION_ASSETS_DIR;
|
|
1965
|
+
exports.STYLES_ARTIFACT = STYLES_ARTIFACT;
|
|
1966
|
+
exports.SYSTEM_PROPS_ARTIFACT = SYSTEM_PROPS_ARTIFACT;
|
|
1967
|
+
exports.TURBOPACK_SYSTEM_PROPS_ID = TURBOPACK_SYSTEM_PROPS_ID;
|
|
1968
|
+
exports.analysisCommitPath = analysisCommitPath;
|
|
1969
|
+
exports.analysisInputsPath = analysisInputsPath;
|
|
1970
|
+
exports.analysisStatusPath = analysisStatusPath;
|
|
1971
|
+
exports.checkLockLiveness = checkLockLiveness;
|
|
1972
|
+
exports.collectSessionAssets = collectSessionAssets;
|
|
1973
|
+
exports.decodeCommitRecord = decodeCommitRecord;
|
|
1974
|
+
exports.engineApi = engineApi;
|
|
1975
|
+
exports.getAnalysisStartedPromise = getAnalysisStartedPromise;
|
|
1976
|
+
exports.getAnalyzedHashes = getAnalyzedHashes;
|
|
1977
|
+
exports.getManifestJson = getManifestJson;
|
|
1978
|
+
exports.getReplacementEpoch = getReplacementEpoch;
|
|
1979
|
+
exports.getSessionArtifactDir = getSessionArtifactDir;
|
|
1980
|
+
exports.getSharedCss = getSharedCss;
|
|
1981
|
+
exports.getSharedExternalDirs = getSharedExternalDirs;
|
|
1982
|
+
exports.getSharedExternalEntries = getSharedExternalEntries;
|
|
1983
|
+
exports.getSharedSystemProps = getSharedSystemProps;
|
|
1984
|
+
exports.holdDirectoryClaim = holdDirectoryClaim;
|
|
1985
|
+
exports.isLockHolderAlive = isLockHolderAlive;
|
|
1986
|
+
exports.lockRecordBytes = lockRecordBytes;
|
|
1987
|
+
exports.manifestPath = manifestPath;
|
|
1988
|
+
exports.pruneStaleAssets = pruneStaleAssets;
|
|
1989
|
+
exports.readCliLockRecord = readCliLockRecord;
|
|
1990
|
+
exports.readJsonEnvelope = readJsonEnvelope;
|
|
1991
|
+
exports.replacementEpochPath = replacementEpochPath;
|
|
1992
|
+
exports.runSessionPipeline = runSessionPipeline;
|
|
1993
|
+
exports.sessionArtifactDir = sessionArtifactDir;
|
|
1994
|
+
exports.setAnalysisStartedPromise = setAnalysisStartedPromise;
|
|
1995
|
+
exports.setSharedEngine = setSharedEngine;
|
|
1996
|
+
exports.startTurbopackWatcher = startTurbopackWatcher;
|
|
1997
|
+
exports.stylesPath = stylesPath;
|
|
1998
|
+
exports.systemPropsPath = systemPropsPath;
|
|
1999
|
+
exports.verifyCommitRecord = verifyCommitRecord;
|