@lesliechan721/aw 0.1.0-beta.4 → 0.1.0-beta.6
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/authoring/scripts/lib/plugin-version-check.d.mts +14 -0
- package/dist/authoring/scripts/lib/plugin-version-check.mjs +10 -224
- package/dist/authoring/scripts/lib/plugin-version-check.mjs.map +1 -1
- package/dist/authoring/scripts/remove-plugin.d.mts +2 -0
- package/dist/authoring/scripts/remove-plugin.mjs +366 -0
- package/dist/authoring/scripts/remove-plugin.mjs.map +1 -0
- package/dist/authoring/scripts/validate-plugin.mjs +9 -46
- package/dist/authoring/scripts/validate-plugin.mjs.map +1 -1
- package/dist/scripts/bundle-plugins.d.ts +0 -2
- package/dist/scripts/bundle-plugins.js +4 -2
- package/dist/scripts/bundle-plugins.js.map +1 -1
- package/dist/scripts/plugin-staging.d.ts +1 -16
- package/dist/scripts/plugin-staging.js +8 -115
- package/dist/scripts/plugin-staging.js.map +1 -1
- package/dist/src/authoring/workspace-commands.d.ts +9 -1
- package/dist/src/authoring/workspace-commands.js +24 -16
- package/dist/src/authoring/workspace-commands.js.map +1 -1
- package/dist/src/commands/catalog-publication-inspection.d.ts +106 -0
- package/dist/src/commands/catalog-publication-inspection.js +444 -0
- package/dist/src/commands/catalog-publication-inspection.js.map +1 -0
- package/dist/src/commands/catalog-publish.d.ts +18 -2
- package/dist/src/commands/catalog-publish.js +261 -316
- package/dist/src/commands/catalog-publish.js.map +1 -1
- package/dist/src/commands/catalog-status.d.ts +60 -0
- package/dist/src/commands/catalog-status.js +251 -0
- package/dist/src/commands/catalog-status.js.map +1 -0
- package/dist/src/commands/plugin-capabilities.d.ts +22 -0
- package/dist/src/commands/plugin-capabilities.js +143 -0
- package/dist/src/commands/plugin-capabilities.js.map +1 -0
- package/dist/src/commands/rebuild-command.d.ts +2 -0
- package/dist/src/commands/rebuild-command.js +37 -6
- package/dist/src/commands/rebuild-command.js.map +1 -1
- package/dist/src/commands/rebuild-integration.d.ts +1 -0
- package/dist/src/commands/rebuild-integration.js +55 -57
- package/dist/src/commands/rebuild-integration.js.map +1 -1
- package/dist/src/commands/status-v4.d.ts +21 -0
- package/dist/src/commands/status-v4.js +63 -2
- package/dist/src/commands/status-v4.js.map +1 -1
- package/dist/src/index.js +79 -18
- package/dist/src/index.js.map +1 -1
- package/dist/src/tui/components.d.ts +70 -0
- package/dist/src/tui/components.js +149 -6
- package/dist/src/tui/components.js.map +1 -1
- package/dist/src/tui/console-app-v4.js +1660 -628
- package/dist/src/tui/console-app-v4.js.map +1 -1
- package/dist/src/tui/controller-runtime.d.ts +5 -1
- package/dist/src/tui/controller-runtime.js +184 -33
- package/dist/src/tui/controller-runtime.js.map +1 -1
- package/dist/src/tui/controller.d.ts +35 -2
- package/dist/src/tui/controller.js +300 -39
- package/dist/src/tui/controller.js.map +1 -1
- package/dist/src/tui/diagnostics.d.ts +31 -0
- package/dist/src/tui/diagnostics.js +71 -0
- package/dist/src/tui/diagnostics.js.map +1 -0
- package/dist/src/tui/index.d.ts +5 -3
- package/dist/src/tui/index.js +3 -1
- package/dist/src/tui/index.js.map +1 -1
- package/dist/src/tui/model.d.ts +33 -4
- package/dist/src/tui/model.js +252 -17
- package/dist/src/tui/model.js.map +1 -1
- package/dist/src/tui/router.d.ts +15 -9
- package/dist/src/tui/router.js +35 -0
- package/dist/src/tui/router.js.map +1 -1
- package/dist/src/tui/theme.d.ts +18 -10
- package/dist/src/tui/theme.js +18 -10
- package/dist/src/tui/theme.js.map +1 -1
- package/dist/src/tui/viewport.d.ts +20 -0
- package/dist/src/tui/viewport.js +29 -0
- package/dist/src/tui/viewport.js.map +1 -0
- package/dist/src/utils/catalog-runtime-projection.d.ts +1 -7
- package/dist/src/utils/catalog-runtime-projection.js +2 -62
- package/dist/src/utils/catalog-runtime-projection.js.map +1 -1
- package/dist/src/utils/catalog-source-cache.d.ts +12 -11
- package/dist/src/utils/catalog-source-cache.js +20 -0
- package/dist/src/utils/catalog-source-cache.js.map +1 -1
- package/dist/src/utils/dynamic-skill-tree.js +1 -1
- package/dist/src/utils/dynamic-skill-tree.js.map +1 -1
- package/dist/src/utils/git-catalog-provider.js +1 -1
- package/dist/src/utils/git-catalog-provider.js.map +1 -1
- package/dist/src/utils/manifest.d.ts +0 -1
- package/dist/src/utils/manifest.js +0 -1
- package/dist/src/utils/manifest.js.map +1 -1
- package/dist/src/utils/plugin-config.js +28 -30
- package/dist/src/utils/plugin-config.js.map +1 -1
- package/dist/src/utils/skill-validation.d.ts +1 -1
- package/dist/src/utils/skill-validation.js +1 -1
- package/dist/src/utils/skill-validation.js.map +1 -1
- package/dist/src/utils/state-paths.d.ts +1 -0
- package/dist/src/utils/state-paths.js +5 -0
- package/dist/src/utils/state-paths.js.map +1 -1
- package/package.json +3 -4
- package/dist/src/utils/plugin-authoring-capabilities.d.ts +0 -23
- package/dist/src/utils/plugin-authoring-capabilities.js +0 -37
- package/dist/src/utils/plugin-authoring-capabilities.js.map +0 -1
- package/dist/src/utils/plugin-authoring-capabilities.json +0 -92
- package/dist/src/utils/skill-frontmatter-capabilities.d.ts +0 -201
- package/dist/src/utils/skill-frontmatter-capabilities.js +0 -5
- package/dist/src/utils/skill-frontmatter-capabilities.js.map +0 -1
- package/dist/src/utils/skill-frontmatter-capabilities.json +0 -200
|
@@ -1,108 +1,36 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
|
-
import { execFile } from 'node:child_process';
|
|
3
|
-
import os from 'node:os';
|
|
4
2
|
import path from 'node:path';
|
|
5
|
-
import { promisify } from 'node:util';
|
|
6
3
|
import fs from 'fs-extra';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
import { resolveCliPackageRoot } from '../utils/paths.js';
|
|
11
|
-
const execFileAsync = promisify(execFile);
|
|
12
|
-
const LEGACY_ATTEMPTED_MARKER = path.join('.aw', 'publish', 'attempted.json');
|
|
13
|
-
const INHERITED_GIT_AUTH_ENV = new Set([
|
|
14
|
-
'GIT_ASKPASS',
|
|
15
|
-
'GIT_HTTP_USER_AGENT',
|
|
16
|
-
'GIT_SSH',
|
|
17
|
-
'GIT_SSH_COMMAND',
|
|
18
|
-
'GIT_SSH_VARIANT',
|
|
19
|
-
'GIT_SSL_CAINFO',
|
|
20
|
-
'GIT_SSL_NO_VERIFY',
|
|
21
|
-
'GIT_TERMINAL_PROMPT',
|
|
22
|
-
]);
|
|
4
|
+
import { validatePublishedCatalogSnapshot } from '@lesliechan721/aw-plugin-core';
|
|
5
|
+
import { disposeCatalogPublicationInspection, parseRemoteRefs, prepareCatalogPublicationInspection, publicationGit, publicationGitExit, } from './catalog-publication-inspection.js';
|
|
6
|
+
export { safeRemoteIdentity, sanitizedGitEnvironment } from './catalog-publication-inspection.js';
|
|
23
7
|
function digest(value) {
|
|
24
8
|
return `sha256:${crypto.createHash('sha256').update(value).digest('hex')}`;
|
|
25
9
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const url = new URL(value);
|
|
33
|
-
url.username = '';
|
|
34
|
-
url.password = '';
|
|
35
|
-
url.search = '';
|
|
36
|
-
url.hash = '';
|
|
37
|
-
return url.toString();
|
|
38
|
-
}
|
|
39
|
-
catch {
|
|
40
|
-
// Fall through to conservative display sanitization for malformed URLs.
|
|
41
|
-
}
|
|
10
|
+
function canonicalJson(value) {
|
|
11
|
+
if (Array.isArray(value))
|
|
12
|
+
return `[${value.map(canonicalJson).join(',')}]`;
|
|
13
|
+
if (value !== null && typeof value === 'object') {
|
|
14
|
+
const record = value;
|
|
15
|
+
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(',')}}`;
|
|
42
16
|
}
|
|
43
|
-
|
|
44
|
-
return withoutQuery
|
|
45
|
-
.replace(/^([^/@\s:]+):([^/@\s]+)@/u, '')
|
|
46
|
-
.replace(/^[^@\s]+@(?=[^:/\s]+[/:])/u, '');
|
|
17
|
+
return JSON.stringify(value);
|
|
47
18
|
}
|
|
48
|
-
function
|
|
49
|
-
return
|
|
50
|
-
}
|
|
51
|
-
async function migrateLegacyAttempt(workspaceRoot, catalogVersion) {
|
|
52
|
-
const legacyPath = path.join(workspaceRoot, LEGACY_ATTEMPTED_MARKER);
|
|
53
|
-
if (!(await fs.pathExists(legacyPath)))
|
|
54
|
-
return;
|
|
55
|
-
let recordedVersion = catalogVersion;
|
|
56
|
-
try {
|
|
57
|
-
const legacy = await fs.readJson(legacyPath);
|
|
58
|
-
if (legacy.schemaVersion === 1 && typeof legacy.catalogVersion === 'string') {
|
|
59
|
-
recordedVersion = assertStrictSemVer(legacy.catalogVersion, 'legacy attempted Catalog version');
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
catch {
|
|
63
|
-
// Bind an unreadable legacy marker to the current version. A later version uses a new file.
|
|
64
|
-
}
|
|
65
|
-
const destination = attemptPath(workspaceRoot, recordedVersion);
|
|
66
|
-
await fs.ensureDir(path.dirname(destination));
|
|
67
|
-
if (await fs.pathExists(destination))
|
|
68
|
-
return;
|
|
69
|
-
await fs.rename(legacyPath, destination);
|
|
19
|
+
export function catalogPublicationDescriptorDigest(descriptor) {
|
|
20
|
+
return digest(canonicalJson(descriptor));
|
|
70
21
|
}
|
|
71
|
-
async function
|
|
72
|
-
try {
|
|
73
|
-
const result = await execFileAsync('git', [...args], {
|
|
74
|
-
cwd,
|
|
75
|
-
env,
|
|
76
|
-
encoding: 'utf8',
|
|
77
|
-
maxBuffer: 16 * 1024 * 1024,
|
|
78
|
-
});
|
|
79
|
-
return result.stdout.trim();
|
|
80
|
-
}
|
|
81
|
-
catch (error) {
|
|
82
|
-
throw new Error(label, { cause: error });
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
async function gitExit(cwd, args, env) {
|
|
86
|
-
try {
|
|
87
|
-
await execFileAsync('git', [...args], { cwd, env, encoding: 'utf8' });
|
|
88
|
-
return true;
|
|
89
|
-
}
|
|
90
|
-
catch {
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
function parseRemoteRefs(output) {
|
|
95
|
-
return new Map(output.split('\n').filter(Boolean).map((line) => {
|
|
96
|
-
const [objectId, ref] = line.split(/\s+/u);
|
|
97
|
-
return [ref, objectId];
|
|
98
|
-
}));
|
|
99
|
-
}
|
|
100
|
-
async function writeJsonAtomic(filePath, value) {
|
|
22
|
+
async function writeJsonAtomic(filePath, value, exclusive) {
|
|
101
23
|
await fs.ensureDir(path.dirname(filePath));
|
|
102
24
|
const temporary = path.join(path.dirname(filePath), `.${path.basename(filePath)}.${crypto.randomUUID()}.tmp`);
|
|
103
25
|
await fs.writeFile(temporary, `${JSON.stringify(value, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
|
|
104
26
|
try {
|
|
105
|
-
|
|
27
|
+
if (exclusive) {
|
|
28
|
+
await fs.link(temporary, filePath);
|
|
29
|
+
await fs.remove(temporary);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
await fs.rename(temporary, filePath);
|
|
33
|
+
}
|
|
106
34
|
}
|
|
107
35
|
catch (error) {
|
|
108
36
|
await fs.remove(temporary);
|
|
@@ -115,164 +43,255 @@ async function clearWorktree(repositoryRoot) {
|
|
|
115
43
|
await fs.remove(path.join(repositoryRoot, name));
|
|
116
44
|
}
|
|
117
45
|
}
|
|
118
|
-
async function
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
try {
|
|
127
|
-
await execFileAsync('tar', ['-xf', archivePath, '-C', options.outputRoot], {
|
|
128
|
-
env: tarEnv,
|
|
129
|
-
encoding: 'utf8',
|
|
130
|
-
maxBuffer: 16 * 1024 * 1024,
|
|
131
|
-
});
|
|
46
|
+
async function assertNoGitAttributes(root, relative = '') {
|
|
47
|
+
for (const entry of await fs.readdir(path.join(root, relative), { withFileTypes: true })) {
|
|
48
|
+
const child = relative ? path.posix.join(relative, entry.name) : entry.name;
|
|
49
|
+
if (entry.name === '.gitattributes') {
|
|
50
|
+
throw new Error(`Catalog publication snapshot must not contain .gitattributes (${child}).`);
|
|
51
|
+
}
|
|
52
|
+
if (entry.isDirectory())
|
|
53
|
+
await assertNoGitAttributes(root, child);
|
|
132
54
|
}
|
|
133
|
-
|
|
134
|
-
|
|
55
|
+
}
|
|
56
|
+
async function assertSnapshotBinding(snapshotRoot, descriptor) {
|
|
57
|
+
const [stats, validation, ledgerBytes] = await Promise.all([
|
|
58
|
+
fs.lstat(snapshotRoot),
|
|
59
|
+
validatePublishedCatalogSnapshot(snapshotRoot),
|
|
60
|
+
fs.readFile(path.join(snapshotRoot, 'plugin-release-ledger.json')),
|
|
61
|
+
]);
|
|
62
|
+
if (!stats.isDirectory() || stats.isSymbolicLink()) {
|
|
63
|
+
throw new Error('Prepared Catalog snapshot must be a regular local directory.');
|
|
135
64
|
}
|
|
136
|
-
|
|
137
|
-
|
|
65
|
+
const release = validation.manifest.release;
|
|
66
|
+
if (validation.manifest.catalogVersion !== descriptor.catalogVersion
|
|
67
|
+
|| validation.manifest.channel !== descriptor.channel
|
|
68
|
+
|| validation.snapshotDigest !== descriptor.snapshotDigest
|
|
69
|
+
|| release.state !== 'published'
|
|
70
|
+
|| release.sourceHead !== descriptor.sourceHead
|
|
71
|
+
|| release.contentDigest !== descriptor.contentDigest
|
|
72
|
+
|| release.planId !== descriptor.planId
|
|
73
|
+
|| digest(ledgerBytes) !== descriptor.ledgerDigest) {
|
|
74
|
+
throw new Error('Prepared Catalog descriptor, snapshot, ledger, or plan identity changed.');
|
|
138
75
|
}
|
|
76
|
+
await assertNoGitAttributes(snapshotRoot);
|
|
139
77
|
}
|
|
140
|
-
async function
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
78
|
+
async function publishCatalogPublicationPreparation(options) {
|
|
79
|
+
const { preparation: inspection, descriptor, snapshotRoot } = options;
|
|
80
|
+
const { temporaryRoot, repositoryRoot, repoRoot, sourceHead, remote, branch, branchRef, tag, tagRef, fetchUrl, pushUrl, safeFetchUrl, safePushUrl, remoteIdentityDigest, parent, attemptPath, env, } = inspection;
|
|
81
|
+
if (catalogPublicationDescriptorDigest(inspection.descriptor)
|
|
82
|
+
!== catalogPublicationDescriptorDigest(descriptor)) {
|
|
83
|
+
throw new Error('Catalog publication descriptor changed after preparation.');
|
|
84
|
+
}
|
|
85
|
+
if (!inspection.tagAvailable) {
|
|
86
|
+
throw new Error(`Remote Catalog tag ${tag} already exists. Increase the Catalog version.`);
|
|
87
|
+
}
|
|
88
|
+
if (!inspection.attemptAvailable || await fs.pathExists(attemptPath)) {
|
|
89
|
+
throw new Error(`Catalog ${descriptor.catalogVersion} already has an attempted publication record. `
|
|
90
|
+
+ 'Run aw catalog version <higher> before publishing again.');
|
|
91
|
+
}
|
|
92
|
+
if (!(await publicationGitExit(repoRoot, ['check-ignore', '-q', attemptPath], env))) {
|
|
93
|
+
throw new Error('Catalog publish state under .aw/publish must be ignored by Git. Add .aw/ to .gitignore.');
|
|
94
|
+
}
|
|
95
|
+
await assertSnapshotBinding(snapshotRoot, descriptor);
|
|
96
|
+
if (parent) {
|
|
97
|
+
await publicationGit(repositoryRoot, [
|
|
98
|
+
'checkout', '--quiet', '--detach', 'refs/aw/catalog-parent',
|
|
99
|
+
], env, 'publicationCommitFailed', 'Could not select the publication parent.');
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
await publicationGit(repositoryRoot, [
|
|
103
|
+
'checkout', '--quiet', '--orphan', 'catalog-publication',
|
|
104
|
+
], env, 'publicationCommitFailed', 'Could not create the initial Catalog publication.');
|
|
105
|
+
}
|
|
106
|
+
await clearWorktree(repositoryRoot);
|
|
107
|
+
await fs.copy(snapshotRoot, repositoryRoot, { dereference: false });
|
|
108
|
+
await publicationGit(repositoryRoot, ['add', '--force', '--all'], env, 'publicationCommitFailed', 'Could not stage the Catalog publication tree.');
|
|
109
|
+
await publicationGit(repositoryRoot, [
|
|
110
|
+
'-c', 'user.name=AW Catalog Publisher',
|
|
111
|
+
'-c', 'user.email=catalog-publisher@agent-workflows.invalid',
|
|
112
|
+
'commit', '--quiet', '-m', `Catalog ${descriptor.catalogVersion}`,
|
|
113
|
+
], env, 'publicationCommitFailed', 'Could not create the Catalog publication commit.');
|
|
114
|
+
const commit = await publicationGit(repositoryRoot, [
|
|
115
|
+
'rev-parse', 'HEAD',
|
|
116
|
+
], env, 'publicationCommitFailed', 'Could not resolve the Catalog publication commit.');
|
|
117
|
+
const parents = (await publicationGit(repositoryRoot, [
|
|
118
|
+
'show', '-s', '--format=%P', commit,
|
|
119
|
+
], env, 'publicationCommitFailed', 'Could not verify the Catalog publication parent.'))
|
|
120
|
+
.split(/\s+/u)
|
|
121
|
+
.filter(Boolean);
|
|
122
|
+
if ((parent && (parents.length !== 1 || parents[0] !== parent)) || (!parent && parents.length !== 0)) {
|
|
123
|
+
throw new Error('Catalog publication commit has an invalid parent.');
|
|
124
|
+
}
|
|
125
|
+
const verificationRoot = path.join(temporaryRoot, 'verification');
|
|
126
|
+
await fs.ensureDir(verificationRoot);
|
|
127
|
+
await publicationGit(repositoryRoot, [
|
|
128
|
+
'--work-tree', verificationRoot, 'checkout', '--quiet', commit, '--', '.',
|
|
129
|
+
], env, 'publicationCommitFailed', 'Could not verify the Catalog publication commit tree.');
|
|
130
|
+
await assertSnapshotBinding(verificationRoot, descriptor);
|
|
131
|
+
await publicationGit(repositoryRoot, [
|
|
132
|
+
'-c', 'user.name=AW Catalog Publisher',
|
|
133
|
+
'-c', 'user.email=catalog-publisher@agent-workflows.invalid',
|
|
134
|
+
'tag', '--annotate', tag, '--message', `Catalog ${descriptor.catalogVersion}`,
|
|
135
|
+
], env, 'publicationCommitFailed', 'Could not create the annotated Catalog tag.');
|
|
136
|
+
const liveHead = await publicationGit(repoRoot, [
|
|
137
|
+
'rev-parse', '--verify', 'HEAD',
|
|
138
|
+
], env, 'sourceDrift', 'Could not recheck the source Git HEAD.');
|
|
139
|
+
const liveStatus = await publicationGit(repoRoot, [
|
|
140
|
+
'status', '--porcelain=v1', '--untracked-files=all', '--ignore-submodules=none',
|
|
141
|
+
], env, 'sourceDrift', 'Could not recheck the source worktree.');
|
|
142
|
+
if (liveHead !== sourceHead || liveStatus !== '') {
|
|
143
|
+
throw new Error('Source HEAD or worktree changed before Catalog publication.');
|
|
144
|
+
}
|
|
145
|
+
let liveFetchUrl;
|
|
146
|
+
let livePushUrl;
|
|
147
|
+
if (descriptor.remote.name === null) {
|
|
148
|
+
liveFetchUrl = fetchUrl;
|
|
149
|
+
livePushUrl = pushUrl;
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
[liveFetchUrl, livePushUrl] = await Promise.all([
|
|
153
|
+
publicationGit(repoRoot, ['remote', 'get-url', descriptor.remote.name], env, 'remoteDrift', 'Could not recheck the Catalog publication remote configuration.'),
|
|
154
|
+
publicationGit(repoRoot, ['remote', 'get-url', '--push', descriptor.remote.name], env, 'remoteDrift', 'Could not recheck the Catalog publication remote configuration.'),
|
|
155
|
+
]);
|
|
156
|
+
}
|
|
157
|
+
if (digest(JSON.stringify({ fetchUrl: liveFetchUrl, pushUrl: livePushUrl })) !== remoteIdentityDigest) {
|
|
158
|
+
throw new Error('Catalog remote configuration changed during preparation.');
|
|
159
|
+
}
|
|
160
|
+
const liveRefs = parseRemoteRefs(await publicationGit(repositoryRoot, [
|
|
161
|
+
'ls-remote', 'publish', branchRef, tagRef, `${tagRef}^{}`,
|
|
162
|
+
], env, 'remoteDrift', 'Could not recheck the Catalog publication remote.'));
|
|
163
|
+
if ((liveRefs.get(branchRef) ?? null) !== parent || liveRefs.has(tagRef) || liveRefs.has(`${tagRef}^{}`)) {
|
|
164
|
+
throw new Error('Catalog remote changed during preparation. Increase the Catalog version before publishing again.');
|
|
165
|
+
}
|
|
166
|
+
if (await fs.pathExists(attemptPath)) {
|
|
167
|
+
throw new Error(`Catalog ${descriptor.catalogVersion} already has an attempted publication record. `
|
|
168
|
+
+ 'Run aw catalog version <higher> before publishing again.');
|
|
169
|
+
}
|
|
170
|
+
await options.beforeRemoteWrite?.();
|
|
171
|
+
const attempted = {
|
|
172
|
+
schemaVersion: 1,
|
|
173
|
+
state: 'attempted',
|
|
174
|
+
workspaceIdentity: descriptor.workspaceIdentity,
|
|
175
|
+
catalogVersion: descriptor.catalogVersion,
|
|
176
|
+
sourceHead,
|
|
177
|
+
remote: { name: descriptor.remote.name, identityDigest: remoteIdentityDigest },
|
|
178
|
+
branch,
|
|
179
|
+
parent,
|
|
180
|
+
tag,
|
|
181
|
+
snapshotDigest: descriptor.snapshotDigest,
|
|
182
|
+
contentDigest: descriptor.contentDigest,
|
|
183
|
+
planId: descriptor.planId,
|
|
184
|
+
descriptorDigest: catalogPublicationDescriptorDigest(descriptor),
|
|
185
|
+
attemptedAt: new Date().toISOString(),
|
|
186
|
+
};
|
|
187
|
+
try {
|
|
188
|
+
await writeJsonAtomic(attemptPath, attempted, true);
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
if (error.code === 'EEXIST') {
|
|
192
|
+
throw new Error(`Catalog ${descriptor.catalogVersion} already has an attempted publication record. `
|
|
193
|
+
+ 'Run aw catalog version <higher> before publishing again.');
|
|
171
194
|
}
|
|
195
|
+
throw error;
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
await publicationGit(repositoryRoot, [
|
|
199
|
+
'push', '--atomic', `--force-with-lease=${branchRef}:${parent ?? ''}`,
|
|
200
|
+
'publish', `${commit}:${branchRef}`, `${tagRef}:${tagRef}`,
|
|
201
|
+
], env, 'publicationUncertain', `Catalog atomic push failed or its result is uncertain. Do not retry ${descriptor.catalogVersion}; increase the Catalog version.`);
|
|
172
202
|
}
|
|
203
|
+
catch {
|
|
204
|
+
throw new Error(`Catalog atomic push failed or its result is uncertain. Do not retry ${descriptor.catalogVersion}; run aw catalog version <higher>.`);
|
|
205
|
+
}
|
|
206
|
+
const publishedRefs = parseRemoteRefs(await publicationGit(repositoryRoot, [
|
|
207
|
+
'ls-remote', 'publish', branchRef, tagRef, `${tagRef}^{}`,
|
|
208
|
+
], env, 'publicationUncertain', `Catalog publication result is uncertain. Do not retry ${descriptor.catalogVersion}; increase the Catalog version.`));
|
|
209
|
+
if (publishedRefs.get(branchRef) !== commit || publishedRefs.get(`${tagRef}^{}`) !== commit) {
|
|
210
|
+
throw new Error(`Catalog publication result is uncertain. Do not retry ${descriptor.catalogVersion}; run aw catalog version <higher>.`);
|
|
211
|
+
}
|
|
212
|
+
await writeJsonAtomic(attemptPath, {
|
|
213
|
+
...attempted,
|
|
214
|
+
state: 'published',
|
|
215
|
+
commit,
|
|
216
|
+
publishedAt: new Date().toISOString(),
|
|
217
|
+
}, false);
|
|
218
|
+
return {
|
|
219
|
+
command: 'catalog:publish',
|
|
220
|
+
ok: true,
|
|
221
|
+
workspaceIdentity: descriptor.workspaceIdentity,
|
|
222
|
+
catalogVersion: descriptor.catalogVersion,
|
|
223
|
+
channel: descriptor.channel,
|
|
224
|
+
remote,
|
|
225
|
+
remoteUrl: safeFetchUrl,
|
|
226
|
+
fetchUrl: safeFetchUrl,
|
|
227
|
+
pushUrl: safePushUrl,
|
|
228
|
+
branch,
|
|
229
|
+
parent,
|
|
230
|
+
tag,
|
|
231
|
+
commit,
|
|
232
|
+
sourceHead,
|
|
233
|
+
pluginCount: descriptor.pluginNames.length,
|
|
234
|
+
snapshotDigest: descriptor.snapshotDigest,
|
|
235
|
+
planId: descriptor.planId,
|
|
236
|
+
descriptorDigest: catalogPublicationDescriptorDigest(descriptor),
|
|
237
|
+
};
|
|
173
238
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
239
|
+
export async function publishPreparedCatalogPublication(options) {
|
|
240
|
+
const inspection = await prepareCatalogPublicationInspection({
|
|
241
|
+
workspaceRoot: options.workspaceRoot,
|
|
242
|
+
remote: options.remote,
|
|
243
|
+
remoteUrl: options.remoteUrl,
|
|
244
|
+
branch: options.branch,
|
|
245
|
+
env: options.env,
|
|
246
|
+
failOnUnavailable: true,
|
|
247
|
+
});
|
|
248
|
+
try {
|
|
249
|
+
return await publishCatalogPublicationPreparation({
|
|
250
|
+
preparation: inspection,
|
|
251
|
+
descriptor: options.descriptor,
|
|
252
|
+
snapshotRoot: options.snapshotRoot,
|
|
253
|
+
beforeRemoteWrite: options.beforeRemoteWrite,
|
|
179
254
|
});
|
|
180
|
-
}
|
|
181
|
-
|
|
255
|
+
}
|
|
256
|
+
finally {
|
|
257
|
+
await disposeCatalogPublicationInspection(inspection);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
182
260
|
export async function runCatalogPublishCommand(options) {
|
|
183
261
|
if (options.json && !options.yes)
|
|
184
262
|
throw new Error('Catalog publish with --json requires --yes.');
|
|
185
263
|
if (!options.interactive && !options.yes)
|
|
186
264
|
throw new Error('Non-interactive Catalog publish requires --yes.');
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
'status', '--porcelain=v1', '--untracked-files=all', '--ignore-submodules=none',
|
|
195
|
-
], env, 'Could not inspect the source worktree.');
|
|
196
|
-
if (status !== '')
|
|
197
|
-
throw new Error('Catalog publish requires the complete source Git worktree to be clean.');
|
|
198
|
-
const temporaryRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'aw-public-catalog-'));
|
|
199
|
-
let fetchUrl = '';
|
|
200
|
-
let pushUrl = '';
|
|
201
|
-
let safeFetchUrl = '';
|
|
202
|
-
let safePushUrl = '';
|
|
265
|
+
if (!/^sha256:[0-9a-f]{64}$/u.test(options.expectedDescriptorDigest ?? '')) {
|
|
266
|
+
throw new Error('Catalog publish requires --expected-descriptor-digest from a successful publication preflight.');
|
|
267
|
+
}
|
|
268
|
+
const inspection = await prepareCatalogPublicationInspection({
|
|
269
|
+
...options,
|
|
270
|
+
failOnUnavailable: true,
|
|
271
|
+
});
|
|
203
272
|
try {
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
if (workspaceRelative === '..' || workspaceRelative.startsWith(`..${path.sep}`) || path.isAbsolute(workspaceRelative)) {
|
|
208
|
-
throw new Error('Catalog workspace must stay inside its Git worktree root.');
|
|
273
|
+
const preparedDescriptorDigest = catalogPublicationDescriptorDigest(inspection.descriptor);
|
|
274
|
+
if (options.expectedDescriptorDigest !== preparedDescriptorDigest) {
|
|
275
|
+
throw new Error('Catalog publication descriptor does not match the authorized preflight. Run preflight and authorize the current exact identity.');
|
|
209
276
|
}
|
|
210
|
-
|
|
211
|
-
await git(repoRoot, ['check-ref-format', '--branch', branch], env, 'Invalid Catalog publication branch name.');
|
|
212
|
-
const workspace = await readPluginWorkspace(sourceWorkspaceRoot, { reservedImports: CLI_RESERVED_IMPORTS });
|
|
213
|
-
await validatePublishedSourceWorkspace(workspace, env);
|
|
214
|
-
const catalogVersion = workspace.metadata.catalog.version;
|
|
215
|
-
const tag = `catalog-v${catalogVersion}`;
|
|
216
|
-
await git(repoRoot, ['check-ref-format', `refs/tags/${tag}`], env, 'Invalid Catalog publication tag.');
|
|
217
|
-
const markerPath = attemptPath(workspaceRoot, catalogVersion);
|
|
218
|
-
if (!(await gitExit(repoRoot, ['check-ignore', '-q', markerPath], env))) {
|
|
277
|
+
if (!(await publicationGitExit(inspection.repoRoot, ['check-ignore', '-q', inspection.attemptPath], inspection.env))) {
|
|
219
278
|
throw new Error('Catalog publish state under .aw/publish must be ignored by Git. Add .aw/ to .gitignore.');
|
|
220
279
|
}
|
|
221
|
-
await migrateLegacyAttempt(workspaceRoot, catalogVersion);
|
|
222
|
-
if (await fs.pathExists(markerPath)) {
|
|
223
|
-
throw new Error(`Catalog ${catalogVersion} already has an attempted publication record. `
|
|
224
|
-
+ 'Run aw catalog version <higher> before publishing again.');
|
|
225
|
-
}
|
|
226
|
-
fetchUrl = await git(repoRoot, ['remote', 'get-url', remote], env, `Git remote ${JSON.stringify(remote)} is not configured.`);
|
|
227
|
-
pushUrl = await git(repoRoot, ['remote', 'get-url', '--push', remote], env, `Git remote ${JSON.stringify(remote)} has no push URL.`);
|
|
228
|
-
safeFetchUrl = safeRemoteIdentity(fetchUrl);
|
|
229
|
-
safePushUrl = safeRemoteIdentity(pushUrl);
|
|
230
|
-
const remoteIdentityDigest = digest(JSON.stringify({ fetchUrl, pushUrl }));
|
|
231
|
-
const branchRef = `refs/heads/${branch}`;
|
|
232
|
-
const tagRef = `refs/tags/${tag}`;
|
|
233
|
-
const repositoryRoot = path.join(temporaryRoot, 'repository');
|
|
234
|
-
await fs.ensureDir(repositoryRoot);
|
|
235
|
-
await git(repositoryRoot, ['init', '--quiet'], env, 'Could not create the isolated publication repository.');
|
|
236
|
-
await git(repositoryRoot, ['config', 'core.hooksPath', path.join(temporaryRoot, 'disabled-hooks')], env, 'Could not disable publication hooks.');
|
|
237
|
-
await git(repositoryRoot, ['config', 'core.symlinks', 'true'], env, 'Could not configure the isolated publication repository.');
|
|
238
|
-
await git(repositoryRoot, ['remote', 'add', 'publish', pushUrl], env, 'Could not configure the publication remote.');
|
|
239
|
-
const initialRefs = parseRemoteRefs(await git(repositoryRoot, [
|
|
240
|
-
'ls-remote', 'publish', branchRef, tagRef, `${tagRef}^{}`,
|
|
241
|
-
], env, 'Could not inspect the Catalog publication remote.'));
|
|
242
|
-
if (initialRefs.has(tagRef) || initialRefs.has(`${tagRef}^{}`)) {
|
|
243
|
-
throw new Error(`Remote Catalog tag ${tag} already exists. Increase the Catalog version.`);
|
|
244
|
-
}
|
|
245
|
-
const parent = initialRefs.get(branchRef) ?? null;
|
|
246
|
-
let previousSnapshotRoot;
|
|
247
|
-
if (parent) {
|
|
248
|
-
await git(repositoryRoot, ['fetch', '--quiet', '--no-tags', 'publish', `${branchRef}:refs/remotes/publish/catalog`], env, 'Could not fetch the published Catalog branch.');
|
|
249
|
-
previousSnapshotRoot = path.join(temporaryRoot, 'previous');
|
|
250
|
-
await fs.ensureDir(previousSnapshotRoot);
|
|
251
|
-
await git(repositoryRoot, ['--work-tree', previousSnapshotRoot, 'checkout', '--quiet', 'refs/remotes/publish/catalog', '--', '.'], env, 'Could not read the published Catalog snapshot.');
|
|
252
|
-
await validatePublishedCatalogSnapshot(previousSnapshotRoot);
|
|
253
|
-
}
|
|
254
|
-
const prepared = await preparePublishedWorkspaceCatalog({
|
|
255
|
-
workspaceRoot: sourceWorkspaceRoot,
|
|
256
|
-
sourceHead,
|
|
257
|
-
previousSnapshotRoot,
|
|
258
|
-
adapter: publishBuildAdapter,
|
|
259
|
-
reservedImports: CLI_RESERVED_IMPORTS,
|
|
260
|
-
});
|
|
261
|
-
const preparedHead = await git(repoRoot, ['rev-parse', '--verify', 'HEAD'], env, 'Could not recheck the source Git HEAD.');
|
|
262
|
-
const preparedStatus = await git(repoRoot, [
|
|
263
|
-
'status', '--porcelain=v1', '--untracked-files=all', '--ignore-submodules=none',
|
|
264
|
-
], env, 'Could not recheck the source worktree.');
|
|
265
|
-
if (preparedHead !== sourceHead || preparedStatus !== '') {
|
|
266
|
-
throw new Error('Source HEAD or worktree changed during Catalog preparation. Commit or clean it before publishing.');
|
|
267
|
-
}
|
|
268
280
|
if (!options.yes) {
|
|
269
281
|
options.io.stderr([
|
|
270
|
-
`
|
|
271
|
-
`
|
|
272
|
-
`
|
|
273
|
-
`
|
|
274
|
-
`
|
|
275
|
-
`
|
|
282
|
+
`Workspace identity: ${inspection.descriptor.workspaceIdentity}`,
|
|
283
|
+
`Source HEAD: ${inspection.sourceHead}`,
|
|
284
|
+
`Catalog: ${inspection.descriptor.catalogVersion} (${inspection.descriptor.channel})`,
|
|
285
|
+
`Remote: ${inspection.remote}`,
|
|
286
|
+
`Fetch URL: ${inspection.safeFetchUrl}`,
|
|
287
|
+
`Push URL: ${inspection.safePushUrl}`,
|
|
288
|
+
`Branch: ${inspection.branchRef}`,
|
|
289
|
+
`Parent: ${inspection.parent ?? '<root>'}`,
|
|
290
|
+
`Tag: ${inspection.tagRef}`,
|
|
291
|
+
`Snapshot digest: ${inspection.descriptor.snapshotDigest}`,
|
|
292
|
+
`Plan ID: ${inspection.descriptor.planId}`,
|
|
293
|
+
`Descriptor digest: ${preparedDescriptorDigest}`,
|
|
294
|
+
`Plugins: ${inspection.descriptor.pluginNames.length}`,
|
|
276
295
|
'One atomic push will publish the branch and annotated tag.',
|
|
277
296
|
].join('\n') + '\n');
|
|
278
297
|
if (!options.confirm || !(await options.confirm('Publish this Catalog snapshot?'))) {
|
|
@@ -280,95 +299,21 @@ export async function runCatalogPublishCommand(options) {
|
|
|
280
299
|
return null;
|
|
281
300
|
}
|
|
282
301
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
await fs.copy(prepared.outputRoot, repositoryRoot, { dereference: false });
|
|
289
|
-
await git(repositoryRoot, ['add', '--force', '--all'], env, 'Could not stage the Catalog publication tree.');
|
|
290
|
-
await git(repositoryRoot, [
|
|
291
|
-
'-c', 'user.name=AW Catalog Publisher',
|
|
292
|
-
'-c', 'user.email=catalog-publisher@agent-workflows.invalid',
|
|
293
|
-
'commit', '--quiet', '-m', `Catalog ${catalogVersion}`,
|
|
294
|
-
], env, 'Could not create the Catalog publication commit.');
|
|
295
|
-
const commit = await git(repositoryRoot, ['rev-parse', 'HEAD'], env, 'Could not resolve the Catalog publication commit.');
|
|
296
|
-
const parents = (await git(repositoryRoot, ['show', '-s', '--format=%P', commit], env, 'Could not verify the Catalog publication parent.')).split(/\s+/u).filter(Boolean);
|
|
297
|
-
if ((parent && (parents.length !== 1 || parents[0] !== parent)) || (!parent && parents.length !== 0)) {
|
|
298
|
-
throw new Error('Catalog publication commit has an invalid parent.');
|
|
299
|
-
}
|
|
300
|
-
const verificationRoot = path.join(temporaryRoot, 'verification');
|
|
301
|
-
await fs.ensureDir(verificationRoot);
|
|
302
|
-
await git(repositoryRoot, ['--work-tree', verificationRoot, 'checkout', '--quiet', commit, '--', '.'], env, 'Could not verify the Catalog publication commit tree.');
|
|
303
|
-
const committed = await validatePublishedCatalogSnapshot(verificationRoot);
|
|
304
|
-
if (committed.snapshotDigest !== prepared.snapshotDigest)
|
|
305
|
-
throw new Error('Catalog publication commit tree differs from the prepared snapshot.');
|
|
306
|
-
await git(repositoryRoot, [
|
|
307
|
-
'-c', 'user.name=AW Catalog Publisher',
|
|
308
|
-
'-c', 'user.email=catalog-publisher@agent-workflows.invalid',
|
|
309
|
-
'tag', '--annotate', tag, '--message', `Catalog ${catalogVersion}`,
|
|
310
|
-
], env, 'Could not create the annotated Catalog tag.');
|
|
311
|
-
const attemptedAt = new Date().toISOString();
|
|
312
|
-
const attempted = {
|
|
313
|
-
schemaVersion: 1,
|
|
314
|
-
state: 'attempted',
|
|
315
|
-
workspaceIdentity: digest(JSON.stringify({ repoRoot, workspaceRelative: path.relative(repoRoot, workspaceRoot) })),
|
|
316
|
-
catalogVersion,
|
|
317
|
-
sourceHead,
|
|
318
|
-
remote: { name: remote, identityDigest: remoteIdentityDigest },
|
|
319
|
-
branch,
|
|
320
|
-
tag,
|
|
321
|
-
preparedSnapshotDigest: prepared.snapshotDigest,
|
|
322
|
-
attemptedAt,
|
|
323
|
-
};
|
|
324
|
-
await writeJsonAtomic(markerPath, attempted);
|
|
325
|
-
const liveRefs = parseRemoteRefs(await git(repositoryRoot, [
|
|
326
|
-
'ls-remote', 'publish', branchRef, tagRef, `${tagRef}^{}`,
|
|
327
|
-
], env, 'Could not recheck the Catalog publication remote.'));
|
|
328
|
-
if ((liveRefs.get(branchRef) ?? null) !== parent || liveRefs.has(tagRef) || liveRefs.has(`${tagRef}^{}`)) {
|
|
329
|
-
throw new Error('Catalog remote changed during preparation. Increase the Catalog version before publishing again.');
|
|
330
|
-
}
|
|
331
|
-
try {
|
|
332
|
-
await git(repositoryRoot, [
|
|
333
|
-
'push', '--atomic', 'publish', `${commit}:${branchRef}`, `${tagRef}:${tagRef}`,
|
|
334
|
-
], env, `Catalog atomic push failed or its result is uncertain. Do not retry ${catalogVersion}; increase the Catalog version.`);
|
|
335
|
-
}
|
|
336
|
-
catch {
|
|
337
|
-
throw new Error(`Catalog atomic push failed or its result is uncertain. Do not retry ${catalogVersion}; run aw catalog version <higher>.`);
|
|
338
|
-
}
|
|
339
|
-
const publishedRefs = parseRemoteRefs(await git(repositoryRoot, [
|
|
340
|
-
'ls-remote', 'publish', branchRef, tagRef, `${tagRef}^{}`,
|
|
341
|
-
], env, `Catalog publication result is uncertain. Do not retry ${catalogVersion}; increase the Catalog version.`));
|
|
342
|
-
if (publishedRefs.get(branchRef) !== commit || publishedRefs.get(`${tagRef}^{}`) !== commit) {
|
|
343
|
-
throw new Error(`Catalog publication result is uncertain. Do not retry ${catalogVersion}; run aw catalog version <higher>.`);
|
|
344
|
-
}
|
|
345
|
-
await writeJsonAtomic(markerPath, { ...attempted, state: 'published', commit, publishedAt: new Date().toISOString() });
|
|
346
|
-
return {
|
|
347
|
-
command: 'catalog:publish',
|
|
348
|
-
ok: true,
|
|
349
|
-
catalogVersion,
|
|
350
|
-
channel: prepared.channel,
|
|
351
|
-
remote,
|
|
352
|
-
remoteUrl: safeFetchUrl,
|
|
353
|
-
fetchUrl: safeFetchUrl,
|
|
354
|
-
pushUrl: safePushUrl,
|
|
355
|
-
branch,
|
|
356
|
-
tag,
|
|
357
|
-
commit,
|
|
358
|
-
sourceHead,
|
|
359
|
-
pluginCount: prepared.pluginNames.length,
|
|
360
|
-
snapshotDigest: prepared.snapshotDigest,
|
|
361
|
-
};
|
|
302
|
+
return await publishCatalogPublicationPreparation({
|
|
303
|
+
preparation: inspection,
|
|
304
|
+
descriptor: inspection.descriptor,
|
|
305
|
+
snapshotRoot: inspection.prepared.outputRoot,
|
|
306
|
+
});
|
|
362
307
|
}
|
|
363
308
|
catch (error) {
|
|
364
|
-
const message = error instanceof Error ? error.message : String(error)
|
|
365
|
-
|
|
366
|
-
.replaceAll(
|
|
367
|
-
.replaceAll(
|
|
368
|
-
|
|
309
|
+
const message = (error instanceof Error ? error.message : String(error))
|
|
310
|
+
.replaceAll(inspection.temporaryRoot, '<temporary Catalog state>')
|
|
311
|
+
.replaceAll(inspection.fetchUrl, inspection.safeFetchUrl)
|
|
312
|
+
.replaceAll(inspection.pushUrl, inspection.safePushUrl);
|
|
313
|
+
throw new Error(message);
|
|
369
314
|
}
|
|
370
315
|
finally {
|
|
371
|
-
await
|
|
316
|
+
await disposeCatalogPublicationInspection(inspection);
|
|
372
317
|
}
|
|
373
318
|
}
|
|
374
319
|
//# sourceMappingURL=catalog-publish.js.map
|