@helix3/helix-cli 0.1.18-helix3.277 → 0.1.18-helix3.278

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.
@@ -0,0 +1,47 @@
1
+ import type { CliCredentials } from './config';
2
+ export type PackagePin = {
3
+ packageId: string;
4
+ version: string;
5
+ manifestCid: string;
6
+ };
7
+ export type PublishAddOnPackageVersionOptions = {
8
+ reason: string;
9
+ notes?: string;
10
+ expectedCurrentVersionId?: string;
11
+ updateId?: string;
12
+ /** Bounded wait for the uploaded mesh to clear verify/optimize and reach `ready`. */
13
+ assetReadyTimeoutMs?: number;
14
+ onProgress?: (message: string) => void;
15
+ };
16
+ export type PublishAddOnPackageVersionResult = {
17
+ itemId: string;
18
+ revision: number;
19
+ packagePin: PackagePin;
20
+ previousRevision: number;
21
+ previousPackagePin: PackagePin;
22
+ updateId?: string;
23
+ [key: string]: unknown;
24
+ };
25
+ /**
26
+ * Ship a corrected mesh onto an ALREADY-PUBLISHED character add-on, in place:
27
+ * same item id, same owner, same serial, same listings and distributions —
28
+ * every existing owner picks up the new geometry at their next spawn/session
29
+ * start. Two backend steps, in sequence:
30
+ *
31
+ * 1. The ordinary asset door: `POST :id/assets` (presign) → PUT the bytes →
32
+ * `POST :id/assets/:assetId/finalize` — the exact verify/optimize
33
+ * pipeline `helix item publish` runs on a brand-new item. This function
34
+ * polls the asset's status afterward, because finalize only STARTS that
35
+ * pipeline; it does not wait for it.
36
+ * 2. `POST :id/add-on-package/versions` — seals that now-Ready GLB as a new
37
+ * Version of the item's own Continuum Package and moves the item's
38
+ * pointer through the platform's real, audited, revision-checked
39
+ * version-update path (the same one Skylark/Ocean Drive republish
40
+ * through). Refused (409) if the new geometry breaks the compatibility
41
+ * contract (slot removed, contract changed, …) — that is not a bug in
42
+ * this command, it means the change is not a valid update to this item.
43
+ *
44
+ * There is no local dry-run: everything here is a server-verified round trip,
45
+ * same as `helix item publish`.
46
+ */
47
+ export declare function publishAddOnPackageVersion(itemId: string, file: string, options: PublishAddOnPackageVersionOptions, credentials: CliCredentials): Promise<PublishAddOnPackageVersionResult>;
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.publishAddOnPackageVersion = publishAddOnPackageVersion;
4
+ const promises_1 = require("node:fs/promises");
5
+ const node_path_1 = require("node:path");
6
+ const api_1 = require("./api");
7
+ const item_1 = require("./item");
8
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
9
+ const MAX_ADDON_WEB_GLB_BYTES = 64 * 1024 * 1024; // mirrors the backend's own ceiling for this lane
10
+ const DEFAULT_ASSET_READY_TIMEOUT_MS = 180_000;
11
+ const ASSET_POLL_INTERVAL_MS = 2_000;
12
+ /**
13
+ * Ship a corrected mesh onto an ALREADY-PUBLISHED character add-on, in place:
14
+ * same item id, same owner, same serial, same listings and distributions —
15
+ * every existing owner picks up the new geometry at their next spawn/session
16
+ * start. Two backend steps, in sequence:
17
+ *
18
+ * 1. The ordinary asset door: `POST :id/assets` (presign) → PUT the bytes →
19
+ * `POST :id/assets/:assetId/finalize` — the exact verify/optimize
20
+ * pipeline `helix item publish` runs on a brand-new item. This function
21
+ * polls the asset's status afterward, because finalize only STARTS that
22
+ * pipeline; it does not wait for it.
23
+ * 2. `POST :id/add-on-package/versions` — seals that now-Ready GLB as a new
24
+ * Version of the item's own Continuum Package and moves the item's
25
+ * pointer through the platform's real, audited, revision-checked
26
+ * version-update path (the same one Skylark/Ocean Drive republish
27
+ * through). Refused (409) if the new geometry breaks the compatibility
28
+ * contract (slot removed, contract changed, …) — that is not a bug in
29
+ * this command, it means the change is not a valid update to this item.
30
+ *
31
+ * There is no local dry-run: everything here is a server-verified round trip,
32
+ * same as `helix item publish`.
33
+ */
34
+ async function publishAddOnPackageVersion(itemId, file, options, credentials) {
35
+ if (!UUID_RE.test(itemId))
36
+ throw new Error('Item ID must be a UUID.');
37
+ const reason = options.reason?.trim() ?? '';
38
+ if (!reason || reason.length > 500) {
39
+ throw new Error('--reason is required (1-500 characters) and lands in the audit ledger.');
40
+ }
41
+ const bytes = await (0, promises_1.readFile)(file);
42
+ (0, item_1.assertLooksLikeGlb)(bytes, file);
43
+ if (bytes.length > MAX_ADDON_WEB_GLB_BYTES) {
44
+ throw new Error(`"${file}" is ${(bytes.length / (1024 * 1024)).toFixed(1)} MiB; an add-on's web GLB must be at most ${MAX_ADDON_WEB_GLB_BYTES / (1024 * 1024)} MiB.`);
45
+ }
46
+ const progress = options.onProgress ?? (() => { });
47
+ const api = api_1.HelixApi.forCredentials(credentials);
48
+ const filename = (0, node_path_1.basename)(file);
49
+ const mimeType = 'model/gltf-binary';
50
+ progress('Requesting a presigned upload for the corrected mesh…');
51
+ const presign = await api.requestItem('POST', `/api/v1/universal-items/${itemId}/assets`, {
52
+ rendition: 'web',
53
+ filename,
54
+ mimeType,
55
+ sizeBytes: bytes.length,
56
+ });
57
+ if (!presign.uploadUrl)
58
+ throw new Error('Server did not return an upload URL for the web asset.');
59
+ progress('Uploading the mesh…');
60
+ await (0, api_1.uploadToTicket)(presign.uploadUrl, Buffer.from(bytes), mimeType);
61
+ progress('Finalizing the upload (verify → optimize → thumbnail)…');
62
+ await api.requestItem('POST', `/api/v1/universal-items/${itemId}/assets/${presign.id}/finalize`, { sizeBytes: bytes.length });
63
+ progress('Waiting for the pipeline to mark the mesh ready…');
64
+ const ready = await waitForAssetReady(api, itemId, presign.id, options.assetReadyTimeoutMs ?? DEFAULT_ASSET_READY_TIMEOUT_MS);
65
+ if (ready.status !== 'ready') {
66
+ throw new Error(`Upload finished but the pipeline left it "${ready.status}", not "ready" — it will not be sealed into a new Package Version. Check the item's assets for the rejection reason.`);
67
+ }
68
+ progress('Sealing the new Package Version and publishing it to every owner…');
69
+ const body = { reason };
70
+ if (options.notes !== undefined)
71
+ body.notes = options.notes;
72
+ if (options.expectedCurrentVersionId !== undefined)
73
+ body.expectedCurrentVersionId = options.expectedCurrentVersionId;
74
+ if (options.updateId !== undefined)
75
+ body.updateId = options.updateId;
76
+ return api.request('POST', `/api/v1/universal-items/${itemId}/add-on-package/versions`, body);
77
+ }
78
+ async function waitForAssetReady(api, itemId, assetId, timeoutMs) {
79
+ const deadline = Date.now() + timeoutMs;
80
+ for (;;) {
81
+ const assets = await api.request('GET', `/api/v1/universal-items/${itemId}/assets`);
82
+ const row = assets.find((a) => a.id === assetId);
83
+ if (!row)
84
+ throw new Error(`Asset ${assetId} disappeared from ${itemId}'s asset list.`);
85
+ if (row.status === 'ready' || row.status === 'rejected' || row.status === 'failed')
86
+ return row;
87
+ if (Date.now() >= deadline) {
88
+ throw new Error(`Timed out after ${Math.round(timeoutMs / 1000)}s waiting for asset ${assetId} to leave "${row.status}".`);
89
+ }
90
+ await new Promise((resolve) => setTimeout(resolve, ASSET_POLL_INTERVAL_MS));
91
+ }
92
+ }
93
+ //# sourceMappingURL=addOnPackageVersion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"addOnPackageVersion.js","sourceRoot":"","sources":["../src/addOnPackageVersion.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -39,6 +39,7 @@ const commands_1 = require("./sceneHlod/commands");
39
39
  const placementRequest_1 = require("./placementRequest");
