@guiho/mirror 2.3.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/CHANGELOG.md +16 -0
- package/LICENSE.md +23 -0
- package/README.md +190 -0
- package/jsr.json +12 -0
- package/library/adapters.d.ts +27 -0
- package/library/adapters.d.ts.map +1 -0
- package/library/adapters.js +152 -0
- package/library/cli.d.ts +29 -0
- package/library/cli.d.ts.map +1 -0
- package/library/cli.js +310 -0
- package/library/config.d.ts +14 -0
- package/library/config.d.ts.map +1 -0
- package/library/config.js +193 -0
- package/library/errors.d.ts +9 -0
- package/library/errors.d.ts.map +1 -0
- package/library/errors.js +15 -0
- package/library/executor.d.ts +7 -0
- package/library/executor.d.ts.map +1 -0
- package/library/executor.js +34 -0
- package/library/flags.d.ts +6 -0
- package/library/flags.d.ts.map +1 -0
- package/library/flags.js +70 -0
- package/library/guiho-mirror-bin.d.ts +6 -0
- package/library/guiho-mirror-bin.d.ts.map +1 -0
- package/library/guiho-mirror-bin.js +6 -0
- package/library/guiho-mirror.d.ts +14 -0
- package/library/guiho-mirror.d.ts.map +1 -0
- package/library/guiho-mirror.js +12 -0
- package/library/plan.d.ts +10 -0
- package/library/plan.d.ts.map +1 -0
- package/library/plan.js +81 -0
- package/library/reporter.d.ts +12 -0
- package/library/reporter.d.ts.map +1 -0
- package/library/reporter.js +121 -0
- package/library/types.d.ts +124 -0
- package/library/types.d.ts.map +1 -0
- package/library/types.js +4 -0
- package/library/version.d.ts +10 -0
- package/library/version.d.ts.map +1 -0
- package/library/version.js +31 -0
- package/package.json +79 -0
package/library/cli.js
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Copyright (c) 2026 GUIHO Technologies as represented by Cristóvão GUIHO. All Rights Reserved.
|
|
3
|
+
*/
|
|
4
|
+
import { defineCommand, runMain } from 'citty';
|
|
5
|
+
import { readFileSync } from 'node:fs';
|
|
6
|
+
import { resolve } from 'node:path';
|
|
7
|
+
import { MirrorError } from './errors';
|
|
8
|
+
import { readCurrentVersion, resolveProjectName } from './adapters';
|
|
9
|
+
import { configPathForDisplay, discoverMirrorConfig, loadMirrorConfig, relativeFromCwd, writeInitConfig } from './config';
|
|
10
|
+
import { executeVersionPlan } from './executor';
|
|
11
|
+
import { parseMirrorCliOptions } from './flags';
|
|
12
|
+
import { buildVersionPlan, validateMirrorConfig } from './plan';
|
|
13
|
+
import { mirrorBanner, reportConfig, reportConfigSchema, reportExecution, reportExecutionSummary, reportPlan, reportValue } from './reporter';
|
|
14
|
+
import { resolveNextVersion } from './version';
|
|
15
|
+
const mirrorVersion = readInstalledVersion();
|
|
16
|
+
const globalArgs = {
|
|
17
|
+
config: { type: 'string', description: 'Path to mirror.config.toml' },
|
|
18
|
+
cwd: { type: 'string', description: 'Run as if Mirror started in this directory' },
|
|
19
|
+
format: { type: 'enum', options: ['text', 'json'], default: 'text', description: 'Output format' },
|
|
20
|
+
'no-color': { type: 'boolean', description: 'Disable color output' },
|
|
21
|
+
verbose: { type: 'boolean', description: 'Show full error details and stack traces' },
|
|
22
|
+
};
|
|
23
|
+
const overrideArgs = {
|
|
24
|
+
...globalArgs,
|
|
25
|
+
source: { type: 'enum', options: ['package.json', 'jsr.json', 'git'], description: 'Override version source' },
|
|
26
|
+
output: { type: 'string', description: 'Override version output. Repeat or comma-separate values.' },
|
|
27
|
+
'package-file': { type: 'string', description: 'Override package.json path' },
|
|
28
|
+
'jsr-file': { type: 'string', description: 'Override jsr.json path' },
|
|
29
|
+
preid: { type: 'string', description: 'Override prerelease identifier' },
|
|
30
|
+
};
|
|
31
|
+
const applyArgs = {
|
|
32
|
+
...overrideArgs,
|
|
33
|
+
'dry-run': { type: 'boolean', alias: 'dy', description: 'Build and print the plan without applying it' },
|
|
34
|
+
commit: { type: 'boolean', description: 'Create a release commit when file outputs changed' },
|
|
35
|
+
push: { type: 'boolean', description: 'Create the release commit when needed, then push release refs' },
|
|
36
|
+
'allow-dirty': { type: 'boolean', description: 'Allow release in a dirty Git worktree' },
|
|
37
|
+
yes: { type: 'boolean', alias: 'y', description: 'Apply without interactive confirmation' },
|
|
38
|
+
};
|
|
39
|
+
const targetArg = {
|
|
40
|
+
target: { type: 'positional', description: 'Release target or exact semantic version', required: true },
|
|
41
|
+
};
|
|
42
|
+
export const createMirrorCommand = () => defineCommand({
|
|
43
|
+
meta: {
|
|
44
|
+
name: 'mirror',
|
|
45
|
+
version: mirrorVersion,
|
|
46
|
+
description: 'Open source project versioning for Bun, npm, JSR, and Git.',
|
|
47
|
+
},
|
|
48
|
+
args: globalArgs,
|
|
49
|
+
subCommands: {
|
|
50
|
+
init: createInitCommand(),
|
|
51
|
+
config: createConfigCommand(),
|
|
52
|
+
version: createVersionCommand(),
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
export const runMirrorCli = async (rawArgs = process.argv.slice(2)) => {
|
|
56
|
+
const effectiveArgs = rawArgs.length === 0 ? ['--help'] : rawArgs;
|
|
57
|
+
const verbose = effectiveArgs.includes('--verbose');
|
|
58
|
+
const restoreColorOutput = effectiveArgs.includes('--no-color') ? stripColorFromProcessOutput() : () => { };
|
|
59
|
+
try {
|
|
60
|
+
if (effectiveArgs.includes('--no-color'))
|
|
61
|
+
process.env['NO_COLOR'] = '1';
|
|
62
|
+
if (effectiveArgs.includes('--help')) {
|
|
63
|
+
const parsed = parseMirrorCliOptions(effectiveArgs);
|
|
64
|
+
const cwd = resolve(parsed.cwd ?? process.cwd());
|
|
65
|
+
const discovery = await discoverMirrorConfig(cwd, parsed.config);
|
|
66
|
+
const configDisplay = discovery.path ? relativeFromCwd(cwd, discovery.path) : '';
|
|
67
|
+
process.stdout.write(mirrorBanner(configDisplay));
|
|
68
|
+
}
|
|
69
|
+
if (rawArgs.length === 0) {
|
|
70
|
+
process.on('exit', () => {
|
|
71
|
+
process.stdout.write([
|
|
72
|
+
'EXAMPLES',
|
|
73
|
+
'',
|
|
74
|
+
' mirror version current # Print the current version',
|
|
75
|
+
' mirror version plan patch # Preview a patch release plan',
|
|
76
|
+
' mirror version apply minor --commit # Apply a minor release with commit',
|
|
77
|
+
' mirror version plan patch --output=package.json,jsr.json,git # Plan with package, jsr, and git',
|
|
78
|
+
' mirror config schema # Print the configuration file reference',
|
|
79
|
+
'',
|
|
80
|
+
].join('\n') + '\n');
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
// Intercept console.error during runMain to suppress citty's default
|
|
84
|
+
// ugly error dump (it does console.error(error, "\n") for non-CLIErrors).
|
|
85
|
+
// We capture the error object and handle it ourselves below.
|
|
86
|
+
let capturedError = undefined;
|
|
87
|
+
const originalConsoleError = console.error;
|
|
88
|
+
console.error = (...args) => {
|
|
89
|
+
// citty prints CLIError messages as strings — let those through
|
|
90
|
+
// citty prints non-CLIError errors as objects — capture those
|
|
91
|
+
if (args.length > 0 && args[0] instanceof Error) {
|
|
92
|
+
capturedError = args[0];
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
originalConsoleError(...args);
|
|
96
|
+
};
|
|
97
|
+
const originalProcessExit = process.exit;
|
|
98
|
+
let exitCode;
|
|
99
|
+
process.exit = ((code) => {
|
|
100
|
+
// If runMain calls process.exit(1) after an error, intercept it
|
|
101
|
+
// so we can handle the error ourselves. Let exit(0) through (--help).
|
|
102
|
+
if (code !== 0 && capturedError) {
|
|
103
|
+
exitCode = code;
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
originalProcessExit(code);
|
|
107
|
+
});
|
|
108
|
+
try {
|
|
109
|
+
await runMain(createMirrorCommand(), { rawArgs: effectiveArgs });
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
console.error = originalConsoleError;
|
|
113
|
+
process.exit = originalProcessExit;
|
|
114
|
+
}
|
|
115
|
+
if (capturedError) {
|
|
116
|
+
handleCliError(capturedError, verbose, exitCode);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
handleCliError(error, verbose);
|
|
121
|
+
}
|
|
122
|
+
finally {
|
|
123
|
+
restoreColorOutput();
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
const handleCliError = (error, verbose, exitCode) => {
|
|
127
|
+
if (error instanceof MirrorError) {
|
|
128
|
+
console.error(`error: ${error.message}`);
|
|
129
|
+
if (verbose)
|
|
130
|
+
console.error(error.stack);
|
|
131
|
+
process.exit(error.exitCode);
|
|
132
|
+
}
|
|
133
|
+
if (error instanceof Error) {
|
|
134
|
+
console.error(`error: ${error.message}`);
|
|
135
|
+
if (verbose)
|
|
136
|
+
console.error(error.stack);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
console.error('error: An unexpected error occurred.');
|
|
140
|
+
if (verbose)
|
|
141
|
+
console.error(error);
|
|
142
|
+
}
|
|
143
|
+
process.exit(exitCode ?? 1);
|
|
144
|
+
};
|
|
145
|
+
const createInitCommand = () => defineCommand({
|
|
146
|
+
meta: { name: 'init', description: 'Create a Mirror configuration file.' },
|
|
147
|
+
subCommands: {
|
|
148
|
+
'package.json': createInitKindCommand('package.json'),
|
|
149
|
+
'jsr.json': createInitKindCommand('jsr.json'),
|
|
150
|
+
git: createInitKindCommand('git'),
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
const createInitKindCommand = (kind) => defineCommand({
|
|
154
|
+
meta: { name: kind, description: `Create ${kind} project configuration.` },
|
|
155
|
+
args: {
|
|
156
|
+
...globalArgs,
|
|
157
|
+
yes: { type: 'boolean', description: 'Overwrite existing mirror.config.toml' },
|
|
158
|
+
},
|
|
159
|
+
async run(context) {
|
|
160
|
+
const options = cliOptions(context.rawArgs, context.args);
|
|
161
|
+
const path = await writeInitConfig(kind, options.cwd ?? process.cwd(), Boolean(options.yes));
|
|
162
|
+
process.stdout.write(reportValue(`created ${path}`, options.format));
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
const createConfigCommand = () => defineCommand({
|
|
166
|
+
meta: { name: 'config', description: 'Inspect and validate Mirror configuration.' },
|
|
167
|
+
subCommands: {
|
|
168
|
+
show: defineCommand({
|
|
169
|
+
meta: { name: 'show', description: 'Print the resolved configuration.' },
|
|
170
|
+
args: overrideArgs,
|
|
171
|
+
async run(context) {
|
|
172
|
+
const options = cliOptions(context.rawArgs, context.args);
|
|
173
|
+
const config = await loadMirrorConfig(options);
|
|
174
|
+
if (options.format !== 'json')
|
|
175
|
+
process.stdout.write(mirrorBanner(configPathForDisplay(config)));
|
|
176
|
+
process.stdout.write(reportConfig(config, options.format));
|
|
177
|
+
},
|
|
178
|
+
}),
|
|
179
|
+
check: defineCommand({
|
|
180
|
+
meta: { name: 'check', description: 'Validate the resolved configuration.' },
|
|
181
|
+
args: overrideArgs,
|
|
182
|
+
async run(context) {
|
|
183
|
+
const options = cliOptions(context.rawArgs, context.args);
|
|
184
|
+
await validateMirrorConfig(options);
|
|
185
|
+
process.stdout.write(reportValue('ok', options.format));
|
|
186
|
+
},
|
|
187
|
+
}),
|
|
188
|
+
schema: defineCommand({
|
|
189
|
+
meta: { name: 'schema', description: 'Print the configuration file reference.' },
|
|
190
|
+
args: globalArgs,
|
|
191
|
+
run(context) {
|
|
192
|
+
const options = cliOptions(context.rawArgs, context.args);
|
|
193
|
+
if (options.format !== 'json')
|
|
194
|
+
process.stdout.write(mirrorBanner());
|
|
195
|
+
process.stdout.write(reportConfigSchema(options.format));
|
|
196
|
+
},
|
|
197
|
+
}),
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
const createVersionCommand = () => defineCommand({
|
|
201
|
+
meta: { name: 'version', description: 'Read, plan, and apply version changes.' },
|
|
202
|
+
subCommands: {
|
|
203
|
+
current: defineCommand({
|
|
204
|
+
meta: { name: 'current', description: 'Print the current project version.' },
|
|
205
|
+
args: overrideArgs,
|
|
206
|
+
async run(context) {
|
|
207
|
+
const options = cliOptions(context.rawArgs, context.args);
|
|
208
|
+
const config = await loadMirrorConfig(options);
|
|
209
|
+
const projectName = await resolveProjectName(config);
|
|
210
|
+
process.stdout.write(reportValue(await readCurrentVersion(config, projectName), options.format));
|
|
211
|
+
},
|
|
212
|
+
}),
|
|
213
|
+
next: defineCommand({
|
|
214
|
+
meta: { name: 'next', description: 'Print the next version without checking outputs.' },
|
|
215
|
+
args: { ...overrideArgs, ...targetArg },
|
|
216
|
+
async run(context) {
|
|
217
|
+
const options = cliOptions(context.rawArgs, context.args);
|
|
218
|
+
const config = await loadMirrorConfig(options);
|
|
219
|
+
const projectName = await resolveProjectName(config);
|
|
220
|
+
const currentVersion = await readCurrentVersion(config, projectName);
|
|
221
|
+
process.stdout.write(reportValue(resolveNextVersion(currentVersion, String(context.args['target']), config.version.prereleaseId), options.format));
|
|
222
|
+
},
|
|
223
|
+
}),
|
|
224
|
+
plan: defineCommand({
|
|
225
|
+
meta: { name: 'plan', description: 'Print the release plan without writing anything.' },
|
|
226
|
+
args: { ...overrideArgs, ...targetArg },
|
|
227
|
+
async run(context) {
|
|
228
|
+
const options = cliOptions(context.rawArgs, context.args);
|
|
229
|
+
const plan = await buildVersionPlan(String(context.args['target']), options);
|
|
230
|
+
if (options.format !== 'json')
|
|
231
|
+
process.stdout.write(mirrorBanner(plan.configPath ? plan.configPath : ''));
|
|
232
|
+
process.stdout.write(reportPlan(plan, options.format));
|
|
233
|
+
},
|
|
234
|
+
}),
|
|
235
|
+
apply: defineCommand({
|
|
236
|
+
meta: { name: 'apply', description: 'Apply the release plan.' },
|
|
237
|
+
args: { ...applyArgs, ...targetArg },
|
|
238
|
+
async run(context) {
|
|
239
|
+
const options = cliOptions(context.rawArgs, context.args);
|
|
240
|
+
const plan = await buildVersionPlan(String(context.args['target']), options);
|
|
241
|
+
if (options.format !== 'json')
|
|
242
|
+
process.stdout.write(mirrorBanner(plan.configPath ? plan.configPath : ''));
|
|
243
|
+
if (options.format !== 'json')
|
|
244
|
+
process.stdout.write(reportPlan(plan, options.format));
|
|
245
|
+
const result = await executeVersionPlan(plan, options);
|
|
246
|
+
process.stdout.write(options.format === 'json' ? reportExecution(result, options.format) : reportExecutionSummary(result, options.format));
|
|
247
|
+
},
|
|
248
|
+
}),
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
const cliOptions = (rawArgs, args) => {
|
|
252
|
+
const parsed = parseMirrorCliOptions(rawArgs);
|
|
253
|
+
return {
|
|
254
|
+
...parsed,
|
|
255
|
+
config: parsed.config ?? stringArg(args['config']),
|
|
256
|
+
cwd: parsed.cwd ?? stringArg(args['cwd']),
|
|
257
|
+
format: parsed.format ?? (args['format'] === 'json' ? 'json' : 'text'),
|
|
258
|
+
source: parsed.source ?? adapterArg(args['source']),
|
|
259
|
+
output: parsed.output ?? outputArg(args['output']),
|
|
260
|
+
packageFile: parsed.packageFile ?? stringArg(args['packageFile']),
|
|
261
|
+
jsrFile: parsed.jsrFile ?? stringArg(args['jsrFile']),
|
|
262
|
+
preid: parsed.preid ?? stringArg(args['preid']),
|
|
263
|
+
dryRun: parsed.dryRun || args['dryRun'] === true,
|
|
264
|
+
commit: parsed.commit || args['commit'] === true,
|
|
265
|
+
push: parsed.push || args['push'] === true,
|
|
266
|
+
allowDirty: parsed.allowDirty || args['allowDirty'] === true,
|
|
267
|
+
yes: parsed.yes || args['yes'] === true,
|
|
268
|
+
verbose: parsed.verbose || args['verbose'] === true,
|
|
269
|
+
};
|
|
270
|
+
};
|
|
271
|
+
const stringArg = (value) => (typeof value === 'string' ? value : undefined);
|
|
272
|
+
const adapterArg = (value) => {
|
|
273
|
+
if (value === 'package.json' || value === 'jsr.json' || value === 'git')
|
|
274
|
+
return value;
|
|
275
|
+
return undefined;
|
|
276
|
+
};
|
|
277
|
+
const outputArg = (value) => {
|
|
278
|
+
if (typeof value !== 'string')
|
|
279
|
+
return undefined;
|
|
280
|
+
const values = value.split(',').map((item) => item.trim()).filter(Boolean);
|
|
281
|
+
if (values.length === 0)
|
|
282
|
+
return undefined;
|
|
283
|
+
return values.map((item) => {
|
|
284
|
+
const adapter = adapterArg(item);
|
|
285
|
+
if (!adapter)
|
|
286
|
+
throw new MirrorError(`Invalid --output value: ${item}`);
|
|
287
|
+
return adapter;
|
|
288
|
+
});
|
|
289
|
+
};
|
|
290
|
+
function readInstalledVersion() {
|
|
291
|
+
try {
|
|
292
|
+
const packageJson = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
293
|
+
return typeof packageJson['version'] === 'string' ? packageJson['version'] : '0.0.0';
|
|
294
|
+
}
|
|
295
|
+
catch {
|
|
296
|
+
return '0.0.0';
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
const stripAnsi = (value) => value.replace(/\u001B\[[0-?]*[ -/]*[@-~]/g, '');
|
|
300
|
+
const stripColorFromProcessOutput = () => {
|
|
301
|
+
const originalLog = console.log;
|
|
302
|
+
const originalError = console.error;
|
|
303
|
+
console.log = (...values) => originalLog(...values.map(stripAnsiValue));
|
|
304
|
+
console.error = (...values) => originalError(...values.map(stripAnsiValue));
|
|
305
|
+
return () => {
|
|
306
|
+
console.log = originalLog;
|
|
307
|
+
console.error = originalError;
|
|
308
|
+
};
|
|
309
|
+
};
|
|
310
|
+
const stripAnsiValue = (value) => (typeof value === 'string' ? stripAnsi(value) : value);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Copyright (c) 2026 GUIHO Technologies as represented by Cristóvão GUIHO. All Rights Reserved.
|
|
3
|
+
*/
|
|
4
|
+
import type { MirrorAdapterName, MirrorCliOptions, MirrorConfig, MirrorConfigDiscovery, MirrorRawConfig } from './types';
|
|
5
|
+
export declare const resolveMirrorPath: (cwd: string, path: string) => string;
|
|
6
|
+
export declare const relativeFromCwd: (cwd: string, path: string) => string;
|
|
7
|
+
export declare const discoverMirrorConfig: (cwd: string, explicitPath?: string) => Promise<MirrorConfigDiscovery>;
|
|
8
|
+
export declare const readConfigFile: (path: string) => Promise<MirrorRawConfig>;
|
|
9
|
+
export declare const loadMirrorConfig: (options?: MirrorCliOptions) => Promise<MirrorConfig>;
|
|
10
|
+
export declare const normalizeMirrorConfig: (raw: MirrorRawConfig, cwd: string, configPath: string | undefined, options?: MirrorCliOptions) => MirrorConfig;
|
|
11
|
+
export declare const createInitConfig: (kind: MirrorAdapterName, cwd: string) => string;
|
|
12
|
+
export declare const writeInitConfig: (kind: MirrorAdapterName, cwd: string, overwrite?: boolean) => Promise<string>;
|
|
13
|
+
export declare const configPathForDisplay: (config: MirrorConfig) => string;
|
|
14
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../source/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EACV,iBAAiB,EACjB,gBAAgB,EAChB,YAAY,EACZ,qBAAqB,EAErB,eAAe,EAChB,MAAM,SAAS,CAAA;AAMhB,eAAO,MAAM,iBAAiB,GAAI,KAAK,MAAM,EAAE,MAAM,MAAM,WAAmD,CAAA;AAE9G,eAAO,MAAM,eAAe,GAAI,KAAK,MAAM,EAAE,MAAM,MAAM,WAGxD,CAAA;AAED,eAAO,MAAM,oBAAoB,GAAU,KAAK,MAAM,EAAE,eAAe,MAAM,KAAG,OAAO,CAAC,qBAAqB,CAa5G,CAAA;AAED,eAAO,MAAM,cAAc,GAAU,MAAM,MAAM,KAAG,OAAO,CAAC,eAAe,CAQ1E,CAAA;AAED,eAAO,MAAM,gBAAgB,GAAU,UAAS,gBAAqB,KAAG,OAAO,CAAC,YAAY,CAO3F,CAAA;AAED,eAAO,MAAM,qBAAqB,GAChC,KAAK,eAAe,EACpB,KAAK,MAAM,EACX,YAAY,MAAM,GAAG,SAAS,EAC9B,UAAS,gBAAqB,KAC7B,YA6CF,CAAA;AAED,eAAO,MAAM,gBAAgB,GAAI,MAAM,iBAAiB,EAAE,KAAK,MAAM,WAqEpE,CAAA;AAED,eAAO,MAAM,eAAe,GAAU,MAAM,iBAAiB,EAAE,KAAK,MAAM,EAAE,mBAAiB,oBAO5F,CAAA;AAED,eAAO,MAAM,oBAAoB,GAAI,QAAQ,YAAY,WAAoF,CAAA"}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Copyright (c) 2026 GUIHO Technologies as represented by Cristóvão GUIHO. All Rights Reserved.
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync } from 'node:fs';
|
|
5
|
+
import { basename, isAbsolute, join, relative, resolve } from 'node:path';
|
|
6
|
+
import { MirrorError } from './errors';
|
|
7
|
+
const adapters = new Set(['package.json', 'jsr.json', 'git']);
|
|
8
|
+
const projectNameSources = new Set(['package.json', 'jsr.json']);
|
|
9
|
+
export const resolveMirrorPath = (cwd, path) => (isAbsolute(path) ? path : resolve(cwd, path));
|
|
10
|
+
export const relativeFromCwd = (cwd, path) => {
|
|
11
|
+
const relativePath = relative(cwd, resolveMirrorPath(cwd, path));
|
|
12
|
+
return relativePath || '.';
|
|
13
|
+
};
|
|
14
|
+
export const discoverMirrorConfig = async (cwd, explicitPath) => {
|
|
15
|
+
if (explicitPath) {
|
|
16
|
+
const configPath = resolveMirrorPath(cwd, explicitPath);
|
|
17
|
+
return { path: configPath, raw: await readConfigFile(configPath) };
|
|
18
|
+
}
|
|
19
|
+
const rootConfigPath = resolve(cwd, 'mirror.config.toml');
|
|
20
|
+
if (existsSync(rootConfigPath))
|
|
21
|
+
return { path: rootConfigPath, raw: await readConfigFile(rootConfigPath) };
|
|
22
|
+
const nestedConfigPath = resolve(cwd, 'config', 'mirror.config.toml');
|
|
23
|
+
if (existsSync(nestedConfigPath))
|
|
24
|
+
return { path: nestedConfigPath, raw: await readConfigFile(nestedConfigPath) };
|
|
25
|
+
return {};
|
|
26
|
+
};
|
|
27
|
+
export const readConfigFile = async (path) => {
|
|
28
|
+
if (!existsSync(path))
|
|
29
|
+
throw new MirrorError(`Configuration file not found: ${path}`);
|
|
30
|
+
const parsed = Bun.TOML.parse(await Bun.file(path).text());
|
|
31
|
+
if (!isRecord(parsed))
|
|
32
|
+
throw new MirrorError(`Configuration file must contain a TOML object: ${path}`);
|
|
33
|
+
return parsed;
|
|
34
|
+
};
|
|
35
|
+
export const loadMirrorConfig = async (options = {}) => {
|
|
36
|
+
const cwd = resolve(options.cwd ?? process.cwd());
|
|
37
|
+
const discovered = await discoverMirrorConfig(cwd, options.config);
|
|
38
|
+
if (!discovered.raw)
|
|
39
|
+
throw new MirrorError('Mirror configuration not found. Run `mirror init package`, `mirror init jsr`, or `mirror init git`.');
|
|
40
|
+
return normalizeMirrorConfig(discovered.raw, cwd, discovered.path, options);
|
|
41
|
+
};
|
|
42
|
+
export const normalizeMirrorConfig = (raw, cwd, configPath, options = {}) => {
|
|
43
|
+
if (raw.schema !== 1)
|
|
44
|
+
throw new MirrorError('Unsupported or missing configuration schema. Expected `schema = 1`.');
|
|
45
|
+
if (raw.version?.scheme !== undefined && raw.version.scheme !== 'semver')
|
|
46
|
+
throw new MirrorError('Only `version.scheme = "semver"` is supported.');
|
|
47
|
+
const source = options.source ?? assertAdapter(raw.version?.source, 'version.source');
|
|
48
|
+
const output = dedupeAdapters(options.output ?? assertOutput(raw.version?.output));
|
|
49
|
+
const nameSource = raw.project?.name_source
|
|
50
|
+
? assertProjectNameSource(raw.project.name_source, 'project.name_source')
|
|
51
|
+
: undefined;
|
|
52
|
+
const projectName = optionalString(raw.project?.name, 'project.name');
|
|
53
|
+
const prereleaseId = options.preid ?? optionalString(raw.version?.prerelease_id, 'version.prerelease_id') ?? '';
|
|
54
|
+
const packagePath = options.packageFile ?? optionalString(raw.package?.path, 'package.path') ?? 'package.json';
|
|
55
|
+
const jsrPath = options.jsrFile ?? optionalString(raw.jsr?.path, 'jsr.path') ?? 'jsr.json';
|
|
56
|
+
const tagTemplate = optionalString(raw.git?.tag_template, 'git.tag_template') ?? 'v{version}';
|
|
57
|
+
const gitCommit = optionalBoolean(raw.git?.commit, 'git.commit') === true;
|
|
58
|
+
const gitPush = optionalBoolean(raw.git?.push, 'git.push') === true;
|
|
59
|
+
const gitAllowDirty = optionalBoolean(raw.git?.allow_dirty, 'git.allow_dirty') === true;
|
|
60
|
+
return {
|
|
61
|
+
schema: 1,
|
|
62
|
+
cwd,
|
|
63
|
+
configPath,
|
|
64
|
+
project: {
|
|
65
|
+
name: projectName,
|
|
66
|
+
nameSource,
|
|
67
|
+
},
|
|
68
|
+
version: {
|
|
69
|
+
scheme: 'semver',
|
|
70
|
+
source,
|
|
71
|
+
output,
|
|
72
|
+
prereleaseId,
|
|
73
|
+
},
|
|
74
|
+
package: {
|
|
75
|
+
path: packagePath,
|
|
76
|
+
},
|
|
77
|
+
jsr: {
|
|
78
|
+
path: jsrPath,
|
|
79
|
+
},
|
|
80
|
+
git: {
|
|
81
|
+
tagTemplate,
|
|
82
|
+
commit: options.commit === true || options.push === true || gitCommit || gitPush,
|
|
83
|
+
push: options.push === true || gitPush,
|
|
84
|
+
allowDirty: options.allowDirty === true || gitAllowDirty,
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
export const createInitConfig = (kind, cwd) => {
|
|
89
|
+
const projectName = basename(cwd);
|
|
90
|
+
if (kind === 'package.json') {
|
|
91
|
+
return `schema = 1
|
|
92
|
+
|
|
93
|
+
[project]
|
|
94
|
+
name_source = "package.json"
|
|
95
|
+
|
|
96
|
+
[version]
|
|
97
|
+
scheme = "semver"
|
|
98
|
+
source = "package.json"
|
|
99
|
+
output = ["package.json"]
|
|
100
|
+
prerelease_id = ""
|
|
101
|
+
|
|
102
|
+
[package]
|
|
103
|
+
path = "package.json"
|
|
104
|
+
|
|
105
|
+
[jsr]
|
|
106
|
+
path = "jsr.json"
|
|
107
|
+
|
|
108
|
+
[git]
|
|
109
|
+
tag_template = "{name}@{version}"
|
|
110
|
+
commit = false
|
|
111
|
+
push = false
|
|
112
|
+
allow_dirty = false
|
|
113
|
+
`;
|
|
114
|
+
}
|
|
115
|
+
if (kind === 'jsr.json') {
|
|
116
|
+
return `schema = 1
|
|
117
|
+
|
|
118
|
+
[project]
|
|
119
|
+
name_source = "jsr.json"
|
|
120
|
+
|
|
121
|
+
[version]
|
|
122
|
+
scheme = "semver"
|
|
123
|
+
source = "jsr.json"
|
|
124
|
+
output = ["jsr.json"]
|
|
125
|
+
prerelease_id = ""
|
|
126
|
+
|
|
127
|
+
[jsr]
|
|
128
|
+
path = "jsr.json"
|
|
129
|
+
|
|
130
|
+
[git]
|
|
131
|
+
tag_template = "{name}@{version}"
|
|
132
|
+
commit = false
|
|
133
|
+
push = false
|
|
134
|
+
allow_dirty = false
|
|
135
|
+
`;
|
|
136
|
+
}
|
|
137
|
+
return `schema = 1
|
|
138
|
+
|
|
139
|
+
[project]
|
|
140
|
+
name = "${projectName}"
|
|
141
|
+
|
|
142
|
+
[version]
|
|
143
|
+
scheme = "semver"
|
|
144
|
+
source = "git"
|
|
145
|
+
output = ["git"]
|
|
146
|
+
prerelease_id = ""
|
|
147
|
+
|
|
148
|
+
[git]
|
|
149
|
+
tag_template = "v{version}"
|
|
150
|
+
commit = false
|
|
151
|
+
push = false
|
|
152
|
+
allow_dirty = false
|
|
153
|
+
`;
|
|
154
|
+
};
|
|
155
|
+
export const writeInitConfig = async (kind, cwd, overwrite = false) => {
|
|
156
|
+
const path = join(cwd, 'mirror.config.toml');
|
|
157
|
+
if (existsSync(path) && !overwrite)
|
|
158
|
+
throw new MirrorError(`Configuration already exists: ${path}`);
|
|
159
|
+
await Bun.write(path, createInitConfig(kind, cwd));
|
|
160
|
+
return path;
|
|
161
|
+
};
|
|
162
|
+
export const configPathForDisplay = (config) => (config.configPath ? relativeFromCwd(config.cwd, config.configPath) : '(none)');
|
|
163
|
+
const assertAdapter = (value, key) => {
|
|
164
|
+
if (typeof value !== 'string' || !adapters.has(value))
|
|
165
|
+
throw new MirrorError(`Invalid or missing ${key}. Expected package.json, jsr.json, or git.`);
|
|
166
|
+
return value;
|
|
167
|
+
};
|
|
168
|
+
const assertProjectNameSource = (value, key) => {
|
|
169
|
+
if (typeof value !== 'string' || !projectNameSources.has(value))
|
|
170
|
+
throw new MirrorError(`Invalid ${key}. Expected package.json or jsr.json.`);
|
|
171
|
+
return value;
|
|
172
|
+
};
|
|
173
|
+
const assertOutput = (value) => {
|
|
174
|
+
if (!Array.isArray(value) || value.length === 0)
|
|
175
|
+
throw new MirrorError('Invalid or missing version.output. Expected at least one output adapter.');
|
|
176
|
+
return value.map((item) => assertAdapter(item, 'version.output'));
|
|
177
|
+
};
|
|
178
|
+
const dedupeAdapters = (value) => [...new Set(value)];
|
|
179
|
+
const optionalString = (value, key) => {
|
|
180
|
+
if (value === undefined)
|
|
181
|
+
return undefined;
|
|
182
|
+
if (typeof value !== 'string')
|
|
183
|
+
throw new MirrorError(`Invalid ${key}. Expected a string.`);
|
|
184
|
+
return value;
|
|
185
|
+
};
|
|
186
|
+
const optionalBoolean = (value, key) => {
|
|
187
|
+
if (value === undefined)
|
|
188
|
+
return undefined;
|
|
189
|
+
if (typeof value !== 'boolean')
|
|
190
|
+
throw new MirrorError(`Invalid ${key}. Expected true or false.`);
|
|
191
|
+
return value;
|
|
192
|
+
};
|
|
193
|
+
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Copyright (c) 2026 GUIHO Technologies as represented by Cristóvão GUIHO. All Rights Reserved.
|
|
3
|
+
*/
|
|
4
|
+
export declare class MirrorError extends Error {
|
|
5
|
+
readonly exitCode: number;
|
|
6
|
+
constructor(message: string, exitCode?: number);
|
|
7
|
+
}
|
|
8
|
+
export declare const invariant: (condition: unknown, message: string) => asserts condition;
|
|
9
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../source/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,qBAAa,WAAY,SAAQ,KAAK;IACpC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;gBAEb,OAAO,EAAE,MAAM,EAAE,QAAQ,SAAI;CAK1C;AAED,eAAO,MAAM,SAAS,GAAI,WAAW,OAAO,EAAE,SAAS,MAAM,KAAG,QAAQ,SAEvE,CAAA"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Copyright (c) 2026 GUIHO Technologies as represented by Cristóvão GUIHO. All Rights Reserved.
|
|
3
|
+
*/
|
|
4
|
+
export class MirrorError extends Error {
|
|
5
|
+
exitCode;
|
|
6
|
+
constructor(message, exitCode = 1) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = 'MirrorError';
|
|
9
|
+
this.exitCode = exitCode;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export const invariant = (condition, message) => {
|
|
13
|
+
if (!condition)
|
|
14
|
+
throw new MirrorError(message);
|
|
15
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Copyright (c) 2026 GUIHO Technologies as represented by Cristóvão GUIHO. All Rights Reserved.
|
|
3
|
+
*/
|
|
4
|
+
import type { MirrorCliOptions, MirrorExecutionResult } from './types';
|
|
5
|
+
export declare const applyVersionPlan: (target: string, options?: MirrorCliOptions) => Promise<MirrorExecutionResult>;
|
|
6
|
+
export declare const executeVersionPlan: (plan: MirrorExecutionResult["plan"], options?: MirrorCliOptions) => Promise<MirrorExecutionResult>;
|
|
7
|
+
//# sourceMappingURL=executor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../source/executor.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAA;AAMtE,eAAO,MAAM,gBAAgB,GAAU,QAAQ,MAAM,EAAE,UAAS,gBAAqB,KAAG,OAAO,CAAC,qBAAqB,CAIpH,CAAA;AAED,eAAO,MAAM,kBAAkB,GAC7B,MAAM,qBAAqB,CAAC,MAAM,CAAC,EACnC,UAAS,gBAAqB,KAC7B,OAAO,CAAC,qBAAqB,CAmB/B,CAAA"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Copyright (c) 2026 GUIHO Technologies as represented by Cristóvão GUIHO. All Rights Reserved.
|
|
3
|
+
*/
|
|
4
|
+
import { MirrorError } from './errors';
|
|
5
|
+
import { createGitCommit, createGitTag, isGitDirty, isGitRepository, pushGitRefs, writeJsrVersion, writePackageVersion } from './adapters';
|
|
6
|
+
import { loadMirrorConfig } from './config';
|
|
7
|
+
import { buildVersionPlan } from './plan';
|
|
8
|
+
export const applyVersionPlan = async (target, options = {}) => {
|
|
9
|
+
const plan = await buildVersionPlan(target, options);
|
|
10
|
+
return executeVersionPlan(plan, options);
|
|
11
|
+
};
|
|
12
|
+
export const executeVersionPlan = async (plan, options = {}) => {
|
|
13
|
+
if (options.dryRun)
|
|
14
|
+
return { plan, applied: false, dryRun: true };
|
|
15
|
+
if (!plan.allowDirty && (await isGitRepository(plan.cwd)) && (await isGitDirty(plan.cwd))) {
|
|
16
|
+
throw new MirrorError('Git worktree is dirty. Commit changes or pass --allow-dirty.');
|
|
17
|
+
}
|
|
18
|
+
if (!options.yes)
|
|
19
|
+
throw new MirrorError('Refusing to apply without confirmation. Pass --yes to apply the plan.');
|
|
20
|
+
const config = await loadMirrorConfig(options);
|
|
21
|
+
if (config.version.output.includes('package.json'))
|
|
22
|
+
await writePackageVersion(config, plan.nextVersion);
|
|
23
|
+
if (config.version.output.includes('jsr.json'))
|
|
24
|
+
await writeJsrVersion(config, plan.nextVersion);
|
|
25
|
+
for (const action of plan.actions) {
|
|
26
|
+
if (action.type === 'git-commit')
|
|
27
|
+
await createGitCommit(plan.cwd, action.paths, action.message);
|
|
28
|
+
if (action.type === 'git-tag')
|
|
29
|
+
await createGitTag(plan.cwd, action.tag);
|
|
30
|
+
if (action.type === 'git-push')
|
|
31
|
+
await pushGitRefs(plan.cwd, action.includeCommit, action.includeTags);
|
|
32
|
+
}
|
|
33
|
+
return { plan, applied: true, dryRun: false };
|
|
34
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Copyright (c) 2026 GUIHO Technologies as represented by Cristóvão GUIHO. All Rights Reserved.
|
|
3
|
+
*/
|
|
4
|
+
import type { MirrorCliOptions } from './types';
|
|
5
|
+
export declare const parseMirrorCliOptions: (rawArgs: string[]) => MirrorCliOptions;
|
|
6
|
+
//# sourceMappingURL=flags.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flags.d.ts","sourceRoot":"","sources":["../source/flags.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAqB,gBAAgB,EAAgB,MAAM,SAAS,CAAA;AAehF,eAAO,MAAM,qBAAqB,GAAI,SAAS,MAAM,EAAE,KAAG,gBAuDzD,CAAA"}
|