@vltpkg/cli-sdk 1.4.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 (74) hide show
  1. package/dist/allow-skills.d.ts +36 -0
  2. package/dist/allow-skills.js +49 -0
  3. package/dist/commands/build.d.ts +15 -4
  4. package/dist/commands/build.js +50 -12
  5. package/dist/commands/ci.d.ts +2 -2
  6. package/dist/commands/ci.js +10 -2
  7. package/dist/commands/create.d.ts +2 -1
  8. package/dist/commands/create.js +2 -3
  9. package/dist/commands/exec.d.ts +2 -1
  10. package/dist/commands/exec.js +2 -3
  11. package/dist/commands/init.js +4 -3
  12. package/dist/commands/install/reporter.d.ts +1 -1
  13. package/dist/commands/install/reporter.js +91 -5
  14. package/dist/commands/install.d.ts +50 -4
  15. package/dist/commands/install.js +107 -25
  16. package/dist/commands/list.js +8 -2
  17. package/dist/commands/publish.d.ts +0 -1
  18. package/dist/commands/publish.js +27 -16
  19. package/dist/commands/query.js +8 -2
  20. package/dist/commands/setup.d.ts +28 -2
  21. package/dist/commands/setup.js +185 -3
  22. package/dist/commands/skills.d.ts +56 -0
  23. package/dist/commands/skills.js +267 -0
  24. package/dist/commands/token.js +1 -1
  25. package/dist/commands/uninstall.d.ts +15 -4
  26. package/dist/commands/uninstall.js +32 -4
  27. package/dist/commands/unpublish.d.ts +0 -1
  28. package/dist/commands/unpublish.js +17 -4
  29. package/dist/commands/update.d.ts +13 -1
  30. package/dist/commands/update.js +18 -1
  31. package/dist/config/definition.d.ts +52 -0
  32. package/dist/config/definition.js +109 -1
  33. package/dist/config/index.d.ts +26 -3
  34. package/dist/config/index.js +149 -7
  35. package/dist/custom-help.js +4 -0
  36. package/dist/global-bin-dir.d.ts +37 -0
  37. package/dist/global-bin-dir.js +138 -0
  38. package/dist/global-project.d.ts +18 -0
  39. package/dist/global-project.js +67 -0
  40. package/dist/global-state.d.ts +24 -0
  41. package/dist/global-state.js +94 -0
  42. package/dist/global.d.ts +78 -0
  43. package/dist/global.js +409 -0
  44. package/dist/index.js +14 -3
  45. package/dist/install-receipt.d.ts +40 -0
  46. package/dist/install-receipt.js +69 -0
  47. package/dist/load-command.d.ts +2 -0
  48. package/dist/migrate-workspaces.d.ts +16 -0
  49. package/dist/migrate-workspaces.js +44 -0
  50. package/dist/output.js +13 -0
  51. package/dist/parse-add-remove-args.js +2 -14
  52. package/dist/print-err.js +4 -1
  53. package/dist/publish-config.d.ts +9 -0
  54. package/dist/publish-config.js +8 -0
  55. package/dist/query-auth.d.ts +3 -2
  56. package/dist/query-auth.js +6 -4
  57. package/dist/reload-config.js +2 -1
  58. package/dist/require-registry.d.ts +7 -0
  59. package/dist/require-registry.js +13 -1
  60. package/dist/self-update.d.ts +74 -0
  61. package/dist/self-update.js +401 -0
  62. package/dist/shell-setup/env.d.ts +9 -0
  63. package/dist/shell-setup/env.js +25 -0
  64. package/dist/shell-setup/index.d.ts +69 -0
  65. package/dist/shell-setup/index.js +213 -0
  66. package/dist/shell-setup/posix.d.ts +36 -0
  67. package/dist/shell-setup/posix.js +158 -0
  68. package/dist/shell-setup/windows.d.ts +39 -0
  69. package/dist/shell-setup/windows.js +151 -0
  70. package/dist/shell.d.ts +18 -0
  71. package/dist/shell.js +110 -0
  72. package/dist/skills-prompt.d.ts +28 -0
  73. package/dist/skills-prompt.js +78 -0
  74. package/package.json +30 -28
