@vltpkg/cli-sdk 1.3.8 → 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.
Files changed (50) 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 +7 -1
  4. package/dist/commands/build.js +19 -9
  5. package/dist/commands/cache.d.ts +2 -2
  6. package/dist/commands/cache.js +22 -7
  7. package/dist/commands/ci.d.ts +1 -1
  8. package/dist/commands/ci.js +10 -2
  9. package/dist/commands/init.js +7 -8
  10. package/dist/commands/install/reporter.js +46 -0
  11. package/dist/commands/install.d.ts +29 -1
  12. package/dist/commands/install.js +35 -1
  13. package/dist/commands/list.js +3 -1
  14. package/dist/commands/pack.js +2 -1
  15. package/dist/commands/pkg.js +4 -1
  16. package/dist/commands/publish.d.ts +0 -1
  17. package/dist/commands/publish.js +31 -17
  18. package/dist/commands/query.js +3 -1
  19. package/dist/commands/skills.d.ts +56 -0
  20. package/dist/commands/skills.js +268 -0
  21. package/dist/commands/uninstall.d.ts +5 -1
  22. package/dist/commands/uninstall.js +4 -2
  23. package/dist/commands/unpublish.d.ts +0 -1
  24. package/dist/commands/unpublish.js +17 -4
  25. package/dist/commands/update.d.ts +12 -0
  26. package/dist/commands/update.js +18 -1
  27. package/dist/commands/version.js +4 -1
  28. package/dist/config/definition.d.ts +26 -2
  29. package/dist/config/definition.js +75 -11
  30. package/dist/config/index.d.ts +4 -0
  31. package/dist/config/index.js +22 -6
  32. package/dist/config/spec-aliases.d.ts +22 -0
  33. package/dist/config/spec-aliases.js +47 -0
  34. package/dist/custom-help.js +4 -0
  35. package/dist/exec-command.js +12 -6
  36. package/dist/index.js +18 -8
  37. package/dist/load-command.d.ts +2 -0
  38. package/dist/migrate-workspaces.d.ts +16 -0
  39. package/dist/migrate-workspaces.js +44 -0
  40. package/dist/output.js +2 -1
  41. package/dist/print-err.js +4 -1
  42. package/dist/publish-config.d.ts +9 -0
  43. package/dist/publish-config.js +8 -0
  44. package/dist/save-types.d.ts +20 -0
  45. package/dist/save-types.js +105 -0
  46. package/dist/skills-prompt.d.ts +28 -0
  47. package/dist/skills-prompt.js +78 -0
  48. package/dist/sort-scope-locations.d.ts +11 -0
  49. package/dist/sort-scope-locations.js +57 -0
  50. package/package.json +28 -28
@@ -10,17 +10,21 @@ import prettyBytes from 'pretty-bytes';
10
10
  import { actual } from '@vltpkg/graph';
11
11
  import { Query } from '@vltpkg/query';
12
12
  import { createHostContextsMap } from "../query-host-contexts.js";
13
+ import { sortScopeLocations } from "../sort-scope-locations.js";
13
14
  import { minimatch } from 'minimatch';
14
15
  import { resolveRegistry } from "../require-registry.js";
15
16
  import { stderr } from "../output.js";
