@zveltio/cli 3.0.0-beta.76 → 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 +374 -50
- 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);
|
|
@@ -3325,22 +3325,25 @@ async function runMigrationsDirectly(opts, databaseUrl) {
|
|
|
3325
3325
|
process.env.DATABASE_URL = databaseUrl;
|
|
3326
3326
|
const { db: dbModule, migrations } = await loadEngineMigrationModules();
|
|
3327
3327
|
const { initDatabase } = dbModule;
|
|
3328
|
-
const { runMigrations, getAppliedMigrations,
|
|
3328
|
+
const { runMigrations, getAppliedMigrations, pendingMigrations } = migrations;
|
|
3329
3329
|
const db = await initDatabase();
|
|
3330
3330
|
if (opts.dryRun) {
|
|
3331
|
-
const
|
|
3332
|
-
const
|
|
3333
|
-
console.log(` Applied migrations: ${
|
|
3334
|
-
console.log(` Last applied version: ${
|
|
3331
|
+
const applied2 = await getAppliedMigrations(db);
|
|
3332
|
+
const { lastApplied, pending } = await pendingMigrations(db);
|
|
3333
|
+
console.log(` Applied migrations: ${applied2.length}`);
|
|
3334
|
+
console.log(` Last applied version: ${lastApplied}`);
|
|
3335
|
+
console.log(` Pending migrations: ${pending.length}`);
|
|
3336
|
+
for (const m of pending)
|
|
3337
|
+
console.log(` - ${m.filename}`);
|
|
3335
3338
|
console.log(`
|
|
3336
3339
|
(dry run \u2014 no changes made)
|
|
3337
3340
|
`);
|
|
3338
3341
|
await db.destroy?.();
|
|
3339
3342
|
return;
|
|
3340
3343
|
}
|
|
3341
|
-
await runMigrations(db);
|
|
3344
|
+
const applied = await runMigrations(db);
|
|
3342
3345
|
console.log(`
|
|
3343
|
-
\u2705
|
|
3346
|
+
\u2705 ${applied.length} migration(s) applied; nothing pending.
|
|
3344
3347
|
`);
|
|
3345
3348
|
await db.destroy?.();
|
|
3346
3349
|
} catch (err) {
|
|
@@ -3457,10 +3460,14 @@ Creating extension: ${extName}
|
|
|
3457
3460
|
slots: []
|
|
3458
3461
|
}
|
|
3459
3462
|
}, null, 2));
|
|
3460
|
-
await writeFile(join8(targetDir, "engine", "index.ts"), `import
|
|
3463
|
+
await writeFile(join8(targetDir, "engine", "index.ts"), `import { defineExtension } from '@zveltio/sdk/extension';
|
|
3461
3464
|
import { join } from 'path';
|
|
3462
3465
|
|
|
3463
|
-
|
|
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([], {
|
|
3464
3471
|
name: '${extName}',
|
|
3465
3472
|
category: '${category}',
|
|
3466
3473
|
|
|
@@ -3483,9 +3490,7 @@ const extension: ZveltioExtension = {
|
|
|
3483
3490
|
return c.json({ data });
|
|
3484
3491
|
});
|
|
3485
3492
|
},
|
|
3486
|
-
};
|
|
3487
|
-
|
|
3488
|
-
export default extension;
|
|
3493
|
+
});
|
|
3489
3494
|
`);
|
|
3490
3495
|
await writeFile(join8(targetDir, "engine", "migrations", "001_init.sql"), `-- ${name} \u2014 initial schema.
|
|
3491
3496
|
--
|
|
@@ -3658,7 +3663,7 @@ Where UI goes:
|
|
|
3658
3663
|
Do NOT add studio/vite.config.ts, studio/package.json, or studio/dist/ \u2014 removed in alpha.94/beta.15.
|
|
3659
3664
|
`);
|
|
3660
3665
|
if (codePage) {
|
|
3661
|
-
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
|
|
3662
3667
|
schemas and seven are code \u2014 and each of those seven sits beside a schema, for
|
|
3663
3668
|
the part a schema could not express. On this road you own fetching,
|
|
3664
3669
|
loading and error states, i18n by hand, and a Studio release to ship a change.
|
|
@@ -3989,6 +3994,91 @@ function emitTypeScript(schema, opts = {}) {
|
|
|
3989
3994
|
function quoteIdentifierIfNeeded(name) {
|
|
3990
3995
|
return /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name) ? name : `'${name.replace(/'/g, "\\'")}'`;
|
|
3991
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
|
+
}
|
|
3992
4082
|
|
|
3993
4083
|
// src/commands/extension-types.ts
|
|
3994
4084
|
var c6 = {
|
|
@@ -4347,6 +4437,32 @@ function validateManifest(input) {
|
|
|
4347
4437
|
if (typeof obj.zveltioMaxVersion === "string" && !SEMVER_RE.test(obj.zveltioMaxVersion)) {
|
|
4348
4438
|
out.push(err("MANIFEST_BAD_MAX_VERSION", `zveltioMaxVersion "${obj.zveltioMaxVersion}" is not valid semver`));
|
|
4349
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
|
+
}
|
|
4350
4466
|
if (obj.apiKeyRoutes !== undefined) {
|
|
4351
4467
|
const routes = Array.isArray(obj.apiKeyRoutes) ? obj.apiKeyRoutes : [obj.apiKeyRoutes];
|
|
4352
4468
|
for (const r of routes) {
|
|
@@ -4895,6 +5011,51 @@ function inferExpectedName(dir) {
|
|
|
4895
5011
|
}
|
|
4896
5012
|
return norm.split("/").filter(Boolean).pop() ?? "";
|
|
4897
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
|
+
}
|
|
4898
5059
|
function checkBundleIntegrity(dir, manifest) {
|
|
4899
5060
|
const bundle = join10(dir, "engine", "index.js");
|
|
4900
5061
|
const integrity = manifest?.integrity;
|
|
@@ -5125,6 +5286,11 @@ ${c7.bold("Extension validate")}
|
|
|
5125
5286
|
if (integrityErrors.some((e) => e.severity !== "warning"))
|
|
5126
5287
|
result.ok = false;
|
|
5127
5288
|
}
|
|
5289
|
+
const capabilityErrors = checkDeclaredCapabilities(readEngineTypeScript(dir), manifest);
|
|
5290
|
+
if (capabilityErrors.length > 0) {
|
|
5291
|
+
result.errors.push(...capabilityErrors);
|
|
5292
|
+
result.ok = false;
|
|
5293
|
+
}
|
|
5128
5294
|
const warnings = result.errors.filter((e) => e.severity === "warning");
|
|
5129
5295
|
const hardErrors = result.errors.filter((e) => e.severity !== "warning");
|
|
5130
5296
|
if (warnings.length > 0) {
|
|
@@ -5787,8 +5953,8 @@ ${c10.dim(`Ready. Edit engine/ files to trigger a hot-reload. Ctrl+C to stop.
|
|
|
5787
5953
|
}
|
|
5788
5954
|
|
|
5789
5955
|
// src/commands/generate-types.ts
|
|
5790
|
-
import { mkdirSync as mkdirSync5 } from "fs";
|
|
5791
|
-
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";
|
|
5792
5958
|
var c11 = {
|
|
5793
5959
|
bold: (s) => `\x1B[1m${s}\x1B[0m`,
|
|
5794
5960
|
green: (s) => `\x1B[32m${s}\x1B[0m`,
|
|
@@ -5797,6 +5963,8 @@ var c11 = {
|
|
|
5797
5963
|
red: (s) => `\x1B[31m${s}\x1B[0m`
|
|
5798
5964
|
};
|
|
5799
5965
|
async function generateTypesCommand(collection, opts) {
|
|
5966
|
+
if (opts.from)
|
|
5967
|
+
return typesFromFiles(collection, opts.from, opts.output || "./types/zveltio.d.ts");
|
|
5800
5968
|
const engineUrl = opts.url || process.env.ZVELTIO_URL || "http://localhost:3000";
|
|
5801
5969
|
const outputPath = opts.output || "./types/zveltio.d.ts";
|
|
5802
5970
|
console.log(`
|
|
@@ -5843,11 +6011,157 @@ ${c11.bold("Generate Types")}
|
|
|
5843
6011
|
process.exit(1);
|
|
5844
6012
|
}
|
|
5845
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
|
+
}
|
|
5846
6160
|
|
|
5847
6161
|
// src/commands/install.ts
|
|
5848
|
-
import { existsSync as
|
|
6162
|
+
import { existsSync as existsSync17 } from "fs";
|
|
5849
6163
|
import { readFile, mkdir as mkdir2, cp } from "fs/promises";
|
|
5850
|
-
import { join as
|
|
6164
|
+
import { join as join16 } from "path";
|
|
5851
6165
|
async function installCommand(name, opts) {
|
|
5852
6166
|
const engineUrl = opts.url || process.env.ENGINE_URL || "http://localhost:3000";
|
|
5853
6167
|
console.log(`
|
|
@@ -5860,13 +6174,13 @@ async function installCommand(name, opts) {
|
|
|
5860
6174
|
await installFromCatalog(name, engineUrl);
|
|
5861
6175
|
}
|
|
5862
6176
|
async function installFromPath(name, sourcePath, engineUrl, force) {
|
|
5863
|
-
if (!
|
|
6177
|
+
if (!existsSync17(join16(sourcePath, "manifest.json"))) {
|
|
5864
6178
|
console.error("\u274C No manifest.json found at", sourcePath);
|
|
5865
6179
|
process.exit(1);
|
|
5866
6180
|
}
|
|
5867
6181
|
let manifest;
|
|
5868
6182
|
try {
|
|
5869
|
-
manifest = JSON.parse(await readFile(
|
|
6183
|
+
manifest = JSON.parse(await readFile(join16(sourcePath, "manifest.json"), "utf-8"));
|
|
5870
6184
|
} catch {
|
|
5871
6185
|
console.error("\u274C Failed to read manifest.json");
|
|
5872
6186
|
process.exit(1);
|
|
@@ -5890,8 +6204,8 @@ async function installFromPath(name, sourcePath, engineUrl, force) {
|
|
|
5890
6204
|
console.log(` Name: ${manifest.displayName || manifest.name}`);
|
|
5891
6205
|
console.log(` Version: ${manifest.version || "?"}`);
|
|
5892
6206
|
console.log(` Category: ${category}`);
|
|
5893
|
-
const enginePkgPath =
|
|
5894
|
-
if (
|
|
6207
|
+
const enginePkgPath = join16(process.cwd(), "packages/engine/package.json");
|
|
6208
|
+
if (existsSync17(enginePkgPath)) {
|
|
5895
6209
|
const enginePkg = JSON.parse(await readFile(enginePkgPath, "utf-8"));
|
|
5896
6210
|
const engineVersion = enginePkg.version || "unknown";
|
|
5897
6211
|
if (manifest.zveltioMinVersion) {
|
|
@@ -5902,8 +6216,8 @@ async function installFromPath(name, sourcePath, engineUrl, force) {
|
|
|
5902
6216
|
}
|
|
5903
6217
|
}
|
|
5904
6218
|
const extName = manifest.name;
|
|
5905
|
-
const targetDir =
|
|
5906
|
-
if (
|
|
6219
|
+
const targetDir = join16(process.cwd(), "extensions", category, extName);
|
|
6220
|
+
if (existsSync17(targetDir) && !force) {
|
|
5907
6221
|
console.error(`\u274C Extension already exists at ${targetDir}`);
|
|
5908
6222
|
console.error(` Use --force to overwrite.`);
|
|
5909
6223
|
process.exit(1);
|
|
@@ -6029,7 +6343,7 @@ async function activateViaApi(name, category, engineUrl) {
|
|
|
6029
6343
|
}
|
|
6030
6344
|
|
|
6031
6345
|
// src/commands/create-god.ts
|
|
6032
|
-
import { createInterface as
|
|
6346
|
+
import { createInterface as createInterface3 } from "readline";
|
|
6033
6347
|
async function prompt(rl2, question) {
|
|
6034
6348
|
return new Promise((resolve4) => {
|
|
6035
6349
|
rl2.question(question, resolve4);
|
|
@@ -6084,7 +6398,7 @@ async function createGodCommand(opts) {
|
|
|
6084
6398
|
console.log(" \u2022 Audit logins for this account separately");
|
|
6085
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
|
|
6086
6400
|
`);
|
|
6087
|
-
const rlConfirm =
|
|
6401
|
+
const rlConfirm = createInterface3({
|
|
6088
6402
|
input: process.stdin,
|
|
6089
6403
|
output: process.stdout
|
|
6090
6404
|
});
|
|
@@ -6097,7 +6411,7 @@ async function createGodCommand(opts) {
|
|
|
6097
6411
|
console.log(`
|
|
6098
6412
|
Creating God (super-admin) user at ${engineUrl}
|
|
6099
6413
|
`);
|
|
6100
|
-
const rl2 =
|
|
6414
|
+
const rl2 = createInterface3({ input: process.stdin, output: process.stdout });
|
|
6101
6415
|
try {
|
|
6102
6416
|
const email = opts.email || await prompt(rl2, "Email: ");
|
|
6103
6417
|
const name = opts.name || await prompt(rl2, "Name: ");
|
|
@@ -6391,8 +6705,8 @@ var versionCommand = new Command("version").description("Show version informatio
|
|
|
6391
6705
|
});
|
|
6392
6706
|
|
|
6393
6707
|
// src/commands/update.ts
|
|
6394
|
-
import { existsSync as
|
|
6395
|
-
import { join as
|
|
6708
|
+
import { existsSync as existsSync18 } from "fs";
|
|
6709
|
+
import { join as join17 } from "path";
|
|
6396
6710
|
var c12 = {
|
|
6397
6711
|
bold: (s) => `\x1B[1m${s}\x1B[0m`,
|
|
6398
6712
|
green: (s) => `\x1B[32m${s}\x1B[0m`,
|
|
@@ -6406,10 +6720,10 @@ var updateCommand = new Command("update").description("Update Zveltio to the lat
|
|
|
6406
6720
|
console.log(`
|
|
6407
6721
|
${c12.bold("Zveltio Update")}
|
|
6408
6722
|
`);
|
|
6409
|
-
const installDir = opts.dir || process.env.ZVELTIO_DIR ||
|
|
6410
|
-
const metaPath =
|
|
6723
|
+
const installDir = opts.dir || process.env.ZVELTIO_DIR || join17(process.cwd(), "zveltio");
|
|
6724
|
+
const metaPath = join17(installDir, ".zveltio-install.json");
|
|
6411
6725
|
let meta = null;
|
|
6412
|
-
if (
|
|
6726
|
+
if (existsSync18(metaPath)) {
|
|
6413
6727
|
try {
|
|
6414
6728
|
meta = JSON.parse(await Bun.file(metaPath).text());
|
|
6415
6729
|
} catch {}
|
|
@@ -6521,11 +6835,11 @@ ${c12.bold("Zveltio Update")}
|
|
|
6521
6835
|
});
|
|
6522
6836
|
async function getVersionFromPackageJson() {
|
|
6523
6837
|
const candidates = [
|
|
6524
|
-
|
|
6525
|
-
|
|
6838
|
+
join17(process.cwd(), "packages/engine/package.json"),
|
|
6839
|
+
join17(process.cwd(), "package.json")
|
|
6526
6840
|
];
|
|
6527
6841
|
for (const pkgPath of candidates) {
|
|
6528
|
-
if (!
|
|
6842
|
+
if (!existsSync18(pkgPath))
|
|
6529
6843
|
continue;
|
|
6530
6844
|
try {
|
|
6531
6845
|
const pkg = JSON.parse(await Bun.file(pkgPath).text());
|
|
@@ -6548,15 +6862,15 @@ async function getCurrentVersionFromAPI(port) {
|
|
|
6548
6862
|
}
|
|
6549
6863
|
}
|
|
6550
6864
|
async function runUpdateScript(targetVersion, installDir, meta, force) {
|
|
6551
|
-
const envPath =
|
|
6552
|
-
if (
|
|
6865
|
+
const envPath = join17(installDir, ".env");
|
|
6866
|
+
if (existsSync18(envPath)) {
|
|
6553
6867
|
const backupPath = `${envPath}.backup.${Date.now()}`;
|
|
6554
6868
|
const envContent = await Bun.file(envPath).text();
|
|
6555
6869
|
await Bun.write(backupPath, envContent);
|
|
6556
6870
|
console.log(` Backed up .env \u2192 ${backupPath}`);
|
|
6557
6871
|
}
|
|
6558
|
-
const installScript =
|
|
6559
|
-
if (
|
|
6872
|
+
const installScript = join17(installDir, "install.sh");
|
|
6873
|
+
if (existsSync18(installScript)) {
|
|
6560
6874
|
const args = [
|
|
6561
6875
|
"bash",
|
|
6562
6876
|
installScript,
|
|
@@ -6575,7 +6889,7 @@ async function runUpdateScript(targetVersion, installDir, meta, force) {
|
|
|
6575
6889
|
if (code !== 0) {
|
|
6576
6890
|
console.error(`
|
|
6577
6891
|
Update failed. Your previous version is still running.`);
|
|
6578
|
-
if (
|
|
6892
|
+
if (existsSync18(envPath)) {
|
|
6579
6893
|
console.log(` Restore .env: copy ${envPath}.backup.* to ${envPath}`);
|
|
6580
6894
|
}
|
|
6581
6895
|
process.exit(1);
|
|
@@ -6740,14 +7054,14 @@ ${c13.bold("Zveltio Status")}
|
|
|
6740
7054
|
console.log("");
|
|
6741
7055
|
}
|
|
6742
7056
|
async function getLocalVersion() {
|
|
6743
|
-
const { existsSync:
|
|
6744
|
-
const { join:
|
|
7057
|
+
const { existsSync: existsSync19 } = await import("fs");
|
|
7058
|
+
const { join: join18 } = await import("path");
|
|
6745
7059
|
const candidates = [
|
|
6746
|
-
|
|
6747
|
-
|
|
7060
|
+
join18(process.cwd(), "packages/engine/package.json"),
|
|
7061
|
+
join18(process.cwd(), "package.json")
|
|
6748
7062
|
];
|
|
6749
7063
|
for (const p of candidates) {
|
|
6750
|
-
if (!
|
|
7064
|
+
if (!existsSync19(p))
|
|
6751
7065
|
continue;
|
|
6752
7066
|
try {
|
|
6753
7067
|
const pkg = JSON.parse(await Bun.file(p).text());
|
|
@@ -6760,7 +7074,7 @@ async function getLocalVersion() {
|
|
|
6760
7074
|
// package.json
|
|
6761
7075
|
var package_default = {
|
|
6762
7076
|
name: "@zveltio/cli",
|
|
6763
|
-
version: "3.0.0-beta.
|
|
7077
|
+
version: "3.0.0-beta.78",
|
|
6764
7078
|
type: "module",
|
|
6765
7079
|
bin: {
|
|
6766
7080
|
zveltio: "./dist/index.js"
|
|
@@ -6775,7 +7089,7 @@ var package_default = {
|
|
|
6775
7089
|
dev: "bun run --watch src/index.ts"
|
|
6776
7090
|
},
|
|
6777
7091
|
dependencies: {
|
|
6778
|
-
"@zveltio/sdk": "3.0.0-beta.
|
|
7092
|
+
"@zveltio/sdk": "3.0.0-beta.78",
|
|
6779
7093
|
commander: "^12.1.0"
|
|
6780
7094
|
},
|
|
6781
7095
|
devDependencies: {
|
|
@@ -6802,8 +7116,12 @@ program2.addCommand(versionCommand);
|
|
|
6802
7116
|
program2.addCommand(updateCommand);
|
|
6803
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);
|
|
6804
7118
|
var generate = program2.command("generate").description("Code generation utilities");
|
|
6805
|
-
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);
|
|
6806
|
-
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);
|
|
6807
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));
|
|
6808
7126
|
var extensions = program2.command("extensions").description("Manage Zveltio extensions");
|
|
6809
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));
|
|
@@ -6893,4 +7211,10 @@ adminMarketplace.command("enroll-publisher").description("Add a new publisher to
|
|
|
6893
7211
|
const { adminMarketplaceEnrollPublisher: adminMarketplaceEnrollPublisher2 } = await Promise.resolve().then(() => (init_admin_marketplace(), exports_admin_marketplace));
|
|
6894
7212
|
await adminMarketplaceEnrollPublisher2(opts);
|
|
6895
7213
|
});
|
|
7214
|
+
var refuseExcessArguments = (cmd) => {
|
|
7215
|
+
cmd.allowExcessArguments(false);
|
|
7216
|
+
for (const sub of cmd.commands)
|
|
7217
|
+
refuseExcessArguments(sub);
|
|
7218
|
+
};
|
|
7219
|
+
refuseExcessArguments(program2);
|
|
6896
7220
|
await program2.parseAsync(process.argv);
|
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": {
|