@rohal12/twee-ts 1.18.1 → 2.0.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.
Files changed (42) hide show
  1. package/README.md +23 -3
  2. package/THIRD_PARTY_NOTICES +111 -0
  3. package/UNLICENSE +24 -0
  4. package/dist/bin/twee-ts.js +689 -9335
  5. package/dist/bin/twee-ts.js.map +1 -1
  6. package/dist/diagnostic-text-BRddVulf.cjs +26463 -0
  7. package/dist/diagnostic-text-BRddVulf.cjs.map +1 -0
  8. package/dist/diagnostic-text-CebYCTPQ.js +26040 -0
  9. package/dist/diagnostic-text-CebYCTPQ.js.map +1 -0
  10. package/dist/index.cjs +86 -9026
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.cts +192 -415
  13. package/dist/index.d.ts +192 -415
  14. package/dist/index.js +4 -9206
  15. package/dist/lint-VayZI_jp.js +288 -0
  16. package/dist/lint-VayZI_jp.js.map +1 -0
  17. package/dist/options-DuBkFyqi.d.cts +28 -0
  18. package/dist/options-nc5NpnSc.d.ts +28 -0
  19. package/dist/plugins/rollup.cjs +172 -0
  20. package/dist/plugins/rollup.cjs.map +1 -0
  21. package/dist/plugins/rollup.d.cts +46 -0
  22. package/dist/plugins/rollup.d.ts +7 -12
  23. package/dist/plugins/rollup.js +130 -65
  24. package/dist/plugins/rollup.js.map +1 -1
  25. package/dist/plugins/vite.cjs +1926 -0
  26. package/dist/plugins/vite.cjs.map +1 -0
  27. package/dist/plugins/vite.d.cts +15 -0
  28. package/dist/plugins/vite.d.ts +5 -12
  29. package/dist/plugins/vite.js +1771 -448
  30. package/dist/plugins/vite.js.map +1 -1
  31. package/dist/types-BvI6pg2y.d.cts +389 -0
  32. package/dist/types-BvI6pg2y.d.ts +389 -0
  33. package/dist/watch-targets-BI7jYrwa.js +486 -0
  34. package/dist/watch-targets-BI7jYrwa.js.map +1 -0
  35. package/dist/watch-targets-C0Anuw17.cjs +575 -0
  36. package/dist/watch-targets-C0Anuw17.cjs.map +1 -0
  37. package/package.json +80 -27
  38. package/schemas/twee-ts.config.schema.json +55 -17
  39. package/dist/index.js.map +0 -1
  40. package/dist/paths-CUv1qfz-.js +0 -6926
  41. package/dist/paths-CUv1qfz-.js.map +0 -1
  42. package/dist/types-D8Xfe0l8.d.ts +0 -71
