@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.
Files changed (2) hide show
  1. package/dist/index.js +358 -43
  2. 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 readFileSync7 } from "fs";
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 = readFileSync7(opts.keyFile, "utf8").trim();
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 type { ZveltioExtension } from '@zveltio/sdk/extension';
3463
+ await writeFile(join8(targetDir, "engine", "index.ts"), `import { defineExtension } from '@zveltio/sdk/extension';
3464
3464
  import { join } from 'path';
3465
3465
 
3466
- const extension: ZveltioExtension = {
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 56 shipped extensions, 61 pages are
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 existsSync15 } from "fs";
6162
+ import { existsSync as existsSync17 } from "fs";
5852
6163
  import { readFile, mkdir as mkdir2, cp } from "fs/promises";
5853
- import { join as join14 } from "path";
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 (!existsSync15(join14(sourcePath, "manifest.json"))) {
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(join14(sourcePath, "manifest.json"), "utf-8"));
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 = join14(process.cwd(), "packages/engine/package.json");
5897
- if (existsSync15(enginePkgPath)) {
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 = join14(process.cwd(), "extensions", category, extName);
5909
- if (existsSync15(targetDir) && !force) {
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 createInterface2 } from "readline";
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 = createInterface2({
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 = createInterface2({ input: process.stdin, output: process.stdout });
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 existsSync16 } from "fs";
6398
- import { join as join15 } from "path";
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 || join15(process.cwd(), "zveltio");
6413
- const metaPath = join15(installDir, ".zveltio-install.json");
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 (existsSync16(metaPath)) {
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
- join15(process.cwd(), "packages/engine/package.json"),
6528
- join15(process.cwd(), "package.json")
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 (!existsSync16(pkgPath))
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 = join15(installDir, ".env");
6555
- if (existsSync16(envPath)) {
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 = join15(installDir, "install.sh");
6562
- if (existsSync16(installScript)) {
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 (existsSync16(envPath)) {
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: existsSync17 } = await import("fs");
6747
- const { join: join16 } = await import("path");
7057
+ const { existsSync: existsSync19 } = await import("fs");
7058
+ const { join: join18 } = await import("path");
6748
7059
  const candidates = [
6749
- join16(process.cwd(), "packages/engine/package.json"),
6750
- join16(process.cwd(), "package.json")
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 (!existsSync17(p))
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.77",
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.77",
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.77",
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.77",
18
+ "@zveltio/sdk": "3.0.0-beta.78",
19
19
  "commander": "^12.1.0"
20
20
  },
21
21
  "devDependencies": {