rainbowindex 0.2.2 → 0.4.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/cli.mjs CHANGED
@@ -2,28 +2,24 @@
2
2
  import {
3
3
  CSS_ENTRY_CANDIDATES,
4
4
  enumerateClassNames
5
- } from "./chunk-KYDEHYIE.mjs";
5
+ } from "./chunk-YLB6FGIG.mjs";
6
6
  import {
7
7
  DEFAULT_EXCLUDES,
8
8
  DEFAULT_PATTERNS,
9
- collectProjectClasses,
10
- finalizeProjectCompilation,
11
- getFontPreloadLinks,
12
- resolveGoogleFonts,
13
- validateGlobPattern
14
- } from "./chunk-FHATRQMN.mjs";
9
+ compileScannedProject,
10
+ getFontPreloadLinks
11
+ } from "./chunk-4SVFFDS2.mjs";
15
12
  import {
16
13
  MAX_DIRECTIVE_INPUT_SIZE,
17
- analyzeProjectCSS,
18
14
  extractDirectives,
19
15
  hasApplyLikeDirective,
20
16
  hasRIActivation,
21
17
  listVariants,
22
18
  resolveDirectives
23
- } from "./chunk-W6XIBM4M.mjs";
19
+ } from "./chunk-6DAHUFNU.mjs";
24
20
  import {
25
21
  devWarn
26
- } from "./chunk-SOMDX7V6.mjs";
22
+ } from "./chunk-5Y7EXXLS.mjs";
27
23
 
28
24
  // src/entries/cli.ts
29
25
  import { realpathSync } from "fs";
@@ -32,6 +28,153 @@ import { pathToFileURL } from "url";
32
28
 
33
29
  // src/cli/args.ts
34
30
  import { relative, resolve } from "path";
