@suluk/platform 0.4.1 → 0.4.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suluk/platform",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "The platform generator (C051): write one `definePlatform` manifest → it plans the shadcn-registry adds, generates the wired Hono entry, and merges each module's provision fragment into a single provision.config. The manifest compiles to a shadcn-add list + a C047 provision.config; the generator runs the adds + `@suluk/provision`. Turns the Suluk backend registry into a one-command platform. CANDIDATE tooling.",
5
5
  "publishConfig": {
6
6
  "access": "public"
package/src/plan.ts CHANGED
@@ -241,8 +241,9 @@ function buildEnvTemp(env: EnvVar[]): string {
241
241
  "# Provisioning creds (used to create infra + mint scoped tokens; the master is DELETED, never committed):",
242
242
  ...provisioningOf(env).filter((e) => !e.minted).map(line),
243
243
  "",
244
- "# Runtime secrets (encrypted into .env + committed; shipped to the Worker):",
245
- ...runtimeSecretsOf(env).map(line),
244
+ "# Runtime secrets (encrypted into .env + committed; shipped to the Worker). AUTO-GENERATED ones are NOT here — `provision`",
245
+ "# creates them: " + (runtimeSecretsOf(env).filter((e) => e.generated).map((e) => e.name).join(", ") || "(none)") + ".",
246
+ ...runtimeSecretsOf(env).filter((e) => !e.generated).map(line),
246
247
  "",
247
248
  ].join("\n");
248
249
  }
@@ -303,13 +304,16 @@ console.log("✓ scoped tokens ready (encrypted in .env).");
303
304
  */
304
305
  function buildProvisionScript(env: EnvVar[]): string {
305
306
  const ephemeral = ephemeralOf(env).map((e) => e.name);
307
+ const generated = env.filter((e) => e.generated).map((e) => e.name);
306
308
  return `#!/usr/bin/env bun
307
309
  // AUTO-GENERATED by @suluk/platform — stand up the infra + SEAL the secrets (@suluk/env encrypted-commit model). Run once
308
310
  // after filling .env.temp (or with an existing encrypted .env). Idempotent.
309
311
  import { existsSync, rmSync, readFileSync, writeFileSync } from "node:fs";
312
+ import { randomBytes } from "node:crypto";
310
313
  import { loadEnvFile, setVar } from "@suluk/env/node";
311
314
 
312
315
  const EPHEMERAL = ${JSON.stringify(ephemeral)}; // the CF master token(s): used to provision + mint, then DELETED (never committed)
316
+ const GENERATED = ${JSON.stringify(generated)}; // secrets the app creates itself (e.g. BETTER_AUTH_SECRET) — never operator-supplied
313
317
  const sh = async (cmd: string, args: string[]) => { const p = Bun.spawn([cmd, ...args], { stdout: "inherit", stderr: "inherit" }); if ((await p.exited) !== 0) { console.error(\`✗ \${cmd} \${args.join(" ")}\`); process.exit(1); } };
314
318
 
315
319
  // 1. keypair → the central ~/.suluk/settings.json (the private key never stays in the repo).
@@ -327,6 +331,12 @@ if (existsSync(".env.temp")) {
327
331
  await loadEnvFile({ override: true }); // decrypt everything into process.env
328
332
  if (!process.env.CLOUDFLARE_API_TOKEN || !process.env.CLOUDFLARE_ACCOUNT_ID) { console.error("✗ CLOUDFLARE_API_TOKEN / CLOUDFLARE_ACCOUNT_ID missing — put them in .env.temp"); process.exit(1); }
329
333
 
334
+ // 2b. auto-generate any app-created secret (e.g. BETTER_AUTH_SECRET ← 32 random bytes) not already set — the operator never
335
+ // supplies these in .env.temp. Staged plaintext here, encrypted at step 5.
336
+ for (const name of GENERATED) {
337
+ if (!process.env[name]) { await setVar(name, randomBytes(32).toString("base64"), { plain: true }); console.log(\`✓ generated \${name}\`); }
338
+ }
339
+
330
340
  // 3. provision the infra (D1/KV — the C047 provision.config), 4. mint the scoped least-privilege tokens from the master.
331
341
  await sh("bunx", ["suluk-provision", "apply"]);
332
342
  await sh("bun", ["run", "scripts/mint-tokens.ts"]);
package/src/service.ts CHANGED
@@ -40,6 +40,9 @@ export interface EnvVar {
40
40
  provisioning?: boolean;
41
41
  /** a scoped least-privilege token MINTED during provisioning (from the master), then kept ENCRYPTED in `.env`. `surface: "local"`. */
42
42
  minted?: boolean;
43
+ /** a random secret the provisioning flow AUTO-GENERATES (e.g. `BETTER_AUTH_SECRET` ← 32 random bytes) if not already set —
44
+ * so the operator never supplies it in `.env.temp`; it still lands ENCRYPTED in the committed `.env`. */
45
+ generated?: boolean;
43
46
  }
44
47
 
45
48
  /** The old catalog record — now a DERIVED VIEW of a {@link Service} (see {@link toCatalogEntry}); kept so `planPlatform`
@@ -191,7 +194,7 @@ export const authService = defineService({
191
194
  provision: { symbol: "authProvision", from: "./src/provision/auth" },
192
195
  deps: ["better-auth", "@better-auth/api-key", "@better-auth/passkey", "@suluk/better-auth"],
193
196
  env: [
194
- { name: "BETTER_AUTH_SECRET", required: true, secret: true, hint: "session-signing key — `openssl rand -base64 32`" },
197
+ { name: "BETTER_AUTH_SECRET", required: true, secret: true, generated: true, hint: "session-signing key — AUTO-GENERATED by `bun run provision` (32 random bytes); no need to supply it" },
195
198
  { name: "BETTER_AUTH_URL", hint: "your deployed origin, e.g. https://api.example.com" },
196
199
  { name: "GOOGLE_CLIENT_ID", secret: true, hint: "optional — enables Google sign-in" },
197
200
  { name: "GOOGLE_CLIENT_SECRET", secret: true, hint: "optional — pairs with GOOGLE_CLIENT_ID" },