@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.
Files changed (2) hide show
  1. package/dist/index.js +379 -44
  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 = {
@@ -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\b/im);
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 existsSync15 } from "fs";
6182
+ import { existsSync as existsSync17 } from "fs";
5852
6183
  import { readFile, mkdir as mkdir2, cp } from "fs/promises";
5853
- import { join as join14 } from "path";
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 (!existsSync15(join14(sourcePath, "manifest.json"))) {
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(join14(sourcePath, "manifest.json"), "utf-8"));
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 = join14(process.cwd(), "packages/engine/package.json");
5897
- if (existsSync15(enginePkgPath)) {
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 = join14(process.cwd(), "extensions", category, extName);
5909
- if (existsSync15(targetDir) && !force) {
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 createInterface2 } from "readline";
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 = createInterface2({
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 = createInterface2({ input: process.stdin, output: process.stdout });
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 existsSync16 } from "fs";
6398
- import { join as join15 } from "path";
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 || join15(process.cwd(), "zveltio");
6413
- const metaPath = join15(installDir, ".zveltio-install.json");
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 (existsSync16(metaPath)) {
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
- join15(process.cwd(), "packages/engine/package.json"),
6528
- join15(process.cwd(), "package.json")
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 (!existsSync16(pkgPath))
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 = join15(installDir, ".env");
6555
- if (existsSync16(envPath)) {
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 = join15(installDir, "install.sh");
6562
- if (existsSync16(installScript)) {
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 (existsSync16(envPath)) {
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: existsSync17 } = await import("fs");
6747
- const { join: join16 } = await import("path");
7077
+ const { existsSync: existsSync19 } = await import("fs");
7078
+ const { join: join18 } = await import("path");
6748
7079
  const candidates = [
6749
- join16(process.cwd(), "packages/engine/package.json"),
6750
- join16(process.cwd(), "package.json")
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 (!existsSync17(p))
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.77",
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.77",
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.77",
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.77",
18
+ "@zveltio/sdk": "3.0.0-beta.79",
19
19
  "commander": "^12.1.0"
20
20
  },
21
21
  "devDependencies": {