16
- export const needsRegistry = true;
17
+ import { getPublishConfig } from "../publish-config.js";
17
18
  export const usage = () => commandUsage({
18
19
  command: 'publish',
19
20
  usage: '',
20
21
  description: `Create a tarball from a package and publish it to the configured registry.
21
22
 
22
23
  This command will pack the package in the current directory or specified folder,
23
- 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.`,
24
28
  options: {
25
29
  tag: {
26
30
  description: 'Publish the package with the given tag',
@@ -114,13 +118,15 @@ export const command = async (conf) => {
114
118
  const { nodes } = await query.search(queryString, {
115
119
  signal: new AbortController().signal,
116
120
  });
121
+ const found = [];
117
122
  for (const node of nodes) {
118
123
  const { location } = node.toJSON();
119
124
  assert(location, error(`node ${node.id} has no location`, {
120
125
  found: node,
121
126
  }));
122
- locations.push(resolve(projectRoot, location));
127
+ found.push(resolve(projectRoot, location));
123
128
  }
129
+ locations.push(...sortScopeLocations(found, options.monorepo));
124
130
  }
125
131
  else if (paths?.length || groups?.length || recursive) {
126
132
  for (const workspace of options.monorepo ?? []) {
@@ -140,22 +146,34 @@ export const command = async (conf) => {
140
146
  single = options.packageJson.find(process.cwd()) ?? projectRoot;
141
147
  }
142
148
  if (single) {
143
- return commandSingle(single, conf);
149
+ const [target] = await resolveTargets([single], conf);
150
+ assert(target);
151
+ return commandSingle(target, conf);
144
152
  }
145
153
  assert(locations.length > 0, error('No workspaces or query results found'));
146
154
  const results = [];
147
- for (const location of locations) {
148
- results.push(await commandSingle(location, conf));
155
+ for (const target of await resolveTargets(locations, conf)) {
156
+ results.push(await commandSingle(target, conf));
149
157
  }
150
158
  return results;
151
159
  };
152
- const getPublishConfig = (manifest) => {
153
- const pc = manifest
154
- .publishConfig;
155
- if (pc && typeof pc === 'object') {
156
- 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 });
157
175
  }
158
- return undefined;
176
+ return targets;
159
177
  };
160
178
  /** Of several readmes at the package root, npm prefers the markdown one. */
161
179
  const README_MARKDOWN_EXTENSION = /\.(?:md|markdown|mdown|mdwn|mkd|mkdn)$/i;
@@ -179,17 +197,13 @@ const readPublishReadme = (packDir, files) => {
179
197
  readmeFilename,
180
198
  };
181
199
  };
182
- const commandSingle = async (location, conf) => {
183
- const manifestPath = conf.options.packageJson.find(location);
184
- assert(manifestPath, 'No package.json found');
185
- const manifestDir = dirname(manifestPath);
200
+ const commandSingle = async ({ manifestDir, registry }, conf) => {
186
201
  const manifest = conf.options.packageJson.read(manifestDir);
187
202
  assert(!manifest.private, error('Package has been marked as private'));
188
203
  const { 'dry-run': dry = false, otp } = conf.options;
189
204
  const publishConfig = getPublishConfig(manifest);
190
205
  const tag = publishConfig?.tag ?? conf.options.tag;
191
206
  const access = publishConfig?.access ?? conf.options.access;
192
- const registry = publishConfig?.registry ?? (await resolveRegistry(conf));
193
207
  const registryUrl = new URL(registryBase(registry));
194
208
  const runOptions = {
195
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>;
@@ -0,0 +1,268 @@
1
+ import { styleText } from 'node:util';
2
+ import { error } from '@vltpkg/error-cause';
3
+ import { assertQuery, linkSkills, listSkills, mermaidOutput, } from '@vltpkg/graph';
4
+ import { configLabel, savedLine, storedAllowSkills, unmatchedLines, unscannedLines, } from "../allow-skills.js";
5
+ import { configWriteTarget } from "../config/index.js";
6
+ import { commandUsage } from "../config/usage.js";
7
+ import { confirm, editQuery, interactive } from "../skills-prompt.js";
8
+ export const needsRegistry = true;
9
+ export const usage = () => commandUsage({
10
+ command: 'skills',
11
+ usage: ['', 'list [<query>]', 'link [<query>]'],
12
+ description: `Manage the agent skills shipped by installed packages.
13
+
14
+ A skill is a folder with a \`SKILL.md\` file
15
+ (https://agentskills.io/specification). Packages ship them as a
16
+ root \`SKILL.md\`, or at \`<name>/SKILL.md\` in \`skills/\`,
17
+ \`dist/skills/\` or \`.agents/skills/\`: the first skill of a name
18
+ wins.
19
+
20
+ Skills are linked into the project's agent skill dirs:
21
+ \`.agents/skills\`, plus \`<folder>/skills\` for each agent folder
22
+ at the project root (e.g. \`.claude/\`), or the \`skills-dir\`
23
+ config. When packages ship skills with the same name, one is
24
+ linked and the others are reported. Only links made by vlt
25
+ (pointing into \`node_modules/.vlt\`) are ever replaced or
26
+ removed; anything else in the way is skipped.
27
+
28
+ Use --target or a query after the subcommand to select packages
29
+ using DSS query syntax. End a selector with
30
+ \`:skill(<name>, …)\` to pick single skills, e.g.
31
+ \`#skills:skill(find-skills)\`. Use
32
+ \`vlt install --allow-skills=<query>\` to link skills on install.`,
33
+ subcommands: {
34
+ list: {
35
+ usage: '[<query>]',
36
+ description: `List the skills of installed packages. The default.
37
+ Selects all packages (\`*\`) by default. Applies
38
+ \`:skill()\`: preview a query before \`link\` saves
39
+ it.`,
40
+ },
41
+ link: {
42
+ usage: '[<query>]',
43
+ description: `Set, save and apply \`allow-skills\`: link what the
44
+ query selects and remove vlt links to other skills.
45
+ With no query: the stored one, or
46
+ \`:not(:malware)\`. Asks before replacing or editing
47
+ a stored query (-y skips). Saves to the project
48
+ vlt.json, or the user one with --config=user.`,
49
+ },
50
+ },
51
+ options: {
52
+ target: {
53
+ value: '<query>',
54
+ description: 'Query selector to filter packages using DSS syntax.',
55
+ },
56
+ yes: {
57
+ description: 'Skip the prompts: replace or keep the stored allow-skills without asking.',
58
+ },
59
+ config: {
60
+ value: 'user|project',
61
+ description: 'vlt.json to save allow-skills to (default: project).',
62
+ },
63
+ view: {
64
+ value: '[human | json | mermaid]',
65
+ description: 'Output format. mermaid: list only.',
66
+ },
67
+ },
68
+ });
69
+ const plural = (n, s) => `${n} ${s}${n === 1 ? '' : 's'}`;
70
+ const lines = (header, items) => [header, ...items.map(i => ` ${i}`)].join('\n');
71
+ // warnings shared by list and link
72
+ const notes = (r) => [
73
+ ...(r.skipped ? [`⚠️ Skipped ${r.skipped.join(', ')}`] : []),
74
+ ...(r.gitignore ?
75
+ [`⚠️ Could not update ${r.gitignore.join(', ')}`]
76
+ : []),
77
+ ...unscannedLines(r),
78
+ ];
79
+ // `vlt ls` style
80
+ const pkgLabel = (s) => {
81
+ const pkg = s.version ? `${s.name}@${s.version}` : s.name;
82
+ return s.alias ? `${s.alias} (${pkg})` : pkg;
83
+ };
84
+ const linkedSuffix = (s, dirs) => dirs.length && dirs.every(d => s.linked.includes(d)) ? ' (linked)'
85
+ : s.linked.length ? ` (linked in ${s.linked.join(', ')})`
86
+ : '';
87
+ const row = (s, dirs) => `${s.skill} (${pkgLabel(s)})${linkedSuffix(s, dirs)}`;
88
+ const listNotes = (r) => [
89
+ ...(!r.dirs.length && !r.skipped ?
90
+ ['No agent skill dirs: skills-dir is empty']
91
+ : []),
92
+ ...notes(r),
93
+ ];
94
+ // colors as `vlt ls` / `vlt query`: linked skills = selected nodes
95
+ const humanList = (r, { colors } = {}) => {
96
+ const style = (format) => (s) => colors ? styleText(format, s, { validateStream: false }) : s;
97
+ const yellow = style('yellow');
98
+ const rows = r.skills.map(s => {
99
+ const x = row(s, r.dirs);
100
+ return s.linked.length ? yellow(x) : x;
101
+ });
102
+ const last = rows.length - 1;
103
+ return [
104
+ ...(rows.length ?
105
+ [
106
+ style('reset')([
107
+ r.project,
108
+ ...rows.map((x, i) => `${i === last ? '└──' : '├──'} ${x}`),
109
+ ].join('\n')),
110
+ ]
111
+ : ['No agent skills found in installed packages.']),
112
+ ...listNotes(r),
113
+ ].join('\n');
114
+ };
115
+ // the dependency graph down to each package, its skills as leaves
116
+ const mermaidList = (r) => {
117
+ const children = new Map();
118
+ for (const s of r.skills) {
119
+ const list = children.get(s.id) ?? [];
120
+ list.push({
121
+ edge: pkgLabel(s),
122
+ label: `${s.skill}${linkedSuffix(s, r.dirs)}`,
123
+ });
124
+ children.set(s.id, list);
125
+ }
126
+ const { graph } = r;
127
+ return [
128
+ mermaidOutput({
129
+ edges: [],
130
+ importers: new Set([graph.mainImporter]),
131
+ nodes: [...graph.nodes.values()].filter(n => n === graph.mainImporter || children.has(n.id)),
132
+ children,
133
+ }),
134
+ ...listNotes(r).map(n => `%% ${n}`),
135
+ ].join('\n');
136
+ };
137
+ // `name`: the skill, `dependency-name`: the dep it's installed as,
138
+ // `package-id`: its package
139
+ const jsonSkill = ({ skill, name, alias, version, id, description, ...rest }) => ({
140
+ name: skill,
141
+ description,
142
+ 'dependency-name': alias ?? name,
143
+ 'package-id': id,
144
+ ...rest,
145
+ });
146
+ // distinct skills, in any number of dirs
147
+ const count = (rows) => new Set(rows.map(r => `${r.id}\0${r.skill}`)).size;
148
+ const humanLink = (r) => {
149
+ const out = [];
150
+ if (r.linked.length) {
151
+ out.push(lines(`🔗 Linked ${plural(count(r.linked), 'agent skill')}:`, r.linked.map(s => s.mount)));
152
+ }
153
+ else if (r.unchanged.length) {
154
+ out.push(`${plural(count(r.unchanged), 'agent skill')} already linked.`);
155
+ }
156
+ if (r.removed.length) {
157
+ out.push(lines(`🧹 Removed ${plural(r.removed.length, 'skill link')}:`, r.removed));
158
+ }
159
+ if (r.conflicts.length) {
160
+ out.push(lines(`⚠️ Skipped ${plural(r.conflicts.length, 'skill')}, path in use or taken by another package:`, r.conflicts));
161
+ }
162
+ out.push(...notes(r));
163
+ return out.join('\n') || 'No agent skills found to link.';
164
+ };
165
+ const human = (r, o) => r.action === 'list' ? humanList(r, o)
166
+ : r.action === 'unchanged' ? 'No changes made.'
167
+ : [
168
+ humanLink(r),
169
+ ...unmatchedLines(r.unmatched),
170
+ ...(r.saved ? [savedLine(r.query, r.saved)] : []),
171
+ ].join('\n');
172
+ export const views = {
173
+ human,
174
+ json: (r) => {
175
+ if (r.action !== 'list')
176
+ return r;
177
+ // key order kept
178
+ const { project, graph, ...rest } = r;
179
+ return { ...rest, skills: rest.skills.map(jsonSkill) };
180
+ },
181
+ mermaid: (r) => r.action === 'list' ? mermaidList(r) : human(r),
182
+ };
183
+ const defaultQuery = ':not(:malware)';
184
+ const where = (which) => which === 'user' ?
185
+ 'in the user vlt.json'
186
+ : 'for this project (vlt.json)';
187
+ // the query to link; undefined: no changes
188
+ const pickQuery = async (conf, given) => {
189
+ const check = (q) => assertQuery(q, 'allow-skills', ['skill']);
190
+ const stored = storedAllowSkills(conf);
191
+ const yes = !!conf.get('yes');
192
+ const tty = interactive();
193
+ if (given) {
194
+ await check(given);
195
+ if (!stored || stored.query === given || yes)
196
+ return given;
197
+ if (!tty) {
198
+ throw error(`allow-skills is already set (${configLabel(stored.which)}): "${stored.query}"; pass -y to replace it`, { code: 'EUSAGE', found: given, wanted: stored.query });
199
+ }
200
+ if (!(await confirm(`allow-skills is already set ${where(stored.which)}: "${stored.query}". Replace it? [y/N] `))) {
201
+ return undefined;
202
+ }
203
+ return editQuery({ current: stored.query, prefill: given, check });
204
+ }
205
+ if (stored) {
206
+ if (yes || !tty)
207
+ return stored.query;
208
+ const edit = await confirm(`allow-skills is set ${where(stored.which)}: "${stored.query}". Edit it? [y/N] `);
209
+ // ctrl+C/D: cancel
210
+ if (edit === undefined)
211
+ return undefined;
212
+ return edit ?
213
+ editQuery({
214
+ current: stored.query,
215
+ prefill: stored.query,
216
+ check,
217
+ })
218
+ : stored.query;
219
+ }
220
+ if (yes || !tty)
221
+ return defaultQuery;
222
+ return editQuery({ prefill: defaultQuery, check });
223
+ };
224
+ export const command = async (conf) => {
225
+ const [sub = 'list', query] = conf.positionals;
226
+ const target = conf.get('target') || query;
227
+ const opts = {
228
+ ...conf.options,
229
+ projectRoot: conf.projectRoot,
230
+ skillsDirs: conf.get('skills-dir'),
231
+ };
232
+ switch (sub) {
233
+ case 'list':
234
+ case 'ls':
235
+ return {
236
+ action: 'list',
237
+ ...(await listSkills({ ...opts, target })),
238
+ };
239
+ case 'link': {
240
+ const query = await pickQuery(conf, target);
241
+ if (query === undefined)
242
+ return { action: 'unchanged' };
243
+ const res = await linkSkills({
244
+ ...opts,
245
+ target: query,
246
+ apply: true,
247
+ });
248
+ // after linking: a link error saves nothing
249
+ let saved;
250
+ if (storedAllowSkills(conf)?.query !== query) {
251
+ saved = configWriteTarget(conf, 'project');
252
+ await conf.addConfigToFile(saved, { 'allow-skills': query });
253
+ }
254
+ return {
255
+ action: 'link',
256
+ query,
257
+ ...(saved && { saved }),
258
+ ...res,
259
+ };
260
+ }
261
+ default:
262
+ throw error('Unknown skills subcommand', {
263
+ code: 'EUSAGE',
264
+ found: sub,
265
+ validOptions: ['list', 'ls', 'link'],
266
+ });
267
+ }
268
+ };
@@ -1,10 +1,14 @@
1
- import type { Graph } from '@vltpkg/graph';
1
+ import type { Graph, LinkSkillsResult } from '@vltpkg/graph';
2
2
  import type { CommandFn, CommandUsage } from '../index.ts';
3
3
  export type UninstallResult = {
4
4
  /**
5
5
  * The resulting graph structure at the end of an uninstall.
6
6
  */
7
7
  graph: Graph;
8
+ /**
9
+ * Agent skill links added or removed.
10
+ */
11
+ skills?: LinkSkillsResult;
8
12
  };
9
13
  export declare const needsRegistry = true;
10
14
  export declare const needsNpmRegistry = true;
@@ -39,10 +39,12 @@ export const command = async (conf) => {
39
39
  String(conf.get('allow-scripts'))
40
40
  : ':not(*)';
41
41
  /* c8 ignore stop */
42
- const { graph } = await uninstall({
42
+ const { graph, skills } = await uninstall({
43
43
  ...conf.options,
44
44
  allowScripts,
45
+ allowSkills: conf.get('allow-skills'),
46
+ skillsDirs: conf.get('skills-dir'),
45
47
  lockfileOnly: conf.options['lockfile-only'],
46
48
  }, remove);
47
- return { graph };
49
+ return { graph, ...(skills ? { skills } : null) };
48
50
  };
@@ -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 CommandResult = {
5
4
  /** The package name */
@@ -1,9 +1,10 @@
1
1
  import { error } from '@vltpkg/error-cause';
2
- import { RegistryClient, assertOk, requestError, } from '@vltpkg/registry-client';
2
+ import { RegistryClient, assertOk, registryBase, requestError, } from '@vltpkg/registry-client';
3
3
  import { Spec } from '@vltpkg/spec';
4
+ import { dirname } from 'node:path';
4
5
  import { commandUsage } from "../config/usage.js";
6
+ import { getPublishConfig } from "../publish-config.js";
5
7
  import { resolveRegistry } from "../require-registry.js";
6
- export const needsRegistry = true;
7
8
  export const usage = () => commandUsage({
8
9
  command: 'unpublish',
9
10
  usage: ['<package>@<version>', '<package> --force'],
@@ -11,6 +12,8 @@ export const usage = () => commandUsage({
11
12
 
12
13
  To unpublish a single version, specify the package name and version.
13
14
  To unpublish an entire package, specify the package name and use --force.
15
+ A spec naming the package in the current directory uses its
16
+ \`publishConfig.registry\`, if set.
14
17
 
15
18
  ⚠️ Unpublishing is a destructive action that cannot be undone.
16
19
  Consider using \`vlt deprecate\` instead if you want to discourage
@@ -45,14 +48,22 @@ export const views = {
45
48
  },
46
49
  json: (r) => r,
47
50
  };
51
+ /** `publishConfig.registry` of the local package, if it is `name` */
52
+ const localPublishRegistry = (conf, name) => {
53
+ const path = conf.options.packageJson.find(process.cwd());
54
+ if (!path)
55
+ return undefined;
56
+ const manifest = conf.options.packageJson.read(dirname(path));
57
+ return manifest.name === name ?
58
+ getPublishConfig(manifest)?.registry
59
+ : undefined;
60
+ };
48
61
  export const command = async (conf) => {
49
62
  const specArg = conf.positionals[0];
50
63
  if (!specArg) {
51
64
  throw error('unpublish requires a package spec argument (e.g. pkg@version)', { code: 'EUSAGE' });
52
65
  }
53
66
  const { otp, force } = conf.options;
54
- const registry = await resolveRegistry(conf);
55
- const registryUrl = new URL(registry);
56
67
  const spec = Spec.parseArgs(specArg, conf.options);
57
68
  const name = spec.name;
58
69
  /* c8 ignore start - Spec.parseArgs always produces a name for valid input */
@@ -71,6 +82,8 @@ export const command = async (conf) => {
71
82
  'To unpublish all versions, use: vlt unpublish <package> --force');
72
83
  }
73
84
  }
85
+ const registryUrl = new URL(registryBase(localPublishRegistry(conf, name) ??
86
+ (await resolveRegistry(conf))));
74
87
  const rc = new RegistryClient(conf.options);
75
88
  const encodedName = name.startsWith('@') ? name.replace('/', '%2F') : name;
76
89
  if (version) {
@@ -5,6 +5,18 @@ export declare const needsNpmRegistry = true;
5
5
  export declare const usage: CommandUsage;
6
6
  export declare const views: {
7
7
  readonly json: (i: InstallResult) => {
8
+ allowSkills?: import("../allow-skills.ts").AllowSkillsSave | undefined;
9
+ skills?: {
10
+ unscanned?: string[];
11
+ unscannedReason?: "timeout" | "unscanned";
12
+ removed: string[];
13
+ conflicts: string[];
14
+ dirs: string[];
15
+ skipped?: string[];
16
+ gitignore?: string[];
17
+ unmatched?: import("@vltpkg/graph").UnmatchedPattern[];
18
+ linked: string[];
19
+ } | undefined;
8
20
  persistedConfig?: import("../persist-spec-config.ts").SpecConfigPersistPlan | undefined;
9
21
  graph: import("@vltpkg/graph").LockfileData;
10
22
  buildQueue?: import("@vltpkg/dep-id").DepID[] | undefined;
@@ -1,6 +1,7 @@
1
1
  import { update } from '@vltpkg/graph';
2
2
  import { error } from '@vltpkg/error-cause';
3
3
  import { commandUsage } from "../config/usage.js";
4
+ import { saveAllowSkills } from "../allow-skills.js";
4
5
  import { planSpecConfigPersist } from "../persist-spec-config.js";
5
6
  import { asUnknownSpecPrefix } from "../require-registry.js";
6
7
  import { lazyView } from "../view.js";
@@ -28,8 +29,17 @@ export const usage = () => commandUsage({
28
29
  value: '<query>',
29
30
  description: 'Filter which packages are allowed to run lifecycle scripts using DSS query syntax.',
30
31
  },
32
+ 'allow-skills': {
33
+ value: '<query>',
34
+ description: "Link agent skills of packages matching this DSS query into the project's agent skill dirs. Saved to vlt.json.",
35
+ },
31
36
  },
32
37
  });
38
+ // link mounts; `unchanged` left out
39
+ const skillsJson = ({ linked, unchanged: _, ...rest }) => ({
40
+ linked: linked.map(s => s.mount),
41
+ ...rest,
42
+ });
33
43
  export const views = {
34
44
  json: i => ({
35
45
  ...(i.buildQueue?.length ?
@@ -42,6 +52,8 @@ export const views = {
42
52
  ...(i.persistedConfig ?
43
53
  { persistedConfig: i.persistedConfig }
44
54
  : null),
55
+ ...(i.skills ? { skills: skillsJson(i.skills) } : null),
56
+ ...(i.allowSkills ? { allowSkills: i.allowSkills } : null),
45
57
  }),
46
58
  human: lazyView(async () => (await import('./install/reporter.ts')).InstallReporter),
47
59
  };
@@ -58,17 +70,22 @@ export const command = async (conf) => {
58
70
  String(conf.get('allow-scripts'))
59
71
  : ':not(*)';
60
72
  /* c8 ignore stop */
61
- const { buildQueue, graph } = await update({
73
+ const { buildQueue, graph, skills } = await update({
62
74
  ...conf.options,
63
75
  allowScripts,
76
+ allowSkills: conf.get('allow-skills'),
77
+ skillsDirs: conf.get('skills-dir'),
64
78
  }).catch((er) => {
65
79
  throw asUnknownSpecPrefix(er);
66
80
  });
67
81
  if (persist)
68
82
  await conf.addConfigToFile(persist.which, persist.values);
83
+ const allowSkillsSave = await saveAllowSkills(conf);
69
84
  return {
70
85
  buildQueue,
71
86
  graph,
72
87
  ...(persist ? { persistedConfig: persist } : null),
88
+ ...(skills ? { skills } : null),
89
+ ...(allowSkillsSave ? { allowSkills: allowSkillsSave } : null),
73
90
  };
74
91
  };
@@ -8,6 +8,7 @@ import assert from 'node:assert';
8
8
  import { actual } from '@vltpkg/graph';
9
9
  import { Query } from '@vltpkg/query';
10
10
  import { createHostContextsMap } from "../query-host-contexts.js";
11
+ import { sortScopeLocations } from "../sort-scope-locations.js";
11
12
  import { minimatch } from 'minimatch';
12
13
  /**
13
14
  * `--no-git-tag-version` skips the whole git dance, matching npm.
@@ -223,13 +224,15 @@ export const command = async (conf) => {
223
224
  const { nodes } = await query.search(queryString, {
224
225
  signal: new AbortController().signal,
225
226
  });
227
+ const found = [];
226
228
  for (const node of nodes) {
227
229
  const location = node.location;
228
230
  assert(location, error(`node ${node.id} has no location`, {
229
231
  found: node,
230
232
  }));
231
- locations.push(resolve(node.projectRoot, location));
233
+ found.push(resolve(node.projectRoot, location));
232
234
  }
235
+ locations.push(...sortScopeLocations(found, options.monorepo));
233
236
  }
234
237
  else if (paths?.length || groups?.length || recursive) {
235
238
  for (const workspace of options.monorepo ?? []) {