@@ -0,0 +1,36 @@
1
+ import type { UnmatchedPattern, UnscannedSkills } from '@vltpkg/graph';
2
+ import type { WhichConfig } from '@vltpkg/vlt-json';
3
+ import type { LoadedConfig } from './config/index.ts';
4
+ /** An `allow-skills` query stored in a config file. */
5
+ export type StoredAllowSkills = {
6
+ query: string;
7
+ which: WhichConfig;
8
+ };
9
+ /**
10
+ * Outcome of saving `--allow-skills` after install/update. `stored`
11
+ * set: another query is stored and was kept.
12
+ */
13
+ export type AllowSkillsSave = {
14
+ query: string;
15
+ stored?: StoredAllowSkills;
16
+ };
17
+ /**
18
+ * The top-level `allow-skills` of the project vlt.json, else the user
19
+ * one. A project `null` or `''` drops the user's.
20
+ */
21
+ export declare const storedAllowSkills: (conf: Pick<LoadedConfig, "layers">) => StoredAllowSkills | undefined;
22
+ /** The file's name in messages. */
23
+ export declare const configLabel: (which: WhichConfig) => string;
24
+ /**
25
+ * After a successful install/update: save an explicit `--allow-skills`
26
+ * to the project vlt.json, unless another query is stored.
27
+ */
28
+ export declare const saveAllowSkills: (conf: LoadedConfig) => Promise<AllowSkillsSave | undefined>;
29
+ /** Warnings for `:skill()` patterns that matched nothing. */
30
+ export declare const unmatchedLines: (unmatched?: UnmatchedPattern[]) => string[];
31
+ /** Skills left out: their packages have no security data. */
32
+ export declare const unscannedLines: ({ unscanned, unscannedReason, }: UnscannedSkills) => string[];
33
+ /** `allow-skills` saved to a config file. */
34
+ export declare const savedLine: (query: string, which: WhichConfig) => string;
35
+ /** `--allow-skills` not saved: another query is stored. */
36
+ export declare const keptLine: (s: StoredAllowSkills) => string;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * The top-level `allow-skills` of the project vlt.json, else the user
3
+ * one. A project `null` or `''` drops the user's.
4
+ */
5
+ export const storedAllowSkills = (conf) => {
6
+ const { project, user } = conf.layers;
7
+ const p = project?.['allow-skills'];
8
+ if (p)
9
+ return { query: p, which: 'project' };
10
+ if (project && 'allow-skills' in project)
11
+ return undefined;
12
+ const u = user?.['allow-skills'];
13
+ return u ? { query: u, which: 'user' } : undefined;
14
+ };
15
+ /** The file's name in messages. */
16
+ export const configLabel = (which) => which === 'user' ? 'user vlt.json' : 'vlt.json';
17
+ /**
18
+ * After a successful install/update: save an explicit `--allow-skills`
19
+ * to the project vlt.json, unless another query is stored.
20
+ */
21
+ export const saveAllowSkills = async (conf) => {
22
+ const query = conf.explicit['allow-skills'];
23
+ if (!query || query === ':not(*)')
24
+ return undefined;
25
+ const stored = storedAllowSkills(conf);
26
+ if (stored?.query === query)
27
+ return undefined;
28
+ if (stored)
29
+ return { query, stored };
30
+ await conf.addConfigToFile('project', { 'allow-skills': query });
31
+ return { query };
32
+ };
33
+ /** Warnings for `:skill()` patterns that matched nothing. */
34
+ export const unmatchedLines = (unmatched = []) => unmatched.map(({ pattern, skills }) => `⚠️ No skill matches "${pattern}"; ${!skills ? 'no installed package matches its selector'
35
+ : skills.length ?
36
+ `the selected packages ship: ${skills.join(', ')}`
37
+ : 'the selected packages ship no skills'}`);
38
+ /** Skills left out: their packages have no security data. */
39
+ export const unscannedLines = ({ unscanned = [], unscannedReason, }) => unscanned.length ?
40
+ [
41
+ `⏳ ${unscanned.length} skill${unscanned.length === 1 ? '' : 's'} left out, ${unscannedReason === 'timeout' ?
42
+ 'security data timed out'
43
+ : 'Socket has not scanned their packages yet'}: ${unscanned.join(', ')}. Try again later.`,
44
+ ]
45
+ : [];
46
+ /** `allow-skills` saved to a config file. */
47
+ export const savedLine = (query, which) => `Saved allow-skills="${query}" to ${configLabel(which)}`;
48
+ /** `--allow-skills` not saved: another query is stored. */
49
+ export const keptLine = (s) => `allow-skills is already set (${configLabel(s.which)}): "${s.query}"; not replaced. Run vlt skills link to change it, or edit ${configLabel(s.which)}`;
@@ -1,8 +1,20 @@
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;
11
+ message: string;
12
+ skipped?: {
13
+ id: import("@vltpkg/dep-id").DepID;
14
+ name: string;
15
+ version: string | undefined;
16
+ }[] | undefined;
17
+ skippedReason?: "timeout" | "unscanned" | undefined;
6
18
  success: {
7
19
  id: import("@vltpkg/dep-id").DepID;
8
20
  name: string;
@@ -13,7 +25,6 @@ export declare const views: {
13
25
  name: string;
14
26
  version: string | undefined;
15
27
  }[];
16
- message: string;
17
28
  };
