@vltpkg/cli-sdk 1.5.0 → 1.6.0

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 (55) hide show
  1. package/dist/commands/build.d.ts +8 -3
  2. package/dist/commands/build.js +31 -3
  3. package/dist/commands/ci.d.ts +1 -1
  4. package/dist/commands/create.d.ts +2 -1
  5. package/dist/commands/create.js +2 -3
  6. package/dist/commands/exec.d.ts +2 -1
  7. package/dist/commands/exec.js +2 -3
  8. package/dist/commands/install/reporter.d.ts +1 -1
  9. package/dist/commands/install/reporter.js +45 -5
  10. package/dist/commands/install.d.ts +21 -3
  11. package/dist/commands/install.js +84 -30
  12. package/dist/commands/list.js +5 -1
  13. package/dist/commands/query.js +5 -1
  14. package/dist/commands/setup.d.ts +28 -2
  15. package/dist/commands/setup.js +185 -3
  16. package/dist/commands/skills.js +2 -3
  17. package/dist/commands/token.js +1 -1
  18. package/dist/commands/uninstall.d.ts +10 -3
  19. package/dist/commands/uninstall.js +28 -2
  20. package/dist/commands/update.d.ts +1 -1
  21. package/dist/config/definition.d.ts +32 -0
  22. package/dist/config/definition.js +60 -0
  23. package/dist/config/index.d.ts +26 -3
  24. package/dist/config/index.js +149 -7
  25. package/dist/global-bin-dir.d.ts +37 -0
  26. package/dist/global-bin-dir.js +138 -0
  27. package/dist/global-project.d.ts +18 -0
  28. package/dist/global-project.js +67 -0
  29. package/dist/global-state.d.ts +24 -0
  30. package/dist/global-state.js +94 -0
  31. package/dist/global.d.ts +78 -0
  32. package/dist/global.js +409 -0
  33. package/dist/index.js +7 -2
  34. package/dist/install-receipt.d.ts +40 -0
  35. package/dist/install-receipt.js +69 -0
  36. package/dist/output.js +13 -0
  37. package/dist/parse-add-remove-args.js +2 -14
  38. package/dist/query-auth.d.ts +3 -2
  39. package/dist/query-auth.js +6 -4
  40. package/dist/reload-config.js +2 -1
  41. package/dist/require-registry.d.ts +7 -0
  42. package/dist/require-registry.js +13 -1
  43. package/dist/self-update.d.ts +74 -0
  44. package/dist/self-update.js +401 -0
  45. package/dist/shell-setup/env.d.ts +9 -0
  46. package/dist/shell-setup/env.js +25 -0
  47. package/dist/shell-setup/index.d.ts +69 -0
  48. package/dist/shell-setup/index.js +213 -0
  49. package/dist/shell-setup/posix.d.ts +36 -0
  50. package/dist/shell-setup/posix.js +158 -0
  51. package/dist/shell-setup/windows.d.ts +39 -0
  52. package/dist/shell-setup/windows.js +151 -0
  53. package/dist/shell.d.ts +18 -0
  54. package/dist/shell.js +110 -0
  55. package/package.json +30 -28
@@ -1,8 +1,13 @@
1
1
  import type { BuildResult } from '@vltpkg/graph';
2
+ import type { GlobalBins } from '../global.ts';
2
3
  import type { CommandFn, CommandUsage } from '../index.ts';