40
40
  const bundle_1 = require("./bundle");
41
41
  const avatarMeshReplacement_1 = require("./avatarMeshReplacement");
42
+ const addOnPackageVersion_1 = require("./addOnPackageVersion");
42
43
  const node_child_process_1 = require("node:child_process");
43
44
  const node_fs_1 = require("node:fs");
44
45
  const node_path_1 = require("node:path");
@@ -1207,6 +1208,49 @@ item
1207
1208
  fail(error instanceof lib_1.ApiError ? error.render() : String(error));
1208
1209
  }
1209
1210
  });
1211
+ // Ship a fix to an already-published item WITHOUT churning it onto a new id:
1212
+ // same item, same owner, same serial, same listings/distributions, every
1213
+ // existing owner picks up the new geometry at their next spawn. Character
1214
+ // add-ons only today — the backend's `add-on-package/versions` lane exists
1215
+ // because an add-on's Continuum Package is built FROM the item's own uploaded
1216
+ // mesh, unlike a vehicle or Scene Package, which are authored and sealed
1217
+ // independently and update through the generic (not-yet-CLI-exposed)
1218
+ // `POST :itemId/versions`. `helix item delist` is the OTHER lane here and does
1219
+ // something different: it takes an item off browse/marketplace for good,
1220
+ // rather than shipping owners an update.
1221
+ item
1222
+ .command('publish-version')
1223
+ .description('Ship a corrected mesh onto an already-published character add-on, IN PLACE — new Package Version, same item id/owner/listings; every existing owner gets it next spawn')
1224
+ .argument('<item-id>', 'existing, already-published character add-on item UUID')
1225
+ .argument('<file>', 'corrected mesh (binary glTF, .glb)')
1226
+ .requiredOption('--reason <text>', 'required, lands in the audit ledger (e.g. "fixed poke-through on the left cuff")')
1227
+ .option('--notes <text>', 'optional creator-facing changelog note')
1228
+ .option('--expected-current-version-id <uuid>', 'refuse if the item has moved off this Package Version since you last read it')
1229
+ .option('--update-id <uuid>', 'idempotency key — retrying with the same id never double-publishes')
1230
+ .option('--json', 'machine-readable output')
1231
+ .action(async (itemId, file, opts) => {
1232
+ const creds = requireAuth();
1233
+ try {
1234
+ const result = await (0, addOnPackageVersion_1.publishAddOnPackageVersion)(itemId, (0, node_path_1.resolve)(file), {
1235
+ reason: opts.reason,
1236
+ notes: opts.notes,
1237
+ expectedCurrentVersionId: opts.expectedCurrentVersionId,
1238
+ updateId: opts.updateId,
1239
+ onProgress: (message) => !opts.json && console.log(message),
1240
+ }, creds);
1241
+ if (opts.json) {
1242
+ console.log(JSON.stringify(result, null, 2));
1243
+ return;
1244
+ }
1245
+ console.log(`✔ Published a new Version of ${itemId} — revision ${result.previousRevision} → ${result.revision}`);
1246
+ console.log(` package: ${result.packagePin.packageId}@${result.packagePin.version}`);
1247
+ console.log(` previous: ${result.previousPackagePin.packageId}@${result.previousPackagePin.version}`);
1248
+ console.log(' same item id, same owner(s); existing owners see the change at their next spawn/session start.');
1249
+ }
1250
+ catch (error) {
1251
+ fail(error instanceof lib_1.ApiError ? error.render() : `✖ ${error instanceof Error ? error.message : String(error)}`);
1252
+ }
1253
+ });
1210
1254
  // One command prints the whole Character-Creator vocabulary a wearable must
1211
1255
  // declare — the 34 slot tags AND the two genders. There is no `list-genders`:
1212
1256
  // a dedicated command for a closed two-value list is a command to discover, to