@helix3/helix-cli 0.1.13-helix3.83 → 0.1.13-helix3.85
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/bundle.js +22 -3
- package/dist/bundle.js.map +1 -1
- package/dist/dev/devServer.d.ts +53 -0
- package/dist/dev/devServer.js +228 -0
- package/dist/dev/devServer.js.map +1 -0
- package/dist/dev/shellPage.d.ts +3 -0
- package/dist/dev/shellPage.js +297 -0
- package/dist/dev/shellPage.js.map +1 -0
- package/dist/dev/state.d.ts +84 -0
- package/dist/dev/state.js +265 -0
- package/dist/dev/state.js.map +1 -0
- package/dist/index.js +124 -6
- package/dist/index.js.map +1 -1
- package/dist/lib.d.ts +31 -0
- package/dist/lib.js +42 -6
- package/dist/lib.js.map +1 -1
- package/dist/screenshot/staticServe.d.ts +8 -1
- package/dist/screenshot/staticServe.js +10 -6
- package/dist/screenshot/staticServe.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +2 -1
package/dist/bundle.js
CHANGED
|
@@ -45,9 +45,13 @@ function walk(dir, root, out) {
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
//
|
|
48
|
+
// The `helix dev` simulator's import specifier — shipping it inside a published world would hand every
|
|
49
|
+
// player a menu that grants entitlements and fakes purchases. Matching the literal specifier is what
|
|
50
|
+
// keeps a minified bundle that never imported it from tripping the rule.
|
|
51
|
+
const DEV_SHELL_SPECIFIER = 'helix-sdk/dev-shell';
|
|
52
|
+
// Local validation with the exact same rules the server enforces at publish — shared via
|
|
53
|
+
// @hypersoniclabs/helix-manifest, so "validate passed but publish failed" can't happen for contract
|
|
54
|
+
// reasons. ONE rule is CLI-only: the dev-simulator scan below, also the first rule here reading CONTENT.
|
|
51
55
|
function checkBundle(root) {
|
|
52
56
|
const errors = [];
|
|
53
57
|
const warnings = [];
|
|
@@ -103,6 +107,21 @@ function checkBundle(root) {
|
|
|
103
107
|
errors.push(`HELIX assets must stream from the CDN, not ship under helix_modules/: ${leaked.join(', ')}`);
|
|
104
108
|
}
|
|
105
109
|
}
|
|
110
|
+
// Read lazily and only for .js — every other rule here works off the directory listing alone.
|
|
111
|
+
for (const file of files) {
|
|
112
|
+
if (!file.path.toLowerCase().endsWith('.js'))
|
|
113
|
+
continue;
|
|
114
|
+
let source;
|
|
115
|
+
try {
|
|
116
|
+
source = (0, node_fs_1.readFileSync)(file.absolutePath, 'utf8');
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (source.includes(DEV_SHELL_SPECIFIER)) {
|
|
122
|
+
errors.push(`${file.path} imports the helix dev simulator ("${DEV_SHELL_SPECIFIER}") — the dev shell must never ship inside a world; remove the import`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
106
125
|
// Legal, but almost certainly a mistake, and previously invisible.
|
|
107
126
|
const streamable = streamablePayload(files);
|
|
108
127
|
if (streamable.files.length) {
|
package/dist/bundle.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bundle.js","sourceRoot":"","sources":["../src/bundle.ts"],"names":[],"mappings":";;;AA2CA,8CAaC;
|
|
1
|
+
{"version":3,"file":"bundle.js","sourceRoot":"","sources":["../src/bundle.ts"],"names":[],"mappings":";;;AA2CA,8CAaC;AAyBD,kCAsFC;AAYD,kEASC;AA5LD,qCAA8D;AAC9D,yCAA2C;AAC3C,mEAUwC;AAcxC,oGAAoG;AACpG,iGAAiG;AACjG,iGAAiG;AACjG,SAAS;AACT,EAAE;AACF,kGAAkG;AAClG,kGAAkG;AAClG,kGAAkG;AAClG,mGAAmG;AACnG,mGAAmG;AACnG,4EAA4E;AAC5E,EAAE;AACF,oGAAoG;AACpG,kGAAkG;AAClG,WAAW;AACX,MAAM,6BAA6B,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;AAEnF,SAAgB,iBAAiB,CAAC,KAAmB;IAInD,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CACpC,6BAA6B,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAC/C,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,CAC5C,CACF,CAAC;IACF,OAAO;QACL,KAAK,EAAE,OAAO;QACd,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;KACzD,CAAC;AACJ,CAAC;AAED,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,CAAC,cAAc,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC;AAE1F,SAAS,IAAI,CAAC,GAAW,EAAE,IAAY,EAAE,GAAiB;IACxD,KAAK,MAAM,IAAI,IAAI,IAAA,qBAAW,EAAC,GAAG,CAAC,EAAE,CAAC;QACpC,IAAI,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAS;QACrC,MAAM,YAAY,GAAG,IAAA,gBAAI,EAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QACrC,MAAM,IAAI,GAAG,IAAA,kBAAQ,EAAC,YAAY,CAAC,CAAC;QACpC,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;YACvB,IAAI,CAAC,YAAY,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;QAChC,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAA,oBAAQ,EAAC,IAAI,EAAE,YAAY,CAAC,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC;QACxG,CAAC;IACH,CAAC;AACH,CAAC;AAED,uGAAuG;AACvG,qGAAqG;AACrG,yEAAyE;AACzE,MAAM,mBAAmB,GAAG,qBAAqB,CAAC;AAElD,yFAAyF;AACzF,oGAAoG;AACpG,yGAAyG;AACzG,SAAgB,WAAW,CAAC,IAAY;IACtC,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,KAAK,GAAiB,EAAE,CAAC;IAE/B,IAAI,CAAC;QACH,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;IAC1B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,QAAQ,EAAE,IAAI;YACd,KAAK,EAAE,EAAE;YACT,MAAM,EAAE,CAAC,0BAA0B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;YAC9E,QAAQ,EAAE,EAAE;SACb,CAAC;IACJ,CAAC;IAED,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,kCAAiB,CAAC,CAAC;IACrE,IAAI,QAAQ,GAAmC,IAAI,CAAC;IACpD,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,MAAM,CAAC,IAAI,CAAC,MAAM,kCAAiB,2BAA2B,CAAC,CAAC;IAClE,CAAC;SAAM,CAAC;QACN,MAAM,MAAM,GAAG,IAAA,8BAAa,EAAC,IAAA,sBAAY,EAAC,YAAY,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;QAC9E,IAAI,MAAM,CAAC,KAAK;YAAE,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;;YACxC,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IACrC,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,GAAG,iCAAgB;QAAE,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,qBAAqB,iCAAgB,EAAE,CAAC,CAAC;IACzG,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC;QACnB,IAAI,CAAC,oCAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,MAAM,CAAC,IAAI,CAAC,iBAAiB,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;aAC/E,IAAI,IAAA,kCAAiB,EAAC,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI;YAAE,MAAM,CAAC,IAAI,CAAC,0BAA0B,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QACnG,IAAI,IAAI,CAAC,IAAI,GAAG,sCAAqB;YAAE,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,OAAO,IAAI,CAAC,IAAI,8BAA8B,sCAAqB,EAAE,CAAC,CAAC;QACtI,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC;YAAE,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,WAAW,CAAC,CAAC;IAC5D,CAAC;IACD,IAAI,KAAK,GAAG,uCAAsB;QAAE,MAAM,CAAC,IAAI,CAAC,aAAa,KAAK,qBAAqB,uCAAsB,EAAE,CAAC,CAAC;IAEjH,IAAI,QAAQ,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9D,MAAM,CAAC,IAAI,CAAC,UAAU,QAAQ,CAAC,KAAK,gCAAgC,CAAC,CAAC;IACxE,CAAC;IAED,uFAAuF;IACvF,sFAAsF;IACtF,+GAA+G;IAC/G,IAAI,QAAQ,EAAE,YAAY,KAAK,KAAK,IAAI,QAAQ,EAAE,YAAY,KAAK,KAAK,EAAE,CAAC;QACzE,MAAM,MAAM,GAAG,IAAA,qCAAoB,EAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QAC9D,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAClB,MAAM,CAAC,IAAI,CAAC,yEAAyE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC5G,CAAC;IACH,CAAC;IAED,8FAA8F;IAC9F,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,SAAS;QACvD,IAAI,MAAc,CAAC;QACnB,IAAI,CAAC;YACH,MAAM,GAAG,IAAA,sBAAY,EAAC,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;QACnD,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QACD,IAAI,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAAC;YACzC,MAAM,CAAC,IAAI,CACT,GAAG,IAAI,CAAC,IAAI,sCAAsC,mBAAmB,sEAAsE,CAC5I,CAAC;QACJ,CAAC;IACH,CAAC;IAED,mEAAmE;IACnE,MAAM,UAAU,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;IAC5C,IAAI,UAAU,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;QAC5B,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK;aAC3B,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;aACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QACxE,MAAM,IAAI,GACR,UAAU,CAAC,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM;YACpC,CAAC,CAAC,QAAQ,UAAU,CAAC,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,OAAO;YACvD,CAAC,CAAC,EAAE,CAAC;QACT,QAAQ,CAAC,IAAI,CACX,GAAG,UAAU,CAAC,KAAK,CAAC,MAAM,qDAAqD;YAC7E,GAAG,CAAC,UAAU,CAAC,KAAK,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,IAAI;YACvF,8FAA8F;YAC9F,2DAA2D,CAC9D,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;AAC/C,CAAC;AAED,qGAAqG;AACrG,mGAAmG;AACnG,mGAAmG;AACnG,mGAAmG;AACtF,QAAA,2BAA2B,GAAG,kBAAkB,CAAC;AAE9D,0GAA0G;AAC1G,qGAAqG;AACrG,yGAAyG;AACzG,qEAAqE;AACrE,SAAgB,2BAA2B,CAAC,IAAY,EAAE,QAAiC;IACzF,IAAI,CAAC,QAAQ,CAAC,WAAW;QAAE,OAAO,IAAI,CAAC;IACvC,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC1F,OAAO;QACL,IAAI,EAAE,mCAA2B;QACjC,IAAI,EAAE,OAAO,CAAC,UAAU;QACxB,YAAY,EAAE,IAAA,gBAAI,EAAC,IAAI,EAAE,mCAA2B,CAAC;QACrD,OAAO;KACR,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type DevIdentity, type IgnoreOutcome } from './state';
|
|
2
|
+
/** Clear of the dev-stack's ports (3001 portal · 4173 world · 5000 backend · 2567 room) and vite's 5173. */
|
|
3
|
+
export declare const DEV_SHELL_DEFAULT_PORT = 5190;
|
|
4
|
+
/** The world has no real row here, so it gets an obviously synthetic uuid rather than a real-looking one. */
|
|
5
|
+
export declare const DEV_WORLD_ID = "00000000-0000-4000-8000-000000000001";
|
|
6
|
+
export type DevWorldIdentity = {
|
|
7
|
+
id: string;
|
|
8
|
+
slug: string;
|
|
9
|
+
title: string;
|
|
10
|
+
};
|
|
11
|
+
export type DevServerOptions = {
|
|
12
|
+
/** The world PROJECT dir — its built bundle is <dir>/dist and its state lives in <dir>/.helix. */
|
|
13
|
+
worldDir: string;
|
|
14
|
+
/** Shell port. The world takes the next one; 0 gives both an OS-assigned port (what the specs use). */
|
|
15
|
+
port?: number;
|
|
16
|
+
reset?: boolean;
|
|
17
|
+
fresh?: boolean;
|
|
18
|
+
};
|
|
19
|
+
export type DevServerHandle = {
|
|
20
|
+
shellUrl: string;
|
|
21
|
+
worldUrl: string;
|
|
22
|
+
shellPort: number;
|
|
23
|
+
worldPort: number;
|
|
24
|
+
distDir: string;
|
|
25
|
+
world: DevWorldIdentity;
|
|
26
|
+
identity: DevIdentity;
|
|
27
|
+
stateFile: string;
|
|
28
|
+
products: {
|
|
29
|
+
path: string;
|
|
30
|
+
count: number;
|
|
31
|
+
} | null;
|
|
32
|
+
achievements: {
|
|
33
|
+
path: string;
|
|
34
|
+
count: number;
|
|
35
|
+
criteriaCount: number;
|
|
36
|
+
} | null;
|
|
37
|
+
gitignore: IgnoreOutcome;
|
|
38
|
+
sdkDistDir: string;
|
|
39
|
+
close: () => Promise<void>;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Where the browser gets the dev shell from. `@hypersoniclabs/helix-sdk/package.json` is NOT resolvable —
|
|
43
|
+
* the SDK's exports map does not list it — so resolve a real subpath instead and walk up from the file:
|
|
44
|
+
* dist/dev-shell/index.js and dist/index.js both land on dist/.
|
|
45
|
+
*/
|
|
46
|
+
export declare function resolveSdkDistDir(): string;
|
|
47
|
+
/** slug/title from the project's own manifest; a dir with no helix.json still runs, just unnamed. */
|
|
48
|
+
export declare function readWorldIdentity(worldDir: string): DevWorldIdentity;
|
|
49
|
+
/**
|
|
50
|
+
* Boot both servers. Nothing here reads credentials or touches the network: working with no backend at
|
|
51
|
+
* all is the whole point of the command.
|
|
52
|
+
*/
|
|
53
|
+
export declare function startDevServer(options: DevServerOptions): Promise<DevServerHandle>;
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEV_WORLD_ID = exports.DEV_SHELL_DEFAULT_PORT = void 0;
|
|
4
|
+
exports.resolveSdkDistDir = resolveSdkDistDir;
|
|
5
|
+
exports.readWorldIdentity = readWorldIdentity;
|
|
6
|
+
exports.startDevServer = startDevServer;
|
|
7
|
+
// The transport half of `helix dev`: the world's BUILT bundle on one port, the simulated shell page on
|
|
8
|
+
// another. Two ports means two ORIGINS — the SDK only handshakes when window.parent !== window, and a
|
|
9
|
+
// same-origin iframe would pass tests that production's cross-origin postMessage semantics would fail.
|
|
10
|
+
//
|
|
11
|
+
// Entirely offline: no auth, no backend, no credentials. The shell answers helix:ready immediately (an
|
|
12
|
+
// unanswered handshake costs every boot the SDK's full 8 s INIT_TIMEOUT_MS) and the SDK's dev-shell dist
|
|
13
|
+
// is served raw to the browser as native ESM.
|
|
14
|
+
const node_fs_1 = require("node:fs");
|
|
15
|
+
const node_path_1 = require("node:path");
|
|
16
|
+
const install_1 = require("../install");
|
|
17
|
+
const staticServe_1 = require("../screenshot/staticServe");
|
|
18
|
+
const shellPage_1 = require("./shellPage");
|
|
19
|
+
const state_1 = require("./state");
|
|
20
|
+
/** Clear of the dev-stack's ports (3001 portal · 4173 world · 5000 backend · 2567 room) and vite's 5173. */
|
|
21
|
+
exports.DEV_SHELL_DEFAULT_PORT = 5190;
|
|
22
|
+
/** The world has no real row here, so it gets an obviously synthetic uuid rather than a real-looking one. */
|
|
23
|
+
exports.DEV_WORLD_ID = '00000000-0000-4000-8000-000000000001';
|
|
24
|
+
/**
|
|
25
|
+
* Where the browser gets the dev shell from. `@hypersoniclabs/helix-sdk/package.json` is NOT resolvable —
|
|
26
|
+
* the SDK's exports map does not list it — so resolve a real subpath instead and walk up from the file:
|
|
27
|
+
* dist/dev-shell/index.js and dist/index.js both land on dist/.
|
|
28
|
+
*/
|
|
29
|
+
function resolveSdkDistDir() {
|
|
30
|
+
const entries = [];
|
|
31
|
+
for (const specifier of ['@hypersoniclabs/helix-sdk/dev-shell', '@hypersoniclabs/helix-sdk']) {
|
|
32
|
+
try {
|
|
33
|
+
entries.push(require.resolve(specifier));
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// Older SDK without the subpath, or not installed — the next candidate (or the throw) covers it.
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
for (const entry of entries) {
|
|
40
|
+
let dir = (0, node_path_1.dirname)(entry);
|
|
41
|
+
if ((0, node_path_1.basename)(dir) === 'dev-shell')
|
|
42
|
+
dir = (0, node_path_1.dirname)(dir);
|
|
43
|
+
if ((0, node_fs_1.existsSync)((0, node_path_1.join)(dir, 'dev-shell', 'index.js')))
|
|
44
|
+
return dir;
|
|
45
|
+
}
|
|
46
|
+
throw new Error('could not find the dev shell in @hypersoniclabs/helix-sdk — install a version that exports ' +
|
|
47
|
+
'"./dev-shell" (npm i @hypersoniclabs/helix-sdk@latest)');
|
|
48
|
+
}
|
|
49
|
+
/** slug/title from the project's own manifest; a dir with no helix.json still runs, just unnamed. */
|
|
50
|
+
function readWorldIdentity(worldDir) {
|
|
51
|
+
const manifestPath = install_1.WORLD_MANIFEST_LOCATIONS.map((rel) => (0, node_path_1.join)(worldDir, rel)).find((p) => (0, node_fs_1.existsSync)(p));
|
|
52
|
+
let slug;
|
|
53
|
+
let title;
|
|
54
|
+
if (manifestPath) {
|
|
55
|
+
try {
|
|
56
|
+
// Same BOM tolerance as the seed readers — a BOM here silently unnamed the world.
|
|
57
|
+
const raw = (0, node_fs_1.readFileSync)(manifestPath, 'utf8');
|
|
58
|
+
const manifest = JSON.parse(raw.charCodeAt(0) === 0xfeff ? raw.slice(1) : raw);
|
|
59
|
+
if (typeof manifest.slug === 'string' && manifest.slug)
|
|
60
|
+
slug = manifest.slug;
|
|
61
|
+
if (typeof manifest.title === 'string' && manifest.title)
|
|
62
|
+
title = manifest.title;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// A malformed manifest is the world's problem to fix; it must not block the local shell.
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const fallbackSlug = (0, node_path_1.basename)(worldDir) || 'dev-world';
|
|
69
|
+
return { id: exports.DEV_WORLD_ID, slug: slug ?? fallbackSlug, title: title ?? 'Dev World' };
|
|
70
|
+
}
|
|
71
|
+
const json = (res, status, body) => {
|
|
72
|
+
const payload = Buffer.from(`${JSON.stringify(body)}\n`, 'utf8');
|
|
73
|
+
res.writeHead(status, { 'content-type': 'application/json; charset=utf-8', 'content-length': payload.byteLength });
|
|
74
|
+
res.end(payload);
|
|
75
|
+
};
|
|
76
|
+
const text = (res, contentType, body) => {
|
|
77
|
+
const payload = Buffer.from(body, 'utf8');
|
|
78
|
+
res.writeHead(200, { 'content-type': contentType, 'content-length': payload.byteLength, 'cache-control': 'no-store' });
|
|
79
|
+
res.end(payload);
|
|
80
|
+
};
|
|
81
|
+
function readBody(req, limitBytes) {
|
|
82
|
+
return new Promise((resolvePromise, reject) => {
|
|
83
|
+
const chunks = [];
|
|
84
|
+
let size = 0;
|
|
85
|
+
req.on('data', (chunk) => {
|
|
86
|
+
size += chunk.length;
|
|
87
|
+
if (size > limitBytes) {
|
|
88
|
+
reject(new Error(`state payload exceeds ${limitBytes} bytes`));
|
|
89
|
+
req.destroy();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
chunks.push(chunk);
|
|
93
|
+
});
|
|
94
|
+
req.on('end', () => resolvePromise(Buffer.concat(chunks).toString('utf8')));
|
|
95
|
+
req.on('error', reject);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
// One simulated player's state is small, but a world can fill its data store — this is the same order as
|
|
99
|
+
// the shell lane's 100 MB per-world storage cap, so a legitimate state never trips it.
|
|
100
|
+
const STATE_LIMIT_BYTES = 128 * 1024 * 1024;
|
|
101
|
+
const listenError = (err, port) => {
|
|
102
|
+
if (err?.code === 'EADDRINUSE') {
|
|
103
|
+
return new Error(`port ${port} is already in use — pass --port <n> (helix dev takes <n> for the shell and <n>+1 for the world)`);
|
|
104
|
+
}
|
|
105
|
+
return err instanceof Error ? err : new Error(String(err));
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Boot both servers. Nothing here reads credentials or touches the network: working with no backend at
|
|
109
|
+
* all is the whole point of the command.
|
|
110
|
+
*/
|
|
111
|
+
async function startDevServer(options) {
|
|
112
|
+
const worldDir = (0, node_path_1.resolve)(options.worldDir);
|
|
113
|
+
const distDir = (0, node_path_1.join)(worldDir, 'dist');
|
|
114
|
+
if (!(0, node_fs_1.existsSync)((0, node_path_1.join)(distDir, 'index.html'))) {
|
|
115
|
+
throw new Error(`no built bundle at ${distDir} — run your build first (npm run build). ` +
|
|
116
|
+
'helix dev serves the BUILT bundle, not the source project.');
|
|
117
|
+
}
|
|
118
|
+
const sdkDistDir = resolveSdkDistDir();
|
|
119
|
+
const world = readWorldIdentity(worldDir);
|
|
120
|
+
const gitignore = (0, state_1.ensureDevDirIgnored)(worldDir);
|
|
121
|
+
const seed = (0, state_1.readDevProductsSeed)(worldDir);
|
|
122
|
+
const achievementsSeed = (0, state_1.readDevAchievementsSeed)(worldDir);
|
|
123
|
+
const selection = (0, state_1.selectDevState)((0, state_1.readDevStateFile)(worldDir), world.slug, {
|
|
124
|
+
reset: options.reset,
|
|
125
|
+
fresh: options.fresh,
|
|
126
|
+
});
|
|
127
|
+
let stateFile = selection.file;
|
|
128
|
+
if (selection.mutated)
|
|
129
|
+
(0, state_1.writeDevStateFile)(worldDir, stateFile);
|
|
130
|
+
// What /dev/config hands the page. It has to track the POSTs, not stay pinned to the boot state:
|
|
131
|
+
// reloading the tab is the ordinary way to check that a simulated save survived.
|
|
132
|
+
let liveState = selection.state;
|
|
133
|
+
// The seed REPLACES the stored registry at boot (earned survives), so editing the file between runs
|
|
134
|
+
// takes effect on the next `helix dev` — the local analog of re-registering. With no seed file the
|
|
135
|
+
// stored registry is left alone, so a hand-edited dev-state.json keeps working.
|
|
136
|
+
if (achievementsSeed && liveState) {
|
|
137
|
+
const prior = liveState.achievements;
|
|
138
|
+
const earned = typeof prior === 'object' && prior !== null && Array.isArray(prior.earned)
|
|
139
|
+
? prior.earned
|
|
140
|
+
: [];
|
|
141
|
+
liveState.achievements = { registry: achievementsSeed.achievements, earned };
|
|
142
|
+
}
|
|
143
|
+
const shellPort = options.port ?? exports.DEV_SHELL_DEFAULT_PORT;
|
|
144
|
+
const worldPort = shellPort ? shellPort + 1 : 0;
|
|
145
|
+
let worldServer;
|
|
146
|
+
try {
|
|
147
|
+
worldServer = await (0, staticServe_1.startStaticServer)(distDir, {}, '', { port: worldPort });
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
throw listenError(err, worldPort);
|
|
151
|
+
}
|
|
152
|
+
const worldOrigin = `http://127.0.0.1:${worldServer.port}`;
|
|
153
|
+
const products = seed?.seed ?? null;
|
|
154
|
+
const route = (req, res) => {
|
|
155
|
+
const path = (req.url ?? '/').split('?')[0];
|
|
156
|
+
if (req.method === 'GET' && (path === '/' || path === '/index.html')) {
|
|
157
|
+
text(res, 'text/html; charset=utf-8', shellPage_1.DEV_SHELL_PAGE_HTML);
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
if (req.method === 'GET' && path === '/dev/shell.js') {
|
|
161
|
+
text(res, 'text/javascript; charset=utf-8', shellPage_1.DEV_SHELL_PAGE_JS);
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
if (req.method === 'GET' && path === '/dev/config') {
|
|
165
|
+
json(res, 200, {
|
|
166
|
+
mode: 'simulated',
|
|
167
|
+
worldOrigin,
|
|
168
|
+
world,
|
|
169
|
+
user: selection.identity,
|
|
170
|
+
state: liveState,
|
|
171
|
+
products,
|
|
172
|
+
// For the page's fresh-player path: a null state still needs the registry minted into it.
|
|
173
|
+
achievements: achievementsSeed?.achievements ?? null,
|
|
174
|
+
stateFile: (0, state_1.devStatePath)(worldDir),
|
|
175
|
+
flags: { reset: Boolean(options.reset), fresh: Boolean(options.fresh) },
|
|
176
|
+
});
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
if (path === '/dev/state') {
|
|
180
|
+
if (req.method !== 'POST') {
|
|
181
|
+
json(res, 405, { error: 'POST the serialized dev-shell state here' });
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
readBody(req, STATE_LIMIT_BYTES)
|
|
185
|
+
.then((body) => {
|
|
186
|
+
const parsed = JSON.parse(body);
|
|
187
|
+
liveState = parsed;
|
|
188
|
+
stateFile = (0, state_1.recordDevState)(stateFile, world.slug, parsed);
|
|
189
|
+
(0, state_1.writeDevStateFile)(worldDir, stateFile);
|
|
190
|
+
json(res, 200, { ok: true });
|
|
191
|
+
})
|
|
192
|
+
.catch((err) => {
|
|
193
|
+
json(res, 400, { error: err instanceof Error ? err.message : String(err) });
|
|
194
|
+
});
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
return false;
|
|
198
|
+
};
|
|
199
|
+
let shellServer;
|
|
200
|
+
try {
|
|
201
|
+
shellServer = await (0, staticServe_1.startStaticServer)(sdkDistDir, {}, 'sdk', { port: shellPort, route });
|
|
202
|
+
}
|
|
203
|
+
catch (err) {
|
|
204
|
+
await worldServer.close();
|
|
205
|
+
throw listenError(err, shellPort);
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
shellUrl: `http://127.0.0.1:${shellServer.port}/`,
|
|
209
|
+
worldUrl: `${worldOrigin}/`,
|
|
210
|
+
shellPort: shellServer.port,
|
|
211
|
+
worldPort: worldServer.port,
|
|
212
|
+
distDir,
|
|
213
|
+
world,
|
|
214
|
+
identity: selection.identity,
|
|
215
|
+
stateFile: (0, state_1.devStatePath)(worldDir),
|
|
216
|
+
products: seed ? { path: seed.path, count: seed.count } : null,
|
|
217
|
+
achievements: achievementsSeed
|
|
218
|
+
? { path: achievementsSeed.path, count: achievementsSeed.count, criteriaCount: achievementsSeed.criteriaCount }
|
|
219
|
+
: null,
|
|
220
|
+
gitignore,
|
|
221
|
+
sdkDistDir,
|
|
222
|
+
close: async () => {
|
|
223
|
+
await shellServer.close();
|
|
224
|
+
await worldServer.close();
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
//# sourceMappingURL=devServer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"devServer.js","sourceRoot":"","sources":["../../src/dev/devServer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare const DEV_SHELL_BANNER: string;
|
|
2
|
+
export declare const DEV_SHELL_PAGE_HTML = "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>helix dev \u2014 SIMULATED shell</title>\n<style>\n :root { color-scheme: dark; }\n * { box-sizing: border-box; }\n body { margin: 0; height: 100vh; display: flex; flex-direction: column;\n font: 13px/1.5 ui-sans-serif, system-ui, sans-serif; background: #0d0f13; color: #e6e8ec; }\n .banner { padding: 8px 14px; background: #7a4b00; color: #fff5e0; font-weight: 600; }\n .banner code { background: rgba(0,0,0,.3); padding: 1px 5px; border-radius: 3px; }\n main { flex: 1; display: flex; min-height: 0; }\n .stage { flex: 1; min-width: 0; background: #000; position: relative; }\n iframe { width: 100%; height: 100%; border: 0; display: block; }\n #toast { position: absolute; left: 50%; top: 18px; transform: translateX(-50%); max-width: 80%;\n background: #16241a; color: #c9f0c9; border: 1px solid #3f7a3f; border-radius: 6px;\n padding: 8px 14px; font-weight: 600; opacity: 0; pointer-events: none; transition: opacity .25s; }\n #toast.show { opacity: 1; }\n #confirm { position: absolute; inset: 0; display: none; align-items: center; justify-content: center;\n background: rgba(0,0,0,.55); }\n #confirm.show { display: flex; }\n .confirm-card { background: #161a21; border: 1px solid #3a4152; border-radius: 10px; padding: 18px 20px;\n width: 320px; box-shadow: 0 12px 40px rgba(0,0,0,.5); }\n .confirm-card .tag { color: #ffcf8a; font-size: 11px; letter-spacing: .08em; text-transform: uppercase; }\n .confirm-card h3 { margin: 4px 0 10px; font-size: 15px; }\n .confirm-card .price { color: #b7bdc8; }\n .confirm-card .price b { color: #e6e8ec; }\n .confirm-card .row { display: flex; gap: 8px; justify-content: flex-end; margin-top: 16px; }\n .confirm-card button.buy { border-color: #3f7a3f; color: #c9f0c9; }\n .panel { width: 320px; border-left: 1px solid #23262d; display: flex; flex-direction: column; min-height: 0; }\n .panel h2 { margin: 0; padding: 8px 12px; font-size: 11px; letter-spacing: .08em; text-transform: uppercase;\n color: #8b93a1; border-bottom: 1px solid #23262d; }\n .meta { padding: 8px 12px; color: #8b93a1; border-bottom: 1px solid #23262d; }\n .meta b { color: #e6e8ec; font-weight: 600; }\n .menu { padding: 8px; display: grid; gap: 6px; border-bottom: 1px solid #23262d; }\n button { font: inherit; text-align: left; padding: 6px 9px; border-radius: 5px; cursor: pointer;\n background: #1a1d24; color: #e6e8ec; border: 1px solid #2c3039; }\n button:hover { background: #232732; }\n button.armed { border-color: #b4741a; color: #ffcf8a; }\n #log { flex: 1; margin: 0; padding: 8px 12px; overflow: auto; white-space: pre-wrap; word-break: break-word;\n font: 11px/1.45 ui-monospace, SFMono-Regular, Menlo, monospace; color: #b7bdc8; }\n #log .sim { color: #9fd39f; }\n #log .err { color: #ff9b9b; }\n</style>\n</head>\n<body>\n<div class=\"banner\">SIMULATED shell \u2014 helix dev. Purchases, saves and achievements are simulated locally; nothing here proves a product is registered or that settlement works.</div>\n<main>\n <div class=\"stage\"><iframe id=\"world\" title=\"world\" allow=\"autoplay; microphone; gamepad; fullscreen\"></iframe><div id=\"toast\"></div><div id=\"confirm\"><div class=\"confirm-card\"><div class=\"tag\">Simulated purchase</div><h3 id=\"confirmTitle\"></h3><div class=\"price\" id=\"confirmPrice\"></div><div class=\"row\"><button id=\"confirmCancel\">Cancel</button><button id=\"confirmBuy\" class=\"buy\">Buy</button></div></div></div></div>\n <aside class=\"panel\">\n <h2>helix dev \u2014 simulated</h2>\n <div class=\"meta\" id=\"meta\">loading\u2026</div>\n <h2>Debug menu</h2>\n <div class=\"menu\" id=\"menu\"></div>\n <h2>Log</h2>\n <pre id=\"log\"></pre>\n </aside>\n</main>\n<script type=\"module\" src=\"/dev/shell.js\"></script>\n</body>\n</html>\n";
|
|
3
|
+
export declare const DEV_SHELL_PAGE_JS = "// Served by `helix dev`. Everything here is SIMULATED \u2014 see the banner.\nconst logEl = document.getElementById('log');\nconst menuEl = document.getElementById('menu');\nconst metaEl = document.getElementById('meta');\nconst frame = document.getElementById('world');\nconst toastEl = document.getElementById('toast');\n\nlet toastTimer = null;\nfunction toast(text) {\n toastEl.textContent = text;\n toastEl.className = 'show';\n if (toastTimer) clearTimeout(toastTimer);\n toastTimer = setTimeout(function () { toastEl.className = ''; }, 4000);\n}\n\n// The real shell's purchase confirm, simulated: one dialog at a time, queued in arrival order.\nconst confirmEl = document.getElementById('confirm');\nlet confirmChain = Promise.resolve();\nfunction confirmPurchase(req) {\n const ask = function () {\n return new Promise(function (done) {\n document.getElementById('confirmTitle').textContent = req.title;\n document.getElementById('confirmPrice').innerHTML = req.isFree\n ? 'Free claim · balance stays <b>' + req.balanceLix + ' LIX</b>'\n : '<b>' + req.priceLix + ' LIX</b> · balance ' + req.balanceLix + ' → <b>' + req.balanceAfterLix + '</b>';\n confirmEl.className = 'show';\n const buy = document.getElementById('confirmBuy');\n const cancel = document.getElementById('confirmCancel');\n const settle = function (verdict) {\n confirmEl.className = '';\n buy.onclick = null;\n cancel.onclick = null;\n done(verdict);\n };\n buy.onclick = function () { settle(true); };\n cancel.onclick = function () { settle(false); };\n });\n };\n const result = confirmChain.then(ask);\n confirmChain = result.then(function () {}, function () {});\n return result;\n}\n\nfunction append(text, kind) {\n const line = document.createElement('div');\n if (kind) line.className = kind;\n line.textContent = text;\n logEl.appendChild(line);\n while (logEl.childElementCount > 500) logEl.removeChild(logEl.firstChild);\n logEl.scrollTop = logEl.scrollHeight;\n}\n\nfunction show(value) {\n if (value === undefined) return '';\n try {\n return typeof value === 'string' ? value : JSON.stringify(value);\n } catch (err) {\n return String(value);\n }\n}\n\nfunction button(label, onClick) {\n const el = document.createElement('button');\n el.textContent = label;\n el.addEventListener('click', function () { onClick(el); });\n menuEl.appendChild(el);\n return el;\n}\n\nasync function main() {\n const config = await (await fetch('/dev/config')).json();\n frame.src = config.worldOrigin + '/';\n\n const devShell = await import('/sdk/dev-shell/index.js');\n const prefix = devShell.DEV_SHELL_LOG_PREFIX;\n\n let pending = null;\n let sending = false;\n async function flush() {\n if (sending || pending === null) return;\n sending = true;\n const body = pending;\n pending = null;\n try {\n await fetch('/dev/state', { method: 'POST', headers: { 'content-type': 'application/json' }, body: body });\n } catch (err) {\n append('state was not saved: ' + String(err), 'err');\n }\n sending = false;\n flush();\n }\n\n // A fresh player has no stored state, so the seeded achievement registry is minted into a new one here.\n const freshState = !config.state && config.achievements\n ? devShell.createDevShellState({ identity: config.user, achievements: config.achievements })\n : undefined;\n\n const shell = devShell.createDevShell({\n worldWindow: frame,\n worldOrigin: config.worldOrigin,\n world: config.world,\n user: config.user,\n state: config.state || freshState,\n products: config.products || undefined,\n debug: true,\n onAchievementEarned: function (a) {\n toast('Achievement unlocked (SIMULATED): ' + a.name + (a.points ? ' (+' + a.points + ')' : ''));\n },\n confirmPurchase: confirmPurchase,\n log: function (message, detail) {\n append(detail === undefined ? message : message + ' ' + show(detail), message.indexOf(prefix) === 0 ? 'sim' : null);\n if (detail === undefined) console.log(message);\n else console.log(message, detail);\n },\n onStateChange: function (state) {\n pending = JSON.stringify(state);\n flush();\n },\n });\n window.helixDevShell = shell;\n\n metaEl.innerHTML =\n '<div>world <b>' + config.world.title + '</b> (' + config.world.slug + ')</div>' +\n '<div>player <b>' + config.user.id + '</b></div>' +\n '<div>served from ' + config.worldOrigin + '</div>';\n\n let failMode = 'off';\n let saveFailure = false;\n let signedIn = true;\n\n button('Grant all entitlements', function () { shell.grantAllEntitlements(); });\n button('Grant one entitlement\u2026', function () {\n const ref = window.prompt('Product key or ref to grant');\n if (ref) shell.grantEntitlement(ref);\n });\n button('Remove one entitlement\u2026', function () {\n const ref = window.prompt('Product key or ref to remove');\n if (ref) shell.removeEntitlement(ref);\n });\n button('Print owned entitlements', function () {\n append(prefix + ' entitlements ' + show(shell.getEntitlementsSnapshot()), 'sim');\n });\n const failButton = button('Purchases always fail: off', function (el) {\n failMode = shell.cyclePurchasesAlwaysFail();\n el.textContent = 'Purchases always fail: ' + failMode;\n el.className = failMode === 'off' ? '' : 'armed';\n });\n failButton.textContent = 'Purchases always fail: ' + failMode;\n button('Reset player document', function () { shell.resetPlayerDocument(); });\n button('Force save failure: off', function (el) {\n saveFailure = !saveFailure;\n shell.setForceSaveFailure(saveFailure);\n el.textContent = 'Force save failure: ' + (saveFailure ? 'on' : 'off');\n el.className = saveFailure ? 'armed' : '';\n });\n button('Exhaust consume fence', function () { shell.exhaustConsumeFence(); });\n button('Set wallet\u2026', function () {\n const lix = window.prompt('Simulated LIX balance', '500');\n if (lix !== null && lix !== '') shell.setWallet({ lix: Number(lix) });\n });\n button('Award achievement\u2026', function () {\n const key = window.prompt('Achievement key to award (must be in the local registry)');\n if (key) shell.awardAchievement(key);\n });\n button('Print achievement progress', function () { shell.getAchievementsProgress(); });\n button('Add 5 min playtime', function () {\n shell.addPlaytime(300);\n append(prefix + ' +300s playtime added', 'sim');\n });\n button('Bump analytics event\u2026', function () {\n const name = window.prompt('Analytics event name (e.g. world_entered) \u2014 real events never reach a shell');\n if (name) shell.bumpAnalyticsEvent(name);\n });\n button('Set MP player var\u2026', function () {\n const field = window.prompt('Persistent playerVar (dot-path, e.g. wins)');\n if (!field) return;\n const value = window.prompt('Numeric value for \"' + field + '\"', '1');\n if (value !== null && value !== '') shell.setMpPlayerVar(field, Number(value));\n });\n button('Reset earned achievements', function () { shell.resetAchievements(); });\n button('Seed leaderboard score\u2026', function () {\n const board = window.prompt('Board name (declared boards are room-written live \u2014 this is the room, by hand)', 'main');\n if (!board) return;\n const player = window.prompt('Player name (empty = the simulated player)');\n const score = window.prompt('Score', '100');\n if (score === null || score === '') return;\n shell.seedLeaderboardScore(board, Number(score), player ? { subject: player, displayName: player } : {});\n });\n button('Set data-store doc\u2026', function () {\n const key = window.prompt('Document key (world-global / mp:world:* / reserved keys allowed \u2014 this is the server, by hand)');\n if (!key) return;\n const raw = window.prompt('JSON value for \"' + key + '\"', '{}');\n if (raw === null) return;\n try { shell.seedDocument(key, JSON.parse(raw)); }\n catch (err) { append('not valid JSON: ' + String(err), 'err'); }\n });\n button('Push shell event\u2026', function () {\n const event = window.prompt('Shell event (avatar-changed, equipment-changed, overlay-open, overlay-closed, \u2026)');\n if (event) shell.pushEvent(event);\n });\n button('Signed in: yes', function (el) {\n signedIn = !signedIn;\n shell.setSignedIn(signedIn);\n el.textContent = 'Signed in: ' + (signedIn ? 'yes' : 'no');\n el.className = signedIn ? '' : 'armed';\n });\n button('Print state', function () { append(prefix + ' state ' + show(shell.getState()), 'sim'); });\n button('Reload world', function () { frame.src = config.worldOrigin + '/?t=' + Date.now(); });\n button('Clear log', function () { logEl.textContent = ''; });\n\n // Playtime evidence accrues while the shell is open \u2014 the local stand-in for the server's rollup.\n setInterval(function () { shell.addPlaytime(15); }, 15000);\n\n append(prefix + ' shell ready \u2014 the room lane (DSL rules, consume/save/saveRoom) is NOT simulated; ' +\n 'the debug menu stands in for awardAchievement and the persistent-var flush.', 'sim');\n}\n\nmain().catch(function (err) {\n append('dev shell failed to start: ' + String(err && err.stack ? err.stack : err), 'err');\n});\n";
|