@jayrdeaton/scripts 1.0.0 → 1.1.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/README.md +121 -0
- package/package.json +15 -7
- package/src/commands/base64.mjs +45 -0
- package/src/commands/binary.mjs +38 -0
- package/src/commands/bump-ota.mjs +10 -8
- package/src/commands/check-domains.mjs +102 -0
- package/src/commands/clean-builds.mjs +129 -0
- package/src/commands/clean-junk.mjs +123 -0
- package/src/commands/code-count.mjs +11 -16
- package/src/commands/find-dep.mjs +95 -0
- package/src/commands/focus.mjs +11 -0
- package/src/commands/folder-sizes.mjs +10 -5
- package/src/commands/new-expo-project.mjs +77 -0
- package/src/commands/npm-downloads.mjs +95 -0
- package/src/commands/npm-namer.mjs +75 -0
- package/src/commands/rename-season.mjs +4 -5
- package/src/commands/repo-status.mjs +21 -14
- package/src/commands/update-boilerplate.mjs +52 -0
- package/src/commands/update-deps.mjs +10 -8
|
@@ -1,19 +1,11 @@
|
|
|
1
1
|
import { createReadStream, existsSync, lstatSync, readdirSync } from 'node:fs'
|
|
2
2
|
import { extname, join, resolve } from 'node:path'
|
|
3
3
|
|
|
4
|
-
import
|
|
5
|
-
import { command as createCommand } from 'termkit'
|
|
4
|
+
import { Color, command as createCommand, Spinner } from 'termkit'
|
|
6
5
|
|
|
7
|
-
const WHITELIST = new Set([
|
|
8
|
-
'.cjs', '.css', '.csv', '.ejs', '.env', '.gitignore', '.haml', '.html', '.java',
|
|
9
|
-
'.js', '.json', '.mjs', '.paw', '.plist', '.py', '.rake', '.scss', '.sh', '.sql',
|
|
10
|
-
'.stl', '.swift', '.ts', '.tsx', '.txt', '.xib', '.xml', '.yaml', '.yml',
|
|
11
|
-
])
|
|
6
|
+
const WHITELIST = new Set(['.cjs', '.css', '.csv', '.ejs', '.env', '.gitignore', '.haml', '.html', '.java', '.js', '.json', '.mjs', '.paw', '.plist', '.py', '.rake', '.scss', '.sh', '.sql', '.stl', '.swift', '.ts', '.tsx', '.txt', '.xib', '.xml', '.yaml', '.yml'])
|
|
12
7
|
|
|
13
|
-
const SKIP = new Set([
|
|
14
|
-
'.DS_Store', '.git', 'Carthage', 'Dockerfile', 'LICENSE', 'Test',
|
|
15
|
-
'node_modules', 'package-lock.json',
|
|
16
|
-
])
|
|
8
|
+
const SKIP = new Set(['.DS_Store', '.git', 'Carthage', 'Dockerfile', 'LICENSE', 'Test', 'node_modules', 'package-lock.json'])
|
|
17
9
|
|
|
18
10
|
function getFiles(base, { ignore = [], recursive = false } = {}) {
|
|
19
11
|
try {
|
|
@@ -69,19 +61,22 @@ export const command = createCommand('code-count')
|
|
|
69
61
|
|
|
70
62
|
const allPaths = paths.flatMap((p) => getFiles(p, { ignore, recursive }))
|
|
71
63
|
|
|
64
|
+
const spinner = new Spinner({ text: 'Scanning...' })
|
|
65
|
+
spinner.start()
|
|
66
|
+
|
|
72
67
|
const totals = {}
|
|
73
68
|
for (let i = 0; i < allPaths.length; i++) {
|
|
74
69
|
const path = allPaths[i]
|
|
75
70
|
const ext = extname(path)
|
|
76
71
|
if (!ext) continue
|
|
77
|
-
|
|
72
|
+
spinner.message(`Checking files ${i + 1} / ${allPaths.length}`)
|
|
78
73
|
totals[ext] = (totals[ext] ?? 0) + (await countLines(path))
|
|
79
74
|
}
|
|
80
|
-
|
|
75
|
+
|
|
76
|
+
const total = Object.values(totals).reduce((a, n) => a + n, 0)
|
|
77
|
+
spinner.succeed(`${commaString(total)} lines across ${allPaths.length} files`)
|
|
81
78
|
|
|
82
79
|
for (const key of Object.keys(totals).sort()) {
|
|
83
|
-
console.log(`${key}: ${
|
|
80
|
+
console.log(`${key}: ${Color.cyan(commaString(totals[key]))}`)
|
|
84
81
|
}
|
|
85
|
-
const total = Object.values(totals).reduce((a, n) => a + n, 0)
|
|
86
|
-
console.log(`total ${cosmetic.cyan(commaString(total))} in ${cosmetic.cyan(String(allPaths.length))} files`)
|
|
87
82
|
})
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { readFileSync, readdirSync, statSync } from 'node:fs'
|
|
2
|
+
import { homedir } from 'node:os'
|
|
3
|
+
import { join, resolve } from 'node:path'
|
|
4
|
+
|
|
5
|
+
import { Color, command as createCommand, Spinner } from 'termkit'
|
|
6
|
+
|
|
7
|
+
const DEP_FIELDS = ['dependencies', 'devDependencies', 'peerDependencies']
|
|
8
|
+
|
|
9
|
+
function findMatches(pkgPath, targets) {
|
|
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 target of targets) {
|
|
21
|
+
if (pkg[field][target] !== undefined) {
|
|
22
|
+
found.push({ name: target, version: pkg[field][target], field })
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return found.length ? { projectName: pkg.name, found } : null
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const command = createCommand('find-dep', '[deps...]')
|
|
31
|
+
.description('Find projects in a directory that use any of the given dependencies')
|
|
32
|
+
.option('d', 'dir', '[dir]', 'Root directory to scan (default: ~/Developer)')
|
|
33
|
+
.action(async (options) => {
|
|
34
|
+
const targets = options.deps ?? []
|
|
35
|
+
|
|
36
|
+
if (!targets.length) {
|
|
37
|
+
console.error(Color.red('Provide at least one dependency name.'))
|
|
38
|
+
process.exit(1)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const root = resolve(options.dir ?? join(homedir(), 'Developer'))
|
|
42
|
+
|
|
43
|
+
let entries
|
|
44
|
+
try {
|
|
45
|
+
entries = readdirSync(root)
|
|
46
|
+
} catch {
|
|
47
|
+
console.error(Color.red(`Could not read directory: ${root}`))
|
|
48
|
+
process.exit(1)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const spinner = new Spinner({ text: 'Scanning projects...' })
|
|
52
|
+
spinner.start()
|
|
53
|
+
|
|
54
|
+
const results = []
|
|
55
|
+
|
|
56
|
+
for (const name of entries) {
|
|
57
|
+
const dir = join(root, name)
|
|
58
|
+
try {
|
|
59
|
+
if (!statSync(dir).isDirectory()) continue
|
|
60
|
+
} catch {
|
|
61
|
+
continue
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
spinner.message(name)
|
|
65
|
+
const pkgPath = join(dir, 'package.json')
|
|
66
|
+
const match = findMatches(pkgPath, targets)
|
|
67
|
+
if (match) results.push({ dir: name, ...match })
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!results.length) {
|
|
71
|
+
spinner.succeed(`No projects found using: ${targets.join(', ')}`)
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
spinner.stop()
|
|
76
|
+
|
|
77
|
+
console.log(Color.bold(`\nFound ${results.length} project${results.length !== 1 ? 's' : ''}:\n`))
|
|
78
|
+
|
|
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)
|
|
83
|
+
|
|
84
|
+
console.log(` ${label}`)
|
|
85
|
+
|
|
86
|
+
for (const dep of result.found) {
|
|
87
|
+
const fieldLabel = dep.field === 'dependencies' ? 'dep'
|
|
88
|
+
: dep.field === 'devDependencies' ? 'dev'
|
|
89
|
+
: 'peer'
|
|
90
|
+
console.log(` ${Color.cyan(dep.name)} ${Color.faint(`${dep.version} [${fieldLabel}]`)}`)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
console.log()
|
|
95
|
+
})
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process'
|
|
2
|
+
|
|
3
|
+
import { command as createCommand } from 'termkit'
|
|
4
|
+
|
|
5
|
+
export const command = createCommand('focus')
|
|
6
|
+
.description('Bring an application to the front')
|
|
7
|
+
.variable('[app]')
|
|
8
|
+
.action((args) => {
|
|
9
|
+
const app = args.app ?? 'Terminal'
|
|
10
|
+
execSync(`osascript -e 'tell application "${app}" to activate'`)
|
|
11
|
+
})
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { readdirSync, statSync } from 'node:fs'
|
|
2
2
|
import { join, resolve } from 'node:path'
|
|
3
3
|
|
|
4
|
-
import
|
|
5
|
-
import { command as createCommand } from 'termkit'
|
|
4
|
+
import { Color, command as createCommand, Spinner } from 'termkit'
|
|
6
5
|
|
|
7
6
|
function getDirSize(dirPath) {
|
|
8
7
|
let total = 0
|
|
@@ -39,29 +38,35 @@ export const command = createCommand('folder-sizes')
|
|
|
39
38
|
try {
|
|
40
39
|
entries = readdirSync(root, { withFileTypes: true })
|
|
41
40
|
} catch {
|
|
42
|
-
console.error(
|
|
41
|
+
console.error(Color.red(`Could not read directory: ${root}`))
|
|
43
42
|
process.exit(1)
|
|
44
43
|
}
|
|
45
44
|
|
|
45
|
+
const spinner = new Spinner({ text: 'Scanning...' })
|
|
46
|
+
spinner.start()
|
|
47
|
+
|
|
46
48
|
const folders = entries
|
|
47
49
|
.filter((e) => e.isDirectory())
|
|
48
50
|
.map((e) => {
|
|
51
|
+
spinner.message(e.name)
|
|
49
52
|
const size = getDirSize(join(root, e.name))
|
|
50
53
|
return { name: e.name, size }
|
|
51
54
|
})
|
|
52
55
|
.sort((a, b) => b.size - a.size)
|
|
53
56
|
|
|
54
57
|
if (folders.length === 0) {
|
|
55
|
-
|
|
58
|
+
spinner.warn('No folders found.')
|
|
56
59
|
return
|
|
57
60
|
}
|
|
58
61
|
|
|
62
|
+
spinner.succeed(`${folders.length} folders`)
|
|
63
|
+
|
|
59
64
|
const maxName = Math.max(...folders.map((f) => f.name.length))
|
|
60
65
|
const maxSize = Math.max(...folders.map((f) => formatSize(f.size).length))
|
|
61
66
|
|
|
62
67
|
for (const folder of folders) {
|
|
63
68
|
const name = folder.name.padEnd(maxName)
|
|
64
69
|
const size = formatSize(folder.size).padStart(maxSize)
|
|
65
|
-
console.log(` ${
|
|
70
|
+
console.log(` ${Color.cyan(name)} ${Color.bold(size)}`)
|
|
66
71
|
}
|
|
67
72
|
})
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process'
|
|
2
|
+
import { cpSync, existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
3
|
+
import { homedir } from 'node:os'
|
|
4
|
+
import { join } from 'node:path'
|
|
5
|
+
|
|
6
|
+
import { Color, command as createCommand, log } from 'termkit'
|
|
7
|
+
|
|
8
|
+
const BOILERPLATE_REPO = 'git@github.com:jayrdeaton/Expo-Boilerplate.git'
|
|
9
|
+
const BOILERPLATE_DIR = join(homedir(), 'Developer', 'Expo-Boilerplate')
|
|
10
|
+
const DEV_DIR = join(homedir(), 'Developer')
|
|
11
|
+
|
|
12
|
+
function exec(cmd, opts = {}) {
|
|
13
|
+
console.log(Color.faint(`$ ${cmd}`))
|
|
14
|
+
execSync(cmd, { stdio: 'inherit', ...opts })
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function parseWords(name) {
|
|
18
|
+
return name
|
|
19
|
+
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
20
|
+
.split(/[\s\-_]+/)
|
|
21
|
+
.filter(Boolean)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const command = createCommand('new-expo-project')
|
|
25
|
+
.description('Bootstrap a new Expo project from the boilerplate')
|
|
26
|
+
.option('n', 'name', '<name>', 'Project name')
|
|
27
|
+
.action(async (options) => {
|
|
28
|
+
const words = parseWords(options.name)
|
|
29
|
+
const displayName = words.map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(' ')
|
|
30
|
+
const slug = words.map((w) => w.toLowerCase()).join('-')
|
|
31
|
+
const pascal = words.map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join('')
|
|
32
|
+
const lower = words.map((w) => w.toLowerCase()).join('')
|
|
33
|
+
const dirName = words.map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join('-')
|
|
34
|
+
const targetDir = join(DEV_DIR, dirName)
|
|
35
|
+
|
|
36
|
+
if (existsSync(targetDir)) {
|
|
37
|
+
log.fail(`Directory already exists: ${targetDir}`)
|
|
38
|
+
process.exit(1)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (existsSync(BOILERPLATE_DIR)) {
|
|
42
|
+
log.info('Pulling latest boilerplate...')
|
|
43
|
+
exec('git pull', { cwd: BOILERPLATE_DIR })
|
|
44
|
+
} else {
|
|
45
|
+
log.info('Cloning boilerplate...')
|
|
46
|
+
exec(`git clone ${BOILERPLATE_REPO} "${BOILERPLATE_DIR}"`)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
log.info(`Creating ${displayName}...`)
|
|
50
|
+
cpSync(BOILERPLATE_DIR, targetDir, {
|
|
51
|
+
recursive: true,
|
|
52
|
+
filter: (src) => !src.includes('/node_modules/'),
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
rmSync(join(targetDir, '.git'), { recursive: true, force: true })
|
|
56
|
+
|
|
57
|
+
const pkgPath = join(targetDir, 'package.json')
|
|
58
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
|
|
59
|
+
pkg.name = slug
|
|
60
|
+
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n')
|
|
61
|
+
|
|
62
|
+
const appPath = join(targetDir, 'app.json')
|
|
63
|
+
const app = JSON.parse(readFileSync(appPath, 'utf8'))
|
|
64
|
+
app.expo.name = displayName
|
|
65
|
+
app.expo.slug = slug
|
|
66
|
+
app.expo.scheme = lower
|
|
67
|
+
app.expo.description = `${displayName} app`
|
|
68
|
+
app.expo.ios.bundleIdentifier = `com.infinitetoken.${pascal}`
|
|
69
|
+
app.expo.android.package = `com.infinitetoken.${lower}`
|
|
70
|
+
writeFileSync(appPath, JSON.stringify(app, null, 2) + '\n')
|
|
71
|
+
|
|
72
|
+
exec('git init', { cwd: targetDir })
|
|
73
|
+
exec('git add -A', { cwd: targetDir })
|
|
74
|
+
exec('git commit -m "Initial commit"', { cwd: targetDir })
|
|
75
|
+
|
|
76
|
+
log.succeed(`${displayName} created at ${targetDir}`)
|
|
77
|
+
})
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process'
|
|
2
|
+
|
|
3
|
+
import { Color, command as createCommand, Spinner } from 'termkit'
|
|
4
|
+
|
|
5
|
+
async function fetchJson(url) {
|
|
6
|
+
const res = await fetch(url)
|
|
7
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}: ${url}`)
|
|
8
|
+
return res.json()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async function getAllPackages(username) {
|
|
12
|
+
const packages = []
|
|
13
|
+
const size = 250
|
|
14
|
+
let from = 0
|
|
15
|
+
|
|
16
|
+
while (true) {
|
|
17
|
+
const data = await fetchJson(`https://registry.npmjs.org/-/v1/search?text=maintainer:${username}&size=${size}&from=${from}`)
|
|
18
|
+
packages.push(...data.objects.map((o) => o.package.name))
|
|
19
|
+
if (packages.length >= data.total || data.objects.length < size) break
|
|
20
|
+
from += size
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return packages
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function getDownloads(pkg, period) {
|
|
27
|
+
try {
|
|
28
|
+
const data = await fetchJson(`https://api.npmjs.org/downloads/point/${period}/${pkg}`)
|
|
29
|
+
return data.downloads ?? 0
|
|
30
|
+
} catch {
|
|
31
|
+
return 0
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const command = createCommand('npm-downloads')
|
|
36
|
+
.description('List all your npm packages sorted by total downloads')
|
|
37
|
+
.option('u', 'user', '<name>', 'npm username (defaults to npm whoami)')
|
|
38
|
+
.option('p', 'period', '<period>', 'last-day | last-week | last-month | last-year (default: last-month)')
|
|
39
|
+
.option('m', 'mtd', null, 'Use month-to-date instead of rolling 30 days')
|
|
40
|
+
.action(async (options) => {
|
|
41
|
+
const spinner = new Spinner({ text: 'Resolving username...' })
|
|
42
|
+
spinner.start()
|
|
43
|
+
|
|
44
|
+
let username = options.user
|
|
45
|
+
if (!username) {
|
|
46
|
+
try {
|
|
47
|
+
username = execSync('npm whoami', {
|
|
48
|
+
encoding: 'utf8',
|
|
49
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
50
|
+
}).trim()
|
|
51
|
+
} catch {
|
|
52
|
+
spinner.fail('Could not determine npm username. Use --user <name> or run `npm login`.')
|
|
53
|
+
process.exit(1)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let period = options.period ?? 'last-month'
|
|
58
|
+
if (options.mtd) {
|
|
59
|
+
const now = new Date()
|
|
60
|
+
const yyyy = now.getFullYear()
|
|
61
|
+
const mm = String(now.getMonth() + 1).padStart(2, '0')
|
|
62
|
+
const dd = String(now.getDate()).padStart(2, '0')
|
|
63
|
+
period = `${yyyy}-${mm}-01:${yyyy}-${mm}-${dd}`
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
spinner.message(`Fetching packages for ${username}...`)
|
|
67
|
+
const packages = await getAllPackages(username)
|
|
68
|
+
|
|
69
|
+
if (packages.length === 0) {
|
|
70
|
+
spinner.warn(`No packages found for ${username}.`)
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
spinner.message(`Fetching download counts (${period})...`)
|
|
75
|
+
const results = await Promise.all(packages.map(async (name) => ({ name, downloads: await getDownloads(name, period) })))
|
|
76
|
+
|
|
77
|
+
spinner.succeed(`${packages.length} packages · ${period}`)
|
|
78
|
+
|
|
79
|
+
const sorted = results.sort((a, b) => b.downloads - a.downloads)
|
|
80
|
+
const orgs = [...new Set(sorted.filter((p) => p.name.startsWith('@')).map((p) => p.name.split('/')[0]))]
|
|
81
|
+
const maxWidth = String(sorted[0]?.downloads ?? 0).length
|
|
82
|
+
|
|
83
|
+
console.log(`\n${Color.bold(username)} — ${Color.faint(period)}`)
|
|
84
|
+
if (orgs.length > 0) console.log(Color.faint(`orgs: ${orgs.join(' ')}`))
|
|
85
|
+
console.log()
|
|
86
|
+
|
|
87
|
+
for (const { name, downloads } of sorted) {
|
|
88
|
+
const count = String(downloads).padStart(maxWidth)
|
|
89
|
+
const label = downloads === 0 ? Color.faint(name) : name
|
|
90
|
+
console.log(` ${Color.cyan(count)} ${label}`)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const total = sorted.reduce((sum, p) => sum + p.downloads, 0)
|
|
94
|
+
console.log(Color.faint(`\n${total.toLocaleString()} total downloads across ${sorted.length} packages`))
|
|
95
|
+
})
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Color, command as createCommand, Spinner } from 'termkit'
|
|
2
|
+
|
|
3
|
+
async function isAvailable(name) {
|
|
4
|
+
try {
|
|
5
|
+
const encoded = name.startsWith('@') ? name.replace('/', '%2F') : name
|
|
6
|
+
const res = await fetch(`https://registry.npmjs.org/${encoded}`, { method: 'HEAD' })
|
|
7
|
+
return res.status === 404
|
|
8
|
+
} catch {
|
|
9
|
+
return null
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function getVariants(name) {
|
|
14
|
+
const normalized = name.replace(/[-_]/g, ' ').trim()
|
|
15
|
+
if (!normalized.includes(' ')) return [normalized]
|
|
16
|
+
|
|
17
|
+
const nospace = normalized.replace(/ /g, '')
|
|
18
|
+
const hyphenated = normalized.replace(/ /g, '-')
|
|
19
|
+
const underscored = normalized.replace(/ /g, '_')
|
|
20
|
+
return [...new Set([nospace, hyphenated, underscored])]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function getSynonyms(word) {
|
|
24
|
+
try {
|
|
25
|
+
const base = word.replace(/[-_]/g, ' ').trim().split(' ')[0]
|
|
26
|
+
const res = await fetch(`https://api.datamuse.com/words?ml=${encodeURIComponent(base)}&max=20`)
|
|
27
|
+
if (!res.ok) return []
|
|
28
|
+
const data = await res.json()
|
|
29
|
+
return data.map((w) => w.word).filter((w) => !w.includes(' '))
|
|
30
|
+
} catch {
|
|
31
|
+
return []
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const command = createCommand('npm-namer')
|
|
36
|
+
.description('Check npm package name availability, including variations')
|
|
37
|
+
.variable('<name>')
|
|
38
|
+
.option('s', 'synonyms', null, 'Also check synonyms of the name')
|
|
39
|
+
.action(async (args) => {
|
|
40
|
+
const { name, synonyms } = args
|
|
41
|
+
const spinner = new Spinner({ text: 'Checking availability...' })
|
|
42
|
+
spinner.start()
|
|
43
|
+
|
|
44
|
+
const namesToCheck = new Set(getVariants(name))
|
|
45
|
+
|
|
46
|
+
if (synonyms) {
|
|
47
|
+
spinner.message('Fetching synonyms...')
|
|
48
|
+
const syns = await getSynonyms(name)
|
|
49
|
+
for (const syn of syns) {
|
|
50
|
+
for (const variant of getVariants(syn)) {
|
|
51
|
+
namesToCheck.add(variant)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
spinner.message(`Checking ${namesToCheck.size} names...`)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const results = await Promise.all([...namesToCheck].map(async (n) => ({ name: n, available: await isAvailable(n) })))
|
|
58
|
+
|
|
59
|
+
spinner.stop()
|
|
60
|
+
|
|
61
|
+
const available = results.filter((r) => r.available === true)
|
|
62
|
+
const taken = results.filter((r) => r.available === false)
|
|
63
|
+
const errored = results.filter((r) => r.available === null)
|
|
64
|
+
|
|
65
|
+
console.log()
|
|
66
|
+
for (const { name: n, available: avail } of results) {
|
|
67
|
+
if (avail === true) console.log(` ${Color.green('✓')} ${n}`)
|
|
68
|
+
else if (avail === false) console.log(` ${Color.red('✗')} ${Color.faint(n)}`)
|
|
69
|
+
else console.log(` ${Color.yellow('?')} ${Color.faint(n)}`)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const parts = [`${available.length} available`, `${taken.length} taken`]
|
|
73
|
+
if (errored.length > 0) parts.push(`${errored.length} error`)
|
|
74
|
+
console.log(`\n${Color.faint(parts.join(' · '))}`)
|
|
75
|
+
})
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { readdirSync, renameSync, statSync } from 'node:fs'
|
|
2
2
|
import { extname, join } from 'node:path'
|
|
3
3
|
|
|
4
|
-
import
|
|
5
|
-
import { command as createCommand } from 'termkit'
|
|
4
|
+
import { Color, command as createCommand, log } from 'termkit'
|
|
6
5
|
|
|
7
6
|
export const command = createCommand('rename-season')
|
|
8
7
|
.description('Rename files in a directory to SxEE format for TV library pickup')
|
|
@@ -14,7 +13,7 @@ export const command = createCommand('rename-season')
|
|
|
14
13
|
try {
|
|
15
14
|
files = readdirSync(dir)
|
|
16
15
|
} catch {
|
|
17
|
-
|
|
16
|
+
log.fail(`Could not read directory: ${dir}`)
|
|
18
17
|
process.exit(1)
|
|
19
18
|
}
|
|
20
19
|
|
|
@@ -33,7 +32,7 @@ export const command = createCommand('rename-season')
|
|
|
33
32
|
const newPath = join(dir, newName)
|
|
34
33
|
|
|
35
34
|
if (filePath !== newPath) {
|
|
36
|
-
console.log(`${
|
|
35
|
+
console.log(`${Color.faint(file)} -> ${Color.cyan(newName)}`)
|
|
37
36
|
renameSync(filePath, newPath)
|
|
38
37
|
renamed++
|
|
39
38
|
}
|
|
@@ -41,5 +40,5 @@ export const command = createCommand('rename-season')
|
|
|
41
40
|
counter++
|
|
42
41
|
}
|
|
43
42
|
|
|
44
|
-
|
|
43
|
+
log.succeed(`Renamed ${renamed} files.`)
|
|
45
44
|
})
|
|
@@ -2,15 +2,14 @@ import { execSync } from 'node:child_process'
|
|
|
2
2
|
import { readdirSync, statSync } from 'node:fs'
|
|
3
3
|
import { join, resolve } from 'node:path'
|
|
4
4
|
|
|
5
|
-
import
|
|
6
|
-
import { command as createCommand } from 'termkit'
|
|
5
|
+
import { Color, command as createCommand, Spinner } from 'termkit'
|
|
7
6
|
|
|
8
7
|
function getGitStatus(dir) {
|
|
9
8
|
try {
|
|
10
9
|
const out = execSync('git status --porcelain', {
|
|
11
10
|
cwd: dir,
|
|
12
11
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
13
|
-
encoding: 'utf8'
|
|
12
|
+
encoding: 'utf8'
|
|
14
13
|
})
|
|
15
14
|
const lines = out.split('\n').filter(Boolean)
|
|
16
15
|
const dirty = lines.filter((l) => !l.startsWith('??'))
|
|
@@ -21,7 +20,7 @@ function getGitStatus(dir) {
|
|
|
21
20
|
const ahead = execSync('git rev-list --count @{u}..HEAD', {
|
|
22
21
|
cwd: dir,
|
|
23
22
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
24
|
-
encoding: 'utf8'
|
|
23
|
+
encoding: 'utf8'
|
|
25
24
|
}).trim()
|
|
26
25
|
unpushed = parseInt(ahead, 10) || 0
|
|
27
26
|
} catch {
|
|
@@ -44,10 +43,13 @@ export const command = createCommand('repo-status')
|
|
|
44
43
|
try {
|
|
45
44
|
entries = readdirSync(root)
|
|
46
45
|
} catch {
|
|
47
|
-
console.error(
|
|
46
|
+
console.error(Color.red(`Could not read directory: ${root}`))
|
|
48
47
|
process.exit(1)
|
|
49
48
|
}
|
|
50
49
|
|
|
50
|
+
const spinner = new Spinner({ text: 'Checking repos...' })
|
|
51
|
+
spinner.start()
|
|
52
|
+
|
|
51
53
|
const repos = entries
|
|
52
54
|
.filter((name) => {
|
|
53
55
|
try {
|
|
@@ -56,7 +58,10 @@ export const command = createCommand('repo-status')
|
|
|
56
58
|
return false
|
|
57
59
|
}
|
|
58
60
|
})
|
|
59
|
-
.map((name) =>
|
|
61
|
+
.map((name) => {
|
|
62
|
+
spinner.message(name)
|
|
63
|
+
return { name, ...getGitStatus(join(root, name)) }
|
|
64
|
+
})
|
|
60
65
|
.filter((r) => r.isRepo)
|
|
61
66
|
|
|
62
67
|
const dirty = repos.filter((r) => r.dirty.length > 0)
|
|
@@ -65,30 +70,32 @@ export const command = createCommand('repo-status')
|
|
|
65
70
|
const clean = repos.filter((r) => r.dirty.length === 0 && r.untracked.length === 0 && r.unpushed === 0)
|
|
66
71
|
|
|
67
72
|
if (dirty.length === 0 && untracked.length === 0 && unpushed.length === 0) {
|
|
68
|
-
|
|
73
|
+
spinner.succeed(`All ${clean.length} repos are clean.`)
|
|
69
74
|
return
|
|
70
75
|
}
|
|
71
76
|
|
|
77
|
+
spinner.stop()
|
|
78
|
+
|
|
72
79
|
if (dirty.length > 0) {
|
|
73
|
-
console.log(
|
|
80
|
+
console.log(Color.bold.red(`\nDirty (${dirty.length})`))
|
|
74
81
|
for (const repo of dirty) {
|
|
75
|
-
console.log(` ${
|
|
82
|
+
console.log(` ${Color.red(repo.name)} ${Color.faint(`${repo.dirty.length} change${repo.dirty.length !== 1 ? 's' : ''}`)}`)
|
|
76
83
|
}
|
|
77
84
|
}
|
|
78
85
|
|
|
79
86
|
if (untracked.length > 0) {
|
|
80
|
-
console.log(
|
|
87
|
+
console.log(Color.bold.yellow(`\nUntracked (${untracked.length})`))
|
|
81
88
|
for (const repo of untracked) {
|
|
82
|
-
console.log(` ${
|
|
89
|
+
console.log(` ${Color.yellow(repo.name)} ${Color.faint(`${repo.untracked.length} file${repo.untracked.length !== 1 ? 's' : ''}`)}`)
|
|
83
90
|
}
|
|
84
91
|
}
|
|
85
92
|
|
|
86
93
|
if (unpushed.length > 0) {
|
|
87
|
-
console.log(
|
|
94
|
+
console.log(Color.bold.cyan(`\nUnpushed (${unpushed.length})`))
|
|
88
95
|
for (const repo of unpushed) {
|
|
89
|
-
console.log(` ${
|
|
96
|
+
console.log(` ${Color.cyan(repo.name)} ${Color.faint(`${repo.unpushed} commit${repo.unpushed !== 1 ? 's' : ''} ahead`)}`)
|
|
90
97
|
}
|
|
91
98
|
}
|
|
92
99
|
|
|
93
|
-
console.log(
|
|
100
|
+
console.log(Color.faint(`\n${clean.length} of ${repos.length} repos clean`))
|
|
94
101
|
})
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process'
|
|
2
|
+
import { existsSync } from 'node:fs'
|
|
3
|
+
import { homedir } from 'node:os'
|
|
4
|
+
import { join } from 'node:path'
|
|
5
|
+
|
|
6
|
+
import { Color, command as createCommand, log } from 'termkit'
|
|
7
|
+
|
|
8
|
+
const BOILERPLATE_REPO = 'git@github.com:jayrdeaton/Expo-Boilerplate.git'
|
|
9
|
+
const BOILERPLATE_DIR = join(homedir(), 'Developer', 'Expo-Boilerplate')
|
|
10
|
+
|
|
11
|
+
function exec(cmd, opts = {}) {
|
|
12
|
+
console.log(Color.faint(`$ ${cmd}`))
|
|
13
|
+
execSync(cmd, { stdio: 'inherit', ...opts })
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const command = createCommand('update-boilerplate')
|
|
17
|
+
.description('Update Expo boilerplate — clones if absent, updates deps, commits, and pushes')
|
|
18
|
+
.action(async () => {
|
|
19
|
+
if (existsSync(BOILERPLATE_DIR)) {
|
|
20
|
+
log.info('Boilerplate found, pulling latest...')
|
|
21
|
+
exec('git pull', { cwd: BOILERPLATE_DIR })
|
|
22
|
+
} else {
|
|
23
|
+
log.info('Cloning boilerplate...')
|
|
24
|
+
exec(`git clone ${BOILERPLATE_REPO} "${BOILERPLATE_DIR}"`)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
log.info('Updating dependencies...')
|
|
28
|
+
exec('jrd update-deps', { cwd: BOILERPLATE_DIR })
|
|
29
|
+
|
|
30
|
+
const status = execSync('git status --porcelain', { cwd: BOILERPLATE_DIR }).toString().trim()
|
|
31
|
+
|
|
32
|
+
if (!status) {
|
|
33
|
+
log.succeed('Nothing to commit — boilerplate is already up to date.')
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
log.info('Running lint fix...')
|
|
38
|
+
exec('npm run fix', { cwd: BOILERPLATE_DIR })
|
|
39
|
+
|
|
40
|
+
log.info('Running type check...')
|
|
41
|
+
exec('npm run check', { cwd: BOILERPLATE_DIR })
|
|
42
|
+
|
|
43
|
+
log.info('Running tests...')
|
|
44
|
+
exec('npm test', { cwd: BOILERPLATE_DIR })
|
|
45
|
+
|
|
46
|
+
log.info('Committing and pushing...')
|
|
47
|
+
exec('git add -A', { cwd: BOILERPLATE_DIR })
|
|
48
|
+
exec('git commit -m "Update dependencies"', { cwd: BOILERPLATE_DIR })
|
|
49
|
+
exec('git push', { cwd: BOILERPLATE_DIR })
|
|
50
|
+
|
|
51
|
+
log.succeed('Boilerplate updated.')
|
|
52
|
+
})
|