@agentage/cli 0.45.0 → 0.47.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/README.md +2 -0
- package/dist/commands/extension/create.js +18 -5
- package/dist/commands/extension/release.js +37 -14
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -189,6 +189,8 @@ Releases publish to npm via GitHub Actions when a `chore(release): X.Y.Z` commit
|
|
|
189
189
|
`package.json` lands on `master` (the squash-merge subject of a release PR). The workflow skips
|
|
190
190
|
any version already on npm.
|
|
191
191
|
|
|
192
|
+
An extension release (`agentage extension release`) skips npm for a package with `"private": true`: it pushes the images, attaches the record and prints `PASS npm: private package, not published`.
|
|
193
|
+
|
|
192
194
|
## License
|
|
193
195
|
|
|
194
196
|
MIT - see [LICENSE](./LICENSE).
|
|
@@ -76,13 +76,28 @@ const renameLock = (dir, id) => {
|
|
|
76
76
|
Object.assign(at, { name });
|
|
77
77
|
save(join(dir, LOCK), lock);
|
|
78
78
|
};
|
|
79
|
+
// Bounded, a partial folder removed; git's own first line says why (no repo, no network, no access).
|
|
79
80
|
const clone = async (url, from, dir) => {
|
|
80
81
|
const env = { ...process.env, GIT_TERMINAL_PROMPT: '0' };
|
|
81
|
-
await run('git', ['clone', '-q', '--depth', '1', url, dir], { env }).catch(() => {
|
|
82
|
-
|
|
82
|
+
await run('git', ['clone', '-q', '--depth', '1', url, dir], { env, timeout: 120_000 }).catch((err) => {
|
|
83
|
+
rmSync(dir, { recursive: true, force: true });
|
|
84
|
+
const why = (err.stderr?.trim() || err.message).split('\n')[0];
|
|
85
|
+
throw new Error(`cannot clone template ${from}: ${why}`);
|
|
83
86
|
});
|
|
84
87
|
rmSync(join(dir, '.git'), { recursive: true, force: true });
|
|
85
88
|
};
|
|
89
|
+
// A half-made folder is removed, so a re-run is not refused as "exists".
|
|
90
|
+
const shape = (dir, from, id, kept) => {
|
|
91
|
+
try {
|
|
92
|
+
drop(dir, kept);
|
|
93
|
+
rewrite(dir, id);
|
|
94
|
+
renameLock(dir, id);
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
rmSync(dir, { recursive: true, force: true });
|
|
98
|
+
throw new Error(`template ${from}: ${err.message}`);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
86
101
|
// Clones the template's default branch into ./extension-<id> as a new working copy, no history.
|
|
87
102
|
export const runExtensionCreate = async (opts, url = (from) => `https://github.com/${from}`) => {
|
|
88
103
|
const { id, from = TEMPLATE } = opts;
|
|
@@ -92,9 +107,7 @@ export const runExtensionCreate = async (opts, url = (from) => `https://github.c
|
|
|
92
107
|
throw new Error(refused);
|
|
93
108
|
const dir = `extension-${id}`;
|
|
94
109
|
await clone(url(from), from, dir);
|
|
95
|
-
|
|
96
|
-
rewrite(dir, id);
|
|
97
|
-
renameLock(dir, id);
|
|
110
|
+
shape(dir, from, id, kept);
|
|
98
111
|
const kit = String(json(join(dir, 'package.json'))['dependencies']?.['@agentage/extension'] ?? '?');
|
|
99
112
|
console.log(` template ${from} (default branch, kit ${kit.replace(/^[\^~]/, '')})`);
|
|
100
113
|
console.log(` kept ${kept.join(', ')}`);
|
|
@@ -39,17 +39,35 @@ const tokenConfig = async (token) => {
|
|
|
39
39
|
await writeFile(file, '//registry.npmjs.org/:_authToken=${NPM_TOKEN}\n');
|
|
40
40
|
return ['--userconfig', file];
|
|
41
41
|
};
|
|
42
|
+
// Only E404 means "not on npm"; a registry that cannot answer stops the release, never publishes.
|
|
43
|
+
const onNpm = async (run, args, what) => {
|
|
44
|
+
const viewed = await run('npm', ['view', ...args]);
|
|
45
|
+
if (viewed.ok)
|
|
46
|
+
return viewed.out.trim() !== '';
|
|
47
|
+
if (!/E404/.test(viewed.err))
|
|
48
|
+
fail(`npm: cannot tell whether ${what} is on npm: ${viewed.err}`);
|
|
49
|
+
return false;
|
|
50
|
+
};
|
|
51
|
+
// The upload with provenance signs first: its own bound, longer than the checks.
|
|
52
|
+
const PUBLISH_SECONDS = 300;
|
|
53
|
+
// A killed publish may have landed: stop, never stage it again; a re-run reads "already on npm".
|
|
54
|
+
const direct = async (push, spec, flags) => {
|
|
55
|
+
const published = await push('npm', ['publish', ...flags]);
|
|
56
|
+
if (/timed out after/.test(published.err))
|
|
57
|
+
fail(`npm: publish of ${spec} ${published.err}; re-run the release: it reads "already on npm" if the upload landed`);
|
|
58
|
+
return published.ok;
|
|
59
|
+
};
|
|
42
60
|
// OIDC trusted publishing; a new name (NPM_TOKEN set) or an unauthorized direct publish is staged.
|
|
43
|
-
const publish = async (run, { name, version }, token) => {
|
|
61
|
+
const publish = async (run, push, { name, version }, token) => {
|
|
44
62
|
const cfg = await tokenConfig(token);
|
|
45
63
|
const spec = `${name}@${version}`;
|
|
46
|
-
if (
|
|
64
|
+
if (await onNpm(run, [spec, 'version', ...cfg], spec))
|
|
47
65
|
return step(`npm: ${spec} is already on npm`);
|
|
48
66
|
const flags = ['--access', 'public', '--provenance', '--ignore-scripts', ...cfg];
|
|
49
|
-
const fresh = token !== undefined && !(await run
|
|
50
|
-
if (!fresh && (await
|
|
67
|
+
const fresh = token !== undefined && !(await onNpm(run, [name, 'name', ...cfg], name));
|
|
68
|
+
if (!fresh && (await direct(push, spec, flags)))
|
|
51
69
|
return step(`npm: ${spec} published`);
|
|
52
|
-
const staged = await
|
|
70
|
+
const staged = await push('npm', ['stage', 'publish', ...flags]);
|
|
53
71
|
if (!staged.ok)
|
|
54
72
|
fail(`npm: ${staged.err}`);
|
|
55
73
|
const why = fresh
|
|
@@ -64,7 +82,7 @@ const npmVersion = async (run) => {
|
|
|
64
82
|
fail(`npm: npm ${v} cannot publish with OIDC: needs npm >= 11.5.1`);
|
|
65
83
|
step(`npm: npm ${v}`);
|
|
66
84
|
};
|
|
67
|
-
const preflight = async (run, pkg, tag, sha) => {
|
|
85
|
+
const preflight = async (run, pkg, tag, sha, isPrivate) => {
|
|
68
86
|
if (pkg.agentage.id === 'template')
|
|
69
87
|
fail('id: this repo is the starting point for extensions: create one, release that');
|
|
70
88
|
step(`id: ${pkg.agentage.id} is not the template`);
|
|
@@ -76,7 +94,8 @@ const preflight = async (run, pkg, tag, sha) => {
|
|
|
76
94
|
if (tag !== v)
|
|
77
95
|
fail(`tag: ${tag} is not ${v}, the package.json version; tag the commit that sets it`);
|
|
78
96
|
step(`tag: ${tag} is the package.json version`);
|
|
79
|
-
|
|
97
|
+
if (!isPrivate)
|
|
98
|
+
await npmVersion(run);
|
|
80
99
|
};
|
|
81
100
|
// One line per step, PASS or FAIL; any refusal exits 1 before anything is pushed or published.
|
|
82
101
|
export const runExtensionRelease = async ({ dir = '.', timeout = 60 } = {}, deps = defaultDeps) => {
|
|
@@ -86,25 +105,29 @@ export const runExtensionRelease = async ({ dir = '.', timeout = 60 } = {}, deps
|
|
|
86
105
|
return fail(`inputs: ${missing} is not set; run it on a tag push`);
|
|
87
106
|
const secrets = takeSecrets(deps.env);
|
|
88
107
|
const pkg = await (await deps.tool()).readManifest(dir);
|
|
89
|
-
const run = scoped(deps.exec(dir), secrets);
|
|
90
|
-
|
|
108
|
+
const run = scoped(deps.exec(dir, timeout), secrets);
|
|
109
|
+
const isPrivate = pkg['private'] === true;
|
|
110
|
+
await preflight(run, pkg, tag, sha, isPrivate);
|
|
91
111
|
const { id } = pkg.agentage;
|
|
92
112
|
const g = { dir, tag, sha, repo, id, version: pkg.version, parts: imageParts(pkg) };
|
|
93
|
-
const
|
|
94
|
-
const decision = await releaseGuard(g, bounded).catch((e) => fail(`guard: ${e.message}`));
|
|
113
|
+
const decision = await releaseGuard(g, run).catch((e) => fail(`guard: ${e.message}`));
|
|
95
114
|
const build = decision === 'build';
|
|
96
|
-
|
|
115
|
+
const done = isPrivate ? 'is released' : 'is released: npm only';
|
|
116
|
+
step(`guard: ${tag} ${build ? 'has no record and no image: build' : done}`);
|
|
97
117
|
if (build) {
|
|
98
118
|
await runExtensionBuild({ push: true, dir }, deps.tool);
|
|
99
119
|
await attach(run, tag, repo);
|
|
100
120
|
}
|
|
101
|
-
|
|
121
|
+
if (isPrivate)
|
|
122
|
+
return step('npm: private package, not published');
|
|
123
|
+
const push = scoped(deps.exec(dir, PUBLISH_SECONDS), secrets);
|
|
124
|
+
await publish(run, push, pkg, secrets['npm']?.['NPM_TOKEN']);
|
|
102
125
|
};
|
|
103
126
|
export const addRelease = (group, release) => {
|
|
104
127
|
group
|
|
105
128
|
.command('release')
|
|
106
129
|
.description('Release an extension from its v* tag in CI: guard, images, record, npm package')
|
|
107
130
|
.option('--dir <path>', 'the extension package folder (default: the current folder)')
|
|
108
|
-
.option('--timeout <seconds>', 'bound on each GitHub and
|
|
131
|
+
.option('--timeout <seconds>', 'bound on each git, GitHub and npm check (default: 60; npm publish: 300)', Number)
|
|
109
132
|
.action(release);
|
|
110
133
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentage/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.47.0",
|
|
4
4
|
"description": "The agentage CLI - connect this machine to agentage from the terminal",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"prepublishOnly": "npm run verify"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@agentage/extension": "^0.
|
|
58
|
+
"@agentage/extension": "^0.6.0",
|
|
59
59
|
"chalk": "^6.0.0",
|
|
60
60
|
"commander": "^15.0.0",
|
|
61
61
|
"open": "^11.0.0"
|