@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.
Files changed (96) hide show
  1. package/dist/adapter.d.ts +95 -0
  2. package/dist/adapter.d.ts.map +1 -0
  3. package/dist/adapter.js +3 -0
  4. package/dist/adapter.js.map +1 -0
  5. package/dist/bootstrap-token.d.ts +11 -0
  6. package/dist/bootstrap-token.d.ts.map +1 -0
  7. package/dist/bootstrap-token.js +9 -0
  8. package/dist/bootstrap-token.js.map +1 -0
  9. package/dist/caddy.d.ts +34 -0
  10. package/dist/caddy.d.ts.map +1 -0
  11. package/dist/caddy.js +53 -0
  12. package/dist/caddy.js.map +1 -0
  13. package/{src/cdn-copy.ts → dist/cdn-copy.d.ts} +11 -42
  14. package/dist/cdn-copy.d.ts.map +1 -0
  15. package/dist/cdn-copy.js +48 -0
  16. package/dist/cdn-copy.js.map +1 -0
  17. package/dist/cli.d.ts +3 -0
  18. package/dist/cli.d.ts.map +1 -0
  19. package/dist/cli.js +670 -0
  20. package/dist/cli.js.map +1 -0
  21. package/dist/compose.d.ts +27 -0
  22. package/dist/compose.d.ts.map +1 -0
  23. package/{src/compose.ts → dist/compose.js} +15 -35
  24. package/dist/compose.js.map +1 -0
  25. package/dist/dns/cloudflare.d.ts +9 -0
  26. package/dist/dns/cloudflare.d.ts.map +1 -0
  27. package/dist/dns/cloudflare.js +160 -0
  28. package/dist/dns/cloudflare.js.map +1 -0
  29. package/dist/dns/index.d.ts +12 -0
  30. package/dist/dns/index.d.ts.map +1 -0
  31. package/dist/dns/index.js +42 -0
  32. package/dist/dns/index.js.map +1 -0
  33. package/dist/dns/manual.d.ts +5 -0
  34. package/dist/dns/manual.d.ts.map +1 -0
  35. package/dist/dns/manual.js +96 -0
  36. package/dist/dns/manual.js.map +1 -0
  37. package/dist/dns/types.d.ts +23 -0
  38. package/dist/dns/types.d.ts.map +1 -0
  39. package/dist/dns/types.js +3 -0
  40. package/dist/dns/types.js.map +1 -0
  41. package/dist/gcloud.d.ts +42 -0
  42. package/dist/gcloud.d.ts.map +1 -0
  43. package/dist/gcloud.js +187 -0
  44. package/dist/gcloud.js.map +1 -0
  45. package/dist/index.d.ts +22 -0
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +7 -0
  48. package/dist/index.js.map +1 -0
  49. package/dist/install-state.d.ts +54 -0
  50. package/dist/install-state.d.ts.map +1 -0
  51. package/dist/install-state.js +118 -0
  52. package/dist/install-state.js.map +1 -0
  53. package/dist/lifecycle.d.ts +19 -0
  54. package/dist/lifecycle.d.ts.map +1 -0
  55. package/dist/lifecycle.js +589 -0
  56. package/dist/lifecycle.js.map +1 -0
  57. package/dist/migration-runner.d.ts +15 -0
  58. package/dist/migration-runner.d.ts.map +1 -0
  59. package/dist/migration-runner.js +174 -0
  60. package/dist/migration-runner.js.map +1 -0
  61. package/dist/redirects-emit.d.ts +65 -0
  62. package/dist/redirects-emit.d.ts.map +1 -0
  63. package/dist/redirects-emit.js +92 -0
  64. package/dist/redirects-emit.js.map +1 -0
  65. package/dist/wizard.d.ts +35 -0
  66. package/dist/wizard.d.ts.map +1 -0
  67. package/dist/wizard.js +160 -0
  68. package/dist/wizard.js.map +1 -0
  69. package/dist/wizards/gcp-cost.d.ts +27 -0
  70. package/dist/wizards/gcp-cost.d.ts.map +1 -0
  71. package/dist/wizards/gcp-cost.js +77 -0
  72. package/dist/wizards/gcp-cost.js.map +1 -0
  73. package/dist/wizards/gcp-pulumi.d.ts +37 -0
  74. package/dist/wizards/gcp-pulumi.d.ts.map +1 -0
  75. package/dist/wizards/gcp-pulumi.js +100 -0
  76. package/dist/wizards/gcp-pulumi.js.map +1 -0
  77. package/dist/wizards/gcp.d.ts +9 -0
  78. package/dist/wizards/gcp.d.ts.map +1 -0
  79. package/dist/wizards/gcp.js +895 -0
  80. package/dist/wizards/gcp.js.map +1 -0
  81. package/package.json +34 -7
  82. package/stacks/aws/index.ts +6 -7
  83. package/stacks/azure/index.ts +11 -11
  84. package/stacks/gcp/Pulumi.production.yaml +16 -0
  85. package/stacks/gcp/Pulumi.yaml +52 -6
  86. package/stacks/gcp/index.ts +569 -188
  87. package/stacks/self-hosted/index.ts +3 -3
  88. package/static/welcome.html +155 -0
  89. package/src/adapter.ts +0 -103
  90. package/src/bootstrap-token.ts +0 -20
  91. package/src/caddy.ts +0 -93
  92. package/src/cli.ts +0 -674
  93. package/src/index.test.ts +0 -246
  94. package/src/index.ts +0 -52
  95. package/src/redirects-emit.ts +0 -166
  96. package/tsconfig.json +0 -16
