postgresdk 0.18.26 → 0.18.28

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.
@@ -0,0 +1,14 @@
1
+ /** Parse --force / -y / --yes from CLI args */
2
+ export declare function parseForceFlag(args: string[]): boolean;
3
+ /**
4
+ * For each stale file, prompt the user to confirm deletion (shadcn-style per-file).
5
+ * If `force` is true, all files are deleted without prompting.
6
+ * If stdout is not a TTY (e.g. CI), files are skipped with a warning unless `force` is true.
7
+ *
8
+ * Returns counts of deleted and skipped files.
9
+ */
10
+ export declare function confirmAndDeleteStaleFiles(staleFiles: string[], force: boolean): Promise<{
11
+ deleted: number;
12
+ skipped: number;
13
+ filesDeleted: string[];
14
+ }>;
package/dist/cli.js CHANGED
@@ -515,9 +515,21 @@ async function ensureDirs(dirs) {
515
515
  for (const d of dirs)
516
516
  await mkdir(d, { recursive: true });
517
517
  }
518
- async function deleteStaleFiles(generatedPaths, dirsToScan) {
519
- let deleted = 0;
520
- const filesDeleted = [];
518
+ async function collectDirsRecursively(root) {
519
+ if (!existsSync(root))
520
+ return [];
521
+ const dirs = [root];
522
+ const entries = await readdir(root, { withFileTypes: true });
523
+ for (const entry of entries) {
524
+ if (!entry.isDirectory())
525
+ continue;
526
+ const sub = join(root, entry.name);
527
+ dirs.push(...await collectDirsRecursively(sub));
528
+ }
529
+ return dirs;
530
+ }
531
+ async function findStaleFiles(generatedPaths, dirsToScan) {
532
+ const stale = [];
521
533
  for (const dir of dirsToScan) {
522
534
  if (!existsSync(dir))
523
535
  continue;
@@ -529,13 +541,11 @@ async function deleteStaleFiles(generatedPaths, dirsToScan) {
529
541
  continue;
530
542
  const fullPath = join(dir, entry.name);
531
543
  if (!generatedPaths.has(fullPath)) {
532
- await unlink(fullPath);
533
- deleted++;
534
- filesDeleted.push(fullPath);
544
+ stale.push(fullPath);
535
545
  }
536
546
  }
537
547
  }
538
- return { deleted, filesDeleted };
548
+ return stale;
539
549
  }
540
550
  var pascal = (s) => s.split(/[_\s-]+/).map((w) => w?.[0] ? w[0].toUpperCase() + w.slice(1) : "").join("");
541
551
  var init_utils = () => {};
@@ -1414,6 +1424,50 @@ var init_emit_sdk_contract = __esm(() => {
1414
1424
  init_emit_include_methods();
1415
1425
  });
1416
1426
 
1427
+ // src/cli-utils.ts
1428
+ import { unlink as unlink2 } from "fs/promises";
1429
+ import prompts from "prompts";
1430
+ function parseForceFlag(args) {
1431
+ return args.includes("--force") || args.includes("-y") || args.includes("--yes");
1432
+ }
1433
+ async function confirmAndDeleteStaleFiles(staleFiles, force) {
1434
+ if (staleFiles.length === 0)
1435
+ return { deleted: 0, skipped: 0, filesDeleted: [] };
1436
+ if (!force && !process.stdout.isTTY) {
1437
+ console.log(`⚠️ ${staleFiles.length} stale file(s) not deleted (non-interactive shell). Re-run with --force to delete.`);
1438
+ return { deleted: 0, skipped: staleFiles.length, filesDeleted: [] };
1439
+ }
1440
+ let deleted = 0;
1441
+ let skipped = 0;
1442
+ const filesDeleted = [];
1443
+ for (const filePath of staleFiles) {
1444
+ let shouldDelete = force;
1445
+ if (!force) {
1446
+ const { confirmed } = await prompts({
1447
+ type: "confirm",
1448
+ name: "confirmed",
1449
+ message: `Delete stale file: ${filePath}?`,
1450
+ initial: false
1451
+ });
1452
+ if (confirmed === undefined) {
1453
+ console.log("Stale file deletion aborted.");
1454
+ break;
1455
+ }
1456
+ shouldDelete = confirmed;
1457
+ }
1458
+ if (shouldDelete) {
1459
+ await unlink2(filePath);
1460
+ console.log(` ✗ ${filePath}`);
1461
+ filesDeleted.push(filePath);
1462
+ deleted++;
1463
+ } else {
1464
+ skipped++;
1465
+ }
1466
+ }
1467
+ return { deleted, skipped, filesDeleted };
1468
+ }
1469
+ var init_cli_utils = () => {};
1470
+
1417
1471
  // src/cli-config-utils.ts
1418
1472
  function extractConfigFields(configContent) {
1419
1473
  const fields = [];
@@ -1835,7 +1889,7 @@ __export(exports_cli_init, {
1835
1889
  });
1836
1890
  import { existsSync as existsSync3, writeFileSync, readFileSync as readFileSync2, copyFileSync } from "fs";
1837
1891
  import { resolve } from "path";
1838
- import prompts from "prompts";
1892
+ import prompts2 from "prompts";
1839
1893
  async function initCommand(args) {
1840
1894
  console.log(`\uD83D\uDE80 Initializing postgresdk configuration...
1841
1895
  `);
@@ -1860,7 +1914,7 @@ async function initCommand(args) {
1860
1914
  }
1861
1915
  });
1862
1916
  console.log();
1863
- const { mergeStrategy } = await prompts({
1917
+ const { mergeStrategy } = await prompts2({
1864
1918
  type: "select",
1865
1919
  name: "mergeStrategy",
1866
1920
  message: "How would you like to proceed?",
@@ -1900,7 +1954,7 @@ async function initCommand(args) {
1900
1954
  \uD83D\uDD04 Let's review your configuration:
1901
1955
  `);
1902
1956
  for (const field of existingFields.filter((f) => !f.isCommented)) {
1903
- const { choice } = await prompts({
1957
+ const { choice } = await prompts2({
1904
1958
  type: "select",
1905
1959
  name: "choice",
1906
1960
  message: `${field.description}:
@@ -1932,7 +1986,7 @@ async function initCommand(args) {
1932
1986
  \uD83D\uDCE6 New configuration options available:
1933
1987
  `);
1934
1988
  for (const option of missingOptions) {
1935
- const { addOption } = await prompts({
1989
+ const { addOption } = await prompts2({
1936
1990
  type: "confirm",
1937
1991
  name: "addOption",
1938
1992
  message: `Add ${option.description} configuration? (commented out by default)`,
@@ -1971,7 +2025,7 @@ async function initCommand(args) {
1971
2025
  } else if (isSdkSide) {
1972
2026
  projectType = "sdk";
1973
2027
  } else {
1974
- const response = await prompts({
2028
+ const response = await prompts2({
1975
2029
  type: "select",
1976
2030
  name: "projectType",
1977
2031
  message: "What type of project is this?",
@@ -2267,6 +2321,7 @@ async function pullCommand(args) {
2267
2321
  console.error("⚠️ Failed to load config file:", err);
2268
2322
  }
2269
2323
  }
2324
+ const force = parseForceFlag(args);
2270
2325
  const cliConfig = {
2271
2326
  from: args.find((a) => a.startsWith("--from="))?.split("=")[1],
2272
2327
  output: args.find((a) => a.startsWith("--output="))?.split("=")[1],
@@ -2328,8 +2383,9 @@ Options:`);
2328
2383
  let filesWritten = 0;
2329
2384
  let filesUnchanged = 0;
2330
2385
  const changedFiles = [];
2386
+ const resolvedOutput = resolve2(config.output);
2331
2387
  for (const [path, content] of Object.entries(sdk.files)) {
2332
- const fullPath = join3(config.output, path);
2388
+ const fullPath = join3(resolvedOutput, path);
2333
2389
  await mkdir2(dirname3(fullPath), { recursive: true });
2334
2390
  let shouldWrite = true;
2335
2391
  if (existsSync4(fullPath)) {
@@ -2346,7 +2402,11 @@ Options:`);
2346
2402
  console.log(` ✓ ${path}`);
2347
2403
  }
2348
2404
  }
2349
- const metadataPath = join3(config.output, ".postgresdk.json");
2405
+ const generatedPaths = new Set(Object.keys(sdk.files).map((p) => join3(resolvedOutput, p)));
2406
+ const dirsToScan = await collectDirsRecursively(resolvedOutput);
2407
+ const staleFiles = await findStaleFiles(generatedPaths, dirsToScan);
2408
+ const deleteResult = await confirmAndDeleteStaleFiles(staleFiles, force);
2409
+ const metadataPath = join3(resolvedOutput, ".postgresdk.json");
2350
2410
  const metadata = {
2351
2411
  version: sdk.version,
2352
2412
  pulledFrom: config.from
@@ -2361,18 +2421,30 @@ Options:`);
2361
2421
  if (metadataChanged) {
2362
2422
  await writeFile2(metadataPath, JSON.stringify(metadata, null, 2));
2363
2423
  }
2364
- if (filesWritten === 0 && !metadataChanged) {
2424
+ if (filesWritten === 0 && !metadataChanged && deleteResult.deleted === 0 && deleteResult.skipped === 0) {
2365
2425
  console.log(`✅ SDK up-to-date (${filesUnchanged} files unchanged)`);
2366
2426
  } else {
2427
+ const parts = [];
2428
+ if (filesWritten > 0)
2429
+ parts.push(`updated ${filesWritten} files`);
2430
+ if (deleteResult.deleted > 0)
2431
+ parts.push(`deleted ${deleteResult.deleted} stale files`);
2432
+ if (deleteResult.skipped > 0)
2433
+ parts.push(`skipped ${deleteResult.skipped} stale files`);
2434
+ if (filesUnchanged > 0)
2435
+ parts.push(`${filesUnchanged} unchanged`);
2367
2436
  console.log(`✅ SDK pulled successfully to ${config.output}`);
2368
- console.log(` Updated: ${filesWritten} files, Unchanged: ${filesUnchanged} files`);
2437
+ console.log(` ${parts.join(", ")}`);
2369
2438
  }
2370
2439
  } catch (err) {
2371
2440
  console.error(`❌ Pull failed:`, err);
2372
2441
  process.exit(1);
2373
2442
  }
2374
2443
  }
2375
- var init_cli_pull = () => {};
2444
+ var init_cli_pull = __esm(() => {
2445
+ init_utils();
2446
+ init_cli_utils();
2447
+ });
2376
2448
 
2377
2449
  // src/index.ts
2378
2450
  var import_config = __toESM(require_config(), 1);
@@ -7082,6 +7154,7 @@ function generateTestCases(table, sampleData, updateData, hasForeignKeys = false
7082
7154
  // src/index.ts
7083
7155
  init_emit_sdk_contract();
7084
7156
  init_utils();
7157
+ init_cli_utils();
7085
7158
  var __filename2 = fileURLToPath(import.meta.url);
7086
7159
  var __dirname2 = dirname2(__filename2);
7087
7160
  var { version: CLI_VERSION } = JSON.parse(readFileSync(join2(__dirname2, "../package.json"), "utf-8"));
@@ -7091,7 +7164,7 @@ function resolveSoftDeleteColumn(cfg, tableName) {
7091
7164
  return overrides[tableName] ?? null;
7092
7165
  return cfg.softDeleteColumn ?? null;
7093
7166
  }
7094
- async function generate(configPath) {
7167
+ async function generate(configPath, options) {
7095
7168
  if (!existsSync2(configPath)) {
7096
7169
  throw new Error(`Config file not found: ${configPath}
7097
7170
 
@@ -7286,7 +7359,7 @@ async function generate(configPath) {
7286
7359
  }
7287
7360
  console.log("✍️ Writing files...");
7288
7361
  const writeResult = await writeFilesIfChanged(files);
7289
- let deleteResult = { deleted: 0, filesDeleted: [] };
7362
+ let deleteResult = { deleted: 0, skipped: 0, filesDeleted: [] };
7290
7363
  if (cfg.clean !== false) {
7291
7364
  const dirsToScan = [
7292
7365
  serverDir,
@@ -7302,9 +7375,10 @@ async function generate(configPath) {
7302
7375
  if (generateTests)
7303
7376
  dirsToScan.push(testDir);
7304
7377
  const generatedPaths = new Set(files.map((f) => f.path));
7305
- deleteResult = await deleteStaleFiles(generatedPaths, dirsToScan);
7378
+ const staleFiles = await findStaleFiles(generatedPaths, dirsToScan);
7379
+ deleteResult = await confirmAndDeleteStaleFiles(staleFiles, options?.force ?? false);
7306
7380
  }
7307
- if (writeResult.written === 0 && deleteResult.deleted === 0) {
7381
+ if (writeResult.written === 0 && deleteResult.deleted === 0 && deleteResult.skipped === 0) {
7308
7382
  console.log(`✅ All ${writeResult.unchanged} files up-to-date (no changes)`);
7309
7383
  } else {
7310
7384
  const parts = [];
@@ -7312,6 +7386,8 @@ async function generate(configPath) {
7312
7386
  parts.push(`updated ${writeResult.written} files`);
7313
7387
  if (deleteResult.deleted > 0)
7314
7388
  parts.push(`deleted ${deleteResult.deleted} stale files`);
7389
+ if (deleteResult.skipped > 0)
7390
+ parts.push(`skipped ${deleteResult.skipped} stale files`);
7315
7391
  if (writeResult.unchanged > 0)
7316
7392
  parts.push(`${writeResult.unchanged} unchanged`);
7317
7393
  console.log(`✅ ${parts.join(", ")}`);
@@ -7354,6 +7430,7 @@ import { resolve as resolve3 } from "node:path";
7354
7430
  import { readFileSync as readFileSync3 } from "node:fs";
7355
7431
  import { fileURLToPath as fileURLToPath2 } from "node:url";
7356
7432
  import { dirname as dirname4, join as join4 } from "node:path";
7433
+ init_cli_utils();
7357
7434
  var __filename3 = fileURLToPath2(import.meta.url);
7358
7435
  var __dirname3 = dirname4(__filename3);
7359
7436
  var packageJson = JSON.parse(readFileSync3(join4(__dirname3, "../package.json"), "utf-8"));
@@ -7383,11 +7460,13 @@ Init Options:
7383
7460
 
7384
7461
  Generate Options:
7385
7462
  -c, --config <path> Path to config file (default: postgresdk.config.ts)
7463
+ --force, -y Delete stale files without prompting
7386
7464
 
7387
7465
  Pull Options:
7388
7466
  --from <url> API URL to pull SDK from
7389
7467
  --output <path> Output directory (default: ./src/sdk)
7390
7468
  --token <token> Authentication token
7469
+ --force, -y Delete stale files without prompting
7391
7470
  -c, --config <path> Path to config file with pull settings
7392
7471
 
7393
7472
  Examples:
@@ -7409,8 +7488,9 @@ if (command === "init") {
7409
7488
  if (configIndex !== -1 && args[configIndex + 1]) {
7410
7489
  configPath = args[configIndex + 1];
7411
7490
  }
7491
+ const force = parseForceFlag(args);
7412
7492
  try {
7413
- await generate(resolve3(process.cwd(), configPath));
7493
+ await generate(resolve3(process.cwd(), configPath), { force });
7414
7494
  } catch (err) {
7415
7495
  console.error("❌ Generation failed:", err);
7416
7496
  process.exit(1);
package/dist/index.d.ts CHANGED
@@ -2,4 +2,6 @@ import "dotenv/config";
2
2
  import type { Config } from "./types";
3
3
  /** Resolves the effective soft delete column for a given table, respecting per-table overrides. */
4
4
  export declare function resolveSoftDeleteColumn(cfg: Pick<Config, "softDeleteColumn" | "softDeleteColumnOverrides">, tableName: string): string | null;
5
- export declare function generate(configPath: string): Promise<void>;
5
+ export declare function generate(configPath: string, options?: {
6
+ force?: boolean;
7
+ }): Promise<void>;
package/dist/index.js CHANGED
@@ -514,9 +514,21 @@ async function ensureDirs(dirs) {
514
514
  for (const d of dirs)
515
515
  await mkdir(d, { recursive: true });
516
516
  }
517
- async function deleteStaleFiles(generatedPaths, dirsToScan) {
518
- let deleted = 0;
519
- const filesDeleted = [];
517
+ async function collectDirsRecursively(root) {
518
+ if (!existsSync(root))
519
+ return [];
520
+ const dirs = [root];
521
+ const entries = await readdir(root, { withFileTypes: true });
522
+ for (const entry of entries) {
523
+ if (!entry.isDirectory())
524
+ continue;
525
+ const sub = join(root, entry.name);
526
+ dirs.push(...await collectDirsRecursively(sub));
527
+ }
528
+ return dirs;
529
+ }
530
+ async function findStaleFiles(generatedPaths, dirsToScan) {
531
+ const stale = [];
520
532
  for (const dir of dirsToScan) {
521
533
  if (!existsSync(dir))
522
534
  continue;
@@ -528,13 +540,11 @@ async function deleteStaleFiles(generatedPaths, dirsToScan) {
528
540
  continue;
529
541
  const fullPath = join(dir, entry.name);
530
542
  if (!generatedPaths.has(fullPath)) {
531
- await unlink(fullPath);
532
- deleted++;
533
- filesDeleted.push(fullPath);
543
+ stale.push(fullPath);
534
544
  }
535
545
  }
536
546
  }
537
- return { deleted, filesDeleted };
547
+ return stale;
538
548
  }
539
549
  var pascal = (s) => s.split(/[_\s-]+/).map((w) => w?.[0] ? w[0].toUpperCase() + w.slice(1) : "").join("");
540
550
  var init_utils = () => {};
@@ -1413,6 +1423,50 @@ var init_emit_sdk_contract = __esm(() => {
1413
1423
  init_emit_include_methods();
1414
1424
  });
1415
1425
 
1426
+ // src/cli-utils.ts
1427
+ import { unlink as unlink2 } from "fs/promises";
1428
+ import prompts from "prompts";
1429
+ function parseForceFlag(args) {
1430
+ return args.includes("--force") || args.includes("-y") || args.includes("--yes");
1431
+ }
1432
+ async function confirmAndDeleteStaleFiles(staleFiles, force) {
1433
+ if (staleFiles.length === 0)
1434
+ return { deleted: 0, skipped: 0, filesDeleted: [] };
1435
+ if (!force && !process.stdout.isTTY) {
1436
+ console.log(`⚠️ ${staleFiles.length} stale file(s) not deleted (non-interactive shell). Re-run with --force to delete.`);
1437
+ return { deleted: 0, skipped: staleFiles.length, filesDeleted: [] };
1438
+ }
1439
+ let deleted = 0;
1440
+ let skipped = 0;
1441
+ const filesDeleted = [];
1442
+ for (const filePath of staleFiles) {
1443
+ let shouldDelete = force;
1444
+ if (!force) {
1445
+ const { confirmed } = await prompts({
1446
+ type: "confirm",
1447
+ name: "confirmed",
1448
+ message: `Delete stale file: ${filePath}?`,
1449
+ initial: false
1450
+ });
1451
+ if (confirmed === undefined) {
1452
+ console.log("Stale file deletion aborted.");
1453
+ break;
1454
+ }
1455
+ shouldDelete = confirmed;
1456
+ }
1457
+ if (shouldDelete) {
1458
+ await unlink2(filePath);
1459
+ console.log(` ✗ ${filePath}`);
1460
+ filesDeleted.push(filePath);
1461
+ deleted++;
1462
+ } else {
1463
+ skipped++;
1464
+ }
1465
+ }
1466
+ return { deleted, skipped, filesDeleted };
1467
+ }
1468
+ var init_cli_utils = () => {};
1469
+
1416
1470
  // src/index.ts
1417
1471
  var import_config = __toESM(require_config(), 1);
1418
1472
  import { join as join2, relative, dirname as dirname2 } from "node:path";
@@ -6121,6 +6175,7 @@ function generateTestCases(table, sampleData, updateData, hasForeignKeys = false
6121
6175
  // src/index.ts
6122
6176
  init_emit_sdk_contract();
6123
6177
  init_utils();
6178
+ init_cli_utils();
6124
6179
  var __filename2 = fileURLToPath(import.meta.url);
6125
6180
  var __dirname2 = dirname2(__filename2);
6126
6181
  var { version: CLI_VERSION } = JSON.parse(readFileSync(join2(__dirname2, "../package.json"), "utf-8"));
@@ -6130,7 +6185,7 @@ function resolveSoftDeleteColumn(cfg, tableName) {
6130
6185
  return overrides[tableName] ?? null;
6131
6186
  return cfg.softDeleteColumn ?? null;
6132
6187
  }
6133
- async function generate(configPath) {
6188
+ async function generate(configPath, options) {
6134
6189
  if (!existsSync2(configPath)) {
6135
6190
  throw new Error(`Config file not found: ${configPath}
6136
6191
 
@@ -6325,7 +6380,7 @@ async function generate(configPath) {
6325
6380
  }
6326
6381
  console.log("✍️ Writing files...");
6327
6382
  const writeResult = await writeFilesIfChanged(files);
6328
- let deleteResult = { deleted: 0, filesDeleted: [] };
6383
+ let deleteResult = { deleted: 0, skipped: 0, filesDeleted: [] };
6329
6384
  if (cfg.clean !== false) {
6330
6385
  const dirsToScan = [
6331
6386
  serverDir,
@@ -6341,9 +6396,10 @@ async function generate(configPath) {
6341
6396
  if (generateTests)
6342
6397
  dirsToScan.push(testDir);
6343
6398
  const generatedPaths = new Set(files.map((f) => f.path));
6344
- deleteResult = await deleteStaleFiles(generatedPaths, dirsToScan);
6399
+ const staleFiles = await findStaleFiles(generatedPaths, dirsToScan);
6400
+ deleteResult = await confirmAndDeleteStaleFiles(staleFiles, options?.force ?? false);
6345
6401
  }
6346
- if (writeResult.written === 0 && deleteResult.deleted === 0) {
6402
+ if (writeResult.written === 0 && deleteResult.deleted === 0 && deleteResult.skipped === 0) {
6347
6403
  console.log(`✅ All ${writeResult.unchanged} files up-to-date (no changes)`);
6348
6404
  } else {
6349
6405
  const parts = [];
@@ -6351,6 +6407,8 @@ async function generate(configPath) {
6351
6407
  parts.push(`updated ${writeResult.written} files`);
6352
6408
  if (deleteResult.deleted > 0)
6353
6409
  parts.push(`deleted ${deleteResult.deleted} stale files`);
6410
+ if (deleteResult.skipped > 0)
6411
+ parts.push(`skipped ${deleteResult.skipped} stale files`);
6354
6412
  if (writeResult.unchanged > 0)
6355
6413
  parts.push(`${writeResult.unchanged} unchanged`);
6356
6414
  console.log(`✅ ${parts.join(", ")}`);
package/dist/utils.d.ts CHANGED
@@ -20,6 +20,16 @@ export declare function writeFilesIfChanged(files: Array<{
20
20
  */
21
21
  export declare function hashContent(content: string): string;
22
22
  export declare function ensureDirs(dirs: string[]): Promise<void>;
23
+ /**
24
+ * Recursively collect all subdirectory paths under `root`, including root itself.
25
+ * Returns an empty array if `root` does not exist.
26
+ */
27
+ export declare function collectDirsRecursively(root: string): Promise<string[]>;
28
+ /**
29
+ * Find files in the given directories that are not in the set of generated paths,
30
+ * without deleting them. Used to identify stale files before prompting for confirmation.
31
+ */
32
+ export declare function findStaleFiles(generatedPaths: Set<string>, dirsToScan: string[]): Promise<string[]>;
23
33
  /**
24
34
  * Delete files in the given directories that are not in the set of generated paths.
25
35
  * Used to remove stale files for tables that no longer exist in the schema.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postgresdk",
3
- "version": "0.18.26",
3
+ "version": "0.18.28",
4
4
  "description": "Generate a typed server/client SDK from a Postgres schema (includes, Zod, Hono).",
5
5
  "type": "module",
6
6
  "bin": {