@jayrdeaton/scripts 1.1.1 → 1.1.3
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/README.md +144 -0
- package/package.json +1 -1
- package/src/commands/base64.mjs +2 -2
- package/src/commands/binary.mjs +2 -2
- package/src/commands/check-scripts.mjs +145 -0
- package/src/commands/clean-junk.mjs +2 -2
- package/src/commands/find-dep.mjs +3 -7
- package/src/commands/find-script.mjs +81 -0
- package/src/commands/folder-sizes.mjs +2 -1
- package/src/commands/including.mjs +61 -0
- package/src/commands/missing.mjs +61 -0
- package/src/commands/new-expo-project.mjs +2 -2
- package/src/commands/rename-season.mjs +1 -1
- package/src/commands/repo-rulesets.mjs +73 -0
- package/src/commands/repo-status.mjs +2 -1
- package/src/commands/sync-peers.mjs +166 -0
- package/src/commands/update-boilerplate.mjs +1 -1
- package/src/commands/update-deps.mjs +1 -1
- package/src/commands/yalc-check.mjs +95 -0
package/README.md
CHANGED
|
@@ -69,6 +69,22 @@ Example: `jrd check-domains ??fu.com`
|
|
|
69
69
|
|
|
70
70
|
---
|
|
71
71
|
|
|
72
|
+
### `jrd check-scripts`
|
|
73
|
+
|
|
74
|
+
Compare `package.json` scripts across projects for consistency. Highlights scripts whose values differ from the most common value (or a reference project).
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
jrd check-scripts [scripts...] [options]
|
|
78
|
+
|
|
79
|
+
Options:
|
|
80
|
+
-d, --dir <dir> Root directory to scan (default: ~/Developer)
|
|
81
|
+
-r, --ref <ref> Reference project name to compare against
|
|
82
|
+
-a, --all Show all scripts, including matching ones
|
|
83
|
+
-f, --flat Show one line per project instead of grouping by value
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
72
88
|
### `jrd clean-builds`
|
|
73
89
|
|
|
74
90
|
Delete build artifacts (`build`, `dist`, `ios`, `android`) across one or more repos. Dry run by default.
|
|
@@ -118,6 +134,36 @@ Options:
|
|
|
118
134
|
|
|
119
135
|
---
|
|
120
136
|
|
|
137
|
+
### `jrd find-dep`
|
|
138
|
+
|
|
139
|
+
Find projects in a directory that use any of the given dependencies (searches `dependencies`, `devDependencies`, and `peerDependencies`).
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
jrd find-dep <deps...> [options]
|
|
143
|
+
|
|
144
|
+
Options:
|
|
145
|
+
-d, --dir <dir> Root directory to scan (default: ~/Developer)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Example: `jrd find-dep react-native expo`
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
### `jrd find-script`
|
|
153
|
+
|
|
154
|
+
Find projects in a directory whose `package.json` scripts contain an exact command value.
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
jrd find-script <command> [options]
|
|
158
|
+
|
|
159
|
+
Options:
|
|
160
|
+
-d, --dir <dir> Root directory to scan (default: ~/Developer)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Example: `jrd find-script "eslint . --fix"`
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
121
167
|
### `jrd focus`
|
|
122
168
|
|
|
123
169
|
Bring an application to the front using AppleScript.
|
|
@@ -130,6 +176,21 @@ Default app is `Terminal`.
|
|
|
130
176
|
|
|
131
177
|
---
|
|
132
178
|
|
|
179
|
+
### `jrd including`
|
|
180
|
+
|
|
181
|
+
Find projects in a directory that contain a given file.
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
jrd including <file> [options]
|
|
185
|
+
|
|
186
|
+
Options:
|
|
187
|
+
-d, --dir <dir> Root directory to scan (default: ~/Developer)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Example: `jrd including PLAN.md`
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
133
194
|
### `jrd folder-sizes`
|
|
134
195
|
|
|
135
196
|
List all subdirectories sorted by size, largest first.
|
|
@@ -140,6 +201,36 @@ jrd folder-sizes [dir]
|
|
|
140
201
|
|
|
141
202
|
---
|
|
142
203
|
|
|
204
|
+
### `jrd missing`
|
|
205
|
+
|
|
206
|
+
Find projects in a directory that are missing a given file.
|
|
207
|
+
|
|
208
|
+
```
|
|
209
|
+
jrd missing <file> [options]
|
|
210
|
+
|
|
211
|
+
Options:
|
|
212
|
+
-d, --dir <dir> Root directory to scan (default: ~/Developer)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Example: `jrd missing README.md`
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
### `jrd new-expo-project`
|
|
220
|
+
|
|
221
|
+
Bootstrap a new Expo project from the boilerplate repo. Clones or updates the boilerplate, copies it to `~/Developer/<Name>`, rewrites `package.json` and `app.json` with the derived name/slug/bundle identifiers, and creates an initial git commit.
|
|
222
|
+
|
|
223
|
+
```
|
|
224
|
+
jrd new-expo-project [options]
|
|
225
|
+
|
|
226
|
+
Options:
|
|
227
|
+
-n, --name <name> Project name (required)
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Example: `jrd new-expo-project --name MyApp`
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
143
234
|
### `jrd npm-downloads`
|
|
144
235
|
|
|
145
236
|
List all your npm packages sorted by total downloads.
|
|
@@ -178,6 +269,19 @@ jrd rename-season <season> [dir]
|
|
|
178
269
|
|
|
179
270
|
---
|
|
180
271
|
|
|
272
|
+
### `jrd repo-rulesets`
|
|
273
|
+
|
|
274
|
+
Find public GitHub repos that have no ruleset attached. Requires `gh auth login`.
|
|
275
|
+
|
|
276
|
+
```
|
|
277
|
+
jrd repo-rulesets [options]
|
|
278
|
+
|
|
279
|
+
Options:
|
|
280
|
+
-u, --user <user> GitHub username (defaults to authenticated user)
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
181
285
|
### `jrd repo-status`
|
|
182
286
|
|
|
183
287
|
Scan a directory of git repos and report which ones have dirty files, untracked files, or unpushed commits.
|
|
@@ -188,6 +292,33 @@ jrd repo-status [dir]
|
|
|
188
292
|
|
|
189
293
|
---
|
|
190
294
|
|
|
295
|
+
### `jrd sync-peers`
|
|
296
|
+
|
|
297
|
+
Sync `@rific` package `peerDependency` floors and `devDependency` versions to match Expo-Starter. Dry run by default.
|
|
298
|
+
|
|
299
|
+
```
|
|
300
|
+
jrd sync-peers [options]
|
|
301
|
+
|
|
302
|
+
Options:
|
|
303
|
+
-s, --starter <path> Path to Expo-Starter project (default: ~/Developer/Expo-Starter)
|
|
304
|
+
-r, --root <path> Root directory containing @rific packages (default: ~/Developer)
|
|
305
|
+
-d, --dry Preview changes without writing
|
|
306
|
+
-i, --install Run npm install in each changed repo
|
|
307
|
+
-t, --test Run npm test in each changed repo (implies --install)
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
### `jrd update-boilerplate`
|
|
313
|
+
|
|
314
|
+
Update the Expo boilerplate repo — clones it if absent, runs `jrd update-deps`, lint-fixes, type-checks, tests, then commits and pushes the result.
|
|
315
|
+
|
|
316
|
+
```
|
|
317
|
+
jrd update-boilerplate
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
191
322
|
### `jrd update-deps`
|
|
192
323
|
|
|
193
324
|
Update all npm dependencies to `@latest`. Automatically runs `npx expo install --fix` if the project uses Expo.
|
|
@@ -201,6 +332,19 @@ Options:
|
|
|
201
332
|
-l, --legacy Pass --legacy-peer-deps to npm install
|
|
202
333
|
```
|
|
203
334
|
|
|
335
|
+
### `jrd yalc-check`
|
|
336
|
+
|
|
337
|
+
Find projects in a directory that have yalc dependencies (version entries starting with `file:.yalc/` or a `yalc.lock` present).
|
|
338
|
+
|
|
339
|
+
```
|
|
340
|
+
jrd yalc-check [options]
|
|
341
|
+
|
|
342
|
+
Options:
|
|
343
|
+
-d, --dir <dir> Root directory to scan (default: ~/Developer)
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
204
348
|
## Requirements
|
|
205
349
|
|
|
206
350
|
Node >= 20
|
package/package.json
CHANGED
package/src/commands/base64.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from 'node:fs'
|
|
2
1
|
import { execSync } from 'node:child_process'
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
3
3
|
|
|
4
4
|
import { Color, Program } from 'termkit'
|
|
5
5
|
|
|
@@ -41,5 +41,5 @@ export const command = Program.command('base64')
|
|
|
41
41
|
.action(({ value, copy }) => {
|
|
42
42
|
const result = Buffer.from(value, 'base64').toString('utf8')
|
|
43
43
|
output(result, copy)
|
|
44
|
-
})
|
|
44
|
+
})
|
|
45
45
|
])
|
package/src/commands/binary.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
2
1
|
import { execSync } from 'node:child_process'
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
3
3
|
|
|
4
4
|
import { Color, Program } from 'termkit'
|
|
5
5
|
|
|
@@ -34,5 +34,5 @@ export const command = Program.command('binary')
|
|
|
34
34
|
const buf = Buffer.from(JSON.parse(readFileSync(file, 'utf8')), 'binary')
|
|
35
35
|
writeFileSync(destination, buf)
|
|
36
36
|
console.log(`${Color.green('Success:')} Restored ${file} to ${destination}`)
|
|
37
|
-
})
|
|
37
|
+
})
|
|
38
38
|
])
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { readdirSync, readFileSync, statSync } from 'node:fs'
|
|
2
|
+
import { homedir } from 'node:os'
|
|
3
|
+
import { join, resolve } from 'node:path'
|
|
4
|
+
|
|
5
|
+
import { Color, Program, Spinner } from 'termkit'
|
|
6
|
+
|
|
7
|
+
function loadProject(pkgPath) {
|
|
8
|
+
try {
|
|
9
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
|
|
10
|
+
return { name: pkg.name, scripts: pkg.scripts ?? {} }
|
|
11
|
+
} catch {
|
|
12
|
+
return null
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function mostCommon(values) {
|
|
17
|
+
const freq = {}
|
|
18
|
+
for (const v of values) freq[v] = (freq[v] ?? 0) + 1
|
|
19
|
+
return Object.entries(freq).sort((a, b) => b[1] - a[1])[0][0]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const command = Program.command('check-scripts', '[scripts...]')
|
|
23
|
+
.description('Compare package.json scripts across projects for consistency')
|
|
24
|
+
.option('d', 'dir', '[dir]', 'Root directory to scan (default: ~/Developer)')
|
|
25
|
+
.option('r', 'ref', '[ref]', 'Reference project name to compare against')
|
|
26
|
+
.option('a', 'all', null, 'Show all scripts, including matching ones')
|
|
27
|
+
.option('f', 'flat', null, 'Show one line per project instead of grouping by value')
|
|
28
|
+
.action(async (options) => {
|
|
29
|
+
const root = resolve(options.dir ?? join(homedir(), 'Developer'))
|
|
30
|
+
const filterScripts = options.scripts ?? []
|
|
31
|
+
|
|
32
|
+
let entries
|
|
33
|
+
try {
|
|
34
|
+
entries = readdirSync(root)
|
|
35
|
+
} catch {
|
|
36
|
+
console.error(Color.red(`Could not read directory: ${root}`))
|
|
37
|
+
process.exit(1)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const spinner = new Spinner({ text: 'Scanning projects...' })
|
|
41
|
+
spinner.start()
|
|
42
|
+
|
|
43
|
+
const projects = []
|
|
44
|
+
|
|
45
|
+
for (const entry of entries) {
|
|
46
|
+
const dir = join(root, entry)
|
|
47
|
+
try {
|
|
48
|
+
if (!statSync(dir).isDirectory()) continue
|
|
49
|
+
} catch {
|
|
50
|
+
continue
|
|
51
|
+
}
|
|
52
|
+
spinner.message(entry)
|
|
53
|
+
const result = loadProject(join(dir, 'package.json'))
|
|
54
|
+
if (result) projects.push({ dir: entry, ...result })
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
spinner.stop()
|
|
58
|
+
|
|
59
|
+
if (!projects.length) {
|
|
60
|
+
console.log(Color.yellow('No projects with package.json found.'))
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let refProject = null
|
|
65
|
+
if (options.ref) {
|
|
66
|
+
refProject = projects.find((p) => p.dir === options.ref || p.name === options.ref)
|
|
67
|
+
if (!refProject) {
|
|
68
|
+
console.error(Color.red(`Reference project not found: ${options.ref}`))
|
|
69
|
+
process.exit(1)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const allScriptNames = new Set()
|
|
74
|
+
for (const p of projects) {
|
|
75
|
+
for (const key of Object.keys(p.scripts)) {
|
|
76
|
+
if (!filterScripts.length || filterScripts.includes(key)) {
|
|
77
|
+
allScriptNames.add(key)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let printed = 0
|
|
83
|
+
|
|
84
|
+
for (const scriptName of [...allScriptNames].sort()) {
|
|
85
|
+
const withScript = projects.filter((p) => p.scripts[scriptName] !== undefined)
|
|
86
|
+
if (withScript.length < 2 && !refProject) continue
|
|
87
|
+
|
|
88
|
+
const expectedValue = refProject ? refProject.scripts[scriptName] : mostCommon(withScript.map((p) => p.scripts[scriptName]))
|
|
89
|
+
|
|
90
|
+
const allMatch = withScript.every((p) => p.scripts[scriptName] === expectedValue) && (!refProject || projects.every((p) => p.scripts[scriptName] !== undefined))
|
|
91
|
+
|
|
92
|
+
if (!options.all && allMatch) continue
|
|
93
|
+
|
|
94
|
+
console.log(`\n${Color.bold(scriptName)}`)
|
|
95
|
+
|
|
96
|
+
if (options.flat) {
|
|
97
|
+
// Per-project lines
|
|
98
|
+
const allRelevant = refProject ? projects : withScript
|
|
99
|
+
for (const p of allRelevant) {
|
|
100
|
+
const value = p.scripts[scriptName]
|
|
101
|
+
const missing = value === undefined
|
|
102
|
+
const matches = !missing && value === expectedValue
|
|
103
|
+
const icon = matches ? Color.green('✓') : Color.red('✗')
|
|
104
|
+
const label = matches ? Color.faint(p.dir) : Color.bold(p.dir)
|
|
105
|
+
const display = missing ? Color.faint('(missing)') : Color.faint(value)
|
|
106
|
+
console.log(` ${icon} ${label} ${display}`)
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
// Grouped by value
|
|
110
|
+
const groups = new Map()
|
|
111
|
+
const allRelevant = refProject ? projects : withScript
|
|
112
|
+
|
|
113
|
+
for (const p of allRelevant) {
|
|
114
|
+
const value = p.scripts[scriptName] ?? null
|
|
115
|
+
if (!groups.has(value)) groups.set(value, [])
|
|
116
|
+
groups.get(value).push(p.dir)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Sort: expected value first, then others, missing last
|
|
120
|
+
const sorted = [...groups.entries()].sort(([a], [b]) => {
|
|
121
|
+
if (a === expectedValue) return -1
|
|
122
|
+
if (b === expectedValue) return 1
|
|
123
|
+
if (a === null) return 1
|
|
124
|
+
if (b === null) return -1
|
|
125
|
+
return 0
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
for (const [value, dirs] of sorted) {
|
|
129
|
+
const matches = value === expectedValue
|
|
130
|
+
const icon = matches ? Color.green('✓') : Color.red('✗')
|
|
131
|
+
const label = matches ? Color.faint(dirs.join(', ')) : Color.bold(dirs.join(', '))
|
|
132
|
+
const display = value === null ? Color.faint('(missing)') : Color.faint(value)
|
|
133
|
+
console.log(` ${icon} ${label} ${display}`)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
printed++
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (!printed) {
|
|
141
|
+
console.log(Color.green('\nAll scripts are consistent across projects.'))
|
|
142
|
+
} else {
|
|
143
|
+
console.log()
|
|
144
|
+
}
|
|
145
|
+
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { extname, basename } from 'node:path'
|
|
2
1
|
import { readdirSync, rmSync, statSync } from 'node:fs'
|
|
3
|
-
import {
|
|
2
|
+
import { basename, extname } from 'node:path'
|
|
4
3
|
import { join, resolve } from 'node:path'
|
|
4
|
+
import { createInterface } from 'node:readline'
|
|
5
5
|
|
|
6
6
|
import { Color, Program, Spinner } from 'termkit'
|
|
7
7
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readdirSync, readFileSync, statSync } from 'node:fs'
|
|
2
2
|
import { homedir } from 'node:os'
|
|
3
3
|
import { join, resolve } from 'node:path'
|
|
4
4
|
|
|
@@ -77,16 +77,12 @@ export const command = Program.command('find-dep', '[deps...]')
|
|
|
77
77
|
console.log(Color.bold(`\nFound ${results.length} project${results.length !== 1 ? 's' : ''}:\n`))
|
|
78
78
|
|
|
79
79
|
for (const result of results) {
|
|
80
|
-
const label = result.projectName && result.projectName !== result.dir
|
|
81
|
-
? `${Color.bold(result.dir)} ${Color.faint(`(${result.projectName})`)}`
|
|
82
|
-
: Color.bold(result.dir)
|
|
80
|
+
const label = result.projectName && result.projectName !== result.dir ? `${Color.bold(result.dir)} ${Color.faint(`(${result.projectName})`)}` : Color.bold(result.dir)
|
|
83
81
|
|
|
84
82
|
console.log(` ${label}`)
|
|
85
83
|
|
|
86
84
|
for (const dep of result.found) {
|
|
87
|
-
const fieldLabel = dep.field === 'dependencies' ? 'dep'
|
|
88
|
-
: dep.field === 'devDependencies' ? 'dev'
|
|
89
|
-
: 'peer'
|
|
85
|
+
const fieldLabel = dep.field === 'dependencies' ? 'dep' : dep.field === 'devDependencies' ? 'dev' : 'peer'
|
|
90
86
|
console.log(` ${Color.cyan(dep.name)} ${Color.faint(`${dep.version} [${fieldLabel}]`)}`)
|
|
91
87
|
}
|
|
92
88
|
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { readdirSync, readFileSync, statSync } from 'node:fs'
|
|
2
|
+
import { homedir } from 'node:os'
|
|
3
|
+
import { join, resolve } from 'node:path'
|
|
4
|
+
|
|
5
|
+
import { Color, Program, Spinner } from 'termkit'
|
|
6
|
+
|
|
7
|
+
function findMatches(pkgPath, command) {
|
|
8
|
+
let pkg
|
|
9
|
+
try {
|
|
10
|
+
pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
|
|
11
|
+
} catch {
|
|
12
|
+
return null
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (!pkg.scripts) return null
|
|
16
|
+
|
|
17
|
+
const found = Object.entries(pkg.scripts)
|
|
18
|
+
.filter(([, value]) => value === command)
|
|
19
|
+
.map(([key]) => key)
|
|
20
|
+
|
|
21
|
+
return found.length ? { projectName: pkg.name, found } : null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const command = Program.command('find-script', '<command>')
|
|
25
|
+
.description('Find projects whose package.json scripts contain an exact command value')
|
|
26
|
+
.option('d', 'dir', '[dir]', 'Root directory to scan (default: ~/Developer)')
|
|
27
|
+
.action(async (options) => {
|
|
28
|
+
const target = options.command
|
|
29
|
+
|
|
30
|
+
const root = resolve(options.dir ?? join(homedir(), 'Developer'))
|
|
31
|
+
|
|
32
|
+
let entries
|
|
33
|
+
try {
|
|
34
|
+
entries = readdirSync(root)
|
|
35
|
+
} catch {
|
|
36
|
+
console.error(Color.red(`Could not read directory: ${root}`))
|
|
37
|
+
process.exit(1)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const spinner = new Spinner({ text: 'Scanning projects...' })
|
|
41
|
+
spinner.start()
|
|
42
|
+
|
|
43
|
+
const results = []
|
|
44
|
+
|
|
45
|
+
for (const name of entries) {
|
|
46
|
+
const dir = join(root, name)
|
|
47
|
+
try {
|
|
48
|
+
if (!statSync(dir).isDirectory()) continue
|
|
49
|
+
} catch {
|
|
50
|
+
continue
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
spinner.message(name)
|
|
54
|
+
const match = findMatches(join(dir, 'package.json'), target)
|
|
55
|
+
if (match) results.push({ dir: name, ...match })
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!results.length) {
|
|
59
|
+
spinner.succeed(`No projects found with script: ${target}`)
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
spinner.stop()
|
|
64
|
+
|
|
65
|
+
console.log(Color.bold(`\nFound ${results.length} project${results.length !== 1 ? 's' : ''}:\n`))
|
|
66
|
+
|
|
67
|
+
for (const result of results) {
|
|
68
|
+
const label =
|
|
69
|
+
result.projectName && result.projectName !== result.dir
|
|
70
|
+
? `${Color.bold(result.dir)} ${Color.faint(`(${result.projectName})`)}`
|
|
71
|
+
: Color.bold(result.dir)
|
|
72
|
+
|
|
73
|
+
console.log(` ${label}`)
|
|
74
|
+
|
|
75
|
+
for (const key of result.found) {
|
|
76
|
+
console.log(` ${Color.cyan(key)}`)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
console.log()
|
|
81
|
+
})
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readdirSync, statSync } from 'node:fs'
|
|
2
|
+
import { homedir } from 'node:os'
|
|
2
3
|
import { join, resolve } from 'node:path'
|
|
3
4
|
|
|
4
5
|
import { Color, Program, Spinner } from 'termkit'
|
|
@@ -32,7 +33,7 @@ export const command = Program.command('folder-sizes')
|
|
|
32
33
|
.description('List folders sorted by size, largest first')
|
|
33
34
|
.variable('[dir]')
|
|
34
35
|
.action(async (args) => {
|
|
35
|
-
const root = resolve(args.dir ?? '
|
|
36
|
+
const root = resolve(args.dir ?? join(homedir(), 'Developer'))
|
|
36
37
|
|
|
37
38
|
let entries
|
|
38
39
|
try {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { existsSync, readdirSync, statSync } from 'node:fs'
|
|
2
|
+
import { homedir } from 'node:os'
|
|
3
|
+
import { join, resolve } from 'node:path'
|
|
4
|
+
|
|
5
|
+
import { Color, Program, Spinner } from 'termkit'
|
|
6
|
+
|
|
7
|
+
export const command = Program.command('including', '[file]')
|
|
8
|
+
.description('Find projects in a directory that contain a given file')
|
|
9
|
+
.option('d', 'dir', '[dir]', 'Root directory to scan (default: ~/Developer)')
|
|
10
|
+
.action(async (options) => {
|
|
11
|
+
const file = options.file
|
|
12
|
+
|
|
13
|
+
if (!file) {
|
|
14
|
+
console.error(Color.red('Provide a file name to search for.'))
|
|
15
|
+
process.exit(1)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const root = resolve(options.dir ?? join(homedir(), 'Developer'))
|
|
19
|
+
|
|
20
|
+
let entries
|
|
21
|
+
try {
|
|
22
|
+
entries = readdirSync(root)
|
|
23
|
+
} catch {
|
|
24
|
+
console.error(Color.red(`Could not read directory: ${root}`))
|
|
25
|
+
process.exit(1)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const spinner = new Spinner({ text: 'Scanning projects...' })
|
|
29
|
+
spinner.start()
|
|
30
|
+
|
|
31
|
+
const found = []
|
|
32
|
+
|
|
33
|
+
for (const name of entries) {
|
|
34
|
+
const dir = join(root, name)
|
|
35
|
+
try {
|
|
36
|
+
if (!statSync(dir).isDirectory()) continue
|
|
37
|
+
} catch {
|
|
38
|
+
continue
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
spinner.message(name)
|
|
42
|
+
|
|
43
|
+
if (!existsSync(join(dir, 'package.json'))) continue
|
|
44
|
+
if (existsSync(join(dir, file))) found.push(name)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!found.length) {
|
|
48
|
+
spinner.succeed(`No projects found containing: ${file}`)
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
spinner.stop()
|
|
53
|
+
|
|
54
|
+
console.log(Color.bold(`\n${found.length} project${found.length !== 1 ? 's' : ''} containing ${Color.cyan(file)}:\n`))
|
|
55
|
+
|
|
56
|
+
for (const name of found) {
|
|
57
|
+
console.log(` ${Color.green(name)}`)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
console.log()
|
|
61
|
+
})
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { existsSync, readdirSync, statSync } from 'node:fs'
|
|
2
|
+
import { homedir } from 'node:os'
|
|
3
|
+
import { join, resolve } from 'node:path'
|
|
4
|
+
|
|
5
|
+
import { Color, Program, Spinner } from 'termkit'
|
|
6
|
+
|
|
7
|
+
export const command = Program.command('missing', '[file]')
|
|
8
|
+
.description('Find projects in a directory that are missing a given file')
|
|
9
|
+
.option('d', 'dir', '[dir]', 'Root directory to scan (default: ~/Developer)')
|
|
10
|
+
.action(async (options) => {
|
|
11
|
+
const file = options.file
|
|
12
|
+
|
|
13
|
+
if (!file) {
|
|
14
|
+
console.error(Color.red('Provide a file name to search for.'))
|
|
15
|
+
process.exit(1)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const root = resolve(options.dir ?? join(homedir(), 'Developer'))
|
|
19
|
+
|
|
20
|
+
let entries
|
|
21
|
+
try {
|
|
22
|
+
entries = readdirSync(root)
|
|
23
|
+
} catch {
|
|
24
|
+
console.error(Color.red(`Could not read directory: ${root}`))
|
|
25
|
+
process.exit(1)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const spinner = new Spinner({ text: 'Scanning projects...' })
|
|
29
|
+
spinner.start()
|
|
30
|
+
|
|
31
|
+
const missing = []
|
|
32
|
+
|
|
33
|
+
for (const name of entries) {
|
|
34
|
+
const dir = join(root, name)
|
|
35
|
+
try {
|
|
36
|
+
if (!statSync(dir).isDirectory()) continue
|
|
37
|
+
} catch {
|
|
38
|
+
continue
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
spinner.message(name)
|
|
42
|
+
|
|
43
|
+
if (!existsSync(join(dir, 'package.json'))) continue
|
|
44
|
+
if (!existsSync(join(dir, file))) missing.push(name)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!missing.length) {
|
|
48
|
+
spinner.succeed(`All projects contain: ${file}`)
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
spinner.stop()
|
|
53
|
+
|
|
54
|
+
console.log(Color.bold(`\n${missing.length} project${missing.length !== 1 ? 's' : ''} missing ${Color.cyan(file)}:\n`))
|
|
55
|
+
|
|
56
|
+
for (const name of missing) {
|
|
57
|
+
console.log(` ${Color.yellow(name)}`)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
console.log()
|
|
61
|
+
})
|
|
@@ -3,7 +3,7 @@ import { cpSync, existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs
|
|
|
3
3
|
import { homedir } from 'node:os'
|
|
4
4
|
import { join } from 'node:path'
|
|
5
5
|
|
|
6
|
-
import { Color,
|
|
6
|
+
import { Color, log, Program } from 'termkit'
|
|
7
7
|
|
|
8
8
|
const BOILERPLATE_REPO = 'git@github.com:jayrdeaton/Expo-Boilerplate.git'
|
|
9
9
|
const BOILERPLATE_DIR = join(homedir(), 'Developer', 'Expo-Boilerplate')
|
|
@@ -49,7 +49,7 @@ export const command = Program.command('new-expo-project')
|
|
|
49
49
|
log.info(`Creating ${displayName}...`)
|
|
50
50
|
cpSync(BOILERPLATE_DIR, targetDir, {
|
|
51
51
|
recursive: true,
|
|
52
|
-
filter: (src) => !src.includes('/node_modules/')
|
|
52
|
+
filter: (src) => !src.includes('/node_modules/')
|
|
53
53
|
})
|
|
54
54
|
|
|
55
55
|
rmSync(join(targetDir, '.git'), { recursive: true, force: true })
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readdirSync, renameSync, statSync } from 'node:fs'
|
|
2
2
|
import { extname, join } from 'node:path'
|
|
3
3
|
|
|
4
|
-
import { Color,
|
|
4
|
+
import { Color, log, Program } from 'termkit'
|
|
5
5
|
|
|
6
6
|
export const command = Program.command('rename-season')
|
|
7
7
|
.description('Rename files in a directory to SxEE format for TV library pickup')
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process'
|
|
2
|
+
|
|
3
|
+
import { Color, Program, Spinner } from 'termkit'
|
|
4
|
+
|
|
5
|
+
function gh(path) {
|
|
6
|
+
const out = execSync(`gh api "${path}" --paginate`, {
|
|
7
|
+
encoding: 'utf8',
|
|
8
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
9
|
+
})
|
|
10
|
+
return JSON.parse(out)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const command = Program.command('repo-rulesets')
|
|
14
|
+
.description('Find public repos that have no ruleset attached')
|
|
15
|
+
.option('u', 'user', '<user>', 'GitHub username (defaults to authenticated user)')
|
|
16
|
+
.action(async (options) => {
|
|
17
|
+
const spinner = new Spinner({ text: 'Resolving user...' })
|
|
18
|
+
spinner.start()
|
|
19
|
+
|
|
20
|
+
let user = options.user
|
|
21
|
+
if (!user) {
|
|
22
|
+
try {
|
|
23
|
+
const data = gh('/user')
|
|
24
|
+
user = data.login
|
|
25
|
+
} catch {
|
|
26
|
+
spinner.fail('Could not resolve GitHub user. Run `gh auth login` or pass --user.')
|
|
27
|
+
process.exit(1)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
spinner.message(`Fetching public repos for ${user}...`)
|
|
32
|
+
|
|
33
|
+
let repos
|
|
34
|
+
try {
|
|
35
|
+
repos = gh(`/users/${user}/repos?type=public&per_page=100`)
|
|
36
|
+
} catch {
|
|
37
|
+
spinner.fail(`Could not fetch repos for ${user}.`)
|
|
38
|
+
process.exit(1)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!repos.length) {
|
|
42
|
+
spinner.succeed(`No public repos found for ${user}.`)
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
spinner.message(`Checking rulesets across ${repos.length} repos...`)
|
|
47
|
+
|
|
48
|
+
const missing = []
|
|
49
|
+
|
|
50
|
+
for (const repo of repos) {
|
|
51
|
+
spinner.message(repo.name)
|
|
52
|
+
try {
|
|
53
|
+
const rulesets = gh(`/repos/${user}/${repo.name}/rulesets`)
|
|
54
|
+
if (!rulesets.length) missing.push(repo.name)
|
|
55
|
+
} catch {
|
|
56
|
+
// API error means no access or rulesets not available — treat as missing
|
|
57
|
+
missing.push(repo.name)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!missing.length) {
|
|
62
|
+
spinner.succeed(`All ${repos.length} public repos have a ruleset.`)
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
spinner.stop()
|
|
67
|
+
|
|
68
|
+
console.log(Color.bold.yellow(`\nNo ruleset (${missing.length} of ${repos.length})\n`))
|
|
69
|
+
for (const name of missing) {
|
|
70
|
+
console.log(` ${Color.yellow(name)}`)
|
|
71
|
+
}
|
|
72
|
+
console.log()
|
|
73
|
+
})
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { execSync } from 'node:child_process'
|
|
2
2
|
import { readdirSync, statSync } from 'node:fs'
|
|
3
|
+
import { homedir } from 'node:os'
|
|
3
4
|
import { join, resolve } from 'node:path'
|
|
4
5
|
|
|
5
6
|
import { Color, Program, Spinner } from 'termkit'
|
|
@@ -37,7 +38,7 @@ export const command = Program.command('repo-status')
|
|
|
37
38
|
.description('Report dirty and untracked files across repos in a directory')
|
|
38
39
|
.variable('[dir]')
|
|
39
40
|
.action(async (args) => {
|
|
40
|
-
const root = resolve(args.dir ?? '
|
|
41
|
+
const root = resolve(args.dir ?? join(homedir(), 'Developer'))
|
|
41
42
|
|
|
42
43
|
let entries
|
|
43
44
|
try {
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process'
|
|
2
|
+
import { readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'
|
|
3
|
+
import { homedir } from 'node:os'
|
|
4
|
+
import { join, resolve } from 'node:path'
|
|
5
|
+
|
|
6
|
+
import { Color, log, Program } from 'termkit'
|
|
7
|
+
|
|
8
|
+
const NON_SEMVER = /^(file:|link:|workspace:|git\+|github:|https?:|\/)/
|
|
9
|
+
|
|
10
|
+
const RUNTIME_PATTERNS = [/^react$/, /^react-dom$/, /^react-native/, /^expo(-|$)/, /^@expo\//, /^@gorhom\//, /^@shopify\//, /^@reduxjs\//]
|
|
11
|
+
|
|
12
|
+
function isRuntimeDep(name) {
|
|
13
|
+
return RUNTIME_PATTERNS.some((p) => p.test(name))
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function stripRange(raw) {
|
|
17
|
+
return raw.replace(/^[\^~>=<\s]+/, '').trim()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function toFloor(raw) {
|
|
21
|
+
if (NON_SEMVER.test(raw.trim())) return null
|
|
22
|
+
const clean = stripRange(raw)
|
|
23
|
+
if (!/^\d/.test(clean)) return null
|
|
24
|
+
const parts = clean.split('.')
|
|
25
|
+
const major = parseInt(parts[0], 10) || 0
|
|
26
|
+
const minor = parseInt(parts[1], 10) || 0
|
|
27
|
+
return `>=${major}.${minor}.0`
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isSemver(raw) {
|
|
31
|
+
return !NON_SEMVER.test(raw.trim()) && /^\d/.test(stripRange(raw))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function sortedDeps(deps) {
|
|
35
|
+
return Object.fromEntries(Object.entries(deps).sort(([a], [b]) => a.localeCompare(b)))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function readPkg(dir) {
|
|
39
|
+
try {
|
|
40
|
+
return JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8'))
|
|
41
|
+
} catch {
|
|
42
|
+
return null
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const command = Program.command('sync-peers')
|
|
47
|
+
.description('Sync @rific peerDependency floors and devDependency versions to match Expo-Starter')
|
|
48
|
+
.option('s', 'starter', '[starter]', 'Path to Expo-Starter project (default: ~/Developer/Expo-Starter)')
|
|
49
|
+
.option('r', 'root', '[root]', 'Root directory containing @rific packages (default: ~/Developer)')
|
|
50
|
+
.option('d', 'dry', null, 'Preview changes without writing')
|
|
51
|
+
.option('i', 'install', null, 'Run npm install in each changed repo')
|
|
52
|
+
.option('t', 'test', null, 'Run npm test in each changed repo (implies --install)')
|
|
53
|
+
.action(async (options) => {
|
|
54
|
+
const starterPath = resolve(options.starter ?? join(homedir(), 'Developer', 'Expo-Starter'))
|
|
55
|
+
const rootPath = resolve(options.root ?? join(homedir(), 'Developer'))
|
|
56
|
+
const isDry = !!options.dry
|
|
57
|
+
const shouldTest = !!options.test
|
|
58
|
+
const shouldInstall = !!options.install || shouldTest
|
|
59
|
+
|
|
60
|
+
const starterPkg = readPkg(starterPath)
|
|
61
|
+
if (!starterPkg) {
|
|
62
|
+
log.fail(`No package.json found at: ${starterPath}`)
|
|
63
|
+
process.exit(1)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const starterDeps = { ...starterPkg.dependencies, ...starterPkg.devDependencies }
|
|
67
|
+
const expoVersion = starterDeps.expo ? stripRange(starterDeps.expo) : 'unknown'
|
|
68
|
+
log.info(`Expo SDK floor: ${expoVersion}`)
|
|
69
|
+
|
|
70
|
+
const entries = readdirSync(rootPath).filter((name) => {
|
|
71
|
+
try {
|
|
72
|
+
return statSync(join(rootPath, name)).isDirectory()
|
|
73
|
+
} catch {
|
|
74
|
+
return false
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
const rificPkgs = entries
|
|
79
|
+
.map((name) => {
|
|
80
|
+
const dir = join(rootPath, name)
|
|
81
|
+
const pkg = readPkg(dir)
|
|
82
|
+
if (!pkg?.name?.startsWith('@rific/')) return null
|
|
83
|
+
return { name, dir, pkg }
|
|
84
|
+
})
|
|
85
|
+
.filter(Boolean)
|
|
86
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
87
|
+
|
|
88
|
+
log.info(`Found ${rificPkgs.length} @rific packages\n`)
|
|
89
|
+
|
|
90
|
+
let totalChanges = 0
|
|
91
|
+
|
|
92
|
+
for (const { name, dir, pkg } of rificPkgs) {
|
|
93
|
+
const peerUpdates = {}
|
|
94
|
+
const devUpdates = {}
|
|
95
|
+
|
|
96
|
+
for (const [dep, current] of Object.entries(pkg.peerDependencies ?? {})) {
|
|
97
|
+
if (!(dep in starterDeps)) continue
|
|
98
|
+
const floor = toFloor(starterDeps[dep])
|
|
99
|
+
if (!floor || floor === current) continue
|
|
100
|
+
peerUpdates[dep] = { from: current, to: floor }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
for (const [dep, current] of Object.entries(pkg.devDependencies ?? {})) {
|
|
104
|
+
if (!isRuntimeDep(dep) || !(dep in starterDeps)) continue
|
|
105
|
+
const next = starterDeps[dep]
|
|
106
|
+
if (!isSemver(next) || next === current) continue
|
|
107
|
+
devUpdates[dep] = { from: current, to: next }
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const hasChanges = Object.keys(peerUpdates).length > 0 || Object.keys(devUpdates).length > 0
|
|
111
|
+
|
|
112
|
+
if (!hasChanges) {
|
|
113
|
+
console.log(`${Color.bold(name)} ${Color.faint('no changes')}`)
|
|
114
|
+
continue
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
console.log(Color.bold(name))
|
|
118
|
+
|
|
119
|
+
if (Object.keys(peerUpdates).length > 0) {
|
|
120
|
+
console.log(Color.faint(' peerDependencies'))
|
|
121
|
+
for (const [dep, { from, to }] of Object.entries(peerUpdates)) {
|
|
122
|
+
console.log(` ${dep}`)
|
|
123
|
+
console.log(` ${Color.red(from)} → ${Color.green(to)}`)
|
|
124
|
+
totalChanges++
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (Object.keys(devUpdates).length > 0) {
|
|
129
|
+
console.log(Color.faint(' devDependencies'))
|
|
130
|
+
for (const [dep, { from, to }] of Object.entries(devUpdates)) {
|
|
131
|
+
console.log(` ${dep}`)
|
|
132
|
+
console.log(` ${Color.red(from)} → ${Color.green(to)}`)
|
|
133
|
+
totalChanges++
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!isDry) {
|
|
138
|
+
const updated = { ...pkg }
|
|
139
|
+
if (Object.keys(peerUpdates).length > 0) {
|
|
140
|
+
updated.peerDependencies = sortedDeps({ ...pkg.peerDependencies, ...Object.fromEntries(Object.entries(peerUpdates).map(([d, { to }]) => [d, to])) })
|
|
141
|
+
}
|
|
142
|
+
if (Object.keys(devUpdates).length > 0) {
|
|
143
|
+
updated.devDependencies = sortedDeps({ ...pkg.devDependencies, ...Object.fromEntries(Object.entries(devUpdates).map(([d, { to }]) => [d, to])) })
|
|
144
|
+
}
|
|
145
|
+
writeFileSync(join(dir, 'package.json'), JSON.stringify(updated, null, 2) + '\n')
|
|
146
|
+
if (shouldInstall) {
|
|
147
|
+
console.log(Color.faint(' npm install'))
|
|
148
|
+
execSync('npm install', { cwd: dir, stdio: 'inherit' })
|
|
149
|
+
}
|
|
150
|
+
if (shouldTest) {
|
|
151
|
+
console.log(Color.faint(' npm test'))
|
|
152
|
+
execSync('npm test', { cwd: dir, stdio: 'inherit' })
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
console.log()
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (totalChanges === 0) {
|
|
160
|
+
log.succeed('All dependencies already aligned.')
|
|
161
|
+
} else if (isDry) {
|
|
162
|
+
log.info(`${totalChanges} change${totalChanges !== 1 ? 's' : ''} pending — run without --dry to apply`)
|
|
163
|
+
} else {
|
|
164
|
+
log.succeed(`Applied ${totalChanges} change${totalChanges !== 1 ? 's' : ''}.`)
|
|
165
|
+
}
|
|
166
|
+
})
|
|
@@ -3,7 +3,7 @@ import { existsSync } from 'node:fs'
|
|
|
3
3
|
import { homedir } from 'node:os'
|
|
4
4
|
import { join } from 'node:path'
|
|
5
5
|
|
|
6
|
-
import { Color,
|
|
6
|
+
import { Color, log, Program } from 'termkit'
|
|
7
7
|
|
|
8
8
|
const BOILERPLATE_REPO = 'git@github.com:jayrdeaton/Expo-Boilerplate.git'
|
|
9
9
|
const BOILERPLATE_DIR = join(homedir(), 'Developer', 'Expo-Boilerplate')
|
|
@@ -2,7 +2,7 @@ import { execSync } from 'node:child_process'
|
|
|
2
2
|
import { readFileSync } from 'node:fs'
|
|
3
3
|
import { resolve } from 'node:path'
|
|
4
4
|
|
|
5
|
-
import { Color,
|
|
5
|
+
import { Color, log, Program } from 'termkit'
|
|
6
6
|
|
|
7
7
|
function exec(cmd) {
|
|
8
8
|
console.log(Color.faint(`$ ${cmd}`))
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'
|
|
2
|
+
import { homedir } from 'node:os'
|
|
3
|
+
import { join, resolve } from 'node:path'
|
|
4
|
+
|
|
5
|
+
import { Color, Program, Spinner } from 'termkit'
|
|
6
|
+
|
|
7
|
+
const DEP_FIELDS = ['dependencies', 'devDependencies', 'peerDependencies']
|
|
8
|
+
|
|
9
|
+
function findYalcDeps(pkgPath) {
|
|
10
|
+
let pkg
|
|
11
|
+
try {
|
|
12
|
+
pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
|
|
13
|
+
} catch {
|
|
14
|
+
return null
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const found = []
|
|
18
|
+
for (const field of DEP_FIELDS) {
|
|
19
|
+
if (!pkg[field]) continue
|
|
20
|
+
for (const [name, version] of Object.entries(pkg[field])) {
|
|
21
|
+
if (typeof version === 'string' && version.startsWith('file:.yalc/')) {
|
|
22
|
+
found.push({ name, version, field })
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return { projectName: pkg.name, found }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const command = Program.command('yalc-check')
|
|
31
|
+
.description('Find projects in a directory that have yalc dependencies')
|
|
32
|
+
.option('d', 'dir', '[dir]', 'Root directory to scan (default: ~/Developer)')
|
|
33
|
+
.action(async (options) => {
|
|
34
|
+
const root = resolve(options.dir ?? join(homedir(), 'Developer'))
|
|
35
|
+
|
|
36
|
+
let entries
|
|
37
|
+
try {
|
|
38
|
+
entries = readdirSync(root)
|
|
39
|
+
} catch {
|
|
40
|
+
console.error(Color.red(`Could not read directory: ${root}`))
|
|
41
|
+
process.exit(1)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const spinner = new Spinner({ text: 'Scanning projects...' })
|
|
45
|
+
spinner.start()
|
|
46
|
+
|
|
47
|
+
const results = []
|
|
48
|
+
|
|
49
|
+
for (const name of entries) {
|
|
50
|
+
const dir = join(root, name)
|
|
51
|
+
try {
|
|
52
|
+
if (!statSync(dir).isDirectory()) continue
|
|
53
|
+
} catch {
|
|
54
|
+
continue
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
spinner.message(name)
|
|
58
|
+
|
|
59
|
+
const pkgPath = join(dir, 'package.json')
|
|
60
|
+
if (!existsSync(pkgPath)) continue
|
|
61
|
+
|
|
62
|
+
const hasLock = existsSync(join(dir, 'yalc.lock'))
|
|
63
|
+
const { projectName, found } = findYalcDeps(pkgPath)
|
|
64
|
+
|
|
65
|
+
if (found.length || hasLock) {
|
|
66
|
+
results.push({ dir: name, projectName, found, hasLock })
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!results.length) {
|
|
71
|
+
spinner.succeed('No projects with yalc dependencies found')
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
spinner.stop()
|
|
76
|
+
|
|
77
|
+
console.log(Color.bold(`\n${results.length} project${results.length !== 1 ? 's' : ''} with yalc dependencies:\n`))
|
|
78
|
+
|
|
79
|
+
for (const result of results) {
|
|
80
|
+
const label =
|
|
81
|
+
result.projectName && result.projectName !== result.dir
|
|
82
|
+
? `${Color.bold(result.dir)} ${Color.faint(`(${result.projectName})`)}`
|
|
83
|
+
: Color.bold(result.dir)
|
|
84
|
+
|
|
85
|
+
const lockNote = result.hasLock && !result.found.length ? Color.yellow(' yalc.lock present') : ''
|
|
86
|
+
console.log(` ${label}${lockNote}`)
|
|
87
|
+
|
|
88
|
+
for (const dep of result.found) {
|
|
89
|
+
const fieldLabel = dep.field === 'dependencies' ? 'dep' : dep.field === 'devDependencies' ? 'dev' : 'peer'
|
|
90
|
+
console.log(` ${Color.cyan(dep.name)} ${Color.faint(`${dep.version} [${fieldLabel}]`)}`)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
console.log()
|
|
95
|
+
})
|