@rohal12/twee-ts 1.6.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/twee-ts.js +191 -0
- package/dist/bin/twee-ts.js.map +1 -1
- package/dist/index.cjs +67 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +40 -1
- package/dist/index.d.ts +40 -1
- package/dist/index.js +65 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/bin/twee-ts.js
CHANGED
|
@@ -151,6 +151,9 @@ function hasInfoTags(p) {
|
|
|
151
151
|
function hasInfoName(p) {
|
|
152
152
|
return INFO_PASSAGE_NAMES.has(p.name);
|
|
153
153
|
}
|
|
154
|
+
function isInfoPassage(p) {
|
|
155
|
+
return hasInfoName(p) || hasInfoTags(p);
|
|
156
|
+
}
|
|
154
157
|
function isStoryPassage(p) {
|
|
155
158
|
return !hasInfoName(p) && !hasInfoTags(p);
|
|
156
159
|
}
|
|
@@ -2272,6 +2275,171 @@ function storyToJSON(story) {
|
|
|
2272
2275
|
return JSON.stringify(obj, null, 2);
|
|
2273
2276
|
}
|
|
2274
2277
|
|
|
2278
|
+
// src/inspect.ts
|
|
2279
|
+
function extractLinksFromText(text) {
|
|
2280
|
+
const targets = /* @__PURE__ */ new Set();
|
|
2281
|
+
const wikiLinkRe = /\[\[([^\]]+)\]\]/g;
|
|
2282
|
+
let m;
|
|
2283
|
+
while ((m = wikiLinkRe.exec(text)) !== null) {
|
|
2284
|
+
const content = m[1];
|
|
2285
|
+
const arrowIdx = content.indexOf("->");
|
|
2286
|
+
if (arrowIdx !== -1) {
|
|
2287
|
+
targets.add(content.slice(arrowIdx + 2).trim());
|
|
2288
|
+
continue;
|
|
2289
|
+
}
|
|
2290
|
+
const pipeIdx = content.indexOf("|");
|
|
2291
|
+
if (pipeIdx !== -1) {
|
|
2292
|
+
targets.add(content.slice(pipeIdx + 1).trim());
|
|
2293
|
+
continue;
|
|
2294
|
+
}
|
|
2295
|
+
targets.add(content.trim());
|
|
2296
|
+
}
|
|
2297
|
+
const gotoRe = /<<goto\s+["']([^"']+)["']\s*>>/g;
|
|
2298
|
+
while ((m = gotoRe.exec(text)) !== null) {
|
|
2299
|
+
targets.add(m[1]);
|
|
2300
|
+
}
|
|
2301
|
+
const linkMacroRe = /<<link\s+["'][^"']*["']\s+["']([^"']+)["']\s*>>/g;
|
|
2302
|
+
while ((m = linkMacroRe.exec(text)) !== null) {
|
|
2303
|
+
targets.add(m[1]);
|
|
2304
|
+
}
|
|
2305
|
+
return [...targets];
|
|
2306
|
+
}
|
|
2307
|
+
function storyInspect(story) {
|
|
2308
|
+
const allNames = new Set(story.passages.map((p) => p.name));
|
|
2309
|
+
const passages = [];
|
|
2310
|
+
const storyPassages = [];
|
|
2311
|
+
const infoPassages = [];
|
|
2312
|
+
const tagSet = /* @__PURE__ */ new Set();
|
|
2313
|
+
const passagesByTag = /* @__PURE__ */ new Map();
|
|
2314
|
+
const tagsByPassage = /* @__PURE__ */ new Map();
|
|
2315
|
+
const links = /* @__PURE__ */ new Map();
|
|
2316
|
+
for (const p of story.passages) {
|
|
2317
|
+
passages.push(p.name);
|
|
2318
|
+
if (isStoryPassage(p)) {
|
|
2319
|
+
storyPassages.push(p.name);
|
|
2320
|
+
}
|
|
2321
|
+
if (isInfoPassage(p)) {
|
|
2322
|
+
infoPassages.push(p.name);
|
|
2323
|
+
}
|
|
2324
|
+
tagsByPassage.set(p.name, [...p.tags]);
|
|
2325
|
+
for (const tag of p.tags) {
|
|
2326
|
+
tagSet.add(tag);
|
|
2327
|
+
let list = passagesByTag.get(tag);
|
|
2328
|
+
if (!list) {
|
|
2329
|
+
list = [];
|
|
2330
|
+
passagesByTag.set(tag, list);
|
|
2331
|
+
}
|
|
2332
|
+
list.push(p.name);
|
|
2333
|
+
}
|
|
2334
|
+
const targets = extractLinksFromText(p.text);
|
|
2335
|
+
links.set(p.name, targets);
|
|
2336
|
+
}
|
|
2337
|
+
const brokenLinks = [];
|
|
2338
|
+
for (const [from, targets] of links) {
|
|
2339
|
+
for (const to of targets) {
|
|
2340
|
+
if (!allNames.has(to)) {
|
|
2341
|
+
brokenLinks.push({ from, to });
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
const deadEnds = [];
|
|
2346
|
+
for (const name of storyPassages) {
|
|
2347
|
+
const targets = links.get(name);
|
|
2348
|
+
if (!targets || targets.length === 0) {
|
|
2349
|
+
deadEnds.push(name);
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
const start = story.twine2.start || "Start";
|
|
2353
|
+
const linkedTo = /* @__PURE__ */ new Set();
|
|
2354
|
+
for (const targets of links.values()) {
|
|
2355
|
+
for (const t of targets) {
|
|
2356
|
+
linkedTo.add(t);
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
const orphans = [];
|
|
2360
|
+
for (const name of storyPassages) {
|
|
2361
|
+
if (name !== start && !linkedTo.has(name)) {
|
|
2362
|
+
orphans.push(name);
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
2365
|
+
return {
|
|
2366
|
+
passages,
|
|
2367
|
+
storyPassages,
|
|
2368
|
+
infoPassages,
|
|
2369
|
+
tags: [...tagSet].sort(),
|
|
2370
|
+
passagesByTag,
|
|
2371
|
+
tagsByPassage,
|
|
2372
|
+
links,
|
|
2373
|
+
brokenLinks,
|
|
2374
|
+
deadEnds,
|
|
2375
|
+
orphans,
|
|
2376
|
+
start
|
|
2377
|
+
};
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
// src/lint.ts
|
|
2381
|
+
async function lint(options) {
|
|
2382
|
+
const result = await compile({ ...options, outputMode: "json" });
|
|
2383
|
+
const map = storyInspect(result.story);
|
|
2384
|
+
return {
|
|
2385
|
+
diagnostics: result.diagnostics,
|
|
2386
|
+
stats: result.stats,
|
|
2387
|
+
formatName: result.story.twine2.format,
|
|
2388
|
+
formatVersion: result.story.twine2.formatVersion,
|
|
2389
|
+
start: map.start,
|
|
2390
|
+
passages: map.passages.length,
|
|
2391
|
+
storyPassages: map.storyPassages.length,
|
|
2392
|
+
infoPassages: map.infoPassages.length,
|
|
2393
|
+
brokenLinks: map.brokenLinks,
|
|
2394
|
+
deadEnds: map.deadEnds,
|
|
2395
|
+
orphans: map.orphans
|
|
2396
|
+
};
|
|
2397
|
+
}
|
|
2398
|
+
function formatLintReport(result) {
|
|
2399
|
+
const lines = [];
|
|
2400
|
+
const formatStr = result.formatName ? `${result.formatName}${result.formatVersion ? " " + result.formatVersion : ""}` : "unknown";
|
|
2401
|
+
lines.push(`Format: ${formatStr}`);
|
|
2402
|
+
lines.push(
|
|
2403
|
+
`Passages: ${result.passages} total (${result.storyPassages} story, ${result.infoPassages} info), ${result.stats.words.toLocaleString()} words, ${result.stats.files.length} files`
|
|
2404
|
+
);
|
|
2405
|
+
lines.push(`Start: ${result.start}`);
|
|
2406
|
+
if (result.brokenLinks.length > 0) {
|
|
2407
|
+
lines.push("");
|
|
2408
|
+
lines.push(`Broken links (${result.brokenLinks.length}):`);
|
|
2409
|
+
for (const link of result.brokenLinks) {
|
|
2410
|
+
lines.push(` ${link.from} -> ${link.to} (passage "${link.to}" does not exist)`);
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
if (result.deadEnds.length > 0) {
|
|
2414
|
+
lines.push("");
|
|
2415
|
+
lines.push(`Dead ends (${result.deadEnds.length}): ${result.deadEnds.join(", ")}`);
|
|
2416
|
+
}
|
|
2417
|
+
if (result.orphans.length > 0) {
|
|
2418
|
+
lines.push("");
|
|
2419
|
+
lines.push(`Orphans (${result.orphans.length}): ${result.orphans.join(", ")}`);
|
|
2420
|
+
}
|
|
2421
|
+
const errors = result.diagnostics.filter((d) => d.level === "error");
|
|
2422
|
+
const warnings = result.diagnostics.filter((d) => d.level === "warning");
|
|
2423
|
+
if (errors.length > 0 || warnings.length > 0) {
|
|
2424
|
+
lines.push("");
|
|
2425
|
+
lines.push(`Diagnostics: ${errors.length} error(s), ${warnings.length} warning(s)`);
|
|
2426
|
+
for (const d of errors) {
|
|
2427
|
+
lines.push(` error: ${d.message}`);
|
|
2428
|
+
}
|
|
2429
|
+
for (const d of warnings) {
|
|
2430
|
+
lines.push(` warning: ${d.message}`);
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
lines.push("");
|
|
2434
|
+
const hasErrors = errors.length > 0 || result.brokenLinks.length > 0;
|
|
2435
|
+
if (hasErrors) {
|
|
2436
|
+
lines.push("Lint failed.");
|
|
2437
|
+
} else {
|
|
2438
|
+
lines.push("Lint passed.");
|
|
2439
|
+
}
|
|
2440
|
+
return lines.join("\n");
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2275
2443
|
// src/config.ts
|
|
2276
2444
|
import { readFileSync as readFileSync4, existsSync as existsSync2 } from "fs";
|
|
2277
2445
|
import { join as join5 } from "path";
|
|
@@ -2388,6 +2556,7 @@ var { values, positionals } = parseArgs({
|
|
|
2388
2556
|
"archive-twine1": { type: "boolean" },
|
|
2389
2557
|
"twee2-compat": { type: "boolean" },
|
|
2390
2558
|
"no-trim": { type: "boolean" },
|
|
2559
|
+
lint: { type: "boolean" },
|
|
2391
2560
|
test: { type: "boolean", short: "t" },
|
|
2392
2561
|
watch: { type: "boolean", short: "w" },
|
|
2393
2562
|
"log-stats": { type: "boolean", short: "l" },
|
|
@@ -2456,6 +2625,27 @@ async function main() {
|
|
|
2456
2625
|
tagAliases[pair.slice(0, eq)] = pair.slice(eq + 1);
|
|
2457
2626
|
}
|
|
2458
2627
|
}
|
|
2628
|
+
if (values.lint) {
|
|
2629
|
+
const lintResult = await lint({
|
|
2630
|
+
sources,
|
|
2631
|
+
formatId: values.format ?? config?.formatId,
|
|
2632
|
+
startPassage: values.start ?? config?.startPassage,
|
|
2633
|
+
formatPaths: config?.formatPaths,
|
|
2634
|
+
modules: values.module ?? config?.modules,
|
|
2635
|
+
headFile: values.head ?? config?.headFile,
|
|
2636
|
+
trim: values["no-trim"] ? false : config?.trim ?? true,
|
|
2637
|
+
twee2Compat: values["twee2-compat"] ?? config?.twee2Compat ?? false,
|
|
2638
|
+
testMode: values.test ?? config?.testMode ?? false,
|
|
2639
|
+
formatIndices: values["format-index"] ?? config?.formatIndices,
|
|
2640
|
+
formatUrls: values["format-url"] ?? config?.formatUrls,
|
|
2641
|
+
noRemote: values["no-remote"] ?? config?.noRemote ?? false,
|
|
2642
|
+
tagAliases,
|
|
2643
|
+
sourceInfo: values["source-info"] ?? config?.sourceInfo ?? false
|
|
2644
|
+
});
|
|
2645
|
+
console.log(formatLintReport(lintResult));
|
|
2646
|
+
const hasErrors = lintResult.brokenLinks.length > 0 || lintResult.diagnostics.some((d) => d.level === "error");
|
|
2647
|
+
process.exit(hasErrors ? 1 : 0);
|
|
2648
|
+
}
|
|
2459
2649
|
const compileOptions = {
|
|
2460
2650
|
sources,
|
|
2461
2651
|
outputMode,
|
|
@@ -2588,6 +2778,7 @@ Options:
|
|
|
2588
2778
|
--archive-twine1 Output as Twine 1 archive
|
|
2589
2779
|
--json Output as JSON
|
|
2590
2780
|
--twee2-compat Enable Twee2 syntax compatibility
|
|
2781
|
+
--lint Lint story structure (broken links, dead ends, orphans)
|
|
2591
2782
|
--no-trim Don't trim passage whitespace
|
|
2592
2783
|
-t, --test Enable test/debug mode
|
|
2593
2784
|
-w, --watch Watch for changes and rebuild
|