@sparkelf/dsh-plus 0.1.0-rc.30 → 0.1.0-rc.32
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/lib/bin.js
CHANGED
|
@@ -3,7 +3,7 @@ import { r as runApply } from "./apply-e0aMC4B4.js";
|
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
5
5
|
import { createWriteStream, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
6
|
-
import { dirname, join, resolve } from "node:path";
|
|
6
|
+
import { dirname, join, posix, resolve, win32 } from "node:path";
|
|
7
7
|
import { homedir } from "node:os";
|
|
8
8
|
import semver from "semver";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
@@ -204,6 +204,31 @@ async function waitForServer(url, timeoutMilliseconds) {
|
|
|
204
204
|
}
|
|
205
205
|
return false;
|
|
206
206
|
}
|
|
207
|
+
/**
|
|
208
|
+
* Wait until the launcher's log carries the authenticated URL.
|
|
209
|
+
*
|
|
210
|
+
* Readiness and the printed line are separate events: the server answers before the
|
|
211
|
+
* launcher finishes its own startup, so the log is polled here rather than read once.
|
|
212
|
+
*
|
|
213
|
+
* @param logPath - the launcher's log file.
|
|
214
|
+
* @param timeoutMilliseconds - how long to keep polling.
|
|
215
|
+
* @returns the printed URL, or undefined when it never appeared.
|
|
216
|
+
*/
|
|
217
|
+
async function waitForAuthenticatedUrl(logPath, timeoutMilliseconds) {
|
|
218
|
+
const deadline = Date.now() + timeoutMilliseconds;
|
|
219
|
+
for (;;) {
|
|
220
|
+
let text = "";
|
|
221
|
+
try {
|
|
222
|
+
text = readFileSync(logPath, "utf8");
|
|
223
|
+
} catch {}
|
|
224
|
+
const found = /dsh web: (http:\/\/\S+)/u.exec(text);
|
|
225
|
+
if (found?.[1] !== void 0) return found[1];
|
|
226
|
+
if (Date.now() >= deadline) return void 0;
|
|
227
|
+
await new Promise((resolveDelay) => {
|
|
228
|
+
setTimeout(resolveDelay, 250);
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
207
232
|
//#endregion
|
|
208
233
|
//#region lib/types/standalone-profile.js
|
|
209
234
|
/**
|
|
@@ -232,6 +257,27 @@ function resolveHome(env = process.env) {
|
|
|
232
257
|
return configured === void 0 || configured === "" ? join(homedir(), ".dsh") : resolve(configured);
|
|
233
258
|
}
|
|
234
259
|
/**
|
|
260
|
+
* Whether one absolute path is the other or sits beneath it.
|
|
261
|
+
*
|
|
262
|
+
* A path outside the parent has a relative form starting with `..`, which every
|
|
263
|
+
* platform answers the same way. Comparing the text against a literal separator is
|
|
264
|
+
* not: Windows separates with a backslash, so a nested path never matched its own
|
|
265
|
+
* ancestor and every Windows installation reported the distribution as sitting
|
|
266
|
+
* outside its own tree.
|
|
267
|
+
*
|
|
268
|
+
* @param candidate - absolute path to test.
|
|
269
|
+
* @param parent - absolute path that may contain it.
|
|
270
|
+
* @param platform - separating convention, injectable so the Windows answer is
|
|
271
|
+
* testable where the suite runs on a POSIX host.
|
|
272
|
+
* @returns true when the candidate is the parent or inside it.
|
|
273
|
+
*/
|
|
274
|
+
function isWithin(candidate, parent, platform = process.platform) {
|
|
275
|
+
if (candidate === parent) return true;
|
|
276
|
+
const path = platform === "win32" ? win32 : posix;
|
|
277
|
+
const inside = path.relative(parent, candidate);
|
|
278
|
+
return inside !== "" && !inside.startsWith("..") && !inside.startsWith(path.sep + "..");
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
235
281
|
* Resolve the installed distribution directory from one requiring anchor.
|
|
236
282
|
*
|
|
237
283
|
* The anchor must be a path inside the consumer's own tree. Resolving from this
|
|
@@ -250,7 +296,7 @@ function resolveDistributionDirectory(anchor) {
|
|
|
250
296
|
}
|
|
251
297
|
let current = resolve(dirname(anchor));
|
|
252
298
|
for (;;) {
|
|
253
|
-
if (resolved
|
|
299
|
+
if (isWithin(resolved, current)) return resolved;
|
|
254
300
|
const parent = dirname(current);
|
|
255
301
|
if (parent === current) break;
|
|
256
302
|
current = parent;
|
|
@@ -529,12 +575,13 @@ async function startDetached(home, entry, options) {
|
|
|
529
575
|
if (isRunning(pid)) process.kill(pid, "SIGTERM");
|
|
530
576
|
return 1;
|
|
531
577
|
}
|
|
578
|
+
const opened = await waitForAuthenticatedUrl(logPath, READY_TIMEOUT_MILLISECONDS) ?? url;
|
|
532
579
|
writeState(home, {
|
|
533
580
|
pid,
|
|
534
581
|
port,
|
|
535
|
-
url
|
|
582
|
+
url: opened
|
|
536
583
|
});
|
|
537
|
-
console.log("Plus is running at " +
|
|
584
|
+
console.log("Plus is running at " + opened);
|
|
538
585
|
console.log("Logs: " + logPath);
|
|
539
586
|
console.log("Stop it with: dsh-plus stop");
|
|
540
587
|
return 0;
|
|
@@ -13,7 +13,7 @@ import { createRequire } from 'node:module';
|
|
|
13
13
|
import { dirname, join } from 'node:path';
|
|
14
14
|
import { fileURLToPath } from 'node:url';
|
|
15
15
|
import { newerVersion } from "./registry-versions.js";
|
|
16
|
-
import { DEFAULT_PORT, STOP_GRACE_MILLISECONDS, choosePort, clearState, isRunning, portAvailable, readState, spawnServer, stateDirectory, waitForServer, writeState, } from "./standalone-server.js";
|
|
16
|
+
import { DEFAULT_PORT, STOP_GRACE_MILLISECONDS, choosePort, clearState, isRunning, portAvailable, readState, spawnServer, stateDirectory, waitForAuthenticatedUrl, waitForServer, writeState, } from "./standalone-server.js";
|
|
17
17
|
import { STANDALONE_PROFILE, ensureProfile, readDistributionProfile, resolvePaths, } from "./standalone-profile.js";
|
|
18
18
|
/** Milliseconds a start waits for the server to answer before reporting failure. */
|
|
19
19
|
const READY_TIMEOUT_MILLISECONDS = 90_000;
|
|
@@ -137,8 +137,13 @@ async function startDetached(home, entry, options) {
|
|
|
137
137
|
process.kill(pid, 'SIGTERM');
|
|
138
138
|
return 1;
|
|
139
139
|
}
|
|
140
|
-
|
|
141
|
-
|
|
140
|
+
// The launcher prints the URL carrying the process launch token, and the server
|
|
141
|
+
// refuses every request without the cookie that token mints. Reporting the bare
|
|
142
|
+
// address sends the user to a 401 that tells them to reopen the launcher's URL,
|
|
143
|
+
// which lives only in the log this command points at.
|
|
144
|
+
const opened = await waitForAuthenticatedUrl(logPath, READY_TIMEOUT_MILLISECONDS) ?? url;
|
|
145
|
+
writeState(home, { pid, port, url: opened });
|
|
146
|
+
console.log('Plus is running at ' + opened);
|
|
142
147
|
console.log('Logs: ' + logPath);
|
|
143
148
|
console.log('Stop it with: dsh-plus stop');
|
|
144
149
|
return 0;
|
|
@@ -21,6 +21,22 @@ export interface StandalonePaths {
|
|
|
21
21
|
}
|
|
22
22
|
/** Resolve the DSH home, honouring the environment override the launcher itself reads. */
|
|
23
23
|
export declare function resolveHome(env?: NodeJS.ProcessEnv): string;
|
|
24
|
+
/**
|
|
25
|
+
* Whether one absolute path is the other or sits beneath it.
|
|
26
|
+
*
|
|
27
|
+
* A path outside the parent has a relative form starting with `..`, which every
|
|
28
|
+
* platform answers the same way. Comparing the text against a literal separator is
|
|
29
|
+
* not: Windows separates with a backslash, so a nested path never matched its own
|
|
30
|
+
* ancestor and every Windows installation reported the distribution as sitting
|
|
31
|
+
* outside its own tree.
|
|
32
|
+
*
|
|
33
|
+
* @param candidate - absolute path to test.
|
|
34
|
+
* @param parent - absolute path that may contain it.
|
|
35
|
+
* @param platform - separating convention, injectable so the Windows answer is
|
|
36
|
+
* testable where the suite runs on a POSIX host.
|
|
37
|
+
* @returns true when the candidate is the parent or inside it.
|
|
38
|
+
*/
|
|
39
|
+
export declare function isWithin(candidate: string, parent: string, platform?: NodeJS.Platform): boolean;
|
|
24
40
|
/**
|
|
25
41
|
* Resolve the installed distribution directory from one requiring anchor.
|
|
26
42
|
*
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { existsSync, mkdirSync, readdirSync, readFileSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
12
12
|
import { createRequire } from 'node:module';
|
|
13
13
|
import { homedir } from 'node:os';
|
|
14
|
-
import { dirname, join, resolve } from 'node:path';
|
|
14
|
+
import { dirname, join, posix, resolve, win32 } from 'node:path';
|
|
15
15
|
/** Profile name a standalone installation owns. */
|
|
16
16
|
export const STANDALONE_PROFILE = 'plus';
|
|
17
17
|
function requireRecord(value, label) {
|
|
@@ -29,6 +29,28 @@ export function resolveHome(env = process.env) {
|
|
|
29
29
|
const configured = env.DSH_HOME;
|
|
30
30
|
return configured === undefined || configured === '' ? join(homedir(), '.dsh') : resolve(configured);
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Whether one absolute path is the other or sits beneath it.
|
|
34
|
+
*
|
|
35
|
+
* A path outside the parent has a relative form starting with `..`, which every
|
|
36
|
+
* platform answers the same way. Comparing the text against a literal separator is
|
|
37
|
+
* not: Windows separates with a backslash, so a nested path never matched its own
|
|
38
|
+
* ancestor and every Windows installation reported the distribution as sitting
|
|
39
|
+
* outside its own tree.
|
|
40
|
+
*
|
|
41
|
+
* @param candidate - absolute path to test.
|
|
42
|
+
* @param parent - absolute path that may contain it.
|
|
43
|
+
* @param platform - separating convention, injectable so the Windows answer is
|
|
44
|
+
* testable where the suite runs on a POSIX host.
|
|
45
|
+
* @returns true when the candidate is the parent or inside it.
|
|
46
|
+
*/
|
|
47
|
+
export function isWithin(candidate, parent, platform = process.platform) {
|
|
48
|
+
if (candidate === parent)
|
|
49
|
+
return true;
|
|
50
|
+
const path = platform === 'win32' ? win32 : posix;
|
|
51
|
+
const inside = path.relative(parent, candidate);
|
|
52
|
+
return inside !== '' && !inside.startsWith('..') && !inside.startsWith(path.sep + '..');
|
|
53
|
+
}
|
|
32
54
|
/**
|
|
33
55
|
* Resolve the installed distribution directory from one requiring anchor.
|
|
34
56
|
*
|
|
@@ -54,7 +76,7 @@ export function resolveDistributionDirectory(anchor) {
|
|
|
54
76
|
// reports \`<root>/node_modules/@sparkelf/dsh-plus\`.
|
|
55
77
|
let current = resolve(dirname(anchor));
|
|
56
78
|
for (;;) {
|
|
57
|
-
if (resolved
|
|
79
|
+
if (isWithin(resolved, current))
|
|
58
80
|
return resolved;
|
|
59
81
|
const parent = dirname(current);
|
|
60
82
|
if (parent === current)
|
|
@@ -56,4 +56,15 @@ export declare function spawnServer(options: {
|
|
|
56
56
|
* @returns whether the server answered within the budget.
|
|
57
57
|
*/
|
|
58
58
|
export declare function waitForServer(url: string, timeoutMilliseconds: number): Promise<boolean>;
|
|
59
|
+
/**
|
|
60
|
+
* Wait until the launcher's log carries the authenticated URL.
|
|
61
|
+
*
|
|
62
|
+
* Readiness and the printed line are separate events: the server answers before the
|
|
63
|
+
* launcher finishes its own startup, so the log is polled here rather than read once.
|
|
64
|
+
*
|
|
65
|
+
* @param logPath - the launcher's log file.
|
|
66
|
+
* @param timeoutMilliseconds - how long to keep polling.
|
|
67
|
+
* @returns the printed URL, or undefined when it never appeared.
|
|
68
|
+
*/
|
|
69
|
+
export declare function waitForAuthenticatedUrl(logPath: string, timeoutMilliseconds: number): Promise<string | undefined>;
|
|
59
70
|
//# sourceMappingURL=standalone-server.d.ts.map
|
|
@@ -126,4 +126,33 @@ export async function waitForServer(url, timeoutMilliseconds) {
|
|
|
126
126
|
}
|
|
127
127
|
return false;
|
|
128
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* Wait until the launcher's log carries the authenticated URL.
|
|
131
|
+
*
|
|
132
|
+
* Readiness and the printed line are separate events: the server answers before the
|
|
133
|
+
* launcher finishes its own startup, so the log is polled here rather than read once.
|
|
134
|
+
*
|
|
135
|
+
* @param logPath - the launcher's log file.
|
|
136
|
+
* @param timeoutMilliseconds - how long to keep polling.
|
|
137
|
+
* @returns the printed URL, or undefined when it never appeared.
|
|
138
|
+
*/
|
|
139
|
+
export async function waitForAuthenticatedUrl(logPath, timeoutMilliseconds) {
|
|
140
|
+
const deadline = Date.now() + timeoutMilliseconds;
|
|
141
|
+
for (;;) {
|
|
142
|
+
let text = '';
|
|
143
|
+
try {
|
|
144
|
+
text = readFileSync(logPath, 'utf8');
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// The launcher creates the log before it prints, so an absent file is a race
|
|
148
|
+
// with its first write rather than an error to report.
|
|
149
|
+
}
|
|
150
|
+
const found = /dsh web: (http:\/\/\S+)/u.exec(text);
|
|
151
|
+
if (found?.[1] !== undefined)
|
|
152
|
+
return found[1];
|
|
153
|
+
if (Date.now() >= deadline)
|
|
154
|
+
return undefined;
|
|
155
|
+
await new Promise((resolveDelay) => { setTimeout(resolveDelay, 250); });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
129
158
|
//# sourceMappingURL=standalone-server.js.map
|
package/package.json
CHANGED