@zveltio/cli 2.0.0 → 3.0.0-beta.1

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 +2492 -188
  2. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -31,6 +31,20 @@ var __toESM = (mod, isNodeMode, target) => {
31
31
  return to;
32
32
  };
33
33
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
34
+ var __returnValue = (v) => v;
35
+ function __exportSetter(name, newValue) {
36
+ this[name] = __returnValue.bind(null, newValue);
37
+ }
38
+ var __export = (target, all) => {
39
+ for (var name in all)
40
+ __defProp(target, name, {
41
+ get: all[name],
42
+ enumerable: true,
43
+ configurable: true,
44
+ set: __exportSetter.bind(all, name)
45
+ });
46
+ };
47
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
34
48
  var __require = import.meta.require;
35
49
 
36
50
  // ../../node_modules/.bun/commander@12.1.0/node_modules/commander/lib/error.js
@@ -1872,6 +1886,649 @@ var require_commander = __commonJS((exports) => {
1872
1886
  exports.InvalidOptionArgumentError = InvalidArgumentError;
1873
1887
  });
1874
1888
 
1889
+ // src/lib/publisher-tier.ts
1890
+ function tierAllowsInline(tier) {
1891
+ return tier === "first-party" || tier === "verified";
1892
+ }
1893
+ async function resolvePublisherTier(opts = {}) {
1894
+ if (opts.firstParty) {
1895
+ return { tier: "first-party", allowsInline: true, source: "flag" };
1896
+ }
1897
+ const token = opts.token ?? process.env.ZVELTIO_REGISTRY_TOKEN;
1898
+ const registryUrl = opts.registryUrl ?? process.env.ZVELTIO_REGISTRY_URL ?? "https://registry.zveltio.com";
1899
+ if (token) {
1900
+ try {
1901
+ const base = registryUrl.replace(/\/$/, "");
1902
+ const url = new URL(`${base}/api/dev/publisher/self`);
1903
+ if (opts.keyId)
1904
+ url.searchParams.set("keyId", opts.keyId);
1905
+ const res = await fetch(url.toString(), {
1906
+ headers: { Authorization: `Bearer ${token}` },
1907
+ signal: AbortSignal.timeout(5000)
1908
+ });
1909
+ if (res.ok) {
1910
+ const data = await res.json();
1911
+ const tier = normalizeTier(data.tier);
1912
+ if (tier) {
1913
+ return { tier, allowsInline: tierAllowsInline(tier), source: "registry" };
1914
+ }
1915
+ }
1916
+ } catch {}
1917
+ }
1918
+ return { tier: "community", allowsInline: false, source: "default" };
1919
+ }
1920
+ function normalizeTier(v) {
1921
+ return v === "first-party" || v === "verified" || v === "community" ? v : null;
1922
+ }
1923
+
1924
+ // ../sdk/dist/build/index.js
1925
+ import { createRequire } from "module";
1926
+ import { existsSync as existsSync8, readFileSync as readFileSync3 } from "fs";
1927
+ import { dirname, join as join8 } from "path";
1928
+ function escapeRegExp(s) {
1929
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1930
+ }
1931
+ function pickExportTarget(target) {
1932
+ if (typeof target === "string")
1933
+ return target;
1934
+ if (target && typeof target === "object") {
1935
+ const o = target;
1936
+ if (typeof o.import === "string")
1937
+ return o.import;
1938
+ if (typeof o.default === "string")
1939
+ return o.default;
1940
+ if (o.import)
1941
+ return pickExportTarget(o.import);
1942
+ }
1943
+ return;
1944
+ }
1945
+ function resolveSearchPaths(resolveFromDir) {
1946
+ const paths = [resolveFromDir];
1947
+ let cur = resolveFromDir;
1948
+ for (let i = 0;i < 6; i++) {
1949
+ cur = dirname(cur);
1950
+ paths.push(cur);
1951
+ if (existsSync8(join8(cur, "node_modules")))
1952
+ break;
1953
+ }
1954
+ return [...new Set(paths)];
1955
+ }
1956
+ function resolvePackageRoot(pkg, searchPaths) {
1957
+ let candidate;
1958
+ try {
1959
+ candidate = dirname(require2.resolve(pkg, { paths: searchPaths }));
1960
+ } catch {
1961
+ throw new Error(`Cannot find package "${pkg}" from ${searchPaths[0]}. ` + `Run \`bun install\` in the extension directory or monorepo root.`);
1962
+ }
1963
+ for (;; ) {
1964
+ const pkgJsonPath = join8(candidate, "package.json");
1965
+ if (existsSync8(pkgJsonPath)) {
1966
+ const manifest = JSON.parse(readFileSync3(pkgJsonPath, "utf8"));
1967
+ if (manifest.name === pkg)
1968
+ return candidate;
1969
+ }
1970
+ const parent = dirname(candidate);
1971
+ if (parent === candidate)
1972
+ break;
1973
+ candidate = parent;
1974
+ }
1975
+ throw new Error(`Cannot find package root for "${pkg}"`);
1976
+ }
1977
+ function resolvePackageEsmEntry(pkg, resolveFromDir) {
1978
+ const searchPaths = resolveSearchPaths(resolveFromDir);
1979
+ const root = resolvePackageRoot(pkg, searchPaths);
1980
+ const pkgJsonPath = join8(root, "package.json");
1981
+ const manifest = JSON.parse(readFileSync3(pkgJsonPath, "utf8"));
1982
+ const dot = manifest.exports?.["."];
1983
+ let rel = pickExportTarget(dot);
1984
+ rel ??= manifest.module ?? manifest.main;
1985
+ if (!rel || typeof rel !== "string") {
1986
+ throw new Error(`Cannot determine ESM entry for package "${pkg}"`);
1987
+ }
1988
+ const abs = join8(root, rel.replace(/^\.\//, ""));
1989
+ if (!existsSync8(abs)) {
1990
+ throw new Error(`ESM entry missing for "${pkg}": ${abs}`);
1991
+ }
1992
+ if (abs.endsWith(".d.ts")) {
1993
+ throw new Error(`Package "${pkg}" resolved to a declaration file (${abs}). ` + `Expected exports.import or module field to point at JavaScript.`);
1994
+ }
1995
+ return abs;
1996
+ }
1997
+ function createExtensionBundleResolvePlugin(resolveFromDir) {
1998
+ return {
1999
+ name: "zveltio-extension-bundle-resolve",
2000
+ setup(build) {
2001
+ for (const pkg of EXTENSION_BUNDLE_CORE_DEPS) {
2002
+ const filter = new RegExp(`^${escapeRegExp(pkg)}$`);
2003
+ build.onResolve({ filter }, () => ({
2004
+ path: resolvePackageEsmEntry(pkg, resolveFromDir)
2005
+ }));
2006
+ }
2007
+ }
2008
+ };
2009
+ }
2010
+ function createExtensionBuildConfig(opts) {
2011
+ return {
2012
+ entrypoints: [opts.entry],
2013
+ outdir: opts.outdir,
2014
+ target: "bun",
2015
+ format: "esm",
2016
+ sourcemap: opts.sourcemap ? "linked" : "none",
2017
+ external: opts.external ?? [],
2018
+ conditions: [...BUNDLE_CONDITIONS],
2019
+ plugins: [createExtensionBundleResolvePlugin(opts.resolveDir)]
2020
+ };
2021
+ }
2022
+ var require2, EXTENSION_BUNDLE_CORE_DEPS, BUNDLE_CONDITIONS;
2023
+ var init_build = __esm(() => {
2024
+ require2 = createRequire(import.meta.url);
2025
+ EXTENSION_BUNDLE_CORE_DEPS = [
2026
+ "hono",
2027
+ "zod",
2028
+ "kysely",
2029
+ "@hono/zod-validator"
2030
+ ];
2031
+ BUNDLE_CONDITIONS = ["import", "bun", "node", "default"];
2032
+ });
2033
+
2034
+ // src/lib/extension-bundle.ts
2035
+ import { existsSync as existsSync9, renameSync } from "fs";
2036
+ import { basename, dirname as dirname2, join as join9 } from "path";
2037
+ async function bundleExtensionEngine(opts) {
2038
+ const outdir = dirname2(opts.outfile);
2039
+ const entryBase = basename(opts.entry).replace(/\.[^.]+$/, "");
2040
+ const builtPath = join9(outdir, `${entryBase}.js`);
2041
+ const result = await Bun.build({
2042
+ ...createExtensionBuildConfig({
2043
+ entry: opts.entry,
2044
+ outdir,
2045
+ resolveDir: opts.resolveDir,
2046
+ external: opts.external,
2047
+ sourcemap: opts.sourcemap
2048
+ }),
2049
+ naming: `${entryBase}.[ext]`
2050
+ });
2051
+ if (!result.success) {
2052
+ const messages = result.logs.map((l) => l.message).join(`
2053
+ `);
2054
+ throw new Error(messages || "Bun.build failed");
2055
+ }
2056
+ if (!existsSync9(builtPath)) {
2057
+ throw new Error(`Bun.build succeeded but ${builtPath} was not produced`);
2058
+ }
2059
+ if (builtPath !== opts.outfile) {
2060
+ renameSync(builtPath, opts.outfile);
2061
+ }
2062
+ }
2063
+ var EXTENSION_BUNDLE_CORE_DEPS2;
2064
+ var init_extension_bundle = __esm(() => {
2065
+ init_build();
2066
+ EXTENSION_BUNDLE_CORE_DEPS2 = EXTENSION_BUNDLE_CORE_DEPS;
2067
+ });
2068
+
2069
+ // src/commands/extension-pack.ts
2070
+ var exports_extension_pack = {};
2071
+ __export(exports_extension_pack, {
2072
+ extensionPackCommand: () => extensionPackCommand
2073
+ });
2074
+ import { createHash } from "crypto";
2075
+ import { existsSync as existsSync10, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
2076
+ import { join as join10, resolve } from "path";
2077
+ function readManifest(dir) {
2078
+ const path = join10(dir, "manifest.json");
2079
+ if (!existsSync10(path)) {
2080
+ throw new Error(`No manifest.json at ${path}.`);
2081
+ }
2082
+ return JSON.parse(readFileSync4(path, "utf8"));
2083
+ }
2084
+ function writeManifest(dir, m) {
2085
+ writeFileSync2(join10(dir, "manifest.json"), `${JSON.stringify(m, null, 2)}
2086
+ `, "utf8");
2087
+ }
2088
+ function validatePeerDeps(m) {
2089
+ const peers = Object.keys(m.peerDependencies ?? {});
2090
+ if (peers.length === 0)
2091
+ return [];
2092
+ const bundlePeers = m.engine?.bundlePeers === true;
2093
+ if (!bundlePeers) {
2094
+ return peers;
2095
+ }
2096
+ return [];
2097
+ }
2098
+ async function extensionPackCommand(opts) {
2099
+ const dir = resolve(opts.dir ?? process.cwd());
2100
+ const entry = join10(dir, "engine", "index.ts");
2101
+ const outfile = join10(dir, "engine", "index.js");
2102
+ if (!existsSync10(entry)) {
2103
+ throw new Error(`No engine/index.ts at ${entry}. ` + `Run from the extension root or pass --dir. Extensions without an engine entry don't need pack.`);
2104
+ }
2105
+ const manifest = readManifest(dir);
2106
+ console.log(`\uD83D\uDCE6 Packing ${c7.bold(manifest.name)} v${manifest.version}`);
2107
+ const violations = validatePeerDeps(manifest);
2108
+ if (violations.length > 0) {
2109
+ throw new Error(`peerDependencies declared without engine.bundlePeers=true:
2110
+ ` + violations.map((v) => ` - ${v}`).join(`
2111
+ `) + `
2112
+
2113
+ ` + `Bun compiled binary cannot resolve bare specifiers from a ` + `dynamically-imported extension bundle, so external peer deps don't ` + `work in production. Either set engine.bundlePeers=true in ` + `manifest.json (and install the deps locally so Bun.build can ` + `inline them), or drop the peerDependencies entry if the import ` + `is dead code.`);
2114
+ }
2115
+ const externals = [];
2116
+ console.log(` ${c7.dim(`$ bun build ${entry} (zveltio extension-bundle plugin)`)}` + (externals.length > 0 ? c7.dim(` external:${externals.join(",")}`) : ""));
2117
+ try {
2118
+ await bundleExtensionEngine({
2119
+ entry,
2120
+ outfile,
2121
+ external: externals,
2122
+ sourcemap: opts.sourcemap,
2123
+ resolveDir: dir
2124
+ });
2125
+ } catch (err2) {
2126
+ throw new Error(`Bun bundle failed: ${err2.message}`);
2127
+ }
2128
+ const bundleBytes = readFileSync4(outfile);
2129
+ const engineSha256 = createHash("sha256").update(bundleBytes).digest("hex");
2130
+ const bundleText = bundleBytes.toString("utf8").replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|\s)\/\/[^\n]*/g, "$1");
2131
+ for (const dep of CORE_DEPS) {
2132
+ const baseImportRe = new RegExp(`(?:from|import\\()\\s*['"]${dep.replace(/[.*+?^${}()|[\\]/g, "\\$&")}['"]`, "m");
2133
+ if (baseImportRe.test(bundleText)) {
2134
+ throw new Error(`Bundled output still contains a bare import of '${dep}'. ` + `Bun couldn't bundle it \u2014 check that the dep is installed in node_modules ` + `(extension dir or a parent workspace).`);
2135
+ }
2136
+ }
2137
+ const sizeKb = Math.round(bundleBytes.byteLength / 1024);
2138
+ console.log(` ${c7.green("\u2713")} ${c7.bold(`engine/index.js`)} ${c7.dim(`(${sizeKb} KB, sha256=${engineSha256.slice(0, 12)}\u2026)`)}`);
2139
+ let resolvedIsolation = manifest.engine?.isolation;
2140
+ if (!resolvedIsolation) {
2141
+ const resolved = await resolvePublisherTier({
2142
+ firstParty: opts.firstParty,
2143
+ token: opts.token,
2144
+ registryUrl: opts.registryUrl
2145
+ });
2146
+ if (!resolved.allowsInline) {
2147
+ resolvedIsolation = "worker";
2148
+ console.log(` ${c7.yellow("\u26A0")} ${resolved.tier} publisher \u2014 auto-set ${c7.bold('engine.isolation: "worker"')} ` + `(community extensions can't run inline).`);
2149
+ if (resolved.source === "default") {
2150
+ console.log(c7.dim(" Tier defaulted to community (no --first-party flag, no registry token). " + "If you are verified/first-party, pass --first-party or set ZVELTIO_REGISTRY_TOKEN."));
2151
+ }
2152
+ }
2153
+ }
2154
+ if (!opts.noManifestUpdate) {
2155
+ manifest.engine = {
2156
+ entry: "engine/index.js",
2157
+ format: "esm",
2158
+ target: "bun",
2159
+ bundled: true,
2160
+ bundlePeers: manifest.engine?.bundlePeers ?? false,
2161
+ ...resolvedIsolation ? { isolation: resolvedIsolation } : {}
2162
+ };
2163
+ manifest.integrity = {
2164
+ engineSha256,
2165
+ ...manifest.integrity?.archiveSha256 ? { archiveSha256: manifest.integrity.archiveSha256 } : {}
2166
+ };
2167
+ writeManifest(dir, manifest);
2168
+ console.log(` ${c7.green("\u2713")} manifest.json updated with engine + integrity blocks`);
2169
+ }
2170
+ console.log(`${c7.green("\u2713")} pack complete`);
2171
+ }
2172
+ var PEER_DEP_ALLOWLIST, CORE_DEPS, c7;
2173
+ var init_extension_pack = __esm(() => {
2174
+ init_extension_bundle();
2175
+ PEER_DEP_ALLOWLIST = new Set;
2176
+ CORE_DEPS = [...EXTENSION_BUNDLE_CORE_DEPS2];
2177
+ c7 = {
2178
+ bold: (s) => `\x1B[1m${s}\x1B[0m`,
2179
+ green: (s) => `\x1B[32m${s}\x1B[0m`,
2180
+ red: (s) => `\x1B[31m${s}\x1B[0m`,
2181
+ yellow: (s) => `\x1B[33m${s}\x1B[0m`,
2182
+ dim: (s) => `\x1B[2m${s}\x1B[0m`
2183
+ };
2184
+ });
2185
+
2186
+ // src/commands/extension-status.ts
2187
+ var exports_extension_status = {};
2188
+ __export(exports_extension_status, {
2189
+ extensionStatusCommand: () => extensionStatusCommand
2190
+ });
2191
+ async function extensionStatusCommand(name, opts = {}) {
2192
+ const registryUrl = (opts.registryUrl ?? process.env.ZVELTIO_REGISTRY_URL ?? "https://registry.zveltio.com").replace(/\/$/, "");
2193
+ console.log(`
2194
+ ${c14.bold("Extension status")}
2195
+ `);
2196
+ console.log(` Name: ${c14.bold(name)}`);
2197
+ console.log(` Registry: ${c14.dim(registryUrl)}
2198
+ `);
2199
+ const listRes = await fetch(`${registryUrl}/api/extensions/list`).catch(() => null);
2200
+ if (listRes?.ok) {
2201
+ const body = await listRes.json();
2202
+ const found = body.extensions.find((e) => e.name === name);
2203
+ if (found) {
2204
+ console.log(` Status: ${c14.green("published")} (v${found.version})`);
2205
+ console.log(` ${c14.dim("Live in marketplace. Engines can install + enable it.")}`);
2206
+ console.log("");
2207
+ return;
2208
+ }
2209
+ }
2210
+ const detailRes = await fetch(`${registryUrl}/api/dev/extensions/by-name/${encodeURIComponent(name)}`).catch(() => null);
2211
+ if (detailRes?.ok) {
2212
+ const body = await detailRes.json();
2213
+ const statusColor = body.status === "published" ? c14.green : body.status === "pending" ? c14.yellow : c14.red;
2214
+ console.log(` Status: ${statusColor(body.status)} (v${body.version})`);
2215
+ console.log(` Submitted: ${c14.dim(body.created_at)}`);
2216
+ if (body.rejection_reason) {
2217
+ console.log(` ${c14.red("Rejection:")} ${body.rejection_reason}`);
2218
+ }
2219
+ if (body.taken_down_reason) {
2220
+ console.log(` ${c14.red("Takedown:")} ${body.taken_down_reason}`);
2221
+ }
2222
+ switch (body.status) {
2223
+ case "pending":
2224
+ console.log(c14.dim(" Awaiting marketplace admin review."));
2225
+ break;
2226
+ case "published":
2227
+ console.log(c14.dim(" Live in marketplace."));
2228
+ break;
2229
+ case "rejected":
2230
+ console.log(c14.dim(" Address the reason above and re-submit."));
2231
+ break;
2232
+ case "taken_down":
2233
+ console.log(c14.dim(" Removed by admin. Contact support to dispute."));
2234
+ break;
2235
+ }
2236
+ console.log("");
2237
+ return;
2238
+ }
2239
+ console.log(c14.yellow(` \u26A0 Extension "${name}" not found in the marketplace.`));
2240
+ console.log(c14.dim(` - If you just submitted, the registry may take a moment to index.
2241
+ ` + ` - If you publish under a developer account, sign in to the registry web UI to see pending/rejected submissions.
2242
+ ` + " - First-party / bundled extensions are not surfaced by this command \u2014 they ship with the engine binary directly."));
2243
+ console.log("");
2244
+ }
2245
+ var c14;
2246
+ var init_extension_status = __esm(() => {
2247
+ c14 = {
2248
+ bold: (s) => `\x1B[1m${s}\x1B[0m`,
2249
+ green: (s) => `\x1B[32m${s}\x1B[0m`,
2250
+ red: (s) => `\x1B[31m${s}\x1B[0m`,
2251
+ yellow: (s) => `\x1B[33m${s}\x1B[0m`,
2252
+ dim: (s) => `\x1B[2m${s}\x1B[0m`
2253
+ };
2254
+ });
2255
+
2256
+ // src/commands/admin-marketplace.ts
2257
+ var exports_admin_marketplace = {};
2258
+ __export(exports_admin_marketplace, {
2259
+ adminTeamSetRole: () => adminTeamSetRole,
2260
+ adminTeamRemove: () => adminTeamRemove,
2261
+ adminTeamList: () => adminTeamList,
2262
+ adminTeamAdd: () => adminTeamAdd,
2263
+ adminMarketplaceTakedown: () => adminMarketplaceTakedown,
2264
+ adminMarketplaceReject: () => adminMarketplaceReject,
2265
+ adminMarketplacePublishers: () => adminMarketplacePublishers,
2266
+ adminMarketplacePending: () => adminMarketplacePending,
2267
+ adminMarketplaceEnrollPublisher: () => adminMarketplaceEnrollPublisher,
2268
+ adminMarketplaceApprove: () => adminMarketplaceApprove
2269
+ });
2270
+ import { readFileSync as readFileSync7 } from "fs";
2271
+ function getRegistryUrl(opts) {
2272
+ return opts.registryUrl ?? process.env.ZVELTIO_REGISTRY_URL ?? "https://registry.zveltio.com";
2273
+ }
2274
+ function getCookie(opts) {
2275
+ const cookie = opts.cookie ?? process.env.ZVELTIO_ADMIN_COOKIE;
2276
+ if (!cookie) {
2277
+ console.error(c15.red("No admin session cookie. Sign in to the registry web UI and pass --cookie or set ZVELTIO_ADMIN_COOKIE."));
2278
+ process.exit(1);
2279
+ }
2280
+ return cookie;
2281
+ }
2282
+ async function adminFetch(opts, path, init = {}) {
2283
+ return fetch(`${getRegistryUrl(opts).replace(/\/$/, "")}${path}`, {
2284
+ ...init,
2285
+ headers: {
2286
+ ...init.headers,
2287
+ Cookie: getCookie(opts)
2288
+ }
2289
+ });
2290
+ }
2291
+ async function resolveExtensionId(opts, nameOrId) {
2292
+ if (/^[0-9a-f-]{36}$/i.test(nameOrId))
2293
+ return nameOrId;
2294
+ const res = await adminFetch(opts, "/api/admin/pending");
2295
+ if (!res.ok) {
2296
+ throw new Error(`Could not list pending to resolve name: ${res.status}`);
2297
+ }
2298
+ const body = await res.json();
2299
+ const found = body.pending.find((e) => e.name === nameOrId);
2300
+ if (!found) {
2301
+ throw new Error(`No pending extension named "${nameOrId}". Use the id directly if it's already approved.`);
2302
+ }
2303
+ return found.id;
2304
+ }
2305
+ async function adminMarketplacePending(opts) {
2306
+ const res = await adminFetch(opts, "/api/admin/pending");
2307
+ if (!res.ok) {
2308
+ console.error(c15.red(`Registry returned ${res.status}: ${await res.text()}`));
2309
+ process.exit(1);
2310
+ }
2311
+ const body = await res.json();
2312
+ console.log(`
2313
+ ${c15.bold("Pending submissions:")} ${body.count}
2314
+ `);
2315
+ if (body.count === 0) {
2316
+ console.log(c15.dim(" (review queue empty)"));
2317
+ return;
2318
+ }
2319
+ for (const e of body.pending) {
2320
+ console.log(` ${c15.bold(e.name)} v${e.version} ${c15.dim(`(${e.category})`)}`);
2321
+ console.log(` id: ${c15.dim(e.id)}`);
2322
+ console.log(` publisher: ${e.publisher_name ?? "(no name)"} <${e.publisher_email}>`);
2323
+ console.log(` submitted: ${c15.dim(e.created_at)}
2324
+ `);
2325
+ }
2326
+ }
2327
+ async function adminMarketplaceApprove(nameOrId, opts) {
2328
+ const id = await resolveExtensionId(opts, nameOrId);
2329
+ const res = await adminFetch(opts, `/api/admin/approve/${id}`, {
2330
+ method: "POST",
2331
+ headers: { "Content-Type": "application/json" },
2332
+ body: JSON.stringify({ note: opts.note ?? null })
2333
+ });
2334
+ if (!res.ok) {
2335
+ console.error(c15.red(`Approve failed (${res.status}): ${await res.text()}`));
2336
+ process.exit(1);
2337
+ }
2338
+ console.log(c15.green(`\u2713 Approved ${nameOrId}`));
2339
+ }
2340
+ async function adminMarketplaceReject(nameOrId, opts) {
2341
+ if (!opts.reason) {
2342
+ console.error(c15.red("--reason is required for reject"));
2343
+ process.exit(1);
2344
+ }
2345
+ const id = await resolveExtensionId(opts, nameOrId);
2346
+ const res = await adminFetch(opts, `/api/admin/reject/${id}`, {
2347
+ method: "POST",
2348
+ headers: { "Content-Type": "application/json" },
2349
+ body: JSON.stringify({ reason: opts.reason })
2350
+ });
2351
+ if (!res.ok) {
2352
+ console.error(c15.red(`Reject failed (${res.status}): ${await res.text()}`));
2353
+ process.exit(1);
2354
+ }
2355
+ console.log(c15.green(`\u2713 Rejected ${nameOrId}`));
2356
+ }
2357
+ async function adminMarketplaceTakedown(nameOrId, opts) {
2358
+ if (!opts.reason) {
2359
+ console.error(c15.red("--reason is required for takedown"));
2360
+ process.exit(1);
2361
+ }
2362
+ if (!/^[0-9a-f-]{36}$/i.test(nameOrId)) {
2363
+ console.error(c15.red(`Takedown requires the extension id (UUID), not the name. Look it up at ${getRegistryUrl(opts)}/extensions/${encodeURIComponent(nameOrId)}.`));
2364
+ process.exit(1);
2365
+ }
2366
+ const res = await adminFetch(opts, `/api/admin/takedown/${nameOrId}`, {
2367
+ method: "POST",
2368
+ headers: { "Content-Type": "application/json" },
2369
+ body: JSON.stringify({ reason: opts.reason })
2370
+ });
2371
+ if (!res.ok) {
2372
+ console.error(c15.red(`Takedown failed (${res.status}): ${await res.text()}`));
2373
+ process.exit(1);
2374
+ }
2375
+ console.log(c15.green(`\u2713 Taken down ${nameOrId}`));
2376
+ }
2377
+ async function adminMarketplacePublishers(opts) {
2378
+ const res = await adminFetch(opts, "/api/admin/publishers");
2379
+ if (!res.ok) {
2380
+ console.error(c15.red(`Registry returned ${res.status}: ${await res.text()}`));
2381
+ process.exit(1);
2382
+ }
2383
+ const body = await res.json();
2384
+ console.log(`
2385
+ ${c15.bold("Enrolled publishers:")} ${body.count}
2386
+ `);
2387
+ if (body.count === 0) {
2388
+ console.log(c15.dim(" (no publishers enrolled \u2014 only first-party SYNC_TOKEN flow active)"));
2389
+ return;
2390
+ }
2391
+ for (const p of body.publishers) {
2392
+ const statusColor = p.status === "active" ? c15.green : p.status === "suspended" ? c15.yellow : c15.red;
2393
+ console.log(` ${c15.bold(p.publisher_name)} ${c15.dim(`(${p.contact_email})`)} [${statusColor(p.status)}]`);
2394
+ console.log(` tier: ${p.tier}`);
2395
+ console.log(` key_id: ${c15.dim(p.key_id)}`);
2396
+ console.log(` id: ${c15.dim(p.id)}`);
2397
+ console.log(` enrolled: ${c15.dim(p.enrolled_at)}
2398
+ `);
2399
+ }
2400
+ }
2401
+ async function adminTeamList(opts) {
2402
+ const res = await adminFetch(opts, "/api/admin/team");
2403
+ if (!res.ok) {
2404
+ console.error(c15.red(`Registry returned ${res.status}: ${await res.text()}`));
2405
+ process.exit(1);
2406
+ }
2407
+ const body = await res.json();
2408
+ console.log(`
2409
+ ${c15.bold("Admin team:")} ${body.count}
2410
+ `);
2411
+ if (body.count === 0) {
2412
+ console.log(c15.dim(" (empty \u2014 sign in with the ADMIN_EMAIL user to bootstrap)"));
2413
+ return;
2414
+ }
2415
+ for (const m of body.team) {
2416
+ const roleColor = m.role === "owner" ? c15.green : c15.dim;
2417
+ console.log(` ${c15.bold(m.name ?? m.email)} ${c15.dim(`(${m.email})`)} [${roleColor(m.role)}]`);
2418
+ console.log(` added: ${c15.dim(m.added_at)}${m.added_by_email ? ` by ${m.added_by_email}` : " (bootstrap)"}`);
2419
+ if (m.notes)
2420
+ console.log(` notes: ${c15.dim(m.notes)}`);
2421
+ console.log("");
2422
+ }
2423
+ }
2424
+ async function adminTeamAdd(email, opts) {
2425
+ const res = await adminFetch(opts, "/api/admin/team", {
2426
+ method: "POST",
2427
+ headers: { "Content-Type": "application/json" },
2428
+ body: JSON.stringify({
2429
+ email,
2430
+ role: opts.role ?? "admin",
2431
+ notes: opts.notes ?? null
2432
+ })
2433
+ });
2434
+ if (!res.ok) {
2435
+ console.error(c15.red(`Add failed (${res.status}): ${await res.text()}`));
2436
+ process.exit(1);
2437
+ }
2438
+ console.log(c15.green(`\u2713 Added ${email} as ${opts.role ?? "admin"}`));
2439
+ }
2440
+ async function adminTeamSetRole(email, role, opts) {
2441
+ const listRes = await adminFetch(opts, "/api/admin/team");
2442
+ if (!listRes.ok) {
2443
+ console.error(c15.red(`Could not list team: ${listRes.status}`));
2444
+ process.exit(1);
2445
+ }
2446
+ const body = await listRes.json();
2447
+ const match = body.team.find((m) => m.email.toLowerCase() === email.toLowerCase());
2448
+ if (!match) {
2449
+ console.error(c15.red(`No team member with email ${email}`));
2450
+ process.exit(1);
2451
+ }
2452
+ const res = await adminFetch(opts, `/api/admin/team/${match.user_id}`, {
2453
+ method: "PATCH",
2454
+ headers: { "Content-Type": "application/json" },
2455
+ body: JSON.stringify({ role })
2456
+ });
2457
+ if (!res.ok) {
2458
+ console.error(c15.red(`Role change failed (${res.status}): ${await res.text()}`));
2459
+ process.exit(1);
2460
+ }
2461
+ console.log(c15.green(`\u2713 ${email} is now ${role}`));
2462
+ }
2463
+ async function adminTeamRemove(email, opts) {
2464
+ const listRes = await adminFetch(opts, "/api/admin/team");
2465
+ if (!listRes.ok) {
2466
+ console.error(c15.red(`Could not list team: ${listRes.status}`));
2467
+ process.exit(1);
2468
+ }
2469
+ const body = await listRes.json();
2470
+ const match = body.team.find((m) => m.email.toLowerCase() === email.toLowerCase());
2471
+ if (!match) {
2472
+ console.error(c15.red(`No team member with email ${email}`));
2473
+ process.exit(1);
2474
+ }
2475
+ const res = await adminFetch(opts, `/api/admin/team/${match.user_id}`, {
2476
+ method: "DELETE"
2477
+ });
2478
+ if (!res.ok) {
2479
+ console.error(c15.red(`Remove failed (${res.status}): ${await res.text()}`));
2480
+ process.exit(1);
2481
+ }
2482
+ console.log(c15.green(`\u2713 Removed ${email} from admin team`));
2483
+ }
2484
+ async function adminMarketplaceEnrollPublisher(opts) {
2485
+ if (!opts.name || !opts.email || !opts.keyId || !opts.keyFile) {
2486
+ console.error(c15.red("--name, --email, --key-id, --key-file required for enroll-publisher"));
2487
+ process.exit(1);
2488
+ }
2489
+ let keyJwk;
2490
+ try {
2491
+ keyJwk = readFileSync7(opts.keyFile, "utf8").trim();
2492
+ } catch (err2) {
2493
+ console.error(c15.red(`Could not read key file ${opts.keyFile}: ${err2.message}`));
2494
+ process.exit(1);
2495
+ }
2496
+ try {
2497
+ JSON.parse(keyJwk);
2498
+ } catch {
2499
+ console.error(c15.red(`Key file ${opts.keyFile} is not valid JSON`));
2500
+ process.exit(1);
2501
+ }
2502
+ const res = await adminFetch(opts, "/api/admin/publishers", {
2503
+ method: "POST",
2504
+ headers: { "Content-Type": "application/json" },
2505
+ body: JSON.stringify({
2506
+ publisher_name: opts.name,
2507
+ contact_email: opts.email,
2508
+ public_key_jwk: keyJwk,
2509
+ key_id: opts.keyId,
2510
+ tier: opts.tier ?? "community",
2511
+ notes: opts.notes ?? null
2512
+ })
2513
+ });
2514
+ if (!res.ok) {
2515
+ console.error(c15.red(`Enroll failed (${res.status}): ${await res.text()}`));
2516
+ process.exit(1);
2517
+ }
2518
+ const body = await res.json();
2519
+ console.log(c15.green(`\u2713 Publisher enrolled \u2014 id ${body.id}`));
2520
+ }
2521
+ var c15;
2522
+ var init_admin_marketplace = __esm(() => {
2523
+ c15 = {
2524
+ bold: (s) => `\x1B[1m${s}\x1B[0m`,
2525
+ green: (s) => `\x1B[32m${s}\x1B[0m`,
2526
+ red: (s) => `\x1B[31m${s}\x1B[0m`,
2527
+ yellow: (s) => `\x1B[33m${s}\x1B[0m`,
2528
+ dim: (s) => `\x1B[2m${s}\x1B[0m`
2529
+ };
2530
+ });
2531
+
1875
2532
  // ../../node_modules/.bun/commander@12.1.0/node_modules/commander/esm.mjs
1876
2533
  var import__ = __toESM(require_commander(), 1);
1877
2534
  var {
@@ -1983,6 +2640,31 @@ ${c.bold(c.cyan("Zveltio Init"))}
1983
2640
  const portStr = await askWithDefault(iface, `Port ${c.dim("[3000]")}: `, "3000");
1984
2641
  const port = parseInt(portStr, 10) || 3000;
1985
2642
  const enableStudio = await askYesNo(iface, "Enable Studio (admin UI)?", true);
2643
+ const enableIntranet = await askYesNo(iface, "Enable Employee Intranet zone? (portal for non-admin employees)", false);
2644
+ const enableClientPortal = await askYesNo(iface, "Enable Client Portal? (customer-facing portal with multiple templates)", false);
2645
+ let clientPortalTemplate = "generic";
2646
+ if (enableClientPortal) {
2647
+ console.log("");
2648
+ console.log(c.dim(" Available templates:"));
2649
+ console.log(c.dim(" 1. generic \u2014 Dashboard + support tickets + profile"));
2650
+ console.log(c.dim(" 2. saas \u2014 Subscription management + support"));
2651
+ console.log(c.dim(" 3. services \u2014 Professional services portal"));
2652
+ console.log(c.dim(" 4. regulatory \u2014 Authorizations, inspections, business locations"));
2653
+ console.log("");
2654
+ const tmplAnswer = await ask(iface, ` Template ${c.dim("[1-4, default: 1]")}: `);
2655
+ const tmplMap = {
2656
+ "1": "generic",
2657
+ "2": "saas",
2658
+ "3": "services",
2659
+ "4": "regulatory",
2660
+ generic: "generic",
2661
+ saas: "saas",
2662
+ services: "services",
2663
+ regulatory: "regulatory"
2664
+ };
2665
+ clientPortalTemplate = tmplMap[tmplAnswer.trim()] ?? "generic";
2666
+ console.log(` ${c.green("\u2714")} Template: ${clientPortalTemplate}`);
2667
+ }
1986
2668
  const officialExtensions = await extensionsPromise;
1987
2669
  let selectedExtensions = [];
1988
2670
  if (officialExtensions.length > 0) {
@@ -2004,7 +2686,7 @@ Directory "${name}" already exists`));
2004
2686
  const s3SecretKey = crypto.randomUUID().replace(/-/g, "");
2005
2687
  const authSecret = crypto.randomUUID().replace(/-/g, "");
2006
2688
  const extensionsValue = selectedExtensions.join(",");
2007
- await Bun.write(join(dir, ".env"), [
2689
+ const envLines = [
2008
2690
  "# WARNING: NEVER commit this file to git \u2014 it contains secrets.",
2009
2691
  "# For production, replace ALL values with strong credentials.",
2010
2692
  `DATABASE_URL=${databaseUrl}`,
@@ -2016,9 +2698,19 @@ Directory "${name}" already exists`));
2016
2698
  `S3_ACCESS_KEY=${s3AccessKey}`,
2017
2699
  `S3_SECRET_KEY=${s3SecretKey}`,
2018
2700
  `ENABLE_STUDIO=${enableStudio ? "true" : "false"}`,
2019
- `ZVELTIO_EXTENSIONS=${extensionsValue}`,
2020
- ""
2021
- ].join(`
2701
+ `ZVELTIO_EXTENSIONS=${extensionsValue}`
2702
+ ];
2703
+ if (enableIntranet) {
2704
+ envLines.push("# Intranet employee zone is enabled");
2705
+ envLines.push("ENABLE_INTRANET=true");
2706
+ }
2707
+ if (enableClientPortal) {
2708
+ envLines.push("# Client Portal");
2709
+ envLines.push("ENABLE_CLIENT_PORTAL=true");
2710
+ envLines.push(`CLIENT_PORTAL_TEMPLATE=${clientPortalTemplate}`);
2711
+ }
2712
+ envLines.push("");
2713
+ await Bun.write(join(dir, ".env"), envLines.join(`
2022
2714
  `));
2023
2715
  await Bun.write(join(dir, ".env.example"), [
2024
2716
  "# Copy to .env and fill in real values. Never commit .env to git.",
@@ -2032,6 +2724,10 @@ Directory "${name}" already exists`));
2032
2724
  "S3_SECRET_KEY=CHANGE_ME",
