@vltpkg/cli-sdk 1.4.0 → 1.5.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.
@@ -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)}`;
@@ -3,6 +3,13 @@ import type { CommandFn, CommandUsage } from '../index.ts';
3
3
  export declare const views: {
4
4
  readonly human: (result: BuildResult) => string;
5
5
  readonly json: (result: BuildResult) => {
6
+ message: string;
7
+ skipped?: {
8
+ id: import("@vltpkg/dep-id").DepID;
9
+ name: string;
10
+ version: string | undefined;
11
+ }[] | undefined;
12
+ skippedReason?: "timeout" | "unscanned" | undefined;
6
13
  success: {
7
14
  id: import("@vltpkg/dep-id").DepID;
8
15
  name: string;
@@ -13,7 +20,6 @@ export declare const views: {
13
20
  name: string;
14
21
  version: string | undefined;
15
22
  }[];
16
- message: string;
17
23
  };
18
24
  };
19
25
  export declare const needsRegistry = true;
@@ -2,39 +2,49 @@ import { build } from '@vltpkg/graph';
2
2
  import { error } from '@vltpkg/error-cause';
3
3
  import { commandUsage } from "../config/usage.js";
4
4
  import { isErrorWithCause } from '@vltpkg/types';
5
+ const skippedMessage = (count, reason) => `${count} package${count === 1 ? '' : 's'} not built, ${reason === 'unscanned' ?
6
+ 'Socket has not scanned them yet'
7
+ : 'security data timed out'}. Run "vlt build" again later.`;
5
8
  export const views = {
6
9
  human: (result) => {
7
10
  const successCount = result.success.length;
8
11
  const failureCount = result.failure.length;
12
+ const skippedCount = result.skipped?.length ?? 0;
9
13
  const messages = [];
10
14
  if (successCount > 0) {
11
15
  messages.push(`🔨 Built ${successCount} package${successCount === 1 ? '' : 's'} successfully.`);
12
16
  }
13
- else {
17
+ else if (!skippedCount) {
14
18
  messages.push('📦 All packages are already built.');
15
19
  }
16
20
  if (failureCount > 0) {
17
21
  messages.push(`🔎 ${failureCount} optional package${ /* c8 ignore next */failureCount === 1 ? '' : 's'} failed to build.`);
18
22
  }
23
+ if (skippedCount > 0) {
24
+ messages.push(`⏳ ${skippedMessage(skippedCount, result.skippedReason)}`);
25
+ }
19
26
  return messages.join('\n');
20
27
  },
21
28
  json: (result) => {
22
- const successList = result.success.map(node => ({
29
+ const toJSON = (node) => ({
23
30
  id: node.id,
24
31
  name: node.name,
25
32
  version: node.version,
26
- }));
27
- const failureList = result.failure.map(node => ({
28
- id: node.id,
29
- name: node.name,
30
- version: node.version,
31
- }));
33
+ });
34
+ const successList = result.success.map(toJSON);
35
+ const failureList = result.failure.map(toJSON);
36
+ const skippedList = result.skipped?.map(toJSON) ?? [];
32
37
  return {
33
38
  success: successList,
34
39
  failure: failureList,
40
+ ...(skippedList.length ?
41
+ { skipped: skippedList, skippedReason: result.skippedReason }
42
+ : {}),
35
43
  message: successList.length > 0 ?
36
44
  `Built ${successList.length} package${successList.length === 1 ? '' : 's'}.`
37
- : 'No packages needed building.',
45
+ : skippedList.length ?
46
+ skippedMessage(skippedList.length, result.skippedReason)
47
+ : 'No packages needed building.',
38
48
  };
39
49
  },
40
50
  };
@@ -1,6 +1,6 @@
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;
@@ -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
  };
@@ -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
@@ -5,6 +5,7 @@ import { createElement as $, Fragment, useEffect, useState, } from 'react';
5
5
  import { ViewClass } from "../../view.js";
6
6
  import { asError } from '@vltpkg/types';
7
7
  import { persistedEntries } from "../../persist-spec-config.js";
8
+ import { keptLine, savedLine, unmatchedLines, unscannedLines, } from "../../allow-skills.js";
8
9
  const labels = {
9
10
  build: 'resolving dependencies',
10
11
  actual: '',
@@ -75,6 +76,17 @@ const App = ({ trailer }) => {
75
76
  export class InstallReporter extends ViewClass {
76
77
  #instance = null;
77
78
  start() {
79
+ // Ink measures the terminal on every render, and when stdout has no
80
+ // width (a pipe, a CI log) it does so by spawning `tput cols` and
81
+ // `tput lines`: thousands of processes over one install. Give a
82
+ // non-TTY stdout the width Ink would fall back to anyway, so the
83
+ // output is the same and nothing is spawned.
84
+ if (!process.stdout.isTTY) {
85
+ // only a sane COLUMNS counts; -1 or Infinity would be truthy
86
+ const cols = Number(process.env.COLUMNS);
87
+ process.stdout.columns ||=
88
+ Number.isInteger(cols) && cols > 0 ? cols : 80;
89
+ }
78
90
  this.#instance = render($(App));
79
91
  }
80
92
  async done(_result, { time }) {
@@ -87,12 +99,46 @@ export class InstallReporter extends ViewClass {
87
99
  out +=
88
100
  '🔨 Run `vlt build` to run all required scripts to build installed packages.\n';
89
101
  }
102
+ const { linked = [], removed = [], conflicts = [], skipped = [], gitignore = [], unmatched = [], } = _result.skills ?? {};
103
+ const s = (n) => (n === 1 ? '' : 's');
104
+ const skills = [];
105
+ if (linked.length) {
106
+ // one per skill, linked in any number of dirs
107
+ const n = new Set(linked.map(r => `${r.id}\0${r.skill}`)).size;
108
+ const dirs = [...new Set(linked.map(r => r.dir))].join(', ');
109
+ skills.push(`🔗 Linked ${n} agent skill${s(n)} into ${dirs}`);
110
+ }
111
+ if (removed.length) {
112
+ skills.push(`🧹 Removed ${removed.length} stale skill link${s(removed.length)}: ${removed.join(', ')}`);
113
+ }
114
+ if (conflicts.length) {
115
+ skills.push(`⚠️ ${conflicts.length} skill link${s(conflicts.length)} skipped, path in use or taken by another package: ${conflicts.join(', ')}`);
116
+ }
117
+ skills.push(...unmatchedLines(unmatched));
118
+ if (skipped.length) {
119
+ skills.push(`⚠️ Skipped ${skipped.join(', ')}`);
120
+ }
121
+ if (gitignore.length) {
122
+ skills.push(`⚠️ Could not update ${gitignore.join(', ')}`);
123
+ }
124
+ skills.push(...unscannedLines(_result.skills ?? {}));
125
+ if (skills.length)
126
+ out += `\n${skills.join('\n')}\n`;
90
127
  const saved = _result.persistedConfig;
91
128
  if (saved) {
92
129
  for (const e of persistedEntries(saved.values)) {
93
130
  out += `\nSaved ${e} to ${saved.which} vlt.json`;
94
131
  }
95
132
  }
133
+ const migrated = _result.migratedWorkspaces;
134
+ if (migrated) {
135
+ out += `\nSaved workspaces from ${migrated.source} to vlt.json`;
136
+ }
137
+ const a = _result.allowSkills;
138
+ if (a?.stored)
139
+ out += `\n${keptLine(a.stored)}`;
140
+ else if (a)
141
+ out += `\n${savedLine(a.query, 'project')}`;
96
142
  this.#instance?.rerender($(App, { trailer: out }));
97
143
  return undefined;
98
144
  }
@@ -1,6 +1,8 @@
1
+ import type { AllowSkillsSave } from '../allow-skills.ts';
2
+ import type { MigratedWorkspaces } from '../migrate-workspaces.ts';
1
3
  import type { SpecConfigPersistPlan } from '../persist-spec-config.ts';
2
4
  import type { DepID } from '@vltpkg/dep-id';
3
- import type { Diff, Graph } from '@vltpkg/graph';
5
+ import type { Diff, Graph, LinkSkillsResult } from '@vltpkg/graph';
4
6
  import type { CommandFn, CommandUsage } from '../index.ts';
5
7
  /**
6
8
  * The resulting object of an install operation. To be used by the view impl.
@@ -22,12 +24,38 @@ export type InstallResult = {
22
24
  * Spec config from the cli / env that was saved to a config file.
23
25
  */
24
26
  persistedConfig?: SpecConfigPersistPlan;
27
+ /**
28
+ * Workspaces saved to vlt.json from package.json or pnpm-workspace.yaml.
29
+ */
30
+ migratedWorkspaces?: MigratedWorkspaces;
31
+ /**
32
+ * Agent skill links added or removed.
33
+ */
34
+ skills?: LinkSkillsResult;
35
+ /**
36
+ * `--allow-skills` saved to vlt.json, or kept because another query
37
+ * is stored.
38
+ */
39
+ allowSkills?: AllowSkillsSave;
25
40
  };
26
41
  export declare const needsRegistry = true;
27
42
  export declare const needsNpmRegistry = true;
28
43
  export declare const usage: CommandUsage;
29
44
  export declare const views: {
30
45
  readonly json: (i: InstallResult) => {
46
+ allowSkills?: AllowSkillsSave | undefined;
47
+ skills?: {
48
+ unscanned?: string[];
49
+ unscannedReason?: "timeout" | "unscanned";
50
+ removed: string[];
51
+ conflicts: string[];
52
+ dirs: string[];
53
+ skipped?: string[];
54
+ gitignore?: string[];
55
+ unmatched?: import("@vltpkg/graph").UnmatchedPattern[];
56
+ linked: string[];
57
+ } | undefined;
58
+ migratedWorkspaces?: MigratedWorkspaces | undefined;
31
59
  persistedConfig?: SpecConfigPersistPlan | undefined;
32
60
  buildQueue?: DepID[] | undefined;
33
61
  message?: string | undefined;
@@ -1,9 +1,11 @@
1
1
  import { commandUsage } from "../config/usage.js";
2
2
  import { install } from '@vltpkg/graph';
3
+ import { saveAllowSkills } from "../allow-skills.js";
3
4
  import { parseAddArgs } from "../parse-add-remove-args.js";
4
5
  import { planSpecConfigPersist } from "../persist-spec-config.js";
5
6
  import { asUnknownSpecPrefix } from "../require-registry.js";
6
7
  import { addTypesDeps } from "../save-types.js";
8
+ import { migrateWorkspaces } from "../migrate-workspaces.js";
7
9
  import { trackInstall } from "../telemetry.js";
8
10
  import { lazyView } from "../view.js";
9
11
  export const needsRegistry = true;
@@ -39,6 +41,9 @@ export const usage = () => commandUsage({
39
41
  'save-config': {
40
42
  description: 'Save registry and git host options given on the command line or via env (e.g. `--registries name=url`) to the project vlt.json, or the user vlt.json with --config=user.',
41
43
  },
44
+ 'no-migrate-workspaces': {
45
+ description: "Don't save workspaces read from package.json or pnpm-workspace.yaml to vlt.json.",
46
+ },
42
47
  workspace: {
43
48
  value: '<path|glob>',
44
49
  description: 'Limit installation targets to matching workspaces.',
@@ -60,8 +65,17 @@ export const usage = () => commandUsage({
60
65
  value: '<query>',
61
66
  description: 'Filter which packages are allowed to run lifecycle scripts using DSS query syntax.',
62
67
  },
68
+ 'allow-skills': {
69
+ value: '<query>',
70
+ description: "Link agent skills of packages matching this DSS query into the project's agent skill dirs. Saved to vlt.json.",
71
+ },
63
72
  },
64
73
  });
74
+ // link mounts; `unchanged` left out
75
+ const skillsJson = ({ linked, unchanged: _, ...rest }) => ({
76
+ linked: linked.map(s => s.mount),
77
+ ...rest,
78
+ });
65
79
  export const views = {
66
80
  json: i => {
67
81
  const added = i.diff?.nodes.add ?? new Set();
@@ -123,6 +137,11 @@ export const views = {
123
137
  ...(i.persistedConfig ?
124
138
  { persistedConfig: i.persistedConfig }
125
139
  : null),
140
+ ...(i.migratedWorkspaces ?
141
+ { migratedWorkspaces: i.migratedWorkspaces }
142
+ : null),
143
+ ...(i.skills ? { skills: skillsJson(i.skills) } : null),
144
+ ...(i.allowSkills ? { allowSkills: i.allowSkills } : null),
126
145
  };
127
146
  },
128
147
  human: lazyView(async () => (await import('./install/reporter.ts')).InstallReporter),
@@ -146,20 +165,26 @@ export const command = async (conf) => {
146
165
  String(conf.get('allow-scripts'))
147
166
  : ':not(*)';
148
167
  /* c8 ignore stop */
168
+ const allowSkills = conf.get('allow-skills');
149
169
  const installStart = Date.now();
150
- const { buildQueue, graph, diff } = await install({
170
+ const { buildQueue, graph, diff, skills } = await install({
151
171
  ...conf.options,
152
172
  frozenLockfile,
153
173
  expectLockfile,
154
174
  allowScripts,
175
+ allowSkills,
176
+ skillsDirs: conf.get('skills-dir'),
155
177
  lockfileOnly,
156
178
  saveExact,
157
179
  savePrefix,
158
180
  }, add).catch((er) => {
159
181
  throw asUnknownSpecPrefix(er);
160
182
  });
183
+ const migrated = frozenLockfile || expectLockfile ? undefined : (migrateWorkspaces(conf));
161
184
  if (persist)
162
185
  await conf.addConfigToFile(persist.which, persist.values);
186
+ // nothing linked on --lockfile-only
187
+ const allowSkillsSave = lockfileOnly ? undefined : await saveAllowSkills(conf);
163
188
  /* c8 ignore next 9 - telemetry is best-effort */
164
189
  try {
165
190
  trackInstall({
@@ -173,5 +198,8 @@ export const command = async (conf) => {
173
198
  graph,
174
199
  diff,
175
200
  ...(persist ? { persistedConfig: persist } : null),
201
+ ...(migrated ? { migratedWorkspaces: migrated } : null),
202
+ ...(skills ? { skills } : null),
203
+ ...(allowSkillsSave ? { allowSkills: allowSkillsSave } : null),
176
204
  };
177
205
  };
@@ -1,4 +1,4 @@
1
- import { actual, asNode, humanReadableOutput, jsonOutput, mermaidOutput, GraphModifier, } from '@vltpkg/graph';
1
+ import { actual, asNode, hasSkills, humanReadableOutput, jsonOutput, mermaidOutput, GraphModifier, } from '@vltpkg/graph';
2
2
  import { Query } from '@vltpkg/query';
3
3
  import { SecurityArchive } from '@vltpkg/security-archive';
4
4
  import { error } from '@vltpkg/error-cause';
@@ -138,6 +138,7 @@ export const command = async (conf) => {
138
138
  securityArchive,
139
139
  hostContexts,
140
140
  getAuthHeader,
141
+ hasSkills,
141
142
  });
142
143
  const { nodes: resultNodes } = await scopeQuery.search(scopeQueryString, {
143
144
  signal: new AbortController().signal,
@@ -190,6 +191,7 @@ export const command = async (conf) => {
190
191
  securityArchive,
191
192
  hostContexts,
192
193
  getAuthHeader,
194
+ hasSkills,
193
195
  });
194
196
  const query = queryString ||
195
197
  /* c8 ignore next */
@@ -1,5 +1,4 @@
1
1
  import type { CommandFn, CommandUsage } from '../index.ts';
2
- export declare const needsRegistry = true;
3
2
  export declare const usage: CommandUsage;
4
3
  export type CommandResultSingle = {
5
4
  id: string;
@@ -14,14 +14,17 @@ import { sortScopeLocations } from "../sort-scope-locations.js";
14
14
  import { minimatch } from 'minimatch';
15
15
  import { resolveRegistry } from "../require-registry.js";
16
16
  import { stderr } from "../output.js";
17
- export const needsRegistry = true;
17
+ import { getPublishConfig } from "../publish-config.js";
18
18
  export const usage = () => commandUsage({
19
19
  command: 'publish',
20
20
  usage: '',
21
21
  description: `Create a tarball from a package and publish it to the configured registry.
