tailwindcss 3.1.7 → 3.2.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.
Files changed (112) hide show
  1. package/README.md +12 -8
  2. package/lib/cli/build/deps.js +54 -0
  3. package/lib/cli/build/index.js +44 -0
  4. package/lib/cli/build/plugin.js +335 -0
  5. package/lib/cli/build/utils.js +78 -0
  6. package/lib/cli/build/watching.js +113 -0
  7. package/lib/cli/help/index.js +71 -0
  8. package/lib/cli/index.js +18 -0
  9. package/lib/cli/init/index.js +46 -0
  10. package/lib/cli/shared.js +12 -0
  11. package/lib/cli.js +11 -590
  12. package/lib/corePlugins.js +332 -108
  13. package/lib/css/preflight.css +5 -0
  14. package/lib/featureFlags.js +7 -4
  15. package/lib/index.js +6 -1
  16. package/lib/lib/content.js +167 -0
  17. package/lib/lib/defaultExtractor.js +15 -10
  18. package/lib/lib/detectNesting.js +2 -2
  19. package/lib/lib/evaluateTailwindFunctions.js +17 -1
  20. package/lib/lib/expandApplyAtRules.js +71 -37
  21. package/lib/lib/expandTailwindAtRules.js +10 -42
  22. package/lib/lib/findAtConfigPath.js +44 -0
  23. package/lib/lib/generateRules.js +181 -96
  24. package/lib/lib/normalizeTailwindDirectives.js +1 -1
  25. package/lib/lib/offsets.js +217 -0
  26. package/lib/lib/regex.js +1 -1
  27. package/lib/lib/setupContextUtils.js +339 -100
  28. package/lib/lib/setupTrackingContext.js +5 -39
  29. package/lib/lib/sharedState.js +2 -0
  30. package/lib/public/colors.js +1 -1
  31. package/lib/util/buildMediaQuery.js +6 -3
  32. package/lib/util/configurePlugins.js +1 -1
  33. package/lib/util/dataTypes.js +15 -19
  34. package/lib/util/formatVariantSelector.js +92 -8
  35. package/lib/util/getAllConfigs.js +14 -3
  36. package/lib/util/isValidArbitraryValue.js +1 -1
  37. package/lib/util/nameClass.js +3 -0
  38. package/lib/util/negateValue.js +15 -2
  39. package/lib/util/normalizeConfig.js +17 -3
  40. package/lib/util/normalizeScreens.js +100 -3
  41. package/lib/util/parseAnimationValue.js +1 -1
  42. package/lib/util/parseBoxShadowValue.js +1 -1
  43. package/lib/util/parseDependency.js +33 -54
  44. package/lib/util/parseGlob.js +34 -0
  45. package/lib/util/parseObjectStyles.js +1 -1
  46. package/lib/util/pluginUtils.js +86 -17
  47. package/lib/util/resolveConfig.js +3 -3
  48. package/lib/util/splitAtTopLevelOnly.js +31 -81
  49. package/lib/util/transformThemeValue.js +9 -2
  50. package/lib/util/validateConfig.js +1 -1
  51. package/lib/util/validateFormalSyntax.js +24 -0
  52. package/package.json +14 -12
  53. package/peers/.DS_Store +0 -0
  54. package/peers/.svgo.yml +75 -0
  55. package/peers/index.js +3690 -2274
  56. package/peers/orders/concentric-css.json +299 -0
  57. package/peers/orders/smacss.json +299 -0
  58. package/peers/orders/source.json +295 -0
  59. package/plugin.d.ts +3 -3
  60. package/scripts/release-channel.js +18 -0
  61. package/scripts/release-notes.js +21 -0
  62. package/src/.DS_Store +0 -0
  63. package/src/cli/build/deps.js +56 -0
  64. package/src/cli/build/index.js +45 -0
  65. package/src/cli/build/plugin.js +397 -0
  66. package/src/cli/build/utils.js +76 -0
  67. package/src/cli/build/watching.js +134 -0
  68. package/src/cli/help/index.js +70 -0
  69. package/src/cli/index.js +3 -0
  70. package/src/cli/init/index.js +50 -0
  71. package/src/cli/shared.js +5 -0
  72. package/src/cli.js +4 -696
  73. package/src/corePlugins.js +262 -39
  74. package/src/css/preflight.css +5 -0
  75. package/src/featureFlags.js +12 -2
  76. package/src/index.js +5 -0
  77. package/src/lib/content.js +205 -0
  78. package/src/lib/defaultExtractor.js +3 -0
  79. package/src/lib/evaluateTailwindFunctions.js +22 -1
  80. package/src/lib/expandApplyAtRules.js +76 -29
  81. package/src/lib/expandTailwindAtRules.js +8 -46
  82. package/src/lib/findAtConfigPath.js +48 -0
  83. package/src/lib/generateRules.js +224 -105
  84. package/src/lib/offsets.js +270 -0
  85. package/src/lib/setupContextUtils.js +376 -89
  86. package/src/lib/setupTrackingContext.js +4 -45
  87. package/src/lib/sharedState.js +2 -0
  88. package/src/util/buildMediaQuery.js +5 -3
  89. package/src/util/dataTypes.js +15 -17
  90. package/src/util/formatVariantSelector.js +113 -9
  91. package/src/util/getAllConfigs.js +14 -2
  92. package/src/util/nameClass.js +4 -0
  93. package/src/util/negateValue.js +10 -2
  94. package/src/util/normalizeConfig.js +22 -2
  95. package/src/util/normalizeScreens.js +99 -4
  96. package/src/util/parseBoxShadowValue.js +1 -1
  97. package/src/util/parseDependency.js +37 -42
  98. package/src/util/parseGlob.js +24 -0
  99. package/src/util/pluginUtils.js +90 -14
  100. package/src/util/resolveConfig.js +2 -2
  101. package/src/util/splitAtTopLevelOnly.js +23 -49
  102. package/src/util/transformThemeValue.js +9 -1
  103. package/src/util/validateFormalSyntax.js +34 -0
  104. package/stubs/defaultConfig.stub.js +19 -3
  105. package/tmp.css +11 -0
  106. package/tmp.dependency-graph.js +2 -0
  107. package/tmp.in.css +3 -0
  108. package/tmp.js +0 -0
  109. package/tmp.out.css +524 -0
  110. package/types/config.d.ts +47 -13
  111. package/types/generated/default-theme.d.ts +11 -0
  112. package/CHANGELOG.md +0 -2222