2033
2725
  "ENABLE_STUDIO=true",
2034
2726
  `ZVELTIO_EXTENSIONS=${extensionsValue}`,
2727
+ "# Optional zones",
2728
+ "# ENABLE_INTRANET=true",
2729
+ "# ENABLE_CLIENT_PORTAL=true",
2730
+ "# CLIENT_PORTAL_TEMPLATE=generic # generic | saas | services | regulatory",
2035
2731
  ""
2036
2732
  ].join(`
2037
2733
  `));
@@ -2080,20 +2776,20 @@ services:
2080
2776
  volumes:
2081
2777
  pgdata:
2082
2778
  `);
2083
- await Bun.write(join(dir, ".gitignore"), [
2084
- ".env",
2085
- "node_modules/",
2086
- "dist/",
2087
- ".DS_Store",
2088
- "*.local",
2089
- "types/",
2090
- ""
2091
- ].join(`
2779
+ await Bun.write(join(dir, ".gitignore"), [".env", "node_modules/", "dist/", ".DS_Store", "*.local", "types/", ""].join(`
2092
2780
  `));
2093
2781
  const extSummary = selectedExtensions.length > 0 ? `
2094
2782
  ${c.green("\u2714")} Extensions: ${selectedExtensions.map((e) => e.split("/").pop()).join(", ")}` : "";
2783
+ const intranetSummary = enableIntranet ? `
2784
+ ${c.green("\u2714")} Employee Intranet: enabled` : "";
2785
+ const portalSummary = enableClientPortal ? `
2786
+ ${c.green("\u2714")} Client Portal: ${clientPortalTemplate} template` : "";
2095
2787
  console.log(`
2096
- ${c.green(`\u2714 Project "${projectName}" initialized`)} at ${dir}${extSummary}`);
2788
+ ${c.green(`\u2714 Project "${projectName}" initialized`)} at ${dir}${extSummary}${intranetSummary}${portalSummary}`);
2789
+ const portalNote = enableClientPortal ? `
2790
+ ${c.cyan(`Client Portal: http://localhost:${port}/portal-client/login`)}` : "";
2791
+ const intranetNote = enableIntranet ? `
2792
+ ${c.cyan(`Intranet: http://localhost:${port}/admin/intranet`)}` : "";
2097
2793
  console.log(`
2098
2794
  ${c.bold("Next steps:")}