@@ -0,0 +1,1926 @@
1
+ /*! @rohal12/twee-ts (Unlicense). Bundles third-party code under its own licence: see THIRD_PARTY_NOTICES. */
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const require_diagnostic_text = require("../diagnostic-text-BRddVulf.cjs");
4
+ const require_watch_targets = require("../watch-targets-C0Anuw17.cjs");
5
+ let node_path = require("node:path");
6
+ let node_fs = require("node:fs");
7
+ let node_util = require("node:util");
8
+ let vite = require("vite");
9
+ //#region src/plugins/vite-glob.ts
10
+ /**
11
+ * The folders an entry's `import.meta.glob()` calls read. Vite replaces each call with the files that match it
12
+ * when it bundles, so the bundle names those files and nothing else: a file added later that matches is in no
13
+ * bundle's module list. To bundle again when the files a glob selects change, the plugin reads each call itself,
14
+ * just before Vite's own import-glob transform does, and notes the folder below which it can match (its scope).
15
+ *
16
+ * A scope is a superset of what the glob matches: a file added to it or removed from it may not match (a negated
17
+ * pattern, a name the wildcards leave out), which only bundles again for nothing. The calls are read with acorn as Vite reads them: patterns are
18
+ * string literals, template literals without expressions, or arrays of those; `base` and `exhaustive` are literal
19
+ * options. A call written otherwise is one Vite rejects, and the bundle fails before its scope matters.
20
+ *
21
+ * Code that is a module is read whole, so only real calls count. Code that is not (yet) one, as a plugin may hand
22
+ * on a component file or TypeScript it transforms later, is read the way Vite's import-glob reads every module:
23
+ * each `import.meta.glob(` in the text, with TypeScript type arguments allowed, is read on its own as a call.
24
+ * Text in a comment or a string that reads as a call then adds a scope too, which only costs a bundle for nothing.
25
+ */
26
+ /** A module, as the bundler hands it to a plugin's transform hook (the entry build's code is a module). */
27
+ const MODULE_OPTIONS = {
28
+ ecmaVersion: "latest",
29
+ sourceType: "module",
30
+ allowHashBang: true
31
+ };
32
+ /** Whether `code` may hold a glob call, as Vite's import-glob filters the modules it reads. */
33
+ function mayHoldGlob(code) {
34
+ return code.includes("import.meta.glob");
35
+ }
36
+ function isNode(value) {
37
+ return require_diagnostic_text.isRecord(value) && typeof value["type"] === "string";
38
+ }
39
+ /** Every node below `root`, `root` first, in source order. */
40
+ function* nodesOf(root) {
41
+ const stack = [root];
42
+ for (let value = stack.pop(); value !== void 0; value = stack.pop()) {
43
+ if (isNode(value)) yield value;
44
+ const children = Array.isArray(value) ? value : isNode(value) ? Object.values(value) : [];
45
+ require_diagnostic_text.pushAll(stack, children.toReversed());
46
+ }
47
+ }
48
+ /** Whether `node` is the callee `import.meta.glob`. */
49
+ function isGlobCallee(node) {
50
+ return node.type === "MemberExpression" && !node.computed && node.property.type === "Identifier" && node.property.name === "glob" && node.object.type === "MetaProperty" && node.object.meta.name === "import" && node.object.property.name === "meta";
51
+ }
52
+ /** The string a pattern argument spells: a string literal, or a template literal without expressions. */
53
+ function stringOf(node) {
54
+ if (node === null) return void 0;
55
+ if (node.type === "Literal") return typeof node.value === "string" ? node.value : void 0;
56
+ if (node.type === "TemplateLiteral" && node.expressions.length === 0) return node.quasis[0]?.value.raw;
57
+ }
58
+ /** The literal value of the option `name` of a call's options object. */
59
+ function optionOf(options, name) {
60
+ for (const property of options?.properties ?? []) {
61
+ if (property.type !== "Property" || property.computed || property.value.type !== "Literal") continue;
62
+ const { key } = property;
63
+ if ((key.type === "Identifier" ? key.name : key.type === "Literal" ? key.value : void 0) === name) return property.value.value;
64
+ }
65
+ }
66
+ /** The call a glob call's arguments make. */
67
+ function callOf(args) {
68
+ const [first, second] = args;
69
+ const patterns = (first === void 0 ? [] : first.type === "ArrayExpression" ? first.elements : [first]).map(stringOf).filter((pattern) => pattern !== void 0);
70
+ const options = second?.type === "ObjectExpression" ? second : void 0;
71
+ const base = optionOf(options, "base");
72
+ return {
73
+ patterns,
74
+ base: typeof base === "string" ? base : void 0,
75
+ exhaustive: optionOf(options, "exhaustive") === true
76
+ };
77
+ }
78
+ /** Where Vite's import-glob finds a call in a module's text: the callee, any TypeScript type arguments, `(`. */
79
+ const GLOB_CALL_START = /\bimport\.meta\.glob(?:<\w+>)?\s*\(/g;
80
+ /**
81
+ * The end (offset just past it) of the argument list that opens at `paren`, as acorn's tokenizer reads the text from
82
+ * there: past strings, template literals, regular expressions and comments that hold parentheses. Undefined when the
83
+ * list does not close, or the text cannot be tokenized.
84
+ */
85
+ function argumentsEnd(code, paren) {
86
+ const read = require_diagnostic_text.trySyntax(() => {
87
+ let depth = 0;
88
+ for (const token of require_diagnostic_text.AcornParser.tokenizer(code.slice(paren), MODULE_OPTIONS)) {
89
+ if (token.type === require_diagnostic_text.types$1.parenL) depth += 1;
90
+ else if (token.type === require_diagnostic_text.types$1.parenR) depth -= 1;
91
+ if (depth === 0) return paren + token.end;
92
+ }
93
+ });
94
+ return read.ok ? read.value : void 0;
95
+ }
96
+ /** `text` read as one expression, which must take all of it; undefined when acorn reads it otherwise. */
97
+ function wholeExpression(text, options) {
98
+ const read = require_diagnostic_text.trySyntax(() => require_diagnostic_text.AcornParser.parseExpressionAt(text, 0, options));
99
+ return read.ok && read.value.end === text.length ? read.value : void 0;
100
+ }
101
+ /**
102
+ * The call whose callee starts at `start` and whose argument list opens at `paren`, read on its own, up to the end
103
+ * of its argument list: as a call expression, or, with type arguments between the two (which JavaScript reads as
104
+ * comparisons), as its argument list in parentheses. Undefined when acorn reads neither.
105
+ */
106
+ function callAt(code, start, paren) {
107
+ const end = argumentsEnd(code, paren);
108
+ if (end === void 0) return void 0;
109
+ const call = wholeExpression(code.slice(start, end), MODULE_OPTIONS);
110
+ if (call?.type === "CallExpression" && isGlobCallee(call.callee)) return callOf(call.arguments);
111
+ const list = wholeExpression(code.slice(paren, end), {
112
+ ...MODULE_OPTIONS,
113
+ preserveParens: true
114
+ });
115
+ if (list?.type !== "ParenthesizedExpression") return void 0;
116
+ const { expression } = list;
117
+ return callOf(expression.type === "SequenceExpression" ? expression.expressions : [expression]);
118
+ }
119
+ /** The glob calls of a module's code: read whole when it is a module, else each call on its own. */
120
+ function globCalls(code) {
121
+ const parsed = require_diagnostic_text.trySyntax(() => require_diagnostic_text.AcornParser.parse(code, MODULE_OPTIONS));
122
+ if (parsed.ok) return {
123
+ calls: [...nodesOf(parsed.value)].flatMap((node) => node.type === "CallExpression" && isGlobCallee(node.callee) ? [callOf(node.arguments)] : []),
124
+ unreadable: []
125
+ };
126
+ const calls = [];
127
+ const unreadable = [];
128
+ for (const match of code.matchAll(GLOB_CALL_START)) {
129
+ const call = callAt(code, match.index, match.index + match[0].length - 1);
130
+ if (call === void 0) unreadable.push(match.index);
131
+ else calls.push(call);
132
+ }
133
+ return {
134
+ calls,
135
+ unreadable
136
+ };
137
+ }
138
+ /** Where `offset` is in `code`, as `line:column`, both from 1. */
139
+ function lineColumn(code, offset) {
140
+ const lines = code.slice(0, offset).split("\n");
141
+ return `${String(lines.length)}:${String((lines.at(-1)?.length ?? 0) + 1)}`;
142
+ }
143
+ /** Characters that make a segment of a pattern match more than its own name (picomatch's, and its escape). */
144
+ const MAGIC = /[*?[\]{}()!+@\\]/;
145
+ /** The characters at the end of a segment after its last MAGIC one. */
146
+ const LITERAL_END = /[^*?[\]{}()!+@\\]*$/;
147
+ /** Whether a pattern's segments may match a name starting with a dot, which a wildcard alone never does. */
148
+ function namesDot(segments) {
149
+ return segments.some((segment) => segment !== ".." && /(?:^|[{,])\./.test(segment));
150
+ }
151
+ /**
152
+ * The scope of the pattern `rest` below an absolute folder `dir` (as the file system spells it, so nothing in it
153
+ * is taken for a wildcard): the folder its leading plain segments name (`..` included), whether the segments after
154
+ * those reach below it, and the literal end of its last segment.
155
+ */
156
+ function scopeOf(dir, rest, exhaustive) {
157
+ const segments = node_path.posix.normalize(rest).split("/");
158
+ const magic = segments.findIndex((segment) => MAGIC.test(segment));
159
+ const plain = magic === -1 ? segments.slice(0, -1) : segments.slice(0, magic);
160
+ const below = segments.slice(plain.length);
161
+ return {
162
+ dir: require_watch_targets.toPosix(plain.reduce((folder, segment) => (0, node_path.resolve)(folder, segment), dir)),
163
+ deep: below.length > 1 || below.some((segment) => segment.includes("**")),
164
+ dot: exhaustive || namesDot(below),
165
+ exhaustive,
166
+ suffix: (LITERAL_END.exec(below.at(-1) ?? "")?.[0] ?? "").toLowerCase()
167
+ };
168
+ }
169
+ /**
170
+ * The scopes of the patterns of `call`, made in the module `importer` (a file path, or undefined for a virtual
171
+ * module) under `root`, as Vite's import-glob places them: `/` from the root, `./` and `../` from the importer's
172
+ * folder or the `base` option, `**` from the root, and anything else (an alias, a `#` import) as the resolver
173
+ * resolves it. A negated pattern only takes files away, and has none.
174
+ */
175
+ async function scopesOf(call, importer, root, resolver) {
176
+ const folder = importer === void 0 ? root : (0, node_path.dirname)(importer);
177
+ const from = call.base === void 0 ? folder : call.base.startsWith("/") ? (0, node_path.join)(root, call.base) : (0, node_path.resolve)(folder, call.base);
178
+ const scopes = [];
179
+ for (const pattern of call.patterns) {
180
+ if (pattern.startsWith("!")) continue;
181
+ if (pattern.startsWith("/")) scopes.push(scopeOf(root, pattern.slice(1), call.exhaustive));
182
+ else if (pattern.startsWith("./") || pattern.startsWith("../")) scopes.push(scopeOf(from, pattern, call.exhaustive));
183
+ else if (pattern.startsWith("**")) scopes.push(scopeOf(root, pattern, call.exhaustive));
184
+ else {
185
+ const resolved = await resolver(pattern);
186
+ if (resolved === void 0 || !(0, node_path.isAbsolute)(resolved)) continue;
187
+ const spelled = require_watch_targets.toPosix(resolved);
188
+ let shared = 0;
189
+ while (shared < Math.min(spelled.length, pattern.length) && spelled.at(-1 - shared) === pattern.at(-1 - shared)) shared += 1;
190
+ const cut = spelled.lastIndexOf("/", spelled.length - shared);
191
+ scopes.push(scopeOf(spelled.slice(0, Math.max(cut, 0)) || "/", spelled.slice(cut + 1), call.exhaustive));
192
+ }
193
+ }
194
+ return scopes;
195
+ }
196
+ /** A key that is the same for two equal scopes. */
197
+ function scopeId(scope) {
198
+ return JSON.stringify([
199
+ scope.dir,
200
+ scope.deep,
201
+ scope.dot,
202
+ scope.exhaustive,
203
+ scope.suffix
204
+ ]);
205
+ }
206
+ /**
207
+ * Whether an entry named `name` of a folder in the scope may be matched, or, for a folder, lead to a match. The
208
+ * temporary copies Vite makes of a config file never are: they come and go with every entry build.
209
+ */
210
+ function admits(scope, name, isDir) {
211
+ if (name.startsWith(".") && !scope.dot || require_watch_targets.isViteConfigTemp(name)) return false;
212
+ if (isDir) return scope.deep && (scope.exhaustive || name !== "node_modules");
213
+ return name.toLowerCase().endsWith(scope.suffix);
214
+ }
215
+ /** The real path (forward slashes) of the folder the link at `path` leads to; undefined for a link to anything else. */
216
+ function linkedFolder(path) {
217
+ try {
218
+ return (0, node_fs.statSync)(path).isDirectory() ? require_watch_targets.canonicalPath(path) : void 0;
219
+ } catch {
220
+ return;
221
+ }
222
+ }
223
+ /**
224
+ * What is in a scope: the files of its folder that `admits` lets through, and for a deep scope the folders below
225
+ * it and theirs. A link to a folder is followed, as Vite's import-glob follows it, and listed with its target, so
226
+ * retargeting it shows; a folder already listed (a link back to a folder above, or a second link to one) is not
227
+ * listed again, as Vite's crawler does not crawl it again. A link to a file is listed as a file. A scope whose folder
228
+ * does not exist (yet) lists as missing.
229
+ */
230
+ function readScope(scope) {
231
+ const entries = [];
232
+ const linked = /* @__PURE__ */ new Set();
233
+ const visited = /* @__PURE__ */ new Set();
234
+ const list = (dir, prefix) => {
235
+ let found;
236
+ try {
237
+ found = (0, node_fs.readdirSync)(dir, { withFileTypes: true });
238
+ } catch {
239
+ return false;
240
+ }
241
+ visited.add(require_watch_targets.fileKey(dir));
242
+ for (const entry of found.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)) {
243
+ const path = `${prefix}${entry.name}`;
244
+ const target = entry.isSymbolicLink() ? linkedFolder((0, node_path.join)(dir, entry.name)) : void 0;
245
+ const isDir = entry.isDirectory() || target !== void 0;
246
+ if (!admits(scope, entry.name, isDir)) continue;
247
+ entries.push(target === void 0 ? `${isDir ? "d" : "f"}:${path}` : `l:${path}>${target}`);
248
+ if (target !== void 0) {
249
+ if (visited.has(require_watch_targets.fileKey(target))) continue;
250
+ linked.add(target);
251
+ }
252
+ if (isDir) list((0, node_path.join)(dir, entry.name), `${path}/`);
253
+ }
254
+ return true;
255
+ };
256
+ return {
257
+ listing: list(scope.dir, "") ? entries.join("\n") : "\0missing",
258
+ linked: [...linked]
259
+ };
260
+ }
261
+ /**
262
+ * Whether adding or removing the file (or folder, `isDir`) at `path` may change what the glob of `scope` matches:
263
+ * it is in the scope, or in one of the folders it reaches through links (`linked`, see readScope), and `admits`
264
+ * lets it through, or it is one of those folders itself. Folders are compared by identity key, so one is
265
+ * recognised however the path spells it, through a link or not.
266
+ */
267
+ function scopeAdmits(scope, path, isDir, linked) {
268
+ const folders = [scope.dir, ...scope.deep ? linked : []].map(require_watch_targets.fileKey);
269
+ if (folders.includes(require_watch_targets.fileKey(path))) return true;
270
+ const parent = require_watch_targets.fileKey((0, node_path.dirname)(path));
271
+ return (scope.deep ? require_watch_targets.keyWithin(parent, folders) : parent === folders[0]) && admits(scope, (0, node_path.basename)(path), isDir);
272
+ }
273
+ /**
274
+ * The folders a watcher watches for `scopes`: each scope's folder, or, for one that does not exist, the nearest
275
+ * folder above it that does, where creating it shows; and the folders a deep scope reaches through links, which a
276
+ * watcher of the scope's folder may not follow.
277
+ */
278
+ function scopeWatchTargets(scopes) {
279
+ return [...new Set([...scopes].flatMap((scope) => [require_watch_targets.toPosix(require_watch_targets.missingImportFolders([scope.dir])[0] ?? scope.dir), ...readScope(scope).linked]))];
280
+ }
281
+ /**
282
+ * The file `pattern` (an alias or a `#` import, not a path) resolves to from `importer`, asked as Vite's
283
+ * import-glob asks; undefined when it resolves to none, or the resolver fails (Vite's import-glob then reports
284
+ * the pattern itself).
285
+ */
286
+ async function resolveGlob(context, pattern, importer) {
287
+ const isSubImportsPattern = pattern.startsWith("#") && pattern.includes("*");
288
+ try {
289
+ return (await context.resolve(pattern, importer, { custom: { "vite:import-glob": { isSubImportsPattern } } }))?.id.replace(/[?#].*$/s, "");
290
+ } catch {
291
+ return;
292
+ }
293
+ }
294
+ /**
295
+ * The scopes of the glob calls in a module's code, read in a plugin's transform hook, whose `context` resolves an
296
+ * alias or `#` pattern as Vite's import-glob would; a call acorn cannot read is reported with a warning and has
297
+ * none.
298
+ */
299
+ async function moduleGlobScopes(context, code, id, root) {
300
+ if (!mayHoldGlob(code)) return [];
301
+ const { calls, unreadable } = globCalls(code);
302
+ if (unreadable.length > 0) {
303
+ const where = unreadable.map((offset) => lineColumn(code, offset)).join(", ");
304
+ context.warn(`twee-ts could not read the import.meta.glob() call at ${where} of ${id}; files added to its folders are bundled the next time something else changes.`);
305
+ }
306
+ const file = id.replace(/[?#].*$/s, "");
307
+ const importer = (0, node_path.isAbsolute)(file) && !id.startsWith("\0") ? file : void 0;
308
+ const resolver = (pattern) => resolveGlob(context, pattern, id);
309
+ return (await Promise.all(calls.map((call) => scopesOf(call, importer, root, resolver)))).flat();
310
+ }
311
+ //#endregion
312
+ //#region src/plugins/vite-entry.ts
313
+ /**
314
+ * Bundling the Vite plugin's `entry` into one script and one stylesheet for the
315
+ * story. In a build, the plugin bundles the entry inside the user's build when
316
+ * it can (see entryInputSettings), and otherwise, and always in dev, with a
317
+ * build of its own: bundleEntry() below, which replays the user's configuration.
318
+ */
319
+ /** The plugin's name, which is also how the entry build tells it from the user's other plugins. */
320
+ const PLUGIN_NAME = "twee-ts";
321
+ /** Passage names the bundled entry takes inside the story. */
322
+ const ENTRY_SCRIPT_NAME = "twee-ts-entry.js";
323
+ const ENTRY_STYLE_NAME = "twee-ts-entry.css";
324
+ /** How the name of the entry's input starts when the plugin bundles it inside the user's build. */
325
+ const ENTRY_INPUT_NAME = "twee-ts-entry";
326
+ /** The error for a file the entry emits under a name the build (or another story's entry) already writes. */
327
+ function entryCollisionMessage(fileName) {
328
+ return `twee-ts: the entry emits ${fileName}, a file the build already writes; rename one of them.`;
329
+ }
330
+ /** The entry's script and stylesheet as inline sources of the story (none without an entry). */
331
+ function entrySources(entry) {
332
+ if (entry === void 0) return [];
333
+ return [{
334
+ filename: ENTRY_SCRIPT_NAME,
335
+ content: `;${entry.script}`
336
+ }, ...entry.style === "" ? [] : [{
337
+ filename: ENTRY_STYLE_NAME,
338
+ content: entry.style
339
+ }]];
340
+ }
341
+ /**
342
+ * The output options of the entry's bundle: one IIFE script, and assets under
343
+ * their own names, so a build writes them where the dev server serves them.
344
+ */
345
+ const ENTRY_OUTPUT = {
346
+ format: "iife",
347
+ entryFileNames: ENTRY_SCRIPT_NAME,
348
+ assetFileNames: "[name][extname]"
349
+ };
350
+ /**
351
+ * The output options of the entry's bundle given the user's `output` option:
352
+ * the user's own when it is one object, with the entry's format and file names
353
+ * on top. An array of outputs describes the user's own files; none of them
354
+ * applies to the entry.
355
+ */
356
+ function entryOutput(userOutput) {
357
+ return userOutput === void 0 || Array.isArray(userOutput) ? { ...ENTRY_OUTPUT } : {
358
+ ...userOutput,
359
+ ...ENTRY_OUTPUT
360
+ };
361
+ }
362
+ /**
363
+ * Settings that make every font and image the entry uses inline as a data URL,
364
+ * and its CSS one stylesheet, so the story stays one file; only an import marked
365
+ * `?no-inline` is still emitted separately.
366
+ */
367
+ const ONE_FILE = {
368
+ cssCodeSplit: false,
369
+ assetsInlineLimit: () => true
370
+ };
371
+ /**
372
+ * What `import.meta.url` stands for in the entry's script, given the story's output file name. The script runs
373
+ * as a classic script inside the story page, where `import.meta` does not exist (the bundler leaves `{}.url`, so
374
+ * `new URL(asset, import.meta.url)` and Vite's worker URLs throw `Invalid URL`). The files the build writes
375
+ * besides the story, such as an asset marked `?no-inline`, are found from the output folder the story is below,
376
+ * so the base is the story page's URL, taken up one level for each folder of a nested `outputFilename`.
377
+ */
378
+ function entryDefine(outputFilename) {
379
+ const depth = folderDepth(outputFilename);
380
+ return { "import.meta.url": depth === 0 ? "document.baseURI" : `new URL(${JSON.stringify("../".repeat(depth))}, document.baseURI).href` };
381
+ }
382
+ /** How many folders deep `outputFilename` puts the story below the output folder. */
383
+ function folderDepth(outputFilename) {
384
+ return outputFilename.split("/").length - 1;
385
+ }
386
+ /**
387
+ * The `experimental.renderBuiltUrl` of a build that bundles the entry, given the build's `base`, the story's
388
+ * `outputFilename` and the configuration's own `renderBuiltUrl` (`user`, which decides first), or `user` itself
389
+ * when nothing changes. With a relative `base` (`''` or `'./'`), Vite makes the URL of a file the build writes
390
+ * besides the story (an asset marked `?no-inline`, a worker) relative to the script or stylesheet that names it.
391
+ * The story page holds both inline, so such a URL resolves against the page, which a nested `outputFilename` puts
392
+ * below the output folder the files are written to. For the entry's script, the URL is then resolved at run time
393
+ * against the output folder, as `import.meta.url` is (see `entryDefine`); in its stylesheet, it is a path up from
394
+ * the page. Every stylesheet of such a build is the entry's: the build bundles nothing else.
395
+ */
396
+ function entryRenderBuiltUrl(base, outputFilename, user) {
397
+ const depth = folderDepth(outputFilename);
398
+ if (base !== "" && base !== "./" || depth === 0) return user;
399
+ const outputFolder = entryDefine(outputFilename)["import.meta.url"];
400
+ return (filename, type) => {
401
+ const own = user?.(filename, type);
402
+ if (own !== void 0) return own;
403
+ const path = filename.replaceAll("%", "%25");
404
+ if (type.hostType === "js" && type.hostId === ENTRY_SCRIPT_NAME) return { runtime: `new URL(${JSON.stringify(path)}, ${outputFolder}).href` };
405
+ return type.hostType === "css" ? `${"../".repeat(depth)}${path}` : void 0;
406
+ };
407
+ }
408
+ /**
409
+ * Build settings that bundle the entry inside the user's build, as its only
410
+ * input, under `inputName`, which tells the plugin instance it is its own, and
411
+ * what the bundled script needs to run in the story (`define`).
412
+ */
413
+ function entryInputSettings(inputName, entryPath, userOutput, outputFilename) {
414
+ return {
415
+ build: {
416
+ ...ONE_FILE,
417
+ rolldownOptions: {
418
+ input: { [inputName]: entryPath },
419
+ output: entryOutput(userOutput)
420
+ }
421
+ },
422
+ define: entryDefine(outputFilename)
423
+ };
424
+ }
425
+ /** Removes a trailing source-map comment that points at a file (a data: URL map stays). */
426
+ function dropFileSourceMapComment(code) {
427
+ return code.replace(/\n?\/\/# sourceMappingURL=(?!data:)\S*\s*$/, "").replace(/\n?\/\*# sourceMappingURL=(?!data:)[^*]*\*\/\s*$/, "");
428
+ }
429
+ /** A module id or watched file as a forward-slash file path, without a query or hash (like Vite's cleanUrl). */
430
+ function fileOfId(id) {
431
+ return require_watch_targets.toPosix(id.replace(/[?#].*$/s, ""));
432
+ }
433
+ /** Removes a file from a bundle, which bundlers hand plugins as a plain object keyed by file name. */
434
+ function removeFromBundle(bundle, fileName) {
435
+ delete bundle[fileName];
436
+ }
437
+ /**
438
+ * Whether an asset is a file the entry imported as a URL (`theme.css?url`), not the stylesheet the bundler made
439
+ * of the entry's CSS. The bundler names the files an asset came from; for its own stylesheet that is a bare
440
+ * output name (`style.css`), for an imported file the file's path.
441
+ */
442
+ function isImportedFile(item) {
443
+ return (item.originalFileNames ?? []).some((name) => (0, node_path.isAbsolute)(name) || node_path.win32.isAbsolute(name));
444
+ }
445
+ /**
446
+ * Takes the entry out of a bundle: its entry chunk (the one named `inputName`,
447
+ * when the entry is one input of the user's build; the only one, from the
448
+ * entry's own build), every stylesheet it made of the entry's CSS (a CSS file imported as a URL stays an asset), and the map files, which the story
449
+ * cannot ship. The chunk is found by its input's name, not by its module's
450
+ * path, which the bundler may spell another way (a root reached through a
451
+ * link is read by its real path). Anything else the bundler emitted for it
452
+ * stays in the bundle, so a build writes it next to the HTML, and is also
453
+ * returned, so the dev server can serve it.
454
+ */
455
+ function takeEntryFromBundle(bundle, inputName) {
456
+ let script = "";
457
+ const files = /* @__PURE__ */ new Set();
458
+ const assets = /* @__PURE__ */ new Map();
459
+ const styles = [];
460
+ for (const [fileName, item] of Object.entries(bundle)) if (item.type === "chunk") {
461
+ if (!item.isEntry || inputName !== void 0 && item.name !== inputName) continue;
462
+ script = dropFileSourceMapComment(item.code);
463
+ for (const id of item.moduleIds) if (!id.startsWith("\0")) files.add(fileOfId(id));
464
+ removeFromBundle(bundle, fileName);
465
+ } else if (fileName.endsWith(".css") && !isImportedFile(item)) {
466
+ const css = typeof item.source === "string" ? item.source : new TextDecoder().decode(item.source);
467
+ styles.push(dropFileSourceMapComment(css));
468
+ removeFromBundle(bundle, fileName);
469
+ } else if (fileName.endsWith(".map")) removeFromBundle(bundle, fileName);
470
+ else assets.set(fileName, item.source);
471
+ return {
472
+ script,
473
+ style: styles.join("\n"),
474
+ files,
475
+ assets
476
+ };
477
+ }
478
+ /**
479
+ * The entry build's logger: its warnings go to the outer logger; its progress
480
+ * lines and its own "build failed" line are dropped, because the plugin
481
+ * reports a failed bundle itself. (The progress lines Vite 8 prints itself are
482
+ * kept quiet by the entry build's log level; see bundleEntry.)
483
+ */
484
+ function entryBuildLogger(outer) {
485
+ return {
486
+ info: () => {},
487
+ warn: (message, options) => {
488
+ outer.warn(message, options);
489
+ },
490
+ warnOnce: (message, options) => {
491
+ outer.warnOnce(message, options);
492
+ },
493
+ error: () => {},
494
+ clearScreen: () => {},
495
+ hasErrorLogged: () => false,
496
+ get hasWarned() {
497
+ return outer.hasWarned;
498
+ }
499
+ };
500
+ }
501
+ /**
502
+ * The entry build's log level: no progress lines (Vite 8 prints those itself
503
+ * at `info`), and no more than the outer build or server shows.
504
+ */
505
+ function entryBuildLogLevel(outer) {
506
+ return outer === void 0 || outer === "info" ? "warn" : outer;
507
+ }
508
+ /**
509
+ * Top-level keys of the user's configuration the entry build doesn't take, and
510
+ * why. Everything else (define, resolve, css, json, esbuild/oxc, env options,
511
+ * base, assetsInclude, worker, build options, environments, experimental and
512
+ * the rest) comes across as the user gave it, so the entry bundles the same in
513
+ * dev as in a build.
514
+ */
515
+ const ENTRY_BUILD_EXCLUDED_KEYS = {
516
+ configFile: "the entry build is given the loaded config file itself",
517
+ plugins: "replaced by the same plugins without twee-ts, plus the entry build’s own",
518
+ root: "set to the resolved root",
519
+ mode: "set to the resolved mode",
520
+ logLevel: "set so the entry build prints no progress lines",
521
+ customLogger: "the entry build reports through the outer logger",
522
+ clearScreen: "the entry build never clears the screen",
523
+ publicDir: "set to the outer build’s resolved public directory, which the entry build resolves from but never copies",
524
+ server: "configures the dev server, not a build",
525
+ preview: "configures the preview server, not a build",
526
+ builder: "would build every environment instead of the entry",
527
+ devtools: "starts Vite’s devtools integration, which belongs to the outer server or build"
528
+ };
529
+ /** The user's configuration without the keys the entry build doesn't take. */
530
+ function withoutExcludedKeys(config) {
531
+ const copy = { ...config };
532
+ for (const key of Object.keys(ENTRY_BUILD_EXCLUDED_KEYS)) Reflect.deleteProperty(copy, key);
533
+ return copy;
534
+ }
535
+ /** A plugin option (nested arrays, promises, falsy entries) as one flat list. */
536
+ async function flattenPlugins(option) {
537
+ const value = await option;
538
+ if (Array.isArray(value)) return (await Promise.all(value.map(flattenPlugins))).flat();
539
+ return value ? [value] : [];
540
+ }
541
+ function isPlugin(value) {
542
+ return require_diagnostic_text.isRecord(value) && typeof value["name"] === "string";
543
+ }
544
+ /** The handler of a plugin hook in function or object form. */
545
+ function hookHandler(hook) {
546
+ return require_diagnostic_text.isRecord(hook) ? hook["handler"] : hook;
547
+ }
548
+ /** A hook with its handler replaced, keeping the object form's order and other fields. */
549
+ function withHandler(hook, handler) {
550
+ return require_diagnostic_text.isRecord(hook) ? {
551
+ ...hook,
552
+ handler
553
+ } : handler;
554
+ }
555
+ /**
556
+ * Whether Vite applies `plugin` for `command`: as Vite reads `apply`, with
557
+ * the configuration and environment the plugin would see.
558
+ */
559
+ function appliesTo(plugin, command, config, env) {
560
+ const { apply } = plugin;
561
+ if (apply === void 0) return true;
562
+ if (typeof apply === "function") return apply({
563
+ ...config,
564
+ mode: env.mode
565
+ }, env);
566
+ return apply === command;
567
+ }
568
+ /**
569
+ * `plugin` as the entry build runs it: without `apply` (already decided by
570
+ * appliesTo), and, for the dev command, with its `config`, `configEnvironment`
571
+ * and `configResolved` hooks seeing the dev command, as the dev server's
572
+ * instance of the plugin sees it. The plugin object itself is left untouched.
573
+ */
574
+ function asEntryBuildPlugin(plugin, command) {
575
+ const overrides = /* @__PURE__ */ new Map([["apply", void 0]]);
576
+ if (command === "serve") {
577
+ const devEnv = (env) => Object.assign({}, env, {
578
+ command,
579
+ isSsrBuild: false
580
+ });
581
+ const wrap = (name, patch) => {
582
+ const hook = plugin[name];
583
+ const handler = hookHandler(hook);
584
+ if (typeof handler !== "function") return;
585
+ overrides.set(name, withHandler(hook, function(...args) {
586
+ return Reflect.apply(handler, this, patch(args));
587
+ }));
588
+ };
589
+ wrap("config", ([config, env]) => [config, devEnv(env)]);
590
+ wrap("configEnvironment", ([name, config, env]) => [
591
+ name,
592
+ config,
593
+ devEnv(env)
594
+ ]);
595
+ wrap("configResolved", ([config]) => [require_diagnostic_text.isRecord(config) ? withOverrides(config, /* @__PURE__ */ new Map([["command", command]])) : config]);
596
+ }
597
+ return pluginWith(plugin, overrides);
598
+ }
599
+ /**
600
+ * A copy of `plugin` with some properties of its own reading differently; the plugin itself is left untouched. The
601
+ * copy has the plugin's prototype (a class's methods and getters) and each of its own properties as they are
602
+ * defined (a getter stays a getter), so a frozen plugin, or one whose hooks are getters, is copied as well: a Proxy
603
+ * may not answer differently for a property a frozen object defines. The copy stands in for the plugin wherever
604
+ * the plugin list has it.
605
+ */
606
+ function pluginWith(plugin, overrides) {
607
+ const copy = { ...plugin };
608
+ const layer = {};
609
+ Object.setPrototypeOf(layer, Reflect.getPrototypeOf(plugin));
610
+ for (const [key, value] of overrides) Object.defineProperty(layer, key, {
611
+ value,
612
+ writable: true,
613
+ enumerable: false,
614
+ configurable: true
615
+ });
616
+ Object.setPrototypeOf(copy, layer);
617
+ for (const key of Reflect.ownKeys(plugin)) {
618
+ const descriptor = Object.getOwnPropertyDescriptor(plugin, key);
619
+ if (descriptor !== void 0 && !overrides.has(key)) Object.defineProperty(copy, key, descriptor);
620
+ }
621
+ for (const [key, value] of overrides) Object.defineProperty(copy, key, {
622
+ value,
623
+ writable: true,
624
+ enumerable: true,
625
+ configurable: true
626
+ });
627
+ return copy;
628
+ }
629
+ /** `target` with some properties reading differently; the object itself is left untouched. */
630
+ function withOverrides(target, overrides) {
631
+ return new Proxy(target, { get(object, key, receiver) {
632
+ return overrides.has(key) ? overrides.get(key) : Reflect.get(object, key, receiver);
633
+ } });
634
+ }
635
+ /**
636
+ * Build settings that make the entry the build's one input and its bundle one
637
+ * script and one stylesheet held in memory, whatever the user's build settings
638
+ * say; for the dev command also unminified, with an inline source map. Settings
639
+ * that would build something else (`lib`, `ssr`) or write files are dropped.
640
+ */
641
+ function entryBuildSettings(userBuild, entryPath, command) {
642
+ const { lib: _lib, ssr: _ssr, rollupOptions, rolldownOptions, ...rest } = { ...userBuild };
643
+ const { input: _input, output, ...bundler } = rolldownOptions ?? rollupOptions ?? {};
644
+ return {
645
+ ...rest,
646
+ ...ONE_FILE,
647
+ write: false,
648
+ watch: null,
649
+ emptyOutDir: false,
650
+ copyPublicDir: false,
651
+ ...command === "serve" ? {
652
+ minify: false,
653
+ sourcemap: "inline"
654
+ } : {},
655
+ rolldownOptions: {
656
+ ...bundler,
657
+ input: entryPath,
658
+ output: entryOutput(output)
659
+ }
660
+ };
661
+ }
662
+ /**
663
+ * Applies entryBuildSettings last, after every config hook, in the
664
+ * configuration and in the client environment's own options (which the build
665
+ * reads), so no setting from the user or a plugin makes the entry build build
666
+ * or write anything else.
667
+ */
668
+ function entryBuildEnforcer(entryPath, command, outputFilename) {
669
+ return {
670
+ name: `${PLUGIN_NAME}:entry-build`,
671
+ enforce: "post",
672
+ config: {
673
+ order: "post",
674
+ handler(config) {
675
+ config.build = entryBuildSettings(config.build, entryPath, command);
676
+ config.define = {
677
+ ...config.define,
678
+ ...entryDefine(outputFilename)
679
+ };
680
+ const renderBuiltUrl = entryRenderBuiltUrl(config.base, outputFilename, config.experimental?.renderBuiltUrl);
681
+ if (renderBuiltUrl !== void 0) config.experimental = {
682
+ ...config.experimental,
683
+ renderBuiltUrl
684
+ };
685
+ }
686
+ },
687
+ configEnvironment: {
688
+ order: "post",
689
+ handler(name, config) {
690
+ if (name !== "client") return;
691
+ config.build = entryBuildSettings(config.build, entryPath, command);
692
+ config.define = {
693
+ ...config.define,
694
+ ...entryDefine(outputFilename)
695
+ };
696
+ }
697
+ }
698
+ };
699
+ }
700
+ /** Build-phase plugin hooks that may call `this.addWatchFile`. */
701
+ const WATCH_FILE_HOOKS = [
702
+ "buildStart",
703
+ "resolveId",
704
+ "resolveDynamicImport",
705
+ "load",
706
+ "transform",
707
+ "moduleParsed",
708
+ "buildEnd"
709
+ ];
710
+ /** A plugin context whose `addWatchFile` also passes the file to `record`. */
711
+ function recordingContext(context, record) {
712
+ return new Proxy(context, { get(target, key) {
713
+ const value = Reflect.get(target, key, target);
714
+ if (typeof value !== "function") return value;
715
+ if (key !== "addWatchFile") return value.bind(target);
716
+ return (id) => {
717
+ if (typeof id === "string" && !id.startsWith("\0")) record(fileOfId((0, node_path.resolve)(id)));
718
+ return Reflect.apply(value, target, [id]);
719
+ };
720
+ } });
721
+ }
722
+ /**
723
+ * `plugin` with hooks that pass the file of each of its `addWatchFile` calls to `record`. The
724
+ * original stays untouched, so a plugin object shared across entry builds
725
+ * never collects wrappers. A plugin without such hooks (a built-in one, for
726
+ * instance) is returned as it is.
727
+ */
728
+ function recordingPlugin(plugin, record) {
729
+ const overrides = /* @__PURE__ */ new Map();
730
+ for (const name of WATCH_FILE_HOOKS) {
731
+ const hook = plugin[name];
732
+ const handler = hookHandler(hook);
733
+ if (typeof handler !== "function") continue;
734
+ overrides.set(name, withHandler(hook, function(...args) {
735
+ const context = typeof this === "object" && this !== null ? recordingContext(this, record) : this;
736
+ return Reflect.apply(handler, context, args);
737
+ }));
738
+ }
739
+ return overrides.size === 0 ? plugin : pluginWith(plugin, overrides);
740
+ }
741
+ const isFile = (path) => (0, node_fs.statSync)(path, { throwIfNoEntry: false })?.isFile() === true;
742
+ /** How an import is resolved with its links kept (see keptPath), one resolver per configuration. */
743
+ const keptResolvers = /* @__PURE__ */ new WeakMap();
744
+ /**
745
+ * Makes keptPath resolve with a new resolver for `config`: Vite's resolver remembers what it found, which a build of
746
+ * `vite build --watch` must not take from the one before.
747
+ */
748
+ function forgetKeptResolver(config) {
749
+ keptResolvers.delete(config);
750
+ }
751
+ /**
752
+ * The file `source`, imported by `importer`, reaches as Vite's resolver finds it, with the links on the way kept
753
+ * (`resolve.preserveSymlinks`): the path through a linked file, folder or package folder, which the module's own
754
+ * (real) path never shows. It follows every spelling Vite's resolver reads: relative and absolute paths, the root's
755
+ * `/`, missing extensions, a folder's index, `resolve.alias`, bare package names, a package's subpaths and exports,
756
+ * `#` subpath imports and `resolve.tsconfigPaths`. Undefined for an import it resolves to no file (a virtual module,
757
+ * an external one, one it cannot resolve), or when resolving fails, which the bundle itself then reports.
758
+ */
759
+ async function keptPath(context, source, importer) {
760
+ const { environment } = context;
761
+ let resolver = keptResolvers.get(environment.config);
762
+ if (resolver === void 0) {
763
+ resolver = (0, vite.createIdResolver)(environment.config, { preserveSymlinks: true });
764
+ keptResolvers.set(environment.config, resolver);
765
+ }
766
+ try {
767
+ const id = await resolver(environment, source, importer);
768
+ return id === void 0 || id.startsWith("\0") ? void 0 : fileOfId(id);
769
+ } catch {
770
+ return;
771
+ }
772
+ }
773
+ /**
774
+ * The paths an import of `source` by `importer` is written as, absolute and with forward slashes, when it is written
775
+ * as a path: relative to the importer, or absolute; also when that reaches nothing yet, so creating it shows. None for
776
+ * a bare name or an alias. An absolute path that starts with `/` is read as Vite reads it: from the root first, unless
777
+ * it already names a path inside the root, and as a path of the file system when that is where it reaches a file.
778
+ */
779
+ function writtenPaths(source, importer, root) {
780
+ const from = importer === void 0 ? void 0 : fileOfId(importer);
781
+ if ((0, node_path.isAbsolute)(source)) {
782
+ const file = fileOfId(source);
783
+ return [.../^[/\\]/.test(source) && !file.startsWith(`${require_watch_targets.toPosix(root).replace(/\/$/, "")}/`) && !(0, node_fs.existsSync)(file) ? [fileOfId((0, node_path.join)(root, source))] : [], file];
784
+ }
785
+ return from !== void 0 && (0, node_path.isAbsolute)(from) && /^\.\.?(?:[/\\]|$)/.test(source) ? [fileOfId((0, node_path.resolve)((0, node_path.dirname)(from), source))] : [];
786
+ }
787
+ /**
788
+ * Every path an import of `source` by `importer` is spelled as (see writtenPaths), and the path Vite's resolver
789
+ * reaches the file by, links kept (`kept`, see keptPath). A caller then sees a link on the way that is retargeted,
790
+ * which the file's own path never shows. Nothing for an import from a virtual module, or of one.
791
+ */
792
+ async function importSpellings(context, source, importer, root) {
793
+ if (source.startsWith("\0") || (importer?.startsWith("\0") ?? false)) return {
794
+ spellings: [],
795
+ kept: void 0
796
+ };
797
+ const written = writtenPaths(source, importer, root);
798
+ const resolved = written.find(isFile) ?? await keptPath(context, source, importer);
799
+ const kept = resolved !== void 0 && (0, node_path.isAbsolute)(resolved) ? resolved : void 0;
800
+ return {
801
+ spellings: [.../* @__PURE__ */ new Set([...written, ...kept === void 0 ? [] : [kept]])],
802
+ kept
803
+ };
804
+ }
805
+ /**
806
+ * Collects the files the entry build's plugins add with `addWatchFile`. These
807
+ * are not modules of the bundle: Vite's CSS plugin adds the stylesheets pulled
808
+ * in by `@import` and the files `url()` points at this way. The build runs on
809
+ * recording stand-ins of its plugins, made in the bundler's `options` hook,
810
+ * which sees the final plugin list, including the plugins Vite resolves per
811
+ * environment (`applyToEnvironment`) after `configResolved`.
812
+ */
813
+ function recordWatchFiles(files, root, observer) {
814
+ const { onLoad, onResolve, onGlob, onWatchFile } = observer;
815
+ const record = (file) => {
816
+ files.add(file);
817
+ onWatchFile?.(file);
818
+ };
819
+ return {
820
+ name: `${PLUGIN_NAME}:record-watch-files`,
821
+ load: {
822
+ order: "pre",
823
+ handler(id) {
824
+ const file = fileOfId(id);
825
+ if (!(0, node_path.isAbsolute)(file) || id.startsWith("\0")) return null;
826
+ onLoad?.(file);
827
+ onResolve?.(file);
828
+ return null;
829
+ }
830
+ },
831
+ resolveId: {
832
+ order: "pre",
833
+ async handler(source, importer) {
834
+ if (onResolve === void 0) return null;
835
+ for (const spelled of (await importSpellings(this, source, importer, root)).spellings) onResolve(spelled);
836
+ return null;
837
+ }
838
+ },
839
+ transform: {
840
+ filter: { code: "import.meta.glob" },
841
+ async handler(code, id) {
842
+ if (onGlob === void 0) return null;
843
+ for (const scope of await moduleGlobScopes(this, code, id, root)) onGlob(scope);
844
+ return null;
845
+ }
846
+ },
847
+ buildEnd() {
848
+ for (const id of this.getModuleIds()) {
849
+ const file = fileOfId(id);
850
+ if ((0, node_path.isAbsolute)(file)) files.add(file);
851
+ }
852
+ },
853
+ options: {
854
+ order: "post",
855
+ async handler(inputOptions) {
856
+ const plugins = (await flattenPlugins(inputOptions.plugins)).filter(isPlugin).map((p) => recordingPlugin(p, record));
857
+ return {
858
+ ...inputOptions,
859
+ plugins
860
+ };
861
+ }
862
+ }
863
+ };
864
+ }
865
+ /**
866
+ * `worker` with `plugin()` after its own `worker.plugins`, which Vite calls for each worker build (nested ones
867
+ * too); the user's plugins come first, in their order, whether given as a function or as the list older Vite
868
+ * versions took.
869
+ */
870
+ function withWorkerPlugin(worker, plugin) {
871
+ const own = worker?.plugins;
872
+ return {
873
+ ...worker,
874
+ plugins: () => [typeof own === "function" ? own() : own, plugin()]
875
+ };
876
+ }
877
+ /**
878
+ * The user's own configuration as Vite evaluated it for the outer server or
879
+ * build, but for `command`: the config file (when there is one) evaluated
880
+ * again for that command and mode, with what was passed to `createServer()` or
881
+ * `build()` (or the CLI flags) on top, as Vite merges them.
882
+ */
883
+ async function userConfigFor(config, env) {
884
+ const inline = config.inlineConfig;
885
+ if (!config.configFile) return inline;
886
+ const loaded = await (0, vite.loadConfigFromFile)(env, config.configFile, config.root, entryBuildLogLevel(config.logLevel), entryBuildLogger(config.logger), inline.configLoader);
887
+ return loaded === null ? inline : (0, vite.mergeConfig)(loaded.config, inline);
888
+ }
889
+ /**
890
+ * Bundles the entry with a Vite build of its own, from the user's own
891
+ * configuration, evaluated for `command`: the config file's function and each
892
+ * plugin's `apply` see that command, and so do the `config`, `configEnvironment`
893
+ * and `configResolved` hooks of the user's plugins. Every setting comes across
894
+ * but those ENTRY_BUILD_EXCLUDED_KEYS lists and the build settings
895
+ * entryBuildSettings decides. twee-ts itself (every instance) is left out.
896
+ */
897
+ async function bundleEntry(config, entryPath, command, outputFilename, observer = {}) {
898
+ const watchFiles = observer.watchFiles ?? /* @__PURE__ */ new Set();
899
+ const env = {
900
+ command,
901
+ mode: config.mode,
902
+ isSsrBuild: false,
903
+ isPreview: false
904
+ };
905
+ const user = await userConfigFor(config, env);
906
+ const plugins = (await flattenPlugins(user.plugins)).filter(isPlugin).filter((plugin) => plugin.name !== "twee-ts" && appliesTo(plugin, command, user, env)).map((plugin) => asEntryBuildPlugin(plugin, command));
907
+ const inline = {
908
+ ...withoutExcludedKeys(user),
909
+ configFile: false,
910
+ root: config.root,
911
+ mode: config.mode,
912
+ logLevel: entryBuildLogLevel(config.logLevel),
913
+ customLogger: entryBuildLogger(config.logger),
914
+ clearScreen: false,
915
+ publicDir: config.publicDir || false,
916
+ plugins: [
917
+ ...plugins,
918
+ entryBuildEnforcer(entryPath, command, outputFilename),
919
+ recordWatchFiles(watchFiles, config.root, observer)
920
+ ],
921
+ worker: withWorkerPlugin(user.worker, () => recordWatchFiles(watchFiles, config.root, observer))
922
+ };
923
+ const [result] = [await (0, vite.build)(inline)].flat();
924
+ if (result === void 0 || !("output" in result)) throw new Error("twee-ts: the entry build returned no bundle.");
925
+ const entry = takeEntryFromBundle(Object.fromEntries(result.output.map((item) => [item.fileName, item])));
926
+ return {
927
+ ...entry,
928
+ files: /* @__PURE__ */ new Set([...entry.files, ...watchFiles])
929
+ };
930
+ }
931
+ //#endregion
932
+ //#region src/plugins/vite-dev.ts
933
+ /** How long after a change the story is compiled, so saves landing together compile once. */
934
+ const DEBOUNCE_MS = 50;
935
+ /**
936
+ * How long a file the watcher was asked to add may take to be listed as watched, and how long after that its
937
+ * watch may still miss an edit (macOS FSEvents starts reporting a moment after the watch is set up); see
938
+ * `recheckOnceWatched` in setUpDevStory.
939
+ */
940
+ const WATCH_LISTED_TIMEOUT_MS = 1e4;
941
+ const WATCH_START_MS = 500;
942
+ /** Resolves after `ms`, without keeping the process alive. */
943
+ function delay(ms) {
944
+ return new Promise((done) => setTimeout(done, ms).unref());
945
+ }
946
+ /**
947
+ * The paths under the base URL the story is served at: the output file name,
948
+ * and, as a static host serves it, the folder of an `index.html`.
949
+ */
950
+ function storyPaths(outputFilename) {
951
+ const index = "index.html";
952
+ return outputFilename === index || outputFilename.endsWith(`/${index}`) ? [outputFilename, outputFilename.slice(0, -10)] : [outputFilename];
953
+ }
954
+ /**
955
+ * `path` (a decoded URL path below the base, with no leading slash) as one spelling: a backslash is a slash, as in
956
+ * an http URL, a `.` segment is dropped and a `..` segment drops the one before it, as the URL standard resolves
957
+ * them (a last `.` or `..` leaves a trailing slash), and empty segments (`a//b`) are dropped, as Vite's file
958
+ * middlewares and static hosts drop them. A `..` never leaves the base.
959
+ */
960
+ function oneSpelling(path) {
961
+ const segments = path.replaceAll("\\", "/").split("/");
962
+ const kept = [];
963
+ segments.forEach((segment, at) => {
964
+ const last = at === segments.length - 1;
965
+ if (segment === "..") kept.pop();
966
+ if (segment !== "" && segment !== "." && segment !== "..") kept.push(segment);
967
+ else if (last) kept.push("");
968
+ });
969
+ return kept.join("/");
970
+ }
971
+ /**
972
+ * A request URL's path below `base`, decoded, without query or hash, and with its dot segments, empty segments and
973
+ * backslashes resolved (see oneSpelling), so that every spelling of the story's path is the story (#401); undefined
974
+ * when the path is outside the base or is not a valid percent-encoding.
975
+ */
976
+ function pathBelowBase(url, base) {
977
+ const raw = url.replace(/[?#].*$/s, "");
978
+ let path;
979
+ let decodedBase;
980
+ try {
981
+ path = decodeURIComponent(raw);
982
+ decodedBase = decodeURIComponent(base);
983
+ } catch {
984
+ return;
985
+ }
986
+ return path.startsWith(decodedBase) ? oneSpelling(path.slice(decodedBase.length)) : void 0;
987
+ }
988
+ /** The reload message for a rebuilt story: limited to the story's pages unless Vite's client could not match them. */
989
+ function reloadPayload(base, outputFilename) {
990
+ let decoded;
991
+ try {
992
+ decoded = decodeURI(base);
993
+ } catch {
994
+ return { type: "full-reload" };
995
+ }
996
+ return decoded === base ? {
997
+ type: "full-reload",
998
+ path: `/${outputFilename}`
999
+ } : { type: "full-reload" };
1000
+ }
1001
+ /** The media type of the story as each output mode writes it. */
1002
+ const OUTPUT_MEDIA_TYPES = {
1003
+ html: "text/html; charset=utf-8",
1004
+ "twine2-archive": "text/html; charset=utf-8",
1005
+ "twine1-archive": "text/html; charset=utf-8",
1006
+ twee3: "text/plain; charset=utf-8",
1007
+ twee1: "text/plain; charset=utf-8",
1008
+ json: "application/json; charset=utf-8"
1009
+ };
1010
+ /** Served until the first successful compile, so the overlay has a page to appear on. */
1011
+ function waitingPage(base) {
1012
+ return require_diagnostic_text.viteWaitingPage(`${base}@vite/client`);
1013
+ }
1014
+ function toOverlayError(e) {
1015
+ const error = require_watch_targets.fatalError(e);
1016
+ return {
1017
+ message: (0, node_util.stripVTControlCharacters)(error.message),
1018
+ stack: "",
1019
+ plugin: PLUGIN_NAME,
1020
+ ...error.id ? { id: error.id } : {},
1021
+ ...error.loc ? { loc: error.loc } : {}
1022
+ };
1023
+ }
1024
+ /**
1025
+ * Media types of the files a bundle emits that Tweego's table of story media does not have: a browser
1026
+ * refuses a worker or stylesheet served as `application/octet-stream`.
1027
+ */
1028
+ const BUNDLE_MEDIA_TYPES = /* @__PURE__ */ new Map([
1029
+ ["js", "text/javascript"],
1030
+ ["mjs", "text/javascript"],
1031
+ ["css", "text/css"],
1032
+ ["json", "application/json"],
1033
+ ["map", "application/json"],
1034
+ ["wasm", "application/wasm"],
1035
+ ["html", "text/html; charset=utf-8"],
1036
+ ["txt", "text/plain; charset=utf-8"]
1037
+ ]);
1038
+ /** The media type the dev server gives an asset of the entry's bundle. */
1039
+ function assetMediaType(path) {
1040
+ return BUNDLE_MEDIA_TYPES.get(require_diagnostic_text.normalizedFileExt(path)) ?? require_diagnostic_text.mediaTypeFromFilename(path);
1041
+ }
1042
+ /**
1043
+ * What a change to a file alters: modification time, change time (which an in-place edit that restores the
1044
+ * modification time still moves, as the loader's file signature relies on), size and inode (a file replaced by a
1045
+ * new one).
1046
+ */
1047
+ function fileState(file) {
1048
+ try {
1049
+ const stat = (0, node_fs.statSync)(file);
1050
+ return `${stat.mtimeMs}:${stat.ctimeMs}:${stat.size}:${stat.ino}`;
1051
+ } catch {
1052
+ return;
1053
+ }
1054
+ }
1055
+ /**
1056
+ * What is in the folder `folder`, at any depth: the name and state (see fileState) of each file and folder in it,
1057
+ * links to folders not followed. A file added, removed, renamed or changed anywhere in it changes this, as it
1058
+ * makes the bundler's own watcher (`vite build --watch`) build again for a folder a plugin watches. undefined when
1059
+ * `folder` is not a folder.
1060
+ */
1061
+ function folderState(folder) {
1062
+ if ((0, node_fs.statSync)(folder, { throwIfNoEntry: false })?.isDirectory() !== true) return void 0;
1063
+ let names;
1064
+ try {
1065
+ names = (0, node_fs.readdirSync)(folder, {
1066
+ recursive: true,
1067
+ encoding: "utf8"
1068
+ });
1069
+ } catch {
1070
+ return;
1071
+ }
1072
+ return names.sort().map((name) => `${name}\0${fileState((0, node_path.join)(folder, name)) ?? ""}`).join("\0");
1073
+ }
1074
+ /** `file` as it is now, as a FileRead; undefined when it can't be read. */
1075
+ function fileRead(file) {
1076
+ const state = fileState(file);
1077
+ if (state === void 0) return void 0;
1078
+ try {
1079
+ return {
1080
+ state,
1081
+ hash: require_diagnostic_text.sha256Hex((0, node_fs.readFileSync)(file))
1082
+ };
1083
+ } catch {
1084
+ return;
1085
+ }
1086
+ }
1087
+ /** `paths` as TrackedFiles. */
1088
+ function tracked(paths) {
1089
+ return new Map([...paths].map((path) => [require_watch_targets.fileKey(path), require_watch_targets.canonicalPath(path)]));
1090
+ }
1091
+ /**
1092
+ * The key of the file `authored` reaches, when it reaches it through a link (somewhere along the path, or
1093
+ * at the end of it): the one case where the file's own path says nothing of a change of target.
1094
+ * undefined for a path with no link in it, and for one that reaches nothing.
1095
+ */
1096
+ function linkKey(authored) {
1097
+ if (!(0, node_fs.existsSync)(authored)) return void 0;
1098
+ return require_watch_targets.canonicalPath(authored) === require_watch_targets.toPosix((0, node_path.resolve)(authored)) ? void 0 : require_watch_targets.fileKey(authored);
1099
+ }
1100
+ /** The state of each of `files`, by key, for telling later which changed (see filesChanged). */
1101
+ function fileStates(files) {
1102
+ const states = /* @__PURE__ */ new Map();
1103
+ for (const [key, path] of files) {
1104
+ const state = fileState(path);
1105
+ if (state !== void 0) states.set(key, state);
1106
+ }
1107
+ return states;
1108
+ }
1109
+ /**
1110
+ * The states to compare later changes with, for the files a bundle was made from: each file as it was just
1111
+ * before the bundle read it (`observed`, noted as each module loads), else as it was before the bundle began
1112
+ * (`before`, for a file known from the last bundle), else as it is now. An edit while the bundle was made
1113
+ * then differs from the recorded state afterwards, and the next request makes the bundle again.
1114
+ */
1115
+ function settledStates(files, observed, before) {
1116
+ const states = /* @__PURE__ */ new Map();
1117
+ for (const [key, path] of files) {
1118
+ const state = observed.get(key) ?? before.get(key) ?? fileState(path);
1119
+ if (state !== void 0) states.set(key, state);
1120
+ }
1121
+ return states;
1122
+ }
1123
+ /** The keys of the files added, removed or changed between two `fileStates` results. */
1124
+ function filesChanged(before, after) {
1125
+ const changed = /* @__PURE__ */ new Set();
1126
+ for (const [file, state] of after) if (before.get(file) !== state) changed.add(file);
1127
+ for (const file of before.keys()) if (!after.has(file)) changed.add(file);
1128
+ return changed;
1129
+ }
1130
+ /**
1131
+ * The names of Vite's middlewares that answer a request with a file: from the public folder, the module
1132
+ * transform, the files of the root and the file system, and the HTML fallback.
1133
+ */
1134
+ const VITE_FILE_MIDDLEWARES = /* @__PURE__ */ new Set([
1135
+ "viteServePublicMiddleware",
1136
+ "viteTriggerLazyBundlingMiddleware",
1137
+ "viteMemoryFilesMiddleware",
1138
+ "viteTransformMiddleware",
1139
+ "viteServeRawFsMiddleware",
1140
+ "viteServeStaticMiddleware",
1141
+ "viteHtmlFallbackMiddleware",
1142
+ "viteIndexHtmlMiddleware"
1143
+ ]);
1144
+ /**
1145
+ * Installs the story's route (see setUpDevStory) among `middlewares`, as the plugin's post hook does: after Vite's
1146
+ * checks of a request (host, CORS, proxy, base), so they apply to the story as to any page, and before the first
1147
+ * of its middlewares that serve files. A build writes the story and the entry's assets over a public file of the
1148
+ * same name, and a static host serves nothing else at their paths, so the dev server must not answer them with a
1149
+ * file from the public folder or the root (#339). Appended when none of those middlewares is there.
1150
+ */
1151
+ function installStoryRoute(middlewares, route) {
1152
+ const at = middlewares.stack.findIndex(({ handle }) => typeof handle === "function" && VITE_FILE_MIDDLEWARES.has(handle.name));
1153
+ if (at === -1) middlewares.use(route);
1154
+ else middlewares.stack.splice(at, 0, {
1155
+ route: "",
1156
+ handle: route
1157
+ });
1158
+ }
1159
+ /** The story and assets each story instance of a dev server serves, so instances can see each other's names. */
1160
+ const devInstances = /* @__PURE__ */ new WeakMap();
1161
+ /** The error for a story page whose URL another story or an entry asset of the same dev server also claims. */
1162
+ function storyCollisionMessage(path) {
1163
+ return `twee-ts: more than one generated file is served at ${path || "/"} (another story or an entry asset); set a distinct outputFilename.`;
1164
+ }
1165
+ function sameBytes(a, b) {
1166
+ return Buffer.from(a).equals(Buffer.from(b));
1167
+ }
1168
+ /**
1169
+ * Sets up the story in `server`: the first compile, the watcher, and a
1170
+ * middleware that notes each request's URL before Vite's own middlewares
1171
+ * rewrite it. Resolves, after the first compile, to the middleware that serves
1172
+ * the story and the entry's assets, for installStoryRoute.
1173
+ */
1174
+ async function setUpDevStory(server, dev) {
1175
+ const { options, cache } = dev;
1176
+ const config = server.config;
1177
+ const { base } = config;
1178
+ const paths = storyPaths(options.outputFilename);
1179
+ const { inputs, excluded: excludedGlob } = options;
1180
+ const entryPath = options.entry;
1181
+ const root = require_watch_targets.fileKey(config.root);
1182
+ const inputKeys = () => inputs.map(require_watch_targets.fileKey);
1183
+ let outputs = dev.outputs(config);
1184
+ let output = require_diagnostic_text.outputPaths(outputs);
1185
+ const excluded = (file) => excludedGlob(file) || output.isOutput(file, inputs);
1186
+ server.watcher.add([...inputs]);
1187
+ const outputMode = options.compile().outputMode ?? "html";
1188
+ let html = "";
1189
+ let lastError;
1190
+ let entry;
1191
+ let servedAssets;
1192
+ const instances = devInstances.get(server) ?? /* @__PURE__ */ new Set();
1193
+ devInstances.set(server, instances);
1194
+ const self = {
1195
+ paths,
1196
+ assets: () => servedAssets
1197
+ };
1198
+ instances.add(self);
1199
+ const fail = (res, message) => {
1200
+ config.logger.error(`[twee-ts] ${message}`);
1201
+ res.statusCode = 500;
1202
+ res.setHeader("Content-Type", "text/plain; charset=utf-8");
1203
+ res.end(message);
1204
+ };
1205
+ let entryStale = true;
1206
+ let entryFiles = /* @__PURE__ */ new Map();
1207
+ let entryStates = /* @__PURE__ */ new Map();
1208
+ let entryLinks = /* @__PURE__ */ new Map();
1209
+ let entryMissingFolders = [];
1210
+ let entryGlobs = /* @__PURE__ */ new Map();
1211
+ let globStates = /* @__PURE__ */ new Map();
1212
+ let entryFolders = /* @__PURE__ */ new Map();
1213
+ let entryReads = /* @__PURE__ */ new Map();
1214
+ const readsModules = config.server.watch !== null;
1215
+ const watchedForEntry = /* @__PURE__ */ new Map();
1216
+ let queue = Promise.resolve();
1217
+ let pending = /* @__PURE__ */ new Set();
1218
+ let timer;
1219
+ let closed = false;
1220
+ let compiledInputs = /* @__PURE__ */ new Map();
1221
+ let catchingUp;
1222
+ const closeWatcher = server.watcher.close.bind(server.watcher);
1223
+ server.watcher.close = async () => {
1224
+ closed = true;
1225
+ clearTimeout(timer);
1226
+ pending.clear();
1227
+ return closeWatcher();
1228
+ };
1229
+ const namedInputs = () => inputs.map((input) => {
1230
+ const stat = (0, node_fs.statSync)(input, { throwIfNoEntry: false });
1231
+ const kind = stat === void 0 ? "none" : stat.isFile() ? "file" : stat.isDirectory() ? "folder" : "other";
1232
+ return `${require_watch_targets.fileKey(input)}\0${kind}`;
1233
+ }).join("\0");
1234
+ let inputWalk;
1235
+ const walkInputs = () => {
1236
+ const named = namedInputs();
1237
+ const folders = /* @__PURE__ */ new Map();
1238
+ const files = require_diagnostic_text.getFilenames(inputs, outputs, [], "source", (folder) => folders.set(folder, fileState(folder))).files.filter((file) => !excludedGlob(file.path)).map((file) => ({
1239
+ key: file.key,
1240
+ path: (0, node_path.resolve)(file.path),
1241
+ link: file.link === true
1242
+ }));
1243
+ return {
1244
+ outputs,
1245
+ named,
1246
+ folders,
1247
+ files
1248
+ };
1249
+ };
1250
+ const storyInputFiles = (walk) => {
1251
+ const last = inputWalk;
1252
+ const used = last !== void 0 && !walk && last.outputs === outputs && last.named === namedInputs() && [...last.folders].every(([folder, state]) => fileState(folder) === state) ? last : walkInputs();
1253
+ inputWalk = used;
1254
+ const states = /* @__PURE__ */ new Map();
1255
+ for (const { key, path, link } of used.files) {
1256
+ const state = fileState(path);
1257
+ if (state !== void 0) states.set(link ? require_watch_targets.fileKey(path) : key, state);
1258
+ }
1259
+ return states;
1260
+ };
1261
+ const touchesEntry = (key) => entryPath !== void 0 && (entryFiles.has(key) || entryStale && require_watch_targets.keyWithin(key, [root, ...entryMissingFolders]));
1262
+ const linkMoved = () => [...entryLinks].some(([spelled, key]) => linkKey(spelled) !== key);
1263
+ const globsChanged = () => [...entryGlobs].some(([id, scope]) => readScope(scope).listing !== globStates.get(id)?.listing);
1264
+ const foldersChanged = () => [...entryFolders.values()].some(({ path, state }) => folderState(path) !== state);
1265
+ const inWatchedFolder = (path) => {
1266
+ if (entryPath === void 0 || entryFolders.size === 0) return false;
1267
+ const folders = [...entryFolders.keys()];
1268
+ return require_watch_targets.keyWithin(require_watch_targets.fileKey(path), folders) || require_watch_targets.keyWithin(require_watch_targets.fileKey((0, node_path.dirname)(path)), folders);
1269
+ };
1270
+ const inGlobScope = (path, isDir) => [...entryGlobs].some(([id, scope]) => scopeAdmits(scope, path, isDir, globStates.get(id)?.linked ?? []));
1271
+ const entryWatchTargets = () => new Map([...entryFiles, ...tracked(scopeWatchTargets(entryGlobs.values()))]);
1272
+ const watchEntryFiles = (files) => {
1273
+ const added = [...files].filter(([key]) => !require_watch_targets.keyWithin(key, [root]) && !require_watch_targets.keyWithin(key, inputKeys()) && !watchedForEntry.has(key));
1274
+ const dropped = [...watchedForEntry].filter(([key]) => !files.has(key));
1275
+ for (const [key] of dropped) watchedForEntry.delete(key);
1276
+ for (const [key, path] of added) watchedForEntry.set(key, path);
1277
+ if (dropped.length > 0) server.watcher.unwatch(dropped.map(([, path]) => path));
1278
+ if (added.length > 0) {
1279
+ server.watcher.add(added.map(([, path]) => path));
1280
+ recheckOnceWatched(new Map(added)).catch(keepQueueAlive);
1281
+ }
1282
+ };
1283
+ const isListed = (path) => {
1284
+ return server.watcher.getWatched()[(0, node_path.dirname)(path)]?.includes((0, node_path.basename)(path)) ?? false;
1285
+ };
1286
+ const recheckOnceWatched = async (files) => {
1287
+ if (config.server.watch === null) return;
1288
+ const deadline = Date.now() + WATCH_LISTED_TIMEOUT_MS;
1289
+ while (!closed && Date.now() < deadline && ![...files.values()].every(isListed)) await delay(DEBOUNCE_MS);
1290
+ await delay(WATCH_START_MS);
1291
+ if (closed) return;
1292
+ queue = queue.then(() => {
1293
+ const own = new Map([...files].filter(([key]) => entryFiles.has(key)));
1294
+ const now = fileStates(own);
1295
+ const then = new Map([...entryStates].filter(([key]) => own.has(key)));
1296
+ for (const key of filesChanged(then, now)) if (watchedForEntry.has(key)) noteChange(key);
1297
+ const [folder] = [...files.keys()].filter((key) => !own.has(key) && watchedForEntry.has(key));
1298
+ if (folder !== void 0 && globsChanged()) noteChange(folder);
1299
+ const [watched] = [...files.keys()].filter((key) => entryFolders.has(key) && watchedForEntry.has(key));
1300
+ if (watched !== void 0 && foldersChanged()) noteChange(watched);
1301
+ }).catch(keepQueueAlive);
1302
+ await queue;
1303
+ };
1304
+ const bundle = async () => {
1305
+ if (entryPath === void 0) return;
1306
+ entryStale = true;
1307
+ const spelled = /* @__PURE__ */ new Map();
1308
+ const authoredPaths = /* @__PURE__ */ new Set();
1309
+ const watchFiles = /* @__PURE__ */ new Set();
1310
+ const noteSpelling = (authored) => {
1311
+ authoredPaths.add(authored);
1312
+ const key = linkKey(authored);
1313
+ if (key !== void 0 && !spelled.has(authored)) spelled.set(authored, key);
1314
+ };
1315
+ noteSpelling(entryPath);
1316
+ if (entry === void 0) {
1317
+ entryFiles = tracked([entryPath]);
1318
+ entryStates = fileStates(entryFiles);
1319
+ watchEntryFiles(entryWatchTargets());
1320
+ }
1321
+ const globs = /* @__PURE__ */ new Map();
1322
+ const globsRead = /* @__PURE__ */ new Map();
1323
+ const globsBefore = new Map([...entryGlobs].map(([id, scope]) => [id, readScope(scope)]));
1324
+ const folders = /* @__PURE__ */ new Map();
1325
+ const before = fileStates(entryFiles);
1326
+ const observed = /* @__PURE__ */ new Map();
1327
+ const loaded = /* @__PURE__ */ new Map();
1328
+ const reads = /* @__PURE__ */ new Map();
1329
+ let next;
1330
+ try {
1331
+ next = await bundleEntry(config, entryPath, "serve", options.outputFilename, {
1332
+ watchFiles,
1333
+ onWatchFile: (file) => {
1334
+ const key = require_watch_targets.fileKey(file);
1335
+ if (folders.has(key)) return;
1336
+ const path = require_watch_targets.canonicalPath(file);
1337
+ const state = folderState(path);
1338
+ if (state !== void 0) folders.set(key, {
1339
+ path,
1340
+ state
1341
+ });
1342
+ },
1343
+ onLoad: (file) => {
1344
+ const key = require_watch_targets.fileKey(file);
1345
+ const read = readsModules ? fileRead(require_watch_targets.canonicalPath(file)) : void 0;
1346
+ const state = read?.state ?? fileState(require_watch_targets.canonicalPath(file));
1347
+ if (state !== void 0 && !observed.has(key)) observed.set(key, state);
1348
+ if (read !== void 0 && !reads.has(key)) reads.set(key, read);
1349
+ if (!loaded.has(key)) loaded.set(key, require_watch_targets.canonicalPath(file));
1350
+ },
1351
+ onResolve: noteSpelling,
1352
+ onGlob: (scope) => {
1353
+ const id = scopeId(scope);
1354
+ if (globs.has(id)) return;
1355
+ globs.set(id, scope);
1356
+ globsRead.set(id, readScope(scope));
1357
+ }
1358
+ });
1359
+ } catch (error) {
1360
+ const missing = require_watch_targets.missingImportFolders(authoredPaths);
1361
+ entryMissingFolders = missing.map(require_watch_targets.fileKey);
1362
+ entryFiles = new Map([
1363
+ ...entryFiles,
1364
+ ...loaded,
1365
+ ...tracked(watchFiles),
1366
+ ...tracked(missing)
1367
+ ]);
1368
+ entryStates = settledStates(entryFiles, observed, before);
1369
+ entryLinks = new Map([...entryLinks, ...spelled]);
1370
+ entryGlobs = new Map([...entryGlobs, ...globs]);
1371
+ globStates = new Map([...globsBefore, ...globsRead]);
1372
+ entryFolders = new Map([...entryFolders, ...folders]);
1373
+ watchEntryFiles(entryWatchTargets());
1374
+ throw error;
1375
+ }
1376
+ entry = next;
1377
+ entryStale = false;
1378
+ entryFiles = tracked(next.files);
1379
+ entryStates = settledStates(entryFiles, observed, before);
1380
+ entryLinks = spelled;
1381
+ entryMissingFolders = [];
1382
+ entryGlobs = globs;
1383
+ globStates = globsRead;
1384
+ entryFolders = folders;
1385
+ entryReads = reads;
1386
+ watchEntryFiles(entryWatchTargets());
1387
+ };
1388
+ const alreadyRead = (key) => {
1389
+ const read = entryReads.get(key);
1390
+ const path = entryFiles.get(key);
1391
+ if (entryStale || require_watch_targets.keyWithin(key, inputKeys())) return false;
1392
+ if (read === void 0 && !entryFiles.has(key) && require_watch_targets.keyWithin(key, [...entryFolders.keys()])) return !foldersChanged();
1393
+ if (read === void 0 || path === void 0) return false;
1394
+ const now = fileRead(path);
1395
+ return now?.state === read.state && now.hash === read.hash;
1396
+ };
1397
+ const rebuild = async (changed, initial = false) => {
1398
+ if (closed) return;
1399
+ try {
1400
+ const unread = [...changed].filter((key) => !alreadyRead(key));
1401
+ const regrouped = linkMoved() || globsChanged() || foldersChanged();
1402
+ if (changed.size > 0 && unread.length === 0 && !regrouped) return;
1403
+ outputs = dev.outputs(config);
1404
+ output = require_diagnostic_text.outputPaths(outputs);
1405
+ compiledInputs = storyInputFiles(true);
1406
+ for (const key of [...cache.keys()]) if (changed.has(require_watch_targets.fileKey(key))) cache.delete(key);
1407
+ if (entryStale || regrouped || unread.some(touchesEntry)) await bundle();
1408
+ const story = await require_watch_targets.compileStory(options.compile(entrySources(entry)), outputs, cache);
1409
+ for (const warning of story.warnings) config.logger.warn(`[twee-ts] ${warning}`);
1410
+ html = outputMode === "html" ? dev.injectClient(story.output, base) : story.output;
1411
+ servedAssets = entry?.assets;
1412
+ lastError = void 0;
1413
+ if (!initial) server.ws.send(reloadPayload(base, options.outputFilename));
1414
+ } catch (e) {
1415
+ lastError = toOverlayError(e);
1416
+ config.logger.error(`[twee-ts] ${lastError.message}`);
1417
+ server.ws.send({
1418
+ type: "error",
1419
+ err: lastError
1420
+ });
1421
+ }
1422
+ };
1423
+ const keepQueueAlive = (e) => {
1424
+ try {
1425
+ config.logger.error(`[twee-ts] ${require_watch_targets.fatalError(e).message}`);
1426
+ } catch {}
1427
+ };
1428
+ const noteChange = (changed) => {
1429
+ if (closed) return;
1430
+ pending.add(changed);
1431
+ clearTimeout(timer);
1432
+ timer = setTimeout(() => {
1433
+ const files = pending;
1434
+ pending = /* @__PURE__ */ new Set();
1435
+ queue = queue.then(() => rebuild(files)).catch(keepQueueAlive);
1436
+ }, DEBOUNCE_MS);
1437
+ };
1438
+ await rebuild(/* @__PURE__ */ new Set(), true);
1439
+ server.watcher.on("all", (event, path) => {
1440
+ if (require_watch_targets.isViteConfigTemp(path)) return;
1441
+ const changed = require_watch_targets.fileKey(path);
1442
+ const isDir = event === "addDir" || event === "unlinkDir";
1443
+ const regrouped = event !== "change" && inGlobScope(path, isDir) || inWatchedFolder(path) || isDir && entryPath !== void 0 && entryFiles.has(changed);
1444
+ if (event !== "add" && event !== "change" && event !== "unlink" && !regrouped) return;
1445
+ if (!regrouped && (!require_watch_targets.keyWithin(changed, inputKeys()) || excluded(path)) && !touchesEntry(changed) && !linkMoved()) return;
1446
+ noteChange(changed);
1447
+ });
1448
+ const catchUp = () => {
1449
+ catchingUp ??= (async () => {
1450
+ for (let waited = queue;; waited = queue) {
1451
+ await waited;
1452
+ if (queue === waited) break;
1453
+ }
1454
+ const changed = filesChanged(compiledInputs, storyInputFiles(false));
1455
+ for (const file of filesChanged(entryStates, fileStates(entryFiles))) changed.add(file);
1456
+ if (changed.size === 0 && pending.size === 0 && !linkMoved() && !globsChanged() && !foldersChanged()) return;
1457
+ for (const file of pending) changed.add(file);
1458
+ pending = /* @__PURE__ */ new Set();
1459
+ clearTimeout(timer);
1460
+ queue = queue.then(() => rebuild(changed)).catch(keepQueueAlive);
1461
+ await queue;
1462
+ })().finally(() => {
1463
+ catchingUp = void 0;
1464
+ });
1465
+ return catchingUp;
1466
+ };
1467
+ server.ws.on("connection", () => {
1468
+ if (lastError) server.ws.send({
1469
+ type: "error",
1470
+ err: lastError
1471
+ });
1472
+ });
1473
+ const requestUrls = /* @__PURE__ */ new WeakMap();
1474
+ server.middlewares.use((req, _res, next) => {
1475
+ requestUrls.set(req, String(req.url));
1476
+ next();
1477
+ });
1478
+ const send = (req, res, type, body) => {
1479
+ for (const [name, value] of Object.entries({ ...config.server.headers })) if (typeof value === "string" || typeof value === "number") res.setHeader(name, value);
1480
+ else if (Array.isArray(value)) res.setHeader(name, value.map(String));
1481
+ res.statusCode = 200;
1482
+ res.setHeader("Content-Type", type);
1483
+ res.setHeader("Content-Length", typeof body === "string" ? Buffer.byteLength(body) : body.byteLength);
1484
+ res.setHeader("Cache-Control", "no-cache");
1485
+ res.end(req.method === "HEAD" ? void 0 : body);
1486
+ };
1487
+ return (req, res, next) => {
1488
+ if (req.method !== "GET" && req.method !== "HEAD") {
1489
+ next();
1490
+ return;
1491
+ }
1492
+ const path = pathBelowBase(String(requestUrls.get(req)), base);
1493
+ if (path === void 0) {
1494
+ next();
1495
+ return;
1496
+ }
1497
+ const others = [...instances].filter((other) => other !== self);
1498
+ const mine = paths.includes(path);
1499
+ if ((mine || servedAssets?.has(path) === true) && others.some((other) => other.paths.includes(path) || mine && other.assets()?.has(path) === true)) {
1500
+ fail(res, storyCollisionMessage(path));
1501
+ return;
1502
+ }
1503
+ if (paths.includes(path)) {
1504
+ catchUp().then(() => {
1505
+ if (html) send(req, res, OUTPUT_MEDIA_TYPES[outputMode], html);
1506
+ else send(req, res, "text/html; charset=utf-8", waitingPage(base));
1507
+ }, next);
1508
+ return;
1509
+ }
1510
+ const asset = servedAssets?.get(path);
1511
+ if (asset === void 0) {
1512
+ next();
1513
+ return;
1514
+ }
1515
+ for (const other of others) {
1516
+ const theirs = other.assets()?.get(path);
1517
+ if (theirs !== void 0 && !sameBytes(theirs, asset)) {
1518
+ fail(res, entryCollisionMessage(path));
1519
+ return;
1520
+ }
1521
+ }
1522
+ send(req, res, assetMediaType(path), asset);
1523
+ };
1524
+ }
1525
+ //#endregion
1526
+ //#region src/plugins/vite.ts
1527
+ /**
1528
+ * Ready-made Vite plugin for twee-ts.
1529
+ *
1530
+ * Compiles Twee sources into the story HTML. With `entry`, Vite also bundles a
1531
+ * script, and the CSS it imports, into the story as its Story JavaScript and
1532
+ * Story Stylesheet.
1533
+ *
1534
+ * In dev the compiled HTML is served at the base URL with Vite's client added.
1535
+ * Every change to a source, the head file, a module or a file the entry was
1536
+ * bundled from recompiles it and reloads the page, and errors appear in Vite's
1537
+ * overlay (see vite-dev.ts). A build emits the story with the configured file
1538
+ * name, in the client environment only.
1539
+ */
1540
+ /**
1541
+ * A build input that stands in when the user's config names none, so Vite does
1542
+ * not look for an index.html (whose page would replace the story). Its empty
1543
+ * chunk is dropped from the output.
1544
+ */
1545
+ const EMPTY_INPUT = "virtual:twee-ts-empty-input";
1546
+ const RESOLVED_EMPTY_INPUT = "\0" + EMPTY_INPUT;
1547
+ /** The environment the story belongs to: the browser's. */
1548
+ const CLIENT = "client";
1549
+ /** Vite's plugin that bundles workers, and keeps their bundles between the builds of `vite build --watch`. */
1550
+ const VITE_WORKER_PLUGIN = "vite:worker";
1551
+ /** The plugin instances created so far, which numbers each instance's entry input. */
1552
+ let instances = 0;
1553
+ /** The bundler options of build settings: `rolldownOptions`, or the older name Vite 8 still reads. */
1554
+ function bundlerOptionsOf(build) {
1555
+ if (!require_diagnostic_text.isRecord(build)) return void 0;
1556
+ const options = build["rolldownOptions"] ?? build["rollupOptions"];
1557
+ return require_diagnostic_text.isRecord(options) ? options : void 0;
1558
+ }
1559
+ /**
1560
+ * Whether a config (or an environment's options) names what the build builds:
1561
+ * a bundler input, Vite 8's top-level `input`, a library entry or an SSR entry.
1562
+ */
1563
+ function namesInput(config) {
1564
+ const build = Reflect.get(config, "build");
1565
+ return Reflect.get(config, "input") !== void 0 || bundlerOptionsOf(build)?.["input"] !== void 0 || require_diagnostic_text.isRecord(build) && (Boolean(build["lib"]) || Boolean(build["ssr"]));
1566
+ }
1567
+ /** Build settings naming one input. */
1568
+ function inputOnly(input) {
1569
+ return { rolldownOptions: { input } };
1570
+ }
1571
+ /**
1572
+ * Where a build with this config writes: each output's folder, the bundler's
1573
+ * `output.dir` or else `build.outDir`, or the bundler's `output.file`. Vite
1574
+ * resolves an `output.dir` against the root when it empties the folder and
1575
+ * copies the public files into it, while the bundler resolves it against the
1576
+ * working directory when it writes; both count.
1577
+ */
1578
+ function configOutputLocations(config) {
1579
+ const outDir = (0, node_path.resolve)(config.root, config.build.outDir);
1580
+ const option = bundlerOptionsOf(config.build)?.["output"];
1581
+ return (option === void 0 ? [{}] : Array.isArray(option) ? option : [option]).flatMap((output) => {
1582
+ const [location] = require_watch_targets.outputLocations(output);
1583
+ if (location === void 0) return [{ dir: outDir }];
1584
+ if (location.dir === void 0) return [location];
1585
+ return [{ dir: (0, node_path.resolve)(location.dir) }, { dir: (0, node_path.resolve)(config.root, location.dir) }];
1586
+ });
1587
+ }
1588
+ /**
1589
+ * Every path a build with this config writes, as far as the config tells: each
1590
+ * output's folder (see configOutputLocations), the story in it, and the copies
1591
+ * of the public files Vite puts there. The plugin adds the files each bundle
1592
+ * writes as it builds (see createOutputRecord).
1593
+ */
1594
+ function configOutputs(config, outputFilename) {
1595
+ const record = require_watch_targets.createOutputRecord(outputFilename);
1596
+ const publicFiles = config.build.copyPublicDir && config.publicDir ? require_diagnostic_text.getFilenames([config.publicDir]).filenames.map((file) => (0, node_path.relative)(config.publicDir, (0, node_path.resolve)(file))) : [];
1597
+ for (const location of configOutputLocations(config)) {
1598
+ record.addLocation(location);
1599
+ record.addFiles(location, publicFiles);
1600
+ }
1601
+ return record.outputs();
1602
+ }
1603
+ /** Both builds' outputs together. */
1604
+ function mergeOutputs(a, b) {
1605
+ return {
1606
+ files: [...a.files, ...b.files],
1607
+ stories: [...a.stories ?? [], ...b.stories ?? []],
1608
+ dirs: [...a.dirs, ...b.dirs]
1609
+ };
1610
+ }
1611
+ /** Adds Vite's client to the page, first in its head, so reloads and the error overlay reach it. */
1612
+ function injectViteClient(html, base) {
1613
+ return require_diagnostic_text.insertViteClient(html, `${base}@vite/client`);
1614
+ }
1615
+ /**
1616
+ * What `vite build --watch` is told to watch for the entry's globs: each glob's folder (or the nearest one above it
1617
+ * that exists), by real path, as the bundler's watcher reports it. A folder that holds a build output is left out,
1618
+ * since the watcher would report what the build writes there and rebuild for ever: a file added to it is bundled
1619
+ * with the next build something else starts.
1620
+ */
1621
+ function globWatchTargets(scopes, outputs) {
1622
+ return scopeWatchTargets(scopes).map(require_watch_targets.canonicalPath).filter((folder) => !outputs.holds(folder));
1623
+ }
1624
+ /** Whether a resolved config is a build of the client, the only build that carries the story. */
1625
+ function isStoryBuild(config) {
1626
+ return config.command === "build" && !config.build.ssr;
1627
+ }
1628
+ /** An import as written: its importer and its source. */
1629
+ function importKey(importer, source) {
1630
+ return `${importer ?? ""}\0${source}`;
1631
+ }
1632
+ /** Whether a plugin is Vite's own (its resolver, alias and the rest) or one of twee-ts's. */
1633
+ function isOwnPlugin(plugin) {
1634
+ return plugin.name === "alias" || plugin.name.startsWith("vite:") || plugin.name.startsWith("builtin:") || plugin.name === "twee-ts" || plugin.name.startsWith(`twee-ts:`);
1635
+ }
1636
+ /**
1637
+ * A copy of `plugin` (see pluginWith) with a `resolveId` hook that passes each import it resolves (see importKey) to
1638
+ * `claim`. A plugin without a `resolveId` hook in JavaScript is returned as it is.
1639
+ */
1640
+ function claimingPlugin(plugin, claim) {
1641
+ const hook = plugin.resolveId;
1642
+ const handler = typeof hook === "function" ? hook : hook?.handler;
1643
+ if (typeof handler !== "function") return plugin;
1644
+ const claiming = async function(...args) {
1645
+ const result = await Reflect.apply(handler, this, args);
1646
+ const [source, importer] = args;
1647
+ if (result !== null && result !== void 0 && typeof source === "string") claim(importKey(typeof importer === "string" ? importer : void 0, source));
1648
+ return result;
1649
+ };
1650
+ return pluginWith(plugin, /* @__PURE__ */ new Map([["resolveId", typeof hook === "function" ? claiming : {
1651
+ ...hook,
1652
+ handler: claiming
1653
+ }]]));
1654
+ }
1655
+ function tweeTsPlugin(options) {
1656
+ require_watch_targets.checkViteVersion(vite.version);
1657
+ const resolved = require_watch_targets.resolvePluginOptions("vite", options);
1658
+ const { outputFilename, entry: entryPath } = resolved;
1659
+ instances += 1;
1660
+ const entryInput = `${ENTRY_INPUT_NAME}-${instances}`;
1661
+ const cache = /* @__PURE__ */ new Map();
1662
+ const ownBuilds = /* @__PURE__ */ new WeakMap();
1663
+ const record = require_watch_targets.createOutputRecord(outputFilename);
1664
+ const allOutputs = (config) => mergeOutputs(configOutputs(config, outputFilename), record.outputs());
1665
+ /**
1666
+ * The build settings this instance adds to a client build: none when the
1667
+ * user's config names an input (the entry is then bundled by a build of its
1668
+ * own); else, with an entry, the entry as the only input (unless the user's
1669
+ * output is an array, which the entry's IIFE can't use); else the stand-in
1670
+ * input.
1671
+ */
1672
+ const inputSettings = (config) => {
1673
+ if (namesInput(config)) return void 0;
1674
+ if (entryPath === void 0) return { build: inputOnly(EMPTY_INPUT) };
1675
+ const output = config.build?.rolldownOptions?.output;
1676
+ if (Array.isArray(output)) return { build: inputOnly(EMPTY_INPUT) };
1677
+ return entryInputSettings(entryInput, entryPath, output, outputFilename);
1678
+ };
1679
+ /** Whether this instance's entry is an input of the build with this config. */
1680
+ const bundlesEntryInside = (config) => {
1681
+ const input = bundlerOptionsOf(config.build)?.["input"];
1682
+ return require_diagnostic_text.isRecord(input) && Object.hasOwn(input, entryInput);
1683
+ };
1684
+ /** The resolved config of the build a hook runs in: its environment's, so builds sharing the instance keep apart. */
1685
+ const configOf = (context) => context.environment.config;
1686
+ let watchOutputs = /* @__PURE__ */ new WeakMap();
1687
+ const keptTargets = /* @__PURE__ */ new Map();
1688
+ const claimed = /* @__PURE__ */ new Set();
1689
+ const outputsOf = (config) => {
1690
+ const known = watchOutputs.get(config);
1691
+ if (known !== void 0) return known;
1692
+ const outputs = require_diagnostic_text.outputPaths(allOutputs(config));
1693
+ watchOutputs.set(config, outputs);
1694
+ return outputs;
1695
+ };
1696
+ /**
1697
+ * `vite build --watch` with the entry bundled inside the build: the bundler watches each module by its real path,
1698
+ * so the links an import of the entry's graph goes through (or the folder where an import that reaches nothing
1699
+ * would be created) are registered here, as the import is resolved (see importWatchTargets).
1700
+ */
1701
+ const registerImport = async (context, source, importer) => {
1702
+ const config = configOf(context);
1703
+ const { spellings, kept } = await importSpellings(context, source, importer, config.root);
1704
+ for (const target of require_watch_targets.importWatchTargets(spellings, outputsOf(config))) context.addWatchFile(target);
1705
+ return kept;
1706
+ };
1707
+ /** Likewise, each glob's folder: Vite's import-glob reads the files a glob matches, and registers no folder. */
1708
+ const registerGlobs = async (context, code, id) => {
1709
+ const config = configOf(context);
1710
+ const scopes = await moduleGlobScopes(context, code, id, config.root);
1711
+ for (const target of globWatchTargets(scopes, outputsOf(config))) context.addWatchFile(target);
1712
+ return scopes;
1713
+ };
1714
+ const workerGlobs = /* @__PURE__ */ new Map();
1715
+ let warnedWorkerCache = false;
1716
+ /**
1717
+ * `vite build --watch` keeps each worker's bundle until a file it read changes, and a file added to the folder of
1718
+ * one of its globs is none of those. When `file` is added to (or removed from) the scope of a worker's glob, the
1719
+ * bundles of the workers whose modules call the glob are let go, through Vite's own worker plugin, as an edit of
1720
+ * those modules would; the build that follows bundles them again (#424).
1721
+ */
1722
+ const releaseWorkerGlobs = async (context, file) => {
1723
+ const importers = [...workerGlobs.values()].filter(({ scope }) => {
1724
+ const { linked } = readScope(scope);
1725
+ return scopeAdmits(scope, file, false, linked) || scopeAdmits(scope, file, true, linked);
1726
+ }).flatMap(({ importers: modules }) => [...modules]);
1727
+ if (importers.length === 0) return;
1728
+ const hook = configOf(context).plugins.find((plugin) => plugin.name === VITE_WORKER_PLUGIN)?.watchChange;
1729
+ const handler = typeof hook === "function" ? hook : hook?.handler;
1730
+ if (typeof handler !== "function") {
1731
+ if (!warnedWorkerCache) {
1732
+ warnedWorkerCache = true;
1733
+ configOf(context).logger.warn(`[twee-ts] This Vite version has no ${VITE_WORKER_PLUGIN} plugin with a watchChange hook, through which twee-ts has Vite bundle a worker again: a file added to the folder of a worker's import.meta.glob() is bundled when one of the worker's modules changes.`);
1734
+ }
1735
+ return;
1736
+ }
1737
+ for (const module of importers) await Reflect.apply(handler, context, [module, { event: "update" }]);
1738
+ };
1739
+ /**
1740
+ * The same registrations in each build of a worker the entry starts, which Vite bundles nested in the entry's
1741
+ * build with the plugins of `worker.plugins` (#424). What a worker build registers is watched by the outer build.
1742
+ */
1743
+ const workerWatcher = () => ({
1744
+ name: `${PLUGIN_NAME}:worker-watch`,
1745
+ resolveId: {
1746
+ order: "pre",
1747
+ async handler(source, importer) {
1748
+ await registerImport(this, source, importer);
1749
+ }
1750
+ },
1751
+ transform: {
1752
+ filter: { code: "import.meta.glob" },
1753
+ async handler(code, id) {
1754
+ for (const scope of await registerGlobs(this, code, id)) {
1755
+ const key = scopeId(scope);
1756
+ const known = workerGlobs.get(key) ?? {
1757
+ scope,
1758
+ importers: /* @__PURE__ */ new Set()
1759
+ };
1760
+ known.importers.add(fileOfId(id));
1761
+ workerGlobs.set(key, known);
1762
+ }
1763
+ }
1764
+ }
1765
+ });
1766
+ return {
1767
+ name: PLUGIN_NAME,
1768
+ applyToEnvironment: (environment) => environment.name === CLIENT,
1769
+ config: {
1770
+ order: "post",
1771
+ handler(config, env) {
1772
+ const client = config.environments?.[CLIENT] ?? {};
1773
+ if (env.command !== "build" || entryPath === void 0 || namesInput(config) || namesInput(client)) return;
1774
+ const renderBuiltUrl = entryRenderBuiltUrl(config.base, outputFilename, config.experimental?.renderBuiltUrl);
1775
+ if (renderBuiltUrl !== void 0) config.experimental = {
1776
+ ...config.experimental,
1777
+ renderBuiltUrl
1778
+ };
1779
+ if (config.build?.watch) config.worker = withWorkerPlugin(config.worker, workerWatcher);
1780
+ }
1781
+ },
1782
+ configEnvironment(name, environmentConfig, env) {
1783
+ if (name !== CLIENT || env.command !== "build") return void 0;
1784
+ return inputSettings(environmentConfig);
1785
+ },
1786
+ configResolved(config) {
1787
+ if (entryPath === void 0) return;
1788
+ if (config.command === "build" && config.build.watch && bundlesEntryInside(config)) config.plugins.forEach((plugin, at) => {
1789
+ if (!isOwnPlugin(plugin)) Reflect.set(config.plugins, at, claimingPlugin(plugin, (key) => claimed.add(key)));
1790
+ });
1791
+ if (resolved.inputs.some((input) => require_diagnostic_text.isSameOrInside(entryPath, input))) config.logger.warn(`[twee-ts] The entry ${require_watch_targets.toPosix(entryPath)} is inside the story sources (${resolved.sources.join(", ")}). twee-ts also loads the .js and .css files it finds in source folders, unbundled, as Story JavaScript and Story Stylesheet; keep the entry's folder out of \`sources\`.`);
1792
+ },
1793
+ async buildStart() {
1794
+ const config = configOf(this);
1795
+ if (!isStoryBuild(config)) return;
1796
+ watchOutputs = /* @__PURE__ */ new WeakMap();
1797
+ forgetKeptResolver(config);
1798
+ if (this.meta.watchMode) {
1799
+ const outputs = require_diagnostic_text.outputPaths(allOutputs(config));
1800
+ for (const target of require_watch_targets.watchTargets(resolved.inputs, resolved.excluded, outputs, "real")) this.addWatchFile(target);
1801
+ }
1802
+ if (entryPath === void 0 || bundlesEntryInside(config)) return;
1803
+ const watched = /* @__PURE__ */ new Set();
1804
+ const authored = /* @__PURE__ */ new Set([entryPath]);
1805
+ const globs = [];
1806
+ const registerEntryInputs = () => {
1807
+ if (!this.meta.watchMode) return;
1808
+ for (const file of watched) this.addWatchFile(require_watch_targets.canonicalPath(file));
1809
+ const outputs = require_diagnostic_text.outputPaths(allOutputs(config));
1810
+ for (const target of require_watch_targets.importWatchTargets(authored, outputs)) this.addWatchFile(target);
1811
+ for (const target of globWatchTargets(globs, outputs)) this.addWatchFile(target);
1812
+ };
1813
+ watched.add(entryPath);
1814
+ let bundle;
1815
+ try {
1816
+ bundle = await bundleEntry(config, entryPath, "build", outputFilename, {
1817
+ watchFiles: watched,
1818
+ onLoad: (file) => watched.add(file),
1819
+ onResolve: (spelling) => authored.add(spelling),
1820
+ onGlob: (scope) => globs.push(scope)
1821
+ });
1822
+ } catch (e) {
1823
+ registerEntryInputs();
1824
+ return this.error(require_watch_targets.fatalError(e));
1825
+ }
1826
+ ownBuilds.set(this.environment, bundle);
1827
+ for (const file of bundle.files) watched.add(file);
1828
+ registerEntryInputs();
1829
+ },
1830
+ async watchChange(id, change) {
1831
+ const changed = require_watch_targets.fileKey(id);
1832
+ for (const key of [...cache.keys()]) if (require_watch_targets.fileKey(key) === changed) cache.delete(key);
1833
+ if (change.event !== "update") await releaseWorkerGlobs(this, id);
1834
+ },
1835
+ transform: {
1836
+ filter: { code: "import.meta.glob" },
1837
+ async handler(code, id) {
1838
+ const config = configOf(this);
1839
+ if (!this.meta.watchMode || !isStoryBuild(config) || !bundlesEntryInside(config)) return;
1840
+ await registerGlobs(this, code, id);
1841
+ }
1842
+ },
1843
+ resolveId: {
1844
+ order: "pre",
1845
+ async handler(id, importer, options) {
1846
+ if (id === EMPTY_INPUT) return RESOLVED_EMPTY_INPUT;
1847
+ const config = configOf(this);
1848
+ if (!this.meta.watchMode || !isStoryBuild(config) || !bundlesEntryInside(config)) return void 0;
1849
+ const kept = await registerImport(this, id, importer);
1850
+ if (kept === void 0) return void 0;
1851
+ const now = require_watch_targets.canonicalPath(kept);
1852
+ const written = importKey(importer, id);
1853
+ const targets = keptTargets.get(written) ?? /* @__PURE__ */ new Set();
1854
+ keptTargets.set(written, targets.add(now));
1855
+ if (targets.size < 2) return void 0;
1856
+ claimed.delete(written);
1857
+ const resolved = await this.resolve(id, importer, {
1858
+ ...options,
1859
+ skipSelf: true
1860
+ });
1861
+ if (claimed.has(written)) return void 0;
1862
+ if (resolved === null || resolved.external === true || resolved.external === "absolute") return void 0;
1863
+ const file = require_watch_targets.canonicalPath(fileOfId(resolved.id));
1864
+ if (file === now || !targets.has(file)) return void 0;
1865
+ return {
1866
+ ...resolved,
1867
+ id: `${now}${/[?#].*$/s.exec(resolved.id)?.[0] ?? ""}`
1868
+ };
1869
+ }
1870
+ },
1871
+ load(id) {
1872
+ return id === RESOLVED_EMPTY_INPUT ? "globalThis.tweeTsEmptyInput = true;" : void 0;
1873
+ },
1874
+ generateBundle: {
1875
+ order: "post",
1876
+ async handler(outputOptions, bundle) {
1877
+ const config = configOf(this);
1878
+ if (!isStoryBuild(config)) return;
1879
+ for (const [fileName, item] of Object.entries(bundle)) if (item.type === "chunk" && item.facadeModuleId === RESOLVED_EMPTY_INPUT) removeFromBundle(bundle, fileName);
1880
+ if (Object.hasOwn(bundle, outputFilename)) return this.error(`twee-ts: the build already writes a file named ${outputFilename} (another input or plugin emits it); set the plugin's outputFilename to another name.`);
1881
+ let entry;
1882
+ if (entryPath !== void 0) {
1883
+ const items = bundle;
1884
+ entry = bundlesEntryInside(config) ? takeEntryFromBundle(items, entryInput) : ownBuilds.get(this.environment);
1885
+ if (!bundlesEntryInside(config)) for (const [fileName, source] of entry?.assets ?? []) {
1886
+ if (Object.hasOwn(bundle, fileName)) return this.error(entryCollisionMessage(fileName));
1887
+ this.emitFile({
1888
+ type: "asset",
1889
+ fileName,
1890
+ source
1891
+ });
1892
+ }
1893
+ }
1894
+ record.addLocation(outputOptions);
1895
+ record.addFiles(outputOptions, [...Object.keys(bundle), ...entry?.assets.keys() ?? []]);
1896
+ let story;
1897
+ try {
1898
+ story = await require_watch_targets.compileStory(resolved.compile(entrySources(entry)), allOutputs(config), cache, true);
1899
+ } catch (e) {
1900
+ return this.error(require_watch_targets.fatalError(e));
1901
+ }
1902
+ for (const warning of story.warnings) this.warn(warning);
1903
+ this.emitFile({
1904
+ type: "asset",
1905
+ fileName: outputFilename,
1906
+ source: story.output
1907
+ });
1908
+ }
1909
+ },
1910
+ async configureServer(server) {
1911
+ const route = await setUpDevStory(server, {
1912
+ options: resolved,
1913
+ cache,
1914
+ outputs: allOutputs,
1915
+ injectClient: injectViteClient
1916
+ });
1917
+ return () => {
1918
+ installStoryRoute(server.middlewares, route);
1919
+ };
1920
+ }
1921
+ };
1922
+ }
1923
+ //#endregion
1924
+ exports.tweeTsPlugin = tweeTsPlugin;
1925
+
1926
+ //# sourceMappingURL=vite.cjs.map