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.
- package/dist/index.js +287 -19
- 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
|
|
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
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
1225
|
-
|
|
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(
|
|
1493
|
+
async function storageDeleteCommand(key2, opts) {
|
|
1229
1494
|
const config = requireProject();
|
|
1230
|
-
await confirmOrDie(`Delete "${
|
|
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(
|
|
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 ${
|
|
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(
|
|
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(
|
|
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 '${
|
|
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(
|
|
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 '${
|
|
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.
|
|
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
|
+
}
|