@sitecore-content-sdk/core 2.1.3 → 2.1.4-canary.20260824045921
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.
|
@@ -3,6 +3,19 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.getMetadata = getMetadata;
|
|
4
4
|
const child_process_1 = require("child_process");
|
|
5
5
|
const trackedScopes = ['@sitecore', '@sitecore-feaas', '@sitecore-content-sdk'];
|
|
6
|
+
// pnpm and yarn match name patterns as globs, in which '*' does not cross the scope separator,
|
|
7
|
+
// so both segments are wildcarded to cover every '@sitecore*' scope.
|
|
8
|
+
const TRACKED_SCOPE_GLOB = '"@sitecore*/*"';
|
|
9
|
+
const PNPM_DEPENDENCY_GROUPS = [
|
|
10
|
+
'dependencies',
|
|
11
|
+
'devDependencies',
|
|
12
|
+
'optionalDependencies',
|
|
13
|
+
'unsavedDependencies',
|
|
14
|
+
];
|
|
15
|
+
// Matches the 'name@version' entries of the tree printed by `bun pm ls`, e.g '├── @scope/pkg@1.0.0'
|
|
16
|
+
const BUN_PACKAGE_ENTRY = /^[\s│├└─]*((?:@[^\s@/]+\/)?[^\s@/]+)@([^\s]+)$/;
|
|
17
|
+
// Dependency trees of an app can be sizeable, the 1MB default of execSync is not enough.
|
|
18
|
+
const MAX_OUTPUT_BUFFER = 10 * 1024 * 1024;
|
|
6
19
|
/**
|
|
7
20
|
* Get application metadata
|
|
8
21
|
* @param {boolean} allowWorkspaces - Whether to allow workspaces in the metadata generation.
|
|
@@ -10,12 +23,17 @@ const trackedScopes = ['@sitecore', '@sitecore-feaas', '@sitecore-content-sdk'];
|
|
|
10
23
|
*/
|
|
11
24
|
function getMetadata(allowWorkspaces = false) {
|
|
12
25
|
const metadata = { packages: {} };
|
|
26
|
+
const packageManagement = getPackageManagement(allowWorkspaces);
|
|
27
|
+
const { query, parse } = packageManagement[detectPackageManager(packageManagement)];
|
|
13
28
|
let queryResult = [];
|
|
14
29
|
try {
|
|
15
|
-
queryResult =
|
|
30
|
+
queryResult = parse((0, child_process_1.execSync)(query, {
|
|
31
|
+
maxBuffer: MAX_OUTPUT_BUFFER,
|
|
32
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
33
|
+
}).toString());
|
|
16
34
|
}
|
|
17
35
|
catch (error) {
|
|
18
|
-
console.error(
|
|
36
|
+
console.error(`Failed to retrieve sitecore packages using '${query}'`, error);
|
|
19
37
|
return metadata;
|
|
20
38
|
}
|
|
21
39
|
metadata.packages = getPackagesFromQueryResult(queryResult);
|
|
@@ -35,3 +53,226 @@ function getPackagesFromQueryResult(scPackages) {
|
|
|
35
53
|
});
|
|
36
54
|
return packages;
|
|
37
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Build the listing command and parser for each supported package manager.
|
|
58
|
+
* Yarn classic is a dedicated entry because its command differs from yarn berry.
|
|
59
|
+
* @param {boolean} allowWorkspaces whether packages of all workspaces should be included
|
|
60
|
+
* @returns {Record<string, PackagesQuery>} query and parser per package manager
|
|
61
|
+
* @internal
|
|
62
|
+
*/
|
|
63
|
+
function getPackageManagement(allowWorkspaces) {
|
|
64
|
+
return {
|
|
65
|
+
pnpm: {
|
|
66
|
+
query: `pnpm list --depth Infinity --json ${allowWorkspaces ? '--recursive ' : ''}${TRACKED_SCOPE_GLOB}`,
|
|
67
|
+
parse: parsePnpmList,
|
|
68
|
+
},
|
|
69
|
+
npm: {
|
|
70
|
+
query: `npm query [name*=@sitecore] --workspaces ${allowWorkspaces}`,
|
|
71
|
+
parse: parseNpmQuery,
|
|
72
|
+
},
|
|
73
|
+
yarn: {
|
|
74
|
+
query: `yarn info --json --name-only --recursive ${allowWorkspaces ? '--all ' : ''}${TRACKED_SCOPE_GLOB}`,
|
|
75
|
+
parse: parseYarnInfo,
|
|
76
|
+
},
|
|
77
|
+
yarnClassic: {
|
|
78
|
+
query: `yarn list --json --depth 0 --pattern ${TRACKED_SCOPE_GLOB}`,
|
|
79
|
+
parse: parseYarnClassicList,
|
|
80
|
+
},
|
|
81
|
+
bun: {
|
|
82
|
+
// `bun pm ls` has no name filter and only recently gained JSON output, so the tree it
|
|
83
|
+
// prints is parsed instead, and filtered by scope afterwards.
|
|
84
|
+
query: 'bun pm ls --all',
|
|
85
|
+
parse: parseBunPackageList,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Detect the package manager that runs the current process. Package managers advertise themselves
|
|
91
|
+
* through the environment they set up for the commands they run, so nothing has to be read from
|
|
92
|
+
* disk - which matters because the app being built is not necessarily the project that declares
|
|
93
|
+
* the package manager (workspaces).
|
|
94
|
+
* @param {Record<string, PackagesQuery>} packageManagement supported package managers
|
|
95
|
+
* @returns {string} the detected package manager, npm when it could not be determined
|
|
96
|
+
* @internal
|
|
97
|
+
*/
|
|
98
|
+
function detectPackageManager(packageManagement) {
|
|
99
|
+
const managerNames = Object.keys(packageManagement).sort((left, right) => right.length - left.length);
|
|
100
|
+
const { npm_config_user_agent: userAgent, npm_execpath: execPath } = process.env;
|
|
101
|
+
// e.g. 'pnpm/10.33.0 npm/? node/v22.11.0 win32 x64'
|
|
102
|
+
if (userAgent) {
|
|
103
|
+
const [descriptor] = userAgent.trim().split(/\s+/);
|
|
104
|
+
const separatorIndex = descriptor.lastIndexOf('/');
|
|
105
|
+
const name = (separatorIndex === -1 ? descriptor : descriptor.slice(0, separatorIndex)).toLowerCase();
|
|
106
|
+
const knownName = managerNames.find((candidate) => candidate === name);
|
|
107
|
+
if (knownName) {
|
|
108
|
+
const majorVersion = parseInt(descriptor.slice(separatorIndex + 1), 10);
|
|
109
|
+
return knownName === 'yarn' && majorVersion === 1 ? 'yarnClassic' : knownName;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// e.g. 'C:\\Users\\dev\\AppData\\Roaming\\npm\\node_modules\\pnpm\\bin\\pnpm.cjs', which does
|
|
113
|
+
// not carry a version
|
|
114
|
+
if (execPath) {
|
|
115
|
+
const executable = execPath.toLowerCase().split(/[\\/]/).pop();
|
|
116
|
+
const knownName = managerNames.find((candidate) => executable.includes(candidate));
|
|
117
|
+
if (knownName) {
|
|
118
|
+
return knownName;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return 'npm';
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Parse the output of `npm query`, which is a JSON array of package manifests
|
|
125
|
+
* @param {string} output command output
|
|
126
|
+
* @returns {Package[]} installed packages
|
|
127
|
+
* @internal
|
|
128
|
+
*/
|
|
129
|
+
function parseNpmQuery(output) {
|
|
130
|
+
const parsed = JSON.parse(output);
|
|
131
|
+
return Array.isArray(parsed)
|
|
132
|
+
? parsed.filter((scPackage) => typeof (scPackage === null || scPackage === void 0 ? void 0 : scPackage.name) === 'string' && typeof (scPackage === null || scPackage === void 0 ? void 0 : scPackage.version) === 'string')
|
|
133
|
+
: [];
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Parse the output of `pnpm list --json`, which is a dependency tree per project
|
|
137
|
+
* @param {string} output command output
|
|
138
|
+
* @returns {Package[]} installed packages
|
|
139
|
+
* @internal
|
|
140
|
+
*/
|
|
141
|
+
function parsePnpmList(output) {
|
|
142
|
+
if (!output.trim()) {
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
const parsed = JSON.parse(output);
|
|
146
|
+
const projects = Array.isArray(parsed) ? parsed : [parsed];
|
|
147
|
+
const packages = [];
|
|
148
|
+
const visited = new Set();
|
|
149
|
+
const collect = (dependencies) => {
|
|
150
|
+
if (!dependencies) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
Object.entries(dependencies).forEach(([name, node]) => {
|
|
154
|
+
var _a;
|
|
155
|
+
if (!node) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const key = `${name}@${(_a = node.version) !== null && _a !== void 0 ? _a : ''}`;
|
|
159
|
+
// the same package can be reached through many paths, walk it once
|
|
160
|
+
if (visited.has(key)) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
visited.add(key);
|
|
164
|
+
if (node.version) {
|
|
165
|
+
packages.push({ name, version: node.version });
|
|
166
|
+
}
|
|
167
|
+
collect(node.dependencies);
|
|
168
|
+
});
|
|
169
|
+
};
|
|
170
|
+
projects.forEach((project) => PNPM_DEPENDENCY_GROUPS.forEach((group) => collect(project === null || project === void 0 ? void 0 : project[group])));
|
|
171
|
+
return packages;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Parse the output of `yarn info --json`, which is an NDJSON stream of package locators
|
|
175
|
+
* @param {string} output command output
|
|
176
|
+
* @returns {Package[]} installed packages
|
|
177
|
+
* @internal
|
|
178
|
+
*/
|
|
179
|
+
function parseYarnInfo(output) {
|
|
180
|
+
return parseJsonLines(output).reduce((packages, entry) => {
|
|
181
|
+
const locator = typeof entry === 'string' ? entry : getRecordString(entry, 'value');
|
|
182
|
+
const scPackage = locator ? parsePackageLocator(locator) : undefined;
|
|
183
|
+
return scPackage ? [...packages, scPackage] : packages;
|
|
184
|
+
}, []);
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Parse the output of `yarn list --json` (yarn classic), which is an NDJSON stream in which the
|
|
188
|
+
* 'tree' entry holds the hoisted dependency trees
|
|
189
|
+
* @param {string} output command output
|
|
190
|
+
* @returns {Package[]} installed packages
|
|
191
|
+
* @internal
|
|
192
|
+
*/
|
|
193
|
+
function parseYarnClassicList(output) {
|
|
194
|
+
return parseJsonLines(output).reduce((packages, entry) => {
|
|
195
|
+
const data = isRecord(entry) && entry.type === 'tree' ? entry.data : undefined;
|
|
196
|
+
const trees = isRecord(data) && Array.isArray(data.trees) ? data.trees : undefined;
|
|
197
|
+
if (!trees) {
|
|
198
|
+
return packages;
|
|
199
|
+
}
|
|
200
|
+
return trees.reduce((treePackages, tree) => {
|
|
201
|
+
const name = getRecordString(tree, 'name');
|
|
202
|
+
const scPackage = name ? parsePackageLocator(name) : undefined;
|
|
203
|
+
return scPackage ? [...treePackages, scPackage] : treePackages;
|
|
204
|
+
}, packages);
|
|
205
|
+
}, []);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Parse the dependency tree printed by `bun pm ls`
|
|
209
|
+
* @param {string} output command output
|
|
210
|
+
* @returns {Package[]} installed packages
|
|
211
|
+
* @internal
|
|
212
|
+
*/
|
|
213
|
+
function parseBunPackageList(output) {
|
|
214
|
+
return output.split(/\r?\n/).reduce((packages, line) => {
|
|
215
|
+
const match = BUN_PACKAGE_ENTRY.exec(line.trim());
|
|
216
|
+
return match ? [...packages, { name: match[1], version: match[2] }] : packages;
|
|
217
|
+
}, []);
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Parse an NDJSON stream, skipping lines that are not valid JSON - package managers mix
|
|
221
|
+
* diagnostics into their output
|
|
222
|
+
* @param {string} output command output
|
|
223
|
+
* @returns {unknown[]} parsed entries
|
|
224
|
+
* @internal
|
|
225
|
+
*/
|
|
226
|
+
function parseJsonLines(output) {
|
|
227
|
+
return output.split(/\r?\n/).reduce((entries, line) => {
|
|
228
|
+
const trimmed = line.trim();
|
|
229
|
+
if (!trimmed) {
|
|
230
|
+
return entries;
|
|
231
|
+
}
|
|
232
|
+
try {
|
|
233
|
+
return [...entries, JSON.parse(trimmed)];
|
|
234
|
+
}
|
|
235
|
+
catch (_a) {
|
|
236
|
+
return entries;
|
|
237
|
+
}
|
|
238
|
+
}, []);
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Narrow an unknown value to a plain object record
|
|
242
|
+
* @param {unknown} value value to check
|
|
243
|
+
* @returns {value is Record<string, unknown>} whether the value is a non-null object
|
|
244
|
+
* @internal
|
|
245
|
+
*/
|
|
246
|
+
function isRecord(value) {
|
|
247
|
+
return typeof value === 'object' && value !== null;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Read a string property from an unknown object
|
|
251
|
+
* @param {unknown} value object that may contain the property
|
|
252
|
+
* @param {string} key property name
|
|
253
|
+
* @returns {string | undefined} the string value, or undefined when it is missing or not a string
|
|
254
|
+
* @internal
|
|
255
|
+
*/
|
|
256
|
+
function getRecordString(value, key) {
|
|
257
|
+
if (!isRecord(value)) {
|
|
258
|
+
return undefined;
|
|
259
|
+
}
|
|
260
|
+
const property = value[key];
|
|
261
|
+
return typeof property === 'string' ? property : undefined;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Split a package locator into name and version, e.g. '@scope/pkg@npm:1.0.0'. Yarn prefixes the
|
|
265
|
+
* reference with the protocol it was resolved with, which is not part of the version.
|
|
266
|
+
* @param {string} locator package locator
|
|
267
|
+
* @returns {Package | undefined} the package, or undefined when the locator could not be parsed
|
|
268
|
+
* @internal
|
|
269
|
+
*/
|
|
270
|
+
function parsePackageLocator(locator) {
|
|
271
|
+
const separatorIndex = locator.lastIndexOf('@');
|
|
272
|
+
if (separatorIndex <= 0) {
|
|
273
|
+
return undefined;
|
|
274
|
+
}
|
|
275
|
+
const reference = locator.slice(separatorIndex + 1);
|
|
276
|
+
const version = reference.slice(reference.lastIndexOf(':') + 1);
|
|
277
|
+
return version ? { name: locator.slice(0, separatorIndex), version } : undefined;
|
|
278
|
+
}
|
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
import { execSync } from 'child_process';
|
|
2
2
|
const trackedScopes = ['@sitecore', '@sitecore-feaas', '@sitecore-content-sdk'];
|
|
3
|
+
// pnpm and yarn match name patterns as globs, in which '*' does not cross the scope separator,
|
|
4
|
+
// so both segments are wildcarded to cover every '@sitecore*' scope.
|
|
5
|
+
const TRACKED_SCOPE_GLOB = '"@sitecore*/*"';
|
|
6
|
+
const PNPM_DEPENDENCY_GROUPS = [
|
|
7
|
+
'dependencies',
|
|
8
|
+
'devDependencies',
|
|
9
|
+
'optionalDependencies',
|
|
10
|
+
'unsavedDependencies',
|
|
11
|
+
];
|
|
12
|
+
// Matches the 'name@version' entries of the tree printed by `bun pm ls`, e.g '├── @scope/pkg@1.0.0'
|
|
13
|
+
const BUN_PACKAGE_ENTRY = /^[\s│├└─]*((?:@[^\s@/]+\/)?[^\s@/]+)@([^\s]+)$/;
|
|
14
|
+
// Dependency trees of an app can be sizeable, the 1MB default of execSync is not enough.
|
|
15
|
+
const MAX_OUTPUT_BUFFER = 10 * 1024 * 1024;
|
|
3
16
|
/**
|
|
4
17
|
* Get application metadata
|
|
5
18
|
* @param {boolean} allowWorkspaces - Whether to allow workspaces in the metadata generation.
|
|
@@ -7,12 +20,17 @@ const trackedScopes = ['@sitecore', '@sitecore-feaas', '@sitecore-content-sdk'];
|
|
|
7
20
|
*/
|
|
8
21
|
export function getMetadata(allowWorkspaces = false) {
|
|
9
22
|
const metadata = { packages: {} };
|
|
23
|
+
const packageManagement = getPackageManagement(allowWorkspaces);
|
|
24
|
+
const { query, parse } = packageManagement[detectPackageManager(packageManagement)];
|
|
10
25
|
let queryResult = [];
|
|
11
26
|
try {
|
|
12
|
-
queryResult =
|
|
27
|
+
queryResult = parse(execSync(query, {
|
|
28
|
+
maxBuffer: MAX_OUTPUT_BUFFER,
|
|
29
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
30
|
+
}).toString());
|
|
13
31
|
}
|
|
14
32
|
catch (error) {
|
|
15
|
-
console.error(
|
|
33
|
+
console.error(`Failed to retrieve sitecore packages using '${query}'`, error);
|
|
16
34
|
return metadata;
|
|
17
35
|
}
|
|
18
36
|
metadata.packages = getPackagesFromQueryResult(queryResult);
|
|
@@ -32,3 +50,226 @@ function getPackagesFromQueryResult(scPackages) {
|
|
|
32
50
|
});
|
|
33
51
|
return packages;
|
|
34
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Build the listing command and parser for each supported package manager.
|
|
55
|
+
* Yarn classic is a dedicated entry because its command differs from yarn berry.
|
|
56
|
+
* @param {boolean} allowWorkspaces whether packages of all workspaces should be included
|
|
57
|
+
* @returns {Record<string, PackagesQuery>} query and parser per package manager
|
|
58
|
+
* @internal
|
|
59
|
+
*/
|
|
60
|
+
function getPackageManagement(allowWorkspaces) {
|
|
61
|
+
return {
|
|
62
|
+
pnpm: {
|
|
63
|
+
query: `pnpm list --depth Infinity --json ${allowWorkspaces ? '--recursive ' : ''}${TRACKED_SCOPE_GLOB}`,
|
|
64
|
+
parse: parsePnpmList,
|
|
65
|
+
},
|
|
66
|
+
npm: {
|
|
67
|
+
query: `npm query [name*=@sitecore] --workspaces ${allowWorkspaces}`,
|
|
68
|
+
parse: parseNpmQuery,
|
|
69
|
+
},
|
|
70
|
+
yarn: {
|
|
71
|
+
query: `yarn info --json --name-only --recursive ${allowWorkspaces ? '--all ' : ''}${TRACKED_SCOPE_GLOB}`,
|
|
72
|
+
parse: parseYarnInfo,
|
|
73
|
+
},
|
|
74
|
+
yarnClassic: {
|
|
75
|
+
query: `yarn list --json --depth 0 --pattern ${TRACKED_SCOPE_GLOB}`,
|
|
76
|
+
parse: parseYarnClassicList,
|
|
77
|
+
},
|
|
78
|
+
bun: {
|
|
79
|
+
// `bun pm ls` has no name filter and only recently gained JSON output, so the tree it
|
|
80
|
+
// prints is parsed instead, and filtered by scope afterwards.
|
|
81
|
+
query: 'bun pm ls --all',
|
|
82
|
+
parse: parseBunPackageList,
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Detect the package manager that runs the current process. Package managers advertise themselves
|
|
88
|
+
* through the environment they set up for the commands they run, so nothing has to be read from
|
|
89
|
+
* disk - which matters because the app being built is not necessarily the project that declares
|
|
90
|
+
* the package manager (workspaces).
|
|
91
|
+
* @param {Record<string, PackagesQuery>} packageManagement supported package managers
|
|
92
|
+
* @returns {string} the detected package manager, npm when it could not be determined
|
|
93
|
+
* @internal
|
|
94
|
+
*/
|
|
95
|
+
function detectPackageManager(packageManagement) {
|
|
96
|
+
const managerNames = Object.keys(packageManagement).sort((left, right) => right.length - left.length);
|
|
97
|
+
const { npm_config_user_agent: userAgent, npm_execpath: execPath } = process.env;
|
|
98
|
+
// e.g. 'pnpm/10.33.0 npm/? node/v22.11.0 win32 x64'
|
|
99
|
+
if (userAgent) {
|
|
100
|
+
const [descriptor] = userAgent.trim().split(/\s+/);
|
|
101
|
+
const separatorIndex = descriptor.lastIndexOf('/');
|
|
102
|
+
const name = (separatorIndex === -1 ? descriptor : descriptor.slice(0, separatorIndex)).toLowerCase();
|
|
103
|
+
const knownName = managerNames.find((candidate) => candidate === name);
|
|
104
|
+
if (knownName) {
|
|
105
|
+
const majorVersion = parseInt(descriptor.slice(separatorIndex + 1), 10);
|
|
106
|
+
return knownName === 'yarn' && majorVersion === 1 ? 'yarnClassic' : knownName;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// e.g. 'C:\\Users\\dev\\AppData\\Roaming\\npm\\node_modules\\pnpm\\bin\\pnpm.cjs', which does
|
|
110
|
+
// not carry a version
|
|
111
|
+
if (execPath) {
|
|
112
|
+
const executable = execPath.toLowerCase().split(/[\\/]/).pop();
|
|
113
|
+
const knownName = managerNames.find((candidate) => executable.includes(candidate));
|
|
114
|
+
if (knownName) {
|
|
115
|
+
return knownName;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return 'npm';
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Parse the output of `npm query`, which is a JSON array of package manifests
|
|
122
|
+
* @param {string} output command output
|
|
123
|
+
* @returns {Package[]} installed packages
|
|
124
|
+
* @internal
|
|
125
|
+
*/
|
|
126
|
+
function parseNpmQuery(output) {
|
|
127
|
+
const parsed = JSON.parse(output);
|
|
128
|
+
return Array.isArray(parsed)
|
|
129
|
+
? parsed.filter((scPackage) => typeof (scPackage === null || scPackage === void 0 ? void 0 : scPackage.name) === 'string' && typeof (scPackage === null || scPackage === void 0 ? void 0 : scPackage.version) === 'string')
|
|
130
|
+
: [];
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Parse the output of `pnpm list --json`, which is a dependency tree per project
|
|
134
|
+
* @param {string} output command output
|
|
135
|
+
* @returns {Package[]} installed packages
|
|
136
|
+
* @internal
|
|
137
|
+
*/
|
|
138
|
+
function parsePnpmList(output) {
|
|
139
|
+
if (!output.trim()) {
|
|
140
|
+
return [];
|
|
141
|
+
}
|
|
142
|
+
const parsed = JSON.parse(output);
|
|
143
|
+
const projects = Array.isArray(parsed) ? parsed : [parsed];
|
|
144
|
+
const packages = [];
|
|
145
|
+
const visited = new Set();
|
|
146
|
+
const collect = (dependencies) => {
|
|
147
|
+
if (!dependencies) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
Object.entries(dependencies).forEach(([name, node]) => {
|
|
151
|
+
var _a;
|
|
152
|
+
if (!node) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const key = `${name}@${(_a = node.version) !== null && _a !== void 0 ? _a : ''}`;
|
|
156
|
+
// the same package can be reached through many paths, walk it once
|
|
157
|
+
if (visited.has(key)) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
visited.add(key);
|
|
161
|
+
if (node.version) {
|
|
162
|
+
packages.push({ name, version: node.version });
|
|
163
|
+
}
|
|
164
|
+
collect(node.dependencies);
|
|
165
|
+
});
|
|
166
|
+
};
|
|
167
|
+
projects.forEach((project) => PNPM_DEPENDENCY_GROUPS.forEach((group) => collect(project === null || project === void 0 ? void 0 : project[group])));
|
|
168
|
+
return packages;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Parse the output of `yarn info --json`, which is an NDJSON stream of package locators
|
|
172
|
+
* @param {string} output command output
|
|
173
|
+
* @returns {Package[]} installed packages
|
|
174
|
+
* @internal
|
|
175
|
+
*/
|
|
176
|
+
function parseYarnInfo(output) {
|
|
177
|
+
return parseJsonLines(output).reduce((packages, entry) => {
|
|
178
|
+
const locator = typeof entry === 'string' ? entry : getRecordString(entry, 'value');
|
|
179
|
+
const scPackage = locator ? parsePackageLocator(locator) : undefined;
|
|
180
|
+
return scPackage ? [...packages, scPackage] : packages;
|
|
181
|
+
}, []);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Parse the output of `yarn list --json` (yarn classic), which is an NDJSON stream in which the
|
|
185
|
+
* 'tree' entry holds the hoisted dependency trees
|
|
186
|
+
* @param {string} output command output
|
|
187
|
+
* @returns {Package[]} installed packages
|
|
188
|
+
* @internal
|
|
189
|
+
*/
|
|
190
|
+
function parseYarnClassicList(output) {
|
|
191
|
+
return parseJsonLines(output).reduce((packages, entry) => {
|
|
192
|
+
const data = isRecord(entry) && entry.type === 'tree' ? entry.data : undefined;
|
|
193
|
+
const trees = isRecord(data) && Array.isArray(data.trees) ? data.trees : undefined;
|
|
194
|
+
if (!trees) {
|
|
195
|
+
return packages;
|
|
196
|
+
}
|
|
197
|
+
return trees.reduce((treePackages, tree) => {
|
|
198
|
+
const name = getRecordString(tree, 'name');
|
|
199
|
+
const scPackage = name ? parsePackageLocator(name) : undefined;
|
|
200
|
+
return scPackage ? [...treePackages, scPackage] : treePackages;
|
|
201
|
+
}, packages);
|
|
202
|
+
}, []);
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Parse the dependency tree printed by `bun pm ls`
|
|
206
|
+
* @param {string} output command output
|
|
207
|
+
* @returns {Package[]} installed packages
|
|
208
|
+
* @internal
|
|
209
|
+
*/
|
|
210
|
+
function parseBunPackageList(output) {
|
|
211
|
+
return output.split(/\r?\n/).reduce((packages, line) => {
|
|
212
|
+
const match = BUN_PACKAGE_ENTRY.exec(line.trim());
|
|
213
|
+
return match ? [...packages, { name: match[1], version: match[2] }] : packages;
|
|
214
|
+
}, []);
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Parse an NDJSON stream, skipping lines that are not valid JSON - package managers mix
|
|
218
|
+
* diagnostics into their output
|
|
219
|
+
* @param {string} output command output
|
|
220
|
+
* @returns {unknown[]} parsed entries
|
|
221
|
+
* @internal
|
|
222
|
+
*/
|
|
223
|
+
function parseJsonLines(output) {
|
|
224
|
+
return output.split(/\r?\n/).reduce((entries, line) => {
|
|
225
|
+
const trimmed = line.trim();
|
|
226
|
+
if (!trimmed) {
|
|
227
|
+
return entries;
|
|
228
|
+
}
|
|
229
|
+
try {
|
|
230
|
+
return [...entries, JSON.parse(trimmed)];
|
|
231
|
+
}
|
|
232
|
+
catch (_a) {
|
|
233
|
+
return entries;
|
|
234
|
+
}
|
|
235
|
+
}, []);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Narrow an unknown value to a plain object record
|
|
239
|
+
* @param {unknown} value value to check
|
|
240
|
+
* @returns {value is Record<string, unknown>} whether the value is a non-null object
|
|
241
|
+
* @internal
|
|
242
|
+
*/
|
|
243
|
+
function isRecord(value) {
|
|
244
|
+
return typeof value === 'object' && value !== null;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Read a string property from an unknown object
|
|
248
|
+
* @param {unknown} value object that may contain the property
|
|
249
|
+
* @param {string} key property name
|
|
250
|
+
* @returns {string | undefined} the string value, or undefined when it is missing or not a string
|
|
251
|
+
* @internal
|
|
252
|
+
*/
|
|
253
|
+
function getRecordString(value, key) {
|
|
254
|
+
if (!isRecord(value)) {
|
|
255
|
+
return undefined;
|
|
256
|
+
}
|
|
257
|
+
const property = value[key];
|
|
258
|
+
return typeof property === 'string' ? property : undefined;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Split a package locator into name and version, e.g. '@scope/pkg@npm:1.0.0'. Yarn prefixes the
|
|
262
|
+
* reference with the protocol it was resolved with, which is not part of the version.
|
|
263
|
+
* @param {string} locator package locator
|
|
264
|
+
* @returns {Package | undefined} the package, or undefined when the locator could not be parsed
|
|
265
|
+
* @internal
|
|
266
|
+
*/
|
|
267
|
+
function parsePackageLocator(locator) {
|
|
268
|
+
const separatorIndex = locator.lastIndexOf('@');
|
|
269
|
+
if (separatorIndex <= 0) {
|
|
270
|
+
return undefined;
|
|
271
|
+
}
|
|
272
|
+
const reference = locator.slice(separatorIndex + 1);
|
|
273
|
+
const version = reference.slice(reference.lastIndexOf(':') + 1);
|
|
274
|
+
return version ? { name: locator.slice(0, separatorIndex), version } : undefined;
|
|
275
|
+
}
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../../../src/tools/metadata/metadata.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../../../src/tools/metadata/metadata.ts"],"names":[],"mappings":"AAyBA;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;CACrC;AAqBD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,eAAe,GAAE,OAAe,GAAG,QAAQ,CAqBtE"}
|