@vltpkg/cli-sdk 1.0.0-rc.23 → 1.0.0-rc.24

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 (103) hide show
  1. package/dist/commands/bugs.d.ts +17 -0
  2. package/dist/commands/bugs.js +163 -0
  3. package/dist/commands/build.d.ts +24 -0
  4. package/dist/commands/build.js +101 -0
  5. package/dist/commands/cache.d.ts +64 -0
  6. package/dist/commands/cache.js +256 -0
  7. package/dist/commands/ci.d.ts +10 -0
  8. package/dist/commands/ci.js +40 -0
  9. package/dist/commands/config.d.ts +5 -0
  10. package/dist/commands/config.js +429 -0
  11. package/dist/commands/create.d.ts +8 -0
  12. package/dist/commands/create.js +102 -0
  13. package/dist/commands/docs.d.ts +17 -0
  14. package/dist/commands/docs.js +153 -0
  15. package/dist/commands/exec-cache.d.ts +48 -0
  16. package/dist/commands/exec-cache.js +145 -0
  17. package/dist/commands/exec-local.d.ts +5 -0
  18. package/dist/commands/exec-local.js +46 -0
  19. package/dist/commands/exec.d.ts +8 -0
  20. package/dist/commands/exec.js +161 -0
  21. package/dist/commands/help.d.ts +3 -0
  22. package/dist/commands/help.js +43 -0
  23. package/dist/commands/init.d.ts +7 -0
  24. package/dist/commands/init.js +116 -0
  25. package/dist/commands/install/reporter.d.ts +10 -0
  26. package/dist/commands/install/reporter.js +93 -0
  27. package/dist/commands/install.d.ts +27 -0
  28. package/dist/commands/install.js +80 -0
  29. package/dist/commands/list.d.ts +17 -0
  30. package/dist/commands/list.js +197 -0
  31. package/dist/commands/login.d.ts +3 -0
  32. package/dist/commands/login.js +22 -0
  33. package/dist/commands/logout.d.ts +3 -0
  34. package/dist/commands/logout.js +22 -0
  35. package/dist/commands/pack.d.ts +31 -0
  36. package/dist/commands/pack.js +205 -0
  37. package/dist/commands/ping.d.ts +17 -0
  38. package/dist/commands/ping.js +114 -0
  39. package/dist/commands/pkg.d.ts +6 -0
  40. package/dist/commands/pkg.js +232 -0
  41. package/dist/commands/publish.d.ts +21 -0
  42. package/dist/commands/publish.js +282 -0
  43. package/dist/commands/query.d.ts +18 -0
  44. package/dist/commands/query.js +216 -0
  45. package/dist/commands/repo.d.ts +17 -0
  46. package/dist/commands/repo.js +157 -0
  47. package/dist/commands/run-exec.d.ts +5 -0
  48. package/dist/commands/run-exec.js +40 -0
  49. package/dist/commands/run.d.ts +5 -0
  50. package/dist/commands/run.js +62 -0
  51. package/dist/commands/token.d.ts +3 -0
  52. package/dist/commands/token.js +39 -0
  53. package/dist/commands/uninstall.d.ts +15 -0
  54. package/dist/commands/uninstall.js +39 -0
  55. package/dist/commands/update.d.ts +13 -0
  56. package/dist/commands/update.js +46 -0
  57. package/dist/commands/version.d.ts +25 -0
  58. package/dist/commands/version.js +252 -0
  59. package/dist/commands/view.d.ts +22 -0
  60. package/dist/commands/view.js +334 -0
  61. package/dist/commands/whoami.d.ts +12 -0
  62. package/dist/commands/whoami.js +28 -0
  63. package/dist/config/definition.d.ts +407 -0
  64. package/dist/config/definition.js +684 -0
  65. package/dist/config/index.d.ts +218 -0
  66. package/dist/config/index.js +488 -0
  67. package/dist/config/merge.d.ts +3 -0
  68. package/dist/config/merge.js +27 -0
  69. package/dist/config/usage.d.ts +18 -0
  70. package/dist/config/usage.js +39 -0
  71. package/dist/custom-help.d.ts +8 -0
  72. package/dist/custom-help.js +419 -0
  73. package/dist/exec-command.d.ts +52 -0
  74. package/dist/exec-command.js +313 -0
  75. package/dist/index.d.ts +3 -0
  76. package/dist/index.js +72 -0
  77. package/dist/load-command.d.ts +15 -0
  78. package/dist/load-command.js +20 -0
  79. package/dist/mermaid-image-view.d.ts +18 -0
  80. package/dist/mermaid-image-view.js +36 -0
  81. package/dist/output.d.ts +20 -0
  82. package/dist/output.js +125 -0
  83. package/dist/pack-tarball.d.ts +23 -0
  84. package/dist/pack-tarball.js +256 -0
  85. package/dist/parse-add-remove-args.d.ts +28 -0
  86. package/dist/parse-add-remove-args.js +103 -0
  87. package/dist/print-err.d.ts +13 -0
  88. package/dist/print-err.js +193 -0
  89. package/dist/query-diff-files.d.ts +17 -0
  90. package/dist/query-diff-files.js +63 -0
  91. package/dist/query-host-contexts.d.ts +15 -0
  92. package/dist/query-host-contexts.js +136 -0
  93. package/dist/read-password.d.ts +7 -0
  94. package/dist/read-password.js +32 -0
  95. package/dist/read-project-folders.d.ts +17 -0
  96. package/dist/read-project-folders.js +100 -0
  97. package/dist/reload-config.d.ts +2 -0
  98. package/dist/reload-config.js +11 -0
  99. package/dist/render-mermaid.d.ts +22 -0
  100. package/dist/render-mermaid.js +68 -0
  101. package/dist/view.d.ts +29 -0
  102. package/dist/view.js +30 -0
  103. package/package.json +25 -25
