@caelo-cms/provisioning 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter.d.ts +95 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +3 -0
- package/dist/adapter.js.map +1 -0
- package/dist/bootstrap-token.d.ts +11 -0
- package/dist/bootstrap-token.d.ts.map +1 -0
- package/dist/bootstrap-token.js +9 -0
- package/dist/bootstrap-token.js.map +1 -0
- package/dist/caddy.d.ts +34 -0
- package/dist/caddy.d.ts.map +1 -0
- package/dist/caddy.js +53 -0
- package/dist/caddy.js.map +1 -0
- package/{src/cdn-copy.ts → dist/cdn-copy.d.ts} +11 -42
- package/dist/cdn-copy.d.ts.map +1 -0
- package/dist/cdn-copy.js +48 -0
- package/dist/cdn-copy.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +670 -0
- package/dist/cli.js.map +1 -0
- package/dist/compose.d.ts +27 -0
- package/dist/compose.d.ts.map +1 -0
- package/{src/compose.ts → dist/compose.js} +15 -35
- package/dist/compose.js.map +1 -0
- package/dist/dns/cloudflare.d.ts +9 -0
- package/dist/dns/cloudflare.d.ts.map +1 -0
- package/dist/dns/cloudflare.js +160 -0
- package/dist/dns/cloudflare.js.map +1 -0
- package/dist/dns/index.d.ts +12 -0
- package/dist/dns/index.d.ts.map +1 -0
- package/dist/dns/index.js +42 -0
- package/dist/dns/index.js.map +1 -0
- package/dist/dns/manual.d.ts +5 -0
- package/dist/dns/manual.d.ts.map +1 -0
- package/dist/dns/manual.js +96 -0
- package/dist/dns/manual.js.map +1 -0
- package/dist/dns/types.d.ts +23 -0
- package/dist/dns/types.d.ts.map +1 -0
- package/dist/dns/types.js +3 -0
- package/dist/dns/types.js.map +1 -0
- package/dist/gcloud.d.ts +42 -0
- package/dist/gcloud.d.ts.map +1 -0
- package/dist/gcloud.js +187 -0
- package/dist/gcloud.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/install-state.d.ts +54 -0
- package/dist/install-state.d.ts.map +1 -0
- package/dist/install-state.js +118 -0
- package/dist/install-state.js.map +1 -0
- package/dist/lifecycle.d.ts +19 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +589 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/migration-runner.d.ts +15 -0
- package/dist/migration-runner.d.ts.map +1 -0
- package/dist/migration-runner.js +174 -0
- package/dist/migration-runner.js.map +1 -0
- package/dist/redirects-emit.d.ts +65 -0
- package/dist/redirects-emit.d.ts.map +1 -0
- package/dist/redirects-emit.js +92 -0
- package/dist/redirects-emit.js.map +1 -0
- package/dist/wizard.d.ts +35 -0
- package/dist/wizard.d.ts.map +1 -0
- package/dist/wizard.js +160 -0
- package/dist/wizard.js.map +1 -0
- package/dist/wizards/gcp-cost.d.ts +27 -0
- package/dist/wizards/gcp-cost.d.ts.map +1 -0
- package/dist/wizards/gcp-cost.js +77 -0
- package/dist/wizards/gcp-cost.js.map +1 -0
- package/dist/wizards/gcp-pulumi.d.ts +37 -0
- package/dist/wizards/gcp-pulumi.d.ts.map +1 -0
- package/dist/wizards/gcp-pulumi.js +100 -0
- package/dist/wizards/gcp-pulumi.js.map +1 -0
- package/dist/wizards/gcp.d.ts +9 -0
- package/dist/wizards/gcp.d.ts.map +1 -0
- package/dist/wizards/gcp.js +895 -0
- package/dist/wizards/gcp.js.map +1 -0
- package/package.json +34 -7
- package/stacks/aws/index.ts +6 -7
- package/stacks/azure/index.ts +11 -11
- package/stacks/gcp/Pulumi.production.yaml +16 -0
- package/stacks/gcp/Pulumi.yaml +52 -6
- package/stacks/gcp/index.ts +569 -188
- package/stacks/self-hosted/index.ts +3 -3
- package/static/welcome.html +155 -0
- package/src/adapter.ts +0 -103
- package/src/bootstrap-token.ts +0 -20
- package/src/caddy.ts +0 -93
- package/src/cli.ts +0 -674
- package/src/index.test.ts +0 -246
- package/src/index.ts +0 -52
- package/src/redirects-emit.ts +0 -166
- package/tsconfig.json +0 -16
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
/**
|
|
3
|
+
* P21 ship 3 — shared migration runner. Used by both the GCP wizard
|
|
4
|
+
* (apps/admin/scripts/wizards/gcp.ts) AND the lifecycle `upgrade`
|
|
5
|
+
* command (lifecycle.ts).
|
|
6
|
+
*
|
|
7
|
+
* Spawns a one-shot Cloud Run Job that invokes
|
|
8
|
+
* `bun /app/packages/migrations/src/migrate.ts <target>` against the
|
|
9
|
+
* private-IP Cloud SQL instance. The job inherits the running admin
|
|
10
|
+
* Cloud Run's image (so it always carries the matching migration set)
|
|
11
|
+
* + its DB URLs + its network/subnet.
|
|
12
|
+
*
|
|
13
|
+
* Idempotent: drizzle's `__drizzle_migrations` table tracks applied
|
|
14
|
+
* versions, so re-runs only apply NEW migrations. Returns `{ok}` so
|
|
15
|
+
* callers (especially `upgrade`) can abort cleanly when a migration
|
|
16
|
+
* fails BEFORE traffic shifts to the new revision.
|
|
17
|
+
*/
|
|
18
|
+
import { spinner } from "@clack/prompts";
|
|
19
|
+
import { green, red } from "kleur/colors";
|
|
20
|
+
import { gcloud } from "./gcloud.js";
|
|
21
|
+
/**
|
|
22
|
+
* Resolve the running admin Cloud Run service's image + DB URLs +
|
|
23
|
+
* VPC config. Returns null on any failure; caller surfaces the error.
|
|
24
|
+
*/
|
|
25
|
+
async function readAdminConfig(opts) {
|
|
26
|
+
// Find the actual deployed service name (Pulumi appends a 7-char
|
|
27
|
+
// suffix; hardcoding the bare name 404s).
|
|
28
|
+
const list = await gcloud([
|
|
29
|
+
"run",
|
|
30
|
+
"services",
|
|
31
|
+
"list",
|
|
32
|
+
"--region",
|
|
33
|
+
opts.region,
|
|
34
|
+
"--project",
|
|
35
|
+
opts.projectId,
|
|
36
|
+
"--filter",
|
|
37
|
+
"metadata.name~^caelo-production-admin",
|
|
38
|
+
"--format=value(metadata.name)",
|
|
39
|
+
]);
|
|
40
|
+
if (!list.ok)
|
|
41
|
+
return null;
|
|
42
|
+
const serviceName = list.stdout.trim().split("\n")[0]?.trim();
|
|
43
|
+
if (!serviceName)
|
|
44
|
+
return null;
|
|
45
|
+
const descr = await gcloud([
|
|
46
|
+
"run",
|
|
47
|
+
"services",
|
|
48
|
+
"describe",
|
|
49
|
+
serviceName,
|
|
50
|
+
"--region",
|
|
51
|
+
opts.region,
|
|
52
|
+
"--project",
|
|
53
|
+
opts.projectId,
|
|
54
|
+
"--format=json",
|
|
55
|
+
]);
|
|
56
|
+
if (!descr.ok)
|
|
57
|
+
return null;
|
|
58
|
+
try {
|
|
59
|
+
const d = JSON.parse(descr.stdout);
|
|
60
|
+
const c = d.spec.template.spec.containers[0];
|
|
61
|
+
const imageRef = c?.image ?? "";
|
|
62
|
+
let adminUrl = "";
|
|
63
|
+
let publicUrl = "";
|
|
64
|
+
for (const e of c?.env ?? []) {
|
|
65
|
+
if (e.name === "ADMIN_DATABASE_URL")
|
|
66
|
+
adminUrl = e.value ?? "";
|
|
67
|
+
if (e.name === "PUBLIC_ADMIN_DATABASE_URL")
|
|
68
|
+
publicUrl = e.value ?? "";
|
|
69
|
+
}
|
|
70
|
+
let networkRef = "";
|
|
71
|
+
let subnetRef = "";
|
|
72
|
+
const niAnnotation = d.spec.template.metadata?.annotations?.["run.googleapis.com/network-interfaces"];
|
|
73
|
+
if (niAnnotation) {
|
|
74
|
+
try {
|
|
75
|
+
const parsed = JSON.parse(niAnnotation);
|
|
76
|
+
networkRef = parsed[0]?.network ?? "";
|
|
77
|
+
subnetRef = parsed[0]?.subnetwork ?? "";
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// fall through
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (!networkRef || !subnetRef) {
|
|
84
|
+
const ni = d.spec.template.spec.vpcAccess?.networkInterfaces?.[0];
|
|
85
|
+
networkRef ||= ni?.network ?? "";
|
|
86
|
+
subnetRef ||= ni?.subnetwork ?? "";
|
|
87
|
+
}
|
|
88
|
+
if (!imageRef || !adminUrl || !publicUrl || !networkRef || !subnetRef)
|
|
89
|
+
return null;
|
|
90
|
+
return { imageRef, adminUrl, publicUrl, networkRef, subnetRef };
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Run admin + public migrations against Cloud SQL. Returns `{ok}` so
|
|
98
|
+
* callers can decide what to do on failure (the wizard exits the
|
|
99
|
+
* process; upgrade aborts before rolling Cloud Run).
|
|
100
|
+
*/
|
|
101
|
+
export async function runMigrationsViaCloudRunJob(opts) {
|
|
102
|
+
const sAdmin = spinner();
|
|
103
|
+
sAdmin.start("Reading admin Cloud Run config for migration job...");
|
|
104
|
+
const cfg = await readAdminConfig(opts);
|
|
105
|
+
if (!cfg) {
|
|
106
|
+
sAdmin.stop(red("Couldn't resolve admin config — admin may not be deployed yet"));
|
|
107
|
+
return { ok: false, error: "admin-config-unresolved" };
|
|
108
|
+
}
|
|
109
|
+
sAdmin.stop(green(`Admin config resolved (${cfg.imageRef.slice(-19)})`));
|
|
110
|
+
for (const target of ["admin", "public"]) {
|
|
111
|
+
const s = spinner();
|
|
112
|
+
s.start(`Applying ${target} migrations via one-shot Cloud Run Job...`);
|
|
113
|
+
// Delete-then-create so each invocation runs with the freshest
|
|
114
|
+
// image + env (no stale job specs).
|
|
115
|
+
await gcloud([
|
|
116
|
+
"run",
|
|
117
|
+
"jobs",
|
|
118
|
+
"delete",
|
|
119
|
+
`caelo-migrate-${target}`,
|
|
120
|
+
"--region",
|
|
121
|
+
opts.region,
|
|
122
|
+
"--project",
|
|
123
|
+
opts.projectId,
|
|
124
|
+
"--quiet",
|
|
125
|
+
]);
|
|
126
|
+
const create = await gcloud([
|
|
127
|
+
"run",
|
|
128
|
+
"jobs",
|
|
129
|
+
"create",
|
|
130
|
+
`caelo-migrate-${target}`,
|
|
131
|
+
`--image=${cfg.imageRef}`,
|
|
132
|
+
"--region",
|
|
133
|
+
opts.region,
|
|
134
|
+
"--project",
|
|
135
|
+
opts.projectId,
|
|
136
|
+
"--service-account",
|
|
137
|
+
`caelo-production-run-sa@${opts.projectId}.iam.gserviceaccount.com`,
|
|
138
|
+
"--network",
|
|
139
|
+
cfg.networkRef.split("/").pop() ?? cfg.networkRef,
|
|
140
|
+
"--subnet",
|
|
141
|
+
cfg.subnetRef.split("/").pop() ?? cfg.subnetRef,
|
|
142
|
+
"--vpc-egress=private-ranges-only",
|
|
143
|
+
"--command=bun",
|
|
144
|
+
`--args=--bun,/app/packages/migrations/src/migrate.ts,${target}`,
|
|
145
|
+
"--set-env-vars",
|
|
146
|
+
`ADMIN_DATABASE_URL=${cfg.adminUrl},PUBLIC_ADMIN_DATABASE_URL=${cfg.publicUrl}`,
|
|
147
|
+
"--max-retries=0",
|
|
148
|
+
"--task-timeout=10m",
|
|
149
|
+
"--quiet",
|
|
150
|
+
]);
|
|
151
|
+
if (!create.ok) {
|
|
152
|
+
s.stop(red(`Job create failed: ${create.stderr.trim()}`));
|
|
153
|
+
return { ok: false, error: `migrate-${target}-create: ${create.stderr.trim()}` };
|
|
154
|
+
}
|
|
155
|
+
const exec = await gcloud([
|
|
156
|
+
"run",
|
|
157
|
+
"jobs",
|
|
158
|
+
"execute",
|
|
159
|
+
`caelo-migrate-${target}`,
|
|
160
|
+
"--region",
|
|
161
|
+
opts.region,
|
|
162
|
+
"--project",
|
|
163
|
+
opts.projectId,
|
|
164
|
+
"--wait",
|
|
165
|
+
]);
|
|
166
|
+
if (!exec.ok) {
|
|
167
|
+
s.stop(red(`${target} migrations failed: ${exec.stderr.trim()}`));
|
|
168
|
+
return { ok: false, error: `migrate-${target}-exec: ${exec.stderr.trim()}` };
|
|
169
|
+
}
|
|
170
|
+
s.stop(green(`${target} migrations applied`));
|
|
171
|
+
}
|
|
172
|
+
return { ok: true };
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=migration-runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migration-runner.js","sourceRoot":"","sources":["../src/migration-runner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* P15 — per-provider redirect file generators.
|
|
3
|
+
*
|
|
4
|
+
* P14's self-hosted Caddy consumes a `_redirects.caddy` file. Cloud
|
|
5
|
+
* providers want different formats: Cloudflare Pages reads `_redirects`
|
|
6
|
+
* (also a fine fit for any platform that consumes that format),
|
|
7
|
+
* CloudFront wants a JSON config a Lambda@Edge function reads at
|
|
8
|
+
* startup, and Azure Front Door wants a rules-engine RuleEngineRule[].
|
|
9
|
+
*
|
|
10
|
+
* The deploy step calls the right emitter based on `process.env.CAELO_PROVIDER`
|
|
11
|
+
* and uploads the result to the right place. Each emitter is pure +
|
|
12
|
+
* idempotent — same input → same byte output.
|
|
13
|
+
*/
|
|
14
|
+
export type RedirectStatusCode = 301 | 302 | 307 | 308;
|
|
15
|
+
export interface RedirectRow {
|
|
16
|
+
readonly fromPath: string;
|
|
17
|
+
readonly toPath: string;
|
|
18
|
+
readonly statusCode: RedirectStatusCode;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Cloudflare Pages `_redirects` format (also consumed by Netlify, Vercel,
|
|
22
|
+
* Render, etc.). One redirect per line: `<from> <to> <status>`. Wildcards
|
|
23
|
+
* use `:splat`; we only support exact-path redirects in v1 (matches what
|
|
24
|
+
* Caelo's redirects table allows).
|
|
25
|
+
*/
|
|
26
|
+
export declare function emitRedirectsCloudflare(rows: ReadonlyArray<RedirectRow>): string;
|
|
27
|
+
/**
|
|
28
|
+
* CloudFront via Lambda@Edge. Returns a JSON config the L@E function
|
|
29
|
+
* reads at startup + a tiny Lambda source that consumes it. The L@E
|
|
30
|
+
* function is deployed to `us-east-1` (CloudFront constraint); the
|
|
31
|
+
* config blob is bundled into the Lambda deployment artifact.
|
|
32
|
+
*
|
|
33
|
+
* Returning both halves lets the AWS stack ship a single asset.
|
|
34
|
+
*/
|
|
35
|
+
export interface CloudFrontRedirectArtifact {
|
|
36
|
+
readonly jsonConfig: string;
|
|
37
|
+
readonly lambdaSource: string;
|
|
38
|
+
}
|
|
39
|
+
export declare function emitRedirectsCloudFront(rows: ReadonlyArray<RedirectRow>): CloudFrontRedirectArtifact;
|
|
40
|
+
/**
|
|
41
|
+
* Azure Front Door rules engine. Returns a Pulumi-compatible
|
|
42
|
+
* RuleEngineRule[]-shaped array (untyped here so callers don't need to
|
|
43
|
+
* import @pulumi/azure-native). Each rule fires on a path-equals match
|
|
44
|
+
* + executes a 301/302/etc. response.
|
|
45
|
+
*/
|
|
46
|
+
export interface FrontDoorRule {
|
|
47
|
+
readonly name: string;
|
|
48
|
+
readonly order: number;
|
|
49
|
+
readonly conditions: ReadonlyArray<{
|
|
50
|
+
readonly name: "RequestUri";
|
|
51
|
+
readonly parameters: {
|
|
52
|
+
readonly operator: "Equal";
|
|
53
|
+
readonly matchValues: ReadonlyArray<string>;
|
|
54
|
+
};
|
|
55
|
+
}>;
|
|
56
|
+
readonly actions: ReadonlyArray<{
|
|
57
|
+
readonly name: "UrlRedirect";
|
|
58
|
+
readonly parameters: {
|
|
59
|
+
readonly redirectType: "Moved" | "Found" | "TemporaryRedirect" | "PermanentRedirect";
|
|
60
|
+
readonly destinationPath: string;
|
|
61
|
+
};
|
|
62
|
+
}>;
|
|
63
|
+
}
|
|
64
|
+
export declare function emitRedirectsAzureFrontDoor(rows: ReadonlyArray<RedirectRow>): ReadonlyArray<FrontDoorRule>;
|
|
65
|
+
//# sourceMappingURL=redirects-emit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redirects-emit.d.ts","sourceRoot":"","sources":["../src/redirects-emit.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AAEH,MAAM,MAAM,kBAAkB,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAEvD,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,kBAAkB,CAAC;CACzC;AA6BD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,aAAa,CAAC,WAAW,CAAC,GAAG,MAAM,CAKhF;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,aAAa,CAAC,WAAW,CAAC,GAC/B,0BAA0B,CAwB5B;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC;QACjC,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;QAC5B,QAAQ,CAAC,UAAU,EAAE;YACnB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;YAC3B,QAAQ,CAAC,WAAW,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;SAC7C,CAAC;KACH,CAAC,CAAC;IACH,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;QAC9B,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;QAC7B,QAAQ,CAAC,UAAU,EAAE;YACnB,QAAQ,CAAC,YAAY,EAAE,OAAO,GAAG,OAAO,GAAG,mBAAmB,GAAG,mBAAmB,CAAC;YACrF,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;SAClC,CAAC;KACH,CAAC,CAAC;CACJ;AAED,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,aAAa,CAAC,WAAW,CAAC,GAC/B,aAAa,CAAC,aAAa,CAAC,CAqB9B"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
/**
|
|
3
|
+
* Reject whitespace + control chars in either path before any emitter
|
|
4
|
+
* touches them. The Cloudflare `_redirects` format is space-delimited;
|
|
5
|
+
* a `fromPath` containing a space ambiguates the line (CF parses
|
|
6
|
+
* `/old foo /new 301` as from=`/old`, to=`foo`, status=`/new`, then
|
|
7
|
+
* fails on `301`). Caelo's redirects table normally rejects these
|
|
8
|
+
* upstream but the emitter shouldn't trust that — pure functions
|
|
9
|
+
* defend their own contract.
|
|
10
|
+
*/
|
|
11
|
+
function assertPathClean(label, path) {
|
|
12
|
+
// \p{Cc} matches the Unicode control category (U+0000–U+001F + U+007F–U+009F);
|
|
13
|
+
// the linter rejects literal \x00-\x1f ranges in regex, but the intent — defending
|
|
14
|
+
// emitter contracts against control chars — is the explicit reason for this check.
|
|
15
|
+
if (/[\s\p{Cc}]/u.test(path)) {
|
|
16
|
+
throw new Error(`redirects-emit: ${label}=${JSON.stringify(path)} contains whitespace or control chars; reject upstream`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function assertRowsClean(rows) {
|
|
20
|
+
for (const r of rows) {
|
|
21
|
+
assertPathClean("fromPath", r.fromPath);
|
|
22
|
+
assertPathClean("toPath", r.toPath);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Cloudflare Pages `_redirects` format (also consumed by Netlify, Vercel,
|
|
27
|
+
* Render, etc.). One redirect per line: `<from> <to> <status>`. Wildcards
|
|
28
|
+
* use `:splat`; we only support exact-path redirects in v1 (matches what
|
|
29
|
+
* Caelo's redirects table allows).
|
|
30
|
+
*/
|
|
31
|
+
export function emitRedirectsCloudflare(rows) {
|
|
32
|
+
assertRowsClean(rows);
|
|
33
|
+
const header = "# Generated by @caelo-cms/provisioning — do not edit by hand.\n";
|
|
34
|
+
const lines = rows.map((r) => `${r.fromPath} ${r.toPath} ${r.statusCode}`);
|
|
35
|
+
return header + lines.join("\n") + (rows.length > 0 ? "\n" : "");
|
|
36
|
+
}
|
|
37
|
+
export function emitRedirectsCloudFront(rows) {
|
|
38
|
+
assertRowsClean(rows);
|
|
39
|
+
const jsonConfig = JSON.stringify({ redirects: rows.map((r) => ({ from: r.fromPath, to: r.toPath, status: r.statusCode })) }, null, 2);
|
|
40
|
+
// Tiny Lambda@Edge handler. v1 is exact-path match (matches Caelo's
|
|
41
|
+
// redirects table). Wildcard support lands when the table grows it.
|
|
42
|
+
const lambdaSource = `// SPDX-License-Identifier: MPL-2.0
|
|
43
|
+
// Generated by @caelo-cms/provisioning. Bundled with redirects.json.
|
|
44
|
+
const REDIRECTS = require("./redirects.json").redirects;
|
|
45
|
+
const TABLE = new Map(REDIRECTS.map((r) => [r.from, r]));
|
|
46
|
+
exports.handler = (event, _ctx, callback) => {
|
|
47
|
+
const req = event.Records[0].cf.request;
|
|
48
|
+
const m = TABLE.get(req.uri);
|
|
49
|
+
if (!m) return callback(null, req);
|
|
50
|
+
callback(null, {
|
|
51
|
+
status: String(m.status),
|
|
52
|
+
statusDescription: "Redirect",
|
|
53
|
+
headers: { location: [{ key: "Location", value: m.to }] },
|
|
54
|
+
});
|
|
55
|
+
};`;
|
|
56
|
+
return { jsonConfig, lambdaSource };
|
|
57
|
+
}
|
|
58
|
+
export function emitRedirectsAzureFrontDoor(rows) {
|
|
59
|
+
assertRowsClean(rows);
|
|
60
|
+
return rows.map((r, i) => ({
|
|
61
|
+
name: `redirect-${i + 1}`,
|
|
62
|
+
order: i + 1,
|
|
63
|
+
conditions: [
|
|
64
|
+
{
|
|
65
|
+
name: "RequestUri",
|
|
66
|
+
parameters: { operator: "Equal", matchValues: [r.fromPath] },
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
actions: [
|
|
70
|
+
{
|
|
71
|
+
name: "UrlRedirect",
|
|
72
|
+
parameters: {
|
|
73
|
+
redirectType: statusToFrontDoor(r.statusCode),
|
|
74
|
+
destinationPath: r.toPath,
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
function statusToFrontDoor(status) {
|
|
81
|
+
switch (status) {
|
|
82
|
+
case 301:
|
|
83
|
+
return "Moved";
|
|
84
|
+
case 302:
|
|
85
|
+
return "Found";
|
|
86
|
+
case 307:
|
|
87
|
+
return "TemporaryRedirect";
|
|
88
|
+
case 308:
|
|
89
|
+
return "PermanentRedirect";
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=redirects-emit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redirects-emit.js","sourceRoot":"","sources":["../src/redirects-emit.ts"],"names":[],"mappings":"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"}
|
package/dist/wizard.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `bunx @caelo-cms/provisioning` — interactive wizard.
|
|
3
|
+
*
|
|
4
|
+
* Per CLAUDE.md §11.C, this is the primary surface OSS users interact
|
|
5
|
+
* with: one command, end-to-end, < 20 min, on the user's own cloud.
|
|
6
|
+
*
|
|
7
|
+
* Top-level shape:
|
|
8
|
+
* 1. Pick provider (gcp / aws / azure / self-hosted)
|
|
9
|
+
* 2. Detect existing install at `~/.caelo-<id>/` and offer to resume
|
|
10
|
+
* 3. Prompt for the few inputs the provider needs (domain, owner email,
|
|
11
|
+
* project id, Anthropic key — input-hidden)
|
|
12
|
+
* 4. Show cost estimate + final confirm
|
|
13
|
+
* 5. Delegate to the provider-specific wizard which handles project
|
|
14
|
+
* bootstrap → Pulumi up → DNS → cert → IAP enable → bootstrap URL
|
|
15
|
+
*
|
|
16
|
+
* Each provider wizard is a separate file under `wizards/<provider>.ts`
|
|
17
|
+
* so adding a new provider is one new file, not a sprawling switch.
|
|
18
|
+
*/
|
|
19
|
+
import { spinner } from "@clack/prompts";
|
|
20
|
+
import { type Provider } from "./install-state.js";
|
|
21
|
+
export interface WizardOptions {
|
|
22
|
+
/** Skip prompts; require all inputs via flags. CI-friendly. */
|
|
23
|
+
nonInteractive?: boolean;
|
|
24
|
+
/** Pre-supplied provider — skips the picker. */
|
|
25
|
+
provider?: Provider;
|
|
26
|
+
/** Pre-supplied domain — skips the prompt. */
|
|
27
|
+
domain?: string;
|
|
28
|
+
/** Pre-supplied owner email — skips the prompt. */
|
|
29
|
+
ownerEmail?: string;
|
|
30
|
+
/** Pre-supplied GCP project id — skips the prompt for gcp. */
|
|
31
|
+
projectId?: string;
|
|
32
|
+
}
|
|
33
|
+
export declare function runWizard(opts?: WizardOptions): Promise<void>;
|
|
34
|
+
export { spinner };
|
|
35
|
+
//# sourceMappingURL=wizard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wizard.d.ts","sourceRoot":"","sources":["../src/wizard.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAmD,OAAO,EAAQ,MAAM,gBAAgB,CAAC;AAEhG,OAAO,EAGL,KAAK,QAAQ,EAGd,MAAM,oBAAoB,CAAC;AAG5B,MAAM,WAAW,aAAa;IAC5B,+DAA+D;IAC/D,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,gDAAgD;IAChD,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,8CAA8C;IAC9C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAsB,SAAS,CAAC,IAAI,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAyEvE;AA0ED,OAAO,EAAE,OAAO,EAAE,CAAC"}
|
package/dist/wizard.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
/**
|
|
3
|
+
* `bunx @caelo-cms/provisioning` — interactive wizard.
|
|
4
|
+
*
|
|
5
|
+
* Per CLAUDE.md §11.C, this is the primary surface OSS users interact
|
|
6
|
+
* with: one command, end-to-end, < 20 min, on the user's own cloud.
|
|
7
|
+
*
|
|
8
|
+
* Top-level shape:
|
|
9
|
+
* 1. Pick provider (gcp / aws / azure / self-hosted)
|
|
10
|
+
* 2. Detect existing install at `~/.caelo-<id>/` and offer to resume
|
|
11
|
+
* 3. Prompt for the few inputs the provider needs (domain, owner email,
|
|
12
|
+
* project id, Anthropic key — input-hidden)
|
|
13
|
+
* 4. Show cost estimate + final confirm
|
|
14
|
+
* 5. Delegate to the provider-specific wizard which handles project
|
|
15
|
+
* bootstrap → Pulumi up → DNS → cert → IAP enable → bootstrap URL
|
|
16
|
+
*
|
|
17
|
+
* Each provider wizard is a separate file under `wizards/<provider>.ts`
|
|
18
|
+
* so adding a new provider is one new file, not a sprawling switch.
|
|
19
|
+
*/
|
|
20
|
+
import { cancel, confirm, intro, isCancel, outro, select, spinner, text } from "@clack/prompts";
|
|
21
|
+
import { bold, cyan, dim } from "kleur/colors";
|
|
22
|
+
import { deriveInstallId, ensureInstallDir, readMetadata, writeMetadata, } from "./install-state.js";
|
|
23
|
+
import { runGcpWizard } from "./wizards/gcp.js";
|
|
24
|
+
export async function runWizard(opts = {}) {
|
|
25
|
+
intro(bold(cyan("Caelo CMS provisioner")) + dim(" — one-command deploy"));
|
|
26
|
+
// 1. Provider — pre-supplied via flag OR picker.
|
|
27
|
+
const provider = opts.provider ?? (await pickProvider());
|
|
28
|
+
// 2. Common inputs the provider wizard always needs. Provider-specific
|
|
29
|
+
// inputs (GCP project id, AWS region, etc.) are prompted inside the
|
|
30
|
+
// per-provider wizard.
|
|
31
|
+
const domain = opts.domain ?? (await promptDomain());
|
|
32
|
+
const ownerEmail = opts.ownerEmail ?? (await promptOwnerEmail());
|
|
33
|
+
// 3. Per-install state directory + resume detection. The install id is
|
|
34
|
+
// derived from (provider, projectId-or-domain) so re-runs land here.
|
|
35
|
+
// For GCP we don't have the project id yet (it's the next prompt);
|
|
36
|
+
// use domain as the seed, then rewrite metadata once project id is
|
|
37
|
+
// set in the GCP wizard.
|
|
38
|
+
const installId = deriveInstallId(provider, opts.projectId ?? domain);
|
|
39
|
+
ensureInstallDir(installId);
|
|
40
|
+
const existing = readMetadata(installId);
|
|
41
|
+
if (existing && !opts.nonInteractive) {
|
|
42
|
+
const resume = await confirm({
|
|
43
|
+
message: `Existing install '${installId}' detected (created ${dim(existing.createdAt)}). Resume?`,
|
|
44
|
+
initialValue: true,
|
|
45
|
+
});
|
|
46
|
+
if (isCancel(resume)) {
|
|
47
|
+
cancel("Cancelled.");
|
|
48
|
+
process.exit(0);
|
|
49
|
+
}
|
|
50
|
+
if (!resume) {
|
|
51
|
+
cancel(`Aborted. Run with a different domain or remove ~/.caelo-${installId}/ first.`);
|
|
52
|
+
process.exit(0);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
writeMetadata(installId, {
|
|
57
|
+
installId,
|
|
58
|
+
provider,
|
|
59
|
+
projectId: opts.projectId ?? null,
|
|
60
|
+
domain,
|
|
61
|
+
ownerEmail,
|
|
62
|
+
region: null,
|
|
63
|
+
createdAt: new Date().toISOString(),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
// 4. Delegate to the provider-specific wizard.
|
|
67
|
+
switch (provider) {
|
|
68
|
+
case "gcp":
|
|
69
|
+
await runGcpWizard({
|
|
70
|
+
installId,
|
|
71
|
+
domain,
|
|
72
|
+
ownerEmail,
|
|
73
|
+
projectId: opts.projectId ?? null,
|
|
74
|
+
nonInteractive: opts.nonInteractive ?? false,
|
|
75
|
+
});
|
|
76
|
+
break;
|
|
77
|
+
case "self-hosted":
|
|
78
|
+
cancel("Self-hosted wizard is the existing `cms-provision init` flow. Run `bunx @caelo-cms/provisioning init --domain ... --owner-email ...` for now; wizard polish lands in a follow-up commit.");
|
|
79
|
+
process.exit(0);
|
|
80
|
+
break;
|
|
81
|
+
case "aws":
|
|
82
|
+
case "azure":
|
|
83
|
+
cancel(`${provider.toUpperCase()} provider wizard not yet implemented. GCP is the first cloud target; AWS + Azure follow the same shape and land in upcoming commits.`);
|
|
84
|
+
process.exit(0);
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
outro(bold("Done. Welcome to Caelo CMS."));
|
|
88
|
+
}
|
|
89
|
+
async function pickProvider() {
|
|
90
|
+
const choice = await select({
|
|
91
|
+
message: "Where do you want to deploy?",
|
|
92
|
+
options: [
|
|
93
|
+
{
|
|
94
|
+
value: "gcp",
|
|
95
|
+
label: "Google Cloud Platform",
|
|
96
|
+
hint: "Cloud Run + Cloud SQL + Cloud Storage + Cloud CDN. Default for v0.1.",
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
value: "self-hosted",
|
|
100
|
+
label: "Self-hosted (Docker Compose)",
|
|
101
|
+
hint: "Single Linux box. Postgres + Caddy + the admin + the gateway.",
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
value: "aws",
|
|
105
|
+
label: "Amazon Web Services",
|
|
106
|
+
hint: "Lambda + RDS + S3 + CloudFront. Lands in a follow-up commit.",
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
value: "azure",
|
|
110
|
+
label: "Microsoft Azure",
|
|
111
|
+
hint: "Container Apps + Azure DB + Blob + Front Door. Lands in a follow-up commit.",
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
});
|
|
115
|
+
if (isCancel(choice)) {
|
|
116
|
+
cancel("Cancelled.");
|
|
117
|
+
process.exit(0);
|
|
118
|
+
}
|
|
119
|
+
return choice;
|
|
120
|
+
}
|
|
121
|
+
async function promptDomain() {
|
|
122
|
+
const value = await text({
|
|
123
|
+
message: "Domain you'd like to use",
|
|
124
|
+
placeholder: "mysite.com",
|
|
125
|
+
validate: (v) => {
|
|
126
|
+
if (!v || v.length === 0)
|
|
127
|
+
return "Domain is required";
|
|
128
|
+
if (!/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+$/.test(v)) {
|
|
129
|
+
return "Looks like an invalid domain (e.g. mysite.com)";
|
|
130
|
+
}
|
|
131
|
+
return undefined;
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
if (isCancel(value)) {
|
|
135
|
+
cancel("Cancelled.");
|
|
136
|
+
process.exit(0);
|
|
137
|
+
}
|
|
138
|
+
return value;
|
|
139
|
+
}
|
|
140
|
+
async function promptOwnerEmail() {
|
|
141
|
+
const value = await text({
|
|
142
|
+
message: "Owner email (you'll be the IAP allowlisted Owner; can add more later)",
|
|
143
|
+
placeholder: "you@example.com",
|
|
144
|
+
validate: (v) => {
|
|
145
|
+
if (!v || v.length === 0)
|
|
146
|
+
return "Email is required";
|
|
147
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v))
|
|
148
|
+
return "Looks like an invalid email";
|
|
149
|
+
return undefined;
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
if (isCancel(value)) {
|
|
153
|
+
cancel("Cancelled.");
|
|
154
|
+
process.exit(0);
|
|
155
|
+
}
|
|
156
|
+
return value;
|
|
157
|
+
}
|
|
158
|
+
// Re-export the spinner factory so per-provider wizards use a consistent UX.
|
|
159
|
+
export { spinner };
|
|
160
|
+
//# sourceMappingURL=wizard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wizard.js","sourceRoot":"","sources":["../src/wizard.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-resource static price table for the GCP stack — used by the
|
|
3
|
+
* wizard's pre-flight cost estimate. Prices are EUR/USD per month at
|
|
4
|
+
* `europe-west1` list rates as of 2026-05; ship updates with each
|
|
5
|
+
* Caelo release.
|
|
6
|
+
*
|
|
7
|
+
* The estimate is intentionally OVER-conservative — it shows the
|
|
8
|
+
* floor cost (idle + scale-to-zero traffic). Real usage adds egress
|
|
9
|
+
* + AI calls + storage growth on top.
|
|
10
|
+
*/
|
|
11
|
+
export interface CostLine {
|
|
12
|
+
name: string;
|
|
13
|
+
monthlyUsd: number;
|
|
14
|
+
notes?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface CostEstimateInputs {
|
|
17
|
+
cloudSqlTier: string;
|
|
18
|
+
cloudSqlHa: boolean;
|
|
19
|
+
adminMinInstances: number;
|
|
20
|
+
gatewayMinInstances: number;
|
|
21
|
+
wafAdaptiveProtection: boolean;
|
|
22
|
+
}
|
|
23
|
+
export declare function estimateGcpCost(inputs: CostEstimateInputs): {
|
|
24
|
+
lines: CostLine[];
|
|
25
|
+
totalUsd: number;
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=gcp-cost.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gcp-cost.d.ts","sourceRoot":"","sources":["../../src/wizards/gcp-cost.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AAEH,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,OAAO,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,qBAAqB,EAAE,OAAO,CAAC;CAChC;AAgBD,wBAAgB,eAAe,CAAC,MAAM,EAAE,kBAAkB,GAAG;IAC3D,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAiEA"}
|