@thinkingsage/kanon 0.8.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/CHANGELOG.md +410 -0
- package/LICENSE +21 -0
- package/README.md +168 -0
- package/bridge/mcp-server.cjs +14171 -0
- package/package.json +98 -0
- package/src/adapters/capabilities.ts +178 -0
- package/src/adapters/claude-code.ts +110 -0
- package/src/adapters/cline.ts +98 -0
- package/src/adapters/codex.ts +173 -0
- package/src/adapters/copilot.ts +106 -0
- package/src/adapters/cursor.ts +97 -0
- package/src/adapters/degradation.ts +95 -0
- package/src/adapters/index.ts +324 -0
- package/src/adapters/kiro-frontmatter.ts +139 -0
- package/src/adapters/kiro-inclusion.ts +86 -0
- package/src/adapters/kiro.ts +412 -0
- package/src/adapters/qdeveloper.ts +115 -0
- package/src/adapters/types.ts +81 -0
- package/src/adapters/windsurf.ts +96 -0
- package/src/admin.ts +283 -0
- package/src/asset-conventions.ts +118 -0
- package/src/attribution-backfill.ts +319 -0
- package/src/attribution-report.ts +95 -0
- package/src/attribution.ts +239 -0
- package/src/backends/github.ts +194 -0
- package/src/backends/http.ts +122 -0
- package/src/backends/index.ts +39 -0
- package/src/backends/local.ts +47 -0
- package/src/backends/s3.ts +157 -0
- package/src/backends/types.ts +59 -0
- package/src/base-cache.ts +270 -0
- package/src/browse-ui.ts +3754 -0
- package/src/browse.ts +1038 -0
- package/src/build.ts +1108 -0
- package/src/catalog.ts +204 -0
- package/src/cli-deprecated.ts +29 -0
- package/src/cli.ts +773 -0
- package/src/collection-admin.ts +287 -0
- package/src/collection-builder.ts +464 -0
- package/src/collections.ts +116 -0
- package/src/compatibility.ts +105 -0
- package/src/config.ts +743 -0
- package/src/eval/rubrics/kiro-progressive-steering.ts +841 -0
- package/src/eval.ts +1169 -0
- package/src/file-writer.ts +61 -0
- package/src/format-registry.ts +141 -0
- package/src/guild/auto-updater.ts +163 -0
- package/src/guild/backend-resolver.ts +49 -0
- package/src/guild/cli.ts +592 -0
- package/src/guild/collection-expander.ts +47 -0
- package/src/guild/global-cache.ts +247 -0
- package/src/guild/hook-generator.ts +100 -0
- package/src/guild/manifest.ts +154 -0
- package/src/guild/path-utils.ts +12 -0
- package/src/guild/sync.ts +622 -0
- package/src/guild/version-resolver.ts +42 -0
- package/src/help/metadata.ts +445 -0
- package/src/help/renderer.ts +265 -0
- package/src/help/typo-suggester.ts +25 -0
- package/src/hooks/expression.ts +493 -0
- package/src/hooks/pipeline.ts +141 -0
- package/src/import.ts +773 -0
- package/src/importers/claude-code.ts +134 -0
- package/src/importers/cline.ts +103 -0
- package/src/importers/codex.ts +140 -0
- package/src/importers/copilot.ts +103 -0
- package/src/importers/cursor.ts +105 -0
- package/src/importers/index.ts +390 -0
- package/src/importers/kiro.ts +110 -0
- package/src/importers/qdeveloper.ts +103 -0
- package/src/importers/types.ts +54 -0
- package/src/importers/windsurf.ts +104 -0
- package/src/install.ts +1005 -0
- package/src/manifest-admin.ts +306 -0
- package/src/mcp-bridge.ts +240 -0
- package/src/mutation/delta.ts +50 -0
- package/src/mutation/history.ts +66 -0
- package/src/mutation/operators.ts +524 -0
- package/src/mutation/runner.ts +332 -0
- package/src/new.ts +106 -0
- package/src/outcomes/collision.ts +127 -0
- package/src/outcomes/normalize.ts +208 -0
- package/src/outcomes/registry.ts +173 -0
- package/src/parser.ts +446 -0
- package/src/provenance-backfill-cli.ts +319 -0
- package/src/provenance-backfill.ts +520 -0
- package/src/publish.ts +354 -0
- package/src/reconcile-orchestrator.ts +502 -0
- package/src/reconcile-report-renderer.ts +176 -0
- package/src/resolve-body.ts +15 -0
- package/src/rosetta/builtins/compatibility-profiles.ts +297 -0
- package/src/rosetta/builtins/contracts.ts +1033 -0
- package/src/rosetta/builtins/pretty-printers/claude-code-native.ts +122 -0
- package/src/rosetta/builtins/pretty-printers/cline-native.ts +50 -0
- package/src/rosetta/builtins/pretty-printers/codex-native.ts +127 -0
- package/src/rosetta/builtins/pretty-printers/copilot-native.ts +50 -0
- package/src/rosetta/builtins/pretty-printers/cursor-native.ts +50 -0
- package/src/rosetta/builtins/pretty-printers/index.ts +81 -0
- package/src/rosetta/builtins/pretty-printers/kiro-native.ts +166 -0
- package/src/rosetta/builtins/pretty-printers/kiro-power.ts +108 -0
- package/src/rosetta/builtins/pretty-printers/kiro-skill.ts +88 -0
- package/src/rosetta/builtins/pretty-printers/qdeveloper-native.ts +51 -0
- package/src/rosetta/builtins/pretty-printers/superpowers.ts +97 -0
- package/src/rosetta/builtins/pretty-printers/windsurf-native.ts +50 -0
- package/src/rosetta/builtins/sources/claude-code-native.ts +348 -0
- package/src/rosetta/builtins/sources/cline-native.ts +176 -0
- package/src/rosetta/builtins/sources/codex-native.ts +343 -0
- package/src/rosetta/builtins/sources/copilot-native.ts +178 -0
- package/src/rosetta/builtins/sources/cursor-native.ts +176 -0
- package/src/rosetta/builtins/sources/index.ts +95 -0
- package/src/rosetta/builtins/sources/kiro-native.ts +462 -0
- package/src/rosetta/builtins/sources/kiro-power.ts +285 -0
- package/src/rosetta/builtins/sources/kiro-skill.ts +230 -0
- package/src/rosetta/builtins/sources/qdeveloper-native.ts +181 -0
- package/src/rosetta/builtins/sources/superpowers.ts +240 -0
- package/src/rosetta/builtins/sources/windsurf-native.ts +176 -0
- package/src/rosetta/builtins/targets/claude-code.ts +181 -0
- package/src/rosetta/builtins/targets/cline.ts +87 -0
- package/src/rosetta/builtins/targets/codex.ts +226 -0
- package/src/rosetta/builtins/targets/copilot.ts +103 -0
- package/src/rosetta/builtins/targets/cursor.ts +87 -0
- package/src/rosetta/builtins/targets/index.ts +60 -0
- package/src/rosetta/builtins/targets/kiro.ts +278 -0
- package/src/rosetta/builtins/targets/qdeveloper.ts +103 -0
- package/src/rosetta/builtins/targets/windsurf.ts +87 -0
- package/src/rosetta/canonical.ts +729 -0
- package/src/rosetta/compatibility.ts +432 -0
- package/src/rosetta/contracts.ts +329 -0
- package/src/rosetta/detector.ts +724 -0
- package/src/rosetta/diagnostics.ts +630 -0
- package/src/rosetta/engine-bootstrap.ts +103 -0
- package/src/rosetta/engine.ts +744 -0
- package/src/rosetta/index.ts +381 -0
- package/src/rosetta/inspection.ts +530 -0
- package/src/rosetta/plan.ts +448 -0
- package/src/rosetta/provenance-digest.ts +369 -0
- package/src/rosetta/reconcile.ts +812 -0
- package/src/rosetta/redaction.ts +467 -0
- package/src/rosetta/registry.ts +712 -0
- package/src/rosetta/renderers.ts +571 -0
- package/src/rosetta/request-guard.ts +335 -0
- package/src/rosetta/resolution.ts +419 -0
- package/src/rosetta/source-accounting.ts +233 -0
- package/src/rosetta/templates.ts +129 -0
- package/src/rosetta-cli.ts +717 -0
- package/src/rosetta-docs-generator.ts +793 -0
- package/src/rosetta-profiles-cli.ts +367 -0
- package/src/schemas.ts +1712 -0
- package/src/spec-coordination.ts +1141 -0
- package/src/temper.ts +747 -0
- package/src/template-bundle-loader.ts +312 -0
- package/src/template-engine.ts +53 -0
- package/src/translation-application-policy.ts +496 -0
- package/src/translation-orchestrator.ts +1013 -0
- package/src/translation-plan-applier.ts +473 -0
- package/src/tutorial.ts +305 -0
- package/src/validate.ts +1093 -0
- package/src/versioning.ts +553 -0
- package/src/wizard.ts +660 -0
- package/src/workspace.ts +237 -0
- package/templates/eval-contexts/claude-code.md.njk +6 -0
- package/templates/eval-contexts/cline.md.njk +6 -0
- package/templates/eval-contexts/copilot.md.njk +6 -0
- package/templates/eval-contexts/cursor.md.njk +6 -0
- package/templates/eval-contexts/kiro.md.njk +10 -0
- package/templates/eval-contexts/qdeveloper.md.njk +6 -0
- package/templates/eval-contexts/windsurf.md.njk +6 -0
- package/templates/harness-adapters/_base/attribution-footer.md.njk +17 -0
- package/templates/harness-adapters/_base/base.md.njk +16 -0
- package/templates/harness-adapters/claude-code/claude.md.njk +1 -0
- package/templates/harness-adapters/claude-code/mcp.json.njk +1 -0
- package/templates/harness-adapters/claude-code/settings.json.njk +1 -0
- package/templates/harness-adapters/claude-code/skill-library-index.md.njk +13 -0
- package/templates/harness-adapters/claude-code/skill.md.njk +19 -0
- package/templates/harness-adapters/cline/hook.sh.njk +4 -0
- package/templates/harness-adapters/cline/mcp.json.njk +1 -0
- package/templates/harness-adapters/cline/rule.md.njk +1 -0
- package/templates/harness-adapters/codex/agents-md.md.njk +6 -0
- package/templates/harness-adapters/codex/agents-pointer.md.njk +16 -0
- package/templates/harness-adapters/codex/skill.md.njk +27 -0
- package/templates/harness-adapters/copilot/agents.md.njk +1 -0
- package/templates/harness-adapters/copilot/instructions.md.njk +1 -0
- package/templates/harness-adapters/copilot/scoped.md.njk +6 -0
- package/templates/harness-adapters/cursor/mcp.json.njk +1 -0
- package/templates/harness-adapters/cursor/rule.md.njk +6 -0
- package/templates/harness-adapters/kiro/hook.json.njk +1 -0
- package/templates/harness-adapters/kiro/mcp.json.njk +1 -0
- package/templates/harness-adapters/kiro/power-steering.md.njk +3 -0
- package/templates/harness-adapters/kiro/power.md.njk +12 -0
- package/templates/harness-adapters/kiro/steering.md.njk +16 -0
- package/templates/harness-adapters/qdeveloper/agent.md.njk +1 -0
- package/templates/harness-adapters/qdeveloper/mcp.json.njk +1 -0
- package/templates/harness-adapters/qdeveloper/rule.md.njk +1 -0
- package/templates/harness-adapters/windsurf/mcp.json.njk +1 -0
- package/templates/harness-adapters/windsurf/rule.md.njk +1 -0
- package/templates/harness-adapters/windsurf/workflow.md.njk +1 -0
- package/templates/knowledge/hooks.yaml.njk +4 -0
- package/templates/knowledge/knowledge.md.njk +53 -0
- package/templates/knowledge/mcp-servers.yaml.njk +2 -0
package/src/publish.ts
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import { exists, mkdir, readdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { SOURCE_DIRS as BUILD_SOURCE_DIRS, build } from "./build";
|
|
5
|
+
import {
|
|
6
|
+
SOURCE_DIRS as CATALOG_SOURCE_DIRS,
|
|
7
|
+
generateCatalog,
|
|
8
|
+
serializeCatalog,
|
|
9
|
+
} from "./catalog";
|
|
10
|
+
import { buildCollectionMembership, loadCollections } from "./collections";
|
|
11
|
+
import { loadForgeConfig } from "./config";
|
|
12
|
+
import { SUPPORTED_HARNESSES } from "./schemas";
|
|
13
|
+
import { SOURCE_DIRS as VALIDATE_SOURCE_DIRS, validateAll } from "./validate";
|
|
14
|
+
|
|
15
|
+
export interface PublishOptions {
|
|
16
|
+
/** Tag/version to publish as, e.g. "v1.2.0". Defaults to package.json version with prefix. */
|
|
17
|
+
tag?: string;
|
|
18
|
+
/** Named backend from kanon.config.yaml to publish to. */
|
|
19
|
+
backend?: string;
|
|
20
|
+
/** Only validate and package — do not upload. */
|
|
21
|
+
dryRun?: boolean;
|
|
22
|
+
/** Path to a markdown file whose content becomes the release notes. */
|
|
23
|
+
notes?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ReleaseManifest {
|
|
27
|
+
version: string;
|
|
28
|
+
date: string;
|
|
29
|
+
artifactCount: number;
|
|
30
|
+
harnesses: string[];
|
|
31
|
+
governanceSummary: {
|
|
32
|
+
official: number;
|
|
33
|
+
partner: number;
|
|
34
|
+
community: number;
|
|
35
|
+
experimental: number;
|
|
36
|
+
unclassified: number;
|
|
37
|
+
};
|
|
38
|
+
collectionSummary: Record<string, number>;
|
|
39
|
+
perHarnessFileCounts: Record<string, number>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Run the full publish pipeline:
|
|
44
|
+
* 1. kanon validate --strict
|
|
45
|
+
* 2. kanon build
|
|
46
|
+
* 3. kanon catalog generate → catalog.json
|
|
47
|
+
* 4. Generate release-manifest.json
|
|
48
|
+
* 5. Package per-harness tarballs
|
|
49
|
+
* 6. Publish via configured backend (or report dry-run)
|
|
50
|
+
*/
|
|
51
|
+
export async function publish(options: PublishOptions = {}): Promise<void> {
|
|
52
|
+
const { dryRun = false } = options;
|
|
53
|
+
|
|
54
|
+
console.error(chalk.bold("\n📦 kanon publish\n"));
|
|
55
|
+
|
|
56
|
+
// ─── Step 1: Validate --strict ──────────────────────────────────────────────
|
|
57
|
+
console.error(chalk.cyan("1/5 Validating artifacts…"));
|
|
58
|
+
const validationResults = await validateAll([...VALIDATE_SOURCE_DIRS]);
|
|
59
|
+
const errors = validationResults.filter((r) => !r.valid);
|
|
60
|
+
if (errors.length > 0) {
|
|
61
|
+
console.error(
|
|
62
|
+
chalk.red(
|
|
63
|
+
`\n✗ Validation failed — ${errors.length} artifact(s) have errors:\n`,
|
|
64
|
+
),
|
|
65
|
+
);
|
|
66
|
+
for (const result of errors) {
|
|
67
|
+
console.error(chalk.red(` ${result.artifactName}:`));
|
|
68
|
+
for (const err of result.errors) {
|
|
69
|
+
console.error(chalk.red(` ${err.field}: ${err.message}`));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
console.error(
|
|
75
|
+
chalk.green(` ✓ ${validationResults.length} artifact(s) valid`),
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// ─── Step 1b: Rebuild MCP bridge ─────────────────────────────────────────────
|
|
79
|
+
console.error(chalk.cyan("1b Rebuilding MCP bridge…"));
|
|
80
|
+
const bridgeProc = Bun.spawnSync(["bun", "run", "build:bridge"], {
|
|
81
|
+
stdout: "pipe",
|
|
82
|
+
stderr: "pipe",
|
|
83
|
+
});
|
|
84
|
+
if (bridgeProc.exitCode !== 0) {
|
|
85
|
+
console.error(
|
|
86
|
+
chalk.red(" ✗ Bridge build failed — cannot publish plugin assets"),
|
|
87
|
+
);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
} else {
|
|
90
|
+
console.error(chalk.green(" ✓ bridge/mcp-server.cjs rebuilt"));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ─── Step 2: Build ───────────────────────────────────────────────────────────
|
|
94
|
+
console.error(chalk.cyan("2/5 Building all harnesses…"));
|
|
95
|
+
const buildResult = await build({
|
|
96
|
+
knowledgeDirs: [...BUILD_SOURCE_DIRS],
|
|
97
|
+
distDir: "dist",
|
|
98
|
+
templatesDir: "templates/harness-adapters",
|
|
99
|
+
mcpServersDir: "mcp-servers",
|
|
100
|
+
strict: true,
|
|
101
|
+
});
|
|
102
|
+
if (buildResult.errors.length > 0) {
|
|
103
|
+
console.error(
|
|
104
|
+
chalk.red(`\n✗ Build failed with ${buildResult.errors.length} error(s)`),
|
|
105
|
+
);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
console.error(
|
|
109
|
+
chalk.green(
|
|
110
|
+
` ✓ ${buildResult.artifactsCompiled} artifacts, ${buildResult.filesWritten} files`,
|
|
111
|
+
),
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
// ─── Step 3: Generate catalog ────────────────────────────────────────────────
|
|
115
|
+
console.error(chalk.cyan("3/5 Generating catalog…"));
|
|
116
|
+
const catalogEntries = await generateCatalog([...CATALOG_SOURCE_DIRS]);
|
|
117
|
+
await writeFile("catalog.json", serializeCatalog(catalogEntries), "utf-8");
|
|
118
|
+
console.error(
|
|
119
|
+
chalk.green(` ✓ catalog.json — ${catalogEntries.length} entries`),
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
// ─── Step 4: Generate release-manifest.json ──────────────────────────────────
|
|
123
|
+
console.error(chalk.cyan("4/5 Generating release manifest…"));
|
|
124
|
+
|
|
125
|
+
const version = options.tag ?? (await resolveVersion());
|
|
126
|
+
const _collections = await loadCollections("collections");
|
|
127
|
+
const membership = buildCollectionMembership(catalogEntries);
|
|
128
|
+
|
|
129
|
+
const collectionSummary: Record<string, number> = {};
|
|
130
|
+
for (const [name, members] of membership) {
|
|
131
|
+
collectionSummary[name] = members.length;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const governance = {
|
|
135
|
+
official: 0,
|
|
136
|
+
partner: 0,
|
|
137
|
+
community: 0,
|
|
138
|
+
experimental: 0,
|
|
139
|
+
unclassified: 0,
|
|
140
|
+
};
|
|
141
|
+
for (const entry of catalogEntries) {
|
|
142
|
+
const lane = entry.trust ?? "unclassified";
|
|
143
|
+
if (lane in governance) {
|
|
144
|
+
governance[lane as keyof typeof governance]++;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const perHarnessFileCounts: Record<string, number> = {};
|
|
149
|
+
for (const harness of SUPPORTED_HARNESSES) {
|
|
150
|
+
perHarnessFileCounts[harness] = await countDistFiles(join("dist", harness));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const manifest: ReleaseManifest = {
|
|
154
|
+
version,
|
|
155
|
+
date: new Date().toISOString(),
|
|
156
|
+
artifactCount: catalogEntries.length,
|
|
157
|
+
harnesses: SUPPORTED_HARNESSES.filter((h) => perHarnessFileCounts[h] > 0),
|
|
158
|
+
governanceSummary: governance,
|
|
159
|
+
collectionSummary,
|
|
160
|
+
perHarnessFileCounts,
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
await writeFile(
|
|
164
|
+
"release-manifest.json",
|
|
165
|
+
JSON.stringify(manifest, null, 2),
|
|
166
|
+
"utf-8",
|
|
167
|
+
);
|
|
168
|
+
console.error(chalk.green(` ✓ release-manifest.json`));
|
|
169
|
+
|
|
170
|
+
// ─── Step 5: Package + publish ───────────────────────────────────────────────
|
|
171
|
+
console.error(
|
|
172
|
+
chalk.cyan(
|
|
173
|
+
`5/5 ${dryRun ? "Packaging (dry run — no upload)" : "Publishing"}…`,
|
|
174
|
+
),
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
const assets: string[] = [
|
|
178
|
+
"catalog.json",
|
|
179
|
+
"release-manifest.json",
|
|
180
|
+
"bridge/mcp-server.cjs",
|
|
181
|
+
];
|
|
182
|
+
|
|
183
|
+
// Create per-harness tarballs
|
|
184
|
+
const distExists = await exists("dist");
|
|
185
|
+
if (distExists) {
|
|
186
|
+
await mkdir(".forge-publish", { recursive: true });
|
|
187
|
+
for (const harness of SUPPORTED_HARNESSES) {
|
|
188
|
+
const harnessDir = join("dist", harness);
|
|
189
|
+
if (!(await exists(harnessDir))) continue;
|
|
190
|
+
|
|
191
|
+
const tarName = `.forge-publish/dist-${harness}.tar.gz`;
|
|
192
|
+
const tarProc = Bun.spawnSync(
|
|
193
|
+
["tar", "-czf", tarName, "-C", "dist", harness],
|
|
194
|
+
{ stdout: "pipe", stderr: "pipe" },
|
|
195
|
+
);
|
|
196
|
+
if (tarProc.exitCode === 0) {
|
|
197
|
+
assets.push(tarName);
|
|
198
|
+
console.error(chalk.dim(` Packaged ${tarName}`));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (dryRun) {
|
|
204
|
+
console.error(chalk.yellow("\n Dry run — would publish:"));
|
|
205
|
+
console.error(chalk.yellow(` Tag: ${version}`));
|
|
206
|
+
for (const asset of assets) {
|
|
207
|
+
console.error(chalk.yellow(` Asset: ${asset}`));
|
|
208
|
+
}
|
|
209
|
+
console.error(chalk.green("\n✓ Dry run complete — no files uploaded"));
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Publish via configured backend
|
|
214
|
+
const config = await loadForgeConfig();
|
|
215
|
+
const publishConfig = config.publish;
|
|
216
|
+
const backendName = options.backend ?? publishConfig?.backend ?? "github";
|
|
217
|
+
|
|
218
|
+
if (backendName === "github") {
|
|
219
|
+
await publishToGitHub(version, assets, options.notes);
|
|
220
|
+
} else if (backendName === "s3") {
|
|
221
|
+
await publishToS3(version, config, assets);
|
|
222
|
+
} else {
|
|
223
|
+
console.error(
|
|
224
|
+
chalk.yellow(
|
|
225
|
+
` Unknown publish backend "${backendName}" — assets packaged but not uploaded`,
|
|
226
|
+
),
|
|
227
|
+
);
|
|
228
|
+
console.error(
|
|
229
|
+
chalk.yellow(` Configure publish.backend in kanon.config.yaml`),
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async function publishToGitHub(
|
|
235
|
+
tag: string,
|
|
236
|
+
assets: string[],
|
|
237
|
+
notesFile?: string,
|
|
238
|
+
): Promise<void> {
|
|
239
|
+
const args = [
|
|
240
|
+
"gh",
|
|
241
|
+
"release",
|
|
242
|
+
"create",
|
|
243
|
+
tag,
|
|
244
|
+
"--title",
|
|
245
|
+
tag,
|
|
246
|
+
"--generate-notes",
|
|
247
|
+
];
|
|
248
|
+
|
|
249
|
+
if (notesFile && (await exists(notesFile))) {
|
|
250
|
+
args.push("--notes-file", notesFile);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
args.push(...assets);
|
|
254
|
+
|
|
255
|
+
console.error(chalk.dim(` Running: ${args.join(" ")}`));
|
|
256
|
+
const proc = Bun.spawnSync(args, { stdout: "pipe", stderr: "pipe" });
|
|
257
|
+
|
|
258
|
+
if (proc.exitCode !== 0) {
|
|
259
|
+
const stderr = new TextDecoder().decode(proc.stderr);
|
|
260
|
+
throw new Error(`gh release create failed: ${stderr}`);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const stdout = new TextDecoder().decode(proc.stdout);
|
|
264
|
+
console.error(chalk.green(`\n✓ Published ${tag} to GitHub`));
|
|
265
|
+
if (stdout.trim()) console.error(chalk.dim(` ${stdout.trim()}`));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async function publishToS3(
|
|
269
|
+
version: string,
|
|
270
|
+
config: Awaited<ReturnType<typeof loadForgeConfig>>,
|
|
271
|
+
_assets: string[],
|
|
272
|
+
): Promise<void> {
|
|
273
|
+
// S3 publishing is configuration-driven; delegate to aws CLI
|
|
274
|
+
const s3Config = config.install?.backends?.internal;
|
|
275
|
+
if (s3Config?.type !== "s3") {
|
|
276
|
+
throw new Error(
|
|
277
|
+
"No S3 backend configured. Add an 's3' backend to kanon.config.yaml",
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const prefix = (s3Config.prefix ?? "").replace(/\/$/, "");
|
|
282
|
+
const dest = `s3://${s3Config.bucket}/${prefix}/${version}/`;
|
|
283
|
+
|
|
284
|
+
const args = ["aws", "s3", "sync", ".forge-publish/", dest];
|
|
285
|
+
if (s3Config.region) args.push("--region", s3Config.region);
|
|
286
|
+
if (s3Config.endpoint) args.push("--endpoint-url", s3Config.endpoint);
|
|
287
|
+
|
|
288
|
+
// Also upload catalog.json and release-manifest.json to the versioned prefix
|
|
289
|
+
for (const asset of ["catalog.json", "release-manifest.json"]) {
|
|
290
|
+
if (await exists(asset)) {
|
|
291
|
+
const uploadArgs = ["aws", "s3", "cp", asset, `${dest}${asset}`];
|
|
292
|
+
if (s3Config.region) uploadArgs.push("--region", s3Config.region);
|
|
293
|
+
Bun.spawnSync(uploadArgs, { stdout: "pipe", stderr: "pipe" });
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Upload latest catalog.json to the bucket root for easy browsing
|
|
298
|
+
const rootArgs = [
|
|
299
|
+
"aws",
|
|
300
|
+
"s3",
|
|
301
|
+
"cp",
|
|
302
|
+
"catalog.json",
|
|
303
|
+
`s3://${s3Config.bucket}/${prefix}/catalog.json`,
|
|
304
|
+
];
|
|
305
|
+
if (s3Config.region) rootArgs.push("--region", s3Config.region);
|
|
306
|
+
Bun.spawnSync(rootArgs, { stdout: "pipe", stderr: "pipe" });
|
|
307
|
+
|
|
308
|
+
const proc = Bun.spawnSync(args, { stdout: "pipe", stderr: "pipe" });
|
|
309
|
+
if (proc.exitCode !== 0) {
|
|
310
|
+
const stderr = new TextDecoder().decode(proc.stderr);
|
|
311
|
+
throw new Error(`aws s3 sync failed: ${stderr}`);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
console.error(chalk.green(`\n✓ Published ${version} to ${dest}`));
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async function resolveVersion(): Promise<string> {
|
|
318
|
+
try {
|
|
319
|
+
const pkg = (await Bun.file("package.json").json()) as { version?: string };
|
|
320
|
+
return `v${pkg.version ?? "0.2.0"}`;
|
|
321
|
+
} catch {
|
|
322
|
+
return "v0.2.0";
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async function countDistFiles(dir: string): Promise<number> {
|
|
327
|
+
if (!(await exists(dir))) return 0;
|
|
328
|
+
let count = 0;
|
|
329
|
+
try {
|
|
330
|
+
const entries = await readdir(dir, {
|
|
331
|
+
withFileTypes: true,
|
|
332
|
+
recursive: true,
|
|
333
|
+
});
|
|
334
|
+
count = entries.filter((e) => e.isFile()).length;
|
|
335
|
+
} catch {
|
|
336
|
+
// ignore
|
|
337
|
+
}
|
|
338
|
+
return count;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export async function publishCommand(options: {
|
|
342
|
+
backend?: string;
|
|
343
|
+
tag?: string;
|
|
344
|
+
dryRun?: boolean;
|
|
345
|
+
notes?: string;
|
|
346
|
+
}): Promise<void> {
|
|
347
|
+
try {
|
|
348
|
+
await publish(options);
|
|
349
|
+
} catch (err: unknown) {
|
|
350
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
351
|
+
console.error(chalk.red(`\n✗ Publish failed: ${msg}`));
|
|
352
|
+
process.exit(1);
|
|
353
|
+
}
|
|
354
|
+
}
|