@@ -0,0 +1,282 @@
1
+ import { error } from '@vltpkg/error-cause';
2
+ import { RegistryClient, oidc } from '@vltpkg/registry-client';
3
+ import { run } from '@vltpkg/run';
4
+ import { commandUsage } from "../config/usage.js";
5
+ import { packTarball } from "../pack-tarball.js";
6
+ import assert from 'node:assert';
7
+ import { asError } from '@vltpkg/types';
8
+ import { dirname, resolve } from 'node:path';
9
+ import prettyBytes from 'pretty-bytes';
10
+ import { actual } from '@vltpkg/graph';
11
+ import { Query } from '@vltpkg/query';
12
+ import { createHostContextsMap } from "../query-host-contexts.js";
13
+ import { minimatch } from 'minimatch';
14
+ export const usage = () => commandUsage({
15
+ command: 'publish',
16
+ usage: '',
17
+ description: `Create a tarball from a package and publish it to the configured registry.
18
+
19
+ This command will pack the package in the current directory or specified folder,
20
+ and then upload it to the configured registry.`,
21
+ options: {
22
+ tag: {
23
+ description: 'Publish the package with the given tag',
24
+ value: '<tag>',
25
+ },
26
+ access: {
27
+ description: 'Set access level (public or restricted)',
28
+ value: '<level>',
29
+ },
30
+ otp: {
31
+ description: `Provide an OTP to use when publishing a package.`,
32
+ value: '<otp>',
33
+ },
34
+ 'publish-directory': {
35
+ description: `Directory to use for pack and publish operations instead of the current directory.
36
+ The directory must exist and nothing will be copied to it.`,
37
+ value: '<path>',
38
+ },
39
+ 'dry-run': {
40
+ description: 'Show what would be published without actually publishing.',
41
+ },
42
+ scope: {
43
+ value: '<query>',
44
+ description: 'Filter packages to publish using a DSS query selector.',
45
+ },
46
+ workspace: {
47
+ value: '<path|glob>',
48
+ description: 'Limit publish targets to matching workspaces.',
49
+ },
50
+ 'workspace-group': {
51
+ value: '<name>',
52
+ description: 'Limit publish targets to workspace groups.',
53
+ },
54
+ recursive: {
55
+ description: 'Publish all workspaces in the monorepo.',
56
+ },
57
+ },
58
+ });
59
+ export const views = {
60
+ human: results => {
61
+ const item = (r) => {
62
+ const lines = [
63
+ `📦 Package: ${r.id}`,
64
+ `🏷️ Tag: ${r.tag}`,
65
+ `📡 Registry: ${r.registry}`,
66
+ `📁 ${r.files.length} Files`,
67
+ ...r.files.map(f => ` - ${f}`),
68
+ `📊 Package Size: ${prettyBytes(r.size)}`,
69
+ `📂 Unpacked Size: ${prettyBytes(r.unpackedSize)}`,
70
+ ];
71
+ if (r.shasum)
72
+ lines.push(`🔒 Shasum: ${r.shasum}`);
73
+ if (r.integrity)
74
+ lines.push(`🔐 Integrity: ${r.integrity}`);
75
+ return lines.join('\n');
76
+ };
77
+ return Array.isArray(results) ?
78
+ results.map(item).join('\n\n')
79
+ : item(results);
80
+ },
81
+ json: r => r,
82
+ };
83
+ export const command = async (conf) => {
84
+ const { options, projectRoot } = conf;
85
+ const queryString = conf.get('scope');
86
+ const paths = conf.get('workspace');
87
+ const groups = conf.get('workspace-group');
88
+ const recursive = conf.get('recursive');
89
+ const locations = [];
90
+ let single = null;
91
+ if (queryString) {
92
+ const mainManifest = options.packageJson.maybeRead(projectRoot);
93
+ let graph;
94
+ if (mainManifest) {
95
+ graph = actual.load({
96
+ ...options,
97
+ mainManifest,
98
+ monorepo: options.monorepo,
99
+ loadManifests: false,
100
+ });
101
+ }
102
+ const hostContexts = await createHostContextsMap(conf);
103
+ const query = new Query({
104
+ /* c8 ignore next */
105
+ nodes: graph ? new Set(graph.nodes.values()) : new Set(),
106
+ edges: graph?.edges ?? new Set(),
107
+ importers: graph?.importers ?? new Set(),
108
+ securityArchive: undefined,
109
+ hostContexts,
110
+ });
111
+ const { nodes } = await query.search(queryString, {
112
+ signal: new AbortController().signal,
113
+ });
114
+ for (const node of nodes) {
115
+ const { location } = node.toJSON();
116
+ assert(location, error(`node ${node.id} has no location`, {
117
+ found: node,
118
+ }));
119
+ locations.push(resolve(projectRoot, location));
120
+ }
121
+ }
122
+ else if (paths?.length || groups?.length || recursive) {
123
+ for (const workspace of options.monorepo ?? []) {
124
+ if (paths?.length) {
125
+ const matches = paths.some((p) => workspace.path === p ||
126
+ workspace.name === p ||
127
+ workspace.fullpath === p ||
128
+ resolve(projectRoot, p) === workspace.fullpath ||
129
+ minimatch(workspace.path, p));
130
+ if (!matches)
131
+ continue;
132
+ }
133
+ locations.push(workspace.fullpath);
134
+ }
135
+ }
136
+ else {
137
+ single = options.packageJson.find(process.cwd()) ?? projectRoot;
138
+ }
139
+ if (single) {
140
+ return commandSingle(single, conf);
141
+ }
142
+ assert(locations.length > 0, error('No workspaces or query results found'));
143
+ const results = [];
144
+ for (const location of locations) {
145
+ results.push(await commandSingle(location, conf));
146
+ }
147
+ return results;
148
+ };
149
+ const commandSingle = async (location, conf) => {
150
+ const manifestPath = conf.options.packageJson.find(location);
151
+ assert(manifestPath, 'No package.json found');
152
+ const manifestDir = dirname(manifestPath);
153
+ const manifest = conf.options.packageJson.read(manifestDir);
154
+ 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);
157
+ const runOptions = {
158
+ cwd: manifestDir,
159
+ projectRoot: conf.projectRoot,
160
+ packageJson: conf.options.packageJson,
161
+ manifest,
162
+ ignoreMissing: true,
163
+ ignorePrePost: true,
164
+ };
165
+ await run({
166
+ ...runOptions,
167
+ arg0: 'prepublishOnly',
168
+ });
169
+ await run({
170
+ ...runOptions,
171
+ arg0: 'prepublish',
172
+ });
173
+ await run({
174
+ ...runOptions,
175
+ arg0: 'prepack',
176
+ });
177
+ await run({
178
+ ...runOptions,
179
+ arg0: 'prepare',
180
+ });
181
+ // Re-read the manifest after lifecycle scripts, which may have modified package.json.
182
+ const updatedManifest = conf.options.packageJson.read(manifestDir, {
183
+ reload: true,
184
+ });
185
+ const { name, version, tarballName, tarballData, unpackedSize, files, integrity, shasum, resolvedManifest, } = await packTarball(updatedManifest, manifestDir, conf);
186
+ await run({
187
+ ...runOptions,
188
+ arg0: 'postpack',
189
+ });
190
+ await run({
191
+ ...runOptions,
192
+ arg0: 'publish',
193
+ });
194
+ const publishMetadata = {
195
+ _id: name,
196
+ name,
197
+ description: resolvedManifest.description || '',
198
+ 'dist-tags': {
199
+ [tag]: version,
200
+ },
201
+ versions: {
202
+ [version]: {
203
+ ...resolvedManifest,
204
+ _id: `${name}@${version}`,
205
+ _nodeVersion: process.versions.node,
206
+ dist: {
207
+ ...resolvedManifest.dist,
208
+ integrity,
209
+ shasum,
210
+ tarball: new URL(`${name}/-/${tarballName}`, registryUrl)
211
+ .href,
212
+ },
213
+ },
214
+ },
215
+ ...(access ? { access } : {}),
216
+ _attachments: {
217
+ [tarballName]: {
218
+ content_type: 'application/octet-stream',
219
+ data: tarballData.toString('base64'),
220
+ length: tarballData.length,
221
+ },
222
+ },
223
+ };
224
+ const rc = new RegistryClient(conf.options);
225
+ // Attempt OIDC token exchange for CI environments (GitHub Actions, GitLab, CircleCI).
226
+ // This is always optional — failures are silent and won't affect local publishing.
227
+ await oidc({
228
+ packageName: name,
229
+ registry: registryUrl.href,
230
+ });
231
+ const publishUrl = new URL(name.startsWith('@') ? name.replace('/', '%2F') : name, registryUrl);
232
+ if (!dry) {
233
+ let response;
234
+ try {
235
+ response = await rc.request(publishUrl, {
236
+ method: 'PUT',
237
+ headers: {
238
+ 'content-type': 'application/json',
239
+ // These control what type of OTP auth flow is used
240
+ 'npm-auth-type': 'web',
241
+ 'npm-command': 'publish',
242
+ },
243
+ body: JSON.stringify(publishMetadata),
244
+ otp,
245
+ });
246
+ }
247
+ catch (err) {
248
+ throw error('Failed to publish package', {
249
+ cause: asError(err),
250
+ });
251
+ }
252
+ if (response.statusCode !== 200 && response.statusCode !== 201) {
253
+ let extraMsg = '';
254
+ // special case 404 errors to provide a better hint to the user
255
+ if (response.statusCode === 404) {
256
+ extraMsg =
257
+ ".\n⚠️ Make sure you're logged in and have access to publish the package.";
258
+ }
259
+ throw error(`Failed to publish package${extraMsg}`, {
260
+ url: publishUrl,
261
+ response,
262
+ });
263
+ }
264
+ }
265
+ await run({
266
+ ...runOptions,
267
+ arg0: 'postpublish',
268
+ });
269
+ return {
270
+ id: `${name}@${version}`,
271
+ name,
272
+ version,
273
+ tag,
274
+ ...(access ? { access } : {}),
275
+ registry: registryUrl.origin,
276
+ integrity,
277
+ shasum,
278
+ size: tarballData.length,
279
+ unpackedSize,
280
+ files,
281
+ };
282
+ };
@@ -0,0 +1,18 @@
1
+ import { humanReadableOutput, jsonOutput, mermaidOutput } from '@vltpkg/graph';
2
+ import type { HumanReadableOutputGraph, JSONOutputGraph, MermaidOutputGraph } from '@vltpkg/graph';
3
+ import { MermaidImageView } from '../mermaid-image-view.ts';
4
+ import type { CommandFn, CommandUsage } from '../index.ts';
5
+ export declare const usage: CommandUsage;
6
+ type QueryResult = JSONOutputGraph & MermaidOutputGraph & HumanReadableOutputGraph & {
7
+ queryString: string;
8
+ };
9
+ export declare const views: {
10
+ readonly json: typeof jsonOutput;
11
+ readonly mermaid: typeof mermaidOutput;
12
+ readonly human: typeof humanReadableOutput;
13
+ readonly count: (result: QueryResult) => number;
14
+ readonly svg: typeof MermaidImageView;
15
+ readonly png: typeof MermaidImageView;
16
+ };
17
+ export declare const command: CommandFn<QueryResult>;
18
+ export {};
@@ -0,0 +1,216 @@
1
+ import { actual, asNode, humanReadableOutput, jsonOutput, mermaidOutput, GraphModifier, } from '@vltpkg/graph';
2
+ import { error } from '@vltpkg/error-cause';
3
+ import { Query } from '@vltpkg/query';
4
+ import { createDiffFilesProvider } from "../query-diff-files.js";
5
+ import { SecurityArchive } from '@vltpkg/security-archive';
6
+ import { commandUsage } from "../config/usage.js";
7
+ import { createHostContextsMap } from "../query-host-contexts.js";
8
+ import { MermaidImageView } from "../mermaid-image-view.js";
9
+ export const usage = () => commandUsage({
10
+ command: 'query',
11
+ usage: [
12
+ '',
13
+ '<query> --view=<human | json | mermaid | svg | png | count>',
14
+ '<query> --expect-results=<comparison string>',
15
+ '--target=<query> --view=<human | json | mermaid | svg | png | count>',
16
+ ],
17
+ description: `List installed dependencies matching the provided query.
18
+
19
+ The vlt Dependency Selector Syntax is a CSS-like query language that
20
+ allows you to filter installed dependencies using a variety of metadata
21
+ in the form of CSS-like attributes, pseudo selectors & combinators.
22
+
23
+ The --scope and --target options accepts DSS query selectors to filter
24
+ packages. Using --scope, you can specify which packages to treat as the
25
+ top-level items in the output graph. The --target option can be used as
26
+ an alternative to positional arguments, it allows you to filter what
27
+ dependencies to include in the output. Using both options allows you to
28
+ render subgraphs of the dependency graph.
29
+
30
+ Defaults to listing all dependencies of the project root and workspaces.`,
31
+ examples: {
32
+ [`'#foo'`]: {
33
+ description: 'Query dependencies declared as "foo"',
34
+ },
35
+ [`'*:workspace > *:peer'`]: {
36
+ description: 'Query all peer dependencies of workspaces',
37
+ },
38
+ [`':project > *:attr(scripts, [build])'`]: {
39
+ description: 'Query all direct project dependencies with a "build" script',
40
+ },
41
+ [`'[name^="@vltpkg"]'`]: {
42
+ description: 'Query packages with names starting with "@vltpkg"',
43
+ },
44
+ [`'*:license(copyleft) --expect-results=0'`]: {
45
+ description: 'Errors if a copyleft licensed package is found',
46
+ },
47
+ '--scope=":root > #dependency-name"': {
48
+ description: 'Defines a direct dependency as the output top-level scope',
49
+ },
50
+ [`'--target="*"'`]: {
51
+ description: 'Query all dependencies using the target option',
52
+ },
53
+ [`'--target=":workspace > *:peer"'`]: {
54
+ description: 'Query all peer dependencies of workspaces using target option',
55
+ },
56
+ },
57
+ options: {
58
+ 'expect-results': {
59
+ value: '[number | string]',
60
+ description: 'Sets an expected number of resulting items. Errors if the number of resulting items does not match the set value. Accepts a specific numeric value or a string value starting with either ">", "<", ">=" or "<=" followed by a numeric value to be compared.',
61
+ },
62
+ scope: {
63
+ value: '<query>',
64
+ description: 'Query selector to select top-level packages using the DSS query language syntax.',
65
+ },
66
+ target: {
67
+ value: '<query>',
68
+ description: 'Query selector to filter packages using DSS syntax.',
69
+ },
70
+ view: {
71
+ value: '[human | json | mermaid | svg | png | count]',
72
+ description: 'Output format. Defaults to human-readable or json if no tty. Use svg or png to render the dependency graph as an image and open it. Count outputs the number of dependency relationships in the result.',
73
+ },
74
+ },
75
+ });
76
+ const validateExpectedResult = (conf, edges) => {
77
+ const expectResults = conf.values['expect-results'];
78
+ if (expectResults?.startsWith('>=')) {
79
+ return edges.length >= parseInt(expectResults.slice(2).trim(), 10);
80
+ }
81
+ else if (expectResults?.startsWith('<=')) {
82
+ return edges.length <= parseInt(expectResults.slice(2).trim(), 10);
83
+ }
84
+ else if (expectResults?.startsWith('>')) {
85
+ return edges.length > parseInt(expectResults.slice(1).trim(), 10);
86
+ }
87
+ else if (expectResults?.startsWith('<')) {
88
+ return edges.length < parseInt(expectResults.slice(1).trim(), 10);
89
+ }
90
+ else if (expectResults) {
91
+ return edges.length === parseInt(expectResults.trim(), 10);
92
+ }
93
+ return true;
94
+ };
95
+ export const views = {
96
+ json: jsonOutput,
97
+ mermaid: mermaidOutput,
98
+ human: humanReadableOutput,
99
+ count: (result) => result.edges.length,
100
+ svg: MermaidImageView,
101
+ png: MermaidImageView,
102
+ };
103
+ export const command = async (conf) => {
104
+ const modifiers = GraphModifier.maybeLoad(conf.options);
105
+ const monorepo = conf.options.monorepo;
106
+ const mainManifest = conf.options.packageJson.maybeRead(conf.options.projectRoot);
107
+ let graph;
108
+ let securityArchive;
109
+ // optionally load the cwd graph if we found a package.json file
110
+ if (mainManifest) {
111
+ graph = actual.load({
112
+ ...conf.options,
113
+ mainManifest,
114
+ modifiers,
115
+ monorepo,
116
+ loadManifests: true,
117
+ });
118
+ securityArchive = await SecurityArchive.start({
119
+ nodes: [...graph.nodes.values()],
120
+ });
121
+ }
122
+ // retrieve default values and set up host contexts
123
+ const defaultProjectQueryString = '*';
124
+ const defaultLocalScopeQueryString = ':host(local) *';
125
+ const positionalQueryString = conf.positionals[0];
126
+ const targetQueryString = conf.get('target');
127
+ const scopeQueryString = conf.get('scope');
128
+ const queryString = targetQueryString || positionalQueryString;
129
+ const hostContexts = await createHostContextsMap(conf);
130
+ const diffFiles = createDiffFilesProvider(conf.options.projectRoot);
131
+ const importers = new Set();
132
+ const scopeIDs = [];
133
+ // Handle --scope option to add scope nodes as importers
134
+ let scopeNodes;
135
+ if (scopeQueryString) {
136
+ // Run scope query to get all matching nodes
137
+ /* c8 ignore start */
138
+ const edges = graph?.edges ?? new Set();
139
+ const nodes = graph?.nodes ?
140
+ new Set(graph.nodes.values())
141
+ : new Set();
142
+ const importers = graph?.importers ?? new Set();
143
+ /* c8 ignore stop */
144
+ const scopeQuery = new Query({
145
+ edges,
146
+ nodes,
147
+ importers,
148
+ securityArchive,
149
+ hostContexts,
150
+ diffFiles,
151
+ });
152
+ const { nodes: resultNodes } = await scopeQuery.search(scopeQueryString, {
153
+ signal: new AbortController().signal,
154
+ });
155
+ scopeNodes = resultNodes;
156
+ }
157
+ if (scopeQueryString && scopeNodes) {
158
+ // Add all scope nodes to importers Set (treat them as top-level items)
159
+ for (const queryNode of scopeNodes) {
160
+ importers.add(asNode(queryNode));
161
+ }
162
+ }
163
+ else if ('workspace' in conf.values) {
164
+ // if in a workspace environment, select only the specified
165
+ // workspaces as top-level items
166
+ if (monorepo && graph) {
167
+ for (const workspace of monorepo.filter(conf.values)) {
168
+ const w = graph.nodes.get(workspace.id);
169
+ if (w) {
170
+ importers.add(w);
171
+ scopeIDs.push(workspace.id);
172
+ }
173
+ }
174
+ }
175
+ }
176
+ // retrieve the selected nodes and edges
177
+ const edges_ = graph?.edges ?? new Set();
178
+ const nodes_ = graph?.nodes ?
179
+ new Set(graph.nodes.values())
180
+ : new Set();
181
+ const importers_ = importers.size === 0 && graph ?
182
+ new Set([graph.mainImporter])
183
+ : importers;
184
+ const q = new Query({
185
+ edges: edges_,
186
+ nodes: nodes_,
187
+ importers: importers_,
188
+ securityArchive,
189
+ hostContexts,
190
+ diffFiles,
191
+ });
192
+ const query = queryString ||
193
+ (graph ? defaultProjectQueryString : defaultLocalScopeQueryString);
194
+ const { edges, nodes, importers: queryResultImporters, } = await q.search(query, {
195
+ signal: new AbortController().signal,
196
+ scopeIDs: scopeIDs.length > 0 ? scopeIDs : undefined,
197
+ });
198
+ if (!validateExpectedResult(conf, edges)) {
199
+ throw error('Unexpected number of items', {
200
+ found: edges.length,
201
+ wanted: conf.values['expect-results'],
202
+ });
203
+ }
204
+ return {
205
+ importers: importers.size === 0 ?
206
+ new Set(queryResultImporters)
207
+ : importers,
208
+ edges,
209
+ nodes,
210
+ highlightSelection: !!(targetQueryString || positionalQueryString),
211
+ queryString: queryString ||
212
+ (graph ?
213
+ defaultProjectQueryString
214
+ : defaultLocalScopeQueryString),
215
+ };
216
+ };
@@ -0,0 +1,17 @@
1
+ import type { CommandFn, CommandUsage } from '../index.ts';
2
+ export declare const usage: CommandUsage;
3
+ type CommandResultSingle = {
4
+ url: string;
5
+ name: string;
6
+ };
7
+ type CommandResultMultiple = {
8
+ url: string;
9
+ name: string;
10
+ }[];
11
+ export type CommandResult = CommandResultSingle | CommandResultMultiple;
12
+ export declare const views: {
13
+ readonly human: (r: CommandResult) => string;
14
+ readonly json: (r: CommandResult) => CommandResult;
15
+ };
16
+ export declare const command: CommandFn<CommandResult>;
17
+ export {};
@@ -0,0 +1,157 @@
1
+ import { error } from '@vltpkg/error-cause';
2
+ import { PackageInfoClient } from '@vltpkg/package-info';
3
+ import { Spec } from '@vltpkg/spec';
4
+ import { urlOpen } from '@vltpkg/url-open';
5
+ import { actual } from '@vltpkg/graph';
6
+ import { Query } from '@vltpkg/query';
7
+ import { SecurityArchive } from '@vltpkg/security-archive';
8
+ import { createHostContextsMap } from "../query-host-contexts.js";
9
+ import { commandUsage } from "../config/usage.js";
10
+ import hostedGitInfo from 'hosted-git-info';
11
+ const { fromUrl: hostedGitInfoFromUrl } = hostedGitInfo;
12
+ export const usage = () => commandUsage({
13
+ command: 'repo',
14
+ usage: ['[<spec>]', '[--target=<query>]'],
15
+ description: `Open repository page for a package in a web browser.
16
+ Reads repository information from package.json or fetches
17
+ manifest data for the specified package.`,
18
+ options: {
19
+ target: {
20
+ value: '<query>',
21
+ description: 'Query selector to filter packages using DSS syntax.',
22
+ },
23
+ },
24
+ examples: {
25
+ '': {
26
+ description: 'Open repo for the current package (reads local package.json)',
27
+ },
28
+ 'abbrev@2.0.0': {
29
+ description: 'Open repo for a specific package version',
30
+ },
31
+ '--target=":root > *"': {
32
+ description: 'List repository URLs for all direct dependencies',
33
+ },
34
+ },
35
+ });
36
+ export const views = {
37
+ human: r => {
38
+ if (Array.isArray(r)) {
39
+ let msg = 'Multiple package repositories found:\n';
40
+ msg += r.map(item => `• ${item.name}: ${item.url}`).join('\n');
41
+ return msg;
42
+ }
43
+ return '';
44
+ },
45
+ json: r => r,
46
+ };
47
+ const getUrlFromManifest = (manifest) => {
48
+ const { name, repository, homepage } = manifest;
49
+ if (!name) {
50
+ throw error('No package name found');
51
+ }
52
+ let url;
53
+ // Check repository field first
54
+ if (repository) {
55
+ const repoUrl = typeof repository === 'string' ? repository
56
+ : typeof repository === 'object' && 'url' in repository ?
57
+ repository.url
58
+ : /* c8 ignore next */ undefined;
59
+ if (repoUrl) {
60
+ const info = hostedGitInfoFromUrl(repoUrl.replace(/^git\+/, ''));
61
+ if (info?.browse && typeof info.browse === 'function') {
62
+ url = info.browse();
63
+ }
64
+ else {
65
+ // Use the raw URL if hosted-git-info can't parse it
66
+ url = repoUrl.replace(/^git\+/, '');
67
+ }
68
+ }
69
+ }
70
+ // Fall back to homepage
71
+ if (!url && homepage) {
72
+ url = homepage;
73
+ }
74
+ // Fallback to vlt.io package page
75
+ if (!url) {
76
+ url = `https://vlt.io/explore/npm/${name}/overview`;
77
+ }
78
+ return url;
79
+ };
80
+ export const command = async (conf) => {
81
+ const { projectRoot, packageJson } = conf.options;
82
+ const targetOption = conf.get('target');
83
+ // Handle --target query
84
+ if (targetOption) {
85
+ const mainManifest = packageJson.maybeRead(projectRoot);
86
+ if (!mainManifest) {
87
+ throw error('No package.json found in project root', {
88
+ path: projectRoot,
89
+ });
90
+ }
91
+ const graph = actual.load({
92
+ ...conf.options,
93
+ mainManifest,
94
+ monorepo: conf.options.monorepo,
95
+ loadManifests: true,
96
+ });
97
+ const securityArchive = Query.hasSecuritySelectors(targetOption) ?
98
+ await SecurityArchive.start({
99
+ nodes: [...graph.nodes.values()],
100
+ })
101
+ : undefined;
102
+ const hostContexts = await createHostContextsMap(conf);
103
+ const query = new Query({
104
+ nodes: new Set(graph.nodes.values()),
105
+ edges: graph.edges,
106
+ importers: graph.importers,
107
+ securityArchive,
108
+ hostContexts,
109
+ });
110
+ const { nodes } = await query.search(targetOption, {
111
+ signal: new AbortController().signal,
112
+ });
113
+ const results = [];
114
+ for (const node of nodes) {
115
+ if (!node.manifest)
116
+ continue;
117
+ const url = getUrlFromManifest(node.manifest);
118
+ results.push({
119
+ url,
120
+ name: node.name /* c8 ignore next */ ?? '(unknown)',
121
+ });
122
+ }
123
+ if (results.length === 0) {
124
+ throw error('No packages found matching target query', {
125
+ found: targetOption,
126
+ });
127
+ }
128
+ // If single result, open it
129
+ if (results.length === 1) {
130
+ const result = results[0];
131
+ /* c8 ignore next 3 */
132
+ if (!result) {
133
+ throw error('Unexpected empty result');
134
+ }
135
+ await urlOpen(result.url);
136
+ return result;
137
+ }
138
+ // Multiple results, return the list
139
+ return results;
140
+ }
141
+ // read the package spec from a positional argument or local package.json
142
+ const specArg = conf.positionals[0];
143
+ const manifest = conf.positionals.length === 0 ? packageJson.read(projectRoot)
144
+ : specArg ?
145
+ await new PackageInfoClient(conf.options).manifest(Spec.parseArgs(specArg, conf.options))
146
+ : /* c8 ignore next */ packageJson.read(projectRoot);
147
+ const url = getUrlFromManifest(manifest);
148
+ const { name } = manifest;
149
+ /* c8 ignore start - getUrlFromManifest already validates name */
150
+ if (!name) {
151
+ throw error('No package name found');
152
+ }
153
+ /* c8 ignore stop */
154
+ // Open the URL
155
+ await urlOpen(url);
156
+ return { url, name };
157
+ };