@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
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,38 +1355,48 @@ 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 = (
|
|
1317
|
-
storyAdd(
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1363
|
+
const name = baseNameWithoutExt(filename);
|
|
1364
|
+
storyAdd(
|
|
1365
|
+
story,
|
|
1366
|
+
{
|
|
1367
|
+
name,
|
|
1368
|
+
tags: [tag],
|
|
1369
|
+
text: `data:${mediaTypeFromFilename(filename)};base64,${source}`
|
|
1370
|
+
},
|
|
1371
|
+
diagnostics
|
|
1372
|
+
);
|
|
1322
1373
|
}
|
|
1323
1374
|
function loadFont(story, filename, diagnostics) {
|
|
1324
1375
|
const source = readBase64(filename);
|
|
1325
|
-
const name = (0,
|
|
1326
|
-
const family =
|
|
1376
|
+
const name = (0, import_node_path5.basename)(filename);
|
|
1377
|
+
const family = baseNameWithoutExt(filename);
|
|
1327
1378
|
const ext = normalizedFileExt(filename);
|
|
1328
1379
|
const mediaType = mediaTypeFromExt(ext);
|
|
1329
1380
|
const hint = fontFormatHint(ext);
|
|
1330
|
-
storyAdd(
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1381
|
+
storyAdd(
|
|
1382
|
+
story,
|
|
1383
|
+
{
|
|
1384
|
+
name,
|
|
1385
|
+
tags: ["stylesheet"],
|
|
1386
|
+
text: `@font-face {
|
|
1334
1387
|
font-family: "${family}";
|
|
1335
1388
|
src: url("data:${mediaType};base64,${source}") format("${hint}");
|
|
1336
1389
|
}`
|
|
1337
|
-
|
|
1390
|
+
},
|
|
1391
|
+
diagnostics
|
|
1392
|
+
);
|
|
1338
1393
|
}
|
|
1339
1394
|
|
|
1395
|
+
// src/version.ts
|
|
1396
|
+
var VERSION = "1.2.0";
|
|
1397
|
+
|
|
1340
1398
|
// src/output-twine2.ts
|
|
1341
1399
|
var CREATOR_NAME = "Twee-ts";
|
|
1342
|
-
var CREATOR_VERSION = "0.1.0";
|
|
1343
1400
|
function toTwine2Archive(story, startName, diagnostics) {
|
|
1344
1401
|
return getTwine2DataChunk(story, startName, diagnostics) + "\n";
|
|
1345
1402
|
}
|
|
@@ -1367,12 +1424,12 @@ function getTwine2DataChunk(story, startName, diagnostics) {
|
|
|
1367
1424
|
}
|
|
1368
1425
|
let styleContent = "";
|
|
1369
1426
|
if (stylesheets.length === 1) {
|
|
1370
|
-
styleContent = stylesheets[0]
|
|
1427
|
+
styleContent = stylesheets[0]?.text ?? "";
|
|
1371
1428
|
} else if (stylesheets.length > 1) {
|
|
1372
1429
|
let pidS = 1;
|
|
1373
1430
|
for (const p of stylesheets) {
|
|
1374
1431
|
if (pidS > 1 && !styleContent.endsWith("\n")) styleContent += "\n";
|
|
1375
|
-
styleContent += `/* twine-user-stylesheet #${pidS}: "${p.name}" */
|
|
1432
|
+
styleContent += `/* twine-user-stylesheet #${pidS}: "${commentSanitize(p.name)}" */
|
|
1376
1433
|
`;
|
|
1377
1434
|
styleContent += p.text;
|
|
1378
1435
|
pidS++;
|
|
@@ -1381,12 +1438,12 @@ function getTwine2DataChunk(story, startName, diagnostics) {
|
|
|
1381
1438
|
parts.push(`<style role="stylesheet" id="twine-user-stylesheet" type="text/twine-css">${styleContent}</style>`);
|
|
1382
1439
|
let scriptContent = "";
|
|
1383
1440
|
if (scripts.length === 1) {
|
|
1384
|
-
scriptContent = scripts[0]
|
|
1441
|
+
scriptContent = scripts[0]?.text ?? "";
|
|
1385
1442
|
} else if (scripts.length > 1) {
|
|
1386
1443
|
let pidS = 1;
|
|
1387
1444
|
for (const p of scripts) {
|
|
1388
1445
|
if (pidS > 1 && !scriptContent.endsWith("\n")) scriptContent += "\n";
|
|
1389
|
-
scriptContent += `/* twine-user-script #${pidS}: "${p.name}" */
|
|
1446
|
+
scriptContent += `/* twine-user-script #${pidS}: "${commentSanitize(p.name)}" */
|
|
1390
1447
|
`;
|
|
1391
1448
|
scriptContent += p.text;
|
|
1392
1449
|
pidS++;
|
|
@@ -1416,7 +1473,7 @@ function getTwine2DataChunk(story, startName, diagnostics) {
|
|
|
1416
1473
|
}
|
|
1417
1474
|
const options = opts.join(" ");
|
|
1418
1475
|
const zoom = story.twine2.zoom === Math.floor(story.twine2.zoom) ? story.twine2.zoom.toString() : story.twine2.zoom.toFixed(1);
|
|
1419
|
-
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>`;
|
|
1420
1477
|
return wrapper + parts.join("") + "</tw-storydata>";
|
|
1421
1478
|
}
|
|
1422
1479
|
function ensureIFID(story, diagnostics) {
|
|
@@ -1438,50 +1495,50 @@ function ensureIFID(story, diagnostics) {
|
|
|
1438
1495
|
|
|
1439
1496
|
// src/output-twine1.ts
|
|
1440
1497
|
var import_node_fs4 = require("fs");
|
|
1441
|
-
var
|
|
1498
|
+
var import_node_path6 = require("path");
|
|
1442
1499
|
var CREATOR_NAME2 = "twee-ts";
|
|
1443
|
-
var CREATOR_VERSION2 = "0.1.0";
|
|
1444
1500
|
function toTwine1Archive(story, _startName) {
|
|
1445
1501
|
const { data, count } = getTwine1PassageChunk(story);
|
|
1446
1502
|
return `<div id="storeArea" data-size="${count}" hidden>${data}</div>
|
|
1447
1503
|
`;
|
|
1448
1504
|
}
|
|
1449
1505
|
function toTwine1HTML(story, format, startName, _diagnostics) {
|
|
1450
|
-
const formatDir = (0,
|
|
1451
|
-
const parentDir = (0,
|
|
1506
|
+
const formatDir = (0, import_node_path6.dirname)(format.filename);
|
|
1507
|
+
const parentDir = (0, import_node_path6.dirname)(formatDir);
|
|
1452
1508
|
let template = readFormatSource(format);
|
|
1453
1509
|
const { data, count } = getTwine1PassageChunk(story);
|
|
1454
|
-
template = tryReplaceComponent(template, '"USER_LIB"', (0,
|
|
1455
|
-
template = tryReplaceComponent(template, '"ENGINE"', (0,
|
|
1456
|
-
template = tryReplaceComponent(template, '"SUGARCANE"', (0,
|
|
1457
|
-
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);
|
|
1458
1514
|
if (story.twine1.settings.get("jquery") === "on") {
|
|
1459
|
-
template = tryReplaceComponent(template, '"JQUERY"', (0,
|
|
1515
|
+
template = tryReplaceComponent(template, '"JQUERY"', (0, import_node_path6.join)(parentDir, "jquery.js"), true);
|
|
1460
1516
|
}
|
|
1461
1517
|
if (story.twine1.settings.get("modernizr") === "on") {
|
|
1462
|
-
template = tryReplaceComponent(template, '"MODERNIZR"', (0,
|
|
1518
|
+
template = tryReplaceComponent(template, '"MODERNIZR"', (0, import_node_path6.join)(parentDir, "modernizr.js"), true);
|
|
1463
1519
|
}
|
|
1464
1520
|
const displayStart = startName === "Start" ? "" : startName;
|
|
1465
|
-
template = template.replace('"VERSION"', `Compiled with ${CREATOR_NAME2}, ${
|
|
1521
|
+
template = template.replace('"VERSION"', `Compiled with ${CREATOR_NAME2}, ${VERSION}`);
|
|
1466
1522
|
template = template.replace('"TIME"', `Built on ${(/* @__PURE__ */ new Date()).toUTCString()}`);
|
|
1467
|
-
template = template.replace('"START_AT"', `"${displayStart}"`);
|
|
1523
|
+
template = template.replace('"START_AT"', `"${jsStringEscape(displayStart)}"`);
|
|
1468
1524
|
template = template.replace('"STORY_SIZE"', `"${count}"`);
|
|
1469
1525
|
if (template.includes('"STORY"')) {
|
|
1470
1526
|
template = template.replace('"STORY"', data);
|
|
1471
1527
|
} else {
|
|
1472
1528
|
let footer;
|
|
1473
1529
|
try {
|
|
1474
|
-
footer = (0, import_node_fs4.readFileSync)((0,
|
|
1530
|
+
footer = (0, import_node_fs4.readFileSync)((0, import_node_path6.join)(formatDir, "footer.html"), "utf-8");
|
|
1475
1531
|
} catch {
|
|
1476
1532
|
footer = "</div>\n</body>\n</html>\n";
|
|
1477
1533
|
}
|
|
1478
1534
|
template += data + footer;
|
|
1479
1535
|
}
|
|
1480
1536
|
if (story.ifid) {
|
|
1537
|
+
const safeIfid = htmlCommentSanitize(story.ifid);
|
|
1481
1538
|
if (template.includes('<div id="store-area"')) {
|
|
1482
|
-
template = template.replace('<div id="store-area"', `<!-- UUID://${
|
|
1539
|
+
template = template.replace('<div id="store-area"', `<!-- UUID://${safeIfid}// --><div id="store-area"`);
|
|
1483
1540
|
} else {
|
|
1484
|
-
template = template.replace('<div id="storeArea"', `<!-- UUID://${
|
|
1541
|
+
template = template.replace('<div id="storeArea"', `<!-- UUID://${safeIfid}// --><div id="storeArea"`);
|
|
1485
1542
|
}
|
|
1486
1543
|
}
|
|
1487
1544
|
return template;
|
|
@@ -1496,13 +1553,18 @@ function getTwine1PassageChunk(story) {
|
|
|
1496
1553
|
}
|
|
1497
1554
|
return { data, count };
|
|
1498
1555
|
}
|
|
1499
|
-
function tryReplaceComponent(template, placeholder, componentPath,
|
|
1556
|
+
function tryReplaceComponent(template, placeholder, componentPath, required) {
|
|
1500
1557
|
if (!template.includes(placeholder)) return template;
|
|
1501
1558
|
try {
|
|
1502
1559
|
let content = (0, import_node_fs4.readFileSync)(componentPath, "utf-8");
|
|
1503
1560
|
if (content.charCodeAt(0) === 65279) content = content.slice(1);
|
|
1504
1561
|
return template.replace(placeholder, content);
|
|
1505
|
-
} 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
|
+
}
|
|
1506
1568
|
return template;
|
|
1507
1569
|
}
|
|
1508
1570
|
}
|
|
@@ -1517,16 +1579,6 @@ function toTwee(story, outMode) {
|
|
|
1517
1579
|
}
|
|
1518
1580
|
|
|
1519
1581
|
// src/modules.ts
|
|
1520
|
-
var import_node_fs5 = require("fs");
|
|
1521
|
-
var import_node_path6 = require("path");
|
|
1522
|
-
function readUTF83(filename) {
|
|
1523
|
-
let content = (0, import_node_fs5.readFileSync)(filename, "utf-8");
|
|
1524
|
-
if (content.charCodeAt(0) === 65279) content = content.slice(1);
|
|
1525
|
-
return content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
1526
|
-
}
|
|
1527
|
-
function readBase642(filename) {
|
|
1528
|
-
return (0, import_node_fs5.readFileSync)(filename).toString("base64");
|
|
1529
|
-
}
|
|
1530
1582
|
function loadModules(filenames) {
|
|
1531
1583
|
const processed = /* @__PURE__ */ new Set();
|
|
1532
1584
|
const headTags = [];
|
|
@@ -1556,16 +1608,16 @@ function loadModules(filenames) {
|
|
|
1556
1608
|
return headTags.join("\n");
|
|
1557
1609
|
}
|
|
1558
1610
|
function loadModuleTagged(tag, filename) {
|
|
1559
|
-
const source =
|
|
1611
|
+
const source = readUTF8(filename).trim();
|
|
1560
1612
|
if (source.length === 0) return null;
|
|
1561
|
-
const family = (
|
|
1613
|
+
const family = baseNameWithoutExt(filename);
|
|
1562
1614
|
const idSlug = `${tag}-module-${slugify(family)}`;
|
|
1563
1615
|
const mimeType = tag === "script" ? "text/javascript" : "text/css";
|
|
1564
1616
|
return `<${tag} id="${idSlug}" type="${mimeType}">${source}</${tag}>`;
|
|
1565
1617
|
}
|
|
1566
1618
|
function loadModuleFont(filename) {
|
|
1567
|
-
const source =
|
|
1568
|
-
const family = (
|
|
1619
|
+
const source = readBase64(filename);
|
|
1620
|
+
const family = baseNameWithoutExt(filename);
|
|
1569
1621
|
const idSlug = `style-module-${slugify(family)}`;
|
|
1570
1622
|
const ext = normalizedFileExt(filename);
|
|
1571
1623
|
const mediaType = mediaTypeFromExt(ext);
|
|
@@ -1575,7 +1627,7 @@ function loadModuleFont(filename) {
|
|
|
1575
1627
|
src: url("data:${mediaType};base64,${source}") format("${hint}");
|
|
1576
1628
|
}</style>`;
|
|
1577
1629
|
}
|
|
1578
|
-
function modifyHead(html, modulePaths, headFile) {
|
|
1630
|
+
function modifyHead(html, modulePaths, headFile, diagnostics) {
|
|
1579
1631
|
const parts = [];
|
|
1580
1632
|
if (modulePaths.length > 0) {
|
|
1581
1633
|
const modules = loadModules(modulePaths).trim();
|
|
@@ -1583,9 +1635,13 @@ function modifyHead(html, modulePaths, headFile) {
|
|
|
1583
1635
|
}
|
|
1584
1636
|
if (headFile) {
|
|
1585
1637
|
try {
|
|
1586
|
-
const source =
|
|
1638
|
+
const source = readUTF8(headFile).trim();
|
|
1587
1639
|
if (source.length > 0) parts.push(source);
|
|
1588
|
-
} 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
|
+
});
|
|
1589
1645
|
}
|
|
1590
1646
|
}
|
|
1591
1647
|
if (parts.length > 0) {
|
|
@@ -1596,7 +1652,7 @@ function modifyHead(html, modulePaths, headFile) {
|
|
|
1596
1652
|
}
|
|
1597
1653
|
|
|
1598
1654
|
// src/remote-formats.ts
|
|
1599
|
-
var
|
|
1655
|
+
var import_node_fs5 = require("fs");
|
|
1600
1656
|
var import_node_path7 = require("path");
|
|
1601
1657
|
var import_node_os2 = require("os");
|
|
1602
1658
|
var DEFAULT_SFA_INDICES = [
|
|
@@ -1612,6 +1668,24 @@ var indexCache = /* @__PURE__ */ new Map();
|
|
|
1612
1668
|
function clearIndexCache() {
|
|
1613
1669
|
indexCache.clear();
|
|
1614
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
|
+
}
|
|
1615
1689
|
async function fetchIndex(url) {
|
|
1616
1690
|
const cached = indexCache.get(url);
|
|
1617
1691
|
if (cached) return cached;
|
|
@@ -1619,37 +1693,42 @@ async function fetchIndex(url) {
|
|
|
1619
1693
|
if (!res.ok) {
|
|
1620
1694
|
throw new Error(`Failed to fetch format index from ${url}: ${res.status} ${res.statusText}`);
|
|
1621
1695
|
}
|
|
1622
|
-
const data = await res.json();
|
|
1696
|
+
const data = validateSFAIndex(await res.json());
|
|
1623
1697
|
indexCache.set(url, data);
|
|
1624
1698
|
return data;
|
|
1625
1699
|
}
|
|
1626
1700
|
function findEntry(index, name, version) {
|
|
1627
1701
|
const wanted = parseSemver(version);
|
|
1628
|
-
const
|
|
1629
|
-
|
|
1702
|
+
const searchArrays = [
|
|
1703
|
+
{ entries: index.twine2 ?? [], formatType: "twine2" },
|
|
1704
|
+
{ entries: index.twine1 ?? [], formatType: "twine1" }
|
|
1705
|
+
];
|
|
1706
|
+
let bestResult;
|
|
1630
1707
|
let bestVersion = null;
|
|
1631
|
-
for (const
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
if (
|
|
1638
|
-
bestVersion
|
|
1639
|
-
|
|
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
|
+
}
|
|
1640
1719
|
}
|
|
1641
1720
|
}
|
|
1642
1721
|
}
|
|
1643
|
-
return
|
|
1722
|
+
return bestResult;
|
|
1644
1723
|
}
|
|
1645
1724
|
async function verifySHA256(content, expectedHex) {
|
|
1646
1725
|
const digest = await crypto.subtle.digest("SHA-256", content);
|
|
1647
1726
|
const hex = Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1648
1727
|
return hex === expectedHex.toLowerCase();
|
|
1649
1728
|
}
|
|
1650
|
-
function getDownloadUrl(indexUrl, entry) {
|
|
1729
|
+
function getDownloadUrl(indexUrl, entry, formatType) {
|
|
1651
1730
|
const base = indexUrl.replace(/\/index\.json$/, "");
|
|
1652
|
-
return `${base}
|
|
1731
|
+
return `${base}/${formatType}/${entry.name}/${entry.version}/format.js`;
|
|
1653
1732
|
}
|
|
1654
1733
|
async function fetchAndCacheFormat(entry, downloadUrl) {
|
|
1655
1734
|
const res = await fetch(downloadUrl);
|
|
@@ -1660,22 +1739,23 @@ async function fetchAndCacheFormat(entry, downloadUrl) {
|
|
|
1660
1739
|
const checksumKey = Object.keys(entry.checksums ?? {}).find((k) => k.endsWith("format.js"));
|
|
1661
1740
|
if (checksumKey) {
|
|
1662
1741
|
const expected = entry.checksums[checksumKey];
|
|
1742
|
+
if (!expected) throw new Error(`Missing checksum value for key "${checksumKey}"`);
|
|
1663
1743
|
const encoder = new TextEncoder();
|
|
1664
1744
|
const valid = await verifySHA256(encoder.encode(text), expected);
|
|
1665
1745
|
if (!valid) {
|
|
1666
1746
|
throw new Error(`Checksum verification failed for ${entry.name} ${entry.version}`);
|
|
1667
1747
|
}
|
|
1668
1748
|
}
|
|
1669
|
-
const id =
|
|
1749
|
+
const id = makeFormatId(entry.name, entry.version);
|
|
1670
1750
|
const data = parseFormatJSON(text, id);
|
|
1671
1751
|
if (!data) {
|
|
1672
1752
|
throw new Error(`Failed to parse format JSON from ${downloadUrl}`);
|
|
1673
1753
|
}
|
|
1674
1754
|
const cacheDir = getCacheDir();
|
|
1675
1755
|
const formatDir = (0, import_node_path7.join)(cacheDir, entry.name, entry.version);
|
|
1676
|
-
(0,
|
|
1756
|
+
(0, import_node_fs5.mkdirSync)(formatDir, { recursive: true });
|
|
1677
1757
|
const formatPath = (0, import_node_path7.join)(formatDir, "format.js");
|
|
1678
|
-
(0,
|
|
1758
|
+
(0, import_node_fs5.writeFileSync)(formatPath, text, "utf-8");
|
|
1679
1759
|
return {
|
|
1680
1760
|
id,
|
|
1681
1761
|
filename: formatPath,
|
|
@@ -1696,12 +1776,12 @@ async function fetchDirectFormat(url) {
|
|
|
1696
1776
|
if (!data) {
|
|
1697
1777
|
throw new Error(`Failed to parse format JSON from ${url}`);
|
|
1698
1778
|
}
|
|
1699
|
-
const id =
|
|
1779
|
+
const id = makeFormatId(data.name, data.version);
|
|
1700
1780
|
const cacheDir = getCacheDir();
|
|
1701
1781
|
const formatDir = (0, import_node_path7.join)(cacheDir, data.name, data.version);
|
|
1702
|
-
(0,
|
|
1782
|
+
(0, import_node_fs5.mkdirSync)(formatDir, { recursive: true });
|
|
1703
1783
|
const formatPath = (0, import_node_path7.join)(formatDir, "format.js");
|
|
1704
|
-
(0,
|
|
1784
|
+
(0, import_node_fs5.writeFileSync)(formatPath, text, "utf-8");
|
|
1705
1785
|
return {
|
|
1706
1786
|
id,
|
|
1707
1787
|
filename: formatPath,
|
|
@@ -1712,6 +1792,7 @@ async function fetchDirectFormat(url) {
|
|
|
1712
1792
|
};
|
|
1713
1793
|
}
|
|
1714
1794
|
async function resolveRemoteFormat(name, version, indices, urls) {
|
|
1795
|
+
let lastError;
|
|
1715
1796
|
if (urls) {
|
|
1716
1797
|
for (const url of urls) {
|
|
1717
1798
|
try {
|
|
@@ -1723,7 +1804,8 @@ async function resolveRemoteFormat(name, version, indices, urls) {
|
|
|
1723
1804
|
return info;
|
|
1724
1805
|
}
|
|
1725
1806
|
}
|
|
1726
|
-
} catch {
|
|
1807
|
+
} catch (e) {
|
|
1808
|
+
lastError = e instanceof Error ? e : new Error(String(e));
|
|
1727
1809
|
}
|
|
1728
1810
|
}
|
|
1729
1811
|
}
|
|
@@ -1731,24 +1813,26 @@ async function resolveRemoteFormat(name, version, indices, urls) {
|
|
|
1731
1813
|
for (const indexUrl of allIndices) {
|
|
1732
1814
|
try {
|
|
1733
1815
|
const index = await fetchIndex(indexUrl);
|
|
1734
|
-
const
|
|
1735
|
-
if (
|
|
1736
|
-
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);
|
|
1737
1819
|
if (cached) return cached;
|
|
1738
|
-
const downloadUrl = getDownloadUrl(indexUrl, entry);
|
|
1739
|
-
return await fetchAndCacheFormat(entry, downloadUrl);
|
|
1820
|
+
const downloadUrl = getDownloadUrl(indexUrl, result.entry, result.formatType);
|
|
1821
|
+
return await fetchAndCacheFormat(result.entry, downloadUrl);
|
|
1740
1822
|
}
|
|
1741
|
-
} catch {
|
|
1823
|
+
} catch (e) {
|
|
1824
|
+
lastError = e instanceof Error ? e : new Error(String(e));
|
|
1742
1825
|
}
|
|
1743
1826
|
}
|
|
1827
|
+
if (lastError) throw lastError;
|
|
1744
1828
|
return void 0;
|
|
1745
1829
|
}
|
|
1746
1830
|
function getCachedFormat(name, version) {
|
|
1747
1831
|
const formatPath = (0, import_node_path7.join)(getCacheDir(), name, version, "format.js");
|
|
1748
1832
|
try {
|
|
1749
|
-
if (!(0,
|
|
1750
|
-
const source = (0,
|
|
1751
|
-
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);
|
|
1752
1836
|
const data = parseFormatJSON(source, id);
|
|
1753
1837
|
if (!data) return void 0;
|
|
1754
1838
|
return {
|
|
@@ -1767,26 +1851,26 @@ function discoverCachedFormats() {
|
|
|
1767
1851
|
const formats = /* @__PURE__ */ new Map();
|
|
1768
1852
|
const cacheDir = getCacheDir();
|
|
1769
1853
|
try {
|
|
1770
|
-
if (!(0,
|
|
1854
|
+
if (!(0, import_node_fs5.existsSync)(cacheDir)) return formats;
|
|
1771
1855
|
} catch {
|
|
1772
1856
|
return formats;
|
|
1773
1857
|
}
|
|
1774
1858
|
let names;
|
|
1775
1859
|
try {
|
|
1776
|
-
names = (0,
|
|
1860
|
+
names = (0, import_node_fs5.readdirSync)(cacheDir);
|
|
1777
1861
|
} catch {
|
|
1778
1862
|
return formats;
|
|
1779
1863
|
}
|
|
1780
1864
|
for (const name of names) {
|
|
1781
1865
|
const nameDir = (0, import_node_path7.join)(cacheDir, name);
|
|
1782
1866
|
try {
|
|
1783
|
-
if (!(0,
|
|
1867
|
+
if (!(0, import_node_fs5.statSync)(nameDir).isDirectory()) continue;
|
|
1784
1868
|
} catch {
|
|
1785
1869
|
continue;
|
|
1786
1870
|
}
|
|
1787
1871
|
let versions;
|
|
1788
1872
|
try {
|
|
1789
|
-
versions = (0,
|
|
1873
|
+
versions = (0, import_node_fs5.readdirSync)(nameDir);
|
|
1790
1874
|
} catch {
|
|
1791
1875
|
continue;
|
|
1792
1876
|
}
|
|
@@ -1815,7 +1899,7 @@ async function compile(options) {
|
|
|
1815
1899
|
}
|
|
1816
1900
|
async function compileToFile(options) {
|
|
1817
1901
|
const result = await compile(options);
|
|
1818
|
-
(0,
|
|
1902
|
+
(0, import_node_fs6.writeFileSync)(options.outFile, result.output, "utf-8");
|
|
1819
1903
|
return result;
|
|
1820
1904
|
}
|
|
1821
1905
|
async function watch(options) {
|
|
@@ -1825,7 +1909,7 @@ async function watch(options) {
|
|
|
1825
1909
|
const allPaths = [...filePaths, ...modulePaths];
|
|
1826
1910
|
const handle = watchFilesystem(allPaths, options.outFile, () => {
|
|
1827
1911
|
buildOutput(options).then((result) => {
|
|
1828
|
-
(0,
|
|
1912
|
+
(0, import_node_fs6.writeFileSync)(options.outFile, result.output, "utf-8");
|
|
1829
1913
|
options.onBuild?.(result);
|
|
1830
1914
|
}).catch((e) => {
|
|
1831
1915
|
options.onError?.(e instanceof Error ? e : new Error(String(e)));
|
|
@@ -1857,18 +1941,12 @@ async function buildOutput(options) {
|
|
|
1857
1941
|
loadSources(story, sourceFilenames, { trim, twee2Compat }, diagnostics, processedFiles);
|
|
1858
1942
|
loadInlineSources(story, inlineSources, { trim, twee2Compat }, diagnostics);
|
|
1859
1943
|
if (options.tagAliases) {
|
|
1860
|
-
applyTagAliases(story.passages, options.tagAliases);
|
|
1861
|
-
}
|
|
1862
|
-
const fatalErrors = diagnostics.filter((d) => d.level === "error");
|
|
1863
|
-
if (fatalErrors.length > 0 && outputMode === "html") {
|
|
1944
|
+
story.passages = applyTagAliases(story.passages, options.tagAliases);
|
|
1864
1945
|
}
|
|
1865
1946
|
let format;
|
|
1866
1947
|
let formatId = options.formatId ?? "";
|
|
1867
1948
|
if (outputMode === "html") {
|
|
1868
|
-
const formatSearchDirs = getFormatSearchDirs(
|
|
1869
|
-
options.formatPaths,
|
|
1870
|
-
options.useTweegoPath ?? true
|
|
1871
|
-
);
|
|
1949
|
+
const formatSearchDirs = getFormatSearchDirs(options.formatPaths, options.useTweegoPath ?? true);
|
|
1872
1950
|
const formats = discoverFormats(formatSearchDirs);
|
|
1873
1951
|
if (!formatId && story.twine2.format) {
|
|
1874
1952
|
formatId = getFormatIdByNameAndVersion(formats, story.twine2.format, story.twine2.formatVersion) ?? "";
|
|
@@ -1881,13 +1959,12 @@ async function buildOutput(options) {
|
|
|
1881
1959
|
const remoteName = story.twine2.format || formatId;
|
|
1882
1960
|
const remoteVersion = story.twine2.formatVersion || "";
|
|
1883
1961
|
try {
|
|
1884
|
-
format = await resolveRemoteFormat(
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
);
|
|
1890
|
-
} catch {
|
|
1962
|
+
format = await resolveRemoteFormat(remoteName, remoteVersion, options.formatIndices, options.formatUrls);
|
|
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),
|
|
@@ -1958,24 +2031,28 @@ async function buildOutput(options) {
|
|
|
1958
2031
|
return { output, story, format, diagnostics, stats };
|
|
1959
2032
|
}
|
|
1960
2033
|
function storyToJSON(story) {
|
|
1961
|
-
return JSON.stringify(
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
2034
|
+
return JSON.stringify(
|
|
2035
|
+
{
|
|
2036
|
+
name: story.name,
|
|
2037
|
+
ifid: story.ifid,
|
|
2038
|
+
passages: story.passages.map((p) => ({
|
|
2039
|
+
name: p.name,
|
|
2040
|
+
tags: p.tags,
|
|
2041
|
+
text: p.text,
|
|
2042
|
+
metadata: p.metadata ?? null
|
|
2043
|
+
})),
|
|
2044
|
+
twine2: {
|
|
2045
|
+
format: story.twine2.format,
|
|
2046
|
+
formatVersion: story.twine2.formatVersion,
|
|
2047
|
+
start: story.twine2.start,
|
|
2048
|
+
tagColors: Object.fromEntries(story.twine2.tagColors),
|
|
2049
|
+
options: Object.fromEntries(story.twine2.options),
|
|
2050
|
+
zoom: story.twine2.zoom
|
|
2051
|
+
}
|
|
2052
|
+
},
|
|
2053
|
+
null,
|
|
2054
|
+
2
|
|
2055
|
+
);
|
|
1979
2056
|
}
|
|
1980
2057
|
|
|
1981
2058
|
// src/inspect.ts
|
|
@@ -2081,17 +2158,17 @@ function storyInspect(story) {
|
|
|
2081
2158
|
}
|
|
2082
2159
|
|
|
2083
2160
|
// src/config.ts
|
|
2084
|
-
var
|
|
2161
|
+
var import_node_fs7 = require("fs");
|
|
2085
2162
|
var import_node_path8 = require("path");
|
|
2086
2163
|
var CONFIG_FILENAME = "twee-ts.config.json";
|
|
2087
2164
|
var VALID_OUTPUT_MODES = ["html", "twee3", "twee1", "twine2-archive", "twine1-archive", "json"];
|
|
2088
2165
|
function loadConfig(dir) {
|
|
2089
2166
|
const base = dir ?? process.cwd();
|
|
2090
2167
|
const configPath = (0, import_node_path8.join)(base, CONFIG_FILENAME);
|
|
2091
|
-
if (!(0,
|
|
2168
|
+
if (!(0, import_node_fs7.existsSync)(configPath)) return null;
|
|
2092
2169
|
let raw;
|
|
2093
2170
|
try {
|
|
2094
|
-
raw = (0,
|
|
2171
|
+
raw = (0, import_node_fs7.readFileSync)(configPath, "utf-8");
|
|
2095
2172
|
} catch {
|
|
2096
2173
|
return null;
|
|
2097
2174
|
}
|
|
@@ -2111,7 +2188,7 @@ function loadConfig(dir) {
|
|
|
2111
2188
|
function loadConfigFile(filePath) {
|
|
2112
2189
|
let raw;
|
|
2113
2190
|
try {
|
|
2114
|
-
raw = (0,
|
|
2191
|
+
raw = (0, import_node_fs7.readFileSync)(filePath, "utf-8");
|
|
2115
2192
|
} catch (e) {
|
|
2116
2193
|
throw new Error(`Cannot read config file ${filePath}: ${e instanceof Error ? e.message : String(e)}`);
|
|
2117
2194
|
}
|