@zveltio/cli 2.0.0 → 3.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +2523 -189
- 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
|
-
|
|
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
|
-
|
|
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(`
|
|
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
|
-
|
|
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
|
-
|
|
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: "
|
|
2568
|
-
zveltioMaxVersion: "
|
|
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.
|
|
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/
|
|
2742
|
-
import { mkdirSync as mkdirSync2 } from "fs";
|
|
2743
|
-
import {
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
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
|
-
|
|
2771
|
-
|
|
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(
|
|
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(
|
|
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 =
|
|
4907
|
+
const dir = dirname4(outputPath);
|
|
2781
4908
|
if (dir && dir !== ".") {
|
|
2782
|
-
|
|
4909
|
+
mkdirSync5(dir, { recursive: true });
|
|
2783
4910
|
}
|
|
2784
4911
|
await Bun.write(outputPath, types);
|
|
2785
|
-
console.log(`${
|
|
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(
|
|
4916
|
+
console.log(c11.dim(` ${typeCount} type declaration(s) written`));
|
|
2790
4917
|
}
|
|
2791
4918
|
}
|
|
2792
4919
|
console.log("");
|
|
2793
|
-
} catch (
|
|
2794
|
-
console.error(
|
|
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
|
|
4927
|
+
import { existsSync as existsSync14 } from "fs";
|
|
2801
4928
|
import { readFile as readFile2, mkdir as mkdir2, cp } from "fs/promises";
|
|
2802
|
-
import { join as
|
|
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 (!
|
|
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(
|
|
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 =
|
|
2846
|
-
if (
|
|
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 =
|
|
2858
|
-
if (
|
|
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`, {
|
|
5055
|
+
const installRes = await fetch(`${engineUrl}/api/marketplace/${entry.name}/install`, {
|
|
5056
|
+
method: "POST"
|
|
5057
|
+
}).catch(() => null);
|
|
2929
5058
|
if (!installRes?.ok) {
|
|
2930
|
-
const
|
|
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: ${
|
|
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`, {
|
|
5070
|
+
const enableRes = await fetch(`${engineUrl}/api/marketplace/${entry.name}/enable`, {
|
|
5071
|
+
method: "POST"
|
|
5072
|
+
}).catch(() => null);
|
|
2942
5073
|
if (!enableRes?.ok) {
|
|
2943
|
-
const
|
|
2944
|
-
console.error(`\u274C Enable failed: ${
|
|
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((
|
|
2983
|
-
rl2.question(question,
|
|
5113
|
+
return new Promise((resolve4) => {
|
|
5114
|
+
rl2.question(question, resolve4);
|
|
2984
5115
|
});
|
|
2985
5116
|
}
|
|
2986
5117
|
async function promptHidden(question) {
|
|
2987
|
-
return new Promise((
|
|
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
|
-
|
|
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
|
|
3095
|
-
throw new Error(
|
|
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 (
|
|
5267
|
+
} catch (err2) {
|
|
3137
5268
|
rl2.close();
|
|
3138
5269
|
console.error(`
|
|
3139
|
-
\u274C Failed to create god user: ${
|
|
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((
|
|
5376
|
+
const answer = await new Promise((resolve4) => {
|
|
3246
5377
|
rl2.question(`
|
|
3247
|
-
\u26A0\uFE0F This will rollback schema changes. Type "yes" to continue: `,
|
|
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 (
|
|
5404
|
+
} catch (err2) {
|
|
3274
5405
|
console.error(`
|
|
3275
|
-
\u274C Rollback error: ${
|
|
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
|
|
3328
|
-
import { join as
|
|
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 ||
|
|
3334
|
-
const metaPath =
|
|
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 (
|
|
3337
|
-
|
|
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
|
-
|
|
3341
|
-
|
|
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(
|
|
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
|
-
|
|
3346
|
-
|
|
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(`
|
|
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
|
-
|
|
3357
|
-
`);
|
|
5529
|
+
console.log(c12.green(`
|
|
5530
|
+
Update available: v${currentVersion} \u2192 v${targetVersion}
|
|
5531
|
+
`));
|
|
3358
5532
|
if (entry?.breaking_changes) {
|
|
3359
|
-
console.log(
|
|
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
|
-
|
|
5539
|
+
Run: ${c12.cyan("zveltio update")}
|
|
3364
5540
|
`);
|
|
3365
5541
|
} else {
|
|
3366
|
-
console.log(`
|
|
3367
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3382
|
-
|
|
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("
|
|
5562
|
+
process.stdout.write(" Continue? (yes/no): ");
|
|
3385
5563
|
const confirm = await readLine();
|
|
3386
5564
|
if (confirm !== "yes") {
|
|
3387
|
-
console.log(`
|
|
3388
|
-
|
|
3389
|
-
`);
|
|
5565
|
+
console.log(c12.dim(`
|
|
5566
|
+
Update cancelled.
|
|
5567
|
+
`));
|
|
3390
5568
|
return;
|
|
3391
5569
|
}
|
|
3392
5570
|
}
|
|
3393
5571
|
console.log(`
|
|
3394
|
-
|
|
5572
|
+
Upgrading ${c12.cyan(`v${currentVersion}`)} \u2192 ${c12.cyan(`v${targetVersion}`)}...
|
|
3395
5573
|
`);
|
|
3396
|
-
|
|
3397
|
-
|
|
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(`
|
|
5613
|
+
console.log(` Backed up .env \u2192 ${backupPath}`);
|
|
3402
5614
|
}
|
|
3403
|
-
const installScript =
|
|
3404
|
-
if (
|
|
3405
|
-
const
|
|
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
|
-
]
|
|
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
|
-
|
|
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("
|
|
3425
|
-
const
|
|
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
|
-
"-
|
|
3428
|
-
|
|
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
|
|
5667
|
+
const code = await installProc.exited;
|
|
3431
5668
|
if (code !== 0) {
|
|
3432
5669
|
console.error(`
|
|
3433
|
-
Update
|
|
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((
|
|
3463
|
-
process.stdin.once("data", (data) =>
|
|
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
|
|
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
|
|
5703
|
+
return c13.green("OK");
|
|
3479
5704
|
if (ok === false || ok === "error" || ok === "unhealthy")
|
|
3480
|
-
return
|
|
3481
|
-
return
|
|
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
|
-
${
|
|
5721
|
+
${c13.bold("Zveltio Status")}
|
|
3497
5722
|
`);
|
|
3498
|
-
console.log(` Engine: ${
|
|
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(
|
|
3511
|
-
console.error(
|
|
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 (
|
|
5741
|
+
} catch (err2) {
|
|
3517
5742
|
if (opts.json) {
|
|
3518
|
-
console.log(JSON.stringify({ status: "error", message:
|
|
5743
|
+
console.log(JSON.stringify({ status: "error", message: err2.message }));
|
|
3519
5744
|
} else {
|
|
3520
|
-
if (
|
|
3521
|
-
console.error(
|
|
5745
|
+
if (err2.name === "TimeoutError") {
|
|
5746
|
+
console.error(c13.red("Connection timed out after 8s"));
|
|
3522
5747
|
} else {
|
|
3523
|
-
console.error(
|
|
3524
|
-
console.error(
|
|
5748
|
+
console.error(c13.red(`Cannot reach engine at ${engineUrl}`));
|
|
5749
|
+
console.error(c13.dim(` ${err2.message}`));
|
|
3525
5750
|
}
|
|
3526
|
-
console.error(
|
|
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
|
|
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)} ${
|
|
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)} ${
|
|
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,10 +5796,58 @@ ${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
|
+
}
|
|
5817
|
+
// package.json
|
|
5818
|
+
var package_default = {
|
|
5819
|
+
name: "@zveltio/cli",
|
|
5820
|
+
version: "3.0.0-beta.2",
|
|
5821
|
+
type: "module",
|
|
5822
|
+
bin: {
|
|
5823
|
+
zveltio: "./dist/index.js"
|
|
5824
|
+
},
|
|
5825
|
+
files: [
|
|
5826
|
+
"dist",
|
|
5827
|
+
"README.md"
|
|
5828
|
+
],
|
|
5829
|
+
scripts: {
|
|
5830
|
+
build: "bun build src/index.ts --outfile dist/index.js --target bun",
|
|
5831
|
+
typecheck: "tsc --noEmit",
|
|
5832
|
+
dev: "bun run --watch src/index.ts"
|
|
5833
|
+
},
|
|
5834
|
+
dependencies: {
|
|
5835
|
+
"@zveltio/sdk": "3.0.0-beta.2",
|
|
5836
|
+
commander: "^12.0.0"
|
|
5837
|
+
},
|
|
5838
|
+
devDependencies: {
|
|
5839
|
+
"bun-types": "latest",
|
|
5840
|
+
typescript: "^5.4.0"
|
|
5841
|
+
},
|
|
5842
|
+
repository: {
|
|
5843
|
+
type: "git",
|
|
5844
|
+
url: "git+https://github.com/zveltio-devs/zveltio.git"
|
|
5845
|
+
}
|
|
5846
|
+
};
|
|
3574
5847
|
|
|
3575
5848
|
// src/index.ts
|
|
3576
5849
|
var program2 = new Command;
|
|
3577
|
-
program2.name("zveltio").description("The official Zveltio CLI").version(
|
|
5850
|
+
program2.name("zveltio").description("The official Zveltio CLI").version(package_default.version);
|
|
3578
5851
|
program2.command("init [dir]").description("Initialize a new Zveltio project (interactive)").option("--template <template>", "Starter template to use", "default").action(initCommand);
|
|
3579
5852
|
program2.command("dev").description("Start Zveltio in development mode (with hot reload)").option("-p, --port <port>", "Port to listen on", "3000").option("--no-studio", "Disable Studio embed (API only)").action(devCommand);
|
|
3580
5853
|
program2.command("start").description("Start Zveltio in production mode (uses compiled binary if available)").option("-p, --port <port>", "Port to listen on", "3000").option("--binary <path>", "Path to compiled binary").action(startCommand);
|
|
@@ -3614,6 +5887,67 @@ extensions.command("disable <name>").description("Disable an active extension").
|
|
|
3614
5887
|
var ext = program2.command("extension").description('Manage Zveltio extensions (use "extensions" for new commands)');
|
|
3615
5888
|
ext.command("create <name>").description("Scaffold a new extension").option("--category <category>", "Extension category", "custom").action((name, opts) => extensionCommand("create", name, opts));
|
|
3616
5889
|
ext.command("build").description("Build the current extension (.zvext bundle)").action(() => extensionCommand("build", "", {}));
|
|
3617
|
-
ext.command("dev").description("
|
|
3618
|
-
ext.command("
|
|
3619
|
-
|
|
5890
|
+
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));
|
|
5891
|
+
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) => {
|
|
5892
|
+
const { extensionPackCommand: extensionPackCommand2 } = await Promise.resolve().then(() => (init_extension_pack(), exports_extension_pack));
|
|
5893
|
+
return extensionPackCommand2(opts);
|
|
5894
|
+
});
|
|
5895
|
+
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));
|
|
5896
|
+
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) => {
|
|
5897
|
+
const { extensionStatusCommand: extensionStatusCommand2 } = await Promise.resolve().then(() => (init_extension_status(), exports_extension_status));
|
|
5898
|
+
await extensionStatusCommand2(name, opts);
|
|
5899
|
+
});
|
|
5900
|
+
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));
|
|
5901
|
+
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));
|
|
5902
|
+
var keys = program2.command("keys").description("Manage Ed25519 keypairs for signing extensions");
|
|
5903
|
+
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));
|
|
5904
|
+
keys.command("list").description("List keypairs stored in ~/.zveltio/keys/").action(() => keysListCommand());
|
|
5905
|
+
keys.command("export <keyId>").description("Print the public key as a trusted-key JSON entry for REGISTRY_PUBLIC_KEYS_JSON").action((keyId) => keysExportCommand(keyId));
|
|
5906
|
+
var admin = program2.command("admin").description("Registry admin commands (operator-only)");
|
|
5907
|
+
var adminMarketplace = admin.command("marketplace").description("Review-queue management for community extension submissions");
|
|
5908
|
+
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) => {
|
|
5909
|
+
const { adminMarketplacePending: adminMarketplacePending2 } = await Promise.resolve().then(() => (init_admin_marketplace(), exports_admin_marketplace));
|
|
5910
|
+
await adminMarketplacePending2(opts);
|
|
5911
|
+
});
|
|
5912
|
+
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) => {
|
|
5913
|
+
const { adminMarketplaceApprove: adminMarketplaceApprove2 } = await Promise.resolve().then(() => (init_admin_marketplace(), exports_admin_marketplace));
|
|
5914
|
+
await adminMarketplaceApprove2(nameOrId, opts);
|
|
5915
|
+
});
|
|
5916
|
+
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) => {
|
|
5917
|
+
const { adminMarketplaceReject: adminMarketplaceReject2 } = await Promise.resolve().then(() => (init_admin_marketplace(), exports_admin_marketplace));
|
|
5918
|
+
await adminMarketplaceReject2(nameOrId, opts);
|
|
5919
|
+
});
|
|
5920
|
+
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) => {
|
|
5921
|
+
const { adminMarketplaceTakedown: adminMarketplaceTakedown2 } = await Promise.resolve().then(() => (init_admin_marketplace(), exports_admin_marketplace));
|
|
5922
|
+
await adminMarketplaceTakedown2(id, opts);
|
|
5923
|
+
});
|
|
5924
|
+
adminMarketplace.command("publishers").description("List enrolled allowed publishers").option("--registry-url <url>", "Registry base URL").option("--cookie <cookie>", "Admin session cookie").action(async (opts) => {
|
|
5925
|
+
const { adminMarketplacePublishers: adminMarketplacePublishers2 } = await Promise.resolve().then(() => (init_admin_marketplace(), exports_admin_marketplace));
|
|
5926
|
+
await adminMarketplacePublishers2(opts);
|
|
5927
|
+
});
|
|
5928
|
+
var adminTeam = admin.command("team").description("Marketplace admin roster (review-queue access). Owner-only writes.");
|
|
5929
|
+
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) => {
|
|
5930
|
+
const { adminTeamList: adminTeamList2 } = await Promise.resolve().then(() => (init_admin_marketplace(), exports_admin_marketplace));
|
|
5931
|
+
await adminTeamList2(opts);
|
|
5932
|
+
});
|
|
5933
|
+
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) => {
|
|
5934
|
+
const { adminTeamAdd: adminTeamAdd2 } = await Promise.resolve().then(() => (init_admin_marketplace(), exports_admin_marketplace));
|
|
5935
|
+
await adminTeamAdd2(email, opts);
|
|
5936
|
+
});
|
|
5937
|
+
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) => {
|
|
5938
|
+
if (role !== "owner" && role !== "admin") {
|
|
5939
|
+
console.error(`Invalid role "${role}" \u2014 must be 'owner' or 'admin'`);
|
|
5940
|
+
process.exit(1);
|
|
5941
|
+
}
|
|
5942
|
+
const { adminTeamSetRole: adminTeamSetRole2 } = await Promise.resolve().then(() => (init_admin_marketplace(), exports_admin_marketplace));
|
|
5943
|
+
await adminTeamSetRole2(email, role, opts);
|
|
5944
|
+
});
|
|
5945
|
+
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) => {
|
|
5946
|
+
const { adminTeamRemove: adminTeamRemove2 } = await Promise.resolve().then(() => (init_admin_marketplace(), exports_admin_marketplace));
|
|
5947
|
+
await adminTeamRemove2(email, opts);
|
|
5948
|
+
});
|
|
5949
|
+
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) => {
|
|
5950
|
+
const { adminMarketplaceEnrollPublisher: adminMarketplaceEnrollPublisher2 } = await Promise.resolve().then(() => (init_admin_marketplace(), exports_admin_marketplace));
|
|
5951
|
+
await adminMarketplaceEnrollPublisher2(opts);
|
|
5952
|
+
});
|
|
5953
|
+
await program2.parseAsync(process.argv);
|