release-skill 0.2.8 → 0.3.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codebuddy-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +2 -2
- package/.kimi-plugin/plugin.json +1 -1
- package/CHANGELOG.md +36 -0
- package/INSTALL.md +4 -4
- package/INSTALL.zh-CN.md +4 -4
- package/README.md +38 -13
- package/README.zh-CN.md +36 -13
- package/adapters/claude/.claude-plugin/marketplace.json +1 -1
- package/adapters/claude/.claude-plugin/plugin.json +1 -1
- package/adapters/claude/bin/release-skill.bundle.mjs +3961 -2882
- package/adapters/claude/skills/release-help/SKILL.md +9 -1
- package/adapters/codex/.codex-plugin/plugin.json +2 -2
- package/adapters/codex/bin/release-skill.bundle.mjs +3961 -2882
- package/adapters/codex/skills/release-help/SKILL.md +9 -1
- package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
- package/adapters/kimi/bin/release-skill.bundle.mjs +3961 -2882
- package/adapters/kimi/skills/release-help/SKILL.md +9 -1
- package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
- package/adapters/workbuddy/bin/release-skill.bundle.mjs +3961 -2882
- package/adapters/workbuddy/skills/release-help/SKILL.md +9 -1
- package/bin/release-skill-cli.mjs +180 -4
- package/bin/release-skill.bundle.mjs +3961 -2882
- package/package.json +1 -1
- package/skills/release-help/SKILL.md +9 -1
- package/skills-src/release-help/SKILL.md +9 -1
- package/src/adapters/plugin-marketplace.mjs +1 -1
- package/src/adapters/push-snapshot.mjs +3 -1
- package/src/commands/assess.mjs +18 -5
- package/src/commands/attest.mjs +195 -0
- package/src/commands/hooks.mjs +46 -0
- package/src/commands/prepare.mjs +14 -2
- package/src/commands/ship.mjs +356 -0
- package/src/commands/verify.mjs +147 -4
- package/src/core/git-transport.mjs +93 -0
- package/src/core/public-surface.mjs +26 -0
- package/src/core/release-metadata.mjs +105 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { lstat, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import YAML from 'yaml';
|
|
4
|
+
|
|
5
|
+
import { ReleaseError, CONFIG_INVALID, GATE_FAILED } from './errors.mjs';
|
|
6
|
+
|
|
7
|
+
function assertOid(value, label) {
|
|
8
|
+
if (typeof value !== 'string' || !/^[a-f0-9]{40,64}$/.test(value)) {
|
|
9
|
+
throw new ReleaseError(GATE_FAILED, `${label} is not a full Git object id`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* After VERIFIED, advance the project-only previous-public baseline to the
|
|
15
|
+
* exact public commit already frozen, published and verified. This prepares
|
|
16
|
+
* the next release without asking maintainers to copy commits from terminal
|
|
17
|
+
* output. It never edits public files or performs remote writes.
|
|
18
|
+
*/
|
|
19
|
+
export async function updatePreviousPublicBaselines(options = {}) {
|
|
20
|
+
const root = resolve(options.root ?? process.cwd());
|
|
21
|
+
const planPath = resolve(options.planPath ?? '');
|
|
22
|
+
const configPath = resolve(root, '.release-skill', 'project.yaml');
|
|
23
|
+
let plan;
|
|
24
|
+
let configRaw;
|
|
25
|
+
let configStat;
|
|
26
|
+
try {
|
|
27
|
+
[plan, configRaw, configStat] = await Promise.all([
|
|
28
|
+
readFile(planPath, 'utf8').then(JSON.parse),
|
|
29
|
+
readFile(configPath, 'utf8'),
|
|
30
|
+
lstat(configPath),
|
|
31
|
+
]);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
throw new ReleaseError(
|
|
34
|
+
CONFIG_INVALID,
|
|
35
|
+
`cannot load release metadata authorities: ${error.message}`,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
if (!configStat.isFile() || configStat.isSymbolicLink()) {
|
|
39
|
+
throw new ReleaseError(CONFIG_INVALID, 'project config must be a regular non-symlink file');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const doc = YAML.parseDocument(configRaw, { uniqueKeys: true });
|
|
43
|
+
if (doc.errors.length > 0) {
|
|
44
|
+
throw new ReleaseError(CONFIG_INVALID, `project config YAML is invalid: ${doc.errors[0].message}`);
|
|
45
|
+
}
|
|
46
|
+
const releaseUnits = doc.get('releaseUnits', true);
|
|
47
|
+
if (!YAML.isSeq(releaseUnits)) {
|
|
48
|
+
throw new ReleaseError(CONFIG_INVALID, 'project config releaseUnits must be a sequence');
|
|
49
|
+
}
|
|
50
|
+
const configUnits = new Map();
|
|
51
|
+
for (const node of releaseUnits.items) {
|
|
52
|
+
const id = node?.get?.('id');
|
|
53
|
+
if (typeof id === 'string') configUnits.set(id, node);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const updates = [];
|
|
57
|
+
for (const unit of plan.units ?? []) {
|
|
58
|
+
const configUnit = configUnits.get(unit.id);
|
|
59
|
+
if (!configUnit) {
|
|
60
|
+
throw new ReleaseError(CONFIG_INVALID, `frozen unit "${unit.id}" is absent from project config`);
|
|
61
|
+
}
|
|
62
|
+
const frozen = unit.frozenSnapshot;
|
|
63
|
+
assertOid(frozen?.commit, `unit "${unit.id}" frozen commit`);
|
|
64
|
+
assertOid(frozen?.tree, `unit "${unit.id}" frozen tree`);
|
|
65
|
+
if (!/^[a-f0-9]{64}$/.test(frozen?.manifestDigest ?? '')) {
|
|
66
|
+
throw new ReleaseError(GATE_FAILED, `unit "${unit.id}" frozen manifest digest is invalid`);
|
|
67
|
+
}
|
|
68
|
+
const old = configUnit.get('previousPublicBaseline', true)?.toJSON?.() ?? {};
|
|
69
|
+
const next = {
|
|
70
|
+
mode: 'bound',
|
|
71
|
+
repo: old.repo ?? unit.publicRepo,
|
|
72
|
+
ref: old.ref ?? `refs/heads/${frozen.branch}`,
|
|
73
|
+
commit: frozen.commit,
|
|
74
|
+
tree: frozen.tree,
|
|
75
|
+
manifestDigest: frozen.manifestDigest,
|
|
76
|
+
};
|
|
77
|
+
if (!next.repo || !next.ref) {
|
|
78
|
+
throw new ReleaseError(GATE_FAILED, `unit "${unit.id}" cannot derive its next public baseline identity`);
|
|
79
|
+
}
|
|
80
|
+
configUnit.set('previousPublicBaseline', next);
|
|
81
|
+
updates.push({
|
|
82
|
+
id: unit.id,
|
|
83
|
+
previousCommit: old.commit ?? null,
|
|
84
|
+
commit: frozen.commit,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const nextRaw = doc.toString();
|
|
89
|
+
if (nextRaw === configRaw) {
|
|
90
|
+
return { status: 'UNCHANGED', configPath, units: updates };
|
|
91
|
+
}
|
|
92
|
+
const tempPath = `${configPath}.${process.pid}.${Date.now()}.tmp`;
|
|
93
|
+
try {
|
|
94
|
+
await writeFile(tempPath, nextRaw, {
|
|
95
|
+
encoding: 'utf8',
|
|
96
|
+
mode: configStat.mode & 0o777,
|
|
97
|
+
flag: 'wx',
|
|
98
|
+
});
|
|
99
|
+
await rename(tempPath, configPath);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
await rm(tempPath, { force: true }).catch(() => {});
|
|
102
|
+
throw new ReleaseError(CONFIG_INVALID, `cannot update project release metadata: ${error.message}`);
|
|
103
|
+
}
|
|
104
|
+
return { status: 'UPDATED', configPath, units: updates };
|
|
105
|
+
}
|