@animus-ui/extract 0.1.11 → 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 +47 -0
- 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 +11 -6
- package/dist/index.mjs +2 -2
- package/dist/pipeline/analyze-project-args.d.ts +0 -12
- package/dist/pipeline/analyze-project-args.d.ts.map +1 -1
- package/dist/pipeline/assemble-stylesheet.d.ts +0 -35
- package/dist/pipeline/assemble-stylesheet.d.ts.map +1 -1
- package/dist/pipeline/asset-placeholders.d.ts +0 -25
- package/dist/pipeline/asset-placeholders.d.ts.map +1 -1
- package/dist/pipeline/content-hash.d.ts +0 -7
- package/dist/pipeline/content-hash.d.ts.map +1 -1
- package/dist/pipeline/core-options.d.ts +27 -93
- package/dist/pipeline/core-options.d.ts.map +1 -1
- package/dist/pipeline/correlate-external-tokens.d.ts +10 -18
- package/dist/pipeline/correlate-external-tokens.d.ts.map +1 -1
- package/dist/pipeline/discover-files.d.ts +1 -9
- package/dist/pipeline/discover-files.d.ts.map +1 -1
- package/dist/pipeline/discover-packages.d.ts +18 -154
- package/dist/pipeline/discover-packages.d.ts.map +1 -1
- package/dist/pipeline/dynamic-prop-config.d.ts +4 -19
- package/dist/pipeline/dynamic-prop-config.d.ts.map +1 -1
- package/dist/pipeline/engine-adapter.d.ts +16 -52
- package/dist/pipeline/engine-adapter.d.ts.map +1 -1
- package/dist/pipeline/engine-retirement.d.ts +0 -19
- package/dist/pipeline/engine-retirement.d.ts.map +1 -1
- package/dist/pipeline/error-diagnostics.d.ts +0 -24
- package/dist/pipeline/error-diagnostics.d.ts.map +1 -1
- package/dist/pipeline/index.d.ts +9 -8
- package/dist/pipeline/index.d.ts.map +1 -1
- 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 +32 -37
- package/dist/pipeline/manifest-diagnostics.d.ts.map +1 -1
- 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 +9 -33
- package/dist/pipeline/mdx-preprocessor.d.ts.map +1 -1
- package/dist/pipeline/path-aliases.d.ts +0 -19
- package/dist/pipeline/path-aliases.d.ts.map +1 -1
- package/dist/pipeline/post-process-css.d.ts +0 -8
- package/dist/pipeline/post-process-css.d.ts.map +1 -1
- package/dist/pipeline/prefix.d.ts +3 -10
- package/dist/pipeline/prefix.d.ts.map +1 -1
- package/dist/pipeline/replacement-plans.d.ts +10 -34
- package/dist/pipeline/replacement-plans.d.ts.map +1 -1
- package/dist/pipeline/resolve-asset.d.ts +4 -7
- package/dist/pipeline/resolve-asset.d.ts.map +1 -1
- package/dist/pipeline/run-analysis.d.ts +15 -27
- package/dist/pipeline/run-analysis.d.ts.map +1 -1
- 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 +23 -53
- package/dist/pipeline/source-identity.d.ts.map +1 -1
- package/dist/pipeline/source-ingestion.d.ts +48 -53
- package/dist/pipeline/source-ingestion.d.ts.map +1 -1
- package/dist/pipeline/static-css.d.ts +4 -13
- package/dist/pipeline/static-css.d.ts.map +1 -1
- package/dist/pipeline/structural-self-check.d.ts +0 -20
- package/dist/pipeline/structural-self-check.d.ts.map +1 -1
- package/dist/pipeline/svelte-source-adapter.d.ts +5 -12
- package/dist/pipeline/svelte-source-adapter.d.ts.map +1 -1
- package/dist/pipeline/system-config.d.ts +15 -32
- package/dist/pipeline/system-config.d.ts.map +1 -1
- package/dist/pipeline/system-props-module.d.ts +0 -15
- package/dist/pipeline/system-props-module.d.ts.map +1 -1
- package/dist/pipeline/timing-waterfall.d.ts +0 -11
- package/dist/pipeline/timing-waterfall.d.ts.map +1 -1
- package/dist/pipeline/tsconfig-paths.d.ts +2 -3
- package/dist/pipeline/tsconfig-paths.d.ts.map +1 -1
- package/dist/pipeline/unit-fallback.d.ts +0 -5
- package/dist/pipeline/unit-fallback.d.ts.map +1 -1
- package/dist/pipeline/utils.d.ts +1 -1
- package/dist/pipeline/utils.d.ts.map +1 -1
- package/dist/pipeline/watch-keys.d.ts +0 -10
- package/dist/pipeline/watch-keys.d.ts.map +1 -1
- package/dist/{pipeline-lkbOoyAr.mjs → pipeline-DeEENy5C.mjs} +1940 -2263
- package/dist/{pipeline-ojNeABi0.cjs → pipeline-x1yBZ2UP.cjs} +1970 -2269
- package/dist/session/extraction-session.d.ts +110 -316
- package/dist/session/extraction-session.d.ts.map +1 -1
- package/dist/session/index.d.ts +6 -20
- package/dist/session/index.d.ts.map +1 -1
- package/dist/session/published-set.d.ts +45 -25
- package/dist/session/published-set.d.ts.map +1 -1
- package/dist/session/resolve-packages.d.ts +6 -7
- package/dist/session/resolve-packages.d.ts.map +1 -1
- package/dist/session/session-paths.d.ts +38 -72
- package/dist/session/session-paths.d.ts.map +1 -1
- package/dist/session/singleton.d.ts +32 -71
- package/dist/session/singleton.d.ts.map +1 -1
- package/dist/session/timing.d.ts +0 -5
- package/dist/session/timing.d.ts.map +1 -1
- package/dist/session/turbopack-orchestrator.d.ts +17 -33
- package/dist/session/turbopack-orchestrator.d.ts.map +1 -1
- package/dist/session.cjs +530 -677
- package/dist/session.mjs +517 -672
- package/index-v2.js +0 -9
- package/package.json +9 -8
- package/dist/pipeline/external-keyframes.d.ts +0 -23
- package/dist/pipeline/external-keyframes.d.ts.map +0 -1
package/dist/session.cjs
CHANGED
|
@@ -1,36 +1,25 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_pipeline = require("./pipeline-
|
|
2
|
+
const require_pipeline = require("./pipeline-x1yBZ2UP.cjs");
|
|
3
3
|
let crypto = require("crypto");
|
|
4
4
|
let fs = require("fs");
|
|
5
5
|
let path = require("path");
|
|
6
6
|
//#region session/session-paths.ts
|
|
7
7
|
/**
|
|
8
|
-
* Session
|
|
9
|
-
*
|
|
10
|
-
* — shared by the session writer (extraction-session), the webpack
|
|
11
|
-
* plugin/loader, and the Turbopack loader.
|
|
12
|
-
*
|
|
13
|
-
* Deliberately free of singleton/session imports: the Turbopack loader
|
|
14
|
-
* executes in isolated worker processes and its module graph must stay
|
|
15
|
-
* worker-local (guardrail G1) — this module is pure path/shape vocabulary.
|
|
8
|
+
* Session artifact names, path derivations, and artifact shapes. Imports no
|
|
9
|
+
* session or singleton module: the Turbopack loader graph stays worker-local.
|
|
16
10
|
*/
|
|
17
|
-
/** Session-dir-relative name of the replacement-epoch disk witness
|
|
18
|
-
* (openspec: next-webpack-served-transform-coherence, design D2;
|
|
19
|
-
* session-scoped by next-turbopack-served-transform-coherence D2). */
|
|
11
|
+
/** Session-dir-relative name of the replacement-epoch disk witness. */
|
|
20
12
|
const REPLACEMENT_EPOCH_ARTIFACT = "replacements-epoch";
|
|
21
|
-
/** Virtual module id of the emitted system-props module
|
|
22
|
-
* vocabulary shared by the orchestrator and the Turbopack config
|
|
23
|
-
* assembly (hoisted here so the session home stays framework-free). */
|
|
13
|
+
/** Virtual module id of the emitted system-props module. */
|
|
24
14
|
const TURBOPACK_SYSTEM_PROPS_ID = "virtual:animus/system-props";
|
|
25
|
-
/** Session-dir-relative name of the analysis-commit transaction artifact
|
|
26
|
-
* (design D1). */
|
|
15
|
+
/** Session-dir-relative name of the analysis-commit transaction artifact. */
|
|
27
16
|
const ANALYSIS_COMMIT_ARTIFACT = "analysis-commit";
|
|
28
|
-
/** Session-dir-relative name of the analysis-status artifact
|
|
17
|
+
/** Session-dir-relative name of the analysis-status artifact. */
|
|
29
18
|
const ANALYSIS_STATUS_ARTIFACT = "analysis-status.json";
|
|
30
19
|
/** Session-dir-relative name of the manifest payload artifact. */
|
|
31
20
|
const MANIFEST_ARTIFACT = "manifest.json";
|
|
32
|
-
/** Session-dir-relative name of the analysis-inputs payload artifact
|
|
33
|
-
*
|
|
21
|
+
/** Session-dir-relative name of the analysis-inputs payload artifact — the
|
|
22
|
+
* Turbopack loader-worker hydration corpus. */
|
|
34
23
|
const ANALYSIS_INPUTS_ARTIFACT = "analysis-inputs.json";
|
|
35
24
|
/** Session-dir-relative name of the emitted stylesheet artifact. */
|
|
36
25
|
const STYLES_ARTIFACT = "styles.css";
|
|
@@ -38,126 +27,226 @@ const STYLES_ARTIFACT = "styles.css";
|
|
|
38
27
|
const SYSTEM_PROPS_ARTIFACT = "system-props.js";
|
|
39
28
|
/** Session-dir-relative directory holding copied asset() files. */
|
|
40
29
|
const SESSION_ASSETS_DIR = "assets";
|
|
41
|
-
/** The standalone CLI's published-set commit record
|
|
42
|
-
*
|
|
43
|
-
* the CLI writer and the session's start hygiene agree on ONE name — the
|
|
44
|
-
* hygiene confinement gate keys on this record, and a renamed literal on
|
|
45
|
-
* either side would silently re-arm deletion of the CLI's published output. */
|
|
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. */
|
|
46
32
|
const CLI_COMMIT_ARTIFACT = "commit.json";
|
|
47
|
-
/**
|
|
48
|
-
*
|
|
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. */
|
|
49
35
|
const CLI_LOCK_ARTIFACT = "lock.json";
|
|
50
|
-
/**
|
|
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";
|
|
51
44
|
function sessionsRootDir(rootDir) {
|
|
52
|
-
return (0, path.join)(rootDir,
|
|
45
|
+
return (0, path.join)(rootDir, ANIMUS_ARTIFACT_DIR, "sessions");
|
|
53
46
|
}
|
|
54
|
-
/** One session's artifact directory (design D2: session-scoped trees,
|
|
55
|
-
* no global pointer). */
|
|
56
47
|
function sessionArtifactDir(rootDir, sessionId) {
|
|
57
48
|
return (0, path.join)(sessionsRootDir(rootDir), sessionId);
|
|
58
49
|
}
|
|
59
|
-
/** Absolute path of a session's replacement-epoch artifact — the single
|
|
60
|
-
* derivation shared by the session writer, the webpack plugin's
|
|
61
|
-
* watch-ignore entry, and both loaders' file dependencies. Takes the
|
|
62
|
-
* SESSION directory (not the project root) since the artifact was
|
|
63
|
-
* relocated into the session tree. */
|
|
64
50
|
function replacementEpochPath(sessionDir) {
|
|
65
51
|
return (0, path.join)(sessionDir, REPLACEMENT_EPOCH_ARTIFACT);
|
|
66
52
|
}
|
|
67
|
-
/** Absolute path of a session's analysis-commit artifact. */
|
|
68
53
|
function analysisCommitPath(sessionDir) {
|
|
69
54
|
return (0, path.join)(sessionDir, ANALYSIS_COMMIT_ARTIFACT);
|
|
70
55
|
}
|
|
71
|
-
/** Absolute path of a session's analysis-status artifact. */
|
|
72
56
|
function analysisStatusPath(sessionDir) {
|
|
73
57
|
return (0, path.join)(sessionDir, ANALYSIS_STATUS_ARTIFACT);
|
|
74
58
|
}
|
|
75
|
-
/** Absolute path of a session's manifest artifact. */
|
|
76
59
|
function manifestPath(sessionDir) {
|
|
77
60
|
return (0, path.join)(sessionDir, MANIFEST_ARTIFACT);
|
|
78
61
|
}
|
|
79
|
-
/** Absolute path of a session's analysis-inputs artifact. */
|
|
80
62
|
function analysisInputsPath(sessionDir) {
|
|
81
63
|
return (0, path.join)(sessionDir, ANALYSIS_INPUTS_ARTIFACT);
|
|
82
64
|
}
|
|
83
|
-
/** Absolute path of a session's stylesheet artifact. */
|
|
84
65
|
function stylesPath(sessionDir) {
|
|
85
66
|
return (0, path.join)(sessionDir, STYLES_ARTIFACT);
|
|
86
67
|
}
|
|
87
|
-
/** Absolute path of a session's system-props module artifact. */
|
|
88
68
|
function systemPropsPath(sessionDir) {
|
|
89
69
|
return (0, path.join)(sessionDir, SYSTEM_PROPS_ARTIFACT);
|
|
90
70
|
}
|
|
91
|
-
/**
|
|
92
|
-
*
|
|
93
|
-
* (parse/re-stringify could reformat engine-emitted JSON). */
|
|
71
|
+
/** Splices the envelope in as a leading `__animusSession` field without
|
|
72
|
+
* re-serializing: parse/re-stringify would reformat engine-emitted JSON. */
|
|
94
73
|
function envelopeJsonArtifact(payload, envelopeJson) {
|
|
95
74
|
const trimmed = payload.trimStart();
|
|
96
75
|
if (!trimmed.startsWith("{")) return payload;
|
|
97
76
|
if (trimmed.slice(1).trimStart().startsWith("}")) return `{"__animusSession":${envelopeJson}}`;
|
|
98
77
|
return `{"__animusSession":${envelopeJson},${trimmed.slice(1)}`;
|
|
99
78
|
}
|
|
100
|
-
/** Trailing-comment side-band
|
|
101
|
-
*
|
|
79
|
+
/** Trailing-comment side-band: a leading comment would displace the `@layer`
|
|
80
|
+
* declaration consumers pin at offset 0. */
|
|
102
81
|
function envelopeCssArtifact(payload, envelopeJson) {
|
|
103
82
|
return `${payload}\n/* __animusSession ${envelopeJson} */\n`;
|
|
104
83
|
}
|
|
105
|
-
/** Matches the CSS artifact's envelope side-band. */
|
|
106
84
|
const ENVELOPE_CSS_COMMENT_RE = /\/\* __animusSession (\{.*\}) \*\//;
|
|
107
|
-
/** Envelope of a JSON artifact's bytes, or undefined when
|
|
108
|
-
*
|
|
109
|
-
* torn artifact fails closed or degrades. */
|
|
85
|
+
/** Envelope of a JSON artifact's bytes, or undefined when it carries none.
|
|
86
|
+
* Throws on unparseable bytes; callers decide torn-artifact policy. */
|
|
110
87
|
function readJsonEnvelope(bytes) {
|
|
111
88
|
return JSON.parse(bytes).__animusSession;
|
|
112
89
|
}
|
|
113
|
-
/** Envelope of the CSS
|
|
114
|
-
*
|
|
90
|
+
/** Envelope of the CSS side-band comment, or undefined when it is absent.
|
|
91
|
+
* Throws on an unparseable envelope body. */
|
|
115
92
|
function readCssEnvelope(bytes) {
|
|
116
93
|
const match = bytes.match(ENVELOPE_CSS_COMMENT_RE);
|
|
117
94
|
return match ? JSON.parse(match[1]) : void 0;
|
|
118
95
|
}
|
|
119
96
|
//#endregion
|
|
120
97
|
//#region session/published-set.ts
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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. */
|
|
139
129
|
function verifyCommitRecord(dir) {
|
|
140
|
-
let
|
|
130
|
+
let bytes;
|
|
141
131
|
try {
|
|
142
|
-
|
|
132
|
+
bytes = (0, fs.readFileSync)((0, path.join)(dir, CLI_COMMIT_ARTIFACT), "utf-8");
|
|
143
133
|
} catch (error) {
|
|
144
134
|
return [`commit record unreadable: ${String(error)}`];
|
|
145
135
|
}
|
|
146
|
-
|
|
136
|
+
const record = decodeCommitRecord(bytes);
|
|
137
|
+
if (record === null) return ["commit record is not a schema-1 payload record"];
|
|
147
138
|
const failures = [];
|
|
148
139
|
for (const [name, entry] of Object.entries(record.payloads)) try {
|
|
149
|
-
if (require_pipeline.contentHash((0, fs.readFileSync)((0, path.join)(dir, name))) !== entry
|
|
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`);
|
|
150
141
|
} catch (error) {
|
|
151
142
|
failures.push(`${name}: unreadable (${String(error)})`);
|
|
152
143
|
}
|
|
153
144
|
return failures;
|
|
154
145
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
*
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
*/
|
|
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. */
|
|
161
250
|
function collectSessionAssets(sessionDir) {
|
|
162
251
|
if (!sessionDir) return [];
|
|
163
252
|
const dir = (0, path.join)(sessionDir, SESSION_ASSETS_DIR);
|
|
@@ -169,12 +258,6 @@ function collectSessionAssets(sessionDir) {
|
|
|
169
258
|
}
|
|
170
259
|
//#endregion
|
|
171
260
|
//#region session/resolve-packages.ts
|
|
172
|
-
/**
|
|
173
|
-
* Resolve external DS package specifiers to rootDir-relative entry paths.
|
|
174
|
-
* Node-generic (no webpack APIs): Pass 1 walks the consumer's workspaces for
|
|
175
|
-
* matching package names; Pass 2 falls back to `require.resolve`. Specifiers
|
|
176
|
-
* that resolve nowhere are simply omitted (spec: silent skip).
|
|
177
|
-
*/
|
|
178
261
|
function resolvePackagesByName(rootDir, names) {
|
|
179
262
|
if (names.length === 0) return {};
|
|
180
263
|
const nameSet = new Set(names);
|
|
@@ -190,7 +273,8 @@ function resolvePackagesByName(rootDir, names) {
|
|
|
190
273
|
const pkg = JSON.parse((0, fs.readFileSync)((0, path.join)(wsDir, "package.json"), "utf-8"));
|
|
191
274
|
const name = pkg.name || "";
|
|
192
275
|
if (nameSet.has(name)) {
|
|
193
|
-
const
|
|
276
|
+
const main = pkg.main || pkg.module || "index.ts";
|
|
277
|
+
const entryPath = (0, path.resolve)(wsDir, main);
|
|
194
278
|
if ((0, fs.existsSync)(entryPath)) {
|
|
195
279
|
packageMap[name] = (0, path.relative)(rootDir, entryPath);
|
|
196
280
|
resolved.add(name);
|
|
@@ -202,7 +286,8 @@ function resolvePackagesByName(rootDir, names) {
|
|
|
202
286
|
for (const name of nameSet) {
|
|
203
287
|
if (resolved.has(name)) continue;
|
|
204
288
|
try {
|
|
205
|
-
|
|
289
|
+
const entryPath = require.resolve(name, { paths: [rootDir] });
|
|
290
|
+
packageMap[name] = (0, path.relative)(rootDir, entryPath);
|
|
206
291
|
} catch {}
|
|
207
292
|
}
|
|
208
293
|
return packageMap;
|
|
@@ -210,86 +295,96 @@ function resolvePackagesByName(rootDir, names) {
|
|
|
210
295
|
//#endregion
|
|
211
296
|
//#region session/singleton.ts
|
|
212
297
|
/**
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
* Uses globalThis to survive the ESM/CJS module boundary: the plugin is loaded via ESM
|
|
216
|
-
* (next.config.ts import) while the loader is loaded by webpack via require(). Without
|
|
217
|
-
* globalThis, each module system gets its own singleton instance.
|
|
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.
|
|
218
300
|
*/
|
|
219
301
|
const MANIFEST_KEY = "__animus_manifest_json__";
|
|
220
|
-
const
|
|
302
|
+
const ANALYSIS_STARTED_KEY = "__animus_analysis_started__";
|
|
221
303
|
const SHARED_CSS_KEY = "__animus_shared_css__";
|
|
222
304
|
const SHARED_SYSTEM_PROPS_KEY = "__animus_shared_system_props__";
|
|
223
305
|
const SHARED_EXTERNAL_DIRS_KEY = "__animus_external_pkg_dirs__";
|
|
224
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
|
+
};
|
|
225
329
|
function getManifestJson() {
|
|
226
|
-
return
|
|
330
|
+
return singletonStore[MANIFEST_KEY];
|
|
227
331
|
}
|
|
228
332
|
function setManifestJson(json) {
|
|
229
|
-
|
|
333
|
+
singletonStore[MANIFEST_KEY] = json;
|
|
230
334
|
}
|
|
231
|
-
|
|
232
|
-
|
|
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];
|
|
233
341
|
}
|
|
234
|
-
function
|
|
235
|
-
|
|
342
|
+
function setAnalysisStartedPromise(promise) {
|
|
343
|
+
singletonStore[ANALYSIS_STARTED_KEY] = promise;
|
|
236
344
|
}
|
|
237
|
-
function
|
|
238
|
-
|
|
345
|
+
function resetAnalysisStartedPromise() {
|
|
346
|
+
singletonStore[ANALYSIS_STARTED_KEY] = null;
|
|
239
347
|
}
|
|
240
348
|
function getSharedCss() {
|
|
241
|
-
return
|
|
349
|
+
return singletonStore[SHARED_CSS_KEY] || "";
|
|
242
350
|
}
|
|
243
351
|
function setSharedCss(css) {
|
|
244
|
-
|
|
352
|
+
singletonStore[SHARED_CSS_KEY] = css;
|
|
245
353
|
}
|
|
246
354
|
function getSharedSystemProps() {
|
|
247
|
-
return
|
|
355
|
+
return singletonStore[SHARED_SYSTEM_PROPS_KEY] || "";
|
|
248
356
|
}
|
|
249
357
|
function setSharedSystemProps(content) {
|
|
250
|
-
|
|
358
|
+
singletonStore[SHARED_SYSTEM_PROPS_KEY] = content;
|
|
251
359
|
}
|
|
252
360
|
function getSharedExternalDirs() {
|
|
253
|
-
return
|
|
361
|
+
return singletonStore[SHARED_EXTERNAL_DIRS_KEY] || [];
|
|
254
362
|
}
|
|
255
363
|
function setSharedExternalDirs(dirs) {
|
|
256
|
-
|
|
364
|
+
singletonStore[SHARED_EXTERNAL_DIRS_KEY] = dirs;
|
|
257
365
|
}
|
|
258
366
|
function getSharedExternalEntries() {
|
|
259
|
-
return
|
|
367
|
+
return singletonStore[SHARED_EXTERNAL_ENTRIES_KEY] || /* @__PURE__ */ new Map();
|
|
260
368
|
}
|
|
261
369
|
function setSharedExternalEntries(entries) {
|
|
262
|
-
|
|
370
|
+
singletonStore[SHARED_EXTERNAL_ENTRIES_KEY] = entries;
|
|
263
371
|
}
|
|
264
|
-
/** One typed globalThis slot — the single accessor shape every
|
|
265
|
-
* singleton-published value shares (null when unset). */
|
|
266
372
|
function globalSlot(key) {
|
|
267
|
-
const store = globalThis;
|
|
268
373
|
return {
|
|
269
|
-
get: () =>
|
|
374
|
+
get: () => singletonStore[key] ?? null,
|
|
270
375
|
set: (value) => {
|
|
271
|
-
|
|
376
|
+
singletonStore[key] = value;
|
|
272
377
|
}
|
|
273
378
|
};
|
|
274
379
|
}
|
|
275
|
-
const ANALYZED_HASHES_KEY = "__animus_analyzed_hashes__";
|
|
276
|
-
const REPLACEMENT_EPOCH_KEY = "__animus_replacement_epoch__";
|
|
277
|
-
const WATCH_TRANSACTION_KEY = "__animus_watch_transaction__";
|
|
278
|
-
const PROCESS_SESSION_ID_KEY = "__animus_process_session_id__";
|
|
279
|
-
const SESSION_ARTIFACT_DIR_KEY = "__animus_session_artifact_dir__";
|
|
280
|
-
const OWNING_WATCH_SESSION_KEY = "__animus_owning_watch_session__";
|
|
281
|
-
const EXCLUSIVE_SESSION_OWNER_KEY = "__animus_exclusive_session_owner__";
|
|
282
380
|
const analyzedHashesSlot = globalSlot(ANALYZED_HASHES_KEY);
|
|
283
381
|
const replacementEpochSlot = globalSlot(REPLACEMENT_EPOCH_KEY);
|
|
284
382
|
const watchTransactionSlot = globalSlot(WATCH_TRANSACTION_KEY);
|
|
285
383
|
const sessionArtifactDirSlot = globalSlot(SESSION_ARTIFACT_DIR_KEY);
|
|
286
384
|
const owningWatchSessionSlot = globalSlot(OWNING_WATCH_SESSION_KEY);
|
|
287
385
|
/**
|
|
288
|
-
* The session that
|
|
289
|
-
*
|
|
290
|
-
* its own session AND its own watcher with its own modified set; a
|
|
291
|
-
* non-owning session forwards its batch here instead of dropping it (a file
|
|
292
|
-
* only that compiler watches would otherwise never be analyzed).
|
|
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.
|
|
293
388
|
*/
|
|
294
389
|
function getOwningWatchSession() {
|
|
295
390
|
return owningWatchSessionSlot.get();
|
|
@@ -298,12 +393,8 @@ function setOwningWatchSession(session) {
|
|
|
298
393
|
owningWatchSessionSlot.set(session);
|
|
299
394
|
}
|
|
300
395
|
/**
|
|
301
|
-
* Per-file
|
|
302
|
-
*
|
|
303
|
-
* unconditional catching-up guard (openspec:
|
|
304
|
-
* next-webpack-served-transform-coherence, design D4). Includes files
|
|
305
|
-
* analyzed with ZERO animus entries: analyzed identity is membership in the
|
|
306
|
-
* analysis input set, not manifest entry count.
|
|
396
|
+
* Per-file content hashes of the last published analysis (relPath → hash of
|
|
397
|
+
* the exact bytes analyzed), including files that produced zero entries.
|
|
307
398
|
*/
|
|
308
399
|
function getAnalyzedHashes() {
|
|
309
400
|
return analyzedHashesSlot.get();
|
|
@@ -312,52 +403,31 @@ function setAnalyzedHashes(hashes) {
|
|
|
312
403
|
analyzedHashesSlot.set(hashes);
|
|
313
404
|
}
|
|
314
405
|
/**
|
|
315
|
-
* Session identity is per
|
|
316
|
-
*
|
|
317
|
-
* next-turbopack-served-transform-coherence, design D2). The first
|
|
318
|
-
* ExtractionSession constructed in a process claims a fresh randomUUID;
|
|
319
|
-
* every later instance (client/server/RSC compilers each construct one)
|
|
320
|
-
* adopts it, so all compilers alias, watch-ignore, and publish ONE
|
|
321
|
-
* session-scoped artifact tree. Separate invocations (`next dev` +
|
|
322
|
-
* `next build` co-writing) are separate processes and therefore separate
|
|
323
|
-
* sessions by construction.
|
|
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.
|
|
324
408
|
*/
|
|
325
409
|
function claimProcessSessionId() {
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
if (typeof existing === "string" && existing.length > 0) return existing;
|
|
410
|
+
const existing = singletonStore[PROCESS_SESSION_ID_KEY];
|
|
411
|
+
if (isSingletonString(existing) && existing.length > 0) return existing;
|
|
329
412
|
const fresh = (0, crypto.randomUUID)();
|
|
330
|
-
|
|
413
|
+
singletonStore[PROCESS_SESSION_ID_KEY] = fresh;
|
|
331
414
|
return fresh;
|
|
332
415
|
}
|
|
333
416
|
/**
|
|
334
|
-
*
|
|
335
|
-
*
|
|
336
|
-
* shared slots above (manifest, css, artifact dir) plus the one session id
|
|
337
|
-
* mean two CONCURRENT drive loops in one process (a webpack MultiCompiler
|
|
338
|
-
* array config, parallel rollup array builds, two programmatic CLI runs)
|
|
339
|
-
* would share one session directory, overwrite each other's manifests, and
|
|
340
|
-
* delete each other's live trees on dispose. Drivers that run the loop
|
|
341
|
-
* exclusively (the unplugin host) claim for the build's lifetime and
|
|
342
|
-
* release on dispose; SEQUENTIAL claim/release cycles are legal. The Next
|
|
343
|
-
* multi-compiler adoption path stays the non-exclusive default. Throws
|
|
344
|
-
* naming both hosts and the remediation on overlap. The key lives in
|
|
345
|
-
* `SINGLETON_GLOBAL_KEYS`, so per-test global resets clear a leaked claim.
|
|
417
|
+
* Two CONCURRENT drive loops in one process would share one session tree and
|
|
418
|
+
* clobber each other; sequential claim/release cycles are legal.
|
|
346
419
|
*/
|
|
347
420
|
function claimExclusiveSessionOwner(label) {
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
store[EXCLUSIVE_SESSION_OWNER_KEY] = 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;
|
|
352
424
|
return () => {
|
|
353
|
-
if (
|
|
425
|
+
if (singletonStore[EXCLUSIVE_SESSION_OWNER_KEY] === label) delete singletonStore[EXCLUSIVE_SESSION_OWNER_KEY];
|
|
354
426
|
};
|
|
355
427
|
}
|
|
356
428
|
/**
|
|
357
|
-
* Absolute
|
|
358
|
-
*
|
|
359
|
-
* dependency path (the loader shares the process with the pipeline; the
|
|
360
|
-
* Turbopack loader instead receives the directory via its options).
|
|
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.
|
|
361
431
|
*/
|
|
362
432
|
function getSessionArtifactDir() {
|
|
363
433
|
return sessionArtifactDirSlot.get();
|
|
@@ -366,14 +436,8 @@ function setSessionArtifactDir(dir) {
|
|
|
366
436
|
sessionArtifactDirSlot.set(dir);
|
|
367
437
|
}
|
|
368
438
|
/**
|
|
369
|
-
*
|
|
370
|
-
*
|
|
371
|
-
* — openspec: next-webpack-served-transform-coherence, design D5; the
|
|
372
|
-
* served system-props module rides as the served-dependency witness so
|
|
373
|
-
* offline system-props changes move the epoch). Published by the
|
|
374
|
-
* owning session AFTER the manifest so a reader that observes the epoch
|
|
375
|
-
* always observes at least that generation's manifest. (Session
|
|
376
|
-
* attribution lives on the DISK epoch artifact — no in-process mirror.)
|
|
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.
|
|
377
441
|
*/
|
|
378
442
|
function getReplacementEpoch() {
|
|
379
443
|
return replacementEpochSlot.get();
|
|
@@ -382,10 +446,8 @@ function setReplacementEpoch(epoch) {
|
|
|
382
446
|
replacementEpochSlot.set(epoch);
|
|
383
447
|
}
|
|
384
448
|
/**
|
|
385
|
-
* The one in-flight watch-analysis transaction
|
|
386
|
-
*
|
|
387
|
-
* compiler (client/server/RSC — each holds its own session instance) joins
|
|
388
|
-
* this promise instead of proceeding against a pre-transaction generation.
|
|
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.
|
|
389
451
|
*/
|
|
390
452
|
function getWatchTransaction() {
|
|
391
453
|
return watchTransactionSlot.get();
|
|
@@ -393,69 +455,49 @@ function getWatchTransaction() {
|
|
|
393
455
|
function setWatchTransaction(transaction) {
|
|
394
456
|
watchTransactionSlot.set(transaction);
|
|
395
457
|
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
* instances and the webpack loader honor the same choice as the owner. */
|
|
458
|
+
/** Engine selection travels through the singleton so non-owning compilers
|
|
459
|
+
* and the webpack loader honor the owner's choice. */
|
|
399
460
|
function setSharedEngine(engine) {
|
|
400
|
-
|
|
461
|
+
singletonStore[ENGINE_KEY] = engine;
|
|
401
462
|
}
|
|
402
463
|
function getSharedEngine() {
|
|
403
|
-
return
|
|
464
|
+
return singletonStore[ENGINE_KEY] || "v2";
|
|
404
465
|
}
|
|
405
|
-
/** Single
|
|
406
|
-
*
|
|
407
|
-
* module's own .d.ts is the authoritative surface. */
|
|
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. */
|
|
408
468
|
function requireEngine() {
|
|
409
469
|
return require("@animus-ui/extract");
|
|
410
470
|
}
|
|
411
|
-
const V2_ENGINE_KEY = "__animus_v2_engine__";
|
|
412
471
|
/**
|
|
413
|
-
*
|
|
414
|
-
*
|
|
415
|
-
* (hoisted to `createV2EngineApi` in @animus-ui/extract/pipeline — the
|
|
416
|
-
* single authoritative copy shared with vite-plugin). The engine INSTANCE,
|
|
417
|
-
* its analyze-time sources, and the one-shot drift flag all live on
|
|
418
|
-
* globalThis for the same reason the manifest does — the ESM plugin and the
|
|
419
|
-
* CJS webpack loader must see one engine (and one drift warning) across the
|
|
420
|
-
* double-load. (The next-plugin is already process-singleton by design:
|
|
421
|
-
* manifest, css, and system props share the same globalThis keys.)
|
|
422
|
-
* loadSystemModule is exported by both bindings from one engine-neutral
|
|
423
|
-
* Rust crate, so the default path no longer loads the v1 binary.
|
|
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.
|
|
424
474
|
*/
|
|
425
|
-
const V2_SENT_SOURCES_KEY = "__animus_v2_sent_sources__";
|
|
426
|
-
const V2_DRIFT_WARNED_KEY = "__animus_v2_drift_warned__";
|
|
427
475
|
const v2EngineApi = require_pipeline.createV2EngineApi({
|
|
428
476
|
label: "animus",
|
|
429
477
|
isV2: () => getSharedEngine() === "v2",
|
|
430
478
|
loadNativeEngine: requireEngine,
|
|
431
479
|
passThroughUnknownPaths: true,
|
|
432
480
|
store: {
|
|
433
|
-
getEngine: () =>
|
|
481
|
+
getEngine: () => singletonStore[V2_ENGINE_KEY],
|
|
434
482
|
setEngine: (engine) => {
|
|
435
|
-
|
|
483
|
+
singletonStore[V2_ENGINE_KEY] = engine;
|
|
436
484
|
},
|
|
437
|
-
getSentSources: () =>
|
|
485
|
+
getSentSources: () => singletonStore[V2_SENT_SOURCES_KEY] ?? null,
|
|
438
486
|
setSentSources: (sources) => {
|
|
439
|
-
|
|
487
|
+
singletonStore[V2_SENT_SOURCES_KEY] = sources;
|
|
440
488
|
},
|
|
441
|
-
getDriftWarned: () => Boolean(
|
|
489
|
+
getDriftWarned: () => Boolean(singletonStore[V2_DRIFT_WARNED_KEY]),
|
|
442
490
|
setDriftWarned: (value) => {
|
|
443
|
-
|
|
491
|
+
singletonStore[V2_DRIFT_WARNED_KEY] = value;
|
|
444
492
|
}
|
|
445
493
|
}
|
|
446
494
|
});
|
|
447
|
-
const ENGINE_API_OVERRIDE_KEY = "__animus_engine_api_override__";
|
|
448
495
|
function engineApi() {
|
|
449
|
-
const override =
|
|
496
|
+
const override = singletonStore[ENGINE_API_OVERRIDE_KEY];
|
|
450
497
|
return override ? override() : v2EngineApi();
|
|
451
498
|
}
|
|
452
499
|
//#endregion
|
|
453
500
|
//#region session/timing.ts
|
|
454
|
-
/**
|
|
455
|
-
* Verbose build-timing waterfall: JS phases, analysis sub-phases, and the
|
|
456
|
-
* shared Rust phase table. `ANIMUS_TIMING_JSON=1` additionally dumps a
|
|
457
|
-
* machine-readable merged record.
|
|
458
|
-
*/
|
|
459
501
|
function logBuildTimings(bt, rustTiming, log, verbose) {
|
|
460
502
|
if (!verbose) return;
|
|
461
503
|
const pad = (label, width) => label + " ".repeat(Math.max(0, width - label.length));
|
|
@@ -486,28 +528,14 @@ function logBuildTimings(bt, rustTiming, log, verbose) {
|
|
|
486
528
|
if (process.env.ANIMUS_TIMING_JSON === "1") {
|
|
487
529
|
const merged = {};
|
|
488
530
|
for (const [k, v] of Object.entries(bt)) merged[`buildStart.${k}`] = v;
|
|
489
|
-
if (rustTiming) {
|
|
490
|
-
for (const [k, v] of Object.entries(rustTiming)) if (typeof v === "number") merged[`rust.${k}`] = v;
|
|
491
|
-
}
|
|
531
|
+
if (rustTiming) for (const [k, v] of Object.entries(rustTiming)) merged[`rust.${k}`] = v;
|
|
492
532
|
console.error(`[animus:timing] ${JSON.stringify(merged)}`);
|
|
493
533
|
}
|
|
494
534
|
}
|
|
495
535
|
//#endregion
|
|
496
536
|
//#region session/extraction-session.ts
|
|
497
|
-
|
|
498
|
-
* Module id the Rust emitter injects for the extracted stylesheet — also the
|
|
499
|
-
* exact resolve.alias key with-animus registers for it, which the adapter's
|
|
500
|
-
* alias harvesting must skip.
|
|
501
|
-
*/
|
|
502
|
-
const ANIMUS_CSS_MODULE_ID = ".animus/styles.css";
|
|
503
|
-
/** Retention window for sibling session directories (design D2). */
|
|
504
|
-
const SESSION_DIR_MAX_AGE_MS = 1440 * 60 * 1e3;
|
|
505
|
-
/** Watchdog added to the debounce ceiling for status deadlines (D3). */
|
|
537
|
+
const SESSION_DIR_MAX_AGE_MS = 864e5;
|
|
506
538
|
const STATUS_WATCHDOG_MS = 2e3;
|
|
507
|
-
/** Flat legacy `.animus/` artifacts removed at session start — unreachable
|
|
508
|
-
* by session-scoped loaders. The standalone CLI still publishes the three
|
|
509
|
-
* payload names flat; its verified/live sets are fenced off inside
|
|
510
|
-
* `runSessionStartHygiene`. */
|
|
511
539
|
const LEGACY_FLAT_ARTIFACTS = [
|
|
512
540
|
MANIFEST_ARTIFACT,
|
|
513
541
|
ANALYSIS_INPUTS_ARTIFACT,
|
|
@@ -517,22 +545,16 @@ const LEGACY_FLAT_ARTIFACTS = [
|
|
|
517
545
|
ANALYSIS_COMMIT_ARTIFACT,
|
|
518
546
|
ANALYSIS_STATUS_ARTIFACT
|
|
519
547
|
];
|
|
520
|
-
/**
|
|
521
|
-
*
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
*
|
|
530
|
-
* the bundler (Turbopack has no compiler-hook surface), reusing everything
|
|
531
|
-
* here unchanged.
|
|
532
|
-
*
|
|
533
|
-
* Outputs are published through the package singleton (shared CSS,
|
|
534
|
-
* manifest, system props) and written to `.animus/` on disk.
|
|
535
|
-
*/
|
|
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. */
|
|
536
558
|
var ExtractionSession = class {
|
|
537
559
|
/** Set by the adapter before any pipeline run. */
|
|
538
560
|
rootDir = null;
|
|
@@ -542,19 +564,13 @@ var ExtractionSession = class {
|
|
|
542
564
|
/** Physical asset files registered with the host watcher. */
|
|
543
565
|
assetDependencyPaths = /* @__PURE__ */ new Set();
|
|
544
566
|
assetDependencyKeys = /* @__PURE__ */ new Set();
|
|
545
|
-
/**
|
|
546
|
-
*
|
|
547
|
-
* NormalModuleReplacement; Turbopack rejects absolute-path imports, so
|
|
548
|
-
* its driver sets the virtual id that `resolveAlias` maps to disk. */
|
|
567
|
+
/** System-props module id override. Turbopack rejects absolute-path
|
|
568
|
+
* imports, so its driver sets a virtual id; null injects the abs path. */
|
|
549
569
|
systemPropsModuleId = null;
|
|
550
|
-
/** Whether the analysis-inputs hydration corpus is
|
|
551
|
-
*
|
|
552
|
-
* the manifest from memory — the corpus is never persisted. True is set
|
|
553
|
-
* by the Turbopack orchestration driver, whose isolated loader workers
|
|
554
|
-
* replay it (spec: next-turbopack-integration, "Manifest disk artifact" /
|
|
555
|
-
* "Webpack mode skips the hydration corpus"). */
|
|
570
|
+
/** Whether the analysis-inputs hydration corpus is written to disk. Only
|
|
571
|
+
* isolated loader workers replay it; an in-process loader reads memory. */
|
|
556
572
|
persistAnalysisInputs = false;
|
|
557
|
-
/** Absolute directory prefixes for external
|
|
573
|
+
/** Absolute directory prefixes for external packages (loader allowlist). */
|
|
558
574
|
externalPackageDirs = [];
|
|
559
575
|
/** Absolute package dir → owning specifier (cross-source correlation). */
|
|
560
576
|
externalDirOwners = {};
|
|
@@ -562,14 +578,11 @@ var ExtractionSession = class {
|
|
|
562
578
|
externalFileOwners = {};
|
|
563
579
|
/** External package specifier → absolute source entry path. */
|
|
564
580
|
externalSourceEntries = /* @__PURE__ */ new Map();
|
|
565
|
-
/** SourceId
|
|
566
|
-
*
|
|
567
|
-
* realpath'd once per full pipeline; alias→SourceId associations
|
|
568
|
-
* recorded while files exist so deletion resolves through the cache. */
|
|
581
|
+
/** SourceId authority for this generation — roots realpath'd once per
|
|
582
|
+
* pipeline; aliases recorded while files exist so deletions resolve. */
|
|
569
583
|
sourceIdentity = null;
|
|
570
|
-
/** Canonical admitted external root → declared specifiers
|
|
571
|
-
*
|
|
572
|
-
* root share it). Engine watch wiring and tests read this. */
|
|
584
|
+
/** Canonical admitted external root → declared specifiers; duplicate
|
|
585
|
+
* specifiers resolving to one canonical root share an entry. */
|
|
573
586
|
externalRootOwners = /* @__PURE__ */ new Map();
|
|
574
587
|
/** Canonical external root → the extension set its collection walk used
|
|
575
588
|
* (widened for dist-only roots); rewalks and classification share it. */
|
|
@@ -577,90 +590,81 @@ var ExtractionSession = class {
|
|
|
577
590
|
/** Canonical admitted external watch roots — the engine-side watch
|
|
578
591
|
* surface (webpack contextDependencies / Turbopack watchers). */
|
|
579
592
|
externalWatchRoots = [];
|
|
580
|
-
/** Canonical external root →
|
|
581
|
-
*
|
|
582
|
-
* diffs against (design D3). Raw hashes — MDX preprocessing never skews
|
|
583
|
-
* the diff; the recorded abs path is a registered deletion alias, so a
|
|
584
|
-
* reconstructed deletion always resolves through cached identity. */
|
|
593
|
+
/** Canonical external root → sourceKey → {raw content hash, recorded abs}:
|
|
594
|
+
* the inventory a dirty-root rewalk diffs against, hashed before MDX. */
|
|
585
595
|
externalInventory = /* @__PURE__ */ new Map();
|
|
586
|
-
/**
|
|
587
|
-
*
|
|
596
|
+
/** Stable key → message, re-surfaced on every full pipeline until the
|
|
597
|
+
* underlying condition clears. */
|
|
588
598
|
stickyDiagnostics = /* @__PURE__ */ new Map();
|
|
589
|
-
/** Volume-membership predicate for the cross-volume gate
|
|
590
|
-
*
|
|
591
|
-
* (win32 semantics are unit-tested via path.win32), so runtime tests
|
|
592
|
-
* drive the gate through this. */
|
|
599
|
+
/** Volume-membership predicate for the cross-volume gate. Injected seam:
|
|
600
|
+
* the real predicate only discriminates on Windows, so tests replace it. */
|
|
593
601
|
sharesProjectVolume = require_pipeline.sharesVolumeRoot;
|
|
594
|
-
/**
|
|
595
|
-
*
|
|
596
|
-
* DURING collection — after resolution, BEFORE that root's sources are
|
|
597
|
-
* walked — so a watching host can open its handle with no blind gap
|
|
598
|
-
* between scan and watch. `onExternalRootsCommitted` fires once after a
|
|
599
|
-
* successful full-pipeline publication with the complete admitted
|
|
600
|
-
* canonical root set (the host's promote/replay/close-old phase; a
|
|
601
|
-
* failed pipeline never fires it, so the host rolls back). */
|
|
602
|
+
/** `onExternalRootResolved` fires per admitted root DURING collection,
|
|
603
|
+
* before its walk; `onExternalRootsCommitted` only after publication. */
|
|
602
604
|
onExternalRootResolved = null;
|
|
603
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;
|
|
604
609
|
options;
|
|
605
610
|
staticCssJson;
|
|
606
611
|
system = null;
|
|
607
|
-
/**
|
|
608
|
-
|
|
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 = [];
|
|
609
618
|
/** Full package-resolution map from the last full pipeline — replayed by
|
|
610
619
|
* incremental passes (sourceEntries alone omits dist-resolved packages). */
|
|
611
620
|
lastPackageMap = {};
|
|
612
621
|
fileCache = /* @__PURE__ */ new Map();
|
|
613
|
-
|
|
614
|
-
|
|
622
|
+
corpus = require_pipeline.createSourceCorpus({
|
|
623
|
+
engineApi: () => engineApi(),
|
|
624
|
+
prefix: "[animus-next]",
|
|
625
|
+
strict: () => !!this.options.strict,
|
|
626
|
+
warn: (message) => this.warn(message)
|
|
627
|
+
});
|
|
615
628
|
systemDependencyKeys = /* @__PURE__ */ new Set();
|
|
616
629
|
/** Loader-reported dependency paths for bundler watch registration. */
|
|
617
630
|
systemDependencyPaths = [];
|
|
618
631
|
systemDependencyHashes = /* @__PURE__ */ new Map();
|
|
619
632
|
lastSystemPropsHash = null;
|
|
620
|
-
/** Per-payload write guards: payloadHash gates rewrites (byte-identical
|
|
621
|
-
* payloads leave disk untouched); diskHash is the hash of the CURRENT
|
|
622
|
-
* disk bytes (envelope included) recorded in the analysis-commit. null =
|
|
623
|
-
* not yet seeded (first publication reads the artifact's envelope so a
|
|
624
|
-
* same-session restart never rewrites byte-identical artifacts). */
|
|
625
633
|
artifactRecords = {
|
|
626
634
|
manifest: null,
|
|
627
635
|
inputs: null,
|
|
628
636
|
styles: null
|
|
629
637
|
};
|
|
630
|
-
/** Last written analysis-commit; null
|
|
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. */
|
|
631
640
|
lastCommit = null;
|
|
632
|
-
/**
|
|
633
|
-
*
|
|
634
|
-
|
|
635
|
-
|
|
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. */
|
|
636
646
|
sessionId = claimProcessSessionId();
|
|
637
|
-
/** Epoch
|
|
638
|
-
*
|
|
639
|
-
* same-session restart with unchanged plans never rewrites bytes). */
|
|
647
|
+
/** Epoch of the last published analysis; null seeds from disk so a
|
|
648
|
+
* same-session restart with unchanged plans rewrites no bytes. */
|
|
640
649
|
lastEpochValue = null;
|
|
641
|
-
/** Watcher debounce ceiling feeding status deadlines
|
|
650
|
+
/** Watcher debounce ceiling feeding status deadlines. */
|
|
642
651
|
debounceCeilingMs = 75;
|
|
643
652
|
/** Test seam: observes every session-artifact write (name, content)
|
|
644
653
|
* post-rename — write ORDER is part of the transaction contract. */
|
|
645
654
|
onArtifactWrite = null;
|
|
646
|
-
/** Status attempt bookkeeping (design D3). */
|
|
647
655
|
statusAttemptId = 0;
|
|
648
656
|
statusAttemptOpen = false;
|
|
649
|
-
/** Monotonic
|
|
650
|
-
*
|
|
651
|
-
* succeeds and never regresses — every later status write (idle AND
|
|
652
|
-
* failed) carries it, so readiness stays observable across attempts. */
|
|
657
|
+
/** Monotonic readiness witness: flips true on the first complete
|
|
658
|
+
* publication and never regresses; every later status write carries it. */
|
|
653
659
|
firstEmissionComplete = false;
|
|
654
660
|
/** Debounce-window observations pending analysis (sourceKey → hash). */
|
|
655
661
|
debouncePending = /* @__PURE__ */ new Map();
|
|
656
|
-
/** Session-start hygiene (pruning + legacy cleanup) runs once. */
|
|
657
662
|
sessionStartHygieneDone = false;
|
|
658
663
|
lcssTargets = null;
|
|
659
664
|
constructor(options) {
|
|
660
665
|
this.options = options;
|
|
661
666
|
this.staticCssJson = require_pipeline.serializeStaticCss(options.staticCss);
|
|
662
667
|
}
|
|
663
|
-
/** This session's artifact directory. Requires `rootDir` to be set. */
|
|
664
668
|
get sessionDir() {
|
|
665
669
|
return sessionArtifactDir(this.rootDir, this.sessionId);
|
|
666
670
|
}
|
|
@@ -679,24 +683,20 @@ var ExtractionSession = class {
|
|
|
679
683
|
elapsed(t) {
|
|
680
684
|
return this.verbose ? Math.round(performance.now() - t) : 0;
|
|
681
685
|
}
|
|
682
|
-
/**
|
|
683
|
-
*
|
|
684
|
-
/** Diagnostic prefix label — drivers set their own so a host's errors
|
|
685
|
-
* never masquerade as another driver's (inc 05 review S1). The default
|
|
686
|
-
* keeps the Next plugin's historical prefix. */
|
|
686
|
+
/** Diagnostic prefix label — drivers set their own so one host's errors
|
|
687
|
+
* never masquerade as another driver's. */
|
|
687
688
|
driverLabel = "animus-next";
|
|
688
689
|
/** Per-specifier discovery outcomes from the last full collection —
|
|
689
690
|
* driver-consumed reporting surface (the CLI's summary). */
|
|
690
691
|
lastExternalOutcomes = [];
|
|
691
|
-
/** Component count of the last published analysis,
|
|
692
|
-
*
|
|
693
|
-
* manifest JSON every cycle. */
|
|
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
694
|
lastComponentCount = null;
|
|
695
|
-
/**
|
|
696
|
-
*
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
*
|
|
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
700
|
structuralExclude = [];
|
|
701
701
|
scanConfigMemo = null;
|
|
702
702
|
/** Per-pattern exclusion hit counts from this session's matcher —
|
|
@@ -716,17 +716,18 @@ var ExtractionSession = class {
|
|
|
716
716
|
}
|
|
717
717
|
return this.scanConfigMemo;
|
|
718
718
|
}
|
|
719
|
-
/**
|
|
720
|
-
*
|
|
721
|
-
* batch (openspec: next-webpack-served-transform-coherence, design D3).
|
|
722
|
-
* The first entering session runs the transaction; every concurrent
|
|
723
|
-
* entry — same session re-entered, or another compiler's session
|
|
724
|
-
* (client/server/RSC each hold their own instance) — joins the in-flight
|
|
725
|
-
* promise, so no compiler proceeds against a generation older than the
|
|
726
|
-
* one the transaction publishes. A rejected transaction rejects every
|
|
727
|
-
* joiner; the gate always clears.
|
|
728
|
-
*/
|
|
719
|
+
/** One single-flight analysis transaction per event batch: concurrent
|
|
720
|
+
* entries join the in-flight promise; each settled entry reports outcome. */
|
|
729
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) {
|
|
730
731
|
const inflight = getWatchTransaction();
|
|
731
732
|
if (inflight) {
|
|
732
733
|
await inflight;
|
|
@@ -746,13 +747,8 @@ var ExtractionSession = class {
|
|
|
746
747
|
setWatchTransaction(null);
|
|
747
748
|
}
|
|
748
749
|
}
|
|
749
|
-
/**
|
|
750
|
-
*
|
|
751
|
-
* watcher. Serializes behind any in-flight transaction, then runs the
|
|
752
|
-
* forwarded batch as its own transaction — never drops it (a forwarded
|
|
753
|
-
* batch has no other delivery; content-hash diffing makes an
|
|
754
|
-
* already-covered batch a cheap no-op).
|
|
755
|
-
*/
|
|
750
|
+
/** Owner-side entry for a batch a non-owning compiler observed. Serializes
|
|
751
|
+
* behind any in-flight transaction — it has no other delivery. */
|
|
756
752
|
async ingestForwardedBatch(changes) {
|
|
757
753
|
if (!this.system) return;
|
|
758
754
|
for (;;) {
|
|
@@ -768,11 +764,6 @@ var ExtractionSession = class {
|
|
|
768
764
|
setWatchTransaction(null);
|
|
769
765
|
}
|
|
770
766
|
}
|
|
771
|
-
/**
|
|
772
|
-
* Incremental watch pass: geological reset when the system file changed,
|
|
773
|
-
* otherwise content-hash diffing restricted to the watcher's change sets
|
|
774
|
-
* (falling back to a full discovery walk when no sets are provided).
|
|
775
|
-
*/
|
|
776
767
|
async processWatchUpdate(changes) {
|
|
777
768
|
const rootDir = this.rootDir;
|
|
778
769
|
try {
|
|
@@ -796,14 +787,14 @@ var ExtractionSession = class {
|
|
|
796
787
|
}
|
|
797
788
|
else systemHit = changed.find((path$2) => require_pipeline.toWatchKeys(path$2).some((key) => this.systemDependencyKeys.has(key)));
|
|
798
789
|
} catch (err) {
|
|
799
|
-
this.warn(`HMR
|
|
790
|
+
this.warn(`HMR system-reload check failed: ${String(err)}`);
|
|
800
791
|
}
|
|
801
792
|
if (systemHit) {
|
|
802
|
-
this.log(`
|
|
793
|
+
this.log(`system reload: system dependency changed (${(0, path.relative)(rootDir, systemHit)})`);
|
|
803
794
|
this.resetForHmr();
|
|
804
795
|
try {
|
|
805
796
|
const promise = this.runFullPipeline(this.pendingFromBatch(changes));
|
|
806
|
-
|
|
797
|
+
setAnalysisStartedPromise(promise);
|
|
807
798
|
await promise;
|
|
808
799
|
} catch (err) {
|
|
809
800
|
try {
|
|
@@ -907,11 +898,14 @@ var ExtractionSession = class {
|
|
|
907
898
|
let ingested;
|
|
908
899
|
try {
|
|
909
900
|
this.beginStatusAttempt();
|
|
910
|
-
ingested = await this.
|
|
911
|
-
|
|
912
|
-
|
|
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();
|
|
913
907
|
const promise = this.runIncrementalPipeline(ingested.analysisEntries, pending);
|
|
914
|
-
|
|
908
|
+
setAnalysisStartedPromise(promise);
|
|
915
909
|
await promise;
|
|
916
910
|
} catch (err) {
|
|
917
911
|
for (const [key, prior] of priorCacheEntries) if (prior === null) this.fileCache.delete(key);
|
|
@@ -942,60 +936,39 @@ var ExtractionSession = class {
|
|
|
942
936
|
this.writeAnalysisStatus("idle", []);
|
|
943
937
|
}
|
|
944
938
|
}
|
|
945
|
-
/** The shared ingestion policy point (vite-plugin parity BY CODE): the
|
|
946
|
-
* capability guard, facts memo, and warn-dedupe lifecycle live in the
|
|
947
|
-
* pipeline; this host holds only its prefix, strict flag, and warn sink. */
|
|
948
|
-
sourceIngestor = require_pipeline.createSourceIngestor({
|
|
949
|
-
engineApi: () => engineApi(),
|
|
950
|
-
prefix: "[animus-next]",
|
|
951
|
-
strict: () => !!this.options.strict,
|
|
952
|
-
warn: (message) => this.warn(message)
|
|
953
|
-
});
|
|
954
|
-
/** Prepare one raw-source corpus through the shared adaptation boundary. */
|
|
955
|
-
async ingestRawSources(entries) {
|
|
956
|
-
return this.sourceIngestor.ingest(entries);
|
|
957
|
-
}
|
|
958
|
-
/** Ingest and apply the shared per-file quarantine: one invalid original
|
|
959
|
-
* (an `.mdx` with the optional peer absent, an unsupported `.svelte`
|
|
960
|
-
* shape) warns and drops; the rest of the corpus still analyzes. Strict
|
|
961
|
-
* mode throws from the shared policy — callers route that into their own
|
|
962
|
-
* status/rollback handling. */
|
|
963
|
-
async ingestAccepted(entries) {
|
|
964
|
-
const ingested = await this.ingestRawSources(entries ?? this.buildRawEntriesFromCache());
|
|
965
|
-
return require_pipeline.withoutInvalidOriginals(ingested, this.surfaceSourceDiagnostics(ingested.diagnostics));
|
|
966
|
-
}
|
|
967
|
-
/** Surface parser diagnostics under the shared strict/warn policy. */
|
|
968
|
-
surfaceSourceDiagnostics(diagnostics) {
|
|
969
|
-
return this.sourceIngestor.surfaceDiagnostics(diagnostics);
|
|
970
|
-
}
|
|
971
|
-
/**
|
|
972
|
-
* Correlate diagnostics emitted against generated children back to the
|
|
973
|
-
* external package owning their raw original.
|
|
974
|
-
*/
|
|
975
|
-
projectExternalFileOwners(result, rawOwners) {
|
|
976
|
-
const projected = {};
|
|
977
|
-
for (const owner of Object.values(result.ownership)) {
|
|
978
|
-
const packageOwner = rawOwners[owner.originalPath];
|
|
979
|
-
if (!packageOwner) continue;
|
|
980
|
-
projected[owner.originalPath] = packageOwner;
|
|
981
|
-
for (const analysisPath of owner.analysisPaths) projected[analysisPath] = packageOwner;
|
|
982
|
-
}
|
|
983
|
-
return projected;
|
|
984
|
-
}
|
|
985
|
-
/** Publish the raw cache and complete parser projection atomically. */
|
|
986
939
|
publishSourceIngestion(result) {
|
|
987
|
-
this.sourceIngestor.markPublished(result);
|
|
988
940
|
this.fileCache = new Map(result.originalEntries.map((entry) => [entry.path, {
|
|
989
941
|
hash: entry.hash,
|
|
990
942
|
source: entry.source
|
|
991
943
|
}]));
|
|
992
|
-
this.
|
|
993
|
-
hash: entry.hash,
|
|
994
|
-
source: entry.source
|
|
995
|
-
}]));
|
|
996
|
-
this.sourceOwnership = result.ownership;
|
|
944
|
+
this.corpus.publish(result);
|
|
997
945
|
}
|
|
946
|
+
/** Full analysis + publication, registered in the one in-flight
|
|
947
|
+
* transaction slot; a nested reload leaves the enclosing registration. */
|
|
998
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) {
|
|
999
972
|
const pipelineStart = this.now();
|
|
1000
973
|
const bt = {};
|
|
1001
974
|
this.runSessionStartHygiene();
|
|
@@ -1009,6 +982,7 @@ var ExtractionSession = class {
|
|
|
1009
982
|
rootDir,
|
|
1010
983
|
prefix: this.options.prefix
|
|
1011
984
|
});
|
|
985
|
+
this.systemVocabularyDiagnostics = require_pipeline.vocabularyWitnessDiagnostics(this.system.vocabularyWitnessesJson);
|
|
1012
986
|
this.assetCopyCache.clear();
|
|
1013
987
|
this.assetDependencyPaths.clear();
|
|
1014
988
|
this.assetDependencyKeys.clear();
|
|
@@ -1049,6 +1023,7 @@ var ExtractionSession = class {
|
|
|
1049
1023
|
const packageNames = require_pipeline.extractSystemFilePackages(resolvedSystemPath);
|
|
1050
1024
|
const preResolved = resolvePackagesByName(rootDir, packageNames);
|
|
1051
1025
|
const rawExternalFiles = /* @__PURE__ */ new Map();
|
|
1026
|
+
const ingestionFailures = [];
|
|
1052
1027
|
const collected = await require_pipeline.collectExternalPackageSources({
|
|
1053
1028
|
specifiers: packageNames,
|
|
1054
1029
|
resolveSpecifier: (name) => preResolved[name] ? (0, path.resolve)(rootDir, preResolved[name]) : null,
|
|
@@ -1058,7 +1033,7 @@ var ExtractionSession = class {
|
|
|
1058
1033
|
onSourceRead: (source, _relPath, absPath) => {
|
|
1059
1034
|
rawExternalFiles.set(absPath, require_pipeline.contentHash(source));
|
|
1060
1035
|
},
|
|
1061
|
-
onUnreadable: (relPath, err) =>
|
|
1036
|
+
onUnreadable: (relPath, err) => ingestionFailures.push(require_pipeline.unreadableSourceDiagnostic(relPath, err)),
|
|
1062
1037
|
onPackageResolved: (_specifier, packageDir) => {
|
|
1063
1038
|
if (!this.onExternalRootResolved) return;
|
|
1064
1039
|
if (!this.sharesProjectVolume(rootDir, packageDir)) return;
|
|
@@ -1070,6 +1045,7 @@ var ExtractionSession = class {
|
|
|
1070
1045
|
}
|
|
1071
1046
|
});
|
|
1072
1047
|
this.lastExternalOutcomes = collected.outcomes;
|
|
1048
|
+
this.ingestionFailureDiagnostics = ingestionFailures;
|
|
1073
1049
|
for (const record of collected.outcomes) if (record.outcome === "empty") this.warn(`include '${record.specifier}' resolved but discovered no component sources`);
|
|
1074
1050
|
const unresolvableMessage = require_pipeline.unresolvableIncludesMessage(collected.outcomes);
|
|
1075
1051
|
if (unresolvableMessage !== null) {
|
|
@@ -1138,23 +1114,17 @@ var ExtractionSession = class {
|
|
|
1138
1114
|
});
|
|
1139
1115
|
}
|
|
1140
1116
|
this.externalPackageDirs = admitted.packageDirs;
|
|
1141
|
-
if (this.system && admitted.keyframesScanEntries.size > 0) {
|
|
1142
|
-
const api = engineApi();
|
|
1143
|
-
const merge = require_pipeline.mergeExternalKeyframes((entry, root) => api.scanKeyframesExports(entry, root), this.system.keyframesJson, admitted.keyframesScanEntries.values(), this.rootDir);
|
|
1144
|
-
this.system.keyframesJson = merge.keyframesJson;
|
|
1145
|
-
this.externalKeyframesDiagnostics = merge.diagnostics;
|
|
1146
|
-
} else this.externalKeyframesDiagnostics = [];
|
|
1147
1117
|
bt.packageResolve = this.elapsed(t);
|
|
1148
1118
|
this.beginStatusAttempt();
|
|
1149
1119
|
let accepted;
|
|
1150
1120
|
try {
|
|
1151
|
-
accepted = await this.
|
|
1121
|
+
accepted = await this.corpus.prepare(rawEntries);
|
|
1152
1122
|
} catch (err) {
|
|
1153
1123
|
this.debouncePending.clear();
|
|
1154
1124
|
this.writeAnalysisStatus("failed", pending, String(err));
|
|
1155
1125
|
throw err;
|
|
1156
1126
|
}
|
|
1157
|
-
this.externalFileOwners =
|
|
1127
|
+
this.externalFileOwners = require_pipeline.projectExternalFileOwners(accepted, this.externalFileOwners);
|
|
1158
1128
|
bt.fileCount = accepted.analysisEntries.length;
|
|
1159
1129
|
require_pipeline.clearEngineCache(engineApi);
|
|
1160
1130
|
await this.analyzeAndEmit(accepted.analysisEntries, packageMap, false, bt, pipelineStart, pending);
|
|
@@ -1168,10 +1138,8 @@ var ExtractionSession = class {
|
|
|
1168
1138
|
throw err;
|
|
1169
1139
|
}
|
|
1170
1140
|
}
|
|
1171
|
-
/**
|
|
1172
|
-
*
|
|
1173
|
-
* deep-copied fields are the ones later steps mutate in place rather
|
|
1174
|
-
* than reassign; everything else is rebound wholesale on success. */
|
|
1141
|
+
/** Per-generation source state captured as ONE object so the restore
|
|
1142
|
+
* cannot omit a member; deep-copied fields are mutated in place. */
|
|
1175
1143
|
snapshotSourceState() {
|
|
1176
1144
|
return {
|
|
1177
1145
|
sourceIdentity: this.sourceIdentity,
|
|
@@ -1184,20 +1152,23 @@ var ExtractionSession = class {
|
|
|
1184
1152
|
externalFileOwners: this.externalFileOwners,
|
|
1185
1153
|
externalSourceEntries: this.externalSourceEntries,
|
|
1186
1154
|
externalPackageDirs: this.externalPackageDirs,
|
|
1187
|
-
|
|
1155
|
+
systemVocabularyDiagnostics: this.systemVocabularyDiagnostics,
|
|
1156
|
+
ingestionFailureDiagnostics: this.ingestionFailureDiagnostics
|
|
1188
1157
|
};
|
|
1189
1158
|
}
|
|
1190
1159
|
restoreSourceState(snapshot) {
|
|
1191
1160
|
Object.assign(this, snapshot);
|
|
1192
1161
|
}
|
|
1193
|
-
/**
|
|
1194
|
-
*
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
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. */
|
|
1199
1170
|
resetForHmr() {
|
|
1200
|
-
|
|
1171
|
+
resetAnalysisStartedPromise();
|
|
1201
1172
|
this.artifactRecords = {
|
|
1202
1173
|
manifest: null,
|
|
1203
1174
|
inputs: null,
|
|
@@ -1205,12 +1176,8 @@ var ExtractionSession = class {
|
|
|
1205
1176
|
};
|
|
1206
1177
|
this.lastSystemPropsHash = null;
|
|
1207
1178
|
}
|
|
1208
|
-
/**
|
|
1209
|
-
*
|
|
1210
|
-
* evidence for the status file's pending set on the geological-reset
|
|
1211
|
-
* path, where the batch's component edits ride along with the system
|
|
1212
|
-
* edit (design D3: loaders wait only on observed inputs).
|
|
1213
|
-
*/
|
|
1179
|
+
/** The (sourceKey, observed hash) pairs of a watch batch — the status
|
|
1180
|
+
* file's pending set; loaders wait only on observed inputs. */
|
|
1214
1181
|
pendingFromBatch(changes) {
|
|
1215
1182
|
const { excludeMatcher, extensionsSet } = this.resolveScanConfig();
|
|
1216
1183
|
const pending = [];
|
|
@@ -1226,17 +1193,8 @@ var ExtractionSession = class {
|
|
|
1226
1193
|
}
|
|
1227
1194
|
return pending;
|
|
1228
1195
|
}
|
|
1229
|
-
/**
|
|
1230
|
-
*
|
|
1231
|
-
* watcher's change sets into explicit FILE events and external ROOT hits
|
|
1232
|
-
* (a reported path that IS an admitted root, or a directory inside one),
|
|
1233
|
-
* rewalk each dirty root through the shared discovery policy, diff the
|
|
1234
|
-
* SourceId+raw-hash inventory against the previous generation, and merge
|
|
1235
|
-
* the reconstructed created/edited/deleted deltas with the explicit
|
|
1236
|
-
* events (identity-level dedup happens downstream at the cache gate).
|
|
1237
|
-
* Deletion falls out of the inventory diff — webpack may report only the
|
|
1238
|
-
* directory (probe-proven).
|
|
1239
|
-
*/
|
|
1196
|
+
/** Rewalk dirty external roots and diff the inventory into created,
|
|
1197
|
+
* edited and deleted deltas: a watcher may report only the directory. */
|
|
1240
1198
|
reconcileExternalRoots(changes) {
|
|
1241
1199
|
const identity = this.sourceIdentity;
|
|
1242
1200
|
if (!identity) return changes;
|
|
@@ -1289,14 +1247,8 @@ var ExtractionSession = class {
|
|
|
1289
1247
|
removedFiles: removed
|
|
1290
1248
|
};
|
|
1291
1249
|
}
|
|
1292
|
-
/**
|
|
1293
|
-
*
|
|
1294
|
-
* (lexical containment — existing local semantics preserved; consumer
|
|
1295
|
-
* exclude patterns apply) or an admitted external member (identity
|
|
1296
|
-
* resolution with symlink-escape rejection; package-relative excludes
|
|
1297
|
-
* mirror the collection walk's filters — guardrail G1, one policy).
|
|
1298
|
-
* Returns null for dropped paths.
|
|
1299
|
-
*/
|
|
1250
|
+
/** Route one watcher path: a project-root member (consumer excludes apply)
|
|
1251
|
+
* or an admitted external member (symlink escapes rejected). */
|
|
1300
1252
|
classifyWatchPath(absPath, scan) {
|
|
1301
1253
|
const ext = (0, path.extname)(absPath);
|
|
1302
1254
|
const rootDir = this.rootDir;
|
|
@@ -1326,51 +1278,13 @@ var ExtractionSession = class {
|
|
|
1326
1278
|
owningRoot: resolved.owningRoot
|
|
1327
1279
|
};
|
|
1328
1280
|
}
|
|
1329
|
-
/** Build full raw originals for one shared adaptation attempt. */
|
|
1330
|
-
buildRawEntriesFromCache() {
|
|
1331
|
-
const entries = [];
|
|
1332
|
-
for (const [path$8, { hash, source }] of this.fileCache) entries.push({
|
|
1333
|
-
path: path$8,
|
|
1334
|
-
source,
|
|
1335
|
-
hash
|
|
1336
|
-
});
|
|
1337
|
-
return entries;
|
|
1338
|
-
}
|
|
1339
|
-
/**
|
|
1340
|
-
* Run incremental pipeline with cache-aware file entries.
|
|
1341
|
-
* Reuses system config from the last full pipeline run.
|
|
1342
|
-
*/
|
|
1343
1281
|
async runIncrementalPipeline(fileEntries, pending = []) {
|
|
1344
1282
|
const bt = {};
|
|
1345
1283
|
const pipelineStart = this.now();
|
|
1346
1284
|
await this.analyzeAndEmit(fileEntries, this.lastPackageMap, true, bt, pipelineStart, pending);
|
|
1347
1285
|
}
|
|
1348
|
-
/**
|
|
1349
|
-
*
|
|
1350
|
-
* that routes every manifest through the shared `runProjectAnalysis`,
|
|
1351
|
-
* reachable from both runFullPipeline (production) and
|
|
1352
|
-
* runIncrementalPipeline (HMR).
|
|
1353
|
-
*
|
|
1354
|
-
* Owns diagnostic surfacing, CSS assembly + styles.css write guard,
|
|
1355
|
-
* system-props module emit, and the timing log. The engine's own
|
|
1356
|
-
* `dev_mode` (reconciliation pruning) is derived separately via
|
|
1357
|
-
* `engineDevMode` — an explicit `mode` option overrides the pipeline
|
|
1358
|
-
* path there. For everything else the `devMode` flag is the ONLY
|
|
1359
|
-
* behavioral fork:
|
|
1360
|
-
*
|
|
1361
|
-
* - `false` (production): computes bt.analysis + logs the extraction
|
|
1362
|
-
* report, and writes system-props.js UNCONDITIONALLY (no
|
|
1363
|
-
* lastSystemPropsHash guard).
|
|
1364
|
-
* - `true` (HMR): skips the report log, and guards the system-props.js
|
|
1365
|
-
* write by lastSystemPropsHash.
|
|
1366
|
-
*
|
|
1367
|
-
* The disk artifacts land under the session directory in transaction
|
|
1368
|
-
* order (design D1): manifest → analysis-inputs (Turbopack orchestration
|
|
1369
|
-
* only) → styles.css → system-props → analysis-commit →
|
|
1370
|
-
* replacements-epoch (last, only when moved). The analysis-status file walks starting → analyzing →
|
|
1371
|
-
* committing → idle around the attempt, landing in `failed` (with the
|
|
1372
|
-
* diagnostic) on any throw (design D3).
|
|
1373
|
-
*/
|
|
1286
|
+
/** Shared analysis + emit core for both pipelines. `devMode` forks only
|
|
1287
|
+
* the report log and the system-props write guard. */
|
|
1374
1288
|
async analyzeAndEmit(fileEntries, packageMap, devMode, bt, pipelineStart, pending = []) {
|
|
1375
1289
|
this.beginStatusAttempt();
|
|
1376
1290
|
this.debouncePending.clear();
|
|
@@ -1384,16 +1298,8 @@ var ExtractionSession = class {
|
|
|
1384
1298
|
throw err;
|
|
1385
1299
|
}
|
|
1386
1300
|
}
|
|
1387
|
-
/**
|
|
1388
|
-
*
|
|
1389
|
-
* pruning) is an EMISSION decision, so an explicit `mode` option wins over
|
|
1390
|
-
* the pipeline path (core-options: `mode` "decides emitted bytes …
|
|
1391
|
-
* engine devMode"). Without an explicit mode the historical per-pipeline
|
|
1392
|
-
* default applies: full = production pruning, incremental = dev retention.
|
|
1393
|
-
* A pinned-production watch must not flip to unpruned CSS on its first
|
|
1394
|
-
* incremental republication, and a pinned-development full build must
|
|
1395
|
-
* retain all components.
|
|
1396
|
-
*/
|
|
1301
|
+
/** An explicit `mode` wins over the pipeline path: a pinned-production
|
|
1302
|
+
* watch must not emit unpruned CSS on its first incremental republish. */
|
|
1397
1303
|
engineDevMode(pipelineDefault) {
|
|
1398
1304
|
if (this.options.mode !== void 0) return this.options.mode === "development";
|
|
1399
1305
|
return pipelineDefault;
|
|
@@ -1419,7 +1325,7 @@ var ExtractionSession = class {
|
|
|
1419
1325
|
...analysisOptions,
|
|
1420
1326
|
warn: (message) => this.warn(message),
|
|
1421
1327
|
strict: this.options.strict,
|
|
1422
|
-
extraDiagnostics: this.
|
|
1328
|
+
extraDiagnostics: [...this.systemVocabularyDiagnostics, ...this.ingestionFailureDiagnostics]
|
|
1423
1329
|
});
|
|
1424
1330
|
require_pipeline.assertNoErrorDiagnostics(result.manifest?.diagnostics);
|
|
1425
1331
|
require_pipeline.enforceExternalTokenContracts({
|
|
@@ -1440,7 +1346,7 @@ var ExtractionSession = class {
|
|
|
1440
1346
|
bt.analysis = (bt.jsonSerialize ?? 0) + (bt.rustExtract ?? 0) + (bt.jsonParse ?? 0);
|
|
1441
1347
|
if (manifest?.report) this.log(`Extracted ${manifest.report.components_extracted ?? "?"}/${manifest.report.components_total ?? "?"} components (${bt.analysis}ms)`);
|
|
1442
1348
|
}
|
|
1443
|
-
const globalCss = this.substituteAssetReferences(result.globalCss, devMode);
|
|
1349
|
+
const globalCss = this.substituteAssetReferences(result.globalCss, !devMode || this.staleAssetPruning === "every-cycle");
|
|
1444
1350
|
const { declaration, variables, body } = require_pipeline.assembleStylesheet({
|
|
1445
1351
|
layers: this.options.layers,
|
|
1446
1352
|
variableCss: system.variableCss,
|
|
@@ -1497,8 +1403,8 @@ var ExtractionSession = class {
|
|
|
1497
1403
|
this.statusAttemptOpen = true;
|
|
1498
1404
|
}
|
|
1499
1405
|
}
|
|
1500
|
-
/** Write the session's analysis-status artifact
|
|
1501
|
-
*
|
|
1406
|
+
/** Write the session's analysis-status artifact. Terminal states
|
|
1407
|
+
* (idle/failed) close the attempt. */
|
|
1502
1408
|
writeAnalysisStatus(state, pending, diagnostic) {
|
|
1503
1409
|
const status = {
|
|
1504
1410
|
schema: 2,
|
|
@@ -1506,19 +1412,15 @@ var ExtractionSession = class {
|
|
|
1506
1412
|
attemptId: this.statusAttemptId,
|
|
1507
1413
|
state,
|
|
1508
1414
|
pending,
|
|
1509
|
-
deadlineAt: Date.now() + this.debounceCeilingMs + STATUS_WATCHDOG_MS
|
|
1510
|
-
...diagnostic !== void 0 ? { diagnostic } : {},
|
|
1511
|
-
ready: this.firstEmissionComplete
|
|
1415
|
+
deadlineAt: Date.now() + this.debounceCeilingMs + STATUS_WATCHDOG_MS
|
|
1512
1416
|
};
|
|
1417
|
+
if (diagnostic !== void 0) status.diagnostic = diagnostic;
|
|
1418
|
+
status.ready = this.firstEmissionComplete;
|
|
1513
1419
|
this.writeSessionArtifact(ANALYSIS_STATUS_ARTIFACT, JSON.stringify(status));
|
|
1514
1420
|
if (state === "idle" || state === "failed") this.statusAttemptOpen = false;
|
|
1515
1421
|
}
|
|
1516
|
-
/**
|
|
1517
|
-
*
|
|
1518
|
-
* during the debounce window so a loader running ahead of the analysis
|
|
1519
|
-
* has positive evidence to wait on (design D3 'debouncing'). Paths are
|
|
1520
|
-
* filtered by the scan config and hashed at observation time.
|
|
1521
|
-
*/
|
|
1422
|
+
/** Record watch events observed during the debounce window so a loader
|
|
1423
|
+
* running ahead of the analysis has evidence to wait on. */
|
|
1522
1424
|
noteDebouncedWatchEvents(absPaths) {
|
|
1523
1425
|
if (!this.rootDir) return;
|
|
1524
1426
|
const additions = this.pendingFromBatch({ modifiedFiles: new Set(absPaths) });
|
|
@@ -1539,14 +1441,8 @@ var ExtractionSession = class {
|
|
|
1539
1441
|
}
|
|
1540
1442
|
/** One pending microtask flushes a burst of debounce observations. */
|
|
1541
1443
|
debounceStatusWriteScheduled = false;
|
|
1542
|
-
/**
|
|
1543
|
-
*
|
|
1544
|
-
* `{sessionId, generation, replacementEpoch, payloadHash}` and rewritten
|
|
1545
|
-
* only when the PAYLOAD bytes changed (byte-identical re-analyses leave
|
|
1546
|
-
* disk untouched — spec: "Unchanged manifest is not rewritten"). The
|
|
1547
|
-
* recorded diskHash (hash of the enveloped bytes) feeds the
|
|
1548
|
-
* analysis-commit.
|
|
1549
|
-
*/
|
|
1444
|
+
/** Write one enveloped payload artifact, rewritten only when the PAYLOAD
|
|
1445
|
+
* bytes changed; the recorded diskHash feeds the analysis-commit. */
|
|
1550
1446
|
publishPayloadArtifact(key, name, payload, epoch, generation, wrap) {
|
|
1551
1447
|
const payloadHash = require_pipeline.contentHash(payload);
|
|
1552
1448
|
if (this.artifactRecords[key] === null) this.artifactRecords[key] = this.seedPayloadRecord(key, name);
|
|
@@ -1580,7 +1476,7 @@ var ExtractionSession = class {
|
|
|
1580
1476
|
} catch {
|
|
1581
1477
|
return null;
|
|
1582
1478
|
}
|
|
1583
|
-
if (!envelope ||
|
|
1479
|
+
if (!envelope || !isDiskString(envelope.payloadHash)) return null;
|
|
1584
1480
|
return {
|
|
1585
1481
|
payloadHash: envelope.payloadHash,
|
|
1586
1482
|
diskHash: require_pipeline.contentHash(bytes)
|
|
@@ -1590,18 +1486,13 @@ var ExtractionSession = class {
|
|
|
1590
1486
|
seedCommitFromDisk() {
|
|
1591
1487
|
try {
|
|
1592
1488
|
const parsed = JSON.parse((0, fs.readFileSync)(analysisCommitPath(this.sessionDir), "utf-8"));
|
|
1593
|
-
return parsed.schema === 1 && parsed.sessionId === this.sessionId &&
|
|
1489
|
+
return parsed.schema === 1 && parsed.sessionId === this.sessionId && isDiskNumber(parsed.generation) ? parsed : null;
|
|
1594
1490
|
} catch {
|
|
1595
1491
|
return null;
|
|
1596
1492
|
}
|
|
1597
1493
|
}
|
|
1598
|
-
/**
|
|
1599
|
-
*
|
|
1600
|
-
* Written AFTER every payload and BEFORE the epoch; skipped entirely when
|
|
1601
|
-
* the payload set (disk hashes) and epoch are unchanged, so a no-op
|
|
1602
|
-
* re-analysis neither re-keys loader-worker hydration nor burns a
|
|
1603
|
-
* generation.
|
|
1604
|
-
*/
|
|
1494
|
+
/** Publish the analysis-commit: written after every payload and before the
|
|
1495
|
+
* epoch, skipped when the payload hashes and epoch are unchanged. */
|
|
1605
1496
|
publishAnalysisCommit(epoch, generation) {
|
|
1606
1497
|
const manifestHash = this.artifactRecords.manifest?.diskHash ?? "";
|
|
1607
1498
|
const inputsHash = this.persistAnalysisInputs ? this.artifactRecords.inputs?.diskHash ?? "" : void 0;
|
|
@@ -1614,21 +1505,14 @@ var ExtractionSession = class {
|
|
|
1614
1505
|
generation,
|
|
1615
1506
|
replacementEpoch: epoch,
|
|
1616
1507
|
manifestHash,
|
|
1617
|
-
...inputsHash !== void 0 ? { inputsHash } : {},
|
|
1618
1508
|
stylesHash
|
|
1619
1509
|
};
|
|
1510
|
+
if (inputsHash !== void 0) commit.inputsHash = inputsHash;
|
|
1620
1511
|
this.writeSessionArtifact(ANALYSIS_COMMIT_ARTIFACT, JSON.stringify(commit));
|
|
1621
1512
|
this.lastCommit = commit;
|
|
1622
1513
|
}
|
|
1623
|
-
/**
|
|
1624
|
-
*
|
|
1625
|
-
* disk witness `{schema, sessionId, epoch}`, rewritten ONLY when the
|
|
1626
|
-
* epoch VALUE changes so style-only analyses and same-session restarts
|
|
1627
|
-
* leave bytes and mtime untouched, then expose the value through the
|
|
1628
|
-
* singleton for the plugin's needBuild fan-out and the loader's catch-up
|
|
1629
|
-
* re-check. Every VALUE move also reconciles sibling sessions' epoch
|
|
1630
|
-
* artifacts (below) — the webpack cold-cache validity witness.
|
|
1631
|
-
*/
|
|
1514
|
+
/** Maintain the session's epoch witness, rewritten ONLY when the value
|
|
1515
|
+
* changes, then expose it through the singleton. */
|
|
1632
1516
|
publishReplacementEpoch(epoch) {
|
|
1633
1517
|
const onDisk = this.diskEpochValue();
|
|
1634
1518
|
if (this.lastEpochValue === null) this.lastEpochValue = onDisk;
|
|
@@ -1643,26 +1527,13 @@ var ExtractionSession = class {
|
|
|
1643
1527
|
}
|
|
1644
1528
|
setReplacementEpoch(epoch);
|
|
1645
1529
|
}
|
|
1646
|
-
/**
|
|
1647
|
-
* Sibling epoch reconciliation — the cross-session half of the webpack
|
|
1648
|
-
* persistent-cache witness (spec: dev-served-transform-coherence,
|
|
1649
|
-
* "Offline change invalidates restored modules"). Restored-module
|
|
1650
|
-
* snapshots reference the epoch artifact of the session that BUILT them;
|
|
1651
|
-
* with session-scoped trees that file would otherwise sit untouched
|
|
1652
|
-
* forever, keeping stale snapshots valid. Whenever this session's epoch
|
|
1653
|
-
* value moves, delete every sibling epoch artifact that disagrees (their
|
|
1654
|
-
* snapshots must invalidate — restore-on-demand then rebuilds from the
|
|
1655
|
-
* current generation) and leave agreeing siblings byte-untouched (warm
|
|
1656
|
-
* restores stay valid). Deleting a stale artifact is pruning, not a
|
|
1657
|
-
* foreign-session write; races with concurrent sessions are tolerated
|
|
1658
|
-
* (S14).
|
|
1659
|
-
*/
|
|
1660
|
-
/** Sibling ids already reconciled away (artifact deleted or absent) —
|
|
1661
|
-
* nothing left to invalidate for them on later moves. */
|
|
1662
|
-
reconciledSiblingIds = /* @__PURE__ */ new Set();
|
|
1663
1530
|
/** sessions-root listing memo, keyed by the root dir's mtime — a new or
|
|
1664
1531
|
* pruned sibling DIRECTORY moves it; agreeing siblings stay listed. */
|
|
1665
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. */
|
|
1666
1537
|
reconcileSiblingEpochs(epoch) {
|
|
1667
1538
|
const rootPath = sessionsRootDir(this.rootDir);
|
|
1668
1539
|
let entries;
|
|
@@ -1681,61 +1552,61 @@ var ExtractionSession = class {
|
|
|
1681
1552
|
}
|
|
1682
1553
|
for (const entry of entries) {
|
|
1683
1554
|
if (entry === this.sessionId) continue;
|
|
1684
|
-
|
|
1685
|
-
const siblingEpochPath = (0, path.join)(
|
|
1555
|
+
const siblingDir = (0, path.join)(rootPath, entry);
|
|
1556
|
+
const siblingEpochPath = (0, path.join)(siblingDir, REPLACEMENT_EPOCH_ARTIFACT);
|
|
1686
1557
|
try {
|
|
1687
1558
|
if (JSON.parse((0, fs.readFileSync)(siblingEpochPath, "utf-8")).epoch === epoch) continue;
|
|
1688
1559
|
} catch (err) {
|
|
1689
|
-
if (err?.code === "ENOENT")
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
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;
|
|
1693
1566
|
}
|
|
1694
1567
|
try {
|
|
1695
1568
|
(0, fs.unlinkSync)(siblingEpochPath);
|
|
1696
1569
|
} catch {}
|
|
1697
|
-
this.reconciledSiblingIds.add(entry);
|
|
1698
1570
|
}
|
|
1699
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
|
+
}
|
|
1700
1587
|
/** Epoch value held by this session's on-disk artifact, or null when
|
|
1701
1588
|
* absent, unreadable, or not the expected schema. */
|
|
1702
1589
|
diskEpochValue() {
|
|
1703
1590
|
try {
|
|
1704
1591
|
const parsed = JSON.parse((0, fs.readFileSync)(replacementEpochPath(this.sessionDir), "utf-8"));
|
|
1705
|
-
return parsed.schema === 1 &&
|
|
1592
|
+
return parsed.schema === 1 && isDiskString(parsed.epoch) ? parsed.epoch : null;
|
|
1706
1593
|
} catch {
|
|
1707
1594
|
return null;
|
|
1708
1595
|
}
|
|
1709
1596
|
}
|
|
1710
|
-
/** True when the flat `.animus/` advisory lock
|
|
1711
|
-
*
|
|
1712
|
-
* tree right now, and any instantaneous inconsistency is its in-flight
|
|
1713
|
-
* write, not debris. */
|
|
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. */
|
|
1714
1599
|
cliWriterHoldsLock(animusDir) {
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
} catch {
|
|
1719
|
-
return false;
|
|
1720
|
-
}
|
|
1721
|
-
if (typeof holder.pid !== "number") return false;
|
|
1722
|
-
try {
|
|
1723
|
-
process.kill(holder.pid, 0);
|
|
1724
|
-
return true;
|
|
1725
|
-
} catch {
|
|
1726
|
-
return false;
|
|
1727
|
-
}
|
|
1600
|
+
const lock = readCliLockRecord(animusDir);
|
|
1601
|
+
if (lock.kind === "none") return false;
|
|
1602
|
+
return lock.kind === "indeterminate" || checkLockLiveness(lock.record).live;
|
|
1728
1603
|
}
|
|
1729
|
-
/**
|
|
1730
|
-
*
|
|
1731
|
-
* remaining writer of flat payload names, and its live output is
|
|
1732
|
-
* fenced off below) and prune sibling session directories older than
|
|
1733
|
-
* the retention window — never the own dir, tolerating races with
|
|
1734
|
-
* concurrently-pruning sessions (S14). */
|
|
1604
|
+
/** Delete legacy flat artifacts and prune sibling session directories past
|
|
1605
|
+
* the retention window — never this session's own dir. */
|
|
1735
1606
|
runSessionStartHygiene() {
|
|
1736
1607
|
if (this.sessionStartHygieneDone) return;
|
|
1737
1608
|
this.sessionStartHygieneDone = true;
|
|
1738
|
-
const animusDir = (0, path.join)(this.rootDir,
|
|
1609
|
+
const animusDir = (0, path.join)(this.rootDir, ANIMUS_ARTIFACT_DIR);
|
|
1739
1610
|
const isCliPublishedSet = this.cliWriterHoldsLock(animusDir) || verifyCommitRecord(animusDir).length === 0;
|
|
1740
1611
|
const cliPublishedNames = [
|
|
1741
1612
|
MANIFEST_ARTIFACT,
|
|
@@ -1762,18 +1633,17 @@ var ExtractionSession = class {
|
|
|
1762
1633
|
if (entry === this.sessionId) continue;
|
|
1763
1634
|
const dir = (0, path.join)(sessionsRootDir(this.rootDir), entry);
|
|
1764
1635
|
try {
|
|
1765
|
-
if ((0, fs.statSync)(dir).mtimeMs
|
|
1636
|
+
if ((0, fs.statSync)(dir).mtimeMs >= cutoff) continue;
|
|
1637
|
+
if (this.liveSiblingOwnerPid(dir) !== null) continue;
|
|
1638
|
+
(0, fs.rmSync)(dir, {
|
|
1766
1639
|
recursive: true,
|
|
1767
1640
|
force: true
|
|
1768
1641
|
});
|
|
1769
1642
|
} catch {}
|
|
1770
1643
|
}
|
|
1771
1644
|
}
|
|
1772
|
-
/**
|
|
1773
|
-
*
|
|
1774
|
-
* can never observe a torn half-written file. The tmp name carries the
|
|
1775
|
-
* pid — Next dev evaluates the config in more than one process, and two
|
|
1776
|
-
* sessions writing the same artifact must not race on one tmp path. */
|
|
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. */
|
|
1777
1647
|
writeSessionArtifact(name, content) {
|
|
1778
1648
|
const dir = this.sessionDir;
|
|
1779
1649
|
(0, fs.mkdirSync)(dir, { recursive: true });
|
|
@@ -1782,19 +1652,9 @@ var ExtractionSession = class {
|
|
|
1782
1652
|
(0, fs.renameSync)(tmpPath, (0, path.join)(dir, name));
|
|
1783
1653
|
this.onArtifactWrite?.(name, content);
|
|
1784
1654
|
}
|
|
1785
|
-
/**
|
|
1786
|
-
*
|
|
1787
|
-
|
|
1788
|
-
* session directory's `assets/` under a content-hashed name, and
|
|
1789
|
-
* substitute a RELATIVE url — relative to the session-scoped styles.css,
|
|
1790
|
-
* which sits beside `assets/`, so the emitted `./assets/<file>` form is
|
|
1791
|
-
* unchanged. The stylesheet is processed by Next's own CSS pipeline
|
|
1792
|
-
* (webpack and Turbopack alike), which applies its native asset handling
|
|
1793
|
-
* — publicPath and output hashing — to relative url() references.
|
|
1794
|
-
* Unsubstitutable specifiers warn and emit literally in non-strict mode,
|
|
1795
|
-
* fail the build under `strict: true`.
|
|
1796
|
-
*/
|
|
1797
|
-
substituteAssetReferences(globalCss, devMode) {
|
|
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) {
|
|
1798
1658
|
const specifiers = require_pipeline.findAssetSpecifiers(globalCss);
|
|
1799
1659
|
const assetsDir = (0, path.join)(this.sessionDir, SESSION_ASSETS_DIR);
|
|
1800
1660
|
const expected = /* @__PURE__ */ new Set();
|
|
@@ -1839,19 +1699,15 @@ var ExtractionSession = class {
|
|
|
1839
1699
|
url
|
|
1840
1700
|
});
|
|
1841
1701
|
}
|
|
1842
|
-
if (
|
|
1702
|
+
if (pruneSuperseded) pruneStaleAssets(assetsDir, expected);
|
|
1843
1703
|
return require_pipeline.substituteAssetPlaceholders(globalCss, urlBySpecifier);
|
|
1844
1704
|
}
|
|
1845
|
-
trackAssetDependency(path$
|
|
1846
|
-
this.assetDependencyPaths.add(path$
|
|
1847
|
-
for (const key of require_pipeline.toWatchKeys(path$
|
|
1705
|
+
trackAssetDependency(path$8) {
|
|
1706
|
+
this.assetDependencyPaths.add(path$8);
|
|
1707
|
+
for (const key of require_pipeline.toWatchKeys(path$8)) this.assetDependencyKeys.add(key);
|
|
1848
1708
|
}
|
|
1849
|
-
/**
|
|
1850
|
-
*
|
|
1851
|
-
* resolver (host aliases → Node resolution → package root), with one
|
|
1852
|
-
* session-local last resort: an already-discovered source entry's package
|
|
1853
|
-
* root (dist-less workspace kits the shared resolver cannot see).
|
|
1854
|
-
*/
|
|
1709
|
+
/** Resolve an asset specifier through the shared resolver, with one
|
|
1710
|
+
* last resort: a discovered source entry's package root. */
|
|
1855
1711
|
resolveAssetSpecifier(specifier) {
|
|
1856
1712
|
const resolved = require_pipeline.resolveAssetFile(specifier, this.rootDir, this.pathAliasesJson);
|
|
1857
1713
|
if (resolved) return resolved;
|
|
@@ -1867,15 +1723,8 @@ var ExtractionSession = class {
|
|
|
1867
1723
|
return null;
|
|
1868
1724
|
}
|
|
1869
1725
|
};
|
|
1870
|
-
/**
|
|
1871
|
-
*
|
|
1872
|
-
* anything else in the directory is a superseded revision (the copies are
|
|
1873
|
-
* content-addressed and never overwritten) or the leftover of an asset()
|
|
1874
|
-
* reference that no longer exists. Failures are tolerated per entry — Next
|
|
1875
|
-
* dev evaluates the config in more than one process, and a concurrent
|
|
1876
|
-
* session may have removed (or be about to rewrite) the same file; every
|
|
1877
|
-
* pass rewrites whatever of its own set is missing, so races self-heal.
|
|
1878
|
-
*/
|
|
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. */
|
|
1879
1728
|
function pruneStaleAssets(assetsDir, expected) {
|
|
1880
1729
|
if (!(0, fs.existsSync)(assetsDir)) return;
|
|
1881
1730
|
let entries;
|
|
@@ -1893,61 +1742,54 @@ function pruneStaleAssets(assetsDir, expected) {
|
|
|
1893
1742
|
}
|
|
1894
1743
|
//#endregion
|
|
1895
1744
|
//#region session/turbopack-orchestrator.ts
|
|
1896
|
-
/**
|
|
1897
|
-
* Out-of-band pipeline orchestration for Turbopack (spec:
|
|
1898
|
-
* next-turbopack-integration). Turbopack exposes no compiler hooks, so the
|
|
1899
|
-
* pipeline runs during next.config resolution and, in dev, from a
|
|
1900
|
-
* filesystem watcher — the bundler only ever sees the `.animus/` disk
|
|
1901
|
-
* artifacts.
|
|
1902
|
-
*/
|
|
1903
|
-
/** Run the full pipeline with the Turbopack emitter identity: the virtual
|
|
1904
|
-
* system-props id (Turbopack rejects the webpack path's absolute-path
|
|
1905
|
-
* imports). Turbopack orchestration persists the analysis-inputs
|
|
1906
|
-
* hydration corpus — its isolated loader workers replay it (spec:
|
|
1907
|
-
* next-turbopack-integration, "Manifest disk artifact"; webpack mode
|
|
1908
|
-
* skips the corpus). */
|
|
1909
1745
|
async function runSessionPipeline(session) {
|
|
1910
1746
|
session.systemPropsModuleId = TURBOPACK_SYSTEM_PROPS_ID;
|
|
1911
1747
|
session.persistAnalysisInputs = true;
|
|
1912
1748
|
await session.runFullPipeline();
|
|
1913
1749
|
}
|
|
1914
1750
|
const activeWatcherRoots = /* @__PURE__ */ new Set();
|
|
1915
|
-
const IGNORED_SEGMENTS = new Set([
|
|
1916
|
-
|
|
1751
|
+
const IGNORED_SEGMENTS = /* @__PURE__ */ new Set([
|
|
1752
|
+
ANIMUS_ARTIFACT_DIR,
|
|
1917
1753
|
".next",
|
|
1918
1754
|
"node_modules"
|
|
1919
1755
|
]);
|
|
1920
|
-
/**
|
|
1921
|
-
*
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
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" };
|
|
1936
1777
|
activeWatcherRoots.add(rootDir);
|
|
1937
|
-
|
|
1778
|
+
session.debounceCeilingMs = debounceMs;
|
|
1938
1779
|
const pendingPaths = /* @__PURE__ */ new Set();
|
|
1939
1780
|
const watchers = /* @__PURE__ */ new Map();
|
|
1940
1781
|
let timer = null;
|
|
1941
1782
|
let updateChain = Promise.resolve();
|
|
1942
1783
|
let closed = false;
|
|
1943
1784
|
let died = false;
|
|
1785
|
+
let holdingEvents = holdEvents;
|
|
1944
1786
|
const externalWatchers = /* @__PURE__ */ new Map();
|
|
1945
1787
|
let pendingOpened = /* @__PURE__ */ new Map();
|
|
1946
1788
|
let capturedDuringSnapshot = [];
|
|
1947
1789
|
const externalDiagnosticKey = (root) => `external-watch:${root}`;
|
|
1948
1790
|
const failureReason = (err) => {
|
|
1949
|
-
const code = err
|
|
1950
|
-
if (code
|
|
1791
|
+
const code = errnoCode(err);
|
|
1792
|
+
if (code !== null && CAPACITY_CODES.has(code)) return `capacity(${code})`;
|
|
1951
1793
|
if (/inotify|too many/i.test(String(err))) return "capacity";
|
|
1952
1794
|
return code ?? "error";
|
|
1953
1795
|
};
|
|
@@ -1966,14 +1808,17 @@ function startTurbopackWatcher(session, rootDir, debounceMs = 75, watchFn = fs.w
|
|
|
1966
1808
|
const removedFiles = /* @__PURE__ */ new Set();
|
|
1967
1809
|
for (const path$1 of pendingPaths) ((0, fs.existsSync)(path$1) ? modifiedFiles : removedFiles).add(path$1);
|
|
1968
1810
|
pendingPaths.clear();
|
|
1969
|
-
updateChain = updateChain.then(() =>
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
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
|
+
});
|
|
1977
1822
|
};
|
|
1978
1823
|
const rollbackPendingExternal = () => {
|
|
1979
1824
|
for (const watcher of pendingOpened.values()) watcher.close();
|
|
@@ -2002,6 +1847,7 @@ function startTurbopackWatcher(session, rootDir, debounceMs = 75, watchFn = fs.w
|
|
|
2002
1847
|
};
|
|
2003
1848
|
const enqueuePath = (abs) => {
|
|
2004
1849
|
pendingPaths.add(abs);
|
|
1850
|
+
if (holdingEvents) return;
|
|
2005
1851
|
try {
|
|
2006
1852
|
session.noteDebouncedWatchEvents?.([abs]);
|
|
2007
1853
|
} catch (err) {
|
|
@@ -2022,11 +1868,6 @@ function startTurbopackWatcher(session, rootDir, debounceMs = 75, watchFn = fs.w
|
|
|
2022
1868
|
} catch {}
|
|
2023
1869
|
enqueuePath(abs);
|
|
2024
1870
|
};
|
|
2025
|
-
/** External-root event path: fenced by watcher identity ({watcherRootId,
|
|
2026
|
-
* generation} — a handle that is neither current nor pending belongs to
|
|
2027
|
-
* a retired generation and its events are rejected), `filename == null`
|
|
2028
|
-
* marks the ROOT dirty for rediscovery, and events observed while the
|
|
2029
|
-
* root's generation is still snapshotting are buffered for replay. */
|
|
2030
1871
|
const onExternalEvent = (root, self, filename) => {
|
|
2031
1872
|
if (closed) return;
|
|
2032
1873
|
const watcher = self();
|
|
@@ -2046,10 +1887,6 @@ function startTurbopackWatcher(session, rootDir, debounceMs = 75, watchFn = fs.w
|
|
|
2046
1887
|
}
|
|
2047
1888
|
enqueuePath(abs);
|
|
2048
1889
|
};
|
|
2049
|
-
/** Open one external-root watcher into `into` (the committed set at
|
|
2050
|
-
* startup, the pending set during reset reconciliation). Failures
|
|
2051
|
-
* degrade ONLY this root (design D7); node_modules-resident roots are
|
|
2052
|
-
* documented unwatchable and never registered (guardrail G2). */
|
|
2053
1890
|
const openExternalWatcher = (root, into) => {
|
|
2054
1891
|
if (root.split(/[\\/]/).includes("node_modules")) return;
|
|
2055
1892
|
try {
|
|
@@ -2078,7 +1915,7 @@ function startTurbopackWatcher(session, rootDir, debounceMs = 75, watchFn = fs.w
|
|
|
2078
1915
|
} catch (err) {
|
|
2079
1916
|
closeAll();
|
|
2080
1917
|
console.warn(`[animus-extract] Turbopack dev watcher unavailable (${String(err)}); source edits require a dev-server restart`);
|
|
2081
|
-
return
|
|
1918
|
+
return { kind: "unavailable" };
|
|
2082
1919
|
}
|
|
2083
1920
|
for (const root of session.externalWatchRoots ?? []) openExternalWatcher(root, externalWatchers);
|
|
2084
1921
|
session.onExternalRootResolved = (root) => {
|
|
@@ -2103,14 +1940,25 @@ function startTurbopackWatcher(session, rootDir, debounceMs = 75, watchFn = fs.w
|
|
|
2103
1940
|
return died;
|
|
2104
1941
|
},
|
|
2105
1942
|
onDied: null,
|
|
1943
|
+
deliverHeldEvents: () => {
|
|
1944
|
+
holdingEvents = false;
|
|
1945
|
+
if (pendingPaths.size > 0) flush();
|
|
1946
|
+
return updateChain;
|
|
1947
|
+
},
|
|
2106
1948
|
settle: () => updateChain
|
|
2107
1949
|
};
|
|
2108
|
-
return
|
|
1950
|
+
return {
|
|
1951
|
+
kind: "started",
|
|
1952
|
+
handle
|
|
1953
|
+
};
|
|
2109
1954
|
}
|
|
2110
1955
|
//#endregion
|
|
1956
|
+
exports.ANIMUS_ARTIFACT_DIR = ANIMUS_ARTIFACT_DIR;
|
|
2111
1957
|
exports.ANIMUS_CSS_MODULE_ID = ANIMUS_CSS_MODULE_ID;
|
|
2112
1958
|
exports.CLI_COMMIT_ARTIFACT = CLI_COMMIT_ARTIFACT;
|
|
2113
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;
|
|
2114
1962
|
exports.ExtractionSession = ExtractionSession;
|
|
2115
1963
|
exports.MANIFEST_ARTIFACT = MANIFEST_ARTIFACT;
|
|
2116
1964
|
exports.SESSION_ASSETS_DIR = SESSION_ASSETS_DIR;
|
|
@@ -2120,10 +1968,11 @@ exports.TURBOPACK_SYSTEM_PROPS_ID = TURBOPACK_SYSTEM_PROPS_ID;
|
|
|
2120
1968
|
exports.analysisCommitPath = analysisCommitPath;
|
|
2121
1969
|
exports.analysisInputsPath = analysisInputsPath;
|
|
2122
1970
|
exports.analysisStatusPath = analysisStatusPath;
|
|
2123
|
-
exports.
|
|
1971
|
+
exports.checkLockLiveness = checkLockLiveness;
|
|
2124
1972
|
exports.collectSessionAssets = collectSessionAssets;
|
|
1973
|
+
exports.decodeCommitRecord = decodeCommitRecord;
|
|
2125
1974
|
exports.engineApi = engineApi;
|
|
2126
|
-
exports.
|
|
1975
|
+
exports.getAnalysisStartedPromise = getAnalysisStartedPromise;
|
|
2127
1976
|
exports.getAnalyzedHashes = getAnalyzedHashes;
|
|
2128
1977
|
exports.getManifestJson = getManifestJson;
|
|
2129
1978
|
exports.getReplacementEpoch = getReplacementEpoch;
|
|
@@ -2132,13 +1981,17 @@ exports.getSharedCss = getSharedCss;
|
|
|
2132
1981
|
exports.getSharedExternalDirs = getSharedExternalDirs;
|
|
2133
1982
|
exports.getSharedExternalEntries = getSharedExternalEntries;
|
|
2134
1983
|
exports.getSharedSystemProps = getSharedSystemProps;
|
|
1984
|
+
exports.holdDirectoryClaim = holdDirectoryClaim;
|
|
1985
|
+
exports.isLockHolderAlive = isLockHolderAlive;
|
|
1986
|
+
exports.lockRecordBytes = lockRecordBytes;
|
|
2135
1987
|
exports.manifestPath = manifestPath;
|
|
2136
1988
|
exports.pruneStaleAssets = pruneStaleAssets;
|
|
1989
|
+
exports.readCliLockRecord = readCliLockRecord;
|
|
2137
1990
|
exports.readJsonEnvelope = readJsonEnvelope;
|
|
2138
1991
|
exports.replacementEpochPath = replacementEpochPath;
|
|
2139
1992
|
exports.runSessionPipeline = runSessionPipeline;
|
|
2140
1993
|
exports.sessionArtifactDir = sessionArtifactDir;
|
|
2141
|
-
exports.
|
|
1994
|
+
exports.setAnalysisStartedPromise = setAnalysisStartedPromise;
|
|
2142
1995
|
exports.setSharedEngine = setSharedEngine;
|
|
2143
1996
|
exports.startTurbopackWatcher = startTurbopackWatcher;
|
|
2144
1997
|
exports.stylesPath = stylesPath;
|