intentdna 1.7.4 → 1.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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/LICENSE +201 -0
- package/README.md +143 -194
- package/dist/assets/candidates.d.ts +124 -0
- package/dist/assets/candidates.js +1249 -0
- package/dist/assets/catalog.d.ts +101 -0
- package/dist/assets/catalog.js +311 -0
- package/dist/assets/evidence.d.ts +93 -0
- package/dist/assets/evidence.js +391 -0
- package/dist/assets/index.d.ts +4 -0
- package/dist/assets/index.js +4 -0
- package/dist/assets/metadata.d.ts +19 -0
- package/dist/assets/metadata.js +227 -0
- package/dist/cli/commands/assets.d.ts +65 -0
- package/dist/cli/commands/assets.js +2871 -0
- package/dist/cli/commands/auth.d.ts +322 -0
- package/dist/cli/commands/auth.js +1592 -0
- package/dist/cli/commands/cloud-auth-guidance.d.ts +2 -0
- package/dist/cli/commands/cloud-auth-guidance.js +4 -0
- package/dist/cli/commands/cloud.d.ts +1359 -0
- package/dist/cli/commands/cloud.js +8364 -0
- package/dist/cli/commands/evolve.js +18 -2
- package/dist/cli/commands/generate.js +25 -2
- package/dist/cli/commands/guard.d.ts +1 -1
- package/dist/cli/commands/guard.js +1 -1
- package/dist/cli/commands/guide.d.ts +15 -0
- package/dist/cli/commands/guide.js +1747 -0
- package/dist/cli/commands/init.d.ts +12 -0
- package/dist/cli/commands/init.js +95 -60
- package/dist/cli/commands/org.d.ts +16 -0
- package/dist/cli/commands/org.js +2393 -0
- package/dist/cli/commands/release.d.ts +59 -0
- package/dist/cli/commands/release.js +716 -0
- package/dist/cli/commands/report.d.ts +82 -0
- package/dist/cli/commands/report.js +1516 -0
- package/dist/cli/commands/run.d.ts +6 -1
- package/dist/cli/commands/run.js +95 -26
- package/dist/cli/commands/sync.d.ts +8 -1
- package/dist/cli/commands/sync.js +436 -141
- package/dist/cli/commands/templates.d.ts +64 -0
- package/dist/cli/commands/templates.js +2151 -1
- package/dist/cli/commands/workflow.d.ts +26 -0
- package/dist/cli/commands/workflow.js +3053 -0
- package/dist/cli/index.js +719 -42
- package/dist/compiler/cascade.js +4 -9
- package/dist/compiler/compile.js +1 -0
- package/dist/governance/evidence-capture-attribution.d.ts +12 -0
- package/dist/governance/evidence-capture-attribution.js +46 -0
- package/dist/governance/index.d.ts +2 -0
- package/dist/governance/index.js +1 -0
- package/dist/hooks/cli.d.ts +6 -0
- package/dist/hooks/cli.js +168 -12
- package/dist/hooks/enforce.d.ts +1 -0
- package/dist/hooks/protocol.d.ts +1 -1
- package/dist/hooks/protocol.js +1 -1
- package/dist/hooks/state.d.ts +41 -0
- package/dist/hooks/state.js +126 -2
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/mcp/tools-state.js +2 -0
- package/dist/organization/index.d.ts +121 -0
- package/dist/organization/index.js +413 -0
- package/dist/runtime/claude-sync-target.d.ts +49 -0
- package/dist/runtime/claude-sync-target.js +280 -0
- package/dist/runtime/codex-adapter.d.ts +254 -0
- package/dist/runtime/codex-adapter.js +1204 -0
- package/dist/runtime/codex-sync-target.d.ts +33 -0
- package/dist/runtime/codex-sync-target.js +241 -0
- package/dist/runtime/index.d.ts +8 -0
- package/dist/runtime/index.js +4 -0
- package/dist/runtime/output-artifact-registry.d.ts +1 -1
- package/dist/runtime/output-artifact-registry.js +2 -0
- package/dist/runtime/settings-adapter.d.ts +1 -1
- package/dist/runtime/settings-adapter.js +1 -1
- package/dist/runtime/skill-adapter.d.ts +5 -1
- package/dist/runtime/skill-adapter.js +120 -34
- package/dist/runtime/sync-artifact-registry.d.ts +99 -0
- package/dist/runtime/sync-artifact-registry.js +194 -0
- package/dist/runtime/sync-target-plan.d.ts +45 -0
- package/dist/runtime/sync-target-plan.js +19 -0
- package/dist/schema/controller-registry.d.ts +8 -1
- package/dist/schema/controller-registry.js +8 -0
- package/dist/schema/types.d.ts +35 -23
- package/dist/schema/types.js +10 -1
- package/dist/schema/validate.js +22 -22
- package/dist/schema/validators/controllers.js +38 -20
- package/dist/schema/workflow-authoring-contract.d.ts +29 -0
- package/dist/schema/workflow-authoring-contract.js +109 -0
- package/dist/schema/yaml-parser.js +18 -0
- package/dist/templates/api-backend.dna.yaml +19 -0
- package/dist/templates/brainstorming-first.dna.yaml +19 -0
- package/dist/templates/code-cleanup.dna.yaml +19 -0
- package/dist/templates/code-review-pipeline.dna.yaml +18 -0
- package/dist/templates/documentation-writer.dna.yaml +19 -0
- package/dist/templates/flutter-behavior-lock.dna.yaml +19 -0
- package/dist/templates/flutter-refactoring-rescue.dna.yaml +19 -0
- package/dist/templates/flutter-rewrite-diagnosis-review.dna.yaml +210 -0
- package/dist/templates/flutter-rewrite-fix-review-loop.dna.yaml +260 -0
- package/dist/templates/flutter-rewrite-new.dna.yaml +506 -0
- package/dist/templates/flutter-rewrite.dna.yaml +20 -0
- package/dist/templates/frontend-quality.dna.yaml +19 -0
- package/dist/templates/full-pipeline.dna.yaml +20 -0
- package/dist/templates/marketplace.d.ts +92 -0
- package/dist/templates/marketplace.js +502 -0
- package/dist/templates/metadata.d.ts +25 -0
- package/dist/templates/metadata.js +136 -3
- package/dist/templates/mobile-dev.dna.yaml +19 -0
- package/dist/templates/multi-agent-handoff-coordination.dna.yaml +156 -0
- package/dist/templates/multi-perspective-review.dna.yaml +19 -0
- package/dist/templates/persistent-executor.dna.yaml +19 -0
- package/dist/templates/qa-loop.dna.yaml +18 -0
- package/dist/templates/release-evidence-gate.dna.yaml +131 -0
- package/dist/templates/requirements-gate.dna.yaml +19 -0
- package/dist/templates/research-orchestration.dna.yaml +19 -0
- package/dist/templates/root-cause-analysis.dna.yaml +19 -0
- package/dist/templates/safe-refactoring.dna.yaml +321 -0
- package/dist/templates/secure-dev.dna.yaml +19 -0
- package/dist/templates/subagent-parallel.dna.yaml +19 -0
- package/dist/templates/systematic-debugging.dna.yaml +19 -0
- package/dist/templates/tdd-strict.dna.yaml +19 -0
- package/dist/templates/ui-visual-regression-qa.dna.yaml +133 -0
- package/dist/templates/workflow-author.dna.yaml +302 -0
- package/dist/templates/workflow-blocked-scope-triage.dna.yaml +242 -0
- package/dist/templates/workflow-completion-audit.dna.yaml +254 -0
- package/dist/templates/workflow-cross-boundary-resource-approval.dna.yaml +249 -0
- package/dist/templates/workflow-evidence-review.dna.yaml +198 -0
- package/dist/templates/workflow-module-inventory.dna.yaml +257 -0
- package/dist/templates/workflow-runtime-smoke-review.dna.yaml +252 -0
- package/dist/templates/workflow-verification-command-routing.dna.yaml +278 -0
- package/dist/workflow/authoring-packet.d.ts +67 -0
- package/dist/workflow/authoring-packet.js +218 -0
- package/dist/workflow/draft.d.ts +159 -0
- package/dist/workflow/draft.js +800 -0
- package/dist/workflow/index.d.ts +3 -0
- package/dist/workflow/index.js +3 -0
- package/dist/workflow/suggestion-review.d.ts +65 -0
- package/dist/workflow/suggestion-review.js +245 -0
- package/package.json +1 -1
- package/spec/README.md +15 -48
- package/spec/codex-adapter-contract.md +176 -0
- package/spec/sync-target-plan.md +158 -0
- package/spec/workflow-capability-ir.md +153 -0
- package/spec/agent-dispatch-fix.md +0 -116
- package/spec/control-plane-convergence-handoff-2026-04-24.md +0 -432
- package/spec/flutter-rewrite-template-optimization.md +0 -301
- package/spec/foundation-hardening.md +0 -178
- package/spec/hooks-infra-harness-hardening.md +0 -741
- package/spec/multi-config.md +0 -172
- package/spec/parallel-isolation.md +0 -268
- package/spec/template-version-namespace-fix.md +0 -653
- package/spec/workflow-pipeline.md +0 -101
|
@@ -0,0 +1,716 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dna release — read-only release/deploy gate planning.
|
|
3
|
+
*/
|
|
4
|
+
import { execFile } from "node:child_process";
|
|
5
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
6
|
+
import { dirname, join, resolve as resolvePath } from "node:path";
|
|
7
|
+
import { relative, resolve } from "node:path";
|
|
8
|
+
import { promisify } from "node:util";
|
|
9
|
+
const execFileAsync = promisify(execFile);
|
|
10
|
+
const VERCEL_CLI = "npx -y vercel@latest";
|
|
11
|
+
const SUPABASE_CLI = "npx -y supabase@latest";
|
|
12
|
+
const CLOUD_FUNCTIONS = ["issue-api-token", "bootstrap-account", "ingest-report", "list-receipts", "manage-api-tokens", "list-projects", "publish-template", "list-template-publications", "revoke-template-publication"];
|
|
13
|
+
const WEB_SURFACE_RULES = [
|
|
14
|
+
{
|
|
15
|
+
id: "online",
|
|
16
|
+
label: "IntentDNA online public system",
|
|
17
|
+
prefix: "apps/online/",
|
|
18
|
+
include: (file) => isOnlineDeploySurfaceFile(file),
|
|
19
|
+
requirement: "required",
|
|
20
|
+
requiredWhen: "apps/online public, embed, management, or static handoff surface changed",
|
|
21
|
+
cwd: "apps/online",
|
|
22
|
+
buildCommand: "npm --prefix apps/online run build",
|
|
23
|
+
testCommand: "npm --prefix apps/online test",
|
|
24
|
+
previewCommand: "npm --prefix apps/online run preview",
|
|
25
|
+
deployCommand: `${VERCEL_CLI} deploy --prod`,
|
|
26
|
+
verificationRoutes: ["/", "/?mode=public", "/?mode=public&embed=1"],
|
|
27
|
+
verification: [
|
|
28
|
+
"Confirm Vercel CLI is available through npx or an equivalent installed vercel command.",
|
|
29
|
+
"Capture the deployment URL from Vercel CLI stdout.",
|
|
30
|
+
"Wait for the deployment to complete before route verification.",
|
|
31
|
+
"Verify the listed online routes, including public/embed plus any changed management routes.",
|
|
32
|
+
"Record the deployed URL and checked routes in the release evidence or final report.",
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: "dashboard",
|
|
37
|
+
label: "IntentDNA dashboard projection",
|
|
38
|
+
prefix: "apps/dashboard/",
|
|
39
|
+
requirement: "conditional",
|
|
40
|
+
requiredWhen: "dashboard changes are intended for external preview through a deployed URL",
|
|
41
|
+
cwd: "apps/dashboard",
|
|
42
|
+
buildCommand: "npm --prefix apps/dashboard run build",
|
|
43
|
+
testCommand: "npm --prefix apps/dashboard test",
|
|
44
|
+
previewCommand: "npm --prefix apps/dashboard run preview",
|
|
45
|
+
deployCommand: `${VERCEL_CLI} deploy --prod`,
|
|
46
|
+
verificationRoutes: ["/", "/workflows", "/evidence", "/health"],
|
|
47
|
+
verification: [
|
|
48
|
+
"Confirm Vercel CLI is available through npx or an equivalent installed vercel command.",
|
|
49
|
+
"Capture the deployment URL from Vercel CLI stdout when the dashboard is externally reviewed.",
|
|
50
|
+
"Wait for the deployment to complete before route verification.",
|
|
51
|
+
"Verify overview, workflows, evidence, and health remain read-only projection views.",
|
|
52
|
+
"Record the deployed URL and checked routes in the release evidence or final report.",
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: "public-static",
|
|
57
|
+
label: "Public static or article surface",
|
|
58
|
+
prefix: "public/",
|
|
59
|
+
requirement: "required",
|
|
60
|
+
requiredWhen: "public static assets, public articles, launch pages, or preview pages changed",
|
|
61
|
+
cwd: ".",
|
|
62
|
+
buildCommand: "npm run build",
|
|
63
|
+
testCommand: "npm test",
|
|
64
|
+
previewCommand: "run the owning app preview for the changed public route",
|
|
65
|
+
deployCommand: `${VERCEL_CLI} deploy --prod`,
|
|
66
|
+
verificationRoutes: [],
|
|
67
|
+
verification: [
|
|
68
|
+
"Confirm Vercel CLI is available through npx or an equivalent installed vercel command.",
|
|
69
|
+
"Capture the deployment URL from Vercel CLI stdout.",
|
|
70
|
+
"Wait for the deployment to complete before route verification.",
|
|
71
|
+
"Verify the changed public article, launch page, or asset is visible at its deployed route.",
|
|
72
|
+
"Record the deployed URL and checked routes in the release evidence or final report.",
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
];
|
|
76
|
+
const CLOUD_FILE_PREFIXES = [
|
|
77
|
+
"supabase/",
|
|
78
|
+
"src/cli/commands/cloud.ts",
|
|
79
|
+
"src/cli/commands/auth.ts",
|
|
80
|
+
"src/cli/commands/report.ts",
|
|
81
|
+
"src/cli/commands/templates.ts",
|
|
82
|
+
".omx/plans/prd-intentdna-supabase-saas-backend.md",
|
|
83
|
+
".omx/plans/test-spec-intentdna-supabase-saas-backend.md",
|
|
84
|
+
];
|
|
85
|
+
const CLOUD_RELEASE_CHECKS = [
|
|
86
|
+
{
|
|
87
|
+
id: "cloud-contract-check",
|
|
88
|
+
label: "Local Supabase migration/function contract check",
|
|
89
|
+
command: "node dist/cli/index.js cloud contract-check --json",
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: "cloud-edge-check",
|
|
93
|
+
label: "Local Supabase Edge Function typecheck",
|
|
94
|
+
command: "node dist/cli/index.js cloud edge-check --json",
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: "cloud-deploy-plan",
|
|
98
|
+
label: "Guarded Supabase live deploy execution dry-run",
|
|
99
|
+
command: "node dist/cli/index.js cloud deploy-execute --dry-run --json",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: "cloud-function-health",
|
|
103
|
+
label: "Deployed Supabase Edge Function health checks",
|
|
104
|
+
command: "node dist/cli/index.js cloud function-health --json",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
id: "cloud-secrets-check",
|
|
108
|
+
label: "Remote Supabase Function secret name presence check",
|
|
109
|
+
command: "node dist/cli/index.js cloud secrets-check --json",
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: "cloud-vercel-env-check",
|
|
113
|
+
label: "Vercel production public Supabase env presence check",
|
|
114
|
+
command: "node dist/cli/index.js cloud vercel-env-check --json",
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
id: "cloud-ingest-smoke",
|
|
118
|
+
label: "Live signed report upload smoke with local receipt acceptance check",
|
|
119
|
+
command: "node dist/cli/index.js cloud smoke-execute --yes --json > <cloud-smoke-receipt.json> && node dist/cli/index.js cloud smoke-receipt-check --receipt <cloud-smoke-receipt.json> --json",
|
|
120
|
+
},
|
|
121
|
+
];
|
|
122
|
+
const BOUNDARIES = [
|
|
123
|
+
"dna CLI/core/runtime, docs, and internal wiki changes do not require Vercel deployment by themselves.",
|
|
124
|
+
"Vercel is required only for web/public deployed surfaces that reviewers or users access through a deployed URL.",
|
|
125
|
+
"Supabase SaaS changes require cloud contract, Edge Function, deploy-plan, remote secret-name presence, Vercel public env presence, function health, and live ingest-smoke evidence before production release claims.",
|
|
126
|
+
"Deployment is separate from organization rollout and runtime sync.",
|
|
127
|
+
"A web deploy is not complete until the deployed URL is opened or fetched and the intended visible change is verified.",
|
|
128
|
+
"A cloud deploy is not complete until Supabase migrations/functions/secrets and ingestion smoke evidence are recorded.",
|
|
129
|
+
"Dashboard remains a read-only projection; deployment must not turn it into policy truth or a workflow builder.",
|
|
130
|
+
];
|
|
131
|
+
function releaseEvidenceBoundaries() {
|
|
132
|
+
return [
|
|
133
|
+
...BOUNDARIES,
|
|
134
|
+
"Release evidence records deploy-plan outputs, supplied deployed URL checks, and supplied cloud checks; it does not run Vercel or Supabase commands.",
|
|
135
|
+
"Build/test success is local readiness evidence only and is not release completion.",
|
|
136
|
+
"Release completion requires a deployed URL plus verification of every route listed by deploy-plan when web surfaces changed.",
|
|
137
|
+
"Release completion requires every cloud check listed by deploy-plan when Supabase SaaS surfaces changed.",
|
|
138
|
+
"Release evidence is separate from runtime sync, org rollout, dashboard state, and policy truth.",
|
|
139
|
+
];
|
|
140
|
+
}
|
|
141
|
+
function normalizePath(projectDir, file) {
|
|
142
|
+
const trimmed = file.trim();
|
|
143
|
+
if (!trimmed)
|
|
144
|
+
return "";
|
|
145
|
+
const relativePath = trimmed.startsWith("/")
|
|
146
|
+
? relative(projectDir, trimmed)
|
|
147
|
+
: trimmed;
|
|
148
|
+
return relativePath.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
149
|
+
}
|
|
150
|
+
function unique(values) {
|
|
151
|
+
return [...new Set(values.filter(Boolean))];
|
|
152
|
+
}
|
|
153
|
+
function shellQuote(value) {
|
|
154
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
155
|
+
}
|
|
156
|
+
function commandInCwd(cwd, command) {
|
|
157
|
+
return cwd === "." ? command : `cd ${cwd} && ${command}`;
|
|
158
|
+
}
|
|
159
|
+
function deploymentUrlVariable(surfaceId) {
|
|
160
|
+
return `${surfaceId.replace(/[^a-z0-9]+/gi, "_").replace(/^_+|_+$/g, "").toUpperCase()}_DEPLOYMENT_URL`;
|
|
161
|
+
}
|
|
162
|
+
async function gitLines(projectDir, args) {
|
|
163
|
+
const { stdout } = await execFileAsync("git", args, {
|
|
164
|
+
cwd: projectDir,
|
|
165
|
+
timeout: 5000,
|
|
166
|
+
maxBuffer: 1024 * 1024,
|
|
167
|
+
});
|
|
168
|
+
return stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
169
|
+
}
|
|
170
|
+
async function collectChangedFiles(projectDir, files) {
|
|
171
|
+
if (files && files.length > 0) {
|
|
172
|
+
return {
|
|
173
|
+
source: "explicit_files",
|
|
174
|
+
files: unique(files.map((file) => normalizePath(projectDir, file))),
|
|
175
|
+
warnings: [],
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
const changed = [
|
|
180
|
+
...await gitLines(projectDir, ["diff", "--name-only", "--cached"]),
|
|
181
|
+
...await gitLines(projectDir, ["diff", "--name-only"]),
|
|
182
|
+
...await gitLines(projectDir, ["ls-files", "--others", "--exclude-standard"]),
|
|
183
|
+
];
|
|
184
|
+
return {
|
|
185
|
+
source: changed.length > 0 ? "git_worktree" : "none",
|
|
186
|
+
files: unique(changed.map((file) => normalizePath(projectDir, file))),
|
|
187
|
+
warnings: [],
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
return {
|
|
192
|
+
source: "none",
|
|
193
|
+
files: [],
|
|
194
|
+
warnings: [`Could not inspect git changes: ${err instanceof Error ? err.message : String(err)}`],
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function buildSurfacePlans(files) {
|
|
199
|
+
const webSurfaces = [];
|
|
200
|
+
for (const rule of WEB_SURFACE_RULES) {
|
|
201
|
+
const surfaceFiles = files.filter((file) => {
|
|
202
|
+
const matchesPrefix = file === rule.prefix.slice(0, -1) || file.startsWith(rule.prefix);
|
|
203
|
+
return matchesPrefix && (rule.include ? rule.include(file) : true);
|
|
204
|
+
});
|
|
205
|
+
if (surfaceFiles.length === 0)
|
|
206
|
+
continue;
|
|
207
|
+
const urlVariable = deploymentUrlVariable(rule.id);
|
|
208
|
+
const verificationRoutes = routesForSurface(rule, surfaceFiles);
|
|
209
|
+
webSurfaces.push({
|
|
210
|
+
id: rule.id,
|
|
211
|
+
label: rule.label,
|
|
212
|
+
kind: "web",
|
|
213
|
+
requirement: rule.requirement,
|
|
214
|
+
requiredWhen: rule.requiredWhen,
|
|
215
|
+
cwd: rule.cwd,
|
|
216
|
+
files: surfaceFiles,
|
|
217
|
+
localCommands: [`${VERCEL_CLI} --version`, rule.buildCommand, rule.testCommand, rule.previewCommand],
|
|
218
|
+
deploymentUrlVariable: urlVariable,
|
|
219
|
+
deployCommand: `${urlVariable}=$(${commandInCwd(rule.cwd, rule.deployCommand)})`,
|
|
220
|
+
readinessCommand: commandInCwd(rule.cwd, `${VERCEL_CLI} inspect "$${urlVariable}" --wait`),
|
|
221
|
+
verificationCommands: verificationRoutes.map((route) => commandInCwd(rule.cwd, `${VERCEL_CLI} curl ${shellQuote(route)} --deployment "$${urlVariable}"`)),
|
|
222
|
+
verification: rule.verification,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
const cloudSurface = buildCloudSurfacePlan(files);
|
|
226
|
+
return cloudSurface ? [...webSurfaces, cloudSurface] : webSurfaces;
|
|
227
|
+
}
|
|
228
|
+
function isCloudFile(file) {
|
|
229
|
+
return CLOUD_FILE_PREFIXES.some((prefix) => file === prefix.replace(/\/$/, "") || file.startsWith(prefix));
|
|
230
|
+
}
|
|
231
|
+
function buildCloudSurfacePlan(files) {
|
|
232
|
+
const cloudFiles = files.filter(isCloudFile);
|
|
233
|
+
if (cloudFiles.length === 0)
|
|
234
|
+
return undefined;
|
|
235
|
+
const functionDeployCommands = CLOUD_FUNCTIONS.map((name) => `${SUPABASE_CLI} functions deploy ${name}`);
|
|
236
|
+
return {
|
|
237
|
+
id: "supabase-cloud",
|
|
238
|
+
label: "IntentDNA Supabase SaaS backend",
|
|
239
|
+
kind: "cloud",
|
|
240
|
+
requirement: "required",
|
|
241
|
+
requiredWhen: "Supabase migrations, Edge Functions, cloud auth/report CLI, or SaaS backend plans changed",
|
|
242
|
+
cwd: ".",
|
|
243
|
+
files: cloudFiles,
|
|
244
|
+
localCommands: [
|
|
245
|
+
"npm run build",
|
|
246
|
+
"npm test",
|
|
247
|
+
"node dist/cli/index.js cloud credential-check --json",
|
|
248
|
+
"node dist/cli/index.js cloud contract-check --json",
|
|
249
|
+
"node dist/cli/index.js cloud edge-check --json",
|
|
250
|
+
"node dist/cli/index.js cloud deploy-plan --json",
|
|
251
|
+
"node dist/cli/index.js cloud deploy-execute --dry-run --json",
|
|
252
|
+
"node dist/cli/index.js cloud secrets-check --json",
|
|
253
|
+
"node dist/cli/index.js cloud smoke-execute --dry-run --json",
|
|
254
|
+
],
|
|
255
|
+
deploymentUrlVariable: "INTENTDNA_CLOUD_URL",
|
|
256
|
+
deployCommand: [
|
|
257
|
+
`${SUPABASE_CLI} link --project-ref "$SUPABASE_PROJECT_REF"`,
|
|
258
|
+
`${SUPABASE_CLI} db push`,
|
|
259
|
+
`${SUPABASE_CLI} secrets set INTENTDNA_TOKEN_PEPPER="$INTENTDNA_TOKEN_PEPPER"`,
|
|
260
|
+
`${SUPABASE_CLI} secrets set SUPABASE_SECRET_KEYS="$SUPABASE_SECRET_KEYS"`,
|
|
261
|
+
`${SUPABASE_CLI} secrets set INTENTDNA_INGEST_MAX_BYTES=10485760`,
|
|
262
|
+
...functionDeployCommands,
|
|
263
|
+
].join(" && "),
|
|
264
|
+
readinessCommand: "node dist/cli/index.js cloud deploy-plan --json",
|
|
265
|
+
verificationCommands: [
|
|
266
|
+
"node dist/cli/index.js cloud contract-check --json",
|
|
267
|
+
"node dist/cli/index.js cloud edge-check --json",
|
|
268
|
+
"node dist/cli/index.js cloud deploy-execute --dry-run --json",
|
|
269
|
+
"node dist/cli/index.js cloud function-health --json",
|
|
270
|
+
"node dist/cli/index.js cloud secrets-check --json",
|
|
271
|
+
"node dist/cli/index.js cloud vercel-env-check --json",
|
|
272
|
+
"node dist/cli/index.js cloud smoke-execute --dry-run --json",
|
|
273
|
+
"node dist/cli/index.js cloud smoke-execute --yes --json > <cloud-smoke-receipt.json>",
|
|
274
|
+
"node dist/cli/index.js cloud smoke-receipt-check --receipt <cloud-smoke-receipt.json> --json",
|
|
275
|
+
],
|
|
276
|
+
verification: [
|
|
277
|
+
"Run cloud contract-check and edge-check locally before any Supabase deployment claim.",
|
|
278
|
+
"Confirm deploy-plan is ready or records the exact missing Supabase credentials/secrets.",
|
|
279
|
+
"Confirm Vercel production has the browser-safe public Supabase env names before online SaaS projection claims.",
|
|
280
|
+
"After Supabase deployment, verify the deployed ingest-report function and run a signed report upload smoke.",
|
|
281
|
+
"Record cloud checks in release evidence with --checked-cloud-check for each completed check.",
|
|
282
|
+
"Cloud release evidence is persistence/auth/transport evidence; it does not grant runtime authority.",
|
|
283
|
+
],
|
|
284
|
+
cloudChecks: CLOUD_RELEASE_CHECKS,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
function routesForSurface(rule, files) {
|
|
288
|
+
if (rule.id === "online")
|
|
289
|
+
return onlineRoutesForFiles(rule.verificationRoutes, files);
|
|
290
|
+
if (rule.id !== "public-static")
|
|
291
|
+
return unique(rule.verificationRoutes);
|
|
292
|
+
const routes = files.map(publicRouteForFile).filter((route) => route !== undefined);
|
|
293
|
+
return unique(routes).slice(0, 5);
|
|
294
|
+
}
|
|
295
|
+
function isOnlineDeploySurfaceFile(file) {
|
|
296
|
+
if (!file.startsWith("apps/online/"))
|
|
297
|
+
return false;
|
|
298
|
+
if (file.startsWith("apps/online/src/"))
|
|
299
|
+
return true;
|
|
300
|
+
if (file.startsWith("apps/online/public/"))
|
|
301
|
+
return true;
|
|
302
|
+
if (file === "apps/online/index.html")
|
|
303
|
+
return true;
|
|
304
|
+
if (file === "apps/online/package.json")
|
|
305
|
+
return true;
|
|
306
|
+
if (file === "apps/online/package-lock.json")
|
|
307
|
+
return true;
|
|
308
|
+
if (file === "apps/online/tsconfig.json")
|
|
309
|
+
return true;
|
|
310
|
+
if (file === "apps/online/vite.config.ts" || file === "apps/online/vite.config.mts")
|
|
311
|
+
return true;
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
function onlineRoutesForFiles(baseRoutes, files) {
|
|
315
|
+
const routes = [...baseRoutes];
|
|
316
|
+
const add = (route) => {
|
|
317
|
+
if (!routes.includes(route))
|
|
318
|
+
routes.push(route);
|
|
319
|
+
};
|
|
320
|
+
for (const file of files) {
|
|
321
|
+
if (file === "apps/online/src/app/main.ts" || file === "apps/online/src/main.ts") {
|
|
322
|
+
add("/runtime");
|
|
323
|
+
add("/templates");
|
|
324
|
+
add("/readiness");
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
if (file.includes("template") || file.includes("marketplace"))
|
|
328
|
+
add("/templates");
|
|
329
|
+
if (file.includes("runtime") || file.includes("report") || file.includes("cloud"))
|
|
330
|
+
add("/runtime");
|
|
331
|
+
if (file.includes("readiness") || file.includes("release"))
|
|
332
|
+
add("/readiness");
|
|
333
|
+
}
|
|
334
|
+
return unique(routes);
|
|
335
|
+
}
|
|
336
|
+
function publicRouteForFile(file) {
|
|
337
|
+
if (!file.startsWith("public/"))
|
|
338
|
+
return undefined;
|
|
339
|
+
const route = `/${file.slice("public/".length)}`;
|
|
340
|
+
if (route === "/")
|
|
341
|
+
return "/";
|
|
342
|
+
if (route.endsWith("/index.html"))
|
|
343
|
+
return route.slice(0, -"index.html".length) || "/";
|
|
344
|
+
if (route.endsWith(".html"))
|
|
345
|
+
return route.slice(0, -".html".length) || "/";
|
|
346
|
+
return route;
|
|
347
|
+
}
|
|
348
|
+
function planStatus(changedFiles, surfaces) {
|
|
349
|
+
if (changedFiles.length === 0)
|
|
350
|
+
return "no_changes";
|
|
351
|
+
if (surfaces.some((surface) => surface.requirement === "required"))
|
|
352
|
+
return "deployment_required";
|
|
353
|
+
if (surfaces.some((surface) => surface.requirement === "conditional"))
|
|
354
|
+
return "deployment_conditional";
|
|
355
|
+
return "deployment_not_required";
|
|
356
|
+
}
|
|
357
|
+
function deployGuidance(status, surfaces) {
|
|
358
|
+
if (status === "deployment_required" || status === "deployment_conditional") {
|
|
359
|
+
const commands = surfaces.flatMap((surface) => [
|
|
360
|
+
...surface.localCommands,
|
|
361
|
+
surface.deployCommand,
|
|
362
|
+
surface.readinessCommand,
|
|
363
|
+
...surface.verificationCommands,
|
|
364
|
+
]);
|
|
365
|
+
const verification = unique(surfaces.flatMap((surface) => surface.verification));
|
|
366
|
+
const verificationCommands = surfaces.flatMap((surface) => [
|
|
367
|
+
surface.readinessCommand,
|
|
368
|
+
...surface.verificationCommands,
|
|
369
|
+
]);
|
|
370
|
+
const hasWebSurface = surfaces.some((surface) => surface.kind === "web");
|
|
371
|
+
const hasCloudSurface = surfaces.some((surface) => surface.kind === "cloud");
|
|
372
|
+
const recordEvidence = hasCloudSurface && !hasWebSurface
|
|
373
|
+
? "Record completed cloud checks in release evidence."
|
|
374
|
+
: "Verify the deployed URL and record checked routes.";
|
|
375
|
+
const nextCommands = status === "deployment_conditional"
|
|
376
|
+
? [
|
|
377
|
+
"Confirm whether the changed dashboard/projection surface needs external deployed review.",
|
|
378
|
+
...commands,
|
|
379
|
+
hasCloudSurface ? "Record completed cloud checks if Supabase SaaS surfaces changed." : "Verify the deployed URL if deployment is required.",
|
|
380
|
+
]
|
|
381
|
+
: [...commands, recordEvidence];
|
|
382
|
+
return { commands, verification, verificationCommands, nextCommands };
|
|
383
|
+
}
|
|
384
|
+
if (status === "no_changes") {
|
|
385
|
+
return {
|
|
386
|
+
commands: [],
|
|
387
|
+
verification: ["No changed files were provided or detected, so no deploy gate is open."],
|
|
388
|
+
verificationCommands: [],
|
|
389
|
+
nextCommands: ["Make a scoped change or pass files explicitly: dna release deploy-plan <files...>"],
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
return {
|
|
393
|
+
commands: [],
|
|
394
|
+
verification: ["No web/public deployed surface changed, so Vercel deployment is not required."],
|
|
395
|
+
verificationCommands: [],
|
|
396
|
+
nextCommands: ["Continue local verification for the changed CLI/core/runtime/docs/wiki slice."],
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
function normalizeCheckedRoutes(routes) {
|
|
400
|
+
return unique((routes ?? [])
|
|
401
|
+
.flatMap((route) => route.split(","))
|
|
402
|
+
.map((route) => route.trim())
|
|
403
|
+
.filter(Boolean)
|
|
404
|
+
.map((route) => route.startsWith("/") ? route : `/${route}`));
|
|
405
|
+
}
|
|
406
|
+
function normalizeCheckedCloudChecks(checks) {
|
|
407
|
+
return unique((checks ?? [])
|
|
408
|
+
.flatMap((check) => check.split(","))
|
|
409
|
+
.map((check) => check.trim())
|
|
410
|
+
.filter(Boolean));
|
|
411
|
+
}
|
|
412
|
+
function expectedRoutes(plan) {
|
|
413
|
+
return unique(plan.surfaces.filter((surface) => surface.kind === "web").flatMap((surface) => surface.verificationCommands.map((command) => {
|
|
414
|
+
const match = command.match(/curl '([^']+)' --deployment/);
|
|
415
|
+
return match?.[1] ?? "";
|
|
416
|
+
})));
|
|
417
|
+
}
|
|
418
|
+
function expectedCloudChecks(plan) {
|
|
419
|
+
return unique(plan.surfaces.flatMap((surface) => surface.cloudChecks?.map((check) => check.id) ?? []));
|
|
420
|
+
}
|
|
421
|
+
function evidenceStatus(plan, deploymentUrl, checkedRoutes, checkedCloudChecks) {
|
|
422
|
+
if (plan.status === "deployment_not_required" || plan.status === "no_changes")
|
|
423
|
+
return "not_required";
|
|
424
|
+
const routes = expectedRoutes(plan);
|
|
425
|
+
if (routes.length > 0 && !deploymentUrl)
|
|
426
|
+
return "pending_deployed_url";
|
|
427
|
+
const missing = routes.filter((route) => !checkedRoutes.includes(route));
|
|
428
|
+
if (missing.length > 0)
|
|
429
|
+
return "pending_route_verification";
|
|
430
|
+
const missingCloudChecks = expectedCloudChecks(plan).filter((check) => !checkedCloudChecks.includes(check));
|
|
431
|
+
return missingCloudChecks.length === 0 ? "verified" : "pending_cloud_verification";
|
|
432
|
+
}
|
|
433
|
+
function defaultEvidencePath(projectDir) {
|
|
434
|
+
return join(projectDir, ".dna", "release", "latest-release-evidence.json");
|
|
435
|
+
}
|
|
436
|
+
function buildReleaseEvidenceArtifact(plan, opts, evidencePath, generatedAt = new Date().toISOString()) {
|
|
437
|
+
const checkedRoutes = normalizeCheckedRoutes(opts.checkedRoute);
|
|
438
|
+
const checkedCloudChecks = normalizeCheckedCloudChecks(opts.checkedCloudCheck);
|
|
439
|
+
const deploymentUrl = opts.deploymentUrl?.trim() || undefined;
|
|
440
|
+
const routes = expectedRoutes(plan);
|
|
441
|
+
const missingRoutes = routes.filter((route) => !checkedRoutes.includes(route));
|
|
442
|
+
const cloudChecks = expectedCloudChecks(plan);
|
|
443
|
+
const missingCloudChecks = cloudChecks.filter((check) => !checkedCloudChecks.includes(check));
|
|
444
|
+
const status = evidenceStatus(plan, deploymentUrl, checkedRoutes, checkedCloudChecks);
|
|
445
|
+
const deploymentRequired = routes.length > 0 && (plan.status === "deployment_required" || plan.status === "deployment_conditional");
|
|
446
|
+
const cloudRequired = cloudChecks.length > 0;
|
|
447
|
+
return {
|
|
448
|
+
schema_version: "intentdna.release_evidence.v1",
|
|
449
|
+
generated_at: generatedAt,
|
|
450
|
+
status,
|
|
451
|
+
deploy_plan: plan,
|
|
452
|
+
deployment: {
|
|
453
|
+
required: deploymentRequired,
|
|
454
|
+
url: deploymentUrl,
|
|
455
|
+
url_recorded: Boolean(deploymentUrl),
|
|
456
|
+
route_verification_complete: deploymentRequired ? missingRoutes.length === 0 && Boolean(deploymentUrl) : true,
|
|
457
|
+
checked_routes: checkedRoutes,
|
|
458
|
+
missing_routes: deploymentRequired ? missingRoutes : [],
|
|
459
|
+
},
|
|
460
|
+
cloud: {
|
|
461
|
+
required: cloudRequired,
|
|
462
|
+
check_verification_complete: cloudRequired ? missingCloudChecks.length === 0 : true,
|
|
463
|
+
checked_checks: checkedCloudChecks,
|
|
464
|
+
missing_checks: cloudRequired ? missingCloudChecks : [],
|
|
465
|
+
},
|
|
466
|
+
surfaces: plan.surfaces.map((surface) => ({
|
|
467
|
+
id: surface.id,
|
|
468
|
+
label: surface.label,
|
|
469
|
+
kind: surface.kind,
|
|
470
|
+
requirement: surface.requirement,
|
|
471
|
+
deploymentUrlVariable: surface.deploymentUrlVariable,
|
|
472
|
+
deployCommand: surface.deployCommand,
|
|
473
|
+
readinessCommand: surface.readinessCommand,
|
|
474
|
+
routes: surface.kind === "web"
|
|
475
|
+
? surface.verificationCommands.map((command) => {
|
|
476
|
+
const route = command.match(/curl '([^']+)' --deployment/)?.[1] ?? "";
|
|
477
|
+
return {
|
|
478
|
+
route,
|
|
479
|
+
verified: checkedRoutes.includes(route),
|
|
480
|
+
verificationCommand: command,
|
|
481
|
+
};
|
|
482
|
+
})
|
|
483
|
+
: [],
|
|
484
|
+
cloud_checks: surface.cloudChecks?.map((check) => ({
|
|
485
|
+
...check,
|
|
486
|
+
verified: checkedCloudChecks.includes(check.id),
|
|
487
|
+
})),
|
|
488
|
+
})),
|
|
489
|
+
evidence: {
|
|
490
|
+
local_commands: unique(plan.surfaces.flatMap((surface) => surface.localCommands)),
|
|
491
|
+
deploy_command: plan.surfaces[0]?.deployCommand,
|
|
492
|
+
readiness_commands: plan.surfaces.map((surface) => surface.readinessCommand),
|
|
493
|
+
route_verification_commands: plan.surfaces
|
|
494
|
+
.filter((surface) => surface.kind === "web")
|
|
495
|
+
.flatMap((surface) => surface.verificationCommands),
|
|
496
|
+
cloud_verification_commands: plan.surfaces
|
|
497
|
+
.filter((surface) => surface.kind === "cloud")
|
|
498
|
+
.flatMap((surface) => surface.verificationCommands),
|
|
499
|
+
release_complete: status === "verified" || status === "not_required",
|
|
500
|
+
},
|
|
501
|
+
files: {
|
|
502
|
+
evidence: evidencePath,
|
|
503
|
+
},
|
|
504
|
+
boundaries: releaseEvidenceBoundaries(),
|
|
505
|
+
nextCommands: status === "verified" || status === "not_required"
|
|
506
|
+
? ["Record this release evidence artifact in the final report."]
|
|
507
|
+
: [
|
|
508
|
+
...(!deploymentUrl ? plan.surfaces.map((surface) => surface.deployCommand) : []),
|
|
509
|
+
...plan.verificationCommands,
|
|
510
|
+
"Re-run dna release evidence with --deployment-url/--checked-route for every verified route and --checked-cloud-check for every completed cloud check.",
|
|
511
|
+
],
|
|
512
|
+
mutationBoundary: "Release evidence may write an evidence JSON file when --output is used, but it does not build, deploy, verify URLs, sync runtime artifacts, mutate dashboard state, or change policy truth.",
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
async function writeJson(path, value) {
|
|
516
|
+
await mkdir(dirname(path), { recursive: true });
|
|
517
|
+
await writeFile(path, JSON.stringify(value, null, 2) + "\n", "utf-8");
|
|
518
|
+
}
|
|
519
|
+
export async function buildReleaseDeployPlan(opts) {
|
|
520
|
+
const projectDir = resolve(opts.projectDir ?? process.cwd());
|
|
521
|
+
const changed = await collectChangedFiles(projectDir, opts.files);
|
|
522
|
+
const surfaces = buildSurfacePlans(changed.files);
|
|
523
|
+
const webPublicFiles = unique(surfaces.filter((surface) => surface.kind === "web").flatMap((surface) => surface.files));
|
|
524
|
+
const cloudFiles = unique(surfaces.filter((surface) => surface.kind === "cloud").flatMap((surface) => surface.files));
|
|
525
|
+
const status = planStatus(changed.files, surfaces);
|
|
526
|
+
const guidance = deployGuidance(status, surfaces);
|
|
527
|
+
return {
|
|
528
|
+
mode: "release_deploy_plan",
|
|
529
|
+
status,
|
|
530
|
+
source: changed.source,
|
|
531
|
+
changedFiles: changed.files,
|
|
532
|
+
webPublicFiles,
|
|
533
|
+
cloudFiles,
|
|
534
|
+
nonDeployFiles: changed.files.filter((file) => !webPublicFiles.includes(file) && !cloudFiles.includes(file)),
|
|
535
|
+
surfaces,
|
|
536
|
+
commands: guidance.commands,
|
|
537
|
+
verification: guidance.verification,
|
|
538
|
+
verificationCommands: guidance.verificationCommands,
|
|
539
|
+
boundaries: BOUNDARIES,
|
|
540
|
+
warnings: changed.warnings,
|
|
541
|
+
nextCommands: guidance.nextCommands,
|
|
542
|
+
mutationBoundary: "Read-only release deploy plan; no build, test, Vercel deploy, URL verification, runtime sync, dashboard state, or policy truth is changed.",
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
function writeDeployPlan(plan) {
|
|
546
|
+
process.stderr.write("Release deploy plan\n");
|
|
547
|
+
process.stderr.write(`Mode: ${plan.mode}\n`);
|
|
548
|
+
process.stderr.write(`Status: ${plan.status}\n`);
|
|
549
|
+
process.stderr.write(`Source: ${plan.source}\n`);
|
|
550
|
+
process.stderr.write(`Mutation boundary: ${plan.mutationBoundary}\n`);
|
|
551
|
+
process.stderr.write("\nChanged files:\n");
|
|
552
|
+
if (plan.changedFiles.length === 0) {
|
|
553
|
+
process.stderr.write(" - none\n");
|
|
554
|
+
}
|
|
555
|
+
else {
|
|
556
|
+
for (const file of plan.changedFiles)
|
|
557
|
+
process.stderr.write(` - ${file}\n`);
|
|
558
|
+
}
|
|
559
|
+
process.stderr.write("\nWeb/public surface files:\n");
|
|
560
|
+
if (plan.webPublicFiles.length === 0) {
|
|
561
|
+
process.stderr.write(" - none\n");
|
|
562
|
+
}
|
|
563
|
+
else {
|
|
564
|
+
for (const file of plan.webPublicFiles)
|
|
565
|
+
process.stderr.write(` - ${file}\n`);
|
|
566
|
+
}
|
|
567
|
+
process.stderr.write("\nSurfaces:\n");
|
|
568
|
+
if (plan.surfaces.length === 0) {
|
|
569
|
+
process.stderr.write(" - none\n");
|
|
570
|
+
}
|
|
571
|
+
else {
|
|
572
|
+
for (const surface of plan.surfaces) {
|
|
573
|
+
process.stderr.write(` - ${surface.id}: ${surface.requirement}\n`);
|
|
574
|
+
process.stderr.write(` label: ${surface.label}\n`);
|
|
575
|
+
process.stderr.write(` required_when: ${surface.requiredWhen}\n`);
|
|
576
|
+
process.stderr.write(` cwd: ${surface.cwd}\n`);
|
|
577
|
+
process.stderr.write(" local commands:\n");
|
|
578
|
+
for (const command of surface.localCommands)
|
|
579
|
+
process.stderr.write(` - ${command}\n`);
|
|
580
|
+
process.stderr.write(` deployment_url_variable: ${surface.deploymentUrlVariable}\n`);
|
|
581
|
+
process.stderr.write(` deploy: ${surface.deployCommand}\n`);
|
|
582
|
+
process.stderr.write(` readiness: ${surface.readinessCommand}\n`);
|
|
583
|
+
process.stderr.write(" verification commands:\n");
|
|
584
|
+
if (surface.verificationCommands.length === 0) {
|
|
585
|
+
process.stderr.write(" - none\n");
|
|
586
|
+
}
|
|
587
|
+
else {
|
|
588
|
+
for (const command of surface.verificationCommands)
|
|
589
|
+
process.stderr.write(` - ${command}\n`);
|
|
590
|
+
}
|
|
591
|
+
process.stderr.write(" verification:\n");
|
|
592
|
+
for (const item of surface.verification)
|
|
593
|
+
process.stderr.write(` - ${item}\n`);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
process.stderr.write("\nNon-deploy files:\n");
|
|
597
|
+
if (plan.nonDeployFiles.length === 0) {
|
|
598
|
+
process.stderr.write(" - none\n");
|
|
599
|
+
}
|
|
600
|
+
else {
|
|
601
|
+
for (const file of plan.nonDeployFiles)
|
|
602
|
+
process.stderr.write(` - ${file}\n`);
|
|
603
|
+
}
|
|
604
|
+
process.stderr.write("\nBoundaries:\n");
|
|
605
|
+
for (const boundary of plan.boundaries)
|
|
606
|
+
process.stderr.write(` - ${boundary}\n`);
|
|
607
|
+
if (plan.warnings.length > 0) {
|
|
608
|
+
process.stderr.write("\nWarnings:\n");
|
|
609
|
+
for (const warning of plan.warnings)
|
|
610
|
+
process.stderr.write(` - ${warning}\n`);
|
|
611
|
+
}
|
|
612
|
+
process.stderr.write("\nCommands:\n");
|
|
613
|
+
if (plan.commands.length === 0) {
|
|
614
|
+
process.stderr.write(" - none\n");
|
|
615
|
+
}
|
|
616
|
+
else {
|
|
617
|
+
for (const command of plan.commands)
|
|
618
|
+
process.stderr.write(` - ${command}\n`);
|
|
619
|
+
}
|
|
620
|
+
process.stderr.write("\nVerification:\n");
|
|
621
|
+
for (const item of plan.verification)
|
|
622
|
+
process.stderr.write(` - ${item}\n`);
|
|
623
|
+
process.stderr.write("\nVerification commands:\n");
|
|
624
|
+
if (plan.verificationCommands.length === 0) {
|
|
625
|
+
process.stderr.write(" - none\n");
|
|
626
|
+
}
|
|
627
|
+
else {
|
|
628
|
+
for (const command of plan.verificationCommands)
|
|
629
|
+
process.stderr.write(` - ${command}\n`);
|
|
630
|
+
}
|
|
631
|
+
process.stderr.write("\nNext commands:\n");
|
|
632
|
+
for (const command of plan.nextCommands)
|
|
633
|
+
process.stderr.write(` - ${command}\n`);
|
|
634
|
+
}
|
|
635
|
+
function usage() {
|
|
636
|
+
return "Usage: dna release <deploy-plan|evidence> [files...] [--json]\n";
|
|
637
|
+
}
|
|
638
|
+
function writeReleaseEvidence(evidence) {
|
|
639
|
+
process.stderr.write("Release evidence\n");
|
|
640
|
+
process.stderr.write(`Schema: ${evidence.schema_version}\n`);
|
|
641
|
+
process.stderr.write(`Status: ${evidence.status}\n`);
|
|
642
|
+
process.stderr.write(`Mutation boundary: ${evidence.mutationBoundary}\n`);
|
|
643
|
+
process.stderr.write(`Deployment required: ${String(evidence.deployment.required)}\n`);
|
|
644
|
+
process.stderr.write(`Deployment URL: ${evidence.deployment.url ?? "not recorded"}\n`);
|
|
645
|
+
process.stderr.write(`Route verification complete: ${String(evidence.deployment.route_verification_complete)}\n`);
|
|
646
|
+
process.stderr.write(`Cloud verification required: ${String(evidence.cloud.required)}\n`);
|
|
647
|
+
process.stderr.write(`Cloud verification complete: ${String(evidence.cloud.check_verification_complete)}\n`);
|
|
648
|
+
process.stderr.write("\nChecked routes:\n");
|
|
649
|
+
if (evidence.deployment.checked_routes.length === 0) {
|
|
650
|
+
process.stderr.write(" - none\n");
|
|
651
|
+
}
|
|
652
|
+
else {
|
|
653
|
+
for (const route of evidence.deployment.checked_routes)
|
|
654
|
+
process.stderr.write(` - ${route}\n`);
|
|
655
|
+
}
|
|
656
|
+
process.stderr.write("\nMissing routes:\n");
|
|
657
|
+
if (evidence.deployment.missing_routes.length === 0) {
|
|
658
|
+
process.stderr.write(" - none\n");
|
|
659
|
+
}
|
|
660
|
+
else {
|
|
661
|
+
for (const route of evidence.deployment.missing_routes)
|
|
662
|
+
process.stderr.write(` - ${route}\n`);
|
|
663
|
+
}
|
|
664
|
+
process.stderr.write("\nChecked cloud checks:\n");
|
|
665
|
+
if (evidence.cloud.checked_checks.length === 0) {
|
|
666
|
+
process.stderr.write(" - none\n");
|
|
667
|
+
}
|
|
668
|
+
else {
|
|
669
|
+
for (const check of evidence.cloud.checked_checks)
|
|
670
|
+
process.stderr.write(` - ${check}\n`);
|
|
671
|
+
}
|
|
672
|
+
process.stderr.write("\nMissing cloud checks:\n");
|
|
673
|
+
if (evidence.cloud.missing_checks.length === 0) {
|
|
674
|
+
process.stderr.write(" - none\n");
|
|
675
|
+
}
|
|
676
|
+
else {
|
|
677
|
+
for (const check of evidence.cloud.missing_checks)
|
|
678
|
+
process.stderr.write(` - ${check}\n`);
|
|
679
|
+
}
|
|
680
|
+
process.stderr.write("\nNext commands:\n");
|
|
681
|
+
for (const command of evidence.nextCommands)
|
|
682
|
+
process.stderr.write(` - ${command}\n`);
|
|
683
|
+
process.stderr.write("\nBoundaries:\n");
|
|
684
|
+
for (const boundary of evidence.boundaries)
|
|
685
|
+
process.stderr.write(` - ${boundary}\n`);
|
|
686
|
+
}
|
|
687
|
+
export async function runRelease(opts) {
|
|
688
|
+
const subcommand = opts.subcommand || "deploy-plan";
|
|
689
|
+
if (subcommand !== "deploy-plan" && subcommand !== "evidence") {
|
|
690
|
+
process.stderr.write(usage());
|
|
691
|
+
return 2;
|
|
692
|
+
}
|
|
693
|
+
const plan = await buildReleaseDeployPlan(opts);
|
|
694
|
+
if (subcommand === "evidence") {
|
|
695
|
+
const projectDir = resolve(opts.projectDir ?? process.cwd());
|
|
696
|
+
const evidencePath = opts.output
|
|
697
|
+
? resolvePath(projectDir, opts.output)
|
|
698
|
+
: opts.dryRun ? undefined : defaultEvidencePath(projectDir);
|
|
699
|
+
const evidence = buildReleaseEvidenceArtifact(plan, opts, evidencePath);
|
|
700
|
+
if (evidencePath && !opts.dryRun)
|
|
701
|
+
await writeJson(evidencePath, evidence);
|
|
702
|
+
if (opts.json) {
|
|
703
|
+
process.stdout.write(JSON.stringify(evidence, null, 2) + "\n");
|
|
704
|
+
}
|
|
705
|
+
else {
|
|
706
|
+
writeReleaseEvidence(evidence);
|
|
707
|
+
}
|
|
708
|
+
return 0;
|
|
709
|
+
}
|
|
710
|
+
if (opts.json) {
|
|
711
|
+
process.stdout.write(JSON.stringify(plan, null, 2) + "\n");
|
|
712
|
+
return 0;
|
|
713
|
+
}
|
|
714
|
+
writeDeployPlan(plan);
|
|
715
|
+
return 0;
|
|
716
|
+
}
|