cloudflare-next-intl 0.8.10 → 0.8.12

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.
@@ -38,7 +38,9 @@ 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
40
  import { startEphemeralPostgres } from './ephemeral_pg.mjs';
41
+ import { orderedSqlFiles } from './ddl_order.mjs';
41
42
 
43
+ const PACKAGE_ROOT = new URL('..', import.meta.url).pathname;
42
44
  const paths = resolveCodegenPaths(process.argv.slice(2), process.env, process.cwd());
43
45
 
44
46
  async function isReachable(url) {
@@ -96,21 +98,48 @@ if (paths.check) {
96
98
 
97
99
  let effectiveDbUrl = paths.dbUrl;
98
100
  let ephemeral = null;
99
- if (!(await isReachable(paths.dbUrl))) {
100
- if (paths.dbUrlExplicit) failUnreachable(paths.dbUrl);
101
- ephemeral = await startEphemeralPostgres(paths.ephemeralDir, sqlFiles(paths.ddlDir));
102
- if (!ephemeral) failUnreachable(paths.dbUrl);
103
- effectiveDbUrl = ephemeral.url;
104
- }
105
-
106
- rmSync(paths.pullDir, { recursive: true, force: true });
101
+ // drizzle-kit's own default config resolution looks for `drizzle.config.json`
102
+ // in the cwd and errors out if it's missing — a project has no reason to
103
+ // keep one around just for this script when --db-url/CODEGEN_DATABASE_URL
104
+ // (or the ephemeral fallback) already says everything drizzle-kit needs. So
105
+ // when the caller didn't pass --drizzle-config, generate one on the fly.
106
+ let generatedConfigPath = null;
107
107
  try {
108
- execFileSync('npx', ['drizzle-kit', 'pull', ...(paths.drizzleConfig ? [`--config=${paths.drizzleConfig}`] : [])], {
108
+ if (!(await isReachable(paths.dbUrl))) {
109
+ if (paths.dbUrlExplicit) failUnreachable(paths.dbUrl);
110
+ ephemeral = await startEphemeralPostgres(orderedSqlFiles(paths.ddlDir));
111
+ if (!ephemeral) failUnreachable(paths.dbUrl);
112
+ effectiveDbUrl = ephemeral.url;
113
+ }
114
+
115
+ let configPath = paths.drizzleConfig;
116
+ if (!configPath) {
117
+ generatedConfigPath = join(paths.pullDir, '..', '.drizzle-config.json');
118
+ mkdirSync(join(paths.pullDir, '..'), { recursive: true });
119
+ writeFileSync(generatedConfigPath, JSON.stringify({
120
+ out: paths.pullDir,
121
+ dialect: 'postgresql',
122
+ dbCredentials: { url: effectiveDbUrl },
123
+ }, null, 2));
124
+ configPath = generatedConfigPath;
125
+ }
126
+
127
+ rmSync(paths.pullDir, { recursive: true, force: true });
128
+ // drizzle-kit itself requires `drizzle-orm` at runtime. This package
129
+ // depends on drizzle-orm, but npm doesn't guarantee that dependency gets
130
+ // hoisted to the consuming project's own node_modules (it commonly stays
131
+ // nested under node_modules/cloudflare-next-intl/node_modules) — so
132
+ // running from *this* package's own directory, where it's guaranteed
133
+ // resolvable, avoids "please install required packages: drizzle-orm"
134
+ // when a consumer doesn't happen to have it hoisted.
135
+ execFileSync('npx', ['drizzle-kit', 'pull', `--config=${configPath}`], {
109
136
  stdio: 'inherit',
137
+ cwd: PACKAGE_ROOT,
110
138
  env: { ...process.env, CODEGEN_DATABASE_URL: effectiveDbUrl },
111
139
  });
112
140
  } finally {
113
141
  if (ephemeral) await ephemeral.stop();
142
+ if (generatedConfigPath) rmSync(generatedConfigPath, { force: true });
114
143
  }
115
144
 
116
145
  const pulled = join(paths.pullDir, "schema.ts");
@@ -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
+ }
@@ -4,14 +4,30 @@
4
4
  // no --db-url/CODEGEN_DATABASE_URL was given and nothing is reachable at the
5
5
  // local Supabase default.
6
6
  import { readFileSync, rmSync } from 'node:fs';
7
+ import { relative } from 'node:path';
7
8
  import { Client } from 'pg';
8
9
 
9
10
  const EPHEMERAL_PORT = 54329;
10
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
+ ];
11
26
 
12
27
  /** Starts an ephemeral Postgres, loads every file in `sqlFiles` into it, and
13
28
  * returns { url, stop() }. Caller must call stop() when done, even on error. */
14
- export async function startEphemeralPostgres(dataDir, sqlFiles) {
29
+ export async function startEphemeralPostgres(sqlFiles) {
30
+ const dataDir = DATA_DIR;
15
31
  let EmbeddedPostgres;
16
32
  try {
17
33
  ({ default: EmbeddedPostgres } = await import('embedded-postgres'));
@@ -29,19 +45,33 @@ export async function startEphemeralPostgres(dataDir, sqlFiles) {
29
45
  });
30
46
 
31
47
  console.log('ℹ️ No reachable Postgres found — starting an ephemeral one (embedded-postgres, no Docker needed)…');
32
- await pg.initialise();
33
- await pg.start();
34
-
35
- const client = new Client({ connectionString: EPHEMERAL_URL });
36
- await client.connect();
37
48
  try {
38
- for (const file of sqlFiles) {
39
- const sql = readFileSync(file, 'utf8');
40
- if (sql.trim().length === 0) continue;
41
- await client.query(sql);
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();
42
70
  }
43
- } finally {
44
- await client.end();
71
+ } catch (error) {
72
+ await pg.stop().catch(() => { /* best-effort */ });
73
+ rmSync(dataDir, { recursive: true, force: true });
74
+ throw error;
45
75
  }
46
76
 
47
77
  return {
@@ -12,7 +12,6 @@ export interface CodegenPaths {
12
12
  manifest: string;
13
13
  dbUrl: string;
14
14
  dbUrlExplicit: boolean;
15
- ephemeralDir: string;
16
15
  check: boolean;
17
16
  timeoutMs: number;
18
17
  drizzleConfig: string | null;
@@ -53,7 +53,6 @@ export default function resolveCodegenPaths(argv, env, cwd) {
53
53
  manifest: join(outDir, 'manifest.json'),
54
54
  dbUrl: flag(argv, 'db-url') ?? env.CODEGEN_DATABASE_URL ?? DEFAULT_DB_URL,
55
55
  dbUrlExplicit: flag(argv, 'db-url') !== undefined || env.CODEGEN_DATABASE_URL !== undefined,
56
- ephemeralDir: resolve(outDir, '..', '.drizzle-ephemeral-pg'),
57
56
  check: argv.includes('--check'),
58
57
  timeoutMs: Number(env.CODEGEN_CONNECT_TIMEOUT_MS) || DEFAULT_TIMEOUT_MS,
59
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.10",
3
+ "version": "0.8.12",
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",