@weapp-tailwindcss/cli 5.5.11 → 5.5.12

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/bin.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const require_src = require("./src-DME0SpR_.cjs");
2
+ const require_src = require("./src-RBTmE5sb.cjs");
3
3
  let node_process = require("node:process");
4
4
  node_process = require_src.__toESM(node_process, 1);
5
5
  //#region src/bin.ts
package/dist/bin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as runCli } from "./src-BKwkkU2f.js";
2
+ import { t as runCli } from "./src-CJTM0I3s.js";
3
3
  import process from "node:process";
4
4
  //#region src/bin.ts
5
5
  process.title = "node (@weapp-tailwindcss/cli)";
package/dist/index.cjs CHANGED
@@ -1,3 +1,3 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_src = require("./src-DME0SpR_.cjs");
2
+ const require_src = require("./src-RBTmE5sb.cjs");
3
3
  exports.runCli = require_src.runCli;
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { t as runCli } from "./src-BKwkkU2f.js";
1
+ import { t as runCli } from "./src-CJTM0I3s.js";
2
2
  export { runCli };
@@ -12,6 +12,54 @@ import { createHash } from "node:crypto";
12
12
  import * as parcelWatcher from "@parcel/watcher";
13
13
  import fg from "fast-glob";
14
14
  import { createInterface } from "node:readline";
15
+ //#region src/build/paths.ts
16
+ /** 按文件系统路径语义解析,不把 Windows 路径当作 POSIX 路径拼接。 */
17
+ function resolveFilePath(file, cwd, paths = path) {
18
+ return paths.resolve(cwd, file);
19
+ }
20
+ function identify(file) {
21
+ let existing = path.resolve(file);
22
+ const missing = [];
23
+ while (true) try {
24
+ const canonical = fs$1.realpathSync.native(existing);
25
+ const stat = missing.length ? void 0 : fs$1.statSync(existing, { bigint: true });
26
+ return {
27
+ path: path.join(canonical, ...missing),
28
+ inode: stat?.ino ? `${stat.dev}:${stat.ino}` : void 0
29
+ };
30
+ } catch (error) {
31
+ const code = error.code;
32
+ if (code !== "ENOENT" && code !== "ENOTDIR") throw error;
33
+ const parent = path.dirname(existing);
34
+ if (parent === existing) throw error;
35
+ missing.unshift(path.basename(existing));
36
+ existing = parent;
37
+ }
38
+ }
39
+ function identical(left, right) {
40
+ return left.path === right.path || Boolean(left.inode && left.inode === right.inode);
41
+ }
42
+ function validateBuildPaths(options) {
43
+ const files = [
44
+ ["input", options.input],
45
+ ["output", options.output],
46
+ ["map", typeof options.map === "string" ? options.map : void 0]
47
+ ].filter((entry) => Boolean(entry[1] && entry[1] !== "-")).map(([name, file]) => ({
48
+ name,
49
+ file,
50
+ identity: identify(file)
51
+ }));
52
+ for (let i = 0; i < files.length; i++) for (let j = i + 1; j < files.length; j++) if (identical(files[i].identity, files[j].identity)) throw new Error(`Specified ${files[i].name} and ${files[j].name} files are identical: ${files[j].file}`);
53
+ }
54
+ /** 每次匹配重新解析身份,兼容尚未创建的产物及后续替换的符号链接。 */
55
+ function isBuildOutput(file, outputs) {
56
+ if (!outputs.length) return false;
57
+ const absolute = path.resolve(file);
58
+ if (outputs.some((output) => path.resolve(output) === absolute)) return true;
59
+ const identity = identify(file);
60
+ return outputs.some((output) => identical(identity, identify(output)));
61
+ }
62
+ //#endregion
15
63
  //#region src/build/args.ts
16
64
  function value(argv, index, flag) {
17
65
  const item = argv[index];
@@ -73,13 +121,18 @@ function parseBuildArgs(argv) {
73
121
  if (consumesNext) index++;
74
122
  } else throw new Error(`Unknown option: ${arg}`);
75
123
  }
76
- const resolvePath = (file) => file && file !== "-" ? path.resolve(cwd, file) : file;
124
+ const resolvePath = (file) => file && file !== "-" ? resolveFilePath(file, cwd) : file;
77
125
  input = resolvePath(input);
78
126
  output = resolvePath(output);
79
127
  if (typeof map === "string") map = path.resolve(cwd, map);
80
128
  if (input && input !== "-" && !fs$1.existsSync(input)) throw new Error(`Specified input file ${input} does not exist.`);
81
129
  if (input && input !== "-" && input === output) throw new Error("Specified input and output files are identical.");
82
130
  if (target === "weapp" && map) throw new Error("Option \"--map\" is only supported when \"--target web\" is used.");
131
+ validateBuildPaths({
132
+ input,
133
+ output,
134
+ map
135
+ });
83
136
  return {
84
137
  cwd,
85
138
  input,
@@ -95,18 +148,58 @@ function parseBuildArgs(argv) {
95
148
  };
96
149
  }
97
150
  //#endregion
151
+ //#region src/build/watch-lifecycle.ts
152
+ /** 统一管理等待与停止,停止只唤醒循环,由调用方等待在途构建及清理完成。 */
153
+ function createWatchLifecycle() {
154
+ let stopped = false;
155
+ let wake;
156
+ const stop = () => {
157
+ stopped = true;
158
+ wake?.();
159
+ };
160
+ process.once("SIGINT", stop);
161
+ process.once("SIGTERM", stop);
162
+ return {
163
+ get stopped() {
164
+ return stopped;
165
+ },
166
+ wake() {
167
+ wake?.();
168
+ },
169
+ async wait(interval) {
170
+ if (stopped) return;
171
+ await new Promise((resolve) => {
172
+ const timer = interval === void 0 ? void 0 : setTimeout(() => wake?.(), interval);
173
+ wake = () => {
174
+ clearTimeout(timer);
175
+ wake = void 0;
176
+ resolve();
177
+ };
178
+ });
179
+ },
180
+ dispose() {
181
+ process.removeListener("SIGINT", stop);
182
+ process.removeListener("SIGTERM", stop);
183
+ }
184
+ };
185
+ }
186
+ //#endregion
98
187
  //#region src/build/watch.ts
