@guiho/mirror 3.0.0-alpha.4 → 3.0.0-alpha.9
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/jsr.json +1 -1
- package/library/cli.d.ts +4 -0
- package/library/cli.d.ts.map +1 -1
- package/library/cli.js +58 -6
- package/library/flags.d.ts.map +1 -1
- package/library/flags.js +2 -1
- package/library/guiho-mirror-bin.js +0 -0
- package/library/types.d.ts +1 -0
- package/library/types.d.ts.map +1 -1
- package/package.json +26 -28
- package/bin/mirror.exe +0 -0
- package/source/adapters.ts +0 -176
- package/source/cli.ts +0 -285
- package/source/config.ts +0 -224
- package/source/errors.ts +0 -17
- package/source/executor.ts +0 -39
- package/source/flags.ts +0 -84
- package/source/guiho-mirror-bin.ts +0 -8
- package/source/guiho-mirror.spec.ts +0 -501
- package/source/guiho-mirror.ts +0 -44
- package/source/plan.ts +0 -98
- package/source/reporter.ts +0 -127
- package/source/types.ts +0 -128
- package/source/version.ts +0 -39
package/source/cli.ts
DELETED
|
@@ -1,285 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @copyright Copyright (c) 2026 GUIHO Technologies as represented by Cristóvão GUIHO. All Rights Reserved.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { defineCommand, runMain } from 'citty'
|
|
6
|
-
import type { ArgsDef } from 'citty'
|
|
7
|
-
import { readFileSync } from 'node:fs'
|
|
8
|
-
import { resolve } from 'node:path'
|
|
9
|
-
import { MirrorError } from './errors'
|
|
10
|
-
import { readCurrentVersion, resolveProjectName } from './adapters'
|
|
11
|
-
import { configPathForDisplay, discoverMirrorConfig, loadMirrorConfig, relativeFromCwd, writeInitConfig } from './config'
|
|
12
|
-
import { executeVersionPlan } from './executor'
|
|
13
|
-
import { parseMirrorCliOptions } from './flags'
|
|
14
|
-
import { buildVersionPlan, validateMirrorConfig } from './plan'
|
|
15
|
-
import { mirrorBanner, reportConfig, reportConfigSchema, reportExecution, reportExecutionSummary, reportPlan, reportValue } from './reporter'
|
|
16
|
-
import type { MirrorAdapterName, MirrorCliOptions } from './types'
|
|
17
|
-
import { resolveNextVersion } from './version'
|
|
18
|
-
|
|
19
|
-
const mirrorVersion = readInstalledVersion()
|
|
20
|
-
|
|
21
|
-
const globalArgs = {
|
|
22
|
-
config: { type: 'string', description: 'Path to mirror.config.toml' },
|
|
23
|
-
cwd: { type: 'string', description: 'Run as if Mirror started in this directory' },
|
|
24
|
-
format: { type: 'enum', options: ['text', 'json'], default: 'text', description: 'Output format' },
|
|
25
|
-
'no-color': { type: 'boolean', description: 'Disable color output' },
|
|
26
|
-
} satisfies ArgsDef
|
|
27
|
-
|
|
28
|
-
const overrideArgs = {
|
|
29
|
-
...globalArgs,
|
|
30
|
-
source: { type: 'enum', options: ['package.json', 'jsr.json', 'git'], description: 'Override version source' },
|
|
31
|
-
output: { type: 'string', description: 'Override version output. Repeat or comma-separate values.' },
|
|
32
|
-
'package-file': { type: 'string', description: 'Override package.json path' },
|
|
33
|
-
'jsr-file': { type: 'string', description: 'Override jsr.json path' },
|
|
34
|
-
preid: { type: 'string', description: 'Override prerelease identifier' },
|
|
35
|
-
} satisfies ArgsDef
|
|
36
|
-
|
|
37
|
-
const applyArgs = {
|
|
38
|
-
...overrideArgs,
|
|
39
|
-
'dry-run': { type: 'boolean', alias: 'dy', description: 'Build and print the plan without applying it' },
|
|
40
|
-
commit: { type: 'boolean', description: 'Create a release commit when file outputs changed' },
|
|
41
|
-
push: { type: 'boolean', description: 'Create the release commit when needed, then push release refs' },
|
|
42
|
-
'allow-dirty': { type: 'boolean', description: 'Allow release in a dirty Git worktree' },
|
|
43
|
-
yes: { type: 'boolean', alias: 'y', description: 'Apply without interactive confirmation' },
|
|
44
|
-
} satisfies ArgsDef
|
|
45
|
-
|
|
46
|
-
const targetArg = {
|
|
47
|
-
target: { type: 'positional', description: 'Release target or exact semantic version', required: true },
|
|
48
|
-
} satisfies ArgsDef
|
|
49
|
-
|
|
50
|
-
export const createMirrorCommand = () =>
|
|
51
|
-
defineCommand({
|
|
52
|
-
meta: {
|
|
53
|
-
name: 'mirror',
|
|
54
|
-
version: mirrorVersion,
|
|
55
|
-
description: 'Open source project versioning for Bun, npm, JSR, and Git.',
|
|
56
|
-
},
|
|
57
|
-
args: globalArgs,
|
|
58
|
-
subCommands: {
|
|
59
|
-
init: createInitCommand(),
|
|
60
|
-
config: createConfigCommand(),
|
|
61
|
-
version: createVersionCommand(),
|
|
62
|
-
},
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
export const runMirrorCli = async (rawArgs = process.argv.slice(2)) => {
|
|
66
|
-
const effectiveArgs = rawArgs.length === 0 ? ['--help'] : rawArgs
|
|
67
|
-
const restoreColorOutput = effectiveArgs.includes('--no-color') ? stripColorFromProcessOutput() : () => {}
|
|
68
|
-
|
|
69
|
-
try {
|
|
70
|
-
if (effectiveArgs.includes('--no-color')) process.env['NO_COLOR'] = '1'
|
|
71
|
-
|
|
72
|
-
if (effectiveArgs.includes('--help')) {
|
|
73
|
-
const parsed = parseMirrorCliOptions(effectiveArgs)
|
|
74
|
-
const cwd = resolve(parsed.cwd ?? process.cwd())
|
|
75
|
-
const discovery = await discoverMirrorConfig(cwd, parsed.config)
|
|
76
|
-
const configDisplay = discovery.path ? relativeFromCwd(cwd, discovery.path) : ''
|
|
77
|
-
process.stdout.write(mirrorBanner(configDisplay))
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (rawArgs.length === 0) {
|
|
81
|
-
process.on('exit', () => {
|
|
82
|
-
process.stdout.write([
|
|
83
|
-
'EXAMPLES',
|
|
84
|
-
'',
|
|
85
|
-
' mirror version current # Print the current version',
|
|
86
|
-
' mirror version plan patch # Preview a patch release plan',
|
|
87
|
-
' mirror version apply minor --commit # Apply a minor release with commit',
|
|
88
|
-
' mirror version plan patch --output=package.json,jsr.json,git # Plan with package, jsr, and git',
|
|
89
|
-
' mirror config schema # Print the configuration file reference',
|
|
90
|
-
'',
|
|
91
|
-
].join('\n') + '\n')
|
|
92
|
-
})
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
await runMain(createMirrorCommand(), { rawArgs: effectiveArgs })
|
|
96
|
-
} catch (error) {
|
|
97
|
-
if (error instanceof MirrorError) {
|
|
98
|
-
console.error(error.message)
|
|
99
|
-
process.exit(error.exitCode)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
throw error
|
|
103
|
-
} finally {
|
|
104
|
-
restoreColorOutput()
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const createInitCommand = () =>
|
|
109
|
-
defineCommand({
|
|
110
|
-
meta: { name: 'init', description: 'Create a Mirror configuration file.' },
|
|
111
|
-
subCommands: {
|
|
112
|
-
'package.json': createInitKindCommand('package.json'),
|
|
113
|
-
'jsr.json': createInitKindCommand('jsr.json'),
|
|
114
|
-
git: createInitKindCommand('git'),
|
|
115
|
-
},
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
const createInitKindCommand = (kind: MirrorAdapterName) =>
|
|
119
|
-
defineCommand({
|
|
120
|
-
meta: { name: kind, description: `Create ${kind} project configuration.` },
|
|
121
|
-
args: {
|
|
122
|
-
...globalArgs,
|
|
123
|
-
yes: { type: 'boolean', description: 'Overwrite existing mirror.config.toml' },
|
|
124
|
-
},
|
|
125
|
-
async run(context) {
|
|
126
|
-
const options = cliOptions(context.rawArgs, context.args)
|
|
127
|
-
const path = await writeInitConfig(kind, options.cwd ?? process.cwd(), Boolean(options.yes))
|
|
128
|
-
process.stdout.write(reportValue(`created ${path}`, options.format))
|
|
129
|
-
},
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
const createConfigCommand = () =>
|
|
133
|
-
defineCommand({
|
|
134
|
-
meta: { name: 'config', description: 'Inspect and validate Mirror configuration.' },
|
|
135
|
-
subCommands: {
|
|
136
|
-
show: defineCommand({
|
|
137
|
-
meta: { name: 'show', description: 'Print the resolved configuration.' },
|
|
138
|
-
args: overrideArgs,
|
|
139
|
-
async run(context) {
|
|
140
|
-
const options = cliOptions(context.rawArgs, context.args)
|
|
141
|
-
const config = await loadMirrorConfig(options)
|
|
142
|
-
if (options.format !== 'json') process.stdout.write(mirrorBanner(configPathForDisplay(config)))
|
|
143
|
-
process.stdout.write(reportConfig(config, options.format))
|
|
144
|
-
},
|
|
145
|
-
}),
|
|
146
|
-
check: defineCommand({
|
|
147
|
-
meta: { name: 'check', description: 'Validate the resolved configuration.' },
|
|
148
|
-
args: overrideArgs,
|
|
149
|
-
async run(context) {
|
|
150
|
-
const options = cliOptions(context.rawArgs, context.args)
|
|
151
|
-
await validateMirrorConfig(options)
|
|
152
|
-
process.stdout.write(reportValue('ok', options.format))
|
|
153
|
-
},
|
|
154
|
-
}),
|
|
155
|
-
schema: defineCommand({
|
|
156
|
-
meta: { name: 'schema', description: 'Print the configuration file reference.' },
|
|
157
|
-
args: globalArgs,
|
|
158
|
-
run(context) {
|
|
159
|
-
const options = cliOptions(context.rawArgs, context.args)
|
|
160
|
-
if (options.format !== 'json') process.stdout.write(mirrorBanner())
|
|
161
|
-
process.stdout.write(reportConfigSchema(options.format))
|
|
162
|
-
},
|
|
163
|
-
}),
|
|
164
|
-
},
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
const createVersionCommand = () =>
|
|
168
|
-
defineCommand({
|
|
169
|
-
meta: { name: 'version', description: 'Read, plan, and apply version changes.' },
|
|
170
|
-
subCommands: {
|
|
171
|
-
current: defineCommand({
|
|
172
|
-
meta: { name: 'current', description: 'Print the current project version.' },
|
|
173
|
-
args: overrideArgs,
|
|
174
|
-
async run(context) {
|
|
175
|
-
const options = cliOptions(context.rawArgs, context.args)
|
|
176
|
-
const config = await loadMirrorConfig(options)
|
|
177
|
-
const projectName = await resolveProjectName(config)
|
|
178
|
-
process.stdout.write(reportValue(await readCurrentVersion(config, projectName), options.format))
|
|
179
|
-
},
|
|
180
|
-
}),
|
|
181
|
-
next: defineCommand({
|
|
182
|
-
meta: { name: 'next', description: 'Print the next version without checking outputs.' },
|
|
183
|
-
args: { ...overrideArgs, ...targetArg },
|
|
184
|
-
async run(context) {
|
|
185
|
-
const options = cliOptions(context.rawArgs, context.args)
|
|
186
|
-
const config = await loadMirrorConfig(options)
|
|
187
|
-
const projectName = await resolveProjectName(config)
|
|
188
|
-
const currentVersion = await readCurrentVersion(config, projectName)
|
|
189
|
-
process.stdout.write(reportValue(resolveNextVersion(currentVersion, String(context.args['target']), config.version.prereleaseId), options.format))
|
|
190
|
-
},
|
|
191
|
-
}),
|
|
192
|
-
plan: defineCommand({
|
|
193
|
-
meta: { name: 'plan', description: 'Print the release plan without writing anything.' },
|
|
194
|
-
args: { ...overrideArgs, ...targetArg },
|
|
195
|
-
async run(context) {
|
|
196
|
-
const options = cliOptions(context.rawArgs, context.args)
|
|
197
|
-
const plan = await buildVersionPlan(String(context.args['target']), options)
|
|
198
|
-
if (options.format !== 'json') process.stdout.write(mirrorBanner(plan.configPath ? plan.configPath : ''))
|
|
199
|
-
process.stdout.write(reportPlan(plan, options.format))
|
|
200
|
-
},
|
|
201
|
-
}),
|
|
202
|
-
apply: defineCommand({
|
|
203
|
-
meta: { name: 'apply', description: 'Apply the release plan.' },
|
|
204
|
-
args: { ...applyArgs, ...targetArg },
|
|
205
|
-
async run(context) {
|
|
206
|
-
const options = cliOptions(context.rawArgs, context.args)
|
|
207
|
-
const plan = await buildVersionPlan(String(context.args['target']), options)
|
|
208
|
-
|
|
209
|
-
if (options.format !== 'json') process.stdout.write(mirrorBanner(plan.configPath ? plan.configPath : ''))
|
|
210
|
-
if (options.format !== 'json') process.stdout.write(reportPlan(plan, options.format))
|
|
211
|
-
|
|
212
|
-
const result = await executeVersionPlan(plan, options)
|
|
213
|
-
process.stdout.write(options.format === 'json' ? reportExecution(result, options.format) : reportExecutionSummary(result, options.format))
|
|
214
|
-
},
|
|
215
|
-
}),
|
|
216
|
-
},
|
|
217
|
-
})
|
|
218
|
-
|
|
219
|
-
const cliOptions = (rawArgs: string[], args: Record<string, unknown>): MirrorCliOptions => {
|
|
220
|
-
const parsed = parseMirrorCliOptions(rawArgs)
|
|
221
|
-
|
|
222
|
-
return {
|
|
223
|
-
...parsed,
|
|
224
|
-
config: parsed.config ?? stringArg(args['config']),
|
|
225
|
-
cwd: parsed.cwd ?? stringArg(args['cwd']),
|
|
226
|
-
format: parsed.format ?? (args['format'] === 'json' ? 'json' : 'text'),
|
|
227
|
-
source: parsed.source ?? adapterArg(args['source']),
|
|
228
|
-
output: parsed.output ?? outputArg(args['output']),
|
|
229
|
-
packageFile: parsed.packageFile ?? stringArg(args['packageFile']),
|
|
230
|
-
jsrFile: parsed.jsrFile ?? stringArg(args['jsrFile']),
|
|
231
|
-
preid: parsed.preid ?? stringArg(args['preid']),
|
|
232
|
-
dryRun: parsed.dryRun || args['dryRun'] === true,
|
|
233
|
-
commit: parsed.commit || args['commit'] === true,
|
|
234
|
-
push: parsed.push || args['push'] === true,
|
|
235
|
-
allowDirty: parsed.allowDirty || args['allowDirty'] === true,
|
|
236
|
-
yes: parsed.yes || args['yes'] === true,
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const stringArg = (value: unknown) => (typeof value === 'string' ? value : undefined)
|
|
241
|
-
|
|
242
|
-
const adapterArg = (value: unknown) => {
|
|
243
|
-
if (value === 'package.json' || value === 'jsr.json' || value === 'git') return value
|
|
244
|
-
return undefined
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
const outputArg = (value: unknown): MirrorCliOptions['output'] => {
|
|
248
|
-
if (typeof value !== 'string') return undefined
|
|
249
|
-
|
|
250
|
-
const values = value.split(',').map((item) => item.trim()).filter(Boolean)
|
|
251
|
-
|
|
252
|
-
if (values.length === 0) return undefined
|
|
253
|
-
|
|
254
|
-
return values.map((item) => {
|
|
255
|
-
const adapter = adapterArg(item)
|
|
256
|
-
if (!adapter) throw new MirrorError(`Invalid --output value: ${item}`)
|
|
257
|
-
return adapter
|
|
258
|
-
})
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function readInstalledVersion() {
|
|
262
|
-
try {
|
|
263
|
-
const packageJson = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')) as Record<string, unknown>
|
|
264
|
-
return typeof packageJson['version'] === 'string' ? packageJson['version'] : '0.0.0'
|
|
265
|
-
} catch {
|
|
266
|
-
return '0.0.0'
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
const stripAnsi = (value: string) => value.replace(/\u001B\[[0-?]*[ -/]*[@-~]/g, '')
|
|
271
|
-
|
|
272
|
-
const stripColorFromProcessOutput = () => {
|
|
273
|
-
const originalLog = console.log
|
|
274
|
-
const originalError = console.error
|
|
275
|
-
|
|
276
|
-
console.log = (...values: unknown[]) => originalLog(...values.map(stripAnsiValue))
|
|
277
|
-
console.error = (...values: unknown[]) => originalError(...values.map(stripAnsiValue))
|
|
278
|
-
|
|
279
|
-
return () => {
|
|
280
|
-
console.log = originalLog
|
|
281
|
-
console.error = originalError
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
const stripAnsiValue = (value: unknown) => (typeof value === 'string' ? stripAnsi(value) : value)
|
package/source/config.ts
DELETED
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @copyright Copyright (c) 2026 GUIHO Technologies as represented by Cristóvão GUIHO. All Rights Reserved.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { existsSync } from 'node:fs'
|
|
6
|
-
import { basename, isAbsolute, join, relative, resolve } from 'node:path'
|
|
7
|
-
import type {
|
|
8
|
-
MirrorAdapterName,
|
|
9
|
-
MirrorCliOptions,
|
|
10
|
-
MirrorConfig,
|
|
11
|
-
MirrorConfigDiscovery,
|
|
12
|
-
MirrorProjectNameSource,
|
|
13
|
-
MirrorRawConfig,
|
|
14
|
-
} from './types'
|
|
15
|
-
import { MirrorError } from './errors'
|
|
16
|
-
|
|
17
|
-
const adapters = new Set(['package.json', 'jsr.json', 'git'])
|
|
18
|
-
const projectNameSources = new Set(['package.json', 'jsr.json'])
|
|
19
|
-
|
|
20
|
-
export const resolveMirrorPath = (cwd: string, path: string) => (isAbsolute(path) ? path : resolve(cwd, path))
|
|
21
|
-
|
|
22
|
-
export const relativeFromCwd = (cwd: string, path: string) => {
|
|
23
|
-
const relativePath = relative(cwd, resolveMirrorPath(cwd, path))
|
|
24
|
-
return relativePath || '.'
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export const discoverMirrorConfig = async (cwd: string, explicitPath?: string): Promise<MirrorConfigDiscovery> => {
|
|
28
|
-
if (explicitPath) {
|
|
29
|
-
const configPath = resolveMirrorPath(cwd, explicitPath)
|
|
30
|
-
return { path: configPath, raw: await readConfigFile(configPath) }
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const rootConfigPath = resolve(cwd, 'mirror.config.toml')
|
|
34
|
-
if (existsSync(rootConfigPath)) return { path: rootConfigPath, raw: await readConfigFile(rootConfigPath) }
|
|
35
|
-
|
|
36
|
-
const nestedConfigPath = resolve(cwd, 'config', 'mirror.config.toml')
|
|
37
|
-
if (existsSync(nestedConfigPath)) return { path: nestedConfigPath, raw: await readConfigFile(nestedConfigPath) }
|
|
38
|
-
|
|
39
|
-
return {}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export const readConfigFile = async (path: string): Promise<MirrorRawConfig> => {
|
|
43
|
-
if (!existsSync(path)) throw new MirrorError(`Configuration file not found: ${path}`)
|
|
44
|
-
|
|
45
|
-
const parsed = Bun.TOML.parse(await Bun.file(path).text())
|
|
46
|
-
|
|
47
|
-
if (!isRecord(parsed)) throw new MirrorError(`Configuration file must contain a TOML object: ${path}`)
|
|
48
|
-
|
|
49
|
-
return parsed as MirrorRawConfig
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export const loadMirrorConfig = async (options: MirrorCliOptions = {}): Promise<MirrorConfig> => {
|
|
53
|
-
const cwd = resolve(options.cwd ?? process.cwd())
|
|
54
|
-
const discovered = await discoverMirrorConfig(cwd, options.config)
|
|
55
|
-
|
|
56
|
-
if (!discovered.raw) throw new MirrorError('Mirror configuration not found. Run `mirror init package`, `mirror init jsr`, or `mirror init git`.')
|
|
57
|
-
|
|
58
|
-
return normalizeMirrorConfig(discovered.raw, cwd, discovered.path, options)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export const normalizeMirrorConfig = (
|
|
62
|
-
raw: MirrorRawConfig,
|
|
63
|
-
cwd: string,
|
|
64
|
-
configPath: string | undefined,
|
|
65
|
-
options: MirrorCliOptions = {},
|
|
66
|
-
): MirrorConfig => {
|
|
67
|
-
if (raw.schema !== 1) throw new MirrorError('Unsupported or missing configuration schema. Expected `schema = 1`.')
|
|
68
|
-
if (raw.version?.scheme !== undefined && raw.version.scheme !== 'semver') throw new MirrorError('Only `version.scheme = "semver"` is supported.')
|
|
69
|
-
|
|
70
|
-
const source = options.source ?? assertAdapter(raw.version?.source, 'version.source')
|
|
71
|
-
const output = dedupeAdapters(options.output ?? assertOutput(raw.version?.output))
|
|
72
|
-
const nameSource: MirrorProjectNameSource | undefined = raw.project?.name_source
|
|
73
|
-
? assertProjectNameSource(raw.project.name_source, 'project.name_source')
|
|
74
|
-
: undefined
|
|
75
|
-
const projectName = optionalString(raw.project?.name, 'project.name')
|
|
76
|
-
const prereleaseId = options.preid ?? optionalString(raw.version?.prerelease_id, 'version.prerelease_id') ?? ''
|
|
77
|
-
const packagePath = options.packageFile ?? optionalString(raw.package?.path, 'package.path') ?? 'package.json'
|
|
78
|
-
const jsrPath = options.jsrFile ?? optionalString(raw.jsr?.path, 'jsr.path') ?? 'jsr.json'
|
|
79
|
-
const tagTemplate = optionalString(raw.git?.tag_template, 'git.tag_template') ?? 'v{version}'
|
|
80
|
-
const gitCommit = optionalBoolean(raw.git?.commit, 'git.commit') === true
|
|
81
|
-
const gitPush = optionalBoolean(raw.git?.push, 'git.push') === true
|
|
82
|
-
const gitAllowDirty = optionalBoolean(raw.git?.allow_dirty, 'git.allow_dirty') === true
|
|
83
|
-
|
|
84
|
-
return {
|
|
85
|
-
schema: 1,
|
|
86
|
-
cwd,
|
|
87
|
-
configPath,
|
|
88
|
-
project: {
|
|
89
|
-
name: projectName,
|
|
90
|
-
nameSource,
|
|
91
|
-
},
|
|
92
|
-
version: {
|
|
93
|
-
scheme: 'semver',
|
|
94
|
-
source,
|
|
95
|
-
output,
|
|
96
|
-
prereleaseId,
|
|
97
|
-
},
|
|
98
|
-
package: {
|
|
99
|
-
path: packagePath,
|
|
100
|
-
},
|
|
101
|
-
jsr: {
|
|
102
|
-
path: jsrPath,
|
|
103
|
-
},
|
|
104
|
-
git: {
|
|
105
|
-
tagTemplate,
|
|
106
|
-
commit: options.commit === true || options.push === true || gitCommit || gitPush,
|
|
107
|
-
push: options.push === true || gitPush,
|
|
108
|
-
allowDirty: options.allowDirty === true || gitAllowDirty,
|
|
109
|
-
},
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export const createInitConfig = (kind: MirrorAdapterName, cwd: string) => {
|
|
114
|
-
const projectName = basename(cwd)
|
|
115
|
-
|
|
116
|
-
if (kind === 'package.json') {
|
|
117
|
-
return `schema = 1
|
|
118
|
-
|
|
119
|
-
[project]
|
|
120
|
-
name_source = "package.json"
|
|
121
|
-
|
|
122
|
-
[version]
|
|
123
|
-
scheme = "semver"
|
|
124
|
-
source = "package.json"
|
|
125
|
-
output = ["package.json"]
|
|
126
|
-
prerelease_id = ""
|
|
127
|
-
|
|
128
|
-
[package]
|
|
129
|
-
path = "package.json"
|
|
130
|
-
|
|
131
|
-
[jsr]
|
|
132
|
-
path = "jsr.json"
|
|
133
|
-
|
|
134
|
-
[git]
|
|
135
|
-
tag_template = "{name}@{version}"
|
|
136
|
-
commit = false
|
|
137
|
-
push = false
|
|
138
|
-
allow_dirty = false
|
|
139
|
-
`
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (kind === 'jsr.json') {
|
|
143
|
-
return `schema = 1
|
|
144
|
-
|
|
145
|
-
[project]
|
|
146
|
-
name_source = "jsr.json"
|
|
147
|
-
|
|
148
|
-
[version]
|
|
149
|
-
scheme = "semver"
|
|
150
|
-
source = "jsr.json"
|
|
151
|
-
output = ["jsr.json"]
|
|
152
|
-
prerelease_id = ""
|
|
153
|
-
|
|
154
|
-
[jsr]
|
|
155
|
-
path = "jsr.json"
|
|
156
|
-
|
|
157
|
-
[git]
|
|
158
|
-
tag_template = "{name}@{version}"
|
|
159
|
-
commit = false
|
|
160
|
-
push = false
|
|
161
|
-
allow_dirty = false
|
|
162
|
-
`
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return `schema = 1
|
|
166
|
-
|
|
167
|
-
[project]
|
|
168
|
-
name = "${projectName}"
|
|
169
|
-
|
|
170
|
-
[version]
|
|
171
|
-
scheme = "semver"
|
|
172
|
-
source = "git"
|
|
173
|
-
output = ["git"]
|
|
174
|
-
prerelease_id = ""
|
|
175
|
-
|
|
176
|
-
[git]
|
|
177
|
-
tag_template = "v{version}"
|
|
178
|
-
commit = false
|
|
179
|
-
push = false
|
|
180
|
-
allow_dirty = false
|
|
181
|
-
`
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
export const writeInitConfig = async (kind: MirrorAdapterName, cwd: string, overwrite = false) => {
|
|
185
|
-
const path = join(cwd, 'mirror.config.toml')
|
|
186
|
-
|
|
187
|
-
if (existsSync(path) && !overwrite) throw new MirrorError(`Configuration already exists: ${path}`)
|
|
188
|
-
|
|
189
|
-
await Bun.write(path, createInitConfig(kind, cwd))
|
|
190
|
-
return path
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
export const configPathForDisplay = (config: MirrorConfig) => (config.configPath ? relativeFromCwd(config.cwd, config.configPath) : '(none)')
|
|
194
|
-
|
|
195
|
-
const assertAdapter = (value: unknown, key: string): MirrorAdapterName => {
|
|
196
|
-
if (typeof value !== 'string' || !adapters.has(value)) throw new MirrorError(`Invalid or missing ${key}. Expected package.json, jsr.json, or git.`)
|
|
197
|
-
return value as MirrorAdapterName
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const assertProjectNameSource = (value: unknown, key: string): MirrorProjectNameSource => {
|
|
201
|
-
if (typeof value !== 'string' || !projectNameSources.has(value)) throw new MirrorError(`Invalid ${key}. Expected package.json or jsr.json.`)
|
|
202
|
-
return value as MirrorProjectNameSource
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const assertOutput = (value: unknown): MirrorAdapterName[] => {
|
|
206
|
-
if (!Array.isArray(value) || value.length === 0) throw new MirrorError('Invalid or missing version.output. Expected at least one output adapter.')
|
|
207
|
-
return value.map((item) => assertAdapter(item, 'version.output'))
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const dedupeAdapters = (value: MirrorAdapterName[]) => [...new Set(value)]
|
|
211
|
-
|
|
212
|
-
const optionalString = (value: unknown, key: string) => {
|
|
213
|
-
if (value === undefined) return undefined
|
|
214
|
-
if (typeof value !== 'string') throw new MirrorError(`Invalid ${key}. Expected a string.`)
|
|
215
|
-
return value
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const optionalBoolean = (value: unknown, key: string) => {
|
|
219
|
-
if (value === undefined) return undefined
|
|
220
|
-
if (typeof value !== 'boolean') throw new MirrorError(`Invalid ${key}. Expected true or false.`)
|
|
221
|
-
return value
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === 'object' && value !== null && !Array.isArray(value)
|
package/source/errors.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @copyright Copyright (c) 2026 GUIHO Technologies as represented by Cristóvão GUIHO. All Rights Reserved.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export class MirrorError extends Error {
|
|
6
|
-
readonly exitCode: number
|
|
7
|
-
|
|
8
|
-
constructor(message: string, exitCode = 1) {
|
|
9
|
-
super(message)
|
|
10
|
-
this.name = 'MirrorError'
|
|
11
|
-
this.exitCode = exitCode
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const invariant = (condition: unknown, message: string): asserts condition => {
|
|
16
|
-
if (!condition) throw new MirrorError(message)
|
|
17
|
-
}
|
package/source/executor.ts
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @copyright Copyright (c) 2026 GUIHO Technologies as represented by Cristóvão GUIHO. All Rights Reserved.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { MirrorCliOptions, MirrorExecutionResult } from './types'
|
|
6
|
-
import { MirrorError } from './errors'
|
|
7
|
-
import { createGitCommit, createGitTag, isGitDirty, isGitRepository, pushGitRefs, writeJsrVersion, writePackageVersion } from './adapters'
|
|
8
|
-
import { loadMirrorConfig } from './config'
|
|
9
|
-
import { buildVersionPlan } from './plan'
|
|
10
|
-
|
|
11
|
-
export const applyVersionPlan = async (target: string, options: MirrorCliOptions = {}): Promise<MirrorExecutionResult> => {
|
|
12
|
-
const plan = await buildVersionPlan(target, options)
|
|
13
|
-
|
|
14
|
-
return executeVersionPlan(plan, options)
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export const executeVersionPlan = async (
|
|
18
|
-
plan: MirrorExecutionResult['plan'],
|
|
19
|
-
options: MirrorCliOptions = {},
|
|
20
|
-
): Promise<MirrorExecutionResult> => {
|
|
21
|
-
if (options.dryRun) return { plan, applied: false, dryRun: true }
|
|
22
|
-
if (!plan.allowDirty && (await isGitRepository(plan.cwd)) && (await isGitDirty(plan.cwd))) {
|
|
23
|
-
throw new MirrorError('Git worktree is dirty. Commit changes or pass --allow-dirty.')
|
|
24
|
-
}
|
|
25
|
-
if (!options.yes) throw new MirrorError('Refusing to apply without confirmation. Pass --yes to apply the plan.')
|
|
26
|
-
|
|
27
|
-
const config = await loadMirrorConfig(options)
|
|
28
|
-
|
|
29
|
-
if (config.version.output.includes('package.json')) await writePackageVersion(config, plan.nextVersion)
|
|
30
|
-
if (config.version.output.includes('jsr.json')) await writeJsrVersion(config, plan.nextVersion)
|
|
31
|
-
|
|
32
|
-
for (const action of plan.actions) {
|
|
33
|
-
if (action.type === 'git-commit') await createGitCommit(plan.cwd, action.paths, action.message)
|
|
34
|
-
if (action.type === 'git-tag') await createGitTag(plan.cwd, action.tag)
|
|
35
|
-
if (action.type === 'git-push') await pushGitRefs(plan.cwd, action.includeCommit, action.includeTags)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return { plan, applied: true, dryRun: false }
|
|
39
|
-
}
|
package/source/flags.ts
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @copyright Copyright (c) 2026 GUIHO Technologies as represented by Cristóvão GUIHO. All Rights Reserved.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { MirrorAdapterName, MirrorCliOptions, MirrorFormat } from './types'
|
|
6
|
-
import { MirrorError } from './errors'
|
|
7
|
-
|
|
8
|
-
const booleanFlags = new Set(['dry-run', 'commit', 'push', 'allow-dirty', 'yes', 'no-color', 'help', 'version'])
|
|
9
|
-
const adapterNames = new Set(['package.json', 'jsr.json', 'git'])
|
|
10
|
-
|
|
11
|
-
const shortFlagAliases: Record<string, string> = {
|
|
12
|
-
'-dy': '--dry-run',
|
|
13
|
-
'-y': '--yes',
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const normalizeKey = (key: string) => key.replace(/-([a-z])/g, (_match, letter: string) => letter.toUpperCase())
|
|
17
|
-
|
|
18
|
-
const expandShortFlags = (rawArgs: string[]) => rawArgs.map((token) => shortFlagAliases[token] ?? token)
|
|
19
|
-
|
|
20
|
-
export const parseMirrorCliOptions = (rawArgs: string[]): MirrorCliOptions => {
|
|
21
|
-
const parsed: Record<string, string | boolean | string[]> = {}
|
|
22
|
-
const args = expandShortFlags(rawArgs)
|
|
23
|
-
|
|
24
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
25
|
-
const token = args[index]
|
|
26
|
-
|
|
27
|
-
if (!token?.startsWith('--')) continue
|
|
28
|
-
|
|
29
|
-
const withoutPrefix = token.slice(2)
|
|
30
|
-
const equalsIndex = withoutPrefix.indexOf('=')
|
|
31
|
-
const rawKey = equalsIndex >= 0 ? withoutPrefix.slice(0, equalsIndex) : withoutPrefix
|
|
32
|
-
const key = normalizeKey(rawKey)
|
|
33
|
-
|
|
34
|
-
if (booleanFlags.has(rawKey)) {
|
|
35
|
-
parsed[key] = true
|
|
36
|
-
continue
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const value =
|
|
40
|
-
equalsIndex >= 0
|
|
41
|
-
? withoutPrefix.slice(equalsIndex + 1)
|
|
42
|
-
: args[index + 1] && !args[index + 1]?.startsWith('-')
|
|
43
|
-
? args[++index] ?? ''
|
|
44
|
-
: ''
|
|
45
|
-
|
|
46
|
-
if (!value) throw new MirrorError(`Missing value for --${rawKey}`)
|
|
47
|
-
|
|
48
|
-
if (key === 'output') {
|
|
49
|
-
const nextValues = value.split(',').map((item) => item.trim()).filter(Boolean)
|
|
50
|
-
const current = parsed['output']
|
|
51
|
-
parsed['output'] = [...(Array.isArray(current) ? current : current ? [String(current)] : []), ...nextValues]
|
|
52
|
-
continue
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
parsed[key] = value
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return {
|
|
59
|
-
cwd: typeof parsed['cwd'] === 'string' ? parsed['cwd'] : undefined,
|
|
60
|
-
config: typeof parsed['config'] === 'string' ? parsed['config'] : undefined,
|
|
61
|
-
format: typeof parsed['format'] === 'string' ? assertFormat(parsed['format']) : undefined,
|
|
62
|
-
noColor: parsed['noColor'] === true,
|
|
63
|
-
source: typeof parsed['source'] === 'string' ? assertAdapter(parsed['source'], '--source') : undefined,
|
|
64
|
-
output: Array.isArray(parsed['output']) ? parsed['output'].map((value) => assertAdapter(value, '--output')) : undefined,
|
|
65
|
-
packageFile: typeof parsed['packageFile'] === 'string' ? parsed['packageFile'] : undefined,
|
|
66
|
-
jsrFile: typeof parsed['jsrFile'] === 'string' ? parsed['jsrFile'] : undefined,
|
|
67
|
-
preid: typeof parsed['preid'] === 'string' ? parsed['preid'] : undefined,
|
|
68
|
-
dryRun: parsed['dryRun'] === true,
|
|
69
|
-
commit: parsed['commit'] === true,
|
|
70
|
-
push: parsed['push'] === true,
|
|
71
|
-
allowDirty: parsed['allowDirty'] === true,
|
|
72
|
-
yes: parsed['yes'] === true,
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const assertAdapter = (value: string, flagName: string): MirrorAdapterName => {
|
|
77
|
-
if (!adapterNames.has(value)) throw new MirrorError(`Invalid ${flagName} value: ${value}`)
|
|
78
|
-
return value as MirrorAdapterName
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const assertFormat = (value: string): MirrorFormat => {
|
|
82
|
-
if (value !== 'text' && value !== 'json') throw new MirrorError(`Invalid --format value: ${value}`)
|
|
83
|
-
return value
|
|
84
|
-
}
|