22
22
 
23
23
  This command will pack the package in the current directory or specified folder,
24
- and then upload it to the configured registry.`,
24
+ and then upload it to the configured registry.
25
+
26
+ The registry is the package's \`publishConfig.registry\` if set (no other
27
+ registry config needed), else \`--registry\`, else a configured alias.`,
25
28
  options: {
26
29
  tag: {
27
30
  description: 'Publish the package with the given tag',
@@ -143,22 +146,34 @@ export const command = async (conf) => {
143
146
  single = options.packageJson.find(process.cwd()) ?? projectRoot;
144
147
  }
145
148
  if (single) {
146
- return commandSingle(single, conf);
149
+ const [target] = await resolveTargets([single], conf);
150
+ assert(target);
151
+ return commandSingle(target, conf);
147
152
  }
148
153
  assert(locations.length > 0, error('No workspaces or query results found'));
149
154
  const results = [];
150
- for (const location of locations) {
151
- results.push(await commandSingle(location, conf));
155
+ for (const target of await resolveTargets(locations, conf)) {
156
+ results.push(await commandSingle(target, conf));
152
157
  }
153
158
  return results;
154
159
  };
155
- const getPublishConfig = (manifest) => {
156
- const pc = manifest
157
- .publishConfig;
158
- if (pc && typeof pc === 'object') {
159
- return pc;
160
+ /**
161
+ * Registry of every target, before anything runs or publishes.
162
+ * The configured one is only resolved (or prompted for) once.
163
+ */
164
+ const resolveTargets = async (locations, conf) => {
165
+ let fallback;
166
+ const targets = [];
167
+ for (const location of locations) {
168
+ const manifestPath = conf.options.packageJson.find(location);
169
+ assert(manifestPath, 'No package.json found');
170
+ const manifestDir = dirname(manifestPath);
171
+ const manifest = conf.options.packageJson.read(manifestDir);
172
+ const registry = getPublishConfig(manifest)?.registry ??
173
+ (await (fallback ??= resolveRegistry(conf)));
174
+ targets.push({ manifestDir, registry });
160
175
  }
161
- return undefined;
176
+ return targets;
162
177
  };
163
178
  /** Of several readmes at the package root, npm prefers the markdown one. */
164
179
  const README_MARKDOWN_EXTENSION = /\.(?:md|markdown|mdown|mdwn|mkd|mkdn)$/i;
@@ -182,17 +197,13 @@ const readPublishReadme = (packDir, files) => {
182
197
  readmeFilename,
183
198
  };
184
199
  };
185
- const commandSingle = async (location, conf) => {
186
- const manifestPath = conf.options.packageJson.find(location);
187
- assert(manifestPath, 'No package.json found');
188
- const manifestDir = dirname(manifestPath);
200
+ const commandSingle = async ({ manifestDir, registry }, conf) => {
189
201
  const manifest = conf.options.packageJson.read(manifestDir);
190
202
  assert(!manifest.private, error('Package has been marked as private'));
191
203
  const { 'dry-run': dry = false, otp } = conf.options;
192
204
  const publishConfig = getPublishConfig(manifest);
193
205
  const tag = publishConfig?.tag ?? conf.options.tag;
194
206
  const access = publishConfig?.access ?? conf.options.access;
195
- const registry = publishConfig?.registry ?? (await resolveRegistry(conf));
196
207
  const registryUrl = new URL(registryBase(registry));
197
208
  const runOptions = {
198
209
  cwd: manifestDir,
@@ -1,4 +1,4 @@
1
- import { actual, asNode, humanReadableOutput, jsonOutput, mermaidOutput, GraphModifier, } from '@vltpkg/graph';
1
+ import { actual, asNode, hasSkills, humanReadableOutput, jsonOutput, mermaidOutput, GraphModifier, } from '@vltpkg/graph';
2
2
  import { Query } from '@vltpkg/query';
3
3
  import { stdout } from "../output.js";
4
4
  import { error } from '@vltpkg/error-cause';
@@ -166,6 +166,7 @@ export const command = async (conf) => {
166
166
  hostContexts,
167
167
  diffFiles,
168
168
  getAuthHeader,
169
+ hasSkills,
169
170
  });
170
171
  const { nodes: resultNodes } = await scopeQuery.search(scopeQueryString, {
171
172
  signal: new AbortController().signal,
@@ -231,6 +232,7 @@ export const command = async (conf) => {
231
232
  hostContexts,
232
233
  diffFiles,
233
234
  getAuthHeader,
235
+ hasSkills,
234
236
  });
235
237
  const query = queryString ||
236
238
  (graph ? defaultProjectQueryString : defaultLocalScopeQueryString);
@@ -0,0 +1,56 @@
1
+ import type { DepID } from '@vltpkg/dep-id';
2
+ import type { LinkSkillsResult, ListSkillsResult, UnscannedSkills } from '@vltpkg/graph';
3
+ import type { WhichConfig } from '@vltpkg/vlt-json';
4
+ import type { CommandFn, CommandUsage } from '../index.ts';
5
+ import type { ViewOptions } from '../view.ts';
6
+ export type SkillsResult = ({
7
+ action: 'list';
8
+ } & ListSkillsResult) | ({
9
+ action: 'link';
10
+ /** the `allow-skills` applied */
11
+ query: string;
12
+ /** the config file it was saved to, if changed */
13
+ saved?: WhichConfig;
14
+ } & LinkSkillsResult) | {
15
+ action: 'unchanged';
16
+ };
17
+ export declare const needsRegistry = true;
18
+ export declare const usage: CommandUsage;
19
+ export declare const views: {
20
+ readonly human: (r: SkillsResult, o?: ViewOptions) => string;
21
+ readonly json: (r: SkillsResult) => ({
22
+ action: "link";
23
+ /** the `allow-skills` applied */
24
+ query: string;
25
+ /** the config file it was saved to, if changed */
26
+ saved?: WhichConfig;
27
+ } & UnscannedSkills & {
28
+ linked: import("@vltpkg/graph").SkillRow[];
29
+ unchanged: import("@vltpkg/graph").SkillRow[];
30
+ removed: string[];
31
+ conflicts: string[];
32
+ dirs: string[];
33
+ skipped?: string[];
34
+ gitignore?: string[];
35
+ unmatched?: import("@vltpkg/graph").UnmatchedPattern[];
36
+ }) | {
37
+ action: "unchanged";
38
+ } | {
39
+ skills: {
40
+ frontmatterName?: string;
41
+ path: string;
42
+ linked: string[];
43
+ name: string;
44
+ description: string | undefined;
45
+ 'dependency-name': string;
46
+ 'package-id': DepID;
47
+ }[];
48
+ action: "list";
49
+ unscanned?: string[];
50
+ unscannedReason?: "timeout" | "unscanned";
51
+ dirs: string[];
52
+ skipped?: string[];
53
+ };
54
+ readonly mermaid: (r: SkillsResult) => string;
55
+ };
56
+ export declare const command: CommandFn<SkillsResult>;