deploro 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.
Files changed (2) hide show
  1. package/dist/index.js +287 -19
  2. package/package.json +44 -44
package/dist/index.js CHANGED
@@ -230,6 +230,10 @@ Downloading ${keys.length} file(s) to ${opts.storage} \u2026`);
230
230
  }
231
231
  }
232
232
 
233
+ // src/commands/gen.ts
234
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
235
+ import { dirname as dirname3 } from "path";
236
+
233
237
  // src/lib/api.ts
234
238
  function errorMessage(status, body, statusText) {
235
239
  const parts = [body.error ?? statusText];
@@ -268,6 +272,237 @@ async function apiRequestNoProject(path, options) {
268
272
  return res.json();
269
273
  }
270
274
 
275
+ // src/lib/gen-types.ts
276
+ var STRING_NUMERIC = /* @__PURE__ */ new Set(["int8", "numeric", "money"]);
277
+ var NUMBER = /* @__PURE__ */ new Set(["int2", "int4", "float4", "float8", "oid", "xid", "cid", "regclass", "regproc", "regtype"]);
278
+ var STRING = /* @__PURE__ */ new Set([
279
+ "text",
280
+ "varchar",
281
+ "bpchar",
282
+ "char",
283
+ "name",
284
+ "citext",
285
+ "uuid",
286
+ "inet",
287
+ "cidr",
288
+ "macaddr",
289
+ "macaddr8",
290
+ "date",
291
+ "time",
292
+ "timetz",
293
+ "timestamp",
294
+ "timestamptz",
295
+ "interval",
296
+ "xml",
297
+ "tsvector",
298
+ "tsquery",
299
+ "bit",
300
+ "varbit",
301
+ "point",
302
+ "line",
303
+ "lseg",
304
+ "box",
305
+ "path",
306
+ "polygon",
307
+ "circle",
308
+ "ltree"
309
+ ]);
310
+ var DATA_TYPE_TO_UDT = {
311
+ smallint: "int2",
312
+ integer: "int4",
313
+ bigint: "int8",
314
+ real: "float4",
315
+ "double precision": "float8",
316
+ numeric: "numeric",
317
+ money: "money",
318
+ boolean: "bool",
319
+ text: "text",
320
+ "character varying": "varchar",
321
+ character: "bpchar",
322
+ uuid: "uuid",
323
+ json: "json",
324
+ jsonb: "jsonb",
325
+ date: "date",
326
+ bytea: "bytea",
327
+ "timestamp without time zone": "timestamp",
328
+ "timestamp with time zone": "timestamptz",
329
+ "time without time zone": "time",
330
+ "time with time zone": "timetz",
331
+ interval: "interval"
332
+ };
333
+ var IDENT_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
334
+ function key(name) {
335
+ return IDENT_RE.test(name) ? name : JSON.stringify(name);
336
+ }
337
+ function literal(value) {
338
+ return JSON.stringify(value);
339
+ }
340
+ function udtOf(prop) {
341
+ if (prop["x-pg-type"]) return prop["x-pg-type"];
342
+ const dataType = prop.description?.replace(/ NOT NULL$/, "");
343
+ return dataType ? DATA_TYPE_TO_UDT[dataType] ?? null : null;
344
+ }
345
+ function tsType(udt, enums, write, prop) {
346
+ if (udt?.startsWith("_")) {
347
+ const inner = tsType(udt.slice(1), enums, write);
348
+ return /[|\s]/.test(inner) ? `(${inner})[]` : `${inner}[]`;
349
+ }
350
+ if (udt && enums[udt]) return `Database["public"]["Enums"][${literal(udt)}]`;
351
+ if (udt === "bool") return "boolean";
352
+ if (udt === "json" || udt === "jsonb") return "Json";
353
+ if (udt && NUMBER.has(udt)) return "number";
354
+ if (udt && STRING_NUMERIC.has(udt)) return write ? "number | string" : "string";
355
+ if (udt && STRING.has(udt)) return "string";
356
+ if (udt === "void") return "undefined";
357
+ if (prop?.enum?.length) return prop.enum.map(literal).join(" | ");
358
+ const t = Array.isArray(prop?.type) ? null : prop?.type;
359
+ if (t === "number" || t === "integer") return "number";
360
+ if (t === "boolean") return "boolean";
361
+ if (t === "string") return "string";
362
+ if (t === "array") return "unknown[]";
363
+ return "unknown";
364
+ }
365
+ function generateTypes(spec) {
366
+ const schemas = spec.components?.schemas ?? {};
367
+ const enums = spec.components?.["x-pg-enums"] ?? {};
368
+ const tableNames = Object.keys(schemas).sort();
369
+ let legacySpec = false;
370
+ const tableBlocks = [];
371
+ for (const table2 of tableNames) {
372
+ const schema = schemas[table2];
373
+ const required = new Set(schema.required ?? []);
374
+ const row = [];
375
+ const insert = [];
376
+ const update = [];
377
+ for (const [col, prop] of Object.entries(schema.properties ?? {})) {
378
+ if (!("x-pg-type" in prop)) legacySpec = true;
379
+ const nullable = prop.nullable === true || !required.has(col);
380
+ const udt = udtOf(prop);
381
+ const readType = tsType(udt, enums, false, prop) + (nullable ? " | null" : "");
382
+ const writeType = tsType(udt, enums, true, prop) + (nullable ? " | null" : "");
383
+ const neverWritten = prop["x-pg-generated"] === true || prop["x-pg-identity"] === "always";
384
+ const optionalOnInsert = nullable || prop["x-pg-has-default"] === true;
385
+ row.push(` ${key(col)}: ${readType}`);
386
+ if (neverWritten) {
387
+ insert.push(` ${key(col)}?: never`);
388
+ update.push(` ${key(col)}?: never`);
389
+ } else {
390
+ insert.push(` ${key(col)}${optionalOnInsert ? "?" : ""}: ${writeType}`);
391
+ update.push(` ${key(col)}?: ${writeType}`);
392
+ }
393
+ }
394
+ const body = (lines) => lines.length ? `{
395
+ ${lines.join("\n")}
396
+ }` : "Record<string, never>";
397
+ tableBlocks.push(
398
+ ` ${key(table2)}: {
399
+ Row: ${body(row)}
400
+ Insert: ${body(insert)}
401
+ Update: ${body(update)}
402
+ }`
403
+ );
404
+ }
405
+ const functionBlocks = [];
406
+ const fnEntries = Object.entries(spec.paths ?? {}).map(([path, ops]) => ({ name: path.match(/\/studio\/rpc\/([^/]+)$/)?.[1], op: ops.post })).filter((f) => !!f.name && !!f.op).filter((f) => !["trigger", "event_trigger"].includes(f.op["x-pg-returns"]?.type ?? "")).sort((a, b) => a.name.localeCompare(b.name));
407
+ for (const { name, op } of fnEntries) {
408
+ const props = op.requestBody?.content?.["application/json"]?.schema?.properties ?? {};
409
+ const args = Object.entries(props).map(([arg, prop]) => {
410
+ const optional = prop["x-pg-has-default"] === true;
411
+ return ` ${key(arg)}${optional ? "?" : ""}: ${tsType(udtOf(prop), enums, true, prop)}`;
412
+ });
413
+ const ret = op["x-pg-returns"];
414
+ let returns;
415
+ if (!ret) {
416
+ legacySpec = true;
417
+ returns = "unknown";
418
+ } else {
419
+ let single;
420
+ if (ret.columns?.length) {
421
+ single = `{
422
+ ${ret.columns.map((c) => ` ${key(c.name)}: ${tsType(c.type, enums, false)} | null`).join("\n")}
423
+ }`;
424
+ } else if (ret.type && schemas[ret.type]) {
425
+ single = `Database["public"]["Tables"][${literal(ret.type)}]["Row"]`;
426
+ } else {
427
+ single = tsType(ret.type, enums, false);
428
+ }
429
+ returns = ret.set ? /[|\s]/.test(single) && !single.startsWith("{") ? `(${single})[]` : `${single}[]` : single;
430
+ }
431
+ functionBlocks.push(
432
+ ` ${key(name)}: {
433
+ Args: ${args.length ? `{
434
+ ${args.join("\n")}
435
+ }` : "Record<PropertyKey, never>"}
436
+ Returns: ${returns}
437
+ }`
438
+ );
439
+ }
440
+ const enumBlocks = Object.keys(enums).sort().map(
441
+ (name) => ` ${key(name)}: ${enums[name].length ? enums[name].map(literal).join(" | ") : "never"}`
442
+ );
443
+ const section = (blocks) => blocks.length ? `{
444
+ ${blocks.join("\n")}
445
+ }` : "{\n [_ in never]: never\n }";
446
+ const source = `// Generated by \`deploro gen types\`. Do not edit by hand; re-run after each migration.
447
+ //
448
+ // bigint, numeric and money columns are strings in Row: Deploro returns them as
449
+ // strings so large values keep their precision. Insert and Update accept either.
450
+
451
+ export type Json =
452
+ | string
453
+ | number
454
+ | boolean
455
+ | null
456
+ | { [key: string]: Json | undefined }
457
+ | Json[]
458
+
459
+ export type Database = {
460
+ public: {
461
+ Tables: ${section(tableBlocks)}
462
+ Functions: ${section(functionBlocks)}
463
+ Enums: ${section(enumBlocks)}
464
+ }
465
+ }
466
+
467
+ type PublicSchema = Database["public"]
468
+
469
+ export type Tables<T extends keyof PublicSchema["Tables"]> = PublicSchema["Tables"][T]["Row"]
470
+ export type TablesInsert<T extends keyof PublicSchema["Tables"]> = PublicSchema["Tables"][T]["Insert"]
471
+ export type TablesUpdate<T extends keyof PublicSchema["Tables"]> = PublicSchema["Tables"][T]["Update"]
472
+ export type Enums<T extends keyof PublicSchema["Enums"]> = PublicSchema["Enums"][T]
473
+ export type FunctionArgs<T extends keyof PublicSchema["Functions"]> = PublicSchema["Functions"][T]["Args"]
474
+ export type FunctionReturns<T extends keyof PublicSchema["Functions"]> = PublicSchema["Functions"][T]["Returns"]
475
+ `;
476
+ return {
477
+ source,
478
+ tables: tableNames.length,
479
+ functions: fnEntries.length,
480
+ enums: enumBlocks.length,
481
+ legacySpec
482
+ };
483
+ }
484
+
485
+ // src/commands/gen.ts
486
+ async function genTypesCommand(opts) {
487
+ const config = requireProject();
488
+ const spec = await apiRequest(`/api/projects/${config.projectId}/studio/spec`);
489
+ const result = generateTypes(spec);
490
+ if (opts.out) {
491
+ mkdirSync3(dirname3(opts.out) || ".", { recursive: true });
492
+ writeFileSync3(opts.out, result.source, "utf8");
493
+ console.error(
494
+ `Wrote ${opts.out}: ${result.tables} table(s), ${result.functions} function(s), ${result.enums} enum(s)`
495
+ );
496
+ } else {
497
+ process.stdout.write(result.source);
498
+ }
499
+ if (result.legacySpec) {
500
+ console.error(
501
+ "Warning: this server's spec has no column defaults or function return types, so every NOT NULL column is required in Insert and function Returns are `unknown`. They'll be exact once the server is updated."
502
+ );
503
+ }
504
+ }
505
+
271
506
  // src/lib/confirm.ts
272
507
  import { createInterface as createInterface2 } from "readline";
273
508
  async function confirmOrDie(question, yes) {
@@ -444,6 +679,36 @@ async function dbDropTableCommand(tableName, opts) {
444
679
  await apiRequest(`/api/db/tables/${encodeURIComponent(tableName)}`, { method: "DELETE" });
445
680
  console.log(`\u2713 Table "${tableName}" dropped`);
446
681
  }
682
+ var SEVERITY_LABEL = {
683
+ error: "SECURITY",
684
+ warning: "WARNING",
685
+ info: "SUGGEST"
686
+ };
687
+ async function dbAdvisorsCommand(opts) {
688
+ const config = requireProject();
689
+ const data = await apiRequestNoProject(
690
+ `/api/projects/${config.projectId}/database/advisors`
691
+ );
692
+ if (opts.json) {
693
+ json(data.findings);
694
+ return;
695
+ }
696
+ if (data.findings.length === 0) {
697
+ console.log("\u2713 No problems found. RLS, primary keys and foreign-key indexes all look right.");
698
+ return;
699
+ }
700
+ for (const f of data.findings) {
701
+ console.log(`
702
+ [${SEVERITY_LABEL[f.severity]}] ${f.table}: ${f.title}`);
703
+ console.log(` ${f.detail}`);
704
+ if (f.fix) console.log(` Fix: ${f.fix}`);
705
+ }
706
+ const errors = data.findings.filter((f) => f.severity === "error").length;
707
+ console.log(`
708
+ ${data.findings.length} finding${data.findings.length === 1 ? "" : "s"}${errors ? `, ${errors} security` : ""}.`);
709
+ console.log("Fix SQL is never run for you. Apply it as a migration (deploro migrate).");
710
+ if (errors) process.exitCode = 1;
711
+ }
447
712
 
448
713
  // src/commands/auth.ts
449
714
  function isPatToken(accessToken) {
@@ -905,7 +1170,7 @@ async function resolveDomainId(projectId, idOrHostname) {
905
1170
  }
906
1171
 
907
1172
  // src/commands/zones.ts
908
- import { writeFileSync as writeFileSync3 } from "fs";
1173
+ import { writeFileSync as writeFileSync4 } from "fs";
909
1174
  function zonesPath() {
910
1175
  return `/api/projects/${requireProject().projectId}/zones`;
911
1176
  }
@@ -975,7 +1240,7 @@ async function zonesRemoveCommand(domain, opts) {
975
1240
  });
976
1241
  if (data.zone_file) {
977
1242
  const file = `${z.name}.zone.txt`;
978
- writeFileSync3(file, data.zone_file);
1243
+ writeFileSync4(file, data.zone_file);
979
1244
  console.log(`Saved its records to ${file}`);
980
1245
  }
981
1246
  console.log(`\u2713 ${z.name} removed`);
@@ -1170,7 +1435,7 @@ async function zonesUnforwardCommand(address) {
1170
1435
  }
1171
1436
 
1172
1437
  // src/commands/storage.ts
1173
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync4 } from "fs";
1438
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync5 } from "fs";
1174
1439
  import { basename } from "path";
1175
1440
  function authHeaders2(config) {
1176
1441
  return { Authorization: `Bearer ${config.accessToken}` };
@@ -1213,27 +1478,27 @@ async function storageUploadCommand(filePath, opts) {
1213
1478
  const data = await res.json();
1214
1479
  console.log(`\u2713 Uploaded as ${data.key} (${data.size} bytes, ${data.contentType})`);
1215
1480
  }
1216
- async function storageDownloadCommand(key, opts) {
1481
+ async function storageDownloadCommand(key2, opts) {
1217
1482
  const config = requireProject();
1218
1483
  const res = await fetch(
1219
- `${config.baseUrl}/api/projects/${config.projectId}/storage/download?key=${encodeURIComponent(key)}`,
1484
+ `${config.baseUrl}/api/projects/${config.projectId}/storage/download?key=${encodeURIComponent(key2)}`,
1220
1485
  { headers: authHeaders2(config) }
1221
1486
  );
1222
1487
  await throwOnError2(res);
1223
1488
  const bytes = new Uint8Array(await res.arrayBuffer());
1224
- const outPath = opts.out ?? basename(key);
1225
- writeFileSync4(outPath, bytes);
1489
+ const outPath = opts.out ?? basename(key2);
1490
+ writeFileSync5(outPath, bytes);
1226
1491
  console.log(`\u2713 Saved to ${outPath} (${bytes.length} bytes)`);
1227
1492
  }
1228
- async function storageDeleteCommand(key, opts) {
1493
+ async function storageDeleteCommand(key2, opts) {
1229
1494
  const config = requireProject();
1230
- await confirmOrDie(`Delete "${key}" from storage?`, opts.yes);
1495
+ await confirmOrDie(`Delete "${key2}" from storage?`, opts.yes);
1231
1496
  const res = await fetch(
1232
- `${config.baseUrl}/api/projects/${config.projectId}/storage?key=${encodeURIComponent(key)}`,
1497
+ `${config.baseUrl}/api/projects/${config.projectId}/storage?key=${encodeURIComponent(key2)}`,
1233
1498
  { method: "DELETE", headers: authHeaders2(config) }
1234
1499
  );
1235
1500
  await throwOnError2(res);
1236
- console.log(`\u2713 Deleted ${key}`);
1501
+ console.log(`\u2713 Deleted ${key2}`);
1237
1502
  }
1238
1503
  async function storagePublicCommand(mode, opts) {
1239
1504
  const config = requireProject();
@@ -1335,13 +1600,13 @@ function parseDuration(input) {
1335
1600
  const mult = { s: 1, m: 60, h: 3600, d: 86400 }[unit] ?? 1;
1336
1601
  return n * mult;
1337
1602
  }
1338
- async function storageSignCommand(key, opts) {
1603
+ async function storageSignCommand(key2, opts) {
1339
1604
  const config = requireProject();
1340
1605
  const expiresIn = opts.expires ? parseDuration(opts.expires) : void 0;
1341
1606
  const res = await fetch(`${config.baseUrl}/api/projects/${config.projectId}/storage/signed-url`, {
1342
1607
  method: "POST",
1343
1608
  headers: { ...authHeaders2(config), "Content-Type": "application/json" },
1344
- body: JSON.stringify({ key, expiresIn })
1609
+ body: JSON.stringify({ key: key2, expiresIn })
1345
1610
  });
1346
1611
  await throwOnError2(res);
1347
1612
  const data = await res.json();
@@ -1598,13 +1863,13 @@ async function hostingStatusCommand() {
1598
1863
  }
1599
1864
  console.log("");
1600
1865
  }
1601
- async function hostingSetEnvCommand(key, value) {
1866
+ async function hostingSetEnvCommand(key2, value) {
1602
1867
  requireConfig();
1603
1868
  await apiRequest("/api/hosting/env", {
1604
1869
  method: "POST",
1605
- body: JSON.stringify({ key, value })
1870
+ body: JSON.stringify({ key: key2, value })
1606
1871
  });
1607
- console.log(`\u2713 Secret '${key}' updated`);
1872
+ console.log(`\u2713 Secret '${key2}' updated`);
1608
1873
  }
1609
1874
  async function hostingEnvCommand() {
1610
1875
  requireConfig();
@@ -1942,13 +2207,13 @@ async function vpsEnvCommand() {
1942
2207
  for (const v of data.vars) console.log(` ${v.key}`);
1943
2208
  console.log("");
1944
2209
  }
1945
- async function vpsSetEnvCommand(key, value) {
2210
+ async function vpsSetEnvCommand(key2, value) {
1946
2211
  const config = requireProject();
1947
2212
  await apiRequest(`/api/projects/${config.projectId}/vps/env`, {
1948
2213
  method: "POST",
1949
- body: JSON.stringify({ key, value })
2214
+ body: JSON.stringify({ key: key2, value })
1950
2215
  });
1951
- console.log(`\u2713 VPS env var '${key}' set \u2014 run 'deploro vps deploy' to apply it`);
2216
+ console.log(`\u2713 VPS env var '${key2}' set \u2014 run 'deploro vps deploy' to apply it`);
1952
2217
  }
1953
2218
  async function vpsDeployCommand() {
1954
2219
  const config = requireProject();
@@ -2086,12 +2351,15 @@ program.command("create <name>").description("Create a new project (and set it a
2086
2351
  program.command("use <project>").description("Set the active project (accepts id, slug, or name)").action(useProjectCommand);
2087
2352
  program.command("delete <project>").description("Delete a project and its database (accepts id, slug, or name)").option("-y, --yes", "Skip confirmation prompt").action(projectsDeleteCommand);
2088
2353
  program.command("export").description("Export the active project's database and storage manifest (NDJSON)").option("--out <file>", "Where to write the export (default: deploro-export-<date>.ndjson)").option("--storage <dir>", "Also download stored files into this directory").option("--skip-storage", "Skip the object-storage listing entirely").action(exportCommand);
2354
+ var gen = program.command("gen").description("Generate code from the active project");
2355
+ gen.command("types").description("TypeScript types for the project's tables, functions and enums (prints to stdout)").option("--out <file>", "Write to this file instead of stdout").action(genTypesCommand);
2089
2356
  var db = program.command("db").description("Database commands");
2090
2357
  db.command("create").description("Provision the active project's database (idempotent)").action(dbCreateCommand);
2091
2358
  db.command("info").description("Show database status and how to reach it from a deployed app").action(dbInfoCommand);
2092
2359
  db.command("tables").description("List tables in the active project").action(dbTablesCommand);
2093
2360
  db.command("rows <table>").description("List rows in a table").option("--limit <n>", "Rows per page (max 200)", "20").option("--page <n>", "Page number", "1").option("--json", "Output raw JSON").action(dbRowsCommand);
2094
2361
  db.command("query <sql>").description("Run a read-only SQL query (project admin)").option("--param <value>", "Bind a value to $1, $2, ... in order (repeatable)", (v, prev) => [...prev, v], []).option("--json", "Output raw JSON").action(dbQueryCommand);
2362
+ db.command("advisors").description("Run database checks: RLS exposure, missing policies, primary keys, foreign-key indexes").option("--json", "Output raw JSON").action(dbAdvisorsCommand);
2095
2363
  db.command("schema <table>").description("Show schema for a table").action(dbSchemaCommand);
2096
2364
  db.command("insert <table>").description("Insert a row").requiredOption("--data <json>", "Row data as a JSON object").action(dbInsertCommand);
2097
2365
  db.command("update <table> <id>").description("Update a row by id").requiredOption("--data <json>", "Fields to update as a JSON object").action(dbUpdateCommand);
package/package.json CHANGED
@@ -1,44 +1,44 @@
1
- {
2
- "name": "deploro",
3
- "version": "0.7.0",
4
- "description": "Command-line interface for the Deploro developer platform — manage projects, databases, deployments, and hosting from your terminal.",
5
- "type": "module",
6
- "bin": {
7
- "deploro": "dist/index.js"
8
- },
9
- "files": [
10
- "dist"
11
- ],
12
- "engines": {
13
- "node": ">=18"
14
- },
15
- "keywords": [
16
- "deploro",
17
- "cli",
18
- "cloudflare",
19
- "postgres",
20
- "deployment"
21
- ],
22
- "license": "MIT",
23
- "repository": {
24
- "type": "git",
25
- "url": "git+https://github.com/yawdeals1/deploro.git",
26
- "directory": "packages/cli"
27
- },
28
- "homepage": "https://deploro.com",
29
- "publishConfig": {
30
- "access": "public"
31
- },
32
- "scripts": {
33
- "build": "tsup src/index.ts --format esm --no-splitting --target node18 --out-dir dist",
34
- "dev": "tsup src/index.ts --format esm --no-splitting --target node18 --out-dir dist --watch"
35
- },
36
- "dependencies": {
37
- "commander": "^13.0.0"
38
- },
39
- "devDependencies": {
40
- "@types/node": "^22.0.0",
41
- "tsup": "^8.0.0",
42
- "typescript": "^5.5.0"
43
- }
44
- }
1
+ {
2
+ "name": "deploro",
3
+ "version": "0.9.0",
4
+ "description": "Command-line interface for the Deploro developer platform — manage projects, databases, deployments, and hosting from your terminal.",
5
+ "type": "module",
6
+ "bin": {
7
+ "deploro": "dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "engines": {
13
+ "node": ">=18"
14
+ },
15
+ "keywords": [
16
+ "deploro",
17
+ "cli",
18
+ "cloudflare",
19
+ "postgres",
20
+ "deployment"
21
+ ],
22
+ "license": "MIT",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/yawdeals1/deploro.git",
26
+ "directory": "packages/cli"
27
+ },
28
+ "homepage": "https://deploro.com",
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "scripts": {
33
+ "build": "tsup src/index.ts --format esm --no-splitting --target node18 --out-dir dist",
34
+ "dev": "tsup src/index.ts --format esm --no-splitting --target node18 --out-dir dist --watch"
35
+ },
36
+ "dependencies": {
37
+ "commander": "^13.0.0"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^22.0.0",
41
+ "tsup": "^8.0.0",
42
+ "typescript": "^5.5.0"
43
+ }
44
+ }