99
- async function snapshot(cwd, dependencies, output) {
188
+ var NativeWatcherError = class extends Error {};
189
+ function report(error) {
190
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
191
+ }
192
+ async function snapshot(options, dependencies) {
100
193
  const files = /* @__PURE__ */ new Set([...dependencies, ...await fg("**/*", {
101
- cwd,
194
+ cwd: options.cwd,
102
195
  absolute: true,
103
196
  dot: true,
104
197
  onlyFiles: true,
105
198
  ignore: ["node_modules/**", ".git/**"]
106
199
  })]);
107
- if (output && output !== "-") files.delete(path.resolve(output));
108
200
  const state = /* @__PURE__ */ new Map();
109
201
  await Promise.all([...files].map(async (file) => {
202
+ if (isBuildOutput(file, options.outputs)) return;
110
203
  try {
111
204
  const [stat, content] = await Promise.all([fs.stat(file), fs.readFile(file)]);
112
205
  const digest = createHash("sha256").update(content).digest("hex");
@@ -116,120 +209,108 @@ async function snapshot(cwd, dependencies, output) {
116
209
  return state;
117
210
  }
118
211
  function changed(previous, next) {
119
- if (previous.size !== next.size) return true;
120
- for (const [file, value] of next) if (previous.get(file) !== value) return true;
121
- return false;
212
+ return previous.size !== next.size || [...next].some(([file, value]) => previous.get(file) !== value);
122
213
  }
123
214
  async function watchBuildInputs(options) {
124
- if (options.mode === "native") try {
125
- return await watchWithNativeWatcher(options);
126
- } catch (error) {
127
- process.stderr.write(`Native watcher unavailable, falling back to polling: ${error instanceof Error ? error.message : String(error)}\n`);
128
- return watchWithPolling(options);
215
+ const lifecycle = createWatchLifecycle();
216
+ try {
217
+ if (options.mode === "native") try {
218
+ await watchWithNativeWatcher(options, lifecycle);
219
+ return;
220
+ } catch (error) {
221
+ if (!(error instanceof NativeWatcherError)) throw error;
222
+ if (lifecycle.stopped) return;
223
+ report(`Native watcher unavailable, falling back to polling: ${error.message}`);
224
+ }
225
+ await watchWithPolling(options, lifecycle);
226
+ } finally {
227
+ lifecycle.dispose();
129
228
  }
130
- return watchWithPolling(options);
131
229
  }
132
- async function watchWithPolling(options) {
230
+ async function watchWithPolling(options, lifecycle) {
133
231
  let dependencies = /* @__PURE__ */ new Set();
134
- const previous = await snapshot(options.cwd, dependencies, options.output);
232
+ let previous = await snapshot(options, dependencies);
233
+ if (lifecycle.stopped) return;
135
234
  dependencies = await options.rebuild();
136
- const initialized = await snapshot(options.cwd, dependencies, options.output);
137
- for (const [file, value] of initialized) if (!previous.has(file)) previous.set(file, value);
138
- await new Promise((resolve) => {
139
- let running = false;
140
- const timer = setInterval(async () => {
141
- if (running) return;
142
- running = true;
143
- try {
144
- const next = await snapshot(options.cwd, dependencies, options.output);
145
- if (changed(previous, next)) {
146
- previous.clear();
147
- for (const [file, value] of next) previous.set(file, value);
148
- dependencies = await options.rebuild();
149
- }
150
- } catch (error) {
151
- process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
152
- } finally {
153
- running = false;
235
+ const initialized = await snapshot(options, dependencies);
236
+ for (const [file, value] of initialized) if (!previous.has(file) && isOutsideDirectory(file, options.cwd)) previous.set(file, value);
237
+ while (!lifecycle.stopped) {
238
+ await lifecycle.wait(options.interval);
239
+ if (lifecycle.stopped) break;
240
+ try {
241
+ const next = await snapshot(options, dependencies);
242
+ if (!lifecycle.stopped && changed(previous, next)) {
243
+ previous = next;
244
+ dependencies = await options.rebuild();
154
245
  }
155
- }, options.interval);
156
- const stop = () => {
157
- clearInterval(timer);
158
- resolve();
159
- };
160
- process.once("SIGINT", stop);
161
- process.once("SIGTERM", stop);
162
- });
246
+ } catch (error) {
247
+ report(error);
248
+ }
249
+ }
250
+ }
251
+ function isOutsideDirectory(file, cwd) {
252
+ const relative = path.relative(cwd, file);
253
+ return relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative);
163
254
  }
164
255
  function parentDirectories(files, cwd) {
165
256
  const directories = /* @__PURE__ */ new Set([path.resolve(cwd)]);
166
- for (const file of files) {
167
- const absolute = path.resolve(file);
168
- if (!absolute.startsWith(`${path.resolve(cwd)}${path.sep}`)) directories.add(path.dirname(absolute));
169
- }
257
+ for (const file of files) if (isOutsideDirectory(file, cwd)) directories.add(path.dirname(path.resolve(file)));
170
258
  return directories;
171
259
  }
172
- async function watchWithNativeWatcher(options) {
260
+ async function watchWithNativeWatcher(options, lifecycle) {
173
261
  let dependencies = /* @__PURE__ */ new Set();
174
262
  const subscriptions = /* @__PURE__ */ new Map();
175
- let running = false;
176
263
  let pending = false;
177
- let resolvePending;
178
- const output = options.output && options.output !== "-" ? path.resolve(options.output) : void 0;
179
- const requestRebuild = () => {
180
- pending = true;
181
- resolvePending?.();
182
- };
183
264
  const syncSubscriptions = async () => {
184
265
  const directories = parentDirectories(dependencies, options.cwd);
185
266
  for (const directory of directories) {
186
- if (subscriptions.has(directory)) continue;
187
- const subscription = await parcelWatcher.subscribe(directory, (error, events) => {
188
- if (error) {
189
- process.stderr.write(`${error.message}\n`);
190
- return;
191
- }
192
- if (events.some((event) => !output || path.resolve(event.path) !== output)) requestRebuild();
193
- }, { ignore: ["**/node_modules/**", "**/.git/**"] });
194
- subscriptions.set(directory, subscription);
267
+ if (lifecycle.stopped || subscriptions.has(directory)) continue;
268
+ try {
269
+ const subscription = await parcelWatcher.subscribe(directory, (error, events) => {
270
+ if (lifecycle.stopped) return;
271
+ if (error) {
272
+ report(error);
273
+ return;
274
+ }
275
+ try {
276
+ if (events.some((event) => !isBuildOutput(event.path, options.outputs))) {
277
+ pending = true;
278
+ lifecycle.wake();
279
+ }
280
+ } catch (error) {
281
+ report(error);
282
+ }
283
+ }, { ignore: ["**/node_modules/**", "**/.git/**"] });
284
+ subscriptions.set(directory, subscription);
285
+ } catch (error) {
286
+ throw new NativeWatcherError(error instanceof Error ? error.message : String(error));
287
+ }
195
288
  }
196
289
  for (const [directory, subscription] of subscriptions) if (!directories.has(directory)) {
197
290
  await subscription.unsubscribe();
198
291
  subscriptions.delete(directory);
199
292
  }
200
293
  };
201
- await syncSubscriptions();
202
- dependencies = await options.rebuild();
203
- await syncSubscriptions();
204
- await new Promise((resolve) => {
205
- const stop = () => {
206
- Promise.all([...subscriptions.values()].map((subscription) => subscription.unsubscribe())).finally(resolve);
207
- };
208
- process.once("SIGINT", stop);
209
- process.once("SIGTERM", stop);
210
- const loop = async () => {
211
- while (true) {
212
- if (pending) break;
213
- await new Promise((wake) => {
214
- resolvePending = wake;
215
- });
216
- resolvePending = void 0;
217
- }
218
- if (running) return;
219
- running = true;
294
+ try {
295
+ await syncSubscriptions();
296
+ if (lifecycle.stopped) return;
297
+ dependencies = await options.rebuild();
298
+ await syncSubscriptions();
299
+ while (!lifecycle.stopped) {
300
+ if (!pending) await lifecycle.wait();
301
+ if (lifecycle.stopped) break;
220
302
  pending = false;
221
303
  try {
222
304
  dependencies = await options.rebuild();
223
- await syncSubscriptions();
224
305
  } catch (error) {
225
- process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
226
- } finally {
227
- running = false;
228
- if (pending) loop();
306
+ report(error);
307
+ continue;
229
308
  }
230
- };
231
- loop();
232
- });
309
+ await syncSubscriptions();
310
+ }
311
+ } finally {
312
+ await Promise.all([...subscriptions.values()].map((subscription) => subscription.unsubscribe()));
313
+ }
233
314
  }
234
315
  //#endregion
235
316
  //#region src/canonicalize.ts
@@ -324,7 +405,7 @@ async function runCanonicalize(argv) {
324
405
  }
325
406
  if (inputs.length === 0) throw new Error("No candidate groups provided");
326
407
  const records = inputs.map((input) => record(designSystem, input));
327
- const output = format === "json" ? JSON.stringify(records, null, 2) : format === "jsonl" ? records.map(JSON.stringify).join("\n") : records.map((item) => item.output).join("\n");
408
+ const output = format === "json" ? JSON.stringify(records, null, 2) : format === "jsonl" ? records.map((item) => JSON.stringify(item)).join("\n") : records.map((item) => item.output).join("\n");
328
409
  process.stdout.write(`${output}\n`);
329
410
  return 0;
330
411
  }
@@ -344,6 +425,7 @@ function sourceMapComment(value) {
344
425
  return `/*# sourceMappingURL=${value} */`;
345
426
  }
346
427
  async function buildOnce(options, stdinCss) {
428
+ validateBuildPaths(options);
347
429
  const inputCss = options.input === "-" ? stdinCss ?? await drainStdin() : options.input ? await fs.readFile(options.input, "utf8") : DEFAULT_INPUT;
348
430
  const sourceOptions = {
349
431
  base: options.input && options.input !== "-" ? path.dirname(options.input) : options.cwd,
@@ -375,18 +457,18 @@ async function buildOnce(options, stdinCss) {
375
457
  sourceMap: Boolean(options.map)
376
458
  });
377
459
  css = Buffer.from(transformed.code).toString();
378
- map = transformed.map;
460
+ map = transformed.map || void 0;
379
461
  }
380
462
  if (options.map && map) {
381
463
  if (options.map === true) css += `\n${sourceMapComment(`data:application/json;base64,${Buffer.from(map).toString("base64")}`)}`;
382
464
  else {
383
- await writeFile$1(options.map, map);
384
465
  const mapBase = options.output && options.output !== "-" ? path.dirname(options.output) : options.cwd;
385
466
  css += `\n${sourceMapComment(path.relative(mapBase, options.map))}`;
386
467
  }
387
468
  }
388
469
  return {
389
470
  css,
471
+ map,
390
472
  dependencies: /* @__PURE__ */ new Set([
391
473
  ...options.input && options.input !== "-" ? [options.input] : [],
392
474
  ...source.dependencies,
@@ -399,11 +481,13 @@ async function buildOnce(options, stdinCss) {
399
481
  }
400
482
  async function runBuild(argv) {
401
483
  const options = parseBuildArgs(argv);
402
- if (!options.silent) process.stderr.write(`tailwindcss v${process.env.npm_package_version ?? ""}\n\n`);
484
+ if (!options.silent) process.stderr.write(`tailwindcss v${process.env["npm_package_version"] ?? ""}\n\n`);
403
485
  const stdinCss = options.input === "-" ? await drainStdin() : void 0;
404
486
  let previous = "";
405
487
  const rebuild = async () => {
406
488
  const result = await buildOnce(options, stdinCss);
489
+ validateBuildPaths(options);
490
+ if (typeof options.map === "string" && result.map) await writeFile$1(options.map, result.map);
407
491
  if (result.css !== previous) {
408
492
  if (options.output && options.output !== "-") await writeFile$1(options.output, result.css);
409
493
  else process.stdout.write(`${result.css}\n`);
@@ -419,7 +503,7 @@ async function runBuild(argv) {
419
503
  cwd: options.cwd,
420
504
  interval: options.pollInterval,
421
505
  mode: options.watchMode,
422
- output: options.output,
506
+ outputs: [options.output, typeof options.map === "string" ? options.map : void 0].filter((file) => Boolean(file && file !== "-")),
423
507
  rebuild
424
508
  });
425
509
  return 0;
@@ -430,7 +514,7 @@ async function runTailwindCli(rawArgv) {
430
514
  }
431
515
  //#endregion
432
516
  //#region package.json
433
- var version = "5.5.11";
517
+ var version = "5.5.12";
434
518
  //#endregion
435
519
  //#region src/constants.ts
436
520
  const WEAPP_TW_REQUIRED_NODE_VERSION_RANGE = "^22.18.0 || >=24.11.0";
@@ -1151,7 +1235,7 @@ async function runStatus(options) {
1151
1235
  status: "unnecessary",
1152
1236
  message: PATCH_COMMAND_OBSOLETE_NOTICE
1153
1237
  };
1154
- if (toBoolean(options.json, false)) {
1238
+ if (toBoolean(options["json"], false)) {
1155
1239
  logger.log(JSON.stringify(payload, null, 2));
1156
1240
  return;
1157
1241
  }
@@ -1161,21 +1245,21 @@ async function runStatus(options) {
1161
1245
  async function runVscodeEntry(options) {
1162
1246
  const resolvedCwd = resolveCliCwd(options.cwd);
1163
1247
  const baseDir = resolvedCwd ?? process.cwd();
1164
- const cssEntry = readStringOption("css", options.css);
1248
+ const cssEntry = readStringOption("css", options["css"]);
1165
1249
  if (!cssEntry) throw new Error("Option \"--css\" is required.");
1166
1250
  const result = await generateVscodeIntellisenseEntry({
1167
1251
  baseDir,
1168
1252
  cssEntry,
1169
- output: readStringOption("output", options.output),
1170
- sources: readStringArrayOption("source", options.source),
1171
- force: toBoolean(options.force, false)
1253
+ output: readStringOption("output", options["output"]),
1254
+ sources: readStringArrayOption("source", options["source"]),
1255
+ force: toBoolean(options["force"], false)
1172
1256
  });
1173
1257
  logger.success(`VS Code helper generated -> ${formatOutputPath(result.outputPath, resolvedCwd)}`);
1174
1258
  }
1175
1259
  async function runDoctor(options) {
1176
1260
  const report = createDoctorReport({ cwd: resolveCliCwd(options.cwd) });
1177
- logger.log(toBoolean(options.json, false) ? JSON.stringify(report, null, 2) : formatDoctorReport(report));
1178
- if (hasDoctorFailure(report, toBoolean(options.strict, false))) process.exitCode = 1;
1261
+ logger.log(toBoolean(options["json"], false) ? JSON.stringify(report, null, 2) : formatDoctorReport(report));
1262
+ if (hasDoctorFailure(report, toBoolean(options["strict"], false))) process.exitCode = 1;
1179
1263
  }
1180
1264
  async function runCli(argv = process.argv.slice(2)) {
1181
1265
  if (!semver.satisfies(process.versions.node, "^22.18.0 || >=24.11.0")) logger.warn(`You are using Node.js ${process.versions.node}. For @weapp-tailwindcss/cli, Node.js version ${WEAPP_TW_REQUIRED_NODE_VERSION_RANGE} is required.`);
@@ -41,6 +41,54 @@ let fast_glob = require("fast-glob");
41
41
  fast_glob = __toESM(fast_glob, 1);
42
42
  let node_readline = require("node:readline");
43
43
  let node_module = require("node:module");
44
+ //#region src/build/paths.ts
45
+ /** 按文件系统路径语义解析,不把 Windows 路径当作 POSIX 路径拼接。 */
46
+ function resolveFilePath(file, cwd, paths = node_path.default) {
47
+ return paths.resolve(cwd, file);
48
+ }
49
+ function identify(file) {
50
+ let existing = node_path.default.resolve(file);
51
+ const missing = [];
52
+ while (true) try {
53
+ const canonical = node_fs.default.realpathSync.native(existing);
54
+ const stat = missing.length ? void 0 : node_fs.default.statSync(existing, { bigint: true });
55
+ return {
56
+ path: node_path.default.join(canonical, ...missing),
57
+ inode: stat?.ino ? `${stat.dev}:${stat.ino}` : void 0
58
+ };
59
+ } catch (error) {
60
+ const code = error.code;
61
+ if (code !== "ENOENT" && code !== "ENOTDIR") throw error;
62
+ const parent = node_path.default.dirname(existing);
63
+ if (parent === existing) throw error;
64
+ missing.unshift(node_path.default.basename(existing));
65
+ existing = parent;
66
+ }
67
+ }
68
+ function identical(left, right) {
69
+ return left.path === right.path || Boolean(left.inode && left.inode === right.inode);
70
+ }
71
+ function validateBuildPaths(options) {
72
+ const files = [
73
+ ["input", options.input],
74
+ ["output", options.output],
75
+ ["map", typeof options.map === "string" ? options.map : void 0]
76
+ ].filter((entry) => Boolean(entry[1] && entry[1] !== "-")).map(([name, file]) => ({
77
+ name,
78
+ file,
79
+ identity: identify(file)
80
+ }));
81
+ for (let i = 0; i < files.length; i++) for (let j = i + 1; j < files.length; j++) if (identical(files[i].identity, files[j].identity)) throw new Error(`Specified ${files[i].name} and ${files[j].name} files are identical: ${files[j].file}`);
82
+ }
83
+ /** 每次匹配重新解析身份,兼容尚未创建的产物及后续替换的符号链接。 */
84
+ function isBuildOutput(file, outputs) {
85
+ if (!outputs.length) return false;
86
+ const absolute = node_path.default.resolve(file);
87
+ if (outputs.some((output) => node_path.default.resolve(output) === absolute)) return true;
88
+ const identity = identify(file);
89
+ return outputs.some((output) => identical(identity, identify(output)));
90
+ }
91
+ //#endregion
44
92
  //#region src/build/args.ts
45
93
  function value(argv, index, flag) {
46
94
  const item = argv[index];
@@ -102,13 +150,18 @@ function parseBuildArgs(argv) {
102
150
  if (consumesNext) index++;
103
151
  } else throw new Error(`Unknown option: ${arg}`);
104
152
  }
105
- const resolvePath = (file) => file && file !== "-" ? node_path.default.resolve(cwd, file) : file;
153
+ const resolvePath = (file) => file && file !== "-" ? resolveFilePath(file, cwd) : file;
106
154
  input = resolvePath(input);
107
155
  output = resolvePath(output);
108
156
  if (typeof map === "string") map = node_path.default.resolve(cwd, map);
109
157
  if (input && input !== "-" && !node_fs.default.existsSync(input)) throw new Error(`Specified input file ${input} does not exist.`);
110
158
  if (input && input !== "-" && input === output) throw new Error("Specified input and output files are identical.");
111
159
  if (target === "weapp" && map) throw new Error("Option \"--map\" is only supported when \"--target web\" is used.");
160
+ validateBuildPaths({
161
+ input,
162
+ output,
163
+ map
164
+ });
112
165
  return {
113
166
  cwd,
114
167
  input,
@@ -124,18 +177,58 @@ function parseBuildArgs(argv) {
124
177
  };
125
178
  }
126
179
  //#endregion
180
+ //#region src/build/watch-lifecycle.ts
181
+ /** 统一管理等待与停止,停止只唤醒循环,由调用方等待在途构建及清理完成。 */
182
+ function createWatchLifecycle() {
183
+ let stopped = false;
184
+ let wake;
185
+ const stop = () => {
186
+ stopped = true;
187
+ wake?.();
188
+ };
189
+ node_process.default.once("SIGINT", stop);
190
+ node_process.default.once("SIGTERM", stop);
191
+ return {
192
+ get stopped() {
193
+ return stopped;
194
+ },
195
+ wake() {
196
+ wake?.();
197
+ },
198
+ async wait(interval) {
199
+ if (stopped) return;
200
+ await new Promise((resolve) => {
201
+ const timer = interval === void 0 ? void 0 : setTimeout(() => wake?.(), interval);
202
+ wake = () => {
203
+ clearTimeout(timer);
204
+ wake = void 0;
205
+ resolve();
206
+ };
207
+ });
208
+ },
209
+ dispose() {
210
+ node_process.default.removeListener("SIGINT", stop);
211
+ node_process.default.removeListener("SIGTERM", stop);
212
+ }
213
+ };
214
+ }
215
+ //#endregion
127
216
  //#region src/build/watch.ts
128
- async function snapshot(cwd, dependencies, output) {
217
+ var NativeWatcherError = class extends Error {};
218
+ function report(error) {
219
+ node_process.default.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
220
+ }
221
+ async function snapshot(options, dependencies) {
129
222
  const files = /* @__PURE__ */ new Set([...dependencies, ...await (0, fast_glob.default)("**/*", {
130
- cwd,
223
+ cwd: options.cwd,
131
224
  absolute: true,
132
225
  dot: true,
133
226
  onlyFiles: true,
134
227
  ignore: ["node_modules/**", ".git/**"]
135
228
  })]);
136
- if (output && output !== "-") files.delete(node_path.default.resolve(output));
137
229
  const state = /* @__PURE__ */ new Map();
138
230
  await Promise.all([...files].map(async (file) => {
231
+ if (isBuildOutput(file, options.outputs)) return;
139
232
  try {
140
233
  const [stat, content] = await Promise.all([node_fs_promises.default.stat(file), node_fs_promises.default.readFile(file)]);
141
234
  const digest = (0, node_crypto.createHash)("sha256").update(content).digest("hex");
@@ -145,120 +238,108 @@ async function snapshot(cwd, dependencies, output) {
145
238
  return state;
146
239
  }
147
240
  function changed(previous, next) {
148
- if (previous.size !== next.size) return true;
149
- for (const [file, value] of next) if (previous.get(file) !== value) return true;
150
- return false;
241
+ return previous.size !== next.size || [...next].some(([file, value]) => previous.get(file) !== value);
151
242
  }
152
243
  async function watchBuildInputs(options) {
153
- if (options.mode === "native") try {
154
- return await watchWithNativeWatcher(options);
155
- } catch (error) {
156
- node_process.default.stderr.write(`Native watcher unavailable, falling back to polling: ${error instanceof Error ? error.message : String(error)}\n`);
157
- return watchWithPolling(options);
244
+ const lifecycle = createWatchLifecycle();
245
+ try {
246
+ if (options.mode === "native") try {
247
+ await watchWithNativeWatcher(options, lifecycle);
248
+ return;
249
+ } catch (error) {
250
+ if (!(error instanceof NativeWatcherError)) throw error;
251
+ if (lifecycle.stopped) return;
252
+ report(`Native watcher unavailable, falling back to polling: ${error.message}`);
253
+ }
254
+ await watchWithPolling(options, lifecycle);
255
+ } finally {
256
+ lifecycle.dispose();
158
257
  }
159
- return watchWithPolling(options);
160
258
  }
161
- async function watchWithPolling(options) {
259
+ async function watchWithPolling(options, lifecycle) {
162
260
  let dependencies = /* @__PURE__ */ new Set();
163
- const previous = await snapshot(options.cwd, dependencies, options.output);
261
+ let previous = await snapshot(options, dependencies);
262
+ if (lifecycle.stopped) return;
164
263
  dependencies = await options.rebuild();
165
- const initialized = await snapshot(options.cwd, dependencies, options.output);
166
- for (const [file, value] of initialized) if (!previous.has(file)) previous.set(file, value);
167
- await new Promise((resolve) => {
168
- let running = false;
169
- const timer = setInterval(async () => {
170
- if (running) return;
171
- running = true;
172
- try {
173
- const next = await snapshot(options.cwd, dependencies, options.output);
174
- if (changed(previous, next)) {
175
- previous.clear();
176
- for (const [file, value] of next) previous.set(file, value);
177
- dependencies = await options.rebuild();
178
- }
179
- } catch (error) {
180
- node_process.default.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
181
- } finally {
182
- running = false;
264
+ const initialized = await snapshot(options, dependencies);
265
+ for (const [file, value] of initialized) if (!previous.has(file) && isOutsideDirectory(file, options.cwd)) previous.set(file, value);
266
+ while (!lifecycle.stopped) {
267
+ await lifecycle.wait(options.interval);
268
+ if (lifecycle.stopped) break;
269
+ try {
270
+ const next = await snapshot(options, dependencies);
271
+ if (!lifecycle.stopped && changed(previous, next)) {
272
+ previous = next;
273
+ dependencies = await options.rebuild();
183
274
  }
184
- }, options.interval);
185
- const stop = () => {
186
- clearInterval(timer);
187
- resolve();
188
- };
189
- node_process.default.once("SIGINT", stop);
190
- node_process.default.once("SIGTERM", stop);
191
- });
275
+ } catch (error) {
276
+ report(error);
277
+ }
278
+ }
279
+ }
280
+ function isOutsideDirectory(file, cwd) {
281
+ const relative = node_path.default.relative(cwd, file);
282
+ return relative === ".." || relative.startsWith(`..${node_path.default.sep}`) || node_path.default.isAbsolute(relative);
192
283
  }
193
284
  function parentDirectories(files, cwd) {
194
285
  const directories = /* @__PURE__ */ new Set([node_path.default.resolve(cwd)]);
195
- for (const file of files) {
196
- const absolute = node_path.default.resolve(file);
197
- if (!absolute.startsWith(`${node_path.default.resolve(cwd)}${node_path.default.sep}`)) directories.add(node_path.default.dirname(absolute));
198
- }
286
+ for (const file of files) if (isOutsideDirectory(file, cwd)) directories.add(node_path.default.dirname(node_path.default.resolve(file)));
199
287
  return directories;
200
288
  }
201
- async function watchWithNativeWatcher(options) {
289
+ async function watchWithNativeWatcher(options, lifecycle) {
202
290
  let dependencies = /* @__PURE__ */ new Set();
203
291
  const subscriptions = /* @__PURE__ */ new Map();
204
- let running = false;
205
292
  let pending = false;
206
- let resolvePending;
207
- const output = options.output && options.output !== "-" ? node_path.default.resolve(options.output) : void 0;
208
- const requestRebuild = () => {
209
- pending = true;
210
- resolvePending?.();
211
- };
212
293
  const syncSubscriptions = async () => {
213
294
  const directories = parentDirectories(dependencies, options.cwd);
214
295
  for (const directory of directories) {
215
- if (subscriptions.has(directory)) continue;
216
- const subscription = await _parcel_watcher.subscribe(directory, (error, events) => {
217
- if (error) {
218
- node_process.default.stderr.write(`${error.message}\n`);
219
- return;
220
- }
221
- if (events.some((event) => !output || node_path.default.resolve(event.path) !== output)) requestRebuild();
222
- }, { ignore: ["**/node_modules/**", "**/.git/**"] });
223
- subscriptions.set(directory, subscription);
296
+ if (lifecycle.stopped || subscriptions.has(directory)) continue;
297
+ try {
298
+ const subscription = await _parcel_watcher.subscribe(directory, (error, events) => {
299
+ if (lifecycle.stopped) return;
300
+ if (error) {
301
+ report(error);
302
+ return;
303
+ }
304
+ try {
305
+ if (events.some((event) => !isBuildOutput(event.path, options.outputs))) {
306
+ pending = true;
307
+ lifecycle.wake();
308
+ }
309
+ } catch (error) {
310
+ report(error);
311
+ }
312
+ }, { ignore: ["**/node_modules/**", "**/.git/**"] });
313
+ subscriptions.set(directory, subscription);
314
+ } catch (error) {
315
+ throw new NativeWatcherError(error instanceof Error ? error.message : String(error));
316
+ }
224
317
  }
225
318
  for (const [directory, subscription] of subscriptions) if (!directories.has(directory)) {
226
319
  await subscription.unsubscribe();
227
320
  subscriptions.delete(directory);
228
321
  }
229
322
  };
230
- await syncSubscriptions();
231
- dependencies = await options.rebuild();
232
- await syncSubscriptions();
233
- await new Promise((resolve) => {
234
- const stop = () => {
235
- Promise.all([...subscriptions.values()].map((subscription) => subscription.unsubscribe())).finally(resolve);
236
- };
237
- node_process.default.once("SIGINT", stop);
238
- node_process.default.once("SIGTERM", stop);
239
- const loop = async () => {
240
- while (true) {
241
- if (pending) break;
242
- await new Promise((wake) => {
243
- resolvePending = wake;
244
- });
245
- resolvePending = void 0;
246
- }
247
- if (running) return;
248
- running = true;
323
+ try {
324
+ await syncSubscriptions();
325
+ if (lifecycle.stopped) return;
326
+ dependencies = await options.rebuild();
327
+ await syncSubscriptions();
328
+ while (!lifecycle.stopped) {
329
+ if (!pending) await lifecycle.wait();
330
+ if (lifecycle.stopped) break;
249
331
  pending = false;
250
332
  try {
251
333
  dependencies = await options.rebuild();
252
- await syncSubscriptions();
253
334
  } catch (error) {
254
- node_process.default.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
255
- } finally {
256
- running = false;
257
- if (pending) loop();
335
+ report(error);
336
+ continue;
258
337
  }
259
- };
260
- loop();
261
- });
338
+ await syncSubscriptions();
339
+ }
340
+ } finally {
341
+ await Promise.all([...subscriptions.values()].map((subscription) => subscription.unsubscribe()));
342
+ }
262
343
  }
263
344
  //#endregion
264
345
  //#region src/canonicalize.ts
@@ -353,7 +434,7 @@ async function runCanonicalize(argv) {
353
434
  }
354
435
  if (inputs.length === 0) throw new Error("No candidate groups provided");
355
436
  const records = inputs.map((input) => record(designSystem, input));
356
- const output = format === "json" ? JSON.stringify(records, null, 2) : format === "jsonl" ? records.map(JSON.stringify).join("\n") : records.map((item) => item.output).join("\n");
437
+ const output = format === "json" ? JSON.stringify(records, null, 2) : format === "jsonl" ? records.map((item) => JSON.stringify(item)).join("\n") : records.map((item) => item.output).join("\n");
357
438
  node_process.default.stdout.write(`${output}\n`);
358
439
  return 0;
359
440
  }
@@ -373,6 +454,7 @@ function sourceMapComment(value) {
373
454
  return `/*# sourceMappingURL=${value} */`;
374
455
  }
375
456
  async function buildOnce(options, stdinCss) {
457
+ validateBuildPaths(options);
376
458
  const inputCss = options.input === "-" ? stdinCss ?? await drainStdin() : options.input ? await node_fs_promises.default.readFile(options.input, "utf8") : DEFAULT_INPUT;
377
459
  const sourceOptions = {
378
460
  base: options.input && options.input !== "-" ? node_path.default.dirname(options.input) : options.cwd,
@@ -404,18 +486,18 @@ async function buildOnce(options, stdinCss) {
404
486
  sourceMap: Boolean(options.map)
405
487
  });
406
488
  css = node_buffer.Buffer.from(transformed.code).toString();
407
- map = transformed.map;
489
+ map = transformed.map || void 0;
408
490
  }
409
491
  if (options.map && map) {
410
492
  if (options.map === true) css += `\n${sourceMapComment(`data:application/json;base64,${node_buffer.Buffer.from(map).toString("base64")}`)}`;
411
493
  else {
412
- await writeFile$1(options.map, map);
413
494
  const mapBase = options.output && options.output !== "-" ? node_path.default.dirname(options.output) : options.cwd;
414
495
  css += `\n${sourceMapComment(node_path.default.relative(mapBase, options.map))}`;
415
496
  }
416
497
  }
417
498
  return {
418
499
  css,
500
+ map,
419
501
  dependencies: /* @__PURE__ */ new Set([
420
502
  ...options.input && options.input !== "-" ? [options.input] : [],
421
503
  ...source.dependencies,
@@ -428,11 +510,13 @@ async function buildOnce(options, stdinCss) {
428
510
  }
429
511
  async function runBuild(argv) {
430
512
  const options = parseBuildArgs(argv);
431
- if (!options.silent) node_process.default.stderr.write(`tailwindcss v${node_process.default.env.npm_package_version ?? ""}\n\n`);
513
+ if (!options.silent) node_process.default.stderr.write(`tailwindcss v${node_process.default.env["npm_package_version"] ?? ""}\n\n`);
432
514
  const stdinCss = options.input === "-" ? await drainStdin() : void 0;
433
515
  let previous = "";
434
516
  const rebuild = async () => {
435
517
  const result = await buildOnce(options, stdinCss);
518
+ validateBuildPaths(options);
519
+ if (typeof options.map === "string" && result.map) await writeFile$1(options.map, result.map);
436
520
  if (result.css !== previous) {
437
521
  if (options.output && options.output !== "-") await writeFile$1(options.output, result.css);
438
522
  else node_process.default.stdout.write(`${result.css}\n`);
@@ -448,7 +532,7 @@ async function runBuild(argv) {
448
532
  cwd: options.cwd,
449
533
  interval: options.pollInterval,
450
534
  mode: options.watchMode,
451
- output: options.output,
535
+ outputs: [options.output, typeof options.map === "string" ? options.map : void 0].filter((file) => Boolean(file && file !== "-")),
452
536
  rebuild
453
537
  });
454
538
  return 0;
@@ -459,7 +543,7 @@ async function runTailwindCli(rawArgv) {
459
543
  }
460
544
  //#endregion
461
545
  //#region package.json
462
- var version = "5.5.11";
546
+ var version = "5.5.12";
463
547
  //#endregion
464
548
  //#region src/constants.ts
465
549
  const WEAPP_TW_REQUIRED_NODE_VERSION_RANGE = "^22.18.0 || >=24.11.0";
@@ -1180,7 +1264,7 @@ async function runStatus(options) {
1180
1264
  status: "unnecessary",
1181
1265
  message: PATCH_COMMAND_OBSOLETE_NOTICE
1182
1266
  };
1183
- if (toBoolean(options.json, false)) {
1267
+ if (toBoolean(options["json"], false)) {
1184
1268
  _weapp_tailwindcss_logger.logger.log(JSON.stringify(payload, null, 2));
1185
1269
  return;
1186
1270
  }
@@ -1190,21 +1274,21 @@ async function runStatus(options) {
1190
1274
  async function runVscodeEntry(options) {
1191
1275
  const resolvedCwd = resolveCliCwd(options.cwd);
1192
1276
  const baseDir = resolvedCwd ?? node_process.default.cwd();
1193
- const cssEntry = readStringOption("css", options.css);
1277
+ const cssEntry = readStringOption("css", options["css"]);
1194
1278
  if (!cssEntry) throw new Error("Option \"--css\" is required.");
1195
1279
  const result = await generateVscodeIntellisenseEntry({
1196
1280
  baseDir,
1197
1281
  cssEntry,
1198
- output: readStringOption("output", options.output),
1199
- sources: readStringArrayOption("source", options.source),
1200
- force: toBoolean(options.force, false)
1282
+ output: readStringOption("output", options["output"]),
1283
+ sources: readStringArrayOption("source", options["source"]),
1284
+ force: toBoolean(options["force"], false)
1201
1285
  });
1202
1286
  _weapp_tailwindcss_logger.logger.success(`VS Code helper generated -> ${formatOutputPath(result.outputPath, resolvedCwd)}`);
1203
1287
  }
1204
1288
  async function runDoctor(options) {
1205
1289
  const report = createDoctorReport({ cwd: resolveCliCwd(options.cwd) });
1206
- _weapp_tailwindcss_logger.logger.log(toBoolean(options.json, false) ? JSON.stringify(report, null, 2) : formatDoctorReport(report));
1207
- if (hasDoctorFailure(report, toBoolean(options.strict, false))) node_process.default.exitCode = 1;
1290
+ _weapp_tailwindcss_logger.logger.log(toBoolean(options["json"], false) ? JSON.stringify(report, null, 2) : formatDoctorReport(report));
1291
+ if (hasDoctorFailure(report, toBoolean(options["strict"], false))) node_process.default.exitCode = 1;
1208
1292
  }
1209
1293
  async function runCli(argv = node_process.default.argv.slice(2)) {
1210
1294
  if (!semver.default.satisfies(node_process.default.versions.node, "^22.18.0 || >=24.11.0")) _weapp_tailwindcss_logger.logger.warn(`You are using Node.js ${node_process.default.versions.node}. For @weapp-tailwindcss/cli, Node.js version ${WEAPP_TW_REQUIRED_NODE_VERSION_RANGE} is required.`);
package/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@weapp-tailwindcss/cli",
3
3
  "type": "module",
4
- "version": "5.5.11",
4
+ "version": "5.5.12",
5
5
  "description": "Cross-platform Tailwind CSS CLI for Web and mini-program builds. 面向 Web 与小程序构建的跨端 Tailwind CSS CLI。",
6
6
  "author": "ice breaker <1324318532@qq.com>",
7
7
  "license": "MIT",
8
8
  "homepage": "https://tw.weapp.dev/docs/tools/weapp-tw-cli",
9
9
  "repository": {
10
10
  "type": "git",
11
- "url": "git+https://github.com/sonofmagic/weapp-tailwindcss.git",
11
+ "url": "git+https://github.com/weapp-tailwindcss/weapp-tailwindcss.git",
12
12
  "directory": "packages/cli"
13
13
  },
14
14
  "bugs": {
15
- "url": "https://github.com/sonofmagic/weapp-tailwindcss/issues"
15
+ "url": "https://github.com/weapp-tailwindcss/weapp-tailwindcss/issues"
16
16
  },
17
17
  "keywords": [
18
18
  "tailwindcss",
@@ -50,12 +50,12 @@
50
50
  },
51
51
  "dependencies": {
52
52
  "@parcel/watcher": "^2.6.0",
53
- "@weapp-tailwindcss/engine": "0.1.3",
54
- "@weapp-tailwindcss/logger": "2.0.3",
53
+ "@weapp-tailwindcss/engine": "0.1.4",
54
+ "@weapp-tailwindcss/logger": "2.0.4",
55
55
  "fast-glob": "^3.3.3",
56
56
  "lightningcss": "^1.33.0",
57
57
  "semver": "~7.8.5",
58
- "weapp-tailwindcss": "5.5.11"
58
+ "weapp-tailwindcss": "5.5.12"
59
59
  },
60
60
  "scripts": {
61
61
  "build": "tsdown",