@pitlane/assets 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1411 @@
1
+ import { n as quotedList, t as assetsSpecifier } from "./specifier-DLC81sXY.mjs";
2
+ import { t as createAssetManifest } from "./build-BbA-Zq1m.mjs";
3
+ import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
4
+ import { basename, dirname, isAbsolute, join, posix, relative, resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { createFilter, isCSSRequest, normalizePath, parseSync } from "vite";
7
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
8
+ import MagicString from "magic-string";
9
+ import { AsyncLocalStorage } from "node:async_hooks";
10
+ //#region src/vite/boundary-policy.ts
11
+ /**
12
+ * Builds the access check `remix/assets` applies to the files it serves.
13
+ * `root` is a real path; files passed to the check are real absolute paths.
14
+ */
15
+ function createBrowserBoundary(root, options) {
16
+ let allowMatchers = options.allowFiles.map((pattern) => fileMatcher(root, pattern));
17
+ let denyMatchers = (options.denyFiles ?? []).map((pattern) => ({
18
+ pattern,
19
+ matches: fileMatcher(root, pattern)
20
+ }));
21
+ let packageRoots = allowedPackageRoots(root, (options.allowPackages ?? []).map((name) => name.trim()));
22
+ return (file) => {
23
+ if (file.includes("/node_modules/@oxc-project/runtime/")) return { allowed: true };
24
+ if (!(allowMatchers.some((matches) => matches(file)) || packageRoots.some((directory) => within(directory, file)))) return { allowed: false };
25
+ let denial = denyMatchers.find(({ matches }) => matches(file));
26
+ return denial ? {
27
+ allowed: false,
28
+ deniedBy: denial.pattern
29
+ } : { allowed: true };
30
+ };
31
+ }
32
+ /** Whether `file` is `directory` or below it. */
33
+ function within(directory, file) {
34
+ return file === directory || file.startsWith(`${directory}/`);
35
+ }
36
+ function fileMatcher(root, pattern) {
37
+ let path = normalizePath(isAbsolute(pattern) ? pattern : join(root, pattern));
38
+ if (/[*?[\]{}()!+@]/.test(pattern)) return createFilter(path, null, { resolve: false });
39
+ if (!existsSync(path)) return (file) => file === path;
40
+ let real = normalizePath(realpathSync(path));
41
+ return statSync(real).isDirectory() ? (file) => within(real, file) : (file) => file === real;
42
+ }
43
+ /** Whether `name` is a bare or scoped package name, not a path, as `remix/assets` requires. */
44
+ function isPackageName(name) {
45
+ let scoped = name.startsWith("@");
46
+ let parts = (scoped ? name.slice(1) : name).split("/");
47
+ return parts.length === (scoped ? 2 : 1) && parts.every((part) => /^[\w.~-]+$/.test(part) && part !== "." && part !== "..");
48
+ }
49
+ /**
50
+ * The real directories of each named package and of everything its
51
+ * `dependencies` and installed `optionalDependencies` pull in, transitively.
52
+ * Peer dependencies are left out, as `remix/assets` leaves them out.
53
+ */
54
+ function allowedPackageRoots(root, names) {
55
+ let roots = /* @__PURE__ */ new Set();
56
+ let pending = names.map((name) => {
57
+ if (!isPackageName(name)) throw new Error(`[assets] allowPackages values must be package names. Received ${JSON.stringify(name)}.`);
58
+ let directory = packageDirectory(name, root);
59
+ if (!directory) throw new Error(`[assets] Could not resolve allowed package "${name}".`);
60
+ return directory;
61
+ });
62
+ while (pending.length > 0) {
63
+ let directory = pending.pop();
64
+ if (roots.has(directory)) continue;
65
+ roots.add(directory);
66
+ let manifest = JSON.parse(readFileSync(join(directory, "package.json"), "utf8"));
67
+ for (let name of Object.keys(manifest.dependencies ?? {})) {
68
+ let dependency = packageDirectory(name, directory);
69
+ if (!dependency) throw new Error(`[assets] Could not resolve dependency "${name}" of allowed package ${directory}.`);
70
+ pending.push(dependency);
71
+ }
72
+ for (let name of Object.keys(manifest.optionalDependencies ?? {})) {
73
+ let dependency = packageDirectory(name, directory);
74
+ if (dependency) pending.push(dependency);
75
+ }
76
+ }
77
+ return [...roots];
78
+ }
79
+ /** The real directory Node's lookup finds for package `name` from `from`, or `undefined`. */
80
+ function packageDirectory(name, from) {
81
+ for (let directory = from;; directory = dirname(directory)) {
82
+ let candidate = join(directory, "node_modules", name);
83
+ if (existsSync(join(candidate, "package.json"))) return normalizePath(realpathSync(candidate));
84
+ if (dirname(directory) === directory) return void 0;
85
+ }
86
+ }
87
+ //#endregion
88
+ //#region src/vite/entries.ts
89
+ function sourceKey(root, id) {
90
+ let file = id.split("?")[0].split("#")[0];
91
+ if (file.startsWith("file://")) file = fileURLToPath(file);
92
+ return normalizePath(relative(root, file));
93
+ }
94
+ function inputPath(root, key) {
95
+ let path = key.split("#")[0];
96
+ if (path.startsWith("file://")) return fileURLToPath(path);
97
+ if (path.startsWith("file:")) path = path.slice(5);
98
+ return resolve(root, path.replace(/^\/+/, ""));
99
+ }
100
+ function fileModule(id) {
101
+ return !id.startsWith("\0") && isAbsolute(id.split("?")[0]);
102
+ }
103
+ //#endregion
104
+ //#region src/vite/folded-files.ts
105
+ /** `context`, with `addWatchFile` reporting each file to `observe` before Vite records it. */
106
+ function watching(context, observe) {
107
+ return new Proxy(context, { get(target, property) {
108
+ if (property === "addWatchFile") return (file) => {
109
+ observe(file);
110
+ target.addWatchFile(file);
111
+ };
112
+ let value = Reflect.get(target, property, target);
113
+ return typeof value === "function" ? value.bind(target) : value;
114
+ } });
115
+ }
116
+ function unsupported(name) {
117
+ return /* @__PURE__ */ new Error(`[assets] This Vite version does not expose the ${name} hook the browser boundary reads. Use a supported Vite version.`);
118
+ }
119
+ /** Calls `observe` with every file the folding plugins report, by wrapping their hooks' contexts. */
120
+ function observeFoldedFiles(config, observe) {
121
+ let plugin = (name) => config.plugins.find((candidate) => candidate.name === name);
122
+ for (let name of ["vite:css", "vite:worker-import-meta-url"]) {
123
+ let hook = plugin(name)?.transform;
124
+ if (typeof hook !== "object") throw unsupported(name);
125
+ let handler = hook.handler;
126
+ hook.handler = function(code, id, options) {
127
+ let context = watching(this, (file) => observe(this.environment, name, id, file));
128
+ return handler.call(context, code, id, options);
129
+ };
130
+ }
131
+ let hook = plugin("vite:worker")?.load;
132
+ if (typeof hook !== "object") throw unsupported("vite:worker");
133
+ let handler = hook.handler;
134
+ hook.handler = function(id, options) {
135
+ let context = watching(this, (file) => observe(this.environment, "vite:worker", id, file));
136
+ return handler.call(context, id, options);
137
+ };
138
+ }
139
+ //#endregion
140
+ //#region src/vite/boundary.ts
141
+ function describe({ file, importer, deniedBy }) {
142
+ let reason = deniedBy ? `matches denyFiles ${JSON.stringify(deniedBy)}` : "not in allowFiles or allowPackages";
143
+ return `${file}${importer ? ` (imported by ${importer})` : ""}: ${reason}`;
144
+ }
145
+ /** Query-free path of a module id or emitted file name. */
146
+ function filePath(id) {
147
+ return normalizePath(id.split(/[?#]/, 1)[0]);
148
+ }
149
+ /**
150
+ * A page, or an inline script Vite proxies out of one: the boundary governs
151
+ * what a page loads, not the page. An HTML file a module imports is data.
152
+ */
153
+ function htmlDocument(id) {
154
+ return filePath(id).endsWith(".html") && (!id.includes("?") || /[?&]html-proxy\b/.test(id));
155
+ }
156
+ /**
157
+ * The server files a build copies into the client output: every stylesheet
158
+ * the server graph imports, except as a string or URL, and the assets its
159
+ * chunks link.
160
+ */
161
+ function publishedServerFiles(bundle) {
162
+ let files = [];
163
+ for (let output of Object.values(bundle)) {
164
+ if (output.type !== "chunk" || !output.viteMetadata) continue;
165
+ for (let id of output.moduleIds) if (isCSSRequest(id) && !/[?&](?:inline|raw|url)\b/.test(id)) files.push(id);
166
+ for (let fileName of output.viteMetadata.importedAssets) {
167
+ let asset = bundle[fileName];
168
+ if (asset?.type === "asset") files.push(...asset.originalFileNames);
169
+ }
170
+ }
171
+ return files;
172
+ }
173
+ /**
174
+ * Holds the client graph and every file the build publishes for the browser
175
+ * to `allowFiles`, `allowPackages`, and `denyFiles`, the way `remix/assets`
176
+ * holds the files its asset server answers.
177
+ */
178
+ function browserBoundary(options, serverEnvironments) {
179
+ let root = "";
180
+ let inspect;
181
+ let viteClient = normalizePath(dirname(fileURLToPath(import.meta.resolve("vite/dist/client/client.mjs"))));
182
+ /** Build module id → the files a folding plugin read into it. */
183
+ let foldedFiles = /* @__PURE__ */ new Map();
184
+ function refusal(file, importer) {
185
+ let verdict = inspect(file);
186
+ if (verdict.allowed) return;
187
+ return {
188
+ file: sourceKey(root, file),
189
+ importer: importer && sourceKey(root, importer),
190
+ deniedBy: verdict.deniedBy
191
+ };
192
+ }
193
+ /** The real path of the file `path` names from the root, or `undefined` when none exists. */
194
+ function existingFile(path) {
195
+ let file = resolve(root, filePath(path));
196
+ if (!existsSync(file) || !statSync(file).isFile()) return;
197
+ return normalizePath(realpathSync(file));
198
+ }
199
+ /** The source file a dev request reaches, or `undefined` when Vite itself supplies it. */
200
+ function servedSource(environment, file) {
201
+ if (within(viteClient, file)) return;
202
+ let optimizer = environment.depsOptimizer;
203
+ if (!optimizer?.isOptimizedDepFile(file)) return file;
204
+ let { optimized, discovered } = optimizer.metadata;
205
+ let dependency = [...Object.values(optimized), ...Object.values(discovered)].find((info) => normalizePath(info.file) === file);
206
+ return dependency?.src && normalizePath(dependency.src);
207
+ }
208
+ function devRefusal(environment, file, importer) {
209
+ let source = servedSource(environment, file);
210
+ if (!source) return;
211
+ importer ??= [...environment.moduleGraph.getModulesByFile(file) ?? []].flatMap((module) => [...module.importers]).find((module) => module.file)?.file ?? void 0;
212
+ return refusal(source, importer);
213
+ }
214
+ return {
215
+ name: "pitlane-assets-boundary",
216
+ sharedDuringBuild: true,
217
+ configResolved(config) {
218
+ root = config.root;
219
+ inspect = createBrowserBoundary(normalizePath(realpathSync(root)), options);
220
+ observeFoldedFiles(config, (environment, plugin, module, file) => {
221
+ if (plugin === "vite:css" && !isCSSRequest(file) && !config.assetsInclude(file)) return;
222
+ if (environment.mode === "build") {
223
+ let files = foldedFiles.get(module);
224
+ if (!files) foldedFiles.set(module, files = /* @__PURE__ */ new Set());
225
+ files.add(file);
226
+ return;
227
+ }
228
+ if (environment.name !== "client" || environment.mode !== "dev") return;
229
+ let source = existingFile(file);
230
+ let refused = source && devRefusal(environment, source, module);
231
+ if (refused) throw new Error(`[assets] Outside the browser boundary: ${describe(refused)}`);
232
+ });
233
+ },
234
+ configureServer(server) {
235
+ let client = server.environments.client;
236
+ let { publicDir, base } = server.config;
237
+ server.middlewares.use(((request, _response, next) => {
238
+ let path;
239
+ try {
240
+ path = decodeURIComponent(filePath(request.url ?? "/"));
241
+ } catch {
242
+ return next();
243
+ }
244
+ if (base !== "/" && path.startsWith(base)) path = `/${path.slice(base.length)}`;
245
+ if (path.startsWith("/@") && !path.startsWith("/@fs/")) return next();
246
+ if (publicDir && existsSync(join(publicDir, path))) return next();
247
+ if (htmlDocument(path)) return next();
248
+ let requested = join(root, path);
249
+ if (path.startsWith("/@fs/")) {
250
+ let fsPath = path.slice(5);
251
+ requested = /^[A-Za-z]:\//.test(fsPath) ? fsPath : `/${fsPath}`;
252
+ }
253
+ let file = existingFile(requested);
254
+ let refused = file && devRefusal(client, file);
255
+ if (!refused) return next();
256
+ next(/* @__PURE__ */ new Error(`[assets] Outside the browser boundary: ${describe(refused)}`));
257
+ }));
258
+ },
259
+ transform: {
260
+ order: "pre",
261
+ handler(_code, id) {
262
+ let environment = this.environment;
263
+ if (environment.mode !== "dev" || environment.name !== "client" || !fileModule(id)) return;
264
+ if (htmlDocument(id)) return;
265
+ let refused = devRefusal(environment, existingFile(id) ?? filePath(id));
266
+ if (refused) throw new Error(`[assets] Outside the browser boundary: ${describe(refused)}`);
267
+ }
268
+ },
269
+ generateBundle(_options, bundle) {
270
+ let name = this.environment.name;
271
+ let published = [];
272
+ if (name === "client") {
273
+ for (let id of this.getModuleIds()) {
274
+ if (!fileModule(id)) continue;
275
+ let info = this.getModuleInfo(id);
276
+ let importer = [...info?.importers ?? [], ...info?.dynamicImporters ?? []].find(fileModule);
277
+ published.push({
278
+ path: id,
279
+ importer
280
+ });
281
+ }
282
+ for (let output of Object.values(bundle)) {
283
+ if (output.type !== "asset" || output.names.some(isCSSRequest)) continue;
284
+ for (let path of output.originalFileNames) published.push({ path });
285
+ }
286
+ } else if (serverEnvironments.includes(name)) for (let path of publishedServerFiles(bundle)) {
287
+ let importer = this.getModuleInfo(path)?.importers.find(fileModule);
288
+ published.push({
289
+ path,
290
+ importer
291
+ });
292
+ }
293
+ let refusals = /* @__PURE__ */ new Map();
294
+ for (let { path, importer } of published) {
295
+ let entries = [{
296
+ path,
297
+ importer
298
+ }, ...[...foldedFiles.get(path) ?? []].map((file) => ({
299
+ path: file,
300
+ importer: path
301
+ }))];
302
+ for (let entry of entries) {
303
+ let file = !htmlDocument(entry.path) && existingFile(entry.path);
304
+ let refused = file && refusal(file, entry.importer);
305
+ if (refused && !refusals.get(refused.file)?.importer) refusals.set(refused.file, refused);
306
+ }
307
+ }
308
+ if (refusals.size === 0) return;
309
+ let lines = [...refusals.values()].map(describe);
310
+ this.error(`[assets] Outside the browser boundary:\n ${lines.join("\n ")}`);
311
+ }
312
+ };
313
+ }
314
+ //#endregion
315
+ //#region src/vite/output.ts
316
+ function captureOutput(bundle, config, role, assetFiles, entryFiles) {
317
+ let graph = {
318
+ role,
319
+ chunks: Object.create(null),
320
+ entries: Object.create(null),
321
+ assets: Object.create(null)
322
+ };
323
+ let globalStylesheets = config.build.cssCodeSplit ? [] : Object.values(bundle).flatMap((output) => output.type === "asset" && output.originalFileNames.includes("style.css") ? [output.fileName] : []);
324
+ let linked = role === "server" ? linkedFiles(bundle, globalStylesheets) : void 0;
325
+ for (let output of Object.values(bundle)) {
326
+ if (output.type === "asset") {
327
+ if (linked && !linked.has(output.fileName)) continue;
328
+ let stylesheet = output.names.some(isCSSRequest);
329
+ for (let file of output.originalFileNames) {
330
+ if (stylesheet && !isCSSRequest(file)) continue;
331
+ let key = fileModule(file) ? sourceKey(config.root, file) : file.replace(/^\.\//, "");
332
+ graph.assets[key] = output.fileName;
333
+ }
334
+ continue;
335
+ }
336
+ graph.chunks[output.fileName] = {
337
+ file: output.fileName,
338
+ modules: [...new Set(output.moduleIds.filter(fileModule).map((id) => sourceKey(config.root, id)))],
339
+ imports: output.imports.filter((id) => bundle[id]?.type === "chunk"),
340
+ dynamicImports: output.dynamicImports.filter((id) => bundle[id]?.type === "chunk"),
341
+ stylesheets: [...globalStylesheets, ...output.viteMetadata?.importedCss ?? []]
342
+ };
343
+ if (role === "client" && output.isEntry && output.facadeModuleId && fileModule(output.facadeModuleId)) graph.entries[sourceKey(config.root, output.facadeModuleId)] = output.fileName;
344
+ }
345
+ for (let [key, file] of assetFiles) graph.assets[key] = file;
346
+ for (let [key, file] of entryFiles) if (bundle[file]?.type === "chunk") graph.entries[key] = file;
347
+ return graph;
348
+ }
349
+ function linkedFiles(bundle, globalStylesheets) {
350
+ let files = new Set(globalStylesheets);
351
+ for (let output of Object.values(bundle)) {
352
+ if (output.type !== "chunk" || !output.viteMetadata) continue;
353
+ for (let file of output.viteMetadata.importedCss) files.add(file);
354
+ for (let file of output.viteMetadata.importedAssets) files.add(file);
355
+ }
356
+ return files;
357
+ }
358
+ function captureImportMap(bundle, fileName) {
359
+ let output = bundle[fileName];
360
+ if (output?.type !== "asset") throw new Error(`[assets] The client build did not emit its configured import map "${fileName}".`);
361
+ let text = typeof output.source === "string" ? output.source : new TextDecoder().decode(output.source);
362
+ return JSON.parse(text);
363
+ }
364
+ /**
365
+ * The public URL `renderBuiltUrl` chooses for a built file. A server document
366
+ * has no importer to be relative to, so `{ relative: true }` keeps the
367
+ * base-joined path.
368
+ */
369
+ function publicFile(config, file) {
370
+ let render = config.experimental.renderBuiltUrl;
371
+ if (!render) return file;
372
+ let rendered = render(file, {
373
+ type: "asset",
374
+ hostType: "html",
375
+ hostId: "",
376
+ ssr: true
377
+ });
378
+ if (typeof rendered === "string") return rendered;
379
+ if (rendered && "runtime" in rendered && rendered.runtime) throw new Error("[assets] renderBuiltUrl must return a URL for server HTML; runtime JavaScript cannot be serialized in an asset manifest.");
380
+ return file;
381
+ }
382
+ //#endregion
383
+ //#region src/vite/binding-names.ts
384
+ /** Calls `visit` on every AST node below `root`, in no particular order. */
385
+ function forEachNode(root, visit) {
386
+ let pending = [root];
387
+ while (pending.length) {
388
+ let node = pending.pop();
389
+ if (!node || typeof node !== "object") continue;
390
+ if (Array.isArray(node)) {
391
+ pending.push(...node);
392
+ continue;
393
+ }
394
+ visit(node);
395
+ for (let child of Object.values(node)) if (child && typeof child === "object") pending.push(child);
396
+ }
397
+ }
398
+ function patternNames(pattern) {
399
+ switch (pattern?.type) {
400
+ case "Identifier": return [pattern.name];
401
+ case "AssignmentPattern": return patternNames(pattern.left);
402
+ case "RestElement": return patternNames(pattern.argument);
403
+ case "TSParameterProperty": return patternNames(pattern.parameter);
404
+ case "ArrayPattern": return pattern.elements.flatMap(patternNames);
405
+ case "ObjectPattern": return pattern.properties.flatMap((property) => patternNames(property.type === "RestElement" ? property : property.value));
406
+ default: return [];
407
+ }
408
+ }
409
+ /** The names a declaration binds in the scope that contains it. */
410
+ function declaredNames(node) {
411
+ switch (node?.type) {
412
+ case "VariableDeclaration": return node.declarations.flatMap((declarator) => patternNames(declarator.id));
413
+ case "FunctionDeclaration":
414
+ case "TSDeclareFunction":
415
+ case "ClassDeclaration":
416
+ case "TSEnumDeclaration":
417
+ case "TSImportEqualsDeclaration": return node.id ? [node.id.name] : [];
418
+ case "TSModuleDeclaration": return node.id.type === "Identifier" ? [node.id.name] : [];
419
+ default: return [];
420
+ }
421
+ }
422
+ function boundNames(node) {
423
+ switch (node.type) {
424
+ case "VariableDeclaration": return declaredNames(node);
425
+ case "FunctionDeclaration":
426
+ case "FunctionExpression":
427
+ case "ArrowFunctionExpression": {
428
+ let parameters = node.params.flatMap(patternNames);
429
+ return node.type !== "ArrowFunctionExpression" && node.id ? [node.id.name, ...parameters] : parameters;
430
+ }
431
+ case "ClassExpression": return node.id ? [node.id.name] : [];
432
+ case "CatchClause": return patternNames(node.param);
433
+ case "ImportSpecifier":
434
+ case "ImportDefaultSpecifier":
435
+ case "ImportNamespaceSpecifier": return [node.local.name];
436
+ default: return declaredNames(node);
437
+ }
438
+ }
439
+ /** How many times a module binds each name, counting every declaration in every scope. */
440
+ function bindingCounts(program) {
441
+ let counts = /* @__PURE__ */ new Map();
442
+ forEachNode(program, (node) => {
443
+ for (let name of boundNames(node)) counts.set(name, (counts.get(name) ?? 0) + 1);
444
+ });
445
+ return counts;
446
+ }
447
+ //#endregion
448
+ //#region src/vite/resolver-bindings.ts
449
+ function moduleExportName(node) {
450
+ return node.type === "Literal" ? node.value : node.name;
451
+ }
452
+ function constructs(node, constructors) {
453
+ while (node?.type === "AwaitExpression" || node?.type === "ParenthesizedExpression") node = node.type === "AwaitExpression" ? node.argument : node.expression;
454
+ return node?.type === "CallExpression" && node.callee.type === "Identifier" && constructors.has(node.callee.name);
455
+ }
456
+ /** The name a chain of top-level `const a = b` copies starts from. */
457
+ function aliasRoot(name, initializers) {
458
+ let seen = /* @__PURE__ */ new Set();
459
+ let init = initializers.get(name);
460
+ while (init?.type === "Identifier" && !seen.has(name)) {
461
+ seen.add(name);
462
+ name = init.name;
463
+ init = initializers.get(name);
464
+ }
465
+ return name;
466
+ }
467
+ function topLevelBindings(program) {
468
+ let counts = bindingCounts(program);
469
+ let importedConstructors = /* @__PURE__ */ new Set();
470
+ let imports = /* @__PURE__ */ new Map();
471
+ let initializers = /* @__PURE__ */ new Map();
472
+ for (let statement of program.body) {
473
+ if (statement.type === "ImportDeclaration") {
474
+ let module = statement.source.value;
475
+ for (let specifier of statement.specifiers) {
476
+ let local = specifier.local.name;
477
+ if (specifier.type === "ImportNamespaceSpecifier" || counts.get(local) !== 1) continue;
478
+ let name = specifier.type === "ImportDefaultSpecifier" ? "default" : moduleExportName(specifier.imported);
479
+ if (name === "createAssetResolver" && (module === "@pitlane/assets" || module === "pitlane/assets")) importedConstructors.add(local);
480
+ else imports.set(local, {
481
+ module,
482
+ name
483
+ });
484
+ }
485
+ }
486
+ let declaration = statement.type === "ExportNamedDeclaration" ? statement.declaration : statement;
487
+ if (declaration?.type !== "VariableDeclaration") continue;
488
+ for (let { id, init } of declaration.declarations) if (id.type === "Identifier" && init && counts.get(id.name) === 1) initializers.set(id.name, init);
489
+ }
490
+ let constructors = new Set(importedConstructors);
491
+ for (let name of initializers.keys()) if (importedConstructors.has(aliasRoot(name, initializers))) constructors.add(name);
492
+ let resolvers = /* @__PURE__ */ new Map();
493
+ for (let name of [...imports.keys(), ...initializers.keys()]) {
494
+ let root = aliasRoot(name, initializers);
495
+ let origin = imports.get(root) ?? (constructs(initializers.get(root), constructors) ? "local" : void 0);
496
+ if (origin) resolvers.set(name, origin);
497
+ }
498
+ return {
499
+ constructors,
500
+ resolvers
501
+ };
502
+ }
503
+ /** Where the resolver an expression evaluates to comes from, when the expression can name one. */
504
+ function resolverOrigin(node, bindings) {
505
+ if (constructs(node, bindings.constructors)) return "local";
506
+ if (node.type === "Identifier") return bindings.resolvers.get(node.name);
507
+ }
508
+ //#endregion
509
+ //#region src/vite/resolver-usage.ts
510
+ const RELEVANT = /\bget(?:ScriptEntry|Href)\b|\bcreateAssetResolver\b|\bexport\b/;
511
+ /** The method a member call names, through `.name` or a string-literal `["name"]`. */
512
+ function memberName(callee) {
513
+ let { property } = callee;
514
+ if (!callee.computed) return property.type === "Identifier" ? property.name : void 0;
515
+ return property.type === "Literal" && typeof property.value === "string" ? property.value : void 0;
516
+ }
517
+ function literalCall(call, bindings) {
518
+ let { callee, arguments: args } = call;
519
+ if (callee.type !== "MemberExpression") return;
520
+ let method = memberName(callee);
521
+ let argument = args[0];
522
+ if (method !== "getScriptEntry" && method !== "getHref") return;
523
+ if (argument?.type !== "Literal" || typeof argument.value !== "string") return;
524
+ let receiver = resolverOrigin(callee.object, bindings);
525
+ if (!receiver) return;
526
+ return {
527
+ input: {
528
+ key: argument.value,
529
+ kind: method === "getScriptEntry" ? "script" : "asset"
530
+ },
531
+ receiver
532
+ };
533
+ }
534
+ function resolverExports(program, bindings) {
535
+ let exported = /* @__PURE__ */ new Map();
536
+ for (let statement of program.body) {
537
+ if (statement.type === "ExportDefaultDeclaration") exported.set("default", resolverOrigin(statement.declaration, bindings) ?? null);
538
+ if (statement.type === "ExportAllDeclaration" && statement.exported) exported.set(moduleExportName(statement.exported), null);
539
+ if (statement.type !== "ExportNamedDeclaration") continue;
540
+ for (let name of declaredNames(statement.declaration)) exported.set(name, bindings.resolvers.get(name) ?? null);
541
+ for (let specifier of statement.specifiers) {
542
+ let found = statement.source ? {
543
+ module: statement.source.value,
544
+ name: moduleExportName(specifier.local)
545
+ } : resolverOrigin(specifier.local, bindings);
546
+ exported.set(moduleExportName(specifier.exported), found ?? null);
547
+ }
548
+ }
549
+ return exported;
550
+ }
551
+ /**
552
+ * A module's literal `getScriptEntry` and `getHref` calls whose receiver may be
553
+ * a resolver, and its exports that may name one. Import specifiers stay
554
+ * unresolved; pass the result to {@link linkResolverUsage}.
555
+ */
556
+ function scanResolverUsage(code, id) {
557
+ if (!RELEVANT.test(code)) return;
558
+ let { program } = parseSync(id, code);
559
+ let bindings = topLevelBindings(program);
560
+ let calls = [];
561
+ forEachNode(program, (node) => {
562
+ let call = node.type === "CallExpression" ? literalCall(node, bindings) : void 0;
563
+ if (call) calls.push(call);
564
+ });
565
+ let exported = resolverExports(program, bindings);
566
+ let starExports = program.body.flatMap((statement) => statement.type === "ExportAllDeclaration" && !statement.exported ? [statement.source.value] : []);
567
+ let namesResolver = [...exported.values()].some((origin) => origin !== null);
568
+ if (calls.length === 0 && !namesResolver && starExports.length === 0) return;
569
+ return {
570
+ calls,
571
+ resolverExports: exported,
572
+ starExports
573
+ };
574
+ }
575
+ /** Replaces import specifiers with module ids, dropping those that are external or unresolved. */
576
+ async function linkResolverUsage(usage, resolve) {
577
+ let specifiers = new Set(usage.starExports);
578
+ for (let origin of [...usage.calls.map((call) => call.receiver), ...usage.resolverExports.values()]) if (origin && origin !== "local") specifiers.add(origin.module);
579
+ let resolved = /* @__PURE__ */ new Map();
580
+ await Promise.all([...specifiers].map(async (specifier) => {
581
+ let module = await resolve(specifier);
582
+ if (module && !module.external) resolved.set(specifier, module.id);
583
+ }));
584
+ let link = (origin) => {
585
+ if (origin === "local") return origin;
586
+ let module = resolved.get(origin.module);
587
+ return module === void 0 ? void 0 : {
588
+ module,
589
+ name: origin.name
590
+ };
591
+ };
592
+ let calls = [];
593
+ for (let call of usage.calls) {
594
+ let receiver = link(call.receiver);
595
+ if (receiver) calls.push({
596
+ input: call.input,
597
+ receiver
598
+ });
599
+ }
600
+ let exported = /* @__PURE__ */ new Map();
601
+ for (let [name, origin] of usage.resolverExports) exported.set(name, origin && (link(origin) ?? null));
602
+ return {
603
+ calls,
604
+ resolverExports: exported,
605
+ starExports: usage.starExports.flatMap((specifier) => resolved.get(specifier) ?? [])
606
+ };
607
+ }
608
+ function isResolver(modules, origin, seen) {
609
+ if (origin === "local") return true;
610
+ let binding = `${origin.module}\0${origin.name}`;
611
+ if (seen.has(binding)) return false;
612
+ seen.add(binding);
613
+ let usage = modules.get(origin.module);
614
+ let exported = usage?.resolverExports.get(origin.name);
615
+ if (!usage || exported === null) return false;
616
+ if (exported) return isResolver(modules, exported, seen);
617
+ return origin.name !== "default" && usage.starExports.some((module) => isResolver(modules, {
618
+ module,
619
+ name: origin.name
620
+ }, seen));
621
+ }
622
+ /** The browser inputs a module's literal calls register, given its environment's linked usage. */
623
+ function literalInputs(modules, id) {
624
+ return (modules.get(id)?.calls ?? []).filter((call) => isResolver(modules, call.receiver, /* @__PURE__ */ new Set())).map((call) => call.input);
625
+ }
626
+ //#endregion
627
+ //#region src/vite/state.ts
628
+ /**
629
+ * The manifest module a build writes at the root of each server environment's
630
+ * output directory. The server chunks that read the manifest import this file
631
+ * by a relative path, so a host that deploys a single module must bundle it in.
632
+ */
633
+ const ASSETS_MANIFEST_FILE = "__pitlane_assets_manifest.js";
634
+ const MANIFEST_ID = "\0pitlane:assets-manifest";
635
+ const MANIFEST_EXTERNAL = "pitlane:assets-build-manifest";
636
+ const EMPTY_INPUT = "\0pitlane:assets-empty-input";
637
+ /** The manifest was imported from a server environment `assets()` does not serve. */
638
+ function unservedEnvironmentError(name, served) {
639
+ return /* @__PURE__ */ new Error(`[assets] The "${name}" environment imported ${assetsSpecifier("manifest")}, but assets() serves only ${quotedList(served, "conjunction")}. Add "${name}" to assets({ serverEnvironments }) from ${assetsSpecifier("vite-plugin")}.`);
640
+ }
641
+ //#endregion
642
+ //#region src/vite/build.ts
643
+ async function writeManifests(state) {
644
+ let config = state.config;
645
+ let client = state.outputs.get("client");
646
+ if (!client || state.serverEnvironments.some((name) => !state.outputs.has(name))) return;
647
+ let clientStylesheets = /* @__PURE__ */ new Map();
648
+ for (let asset of Object.values(client.bundle)) if (asset.type === "asset" && isCSSRequest(asset.fileName)) clientStylesheets.set(Buffer.from(asset.source).toString("latin1"), asset.fileName);
649
+ let environments = Object.create(null);
650
+ let twins = /* @__PURE__ */ new Map();
651
+ for (let [name, output] of state.outputs) {
652
+ let graph = output.graph;
653
+ let twin = /* @__PURE__ */ new Map();
654
+ if (output !== client) for (let asset of Object.values(output.bundle)) {
655
+ if (asset.type !== "asset" || !isCSSRequest(asset.fileName)) continue;
656
+ let file = clientStylesheets.get(Buffer.from(asset.source).toString("latin1"));
657
+ if (file) twin.set(asset.fileName, file);
658
+ }
659
+ twins.set(name, twin);
660
+ environments[name] = {
661
+ ...graph,
662
+ chunks: Object.fromEntries(Object.entries(graph.chunks).map(([key, chunk]) => [key, {
663
+ ...chunk,
664
+ file: publicFile(config, chunk.file),
665
+ stylesheets: chunk.stylesheets.map((file) => publicFile(config, twin.get(file) ?? file))
666
+ }])),
667
+ assets: Object.fromEntries(Object.entries(graph.assets).map(([key, file]) => [key, publicFile(config, twin.get(file) ?? file)]))
668
+ };
669
+ }
670
+ for (let name of state.serverEnvironments) {
671
+ let manifest = createAssetManifest({
672
+ base: config.base,
673
+ environments: {
674
+ client: environments.client,
675
+ [name]: environments[name]
676
+ },
677
+ importMap: state.importMap
678
+ });
679
+ let output = state.outputs.get(name);
680
+ await mkdir(output.outDir, { recursive: true });
681
+ await writeFile(resolve(output.outDir, ASSETS_MANIFEST_FILE), `export default JSON.parse(${JSON.stringify(JSON.stringify({
682
+ ...manifest,
683
+ serverEnvironment: name
684
+ }))});\n`);
685
+ await publishLinkedFiles(output, twins.get(name), client.outDir);
686
+ }
687
+ }
688
+ /**
689
+ * Copies the stylesheets and assets a server graph links into the public client
690
+ * output, so every URL its manifest names is served. A stylesheet with a client
691
+ * twin already is.
692
+ */
693
+ async function publishLinkedFiles(output, twin, publicDir) {
694
+ let linked = /* @__PURE__ */ new Set([...Object.values(output.graph.chunks).flatMap((chunk) => chunk.stylesheets), ...Object.values(output.graph.assets)]);
695
+ for (let file of linked) {
696
+ let asset = output.bundle[file];
697
+ if (asset?.type !== "asset" || twin.has(file)) continue;
698
+ let destination = resolve(publicDir, file);
699
+ await mkdir(dirname(destination), { recursive: true });
700
+ await writeFile(destination, asset.source);
701
+ }
702
+ }
703
+ function importMapFileName(config) {
704
+ let options = config.build.rolldownOptions.experimental?.chunkImportMap;
705
+ return typeof options === "object" ? options.fileName ?? "importmap.json" : "importmap.json";
706
+ }
707
+ function assetBuild(state) {
708
+ return {
709
+ name: "pitlane-assets-build",
710
+ sharedDuringBuild: true,
711
+ apply: "build",
712
+ configEnvironment(name, config) {
713
+ if (state.serverEnvironments.includes(name)) return { build: { emitAssets: true } };
714
+ if (name === "client" && !config.build?.rolldownOptions?.input && !config.build?.rollupOptions?.input) return { build: { rolldownOptions: { input: EMPTY_INPUT } } };
715
+ },
716
+ resolveId(id) {
717
+ if (id === "\0pitlane:assets-empty-input") return id;
718
+ },
719
+ load(id) {
720
+ if (id === "\0pitlane:assets-empty-input") return "export {};";
721
+ },
722
+ async buildStart() {
723
+ if (this.environment.name !== "client") return;
724
+ let missing = state.serverEnvironments.filter((name) => !state.outputs.has(name));
725
+ if (missing.length) throw new Error(`[assets] Client built before server environments: ${missing.join(", ")}. Build servers before client.`);
726
+ let root = state.config.root;
727
+ state.assetReferences.clear();
728
+ state.scriptReferences.clear();
729
+ let inputs = new Map(state.inputs);
730
+ for (let modules of state.resolverUsage.values()) for (let owner of modules.keys()) for (let input of literalInputs(modules, owner)) {
731
+ let file = inputPath(root, input.key);
732
+ inputs.set(file, {
733
+ ...input,
734
+ key: sourceKey(root, file)
735
+ });
736
+ }
737
+ for (let input of inputs.values()) {
738
+ let id = inputPath(state.config.root, input.key);
739
+ if (input.kind === "asset" && !isCSSRequest(id) && state.config.assetsInclude(id)) {
740
+ let reference = this.emitFile({
741
+ type: "asset",
742
+ name: basename(id),
743
+ originalFileName: id,
744
+ source: await readFile(id)
745
+ });
746
+ state.assetReferences.set(sourceKey(root, id), reference);
747
+ } else {
748
+ let reference = this.emitFile({
749
+ type: "chunk",
750
+ id,
751
+ preserveSignature: "exports-only"
752
+ });
753
+ if (!isCSSRequest(id)) state.scriptReferences.set(sourceKey(root, id), reference);
754
+ }
755
+ }
756
+ },
757
+ renderChunk(code, chunk) {
758
+ if (!code.includes("pitlane:assets-build-manifest")) return;
759
+ let file = posix.relative(posix.dirname(chunk.fileName), ASSETS_MANIFEST_FILE);
760
+ if (!file.startsWith(".")) file = `./${file}`;
761
+ return {
762
+ code: code.replaceAll(MANIFEST_EXTERNAL, file),
763
+ map: null
764
+ };
765
+ },
766
+ generateBundle: {
767
+ order: "post",
768
+ handler(_options, bundle) {
769
+ let removed = /* @__PURE__ */ new Set();
770
+ for (let [name, output] of Object.entries(bundle)) if (output.type === "chunk" && output.facadeModuleId === "\0pitlane:assets-empty-input") {
771
+ removed.add(name);
772
+ delete bundle[name];
773
+ }
774
+ if (this.environment.name !== "client" || !state.mapsEnabled) return;
775
+ let fileName = importMapFileName(this.environment.config);
776
+ let map = captureImportMap(bundle, fileName);
777
+ let base = this.environment.config.base;
778
+ for (let [key, href] of Object.entries(map.imports)) {
779
+ if (!href.startsWith(base)) continue;
780
+ let file = href.slice(base.length);
781
+ if (removed.has(file) || !(file in bundle)) delete map.imports[key];
782
+ }
783
+ let artifact = bundle[fileName];
784
+ if (artifact.type === "asset") artifact.source = JSON.stringify(map);
785
+ }
786
+ },
787
+ writeBundle: {
788
+ order: "post",
789
+ async handler(_options, bundle) {
790
+ let name = this.environment.name;
791
+ if (name !== "client" && !state.serverEnvironments.includes(name)) return;
792
+ let config = this.environment.config;
793
+ let assets = /* @__PURE__ */ new Map();
794
+ let scripts = /* @__PURE__ */ new Map();
795
+ if (name === "client") {
796
+ for (let [key, reference] of state.assetReferences) assets.set(key, this.getFileName(reference));
797
+ for (let [key, reference] of state.scriptReferences) scripts.set(key, this.getFileName(reference));
798
+ state.importMap = state.mapsEnabled ? captureImportMap(bundle, importMapFileName(config)) : void 0;
799
+ }
800
+ state.outputs.set(name, {
801
+ graph: captureOutput(bundle, config, name === "client" ? "client" : "server", assets, scripts),
802
+ bundle,
803
+ outDir: resolve(config.root, config.build.outDir)
804
+ });
805
+ await writeManifests(state);
806
+ }
807
+ },
808
+ buildApp: {
809
+ order: "pre",
810
+ async handler(builder) {
811
+ let build = builder.build.bind(builder);
812
+ builder.build = (async (environment) => {
813
+ if (environment.isBuilt) return;
814
+ return build(environment);
815
+ });
816
+ for (let name of state.serverEnvironments) {
817
+ let environment = builder.environments[name];
818
+ if (!environment) throw new Error(`[assets] Unknown server environment: ${name}`);
819
+ await builder.build(environment);
820
+ }
821
+ await builder.build(builder.environments.client);
822
+ }
823
+ }
824
+ };
825
+ }
826
+ //#endregion
827
+ //#region src/vite/dev-css.ts
828
+ function linkedStylesheetLookup(config) {
829
+ return `
830
+ function pitlaneStyleKey(href) {
831
+ const url = new URL(href, document.baseURI);
832
+ url.searchParams.delete("t");
833
+ url.searchParams.delete("direct");
834
+ url.searchParams.sort();
835
+ return url.href;
836
+ }
837
+ function pitlaneHasLinkedStylesheet(id) {
838
+ const root = ${JSON.stringify(config.root + "/")};
839
+ const path = id.startsWith(root) ? id.slice(root.length) : "@fs/" + id.replace(/^\\//, "");
840
+ const key = pitlaneStyleKey(${JSON.stringify(config.base)} + path);
841
+ for (const link of document.querySelectorAll('link[rel="stylesheet"][href]')) {
842
+ if (pitlaneStyleKey(link.href) === key) return true;
843
+ }
844
+ return false;
845
+ }
846
+ `;
847
+ }
848
+ function assetStyles() {
849
+ let config;
850
+ let clientPath = normalizePath(fileURLToPath(import.meta.resolve("vite/dist/client/client.mjs")));
851
+ return {
852
+ name: "pitlane-assets-css",
853
+ apply: "serve",
854
+ configResolved(resolved) {
855
+ config = resolved;
856
+ },
857
+ configureServer(server) {
858
+ server.middlewares.use((request, _response, next) => {
859
+ if (request.headers.accept?.includes("text/css") && request.url?.includes("&lang.css=")) request.url = request.url.replace("&lang.css=", "?lang.css");
860
+ next();
861
+ });
862
+ },
863
+ transform: {
864
+ order: "post",
865
+ handler(code, id) {
866
+ if (id === clientPath) {
867
+ let position = code.indexOf("function updateStyle(id, content) {");
868
+ if (position === -1) throw new Error("[assets] This Vite client does not expose the expected CSS update function. Use a supported Vite version.");
869
+ let transformed = new MagicString(code);
870
+ transformed.prepend(linkedStylesheetLookup(config));
871
+ transformed.appendLeft(position + 35, "\nif (pitlaneHasLinkedStylesheet(id)) return;\n");
872
+ return {
873
+ code: transformed.toString(),
874
+ map: transformed.generateMap({ hires: true })
875
+ };
876
+ }
877
+ if (this.environment.mode !== "dev" || this.environment.name !== "client" || !isCSSRequest(id) || !/[?&]direct\b/.test(id)) return;
878
+ let module = this.environment.moduleGraph.getModuleById(id);
879
+ if (module) module.isSelfAccepting = true;
880
+ }
881
+ }
882
+ };
883
+ }
884
+ //#endregion
885
+ //#region src/vite/transform-start.ts
886
+ let invalidations = 0;
887
+ let requestStart = new AsyncLocalStorage();
888
+ /** Counts a hard invalidation and returns its place on the timeline. */
889
+ function countInvalidation() {
890
+ return ++invalidations;
891
+ }
892
+ /** Runs `request`, which loads and transforms a module, as starting now. */
893
+ function startRequest(request) {
894
+ return requestStart.run(invalidations, request);
895
+ }
896
+ /**
897
+ * The place on the timeline where the calling transform's request started.
898
+ * A transform no request started, such as one a plugin runs through
899
+ * `pluginContainer.transform` itself, started where this is called.
900
+ */
901
+ function requestStarted() {
902
+ return requestStart.getStore() ?? invalidations;
903
+ }
904
+ //#endregion
905
+ //#region src/vite/dev-graph.ts
906
+ /** CSS requests that import a string or URL rather than apply a stylesheet. */
907
+ const NON_STYLESHEET_QUERY = /[?&](?:inline|url|raw)\b/;
908
+ /**
909
+ * The import specifiers of transformed JavaScript. Only literal `import()`
910
+ * arguments count as dynamic edges; a computed argument names no module.
911
+ */
912
+ function importSpecifiers(code) {
913
+ let { module } = parseSync("module.js", code, { sourceType: "module" });
914
+ let staticSpecifiers = new Set(module.staticImports.map((entry) => entry.moduleRequest.value));
915
+ for (let entry of module.staticExports) for (let item of entry.entries) if (item.moduleRequest) staticSpecifiers.add(item.moduleRequest.value);
916
+ let dynamicSpecifiers = /* @__PURE__ */ new Set();
917
+ for (let entry of module.dynamicImports) {
918
+ let literal = /^(["'`])([^"'`$]*)\1$/.exec(code.slice(entry.moduleRequest.start, entry.moduleRequest.end).trim());
919
+ if (literal) dynamicSpecifiers.add(literal[2]);
920
+ }
921
+ return {
922
+ static: [...staticSpecifiers],
923
+ dynamic: [...dynamicSpecifiers]
924
+ };
925
+ }
926
+ /** The path Vite's dev server serves a resolved module id at, before `base`. */
927
+ function servedPath(root, id) {
928
+ if (id.startsWith("\0")) return `/@id/__x00__${id.slice(1)}`;
929
+ let query = id.indexOf("?");
930
+ let file = query === -1 ? id : id.slice(0, query);
931
+ let search = query === -1 ? "" : id.slice(query);
932
+ if (!isAbsolute(file)) return `/@id/${id}`;
933
+ if (file.startsWith(`${root}/`)) return file.slice(root.length) + search;
934
+ return `/@fs/${file.replace(/^\//, "")}${search}`;
935
+ }
936
+ /**
937
+ * Whether discovery transforms a module. A server environment reads a package
938
+ * from `node_modules` only when Vite transforms rather than externalizes it.
939
+ * The client follows packages Vite serves as source, but not a prebundled
940
+ * dependency: the optimizer's cache file is not a source module, and loading
941
+ * it would wait for the optimizer.
942
+ */
943
+ function followed(environment, id) {
944
+ if (id === "\0pitlane:assets-manifest" || environment.depsOptimizer?.isOptimizedDepFile(id)) return false;
945
+ let pkg = /.*\/node_modules\/((?:@[^/]+\/)?[^/?]+)/.exec(id)?.[1];
946
+ if (pkg === void 0 || environment.config.consumer === "client") return true;
947
+ let { noExternal } = environment.config.resolve;
948
+ if (noExternal === true) return true;
949
+ return (noExternal === void 0 ? [] : Array.isArray(noExternal) ? noExternal : [noExternal]).some((pattern) => typeof pattern === "string" ? pattern === pkg : pattern.test(pkg));
950
+ }
951
+ /**
952
+ * A module is analyzed once. Its edge record was written by the transform
953
+ * hook, whether the runner or discovery ran that transform, and stays
954
+ * current until the module is invalidated, which drops the record. Keying
955
+ * this on `transformResult` instead would re-transform every module the
956
+ * runner has not executed on each rebuild.
957
+ *
958
+ * Client discovery goes through `transformRequest`, so it shares Vite's
959
+ * in-flight and cached transforms with the import analysis that warms a
960
+ * browser module's dependencies, and the browser later reads the cache.
961
+ * Server discovery must not populate `transformResult`: the module runner
962
+ * asks whether a module it already evaluated is still current by that
963
+ * field, so a transform cached between a file change and the runner's next
964
+ * import would keep its stale instance alive.
965
+ */
966
+ async function analyze(environment, id, edges) {
967
+ if (edges.has(id)) return;
968
+ let { root } = environment.config;
969
+ try {
970
+ let url = servedPath(root, id);
971
+ if (environment.config.consumer === "client") {
972
+ await environment.transformRequest(url.startsWith("/@id/") ? id : url);
973
+ if (edges.has(id)) return;
974
+ }
975
+ await startRequest(() => transformOutsideCache(environment, id, url));
976
+ } catch (error) {
977
+ let name = fileModule(id) ? sourceKey(root, id) : id;
978
+ throw new Error(`[assets] Could not analyze "${name}" for the "${environment.name}" development asset manifest: ${error.message}`, { cause: error });
979
+ }
980
+ }
981
+ /**
982
+ * Loads and transforms a module as `transformRequest` does, handing the load
983
+ * result's source map and module type to the transform, but without reading
984
+ * or populating Vite's transform cache.
985
+ */
986
+ async function transformOutsideCache(environment, id, url) {
987
+ await environment.moduleGraph.ensureEntryFromUrl(url);
988
+ let loaded = await environment.pluginContainer.load(id);
989
+ let source = typeof loaded === "string" ? { code: loaded } : loaded;
990
+ let code = source?.code;
991
+ if (code == null) {
992
+ if (!fileModule(id)) throw new Error("No plugin loaded the module.");
993
+ code = await readFile(id.split("?")[0], "utf8");
994
+ }
995
+ await environment.pluginContainer.transform(code, id, {
996
+ inMap: source?.map,
997
+ moduleType: source?.moduleType
998
+ });
999
+ }
1000
+ /**
1001
+ * Every module reachable from `roots` through static and dynamic edges,
1002
+ * transforming each one the graph has not analyzed yet. Stylesheets are
1003
+ * leaves: their own `@import`s are inlined by Vite's CSS pipeline. Passing
1004
+ * the result of an earlier walk extends it without revisiting its modules.
1005
+ */
1006
+ async function walkGraph(environment, roots, edges, visited = /* @__PURE__ */ new Set()) {
1007
+ let frontier = [...roots];
1008
+ while (frontier.length > 0) {
1009
+ let next = [];
1010
+ await Promise.all(frontier.map(async (id) => {
1011
+ if (visited.has(id) || !(isCSSRequest(id) || followed(environment, id))) return;
1012
+ visited.add(id);
1013
+ if (isCSSRequest(id)) return;
1014
+ await analyze(environment, id, edges);
1015
+ let record = edges.get(id);
1016
+ if (record) next.push(...record.static, ...record.dynamic);
1017
+ }));
1018
+ frontier = next;
1019
+ }
1020
+ return visited;
1021
+ }
1022
+ /**
1023
+ * The stylesheet ids a module requires through static imports, in execution
1024
+ * order: each dependency's stylesheets before its importer's later imports.
1025
+ */
1026
+ function staticStylesheets(edges, start) {
1027
+ let seen = /* @__PURE__ */ new Set();
1028
+ let stylesheets = [];
1029
+ let visit = (id) => {
1030
+ if (seen.has(id)) return;
1031
+ seen.add(id);
1032
+ if (isCSSRequest(id)) {
1033
+ if (!NON_STYLESHEET_QUERY.test(id)) stylesheets.push(id);
1034
+ return;
1035
+ }
1036
+ for (let dependency of edges.get(id)?.static ?? []) visit(dependency);
1037
+ };
1038
+ visit(start);
1039
+ return stylesheets;
1040
+ }
1041
+ //#endregion
1042
+ //#region src/vite/dev-snapshot.ts
1043
+ function environmentEdges(graph, name) {
1044
+ let edges = graph.edges.get(name);
1045
+ if (!edges) graph.edges.set(name, edges = /* @__PURE__ */ new Map());
1046
+ return edges;
1047
+ }
1048
+ /**
1049
+ * Whether a module id is a source file with a key of its own. Vite's `?v=`
1050
+ * version hash on a package module does not change which file it is; any
1051
+ * other query, such as a single-file component's style block, names a part
1052
+ * of its file that the file's own key already covers.
1053
+ */
1054
+ function sourceModule(id) {
1055
+ return fileModule(id) && !id.replace(/\?v=[\w-]+$/, "").includes("?");
1056
+ }
1057
+ /** The resolved, non-HTML build inputs an environment declares. */
1058
+ async function configuredInputs(environment) {
1059
+ let { build, root } = environment.config;
1060
+ let input = build.rolldownOptions.input;
1061
+ let inputs = typeof input === "string" ? [input] : Array.isArray(input) ? input : Object.values(input ?? {});
1062
+ let ids = [];
1063
+ for (let entry of inputs) {
1064
+ if (entry.endsWith(".html")) continue;
1065
+ let file = resolve(root, entry);
1066
+ let resolved = await environment.pluginContainer.resolveId(existsSync(file) ? file : entry);
1067
+ if (!resolved) throw new Error(`[assets] Could not resolve the "${environment.name}" environment's input "${entry}".`);
1068
+ ids.push(resolved.id);
1069
+ }
1070
+ return ids;
1071
+ }
1072
+ async function createDevSnapshot(server, state, graph, name) {
1073
+ let config = state.config;
1074
+ let { root } = config;
1075
+ let base = config.base.replace(/\/$/, "");
1076
+ let serverEnvironment = server.environments[name];
1077
+ let clientEnvironment = server.environments.client;
1078
+ let serverEdges = environmentEdges(graph, name);
1079
+ let serverIds = await walkGraph(serverEnvironment, [...await configuredInputs(serverEnvironment), ...graph.roots.get(name) ?? []], serverEdges);
1080
+ await clientEnvironment.depsOptimizer?.init();
1081
+ let entryIds = /* @__PURE__ */ new Set([...state.inputs.keys(), ...await configuredInputs(clientEnvironment)]);
1082
+ let usage = graph.resolverUsage.get(name) ?? /* @__PURE__ */ new Map();
1083
+ let registrations = state.registrations.get(name);
1084
+ for (let id of serverIds) {
1085
+ for (let input of literalInputs(usage, id)) entryIds.add(inputPath(root, input.key));
1086
+ for (let entry of registrations?.get(id) ?? []) entryIds.add(entry);
1087
+ }
1088
+ let entries = Object.create(null);
1089
+ let assets = Object.create(null);
1090
+ let scripts = [];
1091
+ for (let id of entryIds) {
1092
+ let url = base + servedPath(root, id);
1093
+ if (isCSSRequest(id) || config.assetsInclude(id.split("?")[0])) assets[sourceKey(root, id)] = url;
1094
+ else {
1095
+ entries[sourceKey(root, id)] = url;
1096
+ scripts.push(id);
1097
+ }
1098
+ }
1099
+ let clientIds = await walkGraph(clientEnvironment, scripts, environmentEdges(graph, "client"));
1100
+ let serverModules = Object.create(null);
1101
+ for (let id of serverIds) {
1102
+ if (!sourceModule(id)) continue;
1103
+ serverModules[sourceKey(root, id)] = {
1104
+ preloads: [],
1105
+ stylesheets: staticStylesheets(serverEdges, id).map((stylesheet) => base + servedPath(root, stylesheet))
1106
+ };
1107
+ }
1108
+ let clientModules = Object.create(null);
1109
+ for (let id of clientIds) if (sourceModule(id)) clientModules[sourceKey(root, id)] = {
1110
+ preloads: [],
1111
+ stylesheets: []
1112
+ };
1113
+ return {
1114
+ manifest: {
1115
+ mode: "dev",
1116
+ environments: {
1117
+ client: {
1118
+ role: "client",
1119
+ modules: clientModules
1120
+ },
1121
+ [name]: {
1122
+ role: "server",
1123
+ modules: serverModules
1124
+ }
1125
+ },
1126
+ entries,
1127
+ assets,
1128
+ serverEnvironment: name
1129
+ },
1130
+ server: serverIds,
1131
+ client: clientIds
1132
+ };
1133
+ }
1134
+ //#endregion
1135
+ //#region src/vite/dev.ts
1136
+ /**
1137
+ * Serves `@pitlane/assets/manifest` during `vite dev` as a data snapshot of
1138
+ * each server environment's module graph, and invalidates it when a change
1139
+ * could alter the stylesheets, routes, or browser entries it records.
1140
+ */
1141
+ function assetDevelopment(state) {
1142
+ let server;
1143
+ let graph = {
1144
+ roots: /* @__PURE__ */ new Map(),
1145
+ edges: /* @__PURE__ */ new Map(),
1146
+ resolverUsage: /* @__PURE__ */ new Map()
1147
+ };
1148
+ let snapshots = /* @__PURE__ */ new Map();
1149
+ let building = /* @__PURE__ */ new Set();
1150
+ /** Server environments invalidated while their snapshot was being built. */
1151
+ let stale = /* @__PURE__ */ new Set();
1152
+ /** Environment name → module id → where its last hard invalidation is on the transform timeline. */
1153
+ let invalidations = /* @__PURE__ */ new Map();
1154
+ /**
1155
+ * Invalidates a server environment's manifest and, through Vite, every
1156
+ * module importing it. A manifest being built is marked instead, and
1157
+ * `load` builds it again, since Vite would cache the code `load` returns.
1158
+ */
1159
+ function invalidate(name, timestamp) {
1160
+ if (building.has(name)) return void stale.add(name);
1161
+ let environment = server?.environments[name];
1162
+ let manifest = environment?.moduleGraph.getModuleById(MANIFEST_ID);
1163
+ if (manifest) environment.moduleGraph.invalidateModule(manifest, /* @__PURE__ */ new Set(), timestamp, true);
1164
+ }
1165
+ /** The modules of `environment` a server environment's snapshot was computed from. */
1166
+ function tracked(environment, serverName) {
1167
+ let snapshot = snapshots.get(serverName);
1168
+ if (environment === serverName) return snapshot?.server;
1169
+ if (environment === "client") return snapshot?.client;
1170
+ }
1171
+ /** Invalidates every snapshot computed, or being computed, from module `id` of `environment`. */
1172
+ function invalidateSnapshotsWith(environment, id, timestamp) {
1173
+ for (let serverName of snapshots.keys()) if (tracked(environment, serverName)?.has(id)) invalidate(serverName, timestamp);
1174
+ for (let serverName of building) if (environment === serverName || environment === "client") stale.add(serverName);
1175
+ }
1176
+ function sameEdges(previous, next) {
1177
+ return previous.static.length === next.static.length && previous.dynamic.length === next.dynamic.length && previous.static.every((edge, index) => edge === next.static[index]) && previous.dynamic.every((edge, index) => edge === next.dynamic[index]);
1178
+ }
1179
+ /**
1180
+ * Records what a transform of module `id` found: its edges and, in a
1181
+ * server environment, its resolver usage.
1182
+ */
1183
+ function recordTransform(environment, id, edges, usage) {
1184
+ let edgeRecords = environmentEdges(graph, environment);
1185
+ let previous = edgeRecords.get(id);
1186
+ edgeRecords.set(id, edges);
1187
+ if (previous && !sameEdges(previous, edges)) invalidateSnapshotsWith(environment, id);
1188
+ let usageRecords = graph.resolverUsage.get(environment);
1189
+ if (usage) usageRecords?.set(id, usage);
1190
+ else usageRecords?.delete(id);
1191
+ }
1192
+ return {
1193
+ name: "pitlane-assets-dev",
1194
+ apply: "serve",
1195
+ enforce: "post",
1196
+ configureServer: {
1197
+ order: "pre",
1198
+ handler(devServer) {
1199
+ server = devServer;
1200
+ for (let environment of Object.values(devServer.environments)) {
1201
+ let { name, moduleGraph } = environment;
1202
+ let edges = environmentEdges(graph, name);
1203
+ let invalidated = /* @__PURE__ */ new Map();
1204
+ invalidations.set(name, invalidated);
1205
+ let invalidateModule = moduleGraph.invalidateModule;
1206
+ moduleGraph.invalidateModule = (mod, seen, timestamp, isHmr, softInvalidate) => {
1207
+ if (!softInvalidate && mod.id && mod.id !== "\0pitlane:assets-manifest" && !isCSSRequest(mod.id)) {
1208
+ edges.delete(mod.id);
1209
+ if (!isHmr) invalidated.set(mod.id, countInvalidation());
1210
+ invalidateSnapshotsWith(name, mod.id, timestamp);
1211
+ }
1212
+ invalidateModule.call(moduleGraph, mod, seen, timestamp, isHmr, softInvalidate);
1213
+ };
1214
+ let { transformRequest, warmupRequest } = environment;
1215
+ environment.transformRequest = (url) => startRequest(() => transformRequest.call(environment, url));
1216
+ environment.warmupRequest = (url) => startRequest(() => warmupRequest.call(environment, url));
1217
+ if (!state.serverEnvironments.includes(name)) continue;
1218
+ let roots = /* @__PURE__ */ new Set();
1219
+ graph.roots.set(name, roots);
1220
+ graph.resolverUsage.set(name, /* @__PURE__ */ new Map());
1221
+ let fetchModule = environment.fetchModule;
1222
+ environment.fetchModule = async (id, importer, options) => {
1223
+ let fetched = await fetchModule.call(environment, id, importer, options);
1224
+ if (!importer && "id" in fetched && fetched.id !== "\0pitlane:assets-manifest") {
1225
+ roots.add(fetched.id);
1226
+ let snapshot = snapshots.get(name);
1227
+ if (snapshot && !snapshot.server.has(fetched.id)) invalidate(name);
1228
+ }
1229
+ return fetched;
1230
+ };
1231
+ }
1232
+ state.onRegistrationChange = (environment, owner) => {
1233
+ if (building.has(environment)) return;
1234
+ if (snapshots.get(environment)?.server.has(owner)) invalidate(environment);
1235
+ };
1236
+ }
1237
+ },
1238
+ async load(id) {
1239
+ if (id !== "\0pitlane:assets-manifest") return;
1240
+ let { name, config } = this.environment;
1241
+ if (config.consumer === "client") {
1242
+ let message = `[assets] ${assetsSpecifier("manifest")} describes the server's module graph and cannot run in the browser. Resolve asset URLs in server code and pass them to the page.`;
1243
+ return `throw new Error(${JSON.stringify(message)});\nexport default undefined;\n`;
1244
+ }
1245
+ if (!state.serverEnvironments.includes(name)) throw unservedEnvironmentError(name, state.serverEnvironments);
1246
+ building.add(name);
1247
+ try {
1248
+ let snapshot;
1249
+ let passes = 0;
1250
+ do {
1251
+ stale.delete(name);
1252
+ snapshot = await createDevSnapshot(server, state, graph, name);
1253
+ } while (stale.has(name) && ++passes < 3);
1254
+ snapshots.set(name, snapshot);
1255
+ if (stale.has(name)) {
1256
+ let { moduleGraph } = server.environments[name];
1257
+ moduleGraph.invalidateModule(await moduleGraph.ensureEntryFromUrl(MANIFEST_ID));
1258
+ }
1259
+ return `export default JSON.parse(${JSON.stringify(JSON.stringify(snapshot.manifest))});\n`;
1260
+ } finally {
1261
+ building.delete(name);
1262
+ stale.delete(name);
1263
+ }
1264
+ },
1265
+ transform: { async handler(code, id) {
1266
+ if (id === "\0pitlane:assets-manifest" || isCSSRequest(id)) return;
1267
+ let name = this.environment.name;
1268
+ let invalidated = invalidations.get(name);
1269
+ let started = requestStarted();
1270
+ let resolve = (specifier) => this.resolve(specifier, id);
1271
+ let resolveAll = async (list) => {
1272
+ let resolved = await Promise.all(list.map(resolve));
1273
+ return [...new Set(resolved.flatMap((result) => result && !result.external ? [result.id] : []))];
1274
+ };
1275
+ let specifiers = importSpecifiers(code);
1276
+ let usage = state.serverEnvironments.includes(name) ? scanResolverUsage(code, id) : void 0;
1277
+ let [staticEdges, dynamicEdges, linkedUsage] = await Promise.all([
1278
+ resolveAll(specifiers.static),
1279
+ resolveAll(specifiers.dynamic),
1280
+ usage && linkResolverUsage(usage, resolve)
1281
+ ]);
1282
+ if ((invalidated?.get(id) ?? 0) > started) return;
1283
+ recordTransform(name, id, {
1284
+ static: staticEdges,
1285
+ dynamic: dynamicEdges
1286
+ }, linkedUsage);
1287
+ } },
1288
+ hotUpdate: {
1289
+ order: "post",
1290
+ handler({ type, file, modules, timestamp }) {
1291
+ if (type === "update" && isCSSRequest(file)) return;
1292
+ let path = normalizePath(file);
1293
+ if (type === "delete") {
1294
+ for (let roots of graph.roots.values()) for (let root of roots) if (root.split("?")[0] === path) roots.delete(root);
1295
+ for (let owners of [...state.registrations.values(), ...graph.resolverUsage.values()]) for (let owner of owners.keys()) if (owner.split("?")[0] === path) owners.delete(owner);
1296
+ }
1297
+ let name = this.environment.name;
1298
+ let ids = modules.flatMap((module) => module.id ? [module.id] : []);
1299
+ for (let serverName of snapshots.keys()) {
1300
+ let set = tracked(name, serverName);
1301
+ if (set && (set.has(path) || ids.some((id) => set.has(id)))) invalidate(serverName, timestamp);
1302
+ }
1303
+ }
1304
+ }
1305
+ };
1306
+ }
1307
+ //#endregion
1308
+ //#region src/vite-plugin.ts
1309
+ /**
1310
+ * The Vite plugins that give `@pitlane/assets` its manifest: they serve
1311
+ * browser entries during `vite dev`, emit them in a build, and replace
1312
+ * `@pitlane/assets/manifest` with the manifest of the current dev server or
1313
+ * build. Setting `allowFiles` also enforces the browser boundary.
1314
+ *
1315
+ * @throws When `allowPackages` or `denyFiles` is set without `allowFiles`.
1316
+ */
1317
+ function assets(options = {}) {
1318
+ let { allowFiles, allowPackages, denyFiles } = options;
1319
+ if (!allowFiles && (allowPackages || denyFiles)) throw new Error("[assets] allowPackages and denyFiles refine allowFiles. Set allowFiles to turn on the browser boundary.");
1320
+ let state = {
1321
+ serverEnvironments: options.serverEnvironments ?? ["ssr"],
1322
+ inputs: /* @__PURE__ */ new Map(),
1323
+ resolverUsage: /* @__PURE__ */ new Map(),
1324
+ registrations: /* @__PURE__ */ new Map(),
1325
+ assetReferences: /* @__PURE__ */ new Map(),
1326
+ scriptReferences: /* @__PURE__ */ new Map(),
1327
+ outputs: /* @__PURE__ */ new Map(),
1328
+ mapsEnabled: false
1329
+ };
1330
+ return [
1331
+ {
1332
+ name: "pitlane-assets",
1333
+ api: { setBrowserEntries({ environment, owner, entries }) {
1334
+ if (!state.serverEnvironments.includes(environment)) throw new Error(`[assets] Cannot register browser entries from "${environment}": it is not in serverEnvironments.`);
1335
+ let owners = state.registrations.get(environment);
1336
+ let previous = owners?.get(owner);
1337
+ if (!previous && entries.length === 0 || previous?.length === entries.length && entries.every((id, index) => id === previous[index])) return;
1338
+ if (entries.length === 0) {
1339
+ owners.delete(owner);
1340
+ if (owners.size === 0) state.registrations.delete(environment);
1341
+ } else {
1342
+ if (!owners) state.registrations.set(environment, owners = /* @__PURE__ */ new Map());
1343
+ owners.set(owner, entries.slice());
1344
+ }
1345
+ state.onRegistrationChange?.(environment, owner);
1346
+ } },
1347
+ enforce: "post",
1348
+ sharedDuringBuild: true,
1349
+ config(config) {
1350
+ state.mapsEnabled = options.chunkImportMap ?? config.environments?.client?.build?.chunkImportMap ?? config.build?.chunkImportMap ?? false;
1351
+ if (state.mapsEnabled && config.experimental?.renderBuiltUrl) throw new Error("[assets] chunkImportMap: true cannot be combined with experimental.renderBuiltUrl.");
1352
+ return {
1353
+ builder: {},
1354
+ build: { assetsInlineLimit: 0 },
1355
+ optimizeDeps: { exclude: ["@pitlane/assets/manifest", "pitlane/assets/manifest"] },
1356
+ environments: { client: { build: { chunkImportMap: state.mapsEnabled } } }
1357
+ };
1358
+ },
1359
+ configResolved(config) {
1360
+ state.config = config;
1361
+ for (let key of options.include ?? []) {
1362
+ let id = inputPath(config.root, key);
1363
+ state.inputs.set(id, {
1364
+ key: sourceKey(config.root, id),
1365
+ kind: "asset"
1366
+ });
1367
+ }
1368
+ },
1369
+ configEnvironment(name) {
1370
+ if (!state.serverEnvironments.includes(name)) return;
1371
+ return {
1372
+ resolve: { noExternal: [/^@pitlane\/assets(?:\/|$)/, /^pitlane(?:\/|$)/] },
1373
+ optimizeDeps: { exclude: ["@pitlane/assets/manifest", "pitlane/assets/manifest"] },
1374
+ build: { chunkImportMap: false }
1375
+ };
1376
+ },
1377
+ resolveId: {
1378
+ order: "pre",
1379
+ handler(id) {
1380
+ if (id !== "@pitlane/assets/manifest" && id !== "pitlane/assets/manifest") return;
1381
+ if (this.environment.mode === "build" && this.environment.name === "client") throw new Error("[assets] The asset manifest is server-only; keep resolver lookups out of browser modules.");
1382
+ if (this.environment.mode !== "build") return MANIFEST_ID;
1383
+ if (!state.serverEnvironments.includes(this.environment.name)) throw unservedEnvironmentError(this.environment.name, state.serverEnvironments);
1384
+ return {
1385
+ id: MANIFEST_EXTERNAL,
1386
+ external: true
1387
+ };
1388
+ }
1389
+ },
1390
+ async transform(code, id) {
1391
+ let { mode, name } = this.environment;
1392
+ if (mode !== "build" || !state.serverEnvironments.includes(name)) return;
1393
+ let modules = state.resolverUsage.get(name);
1394
+ if (!modules) state.resolverUsage.set(name, modules = /* @__PURE__ */ new Map());
1395
+ let usage = scanResolverUsage(code, id);
1396
+ if (!usage) return void modules.delete(id);
1397
+ modules.set(id, await linkResolverUsage(usage, (specifier) => this.resolve(specifier, id)));
1398
+ }
1399
+ },
1400
+ assetBuild(state),
1401
+ assetDevelopment(state),
1402
+ assetStyles(),
1403
+ allowFiles && browserBoundary({
1404
+ allowFiles,
1405
+ allowPackages,
1406
+ denyFiles
1407
+ }, state.serverEnvironments)
1408
+ ];
1409
+ }
1410
+ //#endregion
1411
+ export { ASSETS_MANIFEST_FILE, assets };