@sparkelf/dsh-plus 0.2.4-alpha.48 → 0.2.5-alpha.49

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.
@@ -10,12 +10,15 @@
10
10
  import { spawnSync } from 'node:child_process';
11
11
  import { existsSync, readFileSync, writeFileSync } from 'node:fs';
12
12
  import { createRequire } from 'node:module';
13
- import { dirname, join, resolve } from 'node:path';
13
+ import { dirname, join } from 'node:path';
14
14
  import { fileURLToPath } from 'node:url';
15
15
  import { newerVersion } from "./registry-versions.js";
16
+ import { assertRedirectsPublished, projectEdition, readDistributionFacts, writeProfileProjection, } from "./manifest.js";
16
17
  import { CAPABILITIES, CAPABILITY_ENV_FILE, CAPABILITY_MARKER, CAPABILITY_RECORD, DEFAULT_MINERU_ENDPOINT, capabilityEnvironment, capabilityPatchLayer, detectEnabledCapabilities, editCapabilityPatchLayer, installCapabilityServices, interviewCapabilities, readCapabilityRecord, } from "./standalone-capabilities.js";
17
18
  import { DEFAULT_PORT, STOP_GRACE_MILLISECONDS, choosePort, clearState, isRunning, portAvailable, readState, spawnServer, stateDirectory, waitForAuthenticatedUrl, waitForServer, writeState, } from "./standalone-server.js";
18
- import { STANDALONE_PROFILE, applyProfileNpmPatches, ensureProfile, pnpmAvailable, pnpmInstallCommand, pnpmInstallCommands, registryOrder, readDistributionProfile, resolvePaths, } from "./standalone-profile.js";
19
+ import { STANDALONE_PROFILE, applyProfileNpmPatches, ensureProfile, pnpmAvailable, pnpmInstallCommand, pnpmInstallCommands, registryOrder, readDistributionProfile, resolveDistributionDirectory, resolveInvocationAnchor, resolvePaths, } from "./standalone-profile.js";
20
+ /** Edition the offline build projects when the caller names none. */
21
+ const DEFAULT_EDITION = 'intranet-team';
19
22
  /** Milliseconds a start waits for the server to answer before reporting failure. */
20
23
  const READY_TIMEOUT_MILLISECONDS = 90_000;
21
24
  /** Profile patch file the capability interview rewrites (the profile's user layer). */
