@openrewrite/rewrite 8.68.0-20251202-082117 → 8.68.0-20251202-154952
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.
- package/dist/javascript/assertions.d.ts +1 -0
- package/dist/javascript/assertions.d.ts.map +1 -1
- package/dist/javascript/assertions.js +82 -11
- package/dist/javascript/assertions.js.map +1 -1
- package/dist/javascript/dependency-workspace.d.ts +46 -5
- package/dist/javascript/dependency-workspace.d.ts.map +1 -1
- package/dist/javascript/dependency-workspace.js +70 -35
- package/dist/javascript/dependency-workspace.js.map +1 -1
- package/dist/javascript/index.d.ts +2 -0
- package/dist/javascript/index.d.ts.map +1 -1
- package/dist/javascript/index.js +2 -0
- package/dist/javascript/index.js.map +1 -1
- package/dist/javascript/node-resolution-result.d.ts +204 -0
- package/dist/javascript/node-resolution-result.d.ts.map +1 -0
- package/dist/javascript/node-resolution-result.js +723 -0
- package/dist/javascript/node-resolution-result.js.map +1 -0
- package/dist/javascript/package-json-parser.d.ts +143 -0
- package/dist/javascript/package-json-parser.d.ts.map +1 -0
- package/dist/javascript/package-json-parser.js +773 -0
- package/dist/javascript/package-json-parser.js.map +1 -0
- package/dist/javascript/templating/engine.js +1 -1
- package/dist/javascript/templating/engine.js.map +1 -1
- package/dist/json/parser.js +10 -1
- package/dist/json/parser.js.map +1 -1
- package/dist/json/tree.d.ts +1 -1
- package/dist/json/tree.js +1 -1
- package/dist/json/tree.js.map +1 -1
- package/dist/parser.d.ts +1 -1
- package/dist/parser.d.ts.map +1 -1
- package/dist/rpc/request/parse.d.ts +4 -0
- package/dist/rpc/request/parse.d.ts.map +1 -1
- package/dist/rpc/request/parse.js +17 -1
- package/dist/rpc/request/parse.js.map +1 -1
- package/dist/version.txt +1 -1
- package/package.json +5 -2
- package/src/javascript/assertions.ts +73 -15
- package/src/javascript/dependency-workspace.ts +124 -46
- package/src/javascript/index.ts +2 -0
- package/src/javascript/node-resolution-result.ts +905 -0
- package/src/javascript/package-json-parser.ts +845 -0
- package/src/javascript/templating/engine.ts +1 -1
- package/src/json/parser.ts +18 -1
- package/src/json/tree.ts +1 -1
- package/src/parser.ts +1 -1
- package/src/rpc/request/parse.ts +20 -2
|
@@ -0,0 +1,773 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
36
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
37
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
38
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
39
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
40
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
41
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
|
|
45
|
+
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
|
|
46
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
47
|
+
var g = generator.apply(thisArg, _arguments || []), i, q = [];
|
|
48
|
+
return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
|
|
49
|
+
function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
|
|
50
|
+
function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
|
|
51
|
+
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
|
|
52
|
+
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
|
|
53
|
+
function fulfill(value) { resume("next", value); }
|
|
54
|
+
function reject(value) { resume("throw", value); }
|
|
55
|
+
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
|
|
56
|
+
};
|
|
57
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
58
|
+
exports.PackageJsonParser = void 0;
|
|
59
|
+
/*
|
|
60
|
+
* Copyright 2025 the original author or authors.
|
|
61
|
+
* <p>
|
|
62
|
+
* Licensed under the Moderne Source Available License (the "License");
|
|
63
|
+
* you may not use this file except in compliance with the License.
|
|
64
|
+
* You may obtain a copy of the License at
|
|
65
|
+
* <p>
|
|
66
|
+
* https://docs.moderne.io/licensing/moderne-source-available-license
|
|
67
|
+
* <p>
|
|
68
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
69
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
70
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
71
|
+
* See the License for the specific language governing permissions and
|
|
72
|
+
* limitations under the License.
|
|
73
|
+
*/
|
|
74
|
+
const parser_1 = require("../parser");
|
|
75
|
+
const json_1 = require("../json");
|
|
76
|
+
const node_resolution_result_1 = require("./node-resolution-result");
|
|
77
|
+
const fs = __importStar(require("fs"));
|
|
78
|
+
const fsp = __importStar(require("fs/promises"));
|
|
79
|
+
const path = __importStar(require("path"));
|
|
80
|
+
const YAML = __importStar(require("yaml"));
|
|
81
|
+
const child_process_1 = require("child_process");
|
|
82
|
+
/**
|
|
83
|
+
* A parser for package.json files that wraps the JsonParser.
|
|
84
|
+
*
|
|
85
|
+
* Similar to how MavenParser wraps XmlParser in Java, this parser:
|
|
86
|
+
* - Parses package.json files as JSON documents
|
|
87
|
+
* - Attaches NodeResolutionResult markers with dependency information
|
|
88
|
+
* - Optionally reads corresponding lock files (package-lock.json, yarn.lock, etc.)
|
|
89
|
+
* to provide resolved dependency versions
|
|
90
|
+
*/
|
|
91
|
+
class PackageJsonParser extends parser_1.Parser {
|
|
92
|
+
constructor(options = {}) {
|
|
93
|
+
var _a;
|
|
94
|
+
super(options);
|
|
95
|
+
this.jsonParser = new json_1.JsonParser(options);
|
|
96
|
+
this.skipDependencyResolution = (_a = options.skipDependencyResolution) !== null && _a !== void 0 ? _a : false;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Extracts package metadata from a package.json object into a lock file entry format.
|
|
100
|
+
* Copies version, dependency fields, engines, and license.
|
|
101
|
+
*/
|
|
102
|
+
static extractPackageMetadata(pkgJson, fallbackVersion) {
|
|
103
|
+
const entry = {
|
|
104
|
+
version: pkgJson.version || fallbackVersion,
|
|
105
|
+
};
|
|
106
|
+
for (const field of PackageJsonParser.DEPENDENCY_FIELDS) {
|
|
107
|
+
if (pkgJson[field] && Object.keys(pkgJson[field]).length > 0) {
|
|
108
|
+
entry[field] = pkgJson[field];
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (pkgJson.engines) {
|
|
112
|
+
entry.engines = pkgJson.engines;
|
|
113
|
+
}
|
|
114
|
+
if (pkgJson.license) {
|
|
115
|
+
entry.license = pkgJson.license;
|
|
116
|
+
}
|
|
117
|
+
return entry;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Accepts package.json files.
|
|
121
|
+
*/
|
|
122
|
+
accept(sourcePath) {
|
|
123
|
+
const fileName = path.basename(sourcePath);
|
|
124
|
+
return fileName === 'package.json';
|
|
125
|
+
}
|
|
126
|
+
parse(...inputs) {
|
|
127
|
+
return __asyncGenerator(this, arguments, function* parse_1() {
|
|
128
|
+
// Group inputs by directory to share NodeResolutionResult markers
|
|
129
|
+
const inputsByDir = new Map();
|
|
130
|
+
for (const input of inputs) {
|
|
131
|
+
const filePath = (0, parser_1.parserInputFile)(input);
|
|
132
|
+
const dir = path.dirname(filePath);
|
|
133
|
+
if (!inputsByDir.has(dir)) {
|
|
134
|
+
inputsByDir.set(dir, []);
|
|
135
|
+
}
|
|
136
|
+
inputsByDir.get(dir).push(input);
|
|
137
|
+
}
|
|
138
|
+
// Process each directory's package.json files
|
|
139
|
+
for (const [dir, dirInputs] of inputsByDir) {
|
|
140
|
+
// Create a shared marker for this directory
|
|
141
|
+
let marker = null;
|
|
142
|
+
for (const input of dirInputs) {
|
|
143
|
+
// Parse as JSON first
|
|
144
|
+
const jsonGenerator = this.jsonParser.parse(input);
|
|
145
|
+
const jsonResult = yield __await(jsonGenerator.next());
|
|
146
|
+
if (jsonResult.done || !jsonResult.value) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const jsonDoc = jsonResult.value;
|
|
150
|
+
// Create NodeResolutionResult marker if not already created for this directory
|
|
151
|
+
if (!marker) {
|
|
152
|
+
marker = yield __await(this.createMarker(input, dir));
|
|
153
|
+
}
|
|
154
|
+
// Attach the marker to the JSON document
|
|
155
|
+
if (marker) {
|
|
156
|
+
yield yield __await(Object.assign(Object.assign({}, jsonDoc), { markers: Object.assign(Object.assign({}, jsonDoc.markers), { markers: [...jsonDoc.markers.markers, marker] }) }));
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
yield yield __await(jsonDoc);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Creates a NodeResolutionResult marker from the package.json content and optional lock file.
|
|
167
|
+
*/
|
|
168
|
+
createMarker(input, dir) {
|
|
169
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
170
|
+
try {
|
|
171
|
+
const content = (0, parser_1.parserInputRead)(input);
|
|
172
|
+
const packageJson = JSON.parse(content);
|
|
173
|
+
// Determine the relative path for the marker
|
|
174
|
+
const filePath = (0, parser_1.parserInputFile)(input);
|
|
175
|
+
const relativePath = this.relativeTo
|
|
176
|
+
? path.relative(this.relativeTo, filePath)
|
|
177
|
+
: filePath;
|
|
178
|
+
// Try to read lock file if dependency resolution is not skipped
|
|
179
|
+
// Use relativeTo directory if available (for tests), otherwise use the directory from input path
|
|
180
|
+
let lockContent = undefined;
|
|
181
|
+
let packageManager = undefined;
|
|
182
|
+
if (!this.skipDependencyResolution) {
|
|
183
|
+
const lockDir = this.relativeTo || dir;
|
|
184
|
+
const lockResult = yield this.tryReadLockFile(lockDir);
|
|
185
|
+
lockContent = lockResult === null || lockResult === void 0 ? void 0 : lockResult.content;
|
|
186
|
+
packageManager = lockResult === null || lockResult === void 0 ? void 0 : lockResult.packageManager;
|
|
187
|
+
}
|
|
188
|
+
// Read .npmrc configurations from all scopes
|
|
189
|
+
const projectDir = this.relativeTo || dir;
|
|
190
|
+
const npmrcConfigs = yield (0, node_resolution_result_1.readNpmrcConfigs)(projectDir);
|
|
191
|
+
return (0, node_resolution_result_1.createNodeResolutionResultMarker)(relativePath, packageJson, lockContent, undefined, packageManager, npmrcConfigs.length > 0 ? npmrcConfigs : undefined);
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
console.warn(`Failed to create NodeResolutionResult marker: ${error}`);
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Attempts to read and parse a lock file from the given directory.
|
|
201
|
+
* Supports npm (package-lock.json), bun (bun.lock), pnpm, and yarn.
|
|
202
|
+
*
|
|
203
|
+
* @returns Object with parsed lock file content and detected package manager, or undefined if no lock file found
|
|
204
|
+
*/
|
|
205
|
+
tryReadLockFile(dir) {
|
|
206
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
207
|
+
var _a;
|
|
208
|
+
// Detect which lock file exists (first match wins based on priority)
|
|
209
|
+
for (const config of PackageJsonParser.LOCK_FILE_CONFIG) {
|
|
210
|
+
const lockPath = path.join(dir, config.filename);
|
|
211
|
+
if (!fs.existsSync(lockPath)) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
try {
|
|
215
|
+
const fileContent = yield fsp.readFile(lockPath, 'utf-8');
|
|
216
|
+
const packageManager = typeof config.packageManager === 'function'
|
|
217
|
+
? config.packageManager(fileContent)
|
|
218
|
+
: config.packageManager;
|
|
219
|
+
// For package managers where lock file omits details, prefer node_modules
|
|
220
|
+
if (config.preferNodeModules) {
|
|
221
|
+
const parsed = yield this.walkNodeModules(dir);
|
|
222
|
+
if (parsed) {
|
|
223
|
+
return { content: parsed, packageManager };
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// Parse lock file based on package manager
|
|
227
|
+
const content = yield this.parseLockFileContent(config.filename, fileContent, dir);
|
|
228
|
+
if (content) {
|
|
229
|
+
return { content, packageManager };
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
(_a = console.debug) === null || _a === void 0 ? void 0 : _a.call(console, `Failed to parse ${config.filename}: ${error}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return undefined;
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Parses lock file content based on the lock file type.
|
|
241
|
+
*/
|
|
242
|
+
parseLockFileContent(filename, content, dir) {
|
|
243
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
244
|
+
switch (filename) {
|
|
245
|
+
case 'package-lock.json':
|
|
246
|
+
return JSON.parse(content);
|
|
247
|
+
case 'bun.lock':
|
|
248
|
+
return this.convertBunLockToNpmFormat(this.parseJsonc(content));
|
|
249
|
+
case 'pnpm-lock.yaml':
|
|
250
|
+
// Fall back to pnpm CLI when node_modules unavailable
|
|
251
|
+
return this.getPnpmDependencies(dir);
|
|
252
|
+
case 'yarn.lock':
|
|
253
|
+
return this.parseYarnLock(content);
|
|
254
|
+
default:
|
|
255
|
+
return undefined;
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Parses JSONC (JSON with Comments and trailing commas) content.
|
|
261
|
+
*
|
|
262
|
+
* Note: This is a simple regex-based approach that works for bun.lock files but doesn't
|
|
263
|
+
* handle edge cases like comment-like sequences inside strings (e.g., "// not a comment").
|
|
264
|
+
* For lock files this is acceptable since they don't contain such patterns. If broader
|
|
265
|
+
* JSONC support is needed, consider using a proper parser like `jsonc-parser`.
|
|
266
|
+
*/
|
|
267
|
+
parseJsonc(content) {
|
|
268
|
+
// Remove single-line comments (// ...)
|
|
269
|
+
let stripped = content.replace(/\/\/.*$/gm, '');
|
|
270
|
+
// Remove multi-line comments (/* ... */)
|
|
271
|
+
stripped = stripped.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
272
|
+
// Remove trailing commas before ] or }
|
|
273
|
+
stripped = stripped.replace(/,(\s*[}\]])/g, '$1');
|
|
274
|
+
return JSON.parse(stripped);
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Walks the node_modules directory to build an npm-format packages structure.
|
|
278
|
+
* This provides 100% accurate resolution for all package managers since it reads
|
|
279
|
+
* the actual installed packages rather than trying to interpret lock file formats.
|
|
280
|
+
*
|
|
281
|
+
* @param dir The project directory containing node_modules
|
|
282
|
+
* @returns npm package-lock.json format with packages map, or undefined if node_modules doesn't exist
|
|
283
|
+
*/
|
|
284
|
+
walkNodeModules(dir) {
|
|
285
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
286
|
+
const nodeModulesPath = path.join(dir, 'node_modules');
|
|
287
|
+
if (!fs.existsSync(nodeModulesPath)) {
|
|
288
|
+
return undefined;
|
|
289
|
+
}
|
|
290
|
+
const packages = {
|
|
291
|
+
"": {} // Root package placeholder
|
|
292
|
+
};
|
|
293
|
+
// Check if this is a pnpm project (has .pnpm directory)
|
|
294
|
+
const pnpmPath = path.join(nodeModulesPath, '.pnpm');
|
|
295
|
+
if (fs.existsSync(pnpmPath)) {
|
|
296
|
+
yield this.walkPnpmNodeModules(pnpmPath, packages);
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
yield this.walkNodeModulesRecursive(nodeModulesPath, 'node_modules', packages);
|
|
300
|
+
}
|
|
301
|
+
return Object.keys(packages).length > 1 ? {
|
|
302
|
+
lockfileVersion: 3,
|
|
303
|
+
packages
|
|
304
|
+
} : undefined;
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Walks pnpm's .pnpm directory structure to build packages map.
|
|
309
|
+
* pnpm stores packages in .pnpm/<name>@<version>/node_modules/<name>/
|
|
310
|
+
*/
|
|
311
|
+
walkPnpmNodeModules(pnpmPath, packages) {
|
|
312
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
313
|
+
let entries;
|
|
314
|
+
try {
|
|
315
|
+
entries = yield fsp.readdir(pnpmPath, { withFileTypes: true });
|
|
316
|
+
}
|
|
317
|
+
catch (_a) {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
// Process entries in parallel for better performance
|
|
321
|
+
yield Promise.all(entries.map((entry) => __awaiter(this, void 0, void 0, function* () {
|
|
322
|
+
// Skip non-directories and special files
|
|
323
|
+
if (!entry.isDirectory() || entry.name === 'node_modules') {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
// Parse name@version from directory name
|
|
327
|
+
// Handle scoped packages: @scope+name@version
|
|
328
|
+
const atIndex = entry.name.lastIndexOf('@');
|
|
329
|
+
if (atIndex <= 0)
|
|
330
|
+
return;
|
|
331
|
+
let name = entry.name.substring(0, atIndex);
|
|
332
|
+
const version = entry.name.substring(atIndex + 1);
|
|
333
|
+
// pnpm encodes @ as + in scoped packages: @scope+name -> @scope/name
|
|
334
|
+
if (name.startsWith('@') && name.includes('+')) {
|
|
335
|
+
name = name.replace('+', '/');
|
|
336
|
+
}
|
|
337
|
+
// The actual package is at .pnpm/<name>@<version>/node_modules/<name>/
|
|
338
|
+
const pkgDir = path.join(pnpmPath, entry.name, 'node_modules', name.replace('/', path.sep));
|
|
339
|
+
const packageJsonPath = path.join(pkgDir, 'package.json');
|
|
340
|
+
let pkgJson;
|
|
341
|
+
try {
|
|
342
|
+
const content = yield fsp.readFile(packageJsonPath, 'utf-8');
|
|
343
|
+
pkgJson = JSON.parse(content);
|
|
344
|
+
}
|
|
345
|
+
catch (_a) {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
// Use name@version as the key for pnpm (flat structure with version)
|
|
349
|
+
const pkgKey = `node_modules/${name}@${version}`;
|
|
350
|
+
packages[pkgKey] = PackageJsonParser.extractPackageMetadata(pkgJson, version);
|
|
351
|
+
})));
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Recursively walks a node_modules directory, reading package.json files
|
|
356
|
+
* and building the packages map.
|
|
357
|
+
*
|
|
358
|
+
* @param nodeModulesPath Absolute path to the node_modules directory
|
|
359
|
+
* @param relativePath Relative path from project root (e.g., "node_modules" or "node_modules/foo/node_modules")
|
|
360
|
+
* @param packages The packages map to populate
|
|
361
|
+
*/
|
|
362
|
+
walkNodeModulesRecursive(nodeModulesPath, relativePath, packages) {
|
|
363
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
364
|
+
let entries;
|
|
365
|
+
try {
|
|
366
|
+
entries = yield fsp.readdir(nodeModulesPath, { withFileTypes: true });
|
|
367
|
+
}
|
|
368
|
+
catch (_a) {
|
|
369
|
+
return; // Directory not readable
|
|
370
|
+
}
|
|
371
|
+
// Process entries in parallel for better performance
|
|
372
|
+
yield Promise.all(entries.map((entry) => __awaiter(this, void 0, void 0, function* () {
|
|
373
|
+
// Skip hidden files
|
|
374
|
+
if (entry.name.startsWith('.')) {
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
// Accept directories and symlinks (pnpm uses symlinks)
|
|
378
|
+
const isDirectoryOrSymlink = entry.isDirectory() || entry.isSymbolicLink();
|
|
379
|
+
if (!isDirectoryOrSymlink) {
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
// Handle scoped packages (@scope/name)
|
|
383
|
+
if (entry.name.startsWith('@')) {
|
|
384
|
+
const scopePath = path.join(nodeModulesPath, entry.name);
|
|
385
|
+
let scopeEntries;
|
|
386
|
+
try {
|
|
387
|
+
scopeEntries = yield fsp.readdir(scopePath, { withFileTypes: true });
|
|
388
|
+
}
|
|
389
|
+
catch (_a) {
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
yield Promise.all(scopeEntries.map((scopeEntry) => __awaiter(this, void 0, void 0, function* () {
|
|
393
|
+
// Accept directories and symlinks for scoped packages too
|
|
394
|
+
if (!scopeEntry.isDirectory() && !scopeEntry.isSymbolicLink())
|
|
395
|
+
return;
|
|
396
|
+
const scopedName = `${entry.name}/${scopeEntry.name}`;
|
|
397
|
+
const pkgPath = path.join(scopePath, scopeEntry.name);
|
|
398
|
+
yield this.processPackage(pkgPath, `${relativePath}/${scopedName}`, packages);
|
|
399
|
+
})));
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
const pkgPath = path.join(nodeModulesPath, entry.name);
|
|
403
|
+
yield this.processPackage(pkgPath, `${relativePath}/${entry.name}`, packages);
|
|
404
|
+
}
|
|
405
|
+
})));
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Processes a single package directory, reading its package.json and
|
|
410
|
+
* recursively processing nested node_modules.
|
|
411
|
+
*/
|
|
412
|
+
processPackage(pkgPath, relativePath, packages) {
|
|
413
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
414
|
+
const packageJsonPath = path.join(pkgPath, 'package.json');
|
|
415
|
+
// Read and parse the package's package.json
|
|
416
|
+
let pkgJson;
|
|
417
|
+
try {
|
|
418
|
+
const content = yield fsp.readFile(packageJsonPath, 'utf-8');
|
|
419
|
+
pkgJson = JSON.parse(content);
|
|
420
|
+
}
|
|
421
|
+
catch (_a) {
|
|
422
|
+
return; // Not a valid package
|
|
423
|
+
}
|
|
424
|
+
packages[relativePath] = PackageJsonParser.extractPackageMetadata(pkgJson);
|
|
425
|
+
// Recursively process nested node_modules
|
|
426
|
+
const nestedNodeModules = path.join(pkgPath, 'node_modules');
|
|
427
|
+
try {
|
|
428
|
+
yield fsp.access(nestedNodeModules);
|
|
429
|
+
yield this.walkNodeModulesRecursive(nestedNodeModules, `${relativePath}/node_modules`, packages);
|
|
430
|
+
}
|
|
431
|
+
catch (_b) {
|
|
432
|
+
// No nested node_modules, that's fine
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Converts bun.lock format to npm package-lock.json format for unified processing.
|
|
438
|
+
*
|
|
439
|
+
* bun.lock format (v1):
|
|
440
|
+
* - Keys are package names or paths like "is-even/is-odd" for nested deps
|
|
441
|
+
* - Values are arrays: [name@version, url, metadata, integrity]
|
|
442
|
+
* - metadata can have: { dependencies: {...}, devDependencies: {...}, ... }
|
|
443
|
+
*/
|
|
444
|
+
convertBunLockToNpmFormat(bunLock) {
|
|
445
|
+
if (!bunLock.packages) {
|
|
446
|
+
return undefined;
|
|
447
|
+
}
|
|
448
|
+
const packages = {
|
|
449
|
+
"": {} // Root package placeholder
|
|
450
|
+
};
|
|
451
|
+
for (const [key, value] of Object.entries(bunLock.packages)) {
|
|
452
|
+
// bun.lock array format: [name@version, url, metadata, integrity]
|
|
453
|
+
const [nameAtVersion, , metadata] = value;
|
|
454
|
+
if (typeof nameAtVersion !== 'string')
|
|
455
|
+
continue;
|
|
456
|
+
// Parse name@version from first element
|
|
457
|
+
const atIndex = nameAtVersion.lastIndexOf('@');
|
|
458
|
+
if (atIndex <= 0)
|
|
459
|
+
continue;
|
|
460
|
+
const name = nameAtVersion.substring(0, atIndex);
|
|
461
|
+
const version = nameAtVersion.substring(atIndex + 1);
|
|
462
|
+
const pkgEntry = {
|
|
463
|
+
version,
|
|
464
|
+
dependencies: (metadata === null || metadata === void 0 ? void 0 : metadata.dependencies) && Object.keys(metadata.dependencies).length > 0
|
|
465
|
+
? metadata.dependencies : undefined,
|
|
466
|
+
devDependencies: (metadata === null || metadata === void 0 ? void 0 : metadata.devDependencies) && Object.keys(metadata.devDependencies).length > 0
|
|
467
|
+
? metadata.devDependencies : undefined,
|
|
468
|
+
peerDependencies: (metadata === null || metadata === void 0 ? void 0 : metadata.peerDependencies) && Object.keys(metadata.peerDependencies).length > 0
|
|
469
|
+
? metadata.peerDependencies : undefined,
|
|
470
|
+
optionalDependencies: (metadata === null || metadata === void 0 ? void 0 : metadata.optionalDependencies) && Object.keys(metadata.optionalDependencies).length > 0
|
|
471
|
+
? metadata.optionalDependencies : undefined,
|
|
472
|
+
};
|
|
473
|
+
// Convert bun's path format to npm's node_modules format
|
|
474
|
+
// bun uses "parent/child" for nested deps, npm uses "node_modules/parent/node_modules/child"
|
|
475
|
+
let pkgPath;
|
|
476
|
+
if (key.includes('/')) {
|
|
477
|
+
// Nested dependency - convert "is-even/is-odd" to "node_modules/is-even/node_modules/is-odd"
|
|
478
|
+
const parts = key.split('/');
|
|
479
|
+
pkgPath = parts.map(p => `node_modules/${p}`).join('/');
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
pkgPath = `node_modules/${name}`;
|
|
483
|
+
}
|
|
484
|
+
packages[pkgPath] = pkgEntry;
|
|
485
|
+
}
|
|
486
|
+
return {
|
|
487
|
+
lockfileVersion: 3,
|
|
488
|
+
packages
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Gets dependency information from pnpm using its CLI.
|
|
493
|
+
* Uses `pnpm list --json --depth=Infinity` to get the full dependency tree.
|
|
494
|
+
*/
|
|
495
|
+
getPnpmDependencies(dir) {
|
|
496
|
+
// Use spawnSync with array args to avoid shell injection risks
|
|
497
|
+
const result = (0, child_process_1.spawnSync)('pnpm', ['list', '--json', '--depth=Infinity'], {
|
|
498
|
+
cwd: dir,
|
|
499
|
+
encoding: 'utf-8',
|
|
500
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
501
|
+
timeout: 30000
|
|
502
|
+
});
|
|
503
|
+
if (result.error || result.status !== 0) {
|
|
504
|
+
return undefined;
|
|
505
|
+
}
|
|
506
|
+
const pnpmList = JSON.parse(result.stdout);
|
|
507
|
+
return this.convertPnpmListToNpmFormat(pnpmList);
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Converts pnpm list --json output to npm package-lock.json format.
|
|
511
|
+
*/
|
|
512
|
+
convertPnpmListToNpmFormat(pnpmList) {
|
|
513
|
+
const packages = {
|
|
514
|
+
"": {} // Root package placeholder
|
|
515
|
+
};
|
|
516
|
+
// pnpm list returns an array of projects (for workspaces) or a single object
|
|
517
|
+
const projects = Array.isArray(pnpmList) ? pnpmList : [pnpmList];
|
|
518
|
+
for (const project of projects) {
|
|
519
|
+
this.extractPnpmDependencies(project.dependencies, packages);
|
|
520
|
+
this.extractPnpmDependencies(project.devDependencies, packages);
|
|
521
|
+
this.extractPnpmDependencies(project.optionalDependencies, packages);
|
|
522
|
+
}
|
|
523
|
+
return Object.keys(packages).length > 1 ? {
|
|
524
|
+
lockfileVersion: 3,
|
|
525
|
+
packages
|
|
526
|
+
} : undefined;
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Recursively extracts dependencies from pnpm list output.
|
|
530
|
+
* Uses name@version as key to handle multiple versions of the same package.
|
|
531
|
+
*/
|
|
532
|
+
extractPnpmDependencies(deps, packages) {
|
|
533
|
+
if (!deps)
|
|
534
|
+
return;
|
|
535
|
+
for (const [name, info] of Object.entries(deps)) {
|
|
536
|
+
const version = info.version;
|
|
537
|
+
if (!version)
|
|
538
|
+
continue;
|
|
539
|
+
const pkgKey = `node_modules/${name}@${version}`;
|
|
540
|
+
if (!packages[pkgKey]) {
|
|
541
|
+
const pkgEntry = { version };
|
|
542
|
+
// Extract nested dependency version constraints
|
|
543
|
+
if (info.dependencies) {
|
|
544
|
+
const nestedDeps = {};
|
|
545
|
+
for (const [depName, depInfo] of Object.entries(info.dependencies)) {
|
|
546
|
+
nestedDeps[depName] = depInfo.version || '*';
|
|
547
|
+
}
|
|
548
|
+
if (Object.keys(nestedDeps).length > 0) {
|
|
549
|
+
pkgEntry.dependencies = nestedDeps;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
packages[pkgKey] = pkgEntry;
|
|
553
|
+
}
|
|
554
|
+
// Recursively process nested dependencies
|
|
555
|
+
if (info.dependencies) {
|
|
556
|
+
this.extractPnpmDependencies(info.dependencies, packages);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Parses yarn.lock file and returns npm-format content.
|
|
562
|
+
* Detects whether it's Yarn Classic (v1) or Yarn Berry (v2+) format.
|
|
563
|
+
*/
|
|
564
|
+
parseYarnLock(content) {
|
|
565
|
+
// Yarn Berry (v2+) has __metadata section at the start
|
|
566
|
+
if (content.includes('__metadata:')) {
|
|
567
|
+
return this.parseYarnBerryLock(content);
|
|
568
|
+
}
|
|
569
|
+
// Yarn Classic (v1) starts with "# yarn lockfile v1"
|
|
570
|
+
if (content.includes('# yarn lockfile v1')) {
|
|
571
|
+
return this.parseYarnClassicLock(content);
|
|
572
|
+
}
|
|
573
|
+
return undefined;
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Parses Yarn Berry (v2+) yarn.lock file directly.
|
|
577
|
+
* Format is standard YAML with package entries like:
|
|
578
|
+
* "is-odd@npm:^3.0.1":
|
|
579
|
+
* version: 3.0.1
|
|
580
|
+
* resolution: "is-odd@npm:3.0.1"
|
|
581
|
+
* dependencies:
|
|
582
|
+
* is-number: "npm:^6.0.0"
|
|
583
|
+
*/
|
|
584
|
+
parseYarnBerryLock(content) {
|
|
585
|
+
const lock = YAML.parse(content);
|
|
586
|
+
if (!lock)
|
|
587
|
+
return undefined;
|
|
588
|
+
const packages = {
|
|
589
|
+
"": {} // Root package placeholder
|
|
590
|
+
};
|
|
591
|
+
for (const [key, entry] of Object.entries(lock)) {
|
|
592
|
+
// Skip metadata and workspace entries
|
|
593
|
+
if (key === '__metadata' || key.includes('@workspace:'))
|
|
594
|
+
continue;
|
|
595
|
+
if (!entry || typeof entry !== 'object')
|
|
596
|
+
continue;
|
|
597
|
+
const version = entry.version;
|
|
598
|
+
if (!version)
|
|
599
|
+
continue;
|
|
600
|
+
// Extract package name from resolution like "is-odd@npm:3.0.1"
|
|
601
|
+
let name;
|
|
602
|
+
if (entry.resolution) {
|
|
603
|
+
const npmIndex = entry.resolution.indexOf('@npm:');
|
|
604
|
+
if (npmIndex > 0) {
|
|
605
|
+
name = entry.resolution.substring(0, npmIndex);
|
|
606
|
+
}
|
|
607
|
+
else {
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
else {
|
|
612
|
+
// Fallback: parse from key like "is-odd@npm:^3.0.1"
|
|
613
|
+
const npmIndex = key.indexOf('@npm:');
|
|
614
|
+
if (npmIndex > 0) {
|
|
615
|
+
name = key.substring(0, npmIndex);
|
|
616
|
+
}
|
|
617
|
+
else {
|
|
618
|
+
continue;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
const pkgKey = `node_modules/${name}@${version}`;
|
|
622
|
+
// Skip if already processed (multiple version constraints can resolve to same version)
|
|
623
|
+
if (packages[pkgKey])
|
|
624
|
+
continue;
|
|
625
|
+
const pkgEntry = { version };
|
|
626
|
+
// Parse dependencies
|
|
627
|
+
if (entry.dependencies && typeof entry.dependencies === 'object') {
|
|
628
|
+
const deps = {};
|
|
629
|
+
for (const [depName, depConstraint] of Object.entries(entry.dependencies)) {
|
|
630
|
+
// Constraint is like "npm:^6.0.0" - strip the "npm:" prefix
|
|
631
|
+
deps[depName] = depConstraint.startsWith('npm:')
|
|
632
|
+
? depConstraint.substring(4)
|
|
633
|
+
: depConstraint;
|
|
634
|
+
}
|
|
635
|
+
if (Object.keys(deps).length > 0) {
|
|
636
|
+
pkgEntry.dependencies = deps;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
packages[pkgKey] = pkgEntry;
|
|
640
|
+
}
|
|
641
|
+
return Object.keys(packages).length > 1 ? {
|
|
642
|
+
lockfileVersion: 3,
|
|
643
|
+
packages
|
|
644
|
+
} : undefined;
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Parses Yarn Classic (v1) yarn.lock file directly.
|
|
648
|
+
* Format is a custom format (not standard YAML):
|
|
649
|
+
*
|
|
650
|
+
* is-odd@^3.0.1:
|
|
651
|
+
* version "3.0.1"
|
|
652
|
+
* resolved "https://..."
|
|
653
|
+
* integrity sha512-...
|
|
654
|
+
* dependencies:
|
|
655
|
+
* is-number "^6.0.0"
|
|
656
|
+
*/
|
|
657
|
+
parseYarnClassicLock(content) {
|
|
658
|
+
const packages = {
|
|
659
|
+
"": {} // Root package placeholder
|
|
660
|
+
};
|
|
661
|
+
// Split into package blocks - each block starts with an unindented line ending with ":"
|
|
662
|
+
// and may span multiple version constraints (e.g., "pkg@^1.0.0, pkg@^1.2.0:")
|
|
663
|
+
const lines = content.split('\n');
|
|
664
|
+
let currentNames = [];
|
|
665
|
+
let currentVersion = null;
|
|
666
|
+
let currentDeps = {};
|
|
667
|
+
let inDependencies = false;
|
|
668
|
+
for (const line of lines) {
|
|
669
|
+
// Skip comments and empty lines
|
|
670
|
+
if (line.startsWith('#') || line.trim() === '') {
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
// New package block (unindented line ending with ":")
|
|
674
|
+
if (!line.startsWith(' ') && line.endsWith(':')) {
|
|
675
|
+
// Save previous package if exists
|
|
676
|
+
if (currentNames.length > 0 && currentVersion) {
|
|
677
|
+
const pkgKey = `node_modules/${currentNames[0]}@${currentVersion}`;
|
|
678
|
+
if (!packages[pkgKey]) {
|
|
679
|
+
const pkgEntry = { version: currentVersion };
|
|
680
|
+
if (Object.keys(currentDeps).length > 0) {
|
|
681
|
+
pkgEntry.dependencies = currentDeps;
|
|
682
|
+
}
|
|
683
|
+
packages[pkgKey] = pkgEntry;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
// Parse new package names from line like 'is-odd@^3.0.1, is-odd@^3.0.0:'
|
|
687
|
+
// or '"@babel/core@^7.0.0":'
|
|
688
|
+
const namesStr = line.slice(0, -1); // Remove trailing ":"
|
|
689
|
+
currentNames = [];
|
|
690
|
+
// Split by ", " but handle quoted strings
|
|
691
|
+
const parts = namesStr.split(/,\s*(?=(?:[^"]*"[^"]*")*[^"]*$)/);
|
|
692
|
+
for (const part of parts) {
|
|
693
|
+
// Remove surrounding quotes if present
|
|
694
|
+
let cleaned = part.trim();
|
|
695
|
+
if (cleaned.startsWith('"') && cleaned.endsWith('"')) {
|
|
696
|
+
cleaned = cleaned.slice(1, -1);
|
|
697
|
+
}
|
|
698
|
+
// Extract package name (everything before last @)
|
|
699
|
+
const atIndex = cleaned.lastIndexOf('@');
|
|
700
|
+
if (atIndex > 0) {
|
|
701
|
+
currentNames.push(cleaned.substring(0, atIndex));
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
currentVersion = null;
|
|
705
|
+
currentDeps = {};
|
|
706
|
+
inDependencies = false;
|
|
707
|
+
continue;
|
|
708
|
+
}
|
|
709
|
+
// Version line: ' version "3.0.1"'
|
|
710
|
+
const versionMatch = line.match(/^\s+version\s+"([^"]+)"/);
|
|
711
|
+
if (versionMatch) {
|
|
712
|
+
currentVersion = versionMatch[1];
|
|
713
|
+
continue;
|
|
714
|
+
}
|
|
715
|
+
// Dependencies section start
|
|
716
|
+
if (line.match(/^\s+dependencies:\s*$/)) {
|
|
717
|
+
inDependencies = true;
|
|
718
|
+
continue;
|
|
719
|
+
}
|
|
720
|
+
// Other section (resolved, integrity, etc.) - ends dependencies section
|
|
721
|
+
if (line.match(/^\s+\w+:/) && !line.match(/^\s{4}/)) {
|
|
722
|
+
inDependencies = false;
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
// Dependency entry: ' is-number "^6.0.0"'
|
|
726
|
+
if (inDependencies) {
|
|
727
|
+
const depMatch = line.match(/^\s{4}(.+?)\s+"([^"]+)"/);
|
|
728
|
+
if (depMatch) {
|
|
729
|
+
currentDeps[depMatch[1]] = depMatch[2];
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
// Save last package
|
|
734
|
+
if (currentNames.length > 0 && currentVersion) {
|
|
735
|
+
const pkgKey = `node_modules/${currentNames[0]}@${currentVersion}`;
|
|
736
|
+
if (!packages[pkgKey]) {
|
|
737
|
+
const pkgEntry = { version: currentVersion };
|
|
738
|
+
if (Object.keys(currentDeps).length > 0) {
|
|
739
|
+
pkgEntry.dependencies = currentDeps;
|
|
740
|
+
}
|
|
741
|
+
packages[pkgKey] = pkgEntry;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
return Object.keys(packages).length > 1 ? {
|
|
745
|
+
lockfileVersion: 3,
|
|
746
|
+
packages
|
|
747
|
+
} : undefined;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
exports.PackageJsonParser = PackageJsonParser;
|
|
751
|
+
/** Fields to copy from package.json that contain dependency maps */
|
|
752
|
+
PackageJsonParser.DEPENDENCY_FIELDS = [
|
|
753
|
+
'dependencies',
|
|
754
|
+
'devDependencies',
|
|
755
|
+
'peerDependencies',
|
|
756
|
+
'optionalDependencies'
|
|
757
|
+
];
|
|
758
|
+
/**
|
|
759
|
+
* Lock file detection configuration.
|
|
760
|
+
* Priority order determines which package manager is detected when multiple lock files exist.
|
|
761
|
+
*/
|
|
762
|
+
PackageJsonParser.LOCK_FILE_CONFIG = [
|
|
763
|
+
{ filename: 'package-lock.json', packageManager: node_resolution_result_1.PackageManager.Npm },
|
|
764
|
+
{ filename: 'bun.lock', packageManager: node_resolution_result_1.PackageManager.Bun },
|
|
765
|
+
{ filename: 'pnpm-lock.yaml', packageManager: node_resolution_result_1.PackageManager.Pnpm, preferNodeModules: true },
|
|
766
|
+
// yarn.lock omits transitive dependency details (engines/license), so prefer node_modules
|
|
767
|
+
{ filename: 'yarn.lock', packageManager: (content) => content.includes('__metadata:') ? node_resolution_result_1.PackageManager.YarnBerry : node_resolution_result_1.PackageManager.YarnClassic,
|
|
768
|
+
preferNodeModules: true
|
|
769
|
+
},
|
|
770
|
+
];
|
|
771
|
+
// Register with the Parsers registry for RPC support
|
|
772
|
+
parser_1.Parsers.registerParser("packageJson", PackageJsonParser);
|
|
773
|
+
//# sourceMappingURL=package-json-parser.js.map
|