swallowkit 1.1.2 → 2.0.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +61 -7
- package/README.md +61 -7
- package/dist/cli/commands/add-auth.d.ts.map +1 -1
- package/dist/cli/commands/add-auth.js +103 -27
- package/dist/cli/commands/add-auth.js.map +1 -1
- package/dist/cli/commands/configure-mcp.d.ts +12 -0
- package/dist/cli/commands/configure-mcp.d.ts.map +1 -0
- package/dist/cli/commands/configure-mcp.js +42 -0
- package/dist/cli/commands/configure-mcp.js.map +1 -0
- package/dist/cli/commands/dev.d.ts +5 -2
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +143 -67
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/commands/init.d.ts +14 -7
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +149 -170
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/migrations.d.ts.map +1 -1
- package/dist/cli/commands/migrations.js +25 -0
- package/dist/cli/commands/migrations.js.map +1 -1
- package/dist/cli/commands/provision.d.ts +5 -0
- package/dist/cli/commands/provision.d.ts.map +1 -1
- package/dist/cli/commands/provision.js +278 -55
- package/dist/cli/commands/provision.js.map +1 -1
- package/dist/cli/commands/scaffold.d.ts +5 -1
- package/dist/cli/commands/scaffold.d.ts.map +1 -1
- package/dist/cli/commands/scaffold.js +123 -18
- package/dist/cli/commands/scaffold.js.map +1 -1
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +49 -8
- package/dist/cli/index.js.map +1 -1
- package/dist/core/config.d.ts +2 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +19 -0
- package/dist/core/config.js.map +1 -1
- package/dist/core/operations/create-model.d.ts +2 -0
- package/dist/core/operations/create-model.d.ts.map +1 -1
- package/dist/core/operations/create-model.js +60 -8
- package/dist/core/operations/create-model.js.map +1 -1
- package/dist/core/operations/migration-generator.d.ts.map +1 -1
- package/dist/core/operations/migration-generator.js +12 -1
- package/dist/core/operations/migration-generator.js.map +1 -1
- package/dist/core/operations/model-migration.d.ts +22 -0
- package/dist/core/operations/model-migration.d.ts.map +1 -0
- package/dist/core/operations/model-migration.js +36 -0
- package/dist/core/operations/model-migration.js.map +1 -0
- package/dist/core/operations/provision-deployment.d.ts +52 -0
- package/dist/core/operations/provision-deployment.d.ts.map +1 -0
- package/dist/core/operations/provision-deployment.js +164 -0
- package/dist/core/operations/provision-deployment.js.map +1 -0
- package/dist/core/operations/provision-operations.d.ts +62 -6
- package/dist/core/operations/provision-operations.d.ts.map +1 -1
- package/dist/core/operations/provision-operations.js +653 -115
- package/dist/core/operations/provision-operations.js.map +1 -1
- package/dist/core/operations/scaffold-apply.d.ts +1 -0
- package/dist/core/operations/scaffold-apply.d.ts.map +1 -1
- package/dist/core/operations/scaffold-apply.js +1 -0
- package/dist/core/operations/scaffold-apply.js.map +1 -1
- package/dist/core/operations/scaffold-machine.d.ts +1 -0
- package/dist/core/operations/scaffold-machine.d.ts.map +1 -1
- package/dist/core/operations/scaffold-machine.js.map +1 -1
- package/dist/core/operations/scaffold-plan.d.ts +2 -0
- package/dist/core/operations/scaffold-plan.d.ts.map +1 -1
- package/dist/core/operations/scaffold-plan.js +3 -1
- package/dist/core/operations/scaffold-plan.js.map +1 -1
- package/dist/core/project/agent-instructions.d.ts +2 -1
- package/dist/core/project/agent-instructions.d.ts.map +1 -1
- package/dist/core/project/agent-instructions.js +15 -0
- package/dist/core/project/agent-instructions.js.map +1 -1
- package/dist/core/project/boundaries.d.ts +5 -2
- package/dist/core/project/boundaries.d.ts.map +1 -1
- package/dist/core/project/boundaries.js +51 -20
- package/dist/core/project/boundaries.js.map +1 -1
- package/dist/core/project/capabilities.d.ts +17 -0
- package/dist/core/project/capabilities.d.ts.map +1 -1
- package/dist/core/project/capabilities.js +45 -6
- package/dist/core/project/capabilities.js.map +1 -1
- package/dist/core/project/home-page.d.ts +4 -0
- package/dist/core/project/home-page.d.ts.map +1 -0
- package/dist/core/project/home-page.js +113 -0
- package/dist/core/project/home-page.js.map +1 -0
- package/dist/core/project/manifest.d.ts +3 -1
- package/dist/core/project/manifest.d.ts.map +1 -1
- package/dist/core/project/manifest.js +47 -25
- package/dist/core/project/manifest.js.map +1 -1
- package/dist/core/project/mcp-config.d.ts +26 -0
- package/dist/core/project/mcp-config.d.ts.map +1 -0
- package/dist/core/project/mcp-config.js +117 -0
- package/dist/core/project/mcp-config.js.map +1 -0
- package/dist/core/project/react-project.d.ts +26 -0
- package/dist/core/project/react-project.d.ts.map +1 -0
- package/dist/core/project/react-project.js +443 -0
- package/dist/core/project/react-project.js.map +1 -0
- package/dist/core/project/state.d.ts +7 -0
- package/dist/core/project/state.d.ts.map +1 -1
- package/dist/core/project/state.js +86 -0
- package/dist/core/project/state.js.map +1 -1
- package/dist/core/project/validation.d.ts.map +1 -1
- package/dist/core/project/validation.js +32 -10
- package/dist/core/project/validation.js.map +1 -1
- package/dist/core/project/workflows.d.ts.map +1 -1
- package/dist/core/project/workflows.js +53 -7
- package/dist/core/project/workflows.js.map +1 -1
- package/dist/core/scaffold/auth-generator.d.ts.map +1 -1
- package/dist/core/scaffold/auth-generator.js +26 -27
- package/dist/core/scaffold/auth-generator.js.map +1 -1
- package/dist/core/scaffold/model-parser.d.ts +23 -0
- package/dist/core/scaffold/model-parser.d.ts.map +1 -1
- package/dist/core/scaffold/model-parser.js +407 -137
- package/dist/core/scaffold/model-parser.js.map +1 -1
- package/dist/core/scaffold/native-schema-generator.d.ts +13 -0
- package/dist/core/scaffold/native-schema-generator.d.ts.map +1 -1
- package/dist/core/scaffold/native-schema-generator.js +294 -91
- package/dist/core/scaffold/native-schema-generator.js.map +1 -1
- package/dist/core/scaffold/openapi-generator.d.ts +5 -0
- package/dist/core/scaffold/openapi-generator.d.ts.map +1 -1
- package/dist/core/scaffold/openapi-generator.js +53 -3
- package/dist/core/scaffold/openapi-generator.js.map +1 -1
- package/dist/core/scaffold/react-bff-generator.d.ts +10 -0
- package/dist/core/scaffold/react-bff-generator.d.ts.map +1 -0
- package/dist/core/scaffold/react-bff-generator.js +252 -0
- package/dist/core/scaffold/react-bff-generator.js.map +1 -0
- package/dist/core/scaffold/react-ui-generator.d.ts +6 -0
- package/dist/core/scaffold/react-ui-generator.d.ts.map +1 -0
- package/dist/core/scaffold/react-ui-generator.js +41 -0
- package/dist/core/scaffold/react-ui-generator.js.map +1 -0
- package/dist/core/scaffold/ui-generator.d.ts +0 -3
- package/dist/core/scaffold/ui-generator.d.ts.map +1 -1
- package/dist/core/scaffold/ui-generator.js +68 -7
- package/dist/core/scaffold/ui-generator.js.map +1 -1
- package/dist/core/verify/index.d.ts +4 -3
- package/dist/core/verify/index.d.ts.map +1 -1
- package/dist/core/verify/index.js +122 -5
- package/dist/core/verify/index.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/machine/contracts.d.ts +1 -0
- package/dist/machine/contracts.d.ts.map +1 -1
- package/dist/machine/errors.d.ts +3 -2
- package/dist/machine/errors.d.ts.map +1 -1
- package/dist/machine/errors.js +2 -1
- package/dist/machine/errors.js.map +1 -1
- package/dist/machine/index.d.ts.map +1 -1
- package/dist/machine/index.js +61 -30
- package/dist/machine/index.js.map +1 -1
- package/dist/mcp/index.d.ts +6 -4
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +210 -25
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/version-selection.d.ts +55 -0
- package/dist/mcp/version-selection.d.ts.map +1 -0
- package/dist/mcp/version-selection.js +152 -0
- package/dist/mcp/version-selection.js.map +1 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/azure-cli.d.ts +56 -0
- package/dist/utils/azure-cli.d.ts.map +1 -0
- package/dist/utils/azure-cli.js +326 -0
- package/dist/utils/azure-cli.js.map +1 -0
- package/dist/utils/azure-diagnostics.d.ts +16 -0
- package/dist/utils/azure-diagnostics.d.ts.map +1 -0
- package/dist/utils/azure-diagnostics.js +68 -0
- package/dist/utils/azure-diagnostics.js.map +1 -0
- package/dist/utils/package-manager.d.ts +2 -2
- package/dist/utils/package-manager.d.ts.map +1 -1
- package/dist/utils/package-manager.js +6 -3
- package/dist/utils/package-manager.js.map +1 -1
- package/dist/utils/python-uv.d.ts +5 -7
- package/dist/utils/python-uv.d.ts.map +1 -1
- package/dist/utils/python-uv.js +50 -24
- package/dist/utils/python-uv.js.map +1 -1
- package/dist/utils/react-dev.d.ts +4 -0
- package/dist/utils/react-dev.d.ts.map +1 -0
- package/dist/utils/react-dev.js +35 -0
- package/dist/utils/react-dev.js.map +1 -0
- package/dist/utils/semver.d.ts +14 -0
- package/dist/utils/semver.d.ts.map +1 -0
- package/dist/utils/semver.js +61 -0
- package/dist/utils/semver.js.map +1 -0
- package/package.json +7 -4
- package/src/__tests__/__snapshots__/boundaries.test.ts.snap +154 -0
- package/src/__tests__/agent-instructions.test.ts +6 -1
- package/src/__tests__/agent-loop-phase34.test.ts +143 -8
- package/src/__tests__/auth.test.ts +9 -0
- package/src/__tests__/azure-cli.test.ts +202 -0
- package/src/__tests__/boundaries.test.ts +42 -0
- package/src/__tests__/csharp-schema.test.ts +261 -0
- package/src/__tests__/dev.test.ts +13 -6
- package/src/__tests__/fixtures/az wrapper.cmd +2 -0
- package/src/__tests__/fixtures/azure-cli.cjs +153 -0
- package/src/__tests__/fixtures/provision-runner.cjs +28 -0
- package/src/__tests__/generated-project-matrix.test.ts +789 -0
- package/src/__tests__/home-page.test.ts +50 -0
- package/src/__tests__/init.test.ts +145 -5
- package/src/__tests__/mcp-version.test.ts +262 -0
- package/src/__tests__/mcp.test.ts +120 -3
- package/src/__tests__/multi-auth.test.ts +115 -4
- package/src/__tests__/package-manager.test.ts +17 -2
- package/src/__tests__/plan-apply.test.ts +179 -0
- package/src/__tests__/provision-runtime.test.ts +722 -0
- package/src/__tests__/python-uv.test.ts +50 -14
- package/src/__tests__/python-venv.test.ts +48 -0
- package/src/__tests__/react-dev.test.ts +72 -0
- package/src/__tests__/react.test.ts +838 -0
- package/src/__tests__/release-metadata.test.ts +121 -0
- package/src/__tests__/schema-analysis.test.ts +329 -0
- package/src/cli/commands/add-auth.ts +103 -29
- package/src/cli/commands/configure-mcp.ts +54 -0
- package/src/cli/commands/dev.ts +145 -73
- package/src/cli/commands/init.ts +147 -172
- package/src/cli/commands/migrations.ts +22 -0
- package/src/cli/commands/provision.ts +294 -56
- package/src/cli/commands/scaffold.ts +145 -22
- package/src/cli/index.ts +46 -10
- package/src/core/config.ts +21 -1
- package/src/core/operations/create-model.ts +51 -13
- package/src/core/operations/migration-generator.ts +14 -1
- package/src/core/operations/model-migration.ts +52 -0
- package/src/core/operations/provision-deployment.ts +183 -0
- package/src/core/operations/provision-operations.ts +662 -154
- package/src/core/operations/scaffold-apply.ts +2 -0
- package/src/core/operations/scaffold-machine.ts +1 -0
- package/src/core/operations/scaffold-plan.ts +5 -1
- package/src/core/project/agent-instructions.ts +17 -1
- package/src/core/project/agent-templates/AGENTS.md +113 -15
- package/src/core/project/agent-templates/CLAUDE.md +14 -1
- package/src/core/project/agent-templates/bff-routes.instructions.md +5 -3
- package/src/core/project/agent-templates/copilot-instructions.md +15 -1
- package/src/core/project/agent-templates/shared-models.instructions.md +11 -3
- package/src/core/project/boundaries.ts +62 -20
- package/src/core/project/capabilities.ts +63 -6
- package/src/core/project/home-page.ts +114 -0
- package/src/core/project/manifest.ts +52 -27
- package/src/core/project/mcp-config.ts +84 -0
- package/src/core/project/react-project.ts +529 -0
- package/src/core/project/state.ts +70 -0
- package/src/core/project/validation.ts +32 -10
- package/src/core/project/workflows.ts +53 -7
- package/src/core/scaffold/auth-generator.ts +26 -27
- package/src/core/scaffold/model-parser.ts +413 -129
- package/src/core/scaffold/native-schema-generator.ts +319 -103
- package/src/core/scaffold/openapi-generator.ts +63 -5
- package/src/core/scaffold/react-bff-generator.ts +292 -0
- package/src/core/scaffold/react-ui-generator.ts +51 -0
- package/src/core/scaffold/ui-generator.ts +70 -8
- package/src/core/verify/index.ts +118 -6
- package/src/index.ts +3 -1
- package/src/machine/contracts.ts +1 -0
- package/src/machine/errors.ts +4 -2
- package/src/machine/index.ts +67 -36
- package/src/mcp/index.ts +221 -31
- package/src/mcp/version-selection.ts +163 -0
- package/src/types/index.ts +4 -0
- package/src/utils/azure-cli.ts +315 -0
- package/src/utils/azure-diagnostics.ts +75 -0
- package/src/utils/package-manager.ts +7 -4
- package/src/utils/python-uv.ts +53 -33
- package/src/utils/react-dev.ts +31 -0
- package/src/utils/semver.ts +59 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { ensureSwallowKitProject } from "../../core/config";
|
|
3
3
|
import { initializeLegacyMigrations } from "../../core/operations/migration-generator";
|
|
4
|
+
import { addModelContainerMigration } from "../../core/operations/model-migration";
|
|
4
5
|
|
|
5
6
|
export const migrationsCommand = new Command("migrations")
|
|
6
7
|
.description("Manage versioned Azure infrastructure migrations");
|
|
@@ -18,3 +19,24 @@ migrationsCommand
|
|
|
18
19
|
console.log(`Migration baseline 0 created for project ${manifest.projectId}.`);
|
|
19
20
|
console.log("Commit infra/migrations/manifest.json, then run provision with --adopt-existing --baseline 0.");
|
|
20
21
|
});
|
|
22
|
+
|
|
23
|
+
migrationsCommand
|
|
24
|
+
.command("add-model")
|
|
25
|
+
.description("Generate only the Cosmos DB container migration for a model (e.g. after scaffold --skip-infra)")
|
|
26
|
+
.argument("<model>", "Model file or model name")
|
|
27
|
+
.option("--dry-run", "Show the planned infra changes without writing anything", false)
|
|
28
|
+
.action(async (model: string, options: { dryRun?: boolean }) => {
|
|
29
|
+
ensureSwallowKitProject("migrations add-model");
|
|
30
|
+
const result = await addModelContainerMigration(model, { dryRun: options.dryRun });
|
|
31
|
+
for (const operation of result.operations) {
|
|
32
|
+
if (operation.action !== "skip") console.log(` ${operation.action.padEnd(7)} ${operation.path}`);
|
|
33
|
+
}
|
|
34
|
+
if (!result.migration) {
|
|
35
|
+
console.log(`No migration needed for ${result.model}: ${result.reason}`);
|
|
36
|
+
} else if (result.operations.every((operation) => operation.action === "skip")) {
|
|
37
|
+
console.log(`Migration ${result.migration.version} for ${result.model} already exists: ${result.migration.template}`);
|
|
38
|
+
} else {
|
|
39
|
+
console.log(`${result.dryRun ? "Would create" : "Created"} migration ${result.migration.version} for ${result.model}: ${result.migration.template}`);
|
|
40
|
+
if (!result.dryRun) console.log("Commit the migration, then run provision to plan and apply it.");
|
|
41
|
+
}
|
|
42
|
+
});
|
|
@@ -1,7 +1,142 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
1
|
+
import { Command, Option } from "commander";
|
|
2
2
|
import prompts from "prompts";
|
|
3
3
|
import { ensureSwallowKitProject } from "../../core/config";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
INTERACTIVE_PROVISION_RETRY_HINT,
|
|
6
|
+
ApplyProvisionResult,
|
|
7
|
+
ProvisionPlanData,
|
|
8
|
+
applyProvisionOperation,
|
|
9
|
+
executeProvisionPlan,
|
|
10
|
+
inspectProvisionOperation,
|
|
11
|
+
planProvisionOperation,
|
|
12
|
+
} from "../../core/operations/provision-operations";
|
|
13
|
+
import { MachineCommandError } from "../../machine/errors";
|
|
14
|
+
import { formatAzureDiagnostic } from "../../utils/azure-diagnostics";
|
|
15
|
+
import { runAzCliAsync } from "../../utils/azure-cli";
|
|
16
|
+
|
|
17
|
+
function humanProvisionMessage(message: string): string {
|
|
18
|
+
return message
|
|
19
|
+
.replace(/swallowkit machine inspect provision --plan/g, "swallowkit provision --status")
|
|
20
|
+
.replace(/swallowkit machine apply provision --plan/g, "swallowkit provision --resume")
|
|
21
|
+
.replace(/plan provision/g, "swallowkit provision")
|
|
22
|
+
.replace(/\b[Pp]lan\b/g, "Execution")
|
|
23
|
+
.replace(/preview/g, "deployment preparation")
|
|
24
|
+
.replace(/after approval/g, "")
|
|
25
|
+
.replace(/--approve/g, "")
|
|
26
|
+
.replace(/ {2,}/g, " ");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function formatProvisionError(error: unknown): string {
|
|
30
|
+
if (!(error instanceof MachineCommandError)) return error instanceof Error ? error.message : String(error);
|
|
31
|
+
const message = error.code === "azure-deployment-failed" ? error.message : humanProvisionMessage(error.message);
|
|
32
|
+
const lines = [...new Set(message.split("\n"))];
|
|
33
|
+
const details = error.details as Partial<ApplyProvisionResult> | undefined;
|
|
34
|
+
if (details?.deployment) {
|
|
35
|
+
lines.push(`Deployment: ${details.deployment.reference.name}; state: ${details.deployment.state}`);
|
|
36
|
+
}
|
|
37
|
+
for (const diagnostic of details?.diagnostics ?? []) {
|
|
38
|
+
if (diagnostic.failureKind || diagnostic.outputTruncated) lines.push(formatAzureDiagnostic(diagnostic));
|
|
39
|
+
}
|
|
40
|
+
const runId = details?.runId;
|
|
41
|
+
if (runId) {
|
|
42
|
+
lines.push(`Check: swallowkit provision --status ${runId}`);
|
|
43
|
+
if (error.code !== "azure-deployment-failed") lines.push(`Resume: swallowkit provision --resume ${runId}`);
|
|
44
|
+
else lines.push("Azure reported a terminal failure. Resuming only observes that failed deployment; correct its cause before explicitly deploying again.");
|
|
45
|
+
} else {
|
|
46
|
+
for (const action of error.nextActions ?? []) {
|
|
47
|
+
const command = humanProvisionMessage(action.command);
|
|
48
|
+
if (!command.includes("machine ")) lines.push(command);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return lines.join("\n");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function printProvisionSetup(result: ApplyProvisionResult): Promise<void> {
|
|
55
|
+
if (!result.deployment || result.status !== "complete") return;
|
|
56
|
+
const reference = result.deployment.reference;
|
|
57
|
+
const fields = ["staticWebAppName", "staticWebAppUrl", "functionsAppName", "functionsAppUrl", "cosmosDbAccountName", "cosmosDatabaseName"] as const;
|
|
58
|
+
let outputs: Record<string, unknown>;
|
|
59
|
+
try {
|
|
60
|
+
const response = await runAzCliAsync(
|
|
61
|
+
[
|
|
62
|
+
"deployment",
|
|
63
|
+
"group",
|
|
64
|
+
"show",
|
|
65
|
+
"--name",
|
|
66
|
+
reference.name.startsWith("swallowkit-migration-") ? "swallowkit-baseline" : reference.name,
|
|
67
|
+
"--resource-group",
|
|
68
|
+
reference.resourceGroup,
|
|
69
|
+
"--subscription",
|
|
70
|
+
reference.subscription,
|
|
71
|
+
"--query",
|
|
72
|
+
`{${fields.map((field) => `${field}:properties.outputs.${field}.value`).join(",")}}`,
|
|
73
|
+
"-o",
|
|
74
|
+
"json",
|
|
75
|
+
],
|
|
76
|
+
{ timeout: 30_000 }
|
|
77
|
+
);
|
|
78
|
+
if (response.status !== 0 || response.failureKind || response.stdoutTruncated) throw new Error("Output query did not complete.");
|
|
79
|
+
const parsed: unknown = JSON.parse(response.stdout);
|
|
80
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("Invalid resource output.");
|
|
81
|
+
outputs = parsed as Record<string, unknown>;
|
|
82
|
+
} catch {
|
|
83
|
+
console.warn(
|
|
84
|
+
"Deployment succeeded, but resource information could not be read. Inspect the deployment's named outputs in Azure Portal; do not redeploy just to retrieve them."
|
|
85
|
+
);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
console.log("\nResource information:");
|
|
89
|
+
for (const field of fields) {
|
|
90
|
+
if (typeof outputs[field] === "string") console.log(` ${field}: ${outputs[field]}`);
|
|
91
|
+
}
|
|
92
|
+
const swaName = typeof outputs.staticWebAppName === "string" ? outputs.staticWebAppName : "<swa-name>";
|
|
93
|
+
const functionsAppName = typeof outputs.functionsAppName === "string" ? outputs.functionsAppName : "<functions-app-name>";
|
|
94
|
+
const target = ["--resource-group", reference.resourceGroup, "--subscription", reference.subscription];
|
|
95
|
+
const swaTokenArgs = ["staticwebapp", "secrets", "list", "--name", swaName, ...target, "--query", "properties.apiKey", "-o", "tsv"];
|
|
96
|
+
const publishProfileArgs = ["webapp", "deployment", "list-publishing-profiles", "--name", functionsAppName, ...target, "--xml"];
|
|
97
|
+
|
|
98
|
+
console.log("\nCI/CD settings (save these as secrets in GitHub Actions or Azure Pipelines):\n");
|
|
99
|
+
console.log(" [AZURE_STATIC_WEB_APPS_API_TOKEN]");
|
|
100
|
+
console.log(await readCiCdSecret(swaTokenArgs, typeof outputs.staticWebAppName === "string"));
|
|
101
|
+
console.log(" [AZURE_FUNCTIONAPP_NAME]");
|
|
102
|
+
console.log(` ${functionsAppName}\n`);
|
|
103
|
+
console.log(" [AZURE_FUNCTIONAPP_PUBLISH_PROFILE]");
|
|
104
|
+
console.log(await readCiCdSecret(publishProfileArgs, typeof outputs.functionsAppName === "string"));
|
|
105
|
+
console.log(" These values are shown only in this terminal; SwallowKit does not store them. Treat them as secrets.");
|
|
106
|
+
console.log(" Next: configure your CI/CD pipeline and manually trigger the first deployment (later pushes deploy automatically).");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Reads a CI/CD credential for display in the human terminal only. The value is
|
|
110
|
+
// never written to checkpoints, the ledger, or machine/MCP responses.
|
|
111
|
+
async function readCiCdSecret(args: string[], resourceKnown: boolean): Promise<string> {
|
|
112
|
+
const manual = ` Run manually: az ${args.map((arg) => (/[\s"]/.test(arg) ? `"${arg}"` : arg)).join(" ")}\n`;
|
|
113
|
+
if (!resourceKnown) return ` ⚠️ The resource name was not in the deployment outputs.\n${manual}`;
|
|
114
|
+
try {
|
|
115
|
+
const response = await runAzCliAsync(args, { timeout: 60_000 });
|
|
116
|
+
const value = response.stdout.trim();
|
|
117
|
+
if (response.status === 0 && !response.failureKind && !response.stdoutTruncated && value) return ` ${value}\n`;
|
|
118
|
+
} catch {
|
|
119
|
+
// Fall through to the manual command; deployment itself already succeeded.
|
|
120
|
+
}
|
|
121
|
+
return ` ⚠️ Could not retrieve automatically.\n${manual}`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function describeProvisionAction(plan: Pick<ProvisionPlanData, "mode" | "resourceGroup" | "location" | "pendingMigrations">): string {
|
|
125
|
+
switch (plan.mode) {
|
|
126
|
+
case "bootstrap":
|
|
127
|
+
return `Resource group ${plan.resourceGroup} does not exist yet. It will be created in ${plan.location} and the infrastructure in infra/main.bicep will be deployed.`;
|
|
128
|
+
case "migrate":
|
|
129
|
+
return `${plan.pendingMigrations.length} infrastructure migration(s) will be deployed to ${plan.resourceGroup}: ${plan.pendingMigrations.map((migration) => `${migration.version}-${migration.slug}`).join(", ")}.`;
|
|
130
|
+
case "adopt":
|
|
131
|
+
return `The existing resource group ${plan.resourceGroup} will be recorded as SwallowKit baseline 0. Nothing will be deployed.`;
|
|
132
|
+
case "reconcile":
|
|
133
|
+
return `infra/main.bicep will be redeployed to ${plan.resourceGroup}. Template-managed settings may be reset.`;
|
|
134
|
+
case "recover":
|
|
135
|
+
return `The existing baseline deployment in ${plan.resourceGroup} will be observed and its migration tag/ledger completed after Azure Succeeded. Bicep will not be submitted again.`;
|
|
136
|
+
default:
|
|
137
|
+
return `Resource group ${plan.resourceGroup}:`;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
5
140
|
|
|
6
141
|
const PRIMARY_REGIONS = [
|
|
7
142
|
{ title: "Japan East (japaneast)", value: "japaneast" },
|
|
@@ -24,7 +159,7 @@ const SWA_REGIONS = [
|
|
|
24
159
|
];
|
|
25
160
|
|
|
26
161
|
interface ProvisionCliOptions {
|
|
27
|
-
resourceGroup
|
|
162
|
+
resourceGroup?: string;
|
|
28
163
|
subscription?: string;
|
|
29
164
|
location?: string;
|
|
30
165
|
swaLocation?: string;
|
|
@@ -33,6 +168,40 @@ interface ProvisionCliOptions {
|
|
|
33
168
|
baseline?: string;
|
|
34
169
|
reconcile?: boolean;
|
|
35
170
|
approve?: boolean;
|
|
171
|
+
resume?: string;
|
|
172
|
+
status?: string;
|
|
173
|
+
resumeDeployment?: string;
|
|
174
|
+
waitSeconds?: string;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function formatProvisionResult(result: ApplyProvisionResult): string {
|
|
178
|
+
const outcome =
|
|
179
|
+
result.outcome === "succeeded"
|
|
180
|
+
? "✅ Provisioning completed, including migration tags and the local ledger."
|
|
181
|
+
: result.outcome === "in-progress"
|
|
182
|
+
? "⏳ Azure deployment is still in progress. The local wait ended; this is not an Azure failure."
|
|
183
|
+
: result.outcome === "partial"
|
|
184
|
+
? "⚠️ Provisioning is partially complete. Finish migration bookkeeping from the checkpoint; do not redeploy the baseline."
|
|
185
|
+
: result.outcome === "failed"
|
|
186
|
+
? "❌ Azure deployment reached a failed/cancelled terminal state."
|
|
187
|
+
: "⚠️ Azure deployment result is unknown. A local az interruption does not prove an Azure failure.";
|
|
188
|
+
const lines = [outcome, `Resource group: ${result.resourceGroup}`];
|
|
189
|
+
if (result.deployment) {
|
|
190
|
+
const reference = result.deployment.reference;
|
|
191
|
+
lines.push(`Deployment: ${reference.name}; subscription: ${reference.subscription}; state: ${result.deployment.state}${reference.id ? `; ID: ${reference.id}` : ""}`);
|
|
192
|
+
for (const error of result.deployment.errors) lines.push(`${error.code}: ${error.message}`);
|
|
193
|
+
}
|
|
194
|
+
for (const warning of result.warnings) lines.push(`Warning: ${humanProvisionMessage(warning)}`);
|
|
195
|
+
for (const diagnostic of result.diagnostics.filter((entry) => entry.failureKind || entry.outputTruncated)) {
|
|
196
|
+
lines.push(formatAzureDiagnostic(diagnostic));
|
|
197
|
+
}
|
|
198
|
+
if (result.status !== "complete") {
|
|
199
|
+
lines.push(`Check status: swallowkit provision --status ${result.runId}`);
|
|
200
|
+
if (result.outcome !== "failed") lines.push(`Resume and wait until completion: swallowkit provision --resume ${result.runId}`);
|
|
201
|
+
if (result.outcome !== "failed") lines.push("Azure may continue after Ctrl+C or process termination. No Azure cancellation or deletion is performed.");
|
|
202
|
+
else lines.push("Azure reported a terminal failure. Correct its cause before explicitly deploying again; resuming does not repair a failed deployment.");
|
|
203
|
+
}
|
|
204
|
+
return lines.join("\n");
|
|
36
205
|
}
|
|
37
206
|
|
|
38
207
|
async function resolveLocations(options: ProvisionCliOptions): Promise<{ location: string; swaLocation: string }> {
|
|
@@ -64,61 +233,130 @@ async function resolveLocations(options: ProvisionCliOptions): Promise<{ locatio
|
|
|
64
233
|
return { location, swaLocation };
|
|
65
234
|
}
|
|
66
235
|
|
|
67
|
-
export
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
236
|
+
export function createProvisionCommand(): Command {
|
|
237
|
+
return new Command("provision")
|
|
238
|
+
.description("Deploy Azure infrastructure and show resource information and CI/CD setup")
|
|
239
|
+
.option("-g, --resource-group <name>", "Resource group name (required for deployment)")
|
|
240
|
+
.option("--subscription <id>", "Azure subscription ID")
|
|
241
|
+
.option("--location <region>", "Primary location for Functions and Cosmos DB")
|
|
242
|
+
.option("--swa-location <region>", "Static Web App location")
|
|
243
|
+
.option("--what-if", "Check Azure what-if before deployment", false)
|
|
244
|
+
.option("--adopt-existing", "Adopt an existing untagged resource group without deploying main.bicep", false)
|
|
245
|
+
.option("--baseline <version>", "Baseline version for adoption (currently 0)")
|
|
246
|
+
.option("--reconcile", "Explicitly redeploy infra/main.bicep", false)
|
|
247
|
+
.addOption(new Option("--approve", "Compatibility option; invoking provision already requests execution").hideHelp())
|
|
248
|
+
.option("--status <runId>", "Read an existing execution checkpoint and Azure deployment state without writes")
|
|
249
|
+
.option("--resume <runId>", "Continue the original execution without resubmitting attempted deployments")
|
|
250
|
+
.option("--resume-deployment <name>", "Recover an older untracked baseline deployment after verifying its template and parameters")
|
|
251
|
+
.addOption(new Option("--wait-seconds <seconds>", "Optional limit on waiting, 0-3600 seconds (default: wait until Azure finishes; status: 0)").hideHelp())
|
|
252
|
+
.action(async (options: ProvisionCliOptions) => {
|
|
253
|
+
try {
|
|
254
|
+
ensureSwallowKitProject("provision");
|
|
255
|
+
const waitSeconds = options.waitSeconds === undefined ? undefined : Number(options.waitSeconds);
|
|
256
|
+
if (
|
|
257
|
+
(options.resume && options.status) ||
|
|
258
|
+
((options.resume || options.status) &&
|
|
259
|
+
(options.resourceGroup ||
|
|
260
|
+
options.resumeDeployment ||
|
|
261
|
+
options.reconcile ||
|
|
262
|
+
options.adoptExisting ||
|
|
263
|
+
options.subscription ||
|
|
264
|
+
options.location ||
|
|
265
|
+
options.swaLocation ||
|
|
266
|
+
options.whatIf ||
|
|
267
|
+
options.baseline !== undefined))
|
|
268
|
+
) {
|
|
269
|
+
throw new MachineCommandError(
|
|
270
|
+
"invalid-arguments",
|
|
271
|
+
"Use --status or --resume with the checkpoint ID alone; its original Azure target and reviewed files are preserved.",
|
|
272
|
+
undefined,
|
|
273
|
+
"blocked"
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
if (options.status) {
|
|
277
|
+
const result = await inspectProvisionOperation({
|
|
278
|
+
planId: options.status,
|
|
279
|
+
waitSeconds,
|
|
280
|
+
});
|
|
281
|
+
console.log(formatProvisionResult(result));
|
|
282
|
+
if (result.status === "blocked" || result.status === "failed") process.exitCode = 1;
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
if (options.resume) {
|
|
286
|
+
const result = await applyProvisionOperation({
|
|
287
|
+
planId: options.resume,
|
|
288
|
+
approve: true,
|
|
289
|
+
waitSeconds,
|
|
290
|
+
waitUntilComplete: waitSeconds === undefined,
|
|
291
|
+
});
|
|
292
|
+
console.log(formatProvisionResult(result));
|
|
293
|
+
await printProvisionSetup(result);
|
|
294
|
+
if (result.status === "blocked" || result.status === "failed") process.exitCode = 1;
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
if (!options.resourceGroup) throw new MachineCommandError("invalid-arguments", "Provision requires --resource-group.", undefined, "blocked");
|
|
298
|
+
const locations = await resolveLocations(options);
|
|
299
|
+
const baseline = options.baseline === undefined ? undefined : Number(options.baseline);
|
|
300
|
+
// Preview in memory: the same Azure checks as the agent workflow, but no saved plan file.
|
|
301
|
+
const preview = await planProvisionOperation(
|
|
302
|
+
{
|
|
303
|
+
resourceGroup: options.resourceGroup,
|
|
304
|
+
subscription: options.subscription,
|
|
305
|
+
location: locations.location,
|
|
306
|
+
swaLocation: locations.swaLocation,
|
|
307
|
+
whatIf: options.whatIf,
|
|
308
|
+
adoptExisting: options.adoptExisting,
|
|
309
|
+
baseline,
|
|
310
|
+
reconcile: options.reconcile,
|
|
311
|
+
resumeDeployment: options.resumeDeployment,
|
|
312
|
+
},
|
|
313
|
+
{ persist: false }
|
|
314
|
+
);
|
|
92
315
|
|
|
93
|
-
|
|
94
|
-
for (const warning of plan.warnings) console.warn(`Warning: ${warning}`);
|
|
95
|
-
if (plan.pendingMigrations.length > 0) {
|
|
96
|
-
console.log(`Pending migrations: ${plan.pendingMigrations.map((migration) => `${migration.version}-${migration.slug}`).join(", ")}`);
|
|
97
|
-
}
|
|
98
|
-
for (const command of plan.commands) console.log(` ${command}`);
|
|
316
|
+
for (const warning of preview.warnings) console.warn(`Warning: ${warning}`);
|
|
99
317
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
318
|
+
if (preview.mode === "offline") {
|
|
319
|
+
console.log(
|
|
320
|
+
[
|
|
321
|
+
"",
|
|
322
|
+
"❌ Azure CLI is unavailable, so the current state of Azure could not be checked. Nothing was changed in Azure.",
|
|
323
|
+
" 1. Make sure `az --version` works in this terminal (install: https://aka.ms/azure-cli).",
|
|
324
|
+
" 2. Sign in with `az login` (and `az account set --subscription <id>` if needed).",
|
|
325
|
+
" 3. Run this command again.",
|
|
326
|
+
].join("\n")
|
|
327
|
+
);
|
|
328
|
+
process.exitCode = 1;
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (preview.mode === "adoption-required") {
|
|
333
|
+
throw new Error(
|
|
334
|
+
"The existing resource group has no migration tag. For an interrupted baseline use --resume-deployment <original-name> to verify/observe it without redeploying. Legacy adoption applies only to a legacy manifest; do not replace an existing manifest."
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (preview.mode === "noop") {
|
|
339
|
+
console.log(`Resource group ${preview.resourceGroup} is already up to date. Nothing to do.`);
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
console.log(`\n${describeProvisionAction(preview)}`);
|
|
344
|
+
console.log("Deploying Azure infrastructure. Azure resources may incur charges.");
|
|
345
|
+
console.log("Waiting for the Azure deployment to finish. This often takes 10-20 minutes; resource information and CI/CD settings are shown when it completes.");
|
|
103
346
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
347
|
+
const result = await executeProvisionPlan(preview, {
|
|
348
|
+
approve: true,
|
|
349
|
+
retryHint: INTERACTIVE_PROVISION_RETRY_HINT,
|
|
350
|
+
waitSeconds,
|
|
351
|
+
waitUntilComplete: waitSeconds === undefined,
|
|
352
|
+
});
|
|
353
|
+
console.log(formatProvisionResult(result));
|
|
354
|
+
await printProvisionSetup(result);
|
|
355
|
+
if (result.status === "blocked" || result.status === "failed") process.exitCode = 1;
|
|
356
|
+
} catch (error) {
|
|
357
|
+
throw new Error(formatProvisionError(error));
|
|
108
358
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
name: "approved",
|
|
112
|
-
message: `Apply the ${plan.mode} plan?`,
|
|
113
|
-
initial: false,
|
|
114
|
-
});
|
|
115
|
-
approved = answer.approved === true;
|
|
116
|
-
}
|
|
117
|
-
if (!approved) {
|
|
118
|
-
console.log("Provision cancelled.");
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
359
|
+
});
|
|
360
|
+
}
|
|
121
361
|
|
|
122
|
-
|
|
123
|
-
console.log(`Provision completed (${plan.mode}). ${result.executedCommands.length} command(s) executed.`);
|
|
124
|
-
});
|
|
362
|
+
export const provisionCommand = createProvisionCommand();
|