@@ -23,9 +23,9 @@
23
23
  import { resolve } from "node:path";
24
24
  import { local } from "@pulumi/command";
25
25
  import * as pulumi from "@pulumi/pulumi";
26
- import { generateBootstrapToken } from "../../src/bootstrap-token.js";
27
- import { generateCaddyfile } from "../../src/caddy.js";
28
- import { generateDockerCompose } from "../../src/compose.js";
26
+ import { generateBootstrapToken } from "../../dist/bootstrap-token.js";
27
+ import { generateCaddyfile } from "../../dist/caddy.js";
28
+ import { generateDockerCompose } from "../../dist/compose.js";
29
29
 
30
30
  const cfg = new pulumi.Config();
31
31
  const domain = cfg.require("domain");
@@ -0,0 +1,155 @@
1
+ <!doctype html>
2
+ <!-- SPDX-License-Identifier: MPL-2.0 -->
3
+ <!--
4
+ Caelo CMS — fresh-install placeholder.
5
+ Uploaded by the provisioning wizard the first time it brings up the
6
+ static bucket. Replaced the moment the operator publishes their
7
+ first deploy via the admin app's "Publish" flow.
8
+ -->
9
+ <html lang="en">
10
+ <head>
11
+ <meta charset="utf-8">
12
+ <meta name="viewport" content="width=device-width,initial-scale=1">
13
+ <meta name="robots" content="noindex">
14
+ <title>Caelo CMS — your install is ready</title>
15
+ <style>
16
+ *,*::before,*::after { box-sizing: border-box; margin: 0; padding: 0; }
17
+ :root {
18
+ --fg: #0c0d12;
19
+ --muted: #4a4e5e;
20
+ --accent: #4f46e5;
21
+ --accent-glow: #818cf8;
22
+ --card-bg: rgba(255,255,255,0.78);
23
+ --card-border: rgba(255,255,255,0.55);
24
+ --shadow: 0 30px 60px -20px rgba(15,17,40,0.18), 0 8px 18px -8px rgba(15,17,40,0.10);
25
+ }
26
+ html, body { height: 100%; }
27
+ body {
28
+ font: 16px/1.55 system-ui, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif;
29
+ color: var(--fg);
30
+ display: grid;
31
+ place-items: center;
32
+ padding: 2rem;
33
+ background: #f6f6fb;
34
+ overflow: hidden;
35
+ position: relative;
36
+ }
37
+ /* Animated mesh-gradient background. Three soft blobs orbiting at
38
+ different speeds; backdrop-filter on the card softens them. */
39
+ .blob {
40
+ position: fixed;
41
+ border-radius: 50%;
42
+ filter: blur(80px);
43
+ opacity: 0.55;
44
+ pointer-events: none;
45
+ animation: drift 22s ease-in-out infinite;
46
+ will-change: transform;
47
+ }
48
+ .blob.b1 { width: 520px; height: 520px; background: #818cf8; top: -120px; left: -160px; animation-delay: -3s; }
49
+ .blob.b2 { width: 460px; height: 460px; background: #f0abfc; bottom: -180px; right: -140px; animation-delay: -8s; }
50
+ .blob.b3 { width: 380px; height: 380px; background: #67e8f9; top: 40%; left: 55%; animation-delay: -14s; animation-duration: 27s; }
51
+ @keyframes drift {
52
+ 0%, 100% { transform: translate(0, 0) scale(1); }
53
+ 33% { transform: translate(40px, -30px) scale(1.07); }
54
+ 66% { transform: translate(-30px, 35px) scale(0.96); }
55
+ }
56
+ @media (prefers-reduced-motion: reduce) {
57
+ .blob { animation: none; }
58
+ }
59
+
60
+ main {
61
+ position: relative;
62
+ z-index: 1;
63
+ max-width: 36rem;
64
+ width: 100%;
65
+ background: var(--card-bg);
66
+ border: 1px solid var(--card-border);
67
+ border-radius: 22px;
68
+ padding: 2.5rem;
69
+ box-shadow: var(--shadow);
70
+ backdrop-filter: blur(14px) saturate(140%);
71
+ -webkit-backdrop-filter: blur(14px) saturate(140%);
72
+ }
73
+
74
+ .brand {
75
+ display: flex;
76
+ align-items: center;
77
+ gap: 0.65rem;
78
+ font-weight: 600;
79
+ letter-spacing: -0.01em;
80
+ color: var(--muted);
81
+ font-size: 0.95rem;
82
+ margin-bottom: 1.5rem;
83
+ }
84
+ .brand .dot {
85
+ width: 10px; height: 10px; border-radius: 50%;
86
+ background: var(--accent);
87
+ box-shadow: 0 0 0 0 var(--accent-glow);
88
+ animation: pulse 1.8s ease-out infinite;
89
+ }
90
+ @keyframes pulse {
91
+ 0% { box-shadow: 0 0 0 0 rgba(79,70,229,0.55); }
92
+ 70% { box-shadow: 0 0 0 12px rgba(79,70,229,0); }
93
+ 100% { box-shadow: 0 0 0 0 rgba(79,70,229,0); }
94
+ }
95
+ @media (prefers-reduced-motion: reduce) {
96
+ .brand .dot { animation: none; }
97
+ }
98
+
99
+ h1 {
100
+ font-size: clamp(1.65rem, 1.2rem + 1.6vw, 2.25rem);
101
+ letter-spacing: -0.02em;
102
+ line-height: 1.15;
103
+ margin-bottom: 0.65rem;
104
+ }
105
+ p { color: var(--muted); margin-bottom: 1rem; }
106
+ p:last-child { margin-bottom: 0; }
107
+
108
+ .cta {
109
+ display: inline-flex;
110
+ align-items: center;
111
+ gap: 0.5rem;
112
+ margin-top: 1.5rem;
113
+ padding: 0.75rem 1.1rem;
114
+ background: var(--fg);
115
+ color: #fff;
116
+ text-decoration: none;
117
+ border-radius: 12px;
118
+ font-weight: 500;
119
+ transition: transform 0.15s ease, box-shadow 0.15s ease;
120
+ }
121
+ .cta:hover { transform: translateY(-1px); box-shadow: 0 12px 24px -8px rgba(15,17,40,0.25); }
122
+ .cta svg { width: 14px; height: 14px; }
123
+
124
+ .hint {
125
+ margin-top: 2rem;
126
+ padding-top: 1.5rem;
127
+ border-top: 1px solid rgba(15,17,40,0.08);
128
+ font-size: 0.875rem;
129
+ color: var(--muted);
130
+ }
131
+ .hint code {
132
+ font: 0.85em ui-monospace, "SF Mono", Menlo, Consolas, monospace;
133
+ background: rgba(15,17,40,0.05);
134
+ padding: 0.15em 0.45em;
135
+ border-radius: 6px;
136
+ }
137
+ .hint a { color: var(--accent); text-decoration: none; }
138
+ .hint a:hover { text-decoration: underline; }
139
+ </style>
140
+ </head>
141
+ <body>
142
+ <div class="blob b1"></div>
143
+ <div class="blob b2"></div>
144
+ <div class="blob b3"></div>
145
+
146
+ <main>
147
+ <div class="brand"><span class="dot"></span>Caelo&nbsp;CMS</div>
148
+ <h1>Your install is up. One more step.</h1>
149
+ <p>This is the placeholder Caelo serves on a fresh deploy. The operator who provisioned this install has a one-time bootstrap link in their terminal output — opening it walks through Owner-account setup. After publish, this page disappears.</p>
150
+ <div class="hint">
151
+ Bootstrap link expires in 24h and rotates per <code>bunx @caelo-cms/provisioning</code> run. After Owner setup, the admin lives at <code>{{ADMIN_URL}}</code> behind <code>Sign in with Google</code> (IAP allowlist).
152
+ </div>
153
+ </main>
154
+ </body>
155
+ </html>
package/src/adapter.ts DELETED
@@ -1,103 +0,0 @@
1
- // SPDX-License-Identifier: MPL-2.0
2
-
3
- /**
4
- * P15 — shared cloud-provider adapter contract.
5
- *
6
- * Every per-provider Pulumi stack at `packages/provisioning/stacks/<provider>/`
7
- * exports a `provision(inputs: CloudAdapterInputs): CloudAdapterOutputs`
8
- * function. The Caelo runtime never knows which provider it's running
9
- * on — it just consumes the connection strings + URLs the adapter
10
- * publishes via `CloudAdapterOutputs`. This keeps the per-provider
11
- * surface small (~6 capabilities, see master plan) and the runtime
12
- * provider-agnostic.
13
- *
14
- * Pulumi types are intentionally NOT imported here so that callers
15
- * outside the Pulumi runtime (e.g. the cms-provision CLI, the admin
16
- * app's DNS-guidance page) can consume the *plain* shape without
17
- * dragging in the @pulumi/pulumi peer dep. Per-stack `index.ts` files
18
- * narrow the output type to `pulumi.Output<T>` at their boundary.
19
- */
20
-
21
- export type Environment = "dev" | "staging" | "production";
22
- export type LocaleStrategy = "subdirectory" | "subdomain" | "domain";
23
-
24
- export interface LocaleConfig {
25
- /** ISO code, e.g. "en", "de", "fr-CA". */
26
- readonly code: string;
27
- /** URL strategy. Mixed strategies in one install are explicitly supported. */
28
- readonly strategy: LocaleStrategy;
29
- /** Required when strategy is "subdomain" or "domain"; ignored for "subdirectory". */
30
- readonly host?: string;
31
- }
32
-
33
- export interface CloudAdapterInputs {
34
- /** Primary domain (e.g. example.com). Admin + production public both bind here. */
35
- readonly domain: string;
36
- /** Operator email used for ACME / cert provisioning + Pulumi notifications. */
37
- readonly ownerEmail: string;
38
- /** Three-env model (CMS_REQUIREMENTS §16.5). Cloud installs always provision all three. */
39
- readonly environments: ReadonlyArray<Environment>;
40
- /** Per-locale routing config — drives per-domain cert + DNS guidance + edge routing. */
41
- readonly locales: ReadonlyArray<LocaleConfig>;
42
- /** Optional pre-existing secret references (e.g. from a CI secrets manager). */
43
- readonly preProvisionedSecrets?: {
44
- readonly anthropicApiKey?: string;
45
- readonly resendApiKey?: string;
46
- };
47
- }
48
-
49
- /**
50
- * DNS records the operator must create at their registrar to make the
51
- * install reachable. Surfaced in the admin's /security/dns page with
52
- * live resolver status badges.
53
- */
54
- export interface DnsRecord {
55
- readonly hostname: string; // e.g. "de.example.com"
56
- readonly type: "A" | "AAAA" | "CNAME" | "TXT";
57
- readonly value: string; // the value the operator's registrar must hold
58
- readonly purpose: string; // human-readable description
59
- }
60
-
61
- /**
62
- * Plain-data adapter outputs. Per-stack code wraps each field in
63
- * `pulumi.Output<…>` at the Pulumi boundary, but the *shape* is shared
64
- * across providers so the cms-provision CLI + the DNS UI can consume
65
- * any provider's outputs uniformly.
66
- */
67
- export interface CloudAdapterOutputs {
68
- /** DSN for cms_admin role (encrypted in Pulumi state). */
69
- readonly adminDatabaseUrl: string;
70
- /** DSN for cms_public role (encrypted in Pulumi state). */
71
- readonly publicDatabaseUrl: string;
72
- /** Provider-native blob URL (s3://, gs://, https://<account>.blob.core.windows.net/<container>). */
73
- readonly mediaStorageUrl: string;
74
- /** Public-facing URL the admin reaches for media reads. */
75
- readonly mediaCdnBaseUrl: string;
76
- /** Bootstrap-token URL — operator opens this once after `pulumi up`. */
77
- readonly bootstrapUrl: string;
78
- /** DNS records consumed by the admin's DNS-guidance page. */
79
- readonly dnsRecordsRequired: ReadonlyArray<DnsRecord>;
80
- /**
81
- * Where edge-A/B assignment logs land — read by the P12A analytics plugin's
82
- * provider-specific log adapter. Provider-native sink URL (BigQuery dataset,
83
- * Athena database, Log Analytics workspace).
84
- */
85
- readonly edgeLogSinkUrl: string;
86
- /** Which provider produced these outputs — drives the analytics plugin's adapter dispatch. */
87
- readonly provider: SupportedProvider;
88
- /** Which environment this output snapshot represents. */
89
- readonly environment: Environment;
90
- }
91
-
92
- export type SupportedProvider = "self-hosted" | "gcp" | "aws" | "azure";
93
-
94
- /**
95
- * Convenience: the shape persisted in `cms_admin.provisioning_outputs.outputs_json`.
96
- * Pulumi runs the adapter, the CLI's `pulumi-output-sync` subcommand reads
97
- * `pulumi stack output --json`, hashes the result, and writes a row keyed on
98
- * (provider, environment) so the admin UI can read without provider creds.
99
- */
100
- export interface ProvisioningOutputsJson {
101
- readonly outputs: CloudAdapterOutputs;
102
- readonly syncedAt: string; // ISO timestamp
103
- }
@@ -1,20 +0,0 @@
1
- // SPDX-License-Identifier: MPL-2.0
2
-
3
- /**
4
- * P14 — owner bootstrap token. cms-provision generates one at first
5
- * `up`; the operator visits /setup?token=<…> to create the first
6
- * Owner. Single-use, 24h TTL.
7
- */
8
-
9
- export interface BootstrapToken {
10
- readonly token: string;
11
- readonly expiresAt: string;
12
- }
13
-
14
- export function generateBootstrapToken(): BootstrapToken {
15
- const bytes = new Uint8Array(32);
16
- crypto.getRandomValues(bytes);
17
- const token = [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
18
- const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString();
19
- return { token, expiresAt };
20
- }
package/src/caddy.ts DELETED
@@ -1,93 +0,0 @@
1
- // SPDX-License-Identifier: MPL-2.0
2
-
3
- /**
4
- * P14 — Caddyfile generator.
5
- *
6
- * Reads the `domains` table + the deploy targets and emits a
7
- * deterministic Caddyfile string. The CLI's `regenerate-caddy`
8
- * sub-command writes this to `/etc/caddy/Caddyfile` and runs
9
- * `caddy reload`.
10
- *
11
- * Per-vhost shape:
12
- * <hostname> {
13
- * # admin → reverse_proxy localhost:5173
14
- * # public → root + try_files (static), with /api/* → gateway
15
- * # locale → same as public, scoped to a per-locale dist dir
16
- * tls <ownerEmail>
17
- * }
18
- *
19
- * Staging vhosts force `X-Robots-Tag: noindex`.
20
- */
21
-
22
- export interface CaddyDomainSpec {
23
- readonly hostname: string;
24
- readonly kind: "admin" | "public" | "locale-public";
25
- readonly localeCode?: string;
26
- readonly env: "production" | "staging";
27
- }
28
-
29
- export interface CaddyfileSpec {
30
- readonly ownerEmail: string;
31
- readonly publicSiteRoot: string; // absolute path on disk
32
- readonly stagingSiteRoot: string;
33
- readonly adminPort: number;
34
- readonly gatewayPort: number;
35
- readonly domains: ReadonlyArray<CaddyDomainSpec>;
36
- }
37
-
38
- export function generateCaddyfile(spec: CaddyfileSpec): string {
39
- const blocks: string[] = [];
40
-
41
- // Global options.
42
- blocks.push(`{
43
- email ${spec.ownerEmail}
44
- }
45
- `);
46
-
47
- for (const d of spec.domains) {
48
- blocks.push(vhost(d, spec));
49
- }
50
-
51
- // Localhost dev fallback when no domains are configured (so a fresh
52
- // `cms-provision` produces a working Caddyfile even pre-DNS).
53
- if (spec.domains.length === 0) {
54
- blocks.push(`# No domains configured yet — cms-provision regenerate-caddy
55
- # will overwrite this file when you add one at /security/domains.
56
- :8081 {
57
- reverse_proxy localhost:${spec.adminPort}
58
- }
59
- `);
60
- }
61
-
62
- return blocks.join("\n");
63
- }
64
-
65
- function vhost(d: CaddyDomainSpec, spec: CaddyfileSpec): string {
66
- const noindex = d.env === "staging" ? `\n header X-Robots-Tag "noindex"` : "";
67
- if (d.kind === "admin") {
68
- return `${d.hostname} {${noindex}
69
- reverse_proxy localhost:${spec.adminPort}
70
- }
71
- `;
72
- }
73
- // public / locale-public — same shape: API routes go to the gateway,
74
- // the rest serves static files. Locale variants serve from a
75
- // per-locale subdirectory.
76
- const root = d.env === "staging" ? spec.stagingSiteRoot : spec.publicSiteRoot;
77
- const localeSubdir = d.kind === "locale-public" && d.localeCode ? `/${d.localeCode}` : "";
78
- return `${d.hostname} {${noindex}
79
- root * ${root}${localeSubdir}
80
- handle /api/* {
81
- reverse_proxy localhost:${spec.gatewayPort}
82
- }
83
- handle /admin/* {
84
- reverse_proxy localhost:${spec.adminPort}
85
- }
86
- handle {
87
- file_server {
88
- try_files {path} {path}/index.html /index.html
89
- }
90
- }
91
- }
92
- `;
93
- }