cloudflare-next-intl 0.8.9 → 0.8.11

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/README.md CHANGED
@@ -778,6 +778,15 @@ them and fails naming the first one that is stale.
778
778
  npx cfni-db-codegen --out-dir=src/shared/db/generated --out-dir=../other-app/src/db/generated
779
779
  ```
780
780
 
781
+ If no `--db-url`/`CODEGEN_DATABASE_URL` is set and nothing is reachable at the
782
+ local Supabase default, `cfni-db-codegen` falls back to a throwaway,
783
+ local-only Postgres started via the optional `embedded-postgres` package (a
784
+ prebuilt binary, no Docker required): install it once with
785
+ `npm install --save-dev embedded-postgres`, and no local DB setup is needed —
786
+ the DDL in `--ddl-dir` is loaded into it, introspected, and it's torn down
787
+ after. Passing an explicit `--db-url`/`CODEGEN_DATABASE_URL` skips this
788
+ fallback entirely and fails loudly if that target is unreachable.
789
+
781
790
  ##### Keeping `cfni_exec.sql` in sync (`--rpc-dir`/`--rpc-file-name`/`--tests-dir`/`--tests-file-name`/`--force`/`--skip-exec`)
782
791
 
783
792
  After a successful (non-`--check`) run, `cfni-db-codegen` also copies
@@ -37,26 +37,34 @@ import { join, relative } from 'node:path';
37
37
  import { Client } from 'pg';
38
38
  import resolveCodegenPaths from '../dist/src/db/codegen_paths.js';
39
39
  import { runInstallExecStep } from './install_exec_step.mjs';
40
+ import { startEphemeralPostgres } from './ephemeral_pg.mjs';
41
+ import { orderedSqlFiles } from './ddl_order.mjs';
40
42
 
41
43
  const paths = resolveCodegenPaths(process.argv.slice(2), process.env, process.cwd());
42
44
 
43
- async function assertReachable(url) {
45
+ async function isReachable(url) {
44
46
  const client = new Client({ connectionString: url, connectionTimeoutMillis: paths.timeoutMs });
45
47
  try {
46
48
  await client.connect();
47
49
  await client.end();
48
- } catch (error) {
50
+ return true;
51
+ } catch {
49
52
  await client.end().catch(() => { /* already failed to connect */ });
50
- console.error(`❌ Could not reach Postgres at ${url}\n (${error.message})`);
51
- console.error("\n drizzle-kit pull needs a live Postgres to introspect — any one works, this script has no Docker dependency of its own. Pick one:");
52
- console.error(" - Local Supabase (needs Docker running): ./supabase/scripts/db_start.sh --reset");
53
- console.error(" - A native Postgres you already have: CODEGEN_DATABASE_URL=postgresql://... npm run db:codegen");
54
- console.error(" - A remote/staging database: CODEGEN_DATABASE_URL=postgresql://... npm run db:codegen");
55
- console.error(` Slow/cold-starting target? Raise the timeout: CODEGEN_CONNECT_TIMEOUT_MS=15000 npm run db:codegen`);
56
- process.exit(1);
53
+ return false;
57
54
  }
58
55
  }
59
56
 
57
+ function failUnreachable(url) {
58
+ console.error(`❌ Could not reach Postgres at ${url}`);
59
+ console.error("\n drizzle-kit pull needs a live Postgres to introspect — any one works, this script has no Docker dependency of its own. Pick one:");
60
+ console.error(" - Local Supabase (needs Docker running): ./supabase/scripts/db_start.sh --reset");
61
+ console.error(" - A native Postgres you already have: CODEGEN_DATABASE_URL=postgresql://... npm run db:codegen");
62
+ console.error(" - A remote/staging database: CODEGEN_DATABASE_URL=postgresql://... npm run db:codegen");
63
+ console.error(" - Zero setup (no Docker/Postgres at all): npm install --save-dev embedded-postgres (auto-used as a fallback)");
64
+ console.error(` Slow/cold-starting target? Raise the timeout: CODEGEN_CONNECT_TIMEOUT_MS=15000 npm run db:codegen`);
65
+ process.exit(1);
66
+ }
67
+
60
68
  function sqlFiles(dir) {
61
69
  return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
62
70
  const path = join(dir, entry.name);
@@ -87,10 +95,24 @@ if (paths.check) {
87
95
  process.exit(0);
88
96
  }
89
97
 
90
- await assertReachable(paths.dbUrl);
98
+ let effectiveDbUrl = paths.dbUrl;
99
+ let ephemeral = null;
100
+ try {
101
+ if (!(await isReachable(paths.dbUrl))) {
102
+ if (paths.dbUrlExplicit) failUnreachable(paths.dbUrl);
103
+ ephemeral = await startEphemeralPostgres(orderedSqlFiles(paths.ddlDir));
104
+ if (!ephemeral) failUnreachable(paths.dbUrl);
105
+ effectiveDbUrl = ephemeral.url;
106
+ }
91
107
 
92
- rmSync(paths.pullDir, { recursive: true, force: true });
93
- execFileSync('npx', ['drizzle-kit', 'pull', ...(paths.drizzleConfig ? [`--config=${paths.drizzleConfig}`] : [])], { stdio: 'inherit' });
108
+ rmSync(paths.pullDir, { recursive: true, force: true });
109
+ execFileSync('npx', ['drizzle-kit', 'pull', ...(paths.drizzleConfig ? [`--config=${paths.drizzleConfig}`] : [])], {
110
+ stdio: 'inherit',
111
+ env: { ...process.env, CODEGEN_DATABASE_URL: effectiveDbUrl },
112
+ });
113
+ } finally {
114
+ if (ephemeral) await ephemeral.stop();
115
+ }
94
116
 
95
117
  const pulled = join(paths.pullDir, "schema.ts");
96
118
  if (!existsSync(pulled)) {
@@ -0,0 +1,46 @@
1
+ // Mirrors supabase/scripts/db_start.sh's `process_path`: within each
2
+ // directory, files/subdirectories listed in that directory's own `order.txt`
3
+ // apply first (in the listed order, recursing into subdirectories the same
4
+ // way), then everything else applies in alphabetical order. DDL is not
5
+ // generally safe to apply purely alphabetically — e.g. a function that calls
6
+ // another function must be created after it — so `order.txt` is how a
7
+ // project expresses the real dependency order.
8
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
9
+ import { join } from 'node:path';
10
+
11
+ function orderedEntries(dir) {
12
+ const orderFile = join(dir, 'order.txt');
13
+ const applied = new Set();
14
+ const ordered = [];
15
+
16
+ if (existsSync(orderFile)) {
17
+ for (const rawLine of readFileSync(orderFile, 'utf8').split('\n')) {
18
+ const line = rawLine.trim();
19
+ if (!line || line.startsWith('#')) continue;
20
+ const path = join(dir, line);
21
+ if (existsSync(path)) {
22
+ ordered.push(path);
23
+ applied.add(line);
24
+ }
25
+ }
26
+ }
27
+
28
+ const rest = readdirSync(dir)
29
+ .filter((name) => name !== 'order.txt' && !applied.has(name))
30
+ .sort()
31
+ .map((name) => join(dir, name));
32
+
33
+ return [...ordered, ...rest];
34
+ }
35
+
36
+ /** Returns every `.sql` file under `dir`, in the order a project's own
37
+ * `order.txt` files (one per directory) say they must be applied. */
38
+ export function orderedSqlFiles(dir) {
39
+ const files = [];
40
+ for (const path of orderedEntries(dir)) {
41
+ const stat = statSync(path);
42
+ if (stat.isDirectory()) files.push(...orderedSqlFiles(path));
43
+ else if (path.endsWith('.sql')) files.push(path);
44
+ }
45
+ return files;
46
+ }
@@ -0,0 +1,84 @@
1
+ // Spins up a throwaway, local-only Postgres (via `embedded-postgres`, a
2
+ // prebuilt binary — no Docker) so `cfni-db-codegen` can introspect DDL
3
+ // without any live Postgres already running. Used only as a fallback when
4
+ // no --db-url/CODEGEN_DATABASE_URL was given and nothing is reachable at the
5
+ // local Supabase default.
6
+ import { readFileSync, rmSync } from 'node:fs';
7
+ import { relative } from 'node:path';
8
+ import { Client } from 'pg';
9
+
10
+ const EPHEMERAL_PORT = 54329;
11
+ const EPHEMERAL_URL = `postgresql://postgres:postgres@127.0.0.1:${EPHEMERAL_PORT}/postgres`;
12
+ // Lives under this package's own install dir, not the consuming project —
13
+ // keeps a throwaway Postgres data directory out of the user's repo/tree.
14
+ const DATA_DIR = new URL('../.drizzle-ephemeral-pg', import.meta.url).pathname;
15
+
16
+ // A plain embedded-postgres cluster has none of the roles the real Supabase
17
+ // Postgres image bootstraps into every project (`anon`/`authenticated`/
18
+ // `service_role` for PostgREST, plus the admin roles some GRANT/ALTER
19
+ // statements target) — DDL that GRANTs to them fails otherwise. These are
20
+ // the standard local-dev role names Supabase itself creates; DDL never
21
+ // creates them, so codegen must.
22
+ const SUPABASE_ROLES = [
23
+ 'anon', 'authenticated', 'service_role', 'authenticator',
24
+ 'supabase_admin', 'supabase_auth_admin', 'supabase_storage_admin', 'supabase_realtime_admin',
25
+ ];
26
+
27
+ /** Starts an ephemeral Postgres, loads every file in `sqlFiles` into it, and
28
+ * returns { url, stop() }. Caller must call stop() when done, even on error. */
29
+ export async function startEphemeralPostgres(sqlFiles) {
30
+ const dataDir = DATA_DIR;
31
+ let EmbeddedPostgres;
32
+ try {
33
+ ({ default: EmbeddedPostgres } = await import('embedded-postgres'));
34
+ } catch {
35
+ return null; // optional dep not installed — caller falls back to its normal error message
36
+ }
37
+
38
+ rmSync(dataDir, { recursive: true, force: true });
39
+ const pg = new EmbeddedPostgres({
40
+ databaseDir: dataDir,
41
+ port: EPHEMERAL_PORT,
42
+ user: 'postgres',
43
+ password: 'postgres',
44
+ persistent: false,
45
+ });
46
+
47
+ console.log('ℹ️ No reachable Postgres found — starting an ephemeral one (embedded-postgres, no Docker needed)…');
48
+ try {
49
+ await pg.initialise();
50
+ await pg.start();
51
+
52
+ const client = new Client({ connectionString: EPHEMERAL_URL });
53
+ await client.connect();
54
+ try {
55
+ for (const role of SUPABASE_ROLES) {
56
+ await client.query(`CREATE ROLE ${role} NOLOGIN NOINHERIT;`);
57
+ }
58
+ for (const file of sqlFiles) {
59
+ const sql = readFileSync(file, 'utf8');
60
+ if (sql.trim().length === 0) continue;
61
+ try {
62
+ await client.query(sql);
63
+ } catch (error) {
64
+ error.message = `${relative(process.cwd(), file)}: ${error.message}`;
65
+ throw error;
66
+ }
67
+ }
68
+ } finally {
69
+ await client.end();
70
+ }
71
+ } catch (error) {
72
+ await pg.stop().catch(() => { /* best-effort */ });
73
+ rmSync(dataDir, { recursive: true, force: true });
74
+ throw error;
75
+ }
76
+
77
+ return {
78
+ url: EPHEMERAL_URL,
79
+ async stop() {
80
+ await pg.stop().catch(() => { /* best-effort */ });
81
+ rmSync(dataDir, { recursive: true, force: true });
82
+ },
83
+ };
84
+ }
@@ -11,6 +11,7 @@ export interface CodegenPaths {
11
11
  pullDir: string;
12
12
  manifest: string;
13
13
  dbUrl: string;
14
+ dbUrlExplicit: boolean;
14
15
  check: boolean;
15
16
  timeoutMs: number;
16
17
  drizzleConfig: string | null;
@@ -52,6 +52,7 @@ export default function resolveCodegenPaths(argv, env, cwd) {
52
52
  pullDir: resolve(outDir, '..', '.drizzle-pull'),
53
53
  manifest: join(outDir, 'manifest.json'),
54
54
  dbUrl: flag(argv, 'db-url') ?? env.CODEGEN_DATABASE_URL ?? DEFAULT_DB_URL,
55
+ dbUrlExplicit: flag(argv, 'db-url') !== undefined || env.CODEGEN_DATABASE_URL !== undefined,
55
56
  check: argv.includes('--check'),
56
57
  timeoutMs: Number(env.CODEGEN_CONNECT_TIMEOUT_MS) || DEFAULT_TIMEOUT_MS,
57
58
  drizzleConfig: drizzleConfig === null ? null : abs(cwd, drizzleConfig),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudflare-next-intl",
3
- "version": "0.8.9",
3
+ "version": "0.8.11",
4
4
  "description": "Optimized Next Intl Package Special for App Router and Cloudflare",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -243,6 +243,7 @@
243
243
  "@microsoft/clarity": "^1.0.2",
244
244
  "@supabase/supabase-js": "^2.112.3",
245
245
  "drizzle-orm": "^0.45.2",
246
+ "embedded-postgres": "^18.4.0-beta.17",
246
247
  "firebase": "^12.17.0",
247
248
  "jose": "^6.2.8",
248
249
  "pg": "^8.23.0"