@zveltio/cli 3.0.0-beta.77 → 3.0.0-beta.79
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 +379 -44
- 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 = {
|
|
@@ -4059,7 +4146,7 @@ import { join as join10 } from "path";
|
|
|
4059
4146
|
|
|
4060
4147
|
// ../sdk/dist/validate/migration-parse.js
|
|
4061
4148
|
function parseMigrationSql(raw) {
|
|
4062
|
-
const downIdx = raw.search(/^--\s*DOWN\
|
|
4149
|
+
const downIdx = raw.search(/^--\s*DOWN\s*$/im);
|
|
4063
4150
|
if (downIdx < 0) {
|
|
4064
4151
|
return { up: raw.trim(), down: null };
|
|
4065
4152
|
}
|
|
@@ -4287,6 +4374,7 @@ var SHARED_MESSAGE_KEYS = new Set([
|
|
|
4287
4374
|
]);
|
|
4288
4375
|
|
|
4289
4376
|
// ../sdk/dist/validate/index.js
|
|
4377
|
+
var CREDENTIAL_HEADERS = ["authorization", "cookie", "proxy-authorization", "x-api-key"];
|
|
4290
4378
|
var SEMVER_RE = /^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/;
|
|
4291
4379
|
var KNOWN_CATEGORIES = new Set([
|
|
4292
4380
|
"auth",
|
|
@@ -4350,6 +4438,32 @@ function validateManifest(input) {
|
|
|
4350
4438
|
if (typeof obj.zveltioMaxVersion === "string" && !SEMVER_RE.test(obj.zveltioMaxVersion)) {
|
|
4351
4439
|
out.push(err("MANIFEST_BAD_MAX_VERSION", `zveltioMaxVersion "${obj.zveltioMaxVersion}" is not valid semver`));
|
|
4352
4440
|
}
|
|
4441
|
+
const depNames = new Map;
|
|
4442
|
+
for (const field of ["dependencies", "optionalDependencies"]) {
|
|
4443
|
+
const list = obj[field];
|
|
4444
|
+
if (list === undefined)
|
|
4445
|
+
continue;
|
|
4446
|
+
if (!Array.isArray(list)) {
|
|
4447
|
+
out.push(err("MANIFEST_BAD_DEPENDENCY", `"${field}" must be an array of { name, minVersion? }`));
|
|
4448
|
+
continue;
|
|
4449
|
+
}
|
|
4450
|
+
for (const d of list) {
|
|
4451
|
+
const depName = d && typeof d === "object" ? d.name : undefined;
|
|
4452
|
+
const min = d && typeof d === "object" ? d.minVersion : undefined;
|
|
4453
|
+
if (typeof depName !== "string" || depName.trim() === "") {
|
|
4454
|
+
out.push(err("MANIFEST_BAD_DEPENDENCY", `"${field}" entry ${JSON.stringify(d)} has no name`));
|
|
4455
|
+
continue;
|
|
4456
|
+
}
|
|
4457
|
+
if (min !== undefined && (typeof min !== "string" || !SEMVER_RE.test(min))) {
|
|
4458
|
+
out.push(err("MANIFEST_BAD_DEPENDENCY", `"${field}" ${depName}: minVersion ${JSON.stringify(min)} is not semver`));
|
|
4459
|
+
}
|
|
4460
|
+
const seen = depNames.get(depName);
|
|
4461
|
+
if (seen) {
|
|
4462
|
+
out.push(err("MANIFEST_BAD_DEPENDENCY", `"${depName}" is listed in "${seen}" and "${field}"`));
|
|
4463
|
+
}
|
|
4464
|
+
depNames.set(depName, field);
|
|
4465
|
+
}
|
|
4466
|
+
}
|
|
4353
4467
|
if (obj.apiKeyRoutes !== undefined) {
|
|
4354
4468
|
const routes = Array.isArray(obj.apiKeyRoutes) ? obj.apiKeyRoutes : [obj.apiKeyRoutes];
|
|
4355
4469
|
for (const r of routes) {
|
|
@@ -4358,6 +4472,25 @@ function validateManifest(input) {
|
|
|
4358
4472
|
}
|
|
4359
4473
|
}
|
|
4360
4474
|
}
|
|
4475
|
+
if (obj.forwardCredentials !== undefined) {
|
|
4476
|
+
const fwd = obj.forwardCredentials;
|
|
4477
|
+
const publicRoutes = Array.isArray(obj.publicRoutes) ? obj.publicRoutes : [];
|
|
4478
|
+
if (!fwd || typeof fwd !== "object" || Array.isArray(fwd)) {
|
|
4479
|
+
out.push(err("MANIFEST_BAD_FORWARD_CREDENTIALS", '"forwardCredentials" must map a route to headers'));
|
|
4480
|
+
} else {
|
|
4481
|
+
for (const [route, names] of Object.entries(fwd)) {
|
|
4482
|
+
if (!publicRoutes.includes(route)) {
|
|
4483
|
+
out.push(err("MANIFEST_BAD_FORWARD_CREDENTIALS", `forwardCredentials names "${route}", which is not a publicRoutes entry`));
|
|
4484
|
+
}
|
|
4485
|
+
const list = Array.isArray(names) ? names : [names];
|
|
4486
|
+
for (const n of list) {
|
|
4487
|
+
if (typeof n !== "string" || !CREDENTIAL_HEADERS.includes(n)) {
|
|
4488
|
+
out.push(err("MANIFEST_BAD_FORWARD_CREDENTIALS", `forwardCredentials "${route}": ${JSON.stringify(n)} is not one of ${CREDENTIAL_HEADERS.join(", ")}`));
|
|
4489
|
+
}
|
|
4490
|
+
}
|
|
4491
|
+
}
|
|
4492
|
+
}
|
|
4493
|
+
}
|
|
4361
4494
|
if (typeof obj.category === "string" && !KNOWN_CATEGORIES.has(obj.category)) {
|
|
4362
4495
|
out.push(warn("MANIFEST_UNKNOWN_CATEGORY", `category "${obj.category}" isn't in the known set \u2014 typo? Known: ${[...KNOWN_CATEGORIES].sort().join(", ")}`));
|
|
4363
4496
|
}
|
|
@@ -4898,6 +5031,51 @@ function inferExpectedName(dir) {
|
|
|
4898
5031
|
}
|
|
4899
5032
|
return norm.split("/").filter(Boolean).pop() ?? "";
|
|
4900
5033
|
}
|
|
5034
|
+
function checkDeclaredCapabilities(sources, manifest) {
|
|
5035
|
+
const call = sources.map((src) => /defineExtension\s*(?:<[^>]*>)?\(\s*\[([^\]]*)\]/.exec(src)).find((m) => m !== null);
|
|
5036
|
+
if (!call)
|
|
5037
|
+
return [];
|
|
5038
|
+
const typed = new Set([...(call[1] ?? "").matchAll(/['"`]([^'"`]+)['"`]/g)].map((m) => m[1]));
|
|
5039
|
+
const raw = manifest && typeof manifest === "object" ? manifest.permissions : undefined;
|
|
5040
|
+
const legacy = new Set(["database", "settings", "network"]);
|
|
5041
|
+
const declared = new Set((Array.isArray(raw) ? raw : []).filter((p) => typeof p === "string" && !legacy.has(p)));
|
|
5042
|
+
const out = [];
|
|
5043
|
+
const onlyTyped = [...typed].filter((c8) => !declared.has(c8)).sort();
|
|
5044
|
+
const onlyManifest = [...declared].filter((c8) => !typed.has(c8)).sort();
|
|
5045
|
+
if (onlyTyped.length > 0) {
|
|
5046
|
+
out.push({
|
|
5047
|
+
code: "CAPABILITY_NOT_IN_MANIFEST",
|
|
5048
|
+
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.",
|
|
5049
|
+
file: "manifest.json"
|
|
5050
|
+
});
|
|
5051
|
+
}
|
|
5052
|
+
if (onlyManifest.length > 0) {
|
|
5053
|
+
out.push({
|
|
5054
|
+
code: "CAPABILITY_NOT_IN_DEFINE",
|
|
5055
|
+
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.",
|
|
5056
|
+
file: "manifest.json"
|
|
5057
|
+
});
|
|
5058
|
+
}
|
|
5059
|
+
return out;
|
|
5060
|
+
}
|
|
5061
|
+
function readEngineTypeScript(dir) {
|
|
5062
|
+
const out = [];
|
|
5063
|
+
const walk = (d) => {
|
|
5064
|
+
if (!existsSync11(d))
|
|
5065
|
+
return;
|
|
5066
|
+
for (const name of readdirSync3(d)) {
|
|
5067
|
+
if (name === "node_modules")
|
|
5068
|
+
continue;
|
|
5069
|
+
const p = join10(d, name);
|
|
5070
|
+
if (statSync(p).isDirectory())
|
|
5071
|
+
walk(p);
|
|
5072
|
+
else if (p.endsWith(".ts") && !p.endsWith(".test.ts") && !p.endsWith(".d.ts"))
|
|
5073
|
+
out.push(readFileSync4(p, "utf8"));
|
|
5074
|
+
}
|
|
5075
|
+
};
|
|
5076
|
+
walk(join10(dir, "engine"));
|
|
5077
|
+
return out;
|
|
5078
|
+
}
|
|
4901
5079
|
function checkBundleIntegrity(dir, manifest) {
|
|
4902
5080
|
const bundle = join10(dir, "engine", "index.js");
|
|
4903
5081
|
const integrity = manifest?.integrity;
|
|
@@ -5128,6 +5306,11 @@ ${c7.bold("Extension validate")}
|
|
|
5128
5306
|
if (integrityErrors.some((e) => e.severity !== "warning"))
|
|
5129
5307
|
result.ok = false;
|
|
5130
5308
|
}
|
|
5309
|
+
const capabilityErrors = checkDeclaredCapabilities(readEngineTypeScript(dir), manifest);
|
|
5310
|
+
if (capabilityErrors.length > 0) {
|
|
5311
|
+
result.errors.push(...capabilityErrors);
|
|
5312
|
+
result.ok = false;
|
|
5313
|
+
}
|
|
5131
5314
|
const warnings = result.errors.filter((e) => e.severity === "warning");
|
|
5132
5315
|
const hardErrors = result.errors.filter((e) => e.severity !== "warning");
|
|
5133
5316
|
if (warnings.length > 0) {
|
|
@@ -5790,8 +5973,8 @@ ${c10.dim(`Ready. Edit engine/ files to trigger a hot-reload. Ctrl+C to stop.
|
|
|
5790
5973
|
}
|
|
5791
5974
|
|
|
5792
5975
|
// src/commands/generate-types.ts
|
|
5793
|
-
import { mkdirSync as mkdirSync5 } from "fs";
|
|
5794
|
-
import { dirname as dirname4 } from "path";
|
|
5976
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync5, readdirSync as readdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
5977
|
+
import { dirname as dirname4, join as join14 } from "path";
|
|
5795
5978
|
var c11 = {
|
|
5796
5979
|
bold: (s) => `\x1B[1m${s}\x1B[0m`,
|
|
5797
5980
|
green: (s) => `\x1B[32m${s}\x1B[0m`,
|
|
@@ -5800,6 +5983,8 @@ var c11 = {
|
|
|
5800
5983
|
red: (s) => `\x1B[31m${s}\x1B[0m`
|
|
5801
5984
|
};
|
|
5802
5985
|
async function generateTypesCommand(collection, opts) {
|
|
5986
|
+
if (opts.from)
|
|
5987
|
+
return typesFromFiles(collection, opts.from, opts.output || "./types/zveltio.d.ts");
|
|
5803
5988
|
const engineUrl = opts.url || process.env.ZVELTIO_URL || "http://localhost:3000";
|
|
5804
5989
|
const outputPath = opts.output || "./types/zveltio.d.ts";
|
|
5805
5990
|
console.log(`
|
|
@@ -5846,11 +6031,157 @@ ${c11.bold("Generate Types")}
|
|
|
5846
6031
|
process.exit(1);
|
|
5847
6032
|
}
|
|
5848
6033
|
}
|
|
6034
|
+
function typesFromFiles(collection, dir, outputPath) {
|
|
6035
|
+
const collectionsDir = join14(dir, "collections");
|
|
6036
|
+
if (!existsSync15(collectionsDir)) {
|
|
6037
|
+
console.error(c11.red(`No collections/ in ${dir}. Run \`zveltio schema pull\` first.`));
|
|
6038
|
+
process.exit(1);
|
|
6039
|
+
}
|
|
6040
|
+
const files = {};
|
|
6041
|
+
for (const f of readdirSync6(collectionsDir)) {
|
|
6042
|
+
if (f.endsWith(".json"))
|
|
6043
|
+
files[`collections/${f}`] = readFileSync7(join14(collectionsDir, f), "utf8");
|
|
6044
|
+
}
|
|
6045
|
+
const collections = collectionsFromSchemaFiles(files);
|
|
6046
|
+
let types;
|
|
6047
|
+
if (collection) {
|
|
6048
|
+
const one = collections.find((col) => col.name === collection);
|
|
6049
|
+
if (!one) {
|
|
6050
|
+
console.error(c11.red(`Collection ${collection} is not in ${dir}`));
|
|
6051
|
+
process.exit(1);
|
|
6052
|
+
}
|
|
6053
|
+
types = emitCollectionTypes(one);
|
|
6054
|
+
} else {
|
|
6055
|
+
types = emitCollectionTypesFile(collections);
|
|
6056
|
+
}
|
|
6057
|
+
mkdirSync5(dirname4(outputPath), { recursive: true });
|
|
6058
|
+
writeFileSync5(outputPath, types);
|
|
6059
|
+
console.log(`${c11.green("Types generated:")} ${outputPath} ${c11.dim(`from ${dir}`)}`);
|
|
6060
|
+
}
|
|
6061
|
+
|
|
6062
|
+
// src/commands/schema.ts
|
|
6063
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync6, readdirSync as readdirSync7, readFileSync as readFileSync8, rmSync, writeFileSync as writeFileSync6 } from "fs";
|
|
6064
|
+
import { dirname as dirname5, join as join15 } from "path";
|
|
6065
|
+
import { createInterface as createInterface2 } from "readline";
|
|
6066
|
+
async function schemaPullCommand(opts) {
|
|
6067
|
+
const dir = opts.dir || "./schema";
|
|
6068
|
+
const engineUrl = opts.url || process.env.ZVELTIO_URL || "http://localhost:3000";
|
|
6069
|
+
const res = await fetch(`${engineUrl}/api/admin/schema/export`, {
|
|
6070
|
+
headers: { Authorization: `Bearer ${process.env.ZVELTIO_API_KEY || ""}` }
|
|
6071
|
+
});
|
|
6072
|
+
if (!res.ok) {
|
|
6073
|
+
console.error(`Schema export failed: ${res.status} ${res.statusText}`);
|
|
6074
|
+
if (res.status === 401 || res.status === 403) {
|
|
6075
|
+
console.error(" Set ZVELTIO_API_KEY to a key of an instance admin.");
|
|
6076
|
+
}
|
|
6077
|
+
process.exit(1);
|
|
6078
|
+
}
|
|
6079
|
+
const { files } = await res.json();
|
|
6080
|
+
for (const [path, content] of Object.entries(files)) {
|
|
6081
|
+
const target = join15(dir, path);
|
|
6082
|
+
mkdirSync6(dirname5(target), { recursive: true });
|
|
6083
|
+
writeFileSync6(target, content);
|
|
6084
|
+
}
|
|
6085
|
+
const collectionsDir = join15(dir, "collections");
|
|
6086
|
+
let removed = 0;
|
|
6087
|
+
if (existsSync16(collectionsDir)) {
|
|
6088
|
+
for (const f of readdirSync7(collectionsDir)) {
|
|
6089
|
+
if (f.endsWith(".json") && !(`collections/${f}` in files)) {
|
|
6090
|
+
rmSync(join15(collectionsDir, f));
|
|
6091
|
+
removed++;
|
|
6092
|
+
}
|
|
6093
|
+
}
|
|
6094
|
+
}
|
|
6095
|
+
console.log(`Wrote ${Object.keys(files).length} files to ${dir}` + (removed ? `, removed ${removed} for collections that no longer exist` : ""));
|
|
6096
|
+
}
|
|
6097
|
+
function readSchemaDir(dir) {
|
|
6098
|
+
const files = {};
|
|
6099
|
+
for (const path of ["zveltio-schema.json", "roles.json"]) {
|
|
6100
|
+
if (existsSync16(join15(dir, path)))
|
|
6101
|
+
files[path] = readFileSync8(join15(dir, path), "utf8");
|
|
6102
|
+
}
|
|
6103
|
+
for (const sub of ["collections", "migrations"]) {
|
|
6104
|
+
const subDir = join15(dir, sub);
|
|
6105
|
+
if (!existsSync16(subDir))
|
|
6106
|
+
continue;
|
|
6107
|
+
for (const f of readdirSync7(subDir).filter((f2) => f2.endsWith(".json"))) {
|
|
6108
|
+
files[`${sub}/${f}`] = readFileSync8(join15(subDir, f), "utf8");
|
|
6109
|
+
}
|
|
6110
|
+
}
|
|
6111
|
+
return files;
|
|
6112
|
+
}
|
|
6113
|
+
async function postSchema(engineUrl, route, files, allowDestructive = false) {
|
|
6114
|
+
const res = await fetch(`${engineUrl}/api/admin/schema/${route}`, {
|
|
6115
|
+
method: "POST",
|
|
6116
|
+
headers: {
|
|
6117
|
+
Authorization: `Bearer ${process.env.ZVELTIO_API_KEY || ""}`,
|
|
6118
|
+
"Content-Type": "application/json"
|
|
6119
|
+
},
|
|
6120
|
+
body: JSON.stringify({ files, allowDestructive })
|
|
6121
|
+
});
|
|
6122
|
+
if (!res.ok) {
|
|
6123
|
+
const body = await res.json().catch(() => null);
|
|
6124
|
+
console.error(`Schema ${route} failed: ${res.status} ${body?.detail ?? res.statusText}`);
|
|
6125
|
+
if (res.status === 401 || res.status === 403) {
|
|
6126
|
+
console.error(" Set ZVELTIO_API_KEY to a key of an instance admin.");
|
|
6127
|
+
}
|
|
6128
|
+
process.exit(1);
|
|
6129
|
+
}
|
|
6130
|
+
return (await res.json()).steps;
|
|
6131
|
+
}
|
|
6132
|
+
function printSteps(steps) {
|
|
6133
|
+
const width = Math.max(...steps.map((s) => s.target.length));
|
|
6134
|
+
for (const s of steps) {
|
|
6135
|
+
console.log(`${s.change} ${s.target.padEnd(width)} ${s.action}${s.destructive ? " (destructive)" : ""}`);
|
|
6136
|
+
}
|
|
6137
|
+
const destructive = steps.filter((s) => s.destructive).length;
|
|
6138
|
+
console.log(`
|
|
6139
|
+
${steps.length} change(s)` + (destructive ? `, ${destructive} destructive` : ""));
|
|
6140
|
+
}
|
|
6141
|
+
async function schemaDiffCommand(opts) {
|
|
6142
|
+
const dir = opts.dir || "./schema";
|
|
6143
|
+
const engineUrl = opts.url || process.env.ZVELTIO_URL || "http://localhost:3000";
|
|
6144
|
+
const steps = await postSchema(engineUrl, "plan", readSchemaDir(dir));
|
|
6145
|
+
if (!steps.length) {
|
|
6146
|
+
console.log(`No changes: the instance matches ${dir}`);
|
|
6147
|
+
return;
|
|
6148
|
+
}
|
|
6149
|
+
printSteps(steps);
|
|
6150
|
+
process.exit(1);
|
|
6151
|
+
}
|
|
6152
|
+
async function schemaApplyCommand(opts) {
|
|
6153
|
+
const dir = opts.dir || "./schema";
|
|
6154
|
+
const engineUrl = opts.url || process.env.ZVELTIO_URL || "http://localhost:3000";
|
|
6155
|
+
const files = readSchemaDir(dir);
|
|
6156
|
+
const plan = await postSchema(engineUrl, "plan", files);
|
|
6157
|
+
if (!plan.length) {
|
|
6158
|
+
console.log(`No changes: the instance matches ${dir}`);
|
|
6159
|
+
return;
|
|
6160
|
+
}
|
|
6161
|
+
printSteps(plan);
|
|
6162
|
+
if (plan.some((s) => s.destructive) && !opts.allowDestructive) {
|
|
6163
|
+
console.error(`
|
|
6164
|
+
The plan has destructive steps; nothing was changed. Rerun with --allow-destructive.`);
|
|
6165
|
+
process.exit(1);
|
|
6166
|
+
}
|
|
6167
|
+
if (!opts.yes) {
|
|
6168
|
+
const rl2 = createInterface2({ input: process.stdin, output: process.stdout });
|
|
6169
|
+
const answer = await new Promise((resolve4) => rl2.question(`
|
|
6170
|
+
Apply these changes? Type "yes" to continue: `, resolve4));
|
|
6171
|
+
rl2.close();
|
|
6172
|
+
if (answer.trim().toLowerCase() !== "yes") {
|
|
6173
|
+
console.log("Cancelled; nothing was changed.");
|
|
6174
|
+
return;
|
|
6175
|
+
}
|
|
6176
|
+
}
|
|
6177
|
+
const applied = await postSchema(engineUrl, "apply", files, opts.allowDestructive);
|
|
6178
|
+
console.log(`Applied ${applied.length} change(s).`);
|
|
6179
|
+
}
|
|
5849
6180
|
|
|
5850
6181
|
// src/commands/install.ts
|
|
5851
|
-
import { existsSync as
|
|
6182
|
+
import { existsSync as existsSync17 } from "fs";
|
|
5852
6183
|
import { readFile, mkdir as mkdir2, cp } from "fs/promises";
|
|
5853
|
-
import { join as
|
|
6184
|
+
import { join as join16 } from "path";
|
|
5854
6185
|
async function installCommand(name, opts) {
|
|
5855
6186
|
const engineUrl = opts.url || process.env.ENGINE_URL || "http://localhost:3000";
|
|
5856
6187
|
console.log(`
|
|
@@ -5863,13 +6194,13 @@ async function installCommand(name, opts) {
|
|
|
5863
6194
|
await installFromCatalog(name, engineUrl);
|
|
5864
6195
|
}
|
|
5865
6196
|
async function installFromPath(name, sourcePath, engineUrl, force) {
|
|
5866
|
-
if (!
|
|
6197
|
+
if (!existsSync17(join16(sourcePath, "manifest.json"))) {
|
|
5867
6198
|
console.error("\u274C No manifest.json found at", sourcePath);
|
|
5868
6199
|
process.exit(1);
|
|
5869
6200
|
}
|
|
5870
6201
|
let manifest;
|
|
5871
6202
|
try {
|
|
5872
|
-
manifest = JSON.parse(await readFile(
|
|
6203
|
+
manifest = JSON.parse(await readFile(join16(sourcePath, "manifest.json"), "utf-8"));
|
|
5873
6204
|
} catch {
|
|
5874
6205
|
console.error("\u274C Failed to read manifest.json");
|
|
5875
6206
|
process.exit(1);
|
|
@@ -5893,8 +6224,8 @@ async function installFromPath(name, sourcePath, engineUrl, force) {
|
|
|
5893
6224
|
console.log(` Name: ${manifest.displayName || manifest.name}`);
|
|
5894
6225
|
console.log(` Version: ${manifest.version || "?"}`);
|
|
5895
6226
|
console.log(` Category: ${category}`);
|
|
5896
|
-
const enginePkgPath =
|
|
5897
|
-
if (
|
|
6227
|
+
const enginePkgPath = join16(process.cwd(), "packages/engine/package.json");
|
|
6228
|
+
if (existsSync17(enginePkgPath)) {
|
|
5898
6229
|
const enginePkg = JSON.parse(await readFile(enginePkgPath, "utf-8"));
|
|
5899
6230
|
const engineVersion = enginePkg.version || "unknown";
|
|
5900
6231
|
if (manifest.zveltioMinVersion) {
|
|
@@ -5905,8 +6236,8 @@ async function installFromPath(name, sourcePath, engineUrl, force) {
|
|
|
5905
6236
|
}
|
|
5906
6237
|
}
|
|
5907
6238
|
const extName = manifest.name;
|
|
5908
|
-
const targetDir =
|
|
5909
|
-
if (
|
|
6239
|
+
const targetDir = join16(process.cwd(), "extensions", category, extName);
|
|
6240
|
+
if (existsSync17(targetDir) && !force) {
|
|
5910
6241
|
console.error(`\u274C Extension already exists at ${targetDir}`);
|
|
5911
6242
|
console.error(` Use --force to overwrite.`);
|
|
5912
6243
|
process.exit(1);
|
|
@@ -6032,7 +6363,7 @@ async function activateViaApi(name, category, engineUrl) {
|
|
|
6032
6363
|
}
|
|
6033
6364
|
|
|
6034
6365
|
// src/commands/create-god.ts
|
|
6035
|
-
import { createInterface as
|
|
6366
|
+
import { createInterface as createInterface3 } from "readline";
|
|
6036
6367
|
async function prompt(rl2, question) {
|
|
6037
6368
|
return new Promise((resolve4) => {
|
|
6038
6369
|
rl2.question(question, resolve4);
|
|
@@ -6087,7 +6418,7 @@ async function createGodCommand(opts) {
|
|
|
6087
6418
|
console.log(" \u2022 Audit logins for this account separately");
|
|
6088
6419
|
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
6420
|
`);
|
|
6090
|
-
const rlConfirm =
|
|
6421
|
+
const rlConfirm = createInterface3({
|
|
6091
6422
|
input: process.stdin,
|
|
6092
6423
|
output: process.stdout
|
|
6093
6424
|
});
|
|
@@ -6100,7 +6431,7 @@ async function createGodCommand(opts) {
|
|
|
6100
6431
|
console.log(`
|
|
6101
6432
|
Creating God (super-admin) user at ${engineUrl}
|
|
6102
6433
|
`);
|
|
6103
|
-
const rl2 =
|
|
6434
|
+
const rl2 = createInterface3({ input: process.stdin, output: process.stdout });
|
|
6104
6435
|
try {
|
|
6105
6436
|
const email = opts.email || await prompt(rl2, "Email: ");
|
|
6106
6437
|
const name = opts.name || await prompt(rl2, "Name: ");
|
|
@@ -6394,8 +6725,8 @@ var versionCommand = new Command("version").description("Show version informatio
|
|
|
6394
6725
|
});
|
|
6395
6726
|
|
|
6396
6727
|
// src/commands/update.ts
|
|
6397
|
-
import { existsSync as
|
|
6398
|
-
import { join as
|
|
6728
|
+
import { existsSync as existsSync18 } from "fs";
|
|
6729
|
+
import { join as join17 } from "path";
|
|
6399
6730
|
var c12 = {
|
|
6400
6731
|
bold: (s) => `\x1B[1m${s}\x1B[0m`,
|
|
6401
6732
|
green: (s) => `\x1B[32m${s}\x1B[0m`,
|
|
@@ -6409,10 +6740,10 @@ var updateCommand = new Command("update").description("Update Zveltio to the lat
|
|
|
6409
6740
|
console.log(`
|
|
6410
6741
|
${c12.bold("Zveltio Update")}
|
|
6411
6742
|
`);
|
|
6412
|
-
const installDir = opts.dir || process.env.ZVELTIO_DIR ||
|
|
6413
|
-
const metaPath =
|
|
6743
|
+
const installDir = opts.dir || process.env.ZVELTIO_DIR || join17(process.cwd(), "zveltio");
|
|
6744
|
+
const metaPath = join17(installDir, ".zveltio-install.json");
|
|
6414
6745
|
let meta = null;
|
|
6415
|
-
if (
|
|
6746
|
+
if (existsSync18(metaPath)) {
|
|
6416
6747
|
try {
|
|
6417
6748
|
meta = JSON.parse(await Bun.file(metaPath).text());
|
|
6418
6749
|
} catch {}
|
|
@@ -6524,11 +6855,11 @@ ${c12.bold("Zveltio Update")}
|
|
|
6524
6855
|
});
|
|
6525
6856
|
async function getVersionFromPackageJson() {
|
|
6526
6857
|
const candidates = [
|
|
6527
|
-
|
|
6528
|
-
|
|
6858
|
+
join17(process.cwd(), "packages/engine/package.json"),
|
|
6859
|
+
join17(process.cwd(), "package.json")
|
|
6529
6860
|
];
|
|
6530
6861
|
for (const pkgPath of candidates) {
|
|
6531
|
-
if (!
|
|
6862
|
+
if (!existsSync18(pkgPath))
|
|
6532
6863
|
continue;
|
|
6533
6864
|
try {
|
|
6534
6865
|
const pkg = JSON.parse(await Bun.file(pkgPath).text());
|
|
@@ -6551,15 +6882,15 @@ async function getCurrentVersionFromAPI(port) {
|
|
|
6551
6882
|
}
|
|
6552
6883
|
}
|
|
6553
6884
|
async function runUpdateScript(targetVersion, installDir, meta, force) {
|
|
6554
|
-
const envPath =
|
|
6555
|
-
if (
|
|
6885
|
+
const envPath = join17(installDir, ".env");
|
|
6886
|
+
if (existsSync18(envPath)) {
|
|
6556
6887
|
const backupPath = `${envPath}.backup.${Date.now()}`;
|
|
6557
6888
|
const envContent = await Bun.file(envPath).text();
|
|
6558
6889
|
await Bun.write(backupPath, envContent);
|
|
6559
6890
|
console.log(` Backed up .env \u2192 ${backupPath}`);
|
|
6560
6891
|
}
|
|
6561
|
-
const installScript =
|
|
6562
|
-
if (
|
|
6892
|
+
const installScript = join17(installDir, "install.sh");
|
|
6893
|
+
if (existsSync18(installScript)) {
|
|
6563
6894
|
const args = [
|
|
6564
6895
|
"bash",
|
|
6565
6896
|
installScript,
|
|
@@ -6578,7 +6909,7 @@ async function runUpdateScript(targetVersion, installDir, meta, force) {
|
|
|
6578
6909
|
if (code !== 0) {
|
|
6579
6910
|
console.error(`
|
|
6580
6911
|
Update failed. Your previous version is still running.`);
|
|
6581
|
-
if (
|
|
6912
|
+
if (existsSync18(envPath)) {
|
|
6582
6913
|
console.log(` Restore .env: copy ${envPath}.backup.* to ${envPath}`);
|
|
6583
6914
|
}
|
|
6584
6915
|
process.exit(1);
|
|
@@ -6743,14 +7074,14 @@ ${c13.bold("Zveltio Status")}
|
|
|
6743
7074
|
console.log("");
|
|
6744
7075
|
}
|
|
6745
7076
|
async function getLocalVersion() {
|
|
6746
|
-
const { existsSync:
|
|
6747
|
-
const { join:
|
|
7077
|
+
const { existsSync: existsSync19 } = await import("fs");
|
|
7078
|
+
const { join: join18 } = await import("path");
|
|
6748
7079
|
const candidates = [
|
|
6749
|
-
|
|
6750
|
-
|
|
7080
|
+
join18(process.cwd(), "packages/engine/package.json"),
|
|
7081
|
+
join18(process.cwd(), "package.json")
|
|
6751
7082
|
];
|
|
6752
7083
|
for (const p of candidates) {
|
|
6753
|
-
if (!
|
|
7084
|
+
if (!existsSync19(p))
|
|
6754
7085
|
continue;
|
|
6755
7086
|
try {
|
|
6756
7087
|
const pkg = JSON.parse(await Bun.file(p).text());
|
|
@@ -6763,7 +7094,7 @@ async function getLocalVersion() {
|
|
|
6763
7094
|
// package.json
|
|
6764
7095
|
var package_default = {
|
|
6765
7096
|
name: "@zveltio/cli",
|
|
6766
|
-
version: "3.0.0-beta.
|
|
7097
|
+
version: "3.0.0-beta.79",
|
|
6767
7098
|
type: "module",
|
|
6768
7099
|
bin: {
|
|
6769
7100
|
zveltio: "./dist/index.js"
|
|
@@ -6778,7 +7109,7 @@ var package_default = {
|
|
|
6778
7109
|
dev: "bun run --watch src/index.ts"
|
|
6779
7110
|
},
|
|
6780
7111
|
dependencies: {
|
|
6781
|
-
"@zveltio/sdk": "3.0.0-beta.
|
|
7112
|
+
"@zveltio/sdk": "3.0.0-beta.79",
|
|
6782
7113
|
commander: "^12.1.0"
|
|
6783
7114
|
},
|
|
6784
7115
|
devDependencies: {
|
|
@@ -6805,8 +7136,12 @@ program2.addCommand(versionCommand);
|
|
|
6805
7136
|
program2.addCommand(updateCommand);
|
|
6806
7137
|
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
7138
|
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);
|
|
7139
|
+
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);
|
|
7140
|
+
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);
|
|
7141
|
+
var schema = program2.command("schema").description("Schema as code: the live schema as reviewable JSON files");
|
|
7142
|
+
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);
|
|
7143
|
+
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);
|
|
7144
|
+
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
7145
|
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
7146
|
var extensions = program2.command("extensions").description("Manage Zveltio extensions");
|
|
6812
7147
|
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.79",
|
|
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.79",
|
|
19
19
|
"commander": "^12.1.0"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|