@emeryld/manager 0.2.0 → 0.2.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/src/utils/run.ts DELETED
@@ -1,30 +0,0 @@
1
- // src/utils/run.js
2
- import { spawn, type SpawnOptions } from 'node:child_process'
3
- import path from 'node:path'
4
- import { fileURLToPath } from 'node:url'
5
-
6
- const __filename = fileURLToPath(import.meta.url)
7
- export const rootDir = path.resolve(path.dirname(__filename), '..', '..')
8
-
9
- export function run(
10
- command: string,
11
- args: string[],
12
- options: SpawnOptions = {},
13
- ) {
14
- return new Promise<void>((resolve, reject) => {
15
- const child = spawn(command, args, {
16
- cwd: rootDir,
17
- stdio: 'inherit',
18
- ...options,
19
- })
20
- child.on('close', (code) => {
21
- if (code === 0) resolve()
22
- else
23
- reject(
24
- new Error(
25
- `Command "${command} ${args.join(' ')}" exited with ${code}`,
26
- ),
27
- )
28
- })
29
- })
30
- }
package/src/workspace.ts DELETED
@@ -1,290 +0,0 @@
1
- // src/workspace.js
2
- import { spawnSync } from 'node:child_process'
3
- import { run, rootDir } from './utils/run.js'
4
- import type { LoadedPackage } from './utils/log.js'
5
- import { logGlobal, logPkg, colors } from './utils/log.js'
6
- import { collectGitStatus, gitAdd, gitCommit } from './git.js'
7
- import { askLine, promptSingleKey } from './prompts.js'
8
-
9
-
10
- const dependencyFiles = new Set([
11
- 'package.json',
12
- 'pnpm-lock.yaml',
13
- 'package-lock.json',
14
- 'npm-shrinkwrap.json',
15
- 'pnpm-workspace.yaml',
16
- ])
17
-
18
- function extractPathFromStatus(line: string) {
19
- const match = line.match(/^[AMDR\? ][\S\?]\s+(.*)$/)
20
- if (!match) return undefined
21
- const rawPath = match[1].trim().replace(/"/g, '')
22
- const normalized = rawPath.includes(' -> ')
23
- ? rawPath.split(' -> ').pop()
24
- : rawPath
25
- return normalized
26
- }
27
-
28
- function dependencyPathsFromStatus(status: string[]) {
29
- return status
30
- .map(extractPathFromStatus)
31
- .filter((p): p is string =>
32
- Boolean(p && dependencyFiles.has(p.split('/').pop() ?? '')),
33
- )
34
- }
35
-
36
- function formatPkgLabel(pkg: LoadedPackage) {
37
- return pkg.substitute ?? pkg.name ?? pkg.dirName
38
- }
39
-
40
- function logDependencyChanges(paths: string[], targets: LoadedPackage[]) {
41
- if (paths.length === 0) return
42
- const byDir = new Map(targets.map((t) => [t.dirName, formatPkgLabel(t)]))
43
-
44
- logGlobal('Dependency file changes detected:', colors.cyan)
45
- paths.forEach((p) => {
46
- const parts = p.split('/')
47
- const file = parts[parts.length - 1]
48
- const pkgIndex = parts.indexOf('packages')
49
- if (pkgIndex !== -1 && parts[pkgIndex + 1]) {
50
- const dir = parts[pkgIndex + 1]
51
- const fileLabel = parts.slice(pkgIndex + 2).join('/') || 'package.json'
52
- console.log(
53
- ` • ${colors.dim(`${byDir.get(dir) ?? dir} (${fileLabel})`)}`,
54
- )
55
- return
56
- }
57
- if (
58
- file === 'pnpm-lock.yaml' ||
59
- file === 'package-lock.json' ||
60
- file === 'npm-shrinkwrap.json'
61
- ) {
62
- console.log(` • ${colors.dim(`workspace lockfile (${file})`)}`)
63
- return
64
- }
65
- if (file === 'pnpm-workspace.yaml') {
66
- console.log(` • ${colors.dim('workspace pnpm-workspace.yaml')}`)
67
- return
68
- }
69
- if (file === 'package.json') {
70
- console.log(` • ${colors.dim('workspace package.json')}`)
71
- return
72
- }
73
- console.log(` • ${colors.dim(p)}`)
74
- })
75
- }
76
-
77
- type VersionChange = { dep: string; from: string; to: string; label: string }
78
-
79
- function readDiff(path: string) {
80
- const res = spawnSync('git', ['diff', '--unified=0', '--', path], {
81
- cwd: rootDir,
82
- encoding: 'utf8',
83
- })
84
- if (typeof res.stdout === 'string') return res.stdout
85
- return ''
86
- }
87
-
88
- function parseVersionChanges(path: string, label: string): VersionChange[] {
89
- const diff = readDiff(path)
90
- if (!diff) return []
91
-
92
- const changes = new Map<string, { from?: string; to?: string }>()
93
- const lineRe = /^[\-\+]\s+"([^"]+)":\s*"([^"]+)"/
94
-
95
- diff.split('\n').forEach((line) => {
96
- if (line.startsWith('+++') || line.startsWith('---')) return
97
- const match = line.match(lineRe)
98
- if (!match) return
99
- const [, dep, version] = match
100
- const entry = changes.get(dep) ?? {}
101
- if (line.startsWith('-')) entry.from = version
102
- if (line.startsWith('+')) entry.to = version
103
- changes.set(dep, entry)
104
- })
105
-
106
- return [...changes.entries()]
107
- .filter(([, v]) => v.from && v.to && v.from !== v.to)
108
- .map(([dep, v]) => ({
109
- dep,
110
- from: v.from as string,
111
- to: v.to as string,
112
- label,
113
- }))
114
- }
115
-
116
- function summarizeVersionChanges(paths: string[], targets: LoadedPackage[]) {
117
- const byDir = new Map(targets.map((t) => [t.dirName, formatPkgLabel(t)]))
118
- const changes: VersionChange[] = []
119
-
120
- paths
121
- .filter((p) => p.endsWith('package.json'))
122
- .forEach((p) => {
123
- const parts = p.split('/')
124
- const pkgIndex = parts.indexOf('packages')
125
- const label =
126
- pkgIndex !== -1 && parts[pkgIndex + 1]
127
- ? (byDir.get(parts[pkgIndex + 1]) ?? parts[pkgIndex + 1])
128
- : 'workspace'
129
- changes.push(...parseVersionChanges(p, label))
130
- })
131
-
132
- if (changes.length === 0) return ''
133
-
134
- const grouped = new Map<string, VersionChange[]>()
135
- changes.forEach((c) => {
136
- const list = grouped.get(c.label) ?? []
137
- list.push(c)
138
- grouped.set(c.label, list)
139
- })
140
-
141
- const summaries: string[] = []
142
- grouped.forEach((list, label) => {
143
- const slice = list.slice(0, 3).map((c) => `${c.dep} ${c.from}→${c.to}`)
144
- const extra =
145
- list.length > slice.length ? ` (+${list.length - slice.length} more)` : ''
146
- summaries.push(`${label}: ${slice.join(', ')}${extra}`)
147
- })
148
-
149
- return summaries.join('; ')
150
- }
151
-
152
- function buildUpdateCommitMessage(paths: string[], targets: LoadedPackage[]) {
153
- const changeSummary = summarizeVersionChanges(paths, targets)
154
- if (changeSummary) return `chore(deps): ${changeSummary}`
155
-
156
- const labels = new Set<string>()
157
- const byDir = new Map(targets.map((t) => [t.dirName, formatPkgLabel(t)]))
158
- paths.forEach((p) => {
159
- const parts = p.split('/')
160
- const pkgIndex = parts.indexOf('packages')
161
- if (pkgIndex !== -1 && parts[pkgIndex + 1]) {
162
- const dir = parts[pkgIndex + 1]
163
- labels.add(byDir.get(dir) ?? dir)
164
- return
165
- }
166
- if (parts[parts.length - 1] === 'package.json') {
167
- labels.add('workspace deps')
168
- }
169
- })
170
-
171
- if (labels.size === 0 && targets.length === 1) {
172
- labels.add(formatPkgLabel(targets[0]))
173
- }
174
-
175
- const labelList = [...labels]
176
- if (labelList.length === 0) return 'chore(deps): update dependencies'
177
- if (labelList.length === 1) return `chore(deps): update ${labelList[0]}`
178
- return `chore(deps): update ${labelList.join(', ')}`
179
- }
180
-
181
- async function promptCommitMessage(proposed: string) {
182
- const confirm = await promptSingleKey<'yes' | 'no'>(
183
- `Use commit message "${proposed}"? (y/n): `,
184
- (key) => {
185
- if (key === 'y') return 'yes'
186
- if (key === 'n') return 'no'
187
- return undefined
188
- },
189
- )
190
- if (confirm === 'yes') return proposed
191
-
192
- let custom = ''
193
- while (!custom.trim()) {
194
- // eslint-disable-next-line no-await-in-loop
195
- custom = await askLine('Enter commit message: ')
196
- if (!custom.trim())
197
- console.log(colors.red('Commit message cannot be empty.'))
198
- }
199
- return custom.trim()
200
- }
201
-
202
- export async function runCleanInstall() {
203
- logGlobal('Cleaning workspace…', colors.cyan)
204
- await run('pnpm', ['run', 'clean'])
205
- logGlobal('Reinstalling dependencies…', colors.cyan)
206
- await run('pnpm', ['install'])
207
- }
208
-
209
- export async function updateDependencies(targets: LoadedPackage[]) {
210
- const preStatus = await collectGitStatus()
211
-
212
- if (targets.length === 1) {
213
- const filterArg = targets[0].name ?? `./packages/${targets[0].dirName}`
214
- logPkg(targets[0], `Updating dependencies…`)
215
- await run('pnpm', ['-r', '--filter', filterArg, 'update'])
216
- } else {
217
- logGlobal('Updating dependencies across the workspace…', colors.cyan)
218
- await run('pnpm', ['-r', 'update'])
219
- }
220
-
221
- const postStatus = await collectGitStatus()
222
- const depPaths = dependencyPathsFromStatus(postStatus)
223
- const uniqueDepPaths = [...new Set(depPaths)]
224
-
225
- if (uniqueDepPaths.length === 0) {
226
- logGlobal(
227
- 'No dependency file changes detected; skipping commit.',
228
- colors.dim,
229
- )
230
- return
231
- }
232
-
233
- if (preStatus.length) {
234
- logGlobal(
235
- 'Working tree had changes before update; will only stage dependency files for the commit.',
236
- colors.yellow,
237
- )
238
- }
239
-
240
- logDependencyChanges(uniqueDepPaths, targets)
241
-
242
- const proposed = buildUpdateCommitMessage(uniqueDepPaths, targets)
243
- const message = await promptCommitMessage(proposed)
244
-
245
- logGlobal('Staging dependency changes…', colors.cyan)
246
- await gitAdd(uniqueDepPaths)
247
- logGlobal('Creating commit…', colors.cyan)
248
- await gitCommit(message)
249
- logGlobal(`Commit created: ${message}`, colors.green)
250
- logGlobal('Pushing to origin…', colors.cyan)
251
- await run('git', ['push'])
252
- logGlobal('Push complete.', colors.green)
253
- }
254
-
255
- export async function typecheckAll() {
256
- logGlobal('Running typecheck for all packages…', colors.cyan)
257
- await run('pnpm', ['typecheck'])
258
- }
259
-
260
- export async function typecheckSingle(pkg: LoadedPackage) {
261
- const filterArg = pkg.name ?? `./packages/${pkg.dirName}`
262
- logPkg(pkg, `Running typecheck…`)
263
- await run('pnpm', ['run', '--filter', filterArg, 'typecheck'])
264
- }
265
-
266
- export async function buildAll() {
267
- logGlobal('Running build for all packages…', colors.cyan)
268
- await run('pnpm', ['build'])
269
- }
270
-
271
- export async function buildSingle(pkg: LoadedPackage) {
272
- const filterArg = pkg.name ?? `./packages/${pkg.dirName}`
273
- logPkg(pkg, `Running build…`)
274
- await run('pnpm', ['run', '--filter', filterArg, 'build'])
275
- }
276
-
277
- export async function buildPackageLocally(pkg: LoadedPackage) {
278
- logPkg(pkg, 'Building local dist before publish…')
279
- await run('pnpm', ['run', 'build'], { cwd: pkg.path })
280
- }
281
-
282
- export async function testAll() {
283
- logGlobal('Running tests for all packages…', colors.cyan)
284
- await run('pnpm', ['test'])
285
- }
286
-
287
- export async function testSingle(pkg: LoadedPackage) {
288
- logPkg(pkg, `Running tests…`)
289
- await run('pnpm', ['test', '--', `packages/${pkg.dirName}`])
290
- }