deploro 0.7.0 → 0.8.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 +256 -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) {
@@ -905,7 +1140,7 @@ async function resolveDomainId(projectId, idOrHostname) {
905
1140
  }
906
1141
 
907
1142
  // src/commands/zones.ts
908
- import { writeFileSync as writeFileSync3 } from "fs";
1143
+ import { writeFileSync as writeFileSync4 } from "fs";
909
1144
  function zonesPath() {
910
1145
  return `/api/projects/${requireProject().projectId}/zones`;
911
1146
  }
@@ -975,7 +1210,7 @@ async function zonesRemoveCommand(domain, opts) {
975
1210
  });
976
1211
  if (data.zone_file) {
977
1212
  const file = `${z.name}.zone.txt`;
978
- writeFileSync3(file, data.zone_file);
1213
+ writeFileSync4(file, data.zone_file);
979
1214
  console.log(`Saved its records to ${file}`);
980
1215
  }
981
1216
  console.log(`\u2713 ${z.name} removed`);
@@ -1170,7 +1405,7 @@ async function zonesUnforwardCommand(address) {
1170
1405
  }
1171
1406
 
1172
1407
  // src/commands/storage.ts
1173
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync4 } from "fs";
1408
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync5 } from "fs";
1174
1409
  import { basename } from "path";
1175
1410
  function authHeaders2(config) {
1176
1411
  return { Authorization: `Bearer ${config.accessToken}` };
@@ -1213,27 +1448,27 @@ async function storageUploadCommand(filePath, opts) {
1213
1448
  const data = await res.json();
1214
1449
  console.log(`\u2713 Uploaded as ${data.key} (${data.size} bytes, ${data.contentType})`);
1215
1450
  }
1216
- async function storageDownloadCommand(key, opts) {
1451
+ async function storageDownloadCommand(key2, opts) {
1217
1452
  const config = requireProject();
1218
1453
  const res = await fetch(
1219
- `${config.baseUrl}/api/projects/${config.projectId}/storage/download?key=${encodeURIComponent(key)}`,
1454
+ `${config.baseUrl}/api/projects/${config.projectId}/storage/download?key=${encodeURIComponent(key2)}`,
1220
1455
  { headers: authHeaders2(config) }
1221
1456
  );
1222
1457
  await throwOnError2(res);
1223
1458
  const bytes = new Uint8Array(await res.arrayBuffer());
1224
- const outPath = opts.out ?? basename(key);
1225
- writeFileSync4(outPath, bytes);
1459
+ const outPath = opts.out ?? basename(key2);
1460
+ writeFileSync5(outPath, bytes);
1226
1461
  console.log(`\u2713 Saved to ${outPath} (${bytes.length} bytes)`);
1227
1462
  }
1228
- async function storageDeleteCommand(key, opts) {
1463
+ async function storageDeleteCommand(key2, opts) {
1229
1464
  const config = requireProject();
1230
- await confirmOrDie(`Delete "${key}" from storage?`, opts.yes);
1465
+ await confirmOrDie(`Delete "${key2}" from storage?`, opts.yes);
1231
1466
  const res = await fetch(
1232
- `${config.baseUrl}/api/projects/${config.projectId}/storage?key=${encodeURIComponent(key)}`,
1467
+ `${config.baseUrl}/api/projects/${config.projectId}/storage?key=${encodeURIComponent(key2)}`,
1233
1468
  { method: "DELETE", headers: authHeaders2(config) }
1234
1469
  );
1235
1470
  await throwOnError2(res);
1236
- console.log(`\u2713 Deleted ${key}`);
1471
+ console.log(`\u2713 Deleted ${key2}`);
1237
1472
  }
1238
1473
  async function storagePublicCommand(mode, opts) {
1239
1474
  const config = requireProject();
@@ -1335,13 +1570,13 @@ function parseDuration(input) {
1335
1570
  const mult = { s: 1, m: 60, h: 3600, d: 86400 }[unit] ?? 1;
1336
1571
  return n * mult;
1337
1572
  }
1338
- async function storageSignCommand(key, opts) {
1573
+ async function storageSignCommand(key2, opts) {
1339
1574
  const config = requireProject();
1340
1575
  const expiresIn = opts.expires ? parseDuration(opts.expires) : void 0;
1341
1576
  const res = await fetch(`${config.baseUrl}/api/projects/${config.projectId}/storage/signed-url`, {
1342
1577
  method: "POST",
1343
1578
  headers: { ...authHeaders2(config), "Content-Type": "application/json" },
1344
- body: JSON.stringify({ key, expiresIn })
1579
+ body: JSON.stringify({ key: key2, expiresIn })
1345
1580
  });
1346
1581
  await throwOnError2(res);
1347
1582
  const data = await res.json();
@@ -1598,13 +1833,13 @@ async function hostingStatusCommand() {
1598
1833
  }
1599
1834
  console.log("");
1600
1835
  }
1601
- async function hostingSetEnvCommand(key, value) {
1836
+ async function hostingSetEnvCommand(key2, value) {
1602
1837
  requireConfig();
1603
1838
  await apiRequest("/api/hosting/env", {
1604
1839
  method: "POST",
1605
- body: JSON.stringify({ key, value })
1840
+ body: JSON.stringify({ key: key2, value })
1606
1841
  });
1607
- console.log(`\u2713 Secret '${key}' updated`);
1842
+ console.log(`\u2713 Secret '${key2}' updated`);
1608
1843
  }
1609
1844
  async function hostingEnvCommand() {
1610
1845
  requireConfig();
@@ -1942,13 +2177,13 @@ async function vpsEnvCommand() {
1942
2177
  for (const v of data.vars) console.log(` ${v.key}`);
1943
2178
  console.log("");
1944
2179
  }
1945
- async function vpsSetEnvCommand(key, value) {
2180
+ async function vpsSetEnvCommand(key2, value) {
1946
2181
  const config = requireProject();
1947
2182
  await apiRequest(`/api/projects/${config.projectId}/vps/env`, {
1948
2183
  method: "POST",
1949
- body: JSON.stringify({ key, value })
2184
+ body: JSON.stringify({ key: key2, value })
1950
2185
  });
1951
- console.log(`\u2713 VPS env var '${key}' set \u2014 run 'deploro vps deploy' to apply it`);
2186
+ console.log(`\u2713 VPS env var '${key2}' set \u2014 run 'deploro vps deploy' to apply it`);
1952
2187
  }
1953
2188
  async function vpsDeployCommand() {
1954
2189
  const config = requireProject();
@@ -2086,6 +2321,8 @@ program.command("create <name>").description("Create a new project (and set it a
2086
2321
  program.command("use <project>").description("Set the active project (accepts id, slug, or name)").action(useProjectCommand);
2087
2322
  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
2323
  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);
2324
+ var gen = program.command("gen").description("Generate code from the active project");
2325
+ 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
2326
  var db = program.command("db").description("Database commands");
2090
2327
  db.command("create").description("Provision the active project's database (idempotent)").action(dbCreateCommand);
2091
2328
  db.command("info").description("Show database status and how to reach it from a deployed app").action(dbInfoCommand);
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.8.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
+ }