agentic-workflow-manager 2.1.0 → 3.0.0
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/dist/src/index.js +130 -17
- package/dist/src/release/core.js +83 -0
- package/dist/src/release/index.js +94 -0
- package/dist/src/release/orchestrator.js +116 -0
- package/dist/src/ui/picker-view.js +77 -0
- package/dist/src/ui/picker.js +194 -0
- package/dist/src/ui/text.js +75 -0
- package/dist/src/ui/tty.js +15 -0
- package/dist/src/utils/registry-view.js +35 -21
- package/dist/tests/release/core.test.js +106 -0
- package/dist/tests/release/orchestrator.test.js +188 -0
- package/dist/tests/ui/picker-reducer.test.js +92 -0
- package/dist/tests/ui/picker-shell.test.js +59 -0
- package/dist/tests/ui/picker-view.test.js +57 -0
- package/dist/tests/ui/text.test.js +55 -0
- package/dist/tests/ui/tty.test.js +39 -0
- package/dist/tests/utils/registry-view.test.js +39 -36
- package/package.json +3 -1
- package/dist/tests/core/registry-versioned-sync.test.js +0 -113
- package/dist/tests/core/registry.test.js +0 -51
- package/dist/tests/registry/b3-ledger-wiring.test.js +0 -74
- package/dist/tests/registry/catalog-consistency.test.js +0 -47
- package/dist/tests/registry/design-skills.test.js +0 -146
- package/dist/tests/registry/prose-agnostic.test.js +0 -18
- package/dist/tests/registry/sensor-packs.test.js +0 -45
- package/dist/tests/registry/skill-versions.test.js +0 -26
- package/dist/tests/registry/using-awm.test.js +0 -39
package/dist/src/index.js
CHANGED
|
@@ -9,6 +9,8 @@ const commander_1 = require("commander");
|
|
|
9
9
|
const config_1 = require("./utils/config");
|
|
10
10
|
const grouping_1 = require("./utils/grouping");
|
|
11
11
|
const registry_view_1 = require("./utils/registry-view");
|
|
12
|
+
const tty_1 = require("./ui/tty");
|
|
13
|
+
const picker_1 = require("./ui/picker");
|
|
12
14
|
const providers_1 = require("./providers");
|
|
13
15
|
const executor_1 = require("./core/executor");
|
|
14
16
|
const regenerate_1 = require("./core/context/regenerate");
|
|
@@ -73,6 +75,7 @@ program.command('add [name]')
|
|
|
73
75
|
.option('-s, --scope <scope>', 'Scope: local or global')
|
|
74
76
|
.option('-m, --method <method>', 'Install method: symlink or copy')
|
|
75
77
|
.option('-y, --yes', 'Skip confirmation prompts')
|
|
78
|
+
.option('--all', 'install all artifacts from all packages without prompting')
|
|
76
79
|
.action(async (name, options) => {
|
|
77
80
|
(0, prompts_1.intro)(picocolors_1.default.bgCyan(picocolors_1.default.black(' AWM - Agentic Workflow Manager ')));
|
|
78
81
|
// 1. Sync the registry
|
|
@@ -142,6 +145,10 @@ program.command('add [name]')
|
|
|
142
145
|
(0, prompts_1.outro)(`✅ Installed bundle ${picocolors_1.default.cyan(matchedBundle.name)}:\n ${lines}${recordNote}`);
|
|
143
146
|
return;
|
|
144
147
|
}
|
|
148
|
+
// name given but no matching bundle found — report clearly regardless of TTY
|
|
149
|
+
console.error(picocolors_1.default.red(`Bundle "${name}" not found in registry.`));
|
|
150
|
+
console.error(picocolors_1.default.dim('Run `awm list` to see available packages.'));
|
|
151
|
+
process.exit(1);
|
|
145
152
|
}
|
|
146
153
|
// 2. Discover artifacts
|
|
147
154
|
const skills = (0, discovery_1.discoverSkills)();
|
|
@@ -152,6 +159,103 @@ program.command('add [name]')
|
|
|
152
159
|
process.exit(0);
|
|
153
160
|
}
|
|
154
161
|
const prefs = (0, config_1.getPreferences)();
|
|
162
|
+
// --all: install everything from every package headlessly
|
|
163
|
+
if (options.all) {
|
|
164
|
+
let targetAgents;
|
|
165
|
+
if (options.agent) {
|
|
166
|
+
const validAgents = Object.keys(providers_1.PROVIDERS);
|
|
167
|
+
const parsed = options.agent.split(',').map((a) => a.trim());
|
|
168
|
+
for (const a of parsed) {
|
|
169
|
+
if (!validAgents.includes(a)) {
|
|
170
|
+
console.error(picocolors_1.default.red(`Invalid agent "${a}". Use: ${validAgents.join(', ')}.`));
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
targetAgents = parsed;
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
targetAgents = [prefs.defaultAgent];
|
|
178
|
+
}
|
|
179
|
+
let scopeVal;
|
|
180
|
+
if (options.scope) {
|
|
181
|
+
if (!['local', 'global'].includes(options.scope)) {
|
|
182
|
+
console.error(picocolors_1.default.red(`Invalid scope "${options.scope}". Use: local or global.`));
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
scopeVal = options.scope;
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
scopeVal = prefs.defaultScope;
|
|
189
|
+
}
|
|
190
|
+
let methodVal;
|
|
191
|
+
if (options.method) {
|
|
192
|
+
if (!['symlink', 'copy'].includes(options.method)) {
|
|
193
|
+
console.error(picocolors_1.default.red(`Invalid method "${options.method}". Use: symlink or copy.`));
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
methodVal = options.method;
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
methodVal = scopeVal === 'local' ? 'copy' : 'symlink';
|
|
200
|
+
}
|
|
201
|
+
const includeWorkflows = targetAgents.some((a) => providers_1.PROVIDERS[a].workflow !== null);
|
|
202
|
+
const includeAgents = targetAgents.some((a) => providers_1.PROVIDERS[a].agent !== null);
|
|
203
|
+
const allView = (0, registry_view_1.buildPackageView)(skills, includeWorkflows ? workflows : [], includeAgents ? agents : [], (0, bundles_1.discoverAllBundles)());
|
|
204
|
+
const allDedup = new Map();
|
|
205
|
+
for (const pkg of allView) {
|
|
206
|
+
for (const a of pkg.artifacts) {
|
|
207
|
+
allDedup.set((0, registry_view_1.artifactValue)(a), a);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
const artifactsToInstall = Array.from(allDedup.values()).map((a) => ({
|
|
211
|
+
name: a.installName,
|
|
212
|
+
sourcePath: a.sourcePath,
|
|
213
|
+
type: a.type,
|
|
214
|
+
}));
|
|
215
|
+
if (artifactsToInstall.length === 0) {
|
|
216
|
+
(0, prompts_1.outro)(picocolors_1.default.yellow('No artifacts available.'));
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const installSpinner = (0, prompts_1.spinner)();
|
|
220
|
+
installSpinner.start('Installing artifacts...');
|
|
221
|
+
try {
|
|
222
|
+
const installed = [];
|
|
223
|
+
const skipped = [];
|
|
224
|
+
for (const currentAgent of targetAgents) {
|
|
225
|
+
for (const artifact of artifactsToInstall) {
|
|
226
|
+
if (providers_1.PROVIDERS[currentAgent][artifact.type] === null) {
|
|
227
|
+
skipped.push(`${artifact.name} (${currentAgent})`);
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
const targetDir = (0, providers_1.getTargetPath)(artifact.type, currentAgent, scopeVal);
|
|
231
|
+
const finalDest = path_1.default.join(targetDir, artifact.name);
|
|
232
|
+
(0, executor_1.installArtifact)(artifact.sourcePath, finalDest, methodVal);
|
|
233
|
+
installed.push(`${artifact.name} → ${currentAgent} (${scopeVal})`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
(0, config_1.savePreferences)({ defaultAgent: targetAgents[0], defaultScope: scopeVal, installMethod: methodVal });
|
|
237
|
+
installSpinner.stop('Installation complete!');
|
|
238
|
+
if (skipped.length > 0) {
|
|
239
|
+
for (const sk of skipped) {
|
|
240
|
+
console.log(picocolors_1.default.yellow(` ⚠️ Skipped: ${sk} (not supported by target agent)`));
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
const names = installed.map((n) => picocolors_1.default.green(n)).join('\n ');
|
|
244
|
+
(0, prompts_1.outro)(`✅ Installed:\n ${names}`);
|
|
245
|
+
}
|
|
246
|
+
catch (e) {
|
|
247
|
+
installSpinner.stop('Installation failed.');
|
|
248
|
+
console.error(picocolors_1.default.red(e.message));
|
|
249
|
+
process.exit(1);
|
|
250
|
+
}
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
// Interactive selection requires a TTY. Bundle-by-name (handled above) works headless.
|
|
254
|
+
if (!(0, tty_1.isInteractive)()) {
|
|
255
|
+
console.error(picocolors_1.default.red('`awm add` needs an interactive terminal for selection.'));
|
|
256
|
+
console.error(picocolors_1.default.dim('Install a bundle non-interactively: `awm add <bundle>` (optionally with --agent/--scope).'));
|
|
257
|
+
process.exit(1);
|
|
258
|
+
}
|
|
155
259
|
// 3. Agent & Scope Prompts (Moved up)
|
|
156
260
|
let targetAgents;
|
|
157
261
|
if (options.agent) {
|
|
@@ -207,27 +311,35 @@ program.command('add [name]')
|
|
|
207
311
|
process.exit(0);
|
|
208
312
|
}
|
|
209
313
|
// 5. Level 1 — pick package(s)
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
required: true
|
|
314
|
+
const pickedPackages = await (0, picker_1.multiselectPicker)({
|
|
315
|
+
title: 'Select package(s)',
|
|
316
|
+
items: (0, registry_view_1.packagePickerItems)(view),
|
|
214
317
|
});
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
.
|
|
318
|
+
if (pickedPackages === null) {
|
|
319
|
+
(0, prompts_1.outro)('Operation cancelled.');
|
|
320
|
+
process.exit(0);
|
|
321
|
+
}
|
|
322
|
+
if (pickedPackages.length === 0) {
|
|
323
|
+
(0, prompts_1.outro)(picocolors_1.default.yellow('No packages selected.'));
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
const selectedPackages = pickedPackages
|
|
327
|
+
.map((name) => view.find((p) => p.name === name))
|
|
218
328
|
.filter(Boolean);
|
|
219
329
|
// 5b. Level 2 — pick skills within each package, in sequence
|
|
220
330
|
const dedup = new Map();
|
|
221
331
|
for (let i = 0; i < selectedPackages.length; i++) {
|
|
222
332
|
const pkg = selectedPackages[i];
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
required: true
|
|
333
|
+
const picked = await (0, picker_1.multiselectPicker)({
|
|
334
|
+
title: `[${i + 1}/${selectedPackages.length}] ${pkg.name} — select artifacts`,
|
|
335
|
+
items: (0, registry_view_1.artifactPickerItems)(pkg),
|
|
336
|
+
initialSelected: [registry_view_1.ALL_SENTINEL, ...pkg.artifacts.map((a) => (0, registry_view_1.artifactValue)(a))],
|
|
228
337
|
});
|
|
229
|
-
|
|
230
|
-
|
|
338
|
+
if (picked === null) {
|
|
339
|
+
(0, prompts_1.outro)('Operation cancelled.');
|
|
340
|
+
process.exit(0);
|
|
341
|
+
}
|
|
342
|
+
for (const a of (0, registry_view_1.resolveLevel2Selection)(pkg, picked)) {
|
|
231
343
|
dedup.set((0, registry_view_1.artifactValue)(a), a);
|
|
232
344
|
}
|
|
233
345
|
}
|
|
@@ -469,6 +581,7 @@ program.command('list [package]')
|
|
|
469
581
|
if (packageName && options.all) {
|
|
470
582
|
console.log(picocolors_1.default.dim('(Ignoring --all when a package name is provided.)'));
|
|
471
583
|
}
|
|
584
|
+
const listWidth = process.stdout.isTTY ? process.stdout.columns : undefined;
|
|
472
585
|
// Detail for a single package.
|
|
473
586
|
if (packageName) {
|
|
474
587
|
const { match, suggestion } = (0, registry_view_1.findPackage)(fullView, packageName);
|
|
@@ -480,7 +593,7 @@ program.command('list [package]')
|
|
|
480
593
|
process.exit(1);
|
|
481
594
|
}
|
|
482
595
|
console.log();
|
|
483
|
-
for (const line of (0, registry_view_1.packageDetailLines)(match))
|
|
596
|
+
for (const line of (0, registry_view_1.packageDetailLines)(match, listWidth))
|
|
484
597
|
console.log(line);
|
|
485
598
|
console.log();
|
|
486
599
|
(0, prompts_1.outro)(`Run ${picocolors_1.default.green(`awm add`)} to install artifacts from ${picocolors_1.default.cyan(match.name)}.`);
|
|
@@ -490,7 +603,7 @@ program.command('list [package]')
|
|
|
490
603
|
if (options.all) {
|
|
491
604
|
for (const pkg of view) {
|
|
492
605
|
console.log();
|
|
493
|
-
for (const line of (0, registry_view_1.packageDetailLines)(pkg))
|
|
606
|
+
for (const line of (0, registry_view_1.packageDetailLines)(pkg, listWidth))
|
|
494
607
|
console.log(line);
|
|
495
608
|
}
|
|
496
609
|
console.log();
|
|
@@ -499,7 +612,7 @@ program.command('list [package]')
|
|
|
499
612
|
}
|
|
500
613
|
// Default: compact summary.
|
|
501
614
|
console.log();
|
|
502
|
-
for (const line of (0, registry_view_1.packageSummaryLines)(view))
|
|
615
|
+
for (const line of (0, registry_view_1.packageSummaryLines)(view, listWidth))
|
|
503
616
|
console.log(line);
|
|
504
617
|
console.log();
|
|
505
618
|
console.log(picocolors_1.default.dim(` awm list <pkg> · awm list --all`));
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GIT_LOG_FORMAT = exports.US = exports.RS = exports.PKG_NAME = void 0;
|
|
4
|
+
exports.parseCommits = parseCommits;
|
|
5
|
+
exports.determineBump = determineBump;
|
|
6
|
+
exports.nextVersion = nextVersion;
|
|
7
|
+
exports.selectFloor = selectFloor;
|
|
8
|
+
exports.renderChangelog = renderChangelog;
|
|
9
|
+
const versioning_1 = require("../core/versioning");
|
|
10
|
+
exports.PKG_NAME = 'agentic-workflow-manager';
|
|
11
|
+
exports.RS = '\x1e';
|
|
12
|
+
exports.US = '\x1f';
|
|
13
|
+
exports.GIT_LOG_FORMAT = `%s${exports.US}%b${exports.RS}`;
|
|
14
|
+
const SEMVER_RE = /^(\d+)\.(\d+)\.(\d+)$/;
|
|
15
|
+
const HEADER_RE = /^(\w+)(?:\(([^)]+)\))?(!)?:\s+(.+)$/;
|
|
16
|
+
function parseOne(record) {
|
|
17
|
+
const [header, ...rest] = record.split(exports.US);
|
|
18
|
+
const body = rest.join(exports.US);
|
|
19
|
+
const m = HEADER_RE.exec(header.trim());
|
|
20
|
+
if (!m)
|
|
21
|
+
return null;
|
|
22
|
+
return {
|
|
23
|
+
type: m[1],
|
|
24
|
+
scope: m[2] ?? null,
|
|
25
|
+
breaking: Boolean(m[3]) || /^BREAKING CHANGE:/m.test(body),
|
|
26
|
+
subject: m[4].trim(),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function parseCommits(raw) {
|
|
30
|
+
return raw
|
|
31
|
+
.split(exports.RS)
|
|
32
|
+
.map((r) => r.trim())
|
|
33
|
+
.filter(Boolean)
|
|
34
|
+
.map(parseOne)
|
|
35
|
+
.filter((c) => c !== null);
|
|
36
|
+
}
|
|
37
|
+
function determineBump(commits) {
|
|
38
|
+
if (commits.some((c) => c.breaking))
|
|
39
|
+
return 'major';
|
|
40
|
+
if (commits.some((c) => c.type === 'feat'))
|
|
41
|
+
return 'minor';
|
|
42
|
+
if (commits.some((c) => c.type === 'fix' || c.type === 'perf'))
|
|
43
|
+
return 'patch';
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
function nextVersion(base, bump) {
|
|
47
|
+
const m = SEMVER_RE.exec((base ?? '').trim());
|
|
48
|
+
if (!m)
|
|
49
|
+
throw new Error(`Invalid base version: "${base}"`);
|
|
50
|
+
const [maj, min, pat] = [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
51
|
+
if (bump === 'major')
|
|
52
|
+
return `${maj + 1}.0.0`;
|
|
53
|
+
if (bump === 'minor')
|
|
54
|
+
return `${maj}.${min + 1}.0`;
|
|
55
|
+
return `${maj}.${min}.${pat + 1}`;
|
|
56
|
+
}
|
|
57
|
+
function selectFloor(current, lastTagVersion) {
|
|
58
|
+
if (!SEMVER_RE.test((current ?? '').trim())) {
|
|
59
|
+
throw new Error(`Invalid current version: "${current}"`);
|
|
60
|
+
}
|
|
61
|
+
const cur = current.trim();
|
|
62
|
+
if (!lastTagVersion)
|
|
63
|
+
return cur;
|
|
64
|
+
return (0, versioning_1.compareSemver)(cur, lastTagVersion) >= 0 ? cur : lastTagVersion;
|
|
65
|
+
}
|
|
66
|
+
function line(c) {
|
|
67
|
+
return c.scope ? `- **${c.scope}:** ${c.subject}` : `- ${c.subject}`;
|
|
68
|
+
}
|
|
69
|
+
function renderChangelog(version, dateISO, commits) {
|
|
70
|
+
const sections = [`## v${version} - ${dateISO}`, ''];
|
|
71
|
+
const groups = [
|
|
72
|
+
['Breaking Changes', (c) => c.breaking],
|
|
73
|
+
['Features', (c) => !c.breaking && c.type === 'feat'],
|
|
74
|
+
['Fixes', (c) => !c.breaking && (c.type === 'fix' || c.type === 'perf')],
|
|
75
|
+
];
|
|
76
|
+
for (const [title, pred] of groups) {
|
|
77
|
+
const items = commits.filter(pred);
|
|
78
|
+
if (items.length === 0)
|
|
79
|
+
continue;
|
|
80
|
+
sections.push(`### ${title}`, ...items.map(line), '');
|
|
81
|
+
}
|
|
82
|
+
return sections.join('\n').trimEnd() + '\n';
|
|
83
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.parseArgs = parseArgs;
|
|
7
|
+
exports.main = main;
|
|
8
|
+
// cli/src/release/index.ts
|
|
9
|
+
const child_process_1 = require("child_process");
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const orchestrator_1 = require("./orchestrator");
|
|
13
|
+
function parseArgs(argv) {
|
|
14
|
+
const opts = { dryRun: false, force: null, push: true, branch: 'main', cliDir: '' };
|
|
15
|
+
for (let i = 0; i < argv.length; i++) {
|
|
16
|
+
const a = argv[i];
|
|
17
|
+
if (a === '--dry-run')
|
|
18
|
+
opts.dryRun = true;
|
|
19
|
+
else if (a === '--no-push')
|
|
20
|
+
opts.push = false;
|
|
21
|
+
else if (a === '--branch') {
|
|
22
|
+
const val = argv[++i];
|
|
23
|
+
if (val === undefined || val.startsWith('--')) {
|
|
24
|
+
throw new Error('--branch requiere un valor (ej: --branch main)');
|
|
25
|
+
}
|
|
26
|
+
opts.branch = val;
|
|
27
|
+
}
|
|
28
|
+
else if (a === '--force') {
|
|
29
|
+
const lvl = argv[++i];
|
|
30
|
+
if (lvl !== 'major' && lvl !== 'minor' && lvl !== 'patch') {
|
|
31
|
+
throw new Error(`--force requiere major|minor|patch, recibió "${lvl}"`);
|
|
32
|
+
}
|
|
33
|
+
opts.force = lvl;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
throw new Error(`Flag desconocida: ${a}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return opts;
|
|
40
|
+
}
|
|
41
|
+
function realIO(repoRoot, cliDir) {
|
|
42
|
+
const pkgPath = path_1.default.join(cliDir, 'package.json');
|
|
43
|
+
const changelogPath = path_1.default.join(repoRoot, 'CHANGELOG.md');
|
|
44
|
+
const npmrcPath = path_1.default.join(cliDir, '.npmrc');
|
|
45
|
+
return {
|
|
46
|
+
run(cmd, args, o) {
|
|
47
|
+
return (0, child_process_1.execFileSync)(cmd, args, { cwd: o?.cwd ?? repoRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
48
|
+
},
|
|
49
|
+
readPackageVersion: () => JSON.parse(fs_1.default.readFileSync(pkgPath, 'utf8')).version,
|
|
50
|
+
writePackageVersion: (v) => {
|
|
51
|
+
const pkg = JSON.parse(fs_1.default.readFileSync(pkgPath, 'utf8'));
|
|
52
|
+
pkg.version = v;
|
|
53
|
+
fs_1.default.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
54
|
+
},
|
|
55
|
+
readChangelog: () => (fs_1.default.existsSync(changelogPath) ? fs_1.default.readFileSync(changelogPath, 'utf8') : ''),
|
|
56
|
+
writeChangelog: (c) => fs_1.default.writeFileSync(changelogPath, c),
|
|
57
|
+
writeNpmrc: (token) => fs_1.default.writeFileSync(npmrcPath, `//registry.npmjs.org/:_authToken=${token}\n`),
|
|
58
|
+
removeNpmrc: () => { if (fs_1.default.existsSync(npmrcPath))
|
|
59
|
+
fs_1.default.rmSync(npmrcPath); },
|
|
60
|
+
today: () => new Date().toISOString().slice(0, 10),
|
|
61
|
+
log: (m) => console.log(m),
|
|
62
|
+
env: process.env,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function main(argv) {
|
|
66
|
+
let opts;
|
|
67
|
+
try {
|
|
68
|
+
opts = parseArgs(argv);
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
console.error(e.message);
|
|
72
|
+
return 2;
|
|
73
|
+
}
|
|
74
|
+
// __dirname en dist/src/release/ → 4 niveles arriba = repoRoot
|
|
75
|
+
// dist/src/release → dist/src → dist → cli → repoRoot
|
|
76
|
+
const repoRoot = path_1.default.resolve(__dirname, '..', '..', '..', '..');
|
|
77
|
+
const cliDir = path_1.default.resolve(repoRoot, 'cli');
|
|
78
|
+
opts.cliDir = cliDir;
|
|
79
|
+
try {
|
|
80
|
+
const res = (0, orchestrator_1.release)(opts, realIO(repoRoot, cliDir));
|
|
81
|
+
if (res.released)
|
|
82
|
+
console.log(`✓ Publicado v${res.version}`);
|
|
83
|
+
else
|
|
84
|
+
console.log(`· Sin release: ${res.reason}${res.version ? ` (v${res.version})` : ''}`);
|
|
85
|
+
return 0;
|
|
86
|
+
}
|
|
87
|
+
catch (e) {
|
|
88
|
+
console.error(`✗ ${e.message}`);
|
|
89
|
+
return 1;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (require.main === module) {
|
|
93
|
+
process.exit(main(process.argv.slice(2)));
|
|
94
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.release = release;
|
|
4
|
+
// cli/src/release/orchestrator.ts
|
|
5
|
+
const core_1 = require("./core");
|
|
6
|
+
const TAG_RE = /^v(\d+)\.(\d+)\.(\d+)$/;
|
|
7
|
+
function highestTag(io) {
|
|
8
|
+
const raw = io.run('git', ['tag', '--list', 'v*']).trim();
|
|
9
|
+
const tags = raw.split('\n').map((t) => t.trim()).filter((t) => TAG_RE.test(t));
|
|
10
|
+
if (tags.length === 0)
|
|
11
|
+
return null;
|
|
12
|
+
tags.sort((a, b) => {
|
|
13
|
+
const [, a1, a2, a3] = TAG_RE.exec(a).map(Number);
|
|
14
|
+
const [, b1, b2, b3] = TAG_RE.exec(b).map(Number);
|
|
15
|
+
return a1 - b1 || a2 - b2 || a3 - b3;
|
|
16
|
+
});
|
|
17
|
+
return tags[tags.length - 1];
|
|
18
|
+
}
|
|
19
|
+
function release(opts, io) {
|
|
20
|
+
// --- preflight gates (contrato, antes de cualquier early-exit) ---
|
|
21
|
+
if (!opts.force) {
|
|
22
|
+
const branch = io.run('git', ['rev-parse', '--abbrev-ref', 'HEAD']).trim();
|
|
23
|
+
if (branch !== opts.branch) {
|
|
24
|
+
throw new Error(`Rama actual "${branch}" != esperada "${opts.branch}" (usá --force para relajar)`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (!opts.dryRun) {
|
|
28
|
+
const dirty = io.run('git', ['status', '--porcelain']).trim();
|
|
29
|
+
if (dirty)
|
|
30
|
+
throw new Error('Working tree con cambios sin commitear — abortando');
|
|
31
|
+
if (!io.env.NPM_TOKEN && !io.env.NODE_AUTH_TOKEN && !io.env.ACTIONS_ID_TOKEN_REQUEST_URL) {
|
|
32
|
+
throw new Error('Falta credencial de publicación: configurá NPM_TOKEN, NODE_AUTH_TOKEN, o usa OIDC (id-token: write en GitHub Actions)');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// baseline
|
|
36
|
+
const current = io.readPackageVersion();
|
|
37
|
+
const lastTag = highestTag(io); // "vX.Y.Z" | null
|
|
38
|
+
const lastTagVersion = lastTag ? lastTag.slice(1) : null;
|
|
39
|
+
const floor = (0, core_1.selectFloor)(current, lastTagVersion);
|
|
40
|
+
// rango de commits (solo cli/)
|
|
41
|
+
const range = lastTag ? [`${lastTag}..HEAD`] : [];
|
|
42
|
+
const logArgs = ['log', ...range, '--no-merges', `--format=${core_1.GIT_LOG_FORMAT}`, '--', 'cli/'];
|
|
43
|
+
const commits = (0, core_1.parseCommits)(io.run('git', logArgs));
|
|
44
|
+
// bump
|
|
45
|
+
const bump = opts.force ?? (0, core_1.determineBump)(commits);
|
|
46
|
+
if (!bump)
|
|
47
|
+
return { released: false, reason: 'nada que publicar (no releasable commits)' };
|
|
48
|
+
const version = (0, core_1.nextVersion)(floor, bump);
|
|
49
|
+
// GATE idempotencia
|
|
50
|
+
if (io.run('git', ['tag', '--list', `v${version}`]).trim()) {
|
|
51
|
+
throw new Error(`El tag v${version} ya existe — abortando`);
|
|
52
|
+
}
|
|
53
|
+
let published = '';
|
|
54
|
+
try {
|
|
55
|
+
published = io.run('npm', ['view', `${core_1.PKG_NAME}@${version}`, 'version']).trim();
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
published = '';
|
|
59
|
+
}
|
|
60
|
+
if (published)
|
|
61
|
+
throw new Error(`${core_1.PKG_NAME}@${version} ya está publicado en npm — abortando`);
|
|
62
|
+
if (opts.dryRun) {
|
|
63
|
+
io.log(`[dry-run] publicaría v${version} (bump=${bump}, base=${floor})`);
|
|
64
|
+
return { released: false, version, reason: 'dry-run' };
|
|
65
|
+
}
|
|
66
|
+
// aplicar
|
|
67
|
+
io.writePackageVersion(version);
|
|
68
|
+
const section = (0, core_1.renderChangelog)(version, io.today(), commits);
|
|
69
|
+
io.writeChangelog(section + '\n' + io.readChangelog());
|
|
70
|
+
io.run('git', ['add', 'cli/package.json', 'CHANGELOG.md']);
|
|
71
|
+
io.run('git', ['commit', '-m', `chore(release): v${version} [skip ci]`]);
|
|
72
|
+
io.run('git', ['tag', '-a', `v${version}`, '-m', `v${version}`]);
|
|
73
|
+
// OIDC mode: GitHub inyecta ACTIONS_ID_TOKEN_REQUEST_URL con id-token:write
|
|
74
|
+
// o NODE_AUTH_TOKEN sin NPM_TOKEN (setup-node legacy con OIDC)
|
|
75
|
+
// Legacy mode (NPM_TOKEN): write .npmrc temporal, cleanup en finally
|
|
76
|
+
const useOidc = !io.env.NPM_TOKEN &&
|
|
77
|
+
(!!io.env.ACTIONS_ID_TOKEN_REQUEST_URL || !!io.env.NODE_AUTH_TOKEN);
|
|
78
|
+
let publishError;
|
|
79
|
+
if (useOidc) {
|
|
80
|
+
try {
|
|
81
|
+
io.run('npm', ['publish', '--provenance'], { cwd: opts.cliDir });
|
|
82
|
+
}
|
|
83
|
+
catch (e) {
|
|
84
|
+
publishError = e;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
const token = io.env.NPM_TOKEN;
|
|
89
|
+
try {
|
|
90
|
+
io.writeNpmrc(token);
|
|
91
|
+
io.run('npm', ['publish'], { cwd: opts.cliDir });
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
publishError = e;
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
io.removeNpmrc();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (publishError) {
|
|
101
|
+
try {
|
|
102
|
+
io.run('git', ['tag', '-d', `v${version}`]);
|
|
103
|
+
}
|
|
104
|
+
catch { /* best effort */ }
|
|
105
|
+
try {
|
|
106
|
+
io.run('git', ['reset', '--hard', 'HEAD~1']);
|
|
107
|
+
}
|
|
108
|
+
catch { /* best effort */ }
|
|
109
|
+
throw publishError;
|
|
110
|
+
}
|
|
111
|
+
if (opts.push) {
|
|
112
|
+
io.run('git', ['push', 'origin', opts.branch]);
|
|
113
|
+
io.run('git', ['push', 'origin', `v${version}`]);
|
|
114
|
+
}
|
|
115
|
+
return { released: true, version };
|
|
116
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.TWO_PANE_MIN_COLUMNS = void 0;
|
|
7
|
+
exports.visibleItems = visibleItems;
|
|
8
|
+
exports.renderPicker = renderPicker;
|
|
9
|
+
const picocolors_1 = __importDefault(require("picocolors"));
|
|
10
|
+
const text_1 = require("./text");
|
|
11
|
+
exports.TWO_PANE_MIN_COLUMNS = 72;
|
|
12
|
+
const FOOTER = '↑↓ move · space select · tab all · ⏎ confirm · esc clear';
|
|
13
|
+
function visibleItems(state) {
|
|
14
|
+
if (!state.filter)
|
|
15
|
+
return state.items;
|
|
16
|
+
const q = state.filter.toLowerCase();
|
|
17
|
+
return state.items.filter((i) => i.label.toLowerCase().includes(q));
|
|
18
|
+
}
|
|
19
|
+
/** Compute a scroll window [start, end) around the cursor that fits `height` rows. */
|
|
20
|
+
function windowFor(cursor, total, height) {
|
|
21
|
+
if (total <= height)
|
|
22
|
+
return [0, total];
|
|
23
|
+
let start = cursor - Math.floor(height / 2);
|
|
24
|
+
start = Math.max(0, Math.min(start, total - height));
|
|
25
|
+
return [start, start + height];
|
|
26
|
+
}
|
|
27
|
+
function checkbox(selected) {
|
|
28
|
+
return selected ? picocolors_1.default.green('◼') : '◻';
|
|
29
|
+
}
|
|
30
|
+
function renderPicker(state, vp) {
|
|
31
|
+
const vis = visibleItems(state);
|
|
32
|
+
const cursor = Math.max(0, Math.min(state.cursor, vis.length - 1));
|
|
33
|
+
const selCount = state.selected.size;
|
|
34
|
+
const filterPart = state.filter ? ` filter: ${state.filter}` : '';
|
|
35
|
+
const header = picocolors_1.default.bold(state.title) + filterPart + (selCount ? picocolors_1.default.dim(` ${selCount} sel`) : '');
|
|
36
|
+
const out = [header];
|
|
37
|
+
if (vis.length === 0) {
|
|
38
|
+
out.push(picocolors_1.default.dim(' (no matches)'));
|
|
39
|
+
out.push(FOOTER);
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
const bodyHeight = Math.max(3, vp.rows - 4); // header + footer + breathing room
|
|
43
|
+
const [start, end] = windowFor(cursor, vis.length, bodyHeight);
|
|
44
|
+
const twoPane = vp.columns >= exports.TWO_PANE_MIN_COLUMNS;
|
|
45
|
+
const listWidth = twoPane ? Math.floor(vp.columns * 0.5) : vp.columns - 2;
|
|
46
|
+
const rows = [];
|
|
47
|
+
for (let i = start; i < end; i++) {
|
|
48
|
+
const item = vis[i];
|
|
49
|
+
const isCursor = i === cursor;
|
|
50
|
+
const marker = isCursor ? picocolors_1.default.cyan('❯') : ' ';
|
|
51
|
+
const box = checkbox(state.selected.has(item.value));
|
|
52
|
+
const labelWidth = listWidth - 5; // wide cursor marker (2 cells) + space + checkbox + space
|
|
53
|
+
const label = (0, text_1.truncate)(item.label, labelWidth);
|
|
54
|
+
rows.push(`${marker} ${box} ${label}`);
|
|
55
|
+
}
|
|
56
|
+
if (twoPane) {
|
|
57
|
+
const detailWidth = vp.columns - listWidth - 3; // gutter " │ "
|
|
58
|
+
const detail = (0, text_1.wrap)(vis[cursor].description, detailWidth).slice(0, rows.length);
|
|
59
|
+
const height = Math.max(rows.length, detail.length);
|
|
60
|
+
for (let i = 0; i < height; i++) {
|
|
61
|
+
const leftPadded = padToWidth(rows[i] ?? '', listWidth);
|
|
62
|
+
const right = detail[i] ?? '';
|
|
63
|
+
out.push(`${leftPadded} ${picocolors_1.default.dim('│')} ${picocolors_1.default.dim(right)}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
out.push(...rows);
|
|
68
|
+
out.push(picocolors_1.default.dim(' ' + ((0, text_1.wrap)(vis[cursor].description, vp.columns - 4)[0] ?? '')));
|
|
69
|
+
}
|
|
70
|
+
out.push(FOOTER);
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
/** Pad a possibly-colored string to `width` visible cells. */
|
|
74
|
+
function padToWidth(s, width) {
|
|
75
|
+
const pad = width - (0, text_1.displayWidth)(s);
|
|
76
|
+
return pad > 0 ? s + ' '.repeat(pad) : s;
|
|
77
|
+
}
|