scenri 0.9.0 → 0.9.1
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/CHANGELOG.md +9 -0
- package/dist/{chunk-4NQKRVO2.js → chunk-2IGINCPU.js} +13 -7
- package/dist/{chunk-WGIZJXNE.js → chunk-CMLJGPYC.js} +25 -3
- package/dist/{chunk-QDODTMSN.js → chunk-ECS5LRHB.js} +12 -5
- package/dist/{chunk-HLMR33DT.js → chunk-SDVL5OJF.js} +19 -10
- package/dist/{cli-BVPVS4V5.js → cli-FYWM4HBN.js} +204 -54
- package/dist/index.js +3 -3
- package/dist/install-4W6AARK6.js +4 -0
- package/dist/paths-6ZXRZUHY.js +3 -0
- package/dist/{refresh-7HXPUHLQ.js → refresh-FKQABJOC.js} +26 -12
- package/dist/serve.js +25 -7
- package/launcher/launch.mjs +20 -2
- package/launcher/starting.html +6 -3
- package/package.json +1 -1
- package/dist/install-V6DAA7TT.js +0 -4
- package/dist/paths-36Y73GGY.js +0 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.9.1](https://github.com/tonygorb/Scenri/compare/v0.9.0...v0.9.1) (2026-09-07)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **cli:** the desktop icon on Windows ends in the studio or a sentence, never in silence ([4a5dacb](https://github.com/tonygorb/Scenri/commit/4a5dacbdc1522fa04e506c92354182d0c3f08309))
|
|
9
|
+
* **cli:** the desktop icon on Windows ends in the studio or a sentence, never in silence ([86bdc1e](https://github.com/tonygorb/Scenri/commit/86bdc1e24728d3a706e690c13c08b0adb27714ec))
|
|
10
|
+
* **cli:** the dialog PowerShell keeps windowsHide, because a detached one shows nothing on Windows ([f140c04](https://github.com/tonygorb/Scenri/commit/f140c04b47008359ea4f2e194a08435b9321842a))
|
|
11
|
+
|
|
3
12
|
## [0.9.0](https://github.com/tonygorb/Scenri/compare/v0.8.3...v0.9.0) (2026-09-06)
|
|
4
13
|
|
|
5
14
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { readFileSync, mkdirSync, writeFileSync, renameSync
|
|
2
|
-
import { join } from 'path';
|
|
1
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync, renameSync } from 'fs';
|
|
2
|
+
import { join, win32 } from 'path';
|
|
3
3
|
|
|
4
4
|
// src/desktop/paths.ts
|
|
5
5
|
var LAUNCHER_SCHEMA = 1;
|
|
@@ -32,15 +32,21 @@ function recordedEnv(env) {
|
|
|
32
32
|
return out;
|
|
33
33
|
}
|
|
34
34
|
var KNOWN_FOLDER_DESKTOP = "[Environment]::GetFolderPath('Desktop')";
|
|
35
|
+
var PS_UTF8 = "[Console]::OutputEncoding=[System.Text.Encoding]::UTF8; ";
|
|
36
|
+
function powershellPath(env, exists = existsSync) {
|
|
37
|
+
const root = env.SystemRoot || env.SYSTEMROOT || env.windir || "C:\\Windows";
|
|
38
|
+
const absolute = win32.join(root, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
39
|
+
return exists(absolute) ? absolute : "powershell.exe";
|
|
40
|
+
}
|
|
35
41
|
async function desktopDir(deps) {
|
|
36
42
|
if (deps.env.SCENRI_DESKTOP_DIR) return deps.env.SCENRI_DESKTOP_DIR;
|
|
37
43
|
if (deps.platform === "darwin") return join(deps.homedir, "Desktop");
|
|
38
44
|
if (deps.platform === "win32") {
|
|
39
|
-
const out = await deps.runImpl(
|
|
45
|
+
const out = await deps.runImpl(powershellPath(deps.env), [
|
|
40
46
|
"-NoProfile",
|
|
41
47
|
"-NonInteractive",
|
|
42
48
|
"-Command",
|
|
43
|
-
KNOWN_FOLDER_DESKTOP
|
|
49
|
+
PS_UTF8 + KNOWN_FOLDER_DESKTOP
|
|
44
50
|
]);
|
|
45
51
|
const dir = out.trim();
|
|
46
52
|
return dir || null;
|
|
@@ -50,6 +56,6 @@ async function desktopDir(deps) {
|
|
|
50
56
|
var isSupportedPlatform = (platform) => platform === "darwin" || platform === "win32";
|
|
51
57
|
var launcherInstalled = (homedir) => existsSync(launcherRecordPath(homedir));
|
|
52
58
|
|
|
53
|
-
export { LAUNCHER_SCHEMA, desktopDir, isSupportedPlatform, launcherDir, launcherInstalled, launcherRecordPath, logsDir, readLauncherRecord, recordedEnv, writeLauncherRecord };
|
|
54
|
-
//# sourceMappingURL=chunk-
|
|
55
|
-
//# sourceMappingURL=chunk-
|
|
59
|
+
export { LAUNCHER_SCHEMA, PS_UTF8, desktopDir, isSupportedPlatform, launcherDir, launcherInstalled, launcherRecordPath, logsDir, powershellPath, readLauncherRecord, recordedEnv, writeLauncherRecord };
|
|
60
|
+
//# sourceMappingURL=chunk-2IGINCPU.js.map
|
|
61
|
+
//# sourceMappingURL=chunk-2IGINCPU.js.map
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { realpathSync } from 'fs';
|
|
2
|
+
import { resolve } from 'path';
|
|
3
|
+
|
|
1
4
|
// src/bootError.ts
|
|
2
5
|
var INSTALL_GUIDE = "https://github.com/tonygorb/scenri/blob/main/docs/INSTALL.md";
|
|
3
6
|
var NATIVE_MARKERS = ["NODE_MODULE_VERSION", "Could not locate the bindings file", "ERR_DLOPEN_FAILED"];
|
|
@@ -30,7 +33,26 @@ function bootErrorLines(err) {
|
|
|
30
33
|
"Set SCENRI_DEBUG=1 to see the full error."
|
|
31
34
|
];
|
|
32
35
|
}
|
|
36
|
+
function shouldAdoptRunning(input) {
|
|
37
|
+
if (input.installKind !== "dev" || !input.theirHome) return true;
|
|
38
|
+
return realDir(input.theirHome) === realDir(input.ourHome);
|
|
39
|
+
}
|
|
40
|
+
function realDir(p) {
|
|
41
|
+
try {
|
|
42
|
+
return realpathSync(p);
|
|
43
|
+
} catch {
|
|
44
|
+
return resolve(p);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function anotherScenriLines(port, theirs, ours) {
|
|
48
|
+
return [
|
|
49
|
+
`Port ${port} is held by another Scenri, serving ${theirs}.`,
|
|
50
|
+
`This checkout would serve ${ours}, so it did not start.`,
|
|
51
|
+
"Stop that server, or start this checkout on a different port:",
|
|
52
|
+
` SCENRI_PORT=${port + 1} pnpm dev`
|
|
53
|
+
];
|
|
54
|
+
}
|
|
33
55
|
|
|
34
|
-
export { bootErrorLines, portBusyLines };
|
|
35
|
-
//# sourceMappingURL=chunk-
|
|
36
|
-
//# sourceMappingURL=chunk-
|
|
56
|
+
export { anotherScenriLines, bootErrorLines, portBusyLines, shouldAdoptRunning };
|
|
57
|
+
//# sourceMappingURL=chunk-CMLJGPYC.js.map
|
|
58
|
+
//# sourceMappingURL=chunk-CMLJGPYC.js.map
|
|
@@ -6,7 +6,7 @@ import { dirname, join } from 'path';
|
|
|
6
6
|
async function adoptRunningInstall(deps) {
|
|
7
7
|
if (deps.installKind === "dev") return { adopted: false, reason: "dev" };
|
|
8
8
|
if (deps.installKind === "managed") return { adopted: false, reason: "managed" };
|
|
9
|
-
if (isValidVersionDir(deps.home, deps.pkg, deps.version)) return { adopted: false, reason: "present" };
|
|
9
|
+
if (!deps.force && isValidVersionDir(deps.home, deps.pkg, deps.version)) return { adopted: false, reason: "present" };
|
|
10
10
|
const pkgRoot = dirname(dirname(deps.ownEntry));
|
|
11
11
|
if (!existsSync(deps.ownEntry) || manifestVersion(pkgRoot) !== deps.version) {
|
|
12
12
|
return { adopted: false, reason: "no-source" };
|
|
@@ -23,8 +23,15 @@ async function adoptRunningInstall(deps) {
|
|
|
23
23
|
if (!await (deps.verifyImpl ?? verifyEntry)(copied)) return { adopted: false, reason: "no-source" };
|
|
24
24
|
const target = join(versionsDir(deps.home), deps.version);
|
|
25
25
|
mkdirSync(dirname(target), { recursive: true });
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
const aside = join(stagingDir(deps.home), `replaced-${deps.version}`);
|
|
27
|
+
rmSync(aside, { recursive: true, force: true });
|
|
28
|
+
try {
|
|
29
|
+
if (existsSync(target)) renameSync(target, aside);
|
|
30
|
+
renameSync(work, target);
|
|
31
|
+
} catch {
|
|
32
|
+
return { adopted: false, reason: "busy" };
|
|
33
|
+
}
|
|
34
|
+
rmSync(aside, { recursive: true, force: true });
|
|
28
35
|
} finally {
|
|
29
36
|
rmSync(work, { recursive: true, force: true });
|
|
30
37
|
}
|
|
@@ -58,5 +65,5 @@ function manifestVersion(pkgRoot) {
|
|
|
58
65
|
}
|
|
59
66
|
|
|
60
67
|
export { adoptRunningInstall };
|
|
61
|
-
//# sourceMappingURL=chunk-
|
|
62
|
-
//# sourceMappingURL=chunk-
|
|
68
|
+
//# sourceMappingURL=chunk-ECS5LRHB.js.map
|
|
69
|
+
//# sourceMappingURL=chunk-ECS5LRHB.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { isSupportedPlatform, desktopDir, launcherDir, LAUNCHER_SCHEMA, writeLauncherRecord, recordedEnv, readLauncherRecord } from './chunk-
|
|
2
|
-
import { existsSync, rmSync, mkdirSync, writeFileSync, chmodSync, copyFileSync, renameSync, utimesSync } from 'fs';
|
|
1
|
+
import { powershellPath, isSupportedPlatform, desktopDir, launcherDir, LAUNCHER_SCHEMA, writeLauncherRecord, recordedEnv, readLauncherRecord, PS_UTF8 } from './chunk-2IGINCPU.js';
|
|
2
|
+
import { realpathSync, existsSync, rmSync, mkdirSync, writeFileSync, chmodSync, copyFileSync, renameSync, utimesSync } from 'fs';
|
|
3
3
|
import { join, dirname } from 'path';
|
|
4
4
|
|
|
5
5
|
var MAC_BUNDLE_ID = "co.scenri.desktop-launcher";
|
|
@@ -114,10 +114,10 @@ function writeMacBundle(opts) {
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
// src/desktop/windows.ts
|
|
117
|
-
var POWERSHELL =
|
|
117
|
+
var POWERSHELL = powershellPath(process.env);
|
|
118
118
|
var POWERSHELL_ARGS = ["-NoProfile", "-NonInteractive", "-Command"];
|
|
119
119
|
var CREATE_LNK_COMMAND = "$s = New-Object -ComObject WScript.Shell; $l = $s.CreateShortcut($env:SCENRI_LNK); $l.TargetPath = $env:SCENRI_TARGET; $l.Arguments = $env:SCENRI_ARGS; $l.WorkingDirectory = $env:SCENRI_WORKDIR; $l.IconLocation = $env:SCENRI_ICON; $l.Description = 'Open Scenri'; $l.WindowStyle = 7; $l.Save()";
|
|
120
|
-
var READ_LNK_COMMAND =
|
|
120
|
+
var READ_LNK_COMMAND = `${PS_UTF8}$s = New-Object -ComObject WScript.Shell; $l = $s.CreateShortcut($env:SCENRI_LNK); Write-Output $l.TargetPath; Write-Output $l.Arguments`;
|
|
121
121
|
async function readLnk(runImpl, path) {
|
|
122
122
|
const out = await runImpl(POWERSHELL, [...POWERSHELL_ARGS, READ_LNK_COMMAND], { env: { SCENRI_LNK: path } });
|
|
123
123
|
const [target = "", args = ""] = out.split(/\r?\n/);
|
|
@@ -163,7 +163,8 @@ async function installDesktop(deps) {
|
|
|
163
163
|
if (!ours) return { ok: false, reason: "collision", message: COLLISION };
|
|
164
164
|
}
|
|
165
165
|
const nodeMajor = runningNodeMajor();
|
|
166
|
-
|
|
166
|
+
const nodePath = stableExecPath(deps.execPath, deps.platform);
|
|
167
|
+
writeSupportFiles({ ...deps, execPath: nodePath, nodeMajor }, support);
|
|
167
168
|
try {
|
|
168
169
|
if (darwin) {
|
|
169
170
|
writeMacBundle({
|
|
@@ -175,7 +176,7 @@ async function installDesktop(deps) {
|
|
|
175
176
|
} else {
|
|
176
177
|
await writeLnk(deps.runImpl, {
|
|
177
178
|
path: artifact,
|
|
178
|
-
target:
|
|
179
|
+
target: nodePath,
|
|
179
180
|
args: `"${bootstrap}"`,
|
|
180
181
|
workdir: support,
|
|
181
182
|
icon: `${join(support, "scenri.ico")},0`
|
|
@@ -192,7 +193,7 @@ async function installDesktop(deps) {
|
|
|
192
193
|
schema: LAUNCHER_SCHEMA,
|
|
193
194
|
createdBy: deps.version,
|
|
194
195
|
home: deps.home,
|
|
195
|
-
nodePath
|
|
196
|
+
nodePath,
|
|
196
197
|
nodeMajor,
|
|
197
198
|
env: recordedEnv(deps.env),
|
|
198
199
|
artifact: { kind, path: artifact }
|
|
@@ -200,6 +201,14 @@ async function installDesktop(deps) {
|
|
|
200
201
|
return { ok: true, kind, path: artifact };
|
|
201
202
|
}
|
|
202
203
|
var runningNodeMajor = () => Number(process.versions.node.split(".")[0]);
|
|
204
|
+
function stableExecPath(execPath, platform, realpath = realpathSync) {
|
|
205
|
+
if (platform !== "win32") return execPath;
|
|
206
|
+
try {
|
|
207
|
+
return realpath(execPath);
|
|
208
|
+
} catch {
|
|
209
|
+
return execPath;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
203
212
|
function writeSupportFiles(deps, support) {
|
|
204
213
|
mkdirSync(support, { recursive: true });
|
|
205
214
|
for (const f of SUPPORT_FILES) copyFileSync(join(deps.assetsDir, f), join(support, f));
|
|
@@ -241,6 +250,6 @@ async function desktopStatus(deps) {
|
|
|
241
250
|
return { supported, platform: deps.platform, installed, path, current, record };
|
|
242
251
|
}
|
|
243
252
|
|
|
244
|
-
export { MAC_SCRIPT,
|
|
245
|
-
//# sourceMappingURL=chunk-
|
|
246
|
-
//# sourceMappingURL=chunk-
|
|
253
|
+
export { MAC_SCRIPT, POWERSHELL_ARGS, UNSUPPORTED, desktopStatus, installDesktop, isOurMacBundle, macPlist, removeDesktop, runningNodeMajor, stableExecPath, writeLnk, writeMacBundle, writeSupportFiles };
|
|
254
|
+
//# sourceMappingURL=chunk-SDVL5OJF.js.map
|
|
255
|
+
//# sourceMappingURL=chunk-SDVL5OJF.js.map
|
|
@@ -1,39 +1,107 @@
|
|
|
1
|
-
import { adoptRunningInstall } from './chunk-
|
|
1
|
+
import { adoptRunningInstall } from './chunk-ECS5LRHB.js';
|
|
2
2
|
import { detectInstallKind } from './chunk-PAIAXRAC.js';
|
|
3
3
|
import { readMeta } from './chunk-Y3ZPBPLP.js';
|
|
4
4
|
import { defaultHome, newestStaged, listStaged, compareSemver, entryOf } from './chunk-4MAFHYAD.js';
|
|
5
|
-
import { removeDesktop, installDesktop, POWERSHELL_ARGS
|
|
6
|
-
import { launcherDir, logsDir } from './chunk-
|
|
5
|
+
import { removeDesktop, installDesktop, POWERSHELL_ARGS } from './chunk-SDVL5OJF.js';
|
|
6
|
+
import { launcherDir, logsDir, powershellPath } from './chunk-2IGINCPU.js';
|
|
7
7
|
import { execFile, spawn } from 'child_process';
|
|
8
|
-
import { existsSync, closeSync,
|
|
8
|
+
import { existsSync, closeSync, readFileSync, writeFileSync, mkdirSync, openSync, rmSync, statSync, renameSync, appendFileSync } from 'fs';
|
|
9
9
|
import { homedir } from 'os';
|
|
10
|
-
import { join, dirname, delimiter } from 'path';
|
|
11
|
-
import {
|
|
10
|
+
import { join, dirname, delimiter, win32 } from 'path';
|
|
11
|
+
import { createServer } from 'http';
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
var HELPER_TIMEOUT_MS = 1e4;
|
|
14
|
+
async function openInBrowser(url, platform, env, impl = {}) {
|
|
15
|
+
const timeoutMs = impl.timeoutMs ?? HELPER_TIMEOUT_MS;
|
|
14
16
|
if (platform === "win32") {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
const spawnImpl = impl.spawn ?? spawn;
|
|
18
|
+
const root = env.SystemRoot || env.SYSTEMROOT || env.windir || "C:\\Windows";
|
|
19
|
+
const helpers = [
|
|
20
|
+
{
|
|
21
|
+
cmd: powershellPath(env),
|
|
22
|
+
args: [...POWERSHELL_ARGS, "Start-Process $env:SCENRI_URL"],
|
|
23
|
+
env: { ...env, SCENRI_URL: url },
|
|
24
|
+
ok: (code) => code === 0
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
cmd: win32.join(root, "System32", "rundll32.exe"),
|
|
28
|
+
args: ["url.dll,FileProtocolHandler", url],
|
|
29
|
+
ok: (c) => c === 0
|
|
30
|
+
},
|
|
31
|
+
// explorer.exe answers 1 even when it did the job; only a failure to start counts.
|
|
32
|
+
{ cmd: win32.join(root, "explorer.exe"), args: [url], ok: (code) => code !== null }
|
|
33
|
+
];
|
|
34
|
+
const reasons = [];
|
|
35
|
+
for (const helper of helpers) {
|
|
36
|
+
const result = await runHelper(spawnImpl, helper, env, timeoutMs);
|
|
37
|
+
if (result.ok) return;
|
|
38
|
+
reasons.push(`${win32.basename(helper.cmd)}: ${result.reason}`);
|
|
39
|
+
}
|
|
40
|
+
throw new Error(`no helper could open the browser (${reasons.join("; ")})`);
|
|
41
|
+
}
|
|
42
|
+
const openImpl = impl.open ?? (async (u) => await (await import('open')).default(u));
|
|
43
|
+
const child = await openImpl(url);
|
|
44
|
+
child.ref?.();
|
|
45
|
+
await new Promise((resolve, reject) => {
|
|
46
|
+
let done = false;
|
|
47
|
+
const finish = (err) => {
|
|
48
|
+
if (done) return;
|
|
49
|
+
done = true;
|
|
50
|
+
clearTimeout(timer);
|
|
51
|
+
err ? reject(err) : resolve();
|
|
52
|
+
};
|
|
53
|
+
const timer = setTimeout(() => finish(), timeoutMs);
|
|
54
|
+
child.once("error", (err) => finish(err));
|
|
55
|
+
child.once(
|
|
56
|
+
"close",
|
|
57
|
+
(code) => finish(code === 0 || code === null ? void 0 : new Error(`the opener exited with ${code}`))
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function runHelper(spawnImpl, helper, env, timeoutMs) {
|
|
62
|
+
return new Promise((resolve) => {
|
|
63
|
+
let done = false;
|
|
64
|
+
let stderr = "";
|
|
65
|
+
const finish = (result) => {
|
|
66
|
+
if (done) return;
|
|
67
|
+
done = true;
|
|
68
|
+
clearTimeout(timer);
|
|
69
|
+
resolve(result);
|
|
70
|
+
};
|
|
71
|
+
let child;
|
|
72
|
+
try {
|
|
73
|
+
child = spawnImpl(helper.cmd, helper.args, {
|
|
74
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
18
75
|
windowsHide: true,
|
|
19
|
-
|
|
20
|
-
env: { ...env, SCENRI_URL: url }
|
|
76
|
+
env: helper.env ?? env
|
|
21
77
|
});
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
78
|
+
} catch (err) {
|
|
79
|
+
return finish({ ok: false, reason: err instanceof Error ? err.message : String(err) });
|
|
80
|
+
}
|
|
81
|
+
const timer = setTimeout(() => {
|
|
82
|
+
try {
|
|
83
|
+
child.kill();
|
|
84
|
+
} catch {
|
|
85
|
+
}
|
|
86
|
+
finish({ ok: false, reason: `no answer within ${Math.round(timeoutMs / 1e3)} s` });
|
|
87
|
+
}, timeoutMs);
|
|
88
|
+
child.stderr?.on("data", (d) => {
|
|
89
|
+
stderr += String(d);
|
|
25
90
|
});
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
91
|
+
child.once("error", (err) => finish({ ok: false, reason: err.message }));
|
|
92
|
+
child.once("close", (code) => {
|
|
93
|
+
if (helper.ok(code)) return finish({ ok: true });
|
|
94
|
+
const detail = stderr.trim().split(/\r?\n/)[0];
|
|
95
|
+
finish({ ok: false, reason: `exit ${code}${detail ? `: ${detail}` : ""}` });
|
|
96
|
+
});
|
|
97
|
+
});
|
|
30
98
|
}
|
|
31
99
|
var MESSAGE_BOX = "Add-Type -AssemblyName System.Windows.Forms | Out-Null; [System.Windows.Forms.MessageBox]::Show($env:SCENRI_MESSAGE, 'Scenri') | Out-Null";
|
|
32
|
-
function showDialog(platform, message, env) {
|
|
100
|
+
function showDialog(platform, message, env, spawnImpl = spawn) {
|
|
33
101
|
if (env.SCENRI_NO_DIALOG === "1") return Promise.resolve();
|
|
34
102
|
let cmd;
|
|
35
103
|
let args;
|
|
36
|
-
let
|
|
104
|
+
let opts;
|
|
37
105
|
if (platform === "darwin") {
|
|
38
106
|
cmd = "/usr/bin/osascript";
|
|
39
107
|
args = [
|
|
@@ -46,10 +114,11 @@ function showDialog(platform, message, env) {
|
|
|
46
114
|
"--",
|
|
47
115
|
message
|
|
48
116
|
];
|
|
117
|
+
opts = { stdio: "ignore" };
|
|
49
118
|
} else if (platform === "win32") {
|
|
50
|
-
cmd =
|
|
119
|
+
cmd = powershellPath(env);
|
|
51
120
|
args = [...POWERSHELL_ARGS, MESSAGE_BOX];
|
|
52
|
-
|
|
121
|
+
opts = { stdio: "ignore", windowsHide: true, env: { ...env, SCENRI_MESSAGE: message } };
|
|
53
122
|
} else {
|
|
54
123
|
console.error(`
|
|
55
124
|
${message}
|
|
@@ -58,7 +127,7 @@ function showDialog(platform, message, env) {
|
|
|
58
127
|
}
|
|
59
128
|
return new Promise((resolve) => {
|
|
60
129
|
try {
|
|
61
|
-
const child =
|
|
130
|
+
const child = spawnImpl(cmd, args, opts);
|
|
62
131
|
child.once("error", () => resolve());
|
|
63
132
|
child.once("exit", () => resolve());
|
|
64
133
|
} catch {
|
|
@@ -88,6 +157,8 @@ function openLogFd(path, opts = {}) {
|
|
|
88
157
|
return openSync(path, "a");
|
|
89
158
|
}
|
|
90
159
|
var LOCK_FRESH_MS = 6e4;
|
|
160
|
+
var READY_TIMEOUT_MS = 18e4;
|
|
161
|
+
var PAGE_FETCH_GRACE_MS = 15e3;
|
|
91
162
|
var QUICK_DEATH_MS = 1e4;
|
|
92
163
|
var NATIVE_MARKERS = ["NODE_MODULE_VERSION", "Could not locate the bindings file", "ERR_DLOPEN_FAILED"];
|
|
93
164
|
async function showBrowser(deps, url) {
|
|
@@ -104,8 +175,17 @@ async function openScenri(deps) {
|
|
|
104
175
|
const port = Number(deps.env.SCENRI_PORT || 4747);
|
|
105
176
|
const url = `http://127.0.0.1:${port}/`;
|
|
106
177
|
const noOpen = deps.env.SCENRI_NO_OPEN === "1";
|
|
107
|
-
deps.log(
|
|
178
|
+
deps.log(
|
|
179
|
+
`open: invoked by ${deps.version} (${deps.execPath}, node ${process.version}, ${process.platform}, cwd ${process.cwd()}) for port ${port}`
|
|
180
|
+
);
|
|
181
|
+
let page = null;
|
|
108
182
|
if (!takeLock(deps)) {
|
|
183
|
+
const running = await deps.probe(`${url}api/version`, 2e3);
|
|
184
|
+
if (running?.name === deps.pkg) {
|
|
185
|
+
deps.log("open: another launch is in progress and Scenri already answers, opening the browser");
|
|
186
|
+
if (!noOpen) await showBrowser(deps, url);
|
|
187
|
+
return 0;
|
|
188
|
+
}
|
|
109
189
|
deps.log("open: another launch is in progress, stepping aside");
|
|
110
190
|
return 0;
|
|
111
191
|
}
|
|
@@ -117,46 +197,57 @@ async function openScenri(deps) {
|
|
|
117
197
|
return 0;
|
|
118
198
|
}
|
|
119
199
|
const fd = openLogFd(deps.serverLogPath);
|
|
120
|
-
const child = deps.spawnImpl(deps.execPath, [deps.ownEntry], {
|
|
121
|
-
detached: true,
|
|
122
|
-
stdio: ["ignore", fd, fd],
|
|
123
|
-
windowsHide: true,
|
|
124
|
-
env: {
|
|
125
|
-
...deps.env,
|
|
126
|
-
SCENRI_NO_OPEN: "1",
|
|
127
|
-
SCENRI_HEADLESS: "1",
|
|
128
|
-
// npm sits beside node for nvm, fnm, Volta, Homebrew and the installer,
|
|
129
|
-
// and a Finder or Explorer PATH has none of them: without this the
|
|
130
|
-
// server boots but one-click updates report no npm.
|
|
131
|
-
PATH: [dirname(deps.execPath), deps.env.PATH].filter(Boolean).join(delimiter)
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
child.unref();
|
|
135
|
-
closeSync(fd);
|
|
136
200
|
const started = deps.now();
|
|
137
201
|
let exit = null;
|
|
202
|
+
let spawnError = null;
|
|
203
|
+
let child;
|
|
204
|
+
try {
|
|
205
|
+
child = deps.spawnImpl(deps.execPath, [deps.ownEntry], {
|
|
206
|
+
detached: true,
|
|
207
|
+
stdio: ["ignore", fd, fd],
|
|
208
|
+
windowsHide: true,
|
|
209
|
+
env: {
|
|
210
|
+
...deps.env,
|
|
211
|
+
SCENRI_NO_OPEN: "1",
|
|
212
|
+
SCENRI_HEADLESS: "1",
|
|
213
|
+
// npm sits beside node for nvm, fnm, Volta, Homebrew and the installer,
|
|
214
|
+
// and a Finder or Explorer PATH has none of them: without this the
|
|
215
|
+
// server boots but one-click updates report no npm.
|
|
216
|
+
PATH: [dirname(deps.execPath), deps.env.PATH].filter(Boolean).join(delimiter)
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
} catch (err) {
|
|
220
|
+
closeSync(fd);
|
|
221
|
+
return await couldNotStart(deps, err);
|
|
222
|
+
}
|
|
223
|
+
child.unref();
|
|
224
|
+
closeSync(fd);
|
|
225
|
+
child.on("error", (err) => {
|
|
226
|
+
spawnError = err;
|
|
227
|
+
exit = { code: null };
|
|
228
|
+
});
|
|
138
229
|
child.on("exit", (code) => {
|
|
139
230
|
exit = { code };
|
|
140
231
|
});
|
|
141
232
|
deps.log(`open: started the supervisor, pid ${child.pid ?? "unknown"}, log ${deps.serverLogPath}`);
|
|
142
233
|
let shown = false;
|
|
143
|
-
if (!noOpen && deps.startingTemplate && existsSync(deps.startingTemplate)) {
|
|
234
|
+
if (!noOpen && deps.startingTemplate && existsSync(deps.startingTemplate) && deps.startingServer) {
|
|
144
235
|
try {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
await deps.openBrowser(
|
|
236
|
+
page = await deps.startingServer(renderStartingPage(readFileSync(deps.startingTemplate, "utf8"), url));
|
|
237
|
+
deps.log(`open: serving the starting page at ${page.url}`);
|
|
238
|
+
await deps.openBrowser(page.url);
|
|
148
239
|
shown = true;
|
|
149
240
|
deps.log("open: showing the starting page");
|
|
150
241
|
} catch (err) {
|
|
151
242
|
deps.log(`open: could not show the starting page (${err instanceof Error ? err.message : String(err)})`);
|
|
152
243
|
}
|
|
153
244
|
}
|
|
154
|
-
const deadline = started + (deps.readyTimeoutMs ??
|
|
245
|
+
const deadline = started + (deps.readyTimeoutMs ?? READY_TIMEOUT_MS);
|
|
155
246
|
for (; ; ) {
|
|
156
247
|
const info = await deps.probe(`${url}api/version`, 1e3);
|
|
157
248
|
if (info?.name === deps.pkg) {
|
|
158
249
|
deps.log(`open: ready in ${deps.now() - started}ms`);
|
|
159
|
-
if (!noOpen
|
|
250
|
+
if (!noOpen) await handOver(deps, url, shown ? page : null);
|
|
160
251
|
return 0;
|
|
161
252
|
}
|
|
162
253
|
if (exit) break;
|
|
@@ -168,6 +259,7 @@ async function openScenri(deps) {
|
|
|
168
259
|
}
|
|
169
260
|
await deps.sleep(deps.pollMs ?? 250);
|
|
170
261
|
}
|
|
262
|
+
if (spawnError) return await couldNotStart(deps, spawnError);
|
|
171
263
|
const lived = deps.now() - started;
|
|
172
264
|
deps.log(`open: the server exited with ${exit.code} after ${lived}ms`);
|
|
173
265
|
const previous = deps.previousEntries[0];
|
|
@@ -176,6 +268,9 @@ async function openScenri(deps) {
|
|
|
176
268
|
releaseLock(deps.lockPath);
|
|
177
269
|
const fallback = deps.spawnImpl(deps.execPath, [previous, "open"], {
|
|
178
270
|
stdio: "inherit",
|
|
271
|
+
// This process has no console; without the hint Windows would give
|
|
272
|
+
// the fallback a visible one.
|
|
273
|
+
windowsHide: true,
|
|
179
274
|
env: {
|
|
180
275
|
...deps.env,
|
|
181
276
|
SCENRI_DESKTOP_FALLBACK: deps.version,
|
|
@@ -183,16 +278,34 @@ async function openScenri(deps) {
|
|
|
183
278
|
...shown ? { SCENRI_NO_OPEN: "1" } : {}
|
|
184
279
|
}
|
|
185
280
|
});
|
|
186
|
-
return await new Promise((resolve) =>
|
|
281
|
+
return await new Promise((resolve) => {
|
|
282
|
+
fallback.on("error", (err) => {
|
|
283
|
+
deps.log(`open: could not start the fallback (${err.message})`);
|
|
284
|
+
resolve(1);
|
|
285
|
+
});
|
|
286
|
+
fallback.on("exit", (code) => resolve(code ?? 1));
|
|
287
|
+
});
|
|
187
288
|
}
|
|
188
289
|
const message = explain(deps.readLogTail(), port);
|
|
189
290
|
deps.log(`open: dialog: ${message}`);
|
|
190
291
|
await deps.showDialog(message);
|
|
191
292
|
return 1;
|
|
192
293
|
} finally {
|
|
294
|
+
page?.close();
|
|
193
295
|
releaseLock(deps.lockPath);
|
|
194
296
|
}
|
|
195
297
|
}
|
|
298
|
+
async function handOver(deps, url, page) {
|
|
299
|
+
if (!page) return showBrowser(deps, url);
|
|
300
|
+
const until = deps.now() + PAGE_FETCH_GRACE_MS;
|
|
301
|
+
while (!page.wasFetched() && deps.now() < until) await deps.sleep(deps.pollMs ?? 250);
|
|
302
|
+
if (page.wasFetched()) {
|
|
303
|
+
deps.log(`open: the browser fetched the starting page (${page.userAgent() ?? "unknown browser"})`);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
deps.log("open: the browser never fetched the starting page, opening the studio directly");
|
|
307
|
+
await showBrowser(deps, url);
|
|
308
|
+
}
|
|
196
309
|
var STUDIO_URL = /^http:\/\/127\.0\.0\.1:\d{2,5}\/$/;
|
|
197
310
|
var STUDIO_META = '<meta name="scenri-studio" content="">';
|
|
198
311
|
function renderStartingPage(template, url) {
|
|
@@ -209,11 +322,16 @@ function explain(tail2, port) {
|
|
|
209
322
|
if (tail2.includes("a native component failed to load") || NATIVE_MARKERS.some((m) => tail2.includes(m))) {
|
|
210
323
|
return "Node.js changed since Scenri was installed. Open Terminal and run: npx scenri@latest";
|
|
211
324
|
}
|
|
212
|
-
if (NATIVE_MARKERS.some((m) => tail2.includes(m))) {
|
|
213
|
-
return "Scenri\u2019s files do not match this Node.js. Open Terminal and run: npx scenri";
|
|
214
|
-
}
|
|
215
325
|
return "Scenri could not start. Open Terminal and run npx scenri to see why.";
|
|
216
326
|
}
|
|
327
|
+
async function couldNotStart(deps, err) {
|
|
328
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
329
|
+
deps.log(`open: could not start the supervisor (${reason})`);
|
|
330
|
+
const message = `Scenri could not start (${reason}). Open Terminal and run npx scenri to see why.`;
|
|
331
|
+
deps.log(`open: dialog: ${message}`);
|
|
332
|
+
await deps.showDialog(message);
|
|
333
|
+
return 1;
|
|
334
|
+
}
|
|
217
335
|
function takeLock(deps) {
|
|
218
336
|
try {
|
|
219
337
|
const held = JSON.parse(readFileSync(deps.lockPath, "utf8"));
|
|
@@ -234,6 +352,38 @@ function releaseLock(path) {
|
|
|
234
352
|
} catch {
|
|
235
353
|
}
|
|
236
354
|
}
|
|
355
|
+
function serveStartingPage(html) {
|
|
356
|
+
let fetched = false;
|
|
357
|
+
let agent = null;
|
|
358
|
+
const server = createServer((req, res) => {
|
|
359
|
+
if (req.method !== "GET" && req.method !== "HEAD" || req.url !== "/") {
|
|
360
|
+
res.statusCode = 404;
|
|
361
|
+
res.end();
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
fetched = true;
|
|
365
|
+
const ua = req.headers["user-agent"];
|
|
366
|
+
agent = typeof ua === "string" ? ua : null;
|
|
367
|
+
res.setHeader("content-type", "text/html; charset=utf-8");
|
|
368
|
+
res.setHeader("cache-control", "no-store");
|
|
369
|
+
res.end(html);
|
|
370
|
+
});
|
|
371
|
+
return new Promise((resolve, reject) => {
|
|
372
|
+
server.once("error", reject);
|
|
373
|
+
server.listen(0, "127.0.0.1", () => {
|
|
374
|
+
const { port } = server.address();
|
|
375
|
+
resolve({
|
|
376
|
+
url: `http://127.0.0.1:${port}/`,
|
|
377
|
+
wasFetched: () => fetched,
|
|
378
|
+
userAgent: () => agent,
|
|
379
|
+
close: () => {
|
|
380
|
+
server.close();
|
|
381
|
+
server.closeAllConnections();
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
}
|
|
237
387
|
|
|
238
388
|
// src/desktop/cli.ts
|
|
239
389
|
var assetsDirFor = (ownEntry) => join(dirname(ownEntry), "..", "launcher");
|
|
@@ -348,7 +498,7 @@ async function runOpenCommand(ownEntry) {
|
|
|
348
498
|
serverLogPath,
|
|
349
499
|
readLogTail: () => tail(serverLogPath),
|
|
350
500
|
startingTemplate: join(assetsDirFor(ownEntry), "starting.html"),
|
|
351
|
-
|
|
501
|
+
startingServer: serveStartingPage,
|
|
352
502
|
previousEntries: previous
|
|
353
503
|
});
|
|
354
504
|
}
|
|
@@ -372,5 +522,5 @@ function tail(path, bytes = 4096) {
|
|
|
372
522
|
var supportInstalled = () => existsSync(join(launcherDir(homedir()), "launcher.json"));
|
|
373
523
|
|
|
374
524
|
export { addToDesktop, assetsDirFor, installDeps, runDesktopCommand, runExecFile, runOpenCommand, supportInstalled };
|
|
375
|
-
//# sourceMappingURL=cli-
|
|
376
|
-
//# sourceMappingURL=cli-
|
|
525
|
+
//# sourceMappingURL=cli-FYWM4HBN.js.map
|
|
526
|
+
//# sourceMappingURL=cli-FYWM4HBN.js.map
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { bootErrorLines } from './chunk-
|
|
2
|
+
import { bootErrorLines } from './chunk-CMLJGPYC.js';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
|
|
5
5
|
// src/args.ts
|
|
@@ -86,12 +86,12 @@ try {
|
|
|
86
86
|
break;
|
|
87
87
|
}
|
|
88
88
|
case "desktop": {
|
|
89
|
-
const { runDesktopCommand } = await import('./cli-
|
|
89
|
+
const { runDesktopCommand } = await import('./cli-FYWM4HBN.js');
|
|
90
90
|
process.exit(await runDesktopCommand(command, fileURLToPath(import.meta.url)));
|
|
91
91
|
break;
|
|
92
92
|
}
|
|
93
93
|
case "open": {
|
|
94
|
-
const { runOpenCommand } = await import('./cli-
|
|
94
|
+
const { runOpenCommand } = await import('./cli-FYWM4HBN.js');
|
|
95
95
|
process.exit(await runOpenCommand(fileURLToPath(import.meta.url)));
|
|
96
96
|
break;
|
|
97
97
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { UNSUPPORTED, desktopStatus, installDesktop, removeDesktop, runningNodeMajor, stableExecPath, writeSupportFiles } from './chunk-SDVL5OJF.js';
|
|
2
|
+
import './chunk-2IGINCPU.js';
|
|
3
|
+
//# sourceMappingURL=install-4W6AARK6.js.map
|
|
4
|
+
//# sourceMappingURL=install-4W6AARK6.js.map
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { LAUNCHER_SCHEMA, PS_UTF8, desktopDir, isSupportedPlatform, launcherDir, launcherInstalled, launcherRecordPath, logsDir, powershellPath, readLauncherRecord, recordedEnv, writeLauncherRecord } from './chunk-2IGINCPU.js';
|
|
2
|
+
//# sourceMappingURL=paths-6ZXRZUHY.js.map
|
|
3
|
+
//# sourceMappingURL=paths-6ZXRZUHY.js.map
|
|
@@ -1,37 +1,48 @@
|
|
|
1
|
-
import { adoptRunningInstall } from './chunk-
|
|
1
|
+
import { adoptRunningInstall } from './chunk-ECS5LRHB.js';
|
|
2
2
|
import { newestStaged, compareSemver } from './chunk-4MAFHYAD.js';
|
|
3
|
-
import {
|
|
4
|
-
import { readLauncherRecord, launcherDir, LAUNCHER_SCHEMA, writeLauncherRecord } from './chunk-
|
|
3
|
+
import { runningNodeMajor, stableExecPath, isOurMacBundle, MAC_SCRIPT, macPlist, writeSupportFiles, writeMacBundle, writeLnk } from './chunk-SDVL5OJF.js';
|
|
4
|
+
import { readLauncherRecord, launcherDir, LAUNCHER_SCHEMA, writeLauncherRecord } from './chunk-2IGINCPU.js';
|
|
5
5
|
import { existsSync, readFileSync } from 'fs';
|
|
6
6
|
import { join } from 'path';
|
|
7
7
|
|
|
8
8
|
var ASSETS = ["launch.mjs", "Scenri.icns", "scenri.ico"];
|
|
9
9
|
async function refreshLauncher(deps) {
|
|
10
10
|
if (deps.env.SCENRI_NO_DESKTOP === "1") return {};
|
|
11
|
+
if (deps.installKind === "dev") return {};
|
|
11
12
|
const record = readLauncherRecord(deps.homedir);
|
|
12
13
|
if (!record) return {};
|
|
13
14
|
const out = {};
|
|
15
|
+
const running = runningNodeMajor();
|
|
16
|
+
const ownNode = stableExecPath(deps.execPath, deps.platform);
|
|
17
|
+
const nodeGone = !existsSync(record.nodePath);
|
|
18
|
+
const majorChanged = (nodeGone || samePath(record.nodePath, ownNode, deps.platform) || samePath(record.nodePath, deps.execPath, deps.platform)) && record.nodeMajor !== void 0 && record.nodeMajor !== running;
|
|
19
|
+
let rebuilt = false;
|
|
14
20
|
if (deps.installKind === "npx" || deps.installKind === "global") {
|
|
15
21
|
const newest = newestStaged(deps.home, deps.pkg);
|
|
16
|
-
|
|
22
|
+
const newer = !newest || compareSemver(deps.version, newest) > 0;
|
|
23
|
+
const rebuild = majorChanged && newest !== null && compareSemver(deps.version, newest) >= 0;
|
|
24
|
+
if (newer || rebuild) {
|
|
17
25
|
const r = await adoptRunningInstall({
|
|
18
26
|
home: deps.home,
|
|
19
27
|
pkg: deps.pkg,
|
|
20
28
|
version: deps.version,
|
|
21
29
|
ownEntry: deps.ownEntry,
|
|
22
30
|
installKind: deps.installKind,
|
|
23
|
-
verifyImpl: deps.verifyImpl
|
|
31
|
+
verifyImpl: deps.verifyImpl,
|
|
32
|
+
force: rebuild
|
|
24
33
|
});
|
|
25
|
-
if (r.adopted)
|
|
34
|
+
if (r.adopted) {
|
|
35
|
+
out.adopted = true;
|
|
36
|
+
rebuilt = rebuild;
|
|
37
|
+
}
|
|
26
38
|
}
|
|
27
39
|
}
|
|
28
40
|
const support = launcherDir(deps.homedir);
|
|
29
41
|
const artifact = record.artifact.path;
|
|
30
42
|
const ours = existsSync(artifact) && (record.artifact.kind === "macos-app" ? isOurMacBundle(artifact) : true);
|
|
31
43
|
const schemaStale = record.schema !== LAUNCHER_SCHEMA;
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
const nodeMajor = nodeGone ? runningNodeMajor() : record.nodeMajor ?? runningNodeMajor();
|
|
44
|
+
const nodePath = nodeGone ? ownNode : record.nodePath;
|
|
45
|
+
const nodeMajor = nodeGone || rebuilt ? running : record.nodeMajor ?? running;
|
|
35
46
|
const nodeFilesStale = deps.platform === "darwin" && (readText(join(support, "node-path")) !== `${nodePath}
|
|
36
47
|
` || readText(join(support, "node-major")) !== `${nodeMajor}
|
|
37
48
|
`);
|
|
@@ -59,7 +70,7 @@ async function refreshLauncher(deps) {
|
|
|
59
70
|
icon: `${join(support, "scenri.ico")},0`
|
|
60
71
|
});
|
|
61
72
|
}
|
|
62
|
-
if (schemaStale || supportStale || nodeGone || iconStale || bundleStale) {
|
|
73
|
+
if (schemaStale || supportStale || nodeGone || iconStale || bundleStale || rebuilt) {
|
|
63
74
|
writeLauncherRecord(deps.homedir, {
|
|
64
75
|
...record,
|
|
65
76
|
schema: LAUNCHER_SCHEMA,
|
|
@@ -71,6 +82,9 @@ async function refreshLauncher(deps) {
|
|
|
71
82
|
}
|
|
72
83
|
return out;
|
|
73
84
|
}
|
|
85
|
+
function samePath(a, b, platform) {
|
|
86
|
+
return platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
87
|
+
}
|
|
74
88
|
function readText(path) {
|
|
75
89
|
try {
|
|
76
90
|
return readFileSync(path, "utf8");
|
|
@@ -87,5 +101,5 @@ function sameFile(a, b) {
|
|
|
87
101
|
}
|
|
88
102
|
|
|
89
103
|
export { refreshLauncher };
|
|
90
|
-
//# sourceMappingURL=refresh-
|
|
91
|
-
//# sourceMappingURL=refresh-
|
|
104
|
+
//# sourceMappingURL=refresh-FKQABJOC.js.map
|
|
105
|
+
//# sourceMappingURL=refresh-FKQABJOC.js.map
|
package/dist/serve.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createUpdateChecker, classify, isReleaseTriplet, findNpm, stageVersion } from './chunk-FYQ5BAFA.js';
|
|
2
|
-
import { portBusyLines } from './chunk-
|
|
2
|
+
import { shouldAdoptRunning, anotherScenriLines, portBusyLines } from './chunk-CMLJGPYC.js';
|
|
3
3
|
import { SchemaTooNewError, createCore, BUDGET_EXHAUSTED, SpendCapError, budgetSize, EDIT_REFERENCE_ROLE_DIRECTIVE, REFERENCE_ROLE_DIRECTIVE, ratioLabel, searchTerms, termMatches, SCHEMA_VERSION, ASPECT_TOLERANCE } from './chunk-OJAG3FRX.js';
|
|
4
4
|
import { detectInstallKind } from './chunk-PAIAXRAC.js';
|
|
5
5
|
export { detectInstallKind } from './chunk-PAIAXRAC.js';
|
|
@@ -7789,6 +7789,16 @@ function registerImageRoutes(app, deps) {
|
|
|
7789
7789
|
|
|
7790
7790
|
// src/release/notes.data.ts
|
|
7791
7791
|
var RELEASES = [
|
|
7792
|
+
{
|
|
7793
|
+
version: "0.9.1",
|
|
7794
|
+
date: "2026-09-07",
|
|
7795
|
+
sections: [
|
|
7796
|
+
{
|
|
7797
|
+
heading: "Desktop",
|
|
7798
|
+
body: "The desktop icon on Windows now ends in the studio or in one plain sentence, never in silence. On both systems the Starting page and the studio open in your default browser, a start that fails says why, and after a Node.js upgrade the next terminal start rebuilds the copy the icon runs."
|
|
7799
|
+
}
|
|
7800
|
+
]
|
|
7801
|
+
},
|
|
7792
7802
|
{
|
|
7793
7803
|
version: "0.9.0",
|
|
7794
7804
|
date: "2026-09-06",
|
|
@@ -8739,15 +8749,15 @@ function registerDesktopRoutes(app, deps) {
|
|
|
8739
8749
|
record: null
|
|
8740
8750
|
};
|
|
8741
8751
|
}
|
|
8742
|
-
const { installDeps } = await import('./cli-
|
|
8743
|
-
const { desktopStatus } = await import('./install-
|
|
8752
|
+
const { installDeps } = await import('./cli-FYWM4HBN.js');
|
|
8753
|
+
const { desktopStatus } = await import('./install-4W6AARK6.js');
|
|
8744
8754
|
return desktopStatus(installDeps(runtime.entry));
|
|
8745
8755
|
});
|
|
8746
8756
|
const install = deps.installImpl ?? (async () => {
|
|
8747
8757
|
if (!runtime.entry) {
|
|
8748
8758
|
return { ok: false, reason: "unsupported", message: "Desktop shortcuts are not available on this system yet." };
|
|
8749
8759
|
}
|
|
8750
|
-
const { addToDesktop } = await import('./cli-
|
|
8760
|
+
const { addToDesktop } = await import('./cli-FYWM4HBN.js');
|
|
8751
8761
|
return addToDesktop(runtime.entry);
|
|
8752
8762
|
});
|
|
8753
8763
|
app.get("/api/desktop", async () => {
|
|
@@ -10260,6 +10270,14 @@ async function run() {
|
|
|
10260
10270
|
});
|
|
10261
10271
|
const info = await res.json();
|
|
10262
10272
|
if (info.name === readMeta().name) {
|
|
10273
|
+
if (!shouldAdoptRunning({ installKind, ourHome: core.home, theirHome: info.home })) {
|
|
10274
|
+
console.error("");
|
|
10275
|
+
for (const line of anotherScenriLines(PORT, info.home ?? "another library", core.home)) {
|
|
10276
|
+
console.error(` ${line}`);
|
|
10277
|
+
}
|
|
10278
|
+
console.error("");
|
|
10279
|
+
process.exit(1);
|
|
10280
|
+
}
|
|
10263
10281
|
const url = `http://127.0.0.1:${PORT}`;
|
|
10264
10282
|
console.log(`
|
|
10265
10283
|
Scenri is already running \u2192 ${url}
|
|
@@ -10332,10 +10350,10 @@ async function run() {
|
|
|
10332
10350
|
}
|
|
10333
10351
|
}
|
|
10334
10352
|
const ownEntry = fileURLToPath(import.meta.url);
|
|
10335
|
-
const { addToDesktop, installDeps } = await import('./cli-
|
|
10336
|
-
const { refreshLauncher } = await import('./refresh-
|
|
10353
|
+
const { addToDesktop, installDeps } = await import('./cli-FYWM4HBN.js');
|
|
10354
|
+
const { refreshLauncher } = await import('./refresh-FKQABJOC.js');
|
|
10337
10355
|
const { askOnTerminal, offerDesktop, shouldOfferDesktop } = await import('./offer-IVTNHIDJ.js');
|
|
10338
|
-
const { launcherInstalled } = await import('./paths-
|
|
10356
|
+
const { launcherInstalled } = await import('./paths-6ZXRZUHY.js');
|
|
10339
10357
|
const meta = readMeta();
|
|
10340
10358
|
void refreshLauncher({ ...installDeps(ownEntry), ownEntry, installKind, pkg: meta.name }).then((r) => {
|
|
10341
10359
|
if (r.adopted)
|
package/launcher/launch.mjs
CHANGED
|
@@ -22,7 +22,9 @@ const record = readJson(join(here, 'launcher.json'));
|
|
|
22
22
|
const home = typeof record?.home === 'string' && record.home ? record.home : join(homedir(), '.scenri');
|
|
23
23
|
const logPath = join(home, 'logs', 'launcher.log');
|
|
24
24
|
|
|
25
|
-
log(
|
|
25
|
+
log(
|
|
26
|
+
`bootstrap v${SCHEMA}: invoked from ${here} by ${process.execPath} (node ${process.version}, ${process.platform}), home ${home}`,
|
|
27
|
+
);
|
|
26
28
|
const version = newestVersion(home);
|
|
27
29
|
if (!version) {
|
|
28
30
|
log(`bootstrap v${SCHEMA}: no valid version under ${join(home, 'app', 'versions')}`);
|
|
@@ -49,6 +51,13 @@ const child = spawn(process.execPath, [entry, 'open'], {
|
|
|
49
51
|
env,
|
|
50
52
|
});
|
|
51
53
|
if (fd !== null) closeSync(fd);
|
|
54
|
+
// A node that cannot be executed arrives as an event, not an exit, and unhandled
|
|
55
|
+
// it would end this process with nothing on screen.
|
|
56
|
+
child.on('error', (err) => {
|
|
57
|
+
log(`bootstrap v${SCHEMA}: could not start ${entry} (${err.message})`);
|
|
58
|
+
dialog(`Scenri could not start (${err.message}). Open a terminal and run: npx scenri`);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
});
|
|
52
61
|
log(`bootstrap v${SCHEMA}: handing off to ${entry} (pid ${child.pid ?? 'unknown'})`);
|
|
53
62
|
if (wait) {
|
|
54
63
|
child.on('exit', (code) => process.exit(code ?? 1));
|
|
@@ -127,8 +136,10 @@ function dialog(message) {
|
|
|
127
136
|
{ stdio: 'ignore' },
|
|
128
137
|
);
|
|
129
138
|
} else if (process.platform === 'win32') {
|
|
139
|
+
// windowsHide keeps the console off screen and the box still shows;
|
|
140
|
+
// probed on a real Windows desktop, where a detached PowerShell shows nothing.
|
|
130
141
|
spawnSync(
|
|
131
|
-
|
|
142
|
+
powershell(),
|
|
132
143
|
[
|
|
133
144
|
'-NoProfile',
|
|
134
145
|
'-NonInteractive',
|
|
@@ -142,3 +153,10 @@ function dialog(message) {
|
|
|
142
153
|
/* the log line above is the record */
|
|
143
154
|
}
|
|
144
155
|
}
|
|
156
|
+
|
|
157
|
+
/** Windows PowerShell 5.1 by its absolute path: a Desktop click has Explorer's PATH, not the terminal's. */
|
|
158
|
+
function powershell() {
|
|
159
|
+
const root = process.env.SystemRoot || process.env.windir || 'C:\\Windows';
|
|
160
|
+
const absolute = join(root, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
|
|
161
|
+
return existsSync(absolute) ? absolute : 'powershell.exe';
|
|
162
|
+
}
|
package/launcher/starting.html
CHANGED
|
@@ -49,9 +49,12 @@
|
|
|
49
49
|
var bar = document.getElementById('bar');
|
|
50
50
|
var deadline = Date.now() + 90000;
|
|
51
51
|
var done = false;
|
|
52
|
+
var failed = false;
|
|
52
53
|
function go() { if (done) return; done = true; location.replace(target); }
|
|
54
|
+
// The fail text is a verdict on the wait, not on the server: a start that
|
|
55
|
+
// finishes late (a first Defender scan of a fresh install) still hands over.
|
|
53
56
|
function fail(message) {
|
|
54
|
-
if (done) return;
|
|
57
|
+
if (done || failed) return; failed = true;
|
|
55
58
|
title.textContent = 'Scenri did not start';
|
|
56
59
|
text.innerHTML = message;
|
|
57
60
|
bar.hidden = true;
|
|
@@ -59,7 +62,7 @@
|
|
|
59
62
|
if (!ok) { fail('This page needs to be opened by Scenri itself. Open a terminal and run: <code>npx scenri</code>'); return; }
|
|
60
63
|
function probe() {
|
|
61
64
|
if (done) return;
|
|
62
|
-
if (Date.now() > deadline)
|
|
65
|
+
if (Date.now() > deadline) fail('Open a terminal and run <code>npx scenri</code> to see why.');
|
|
63
66
|
// An image load needs no CORS and works from a file: URL in every browser;
|
|
64
67
|
// the opaque fetch is the second opinion for browsers that block images
|
|
65
68
|
// from a local page.
|
|
@@ -69,7 +72,7 @@
|
|
|
69
72
|
if (window.fetch) {
|
|
70
73
|
fetch(target + 'api/version', { mode: 'no-cors', cache: 'no-store' }).then(go, () => {});
|
|
71
74
|
}
|
|
72
|
-
setTimeout(probe, 500);
|
|
75
|
+
setTimeout(probe, failed ? 2000 : 500);
|
|
73
76
|
}
|
|
74
77
|
probe();
|
|
75
78
|
})();
|
package/package.json
CHANGED
package/dist/install-V6DAA7TT.js
DELETED
package/dist/paths-36Y73GGY.js
DELETED
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
export { LAUNCHER_SCHEMA, desktopDir, isSupportedPlatform, launcherDir, launcherInstalled, launcherRecordPath, logsDir, readLauncherRecord, recordedEnv, writeLauncherRecord } from './chunk-4NQKRVO2.js';
|
|
2
|
-
//# sourceMappingURL=paths-36Y73GGY.js.map
|
|
3
|
-
//# sourceMappingURL=paths-36Y73GGY.js.map
|