@jayrdeaton/scripts 1.0.0 → 1.0.2
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/package.json +17 -7
- package/src/commands/bump-ota.mjs +10 -7
- package/src/commands/code-count.mjs +10 -13
- package/src/commands/folder-sizes.mjs +8 -1
- package/src/commands/npm-downloads.mjs +97 -0
- package/src/commands/rename-season.mjs +3 -2
- package/src/commands/repo-status.mjs +13 -4
- package/src/commands/update-deps.mjs +6 -5
package/package.json
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jayrdeaton/scripts",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Personal dev scripts",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/jayrdeaton/Scripts"
|
|
9
|
+
},
|
|
6
10
|
"license": "MIT",
|
|
7
11
|
"type": "module",
|
|
8
12
|
"bin": {
|
|
@@ -13,12 +17,18 @@
|
|
|
13
17
|
"src"
|
|
14
18
|
],
|
|
15
19
|
"scripts": {
|
|
16
|
-
"fix": "
|
|
17
|
-
"lint": "eslint"
|
|
20
|
+
"fix": "npm run lint:fix",
|
|
21
|
+
"lint": "eslint",
|
|
22
|
+
"lint:fix": "eslint --fix",
|
|
23
|
+
"release:major": "npm version major && git push --follow-tags",
|
|
24
|
+
"release:minor": "npm version minor && git push --follow-tags",
|
|
25
|
+
"release:patch": "npm version patch && git push --follow-tags",
|
|
26
|
+
"preversion": "npm ci && npm run lint"
|
|
18
27
|
},
|
|
19
28
|
"dependencies": {
|
|
20
29
|
"cosmetic": "latest",
|
|
21
|
-
"termkit": "latest"
|
|
30
|
+
"termkit": "latest",
|
|
31
|
+
"termpulse": "^1.1.3"
|
|
22
32
|
},
|
|
23
33
|
"devDependencies": {
|
|
24
34
|
"eslint": "^10.4.1",
|
|
@@ -28,10 +38,10 @@
|
|
|
28
38
|
"eslint-plugin-simple-import-sort": "^13.0.0",
|
|
29
39
|
"prettier": "^3.8.3"
|
|
30
40
|
},
|
|
31
|
-
"publishConfig": {
|
|
32
|
-
"access": "public"
|
|
33
|
-
},
|
|
34
41
|
"engines": {
|
|
35
42
|
"node": ">=20"
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
36
46
|
}
|
|
37
47
|
}
|
|
@@ -2,23 +2,25 @@ import { execSync } from 'node:child_process'
|
|
|
2
2
|
import { readFileSync, writeFileSync } from 'node:fs'
|
|
3
3
|
import { resolve } from 'node:path'
|
|
4
4
|
|
|
5
|
-
import cosmetic from 'cosmetic'
|
|
6
5
|
import { command as createCommand } from 'termkit'
|
|
6
|
+
import { Spinner } from 'termpulse'
|
|
7
7
|
|
|
8
8
|
export const command = createCommand('bump-ota')
|
|
9
9
|
.description('Bump otaVersion in src/constants/release.ts and commit')
|
|
10
10
|
.option('f', 'file', '[file]', 'Path to release file (default: src/constants/release.ts)')
|
|
11
11
|
.action(async (options) => {
|
|
12
12
|
const filePath = resolve(process.cwd(), options.file ?? 'src/constants/release.ts')
|
|
13
|
+
const spinner = new Spinner({ text: 'Checking git status...' })
|
|
14
|
+
spinner.start()
|
|
13
15
|
|
|
14
16
|
try {
|
|
15
17
|
const status = execSync('git status --porcelain').toString().trim()
|
|
16
18
|
if (status) {
|
|
17
|
-
|
|
19
|
+
spinner.fail('Working directory is not clean. Commit or stash changes first.')
|
|
18
20
|
process.exit(1)
|
|
19
21
|
}
|
|
20
22
|
} catch (err) {
|
|
21
|
-
|
|
23
|
+
spinner.fail(`Failed to check git status: ${err.message}`)
|
|
22
24
|
process.exit(1)
|
|
23
25
|
}
|
|
24
26
|
|
|
@@ -26,7 +28,7 @@ export const command = createCommand('bump-ota')
|
|
|
26
28
|
try {
|
|
27
29
|
source = readFileSync(filePath, 'utf8')
|
|
28
30
|
} catch {
|
|
29
|
-
|
|
31
|
+
spinner.fail(`Could not read file: ${filePath}`)
|
|
30
32
|
process.exit(1)
|
|
31
33
|
}
|
|
32
34
|
|
|
@@ -34,7 +36,7 @@ export const command = createCommand('bump-ota')
|
|
|
34
36
|
const match = source.match(pattern)
|
|
35
37
|
|
|
36
38
|
if (!match) {
|
|
37
|
-
|
|
39
|
+
spinner.fail(`Could not find otaVersion in ${filePath}`)
|
|
38
40
|
process.exit(1)
|
|
39
41
|
}
|
|
40
42
|
|
|
@@ -43,13 +45,14 @@ export const command = createCommand('bump-ota')
|
|
|
43
45
|
|
|
44
46
|
writeFileSync(filePath, source.replace(pattern, `$1${next}`))
|
|
45
47
|
|
|
48
|
+
spinner.message('Committing...')
|
|
46
49
|
try {
|
|
47
50
|
execSync(`git add ${filePath}`)
|
|
48
51
|
execSync(`git commit -m "otaVersion ${current} -> ${next}"`)
|
|
49
52
|
} catch (err) {
|
|
50
|
-
|
|
53
|
+
spinner.fail(`Auto-commit failed: ${err.message}`)
|
|
51
54
|
process.exit(1)
|
|
52
55
|
}
|
|
53
56
|
|
|
54
|
-
|
|
57
|
+
spinner.succeed(`otaVersion bumped: ${current} -> ${next}`)
|
|
55
58
|
})
|
|
@@ -3,17 +3,11 @@ import { extname, join, resolve } from 'node:path'
|
|
|
3
3
|
|
|
4
4
|
import cosmetic from 'cosmetic'
|
|
5
5
|
import { command as createCommand } from 'termkit'
|
|
6
|
+
import { Spinner } from 'termpulse'
|
|
6
7
|
|
|
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
|
-
])
|
|
8
|
+
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
9
|
|
|
13
|
-
const SKIP = new Set([
|
|
14
|
-
'.DS_Store', '.git', 'Carthage', 'Dockerfile', 'LICENSE', 'Test',
|
|
15
|
-
'node_modules', 'package-lock.json',
|
|
16
|
-
])
|
|
10
|
+
const SKIP = new Set(['.DS_Store', '.git', 'Carthage', 'Dockerfile', 'LICENSE', 'Test', 'node_modules', 'package-lock.json'])
|
|
17
11
|
|
|
18
12
|
function getFiles(base, { ignore = [], recursive = false } = {}) {
|
|
19
13
|
try {
|
|
@@ -69,19 +63,22 @@ export const command = createCommand('code-count')
|
|
|
69
63
|
|
|
70
64
|
const allPaths = paths.flatMap((p) => getFiles(p, { ignore, recursive }))
|
|
71
65
|
|
|
66
|
+
const spinner = new Spinner({ text: 'Scanning...' })
|
|
67
|
+
spinner.start()
|
|
68
|
+
|
|
72
69
|
const totals = {}
|
|
73
70
|
for (let i = 0; i < allPaths.length; i++) {
|
|
74
71
|
const path = allPaths[i]
|
|
75
72
|
const ext = extname(path)
|
|
76
73
|
if (!ext) continue
|
|
77
|
-
|
|
74
|
+
spinner.message(`Checking files ${i + 1} / ${allPaths.length}`)
|
|
78
75
|
totals[ext] = (totals[ext] ?? 0) + (await countLines(path))
|
|
79
76
|
}
|
|
80
|
-
|
|
77
|
+
|
|
78
|
+
const total = Object.values(totals).reduce((a, n) => a + n, 0)
|
|
79
|
+
spinner.succeed(`${commaString(total)} lines across ${allPaths.length} files`)
|
|
81
80
|
|
|
82
81
|
for (const key of Object.keys(totals).sort()) {
|
|
83
82
|
console.log(`${key}: ${cosmetic.cyan(commaString(totals[key]))}`)
|
|
84
83
|
}
|
|
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
84
|
})
|
|
@@ -3,6 +3,7 @@ import { join, resolve } from 'node:path'
|
|
|
3
3
|
|
|
4
4
|
import cosmetic from 'cosmetic'
|
|
5
5
|
import { command as createCommand } from 'termkit'
|
|
6
|
+
import { Spinner } from 'termpulse'
|
|
6
7
|
|
|
7
8
|
function getDirSize(dirPath) {
|
|
8
9
|
let total = 0
|
|
@@ -43,19 +44,25 @@ export const command = createCommand('folder-sizes')
|
|
|
43
44
|
process.exit(1)
|
|
44
45
|
}
|
|
45
46
|
|
|
47
|
+
const spinner = new Spinner({ text: 'Scanning...' })
|
|
48
|
+
spinner.start()
|
|
49
|
+
|
|
46
50
|
const folders = entries
|
|
47
51
|
.filter((e) => e.isDirectory())
|
|
48
52
|
.map((e) => {
|
|
53
|
+
spinner.message(e.name)
|
|
49
54
|
const size = getDirSize(join(root, e.name))
|
|
50
55
|
return { name: e.name, size }
|
|
51
56
|
})
|
|
52
57
|
.sort((a, b) => b.size - a.size)
|
|
53
58
|
|
|
54
59
|
if (folders.length === 0) {
|
|
55
|
-
|
|
60
|
+
spinner.warn('No folders found.')
|
|
56
61
|
return
|
|
57
62
|
}
|
|
58
63
|
|
|
64
|
+
spinner.succeed(`${folders.length} folders`)
|
|
65
|
+
|
|
59
66
|
const maxName = Math.max(...folders.map((f) => f.name.length))
|
|
60
67
|
const maxSize = Math.max(...folders.map((f) => formatSize(f.size).length))
|
|
61
68
|
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process'
|
|
2
|
+
|
|
3
|
+
import cosmetic from 'cosmetic'
|
|
4
|
+
import { command as createCommand } from 'termkit'
|
|
5
|
+
import { Spinner } from 'termpulse'
|
|
6
|
+
|
|
7
|
+
async function fetchJson(url) {
|
|
8
|
+
const res = await fetch(url)
|
|
9
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}: ${url}`)
|
|
10
|
+
return res.json()
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function getAllPackages(username) {
|
|
14
|
+
const packages = []
|
|
15
|
+
const size = 250
|
|
16
|
+
let from = 0
|
|
17
|
+
|
|
18
|
+
while (true) {
|
|
19
|
+
const data = await fetchJson(`https://registry.npmjs.org/-/v1/search?text=maintainer:${username}&size=${size}&from=${from}`)
|
|
20
|
+
packages.push(...data.objects.map((o) => o.package.name))
|
|
21
|
+
if (packages.length >= data.total || data.objects.length < size) break
|
|
22
|
+
from += size
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return packages
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function getDownloads(pkg, period) {
|
|
29
|
+
try {
|
|
30
|
+
const data = await fetchJson(`https://api.npmjs.org/downloads/point/${period}/${pkg}`)
|
|
31
|
+
return data.downloads ?? 0
|
|
32
|
+
} catch {
|
|
33
|
+
return 0
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const command = createCommand('npm-downloads')
|
|
38
|
+
.description('List all your npm packages sorted by total downloads')
|
|
39
|
+
.option('u', 'user', '<name>', 'npm username (defaults to npm whoami)')
|
|
40
|
+
.option('p', 'period', '<period>', 'last-day | last-week | last-month | last-year (default: last-month)')
|
|
41
|
+
.option('m', 'mtd', null, 'Use month-to-date instead of rolling 30 days')
|
|
42
|
+
.action(async (options) => {
|
|
43
|
+
const spinner = new Spinner({ text: 'Resolving username...' })
|
|
44
|
+
spinner.start()
|
|
45
|
+
|
|
46
|
+
let username = options.user
|
|
47
|
+
if (!username) {
|
|
48
|
+
try {
|
|
49
|
+
username = execSync('npm whoami', {
|
|
50
|
+
encoding: 'utf8',
|
|
51
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
52
|
+
}).trim()
|
|
53
|
+
} catch {
|
|
54
|
+
spinner.fail('Could not determine npm username. Use --user <name> or run `npm login`.')
|
|
55
|
+
process.exit(1)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let period = options.period ?? 'last-month'
|
|
60
|
+
if (options.mtd) {
|
|
61
|
+
const now = new Date()
|
|
62
|
+
const yyyy = now.getFullYear()
|
|
63
|
+
const mm = String(now.getMonth() + 1).padStart(2, '0')
|
|
64
|
+
const dd = String(now.getDate()).padStart(2, '0')
|
|
65
|
+
period = `${yyyy}-${mm}-01:${yyyy}-${mm}-${dd}`
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
spinner.message(`Fetching packages for ${username}...`)
|
|
69
|
+
const packages = await getAllPackages(username)
|
|
70
|
+
|
|
71
|
+
if (packages.length === 0) {
|
|
72
|
+
spinner.warn(`No packages found for ${username}.`)
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
spinner.message(`Fetching download counts (${period})...`)
|
|
77
|
+
const results = await Promise.all(packages.map(async (name) => ({ name, downloads: await getDownloads(name, period) })))
|
|
78
|
+
|
|
79
|
+
spinner.succeed(`${packages.length} packages · ${period}`)
|
|
80
|
+
|
|
81
|
+
const sorted = results.sort((a, b) => b.downloads - a.downloads)
|
|
82
|
+
const orgs = [...new Set(sorted.filter((p) => p.name.startsWith('@')).map((p) => p.name.split('/')[0]))]
|
|
83
|
+
const maxWidth = String(sorted[0]?.downloads ?? 0).length
|
|
84
|
+
|
|
85
|
+
console.log(`\n${cosmetic.bold(username)} — ${cosmetic.faint(period)}`)
|
|
86
|
+
if (orgs.length > 0) console.log(cosmetic.faint(`orgs: ${orgs.join(' ')}`))
|
|
87
|
+
console.log()
|
|
88
|
+
|
|
89
|
+
for (const { name, downloads } of sorted) {
|
|
90
|
+
const count = String(downloads).padStart(maxWidth)
|
|
91
|
+
const label = downloads === 0 ? cosmetic.faint(name) : name
|
|
92
|
+
console.log(` ${cosmetic.cyan(count)} ${label}`)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const total = sorted.reduce((sum, p) => sum + p.downloads, 0)
|
|
96
|
+
console.log(cosmetic.faint(`\n${total.toLocaleString()} total downloads across ${sorted.length} packages`))
|
|
97
|
+
})
|
|
@@ -3,6 +3,7 @@ import { extname, join } from 'node:path'
|
|
|
3
3
|
|
|
4
4
|
import cosmetic from 'cosmetic'
|
|
5
5
|
import { command as createCommand } from 'termkit'
|
|
6
|
+
import { log } from 'termpulse'
|
|
6
7
|
|
|
7
8
|
export const command = createCommand('rename-season')
|
|
8
9
|
.description('Rename files in a directory to SxEE format for TV library pickup')
|
|
@@ -14,7 +15,7 @@ export const command = createCommand('rename-season')
|
|
|
14
15
|
try {
|
|
15
16
|
files = readdirSync(dir)
|
|
16
17
|
} catch {
|
|
17
|
-
|
|
18
|
+
log.fail(`Could not read directory: ${dir}`)
|
|
18
19
|
process.exit(1)
|
|
19
20
|
}
|
|
20
21
|
|
|
@@ -41,5 +42,5 @@ export const command = createCommand('rename-season')
|
|
|
41
42
|
counter++
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
|
|
45
|
+
log.succeed(`Renamed ${renamed} files.`)
|
|
45
46
|
})
|
|
@@ -4,13 +4,14 @@ import { join, resolve } from 'node:path'
|
|
|
4
4
|
|
|
5
5
|
import cosmetic from 'cosmetic'
|
|
6
6
|
import { command as createCommand } from 'termkit'
|
|
7
|
+
import { Spinner } from 'termpulse'
|
|
7
8
|
|
|
8
9
|
function getGitStatus(dir) {
|
|
9
10
|
try {
|
|
10
11
|
const out = execSync('git status --porcelain', {
|
|
11
12
|
cwd: dir,
|
|
12
13
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
13
|
-
encoding: 'utf8'
|
|
14
|
+
encoding: 'utf8'
|
|
14
15
|
})
|
|
15
16
|
const lines = out.split('\n').filter(Boolean)
|
|
16
17
|
const dirty = lines.filter((l) => !l.startsWith('??'))
|
|
@@ -21,7 +22,7 @@ function getGitStatus(dir) {
|
|
|
21
22
|
const ahead = execSync('git rev-list --count @{u}..HEAD', {
|
|
22
23
|
cwd: dir,
|
|
23
24
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
24
|
-
encoding: 'utf8'
|
|
25
|
+
encoding: 'utf8'
|
|
25
26
|
}).trim()
|
|
26
27
|
unpushed = parseInt(ahead, 10) || 0
|
|
27
28
|
} catch {
|
|
@@ -48,6 +49,9 @@ export const command = createCommand('repo-status')
|
|
|
48
49
|
process.exit(1)
|
|
49
50
|
}
|
|
50
51
|
|
|
52
|
+
const spinner = new Spinner({ text: 'Checking repos...' })
|
|
53
|
+
spinner.start()
|
|
54
|
+
|
|
51
55
|
const repos = entries
|
|
52
56
|
.filter((name) => {
|
|
53
57
|
try {
|
|
@@ -56,7 +60,10 @@ export const command = createCommand('repo-status')
|
|
|
56
60
|
return false
|
|
57
61
|
}
|
|
58
62
|
})
|
|
59
|
-
.map((name) =>
|
|
63
|
+
.map((name) => {
|
|
64
|
+
spinner.message(name)
|
|
65
|
+
return { name, ...getGitStatus(join(root, name)) }
|
|
66
|
+
})
|
|
60
67
|
.filter((r) => r.isRepo)
|
|
61
68
|
|
|
62
69
|
const dirty = repos.filter((r) => r.dirty.length > 0)
|
|
@@ -65,10 +72,12 @@ export const command = createCommand('repo-status')
|
|
|
65
72
|
const clean = repos.filter((r) => r.dirty.length === 0 && r.untracked.length === 0 && r.unpushed === 0)
|
|
66
73
|
|
|
67
74
|
if (dirty.length === 0 && untracked.length === 0 && unpushed.length === 0) {
|
|
68
|
-
|
|
75
|
+
spinner.succeed(`All ${clean.length} repos are clean.`)
|
|
69
76
|
return
|
|
70
77
|
}
|
|
71
78
|
|
|
79
|
+
spinner.stop()
|
|
80
|
+
|
|
72
81
|
if (dirty.length > 0) {
|
|
73
82
|
console.log(cosmetic.bold.red(`\nDirty (${dirty.length})`))
|
|
74
83
|
for (const repo of dirty) {
|
|
@@ -4,6 +4,7 @@ import { resolve } from 'node:path'
|
|
|
4
4
|
|
|
5
5
|
import cosmetic from 'cosmetic'
|
|
6
6
|
import { command as createCommand } from 'termkit'
|
|
7
|
+
import { log } from 'termpulse'
|
|
7
8
|
|
|
8
9
|
function exec(cmd) {
|
|
9
10
|
console.log(cosmetic.faint(`$ ${cmd}`))
|
|
@@ -26,7 +27,7 @@ export const command = createCommand('update-deps')
|
|
|
26
27
|
try {
|
|
27
28
|
pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
|
|
28
29
|
} catch {
|
|
29
|
-
|
|
30
|
+
log.fail('No package.json found in current directory.')
|
|
30
31
|
process.exit(1)
|
|
31
32
|
}
|
|
32
33
|
|
|
@@ -35,21 +36,21 @@ export const command = createCommand('update-deps')
|
|
|
35
36
|
const legacyFlag = options.legacy ? ' --legacy-peer-deps' : ''
|
|
36
37
|
|
|
37
38
|
if (!options.dev && prodDeps.length) {
|
|
38
|
-
|
|
39
|
+
log.info('Updating dependencies...')
|
|
39
40
|
exec(`npm install${legacyFlag} ${prodDeps.join(' ')}`)
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
if (!options.prod && devDeps.length) {
|
|
43
|
-
|
|
44
|
+
log.info('Updating devDependencies...')
|
|
44
45
|
exec(`npm install --save-dev${legacyFlag} ${devDeps.join(' ')}`)
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
const hasExpo = pkg.dependencies?.expo !== undefined || pkg.devDependencies?.expo !== undefined
|
|
48
49
|
|
|
49
50
|
if (hasExpo) {
|
|
50
|
-
|
|
51
|
+
log.info('Fixing Expo managed versions...')
|
|
51
52
|
exec(`npx expo install --fix${options.legacy ? ' -- --legacy-peer-deps' : ''}`)
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
|
|
55
|
+
log.succeed('Done.')
|
|
55
56
|
})
|