@rohal12/twee-ts 1.1.2 → 1.3.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/README.md +9 -0
- package/dist/bin/twee-ts.js +258 -166
- package/dist/bin/twee-ts.js.map +1 -1
- package/dist/{chunk-QTXRKA4T.js → chunk-OGP2NXLJ.js} +255 -160
- package/dist/chunk-OGP2NXLJ.js.map +1 -0
- package/dist/index.cjs +288 -195
- 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 +257 -164
- 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++;
|
|
@@ -1423,73 +1472,74 @@ function getTwine2DataChunk(story, startName, diagnostics) {
|
|
|
1423
1472
|
if (val) opts.push(opt);
|
|
1424
1473
|
}
|
|
1425
1474
|
const options = opts.join(" ");
|
|
1426
|
-
const zoom =
|
|
1427
|
-
const wrapper = `<!-- UUID://${story.ifid}// --><tw-storydata name="${attrEscape(story.name)}" startnode="${startID}" creator="${attrEscape(CREATOR_NAME)}" creator-version="${attrEscape(
|
|
1475
|
+
const zoom = String(story.twine2.zoom);
|
|
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) {
|
|
1431
1480
|
if (story.ifid !== "") return;
|
|
1432
1481
|
if (story.legacyIFID !== "") {
|
|
1482
|
+
story.ifid = story.legacyIFID;
|
|
1433
1483
|
diagnostics.push({
|
|
1434
1484
|
level: "warning",
|
|
1435
1485
|
message: 'Story IFID not found; reusing "ifid" entry from the "StorySettings" special passage.'
|
|
1436
1486
|
});
|
|
1437
|
-
story.ifid = story.legacyIFID;
|
|
1438
1487
|
} else {
|
|
1439
|
-
|
|
1488
|
+
const ifid = generateIFID();
|
|
1489
|
+
story.ifid = ifid;
|
|
1440
1490
|
diagnostics.push({
|
|
1441
|
-
level: "
|
|
1442
|
-
message: `Story IFID not found
|
|
1491
|
+
level: "error",
|
|
1492
|
+
message: `Story IFID not found. Add a StoryData passage with: {"ifid":"${ifid}"}`
|
|
1443
1493
|
});
|
|
1444
1494
|
}
|
|
1445
1495
|
}
|
|
1446
1496
|
|
|
1447
1497
|
// src/output-twine1.ts
|
|
1448
1498
|
var import_node_fs4 = require("fs");
|
|
1449
|
-
var
|
|
1499
|
+
var import_node_path6 = require("path");
|
|
1450
1500
|
var CREATOR_NAME2 = "twee-ts";
|
|
1451
|
-
var CREATOR_VERSION2 = "0.1.0";
|
|
1452
1501
|
function toTwine1Archive(story, _startName) {
|
|
1453
1502
|
const { data, count } = getTwine1PassageChunk(story);
|
|
1454
1503
|
return `<div id="storeArea" data-size="${count}" hidden>${data}</div>
|
|
1455
1504
|
`;
|
|
1456
1505
|
}
|
|
1457
1506
|
function toTwine1HTML(story, format, startName, _diagnostics) {
|
|
1458
|
-
const formatDir = (0,
|
|
1459
|
-
const parentDir = (0,
|
|
1507
|
+
const formatDir = (0, import_node_path6.dirname)(format.filename);
|
|
1508
|
+
const parentDir = (0, import_node_path6.dirname)(formatDir);
|
|
1460
1509
|
let template = readFormatSource(format);
|
|
1461
1510
|
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,
|
|
1511
|
+
template = tryReplaceComponent(template, '"USER_LIB"', (0, import_node_path6.join)(formatDir, "userlib.js"), false);
|
|
1512
|
+
template = tryReplaceComponent(template, '"ENGINE"', (0, import_node_path6.join)(parentDir, "engine.js"), true);
|
|
1513
|
+
template = tryReplaceComponent(template, '"SUGARCANE"', (0, import_node_path6.join)(formatDir, "code.js"), true);
|
|
1514
|
+
template = tryReplaceComponent(template, '"JONAH"', (0, import_node_path6.join)(formatDir, "code.js"), true);
|
|
1466
1515
|
if (story.twine1.settings.get("jquery") === "on") {
|
|
1467
|
-
template = tryReplaceComponent(template, '"JQUERY"', (0,
|
|
1516
|
+
template = tryReplaceComponent(template, '"JQUERY"', (0, import_node_path6.join)(parentDir, "jquery.js"), true);
|
|
1468
1517
|
}
|
|
1469
1518
|
if (story.twine1.settings.get("modernizr") === "on") {
|
|
1470
|
-
template = tryReplaceComponent(template, '"MODERNIZR"', (0,
|
|
1519
|
+
template = tryReplaceComponent(template, '"MODERNIZR"', (0, import_node_path6.join)(parentDir, "modernizr.js"), true);
|
|
1471
1520
|
}
|
|
1472
1521
|
const displayStart = startName === "Start" ? "" : startName;
|
|
1473
|
-
template = template.replace('"VERSION"', `Compiled with ${CREATOR_NAME2}, ${
|
|
1522
|
+
template = template.replace('"VERSION"', `Compiled with ${CREATOR_NAME2}, ${VERSION}`);
|
|
1474
1523
|
template = template.replace('"TIME"', `Built on ${(/* @__PURE__ */ new Date()).toUTCString()}`);
|
|
1475
|
-
template = template.replace('"START_AT"', `"${displayStart}"`);
|
|
1524
|
+
template = template.replace('"START_AT"', `"${jsStringEscape(displayStart)}"`);
|
|
1476
1525
|
template = template.replace('"STORY_SIZE"', `"${count}"`);
|
|
1477
1526
|
if (template.includes('"STORY"')) {
|
|
1478
1527
|
template = template.replace('"STORY"', data);
|
|
1479
1528
|
} else {
|
|
1480
1529
|
let footer;
|
|
1481
1530
|
try {
|
|
1482
|
-
footer = (0, import_node_fs4.readFileSync)((0,
|
|
1531
|
+
footer = (0, import_node_fs4.readFileSync)((0, import_node_path6.join)(formatDir, "footer.html"), "utf-8");
|
|
1483
1532
|
} catch {
|
|
1484
1533
|
footer = "</div>\n</body>\n</html>\n";
|
|
1485
1534
|
}
|
|
1486
1535
|
template += data + footer;
|
|
1487
1536
|
}
|
|
1488
1537
|
if (story.ifid) {
|
|
1538
|
+
const safeIfid = htmlCommentSanitize(story.ifid);
|
|
1489
1539
|
if (template.includes('<div id="store-area"')) {
|
|
1490
|
-
template = template.replace('<div id="store-area"', `<!-- UUID://${
|
|
1540
|
+
template = template.replace('<div id="store-area"', `<!-- UUID://${safeIfid}// --><div id="store-area"`);
|
|
1491
1541
|
} else {
|
|
1492
|
-
template = template.replace('<div id="storeArea"', `<!-- UUID://${
|
|
1542
|
+
template = template.replace('<div id="storeArea"', `<!-- UUID://${safeIfid}// --><div id="storeArea"`);
|
|
1493
1543
|
}
|
|
1494
1544
|
}
|
|
1495
1545
|
return template;
|
|
@@ -1504,13 +1554,18 @@ function getTwine1PassageChunk(story) {
|
|
|
1504
1554
|
}
|
|
1505
1555
|
return { data, count };
|
|
1506
1556
|
}
|
|
1507
|
-
function tryReplaceComponent(template, placeholder, componentPath,
|
|
1557
|
+
function tryReplaceComponent(template, placeholder, componentPath, required) {
|
|
1508
1558
|
if (!template.includes(placeholder)) return template;
|
|
1509
1559
|
try {
|
|
1510
1560
|
let content = (0, import_node_fs4.readFileSync)(componentPath, "utf-8");
|
|
1511
1561
|
if (content.charCodeAt(0) === 65279) content = content.slice(1);
|
|
1512
1562
|
return template.replace(placeholder, content);
|
|
1513
|
-
} catch {
|
|
1563
|
+
} catch (e) {
|
|
1564
|
+
if (required) {
|
|
1565
|
+
throw new Error(
|
|
1566
|
+
`Required format component not found: ${componentPath}: ${e instanceof Error ? e.message : String(e)}`
|
|
1567
|
+
);
|
|
1568
|
+
}
|
|
1514
1569
|
return template;
|
|
1515
1570
|
}
|
|
1516
1571
|
}
|
|
@@ -1525,16 +1580,6 @@ function toTwee(story, outMode) {
|
|
|
1525
1580
|
}
|
|
1526
1581
|
|
|
1527
1582
|
// 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
1583
|
function loadModules(filenames) {
|
|
1539
1584
|
const processed = /* @__PURE__ */ new Set();
|
|
1540
1585
|
const headTags = [];
|
|
@@ -1564,16 +1609,16 @@ function loadModules(filenames) {
|
|
|
1564
1609
|
return headTags.join("\n");
|
|
1565
1610
|
}
|
|
1566
1611
|
function loadModuleTagged(tag, filename) {
|
|
1567
|
-
const source =
|
|
1612
|
+
const source = readUTF8(filename).trim();
|
|
1568
1613
|
if (source.length === 0) return null;
|
|
1569
|
-
const family = (
|
|
1614
|
+
const family = baseNameWithoutExt(filename);
|
|
1570
1615
|
const idSlug = `${tag}-module-${slugify(family)}`;
|
|
1571
1616
|
const mimeType = tag === "script" ? "text/javascript" : "text/css";
|
|
1572
1617
|
return `<${tag} id="${idSlug}" type="${mimeType}">${source}</${tag}>`;
|
|
1573
1618
|
}
|
|
1574
1619
|
function loadModuleFont(filename) {
|
|
1575
|
-
const source =
|
|
1576
|
-
const family = (
|
|
1620
|
+
const source = readBase64(filename);
|
|
1621
|
+
const family = baseNameWithoutExt(filename);
|
|
1577
1622
|
const idSlug = `style-module-${slugify(family)}`;
|
|
1578
1623
|
const ext = normalizedFileExt(filename);
|
|
1579
1624
|
const mediaType = mediaTypeFromExt(ext);
|
|
@@ -1583,7 +1628,7 @@ function loadModuleFont(filename) {
|
|
|
1583
1628
|
src: url("data:${mediaType};base64,${source}") format("${hint}");
|
|
1584
1629
|
}</style>`;
|
|
1585
1630
|
}
|
|
1586
|
-
function modifyHead(html, modulePaths, headFile) {
|
|
1631
|
+
function modifyHead(html, modulePaths, headFile, diagnostics) {
|
|
1587
1632
|
const parts = [];
|
|
1588
1633
|
if (modulePaths.length > 0) {
|
|
1589
1634
|
const modules = loadModules(modulePaths).trim();
|
|
@@ -1591,9 +1636,13 @@ function modifyHead(html, modulePaths, headFile) {
|
|
|
1591
1636
|
}
|
|
1592
1637
|
if (headFile) {
|
|
1593
1638
|
try {
|
|
1594
|
-
const source =
|
|
1639
|
+
const source = readUTF8(headFile).trim();
|
|
1595
1640
|
if (source.length > 0) parts.push(source);
|
|
1596
|
-
} catch {
|
|
1641
|
+
} catch (e) {
|
|
1642
|
+
diagnostics?.push({
|
|
1643
|
+
level: "warning",
|
|
1644
|
+
message: `Failed to read head file "${headFile}": ${e instanceof Error ? e.message : String(e)}`
|
|
1645
|
+
});
|
|
1597
1646
|
}
|
|
1598
1647
|
}
|
|
1599
1648
|
if (parts.length > 0) {
|
|
@@ -1604,7 +1653,7 @@ function modifyHead(html, modulePaths, headFile) {
|
|
|
1604
1653
|
}
|
|
1605
1654
|
|
|
1606
1655
|
// src/remote-formats.ts
|
|
1607
|
-
var
|
|
1656
|
+
var import_node_fs5 = require("fs");
|
|
1608
1657
|
var import_node_path7 = require("path");
|
|
1609
1658
|
var import_node_os2 = require("os");
|
|
1610
1659
|
var DEFAULT_SFA_INDICES = [
|
|
@@ -1620,6 +1669,24 @@ var indexCache = /* @__PURE__ */ new Map();
|
|
|
1620
1669
|
function clearIndexCache() {
|
|
1621
1670
|
indexCache.clear();
|
|
1622
1671
|
}
|
|
1672
|
+
function isValidEntry(val) {
|
|
1673
|
+
if (typeof val !== "object" || val === null || Array.isArray(val)) return false;
|
|
1674
|
+
const obj = val;
|
|
1675
|
+
return typeof obj.name === "string" && typeof obj.version === "string" && typeof obj.checksums === "object" && obj.checksums !== null && !Array.isArray(obj.checksums);
|
|
1676
|
+
}
|
|
1677
|
+
function validateSFAIndex(json) {
|
|
1678
|
+
if (typeof json !== "object" || json === null) {
|
|
1679
|
+
throw new Error("SFA index is not an object");
|
|
1680
|
+
}
|
|
1681
|
+
const obj = json;
|
|
1682
|
+
const twine1 = Array.isArray(obj["twine1"]) ? obj["twine1"].filter(isValidEntry) : [];
|
|
1683
|
+
const twine2 = Array.isArray(obj["twine2"]) ? obj["twine2"].filter(isValidEntry) : [];
|
|
1684
|
+
return { twine1, twine2 };
|
|
1685
|
+
}
|
|
1686
|
+
function makeFormatId(name, version) {
|
|
1687
|
+
const major = parseSemver(version)?.[0] ?? "0";
|
|
1688
|
+
return `${name.toLowerCase().replace(/\s+/g, "-")}-${major}`;
|
|
1689
|
+
}
|
|
1623
1690
|
async function fetchIndex(url) {
|
|
1624
1691
|
const cached = indexCache.get(url);
|
|
1625
1692
|
if (cached) return cached;
|
|
@@ -1627,37 +1694,42 @@ async function fetchIndex(url) {
|
|
|
1627
1694
|
if (!res.ok) {
|
|
1628
1695
|
throw new Error(`Failed to fetch format index from ${url}: ${res.status} ${res.statusText}`);
|
|
1629
1696
|
}
|
|
1630
|
-
const data = await res.json();
|
|
1697
|
+
const data = validateSFAIndex(await res.json());
|
|
1631
1698
|
indexCache.set(url, data);
|
|
1632
1699
|
return data;
|
|
1633
1700
|
}
|
|
1634
1701
|
function findEntry(index, name, version) {
|
|
1635
1702
|
const wanted = parseSemver(version);
|
|
1636
|
-
const
|
|
1637
|
-
|
|
1703
|
+
const searchArrays = [
|
|
1704
|
+
{ entries: index.twine2 ?? [], formatType: "twine2" },
|
|
1705
|
+
{ entries: index.twine1 ?? [], formatType: "twine1" }
|
|
1706
|
+
];
|
|
1707
|
+
let bestResult;
|
|
1638
1708
|
let bestVersion = null;
|
|
1639
|
-
for (const
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
if (
|
|
1646
|
-
bestVersion
|
|
1647
|
-
|
|
1709
|
+
for (const { entries, formatType } of searchArrays) {
|
|
1710
|
+
for (const entry of entries) {
|
|
1711
|
+
if (entry.name.toLowerCase() !== name.toLowerCase()) continue;
|
|
1712
|
+
const have = parseSemver(entry.version);
|
|
1713
|
+
if (!have) continue;
|
|
1714
|
+
if (wanted && semverCompare(have, wanted) === 0) return { entry, formatType };
|
|
1715
|
+
if (wanted === null || have[0] === wanted[0] && semverCompare(have, wanted) >= 0) {
|
|
1716
|
+
if (!bestVersion || semverCompare(have, bestVersion) > 0) {
|
|
1717
|
+
bestVersion = have;
|
|
1718
|
+
bestResult = { entry, formatType };
|
|
1719
|
+
}
|
|
1648
1720
|
}
|
|
1649
1721
|
}
|
|
1650
1722
|
}
|
|
1651
|
-
return
|
|
1723
|
+
return bestResult;
|
|
1652
1724
|
}
|
|
1653
1725
|
async function verifySHA256(content, expectedHex) {
|
|
1654
1726
|
const digest = await crypto.subtle.digest("SHA-256", content);
|
|
1655
1727
|
const hex = Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1656
1728
|
return hex === expectedHex.toLowerCase();
|
|
1657
1729
|
}
|
|
1658
|
-
function getDownloadUrl(indexUrl, entry) {
|
|
1730
|
+
function getDownloadUrl(indexUrl, entry, formatType) {
|
|
1659
1731
|
const base = indexUrl.replace(/\/index\.json$/, "");
|
|
1660
|
-
return `${base}
|
|
1732
|
+
return `${base}/${formatType}/${entry.name}/${entry.version}/format.js`;
|
|
1661
1733
|
}
|
|
1662
1734
|
async function fetchAndCacheFormat(entry, downloadUrl) {
|
|
1663
1735
|
const res = await fetch(downloadUrl);
|
|
@@ -1668,22 +1740,23 @@ async function fetchAndCacheFormat(entry, downloadUrl) {
|
|
|
1668
1740
|
const checksumKey = Object.keys(entry.checksums ?? {}).find((k) => k.endsWith("format.js"));
|
|
1669
1741
|
if (checksumKey) {
|
|
1670
1742
|
const expected = entry.checksums[checksumKey];
|
|
1743
|
+
if (!expected) throw new Error(`Missing checksum value for key "${checksumKey}"`);
|
|
1671
1744
|
const encoder = new TextEncoder();
|
|
1672
1745
|
const valid = await verifySHA256(encoder.encode(text), expected);
|
|
1673
1746
|
if (!valid) {
|
|
1674
1747
|
throw new Error(`Checksum verification failed for ${entry.name} ${entry.version}`);
|
|
1675
1748
|
}
|
|
1676
1749
|
}
|
|
1677
|
-
const id =
|
|
1750
|
+
const id = makeFormatId(entry.name, entry.version);
|
|
1678
1751
|
const data = parseFormatJSON(text, id);
|
|
1679
1752
|
if (!data) {
|
|
1680
1753
|
throw new Error(`Failed to parse format JSON from ${downloadUrl}`);
|
|
1681
1754
|
}
|
|
1682
1755
|
const cacheDir = getCacheDir();
|
|
1683
1756
|
const formatDir = (0, import_node_path7.join)(cacheDir, entry.name, entry.version);
|
|
1684
|
-
(0,
|
|
1757
|
+
(0, import_node_fs5.mkdirSync)(formatDir, { recursive: true });
|
|
1685
1758
|
const formatPath = (0, import_node_path7.join)(formatDir, "format.js");
|
|
1686
|
-
(0,
|
|
1759
|
+
(0, import_node_fs5.writeFileSync)(formatPath, text, "utf-8");
|
|
1687
1760
|
return {
|
|
1688
1761
|
id,
|
|
1689
1762
|
filename: formatPath,
|
|
@@ -1704,12 +1777,12 @@ async function fetchDirectFormat(url) {
|
|
|
1704
1777
|
if (!data) {
|
|
1705
1778
|
throw new Error(`Failed to parse format JSON from ${url}`);
|
|
1706
1779
|
}
|
|
1707
|
-
const id =
|
|
1780
|
+
const id = makeFormatId(data.name, data.version);
|
|
1708
1781
|
const cacheDir = getCacheDir();
|
|
1709
1782
|
const formatDir = (0, import_node_path7.join)(cacheDir, data.name, data.version);
|
|
1710
|
-
(0,
|
|
1783
|
+
(0, import_node_fs5.mkdirSync)(formatDir, { recursive: true });
|
|
1711
1784
|
const formatPath = (0, import_node_path7.join)(formatDir, "format.js");
|
|
1712
|
-
(0,
|
|
1785
|
+
(0, import_node_fs5.writeFileSync)(formatPath, text, "utf-8");
|
|
1713
1786
|
return {
|
|
1714
1787
|
id,
|
|
1715
1788
|
filename: formatPath,
|
|
@@ -1720,6 +1793,7 @@ async function fetchDirectFormat(url) {
|
|
|
1720
1793
|
};
|
|
1721
1794
|
}
|
|
1722
1795
|
async function resolveRemoteFormat(name, version, indices, urls) {
|
|
1796
|
+
let lastError;
|
|
1723
1797
|
if (urls) {
|
|
1724
1798
|
for (const url of urls) {
|
|
1725
1799
|
try {
|
|
@@ -1731,7 +1805,8 @@ async function resolveRemoteFormat(name, version, indices, urls) {
|
|
|
1731
1805
|
return info;
|
|
1732
1806
|
}
|
|
1733
1807
|
}
|
|
1734
|
-
} catch {
|
|
1808
|
+
} catch (e) {
|
|
1809
|
+
lastError = e instanceof Error ? e : new Error(String(e));
|
|
1735
1810
|
}
|
|
1736
1811
|
}
|
|
1737
1812
|
}
|
|
@@ -1739,24 +1814,26 @@ async function resolveRemoteFormat(name, version, indices, urls) {
|
|
|
1739
1814
|
for (const indexUrl of allIndices) {
|
|
1740
1815
|
try {
|
|
1741
1816
|
const index = await fetchIndex(indexUrl);
|
|
1742
|
-
const
|
|
1743
|
-
if (
|
|
1744
|
-
const cached = getCachedFormat(entry.name, entry.version);
|
|
1817
|
+
const result = findEntry(index, name, version);
|
|
1818
|
+
if (result) {
|
|
1819
|
+
const cached = getCachedFormat(result.entry.name, result.entry.version);
|
|
1745
1820
|
if (cached) return cached;
|
|
1746
|
-
const downloadUrl = getDownloadUrl(indexUrl, entry);
|
|
1747
|
-
return await fetchAndCacheFormat(entry, downloadUrl);
|
|
1821
|
+
const downloadUrl = getDownloadUrl(indexUrl, result.entry, result.formatType);
|
|
1822
|
+
return await fetchAndCacheFormat(result.entry, downloadUrl);
|
|
1748
1823
|
}
|
|
1749
|
-
} catch {
|
|
1824
|
+
} catch (e) {
|
|
1825
|
+
lastError = e instanceof Error ? e : new Error(String(e));
|
|
1750
1826
|
}
|
|
1751
1827
|
}
|
|
1828
|
+
if (lastError) throw lastError;
|
|
1752
1829
|
return void 0;
|
|
1753
1830
|
}
|
|
1754
1831
|
function getCachedFormat(name, version) {
|
|
1755
1832
|
const formatPath = (0, import_node_path7.join)(getCacheDir(), name, version, "format.js");
|
|
1756
1833
|
try {
|
|
1757
|
-
if (!(0,
|
|
1758
|
-
const source = (0,
|
|
1759
|
-
const id =
|
|
1834
|
+
if (!(0, import_node_fs5.existsSync)(formatPath)) return void 0;
|
|
1835
|
+
const source = (0, import_node_fs5.readFileSync)(formatPath, "utf-8");
|
|
1836
|
+
const id = makeFormatId(name, version);
|
|
1760
1837
|
const data = parseFormatJSON(source, id);
|
|
1761
1838
|
if (!data) return void 0;
|
|
1762
1839
|
return {
|
|
@@ -1775,26 +1852,26 @@ function discoverCachedFormats() {
|
|
|
1775
1852
|
const formats = /* @__PURE__ */ new Map();
|
|
1776
1853
|
const cacheDir = getCacheDir();
|
|
1777
1854
|
try {
|
|
1778
|
-
if (!(0,
|
|
1855
|
+
if (!(0, import_node_fs5.existsSync)(cacheDir)) return formats;
|
|
1779
1856
|
} catch {
|
|
1780
1857
|
return formats;
|
|
1781
1858
|
}
|
|
1782
1859
|
let names;
|
|
1783
1860
|
try {
|
|
1784
|
-
names = (0,
|
|
1861
|
+
names = (0, import_node_fs5.readdirSync)(cacheDir);
|
|
1785
1862
|
} catch {
|
|
1786
1863
|
return formats;
|
|
1787
1864
|
}
|
|
1788
1865
|
for (const name of names) {
|
|
1789
1866
|
const nameDir = (0, import_node_path7.join)(cacheDir, name);
|
|
1790
1867
|
try {
|
|
1791
|
-
if (!(0,
|
|
1868
|
+
if (!(0, import_node_fs5.statSync)(nameDir).isDirectory()) continue;
|
|
1792
1869
|
} catch {
|
|
1793
1870
|
continue;
|
|
1794
1871
|
}
|
|
1795
1872
|
let versions;
|
|
1796
1873
|
try {
|
|
1797
|
-
versions = (0,
|
|
1874
|
+
versions = (0, import_node_fs5.readdirSync)(nameDir);
|
|
1798
1875
|
} catch {
|
|
1799
1876
|
continue;
|
|
1800
1877
|
}
|
|
@@ -1809,6 +1886,7 @@ function discoverCachedFormats() {
|
|
|
1809
1886
|
}
|
|
1810
1887
|
|
|
1811
1888
|
// src/compiler.ts
|
|
1889
|
+
var CREATOR_NAME3 = "Twee-ts";
|
|
1812
1890
|
var DEFAULT_FORMAT_ID = "sugarcube-2";
|
|
1813
1891
|
var DEFAULT_START_NAME = "Start";
|
|
1814
1892
|
var TweeTsError = class extends Error {
|
|
@@ -1823,7 +1901,7 @@ async function compile(options) {
|
|
|
1823
1901
|
}
|
|
1824
1902
|
async function compileToFile(options) {
|
|
1825
1903
|
const result = await compile(options);
|
|
1826
|
-
(0,
|
|
1904
|
+
(0, import_node_fs6.writeFileSync)(options.outFile, result.output, "utf-8");
|
|
1827
1905
|
return result;
|
|
1828
1906
|
}
|
|
1829
1907
|
async function watch(options) {
|
|
@@ -1833,7 +1911,7 @@ async function watch(options) {
|
|
|
1833
1911
|
const allPaths = [...filePaths, ...modulePaths];
|
|
1834
1912
|
const handle = watchFilesystem(allPaths, options.outFile, () => {
|
|
1835
1913
|
buildOutput(options).then((result) => {
|
|
1836
|
-
(0,
|
|
1914
|
+
(0, import_node_fs6.writeFileSync)(options.outFile, result.output, "utf-8");
|
|
1837
1915
|
options.onBuild?.(result);
|
|
1838
1916
|
}).catch((e) => {
|
|
1839
1917
|
options.onError?.(e instanceof Error ? e : new Error(String(e)));
|
|
@@ -1865,10 +1943,7 @@ async function buildOutput(options) {
|
|
|
1865
1943
|
loadSources(story, sourceFilenames, { trim, twee2Compat }, diagnostics, processedFiles);
|
|
1866
1944
|
loadInlineSources(story, inlineSources, { trim, twee2Compat }, diagnostics);
|
|
1867
1945
|
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") {
|
|
1946
|
+
story.passages = applyTagAliases(story.passages, options.tagAliases);
|
|
1872
1947
|
}
|
|
1873
1948
|
let format;
|
|
1874
1949
|
let formatId = options.formatId ?? "";
|
|
@@ -1887,7 +1962,11 @@ async function buildOutput(options) {
|
|
|
1887
1962
|
const remoteVersion = story.twine2.formatVersion || "";
|
|
1888
1963
|
try {
|
|
1889
1964
|
format = await resolveRemoteFormat(remoteName, remoteVersion, options.formatIndices, options.formatUrls);
|
|
1890
|
-
} catch {
|
|
1965
|
+
} catch (e) {
|
|
1966
|
+
diagnostics.push({
|
|
1967
|
+
level: "warning",
|
|
1968
|
+
message: `Remote format fetch failed for "${remoteName}": ${e instanceof Error ? e.message : String(e)}`
|
|
1969
|
+
});
|
|
1891
1970
|
}
|
|
1892
1971
|
}
|
|
1893
1972
|
if (!format) {
|
|
@@ -1898,14 +1977,7 @@ async function buildOutput(options) {
|
|
|
1898
1977
|
});
|
|
1899
1978
|
}
|
|
1900
1979
|
}
|
|
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
|
-
}
|
|
1980
|
+
const startName = options.startPassage || story.twine2.start || DEFAULT_START_NAME;
|
|
1909
1981
|
if (testMode) {
|
|
1910
1982
|
story.twine2.options.set("debug", true);
|
|
1911
1983
|
}
|
|
@@ -1924,8 +1996,7 @@ async function buildOutput(options) {
|
|
|
1924
1996
|
case "json":
|
|
1925
1997
|
output = storyToJSON(story);
|
|
1926
1998
|
break;
|
|
1927
|
-
case "html":
|
|
1928
|
-
default: {
|
|
1999
|
+
case "html": {
|
|
1929
2000
|
if (!storyHas(story, startName)) {
|
|
1930
2001
|
diagnostics.push({
|
|
1931
2002
|
level: "error",
|
|
@@ -1947,9 +2018,13 @@ async function buildOutput(options) {
|
|
|
1947
2018
|
output = toTwine1HTML(story, format, startName, diagnostics);
|
|
1948
2019
|
}
|
|
1949
2020
|
const modulePaths = options.modules ? getFilenames(options.modules) : [];
|
|
1950
|
-
output = modifyHead(output, modulePaths, options.headFile);
|
|
2021
|
+
output = modifyHead(output, modulePaths, options.headFile, diagnostics);
|
|
1951
2022
|
break;
|
|
1952
2023
|
}
|
|
2024
|
+
default: {
|
|
2025
|
+
const _exhaustive = outputMode;
|
|
2026
|
+
throw new TweeTsError(`Unhandled output mode: ${_exhaustive}`, diagnostics);
|
|
2027
|
+
}
|
|
1953
2028
|
}
|
|
1954
2029
|
const stats = {
|
|
1955
2030
|
...getStoryStats(story),
|
|
@@ -1958,28 +2033,46 @@ async function buildOutput(options) {
|
|
|
1958
2033
|
return { output, story, format, diagnostics, stats };
|
|
1959
2034
|
}
|
|
1960
2035
|
function storyToJSON(story) {
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
2036
|
+
const scripts = [];
|
|
2037
|
+
const stylesheets = [];
|
|
2038
|
+
const storyPassages = [];
|
|
2039
|
+
for (const p of story.passages) {
|
|
2040
|
+
if (p.name === "StoryTitle" || p.name === "StoryData") continue;
|
|
2041
|
+
if (hasTag(p, "Twine.private")) continue;
|
|
2042
|
+
if (hasTag(p, "script")) {
|
|
2043
|
+
scripts.push(p.text);
|
|
2044
|
+
} else if (hasTag(p, "stylesheet")) {
|
|
2045
|
+
stylesheets.push(p.text);
|
|
2046
|
+
} else {
|
|
2047
|
+
const entry = {
|
|
1966
2048
|
name: p.name,
|
|
1967
2049
|
tags: p.tags,
|
|
1968
|
-
text: p.text
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
tagColors: Object.fromEntries(story.twine2.tagColors),
|
|
1976
|
-
options: Object.fromEntries(story.twine2.options),
|
|
1977
|
-
zoom: story.twine2.zoom
|
|
2050
|
+
text: p.text
|
|
2051
|
+
};
|
|
2052
|
+
if (p.metadata && (p.metadata.position || p.metadata.size)) {
|
|
2053
|
+
const meta = {};
|
|
2054
|
+
if (p.metadata.position) meta.position = p.metadata.position;
|
|
2055
|
+
if (p.metadata.size) meta.size = p.metadata.size;
|
|
2056
|
+
entry.metadata = meta;
|
|
1978
2057
|
}
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
2058
|
+
storyPassages.push(entry);
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
const obj = {
|
|
2062
|
+
name: story.name
|
|
2063
|
+
};
|
|
2064
|
+
if (story.ifid) obj.ifid = story.ifid;
|
|
2065
|
+
if (story.twine2.format) obj.format = story.twine2.format;
|
|
2066
|
+
if (story.twine2.formatVersion) obj["format-version"] = story.twine2.formatVersion;
|
|
2067
|
+
if (story.twine2.start) obj.start = story.twine2.start;
|
|
2068
|
+
if (story.twine2.tagColors.size > 0) obj["tag-colors"] = Object.fromEntries(story.twine2.tagColors);
|
|
2069
|
+
if (story.twine2.zoom !== 1) obj.zoom = story.twine2.zoom;
|
|
2070
|
+
obj.creator = CREATOR_NAME3;
|
|
2071
|
+
obj["creator-version"] = VERSION;
|
|
2072
|
+
obj.style = stylesheets.join("\n");
|
|
2073
|
+
obj.script = scripts.join("\n");
|
|
2074
|
+
obj.passages = storyPassages;
|
|
2075
|
+
return JSON.stringify(obj, null, 2);
|
|
1983
2076
|
}
|
|
1984
2077
|
|
|
1985
2078
|
// src/inspect.ts
|
|
@@ -2085,17 +2178,17 @@ function storyInspect(story) {
|
|
|
2085
2178
|
}
|
|
2086
2179
|
|
|
2087
2180
|
// src/config.ts
|
|
2088
|
-
var
|
|
2181
|
+
var import_node_fs7 = require("fs");
|
|
2089
2182
|
var import_node_path8 = require("path");
|
|
2090
2183
|
var CONFIG_FILENAME = "twee-ts.config.json";
|
|
2091
2184
|
var VALID_OUTPUT_MODES = ["html", "twee3", "twee1", "twine2-archive", "twine1-archive", "json"];
|
|
2092
2185
|
function loadConfig(dir) {
|
|
2093
2186
|
const base = dir ?? process.cwd();
|
|
2094
2187
|
const configPath = (0, import_node_path8.join)(base, CONFIG_FILENAME);
|
|
2095
|
-
if (!(0,
|
|
2188
|
+
if (!(0, import_node_fs7.existsSync)(configPath)) return null;
|
|
2096
2189
|
let raw;
|
|
2097
2190
|
try {
|
|
2098
|
-
raw = (0,
|
|
2191
|
+
raw = (0, import_node_fs7.readFileSync)(configPath, "utf-8");
|
|
2099
2192
|
} catch {
|
|
2100
2193
|
return null;
|
|
2101
2194
|
}
|
|
@@ -2115,7 +2208,7 @@ function loadConfig(dir) {
|
|
|
2115
2208
|
function loadConfigFile(filePath) {
|
|
2116
2209
|
let raw;
|
|
2117
2210
|
try {
|
|
2118
|
-
raw = (0,
|
|
2211
|
+
raw = (0, import_node_fs7.readFileSync)(filePath, "utf-8");
|
|
2119
2212
|
} catch (e) {
|
|
2120
2213
|
throw new Error(`Cannot read config file ${filePath}: ${e instanceof Error ? e.message : String(e)}`);
|
|
2121
2214
|
}
|