@openwop/cli 1.2.3 → 1.3.0
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/README.md +12 -6
- package/dist/cli.js +346 -73
- package/dist/openwop.js +347 -74
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -578,7 +578,7 @@ function errText(err) {
|
|
|
578
578
|
// package.json
|
|
579
579
|
var package_default = {
|
|
580
580
|
name: "@openwop/cli",
|
|
581
|
-
version: "1.
|
|
581
|
+
version: "1.3.0",
|
|
582
582
|
description: "OpenWOP CLI for operating the workflow-engine demo app and OpenWOP-compatible hosts.",
|
|
583
583
|
license: "Apache-2.0",
|
|
584
584
|
type: "module",
|
|
@@ -1153,6 +1153,17 @@ var V2_PATH_TEMPLATES = [
|
|
|
1153
1153
|
"/workflows/{workflowId}"
|
|
1154
1154
|
];
|
|
1155
1155
|
var V2_MANIFEST_OMITTED = ["/.well-known/openwop", "/openapi.json"];
|
|
1156
|
+
var V2_RENAMED = { "/v1/openapi.json": "/openapi.json" };
|
|
1157
|
+
var V2_SEAMS_PROFILE = "openwop-conformance-seams-v2";
|
|
1158
|
+
var V2_SEAM_PREFIXES = {
|
|
1159
|
+
"/v1/host/workspace/files": "/conformance/seams/workspace/files"
|
|
1160
|
+
};
|
|
1161
|
+
function v2SeamTwin(path) {
|
|
1162
|
+
for (const [v1, v2] of Object.entries(V2_SEAM_PREFIXES)) {
|
|
1163
|
+
if (path === v1 || path.startsWith(`${v1}/`) || path.startsWith(`${v1}?`)) return `${v2}${path.slice(v1.length)}`;
|
|
1164
|
+
}
|
|
1165
|
+
return null;
|
|
1166
|
+
}
|
|
1156
1167
|
var V2_MATCHERS = V2_PATH_TEMPLATES.map(
|
|
1157
1168
|
(t) => new RegExp(`^${t.replace(/[.:]/g, "\\$&").replace(/\{[A-Za-z]+\}/g, "[^/]+")}$`)
|
|
1158
1169
|
);
|
|
@@ -1161,6 +1172,7 @@ function v2Twin(path) {
|
|
|
1161
1172
|
const bare = q === -1 ? path : path.slice(0, q);
|
|
1162
1173
|
const query = q === -1 ? "" : path.slice(q);
|
|
1163
1174
|
if (!bare.startsWith("/v1/")) return null;
|
|
1175
|
+
if (V2_RENAMED[bare] !== void 0) return `${V2_RENAMED[bare]}${query}`;
|
|
1164
1176
|
const unversioned = bare.slice("/v1".length);
|
|
1165
1177
|
return V2_MATCHERS.some((m) => m.test(unversioned)) ? `${unversioned}${query}` : null;
|
|
1166
1178
|
}
|
|
@@ -1204,6 +1216,7 @@ async function negotiateMajor(ctx) {
|
|
|
1204
1216
|
}
|
|
1205
1217
|
let major = 1;
|
|
1206
1218
|
let roots = {};
|
|
1219
|
+
let seams = false;
|
|
1207
1220
|
try {
|
|
1208
1221
|
const url = new URL(".well-known/openwop", ctx.baseUrl.endsWith("/") ? ctx.baseUrl : `${ctx.baseUrl}/`);
|
|
1209
1222
|
const res = await ctx.fetchImpl(url, { method: "GET", headers: { accept: "application/json", "openwop-version": "2" } });
|
|
@@ -1212,7 +1225,10 @@ async function negotiateMajor(ctx) {
|
|
|
1212
1225
|
ctx.discovery = { doc, servedVersion: res.headers?.get?.("openwop-version") ?? void 0 };
|
|
1213
1226
|
ctx.discoveredStreamBase = streamBaseFrom(doc?.extensions, ctx.baseUrl);
|
|
1214
1227
|
major = selectMajor(doc?.protocolVersions);
|
|
1215
|
-
if (major === 2)
|
|
1228
|
+
if (major === 2) {
|
|
1229
|
+
roots = hostRootsFrom(doc?.extensions);
|
|
1230
|
+
seams = doc?.conformance?.seamsProfile === V2_SEAMS_PROFILE;
|
|
1231
|
+
}
|
|
1216
1232
|
} else if (res.status === 406) {
|
|
1217
1233
|
major = selectMajor(doc?.details?.protocolVersions);
|
|
1218
1234
|
}
|
|
@@ -1220,6 +1236,7 @@ async function negotiateMajor(ctx) {
|
|
|
1220
1236
|
}
|
|
1221
1237
|
ctx.protocolMajor = major;
|
|
1222
1238
|
ctx.hostRoots = roots;
|
|
1239
|
+
ctx.seamsV2 = seams;
|
|
1223
1240
|
return major;
|
|
1224
1241
|
}
|
|
1225
1242
|
async function resolveRequest(ctx, path, headers) {
|
|
@@ -1227,6 +1244,8 @@ async function resolveRequest(ctx, path, headers) {
|
|
|
1227
1244
|
if (major !== 2) return { path, headers };
|
|
1228
1245
|
const twin = v2Twin(path);
|
|
1229
1246
|
if (twin !== null) return { path: projectRunIdsInPath(twin), headers: { ...headers, "openwop-version": "2.0" } };
|
|
1247
|
+
const seam = ctx.seamsV2 ? v2SeamTwin(path) : null;
|
|
1248
|
+
if (seam !== null) return { path: seam, headers: { ...headers, "openwop-version": "2.0" } };
|
|
1230
1249
|
const rooted = hostRootFor(path, ctx.hostRoots ?? {});
|
|
1231
1250
|
if (rooted !== null) return { path: rooted, headers };
|
|
1232
1251
|
return { path, headers };
|
|
@@ -4389,6 +4408,10 @@ registry (\`openwop packs\` operates packs.openwop.dev):
|
|
|
4389
4408
|
get GET /v1/packs/{packName} \u2014 one pack's node types (reverse-DNS name)
|
|
4390
4409
|
export GET /v1/packs/export \u2014 the installed agent manifests (RFC 0003
|
|
4391
4410
|
round-trip), re-installable elsewhere; --out writes them to a file.
|
|
4411
|
+
These four are v1 operations: protocol v2 names no installed-packs operation
|
|
4412
|
+
(spec/v2/path-manifest.json, api/v2/openapi.yaml), so they are sent as the v1
|
|
4413
|
+
paths whichever major the host speaks. To read the published catalog, use
|
|
4414
|
+
\`openwop packs\`, which resolves the registry's v2 tree.
|
|
4392
4415
|
|
|
4393
4416
|
\`tools\` reads the portable tool catalog (RFC 0078 \xA7B) \u2014 GET /v1/tools, or
|
|
4394
4417
|
GET /v1/tools/<toolId> for one descriptor. This is the tools an agent/workflow
|
|
@@ -11851,8 +11874,12 @@ ${routesHelp("workspace", WORKSPACE_ROUTES)}
|
|
|
11851
11874
|
|
|
11852
11875
|
Agent workspace (RFC 0059 \xA7C). A tenant-scoped file area an agent reads and
|
|
11853
11876
|
writes during a run, with optimistic concurrency via ETag / If-Match (WCT-1
|
|
11854
|
-
cross-owner isolation is enforced host-side). Drives
|
|
11855
|
-
|
|
11877
|
+
cross-owner isolation is enforced host-side). Drives GET/PUT/DELETE
|
|
11878
|
+
/v1/host/workspace/files. v2 names no canonical workspace operation: its only
|
|
11879
|
+
v2 home is the conformance seams profile, so on a host that speaks major 2 AND
|
|
11880
|
+
advertises conformance.seamsProfile "openwop-conformance-seams-v2" the same
|
|
11881
|
+
calls go to /conformance/seams/workspace/files (OpenWOP-Version: 2.0);
|
|
11882
|
+
otherwise the v1 path is used. (The /v1/host/openwop-app/workspace/op
|
|
11856
11883
|
cross-owner seam is a conformance-only test seam \u2014 reachable here as
|
|
11857
11884
|
\`workspace op\`, and only on a host started with OPENWOP_TEST_SEAM_ENABLED=true.)
|
|
11858
11885
|
|
|
@@ -14972,30 +14999,49 @@ import { existsSync as existsSync6, readFileSync as readFileSync15, writeFileSyn
|
|
|
14972
14999
|
import { join as join6, resolve as resolvePath8 } from "node:path";
|
|
14973
15000
|
import { homedir as homedir3 } from "node:os";
|
|
14974
15001
|
var PACKS_HELP = `Usage:
|
|
14975
|
-
openwop packs search [query] [--registry-url url] [--limit n] [--json]
|
|
14976
|
-
openwop packs info <name> [--version v] [--registry-url url] [--json]
|
|
14977
|
-
openwop packs install <name>[@version] [--version v] [--dir path] [--no-verify] [--registry-url url] [--json]
|
|
14978
|
-
openwop packs publish <dir> [--key ed25519.pem] [--key-id id] [--out dir] [--json]
|
|
14979
|
-
openwop packs yank <name>[@version] [--version v] [--undo] [--json]
|
|
15002
|
+
openwop packs search [query] [--registry-url url] [--tree v1|v2] [--limit n] [--json]
|
|
15003
|
+
openwop packs info <name> [--version v] [--registry-url url] [--tree v1|v2] [--json]
|
|
15004
|
+
openwop packs install <name>[@version|@range] [--version v] [--dir path] [--no-verify] [--registry-url url] [--tree v1|v2] [--json]
|
|
15005
|
+
openwop packs publish <dir> [--key ed25519.pem] [--key-id id] [--out dir] [--tree v1|v2] [--json]
|
|
15006
|
+
openwop packs yank <name>[@version] [--version v] [--undo] [--tree v1|v2] [--json]
|
|
14980
15007
|
|
|
14981
15008
|
Operates the signed node-pack registry (default: https://packs.openwop.dev,
|
|
14982
15009
|
override with --registry-url or OPENWOP_REGISTRY_URL). The registry is a
|
|
14983
15010
|
separate surface from the host --base-url.
|
|
14984
15011
|
|
|
14985
|
-
|
|
14986
|
-
|
|
14987
|
-
|
|
14988
|
-
|
|
14989
|
-
|
|
14990
|
-
|
|
15012
|
+
Every registry path is resolved through the registry's
|
|
15013
|
+
/.well-known/openwop-registry.json \`endpoints\` map (spec/v2/core/packs.md
|
|
15014
|
+
\xA7"The registry tree"), preferring the v2 tree (\`endpoints.v2\`). The v1 tree
|
|
15015
|
+
is used only when the registry names no v2 tree (or publishes no discovery
|
|
15016
|
+
document), or when you pass --tree v1.
|
|
15017
|
+
|
|
15018
|
+
search Reads the registry index (endpoints.registryIndex, /v2/index.json on
|
|
15019
|
+
packs.openwop.dev) and filters the catalog client-side.
|
|
15020
|
+
info Reads the pack index (endpoints.packMetadata) + the version manifest
|
|
15021
|
+
with --version.
|
|
15022
|
+
install Resolves the version, downloads the tarball, checks its sha256
|
|
15023
|
+
integrity against the manifest, and verifies the signature (skip
|
|
15024
|
+
with --no-verify). v2: the detached Ed25519 .sig over the in-tarball
|
|
15025
|
+
pack.json (RFC 8785 canonical JSON), under the registry key named by
|
|
15026
|
+
signing.keyId, whose permittedNamespaces must cover the pack name.
|
|
15027
|
+
Places the tarball + manifest under ~/.openwop/packs/{name}/{version}/
|
|
14991
15028
|
(override with --dir).
|
|
15029
|
+
Version: none = the pack's latest unyanked version; a range (^1.2,
|
|
15030
|
+
~1.2.0, 1.x, >=1.0.0 <2.0.0) = the highest matching unyanked version;
|
|
15031
|
+
an exact version is a pin and MAY install a yanked version (with a
|
|
15032
|
+
warning). A deprecated version installs with a warning.
|
|
14992
15033
|
publish The reference registry has NO write API (publish is PR-based). This
|
|
14993
15034
|
packages + Ed25519-signs a local pack dir into a signed tarball +
|
|
14994
|
-
sidecars
|
|
14995
|
-
|
|
15035
|
+
sidecars, ready to commit + open a PR against openwop-registry. The
|
|
15036
|
+
default (v2) signing block is { keyId, scheme: "ed25519-canonical-json" };
|
|
15037
|
+
--tree v1 writes the legacy v1 block. Private key: --key, else
|
|
15038
|
+
~/.openwop-keys/{keyId}.private.pem.
|
|
14996
15039
|
yank Edits a local registry checkout \u2014 flips "yanked": true in the version
|
|
14997
|
-
manifest (--
|
|
15040
|
+
manifest under registry/v2/ (--tree v1 for the frozen v1 tree;
|
|
15041
|
+
--undo reverses). The published change lands via PR + a
|
|
14998
15042
|
build-index.mjs rerun. Run from inside the repo.
|
|
15043
|
+
|
|
15044
|
+
Exit codes: 0 ok \xB7 1 verification / integrity / resolution failure \xB7 2 usage.
|
|
14999
15045
|
`;
|
|
15000
15046
|
async function runPacks(ctx, argv) {
|
|
15001
15047
|
const sub = argv[0];
|
|
@@ -15023,6 +15069,71 @@ Run \`openwop packs --help\` for usage.`);
|
|
|
15023
15069
|
function registryUrlFor(options, env) {
|
|
15024
15070
|
return normalizeBaseUrl(options.registryUrl ?? env.OPENWOP_REGISTRY_URL ?? DEFAULT_REGISTRY_URL);
|
|
15025
15071
|
}
|
|
15072
|
+
var REQUIRED_ENDPOINTS = ["registryIndex", "packMetadata", "versionManifest", "versionTarball", "versionSignature"];
|
|
15073
|
+
var LEGACY_V1_ENDPOINTS = {
|
|
15074
|
+
registryIndex: "/v1/index.json",
|
|
15075
|
+
packMetadata: "/v1/packs/{name}/index.json",
|
|
15076
|
+
versionManifest: "/v1/packs/{name}/-/{version}.json",
|
|
15077
|
+
versionTarball: "/v1/packs/{name}/-/{version}.tgz",
|
|
15078
|
+
versionSignature: "/v1/packs/{name}/-/{version}.sig",
|
|
15079
|
+
publicKey: "/keys/{keyId}.pub"
|
|
15080
|
+
};
|
|
15081
|
+
function treeOption(options) {
|
|
15082
|
+
if (options.tree === void 0) return void 0;
|
|
15083
|
+
if (options.tree !== "v1" && options.tree !== "v2") throw new CliError(`--tree must be v1 or v2 (got "${options.tree}").`);
|
|
15084
|
+
return options.tree;
|
|
15085
|
+
}
|
|
15086
|
+
function stringTemplates(value) {
|
|
15087
|
+
const out = {};
|
|
15088
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return out;
|
|
15089
|
+
for (const [k, v] of Object.entries(value)) if (typeof v === "string" && !k.startsWith("_")) out[k] = v;
|
|
15090
|
+
return out;
|
|
15091
|
+
}
|
|
15092
|
+
async function resolveRegistry(ctx, options) {
|
|
15093
|
+
const url = registryUrlFor(options, ctx.env);
|
|
15094
|
+
const forced = treeOption(options);
|
|
15095
|
+
let doc = null;
|
|
15096
|
+
try {
|
|
15097
|
+
doc = await registryJson(ctx, url, "/.well-known/openwop-registry.json");
|
|
15098
|
+
} catch (err) {
|
|
15099
|
+
if (!(err instanceof HttpError) || err.status !== 404) throw err;
|
|
15100
|
+
}
|
|
15101
|
+
const endpoints = doc && typeof doc.endpoints === "object" ? doc.endpoints : null;
|
|
15102
|
+
if (!endpoints) {
|
|
15103
|
+
if (forced === "v2") throw new CliError(`${url} publishes no /.well-known/openwop-registry.json endpoints map, so no v2 tree can be resolved.`, 1);
|
|
15104
|
+
return { url, tree: "v1", endpoints: { ...LEGACY_V1_ENDPOINTS }, signingKeys: null, source: "legacy-v1" };
|
|
15105
|
+
}
|
|
15106
|
+
const flat = stringTemplates(endpoints);
|
|
15107
|
+
const publicKey = flat.publicKey ?? LEGACY_V1_ENDPOINTS.publicKey;
|
|
15108
|
+
const v2 = stringTemplates(endpoints.v2);
|
|
15109
|
+
const hasV2 = REQUIRED_ENDPOINTS.every((k) => typeof v2[k] === "string");
|
|
15110
|
+
let tree;
|
|
15111
|
+
let map;
|
|
15112
|
+
let source;
|
|
15113
|
+
if (forced === "v2" || forced === void 0 && hasV2) {
|
|
15114
|
+
if (!hasV2) throw new CliError(`${url} names no v2 tree in its discovery document (endpoints.v2); retry with --tree v1.`, 1);
|
|
15115
|
+
tree = "v2";
|
|
15116
|
+
map = v2;
|
|
15117
|
+
source = "endpoints.v2";
|
|
15118
|
+
} else {
|
|
15119
|
+
const v1 = stringTemplates(endpoints.v1);
|
|
15120
|
+
tree = "v1";
|
|
15121
|
+
map = { ...flat, ...v1 };
|
|
15122
|
+
source = "endpoints.v1";
|
|
15123
|
+
}
|
|
15124
|
+
for (const k of REQUIRED_ENDPOINTS) {
|
|
15125
|
+
if (typeof map[k] !== "string") throw new CliError(`${url}'s discovery document names no ${tree} "${k}" endpoint.`, 1);
|
|
15126
|
+
}
|
|
15127
|
+
return { url, tree, endpoints: { ...map, publicKey }, signingKeys: Array.isArray(doc.signingKeys) ? doc.signingKeys : null, source };
|
|
15128
|
+
}
|
|
15129
|
+
function registryPath(view, key, params = {}) {
|
|
15130
|
+
const template = view.endpoints[key];
|
|
15131
|
+
if (typeof template !== "string") throw new CliError(`${view.url}'s discovery document names no "${key}" endpoint.`, 1);
|
|
15132
|
+
return template.replace(/\{([A-Za-z]+)\}/g, (_m, p) => {
|
|
15133
|
+
if (params[p] === void 0) throw new CliError(`Registry endpoint ${key} needs {${p}}.`, 1);
|
|
15134
|
+
return encodeURIComponent(params[p]);
|
|
15135
|
+
});
|
|
15136
|
+
}
|
|
15026
15137
|
async function registryJson(ctx, registryUrl, path) {
|
|
15027
15138
|
const url = new URL(path, registryUrl + "/");
|
|
15028
15139
|
const res = await ctx.fetchImpl(url, { method: "GET", headers: { accept: "application/json" } });
|
|
@@ -15043,15 +15154,15 @@ async function registryBytes(ctx, registryUrl, path) {
|
|
|
15043
15154
|
async function runPacksSearch(ctx, argv) {
|
|
15044
15155
|
const { options, positionals } = parseOptions(argv, {
|
|
15045
15156
|
bool: ["--help"],
|
|
15046
|
-
value: ["--registry-url", "--limit"]
|
|
15157
|
+
value: ["--registry-url", "--limit", "--tree"]
|
|
15047
15158
|
});
|
|
15048
15159
|
if (options.help) {
|
|
15049
15160
|
write(ctx.io.stdout, PACKS_HELP);
|
|
15050
15161
|
return 0;
|
|
15051
15162
|
}
|
|
15052
15163
|
const query = String(positionals[0] ?? "").toLowerCase();
|
|
15053
|
-
const
|
|
15054
|
-
const index = await registryJson(ctx,
|
|
15164
|
+
const registry = await resolveRegistry(ctx, options);
|
|
15165
|
+
const index = await registryJson(ctx, registry.url, registryPath(registry, "registryIndex"));
|
|
15055
15166
|
const packs = Array.isArray(index?.packs) ? index.packs : [];
|
|
15056
15167
|
const matched = packs.filter((p) => {
|
|
15057
15168
|
if (!query) return true;
|
|
@@ -15060,7 +15171,7 @@ async function runPacksSearch(ctx, argv) {
|
|
|
15060
15171
|
});
|
|
15061
15172
|
const limit = Number(options.limit ?? 30);
|
|
15062
15173
|
if (ctx.json) {
|
|
15063
|
-
writeJson(ctx.io.stdout, { query: positionals[0] ?? "", total: matched.length, packs: matched });
|
|
15174
|
+
writeJson(ctx.io.stdout, { query: positionals[0] ?? "", tree: registry.tree, total: matched.length, packs: matched });
|
|
15064
15175
|
return 0;
|
|
15065
15176
|
}
|
|
15066
15177
|
if (matched.length === 0) {
|
|
@@ -15083,7 +15194,7 @@ async function runPacksSearch(ctx, argv) {
|
|
|
15083
15194
|
async function runPacksInfo(ctx, argv) {
|
|
15084
15195
|
const { options, positionals } = parseOptions(argv, {
|
|
15085
15196
|
bool: ["--help"],
|
|
15086
|
-
value: ["--registry-url", "--version"]
|
|
15197
|
+
value: ["--registry-url", "--version", "--tree"]
|
|
15087
15198
|
});
|
|
15088
15199
|
if (options.help) {
|
|
15089
15200
|
write(ctx.io.stdout, PACKS_HELP);
|
|
@@ -15091,15 +15202,11 @@ async function runPacksInfo(ctx, argv) {
|
|
|
15091
15202
|
}
|
|
15092
15203
|
const name = positionals[0];
|
|
15093
15204
|
if (!name) throw new CliError("packs info requires a pack name.\nUsage: openwop packs info <name> [--version v]");
|
|
15094
|
-
const
|
|
15095
|
-
const pack = await
|
|
15205
|
+
const registry = await resolveRegistry(ctx, options);
|
|
15206
|
+
const pack = await packMetadata(ctx, registry, name);
|
|
15096
15207
|
let versionManifest = null;
|
|
15097
15208
|
if (options.version) {
|
|
15098
|
-
versionManifest = await registryJson(
|
|
15099
|
-
ctx,
|
|
15100
|
-
registryUrl,
|
|
15101
|
-
`/v1/packs/${encodeURIComponent(name)}/-/${encodeURIComponent(options.version)}.json`
|
|
15102
|
-
);
|
|
15209
|
+
versionManifest = await registryJson(ctx, registry.url, registryPath(registry, "versionManifest", { name, version: options.version }));
|
|
15103
15210
|
}
|
|
15104
15211
|
if (ctx.json) {
|
|
15105
15212
|
writeJson(ctx.io.stdout, versionManifest ? { ...pack, requestedVersion: versionManifest } : pack);
|
|
@@ -15111,7 +15218,8 @@ async function runPacksInfo(ctx, argv) {
|
|
|
15111
15218
|
`Latest: ${pack.latest ?? "(none)"}`,
|
|
15112
15219
|
`License: ${pack.license || "\u2014"}`,
|
|
15113
15220
|
`Author: ${pack.author || "\u2014"}`,
|
|
15114
|
-
`Description: ${pack.description || "\u2014"}
|
|
15221
|
+
`Description: ${pack.description || "\u2014"}`,
|
|
15222
|
+
`Tree: ${registry.tree}`
|
|
15115
15223
|
];
|
|
15116
15224
|
if (pack.homepage) lines.push(`Homepage: ${pack.homepage}`);
|
|
15117
15225
|
lines.push("");
|
|
@@ -15121,7 +15229,7 @@ async function runPacksInfo(ctx, argv) {
|
|
|
15121
15229
|
const rows = versions.map((v) => ({
|
|
15122
15230
|
version: v.version,
|
|
15123
15231
|
keyId: v.signingKeyId ?? "",
|
|
15124
|
-
flags: v
|
|
15232
|
+
flags: versionFlags(v),
|
|
15125
15233
|
integrity: typeof v.integrity === "string" ? v.integrity.slice(0, 24) + "\u2026" : ""
|
|
15126
15234
|
}));
|
|
15127
15235
|
writeLine(ctx.io.stdout, formatTable(rows, ["version", "keyId", "flags", "integrity"]));
|
|
@@ -15130,38 +15238,52 @@ async function runPacksInfo(ctx, argv) {
|
|
|
15130
15238
|
}
|
|
15131
15239
|
return 0;
|
|
15132
15240
|
}
|
|
15241
|
+
async function packMetadata(ctx, registry, name) {
|
|
15242
|
+
try {
|
|
15243
|
+
return await registryJson(ctx, registry.url, registryPath(registry, "packMetadata", { name }));
|
|
15244
|
+
} catch (err) {
|
|
15245
|
+
if (err instanceof HttpError && err.status === 404) throw new CliError(`No pack named ${name} on ${registry.url} (${registry.tree} tree). Try \`openwop packs search\`.`, 2);
|
|
15246
|
+
throw err;
|
|
15247
|
+
}
|
|
15248
|
+
}
|
|
15249
|
+
function versionFlags(v) {
|
|
15250
|
+
return v?.yanked ? "yanked" : v?.versionDeprecated || v?.deprecated ? "deprecated" : "";
|
|
15251
|
+
}
|
|
15133
15252
|
async function runPacksInstall(ctx, argv) {
|
|
15134
15253
|
const { options, positionals } = parseOptions(argv, {
|
|
15135
15254
|
bool: ["--help", "--no-verify"],
|
|
15136
|
-
value: ["--registry-url", "--version", "--dir"]
|
|
15255
|
+
value: ["--registry-url", "--version", "--dir", "--tree"]
|
|
15137
15256
|
});
|
|
15138
15257
|
if (options.help) {
|
|
15139
15258
|
write(ctx.io.stdout, PACKS_HELP);
|
|
15140
15259
|
return 0;
|
|
15141
15260
|
}
|
|
15142
15261
|
let name = positionals[0];
|
|
15143
|
-
let
|
|
15262
|
+
let requested = options.version;
|
|
15144
15263
|
if (name && name.includes("@")) {
|
|
15145
15264
|
const at = name.lastIndexOf("@");
|
|
15146
|
-
|
|
15265
|
+
requested = requested ?? name.slice(at + 1);
|
|
15147
15266
|
name = name.slice(0, at);
|
|
15148
15267
|
}
|
|
15149
15268
|
if (!name) throw new CliError("packs install requires a pack name.\nUsage: openwop packs install <name>[@version] [--version v]");
|
|
15150
|
-
const
|
|
15151
|
-
|
|
15152
|
-
|
|
15153
|
-
|
|
15154
|
-
|
|
15269
|
+
const registry = await resolveRegistry(ctx, options);
|
|
15270
|
+
const warnings = [];
|
|
15271
|
+
let version;
|
|
15272
|
+
if (requested && isExactVersion(requested)) {
|
|
15273
|
+
version = requested;
|
|
15274
|
+
} else {
|
|
15275
|
+
const pack = await packMetadata(ctx, registry, name);
|
|
15276
|
+
version = resolveVersion(name, pack, requested);
|
|
15155
15277
|
}
|
|
15156
|
-
const manifest = await registryJson(
|
|
15157
|
-
ctx,
|
|
15158
|
-
registryUrl,
|
|
15159
|
-
`/v1/packs/${encodeURIComponent(name)}/-/${encodeURIComponent(version)}.json`
|
|
15160
|
-
);
|
|
15278
|
+
const manifest = await registryJson(ctx, registry.url, registryPath(registry, "versionManifest", { name, version }));
|
|
15161
15279
|
if (manifest?.yanked) {
|
|
15162
|
-
|
|
15280
|
+
warnings.push(`${name}@${version} is yanked${manifest.yankedReason ? ` (${manifest.yankedReason})` : ""}; installing it because it was pinned exactly.`);
|
|
15163
15281
|
}
|
|
15164
|
-
|
|
15282
|
+
if (manifest?.versionDeprecated || manifest?.deprecated) {
|
|
15283
|
+
const reason = manifest.deprecationReason ?? (typeof manifest.deprecated === "string" ? manifest.deprecated : "");
|
|
15284
|
+
warnings.push(`${name}@${version} is deprecated${reason ? ` (${reason})` : ""}${manifest.supersededBy ? `; superseded by ${manifest.supersededBy}` : ""}.`);
|
|
15285
|
+
}
|
|
15286
|
+
const tgz = await registryBytes(ctx, registry.url, registryPath(registry, "versionTarball", { name, version }));
|
|
15165
15287
|
const integrity = "sha256-" + createHash("sha256").update(tgz).digest("base64");
|
|
15166
15288
|
if (manifest?.integrity && manifest.integrity !== integrity) {
|
|
15167
15289
|
throw new CliError(
|
|
@@ -15171,19 +15293,7 @@ async function runPacksInstall(ctx, argv) {
|
|
|
15171
15293
|
}
|
|
15172
15294
|
let verifyResult = "skipped";
|
|
15173
15295
|
if (!options.noVerify) {
|
|
15174
|
-
|
|
15175
|
-
if (!keyId) throw new CliError(`${name}@${version} manifest has no signing key reference; re-run with --no-verify to bypass.`, 1);
|
|
15176
|
-
const sig = await registryBytes(ctx, registryUrl, `/v1/packs/${encodeURIComponent(name)}/-/${encodeURIComponent(version)}.sig`);
|
|
15177
|
-
if (sig.length !== 64) throw new CliError(`Signature for ${name}@${version} is ${sig.length} bytes; expected 64 for Ed25519.`, 1);
|
|
15178
|
-
const pubPem = (await registryBytes(ctx, registryUrl, `/keys/${encodeURIComponent(keyId)}.pub`)).toString("utf8");
|
|
15179
|
-
const publicKey = createPublicKey(pubPem);
|
|
15180
|
-
const method = manifest?.signing?.method ?? "ed25519";
|
|
15181
|
-
const signedBytes = method === "manual" ? extractPackJsonBytes(tgz) : tgz;
|
|
15182
|
-
const valid = ed25519Verify(null, signedBytes, publicKey, sig);
|
|
15183
|
-
if (!valid) {
|
|
15184
|
-
throw new CliError(`Signature verification FAILED for ${name}@${version} (keyId=${keyId}, method=${method}).`, 1);
|
|
15185
|
-
}
|
|
15186
|
-
verifyResult = `verified (keyId=${keyId}, method=${method})`;
|
|
15296
|
+
verifyResult = registry.tree === "v2" ? await verifyV2(ctx, registry, name, version, manifest, tgz) : await verifyV1(ctx, registry, name, version, manifest, tgz);
|
|
15187
15297
|
}
|
|
15188
15298
|
const baseDir = options.dir ? resolvePath8(ctx.cwd, options.dir) : join6(configHomeDir(ctx.env), "packs");
|
|
15189
15299
|
const destDir = join6(baseDir, name, version);
|
|
@@ -15192,27 +15302,182 @@ async function runPacksInstall(ctx, argv) {
|
|
|
15192
15302
|
const manifestPath = join6(destDir, `${version}.json`);
|
|
15193
15303
|
writeFileSync10(tgzPath, tgz);
|
|
15194
15304
|
writeFileSync10(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf8");
|
|
15305
|
+
for (const w of warnings) writeLine(ctx.io.stderr, `warning: ${w}`);
|
|
15195
15306
|
if (ctx.json) {
|
|
15196
15307
|
writeJson(ctx.io.stdout, {
|
|
15197
15308
|
name,
|
|
15198
15309
|
version,
|
|
15310
|
+
tree: registry.tree,
|
|
15199
15311
|
integrity,
|
|
15200
15312
|
signature: verifyResult,
|
|
15313
|
+
yanked: Boolean(manifest?.yanked),
|
|
15314
|
+
deprecated: Boolean(manifest?.versionDeprecated || manifest?.deprecated),
|
|
15201
15315
|
tarball: tgzPath,
|
|
15202
15316
|
manifest: manifestPath
|
|
15203
15317
|
});
|
|
15204
15318
|
return 0;
|
|
15205
15319
|
}
|
|
15206
|
-
writeLine(ctx.io.stdout, `Installed ${name}@${version}`);
|
|
15320
|
+
writeLine(ctx.io.stdout, `Installed ${name}@${version} (${registry.tree} tree)`);
|
|
15207
15321
|
writeLine(ctx.io.stdout, ` signature: ${verifyResult}`);
|
|
15208
15322
|
writeLine(ctx.io.stdout, ` integrity: ${integrity}`);
|
|
15209
15323
|
writeLine(ctx.io.stdout, ` tarball: ${tgzPath}`);
|
|
15210
15324
|
return 0;
|
|
15211
15325
|
}
|
|
15326
|
+
async function verifyV1(ctx, registry, name, version, manifest, tgz) {
|
|
15327
|
+
const keyId = manifest?.signing?.keyId ?? manifest?.signing?.publicKeyRef;
|
|
15328
|
+
if (!keyId) throw new CliError(`${name}@${version} manifest has no signing key reference; re-run with --no-verify to bypass.`, 1);
|
|
15329
|
+
const sig = await registryBytes(ctx, registry.url, registryPath(registry, "versionSignature", { name, version }));
|
|
15330
|
+
if (sig.length !== 64) throw new CliError(`Signature for ${name}@${version} is ${sig.length} bytes; expected 64 for Ed25519.`, 1);
|
|
15331
|
+
const pubPem = (await registryBytes(ctx, registry.url, registryPath(registry, "publicKey", { keyId }))).toString("utf8");
|
|
15332
|
+
const method = manifest?.signing?.method ?? "ed25519";
|
|
15333
|
+
const signedBytes = method === "manual" ? extractPackJsonBytes(tgz) : tgz;
|
|
15334
|
+
if (!ed25519Verify(null, signedBytes, createPublicKey(pubPem), sig)) {
|
|
15335
|
+
throw new CliError(`Signature verification FAILED for ${name}@${version} (keyId=${keyId}, method=${method}).`, 1);
|
|
15336
|
+
}
|
|
15337
|
+
return `verified (keyId=${keyId}, method=${method})`;
|
|
15338
|
+
}
|
|
15339
|
+
var V2_SCHEME = "ed25519-canonical-json";
|
|
15340
|
+
async function verifyV2(ctx, registry, name, version, manifest, tgz) {
|
|
15341
|
+
const fail3 = (why) => new CliError(`Signature verification FAILED for ${name}@${version} (pack_signature_invalid): ${why}`, 1);
|
|
15342
|
+
const signing = manifest?.signing;
|
|
15343
|
+
if (!signing || typeof signing !== "object") throw fail3("the version manifest has no signing block; re-run with --no-verify to bypass.");
|
|
15344
|
+
const legacy = ["method", "publicKeyRef", "signatureRef"].filter((k) => k in signing);
|
|
15345
|
+
if (legacy.length > 0) throw fail3(`a v2 signing block carries ${legacy.join(", ")} (v1 fields; a v2 block is { keyId, scheme }).`);
|
|
15346
|
+
if (signing.scheme !== V2_SCHEME) throw fail3(`signing.scheme is ${JSON.stringify(signing.scheme)}; the only v2 scheme is ${V2_SCHEME}.`);
|
|
15347
|
+
const keyId = signing.keyId;
|
|
15348
|
+
if (typeof keyId !== "string" || keyId === "") throw fail3("signing.keyId is missing.");
|
|
15349
|
+
if (!registry.signingKeys) throw fail3(`${registry.url} publishes no signingKeys[], so the namespace check cannot run.`);
|
|
15350
|
+
const key = registry.signingKeys.find((k) => k && k.keyId === keyId);
|
|
15351
|
+
if (!key) throw fail3(`keyId ${keyId} is not listed in the registry's signingKeys[].`);
|
|
15352
|
+
const permitted = Array.isArray(key.permittedNamespaces) ? key.permittedNamespaces : [];
|
|
15353
|
+
if (!permitted.some((pattern) => namespaceCovers(pattern, name))) {
|
|
15354
|
+
throw fail3(`key ${keyId} is not permitted to sign ${name} (permittedNamespaces: ${permitted.join(", ") || "none"}).`);
|
|
15355
|
+
}
|
|
15356
|
+
const packJson = extractPackJsonBytes(tgz);
|
|
15357
|
+
let embedded;
|
|
15358
|
+
try {
|
|
15359
|
+
embedded = JSON.parse(packJson.toString("utf8"));
|
|
15360
|
+
} catch {
|
|
15361
|
+
throw fail3("the in-tarball pack.json is not JSON.");
|
|
15362
|
+
}
|
|
15363
|
+
if (embedded?.name !== name || embedded?.version !== version) {
|
|
15364
|
+
throw fail3(`the in-tarball pack.json is ${embedded?.name}@${embedded?.version}, not ${name}@${version}.`);
|
|
15365
|
+
}
|
|
15366
|
+
if (embedded?.signing?.keyId !== void 0 && embedded.signing.keyId !== keyId) {
|
|
15367
|
+
throw fail3(`the in-tarball pack.json names signer ${embedded.signing.keyId}, the version manifest ${keyId}.`);
|
|
15368
|
+
}
|
|
15369
|
+
if (canonicalJsonStringify(embedded) !== packJson.toString("utf8")) {
|
|
15370
|
+
throw fail3("the in-tarball pack.json is not in canonical (RFC 8785) form, so its bytes are not what ed25519-canonical-json signs.");
|
|
15371
|
+
}
|
|
15372
|
+
const sig = await registryBytes(ctx, registry.url, registryPath(registry, "versionSignature", { name, version }));
|
|
15373
|
+
if (sig.length !== 64) throw fail3(`the signature is ${sig.length} bytes; expected 64 for Ed25519.`);
|
|
15374
|
+
const keyPath = typeof key.publicKeyUrl === "string" && key.publicKeyUrl !== "" ? key.publicKeyUrl : registryPath(registry, "publicKey", { keyId });
|
|
15375
|
+
const pubPem = (await registryBytes(ctx, registry.url, keyPath)).toString("utf8");
|
|
15376
|
+
if (!ed25519Verify(null, packJson, createPublicKey(pubPem), sig)) throw fail3(`the signature does not verify under ${keyId}.`);
|
|
15377
|
+
const status = typeof key.status === "string" ? key.status : "active";
|
|
15378
|
+
return `verified (keyId=${keyId}, scheme=${V2_SCHEME}${status !== "active" ? `, key status ${status}` : ""})`;
|
|
15379
|
+
}
|
|
15380
|
+
function namespaceCovers(pattern, name) {
|
|
15381
|
+
if (typeof pattern !== "string") return false;
|
|
15382
|
+
if (pattern.endsWith(".*")) return name.startsWith(pattern.slice(0, -1));
|
|
15383
|
+
return name === pattern;
|
|
15384
|
+
}
|
|
15385
|
+
var SEMVER_RE = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
15386
|
+
function isExactVersion(v) {
|
|
15387
|
+
return SEMVER_RE.test(v.trim());
|
|
15388
|
+
}
|
|
15389
|
+
function parseSemver(v) {
|
|
15390
|
+
const m = SEMVER_RE.exec(String(v).trim());
|
|
15391
|
+
if (!m) return null;
|
|
15392
|
+
return { major: Number(m[1]), minor: Number(m[2]), patch: Number(m[3]), pre: m[4] ? m[4].split(".") : [] };
|
|
15393
|
+
}
|
|
15394
|
+
function compareSemver(a, b) {
|
|
15395
|
+
const x = parseSemver(a);
|
|
15396
|
+
const y = parseSemver(b);
|
|
15397
|
+
if (!x || !y) return String(a).localeCompare(String(b));
|
|
15398
|
+
for (const k of ["major", "minor", "patch"]) if (x[k] !== y[k]) return x[k] - y[k];
|
|
15399
|
+
if (x.pre.length === 0 || y.pre.length === 0) return y.pre.length - x.pre.length;
|
|
15400
|
+
for (let i = 0; i < Math.max(x.pre.length, y.pre.length); i++) {
|
|
15401
|
+
const p = x.pre[i];
|
|
15402
|
+
const q = y.pre[i];
|
|
15403
|
+
if (p === void 0) return -1;
|
|
15404
|
+
if (q === void 0) return 1;
|
|
15405
|
+
const pn = /^\d+$/.test(p);
|
|
15406
|
+
const qn = /^\d+$/.test(q);
|
|
15407
|
+
if (pn && qn && Number(p) !== Number(q)) return Number(p) - Number(q);
|
|
15408
|
+
if (pn !== qn) return pn ? -1 : 1;
|
|
15409
|
+
if (p !== q) return p < q ? -1 : 1;
|
|
15410
|
+
}
|
|
15411
|
+
return 0;
|
|
15412
|
+
}
|
|
15413
|
+
function comparator(raw) {
|
|
15414
|
+
const m = /^(\^|~|>=|<=|>|<|=)?\s*v?(\d+|[xX*])(?:\.(\d+|[xX*]))?(?:\.(\d+|[xX*]))?(-[0-9A-Za-z.-]+)?$/.exec(raw);
|
|
15415
|
+
if (!m) throw new CliError(`Unsupported version range "${raw}". Use an exact version, ^x.y.z, ~x.y.z, x.y.x, or comparators like ">=1.0.0 <2.0.0".`);
|
|
15416
|
+
const op = m[1] ?? "";
|
|
15417
|
+
const wild = (p) => p === void 0 || /^[xX*]$/.test(p);
|
|
15418
|
+
const [maj, min, pat] = [m[2], m[3], m[4]];
|
|
15419
|
+
const n = (p) => wild(p) ? 0 : Number(p);
|
|
15420
|
+
const floor = `${n(maj)}.${n(min)}.${n(pat)}${m[5] ?? ""}`;
|
|
15421
|
+
const cmp = (v) => compareSemver(v, floor);
|
|
15422
|
+
let ceil = null;
|
|
15423
|
+
if (op === "^") {
|
|
15424
|
+
ceil = n(maj) > 0 || wild(min) ? `${n(maj) + 1}.0.0` : n(min) > 0 || wild(pat) ? `0.${n(min) + 1}.0` : `0.0.${n(pat) + 1}`;
|
|
15425
|
+
} else if (op === "~") {
|
|
15426
|
+
ceil = wild(min) ? `${n(maj) + 1}.0.0` : `${n(maj)}.${n(min) + 1}.0`;
|
|
15427
|
+
} else if (op === "" || op === "=") {
|
|
15428
|
+
if (wild(maj)) return () => true;
|
|
15429
|
+
if (wild(min)) ceil = `${n(maj) + 1}.0.0`;
|
|
15430
|
+
else if (wild(pat)) ceil = `${n(maj)}.${n(min) + 1}.0`;
|
|
15431
|
+
else return (v) => cmp(v) === 0;
|
|
15432
|
+
}
|
|
15433
|
+
if (ceil !== null) {
|
|
15434
|
+
const c = ceil;
|
|
15435
|
+
return (v) => cmp(v) >= 0 && compareSemver(v, `${c}-0`) < 0;
|
|
15436
|
+
}
|
|
15437
|
+
switch (op) {
|
|
15438
|
+
case ">=":
|
|
15439
|
+
return (v) => cmp(v) >= 0;
|
|
15440
|
+
case ">":
|
|
15441
|
+
return (v) => cmp(v) > 0;
|
|
15442
|
+
case "<=":
|
|
15443
|
+
return (v) => cmp(v) <= 0;
|
|
15444
|
+
default:
|
|
15445
|
+
return (v) => cmp(v) < 0;
|
|
15446
|
+
}
|
|
15447
|
+
}
|
|
15448
|
+
function satisfiesRange(version, range) {
|
|
15449
|
+
const parsed = parseSemver(version);
|
|
15450
|
+
if (!parsed) return false;
|
|
15451
|
+
return range.split("||").some((set) => {
|
|
15452
|
+
const parts = set.trim().replace(/(>=|<=|>|<|=|\^|~)\s+/g, "$1").split(/\s+/).filter(Boolean);
|
|
15453
|
+
if (parts.length === 0) return true;
|
|
15454
|
+
if (parsed.pre.length > 0 && !parts.some((p) => p.includes("-"))) return false;
|
|
15455
|
+
return parts.every((p) => comparator(p)(version));
|
|
15456
|
+
});
|
|
15457
|
+
}
|
|
15458
|
+
function resolveVersion(name, pack, range) {
|
|
15459
|
+
const versions = Array.isArray(pack?.versions) ? pack.versions : [];
|
|
15460
|
+
const unyanked = versions.filter((v) => v && typeof v.version === "string" && !v.yanked).map((v) => v.version);
|
|
15461
|
+
if (!range || range === "latest") {
|
|
15462
|
+
const latest = typeof pack?.latest === "string" ? pack.latest : void 0;
|
|
15463
|
+
const latestEntry = versions.find((v) => v?.version === latest);
|
|
15464
|
+
if (latest && versions.length === 0) return latest;
|
|
15465
|
+
if (latest && latestEntry && !latestEntry.yanked) return latest;
|
|
15466
|
+
const best = unyanked.filter((v) => (parseSemver(v)?.pre.length ?? 1) === 0).sort(compareSemver).pop() ?? unyanked.sort(compareSemver).pop();
|
|
15467
|
+
if (!best) throw new CliError(`${name} has no installable (unyanked) version; pin an exact version to install a yanked one.`, 1);
|
|
15468
|
+
return best;
|
|
15469
|
+
}
|
|
15470
|
+
const match = unyanked.filter((v) => satisfiesRange(v, range)).sort(compareSemver).pop();
|
|
15471
|
+
if (!match) {
|
|
15472
|
+
const yankedHit = versions.some((v) => v?.yanked && typeof v.version === "string" && satisfiesRange(v.version, range));
|
|
15473
|
+
throw new CliError(`No unyanked version of ${name} satisfies "${range}"${yankedHit ? " (a yanked version does; pin it exactly to install it)" : ""}.`, 1);
|
|
15474
|
+
}
|
|
15475
|
+
return match;
|
|
15476
|
+
}
|
|
15212
15477
|
async function runPacksPublish(ctx, argv) {
|
|
15213
15478
|
const { options, positionals } = parseOptions(argv, {
|
|
15214
15479
|
bool: ["--help"],
|
|
15215
|
-
value: ["--key", "--key-id", "--out"]
|
|
15480
|
+
value: ["--key", "--key-id", "--out", "--tree"]
|
|
15216
15481
|
});
|
|
15217
15482
|
if (options.help) {
|
|
15218
15483
|
write(ctx.io.stdout, PACKS_HELP);
|
|
@@ -15230,9 +15495,10 @@ async function runPacksPublish(ctx, argv) {
|
|
|
15230
15495
|
const version = manifest.version;
|
|
15231
15496
|
if (!name || !version) throw new CliError('pack.json must declare both "name" and "version".');
|
|
15232
15497
|
const keyId = options.keyId ?? "openwop-team-1";
|
|
15498
|
+
const tree = treeOption(options) ?? "v2";
|
|
15233
15499
|
const signedManifest = {
|
|
15234
15500
|
...manifest,
|
|
15235
|
-
signing: { method: "manual", publicKeyRef: keyId, signatureRef: "keys/pack.json.sig" }
|
|
15501
|
+
signing: tree === "v2" ? { keyId, scheme: V2_SCHEME } : { method: "manual", publicKeyRef: keyId, signatureRef: "keys/pack.json.sig" }
|
|
15236
15502
|
};
|
|
15237
15503
|
const canonical = canonicalJsonStringify(signedManifest);
|
|
15238
15504
|
let privateKey;
|
|
@@ -15269,6 +15535,7 @@ async function runPacksPublish(ctx, argv) {
|
|
|
15269
15535
|
name,
|
|
15270
15536
|
version,
|
|
15271
15537
|
keyId,
|
|
15538
|
+
tree,
|
|
15272
15539
|
integrity: `sha256:${sha}`,
|
|
15273
15540
|
tarball: tgzPath,
|
|
15274
15541
|
signature: sigPath,
|
|
@@ -15279,7 +15546,7 @@ async function runPacksPublish(ctx, argv) {
|
|
|
15279
15546
|
});
|
|
15280
15547
|
return 0;
|
|
15281
15548
|
}
|
|
15282
|
-
writeLine(ctx.io.stdout, `Packaged + signed ${name}@${version} (keyId=${keyId})`);
|
|
15549
|
+
writeLine(ctx.io.stdout, `Packaged + signed ${name}@${version} for the ${tree} tree (keyId=${keyId})`);
|
|
15283
15550
|
writeLine(ctx.io.stdout, ` tarball: ${tgzPath}`);
|
|
15284
15551
|
writeLine(ctx.io.stdout, ` signature: ${sigPath}`);
|
|
15285
15552
|
writeLine(ctx.io.stdout, ` manifest: ${manifestOut}`);
|
|
@@ -15291,15 +15558,15 @@ async function runPacksPublish(ctx, argv) {
|
|
|
15291
15558
|
}
|
|
15292
15559
|
writeLine(ctx.io.stdout, "");
|
|
15293
15560
|
writeLine(ctx.io.stdout, "The reference registry has no write API. To publish:");
|
|
15294
|
-
writeLine(ctx.io.stdout, ` 1. Copy the artifacts into registry/
|
|
15295
|
-
writeLine(ctx.io.stdout,
|
|
15561
|
+
writeLine(ctx.io.stdout, ` 1. Copy the artifacts into registry/${tree}/packs/${name}/-/ of an openwop-registry checkout`);
|
|
15562
|
+
writeLine(ctx.io.stdout, ` 2. Run \`node registry/scripts/build-index.mjs --tree ${tree}\` to refresh the index.`);
|
|
15296
15563
|
writeLine(ctx.io.stdout, " 3. Open a pull request (publishMethod: github-pull-request).");
|
|
15297
15564
|
return 0;
|
|
15298
15565
|
}
|
|
15299
15566
|
async function runPacksYank(ctx, argv) {
|
|
15300
15567
|
const { options, positionals } = parseOptions(argv, {
|
|
15301
15568
|
bool: ["--help", "--undo"],
|
|
15302
|
-
value: ["--version"]
|
|
15569
|
+
value: ["--version", "--tree"]
|
|
15303
15570
|
});
|
|
15304
15571
|
if (options.help) {
|
|
15305
15572
|
write(ctx.io.stdout, PACKS_HELP);
|
|
@@ -15316,7 +15583,8 @@ async function runPacksYank(ctx, argv) {
|
|
|
15316
15583
|
throw new CliError("packs yank requires <name>@<version> (or <name> --version v).");
|
|
15317
15584
|
}
|
|
15318
15585
|
const root = requireRepoRoot(ctx);
|
|
15319
|
-
const
|
|
15586
|
+
const tree = treeOption(options) ?? "v2";
|
|
15587
|
+
const manifestPath = join6(root, "registry", tree, "packs", name, "-", `${version}.json`);
|
|
15320
15588
|
if (!existsSync6(manifestPath)) {
|
|
15321
15589
|
throw new CliError(`Version manifest not found: ${manifestPath}
|
|
15322
15590
|
(packs yank edits a local registry checkout; the published change lands via PR.)`);
|
|
@@ -15330,12 +15598,12 @@ async function runPacksYank(ctx, argv) {
|
|
|
15330
15598
|
manifest.yanked = newValue;
|
|
15331
15599
|
writeFileSync10(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf8");
|
|
15332
15600
|
if (ctx.json) {
|
|
15333
|
-
writeJson(ctx.io.stdout, { name, version, yanked: newValue, manifest: manifestPath, publishMethod: "github-pull-request" });
|
|
15601
|
+
writeJson(ctx.io.stdout, { name, version, tree, yanked: newValue, manifest: manifestPath, publishMethod: "github-pull-request" });
|
|
15334
15602
|
return 0;
|
|
15335
15603
|
}
|
|
15336
15604
|
writeLine(ctx.io.stdout, `${newValue ? "Yanked" : "Un-yanked"} ${name}@${version}`);
|
|
15337
15605
|
writeLine(ctx.io.stdout, ` edited: ${manifestPath}`);
|
|
15338
|
-
writeLine(ctx.io.stdout,
|
|
15606
|
+
writeLine(ctx.io.stdout, ` Next: run \`node registry/scripts/build-index.mjs --tree ${tree}\`, commit, and open a PR.`);
|
|
15339
15607
|
return 0;
|
|
15340
15608
|
}
|
|
15341
15609
|
function canonicalJsonStringify(value) {
|
|
@@ -22507,7 +22775,10 @@ pack's main-frame ES module (GET ${BASE18}/trusted/<name>/plugins/<id>/entry.mjs
|
|
|
22507
22775
|
\`rpc\` sends one ui-plugin/1 request (POST ${RPC_PRODUCT}, body { message }); the
|
|
22508
22776
|
message is { openwop: "ui-plugin/1", type: "request", id (integer,
|
|
22509
22777
|
default 1), method, params } unless you pass a
|
|
22510
|
-
whole body with --body/--body-file.
|
|
22778
|
+
whole body with --body/--body-file. --conformance-alias posts to the test-seam
|
|
22779
|
+
alias ${RPC_CONFORMANCE} instead; that seam has no v2 equivalent (the v2
|
|
22780
|
+
seams profile, api/seams-v2.yaml, defines no ui-plugin operation), so it is
|
|
22781
|
+
always sent as the v1 path, on either protocol major.
|
|
22511
22782
|
|
|
22512
22783
|
Exit codes: 0 ok \xB7 2 usage / not found / rejected request \xB7 4 forbidden.
|
|
22513
22784
|
|
|
@@ -27010,8 +27281,10 @@ var OPENAPI_HELP = `Usage:
|
|
|
27010
27281
|
openwop openapi [--out <file>] [--json]
|
|
27011
27282
|
openwop openapi paths [--filter <text>] [--json]
|
|
27012
27283
|
|
|
27013
|
-
Fetch the host's OpenAPI document
|
|
27014
|
-
host
|
|
27284
|
+
Fetch the host's OpenAPI document \u2014 the document the host serves, which lists
|
|
27285
|
+
the operations it implements. A host that speaks protocol major 2 is asked for
|
|
27286
|
+
GET /openapi.json with OpenWOP-Version: 2.0 (its v2 document); a v1-only host
|
|
27287
|
+
for GET /v1/openapi.json.
|
|
27015
27288
|
|
|
27016
27289
|
(default) Prints the document's title, version and operation count; --json
|
|
27017
27290
|
prints the whole document; --out <file> saves it.
|