@vltpkg/cli-sdk 1.0.0-rc.29 → 1.0.0-rc.30

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.
@@ -1,5 +1,5 @@
1
1
  import { error } from '@vltpkg/error-cause';
2
- import { RegistryClient, oidc } from '@vltpkg/registry-client';
2
+ import { RegistryClient, oidc, registryBase, } from '@vltpkg/registry-client';
3
3
  import { run } from '@vltpkg/run';
4
4
  import { commandUsage } from "../config/usage.js";
5
5
  import { packTarball } from "../pack-tarball.js";
@@ -146,14 +146,26 @@ export const command = async (conf) => {
146
146
  }
147
147
  return results;
148
148
  };
149
+ const getPublishConfig = (manifest) => {
150
+ const pc = manifest
151
+ .publishConfig;
152
+ if (pc && typeof pc === 'object') {
153
+ return pc;
154
+ }
155
+ return undefined;
156
+ };
149
157
  const commandSingle = async (location, conf) => {
150
158
  const manifestPath = conf.options.packageJson.find(location);
151
159
  assert(manifestPath, 'No package.json found');
152
160
  const manifestDir = dirname(manifestPath);
153
161
  const manifest = conf.options.packageJson.read(manifestDir);
154
162
  assert(!manifest.private, error('Package has been marked as private'));
155
- const { tag, access, registry, 'dry-run': dry = false, otp, } = conf.options;
156
- const registryUrl = new URL(registry);
163
+ const { 'dry-run': dry = false, otp } = conf.options;
164
+ const publishConfig = getPublishConfig(manifest);
165
+ const tag = publishConfig?.tag ?? conf.options.tag;
166
+ const access = publishConfig?.access ?? conf.options.access;
167
+ const registry = publishConfig?.registry ?? conf.options.registry;
168
+ const registryUrl = new URL(registryBase(registry));
157
169
  const runOptions = {
158
170
  cwd: manifestDir,
159
171
  projectRoot: conf.projectRoot,
@@ -251,8 +263,10 @@ const commandSingle = async (location, conf) => {
251
263
  }
252
264
  if (response.statusCode !== 200 && response.statusCode !== 201) {
253
265
  let extraMsg = '';
254
- // special case 404 errors to provide a better hint to the user
255
- if (response.statusCode === 404) {
266
+ if (response.statusCode === 409) {
267
+ extraMsg = `.\n⚠️ ${name}@${version} already exists in the registry. Bump the version and try again.`;
268
+ }
269
+ else if (response.statusCode === 404) {
256
270
  extraMsg =
257
271
  ".\n⚠️ Make sure you're logged in and have access to publish the package.";
258
272
  }
@@ -14,10 +14,15 @@ export type TokenInfo = {
14
14
  export type RegistryTokens = {
15
15
  registry: string;
16
16
  alias?: string;
17
+ /** Masked local keychain token, if stored */
18
+ localToken?: string;
17
19
  tokens: TokenInfo[];
18
20
  error?: string;
19
21
  };
20
- export type TokenListResult = RegistryTokens[];
22
+ export type TokenListResult = {
23
+ identity: string;
24
+ registries: RegistryTokens[];
25
+ };
21
26
  export declare const usage: CommandUsage;
22
27
  export declare const views: {
23
28
  readonly human: (r: TokenListResult | void) => string | undefined;
@@ -1,5 +1,5 @@
1
1
  import { error } from '@vltpkg/error-cause';
2
- import { deleteToken, normalizeRegistryKey, RegistryClient, setToken, } from '@vltpkg/registry-client';
2
+ import { deleteToken, getKC, getToken, normalizeRegistryKey, registryBase, RegistryClient, setToken, } from '@vltpkg/registry-client';
3
3
  import { commandUsage } from "../config/usage.js";
4
4
  import { readPassword } from "../read-password.js";
5
5
  export const usage = () => commandUsage({
@@ -9,10 +9,9 @@ export const usage = () => commandUsage({
9
9
  subcommands: {
10
10
  list: {
11
11
  usage: '',
12
- description: `List all tokens for configured registries.
13
- Queries each registry's token API and displays
14
- token metadata including key, creation date,
15
- and permissions.`,
12
+ description: `List tokens for configured registries. Shows
13
+ locally stored auth tokens and queries each
14
+ registry's token API for remote token metadata.`,
16
15
  },
17
16
  add: {
18
17
  usage: '',
@@ -47,6 +46,12 @@ const formatDate = (iso) => {
47
46
  day: 'numeric',
48
47
  });
49
48
  };
49
+ const maskToken = (token) => {
50
+ const bare = token.replace(/^(Bearer|Basic)\s+/i, '');
51
+ if (bare.length <= 8)
52
+ return bare + '…';
53
+ return bare.slice(0, 8) + '…';
54
+ };
50
55
  const formatTokenEntry = (t) => {
51
56
  const parts = [
52
57
  `key: ${t.key}`,
@@ -61,20 +66,30 @@ const formatTokenEntry = (t) => {
61
66
  };
62
67
  const formatRegistryTokens = (r) => {
63
68
  const header = r.alias ? `${r.alias} (${r.registry})` : r.registry;
64
- if (r.error)
65
- return `${header}\n error: ${r.error}`;
66
- if (r.tokens.length === 0)
67
- return `${header}\n (no tokens found)`;
68
- return [
69
- header,
70
- ...r.tokens.map(t => ` ${formatTokenEntry(t)}`),
71
- ].join('\n');
69
+ const lines = [header];
70
+ lines.push(` local: ${r.localToken ? maskToken(r.localToken) : '(none)'}`);
71
+ if (!r.localToken) {
72
+ lines.push(' (skipped remote query — no auth token)');
73
+ }
74
+ else if (r.error) {
75
+ lines.push(` error: ${r.error}`);
76
+ }
77
+ else if (r.tokens.length === 0) {
78
+ lines.push(' (no remote tokens found)');
79
+ }
80
+ else {
81
+ for (const t of r.tokens) {
82
+ lines.push(` ${formatTokenEntry(t)}`);
83
+ }
84
+ }
85
+ return lines.join('\n');
72
86
  };
73
87
  export const views = {
74
88
  human: (r) => {
75
89
  if (!r)
76
90
  return;
77
- return r.map(formatRegistryTokens).join('\n\n');
91
+ const header = r.identity ? `identity: ${r.identity}` : 'identity: (default)';
92
+ return [header, ...r.registries.map(formatRegistryTokens)].join('\n\n');
78
93
  },
79
94
  json: (r) => {
80
95
  if (!r)
@@ -82,8 +97,12 @@ export const views = {
82
97
  return r;
83
98
  },
84
99
  };
85
- const listTokens = async (rc, registry, alias) => {
86
- const tokensUrl = new URL('-/npm/v1/tokens', registry);
100
+ const listTokens = async (rc, registry, identity, alias) => {
101
+ const localTok = await getToken(normalizeRegistryKey(registry), identity);
102
+ if (!localTok) {
103
+ return { registry, alias, tokens: [] };
104
+ }
105
+ const tokensUrl = new URL('-/npm/v1/tokens', registryBase(registry));
87
106
  try {
88
107
  const objects = await rc.scroll(tokensUrl, {
89
108
  useCache: false,
@@ -91,6 +110,7 @@ const listTokens = async (rc, registry, alias) => {
91
110
  return {
92
111
  registry,
93
112
  alias,
113
+ localToken: localTok,
94
114
  tokens: objects.map(o => ({
95
115
  key: o.key,
96
116
  token: o.token,
@@ -104,6 +124,7 @@ const listTokens = async (rc, registry, alias) => {
104
124
  return {
105
125
  registry,
106
126
  alias,
127
+ localToken: localTok,
107
128
  tokens: [],
108
129
  error: err instanceof Error ? err.message : String(err),
109
130
  };
@@ -114,18 +135,37 @@ export const command = async (conf) => {
114
135
  switch (conf.positionals[0]) {
115
136
  case 'list': {
116
137
  const rc = new RegistryClient(conf.options);
117
- const results = [];
138
+ const identity = conf.options.identity;
139
+ const registries = [];
140
+ const seen = new Set();
118
141
  // Always query the default registry first
119
- results.push(await listTokens(rc, conf.options.registry, 'default'));
142
+ const defaultReg = conf.options.registry;
143
+ seen.add(normalizeRegistryKey(defaultReg));
144
+ registries.push(await listTokens(rc, defaultReg, identity, 'default'));
120
145
  // Then query all configured registry aliases
121
- const registries = conf.options.registries;
122
- for (const [alias, registry] of Object.entries(registries)) {
123
- // Skip if it's the same as the default registry
124
- if (registry !== conf.options.registry) {
125
- results.push(await listTokens(rc, registry, alias));
146
+ const configuredRegistries = conf.options.registries;
147
+ for (const [alias, registry] of Object.entries(configuredRegistries)) {
148
+ const key = normalizeRegistryKey(registry);
149
+ if (!seen.has(key)) {
150
+ seen.add(key);
151
+ registries.push(await listTokens(rc, registry, identity, alias));
152
+ }
153
+ }
154
+ // Also show any keychain entries for registries not
155
+ // already covered (e.g. added via `vlt token add --registry`)
156
+ const kc = getKC(identity);
157
+ for (const key of await kc.keys()) {
158
+ if (!seen.has(key)) {
159
+ seen.add(key);
160
+ const tok = await kc.get(key);
161
+ registries.push({
162
+ registry: key,
163
+ localToken: tok ?? undefined,
164
+ tokens: [],
165
+ });
126
166
  }
127
167
  }
128
- return results;
168
+ return { identity, registries };
129
169
  }
130
170
  case 'add': {
131
171
  await setToken(reg, `Bearer ${await readPassword('Paste bearer token: ')}`, conf.options.identity);
@@ -13,6 +13,19 @@ export type PackTarballResult = {
13
13
  /** The manifest used for packing (may differ from input when publishConfig.directory is set). */
14
14
  resolvedManifest: NormalizedManifest;
15
15
  };
16
+ /**
17
+ * Build the tar filter function for packing.
18
+ *
19
+ * Follows npm's precedence rules:
20
+ * 1. `files` field in package.json → allowlist (ignore files are not read)
21
+ * 2. `.npmignore` → denylist (`.gitignore` is NOT read)
22
+ * 3. `.gitignore` → fallback denylist when no `.npmignore` exists
23
+ *
24
+ * Regardless of mode, always-excluded files (lockfiles, .git, node_modules,
25
+ * .npmrc, vlt.json, etc.) are excluded and always-included files (package.json,
26
+ * README, LICENSE, etc.) are included.
27
+ */
28
+ export declare const buildPackFilter: (manifest: NormalizedManifest, packDir: string) => ((path: string) => boolean);
16
29
  /**
17
30
  * Create a tarball from a package directory
18
31
  * @param {NormalizedManifest} manifest - The manifest of the package to pack
@@ -3,10 +3,11 @@ import { minimatch } from 'minimatch';
3
3
  import { error } from '@vltpkg/error-cause';
4
4
  import * as ssri from 'ssri';
5
5
  import assert from 'node:assert';
6
- import { existsSync, statSync } from 'node:fs';
6
+ import { existsSync, readFileSync, statSync } from 'node:fs';
7
7
  import { Spec } from '@vltpkg/spec';
8
8
  import { join } from 'node:path';
9
9
  import { parse, stringify } from 'polite-json';
10
+ import ignore from 'ignore';
10
11
  /**
11
12
  * Replace workspace: and catalog: specs with actual versions
12
13
  * @param {NormalizedManifest} manifest_ - The manifest to process
@@ -82,6 +83,121 @@ const replaceWorkspaceAndCatalogSpecs = (manifest_, config) => {
82
83
  }
83
84
  return manifest;
84
85
  };
86
+ const alwaysExcludePatterns = [
87
+ /^\.?\/?\.git(\/|$)/,
88
+ /^\.?\/?node_modules(\/|$)/,
89
+ /^\.?\/?\.nyc_output(\/|$)/,
90
+ /^\.?\/?coverage(\/|$)/,
91
+ /^\.?\/?\.vscode(\/|$)/,
92
+ /^\.?\/?\.idea(\/|$)/,
93
+ /^\.?\/?\.DS_Store$/,
94
+ /^\.?\/?\.npmrc$/,
95
+ /^\.?\/?\.gitignore$/,
96
+ /^\.?\/?\.npmignore$/,
97
+ /^\.?\/?\.editorconfig$/,
98
+ /^\.?\/?package-lock\.json$/,
99
+ /^\.?\/?yarn\.lock$/,
100
+ /^\.?\/?pnpm-lock\.yaml$/,
101
+ /^\.?\/?bun\.lockb$/,
102
+ /^\.?\/?bun\.lock$/,
103
+ /^\.?\/?vlt-lock\.json$/,
104
+ /^\.?\/?vlt\.json$/,
105
+ /~$/,
106
+ /\.swp$/,
107
+ /\.tgz$/,
108
+ ];
109
+ const alwaysIncludePatterns = [
110
+ /^README(\..*)?$/i,
111
+ /^CHANGELOG(\..*)?$/i,
112
+ /^HISTORY(\..*)?$/i,
113
+ /^LICENSE(\..*)?$/i,
114
+ /^LICENCE(\..*)?$/i,
115
+ ];
116
+ /**
117
+ * Read an ignore file (.npmignore or .gitignore) and return its contents,
118
+ * or undefined if the file does not exist.
119
+ */
120
+ const readIgnoreFile = (dir, name) => {
121
+ const filePath = join(dir, name);
122
+ if (!existsSync(filePath))
123
+ return undefined;
124
+ return readFileSync(filePath, 'utf8');
125
+ };
126
+ /**
127
+ * Build the tar filter function for packing.
128
+ *
129
+ * Follows npm's precedence rules:
130
+ * 1. `files` field in package.json → allowlist (ignore files are not read)
131
+ * 2. `.npmignore` → denylist (`.gitignore` is NOT read)
132
+ * 3. `.gitignore` → fallback denylist when no `.npmignore` exists
133
+ *
134
+ * Regardless of mode, always-excluded files (lockfiles, .git, node_modules,
135
+ * .npmrc, vlt.json, etc.) are excluded and always-included files (package.json,
136
+ * README, LICENSE, etc.) are included.
137
+ */
138
+ export const buildPackFilter = (manifest, packDir) => {
139
+ const manifestWithFiles = manifest;
140
+ const hasFilesField = Array.isArray(manifestWithFiles.files) &&
141
+ manifestWithFiles.files.length > 0;
142
+ const hasEmptyFilesField = Array.isArray(manifestWithFiles.files) &&
143
+ manifestWithFiles.files.length === 0;
144
+ // Build ignore matcher from .npmignore or .gitignore (only when no files field)
145
+ let ig;
146
+ if (!hasFilesField && !hasEmptyFilesField) {
147
+ const npmignoreContent = readIgnoreFile(packDir, '.npmignore');
148
+ if (npmignoreContent !== undefined) {
149
+ ig = ignore().add(npmignoreContent);
150
+ }
151
+ else {
152
+ const gitignoreContent = readIgnoreFile(packDir, '.gitignore');
153
+ if (gitignoreContent !== undefined) {
154
+ ig = ignore().add(gitignoreContent);
155
+ }
156
+ }
157
+ }
158
+ return (path) => {
159
+ const normalizedPath = path.replace(/^\.\//, '');
160
+ if (path === '.' || normalizedPath === '') {
161
+ return true;
162
+ }
163
+ if (alwaysExcludePatterns.some(pattern => pattern.test(normalizedPath))) {
164
+ return false;
165
+ }
166
+ if (alwaysIncludePatterns.some(pattern => pattern.test(normalizedPath))) {
167
+ return true;
168
+ }
169
+ if (normalizedPath === 'package.json') {
170
+ return true;
171
+ }
172
+ // files field: allowlist mode
173
+ if (hasEmptyFilesField) {
174
+ return false;
175
+ }
176
+ if (hasFilesField && manifestWithFiles.files) {
177
+ return manifestWithFiles.files.some((pattern) => {
178
+ if (pattern.endsWith('/')) {
179
+ const dirName = pattern.slice(0, -1);
180
+ const globPattern = pattern.replace(/\/$/, '/**');
181
+ const matchesDir = normalizedPath === dirName;
182
+ const matchesContents = minimatch(normalizedPath, globPattern, { dot: true });
183
+ return matchesDir || matchesContents;
184
+ }
185
+ const directMatch = minimatch(normalizedPath, pattern, {
186
+ dot: true,
187
+ });
188
+ const isParentDir = pattern.includes('/') &&
189
+ pattern.startsWith(normalizedPath + '/');
190
+ const isChildOfPattern = minimatch(normalizedPath, pattern + '/**', { dot: true });
191
+ return directMatch || isParentDir || isChildOfPattern;
192
+ });
193
+ }
194
+ // Ignore-file mode: apply .npmignore or .gitignore deny patterns
195
+ if (ig?.ignores(normalizedPath)) {
196
+ return false;
197
+ }
198
+ return true;
199
+ };
200
+ };
85
201
  /**
86
202
  * Create a tarball from a package directory
87
203
  * @param {NormalizedManifest} manifest - The manifest of the package to pack
@@ -117,94 +233,13 @@ export const packTarball = async (manifest, dir, config) => {
117
233
  const tarballName = `${manifest.name}-${manifest.version}.tgz`;
118
234
  try {
119
235
  config.options.packageJson.write(packDir, processedManifest);
236
+ const packFilter = buildPackFilter(manifest, packDir);
120
237
  const tarballData = await tarCreate({
121
238
  cwd: packDir,
122
239
  gzip: true,
123
240
  portable: true,
124
241
  prefix: 'package/',
125
- filter: (path) => {
126
- // Normalize path - remove leading './'
127
- const normalizedPath = path.replace(/^\.\//, '');
128
- // Always include root directory
129
- if (path === '.' || normalizedPath === '') {
130
- return true;
131
- }
132
- // Always exclude certain files/directories
133
- const alwaysExcludePatterns = [
134
- /^\.?\/?\.git(\/|$)/,
135
- /^\.?\/?node_modules(\/|$)/,
136
- /^\.?\/?\.nyc_output(\/|$)/,
137
- /^\.?\/?coverage(\/|$)/,
138
- /^\.?\/?\.DS_Store$/,
139
- /^\.?\/?\.npmrc$/,
140
- /^\.?\/?package-lock\.json$/,
141
- /^\.?\/?yarn\.lock$/,
142
- /^\.?\/?pnpm-lock\.yaml$/,
143
- /^\.?\/?bun\.lockb$/,
144
- /^\.?\/?bun\.lock$/,
145
- /^\.?\/?vlt-lock\.json$/,
146
- /~$/,
147
- /\.swp$/,
148
- /\.tgz$/,
149
- ];
150
- if (alwaysExcludePatterns.some(pattern => pattern.test(normalizedPath))) {
151
- return false;
152
- }
153
- // Always include certain files
154
- const alwaysIncludePatterns = [
155
- /^README(\..*)?$/i,
156
- /^CHANGELOG(\..*)?$/i,
157
- /^HISTORY(\..*)?$/i,
158
- /^LICENSE(\..*)?$/i,
159
- /^LICENCE(\..*)?$/i,
160
- ];
161
- if (alwaysIncludePatterns.some(pattern => pattern.test(normalizedPath))) {
162
- return true;
163
- }
164
- // Always include package.json
165
- if (normalizedPath === 'package.json') {
166
- return true;
167
- }
168
- // If files field is specified in package.json, use it for inclusion
169
- const manifestWithFiles = manifest;
170
- if (manifestWithFiles.files &&
171
- Array.isArray(manifestWithFiles.files)) {
172
- // Empty files array means exclude everything except always-included files
173
- if (manifestWithFiles.files.length === 0) {
174
- return false;
175
- }
176
- return manifestWithFiles.files.some((pattern) => {
177
- if (pattern.endsWith('/')) {
178
- const dirName = pattern.slice(0, -1);
179
- const globPattern = pattern.replace(/\/$/, '/**');
180
- const matchesDir = normalizedPath === dirName;
181
- const matchesContents = minimatch(normalizedPath, globPattern, {
182
- dot: true,
183
- });
184
- return matchesDir || matchesContents;
185
- }
186
- const directMatch = minimatch(normalizedPath, pattern, {
187
- dot: true,
188
- });
189
- // Check if this path is a parent directory of the pattern
190
- const isParentDir = pattern.includes('/') &&
191
- pattern.startsWith(normalizedPath + '/');
192
- // Check if this path is inside a directory matched by the pattern
193
- // (e.g. files: ["dist"] should include dist/index.js)
194
- const isChildOfPattern = minimatch(normalizedPath, pattern + '/**', { dot: true });
195
- return directMatch || isParentDir || isChildOfPattern;
196
- });
197
- }
198
- // Default behavior when no files field - exclude common development files
199
- const defaultExcludePatterns = [
200
- /^\.?\/?\.vscode(\/|$)/,
201
- /^\.?\/?\.idea(\/|$)/,
202
- /^\.?\/?\.gitignore$/,
203
- /^\.?\/?\.npmignore$/,
204
- /^\.?\/?\.editorconfig$/,
205
- ];
206
- return !defaultExcludePatterns.some(pattern => pattern.test(normalizedPath));
207
- },
242
+ filter: packFilter,
208
243
  }, ['.']).concat();
209
244
  let unpackedSize = 0;
210
245
  const files = [];
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vltpkg/cli-sdk",
3
3
  "description": "The source for the vlt CLI",
4
- "version": "1.0.0-rc.29",
4
+ "version": "1.0.0-rc.30",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/vltpkg/vltpkg.git",
@@ -14,34 +14,35 @@
14
14
  },
15
15
  "dependencies": {
16
16
  "@resvg/resvg-wasm": "^2.6.2",
17
- "@vltpkg/config": "1.0.0-rc.29",
18
- "@vltpkg/dep-id": "1.0.0-rc.29",
19
- "@vltpkg/dot-prop": "1.0.0-rc.29",
20
- "@vltpkg/error-cause": "1.0.0-rc.29",
21
- "@vltpkg/git": "1.0.0-rc.29",
22
- "@vltpkg/graph": "1.0.0-rc.29",
23
- "@vltpkg/graph-run": "1.0.0-rc.29",
24
- "@vltpkg/init": "1.0.0-rc.29",
25
- "@vltpkg/output": "1.0.0-rc.29",
26
- "@vltpkg/package-info": "1.0.0-rc.29",
27
- "@vltpkg/package-json": "1.0.0-rc.29",
28
- "@vltpkg/promise-spawn": "1.0.0-rc.29",
29
- "@vltpkg/query": "1.0.0-rc.29",
30
- "@vltpkg/registry-client": "1.0.0-rc.29",
31
- "@vltpkg/rollback-remove": "1.0.0-rc.29",
32
- "@vltpkg/run": "1.0.0-rc.29",
33
- "@vltpkg/security-archive": "1.0.0-rc.29",
34
- "@vltpkg/spec": "1.0.0-rc.29",
35
- "@vltpkg/types": "1.0.0-rc.29",
36
- "@vltpkg/url-open": "1.0.0-rc.29",
37
- "@vltpkg/vlt-json": "1.0.0-rc.29",
38
- "@vltpkg/vlx": "1.0.0-rc.29",
39
- "@vltpkg/workspaces": "1.0.0-rc.29",
40
- "@vltpkg/xdg": "1.0.0-rc.29",
17
+ "@vltpkg/config": "1.0.0-rc.30",
18
+ "@vltpkg/dep-id": "1.0.0-rc.30",
19
+ "@vltpkg/dot-prop": "1.0.0-rc.30",
20
+ "@vltpkg/error-cause": "1.0.0-rc.30",
21
+ "@vltpkg/git": "1.0.0-rc.30",
22
+ "@vltpkg/graph": "1.0.0-rc.30",
23
+ "@vltpkg/graph-run": "1.0.0-rc.30",
24
+ "@vltpkg/init": "1.0.0-rc.30",
25
+ "@vltpkg/output": "1.0.0-rc.30",
26
+ "@vltpkg/package-info": "1.0.0-rc.30",
27
+ "@vltpkg/package-json": "1.0.0-rc.30",
28
+ "@vltpkg/promise-spawn": "1.0.0-rc.30",
29
+ "@vltpkg/query": "1.0.0-rc.30",
30
+ "@vltpkg/registry-client": "1.0.0-rc.30",
31
+ "@vltpkg/rollback-remove": "1.0.0-rc.30",
32
+ "@vltpkg/run": "1.0.0-rc.30",
33
+ "@vltpkg/security-archive": "1.0.0-rc.30",
34
+ "@vltpkg/spec": "1.0.0-rc.30",
35
+ "@vltpkg/types": "1.0.0-rc.30",
36
+ "@vltpkg/url-open": "1.0.0-rc.30",
37
+ "@vltpkg/vlt-json": "1.0.0-rc.30",
38
+ "@vltpkg/vlx": "1.0.0-rc.30",
39
+ "@vltpkg/workspaces": "1.0.0-rc.30",
40
+ "@vltpkg/xdg": "1.0.0-rc.30",
41
41
  "ansi-to-pre": "^1.0.6",
42
42
  "beautiful-mermaid": "^1.1.3",
43
43
  "chalk": "^5.6.2",
44
44
  "hosted-git-info": "^9.0.2",
45
+ "ignore": "^7.0.5",
45
46
  "ink": "^6.5.1",
46
47
  "ink-spinner": "^5.0.0",
47
48
  "jackspeak": "^4.1.1",