@animus-ui/unplugin 0.1.11

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.
@@ -0,0 +1,420 @@
1
+ import { AnimusConfigError, ENGINE_TRANSFORM_EXTENSIONS, assertKnownOptionKeys, assertNoRetiredEngineSelection, buildPathAliasesJson, isPathWithinRoot, readTsconfigAliasPairs, resolveMode } from "@animus-ui/extract/pipeline";
2
+ import { ANIMUS_CSS_MODULE_ID, ExtractionSession, SESSION_ASSETS_DIR, TURBOPACK_SYSTEM_PROPS_ID, claimExclusiveSessionOwner, collectSessionAssets, engineApi, getAnalyzedHashes, getManifestJson, getSessionArtifactDir, getSharedCss, getSharedSystemProps } from "@animus-ui/extract/session";
3
+ import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
+ import { dirname, join, relative, resolve } from "node:path";
5
+ //#region src/options.ts
6
+ /**
7
+ * Host option intake — the transform host is the fourth driver over the
8
+ * shared option core (openspec: standalone-extraction-cli, D2/D9): one
9
+ * schema, `assertKnownOptionKeys` at the boundary, and an explicit
10
+ * emission `mode` that is plumbed — never environment-sniffed (D10).
11
+ *
12
+ * Unlike the Vite/Next plugin drivers, this host HONORS the core `root`
13
+ * key: rollup and esbuild expose no root authority a plugin could derive,
14
+ * so the host's root is the explicit option, defaulting to the process
15
+ * working directory (the CLI's shape, not the plugin drivers').
16
+ */
17
+ /**
18
+ * Validate raw options and resolve the root authority. Throws
19
+ * `AnimusConfigError` (the shared config-error class) on unknown keys,
20
+ * invalid `mode` values, a retired engine selection, or a missing
21
+ * `system`.
22
+ */
23
+ function resolveHostOptions(raw, cwd = process.cwd()) {
24
+ const options = raw ?? {};
25
+ assertNoRetiredEngineSelection(options.engine);
26
+ assertKnownOptionKeys(options);
27
+ if (typeof options.system !== "string" || options.system.length === 0) throw new AnimusConfigError("Missing required option `system` — pass `system: \"./src/ds.ts\"` to the Animus plugin.");
28
+ return {
29
+ root: options.root ? resolve(cwd, options.root) : cwd,
30
+ options
31
+ };
32
+ }
33
+ /**
34
+ * Resolve the effective emission mode: the explicit option wins over every
35
+ * bundler signal; otherwise the adapter-supplied command oracle applies;
36
+ * otherwise production (the documented host default — never NODE_ENV).
37
+ */
38
+ function resolveHostMode(explicit, oracle) {
39
+ return resolveMode(explicit, () => oracle ?? "production").mode;
40
+ }
41
+ //#endregion
42
+ //#region src/core.ts
43
+ /**
44
+ * The Animus transform host core (openspec: standalone-extraction-cli,
45
+ * D4 under D10): an unplugin factory that drives the ONE
46
+ * `ExtractionSession` at buildStart inside the consumer's bundler process
47
+ * and serves per-file transforms from retained engine state. No artifacts,
48
+ * no lock, no staleness protocol — analysis failure IS build failure,
49
+ * never a passthrough (the session's strict/policy seams carry this).
50
+ *
51
+ * Ported from the winning DEF-1 prototype arm
52
+ * (e2e/rollup-app/prototype/animus-t0-plugin.mjs), productized per the
53
+ * inc 05 packet: the emitted CSS reaches the consumer as a real asset
54
+ * (NS3 — the prototype's CSS-as-string module was measurement
55
+ * scaffolding), `__ANIMUS_DEV__` goes through each adapter's define
56
+ * mechanism, kit-specifier redirects come from the session's discovery
57
+ * output (the Turbopack alias assembly generalized), and the session
58
+ * directory is cleaned on success AND failure (inc 04 rider F6).
59
+ *
60
+ * ORDERING CONTRACT: the host transform must run before any TS/JSX
61
+ * transpilation — the engine parses raw TSX. `enforce: 'pre'` covers
62
+ * webpack/rspack; rollup consumers must list this plugin before their
63
+ * transpiler (see the e2e/rollup-app lane config).
64
+ */
65
+ /** Resolved virtual id of the stylesheet import. Deliberately
66
+ * extension-free: esbuild guesses loaders from extensions, and this
67
+ * module is a JS stub in every bundler — the CSS itself is delivered as
68
+ * an emitted asset, never as a module (NS3). No `\0` prefix: webpack's
69
+ * virtual-module bridge requires plain ids. */
70
+ const STYLES_VIRTUAL_ID = "animus:styles";
71
+ /** Resolved virtual id of the emitted system-props runtime module. */
72
+ const PROPS_VIRTUAL_ID = "animus:system-props";
73
+ /** File name of the emitted stylesheet asset. */
74
+ const CSS_ASSET_NAME = "animus.css";
75
+ /** Files the engine transform may rewrite — derived from the ONE shared
76
+ * extension set (also the Turbopack rule glob's source). */
77
+ const ENGINE_TRANSFORM_RE = new RegExp(`\\.(?:${ENGINE_TRANSFORM_EXTENSIONS.join("|")})$`);
78
+ /** Files the transform hook claims at all (define substitution included —
79
+ * `.cjs` carries no components but may read the dev-signal token). */
80
+ const TRANSFORM_INCLUDE_RE = new RegExp(`\\.(?:${[...ENGINE_TRANSFORM_EXTENSIONS, "cjs"].join("|")})$`);
81
+ /** Bare dev-signal token, assignment-guarded like @rollup/plugin-replace's
82
+ * `preventAssignment` — `__ANIMUS_DEV__ = x` is left alone. */
83
+ const DEV_DEFINE_RE = /\b__ANIMUS_DEV__\b(?!\s*=[^=])/g;
84
+ function createHostState() {
85
+ return {
86
+ pipeline: null,
87
+ mode: null,
88
+ cssText: "",
89
+ systemPropsJs: "",
90
+ kitRedirects: /* @__PURE__ */ new Map(),
91
+ redirectTargets: /* @__PURE__ */ new Set(),
92
+ externalPackageDirs: [],
93
+ watchPaths: [],
94
+ transformFile: null,
95
+ sessionDir: null
96
+ };
97
+ }
98
+ /**
99
+ * The transform-claim predicate over module paths. The analysis universe
100
+ * deliberately excludes node_modules (external kits ride their own
101
+ * collection path), so the transform must too — the Next webpack rule's
102
+ * exclude, ported: without it every dependency module in the graph is
103
+ * routed through the engine, scaling build time with dependency-graph size
104
+ * instead of source-file count. node_modules paths are claimed only when
105
+ * they belong to an ADMITTED external package (or are a kit redirect
106
+ * target) — those carry builder chains the engine must rewrite. Such files
107
+ * only enter the module graph after discovery published (their specifiers
108
+ * resolve through the pipeline-gated kitRedirects), so consulting captured
109
+ * state here is race-free.
110
+ */
111
+ function shouldClaimTransform(filePath, state) {
112
+ if (!filePath.includes("node_modules")) return true;
113
+ return state.redirectTargets.has(filePath) || state.externalPackageDirs.some((dir) => isPathWithinRoot(dir, filePath));
114
+ }
115
+ /** Remove the one-shot session tree; idempotent, runs on success AND
116
+ * failure paths (inc 04 rider F6). */
117
+ function disposeSessionDir(state, removeDir = (dir) => rmSync(dir, {
118
+ recursive: true,
119
+ force: true
120
+ })) {
121
+ if (state.sessionDir) {
122
+ removeDir(state.sessionDir);
123
+ state.sessionDir = null;
124
+ }
125
+ }
126
+ /**
127
+ * Run one pipeline attempt through the shared drive loop, recording it as
128
+ * `state.pipeline` for hook joiners. A failed attempt disposes the session
129
+ * directory before rethrowing — the consumer's build fails; nothing leaks.
130
+ */
131
+ async function drivePipeline(state, run, removeDir) {
132
+ const attempt = run();
133
+ state.pipeline = attempt;
134
+ try {
135
+ await attempt;
136
+ } catch (error) {
137
+ if (!state.sessionDir) state.sessionDir = getSessionArtifactDir();
138
+ disposeSessionDir(state, removeDir);
139
+ throw error;
140
+ }
141
+ }
142
+ /**
143
+ * Map an import id onto the host's CONSTANT resolution family: the
144
+ * stylesheet id and the system-props id — the mappings that answer without
145
+ * the pipeline. Kit-specifier redirects are the caller's second step (they
146
+ * exist only after discovery published, behind the pipeline join).
147
+ */
148
+ function resolveAnimusId(id) {
149
+ if (id === ANIMUS_CSS_MODULE_ID || id.endsWith(`/${ANIMUS_CSS_MODULE_ID}`)) return STYLES_VIRTUAL_ID;
150
+ if (id === "animus:styles" || id === "animus:system-props") return id;
151
+ if (id === TURBOPACK_SYSTEM_PROPS_ID) return PROPS_VIRTUAL_ID;
152
+ return null;
153
+ }
154
+ /**
155
+ * Substitute the bare `__ANIMUS_DEV__` token with its boolean literal —
156
+ * the define mechanism for bundlers without a native one (rollup). The
157
+ * token-as-initializer-conditional shape in the system runtime's is-dev
158
+ * module folds under the bundler's own dead-branch elimination once the
159
+ * literal lands. Returns null when the code carries no token.
160
+ */
161
+ function substituteDevDefine(code, isDev) {
162
+ if (!code.includes("__ANIMUS_DEV__")) return null;
163
+ return code.replace(DEV_DEFINE_RE, isDev ? "true" : "false");
164
+ }
165
+ /**
166
+ * Per-file engine transform from retained state. The path handed to the
167
+ * engine is the rootDir-relative posix key the analysis used — external
168
+ * kit sources ride the same derivation (`../…` keys). Files outside the
169
+ * analysis universe come back unchanged (`hasComponents: false`); a
170
+ * transform before analysis fails loud inside the engine adapter.
171
+ */
172
+ function transformWithEngine(code, id, ctx) {
173
+ const filename = relative(ctx.rootDir, resolve(id)).split("\\").join("/");
174
+ const result = ctx.transformFile(code, filename, ctx.manifestJson);
175
+ return result.hasComponents ? result.code : null;
176
+ }
177
+ /** Strip a bundler query suffix (`?worker`, webpack resource queries). */
178
+ function moduleFilePath(id) {
179
+ const query = id.indexOf("?");
180
+ return query === -1 ? id : id.slice(0, query);
181
+ }
182
+ const PLUGIN_NAME = "animus-host";
183
+ /**
184
+ * The one-live-host-per-process constraint, enforced where the invariant
185
+ * lives: the SESSION singleton (its slots are what concurrent hosts would
186
+ * clobber). Re-exported under the host's historical name; the key sits in
187
+ * `SINGLETON_GLOBAL_KEYS`, so test harness resets clear a leaked claim.
188
+ */
189
+ const claimProcessHost = claimExclusiveSessionOwner;
190
+ /**
191
+ * The unplugin factory. One factory invocation = one host = one session
192
+ * per build; the singleton drive loop stays exactly-one (guardrail G1 —
193
+ * the host defines no session class and no drive loop of its own).
194
+ */
195
+ const unpluginFactory = (rawOptions, meta) => {
196
+ const { root, options } = resolveHostOptions(rawOptions);
197
+ const state = createHostState();
198
+ /** Release handle of this host's process claim, or null when not held. */
199
+ let releaseHostClaim = null;
200
+ /** Adapter-supplied command oracle (null = no bundler signal). */
201
+ let modeOracle = null;
202
+ let esbuildOptions = null;
203
+ /** Resolves once buildStart has begun — hooks that can fire before the
204
+ * bundler-parallel buildStart (webpack's make taps run concurrently)
205
+ * wait on this, then join the pipeline itself. */
206
+ let signalPipelineStarted;
207
+ const pipelineStarted = new Promise((res) => {
208
+ signalPipelineStarted = res;
209
+ });
210
+ const effectiveMode = () => resolveHostMode(options.mode, modeOracle);
211
+ /** The define is supplied natively where the bundler has a mechanism
212
+ * (esbuild define, webpack/rspack DefinePlugin); everywhere else the
213
+ * transform substitutes the token itself. */
214
+ const needsInlineDefine = meta.framework !== "esbuild" && meta.framework !== "webpack" && meta.framework !== "rspack";
215
+ async function startPipeline() {
216
+ signalPipelineStarted();
217
+ releaseHostClaim = claimProcessHost(`${PLUGIN_NAME}:${root}`);
218
+ try {
219
+ await runClaimedPipeline();
220
+ } catch (error) {
221
+ releaseClaim();
222
+ throw error;
223
+ }
224
+ }
225
+ function releaseClaim() {
226
+ releaseHostClaim?.();
227
+ releaseHostClaim = null;
228
+ }
229
+ async function runClaimedPipeline() {
230
+ await drivePipeline(state, async () => {
231
+ const mode = effectiveMode();
232
+ state.mode = mode;
233
+ const session = new ExtractionSession({
234
+ ...options,
235
+ mode
236
+ });
237
+ session.driverLabel = "animus-unplugin";
238
+ session.rootDir = root;
239
+ session.systemPropsModuleId = TURBOPACK_SYSTEM_PROPS_ID;
240
+ const builtAliases = buildPathAliasesJson(readTsconfigAliasPairs(root), root);
241
+ if (builtAliases) session.pathAliasesJson = builtAliases.json;
242
+ await session.runFullPipeline();
243
+ state.sessionDir = getSessionArtifactDir();
244
+ const analyzed = getAnalyzedHashes();
245
+ if (!analyzed || analyzed.size === 0) throw new Error(`[animus] discovery found zero source files under ${root} — check the plugin's \`root\` and \`exclude\` options`);
246
+ state.cssText = getSharedCss();
247
+ state.systemPropsJs = getSharedSystemProps();
248
+ state.kitRedirects = new Map(session.externalSourceEntries);
249
+ state.redirectTargets = new Set(state.kitRedirects.values());
250
+ state.externalPackageDirs = [...session.externalPackageDirs];
251
+ const watchPaths = /* @__PURE__ */ new Set();
252
+ for (const key of getAnalyzedHashes()?.keys() ?? []) watchPaths.add(resolve(root, key));
253
+ for (const dep of session.systemDependencyPaths) watchPaths.add(dep);
254
+ for (const dep of session.assetDependencyPaths) watchPaths.add(dep);
255
+ watchPaths.add(join(root, "tsconfig.json"));
256
+ state.watchPaths = [...watchPaths];
257
+ state.transformFile = engineApi().transformFile;
258
+ });
259
+ }
260
+ /** Join the analysis: every serving hook waits for buildStart to have
261
+ * begun, then for the pipeline to have published. A rejected pipeline
262
+ * rejects every joiner — analysis failure is build failure, never a
263
+ * passthrough. */
264
+ async function joinPipeline() {
265
+ await pipelineStarted;
266
+ await state.pipeline;
267
+ }
268
+ function emitCssAsset(context) {
269
+ if (!state.cssText) return;
270
+ const assets = collectSessionAssets(state.sessionDir);
271
+ if (meta.framework === "esbuild") {
272
+ const outDir = esbuildOptions?.outdir ?? (esbuildOptions?.outfile ? dirname(esbuildOptions.outfile) : null);
273
+ if (outDir === null) {
274
+ console.warn(`[animus] esbuild build has no outdir/outfile — the extracted stylesheet (${CSS_ASSET_NAME}) was not written`);
275
+ return;
276
+ }
277
+ if (esbuildOptions?.write === false) {
278
+ console.warn(`[animus] esbuild \`write: false\` build — the extracted stylesheet (${CSS_ASSET_NAME}) and its asset files were NOT produced (no in-memory output seam); use \`write: true\` or the standalone \`animus build\` CLI for in-memory pipelines`);
279
+ return;
280
+ }
281
+ const absOut = resolve(esbuildOptions?.absWorkingDir ?? process.cwd(), outDir);
282
+ mkdirSync(absOut, { recursive: true });
283
+ writeFileSync(join(absOut, CSS_ASSET_NAME), state.cssText);
284
+ if (assets.length > 0) {
285
+ mkdirSync(join(absOut, SESSION_ASSETS_DIR), { recursive: true });
286
+ for (const { name, bytes } of assets) writeFileSync(join(absOut, SESSION_ASSETS_DIR, name), bytes);
287
+ }
288
+ return;
289
+ }
290
+ context.emitFile({
291
+ type: "asset",
292
+ fileName: CSS_ASSET_NAME,
293
+ source: state.cssText
294
+ });
295
+ for (const { name, bytes } of assets) context.emitFile({
296
+ type: "asset",
297
+ fileName: `${SESSION_ASSETS_DIR}/${name}`,
298
+ source: bytes
299
+ });
300
+ }
301
+ /** Register the analysis universe with the bundler's watcher — the
302
+ * session analyzes a filesystem WALK, so watch mode misses edits to
303
+ * analyzed-but-unimported files (and tsconfig/system deps) without
304
+ * this. esbuild has no per-plugin watch-file seam; its rebuilds rely on
305
+ * the module graph alone. */
306
+ function registerWatchTargets(context) {
307
+ if (meta.framework === "esbuild") return;
308
+ for (const path of state.watchPaths) try {
309
+ context.addWatchFile(path);
310
+ } catch {}
311
+ }
312
+ return {
313
+ name: PLUGIN_NAME,
314
+ enforce: "pre",
315
+ async buildStart() {
316
+ await startPipeline();
317
+ registerWatchTargets(this);
318
+ },
319
+ async resolveId(id) {
320
+ const virtual = resolveAnimusId(id);
321
+ if (virtual !== null) return virtual;
322
+ if (id.startsWith(".") || id.startsWith("/") || id.startsWith("\0") || id.startsWith("animus:")) return null;
323
+ await joinPipeline();
324
+ return state.kitRedirects.get(id) ?? null;
325
+ },
326
+ loadInclude(id) {
327
+ return id === "animus:styles" || id === "animus:system-props" || state.redirectTargets.has(moduleFilePath(id));
328
+ },
329
+ async load(id) {
330
+ if (id === "animus:styles") {
331
+ await joinPipeline();
332
+ return {
333
+ code: "export {};\n",
334
+ map: null
335
+ };
336
+ }
337
+ if (id === "animus:system-props") {
338
+ await joinPipeline();
339
+ return {
340
+ code: state.systemPropsJs,
341
+ map: null
342
+ };
343
+ }
344
+ const filePath = moduleFilePath(id);
345
+ if (state.redirectTargets.has(filePath)) return {
346
+ code: readFileSync(filePath, "utf-8"),
347
+ map: null
348
+ };
349
+ return null;
350
+ },
351
+ transformInclude(id) {
352
+ const filePath = moduleFilePath(id);
353
+ return !id.startsWith("\0") && !id.startsWith("animus:") && TRANSFORM_INCLUDE_RE.test(filePath) && shouldClaimTransform(filePath, state);
354
+ },
355
+ async transform(code, id) {
356
+ await joinPipeline();
357
+ const filePath = moduleFilePath(id);
358
+ let output = code;
359
+ if (ENGINE_TRANSFORM_RE.test(filePath)) {
360
+ const transformFile = state.transformFile ?? engineApi().transformFile;
361
+ const transformed = transformWithEngine(output, filePath, {
362
+ rootDir: root,
363
+ manifestJson: getManifestJson() ?? "",
364
+ transformFile
365
+ });
366
+ if (transformed !== null) output = transformed;
367
+ }
368
+ if (needsInlineDefine) {
369
+ const substituted = substituteDevDefine(output, state.mode === "development");
370
+ if (substituted !== null) output = substituted;
371
+ }
372
+ return output === code ? null : {
373
+ code: output,
374
+ map: null
375
+ };
376
+ },
377
+ buildEnd() {
378
+ try {
379
+ emitCssAsset(this);
380
+ } finally {
381
+ disposeSessionDir(state);
382
+ releaseClaim();
383
+ }
384
+ },
385
+ rollup: { async buildStart() {
386
+ modeOracle = this.meta?.watchMode ? "development" : "production";
387
+ await startPipeline();
388
+ registerWatchTargets(this);
389
+ } },
390
+ webpack(compiler) {
391
+ wireWebpackLike(compiler);
392
+ },
393
+ rspack(compiler) {
394
+ wireWebpackLike(compiler);
395
+ },
396
+ esbuild: { config(buildOptions) {
397
+ esbuildOptions = buildOptions;
398
+ buildOptions.define = {
399
+ ...buildOptions.define,
400
+ __ANIMUS_DEV__: JSON.stringify(effectiveMode() === "development")
401
+ };
402
+ } }
403
+ };
404
+ function wireWebpackLike(compiler) {
405
+ modeOracle = compiler.options.mode === "development" ? "development" : "production";
406
+ const DefinePlugin = compiler.webpack?.DefinePlugin ?? compiler.rspack?.DefinePlugin;
407
+ if (!DefinePlugin) throw new Error("[animus] compiler exposes no DefinePlugin — cannot supply the __ANIMUS_DEV__ dev-signal define");
408
+ new DefinePlugin({ __ANIMUS_DEV__: JSON.stringify(effectiveMode() === "development") }).apply(compiler);
409
+ compiler.hooks.done.tap(PLUGIN_NAME, () => {
410
+ disposeSessionDir(state);
411
+ releaseClaim();
412
+ });
413
+ compiler.hooks.failed?.tap(PLUGIN_NAME, () => {
414
+ disposeSessionDir(state);
415
+ releaseClaim();
416
+ });
417
+ }
418
+ };
419
+ //#endregion
420
+ export { unpluginFactory as t };
package/dist/core.d.ts ADDED
@@ -0,0 +1,136 @@
1
+ /**
2
+ * The Animus transform host core (openspec: standalone-extraction-cli,
3
+ * D4 under D10): an unplugin factory that drives the ONE
4
+ * `ExtractionSession` at buildStart inside the consumer's bundler process
5
+ * and serves per-file transforms from retained engine state. No artifacts,
6
+ * no lock, no staleness protocol — analysis failure IS build failure,
7
+ * never a passthrough (the session's strict/policy seams carry this).
8
+ *
9
+ * Ported from the winning DEF-1 prototype arm
10
+ * (e2e/rollup-app/prototype/animus-t0-plugin.mjs), productized per the
11
+ * inc 05 packet: the emitted CSS reaches the consumer as a real asset
12
+ * (NS3 — the prototype's CSS-as-string module was measurement
13
+ * scaffolding), `__ANIMUS_DEV__` goes through each adapter's define
14
+ * mechanism, kit-specifier redirects come from the session's discovery
15
+ * output (the Turbopack alias assembly generalized), and the session
16
+ * directory is cleaned on success AND failure (inc 04 rider F6).
17
+ *
18
+ * ORDERING CONTRACT: the host transform must run before any TS/JSX
19
+ * transpilation — the engine parses raw TSX. `enforce: 'pre'` covers
20
+ * webpack/rspack; rollup consumers must list this plugin before their
21
+ * transpiler (see the e2e/rollup-app lane config).
22
+ */
23
+ import { claimExclusiveSessionOwner } from '@animus-ui/extract/session';
24
+ import type { AnimusUnpluginOptions } from './options';
25
+ import type { AnimusMode } from '@animus-ui/extract/pipeline';
26
+ import type { UnpluginFactory } from 'unplugin';
27
+ /** Resolved virtual id of the stylesheet import. Deliberately
28
+ * extension-free: esbuild guesses loaders from extensions, and this
29
+ * module is a JS stub in every bundler — the CSS itself is delivered as
30
+ * an emitted asset, never as a module (NS3). No `\0` prefix: webpack's
31
+ * virtual-module bridge requires plain ids. */
32
+ export declare const STYLES_VIRTUAL_ID = "animus:styles";
33
+ /** Resolved virtual id of the emitted system-props runtime module. */
34
+ export declare const PROPS_VIRTUAL_ID = "animus:system-props";
35
+ /** File name of the emitted stylesheet asset. */
36
+ export declare const CSS_ASSET_NAME = "animus.css";
37
+ /** Mutable per-build host state (one per factory invocation). */
38
+ export interface HostState {
39
+ /** The in-flight (or settled) analysis; transforms and loads await it. */
40
+ pipeline: Promise<void> | null;
41
+ /** Resolved emission mode of the current build. */
42
+ mode: AnimusMode | null;
43
+ /** Assembled stylesheet of the published analysis ('' until published). */
44
+ cssText: string;
45
+ /** Emitted system-props runtime module source. */
46
+ systemPropsJs: string;
47
+ /** Kit specifier → absolute analyzed source entry (discovery output). */
48
+ kitRedirects: Map<string, string>;
49
+ /** Absolute analyzed entries (values of kitRedirects) — load allowlist. */
50
+ redirectTargets: Set<string>;
51
+ /** Admitted external package dirs (discovery output) — the ONLY
52
+ * node_modules subtrees the transform claims. */
53
+ externalPackageDirs: string[];
54
+ /** Absolute paths of the ANALYSIS UNIVERSE — every analyzed source file
55
+ * plus the system module's evaluated dependencies, asset() files, and
56
+ * the tsconfig alias source. Registered with the bundler's watcher: the
57
+ * session analyzes a filesystem walk, not the module graph, so an edit
58
+ * to an analyzed-but-unimported file must still trigger a rebuild. */
59
+ watchPaths: string[];
60
+ /** The engine adapter's transformFile, resolved once per pipeline run —
61
+ * never per module. */
62
+ transformFile: ((source: string, path: string, manifestJson: string) => {
63
+ code: string;
64
+ hasComponents: boolean;
65
+ }) | null;
66
+ /** Session artifact directory awaiting cleanup, or null. */
67
+ sessionDir: string | null;
68
+ }
69
+ export declare function createHostState(): HostState;
70
+ /**
71
+ * The transform-claim predicate over module paths. The analysis universe
72
+ * deliberately excludes node_modules (external kits ride their own
73
+ * collection path), so the transform must too — the Next webpack rule's
74
+ * exclude, ported: without it every dependency module in the graph is
75
+ * routed through the engine, scaling build time with dependency-graph size
76
+ * instead of source-file count. node_modules paths are claimed only when
77
+ * they belong to an ADMITTED external package (or are a kit redirect
78
+ * target) — those carry builder chains the engine must rewrite. Such files
79
+ * only enter the module graph after discovery published (their specifiers
80
+ * resolve through the pipeline-gated kitRedirects), so consulting captured
81
+ * state here is race-free.
82
+ */
83
+ export declare function shouldClaimTransform(filePath: string, state: Pick<HostState, 'externalPackageDirs' | 'redirectTargets'>): boolean;
84
+ /** Remove the one-shot session tree; idempotent, runs on success AND
85
+ * failure paths (inc 04 rider F6). */
86
+ export declare function disposeSessionDir(state: HostState, removeDir?: (dir: string) => void): void;
87
+ /**
88
+ * Run one pipeline attempt through the shared drive loop, recording it as
89
+ * `state.pipeline` for hook joiners. A failed attempt disposes the session
90
+ * directory before rethrowing — the consumer's build fails; nothing leaks.
91
+ */
92
+ export declare function drivePipeline(state: HostState, run: () => Promise<void>, removeDir?: (dir: string) => void): Promise<void>;
93
+ /**
94
+ * Map an import id onto the host's CONSTANT resolution family: the
95
+ * stylesheet id and the system-props id — the mappings that answer without
96
+ * the pipeline. Kit-specifier redirects are the caller's second step (they
97
+ * exist only after discovery published, behind the pipeline join).
98
+ */
99
+ export declare function resolveAnimusId(id: string): string | null;
100
+ /**
101
+ * Substitute the bare `__ANIMUS_DEV__` token with its boolean literal —
102
+ * the define mechanism for bundlers without a native one (rollup). The
103
+ * token-as-initializer-conditional shape in the system runtime's is-dev
104
+ * module folds under the bundler's own dead-branch elimination once the
105
+ * literal lands. Returns null when the code carries no token.
106
+ */
107
+ export declare function substituteDevDefine(code: string, isDev: boolean): string | null;
108
+ /**
109
+ * Per-file engine transform from retained state. The path handed to the
110
+ * engine is the rootDir-relative posix key the analysis used — external
111
+ * kit sources ride the same derivation (`../…` keys). Files outside the
112
+ * analysis universe come back unchanged (`hasComponents: false`); a
113
+ * transform before analysis fails loud inside the engine adapter.
114
+ */
115
+ export declare function transformWithEngine(code: string, id: string, ctx: {
116
+ rootDir: string;
117
+ manifestJson: string;
118
+ transformFile: (source: string, path: string, manifestJson: string) => {
119
+ code: string;
120
+ hasComponents: boolean;
121
+ };
122
+ }): string | null;
123
+ /**
124
+ * The one-live-host-per-process constraint, enforced where the invariant
125
+ * lives: the SESSION singleton (its slots are what concurrent hosts would
126
+ * clobber). Re-exported under the host's historical name; the key sits in
127
+ * `SINGLETON_GLOBAL_KEYS`, so test harness resets clear a leaked claim.
128
+ */
129
+ export declare const claimProcessHost: typeof claimExclusiveSessionOwner;
130
+ /**
131
+ * The unplugin factory. One factory invocation = one host = one session
132
+ * per build; the singleton drive loop stays exactly-one (guardrail G1 —
133
+ * the host defines no session class and no drive loop of its own).
134
+ */
135
+ export declare const unpluginFactory: UnpluginFactory<AnimusUnpluginOptions | undefined>;
136
+ //# sourceMappingURL=core.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ Object.defineProperties(exports, {
2
+ __esModule: { value: true },
3
+ [Symbol.toStringTag]: { value: "Module" }
4
+ });
5
+ const require_core = require("./core-B2NB6x1D.cjs");
6
+ //#region src/esbuild.ts
7
+ /**
8
+ * esbuild entry: `import animus from '@animus-ui/unplugin/esbuild'`.
9
+ * The dev-signal define rides `build.initialOptions.define`; the emitted
10
+ * stylesheet is written into `outdir` (or beside `outfile`).
11
+ */
12
+ /** Named beside default: keeps the CJS emission on the `exports.default`
13
+ * interop shape (attw node16). */
14
+ const animusEsbuild = (0, require("unplugin").createEsbuildPlugin)(require_core.unpluginFactory);
15
+ //#endregion
16
+ exports.animusEsbuild = animusEsbuild;
17
+ exports.default = animusEsbuild;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * esbuild entry: `import animus from '@animus-ui/unplugin/esbuild'`.
3
+ * The dev-signal define rides `build.initialOptions.define`; the emitted
4
+ * stylesheet is written into `outdir` (or beside `outfile`).
5
+ */
6
+ export type { AnimusUnpluginOptions } from './options';
7
+ /** Named beside default: keeps the CJS emission on the `exports.default`
8
+ * interop shape (attw node16). */
9
+ export declare const animusEsbuild: (options?: import("@animus-ui/extract/pipeline").AnimusCoreOptions | undefined) => import("unplugin").EsbuildPlugin;
10
+ export default animusEsbuild;
11
+ //# sourceMappingURL=esbuild.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"esbuild.d.ts","sourceRoot":"","sources":["../src/esbuild.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,YAAY,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAEvD;mCACmC;AACnC,eAAO,MAAM,aAAa,qHAAuC,CAAC;eAEnD,aAAa"}
@@ -0,0 +1,13 @@
1
+ import { t as unpluginFactory } from "./core-C3ZORO6V.mjs";
2
+ import { createEsbuildPlugin } from "unplugin";
3
+ //#region src/esbuild.ts
4
+ /**
5
+ * esbuild entry: `import animus from '@animus-ui/unplugin/esbuild'`.
6
+ * The dev-signal define rides `build.initialOptions.define`; the emitted
7
+ * stylesheet is written into `outdir` (or beside `outfile`).
8
+ */
9
+ /** Named beside default: keeps the CJS emission on the `exports.default`
10
+ * interop shape (attw node16). */
11
+ const animusEsbuild = createEsbuildPlugin(unpluginFactory);
12
+ //#endregion
13
+ export { animusEsbuild, animusEsbuild as default };
package/dist/index.cjs ADDED
@@ -0,0 +1,24 @@
1
+ Object.defineProperties(exports, {
2
+ __esModule: { value: true },
3
+ [Symbol.toStringTag]: { value: "Module" }
4
+ });
5
+ const require_core = require("./core-B2NB6x1D.cjs");
6
+ //#region src/index.ts
7
+ /**
8
+ * `@animus-ui/unplugin` — the Animus transform host for non-plugin
9
+ * bundlers (openspec: standalone-extraction-cli, D4/D10). Per-bundler
10
+ * entry points live on subpaths (`./rollup`, `./esbuild`, `./rspack`,
11
+ * `./webpack`); this root exports the unplugin instance and the factory.
12
+ *
13
+ * UNSTABLE MODULE SURFACE: the supported consumer surface is the
14
+ * per-bundler subpath entries. The factory and instance exports exist for
15
+ * the repo's own lanes and tests and may change without semver ceremony
16
+ * until the consumer contract ships (standalone-extraction-cli inc 07).
17
+ */
18
+ /** The unplugin instance: `animusUnplugin.rollup(options)`, `.esbuild(…)`,
19
+ * `.webpack(…)`, `.rspack(…)`. */
20
+ const animusUnplugin = (0, require("unplugin").createUnplugin)(require_core.unpluginFactory);
21
+ //#endregion
22
+ exports.animusUnplugin = animusUnplugin;
23
+ exports.default = animusUnplugin;
24
+ exports.unpluginFactory = require_core.unpluginFactory;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * `@animus-ui/unplugin` — the Animus transform host for non-plugin
3
+ * bundlers (openspec: standalone-extraction-cli, D4/D10). Per-bundler
4
+ * entry points live on subpaths (`./rollup`, `./esbuild`, `./rspack`,
5
+ * `./webpack`); this root exports the unplugin instance and the factory.
6
+ *
7
+ * UNSTABLE MODULE SURFACE: the supported consumer surface is the
8
+ * per-bundler subpath entries. The factory and instance exports exist for
9
+ * the repo's own lanes and tests and may change without semver ceremony
10
+ * until the consumer contract ships (standalone-extraction-cli inc 07).
11
+ */
12
+ import { unpluginFactory } from './core';
13
+ export type { AnimusUnpluginOptions } from './options';
14
+ export { unpluginFactory };
15
+ /** The unplugin instance: `animusUnplugin.rollup(options)`, `.esbuild(…)`,
16
+ * `.webpack(…)`, `.rspack(…)`. */
17
+ export declare const animusUnplugin: import("unplugin").UnpluginInstance<import("@animus-ui/extract/pipeline").AnimusCoreOptions | undefined, boolean>;
18
+ export default animusUnplugin;
19
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AAEzC,YAAY,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,CAAC;AAE3B;mCACmC;AACnC,eAAO,MAAM,cAAc,mHAAkC,CAAC;eAE/C,cAAc"}