@sparkelf/dsh-plus 0.1.0-rc.38 → 0.1.0-rc.40
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 +112 -16
- package/lib/types/standalone-cli.js +40 -1
- package/lib/types/standalone-profile.d.ts +37 -0
- package/lib/types/standalone-profile.js +87 -16
- package/package.json +1 -1
package/lib/bin.js
CHANGED
|
@@ -363,11 +363,40 @@ function installProfilePackages(paths, consumerDirectory) {
|
|
|
363
363
|
linkNestedBundles(paths, profileModules);
|
|
364
364
|
return;
|
|
365
365
|
}
|
|
366
|
-
|
|
366
|
+
installWithRegistryFallback(paths);
|
|
367
367
|
alignReplacedPackageNames(profileModules);
|
|
368
368
|
linkNestedBundles(paths, profileModules);
|
|
369
369
|
}
|
|
370
370
|
/**
|
|
371
|
+
* Install the profile from the first registry that answers.
|
|
372
|
+
*
|
|
373
|
+
* The install fetches a full dependency closure, and a mainland consumer reaches the
|
|
374
|
+
* mirror far faster than the origin. Trying the preferred registry and falling back once
|
|
375
|
+
* keeps a first start quick without failing when the preferred one is unreachable.
|
|
376
|
+
*
|
|
377
|
+
* @param paths - resolved standalone paths.
|
|
378
|
+
*/
|
|
379
|
+
function installWithRegistryFallback(paths) {
|
|
380
|
+
const registries = registryOrder();
|
|
381
|
+
for (const [index, registry] of registries.entries()) {
|
|
382
|
+
const result = spawnSync("pnpm", [
|
|
383
|
+
"install",
|
|
384
|
+
"--no-frozen-lockfile",
|
|
385
|
+
"--registry",
|
|
386
|
+
registry
|
|
387
|
+
], {
|
|
388
|
+
cwd: paths.profileDirectory,
|
|
389
|
+
stdio: "inherit",
|
|
390
|
+
shell: process.platform === "win32"
|
|
391
|
+
});
|
|
392
|
+
if (result.error !== void 0) throw result.error;
|
|
393
|
+
if (result.status === 0) return;
|
|
394
|
+
const next = registries[index + 1];
|
|
395
|
+
if (next === void 0) throw new Error("pnpm install in the plus profile failed with exit code " + String(result.status));
|
|
396
|
+
console.log("Install from " + registry + " failed; trying " + next + ".");
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
371
400
|
* Make each replaced package declare the name of the location it occupies.
|
|
372
401
|
*
|
|
373
402
|
* \`overrides\` installs our build at the official path, but the manifest inside still
|
|
@@ -401,25 +430,63 @@ function alignReplacedPackageNames(profileModules) {
|
|
|
401
430
|
renameSync(temporary, manifestPath);
|
|
402
431
|
}
|
|
403
432
|
}
|
|
433
|
+
/** The npm registry the distribution installs from by default. */
|
|
434
|
+
const OFFICIAL_REGISTRY = "https://registry.npmjs.org";
|
|
435
|
+
/** The mainland mirror, which serves the same packages and answers faster there. */
|
|
436
|
+
const MAINLAND_REGISTRY = "https://registry.npmmirror.com";
|
|
404
437
|
/**
|
|
405
|
-
*
|
|
438
|
+
* The registries to try, in order, for this machine.
|
|
406
439
|
*
|
|
407
|
-
*
|
|
408
|
-
*
|
|
409
|
-
*
|
|
440
|
+
* A mainland locale reaches the mirror faster than the origin, which is why the Desktop
|
|
441
|
+
* installer already prefers it there. The same choice belongs to the profile install: it
|
|
442
|
+
* fetches a full dependency closure, so the delay is the first thing a consumer notices.
|
|
443
|
+
* `DSH_PLUS_INSTALL_REGISTRY` overrides the choice, and a failure falls back once.
|
|
410
444
|
*
|
|
411
|
-
* @
|
|
412
|
-
* @param args - pnpm arguments, without the executable.
|
|
413
|
-
* @param label - the failing step's name, for the error.
|
|
445
|
+
* @returns registries to try in order.
|
|
414
446
|
*/
|
|
415
|
-
function
|
|
416
|
-
const
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
447
|
+
function registryOrder() {
|
|
448
|
+
const configured = process.env.DSH_PLUS_INSTALL_REGISTRY;
|
|
449
|
+
if (configured !== void 0 && configured !== "") return [configured, MAINLAND_REGISTRY];
|
|
450
|
+
const locale = (process.env.LANG ?? process.env.LC_ALL ?? "").toLowerCase();
|
|
451
|
+
return locale.includes("zh") || locale.includes("cn") ? [MAINLAND_REGISTRY, OFFICIAL_REGISTRY] : [OFFICIAL_REGISTRY, MAINLAND_REGISTRY];
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Report whether pnpm can run.
|
|
455
|
+
*
|
|
456
|
+
* The profile installs its own tree so its \`overrides\` apply, and only pnpm reads those
|
|
457
|
+
* from a workspace, so pnpm is a prerequisite the distribution cannot supply. Probing
|
|
458
|
+
* first turns a cryptic failure from the install into a message naming what is missing.
|
|
459
|
+
*
|
|
460
|
+
* @returns \`true\` when pnpm answers with a version.
|
|
461
|
+
*/
|
|
462
|
+
function pnpmAvailable() {
|
|
463
|
+
return spawnSync("pnpm", ["--version"], {
|
|
464
|
+
stdio: "pipe",
|
|
465
|
+
shell: process.platform === "win32",
|
|
466
|
+
encoding: "utf8"
|
|
467
|
+
}).status === 0;
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* The commands that install pnpm, in the order worth trying.
|
|
471
|
+
*
|
|
472
|
+
* Corepack ships with Node and needs no download, so it comes first; npm is the fallback
|
|
473
|
+
* for an installation whose Corepack is absent or disabled. Both are the consumer's own
|
|
474
|
+
* toolchain, which is what lets the first start offer to install rather than only report.
|
|
475
|
+
*
|
|
476
|
+
* @returns commands to try in order, stopping at the first that works.
|
|
477
|
+
*/
|
|
478
|
+
function pnpmInstallCommands() {
|
|
479
|
+
const registry = registryOrder()[0];
|
|
480
|
+
if (registry === void 0) return ["corepack enable pnpm", "npm install -g pnpm"];
|
|
481
|
+
return ["corepack enable pnpm", "npm install -g pnpm --registry " + registry];
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* The command to show a consumer who declines the automatic install.
|
|
485
|
+
*
|
|
486
|
+
* @returns the preferred command, which is the one the offer runs first.
|
|
487
|
+
*/
|
|
488
|
+
function pnpmInstallCommand() {
|
|
489
|
+
return pnpmInstallCommands()[0] ?? "npm install -g pnpm";
|
|
423
490
|
}
|
|
424
491
|
/**
|
|
425
492
|
* Expose the distribution's nested packages at the top level the profile searches.
|
|
@@ -698,6 +765,35 @@ async function start(argv) {
|
|
|
698
765
|
const options = parseStartOptions(argv);
|
|
699
766
|
const anchor = installationAnchor();
|
|
700
767
|
const paths = resolvePaths(anchor);
|
|
768
|
+
if (!pnpmAvailable()) {
|
|
769
|
+
const command = pnpmInstallCommand();
|
|
770
|
+
console.log("pnpm is required to install the plus profile, and was not found.");
|
|
771
|
+
console.log("Install it with: " + command);
|
|
772
|
+
if (!await confirm("Install pnpm now?")) {
|
|
773
|
+
console.log("Install pnpm and run dsh-plus start again.");
|
|
774
|
+
return 1;
|
|
775
|
+
}
|
|
776
|
+
let installed = false;
|
|
777
|
+
const preferred = registryOrder()[0];
|
|
778
|
+
for (const candidate of pnpmInstallCommands()) if (spawnSync(candidate, {
|
|
779
|
+
stdio: "inherit",
|
|
780
|
+
shell: true,
|
|
781
|
+
env: preferred === void 0 ? process.env : {
|
|
782
|
+
...process.env,
|
|
783
|
+
COREPACK_NPM_REGISTRY: preferred
|
|
784
|
+
}
|
|
785
|
+
}).status === 0 && pnpmAvailable()) {
|
|
786
|
+
installed = true;
|
|
787
|
+
break;
|
|
788
|
+
}
|
|
789
|
+
if (!installed) {
|
|
790
|
+
console.log("Could not install pnpm automatically.");
|
|
791
|
+
console.log("Run one of these, then run dsh-plus start again:");
|
|
792
|
+
for (const candidate of pnpmInstallCommands()) console.log(" " + candidate);
|
|
793
|
+
return 1;
|
|
794
|
+
}
|
|
795
|
+
console.log("pnpm installed.");
|
|
796
|
+
}
|
|
701
797
|
const created = ensureProfile(paths, installationRoot());
|
|
702
798
|
console.log(created ? "Created the plus profile at " + paths.profileDirectory : "Using the existing plus profile");
|
|
703
799
|
for (const label of applyProfileNpmPatches(paths.distributionDirectory, paths.profileDirectory)) console.log("Applied the reviewed patch " + label);
|
|
@@ -14,7 +14,7 @@ import { dirname, join } from 'node:path';
|
|
|
14
14
|
import { fileURLToPath } from 'node:url';
|
|
15
15
|
import { newerVersion } from "./registry-versions.js";
|
|
16
16
|
import { DEFAULT_PORT, STOP_GRACE_MILLISECONDS, choosePort, clearState, isRunning, portAvailable, readState, spawnServer, stateDirectory, waitForAuthenticatedUrl, waitForServer, writeState, } from "./standalone-server.js";
|
|
17
|
-
import { STANDALONE_PROFILE, applyProfileNpmPatches, ensureProfile, readDistributionProfile, resolvePaths, } from "./standalone-profile.js";
|
|
17
|
+
import { STANDALONE_PROFILE, applyProfileNpmPatches, ensureProfile, pnpmAvailable, pnpmInstallCommand, pnpmInstallCommands, registryOrder, 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;
|
|
20
20
|
function parseStartOptions(argv) {
|
|
@@ -100,6 +100,45 @@ async function start(argv) {
|
|
|
100
100
|
const options = parseStartOptions(argv);
|
|
101
101
|
const anchor = installationAnchor();
|
|
102
102
|
const paths = resolvePaths(anchor);
|
|
103
|
+
// The profile installs its own dependency tree, which needs pnpm. Asking here rather
|
|
104
|
+
// than failing inside the install turns a missing prerequisite into a decision the
|
|
105
|
+
// consumer makes, and the install it can run is the one command that provides it.
|
|
106
|
+
if (!pnpmAvailable()) {
|
|
107
|
+
const command = pnpmInstallCommand();
|
|
108
|
+
console.log('pnpm is required to install the ' + STANDALONE_PROFILE + ' profile, and was not found.');
|
|
109
|
+
console.log('Install it with: ' + command);
|
|
110
|
+
if (!await confirm('Install pnpm now?')) {
|
|
111
|
+
console.log('Install pnpm and run dsh-plus start again.');
|
|
112
|
+
return 1;
|
|
113
|
+
}
|
|
114
|
+
// Try each installer in turn: Corepack is absent on an installation that disabled it,
|
|
115
|
+
// and npm succeeds there. A failure reports the commands rather than a stack trace.
|
|
116
|
+
let installed = false;
|
|
117
|
+
const preferred = registryOrder()[0];
|
|
118
|
+
for (const candidate of pnpmInstallCommands()) {
|
|
119
|
+
// Corepack reads its registry from the environment; npm takes a flag, which the
|
|
120
|
+
// command already carries. Setting it for both keeps the download on the mirror.
|
|
121
|
+
const attempt = spawnSync(candidate, {
|
|
122
|
+
stdio: 'inherit',
|
|
123
|
+
shell: true,
|
|
124
|
+
env: preferred === undefined
|
|
125
|
+
? process.env
|
|
126
|
+
: { ...process.env, COREPACK_NPM_REGISTRY: preferred },
|
|
127
|
+
});
|
|
128
|
+
if (attempt.status === 0 && pnpmAvailable()) {
|
|
129
|
+
installed = true;
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (!installed) {
|
|
134
|
+
console.log('Could not install pnpm automatically.');
|
|
135
|
+
console.log('Run one of these, then run dsh-plus start again:');
|
|
136
|
+
for (const candidate of pnpmInstallCommands())
|
|
137
|
+
console.log(' ' + candidate);
|
|
138
|
+
return 1;
|
|
139
|
+
}
|
|
140
|
+
console.log('pnpm installed.');
|
|
141
|
+
}
|
|
103
142
|
const created = ensureProfile(paths, installationRoot());
|
|
104
143
|
console.log(created
|
|
105
144
|
? 'Created the ' + STANDALONE_PROFILE + ' profile at ' + paths.profileDirectory
|
|
@@ -94,6 +94,43 @@ export declare function installProfilePackages(paths: StandalonePaths, consumerD
|
|
|
94
94
|
* @param profileModules - the profile's \`node_modules\` directory.
|
|
95
95
|
*/
|
|
96
96
|
export declare function alignReplacedPackageNames(profileModules: string): void;
|
|
97
|
+
/**
|
|
98
|
+
* The registries to try, in order, for this machine.
|
|
99
|
+
*
|
|
100
|
+
* A mainland locale reaches the mirror faster than the origin, which is why the Desktop
|
|
101
|
+
* installer already prefers it there. The same choice belongs to the profile install: it
|
|
102
|
+
* fetches a full dependency closure, so the delay is the first thing a consumer notices.
|
|
103
|
+
* `DSH_PLUS_INSTALL_REGISTRY` overrides the choice, and a failure falls back once.
|
|
104
|
+
*
|
|
105
|
+
* @returns registries to try in order.
|
|
106
|
+
*/
|
|
107
|
+
export declare function registryOrder(): readonly string[];
|
|
108
|
+
/**
|
|
109
|
+
* Report whether pnpm can run.
|
|
110
|
+
*
|
|
111
|
+
* The profile installs its own tree so its \`overrides\` apply, and only pnpm reads those
|
|
112
|
+
* from a workspace, so pnpm is a prerequisite the distribution cannot supply. Probing
|
|
113
|
+
* first turns a cryptic failure from the install into a message naming what is missing.
|
|
114
|
+
*
|
|
115
|
+
* @returns \`true\` when pnpm answers with a version.
|
|
116
|
+
*/
|
|
117
|
+
export declare function pnpmAvailable(): boolean;
|
|
118
|
+
/**
|
|
119
|
+
* The commands that install pnpm, in the order worth trying.
|
|
120
|
+
*
|
|
121
|
+
* Corepack ships with Node and needs no download, so it comes first; npm is the fallback
|
|
122
|
+
* for an installation whose Corepack is absent or disabled. Both are the consumer's own
|
|
123
|
+
* toolchain, which is what lets the first start offer to install rather than only report.
|
|
124
|
+
*
|
|
125
|
+
* @returns commands to try in order, stopping at the first that works.
|
|
126
|
+
*/
|
|
127
|
+
export declare function pnpmInstallCommands(): readonly string[];
|
|
128
|
+
/**
|
|
129
|
+
* The command to show a consumer who declines the automatic install.
|
|
130
|
+
*
|
|
131
|
+
* @returns the preferred command, which is the one the offer runs first.
|
|
132
|
+
*/
|
|
133
|
+
export declare function pnpmInstallCommand(): string;
|
|
97
134
|
/** Resolve every path a command needs, without creating anything. */
|
|
98
135
|
export declare function resolvePaths(anchor: string, env?: NodeJS.ProcessEnv): StandalonePaths;
|
|
99
136
|
/**
|
|
@@ -159,10 +159,38 @@ export function installProfilePackages(paths, consumerDirectory) {
|
|
|
159
159
|
linkNestedBundles(paths, profileModules);
|
|
160
160
|
return;
|
|
161
161
|
}
|
|
162
|
-
|
|
162
|
+
installWithRegistryFallback(paths);
|
|
163
163
|
alignReplacedPackageNames(profileModules);
|
|
164
164
|
linkNestedBundles(paths, profileModules);
|
|
165
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Install the profile from the first registry that answers.
|
|
168
|
+
*
|
|
169
|
+
* The install fetches a full dependency closure, and a mainland consumer reaches the
|
|
170
|
+
* mirror far faster than the origin. Trying the preferred registry and falling back once
|
|
171
|
+
* keeps a first start quick without failing when the preferred one is unreachable.
|
|
172
|
+
*
|
|
173
|
+
* @param paths - resolved standalone paths.
|
|
174
|
+
*/
|
|
175
|
+
function installWithRegistryFallback(paths) {
|
|
176
|
+
const registries = registryOrder();
|
|
177
|
+
for (const [index, registry] of registries.entries()) {
|
|
178
|
+
const result = spawnSync('pnpm', ['install', '--no-frozen-lockfile', '--registry', registry], {
|
|
179
|
+
cwd: paths.profileDirectory,
|
|
180
|
+
stdio: 'inherit',
|
|
181
|
+
shell: process.platform === 'win32',
|
|
182
|
+
});
|
|
183
|
+
if (result.error !== undefined)
|
|
184
|
+
throw result.error;
|
|
185
|
+
if (result.status === 0)
|
|
186
|
+
return;
|
|
187
|
+
const next = registries[index + 1];
|
|
188
|
+
if (next === undefined) {
|
|
189
|
+
throw new Error('pnpm install in the plus profile failed with exit code ' + String(result.status));
|
|
190
|
+
}
|
|
191
|
+
console.log('Install from ' + registry + ' failed; trying ' + next + '.');
|
|
192
|
+
}
|
|
193
|
+
}
|
|
166
194
|
/**
|
|
167
195
|
* Make each replaced package declare the name of the location it occupies.
|
|
168
196
|
*
|
|
@@ -197,27 +225,70 @@ export function alignReplacedPackageNames(profileModules) {
|
|
|
197
225
|
renameSync(temporary, manifestPath);
|
|
198
226
|
}
|
|
199
227
|
}
|
|
228
|
+
/** The npm registry the distribution installs from by default. */
|
|
229
|
+
const OFFICIAL_REGISTRY = 'https://registry.npmjs.org';
|
|
230
|
+
/** The mainland mirror, which serves the same packages and answers faster there. */
|
|
231
|
+
const MAINLAND_REGISTRY = 'https://registry.npmmirror.com';
|
|
232
|
+
/**
|
|
233
|
+
* The registries to try, in order, for this machine.
|
|
234
|
+
*
|
|
235
|
+
* A mainland locale reaches the mirror faster than the origin, which is why the Desktop
|
|
236
|
+
* installer already prefers it there. The same choice belongs to the profile install: it
|
|
237
|
+
* fetches a full dependency closure, so the delay is the first thing a consumer notices.
|
|
238
|
+
* `DSH_PLUS_INSTALL_REGISTRY` overrides the choice, and a failure falls back once.
|
|
239
|
+
*
|
|
240
|
+
* @returns registries to try in order.
|
|
241
|
+
*/
|
|
242
|
+
export function registryOrder() {
|
|
243
|
+
const configured = process.env.DSH_PLUS_INSTALL_REGISTRY;
|
|
244
|
+
if (configured !== undefined && configured !== '')
|
|
245
|
+
return [configured, MAINLAND_REGISTRY];
|
|
246
|
+
const locale = (process.env.LANG ?? process.env.LC_ALL ?? '').toLowerCase();
|
|
247
|
+
const mainlandFirst = locale.includes('zh') || locale.includes('cn');
|
|
248
|
+
return mainlandFirst ? [MAINLAND_REGISTRY, OFFICIAL_REGISTRY] : [OFFICIAL_REGISTRY, MAINLAND_REGISTRY];
|
|
249
|
+
}
|
|
200
250
|
/**
|
|
201
|
-
*
|
|
251
|
+
* Report whether pnpm can run.
|
|
202
252
|
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
253
|
+
* The profile installs its own tree so its \`overrides\` apply, and only pnpm reads those
|
|
254
|
+
* from a workspace, so pnpm is a prerequisite the distribution cannot supply. Probing
|
|
255
|
+
* first turns a cryptic failure from the install into a message naming what is missing.
|
|
206
256
|
*
|
|
207
|
-
* @
|
|
208
|
-
* @param args - pnpm arguments, without the executable.
|
|
209
|
-
* @param label - the failing step's name, for the error.
|
|
257
|
+
* @returns \`true\` when pnpm answers with a version.
|
|
210
258
|
*/
|
|
211
|
-
function
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
stdio: 'inherit',
|
|
259
|
+
export function pnpmAvailable() {
|
|
260
|
+
const probe = spawnSync('pnpm', ['--version'], {
|
|
261
|
+
stdio: 'pipe',
|
|
215
262
|
shell: process.platform === 'win32',
|
|
263
|
+
encoding: 'utf8',
|
|
216
264
|
});
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
265
|
+
return probe.status === 0;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* The commands that install pnpm, in the order worth trying.
|
|
269
|
+
*
|
|
270
|
+
* Corepack ships with Node and needs no download, so it comes first; npm is the fallback
|
|
271
|
+
* for an installation whose Corepack is absent or disabled. Both are the consumer's own
|
|
272
|
+
* toolchain, which is what lets the first start offer to install rather than only report.
|
|
273
|
+
*
|
|
274
|
+
* @returns commands to try in order, stopping at the first that works.
|
|
275
|
+
*/
|
|
276
|
+
export function pnpmInstallCommands() {
|
|
277
|
+
const registry = registryOrder()[0];
|
|
278
|
+
if (registry === undefined)
|
|
279
|
+
return ['corepack enable pnpm', 'npm install -g pnpm'];
|
|
280
|
+
// Both installers take the registry explicitly. npm does so with a flag; corepack reads
|
|
281
|
+
// COREPACK_NPM_REGISTRY, which the caller sets. A mainland consumer therefore downloads
|
|
282
|
+
// the package from the mirror, for the same reason the profile install prefers it.
|
|
283
|
+
return ['corepack enable pnpm', 'npm install -g pnpm --registry ' + registry];
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* The command to show a consumer who declines the automatic install.
|
|
287
|
+
*
|
|
288
|
+
* @returns the preferred command, which is the one the offer runs first.
|
|
289
|
+
*/
|
|
290
|
+
export function pnpmInstallCommand() {
|
|
291
|
+
return pnpmInstallCommands()[0] ?? 'npm install -g pnpm';
|
|
221
292
|
}
|
|
222
293
|
/**
|
|
223
294
|
* Expose the distribution's nested packages at the top level the profile searches.
|
package/package.json
CHANGED