@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.
Files changed (2) hide show
  1. package/dist/index.js +374 -50
  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);
@@ -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, getLastAppliedMigration } = migrations;
3328
+ const { runMigrations, getAppliedMigrations, pendingMigrations } = migrations;
3329
3329
  const db = await initDatabase();
3330
3330
  if (opts.dryRun) {
3331
- const applied = await getAppliedMigrations(db);
3332
- const lastVersion = await getLastAppliedMigration(db);
3333
- console.log(` Applied migrations: ${applied.length}`);
3334
- console.log(` Last applied version: ${lastVersion}`);
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 All migrations applied.
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 type { ZveltioExtension } from '@zveltio/sdk/extension';
3463
+ await writeFile(join8(targetDir, "engine", "index.ts"), `import { defineExtension } from '@zveltio/sdk/extension';
3461
3464
  import { join } from 'path';
3462
3465
 
3463
- 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([], {
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 56 shipped extensions, 61 pages are
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 existsSync15 } from "fs";
6162
+ import { existsSync as existsSync17 } from "fs";
5849
6163
  import { readFile, mkdir as mkdir2, cp } from "fs/promises";
5850
- import { join as join14 } from "path";
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 (!existsSync15(join14(sourcePath, "manifest.json"))) {
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(join14(sourcePath, "manifest.json"), "utf-8"));
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 = join14(process.cwd(), "packages/engine/package.json");
5894
- if (existsSync15(enginePkgPath)) {
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 = join14(process.cwd(), "extensions", category, extName);
5906
- if (existsSync15(targetDir) && !force) {
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 createInterface2 } from "readline";
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 = createInterface2({
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 = createInterface2({ input: process.stdin, output: process.stdout });
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 existsSync16 } from "fs";
6395
- import { join as join15 } from "path";
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 || join15(process.cwd(), "zveltio");
6410
- 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");
6411
6725
  let meta = null;
6412
- if (existsSync16(metaPath)) {
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
- join15(process.cwd(), "packages/engine/package.json"),
6525
- join15(process.cwd(), "package.json")
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 (!existsSync16(pkgPath))
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 = join15(installDir, ".env");
6552
- if (existsSync16(envPath)) {
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 = join15(installDir, "install.sh");
6559
- if (existsSync16(installScript)) {
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 (existsSync16(envPath)) {
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: existsSync17 } = await import("fs");
6744
- const { join: join16 } = await import("path");
7057
+ const { existsSync: existsSync19 } = await import("fs");
7058
+ const { join: join18 } = await import("path");
6745
7059
  const candidates = [
6746
- join16(process.cwd(), "packages/engine/package.json"),
6747
- join16(process.cwd(), "package.json")
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 (!existsSync17(p))
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.76",
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.76",
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.76",
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.76",
18
+ "@zveltio/sdk": "3.0.0-beta.78",
19
19
  "commander": "^12.1.0"
20
20
  },
21
21
  "devDependencies": {