@zveltio/cli 3.0.0-beta.77 → 3.0.0-beta.78
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 +358 -43
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -2398,7 +2398,7 @@ __export(exports_admin_marketplace, {
|
|
|
2398
2398
|
adminMarketplaceEnrollPublisher: () => adminMarketplaceEnrollPublisher,
|
|
2399
2399
|
adminMarketplaceApprove: () => adminMarketplaceApprove
|
|
2400
2400
|
});
|
|
2401
|
-
import { readFileSync as
|
|
2401
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
2402
2402
|
function getRegistryUrl(opts) {
|
|
2403
2403
|
return opts.registryUrl ?? process.env.ZVELTIO_REGISTRY_URL ?? "https://registry.zveltio.com";
|
|
2404
2404
|
}
|
|
@@ -2619,7 +2619,7 @@ async function adminMarketplaceEnrollPublisher(opts) {
|
|
|
2619
2619
|
}
|
|
2620
2620
|
let keyJwk;
|
|
2621
2621
|
try {
|
|
2622
|
-
keyJwk =
|
|
2622
|
+
keyJwk = readFileSync9(opts.keyFile, "utf8").trim();
|
|
2623
2623
|
} catch (err2) {
|
|
2624
2624
|
console.error(c15.red(`Could not read key file ${opts.keyFile}: ${err2.message}`));
|
|
2625
2625
|
process.exit(1);
|
|
@@ -3460,10 +3460,14 @@ Creating extension: ${extName}
|
|
|
3460
3460
|
slots: []
|
|
3461
3461
|
}
|
|
3462
3462
|
}, null, 2));
|
|
3463
|
-
await writeFile(join8(targetDir, "engine", "index.ts"), `import
|
|
3463
|
+
await writeFile(join8(targetDir, "engine", "index.ts"), `import { defineExtension } from '@zveltio/sdk/extension';
|
|
3464
3464
|
import { join } from 'path';
|
|
3465
3465
|
|
|
3466
|
-
|
|
3466
|
+
// The capabilities this extension uses \u2014 the same list as "permissions" in
|
|
3467
|
+
// manifest.json (\`zveltio extension validate\` compares them). It types \`ctx\`:
|
|
3468
|
+
// a ctx.internals member whose capability is not listed is a compile error,
|
|
3469
|
+
// not a refusal on the first request that needs it. Start with none.
|
|
3470
|
+
export default defineExtension([], {
|
|
3467
3471
|
name: '${extName}',
|
|
3468
3472
|
category: '${category}',
|
|
3469
3473
|
|
|
@@ -3486,9 +3490,7 @@ const extension: ZveltioExtension = {
|
|
|
3486
3490
|
return c.json({ data });
|
|
3487
3491
|
});
|
|
3488
3492
|
},
|
|
3489
|
-
};
|
|
3490
|
-
|
|
3491
|
-
export default extension;
|
|
3493
|
+
});
|
|
3492
3494
|
`);
|
|
3493
3495
|
await writeFile(join8(targetDir, "engine", "migrations", "001_init.sql"), `-- ${name} \u2014 initial schema.
|
|
3494
3496
|
--
|
|
@@ -3661,7 +3663,7 @@ Where UI goes:
|
|
|
3661
3663
|
Do NOT add studio/vite.config.ts, studio/package.json, or studio/dist/ \u2014 removed in alpha.94/beta.15.
|
|
3662
3664
|
`);
|
|
3663
3665
|
if (codePage) {
|
|
3664
|
-
console.warn(`\x1B[33mScaffolded a CODE page. Across the
|
|
3666
|
+
console.warn(`\x1B[33mScaffolded a CODE page. Across the 55 shipped extensions, 60 pages are
|
|
3665
3667
|
schemas and seven are code \u2014 and each of those seven sits beside a schema, for
|
|
3666
3668
|
the part a schema could not express. On this road you own fetching,
|
|
3667
3669
|
loading and error states, i18n by hand, and a Studio release to ship a change.
|
|
@@ -3992,6 +3994,91 @@ function emitTypeScript(schema, opts = {}) {
|
|
|
3992
3994
|
function quoteIdentifierIfNeeded(name) {
|
|
3993
3995
|
return /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name) ? name : `'${name.replace(/'/g, "\\'")}'`;
|
|
3994
3996
|
}
|
|
3997
|
+
var CORE_FIELD_TS_TYPES = {
|
|
3998
|
+
text: "string",
|
|
3999
|
+
number: "number",
|
|
4000
|
+
boolean: "boolean",
|
|
4001
|
+
date: "string",
|
|
4002
|
+
datetime: "string",
|
|
4003
|
+
email: "string",
|
|
4004
|
+
url: "string",
|
|
4005
|
+
uuid: "string",
|
|
4006
|
+
json: "Record<string, any>",
|
|
4007
|
+
richtext: "string",
|
|
4008
|
+
file: "{ url: string; name: string; size: number; type: string }",
|
|
4009
|
+
enum: "string",
|
|
4010
|
+
reference: "string",
|
|
4011
|
+
m2o: "string",
|
|
4012
|
+
location: "{ lat: number; lng: number }",
|
|
4013
|
+
computed: "never",
|
|
4014
|
+
integer: "number",
|
|
4015
|
+
float: "number",
|
|
4016
|
+
textarea: "string",
|
|
4017
|
+
password: "string",
|
|
4018
|
+
slug: "string",
|
|
4019
|
+
color: "string",
|
|
4020
|
+
phone: "string",
|
|
4021
|
+
tags: "string[]",
|
|
4022
|
+
image: "{ url: string; name: string; size: number; width?: number; height?: number; alt?: string }",
|
|
4023
|
+
o2m: "string[]",
|
|
4024
|
+
m2m: "string[]",
|
|
4025
|
+
m2a: "{ collection: string; id: string }[]",
|
|
4026
|
+
geometry: "{ type: string; coordinates: any }",
|
|
4027
|
+
vector: "number[]",
|
|
4028
|
+
smallint: "number",
|
|
4029
|
+
real: "number",
|
|
4030
|
+
decimal: "number",
|
|
4031
|
+
money: "number",
|
|
4032
|
+
varchar: "string",
|
|
4033
|
+
char: "string",
|
|
4034
|
+
time: "string",
|
|
4035
|
+
timetz: "string",
|
|
4036
|
+
interval: "string",
|
|
4037
|
+
bytea: "string",
|
|
4038
|
+
tsvector: "string",
|
|
4039
|
+
inet: "string",
|
|
4040
|
+
cidr: "string",
|
|
4041
|
+
macaddr: "string",
|
|
4042
|
+
xml: "string",
|
|
4043
|
+
bit: "string",
|
|
4044
|
+
varbit: "string",
|
|
4045
|
+
int4range: "{ lower: number; upper: number }",
|
|
4046
|
+
int8range: "{ lower: number; upper: number }",
|
|
4047
|
+
numrange: "{ lower: number; upper: number }",
|
|
4048
|
+
daterange: "{ lower: string; upper: string }",
|
|
4049
|
+
tsrange: "{ lower: string; upper: string }",
|
|
4050
|
+
tstzrange: "{ lower: string; upper: string }"
|
|
4051
|
+
};
|
|
4052
|
+
function emitCollectionTypes(collection, tsTypeOf = (t) => CORE_FIELD_TS_TYPES[t]) {
|
|
4053
|
+
const typeName = collection.name.charAt(0).toUpperCase() + collection.name.slice(1);
|
|
4054
|
+
const inputFields = collection.fields.map((f) => ` ${f.name}${f.required ? "" : "?"}: ${tsTypeOf(f.type) || "any"};`).join(`
|
|
4055
|
+
`);
|
|
4056
|
+
return `export interface ${typeName}Input {
|
|
4057
|
+
${inputFields}
|
|
4058
|
+
}
|
|
4059
|
+
|
|
4060
|
+
export interface ${typeName} extends ${typeName}Input {
|
|
4061
|
+
id: string;
|
|
4062
|
+
created_at: string;
|
|
4063
|
+
updated_at: string;
|
|
4064
|
+
status: string;
|
|
4065
|
+
}`;
|
|
4066
|
+
}
|
|
4067
|
+
function emitCollectionTypesFile(collections, tsTypeOf) {
|
|
4068
|
+
const types = collections.map((c6) => emitCollectionTypes(c6, tsTypeOf)).join(`
|
|
4069
|
+
|
|
4070
|
+
`);
|
|
4071
|
+
return `// Auto-generated by Zveltio
|
|
4072
|
+
// Do not edit manually
|
|
4073
|
+
|
|
4074
|
+
${types}`;
|
|
4075
|
+
}
|
|
4076
|
+
function collectionsFromSchemaFiles(files) {
|
|
4077
|
+
return Object.keys(files).filter((p) => /^collections\/[^/]+\.json$/.test(p)).sort().map((p) => {
|
|
4078
|
+
const file = JSON.parse(files[p]);
|
|
4079
|
+
return { name: file.name, fields: file.fields ?? [] };
|
|
4080
|
+
});
|
|
4081
|
+
}
|
|
3995
4082
|
|
|
3996
4083
|
// src/commands/extension-types.ts
|
|
3997
4084
|
var c6 = {
|
|
@@ -4350,6 +4437,32 @@ function validateManifest(input) {
|
|
|
4350
4437
|
if (typeof obj.zveltioMaxVersion === "string" && !SEMVER_RE.test(obj.zveltioMaxVersion)) {
|
|
4351
4438
|
out.push(err("MANIFEST_BAD_MAX_VERSION", `zveltioMaxVersion "${obj.zveltioMaxVersion}" is not valid semver`));
|
|
4352
4439
|
}
|
|
4440
|
+
const depNames = new Map;
|
|
4441
|
+
for (const field of ["dependencies", "optionalDependencies"]) {
|
|
4442
|
+
const list = obj[field];
|
|
4443
|
+
if (list === undefined)
|
|
4444
|
+
continue;
|
|
4445
|
+
if (!Array.isArray(list)) {
|
|
4446
|
+
out.push(err("MANIFEST_BAD_DEPENDENCY", `"${field}" must be an array of { name, minVersion? }`));
|
|
4447
|
+
continue;
|
|
4448
|
+
}
|
|
4449
|
+
for (const d of list) {
|
|
4450
|
+
const depName = d && typeof d === "object" ? d.name : undefined;
|
|
4451
|
+
const min = d && typeof d === "object" ? d.minVersion : undefined;
|
|
4452
|
+
if (typeof depName !== "string" || depName.trim() === "") {
|
|
4453
|
+
out.push(err("MANIFEST_BAD_DEPENDENCY", `"${field}" entry ${JSON.stringify(d)} has no name`));
|
|
4454
|
+
continue;
|
|
4455
|
+
}
|
|
4456
|
+
if (min !== undefined && (typeof min !== "string" || !SEMVER_RE.test(min))) {
|
|
4457
|
+
out.push(err("MANIFEST_BAD_DEPENDENCY", `"${field}" ${depName}: minVersion ${JSON.stringify(min)} is not semver`));
|
|
4458
|
+
}
|
|
4459
|
+
const seen = depNames.get(depName);
|
|
4460
|
+
if (seen) {
|
|
4461
|
+
out.push(err("MANIFEST_BAD_DEPENDENCY", `"${depName}" is listed in "${seen}" and "${field}"`));
|
|
4462
|
+
}
|
|
4463
|
+
depNames.set(depName, field);
|
|
4464
|
+
}
|
|
4465
|
+
}
|
|
4353
4466
|
if (obj.apiKeyRoutes !== undefined) {
|
|
4354
4467
|
const routes = Array.isArray(obj.apiKeyRoutes) ? obj.apiKeyRoutes : [obj.apiKeyRoutes];
|
|
4355
4468
|
for (const r of routes) {
|
|
@@ -4898,6 +5011,51 @@ function inferExpectedName(dir) {
|
|
|
4898
5011
|
}
|
|
4899
5012
|
return norm.split("/").filter(Boolean).pop() ?? "";
|
|
4900
5013
|
}
|
|
5014
|
+
function checkDeclaredCapabilities(sources, manifest) {
|
|
5015
|
+
const call = sources.map((src) => /defineExtension\s*(?:<[^>]*>)?\(\s*\[([^\]]*)\]/.exec(src)).find((m) => m !== null);
|
|
5016
|
+
if (!call)
|
|
5017
|
+
return [];
|
|
5018
|
+
const typed = new Set([...(call[1] ?? "").matchAll(/['"`]([^'"`]+)['"`]/g)].map((m) => m[1]));
|
|
5019
|
+
const raw = manifest && typeof manifest === "object" ? manifest.permissions : undefined;
|
|
5020
|
+
const legacy = new Set(["database", "settings", "network"]);
|
|
5021
|
+
const declared = new Set((Array.isArray(raw) ? raw : []).filter((p) => typeof p === "string" && !legacy.has(p)));
|
|
5022
|
+
const out = [];
|
|
5023
|
+
const onlyTyped = [...typed].filter((c8) => !declared.has(c8)).sort();
|
|
5024
|
+
const onlyManifest = [...declared].filter((c8) => !typed.has(c8)).sort();
|
|
5025
|
+
if (onlyTyped.length > 0) {
|
|
5026
|
+
out.push({
|
|
5027
|
+
code: "CAPABILITY_NOT_IN_MANIFEST",
|
|
5028
|
+
message: `defineExtension declares ${onlyTyped.join(", ")}, which manifest.json "permissions" ` + "does not: the code type-checks and the engine refuses it at runtime. Add it to the manifest.",
|
|
5029
|
+
file: "manifest.json"
|
|
5030
|
+
});
|
|
5031
|
+
}
|
|
5032
|
+
if (onlyManifest.length > 0) {
|
|
5033
|
+
out.push({
|
|
5034
|
+
code: "CAPABILITY_NOT_IN_DEFINE",
|
|
5035
|
+
message: `manifest.json "permissions" asks for ${onlyManifest.join(", ")}, which defineExtension ` + "does not declare: the administrator is asked for power the code says it does not use. " + "Remove it from the manifest, or add it to defineExtension.",
|
|
5036
|
+
file: "manifest.json"
|
|
5037
|
+
});
|
|
5038
|
+
}
|
|
5039
|
+
return out;
|
|
5040
|
+
}
|
|
5041
|
+
function readEngineTypeScript(dir) {
|
|
5042
|
+
const out = [];
|
|
5043
|
+
const walk = (d) => {
|
|
5044
|
+
if (!existsSync11(d))
|
|
5045
|
+
return;
|
|
5046
|
+
for (const name of readdirSync3(d)) {
|
|
5047
|
+
if (name === "node_modules")
|
|
5048
|
+
continue;
|
|
5049
|
+
const p = join10(d, name);
|
|
5050
|
+
if (statSync(p).isDirectory())
|
|
5051
|
+
walk(p);
|
|
5052
|
+
else if (p.endsWith(".ts") && !p.endsWith(".test.ts") && !p.endsWith(".d.ts"))
|
|
5053
|
+
out.push(readFileSync4(p, "utf8"));
|
|
5054
|
+
}
|
|
5055
|
+
};
|
|
5056
|
+
walk(join10(dir, "engine"));
|
|
5057
|
+
return out;
|
|
5058
|
+
}
|
|
4901
5059
|
function checkBundleIntegrity(dir, manifest) {
|
|
4902
5060
|
const bundle = join10(dir, "engine", "index.js");
|
|
4903
5061
|
const integrity = manifest?.integrity;
|
|
@@ -5128,6 +5286,11 @@ ${c7.bold("Extension validate")}
|
|
|
5128
5286
|
if (integrityErrors.some((e) => e.severity !== "warning"))
|
|
5129
5287
|
result.ok = false;
|
|
5130
5288
|
}
|
|
5289
|
+
const capabilityErrors = checkDeclaredCapabilities(readEngineTypeScript(dir), manifest);
|
|
5290
|
+
if (capabilityErrors.length > 0) {
|
|
5291
|
+
result.errors.push(...capabilityErrors);
|
|
5292
|
+
result.ok = false;
|
|
5293
|
+
}
|
|
5131
5294
|
const warnings = result.errors.filter((e) => e.severity === "warning");
|
|
5132
5295
|
const hardErrors = result.errors.filter((e) => e.severity !== "warning");
|
|
5133
5296
|
if (warnings.length > 0) {
|
|
@@ -5790,8 +5953,8 @@ ${c10.dim(`Ready. Edit engine/ files to trigger a hot-reload. Ctrl+C to stop.
|
|
|
5790
5953
|
}
|
|
5791
5954
|
|
|
5792
5955
|
// src/commands/generate-types.ts
|
|
5793
|
-
import { mkdirSync as mkdirSync5 } from "fs";
|
|
5794
|
-
import { dirname as dirname4 } from "path";
|
|
5956
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync5, readdirSync as readdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
5957
|
+
import { dirname as dirname4, join as join14 } from "path";
|
|
5795
5958
|
var c11 = {
|
|
5796
5959
|
bold: (s) => `\x1B[1m${s}\x1B[0m`,
|
|
5797
5960
|
green: (s) => `\x1B[32m${s}\x1B[0m`,
|
|
@@ -5800,6 +5963,8 @@ var c11 = {
|
|
|
5800
5963
|
red: (s) => `\x1B[31m${s}\x1B[0m`
|
|
5801
5964
|
};
|
|
5802
5965
|
async function generateTypesCommand(collection, opts) {
|
|
5966
|
+
if (opts.from)
|
|
5967
|
+
return typesFromFiles(collection, opts.from, opts.output || "./types/zveltio.d.ts");
|
|
5803
5968
|
const engineUrl = opts.url || process.env.ZVELTIO_URL || "http://localhost:3000";
|
|
5804
5969
|
const outputPath = opts.output || "./types/zveltio.d.ts";
|
|
5805
5970
|
console.log(`
|
|
@@ -5846,11 +6011,157 @@ ${c11.bold("Generate Types")}
|
|
|
5846
6011
|
process.exit(1);
|
|
5847
6012
|
}
|
|
5848
6013
|
}
|
|
6014
|
+
function typesFromFiles(collection, dir, outputPath) {
|
|
6015
|
+
const collectionsDir = join14(dir, "collections");
|
|
6016
|
+
if (!existsSync15(collectionsDir)) {
|
|
6017
|
+
console.error(c11.red(`No collections/ in ${dir}. Run \`zveltio schema pull\` first.`));
|
|
6018
|
+
process.exit(1);
|
|
6019
|
+
}
|
|
6020
|
+
const files = {};
|
|
6021
|
+
for (const f of readdirSync6(collectionsDir)) {
|
|
6022
|
+
if (f.endsWith(".json"))
|
|
6023
|
+
files[`collections/${f}`] = readFileSync7(join14(collectionsDir, f), "utf8");
|
|
6024
|
+
}
|
|
6025
|
+
const collections = collectionsFromSchemaFiles(files);
|
|
6026
|
+
let types;
|
|
6027
|
+
if (collection) {
|
|
6028
|
+
const one = collections.find((col) => col.name === collection);
|
|
6029
|
+
if (!one) {
|
|
6030
|
+
console.error(c11.red(`Collection ${collection} is not in ${dir}`));
|
|
6031
|
+
process.exit(1);
|
|
6032
|
+
}
|
|
6033
|
+
types = emitCollectionTypes(one);
|
|
6034
|
+
} else {
|
|
6035
|
+
types = emitCollectionTypesFile(collections);
|
|
6036
|
+
}
|
|
6037
|
+
mkdirSync5(dirname4(outputPath), { recursive: true });
|
|
6038
|
+
writeFileSync5(outputPath, types);
|
|
6039
|
+
console.log(`${c11.green("Types generated:")} ${outputPath} ${c11.dim(`from ${dir}`)}`);
|
|
6040
|
+
}
|
|
6041
|
+
|
|
6042
|
+
// src/commands/schema.ts
|
|
6043
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync6, readdirSync as readdirSync7, readFileSync as readFileSync8, rmSync, writeFileSync as writeFileSync6 } from "fs";
|
|
6044
|
+
import { dirname as dirname5, join as join15 } from "path";
|
|
6045
|
+
import { createInterface as createInterface2 } from "readline";
|
|
6046
|
+
async function schemaPullCommand(opts) {
|
|
6047
|
+
const dir = opts.dir || "./schema";
|
|
6048
|
+
const engineUrl = opts.url || process.env.ZVELTIO_URL || "http://localhost:3000";
|
|
6049
|
+
const res = await fetch(`${engineUrl}/api/admin/schema/export`, {
|
|
6050
|
+
headers: { Authorization: `Bearer ${process.env.ZVELTIO_API_KEY || ""}` }
|
|
6051
|
+
});
|
|
6052
|
+
if (!res.ok) {
|
|
6053
|
+
console.error(`Schema export failed: ${res.status} ${res.statusText}`);
|
|
6054
|
+
if (res.status === 401 || res.status === 403) {
|
|
6055
|
+
console.error(" Set ZVELTIO_API_KEY to a key of an instance admin.");
|
|
6056
|
+
}
|
|
6057
|
+
process.exit(1);
|
|
6058
|
+
}
|
|
6059
|
+
const { files } = await res.json();
|
|
6060
|
+
for (const [path, content] of Object.entries(files)) {
|
|
6061
|
+
const target = join15(dir, path);
|
|
6062
|
+
mkdirSync6(dirname5(target), { recursive: true });
|
|
6063
|
+
writeFileSync6(target, content);
|
|
6064
|
+
}
|
|
6065
|
+
const collectionsDir = join15(dir, "collections");
|
|
6066
|
+
let removed = 0;
|
|
6067
|
+
if (existsSync16(collectionsDir)) {
|
|
6068
|
+
for (const f of readdirSync7(collectionsDir)) {
|
|
6069
|
+
if (f.endsWith(".json") && !(`collections/${f}` in files)) {
|
|
6070
|
+
rmSync(join15(collectionsDir, f));
|
|
6071
|
+
removed++;
|
|
6072
|
+
}
|
|
6073
|
+
}
|
|
6074
|
+
}
|
|
6075
|
+
console.log(`Wrote ${Object.keys(files).length} files to ${dir}` + (removed ? `, removed ${removed} for collections that no longer exist` : ""));
|
|
6076
|
+
}
|
|
6077
|
+
function readSchemaDir(dir) {
|
|
6078
|
+
const files = {};
|
|
6079
|
+
for (const path of ["zveltio-schema.json", "roles.json"]) {
|
|
6080
|
+
if (existsSync16(join15(dir, path)))
|
|
6081
|
+
files[path] = readFileSync8(join15(dir, path), "utf8");
|
|
6082
|
+
}
|
|
6083
|
+
for (const sub of ["collections", "migrations"]) {
|
|
6084
|
+
const subDir = join15(dir, sub);
|
|
6085
|
+
if (!existsSync16(subDir))
|
|
6086
|
+
continue;
|
|
6087
|
+
for (const f of readdirSync7(subDir).filter((f2) => f2.endsWith(".json"))) {
|
|
6088
|
+
files[`${sub}/${f}`] = readFileSync8(join15(subDir, f), "utf8");
|
|
6089
|
+
}
|
|
6090
|
+
}
|
|
6091
|
+
return files;
|
|
6092
|
+
}
|
|
6093
|
+
async function postSchema(engineUrl, route, files, allowDestructive = false) {
|
|
6094
|
+
const res = await fetch(`${engineUrl}/api/admin/schema/${route}`, {
|
|
6095
|
+
method: "POST",
|
|
6096
|
+
headers: {
|
|
6097
|
+
Authorization: `Bearer ${process.env.ZVELTIO_API_KEY || ""}`,
|
|
6098
|
+
"Content-Type": "application/json"
|
|
6099
|
+
},
|
|
6100
|
+
body: JSON.stringify({ files, allowDestructive })
|
|
6101
|
+
});
|
|
6102
|
+
if (!res.ok) {
|
|
6103
|
+
const body = await res.json().catch(() => null);
|
|
6104
|
+
console.error(`Schema ${route} failed: ${res.status} ${body?.detail ?? res.statusText}`);
|
|
6105
|
+
if (res.status === 401 || res.status === 403) {
|
|
6106
|
+
console.error(" Set ZVELTIO_API_KEY to a key of an instance admin.");
|
|
6107
|
+
}
|
|
6108
|
+
process.exit(1);
|
|
6109
|
+
}
|
|
6110
|
+
return (await res.json()).steps;
|
|
6111
|
+
}
|
|
6112
|
+
function printSteps(steps) {
|
|
6113
|
+
const width = Math.max(...steps.map((s) => s.target.length));
|
|
6114
|
+
for (const s of steps) {
|
|
6115
|
+
console.log(`${s.change} ${s.target.padEnd(width)} ${s.action}${s.destructive ? " (destructive)" : ""}`);
|
|
6116
|
+
}
|
|
6117
|
+
const destructive = steps.filter((s) => s.destructive).length;
|
|
6118
|
+
console.log(`
|
|
6119
|
+
${steps.length} change(s)` + (destructive ? `, ${destructive} destructive` : ""));
|
|
6120
|
+
}
|
|
6121
|
+
async function schemaDiffCommand(opts) {
|
|
6122
|
+
const dir = opts.dir || "./schema";
|
|
6123
|
+
const engineUrl = opts.url || process.env.ZVELTIO_URL || "http://localhost:3000";
|
|
6124
|
+
const steps = await postSchema(engineUrl, "plan", readSchemaDir(dir));
|
|
6125
|
+
if (!steps.length) {
|
|
6126
|
+
console.log(`No changes: the instance matches ${dir}`);
|
|
6127
|
+
return;
|
|
6128
|
+
}
|
|
6129
|
+
printSteps(steps);
|
|
6130
|
+
process.exit(1);
|
|
6131
|
+
}
|
|
6132
|
+
async function schemaApplyCommand(opts) {
|
|
6133
|
+
const dir = opts.dir || "./schema";
|
|
6134
|
+
const engineUrl = opts.url || process.env.ZVELTIO_URL || "http://localhost:3000";
|
|
6135
|
+
const files = readSchemaDir(dir);
|
|
6136
|
+
const plan = await postSchema(engineUrl, "plan", files);
|
|
6137
|
+
if (!plan.length) {
|
|
6138
|
+
console.log(`No changes: the instance matches ${dir}`);
|
|
6139
|
+
return;
|
|
6140
|
+
}
|
|
6141
|
+
printSteps(plan);
|
|
6142
|
+
if (plan.some((s) => s.destructive) && !opts.allowDestructive) {
|
|
6143
|
+
console.error(`
|
|
6144
|
+
The plan has destructive steps; nothing was changed. Rerun with --allow-destructive.`);
|
|
6145
|
+
process.exit(1);
|
|
6146
|
+
}
|
|
6147
|
+
if (!opts.yes) {
|
|
6148
|
+
const rl2 = createInterface2({ input: process.stdin, output: process.stdout });
|
|
6149
|
+
const answer = await new Promise((resolve4) => rl2.question(`
|
|
6150
|
+
Apply these changes? Type "yes" to continue: `, resolve4));
|
|
6151
|
+
rl2.close();
|
|
6152
|
+
if (answer.trim().toLowerCase() !== "yes") {
|
|
6153
|
+
console.log("Cancelled; nothing was changed.");
|
|
6154
|
+
return;
|
|
6155
|
+
}
|
|
6156
|
+
}
|
|
6157
|
+
const applied = await postSchema(engineUrl, "apply", files, opts.allowDestructive);
|
|
6158
|
+
console.log(`Applied ${applied.length} change(s).`);
|
|
6159
|
+
}
|
|
5849
6160
|
|
|
5850
6161
|
// src/commands/install.ts
|
|
5851
|
-
import { existsSync as
|
|
6162
|
+
import { existsSync as existsSync17 } from "fs";
|
|
5852
6163
|
import { readFile, mkdir as mkdir2, cp } from "fs/promises";
|
|
5853
|
-
import { join as
|
|
6164
|
+
import { join as join16 } from "path";
|
|
5854
6165
|
async function installCommand(name, opts) {
|
|
5855
6166
|
const engineUrl = opts.url || process.env.ENGINE_URL || "http://localhost:3000";
|
|
5856
6167
|
console.log(`
|
|
@@ -5863,13 +6174,13 @@ async function installCommand(name, opts) {
|
|
|
5863
6174
|
await installFromCatalog(name, engineUrl);
|
|
5864
6175
|
}
|
|
5865
6176
|
async function installFromPath(name, sourcePath, engineUrl, force) {
|
|
5866
|
-
if (!
|
|
6177
|
+
if (!existsSync17(join16(sourcePath, "manifest.json"))) {
|
|
5867
6178
|
console.error("\u274C No manifest.json found at", sourcePath);
|
|
5868
6179
|
process.exit(1);
|
|
5869
6180
|
}
|
|
5870
6181
|
let manifest;
|
|
5871
6182
|
try {
|
|
5872
|
-
manifest = JSON.parse(await readFile(
|
|
6183
|
+
manifest = JSON.parse(await readFile(join16(sourcePath, "manifest.json"), "utf-8"));
|
|
5873
6184
|
} catch {
|
|
5874
6185
|
console.error("\u274C Failed to read manifest.json");
|
|
5875
6186
|
process.exit(1);
|
|
@@ -5893,8 +6204,8 @@ async function installFromPath(name, sourcePath, engineUrl, force) {
|
|
|
5893
6204
|
console.log(` Name: ${manifest.displayName || manifest.name}`);
|
|
5894
6205
|
console.log(` Version: ${manifest.version || "?"}`);
|
|
5895
6206
|
console.log(` Category: ${category}`);
|
|
5896
|
-
const enginePkgPath =
|
|
5897
|
-
if (
|
|
6207
|
+
const enginePkgPath = join16(process.cwd(), "packages/engine/package.json");
|
|
6208
|
+
if (existsSync17(enginePkgPath)) {
|
|
5898
6209
|
const enginePkg = JSON.parse(await readFile(enginePkgPath, "utf-8"));
|
|
5899
6210
|
const engineVersion = enginePkg.version || "unknown";
|
|
5900
6211
|
if (manifest.zveltioMinVersion) {
|
|
@@ -5905,8 +6216,8 @@ async function installFromPath(name, sourcePath, engineUrl, force) {
|
|
|
5905
6216
|
}
|
|
5906
6217
|
}
|
|
5907
6218
|
const extName = manifest.name;
|
|
5908
|
-
const targetDir =
|
|
5909
|
-
if (
|
|
6219
|
+
const targetDir = join16(process.cwd(), "extensions", category, extName);
|
|
6220
|
+
if (existsSync17(targetDir) && !force) {
|
|
5910
6221
|
console.error(`\u274C Extension already exists at ${targetDir}`);
|
|
5911
6222
|
console.error(` Use --force to overwrite.`);
|
|
5912
6223
|
process.exit(1);
|
|
@@ -6032,7 +6343,7 @@ async function activateViaApi(name, category, engineUrl) {
|
|
|
6032
6343
|
}
|
|
6033
6344
|
|
|
6034
6345
|
// src/commands/create-god.ts
|
|
6035
|
-
import { createInterface as
|
|
6346
|
+
import { createInterface as createInterface3 } from "readline";
|
|
6036
6347
|
async function prompt(rl2, question) {
|
|
6037
6348
|
return new Promise((resolve4) => {
|
|
6038
6349
|
rl2.question(question, resolve4);
|
|
@@ -6087,7 +6398,7 @@ async function createGodCommand(opts) {
|
|
|
6087
6398
|
console.log(" \u2022 Audit logins for this account separately");
|
|
6088
6399
|
console.log(`\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
6089
6400
|
`);
|
|
6090
|
-
const rlConfirm =
|
|
6401
|
+
const rlConfirm = createInterface3({
|
|
6091
6402
|
input: process.stdin,
|
|
6092
6403
|
output: process.stdout
|
|
6093
6404
|
});
|
|
@@ -6100,7 +6411,7 @@ async function createGodCommand(opts) {
|
|
|
6100
6411
|
console.log(`
|
|
6101
6412
|
Creating God (super-admin) user at ${engineUrl}
|
|
6102
6413
|
`);
|
|
6103
|
-
const rl2 =
|
|
6414
|
+
const rl2 = createInterface3({ input: process.stdin, output: process.stdout });
|
|
6104
6415
|
try {
|
|
6105
6416
|
const email = opts.email || await prompt(rl2, "Email: ");
|
|
6106
6417
|
const name = opts.name || await prompt(rl2, "Name: ");
|
|
@@ -6394,8 +6705,8 @@ var versionCommand = new Command("version").description("Show version informatio
|
|
|
6394
6705
|
});
|
|
6395
6706
|
|
|
6396
6707
|
// src/commands/update.ts
|
|
6397
|
-
import { existsSync as
|
|
6398
|
-
import { join as
|
|
6708
|
+
import { existsSync as existsSync18 } from "fs";
|
|
6709
|
+
import { join as join17 } from "path";
|
|
6399
6710
|
var c12 = {
|
|
6400
6711
|
bold: (s) => `\x1B[1m${s}\x1B[0m`,
|
|
6401
6712
|
green: (s) => `\x1B[32m${s}\x1B[0m`,
|
|
@@ -6409,10 +6720,10 @@ var updateCommand = new Command("update").description("Update Zveltio to the lat
|
|
|
6409
6720
|
console.log(`
|
|
6410
6721
|
${c12.bold("Zveltio Update")}
|
|
6411
6722
|
`);
|
|
6412
|
-
const installDir = opts.dir || process.env.ZVELTIO_DIR ||
|
|
6413
|
-
const metaPath =
|
|
6723
|
+
const installDir = opts.dir || process.env.ZVELTIO_DIR || join17(process.cwd(), "zveltio");
|
|
6724
|
+
const metaPath = join17(installDir, ".zveltio-install.json");
|
|
6414
6725
|
let meta = null;
|
|
6415
|
-
if (
|
|
6726
|
+
if (existsSync18(metaPath)) {
|
|
6416
6727
|
try {
|
|
6417
6728
|
meta = JSON.parse(await Bun.file(metaPath).text());
|
|
6418
6729
|
} catch {}
|
|
@@ -6524,11 +6835,11 @@ ${c12.bold("Zveltio Update")}
|
|
|
6524
6835
|
});
|
|
6525
6836
|
async function getVersionFromPackageJson() {
|
|
6526
6837
|
const candidates = [
|
|
6527
|
-
|
|
6528
|
-
|
|
6838
|
+
join17(process.cwd(), "packages/engine/package.json"),
|
|
6839
|
+
join17(process.cwd(), "package.json")
|
|
6529
6840
|
];
|
|
6530
6841
|
for (const pkgPath of candidates) {
|
|
6531
|
-
if (!
|
|
6842
|
+
if (!existsSync18(pkgPath))
|
|
6532
6843
|
continue;
|
|
6533
6844
|
try {
|
|
6534
6845
|
const pkg = JSON.parse(await Bun.file(pkgPath).text());
|
|
@@ -6551,15 +6862,15 @@ async function getCurrentVersionFromAPI(port) {
|
|
|
6551
6862
|
}
|
|
6552
6863
|
}
|
|
6553
6864
|
async function runUpdateScript(targetVersion, installDir, meta, force) {
|
|
6554
|
-
const envPath =
|
|
6555
|
-
if (
|
|
6865
|
+
const envPath = join17(installDir, ".env");
|
|
6866
|
+
if (existsSync18(envPath)) {
|
|
6556
6867
|
const backupPath = `${envPath}.backup.${Date.now()}`;
|
|
6557
6868
|
const envContent = await Bun.file(envPath).text();
|
|
6558
6869
|
await Bun.write(backupPath, envContent);
|
|
6559
6870
|
console.log(` Backed up .env \u2192 ${backupPath}`);
|
|
6560
6871
|
}
|
|
6561
|
-
const installScript =
|
|
6562
|
-
if (
|
|
6872
|
+
const installScript = join17(installDir, "install.sh");
|
|
6873
|
+
if (existsSync18(installScript)) {
|
|
6563
6874
|
const args = [
|
|
6564
6875
|
"bash",
|
|
6565
6876
|
installScript,
|
|
@@ -6578,7 +6889,7 @@ async function runUpdateScript(targetVersion, installDir, meta, force) {
|
|
|
6578
6889
|
if (code !== 0) {
|
|
6579
6890
|
console.error(`
|
|
6580
6891
|
Update failed. Your previous version is still running.`);
|
|
6581
|
-
if (
|
|
6892
|
+
if (existsSync18(envPath)) {
|
|
6582
6893
|
console.log(` Restore .env: copy ${envPath}.backup.* to ${envPath}`);
|
|
6583
6894
|
}
|
|
6584
6895
|
process.exit(1);
|
|
@@ -6743,14 +7054,14 @@ ${c13.bold("Zveltio Status")}
|
|
|
6743
7054
|
console.log("");
|
|
6744
7055
|
}
|
|
6745
7056
|
async function getLocalVersion() {
|
|
6746
|
-
const { existsSync:
|
|
6747
|
-
const { join:
|
|
7057
|
+
const { existsSync: existsSync19 } = await import("fs");
|
|
7058
|
+
const { join: join18 } = await import("path");
|
|
6748
7059
|
const candidates = [
|
|
6749
|
-
|
|
6750
|
-
|
|
7060
|
+
join18(process.cwd(), "packages/engine/package.json"),
|
|
7061
|
+
join18(process.cwd(), "package.json")
|
|
6751
7062
|
];
|
|
6752
7063
|
for (const p of candidates) {
|
|
6753
|
-
if (!
|
|
7064
|
+
if (!existsSync19(p))
|
|
6754
7065
|
continue;
|
|
6755
7066
|
try {
|
|
6756
7067
|
const pkg = JSON.parse(await Bun.file(p).text());
|
|
@@ -6763,7 +7074,7 @@ async function getLocalVersion() {
|
|
|
6763
7074
|
// package.json
|
|
6764
7075
|
var package_default = {
|
|
6765
7076
|
name: "@zveltio/cli",
|
|
6766
|
-
version: "3.0.0-beta.
|
|
7077
|
+
version: "3.0.0-beta.78",
|
|
6767
7078
|
type: "module",
|
|
6768
7079
|
bin: {
|
|
6769
7080
|
zveltio: "./dist/index.js"
|
|
@@ -6778,7 +7089,7 @@ var package_default = {
|
|
|
6778
7089
|
dev: "bun run --watch src/index.ts"
|
|
6779
7090
|
},
|
|
6780
7091
|
dependencies: {
|
|
6781
|
-
"@zveltio/sdk": "3.0.0-beta.
|
|
7092
|
+
"@zveltio/sdk": "3.0.0-beta.78",
|
|
6782
7093
|
commander: "^12.1.0"
|
|
6783
7094
|
},
|
|
6784
7095
|
devDependencies: {
|
|
@@ -6805,8 +7116,12 @@ program2.addCommand(versionCommand);
|
|
|
6805
7116
|
program2.addCommand(updateCommand);
|
|
6806
7117
|
program2.command("create-god").description("Create the first super-admin (god) user interactively").option("--url <url>", "Engine URL", "http://localhost:3000").option("--email <email>", "Admin email (skip prompt)").option("--name <name>", "Admin name (skip prompt)").option("--recovery-token <token>", "RECOVERY_TOKEN the engine was started with \u2014 required to grant the god role (env: RECOVERY_TOKEN)").action(createGodCommand);
|
|
6807
7118
|
var generate = program2.command("generate").description("Code generation utilities");
|
|
6808
|
-
generate.command("types [collection]").description("Generate TypeScript types for collections (writes to ./types/zveltio.d.ts)").option("-o, --output <path>", "Output file path", "./types/zveltio.d.ts").option("--url <url>", "Engine URL", "http://localhost:3000").action(generateTypesCommand);
|
|
6809
|
-
program2.command("generate-types [collection]").description("Generate TypeScript types for your collections").option("-o, --output <path>", "Output file path", "./types/zveltio.d.ts").option("--url <url>", "Engine URL", "http://localhost:3000").action(generateTypesCommand);
|
|
7119
|
+
generate.command("types [collection]").description("Generate TypeScript types for collections (writes to ./types/zveltio.d.ts)").option("-o, --output <path>", "Output file path", "./types/zveltio.d.ts").option("--url <url>", "Engine URL", "http://localhost:3000").option("--from <dir>", "Build from schema files (zveltio schema pull) instead of an engine").action(generateTypesCommand);
|
|
7120
|
+
program2.command("generate-types [collection]").description("Generate TypeScript types for your collections").option("-o, --output <path>", "Output file path", "./types/zveltio.d.ts").option("--url <url>", "Engine URL", "http://localhost:3000").option("--from <dir>", "Build from schema files (zveltio schema pull) instead of an engine").action(generateTypesCommand);
|
|
7121
|
+
var schema = program2.command("schema").description("Schema as code: the live schema as reviewable JSON files");
|
|
7122
|
+
schema.command("pull").description("Write the live schema to ./schema (collections, roles)").option("--dir <path>", "Schema directory", "./schema").option("--url <url>", "Engine URL (env: ZVELTIO_URL)").action(schemaPullCommand);
|
|
7123
|
+
schema.command("diff").description("Print what apply would change to match ./schema; exits 1 on drift").option("--dir <path>", "Schema directory", "./schema").option("--url <url>", "Engine URL (env: ZVELTIO_URL)").action(schemaDiffCommand);
|
|
7124
|
+
schema.command("apply").description("Make the instance match ./schema; shows the plan and asks first").option("--dir <path>", "Schema directory", "./schema").option("--url <url>", "Engine URL (env: ZVELTIO_URL)").option("--yes", "Apply without asking").option("--allow-destructive", "Run drops and type changes that a migration in migrations/ names").action(schemaApplyCommand);
|
|
6810
7125
|
program2.command("install <name>").description("Install a Zveltio extension from marketplace or local path").option("--path <path>", "Install from local directory (offline)").option("--url <url>", "Engine URL for marketplace install", "http://localhost:3000").option("--force", "Overwrite existing extension (local install only)").action((name, opts) => installCommand(name, opts));
|
|
6811
7126
|
var extensions = program2.command("extensions").description("Manage Zveltio extensions");
|
|
6812
7127
|
extensions.command("list").description("List all available and installed extensions").option("--url <url>", "Engine URL", "http://localhost:3000").option("--category <category>", "Filter by category").option("--json", "Output as JSON").action((opts) => extensionsListCommand(opts));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zveltio/cli",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.78",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"zveltio": "./dist/index.js"
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"dev": "bun run --watch src/index.ts"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@zveltio/sdk": "3.0.0-beta.
|
|
18
|
+
"@zveltio/sdk": "3.0.0-beta.78",
|
|
19
19
|
"commander": "^12.1.0"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|