@rohal12/twee-ts 1.15.4 → 1.16.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
@@ -12,6 +12,11 @@ type SourceInput = string | InlineSource;
12
12
  interface CompileOptions {
13
13
  /** Files, directories, or inline sources to compile. */
14
14
  sources: SourceInput[];
15
+ /**
16
+ * Glob patterns for files to leave out of `sources`, matched against each file's
17
+ * path relative to the working directory (as `stats.files` lists it). Modules are not affected.
18
+ */
19
+ exclude?: string[];
15
20
  /** Output mode. Default: 'html'. */
16
21
  outputMode?: OutputMode;
17
22
  /** Story format directory ID (e.g. 'sugarcube-2'). */
@@ -195,6 +200,7 @@ interface FileCacheEntry {
195
200
  }
196
201
  interface TweeTsConfig {
197
202
  sources?: string[];
203
+ exclude?: string[];
198
204
  output?: string;
199
205
  outputMode?: OutputMode;
200
206
  formatId?: string;
package/dist/index.d.ts CHANGED
@@ -12,6 +12,11 @@ type SourceInput = string | InlineSource;
12
12
  interface CompileOptions {
13
13
  /** Files, directories, or inline sources to compile. */
14
14
  sources: SourceInput[];
15
+ /**
16
+ * Glob patterns for files to leave out of `sources`, matched against each file's
17
+ * path relative to the working directory (as `stats.files` lists it). Modules are not affected.
18
+ */
19
+ exclude?: string[];
15
20
  /** Output mode. Default: 'html'. */
16
21
  outputMode?: OutputMode;
17
22
  /** Story format directory ID (e.g. 'sugarcube-2'). */
@@ -195,6 +200,7 @@ interface FileCacheEntry {
195
200
  }
196
201
  interface TweeTsConfig {
197
202
  sources?: string[];
203
+ exclude?: string[];
198
204
  output?: string;
199
205
  outputMode?: OutputMode;
200
206
  formatId?: string;
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, watch as watch$1, writeFileSync } from "node:fs";
2
+ import * as nodePath from "node:path";
3
+ import { basename, dirname, extname, join, parse, relative, resolve, sep } from "node:path";
2
4
  import { randomUUID } from "node:crypto";
3
- import { basename, dirname, extname, join, parse, relative, resolve } from "node:path";
4
5
  import { homedir } from "node:os";
5
6
  import { fileURLToPath } from "node:url";
6
7
  //#region src/ifid.ts
@@ -631,11 +632,25 @@ function fontFormatHint(ext) {
631
632
  * Ported from filesystem.go.
632
633
  */
633
634
  /**
635
+ * Whether `filename` matches one of the `exclude` globs. The globs are matched
636
+ * against the file's path relative to the working directory, the path
637
+ * getFilenames reports; an absolute `filename` is made relative first. A leading
638
+ * `./` in a glob is dropped. Matching is Node's `path.matchesGlob`.
639
+ */
640
+ function isExcluded(filename, exclude) {
641
+ if (exclude.length === 0) return false;
642
+ const { matchesGlob } = nodePath;
643
+ if (typeof matchesGlob !== "function") throw new Error(`The exclude option needs Node.js 22.5 or newer (found ${process.version}).`);
644
+ const rel = relative(process.cwd(), resolve(filename)) || filename;
645
+ return exclude.some((pattern) => matchesGlob(rel, pattern.replace(/^\.\//, "")));
646
+ }
647
+ /**
634
648
  * Recursively walk directories, collecting regular file paths.
635
- * Filters out the output file to prevent circular compilation.
649
+ * Filters out the output file to prevent circular compilation, and the files
650
+ * that match an `exclude` glob (see isExcluded).
636
651
  * Like Tweego, a path that cannot be read is reported as a warning and skipped.
637
652
  */
638
- function getFilenames(pathnames, outFilename) {
653
+ function getFilenames(pathnames, outFilename, exclude = []) {
639
654
  const filenames = [];
640
655
  const diagnostics = [];
641
656
  const absOutFile = outFilename ? resolve(outFilename) : "";
@@ -657,6 +672,7 @@ function getFilenames(pathnames, outFilename) {
657
672
  const abs = resolve(pathname);
658
673
  if (abs === absOutFile) return;
659
674
  const rel = relative(process.cwd(), abs);
675
+ if (isExcluded(rel || abs, exclude)) return;
660
676
  filenames.push(rel || abs);
661
677
  } else if (stat.isDirectory()) {
662
678
  let entries;
@@ -677,9 +693,11 @@ function getFilenames(pathnames, outFilename) {
677
693
  }
678
694
  /**
679
695
  * Watch paths for changes, calling the build callback on known file type changes.
696
+ * A change to a file `ignore` returns true for (given the path relative to the
697
+ * working directory) schedules no build.
680
698
  * Uses debouncing to avoid rapid rebuilds.
681
699
  */
682
- function watchFilesystem(pathnames, outFilename, callback) {
700
+ function watchFilesystem(pathnames, outFilename, callback, ignore = () => false) {
683
701
  const absOutFile = resolve(outFilename);
684
702
  const watchers = [];
685
703
  let buildTimer = null;
@@ -700,7 +718,10 @@ function watchFilesystem(pathnames, outFilename, callback) {
700
718
  if (!filename) return;
701
719
  const abs = resolve(pathname, filename);
702
720
  if (abs === absOutFile) return;
703
- if (isKnownFileType(filename)) scheduleBuild(relative(process.cwd(), abs) || abs);
721
+ if (isKnownFileType(filename)) {
722
+ const rel = relative(process.cwd(), abs);
723
+ if (!ignore(rel || abs)) scheduleBuild(rel || abs);
724
+ }
704
725
  });
705
726
  watchers.push(watcher);
706
727
  } catch {}
@@ -5171,14 +5192,22 @@ async function watch(options) {
5171
5192
  const cache = /* @__PURE__ */ new Map();
5172
5193
  const filePaths = options.sources.filter((s) => typeof s === "string");
5173
5194
  const modulePaths = options.modules ?? [];
5174
- const handle = watchFilesystem([...filePaths, ...modulePaths], options.outFile, (changedFiles) => {
5195
+ const allPaths = [...filePaths, ...modulePaths];
5196
+ const exclude = options.exclude ?? [];
5197
+ const moduleRoots = modulePaths.map((p) => resolve(p));
5198
+ const ignore = (filename) => {
5199
+ if (!isExcluded(filename, exclude)) return false;
5200
+ const abs = resolve(filename);
5201
+ return !moduleRoots.some((root) => abs === root || abs.startsWith(root + sep));
5202
+ };
5203
+ const handle = watchFilesystem(allPaths, options.outFile, (changedFiles) => {
5175
5204
  buildOutput(options, cache, changedFiles).then((result) => {
5176
5205
  writeFileSync(options.outFile, result.output, "utf-8");
5177
5206
  options.onBuild?.(result);
5178
5207
  }).catch((e) => {
5179
5208
  options.onError?.(e instanceof Error ? e : new Error(String(e)));
5180
5209
  });
5181
- });
5210
+ }, ignore);
5182
5211
  controller.signal.addEventListener("abort", () => handle.close());
5183
5212
  return controller;
5184
5213
  }
@@ -5195,7 +5224,7 @@ async function buildOutput(options, cache, changedFiles) {
5195
5224
  const inlineSources = [];
5196
5225
  for (const source of options.sources) if (typeof source === "string") filePaths.push(source);
5197
5226
  else inlineSources.push(source);
5198
- const { filenames: sourceFilenames, diagnostics: sourcePathDiagnostics } = getFilenames(filePaths);
5227
+ const { filenames: sourceFilenames, diagnostics: sourcePathDiagnostics } = getFilenames(filePaths, void 0, options.exclude);
5199
5228
  diagnostics.push(...sourcePathDiagnostics);
5200
5229
  const story = createStory();
5201
5230
  const processedFiles = /* @__PURE__ */ new Set();
@@ -6720,6 +6749,7 @@ function validateConfig(data) {
6720
6749
  ]) if (key in obj && typeof obj[key] !== "boolean") errors.push(`"${key}" must be a boolean.`);
6721
6750
  for (const key of [
6722
6751
  "sources",
6752
+ "exclude",
6723
6753
  "formatPaths",
6724
6754
  "formatIndices",
6725
6755
  "formatUrls",