|
1 | 1 | #!/usr/bin/env node |
2 | 2 |
|
3 | | -import gaze from 'gaze'; |
| 3 | +import chokidar from 'chokidar'; |
4 | 4 | import fs from 'fs'; |
5 | 5 | import path from 'path'; |
6 | 6 | import minimist from 'minimist'; |
7 | 7 | import { introspectMachine } from './introspectMachine'; |
8 | 8 | import { extractMachines } from './extractMachines'; |
9 | 9 | import { printToFile, printJsFiles } from './printToFile'; |
10 | 10 |
|
11 | | -const { _: arrayArgs, ...objectArgs } = minimist(process.argv.slice(2)); |
| 11 | +const { _: patterns, ...objectArgs } = minimist(process.argv.slice(2)); |
| 12 | +const onlyOnce = objectArgs.once; |
12 | 13 |
|
13 | | -const [, , pattern] = process.argv; |
14 | | - |
15 | | -type RecordOfArrays = Record<string, string[]>; |
16 | | - |
17 | | -const flattenRecords = ( |
18 | | - filesAsRecord: Record<string, RecordOfArrays | string[]> | string[], |
19 | | -): string[] => { |
20 | | - if (Array.isArray(filesAsRecord)) { |
21 | | - return filesAsRecord; |
22 | | - } |
23 | | - return Object.values(filesAsRecord).reduce( |
24 | | - (array, paths) => array.concat(flattenRecords(paths)), |
25 | | - [] as any, |
26 | | - ) as string[]; |
27 | | -}; |
28 | | - |
29 | | -if (!pattern) { |
30 | | - console.log('You must pass a glob, for instance "**/src/**.machine.ts"'); |
| 14 | +if (patterns.length === 0) { |
| 15 | + console.log( |
| 16 | + 'You must pass at least one glob, for instance "**/src/**.machine.ts"', |
| 17 | + ); |
31 | 18 | process.exit(1); |
32 | 19 | } |
33 | 20 |
|
34 | | -const toRelative = (filePath: string) => path.relative(process.cwd(), filePath); |
35 | | - |
36 | 21 | const typedSuffix = /\.typed\.(js|ts|tsx|jsx)$/; |
37 | | - |
38 | 22 | const tsExtension = /\.(ts|tsx|js|jsx)$/; |
| 23 | +function isValidFile(filePath: string) { |
| 24 | + return !typedSuffix.test(filePath) && tsExtension.test(filePath); |
| 25 | +} |
39 | 26 |
|
40 | | -let fileCache: Record< |
| 27 | +const fileCache: Record< |
41 | 28 | string, |
42 | 29 | ReturnType<typeof introspectMachine> & { id: string } |
43 | 30 | > = {}; |
44 | 31 |
|
45 | | -gaze(pattern, {}, async function(err, watcher) { |
46 | | - if (err) { |
47 | | - console.log(err); |
48 | | - process.exit(1); |
49 | | - } |
| 32 | +printJsFiles(); |
| 33 | +if (!onlyOnce) { |
| 34 | + console.clear(); |
| 35 | +} |
50 | 36 |
|
51 | | - const filesAsRecord: Record<string, string[]> = watcher.watched() as any; |
| 37 | +const watcher = chokidar.watch(patterns, { |
| 38 | + persistent: !onlyOnce, |
| 39 | +}); |
52 | 40 |
|
53 | | - const files = flattenRecords(filesAsRecord); |
| 41 | +watcher.on('error', (err) => { |
| 42 | + console.error(err); |
| 43 | + process.exit(1); |
| 44 | +}); |
54 | 45 |
|
55 | | - const filteredFiles = files.filter((filePath) => { |
56 | | - return !typedSuffix.test(filePath) && tsExtension.test(filePath); |
57 | | - }); |
| 46 | +const toRelative = (filePath: string) => path.relative(process.cwd(), filePath); |
58 | 47 |
|
59 | | - if (filteredFiles.length === 0) { |
60 | | - console.log('No files found from that glob'); |
61 | | - process.exit(1); |
| 48 | +watcher.on('all', async (eventName, filePath) => { |
| 49 | + if (!isValidFile(filePath)) { |
| 50 | + return; |
| 51 | + } |
| 52 | + let message = ''; |
| 53 | + if (eventName === 'add') { |
| 54 | + message += `Scanning File: `.cyan.bold; |
| 55 | + await addToCache(filePath); |
| 56 | + } |
| 57 | + if (eventName === 'change') { |
| 58 | + message += `File Changed: `.yellow.bold; |
| 59 | + await addToCache(filePath); |
| 60 | + } |
| 61 | + if (eventName === 'unlink') { |
| 62 | + message += `File Deleted: `.red.bold; |
| 63 | + removeFromCache(filePath); |
| 64 | + } |
| 65 | + if (message) { |
| 66 | + console.log(`${message} ${toRelative(filePath).gray}`); |
62 | 67 | } |
63 | | - |
64 | | - printJsFiles(); |
65 | | - console.clear(); |
66 | | - |
67 | | - const addToCache = async (filePath: string) => { |
68 | | - let code: string = ''; |
69 | | - try { |
70 | | - code = fs.readFileSync(filePath, 'utf8'); |
71 | | - } catch (e) {} |
72 | | - if (!code) { |
73 | | - console.log(`Could not read from path ${filePath}`); |
74 | | - return; |
75 | | - } |
76 | | - if (!code.includes('@xstate/compiled')) { |
77 | | - return; |
78 | | - } |
79 | | - const machines = await extractMachines(filePath); |
80 | | - if (machines.length === 0) { |
81 | | - return; |
82 | | - } |
83 | | - const { machine, id } = machines[0]; |
84 | | - fileCache[filePath] = { ...introspectMachine(machine), id }; |
85 | | - }; |
86 | | - |
87 | | - await filteredFiles.reduce(async (promise, filePath) => { |
88 | | - await promise; |
89 | | - try { |
90 | | - console.log(`Scanning File: `.cyan.bold + toRelative(filePath).gray); |
91 | | - await addToCache(filePath); |
92 | | - } catch (e) { |
93 | | - console.log(e); |
94 | | - if (objectArgs.once) { |
95 | | - console.log('Could not complete due to errors'.red.bold); |
96 | | - // @ts-ignore |
97 | | - this.close(); |
98 | | - process.exit(1); |
99 | | - } |
100 | | - } |
101 | | - }, Promise.resolve()); |
102 | | - |
103 | 68 | printToFile(fileCache, objectArgs.outDir); |
| 69 | +}); |
104 | 70 |
|
105 | | - if (objectArgs.once) { |
| 71 | +process.on('exit', () => { |
| 72 | + if (onlyOnce) { |
| 73 | + // little trick because `ready` doesn't work well to know the inital run is complete |
106 | 74 | console.log('Completed!'.green.bold); |
107 | | - // @ts-ignore |
108 | | - this.close(); |
109 | | - process.exit(0); |
110 | 75 | } |
| 76 | +}); |
111 | 77 |
|
112 | | - // @ts-ignore |
113 | | - this.on('changed', async (filePath) => { |
114 | | - console.log(`File Changed: `.cyan.bold + toRelative(filePath).gray); |
115 | | - await addToCache(filePath); |
116 | | - printToFile(fileCache, objectArgs.outDir); |
117 | | - }); |
118 | | - // @ts-ignore |
119 | | - this.on('added', async (filePath) => { |
120 | | - console.log(`File Added: `.green.bold + toRelative(filePath).gray); |
121 | | - await addToCache(filePath); |
122 | | - printToFile(fileCache, objectArgs.outDir); |
123 | | - }); |
| 78 | +watcher.on('ready', async () => { |
| 79 | + if (!onlyOnce) { |
| 80 | + patterns.forEach((pattern) => { |
| 81 | + console.log(`Watching for file changes in: `.cyan.bold + pattern); |
| 82 | + }); |
| 83 | + } |
| 84 | +}); |
124 | 85 |
|
125 | | - // @ts-ignore |
126 | | - this.on('deleted', async (filePath) => { |
127 | | - console.log(`File Deleted: `.red.bold + toRelative(filePath).gray); |
128 | | - delete fileCache[filePath]; |
129 | | - printToFile(fileCache, objectArgs.outDir); |
130 | | - }); |
| 86 | +async function addToCache(filePath: string) { |
| 87 | + let code: string = ''; |
| 88 | + try { |
| 89 | + code = fs.readFileSync(filePath, 'utf8'); |
| 90 | + } catch (e) {} |
| 91 | + if (!code) { |
| 92 | + throw new Error(`Could not read from path ${filePath}`); |
| 93 | + } |
| 94 | + if (!code.includes('@xstate/compiled')) { |
| 95 | + return; |
| 96 | + } |
| 97 | + const machines = await extractMachines(filePath); |
| 98 | + if (machines.length === 0) { |
| 99 | + return; |
| 100 | + } |
| 101 | + const { machine, id } = machines[0]; |
| 102 | + fileCache[filePath] = { ...introspectMachine(machine), id }; |
| 103 | +} |
131 | 104 |
|
132 | | - console.log(`Watching for file changes in: `.cyan.bold + pattern.gray); |
133 | | -}); |
| 105 | +function removeFromCache(filePath: string) { |
| 106 | + delete fileCache[filePath]; |
| 107 | +} |
0 commit comments