4
+ export type BuildCommandResult = BuildResult & {
5
+ global?: GlobalBins;
6
+ };
3
7
  export declare const views: {
4
- readonly human: (result: BuildResult) => string;
5
- readonly json: (result: BuildResult) => {
8
+ readonly human: (result: BuildCommandResult) => string;
9
+ readonly json: (result: BuildCommandResult) => {
10
+ global?: GlobalBins | undefined;
6
11
  message: string;
7
12
  skipped?: {
8
13
  id: import("@vltpkg/dep-id").DepID;
@@ -28,4 +33,4 @@ export declare const usage: CommandUsage;
28
33
  * Build command implementation. Runs any required "postinstall"
29
34
  * lifecycle scripts and binary linking.
30
35
  */
31
- export declare const command: CommandFn<BuildResult>;
36
+ export declare const command: CommandFn<BuildCommandResult>;
@@ -1,6 +1,8 @@
1
- import { build } from '@vltpkg/graph';
1
+ import { actual, build } from '@vltpkg/graph';
2
2
  import { error } from '@vltpkg/error-cause';
3
3
  import { commandUsage } from "../config/usage.js";
4
+ import { globalBinsLines, linkGlobalBins } from "../global.js";
5
+ import { resolveGlobalBinDir } from "../global-bin-dir.js";
4
6
  import { isErrorWithCause } from '@vltpkg/types';
5
7
  const skippedMessage = (count, reason) => `${count} package${count === 1 ? '' : 's'} not built, ${reason === 'unscanned' ?
6
8
  'Socket has not scanned them yet'
@@ -23,6 +25,8 @@ export const views = {
23
25
  if (skippedCount > 0) {
24
26
  messages.push(`⏳ ${skippedMessage(skippedCount, result.skippedReason)}`);
25
27
  }
28
+ if (result.global)
29
+ messages.push(...globalBinsLines(result.global));
26
30
  return messages.join('\n');
27
31
  },
28
32
  json: (result) => {
@@ -45,6 +49,7 @@ export const views = {
45
49
  : skippedList.length ?
46
50
  skippedMessage(skippedList.length, result.skippedReason)
47
51
  : 'No packages needed building.',
52
+ ...(result.global ? { global: result.global } : null),
48
53
  };
49
54
  },
50
55
  };
@@ -68,6 +73,9 @@ export const usage = () => commandUsage({
68
73
  value: '<query>',
69
74
  description: 'Query selector to filter packages using DSS syntax.',
70
75
  },
76
+ global: {
77
+ description: 'Build packages installed in the global project.',
78
+ },
71
79
  },
72
80
  });
73
81
  const isGraphRunError = (error) => typeof error === 'object' &&
@@ -81,6 +89,8 @@ const isGraphRunError = (error) => typeof error === 'object' &&
81
89
  */
82
90
  export const command = async (conf) => {
83
91
  const { options, projectRoot } = conf;
92
+ const binDir = conf.globalRoot ? await resolveGlobalBinDir(conf) : undefined;
93
+ let result;
84
94
  try {
85
95
  // Get target from option or first positional, default to all scripts
86
96
  const targetOption = conf.get('target');
@@ -89,7 +99,7 @@ export const command = async (conf) => {
89
99
  targetPositional ||
90
100
  ':scripts:not(:built):not(:malware)';
91
101
  // Run the build process using the graph build function
92
- const result = await build({
102
+ result = await build({
93
103
  ...options,
94
104
  projectRoot,
95
105
  packageJson: options.packageJson,
@@ -97,7 +107,6 @@ export const command = async (conf) => {
97
107
  scurry: options.scurry,
98
108
  target,
99
109
  });
100
- return result;
101
110
  }
102
111
  catch (cause) {
103
112
  const graphRunError = isErrorWithCause(cause) && isGraphRunError(cause.cause) ?
@@ -109,4 +118,23 @@ export const command = async (conf) => {
109
118
  }
110
119
  throw error('Build failed', { cause });
111
120
  }
121
+ if (!binDir)
122
+ return result;
123
+ // relink global bins, targets may be new
124
+ const graph = actual.load({
125
+ ...options,
126
+ projectRoot,
127
+ mainManifest: options.packageJson.read(projectRoot),
128
+ monorepo: options.monorepo,
129
+ scurry: options.scurry,
130
+ loadManifests: true,
131
+ });
132
+ const global = await linkGlobalBins(graph, {
133
+ projectRoot,
134
+ scurry: options.scurry,
135
+ force: options.force,
136
+ binDir,
137
+ fallback: () => resolveGlobalBinDir(conf, { selfUnwritable: true }),
138
+ });
139
+ return { ...result, global };
112
140
  };
@@ -6,6 +6,6 @@ export declare const needsNpmRegistry = true;
6
6
  export declare const usage: CommandUsage;
7
7
  export declare const views: {
8
8
  readonly json: (i: CIResult) => import("@vltpkg/graph").LockfileData;
9
- readonly human: import("../view.ts").LazyView<InstallResult>;
9
+ readonly human: import("../view.ts").LazyView<Partial<InstallResult>>;
10
10
  };
11
11
  export declare const command: CommandFn<CIResult>;
@@ -1,9 +1,10 @@
1
1
  import type { PromptFn } from '@vltpkg/vlx';
2
2
  import type { ExecResult } from '../exec-command.ts';
3
3
  import type { CommandFn, CommandUsage } from '../index.ts';
4
+ import { prettyPath } from '../shell.ts';
4
5
  export { views } from '../exec-command.ts';
5
6
  export declare const needsRegistry = true;
6
7
  export declare const usage: CommandUsage;
7
- export declare const prettyPath: (path: string) => string;
8
+ export { prettyPath };
8
9
  export declare const promptFn: PromptFn;
9
10
  export declare const command: CommandFn<ExecResult>;
@@ -1,10 +1,10 @@
1
1
  import { exec, execFG } from '@vltpkg/run';
2
2
  import * as vlx from '@vltpkg/vlx';
3
- import { homedir } from 'node:os';
4
3
  import { createInterface } from 'node:readline/promises';
5
4
  import { commandUsage } from "../config/usage.js";
6
5
  import { ExecCommand } from "../exec-command.js";
7
6
  import { styleTextStdout } from "../output.js";
7
+ import { prettyPath } from "../shell.js";
8
8
  export { views } from "../exec-command.js";
9
9
  export const needsRegistry = true;
10
10
  export const usage = () => commandUsage({
@@ -47,8 +47,7 @@ export const usage = () => commandUsage({
47
47
  },
48
48
  },
49
49
  });
50
- const HOME = homedir();
51
- export const prettyPath = (path) => path.startsWith(HOME) ? `~${path.substring(HOME.length)}` : path;
50
+ export { prettyPath };
52
51
  export const promptFn = async (pkgSpec, path, resolution) => {
53
52
  const response = await createInterface(process.stdin, process.stdout).question(`About to install: ${styleTextStdout(['bgWhiteBright', 'black', 'bold'], String(pkgSpec))}
54
53
  from: ${styleTextStdout(['bgWhiteBright', 'black', 'bold'], resolution)}
@@ -1,10 +1,11 @@
1
1
  import type { PromptFn } from '@vltpkg/vlx';
2
2
  import type { ExecResult } from '../exec-command.ts';
3
3
  import type { CommandFn, CommandUsage } from '../index.ts';
4
+ import { prettyPath } from '../shell.ts';
4
5
  export { views } from '../exec-command.ts';
5
6
  export declare const needsRegistry = true;
6
7
  export declare const needsNpmRegistry = true;
7
8
  export declare const usage: CommandUsage;
8
- export declare const prettyPath: (path: string) => string;
9
+ export { prettyPath };
9
10
  export declare const promptFn: PromptFn;
10
11
  export declare const command: CommandFn<ExecResult>;
@@ -1,11 +1,11 @@
1
1
  import { exec, execFG } from '@vltpkg/run';
2
2
  import * as vlx from '@vltpkg/vlx';
3
- import { homedir } from 'node:os';
4
3
  import { env, platform } from 'node:process';
5
4
  import { createInterface } from 'node:readline/promises';
6
5
  import { commandUsage } from "../config/usage.js";
7
6
  import { ExecCommand } from "../exec-command.js";
8
7
  import { styleTextStdout } from "../output.js";
8
+ import { prettyPath } from "../shell.js";
9
9
  export { views } from "../exec-command.js";
10
10
  export const needsRegistry = true;
11
11
  export const needsNpmRegistry = true;
@@ -113,8 +113,7 @@ export const usage = () => commandUsage({
113
113
  },
114
114
  },
115
115
  });
116
- const HOME = homedir();
117
- export const prettyPath = (path) => path.startsWith(HOME) ? `~${path.substring(HOME.length)}` : path;
116
+ export { prettyPath };
118
117
  export const promptFn = async (pkgSpec, path, resolution) => {
119
118
  const response = await createInterface(process.stdin, process.stdout).question(`About to install: ${styleTextStdout(['bgWhiteBright', 'black', 'bold'], String(pkgSpec))}
120
119
  from: ${styleTextStdout(['bgWhiteBright', 'black', 'bold'], resolution)}
@@ -3,7 +3,7 @@ import type { InstallResult } from '../install.ts';
3
3
  export declare class InstallReporter extends ViewClass {
4
4
  #private;
5
5
  start(): void;
6
- done(_result: InstallResult, { time }: {
6
+ done(_result: Partial<InstallResult>, { time }: {
7
7
  time: number;
8
8
  }): Promise<undefined>;
9
9
  error(err: unknown): void;
@@ -4,8 +4,11 @@ import Spinner from 'ink-spinner';
4
4
  import { createElement as $, Fragment, useEffect, useState, } from 'react';
5
5
  import { ViewClass } from "../../view.js";
6
6
  import { asError } from '@vltpkg/types';
7
+ import { isSelfUpdateArgs, selfUpdateLines, } from "../../self-update.js";
7
8
  import { persistedEntries } from "../../persist-spec-config.js";
8
9
  import { keptLine, savedLine, unmatchedLines, unscannedLines, } from "../../allow-skills.js";
10
+ import { globalBinsLines } from "../../global.js";
11
+ import { offerPathSetup } from "../../shell-setup/index.js";
9
12
  const labels = {
10
13
  build: 'resolving dependencies',
11
14
  actual: '',
@@ -75,6 +78,7 @@ const App = ({ trailer }) => {
75
78
  };
76
79
  export class InstallReporter extends ViewClass {
77
80
  #instance = null;
81
+ #plain = false;
78
82
  start() {
79
83
  // Ink measures the terminal on every render, and when stdout has no
80
84
  // width (a pipe, a CI log) it does so by spawning `tput cols` and
@@ -87,17 +91,35 @@ export class InstallReporter extends ViewClass {
87
91
  process.stdout.columns ||=
88
92
  Number.isInteger(cols) && cols > 0 ? cols : 80;
89
93
  }
94
+ // npm may write to the terminal: no live view
95
+ if (isSelfUpdateArgs(this.config)) {
96
+ this.#plain = true;
97
+ return;
98
+ }
90
99
  this.#instance = render($(App));
91
100
  }
92
101
  async done(_result, { time }) {
93
- let out = `Done in ${time}ms`;
102
+ const { self } = _result;
103
+ let out = self && !_result.graph ? '' : `Done in ${time}ms`;
94
104
  // prints a very complete message explaining users the next steps
95
105
  // in case there are packages to be built
106
+ const g = _result.global ? ' -g' : '';
96
107
  if (_result.buildQueue?.length) {
97
108
  out += `\n\n📦 ${_result.buildQueue.length} packages have install scripts defined & were not fully built\n`;
98
- out += '🔎 Run `vlt query :scripts` to list them\n';
99
- out +=
100
- '🔨 Run `vlt build` to run all required scripts to build installed packages.\n';
109
+ out += `🔎 Run \`vlt query${g} :scripts\` to list them\n`;
110
+ out += `🔨 Run \`vlt build${g}\` to run all required scripts to build installed packages.\n`;
111
+ }
112
+ if (self) {
113
+ for (const l of selfUpdateLines(self))
114
+ out += out ? `\n${l}` : l;
115
+ }
116
+ if (_result.global) {
117
+ // the self-update line says why
118
+ const bins = self?.status === 'updated' && self.case === 'bootstrap' ?
119
+ { ..._result.global, notWritable: undefined }
120
+ : _result.global;
121
+ for (const l of globalBinsLines(bins))
122
+ out += `\n${l}`;
101
123
  }
102
124
  const { linked = [], removed = [], conflicts = [], skipped = [], gitignore = [], unmatched = [], } = _result.skills ?? {};
103
125
  const s = (n) => (n === 1 ? '' : 's');
@@ -139,7 +161,25 @@ export class InstallReporter extends ViewClass {
139
161
  out += `\n${keptLine(a.stored)}`;
140
162
  else if (a)
141
163
  out += `\n${savedLine(a.query, 'project')}`;
142
- this.#instance?.rerender($(App, { trailer: out }));
164
+ // failed: npm already said why
165
+ if (!out)
166
+ return undefined;
167
+ if (this.#plain)
168
+ this.#instance = render($(Text, null, out));
169
+ else
170
+ this.#instance?.rerender($(App, { trailer: out }));
171
+ if (_result.global) {
172
+ await offerPathSetup(_result.global, {
173
+ globalDir: this.config.projectRoot,
174
+ yes: !!this.config.get('yes'),
175
+ noModifyPath: !!this.config.get('no-modify-path'),
176
+ // ink owns the terminal until unmounted
177
+ beforeOutput: () => {
178
+ this.#instance?.unmount();
179
+ this.#instance = null;
180
+ },
181
+ });
182
+ }
143
183
  return undefined;
144
184
  }
145
185
  error(err) {
@@ -1,4 +1,6 @@
1
1
  import type { AllowSkillsSave } from '../allow-skills.ts';
2
+ import type { GlobalBins } from '../global.ts';
3
+ import type { SelfUpdate } from '../self-update.ts';
2
4
  import type { MigratedWorkspaces } from '../migrate-workspaces.ts';
3
5
  import type { SpecConfigPersistPlan } from '../persist-spec-config.ts';
4
6
  import type { DepID } from '@vltpkg/dep-id';
@@ -37,12 +39,28 @@ export type InstallResult = {
37
39
  * is stored.
38
40
  */
39
41
  allowSkills?: AllowSkillsSave;
42
+ /**
43
+ * Bins linked by a `--global` install.
44
+ */
45
+ global?: GlobalBins;
46
+ /**
47
+ * `vlt install -g vlt`
48
+ */
49
+ self?: SelfUpdate;
50
+ };
51
+ /** self-update handed off or nothing to do */
52
+ export type InstallCommandResult = InstallResult | {
53
+ self: SelfUpdate;
40
54
  };
41
55
  export declare const needsRegistry = true;
42
56
  export declare const needsNpmRegistry = true;
43
57
  export declare const usage: CommandUsage;
44
58
  export declare const views: {
45
- readonly json: (i: InstallResult) => {
59
+ readonly json: (i: InstallCommandResult) => {
60
+ self: SelfUpdate;
61
+ } | {
62
+ self?: SelfUpdate | undefined;
63
+ global?: GlobalBins | undefined;
46
64
  allowSkills?: AllowSkillsSave | undefined;
47
65
  skills?: {
48
66
  unscanned?: string[];
@@ -76,6 +94,6 @@ export declare const views: {
76
94
  }[];
77
95
  removed: number;
78
96
  };
79
- readonly human: import("../view.ts").LazyView<InstallResult>;
97
+ readonly human: import("../view.ts").LazyView<Partial<InstallResult>>;
80
98
  };
81
- export declare const command: CommandFn<InstallResult>;
99
+ export declare const command: CommandFn<InstallCommandResult>;
@@ -2,6 +2,9 @@ import { commandUsage } from "../config/usage.js";
2
2
  import { install } from '@vltpkg/graph';
3
3
  import { saveAllowSkills } from "../allow-skills.js";
4
4
  import { parseAddArgs } from "../parse-add-remove-args.js";
5
+ import { assertGlobalOptions, linkGlobalBins, parseGlobalAddArgs, removeGlobalWorkspaces, } from "../global.js";
6
+ import { resolveGlobalBinDir } from "../global-bin-dir.js";
7
+ import { deferSelfRemove, runningStoreDep, selfUpdate, } from "../self-update.js";
5
8
  import { planSpecConfigPersist } from "../persist-spec-config.js";
6
9
  import { asUnknownSpecPrefix } from "../require-registry.js";
7
10
  import { addTypesDeps } from "../save-types.js";
@@ -69,6 +72,9 @@ export const usage = () => commandUsage({
69
72
  value: '<query>',
70
73
  description: "Link agent skills of packages matching this DSS query into the project's agent skill dirs. Saved to vlt.json.",
71
74
  },
75
+ global: {
76
+ description: 'Install packages globally, each in its own workspace of the global project, linking their bins. `vlt install -g vlt` updates vlt itself.',
77
+ },
72
78
  },
73
79
  });
74
80
  // link mounts; `unchanged` left out
@@ -78,6 +84,8 @@ const skillsJson = ({ linked, unchanged: _, ...rest }) => ({
78
84
  });
79
85
  export const views = {
80
86
  json: i => {
87
+ if (!('graph' in i))
88
+ return { self: i.self };
81
89
  const added = i.diff?.nodes.add ?? new Set();
82
90
  const deleted = i.diff?.nodes.delete ?? new Set();
83
91
  // The actual (from) graph represents what was on disk before the install.
@@ -131,7 +139,7 @@ export const views = {
131
139
  ...(i.buildQueue?.length ?
132
140
  {
133
141
  buildQueue: i.buildQueue,
134
- message: `${i.buildQueue.length} packages that will need to be built, run "vlt build" to complete the install.`,
142
+ message: `${i.buildQueue.length} packages that will need to be built, run "vlt build${i.global ? ' -g' : ''}" to complete the install.`,
135
143
  }
136
144
  : null),
137
145
  ...(i.persistedConfig ?
@@ -142,6 +150,8 @@ export const views = {
142
150
  : null),
143
151
  ...(i.skills ? { skills: skillsJson(i.skills) } : null),
144
152
  ...(i.allowSkills ? { allowSkills: i.allowSkills } : null),
153
+ ...(i.global ? { global: i.global } : null),
154
+ ...(i.self ? { self: i.self } : null),
145
155
  };
146
156
  },
147
157
  human: lazyView(async () => (await import('./install/reporter.ts')).InstallReporter),
@@ -149,42 +159,52 @@ export const views = {
149
159
  export const command = async (conf) => {
150
160
  // TODO: we should probably throw an error if the user
151
161
  // tries to install using view=mermaid
152
- const monorepo = conf.options.monorepo;
153
- const scurry = conf.options.scurry;
154
- const { add } = parseAddArgs(conf, scurry, monorepo);
162
+ if (conf.globalRoot)
163
+ assertGlobalOptions(conf);
164
+ // throws before any global ws dir is created
155
165
  const persist = planSpecConfigPersist(conf);
156
- if (conf.values['save-types'])
157
- await addTypesDeps(conf, add);
158
- const frozenLockfile = conf.options['frozen-lockfile'];
159
- const expectLockfile = conf.options['expect-lockfile'];
160
- const lockfileOnly = conf.options['lockfile-only'];
161
- const saveExact = conf.values['save-exact'];
162
- const savePrefix = conf.values['save-prefix'];
163
- /* c8 ignore start */
164
- const allowScripts = conf.get('allow-scripts') ?
165
- String(conf.get('allow-scripts'))
166
- : ':not(*)';
167
- /* c8 ignore stop */
168
- const allowSkills = conf.get('allow-skills');
166
+ // vlt i -g vlt: before any global ws or bin dir is created
167
+ const selfPlan = await selfUpdate(conf);
168
+ if (selfPlan && 'done' in selfPlan)
169
+ return { self: selfPlan.done };
170
+ const binDir = conf.globalRoot ?
171
+ await resolveGlobalBinDir(conf, {
172
+ selfUnwritable: !!selfPlan?.selfUnwritable,
173
+ })
174
+ : undefined;
175
+ // before reading options, it may reset them
176
+ const globalAdd = conf.globalRoot ? await parseGlobalAddArgs(conf) : undefined;
177
+ // never removed from under itself
178
+ const keep = conf.globalRoot ?
179
+ await runningStoreDep(conf.globalRoot)
180
+ : undefined;
169
181
  const installStart = Date.now();
170
- const { buildQueue, graph, diff, skills } = await install({
171
- ...conf.options,
172
- frozenLockfile,
173
- expectLockfile,
174
- allowScripts,
175
- allowSkills,
176
- skillsDirs: conf.get('skills-dir'),
177
- lockfileOnly,
178
- saveExact,
179
- savePrefix,
180
- }, add).catch((er) => {
182
+ const { buildQueue, graph, diff, skills } = await runInstall(conf, globalAdd?.add, keep).catch(async (er) => {
183
+ if (globalAdd)
184
+ await removeGlobalWorkspaces(globalAdd.created);
181
185
  throw asUnknownSpecPrefix(er);
182
186
  });
183
- const migrated = frozenLockfile || expectLockfile ? undefined : (migrateWorkspaces(conf));
187
+ const migrated = (conf.options['frozen-lockfile'] ||
188
+ conf.options['expect-lockfile']) ?
189
+ undefined
190
+ : migrateWorkspaces(conf);
191
+ if (conf.globalRoot)
192
+ await deferSelfRemove(conf.globalRoot, graph, keep);
193
+ const global = globalAdd &&
194
+ binDir &&
195
+ (await linkGlobalBins(graph, {
196
+ projectRoot: conf.options.projectRoot,
197
+ scurry: conf.options.scurry,
198
+ force: conf.options.force,
199
+ binDir,
200
+ fallback: () => resolveGlobalBinDir(conf, { selfUnwritable: true }),
201
+ }, globalAdd.importers));
184
202
  if (persist)
185
203
  await conf.addConfigToFile(persist.which, persist.values);
186
204
  // nothing linked on --lockfile-only
187
- const allowSkillsSave = lockfileOnly ? undefined : await saveAllowSkills(conf);
205
+ const allowSkillsSave = conf.options['lockfile-only'] ?
206
+ undefined
207
+ : await saveAllowSkills(conf);
188
208
  /* c8 ignore next 9 - telemetry is best-effort */
189
209
  try {
190
210
  trackInstall({
@@ -201,5 +221,39 @@ export const command = async (conf) => {
201
221
  ...(migrated ? { migratedWorkspaces: migrated } : null),
202
222
  ...(skills ? { skills } : null),
203
223
  ...(allowSkillsSave ? { allowSkills: allowSkillsSave } : null),
224
+ ...(global ? { global } : null),
225
+ ...(selfPlan ? { self: await selfPlan.finish(global) } : null),
204
226
  };
205
227
  };
228
+ // async, so any throw rolls back global ws dirs
229
+ const runInstall = async (conf, globalAdd, keep) => {
230
+ const monorepo = conf.options.monorepo;
231
+ const scurry = conf.options.scurry;
232
+ const add = globalAdd ?? parseAddArgs(conf, scurry, monorepo).add;
233
+ // global ws has only the package as dep
234
+ if (conf.values['save-types'] && !globalAdd)
235
+ await addTypesDeps(conf, add);
236
+ const frozenLockfile = conf.options['frozen-lockfile'];
237
+ const expectLockfile = conf.options['expect-lockfile'];
238
+ const lockfileOnly = conf.options['lockfile-only'];
239
+ const saveExact = conf.values['save-exact'];
240
+ const savePrefix = conf.values['save-prefix'];
241
+ /* c8 ignore start */
242
+ const allowScripts = conf.get('allow-scripts') ?
243
+ String(conf.get('allow-scripts'))
244
+ : ':not(*)';
245
+ /* c8 ignore stop */
246
+ const allowSkills = conf.get('allow-skills');
247
+ return install({
248
+ ...conf.options,
249
+ frozenLockfile,
250
+ expectLockfile,
251
+ allowScripts,
252
+ allowSkills,
253
+ skillsDirs: conf.get('skills-dir'),
254
+ lockfileOnly,
255
+ saveExact,
256
+ savePrefix,
257
+ ...(keep ? { keepNodes: new Set([keep]) } : null),
258
+ }, add);
259
+ };
@@ -63,6 +63,9 @@ export const usage = () => commandUsage({
63
63
  value: '[human | json | mermaid | svg | png | count]',
64
64
  description: 'Output format. Defaults to human-readable or json if no tty. Use svg or png to render the dependency graph as an image and open it. Count outputs the number of dependency relationships in the result.',
65
65
  },
66
+ global: {
67
+ description: 'List globally installed packages.',
68
+ },
66
69
  },
67
70
  });
68
71
  export const views = {
@@ -101,7 +104,8 @@ export const command = async (conf) => {
101
104
  const usesHostContext = [queryString, scopeQueryString].some(q => q?.includes(':host('));
102
105
  // optionally load the cwd graph if we found a package.json file
103
106
  if (mainManifest) {
104
- if (!usesHostContext) {
107
+ // the global project is always vlt managed
108
+ if (!usesHostContext && !conf.globalRoot) {
105
109
  assertVltInstalled(conf.options.projectRoot, 'ls');
106
110
  }
107
111
  graph = actual.load({
@@ -80,6 +80,9 @@ export const usage = () => commandUsage({
80
80
  value: '[human | json | mermaid | svg | png | count]',
81
81
  description: 'Output format. Defaults to human-readable or json if no tty. Use svg or png to render the dependency graph as an image and open it. Count outputs the number of dependency relationships in the result.',
82
82
  },
83
+ global: {
84
+ description: 'Query the global project.',
85
+ },
83
86
  },
84
87
  });
85
88
  const validateExpectedResult = (conf, edges) => {
@@ -128,7 +131,8 @@ export const command = async (conf) => {
128
131
  const usesHostContext = [queryString, scopeQueryString].some(q => q?.includes(':host('));
129
132
  // optionally load the cwd graph if we found a package.json file
130
133
  if (mainManifest) {
131
- if (!usesHostContext) {
134
+ // the global project is always vlt managed
135
+ if (!usesHostContext && !conf.globalRoot) {
132
136
  assertVltInstalled(conf.options.projectRoot, 'query');
133
137
  }
134
138
  graph = actual.load({
@@ -1,3 +1,5 @@
1
+ import type { Shell } from '../shell.ts';
2
+ import type { PathSetupFile } from '../shell-setup/index.ts';
1
3
  import type { CommandFn, CommandUsage } from '../index.ts';
2
4
  /** Where users sign up for / log into a vlt.io account. */
3
5
  export declare const VLT_SIGNUP_URL = "https://www.vlt.io";
@@ -29,9 +31,33 @@ export type SetupResult = {
29
31
  * the project's `vlt.json`, so the result won't take effect here.
30
32
  */
31
33
  shadowedByProject?: boolean;
34
+ /**
35
+ * PATH step outcome; absent when skipped (--yes, no terminal,
36
+ * no-modify-path)
37
+ */
38
+ path?: SetupPathResult['path'] | SetupPathFailed;
39
+ };
40
+ export type SetupPathFailed = {
41
+ status: 'failed';
42
+ /** why; may hold the manual line */
43
+ message: string;
44
+ };
45
+ export type SetupPathResult = {
46
+ path: {
47
+ status: 'added' | 'unchanged' | 'in-path' | 'disabled' | 'cancelled';
48
+ /** canonical dir added to the PATH */
49
+ dir: string;
50
+ shell: Shell;
51
+ files: PathSetupFile[];
52
+ backups: string[];
53
+ /** manual line for the shell */
54
+ command: string;
55
+ /** loads it in the current shell */
56
+ source: string;
57
+ };
32
58
  };
33
59
  export declare const views: {
34
- readonly human: (result: SetupResult) => string;
60
+ readonly human: (result: SetupResult | SetupPathResult) => string;
35
61
  };
36
62
  export declare const usage: CommandUsage;
37
- export declare const command: CommandFn<SetupResult>;
63
+ export declare const command: CommandFn<SetupResult | SetupPathResult>;