@rohal12/twee-ts 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/twee-ts.js +266 -184
- package/dist/bin/twee-ts.js.map +1 -1
- package/dist/{chunk-QPA7RLK5.js → chunk-S4SWCQLW.js} +253 -174
- package/dist/chunk-S4SWCQLW.js.map +1 -0
- package/dist/index.cjs +286 -209
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +255 -178
- package/dist/index.js.map +1 -1
- package/dist/plugins/rollup.d.ts +7 -1
- package/dist/plugins/rollup.js +2 -2
- package/dist/plugins/rollup.js.map +1 -1
- package/dist/plugins/vite.d.ts +7 -1
- package/dist/plugins/vite.js +2 -2
- package/dist/plugins/vite.js.map +1 -1
- package/package.json +5 -2
- package/dist/chunk-QPA7RLK5.js.map +0 -1
|
@@ -69,13 +69,26 @@ function tweeEscape(s) {
|
|
|
69
69
|
if (s.length === 0) return s;
|
|
70
70
|
return s.replace(/[\\[\]{}]/g, (ch) => "\\" + ch);
|
|
71
71
|
}
|
|
72
|
+
function jsStringEscape(s) {
|
|
73
|
+
if (s.length === 0) return s;
|
|
74
|
+
return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
|
|
75
|
+
}
|
|
76
|
+
function commentSanitize(s) {
|
|
77
|
+
return s.replace(/\*\//g, "* /");
|
|
78
|
+
}
|
|
79
|
+
function htmlCommentSanitize(s) {
|
|
80
|
+
return s.replace(/-->/g, "-- >");
|
|
81
|
+
}
|
|
72
82
|
function tweeUnescape(s) {
|
|
73
83
|
if (s.length === 0) return s;
|
|
74
84
|
let result = "";
|
|
75
85
|
for (let i = 0; i < s.length; i++) {
|
|
76
86
|
if (s[i] === "\\") {
|
|
77
87
|
i++;
|
|
78
|
-
if (i >= s.length)
|
|
88
|
+
if (i >= s.length) {
|
|
89
|
+
result += "\\";
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
79
92
|
}
|
|
80
93
|
result += s[i];
|
|
81
94
|
}
|
|
@@ -165,16 +178,16 @@ function passageToPassagedata(p, pid) {
|
|
|
165
178
|
let position;
|
|
166
179
|
let size;
|
|
167
180
|
if (hasMetadataPosition(p)) {
|
|
168
|
-
position = p.metadata
|
|
181
|
+
position = p.metadata?.position ?? "";
|
|
169
182
|
} else {
|
|
170
183
|
const x = pid % 10;
|
|
171
|
-
|
|
184
|
+
const y = Math.floor(pid / 10);
|
|
172
185
|
const xp = x === 0 ? 10 : x;
|
|
173
186
|
const yp = x === 0 ? y : y + 1;
|
|
174
187
|
position = `${xp * 125 - 25},${yp * 125 - 25}`;
|
|
175
188
|
}
|
|
176
189
|
if (hasMetadataSize(p)) {
|
|
177
|
-
size = p.metadata
|
|
190
|
+
size = p.metadata?.size ?? "";
|
|
178
191
|
} else {
|
|
179
192
|
size = "100,100";
|
|
180
193
|
}
|
|
@@ -183,10 +196,10 @@ function passageToPassagedata(p, pid) {
|
|
|
183
196
|
function passageToTiddler(p, pid) {
|
|
184
197
|
let position;
|
|
185
198
|
if (hasMetadataPosition(p)) {
|
|
186
|
-
position = p.metadata
|
|
199
|
+
position = p.metadata?.position ?? "";
|
|
187
200
|
} else {
|
|
188
201
|
const x = pid % 10;
|
|
189
|
-
|
|
202
|
+
const y = Math.floor(pid / 10);
|
|
190
203
|
const xp = x === 0 ? 10 : x;
|
|
191
204
|
const yp = x === 0 ? y : y + 1;
|
|
192
205
|
position = `${xp * 140 - 130},${yp * 140 - 130}`;
|
|
@@ -205,15 +218,17 @@ function countWords(p) {
|
|
|
205
218
|
}
|
|
206
219
|
function applyTagAliases(passages, aliases) {
|
|
207
220
|
const entries = Object.entries(aliases);
|
|
208
|
-
if (entries.length === 0) return;
|
|
209
|
-
|
|
210
|
-
const original =
|
|
221
|
+
if (entries.length === 0) return [...passages];
|
|
222
|
+
return passages.map((p) => {
|
|
223
|
+
const original = p.tags;
|
|
224
|
+
const added = [];
|
|
211
225
|
for (const [alias, canonical] of entries) {
|
|
212
|
-
if (original.includes(alias) && !
|
|
213
|
-
|
|
226
|
+
if (original.includes(alias) && !original.includes(canonical) && !added.includes(canonical)) {
|
|
227
|
+
added.push(canonical);
|
|
214
228
|
}
|
|
215
229
|
}
|
|
216
|
-
|
|
230
|
+
return added.length > 0 ? { ...p, tags: [...original, ...added] } : p;
|
|
231
|
+
});
|
|
217
232
|
}
|
|
218
233
|
function quote(s) {
|
|
219
234
|
return `"${s}"`;
|
|
@@ -285,27 +300,32 @@ function marshalStoryData(story) {
|
|
|
285
300
|
return JSON.stringify(data, null, " ");
|
|
286
301
|
}
|
|
287
302
|
function unmarshalStoryData(story, json) {
|
|
288
|
-
let
|
|
303
|
+
let raw;
|
|
289
304
|
try {
|
|
290
|
-
|
|
305
|
+
raw = JSON.parse(json);
|
|
291
306
|
} catch (e) {
|
|
292
307
|
return `Cannot unmarshal "StoryData"; ${e instanceof Error ? e.message : String(e)}`;
|
|
293
308
|
}
|
|
294
|
-
if (
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
309
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
310
|
+
return 'Cannot unmarshal "StoryData"; expected a JSON object';
|
|
311
|
+
}
|
|
312
|
+
const data = raw;
|
|
313
|
+
if (typeof data.ifid === "string" && data.ifid) story.ifid = data.ifid.toUpperCase();
|
|
314
|
+
if (typeof data.format === "string" && data.format) story.twine2.format = data.format;
|
|
315
|
+
if (typeof data["format-version"] === "string" && data["format-version"])
|
|
316
|
+
story.twine2.formatVersion = data["format-version"];
|
|
317
|
+
if (Array.isArray(data.options)) {
|
|
298
318
|
for (const opt of data.options) {
|
|
299
|
-
story.twine2.options.set(opt, true);
|
|
319
|
+
if (typeof opt === "string") story.twine2.options.set(opt, true);
|
|
300
320
|
}
|
|
301
321
|
}
|
|
302
|
-
if (data.start) story.twine2.start = data.start;
|
|
303
|
-
if (data["tag-colors"]) {
|
|
322
|
+
if (typeof data.start === "string" && data.start) story.twine2.start = data.start;
|
|
323
|
+
if (typeof data["tag-colors"] === "object" && data["tag-colors"] !== null && !Array.isArray(data["tag-colors"])) {
|
|
304
324
|
for (const [tag, color] of Object.entries(data["tag-colors"])) {
|
|
305
|
-
story.twine2.tagColors.set(tag, color);
|
|
325
|
+
if (typeof color === "string") story.twine2.tagColors.set(tag, color);
|
|
306
326
|
}
|
|
307
327
|
}
|
|
308
|
-
if (data.zoom
|
|
328
|
+
if (typeof data.zoom === "number" && data.zoom !== 0) story.twine2.zoom = data.zoom;
|
|
309
329
|
return null;
|
|
310
330
|
}
|
|
311
331
|
function unmarshalStorySettings(story, text, diagnostics) {
|
|
@@ -348,6 +368,7 @@ function unmarshalStorySettings(story, text, diagnostics) {
|
|
|
348
368
|
}
|
|
349
369
|
}
|
|
350
370
|
function storyAdd(story, p, diagnostics) {
|
|
371
|
+
let processed = p;
|
|
351
372
|
switch (p.name) {
|
|
352
373
|
case "StoryIncludes":
|
|
353
374
|
diagnostics.push({
|
|
@@ -367,7 +388,7 @@ function storyAdd(story, p, diagnostics) {
|
|
|
367
388
|
});
|
|
368
389
|
}
|
|
369
390
|
}
|
|
370
|
-
p
|
|
391
|
+
processed = { ...p, text: marshalStoryData(story) };
|
|
371
392
|
} else {
|
|
372
393
|
diagnostics.push({
|
|
373
394
|
level: "error",
|
|
@@ -379,12 +400,14 @@ function storyAdd(story, p, diagnostics) {
|
|
|
379
400
|
case "StorySettings":
|
|
380
401
|
unmarshalStorySettings(story, p.text, diagnostics);
|
|
381
402
|
break;
|
|
382
|
-
case "StoryTitle":
|
|
383
|
-
|
|
384
|
-
|
|
403
|
+
case "StoryTitle": {
|
|
404
|
+
const trimmed = p.text.trim();
|
|
405
|
+
processed = { ...p, text: trimmed };
|
|
406
|
+
story.name = trimmed;
|
|
385
407
|
break;
|
|
408
|
+
}
|
|
386
409
|
}
|
|
387
|
-
storyAppend(story,
|
|
410
|
+
storyAppend(story, processed, diagnostics);
|
|
388
411
|
}
|
|
389
412
|
function getStoryStats(story) {
|
|
390
413
|
let storyPassages = 0;
|
|
@@ -438,7 +461,7 @@ var MEDIA_TYPE_MAP = {
|
|
|
438
461
|
webm: "video/webm"
|
|
439
462
|
};
|
|
440
463
|
function mediaTypeFromExt(ext) {
|
|
441
|
-
return MEDIA_TYPE_MAP[ext] ?? "";
|
|
464
|
+
return MEDIA_TYPE_MAP[ext] ?? "application/octet-stream";
|
|
442
465
|
}
|
|
443
466
|
function mediaTypeFromFilename(filename) {
|
|
444
467
|
return mediaTypeFromExt(normalizedFileExt(filename));
|
|
@@ -497,23 +520,49 @@ function getFilenames(pathnames, outFilename) {
|
|
|
497
520
|
}
|
|
498
521
|
|
|
499
522
|
// src/formats.ts
|
|
500
|
-
import {
|
|
523
|
+
import { readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
501
524
|
import { join as join2 } from "path";
|
|
502
525
|
import { homedir } from "os";
|
|
526
|
+
|
|
527
|
+
// src/util.ts
|
|
528
|
+
import { readFileSync } from "fs";
|
|
529
|
+
import { parse as parsePath } from "path";
|
|
530
|
+
function readUTF8(filename) {
|
|
531
|
+
let content = readFileSync(filename, "utf-8");
|
|
532
|
+
if (content.charCodeAt(0) === 65279) content = content.slice(1);
|
|
533
|
+
content = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
534
|
+
return content;
|
|
535
|
+
}
|
|
536
|
+
function readBase64(filename) {
|
|
537
|
+
return readFileSync(filename).toString("base64");
|
|
538
|
+
}
|
|
539
|
+
function baseNameWithoutExt(filename) {
|
|
540
|
+
const { name, ext } = parsePath(filename);
|
|
541
|
+
return name || ext;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// src/formats.ts
|
|
503
545
|
function parseFormatJSON(source, formatId) {
|
|
504
546
|
const first = source.indexOf("{");
|
|
505
547
|
const last = source.lastIndexOf("}");
|
|
506
548
|
if (first === -1 || last === -1) return null;
|
|
507
549
|
let chunk = source.slice(first, last + 1);
|
|
550
|
+
const parse = (json) => {
|
|
551
|
+
const raw = JSON.parse(json);
|
|
552
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null;
|
|
553
|
+
const obj = raw;
|
|
554
|
+
if (typeof obj.name !== "string" || typeof obj.version !== "string" || typeof obj.source !== "string") return null;
|
|
555
|
+
return { name: obj.name, version: obj.version, source: obj.source, proofing: obj.proofing === true };
|
|
556
|
+
};
|
|
508
557
|
try {
|
|
509
|
-
return
|
|
558
|
+
return parse(chunk);
|
|
510
559
|
} catch {
|
|
511
560
|
if (formatId.toLowerCase().startsWith("harlowe")) {
|
|
512
561
|
const setupIdx = chunk.lastIndexOf(',"setup": function');
|
|
513
562
|
if (setupIdx !== -1) {
|
|
514
563
|
chunk = chunk.slice(0, setupIdx) + "}";
|
|
515
564
|
try {
|
|
516
|
-
return
|
|
565
|
+
return parse(chunk);
|
|
517
566
|
} catch {
|
|
518
567
|
}
|
|
519
568
|
}
|
|
@@ -521,12 +570,6 @@ function parseFormatJSON(source, formatId) {
|
|
|
521
570
|
return null;
|
|
522
571
|
}
|
|
523
572
|
}
|
|
524
|
-
function readUTF8(filename) {
|
|
525
|
-
let content = readFileSync(filename, "utf-8");
|
|
526
|
-
if (content.charCodeAt(0) === 65279) content = content.slice(1);
|
|
527
|
-
content = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
528
|
-
return content;
|
|
529
|
-
}
|
|
530
573
|
function discoverFormats(searchDirs) {
|
|
531
574
|
const formats = /* @__PURE__ */ new Map();
|
|
532
575
|
const baseFilenames = ["format.js", "header.html"];
|
|
@@ -621,9 +664,11 @@ function parseSemver(v) {
|
|
|
621
664
|
return [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
622
665
|
}
|
|
623
666
|
function semverCompare(a, b) {
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
667
|
+
const [aMajor, aMinor, aPatch] = a;
|
|
668
|
+
const [bMajor, bMinor, bPatch] = b;
|
|
669
|
+
if (aMajor !== bMajor) return aMajor - bMajor;
|
|
670
|
+
if (aMinor !== bMinor) return aMinor - bMinor;
|
|
671
|
+
if (aPatch !== bPatch) return aPatch - bPatch;
|
|
627
672
|
return 0;
|
|
628
673
|
}
|
|
629
674
|
function getFormatIdByNameAndVersion(formats, name, version) {
|
|
@@ -654,7 +699,6 @@ function readFormatSource(format) {
|
|
|
654
699
|
}
|
|
655
700
|
|
|
656
701
|
// src/loader.ts
|
|
657
|
-
import { readFileSync as readFileSync2 } from "fs";
|
|
658
702
|
import { basename } from "path";
|
|
659
703
|
|
|
660
704
|
// src/lexer.ts
|
|
@@ -931,13 +975,13 @@ function hasTwee2Syntax(s) {
|
|
|
931
975
|
}
|
|
932
976
|
function twee2ToV3(s) {
|
|
933
977
|
if (!hasTwee2Syntax(s)) return s;
|
|
934
|
-
|
|
978
|
+
let result = s.replace(twee2HeaderRe, (_, p1, p2, p3) => {
|
|
935
979
|
const tags = p2 ?? "";
|
|
936
980
|
const pos = p3 ?? "";
|
|
937
981
|
return `${p1}${tags} {"position":"${pos}"}`;
|
|
938
982
|
});
|
|
939
|
-
|
|
940
|
-
return
|
|
983
|
+
result = result.replace(twee2BadPosRe, "$1");
|
|
984
|
+
return result;
|
|
941
985
|
}
|
|
942
986
|
|
|
943
987
|
// src/parser.ts
|
|
@@ -1015,7 +1059,11 @@ function parseTwee(source, options = {}) {
|
|
|
1015
1059
|
return { passages, diagnostics };
|
|
1016
1060
|
}
|
|
1017
1061
|
try {
|
|
1018
|
-
const
|
|
1062
|
+
const raw = JSON.parse(item.val);
|
|
1063
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
1064
|
+
throw new Error("expected a JSON object");
|
|
1065
|
+
}
|
|
1066
|
+
const parsed = raw;
|
|
1019
1067
|
const meta = {};
|
|
1020
1068
|
if (typeof parsed.position === "string") meta.position = parsed.position;
|
|
1021
1069
|
if (typeof parsed.size === "string") meta.size = parsed.size;
|
|
@@ -1035,6 +1083,16 @@ function parseTwee(source, options = {}) {
|
|
|
1035
1083
|
current.text = trim ? item.val.trim() : item.val;
|
|
1036
1084
|
break;
|
|
1037
1085
|
}
|
|
1086
|
+
default: {
|
|
1087
|
+
const _exhaustive = item.type;
|
|
1088
|
+
diagnostics.push({
|
|
1089
|
+
level: "error",
|
|
1090
|
+
message: `line ${item.line}: Unhandled lexer item type: ${_exhaustive}.`,
|
|
1091
|
+
file: filename,
|
|
1092
|
+
line: item.line
|
|
1093
|
+
});
|
|
1094
|
+
return { passages, diagnostics };
|
|
1095
|
+
}
|
|
1038
1096
|
}
|
|
1039
1097
|
lastType = item.type;
|
|
1040
1098
|
}
|
|
@@ -1045,15 +1103,6 @@ function parseTwee(source, options = {}) {
|
|
|
1045
1103
|
}
|
|
1046
1104
|
|
|
1047
1105
|
// src/loader.ts
|
|
1048
|
-
function readUTF82(filename) {
|
|
1049
|
-
let content = readFileSync2(filename, "utf-8");
|
|
1050
|
-
if (content.charCodeAt(0) === 65279) content = content.slice(1);
|
|
1051
|
-
content = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
1052
|
-
return content;
|
|
1053
|
-
}
|
|
1054
|
-
function readBase64(filename) {
|
|
1055
|
-
return readFileSync2(filename).toString("base64");
|
|
1056
|
-
}
|
|
1057
1106
|
function loadSources(story, filenames, opts, diagnostics, processedFiles) {
|
|
1058
1107
|
for (const filename of filenames) {
|
|
1059
1108
|
if (processedFiles.has(filename)) {
|
|
@@ -1152,7 +1201,7 @@ function loadInlineSources(story, sources, opts, diagnostics) {
|
|
|
1152
1201
|
}
|
|
1153
1202
|
}
|
|
1154
1203
|
function loadTwee(story, filename, opts, diagnostics) {
|
|
1155
|
-
const source =
|
|
1204
|
+
const source = readUTF8(filename);
|
|
1156
1205
|
const result = parseTwee(source, {
|
|
1157
1206
|
filename,
|
|
1158
1207
|
trim: opts.trim ?? true,
|
|
@@ -1164,38 +1213,48 @@ function loadTwee(story, filename, opts, diagnostics) {
|
|
|
1164
1213
|
}
|
|
1165
1214
|
}
|
|
1166
1215
|
function loadTagged(story, tag, filename, diagnostics) {
|
|
1167
|
-
const source =
|
|
1216
|
+
const source = readUTF8(filename);
|
|
1168
1217
|
storyAdd(story, { name: basename(filename), tags: [tag], text: source }, diagnostics);
|
|
1169
1218
|
}
|
|
1170
1219
|
function loadMedia(story, tag, filename, diagnostics) {
|
|
1171
1220
|
const source = readBase64(filename);
|
|
1172
|
-
const name =
|
|
1173
|
-
storyAdd(
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1221
|
+
const name = baseNameWithoutExt(filename);
|
|
1222
|
+
storyAdd(
|
|
1223
|
+
story,
|
|
1224
|
+
{
|
|
1225
|
+
name,
|
|
1226
|
+
tags: [tag],
|
|
1227
|
+
text: `data:${mediaTypeFromFilename(filename)};base64,${source}`
|
|
1228
|
+
},
|
|
1229
|
+
diagnostics
|
|
1230
|
+
);
|
|
1178
1231
|
}
|
|
1179
1232
|
function loadFont(story, filename, diagnostics) {
|
|
1180
1233
|
const source = readBase64(filename);
|
|
1181
1234
|
const name = basename(filename);
|
|
1182
|
-
const family =
|
|
1235
|
+
const family = baseNameWithoutExt(filename);
|
|
1183
1236
|
const ext = normalizedFileExt(filename);
|
|
1184
1237
|
const mediaType = mediaTypeFromExt(ext);
|
|
1185
1238
|
const hint = fontFormatHint(ext);
|
|
1186
|
-
storyAdd(
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1239
|
+
storyAdd(
|
|
1240
|
+
story,
|
|
1241
|
+
{
|
|
1242
|
+
name,
|
|
1243
|
+
tags: ["stylesheet"],
|
|
1244
|
+
text: `@font-face {
|
|
1190
1245
|
font-family: "${family}";
|
|
1191
1246
|
src: url("data:${mediaType};base64,${source}") format("${hint}");
|
|
1192
1247
|
}`
|
|
1193
|
-
|
|
1248
|
+
},
|
|
1249
|
+
diagnostics
|
|
1250
|
+
);
|
|
1194
1251
|
}
|
|
1195
1252
|
|
|
1253
|
+
// src/version.ts
|
|
1254
|
+
var VERSION = "1.2.0";
|
|
1255
|
+
|
|
1196
1256
|
// src/output-twine2.ts
|
|
1197
1257
|
var CREATOR_NAME = "Twee-ts";
|
|
1198
|
-
var CREATOR_VERSION = "0.1.0";
|
|
1199
1258
|
function toTwine2Archive(story, startName, diagnostics) {
|
|
1200
1259
|
return getTwine2DataChunk(story, startName, diagnostics) + "\n";
|
|
1201
1260
|
}
|
|
@@ -1223,12 +1282,12 @@ function getTwine2DataChunk(story, startName, diagnostics) {
|
|
|
1223
1282
|
}
|
|
1224
1283
|
let styleContent = "";
|
|
1225
1284
|
if (stylesheets.length === 1) {
|
|
1226
|
-
styleContent = stylesheets[0]
|
|
1285
|
+
styleContent = stylesheets[0]?.text ?? "";
|
|
1227
1286
|
} else if (stylesheets.length > 1) {
|
|
1228
1287
|
let pidS = 1;
|
|
1229
1288
|
for (const p of stylesheets) {
|
|
1230
1289
|
if (pidS > 1 && !styleContent.endsWith("\n")) styleContent += "\n";
|
|
1231
|
-
styleContent += `/* twine-user-stylesheet #${pidS}: "${p.name}" */
|
|
1290
|
+
styleContent += `/* twine-user-stylesheet #${pidS}: "${commentSanitize(p.name)}" */
|
|
1232
1291
|
`;
|
|
1233
1292
|
styleContent += p.text;
|
|
1234
1293
|
pidS++;
|
|
@@ -1237,12 +1296,12 @@ function getTwine2DataChunk(story, startName, diagnostics) {
|
|
|
1237
1296
|
parts.push(`<style role="stylesheet" id="twine-user-stylesheet" type="text/twine-css">${styleContent}</style>`);
|
|
1238
1297
|
let scriptContent = "";
|
|
1239
1298
|
if (scripts.length === 1) {
|
|
1240
|
-
scriptContent = scripts[0]
|
|
1299
|
+
scriptContent = scripts[0]?.text ?? "";
|
|
1241
1300
|
} else if (scripts.length > 1) {
|
|
1242
1301
|
let pidS = 1;
|
|
1243
1302
|
for (const p of scripts) {
|
|
1244
1303
|
if (pidS > 1 && !scriptContent.endsWith("\n")) scriptContent += "\n";
|
|
1245
|
-
scriptContent += `/* twine-user-script #${pidS}: "${p.name}" */
|
|
1304
|
+
scriptContent += `/* twine-user-script #${pidS}: "${commentSanitize(p.name)}" */
|
|
1246
1305
|
`;
|
|
1247
1306
|
scriptContent += p.text;
|
|
1248
1307
|
pidS++;
|
|
@@ -1272,7 +1331,7 @@ function getTwine2DataChunk(story, startName, diagnostics) {
|
|
|
1272
1331
|
}
|
|
1273
1332
|
const options = opts.join(" ");
|
|
1274
1333
|
const zoom = story.twine2.zoom === Math.floor(story.twine2.zoom) ? story.twine2.zoom.toString() : story.twine2.zoom.toFixed(1);
|
|
1275
|
-
const wrapper = `<!-- UUID://${story.ifid}// --><tw-storydata name="${attrEscape(story.name)}" startnode="${startID}" creator="${attrEscape(CREATOR_NAME)}" creator-version="${attrEscape(
|
|
1334
|
+
const wrapper = `<!-- UUID://${htmlCommentSanitize(story.ifid)}// --><tw-storydata name="${attrEscape(story.name)}" startnode="${attrEscape(startID)}" creator="${attrEscape(CREATOR_NAME)}" creator-version="${attrEscape(VERSION)}" ifid="${attrEscape(story.ifid)}" zoom="${attrEscape(zoom)}" format="${attrEscape(story.twine2.format)}" format-version="${attrEscape(story.twine2.formatVersion)}" options="${attrEscape(options)}" hidden>`;
|
|
1276
1335
|
return wrapper + parts.join("") + "</tw-storydata>";
|
|
1277
1336
|
}
|
|
1278
1337
|
function ensureIFID(story, diagnostics) {
|
|
@@ -1293,10 +1352,9 @@ function ensureIFID(story, diagnostics) {
|
|
|
1293
1352
|
}
|
|
1294
1353
|
|
|
1295
1354
|
// src/output-twine1.ts
|
|
1296
|
-
import { readFileSync as
|
|
1355
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
1297
1356
|
import { join as join3, dirname } from "path";
|
|
1298
1357
|
var CREATOR_NAME2 = "twee-ts";
|
|
1299
|
-
var CREATOR_VERSION2 = "0.1.0";
|
|
1300
1358
|
function toTwine1Archive(story, _startName) {
|
|
1301
1359
|
const { data, count } = getTwine1PassageChunk(story);
|
|
1302
1360
|
return `<div id="storeArea" data-size="${count}" hidden>${data}</div>
|
|
@@ -1318,26 +1376,27 @@ function toTwine1HTML(story, format, startName, _diagnostics) {
|
|
|
1318
1376
|
template = tryReplaceComponent(template, '"MODERNIZR"', join3(parentDir, "modernizr.js"), true);
|
|
1319
1377
|
}
|
|
1320
1378
|
const displayStart = startName === "Start" ? "" : startName;
|
|
1321
|
-
template = template.replace('"VERSION"', `Compiled with ${CREATOR_NAME2}, ${
|
|
1379
|
+
template = template.replace('"VERSION"', `Compiled with ${CREATOR_NAME2}, ${VERSION}`);
|
|
1322
1380
|
template = template.replace('"TIME"', `Built on ${(/* @__PURE__ */ new Date()).toUTCString()}`);
|
|
1323
|
-
template = template.replace('"START_AT"', `"${displayStart}"`);
|
|
1381
|
+
template = template.replace('"START_AT"', `"${jsStringEscape(displayStart)}"`);
|
|
1324
1382
|
template = template.replace('"STORY_SIZE"', `"${count}"`);
|
|
1325
1383
|
if (template.includes('"STORY"')) {
|
|
1326
1384
|
template = template.replace('"STORY"', data);
|
|
1327
1385
|
} else {
|
|
1328
1386
|
let footer;
|
|
1329
1387
|
try {
|
|
1330
|
-
footer =
|
|
1388
|
+
footer = readFileSync2(join3(formatDir, "footer.html"), "utf-8");
|
|
1331
1389
|
} catch {
|
|
1332
1390
|
footer = "</div>\n</body>\n</html>\n";
|
|
1333
1391
|
}
|
|
1334
1392
|
template += data + footer;
|
|
1335
1393
|
}
|
|
1336
1394
|
if (story.ifid) {
|
|
1395
|
+
const safeIfid = htmlCommentSanitize(story.ifid);
|
|
1337
1396
|
if (template.includes('<div id="store-area"')) {
|
|
1338
|
-
template = template.replace('<div id="store-area"', `<!-- UUID://${
|
|
1397
|
+
template = template.replace('<div id="store-area"', `<!-- UUID://${safeIfid}// --><div id="store-area"`);
|
|
1339
1398
|
} else {
|
|
1340
|
-
template = template.replace('<div id="storeArea"', `<!-- UUID://${
|
|
1399
|
+
template = template.replace('<div id="storeArea"', `<!-- UUID://${safeIfid}// --><div id="storeArea"`);
|
|
1341
1400
|
}
|
|
1342
1401
|
}
|
|
1343
1402
|
return template;
|
|
@@ -1352,13 +1411,18 @@ function getTwine1PassageChunk(story) {
|
|
|
1352
1411
|
}
|
|
1353
1412
|
return { data, count };
|
|
1354
1413
|
}
|
|
1355
|
-
function tryReplaceComponent(template, placeholder, componentPath,
|
|
1414
|
+
function tryReplaceComponent(template, placeholder, componentPath, required) {
|
|
1356
1415
|
if (!template.includes(placeholder)) return template;
|
|
1357
1416
|
try {
|
|
1358
|
-
let content =
|
|
1417
|
+
let content = readFileSync2(componentPath, "utf-8");
|
|
1359
1418
|
if (content.charCodeAt(0) === 65279) content = content.slice(1);
|
|
1360
1419
|
return template.replace(placeholder, content);
|
|
1361
|
-
} catch {
|
|
1420
|
+
} catch (e) {
|
|
1421
|
+
if (required) {
|
|
1422
|
+
throw new Error(
|
|
1423
|
+
`Required format component not found: ${componentPath}: ${e instanceof Error ? e.message : String(e)}`
|
|
1424
|
+
);
|
|
1425
|
+
}
|
|
1362
1426
|
return template;
|
|
1363
1427
|
}
|
|
1364
1428
|
}
|
|
@@ -1373,16 +1437,6 @@ function toTwee(story, outMode) {
|
|
|
1373
1437
|
}
|
|
1374
1438
|
|
|
1375
1439
|
// src/modules.ts
|
|
1376
|
-
import { readFileSync as readFileSync4 } from "fs";
|
|
1377
|
-
import { basename as basename2 } from "path";
|
|
1378
|
-
function readUTF83(filename) {
|
|
1379
|
-
let content = readFileSync4(filename, "utf-8");
|
|
1380
|
-
if (content.charCodeAt(0) === 65279) content = content.slice(1);
|
|
1381
|
-
return content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
1382
|
-
}
|
|
1383
|
-
function readBase642(filename) {
|
|
1384
|
-
return readFileSync4(filename).toString("base64");
|
|
1385
|
-
}
|
|
1386
1440
|
function loadModules(filenames) {
|
|
1387
1441
|
const processed = /* @__PURE__ */ new Set();
|
|
1388
1442
|
const headTags = [];
|
|
@@ -1412,16 +1466,16 @@ function loadModules(filenames) {
|
|
|
1412
1466
|
return headTags.join("\n");
|
|
1413
1467
|
}
|
|
1414
1468
|
function loadModuleTagged(tag, filename) {
|
|
1415
|
-
const source =
|
|
1469
|
+
const source = readUTF8(filename).trim();
|
|
1416
1470
|
if (source.length === 0) return null;
|
|
1417
|
-
const family =
|
|
1471
|
+
const family = baseNameWithoutExt(filename);
|
|
1418
1472
|
const idSlug = `${tag}-module-${slugify(family)}`;
|
|
1419
1473
|
const mimeType = tag === "script" ? "text/javascript" : "text/css";
|
|
1420
1474
|
return `<${tag} id="${idSlug}" type="${mimeType}">${source}</${tag}>`;
|
|
1421
1475
|
}
|
|
1422
1476
|
function loadModuleFont(filename) {
|
|
1423
|
-
const source =
|
|
1424
|
-
const family =
|
|
1477
|
+
const source = readBase64(filename);
|
|
1478
|
+
const family = baseNameWithoutExt(filename);
|
|
1425
1479
|
const idSlug = `style-module-${slugify(family)}`;
|
|
1426
1480
|
const ext = normalizedFileExt(filename);
|
|
1427
1481
|
const mediaType = mediaTypeFromExt(ext);
|
|
@@ -1431,7 +1485,7 @@ function loadModuleFont(filename) {
|
|
|
1431
1485
|
src: url("data:${mediaType};base64,${source}") format("${hint}");
|
|
1432
1486
|
}</style>`;
|
|
1433
1487
|
}
|
|
1434
|
-
function modifyHead(html, modulePaths, headFile) {
|
|
1488
|
+
function modifyHead(html, modulePaths, headFile, diagnostics) {
|
|
1435
1489
|
const parts = [];
|
|
1436
1490
|
if (modulePaths.length > 0) {
|
|
1437
1491
|
const modules = loadModules(modulePaths).trim();
|
|
@@ -1439,9 +1493,13 @@ function modifyHead(html, modulePaths, headFile) {
|
|
|
1439
1493
|
}
|
|
1440
1494
|
if (headFile) {
|
|
1441
1495
|
try {
|
|
1442
|
-
const source =
|
|
1496
|
+
const source = readUTF8(headFile).trim();
|
|
1443
1497
|
if (source.length > 0) parts.push(source);
|
|
1444
|
-
} catch {
|
|
1498
|
+
} catch (e) {
|
|
1499
|
+
diagnostics?.push({
|
|
1500
|
+
level: "warning",
|
|
1501
|
+
message: `Failed to read head file "${headFile}": ${e instanceof Error ? e.message : String(e)}`
|
|
1502
|
+
});
|
|
1445
1503
|
}
|
|
1446
1504
|
}
|
|
1447
1505
|
if (parts.length > 0) {
|
|
@@ -1452,7 +1510,7 @@ function modifyHead(html, modulePaths, headFile) {
|
|
|
1452
1510
|
}
|
|
1453
1511
|
|
|
1454
1512
|
// src/remote-formats.ts
|
|
1455
|
-
import { mkdirSync, readFileSync as
|
|
1513
|
+
import { mkdirSync, readFileSync as readFileSync3, writeFileSync, existsSync, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
|
|
1456
1514
|
import { join as join4 } from "path";
|
|
1457
1515
|
import { homedir as homedir2 } from "os";
|
|
1458
1516
|
var DEFAULT_SFA_INDICES = [
|
|
@@ -1468,6 +1526,24 @@ var indexCache = /* @__PURE__ */ new Map();
|
|
|
1468
1526
|
function clearIndexCache() {
|
|
1469
1527
|
indexCache.clear();
|
|
1470
1528
|
}
|
|
1529
|
+
function isValidEntry(val) {
|
|
1530
|
+
if (typeof val !== "object" || val === null || Array.isArray(val)) return false;
|
|
1531
|
+
const obj = val;
|
|
1532
|
+
return typeof obj.name === "string" && typeof obj.version === "string" && typeof obj.checksums === "object" && obj.checksums !== null && !Array.isArray(obj.checksums);
|
|
1533
|
+
}
|
|
1534
|
+
function validateSFAIndex(json) {
|
|
1535
|
+
if (typeof json !== "object" || json === null) {
|
|
1536
|
+
throw new Error("SFA index is not an object");
|
|
1537
|
+
}
|
|
1538
|
+
const obj = json;
|
|
1539
|
+
const twine1 = Array.isArray(obj["twine1"]) ? obj["twine1"].filter(isValidEntry) : [];
|
|
1540
|
+
const twine2 = Array.isArray(obj["twine2"]) ? obj["twine2"].filter(isValidEntry) : [];
|
|
1541
|
+
return { twine1, twine2 };
|
|
1542
|
+
}
|
|
1543
|
+
function makeFormatId(name, version) {
|
|
1544
|
+
const major = parseSemver(version)?.[0] ?? "0";
|
|
1545
|
+
return `${name.toLowerCase().replace(/\s+/g, "-")}-${major}`;
|
|
1546
|
+
}
|
|
1471
1547
|
async function fetchIndex(url) {
|
|
1472
1548
|
const cached = indexCache.get(url);
|
|
1473
1549
|
if (cached) return cached;
|
|
@@ -1475,37 +1551,42 @@ async function fetchIndex(url) {
|
|
|
1475
1551
|
if (!res.ok) {
|
|
1476
1552
|
throw new Error(`Failed to fetch format index from ${url}: ${res.status} ${res.statusText}`);
|
|
1477
1553
|
}
|
|
1478
|
-
const data = await res.json();
|
|
1554
|
+
const data = validateSFAIndex(await res.json());
|
|
1479
1555
|
indexCache.set(url, data);
|
|
1480
1556
|
return data;
|
|
1481
1557
|
}
|
|
1482
1558
|
function findEntry(index, name, version) {
|
|
1483
1559
|
const wanted = parseSemver(version);
|
|
1484
|
-
const
|
|
1485
|
-
|
|
1560
|
+
const searchArrays = [
|
|
1561
|
+
{ entries: index.twine2 ?? [], formatType: "twine2" },
|
|
1562
|
+
{ entries: index.twine1 ?? [], formatType: "twine1" }
|
|
1563
|
+
];
|
|
1564
|
+
let bestResult;
|
|
1486
1565
|
let bestVersion = null;
|
|
1487
|
-
for (const
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
if (
|
|
1494
|
-
bestVersion
|
|
1495
|
-
|
|
1566
|
+
for (const { entries, formatType } of searchArrays) {
|
|
1567
|
+
for (const entry of entries) {
|
|
1568
|
+
if (entry.name.toLowerCase() !== name.toLowerCase()) continue;
|
|
1569
|
+
const have = parseSemver(entry.version);
|
|
1570
|
+
if (!have) continue;
|
|
1571
|
+
if (wanted && semverCompare(have, wanted) === 0) return { entry, formatType };
|
|
1572
|
+
if (wanted === null || have[0] === wanted[0] && semverCompare(have, wanted) >= 0) {
|
|
1573
|
+
if (!bestVersion || semverCompare(have, bestVersion) > 0) {
|
|
1574
|
+
bestVersion = have;
|
|
1575
|
+
bestResult = { entry, formatType };
|
|
1576
|
+
}
|
|
1496
1577
|
}
|
|
1497
1578
|
}
|
|
1498
1579
|
}
|
|
1499
|
-
return
|
|
1580
|
+
return bestResult;
|
|
1500
1581
|
}
|
|
1501
1582
|
async function verifySHA256(content, expectedHex) {
|
|
1502
1583
|
const digest = await crypto.subtle.digest("SHA-256", content);
|
|
1503
1584
|
const hex = Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1504
1585
|
return hex === expectedHex.toLowerCase();
|
|
1505
1586
|
}
|
|
1506
|
-
function getDownloadUrl(indexUrl, entry) {
|
|
1587
|
+
function getDownloadUrl(indexUrl, entry, formatType) {
|
|
1507
1588
|
const base = indexUrl.replace(/\/index\.json$/, "");
|
|
1508
|
-
return `${base}
|
|
1589
|
+
return `${base}/${formatType}/${entry.name}/${entry.version}/format.js`;
|
|
1509
1590
|
}
|
|
1510
1591
|
async function fetchAndCacheFormat(entry, downloadUrl) {
|
|
1511
1592
|
const res = await fetch(downloadUrl);
|
|
@@ -1516,13 +1597,14 @@ async function fetchAndCacheFormat(entry, downloadUrl) {
|
|
|
1516
1597
|
const checksumKey = Object.keys(entry.checksums ?? {}).find((k) => k.endsWith("format.js"));
|
|
1517
1598
|
if (checksumKey) {
|
|
1518
1599
|
const expected = entry.checksums[checksumKey];
|
|
1600
|
+
if (!expected) throw new Error(`Missing checksum value for key "${checksumKey}"`);
|
|
1519
1601
|
const encoder = new TextEncoder();
|
|
1520
1602
|
const valid = await verifySHA256(encoder.encode(text), expected);
|
|
1521
1603
|
if (!valid) {
|
|
1522
1604
|
throw new Error(`Checksum verification failed for ${entry.name} ${entry.version}`);
|
|
1523
1605
|
}
|
|
1524
1606
|
}
|
|
1525
|
-
const id =
|
|
1607
|
+
const id = makeFormatId(entry.name, entry.version);
|
|
1526
1608
|
const data = parseFormatJSON(text, id);
|
|
1527
1609
|
if (!data) {
|
|
1528
1610
|
throw new Error(`Failed to parse format JSON from ${downloadUrl}`);
|
|
@@ -1552,7 +1634,7 @@ async function fetchDirectFormat(url) {
|
|
|
1552
1634
|
if (!data) {
|
|
1553
1635
|
throw new Error(`Failed to parse format JSON from ${url}`);
|
|
1554
1636
|
}
|
|
1555
|
-
const id =
|
|
1637
|
+
const id = makeFormatId(data.name, data.version);
|
|
1556
1638
|
const cacheDir = getCacheDir();
|
|
1557
1639
|
const formatDir = join4(cacheDir, data.name, data.version);
|
|
1558
1640
|
mkdirSync(formatDir, { recursive: true });
|
|
@@ -1568,6 +1650,7 @@ async function fetchDirectFormat(url) {
|
|
|
1568
1650
|
};
|
|
1569
1651
|
}
|
|
1570
1652
|
async function resolveRemoteFormat(name, version, indices, urls) {
|
|
1653
|
+
let lastError;
|
|
1571
1654
|
if (urls) {
|
|
1572
1655
|
for (const url of urls) {
|
|
1573
1656
|
try {
|
|
@@ -1579,7 +1662,8 @@ async function resolveRemoteFormat(name, version, indices, urls) {
|
|
|
1579
1662
|
return info;
|
|
1580
1663
|
}
|
|
1581
1664
|
}
|
|
1582
|
-
} catch {
|
|
1665
|
+
} catch (e) {
|
|
1666
|
+
lastError = e instanceof Error ? e : new Error(String(e));
|
|
1583
1667
|
}
|
|
1584
1668
|
}
|
|
1585
1669
|
}
|
|
@@ -1587,24 +1671,26 @@ async function resolveRemoteFormat(name, version, indices, urls) {
|
|
|
1587
1671
|
for (const indexUrl of allIndices) {
|
|
1588
1672
|
try {
|
|
1589
1673
|
const index = await fetchIndex(indexUrl);
|
|
1590
|
-
const
|
|
1591
|
-
if (
|
|
1592
|
-
const cached = getCachedFormat(entry.name, entry.version);
|
|
1674
|
+
const result = findEntry(index, name, version);
|
|
1675
|
+
if (result) {
|
|
1676
|
+
const cached = getCachedFormat(result.entry.name, result.entry.version);
|
|
1593
1677
|
if (cached) return cached;
|
|
1594
|
-
const downloadUrl = getDownloadUrl(indexUrl, entry);
|
|
1595
|
-
return await fetchAndCacheFormat(entry, downloadUrl);
|
|
1678
|
+
const downloadUrl = getDownloadUrl(indexUrl, result.entry, result.formatType);
|
|
1679
|
+
return await fetchAndCacheFormat(result.entry, downloadUrl);
|
|
1596
1680
|
}
|
|
1597
|
-
} catch {
|
|
1681
|
+
} catch (e) {
|
|
1682
|
+
lastError = e instanceof Error ? e : new Error(String(e));
|
|
1598
1683
|
}
|
|
1599
1684
|
}
|
|
1685
|
+
if (lastError) throw lastError;
|
|
1600
1686
|
return void 0;
|
|
1601
1687
|
}
|
|
1602
1688
|
function getCachedFormat(name, version) {
|
|
1603
1689
|
const formatPath = join4(getCacheDir(), name, version, "format.js");
|
|
1604
1690
|
try {
|
|
1605
1691
|
if (!existsSync(formatPath)) return void 0;
|
|
1606
|
-
const source =
|
|
1607
|
-
const id =
|
|
1692
|
+
const source = readFileSync3(formatPath, "utf-8");
|
|
1693
|
+
const id = makeFormatId(name, version);
|
|
1608
1694
|
const data = parseFormatJSON(source, id);
|
|
1609
1695
|
if (!data) return void 0;
|
|
1610
1696
|
return {
|
|
@@ -1656,18 +1742,12 @@ async function buildOutput(options) {
|
|
|
1656
1742
|
loadSources(story, sourceFilenames, { trim, twee2Compat }, diagnostics, processedFiles);
|
|
1657
1743
|
loadInlineSources(story, inlineSources, { trim, twee2Compat }, diagnostics);
|
|
1658
1744
|
if (options.tagAliases) {
|
|
1659
|
-
applyTagAliases(story.passages, options.tagAliases);
|
|
1660
|
-
}
|
|
1661
|
-
const fatalErrors = diagnostics.filter((d) => d.level === "error");
|
|
1662
|
-
if (fatalErrors.length > 0 && outputMode === "html") {
|
|
1745
|
+
story.passages = applyTagAliases(story.passages, options.tagAliases);
|
|
1663
1746
|
}
|
|
1664
1747
|
let format;
|
|
1665
1748
|
let formatId = options.formatId ?? "";
|
|
1666
1749
|
if (outputMode === "html") {
|
|
1667
|
-
const formatSearchDirs = getFormatSearchDirs(
|
|
1668
|
-
options.formatPaths,
|
|
1669
|
-
options.useTweegoPath ?? true
|
|
1670
|
-
);
|
|
1750
|
+
const formatSearchDirs = getFormatSearchDirs(options.formatPaths, options.useTweegoPath ?? true);
|
|
1671
1751
|
const formats = discoverFormats(formatSearchDirs);
|
|
1672
1752
|
if (!formatId && story.twine2.format) {
|
|
1673
1753
|
formatId = getFormatIdByNameAndVersion(formats, story.twine2.format, story.twine2.formatVersion) ?? "";
|
|
@@ -1680,13 +1760,12 @@ async function buildOutput(options) {
|
|
|
1680
1760
|
const remoteName = story.twine2.format || formatId;
|
|
1681
1761
|
const remoteVersion = story.twine2.formatVersion || "";
|
|
1682
1762
|
try {
|
|
1683
|
-
format = await resolveRemoteFormat(
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
);
|
|
1689
|
-
} catch {
|
|
1763
|
+
format = await resolveRemoteFormat(remoteName, remoteVersion, options.formatIndices, options.formatUrls);
|
|
1764
|
+
} catch (e) {
|
|
1765
|
+
diagnostics.push({
|
|
1766
|
+
level: "warning",
|
|
1767
|
+
message: `Remote format fetch failed for "${remoteName}": ${e instanceof Error ? e.message : String(e)}`
|
|
1768
|
+
});
|
|
1690
1769
|
}
|
|
1691
1770
|
}
|
|
1692
1771
|
if (!format) {
|
|
@@ -1697,14 +1776,7 @@ async function buildOutput(options) {
|
|
|
1697
1776
|
});
|
|
1698
1777
|
}
|
|
1699
1778
|
}
|
|
1700
|
-
|
|
1701
|
-
if (options.startPassage) {
|
|
1702
|
-
startName = options.startPassage;
|
|
1703
|
-
} else if (story.twine2.start) {
|
|
1704
|
-
startName = story.twine2.start;
|
|
1705
|
-
} else {
|
|
1706
|
-
startName = DEFAULT_START_NAME;
|
|
1707
|
-
}
|
|
1779
|
+
const startName = options.startPassage || story.twine2.start || DEFAULT_START_NAME;
|
|
1708
1780
|
if (testMode) {
|
|
1709
1781
|
story.twine2.options.set("debug", true);
|
|
1710
1782
|
}
|
|
@@ -1723,8 +1795,7 @@ async function buildOutput(options) {
|
|
|
1723
1795
|
case "json":
|
|
1724
1796
|
output = storyToJSON(story);
|
|
1725
1797
|
break;
|
|
1726
|
-
case "html":
|
|
1727
|
-
default: {
|
|
1798
|
+
case "html": {
|
|
1728
1799
|
if (!storyHas(story, startName)) {
|
|
1729
1800
|
diagnostics.push({
|
|
1730
1801
|
level: "error",
|
|
@@ -1746,9 +1817,13 @@ async function buildOutput(options) {
|
|
|
1746
1817
|
output = toTwine1HTML(story, format, startName, diagnostics);
|
|
1747
1818
|
}
|
|
1748
1819
|
const modulePaths = options.modules ? getFilenames(options.modules) : [];
|
|
1749
|
-
output = modifyHead(output, modulePaths, options.headFile);
|
|
1820
|
+
output = modifyHead(output, modulePaths, options.headFile, diagnostics);
|
|
1750
1821
|
break;
|
|
1751
1822
|
}
|
|
1823
|
+
default: {
|
|
1824
|
+
const _exhaustive = outputMode;
|
|
1825
|
+
throw new TweeTsError(`Unhandled output mode: ${_exhaustive}`, diagnostics);
|
|
1826
|
+
}
|
|
1752
1827
|
}
|
|
1753
1828
|
const stats = {
|
|
1754
1829
|
...getStoryStats(story),
|
|
@@ -1757,27 +1832,31 @@ async function buildOutput(options) {
|
|
|
1757
1832
|
return { output, story, format, diagnostics, stats };
|
|
1758
1833
|
}
|
|
1759
1834
|
function storyToJSON(story) {
|
|
1760
|
-
return JSON.stringify(
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1835
|
+
return JSON.stringify(
|
|
1836
|
+
{
|
|
1837
|
+
name: story.name,
|
|
1838
|
+
ifid: story.ifid,
|
|
1839
|
+
passages: story.passages.map((p) => ({
|
|
1840
|
+
name: p.name,
|
|
1841
|
+
tags: p.tags,
|
|
1842
|
+
text: p.text,
|
|
1843
|
+
metadata: p.metadata ?? null
|
|
1844
|
+
})),
|
|
1845
|
+
twine2: {
|
|
1846
|
+
format: story.twine2.format,
|
|
1847
|
+
formatVersion: story.twine2.formatVersion,
|
|
1848
|
+
start: story.twine2.start,
|
|
1849
|
+
tagColors: Object.fromEntries(story.twine2.tagColors),
|
|
1850
|
+
options: Object.fromEntries(story.twine2.options),
|
|
1851
|
+
zoom: story.twine2.zoom
|
|
1852
|
+
}
|
|
1853
|
+
},
|
|
1854
|
+
null,
|
|
1855
|
+
2
|
|
1856
|
+
);
|
|
1778
1857
|
}
|
|
1779
1858
|
|
|
1780
1859
|
export {
|
|
1781
1860
|
compile
|
|
1782
1861
|
};
|
|
1783
|
-
//# sourceMappingURL=chunk-
|
|
1862
|
+
//# sourceMappingURL=chunk-S4SWCQLW.js.map
|