Skip to content

Configuration ​

twee-ts automatically loads twee-ts.config.json from the current working directory. Use -c <file> to specify a different path, or --no-config to skip loading entirely.

Paths in a config file (sources, exclude, output, modules, headFile, formatPaths) are relative to the folder that holds the config file, so twee-ts -c proj/twee-ts.config.json from the folder above proj builds proj/src/, not src/. Absolute paths and "-" (stdout) are kept as they are. (Before 2.0 they were relative to the working directory; see Migrating to 2.0.) Paths given on the command line stay relative to the working directory.

JSON Schema ​

The config file has a JSON Schema that provides autocompletion, validation, and inline documentation in editors like VS Code. Add a $schema key to enable it:

json
{
  "$schema": "https://unpkg.com/@rohal12/twee-ts/schemas/twee-ts.config.schema.json",
  "sources": ["src/"],
  "output": "story.html"
}

SchemaStore lists the schema too, so editors that use SchemaStore will automatically associate twee-ts.config.json files with it — no $schema key needed. SchemaStore does not keep a copy: its entry points to the schema in the npm package (https://unpkg.com/@rohal12/twee-ts/schemas/twee-ts.config.schema.json), so the schema an editor uses is always the one of the latest release. To validate against the schema of the version you have installed, use a $schema key with a version in the URL (https://unpkg.com/@rohal12/twee-ts@2/schemas/twee-ts.config.schema.json).

Minimal Config ​

json
{
  "sources": ["src/"],
  "output": "story.html"
}

With this in place, run npx @rohal12/twee-ts with no arguments.

Complete Reference ​

Every key, with its default. sources has none: name the sources here or on the command line. formatId and startPassage override StoryData's format and start passage (the values shown are what a story gets when neither names one), so leave them out to let StoryData decide.

json
{
  "sources": ["src/"],
  "exclude": [],
  "output": "-",
  "outputMode": "html",
  "formatId": "sugarcube-2",
  "startPassage": "Start",
  "formatPaths": [],
  "formatIndices": [],
  "formatUrls": [],
  "useTweegoPath": true,
  "modules": [],
  "headFile": "head.html",
  "trim": true,
  "twee2Compat": false,
  "testMode": false,
  "noRemote": false,
  "formatFetchTimeout": 30000,
  "formatResolutionTimeout": 120000,
  "useDefaultFormatIndices": true,
  "tagAliases": {},
  "sourceInfo": false,
  "wordCountMethod": "tweego"
}

Field Reference ​

Sources & Output ​

KeyTypeDefaultDescription
sourcesstring[]—Files or directories to compile. Directories are walked recursively. Required here or on the command line.
excludestring[][]Glob patterns for source files to leave out. See Excluding files.
outputstring"-"Output file path; "-" for stdout. Must not be empty.
outputModestring"html"One of html, twee3, twee1, twine2-archive, twine1-archive, json.

Source directories are walked as on the command line: a symbolic link to a directory inside one is not followed, and the output file is never read as a source. See the CLI Reference.

Excluding files ​

Like Tweego, twee-ts loads every file it supports from sources: Twee, CSS, JavaScript, fonts, and images, audio and video, which become base64 media passages. To keep files out, such as artwork stored next to the passages that use it but served separately, list glob patterns in exclude:

json
{
  "sources": ["src/story"],
  "exclude": ["src/story/**/*.png"]
}
  • Each pattern is matched against a file's path relative to the working directory, the path --log-files prints, with Node's path.matchesGlob: * matches within one folder, ** across folders, {png,webp} either name. A leading ./ is ignored. * and ** don't match names that start with a dot. In a config file, a relative pattern is relative to the config file's folder; on the command line, to the working directory.
  • The path is also matched as the real path relative to the working directory, so a pattern works whether the working directory or the sources are reached through a symbolic link (macOS's /var and /private/var, a linked project folder). A pattern that starts with / (or a drive letter) is matched against absolute paths.
  • The file extension is matched without regard to letter case, as twee-ts reads Photo.PNG as a PNG image: **/*.png leaves it out. On a volume that ignores letter case (macOS and Windows by default), the whole path is.
  • To leave out a folder, match the files in it: src/story/art/**.
  • ** doesn't reach outside the working directory. For sources outside it, start the pattern with the same ../ path: ../shared/**/*.png.
  • A file listed in sources directly is left out too if a pattern matches it. Modules and the head file are never left out.
  • In watch mode and in the Vite plugin's dev server, changes to excluded files don't trigger a rebuild.
  • --exclude on the command line replaces the config's list.

Story Format ​

KeyTypeDefaultDescription
formatIdstring"sugarcube-2"Story format ID. Without it, StoryData's format and format-version decide, and sugarcube-2 only when StoryData names none. Must not be empty.
formatPathsstring[][]Extra format directories, which outrank TWEEGO_PATH.
formatIndicesstring[][]http:/https: URLs of SFA-compatible index.json files, consulted after formatUrls and before the Story Formats Archive.
formatUrlsstring[][]http:/https: URLs of format.js files, consulted after local formats and before formatIndices.
useTweegoPathbooleantrueAlso search the TWEEGO_PATH environment variable for formats.
noRemotebooleanfalseDisable remote format fetching entirely.
formatFetchTimeoutnumber30000Milliseconds each story format request may take before it fails with a warning and the next source is tried. 0 turns the limit off.
formatResolutionTimeoutnumber120000Milliseconds the search for the story format may take in all. When it passes, the request in progress stops with a warning, and the format URLs and indices not yet asked answer from the download cache only. 0 turns the limit off.
useDefaultFormatIndicesbooleantrueAsk the Story Formats Archive after formatIndices. false asks only the configured format URLs and indices.

Compilation ​

KeyTypeDefaultDescription
startPassagestring"Start"Name of the starting passage. Without it, StoryData's start decides, and Start only when StoryData names none. Must not be empty.
trimbooleantrueTrim leading and trailing whitespace from passage content, in Twee and HTML sources.
twee2CompatbooleanfalseEnable Twee2 syntax compatibility mode.
testModebooleanfalseEnable test/debug mode (sets the debug option in story data).
sourceInfobooleanfalseEmbed source file/line as data- attributes on passage elements.
wordCountMethodstring"tweego"Word counting: "tweego" (characters / 5 after NFKD normalization, matches Tweego) or "whitespace" (words between white space, after removing comments and markup).

The "whitespace" method counts the words a reader sees when SugarCube 2 shows the passage: the runs of characters between white space in the text it shows. Passage markup is read as SugarCube's wikifier reads it, leftmost first:

  1. Macro tags, opening and closing, give no words. A tag runs from << and a macro name to the first >> outside a quoted string, comment or link markup, so a > or >= operator, or a > or >> in a string, doesn't end it (<<if $score >= 10>>, <<set $label to "A > B">>). Text that starts no macro tag, such as << x >> or a << with no >> after it, is prose. The text in every branch of <<if>> and other containers counts.
  2. Comments (/* */, /% %/, <!-- -->) give no words. A comment that isn't closed loses only its opener, as SugarCube drops it, and the text after it counts.
  3. Link markup counts as the text the link shows ([[Go home|Home]] counts as Go home).
  4. HTML start tags that SugarCube's htmlTag parser reads give no words, whatever their quoted attribute values hold (<span title="score > 10">), and a << or [[ inside one is part of the tag. An end tag gives none when it closes the innermost such element; inside an element, the end tag of an outer one is text, as SugarCube reads an element's content up to its own end tag. A < that starts no tag is prose (I <3 you, A < B and C > D), and so is markup SugarCube shows as text, such as a tag left open (Well done <b), an end tag nothing opened, or <!x>. A <style> element gives no words, an <svg> element and the content of <html>…</html> are each read as HTML of their own (so nothing left open inside reaches the text after them), and the content of <nowiki>…</nowiki> as text, as SugarCube's own parsers for them read them; one without its end loses only its opener. SugarCube's parsers for <style and <svg also take a longer tag name (<styles>, <svg-icon>), whose element is read the same way, as HTML of its own.
  5. What is left is shown as SugarCube makes it, one element at a time, and the text a browser shows of it counts. SugarCube makes each HTML element from its own start tag, in the context of the element around it, so a table part outside a table (<tr>, <td>) gives no element of its own and its content counts as it is, and the same tag gives another element inside a table. A line feed is a line break, which separates words, except directly inside a table, ul, ol, dl or another element SugarCube's htmlTag parser takes no line breaks in (<ul>one\ntwo</ul> is one word; in a <li> or <td> in it, a line feed separates words again). A character reference with its ; counts as the character it stands for (one&nbsp;two is two words), one without it is text (one &nbsp two is three), a <br> separates words, and the text of script, style, title, noscript and template elements doesn't count; that of a textarea does. In SVG only the text of text elements (and the tspan, textPath and a elements in them) and of foreignObject counts, nothing inside defs and the like, or in an element SVG does not define. In passage markup no text inside <math> counts, and nor does any HTML or SVG element in it, since SugarCube makes the elements in it HTML elements, which MathML doesn't render. The content of <html>…</html> and <svg> elements is read as jQuery appends it (a first tag that is a table part is read inside a table, so text before a <tr> is lost), and shown as a browser shows it: the text of MathML token elements (mi, mn, mo, ms, mtext) in it counts. White space between elements always separates the words around it, also where a browser shows no text.

Words on either side of any other tag or a macro tag with no white space between them count as one (a<b>b</b> is one word). Styles, hidden attributes and what macros print are not taken into account. SugarCube's other markup counts as written: the * and # of list items (line markup, not the <ul> element), a > that starts a block quote, a ---- rule, the | of table rows, a \ that continues a line, and the """ around verbatim text. A passage whose HTML nests elements more than 512 deep (Chromium's limit, counting the page's html and body elements and the element the passage is shown in), which a passage can do by opening an element in each branch of an <<if>>, is counted as its text without tags, with a warning naming the passage; the build goes on.

Head Injection ​

KeyTypeDefaultDescription
modulesstring[][]JS or CSS files to inject into the HTML <head>.
headFilestring—Path to a raw HTML file whose contents are appended to <head>. Must not be empty. Keep it out of the source folders, where an .html file is read as a Twine story.

See Head Injection for where they go in a template without a closing head tag.

Tag Aliases ​

KeyTypeDefaultDescription
tagAliasesRecord<string, string>{}Map custom tag names to canonical special tags. Both are non-empty and hold no whitespace.

See the dedicated Tag Aliases page for full details and examples.

CLI Override ​

CLI flags take precedence over config file values. For example:

json
{
  "sources": ["src/"],
  "formatId": "sugarcube-2",
  "startPassage": "Begin"
}
sh
# Overrides formatId to harlowe-3, keeps startPassage as Begin
twee-ts -f harlowe-3

For tagAliases, CLI --tag-alias flags are merged on top of config values, so you can set defaults in the config and add or override aliases per invocation. Every other list on the command line (--exclude, -m, --format-url, --format-index) replaces the config's. See Precedence.

Scaffolding ​

Run npx @rohal12/twee-ts --init to generate a starter config and source files:

sh
npx @rohal12/twee-ts --init

This creates:

  • twee-ts.config.json with $schema, sources (["src/"]) and output ("story.html")
  • src/StoryData.tw with a generated IFID
  • src/StoryTitle.tw with the title My Story (SugarCube refuses to start a story without one)
  • src/Start.tw with a starter passage

and reports:

Initializing new twee-ts project...
Created:
  twee-ts.config.json
  src/StoryData.tw
  src/StoryTitle.tw
  src/Start.tw

Run: npx @rohal12/twee-ts

Files that already exist are kept as they are and reported as skipped (Skipped (already exists): src/Start.tw), so running --init in an existing project never replaces your story or its IFID.

Validation ​

twee-ts validates the config file on load. A value of the wrong type stops the build (exit status 1) with an error that lists every problem:

error: Invalid config in /path/to/twee-ts.config.json:
  "sources" must be an array.
  "trim" must be a boolean.

The config file is read as strict JSON; a file that is not is an error giving the line and column. A key given twice is a warning ($.sources is given more than once; the last one is used.), and the last one is used. The checks are those of the JSON Schema, which is generated from the same table twee-ts checks with, so the two agree.

A key the config does not define is ignored, so its option keeps the default. twee-ts warns about each one and the build goes on. When the key differs from a real one only in letter case, - or _, the warning names the real key:

warning: /path/to/twee-ts.config.json: Unknown config key "formatID" (did you mean "formatId"?); it is ignored.

$schema is always allowed.

The config file is read like any other text file: it may start with a byte order mark, be UTF-16 (as Windows PowerShell 5's > writes it) and use CRLF line endings. See Text encoding.

Released under the Unlicense.