18
29
  };
19
30
  export declare const needsRegistry = true;
@@ -22,4 +33,4 @@ export declare const usage: CommandUsage;
22
33
  * Build command implementation. Runs any required "postinstall"
23
34
  * lifecycle scripts and binary linking.
24
35
  */
25
- export declare const command: CommandFn<BuildResult>;
36
+ export declare const command: CommandFn<BuildCommandResult>;
@@ -1,40 +1,55 @@
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';
7
+ const skippedMessage = (count, reason) => `${count} package${count === 1 ? '' : 's'} not built, ${reason === 'unscanned' ?
8
+ 'Socket has not scanned them yet'
9
+ : 'security data timed out'}. Run "vlt build" again later.`;
5
10
  export const views = {
6
11
  human: (result) => {
7
12
  const successCount = result.success.length;
8
13
  const failureCount = result.failure.length;
14
+ const skippedCount = result.skipped?.length ?? 0;
9
15
  const messages = [];
10
16
  if (successCount > 0) {
11
17
  messages.push(`🔨 Built ${successCount} package${successCount === 1 ? '' : 's'} successfully.`);
12
18
  }
13
- else {
19
+ else if (!skippedCount) {
14
20
  messages.push('📦 All packages are already built.');
15
21
  }
16
22
  if (failureCount > 0) {
17
23
  messages.push(`🔎 ${failureCount} optional package${ /* c8 ignore next */failureCount === 1 ? '' : 's'} failed to build.`);
18
24
  }
25
+ if (skippedCount > 0) {
26
+ messages.push(`⏳ ${skippedMessage(skippedCount, result.skippedReason)}`);
27
+ }
28
+ if (result.global)
29
+ messages.push(...globalBinsLines(result.global));
19
30
  return messages.join('\n');
20
31
  },
21
32
  json: (result) => {
22
- const successList = result.success.map(node => ({
23
- id: node.id,
24
- name: node.name,
25
- version: node.version,
26
- }));
27
- const failureList = result.failure.map(node => ({
33
+ const toJSON = (node) => ({
28
34
  id: node.id,
29
35
  name: node.name,
30
36
  version: node.version,
31
- }));
37
+ });
38
+ const successList = result.success.map(toJSON);
39
+ const failureList = result.failure.map(toJSON);
40
+ const skippedList = result.skipped?.map(toJSON) ?? [];
32
41
  return {
33
42
  success: successList,
34
43
  failure: failureList,
44
+ ...(skippedList.length ?
45
+ { skipped: skippedList, skippedReason: result.skippedReason }
46
+ : {}),
35
47
  message: successList.length > 0 ?
36
48
  `Built ${successList.length} package${successList.length === 1 ? '' : 's'}.`
37
- : 'No packages needed building.',
49
+ : skippedList.length ?
50
+ skippedMessage(skippedList.length, result.skippedReason)
51
+ : 'No packages needed building.',
52
+ ...(result.global ? { global: result.global } : null),
38
53
  };
39
54
  },
40
55
  };
@@ -58,6 +73,9 @@ export const usage = () => commandUsage({
58
73
  value: '<query>',
59
74
  description: 'Query selector to filter packages using DSS syntax.',
60
75
  },
76
+ global: {
77
+ description: 'Build packages installed in the global project.',
78
+ },
61
79
  },
62
80
  });
63
81
  const isGraphRunError = (error) => typeof error === 'object' &&
@@ -71,6 +89,8 @@ const isGraphRunError = (error) => typeof error === 'object' &&
71
89
  */
