@volter/editor-live 0.5.64 → 0.5.66
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/BUNDLED_NOTICES +4 -2
- package/dist/editor-document.d.ts +2 -0
- package/dist/editor.d.ts +18 -4
- package/dist/index.js +268 -24
- package/dist/index.js.map +4 -4
- package/package.json +3 -3
- package/src/editor-document.ts +2 -0
- package/src/editor.ts +28 -5
- package/src/session.ts +24 -4
package/BUNDLED_NOTICES
CHANGED
|
@@ -3,8 +3,9 @@ Bundled license notices
|
|
|
3
3
|
These notices accompany code incorporated into this package. Separate npm dependencies retain their own licenses.
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
@volter/editor-project@0.5.
|
|
6
|
+
@volter/editor-project@0.5.66
|
|
7
7
|
Declared license: Apache-2.0
|
|
8
|
+
Source: the unmodified npm package @volter/editor-project@0.5.66, from https://github.com/volter-ai/editor.git
|
|
8
9
|
|
|
9
10
|
--- packages/editor-project/LICENSE ---
|
|
10
11
|
|
|
@@ -212,8 +213,9 @@ Declared license: Apache-2.0
|
|
|
212
213
|
limitations under the License.
|
|
213
214
|
|
|
214
215
|
|
|
215
|
-
@volter/editor-sdk@0.5.
|
|
216
|
+
@volter/editor-sdk@0.5.66
|
|
216
217
|
Declared license: Apache-2.0
|
|
218
|
+
Source: the unmodified npm package @volter/editor-sdk@0.5.66, from https://github.com/volter-ai/editor.git
|
|
217
219
|
|
|
218
220
|
--- packages/editor-sdk/LICENSE ---
|
|
219
221
|
|
|
@@ -50,6 +50,8 @@ export interface DocumentKeyOptions extends DocumentGestureOptions {
|
|
|
50
50
|
metaKey?: boolean;
|
|
51
51
|
shiftKey?: boolean;
|
|
52
52
|
altKey?: boolean;
|
|
53
|
+
/** Hold the key this long before releasing it (a person holding W). */
|
|
54
|
+
holdMs?: number;
|
|
53
55
|
}
|
|
54
56
|
export interface DocumentPasteOptions extends DocumentGestureOptions {
|
|
55
57
|
selector?: string;
|
package/dist/editor.d.ts
CHANGED
|
@@ -28,7 +28,7 @@ export declare class LiveEditor {
|
|
|
28
28
|
readonly document: LiveEditorDocument;
|
|
29
29
|
constructor(client: EditorClient);
|
|
30
30
|
/**
|
|
31
|
-
* THE BLENDER LANE'S VERBS, from `volter-editor eval`.
|
|
31
|
+
* THE BLENDER LANE'S VERBS, from `volter-model-editor eval`.
|
|
32
32
|
*
|
|
33
33
|
* Blender runs headless in the editor tab's worker (ARCHITECTURE-CORE, "THE
|
|
34
34
|
* BLENDER IN THE TAB IS BLENDER") and answers `blender-start`,
|
|
@@ -40,7 +40,7 @@ export declare class LiveEditor {
|
|
|
40
40
|
* per question — the same discovery failure `eval-surface.ts`'s header
|
|
41
41
|
* records, in a lane that had not noticed it yet.
|
|
42
42
|
*
|
|
43
|
-
* volter-editor eval "await editor.blender('blender-execute', { code: 'import bpy; print(len(bpy.data.objects))' })"
|
|
43
|
+
* volter-model-editor eval "await editor.blender('blender-execute', { code: 'import bpy; print(len(bpy.data.objects))' })"
|
|
44
44
|
*
|
|
45
45
|
* `blender-status` is the only verb that creates nothing: it answers whether
|
|
46
46
|
* this tab already has a session without starting one.
|
|
@@ -70,6 +70,9 @@ export declare class LiveEditor {
|
|
|
70
70
|
* `@volter/editor-sdk`'s `CaptureDimensions`.
|
|
71
71
|
* Supply a view to present and photograph it in one editor request.
|
|
72
72
|
*/
|
|
73
|
+
/** The active document's RENDER alone, at the size asked for: what the document draws,
|
|
74
|
+
* without what the viewport draws over it (its navigation gizmo, its readouts). To see a
|
|
75
|
+
* stage as the person does, use `captureEditorChrome({ region: 'document' })`. */
|
|
73
76
|
captureActiveDocument(size?: CaptureDimensions, view?: EditorView): Promise<ActiveDocumentCapture>;
|
|
74
77
|
/**
|
|
75
78
|
* Photograph the editor PAGE — every panel, tab strip and viewport as the
|
|
@@ -159,12 +162,21 @@ export declare class LiveEditor {
|
|
|
159
162
|
*/
|
|
160
163
|
workspace(id: EditorWorkspaceName): Promise<void>;
|
|
161
164
|
/**
|
|
162
|
-
* Apply a STYLE BUNDLE by id — the chrome's palette, material
|
|
163
|
-
*
|
|
165
|
+
* Apply a STYLE BUNDLE by id — the chrome's palette, material and icon set in
|
|
166
|
+
* one gesture, the session door beside
|
|
164
167
|
* `View → <Style> Style`. A bundle the open project does not offer refuses
|
|
165
168
|
* and names the vocabulary; `currentView().style` reports the one worn.
|
|
166
169
|
*/
|
|
167
170
|
style(id: string): Promise<void>;
|
|
171
|
+
/**
|
|
172
|
+
* A document stage's viewport PRESENTATION — its draw mode, lighting, backdrop and overlays
|
|
173
|
+
* (`@volter/editor-sdk/kit/viewport-presentation`) — resolved. With `layer`, that choice is
|
|
174
|
+
* recorded for the view first, as a person's toolbar change would be, e.g.
|
|
175
|
+
* `presentation('model:src/models/cube.blend', { all: { lighting: { studioPreset: 'kit' } } })`.
|
|
176
|
+
* With a STRING, the named view of that id (`*.view.ts`, e.g. `'godot'`) is put on the view
|
|
177
|
+
* whole; the answer's `viewPresets` lists the ones registered.
|
|
178
|
+
*/
|
|
179
|
+
presentation(documentId: string, layer?: Parameters<EditorClient['viewportPresentation']>[1]): ReturnType<EditorClient['viewportPresentation']>;
|
|
168
180
|
/**
|
|
169
181
|
* Set the MATERIAL apart from the bundle that usually carries it.
|
|
170
182
|
* Appearance is palette × material, independent axes by ruling, so
|
|
@@ -392,6 +404,8 @@ export declare class LiveEditor {
|
|
|
392
404
|
* queue — the same one the keyboard shortcut drives. */
|
|
393
405
|
undo(): Promise<HistoryStep>;
|
|
394
406
|
redo(): Promise<HistoryStep>;
|
|
407
|
+
/** Reload the editor's page and resolve once the new document answers. */
|
|
408
|
+
reloadPage(): Promise<void>;
|
|
395
409
|
/** Mirrors `vgai status` — the full live editor state as JSON. */
|
|
396
410
|
status(): Promise<EditorState>;
|
|
397
411
|
/** A live viewport PNG (`EditorClient.captureViewport`) — no direct CLI verb exists; this is the closest wire read. */
|
package/dist/index.js
CHANGED
|
@@ -168,7 +168,7 @@ var EditorClient = class {
|
|
|
168
168
|
commandDispatcher = void 0;
|
|
169
169
|
if (error?.name === "TimeoutError") {
|
|
170
170
|
throw new EditorCommandError(
|
|
171
|
-
`The editor at ${this.baseUrl} never answered "${type}" within ${Math.round(deadlineMs / 1e3)}s \u2014 past every server-side budget, so the server itself is not answering. Check the terminal
|
|
171
|
+
`The editor at ${this.baseUrl} never answered "${type}" within ${Math.round(deadlineMs / 1e3)}s \u2014 past every server-side budget, so the server itself is not answering. Check the terminal that started this editor session.`,
|
|
172
172
|
void 0,
|
|
173
173
|
true
|
|
174
174
|
);
|
|
@@ -180,7 +180,7 @@ var EditorClient = class {
|
|
|
180
180
|
continue;
|
|
181
181
|
}
|
|
182
182
|
throw new EditorCommandError(
|
|
183
|
-
`POST ${url} ("${type}") never reached the editor: ${code}${detail ? ` (${detail})` : ""}.` + (retried ? ` Retried once after ${TRANSPORT_RETRY_DELAY_MS}ms; it failed the same way.` : "") + (options?.retryTransport === true ? "" : " Not retried automatically: this command can change editor state, and a socket that died after the request was written cannot prove the editor did not already run it.") + " A transport failure means the port stopped answering, not that the editor refused \u2014 the dev server restarts on any watched source edit, and it shuts itself down after an idle window. Check the terminal
|
|
183
|
+
`POST ${url} ("${type}") never reached the editor: ${code}${detail ? ` (${detail})` : ""}.` + (retried ? ` Retried once after ${TRANSPORT_RETRY_DELAY_MS}ms; it failed the same way.` : "") + (options?.retryTransport === true ? "" : " Not retried automatically: this command can change editor state, and a socket that died after the request was written cannot prove the editor did not already run it.") + " A transport failure means the port stopped answering, not that the editor refused \u2014 the dev server restarts on any watched source edit, and it shuts itself down after an idle window. Check the terminal that started this editor session and confirm the port this client resolved.",
|
|
184
184
|
code,
|
|
185
185
|
false
|
|
186
186
|
);
|
|
@@ -558,10 +558,21 @@ var EditorClient = class {
|
|
|
558
558
|
}
|
|
559
559
|
/** Apply a STYLE BUNDLE — palette, material, icon set and region defaults
|
|
560
560
|
* in one gesture (`classic`/`glass`/`maya`/`substance`, or one a package
|
|
561
|
-
* the project
|
|
561
|
+
* the project or product composes carries, such as `blender`). */
|
|
562
562
|
async setStyle(style) {
|
|
563
563
|
await this.command({ type: "set-style", style });
|
|
564
564
|
}
|
|
565
|
+
/** A document stage's viewport PRESENTATION (`kit/viewport-presentation`), resolved; with a
|
|
566
|
+
* `layer`, that choice is recorded for the view first, as the toolbar records it. */
|
|
567
|
+
/** A view's presentation, answered resolved. With a LAYER the person's choice is recorded
|
|
568
|
+
* first; with a STRING, the named view (`*.view.ts`) of that id is put on the view whole. */
|
|
569
|
+
async viewportPresentation(documentId, layer) {
|
|
570
|
+
return this.command({
|
|
571
|
+
type: "viewport-presentation",
|
|
572
|
+
documentId,
|
|
573
|
+
...typeof layer === "string" ? { preset: layer } : layer ? { layer } : {}
|
|
574
|
+
});
|
|
575
|
+
}
|
|
565
576
|
/** Set the MATERIAL apart from the bundle that usually carries it.
|
|
566
577
|
* Answers with what the chrome wears afterwards. */
|
|
567
578
|
async setAppearance(appearance) {
|
|
@@ -772,7 +783,8 @@ var EditorClient = class {
|
|
|
772
783
|
async captureEditorChrome(options) {
|
|
773
784
|
return this.command({
|
|
774
785
|
type: "capture-editor-chrome",
|
|
775
|
-
...options?.scale === void 0 ? {} : { scale: options.scale }
|
|
786
|
+
...options?.scale === void 0 ? {} : { scale: options.scale },
|
|
787
|
+
...options?.region === void 0 ? {} : { region: options.region }
|
|
776
788
|
});
|
|
777
789
|
}
|
|
778
790
|
/** With a view, present and capture it in one request so document discovery
|
|
@@ -960,6 +972,50 @@ var EditorClient = class {
|
|
|
960
972
|
async documentTable() {
|
|
961
973
|
return this.command({ type: "document-table" });
|
|
962
974
|
}
|
|
975
|
+
/**
|
|
976
|
+
* Reload the editor's page and wait until the new document answers — the
|
|
977
|
+
* host's `page-reload`, for a product with no game client (`@volter/game-live`'s
|
|
978
|
+
* `page.reload()` is the same order with the same two witnesses). A tab's
|
|
979
|
+
* epoch count rising proves a new document loaded; a host command answering
|
|
980
|
+
* proves it can be driven. It is sent only once that tab reports its command
|
|
981
|
+
* listener ready, because a command sent before is filed as a stall.
|
|
982
|
+
*/
|
|
983
|
+
async reloadPage(timeoutMs = 6e4) {
|
|
984
|
+
const epochs = async () => {
|
|
985
|
+
const tabs = (await this.getState().catch(() => ({ tabs: [] }))).tabs ?? [];
|
|
986
|
+
const byTab = tabs;
|
|
987
|
+
return new Map(byTab.map((tab) => [tab.tabId8, tab]));
|
|
988
|
+
};
|
|
989
|
+
const pause = () => new Promise((settle) => setTimeout(settle, 250));
|
|
990
|
+
const before = await epochs();
|
|
991
|
+
const deadline = Date.now() + timeoutMs;
|
|
992
|
+
await this.command({ type: "page-reload" });
|
|
993
|
+
let reloaded = [];
|
|
994
|
+
while (reloaded.length === 0) {
|
|
995
|
+
if (Date.now() > deadline) throw new Error("The reload was ordered and no tab of this session reported a new page load.");
|
|
996
|
+
await pause();
|
|
997
|
+
const now = await epochs();
|
|
998
|
+
reloaded = [...now].filter(([tabId, tab]) => tab.epochCount > (before.get(tabId)?.epochCount ?? 0)).map(([tabId]) => tabId);
|
|
999
|
+
}
|
|
1000
|
+
for (; ; ) {
|
|
1001
|
+
if (Date.now() > deadline) throw new Error("A new page loaded, but its command listener did not become ready.");
|
|
1002
|
+
const now = await epochs();
|
|
1003
|
+
if (reloaded.some((tabId) => {
|
|
1004
|
+
const listener = now.get(tabId)?.commandListener;
|
|
1005
|
+
return typeof listener !== "string" || listener === "ready";
|
|
1006
|
+
})) break;
|
|
1007
|
+
await pause();
|
|
1008
|
+
}
|
|
1009
|
+
for (; ; ) {
|
|
1010
|
+
try {
|
|
1011
|
+
await this.command({ type: "current-view" });
|
|
1012
|
+
return;
|
|
1013
|
+
} catch (error) {
|
|
1014
|
+
if (Date.now() > deadline) throw error;
|
|
1015
|
+
await pause();
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
963
1019
|
async getState() {
|
|
964
1020
|
const res = await this.httpFetch(`${this.baseUrl}/__editor/state`);
|
|
965
1021
|
const state = await this.readJson(res);
|
|
@@ -1017,7 +1073,7 @@ var EditorClient = class {
|
|
|
1017
1073
|
if (error?.name === "TimeoutError") throw error;
|
|
1018
1074
|
const { code, detail } = describeFetchFailure(error);
|
|
1019
1075
|
throw new EditorCommandError(
|
|
1020
|
-
`${init?.method ?? "GET"} ${url} never reached the editor: ${code}${detail ? ` (${detail})` : ""}. Nothing answered on that port \u2014 check the terminal
|
|
1076
|
+
`${init?.method ?? "GET"} ${url} never reached the editor: ${code}${detail ? ` (${detail})` : ""}. Nothing answered on that port \u2014 check the terminal that started this editor session and confirm the port this client resolved.`,
|
|
1021
1077
|
code,
|
|
1022
1078
|
false
|
|
1023
1079
|
);
|
|
@@ -1033,7 +1089,7 @@ var EditorClient = class {
|
|
|
1033
1089
|
const contentType = res.headers.get("content-type") ?? "";
|
|
1034
1090
|
if (!contentType.includes("application/json")) {
|
|
1035
1091
|
throw new Error(
|
|
1036
|
-
`The editor at ${this.baseUrl} answered with its page fallback (${contentType || "no content-type"}) rather than JSON, so no editor server handled the request. Check that this URL is a running
|
|
1092
|
+
`The editor at ${this.baseUrl} answered with its page fallback (${contentType || "no content-type"}) rather than JSON, so no editor server handled the request. Check that this URL is a running editor session.`
|
|
1037
1093
|
);
|
|
1038
1094
|
}
|
|
1039
1095
|
const body = await res.json();
|
|
@@ -1252,7 +1308,7 @@ function inferAssetKind(path) {
|
|
|
1252
1308
|
return EXTENSION_KIND[ext] ?? "json";
|
|
1253
1309
|
}
|
|
1254
1310
|
var LiveEditor = class {
|
|
1255
|
-
/** `#`-private, not `private`: `volter-editor eval --list` enumerates this object's
|
|
1311
|
+
/** `#`-private, not `private`: `volter-model-editor eval --list` enumerates this object's
|
|
1256
1312
|
* real runtime members, and TypeScript's erased `private` would leave the
|
|
1257
1313
|
* raw `EditorClient` advertised beside them. */
|
|
1258
1314
|
#client;
|
|
@@ -1270,7 +1326,7 @@ var LiveEditor = class {
|
|
|
1270
1326
|
this.document = new LiveEditorDocument(client);
|
|
1271
1327
|
}
|
|
1272
1328
|
/**
|
|
1273
|
-
* THE BLENDER LANE'S VERBS, from `volter-editor eval`.
|
|
1329
|
+
* THE BLENDER LANE'S VERBS, from `volter-model-editor eval`.
|
|
1274
1330
|
*
|
|
1275
1331
|
* Blender runs headless in the editor tab's worker (ARCHITECTURE-CORE, "THE
|
|
1276
1332
|
* BLENDER IN THE TAB IS BLENDER") and answers `blender-start`,
|
|
@@ -1282,7 +1338,7 @@ var LiveEditor = class {
|
|
|
1282
1338
|
* per question — the same discovery failure `eval-surface.ts`'s header
|
|
1283
1339
|
* records, in a lane that had not noticed it yet.
|
|
1284
1340
|
*
|
|
1285
|
-
* volter-editor eval "await editor.blender('blender-execute', { code: 'import bpy; print(len(bpy.data.objects))' })"
|
|
1341
|
+
* volter-model-editor eval "await editor.blender('blender-execute', { code: 'import bpy; print(len(bpy.data.objects))' })"
|
|
1286
1342
|
*
|
|
1287
1343
|
* `blender-status` is the only verb that creates nothing: it answers whether
|
|
1288
1344
|
* this tab already has a session without starting one.
|
|
@@ -1321,6 +1377,9 @@ var LiveEditor = class {
|
|
|
1321
1377
|
* `@volter/editor-sdk`'s `CaptureDimensions`.
|
|
1322
1378
|
* Supply a view to present and photograph it in one editor request.
|
|
1323
1379
|
*/
|
|
1380
|
+
/** The active document's RENDER alone, at the size asked for: what the document draws,
|
|
1381
|
+
* without what the viewport draws over it (its navigation gizmo, its readouts). To see a
|
|
1382
|
+
* stage as the person does, use `captureEditorChrome({ region: 'document' })`. */
|
|
1324
1383
|
async captureActiveDocument(size, view) {
|
|
1325
1384
|
return this.#client.captureActiveDocument(size, view);
|
|
1326
1385
|
}
|
|
@@ -1420,14 +1479,25 @@ var LiveEditor = class {
|
|
|
1420
1479
|
await this.#client.setWorkspace(id);
|
|
1421
1480
|
}
|
|
1422
1481
|
/**
|
|
1423
|
-
* Apply a STYLE BUNDLE by id — the chrome's palette, material
|
|
1424
|
-
*
|
|
1482
|
+
* Apply a STYLE BUNDLE by id — the chrome's palette, material and icon set in
|
|
1483
|
+
* one gesture, the session door beside
|
|
1425
1484
|
* `View → <Style> Style`. A bundle the open project does not offer refuses
|
|
1426
1485
|
* and names the vocabulary; `currentView().style` reports the one worn.
|
|
1427
1486
|
*/
|
|
1428
1487
|
async style(id) {
|
|
1429
1488
|
await this.#client.setStyle(id);
|
|
1430
1489
|
}
|
|
1490
|
+
/**
|
|
1491
|
+
* A document stage's viewport PRESENTATION — its draw mode, lighting, backdrop and overlays
|
|
1492
|
+
* (`@volter/editor-sdk/kit/viewport-presentation`) — resolved. With `layer`, that choice is
|
|
1493
|
+
* recorded for the view first, as a person's toolbar change would be, e.g.
|
|
1494
|
+
* `presentation('model:src/models/cube.blend', { all: { lighting: { studioPreset: 'kit' } } })`.
|
|
1495
|
+
* With a STRING, the named view of that id (`*.view.ts`, e.g. `'godot'`) is put on the view
|
|
1496
|
+
* whole; the answer's `viewPresets` lists the ones registered.
|
|
1497
|
+
*/
|
|
1498
|
+
async presentation(documentId, layer) {
|
|
1499
|
+
return this.#client.viewportPresentation(documentId, layer);
|
|
1500
|
+
}
|
|
1431
1501
|
/**
|
|
1432
1502
|
* Set the MATERIAL apart from the bundle that usually carries it.
|
|
1433
1503
|
* Appearance is palette × material, independent axes by ruling, so
|
|
@@ -1710,6 +1780,10 @@ var LiveEditor = class {
|
|
|
1710
1780
|
async redo() {
|
|
1711
1781
|
return this.#client.redo();
|
|
1712
1782
|
}
|
|
1783
|
+
/** Reload the editor's page and resolve once the new document answers. */
|
|
1784
|
+
async reloadPage() {
|
|
1785
|
+
await this.#client.reloadPage();
|
|
1786
|
+
}
|
|
1713
1787
|
/** Mirrors `vgai status` — the full live editor state as JSON. */
|
|
1714
1788
|
async status() {
|
|
1715
1789
|
return this.#client.getState();
|
|
@@ -1786,8 +1860,8 @@ function lazyChainProxy(resolveRoot, path = []) {
|
|
|
1786
1860
|
}
|
|
1787
1861
|
|
|
1788
1862
|
// src/session.ts
|
|
1789
|
-
import { existsSync as
|
|
1790
|
-
import { dirname, join as
|
|
1863
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
|
|
1864
|
+
import { dirname as dirname2, join as join4, resolve as resolve2 } from "node:path";
|
|
1791
1865
|
|
|
1792
1866
|
// ../editor-project/src/manifest/locate.ts
|
|
1793
1867
|
import { existsSync } from "node:fs";
|
|
@@ -1812,18 +1886,175 @@ function resolveManifestPath(dir) {
|
|
|
1812
1886
|
return join(dir, MANIFEST_FILENAME);
|
|
1813
1887
|
}
|
|
1814
1888
|
|
|
1889
|
+
// ../editor-sdk/src/session/product-locator.ts
|
|
1890
|
+
import { existsSync as existsSync2, readFileSync } from "node:fs";
|
|
1891
|
+
import { createRequire } from "node:module";
|
|
1892
|
+
import { dirname, join as join2 } from "node:path";
|
|
1893
|
+
import { pathToFileURL } from "node:url";
|
|
1894
|
+
var PRODUCT_DECLARATION_KEY = "vgai.product";
|
|
1895
|
+
var PRODUCT_INSTALL_LINES = [
|
|
1896
|
+
" npm install --save-dev @volter/model-editor",
|
|
1897
|
+
" npm install --save-dev @volter/game-editor"
|
|
1898
|
+
];
|
|
1899
|
+
var PRODUCT_CREATE_LINES = [" npx @volter/editor create <name>"];
|
|
1900
|
+
var PRODUCT_COLOR_THEMES = ["dark", "light"];
|
|
1901
|
+
function readProductManifest(packageDir) {
|
|
1902
|
+
const manifestPath = join2(packageDir, "package.json");
|
|
1903
|
+
let manifest;
|
|
1904
|
+
try {
|
|
1905
|
+
manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
1906
|
+
} catch {
|
|
1907
|
+
return null;
|
|
1908
|
+
}
|
|
1909
|
+
const declared = manifest.vgai?.product;
|
|
1910
|
+
if (declared === void 0) return null;
|
|
1911
|
+
const entry = declared.entry;
|
|
1912
|
+
if (typeof entry !== "string" || !entry.startsWith("./"))
|
|
1913
|
+
throw new Error(
|
|
1914
|
+
`${manifestPath} declares ${PRODUCT_DECLARATION_KEY} but its "entry" is ${JSON.stringify(entry)}. It must be a ./-relative module \u2014 the product's ONE source entry, which is also its frame entry (e.g. "./src/index.ts").`
|
|
1915
|
+
);
|
|
1916
|
+
const resolved = join2(packageDir, entry);
|
|
1917
|
+
if (!existsSync2(resolved))
|
|
1918
|
+
throw new Error(
|
|
1919
|
+
`${manifestPath} declares ${PRODUCT_DECLARATION_KEY}.entry ${JSON.stringify(entry)}, which does not exist (${resolved}).`
|
|
1920
|
+
);
|
|
1921
|
+
const colorTheme = declared.colorTheme;
|
|
1922
|
+
if (typeof colorTheme !== "string" || !PRODUCT_COLOR_THEMES.includes(colorTheme))
|
|
1923
|
+
throw new Error(
|
|
1924
|
+
`${manifestPath} declares ${PRODUCT_DECLARATION_KEY} but its "colorTheme" is ${JSON.stringify(colorTheme)}. It must be ${PRODUCT_COLOR_THEMES.map(
|
|
1925
|
+
(one) => JSON.stringify(one)
|
|
1926
|
+
).join(" or ")} \u2014 which way round this product paints, read before any of its code runs so the workbench's first frame is already the product's own background.`
|
|
1927
|
+
);
|
|
1928
|
+
const bin = manifest.bin;
|
|
1929
|
+
const commands = bin !== null && typeof bin === "object" ? Object.keys(bin) : [];
|
|
1930
|
+
if (commands.length !== 1)
|
|
1931
|
+
throw new Error(
|
|
1932
|
+
`${manifestPath} declares ${PRODUCT_DECLARATION_KEY} but its "bin" names ${commands.length === 0 ? "no command" : `${commands.length} commands`}. A product has exactly one command \u2014 the one every session message tells a person to run.`
|
|
1933
|
+
);
|
|
1934
|
+
const displayName = declared.displayName;
|
|
1935
|
+
if (typeof displayName !== "string" || displayName.trim() === "")
|
|
1936
|
+
throw new Error(
|
|
1937
|
+
`${manifestPath} declares ${PRODUCT_DECLARATION_KEY} but its "displayName" is ${JSON.stringify(displayName)}. It must be the name a person sees (e.g. "Volter Editor").`
|
|
1938
|
+
);
|
|
1939
|
+
return {
|
|
1940
|
+
name: typeof manifest.name === "string" ? manifest.name : packageDir,
|
|
1941
|
+
version: typeof manifest.version === "string" ? manifest.version : "unknown",
|
|
1942
|
+
command: commands[0],
|
|
1943
|
+
displayName,
|
|
1944
|
+
dir: packageDir,
|
|
1945
|
+
entry,
|
|
1946
|
+
colorTheme,
|
|
1947
|
+
// A private source product may use an explicitly selected matching checkout.
|
|
1948
|
+
// Public packages must always carry a downloadable, checksummed workbench.
|
|
1949
|
+
workbench: manifest.private === true && declared.workbench === void 0 ? null : readProductWorkbench(manifestPath, declared.workbench)
|
|
1950
|
+
};
|
|
1951
|
+
}
|
|
1952
|
+
var SHA256_HEX = /^[0-9a-f]{64}$/;
|
|
1953
|
+
function readProductWorkbench(manifestPath, declared) {
|
|
1954
|
+
const example = ' "vgai": { "product": { "workbench": { "release": "<product>-<fork sha 12>-<platform>", "tarballSha256": "<64 hex>" } } }\n`node scripts/workbench/build-release.mjs --publish --out <release dir>` cuts the release and prints exactly that block.';
|
|
1955
|
+
if (typeof declared !== "object" || declared === null || Array.isArray(declared))
|
|
1956
|
+
throw new Error(
|
|
1957
|
+
`${manifestPath} declares ${PRODUCT_DECLARATION_KEY} but its "workbench" is ${JSON.stringify(declared)}. A product IS the Code-OSS workbench it was compiled into, so it names the published release those bytes are:
|
|
1958
|
+
${example}`
|
|
1959
|
+
);
|
|
1960
|
+
const record = declared;
|
|
1961
|
+
const extra = Object.keys(record).filter((key) => key !== "release" && key !== "tarballSha256");
|
|
1962
|
+
if (extra.length > 0)
|
|
1963
|
+
throw new Error(
|
|
1964
|
+
`${manifestPath}'s ${PRODUCT_DECLARATION_KEY}.workbench carries ${extra.map((key) => `"${key}"`).join(", ")}, which this declaration has no meaning for. It names the release and the pin, and nothing else:
|
|
1965
|
+
${example}`
|
|
1966
|
+
);
|
|
1967
|
+
const release = record["release"];
|
|
1968
|
+
if (typeof release !== "string" || release.trim() === "")
|
|
1969
|
+
throw new Error(
|
|
1970
|
+
`${manifestPath}'s ${PRODUCT_DECLARATION_KEY}.workbench names no "release" \u2014 the GitHub Release tag its workbench was published under:
|
|
1971
|
+
${example}`
|
|
1972
|
+
);
|
|
1973
|
+
const tarballSha256 = record["tarballSha256"];
|
|
1974
|
+
if (typeof tarballSha256 !== "string" || !SHA256_HEX.test(tarballSha256))
|
|
1975
|
+
throw new Error(
|
|
1976
|
+
`${manifestPath}'s ${PRODUCT_DECLARATION_KEY}.workbench has tarballSha256 ${JSON.stringify(tarballSha256)}, which is not 64 hex characters. It is the PIN \u2014 the hash the downloaded tarball is checked against before a byte of it is extracted, so it is never approximate:
|
|
1977
|
+
${example}`
|
|
1978
|
+
);
|
|
1979
|
+
return { release, tarballSha256 };
|
|
1980
|
+
}
|
|
1981
|
+
function declaredVgaiPackages(projectRoot) {
|
|
1982
|
+
let manifest;
|
|
1983
|
+
try {
|
|
1984
|
+
manifest = JSON.parse(
|
|
1985
|
+
readFileSync(join2(projectRoot, "package.json"), "utf8")
|
|
1986
|
+
);
|
|
1987
|
+
} catch {
|
|
1988
|
+
return [];
|
|
1989
|
+
}
|
|
1990
|
+
return Object.keys({ ...manifest.dependencies ?? {}, ...manifest.devDependencies ?? {} }).filter((name) => name.startsWith("@volter/")).sort();
|
|
1991
|
+
}
|
|
1992
|
+
function locatePackage(projectRoot, name) {
|
|
1993
|
+
const require2 = createRequire(pathToFileURL(join2(projectRoot, "package.json")));
|
|
1994
|
+
try {
|
|
1995
|
+
return dirname(require2.resolve(`${name}/package.json`));
|
|
1996
|
+
} catch {
|
|
1997
|
+
try {
|
|
1998
|
+
let dir = dirname(require2.resolve(name));
|
|
1999
|
+
for (; ; ) {
|
|
2000
|
+
const candidate = join2(dir, "package.json");
|
|
2001
|
+
try {
|
|
2002
|
+
if (JSON.parse(readFileSync(candidate, "utf8")).name === name)
|
|
2003
|
+
return dir;
|
|
2004
|
+
} catch {
|
|
2005
|
+
}
|
|
2006
|
+
const parent = dirname(dir);
|
|
2007
|
+
if (parent === dir) return null;
|
|
2008
|
+
dir = parent;
|
|
2009
|
+
}
|
|
2010
|
+
} catch {
|
|
2011
|
+
return null;
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
function declaredProducts(projectRoot) {
|
|
2016
|
+
const found = [];
|
|
2017
|
+
for (const name of declaredVgaiPackages(projectRoot)) {
|
|
2018
|
+
const dir = locatePackage(projectRoot, name);
|
|
2019
|
+
if (dir === null) continue;
|
|
2020
|
+
const product = readProductManifest(dir);
|
|
2021
|
+
if (product !== null) found.push(product);
|
|
2022
|
+
}
|
|
2023
|
+
return found;
|
|
2024
|
+
}
|
|
2025
|
+
function resolveProductForProject(projectRoot) {
|
|
2026
|
+
const products = declaredProducts(projectRoot);
|
|
2027
|
+
if (products.length === 1) return products[0];
|
|
2028
|
+
if (products.length === 0)
|
|
2029
|
+
throw new Error(
|
|
2030
|
+
`${projectRoot} declares no editor product, so there is no editor to open it in.
|
|
2031
|
+
The installed product supplies the editor,
|
|
2032
|
+
and which one runs is what the project installed, never a flag.
|
|
2033
|
+
For THIS folder, install the one you are building with:
|
|
2034
|
+
${PRODUCT_INSTALL_LINES.join("\n")}
|
|
2035
|
+
then run that product's \`edit\` command again. For a NEW project, the product creates it:
|
|
2036
|
+
${PRODUCT_CREATE_LINES.join("\n")}`
|
|
2037
|
+
);
|
|
2038
|
+
throw new Error(
|
|
2039
|
+
`${projectRoot} declares ${products.length} editor products \u2014 ${products.map((product) => product.name).join(" and ")}.
|
|
2040
|
+
One page runs one product, and nothing here may choose between them. Remove the one you
|
|
2041
|
+
are not building with:
|
|
2042
|
+
` + products.map((product) => ` npm uninstall ${product.name}`).join("\n")
|
|
2043
|
+
);
|
|
2044
|
+
}
|
|
2045
|
+
|
|
1815
2046
|
// ../editor-sdk/src/session/registry-format.ts
|
|
1816
2047
|
import {
|
|
1817
2048
|
mkdirSync,
|
|
1818
2049
|
readdirSync,
|
|
1819
|
-
readFileSync,
|
|
2050
|
+
readFileSync as readFileSync2,
|
|
1820
2051
|
realpathSync,
|
|
1821
2052
|
unlinkSync,
|
|
1822
2053
|
writeFileSync
|
|
1823
2054
|
} from "node:fs";
|
|
1824
2055
|
import { homedir } from "node:os";
|
|
1825
|
-
import { join as
|
|
1826
|
-
var EDITOR_SESSIONS_REGISTRY_FILE =
|
|
2056
|
+
import { join as join3, resolve } from "node:path";
|
|
2057
|
+
var EDITOR_SESSIONS_REGISTRY_FILE = join3(homedir(), ".vgai", "editor-sessions.json");
|
|
1827
2058
|
function isEditorSessionEntry(v) {
|
|
1828
2059
|
if (typeof v !== "object" || v === null) return false;
|
|
1829
2060
|
const s = v;
|
|
@@ -1854,7 +2085,7 @@ function pidAlive(pid) {
|
|
|
1854
2085
|
}
|
|
1855
2086
|
function readLiveRegisteredSessions() {
|
|
1856
2087
|
try {
|
|
1857
|
-
const raw = JSON.parse(
|
|
2088
|
+
const raw = JSON.parse(readFileSync2(EDITOR_SESSIONS_REGISTRY_FILE, "utf8"));
|
|
1858
2089
|
return Array.isArray(raw) ? raw.filter(isEditorSessionEntry).map(normalizeEditorSessionEntry).filter((s) => pidAlive(s.pid)) : [];
|
|
1859
2090
|
} catch {
|
|
1860
2091
|
return [];
|
|
@@ -1933,8 +2164,8 @@ var HttpSessionDiscovery = class {
|
|
|
1933
2164
|
function findProjectRootFrom(dir) {
|
|
1934
2165
|
let cur = resolve2(dir);
|
|
1935
2166
|
for (; ; ) {
|
|
1936
|
-
if (
|
|
1937
|
-
const parent =
|
|
2167
|
+
if (existsSync3(resolveManifestPath(cur))) return cur;
|
|
2168
|
+
const parent = dirname2(cur);
|
|
1938
2169
|
if (parent === cur) return null;
|
|
1939
2170
|
cur = parent;
|
|
1940
2171
|
}
|
|
@@ -1949,7 +2180,7 @@ function canonicalPath(path) {
|
|
|
1949
2180
|
function readProjectSession(projectRoot) {
|
|
1950
2181
|
try {
|
|
1951
2182
|
const value = JSON.parse(
|
|
1952
|
-
|
|
2183
|
+
readFileSync3(join4(projectRoot, ".vgai", "session.json"), "utf8")
|
|
1953
2184
|
);
|
|
1954
2185
|
if (typeof value !== "object" || value === null) return null;
|
|
1955
2186
|
const hint = value;
|
|
@@ -1981,12 +2212,12 @@ ${manifestError}`
|
|
|
1981
2212
|
function noMatchingSessionRefusal(projectRoot, canon, sessions, discoveryFailure) {
|
|
1982
2213
|
if (discoveryFailure !== null) {
|
|
1983
2214
|
return new Error(
|
|
1984
|
-
`@volter/editor-live: could not READ the editor session registry while looking for ${projectRoot} \u2014 ${discoveryFailure}. This is not the answer "no editor is running": the question went unanswered, so nothing is known about what is live. Retry (a probe can time out while the box is loaded); if it keeps failing,
|
|
2215
|
+
`@volter/editor-live: could not READ the editor session registry while looking for ${projectRoot} \u2014 ${discoveryFailure}. This is not the answer "no editor is running": the question went unanswered, so nothing is known about what is live. Retry (a probe can time out while the box is loaded); if it keeps failing, ${sessionsCommandFor(projectRoot)} asks the same question directly.`
|
|
1985
2216
|
);
|
|
1986
2217
|
}
|
|
1987
2218
|
if (sessions.length === 0) {
|
|
1988
2219
|
return new Error(
|
|
1989
|
-
`@volter/editor-live: the editor session registry is readable and lists NO live sessions, so none covers ${projectRoot}. @volter/editor-live only attaches to an already-running session \u2014 it never starts one \u2014 so run
|
|
2220
|
+
`@volter/editor-live: the editor session registry is readable and lists NO live sessions, so none covers ${projectRoot}. @volter/editor-live only attaches to an already-running session \u2014 it never starts one \u2014 so run ${editCommandFor(projectRoot)} in that project first, then retry.`
|
|
1990
2221
|
);
|
|
1991
2222
|
}
|
|
1992
2223
|
const listed = sessions.map((s) => ` port ${s.port} \u2192 ${s.project === null ? "(no project)" : s.project}`).join("\n");
|
|
@@ -1995,7 +2226,7 @@ function noMatchingSessionRefusal(projectRoot, canon, sessions, discoveryFailure
|
|
|
1995
2226
|
looking for (resolved): ${canon}
|
|
1996
2227
|
live sessions:
|
|
1997
2228
|
${listed}
|
|
1998
|
-
If one of those is meant to be this project, the two paths differ after resolution \u2014 the usual cause is a git worktree or a symlink, where the session was opened through a different path to the same files. Run
|
|
2229
|
+
If one of those is meant to be this project, the two paths differ after resolution \u2014 the usual cause is a git worktree or a symlink, where the session was opened through a different path to the same files. Run ${editCommandFor(projectRoot)} from THIS path, or use the path the session lists.`
|
|
1999
2230
|
);
|
|
2000
2231
|
}
|
|
2001
2232
|
async function resolveSession(projectDir = process.cwd(), deps = {}) {
|
|
@@ -2004,7 +2235,7 @@ async function resolveSession(projectDir = process.cwd(), deps = {}) {
|
|
|
2004
2235
|
const projectRoot = findRoot(projectDir);
|
|
2005
2236
|
if (projectRoot === null) {
|
|
2006
2237
|
throw new Error(
|
|
2007
|
-
`@volter/editor-live: no vgai.project.json found in ${projectDir} or any parent directory \u2014 is this a
|
|
2238
|
+
`@volter/editor-live: no vgai.project.json found in ${projectDir} or any parent directory \u2014 is this a project?`
|
|
2008
2239
|
);
|
|
2009
2240
|
}
|
|
2010
2241
|
const localHint = (deps.readProjectSession ?? readProjectSession)(projectRoot);
|
|
@@ -2039,6 +2270,19 @@ async function resolveSession(projectDir = process.cwd(), deps = {}) {
|
|
|
2039
2270
|
if (match.manifestError != null) throw manifestRefusal(projectRoot, match.manifestError);
|
|
2040
2271
|
return { port: match.port, projectRoot };
|
|
2041
2272
|
}
|
|
2273
|
+
function editCommandFor(projectRoot) {
|
|
2274
|
+
return verbFor(projectRoot, "edit");
|
|
2275
|
+
}
|
|
2276
|
+
function sessionsCommandFor(projectRoot) {
|
|
2277
|
+
return verbFor(projectRoot, "sessions");
|
|
2278
|
+
}
|
|
2279
|
+
function verbFor(projectRoot, verb) {
|
|
2280
|
+
try {
|
|
2281
|
+
return `\`${resolveProductForProject(projectRoot).command} ${verb}\``;
|
|
2282
|
+
} catch {
|
|
2283
|
+
return `its product's \`${verb}\` command`;
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2042
2286
|
|
|
2043
2287
|
// src/singleton.ts
|
|
2044
2288
|
function createLazySession(factory) {
|