@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.
Files changed (37) hide show
  1. package/README.md +7 -5
  2. package/README.zh.md +7 -5
  3. package/lib/apply-08huHfLG.js +665 -0
  4. package/lib/apply-DmICvIGh.js +653 -0
  5. package/lib/apply.js +1 -1
  6. package/lib/bin.js +133 -37
  7. package/lib/types/apply.d.ts +1 -1
  8. package/lib/types/apply.js +56 -117
  9. package/lib/types/patch-set.d.ts +59 -0
  10. package/lib/types/patch-set.js +132 -0
  11. package/lib/types/standalone-cli.js +56 -2
  12. package/lib/types/standalone-profile.d.ts +8 -1
  13. package/lib/types/standalone-profile.js +84 -38
  14. package/package.json +625 -176
  15. package/patches/approval-allow-policy.patch +108 -0
  16. package/patches/atomic-write-zombie-lock-holder.patch +53 -0
  17. package/patches/bootstrap-exempt-names.patch +32 -0
  18. package/patches/browser-auth-mode.patch +127 -0
  19. package/patches/composer-popover-boundaries.patch +337 -0
  20. package/patches/diagram-preview.patch +562 -0
  21. package/patches/image-offload-hoist-replay.patch +533 -0
  22. package/patches/legacy-code-preset.patch +148 -0
  23. package/patches/llm-pi-ai-transport-truncation.patch +33 -0
  24. package/patches/mobile-journal-generation.patch +76 -0
  25. package/patches/office-preview-stable-load.patch +34 -0
  26. package/patches/officecli-deliverables.patch +42 -0
  27. package/patches/ptc-mcp-schema-types.patch +156 -0
  28. package/patches/responses-reasoning-status.patch +289 -0
  29. package/patches/session-envelope-ignorable.patch +63 -0
  30. package/patches/session-export-chinese.patch +85 -0
  31. package/patches/session-format-legacy-restart.patch +35 -0
  32. package/patches/session-log-trajectory-toolbar.patch +352 -0
  33. package/patches/state-dot-brand-blue.patch +185 -0
  34. package/patches/ui-settings-compact-phone.patch +76 -0
  35. package/patches/web-runtime-compat.patch +83 -0
  36. package/patches/workspace-storage-restore.patch +186 -0
  37. 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 rawDependencies = requireRecord(profile.dependencies, 'dshPlus.profile.dependencies');
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: distribution.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 plus = requireRecord(manifest.dshPlus, 'dshPlus');
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 value of names) {
545
- if (typeof value !== 'string' || value === '')
546
- throw new Error('dshPlus.patchPackages entries must be non-empty strings');
547
- const patchPackage = resolveInstalledPackage(distributionDirectory, value);
548
- const declaration = requireRecord(patchPackage.manifest.dshPatch, value + ' dshPatch');
549
- const variants = declaration.variants;
550
- if (!Array.isArray(variants))
551
- throw new Error(value + ' dshPatch.variants must be an array');
552
- for (const entry of variants) {
553
- const variant = requireRecord(entry, value + ' variant');
554
- const target = requireRecord(variant.target, value + ' variant target');
555
- // Only npm targets reach an installed package; a source target needs the
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
- throw new Error(packageName + ' is not installed under ' + from);
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 };