@@ -0,0 +1,397 @@
1
+ // @ts-check
2
+
3
+ import path from 'path'
4
+ import fs from 'fs'
5
+ import postcssrc from 'postcss-load-config'
6
+ import { lilconfig } from 'lilconfig'
7
+ import loadPlugins from 'postcss-load-config/src/plugins' // Little bit scary, looking at private/internal API
8
+ import loadOptions from 'postcss-load-config/src/options' // Little bit scary, looking at private/internal API
9
+
10
+ import tailwind from '../../processTailwindFeatures'
11
+ import { loadAutoprefixer, loadCssNano, loadPostcss, loadPostcssImport } from './deps'
12
+ import { formatNodes, drainStdin, outputFile } from './utils'
13
+ import { env } from '../shared'
14
+ import resolveConfig from '../../../resolveConfig.js'
15
+ import getModuleDependencies from '../../lib/getModuleDependencies.js'
16
+ import { parseCandidateFiles } from '../../lib/content.js'
17
+ import { createWatcher } from './watching.js'
18
+ import fastGlob from 'fast-glob'
19
+ import { findAtConfigPath } from '../../lib/findAtConfigPath.js'
20
+ import log from '../../util/log'
21
+
22
+ /**
23
+ *
24
+ * @param {string} [customPostCssPath ]
25
+ * @returns
26
+ */
27
+ async function loadPostCssPlugins(customPostCssPath) {
28
+ let config = customPostCssPath
29
+ ? await (async () => {
30
+ let file = path.resolve(customPostCssPath)
31
+
32
+ // Implementation, see: https://unpkg.com/browse/postcss-load-config@3.1.0/src/index.js
33
+ // @ts-ignore
34
+ let { config = {} } = await lilconfig('postcss').load(file)
35
+ if (typeof config === 'function') {
36
+ config = config()
37
+ } else {
38
+ config = Object.assign({}, config)
39
+ }
40
+
41
+ if (!config.plugins) {
42
+ config.plugins = []
43
+ }
44
+
45
+ return {
46
+ file,
47
+ plugins: loadPlugins(config, file),
48
+ options: loadOptions(config, file),
49
+ }
50
+ })()
51
+ : await postcssrc()
52
+
53
+ let configPlugins = config.plugins
54
+
55
+ let configPluginTailwindIdx = configPlugins.findIndex((plugin) => {
56
+ if (typeof plugin === 'function' && plugin.name === 'tailwindcss') {
57
+ return true
58
+ }
59
+
60
+ if (typeof plugin === 'object' && plugin !== null && plugin.postcssPlugin === 'tailwindcss') {
61
+ return true
62
+ }
63
+
64
+ return false
65
+ })
66
+
67
+ let beforePlugins =
68
+ configPluginTailwindIdx === -1 ? [] : configPlugins.slice(0, configPluginTailwindIdx)
69
+ let afterPlugins =
70
+ configPluginTailwindIdx === -1
71
+ ? configPlugins
72
+ : configPlugins.slice(configPluginTailwindIdx + 1)
73
+
74
+ return [beforePlugins, afterPlugins, config.options]
75
+ }
76
+
77
+ function loadBuiltinPostcssPlugins() {
78
+ let postcss = loadPostcss()
79
+ let IMPORT_COMMENT = '__TAILWIND_RESTORE_IMPORT__: '
80
+ return [
81
+ [
82
+ (root) => {
83
+ root.walkAtRules('import', (rule) => {
84
+ if (rule.params.slice(1).startsWith('tailwindcss/')) {
85
+ rule.after(postcss.comment({ text: IMPORT_COMMENT + rule.params }))
86
+ rule.remove()
87
+ }
88
+ })
89
+ },
90
+ loadPostcssImport(),
91
+ (root) => {
92
+ root.walkComments((rule) => {
93
+ if (rule.text.startsWith(IMPORT_COMMENT)) {
94
+ rule.after(
95
+ postcss.atRule({
96
+ name: 'import',
97
+ params: rule.text.replace(IMPORT_COMMENT, ''),
98
+ })
99
+ )
100
+ rule.remove()
101
+ }
102
+ })
103
+ },
104
+ ],
105
+ [],
106
+ {},
107
+ ]
108
+ }
109
+
110
+ let state = {
111
+ /** @type {any} */
112
+ context: null,
113
+
114
+ /** @type {ReturnType<typeof createWatcher> | null} */
115
+ watcher: null,
116
+
117
+ /** @type {{content: string, extension: string}[]} */
118
+ changedContent: [],
119
+
120
+ configDependencies: new Set(),
121
+ contextDependencies: new Set(),
122
+
123
+ /** @type {import('../../lib/content.js').ContentPath[]} */
124
+ contentPaths: [],
125
+
126
+ refreshContentPaths() {
127
+ this.contentPaths = parseCandidateFiles(this.context, this.context?.tailwindConfig)
128
+ },
129
+
130
+ get config() {
131
+ return this.context.tailwindConfig
132
+ },
133
+
134
+ get contentPatterns() {
135
+ return {
136
+ all: this.contentPaths.map((contentPath) => contentPath.pattern),
137
+ dynamic: this.contentPaths
138
+ .filter((contentPath) => contentPath.glob !== undefined)
139
+ .map((contentPath) => contentPath.pattern),
140
+ }
141
+ },
142
+
143
+ loadConfig(configPath, content) {
144
+ if (this.watcher && configPath) {
145
+ this.refreshConfigDependencies(configPath)
146
+ }
147
+
148
+ let config = configPath ? require(configPath) : {}
149
+
150
+ // @ts-ignore
151
+ config = resolveConfig(config, { content: { files: [] } })
152
+
153
+ // Override content files if `--content` has been passed explicitly
154
+ if (content?.length > 0) {
155
+ config.content.files = content
156
+ }
157
+
158
+ return config
159
+ },
160
+
161
+ refreshConfigDependencies(configPath) {
162
+ env.DEBUG && console.time('Module dependencies')
163
+
164
+ for (let file of this.configDependencies) {
165
+ delete require.cache[require.resolve(file)]
166
+ }
167
+
168
+ if (configPath) {
169
+ let deps = getModuleDependencies(configPath).map(({ file }) => file)
170
+
171
+ for (let dependency of deps) {
172
+ this.configDependencies.add(dependency)
173
+ }
174
+ }
175
+
176
+ env.DEBUG && console.timeEnd('Module dependencies')
177
+ },
178
+
179
+ readContentPaths() {
180
+ let content = []
181
+
182
+ // Resolve globs from the content config
183
+ // TODO: When we make the postcss plugin async-capable this can become async
184
+ let files = fastGlob.sync(this.contentPatterns.all)
185
+
186
+ for (let file of files) {
187
+ content.push({
188
+ content: fs.readFileSync(path.resolve(file), 'utf8'),
189
+ extension: path.extname(file).slice(1),
190
+ })
191
+ }
192
+
193
+ // Resolve raw content in the tailwind config
194
+ let rawContent = this.config.content.files.filter((file) => {
195
+ return file !== null && typeof file === 'object'
196
+ })
197
+
198
+ for (let { raw: content, extension = 'html' } of rawContent) {
199
+ content.push({ content, extension })
200
+ }
201
+
202
+ return content
203
+ },
204
+
205
+ getContext({ createContext, cliConfigPath, root, result, content }) {
206
+ if (this.context) {
207
+ this.context.changedContent = this.changedContent.splice(0)
208
+
209
+ return this.context
210
+ }
211
+
212
+ env.DEBUG && console.time('Searching for config')
213
+ let configPath = findAtConfigPath(root, result) ?? cliConfigPath
214
+ env.DEBUG && console.timeEnd('Searching for config')
215
+
216
+ env.DEBUG && console.time('Loading config')
217
+ let config = this.loadConfig(configPath, content)
218
+ env.DEBUG && console.timeEnd('Loading config')
219
+
220
+ env.DEBUG && console.time('Creating context')
221
+ this.context = createContext(config, [])
222
+ Object.assign(this.context, {
223
+ userConfigPath: configPath,
224
+ })
225
+ env.DEBUG && console.timeEnd('Creating context')
226
+
227
+ env.DEBUG && console.time('Resolving content paths')
228
+ this.refreshContentPaths()
229
+ env.DEBUG && console.timeEnd('Resolving content paths')
230
+
231
+ if (this.watcher) {
232
+ env.DEBUG && console.time('Watch new files')
233
+ this.watcher.refreshWatchedFiles()
234
+ env.DEBUG && console.timeEnd('Watch new files')
235
+ }
236
+
237
+ env.DEBUG && console.time('Reading content files')
238
+ for (let file of this.readContentPaths()) {
239
+ this.context.changedContent.push(file)
240
+ }
241
+ env.DEBUG && console.timeEnd('Reading content files')
242
+
243
+ return this.context
244
+ },
245
+ }
246
+
247
+ export async function createProcessor(args, cliConfigPath) {
248
+ let postcss = loadPostcss()
249
+
250
+ let input = args['--input']
251
+ let output = args['--output']
252
+ let includePostCss = args['--postcss']
253
+ let customPostCssPath = typeof args['--postcss'] === 'string' ? args['--postcss'] : undefined
254
+
255
+ let [beforePlugins, afterPlugins, postcssOptions] = includePostCss
256
+ ? await loadPostCssPlugins(customPostCssPath)
257
+ : loadBuiltinPostcssPlugins()
258
+
259
+ if (args['--purge']) {
260
+ log.warn('purge-flag-deprecated', [
261
+ 'The `--purge` flag has been deprecated.',
262
+ 'Please use `--content` instead.',
263
+ ])
264
+
265
+ if (!args['--content']) {
266
+ args['--content'] = args['--purge']
267
+ }
268
+ }
269
+
270
+ let content = args['--content']?.split(/(?<!{[^}]+),/) ?? []
271
+
272
+ let tailwindPlugin = () => {
273
+ return {
274
+ postcssPlugin: 'tailwindcss',
275
+ Once(root, { result }) {
276
+ env.DEBUG && console.time('Compiling CSS')
277
+ tailwind(({ createContext }) => {
278
+ console.error()
279
+ console.error('Rebuilding...')
280
+
281
+ return () => {
282
+ return state.getContext({
283
+ createContext,
284
+ cliConfigPath,
285
+ root,
286
+ result,
287
+ content,
288
+ })
289
+ }
290
+ })(root, result)
291
+ env.DEBUG && console.timeEnd('Compiling CSS')
292
+ },
293
+ }
294
+ }
295
+
296
+ tailwindPlugin.postcss = true
297
+
298
+ let plugins = [
299
+ ...beforePlugins,
300
+ tailwindPlugin,
301
+ !args['--minify'] && formatNodes,
302
+ ...afterPlugins,
303
+ !args['--no-autoprefixer'] && loadAutoprefixer(),
304
+ args['--minify'] && loadCssNano(),
305
+ ].filter(Boolean)
306
+
307
+ /** @type {import('postcss').Processor} */
308
+ // @ts-ignore
309
+ let processor = postcss(plugins)
310
+
311
+ async function readInput() {
312
+ // Piping in data, let's drain the stdin
313
+ if (input === '-') {
314
+ return drainStdin()
315
+ }
316
+
317
+ // Input file has been provided
318
+ if (input) {
319
+ return fs.promises.readFile(path.resolve(input), 'utf8')
320
+ }
321
+
322
+ // No input file provided, fallback to default atrules
323
+ return '@tailwind base; @tailwind components; @tailwind utilities'
324
+ }
325
+
326
+ async function build() {
327
+ let start = process.hrtime.bigint()
328
+
329
+ return readInput()
330
+ .then((css) => processor.process(css, { ...postcssOptions, from: input, to: output }))
331
+ .then((result) => {
332
+ if (!output) {
333
+ process.stdout.write(result.css)
334
+ return
335
+ }
336
+
337
+ return Promise.all([
338
+ outputFile(output, result.css),
339
+ result.map && outputFile(output + '.map', result.map.toString()),
340
+ ])
341
+ })
342
+ .then(() => {
343
+ let end = process.hrtime.bigint()
344
+ console.error()
345
+ console.error('Done in', (end - start) / BigInt(1e6) + 'ms.')
346
+ })
347
+ }
348
+
349
+ /**
350
+ * @param {{file: string, content(): Promise<string>, extension: string}[]} changes
351
+ */
352
+ async function parseChanges(changes) {
353
+ return Promise.all(
354
+ changes.map(async (change) => ({
355
+ content: await change.content(),
356
+ extension: change.extension,
357
+ }))
358
+ )
359
+ }
360
+
361
+ if (input !== undefined && input !== '-') {
362
+ state.contextDependencies.add(path.resolve(input))
363
+ }
364
+
365
+ return {
366
+ build,
367
+ watch: async () => {
368
+ state.watcher = createWatcher(args, {
369
+ state,
370
+
371
+ /**
372
+ * @param {{file: string, content(): Promise<string>, extension: string}[]} changes
373
+ */
374
+ async rebuild(changes) {
375
+ let needsNewContext = changes.some((change) => {
376
+ return (
377
+ state.configDependencies.has(change.file) ||
378
+ state.contextDependencies.has(change.file)
379
+ )
380
+ })
381
+
382
+ if (needsNewContext) {
383
+ state.context = null
384
+ } else {
385
+ for (let change of await parseChanges(changes)) {
386
+ state.changedContent.push(change)
387
+ }
388
+ }
389
+
390
+ return build()
391
+ },
392
+ })
393
+
394
+ await build()
395
+ },
396
+ }
397
+ }
@@ -0,0 +1,76 @@
1
+ // @ts-check
2
+
3
+ import fs from 'fs'
4
+ import path from 'path'
5
+
6
+ export function indentRecursive(node, indent = 0) {
7
+ node.each &&
8
+ node.each((child, i) => {
9
+ if (!child.raws.before || !child.raws.before.trim() || child.raws.before.includes('\n')) {
10
+ child.raws.before = `\n${node.type !== 'rule' && i > 0 ? '\n' : ''}${' '.repeat(indent)}`
11
+ }
12
+ child.raws.after = `\n${' '.repeat(indent)}`
13
+ indentRecursive(child, indent + 1)
14
+ })
15
+ }
16
+
17
+ export function formatNodes(root) {
18
+ indentRecursive(root)
19
+ if (root.first) {
20
+ root.first.raws.before = ''
21
+ }
22
+ }
23
+
24
+ /**
25
+ * When rapidly saving files atomically a couple of situations can happen:
26
+ * - The file is missing since the external program has deleted it by the time we've gotten around to reading it from the earlier save.
27
+ * - The file is being written to by the external program by the time we're going to read it and is thus treated as busy because a lock is held.
28
+ *
29
+ * To work around this we retry reading the file a handful of times with a delay between each attempt
30
+ *
31
+ * @param {string} path
32
+ * @param {number} tries
33
+ * @returns {Promise<string | undefined>}
34
+ * @throws {Error} If the file is still missing or busy after the specified number of tries
35
+ */
36
+ export async function readFileWithRetries(path, tries = 5) {
37
+ for (let n = 0; n <= tries; n++) {
38
+ try {
39
+ return await fs.promises.readFile(path, 'utf8')
40
+ } catch (err) {
41
+ if (n !== tries) {
42
+ if (err.code === 'ENOENT' || err.code === 'EBUSY') {
43
+ await new Promise((resolve) => setTimeout(resolve, 10))
44
+
45
+ continue
46
+ }
47
+ }
48
+
49
+ throw err
50
+ }
51
+ }
52
+ }
53
+
54
+ export function drainStdin() {
55
+ return new Promise((resolve, reject) => {
56
+ let result = ''
57
+ process.stdin.on('data', (chunk) => {
58
+ result += chunk
59
+ })
60
+ process.stdin.on('end', () => resolve(result))
61
+ process.stdin.on('error', (err) => reject(err))
62
+ })
63
+ }
64
+
65
+ export async function outputFile(file, newContents) {
66
+ try {
67
+ let currentContents = await fs.promises.readFile(file, 'utf8')
68
+ if (currentContents === newContents) {
69
+ return // Skip writing the file
70
+ }
71
+ } catch {}
72
+
73
+ // Write the file
74
+ await fs.promises.mkdir(path.dirname(file), { recursive: true })
75
+ await fs.promises.writeFile(file, newContents, 'utf8')
76
+ }
@@ -0,0 +1,134 @@
1
+ // @ts-check
2
+
3
+ import chokidar from 'chokidar'
4
+ import fs from 'fs'
5
+ import micromatch from 'micromatch'
6
+ import normalizePath from 'normalize-path'
7
+ import path from 'path'
8
+
9
+ import { readFileWithRetries } from './utils.js'
10
+
11
+ /**
12
+ *
13
+ * @param {*} args
14
+ * @param {{ state, rebuild(changedFiles: any[]): Promise<any> }} param1
15
+ * @returns {{
16
+ * fswatcher: import('chokidar').FSWatcher,
17
+ * refreshWatchedFiles(): void,
18
+ * }}
19
+ */
20
+ export function createWatcher(args, { state, rebuild }) {
21
+ let shouldPoll = args['--poll']
22
+ let shouldCoalesceWriteEvents = shouldPoll || process.platform === 'win32'
23
+
24
+ // Polling interval in milliseconds
25
+ // Used only when polling or coalescing add/change events on Windows
26
+ let pollInterval = 10
27
+
28
+ let watcher = chokidar.watch([], {
29
+ // Force checking for atomic writes in all situations
30
+ // This causes chokidar to wait up to 100ms for a file to re-added after it's been unlinked
31
+ // This only works when watching directories though
32
+ atomic: true,
33
+
34
+ usePolling: shouldPoll,
35
+ interval: shouldPoll ? pollInterval : undefined,
36
+ ignoreInitial: true,
37
+ awaitWriteFinish: shouldCoalesceWriteEvents
38
+ ? {
39
+ stabilityThreshold: 50,
40
+ pollInterval: pollInterval,
41
+ }
42
+ : false,
43
+ })
44
+
45
+ let chain = Promise.resolve()
46
+ let pendingRebuilds = new Set()
47
+ let changedContent = []
48
+
49
+ /**
50
+ *
51
+ * @param {*} file
52
+ * @param {(() => Promise<string>) | null} content
53
+ */
54
+ function recordChangedFile(file, content = null) {
55
+ file = path.resolve(file)
56
+
57
+ content = content ?? (async () => await fs.promises.readFile(file, 'utf8'))
58
+
59
+ changedContent.push({
60
+ file,
61
+ content,
62
+ extension: path.extname(file).slice(1),
63
+ })
64
+
65
+ chain = chain.then(() => rebuild(changedContent))
66
+
67
+ return chain
68
+ }
69
+
70
+ watcher.on('change', (file) => recordChangedFile(file))
71
+ watcher.on('add', (file) => recordChangedFile(file))
72
+
73
+ // Restore watching any files that are "removed"
74
+ // This can happen when a file is pseudo-atomically replaced (a copy is created, overwritten, the old one is unlinked, and the new one is renamed)
75
+ // TODO: An an optimization we should allow removal when the config changes
76
+ watcher.on('unlink', (file) => {
77
+ file = normalizePath(file)
78
+
79
+ // Only re-add the file if it's not covered by a dynamic pattern
80
+ if (!micromatch.some([file], state.contentPatterns.dynamic)) {
81
+ watcher.add(file)
82
+ }
83
+ })
84
+
85
+ // Some applications such as Visual Studio (but not VS Code)
86
+ // will only fire a rename event for atomic writes and not a change event
87
+ // This is very likely a chokidar bug but it's one we need to work around
88
+ // We treat this as a change event and rebuild the CSS
89
+ watcher.on('raw', (evt, filePath, meta) => {
90
+ if (evt !== 'rename') {
91
+ return
92
+ }
93
+
94
+ let watchedPath = meta.watchedPath
95
+
96
+ // Watched path might be the file itself
97
+ // Or the directory it is in
98
+ filePath = watchedPath.endsWith(filePath) ? watchedPath : path.join(watchedPath, filePath)
99
+
100
+ // Skip this event since the files it is for does not match any of the registered content globs
101
+ if (!micromatch.some([filePath], state.contentPatterns.all)) {
102
+ return
103
+ }
104
+
105
+ // Skip since we've already queued a rebuild for this file that hasn't happened yet
106
+ if (pendingRebuilds.has(filePath)) {
107
+ return
108
+ }
109
+
110
+ pendingRebuilds.add(filePath)
111
+
112
+ chain = chain.then(async () => {
113
+ let content
114
+
115
+ try {
116
+ content = await readFileWithRetries(path.resolve(filePath))
117
+ } finally {
118
+ pendingRebuilds.delete(filePath)
119
+ }
120
+
121
+ return recordChangedFile(filePath, () => content)
122
+ })
123
+ })
124
+
125
+ return {
126
+ fswatcher: watcher,
127
+
128
+ refreshWatchedFiles() {
129
+ watcher.add(Array.from(state.contextDependencies))
130
+ watcher.add(Array.from(state.configDependencies))
131
+ watcher.add(state.contentPatterns.all)
132
+ },
133
+ }
134
+ }
@@ -0,0 +1,70 @@
1
+ // @ts-check
2
+ import packageJson from '../../../package.json'
3
+
4
+ export function help({ message, usage, commands, options }) {
5
+ let indent = 2
6
+
7
+ // Render header
8
+ console.log()
9
+ console.log(`${packageJson.name} v${packageJson.version}`)
10
+
11
+ // Render message
12
+ if (message) {
13
+ console.log()
14
+ for (let msg of message.split('\n')) {
15
+ console.log(msg)
16
+ }
17
+ }
18
+
19
+ // Render usage
20
+ if (usage && usage.length > 0) {
21
+ console.log()
22
+ console.log('Usage:')
23
+ for (let example of usage) {
24
+ console.log(' '.repeat(indent), example)
25
+ }
26
+ }
27
+
28
+ // Render commands
29
+ if (commands && commands.length > 0) {
30
+ console.log()
31
+ console.log('Commands:')
32
+ for (let command of commands) {
33
+ console.log(' '.repeat(indent), command)
34
+ }
35
+ }
36
+
37
+ // Render options
38
+ if (options) {
39
+ let groupedOptions = {}
40
+ for (let [key, value] of Object.entries(options)) {
41
+ if (typeof value === 'object') {
42
+ groupedOptions[key] = { ...value, flags: [key] }
43
+ } else {
44
+ groupedOptions[value].flags.push(key)
45
+ }
46
+ }
47
+
48
+ console.log()
49
+ console.log('Options:')
50
+ for (let { flags, description, deprecated } of Object.values(groupedOptions)) {
51
+ if (deprecated) continue
52
+
53
+ if (flags.length === 1) {
54
+ console.log(
55
+ ' '.repeat(indent + 4 /* 4 = "-i, ".length */),
56
+ flags.slice().reverse().join(', ').padEnd(20, ' '),
57
+ description
58
+ )
59
+ } else {
60
+ console.log(
61
+ ' '.repeat(indent),
62
+ flags.slice().reverse().join(', ').padEnd(24, ' '),
63
+ description
64
+ )
65
+ }
66
+ }
67
+ }
68
+
69
+ console.log()
70
+ }
@@ -0,0 +1,3 @@
1
+ export * from './build'
2
+ export * from './config'
3
+ export * from './content'