@caelo-cms/provisioning 0.10.48 → 0.10.50
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/caddy/Caddyfile.production +5 -1
- package/caddy/Caddyfile.staging +5 -1
- package/dist/agent-db.js +102 -58
- package/dist/caddy-redirects.d.ts +15 -0
- package/dist/caddy-redirects.d.ts.map +1 -0
- package/dist/caddy-redirects.js +65 -0
- package/dist/caddy-redirects.js.map +1 -0
- package/dist/caddy.d.ts +22 -2
- package/dist/caddy.d.ts.map +1 -1
- package/dist/caddy.js +66 -17
- package/dist/caddy.js.map +1 -1
- package/dist/cli-routing.d.ts.map +1 -1
- package/dist/cli-routing.js +7 -0
- package/dist/cli-routing.js.map +1 -1
- package/dist/cli.js +248 -254
- package/dist/cli.js.map +1 -1
- package/dist/compose.d.ts +54 -2
- package/dist/compose.d.ts.map +1 -1
- package/dist/compose.js +86 -32
- package/dist/compose.js.map +1 -1
- package/dist/gcp-names.d.ts +12 -0
- package/dist/gcp-names.d.ts.map +1 -1
- package/dist/gcp-names.js +21 -0
- package/dist/gcp-names.js.map +1 -1
- package/dist/gcp-url-map.d.ts +27 -0
- package/dist/gcp-url-map.d.ts.map +1 -0
- package/dist/gcp-url-map.js +62 -0
- package/dist/gcp-url-map.js.map +1 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/infra-contract.d.ts +20 -1
- package/dist/infra-contract.d.ts.map +1 -1
- package/dist/infra-contract.js +39 -1
- package/dist/infra-contract.js.map +1 -1
- package/dist/public-role-isolation.d.ts +19 -0
- package/dist/public-role-isolation.d.ts.map +1 -0
- package/dist/public-role-isolation.js +69 -0
- package/dist/public-role-isolation.js.map +1 -0
- package/dist/release-contract.d.ts.map +1 -1
- package/dist/release-contract.js +3 -0
- package/dist/release-contract.js.map +1 -1
- package/dist/release-manifest.d.ts +2 -41
- package/dist/release-manifest.d.ts.map +1 -1
- package/dist/release-manifest.js +20 -122
- package/dist/release-manifest.js.map +1 -1
- package/dist/self-hosted-backup.d.ts +86 -0
- package/dist/self-hosted-backup.d.ts.map +1 -0
- package/dist/self-hosted-backup.js +350 -0
- package/dist/self-hosted-backup.js.map +1 -0
- package/dist/self-hosted-release.d.ts +12 -3
- package/dist/self-hosted-release.d.ts.map +1 -1
- package/dist/self-hosted-release.js +6 -4
- package/dist/self-hosted-release.js.map +1 -1
- package/dist/self-hosted-start.d.ts +50 -0
- package/dist/self-hosted-start.d.ts.map +1 -0
- package/dist/self-hosted-start.js +59 -0
- package/dist/self-hosted-start.js.map +1 -0
- package/dist/self-hosted-upgrade.d.ts +30 -0
- package/dist/self-hosted-upgrade.d.ts.map +1 -1
- package/dist/self-hosted-upgrade.js +29 -7
- package/dist/self-hosted-upgrade.js.map +1 -1
- package/dist/tar-entries.d.ts +14 -0
- package/dist/tar-entries.d.ts.map +1 -0
- package/dist/tar-entries.js +104 -0
- package/dist/tar-entries.js.map +1 -0
- package/dist/upgrade/converge.d.ts +2 -2
- package/dist/upgrade/converge.d.ts.map +1 -1
- package/dist/upgrade/converge.js +44 -2
- package/dist/upgrade/converge.js.map +1 -1
- package/dist/upgrade/release-images.d.ts +14 -2
- package/dist/upgrade/release-images.d.ts.map +1 -1
- package/dist/upgrade/release-images.js +4 -2
- package/dist/upgrade/release-images.js.map +1 -1
- package/dist/upgrader-assess.d.ts +4 -1
- package/dist/upgrader-assess.d.ts.map +1 -1
- package/dist/upgrader-assess.js +8 -1
- package/dist/upgrader-assess.js.map +1 -1
- package/dist/upgrader-job.d.ts +3 -1
- package/dist/upgrader-job.d.ts.map +1 -1
- package/dist/upgrader-job.js.map +1 -1
- package/dist/url-map-redirects-access.d.ts +15 -0
- package/dist/url-map-redirects-access.d.ts.map +1 -0
- package/dist/url-map-redirects-access.js +81 -0
- package/dist/url-map-redirects-access.js.map +1 -0
- package/dist/wizard.d.ts.map +1 -1
- package/dist/wizard.js +3 -1
- package/dist/wizard.js.map +1 -1
- package/dist/wizards/gcp.d.ts.map +1 -1
- package/dist/wizards/gcp.js +45 -0
- package/dist/wizards/gcp.js.map +1 -1
- package/package.json +2 -2
- package/src/admin-memory.test.ts +2 -2
- package/src/agent/run.test.ts +55 -0
- package/src/caddy-redirects.docker.test.ts +255 -0
- package/src/caddy-redirects.test.ts +115 -0
- package/src/caddy-redirects.ts +68 -0
- package/src/caddy-validate.test.ts +119 -0
- package/src/caddy.test.ts +108 -0
- package/src/caddy.ts +89 -20
- package/src/cli-routing.test.ts +12 -0
- package/src/cli-routing.ts +7 -0
- package/src/cli.ts +299 -287
- package/src/compose.ts +106 -34
- package/src/gcp-names.ts +23 -0
- package/src/gcp-url-map.test.ts +150 -0
- package/src/gcp-url-map.ts +81 -0
- package/src/index.test.ts +18 -146
- package/src/index.ts +0 -9
- package/src/infra-contract.test.ts +39 -0
- package/src/infra-contract.ts +42 -1
- package/src/public-role-isolation.test.ts +89 -0
- package/src/public-role-isolation.ts +83 -0
- package/src/release-contract.ts +3 -0
- package/src/release-manifest.test.ts +20 -1
- package/src/release-manifest.ts +39 -158
- package/src/self-hosted-backup.test.ts +344 -0
- package/src/self-hosted-backup.ts +480 -0
- package/src/self-hosted-compose.test.ts +88 -4
- package/src/self-hosted-release.ts +22 -7
- package/src/self-hosted-start.test.ts +68 -0
- package/src/self-hosted-start.ts +123 -0
- package/src/self-hosted-upgrade.ts +35 -8
- package/src/stack-contract.test.ts +2 -2
- package/src/tar-entries.test.ts +52 -0
- package/src/tar-entries.ts +116 -0
- package/src/upgrade/converge.ts +51 -4
- package/src/upgrade/release-images.ts +4 -2
- package/src/upgrade/upgrade.test.ts +78 -1
- package/src/upgrader-assess.test.ts +64 -1
- package/src/upgrader-assess.ts +20 -2
- package/src/upgrader-job.test.ts +3 -0
- package/src/upgrader-job.ts +3 -0
- package/src/url-map-redirects-access.test.ts +88 -0
- package/src/url-map-redirects-access.ts +89 -0
- package/src/wizard.ts +3 -1
- package/src/wizards/gcp.ts +48 -0
- package/stacks/aws/index.ts +34 -2
- package/stacks/azure/index.ts +13 -0
- package/stacks/gcp/index.ts +24 -17
- package/stacks/gcp-firebase/index.ts +7 -0
- package/stacks/self-hosted/Pulumi.yaml +2 -2
- package/stacks/self-hosted/README.md +3 -3
- package/stacks/self-hosted/index.ts +45 -22
- package/dist/redirects-emit.d.ts +0 -52
- package/dist/redirects-emit.d.ts.map +0 -1
- package/dist/redirects-emit.js +0 -111
- package/dist/redirects-emit.js.map +0 -1
- package/src/redirects-emit.ts +0 -175
package/stacks/gcp/index.ts
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
runServiceAccountId,
|
|
31
31
|
staticPublisherServiceAccountId,
|
|
32
32
|
} from "../../dist/gcp-names.js";
|
|
33
|
+
import { createSiteUrlMap } from "../../dist/gcp-url-map.js";
|
|
33
34
|
import {
|
|
34
35
|
ADMIN_MEMORY_DEFAULT,
|
|
35
36
|
adminEnvContract,
|
|
@@ -323,6 +324,13 @@ const pgAdminUser = new gcp.sql.User(
|
|
|
323
324
|
opts,
|
|
324
325
|
);
|
|
325
326
|
|
|
327
|
+
// #633 — the Admin API makes this user a member of cloudsqlsuperuser, and
|
|
328
|
+
// the stack cannot declare "no roles" (an empty `databaseRoles` reads as
|
|
329
|
+
// unset, and the field is write-only, so drift would never show).
|
|
330
|
+
// `cms-provision` revokes the membership on every install pass and every
|
|
331
|
+
// `upgrade` (public-role-isolation.ts); the migration job's role audit
|
|
332
|
+
// fails the install or upgrade while it remains
|
|
333
|
+
// (packages/migrations/src/role-audit.ts).
|
|
326
334
|
const pgPublicUser = new gcp.sql.User(
|
|
327
335
|
`${namePrefix}-pg-public`,
|
|
328
336
|
{ instance: sqlInstance.name, name: "public_role", password: publicRolePassword },
|
|
@@ -997,29 +1005,28 @@ new gcp.iap.WebBackendServiceIamMember(
|
|
|
997
1005
|
// URL map: routes by host header.
|
|
998
1006
|
// admin.<domain> → admin backend (IAP-gated)
|
|
999
1007
|
// <domain> → default static bucket; /api/* → gateway
|
|
1000
|
-
|
|
1008
|
+
// #587 — the public path matcher also carries the redirects table as
|
|
1009
|
+
// `urlRedirect` rules, written by the admin on every Confirm-publish; the
|
|
1010
|
+
// stack ignores that field after creation (src/gcp-url-map.ts).
|
|
1011
|
+
const urlMap = createSiteUrlMap(
|
|
1001
1012
|
`${namePrefix}-url-map`,
|
|
1002
1013
|
{
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
pathMatchers: [
|
|
1009
|
-
{
|
|
1010
|
-
name: "public",
|
|
1011
|
-
defaultService: staticBackendBucket.id,
|
|
1012
|
-
pathRules: [{ paths: ["/api/*"], service: gatewayBackendService.id }],
|
|
1013
|
-
},
|
|
1014
|
-
{
|
|
1015
|
-
name: "admin",
|
|
1016
|
-
defaultService: adminBackendService.id,
|
|
1017
|
-
},
|
|
1018
|
-
],
|
|
1014
|
+
domain,
|
|
1015
|
+
adminDomain,
|
|
1016
|
+
staticBackend: staticBackendBucket.id,
|
|
1017
|
+
gatewayBackend: gatewayBackendService.id,
|
|
1018
|
+
adminBackend: adminBackendService.id,
|
|
1019
1019
|
},
|
|
1020
1020
|
opts,
|
|
1021
1021
|
);
|
|
1022
1022
|
|
|
1023
|
+
// #587 — the admin's run SA may rewrite the URL map's redirect rules through
|
|
1024
|
+
// a project custom role. The CLI owns that role, not this stack: the
|
|
1025
|
+
// installer and `cms-provision upgrade` both run ensureUrlMapRedirectsAccess
|
|
1026
|
+
// (url-map-redirects-access.ts). A role declared here would collide with the
|
|
1027
|
+
// one upgrade creates on installs older than it (a role id exists once per
|
|
1028
|
+
// project, and a create of an existing one fails).
|
|
1029
|
+
|
|
1023
1030
|
// Managed TLS cert covering both hostnames.
|
|
1024
1031
|
const managedCert = new gcp.compute.ManagedSslCertificate(
|
|
1025
1032
|
`${namePrefix}-cert`,
|
|
@@ -295,6 +295,13 @@ const pgAdminUser = new gcp.sql.User(
|
|
|
295
295
|
{ instance: sqlInstance.name, name: "admin_role", password: postgresPassword },
|
|
296
296
|
opts,
|
|
297
297
|
);
|
|
298
|
+
// #633 — the Admin API makes this user a member of cloudsqlsuperuser, and
|
|
299
|
+
// the stack cannot declare "no roles" (an empty `databaseRoles` reads as
|
|
300
|
+
// unset, and the field is write-only, so drift would never show).
|
|
301
|
+
// `cms-provision` revokes the membership on every install pass and every
|
|
302
|
+
// `upgrade` (public-role-isolation.ts); the migration job's role audit
|
|
303
|
+
// fails the install or upgrade while it remains
|
|
304
|
+
// (packages/migrations/src/role-audit.ts).
|
|
298
305
|
new gcp.sql.User(
|
|
299
306
|
`${namePrefix}-public-user`,
|
|
300
307
|
{ instance: sqlInstance.name, name: "public_role", password: publicRolePassword },
|
|
@@ -13,11 +13,11 @@ runtime:
|
|
|
13
13
|
options:
|
|
14
14
|
typescript: true
|
|
15
15
|
packagemanager: bun
|
|
16
|
-
description: Caelo self-hosted Docker Compose stack (Postgres + Caddy +
|
|
16
|
+
description: Caelo self-hosted Docker Compose stack (Postgres + Caddy + admin/gateway).
|
|
17
17
|
config:
|
|
18
18
|
caelo-self-hosted:domain:
|
|
19
19
|
type: string
|
|
20
|
-
description: Primary domain (e.g. example.com).
|
|
20
|
+
description: Primary domain (e.g. example.com). The live site binds here, the admin to admin.<domain>, staging to staging.<domain>.
|
|
21
21
|
caelo-self-hosted:ownerEmail:
|
|
22
22
|
type: string
|
|
23
23
|
description: Operator email used for ACME registration with Let's Encrypt.
|
|
@@ -23,11 +23,11 @@ pulumi config set caelo-self-hosted:gatewayImage ghcr.io/caelo-cms/gateway@sha25
|
|
|
23
23
|
pulumi up
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
Pulumi mints postgres
|
|
26
|
+
Pulumi mints the postgres and role passwords and the KEK, generates `docker-compose.yml` + `Caddyfile` + `bootstrap.sh` + `pending-token.json` under `.caelo/`, starts Postgres, runs the migrations and runs `docker compose up -d`. Point DNS for `<domain>`, `admin.<domain>` and `staging.<domain>` at the host. After the run:
|
|
27
27
|
|
|
28
28
|
```bash
|
|
29
29
|
pulumi stack output bootstrapUrl
|
|
30
|
-
# → https://example.com/setup?token=<64-hex>
|
|
30
|
+
# → https://admin.example.com/setup?token=<64-hex>
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
Open that URL once Caddy has issued the cert (~30 s).
|
|
@@ -38,7 +38,7 @@ Open that URL once Caddy has issued the cert (~30 s).
|
|
|
38
38
|
pulumi destroy
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
Tears down the compose stack with `-v`, deleting all volumes (Postgres +
|
|
41
|
+
Tears down the compose stack with `-v`, deleting all volumes (Postgres + Caddy data). The generated files are removed too.
|
|
42
42
|
|
|
43
43
|
## Why this exists alongside `cms-provision`
|
|
44
44
|
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
* written to `<caeloDir>/pending-token.json`. Captured as a Pulumi
|
|
10
10
|
* output so operators can read `pulumi stack output bootstrapUrl`
|
|
11
11
|
* after `pulumi up`.
|
|
12
|
-
* 3. `caeloDockerCompose` —
|
|
12
|
+
* 3. `caeloDockerCompose` — starts Postgres, runs the migrations, runs
|
|
13
|
+
* `docker compose up -d` and tears down
|
|
13
14
|
* with `docker compose down -v` on destroy.
|
|
14
15
|
*
|
|
15
16
|
* Why thin: Caelo's self-hosted stack is fundamentally a single Compose
|
|
@@ -24,8 +25,15 @@ import { resolve } from "node:path";
|
|
|
24
25
|
import { local } from "@pulumi/command";
|
|
25
26
|
import * as pulumi from "@pulumi/pulumi";
|
|
26
27
|
import { generateBootstrapToken } from "../../dist/bootstrap-token.js";
|
|
27
|
-
import { generateCaddyfile } from "../../dist/caddy.js";
|
|
28
|
-
import {
|
|
28
|
+
import { generateCaddyfile, selfHostedAdminHost, selfHostedSeedDomains } from "../../dist/caddy.js";
|
|
29
|
+
import {
|
|
30
|
+
COMPOSE_ADMIN_UPSTREAM,
|
|
31
|
+
COMPOSE_GATEWAY_UPSTREAM,
|
|
32
|
+
composeProjectName,
|
|
33
|
+
generateDockerCompose,
|
|
34
|
+
SITE_OUTPUT_ROOT,
|
|
35
|
+
} from "../../dist/compose.js";
|
|
36
|
+
import { selfHostedBootstrapScript } from "../../dist/self-hosted-roles.js";
|
|
29
37
|
|
|
30
38
|
const cfg = new pulumi.Config();
|
|
31
39
|
const domain = cfg.require("domain");
|
|
@@ -44,6 +52,8 @@ const gatewayImage = cfg.require("gatewayImage");
|
|
|
44
52
|
|
|
45
53
|
const composePath = resolve(caeloDir, "docker-compose.yml");
|
|
46
54
|
const caddyPath = resolve(caeloDir, "Caddyfile");
|
|
55
|
+
// What Postgres runs on its first start: the roles + databases (compose.ts mounts it).
|
|
56
|
+
const bootstrapPath = resolve(caeloDir, "bootstrap.sh");
|
|
47
57
|
// The admin container mounts only `<caeloDir>/bootstrap/` (compose.ts).
|
|
48
58
|
const tokenPath = resolve(caeloDir, "bootstrap", "pending-token.json");
|
|
49
59
|
|
|
@@ -56,44 +66,45 @@ const postgresPassword = pulumi.secret(randomHex(32));
|
|
|
56
66
|
const adminRolePassword = pulumi.secret(randomHex(32));
|
|
57
67
|
const publicRolePassword = pulumi.secret(randomHex(32));
|
|
58
68
|
const gatewayRolePassword = pulumi.secret(randomHex(32));
|
|
59
|
-
|
|
60
|
-
const
|
|
69
|
+
// P18 — the KEK that encrypts stored provider keys.
|
|
70
|
+
const caeloSecretKek = pulumi.secret(randomHex(32));
|
|
61
71
|
|
|
62
72
|
const composeYaml = pulumi
|
|
63
73
|
.all([
|
|
64
74
|
postgresPassword,
|
|
65
|
-
minioRootPassword,
|
|
66
75
|
adminRolePassword,
|
|
67
76
|
publicRolePassword,
|
|
68
77
|
gatewayRolePassword,
|
|
78
|
+
caeloSecretKek,
|
|
69
79
|
])
|
|
70
|
-
.apply(([pgPw,
|
|
80
|
+
.apply(([pgPw, adminPw, publicPw, gatewayPw, kek]) =>
|
|
71
81
|
generateDockerCompose({
|
|
72
82
|
domain,
|
|
73
83
|
postgresPassword: pgPw,
|
|
74
84
|
rolePasswords: { admin: adminPw, public: publicPw, gateway: gatewayPw },
|
|
75
|
-
|
|
76
|
-
minioRootPassword: minioPw,
|
|
85
|
+
caeloSecretKek: kek,
|
|
77
86
|
anthropicApiKey: process.env.ANTHROPIC_API_KEY,
|
|
78
87
|
resendApiKey: process.env.RESEND_API_KEY,
|
|
79
88
|
diskSize: "20Gi",
|
|
80
89
|
...(adminMemory ? { adminMemory } : {}),
|
|
81
90
|
images: { mode: "release", admin: adminImage, gateway: gatewayImage },
|
|
91
|
+
ports: { http: 80, https: 443 },
|
|
92
|
+
project: composeProjectName(domain),
|
|
82
93
|
}),
|
|
83
94
|
);
|
|
84
95
|
|
|
96
|
+
// Same generator inputs as `cms-provision init` (#707): the admin on
|
|
97
|
+
// admin.<domain>, Caddy reaching the services by their Compose names.
|
|
85
98
|
const caddyConf = generateCaddyfile({
|
|
86
99
|
ownerEmail,
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
{ hostname: domain, kind: "public", env: "production" as const },
|
|
94
|
-
{ hostname: stagingHost, kind: "public", env: "staging" as const },
|
|
95
|
-
],
|
|
100
|
+
tls: "acme",
|
|
101
|
+
publicSiteRoot: `${SITE_OUTPUT_ROOT}/output/production/current`,
|
|
102
|
+
stagingSiteRoot: `${SITE_OUTPUT_ROOT}/output/staging/current`,
|
|
103
|
+
adminUpstream: COMPOSE_ADMIN_UPSTREAM,
|
|
104
|
+
gatewayUpstream: COMPOSE_GATEWAY_UPSTREAM,
|
|
105
|
+
domains: selfHostedSeedDomains(domain, stagingHost),
|
|
96
106
|
});
|
|
107
|
+
const bootstrapScript = selfHostedBootstrapScript({ agent: false });
|
|
97
108
|
|
|
98
109
|
// Mint the owner bootstrap token at preview time. Captured below as a
|
|
99
110
|
// Pulumi output so the URL surfaces on `pulumi up`.
|
|
@@ -111,8 +122,12 @@ EOF
|
|
|
111
122
|
cat > ${tokenPath} <<'EOF'
|
|
112
123
|
${JSON.stringify(tokenInfo, null, 2)}
|
|
113
124
|
EOF
|
|
125
|
+
cat > ${bootstrapPath} <<'EOF'
|
|
126
|
+
${bootstrapScript}
|
|
127
|
+
EOF
|
|
128
|
+
chmod 755 ${bootstrapPath}
|
|
114
129
|
`,
|
|
115
|
-
delete: pulumi.interpolate`rm -f ${composePath} ${caddyPath} ${tokenPath}`,
|
|
130
|
+
delete: pulumi.interpolate`rm -f ${composePath} ${caddyPath} ${tokenPath} ${bootstrapPath}`,
|
|
116
131
|
triggers: [composeYaml, caddyConf],
|
|
117
132
|
});
|
|
118
133
|
|
|
@@ -121,7 +136,15 @@ EOF
|
|
|
121
136
|
const composeUp = new local.Command(
|
|
122
137
|
"caeloDockerCompose",
|
|
123
138
|
{
|
|
124
|
-
|
|
139
|
+
// Same order as `cms-provision init` (self-hosted-start.ts): Postgres
|
|
140
|
+
// (its first start runs bootstrap.sh), the migrations with the
|
|
141
|
+
// install's admin image, then everything else.
|
|
142
|
+
create: pulumi.interpolate`set -e
|
|
143
|
+
docker compose -f ${composePath} up -d --wait postgres
|
|
144
|
+
for target in admin public; do
|
|
145
|
+
docker compose -f ${composePath} run --rm --no-deps -T caelo-admin bun --bun /app/packages/migrations/src/migrate.ts "$target"
|
|
146
|
+
done
|
|
147
|
+
docker compose -f ${composePath} up -d --remove-orphans`,
|
|
125
148
|
delete: pulumi.interpolate`docker compose -f ${composePath} down -v`,
|
|
126
149
|
triggers: [composeYaml],
|
|
127
150
|
},
|
|
@@ -130,9 +153,9 @@ const composeUp = new local.Command(
|
|
|
130
153
|
|
|
131
154
|
export const composeFilePath = composePath;
|
|
132
155
|
export const caddyFilePath = caddyPath;
|
|
133
|
-
export const bootstrapUrl = pulumi.interpolate`https://${domain}/setup?token=${tokenInfo.token}`;
|
|
156
|
+
export const bootstrapUrl = pulumi.interpolate`https://${selfHostedAdminHost(domain)}/setup?token=${tokenInfo.token}`;
|
|
134
157
|
export const bootstrapTokenExpiresAt = tokenInfo.expiresAt;
|
|
135
|
-
export {
|
|
158
|
+
export { postgresPassword };
|
|
136
159
|
|
|
137
160
|
// Re-export so the destroy summary shows operators what they'll lose.
|
|
138
161
|
export const composeRunOutput = composeUp.stdout;
|
package/dist/redirects-emit.d.ts
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
export type RedirectStatusCode = 301 | 302 | 307 | 308;
|
|
2
|
-
export interface RedirectRow {
|
|
3
|
-
readonly fromPath: string;
|
|
4
|
-
readonly toPath: string;
|
|
5
|
-
readonly statusCode: RedirectStatusCode;
|
|
6
|
-
}
|
|
7
|
-
/**
|
|
8
|
-
* Cloudflare Pages `_redirects` format (also consumed by Netlify, Vercel,
|
|
9
|
-
* Render, etc.). One redirect per line: `<from> <to> <status>`. Wildcards
|
|
10
|
-
* use `:splat`; we only support exact-path redirects in v1 (matches what
|
|
11
|
-
* Caelo's redirects table allows).
|
|
12
|
-
*/
|
|
13
|
-
export declare function emitRedirectsCloudflare(rows: ReadonlyArray<RedirectRow>): string;
|
|
14
|
-
/**
|
|
15
|
-
* CloudFront via Lambda@Edge. Returns a JSON config the L@E function
|
|
16
|
-
* reads at startup + a tiny Lambda source that consumes it. The L@E
|
|
17
|
-
* function is deployed to `us-east-1` (CloudFront constraint); the
|
|
18
|
-
* config blob is bundled into the Lambda deployment artifact.
|
|
19
|
-
*
|
|
20
|
-
* Returning both halves lets the AWS stack ship a single asset.
|
|
21
|
-
*/
|
|
22
|
-
export interface CloudFrontRedirectArtifact {
|
|
23
|
-
readonly jsonConfig: string;
|
|
24
|
-
readonly lambdaSource: string;
|
|
25
|
-
}
|
|
26
|
-
export declare function emitRedirectsCloudFront(rows: ReadonlyArray<RedirectRow>): CloudFrontRedirectArtifact;
|
|
27
|
-
/**
|
|
28
|
-
* Azure Front Door rules engine. Returns a Pulumi-compatible
|
|
29
|
-
* RuleEngineRule[]-shaped array (untyped here so callers don't need to
|
|
30
|
-
* import @pulumi/azure-native). Each rule fires on a path-equals match
|
|
31
|
-
* + executes a 301/302/etc. response.
|
|
32
|
-
*/
|
|
33
|
-
export interface FrontDoorRule {
|
|
34
|
-
readonly name: string;
|
|
35
|
-
readonly order: number;
|
|
36
|
-
readonly conditions: ReadonlyArray<{
|
|
37
|
-
readonly name: "RequestUri";
|
|
38
|
-
readonly parameters: {
|
|
39
|
-
readonly operator: "Equal";
|
|
40
|
-
readonly matchValues: ReadonlyArray<string>;
|
|
41
|
-
};
|
|
42
|
-
}>;
|
|
43
|
-
readonly actions: ReadonlyArray<{
|
|
44
|
-
readonly name: "UrlRedirect";
|
|
45
|
-
readonly parameters: {
|
|
46
|
-
readonly redirectType: "Moved" | "Found" | "TemporaryRedirect" | "PermanentRedirect";
|
|
47
|
-
readonly destinationPath: string;
|
|
48
|
-
};
|
|
49
|
-
}>;
|
|
50
|
-
}
|
|
51
|
-
export declare function emitRedirectsAzureFrontDoor(rows: ReadonlyArray<RedirectRow>): ReadonlyArray<FrontDoorRule>;
|
|
52
|
-
//# sourceMappingURL=redirects-emit.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"redirects-emit.d.ts","sourceRoot":"","sources":["../src/redirects-emit.ts"],"names":[],"mappings":"AAkBA,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;AAiCD;;;;;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"}
|
package/dist/redirects-emit.js
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
-
/**
|
|
3
|
-
* P15 — per-provider redirect file generators.
|
|
4
|
-
*
|
|
5
|
-
* P14's self-hosted Caddy consumes a `_redirects.caddy` file. Cloud
|
|
6
|
-
* providers want different formats: Cloudflare Pages reads `_redirects`
|
|
7
|
-
* (also a fine fit for any platform that consumes that format),
|
|
8
|
-
* CloudFront wants a JSON config a Lambda@Edge function reads at
|
|
9
|
-
* startup, and Azure Front Door wants a rules-engine RuleEngineRule[].
|
|
10
|
-
*
|
|
11
|
-
* The deploy step calls the right emitter based on `process.env.CAELO_PROVIDER`
|
|
12
|
-
* and uploads the result to the right place. Each emitter is pure +
|
|
13
|
-
* idempotent — same input → same byte output.
|
|
14
|
-
*/
|
|
15
|
-
import { redirectTargetIssue } from "@caelo-cms/shared";
|
|
16
|
-
/**
|
|
17
|
-
* Reject whitespace + control chars in either path before any emitter
|
|
18
|
-
* touches them. The Cloudflare `_redirects` format is space-delimited;
|
|
19
|
-
* a `fromPath` containing a space ambiguates the line (CF parses
|
|
20
|
-
* `/old foo /new 301` as from=`/old`, to=`foo`, status=`/new`, then
|
|
21
|
-
* fails on `301`). Caelo's redirects table normally rejects these
|
|
22
|
-
* upstream but the emitter shouldn't trust that — pure functions
|
|
23
|
-
* defend their own contract.
|
|
24
|
-
*/
|
|
25
|
-
function assertPathClean(label, path) {
|
|
26
|
-
// \p{Cc} matches the Unicode control category (U+0000–U+001F + U+007F–U+009F);
|
|
27
|
-
// the linter rejects literal \x00-\x1f ranges in regex, but the intent — defending
|
|
28
|
-
// emitter contracts against control chars — is the explicit reason for this check.
|
|
29
|
-
if (/[\s\p{Cc}]/u.test(path)) {
|
|
30
|
-
throw new Error(`redirects-emit: ${label}=${JSON.stringify(path)} contains whitespace or control chars; reject upstream`);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
function assertRowsClean(rows) {
|
|
34
|
-
for (const r of rows) {
|
|
35
|
-
assertPathClean("fromPath", r.fromPath);
|
|
36
|
-
assertPathClean("toPath", r.toPath);
|
|
37
|
-
// #657 — a `//host` or `/https://…` target would ship as an open
|
|
38
|
-
// redirect or a broken 301 in every CDN format below.
|
|
39
|
-
const targetIssue = redirectTargetIssue(r.toPath);
|
|
40
|
-
if (targetIssue)
|
|
41
|
-
throw new Error(`redirects-emit: ${targetIssue}`);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Cloudflare Pages `_redirects` format (also consumed by Netlify, Vercel,
|
|
46
|
-
* Render, etc.). One redirect per line: `<from> <to> <status>`. Wildcards
|
|
47
|
-
* use `:splat`; we only support exact-path redirects in v1 (matches what
|
|
48
|
-
* Caelo's redirects table allows).
|
|
49
|
-
*/
|
|
50
|
-
export function emitRedirectsCloudflare(rows) {
|
|
51
|
-
assertRowsClean(rows);
|
|
52
|
-
const header = "# Generated by @caelo-cms/provisioning — do not edit by hand.\n";
|
|
53
|
-
const lines = rows.map((r) => `${r.fromPath} ${r.toPath} ${r.statusCode}`);
|
|
54
|
-
return header + lines.join("\n") + (rows.length > 0 ? "\n" : "");
|
|
55
|
-
}
|
|
56
|
-
export function emitRedirectsCloudFront(rows) {
|
|
57
|
-
assertRowsClean(rows);
|
|
58
|
-
const jsonConfig = JSON.stringify({ redirects: rows.map((r) => ({ from: r.fromPath, to: r.toPath, status: r.statusCode })) }, null, 2);
|
|
59
|
-
// Tiny Lambda@Edge handler. v1 is exact-path match (matches Caelo's
|
|
60
|
-
// redirects table). Wildcard support lands when the table grows it.
|
|
61
|
-
const lambdaSource = `// SPDX-License-Identifier: MPL-2.0
|
|
62
|
-
// Generated by @caelo-cms/provisioning. Bundled with redirects.json.
|
|
63
|
-
const REDIRECTS = require("./redirects.json").redirects;
|
|
64
|
-
const TABLE = new Map(REDIRECTS.map((r) => [r.from, r]));
|
|
65
|
-
exports.handler = (event, _ctx, callback) => {
|
|
66
|
-
const req = event.Records[0].cf.request;
|
|
67
|
-
const m = TABLE.get(req.uri);
|
|
68
|
-
if (!m) return callback(null, req);
|
|
69
|
-
callback(null, {
|
|
70
|
-
status: String(m.status),
|
|
71
|
-
statusDescription: "Redirect",
|
|
72
|
-
headers: { location: [{ key: "Location", value: m.to }] },
|
|
73
|
-
});
|
|
74
|
-
};`;
|
|
75
|
-
return { jsonConfig, lambdaSource };
|
|
76
|
-
}
|
|
77
|
-
export function emitRedirectsAzureFrontDoor(rows) {
|
|
78
|
-
assertRowsClean(rows);
|
|
79
|
-
return rows.map((r, i) => ({
|
|
80
|
-
name: `redirect-${i + 1}`,
|
|
81
|
-
order: i + 1,
|
|
82
|
-
conditions: [
|
|
83
|
-
{
|
|
84
|
-
name: "RequestUri",
|
|
85
|
-
parameters: { operator: "Equal", matchValues: [r.fromPath] },
|
|
86
|
-
},
|
|
87
|
-
],
|
|
88
|
-
actions: [
|
|
89
|
-
{
|
|
90
|
-
name: "UrlRedirect",
|
|
91
|
-
parameters: {
|
|
92
|
-
redirectType: statusToFrontDoor(r.statusCode),
|
|
93
|
-
destinationPath: r.toPath,
|
|
94
|
-
},
|
|
95
|
-
},
|
|
96
|
-
],
|
|
97
|
-
}));
|
|
98
|
-
}
|
|
99
|
-
function statusToFrontDoor(status) {
|
|
100
|
-
switch (status) {
|
|
101
|
-
case 301:
|
|
102
|
-
return "Moved";
|
|
103
|
-
case 302:
|
|
104
|
-
return "Found";
|
|
105
|
-
case 307:
|
|
106
|
-
return "TemporaryRedirect";
|
|
107
|
-
case 308:
|
|
108
|
-
return "PermanentRedirect";
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
//# sourceMappingURL=redirects-emit.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"redirects-emit.js","sourceRoot":"","sources":["../src/redirects-emit.ts"],"names":[],"mappings":"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"}
|
package/src/redirects-emit.ts
DELETED
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* P15 — per-provider redirect file generators.
|
|
5
|
-
*
|
|
6
|
-
* P14's self-hosted Caddy consumes a `_redirects.caddy` file. Cloud
|
|
7
|
-
* providers want different formats: Cloudflare Pages reads `_redirects`
|
|
8
|
-
* (also a fine fit for any platform that consumes that format),
|
|
9
|
-
* CloudFront wants a JSON config a Lambda@Edge function reads at
|
|
10
|
-
* startup, and Azure Front Door wants a rules-engine RuleEngineRule[].
|
|
11
|
-
*
|
|
12
|
-
* The deploy step calls the right emitter based on `process.env.CAELO_PROVIDER`
|
|
13
|
-
* and uploads the result to the right place. Each emitter is pure +
|
|
14
|
-
* idempotent — same input → same byte output.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { redirectTargetIssue } from "@caelo-cms/shared";
|
|
18
|
-
|
|
19
|
-
export type RedirectStatusCode = 301 | 302 | 307 | 308;
|
|
20
|
-
|
|
21
|
-
export interface RedirectRow {
|
|
22
|
-
readonly fromPath: string;
|
|
23
|
-
readonly toPath: string;
|
|
24
|
-
readonly statusCode: RedirectStatusCode;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Reject whitespace + control chars in either path before any emitter
|
|
29
|
-
* touches them. The Cloudflare `_redirects` format is space-delimited;
|
|
30
|
-
* a `fromPath` containing a space ambiguates the line (CF parses
|
|
31
|
-
* `/old foo /new 301` as from=`/old`, to=`foo`, status=`/new`, then
|
|
32
|
-
* fails on `301`). Caelo's redirects table normally rejects these
|
|
33
|
-
* upstream but the emitter shouldn't trust that — pure functions
|
|
34
|
-
* defend their own contract.
|
|
35
|
-
*/
|
|
36
|
-
function assertPathClean(label: "fromPath" | "toPath", path: string): void {
|
|
37
|
-
// \p{Cc} matches the Unicode control category (U+0000–U+001F + U+007F–U+009F);
|
|
38
|
-
// the linter rejects literal \x00-\x1f ranges in regex, but the intent — defending
|
|
39
|
-
// emitter contracts against control chars — is the explicit reason for this check.
|
|
40
|
-
if (/[\s\p{Cc}]/u.test(path)) {
|
|
41
|
-
throw new Error(
|
|
42
|
-
`redirects-emit: ${label}=${JSON.stringify(path)} contains whitespace or control chars; reject upstream`,
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function assertRowsClean(rows: ReadonlyArray<RedirectRow>): void {
|
|
48
|
-
for (const r of rows) {
|
|
49
|
-
assertPathClean("fromPath", r.fromPath);
|
|
50
|
-
assertPathClean("toPath", r.toPath);
|
|
51
|
-
// #657 — a `//host` or `/https://…` target would ship as an open
|
|
52
|
-
// redirect or a broken 301 in every CDN format below.
|
|
53
|
-
const targetIssue = redirectTargetIssue(r.toPath);
|
|
54
|
-
if (targetIssue) throw new Error(`redirects-emit: ${targetIssue}`);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Cloudflare Pages `_redirects` format (also consumed by Netlify, Vercel,
|
|
60
|
-
* Render, etc.). One redirect per line: `<from> <to> <status>`. Wildcards
|
|
61
|
-
* use `:splat`; we only support exact-path redirects in v1 (matches what
|
|
62
|
-
* Caelo's redirects table allows).
|
|
63
|
-
*/
|
|
64
|
-
export function emitRedirectsCloudflare(rows: ReadonlyArray<RedirectRow>): string {
|
|
65
|
-
assertRowsClean(rows);
|
|
66
|
-
const header = "# Generated by @caelo-cms/provisioning — do not edit by hand.\n";
|
|
67
|
-
const lines = rows.map((r) => `${r.fromPath} ${r.toPath} ${r.statusCode}`);
|
|
68
|
-
return header + lines.join("\n") + (rows.length > 0 ? "\n" : "");
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* CloudFront via Lambda@Edge. Returns a JSON config the L@E function
|
|
73
|
-
* reads at startup + a tiny Lambda source that consumes it. The L@E
|
|
74
|
-
* function is deployed to `us-east-1` (CloudFront constraint); the
|
|
75
|
-
* config blob is bundled into the Lambda deployment artifact.
|
|
76
|
-
*
|
|
77
|
-
* Returning both halves lets the AWS stack ship a single asset.
|
|
78
|
-
*/
|
|
79
|
-
export interface CloudFrontRedirectArtifact {
|
|
80
|
-
readonly jsonConfig: string;
|
|
81
|
-
readonly lambdaSource: string;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export function emitRedirectsCloudFront(
|
|
85
|
-
rows: ReadonlyArray<RedirectRow>,
|
|
86
|
-
): CloudFrontRedirectArtifact {
|
|
87
|
-
assertRowsClean(rows);
|
|
88
|
-
const jsonConfig = JSON.stringify(
|
|
89
|
-
{ redirects: rows.map((r) => ({ from: r.fromPath, to: r.toPath, status: r.statusCode })) },
|
|
90
|
-
null,
|
|
91
|
-
2,
|
|
92
|
-
);
|
|
93
|
-
// Tiny Lambda@Edge handler. v1 is exact-path match (matches Caelo's
|
|
94
|
-
// redirects table). Wildcard support lands when the table grows it.
|
|
95
|
-
const lambdaSource = `// SPDX-License-Identifier: MPL-2.0
|
|
96
|
-
// Generated by @caelo-cms/provisioning. Bundled with redirects.json.
|
|
97
|
-
const REDIRECTS = require("./redirects.json").redirects;
|
|
98
|
-
const TABLE = new Map(REDIRECTS.map((r) => [r.from, r]));
|
|
99
|
-
exports.handler = (event, _ctx, callback) => {
|
|
100
|
-
const req = event.Records[0].cf.request;
|
|
101
|
-
const m = TABLE.get(req.uri);
|
|
102
|
-
if (!m) return callback(null, req);
|
|
103
|
-
callback(null, {
|
|
104
|
-
status: String(m.status),
|
|
105
|
-
statusDescription: "Redirect",
|
|
106
|
-
headers: { location: [{ key: "Location", value: m.to }] },
|
|
107
|
-
});
|
|
108
|
-
};`;
|
|
109
|
-
return { jsonConfig, lambdaSource };
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Azure Front Door rules engine. Returns a Pulumi-compatible
|
|
114
|
-
* RuleEngineRule[]-shaped array (untyped here so callers don't need to
|
|
115
|
-
* import @pulumi/azure-native). Each rule fires on a path-equals match
|
|
116
|
-
* + executes a 301/302/etc. response.
|
|
117
|
-
*/
|
|
118
|
-
export interface FrontDoorRule {
|
|
119
|
-
readonly name: string;
|
|
120
|
-
readonly order: number;
|
|
121
|
-
readonly conditions: ReadonlyArray<{
|
|
122
|
-
readonly name: "RequestUri";
|
|
123
|
-
readonly parameters: {
|
|
124
|
-
readonly operator: "Equal";
|
|
125
|
-
readonly matchValues: ReadonlyArray<string>;
|
|
126
|
-
};
|
|
127
|
-
}>;
|
|
128
|
-
readonly actions: ReadonlyArray<{
|
|
129
|
-
readonly name: "UrlRedirect";
|
|
130
|
-
readonly parameters: {
|
|
131
|
-
readonly redirectType: "Moved" | "Found" | "TemporaryRedirect" | "PermanentRedirect";
|
|
132
|
-
readonly destinationPath: string;
|
|
133
|
-
};
|
|
134
|
-
}>;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export function emitRedirectsAzureFrontDoor(
|
|
138
|
-
rows: ReadonlyArray<RedirectRow>,
|
|
139
|
-
): ReadonlyArray<FrontDoorRule> {
|
|
140
|
-
assertRowsClean(rows);
|
|
141
|
-
return rows.map((r, i) => ({
|
|
142
|
-
name: `redirect-${i + 1}`,
|
|
143
|
-
order: i + 1,
|
|
144
|
-
conditions: [
|
|
145
|
-
{
|
|
146
|
-
name: "RequestUri" as const,
|
|
147
|
-
parameters: { operator: "Equal" as const, matchValues: [r.fromPath] },
|
|
148
|
-
},
|
|
149
|
-
],
|
|
150
|
-
actions: [
|
|
151
|
-
{
|
|
152
|
-
name: "UrlRedirect" as const,
|
|
153
|
-
parameters: {
|
|
154
|
-
redirectType: statusToFrontDoor(r.statusCode),
|
|
155
|
-
destinationPath: r.toPath,
|
|
156
|
-
},
|
|
157
|
-
},
|
|
158
|
-
],
|
|
159
|
-
}));
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function statusToFrontDoor(
|
|
163
|
-
status: RedirectStatusCode,
|
|
164
|
-
): FrontDoorRule["actions"][0]["parameters"]["redirectType"] {
|
|
165
|
-
switch (status) {
|
|
166
|
-
case 301:
|
|
167
|
-
return "Moved";
|
|
168
|
-
case 302:
|
|
169
|
-
return "Found";
|
|
170
|
-
case 307:
|
|
171
|
-
return "TemporaryRedirect";
|
|
172
|
-
case 308:
|
|
173
|
-
return "PermanentRedirect";
|
|
174
|
-
}
|
|
175
|
-
}
|