@celsoft/platform 0.1.0 → 0.3.0

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 (44) hide show
  1. package/dist/adapters/cloudflare.d.ts +7 -1
  2. package/dist/adapters/cloudflare.js +39 -0
  3. package/dist/adapters/cloudflare.js.map +1 -1
  4. package/dist/adapters/process.d.ts +2 -0
  5. package/dist/adapters/process.js +35 -2
  6. package/dist/adapters/process.js.map +1 -1
  7. package/dist/adapters/resend.d.ts +14 -0
  8. package/dist/adapters/resend.js +50 -0
  9. package/dist/adapters/resend.js.map +1 -0
  10. package/dist/cli.js +42 -12
  11. package/dist/cli.js.map +1 -1
  12. package/dist/config.d.ts +6 -2
  13. package/dist/config.js +44 -9
  14. package/dist/config.js.map +1 -1
  15. package/dist/fakes.d.ts +47 -1
  16. package/dist/fakes.js +69 -2
  17. package/dist/fakes.js.map +1 -1
  18. package/dist/init.d.ts +6 -3
  19. package/dist/init.js +37 -10
  20. package/dist/init.js.map +1 -1
  21. package/dist/lifecycle.d.ts +4 -0
  22. package/dist/lifecycle.js +94 -3
  23. package/dist/lifecycle.js.map +1 -1
  24. package/dist/ports.d.ts +32 -0
  25. package/dist/runtime/auth.d.ts +15 -0
  26. package/dist/runtime/auth.js +35 -0
  27. package/dist/runtime/auth.js.map +1 -0
  28. package/dist/runtime/email.d.ts +15 -0
  29. package/dist/runtime/email.js +22 -0
  30. package/dist/runtime/email.js.map +1 -0
  31. package/dist/runtime/index.d.ts +11 -1
  32. package/dist/runtime/index.js +30 -1
  33. package/dist/runtime/index.js.map +1 -1
  34. package/dist/secrets.d.ts +3 -1
  35. package/dist/secrets.js +15 -8
  36. package/dist/secrets.js.map +1 -1
  37. package/dist/workflow.d.ts +3 -1
  38. package/dist/workflow.js +9 -5
  39. package/dist/workflow.js.map +1 -1
  40. package/dist/wrangler-config.d.ts +2 -1
  41. package/dist/wrangler-config.js +2 -1
  42. package/dist/wrangler-config.js.map +1 -1
  43. package/package.json +12 -3
  44. package/schema.json +50 -5
@@ -1,16 +1,26 @@
1
1
  import postgres from "postgres";
2
+ import { type Auth } from "./auth.js";
3
+ import { type Email, type EmailBindings } from "./email.js";
4
+ export type { Auth } from "./auth.js";
5
+ export type { Email, EmailMessage } from "./email.js";
2
6
  export type Sql = postgres.Sql;
