@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
@@ -53,6 +53,7 @@ export declare const commands: {
53
53
  readonly 'run-exec': "run-exec";
54
54
  readonly run: "run";
55
55
  readonly setup: "setup";
56
+ readonly skills: "skills";
56
57
  readonly token: "token";
57
58
  readonly uninstall: "uninstall";
58
59
  readonly unpublish: "unpublish";
@@ -75,7 +76,7 @@ export declare const recordFields: readonly ["git-hosts", "registries", "git-hos
75
76
  export type RecordField = (typeof recordFields)[number];
76
77
  export declare const isRecordField: (s: string) => s is RecordField;
77
78
  /** Valid `store-linker` values. */
78
- export declare const storeLinkers: readonly ["auto", "hardlink", "copy", "unpack"];
79
+ export declare const storeLinkers: readonly ["auto", "hardlink", "clone", "copy", "unpack"];
79
80
  export declare const definition: import("jackspeak").Jack<{
80
81
  color: {
81
82
  short: string;
@@ -139,7 +140,7 @@ export declare const definition: import("jackspeak").Jack<{
139
140
  'store-linker': {
140
141
  hint: string;
141
142
  description: string;
142
- validOptions: readonly ["auto", "hardlink", "copy", "unpack"];
143
+ validOptions: readonly ["auto", "hardlink", "clone", "copy", "unpack"];
143
144
  default: string;
144
145
  };
145
146
  tag: {
@@ -365,16 +366,25 @@ export declare const definition: import("jackspeak").Jack<{
365
366
  short: string;
366
367
  description: string;
367
368
  };
369
+ 'save-types': {
370
+ description: string;
371
+ };
368
372
  'save-config': {
369
373
  description: string;
370
374
  };
375
+ 'migrate-workspaces': {
376
+ description: string;
377
+ default: true;
378
+ };
371
379
  } & {
372
380
  'save-dev': import("jackspeak").ConfigOption<"boolean", false, undefined>;
373
381
  'save-exact': import("jackspeak").ConfigOption<"boolean", false, undefined>;
374
382
  'save-optional': import("jackspeak").ConfigOption<"boolean", false, undefined>;
375
383
  'save-peer': import("jackspeak").ConfigOption<"boolean", false, undefined>;
376
384
  'save-prod': import("jackspeak").ConfigOption<"boolean", false, undefined>;
385
+ 'save-types': import("jackspeak").ConfigOption<"boolean", false, undefined>;
377
386
  'save-config': import("jackspeak").ConfigOption<"boolean", false, undefined>;
387
+ 'migrate-workspaces': import("jackspeak").ConfigOption<"boolean", false, undefined>;
378
388
  } & {
379
389
  'save-prefix': {
380
390
  description: string;
@@ -425,6 +435,20 @@ export declare const definition: import("jackspeak").Jack<{
425
435
  };
426
436
  } & {
427
437
  'allow-scripts': import("jackspeak").ConfigOption<"string", false, readonly string[] | undefined>;
438
+ } & {
439
+ 'allow-skills': {
440
+ hint: string;
441
+ description: string;
442
+ };
443
+ } & {
444
+ 'allow-skills': import("jackspeak").ConfigOption<"string", false, readonly string[] | undefined>;
445
+ } & {
446
+ 'skills-dir': {
447
+ hint: string;
448
+ description: string;
449
+ };
450
+ } & {
451
+ 'skills-dir': import("jackspeak").ConfigOption<"string", true, readonly string[] | undefined>;
428
452
  } & {
429
453
  access: {
430
454
  description: string;
@@ -46,6 +46,7 @@ const canonicalCommands = {
46
46
  'run-exec': 'run-exec',
47
47
  run: 'run',
48
48
  setup: 'setup',
49
+ skills: 'skills',
49
50
  token: 'token',
50
51
  uninstall: 'uninstall',
51
52
  unpublish: 'unpublish',
@@ -114,6 +115,7 @@ export const isRecordField = (s) => recordFields.includes(s);
114
115
  export const storeLinkers = [
115
116
  'auto',
116
117
  'hardlink',
118
+ 'clone',
117
119
  'copy',
118
120
  'unpack',
119
121
  ];
@@ -226,8 +228,11 @@ export const definition = j
226
228
 
227
229
  There is **no** built-in \`npm\` registry URL; the \`npm\`
228
230
  alias must be configured (e.g. via \`vlt setup\`) before it
229
- can be used. The \`gh\` and \`jsr\` aliases have built-in
230
- defaults that can be overridden here.
231
+ can be used. The \`gh\` alias has a built-in default
232
+ that can be overridden here. A name used by
233
+ \`--jsr-registries\` or \`--git-hosts\` (e.g. \`jsr\`,
234
+ \`github\`), or a built-in protocol (e.g. \`file\`), is
235
+ an error.
231
236
 
232
237
  Aliases given on the command line or in
233
238
  \`VLT_REGISTRIES\` (newline-delimited) merge with the
@@ -317,28 +322,35 @@ export const definition = j
317
322
  default: cacheDir,
318
323
  },
319
324
  'store-linker': {
320
- hint: 'auto | hardlink | copy | unpack',
325
+ hint: 'auto | hardlink | clone | copy | unpack',
321
326
  description: `How packages are placed in \`node_modules\`. Every
322
327
  linker but \`unpack\` fills a global store under
323
328
  \`cache\` in the background, then places packages
324
329
  from it.
325
330
 
326
331
  - auto: Default. On Linux, hardlink from the global
327
- store, copying any file that cannot be linked.
328
- Elsewhere, same as \`unpack\`: a hardlink is the
329
- slowest way to place a file on APFS, so macOS and
330
- Windows unpack by default.
332
+ store, copying any file that cannot be linked. On
333
+ macOS, clone from it, on a Node with \`node:ffi\`
334
+ (26.1 and later, and \`--allow-ffi\` under
335
+ \`--permission\`); a hardlink is the slowest way
336
+ to place a file on APFS, so macOS without it, and
337
+ Windows, unpack by default.
331
338
  - hardlink: Hardlink from the global store on every
332
339
  platform.
340
+ - clone: Clone each package directory from the
341
+ global store copy-on-write (\`clonefile\`, macOS
342
+ on APFS). Files share storage with the store
343
+ until edited, so editing in place is safe.
344
+ Unpacks where cloning fails.
333
345
  - copy: Copy from the global store. Use this when
334
346
  editing files in \`node_modules\` in place, since
335
347
  a hardlink shares its content with every project.
336
348
  - unpack: Unpack each package tarball, skipping the
337
349
  global store.
338
350
 
339
- A package is copied right before its install
340
- scripts run, so they don't change its files in the
341
- store.
351
+ A hardlinked package is copied right before its
352
+ install scripts run, so they don't change its files
353
+ in the store; a cloned one needs no copy.
342
354
  An invalid \`VLT_STORE_LINKER\` warns and uses
343
355
  \`unpack\`.`,
344
356
  validOptions: storeLinkers,
@@ -514,7 +526,9 @@ export const definition = j
514
526
  config: {
515
527
  hint: 'all | user | project',
516
528
  description: `Specify which configuration to show or operate on when running
517
- \`vlt config\` commands. For read operations (get, pick, list):
529
+ \`vlt config\` commands, and which file
530
+ \`vlt skills link\` saves \`allow-skills\` to. For read
531
+ operations (get, pick, list):
518
532
  \`all\` shows merged configuration from both user and project
519
533
  files (default). For write operations (set, delete, edit):
520
534
  defaults to \`project\`. \`user\` shows/modifies only user-level
@@ -702,6 +716,11 @@ export const definition = j
702
716
  devDependencies or optionalDependencies, but you want to
703
717
  move it to be a non-optional production dependency.`,
704
718
  },
719
+ 'save-types': {
720
+ description: `Also save the matching \`@types/*\` package as a
721
+ devDependency for each added registry package that
722
+ ships no TypeScript types of its own.`,
723
+ },
705
724
  'save-config': {
706
725
  description: `Save the registry and git host options given on the
707
726
  command line or via \`VLT_*\` env (\`--registry\`,
@@ -713,6 +732,17 @@ export const definition = j
713
732
  \`add\` or \`update\`. Values a config file already
714
733
  has are left alone.`,
715
734
  },
735
+ 'migrate-workspaces': {
736
+ description: `Save workspaces read from the \`workspaces\` field of
737
+ the root \`package.json\` or from
738
+ \`pnpm-workspace.yaml\` to the project \`vlt.json\` on
739
+ \`vlt install\`. \`vlt.json\` then takes precedence.
740
+
741
+ Set \`--no-migrate-workspaces\` to skip this; an
742
+ already saved \`workspaces\` field stays until
743
+ removed.`,
744
+ default: true,
745
+ },
716
746
  })
717
747
  .opt({
718
748
  'save-prefix': {
@@ -780,6 +810,40 @@ export const definition = j
780
810
  Runs scripts only for direct dependencies of the current project and any occurrences
781
811
  of a specific dependency with the name "my-package" anywhere in the dependency graph.`,
782
812
  },
813
+ })
814
+ .opt({
815
+ 'allow-skills': {
816
+ hint: 'query',
817
+ description: `Link the agent skills (folders with a \`SKILL.md\`) shipped by
818
+ installed packages matching this DSS query into
819
+ the project's agent skill dirs (see \`skills-dir\`)
820
+ after \`install\`, \`ci\`, \`update\` or
821
+ \`uninstall\`. End a selector with
822
+ \`:skill(<name>, …)\` to pick single skills (\`*\`
823
+ globs, double-quoted names). Unset (the default) or
824
+ \`:not(*)\` links nothing and skips all skills work.
825
+
826
+ \`vlt install --allow-skills=<query>\` saves the query
827
+ to the project \`vlt.json\`; \`vlt skills link
828
+ [<query>]\` sets, saves and applies it.
829
+
830
+ Example: --allow-skills="#agent-browser:skill(agent-browser)"
831
+ Links the agent-browser skill from a package with the same name.`,
832
+ },
833
+ })
834
+ .optList({
835
+ 'skills-dir': {
836
+ hint: 'path',
837
+ description: `Dirs to link agent skills into (see \`allow-skills\`),
838
+ relative to the project root. Replaces detection:
839
+ \`.agents/skills\`, plus \`<folder>/skills\` for each
840
+ agent folder at the project root (e.g. \`.claude/\`),
841
+ \`.claude/skills\` inside Claude Code, and existing
842
+ \`.<name>/skills\` dirs. An empty list (\`[]\` in
843
+ vlt.json, or \`--skills-dir=\`) links nothing. Each
844
+ dir must be inside the project, not in
845
+ node_modules.`,
846
+ },
783
847
  })
784
848
  .opt({
785
849
  access: {
@@ -85,6 +85,8 @@ export type ConfigOptions = ConfigOptionsNoExtras & Pick<SpecOptions, 'catalog'
85
85
  storeRoot: string;
86
86
  monorepo?: Monorepo;
87
87
  packageInfo: PackageInfoClient;
88
+ /** aborted when the command fails */
89
+ signal: AbortSignal;
88
90
  };
89
91
  /**
90
92
  * The base config definition set as a type
@@ -125,6 +127,8 @@ export declare class Config {
125
127
  * to recalculate the options.
126
128
  */
127
129
  resetOptions(projectRoot?: string): void;
130
+ /** Abort requests still in flight */
131
+ abort(): void;
128
132
  /**
129
133
  * positional arguments to the vlt process
130
134
  */
@@ -24,6 +24,7 @@ import { error } from '@vltpkg/error-cause';
24
24
  import { PackageInfoClient } from '@vltpkg/package-info';
25
25
  import { PackageJson } from '@vltpkg/package-json';
26
26
  import { storeRoot } from '@vltpkg/registry-client/store-root';
27
+ import { cloneAvailable } from '@vltpkg/tar/clonefile';
27
28
  import { resetCaches } from '@vltpkg/dep-id';
28
29
  import { getOptions } from '@vltpkg/spec';
29
30
  import { assertRecordStringString, assertRecordStringT, isObject, isRecordStringString, } from '@vltpkg/types';
@@ -35,6 +36,7 @@ import { PathScurry } from 'path-scurry';
35
36
  import { commands, definition, defaultValues, getCommand, isRecordField, recordFields, storeLinkers, } from "./definition.js";
36
37
  import { merge } from "./merge.js";
37
38
  import { cloneLayer, mergeLayers } from "./merge-layers.js";
39
+ import { assertSpecAliases } from "./spec-aliases.js";
38
40
  export { commands, definition, isRecordField, recordFields, };
39
41
  export const kCustomInspect = Symbol.for('nodejs.util.inspect.custom');
40
42
  // turn a set of pairs into a Record object.
@@ -172,6 +174,7 @@ export class Config {
172
174
  const asRecords = pairsToRecords(this.parse().values);
173
175
  const extras = {
174
176
  projectRoot: this.projectRoot,
177
+ signal: this.#abort.signal,
175
178
  storeRoot: storeRoot(asRecords.cache),
176
179
  scurry,
177
180
  packageJson,
@@ -197,7 +200,8 @@ export class Config {
197
200
  return Object.fromEntries(Object.entries(options).filter(([k]) => k !== 'monorepo' &&
198
201
  k !== 'scurry' &&
199
202
  k !== 'packageJson' &&
200
- k !== 'packageInfo'));
203
+ k !== 'packageInfo' &&
204
+ k !== 'signal'));
201
205
  },
202
206
  });
203
207
  return this.#options;
@@ -213,6 +217,11 @@ export class Config {
213
217
  }
214
218
  // memoized options() getter value
215
219
  #options;
220
+ #abort = new AbortController();
221
+ /** Abort requests still in flight */
222
+ abort() {
223
+ this.#abort.abort();
224
+ }
216
225
  /**
217
226
  * positional arguments to the vlt process
218
227
  */
@@ -277,13 +286,16 @@ export class Config {
277
286
  // ok, applied cmd-specific defaults, do rest of the parse
278
287
  this.jack.applyDefaults(p);
279
288
  // `auto` only hardlinks from the global store on linux: on APFS a
280
- // hardlink is the slowest way to place a file, so darwin and win32
281
- // unpack by default. the env layer is rewritten too, so `explicit`
282
- // records the resolved value.
289
+ // hardlink is the slowest way to place a file, so darwin clones
290
+ // instead, when its node can load `node:ffi` to call clonefile(2)
291
+ // with, and win32 (and darwin without it) unpack by default. the
292
+ // env layer is rewritten too, so `explicit` records the resolved
293
+ // value.
283
294
  if (p.values['store-linker'] === 'auto' &&
284
295
  process.platform !== 'linux') {
285
- p.values['store-linker'] = 'unpack';
286
- envValues['store-linker'] = 'unpack';
296
+ const linker = cloneAvailable() ? 'clone' : 'unpack';
297
+ p.values['store-linker'] = linker;
298
+ envValues['store-linker'] = linker;
287
299
  }
288
300
  // what was set on the cli or env for this run. record fields hold
289
301
  // only those pairs, env then cli, so later ones win per key.
@@ -317,6 +329,10 @@ export class Config {
317
329
  const base = (k in cmdPairs ? cmdPairs[k] : fileValues[k]) ?? [];
318
330
  values[k] = dedupePairs([...base, ...pairs]);
319
331
  }
332
+ // skipped for `vlt config`, so a bad alias can be removed
333
+ if (cmdOrFallback !== 'config') {
334
+ assertSpecAliases(pairsToRecords(p.values));
335
+ }
320
336
  this.jack.writeEnv(p);
321
337
  process.env[kParentEnv] = JSON.stringify({
322
338
  explicit,
@@ -0,0 +1,22 @@
1
+ declare const builtins: {
2
+ registries: {
3
+ gh: string;
4
+ };
5
+ 'jsr-registries': {
6
+ jsr: string;
7
+ };
8
+ 'git-hosts': {
9
+ github: string;
10
+ bitbucket: string;
11
+ gitlab: string;
12
+ gist: string;
13
+ };
14
+ };
15
+ type SpecAliasField = keyof typeof builtins;
16
+ /**
17
+ * Throw `ECONFIG` if a spec prefix alias is in more than one of
18
+ * `registries` / `jsr-registries` / `git-hosts` (built-ins included),
19
+ * or is a built-in protocol. Overriding a built-in in its own field is ok.
20
+ */
21
+ export declare const assertSpecAliases: (values: Partial<Record<SpecAliasField, Record<string, string>>>) => void;
22
+ export {};
@@ -0,0 +1,47 @@
1
+ import { error } from '@vltpkg/error-cause';
2
+ import { builtinProtocols, defaultGitHosts, defaultJsrRegistries, defaultRegistries, } from '@vltpkg/spec';
3
+ const builtins = {
4
+ registries: defaultRegistries,
5
+ 'jsr-registries': defaultJsrRegistries,
6
+ 'git-hosts': defaultGitHosts,
7
+ };
8
+ const fields = Object.keys(builtins);
9
+ /**
10
+ * Throw `ECONFIG` if a spec prefix alias is in more than one of
11
+ * `registries` / `jsr-registries` / `git-hosts` (built-ins included),
12
+ * or is a built-in protocol. Overriding a built-in in its own field is ok.
13
+ */
14
+ export const assertSpecAliases = (values) => {
15
+ const owners = new Map();
16
+ for (const field of fields) {
17
+ for (const key of Object.keys(builtins[field])) {
18
+ if (values[field]?.[key] === undefined) {
19
+ owners.set(key, { field, builtin: true });
20
+ }
21
+ }
22
+ }
23
+ for (const field of fields) {
24
+ for (const key of Object.keys(values[field] ?? {})) {
25
+ const name = `${field}.${key}`;
26
+ if (builtinProtocols.has(key)) {
27
+ throw error(`${name} shadows the built-in \`${key}:\` protocol`, {
28
+ code: 'ECONFIG',
29
+ name,
30
+ found: key,
31
+ wanted: 'a name that is not a built-in protocol',
32
+ });
33
+ }
34
+ const owner = owners.get(key);
35
+ if (owner) {
36
+ const where = `${owner.field}.${key}${owner.builtin ? ' (built-in)' : ''}`;
37
+ throw error(`\`${key}:\` spec prefix defined in both ${where} and ${name}`, {
38
+ code: 'ECONFIG',
39
+ name,
40
+ found: key,
41
+ wanted: `a ${field} name not used by ${owner.field}`,
42
+ });
43
+ }
44
+ owners.set(key, { field, builtin: false });
45
+ }
46
+ }
47
+ };
@@ -183,6 +183,10 @@ const commandHelp = {
183
183
  desc: 'Configure your vlt.io account registries',
184
184
  defaultOrder: 0,
185
185
  },
186
+ skills: {
187
+ args: '[list|link]',
188
+ desc: 'Manage agent skills from installed packages',
189
+ },
186
190
  token: {
187
191
  args: '[add|rm]',
188
192
  desc: 'Manage authentication tokens',
@@ -14,6 +14,7 @@ import { stderr, stdout, styleTextStdout } from "./output.js";
14
14
  import assert from 'node:assert';
15
15
  import { resolve } from 'node:path';
16
16
  import { createHostContextsMap } from "./query-host-contexts.js";
17
+ import { sortScopeLocations } from "./sort-scope-locations.js";
17
18
  const isScriptSet = (o) => {
18
19
  if (!o || typeof o !== 'object')
19
20
  return false;
@@ -98,11 +99,13 @@ export class ExecCommand {
98
99
  this.#defaultIgnoreMissing = true;
99
100
  const mainManifest = conf.options.packageJson.maybeRead(this.projectRoot);
100
101
  let graph;
102
+ let monorepo;
101
103
  if (mainManifest) {
104
+ monorepo = Monorepo.load(this.projectRoot);
102
105
  graph = actual.load({
103
106
  ...conf.options,
104
107
  mainManifest,
105
- monorepo: Monorepo.load(this.projectRoot),
108
+ monorepo,
106
109
  loadManifests: false,
107
110
  });
108
111
  }
@@ -120,14 +123,15 @@ export class ExecCommand {
120
123
  const { nodes } = await query.search(queryString, {
121
124
  signal: new AbortController().signal,
122
125
  });
123
- this.#nodes = [];
126
+ const locations = [];
124
127
  for (const node of nodes) {
125
128
  const { location } = node.toJSON();
126
129
  assert(location, error(`node ${node.id} has no location`, {
127
130
  found: node,
128
131
  }));
129
- this.#nodes.push(location);
132
+ locations.push(location);
130
133
  }
134
+ this.#nodes = sortScopeLocations(locations, monorepo);
131
135
  }
132
136
  else if (paths?.length || groups?.length || recursive) {
133
137
  this.#defaultIgnoreMissing = true;
@@ -163,8 +167,9 @@ export class ExecCommand {
163
167
  }
164
168
  // run across workspaces
165
169
  let failed = false;
166
- const runInDir = async (cwd, label) => {
167
- const result = await this.bg(this.bgArg(cwd)).catch((er) => {
170
+ const runInDir = async (cwd, label, signal) => {
171
+ const arg = { ...this.bgArg(cwd), signal };
172
+ const result = await this.bg(arg).catch((er) => {
168
173
  if (isErrorWithCause(er) && isRunResult(er.cause)) {
169
174
  this.printResult(label, er.cause);
170
175
  }
@@ -189,7 +194,8 @@ export class ExecCommand {
189
194
  }
190
195
  }
191
196
  else if (this.#monorepo) {
192
- const wsResultMap = await this.#monorepo.run(ws => runInDir(ws.fullpath, ws.path));
197
+ // aborted on the first failure, killing the rest
198
+ const wsResultMap = await this.#monorepo.run((ws, signal) => runInDir(ws.fullpath, ws.path, signal));
193
199
  for (const [ws, result] of wsResultMap) {
194
200
  if (result)
195
201
  resultMap.set(ws.path, result);
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { format } from 'node:util';
2
- import { asRootError } from '@vltpkg/output/error';
2
+ import { asRootError, findRootError } from '@vltpkg/output/error';
3
+ import { setVltJsonOnly } from '@vltpkg/workspaces';
3
4
  import { loadPackageJson } from 'package-json-from-dist';
4
5
  import { getSortedCliOptions, getSortedKeys, } from "./config/definition.js";
5
6
  import { Config } from "./config/index.js";
@@ -12,13 +13,14 @@ const loadVlt = async (cwd, argv) => {
12
13
  return await Config.load(cwd, argv);
13
14
  }
14
15
  catch (e) {
15
- const err = asRootError(e, { code: 'JACKSPEAK' });
16
+ const code = findRootError(e, { code: 'ECONFIG' }) ? 'ECONFIG' : 'JACKSPEAK';
17
+ const err = asRootError(e, { code });
16
18
  const { found, path, wanted, name } = err.cause;
17
19
  const isConfigFile = typeof path === 'string';
18
- const msg = isConfigFile ?
19
- `Problem in Config File ${path}`
20
- : 'Invalid Option Flag';
21
- const validOptions = wanted ? undefined
20
+ const msg = isConfigFile ? `Problem in Config File ${path}`
21
+ : code === 'ECONFIG' ? 'Config Error'
22
+ : 'Invalid Option Flag';
23
+ const validOptions = wanted || code === 'ECONFIG' ? undefined
22
24
  : isConfigFile ? getSortedKeys()
23
25
  : getSortedCliOptions();
24
26
  stderr(msg);
@@ -34,7 +36,10 @@ const loadVlt = async (cwd, argv) => {
34
36
  stderr(indent('Valid Options:'));
35
37
  stderr(indent(validOptions.join('\n'), 4));
36
38
  }
37
- stderr(indent(`Run 'vlt help' for more information about available options.`));
39
+ // `vlt help` would fail the same way
40
+ if (code !== 'ECONFIG') {
41
+ stderr(indent(`Run 'vlt help' for more information about available options.`));
42
+ }
38
43
  return flushAndExit(process.exitCode || 1);
39
44
  }
40
45
  };
@@ -45,6 +50,8 @@ const run = async () => {
45
50
  if (vlt.get('version')) {
46
51
  return stdout(version);
47
52
  }
53
+ // ci reads workspaces from vlt.json only; set before options load
54
+ setVltJsonOnly(vlt.command === 'ci');
48
55
  const { monorepo } = vlt.options;
49
56
  // Infer the workspace by being in that directory.
50
57
  if (vlt.get('workspace') === undefined) {
@@ -57,7 +64,10 @@ const run = async () => {
57
64
  if (vlt.command !== 'init' &&
58
65
  (vlt.get('workspace') || vlt.get('workspace-group')) &&
59
66
  ![...(monorepo?.values() ?? [])].length) {
60
- stderr(`Error: No matching workspaces found. Make sure the vlt.json config, or the "workspaces" field of the root package.json, contains the correct workspaces.`);
67
+ const where = vlt.command === 'ci' ?
68
+ 'the vlt.json config (vlt ci ignores package.json and pnpm-workspace.yaml)'
69
+ : 'the vlt.json config, the "workspaces" field of the root package.json, or the "packages" list of pnpm-workspace.yaml';
70
+ stderr(`Error: No matching workspaces found. Make sure ${where} contains the correct workspaces.`);
61
71
  if (vlt.get('workspace')) {
62
72
  stderr(indent(`Workspace: ${format(vlt.get('workspace'))}`));
63
73
  }
@@ -15,6 +15,8 @@ export type Command<T> = {
15
15
  * Set to `true` by commands that hit a registry or resolve registry
16
16
  * specs. There is no default registry, so `outputCommand()` fails
17
17
  * early with an `ECONFIG` error when one of these runs unconfigured.
18
+ * `publish`/`unpublish` check in-command instead, since
19
+ * `publishConfig.registry` may suffice.
18
20
  */
19
21
  needsRegistry?: boolean;
20
22
  /**
@@ -0,0 +1,16 @@
1
+ import type { WorkspaceConfigSource } from '@vltpkg/workspaces';
2
+ import type { LoadedConfig } from './config/index.ts';
3
+ export type MigratedWorkspaces = {
4
+ source: WorkspaceConfigSource;
5
+ workspaces: string[];
6
+ };
7
+ /**
8
+ * Save workspaces read from `package.json` or `pnpm-workspace.yaml` to
9
+ * the project `vlt.json`, unless `--no-migrate-workspaces`.
10
+ */
11
+ export declare const migrateWorkspaces: (conf: LoadedConfig) => MigratedWorkspaces | undefined;
12
+ /**
13
+ * Warn when workspaces are declared only outside `vlt.json`, which
14
+ * `vlt ci` ignores.
15
+ */
16
+ export declare const warnIgnoredWorkspaces: (conf: LoadedConfig) => void;
@@ -0,0 +1,44 @@
1
+ import { save } from '@vltpkg/vlt-json';
2
+ import { migratableWSConfig, resolveWSConfig, } from '@vltpkg/workspaces';
3
+ import { stderr } from "./output.js";
4
+ // workspaces declared outside vlt.json, if any
5
+ const found = (conf) => {
6
+ const resolved = resolveWSConfig(conf.options.projectRoot, conf.options.packageJson, false);
7
+ const workspaces = migratableWSConfig(resolved);
8
+ const { source } = resolved;
9
+ if (!workspaces?.length || !source)
10
+ return;
11
+ return { source, workspaces };
12
+ };
13
+ /**
14
+ * Save workspaces read from `package.json` or `pnpm-workspace.yaml` to
15
+ * the project `vlt.json`, unless `--no-migrate-workspaces`.
16
+ */
17
+ export const migrateWorkspaces = (conf) => {
18
+ if (!conf.options['migrate-workspaces'])
19
+ return;
20
+ const migrated = found(conf);
21
+ if (migrated)
22
+ save('workspaces', migrated.workspaces);
23
+ return migrated;
24
+ };
25
+ /**
26
+ * Warn when workspaces are declared only outside `vlt.json`, which
27
+ * `vlt ci` ignores.
28
+ */
29
+ export const warnIgnoredWorkspaces = (conf) => {
30
+ const { loglevel } = conf.values;
31
+ if (loglevel === 'silent' || loglevel === 'error')
32
+ return;
33
+ let migrated;
34
+ try {
35
+ migrated = found(conf);
36
+ }
37
+ catch {
38
+ // invalid workspaces config: nothing to suggest
39
+ return;
40
+ }
41
+ if (!migrated)
42
+ return;
43
+ stderr(`Warning: vlt ci reads workspaces from vlt.json only; ignoring ${migrated.source}, its workspaces are not installed. Run \`vlt install\` to save them to vlt.json, or add them there.`);
44
+ };
package/dist/output.js CHANGED
@@ -227,6 +227,7 @@ export const outputCommand = async (cliCommand, conf, { start, vltVersion } = {
227
227
  ...formatOptions,
228
228
  colors: stderrColor,
229
229
  });
230
- await flushAndExit(process.exitCode);
230
+ // stop pending requests, so the process can exit on its own
231
+ conf.abort();
231
232
  }
232
233
  };
package/dist/print-err.js CHANGED
@@ -223,8 +223,11 @@ const printCode = (err, usage, stderr, format) => {
223
223
  return {};
224
224
  }
225
225
  case 'ECONFIG': {
226
- const { found, wanted, validOptions } = err.cause;
226
+ const { found, wanted, validOptions, from } = err.cause;
227
227
  stderr(`Config Error: ${err.message}`);
228
+ if (from) {
229
+ stderr(indent(`From: ${format(from)}`));
230
+ }
228
231
  if (found) {
229
232
  stderr(indent(`Found: ${format(found)}`));
230
233
  }
@@ -0,0 +1,9 @@
1
+ import type { NormalizedManifest } from '@vltpkg/types';
2
+ type PublishConfig = {
3
+ directory?: string;
4
+ registry?: string;
5
+ access?: string;
6
+ tag?: string;
7
+ };
8
+ export declare const getPublishConfig: (manifest: NormalizedManifest) => PublishConfig | undefined;
9
+ export {};
@@ -0,0 +1,8 @@
1
+ export const getPublishConfig = (manifest) => {
2
+ const pc = manifest
3
+ .publishConfig;
4
+ if (pc && typeof pc === 'object') {
5
+ return pc;
6
+ }
7
+ return undefined;
8
+ };