@rohal12/twee-ts 1.6.0 → 1.8.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 +329 -2
- package/dist/bin/twee-ts.js.map +1 -1
- package/dist/{chunk-HXL3JC4L.js → chunk-N2OR5KHL.js} +2 -2
- package/dist/{chunk-HXL3JC4L.js.map → chunk-N2OR5KHL.js.map} +1 -1
- package/dist/index.cjs +142 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +56 -1
- package/dist/index.d.ts +56 -1
- package/dist/index.js +138 -1
- package/dist/index.js.map +1 -1
- package/dist/plugins/rollup.js +1 -1
- package/dist/plugins/vite.js +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
|
}
|
|
@@ -1841,7 +1844,7 @@ function modifyHead(html, modulePaths, headFile, diagnostics) {
|
|
|
1841
1844
|
}
|
|
1842
1845
|
|
|
1843
1846
|
// src/remote-formats.ts
|
|
1844
|
-
import { mkdirSync, readFileSync as readFileSync3, writeFileSync, existsSync, readdirSync as readdirSync3, statSync as statSync4 } from "fs";
|
|
1847
|
+
import { mkdirSync, readFileSync as readFileSync3, writeFileSync, existsSync, readdirSync as readdirSync3, statSync as statSync4, rmSync } from "fs";
|
|
1845
1848
|
import { join as join4 } from "path";
|
|
1846
1849
|
import { homedir as homedir2 } from "os";
|
|
1847
1850
|
var DEFAULT_SFA_INDICES = [
|
|
@@ -2072,6 +2075,75 @@ function discoverCachedFormats() {
|
|
|
2072
2075
|
}
|
|
2073
2076
|
return formats;
|
|
2074
2077
|
}
|
|
2078
|
+
function listCachedFormats() {
|
|
2079
|
+
const cacheDir = getCacheDir();
|
|
2080
|
+
const entries = [];
|
|
2081
|
+
try {
|
|
2082
|
+
if (!existsSync(cacheDir)) return entries;
|
|
2083
|
+
} catch {
|
|
2084
|
+
return entries;
|
|
2085
|
+
}
|
|
2086
|
+
let names;
|
|
2087
|
+
try {
|
|
2088
|
+
names = readdirSync3(cacheDir);
|
|
2089
|
+
} catch {
|
|
2090
|
+
return entries;
|
|
2091
|
+
}
|
|
2092
|
+
for (const name of names) {
|
|
2093
|
+
const nameDir = join4(cacheDir, name);
|
|
2094
|
+
try {
|
|
2095
|
+
if (!statSync4(nameDir).isDirectory()) continue;
|
|
2096
|
+
} catch {
|
|
2097
|
+
continue;
|
|
2098
|
+
}
|
|
2099
|
+
let versions;
|
|
2100
|
+
try {
|
|
2101
|
+
versions = readdirSync3(nameDir);
|
|
2102
|
+
} catch {
|
|
2103
|
+
continue;
|
|
2104
|
+
}
|
|
2105
|
+
for (const version of versions) {
|
|
2106
|
+
const formatPath = join4(nameDir, version, "format.js");
|
|
2107
|
+
try {
|
|
2108
|
+
const stat = statSync4(formatPath);
|
|
2109
|
+
entries.push({
|
|
2110
|
+
name,
|
|
2111
|
+
version,
|
|
2112
|
+
sizeBytes: stat.size,
|
|
2113
|
+
modifiedAt: stat.mtime
|
|
2114
|
+
});
|
|
2115
|
+
} catch {
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
return entries;
|
|
2120
|
+
}
|
|
2121
|
+
function clearCachedFormats(name) {
|
|
2122
|
+
const cacheDir = getCacheDir();
|
|
2123
|
+
if (!existsSync(cacheDir)) return 0;
|
|
2124
|
+
if (!name) {
|
|
2125
|
+
const entries = listCachedFormats();
|
|
2126
|
+
const count2 = entries.length;
|
|
2127
|
+
rmSync(cacheDir, { recursive: true, force: true });
|
|
2128
|
+
return count2;
|
|
2129
|
+
}
|
|
2130
|
+
const nameDir = join4(cacheDir, name);
|
|
2131
|
+
if (!existsSync(nameDir)) return 0;
|
|
2132
|
+
let versions;
|
|
2133
|
+
try {
|
|
2134
|
+
versions = readdirSync3(nameDir);
|
|
2135
|
+
} catch {
|
|
2136
|
+
return 0;
|
|
2137
|
+
}
|
|
2138
|
+
const count = versions.length;
|
|
2139
|
+
rmSync(nameDir, { recursive: true, force: true });
|
|
2140
|
+
return count;
|
|
2141
|
+
}
|
|
2142
|
+
function getCacheSize() {
|
|
2143
|
+
const entries = listCachedFormats();
|
|
2144
|
+
const totalBytes = entries.reduce((sum, e) => sum + e.sizeBytes, 0);
|
|
2145
|
+
return { totalBytes, count: entries.length };
|
|
2146
|
+
}
|
|
2075
2147
|
|
|
2076
2148
|
// src/compiler.ts
|
|
2077
2149
|
var CREATOR_NAME3 = "Twee-ts";
|
|
@@ -2272,6 +2344,171 @@ function storyToJSON(story) {
|
|
|
2272
2344
|
return JSON.stringify(obj, null, 2);
|
|
2273
2345
|
}
|
|
2274
2346
|
|
|
2347
|
+
// src/inspect.ts
|
|
2348
|
+
function extractLinksFromText(text) {
|
|
2349
|
+
const targets = /* @__PURE__ */ new Set();
|
|
2350
|
+
const wikiLinkRe = /\[\[([^\]]+)\]\]/g;
|
|
2351
|
+
let m;
|
|
2352
|
+
while ((m = wikiLinkRe.exec(text)) !== null) {
|
|
2353
|
+
const content = m[1];
|
|
2354
|
+
const arrowIdx = content.indexOf("->");
|
|
2355
|
+
if (arrowIdx !== -1) {
|
|
2356
|
+
targets.add(content.slice(arrowIdx + 2).trim());
|
|
2357
|
+
continue;
|
|
2358
|
+
}
|
|
2359
|
+
const pipeIdx = content.indexOf("|");
|
|
2360
|
+
if (pipeIdx !== -1) {
|
|
2361
|
+
targets.add(content.slice(pipeIdx + 1).trim());
|
|
2362
|
+
continue;
|
|
2363
|
+
}
|
|
2364
|
+
targets.add(content.trim());
|
|
2365
|
+
}
|
|
2366
|
+
const gotoRe = /<<goto\s+["']([^"']+)["']\s*>>/g;
|
|
2367
|
+
while ((m = gotoRe.exec(text)) !== null) {
|
|
2368
|
+
targets.add(m[1]);
|
|
2369
|
+
}
|
|
2370
|
+
const linkMacroRe = /<<link\s+["'][^"']*["']\s+["']([^"']+)["']\s*>>/g;
|
|
2371
|
+
while ((m = linkMacroRe.exec(text)) !== null) {
|
|
2372
|
+
targets.add(m[1]);
|
|
2373
|
+
}
|
|
2374
|
+
return [...targets];
|
|
2375
|
+
}
|
|
2376
|
+
function storyInspect(story) {
|
|
2377
|
+
const allNames = new Set(story.passages.map((p) => p.name));
|
|
2378
|
+
const passages = [];
|
|
2379
|
+
const storyPassages = [];
|
|
2380
|
+
const infoPassages = [];
|
|
2381
|
+
const tagSet = /* @__PURE__ */ new Set();
|
|
2382
|
+
const passagesByTag = /* @__PURE__ */ new Map();
|
|
2383
|
+
const tagsByPassage = /* @__PURE__ */ new Map();
|
|
2384
|
+
const links = /* @__PURE__ */ new Map();
|
|
2385
|
+
for (const p of story.passages) {
|
|
2386
|
+
passages.push(p.name);
|
|
2387
|
+
if (isStoryPassage(p)) {
|
|
2388
|
+
storyPassages.push(p.name);
|
|
2389
|
+
}
|
|
2390
|
+
if (isInfoPassage(p)) {
|
|
2391
|
+
infoPassages.push(p.name);
|
|
2392
|
+
}
|
|
2393
|
+
tagsByPassage.set(p.name, [...p.tags]);
|
|
2394
|
+
for (const tag of p.tags) {
|
|
2395
|
+
tagSet.add(tag);
|
|
2396
|
+
let list = passagesByTag.get(tag);
|
|
2397
|
+
if (!list) {
|
|
2398
|
+
list = [];
|
|
2399
|
+
passagesByTag.set(tag, list);
|
|
2400
|
+
}
|
|
2401
|
+
list.push(p.name);
|
|
2402
|
+
}
|
|
2403
|
+
const targets = extractLinksFromText(p.text);
|
|
2404
|
+
links.set(p.name, targets);
|
|
2405
|
+
}
|
|
2406
|
+
const brokenLinks = [];
|
|
2407
|
+
for (const [from, targets] of links) {
|
|
2408
|
+
for (const to of targets) {
|
|
2409
|
+
if (!allNames.has(to)) {
|
|
2410
|
+
brokenLinks.push({ from, to });
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
const deadEnds = [];
|
|
2415
|
+
for (const name of storyPassages) {
|
|
2416
|
+
const targets = links.get(name);
|
|
2417
|
+
if (!targets || targets.length === 0) {
|
|
2418
|
+
deadEnds.push(name);
|
|
2419
|
+
}
|
|
2420
|
+
}
|
|
2421
|
+
const start = story.twine2.start || "Start";
|
|
2422
|
+
const linkedTo = /* @__PURE__ */ new Set();
|
|
2423
|
+
for (const targets of links.values()) {
|
|
2424
|
+
for (const t of targets) {
|
|
2425
|
+
linkedTo.add(t);
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2428
|
+
const orphans = [];
|
|
2429
|
+
for (const name of storyPassages) {
|
|
2430
|
+
if (name !== start && !linkedTo.has(name)) {
|
|
2431
|
+
orphans.push(name);
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
return {
|
|
2435
|
+
passages,
|
|
2436
|
+
storyPassages,
|
|
2437
|
+
infoPassages,
|
|
2438
|
+
tags: [...tagSet].sort(),
|
|
2439
|
+
passagesByTag,
|
|
2440
|
+
tagsByPassage,
|
|
2441
|
+
links,
|
|
2442
|
+
brokenLinks,
|
|
2443
|
+
deadEnds,
|
|
2444
|
+
orphans,
|
|
2445
|
+
start
|
|
2446
|
+
};
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2449
|
+
// src/lint.ts
|
|
2450
|
+
async function lint(options) {
|
|
2451
|
+
const result = await compile({ ...options, outputMode: "json" });
|
|
2452
|
+
const map = storyInspect(result.story);
|
|
2453
|
+
return {
|
|
2454
|
+
diagnostics: result.diagnostics,
|
|
2455
|
+
stats: result.stats,
|
|
2456
|
+
formatName: result.story.twine2.format,
|
|
2457
|
+
formatVersion: result.story.twine2.formatVersion,
|
|
2458
|
+
start: map.start,
|
|
2459
|
+
passages: map.passages.length,
|
|
2460
|
+
storyPassages: map.storyPassages.length,
|
|
2461
|
+
infoPassages: map.infoPassages.length,
|
|
2462
|
+
brokenLinks: map.brokenLinks,
|
|
2463
|
+
deadEnds: map.deadEnds,
|
|
2464
|
+
orphans: map.orphans
|
|
2465
|
+
};
|
|
2466
|
+
}
|
|
2467
|
+
function formatLintReport(result) {
|
|
2468
|
+
const lines = [];
|
|
2469
|
+
const formatStr = result.formatName ? `${result.formatName}${result.formatVersion ? " " + result.formatVersion : ""}` : "unknown";
|
|
2470
|
+
lines.push(`Format: ${formatStr}`);
|
|
2471
|
+
lines.push(
|
|
2472
|
+
`Passages: ${result.passages} total (${result.storyPassages} story, ${result.infoPassages} info), ${result.stats.words.toLocaleString()} words, ${result.stats.files.length} files`
|
|
2473
|
+
);
|
|
2474
|
+
lines.push(`Start: ${result.start}`);
|
|
2475
|
+
if (result.brokenLinks.length > 0) {
|
|
2476
|
+
lines.push("");
|
|
2477
|
+
lines.push(`Broken links (${result.brokenLinks.length}):`);
|
|
2478
|
+
for (const link of result.brokenLinks) {
|
|
2479
|
+
lines.push(` ${link.from} -> ${link.to} (passage "${link.to}" does not exist)`);
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
if (result.deadEnds.length > 0) {
|
|
2483
|
+
lines.push("");
|
|
2484
|
+
lines.push(`Dead ends (${result.deadEnds.length}): ${result.deadEnds.join(", ")}`);
|
|
2485
|
+
}
|
|
2486
|
+
if (result.orphans.length > 0) {
|
|
2487
|
+
lines.push("");
|
|
2488
|
+
lines.push(`Orphans (${result.orphans.length}): ${result.orphans.join(", ")}`);
|
|
2489
|
+
}
|
|
2490
|
+
const errors = result.diagnostics.filter((d) => d.level === "error");
|
|
2491
|
+
const warnings = result.diagnostics.filter((d) => d.level === "warning");
|
|
2492
|
+
if (errors.length > 0 || warnings.length > 0) {
|
|
2493
|
+
lines.push("");
|
|
2494
|
+
lines.push(`Diagnostics: ${errors.length} error(s), ${warnings.length} warning(s)`);
|
|
2495
|
+
for (const d of errors) {
|
|
2496
|
+
lines.push(` error: ${d.message}`);
|
|
2497
|
+
}
|
|
2498
|
+
for (const d of warnings) {
|
|
2499
|
+
lines.push(` warning: ${d.message}`);
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
lines.push("");
|
|
2503
|
+
const hasErrors = errors.length > 0 || result.brokenLinks.length > 0;
|
|
2504
|
+
if (hasErrors) {
|
|
2505
|
+
lines.push("Lint failed.");
|
|
2506
|
+
} else {
|
|
2507
|
+
lines.push("Lint passed.");
|
|
2508
|
+
}
|
|
2509
|
+
return lines.join("\n");
|
|
2510
|
+
}
|
|
2511
|
+
|
|
2275
2512
|
// src/config.ts
|
|
2276
2513
|
import { readFileSync as readFileSync4, existsSync as existsSync2 } from "fs";
|
|
2277
2514
|
import { join as join5 } from "path";
|
|
@@ -2388,6 +2625,7 @@ var { values, positionals } = parseArgs({
|
|
|
2388
2625
|
"archive-twine1": { type: "boolean" },
|
|
2389
2626
|
"twee2-compat": { type: "boolean" },
|
|
2390
2627
|
"no-trim": { type: "boolean" },
|
|
2628
|
+
lint: { type: "boolean" },
|
|
2391
2629
|
test: { type: "boolean", short: "t" },
|
|
2392
2630
|
watch: { type: "boolean", short: "w" },
|
|
2393
2631
|
"log-stats": { type: "boolean", short: "l" },
|
|
@@ -2424,6 +2662,10 @@ async function main() {
|
|
|
2424
2662
|
runInit();
|
|
2425
2663
|
return;
|
|
2426
2664
|
}
|
|
2665
|
+
if (positionals[0] === "cache") {
|
|
2666
|
+
runCache(positionals.slice(1));
|
|
2667
|
+
return;
|
|
2668
|
+
}
|
|
2427
2669
|
let config = null;
|
|
2428
2670
|
if (!values["no-config"]) {
|
|
2429
2671
|
if (values.config) {
|
|
@@ -2456,6 +2698,27 @@ async function main() {
|
|
|
2456
2698
|
tagAliases[pair.slice(0, eq)] = pair.slice(eq + 1);
|
|
2457
2699
|
}
|
|
2458
2700
|
}
|
|
2701
|
+
if (values.lint) {
|
|
2702
|
+
const lintResult = await lint({
|
|
2703
|
+
sources,
|
|
2704
|
+
formatId: values.format ?? config?.formatId,
|
|
2705
|
+
startPassage: values.start ?? config?.startPassage,
|
|
2706
|
+
formatPaths: config?.formatPaths,
|
|
2707
|
+
modules: values.module ?? config?.modules,
|
|
2708
|
+
headFile: values.head ?? config?.headFile,
|
|
2709
|
+
trim: values["no-trim"] ? false : config?.trim ?? true,
|
|
2710
|
+
twee2Compat: values["twee2-compat"] ?? config?.twee2Compat ?? false,
|
|
2711
|
+
testMode: values.test ?? config?.testMode ?? false,
|
|
2712
|
+
formatIndices: values["format-index"] ?? config?.formatIndices,
|
|
2713
|
+
formatUrls: values["format-url"] ?? config?.formatUrls,
|
|
2714
|
+
noRemote: values["no-remote"] ?? config?.noRemote ?? false,
|
|
2715
|
+
tagAliases,
|
|
2716
|
+
sourceInfo: values["source-info"] ?? config?.sourceInfo ?? false
|
|
2717
|
+
});
|
|
2718
|
+
console.log(formatLintReport(lintResult));
|
|
2719
|
+
const hasErrors = lintResult.brokenLinks.length > 0 || lintResult.diagnostics.some((d) => d.level === "error");
|
|
2720
|
+
process.exit(hasErrors ? 1 : 0);
|
|
2721
|
+
}
|
|
2459
2722
|
const compileOptions = {
|
|
2460
2723
|
sources,
|
|
2461
2724
|
outputMode,
|
|
@@ -2571,6 +2834,63 @@ This is the starting passage. Edit this file to begin writing your story.
|
|
|
2571
2834
|
console.log(" src/Start.tw");
|
|
2572
2835
|
console.log("\nRun: npx @rohal12/twee-ts");
|
|
2573
2836
|
}
|
|
2837
|
+
function formatBytes(bytes) {
|
|
2838
|
+
if (bytes < 1024) return `${bytes}B`;
|
|
2839
|
+
const kb = bytes / 1024;
|
|
2840
|
+
if (kb < 1024) return `${Math.round(kb)}K`;
|
|
2841
|
+
const mb = kb / 1024;
|
|
2842
|
+
return `${mb.toFixed(1)}M`;
|
|
2843
|
+
}
|
|
2844
|
+
function runCache(args) {
|
|
2845
|
+
const subcommand = args[0];
|
|
2846
|
+
switch (subcommand) {
|
|
2847
|
+
case "list": {
|
|
2848
|
+
const entries = listCachedFormats();
|
|
2849
|
+
if (entries.length === 0) {
|
|
2850
|
+
console.log("No cached formats.");
|
|
2851
|
+
return;
|
|
2852
|
+
}
|
|
2853
|
+
for (const e of entries) {
|
|
2854
|
+
const date = e.modifiedAt.toISOString().slice(0, 10);
|
|
2855
|
+
console.log(`${e.name.padEnd(16)} ${e.version.padEnd(10)} ${formatBytes(e.sizeBytes).padStart(6)} ${date}`);
|
|
2856
|
+
}
|
|
2857
|
+
return;
|
|
2858
|
+
}
|
|
2859
|
+
case "clear": {
|
|
2860
|
+
const name = args[1];
|
|
2861
|
+
const count = clearCachedFormats(name);
|
|
2862
|
+
if (count === 0) {
|
|
2863
|
+
console.log(name ? `No cached formats matching "${name}".` : "Cache is already empty.");
|
|
2864
|
+
} else {
|
|
2865
|
+
console.log(`Cleared ${count} cached format${count === 1 ? "" : "s"}.`);
|
|
2866
|
+
}
|
|
2867
|
+
return;
|
|
2868
|
+
}
|
|
2869
|
+
case "size": {
|
|
2870
|
+
const { totalBytes, count } = getCacheSize();
|
|
2871
|
+
if (count === 0) {
|
|
2872
|
+
console.log("Cache is empty.");
|
|
2873
|
+
} else {
|
|
2874
|
+
console.log(`Total: ${formatBytes(totalBytes)} (${count} format${count === 1 ? "" : "s"})`);
|
|
2875
|
+
}
|
|
2876
|
+
return;
|
|
2877
|
+
}
|
|
2878
|
+
case "path": {
|
|
2879
|
+
console.log(getCacheDir());
|
|
2880
|
+
return;
|
|
2881
|
+
}
|
|
2882
|
+
default: {
|
|
2883
|
+
console.error(`Usage: twee-ts cache <list|clear|size|path>
|
|
2884
|
+
|
|
2885
|
+
list List cached formats with name, version, size
|
|
2886
|
+
clear Delete all cached formats
|
|
2887
|
+
clear <name> Delete cached formats matching name
|
|
2888
|
+
size Show total cache size
|
|
2889
|
+
path Print cache directory path`);
|
|
2890
|
+
process.exit(subcommand ? 1 : 0);
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2574
2894
|
function printUsage() {
|
|
2575
2895
|
console.log(`twee-ts v${VERSION} \u2014 TypeScript Twee-to-HTML compiler
|
|
2576
2896
|
|
|
@@ -2588,6 +2908,7 @@ Options:
|
|
|
2588
2908
|
--archive-twine1 Output as Twine 1 archive
|
|
2589
2909
|
--json Output as JSON
|
|
2590
2910
|
--twee2-compat Enable Twee2 syntax compatibility
|
|
2911
|
+
--lint Lint story structure (broken links, dead ends, orphans)
|
|
2591
2912
|
--no-trim Don't trim passage whitespace
|
|
2592
2913
|
-t, --test Enable test/debug mode
|
|
2593
2914
|
-w, --watch Watch for changes and rebuild
|
|
@@ -2603,7 +2924,13 @@ Options:
|
|
|
2603
2924
|
-c, --config <file> Config file path (default: ${CONFIG_FILENAME})
|
|
2604
2925
|
--no-config Skip config file loading
|
|
2605
2926
|
-h, --help Show this help
|
|
2606
|
-
-v, --version Show version
|
|
2927
|
+
-v, --version Show version
|
|
2928
|
+
|
|
2929
|
+
Subcommands:
|
|
2930
|
+
cache list List cached remote formats
|
|
2931
|
+
cache clear [name] Clear cached formats (all or by name)
|
|
2932
|
+
cache size Show total cache size
|
|
2933
|
+
cache path Print cache directory path`);
|
|
2607
2934
|
}
|
|
2608
2935
|
main().catch((err) => {
|
|
2609
2936
|
console.error(err instanceof Error ? err.message : String(err));
|