@rohal12/twee-ts 1.17.0 → 1.18.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.d.cts CHANGED
@@ -10,13 +10,17 @@ type InlineSource = {
10
10
  };
11
11
  type SourceInput = string | InlineSource;
12
12
  interface CompileOptions {
13
- /** Files, directories, or inline sources to compile. */
14
- sources: SourceInput[];
13
+ /**
14
+ * Files, directories, or inline sources to compile. Directories are walked
15
+ * recursively; inside one, a symbolic link to a file is read, but a link to a
16
+ * directory is not followed (as in Tweego).
17
+ */
18
+ sources: readonly SourceInput[];
15
19
  /**
16
20
  * Glob patterns for files to leave out of `sources`, matched against each file's
17
21
  * path relative to the working directory (as `stats.files` lists it). Modules are not affected.
18
22
  */
19
- exclude?: string[];
23
+ exclude?: readonly string[];
20
24
  /** Output mode. Default: 'html'. */
21
25
  outputMode?: OutputMode;
22
26
  /** Story format directory ID (e.g. 'sugarcube-2'). */
@@ -24,11 +28,11 @@ interface CompileOptions {
24
28
  /** Name of the starting passage. Default: 'Start'. */
25
29
  startPassage?: string;
26
30
  /** Extra directories to search for story formats. */
27
- formatPaths?: string[];
31
+ formatPaths?: readonly string[];
28
32
  /** Also search TWEEGO_PATH env for formats. Default: true. */
29
33
  useTweegoPath?: boolean;
30
34
  /** Module files to inject into <head>. */
31
- modules?: string[];
35
+ modules?: readonly string[];
32
36
  /** Raw HTML file to append to <head>. */
33
37
  headFile?: string;
34
38
  /** Trim passage whitespace. Default: true. */
@@ -38,11 +42,24 @@ interface CompileOptions {
38
42
  /** Enable debug/test mode option. Default: false. */
39
43
  testMode?: boolean;
40
44
  /** URLs to SFA-compatible index.json files for remote format lookup. */
41
- formatIndices?: string[];
42
- /** Direct URLs to format.js files. */
43
- formatUrls?: string[];
45
+ formatIndices?: readonly string[];
46
+ /**
47
+ * Direct URLs to format.js files. Each URL's download is cached under that URL, and is
48
+ * looked up before the downloads shared by name and version.
49
+ */
50
+ formatUrls?: readonly string[];
44
51
  /** Disable remote format fetching. Default: false. */
45
52
  noRemote?: boolean;
53
+ /**
54
+ * Cancels the compile. Story format requests still in progress are aborted, and the compile
55
+ * rejects with the signal's reason. `watch()` aborts its builds, and stops watching, when it aborts.
56
+ */
57
+ signal?: AbortSignal;
58
+ /**
59
+ * Milliseconds each story format request (an index or a format.js) may take before it fails
60
+ * with a warning and the next source is tried. 0 turns the limit off. Default: 30000.
61
+ */
62
+ formatFetchTimeout?: number;
46
63
  /** Map alias tags to canonical special tags (e.g. { library: 'script' }). */
47
64
  tagAliases?: Record<string, string>;
48
65
  /** Emit source file and line as data- attributes on passage elements. Default: false. */
@@ -51,13 +68,25 @@ interface CompileOptions {
51
68
  wordCountMethod?: WordCountMethod;
52
69
  }
53
70
  interface CompileToFileOptions extends CompileOptions {
54
- /** Output file path. */
71
+ /**
72
+ * Output file path. It is never read as a source, even inside a source folder or
73
+ * reached through a symbolic link. Naming it as a source, a module or the head
74
+ * file is a TweeTsError, and nothing is written.
75
+ */
55
76
  outFile: string;
56
77
  }
57
78
  interface WatchOptions extends CompileToFileOptions {
58
- /** Called after each build is written, including one whose diagnostics report errors. */
79
+ /**
80
+ * Called after each build is written, including one whose diagnostics report errors. Builds
81
+ * run one at a time and are reported in order, each as it finishes, also while changes keep
82
+ * arriving: the changes made during a build go into one follow-up build after it.
83
+ */
59
84
  onBuild?: (result: CompileResult) => void;
60
- /** Called when a build fails with a fatal error (nothing is written). */
85
+ /**
86
+ * Called when a build fails with a fatal error (nothing is written), when `onBuild` throws,
87
+ * and when a watched path can't be watched (the other paths are still watched). An exception
88
+ * thrown here is reported to the console, and watching goes on.
89
+ */
61
90
  onError?: (error: Error) => void;
62
91
  }
63
92
  type Diagnostic = {
@@ -166,8 +195,10 @@ interface InspectOptions {
166
195
  * Check link destinations against this output's passage rules. A link to a passage that the
167
196
  * output leaves out (in Twine 2: `script`, `stylesheet` and `Twine.private` passages,
168
197
  * StoryData, StoryTitle and an empty StorySettings; in Twine 1: `Twine.private` passages) is
169
- * then a broken link, and says why. Without a target, a link is broken only when no passage
170
- * has its name.
198
+ * then a broken link, and says why. The passages it leaves out also give no links, as their
199
+ * text never reaches the player, except script passages, which Twine 2 output runs. Without a
200
+ * target, every source passage gives links, and a link is broken only when no passage has its
201
+ * name.
171
202
  */
172
203
  readonly target?: PassageOutputTarget;
173
204
  }
@@ -222,6 +253,13 @@ interface SFAIndex {
222
253
  twine1: SFAIndexEntry[];
223
254
  twine2: SFAIndexEntry[];
224
255
  }
256
+ /** Options for the network requests that fetch story formats and their indices. */
257
+ interface RemoteFetchOptions {
258
+ /** Aborts the requests; the call then rejects with the signal's reason. */
259
+ readonly signal?: AbortSignal;
260
+ /** Milliseconds each request may take before it fails. 0 turns the limit off. Default: 30000. */
261
+ readonly timeout?: number;
262
+ }
225
263
  interface FileCacheEntry {
226
264
  readonly mtimeMs: number;
227
265
  /**
@@ -250,6 +288,8 @@ interface TweeTsConfig {
250
288
  twee2Compat?: boolean;
251
289
  testMode?: boolean;
252
290
  noRemote?: boolean;
291
+ /** Milliseconds each story format request may take. 0 turns the limit off. Default: 30000. */
292
+ formatFetchTimeout?: number;
253
293
  tagAliases?: Record<string, string>;
254
294
  sourceInfo?: boolean;
255
295
  wordCountMethod?: WordCountMethod;
@@ -265,7 +305,8 @@ export declare class TweeTsError extends Error {
265
305
  */
266
306
  export declare function compile(options: CompileOptions): Promise<CompileResult>;
267
307
  /**
268
- * Compile and write to a file.
308
+ * Compile and write to a file. The file is replaced atomically: a reader sees the previous
309
+ * build or the new one, never part of it, and a failed write leaves the previous build.
269
310
  */
270
311
  export declare function compileToFile(options: CompileToFileOptions): Promise<CompileResult>;
271
312
  /**
@@ -273,14 +314,22 @@ export declare function compileToFile(options: CompileToFileOptions): Promise<Co
273
314
  * Plugins and advanced users can manage their own cache and changed-file tracking.
274
315
  *
275
316
  * Without `changedFiles`, a cached file is reused while its modification time is unchanged.
276
- * With it, a file it names (by the path source discovery gives it, relative to the working
277
- * directory) is always reparsed, whatever its modification time, and every other cached
278
- * file is reused as it is.
317
+ * With it, a file it names is always reparsed, whatever its modification time, and every
318
+ * other cached file is reused as it is. A file may be named by an absolute path or by a path
319
+ * relative to the working directory, with or without a leading `./`: entries are matched to
320
+ * the source files by resolved path. A file that fails to load is dropped from the cache, so
321
+ * the next build tries it again.
279
322
  */
280
323
  export declare function compileIncremental(options: CompileOptions, cache: Map<string, FileCacheEntry>, changedFiles?: ReadonlySet<string>): Promise<CompileResult>;
281
324
  /**
282
- * Watch for file changes and recompile. Every build is written to `outFile`, including
283
- * one whose diagnostics report errors.
325
+ * Watch for file changes and recompile. Every build is written to `outFile` (replacing it
326
+ * atomically, as compileToFile() does), including one whose diagnostics report errors.
327
+ * Builds run one at a time, and each one is written and reported as it finishes, in order:
328
+ * changes made during a build go into one follow-up build after it. A watched path that
329
+ * can't be watched is passed to `onError`.
330
+ *
331
+ * Aborting the returned controller, or the `signal` in the options, stops watching and
332
+ * aborts the story format requests of the build in progress.
284
333
  */
285
334
  export declare function watch(options: WatchOptions): Promise<AbortController>;
286
335
  //#endregion
@@ -294,9 +343,9 @@ interface StoryMap {
294
343
  infoPassages: string[];
295
344
  /** All unique tags used across the story, sorted alphabetically. */
296
345
  tags: string[];
297
- /** Map of tag → passage names that carry that tag. */
346
+ /** Map of tag → passage names that carry that tag, each listed once even if the tag is repeated. */
298
347
  passagesByTag: Map<string, string[]>;
299
- /** Map of passage name → tags on that passage. */
348
+ /** Map of passage name → tags on that passage, as written (a repeated tag appears twice). */
300
349
  tagsByPassage: Map<string, string[]>;
301
350
  /**
302
351
  * Map of passage name → passage names it links to.
@@ -305,7 +354,8 @@ interface StoryMap {
305
354
  * `<<goto "target">>` / `<<link "display" "target">>`, read as SugarCube 2 reads them
306
355
  * (see `sugarcube-macros.ts`). Links and calls in comments are not read. A script passage is
307
356
  * read for links only in its strings; a stylesheet passage (including a loaded `.css` file) is
308
- * CSS and links to nothing.
357
+ * CSS and links to nothing. With a `target` (see `InspectOptions`), a passage that output leaves
358
+ * out links to nothing, except a script passage, which Twine 2 output runs.
309
359
  */
310
360
  links: Map<string, string[]>;
311
361
  /**
@@ -348,14 +398,18 @@ interface BrokenLink {
348
398
  * By default, a link is broken only when no passage has its name. Pass a `target` to check links
349
399
  * against what that output emits: `storyInspect(result.story, { target: 'twine2' })` also reports
350
400
  * links to passages Twine 2 output leaves out, such as script, stylesheet and `Twine.private`
351
- * passages.
401
+ * passages. With a target, passages that output leaves out also give no links (script passages
402
+ * excepted, as Twine 2 output runs them), so they report no broken links and keep no passage from
403
+ * being an orphan.
352
404
  */
353
405
  export declare function storyInspect(story: ReadonlyStory, options?: InspectOptions): StoryMap;
354
406
  //#endregion
355
407
  //#region src/story.d.ts
356
408
  /**
357
409
  * Builder that separates the mutable construction phase from the immutable consumption phase.
358
- * During construction, the internal `Story` is mutable via `add()` and direct access.
410
+ * During construction, the internal `Story` is mutable via `add()` and direct access: you may
411
+ * push, splice, sort, rename or reassign `story.passages` between calls, and `add()` and `has()`
412
+ * see the passages as they are then.
359
413
  * After `build()`, the story is returned as `ReadonlyStory`.
360
414
  */
361
415
  export declare class StoryBuilder {
@@ -378,6 +432,8 @@ interface DecompileResult {
378
432
  /**
379
433
  * Parse a Twine 2 or Twine 1 compiled HTML file back into a Story model.
380
434
  * Tweego always trims passage text; here `trim: false` keeps it exactly, as for Twee sources.
435
+ * A missing or invalid `tw-storydata` IFID is reported as a warning; an invalid one is kept as
436
+ * written (uppercased), a missing one stays empty.
381
437
  */
382
438
  export declare function decompileHTML(html: string, options?: DecompileOptions): DecompileResult;
383
439
  //#endregion
@@ -415,7 +471,8 @@ interface LintResult {
415
471
  * Uses JSON output mode internally to avoid format resolution, so it checks
416
472
  * the starting passage itself, against the Twine 2 passage rules. Link
417
473
  * destinations are checked against the same rules: a link to a passage that
418
- * Twine 2 output leaves out is broken.
474
+ * Twine 2 output leaves out is broken, and a passage it leaves out (other than
475
+ * a script passage) gives no links.
419
476
  */
420
477
  export declare function lint(options: Omit<CompileOptions, 'outputMode'>): Promise<LintResult>;
421
478
  /**
@@ -467,39 +524,49 @@ interface ParseResult {
467
524
  * Parse Twee source text into passages.
468
525
  *
469
526
  * The source is normalized first, as files are when they are read: a leading UTF-8 BOM is
470
- * removed and CRLF and bare CR line endings become LF.
527
+ * removed and CRLF and bare CR line endings become LF. A BOM at the start of a later line,
528
+ * directly before `::` (left there by concatenating files), is removed too, so that line
529
+ * stays a passage header.
471
530
  */
472
531
  export declare function parseTwee(source: string, options?: ParseOptions): ParseResult;
473
532
  //#endregion
474
533
  //#region src/formats.d.ts
475
534
  /**
476
- * Parse the Twine 2 format.js JSON chunk.
477
- * Handles Harlowe's malformed JSON by stripping the "setup" function property. The workaround
478
- * keys on the property itself, so the same bytes parse whether they come from a `harlowe-3`
479
- * directory or a direct download.
480
- * Per spec, the name key is Optional.
535
+ * Parse the Twine 2 format.js JSON chunk; null when it cannot be used ({@link decodeFormatJSON}
536
+ * gives the reason).
481
537
  *
482
538
  * @param _formatId No longer used; kept so existing callers keep compiling.
483
539
  */
484
540
  export declare function parseFormatJSON(source: string, _formatId?: string): Twine2FormatJSON | null;
485
541
  /**
486
- * Discover all story formats in the given search directories.
487
- * Implements SemVer-based version pruning: within each (name, major) group,
488
- * only the highest minor.patch survives.
542
+ * Discover all story formats in the given search directories, pruned by SemVer: within each
543
+ * (name, major) group only the highest version survives. See {@link discoverAllFormats} for the
544
+ * directory ranking and {@link pruneFormats} for pruning.
545
+ */
546
+ export declare function discoverFormats(searchDirs: readonly string[]): Map<string, StoryFormatInfo>;
547
+ /**
548
+ * Get the format search directories, lowest rank first: the story format subdirectories of the
549
+ * home directory, then of the working directory, then each `TWEEGO_PATH` entry, then `extraPaths`
550
+ * (`formatPaths`). A later directory outranks an earlier one for the same format folder name, so
551
+ * `formatPaths` override `TWEEGO_PATH`, which overrides the home and working directories, as in
552
+ * Tweego.
489
553
  */
490
- export declare function discoverFormats(searchDirs: string[]): Map<string, StoryFormatInfo>;
554
+ export declare function getFormatSearchDirs(extraPaths?: readonly string[], useTweegoPath?: boolean): string[];
491
555
  /**
492
- * Get the default format search directories.
556
+ * Parse a version string into [major, minor, patch]. Accepts what {@link parseVersion} accepts
557
+ * (`v1.2.3`, `1.2`, `2.0.0-beta.1`); the prerelease and build metadata are dropped.
493
558
  */
494
- export declare function getFormatSearchDirs(extraPaths?: string[], useTweegoPath?: boolean): string[];
495
- /** Parse a simple semver string into [major, minor, patch]. */
496
559
  export declare function parseSemver(v: string): [number, number, number] | null;
560
+ /** Compare [major, minor, patch] tuples. Use {@link compareVersions} to take prereleases into account. */
497
561
  export declare function semverCompare(a: [number, number, number], b: [number, number, number]): number;
498
562
  //#endregion
499
563
  //#region src/ifid.d.ts
500
564
  /** Generate a new IFID (UUID v4, uppercase). */
501
565
  export declare function generateIFID(): IFID;
502
- /** Validate and brand an IFID string. Throws on invalid input. */
566
+ /**
567
+ * Validate and brand an IFID string. Throws on invalid input.
568
+ * Returns the IFID in its stored form (see `normalizeIFID`).
569
+ */
503
570
  export declare function createIFID(value: string): IFID;
504
571
  /**
505
572
  * Validate an IFID string.
@@ -510,31 +577,63 @@ export declare function validateIFID(ifid: string): string | null;
510
577
  //#endregion
511
578
  //#region src/config.d.ts
512
579
  export declare const CONFIG_FILENAME = "twee-ts.config.json";
513
- /** Load a config file from the given directory (default: cwd). Returns null if not found. */
514
- export declare function loadConfig(dir?: string): TweeTsConfig | null;
515
- /** Load a config from a specific file path. */
516
- export declare function loadConfigFile(filePath: string): TweeTsConfig;
517
- /** Validate a config object. Returns an array of error strings (empty = valid). */
580
+ /**
581
+ * Load a config file from the given directory (default: cwd). Returns null if not found.
582
+ *
583
+ * @param diagnostics Receives warnings that do not stop the config from loading: keys the config does not
584
+ * define, and a file that is not valid UTF-8 (it is read as Windows-1252).
585
+ * @throws When the file is not valid JSON or fails {@link validateConfig}.
586
+ */
587
+ export declare function loadConfig(dir?: string, diagnostics?: Diagnostic[]): TweeTsConfig | null;
588
+ /**
589
+ * Load a config from a specific file path.
590
+ *
591
+ * @param diagnostics Receives warnings that do not stop the config from loading: keys the config does not
592
+ * define, and a file that is not valid UTF-8 (it is read as Windows-1252).
593
+ * @throws When the file cannot be read, is not valid JSON or fails {@link validateConfig}.
594
+ */
595
+ export declare function loadConfigFile(filePath: string, diagnostics?: Diagnostic[]): TweeTsConfig;
596
+ /**
597
+ * Validate a config object. Returns an array of error strings (empty = valid).
598
+ * Keys the config does not define are not errors; {@link unknownConfigKeyWarnings} reports them.
599
+ */
518
600
  export declare function validateConfig(data: unknown): string[];
601
+ /**
602
+ * Warnings for the keys of a config object that the config does not define (the JSON schema's
603
+ * `additionalProperties: false`), other than `$schema`. twee-ts ignores such a key, so a misspelt one would
604
+ * otherwise leave its option at the default without a word. A key that differs from a defined one only in
605
+ * letter case, `-` or `_` (`formatID`, `output-mode`) gets a suggestion. Returns `[]` for anything that is not
606
+ * an object, which {@link validateConfig} reports.
607
+ */
608
+ export declare function unknownConfigKeyWarnings(data: unknown): string[];
519
609
  /** Return a default config JSON string for --init scaffolding. */
520
610
  export declare function scaffoldConfig(): string;
521
611
  //#endregion
522
612
  //#region src/remote-formats.d.ts
523
613
  /** Get the cache directory for downloaded story formats. */
524
614
  export declare function getCacheDir(): string;
525
- /** Download a format, verify its checksum, write to cache, and return StoryFormatInfo. */
526
- export declare function fetchAndCacheFormat(entry: SFAIndexEntry, downloadUrl: string): Promise<StoryFormatInfo>;
527
- /** Download a direct format.js URL, parse its JSON, cache it, and return StoryFormatInfo. */
528
- export declare function fetchDirectFormat(url: string): Promise<StoryFormatInfo>;
615
+ /**
616
+ * Download a format, verify its checksum, write it to the cache shared by name and version, and
617
+ * return its StoryFormatInfo. Concurrent calls for one download URL share one request.
618
+ */
619
+ export declare function fetchAndCacheFormat(entry: SFAIndexEntry, downloadUrl: string, options?: RemoteFetchOptions): Promise<StoryFormatInfo>;
620
+ /**
621
+ * Download a direct format.js URL, parse its JSON, cache it under that URL (apart from the
622
+ * downloads shared by name and version), and return its StoryFormatInfo. Concurrent calls for
623
+ * one URL share one request.
624
+ */
625
+ export declare function fetchDirectFormat(url: string, options?: RemoteFetchOptions): Promise<StoryFormatInfo>;
529
626
  /**
530
627
  * Try to resolve a remote story format by name and version.
531
- * 0. Use an exactly matching cached download, without touching the network
532
- * 1. Try direct format URLs
533
- * 2. Try custom index URLs
534
- * 3. Try default SFA indices
535
- * 4. Fall back to a compatible cached download (e.g. when offline)
628
+ * 1. Try direct format URLs (each one's cached copy, else a download)
629
+ * 2. Use an exactly matching download from the shared cache, without touching the network
630
+ * 3. Try custom index URLs, then the default SFA indices
631
+ * 4. Fall back to a compatible download from the shared cache (e.g. when offline)
632
+ *
633
+ * `options.signal` aborts the lookup, which then rejects with the signal's reason;
634
+ * `options.timeout` limits each request (default {@link DEFAULT_FORMAT_FETCH_TIMEOUT} ms).
536
635
  */
537
- export declare function resolveRemoteFormat(name: string, version: string, indices?: string[], urls?: string[]): Promise<StoryFormatInfo | undefined>;
636
+ export declare function resolveRemoteFormat(name: string, version: string, indices?: readonly string[], urls?: readonly string[], options?: RemoteFetchOptions): Promise<StoryFormatInfo | undefined>;
538
637
  /** Discover all cached remote formats. Returns a Map like discoverFormats(). */
539
638
  export declare function discoverCachedFormats(): Map<string, StoryFormatInfo>;
540
639
  /** Info about a cached format entry with size and modification date. */
@@ -548,6 +647,8 @@ interface CachedFormatEntry {
548
647
  export declare function listCachedFormats(): readonly CachedFormatEntry[];
549
648
  /**
550
649
  * Clear all cached formats, or only those matching a given name. Returns the number of entries removed.
650
+ * Clearing all also removes the downloads from direct format URLs; a name clears only the
651
+ * downloads shared by name and version.
551
652
  * A name must be a single cache entry name (as `listCachedFormats()` reports it); anything with a
552
653
  * path separator or `.`/`..` throws instead of deleting outside the format's own directory.
553
654
  */
@@ -558,4 +659,4 @@ export declare function getCacheSize(): {
558
659
  count: number;
559
660
  };
560
661
  //#endregion
561
- export type { BrokenLink, CachedFormatEntry, CompileOptions, CompileResult, CompileStats, CompileToFileOptions, DecompileOptions, DecompileResult, Diagnostic, FileCacheEntry, IFID, InlineSource, InspectOptions, LexerItem, LintResult, OmittingTag, OutputMode, Passage, PassageMetadata, PassageOmission, PassageOutputTarget, ReadonlyPassage, ReadonlyStory, SFAIndex, SFAIndexEntry, SourceInput, SourceLocation, Story, StoryFormatInfo, StoryMap, TweeTsConfig, WatchOptions, WordCountMethod };
662
+ export type { BrokenLink, CachedFormatEntry, CompileOptions, CompileResult, CompileStats, CompileToFileOptions, DecompileOptions, DecompileResult, Diagnostic, FileCacheEntry, IFID, InlineSource, InspectOptions, LexerItem, LintResult, OmittingTag, OutputMode, Passage, PassageMetadata, PassageOmission, PassageOutputTarget, ReadonlyPassage, ReadonlyStory, RemoteFetchOptions, SFAIndex, SFAIndexEntry, SourceInput, SourceLocation, Story, StoryFormatInfo, StoryMap, TweeTsConfig, WatchOptions, WordCountMethod };