2099
2795
  ${name !== "." ? `cd ${name}
@@ -2102,7 +2798,7 @@ ${c.bold("Next steps:")}
2102
2798
  zveltio migrate ${c.dim("# apply database migrations")}
2103
2799
  zveltio dev ${c.dim("# start development server")}
2104
2800
 
2105
- ${c.cyan(`Open http://localhost:${port}${enableStudio ? "/admin" : "/api"}`)}
2801
+ ${c.cyan(`Studio: http://localhost:${port}${enableStudio ? "/admin" : "/api"}`)}${intranetNote}${portalNote}
2106
2802
  `);
2107
2803
  }
2108
2804
 
@@ -2156,11 +2852,7 @@ ${c2.dim(" Press Ctrl+C to stop")}
2156
2852
  process.exit(exitCode);
2157
2853
  }
2158
2854
  function findEngineEntry() {
2159
- const candidates = [
2160
- "packages/engine/src/index.ts",
2161
- "src/index.ts",
2162
- "index.ts"
2163
- ];
2855
+ const candidates = ["packages/engine/src/index.ts", "src/index.ts", "index.ts"];
2164
2856
  for (const candidate of candidates) {
2165
2857
  if (existsSync2(join2(process.cwd(), candidate))) {
2166
2858
  return candidate;
@@ -2249,11 +2941,7 @@ function findBinary() {
2249
2941
  return null;
2250
2942
  }
2251
2943
  function findEngineEntry2() {
2252
- const candidates = [
2253
- "packages/engine/src/index.ts",
2254
- "src/index.ts",
2255
- "index.ts"
2256
- ];
2944
+ const candidates = ["packages/engine/src/index.ts", "src/index.ts", "index.ts"];
2257
2945
  for (const candidate of candidates) {
2258
2946
  const full = join3(process.cwd(), candidate);
2259
2947
  if (existsSync3(full))
@@ -2329,14 +3017,7 @@ ${c4.bold(c4.cyan("Zveltio Deploy"))}
2329
3017
  await generateDockerfile(cwd, projectName);
2330
3018
  console.log(c4.dim(" Created: Dockerfile"));
2331
3019
  }
2332
- const buildArgs = [
2333
- "docker",
2334
- "build",
2335
- "--platform",
2336
- platform,
2337
- "-t",
2338
- imageName
2339
- ];
3020
+ const buildArgs = ["docker", "build", "--platform", platform, "-t", imageName];
2340
3021
  if (dockerfile) {
2341
3022
  buildArgs.push("-f", dockerfile);
2342
3023
  }
@@ -2508,6 +3189,19 @@ async function runMigrationsViaAPI(opts) {
2508
3189
  method: "POST",
2509
3190
  headers: { "Content-Type": "application/json" }
2510
3191
  });
3192
+ if (res.status === 401 || res.status === 403) {
3193
+ console.error(`
3194
+ \u274C The engine requires authentication for migrations via API.`);
3195
+ console.log("");
3196
+ console.log(" Run migrations directly instead (no running engine needed):");
3197
+ console.log(` DATABASE_URL=<your-url> zveltio migrate`);
3198
+ console.log(` zveltio migrate --database-url postgresql://...`);
3199
+ console.log("");
3200
+ console.log(" Or set DATABASE_URL in your .env and run:");
3201
+ console.log(` source .env && zveltio migrate
3202
+ `);
3203
+ process.exit(1);
3204
+ }
2511
3205
  if (!res.ok) {
2512
3206
  const body = await res.json().catch(() => ({}));
2513
3207
  throw new Error(body.error ?? `Engine returned ${res.status}`);
@@ -2516,10 +3210,20 @@ async function runMigrationsViaAPI(opts) {
2516
3210
  console.log(`\u2705 ${data.applied} migration(s) applied. Schema version: ${data.schema_version}
2517
3211
  `);
2518
3212
  } catch (err) {
2519
- console.error(`
3213
+ if (err.message?.includes("fetch") || err.code === "ECONNREFUSED") {
3214
+ console.error(`
3215
+ \u274C Cannot reach engine at ${opts.url}`);
3216
+ console.log("");
3217
+ console.log(" Run migrations directly (no engine needed):");
3218
+ console.log(` DATABASE_URL=<your-url> zveltio migrate`);
3219
+ console.log(` zveltio migrate --database-url postgresql://...
3220
+ `);
3221
+ } else {
3222
+ console.error(`
2520
3223
  \u274C ${err.message}`);
2521
- console.log(` Tip: Start the engine first, or use --database-url for direct migration
3224
+ console.log(` Tip: use --database-url for direct migration without a running engine
2522
3225
  `);
3226
+ }
2523
3227
  process.exit(1);
2524
3228
  }
2525
3229
  }
@@ -2564,8 +3268,8 @@ Creating extension: ${category}/${safeName}
2564
3268
  displayName: name,
2565
3269
  description: `${name} extension for Zveltio`,
2566
3270
  version: "1.0.0",
2567
- zveltioMinVersion: "2.0.0",
2568
- zveltioMaxVersion: "3.0.0",
3271
+ zveltioMinVersion: "1.0.0",
3272
+ zveltioMaxVersion: "4.0.0",
2569
3273
  permissions: ["database"],
2570
3274
  contributes: { engine: true, studio: true, fieldTypes: [] }
2571
3275
  }, null, 2));
@@ -2665,6 +3369,51 @@ export default defineConfig({
2665
3369
  vite: "^6.0.0"
2666
3370
  }
2667
3371
  }, null, 2));
3372
+ await writeFile(join5(targetDir, ".gitattributes"), `* text=auto eol=lf
3373
+
3374
+ # Packed engine bundle: byte-identical across OSes. Manifest
3375
+ # integrity.engineSha256 is computed over these exact bytes.
3376
+ engine/index.js binary
3377
+ engine/index.js.map binary
3378
+ `);
3379
+ await mkdir(join5(targetDir, ".github", "workflows"), { recursive: true });
3380
+ await writeFile(join5(targetDir, ".github", "workflows", "ci.yml"), `name: Extension CI
3381
+
3382
+ on:
3383
+ push:
3384
+ branches: [main, master]
3385
+ pull_request:
3386
+
3387
+ jobs:
3388
+ pack-and-verify:
3389
+ runs-on: ubuntu-latest
3390
+ steps:
3391
+ - uses: actions/checkout@v4
3392
+ - uses: oven-sh/setup-bun@v2
3393
+ with: { bun-version: latest }
3394
+
3395
+ - name: Install dependencies
3396
+ run: bun install
3397
+
3398
+ - name: Pack extension (engine/index.ts \u2192 engine/index.js + manifest hash)
3399
+ run: bunx @zveltio/cli extension pack
3400
+
3401
+ - name: Verify committed bundle matches manifest engineSha256
3402
+ # If pack changed engine/index.js, the committed bytes drift
3403
+ # from manifest.integrity.engineSha256. The publishing pipeline
3404
+ # would reject this \u2014 fail PR before merging.
3405
+ run: |
3406
+ actual=$(sha256sum engine/index.js | awk '{print $1}')
3407
+ declared=$(node -e "console.log(require('./manifest.json').integrity?.engineSha256 ?? '')")
3408
+ if [ "$actual" != "$declared" ]; then
3409
+ echo "::error::Bundle hash $actual \u2260 manifest engineSha256 $declared"
3410
+ echo "::error::Run \\\`bunx @zveltio/cli extension pack\\\` locally and commit the result."
3411
+ exit 1
3412
+ fi
3413
+
3414
+ - name: Validate manifest + structure
3415
+ run: bunx @zveltio/cli extension validate
3416
+ `);
2668
3417
  console.log(`Extension scaffolded at extensions/${category}/${safeName}/
2669
3418
 
2670
3419
  Structure:
@@ -2677,11 +3426,25 @@ Structure:
2677
3426
  pages/ <- Svelte UI components
2678
3427
  vite.config.ts
2679
3428
  manifest.json
3429
+ .gitattributes <- pins engine/index.js as binary (no EOL conversion)
3430
+ .github/workflows/
3431
+ ci.yml <- pack + hash verify + validate on every PR
2680
3432
 
2681
3433
  Next steps:
2682
3434
  1. Add your business logic in engine/index.ts
2683
3435
  2. Create your UI in studio/src/pages/
2684
- 3. Enable the extension: ZVELTIO_EXTENSIONS=${category}/${safeName}
3436
+ 3. Run \`bunx @zveltio/cli extension pack\` to build the bundle
3437
+ 4. Enable the extension: ZVELTIO_EXTENSIONS=${category}/${safeName}
3438
+
3439
+ Isolation (MARKETPLACE-POLICY \xA72):
3440
+ Community publishers MUST run in worker isolation. \`extension pack\`
3441
+ auto-sets "engine": { ..., "isolation": "worker" } for you unless it can
3442
+ confirm you're a verified/first-party publisher (via --first-party or a
3443
+ registry token). Worker mode = Bun.Worker with crash isolation and no
3444
+ direct DB credentials.
3445
+
3446
+ First-party / vendor builds that want inline isolation: pack with
3447
+ --first-party. See https://github.com/zveltio-devs/zveltio/blob/master/docs/EXTENSION-DEVELOPER-GUIDE.md#135-isolation-tiers-be-honest-about-what-you-ship
2685
3448
  `);
2686
3449
  }
