@rohal12/twee-ts 1.18.0 → 1.18.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/dist/bin/twee-ts.js +229 -107
- package/dist/bin/twee-ts.js.map +1 -1
- package/dist/index.cjs +230 -107
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -3
- package/dist/index.d.ts +5 -3
- package/dist/index.js +230 -107
- package/dist/index.js.map +1 -1
- package/dist/{paths-DgO9oo6G.js → paths-CUv1qfz-.js} +228 -97
- package/dist/paths-CUv1qfz-.js.map +1 -0
- package/dist/plugins/rollup.js +1 -1
- package/dist/plugins/vite.js +1 -1
- package/package.json +1 -1
- package/dist/paths-DgO9oo6G.js.map +0 -1
|
@@ -1322,10 +1322,9 @@ function readUTF8(filename, diagnostics) {
|
|
|
1322
1322
|
function readBase64(filename) {
|
|
1323
1323
|
return readFileSync(filename).toString("base64");
|
|
1324
1324
|
}
|
|
1325
|
-
/** Get the filename without extension,
|
|
1325
|
+
/** Get the filename without extension, a dotfile keeps its whole name. */
|
|
1326
1326
|
function baseNameWithoutExt(filename) {
|
|
1327
|
-
|
|
1328
|
-
return name || ext;
|
|
1327
|
+
return parse(filename).name;
|
|
1329
1328
|
}
|
|
1330
1329
|
//#endregion
|
|
1331
1330
|
//#region src/formats.ts
|
|
@@ -1369,6 +1368,8 @@ function parseFormatObject(text, start, end) {
|
|
|
1369
1368
|
}
|
|
1370
1369
|
}
|
|
1371
1370
|
/** Check a parsed format object's fields. Per spec, `name` is optional; `version` and `source` are required. */
|
|
1371
|
+
/** The name given to a format.js that names no format. */
|
|
1372
|
+
const UNNAMED_FORMAT_NAME = "Untitled Story Format";
|
|
1372
1373
|
function toFormatJSON(raw) {
|
|
1373
1374
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return {
|
|
1374
1375
|
ok: false,
|
|
@@ -1388,7 +1389,7 @@ function toFormatJSON(raw) {
|
|
|
1388
1389
|
reason: `Story format version ${JSON.stringify(obj.version)} is not a SemVer version.`
|
|
1389
1390
|
};
|
|
1390
1391
|
const data = {
|
|
1391
|
-
name: typeof obj.name === "string" ? obj.name :
|
|
1392
|
+
name: typeof obj.name === "string" ? obj.name : UNNAMED_FORMAT_NAME,
|
|
1392
1393
|
version: obj.version,
|
|
1393
1394
|
source: obj.source,
|
|
1394
1395
|
proofing: obj.proofing === true
|
|
@@ -1847,31 +1848,37 @@ function clearIndexCache() {
|
|
|
1847
1848
|
/** Requests in progress, by what they fetch, so concurrent compiles make each request once. */
|
|
1848
1849
|
const sharedRequests = /* @__PURE__ */ new Map();
|
|
1849
1850
|
/**
|
|
1850
|
-
* Runs `start` once for all callers that ask for `key` while it is in progress.
|
|
1851
|
-
*
|
|
1852
|
-
*
|
|
1851
|
+
* Runs `start` once for all callers that ask for `key` while it is in progress. Each caller waits
|
|
1852
|
+
* under its own limits: a caller whose `signal` aborts, or whose `timeout` passes, stops waiting
|
|
1853
|
+
* at once and rejects, while the others keep waiting. The request itself is aborted (through
|
|
1854
|
+
* the signal `start` receives) once no caller waits on it. `start` must not depend on any one
|
|
1855
|
+
* caller: what differs between callers belongs after the shared result.
|
|
1853
1856
|
*/
|
|
1854
|
-
function shareRequest(key, signal, start) {
|
|
1857
|
+
function shareRequest(key, { signal, timeout, timedOut }, start) {
|
|
1855
1858
|
if (signal?.aborted) return Promise.reject(signal.reason);
|
|
1856
1859
|
const request = sharedRequests.get(key) ?? startSharedRequest(key, start);
|
|
1857
1860
|
request.waiters++;
|
|
1858
1861
|
return new Promise((resolve, reject) => {
|
|
1859
1862
|
let waiting = true;
|
|
1863
|
+
let timer;
|
|
1860
1864
|
const stopWaiting = () => {
|
|
1861
1865
|
if (!waiting) return;
|
|
1862
1866
|
waiting = false;
|
|
1863
1867
|
signal?.removeEventListener("abort", onAbort);
|
|
1868
|
+
clearTimeout(timer);
|
|
1864
1869
|
request.waiters--;
|
|
1865
1870
|
};
|
|
1866
|
-
const
|
|
1871
|
+
const giveUp = (reason) => {
|
|
1867
1872
|
stopWaiting();
|
|
1868
1873
|
if (request.waiters === 0) {
|
|
1869
1874
|
if (sharedRequests.get(key) === request) sharedRequests.delete(key);
|
|
1870
|
-
request.controller.abort(
|
|
1875
|
+
request.controller.abort(reason);
|
|
1871
1876
|
}
|
|
1872
|
-
reject(
|
|
1877
|
+
reject(reason);
|
|
1873
1878
|
};
|
|
1879
|
+
const onAbort = () => giveUp(signal?.reason);
|
|
1874
1880
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1881
|
+
if (timeout > 0 && timeout <= MAX_TIMER_DELAY) timer = setTimeout(() => giveUp(timedOut()), timeout);
|
|
1875
1882
|
request.promise.then((value) => {
|
|
1876
1883
|
stopWaiting();
|
|
1877
1884
|
resolve(value);
|
|
@@ -1901,20 +1908,32 @@ function requestTimeout(options) {
|
|
|
1901
1908
|
if (!(timeout >= 0)) throw new RangeError(`A story format request timeout must be 0 or more milliseconds, not ${timeout}`);
|
|
1902
1909
|
return timeout;
|
|
1903
1910
|
}
|
|
1911
|
+
/** How one caller waits for `url`: under its own signal and timeout. `what` names the request in errors. */
|
|
1912
|
+
function waitOptions(options, what, url) {
|
|
1913
|
+
const timeout = requestTimeout(options);
|
|
1914
|
+
return {
|
|
1915
|
+
signal: options.signal,
|
|
1916
|
+
timeout,
|
|
1917
|
+
timedOut: () => /* @__PURE__ */ new Error(`Failed to ${what} from ${url}: timed out after ${timeout} ms`)
|
|
1918
|
+
};
|
|
1919
|
+
}
|
|
1904
1920
|
/**
|
|
1905
|
-
* Fetch `url` as
|
|
1906
|
-
*
|
|
1921
|
+
* Fetch `url` as bytes, aborting when `signal` does. `what` names the request in errors, as in
|
|
1922
|
+
* "Failed to <what> from <url>". The bytes are returned undecoded, so checksums cover what was served.
|
|
1907
1923
|
*/
|
|
1908
|
-
async function
|
|
1909
|
-
const
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1924
|
+
async function fetchBytes(url, what, signal) {
|
|
1925
|
+
const res = await fetch(url, { signal });
|
|
1926
|
+
if (!res.ok) throw new Error(`Failed to ${what} from ${url}: ${res.status} ${res.statusText}`);
|
|
1927
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
1928
|
+
}
|
|
1929
|
+
/** Download a format.js once for every caller in this process that asks for `url` meanwhile. */
|
|
1930
|
+
function shareDownload(url, options) {
|
|
1931
|
+
return shareRequest(`download\0${url}`, waitOptions(options, "download format", url), (signal) => fetchBytes(url, "download format", signal));
|
|
1932
|
+
}
|
|
1933
|
+
/** Decode a downloaded format.js as local ones are (UTF-8, else Windows-1252), without a leading BOM. */
|
|
1934
|
+
function decodeDownload(bytes, url) {
|
|
1935
|
+
const { text } = decodeText(bytes, url);
|
|
1936
|
+
return text.charCodeAt(0) === 65279 ? text.slice(1) : text;
|
|
1918
1937
|
}
|
|
1919
1938
|
/** Validate that a JSON value is a valid SFAIndexEntry. */
|
|
1920
1939
|
function isValidEntry(val) {
|
|
@@ -1936,9 +1955,8 @@ async function fetchIndex(url, options = {}) {
|
|
|
1936
1955
|
options.signal?.throwIfAborted();
|
|
1937
1956
|
const cached = indexCache.get(url);
|
|
1938
1957
|
if (cached) return cached;
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
const text = await fetchText(url, "fetch format index", signal, timeout);
|
|
1958
|
+
return shareRequest(`index\0${url}`, waitOptions(options, "fetch format index", url), async (signal) => {
|
|
1959
|
+
const text = decodeDownload(await fetchBytes(url, "fetch format index", signal), url);
|
|
1942
1960
|
const data = validateSFAIndex(JSON.parse(text));
|
|
1943
1961
|
indexCache.set(url, data);
|
|
1944
1962
|
return data;
|
|
@@ -1975,26 +1993,37 @@ function cachedFormatInfo(id, filename, data) {
|
|
|
1975
1993
|
};
|
|
1976
1994
|
}
|
|
1977
1995
|
/**
|
|
1978
|
-
*
|
|
1979
|
-
*
|
|
1996
|
+
* Check that a downloaded format is the one the index entry promised. The name matches without
|
|
1997
|
+
* regard to case, as format requests do, and the version by SemVer precedence, so "1.0" and "v1.0.0"
|
|
1998
|
+
* agree. A format.js that names no format ({@link UNNAMED_FORMAT_NAME}) is allowed: such formats
|
|
1999
|
+
* exist, and the index entry then supplies the name it is cached under.
|
|
2000
|
+
*/
|
|
2001
|
+
function checkFormatIdentity(entry, data, downloadUrl) {
|
|
2002
|
+
if ((data.name === "Untitled Story Format" || data.name.toLowerCase() === entry.name.toLowerCase()) && sameVersion(data.version, entry.version)) return;
|
|
2003
|
+
throw new Error(`Story format mismatch for ${downloadUrl}: the index lists ${entry.name} ${entry.version}, but the download is ${data.name} ${data.version}`);
|
|
2004
|
+
}
|
|
2005
|
+
/**
|
|
2006
|
+
* Download a format, verify its checksum, check that it is the format the entry names, write it to
|
|
2007
|
+
* the cache shared by name and version, and return its StoryFormatInfo. Concurrent calls for one
|
|
2008
|
+
* download URL share one request for the bytes; each call then verifies them against its own
|
|
2009
|
+
* entry, within its own timeout.
|
|
1980
2010
|
*/
|
|
1981
2011
|
async function fetchAndCacheFormat(entry, downloadUrl, options = {}) {
|
|
1982
2012
|
cachedFormatDir(entry.name, entry.version);
|
|
1983
|
-
const
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
const
|
|
1987
|
-
if (
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
});
|
|
2013
|
+
const bytes = await shareDownload(downloadUrl, options);
|
|
2014
|
+
const checksumKey = Object.keys(entry.checksums ?? {}).find((k) => k.endsWith("format.js"));
|
|
2015
|
+
if (checksumKey) {
|
|
2016
|
+
const expected = entry.checksums[checksumKey];
|
|
2017
|
+
if (!expected) throw new Error(`Missing checksum value for key "${checksumKey}"`);
|
|
2018
|
+
if (!await verifySHA256(bytes, expected)) throw new Error(`Checksum verification failed for ${entry.name} ${entry.version}`);
|
|
2019
|
+
}
|
|
2020
|
+
const text = decodeDownload(bytes, downloadUrl);
|
|
2021
|
+
const id = makeFormatId(entry.name, entry.version);
|
|
2022
|
+
const data = parseFormatJSON(text, id);
|
|
2023
|
+
if (!data) throw new Error(`Failed to parse format JSON from ${downloadUrl}`);
|
|
2024
|
+
checkFormatIdentity(entry, data, downloadUrl);
|
|
2025
|
+
options.signal?.throwIfAborted();
|
|
2026
|
+
return cachedFormatInfo(id, writeCacheEntry(resolve(getCacheDir()), [entry.name, entry.version], text), data);
|
|
1998
2027
|
}
|
|
1999
2028
|
/**
|
|
2000
2029
|
* Download a direct format.js URL, parse its JSON, cache it under that URL (apart from the
|
|
@@ -2002,16 +2031,13 @@ async function fetchAndCacheFormat(entry, downloadUrl, options = {}) {
|
|
|
2002
2031
|
* one URL share one request.
|
|
2003
2032
|
*/
|
|
2004
2033
|
async function fetchDirectFormat(url, options = {}) {
|
|
2005
|
-
const
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
const formatPath = writeCacheEntry(getUrlCacheDir(), [urlCacheKey(url)], text);
|
|
2013
|
-
return cachedFormatInfo(makeFormatId(data.name, data.version), formatPath, data);
|
|
2014
|
-
});
|
|
2034
|
+
const text = decodeDownload(await shareDownload(url, options), url);
|
|
2035
|
+
const data = parseFormatJSON(text);
|
|
2036
|
+
if (!data) throw new Error(`Failed to parse format JSON from ${url}`);
|
|
2037
|
+
cachedFormatDir(data.name, data.version);
|
|
2038
|
+
options.signal?.throwIfAborted();
|
|
2039
|
+
const formatPath = writeCacheEntry(getUrlCacheDir(), [urlCacheKey(url)], text);
|
|
2040
|
+
return cachedFormatInfo(makeFormatId(data.name, data.version), formatPath, data);
|
|
2015
2041
|
}
|
|
2016
2042
|
/** The copy of a direct format URL in the download cache, if it has been downloaded before. */
|
|
2017
2043
|
function getCachedDirectFormat(url) {
|
|
@@ -2475,8 +2501,6 @@ function* tweeLexer(input) {
|
|
|
2475
2501
|
const items = ctx.drainItems();
|
|
2476
2502
|
for (const item of items) yield item;
|
|
2477
2503
|
}
|
|
2478
|
-
const remaining = ctx.drainItems();
|
|
2479
|
-
for (const item of remaining) yield item;
|
|
2480
2504
|
}
|
|
2481
2505
|
//#endregion
|
|
2482
2506
|
//#region src/twee2-compat.ts
|
|
@@ -2603,9 +2627,7 @@ function parseTwee(source, options = {}) {
|
|
|
2603
2627
|
};
|
|
2604
2628
|
}
|
|
2605
2629
|
try {
|
|
2606
|
-
const
|
|
2607
|
-
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) throw new Error("expected a JSON object");
|
|
2608
|
-
const parsed = raw;
|
|
2630
|
+
const parsed = JSON.parse(item.val);
|
|
2609
2631
|
const meta = {};
|
|
2610
2632
|
for (const [key, value] of Object.entries(parsed)) if (typeof value === "string") meta[key] = value;
|
|
2611
2633
|
current.metadata = meta;
|
|
@@ -5850,8 +5872,8 @@ function loadFont(story, filename, diagnostics) {
|
|
|
5850
5872
|
* Load sources with mtime-based caching for incremental rebuilds.
|
|
5851
5873
|
* Always creates a fresh Story; cached passages are replayed via storyAdd().
|
|
5852
5874
|
*/
|
|
5853
|
-
function loadSourcesCached(story, filenames, opts, diagnostics, processedFiles, cache, changedFiles) {
|
|
5854
|
-
const currentFiles =
|
|
5875
|
+
function loadSourcesCached(story, filenames, opts, diagnostics, processedFiles, cache, changedFiles, buildFiles) {
|
|
5876
|
+
const currentFiles = new Set(buildFiles);
|
|
5855
5877
|
const changedPaths = changedFiles === void 0 ? void 0 : new Set([...changedFiles].map((f) => resolve(f)));
|
|
5856
5878
|
for (const filename of filenames) {
|
|
5857
5879
|
if (processedFiles.has(filename)) {
|
|
@@ -5916,16 +5938,6 @@ function loadSourcesCached(story, filenames, opts, diagnostics, processedFiles,
|
|
|
5916
5938
|
}
|
|
5917
5939
|
//#endregion
|
|
5918
5940
|
//#region src/template.ts
|
|
5919
|
-
/**
|
|
5920
|
-
* An HTML closing head tag: `</head` in any letter case, followed by whitespace, `/` or `>`, up to the next `>`.
|
|
5921
|
-
* This is how the HTML tokenizer reads an end tag; it ignores whatever sits between the tag name and the `>`.
|
|
5922
|
-
*/
|
|
5923
|
-
const CLOSING_HEAD_TAG = "<\\/[Hh][Ee][Aa][Dd](?=[\\t\\n\\f\\r />])[^>]*>";
|
|
5924
|
-
/**
|
|
5925
|
-
* The start of an HTML body start tag: `<body` in any letter case, followed by whitespace, `/` or `>` (so not
|
|
5926
|
-
* `<bodyx>`). Only the `<body` is matched; the attributes and `>` after it are left alone.
|
|
5927
|
-
*/
|
|
5928
|
-
const BODY_START_TAG = "<[Bb][Oo][Dd][Yy](?=[\\t\\n\\f\\r />])";
|
|
5929
5941
|
/** The regular expression source matching exactly `text`. */
|
|
5930
5942
|
function literal(text) {
|
|
5931
5943
|
return text.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&");
|
|
@@ -5951,6 +5963,8 @@ function fillTemplateParts(parts) {
|
|
|
5951
5963
|
return part.text.replace(pattern, (match, ...groups) => {
|
|
5952
5964
|
const slot = slots.find((_, i) => groups[i] !== void 0);
|
|
5953
5965
|
if (slot === void 0 || slot.occurrences === "first" && filled.has(slot)) return match;
|
|
5966
|
+
const offset = groups[slots.length];
|
|
5967
|
+
if (slot.accepts !== void 0 && typeof offset === "number" && !slot.accepts(part.text, offset)) return match;
|
|
5954
5968
|
filled.add(slot);
|
|
5955
5969
|
return slot.replacement(match);
|
|
5956
5970
|
});
|
|
@@ -6041,15 +6055,18 @@ function placeHead(content, texts, owner) {
|
|
|
6041
6055
|
slot: void 0,
|
|
6042
6056
|
diagnostics: []
|
|
6043
6057
|
};
|
|
6044
|
-
const
|
|
6045
|
-
|
|
6046
|
-
|
|
6058
|
+
const scans = texts.map((text) => ({
|
|
6059
|
+
text,
|
|
6060
|
+
tags: scanHeadTags(text)
|
|
6061
|
+
}));
|
|
6062
|
+
if (scans.some(({ tags }) => tags.closingHead !== void 0)) return {
|
|
6063
|
+
slot: beforeTag(CLOSING_HEAD_PREFIX, "closingHead", content),
|
|
6047
6064
|
diagnostics: []
|
|
6048
6065
|
};
|
|
6049
|
-
if (
|
|
6066
|
+
if (scans.some(({ tags }) => tags.bodyStart !== void 0)) {
|
|
6050
6067
|
const message = `${owner} has no closing head tag; the modules and head file were injected before its body start tag.`;
|
|
6051
6068
|
return {
|
|
6052
|
-
slot: beforeTag(
|
|
6069
|
+
slot: beforeTag(BODY_START_PREFIX, "bodyStart", content),
|
|
6053
6070
|
diagnostics: [{
|
|
6054
6071
|
level: "warning",
|
|
6055
6072
|
message
|
|
@@ -6064,12 +6081,98 @@ function placeHead(content, texts, owner) {
|
|
|
6064
6081
|
}]
|
|
6065
6082
|
};
|
|
6066
6083
|
}
|
|
6067
|
-
/**
|
|
6068
|
-
|
|
6084
|
+
/** `</head` in any letter case, followed by whitespace, `/` or `>`; the rest of the tag is left alone. */
|
|
6085
|
+
const CLOSING_HEAD_PREFIX = "<\\/[Hh][Ee][Aa][Dd](?=[\\t\\n\\f\\r />])";
|
|
6086
|
+
/** `<body` in any letter case, followed by whitespace, `/` or `>` (so not `<bodyx>`). */
|
|
6087
|
+
const BODY_START_PREFIX = "<[Bb][Oo][Dd][Yy](?=[\\t\\n\\f\\r />])";
|
|
6088
|
+
/** Elements whose content is text, so a tag written there is not a tag. */
|
|
6089
|
+
const RAW_TEXT_ELEMENTS = /* @__PURE__ */ new Set([
|
|
6090
|
+
"script",
|
|
6091
|
+
"style",
|
|
6092
|
+
"textarea",
|
|
6093
|
+
"title"
|
|
6094
|
+
]);
|
|
6095
|
+
const isSpace = (c) => c === " " || c === " " || c === "\n" || c === "\f" || c === "\r";
|
|
6096
|
+
const isLetter = (c) => c !== void 0 && /^[A-Za-z]$/.test(c);
|
|
6097
|
+
/**
|
|
6098
|
+
* Find the first closing head tag and body start tag that HTML would read as tags: not those in a comment, in
|
|
6099
|
+
* the text of a script, style, textarea or title element, or in a quoted attribute value. A small scanner, not
|
|
6100
|
+
* a full HTML tokenizer; it needs no more than the tags and where they end.
|
|
6101
|
+
*/
|
|
6102
|
+
function scanHeadTags(html) {
|
|
6103
|
+
let closingHead;
|
|
6104
|
+
let bodyStart;
|
|
6105
|
+
let i = 0;
|
|
6106
|
+
while (i < html.length) {
|
|
6107
|
+
const lt = html.indexOf("<", i);
|
|
6108
|
+
if (lt === -1) break;
|
|
6109
|
+
const next = html[lt + 1];
|
|
6110
|
+
if (html.startsWith("<!--", lt)) {
|
|
6111
|
+
const end = html.indexOf("-->", lt + 4);
|
|
6112
|
+
i = end === -1 ? html.length : end + 3;
|
|
6113
|
+
} else if (next === "!" || next === "?") {
|
|
6114
|
+
const end = html.indexOf(">", lt);
|
|
6115
|
+
i = end === -1 ? html.length : end + 1;
|
|
6116
|
+
} else if (next === "/" && isLetter(html[lt + 2])) {
|
|
6117
|
+
const { name, end } = readTag(html, lt + 2);
|
|
6118
|
+
if (name === "head" && closingHead === void 0) closingHead = lt;
|
|
6119
|
+
i = end;
|
|
6120
|
+
} else if (isLetter(next)) {
|
|
6121
|
+
const { name, end } = readTag(html, lt + 1);
|
|
6122
|
+
if (name === "body" && bodyStart === void 0) bodyStart = lt;
|
|
6123
|
+
i = RAW_TEXT_ELEMENTS.has(name) ? rawTextEnd(html, name, end) : end;
|
|
6124
|
+
} else i = lt + 1;
|
|
6125
|
+
if (closingHead !== void 0 && bodyStart !== void 0) break;
|
|
6126
|
+
}
|
|
6069
6127
|
return {
|
|
6070
|
-
|
|
6128
|
+
closingHead,
|
|
6129
|
+
bodyStart
|
|
6130
|
+
};
|
|
6131
|
+
}
|
|
6132
|
+
/** Read a tag whose name starts at `from`: its lowercase name, and the offset after its `>` (or the end). */
|
|
6133
|
+
function readTag(html, from) {
|
|
6134
|
+
let i = from;
|
|
6135
|
+
while (i < html.length && !isSpace(html[i]) && html[i] !== "/" && html[i] !== ">") i++;
|
|
6136
|
+
const name = html.slice(from, i).toLowerCase();
|
|
6137
|
+
while (i < html.length && html[i] !== ">") {
|
|
6138
|
+
if (html[i] !== "=") {
|
|
6139
|
+
i++;
|
|
6140
|
+
continue;
|
|
6141
|
+
}
|
|
6142
|
+
i++;
|
|
6143
|
+
while (isSpace(html[i])) i++;
|
|
6144
|
+
const quote = html[i];
|
|
6145
|
+
if (quote === "\"" || quote === "'") {
|
|
6146
|
+
const close = html.indexOf(quote, i + 1);
|
|
6147
|
+
i = close === -1 ? html.length : close + 1;
|
|
6148
|
+
}
|
|
6149
|
+
}
|
|
6150
|
+
return {
|
|
6151
|
+
name,
|
|
6152
|
+
end: Math.min(i + 1, html.length)
|
|
6153
|
+
};
|
|
6154
|
+
}
|
|
6155
|
+
/** The offset of the end tag that closes a raw text element, given where its text starts (or the end). */
|
|
6156
|
+
function rawTextEnd(html, name, from) {
|
|
6157
|
+
const close = new RegExp(`</${name}(?=[\\t\\n\\f\\r />])`, "gi");
|
|
6158
|
+
close.lastIndex = from;
|
|
6159
|
+
return close.exec(html)?.index ?? html.length;
|
|
6160
|
+
}
|
|
6161
|
+
/**
|
|
6162
|
+
* The slot that inserts `content` on its own line before the first real tag of the kind `which` names (see
|
|
6163
|
+
* `scanHeadTags()`), which is kept as written. A look-alike inside a script or comment is left alone.
|
|
6164
|
+
*/
|
|
6165
|
+
function beforeTag(pattern, which, content) {
|
|
6166
|
+
const cache = /* @__PURE__ */ new Map();
|
|
6167
|
+
const offsetIn = (text) => {
|
|
6168
|
+
if (!cache.has(text)) cache.set(text, scanHeadTags(text)[which]);
|
|
6169
|
+
return cache.get(text);
|
|
6170
|
+
};
|
|
6171
|
+
return {
|
|
6172
|
+
pattern,
|
|
6071
6173
|
occurrences: "first",
|
|
6072
|
-
replacement: (match) => `${content}\n${match}
|
|
6174
|
+
replacement: (match) => `${content}\n${match}`,
|
|
6175
|
+
accepts: (text, offset) => offsetIn(text) === offset
|
|
6073
6176
|
};
|
|
6074
6177
|
}
|
|
6075
6178
|
//#endregion
|
|
@@ -6532,34 +6635,61 @@ async function buildOutput(options, cache, changedFiles, outputs) {
|
|
|
6532
6635
|
const timeout = options.formatFetchTimeout;
|
|
6533
6636
|
if (timeout !== void 0 && !(timeout >= 0)) throw new TweeTsError(`formatFetchTimeout must be 0 or more milliseconds, not ${timeout}.`);
|
|
6534
6637
|
clearIndexCache();
|
|
6535
|
-
const
|
|
6536
|
-
const
|
|
6537
|
-
for (const source of options.sources)
|
|
6538
|
-
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
|
|
6542
|
-
|
|
6638
|
+
const groups = [];
|
|
6639
|
+
const outputSources = [];
|
|
6640
|
+
for (const source of options.sources) {
|
|
6641
|
+
const last = groups[groups.length - 1];
|
|
6642
|
+
if (typeof source === "string") {
|
|
6643
|
+
if (last?.kind === "paths") last.paths.push(source);
|
|
6644
|
+
else groups.push({
|
|
6645
|
+
kind: "paths",
|
|
6646
|
+
paths: [source]
|
|
6647
|
+
});
|
|
6648
|
+
} else if (last?.kind === "inline") last.sources.push(source);
|
|
6649
|
+
else groups.push({
|
|
6650
|
+
kind: "inline",
|
|
6651
|
+
sources: [source]
|
|
6652
|
+
});
|
|
6653
|
+
}
|
|
6654
|
+
const walked = groups.map((group) => {
|
|
6655
|
+
if (group.kind === "inline") return group;
|
|
6656
|
+
const found = getFilenames(group.paths, written, options.exclude);
|
|
6657
|
+
diagnostics.push(...found.diagnostics);
|
|
6658
|
+
outputSources.push(...found.outputSources);
|
|
6659
|
+
return {
|
|
6660
|
+
kind: "files",
|
|
6661
|
+
filenames: found.filenames
|
|
6662
|
+
};
|
|
6663
|
+
});
|
|
6664
|
+
rejectOutputSources(outputSources, diagnostics);
|
|
6665
|
+
const buildFiles = new Set(walked.flatMap((group) => group.kind === "files" ? group.filenames : []));
|
|
6543
6666
|
const story = createStory();
|
|
6544
6667
|
const processedFiles = /* @__PURE__ */ new Set();
|
|
6545
|
-
if (
|
|
6668
|
+
for (const group of walked) if (group.kind === "inline") loadInlineSources(story, group.sources, {
|
|
6546
6669
|
trim,
|
|
6547
6670
|
twee2Compat
|
|
6548
|
-
}, diagnostics
|
|
6549
|
-
else
|
|
6671
|
+
}, diagnostics);
|
|
6672
|
+
else if (cache) loadSourcesCached(story, group.filenames, {
|
|
6673
|
+
trim,
|
|
6674
|
+
twee2Compat
|
|
6675
|
+
}, diagnostics, processedFiles, cache, changedFiles, buildFiles);
|
|
6676
|
+
else loadSources(story, group.filenames, {
|
|
6550
6677
|
trim,
|
|
6551
6678
|
twee2Compat
|
|
6552
6679
|
}, diagnostics, processedFiles);
|
|
6553
|
-
|
|
6680
|
+
if (cache && buildFiles.size === 0) loadSourcesCached(story, [], {
|
|
6554
6681
|
trim,
|
|
6555
6682
|
twee2Compat
|
|
6556
|
-
}, diagnostics);
|
|
6683
|
+
}, diagnostics, processedFiles, cache, changedFiles);
|
|
6557
6684
|
if (options.tagAliases) story.passages = applyTagAliases(story.passages, options.tagAliases);
|
|
6558
6685
|
let format;
|
|
6559
|
-
if (outputMode === "html")
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
6686
|
+
if (outputMode === "html") {
|
|
6687
|
+
format = await resolveStoryFormat(formatRequestFor(options.formatId, story.twine2.format, story.twine2.formatVersion, DEFAULT_FORMAT_ID), {
|
|
6688
|
+
...options,
|
|
6689
|
+
noRemote
|
|
6690
|
+
}, diagnostics);
|
|
6691
|
+
options.signal?.throwIfAborted();
|
|
6692
|
+
}
|
|
6563
6693
|
if (options.startPassage) story.twine2.start = options.startPassage;
|
|
6564
6694
|
const startName = story.twine2.start || DEFAULT_START_NAME;
|
|
6565
6695
|
if (testMode) story.twine2.options.set("debug", true);
|
|
@@ -6611,6 +6741,7 @@ async function buildOutput(options, cache, changedFiles, outputs) {
|
|
|
6611
6741
|
...getStoryStats(story, options.wordCountMethod),
|
|
6612
6742
|
files: [...processedFiles]
|
|
6613
6743
|
};
|
|
6744
|
+
options.signal?.throwIfAborted();
|
|
6614
6745
|
return {
|
|
6615
6746
|
output,
|
|
6616
6747
|
story,
|
|
@@ -6792,4 +6923,4 @@ function isViteConfigTemp(file) {
|
|
|
6792
6923
|
//#endregion
|
|
6793
6924
|
export { toPosix as a, splitDiagnostics as c, isExcluded as d, outputPaths as f, mediaTypeFromFilename as h, outputLocations as i, compileForOutputFile as l, walkedEntry as m, isInside as n, fatalError as o, realPathOf as p, isViteConfigTemp as r, formatDiagnostic as s, createOutputRecord as t, getFilenames as u };
|
|
6794
6925
|
|
|
6795
|
-
//# sourceMappingURL=paths-
|
|
6926
|
+
//# sourceMappingURL=paths-CUv1qfz-.js.map
|