tailwind-canonical 0.8.0 → 0.10.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/watch.js CHANGED
@@ -1,33 +1,162 @@
1
- import { watch as fsWatch } from 'node:fs';
2
- import { dirname, resolve } from 'node:path';
1
+ import { existsSync, watch as fsWatch, statSync, } from 'node:fs';
2
+ import { dirname, isAbsolute, relative, resolve, sep } from 'node:path';
3
+ import { DEFAULT_IGNORE } from '../core/scanner.js';
3
4
  import { errMsg, pluralize, timestamp } from './format.js';
4
- export function startWatch({ files, sink, processFile }) {
5
- const fileSet = new Set(files);
6
- const timers = new Map();
7
- const dirs = new Set(files.map((file) => dirname(file)));
8
- sink.log(`Watching ${pluralize(files.length, 'file')}... (Ctrl+C to stop)`);
9
- for (const dir of dirs) {
10
- fsWatch(dir, { recursive: true }, (_, filename) => {
11
- if (!filename)
5
+ function contains(root, path) {
6
+ const rel = relative(root, path);
7
+ return (rel === '' ||
8
+ (!isAbsolute(rel) && rel !== '..' && !rel.startsWith(`..${sep}`)));
9
+ }
10
+ /** Watch the existing ancestor of each target, including currently empty globs. */
11
+ export function watchRoots(targets, configDirs, targetKinds) {
12
+ const dirs = [...configDirs];
13
+ for (const target of targets) {
14
+ if (target.startsWith('!'))
15
+ continue;
16
+ const kind = targetKinds?.get(target.replace(/\\/g, '/'));
17
+ const literal = kind ? kind === 'literal' : existsSync(target);
18
+ const magic = literal ? -1 : target.search(/[*?{[(]/);
19
+ const prefix = target.slice(0, magic);
20
+ let dir = magic < 0 ? target : prefix.endsWith(sep) ? prefix : dirname(prefix);
21
+ if (existsSync(dir) && !statSync(dir).isDirectory())
22
+ dir = dirname(dir);
23
+ while (!existsSync(dir)) {
24
+ const parent = dirname(dir);
25
+ if (parent === dir)
26
+ break;
27
+ dir = parent;
28
+ }
29
+ dirs.push(dir);
30
+ }
31
+ const roots = [...new Set(dirs.map((dir) => resolve(dir)))];
32
+ return roots.filter((dir) => !roots.some((other) => other !== dir && contains(other, dir)));
33
+ }
34
+ export function startWatch({ files, roots, explicitTargets = [], sink, refresh, processFile, }) {
35
+ let fileSet = new Set(files);
36
+ const watchers = new Map();
37
+ const changed = new Set();
38
+ let timer;
39
+ let running = false;
40
+ let closed = false;
41
+ function close() {
42
+ if (closed)
43
+ return;
44
+ closed = true;
45
+ clearTimeout(timer);
46
+ changed.clear();
47
+ for (const watcher of watchers.values())
48
+ watcher.close();
49
+ watchers.clear();
50
+ process.removeListener('SIGINT', stop);
51
+ process.removeListener('SIGTERM', stop);
52
+ }
53
+ function stop() {
54
+ close();
55
+ sink.log('\nWatcher stopped.');
56
+ }
57
+ function fail(path, error) {
58
+ sink.error(`${timestamp()} ${path} — error: ${errMsg(error)}`);
59
+ }
60
+ function schedule() {
61
+ if (closed || running)
62
+ return;
63
+ clearTimeout(timer);
64
+ timer = setTimeout(() => {
65
+ void flush();
66
+ }, 50);
67
+ }
68
+ function synchronize(nextRoots) {
69
+ // Install replacements before closing the old set, so no event gap is added.
70
+ for (const root of nextRoots) {
71
+ if (watchers.has(root))
72
+ continue;
73
+ try {
74
+ const watcher = fsWatch(root, { recursive: true }, (_, filename) => {
75
+ if (closed)
76
+ return;
77
+ const path = filename ? resolve(root, String(filename)) : root;
78
+ const ignored = filename &&
79
+ String(filename)
80
+ .split(/[\\/]/)
81
+ .some((part) => DEFAULT_IGNORE.includes(part));
82
+ const explicitlyIncluded = explicitTargets.some((target) => contains(target, path) &&
83
+ !relative(target, path)
84
+ .split(/[\\/]/)
85
+ .some((part) => DEFAULT_IGNORE.includes(part)));
86
+ if (ignored && !explicitlyIncluded)
87
+ return;
88
+ changed.add(path);
89
+ schedule();
90
+ });
91
+ watchers.set(root, watcher);
92
+ watcher.on('error', (error) => {
93
+ if (closed)
94
+ return;
95
+ fail(root, error);
96
+ process.exitCode = 1;
97
+ close();
98
+ });
99
+ }
100
+ catch (error) {
101
+ fail(root, error);
102
+ process.exitCode = 1;
103
+ close();
104
+ return;
105
+ }
106
+ }
107
+ for (const [root, watcher] of watchers) {
108
+ if (!nextRoots.includes(root)) {
109
+ watcher.close();
110
+ watchers.delete(root);
111
+ }
112
+ }
113
+ }
114
+ async function flush() {
115
+ if (closed || running)
116
+ return;
117
+ running = true;
118
+ const batch = new Set(changed);
119
+ changed.clear();
120
+ try {
121
+ const next = await refresh(batch);
122
+ if (closed)
12
123
  return;
13
- const full = resolve(dir, filename);
14
- if (!fileSet.has(full))
124
+ explicitTargets = next.explicitTargets ?? [];
125
+ synchronize(next.roots);
126
+ if (closed)
15
127
  return;
16
- clearTimeout(timers.get(full));
17
- timers.set(full, setTimeout(() => {
18
- timers.delete(full);
128
+ const nextFiles = new Set(next.files);
129
+ for (const file of nextFiles) {
130
+ if (closed)
131
+ break;
132
+ if (!next.reprocessAll &&
133
+ fileSet.has(file) &&
134
+ ![...batch].some((path) => contains(path, file)))
135
+ continue;
19
136
  try {
20
- processFile(full);
137
+ processFile(file);
21
138
  }
22
139
  catch (error) {
23
- sink.error(`${timestamp()} ${full} — error: ${errMsg(error)}`);
140
+ fail(file, error);
24
141
  }
25
- }, 50));
26
- });
142
+ }
143
+ fileSet = nextFiles;
144
+ }
145
+ catch (error) {
146
+ if (!closed)
147
+ fail('watch refresh', error);
148
+ }
149
+ finally {
150
+ running = false;
151
+ if (changed.size > 0)
152
+ schedule();
153
+ }
27
154
  }
28
- process.once('SIGINT', () => {
29
- sink.log('\nWatcher stopped.');
30
- process.exit(0);
31
- });
32
- return { exitCode: 0, watching: true };
155
+ synchronize(roots);
156
+ if (!closed) {
157
+ process.once('SIGINT', stop);
158
+ process.once('SIGTERM', stop);
159
+ sink.log(`Watching ${pluralize(files.length, 'file')}... (Ctrl+C to stop)`);
160
+ }
161
+ return { exitCode: closed ? 1 : 0, watching: !closed, close };
33
162
  }
@@ -2,6 +2,7 @@ export type ClassStringOpts = {
2
2
  functionNames?: string[];
3
3
  attributeNames?: string[];
4
4
  customTextTokens?: Record<number, string>;
5
+ tailwindVersion?: 3 | 4;
5
6
  isSuppressed?: (line: number) => boolean;
6
7
  };
7
8
  /** Builds the shared {functionNames, attributeNames} extraction options from a config. */
@@ -9,6 +10,7 @@ export declare function toClassStringOpts(config: {
9
10
  functionNames?: string[];
10
11
  attributeNames?: string[];
11
12
  customTextTokens?: Record<number, string>;
13
+ tailwindVersion?: 3 | 4;
12
14
  }): ClassStringOpts;
13
15
  export declare const SINGLE_CLASS_REGEX: RegExp;
14
16
  export declare function replaceClassStrings(content: string, transform: (s: string) => string, opts?: ClassStringOpts): {