@sparkelf/dsh-plus 0.2.1-rc.2.2 → 0.2.2-alpha.2.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/README.md +7 -5
- package/README.zh.md +7 -5
- package/lib/apply-08huHfLG.js +665 -0
- package/lib/apply-DmICvIGh.js +653 -0
- package/lib/apply.js +1 -1
- package/lib/bin.js +133 -37
- package/lib/types/apply.d.ts +1 -1
- package/lib/types/apply.js +56 -117
- package/lib/types/patch-set.d.ts +59 -0
- package/lib/types/patch-set.js +132 -0
- package/lib/types/standalone-cli.js +56 -2
- package/lib/types/standalone-profile.d.ts +8 -1
- package/lib/types/standalone-profile.js +84 -38
- package/package.json +625 -176
- package/patches/approval-allow-policy.patch +108 -0
- package/patches/atomic-write-zombie-lock-holder.patch +53 -0
- package/patches/bootstrap-exempt-names.patch +32 -0
- package/patches/browser-auth-mode.patch +127 -0
- package/patches/composer-popover-boundaries.patch +337 -0
- package/patches/diagram-preview.patch +562 -0
- package/patches/image-offload-hoist-replay.patch +533 -0
- package/patches/legacy-code-preset.patch +148 -0
- package/patches/llm-pi-ai-transport-truncation.patch +33 -0
- package/patches/mobile-journal-generation.patch +76 -0
- package/patches/office-preview-stable-load.patch +34 -0
- package/patches/officecli-deliverables.patch +42 -0
- package/patches/ptc-mcp-schema-types.patch +156 -0
- package/patches/responses-reasoning-status.patch +289 -0
- package/patches/session-envelope-ignorable.patch +63 -0
- package/patches/session-export-chinese.patch +85 -0
- package/patches/session-format-legacy-restart.patch +35 -0
- package/patches/session-log-trajectory-toolbar.patch +352 -0
- package/patches/state-dot-brand-blue.patch +185 -0
- package/patches/ui-settings-compact-phone.patch +76 -0
- package/patches/web-runtime-compat.patch +83 -0
- package/patches/workspace-storage-restore.patch +186 -0
- package/patches/wsl-native-open.patch +265 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read the Plus distribution's embedded patch set.
|
|
3
|
+
*
|
|
4
|
+
* The distribution owns one patch set for official DSH and, separately, the patches it
|
|
5
|
+
* carries for third-party packages. Both live inside the distribution package: the
|
|
6
|
+
* manifest declares entries, and each entry's payload is a file under `patches/`. A
|
|
7
|
+
* consumer resolves no independently published patch package.
|
|
8
|
+
*
|
|
9
|
+
* The two lists are governed differently. A Plus entry must target an official package
|
|
10
|
+
* that the profile republishes and overrides; a third-party entry must name its upstream
|
|
11
|
+
* and a retirement condition, and never becomes part of the official patch set.
|
|
12
|
+
*/
|
|
13
|
+
import { existsSync } from 'node:fs';
|
|
14
|
+
import { isAbsolute, relative, resolve } from 'node:path';
|
|
15
|
+
import { validRange } from 'semver';
|
|
16
|
+
function object(value, label) {
|
|
17
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
18
|
+
throw new Error(label + ' must be an object');
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
function string(value, label) {
|
|
22
|
+
if (typeof value !== 'string' || value === '')
|
|
23
|
+
throw new Error(label + ' must be a non-empty string');
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
function range(value, label) {
|
|
27
|
+
const text = string(value, label);
|
|
28
|
+
if (validRange(text) === null)
|
|
29
|
+
throw new Error(label + ' is not a semantic-version range: ' + text);
|
|
30
|
+
return text;
|
|
31
|
+
}
|
|
32
|
+
/** A payload stays inside the distribution package it is declared by. */
|
|
33
|
+
function patchFile(distributionDirectory, declared, label) {
|
|
34
|
+
const absolute = resolve(distributionDirectory, declared);
|
|
35
|
+
const local = relative(distributionDirectory, absolute);
|
|
36
|
+
if (local === '' || isAbsolute(local) || local === '..' || local.startsWith('../')) {
|
|
37
|
+
throw new Error(label + ' must stay inside the distribution package: ' + declared);
|
|
38
|
+
}
|
|
39
|
+
if (!existsSync(absolute))
|
|
40
|
+
throw new Error(label + ' does not exist: ' + absolute);
|
|
41
|
+
return local.replaceAll('\\', '/');
|
|
42
|
+
}
|
|
43
|
+
function sourceTarget(value, sourceBaseRevision, label) {
|
|
44
|
+
const target = object(value, label);
|
|
45
|
+
if (target.kind !== 'dsh-source')
|
|
46
|
+
throw new Error(label + '.kind must be dsh-source');
|
|
47
|
+
if (!Array.isArray(target.paths) || target.paths.length === 0)
|
|
48
|
+
throw new Error(label + '.paths must be a non-empty array');
|
|
49
|
+
const paths = target.paths.map((entry, index) => {
|
|
50
|
+
const path = string(entry, label + '.paths[' + String(index) + ']');
|
|
51
|
+
if (!path.endsWith('/'))
|
|
52
|
+
throw new Error(label + '.paths entries must be repository directory prefixes');
|
|
53
|
+
return path;
|
|
54
|
+
});
|
|
55
|
+
if (!Array.isArray(target.packages) || target.packages.length === 0) {
|
|
56
|
+
throw new Error(label + '.packages must be a non-empty array');
|
|
57
|
+
}
|
|
58
|
+
const packages = target.packages.map((entry, index) => {
|
|
59
|
+
const name = string(entry, label + '.packages[' + String(index) + ']');
|
|
60
|
+
if (!name.startsWith('@deepseek-ai/'))
|
|
61
|
+
throw new Error(label + '.packages entries must be official package names');
|
|
62
|
+
return name;
|
|
63
|
+
});
|
|
64
|
+
return { kind: 'dsh-source', baseRevision: sourceBaseRevision, packages, paths };
|
|
65
|
+
}
|
|
66
|
+
function npmTarget(value, label) {
|
|
67
|
+
const target = object(value, label);
|
|
68
|
+
if (target.kind !== 'npm')
|
|
69
|
+
throw new Error(label + '.kind must be npm');
|
|
70
|
+
return {
|
|
71
|
+
kind: 'npm',
|
|
72
|
+
name: string(target.name, label + '.name'),
|
|
73
|
+
range: range(target.range, label + '.range'),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function entry(value, scope, sourceBaseRevision, distributionDirectory, label) {
|
|
77
|
+
const record = object(value, label);
|
|
78
|
+
const id = string(record.id, label + '.id');
|
|
79
|
+
const dsh = range(record.dsh, label + '.dsh');
|
|
80
|
+
const file = patchFile(distributionDirectory, string(record.file, label + '.file'), label + '.file');
|
|
81
|
+
const retireWhen = string(record.retireWhen, label + '.retireWhen');
|
|
82
|
+
const targetLabel = label + '.target';
|
|
83
|
+
const target = scope === 'plus'
|
|
84
|
+
? (object(record.target, targetLabel).kind === 'dsh-source'
|
|
85
|
+
? sourceTarget(record.target, sourceBaseRevision, targetLabel)
|
|
86
|
+
: npmTarget(record.target, targetLabel))
|
|
87
|
+
: npmTarget(record.target, targetLabel);
|
|
88
|
+
if (scope === 'third-party') {
|
|
89
|
+
return {
|
|
90
|
+
id,
|
|
91
|
+
scope,
|
|
92
|
+
dsh,
|
|
93
|
+
file,
|
|
94
|
+
retireWhen,
|
|
95
|
+
upstreamUrl: string(record.upstreamUrl, label + '.upstreamUrl'),
|
|
96
|
+
target,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return { id, scope, dsh, file, retireWhen, target };
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Read and validate the embedded patch set from an installed distribution.
|
|
103
|
+
* @param distributionDirectory - installed `@sparkelf/dsh-plus` directory.
|
|
104
|
+
* @param manifest - its parsed package manifest.
|
|
105
|
+
* @returns the ordered Plus entries, third-party entries, and the exact source base.
|
|
106
|
+
*/
|
|
107
|
+
export function readPatchSet(distributionDirectory, manifest) {
|
|
108
|
+
const plus = object(manifest.dshPlus, 'dshPlus');
|
|
109
|
+
const sourceBase = object(plus.sourceBase, 'dshPlus.sourceBase');
|
|
110
|
+
const sourceBaseRevision = string(sourceBase.revision, 'dshPlus.sourceBase.revision');
|
|
111
|
+
const declaration = object(plus.patchSet, 'dshPlus.patchSet');
|
|
112
|
+
if (declaration.formatVersion !== 1)
|
|
113
|
+
throw new Error('dshPlus.patchSet.formatVersion must be 1');
|
|
114
|
+
if (!Array.isArray(declaration.entries) || declaration.entries.length === 0) {
|
|
115
|
+
throw new Error('dshPlus.patchSet.entries must be a non-empty array');
|
|
116
|
+
}
|
|
117
|
+
const entries = declaration.entries.map((value, index) => entry(value, 'plus', sourceBaseRevision, distributionDirectory, 'dshPlus.patchSet.entries[' + String(index) + ']'));
|
|
118
|
+
const thirdPartyRaw = plus.thirdPartyPatches;
|
|
119
|
+
const thirdParty = thirdPartyRaw === undefined
|
|
120
|
+
? []
|
|
121
|
+
: (Array.isArray(thirdPartyRaw)
|
|
122
|
+
? thirdPartyRaw.map((value, index) => entry(value, 'third-party', sourceBaseRevision, distributionDirectory, 'dshPlus.thirdPartyPatches[' + String(index) + ']'))
|
|
123
|
+
: (() => { throw new Error('dshPlus.thirdPartyPatches must be an array'); })());
|
|
124
|
+
const ids = new Set();
|
|
125
|
+
for (const item of [...entries, ...thirdParty]) {
|
|
126
|
+
if (ids.has(item.id))
|
|
127
|
+
throw new Error('duplicate patch entry id: ' + item.id);
|
|
128
|
+
ids.add(item.id);
|
|
129
|
+
}
|
|
130
|
+
return { entries, thirdPartyPatches: thirdParty, sourceBaseRevision };
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=patch-set.js.map
|
|
@@ -25,6 +25,7 @@ function parseStartOptions(argv) {
|
|
|
25
25
|
let host = '127.0.0.1';
|
|
26
26
|
let open = true;
|
|
27
27
|
let foreground = false;
|
|
28
|
+
let edition;
|
|
28
29
|
let capabilities;
|
|
29
30
|
for (let index = 0; index < argv.length; index += 1) {
|
|
30
31
|
const token = argv[index];
|
|
@@ -52,6 +53,14 @@ function parseStartOptions(argv) {
|
|
|
52
53
|
foreground = true;
|
|
53
54
|
continue;
|
|
54
55
|
}
|
|
56
|
+
if (token === '--edition') {
|
|
57
|
+
const value = argv[index + 1];
|
|
58
|
+
if (value === undefined || value === '')
|
|
59
|
+
throw new Error('--edition requires a value');
|
|
60
|
+
edition = value;
|
|
61
|
+
index += 1;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
55
64
|
if (token === '--capabilities') {
|
|
56
65
|
const value = argv[index + 1];
|
|
57
66
|
if (value === undefined)
|
|
@@ -73,7 +82,7 @@ function parseStartOptions(argv) {
|
|
|
73
82
|
}
|
|
74
83
|
throw new Error('unknown option: ' + String(token));
|
|
75
84
|
}
|
|
76
|
-
return { port, host, open, foreground, capabilities };
|
|
85
|
+
return { port, host, open, foreground, edition, capabilities };
|
|
77
86
|
}
|
|
78
87
|
/**
|
|
79
88
|
* Resolve a stated capability selection.
|
|
@@ -157,6 +166,42 @@ function runForeground(entry, profileName, port, host, open) {
|
|
|
157
166
|
const result = spawnSync(process.execPath, args, { stdio: 'inherit' });
|
|
158
167
|
return result.status ?? 1;
|
|
159
168
|
}
|
|
169
|
+
/** Parse the install command's edition selection. */
|
|
170
|
+
function parseInstallOptions(argv) {
|
|
171
|
+
let edition;
|
|
172
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
173
|
+
const token = argv[index];
|
|
174
|
+
if (token === '--edition') {
|
|
175
|
+
const value = argv[index + 1];
|
|
176
|
+
if (value === undefined || value === '')
|
|
177
|
+
throw new Error('--edition requires a value');
|
|
178
|
+
edition = value;
|
|
179
|
+
index += 1;
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
throw new Error('unknown option: ' + String(token));
|
|
183
|
+
}
|
|
184
|
+
if (edition === undefined)
|
|
185
|
+
throw new Error('install requires --edition personal or --edition team');
|
|
186
|
+
return { edition };
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Install one edition's package manifest into the profile.
|
|
190
|
+
*
|
|
191
|
+
* The profile's `package.json` is the install artifact; there is no separate standalone
|
|
192
|
+
* package. A later `start` uses the same manifest, so changing the edition is a new
|
|
193
|
+
* install rather than a second product.
|
|
194
|
+
*
|
|
195
|
+
* @param argv - arguments after `install`.
|
|
196
|
+
* @returns the process exit code.
|
|
197
|
+
*/
|
|
198
|
+
function install(argv) {
|
|
199
|
+
const { edition } = parseInstallOptions(argv);
|
|
200
|
+
const paths = resolvePaths(declarationAnchor());
|
|
201
|
+
ensureProfile(paths, installationRoot(), edition);
|
|
202
|
+
console.log('Installed the ' + edition + ' edition into ' + paths.profileDirectory);
|
|
203
|
+
return 0;
|
|
204
|
+
}
|
|
160
205
|
async function start(argv) {
|
|
161
206
|
const options = parseStartOptions(argv);
|
|
162
207
|
const anchor = installationAnchor();
|
|
@@ -202,10 +247,12 @@ async function start(argv) {
|
|
|
202
247
|
}
|
|
203
248
|
console.log('pnpm installed.');
|
|
204
249
|
}
|
|
205
|
-
const created = ensureProfile(paths, installationRoot());
|
|
250
|
+
const created = ensureProfile(paths, installationRoot(), options.edition);
|
|
206
251
|
console.log(created
|
|
207
252
|
? 'Created the ' + paths.profileName + ' profile at ' + paths.profileDirectory
|
|
208
253
|
: 'Using the existing ' + paths.profileName + ' profile');
|
|
254
|
+
if (options.edition !== undefined)
|
|
255
|
+
console.log('Edition: ' + options.edition);
|
|
209
256
|
// The profile symlinks the consumer's packages, so a patch lands on the installed
|
|
210
257
|
// copy the launcher loads. A reinstall restores the published bytes, which is why
|
|
211
258
|
// this runs on every start rather than only when the profile was created.
|
|
@@ -588,6 +635,7 @@ const USAGE = [
|
|
|
588
635
|
'Usage: dsh-plus <command> [options]',
|
|
589
636
|
'',
|
|
590
637
|
'Commands:',
|
|
638
|
+
' install install an edition package manifest into the profile',
|
|
591
639
|
' start start the server (first run creates the profile)',
|
|
592
640
|
' stop stop the server',
|
|
593
641
|
' status report whether the server is running',
|
|
@@ -600,6 +648,10 @@ const USAGE = [
|
|
|
600
648
|
' --host <h> interface to bind (default 127.0.0.1)',
|
|
601
649
|
' --no-open do not open a browser',
|
|
602
650
|
' --foreground run in this terminal instead of in the background',
|
|
651
|
+
' --edition <id> install the named edition manifest (personal or team)',
|
|
652
|
+
'',
|
|
653
|
+
'install options:',
|
|
654
|
+
' --edition <id> package manifest to install (personal or team)',
|
|
603
655
|
'',
|
|
604
656
|
'capabilities options:',
|
|
605
657
|
' --enable <ids> enable the comma-separated capability ids',
|
|
@@ -622,6 +674,8 @@ export async function runStandaloneCli(argv) {
|
|
|
622
674
|
console.log(USAGE);
|
|
623
675
|
return 0;
|
|
624
676
|
}
|
|
677
|
+
if (command === 'install')
|
|
678
|
+
return install(rest);
|
|
625
679
|
if (command === 'start')
|
|
626
680
|
return await start(rest);
|
|
627
681
|
if (command === 'stop')
|
|
@@ -93,12 +93,18 @@ export declare function isWithin(candidate: string, parent: string, platform?: N
|
|
|
93
93
|
*/
|
|
94
94
|
export declare function resolveDistributionDirectory(anchor: string): string;
|
|
95
95
|
/** The reviewed bundle order and pins the installed distribution declares. */
|
|
96
|
+
export interface EditionComposition {
|
|
97
|
+
readonly label: string;
|
|
98
|
+
readonly bundles: readonly string[];
|
|
99
|
+
readonly packages: Readonly<Record<string, string>>;
|
|
100
|
+
}
|
|
96
101
|
export declare function readDistributionProfile(distributionDirectory: string): {
|
|
97
102
|
readonly name: string;
|
|
98
103
|
readonly bundles: readonly string[];
|
|
99
104
|
readonly dependencies: Readonly<Record<string, string>>;
|
|
100
105
|
readonly allowBuilds: Readonly<Record<string, boolean>>;
|
|
101
106
|
readonly overrides: Readonly<Record<string, string>>;
|
|
107
|
+
readonly editions: Readonly<Record<string, EditionComposition>>;
|
|
102
108
|
readonly dshRange: string;
|
|
103
109
|
readonly version: string;
|
|
104
110
|
};
|
|
@@ -191,9 +197,10 @@ export declare function resolvePaths(anchor: string, env?: NodeJS.ProcessEnv): S
|
|
|
191
197
|
*
|
|
192
198
|
* @param paths - resolved standalone paths.
|
|
193
199
|
* @param consumerDirectory - directory whose `node_modules` holds the installed packages.
|
|
200
|
+
* @param edition - edition id whose package list to install, or undefined for the default.
|
|
194
201
|
* @returns whether this call created the manifest.
|
|
195
202
|
*/
|
|
196
|
-
export declare function ensureProfile(paths: StandalonePaths, consumerDirectory: string): boolean;
|
|
203
|
+
export declare function ensureProfile(paths: StandalonePaths, consumerDirectory: string, edition?: string): boolean;
|
|
197
204
|
/**
|
|
198
205
|
* Apply the reviewed npm-target patches to the profile's installed packages.
|
|
199
206
|
*
|
|
@@ -14,6 +14,7 @@ import { createRequire } from 'node:module';
|
|
|
14
14
|
import { homedir } from 'node:os';
|
|
15
15
|
import { dirname, join, posix, resolve, win32 } from 'node:path';
|
|
16
16
|
import { parseDocument } from 'yaml';
|
|
17
|
+
import { readPatchSet } from "./patch-set.js";
|
|
17
18
|
/** Profile name a standalone installation owns when its package declares none. */
|
|
18
19
|
export const STANDALONE_PROFILE = 'plus';
|
|
19
20
|
/**
|
|
@@ -97,6 +98,12 @@ function requireStringArray(value, label) {
|
|
|
97
98
|
throw new Error(label + ' must be a string array');
|
|
98
99
|
return value;
|
|
99
100
|
}
|
|
101
|
+
/** Read one non-empty string field. */
|
|
102
|
+
function requireString(value, label) {
|
|
103
|
+
if (typeof value !== 'string' || value.trim() === '')
|
|
104
|
+
throw new Error(label + ' must be a non-empty string');
|
|
105
|
+
return value;
|
|
106
|
+
}
|
|
100
107
|
/** Resolve the DSH home, honouring the environment override the launcher itself reads. */
|
|
101
108
|
export function resolveHome(env = process.env) {
|
|
102
109
|
const configured = env.DSH_HOME;
|
|
@@ -158,19 +165,12 @@ export function resolveDistributionDirectory(anchor) {
|
|
|
158
165
|
}
|
|
159
166
|
throw new Error('@sparkelf/dsh-plus resolved outside ' + resolve(dirname(anchor)) + ' (found ' + resolved + '); run this command from the directory that installed it');
|
|
160
167
|
}
|
|
161
|
-
/** The reviewed bundle order and pins the installed distribution declares. */
|
|
162
168
|
export function readDistributionProfile(distributionDirectory) {
|
|
163
169
|
const manifest = requireRecord(JSON.parse(readFileSync(join(distributionDirectory, 'package.json'), 'utf8')), 'Plus distribution manifest');
|
|
164
170
|
const plus = requireRecord(manifest.dshPlus, 'dshPlus');
|
|
165
171
|
const profile = requireRecord(plus.profile, 'dshPlus.profile');
|
|
166
|
-
const
|
|
172
|
+
const dependencies = requireDependencyMap(requireRecord(profile.dependencies, 'dshPlus.profile.dependencies'), 'dshPlus.profile.dependencies');
|
|
167
173
|
const rawAllowBuilds = requireRecord(profile.allowBuilds, 'dshPlus.profile.allowBuilds');
|
|
168
|
-
const dependencies = {};
|
|
169
|
-
for (const [name, spec] of Object.entries(rawDependencies)) {
|
|
170
|
-
if (typeof spec !== 'string' || spec === '')
|
|
171
|
-
throw new Error('dshPlus.profile.dependencies.' + name + ' must be a non-empty string');
|
|
172
|
-
dependencies[name] = spec;
|
|
173
|
-
}
|
|
174
174
|
const allowBuilds = {};
|
|
175
175
|
for (const [name, allowed] of Object.entries(rawAllowBuilds)) {
|
|
176
176
|
if (typeof allowed !== 'boolean')
|
|
@@ -189,6 +189,16 @@ export function readDistributionProfile(distributionDirectory) {
|
|
|
189
189
|
throw new Error('dshPlus.profile.overrides.' + name + ' must be a non-empty string');
|
|
190
190
|
overrides[name] = spec;
|
|
191
191
|
}
|
|
192
|
+
const editions = {};
|
|
193
|
+
const rawEditions = plus.editions === undefined ? {} : requireRecord(plus.editions, 'dshPlus.editions');
|
|
194
|
+
for (const [id, value] of Object.entries(rawEditions)) {
|
|
195
|
+
const edition = requireRecord(value, 'dshPlus.editions.' + id);
|
|
196
|
+
editions[id] = {
|
|
197
|
+
label: requireString(edition.label, 'dshPlus.editions.' + id + '.label'),
|
|
198
|
+
bundles: requireStringArray(edition.bundles, 'dshPlus.editions.' + id + '.bundles'),
|
|
199
|
+
packages: requireDependencyMap(requireRecord(edition.packages, 'dshPlus.editions.' + id + '.packages'), 'dshPlus.editions.' + id + '.packages'),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
192
202
|
const compatibility = requireRecord(plus.compatibility, 'dshPlus.compatibility');
|
|
193
203
|
return {
|
|
194
204
|
name: String(manifest.name),
|
|
@@ -196,10 +206,21 @@ export function readDistributionProfile(distributionDirectory) {
|
|
|
196
206
|
dependencies,
|
|
197
207
|
allowBuilds,
|
|
198
208
|
overrides,
|
|
209
|
+
editions,
|
|
199
210
|
dshRange: String(compatibility.dsh),
|
|
200
211
|
version: String(manifest.version),
|
|
201
212
|
};
|
|
202
213
|
}
|
|
214
|
+
/** Read one package-name-to-spec map, rejecting empty entries. */
|
|
215
|
+
function requireDependencyMap(value, label) {
|
|
216
|
+
const dependencies = {};
|
|
217
|
+
for (const [name, spec] of Object.entries(value)) {
|
|
218
|
+
if (typeof spec !== 'string' || spec === '')
|
|
219
|
+
throw new Error(label + '.' + name + ' must be a non-empty string');
|
|
220
|
+
dependencies[name] = spec;
|
|
221
|
+
}
|
|
222
|
+
return dependencies;
|
|
223
|
+
}
|
|
203
224
|
/**
|
|
204
225
|
* Give the profile its own installed tree, built from the distribution's declarations.
|
|
205
226
|
*
|
|
@@ -250,6 +271,9 @@ function installWithRegistryFallback(paths) {
|
|
|
250
271
|
cwd: paths.profileDirectory,
|
|
251
272
|
stdio: 'inherit',
|
|
252
273
|
shell: process.platform === 'win32',
|
|
274
|
+
// A registry that accepts the connection but never answers would otherwise hold the
|
|
275
|
+
// first start for pnpm's full retry budget before the fallback got a chance.
|
|
276
|
+
timeout: 5 * 60_000,
|
|
253
277
|
});
|
|
254
278
|
if (result.error !== undefined)
|
|
255
279
|
throw result.error;
|
|
@@ -430,12 +454,32 @@ export function resolvePaths(anchor, env = process.env) {
|
|
|
430
454
|
*
|
|
431
455
|
* @param paths - resolved standalone paths.
|
|
432
456
|
* @param consumerDirectory - directory whose `node_modules` holds the installed packages.
|
|
457
|
+
* @param edition - edition id whose package list to install, or undefined for the default.
|
|
433
458
|
* @returns whether this call created the manifest.
|
|
434
459
|
*/
|
|
435
|
-
export function ensureProfile(paths, consumerDirectory) {
|
|
460
|
+
export function ensureProfile(paths, consumerDirectory, edition) {
|
|
436
461
|
const manifestPath = join(paths.profileDirectory, 'package.json');
|
|
437
462
|
const distribution = readDistributionProfile(paths.distributionDirectory);
|
|
463
|
+
const composition = edition === undefined ? undefined : distribution.editions[edition];
|
|
464
|
+
if (edition !== undefined && composition === undefined) {
|
|
465
|
+
const offered = Object.keys(distribution.editions);
|
|
466
|
+
throw new Error('unknown edition "' + edition + '"; this release offers '
|
|
467
|
+
+ (offered.length === 0 ? '(none)' : offered.join(', ')));
|
|
468
|
+
}
|
|
469
|
+
const editionPackages = composition?.packages ?? {};
|
|
470
|
+
const editionBundles = composition?.bundles ?? distribution.bundles;
|
|
438
471
|
if (existsSync(manifestPath)) {
|
|
472
|
+
// A selected edition is an explicit install choice, so its packages and bundle order
|
|
473
|
+
// replace the profile's rather than being ignored. Other profile decisions stay.
|
|
474
|
+
if (composition !== undefined) {
|
|
475
|
+
const manifest = requireRecord(JSON.parse(readFileSync(manifestPath, 'utf8')), 'Plus profile manifest');
|
|
476
|
+
manifest.dependencies = { ...manifest.dependencies, ...editionPackages };
|
|
477
|
+
manifest.dsh = {
|
|
478
|
+
...manifest.dsh,
|
|
479
|
+
profile: { ...manifest.dsh?.profile, bundles: [...editionBundles] },
|
|
480
|
+
};
|
|
481
|
+
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
|
|
482
|
+
}
|
|
439
483
|
// The workspace carries decisions the distribution owns — overrides and the build
|
|
440
484
|
// script allowlist — and a distribution release changes them. Rewriting on every
|
|
441
485
|
// start is what lets an upgraded installation receive the new values; a profile
|
|
@@ -456,10 +500,11 @@ export function ensureProfile(paths, consumerDirectory) {
|
|
|
456
500
|
// fails at load with a module the launcher would have carried.
|
|
457
501
|
'@deepseek-ai/dsh': distribution.dshRange,
|
|
458
502
|
...distribution.dependencies,
|
|
503
|
+
...editionPackages,
|
|
459
504
|
},
|
|
460
505
|
dsh: {
|
|
461
506
|
profile: {
|
|
462
|
-
bundles:
|
|
507
|
+
bundles: [...editionBundles],
|
|
463
508
|
patchReload: 'live',
|
|
464
509
|
},
|
|
465
510
|
},
|
|
@@ -536,34 +581,20 @@ function git(root, args, acceptFailure = false) {
|
|
|
536
581
|
*/
|
|
537
582
|
export function applyProfileNpmPatches(distributionDirectory, profileDirectory) {
|
|
538
583
|
const manifest = requireRecord(JSON.parse(readFileSync(join(distributionDirectory, 'package.json'), 'utf8')), 'Plus distribution manifest');
|
|
539
|
-
const
|
|
540
|
-
const names = plus.patchPackages;
|
|
541
|
-
if (!Array.isArray(names))
|
|
542
|
-
throw new Error('dshPlus.patchPackages must be an array');
|
|
584
|
+
const patchSet = readPatchSet(distributionDirectory, manifest);
|
|
543
585
|
const applied = [];
|
|
544
|
-
for (const
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
const
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
// official checkout, which a standalone installation does not have.
|
|
557
|
-
if (target.kind !== 'npm')
|
|
558
|
-
continue;
|
|
559
|
-
const targetName = String(target.name);
|
|
560
|
-
const patched = resolveInstalledPackage(profileDirectory, targetName);
|
|
561
|
-
const file = resolve(patchPackage.directory, String(variant.file));
|
|
562
|
-
if (git(patched.directory, ['apply', '--reverse', '--check', file], true) !== undefined)
|
|
563
|
-
continue;
|
|
564
|
-
git(patched.directory, ['apply', file]);
|
|
565
|
-
applied.push(value + ' -> ' + targetName);
|
|
566
|
-
}
|
|
586
|
+
for (const entry of [...patchSet.entries, ...patchSet.thirdPartyPatches]) {
|
|
587
|
+
// Only npm targets reach an installed package; a source target needs the
|
|
588
|
+
// official checkout, which a standalone installation does not have.
|
|
589
|
+
if (entry.target.kind !== 'npm')
|
|
590
|
+
continue;
|
|
591
|
+
const targetName = entry.target.name;
|
|
592
|
+
const patched = resolveInstalledPackage(profileDirectory, targetName);
|
|
593
|
+
const file = resolve(distributionDirectory, entry.file);
|
|
594
|
+
if (git(patched.directory, ['apply', '--reverse', '--check', file], true) !== undefined)
|
|
595
|
+
continue;
|
|
596
|
+
git(patched.directory, ['apply', file]);
|
|
597
|
+
applied.push(entry.id + ' -> ' + targetName);
|
|
567
598
|
}
|
|
568
599
|
return applied;
|
|
569
600
|
}
|
|
@@ -575,7 +606,22 @@ function resolveInstalledPackage(from, packageName) {
|
|
|
575
606
|
manifestPath = requireFrom.resolve(packageName + '/package.json');
|
|
576
607
|
}
|
|
577
608
|
catch {
|
|
578
|
-
|
|
609
|
+
// Some packages do not export `./package.json`; resolve the entry module instead and walk
|
|
610
|
+
// up to the directory that holds the manifest.
|
|
611
|
+
let directory;
|
|
612
|
+
try {
|
|
613
|
+
directory = dirname(requireFrom.resolve(packageName));
|
|
614
|
+
}
|
|
615
|
+
catch {
|
|
616
|
+
throw new Error(packageName + ' is not installed under ' + from);
|
|
617
|
+
}
|
|
618
|
+
while (!existsSync(resolve(directory, 'package.json'))) {
|
|
619
|
+
const parent = dirname(directory);
|
|
620
|
+
if (parent === directory)
|
|
621
|
+
throw new Error(packageName + ' has no resolvable package manifest under ' + from);
|
|
622
|
+
directory = parent;
|
|
623
|
+
}
|
|
624
|
+
manifestPath = resolve(directory, 'package.json');
|
|
579
625
|
}
|
|
580
626
|
const manifest = requireRecord(JSON.parse(readFileSync(manifestPath, 'utf8')), packageName + ' manifest');
|
|
581
627
|
return { name: packageName, version: String(manifest.version), directory: dirname(manifestPath), manifest };
|