3
- export interface PlatformBindings {
7
+ export interface PlatformBindings extends EmailBindings {
4
8
  DB?: {
5
9
  connectionString: string;
6
10
  };
11
+ APP_URL?: string;
12
+ BETTER_AUTH_SECRET?: string;
7
13
  }
8
14
  export interface ExecutionContext {
9
15
  waitUntil(promise: Promise<unknown>): void;
10
16
  }
17
+ export type Session = NonNullable<Awaited<ReturnType<Auth["api"]["getSession"]>>>;
11
18
  export interface AppContext<Env> {
12
19
  env: Env;
13
20
  readonly sql: Sql;
21
+ readonly email: Email;
22
+ readonly auth: Auth;
23
+ session(): Promise<Session | null>;
14
24
  waitUntil(promise: Promise<unknown>): void;
15
25
  }
16
26
  export type Handler<Env> = (request: Request, app: AppContext<Env>) => Response | Promise<Response>;
@@ -1,12 +1,17 @@
1
1
  import postgres from "postgres";
2
+ import { AUTH_PATH, authPool, createAuth } from "./auth.js";
3
+ import { createEmail } from "./email.js";
2
4
  const defaultConnect = (connectionString) => postgres(connectionString, { max: 5, fetch_types: false });
3
5
  // `Secrets` names the app's secrets, such as `defineApp<{ SESSION_SECRET: string }>`; the
4
- // platform's own bindings are added to it.
6
+ // platform's own bindings are added to it. With `auth` on, /api/auth/* is handled for the app.
5
7
  export function defineApp(handler, options = {}) {
6
8
  const connect = options.connect ?? defaultConnect;
7
9
  return {
8
10
  async fetch(request, env, ctx) {
9
11
  let sql;
12
+ let email;
13
+ let auth;
14
+ let pool;
10
15
  const app = {
11
16
  env,
12
17
  get sql() {
@@ -14,14 +19,38 @@ export function defineApp(handler, options = {}) {
14
19
  throw new Error("this app has no database; add `database` to platform.jsonc");
15
20
  return (sql ??= connect(env.DB.connectionString));
16
21
  },
22
+ get email() {
23
+ return (email ??= createEmail(env));
24
+ },
25
+ get auth() {
26
+ if (!env.BETTER_AUTH_SECRET)
27
+ throw new Error("this app has no auth; set `auth: true` in platform.jsonc");
28
+ if (!env.DB)
29
+ throw new Error("auth needs a database; add `database` to platform.jsonc");
30
+ return (auth ??= createAuth({
31
+ pool: (pool ??= authPool(env.DB.connectionString)),
32
+ secret: env.BETTER_AUTH_SECRET,
33
+ baseURL: env.APP_URL ?? new URL(request.url).origin,
34
+ email: app.email,
35
+ requireVerification: env.EMAIL_MODE === "send",
36
+ }));
37
+ },
38
+ async session() {
39
+ return env.BETTER_AUTH_SECRET ? app.auth.api.getSession({ headers: request.headers }) : null;
40
+ },
17
41
  waitUntil: (promise) => ctx.waitUntil(promise),
18
42
  };
19
43
  try {
44
+ if (env.BETTER_AUTH_SECRET && new URL(request.url).pathname.startsWith(`${AUTH_PATH}/`)) {
45
+ return await app.auth.handler(request);
46
+ }
20
47
  return await handler(request, app);
21
48
  }
22
49
  finally {
23
50
  if (sql)
24
51
  ctx.waitUntil(sql.end({ timeout: 5 }));
52
+ if (pool)
53
+ ctx.waitUntil(pool.end());
25
54
  }
26
55
  },
27
56
  };
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,UAAU,CAAC;AAwBhC,MAAM,cAAc,GAAG,CAAC,gBAAwB,EAAE,EAAE,CAAC,QAAQ,CAAC,gBAAgB,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC;AAEhH,0FAA0F;AAC1F,2CAA2C;AAC3C,MAAM,UAAU,SAAS,CAAkC,OAA4C,EAAE,OAAO,GAAe,EAAE;IAC/H,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,cAAc,CAAC;IAElD,OAAO;QACL,KAAK,CAAC,KAAK,CAAC,OAAgB,EAAE,GAA+B,EAAE,GAAqB;YAClF,IAAI,GAAoB,CAAC;YACzB,MAAM,GAAG,GAA2C;gBAClD,GAAG;gBACH,IAAI,GAAG;oBACL,IAAI,CAAC,GAAG,CAAC,EAAE;wBAAE,MAAM,IAAI,KAAK,CAAC,4DAA4D,CAAC,CAAC;oBAC3F,OAAO,CAAC,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,gBAAgB,CAAC,CAAC,CAAC;gBACpD,CAAC;gBACD,SAAS,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC;aAC/C,CAAC;YAEF,IAAI,CAAC;gBACH,OAAO,MAAM,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;YACrC,CAAC;oBAAS,CAAC;gBACT,IAAI,GAAG;oBAAE,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YAClD,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"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"}
package/dist/secrets.d.ts CHANGED
@@ -1,3 +1,5 @@
1
1
  export declare const SECRETS_FILE = ".env.secrets";
2
+ export declare const VARS_FILE = ".env";
2
3
  export declare function parseDotenv(text: string): Record<string, string>;
3
- export declare function loadSecrets(startDir: string, env?: NodeJS.ProcessEnv): Promise<Record<string, string>>;
4
+ export declare function loadSecrets(startDir: string, names: string[], env?: NodeJS.ProcessEnv): Promise<Record<string, string>>;
5
+ export declare function loadVars(startDir: string, names: string[], env?: NodeJS.ProcessEnv): Promise<Record<string, string>>;
package/dist/secrets.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import { dirname, join } from "node:path";
3
3
  export const SECRETS_FILE = ".env.secrets";
4
+ export const VARS_FILE = ".env";
4
5
  export function parseDotenv(text) {
5
6
  const values = {};
6
7
  for (const line of text.split(/\r?\n/)) {
@@ -14,17 +15,23 @@ export function parseDotenv(text) {
14
15
  }
15
16
  return values;
16
17
  }
17
- // Locally secrets come from the nearest .env.secrets; in CI the workflow passes the
18
- // Environment's secrets as PLATFORM_SECRETS JSON. Later sources win.
19
- export async function loadSecrets(startDir, env = process.env) {
20
- const fromFile = await findSecretsFile(startDir);
21
- const fromJson = env.PLATFORM_SECRETS ? JSON.parse(env.PLATFORM_SECRETS) : {};
22
- return { ...fromFile, ...fromJson };
18
+ // Locally secrets come from the nearest .env.secrets; in CI the workflow passes each named
19
+ // secret as an environment variable. The environment wins.
20
+ export async function loadSecrets(startDir, names, env = process.env) {
21
+ return { ...(await findEnvFile(startDir, SECRETS_FILE)), ...pick(env, names) };
23
22
  }
24
- async function findSecretsFile(dir) {
23
+ // Non-secret values platform.jsonc references as ${NAME}: the nearest .env locally,
24
+ // GitHub secrets passed by name in CI.
25
+ export async function loadVars(startDir, names, env = process.env) {
26
+ return { ...(await findEnvFile(startDir, VARS_FILE)), ...pick(env, names) };
27
+ }
28
+ function pick(env, names) {
29
+ return Object.fromEntries(names.filter((name) => env[name]).map((name) => [name, env[name]]));
30
+ }
31
+ async function findEnvFile(dir, name) {
25
32
  for (let current = dir;; current = dirname(current)) {
26
33
  try {
27
- return parseDotenv(await readFile(join(current, SECRETS_FILE), "utf8"));
34
+ return parseDotenv(await readFile(join(current, name), "utf8"));
28
35
  }
29
36
  catch {
30
37
  if (dirname(current) === current)
@@ -1 +1 @@
1
- {"version":3,"file":"secrets.js","sourceRoot":"","sources":["../src/secrets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE1C,MAAM,CAAC,MAAM,YAAY,GAAG,cAAc,CAAC;AAE3C,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QACvC,MAAM,KAAK,GAAG,4DAA4D,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtF,IAAI,CAAC,KAAK;YAAE,SAAS;QACrB,MAAM,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,GAAG,KAAK,CAAC;QAC5B,MAAM,KAAK,GAAG,gBAAgB,CAAC,IAAI,CAAC,GAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,GAAI,CAAC;QACvD,IAAI,KAAK;YAAE,MAAM,CAAC,IAAK,CAAC,GAAG,KAAK,CAAC;IACnC,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,oFAAoF;AACpF,qEAAqE;AACrE,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,QAAgB,EAAE,GAAG,GAAsB,OAAO,CAAC,GAAG;IACtF,MAAM,QAAQ,GAAG,MAAM,eAAe,CAAC,QAAQ,CAAC,CAAC;IACjD,MAAM,QAAQ,GAAG,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,gBAAgB,CAA4B,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1G,OAAO,EAAE,GAAG,QAAQ,EAAE,GAAG,QAAQ,EAAE,CAAC;AACtC,CAAC;AAED,KAAK,UAAU,eAAe,CAAC,GAAW;IACxC,KAAK,IAAI,OAAO,GAAG,GAAG,GAAI,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACrD,IAAI,CAAC;YACH,OAAO,WAAW,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QAC1E,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,OAAO,CAAC,OAAO,CAAC,KAAK,OAAO;gBAAE,OAAO,EAAE,CAAC;QAC9C,CAAC;IACH,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"secrets.js","sourceRoot":"","sources":["../src/secrets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE1C,MAAM,CAAC,MAAM,YAAY,GAAG,cAAc,CAAC;AAC3C,MAAM,CAAC,MAAM,SAAS,GAAG,MAAM,CAAC;AAEhC,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QACvC,MAAM,KAAK,GAAG,4DAA4D,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtF,IAAI,CAAC,KAAK;YAAE,SAAS;QACrB,MAAM,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,GAAG,KAAK,CAAC;QAC5B,MAAM,KAAK,GAAG,gBAAgB,CAAC,IAAI,CAAC,GAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,GAAI,CAAC;QACvD,IAAI,KAAK;YAAE,MAAM,CAAC,IAAK,CAAC,GAAG,KAAK,CAAC;IACnC,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,2FAA2F;AAC3F,2DAA2D;AAC3D,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,QAAgB,EAAE,KAAe,EAAE,GAAG,GAAsB,OAAO,CAAC,GAAG;IACvG,OAAO,EAAE,GAAG,CAAC,MAAM,WAAW,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,CAAC;AACjF,CAAC;AAED,oFAAoF;AACpF,uCAAuC;AACvC,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,QAAgB,EAAE,KAAe,EAAE,GAAG,GAAsB,OAAO,CAAC,GAAG;IACpG,OAAO,EAAE,GAAG,CAAC,MAAM,WAAW,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,CAAC;AAC9E,CAAC;AAED,SAAS,IAAI,CAAC,GAAsB,EAAE,KAAe;IACnD,OAAO,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,CAAE,CAAC,CAAC,CAAC,CAAC;AACjG,CAAC;AAED,KAAK,UAAU,WAAW,CAAC,GAAW,EAAE,IAAY;IAClD,KAAK,IAAI,OAAO,GAAG,GAAG,GAAI,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACrD,IAAI,CAAC;YACH,OAAO,WAAW,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QAClE,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,OAAO,CAAC,OAAO,CAAC,KAAK,OAAO;gBAAE,OAAO,EAAE,CAAC;QAC9C,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -3,5 +3,7 @@ export type PackageManager = "pnpm" | "npm";
3
3
  export interface WorkflowInput {
4
4
  appDir: string;
5
5
  packageManager: PackageManager;
6
+ secrets: string[];
7
+ variables: string[];
6
8
  }
7
- export declare function renderWorkflow({ appDir, packageManager }: WorkflowInput): string;
9
+ export declare function renderWorkflow({ appDir, packageManager, secrets, variables }: WorkflowInput): string;
package/dist/workflow.js CHANGED
@@ -1,10 +1,17 @@
1
1
  export const WORKFLOW_PATH = ".github/workflows/platform.yml";
2
2
  // Written by `platform init` and compared by `platform ci`, so it must stay deterministic.
3
- export function renderWorkflow({ appDir, packageManager }) {
3
+ // Each secret and variable is passed by name, so the step only ever sees what the app uses.
4
+ export function renderWorkflow({ appDir, packageManager, secrets, variables }) {
4
5
  const install = packageManager === "pnpm" ? "pnpm install --frozen-lockfile" : "npm ci";
5
6
  const exec = packageManager === "pnpm" ? "pnpm exec platform ci" : "npx --no-install platform ci";
6
7
  const setupPnpm = packageManager === "pnpm" ? "\n - uses: pnpm/action-setup@v4" : "";
7
8
  const workingDirectory = appDir === "." ? "" : `\n working-directory: ${appDir}`;
9
+ const env = [
10
+ ...[...new Set(secrets)].sort().map((name) => `${name}: \${{ secrets.${name} }}`),
11
+ // Config values are stored as secrets too: GitHub prints a step's variables in its public log, but hides secrets.
12
+ ...[...new Set(variables)].sort().map((name) => `${name}: \${{ secrets.${name} }}`),
13
+ "GITHUB_TOKEN: ${{ github.token }}",
14
+ ];
8
15
  return `# Written by \`platform init\`; rerun it to update this file.
9
16
  name: platform
10
17
 
@@ -36,10 +43,7 @@ jobs:
36
43
  - run: ${install}
37
44
  - run: ${exec}${workingDirectory}
38
45
  env:
39
- CLOUDFLARE_API_TOKEN: \${{ secrets.CLOUDFLARE_API_TOKEN }}
40
- NEON_API_KEY: \${{ secrets.NEON_API_KEY }}
41
- PLATFORM_SECRETS: \${{ toJSON(secrets) }}
42
- GITHUB_TOKEN: \${{ github.token }}
46
+ ${env.map((line) => ` ${line}`).join("\n")}
43
47
  `;
44
48
  }
45
49
  //# sourceMappingURL=workflow.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"workflow.js","sourceRoot":"","sources":["../src/workflow.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,aAAa,GAAG,gCAAgC,CAAC;AAS9D,2FAA2F;AAC3F,MAAM,UAAU,cAAc,CAAC,EAAE,MAAM,EAAE,cAAc,EAAiB;IACtE,MAAM,OAAO,GAAG,cAAc,KAAK,MAAM,CAAC,CAAC,CAAC,gCAAgC,CAAC,CAAC,CAAC,QAAQ,CAAC;IACxF,MAAM,IAAI,GAAG,cAAc,KAAK,MAAM,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,8BAA8B,CAAC;IAClG,MAAM,SAAS,GAAG,cAAc,KAAK,MAAM,CAAC,CAAC,CAAC,sCAAsC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1F,MAAM,gBAAgB,GAAG,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,gCAAgC,MAAM,EAAE,CAAC;IAExF,OAAO;;;;;;;;;;;;;;;;;;;;;;;mCAuB0B,SAAS;;;;mBAIzB,cAAc;eAClB,OAAO;eACP,IAAI,GAAG,gBAAgB;;;;;;CAMrC,CAAC;AACF,CAAC"}
1
+ {"version":3,"file":"workflow.js","sourceRoot":"","sources":["../src/workflow.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,aAAa,GAAG,gCAAgC,CAAC;AAW9D,2FAA2F;AAC3F,4FAA4F;AAC5F,MAAM,UAAU,cAAc,CAAC,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,SAAS,EAAiB;IAC1F,MAAM,OAAO,GAAG,cAAc,KAAK,MAAM,CAAC,CAAC,CAAC,gCAAgC,CAAC,CAAC,CAAC,QAAQ,CAAC;IACxF,MAAM,IAAI,GAAG,cAAc,KAAK,MAAM,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,8BAA8B,CAAC;IAClG,MAAM,SAAS,GAAG,cAAc,KAAK,MAAM,CAAC,CAAC,CAAC,sCAAsC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1F,MAAM,gBAAgB,GAAG,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,gCAAgC,MAAM,EAAE,CAAC;IACxF,MAAM,GAAG,GAAG;QACV,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,IAAI,kBAAkB,IAAI,KAAK,CAAC;QACjF,kHAAkH;QAClH,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,IAAI,kBAAkB,IAAI,KAAK,CAAC;QACnF,mCAAmC;KACpC,CAAC;IAEF,OAAO;;;;;;;;;;;;;;;;;;;;;;;mCAuB0B,SAAS;;;;mBAIzB,cAAc;eAClB,OAAO;eACP,IAAI,GAAG,gBAAgB;;EAEpC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;CAClD,CAAC;AACF,CAAC"}
@@ -5,5 +5,6 @@ export interface WranglerInput {
5
5
  hostname: string;
6
6
  accountId: string;
7
7
  hyperdriveId: string | undefined;
8
+ vars: Record<string, string>;
8
9
  }
9
- export declare function wranglerConfig({ config, worker, hostname, accountId, hyperdriveId }: WranglerInput): object;
10
+ export declare function wranglerConfig({ config, worker, hostname, accountId, hyperdriveId, vars }: WranglerInput): object;
@@ -1,4 +1,4 @@
1
- export function wranglerConfig({ config, worker, hostname, accountId, hyperdriveId }) {
1
+ export function wranglerConfig({ config, worker, hostname, accountId, hyperdriveId, vars }) {
2
2
  return {
3
3
  name: worker,
4
4
  account_id: accountId,
@@ -8,6 +8,7 @@ export function wranglerConfig({ config, worker, hostname, accountId, hyperdrive
8
8
  workers_dev: false,
9
9
  routes: [{ pattern: hostname, custom_domain: true }],
10
10
  observability: { enabled: true },
11
+ vars,
11
12
  ...(config.frontend && {
12
13
  assets: { directory: config.frontend, not_found_handling: "single-page-application", run_worker_first: ["/api/*"] },
13
14
  }),
@@ -1 +1 @@
1
- {"version":3,"file":"wrangler-config.js","sourceRoot":"","sources":["../src/wrangler-config.ts"],"names":[],"mappings":"AAUA,MAAM,UAAU,cAAc,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,YAAY,EAAiB;IACjG,OAAO;QACL,IAAI,EAAE,MAAM;QACZ,UAAU,EAAE,SAAS;QACrB,IAAI,EAAE,MAAM,CAAC,MAAM;QACnB,kBAAkB,EAAE,MAAM,CAAC,iBAAiB;QAC5C,mBAAmB,EAAE,CAAC,eAAe,CAAC;QACtC,WAAW,EAAE,KAAK;QAClB,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;QACpD,aAAa,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE;QAChC,GAAG,CAAC,MAAM,CAAC,QAAQ,IAAI;YACrB,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,QAAQ,EAAE,kBAAkB,EAAE,yBAAyB,EAAE,gBAAgB,EAAE,CAAC,QAAQ,CAAC,EAAE;SACpH,CAAC;QACF,GAAG,CAAC,YAAY,IAAI,EAAE,UAAU,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC;KAC3E,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"wrangler-config.js","sourceRoot":"","sources":["../src/wrangler-config.ts"],"names":[],"mappings":"AAWA,MAAM,UAAU,cAAc,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,YAAY,EAAE,IAAI,EAAiB;IACvG,OAAO;QACL,IAAI,EAAE,MAAM;QACZ,UAAU,EAAE,SAAS;QACrB,IAAI,EAAE,MAAM,CAAC,MAAM;QACnB,kBAAkB,EAAE,MAAM,CAAC,iBAAiB;QAC5C,mBAAmB,EAAE,CAAC,eAAe,CAAC;QACtC,WAAW,EAAE,KAAK;QAClB,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;QACpD,aAAa,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE;QAChC,IAAI;QACJ,GAAG,CAAC,MAAM,CAAC,QAAQ,IAAI;YACrB,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,QAAQ,EAAE,kBAAkB,EAAE,yBAAyB,EAAE,gBAAgB,EAAE,CAAC,QAAQ,CAAC,EAAE;SACpH,CAAC;QACF,GAAG,CAAC,YAAY,IAAI,EAAE,UAAU,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC;KAC3E,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,11 +1,17 @@
1
1
  {
2
2
  "name": "@celsoft/platform",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Deploy apps to Cloudflare Workers with Neon Postgres, prod on push and a preview per pull request",
5
5
  "license": "MIT",
6
- "repository": { "type": "git", "url": "git+https://github.com/celsoft-com/platform.git", "directory": "packages/platform" },
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/celsoft-com/platform.git",
9
+ "directory": "packages/platform"
10
+ },
7
11
  "homepage": "https://github.com/celsoft-com/platform#readme",
8
- "publishConfig": { "access": "public" },
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
9
15
  "type": "module",
10
16
  "bin": {
11
17
  "platform": "./dist/cli.js"
@@ -22,13 +28,16 @@
22
28
  },
23
29
  "dependencies": {
24
30
  "@neondatabase/api-client": "^2.7.3",
31
+ "better-auth": "^1.7.12",
25
32
  "cloudflare": "^7.3.0",
26
33
  "jsonc-parser": "^3.3.1",
34
+ "pg": "^8.23.1",
27
35
  "postgres": "^3.4.9",
28
36
  "wrangler": "^4.152.0",
29
37
  "zod": "^4.6.7"
30
38
  },
31
39
  "devDependencies": {
40
+ "@types/pg": "^8.23.1",
32
41
  "typescript": "7.0.2"
33
42
  }
34
43
  }
package/schema.json CHANGED
@@ -11,11 +11,25 @@
11
11
  },
12
12
  "name": {
13
13
  "type": "string",
14
- "pattern": "^[a-z][a-z0-9-]{0,49}$"
14
+ "anyOf": [
15
+ {
16
+ "pattern": "^[a-z][a-z0-9-]{0,49}$"
17
+ },
18
+ {
19
+ "pattern": "^\\$\\{[A-Z][A-Z0-9_]*\\}$"
20
+ }
21
+ ]
15
22
  },
16
23
  "hostname": {
17
24
  "type": "string",
18
- "pattern": "^(?=.{1,253}$)([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z]{2,63}$"
25
+ "anyOf": [
26
+ {
27
+ "pattern": "^(?=.{1,253}$)([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z]{2,63}$"
28
+ },
29
+ {
30
+ "pattern": "^\\$\\{[A-Z][A-Z0-9_]*\\}$"
31
+ }
32
+ ]
19
33
  },
20
34
  "worker": {
21
35
  "type": "string",
@@ -24,7 +38,14 @@
24
38
  },
25
39
  "compatibilityDate": {
26
40
  "type": "string",
27
- "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
41
+ "anyOf": [
42
+ {
43
+ "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
44
+ },
45
+ {
46
+ "pattern": "^\\$\\{[A-Z][A-Z0-9_]*\\}$"
47
+ }
48
+ ]
28
49
  },
29
50
  "frontend": {
30
51
  "description": "Directory holding the built static files",
@@ -36,7 +57,14 @@
36
57
  "properties": {
37
58
  "neonProject": {
38
59
  "type": "string",
39
- "pattern": "^[a-z]+-[a-z]+-\\d+$"
60
+ "anyOf": [
61
+ {
62
+ "pattern": "^[a-z]+-[a-z]+-\\d+$"
63
+ },
64
+ {
65
+ "pattern": "^\\$\\{[A-Z][A-Z0-9_]*\\}$"
66
+ }
67
+ ]
40
68
  },
41
69
  "migrate": {
42
70
  "type": "string",
@@ -50,12 +78,29 @@
50
78
  ],
51
79
  "additionalProperties": false
52
80
  },
81
+ "email": {
82
+ "default": false,
83
+ "description": "Send email through Resend from noreply@ the app's own hostname; the app only sends",
84
+ "type": "boolean"
85
+ },
86
+ "auth": {
87
+ "default": false,
88
+ "description": "Sign-up and login with email and password; needs database and email",
89
+ "type": "boolean"
90
+ },
53
91
  "secrets": {
54
92
  "default": [],
55
93
  "type": "array",
56
94
  "items": {
57
95
  "type": "string",
58
- "pattern": "^[A-Z][A-Z0-9_]*$"
96
+ "anyOf": [
97
+ {
98
+ "pattern": "^[A-Z][A-Z0-9_]*$"
99
+ },
100
+ {
101
+ "pattern": "^\\$\\{[A-Z][A-Z0-9_]*\\}$"
102
+ }
103
+ ]
59
104
  }
60
105
  }
61
106
  },