@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/bin/manager-cli.js +1 -3
- package/dist/git.js +68 -0
- package/dist/helper-cli.js +299 -0
- package/dist/menu.js +130 -0
- package/dist/packages.js +281 -0
- package/dist/preflight.js +26 -0
- package/dist/prompts.js +85 -0
- package/dist/publish.js +142 -0
- package/dist/release.js +292 -0
- package/dist/semver.js +17 -0
- package/dist/utils/colors.js +11 -0
- package/dist/utils/log.js +19 -0
- package/dist/utils/run.js +21 -0
- package/dist/workspace.js +246 -0
- package/package.json +5 -5
- package/tsconfig.base.json +0 -1
- package/src/git.ts +0 -74
- package/src/helper-cli.ts +0 -405
- package/src/menu.ts +0 -142
- package/src/packages.ts +0 -305
- package/src/preflight.ts +0 -26
- package/src/prompts.ts +0 -93
- package/src/publish.ts +0 -183
- package/src/release.ts +0 -410
- package/src/semver.ts +0 -27
- package/src/sync-version.mjs +0 -213
- package/src/utils/colors.ts +0 -11
- package/src/utils/log.ts +0 -42
- package/src/utils/run.ts +0 -30
- package/src/workspace.ts +0 -290
package/src/packages.ts
DELETED
|
@@ -1,305 +0,0 @@
|
|
|
1
|
-
// src/packages.js
|
|
2
|
-
import path from 'node:path'
|
|
3
|
-
import { pathToFileURL } from 'node:url'
|
|
4
|
-
import { readdir, readFile } from 'node:fs/promises'
|
|
5
|
-
import type { PackageColor, LoadedPackage } from './utils/log.js'
|
|
6
|
-
|
|
7
|
-
const rootDir = process.cwd()
|
|
8
|
-
export const packagesDir = path.join(rootDir, 'packages')
|
|
9
|
-
|
|
10
|
-
type ManifestEntry = {
|
|
11
|
-
name: string
|
|
12
|
-
path: string
|
|
13
|
-
color?: PackageColor
|
|
14
|
-
substitute?: string
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
type ManifestState = {
|
|
18
|
-
entries: ManifestEntry[]
|
|
19
|
-
byName: Map<string, ManifestEntry>
|
|
20
|
-
byPath: Map<string, ManifestEntry>
|
|
21
|
-
orderedPackagePaths: string[]
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const colorPalette: PackageColor[] = ['cyan', 'green', 'yellow', 'magenta', 'red']
|
|
25
|
-
let manifestState: ManifestState | undefined
|
|
26
|
-
|
|
27
|
-
function manifestFilePath() {
|
|
28
|
-
return path.join(rootDir, 'scripts', 'packages.mjs')
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function isManifestMissing(error: unknown) {
|
|
32
|
-
if (typeof error !== 'object' || error === null) return false
|
|
33
|
-
const code = (error as { code?: string }).code
|
|
34
|
-
return code === 'ERR_MODULE_NOT_FOUND' || code === 'ENOENT'
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function normalizeManifestPath(value: string) {
|
|
38
|
-
const absolute = path.resolve(rootDir, value || '')
|
|
39
|
-
let relative = path.relative(rootDir, absolute)
|
|
40
|
-
if (!relative) return ''
|
|
41
|
-
relative = relative.replace(/\\/g, '/')
|
|
42
|
-
return relative.replace(/^(?:\.\/)+/, '')
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function colorFromSeed(seed: string) {
|
|
46
|
-
const normalized = `${seed}`.trim() || 'package'
|
|
47
|
-
let hash = 0
|
|
48
|
-
for (let i = 0; i < normalized.length; i++) {
|
|
49
|
-
hash = (hash * 31 + normalized.charCodeAt(i)) >>> 0
|
|
50
|
-
}
|
|
51
|
-
return colorPalette[hash % colorPalette.length]
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function deriveSubstitute(name: string) {
|
|
55
|
-
const trimmed = (name || '').trim()
|
|
56
|
-
if (!trimmed) return ''
|
|
57
|
-
const segments = trimmed.split(/[@\/\-]/).filter(Boolean)
|
|
58
|
-
const transformed = segments
|
|
59
|
-
.map((segment) => segment.slice(0, 2))
|
|
60
|
-
.filter(Boolean)
|
|
61
|
-
.join(' ')
|
|
62
|
-
return transformed || trimmed
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
async function loadWorkspaceManifest(): Promise<ManifestEntry[] | undefined> {
|
|
66
|
-
const manifestPath = manifestFilePath()
|
|
67
|
-
try {
|
|
68
|
-
const manifestModule = await import(pathToFileURL(manifestPath).href)
|
|
69
|
-
if (Array.isArray(manifestModule?.PACKAGE_MANIFEST)) {
|
|
70
|
-
return manifestModule.PACKAGE_MANIFEST
|
|
71
|
-
}
|
|
72
|
-
} catch (error) {
|
|
73
|
-
if (isManifestMissing(error)) return undefined
|
|
74
|
-
throw error
|
|
75
|
-
}
|
|
76
|
-
return undefined
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async function inferManifestFromWorkspace(): Promise<ManifestEntry[]> {
|
|
80
|
-
try {
|
|
81
|
-
const entries = await readdir(packagesDir, { withFileTypes: true })
|
|
82
|
-
const manifest: ManifestEntry[] = []
|
|
83
|
-
for (const entry of entries) {
|
|
84
|
-
if (!entry.isDirectory()) continue
|
|
85
|
-
const pkgJsonPath = path.join(packagesDir, entry.name, 'package.json')
|
|
86
|
-
try {
|
|
87
|
-
const raw = await readFile(pkgJsonPath, 'utf8')
|
|
88
|
-
const json = JSON.parse(raw) as { name?: string }
|
|
89
|
-
const pkgName = json.name?.trim() || entry.name
|
|
90
|
-
manifest.push({
|
|
91
|
-
name: pkgName,
|
|
92
|
-
path: normalizeManifestPath(path.relative(rootDir, path.join(packagesDir, entry.name))),
|
|
93
|
-
color: colorFromSeed(pkgName),
|
|
94
|
-
substitute: deriveSubstitute(pkgName),
|
|
95
|
-
})
|
|
96
|
-
} catch {
|
|
97
|
-
continue
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
return manifest
|
|
101
|
-
} catch {
|
|
102
|
-
return []
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function mergeManifestEntries(
|
|
107
|
-
inferred: ManifestEntry[],
|
|
108
|
-
overrides?: ManifestEntry[],
|
|
109
|
-
) {
|
|
110
|
-
const normalizedOverrides = new Map<string, ManifestEntry>()
|
|
111
|
-
overrides?.forEach((entry) => {
|
|
112
|
-
const normalized = normalizeManifestPath(entry.path)
|
|
113
|
-
if (!normalized) return
|
|
114
|
-
normalizedOverrides.set(normalized, { ...entry, path: normalized })
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
const merged: ManifestEntry[] = []
|
|
118
|
-
for (const baseEntry of inferred) {
|
|
119
|
-
const normalized = normalizeManifestPath(baseEntry.path)
|
|
120
|
-
const override = normalizedOverrides.get(normalized)
|
|
121
|
-
if (override) {
|
|
122
|
-
normalizedOverrides.delete(normalized)
|
|
123
|
-
const name = override.name || baseEntry.name
|
|
124
|
-
const color = override.color ?? baseEntry.color ?? colorFromSeed(name)
|
|
125
|
-
const substitute =
|
|
126
|
-
override.substitute ?? baseEntry.substitute ?? deriveSubstitute(name) ?? name
|
|
127
|
-
merged.push({ name, path: normalized, color, substitute })
|
|
128
|
-
} else {
|
|
129
|
-
merged.push({ ...baseEntry, path: normalized })
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
normalizedOverrides.forEach((entry) => {
|
|
134
|
-
const name = entry.name || path.basename(entry.path) || 'package'
|
|
135
|
-
const color = entry.color ?? colorFromSeed(name)
|
|
136
|
-
const substitute = entry.substitute ?? deriveSubstitute(name) ?? name
|
|
137
|
-
merged.push({ name, path: entry.path, color, substitute })
|
|
138
|
-
})
|
|
139
|
-
|
|
140
|
-
return merged
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
async function ensureManifestState() {
|
|
144
|
-
if (manifestState) return manifestState
|
|
145
|
-
const [workspaceManifest, inferred] = await Promise.all([
|
|
146
|
-
loadWorkspaceManifest(),
|
|
147
|
-
inferManifestFromWorkspace(),
|
|
148
|
-
])
|
|
149
|
-
const entries = mergeManifestEntries(inferred, workspaceManifest)
|
|
150
|
-
const byName = new Map(entries.map((pkg) => [pkg.name.toLowerCase(), pkg]))
|
|
151
|
-
const byPath = new Map(entries.map((pkg) => [pkg.path.toLowerCase(), pkg]))
|
|
152
|
-
manifestState = {
|
|
153
|
-
entries,
|
|
154
|
-
byName,
|
|
155
|
-
byPath,
|
|
156
|
-
orderedPackagePaths: entries.map((pkg) => pkg.path.toLowerCase()),
|
|
157
|
-
}
|
|
158
|
-
return manifestState
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export async function loadPackages(): Promise<LoadedPackage[]> {
|
|
162
|
-
const entries = await readdir(packagesDir, { withFileTypes: true })
|
|
163
|
-
const packages: LoadedPackage[] = []
|
|
164
|
-
const { byName, byPath } = await ensureManifestState()
|
|
165
|
-
for (const entry of entries) {
|
|
166
|
-
if (!entry.isDirectory()) continue
|
|
167
|
-
const pkgJsonPath = path.join(packagesDir, entry.name, 'package.json')
|
|
168
|
-
try {
|
|
169
|
-
const raw = await readFile(pkgJsonPath, 'utf8')
|
|
170
|
-
const json = JSON.parse(raw)
|
|
171
|
-
const pkgName = (json.name as string | undefined) ?? entry.name
|
|
172
|
-
const relativePath = normalizeManifestPath(
|
|
173
|
-
path.relative(rootDir, path.join(packagesDir, entry.name)),
|
|
174
|
-
)
|
|
175
|
-
const meta =
|
|
176
|
-
byPath.get(relativePath.toLowerCase()) ??
|
|
177
|
-
byName.get(pkgName.toLowerCase())
|
|
178
|
-
const substitute =
|
|
179
|
-
meta?.substitute ?? deriveSubstitute(pkgName) ?? entry.name
|
|
180
|
-
const color = meta?.color ?? colorFromSeed(pkgName)
|
|
181
|
-
packages.push({
|
|
182
|
-
dirName: entry.name,
|
|
183
|
-
path: path.join(packagesDir, entry.name),
|
|
184
|
-
packageJsonPath: pkgJsonPath,
|
|
185
|
-
json,
|
|
186
|
-
version: json.version,
|
|
187
|
-
name: pkgName,
|
|
188
|
-
substitute,
|
|
189
|
-
color,
|
|
190
|
-
})
|
|
191
|
-
} catch (error) {
|
|
192
|
-
console.warn(`Skipping ${entry.name}: ${error}`)
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
return packages
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
export function resolvePackage(packages: LoadedPackage[], key?: string) {
|
|
199
|
-
if (!key) return undefined
|
|
200
|
-
const normalized = key.toLowerCase()
|
|
201
|
-
return packages.find((pkg) => {
|
|
202
|
-
const dirMatch = pkg.dirName.toLowerCase() === normalized
|
|
203
|
-
const nameMatch = (pkg.name ?? '').toLowerCase() === normalized
|
|
204
|
-
const aliasMatch = (pkg.substitute ?? '').toLowerCase() === normalized
|
|
205
|
-
const fuzzyMatch = (pkg.name ?? '').toLowerCase().includes(normalized)
|
|
206
|
-
return dirMatch || nameMatch || aliasMatch || fuzzyMatch
|
|
207
|
-
})
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Topologically sort by internal dependencies.
|
|
212
|
-
* Falls back to manifest order for ties to preserve stability.
|
|
213
|
-
*/
|
|
214
|
-
export function getOrderedPackages(packages: LoadedPackage[]) {
|
|
215
|
-
if (packages.length <= 1) return [...packages]
|
|
216
|
-
|
|
217
|
-
// Build lookup by package name
|
|
218
|
-
const byName = new Map<string, LoadedPackage>()
|
|
219
|
-
const byDir = new Map<string, LoadedPackage>()
|
|
220
|
-
for (const p of packages) {
|
|
221
|
-
if (p.name) byName.set(p.name, p)
|
|
222
|
-
byDir.set(p.dirName, p)
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// Build adjacency list: edge dep -> pkg (dep must publish first)
|
|
226
|
-
const depsOf = (p: LoadedPackage) => {
|
|
227
|
-
const j = p.json ?? {}
|
|
228
|
-
const fields = [
|
|
229
|
-
'dependencies',
|
|
230
|
-
'devDependencies',
|
|
231
|
-
'peerDependencies',
|
|
232
|
-
] as const
|
|
233
|
-
const names = new Set<string>()
|
|
234
|
-
for (const f of fields) {
|
|
235
|
-
const obj = j[f] as Record<string, string> | undefined
|
|
236
|
-
if (!obj) continue
|
|
237
|
-
for (const k of Object.keys(obj)) names.add(k)
|
|
238
|
-
}
|
|
239
|
-
return [...names]
|
|
240
|
-
.map((n) => byName.get(n))
|
|
241
|
-
.filter((x): x is LoadedPackage => Boolean(x))
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
const nodes = new Set(packages.map((p) => p.dirName))
|
|
245
|
-
const inDegree = new Map<string, number>()
|
|
246
|
-
const adj = new Map<string, Set<string>>()
|
|
247
|
-
for (const p of packages) {
|
|
248
|
-
inDegree.set(p.dirName, 0)
|
|
249
|
-
adj.set(p.dirName, new Set())
|
|
250
|
-
}
|
|
251
|
-
for (const p of packages) {
|
|
252
|
-
for (const dep of depsOf(p)) {
|
|
253
|
-
// dep -> p
|
|
254
|
-
if (!nodes.has(dep.dirName)) continue
|
|
255
|
-
if (dep.dirName === p.dirName) continue
|
|
256
|
-
const set = adj.get(dep.dirName)!
|
|
257
|
-
if (!set.has(p.dirName)) {
|
|
258
|
-
set.add(p.dirName)
|
|
259
|
-
inDegree.set(p.dirName, (inDegree.get(p.dirName) ?? 0) + 1)
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Kahn's algorithm with stable tie-break using manifest order then alpha
|
|
265
|
-
const orderedFiles = manifestState?.orderedPackagePaths ?? []
|
|
266
|
-
const priorityIndex = new Map<string, number>()
|
|
267
|
-
orderedFiles.forEach((file, i) => priorityIndex.set(file, i))
|
|
268
|
-
const pickOrder = (a: string, b: string) => {
|
|
269
|
-
const pa = priorityIndex.get(a) ?? Number.MAX_SAFE_INTEGER
|
|
270
|
-
const pb = priorityIndex.get(b) ?? Number.MAX_SAFE_INTEGER
|
|
271
|
-
if (pa !== pb) return pa - pb
|
|
272
|
-
return a.localeCompare(b)
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
const queue = [
|
|
276
|
-
...[...nodes].filter((n) => (inDegree.get(n) ?? 0) === 0),
|
|
277
|
-
].sort(pickOrder)
|
|
278
|
-
const result: string[] = []
|
|
279
|
-
while (queue.length) {
|
|
280
|
-
const n = queue.shift()!
|
|
281
|
-
result.push(n)
|
|
282
|
-
for (const m of adj.get(n)!) {
|
|
283
|
-
inDegree.set(m, (inDegree.get(m) ?? 0) - 1)
|
|
284
|
-
if ((inDegree.get(m) ?? 0) === 0) {
|
|
285
|
-
// insert keeping order
|
|
286
|
-
const idx = queue.findIndex((x) => pickOrder(m, x) < 0)
|
|
287
|
-
if (idx === -1) queue.push(m)
|
|
288
|
-
else queue.splice(idx, 0, m)
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// If cycle, append remaining in original manifest order
|
|
294
|
-
const remaining = [...nodes]
|
|
295
|
-
.filter((n) => !result.includes(n))
|
|
296
|
-
.sort(pickOrder)
|
|
297
|
-
const orderedDirNames = [...result, ...remaining]
|
|
298
|
-
const ordered = orderedDirNames
|
|
299
|
-
.map((d) => byDir.get(d))
|
|
300
|
-
.filter((p): p is LoadedPackage => Boolean(p))
|
|
301
|
-
|
|
302
|
-
// Keep any not in the set at the end (shouldn't happen)
|
|
303
|
-
const tail = packages.filter((p) => !ordered.includes(p))
|
|
304
|
-
return [...ordered, ...tail]
|
|
305
|
-
}
|
package/src/preflight.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
// src/preflight.js
|
|
2
|
-
import { collectGitStatus } from './git.js'
|
|
3
|
-
|
|
4
|
-
import { colors, logGlobal } from './utils/log.js'
|
|
5
|
-
import { run } from './utils/run.js'
|
|
6
|
-
import { gitAdd, gitCommit } from './git.js'
|
|
7
|
-
import { askLine } from './prompts.js'
|
|
8
|
-
|
|
9
|
-
export async function ensureWorkingTreeCommitted() {
|
|
10
|
-
const changes = await collectGitStatus()
|
|
11
|
-
if (changes.length === 0) return
|
|
12
|
-
logGlobal('Detected pending git changes:', colors.yellow)
|
|
13
|
-
changes.forEach((line) => console.log(colors.dim(` • ${line}`)))
|
|
14
|
-
let message = ''
|
|
15
|
-
while (!message) {
|
|
16
|
-
// eslint-disable-next-line no-await-in-loop
|
|
17
|
-
message = await askLine('Enter commit message to capture current changes: ')
|
|
18
|
-
if (!message) console.log(colors.red('Commit message cannot be empty.'))
|
|
19
|
-
}
|
|
20
|
-
logGlobal('Staging existing changes…', colors.cyan)
|
|
21
|
-
await run('git', ['add', '--all'])
|
|
22
|
-
logGlobal('Creating commit…', colors.cyan)
|
|
23
|
-
await gitCommit(message)
|
|
24
|
-
logGlobal('Pushing commit…', colors.cyan)
|
|
25
|
-
await run('git', ['push'])
|
|
26
|
-
}
|
package/src/prompts.ts
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
// src/prompts.js
|
|
2
|
-
import readline from 'node:readline/promises'
|
|
3
|
-
import { stdin as input, stdout as output } from 'node:process'
|
|
4
|
-
import { colors } from './utils/log.js'
|
|
5
|
-
|
|
6
|
-
export type YesNoAll = 'yes' | 'no' | 'all'
|
|
7
|
-
|
|
8
|
-
export const publishCliState = { autoConfirmAll: false }
|
|
9
|
-
|
|
10
|
-
export function promptSingleKey<T>(
|
|
11
|
-
message: string,
|
|
12
|
-
resolver: (key: string, raw: string) => T | undefined,
|
|
13
|
-
) {
|
|
14
|
-
const supportsRawMode = typeof input.setRawMode === 'function' && input.isTTY
|
|
15
|
-
if (!supportsRawMode) {
|
|
16
|
-
return (async () => {
|
|
17
|
-
const rl = readline.createInterface({ input, output })
|
|
18
|
-
try {
|
|
19
|
-
// eslint-disable-next-line no-constant-condition
|
|
20
|
-
while (true) {
|
|
21
|
-
const answer = (await rl.question(message)).trim()
|
|
22
|
-
const key = answer.toLowerCase()
|
|
23
|
-
const result = resolver(key, answer)
|
|
24
|
-
if (result !== undefined) return result
|
|
25
|
-
}
|
|
26
|
-
} finally {
|
|
27
|
-
rl.close()
|
|
28
|
-
}
|
|
29
|
-
})()
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return new Promise<T>((resolve) => {
|
|
33
|
-
const wasRaw = input.isRaw
|
|
34
|
-
if (!wasRaw) {
|
|
35
|
-
input.setRawMode(true)
|
|
36
|
-
input.resume()
|
|
37
|
-
}
|
|
38
|
-
process.stdout.write(message)
|
|
39
|
-
const onData = (buffer: Buffer) => {
|
|
40
|
-
const str = buffer.toString()
|
|
41
|
-
if (str === '\u0003') {
|
|
42
|
-
process.stdout.write('\n')
|
|
43
|
-
process.exit(1)
|
|
44
|
-
}
|
|
45
|
-
const key = str.toLowerCase()
|
|
46
|
-
const result = resolver(key, str)
|
|
47
|
-
if (result !== undefined) {
|
|
48
|
-
process.stdout.write('\n')
|
|
49
|
-
cleanup()
|
|
50
|
-
resolve(result)
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
const cleanup = () => {
|
|
54
|
-
input.off('data', onData)
|
|
55
|
-
if (!wasRaw) {
|
|
56
|
-
input.setRawMode(false)
|
|
57
|
-
input.pause()
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
input.on('data', onData)
|
|
61
|
-
})
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export async function askLine(question: string) {
|
|
65
|
-
const rl = readline.createInterface({ input, output })
|
|
66
|
-
try {
|
|
67
|
-
const answer = await rl.question(question)
|
|
68
|
-
return answer.trim()
|
|
69
|
-
} finally {
|
|
70
|
-
rl.close()
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export async function promptYesNoAll(question: string): Promise<YesNoAll> {
|
|
75
|
-
if (publishCliState.autoConfirmAll) {
|
|
76
|
-
console.log(`${question} (auto-confirmed via "all")`)
|
|
77
|
-
return 'yes'
|
|
78
|
-
}
|
|
79
|
-
const result = await promptSingleKey<YesNoAll>(
|
|
80
|
-
`${question} (y/n/a): `,
|
|
81
|
-
(key) => {
|
|
82
|
-
if (key === 'y') return 'yes'
|
|
83
|
-
if (key === 'n') return 'no'
|
|
84
|
-
if (key === 'a') return 'all'
|
|
85
|
-
return undefined
|
|
86
|
-
},
|
|
87
|
-
)
|
|
88
|
-
if (result === 'all') {
|
|
89
|
-
publishCliState.autoConfirmAll = true
|
|
90
|
-
return 'yes'
|
|
91
|
-
}
|
|
92
|
-
return result
|
|
93
|
-
}
|
package/src/publish.ts
DELETED
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
// src/publish.js
|
|
2
|
-
import { runHelperCli } from './helper-cli.js'
|
|
3
|
-
import { buildPackageSelectionMenu, runStepLoop, type StepKey } from './menu.js'
|
|
4
|
-
import { getOrderedPackages, loadPackages, resolvePackage } from './packages.js'
|
|
5
|
-
import {
|
|
6
|
-
releaseMultiple,
|
|
7
|
-
releaseSingle,
|
|
8
|
-
type PublishOptions,
|
|
9
|
-
} from './release.js'
|
|
10
|
-
import { ensureWorkingTreeCommitted } from './preflight.js'
|
|
11
|
-
import { publishCliState } from './prompts.js'
|
|
12
|
-
|
|
13
|
-
type Parsed = {
|
|
14
|
-
selectionArg?: string
|
|
15
|
-
helperArgs: string[]
|
|
16
|
-
// non-interactive publish options
|
|
17
|
-
nonInteractive: boolean
|
|
18
|
-
bumpType?: 'patch' | 'minor' | 'major'
|
|
19
|
-
syncVersion?: string
|
|
20
|
-
tag?: string
|
|
21
|
-
dryRun?: boolean
|
|
22
|
-
provenance?: boolean
|
|
23
|
-
noop?: boolean
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function resolveTargetsFromArg(
|
|
27
|
-
packages: Awaited<ReturnType<typeof loadPackages>>,
|
|
28
|
-
arg: string,
|
|
29
|
-
) {
|
|
30
|
-
if (arg.toLowerCase() === 'all') return getOrderedPackages(packages)
|
|
31
|
-
const pkg = resolvePackage(packages, arg)
|
|
32
|
-
if (!pkg) throw new Error(`Package "${arg}" not found.`)
|
|
33
|
-
return [pkg]
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function parseCliArgs(args: string[]): Parsed {
|
|
37
|
-
let selectionArg: string | undefined
|
|
38
|
-
const helperArgs: string[] = []
|
|
39
|
-
|
|
40
|
-
let nonInteractive = false
|
|
41
|
-
let bumpType: Parsed['bumpType']
|
|
42
|
-
let syncVersion: string | undefined
|
|
43
|
-
let tag: string | undefined
|
|
44
|
-
let dryRun = false
|
|
45
|
-
let provenance = false
|
|
46
|
-
let noop = false
|
|
47
|
-
|
|
48
|
-
for (let i = 0; i < args.length; i++) {
|
|
49
|
-
const arg = args[i]
|
|
50
|
-
|
|
51
|
-
// selectionArg is the first non-flag token
|
|
52
|
-
if (!selectionArg && !arg.startsWith('-')) {
|
|
53
|
-
selectionArg = arg
|
|
54
|
-
helperArgs.push(arg)
|
|
55
|
-
continue
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (
|
|
59
|
-
arg === '--yes' ||
|
|
60
|
-
arg === '-y' ||
|
|
61
|
-
arg === '--non-interactive' ||
|
|
62
|
-
arg === '--ci'
|
|
63
|
-
) {
|
|
64
|
-
nonInteractive = true
|
|
65
|
-
continue
|
|
66
|
-
}
|
|
67
|
-
if (arg === '--bump') {
|
|
68
|
-
bumpType = args[++i] as Parsed['bumpType']
|
|
69
|
-
continue
|
|
70
|
-
}
|
|
71
|
-
if (arg === '--sync') {
|
|
72
|
-
syncVersion = args[++i]
|
|
73
|
-
continue
|
|
74
|
-
}
|
|
75
|
-
if (arg === '--tag') {
|
|
76
|
-
tag = args[++i]
|
|
77
|
-
continue
|
|
78
|
-
}
|
|
79
|
-
if (arg === '--dry-run') {
|
|
80
|
-
dryRun = true
|
|
81
|
-
continue
|
|
82
|
-
}
|
|
83
|
-
if (arg === '--provenance') {
|
|
84
|
-
provenance = true
|
|
85
|
-
continue
|
|
86
|
-
}
|
|
87
|
-
if (arg === '--noop') {
|
|
88
|
-
noop = true
|
|
89
|
-
continue
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// pass-through for helper CLI
|
|
93
|
-
helperArgs.push(arg)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return {
|
|
97
|
-
selectionArg,
|
|
98
|
-
helperArgs,
|
|
99
|
-
nonInteractive,
|
|
100
|
-
bumpType,
|
|
101
|
-
syncVersion,
|
|
102
|
-
tag,
|
|
103
|
-
dryRun,
|
|
104
|
-
provenance,
|
|
105
|
-
noop,
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function optsFromParsed(p: Parsed): PublishOptions {
|
|
110
|
-
return {
|
|
111
|
-
nonInteractive: p.nonInteractive,
|
|
112
|
-
bumpType: p.bumpType,
|
|
113
|
-
syncVersion: p.syncVersion,
|
|
114
|
-
tag: p.tag,
|
|
115
|
-
dryRun: p.dryRun,
|
|
116
|
-
provenance: p.provenance,
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
async function runPackageSelectionLoop(
|
|
121
|
-
packages: Awaited<ReturnType<typeof loadPackages>>,
|
|
122
|
-
helperArgs: string[],
|
|
123
|
-
) {
|
|
124
|
-
let argv = [...helperArgs]
|
|
125
|
-
// eslint-disable-next-line no-constant-condition
|
|
126
|
-
while (true) {
|
|
127
|
-
let lastStep: StepKey | undefined
|
|
128
|
-
await runHelperCli({
|
|
129
|
-
title: 'Pick one of the packages or all',
|
|
130
|
-
scripts: buildPackageSelectionMenu(packages, (step) => {
|
|
131
|
-
lastStep = step
|
|
132
|
-
}),
|
|
133
|
-
argv, // pass through CLI args only once; subsequent loops rely on selection
|
|
134
|
-
})
|
|
135
|
-
argv = []
|
|
136
|
-
if (lastStep !== 'back') return
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
async function main(): Promise<void> {
|
|
141
|
-
const cliArgs = process.argv.slice(2)
|
|
142
|
-
const parsed = parseCliArgs(cliArgs)
|
|
143
|
-
const packages = await loadPackages()
|
|
144
|
-
if (packages.length === 0) throw new Error('No packages found in ./packages')
|
|
145
|
-
|
|
146
|
-
// If user provided non-interactive flags, run headless path
|
|
147
|
-
if (parsed.nonInteractive) {
|
|
148
|
-
publishCliState.autoConfirmAll = true
|
|
149
|
-
|
|
150
|
-
if (!parsed.selectionArg) {
|
|
151
|
-
throw new Error(
|
|
152
|
-
'Non-interactive mode requires a package selection: <pkg> or "all".',
|
|
153
|
-
)
|
|
154
|
-
}
|
|
155
|
-
if (!parsed.bumpType && !parsed.syncVersion && !parsed.noop) {
|
|
156
|
-
throw new Error(
|
|
157
|
-
'Non-interactive mode requires one of: --bump <type> | --sync <version> | --noop',
|
|
158
|
-
)
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const targets = resolveTargetsFromArg(packages, parsed.selectionArg)
|
|
162
|
-
const opts = optsFromParsed(parsed)
|
|
163
|
-
|
|
164
|
-
await ensureWorkingTreeCommitted()
|
|
165
|
-
if (targets.length > 1) await releaseMultiple(targets, packages, opts)
|
|
166
|
-
else await releaseSingle(targets[0], packages, opts)
|
|
167
|
-
return
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Interactive flow (unchanged): selection menu then step menu
|
|
171
|
-
if (parsed.selectionArg) {
|
|
172
|
-
const targets = resolveTargetsFromArg(packages, parsed.selectionArg)
|
|
173
|
-
await runStepLoop(targets, packages)
|
|
174
|
-
return
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
await runPackageSelectionLoop(packages, parsed.helperArgs)
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
main().catch((error) => {
|
|
181
|
-
console.error(error)
|
|
182
|
-
process.exit(1)
|
|
183
|
-
})
|