@rohal12/twee-ts 1.1.2 → 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 +214 -142
- package/dist/bin/twee-ts.js.map +1 -1
- package/dist/{chunk-QTXRKA4T.js → chunk-S4SWCQLW.js} +211 -136
- package/dist/chunk-S4SWCQLW.js.map +1 -0
- package/dist/index.cjs +244 -171
- 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 +213 -140
- 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 +1 -1
- package/dist/chunk-QTXRKA4T.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -50,7 +50,7 @@ __export(src_exports, {
|
|
|
50
50
|
module.exports = __toCommonJS(src_exports);
|
|
51
51
|
|
|
52
52
|
// src/compiler.ts
|
|
53
|
-
var
|
|
53
|
+
var import_node_fs6 = require("fs");
|
|
54
54
|
|
|
55
55
|
// src/ifid.ts
|
|
56
56
|
var import_node_crypto = require("crypto");
|
|
@@ -120,13 +120,26 @@ function tweeEscape(s) {
|
|
|
120
120
|
if (s.length === 0) return s;
|
|
121
121
|
return s.replace(/[\\[\]{}]/g, (ch) => "\\" + ch);
|
|
122
122
|
}
|
|
123
|
+
function jsStringEscape(s) {
|
|
124
|
+
if (s.length === 0) return s;
|
|
125
|
+
return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
|
|
126
|
+
}
|
|
127
|
+
function commentSanitize(s) {
|
|
128
|
+
return s.replace(/\*\//g, "* /");
|
|
129
|
+
}
|
|
130
|
+
function htmlCommentSanitize(s) {
|
|
131
|
+
return s.replace(/-->/g, "-- >");
|
|
132
|
+
}
|
|
123
133
|
function tweeUnescape(s) {
|
|
124
134
|
if (s.length === 0) return s;
|
|
125
135
|
let result = "";
|
|
126
136
|
for (let i = 0; i < s.length; i++) {
|
|
127
137
|
if (s[i] === "\\") {
|
|
128
138
|
i++;
|
|
129
|
-
if (i >= s.length)
|
|
139
|
+
if (i >= s.length) {
|
|
140
|
+
result += "\\";
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
130
143
|
}
|
|
131
144
|
result += s[i];
|
|
132
145
|
}
|
|
@@ -219,16 +232,16 @@ function passageToPassagedata(p, pid) {
|
|
|
219
232
|
let position;
|
|
220
233
|
let size;
|
|
221
234
|
if (hasMetadataPosition(p)) {
|
|
222
|
-
position = p.metadata
|
|
235
|
+
position = p.metadata?.position ?? "";
|
|
223
236
|
} else {
|
|
224
237
|
const x = pid % 10;
|
|
225
|
-
|
|
238
|
+
const y = Math.floor(pid / 10);
|
|
226
239
|
const xp = x === 0 ? 10 : x;
|
|
227
240
|
const yp = x === 0 ? y : y + 1;
|
|
228
241
|
position = `${xp * 125 - 25},${yp * 125 - 25}`;
|
|
229
242
|
}
|
|
230
243
|
if (hasMetadataSize(p)) {
|
|
231
|
-
size = p.metadata
|
|
244
|
+
size = p.metadata?.size ?? "";
|
|
232
245
|
} else {
|
|
233
246
|
size = "100,100";
|
|
234
247
|
}
|
|
@@ -237,10 +250,10 @@ function passageToPassagedata(p, pid) {
|
|
|
237
250
|
function passageToTiddler(p, pid) {
|
|
238
251
|
let position;
|
|
239
252
|
if (hasMetadataPosition(p)) {
|
|
240
|
-
position = p.metadata
|
|
253
|
+
position = p.metadata?.position ?? "";
|
|
241
254
|
} else {
|
|
242
255
|
const x = pid % 10;
|
|
243
|
-
|
|
256
|
+
const y = Math.floor(pid / 10);
|
|
244
257
|
const xp = x === 0 ? 10 : x;
|
|
245
258
|
const yp = x === 0 ? y : y + 1;
|
|
246
259
|
position = `${xp * 140 - 130},${yp * 140 - 130}`;
|
|
@@ -259,15 +272,17 @@ function countWords(p) {
|
|
|
259
272
|
}
|
|
260
273
|
function applyTagAliases(passages, aliases) {
|
|
261
274
|
const entries = Object.entries(aliases);
|
|
262
|
-
if (entries.length === 0) return;
|
|
263
|
-
|
|
264
|
-
const original =
|
|
275
|
+
if (entries.length === 0) return [...passages];
|
|
276
|
+
return passages.map((p) => {
|
|
277
|
+
const original = p.tags;
|
|
278
|
+
const added = [];
|
|
265
279
|
for (const [alias, canonical] of entries) {
|
|
266
|
-
if (original.includes(alias) && !
|
|
267
|
-
|
|
280
|
+
if (original.includes(alias) && !original.includes(canonical) && !added.includes(canonical)) {
|
|
281
|
+
added.push(canonical);
|
|
268
282
|
}
|
|
269
283
|
}
|
|
270
|
-
|
|
284
|
+
return added.length > 0 ? { ...p, tags: [...original, ...added] } : p;
|
|
285
|
+
});
|
|
271
286
|
}
|
|
272
287
|
function quote(s) {
|
|
273
288
|
return `"${s}"`;
|
|
@@ -339,27 +354,32 @@ function marshalStoryData(story) {
|
|
|
339
354
|
return JSON.stringify(data, null, " ");
|
|
340
355
|
}
|
|
341
356
|
function unmarshalStoryData(story, json) {
|
|
342
|
-
let
|
|
357
|
+
let raw;
|
|
343
358
|
try {
|
|
344
|
-
|
|
359
|
+
raw = JSON.parse(json);
|
|
345
360
|
} catch (e) {
|
|
346
361
|
return `Cannot unmarshal "StoryData"; ${e instanceof Error ? e.message : String(e)}`;
|
|
347
362
|
}
|
|
348
|
-
if (
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
363
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
364
|
+
return 'Cannot unmarshal "StoryData"; expected a JSON object';
|
|
365
|
+
}
|
|
366
|
+
const data = raw;
|
|
367
|
+
if (typeof data.ifid === "string" && data.ifid) story.ifid = data.ifid.toUpperCase();
|
|
368
|
+
if (typeof data.format === "string" && data.format) story.twine2.format = data.format;
|
|
369
|
+
if (typeof data["format-version"] === "string" && data["format-version"])
|
|
370
|
+
story.twine2.formatVersion = data["format-version"];
|
|
371
|
+
if (Array.isArray(data.options)) {
|
|
352
372
|
for (const opt of data.options) {
|
|
353
|
-
story.twine2.options.set(opt, true);
|
|
373
|
+
if (typeof opt === "string") story.twine2.options.set(opt, true);
|
|
354
374
|
}
|
|
355
375
|
}
|
|
356
|
-
if (data.start) story.twine2.start = data.start;
|
|
357
|
-
if (data["tag-colors"]) {
|
|
376
|
+
if (typeof data.start === "string" && data.start) story.twine2.start = data.start;
|
|
377
|
+
if (typeof data["tag-colors"] === "object" && data["tag-colors"] !== null && !Array.isArray(data["tag-colors"])) {
|
|
358
378
|
for (const [tag, color] of Object.entries(data["tag-colors"])) {
|
|
359
|
-
story.twine2.tagColors.set(tag, color);
|
|
379
|
+
if (typeof color === "string") story.twine2.tagColors.set(tag, color);
|
|
360
380
|
}
|
|
361
381
|
}
|
|
362
|
-
if (data.zoom
|
|
382
|
+
if (typeof data.zoom === "number" && data.zoom !== 0) story.twine2.zoom = data.zoom;
|
|
363
383
|
return null;
|
|
364
384
|
}
|
|
365
385
|
function unmarshalStorySettings(story, text, diagnostics) {
|
|
@@ -402,6 +422,7 @@ function unmarshalStorySettings(story, text, diagnostics) {
|
|
|
402
422
|
}
|
|
403
423
|
}
|
|
404
424
|
function storyAdd(story, p, diagnostics) {
|
|
425
|
+
let processed = p;
|
|
405
426
|
switch (p.name) {
|
|
406
427
|
case "StoryIncludes":
|
|
407
428
|
diagnostics.push({
|
|
@@ -421,7 +442,7 @@ function storyAdd(story, p, diagnostics) {
|
|
|
421
442
|
});
|
|
422
443
|
}
|
|
423
444
|
}
|
|
424
|
-
p
|
|
445
|
+
processed = { ...p, text: marshalStoryData(story) };
|
|
425
446
|
} else {
|
|
426
447
|
diagnostics.push({
|
|
427
448
|
level: "error",
|
|
@@ -433,12 +454,14 @@ function storyAdd(story, p, diagnostics) {
|
|
|
433
454
|
case "StorySettings":
|
|
434
455
|
unmarshalStorySettings(story, p.text, diagnostics);
|
|
435
456
|
break;
|
|
436
|
-
case "StoryTitle":
|
|
437
|
-
|
|
438
|
-
|
|
457
|
+
case "StoryTitle": {
|
|
458
|
+
const trimmed = p.text.trim();
|
|
459
|
+
processed = { ...p, text: trimmed };
|
|
460
|
+
story.name = trimmed;
|
|
439
461
|
break;
|
|
462
|
+
}
|
|
440
463
|
}
|
|
441
|
-
storyAppend(story,
|
|
464
|
+
storyAppend(story, processed, diagnostics);
|
|
442
465
|
}
|
|
443
466
|
function getStoryStats(story) {
|
|
444
467
|
let storyPassages = 0;
|
|
@@ -492,7 +515,7 @@ var MEDIA_TYPE_MAP = {
|
|
|
492
515
|
webm: "video/webm"
|
|
493
516
|
};
|
|
494
517
|
function mediaTypeFromExt(ext) {
|
|
495
|
-
return MEDIA_TYPE_MAP[ext] ?? "";
|
|
518
|
+
return MEDIA_TYPE_MAP[ext] ?? "application/octet-stream";
|
|
496
519
|
}
|
|
497
520
|
function mediaTypeFromFilename(filename) {
|
|
498
521
|
return mediaTypeFromExt(normalizedFileExt(filename));
|
|
@@ -514,8 +537,6 @@ var KNOWN_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
514
537
|
"twee",
|
|
515
538
|
"tw2",
|
|
516
539
|
"twee2",
|
|
517
|
-
"htm",
|
|
518
|
-
"html",
|
|
519
540
|
"css",
|
|
520
541
|
"js",
|
|
521
542
|
"otf",
|
|
@@ -625,23 +646,49 @@ function watchFilesystem(pathnames, outFilename, callback) {
|
|
|
625
646
|
}
|
|
626
647
|
|
|
627
648
|
// src/formats.ts
|
|
649
|
+
var import_node_fs3 = require("fs");
|
|
650
|
+
var import_node_path4 = require("path");
|
|
651
|
+
var import_node_os = require("os");
|
|
652
|
+
|
|
653
|
+
// src/util.ts
|
|
628
654
|
var import_node_fs2 = require("fs");
|
|
629
655
|
var import_node_path3 = require("path");
|
|
630
|
-
|
|
656
|
+
function readUTF8(filename) {
|
|
657
|
+
let content = (0, import_node_fs2.readFileSync)(filename, "utf-8");
|
|
658
|
+
if (content.charCodeAt(0) === 65279) content = content.slice(1);
|
|
659
|
+
content = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
660
|
+
return content;
|
|
661
|
+
}
|
|
662
|
+
function readBase64(filename) {
|
|
663
|
+
return (0, import_node_fs2.readFileSync)(filename).toString("base64");
|
|
664
|
+
}
|
|
665
|
+
function baseNameWithoutExt(filename) {
|
|
666
|
+
const { name, ext } = (0, import_node_path3.parse)(filename);
|
|
667
|
+
return name || ext;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// src/formats.ts
|
|
631
671
|
function parseFormatJSON(source, formatId) {
|
|
632
672
|
const first = source.indexOf("{");
|
|
633
673
|
const last = source.lastIndexOf("}");
|
|
634
674
|
if (first === -1 || last === -1) return null;
|
|
635
675
|
let chunk = source.slice(first, last + 1);
|
|
676
|
+
const parse = (json) => {
|
|
677
|
+
const raw = JSON.parse(json);
|
|
678
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null;
|
|
679
|
+
const obj = raw;
|
|
680
|
+
if (typeof obj.name !== "string" || typeof obj.version !== "string" || typeof obj.source !== "string") return null;
|
|
681
|
+
return { name: obj.name, version: obj.version, source: obj.source, proofing: obj.proofing === true };
|
|
682
|
+
};
|
|
636
683
|
try {
|
|
637
|
-
return
|
|
684
|
+
return parse(chunk);
|
|
638
685
|
} catch {
|
|
639
686
|
if (formatId.toLowerCase().startsWith("harlowe")) {
|
|
640
687
|
const setupIdx = chunk.lastIndexOf(',"setup": function');
|
|
641
688
|
if (setupIdx !== -1) {
|
|
642
689
|
chunk = chunk.slice(0, setupIdx) + "}";
|
|
643
690
|
try {
|
|
644
|
-
return
|
|
691
|
+
return parse(chunk);
|
|
645
692
|
} catch {
|
|
646
693
|
}
|
|
647
694
|
}
|
|
@@ -649,39 +696,33 @@ function parseFormatJSON(source, formatId) {
|
|
|
649
696
|
return null;
|
|
650
697
|
}
|
|
651
698
|
}
|
|
652
|
-
function readUTF8(filename) {
|
|
653
|
-
let content = (0, import_node_fs2.readFileSync)(filename, "utf-8");
|
|
654
|
-
if (content.charCodeAt(0) === 65279) content = content.slice(1);
|
|
655
|
-
content = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
656
|
-
return content;
|
|
657
|
-
}
|
|
658
699
|
function discoverFormats(searchDirs) {
|
|
659
700
|
const formats = /* @__PURE__ */ new Map();
|
|
660
701
|
const baseFilenames = ["format.js", "header.html"];
|
|
661
702
|
for (const searchDir of searchDirs) {
|
|
662
703
|
try {
|
|
663
|
-
const stat = (0,
|
|
704
|
+
const stat = (0, import_node_fs3.statSync)(searchDir);
|
|
664
705
|
if (!stat.isDirectory()) continue;
|
|
665
706
|
} catch {
|
|
666
707
|
continue;
|
|
667
708
|
}
|
|
668
709
|
let entries;
|
|
669
710
|
try {
|
|
670
|
-
entries = (0,
|
|
711
|
+
entries = (0, import_node_fs3.readdirSync)(searchDir);
|
|
671
712
|
} catch {
|
|
672
713
|
continue;
|
|
673
714
|
}
|
|
674
715
|
for (const entry of entries) {
|
|
675
|
-
const formatDir = (0,
|
|
716
|
+
const formatDir = (0, import_node_path4.join)(searchDir, entry);
|
|
676
717
|
try {
|
|
677
|
-
if (!(0,
|
|
718
|
+
if (!(0, import_node_fs3.statSync)(formatDir).isDirectory()) continue;
|
|
678
719
|
} catch {
|
|
679
720
|
continue;
|
|
680
721
|
}
|
|
681
722
|
for (const baseFilename of baseFilenames) {
|
|
682
|
-
const formatFilename = (0,
|
|
723
|
+
const formatFilename = (0, import_node_path4.join)(formatDir, baseFilename);
|
|
683
724
|
try {
|
|
684
|
-
const stat = (0,
|
|
725
|
+
const stat = (0, import_node_fs3.statSync)(formatFilename);
|
|
685
726
|
if (!stat.isFile()) continue;
|
|
686
727
|
} catch {
|
|
687
728
|
continue;
|
|
@@ -725,9 +766,9 @@ function getFormatSearchDirs(extraPaths = [], useTweegoPath = true) {
|
|
|
725
766
|
const dirs = [];
|
|
726
767
|
for (const base of basePaths) {
|
|
727
768
|
for (const sub of subdirNames) {
|
|
728
|
-
const dir = (0,
|
|
769
|
+
const dir = (0, import_node_path4.join)(base, sub);
|
|
729
770
|
try {
|
|
730
|
-
if ((0,
|
|
771
|
+
if ((0, import_node_fs3.statSync)(dir).isDirectory()) {
|
|
731
772
|
dirs.push(dir);
|
|
732
773
|
}
|
|
733
774
|
} catch {
|
|
@@ -749,9 +790,11 @@ function parseSemver(v) {
|
|
|
749
790
|
return [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
750
791
|
}
|
|
751
792
|
function semverCompare(a, b) {
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
793
|
+
const [aMajor, aMinor, aPatch] = a;
|
|
794
|
+
const [bMajor, bMinor, bPatch] = b;
|
|
795
|
+
if (aMajor !== bMajor) return aMajor - bMajor;
|
|
796
|
+
if (aMinor !== bMinor) return aMinor - bMinor;
|
|
797
|
+
if (aPatch !== bPatch) return aPatch - bPatch;
|
|
755
798
|
return 0;
|
|
756
799
|
}
|
|
757
800
|
function getFormatIdByNameAndVersion(formats, name, version) {
|
|
@@ -782,8 +825,7 @@ function readFormatSource(format) {
|
|
|
782
825
|
}
|
|
783
826
|
|
|
784
827
|
// src/loader.ts
|
|
785
|
-
var
|
|
786
|
-
var import_node_path4 = require("path");
|
|
828
|
+
var import_node_path5 = require("path");
|
|
787
829
|
|
|
788
830
|
// src/lexer.ts
|
|
789
831
|
var EOF = -1;
|
|
@@ -1075,13 +1117,13 @@ function hasTwee2Syntax(s) {
|
|
|
1075
1117
|
}
|
|
1076
1118
|
function twee2ToV3(s) {
|
|
1077
1119
|
if (!hasTwee2Syntax(s)) return s;
|
|
1078
|
-
|
|
1120
|
+
let result = s.replace(twee2HeaderRe, (_, p1, p2, p3) => {
|
|
1079
1121
|
const tags = p2 ?? "";
|
|
1080
1122
|
const pos = p3 ?? "";
|
|
1081
1123
|
return `${p1}${tags} {"position":"${pos}"}`;
|
|
1082
1124
|
});
|
|
1083
|
-
|
|
1084
|
-
return
|
|
1125
|
+
result = result.replace(twee2BadPosRe, "$1");
|
|
1126
|
+
return result;
|
|
1085
1127
|
}
|
|
1086
1128
|
|
|
1087
1129
|
// src/parser.ts
|
|
@@ -1159,7 +1201,11 @@ function parseTwee(source, options = {}) {
|
|
|
1159
1201
|
return { passages, diagnostics };
|
|
1160
1202
|
}
|
|
1161
1203
|
try {
|
|
1162
|
-
const
|
|
1204
|
+
const raw = JSON.parse(item.val);
|
|
1205
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
1206
|
+
throw new Error("expected a JSON object");
|
|
1207
|
+
}
|
|
1208
|
+
const parsed = raw;
|
|
1163
1209
|
const meta = {};
|
|
1164
1210
|
if (typeof parsed.position === "string") meta.position = parsed.position;
|
|
1165
1211
|
if (typeof parsed.size === "string") meta.size = parsed.size;
|
|
@@ -1179,6 +1225,16 @@ function parseTwee(source, options = {}) {
|
|
|
1179
1225
|
current.text = trim ? item.val.trim() : item.val;
|
|
1180
1226
|
break;
|
|
1181
1227
|
}
|
|
1228
|
+
default: {
|
|
1229
|
+
const _exhaustive = item.type;
|
|
1230
|
+
diagnostics.push({
|
|
1231
|
+
level: "error",
|
|
1232
|
+
message: `line ${item.line}: Unhandled lexer item type: ${_exhaustive}.`,
|
|
1233
|
+
file: filename,
|
|
1234
|
+
line: item.line
|
|
1235
|
+
});
|
|
1236
|
+
return { passages, diagnostics };
|
|
1237
|
+
}
|
|
1182
1238
|
}
|
|
1183
1239
|
lastType = item.type;
|
|
1184
1240
|
}
|
|
@@ -1189,15 +1245,6 @@ function parseTwee(source, options = {}) {
|
|
|
1189
1245
|
}
|
|
1190
1246
|
|
|
1191
1247
|
// src/loader.ts
|
|
1192
|
-
function readUTF82(filename) {
|
|
1193
|
-
let content = (0, import_node_fs3.readFileSync)(filename, "utf-8");
|
|
1194
|
-
if (content.charCodeAt(0) === 65279) content = content.slice(1);
|
|
1195
|
-
content = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
1196
|
-
return content;
|
|
1197
|
-
}
|
|
1198
|
-
function readBase64(filename) {
|
|
1199
|
-
return (0, import_node_fs3.readFileSync)(filename).toString("base64");
|
|
1200
|
-
}
|
|
1201
1248
|
function loadSources(story, filenames, opts, diagnostics, processedFiles) {
|
|
1202
1249
|
for (const filename of filenames) {
|
|
1203
1250
|
if (processedFiles.has(filename)) {
|
|
@@ -1296,7 +1343,7 @@ function loadInlineSources(story, sources, opts, diagnostics) {
|
|
|
1296
1343
|
}
|
|
1297
1344
|
}
|
|
1298
1345
|
function loadTwee(story, filename, opts, diagnostics) {
|
|
1299
|
-
const source =
|
|
1346
|
+
const source = readUTF8(filename);
|
|
1300
1347
|
const result = parseTwee(source, {
|
|
1301
1348
|
filename,
|
|
1302
1349
|
trim: opts.trim ?? true,
|
|
@@ -1308,12 +1355,12 @@ function loadTwee(story, filename, opts, diagnostics) {
|
|
|
1308
1355
|
}
|
|
1309
1356
|
}
|
|
1310
1357
|
function loadTagged(story, tag, filename, diagnostics) {
|
|
1311
|
-
const source =
|
|
1312
|
-
storyAdd(story, { name: (0,
|
|
1358
|
+
const source = readUTF8(filename);
|
|
1359
|
+
storyAdd(story, { name: (0, import_node_path5.basename)(filename), tags: [tag], text: source }, diagnostics);
|
|
1313
1360
|
}
|
|
1314
1361
|
function loadMedia(story, tag, filename, diagnostics) {
|
|
1315
1362
|
const source = readBase64(filename);
|
|
1316
|
-
const name = (
|
|
1363
|
+
const name = baseNameWithoutExt(filename);
|
|
1317
1364
|
storyAdd(
|
|
1318
1365
|
story,
|
|
1319
1366
|
{
|
|
@@ -1326,8 +1373,8 @@ function loadMedia(story, tag, filename, diagnostics) {
|
|
|
1326
1373
|
}
|
|
1327
1374
|
function loadFont(story, filename, diagnostics) {
|
|
1328
1375
|
const source = readBase64(filename);
|
|
1329
|
-
const name = (0,
|
|
1330
|
-
const family =
|
|
1376
|
+
const name = (0, import_node_path5.basename)(filename);
|
|
1377
|
+
const family = baseNameWithoutExt(filename);
|
|
1331
1378
|
const ext = normalizedFileExt(filename);
|
|
1332
1379
|
const mediaType = mediaTypeFromExt(ext);
|
|
1333
1380
|
const hint = fontFormatHint(ext);
|
|
@@ -1345,9 +1392,11 @@ function loadFont(story, filename, diagnostics) {
|
|
|
1345
1392
|
);
|
|
1346
1393
|
}
|
|
1347
1394
|
|
|
1395
|
+
// src/version.ts
|
|
1396
|
+
var VERSION = "1.2.0";
|
|
1397
|
+
|
|
1348
1398
|
// src/output-twine2.ts
|
|
1349
1399
|
var CREATOR_NAME = "Twee-ts";
|
|
1350
|
-
var CREATOR_VERSION = "0.1.0";
|
|
1351
1400
|
function toTwine2Archive(story, startName, diagnostics) {
|
|
1352
1401
|
return getTwine2DataChunk(story, startName, diagnostics) + "\n";
|
|
1353
1402
|
}
|
|
@@ -1375,12 +1424,12 @@ function getTwine2DataChunk(story, startName, diagnostics) {
|
|
|
1375
1424
|
}
|
|
1376
1425
|
let styleContent = "";
|
|
1377
1426
|
if (stylesheets.length === 1) {
|
|
1378
|
-
styleContent = stylesheets[0]
|
|
1427
|
+
styleContent = stylesheets[0]?.text ?? "";
|
|
1379
1428
|
} else if (stylesheets.length > 1) {
|
|
1380
1429
|
let pidS = 1;
|
|
1381
1430
|
for (const p of stylesheets) {
|
|
1382
1431
|
if (pidS > 1 && !styleContent.endsWith("\n")) styleContent += "\n";
|
|
1383
|
-
styleContent += `/* twine-user-stylesheet #${pidS}: "${p.name}" */
|
|
1432
|
+
styleContent += `/* twine-user-stylesheet #${pidS}: "${commentSanitize(p.name)}" */
|
|
1384
1433
|
`;
|
|
1385
1434
|
styleContent += p.text;
|
|
1386
1435
|
pidS++;
|
|
@@ -1389,12 +1438,12 @@ function getTwine2DataChunk(story, startName, diagnostics) {
|
|
|
1389
1438
|
parts.push(`<style role="stylesheet" id="twine-user-stylesheet" type="text/twine-css">${styleContent}</style>`);
|
|
1390
1439
|
let scriptContent = "";
|
|
1391
1440
|
if (scripts.length === 1) {
|
|
1392
|
-
scriptContent = scripts[0]
|
|
1441
|
+
scriptContent = scripts[0]?.text ?? "";
|
|
1393
1442
|
} else if (scripts.length > 1) {
|
|
1394
1443
|
let pidS = 1;
|
|
1395
1444
|
for (const p of scripts) {
|
|
1396
1445
|
if (pidS > 1 && !scriptContent.endsWith("\n")) scriptContent += "\n";
|
|
1397
|
-
scriptContent += `/* twine-user-script #${pidS}: "${p.name}" */
|
|
1446
|
+
scriptContent += `/* twine-user-script #${pidS}: "${commentSanitize(p.name)}" */
|
|
1398
1447
|
`;
|
|
1399
1448
|
scriptContent += p.text;
|
|
1400
1449
|
pidS++;
|
|
@@ -1424,7 +1473,7 @@ function getTwine2DataChunk(story, startName, diagnostics) {
|
|
|
1424
1473
|
}
|
|
1425
1474
|
const options = opts.join(" ");
|
|
1426
1475
|
const zoom = story.twine2.zoom === Math.floor(story.twine2.zoom) ? story.twine2.zoom.toString() : story.twine2.zoom.toFixed(1);
|
|
1427
|
-
const wrapper = `<!-- UUID://${story.ifid}// --><tw-storydata name="${attrEscape(story.name)}" startnode="${startID}" creator="${attrEscape(CREATOR_NAME)}" creator-version="${attrEscape(
|
|
1476
|
+
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>`;
|
|
1428
1477
|
return wrapper + parts.join("") + "</tw-storydata>";
|
|
1429
1478
|
}
|
|
1430
1479
|
function ensureIFID(story, diagnostics) {
|
|
@@ -1446,50 +1495,50 @@ function ensureIFID(story, diagnostics) {
|
|
|
1446
1495
|
|
|
1447
1496
|
// src/output-twine1.ts
|
|
1448
1497
|
var import_node_fs4 = require("fs");
|
|
1449
|
-
var
|
|
1498
|
+
var import_node_path6 = require("path");
|
|
1450
1499
|
var CREATOR_NAME2 = "twee-ts";
|
|
1451
|
-
var CREATOR_VERSION2 = "0.1.0";
|
|
1452
1500
|
function toTwine1Archive(story, _startName) {
|
|
1453
1501
|
const { data, count } = getTwine1PassageChunk(story);
|
|
1454
1502
|
return `<div id="storeArea" data-size="${count}" hidden>${data}</div>
|
|
1455
1503
|
`;
|
|
1456
1504
|
}
|
|
1457
1505
|
function toTwine1HTML(story, format, startName, _diagnostics) {
|
|
1458
|
-
const formatDir = (0,
|
|
1459
|
-
const parentDir = (0,
|
|
1506
|
+
const formatDir = (0, import_node_path6.dirname)(format.filename);
|
|
1507
|
+
const parentDir = (0, import_node_path6.dirname)(formatDir);
|
|
1460
1508
|
let template = readFormatSource(format);
|
|
1461
1509
|
const { data, count } = getTwine1PassageChunk(story);
|
|
1462
|
-
template = tryReplaceComponent(template, '"USER_LIB"', (0,
|
|
1463
|
-
template = tryReplaceComponent(template, '"ENGINE"', (0,
|
|
1464
|
-
template = tryReplaceComponent(template, '"SUGARCANE"', (0,
|
|
1465
|
-
template = tryReplaceComponent(template, '"JONAH"', (0,
|
|
1510
|
+
template = tryReplaceComponent(template, '"USER_LIB"', (0, import_node_path6.join)(formatDir, "userlib.js"), false);
|
|
1511
|
+
template = tryReplaceComponent(template, '"ENGINE"', (0, import_node_path6.join)(parentDir, "engine.js"), true);
|
|
1512
|
+
template = tryReplaceComponent(template, '"SUGARCANE"', (0, import_node_path6.join)(formatDir, "code.js"), true);
|
|
1513
|
+
template = tryReplaceComponent(template, '"JONAH"', (0, import_node_path6.join)(formatDir, "code.js"), true);
|
|
1466
1514
|
if (story.twine1.settings.get("jquery") === "on") {
|
|
1467
|
-
template = tryReplaceComponent(template, '"JQUERY"', (0,
|
|
1515
|
+
template = tryReplaceComponent(template, '"JQUERY"', (0, import_node_path6.join)(parentDir, "jquery.js"), true);
|
|
1468
1516
|
}
|
|
1469
1517
|
if (story.twine1.settings.get("modernizr") === "on") {
|
|
1470
|
-
template = tryReplaceComponent(template, '"MODERNIZR"', (0,
|
|
1518
|
+
template = tryReplaceComponent(template, '"MODERNIZR"', (0, import_node_path6.join)(parentDir, "modernizr.js"), true);
|
|
1471
1519
|
}
|
|
1472
1520
|
const displayStart = startName === "Start" ? "" : startName;
|
|
1473
|
-
template = template.replace('"VERSION"', `Compiled with ${CREATOR_NAME2}, ${
|
|
1521
|
+
template = template.replace('"VERSION"', `Compiled with ${CREATOR_NAME2}, ${VERSION}`);
|
|
1474
1522
|
template = template.replace('"TIME"', `Built on ${(/* @__PURE__ */ new Date()).toUTCString()}`);
|
|
1475
|
-
template = template.replace('"START_AT"', `"${displayStart}"`);
|
|
1523
|
+
template = template.replace('"START_AT"', `"${jsStringEscape(displayStart)}"`);
|
|
1476
1524
|
template = template.replace('"STORY_SIZE"', `"${count}"`);
|
|
1477
1525
|
if (template.includes('"STORY"')) {
|
|
1478
1526
|
template = template.replace('"STORY"', data);
|
|
1479
1527
|
} else {
|
|
1480
1528
|
let footer;
|
|
1481
1529
|
try {
|
|
1482
|
-
footer = (0, import_node_fs4.readFileSync)((0,
|
|
1530
|
+
footer = (0, import_node_fs4.readFileSync)((0, import_node_path6.join)(formatDir, "footer.html"), "utf-8");
|
|
1483
1531
|
} catch {
|
|
1484
1532
|
footer = "</div>\n</body>\n</html>\n";
|
|
1485
1533
|
}
|
|
1486
1534
|
template += data + footer;
|
|
1487
1535
|
}
|
|
1488
1536
|
if (story.ifid) {
|
|
1537
|
+
const safeIfid = htmlCommentSanitize(story.ifid);
|
|
1489
1538
|
if (template.includes('<div id="store-area"')) {
|
|
1490
|
-
template = template.replace('<div id="store-area"', `<!-- UUID://${
|
|
1539
|
+
template = template.replace('<div id="store-area"', `<!-- UUID://${safeIfid}// --><div id="store-area"`);
|
|
1491
1540
|
} else {
|
|
1492
|
-
template = template.replace('<div id="storeArea"', `<!-- UUID://${
|
|
1541
|
+
template = template.replace('<div id="storeArea"', `<!-- UUID://${safeIfid}// --><div id="storeArea"`);
|
|
1493
1542
|
}
|
|
1494
1543
|
}
|
|
1495
1544
|
return template;
|
|
@@ -1504,13 +1553,18 @@ function getTwine1PassageChunk(story) {
|
|
|
1504
1553
|
}
|
|
1505
1554
|
return { data, count };
|
|
1506
1555
|
}
|
|
1507
|
-
function tryReplaceComponent(template, placeholder, componentPath,
|
|
1556
|
+
function tryReplaceComponent(template, placeholder, componentPath, required) {
|
|
1508
1557
|
if (!template.includes(placeholder)) return template;
|
|
1509
1558
|
try {
|
|
1510
1559
|
let content = (0, import_node_fs4.readFileSync)(componentPath, "utf-8");
|
|
1511
1560
|
if (content.charCodeAt(0) === 65279) content = content.slice(1);
|
|
1512
1561
|
return template.replace(placeholder, content);
|
|
1513
|
-
} catch {
|
|
1562
|
+
} catch (e) {
|
|
1563
|
+
if (required) {
|
|
1564
|
+
throw new Error(
|
|
1565
|
+
`Required format component not found: ${componentPath}: ${e instanceof Error ? e.message : String(e)}`
|
|
1566
|
+
);
|
|
1567
|
+
}
|
|
1514
1568
|
return template;
|
|
1515
1569
|
}
|
|
1516
1570
|
}
|
|
@@ -1525,16 +1579,6 @@ function toTwee(story, outMode) {
|
|
|
1525
1579
|
}
|
|
1526
1580
|
|
|
1527
1581
|
// src/modules.ts
|
|
1528
|
-
var import_node_fs5 = require("fs");
|
|
1529
|
-
var import_node_path6 = require("path");
|
|
1530
|
-
function readUTF83(filename) {
|
|
1531
|
-
let content = (0, import_node_fs5.readFileSync)(filename, "utf-8");
|
|
1532
|
-
if (content.charCodeAt(0) === 65279) content = content.slice(1);
|
|
1533
|
-
return content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
1534
|
-
}
|
|
1535
|
-
function readBase642(filename) {
|
|
1536
|
-
return (0, import_node_fs5.readFileSync)(filename).toString("base64");
|
|
1537
|
-
}
|
|
1538
1582
|
function loadModules(filenames) {
|
|
1539
1583
|
const processed = /* @__PURE__ */ new Set();
|
|
1540
1584
|
const headTags = [];
|
|
@@ -1564,16 +1608,16 @@ function loadModules(filenames) {
|
|
|
1564
1608
|
return headTags.join("\n");
|
|
1565
1609
|
}
|
|
1566
1610
|
function loadModuleTagged(tag, filename) {
|
|
1567
|
-
const source =
|
|
1611
|
+
const source = readUTF8(filename).trim();
|
|
1568
1612
|
if (source.length === 0) return null;
|
|
1569
|
-
const family = (
|
|
1613
|
+
const family = baseNameWithoutExt(filename);
|
|
1570
1614
|
const idSlug = `${tag}-module-${slugify(family)}`;
|
|
1571
1615
|
const mimeType = tag === "script" ? "text/javascript" : "text/css";
|
|
1572
1616
|
return `<${tag} id="${idSlug}" type="${mimeType}">${source}</${tag}>`;
|
|
1573
1617
|
}
|
|
1574
1618
|
function loadModuleFont(filename) {
|
|
1575
|
-
const source =
|
|
1576
|
-
const family = (
|
|
1619
|
+
const source = readBase64(filename);
|
|
1620
|
+
const family = baseNameWithoutExt(filename);
|
|
1577
1621
|
const idSlug = `style-module-${slugify(family)}`;
|
|
1578
1622
|
const ext = normalizedFileExt(filename);
|
|
1579
1623
|
const mediaType = mediaTypeFromExt(ext);
|
|
@@ -1583,7 +1627,7 @@ function loadModuleFont(filename) {
|
|
|
1583
1627
|
src: url("data:${mediaType};base64,${source}") format("${hint}");
|
|
1584
1628
|
}</style>`;
|
|
1585
1629
|
}
|
|
1586
|
-
function modifyHead(html, modulePaths, headFile) {
|
|
1630
|
+
function modifyHead(html, modulePaths, headFile, diagnostics) {
|
|
1587
1631
|
const parts = [];
|
|
1588
1632
|
if (modulePaths.length > 0) {
|
|
1589
1633
|
const modules = loadModules(modulePaths).trim();
|
|
@@ -1591,9 +1635,13 @@ function modifyHead(html, modulePaths, headFile) {
|
|
|
1591
1635
|
}
|
|
1592
1636
|
if (headFile) {
|
|
1593
1637
|
try {
|
|
1594
|
-
const source =
|
|
1638
|
+
const source = readUTF8(headFile).trim();
|
|
1595
1639
|
if (source.length > 0) parts.push(source);
|
|
1596
|
-
} catch {
|
|
1640
|
+
} catch (e) {
|
|
1641
|
+
diagnostics?.push({
|
|
1642
|
+
level: "warning",
|
|
1643
|
+
message: `Failed to read head file "${headFile}": ${e instanceof Error ? e.message : String(e)}`
|
|
1644
|
+
});
|
|
1597
1645
|
}
|
|
1598
1646
|
}
|
|
1599
1647
|
if (parts.length > 0) {
|
|
@@ -1604,7 +1652,7 @@ function modifyHead(html, modulePaths, headFile) {
|
|
|
1604
1652
|
}
|
|
1605
1653
|
|
|
1606
1654
|
// src/remote-formats.ts
|
|
1607
|
-
var
|
|
1655
|
+
var import_node_fs5 = require("fs");
|
|
1608
1656
|
var import_node_path7 = require("path");
|
|
1609
1657
|
var import_node_os2 = require("os");
|
|
1610
1658
|
var DEFAULT_SFA_INDICES = [
|
|
@@ -1620,6 +1668,24 @@ var indexCache = /* @__PURE__ */ new Map();
|
|
|
1620
1668
|
function clearIndexCache() {
|
|
1621
1669
|
indexCache.clear();
|
|
1622
1670
|
}
|
|
1671
|
+
function isValidEntry(val) {
|
|
1672
|
+
if (typeof val !== "object" || val === null || Array.isArray(val)) return false;
|
|
1673
|
+
const obj = val;
|
|
1674
|
+
return typeof obj.name === "string" && typeof obj.version === "string" && typeof obj.checksums === "object" && obj.checksums !== null && !Array.isArray(obj.checksums);
|
|
1675
|
+
}
|
|
1676
|
+
function validateSFAIndex(json) {
|
|
1677
|
+
if (typeof json !== "object" || json === null) {
|
|
1678
|
+
throw new Error("SFA index is not an object");
|
|
1679
|
+
}
|
|
1680
|
+
const obj = json;
|
|
1681
|
+
const twine1 = Array.isArray(obj["twine1"]) ? obj["twine1"].filter(isValidEntry) : [];
|
|
1682
|
+
const twine2 = Array.isArray(obj["twine2"]) ? obj["twine2"].filter(isValidEntry) : [];
|
|
1683
|
+
return { twine1, twine2 };
|
|
1684
|
+
}
|
|
1685
|
+
function makeFormatId(name, version) {
|
|
1686
|
+
const major = parseSemver(version)?.[0] ?? "0";
|
|
1687
|
+
return `${name.toLowerCase().replace(/\s+/g, "-")}-${major}`;
|
|
1688
|
+
}
|
|
1623
1689
|
async function fetchIndex(url) {
|
|
1624
1690
|
const cached = indexCache.get(url);
|
|
1625
1691
|
if (cached) return cached;
|
|
@@ -1627,37 +1693,42 @@ async function fetchIndex(url) {
|
|
|
1627
1693
|
if (!res.ok) {
|
|
1628
1694
|
throw new Error(`Failed to fetch format index from ${url}: ${res.status} ${res.statusText}`);
|
|
1629
1695
|
}
|
|
1630
|
-
const data = await res.json();
|
|
1696
|
+
const data = validateSFAIndex(await res.json());
|
|
1631
1697
|
indexCache.set(url, data);
|
|
1632
1698
|
return data;
|
|
1633
1699
|
}
|
|
1634
1700
|
function findEntry(index, name, version) {
|
|
1635
1701
|
const wanted = parseSemver(version);
|
|
1636
|
-
const
|
|
1637
|
-
|
|
1702
|
+
const searchArrays = [
|
|
1703
|
+
{ entries: index.twine2 ?? [], formatType: "twine2" },
|
|
1704
|
+
{ entries: index.twine1 ?? [], formatType: "twine1" }
|
|
1705
|
+
];
|
|
1706
|
+
let bestResult;
|
|
1638
1707
|
let bestVersion = null;
|
|
1639
|
-
for (const
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
if (
|
|
1646
|
-
bestVersion
|
|
1647
|
-
|
|
1708
|
+
for (const { entries, formatType } of searchArrays) {
|
|
1709
|
+
for (const entry of entries) {
|
|
1710
|
+
if (entry.name.toLowerCase() !== name.toLowerCase()) continue;
|
|
1711
|
+
const have = parseSemver(entry.version);
|
|
1712
|
+
if (!have) continue;
|
|
1713
|
+
if (wanted && semverCompare(have, wanted) === 0) return { entry, formatType };
|
|
1714
|
+
if (wanted === null || have[0] === wanted[0] && semverCompare(have, wanted) >= 0) {
|
|
1715
|
+
if (!bestVersion || semverCompare(have, bestVersion) > 0) {
|
|
1716
|
+
bestVersion = have;
|
|
1717
|
+
bestResult = { entry, formatType };
|
|
1718
|
+
}
|
|
1648
1719
|
}
|
|
1649
1720
|
}
|
|
1650
1721
|
}
|
|
1651
|
-
return
|
|
1722
|
+
return bestResult;
|
|
1652
1723
|
}
|
|
1653
1724
|
async function verifySHA256(content, expectedHex) {
|
|
1654
1725
|
const digest = await crypto.subtle.digest("SHA-256", content);
|
|
1655
1726
|
const hex = Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1656
1727
|
return hex === expectedHex.toLowerCase();
|
|
1657
1728
|
}
|
|
1658
|
-
function getDownloadUrl(indexUrl, entry) {
|
|
1729
|
+
function getDownloadUrl(indexUrl, entry, formatType) {
|
|
1659
1730
|
const base = indexUrl.replace(/\/index\.json$/, "");
|
|
1660
|
-
return `${base}
|
|
1731
|
+
return `${base}/${formatType}/${entry.name}/${entry.version}/format.js`;
|
|
1661
1732
|
}
|
|
1662
1733
|
async function fetchAndCacheFormat(entry, downloadUrl) {
|
|
1663
1734
|
const res = await fetch(downloadUrl);
|
|
@@ -1668,22 +1739,23 @@ async function fetchAndCacheFormat(entry, downloadUrl) {
|
|
|
1668
1739
|
const checksumKey = Object.keys(entry.checksums ?? {}).find((k) => k.endsWith("format.js"));
|
|
1669
1740
|
if (checksumKey) {
|
|
1670
1741
|
const expected = entry.checksums[checksumKey];
|
|
1742
|
+
if (!expected) throw new Error(`Missing checksum value for key "${checksumKey}"`);
|
|
1671
1743
|
const encoder = new TextEncoder();
|
|
1672
1744
|
const valid = await verifySHA256(encoder.encode(text), expected);
|
|
1673
1745
|
if (!valid) {
|
|
1674
1746
|
throw new Error(`Checksum verification failed for ${entry.name} ${entry.version}`);
|
|
1675
1747
|
}
|
|
1676
1748
|
}
|
|
1677
|
-
const id =
|
|
1749
|
+
const id = makeFormatId(entry.name, entry.version);
|
|
1678
1750
|
const data = parseFormatJSON(text, id);
|
|
1679
1751
|
if (!data) {
|
|
1680
1752
|
throw new Error(`Failed to parse format JSON from ${downloadUrl}`);
|
|
1681
1753
|
}
|
|
1682
1754
|
const cacheDir = getCacheDir();
|
|
1683
1755
|
const formatDir = (0, import_node_path7.join)(cacheDir, entry.name, entry.version);
|
|
1684
|
-
(0,
|
|
1756
|
+
(0, import_node_fs5.mkdirSync)(formatDir, { recursive: true });
|
|
1685
1757
|
const formatPath = (0, import_node_path7.join)(formatDir, "format.js");
|
|
1686
|
-
(0,
|
|
1758
|
+
(0, import_node_fs5.writeFileSync)(formatPath, text, "utf-8");
|
|
1687
1759
|
return {
|
|
1688
1760
|
id,
|
|
1689
1761
|
filename: formatPath,
|
|
@@ -1704,12 +1776,12 @@ async function fetchDirectFormat(url) {
|
|
|
1704
1776
|
if (!data) {
|
|
1705
1777
|
throw new Error(`Failed to parse format JSON from ${url}`);
|
|
1706
1778
|
}
|
|
1707
|
-
const id =
|
|
1779
|
+
const id = makeFormatId(data.name, data.version);
|
|
1708
1780
|
const cacheDir = getCacheDir();
|
|
1709
1781
|
const formatDir = (0, import_node_path7.join)(cacheDir, data.name, data.version);
|
|
1710
|
-
(0,
|
|
1782
|
+
(0, import_node_fs5.mkdirSync)(formatDir, { recursive: true });
|
|
1711
1783
|
const formatPath = (0, import_node_path7.join)(formatDir, "format.js");
|
|
1712
|
-
(0,
|
|
1784
|
+
(0, import_node_fs5.writeFileSync)(formatPath, text, "utf-8");
|
|
1713
1785
|
return {
|
|
1714
1786
|
id,
|
|
1715
1787
|
filename: formatPath,
|
|
@@ -1720,6 +1792,7 @@ async function fetchDirectFormat(url) {
|
|
|
1720
1792
|
};
|
|
1721
1793
|
}
|
|
1722
1794
|
async function resolveRemoteFormat(name, version, indices, urls) {
|
|
1795
|
+
let lastError;
|
|
1723
1796
|
if (urls) {
|
|
1724
1797
|
for (const url of urls) {
|
|
1725
1798
|
try {
|
|
@@ -1731,7 +1804,8 @@ async function resolveRemoteFormat(name, version, indices, urls) {
|
|
|
1731
1804
|
return info;
|
|
1732
1805
|
}
|
|
1733
1806
|
}
|
|
1734
|
-
} catch {
|
|
1807
|
+
} catch (e) {
|
|
1808
|
+
lastError = e instanceof Error ? e : new Error(String(e));
|
|
1735
1809
|
}
|
|
1736
1810
|
}
|
|
1737
1811
|
}
|
|
@@ -1739,24 +1813,26 @@ async function resolveRemoteFormat(name, version, indices, urls) {
|
|
|
1739
1813
|
for (const indexUrl of allIndices) {
|
|
1740
1814
|
try {
|
|
1741
1815
|
const index = await fetchIndex(indexUrl);
|
|
1742
|
-
const
|
|
1743
|
-
if (
|
|
1744
|
-
const cached = getCachedFormat(entry.name, entry.version);
|
|
1816
|
+
const result = findEntry(index, name, version);
|
|
1817
|
+
if (result) {
|
|
1818
|
+
const cached = getCachedFormat(result.entry.name, result.entry.version);
|
|
1745
1819
|
if (cached) return cached;
|
|
1746
|
-
const downloadUrl = getDownloadUrl(indexUrl, entry);
|
|
1747
|
-
return await fetchAndCacheFormat(entry, downloadUrl);
|
|
1820
|
+
const downloadUrl = getDownloadUrl(indexUrl, result.entry, result.formatType);
|
|
1821
|
+
return await fetchAndCacheFormat(result.entry, downloadUrl);
|
|
1748
1822
|
}
|
|
1749
|
-
} catch {
|
|
1823
|
+
} catch (e) {
|
|
1824
|
+
lastError = e instanceof Error ? e : new Error(String(e));
|
|
1750
1825
|
}
|
|
1751
1826
|
}
|
|
1827
|
+
if (lastError) throw lastError;
|
|
1752
1828
|
return void 0;
|
|
1753
1829
|
}
|
|
1754
1830
|
function getCachedFormat(name, version) {
|
|
1755
1831
|
const formatPath = (0, import_node_path7.join)(getCacheDir(), name, version, "format.js");
|
|
1756
1832
|
try {
|
|
1757
|
-
if (!(0,
|
|
1758
|
-
const source = (0,
|
|
1759
|
-
const id =
|
|
1833
|
+
if (!(0, import_node_fs5.existsSync)(formatPath)) return void 0;
|
|
1834
|
+
const source = (0, import_node_fs5.readFileSync)(formatPath, "utf-8");
|
|
1835
|
+
const id = makeFormatId(name, version);
|
|
1760
1836
|
const data = parseFormatJSON(source, id);
|
|
1761
1837
|
if (!data) return void 0;
|
|
1762
1838
|
return {
|
|
@@ -1775,26 +1851,26 @@ function discoverCachedFormats() {
|
|
|
1775
1851
|
const formats = /* @__PURE__ */ new Map();
|
|
1776
1852
|
const cacheDir = getCacheDir();
|
|
1777
1853
|
try {
|
|
1778
|
-
if (!(0,
|
|
1854
|
+
if (!(0, import_node_fs5.existsSync)(cacheDir)) return formats;
|
|
1779
1855
|
} catch {
|
|
1780
1856
|
return formats;
|
|
1781
1857
|
}
|
|
1782
1858
|
let names;
|
|
1783
1859
|
try {
|
|
1784
|
-
names = (0,
|
|
1860
|
+
names = (0, import_node_fs5.readdirSync)(cacheDir);
|
|
1785
1861
|
} catch {
|
|
1786
1862
|
return formats;
|
|
1787
1863
|
}
|
|
1788
1864
|
for (const name of names) {
|
|
1789
1865
|
const nameDir = (0, import_node_path7.join)(cacheDir, name);
|
|
1790
1866
|
try {
|
|
1791
|
-
if (!(0,
|
|
1867
|
+
if (!(0, import_node_fs5.statSync)(nameDir).isDirectory()) continue;
|
|
1792
1868
|
} catch {
|
|
1793
1869
|
continue;
|
|
1794
1870
|
}
|
|
1795
1871
|
let versions;
|
|
1796
1872
|
try {
|
|
1797
|
-
versions = (0,
|
|
1873
|
+
versions = (0, import_node_fs5.readdirSync)(nameDir);
|
|
1798
1874
|
} catch {
|
|
1799
1875
|
continue;
|
|
1800
1876
|
}
|
|
@@ -1823,7 +1899,7 @@ async function compile(options) {
|
|
|
1823
1899
|
}
|
|
1824
1900
|
async function compileToFile(options) {
|
|
1825
1901
|
const result = await compile(options);
|
|
1826
|
-
(0,
|
|
1902
|
+
(0, import_node_fs6.writeFileSync)(options.outFile, result.output, "utf-8");
|
|
1827
1903
|
return result;
|
|
1828
1904
|
}
|
|
1829
1905
|
async function watch(options) {
|
|
@@ -1833,7 +1909,7 @@ async function watch(options) {
|
|
|
1833
1909
|
const allPaths = [...filePaths, ...modulePaths];
|
|
1834
1910
|
const handle = watchFilesystem(allPaths, options.outFile, () => {
|
|
1835
1911
|
buildOutput(options).then((result) => {
|
|
1836
|
-
(0,
|
|
1912
|
+
(0, import_node_fs6.writeFileSync)(options.outFile, result.output, "utf-8");
|
|
1837
1913
|
options.onBuild?.(result);
|
|
1838
1914
|
}).catch((e) => {
|
|
1839
1915
|
options.onError?.(e instanceof Error ? e : new Error(String(e)));
|
|
@@ -1865,10 +1941,7 @@ async function buildOutput(options) {
|
|
|
1865
1941
|
loadSources(story, sourceFilenames, { trim, twee2Compat }, diagnostics, processedFiles);
|
|
1866
1942
|
loadInlineSources(story, inlineSources, { trim, twee2Compat }, diagnostics);
|
|
1867
1943
|
if (options.tagAliases) {
|
|
1868
|
-
applyTagAliases(story.passages, options.tagAliases);
|
|
1869
|
-
}
|
|
1870
|
-
const fatalErrors = diagnostics.filter((d) => d.level === "error");
|
|
1871
|
-
if (fatalErrors.length > 0 && outputMode === "html") {
|
|
1944
|
+
story.passages = applyTagAliases(story.passages, options.tagAliases);
|
|
1872
1945
|
}
|
|
1873
1946
|
let format;
|
|
1874
1947
|
let formatId = options.formatId ?? "";
|
|
@@ -1887,7 +1960,11 @@ async function buildOutput(options) {
|
|
|
1887
1960
|
const remoteVersion = story.twine2.formatVersion || "";
|
|
1888
1961
|
try {
|
|
1889
1962
|
format = await resolveRemoteFormat(remoteName, remoteVersion, options.formatIndices, options.formatUrls);
|
|
1890
|
-
} catch {
|
|
1963
|
+
} catch (e) {
|
|
1964
|
+
diagnostics.push({
|
|
1965
|
+
level: "warning",
|
|
1966
|
+
message: `Remote format fetch failed for "${remoteName}": ${e instanceof Error ? e.message : String(e)}`
|
|
1967
|
+
});
|
|
1891
1968
|
}
|
|
1892
1969
|
}
|
|
1893
1970
|
if (!format) {
|
|
@@ -1898,14 +1975,7 @@ async function buildOutput(options) {
|
|
|
1898
1975
|
});
|
|
1899
1976
|
}
|
|
1900
1977
|
}
|
|
1901
|
-
|
|
1902
|
-
if (options.startPassage) {
|
|
1903
|
-
startName = options.startPassage;
|
|
1904
|
-
} else if (story.twine2.start) {
|
|
1905
|
-
startName = story.twine2.start;
|
|
1906
|
-
} else {
|
|
1907
|
-
startName = DEFAULT_START_NAME;
|
|
1908
|
-
}
|
|
1978
|
+
const startName = options.startPassage || story.twine2.start || DEFAULT_START_NAME;
|
|
1909
1979
|
if (testMode) {
|
|
1910
1980
|
story.twine2.options.set("debug", true);
|
|
1911
1981
|
}
|
|
@@ -1924,8 +1994,7 @@ async function buildOutput(options) {
|
|
|
1924
1994
|
case "json":
|
|
1925
1995
|
output = storyToJSON(story);
|
|
1926
1996
|
break;
|
|
1927
|
-
case "html":
|
|
1928
|
-
default: {
|
|
1997
|
+
case "html": {
|
|
1929
1998
|
if (!storyHas(story, startName)) {
|
|
1930
1999
|
diagnostics.push({
|
|
1931
2000
|
level: "error",
|
|
@@ -1947,9 +2016,13 @@ async function buildOutput(options) {
|
|
|
1947
2016
|
output = toTwine1HTML(story, format, startName, diagnostics);
|
|
1948
2017
|
}
|
|
1949
2018
|
const modulePaths = options.modules ? getFilenames(options.modules) : [];
|
|
1950
|
-
output = modifyHead(output, modulePaths, options.headFile);
|
|
2019
|
+
output = modifyHead(output, modulePaths, options.headFile, diagnostics);
|
|
1951
2020
|
break;
|
|
1952
2021
|
}
|
|
2022
|
+
default: {
|
|
2023
|
+
const _exhaustive = outputMode;
|
|
2024
|
+
throw new TweeTsError(`Unhandled output mode: ${_exhaustive}`, diagnostics);
|
|
2025
|
+
}
|
|
1953
2026
|
}
|
|
1954
2027
|
const stats = {
|
|
1955
2028
|
...getStoryStats(story),
|
|
@@ -2085,17 +2158,17 @@ function storyInspect(story) {
|
|
|
2085
2158
|
}
|
|
2086
2159
|
|
|
2087
2160
|
// src/config.ts
|
|
2088
|
-
var
|
|
2161
|
+
var import_node_fs7 = require("fs");
|
|
2089
2162
|
var import_node_path8 = require("path");
|
|
2090
2163
|
var CONFIG_FILENAME = "twee-ts.config.json";
|
|
2091
2164
|
var VALID_OUTPUT_MODES = ["html", "twee3", "twee1", "twine2-archive", "twine1-archive", "json"];
|
|
2092
2165
|
function loadConfig(dir) {
|
|
2093
2166
|
const base = dir ?? process.cwd();
|
|
2094
2167
|
const configPath = (0, import_node_path8.join)(base, CONFIG_FILENAME);
|
|
2095
|
-
if (!(0,
|
|
2168
|
+
if (!(0, import_node_fs7.existsSync)(configPath)) return null;
|
|
2096
2169
|
let raw;
|
|
2097
2170
|
try {
|
|
2098
|
-
raw = (0,
|
|
2171
|
+
raw = (0, import_node_fs7.readFileSync)(configPath, "utf-8");
|
|
2099
2172
|
} catch {
|
|
2100
2173
|
return null;
|
|
2101
2174
|
}
|
|
@@ -2115,7 +2188,7 @@ function loadConfig(dir) {
|
|
|
2115
2188
|
function loadConfigFile(filePath) {
|
|
2116
2189
|
let raw;
|
|
2117
2190
|
try {
|
|
2118
|
-
raw = (0,
|
|
2191
|
+
raw = (0, import_node_fs7.readFileSync)(filePath, "utf-8");
|
|
2119
2192
|
} catch (e) {
|
|
2120
2193
|
throw new Error(`Cannot read config file ${filePath}: ${e instanceof Error ? e.message : String(e)}`);
|
|
2121
2194
|
}
|