tempest-db-js 0.7.0 → 0.9.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.
package/dist/bin.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { runMigrationCli, diffSchema, replaySchema, reflectSchema, detectRenames } from './chunk-SI4CLSF7.js';
3
- import './chunk-4AWUP7BM.js';
2
+ import { runMigrationCli, diffSchema, replaySchema, reflectSchema, detectRenames } from './chunk-ZBIHRVUS.js';
3
+ import { backupDatabase, restoreDatabase } from './chunk-HXK6WIBP.js';
4
4
  import { existsSync } from 'fs';
5
5
  import { resolve } from 'path';
6
6
  import { createInterface } from 'readline/promises';
@@ -69,8 +69,37 @@ async function promptRenames(config, rest) {
69
69
  }
70
70
  return flags;
71
71
  }
72
+ async function runBackupCommand(argv) {
73
+ const [command, file, ...rest] = argv;
74
+ if (command !== "backup" && command !== "restore") return false;
75
+ const urlFlag = rest.indexOf("--url");
76
+ const url = (urlFlag >= 0 ? rest[urlFlag + 1] : void 0) ?? rest.find((arg) => arg.startsWith("--url="))?.slice("--url=".length) ?? process.env.DATABASE_URL;
77
+ if (!file || !url) {
78
+ process.stderr.write(
79
+ `tempest-db: usage: tempest-db ${command} <file> --url <database-url>
80
+ (or set DATABASE_URL)
81
+ `
82
+ );
83
+ process.exitCode = 1;
84
+ return true;
85
+ }
86
+ const force = rest.includes("--force");
87
+ try {
88
+ const result = command === "backup" ? await backupDatabase(url, file, { force }) : await restoreDatabase(url, file, { force });
89
+ process.stdout.write(
90
+ `${command === "backup" ? "backed up" : "restored"} ${result.dialect} via ${result.via}: ${result.file}
91
+ `
92
+ );
93
+ } catch (error) {
94
+ process.stderr.write(`tempest-db: ${error.message}
95
+ `);
96
+ process.exitCode = 1;
97
+ }
98
+ return true;
99
+ }
72
100
  async function main(argv) {
73
101
  const { configPath, rest } = extractConfigFlag(argv);
102
+ if (await runBackupCommand(rest)) return;
74
103
  const resolved = resolveConfigPath(configPath);
75
104
  if (!resolved) {
76
105
  process.stderr.write(
package/dist/bin.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/bin.ts"],"names":[],"mappings":"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","file":"bin.js","sourcesContent":["#!/usr/bin/env node\n/**\n * tempest-db-js — `tempest-db` executable.\n *\n * A thin wrapper around {@link runMigrationCli}: it discovers a config module,\n * loads it, injects a real timestamp, dispatches the command, prints the output\n * lines, and maps the result code onto `process.exit`. All the migration logic\n * lives in the (fully testable) programmatic core — this file only touches\n * `process`, the filesystem, and the wall clock.\n *\n * @example\n * ```bash\n * tempest-db upgrade\n * tempest-db revision -m \"add users\" --autogenerate\n * tempest-db --config db/tempest.config.mjs current\n * ```\n */\n\nimport { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { createInterface } from \"node:readline/promises\";\nimport { pathToFileURL } from \"node:url\";\nimport type { CliConfig } from \"./migrations/cli.js\";\nimport { runMigrationCli } from \"./migrations/cli.js\";\nimport { diffSchema } from \"./migrations/diff.js\";\nimport { reflectSchema } from \"./migrations/ir.js\";\nimport { type RenameCandidate, detectRenames } from \"./migrations/renames.js\";\nimport { replaySchema } from \"./migrations/replay.js\";\n\n/** Config file names looked up in the current directory, in order. */\nconst DEFAULT_CONFIG_NAMES: readonly string[] = [\n \"tempest-db.config.mjs\",\n \"tempest-db.config.js\",\n \"tempest-db.config.cjs\",\n];\n\n/**\n * Pull the `--config <path>` flag out of the argv, returning the path (if any)\n * and the remaining arguments for the CLI command itself.\n *\n * @param argv The process arguments (without node + script).\n * @returns The explicit config path (or `null`) and the leftover argv.\n */\nfunction extractConfigFlag(argv: readonly string[]): {\n configPath: string | null;\n rest: string[];\n} {\n const rest: string[] = [];\n let configPath: string | null = null;\n for (let i = 0; i < argv.length; i += 1) {\n const arg = argv[i];\n if (arg === \"--config\" || arg === \"-c\") {\n configPath = argv[i + 1] ?? null;\n i += 1;\n } else if (arg?.startsWith(\"--config=\")) {\n configPath = arg.slice(\"--config=\".length);\n } else if (arg !== undefined) {\n rest.push(arg);\n }\n }\n return { configPath, rest };\n}\n\n/**\n * Resolve the config file path: the explicit `--config` value, or the first of\n * the default names that exists in the current directory.\n *\n * @param explicit The `--config` value, or `null` to search defaults.\n * @returns The absolute config path, or `null` if none was found.\n */\nfunction resolveConfigPath(explicit: string | null): string | null {\n if (explicit) return resolve(process.cwd(), explicit);\n for (const name of DEFAULT_CONFIG_NAMES) {\n const candidate = resolve(process.cwd(), name);\n if (existsSync(candidate)) return candidate;\n }\n return null;\n}\n\n/**\n * Dynamically import a config module and extract the `CliConfig` from its\n * `default` export (or a named `config` export as a fallback).\n *\n * @param path The absolute path to the config module.\n * @returns The loaded migration config.\n * @throws If the module has neither a default nor a `config` export.\n */\nasync function loadConfig(path: string): Promise<CliConfig> {\n const mod = (await import(pathToFileURL(path).href)) as {\n default?: CliConfig;\n config?: CliConfig;\n };\n const config = mod.default ?? mod.config;\n if (!config) {\n throw new Error(\n `config at ${path} must default-export (or export \\`config\\`) a CliConfig`,\n );\n }\n return config;\n}\n\n/** Turn a confirmed candidate into the `--rename-*` flag args the CLI parses. */\nfunction renameToFlags(r: RenameCandidate): string[] {\n return r.kind === \"table\"\n ? [\"--rename-table\", `${r.from}:${r.to}`]\n : [\"--rename-column\", `${r.table}.${r.from}:${r.to}`];\n}\n\n/**\n * When running `revision --autogenerate` at an interactive TTY, detect rename\n * candidates and ask the user to confirm each; return the `--rename-*` flags for\n * the accepted ones. No-op when not autogenerating, when `--autorename` or\n * explicit `--rename-*` flags are already present, when there are no models, or\n * when stdin is not a TTY (CI, pipes).\n *\n * @param config The loaded migration config.\n * @param rest The CLI command arguments.\n * @returns Extra `--rename-*` args to append before dispatching.\n */\nasync function promptRenames(\n config: CliConfig,\n rest: readonly string[],\n): Promise<string[]> {\n const decided =\n rest.includes(\"--autorename\") ||\n rest.includes(\"--rename-table\") ||\n rest.includes(\"--rename-column\");\n if (\n rest[0] !== \"revision\" ||\n !rest.includes(\"--autogenerate\") ||\n decided ||\n !config.models ||\n !process.stdin.isTTY\n ) {\n return [];\n }\n\n const ops = diffSchema(replaySchema(config.migrations), reflectSchema(config.models));\n const candidates = detectRenames(ops);\n if (candidates.length === 0) return [];\n\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const flags: string[] = [];\n try {\n for (const c of candidates) {\n const what =\n c.kind === \"table\"\n ? `table \"${c.from}\" → \"${c.to}\"`\n : `column \"${c.table}.${c.from}\" → \"${c.table}.${c.to}\"`;\n const answer = (await rl.question(`Rename ${what}? [y/N] `)).trim().toLowerCase();\n if (answer === \"y\" || answer === \"yes\") flags.push(...renameToFlags(c));\n }\n } finally {\n rl.close();\n }\n return flags;\n}\n\n/**\n * Run the `tempest-db` CLI end to end: discover + load the config, dispatch the\n * command, print output, and exit with the CLI's status code.\n *\n * @param argv The process arguments (without node + script).\n * @returns A promise resolving once the command has printed and exit is set.\n */\nexport async function main(argv: readonly string[]): Promise<void> {\n const { configPath, rest } = extractConfigFlag(argv);\n const resolved = resolveConfigPath(configPath);\n if (!resolved) {\n process.stderr.write(\n `tempest-db: no config found. Create one of ${DEFAULT_CONFIG_NAMES.join(\n \", \",\n )} or pass --config <path>.\\n`,\n );\n process.exitCode = 1;\n return;\n }\n\n let config: CliConfig;\n try {\n config = await loadConfig(resolved);\n } catch (error) {\n process.stderr.write(`tempest-db: ${(error as Error).message}\\n`);\n process.exitCode = 1;\n return;\n }\n\n const withClock: CliConfig = {\n ...config,\n appliedAt: config.appliedAt ?? new Date().toISOString(),\n };\n const renameFlags = await promptRenames(withClock, rest);\n const result = await runMigrationCli([...rest, ...renameFlags], withClock);\n const sink = result.code === 0 ? process.stdout : process.stderr;\n for (const line of result.lines) sink.write(`${line}\\n`);\n process.exitCode = result.code;\n}\n\n// Only auto-run when invoked as a program (not when imported by a test).\nif (import.meta.url === pathToFileURL(process.argv[1] ?? \"\").href) {\n void main(process.argv.slice(2));\n}\n"]}
1
+ {"version":3,"sources":["../src/bin.ts"],"names":[],"mappings":"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","file":"bin.js","sourcesContent":["#!/usr/bin/env node\n/**\n * tempest-db-js — `tempest-db` executable.\n *\n * A thin wrapper around {@link runMigrationCli}: it discovers a config module,\n * loads it, injects a real timestamp, dispatches the command, prints the output\n * lines, and maps the result code onto `process.exit`. All the migration logic\n * lives in the (fully testable) programmatic core — this file only touches\n * `process`, the filesystem, and the wall clock.\n *\n * @example\n * ```bash\n * tempest-db upgrade\n * tempest-db revision -m \"add users\" --autogenerate\n * tempest-db --config db/tempest.config.mjs current\n * ```\n */\n\nimport { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { createInterface } from \"node:readline/promises\";\nimport { pathToFileURL } from \"node:url\";\nimport { backupDatabase, restoreDatabase } from \"./backup.js\";\nimport type { CliConfig } from \"./migrations/cli.js\";\nimport { runMigrationCli } from \"./migrations/cli.js\";\nimport { diffSchema } from \"./migrations/diff.js\";\nimport { reflectSchema } from \"./migrations/ir.js\";\nimport { type RenameCandidate, detectRenames } from \"./migrations/renames.js\";\nimport { replaySchema } from \"./migrations/replay.js\";\n\n/** Config file names looked up in the current directory, in order. */\nconst DEFAULT_CONFIG_NAMES: readonly string[] = [\n \"tempest-db.config.mjs\",\n \"tempest-db.config.js\",\n \"tempest-db.config.cjs\",\n];\n\n/**\n * Pull the `--config <path>` flag out of the argv, returning the path (if any)\n * and the remaining arguments for the CLI command itself.\n *\n * @param argv The process arguments (without node + script).\n * @returns The explicit config path (or `null`) and the leftover argv.\n */\nfunction extractConfigFlag(argv: readonly string[]): {\n configPath: string | null;\n rest: string[];\n} {\n const rest: string[] = [];\n let configPath: string | null = null;\n for (let i = 0; i < argv.length; i += 1) {\n const arg = argv[i];\n if (arg === \"--config\" || arg === \"-c\") {\n configPath = argv[i + 1] ?? null;\n i += 1;\n } else if (arg?.startsWith(\"--config=\")) {\n configPath = arg.slice(\"--config=\".length);\n } else if (arg !== undefined) {\n rest.push(arg);\n }\n }\n return { configPath, rest };\n}\n\n/**\n * Resolve the config file path: the explicit `--config` value, or the first of\n * the default names that exists in the current directory.\n *\n * @param explicit The `--config` value, or `null` to search defaults.\n * @returns The absolute config path, or `null` if none was found.\n */\nfunction resolveConfigPath(explicit: string | null): string | null {\n if (explicit) return resolve(process.cwd(), explicit);\n for (const name of DEFAULT_CONFIG_NAMES) {\n const candidate = resolve(process.cwd(), name);\n if (existsSync(candidate)) return candidate;\n }\n return null;\n}\n\n/**\n * Dynamically import a config module and extract the `CliConfig` from its\n * `default` export (or a named `config` export as a fallback).\n *\n * @param path The absolute path to the config module.\n * @returns The loaded migration config.\n * @throws If the module has neither a default nor a `config` export.\n */\nasync function loadConfig(path: string): Promise<CliConfig> {\n const mod = (await import(pathToFileURL(path).href)) as {\n default?: CliConfig;\n config?: CliConfig;\n };\n const config = mod.default ?? mod.config;\n if (!config) {\n throw new Error(\n `config at ${path} must default-export (or export \\`config\\`) a CliConfig`,\n );\n }\n return config;\n}\n\n/** Turn a confirmed candidate into the `--rename-*` flag args the CLI parses. */\nfunction renameToFlags(r: RenameCandidate): string[] {\n return r.kind === \"table\"\n ? [\"--rename-table\", `${r.from}:${r.to}`]\n : [\"--rename-column\", `${r.table}.${r.from}:${r.to}`];\n}\n\n/**\n * When running `revision --autogenerate` at an interactive TTY, detect rename\n * candidates and ask the user to confirm each; return the `--rename-*` flags for\n * the accepted ones. No-op when not autogenerating, when `--autorename` or\n * explicit `--rename-*` flags are already present, when there are no models, or\n * when stdin is not a TTY (CI, pipes).\n *\n * @param config The loaded migration config.\n * @param rest The CLI command arguments.\n * @returns Extra `--rename-*` args to append before dispatching.\n */\nasync function promptRenames(\n config: CliConfig,\n rest: readonly string[],\n): Promise<string[]> {\n const decided =\n rest.includes(\"--autorename\") ||\n rest.includes(\"--rename-table\") ||\n rest.includes(\"--rename-column\");\n if (\n rest[0] !== \"revision\" ||\n !rest.includes(\"--autogenerate\") ||\n decided ||\n !config.models ||\n !process.stdin.isTTY\n ) {\n return [];\n }\n\n const ops = diffSchema(replaySchema(config.migrations), reflectSchema(config.models));\n const candidates = detectRenames(ops);\n if (candidates.length === 0) return [];\n\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const flags: string[] = [];\n try {\n for (const c of candidates) {\n const what =\n c.kind === \"table\"\n ? `table \"${c.from}\" → \"${c.to}\"`\n : `column \"${c.table}.${c.from}\" → \"${c.table}.${c.to}\"`;\n const answer = (await rl.question(`Rename ${what}? [y/N] `)).trim().toLowerCase();\n if (answer === \"y\" || answer === \"yes\") flags.push(...renameToFlags(c));\n }\n } finally {\n rl.close();\n }\n return flags;\n}\n\n/**\n * Handle `backup` / `restore`, which need only a URL — not the migration config.\n *\n * Kept ahead of the config lookup on purpose: a database that cannot be migrated\n * (no config yet, or a config that fails to load) is exactly the one somebody\n * needs a dump of.\n *\n * @param argv The command and its arguments.\n * @returns True when the command was handled here.\n */\nasync function runBackupCommand(argv: readonly string[]): Promise<boolean> {\n const [command, file, ...rest] = argv;\n if (command !== \"backup\" && command !== \"restore\") return false;\n\n const urlFlag = rest.indexOf(\"--url\");\n const url =\n (urlFlag >= 0 ? rest[urlFlag + 1] : undefined) ??\n rest.find((arg) => arg.startsWith(\"--url=\"))?.slice(\"--url=\".length) ??\n process.env.DATABASE_URL;\n\n if (!file || !url) {\n process.stderr.write(\n `tempest-db: usage: tempest-db ${command} <file> --url <database-url>\\n (or set DATABASE_URL)\\n`,\n );\n process.exitCode = 1;\n return true;\n }\n\n const force = rest.includes(\"--force\");\n try {\n const result =\n command === \"backup\"\n ? await backupDatabase(url, file, { force })\n : await restoreDatabase(url, file, { force });\n process.stdout.write(\n `${command === \"backup\" ? \"backed up\" : \"restored\"} ${result.dialect} via ${result.via}: ${result.file}\\n`,\n );\n } catch (error) {\n process.stderr.write(`tempest-db: ${(error as Error).message}\\n`);\n process.exitCode = 1;\n }\n return true;\n}\n\n/**\n * Run the `tempest-db` CLI end to end: discover + load the config, dispatch the\n * command, print output, and exit with the CLI's status code.\n *\n * @param argv The process arguments (without node + script).\n * @returns A promise resolving once the command has printed and exit is set.\n */\nexport async function main(argv: readonly string[]): Promise<void> {\n const { configPath, rest } = extractConfigFlag(argv);\n if (await runBackupCommand(rest)) return;\n const resolved = resolveConfigPath(configPath);\n if (!resolved) {\n process.stderr.write(\n `tempest-db: no config found. Create one of ${DEFAULT_CONFIG_NAMES.join(\n \", \",\n )} or pass --config <path>.\\n`,\n );\n process.exitCode = 1;\n return;\n }\n\n let config: CliConfig;\n try {\n config = await loadConfig(resolved);\n } catch (error) {\n process.stderr.write(`tempest-db: ${(error as Error).message}\\n`);\n process.exitCode = 1;\n return;\n }\n\n const withClock: CliConfig = {\n ...config,\n appliedAt: config.appliedAt ?? new Date().toISOString(),\n };\n const renameFlags = await promptRenames(withClock, rest);\n const result = await runMigrationCli([...rest, ...renameFlags], withClock);\n const sink = result.code === 0 ? process.stdout : process.stderr;\n for (const line of result.lines) sink.write(`${line}\\n`);\n process.exitCode = result.code;\n}\n\n// Only auto-run when invoked as a program (not when imported by a test).\nif (import.meta.url === pathToFileURL(process.argv[1] ?? \"\").href) {\n void main(process.argv.slice(2));\n}\n"]}