@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.
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { parseArgs } from "node:util";
3
3
  import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, watch, writeFileSync } from "node:fs";
4
- import { basename, dirname, extname, join, parse, relative, resolve } from "node:path";
4
+ import * as nodePath from "node:path";
5
+ import { basename, dirname, extname, join, parse, relative, resolve, sep } from "node:path";
5
6
  import { randomUUID } from "node:crypto";
6
7
  import { homedir } from "node:os";
7
8
  import { fileURLToPath } from "node:url";
@@ -603,11 +604,25 @@ function fontFormatHint(ext) {
603
604
  * Ported from filesystem.go.
604
605
  */
605
606
  /**
607
+ * Whether `filename` matches one of the `exclude` globs. The globs are matched
608
+ * against the file's path relative to the working directory, the path
609
+ * getFilenames reports; an absolute `filename` is made relative first. A leading
610
+ * `./` in a glob is dropped. Matching is Node's `path.matchesGlob`.
611
+ */
612
+ function isExcluded(filename, exclude) {
613
+ if (exclude.length === 0) return false;
614
+ const { matchesGlob } = nodePath;
615
+ if (typeof matchesGlob !== "function") throw new Error(`The exclude option needs Node.js 22.5 or newer (found ${process.version}).`);
616
+ const rel = relative(process.cwd(), resolve(filename)) || filename;
617
+ return exclude.some((pattern) => matchesGlob(rel, pattern.replace(/^\.\//, "")));
618
+ }
619
+ /**
606
620
  * Recursively walk directories, collecting regular file paths.
607
- * Filters out the output file to prevent circular compilation.
621
+ * Filters out the output file to prevent circular compilation, and the files
622
+ * that match an `exclude` glob (see isExcluded).
608
623
  * Like Tweego, a path that cannot be read is reported as a warning and skipped.
609
624
  */
610
- function getFilenames(pathnames, outFilename) {
625
+ function getFilenames(pathnames, outFilename, exclude = []) {
611
626
  const filenames = [];
612
627
  const diagnostics = [];
613
628
  const absOutFile = outFilename ? resolve(outFilename) : "";
@@ -629,6 +644,7 @@ function getFilenames(pathnames, outFilename) {
629
644
  const abs = resolve(pathname);
630
645
  if (abs === absOutFile) return;
631
646
  const rel = relative(process.cwd(), abs);
647
+ if (isExcluded(rel || abs, exclude)) return;
632
648
  filenames.push(rel || abs);
633
649
  } else if (stat.isDirectory()) {
634
650
  let entries;
@@ -649,9 +665,11 @@ function getFilenames(pathnames, outFilename) {
649
665
  }
650
666
  /**
651
667
  * Watch paths for changes, calling the build callback on known file type changes.
668
+ * A change to a file `ignore` returns true for (given the path relative to the
669
+ * working directory) schedules no build.
652
670
  * Uses debouncing to avoid rapid rebuilds.
653
671
  */
654
- function watchFilesystem(pathnames, outFilename, callback) {
672
+ function watchFilesystem(pathnames, outFilename, callback, ignore = () => false) {
655
673
  const absOutFile = resolve(outFilename);
656
674
  const watchers = [];
657
675
  let buildTimer = null;
@@ -672,7 +690,10 @@ function watchFilesystem(pathnames, outFilename, callback) {
672
690
  if (!filename) return;
673
691
  const abs = resolve(pathname, filename);
674
692
  if (abs === absOutFile) return;
675
- if (isKnownFileType(filename)) scheduleBuild(relative(process.cwd(), abs) || abs);
693
+ if (isKnownFileType(filename)) {
694
+ const rel = relative(process.cwd(), abs);
695
+ if (!ignore(rel || abs)) scheduleBuild(rel || abs);
696
+ }
676
697
  });
677
698
  watchers.push(watcher);
678
699
  } catch {}
@@ -5110,14 +5131,22 @@ async function watch$1(options) {
5110
5131
  const cache = /* @__PURE__ */ new Map();
5111
5132
  const filePaths = options.sources.filter((s) => typeof s === "string");
5112
5133
  const modulePaths = options.modules ?? [];
5113
- const handle = watchFilesystem([...filePaths, ...modulePaths], options.outFile, (changedFiles) => {
5134
+ const allPaths = [...filePaths, ...modulePaths];
5135
+ const exclude = options.exclude ?? [];
5136
+ const moduleRoots = modulePaths.map((p) => resolve(p));
5137
+ const ignore = (filename) => {
5138
+ if (!isExcluded(filename, exclude)) return false;
5139
+ const abs = resolve(filename);
5140
+ return !moduleRoots.some((root) => abs === root || abs.startsWith(root + sep));
5141
+ };
5142
+ const handle = watchFilesystem(allPaths, options.outFile, (changedFiles) => {
5114
5143
  buildOutput(options, cache, changedFiles).then((result) => {
5115
5144
  writeFileSync(options.outFile, result.output, "utf-8");
5116
5145
  options.onBuild?.(result);
5117
5146
  }).catch((e) => {
5118
5147
  options.onError?.(e instanceof Error ? e : new Error(String(e)));
5119
5148
  });
5120
- });
5149
+ }, ignore);
5121
5150
  controller.signal.addEventListener("abort", () => handle.close());
5122
5151
  return controller;
5123
5152
  }
@@ -5134,7 +5163,7 @@ async function buildOutput(options, cache, changedFiles) {
5134
5163
  const inlineSources = [];
5135
5164
  for (const source of options.sources) if (typeof source === "string") filePaths.push(source);
5136
5165
  else inlineSources.push(source);
5137
- const { filenames: sourceFilenames, diagnostics: sourcePathDiagnostics } = getFilenames(filePaths);
5166
+ const { filenames: sourceFilenames, diagnostics: sourcePathDiagnostics } = getFilenames(filePaths, void 0, options.exclude);
5138
5167
  diagnostics.push(...sourcePathDiagnostics);
5139
5168
  const story = createStory();
5140
5169
  const processedFiles = /* @__PURE__ */ new Set();
@@ -6659,6 +6688,7 @@ function validateConfig(data) {
6659
6688
  ]) if (key in obj && typeof obj[key] !== "boolean") errors.push(`"${key}" must be a boolean.`);
6660
6689
  for (const key of [
6661
6690
  "sources",
6691
+ "exclude",
6662
6692
  "formatPaths",
6663
6693
  "formatIndices",
6664
6694
  "formatUrls",
@@ -6767,6 +6797,10 @@ const { values, positionals } = parseArgs({
6767
6797
  type: "string",
6768
6798
  multiple: true
6769
6799
  },
6800
+ exclude: {
6801
+ type: "string",
6802
+ multiple: true
6803
+ },
6770
6804
  "source-info": { type: "boolean" },
6771
6805
  "word-count-method": { type: "string" },
6772
6806
  config: {
@@ -6839,6 +6873,7 @@ async function main() {
6839
6873
  if (values.lint) {
6840
6874
  const lintResult = await lint({
6841
6875
  sources,
6876
+ exclude: values.exclude ?? config?.exclude,
6842
6877
  formatId: values.format ?? config?.formatId,
6843
6878
  startPassage: values.start ?? config?.startPassage,
6844
6879
  formatPaths: config?.formatPaths,
@@ -6860,6 +6895,7 @@ async function main() {
6860
6895
  }
6861
6896
  const compileOptions = {
6862
6897
  sources,
6898
+ exclude: values.exclude ?? config?.exclude,
6863
6899
  outputMode,
6864
6900
  formatId: values.format ?? config?.formatId,
6865
6901
  startPassage: values.start ?? config?.startPassage,
@@ -7033,6 +7069,7 @@ Options:
7033
7069
  --format-index <url> SFA-compatible format index URL (repeatable)
7034
7070
  --format-url <url> Direct format.js URL (repeatable)
7035
7071
  --tag-alias <alias=target> Map a tag to a special tag (repeatable)
7072
+ --exclude <glob> Leave out source files matching a glob (repeatable)
7036
7073
  --source-info Emit source file/line as data- attributes on passages
7037
7074
  --word-count-method <m> Word counting method: tweego (default), whitespace
7038
7075
  --no-remote Disable remote format fetching