2687
3450
  async function buildExtension() {
@@ -2738,68 +3501,1432 @@ Publishing to Zveltio marketplace...`);
2738
3501
  console.log(" (Marketplace coming soon!)");
2739
3502
  }
2740
3503
 
2741
- // src/commands/generate-types.ts
2742
- import { mkdirSync as mkdirSync2 } from "fs";
2743
- import { dirname } from "path";
2744
- var c5 = {
2745
- bold: (s) => `\x1B[1m${s}\x1B[0m`,
2746
- green: (s) => `\x1B[32m${s}\x1B[0m`,
2747
- cyan: (s) => `\x1B[36m${s}\x1B[0m`,
2748
- dim: (s) => `\x1B[2m${s}\x1B[0m`,
2749
- red: (s) => `\x1B[31m${s}\x1B[0m`
2750
- };
2751
- async function generateTypesCommand(collection, opts) {
2752
- const engineUrl = opts.url || process.env.ZVELTIO_URL || "http://localhost:3000";
2753
- const outputPath = opts.output || "./types/zveltio.d.ts";
2754
- console.log(`
2755
- ${c5.bold("Generate Types")}
2756
- `);
2757
- console.log(` Engine: ${c5.dim(engineUrl)}`);
2758
- console.log(` Output: ${c5.dim(outputPath)}`);
2759
- if (collection) {
2760
- console.log(` Collection: ${c5.dim(collection)}`);
2761
- }
2762
- console.log("");
2763
- try {
2764
- const path = collection ? `/api/admin/types/${encodeURIComponent(collection)}` : `/api/admin/types`;
2765
- const res = await fetch(`${engineUrl}${path}`, {
2766
- headers: {
2767
- Authorization: `Bearer ${process.env.ZVELTIO_API_KEY || ""}`
3504
+ // src/commands/extension-types.ts
3505
+ import { existsSync as existsSync6, readdirSync, readFileSync, mkdirSync as mkdirSync2, writeFileSync } from "fs";
3506
+ import { join as join6, relative } from "path";
3507
+
3508
+ // ../sdk/dist/schema-codegen.js
3509
+ var NUMERIC_TYPES = new Set([
3510
+ "int",
3511
+ "int2",
3512
+ "int4",
3513
+ "int8",
3514
+ "integer",
3515
+ "bigint",
3516
+ "smallint",
3517
+ "serial",
3518
+ "bigserial",
3519
+ "smallserial",
3520
+ "numeric",
3521
+ "decimal",
3522
+ "real",
3523
+ "double precision",
3524
+ "float",
3525
+ "float4",
3526
+ "float8"
3527
+ ]);
3528
+ var STRING_TYPES = new Set([
3529
+ "text",
3530
+ "varchar",
3531
+ "char",
3532
+ "character",
3533
+ "character varying",
3534
+ "name",
3535
+ "uuid",
3536
+ "citext",
3537
+ "inet",
3538
+ "cidr",
3539
+ "macaddr"
3540
+ ]);
3541
+ var BOOLEAN_TYPES = new Set(["boolean", "bool"]);
3542
+ var DATE_TYPES = new Set([
3543
+ "date",
3544
+ "time",
3545
+ "timetz",
3546
+ "timestamp",
3547
+ "timestamptz",
3548
+ "timestamp without time zone",
3549
+ "timestamp with time zone",
3550
+ "time without time zone",
3551
+ "time with time zone"
3552
+ ]);
3553
+ var JSON_TYPES = new Set(["json", "jsonb"]);
3554
+ var BYTES_TYPES = new Set(["bytea"]);
3555
+ function mapPgTypeToTs(pgType) {
3556
+ const lower = pgType.toLowerCase().trim();
3557
+ const isArray = /\[\]\s*$/.test(lower);
3558
+ const base = lower.replace(/\[\]\s*$/, "").replace(/\(.*\)/, "").trim();
3559
+ let tsBase;
3560
+ if (NUMERIC_TYPES.has(base))
3561
+ tsBase = "number";
3562
+ else if (BOOLEAN_TYPES.has(base))
3563
+ tsBase = "boolean";
3564
+ else if (DATE_TYPES.has(base))
3565
+ tsBase = "Date";
3566
+ else if (JSON_TYPES.has(base))
3567
+ tsBase = "Record<string, unknown>";
3568
+ else if (BYTES_TYPES.has(base))
3569
+ tsBase = "Uint8Array";
3570
+ else if (STRING_TYPES.has(base))
3571
+ tsBase = "string";
3572
+ else
3573
+ tsBase = "unknown";
3574
+ return isArray ? `${tsBase}[]` : tsBase;
3575
+ }
3576
+ function stripComments(sql) {
3577
+ let out = "";
3578
+ let i = 0;
3579
+ const n = sql.length;
3580
+ let inSingle = false;
3581
+ let dollarTag = null;
3582
+ while (i < n) {
3583
+ const ch = sql[i];
3584
+ const next = sql[i + 1];
3585
+ if (dollarTag !== null) {
3586
+ out += ch;
3587
+ if (sql.startsWith(dollarTag, i)) {
3588
+ out += sql.slice(i + 1, i + dollarTag.length);
3589
+ i += dollarTag.length;
3590
+ dollarTag = null;
3591
+ } else {
3592
+ i++;
2768
3593
  }
2769
- });
2770
- if (!res.ok) {
2771
- console.error(c5.red(`Failed to fetch types: ${res.status} ${res.statusText}`));
3594
+ continue;
3595
+ }
3596
+ if (inSingle) {
3597
+ out += ch;
3598
+ if (ch === "'" && next === "'") {
3599
+ out += next;
3600
+ i += 2;
3601
+ } else if (ch === "'") {
3602
+ inSingle = false;
3603
+ i++;
3604
+ } else
3605
+ i++;
3606
+ continue;
3607
+ }
3608
+ if (ch === "-" && next === "-") {
3609
+ while (i < n && sql[i] !== `
3610
+ `)
3611
+ i++;
3612
+ continue;
3613
+ }
3614
+ if (ch === "/" && next === "*") {
3615
+ i += 2;
3616
+ while (i < n && !(sql[i] === "*" && sql[i + 1] === "/"))
3617
+ i++;
3618
+ i += 2;
3619
+ continue;
3620
+ }
3621
+ if (ch === "$") {
3622
+ const m = sql.substring(i).match(/^\$[a-zA-Z_]*\$/);
3623
+ if (m) {
3624
+ dollarTag = m[0];
3625
+ out += dollarTag;
3626
+ i += dollarTag.length;
3627
+ continue;
3628
+ }
3629
+ }
3630
+ if (ch === "'") {
3631
+ inSingle = true;
3632
+ out += ch;
3633
+ i++;
3634
+ continue;
3635
+ }
3636
+ out += ch;
3637
+ i++;
3638
+ }
3639
+ return out;
3640
+ }
3641
+ function findMatchingParen(sql, openIdx) {
3642
+ let depth = 0;
3643
+ let i = openIdx;
3644
+ const n = sql.length;
3645
+ let inSingle = false;
3646
+ let dollarTag = null;
3647
+ while (i < n) {
3648
+ const ch = sql[i];
3649
+ if (dollarTag !== null) {
3650
+ if (sql.startsWith(dollarTag, i)) {
3651
+ dollarTag = null;
3652
+ i += sql.substring(i).match(/^\$[a-zA-Z_]*\$/)[0].length;
3653
+ continue;
3654
+ }
3655
+ i++;
3656
+ continue;
3657
+ }
3658
+ if (inSingle) {
3659
+ if (ch === "'" && sql[i + 1] === "'")
3660
+ i += 2;
3661
+ else if (ch === "'") {
3662
+ inSingle = false;
3663
+ i++;
3664
+ } else
3665
+ i++;
3666
+ continue;
3667
+ }
3668
+ if (ch === "'") {
3669
+ inSingle = true;
3670
+ i++;
3671
+ continue;
3672
+ }
3673
+ if (ch === "$") {
3674
+ const m = sql.substring(i).match(/^\$[a-zA-Z_]*\$/);
3675
+ if (m) {
3676
+ dollarTag = m[0];
3677
+ i += dollarTag.length;
3678
+ continue;
3679
+ }
3680
+ }
3681
+ if (ch === "(")
3682
+ depth++;
3683
+ else if (ch === ")") {
3684
+ depth--;
3685
+ if (depth === 0)
3686
+ return i;
3687
+ }
3688
+ i++;
3689
+ }
3690
+ return -1;
3691
+ }
3692
+ function parseColumnList(body) {
3693
+ const columns = [];
3694
+ const parts = [];
3695
+ let depth = 0;
3696
+ let buf = "";
3697
+ for (let i = 0;i < body.length; i++) {
3698
+ const ch = body[i];
3699
+ if (ch === "(")
3700
+ depth++;
3701
+ else if (ch === ")")
3702
+ depth--;
3703
+ if (ch === "," && depth === 0) {
3704
+ parts.push(buf);
3705
+ buf = "";
3706
+ continue;
3707
+ }
3708
+ buf += ch;
3709
+ }
3710
+ if (buf.trim())
3711
+ parts.push(buf);
3712
+ for (const rawPart of parts) {
3713
+ const part = rawPart.trim();
3714
+ if (!part)
3715
+ continue;
3716
+ if (/^(constraint|primary\s+key|foreign\s+key|unique|check|exclude|like)\b/i.test(part))
3717
+ continue;
3718
+ const col = parseColumnDef(part);
3719
+ if (col)
3720
+ columns.push(col);
3721
+ }
3722
+ return columns;
3723
+ }
3724
+ function parseColumnDef(line) {
3725
+ const m = line.match(/^\s*(?:"([^"]+)"|([a-zA-Z_][a-zA-Z0-9_]*))\s+(.+)$/);
3726
+ if (!m)
3727
+ return null;
3728
+ const name = m[1] ?? m[2];
3729
+ if (!name || name === "")
3730
+ return null;
3731
+ const rest = m[3];
3732
+ const typeMatch = rest.match(/^((?:character\s+varying|character|double\s+precision|timestamp\s+(?:without|with)\s+time\s+zone|time\s+(?:without|with)\s+time\s+zone|[a-zA-Z_][a-zA-Z0-9_]*)(?:\s*\([^)]*\))?(?:\s*\[\s*\])?)\s*(.*)$/i);
3733
+ if (!typeMatch)
3734
+ return null;
3735
+ const pgType = typeMatch[1].trim();
3736
+ const tail = typeMatch[2] ?? "";
3737
+ const upperTail = " " + tail.toUpperCase() + " ";
3738
+ const notNull = / NOT NULL /.test(upperTail) || / PRIMARY KEY /.test(upperTail);
3739
+ const tsBase = mapPgTypeToTs(pgType);
3740
+ const tsType = notNull ? tsBase : `${tsBase} | null`;
3741
+ return { name, pgType, tsType, nullable: !notNull };
3742
+ }
3743
+ function parseSchema(sqlChunks) {
3744
+ const tables = new Map;
3745
+ const order = [];
3746
+ const ensureTable = (name) => {
3747
+ if (!tables.has(name)) {
3748
+ tables.set(name, new Map);
3749
+ order.push(name);
3750
+ }
3751
+ return tables.get(name);
3752
+ };
3753
+ const allSql = sqlChunks.map(stripComments).join(`
3754
+ `);
3755
+ const createRe = /CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:"([^"]+)"|([a-zA-Z_][a-zA-Z0-9_]*))\s*\(/gi;
3756
+ let m;
3757
+ while ((m = createRe.exec(allSql)) !== null) {
3758
+ const tableName = m[1] ?? m[2];
3759
+ const openIdx = createRe.lastIndex - 1;
3760
+ const closeIdx = findMatchingParen(allSql, openIdx);
3761
+ if (closeIdx < 0)
3762
+ continue;
3763
+ const body = allSql.substring(openIdx + 1, closeIdx);
3764
+ const columns = parseColumnList(body);
3765
+ const tableCols = ensureTable(tableName);
3766
+ for (const c5 of columns)
3767
+ tableCols.set(c5.name, c5);
3768
+ createRe.lastIndex = closeIdx + 1;
3769
+ }
3770
+ const alterRe = /ALTER\s+TABLE\s+(?:"([^"]+)"|([a-zA-Z_][a-zA-Z0-9_]*))\s+ADD\s+COLUMN\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:"([^"]+)"|([a-zA-Z_][a-zA-Z0-9_]*))\s+([^;]+?)(?=;|$)/gi;
3771
+ while ((m = alterRe.exec(allSql)) !== null) {
3772
+ const tableName = m[1] ?? m[2];
3773
+ const colName = m[3] ?? m[4];
3774
+ const tail = m[5];
3775
+ const col = parseColumnDef(`${colName} ${tail}`);
3776
+ if (col) {
3777
+ const tableCols = ensureTable(tableName);
3778
+ tableCols.set(col.name, col);
3779
+ }
3780
+ }
3781
+ return {
3782
+ tables: order.map((name) => ({ name, columns: [...tables.get(name).values()] }))
3783
+ };
3784
+ }
3785
+ function emitTypeScript(schema, opts = {}) {
3786
+ const interfaceName = opts.interfaceName ?? "ExtensionSchema";
3787
+ const lines = [];
3788
+ lines.push("// AUTO-GENERATED by zveltio CLI \u2014 do NOT edit by hand.");
3789
+ lines.push("// Regenerate with: zveltio extension types");
3790
+ if (opts.banner)
3791
+ lines.push(`// ${opts.banner}`);
3792
+ lines.push("");
3793
+ lines.push("/* eslint-disable */");
3794
+ lines.push("");
3795
+ lines.push(`export interface ${interfaceName} {`);
3796
+ if (schema.tables.length === 0) {
3797
+ lines.push(" // No tables parsed from migrations.");
3798
+ }
3799
+ for (const t of schema.tables) {
3800
+ lines.push(` ${quoteIdentifierIfNeeded(t.name)}: {`);
3801
+ for (const c5 of t.columns) {
3802
+ lines.push(` ${quoteIdentifierIfNeeded(c5.name)}: ${c5.tsType}; // ${c5.pgType}`);
3803
+ }
3804
+ lines.push(" };");
3805
+ }
3806
+ lines.push("}");
3807
+ lines.push("");
3808
+ return lines.join(`
3809
+ `);
3810
+ }
3811
+ function quoteIdentifierIfNeeded(name) {
3812
+ return /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name) ? name : `'${name.replace(/'/g, "\\'")}'`;
3813
+ }
3814
+
3815
+ // src/commands/extension-types.ts
3816
+ var c5 = {
3817
+ bold: (s) => `\x1B[1m${s}\x1B[0m`,
3818
+ green: (s) => `\x1B[32m${s}\x1B[0m`,
3819
+ dim: (s) => `\x1B[2m${s}\x1B[0m`,
3820
+ red: (s) => `\x1B[31m${s}\x1B[0m`,
3821
+ yellow: (s) => `\x1B[33m${s}\x1B[0m`
3822
+ };
3823
+ async function extensionTypesCommand(opts = {}) {
3824
+ const dir = opts.dir ?? process.cwd();
3825
+ console.log(`
3826
+ ${c5.bold("Extension types")}
3827
+ `);
3828
+ console.log(` Extension dir: ${c5.dim(dir)}`);
3829
+ const manifestPath = join6(dir, "manifest.json");
3830
+ if (!existsSync6(manifestPath)) {
3831
+ console.error(c5.red(`No manifest.json found in ${dir}`));
3832
+ console.error(c5.dim(" Run this command from your extension's root folder, or pass --dir."));
3833
+ process.exit(1);
3834
+ }
3835
+ const migrationsDir = join6(dir, "engine", "migrations");
3836
+ if (!existsSync6(migrationsDir)) {
3837
+ console.error(c5.yellow(`No engine/migrations directory found at ${migrationsDir}`));
3838
+ console.error(c5.dim(" Nothing to generate. Add SQL migrations first."));
3839
+ process.exit(0);
3840
+ }
3841
+ const sqlFiles = readdirSync(migrationsDir).filter((f) => f.endsWith(".sql")).sort();
3842
+ if (sqlFiles.length === 0) {
3843
+ console.error(c5.yellow(`No .sql files in ${migrationsDir}`));
3844
+ process.exit(0);
3845
+ }
3846
+ console.log(` Migrations: ${c5.dim(`${sqlFiles.length} file(s)`)}`);
3847
+ const chunks = sqlFiles.map((f) => readFileSync(join6(migrationsDir, f), "utf8"));
3848
+ const schema = parseSchema(chunks);
3849
+ if (schema.tables.length === 0) {
3850
+ console.log(c5.yellow(" No tables parsed. Check that CREATE TABLE statements are well-formed."));
3851
+ } else {
3852
+ console.log(` Tables parsed: ${c5.dim(`${schema.tables.length}`)}`);
3853
+ for (const t of schema.tables) {
3854
+ console.log(` - ${t.name} (${t.columns.length} cols)`);
3855
+ }
3856
+ }
3857
+ let extName = "<unknown>";
3858
+ try {
3859
+ const m = JSON.parse(readFileSync(manifestPath, "utf8"));
3860
+ if (typeof m.name === "string")
3861
+ extName = m.name;
3862
+ } catch {}
3863
+ const banner = `Generated from ${extName} migrations: ${sqlFiles.join(", ")}`;
3864
+ const tsBody = emitTypeScript(schema, { banner });
3865
+ const outputPath = opts.output ?? join6(dir, ".zveltio", "db.d.ts");
3866
+ mkdirSync2(join6(outputPath, ".."), { recursive: true });
3867
+ writeFileSync(outputPath, tsBody, "utf8");
3868
+ console.log(`
3869
+ ${c5.green("Written:")} ${relative(process.cwd(), outputPath) || outputPath}`);
3870
+ console.log(c5.dim(" Add `.zveltio/` to your .gitignore. Regenerate with this command after every migration."));
3871
+ console.log("");
3872
+ }
3873
+
3874
+ // src/commands/extension-validate.ts
3875
+ import { existsSync as existsSync7, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync } from "fs";
3876
+ import { join as join7 } from "path";
3877
+
3878
+ // ../sdk/dist/validate/migration-parse.js
3879
+ function parseMigrationSql(raw) {
3880
+ const downIdx = raw.search(/^--\s*DOWN\b/im);
3881
+ if (downIdx < 0) {
3882
+ return { up: raw.trim(), down: null };
3883
+ }
3884
+ const up = raw.slice(0, downIdx).trim();
3885
+ const downSection = raw.slice(downIdx);
3886
+ const firstNewline = downSection.indexOf(`
3887
+ `);
3888
+ const downBody = firstNewline >= 0 ? downSection.slice(firstNewline + 1).trim() : "";
3889
+ return { up, down: downBody.length > 0 ? downBody : null };
3890
+ }
3891
+
3892
+ // ../sdk/dist/validate/index.js
3893
+ var SEMVER_RE = /^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/;
3894
+ var KNOWN_CATEGORIES = new Set([
3895
+ "auth",
3896
+ "content",
3897
+ "crm",
3898
+ "finance",
3899
+ "hr",
3900
+ "operations",
3901
+ "developer",
3902
+ "compliance",
3903
+ "communications",
3904
+ "analytics",
3905
+ "geospatial",
3906
+ "ai",
3907
+ "integrations",
3908
+ "i18n",
3909
+ "workflow",
3910
+ "storage",
3911
+ "ecommerce",
3912
+ "projects",
3913
+ "sms",
3914
+ "forms",
3915
+ "billing",
3916
+ "intelligence",
3917
+ "business",
3918
+ "search",
3919
+ "data",
3920
+ "custom"
3921
+ ]);
3922
+ var SAFE_PACKAGE_NAME = /^(@[a-z0-9-_]+\/)?[a-z0-9-_.]+$/;
3923
+ var SAFE_VERSION_RANGE = /^[\d.*^~>=<| -]+$/;
3924
+ function err(code, message, file) {
3925
+ return file ? { code, message, file } : { code, message };
3926
+ }
3927
+ function validateManifest(input) {
3928
+ const out = [];
3929
+ const m = input.manifest;
3930
+ if (m === null || typeof m !== "object" || Array.isArray(m)) {
3931
+ out.push(err("MANIFEST_NOT_OBJECT", "manifest.json must be a JSON object"));
3932
+ return out;
3933
+ }
3934
+ const obj = m;
3935
+ for (const k of ["name", "displayName", "category", "description", "version"]) {
3936
+ if (typeof obj[k] !== "string" || obj[k].trim() === "") {
3937
+ out.push(err("MANIFEST_MISSING_FIELD", `manifest.json missing required string field "${k}"`));
3938
+ }
3939
+ }
3940
+ const name = typeof obj.name === "string" ? obj.name : "";
3941
+ if (input.expectedName && name && name !== input.expectedName) {
3942
+ out.push(err("MANIFEST_NAME_MISMATCH", `manifest "name": "${name}" does not match folder path "${input.expectedName}"`));
3943
+ }
3944
+ if (typeof obj.version === "string" && !SEMVER_RE.test(obj.version)) {
3945
+ out.push(err("MANIFEST_BAD_VERSION", `version "${obj.version}" is not valid semver (x.y.z)`));
3946
+ }
3947
+ if (typeof obj.zveltioMinVersion === "string" && !SEMVER_RE.test(obj.zveltioMinVersion)) {
3948
+ out.push(err("MANIFEST_BAD_MIN_VERSION", `zveltioMinVersion "${obj.zveltioMinVersion}" is not valid semver`));
3949
+ }
3950
+ if (typeof obj.zveltioMaxVersion === "string" && !SEMVER_RE.test(obj.zveltioMaxVersion)) {
3951
+ out.push(err("MANIFEST_BAD_MAX_VERSION", `zveltioMaxVersion "${obj.zveltioMaxVersion}" is not valid semver`));
3952
+ }
3953
+ if (typeof obj.category === "string" && !KNOWN_CATEGORIES.has(obj.category)) {
3954
+ out.push(err("MANIFEST_UNKNOWN_CATEGORY", `category "${obj.category}" isn't in the known set \u2014 typo? Known: ${[...KNOWN_CATEGORIES].sort().join(", ")}`));
3955
+ }
3956
+ return out;
3957
+ }
3958
+ function validatePeerDependencies(input) {
3959
+ const out = [];
3960
+ const deps = input.peerDependencies ?? {};
3961
+ for (const [pkg, version] of Object.entries(deps)) {
3962
+ if (typeof version !== "string") {
3963
+ out.push(err("PEERDEP_BAD_VERSION", `peerDependency "${pkg}" version must be a string, got ${typeof version}`));
3964
+ continue;
3965
+ }
3966
+ if (!SAFE_PACKAGE_NAME.test(pkg)) {
3967
+ out.push(err("PEERDEP_UNSAFE_NAME", `peerDependency "${pkg}" uses an unsupported name format (file:, git:, etc. are forbidden)`));
3968
+ continue;
3969
+ }
3970
+ if (!SAFE_VERSION_RANGE.test(version)) {
3971
+ out.push(err("PEERDEP_UNSAFE_VERSION", `peerDependency "${pkg}" version range "${version}" is not a plain semver range`));
3972
+ continue;
3973
+ }
3974
+ if (!input.allowedPackages.has(pkg)) {
3975
+ out.push(err("PEERDEP_NOT_ALLOWED", `peerDependency "${pkg}" is not on the platform allow-list \u2014 open a PR to peer-deps-allowlist.ts to request inclusion`));
3976
+ }
3977
+ }
3978
+ return out;
3979
+ }
3980
+ function hasDestructiveDdl(upSql) {
3981
+ const s = upSql.replace(/--[^\n]*/g, "").replace(/\/\*[\s\S]*?\*\//g, "");
3982
+ if (/\bDROP\s+TABLE\b/i.test(s))
3983
+ return true;
3984
+ if (/\bDROP\s+COLUMN\b/i.test(s))
3985
+ return true;
3986
+ if (/\bALTER\s+COLUMN\b[\s\S]*?\bDROP\s+(NOT\s+NULL|DEFAULT)\b/i.test(s))
3987
+ return true;
3988
+ return false;
3989
+ }
3990
+ function validateMigrations(input) {
3991
+ const out = [];
3992
+ for (const file of input.files) {
3993
+ if (file.sql.trim() === "") {
3994
+ out.push(err("MIGRATION_EMPTY", "migration file is empty", file.filename));
3995
+ continue;
3996
+ }
3997
+ let parsed;
3998
+ try {
3999
+ parsed = parseMigrationSql(file.sql);
4000
+ } catch (e) {
4001
+ out.push(err("MIGRATION_PARSE", `could not parse migration: ${e.message}`, file.filename));
4002
+ continue;
4003
+ }
4004
+ if (parsed.up.trim() === "") {
4005
+ out.push(err("MIGRATION_NO_UP", "migration has no UP section (everything before -- DOWN)", file.filename));
4006
+ continue;
4007
+ }
4008
+ if (input.requireDownForDestructive && hasDestructiveDdl(parsed.up) && !parsed.down) {
4009
+ out.push(err("MIGRATION_DESTRUCTIVE_NO_DOWN", 'migration contains destructive DDL (DROP TABLE/COLUMN or ALTER ... DROP) but has no "-- DOWN" section. Add one so uninstall --purgeData can roll back.', file.filename));
4010
+ }
4011
+ }
4012
+ return out;
4013
+ }
4014
+ function validateFilePresence(input) {
4015
+ const out = [];
4016
+ for (const p of input.required) {
4017
+ if (!input.paths[p]) {
4018
+ out.push(err("FILE_MISSING", `required file is missing`, p));
4019
+ }
4020
+ }
4021
+ return out;
4022
+ }
4023
+ var DEFAULT_BUNDLE_KB_MAX = 50000;
4024
+ function validateBundleSize(input) {
4025
+ const cap = input.bundleSizeKbMax ?? DEFAULT_BUNDLE_KB_MAX;
4026
+ const observedKb = Math.ceil(input.bundleBytes / 1024);
4027
+ if (observedKb > cap) {
4028
+ return [
4029
+ err("BUNDLE_TOO_LARGE", `extension bundle is ${observedKb} KB; quota is ${cap} KB. Reduce the bundle or raise the quota in manifest.quotas.bundleSizeKbMax.`)
4030
+ ];
4031
+ }
4032
+ return [];
4033
+ }
4034
+ function validateExtension(input) {
4035
+ const errors = [
4036
+ ...validateManifest(input.manifest),
4037
+ ...validatePeerDependencies(input.peerDeps),
4038
+ ...validateMigrations(input.migrations),
4039
+ ...validateFilePresence(input.filePresence),
4040
+ ...input.bundleSize ? validateBundleSize(input.bundleSize) : []
4041
+ ];
4042
+ return {
4043
+ ok: errors.length === 0,
4044
+ errors,
4045
+ stats: {
4046
+ tables: input.stats.tables,
4047
+ migrations: input.stats.migrations,
4048
+ peerDeps: Object.keys(input.peerDeps.peerDependencies ?? {}).length
4049
+ }
4050
+ };
4051
+ }
4052
+
4053
+ // src/commands/extension-validate.ts
4054
+ var c6 = {
4055
+ bold: (s) => `\x1B[1m${s}\x1B[0m`,
4056
+ green: (s) => `\x1B[32m${s}\x1B[0m`,
4057
+ red: (s) => `\x1B[31m${s}\x1B[0m`,
4058
+ yellow: (s) => `\x1B[33m${s}\x1B[0m`,
4059
+ dim: (s) => `\x1B[2m${s}\x1B[0m`
4060
+ };
4061
+ var PEER_DEPS_ALLOWLIST = new Set([
4062
+ "node-saml",
4063
+ "ldapts",
4064
+ "imapflow",
4065
+ "mailparser",
4066
+ "nodemailer",
4067
+ "@aws-sdk/client-s3",
4068
+ "@aws-sdk/s3-request-presigner",
4069
+ "nanoid",
4070
+ "qrcode",
4071
+ "pdfkit",
4072
+ "graphql"
4073
+ ]);
4074
+ function recursiveSize(dir) {
4075
+ let total = 0;
4076
+ if (!existsSync7(dir))
4077
+ return 0;
4078
+ for (const name of readdirSync2(dir)) {
4079
+ const full = join7(dir, name);
4080
+ if (name === "node_modules" || name === ".zveltio" || name === "dist")
4081
+ continue;
4082
+ let st;
4083
+ try {
4084
+ st = statSync(full);
4085
+ } catch {
4086
+ continue;
4087
+ }
4088
+ if (st.isDirectory())
4089
+ total += recursiveSize(full);
4090
+ else if (st.isFile())
4091
+ total += st.size;
4092
+ }
4093
+ return total;
4094
+ }
4095
+ function inferExpectedName(dir) {
4096
+ const norm = dir.replace(/\\/g, "/");
4097
+ const idx = norm.indexOf("zveltio-extensions/");
4098
+ if (idx >= 0) {
4099
+ return norm.slice(idx + "zveltio-extensions/".length).replace(/\/$/, "");
4100
+ }
4101
+ return norm.split("/").filter(Boolean).pop() ?? "";
4102
+ }
4103
+ async function extensionValidateCommand(opts = {}) {
4104
+ const dir = opts.dir ?? process.cwd();
4105
+ console.log(`
4106
+ ${c6.bold("Extension validate")}
4107
+ `);
4108
+ console.log(` Extension dir: ${c6.dim(dir)}`);
4109
+ const manifestPath = join7(dir, "manifest.json");
4110
+ let manifest = null;
4111
+ if (existsSync7(manifestPath)) {
4112
+ try {
4113
+ manifest = JSON.parse(readFileSync2(manifestPath, "utf8"));
4114
+ } catch (e) {
4115
+ console.error(c6.red(` manifest.json is not valid JSON: ${e.message}`));
4116
+ manifest = null;
4117
+ }
4118
+ }
4119
+ const migrationsDir = join7(dir, "engine", "migrations");
4120
+ let sqlFiles = [];
4121
+ let parsedTableCount = 0;
4122
+ if (existsSync7(migrationsDir)) {
4123
+ sqlFiles = readdirSync2(migrationsDir).filter((f) => f.endsWith(".sql")).sort().map((filename) => ({
4124
+ filename,
4125
+ sql: readFileSync2(join7(migrationsDir, filename), "utf8")
4126
+ }));
4127
+ try {
4128
+ parsedTableCount = parseSchema(sqlFiles.map((f) => f.sql)).tables.length;
4129
+ } catch {
4130
+ parsedTableCount = 0;
4131
+ }
4132
+ }
4133
+ const paths = {};
4134
+ for (const rel of ["manifest.json", "engine/index.ts", "engine/index.js"]) {
4135
+ paths[rel] = existsSync7(join7(dir, rel));
4136
+ }
4137
+ const required = ["manifest.json"];
4138
+ if (!(paths["engine/index.ts"] || paths["engine/index.js"])) {
4139
+ required.push("engine/index.ts");
4140
+ }
4141
+ const bundleBytes = recursiveSize(dir);
4142
+ const result = validateExtension({
4143
+ manifest: {
4144
+ manifest,
4145
+ expectedName: inferExpectedName(dir)
4146
+ },
4147
+ peerDeps: {
4148
+ peerDependencies: manifest && typeof manifest === "object" && !Array.isArray(manifest) ? manifest.peerDependencies : undefined,
4149
+ allowedPackages: PEER_DEPS_ALLOWLIST
4150
+ },
4151
+ migrations: {
4152
+ files: sqlFiles,
4153
+ requireDownForDestructive: true
4154
+ },
4155
+ filePresence: { paths, required },
4156
+ bundleSize: {
4157
+ bundleBytes,
4158
+ bundleSizeKbMax: manifest && typeof manifest === "object" && !Array.isArray(manifest) ? manifest?.quotas?.bundleSizeKbMax : undefined
4159
+ },
4160
+ stats: { tables: parsedTableCount, migrations: sqlFiles.length }
4161
+ });
4162
+ const m = manifest;
4163
+ const v2Status = (() => {
4164
+ if (!m)
4165
+ return "unknown";
4166
+ if (!m.engine)
4167
+ return "v1-legacy";
4168
+ if (m.engine.bundled !== true)
4169
+ return "engine-not-bundled";
4170
+ if (!m.engine.entry)
4171
+ return "missing-engine-entry";
4172
+ if (!m.integrity?.engineSha256)
4173
+ return "missing-engine-sha";
4174
+ return "v2-ok";
4175
+ })();
4176
+ console.log(` Manifest: ${manifest ? c6.green("OK") : c6.red("missing or invalid")}`);
4177
+ console.log(` Migrations: ${c6.dim(`${result.stats.migrations} file(s), ${result.stats.tables} table(s) parsed`)}`);
4178
+ console.log(` Peer deps: ${c6.dim(`${result.stats.peerDeps} declared`)}`);
4179
+ console.log(` Bundle size: ${c6.dim(`${Math.ceil(bundleBytes / 1024)} KB`)}`);
4180
+ const v2Label = v2Status === "v2-ok" ? c6.green("v2 (bundled)") : v2Status === "v1-legacy" ? c6.yellow("v1 (no engine block \u2014 run `zveltio extension pack`)") : v2Status === "engine-not-bundled" ? c6.red("engine.bundled !== true \u2014 re-pack required") : v2Status === "missing-engine-entry" ? c6.red("engine.entry missing") : v2Status === "missing-engine-sha" ? c6.red("integrity.engineSha256 missing \u2014 re-pack required") : c6.dim("unknown");
4181
+ console.log(` Manifest v2: ${v2Label}`);
4182
+ const isolation = m?.engine?.isolation ?? "inline";
4183
+ const resolved = await resolvePublisherTier({
4184
+ firstParty: opts.firstParty,
4185
+ token: opts.token,
4186
+ registryUrl: opts.registryUrl
4187
+ });
4188
+ const inlineOk = tierAllowsInline(resolved.tier);
4189
+ const tierSrc = resolved.source === "flag" ? "--first-party" : resolved.source === "registry" ? "registry" : "default";
4190
+ if (isolation === "worker") {
4191
+ console.log(` Isolation: ${c6.green("worker (runs in any tier)")}`);
4192
+ } else if (inlineOk) {
4193
+ console.log(` Isolation: ${c6.dim(`inline (${resolved.tier} \u2014 allowed, via ${tierSrc})`)}`);
4194
+ } else {
4195
+ console.log(` Isolation: ${c6.red(`inline \u2014 ${resolved.tier} publishers MUST use 'worker'`)}`);
4196
+ }
4197
+ console.log("");
4198
+ if (isolation !== "worker" && !inlineOk) {
4199
+ console.error(c6.red(`Validation failed: ${resolved.tier} publishers must declare ` + `engine.isolation: "worker" (MARKETPLACE-POLICY \xA72).`));
4200
+ console.error(c6.dim(' Add `"isolation": "worker"` to the engine block in manifest.json, ' + "then re-pack. If you are a verified/first-party publisher, pass " + "--first-party or set ZVELTIO_REGISTRY_TOKEN so the tier can be confirmed. " + "See docs/MARKETPLACE-POLICY.md \xA72."));
4201
+ if (opts.silentExit)
4202
+ throw new Error("Validation failed: community inline isolation");
4203
+ process.exit(1);
4204
+ }
4205
+ if (v2Status === "v1-legacy") {
4206
+ console.error(c6.red("Validation failed: manifest is v1 (no engine.bundled block)."));
4207
+ console.error(c6.dim(" v1 manifests are unsupported since 1.0.0-beta.1. Run `zveltio extension pack` to " + "generate the v2 engine + integrity blocks. See docs/EXTENSION-DEVELOPER-GUIDE.md \xA74."));
4208
+ if (opts.silentExit)
4209
+ throw new Error("Validation failed: v1 manifest unsupported");
4210
+ process.exit(1);
4211
+ }
4212
+ if (v2Status === "engine-not-bundled" || v2Status === "missing-engine-entry" || v2Status === "missing-engine-sha") {
4213
+ console.error(c6.red("Validation failed: manifest declares v2 engine block but is incomplete."));
4214
+ console.error(c6.dim(" Run `zveltio extension pack --dir <ext>` to regenerate engine + integrity blocks."));
4215
+ if (opts.silentExit)
4216
+ throw new Error("Validation failed: incomplete v2 manifest");
4217
+ process.exit(1);
4218
+ }
4219
+ if (result.ok) {
4220
+ console.log(c6.green("Validation passed."));
4221
+ console.log(c6.dim(" Run `zveltio extension types` to refresh generated types, then `zveltio extension publish` when ready."));
4222
+ console.log("");
4223
+ return;
4224
+ }
4225
+ console.log(c6.red(`Validation failed with ${result.errors.length} error(s):`));
4226
+ console.log("");
4227
+ for (const e of result.errors) {
4228
+ printError(e);
4229
+ }
4230
+ console.log("");
4231
+ if (!opts.silentExit)
4232
+ process.exit(1);
4233
+ }
4234
+ function printError(e) {
4235
+ const loc = e.file ? c6.dim(` (${e.file})`) : "";
4236
+ console.log(` ${c6.red(e.code)} ${e.message}${loc}`);
4237
+ }
4238
+
4239
+ // src/commands/extension-publish.ts
4240
+ import { existsSync as existsSync12, mkdirSync as mkdirSync4, readFileSync as readFileSync6, statSync as statSync3, writeFileSync as writeFileSync4 } from "fs";
4241
+ import { join as join12, resolve as resolve2 } from "path";
4242
+ import { tmpdir } from "os";
4243
+
4244
+ // ../sdk/dist/publish/index.js
4245
+ function bytesToHex(bytes) {
4246
+ const view = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
4247
+ let out = "";
4248
+ for (let i = 0;i < view.length; i++)
4249
+ out += view[i].toString(16).padStart(2, "0");
4250
+ return out;
4251
+ }
4252
+ function bytesToBase64(bytes) {
4253
+ let bin = "";
4254
+ for (let i = 0;i < bytes.length; i++)
4255
+ bin += String.fromCharCode(bytes[i]);
4256
+ return btoa(bin);
4257
+ }
4258
+ async function sha256Hex(bytes) {
4259
+ const buf = await crypto.subtle.digest("SHA-256", bytes);
4260
+ return bytesToHex(buf);
4261
+ }
4262
+ async function generateKeypair(keyId) {
4263
+ const pair = await crypto.subtle.generateKey({ name: "Ed25519" }, true, ["sign", "verify"]);
4264
+ const kp = pair;
4265
+ const publicJwk = await crypto.subtle.exportKey("jwk", kp.publicKey);
4266
+ const privateJwk = await crypto.subtle.exportKey("jwk", kp.privateKey);
4267
+ const finalKeyId = keyId ?? randomKeyId();
4268
+ return {
4269
+ keyId: finalKeyId,
4270
+ createdAt: new Date().toISOString(),
4271
+ publicJwk,
4272
+ privateJwk
4273
+ };
4274
+ }
4275
+ function randomKeyId() {
4276
+ const buf = new Uint8Array(6);
4277
+ crypto.getRandomValues(buf);
4278
+ return "zv-" + bytesToHex(buf);
4279
+ }
4280
+ async function importPrivateKey(jwk) {
4281
+ return await crypto.subtle.importKey("jwk", jwk, { name: "Ed25519" }, false, ["sign"]);
4282
+ }
4283
+ async function signBundle(archive, keypair) {
4284
+ const bundleSha256 = await sha256Hex(archive);
4285
+ const key = await importPrivateKey(keypair.privateJwk);
4286
+ const dataBytes = new TextEncoder().encode(bundleSha256.toLowerCase());
4287
+ const sigBuf = await crypto.subtle.sign({ name: "Ed25519" }, key, dataBytes);
4288
+ return {
4289
+ algorithm: "ed25519",
4290
+ signature: bytesToBase64(new Uint8Array(sigBuf)),
4291
+ bundleSha256,
4292
+ signedAt: new Date().toISOString(),
4293
+ keyId: keypair.keyId
4294
+ };
4295
+ }
4296
+ async function exportTrustedKeyEntry(keyId, publicJwk) {
4297
+ const key = await crypto.subtle.importKey("jwk", publicJwk, { name: "Ed25519" }, true, [
4298
+ "verify"
4299
+ ]);
4300
+ const rawBuf = await crypto.subtle.exportKey("raw", key);
4301
+ return { keyId, publicKeyHex: bytesToHex(new Uint8Array(rawBuf)) };
4302
+ }
4303
+
4304
+ // src/commands/extension-publish.ts
4305
+ init_extension_pack();
4306
+
4307
+ // src/commands/keys.ts
4308
+ import {
4309
+ existsSync as existsSync11,
4310
+ mkdirSync as mkdirSync3,
4311
+ readdirSync as readdirSync3,
4312
+ readFileSync as readFileSync5,
4313
+ writeFileSync as writeFileSync3,
4314
+ chmodSync,
4315
+ statSync as statSync2
4316
+ } from "fs";
4317
+ import { join as join11 } from "path";
4318
+ import { homedir, platform } from "os";
4319
+ var c8 = {
4320
+ bold: (s) => `\x1B[1m${s}\x1B[0m`,
4321
+ green: (s) => `\x1B[32m${s}\x1B[0m`,
4322
+ red: (s) => `\x1B[31m${s}\x1B[0m`,
4323
+ yellow: (s) => `\x1B[33m${s}\x1B[0m`,
4324
+ dim: (s) => `\x1B[2m${s}\x1B[0m`
4325
+ };
4326
+ function keysDir() {
4327
+ return join11(homedir(), ".zveltio", "keys");
4328
+ }
4329
+ function ensureKeysDir() {
4330
+ const dir = keysDir();
4331
+ if (!existsSync11(dir)) {
4332
+ mkdirSync3(dir, { recursive: true });
4333
+ if (platform() !== "win32") {
4334
+ try {
4335
+ chmodSync(dir, 448);
4336
+ } catch {}
4337
+ }
4338
+ }
4339
+ return dir;
4340
+ }
4341
+ function keyPath(keyId) {
4342
+ return join11(keysDir(), `${keyId}.json`);
4343
+ }
4344
+ function writeKeyFile(kp) {
4345
+ const path = keyPath(kp.keyId);
4346
+ writeFileSync3(path, JSON.stringify(kp, null, 2), "utf8");
4347
+ if (platform() !== "win32") {
4348
+ try {
4349
+ chmodSync(path, 384);
4350
+ } catch {}
4351
+ }
4352
+ }
4353
+ function readKeyFile(keyId) {
4354
+ const path = keyPath(keyId);
4355
+ if (!existsSync11(path)) {
4356
+ throw new Error(`Keypair "${keyId}" not found at ${path}. Run \`zveltio keys generate --id ${keyId}\` first.`);
4357
+ }
4358
+ const raw = readFileSync5(path, "utf8");
4359
+ const kp = JSON.parse(raw);
4360
+ if (!kp.privateJwk || !kp.publicJwk || kp.keyId !== keyId) {
4361
+ throw new Error(`Keypair file at ${path} is malformed`);
4362
+ }
4363
+ return kp;
4364
+ }
4365
+ function resolveKeyId(explicit) {
4366
+ if (explicit)
4367
+ return explicit;
4368
+ const dir = keysDir();
4369
+ if (!existsSync11(dir)) {
4370
+ throw new Error("No keypairs found. Run `zveltio keys generate` first.");
4371
+ }
4372
+ const ids = readdirSync3(dir).filter((f) => f.endsWith(".json")).map((f) => f.slice(0, -5));
4373
+ if (ids.length === 0)
4374
+ throw new Error("No keypairs found. Run `zveltio keys generate` first.");
4375
+ if (ids.length > 1) {
4376
+ throw new Error(`Multiple keypairs exist (${ids.join(", ")}). Pass --key-id to choose one.`);
4377
+ }
4378
+ return ids[0];
4379
+ }
4380
+ async function keysGenerateCommand(opts = {}) {
4381
+ ensureKeysDir();
4382
+ const kp = await generateKeypair(opts.id);
4383
+ if (existsSync11(keyPath(kp.keyId)) && !opts.force) {
4384
+ console.error(c8.red(`Key "${kp.keyId}" already exists. Pass --force to overwrite.`));
4385
+ process.exit(1);
4386
+ }
4387
+ writeKeyFile(kp);
4388
+ console.log(`
4389
+ ${c8.bold("Keypair generated")}
4390
+ `);
4391
+ console.log(` keyId: ${c8.green(kp.keyId)}`);
4392
+ console.log(` Created: ${c8.dim(kp.createdAt)}`);
4393
+ console.log(` Stored at: ${c8.dim(keyPath(kp.keyId))}`);
4394
+ if (platform() !== "win32") {
4395
+ console.log(` Permissions: ${c8.dim("0600 (user-only)")}`);
4396
+ }
4397
+ const trusted = await exportTrustedKeyEntry(kp.keyId, kp.publicJwk);
4398
+ console.log(`
4399
+ ${c8.bold("Public key (share with registry admin):")}
4400
+ `);
4401
+ console.log(` ${JSON.stringify([trusted], null, 2)}`);
4402
+ console.log(c8.dim("\n Paste the array above into the engine's `REGISTRY_PUBLIC_KEYS_JSON` env to trust this key locally."));
4403
+ console.log("");
4404
+ }
4405
+ async function keysListCommand() {
4406
+ const dir = keysDir();
4407
+ if (!existsSync11(dir)) {
4408
+ console.log(`${c8.yellow("No keypairs yet.")} Run ${c8.dim("zveltio keys generate")} to create one.`);
4409
+ return;
4410
+ }
4411
+ const files = readdirSync3(dir).filter((f) => f.endsWith(".json")).sort();
4412
+ if (files.length === 0) {
4413
+ console.log(`${c8.yellow("No keypairs yet.")} Run ${c8.dim("zveltio keys generate")} to create one.`);
4414
+ return;
4415
+ }
4416
+ console.log(`
4417
+ ${c8.bold("Keypairs in")} ${c8.dim(dir)}
4418
+ `);
4419
+ for (const file of files) {
4420
+ const keyId = file.slice(0, -5);
4421
+ try {
4422
+ const kp = readKeyFile(keyId);
4423
+ const st = statSync2(join11(dir, file));
4424
+ const sizeKb = (st.size / 1024).toFixed(1);
4425
+ console.log(` ${c8.green(keyId.padEnd(24))} ${c8.dim(kp.createdAt)} ${c8.dim(`(${sizeKb} KB)`)}`);
4426
+ } catch (err2) {
4427
+ console.log(` ${c8.red(keyId)} ${c8.dim("\u2014 malformed: " + err2.message)}`);
4428
+ }
4429
+ }
4430
+ console.log("");
4431
+ }
4432
+ async function keysExportCommand(keyId) {
4433
+ const kp = readKeyFile(keyId);
4434
+ const trusted = await exportTrustedKeyEntry(kp.keyId, kp.publicJwk);
4435
+ console.log(JSON.stringify([trusted], null, 2));
4436
+ }
4437
+
4438
+ // src/commands/extension-publish.ts
4439
+ var c9 = {
4440
+ bold: (s) => `\x1B[1m${s}\x1B[0m`,
4441
+ green: (s) => `\x1B[32m${s}\x1B[0m`,
4442
+ red: (s) => `\x1B[31m${s}\x1B[0m`,
4443
+ yellow: (s) => `\x1B[33m${s}\x1B[0m`,
4444
+ dim: (s) => `\x1B[2m${s}\x1B[0m`
4445
+ };
4446
+ function readManifest2(dir) {
4447
+ const path = join12(dir, "manifest.json");
4448
+ if (!existsSync12(path)) {
4449
+ throw new Error(`No manifest.json at ${path}. Run from the extension's root or pass --dir.`);
4450
+ }
4451
+ const raw = readFileSync6(path, "utf8");
4452
+ let m;
4453
+ try {
4454
+ m = JSON.parse(raw);
4455
+ } catch (e) {
4456
+ throw new Error(`manifest.json is not valid JSON: ${e.message}`);
4457
+ }
4458
+ if (!m.name || !m.version) {
4459
+ throw new Error("manifest.json is missing required `name` or `version` field.");
4460
+ }
4461
+ return m;
4462
+ }
4463
+ function artifactBaseName(m) {
4464
+ return `${m.name.replace(/\//g, "__")}-${m.version}`;
4465
+ }
4466
+ async function createArchive(dir, outFile) {
4467
+ const excludes = [
4468
+ "node_modules",
4469
+ ".zveltio",
4470
+ "dist",
4471
+ "engine/dist",
4472
+ ".git",
4473
+ ".DS_Store",
4474
+ "*.zvext",
4475
+ "*.zvext.sig"
4476
+ ];
4477
+ const args = ["--force-local", "-czf", outFile];
4478
+ for (const ex of excludes) {
4479
+ args.push("--exclude", ex);
4480
+ }
4481
+ args.push(".");
4482
+ const proc = Bun.spawn(["tar", ...args], { cwd: dir, stdout: "inherit", stderr: "inherit" });
4483
+ const code = await proc.exited;
4484
+ if (code !== 0)
4485
+ throw new Error(`tar failed (exit ${code})`);
4486
+ }
4487
+ async function runStudioBuild(dir) {
4488
+ if (!existsSync12(join12(dir, "studio")))
4489
+ return;
4490
+ const proc = Bun.spawn(["bun", "run", "build"], {
4491
+ cwd: join12(dir, "studio"),
4492
+ stdout: "inherit",
4493
+ stderr: "inherit"
4494
+ });
4495
+ const code = await proc.exited;
4496
+ if (code !== 0)
4497
+ throw new Error(`Studio build failed (exit ${code})`);
4498
+ }
4499
+ async function runEnginePack(dir, tierOpts) {
4500
+ if (!existsSync12(join12(dir, "engine", "index.ts")))
4501
+ return;
4502
+ await extensionPackCommand({ dir, ...tierOpts });
4503
+ }
4504
+ async function uploadToRegistry(opts) {
4505
+ const url = `${opts.registryUrl.replace(/\/$/, "")}/api/v1/extensions/publish`;
4506
+ const form = new FormData;
4507
+ form.append("manifest", new Blob([JSON.stringify(opts.manifest)], { type: "application/json" }), "manifest.json");
4508
+ form.append("signature", new Blob([opts.signatureJson], { type: "application/json" }), "signature.json");
4509
+ form.append("archive", new Blob([opts.archive], { type: "application/gzip" }), `${artifactBaseName(opts.manifest)}.zvext`);
4510
+ const res = await fetch(url, {
4511
+ method: "POST",
4512
+ headers: { Authorization: `Bearer ${opts.token}` },
4513
+ body: form
4514
+ });
4515
+ const body = await res.text();
4516
+ return { ok: res.ok, status: res.status, body };
4517
+ }
4518
+ async function extensionPublishCommand(opts = {}) {
4519
+ const dir = resolve2(opts.dir ?? process.cwd());
4520
+ const registryUrl = opts.registryUrl ?? process.env.ZVELTIO_REGISTRY_URL ?? "https://registry.zveltio.com";
4521
+ const token = opts.token ?? process.env.ZVELTIO_REGISTRY_TOKEN;
4522
+ console.log(`
4523
+ ${c9.bold("Extension publish")}
4524
+ `);
4525
+ console.log(` Extension dir: ${c9.dim(dir)}`);
4526
+ let manifest;
4527
+ try {
4528
+ manifest = readManifest2(dir);
4529
+ } catch (e) {
4530
+ console.error(c9.red(e.message));
4531
+ process.exit(1);
4532
+ }
4533
+ console.log(` Name: ${c9.bold(manifest.name)} ${c9.dim("v" + manifest.version)}`);
4534
+ const runValidate = opts.validate !== false;
4535
+ if (runValidate) {
4536
+ console.log(`
4537
+ ${c9.bold("Step 1/5: validate")}`);
4538
+ try {
4539
+ await extensionValidateCommand({
4540
+ dir,
4541
+ silentExit: true,
4542
+ firstParty: opts.firstParty,
4543
+ token,
4544
+ registryUrl
4545
+ });
4546
+ } catch (e) {
4547
+ console.error(c9.red(`Validate failed: ${e.message}`));
4548
+ process.exit(1);
4549
+ }
4550
+ } else {
4551
+ console.log(c9.yellow(`
4552
+ Skipping validate (--no-validate)`));
4553
+ }
4554
+ const runPackStep = opts.pack !== false && opts.build !== false;
4555
+ if (runPackStep) {
4556
+ console.log(`
4557
+ ${c9.bold("Step 2/6: pack engine")}`);
4558
+ try {
4559
+ await runEnginePack(dir, {
4560
+ firstParty: opts.firstParty,
4561
+ token,
4562
+ registryUrl
4563
+ });
4564
+ manifest = readManifest2(dir);
4565
+ } catch (e) {
4566
+ console.error(c9.red(e.message));
4567
+ process.exit(1);
4568
+ }
4569
+ } else {
4570
+ console.log(c9.yellow(`
4571
+ Skipping pack (${opts.build === false ? "--no-build" : "--no-pack"})`));
4572
+ }
4573
+ const runBuildStep = opts.build !== false;
4574
+ if (runBuildStep) {
4575
+ console.log(`
4576
+ ${c9.bold("Step 3/6: studio build")}`);
4577
+ try {
4578
+ await runStudioBuild(dir);
4579
+ } catch (e) {
4580
+ console.error(c9.red(e.message));
4581
+ process.exit(1);
4582
+ }
4583
+ console.log(c9.green(" build OK"));
4584
+ } else {
4585
+ console.log(c9.yellow(`
4586
+ Skipping build (--no-build)`));
4587
+ }
4588
+ if (opts.dryRun) {
4589
+ console.log(`
4590
+ ${c9.green("Dry-run complete")} \u2014 skipped archive/sign/publish.
4591
+ `);
4592
+ return;
4593
+ }
4594
+ console.log(`
4595
+ ${c9.bold("Step 4/6: archive")}`);
4596
+ const tmpDir = opts.output ? resolve2(opts.output) : join12(tmpdir(), `zveltio-publish-${Date.now()}`);
4597
+ if (!existsSync12(tmpDir))
4598
+ mkdirSync4(tmpDir, { recursive: true });
4599
+ const zvextPath = join12(tmpDir, `${artifactBaseName(manifest)}.zvext`);
4600
+ try {
4601
+ await createArchive(dir, zvextPath);
4602
+ } catch (e) {
4603
+ console.error(c9.red(e.message));
4604
+ process.exit(1);
4605
+ }
4606
+ const archive = new Uint8Array(readFileSync6(zvextPath));
4607
+ const stat = statSync3(zvextPath);
4608
+ console.log(` ${c9.dim(zvextPath)} ${c9.dim(`(${(stat.size / 1024).toFixed(1)} KB)`)}`);
4609
+ console.log(`
4610
+ ${c9.bold("Step 5/6: sign")}`);
4611
+ let keyId;
4612
+ try {
4613
+ keyId = resolveKeyId(opts.keyId);
4614
+ } catch (e) {
4615
+ console.error(c9.red(e.message));
4616
+ process.exit(1);
4617
+ }
4618
+ let signature;
4619
+ let sigPath;
4620
+ try {
4621
+ const keypair = readKeyFile(keyId);
4622
+ signature = await signBundle(archive, keypair);
4623
+ sigPath = `${zvextPath}.sig`;
4624
+ writeFileSync4(sigPath, JSON.stringify(signature, null, 2), "utf8");
4625
+ } catch (e) {
4626
+ console.error(c9.red(` Signing failed: ${e.message}`));
4627
+ console.error(c9.dim(e.stack ?? ""));
4628
+ process.exit(1);
4629
+ }
4630
+ const digest = await sha256Hex(archive);
4631
+ console.log(` keyId: ${c9.green(keyId)}`);
4632
+ console.log(` bundleSha256: ${c9.dim(digest.slice(0, 16) + "\u2026")}`);
4633
+ console.log(` signature: ${c9.dim(sigPath)}`);
4634
+ if (opts.output) {
4635
+ console.log(`
4636
+ ${c9.green("Local publish complete.")}`);
4637
+ console.log(c9.dim(` Artifacts in ${tmpDir}. Upload them to the registry manually, or re-run without --output.`));
4638
+ console.log("");
4639
+ return;
4640
+ }
4641
+ if (!token) {
4642
+ console.error(c9.red(`
4643
+ No registry token provided.`));
4644
+ console.error(c9.dim(" Pass --token <token>, set ZVELTIO_REGISTRY_TOKEN, or use --output <dir> for a local-only build."));
4645
+ process.exit(1);
4646
+ }
4647
+ console.log(`
4648
+ ${c9.bold("Step 6/6: upload")}`);
4649
+ console.log(` Registry: ${c9.dim(registryUrl)}`);
4650
+ try {
4651
+ const result = await uploadToRegistry({
4652
+ registryUrl,
4653
+ token,
4654
+ archive,
4655
+ signatureJson: JSON.stringify(signature, null, 2),
4656
+ manifest
4657
+ });
4658
+ if (result.ok) {
4659
+ console.log(c9.green(` HTTP ${result.status} \u2014 submission received.`));
4660
+ console.log(c9.dim(` Body: ${result.body.slice(0, 200)}`));
4661
+ console.log("");
4662
+ console.log(c9.yellow(" \u23F3 Your submission is in the review queue (status: pending)."));
4663
+ console.log(c9.dim(` Marketplace admins review community submissions before they go live.`));
4664
+ console.log(c9.dim(` You'll be notified at your registered email when the decision lands.`));
4665
+ console.log(c9.dim(` Check status: zveltio extension status ${manifest.name}`));
4666
+ } else if (result.status === 404) {
4667
+ console.error(c9.yellow(` HTTP 404 \u2014 the registry at ${registryUrl} does not implement /api/v1/extensions/publish yet.`));
4668
+ console.error(c9.dim(" Use --output <dir> to ship locally for now. The CLI bits ahead of the server is intentional."));
4669
+ process.exit(2);
4670
+ } else if (result.status === 422) {
4671
+ let parsed = {};
4672
+ try {
4673
+ parsed = JSON.parse(result.body);
4674
+ } catch {}
4675
+ if (parsed.code === "ISOLATION_POLICY_VIOLATION") {
4676
+ console.error(c9.red(` HTTP 422 \u2014 isolation policy violation.`));
4677
+ console.error(c9.dim(` ${parsed.error ?? result.body.slice(0, 300)}`));
4678
+ console.error(c9.dim(' Re-pack with engine.isolation: "worker" (community publishers), ' + "or enroll as a verified publisher to ship inline."));
4679
+ if (parsed.docs)
4680
+ console.error(c9.dim(` ${parsed.docs}`));
4681
+ } else {
4682
+ console.error(c9.red(` HTTP 422 \u2014 ${parsed.error ?? "submission rejected"}.`));
4683
+ console.error(c9.dim(` Body: ${result.body.slice(0, 400)}`));
4684
+ }
4685
+ process.exit(1);
4686
+ } else {
4687
+ console.error(c9.red(` HTTP ${result.status} \u2014 upload failed.`));
4688
+ console.error(c9.dim(` Body: ${result.body.slice(0, 400)}`));
4689
+ process.exit(1);
4690
+ }
4691
+ } catch (e) {
4692
+ console.error(c9.red(` Upload error: ${e.message}`));
4693
+ process.exit(1);
4694
+ }
4695
+ console.log("");
4696
+ }
4697
+
4698
+ // src/commands/extension-dev.ts
4699
+ import { existsSync as existsSync13, statSync as statSync4 } from "fs";
4700
+ import { watch as fsWatch } from "fs";
4701
+ import { join as join13, resolve as resolve3, relative as relative3 } from "path";
4702
+ import { readdirSync as readdirSync4 } from "fs";
4703
+ var c10 = {
4704
+ bold: (s) => `\x1B[1m${s}\x1B[0m`,
4705
+ green: (s) => `\x1B[32m${s}\x1B[0m`,
4706
+ red: (s) => `\x1B[31m${s}\x1B[0m`,
4707
+ yellow: (s) => `\x1B[33m${s}\x1B[0m`,
4708
+ dim: (s) => `\x1B[2m${s}\x1B[0m`,
4709
+ cyan: (s) => `\x1B[36m${s}\x1B[0m`
4710
+ };
4711
+ function readManifestName(dir, override) {
4712
+ if (override)
4713
+ return override;
4714
+ const manifestPath = join13(dir, "manifest.json");
4715
+ if (!existsSync13(manifestPath)) {
4716
+ throw new Error(`No manifest.json at ${manifestPath}. Pass --name or run from the extension's root.`);
4717
+ }
4718
+ const Bun_ = globalThis.Bun;
4719
+ const raw = __require("fs").readFileSync(manifestPath, "utf8");
4720
+ let m;
4721
+ try {
4722
+ m = JSON.parse(raw);
4723
+ } catch (e) {
4724
+ throw new Error(`manifest.json is not valid JSON: ${e.message}`);
4725
+ }
4726
+ if (typeof m.name !== "string")
4727
+ throw new Error("manifest.json missing string `name`");
4728
+ return m.name;
4729
+ }
4730
+ function collectEngineFiles(engineDir) {
4731
+ if (!existsSync13(engineDir))
4732
+ return [];
4733
+ const out = [];
4734
+ const stack = [engineDir];
4735
+ while (stack.length > 0) {
4736
+ const dir = stack.pop();
4737
+ let entries;
4738
+ try {
4739
+ entries = readdirSync4(dir);
4740
+ } catch {
4741
+ continue;
4742
+ }
4743
+ for (const name of entries) {
4744
+ if (name === "node_modules" || name === "dist" || name.startsWith("."))
4745
+ continue;
4746
+ const full = join13(dir, name);
4747
+ let st;
4748
+ try {
4749
+ st = statSync4(full);
4750
+ } catch {
4751
+ continue;
4752
+ }
4753
+ if (st.isDirectory()) {
4754
+ stack.push(full);
4755
+ } else if (st.isFile() && (name.endsWith(".ts") || name.endsWith(".js") || name.endsWith(".sql"))) {
4756
+ out.push(full);
4757
+ }
4758
+ }
4759
+ }
4760
+ return out;
4761
+ }
4762
+ async function postReload(url, name) {
4763
+ const endpoint = `${url.replace(/\/$/, "")}/__zveltio_dev_reload`;
4764
+ let res;
4765
+ try {
4766
+ res = await fetch(endpoint, {
4767
+ method: "POST",
4768
+ headers: { "Content-Type": "application/json" },
4769
+ body: JSON.stringify({ name })
4770
+ });
4771
+ } catch (e) {
4772
+ return { ok: false, status: 0, body: { error: e.message } };
4773
+ }
4774
+ const body = await res.json().catch(() => ({}));
4775
+ return { ok: res.ok, status: res.status, body };
4776
+ }
4777
+ async function extensionDevCommand(opts = {}) {
4778
+ const dir = resolve3(opts.dir ?? process.cwd());
4779
+ const url = (opts.url ?? process.env.ZVELTIO_ENGINE_URL ?? "http://localhost:3000").replace(/\/$/, "");
4780
+ const engineDir = join13(dir, "engine");
4781
+ let extName;
4782
+ try {
4783
+ extName = readManifestName(dir, opts.name);
4784
+ } catch (e) {
4785
+ console.error(c10.red(e.message));
4786
+ process.exit(1);
4787
+ }
4788
+ console.log(`
4789
+ ${c10.bold("Extension dev")}
4790
+ `);
4791
+ console.log(` Extension: ${c10.cyan(extName)}`);
4792
+ console.log(` Folder: ${c10.dim(dir)}`);
4793
+ console.log(` Engine: ${c10.dim(url)}`);
4794
+ const probe = await postReload(url, "__probe__");
4795
+ if (probe.status === 0) {
4796
+ console.error(c10.red(`
4797
+ Cannot reach engine at ${url}. Start it with \`bun run dev\` in the engine package first.`));
4798
+ process.exit(1);
4799
+ }
4800
+ if (probe.status === 404) {
4801
+ console.error(c10.red(`
4802
+ Engine returned 404 on /__zveltio_dev_reload. Is NODE_ENV=production? The endpoint is dev-only.`));
4803
+ process.exit(1);
4804
+ }
4805
+ let studioProc = null;
4806
+ if (!opts.noStudio && existsSync13(join13(dir, "studio"))) {
4807
+ studioProc = Bun.spawn(["bun", "run", "dev"], {
4808
+ cwd: join13(dir, "studio"),
4809
+ stdout: "inherit",
4810
+ stderr: "inherit"
4811
+ });
4812
+ console.log(` Studio: ${c10.green("watching (vite dev)")}`);
4813
+ } else {
4814
+ console.log(` Studio: ${c10.dim(opts.noStudio ? "skipped (--no-studio)" : "no studio/ folder \u2014 skipped")}`);
4815
+ }
4816
+ if (!existsSync13(engineDir)) {
4817
+ console.log(` Engine src: ${c10.yellow("no engine/ folder \u2014 engine watch disabled")}`);
4818
+ } else {
4819
+ const files = collectEngineFiles(engineDir);
4820
+ console.log(` Engine src: ${c10.green(`watching ${files.length} file(s)`)} ${c10.dim(`(.ts / .js / .sql under engine/)`)}`);
4821
+ let timer = null;
4822
+ let lastTrigger = "";
4823
+ const debounceMs = 250;
4824
+ const onChange = (filePath) => {
4825
+ lastTrigger = relative3(dir, filePath);
4826
+ if (timer)
4827
+ clearTimeout(timer);
4828
+ timer = setTimeout(async () => {
4829
+ console.log(`
4830
+ ${c10.dim("\u2192")} ${c10.cyan(lastTrigger)} ${c10.dim("changed \u2014 reloading\u2026")}`);
4831
+ const result = await postReload(url, extName);
4832
+ if (result.ok) {
4833
+ console.log(` ${c10.green("\u2713")} engine reloaded ${c10.dim(`(${result.status})`)}`);
4834
+ } else {
4835
+ console.log(` ${c10.red("\u2717")} reload failed ${c10.dim(`(HTTP ${result.status})`)}`);
4836
+ if (result.body?.error)
4837
+ console.log(` ${c10.red(result.body.error)}`);
4838
+ }
4839
+ }, debounceMs);
4840
+ };
4841
+ const watchers = files.map((f) => fsWatch(f, () => onChange(f)));
4842
+ const cleanup = () => {
4843
+ console.log(`
4844
+
4845
+ ${c10.dim("Stopping watchers\u2026")}`);
4846
+ for (const w of watchers) {
4847
+ try {
4848
+ w.close();
4849
+ } catch {}
4850
+ }
4851
+ if (studioProc && !studioProc.killed)
4852
+ studioProc.kill();
4853
+ process.exit(0);
4854
+ };
4855
+ process.on("SIGINT", cleanup);
4856
+ process.on("SIGTERM", cleanup);
4857
+ }
4858
+ console.log(`
4859
+ ${c10.dim(`Ready. Edit engine/ files to trigger a hot-reload. Ctrl+C to stop.
4860
+ `)}`);
4861
+ if (studioProc) {
4862
+ await studioProc.exited;
4863
+ } else {
4864
+ await new Promise(() => {});
4865
+ }
4866
+ }
4867
+
4868
+ // src/commands/generate-types.ts
4869
+ import { mkdirSync as mkdirSync5 } from "fs";
4870
+ import { dirname as dirname4 } from "path";
4871
+ var c11 = {
4872
+ bold: (s) => `\x1B[1m${s}\x1B[0m`,
4873
+ green: (s) => `\x1B[32m${s}\x1B[0m`,
4874
+ cyan: (s) => `\x1B[36m${s}\x1B[0m`,
4875
+ dim: (s) => `\x1B[2m${s}\x1B[0m`,
4876
+ red: (s) => `\x1B[31m${s}\x1B[0m`
4877
+ };
4878
+ async function generateTypesCommand(collection, opts) {
4879
+ const engineUrl = opts.url || process.env.ZVELTIO_URL || "http://localhost:3000";
4880
+ const outputPath = opts.output || "./types/zveltio.d.ts";
4881
+ console.log(`
4882
+ ${c11.bold("Generate Types")}
4883
+ `);
4884
+ console.log(` Engine: ${c11.dim(engineUrl)}`);
4885
+ console.log(` Output: ${c11.dim(outputPath)}`);
4886
+ if (collection) {
4887
+ console.log(` Collection: ${c11.dim(collection)}`);
4888
+ }
4889
+ console.log("");
4890
+ try {
4891
+ const path = collection ? `/api/admin/types/${encodeURIComponent(collection)}` : `/api/admin/types`;
4892
+ const res = await fetch(`${engineUrl}${path}`, {
4893
+ headers: {
4894
+ Authorization: `Bearer ${process.env.ZVELTIO_API_KEY || ""}`
4895
+ }
4896
+ });
4897
+ if (!res.ok) {
4898
+ console.error(c11.red(`Failed to fetch types: ${res.status} ${res.statusText}`));
2772
4899
  if (res.status === 401) {
2773
- console.error(c5.dim(" Set ZVELTIO_API_KEY env var or use an authenticated session."));
4900
+ console.error(c11.dim(" Set ZVELTIO_API_KEY env var or use an authenticated session."));
2774
4901
  } else if (res.status === 404) {
2775
- console.error(c5.dim(" Ensure the engine is running and the /api/admin/types endpoint is available."));
4902
+ console.error(c11.dim(" Ensure the engine is running and the /api/admin/types endpoint is available."));
2776
4903
  }
2777
4904
  process.exit(1);
2778
4905
  }
2779
4906
  const types = await res.text();
2780
- const dir = dirname(outputPath);
4907
+ const dir = dirname4(outputPath);
2781
4908
  if (dir && dir !== ".") {
2782
- mkdirSync2(dir, { recursive: true });
4909
+ mkdirSync5(dir, { recursive: true });
2783
4910
  }
2784
4911
  await Bun.write(outputPath, types);
2785
- console.log(`${c5.green("Types generated:")} ${outputPath}`);
4912
+ console.log(`${c11.green("Types generated:")} ${outputPath}`);
2786
4913
  if (!collection) {
2787
4914
  const typeCount = (types.match(/^export (interface|type) /gm) || []).length;
2788
4915
  if (typeCount > 0) {
2789
- console.log(c5.dim(` ${typeCount} type declaration(s) written`));
4916
+ console.log(c11.dim(` ${typeCount} type declaration(s) written`));
2790
4917
  }
2791
4918
  }
2792
4919
  console.log("");
2793
- } catch (err) {
2794
- console.error(c5.red(`Failed to generate types: ${err.message}`));
4920
+ } catch (err2) {
4921
+ console.error(c11.red(`Failed to generate types: ${err2.message}`));
2795
4922
  process.exit(1);
2796
4923
  }
2797
4924
  }
2798
4925
 
2799
4926
  // src/commands/install.ts
2800
- import { existsSync as existsSync6 } from "fs";
4927
+ import { existsSync as existsSync14 } from "fs";
2801
4928
  import { readFile as readFile2, mkdir as mkdir2, cp } from "fs/promises";
2802
- import { join as join6 } from "path";
4929
+ import { join as join14 } from "path";
2803
4930
  async function installCommand(name, opts) {
2804
4931
  const engineUrl = opts.url || process.env.ENGINE_URL || "http://localhost:3000";
2805
4932
  console.log(`
@@ -2812,13 +4939,13 @@ async function installCommand(name, opts) {
2812
4939
  await installFromCatalog(name, engineUrl);
2813
4940
  }
2814
4941
  async function installFromPath(name, sourcePath, engineUrl, force) {
2815
- if (!existsSync6(join6(sourcePath, "manifest.json"))) {
4942
+ if (!existsSync14(join14(sourcePath, "manifest.json"))) {
2816
4943
  console.error("\u274C No manifest.json found at", sourcePath);
2817
4944
  process.exit(1);
2818
4945
  }
2819
4946
  let manifest;
2820
4947
  try {
2821
- manifest = JSON.parse(await readFile2(join6(sourcePath, "manifest.json"), "utf-8"));
4948
+ manifest = JSON.parse(await readFile2(join14(sourcePath, "manifest.json"), "utf-8"));
2822
4949
  } catch {
2823
4950
  console.error("\u274C Failed to read manifest.json");
2824
4951
  process.exit(1);
@@ -2842,8 +4969,8 @@ async function installFromPath(name, sourcePath, engineUrl, force) {
2842
4969
  console.log(` Name: ${manifest.displayName || manifest.name}`);
2843
4970
  console.log(` Version: ${manifest.version || "?"}`);
2844
4971
  console.log(` Category: ${category}`);
2845
- const enginePkgPath = join6(process.cwd(), "packages/engine/package.json");
2846
- if (existsSync6(enginePkgPath)) {
4972
+ const enginePkgPath = join14(process.cwd(), "packages/engine/package.json");
4973
+ if (existsSync14(enginePkgPath)) {
2847
4974
  const enginePkg = JSON.parse(await readFile2(enginePkgPath, "utf-8"));
2848
4975
  const engineVersion = enginePkg.version || "2.0.0";
2849
4976
  if (manifest.zveltioMinVersion) {
@@ -2854,8 +4981,8 @@ async function installFromPath(name, sourcePath, engineUrl, force) {
2854
4981
  }
2855
4982
  }
2856
4983
  const extName = manifest.name;
2857
- const targetDir = join6(process.cwd(), "extensions", category, extName);
2858
- if (existsSync6(targetDir) && !force) {
4984
+ const targetDir = join14(process.cwd(), "extensions", category, extName);
4985
+ if (existsSync14(targetDir) && !force) {
2859
4986
  console.error(`\u274C Extension already exists at ${targetDir}`);
2860
4987
  console.error(` Use --force to overwrite.`);
2861
4988
  process.exit(1);
@@ -2925,23 +5052,27 @@ Available extensions:`);
2925
5052
  if (entry.is_installed) {
2926
5053
  console.log(` Already installed. Enabling...`);
2927
5054
  } else {
2928
- const installRes = await fetch(`${engineUrl}/api/marketplace/${entry.name}/install`, { method: "POST" }).catch(() => null);
5055
+ const installRes = await fetch(`${engineUrl}/api/marketplace/${entry.name}/install`, {
5056
+ method: "POST"
5057
+ }).catch(() => null);
2929
5058
  if (!installRes?.ok) {
2930
- const err = await installRes?.json().catch(() => ({}));
5059
+ const err2 = await installRes?.json().catch(() => ({}));
2931
5060
  if (installRes?.status === 501) {
2932
5061
  console.log(` \u2139\uFE0F Bundled extension \u2014 skipping install step, enabling directly...`);
2933
5062
  } else {
2934
- console.error(`\u274C Install failed: ${err?.error || `HTTP ${installRes?.status}`}`);
5063
+ console.error(`\u274C Install failed: ${err2?.error || `HTTP ${installRes?.status}`}`);
2935
5064
  process.exit(1);
2936
5065
  }
2937
5066
  } else {
2938
5067
  console.log(` \u2713 Marked as installed`);
2939
5068
  }
2940
5069
  }
2941
- const enableRes = await fetch(`${engineUrl}/api/marketplace/${entry.name}/enable`, { method: "POST" }).catch(() => null);
5070
+ const enableRes = await fetch(`${engineUrl}/api/marketplace/${entry.name}/enable`, {
5071
+ method: "POST"
5072
+ }).catch(() => null);
2942
5073
  if (!enableRes?.ok) {
2943
- const err = await enableRes?.json().catch(() => ({}));
2944
- console.error(`\u274C Enable failed: ${err?.error || `HTTP ${enableRes?.status}`}`);
5074
+ const err2 = await enableRes?.json().catch(() => ({}));
5075
+ console.error(`\u274C Enable failed: ${err2?.error || `HTTP ${enableRes?.status}`}`);
2945
5076
  process.exit(1);
2946
5077
  }
2947
5078
  const result = await enableRes.json();
@@ -2979,12 +5110,12 @@ async function activateViaApi(name, category, engineUrl) {
2979
5110
  // src/commands/create-god.ts
2980
5111
  import { createInterface as createInterface2 } from "readline";
2981
5112
  async function prompt(rl2, question) {
2982
- return new Promise((resolve) => {
2983
- rl2.question(question, resolve);
5113
+ return new Promise((resolve4) => {
5114
+ rl2.question(question, resolve4);
2984
5115
  });
2985
5116
  }
2986
5117
  async function promptHidden(question) {
2987
- return new Promise((resolve) => {
5118
+ return new Promise((resolve4) => {
2988
5119
  process.stdout.write(question);
2989
5120
  const stdin = process.stdin;
2990
5121
  const wasPaused = stdin.isPaused();
@@ -3003,7 +5134,7 @@ async function promptHidden(question) {
3003
5134
  stdin.off("data", handler);
3004
5135
  process.stdout.write(`
3005
5136
  `);
3006
- resolve(value);
5137
+ resolve4(value);
3007
5138
  } else if (char === "\x7F") {
3008
5139
  if (value.length > 0)
3009
5140
  value = value.slice(0, -1);
@@ -3091,8 +5222,8 @@ Creating God (super-admin) user at ${engineUrl}
3091
5222
  })
3092
5223
  });
3093
5224
  if (!res.ok) {
3094
- const err = await res.json().catch(() => ({ message: res.statusText }));
3095
- throw new Error(err.message || `HTTP ${res.status}`);
5225
+ const err2 = await res.json().catch(() => ({ message: res.statusText }));
5226
+ throw new Error(err2.message || `HTTP ${res.status}`);
3096
5227
  }
3097
5228
  const { user } = await res.json();
3098
5229
  console.log("\uD83D\uDD11 Granting god permissions...");
@@ -3133,10 +5264,10 @@ Creating God (super-admin) user at ${engineUrl}
3133
5264
  console.warn(` Use the Permissions page in Studio to assign the admin role manually.
3134
5265
  `);
3135
5266
  }
3136
- } catch (err) {
5267
+ } catch (err2) {
3137
5268
  rl2.close();
3138
5269
  console.error(`
3139
- \u274C Failed to create god user: ${err.message}
5270
+ \u274C Failed to create god user: ${err2.message}
3140
5271
  `);
3141
5272
  process.exit(1);
3142
5273
  }
@@ -3242,9 +5373,9 @@ var rollbackCommand = new Command("rollback").description("Rollback database mig
3242
5373
  input: process.stdin,
3243
5374
  output: process.stdout
3244
5375
  });
3245
- const answer = await new Promise((resolve) => {
5376
+ const answer = await new Promise((resolve4) => {
3246
5377
  rl2.question(`
3247
- \u26A0\uFE0F This will rollback schema changes. Type "yes" to continue: `, resolve);
5378
+ \u26A0\uFE0F This will rollback schema changes. Type "yes" to continue: `, resolve4);
3248
5379
  });
3249
5380
  rl2.close();
3250
5381
  if (answer.trim().toLowerCase() !== "yes") {
@@ -3270,9 +5401,9 @@ var rollbackCommand = new Command("rollback").description("Rollback database mig
3270
5401
  console.log(" \u26A0\uFE0F Restart engine after rollback:");
3271
5402
  console.log(` zveltio stop && zveltio start
3272
5403
  `);
3273
- } catch (err) {
5404
+ } catch (err2) {
3274
5405
  console.error(`
3275
- \u274C Rollback error: ${err.message}
5406
+ \u274C Rollback error: ${err2.message}
3276
5407
  `);
3277
5408
  process.exit(1);
3278
5409
  }
@@ -3324,85 +5455,166 @@ var versionCommand = new Command("version").description("Show version informatio
3324
5455
  });
3325
5456
 
3326
5457
  // src/commands/update.ts
3327
- import { existsSync as existsSync7 } from "fs";
3328
- import { join as join7 } from "path";
5458
+ import { existsSync as existsSync15 } from "fs";
5459
+ import { join as join15 } from "path";
5460
+ var c12 = {
5461
+ bold: (s) => `\x1B[1m${s}\x1B[0m`,
5462
+ green: (s) => `\x1B[32m${s}\x1B[0m`,
5463
+ red: (s) => `\x1B[31m${s}\x1B[0m`,
5464
+ yellow: (s) => `\x1B[33m${s}\x1B[0m`,
5465
+ cyan: (s) => `\x1B[36m${s}\x1B[0m`,
5466
+ dim: (s) => `\x1B[2m${s}\x1B[0m`
5467
+ };
5468
+ var VERSIONS_URL = process.env.ZVELTIO_VERSIONS_URL || "https://get.zveltio.com/versions.json";
3329
5469
  var updateCommand = new Command("update").description("Update Zveltio to the latest version").option("--version <v>", "Target version (default: latest stable)").option("--channel <c>", "Channel: stable | beta", "stable").option("--check", "Check for updates without installing").option("--force", "Skip confirmation").option("--dir <path>", "Installation directory").action(async (opts) => {
3330
5470
  console.log(`
3331
- Zveltio Update
5471
+ ${c12.bold("Zveltio Update")}
3332
5472
  `);
3333
- const installDir = opts.dir || process.env.ZVELTIO_DIR || join7(process.cwd(), "zveltio");
3334
- const metaPath = join7(installDir, ".zveltio-install.json");
5473
+ const installDir = opts.dir || process.env.ZVELTIO_DIR || join15(process.cwd(), "zveltio");
5474
+ const metaPath = join15(installDir, ".zveltio-install.json");
3335
5475
  let meta = null;
3336
- if (existsSync7(metaPath)) {
3337
- meta = JSON.parse(await Bun.file(metaPath).text());
5476
+ if (existsSync15(metaPath)) {
5477
+ try {
5478
+ meta = JSON.parse(await Bun.file(metaPath).text());
5479
+ } catch {}
3338
5480
  }
3339
- const currentVersion = meta?.version || await getCurrentVersionFromAPI(meta?.port ?? 3000) || "0.0.0";
3340
- console.log(` Current version: v${currentVersion}`);
3341
- let versionsData;
5481
+ const currentVersion = meta?.version || await getVersionFromPackageJson() || await getCurrentVersionFromAPI(meta?.port ?? Number(process.env.PORT ?? 3000)) || "0.0.0";
5482
+ const versionSource = meta?.version ? "install meta" : await getVersionFromPackageJson() ? "package.json" : "running engine";
5483
+ console.log(` Current version: ${c12.cyan(`v${currentVersion}`)} ${c12.dim(`(from ${versionSource})`)}`);
5484
+ let versionsData = null;
3342
5485
  try {
3343
- const res = await fetch("https://raw.githubusercontent.com/zveltio/zveltio/main/versions.json");
5486
+ const res = await fetch(VERSIONS_URL, {
5487
+ signal: AbortSignal.timeout(8000)
5488
+ });
5489
+ if (!res.ok) {
5490
+ throw new Error(`Registry returned ${res.status}`);
5491
+ }
3344
5492
  versionsData = await res.json();
3345
- } catch {
3346
- console.error("Cannot fetch version information. Check your connection.");
5493
+ if (!versionsData?.latest) {
5494
+ throw new Error('Invalid versions response (missing "latest" field)');
5495
+ }
5496
+ } catch (err2) {
5497
+ if (opts.check) {
5498
+ console.log(c12.yellow(`
5499
+ Cannot reach version registry.`));
5500
+ console.log(c12.dim(` URL tried: ${VERSIONS_URL}`));
5501
+ console.log(c12.dim(" Override with: ZVELTIO_VERSIONS_URL=<url> zveltio update --check"));
5502
+ console.log(c12.dim(` Error: ${err2.message}
5503
+ `));
5504
+ return;
5505
+ }
5506
+ if (opts.version) {
5507
+ console.log(c12.yellow(`
5508
+ Registry unavailable \u2014 proceeding with explicit version: v${opts.version}`));
5509
+ await runUpdateScript(opts.version, installDir, meta, opts.force ?? false);
5510
+ return;
5511
+ }
5512
+ console.error(c12.red(`
5513
+ Cannot reach the version registry.`));
5514
+ console.error(c12.dim(` URL: ${VERSIONS_URL}`));
5515
+ console.error(c12.dim(` Error: ${err2.message}`));
5516
+ console.log("");
5517
+ console.log(" Options:");
5518
+ console.log(` ${c12.cyan("zveltio update --version <x.y.z>")} ${c12.dim("skip registry, update to specific version")}`);
5519
+ console.log(` ${c12.cyan("ZVELTIO_VERSIONS_URL=<url> zveltio update")} ${c12.dim("use a custom registry")}`);
5520
+ console.log("");
3347
5521
  process.exit(1);
3348
5522
  }
3349
5523
  const targetVersion = opts.version || (opts.channel === "beta" ? versionsData.latest_beta ?? versionsData.latest : versionsData.latest);
3350
- console.log(` Target version: v${targetVersion}`);
5524
+ console.log(` Target version: ${c12.cyan(`v${targetVersion}`)}`);
3351
5525
  if (opts.check) {
3352
5526
  const hasUpdate = compareVersions(targetVersion, currentVersion) > 0;
3353
5527
  if (hasUpdate) {
3354
5528
  const entry = versionsData.versions.find((v) => v.version === targetVersion);
3355
- console.log(`
3356
- Update available!
3357
- `);
5529
+ console.log(c12.green(`
5530
+ Update available: v${currentVersion} \u2192 v${targetVersion}
5531
+ `));
3358
5532
  if (entry?.breaking_changes) {
3359
- console.log(` Contains BREAKING CHANGES`);
5533
+ console.log(c12.yellow(" \u26A0 Contains BREAKING CHANGES \u2014 read release notes before updating."));
5534
+ }
5535
+ if (entry?.release_notes) {
5536
+ console.log(` Release notes: ${entry.release_notes}`);
3360
5537
  }
3361
- console.log(` Release notes: ${entry?.release_notes ?? ""}`);
3362
5538
  console.log(`
3363
- Run: zveltio update
5539
+ Run: ${c12.cyan("zveltio update")}
3364
5540
  `);
3365
5541
  } else {
3366
- console.log(`
3367
- Already up to date.
3368
- `);
5542
+ console.log(c12.green(`
5543
+ Already up to date.
5544
+ `));
3369
5545
  }
3370
5546
  return;
3371
5547
  }
3372
- if (compareVersions(targetVersion, currentVersion) <= 0) {
3373
- console.log(`
3374
- Already up to date.
3375
- `);
5548
+ if (compareVersions(targetVersion, currentVersion) <= 0 && !opts.force) {
5549
+ console.log(c12.green(`
5550
+ Already up to date.
5551
+ `));
3376
5552
  return;
3377
5553
  }
3378
5554
  const targetEntry = versionsData.versions.find((v) => v.version === targetVersion);
3379
5555
  if (targetEntry?.breaking_changes && !opts.force) {
3380
- console.log(`
3381
- This update contains BREAKING CHANGES.`);
3382
- console.log(` Review: ${targetEntry.release_notes}`);
5556
+ console.log(c12.yellow(`
5557
+ This update contains BREAKING CHANGES.`));
5558
+ if (targetEntry.release_notes) {
5559
+ console.log(` Review: ${targetEntry.release_notes}`);
5560
+ }
3383
5561
  console.log("");
3384
- process.stdout.write(" Continue? (yes/no): ");
5562
+ process.stdout.write(" Continue? (yes/no): ");
3385
5563
  const confirm = await readLine();
3386
5564
  if (confirm !== "yes") {
3387
- console.log(`
3388
- Update cancelled.
3389
- `);
5565
+ console.log(c12.dim(`
5566
+ Update cancelled.
5567
+ `));
3390
5568
  return;
3391
5569
  }
3392
5570
  }
3393
5571
  console.log(`
3394
- Upgrading v${currentVersion} -> v${targetVersion}...
5572
+ Upgrading ${c12.cyan(`v${currentVersion}`)} \u2192 ${c12.cyan(`v${targetVersion}`)}...
3395
5573
  `);
3396
- const envPath = join7(installDir, ".env");
3397
- if (existsSync7(envPath)) {
5574
+ await runUpdateScript(targetVersion, installDir, meta, opts.force ?? false);
5575
+ console.log(c12.green(`
5576
+ Updated to Zveltio v${targetVersion}
5577
+ `));
5578
+ });
5579
+ async function getVersionFromPackageJson() {
5580
+ const candidates = [
5581
+ join15(process.cwd(), "packages/engine/package.json"),
5582
+ join15(process.cwd(), "package.json")
5583
+ ];
5584
+ for (const pkgPath of candidates) {
5585
+ if (!existsSync15(pkgPath))
5586
+ continue;
5587
+ try {
5588
+ const pkg = JSON.parse(await Bun.file(pkgPath).text());
5589
+ if (pkg.name === "@zveltio/engine" || pkg._zveltio_version) {
5590
+ return pkg.version ?? null;
5591
+ }
5592
+ } catch {}
5593
+ }
5594
+ return null;
5595
+ }
5596
+ async function getCurrentVersionFromAPI(port) {
5597
+ try {
5598
+ const res = await fetch(`http://localhost:${port}/api/health`, {
5599
+ signal: AbortSignal.timeout(3000)
5600
+ });
5601
+ const data = await res.json();
5602
+ return data.engine ?? data.version ?? data.engine_version ?? null;
5603
+ } catch {
5604
+ return null;
5605
+ }
5606
+ }
5607
+ async function runUpdateScript(targetVersion, installDir, meta, force) {
5608
+ const envPath = join15(installDir, ".env");
5609
+ if (existsSync15(envPath)) {
3398
5610
  const backupPath = `${envPath}.backup.${Date.now()}`;
3399
5611
  const envContent = await Bun.file(envPath).text();
3400
5612
  await Bun.write(backupPath, envContent);
3401
- console.log(` Backed up .env to ${backupPath}`);
5613
+ console.log(` Backed up .env \u2192 ${backupPath}`);
3402
5614
  }
3403
- const installScript = join7(installDir, "install.sh");
3404
- if (existsSync7(installScript)) {
3405
- const proc = Bun.spawn([
5615
+ const installScript = join15(installDir, "install.sh");
5616
+ if (existsSync15(installScript)) {
5617
+ const args = [
3406
5618
  "bash",
3407
5619
  installScript,
3408
5620
  "--version",
@@ -3412,40 +5624,53 @@ Zveltio Update
3412
5624
  installDir,
3413
5625
  "--mode",
3414
5626
  meta?.mode ?? "auto"
3415
- ], { stdout: "inherit", stderr: "inherit", stdin: "inherit" });
5627
+ ];
5628
+ if (force)
5629
+ args.push("--force");
5630
+ const proc = Bun.spawn(args, { stdout: "inherit", stderr: "inherit", stdin: "inherit" });
3416
5631
  const code = await proc.exited;
3417
5632
  if (code !== 0) {
3418
5633
  console.error(`
3419
- Update failed. Your previous version is still running.`);
3420
- console.log(` Restore .env: copy ${envPath}.backup.* to ${envPath}`);
5634
+ Update failed. Your previous version is still running.`);
5635
+ if (existsSync15(envPath)) {
5636
+ console.log(` Restore .env: copy ${envPath}.backup.* to ${envPath}`);
5637
+ }
3421
5638
  process.exit(1);
3422
5639
  }
3423
5640
  } else {
3424
- console.log(" Downloading installer...");
3425
- const dlProc = Bun.spawn([
5641
+ console.log(" No local install.sh found \u2014 downloading installer from registry...");
5642
+ const installUrl = process.env.ZVELTIO_INSTALL_URL || "https://get.zveltio.com/install.sh";
5643
+ if (!/^\d+\.\d+\.\d+(-[\w.]+)?$/.test(targetVersion)) {
5644
+ console.error(c12.red(`
5645
+ Invalid version format: ${targetVersion}`));
5646
+ process.exit(1);
5647
+ }
5648
+ const dlProc = Bun.spawn(["curl", "-fsSL", installUrl, "-o", "/tmp/zveltio-update.sh"], {
5649
+ stdout: "inherit",
5650
+ stderr: "inherit"
5651
+ });
5652
+ if (await dlProc.exited !== 0) {
5653
+ console.error(`
5654
+ Failed to download installer.`);
5655
+ console.error(` Installer URL: ${installUrl}`);
5656
+ process.exit(1);
5657
+ }
5658
+ const installProc = Bun.spawn([
3426
5659
  "bash",
3427
- "-c",
3428
- `curl -fsSL https://get.zveltio.com/install.sh -o /tmp/zveltio-update.sh && ` + `bash /tmp/zveltio-update.sh ` + `--version ${targetVersion} --unattended --dir ${installDir}`
5660
+ "/tmp/zveltio-update.sh",
5661
+ "--version",
5662
+ targetVersion,
5663
+ "--unattended",
5664
+ "--dir",
5665
+ installDir
3429
5666
  ], { stdout: "inherit", stderr: "inherit", stdin: "inherit" });
3430
- const code = await dlProc.exited;
5667
+ const code = await installProc.exited;
3431
5668
  if (code !== 0) {
3432
5669
  console.error(`
3433
- Update download/install failed.`);
5670
+ Update install failed.`);
3434
5671
  process.exit(1);
3435
5672
  }
3436
5673
  }
3437
- console.log(`
3438
- Updated to Zveltio v${targetVersion}
3439
- `);
3440
- });
3441
- async function getCurrentVersionFromAPI(port) {
3442
- try {
3443
- const res = await fetch(`http://localhost:${port}/api/health/version`);
3444
- const data = await res.json();
3445
- return data.engine ?? null;
3446
- } catch {
3447
- return null;
3448
- }
3449
5674
  }
3450
5675
  function compareVersions(a, b) {
3451
5676
  const pa = a.split(".").map(Number);
@@ -3459,13 +5684,13 @@ function compareVersions(a, b) {
3459
5684
  return 0;
3460
5685
  }
3461
5686
  async function readLine() {
3462
- return new Promise((resolve) => {
3463
- process.stdin.once("data", (data) => resolve(data.toString().trim()));
5687
+ return new Promise((resolve4) => {
5688
+ process.stdin.once("data", (data) => resolve4(data.toString().trim()));
3464
5689
  });
3465
5690
  }
3466
5691
 
3467
5692
  // src/commands/status.ts
3468
- var c6 = {
5693
+ var c13 = {
3469
5694
  bold: (s) => `\x1B[1m${s}\x1B[0m`,
3470
5695
  green: (s) => `\x1B[32m${s}\x1B[0m`,
3471
5696
  red: (s) => `\x1B[31m${s}\x1B[0m`,
@@ -3475,10 +5700,10 @@ var c6 = {
3475
5700
  };
3476
5701
  function statusIcon(ok) {
3477
5702
  if (ok === true || ok === "ok" || ok === "healthy" || ok === "connected")
3478
- return c6.green("OK");
5703
+ return c13.green("OK");
3479
5704
  if (ok === false || ok === "error" || ok === "unhealthy")
3480
- return c6.red("FAIL");
3481
- return c6.yellow("UNKNOWN");
5705
+ return c13.red("FAIL");
5706
+ return c13.yellow("UNKNOWN");
3482
5707
  }
3483
5708
  function formatUptime(seconds) {
3484
5709
  if (seconds < 60)
@@ -3493,9 +5718,9 @@ async function statusCommand(opts) {
3493
5718
  const engineUrl = opts.url || process.env.ZVELTIO_URL || process.env.ENGINE_URL || "http://localhost:3000";
3494
5719
  if (!opts.json) {
3495
5720
  console.log(`
3496
- ${c6.bold("Zveltio Status")}
5721
+ ${c13.bold("Zveltio Status")}
3497
5722
  `);
3498
- console.log(` Engine: ${c6.dim(engineUrl)}`);
5723
+ console.log(` Engine: ${c13.dim(engineUrl)}`);
3499
5724
  console.log("");
3500
5725
  }
3501
5726
  let healthData;
@@ -3507,23 +5732,23 @@ ${c6.bold("Zveltio Status")}
3507
5732
  if (opts.json) {
3508
5733
  console.log(JSON.stringify({ status: "error", code: res.status, message: res.statusText }));
3509
5734
  } else {
3510
- console.error(c6.red(`Engine returned ${res.status} ${res.statusText}`));
3511
- console.error(c6.dim(" Is the engine running? Try: zveltio dev"));
5735
+ console.error(c13.red(`Engine returned ${res.status} ${res.statusText}`));
5736
+ console.error(c13.dim(" Is the engine running? Try: zveltio dev"));
3512
5737
  }
3513
5738
  process.exit(1);
3514
5739
  }
3515
5740
  healthData = await res.json();
3516
- } catch (err) {
5741
+ } catch (err2) {
3517
5742
  if (opts.json) {
3518
- console.log(JSON.stringify({ status: "error", message: err.message }));
5743
+ console.log(JSON.stringify({ status: "error", message: err2.message }));
3519
5744
  } else {
3520
- if (err.name === "TimeoutError") {
3521
- console.error(c6.red("Connection timed out after 8s"));
5745
+ if (err2.name === "TimeoutError") {
5746
+ console.error(c13.red("Connection timed out after 8s"));
3522
5747
  } else {
3523
- console.error(c6.red(`Cannot reach engine at ${engineUrl}`));
3524
- console.error(c6.dim(` ${err.message}`));
5748
+ console.error(c13.red(`Cannot reach engine at ${engineUrl}`));
5749
+ console.error(c13.dim(` ${err2.message}`));
3525
5750
  }
3526
- console.error(c6.dim(" Is the engine running? Try: zveltio dev"));
5751
+ console.error(c13.dim(" Is the engine running? Try: zveltio dev"));
3527
5752
  }
3528
5753
  process.exit(1);
3529
5754
  }
@@ -3534,9 +5759,9 @@ ${c6.bold("Zveltio Status")}
3534
5759
  const db = healthData.database ?? healthData.db;
3535
5760
  const cache = healthData.cache ?? healthData.redis ?? healthData.valkey;
3536
5761
  const uptime = healthData.uptime_seconds ?? healthData.uptime;
3537
- const version = healthData.version ?? healthData.engine_version ?? healthData.engine;
3538
5762
  const status = healthData.status ?? "ok";
3539
- const overallIcon = status === "ok" || status === "healthy" ? c6.green("HEALTHY") : c6.red("DEGRADED");
5763
+ const version = healthData.version ?? healthData.engine_version ?? healthData.engine ?? await getLocalVersion();
5764
+ const overallIcon = status === "ok" || status === "healthy" ? c13.green("HEALTHY") : c13.red("DEGRADED");
3540
5765
  console.log(` Status: ${overallIcon}`);
3541
5766
  if (version) {
3542
5767
  console.log(` Version: ${version}`);
@@ -3548,13 +5773,13 @@ ${c6.bold("Zveltio Status")}
3548
5773
  if (db !== undefined) {
3549
5774
  const dbStatus = typeof db === "object" ? db.status ?? db.ok : db;
3550
5775
  const dbLatency = typeof db === "object" ? db.latency_ms : undefined;
3551
- const dbLabel = dbLatency !== undefined ? `${statusIcon(dbStatus)} ${c6.dim(`(${dbLatency}ms)`)}` : statusIcon(dbStatus);
5776
+ const dbLabel = dbLatency !== undefined ? `${statusIcon(dbStatus)} ${c13.dim(`(${dbLatency}ms)`)}` : statusIcon(dbStatus);
3552
5777
  console.log(` Database: ${dbLabel}`);
3553
5778
  }
3554
5779
  if (cache !== undefined) {
3555
5780
  const cacheStatus = typeof cache === "object" ? cache.status ?? cache.ok : cache;
3556
5781
  const cacheLatency = typeof cache === "object" ? cache.latency_ms : undefined;
3557
- const cacheLabel = cacheLatency !== undefined ? `${statusIcon(cacheStatus)} ${c6.dim(`(${cacheLatency}ms)`)}` : statusIcon(cacheStatus);
5782
+ const cacheLabel = cacheLatency !== undefined ? `${statusIcon(cacheStatus)} ${c13.dim(`(${cacheLatency}ms)`)}` : statusIcon(cacheStatus);
3558
5783
  console.log(` Cache: ${cacheLabel}`);
3559
5784
  }
3560
5785
  if (healthData.storage !== undefined) {
@@ -3571,6 +5796,24 @@ ${c6.bold("Zveltio Status")}
3571
5796
  }
3572
5797
  console.log("");
3573
5798
  }
5799
+ async function getLocalVersion() {
5800
+ const { existsSync: existsSync16 } = await import("fs");
5801
+ const { join: join16 } = await import("path");
5802
+ const candidates = [
5803
+ join16(process.cwd(), "packages/engine/package.json"),
5804
+ join16(process.cwd(), "package.json")
5805
+ ];
5806
+ for (const p of candidates) {
5807
+ if (!existsSync16(p))
5808
+ continue;
5809
+ try {
5810
+ const pkg = JSON.parse(await Bun.file(p).text());
5811
+ if (pkg.name === "@zveltio/engine" || pkg._zveltio_version)
5812
+ return pkg.version;
5813
+ } catch {}
5814
+ }
5815
+ return;
5816
+ }
3574
5817
 
3575
5818
  // src/index.ts
3576
5819
  var program2 = new Command;
@@ -3614,6 +5857,67 @@ extensions.command("disable <name>").description("Disable an active extension").
3614
5857
  var ext = program2.command("extension").description('Manage Zveltio extensions (use "extensions" for new commands)');
3615
5858
  ext.command("create <name>").description("Scaffold a new extension").option("--category <category>", "Extension category", "custom").action((name, opts) => extensionCommand("create", name, opts));
3616
5859
  ext.command("build").description("Build the current extension (.zvext bundle)").action(() => extensionCommand("build", "", {}));
3617
- ext.command("dev").description("Start extension dev server with hot reload").action(() => extensionCommand("dev", "", {}));
3618
- ext.command("publish").description("Publish extension to the Zveltio marketplace").option("--token <token>", "Marketplace auth token").action((_opts) => extensionCommand("publish", "", _opts));
3619
- program2.parse(process.argv);
5860
+ ext.command("dev").description("Watch engine/ + studio/, hot-reload the running engine on changes (S4-03)").option("--dir <dir>", "Extension root directory (defaults to cwd)").option("--url <url>", "Engine URL (env: ZVELTIO_ENGINE_URL)", "http://localhost:3000").option("--name <name>", "Extension name (default: read from manifest.json)").option("--no-studio", "Skip the Studio dev process (engine watch only)").action((opts) => extensionDevCommand(opts));
5861
+ ext.command("pack").description("Bundle engine/index.ts \u2192 engine/index.js (target=bun, ESM) and write engine + integrity blocks into manifest.json. Required before publish on v2 channels.").option("--dir <dir>", "Extension root directory (defaults to cwd)").option("--sourcemap", "Emit engine/index.js.map alongside the bundle").option("--no-manifest-update", "Build the bundle but do not patch manifest.json").option("--first-party", "Vendor / monorepo build \u2014 keep inline isolation, skip worker auto-inject").option("--token <token>", "Registry token for the publisher-tier lookup (env: ZVELTIO_REGISTRY_TOKEN)").option("--registry-url <url>", "Registry base URL (env: ZVELTIO_REGISTRY_URL)").action(async (opts) => {
5862
+ const { extensionPackCommand: extensionPackCommand2 } = await Promise.resolve().then(() => (init_extension_pack(), exports_extension_pack));
5863
+ return extensionPackCommand2(opts);
5864
+ });
5865
+ ext.command("publish").description("Validate, build, archive, sign, and upload an extension to the registry (S4-05)").option("--dir <dir>", "Extension root directory (defaults to cwd)").option("--token <token>", "Registry auth token (env: ZVELTIO_REGISTRY_TOKEN)").option("--registry-url <url>", "Registry base URL (env: ZVELTIO_REGISTRY_URL)").option("--key-id <id>", "Signing key id (default: the only key in ~/.zveltio/keys/)").option("--output <dir>", "Local-only mode: write .zvext + .sig here, skip upload").option("--no-build", "Skip both engine pack and Studio build").option("--no-pack", "Skip engine pack (use existing engine/index.js if present)").option("--no-validate", "Skip the validate step (NOT recommended)").option("--dry-run", "Run validate + pack + build, skip archive/sign/upload").option("--first-party", "Vendor / monorepo build \u2014 allow inline isolation (skip \xA72 worker requirement)").action((opts) => extensionPublishCommand(opts));
5866
+ ext.command("status <name>").description("Show marketplace submission status (pending / published / rejected / taken_down)").option("--registry-url <url>", "Registry base URL (env: ZVELTIO_REGISTRY_URL)").action(async (name, opts) => {
5867
+ const { extensionStatusCommand: extensionStatusCommand2 } = await Promise.resolve().then(() => (init_extension_status(), exports_extension_status));
5868
+ await extensionStatusCommand2(name, opts);
5869
+ });
5870
+ ext.command("types").description("Generate a .d.ts from the extension's SQL migrations (S4-01). Writes <extension>/.zveltio/db.d.ts.").option("--dir <dir>", "Extension root directory (defaults to cwd)").option("--output <path>", "Output file path (default: <dir>/.zveltio/db.d.ts)").action((opts) => extensionTypesCommand(opts));
5871
+ ext.command("validate").description("Pre-publish checks: manifest schema, peerDeps allow-list, migrations parse, destructive DDL has DOWN, bundle quota (S4-04)").option("--dir <dir>", "Extension root directory (defaults to cwd)").option("--first-party", "Vendor / monorepo build \u2014 allow inline isolation (skip \xA72 worker requirement)").option("--token <token>", "Registry token for the publisher-tier lookup (env: ZVELTIO_REGISTRY_TOKEN)").option("--registry-url <url>", "Registry base URL (env: ZVELTIO_REGISTRY_URL)").action((opts) => extensionValidateCommand(opts));
5872
+ var keys = program2.command("keys").description("Manage Ed25519 keypairs for signing extensions");
5873
+ keys.command("generate").description("Generate a new keypair and store it in ~/.zveltio/keys/").option("--id <id>", "Stable identifier for the key (random if omitted)").option("--force", "Overwrite an existing key with the same id").action((opts) => keysGenerateCommand(opts));
5874
+ keys.command("list").description("List keypairs stored in ~/.zveltio/keys/").action(() => keysListCommand());
5875
+ keys.command("export <keyId>").description("Print the public key as a trusted-key JSON entry for REGISTRY_PUBLIC_KEYS_JSON").action((keyId) => keysExportCommand(keyId));
5876
+ var admin = program2.command("admin").description("Registry admin commands (operator-only)");
5877
+ var adminMarketplace = admin.command("marketplace").description("Review-queue management for community extension submissions");
5878
+ adminMarketplace.command("pending").description("List extension submissions awaiting review").option("--registry-url <url>", "Registry base URL (env: ZVELTIO_REGISTRY_URL)").option("--cookie <cookie>", "Admin session cookie (env: ZVELTIO_ADMIN_COOKIE)").action(async (opts) => {
5879
+ const { adminMarketplacePending: adminMarketplacePending2 } = await Promise.resolve().then(() => (init_admin_marketplace(), exports_admin_marketplace));
5880
+ await adminMarketplacePending2(opts);
5881
+ });
5882
+ adminMarketplace.command("approve <nameOrId>").description("Approve a pending submission (status \u2192 published)").option("--registry-url <url>", "Registry base URL").option("--cookie <cookie>", "Admin session cookie").option("--note <note>", "Optional internal note for the audit trail").action(async (nameOrId, opts) => {
5883
+ const { adminMarketplaceApprove: adminMarketplaceApprove2 } = await Promise.resolve().then(() => (init_admin_marketplace(), exports_admin_marketplace));
5884
+ await adminMarketplaceApprove2(nameOrId, opts);
5885
+ });
5886
+ adminMarketplace.command("reject <nameOrId>").description("Reject a pending submission with a reason (visible to publisher)").option("--registry-url <url>", "Registry base URL").option("--cookie <cookie>", "Admin session cookie").requiredOption("--reason <reason>", "Rejection reason").action(async (nameOrId, opts) => {
5887
+ const { adminMarketplaceReject: adminMarketplaceReject2 } = await Promise.resolve().then(() => (init_admin_marketplace(), exports_admin_marketplace));
5888
+ await adminMarketplaceReject2(nameOrId, opts);
5889
+ });
5890
+ adminMarketplace.command("takedown <extensionId>").description("Pull a previously approved extension (status \u2192 taken_down)").option("--registry-url <url>", "Registry base URL").option("--cookie <cookie>", "Admin session cookie").requiredOption("--reason <reason>", "Takedown reason").action(async (id, opts) => {
5891
+ const { adminMarketplaceTakedown: adminMarketplaceTakedown2 } = await Promise.resolve().then(() => (init_admin_marketplace(), exports_admin_marketplace));
5892
+ await adminMarketplaceTakedown2(id, opts);
5893
+ });
5894
+ adminMarketplace.command("publishers").description("List enrolled allowed publishers").option("--registry-url <url>", "Registry base URL").option("--cookie <cookie>", "Admin session cookie").action(async (opts) => {
5895
+ const { adminMarketplacePublishers: adminMarketplacePublishers2 } = await Promise.resolve().then(() => (init_admin_marketplace(), exports_admin_marketplace));
5896
+ await adminMarketplacePublishers2(opts);
5897
+ });
5898
+ var adminTeam = admin.command("team").description("Marketplace admin roster (review-queue access). Owner-only writes.");
5899
+ adminTeam.command("list").description("List current admin team members + roles").option("--registry-url <url>", "Registry base URL").option("--cookie <cookie>", "Admin session cookie").action(async (opts) => {
5900
+ const { adminTeamList: adminTeamList2 } = await Promise.resolve().then(() => (init_admin_marketplace(), exports_admin_marketplace));
5901
+ await adminTeamList2(opts);
5902
+ });
5903
+ adminTeam.command("add <email>").description("Add a user to the admin team (they must have an apps account first)").option("--registry-url <url>", "Registry base URL").option("--cookie <cookie>", "Admin session cookie").option("--role <role>", "Role: 'owner' | 'admin'", "admin").option("--notes <notes>", "Internal notes").action(async (email, opts) => {
5904
+ const { adminTeamAdd: adminTeamAdd2 } = await Promise.resolve().then(() => (init_admin_marketplace(), exports_admin_marketplace));
5905
+ await adminTeamAdd2(email, opts);
5906
+ });
5907
+ adminTeam.command("set-role <email> <role>").description("Change a team member's role (owner|admin). Refuses to demote the last owner.").option("--registry-url <url>", "Registry base URL").option("--cookie <cookie>", "Admin session cookie").action(async (email, role, opts) => {
5908
+ if (role !== "owner" && role !== "admin") {
5909
+ console.error(`Invalid role "${role}" \u2014 must be 'owner' or 'admin'`);
5910
+ process.exit(1);
5911
+ }
5912
+ const { adminTeamSetRole: adminTeamSetRole2 } = await Promise.resolve().then(() => (init_admin_marketplace(), exports_admin_marketplace));
5913
+ await adminTeamSetRole2(email, role, opts);
5914
+ });
5915
+ adminTeam.command("remove <email>").description("Remove a user from the admin team").option("--registry-url <url>", "Registry base URL").option("--cookie <cookie>", "Admin session cookie").action(async (email, opts) => {
5916
+ const { adminTeamRemove: adminTeamRemove2 } = await Promise.resolve().then(() => (init_admin_marketplace(), exports_admin_marketplace));
5917
+ await adminTeamRemove2(email, opts);
5918
+ });
5919
+ adminMarketplace.command("enroll-publisher").description("Add a new publisher to the allowlist (key-based submissions)").option("--registry-url <url>", "Registry base URL").option("--cookie <cookie>", "Admin session cookie").requiredOption("--name <name>", "Publisher display name").requiredOption("--email <email>", "Publisher contact email").requiredOption("--key-id <keyId>", "Ed25519 key id (used at signature time)").requiredOption("--key-file <path>", "Path to JSON file containing the public JWK").option("--tier <tier>", "Trust tier: first-party | verified | community", "community").option("--notes <notes>", "Internal notes").action(async (opts) => {
5920
+ const { adminMarketplaceEnrollPublisher: adminMarketplaceEnrollPublisher2 } = await Promise.resolve().then(() => (init_admin_marketplace(), exports_admin_marketplace));
5921
+ await adminMarketplaceEnrollPublisher2(opts);
5922
+ });
5923
+ await program2.parseAsync(process.argv);