31
+ var OUTPUT = {
32
+ names: ["-o", "--output"],
33
+ valueName: "<file>",
34
+ describe: "Output CSS file path (omit to write to stdout)",
35
+ apply(opts, value) {
36
+ opts.output = value;
37
+ }
38
+ };
39
+ var WATCH = {
40
+ names: ["--watch"],
41
+ describe: "Re-run on source-file changes (requires --output)",
42
+ apply(opts) {
43
+ opts.watch = true;
44
+ }
45
+ };
46
+ var MINIFY = {
47
+ names: ["--minify", "--optimize"],
48
+ helpLabel: "--minify",
49
+ describe: "Minify + browser fallbacks via LightningCSS (alias: --optimize)",
50
+ apply(opts) {
51
+ opts.minify = true;
52
+ }
53
+ };
54
+ var STRICT = {
55
+ names: ["--strict"],
56
+ describe: "Drop the `string & {}` escape hatch from the union",
57
+ apply(opts) {
58
+ opts.strict = true;
59
+ }
60
+ };
61
+ var TEMPLATE = {
62
+ names: ["--template"],
63
+ valueName: "<name>",
64
+ describe: "Vite template (default: react-ts)\nExamples: vanilla, vanilla-ts, react, react-ts, vue, vue-ts, svelte, svelte-ts",
65
+ apply(opts, value) {
66
+ opts.template = value;
67
+ }
68
+ };
69
+ var applyCSSFile = (opts, value) => {
70
+ opts.cssFile = value;
71
+ };
72
+ var CSS_INPUT = {
73
+ names: ["--css"],
74
+ valueName: "<file>",
75
+ describe: "CSS input with directives (auto-detected if omitted)",
76
+ apply: applyCSSFile
77
+ };
78
+ var CSS_INPUT_TYPES = {
79
+ names: ["--css"],
80
+ valueName: "<file>",
81
+ describe: "CSS input with @color/@text/@utility/etc directives",
82
+ apply: applyCSSFile
83
+ };
84
+ var CSS_INPUT_FONTS = {
85
+ names: ["--css"],
86
+ valueName: "<file>",
87
+ describe: "CSS input with @font directives",
88
+ apply: applyCSSFile
89
+ };
90
+ var CSS_TARGET = {
91
+ names: ["--css"],
92
+ valueName: "<file>",
93
+ describe: "Stylesheet path to create/patch (default: src/index.css)",
94
+ apply: applyCSSFile
95
+ };
96
+ var COMMANDS = {
97
+ build: {
98
+ summary: "Generate CSS from source files",
99
+ usage: " rainbowindex <glob...> [options]\n rainbowindex --css <file> [options]",
100
+ flags: [OUTPUT, WATCH, MINIFY, CSS_INPUT],
101
+ positionals: "globs",
102
+ body: `Inputs:
103
+ Pass one or more globs to scan for class names. If no globs are passed and
104
+ a CSS file with @source directives is found, those are used instead.
105
+
106
+ Examples:
107
+ rainbowindex "src/**/*.tsx" -o dist/styles.css
108
+ rainbowindex "src/**/*.tsx" --watch -o dist/styles.css
109
+ rainbowindex --css src/styles.css -o dist/styles.css --minify`
110
+ },
111
+ init: {
112
+ summary: "Wire Rainbow Index into the current Vite app",
113
+ usage: " rainbowindex init [options]",
114
+ intro: `What this does:
115
+ 1. Installs rainbowindex as a dev dependency (if not already installed).
116
+ 2. Creates or patches vite.config.* to register the Vite plugin.
117
+ 3. Creates or updates a CSS entry file with @import "rainbowindex".
118
+ 4. Adds the CSS import to your entry file (src/main.tsx, src/main.ts, \u2026).`,
119
+ flags: [CSS_TARGET],
120
+ positionals: "none",
121
+ body: `Notes:
122
+ Detects pnpm / yarn / bun / npm from lockfiles, package.json#packageManager,
123
+ and npm_config_user_agent. Requires an existing Vite project \u2014 if you do not
124
+ have one yet, use \`rainbowindex create <dir>\`.
125
+
126
+ Examples:
127
+ rainbowindex init
128
+ rainbowindex init --css src/styles.css`
129
+ },
130
+ create: {
131
+ summary: "Scaffold a Vite app with Rainbow Index ready",
132
+ usage: " rainbowindex create <dir> [options]",
133
+ flags: [TEMPLATE, CSS_TARGET],
134
+ positionals: "targetDir",
135
+ body: `What this does:
136
+ 1. Runs \`create vite\` for the given template.
137
+ 2. Runs \`rainbowindex init\` inside the new directory.
138
+
139
+ Examples:
140
+ rainbowindex create my-app
141
+ rainbowindex create my-app --template vue-ts`
142
+ },
143
+ "generate-types": {
144
+ summary: "Generate TypeScript types for ri() autocomplete",
145
+ usage: " rainbowindex generate-types [options]",
146
+ flags: [CSS_INPUT_TYPES, STRICT],
147
+ positionals: "none",
148
+ body: `Output:
149
+ Writes \`rainbowindex-env.d.ts\` to the current working directory. Include it
150
+ in tsconfig.json#include (or the equivalent) so the editor picks it up.
151
+
152
+ Examples:
153
+ rainbowindex generate-types
154
+ rainbowindex generate-types --strict --css src/styles.css`
155
+ },
156
+ "preload-fonts": {
157
+ summary: 'Generate <link rel="preload"> tags',
158
+ usage: " rainbowindex preload-fonts [options]",
159
+ flags: [CSS_INPUT_FONTS],
160
+ positionals: "none",
161
+ body: `Output:
162
+ Prints <link rel="preload"> tags to stdout for fonts declared in the @font
163
+ block (faces marked preload), or via @font-face.
164
+
165
+ Examples:
166
+ rainbowindex preload-fonts --css src/styles.css > preload.html`
167
+ }
168
+ };
169
+ var FLAG_TAKES_VALUE = /* @__PURE__ */ new Map();
170
+ for (const spec of Object.values(COMMANDS).flatMap((command) => command.flags)) {
171
+ for (const name of spec.names) {
172
+ FLAG_TAKES_VALUE.set(name, spec.valueName !== void 0);
173
+ }
174
+ }
175
+ function asSubcommand(token) {
176
+ return Object.hasOwn(COMMANDS, token) ? token : null;
177
+ }
35
178
  function parseArgs(argv, callbacks) {
36
179
  if (argv[0] === "framework") {
37
180
  throw new Error(
@@ -47,104 +190,73 @@ function parseArgs(argv, callbacks) {
47
190
  subcommandExplicit: false,
48
191
  earlyExit: false
49
192
  };
50
- let i = 0;
51
- while (i < argv.length) {
52
- const arg = argv[i];
53
- if (arg === "build" && !opts.subcommandExplicit && opts.globs.length === 0) {
54
- opts.command = "build";
55
- opts.subcommandExplicit = true;
56
- i++;
57
- continue;
58
- }
59
- if (arg === "generate-types") {
60
- opts.command = "generate-types";
61
- opts.subcommandExplicit = true;
62
- i++;
63
- continue;
64
- }
65
- if (arg === "init" || arg === "--init") {
66
- opts.command = "init";
193
+ let keywordIndex = -1;
194
+ for (let j = 0; j < argv.length; j++) {
195
+ const token = argv[j];
196
+ if (token === "--init" || token === "--create") {
197
+ opts.command = token.slice(2);
67
198
  opts.subcommandExplicit = true;
68
- i++;
69
- continue;
199
+ keywordIndex = j;
200
+ break;
70
201
  }
71
- if (arg === "create" || arg === "--create") {
72
- opts.command = "create";
73
- opts.subcommandExplicit = true;
74
- i++;
202
+ if (token.startsWith("-")) {
203
+ if (FLAG_TAKES_VALUE.get(token)) j++;
75
204
  continue;
76
205
  }
77
- if (arg === "preload-fonts") {
78
- opts.command = "preload-fonts";
206
+ const command = asSubcommand(token);
207
+ if (command) {
208
+ opts.command = command;
79
209
  opts.subcommandExplicit = true;
80
- i++;
81
- continue;
82
- }
83
- if (arg === "-o" || arg === "--output") {
84
- if (i + 1 >= argv.length) {
85
- throw new Error(`Missing value for ${arg}`);
86
- }
87
- opts.output = argv[++i];
88
- i++;
89
- continue;
210
+ keywordIndex = j;
90
211
  }
91
- if (arg === "--css") {
92
- if (i + 1 >= argv.length) {
93
- throw new Error("Missing value for --css");
212
+ break;
213
+ }
214
+ const spec = COMMANDS[opts.command];
215
+ for (let i = 0; i < argv.length; i++) {
216
+ if (i === keywordIndex) continue;
217
+ const arg = argv[i];
218
+ if (arg === "--version" || arg === "-v" || arg === "--help" || arg === "-h") {
219
+ if (keywordIndex > i) {
220
+ opts.command = "build";
221
+ opts.subcommandExplicit = false;
94
222
  }
95
- opts.cssFile = argv[++i];
96
- i++;
97
- continue;
98
- }
99
- if (arg === "--template") {
100
- if (i + 1 >= argv.length) {
101
- throw new Error("Missing value for --template");
223
+ if (arg === "--version" || arg === "-v") {
224
+ console.log(callbacks.getVersion());
225
+ } else {
226
+ callbacks.printHelp(opts.subcommandExplicit ? opts.command : void 0);
102
227
  }
103
- opts.template = argv[++i];
104
- i++;
105
- continue;
106
- }
107
- if (arg === "--watch") {
108
- opts.watch = true;
109
- i++;
110
- continue;
111
- }
112
- if (arg === "--minify") {
113
- opts.minify = true;
114
- i++;
115
- continue;
116
- }
117
- if (arg === "--strict") {
118
- opts.strict = true;
119
- i++;
120
- continue;
121
- }
122
- if (arg === "--version" || arg === "-v") {
123
- console.log(callbacks.getVersion());
124
- opts.earlyExit = true;
125
- return opts;
126
- }
127
- if (arg === "--help" || arg === "-h") {
128
- callbacks.printHelp(opts.subcommandExplicit ? opts.command : void 0);
129
228
  opts.earlyExit = true;
130
229
  return opts;
131
230
  }
132
231
  if (arg.startsWith("-")) {
232
+ const flag = spec.flags.find((f) => f.names.includes(arg));
233
+ if (flag) {
234
+ if (flag.valueName !== void 0) {
235
+ if (i + 1 >= argv.length) {
236
+ throw new Error(`Missing value for ${arg}`);
237
+ }
238
+ flag.apply(opts, argv[++i]);
239
+ } else {
240
+ flag.apply(opts);
241
+ }
242
+ continue;
243
+ }
244
+ if (FLAG_TAKES_VALUE.has(arg)) {
245
+ throw new Error(
246
+ `${arg} is not supported by ${opts.command}. Run rainbowindex ${opts.command} --help for usage.`
247
+ );
248
+ }
133
249
  throw new Error(`Unknown option "${arg}". Run rainbowindex --help for usage.`);
134
250
  }
135
- if (opts.command === "create") {
136
- if (opts.targetDir) {
137
- throw new Error(`Unexpected extra argument "${arg}" for create.`);
138
- }
139
- opts.targetDir = arg;
140
- i++;
251
+ if (spec.positionals === "globs") {
252
+ opts.globs.push(arg);
141
253
  continue;
142
254
  }
143
- if (opts.command === "init") {
144
- throw new Error(`Unexpected extra argument "${arg}" for init.`);
255
+ if (spec.positionals === "targetDir" && !opts.targetDir) {
256
+ opts.targetDir = arg;
257
+ continue;
145
258
  }
146
- opts.globs.push(arg);
147
- i++;
259
+ throw new Error(`Unexpected extra argument "${arg}" for ${opts.command}.`);
148
260
  }
149
261
  if (opts.command === "create" && !opts.targetDir) {
150
262
  throw new Error("create requires a project directory. Example: rainbowindex create my-app");
@@ -165,115 +277,39 @@ function parseArgs(argv, callbacks) {
165
277
  }
166
278
  return opts;
167
279
  }
168
- var SUBCOMMAND_HELP = {
169
- build: `
170
- \u{1F308} rainbowindex build \u2014 Generate CSS from source files
171
-
172
- Usage:
173
- rainbowindex <glob...> [options]
174
- rainbowindex --css <file> [options]
175
-
176
- Options:
177
- -o, --output <file> Output CSS file path (omit to write to stdout)
178
- --watch Re-run on source-file changes (requires --output)
179
- --minify Minify output via LightningCSS
180
- --css <file> CSS input with directives (auto-detected if omitted)
181
- -h, --help Show this help
182
-
183
- Inputs:
184
- Pass one or more globs to scan for class names. If no globs are passed and
185
- a CSS file with @source directives is found, those are used instead.
186
-
187
- Examples:
188
- rainbowindex "src/**/*.tsx" -o dist/styles.css
189
- rainbowindex "src/**/*.tsx" --watch -o dist/styles.css
190
- rainbowindex --css src/styles.css -o dist/styles.css --minify
191
- `,
192
- init: `
193
- \u{1F308} rainbowindex init \u2014 Wire Rainbow Index into the current Vite app
194
-
195
- Usage:
196
- rainbowindex init [options]
197
-
198
- What this does:
199
- 1. Installs rainbowindex as a dev dependency (if not already installed).
200
- 2. Creates or patches vite.config.* to register the Vite plugin.
201
- 3. Creates or updates a CSS entry file with @import "rainbowindex".
202
- 4. Adds the CSS import to your entry file (src/main.tsx, src/main.ts, \u2026).
203
-
204
- Options:
205
- --css <file> Stylesheet path to create/patch (default: src/index.css)
206
- -h, --help Show this help
207
-
208
- Notes:
209
- Detects pnpm / yarn / bun / npm from lockfiles, package.json#packageManager,
210
- and npm_config_user_agent. Requires an existing Vite project \u2014 if you do not
211
- have one yet, use \`rainbowindex create <dir>\`.
212
-
213
- Examples:
214
- rainbowindex init
215
- rainbowindex init --css src/styles.css
216
- `,
217
- create: `
218
- \u{1F308} rainbowindex create \u2014 Scaffold a Vite app with Rainbow Index ready
219
-
220
- Usage:
221
- rainbowindex create <dir> [options]
222
-
223
- Options:
224
- --template <name> Vite template (default: react-ts)
225
- Examples: vanilla, vanilla-ts, react, react-ts, vue, vue-ts, svelte, svelte-ts
226
- --css <file> Stylesheet path inside the new app (default: src/index.css)
227
- -h, --help Show this help
228
-
229
- What this does:
230
- 1. Runs \`create vite\` for the given template.
231
- 2. Runs \`rainbowindex init\` inside the new directory.
232
-
233
- Examples:
234
- rainbowindex create my-app
235
- rainbowindex create my-app --template vue-ts
236
- `,
237
- "generate-types": `
238
- \u{1F308} rainbowindex generate-types \u2014 Generate TypeScript types for ri() autocomplete
239
-
240
- Usage:
241
- rainbowindex generate-types [options]
242
-
243
- Options:
244
- --css <file> CSS input with @color/@text/@utility/etc directives
245
- --strict Drop the \`string & {}\` escape hatch from the union
246
- -h, --help Show this help
247
-
248
- Output:
249
- Writes \`rainbowindex-env.d.ts\` to the current working directory. Include it
250
- in tsconfig.json#include (or the equivalent) so the editor picks it up.
251
-
252
- Examples:
253
- rainbowindex generate-types
254
- rainbowindex generate-types --strict --css src/styles.css
255
- `,
256
- "preload-fonts": `
257
- \u{1F308} rainbowindex preload-fonts \u2014 Generate <link rel="preload"> tags
258
-
259
- Usage:
260
- rainbowindex preload-fonts [options]
261
-
262
- Options:
263
- --css <file> CSS input with @font directives
264
- -h, --help Show this help
265
-
266
- Output:
267
- Prints <link rel="preload"> tags to stdout for fonts declared in the @font
268
- block (faces marked preload), or via @font-face.
269
-
270
- Examples:
271
- rainbowindex preload-fonts --css src/styles.css > preload.html
272
- `
273
- };
280
+ var HELP_DESCRIBE_COLUMN = 25;
281
+ function renderFlagLines(flags) {
282
+ const lines = [];
283
+ for (const flag of flags) {
284
+ const label = flag.helpLabel ?? flag.names.join(", ");
285
+ const heading = ` ${label}${flag.valueName ? ` ${flag.valueName}` : ""}`;
286
+ const [first, ...rest] = flag.describe.split("\n");
287
+ lines.push(`${heading.padEnd(HELP_DESCRIBE_COLUMN)}${first}`);
288
+ for (const continuation of rest) {
289
+ lines.push(`${" ".repeat(HELP_DESCRIBE_COLUMN)}${continuation}`);
290
+ }
291
+ }
292
+ return lines.join("\n");
293
+ }
294
+ var HELP_LINE = `${" -h, --help".padEnd(HELP_DESCRIBE_COLUMN)}Show this help`;
295
+ var VERSION_LINE = `${" -v, --version".padEnd(HELP_DESCRIBE_COLUMN)}Show version`;
296
+ var GLOBAL_HELP_FLAGS = [OUTPUT, WATCH, MINIFY, CSS_INPUT, TEMPLATE, STRICT];
274
297
  function printHelp(subcommand) {
275
298
  if (subcommand) {
276
- console.log(SUBCOMMAND_HELP[subcommand]);
299
+ const spec = COMMANDS[subcommand];
300
+ const sections = [
301
+ `\u{1F308} rainbowindex ${subcommand} \u2014 ${spec.summary}`,
302
+ `Usage:
303
+ ${spec.usage}`,
304
+ ...spec.intro ? [spec.intro] : [],
305
+ `Options:
306
+ ${renderFlagLines(spec.flags)}
307
+ ${HELP_LINE}`,
308
+ spec.body
309
+ ];
310
+ console.log(`
311
+ ${sections.join("\n\n")}
312
+ `);
277
313
  return;
278
314
  }
279
315
  console.log(`
@@ -289,14 +325,9 @@ Usage:
289
325
  Run \`rainbowindex <subcommand> --help\` for subcommand-specific options.
290
326
 
291
327
  Options:
292
- -o, --output <file> Output CSS file path
293
- --watch Watch for changes
294
- --minify Minify output via LightningCSS
295
- --css <file> CSS file with directives (default: auto-detect)
296
- --template <name> Vite template for create (default: react-ts)
297
- --strict No string escape hatch in generated types
298
- -v, --version Show version
299
- -h, --help Show this help
328
+ ${renderFlagLines(GLOBAL_HELP_FLAGS)}
329
+ ${VERSION_LINE}
330
+ ${HELP_LINE}
300
331
 
301
332
  Environment Variables:
302
333
  RI_CACHE_DIR Override font metadata cache directory (default: node_modules/.cache/rainbowindex)
@@ -377,40 +408,24 @@ async function loadProjectTheme(opts, cwd) {
377
408
  // src/cli/build.ts
378
409
  async function buildCSS(opts, cwd) {
379
410
  const { css: cssSource, cssFile } = await loadProjectCSS(opts, cwd);
380
- const analysis = analyzeProjectCSS(cssSource);
381
- const { theme, warnings, warningSeen } = analysis;
382
- const validGlobs = [];
383
- for (const g of opts.globs) {
384
- const err = validateGlobPattern(g);
385
- if (err) {
411
+ const { compiled } = await compileScannedProject({
412
+ css: cssSource,
413
+ cwd,
414
+ surfacePatterns: opts.globs,
415
+ onInvalidPattern: (err) => {
386
416
  console.error(`[RI-1404] CLI glob pattern rejected: ${err}`);
387
- } else {
388
- validGlobs.push(g);
417
+ return void 0;
389
418
  }
390
- }
391
- const cliSources = validGlobs.map((g) => ({ pattern: g, negated: false, inline: false }));
392
- const scannedClasses = await collectProjectClasses(
393
- theme.sources,
394
- cliSources,
395
- cwd,
396
- warnings,
397
- warningSeen
398
- );
419
+ });
399
420
  const buildWarnings = [];
400
421
  if (hasApplyLikeDirective(cssSource)) {
401
422
  buildWarnings.push(
402
423
  '[RI-1009] @apply is only supported via the PostCSS plugin. To expand @apply: (1) add `postcss.config.js` with `import rainbowindex from "rainbowindex"; export default { plugins: [rainbowindex()] };`, or (2) use the Vite plugin (`import rainbowindex from "rainbowindex/vite"`), which wires PostCSS automatically. The CLI does not run PostCSS so it cannot expand @apply.'
403
424
  );
404
425
  }
405
- const compiled = await finalizeProjectCompilation({
406
- css: cssSource,
407
- classNames: scannedClasses,
408
- analysis,
409
- resolveFonts: resolveGoogleFonts
410
- });
411
426
  if (compiled.classNames.length === 0) {
412
427
  const hadAnyGlob = opts.globs.length > 0;
413
- const hadAnySource = theme.sources.length > 0;
428
+ const hadAnySource = compiled.theme.sources.length > 0;
414
429
  if (!hadAnyGlob && !hadAnySource) {
415
430
  buildWarnings.push(
416
431
  '[RI-1603] No source files were scanned and no utility classes were compiled. Either pass a glob (`rainbowindex "src/**/*.{ts,tsx}" -o out.css`) or add `@source "src/**/*.{ts,tsx}";` to your CSS input.'
@@ -654,10 +669,8 @@ async function watchMode(opts, cwd) {
654
669
  awaitWriteFinish: { stabilityThreshold: 50 }
655
670
  });
656
671
  let debounceTimer = null;
657
- let building = false;
658
- let pendingRebuild = false;
659
- let buildDoneResolve = null;
660
- let buildDonePromise = null;
672
+ let currentBuild = null;
673
+ let dirty = false;
661
674
  let consecutiveErrors = 0;
662
675
  let errorsPaused = false;
663
676
  const MAX_CONSECUTIVE_ERRORS = 5;
@@ -688,11 +701,31 @@ async function watchMode(opts, cwd) {
688
701
  }
689
702
  };
690
703
  syncSourceWatchPaths(initialBuild.theme);
691
- const rebuild = () => {
704
+ const runBuild = async () => {
705
+ lastBuildStart = Date.now();
706
+ try {
707
+ const result = await buildAndWrite(opts, cwd, outputFile);
708
+ console.log(`[rainbowindex] Rebuilt: ${outputFile}`);
709
+ consecutiveErrors = 0;
710
+ errorsPaused = false;
711
+ syncSourceWatchPaths(result.theme);
712
+ } catch (err) {
713
+ consecutiveErrors++;
714
+ console.error("[rainbowindex] Build error:", err);
715
+ } finally {
716
+ currentBuild = null;
717
+ if (dirty) {
718
+ dirty = false;
719
+ scheduleRebuild();
720
+ }
721
+ }
722
+ };
723
+ const scheduleRebuild = () => {
692
724
  if (debounceTimer) clearTimeout(debounceTimer);
693
- debounceTimer = setTimeout(async () => {
694
- if (building) {
695
- pendingRebuild = true;
725
+ const delay = Math.max(100, MIN_REBUILD_INTERVAL_MS - (Date.now() - lastBuildStart));
726
+ debounceTimer = setTimeout(() => {
727
+ if (currentBuild) {
728
+ dirty = true;
696
729
  return;
697
730
  }
698
731
  if (errorsPaused) {
@@ -705,42 +738,12 @@ async function watchMode(opts, cwd) {
705
738
  errorsPaused = true;
706
739
  return;
707
740
  }
708
- const now = Date.now();
709
- const elapsed = now - lastBuildStart;
710
- if (lastBuildStart > 0 && elapsed < MIN_REBUILD_INTERVAL_MS) {
711
- pendingRebuild = true;
712
- setTimeout(rebuild, MIN_REBUILD_INTERVAL_MS - elapsed);
713
- return;
714
- }
715
- lastBuildStart = now;
716
- building = true;
717
- buildDonePromise = new Promise((resolveBuild) => {
718
- buildDoneResolve = resolveBuild;
719
- });
720
- try {
721
- const result = await buildAndWrite(opts, cwd, outputFile);
722
- console.log(`[rainbowindex] Rebuilt: ${outputFile}`);
723
- consecutiveErrors = 0;
724
- errorsPaused = false;
725
- syncSourceWatchPaths(result.theme);
726
- } catch (err) {
727
- consecutiveErrors++;
728
- console.error("[rainbowindex] Build error:", err);
729
- } finally {
730
- building = false;
731
- buildDoneResolve?.();
732
- buildDoneResolve = null;
733
- buildDonePromise = null;
734
- if (pendingRebuild) {
735
- pendingRebuild = false;
736
- rebuild();
737
- }
738
- }
739
- }, 100);
741
+ currentBuild = runBuild();
742
+ }, delay);
740
743
  };
741
- watcher.on("change", rebuild);
742
- watcher.on("add", rebuild);
743
- watcher.on("unlink", rebuild);
744
+ watcher.on("change", scheduleRebuild);
745
+ watcher.on("add", scheduleRebuild);
746
+ watcher.on("unlink", scheduleRebuild);
744
747
  watcher.on("error", (err) => {
745
748
  console.error("[rainbowindex] Watcher error:", err);
746
749
  });
@@ -749,7 +752,7 @@ async function watchMode(opts, cwd) {
749
752
  if (cleanupCalled) return;
750
753
  cleanupCalled = true;
751
754
  if (debounceTimer) clearTimeout(debounceTimer);
752
- pendingRebuild = false;
755
+ dirty = false;
753
756
  const doExit = () => {
754
757
  watcher.close().then(() => {
755
758
  console.log("\n[rainbowindex] Watcher stopped.");
@@ -758,9 +761,9 @@ async function watchMode(opts, cwd) {
758
761
  process.exit(1);
759
762
  });
760
763
  };
761
- if (building && buildDonePromise) {
764
+ if (currentBuild) {
762
765
  const timeout = new Promise((r) => setTimeout(r, 5e3));
763
- Promise.race([buildDonePromise, timeout]).then(doExit);
766
+ Promise.race([currentBuild, timeout]).then(doExit);
764
767
  } else {
765
768
  doExit();
766
769
  }
@@ -1233,7 +1236,7 @@ function readPackageJSONSync(cwd) {
1233
1236
 
1234
1237
  // src/entries/cli.ts
1235
1238
  function getVersion() {
1236
- return true ? "0.2.2" : "unknown";
1239
+ return true ? "0.4.0" : "unknown";
1237
1240
  }
1238
1241
  async function main() {
1239
1242
  const args = process.argv.slice(2);