@rohal12/twee-ts 1.1.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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 import_node_fs7 = require("fs");
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) break;
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.position;
235
+ position = p.metadata?.position ?? "";
223
236
  } else {
224
237
  const x = pid % 10;
225
- let y = Math.floor(pid / 10);
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.size;
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.position;
253
+ position = p.metadata?.position ?? "";
241
254
  } else {
242
255
  const x = pid % 10;
243
- let y = Math.floor(pid / 10);
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
- for (const p of passages) {
264
- const original = [...p.tags];
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) && !p.tags.includes(canonical)) {
267
- p.tags.push(canonical);
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 data;
357
+ let raw;
343
358
  try {
344
- data = JSON.parse(json);
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 (data.ifid) story.ifid = data.ifid.toUpperCase();
349
- if (data.format) story.twine2.format = data.format;
350
- if (data["format-version"]) story.twine2.formatVersion = data["format-version"];
351
- if (data.options) {
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 != null && data.zoom !== 0) story.twine2.zoom = 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.text = marshalStoryData(story);
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
- p.text = p.text.trim();
438
- story.name = p.text;
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, p, diagnostics);
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
- var import_node_os = require("os");
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 JSON.parse(chunk);
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 JSON.parse(chunk);
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, import_node_fs2.statSync)(searchDir);
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, import_node_fs2.readdirSync)(searchDir);
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, import_node_path3.join)(searchDir, entry);
716
+ const formatDir = (0, import_node_path4.join)(searchDir, entry);
676
717
  try {
677
- if (!(0, import_node_fs2.statSync)(formatDir).isDirectory()) continue;
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, import_node_path3.join)(formatDir, baseFilename);
723
+ const formatFilename = (0, import_node_path4.join)(formatDir, baseFilename);
683
724
  try {
684
- const stat = (0, import_node_fs2.statSync)(formatFilename);
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, import_node_path3.join)(base, sub);
769
+ const dir = (0, import_node_path4.join)(base, sub);
729
770
  try {
730
- if ((0, import_node_fs2.statSync)(dir).isDirectory()) {
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
- for (let i = 0; i < 3; i++) {
753
- if (a[i] !== b[i]) return a[i] - b[i];
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 import_node_fs3 = require("fs");
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
- s = s.replace(twee2HeaderRe, (_, p1, p2, p3) => {
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
- s = s.replace(twee2BadPosRe, "$1");
1084
- return s;
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 parsed = JSON.parse(item.val);
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 = readUTF82(filename);
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 = readUTF82(filename);
1312
- storyAdd(story, { name: (0, import_node_path4.basename)(filename), tags: [tag], text: source }, diagnostics);
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 = (0, import_node_path4.basename)(filename).split(".")[0];
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, import_node_path4.basename)(filename);
1330
- const family = name.split(".")[0];
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].text;
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].text;
1441
+ scriptContent = scripts[0]?.text ?? "";
1393
1442
  } else if (scripts.length > 1) {
1394
1443
  let pidS = 1;
1395
1444
  for (const p of scripts) {
1396
1445
  if (pidS > 1 && !scriptContent.endsWith("\n")) scriptContent += "\n";
1397
- scriptContent += `/* twine-user-script #${pidS}: "${p.name}" */
1446
+ scriptContent += `/* twine-user-script #${pidS}: "${commentSanitize(p.name)}" */
1398
1447
  `;
1399
1448
  scriptContent += p.text;
1400
1449
  pidS++;
@@ -1424,7 +1473,7 @@ function getTwine2DataChunk(story, startName, diagnostics) {
1424
1473
  }
1425
1474
  const options = opts.join(" ");
1426
1475
  const zoom = story.twine2.zoom === Math.floor(story.twine2.zoom) ? story.twine2.zoom.toString() : story.twine2.zoom.toFixed(1);
1427
- const wrapper = `<!-- UUID://${story.ifid}// --><tw-storydata name="${attrEscape(story.name)}" startnode="${startID}" creator="${attrEscape(CREATOR_NAME)}" creator-version="${attrEscape(CREATOR_VERSION)}" ifid="${attrEscape(story.ifid)}" zoom="${attrEscape(zoom)}" format="${attrEscape(story.twine2.format)}" format-version="${attrEscape(story.twine2.formatVersion)}" options="${attrEscape(options)}" hidden>`;
1476
+ const wrapper = `<!-- UUID://${htmlCommentSanitize(story.ifid)}// --><tw-storydata name="${attrEscape(story.name)}" startnode="${attrEscape(startID)}" creator="${attrEscape(CREATOR_NAME)}" creator-version="${attrEscape(VERSION)}" ifid="${attrEscape(story.ifid)}" zoom="${attrEscape(zoom)}" format="${attrEscape(story.twine2.format)}" format-version="${attrEscape(story.twine2.formatVersion)}" options="${attrEscape(options)}" hidden>`;
1428
1477
  return wrapper + parts.join("") + "</tw-storydata>";
1429
1478
  }
1430
1479
  function ensureIFID(story, diagnostics) {
@@ -1446,50 +1495,50 @@ function ensureIFID(story, diagnostics) {
1446
1495
 
1447
1496
  // src/output-twine1.ts
1448
1497
  var import_node_fs4 = require("fs");
1449
- var import_node_path5 = require("path");
1498
+ var import_node_path6 = require("path");
1450
1499
  var CREATOR_NAME2 = "twee-ts";
1451
- var CREATOR_VERSION2 = "0.1.0";
1452
1500
  function toTwine1Archive(story, _startName) {
1453
1501
  const { data, count } = getTwine1PassageChunk(story);
1454
1502
  return `<div id="storeArea" data-size="${count}" hidden>${data}</div>
1455
1503
  `;
1456
1504
  }
1457
1505
  function toTwine1HTML(story, format, startName, _diagnostics) {
1458
- const formatDir = (0, import_node_path5.dirname)(format.filename);
1459
- const parentDir = (0, import_node_path5.dirname)(formatDir);
1506
+ const formatDir = (0, import_node_path6.dirname)(format.filename);
1507
+ const parentDir = (0, import_node_path6.dirname)(formatDir);
1460
1508
  let template = readFormatSource(format);
1461
1509
  const { data, count } = getTwine1PassageChunk(story);
1462
- template = tryReplaceComponent(template, '"USER_LIB"', (0, import_node_path5.join)(formatDir, "userlib.js"), false);
1463
- template = tryReplaceComponent(template, '"ENGINE"', (0, import_node_path5.join)(parentDir, "engine.js"), true);
1464
- template = tryReplaceComponent(template, '"SUGARCANE"', (0, import_node_path5.join)(formatDir, "code.js"), true);
1465
- template = tryReplaceComponent(template, '"JONAH"', (0, import_node_path5.join)(formatDir, "code.js"), true);
1510
+ template = tryReplaceComponent(template, '"USER_LIB"', (0, import_node_path6.join)(formatDir, "userlib.js"), false);
1511
+ template = tryReplaceComponent(template, '"ENGINE"', (0, import_node_path6.join)(parentDir, "engine.js"), true);
1512
+ template = tryReplaceComponent(template, '"SUGARCANE"', (0, import_node_path6.join)(formatDir, "code.js"), true);
1513
+ template = tryReplaceComponent(template, '"JONAH"', (0, import_node_path6.join)(formatDir, "code.js"), true);
1466
1514
  if (story.twine1.settings.get("jquery") === "on") {
1467
- template = tryReplaceComponent(template, '"JQUERY"', (0, import_node_path5.join)(parentDir, "jquery.js"), true);
1515
+ template = tryReplaceComponent(template, '"JQUERY"', (0, import_node_path6.join)(parentDir, "jquery.js"), true);
1468
1516
  }
1469
1517
  if (story.twine1.settings.get("modernizr") === "on") {
1470
- template = tryReplaceComponent(template, '"MODERNIZR"', (0, import_node_path5.join)(parentDir, "modernizr.js"), true);
1518
+ template = tryReplaceComponent(template, '"MODERNIZR"', (0, import_node_path6.join)(parentDir, "modernizr.js"), true);
1471
1519
  }
1472
1520
  const displayStart = startName === "Start" ? "" : startName;
1473
- template = template.replace('"VERSION"', `Compiled with ${CREATOR_NAME2}, ${CREATOR_VERSION2}`);
1521
+ template = template.replace('"VERSION"', `Compiled with ${CREATOR_NAME2}, ${VERSION}`);
1474
1522
  template = template.replace('"TIME"', `Built on ${(/* @__PURE__ */ new Date()).toUTCString()}`);
1475
- template = template.replace('"START_AT"', `"${displayStart}"`);
1523
+ template = template.replace('"START_AT"', `"${jsStringEscape(displayStart)}"`);
1476
1524
  template = template.replace('"STORY_SIZE"', `"${count}"`);
1477
1525
  if (template.includes('"STORY"')) {
1478
1526
  template = template.replace('"STORY"', data);
1479
1527
  } else {
1480
1528
  let footer;
1481
1529
  try {
1482
- footer = (0, import_node_fs4.readFileSync)((0, import_node_path5.join)(formatDir, "footer.html"), "utf-8");
1530
+ footer = (0, import_node_fs4.readFileSync)((0, import_node_path6.join)(formatDir, "footer.html"), "utf-8");
1483
1531
  } catch {
1484
1532
  footer = "</div>\n</body>\n</html>\n";
1485
1533
  }
1486
1534
  template += data + footer;
1487
1535
  }
1488
1536
  if (story.ifid) {
1537
+ const safeIfid = htmlCommentSanitize(story.ifid);
1489
1538
  if (template.includes('<div id="store-area"')) {
1490
- template = template.replace('<div id="store-area"', `<!-- UUID://${story.ifid}// --><div id="store-area"`);
1539
+ template = template.replace('<div id="store-area"', `<!-- UUID://${safeIfid}// --><div id="store-area"`);
1491
1540
  } else {
1492
- template = template.replace('<div id="storeArea"', `<!-- UUID://${story.ifid}// --><div id="storeArea"`);
1541
+ template = template.replace('<div id="storeArea"', `<!-- UUID://${safeIfid}// --><div id="storeArea"`);
1493
1542
  }
1494
1543
  }
1495
1544
  return template;
@@ -1504,13 +1553,18 @@ function getTwine1PassageChunk(story) {
1504
1553
  }
1505
1554
  return { data, count };
1506
1555
  }
1507
- function tryReplaceComponent(template, placeholder, componentPath, _required) {
1556
+ function tryReplaceComponent(template, placeholder, componentPath, required) {
1508
1557
  if (!template.includes(placeholder)) return template;
1509
1558
  try {
1510
1559
  let content = (0, import_node_fs4.readFileSync)(componentPath, "utf-8");
1511
1560
  if (content.charCodeAt(0) === 65279) content = content.slice(1);
1512
1561
  return template.replace(placeholder, content);
1513
- } catch {
1562
+ } catch (e) {
1563
+ if (required) {
1564
+ throw new Error(
1565
+ `Required format component not found: ${componentPath}: ${e instanceof Error ? e.message : String(e)}`
1566
+ );
1567
+ }
1514
1568
  return template;
1515
1569
  }
1516
1570
  }
@@ -1525,16 +1579,6 @@ function toTwee(story, outMode) {
1525
1579
  }
1526
1580
 
1527
1581
  // src/modules.ts
1528
- var import_node_fs5 = require("fs");
1529
- var import_node_path6 = require("path");
1530
- function readUTF83(filename) {
1531
- let content = (0, import_node_fs5.readFileSync)(filename, "utf-8");
1532
- if (content.charCodeAt(0) === 65279) content = content.slice(1);
1533
- return content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
1534
- }
1535
- function readBase642(filename) {
1536
- return (0, import_node_fs5.readFileSync)(filename).toString("base64");
1537
- }
1538
1582
  function loadModules(filenames) {
1539
1583
  const processed = /* @__PURE__ */ new Set();
1540
1584
  const headTags = [];
@@ -1564,16 +1608,16 @@ function loadModules(filenames) {
1564
1608
  return headTags.join("\n");
1565
1609
  }
1566
1610
  function loadModuleTagged(tag, filename) {
1567
- const source = readUTF83(filename).trim();
1611
+ const source = readUTF8(filename).trim();
1568
1612
  if (source.length === 0) return null;
1569
- const family = (0, import_node_path6.basename)(filename).split(".")[0];
1613
+ const family = baseNameWithoutExt(filename);
1570
1614
  const idSlug = `${tag}-module-${slugify(family)}`;
1571
1615
  const mimeType = tag === "script" ? "text/javascript" : "text/css";
1572
1616
  return `<${tag} id="${idSlug}" type="${mimeType}">${source}</${tag}>`;
1573
1617
  }
1574
1618
  function loadModuleFont(filename) {
1575
- const source = readBase642(filename);
1576
- const family = (0, import_node_path6.basename)(filename).split(".")[0];
1619
+ const source = readBase64(filename);
1620
+ const family = baseNameWithoutExt(filename);
1577
1621
  const idSlug = `style-module-${slugify(family)}`;
1578
1622
  const ext = normalizedFileExt(filename);
1579
1623
  const mediaType = mediaTypeFromExt(ext);
@@ -1583,7 +1627,7 @@ function loadModuleFont(filename) {
1583
1627
  src: url("data:${mediaType};base64,${source}") format("${hint}");
1584
1628
  }</style>`;
1585
1629
  }
1586
- function modifyHead(html, modulePaths, headFile) {
1630
+ function modifyHead(html, modulePaths, headFile, diagnostics) {
1587
1631
  const parts = [];
1588
1632
  if (modulePaths.length > 0) {
1589
1633
  const modules = loadModules(modulePaths).trim();
@@ -1591,9 +1635,13 @@ function modifyHead(html, modulePaths, headFile) {
1591
1635
  }
1592
1636
  if (headFile) {
1593
1637
  try {
1594
- const source = readUTF83(headFile).trim();
1638
+ const source = readUTF8(headFile).trim();
1595
1639
  if (source.length > 0) parts.push(source);
1596
- } catch {
1640
+ } catch (e) {
1641
+ diagnostics?.push({
1642
+ level: "warning",
1643
+ message: `Failed to read head file "${headFile}": ${e instanceof Error ? e.message : String(e)}`
1644
+ });
1597
1645
  }
1598
1646
  }
1599
1647
  if (parts.length > 0) {
@@ -1604,7 +1652,7 @@ function modifyHead(html, modulePaths, headFile) {
1604
1652
  }
1605
1653
 
1606
1654
  // src/remote-formats.ts
1607
- var import_node_fs6 = require("fs");
1655
+ var import_node_fs5 = require("fs");
1608
1656
  var import_node_path7 = require("path");
1609
1657
  var import_node_os2 = require("os");
1610
1658
  var DEFAULT_SFA_INDICES = [
@@ -1620,6 +1668,24 @@ var indexCache = /* @__PURE__ */ new Map();
1620
1668
  function clearIndexCache() {
1621
1669
  indexCache.clear();
1622
1670
  }
1671
+ function isValidEntry(val) {
1672
+ if (typeof val !== "object" || val === null || Array.isArray(val)) return false;
1673
+ const obj = val;
1674
+ return typeof obj.name === "string" && typeof obj.version === "string" && typeof obj.checksums === "object" && obj.checksums !== null && !Array.isArray(obj.checksums);
1675
+ }
1676
+ function validateSFAIndex(json) {
1677
+ if (typeof json !== "object" || json === null) {
1678
+ throw new Error("SFA index is not an object");
1679
+ }
1680
+ const obj = json;
1681
+ const twine1 = Array.isArray(obj["twine1"]) ? obj["twine1"].filter(isValidEntry) : [];
1682
+ const twine2 = Array.isArray(obj["twine2"]) ? obj["twine2"].filter(isValidEntry) : [];
1683
+ return { twine1, twine2 };
1684
+ }
1685
+ function makeFormatId(name, version) {
1686
+ const major = parseSemver(version)?.[0] ?? "0";
1687
+ return `${name.toLowerCase().replace(/\s+/g, "-")}-${major}`;
1688
+ }
1623
1689
  async function fetchIndex(url) {
1624
1690
  const cached = indexCache.get(url);
1625
1691
  if (cached) return cached;
@@ -1627,37 +1693,42 @@ async function fetchIndex(url) {
1627
1693
  if (!res.ok) {
1628
1694
  throw new Error(`Failed to fetch format index from ${url}: ${res.status} ${res.statusText}`);
1629
1695
  }
1630
- const data = await res.json();
1696
+ const data = validateSFAIndex(await res.json());
1631
1697
  indexCache.set(url, data);
1632
1698
  return data;
1633
1699
  }
1634
1700
  function findEntry(index, name, version) {
1635
1701
  const wanted = parseSemver(version);
1636
- const entries = [...index.twine2 ?? [], ...index.twine1 ?? []];
1637
- let bestEntry;
1702
+ const searchArrays = [
1703
+ { entries: index.twine2 ?? [], formatType: "twine2" },
1704
+ { entries: index.twine1 ?? [], formatType: "twine1" }
1705
+ ];
1706
+ let bestResult;
1638
1707
  let bestVersion = null;
1639
- for (const entry of entries) {
1640
- if (entry.name.toLowerCase() !== name.toLowerCase()) continue;
1641
- const have = parseSemver(entry.version);
1642
- if (!have) continue;
1643
- if (wanted && semverCompare(have, wanted) === 0) return entry;
1644
- if (wanted === null || have[0] === wanted[0] && semverCompare(have, wanted) >= 0) {
1645
- if (!bestVersion || semverCompare(have, bestVersion) > 0) {
1646
- bestVersion = have;
1647
- bestEntry = entry;
1708
+ for (const { entries, formatType } of searchArrays) {
1709
+ for (const entry of entries) {
1710
+ if (entry.name.toLowerCase() !== name.toLowerCase()) continue;
1711
+ const have = parseSemver(entry.version);
1712
+ if (!have) continue;
1713
+ if (wanted && semverCompare(have, wanted) === 0) return { entry, formatType };
1714
+ if (wanted === null || have[0] === wanted[0] && semverCompare(have, wanted) >= 0) {
1715
+ if (!bestVersion || semverCompare(have, bestVersion) > 0) {
1716
+ bestVersion = have;
1717
+ bestResult = { entry, formatType };
1718
+ }
1648
1719
  }
1649
1720
  }
1650
1721
  }
1651
- return bestEntry;
1722
+ return bestResult;
1652
1723
  }
1653
1724
  async function verifySHA256(content, expectedHex) {
1654
1725
  const digest = await crypto.subtle.digest("SHA-256", content);
1655
1726
  const hex = Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
1656
1727
  return hex === expectedHex.toLowerCase();
1657
1728
  }
1658
- function getDownloadUrl(indexUrl, entry) {
1729
+ function getDownloadUrl(indexUrl, entry, formatType) {
1659
1730
  const base = indexUrl.replace(/\/index\.json$/, "");
1660
- return `${base}/twine2/${entry.name}/${entry.version}/format.js`;
1731
+ return `${base}/${formatType}/${entry.name}/${entry.version}/format.js`;
1661
1732
  }
1662
1733
  async function fetchAndCacheFormat(entry, downloadUrl) {
1663
1734
  const res = await fetch(downloadUrl);
@@ -1668,22 +1739,23 @@ async function fetchAndCacheFormat(entry, downloadUrl) {
1668
1739
  const checksumKey = Object.keys(entry.checksums ?? {}).find((k) => k.endsWith("format.js"));
1669
1740
  if (checksumKey) {
1670
1741
  const expected = entry.checksums[checksumKey];
1742
+ if (!expected) throw new Error(`Missing checksum value for key "${checksumKey}"`);
1671
1743
  const encoder = new TextEncoder();
1672
1744
  const valid = await verifySHA256(encoder.encode(text), expected);
1673
1745
  if (!valid) {
1674
1746
  throw new Error(`Checksum verification failed for ${entry.name} ${entry.version}`);
1675
1747
  }
1676
1748
  }
1677
- const id = `${entry.name.toLowerCase().replace(/\s+/g, "-")}-${parseSemver(entry.version)?.[0] ?? "0"}`;
1749
+ const id = makeFormatId(entry.name, entry.version);
1678
1750
  const data = parseFormatJSON(text, id);
1679
1751
  if (!data) {
1680
1752
  throw new Error(`Failed to parse format JSON from ${downloadUrl}`);
1681
1753
  }
1682
1754
  const cacheDir = getCacheDir();
1683
1755
  const formatDir = (0, import_node_path7.join)(cacheDir, entry.name, entry.version);
1684
- (0, import_node_fs6.mkdirSync)(formatDir, { recursive: true });
1756
+ (0, import_node_fs5.mkdirSync)(formatDir, { recursive: true });
1685
1757
  const formatPath = (0, import_node_path7.join)(formatDir, "format.js");
1686
- (0, import_node_fs6.writeFileSync)(formatPath, text, "utf-8");
1758
+ (0, import_node_fs5.writeFileSync)(formatPath, text, "utf-8");
1687
1759
  return {
1688
1760
  id,
1689
1761
  filename: formatPath,
@@ -1704,12 +1776,12 @@ async function fetchDirectFormat(url) {
1704
1776
  if (!data) {
1705
1777
  throw new Error(`Failed to parse format JSON from ${url}`);
1706
1778
  }
1707
- const id = `${data.name.toLowerCase().replace(/\s+/g, "-")}-${parseSemver(data.version)?.[0] ?? "0"}`;
1779
+ const id = makeFormatId(data.name, data.version);
1708
1780
  const cacheDir = getCacheDir();
1709
1781
  const formatDir = (0, import_node_path7.join)(cacheDir, data.name, data.version);
1710
- (0, import_node_fs6.mkdirSync)(formatDir, { recursive: true });
1782
+ (0, import_node_fs5.mkdirSync)(formatDir, { recursive: true });
1711
1783
  const formatPath = (0, import_node_path7.join)(formatDir, "format.js");
1712
- (0, import_node_fs6.writeFileSync)(formatPath, text, "utf-8");
1784
+ (0, import_node_fs5.writeFileSync)(formatPath, text, "utf-8");
1713
1785
  return {
1714
1786
  id,
1715
1787
  filename: formatPath,
@@ -1720,6 +1792,7 @@ async function fetchDirectFormat(url) {
1720
1792
  };
1721
1793
  }
1722
1794
  async function resolveRemoteFormat(name, version, indices, urls) {
1795
+ let lastError;
1723
1796
  if (urls) {
1724
1797
  for (const url of urls) {
1725
1798
  try {
@@ -1731,7 +1804,8 @@ async function resolveRemoteFormat(name, version, indices, urls) {
1731
1804
  return info;
1732
1805
  }
1733
1806
  }
1734
- } catch {
1807
+ } catch (e) {
1808
+ lastError = e instanceof Error ? e : new Error(String(e));
1735
1809
  }
1736
1810
  }
1737
1811
  }
@@ -1739,24 +1813,26 @@ async function resolveRemoteFormat(name, version, indices, urls) {
1739
1813
  for (const indexUrl of allIndices) {
1740
1814
  try {
1741
1815
  const index = await fetchIndex(indexUrl);
1742
- const entry = findEntry(index, name, version);
1743
- if (entry) {
1744
- const cached = getCachedFormat(entry.name, entry.version);
1816
+ const result = findEntry(index, name, version);
1817
+ if (result) {
1818
+ const cached = getCachedFormat(result.entry.name, result.entry.version);
1745
1819
  if (cached) return cached;
1746
- const downloadUrl = getDownloadUrl(indexUrl, entry);
1747
- return await fetchAndCacheFormat(entry, downloadUrl);
1820
+ const downloadUrl = getDownloadUrl(indexUrl, result.entry, result.formatType);
1821
+ return await fetchAndCacheFormat(result.entry, downloadUrl);
1748
1822
  }
1749
- } catch {
1823
+ } catch (e) {
1824
+ lastError = e instanceof Error ? e : new Error(String(e));
1750
1825
  }
1751
1826
  }
1827
+ if (lastError) throw lastError;
1752
1828
  return void 0;
1753
1829
  }
1754
1830
  function getCachedFormat(name, version) {
1755
1831
  const formatPath = (0, import_node_path7.join)(getCacheDir(), name, version, "format.js");
1756
1832
  try {
1757
- if (!(0, import_node_fs6.existsSync)(formatPath)) return void 0;
1758
- const source = (0, import_node_fs6.readFileSync)(formatPath, "utf-8");
1759
- const id = `${name.toLowerCase().replace(/\s+/g, "-")}-${parseSemver(version)?.[0] ?? "0"}`;
1833
+ if (!(0, import_node_fs5.existsSync)(formatPath)) return void 0;
1834
+ const source = (0, import_node_fs5.readFileSync)(formatPath, "utf-8");
1835
+ const id = makeFormatId(name, version);
1760
1836
  const data = parseFormatJSON(source, id);
1761
1837
  if (!data) return void 0;
1762
1838
  return {
@@ -1775,26 +1851,26 @@ function discoverCachedFormats() {
1775
1851
  const formats = /* @__PURE__ */ new Map();
1776
1852
  const cacheDir = getCacheDir();
1777
1853
  try {
1778
- if (!(0, import_node_fs6.existsSync)(cacheDir)) return formats;
1854
+ if (!(0, import_node_fs5.existsSync)(cacheDir)) return formats;
1779
1855
  } catch {
1780
1856
  return formats;
1781
1857
  }
1782
1858
  let names;
1783
1859
  try {
1784
- names = (0, import_node_fs6.readdirSync)(cacheDir);
1860
+ names = (0, import_node_fs5.readdirSync)(cacheDir);
1785
1861
  } catch {
1786
1862
  return formats;
1787
1863
  }
1788
1864
  for (const name of names) {
1789
1865
  const nameDir = (0, import_node_path7.join)(cacheDir, name);
1790
1866
  try {
1791
- if (!(0, import_node_fs6.statSync)(nameDir).isDirectory()) continue;
1867
+ if (!(0, import_node_fs5.statSync)(nameDir).isDirectory()) continue;
1792
1868
  } catch {
1793
1869
  continue;
1794
1870
  }
1795
1871
  let versions;
1796
1872
  try {
1797
- versions = (0, import_node_fs6.readdirSync)(nameDir);
1873
+ versions = (0, import_node_fs5.readdirSync)(nameDir);
1798
1874
  } catch {
1799
1875
  continue;
1800
1876
  }
@@ -1823,7 +1899,7 @@ async function compile(options) {
1823
1899
  }
1824
1900
  async function compileToFile(options) {
1825
1901
  const result = await compile(options);
1826
- (0, import_node_fs7.writeFileSync)(options.outFile, result.output, "utf-8");
1902
+ (0, import_node_fs6.writeFileSync)(options.outFile, result.output, "utf-8");
1827
1903
  return result;
1828
1904
  }
1829
1905
  async function watch(options) {
@@ -1833,7 +1909,7 @@ async function watch(options) {
1833
1909
  const allPaths = [...filePaths, ...modulePaths];
1834
1910
  const handle = watchFilesystem(allPaths, options.outFile, () => {
1835
1911
  buildOutput(options).then((result) => {
1836
- (0, import_node_fs7.writeFileSync)(options.outFile, result.output, "utf-8");
1912
+ (0, import_node_fs6.writeFileSync)(options.outFile, result.output, "utf-8");
1837
1913
  options.onBuild?.(result);
1838
1914
  }).catch((e) => {
1839
1915
  options.onError?.(e instanceof Error ? e : new Error(String(e)));
@@ -1865,10 +1941,7 @@ async function buildOutput(options) {
1865
1941
  loadSources(story, sourceFilenames, { trim, twee2Compat }, diagnostics, processedFiles);
1866
1942
  loadInlineSources(story, inlineSources, { trim, twee2Compat }, diagnostics);
1867
1943
  if (options.tagAliases) {
1868
- applyTagAliases(story.passages, options.tagAliases);
1869
- }
1870
- const fatalErrors = diagnostics.filter((d) => d.level === "error");
1871
- if (fatalErrors.length > 0 && outputMode === "html") {
1944
+ story.passages = applyTagAliases(story.passages, options.tagAliases);
1872
1945
  }
1873
1946
  let format;
1874
1947
  let formatId = options.formatId ?? "";
@@ -1887,7 +1960,11 @@ async function buildOutput(options) {
1887
1960
  const remoteVersion = story.twine2.formatVersion || "";
1888
1961
  try {
1889
1962
  format = await resolveRemoteFormat(remoteName, remoteVersion, options.formatIndices, options.formatUrls);
1890
- } catch {
1963
+ } catch (e) {
1964
+ diagnostics.push({
1965
+ level: "warning",
1966
+ message: `Remote format fetch failed for "${remoteName}": ${e instanceof Error ? e.message : String(e)}`
1967
+ });
1891
1968
  }
1892
1969
  }
1893
1970
  if (!format) {
@@ -1898,14 +1975,7 @@ async function buildOutput(options) {
1898
1975
  });
1899
1976
  }
1900
1977
  }
1901
- let startName;
1902
- if (options.startPassage) {
1903
- startName = options.startPassage;
1904
- } else if (story.twine2.start) {
1905
- startName = story.twine2.start;
1906
- } else {
1907
- startName = DEFAULT_START_NAME;
1908
- }
1978
+ const startName = options.startPassage || story.twine2.start || DEFAULT_START_NAME;
1909
1979
  if (testMode) {
1910
1980
  story.twine2.options.set("debug", true);
1911
1981
  }
@@ -1924,8 +1994,7 @@ async function buildOutput(options) {
1924
1994
  case "json":
1925
1995
  output = storyToJSON(story);
1926
1996
  break;
1927
- case "html":
1928
- default: {
1997
+ case "html": {
1929
1998
  if (!storyHas(story, startName)) {
1930
1999
  diagnostics.push({
1931
2000
  level: "error",
@@ -1947,9 +2016,13 @@ async function buildOutput(options) {
1947
2016
  output = toTwine1HTML(story, format, startName, diagnostics);
1948
2017
  }
1949
2018
  const modulePaths = options.modules ? getFilenames(options.modules) : [];
1950
- output = modifyHead(output, modulePaths, options.headFile);
2019
+ output = modifyHead(output, modulePaths, options.headFile, diagnostics);
1951
2020
  break;
1952
2021
  }
2022
+ default: {
2023
+ const _exhaustive = outputMode;
2024
+ throw new TweeTsError(`Unhandled output mode: ${_exhaustive}`, diagnostics);
2025
+ }
1953
2026
  }
1954
2027
  const stats = {
1955
2028
  ...getStoryStats(story),
@@ -2085,17 +2158,17 @@ function storyInspect(story) {
2085
2158
  }
2086
2159
 
2087
2160
  // src/config.ts
2088
- var import_node_fs8 = require("fs");
2161
+ var import_node_fs7 = require("fs");
2089
2162
  var import_node_path8 = require("path");
2090
2163
  var CONFIG_FILENAME = "twee-ts.config.json";
2091
2164
  var VALID_OUTPUT_MODES = ["html", "twee3", "twee1", "twine2-archive", "twine1-archive", "json"];
2092
2165
  function loadConfig(dir) {
2093
2166
  const base = dir ?? process.cwd();
2094
2167
  const configPath = (0, import_node_path8.join)(base, CONFIG_FILENAME);
2095
- if (!(0, import_node_fs8.existsSync)(configPath)) return null;
2168
+ if (!(0, import_node_fs7.existsSync)(configPath)) return null;
2096
2169
  let raw;
2097
2170
  try {
2098
- raw = (0, import_node_fs8.readFileSync)(configPath, "utf-8");
2171
+ raw = (0, import_node_fs7.readFileSync)(configPath, "utf-8");
2099
2172
  } catch {
2100
2173
  return null;
2101
2174
  }
@@ -2115,7 +2188,7 @@ function loadConfig(dir) {
2115
2188
  function loadConfigFile(filePath) {
2116
2189
  let raw;
2117
2190
  try {
2118
- raw = (0, import_node_fs8.readFileSync)(filePath, "utf-8");
2191
+ raw = (0, import_node_fs7.readFileSync)(filePath, "utf-8");
2119
2192
  } catch (e) {
2120
2193
  throw new Error(`Cannot read config file ${filePath}: ${e instanceof Error ? e.message : String(e)}`);
2121
2194
  }