@@ -25,8 +28,9 @@ function parseStartOptions(argv) {
25
28
  let host = '127.0.0.1';
26
29
  let open = true;
27
30
  let foreground = false;
28
- let edition;
29
31
  let capabilities;
32
+ let edition;
33
+ const exclude = [];
30
34
  for (let index = 0; index < argv.length; index += 1) {
31
35
  const token = argv[index];
32
36
  if (token === '--port' || token === '-p') {
@@ -61,6 +65,14 @@ function parseStartOptions(argv) {
61
65
  index += 1;
62
66
  continue;
63
67
  }
68
+ if (token === '--exclude') {
69
+ const value = argv[index + 1];
70
+ if (value === undefined || value === '')
71
+ throw new Error('--exclude requires a package name');
72
+ exclude.push(value);
73
+ index += 1;
74
+ continue;
75
+ }
64
76
  if (token === '--capabilities') {
65
77
  const value = argv[index + 1];
66
78
  if (value === undefined)
@@ -82,7 +94,7 @@ function parseStartOptions(argv) {
82
94
  }
83
95
  throw new Error('unknown option: ' + String(token));
84
96
  }
85
- return { port, host, open, foreground, edition, capabilities };
97
+ return { port, host, open, foreground, capabilities, edition, exclude };
86
98
  }
87
99
  /**
88
100
  * Resolve a stated capability selection.
@@ -151,12 +163,23 @@ function installationAnchor() {
151
163
  * @returns absolute path to the invoked command, or this module when argv carries none.
152
164
  */
153
165
  function declarationAnchor() {
154
- const invoked = process.argv[1];
155
- return invoked === undefined || invoked === '' ? fileURLToPath(import.meta.url) : resolve(invoked);
166
+ // A global install links the command into a bin directory; the link target is what owning
167
+ // package resolution must start from.
168
+ return resolveInvocationAnchor(process.argv[1], fileURLToPath(import.meta.url));
156
169
  }
157
- /** The launcher entry this installation must drive. */
158
- function launcherEntry(anchor) {
159
- return createRequire(anchor).resolve('@deepseek-ai/dsh/lib/bin.js');
170
+ /**
171
+ * The launcher entry this installation must drive.
172
+ *
173
+ * The profile owns the runtime tree, so the launcher is resolved from the profile rather than
174
+ * from the directory that installed the command: a global distribution has no launcher of its
175
+ * own, and one resolved from the consumer would be a second copy of the process the profile
176
+ * mounts.
177
+ *
178
+ * @param profileDirectory - profile whose node_modules carries the launcher.
179
+ * @returns absolute path to the launcher entry.
180
+ */
181
+ function launcherEntry(profileDirectory) {
182
+ return createRequire(join(profileDirectory, 'package.json')).resolve('@deepseek-ai/dsh/lib/bin.js');
160
183
  }
161
184
  /** Run the server in this process, inheriting stdio. */
162
185
  function runForeground(entry, profileName, port, host, open) {
@@ -166,60 +189,11 @@ function runForeground(entry, profileName, port, host, open) {
166
189
  const result = spawnSync(process.execPath, args, { stdio: 'inherit' });
167
190
  return result.status ?? 1;
168
191
  }
169
- /** Parse the install command's edition selection. */
170
- function parseInstallOptions(argv) {
171
- let edition;
172
- let profile;
173
- for (let index = 0; index < argv.length; index += 1) {
174
- const token = argv[index];
175
- if (token === '--edition') {
176
- const value = argv[index + 1];
177
- if (value === undefined || value === '')
178
- throw new Error('--edition requires a value');
179
- edition = value;
180
- index += 1;
181
- continue;
182
- }
183
- if (token === '--profile') {
184
- const value = argv[index + 1];
185
- if (value === undefined || value === '')
186
- throw new Error('--profile requires a value');
187
- profile = value;
188
- index += 1;
189
- continue;
190
- }
191
- throw new Error('unknown option: ' + String(token));
192
- }
193
- if (edition === undefined)
194
- throw new Error('install requires --edition personal or --edition team');
195
- return { edition, profile };
196
- }
197
- /**
198
- * Install one edition's package manifest into the profile.
199
- *
200
- * The profile's `package.json` is the install artifact; there is no separate standalone
201
- * package. A later `start` uses the same manifest, so changing the edition is a new
202
- * install rather than a second product.
203
- *
204
- * @param argv - arguments after `install`.
205
- * @returns the process exit code.
206
- */
207
- function install(argv) {
208
- const { edition, profile } = parseInstallOptions(argv);
209
- const base = resolvePaths(declarationAnchor());
210
- const paths = profile === undefined
211
- ? base
212
- : { ...base, profileName: profile, profileDirectory: join(base.home, 'profiles', profile) };
213
- ensureProfile(paths, installationRoot(), edition);
214
- console.log('Installed the ' + edition + ' edition into ' + paths.profileDirectory);
215
- return 0;
216
- }
217
192
  async function start(argv) {
218
193
  const options = parseStartOptions(argv);
219
- const anchor = installationAnchor();
220
194
  // The profile's own name and omissions come from the package that installed this
221
195
  // command, which is a different tree position than the dependencies it resolves.
222
- const paths = resolvePaths(declarationAnchor());
196
+ const paths = resolvePaths(declarationAnchor(), process.env, options.edition);
223
197
  // The profile installs its own dependency tree, which needs pnpm. Asking here rather
224
198
  // than failing inside the install turns a missing prerequisite into a decision the
225
199
  // consumer makes, and the install it can run is the one command that provides it.
@@ -259,12 +233,10 @@ async function start(argv) {
259
233
  }
260
234
  console.log('pnpm installed.');
261
235
  }
262
- const created = ensureProfile(paths, installationRoot(), options.edition);
236
+ const created = ensureProfile(paths, options.edition, options.exclude);
263
237
  console.log(created
264
238
  ? 'Created the ' + paths.profileName + ' profile at ' + paths.profileDirectory
265
239
  : 'Using the existing ' + paths.profileName + ' profile');
266
- if (options.edition !== undefined)
267
- console.log('Edition: ' + options.edition);
268
240
  // The profile symlinks the consumer's packages, so a patch lands on the installed
269
241
  // copy the launcher loads. A reinstall restores the published bytes, which is why
270
242
  // this runs on every start rather than only when the profile was created.
@@ -291,7 +263,7 @@ async function start(argv) {
291
263
  console.log(' Exa: add EXA_API_KEY to ' + join(paths.home, CAPABILITY_ENV_FILE) + ' when you have a key.');
292
264
  }
293
265
  }
294
- const entry = launcherEntry(anchor);
266
+ const entry = launcherEntry(paths.profileDirectory);
295
267
  if (options.foreground)
296
268
  return runForeground(entry, paths.profileName, options.port, options.host, options.open);
297
269
  const existing = readState(paths.home);
@@ -640,38 +612,233 @@ async function doctor() {
640
612
  ? 'capabilities: ' + (actual.mounted.length === 0 ? '(none enabled)' : actual.mounted.join(', '))
641
613
  : 'capabilities disagree with ' + CAPABILITY_RECORD + ': ' + drifted.map(capability => capability.id).join(', ')
642
614
  + ' (run dsh-plus capabilities)');
643
- const profileManifestPath = join(paths.profileDirectory, 'package.json');
644
- if (existsSync(profileManifestPath)) {
645
- const profileManifest = JSON.parse(readFileSync(profileManifestPath, 'utf8'));
646
- const editionId = profileManifest.dsh?.profile?.plusEdition;
647
- if (editionId !== undefined) {
648
- const distribution = readDistributionProfile(paths.distributionDirectory);
649
- const edition = distribution.editions[editionId];
650
- if (edition === undefined) {
651
- check(false, 'edition "' + editionId + '" is not declared by this distribution');
652
- }
653
- else {
654
- const bundles = new Set(profileManifest.dsh?.profile?.bundles ?? []);
655
- const missingBundles = edition.bundles.filter(bundle => !bundles.has(bundle));
656
- const packageNames = Object.keys(edition.packages);
657
- const missingPackages = packageNames.filter(name => !existsSync(join(paths.profileDirectory, 'node_modules', ...name.split('/'), 'package.json')));
658
- check(missingBundles.length === 0, 'edition ' + editionId + ': ' + String(edition.bundles.length - missingBundles.length) + '/'
659
- + String(edition.bundles.length) + ' bundles mounted'
660
- + (missingBundles.length === 0 ? '' : ' (missing ' + missingBundles.join(', ') + ')'));
661
- check(missingPackages.length === 0, 'edition ' + editionId + ': ' + String(packageNames.length - missingPackages.length) + '/'
662
- + String(packageNames.length) + ' packages installed'
663
- + (missingPackages.length === 0 ? '' : ' (missing ' + missingPackages.join(', ') + ')'));
664
- }
665
- }
666
- }
667
615
  console.log(failures === 0 ? 'No problems found.' : String(failures) + ' problem(s) found.');
668
616
  return failures === 0 ? 0 : 1;
669
617
  }
618
+ function parseInstallOptions(argv) {
619
+ let edition;
620
+ const exclude = [];
621
+ let dryRun = false;
622
+ for (let index = 0; index < argv.length; index += 1) {
623
+ const token = argv[index];
624
+ const value = argv[index + 1];
625
+ if (token === '--dry-run') {
626
+ dryRun = true;
627
+ continue;
628
+ }
629
+ if (token === '--edition') {
630
+ if (value === undefined || value === '')
631
+ throw new Error('--edition requires a value');
632
+ edition = value;
633
+ index += 1;
634
+ continue;
635
+ }
636
+ if (token === '--exclude') {
637
+ if (value === undefined || value === '')
638
+ throw new Error('--exclude requires a package name');
639
+ exclude.push(value);
640
+ index += 1;
641
+ continue;
642
+ }
643
+ throw new Error('unknown option: ' + String(token));
644
+ }
645
+ return { edition, exclude, dryRun };
646
+ }
647
+ /**
648
+ * Materialize a profile without starting the server.
649
+ *
650
+ * The build system installs the distribution once and needs the profile on disk before it
651
+ * snapshots the image; starting a server to create it would also start a process the image
652
+ * must then kill.
653
+ *
654
+ * @param argv - arguments after the command word.
655
+ * @returns the process exit code.
656
+ */
657
+ function install(argv) {
658
+ if (argv.includes('--help') || argv.includes('-h')) {
659
+ console.log(USAGE);
660
+ return 0;
661
+ }
662
+ const options = parseInstallOptions(argv);
663
+ // One axis: the edition names the package and bundle set this deployment installs, and its
664
+ // profile name travels with it. No edition means the full distribution.
665
+ const paths = resolvePaths(declarationAnchor(), process.env, options.edition);
666
+ const created = ensureProfile(paths, options.edition, options.exclude, options.dryRun);
667
+ console.log((created ? 'Created the ' : 'Using the existing ') + paths.profileName + ' profile at ' + paths.profileDirectory);
668
+ return 0;
669
+ }
670
+ function parseManifestOptions(argv) {
671
+ let distribution;
672
+ let out;
673
+ // The DataOps workspace image is the only build consumer of this command, so its edition is
674
+ // the default; a generic caller states the edition it wants.
675
+ let edition = DEFAULT_EDITION;
676
+ const exclude = [];
677
+ let runtime;
678
+ let registry = 'https://registry.npmjs.org';
679
+ let verify = false;
680
+ let check = false;
681
+ for (let index = 0; index < argv.length; index += 1) {
682
+ const token = argv[index];
683
+ const value = argv[index + 1];
684
+ if (token === '--distribution') {
685
+ if (value === undefined)
686
+ throw new Error('--distribution requires a value');
687
+ distribution = value;
688
+ index += 1;
689
+ continue;
690
+ }
691
+ if (token === '--out') {
692
+ if (value === undefined)
693
+ throw new Error('--out requires a value');
694
+ out = value;
695
+ index += 1;
696
+ continue;
697
+ }
698
+ if (token === '--edition') {
699
+ if (value === undefined)
700
+ throw new Error('--edition requires a value');
701
+ edition = value;
702
+ index += 1;
703
+ continue;
704
+ }
705
+ if (token === '--exclude') {
706
+ if (value === undefined)
707
+ throw new Error('--exclude requires a package name');
708
+ exclude.push(value);
709
+ index += 1;
710
+ continue;
711
+ }
712
+ if (token === '--runtime') {
713
+ if (value === undefined)
714
+ throw new Error('--runtime requires a value');
715
+ runtime = value;
716
+ index += 1;
717
+ continue;
718
+ }
719
+ if (token === '--registry') {
720
+ if (value === undefined)
721
+ throw new Error('--registry requires a value');
722
+ registry = value;
723
+ index += 1;
724
+ continue;
725
+ }
726
+ if (token === '--verify') {
727
+ verify = true;
728
+ continue;
729
+ }
730
+ if (token === '--check') {
731
+ check = true;
732
+ continue;
733
+ }
734
+ throw new Error('unknown option: ' + String(token));
735
+ }
736
+ return { distribution, out, edition, exclude, runtime, registry, verify, check };
737
+ }
738
+ /**
739
+ * Write or verify the profile manifest a build installs.
740
+ *
741
+ * `--out` names the profile directory. `--check` compares what is there against a fresh
742
+ * projection instead of writing, so a committed profile cannot drift from the distribution.
743
+ *
744
+ * @param argv - arguments after the command word.
745
+ * @returns the process exit code.
746
+ */
747
+ async function manifest(argv) {
748
+ const options = parseManifestOptions(argv);
749
+ const directory = options.distribution ?? resolveDistributionDirectory(declarationAnchor());
750
+ const facts = options.runtime === undefined
751
+ ? readDistributionFacts(directory)
752
+ : { ...readDistributionFacts(directory), runtimeVersion: options.runtime };
753
+ const projection = projectEdition(facts, options.edition, options.exclude);
754
+ if (options.verify)
755
+ await assertRedirectsPublished({ ...facts.redirects, ...projection.omittedPackages }, options.registry);
756
+ if (options.out === undefined)
757
+ throw new Error('manifest requires --out <directory>');
758
+ if (options.check) {
759
+ const written = join(options.out, 'package.json');
760
+ if (!existsSync(written))
761
+ throw new Error('manifest: ' + written + ' does not exist; run the generator');
762
+ const actual = JSON.parse(readFileSync(written, 'utf8'));
763
+ const expectedDependencies = {
764
+ '@sparkelf/dsh-plus': facts.version,
765
+ '@deepseek-ai/dsh': '>=' + facts.runtimeVersion,
766
+ ...projection.dependencies,
767
+ };
768
+ const drift = [];
769
+ for (const [name, spec] of Object.entries(expectedDependencies)) {
770
+ if (actual.dependencies?.[name] !== spec)
771
+ drift.push('dependency ' + name);
772
+ }
773
+ for (const name of Object.keys(actual.dependencies ?? {})) {
774
+ if (expectedDependencies[name] === undefined)
775
+ drift.push('extra dependency ' + name);
776
+ }
777
+ for (const bundle of projection.bundles) {
778
+ if (!(actual.dsh?.profile?.bundles ?? []).includes(bundle))
779
+ drift.push('bundle ' + bundle);
780
+ }
781
+ if (drift.length > 0)
782
+ throw new Error('manifest: ' + written + ' is stale in ' + drift.join(', '));
783
+ console.log('manifest: ' + written + ' matches the distribution');
784
+ return 0;
785
+ }
786
+ writeProfileProjection(options.out, facts, options.edition, options.exclude);
787
+ console.log(JSON.stringify({
788
+ profile: projection.profileName,
789
+ runtime: facts.runtimeVersion,
790
+ bundles: projection.bundles.length,
791
+ dependencies: Object.keys(projection.dependencies).length,
792
+ redirects: Object.keys(facts.redirects).length,
793
+ overrides: Object.keys(projection.overrides).length,
794
+ omitted: Object.keys(projection.omittedPackages),
795
+ }));
796
+ return 0;
797
+ }
798
+ /**
799
+ * Print the distribution facts a build or a release gate consumes.
800
+ *
801
+ * @param argv - accepts `--distribution <dir>` only.
802
+ * @returns the process exit code.
803
+ */
804
+ function inspect(argv) {
805
+ let directory;
806
+ for (let index = 0; index < argv.length; index += 1) {
807
+ if (argv[index] !== '--distribution')
808
+ throw new Error('unknown option: ' + String(argv[index]));
809
+ directory = argv[index + 1];
810
+ if (directory === undefined)
811
+ throw new Error('--distribution requires a value');
812
+ index += 1;
813
+ }
814
+ const facts = readDistributionFacts(directory ?? resolveDistributionDirectory(declarationAnchor()));
815
+ console.log(JSON.stringify({
816
+ name: facts.name,
817
+ version: facts.version,
818
+ runtime: facts.runtimeVersion,
819
+ profile: facts.name,
820
+ bundles: facts.bundles,
821
+ dependencies: facts.dependencies,
822
+ redirects: facts.redirects,
823
+ redirectPatch: facts.redirectIds,
824
+ editions: Object.entries(facts.editions).map(([id, edition]) => ({
825
+ id,
826
+ label: edition.label,
827
+ profile: edition.profile,
828
+ bundles: edition.bundles.length,
829
+ packages: edition.packages.length,
830
+ omit: edition.omit.length,
831
+ })),
832
+ }, null, 2));
833
+ return 0;
834
+ }
670
835
  const USAGE = [
671
836
  'Usage: dsh-plus <command> [options]',
672
837
  '',
673
838
  'Commands:',
674
- ' install install an edition package manifest into the profile',
839
+ ' install materialize the profile without starting the server',
840
+ ' manifest write or verify the profile manifest a build installs',
841
+ ' inspect print the distribution facts as JSON',
675
842
  ' start start the server (first run creates the profile)',
676
843
  ' stop stop the server',
677
844
  ' status report whether the server is running',
@@ -679,16 +846,26 @@ const USAGE = [
679
846
  ' update move to a newer distribution release',
680
847
  ' doctor check this installation',
681
848
  '',
849
+ 'install options:',
850
+ ' --edition <id> product edition to materialize (personal, team, intranet-team)',
851
+ ' --exclude <pkg> drop a package and its bundle row (repeatable)',
852
+ '',
853
+ 'manifest options:',
854
+ ' --out <dir> profile directory to write',
855
+ ' --edition <id> product edition to project (default: ' + DEFAULT_EDITION + ')',
856
+ ' --exclude <pkg> drop a package and its bundle row (repeatable)',
857
+ ' --runtime <version> override the runtime version the redirects name',
858
+ ' --registry <url> registry checked by --verify',
859
+ ' --verify fail unless every redirect target is published',
860
+ ' --check compare --out with a fresh projection instead of writing',
861
+ '',
682
862
  'start options:',
683
863
  ' --port <n> port to prefer (default ' + String(DEFAULT_PORT) + '; a taken port moves to the next free one)',
684
864
  ' --host <h> interface to bind (default 127.0.0.1)',
685
865
  ' --no-open do not open a browser',
686
866
  ' --foreground run in this terminal instead of in the background',
687
- ' --edition <id> install the named edition manifest (personal or team)',
688
- '',
689
- 'install options:',
690
- ' --edition <id> package manifest to install (personal or team)',
691
- ' --profile <n> profile name to install into (default: this installation)',
867
+ ' --edition <id> product edition to materialize',
868
+ ' --exclude <pkg> drop a package and its bundle row (repeatable)',
692
869
  '',
693
870
  'capabilities options:',
694
871
  ' --enable <ids> enable the comma-separated capability ids',
@@ -713,6 +890,10 @@ export async function runStandaloneCli(argv) {
713
890
  }
714
891
  if (command === 'install')
715
892
  return install(rest);
893
+ if (command === 'manifest')
894
+ return await manifest(rest);
895
+ if (command === 'inspect')
896
+ return inspect(rest);
716
897
  if (command === 'start')
717
898
  return await start(rest);
718
899
  if (command === 'stop')
@@ -8,6 +8,7 @@
8
8
  * the launcher mounts exactly the bundles the profile names and nothing expands a
9
9
  * bundle's own list.
10
10
  */
11
+ import { readDistributionFacts } from './manifest.ts';
11
12
  /** Profile name a standalone installation owns when its package declares none. */
12
13
  export declare const STANDALONE_PROFILE = "plus";
13
14
  /**
@@ -63,6 +64,19 @@ export interface StandalonePaths {
63
64
  /** Installed Plus distribution directory. */
64
65
  readonly distributionDirectory: string;
65
66
  }
67
+ /**
68
+ * Resolve the file that was actually invoked from the path the shell was handed.
69
+ *
70
+ * A global npm install links `<prefix>/bin/dsh-plus` to the package entry. Resolving
71
+ * `@sparkelf/dsh-plus` from that link starts at the bin directory, which has no
72
+ * `node_modules`, so every globally installed command reported the distribution as
73
+ * missing. The real path is the package file, and the package resolves its own name.
74
+ *
75
+ * @param invoked - `process.argv[1]`, or undefined when node ran without a script.
76
+ * @param fallback - this module's own file, used when there is no script path.
77
+ * @returns an absolute path that exists, or the input when it cannot be resolved.
78
+ */
79
+ export declare function resolveInvocationAnchor(invoked: string | undefined, fallback: string): string;
66
80
  /** Resolve the DSH home, honouring the environment override the launcher itself reads. */
67
81
  export declare function resolveHome(env?: NodeJS.ProcessEnv): string;
68
82
  /**
@@ -92,21 +106,18 @@ export declare function isWithin(candidate: string, parent: string, platform?: N
92
106
  * @returns absolute path to the installed distribution.
93
107
  */
94
108
  export declare function resolveDistributionDirectory(anchor: string): string;
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
- }
101
- export declare function readDistributionProfile(distributionDirectory: string): {
109
+ /** The reviewed bundle order and pins the installed distribution declares, for one variant. */
110
+ export declare function readDistributionProfile(distributionDirectory: string, editionId?: string, extraExcludes?: readonly string[]): {
102
111
  readonly name: string;
112
+ readonly version: string;
113
+ readonly dshRange: string;
114
+ readonly runtimeVersion: string;
103
115
  readonly bundles: readonly string[];
104
116
  readonly dependencies: Readonly<Record<string, string>>;
105
117
  readonly allowBuilds: Readonly<Record<string, boolean>>;
106
118
  readonly overrides: Readonly<Record<string, string>>;
107
- readonly editions: Readonly<Record<string, EditionComposition>>;
108
- readonly dshRange: string;
109
- readonly version: string;
119
+ readonly omittedPackages: Readonly<Record<string, string>>;
120
+ readonly editions: ReturnType<typeof readDistributionFacts>['editions'];
110
121
  };
111
122
  /**
112
123
  * Give the profile its own installed tree, built from the distribution's declarations.
@@ -124,9 +135,34 @@ export declare function readDistributionProfile(distributionDirectory: string):
124
135
  * twice; `dsh-plus apply` remains the command that reinstalls after a change.
125
136
  *
126
137
  * @param paths - resolved standalone paths.
127
- * @param consumerDirectory - directory whose `node_modules` holds the installation.
128
138
  */
129
- export declare function installProfilePackages(paths: StandalonePaths, consumerDirectory: string): void;
139
+ export declare function installProfilePackages(paths: StandalonePaths, omitted?: ReadonlySet<string>, runtimeVersion?: string, registry?: string): void;
140
+ /**
141
+ * Move the shared official services onto the runtime's exact version.
142
+ *
143
+ * The republished builds were cut against one runtime, and DSH disables a row when the exact
144
+ * peers a plugin declares disagree with the process it shares. Plugins declare those services as
145
+ * peers, so the names worth pinning are the peers actually installed -- read from the tree and
146
+ * checked against the registry. The rest of the official family keeps its resolved versions:
147
+ * pinning everything traded one mismatch for a tree with holes.
148
+ *
149
+ * @param profileDirectory - profile whose tree was just installed.
150
+ * @param runtimeVersion - exact runtime the profile boots.
151
+ * @param registry - registry the profile installs from.
152
+ * @returns how many services were realigned.
153
+ */
154
+ export declare function alignOfficialVersions(profileDirectory: string, runtimeVersion: string, registry: string): number;
155
+ /**
156
+ * Assert every bundle row the profile declares resolves in its installed tree.
157
+ *
158
+ * A row without an installed package mounts nothing and reports nothing: the client module
159
+ * system simply never registers it, and every earlier step passes. Reading the rows back from
160
+ * the manifest on disk rather than from the projection is what makes this cover the tree the
161
+ * launcher actually boots.
162
+ *
163
+ * @param profileDirectory - profile whose node_modules was just installed.
164
+ */
165
+ export declare function assertProfileBundles(profileDirectory: string): void;
130
166
  /**
131
167
  * Make each replaced package declare the name of the location it occupies.
132
168
  *
@@ -143,7 +179,7 @@ export declare function installProfilePackages(paths: StandalonePaths, consumerD
143
179
  *
144
180
  * @param profileModules - the profile's \`node_modules\` directory.
145
181
  */
146
- export declare function alignReplacedPackageNames(profileModules: string): void;
182
+ export declare function alignReplacedPackageNames(profileModules: string, omitted?: ReadonlySet<string>): void;
147
183
  /**
148
184
  * The registries to try, in order, for this machine.
149
185
  *
@@ -182,7 +218,7 @@ export declare function pnpmInstallCommands(): readonly string[];
182
218
  */
183
219
  export declare function pnpmInstallCommand(): string;
184
220
  /** Resolve every path a command needs, without creating anything. */
185
- export declare function resolvePaths(anchor: string, env?: NodeJS.ProcessEnv): StandalonePaths;
221
+ export declare function resolvePaths(anchor: string, env?: NodeJS.ProcessEnv, editionId?: string): StandalonePaths;
186
222
  /**
187
223
  * Write the profile manifest when it is absent, leaving an existing one untouched.
188
224
  *
@@ -196,11 +232,12 @@ export declare function resolvePaths(anchor: string, env?: NodeJS.ProcessEnv): S
196
232
  * that installed them.
197
233
  *
198
234
  * @param paths - resolved standalone paths.
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.
235
+ * @param editionId - product edition to materialize.
236
+ * @param extraExcludes - packages the caller drops on top of the edition's own exclusions.
237
+ * @param dryRun - write the profile files without installing their dependency tree.
201
238
  * @returns whether this call created the manifest.
202
239
  */
203
- export declare function ensureProfile(paths: StandalonePaths, consumerDirectory: string, edition?: string): boolean;
240
+ export declare function ensureProfile(paths: StandalonePaths, editionId?: string, extraExcludes?: readonly string[], dryRun?: boolean): boolean;
204
241
  /**
205
242
  * Apply the reviewed npm-target patches to the profile's installed packages.
206
243
  *