72
90
  export const command = async (conf) => {
73
91
  const { options, projectRoot } = conf;
92
+ const binDir = conf.globalRoot ? await resolveGlobalBinDir(conf) : undefined;
93
+ let result;
74
94
  try {
75
95
  // Get target from option or first positional, default to all scripts
76
96
  const targetOption = conf.get('target');
@@ -79,7 +99,7 @@ export const command = async (conf) => {
79
99
  targetPositional ||
80
100
  ':scripts:not(:built):not(:malware)';
81
101
  // Run the build process using the graph build function
82
- const result = await build({
102
+ result = await build({
83
103
  ...options,
84
104
  projectRoot,
85
105
  packageJson: options.packageJson,
@@ -87,7 +107,6 @@ export const command = async (conf) => {
87
107
  scurry: options.scurry,
88
108
  target,
89
109
  });
90
- return result;
91
110
  }
92
111
  catch (cause) {
93
112
  const graphRunError = isErrorWithCause(cause) && isGraphRunError(cause.cause) ?
@@ -99,4 +118,23 @@ export const command = async (conf) => {
99
118
  }
100
119
  throw error('Build failed', { cause });
101
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 };
102
140
  };
@@ -1,11 +1,11 @@
1
1
  import type { CommandFn, CommandUsage } from '../index.ts';
2
2
  import type { InstallResult } from './install.ts';
3
- export type CIResult = Pick<InstallResult, 'buildQueue' | 'graph'>;
3
+ export type CIResult = Pick<InstallResult, 'buildQueue' | 'graph' | 'skills'>;
4
4
  export declare const needsRegistry = true;
5
5
  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,5 +1,6 @@
1
1
  import { install } from '@vltpkg/graph';
2
2
  import { commandUsage } from "../config/usage.js";
3
+ import { warnIgnoredWorkspaces } from "../migrate-workspaces.js";
3
4
  import { lazyView } from "../view.js";
4
5
  export const needsRegistry = true;
5
6
  export const needsNpmRegistry = true;
@@ -22,6 +23,10 @@ export const usage = () => commandUsage({
22
23
  value: '<query>',
23
24
  description: 'Filter which packages are allowed to run lifecycle scripts using DSS query syntax.',
24
25
  },
26
+ 'allow-skills': {
27
+ value: '<query>',
28
+ description: "Link agent skills of packages matching this DSS query into the project's agent skill dirs.",
29
+ },
25
30
  'lockfile-only': {
26
31
  description: 'Only update lockfile and package.json files; skip node_modules operations.',
27
32
  },
@@ -32,15 +37,18 @@ export const views = {
32
37
  human: lazyView(async () => (await import('./install/reporter.ts')).InstallReporter),
33
38
  };
34
39
  export const command = async (conf) => {
40
+ warnIgnoredWorkspaces(conf);
35
41
  const ciOptions = {
36
42
  ...conf.options,
37
43
  // same default as install: no scripts
38
44
  allowScripts: conf.get('allow-scripts') ?? ':not(*)',
45
+ allowSkills: conf.get('allow-skills'),
46
+ skillsDirs: conf.get('skills-dir'),
39
47
  expectLockfile: true,
40
48
  frozenLockfile: true,
41
49
  cleanInstall: true,
42
50
  lockfileOnly: conf.options['lockfile-only'],
43
51
  };
44
- const { buildQueue, graph } = await install(ciOptions);
45
- return { buildQueue, graph };
52
+ const { buildQueue, graph, skills } = await install(ciOptions);
53
+ return { buildQueue, graph, ...(skills ? { skills } : null) };
46
54
  };
@@ -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)}
@@ -4,7 +4,7 @@ import { minimatch } from 'minimatch';
4
4
  import { init } from '@vltpkg/init';
5
5
  import { install } from '@vltpkg/graph';
6
6
  import { load, save } from '@vltpkg/vlt-json';
7
- import { assertWSConfig, resolveWSConfig, splitNegatedPatterns, } from '@vltpkg/workspaces';
7
+ import { assertWSConfig, migratableWSConfig, resolveWSConfig, splitNegatedPatterns, } from '@vltpkg/workspaces';
8
8
  import { commandUsage } from "../config/usage.js";
9
9
  export const usage = () => commandUsage({
10
10
  command: 'init',
@@ -57,14 +57,15 @@ export const command = async (conf) => {
57
57
  if (conf.values.workspace?.length) {
58
58
  // the *effective* config, which may come from the root package.json
59
59
  // rather than vlt.json
60
- const { config: parsedWSConfig, source } = resolveWSConfig(conf.options.projectRoot, conf.options.packageJson);
60
+ const resolved = resolveWSConfig(conf.options.projectRoot, conf.options.packageJson);
61
+ const parsedWSConfig = resolved.config;
61
62
  // what gets written back to vlt.json. The raw vlt.json value is
62
63
  // reused as-is so its existing shape is preserved. When the
63
64
  // definitions live in package.json or pnpm-workspace.yaml instead,
64
65
  // seed vlt.json with them first -- otherwise adding a `workspaces`
65
66
  // field here would shadow every workspace already declared there.
66
67
  const workspacesConfig = load('workspaces', assertWSConfig) ??
67
- (source ? Object.values(parsedWSConfig).flat() : undefined);
68
+ migratableWSConfig(resolved);
68
69
  const results = [];
69
70
  const addToConfig = [];
70
71
  // create a new package.json file for every workspace
@@ -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,7 +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";
9
+ import { keptLine, savedLine, unmatchedLines, unscannedLines, } from "../../allow-skills.js";
10
+ import { globalBinsLines } from "../../global.js";
11
+ import { offerPathSetup } from "../../shell-setup/index.js";
8
12
  const labels = {
9
13
  build: 'resolving dependencies',
10
14
  actual: '',
@@ -74,26 +78,108 @@ const App = ({ trailer }) => {
74
78
  };
75
79
  export class InstallReporter extends ViewClass {
76
80
  #instance = null;
81
+ #plain = false;
77
82
  start() {
83
+ // Ink measures the terminal on every render, and when stdout has no
84
+ // width (a pipe, a CI log) it does so by spawning `tput cols` and
85
+ // `tput lines`: thousands of processes over one install. Give a
86
+ // non-TTY stdout the width Ink would fall back to anyway, so the
87
+ // output is the same and nothing is spawned.
88
+ if (!process.stdout.isTTY) {
89
+ // only a sane COLUMNS counts; -1 or Infinity would be truthy
90
+ const cols = Number(process.env.COLUMNS);
91
+ process.stdout.columns ||=
92
+ Number.isInteger(cols) && cols > 0 ? cols : 80;
93
+ }
94
+ // npm may write to the terminal: no live view
95
+ if (isSelfUpdateArgs(this.config)) {
96
+ this.#plain = true;
97
+ return;
98
+ }
78
99
  this.#instance = render($(App));
79
100
  }
80
101
  async done(_result, { time }) {
81
- let out = `Done in ${time}ms`;
102
+ const { self } = _result;
103
+ let out = self && !_result.graph ? '' : `Done in ${time}ms`;
82
104
  // prints a very complete message explaining users the next steps
83
105
  // in case there are packages to be built
106
+ const g = _result.global ? ' -g' : '';
84
107
  if (_result.buildQueue?.length) {
85
108
  out += `\n\n📦 ${_result.buildQueue.length} packages have install scripts defined & were not fully built\n`;
86
- out += '🔎 Run `vlt query :scripts` to list them\n';
87
- out +=
88
- '🔨 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}`;
123
+ }
124
+ const { linked = [], removed = [], conflicts = [], skipped = [], gitignore = [], unmatched = [], } = _result.skills ?? {};
125
+ const s = (n) => (n === 1 ? '' : 's');
126
+ const skills = [];
127
+ if (linked.length) {
128
+ // one per skill, linked in any number of dirs
129
+ const n = new Set(linked.map(r => `${r.id}\0${r.skill}`)).size;
130
+ const dirs = [...new Set(linked.map(r => r.dir))].join(', ');
131
+ skills.push(`🔗 Linked ${n} agent skill${s(n)} into ${dirs}`);
132
+ }
133
+ if (removed.length) {
134
+ skills.push(`🧹 Removed ${removed.length} stale skill link${s(removed.length)}: ${removed.join(', ')}`);
89
135
  }
136
+ if (conflicts.length) {
137
+ skills.push(`⚠️ ${conflicts.length} skill link${s(conflicts.length)} skipped, path in use or taken by another package: ${conflicts.join(', ')}`);
138
+ }
139
+ skills.push(...unmatchedLines(unmatched));
140
+ if (skipped.length) {
141
+ skills.push(`⚠️ Skipped ${skipped.join(', ')}`);
142
+ }
143
+ if (gitignore.length) {
144
+ skills.push(`⚠️ Could not update ${gitignore.join(', ')}`);
145
+ }
146
+ skills.push(...unscannedLines(_result.skills ?? {}));
147
+ if (skills.length)
148
+ out += `\n${skills.join('\n')}\n`;
90
149
  const saved = _result.persistedConfig;
91
150
  if (saved) {
92
151
  for (const e of persistedEntries(saved.values)) {
93
152
  out += `\nSaved ${e} to ${saved.which} vlt.json`;
94
153
  }
95
154
  }
96
- this.#instance?.rerender($(App, { trailer: out }));
155
+ const migrated = _result.migratedWorkspaces;
156
+ if (migrated) {
157
+ out += `\nSaved workspaces from ${migrated.source} to vlt.json`;
158
+ }
159
+ const a = _result.allowSkills;
160
+ if (a?.stored)
161
+ out += `\n${keptLine(a.stored)}`;
162
+ else if (a)
163
+ out += `\n${savedLine(a.query, 'project')}`;
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
+ }
97
183
  return undefined;
98
184
  }
99
185
  error(err) {
@@ -1,6 +1,10 @@
1
+ import type { AllowSkillsSave } from '../allow-skills.ts';
2
+ import type { GlobalBins } from '../global.ts';
3
+ import type { SelfUpdate } from '../self-update.ts';
4
+ import type { MigratedWorkspaces } from '../migrate-workspaces.ts';
1
5
  import type { SpecConfigPersistPlan } from '../persist-spec-config.ts';
2
6
  import type { DepID } from '@vltpkg/dep-id';
3
- import type { Diff, Graph } from '@vltpkg/graph';
7
+ import type { Diff, Graph, LinkSkillsResult } from '@vltpkg/graph';
4
8
  import type { CommandFn, CommandUsage } from '../index.ts';
5
9
  /**
6
10
  * The resulting object of an install operation. To be used by the view impl.
@@ -22,12 +26,54 @@ export type InstallResult = {
22
26
  * Spec config from the cli / env that was saved to a config file.
23
27
  */
24
28
  persistedConfig?: SpecConfigPersistPlan;
29
+ /**
30
+ * Workspaces saved to vlt.json from package.json or pnpm-workspace.yaml.
31
+ */
32
+ migratedWorkspaces?: MigratedWorkspaces;
33
+ /**
34
+ * Agent skill links added or removed.
35
+ */
36
+ skills?: LinkSkillsResult;
37
+ /**
38
+ * `--allow-skills` saved to vlt.json, or kept because another query
39
+ * is stored.
40
+ */
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;
25
54
  };
26
55
  export declare const needsRegistry = true;
27
56
  export declare const needsNpmRegistry = true;
28
57
  export declare const usage: CommandUsage;
29
58
  export declare const views: {
30
- readonly json: (i: InstallResult) => {
59
+ readonly json: (i: InstallCommandResult) => {
60
+ self: SelfUpdate;
61
+ } | {
62
+ self?: SelfUpdate | undefined;
63
+ global?: GlobalBins | undefined;
64
+ allowSkills?: AllowSkillsSave | undefined;
65
+ skills?: {
66
+ unscanned?: string[];
67
+ unscannedReason?: "timeout" | "unscanned";
68
+ removed: string[];
69
+ conflicts: string[];
70
+ dirs: string[];
71
+ skipped?: string[];
72
+ gitignore?: string[];
73
+ unmatched?: import("@vltpkg/graph").UnmatchedPattern[];
74
+ linked: string[];
75
+ } | undefined;
76
+ migratedWorkspaces?: MigratedWorkspaces | undefined;
31
77
  persistedConfig?: SpecConfigPersistPlan | undefined;
32
78
  buildQueue?: DepID[] | undefined;
33
79
  message?: string | undefined;
@@ -48,6 +94,6 @@ export declare const views: {
48
94
  }[];
49
95
  removed: number;
50
96
  };
51
- readonly human: import("../view.ts").LazyView<InstallResult>;
97
+ readonly human: import("../view.ts").LazyView<Partial<InstallResult>>;
52
98
  };
53
- export declare const command: CommandFn<InstallResult>;
99
+ export declare const command: CommandFn<InstallCommandResult>;