@rohal12/twee-ts 1.16.1 → 1.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/bin/twee-ts.js +505 -207
- package/dist/bin/twee-ts.js.map +1 -1
- package/dist/index.cjs +513 -190
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +82 -11
- package/dist/index.d.ts +82 -11
- package/dist/index.js +513 -190
- package/dist/index.js.map +1 -1
- package/dist/{compiler-Bmpbelqj.js → paths-w4ulhCFb.js} +392 -162
- package/dist/paths-w4ulhCFb.js.map +1 -0
- package/dist/plugins/rollup.d.ts +25 -2
- package/dist/plugins/rollup.js +53 -12
- package/dist/plugins/rollup.js.map +1 -1
- package/dist/plugins/vite.js +37 -62
- package/dist/plugins/vite.js.map +1 -1
- package/package.json +2 -1
- package/dist/compiler-Bmpbelqj.js.map +0 -1
|
@@ -13,6 +13,19 @@ import { fileURLToPath } from "node:url";
|
|
|
13
13
|
function generateIFID() {
|
|
14
14
|
return randomUUID().toUpperCase();
|
|
15
15
|
}
|
|
16
|
+
/** Length of the `UUID://...//` wrapper around a 36-character UUID. */
|
|
17
|
+
const WRAPPED_LENGTH = 45;
|
|
18
|
+
/**
|
|
19
|
+
* Bring an IFID into the form the story model stores and every output writes: the uppercase
|
|
20
|
+
* bare UUID. A valid `UUID://...//` wrapped IFID loses its wrapper; outputs that need the wrapper
|
|
21
|
+
* (the Treaty of Babel comment) add exactly one. Twine 2 IFIDs use only capital letters, so the
|
|
22
|
+
* value is uppercased, as Tweego does. An invalid value is only uppercased, so that validation
|
|
23
|
+
* reports it as written.
|
|
24
|
+
*/
|
|
25
|
+
function normalizeIFID(value) {
|
|
26
|
+
const upper = value.toUpperCase();
|
|
27
|
+
return upper.length === WRAPPED_LENGTH && validateIFID(upper) === null ? upper.slice(7, 43) : upper;
|
|
28
|
+
}
|
|
16
29
|
/**
|
|
17
30
|
* Validate an IFID string.
|
|
18
31
|
* Accepts both bare UUIDs and UUID://...// wrapped format.
|
|
@@ -438,7 +451,7 @@ function unmarshalStoryData(story, json) {
|
|
|
438
451
|
}
|
|
439
452
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return "Cannot unmarshal \"StoryData\"; expected a JSON object";
|
|
440
453
|
const data = raw;
|
|
441
|
-
if (typeof data.ifid === "string" && data.ifid) story.ifid = data.ifid
|
|
454
|
+
if (typeof data.ifid === "string" && data.ifid) story.ifid = normalizeIFID(data.ifid);
|
|
442
455
|
if (typeof data.format === "string" && data.format) story.twine2.format = data.format;
|
|
443
456
|
if (typeof data["format-version"] === "string" && data["format-version"]) story.twine2.formatVersion = data["format-version"];
|
|
444
457
|
if (Array.isArray(data.options)) {
|
|
@@ -469,7 +482,7 @@ function unmarshalStorySettings(story, text, diagnostics) {
|
|
|
469
482
|
const val = line.slice(colonIdx + 1).trim().toLowerCase();
|
|
470
483
|
switch (key) {
|
|
471
484
|
case "ifid":
|
|
472
|
-
if (validateIFID(val) === null) story.legacyIFID = val
|
|
485
|
+
if (validateIFID(val) === null) story.legacyIFID = normalizeIFID(val);
|
|
473
486
|
obsolete.push("\"ifid\"");
|
|
474
487
|
continue;
|
|
475
488
|
case "zoom":
|
|
@@ -1018,7 +1031,7 @@ function isSafeSegment(segment) {
|
|
|
1018
1031
|
return segment !== "" && segment !== "." && segment !== ".." && !/[/\\\0]/.test(segment);
|
|
1019
1032
|
}
|
|
1020
1033
|
/** Whether absolute path `target` lies strictly inside absolute path `root`. */
|
|
1021
|
-
function isInside(root, target) {
|
|
1034
|
+
function isInside$1(root, target) {
|
|
1022
1035
|
const rel = relative(root, target);
|
|
1023
1036
|
return rel !== "" && !isAbsolute(rel) && rel.split(sep)[0] !== "..";
|
|
1024
1037
|
}
|
|
@@ -1028,7 +1041,7 @@ function cachedFormatDir(name, version) {
|
|
|
1028
1041
|
if (!isSafeSegment(version) || !parseSemver(version)) throw new Error(`Refusing to cache story format "${name}" with an unsafe version: ${JSON.stringify(version)}`);
|
|
1029
1042
|
const root = resolve(getCacheDir());
|
|
1030
1043
|
const dir = resolve(root, name, version);
|
|
1031
|
-
if (!isInside(root, dir)) throw new Error(`Refusing to cache a story format outside the cache directory ${root}: ${dir}`);
|
|
1044
|
+
if (!isInside$1(root, dir)) throw new Error(`Refusing to cache a story format outside the cache directory ${root}: ${dir}`);
|
|
1032
1045
|
return dir;
|
|
1033
1046
|
}
|
|
1034
1047
|
/** Create `dir` as a plain directory, or check that it already is one (a symlink could lead out of the cache). */
|
|
@@ -4419,14 +4432,16 @@ function parseDocument(data, options) {
|
|
|
4419
4432
|
*/
|
|
4420
4433
|
/**
|
|
4421
4434
|
* Parse a Twine 2 or Twine 1 compiled HTML file back into a Story model.
|
|
4435
|
+
* Tweego always trims passage text; here `trim: false` keeps it exactly, as for Twee sources.
|
|
4422
4436
|
*/
|
|
4423
|
-
function decompileHTML(html) {
|
|
4437
|
+
function decompileHTML(html, options = {}) {
|
|
4424
4438
|
const diagnostics = [];
|
|
4425
4439
|
const story = createStory();
|
|
4440
|
+
const passageText = options.trim === false ? (text) => text : (text) => text.trim();
|
|
4426
4441
|
const doc = parseDocument(html);
|
|
4427
4442
|
const twine2Data = findElement(doc, "tw-storydata");
|
|
4428
4443
|
if (twine2Data) {
|
|
4429
|
-
decompileTwine2(twine2Data, story, diagnostics);
|
|
4444
|
+
decompileTwine2(twine2Data, story, passageText, diagnostics);
|
|
4430
4445
|
return {
|
|
4431
4446
|
story,
|
|
4432
4447
|
diagnostics
|
|
@@ -4434,7 +4449,7 @@ function decompileHTML(html) {
|
|
|
4434
4449
|
}
|
|
4435
4450
|
const twine1Data = findElementByIdPattern(doc, /^store(?:-a|A)rea$/);
|
|
4436
4451
|
if (twine1Data) {
|
|
4437
|
-
decompileTwine1(twine1Data, story, diagnostics);
|
|
4452
|
+
decompileTwine1(twine1Data, story, passageText, diagnostics);
|
|
4438
4453
|
return {
|
|
4439
4454
|
story,
|
|
4440
4455
|
diagnostics
|
|
@@ -4449,7 +4464,7 @@ function decompileHTML(html) {
|
|
|
4449
4464
|
diagnostics
|
|
4450
4465
|
};
|
|
4451
4466
|
}
|
|
4452
|
-
function decompileTwine2(storyData, story, diagnostics) {
|
|
4467
|
+
function decompileTwine2(storyData, story, passageText, diagnostics) {
|
|
4453
4468
|
let startnode = 0;
|
|
4454
4469
|
const attrs = storyData.attribs ?? {};
|
|
4455
4470
|
if (attrs["name"]) story.name = attrs["name"];
|
|
@@ -4461,7 +4476,7 @@ function decompileTwine2(storyData, story, diagnostics) {
|
|
|
4461
4476
|
});
|
|
4462
4477
|
else startnode = parsed;
|
|
4463
4478
|
}
|
|
4464
|
-
if (attrs["ifid"]) story.ifid = attrs["ifid"]
|
|
4479
|
+
if (attrs["ifid"]) story.ifid = normalizeIFID(attrs["ifid"]);
|
|
4465
4480
|
if (attrs["zoom"]) {
|
|
4466
4481
|
const parsed = parseFloat(attrs["zoom"]);
|
|
4467
4482
|
if (Number.isNaN(parsed)) diagnostics.push({
|
|
@@ -4474,15 +4489,20 @@ function decompileTwine2(storyData, story, diagnostics) {
|
|
|
4474
4489
|
if (attrs["format"]) story.twine2.format = attrs["format"];
|
|
4475
4490
|
if (attrs["format-version"]) story.twine2.formatVersion = attrs["format-version"];
|
|
4476
4491
|
if (attrs["options"]) for (const opt of attrs["options"].split(/\s+/).filter((s) => s.length > 0)) story.twine2.options.set(opt, true);
|
|
4477
|
-
|
|
4492
|
+
const children = storyData.children ?? [];
|
|
4493
|
+
const takenNames = new Set(children.filter((node) => node.type === "tag" && node.name === "tw-passagedata").map(passageDataName));
|
|
4494
|
+
for (const node of children) {
|
|
4478
4495
|
if (node.type !== "tag" && node.type !== "style" && node.type !== "script") continue;
|
|
4479
4496
|
switch (node.name) {
|
|
4480
4497
|
case "style":
|
|
4481
4498
|
case "script": {
|
|
4482
|
-
const
|
|
4483
|
-
if (
|
|
4499
|
+
const content = getTextContent(node);
|
|
4500
|
+
if (content.trim().length === 0) continue;
|
|
4501
|
+
const text = passageText(content);
|
|
4502
|
+
const name = freeName(node.name === "style" ? "Story Stylesheet" : "Story JavaScript", takenNames);
|
|
4503
|
+
takenNames.add(name);
|
|
4484
4504
|
storyAdd(story, {
|
|
4485
|
-
name
|
|
4505
|
+
name,
|
|
4486
4506
|
tags: node.name === "style" ? ["stylesheet"] : ["script"],
|
|
4487
4507
|
text
|
|
4488
4508
|
}, diagnostics);
|
|
@@ -4498,7 +4518,7 @@ function decompileTwine2(storyData, story, diagnostics) {
|
|
|
4498
4518
|
case "tw-passagedata": {
|
|
4499
4519
|
let pid = 0;
|
|
4500
4520
|
const pAttrs = node.attribs ?? {};
|
|
4501
|
-
const name =
|
|
4521
|
+
const name = passageDataName(node);
|
|
4502
4522
|
const tags = pAttrs["tags"] ? pAttrs["tags"].split(/\s+/).filter((s) => s.length > 0) : [];
|
|
4503
4523
|
const metadata = {};
|
|
4504
4524
|
if (pAttrs["pid"]) {
|
|
@@ -4515,7 +4535,7 @@ function decompileTwine2(storyData, story, diagnostics) {
|
|
|
4515
4535
|
const passage = {
|
|
4516
4536
|
name,
|
|
4517
4537
|
tags,
|
|
4518
|
-
text: getTextContent(node)
|
|
4538
|
+
text: passageText(getTextContent(node))
|
|
4519
4539
|
};
|
|
4520
4540
|
if (metadata.position || metadata.size) passage.metadata = metadata;
|
|
4521
4541
|
storyAdd(story, passage, diagnostics);
|
|
@@ -4529,8 +4549,19 @@ function decompileTwine2(storyData, story, diagnostics) {
|
|
|
4529
4549
|
text: marshalStoryData(story)
|
|
4530
4550
|
}, diagnostics);
|
|
4531
4551
|
}
|
|
4532
|
-
function
|
|
4533
|
-
|
|
4552
|
+
function passageDataName(node) {
|
|
4553
|
+
return node.attribs?.["name"] ?? "";
|
|
4554
|
+
}
|
|
4555
|
+
/** The first of `base`, `base 2`, `base 3`, … that is not in `taken`. */
|
|
4556
|
+
function freeName(base, taken) {
|
|
4557
|
+
if (!taken.has(base)) return base;
|
|
4558
|
+
for (let n = 2;; n++) {
|
|
4559
|
+
const candidate = `${base} ${n}`;
|
|
4560
|
+
if (!taken.has(candidate)) return candidate;
|
|
4561
|
+
}
|
|
4562
|
+
}
|
|
4563
|
+
function decompileTwine1(storeArea, story, passageText, diagnostics) {
|
|
4564
|
+
const passages = (storeArea.children ?? []).filter(isTiddler).map((node) => tiddlerToPassage(node, passageText));
|
|
4534
4565
|
const obfuscated = isRot13Obfuscated(passages);
|
|
4535
4566
|
for (const p of passages) storyAdd(story, obfuscated && p.name !== "StorySettings" ? {
|
|
4536
4567
|
...p,
|
|
@@ -4540,12 +4571,12 @@ function decompileTwine1(storeArea, story, diagnostics) {
|
|
|
4540
4571
|
function isTiddler(node) {
|
|
4541
4572
|
return node.type === "tag" && node.name === "div" && node.attribs !== void 0 && "tiddler" in node.attribs;
|
|
4542
4573
|
}
|
|
4543
|
-
function tiddlerToPassage(node) {
|
|
4574
|
+
function tiddlerToPassage(node, passageText) {
|
|
4544
4575
|
const nodeAttrs = node.attribs ?? {};
|
|
4545
4576
|
const passage = {
|
|
4546
4577
|
name: nodeAttrs["tiddler"] ?? "",
|
|
4547
4578
|
tags: nodeAttrs["tags"] ? nodeAttrs["tags"].split(/\s+/).filter((s) => s.length > 0) : [],
|
|
4548
|
-
text: tiddlerUnescape(getTextContent(node))
|
|
4579
|
+
text: passageText(tiddlerUnescape(getTextContent(node)))
|
|
4549
4580
|
};
|
|
4550
4581
|
const position = nodeAttrs["twine-position"];
|
|
4551
4582
|
if (position) passage.metadata = { position };
|
|
@@ -4610,7 +4641,7 @@ function loadSources(story, filenames, opts, diagnostics, processedFiles) {
|
|
|
4610
4641
|
break;
|
|
4611
4642
|
case "htm":
|
|
4612
4643
|
case "html":
|
|
4613
|
-
loadHTML(story, filename, diagnostics);
|
|
4644
|
+
loadHTML(story, filename, opts, diagnostics);
|
|
4614
4645
|
break;
|
|
4615
4646
|
case "css":
|
|
4616
4647
|
loadTagged(story, "stylesheet", filename, diagnostics);
|
|
@@ -4764,8 +4795,8 @@ function parseFontFile(filename) {
|
|
|
4764
4795
|
diagnostics: []
|
|
4765
4796
|
};
|
|
4766
4797
|
}
|
|
4767
|
-
function parseHTMLFile(filename) {
|
|
4768
|
-
const { story, diagnostics } = decompileHTML(readUTF8(filename));
|
|
4798
|
+
function parseHTMLFile(filename, opts) {
|
|
4799
|
+
const { story, diagnostics } = decompileHTML(readUTF8(filename), { trim: opts.trim ?? true });
|
|
4769
4800
|
return {
|
|
4770
4801
|
passages: story.name !== "" && !storyHas(story, "StoryTitle") ? [{
|
|
4771
4802
|
name: "StoryTitle",
|
|
@@ -4777,14 +4808,17 @@ function parseHTMLFile(filename) {
|
|
|
4777
4808
|
}
|
|
4778
4809
|
/**
|
|
4779
4810
|
* Identity of the options that affect how a file parses, stored on its cache entry.
|
|
4780
|
-
*
|
|
4811
|
+
* Twee files depend on `trim` and `twee2Compat`, HTML files on `trim`; every other file type
|
|
4812
|
+
* yields the same key for all options.
|
|
4781
4813
|
*/
|
|
4782
4814
|
function parseOptionsKey(filename, opts) {
|
|
4783
4815
|
const ext = normalizedFileExt(filename);
|
|
4816
|
+
const trim = opts.trim ?? true;
|
|
4817
|
+
if (ext === "htm" || ext === "html") return JSON.stringify({ trim });
|
|
4784
4818
|
const twee2File = ext === "tw2" || ext === "twee2";
|
|
4785
4819
|
if (!twee2File && ext !== "tw" && ext !== "twee") return "";
|
|
4786
4820
|
return JSON.stringify({
|
|
4787
|
-
trim
|
|
4821
|
+
trim,
|
|
4788
4822
|
twee2Compat: twee2File || (opts.twee2Compat ?? false)
|
|
4789
4823
|
});
|
|
4790
4824
|
}
|
|
@@ -4798,7 +4832,7 @@ function parseFile(filename, opts) {
|
|
|
4798
4832
|
twee2Compat: true
|
|
4799
4833
|
});
|
|
4800
4834
|
case "htm":
|
|
4801
|
-
case "html": return parseHTMLFile(filename);
|
|
4835
|
+
case "html": return parseHTMLFile(filename, opts);
|
|
4802
4836
|
case "css": return parseTaggedFile("stylesheet", filename);
|
|
4803
4837
|
case "js": return parseTaggedFile("script", filename);
|
|
4804
4838
|
case "otf":
|
|
@@ -4830,8 +4864,8 @@ function parseFile(filename, opts) {
|
|
|
4830
4864
|
default: return;
|
|
4831
4865
|
}
|
|
4832
4866
|
}
|
|
4833
|
-
function loadHTML(story, filename, diagnostics) {
|
|
4834
|
-
const result = parseHTMLFile(filename);
|
|
4867
|
+
function loadHTML(story, filename, opts, diagnostics) {
|
|
4868
|
+
const result = parseHTMLFile(filename, opts);
|
|
4835
4869
|
diagnostics.push(...result.diagnostics);
|
|
4836
4870
|
for (const p of result.passages) storyAdd(story, p, diagnostics);
|
|
4837
4871
|
}
|
|
@@ -4870,7 +4904,8 @@ function loadSourcesCached(story, filenames, opts, diagnostics, processedFiles,
|
|
|
4870
4904
|
const optionsKey = parseOptionsKey(filename, opts);
|
|
4871
4905
|
const entry = cache.get(filename);
|
|
4872
4906
|
const cached = entry?.parseOptionsKey === optionsKey ? entry : void 0;
|
|
4873
|
-
|
|
4907
|
+
const changed = changedFiles?.has(filename) ?? false;
|
|
4908
|
+
if (changedFiles && cached && !changed) {
|
|
4874
4909
|
diagnostics.push(...cached.diagnostics);
|
|
4875
4910
|
for (const p of cached.passages) storyAdd(story, p, diagnostics);
|
|
4876
4911
|
processedFiles.add(filename);
|
|
@@ -4882,7 +4917,7 @@ function loadSourcesCached(story, filenames, opts, diagnostics, processedFiles,
|
|
|
4882
4917
|
} catch {
|
|
4883
4918
|
continue;
|
|
4884
4919
|
}
|
|
4885
|
-
if (cached && cached.mtimeMs === mtimeMs) {
|
|
4920
|
+
if (cached && !changed && cached.mtimeMs === mtimeMs) {
|
|
4886
4921
|
diagnostics.push(...cached.diagnostics);
|
|
4887
4922
|
for (const p of cached.passages) storyAdd(story, p, diagnostics);
|
|
4888
4923
|
processedFiles.add(filename);
|
|
@@ -4917,6 +4952,121 @@ function loadSourcesCached(story, filenames, opts, diagnostics, processedFiles,
|
|
|
4917
4952
|
}, diagnostics);
|
|
4918
4953
|
}
|
|
4919
4954
|
//#endregion
|
|
4955
|
+
//#region src/template.ts
|
|
4956
|
+
/**
|
|
4957
|
+
* An HTML closing head tag: `</head` in any letter case, followed by whitespace, `/` or `>`, up to the next `>`.
|
|
4958
|
+
* This is how the HTML tokenizer reads an end tag; it ignores whatever sits between the tag name and the `>`.
|
|
4959
|
+
*/
|
|
4960
|
+
const CLOSING_HEAD_TAG = "<\\/[Hh][Ee][Aa][Dd](?=[\\t\\n\\f\\r />])[^>]*>";
|
|
4961
|
+
/** The regular expression source matching exactly `text`. */
|
|
4962
|
+
function literal(text) {
|
|
4963
|
+
return text.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&");
|
|
4964
|
+
}
|
|
4965
|
+
/** Replace the `slots` placeholders found in `template`, without scanning inserted text. */
|
|
4966
|
+
function fillTemplate(template, slots) {
|
|
4967
|
+
return fillTemplateParts([{
|
|
4968
|
+
kind: "scan",
|
|
4969
|
+
text: template,
|
|
4970
|
+
slots
|
|
4971
|
+
}]);
|
|
4972
|
+
}
|
|
4973
|
+
/**
|
|
4974
|
+
* Fill the parts of a template in order, joined. A `'first'` slot replaced in one part is not replaced again in a
|
|
4975
|
+
* later part that lists the same slot object.
|
|
4976
|
+
*/
|
|
4977
|
+
function fillTemplateParts(parts) {
|
|
4978
|
+
const filled = /* @__PURE__ */ new Set();
|
|
4979
|
+
return parts.map((part) => {
|
|
4980
|
+
if (part.kind === "verbatim" || part.slots.length === 0) return part.text;
|
|
4981
|
+
const { slots } = part;
|
|
4982
|
+
const pattern = new RegExp(slots.map((slot) => `(${slot.pattern})`).join("|"), "g");
|
|
4983
|
+
return part.text.replace(pattern, (match, ...groups) => {
|
|
4984
|
+
const slot = slots.find((_, i) => groups[i] !== void 0);
|
|
4985
|
+
if (slot === void 0 || slot.occurrences === "first" && filled.has(slot)) return match;
|
|
4986
|
+
filled.add(slot);
|
|
4987
|
+
return slot.replacement(match);
|
|
4988
|
+
});
|
|
4989
|
+
}).join("");
|
|
4990
|
+
}
|
|
4991
|
+
//#endregion
|
|
4992
|
+
//#region src/modules.ts
|
|
4993
|
+
/**
|
|
4994
|
+
* Load modules and return HTML tags to inject before the closing head tag.
|
|
4995
|
+
*/
|
|
4996
|
+
function loadModules(filenames) {
|
|
4997
|
+
const processed = /* @__PURE__ */ new Set();
|
|
4998
|
+
const headTags = [];
|
|
4999
|
+
for (const filename of filenames) {
|
|
5000
|
+
if (processed.has(filename)) continue;
|
|
5001
|
+
const ext = normalizedFileExt(filename);
|
|
5002
|
+
let tag = null;
|
|
5003
|
+
switch (ext) {
|
|
5004
|
+
case "css":
|
|
5005
|
+
tag = loadModuleTagged("style", filename);
|
|
5006
|
+
break;
|
|
5007
|
+
case "js":
|
|
5008
|
+
tag = loadModuleTagged("script", filename);
|
|
5009
|
+
break;
|
|
5010
|
+
case "otf":
|
|
5011
|
+
case "ttf":
|
|
5012
|
+
case "woff":
|
|
5013
|
+
case "woff2":
|
|
5014
|
+
tag = loadModuleFont(filename);
|
|
5015
|
+
break;
|
|
5016
|
+
default: continue;
|
|
5017
|
+
}
|
|
5018
|
+
if (tag) headTags.push(tag);
|
|
5019
|
+
processed.add(filename);
|
|
5020
|
+
}
|
|
5021
|
+
return headTags.join("\n");
|
|
5022
|
+
}
|
|
5023
|
+
function loadModuleTagged(tag, filename) {
|
|
5024
|
+
const source = readUTF8(filename).trim();
|
|
5025
|
+
if (source.length === 0) return null;
|
|
5026
|
+
return `<${tag} id="${`${tag}-module-${slugify(baseNameWithoutExt(filename))}`}" type="${tag === "script" ? "text/javascript" : "text/css"}">${tag === "script" ? scriptContentEscape(source) : styleContentEscape(source)}</${tag}>`;
|
|
5027
|
+
}
|
|
5028
|
+
function loadModuleFont(filename) {
|
|
5029
|
+
const source = readBase64(filename);
|
|
5030
|
+
const family = baseNameWithoutExt(filename);
|
|
5031
|
+
const idSlug = `style-module-${slugify(family)}`;
|
|
5032
|
+
const ext = normalizedFileExt(filename);
|
|
5033
|
+
return `<style id="${idSlug}" type="text/css">@font-face {\n\tfont-family: "${family}";\n\tsrc: url("data:${mediaTypeFromExt(ext)};base64,${source}") format("${fontFormatHint(ext)}");\n}</style>`;
|
|
5034
|
+
}
|
|
5035
|
+
/**
|
|
5036
|
+
* Load the module tags and head file content to inject before the closing head tag, joined by newlines,
|
|
5037
|
+
* or `''` when there is nothing to inject.
|
|
5038
|
+
*/
|
|
5039
|
+
function loadHeadContent(modulePaths, headFile, diagnostics) {
|
|
5040
|
+
const parts = [];
|
|
5041
|
+
if (modulePaths.length > 0) {
|
|
5042
|
+
const modules = loadModules(modulePaths).trim();
|
|
5043
|
+
if (modules.length > 0) parts.push(modules);
|
|
5044
|
+
}
|
|
5045
|
+
if (headFile) try {
|
|
5046
|
+
const source = readUTF8(headFile).trim();
|
|
5047
|
+
if (source.length > 0) parts.push(source);
|
|
5048
|
+
} catch (e) {
|
|
5049
|
+
diagnostics?.push({
|
|
5050
|
+
level: "warning",
|
|
5051
|
+
message: `Failed to read head file "${headFile}": ${e instanceof Error ? e.message : String(e)}`
|
|
5052
|
+
});
|
|
5053
|
+
}
|
|
5054
|
+
return parts.join("\n");
|
|
5055
|
+
}
|
|
5056
|
+
/**
|
|
5057
|
+
* The template slot that inserts `content` on its own line before the first closing head tag (as Tweego does),
|
|
5058
|
+
* or `undefined` when `content` is empty. The tag may use any letter case and whitespace before its `>`, and is
|
|
5059
|
+
* kept as written.
|
|
5060
|
+
*/
|
|
5061
|
+
function headSlot(content) {
|
|
5062
|
+
if (content.length === 0) return void 0;
|
|
5063
|
+
return {
|
|
5064
|
+
pattern: CLOSING_HEAD_TAG,
|
|
5065
|
+
occurrences: "first",
|
|
5066
|
+
replacement: (tag) => `${content}\n${tag}`
|
|
5067
|
+
};
|
|
5068
|
+
}
|
|
5069
|
+
//#endregion
|
|
4920
5070
|
//#region src/version.ts
|
|
4921
5071
|
/**
|
|
4922
5072
|
* The twee-ts version, read at runtime from the package's own package.json.
|
|
@@ -4950,24 +5100,38 @@ const CREATOR_NAME$2 = "Twee-ts";
|
|
|
4950
5100
|
function toTwine2Archive(story, startName, options) {
|
|
4951
5101
|
return getTwine2DataChunk(story, startName, options) + "\n";
|
|
4952
5102
|
}
|
|
5103
|
+
/**
|
|
5104
|
+
* Fill the Twine 2 format template: every `{{STORY_NAME}}` gets the escaped story name and the first
|
|
5105
|
+
* `{{STORY_DATA}}` the story data (as Tweego does), and `head` goes before the first closing head tag. All three are
|
|
5106
|
+
* found in the template in one pass, so a story name, passage text or head content holding a placeholder or a
|
|
5107
|
+
* closing head tag stays literal.
|
|
5108
|
+
*/
|
|
4953
5109
|
function toTwine2HTML(story, format, startName, options) {
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
5110
|
+
const name = htmlEscape(story.name);
|
|
5111
|
+
const head = headSlot(options?.head ?? "");
|
|
5112
|
+
const slots = [
|
|
5113
|
+
{
|
|
5114
|
+
pattern: literal("{{STORY_NAME}}"),
|
|
5115
|
+
occurrences: "all",
|
|
5116
|
+
replacement: () => name
|
|
5117
|
+
},
|
|
5118
|
+
{
|
|
5119
|
+
pattern: literal("{{STORY_DATA}}"),
|
|
5120
|
+
occurrences: "first",
|
|
5121
|
+
replacement: () => {
|
|
5122
|
+
return getTwine2DataChunk({
|
|
5123
|
+
...story,
|
|
5124
|
+
twine2: {
|
|
5125
|
+
...story.twine2,
|
|
5126
|
+
format: format.name,
|
|
5127
|
+
formatVersion: format.version
|
|
5128
|
+
}
|
|
5129
|
+
}, startName, options);
|
|
4966
5130
|
}
|
|
4967
|
-
},
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
return
|
|
5131
|
+
},
|
|
5132
|
+
...head === void 0 ? [] : [head]
|
|
5133
|
+
];
|
|
5134
|
+
return fillTemplate(readFormatSource(format), slots);
|
|
4971
5135
|
}
|
|
4972
5136
|
const OMITTING_TAGS = [
|
|
4973
5137
|
"Twine.private",
|
|
@@ -5060,7 +5224,13 @@ function toTwine1Archive(story, _startName) {
|
|
|
5060
5224
|
const { data, count } = getTwine1PassageChunk(story);
|
|
5061
5225
|
return `<div id="storeArea" data-size="${count}">${data}</div>\n`;
|
|
5062
5226
|
}
|
|
5063
|
-
|
|
5227
|
+
/**
|
|
5228
|
+
* Fill the Twine 1 format template. The format's components are inserted first, one after another, as Tweego and
|
|
5229
|
+
* Twine 1 do. Then the first of each story placeholder (`"VERSION"`, `"TIME"`, `"START_AT"`, `"STORY_SIZE"`,
|
|
5230
|
+
* `"STORY"`), the IFID comment and `head` (before the first closing head tag) are found in one pass, so a start
|
|
5231
|
+
* passage name or story data holding a placeholder or a closing head tag stays literal.
|
|
5232
|
+
*/
|
|
5233
|
+
function toTwine1HTML(story, format, startName, options) {
|
|
5064
5234
|
const formatDir = dirname(format.filename);
|
|
5065
5235
|
const parentDir = dirname(formatDir);
|
|
5066
5236
|
let template = readFormatSource(format);
|
|
@@ -5071,28 +5241,60 @@ function toTwine1HTML(story, format, startName) {
|
|
|
5071
5241
|
template = tryReplaceComponent(template, "\"JONAH\"", join(formatDir, "code.js"), true);
|
|
5072
5242
|
if (story.twine1.settings.get("jquery") === "on") template = tryReplaceComponent(template, "\"JQUERY\"", join(parentDir, "jquery.js"), true);
|
|
5073
5243
|
if (story.twine1.settings.get("modernizr") === "on") template = tryReplaceComponent(template, "\"MODERNIZR\"", join(parentDir, "modernizr.js"), true);
|
|
5244
|
+
const isPre14 = !template.includes("\"STORY\"");
|
|
5245
|
+
const footer = isPre14 ? readFooter(formatDir) : "";
|
|
5246
|
+
const storeArea = (template + footer).includes("<div id=\"store-area\"") ? "<div id=\"store-area\"" : "<div id=\"storeArea\"";
|
|
5247
|
+
const safeIfid = htmlCommentSanitize(story.ifid);
|
|
5248
|
+
const late = [story.ifid ? {
|
|
5249
|
+
pattern: literal(storeArea),
|
|
5250
|
+
occurrences: "first",
|
|
5251
|
+
replacement: (div) => `<!-- UUID://${safeIfid}// -->${div}`
|
|
5252
|
+
} : void 0, headSlot(options?.head ?? "")].filter((slot) => slot !== void 0);
|
|
5074
5253
|
const displayStart = startName === "Start" ? "" : startName;
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5254
|
+
const slots = [
|
|
5255
|
+
firstSlot("\"VERSION\"", `Compiled with ${CREATOR_NAME$1}, ${VERSION}`),
|
|
5256
|
+
firstSlot("\"TIME\"", `Built on ${(/* @__PURE__ */ new Date()).toUTCString()}`),
|
|
5257
|
+
firstSlot("\"START_AT\"", `"${jsStringEscape(displayStart)}"`),
|
|
5258
|
+
firstSlot("\"STORY_SIZE\"", `"${count}"`),
|
|
5259
|
+
firstSlot("\"STORY\"", data),
|
|
5260
|
+
...late
|
|
5261
|
+
];
|
|
5262
|
+
return fillTemplateParts(isPre14 ? [
|
|
5263
|
+
{
|
|
5264
|
+
kind: "scan",
|
|
5265
|
+
text: template,
|
|
5266
|
+
slots
|
|
5267
|
+
},
|
|
5268
|
+
{
|
|
5269
|
+
kind: "verbatim",
|
|
5270
|
+
text: data
|
|
5271
|
+
},
|
|
5272
|
+
{
|
|
5273
|
+
kind: "scan",
|
|
5274
|
+
text: footer,
|
|
5275
|
+
slots: late
|
|
5087
5276
|
}
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5277
|
+
] : [{
|
|
5278
|
+
kind: "scan",
|
|
5279
|
+
text: template,
|
|
5280
|
+
slots
|
|
5281
|
+
}]);
|
|
5282
|
+
}
|
|
5283
|
+
/** A slot replacing the first `token` with `value`. */
|
|
5284
|
+
function firstSlot(token, value) {
|
|
5285
|
+
return {
|
|
5286
|
+
pattern: literal(token),
|
|
5287
|
+
occurrences: "first",
|
|
5288
|
+
replacement: () => value
|
|
5289
|
+
};
|
|
5290
|
+
}
|
|
5291
|
+
/** The footer of a pre-1.4 format, or the default one when the format has none. */
|
|
5292
|
+
function readFooter(formatDir) {
|
|
5293
|
+
try {
|
|
5294
|
+
return readFileSync(join(formatDir, "footer.html"), "utf-8");
|
|
5295
|
+
} catch {
|
|
5296
|
+
return "</div>\n</body>\n</html>\n";
|
|
5094
5297
|
}
|
|
5095
|
-
return template;
|
|
5096
5298
|
}
|
|
5097
5299
|
/**
|
|
5098
5300
|
* Why Twine 1 output leaves a passage out of its tiddlers, or `undefined` when the passage is emitted.
|
|
@@ -5130,82 +5332,54 @@ function tryReplaceComponent(template, placeholder, componentPath, required) {
|
|
|
5130
5332
|
}
|
|
5131
5333
|
//#endregion
|
|
5132
5334
|
//#region src/output-twee.ts
|
|
5133
|
-
function toTwee(story, outMode) {
|
|
5134
|
-
let data = "";
|
|
5135
|
-
for (const p of story.passages) data += passageToTwee(p, outMode);
|
|
5136
|
-
return data;
|
|
5137
|
-
}
|
|
5138
|
-
//#endregion
|
|
5139
|
-
//#region src/modules.ts
|
|
5140
5335
|
/**
|
|
5141
|
-
*
|
|
5336
|
+
* Serialize the story as Twee. The StoryData passage is written from the story model rather than
|
|
5337
|
+
* from its loaded text, so changes made after loading (a start passage override, test mode) are kept.
|
|
5338
|
+
* Tweego writes the loaded text; its own normalization makes the two the same for an unchanged story.
|
|
5339
|
+
*
|
|
5340
|
+
* `addStoryData`: add a StoryData passage when the story has none, so that StoryData fields the
|
|
5341
|
+
* compile options set are recorded. Default: false.
|
|
5142
5342
|
*/
|
|
5143
|
-
function
|
|
5144
|
-
|
|
5145
|
-
const headTags = [];
|
|
5146
|
-
for (const filename of filenames) {
|
|
5147
|
-
if (processed.has(filename)) continue;
|
|
5148
|
-
const ext = normalizedFileExt(filename);
|
|
5149
|
-
let tag = null;
|
|
5150
|
-
switch (ext) {
|
|
5151
|
-
case "css":
|
|
5152
|
-
tag = loadModuleTagged("style", filename);
|
|
5153
|
-
break;
|
|
5154
|
-
case "js":
|
|
5155
|
-
tag = loadModuleTagged("script", filename);
|
|
5156
|
-
break;
|
|
5157
|
-
case "otf":
|
|
5158
|
-
case "ttf":
|
|
5159
|
-
case "woff":
|
|
5160
|
-
case "woff2":
|
|
5161
|
-
tag = loadModuleFont(filename);
|
|
5162
|
-
break;
|
|
5163
|
-
default: continue;
|
|
5164
|
-
}
|
|
5165
|
-
if (tag) headTags.push(tag);
|
|
5166
|
-
processed.add(filename);
|
|
5167
|
-
}
|
|
5168
|
-
return headTags.join("\n");
|
|
5343
|
+
function toTwee(story, outMode, options = {}) {
|
|
5344
|
+
return withEffectiveStoryData(story, options.addStoryData ?? false).map((p) => passageToTwee(p, outMode)).join("");
|
|
5169
5345
|
}
|
|
5170
|
-
function
|
|
5171
|
-
const
|
|
5172
|
-
|
|
5173
|
-
|
|
5346
|
+
function withEffectiveStoryData(story, addStoryData) {
|
|
5347
|
+
const storyData = {
|
|
5348
|
+
name: "StoryData",
|
|
5349
|
+
tags: [],
|
|
5350
|
+
text: marshalStoryData(story)
|
|
5351
|
+
};
|
|
5352
|
+
const index = story.passages.findIndex((p) => p.name === "StoryData");
|
|
5353
|
+
if (index !== -1) return story.passages.map((p, i) => i === index && isStoryDataJSON(p.text) ? {
|
|
5354
|
+
...p,
|
|
5355
|
+
text: storyData.text
|
|
5356
|
+
} : p);
|
|
5357
|
+
if (!addStoryData) return story.passages;
|
|
5358
|
+
const at = story.passages[0]?.name === "StoryTitle" ? 1 : 0;
|
|
5359
|
+
return [
|
|
5360
|
+
...story.passages.slice(0, at),
|
|
5361
|
+
storyData,
|
|
5362
|
+
...story.passages.slice(at)
|
|
5363
|
+
];
|
|
5174
5364
|
}
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
const idSlug = `style-module-${slugify(family)}`;
|
|
5179
|
-
const ext = normalizedFileExt(filename);
|
|
5180
|
-
return `<style id="${idSlug}" type="text/css">@font-face {\n\tfont-family: "${family}";\n\tsrc: url("data:${mediaTypeFromExt(ext)};base64,${source}") format("${fontFormatHint(ext)}");\n}</style>`;
|
|
5365
|
+
/** Whether the text loaded as StoryData. Text that did not is kept as written, for the author to fix. */
|
|
5366
|
+
function isStoryDataJSON(text) {
|
|
5367
|
+
return unmarshalStoryData(createStory(), text) === null;
|
|
5181
5368
|
}
|
|
5369
|
+
//#endregion
|
|
5370
|
+
//#region src/passage-omission.ts
|
|
5182
5371
|
/**
|
|
5183
|
-
*
|
|
5372
|
+
* Why the `target` output leaves `passage` out of its passage elements, or `undefined` when it
|
|
5373
|
+
* emits the passage (and so a link or the start can name it).
|
|
5184
5374
|
*/
|
|
5185
|
-
function
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
}
|
|
5191
|
-
if (headFile) try {
|
|
5192
|
-
const source = readUTF8(headFile).trim();
|
|
5193
|
-
if (source.length > 0) parts.push(source);
|
|
5194
|
-
} catch (e) {
|
|
5195
|
-
diagnostics?.push({
|
|
5196
|
-
level: "warning",
|
|
5197
|
-
message: `Failed to read head file "${headFile}": ${e instanceof Error ? e.message : String(e)}`
|
|
5198
|
-
});
|
|
5199
|
-
}
|
|
5200
|
-
if (parts.length > 0) {
|
|
5201
|
-
parts.push("</head>");
|
|
5202
|
-
const replacement = parts.join("\n");
|
|
5203
|
-
return html.replace("</head>", () => replacement);
|
|
5375
|
+
function passageOmission(story, passage, target) {
|
|
5376
|
+
switch (target) {
|
|
5377
|
+
case "twine2": return twine2PassageOmission(story, passage);
|
|
5378
|
+
case "twine1": return twine1PassageOmission(passage);
|
|
5379
|
+
default: throw new Error(`unhandled passage output target: ${String(target)}`);
|
|
5204
5380
|
}
|
|
5205
|
-
return html;
|
|
5206
5381
|
}
|
|
5207
|
-
|
|
5208
|
-
//#region src/start-passage.ts
|
|
5382
|
+
/** Why a passage is left out, as a predicate: `Passage "X" ${describeOmission(o)}`. */
|
|
5209
5383
|
function describeOmission(omission) {
|
|
5210
5384
|
switch (omission.kind) {
|
|
5211
5385
|
case "special-name": return `is the special "${omission.name}" passage`;
|
|
@@ -5214,13 +5388,8 @@ function describeOmission(omission) {
|
|
|
5214
5388
|
default: throw new Error(`unhandled passage omission: ${JSON.stringify(omission)}`);
|
|
5215
5389
|
}
|
|
5216
5390
|
}
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
case "twine2": return twine2PassageOmission(story, passage);
|
|
5220
|
-
case "twine1": return twine1PassageOmission(passage);
|
|
5221
|
-
default: throw new Error(`unhandled start passage target: ${String(target)}`);
|
|
5222
|
-
}
|
|
5223
|
-
}
|
|
5391
|
+
//#endregion
|
|
5392
|
+
//#region src/start-passage.ts
|
|
5224
5393
|
/**
|
|
5225
5394
|
* Check that `startName` names a passage the `target` output emits.
|
|
5226
5395
|
* Returns the error diagnostics to report (empty when the start is usable).
|
|
@@ -5231,7 +5400,7 @@ function startPassageDiagnostics(story, startName, target) {
|
|
|
5231
5400
|
level: "error",
|
|
5232
5401
|
message: `Starting passage "${startName}" not found.`
|
|
5233
5402
|
}];
|
|
5234
|
-
const omission =
|
|
5403
|
+
const omission = passageOmission(story, passage, target);
|
|
5235
5404
|
if (omission === void 0) return [];
|
|
5236
5405
|
return [{
|
|
5237
5406
|
level: "error",
|
|
@@ -5252,17 +5421,16 @@ var TweeTsError = class extends Error {
|
|
|
5252
5421
|
}
|
|
5253
5422
|
};
|
|
5254
5423
|
/**
|
|
5255
|
-
*
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
*
|
|
5262
|
-
* Plugins and advanced users can manage their own cache and changed-file tracking.
|
|
5424
|
+
* Builds as compileToFile() does for `outFile`, without writing it: the output may sit
|
|
5425
|
+
* inside a source folder, so its last build is left out of the sources and modules and
|
|
5426
|
+
* never read back. The caller decides whether the result is written. With no `outFile`
|
|
5427
|
+
* (output to stdout, or none), this is compile(). With `cache`, files are cached as
|
|
5428
|
+
* compileIncremental() caches them.
|
|
5429
|
+
*
|
|
5430
|
+
* Internal, for the CLI and the bundler plugins; not part of the public API.
|
|
5263
5431
|
*/
|
|
5264
|
-
async function
|
|
5265
|
-
return buildOutput(options, cache,
|
|
5432
|
+
async function compileForOutputFile(options, outFile, cache) {
|
|
5433
|
+
return buildOutput(options, cache, void 0, outFile);
|
|
5266
5434
|
}
|
|
5267
5435
|
/**
|
|
5268
5436
|
* `outFile`: the file the build is written to, which source and module discovery
|
|
@@ -5311,7 +5479,7 @@ async function buildOutput(options, cache, changedFiles, outFile) {
|
|
|
5311
5479
|
switch (outputMode) {
|
|
5312
5480
|
case "twee3":
|
|
5313
5481
|
case "twee1":
|
|
5314
|
-
output = toTwee(story, outputMode);
|
|
5482
|
+
output = toTwee(story, outputMode, { addStoryData: Boolean(options.startPassage) || testMode });
|
|
5315
5483
|
break;
|
|
5316
5484
|
case "twine2-archive":
|
|
5317
5485
|
output = toTwine2Archive(story, startName, { sourceInfo });
|
|
@@ -5325,17 +5493,17 @@ async function buildOutput(options, cache, changedFiles, outFile) {
|
|
|
5325
5493
|
case "html": {
|
|
5326
5494
|
diagnostics.push(...startPassageDiagnostics(story, startName, format?.isTwine2 === false ? "twine1" : "twine2"));
|
|
5327
5495
|
if (!format) throw new TweeTsError("No story format available for HTML output.", diagnostics);
|
|
5328
|
-
if (format.isTwine2
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
message: "Special passage \"StoryTitle\" not found."
|
|
5333
|
-
});
|
|
5334
|
-
output = toTwine1HTML(story, format, startName);
|
|
5335
|
-
}
|
|
5496
|
+
if (!format.isTwine2 && story.name === "" && !storyHas(story, "StoryTitle")) diagnostics.push({
|
|
5497
|
+
level: "error",
|
|
5498
|
+
message: "Special passage \"StoryTitle\" not found."
|
|
5499
|
+
});
|
|
5336
5500
|
const modules = getFilenames(options.modules ?? [], outFile);
|
|
5337
5501
|
diagnostics.push(...modules.diagnostics);
|
|
5338
|
-
|
|
5502
|
+
const head = loadHeadContent(modules.filenames, options.headFile, diagnostics);
|
|
5503
|
+
output = format.isTwine2 ? toTwine2HTML(story, format, startName, {
|
|
5504
|
+
sourceInfo,
|
|
5505
|
+
head
|
|
5506
|
+
}) : toTwine1HTML(story, format, startName, { head });
|
|
5339
5507
|
break;
|
|
5340
5508
|
}
|
|
5341
5509
|
default: throw new TweeTsError(`Unhandled output mode: ${outputMode}`, diagnostics);
|
|
@@ -5411,6 +5579,68 @@ function storyToJSON(story) {
|
|
|
5411
5579
|
return JSON.stringify(obj, null, 2);
|
|
5412
5580
|
}
|
|
5413
5581
|
//#endregion
|
|
5414
|
-
|
|
5582
|
+
//#region src/plugins/diagnostics.ts
|
|
5583
|
+
function formatDiagnostic(d) {
|
|
5584
|
+
return `${d.file ? `${d.file}${d.line ? `:${d.line}` : ""}: ` : ""}${d.file && d.line ? d.message.replace(/^line \d+: /, "") : d.message}`;
|
|
5585
|
+
}
|
|
5586
|
+
/** Returns the warnings; throws a LocatedError when the compile reported errors. */
|
|
5587
|
+
function splitDiagnostics(result) {
|
|
5588
|
+
const errors = result.diagnostics.filter((d) => d.level === "error");
|
|
5589
|
+
if (errors.length === 0) return result.diagnostics.filter((d) => d.level === "warning");
|
|
5590
|
+
const error = new Error(errors.map(formatDiagnostic).join("\n"));
|
|
5591
|
+
const [first] = errors;
|
|
5592
|
+
if (first?.file) {
|
|
5593
|
+
error.id = first.file;
|
|
5594
|
+
error.loc = {
|
|
5595
|
+
file: first.file,
|
|
5596
|
+
line: first.line ?? 1,
|
|
5597
|
+
column: 1
|
|
5598
|
+
};
|
|
5599
|
+
}
|
|
5600
|
+
throw error;
|
|
5601
|
+
}
|
|
5602
|
+
/** An error thrown by the compiler or bundler, with a TweeTsError's error diagnostics folded into its message. */
|
|
5603
|
+
function fatalError(e) {
|
|
5604
|
+
if (e instanceof TweeTsError) {
|
|
5605
|
+
const errors = e.diagnostics.filter((d) => d.level === "error").map(formatDiagnostic);
|
|
5606
|
+
return new Error([...errors, e.message].join("\n"));
|
|
5607
|
+
}
|
|
5608
|
+
return e instanceof Error ? e : new Error(String(e));
|
|
5609
|
+
}
|
|
5610
|
+
//#endregion
|
|
5611
|
+
//#region src/plugins/paths.ts
|
|
5612
|
+
/**
|
|
5613
|
+
* Path helpers for the bundler plugins. Vite reports module ids with forward
|
|
5614
|
+
* slashes on every platform, while node:path gives backslashes on Windows, so
|
|
5615
|
+
* the Vite plugin compares paths in forward-slash form only.
|
|
5616
|
+
*/
|
|
5617
|
+
/**
|
|
5618
|
+
* The absolute path a bundle writes the file it emits as `fileName` to: inside
|
|
5619
|
+
* `output.dir`, or next to `output.file`, either resolved against the working
|
|
5620
|
+
* directory, as the bundler resolves them. Undefined when the output names
|
|
5621
|
+
* neither, as for a bundle only generated in memory.
|
|
5622
|
+
*/
|
|
5623
|
+
function emittedFilePath(output, fileName) {
|
|
5624
|
+
const dir = output.dir ?? (output.file === void 0 ? void 0 : dirname(output.file));
|
|
5625
|
+
return dir === void 0 ? void 0 : resolve(dir, fileName);
|
|
5626
|
+
}
|
|
5627
|
+
/** The path with Windows separators turned into forward slashes. */
|
|
5628
|
+
function toPosix(path) {
|
|
5629
|
+
return path.replace(/\\/g, "/");
|
|
5630
|
+
}
|
|
5631
|
+
/** Whether `file` is one of `dirs` or inside one of them. All paths in forward-slash form. */
|
|
5632
|
+
function isInside(file, dirs) {
|
|
5633
|
+
return dirs.some((dir) => file === dir || file.startsWith(dir.endsWith("/") ? dir : `${dir}/`));
|
|
5634
|
+
}
|
|
5635
|
+
/**
|
|
5636
|
+
* Whether `file` is a temporary copy of a Vite config file. Vite writes one next
|
|
5637
|
+
* to the config file each time it loads it (when no node_modules folder is
|
|
5638
|
+
* above the config, and always under Deno), then deletes it.
|
|
5639
|
+
*/
|
|
5640
|
+
function isViteConfigTemp(file) {
|
|
5641
|
+
return /\.timestamp-\d+-[0-9a-f]*\.mjs$/.test(file);
|
|
5642
|
+
}
|
|
5643
|
+
//#endregion
|
|
5644
|
+
export { fatalError as a, compileForOutputFile as c, mediaTypeFromFilename as d, toPosix as i, getFilenames as l, isInside as n, formatDiagnostic as o, isViteConfigTemp as r, splitDiagnostics as s, emittedFilePath as t, isExcluded as u };
|
|
5415
5645
|
|
|
5416
|
-
//# sourceMappingURL=
|
|
5646
|
+
//# sourceMappingURL=paths-w4ulhCFb.js.map
|