@openrewrite/rewrite 8.69.0-20251211-160325 → 8.69.0-20251212-112414
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/cli/cli-utils.d.ts.map +1 -1
- package/dist/cli/cli-utils.js +110 -71
- package/dist/cli/cli-utils.js.map +1 -1
- package/dist/javascript/package-json-parser.d.ts +0 -5
- package/dist/javascript/package-json-parser.d.ts.map +1 -1
- package/dist/javascript/package-json-parser.js +2 -12
- package/dist/javascript/package-json-parser.js.map +1 -1
- package/dist/javascript/package-manager.d.ts +72 -1
- package/dist/javascript/package-manager.d.ts.map +1 -1
- package/dist/javascript/package-manager.js +173 -2
- package/dist/javascript/package-manager.js.map +1 -1
- package/dist/javascript/recipes/add-dependency.d.ts.map +1 -1
- package/dist/javascript/recipes/add-dependency.js +11 -8
- package/dist/javascript/recipes/add-dependency.js.map +1 -1
- package/dist/javascript/recipes/upgrade-dependency-version.d.ts.map +1 -1
- package/dist/javascript/recipes/upgrade-dependency-version.js +11 -8
- package/dist/javascript/recipes/upgrade-dependency-version.js.map +1 -1
- package/dist/javascript/recipes/upgrade-transitive-dependency-version.d.ts.map +1 -1
- package/dist/javascript/recipes/upgrade-transitive-dependency-version.js +11 -8
- package/dist/javascript/recipes/upgrade-transitive-dependency-version.js.map +1 -1
- package/dist/json/parser.d.ts.map +1 -1
- package/dist/json/parser.js +355 -123
- package/dist/json/parser.js.map +1 -1
- package/dist/json/tree.d.ts +5 -1
- package/dist/json/tree.d.ts.map +1 -1
- package/dist/json/tree.js +157 -7
- package/dist/json/tree.js.map +1 -1
- package/dist/print.d.ts.map +1 -1
- package/dist/print.js +5 -1
- package/dist/print.js.map +1 -1
- package/dist/rpc/request/get-languages.d.ts.map +1 -1
- package/dist/rpc/request/get-languages.js +1 -0
- package/dist/rpc/request/get-languages.js.map +1 -1
- package/dist/rpc/server.d.ts +1 -0
- package/dist/rpc/server.d.ts.map +1 -1
- package/dist/rpc/server.js +1 -0
- package/dist/rpc/server.js.map +1 -1
- package/dist/version.txt +1 -1
- package/dist/yaml/index.d.ts +6 -0
- package/dist/yaml/index.d.ts.map +1 -0
- package/dist/yaml/index.js +37 -0
- package/dist/yaml/index.js.map +1 -0
- package/dist/yaml/parser.d.ts +6 -0
- package/dist/yaml/parser.d.ts.map +1 -0
- package/dist/yaml/parser.js +803 -0
- package/dist/yaml/parser.js.map +1 -0
- package/dist/yaml/print.d.ts +2 -0
- package/dist/yaml/print.d.ts.map +1 -0
- package/dist/yaml/print.js +234 -0
- package/dist/yaml/print.js.map +1 -0
- package/dist/yaml/rpc.d.ts +2 -0
- package/dist/yaml/rpc.d.ts.map +1 -0
- package/dist/yaml/rpc.js +264 -0
- package/dist/yaml/rpc.js.map +1 -0
- package/dist/yaml/tree.d.ts +188 -0
- package/dist/yaml/tree.d.ts.map +1 -0
- package/dist/yaml/tree.js +117 -0
- package/dist/yaml/tree.js.map +1 -0
- package/dist/yaml/visitor.d.ts +19 -0
- package/dist/yaml/visitor.d.ts.map +1 -0
- package/dist/yaml/visitor.js +170 -0
- package/dist/yaml/visitor.js.map +1 -0
- package/package.json +6 -1
- package/src/cli/cli-utils.ts +112 -35
- package/src/javascript/package-json-parser.ts +2 -12
- package/src/javascript/package-manager.ts +179 -3
- package/src/javascript/recipes/add-dependency.ts +16 -10
- package/src/javascript/recipes/upgrade-dependency-version.ts +16 -10
- package/src/javascript/recipes/upgrade-transitive-dependency-version.ts +15 -9
- package/src/json/parser.ts +443 -146
- package/src/json/tree.ts +159 -7
- package/src/print.ts +6 -2
- package/src/rpc/request/get-languages.ts +1 -0
- package/src/rpc/server.ts +1 -0
- package/src/yaml/index.ts +21 -0
- package/src/yaml/parser.ts +850 -0
- package/src/yaml/print.ts +212 -0
- package/src/yaml/rpc.ts +248 -0
- package/src/yaml/tree.ts +281 -0
- package/src/yaml/visitor.ts +146 -0
package/src/cli/cli-utils.ts
CHANGED
|
@@ -19,8 +19,17 @@ import * as path from 'path';
|
|
|
19
19
|
import {spawn, spawnSync} from 'child_process';
|
|
20
20
|
import {Recipe, RecipeRegistry} from '../recipe';
|
|
21
21
|
import {SourceFile} from '../tree';
|
|
22
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
isYarnBerryLockFile,
|
|
24
|
+
JavaScriptParser,
|
|
25
|
+
JSON_LOCK_FILE_NAMES,
|
|
26
|
+
PackageJsonParser,
|
|
27
|
+
TEXT_LOCK_FILE_NAMES,
|
|
28
|
+
YAML_LOCK_FILE_NAMES
|
|
29
|
+
} from '../javascript';
|
|
23
30
|
import {JsonParser} from '../json';
|
|
31
|
+
import {PlainTextParser} from '../text';
|
|
32
|
+
import {YamlParser} from '../yaml';
|
|
24
33
|
|
|
25
34
|
// ANSI color codes
|
|
26
35
|
const colors = {
|
|
@@ -440,11 +449,17 @@ export async function walkDirectory(
|
|
|
440
449
|
}
|
|
441
450
|
}
|
|
442
451
|
|
|
452
|
+
/**
|
|
453
|
+
* All lock file names (typed as string[] for easier comparison)
|
|
454
|
+
*/
|
|
455
|
+
const ALL_LOCK_FILE_NAMES: readonly string[] = [...JSON_LOCK_FILE_NAMES, ...YAML_LOCK_FILE_NAMES, ...TEXT_LOCK_FILE_NAMES];
|
|
456
|
+
|
|
443
457
|
/**
|
|
444
458
|
* Check if a file is accepted for parsing based on its extension
|
|
445
459
|
*/
|
|
446
460
|
export function isAcceptedFile(filePath: string): boolean {
|
|
447
461
|
const ext = path.extname(filePath).toLowerCase();
|
|
462
|
+
const basename = path.basename(filePath);
|
|
448
463
|
|
|
449
464
|
// JavaScript/TypeScript files
|
|
450
465
|
if (['.js', '.jsx', '.ts', '.tsx', '.mjs', '.mts', '.cjs', '.cts'].includes(ext)) {
|
|
@@ -456,6 +471,16 @@ export function isAcceptedFile(filePath: string): boolean {
|
|
|
456
471
|
return true;
|
|
457
472
|
}
|
|
458
473
|
|
|
474
|
+
// YAML files
|
|
475
|
+
if (['.yaml', '.yml'].includes(ext)) {
|
|
476
|
+
return true;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Lock files (some have non-standard extensions like yarn.lock)
|
|
480
|
+
if (ALL_LOCK_FILE_NAMES.includes(basename)) {
|
|
481
|
+
return true;
|
|
482
|
+
}
|
|
483
|
+
|
|
459
484
|
return false;
|
|
460
485
|
}
|
|
461
486
|
|
|
@@ -466,6 +491,56 @@ export interface ParseFilesOptions {
|
|
|
466
491
|
onProgress?: ProgressCallback;
|
|
467
492
|
}
|
|
468
493
|
|
|
494
|
+
/**
|
|
495
|
+
* Internal context for file parsing progress tracking.
|
|
496
|
+
*/
|
|
497
|
+
interface ParseContext {
|
|
498
|
+
current: number;
|
|
499
|
+
total: number;
|
|
500
|
+
verbose: boolean;
|
|
501
|
+
onProgress?: ProgressCallback;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Helper to parse files with a given parser, handling verbose logging and progress.
|
|
506
|
+
*/
|
|
507
|
+
async function* parseWithParser(
|
|
508
|
+
files: string[],
|
|
509
|
+
parser: { parse(...files: string[]): AsyncGenerator<SourceFile> },
|
|
510
|
+
fileType: string,
|
|
511
|
+
ctx: ParseContext
|
|
512
|
+
): AsyncGenerator<SourceFile, ParseContext> {
|
|
513
|
+
if (files.length === 0) {
|
|
514
|
+
return ctx;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
if (ctx.verbose) {
|
|
518
|
+
console.log(`Parsing ${files.length} ${fileType} files...`);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
for await (const sf of parser.parse(...files)) {
|
|
522
|
+
ctx.current++;
|
|
523
|
+
ctx.onProgress?.(ctx.current, ctx.total, sf.sourcePath);
|
|
524
|
+
yield sf;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
return ctx;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Classifies a yarn.lock file as YAML (Berry) or text (Classic) based on its content.
|
|
532
|
+
* Returns 'yaml' for Yarn Berry (v2+) and 'text' for Yarn Classic (v1).
|
|
533
|
+
*/
|
|
534
|
+
async function classifyYarnLockFile(filePath: string): Promise<'yaml' | 'text'> {
|
|
535
|
+
try {
|
|
536
|
+
const content = await fsp.readFile(filePath, 'utf-8');
|
|
537
|
+
return isYarnBerryLockFile(content) ? 'yaml' : 'text';
|
|
538
|
+
} catch {
|
|
539
|
+
// Default to text format if we can't read the file
|
|
540
|
+
return 'text';
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
469
544
|
/**
|
|
470
545
|
* Parse source files using appropriate parsers (streaming version).
|
|
471
546
|
* Yields source files as they are parsed, allowing immediate processing.
|
|
@@ -483,6 +558,13 @@ export async function* parseFilesStreaming(
|
|
|
483
558
|
const jsFiles: string[] = [];
|
|
484
559
|
const packageJsonFiles: string[] = [];
|
|
485
560
|
const jsonFiles: string[] = [];
|
|
561
|
+
const jsonLockFiles: string[] = [];
|
|
562
|
+
const yamlLockFiles: string[] = [];
|
|
563
|
+
const yamlFiles: string[] = [];
|
|
564
|
+
const textLockFiles: string[] = [];
|
|
565
|
+
|
|
566
|
+
// Collect yarn.lock files for content-based classification
|
|
567
|
+
const yarnLockFiles: string[] = [];
|
|
486
568
|
|
|
487
569
|
for (const filePath of filePaths) {
|
|
488
570
|
const basename = path.basename(filePath);
|
|
@@ -492,49 +574,44 @@ export async function* parseFilesStreaming(
|
|
|
492
574
|
packageJsonFiles.push(filePath);
|
|
493
575
|
} else if (['.js', '.jsx', '.ts', '.tsx', '.mjs', '.mts', '.cjs', '.cts'].includes(ext)) {
|
|
494
576
|
jsFiles.push(filePath);
|
|
577
|
+
} else if ((JSON_LOCK_FILE_NAMES as readonly string[]).includes(basename)) {
|
|
578
|
+
jsonLockFiles.push(filePath);
|
|
579
|
+
} else if ((YAML_LOCK_FILE_NAMES as readonly string[]).includes(basename)) {
|
|
580
|
+
yamlLockFiles.push(filePath);
|
|
581
|
+
} else if (basename === 'yarn.lock') {
|
|
582
|
+
// yarn.lock needs content-based classification
|
|
583
|
+
yarnLockFiles.push(filePath);
|
|
584
|
+
} else if ((TEXT_LOCK_FILE_NAMES as readonly string[]).includes(basename)) {
|
|
585
|
+
// Other text lock files (if any besides yarn.lock)
|
|
586
|
+
textLockFiles.push(filePath);
|
|
495
587
|
} else if (ext === '.json') {
|
|
496
588
|
jsonFiles.push(filePath);
|
|
589
|
+
} else if (['.yaml', '.yml'].includes(ext)) {
|
|
590
|
+
yamlFiles.push(filePath);
|
|
497
591
|
}
|
|
498
592
|
}
|
|
499
593
|
|
|
500
|
-
//
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
current++;
|
|
508
|
-
onProgress?.(current, total, sf.sourcePath);
|
|
509
|
-
yield sf;
|
|
594
|
+
// Classify yarn.lock files by content (Yarn Berry uses YAML, Classic uses text)
|
|
595
|
+
for (const yarnLockPath of yarnLockFiles) {
|
|
596
|
+
const format = await classifyYarnLockFile(yarnLockPath);
|
|
597
|
+
if (format === 'yaml') {
|
|
598
|
+
yamlLockFiles.push(yarnLockPath);
|
|
599
|
+
} else {
|
|
600
|
+
textLockFiles.push(yarnLockPath);
|
|
510
601
|
}
|
|
511
602
|
}
|
|
512
603
|
|
|
513
|
-
//
|
|
514
|
-
|
|
515
|
-
if (verbose) {
|
|
516
|
-
console.log(`Parsing ${packageJsonFiles.length} package.json files...`);
|
|
517
|
-
}
|
|
518
|
-
const pkgParser = new PackageJsonParser({relativeTo: projectRoot});
|
|
519
|
-
for await (const sf of pkgParser.parse(...packageJsonFiles)) {
|
|
520
|
-
current++;
|
|
521
|
-
onProgress?.(current, total, sf.sourcePath);
|
|
522
|
-
yield sf;
|
|
523
|
-
}
|
|
524
|
-
}
|
|
604
|
+
// Create parse context for tracking progress
|
|
605
|
+
const ctx: ParseContext = { current, total, verbose, onProgress };
|
|
525
606
|
|
|
526
|
-
// Parse
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
onProgress?.(current, total, sf.sourcePath);
|
|
535
|
-
yield sf;
|
|
536
|
-
}
|
|
537
|
-
}
|
|
607
|
+
// Parse files by type using helper
|
|
608
|
+
yield* parseWithParser(jsFiles, new JavaScriptParser({relativeTo: projectRoot}), 'JavaScript/TypeScript', ctx);
|
|
609
|
+
yield* parseWithParser(packageJsonFiles, new PackageJsonParser({relativeTo: projectRoot}), 'package.json', ctx);
|
|
610
|
+
yield* parseWithParser(jsonLockFiles, new JsonParser({relativeTo: projectRoot}), 'JSON lock', ctx);
|
|
611
|
+
yield* parseWithParser(yamlLockFiles, new YamlParser({relativeTo: projectRoot}), 'YAML lock', ctx);
|
|
612
|
+
yield* parseWithParser(textLockFiles, new PlainTextParser({relativeTo: projectRoot}), 'text lock', ctx);
|
|
613
|
+
yield* parseWithParser(yamlFiles, new YamlParser({relativeTo: projectRoot}), 'YAML', ctx);
|
|
614
|
+
yield* parseWithParser(jsonFiles, new JsonParser({relativeTo: projectRoot}), 'JSON', ctx);
|
|
538
615
|
}
|
|
539
616
|
|
|
540
617
|
/**
|
|
@@ -336,20 +336,10 @@ export class PackageJsonParser extends Parser {
|
|
|
336
336
|
|
|
337
337
|
/**
|
|
338
338
|
* Parses JSONC (JSON with Comments and trailing commas) content.
|
|
339
|
-
*
|
|
340
|
-
* Note: This is a simple regex-based approach that works for bun.lock files but doesn't
|
|
341
|
-
* handle edge cases like comment-like sequences inside strings (e.g., "// not a comment").
|
|
342
|
-
* For lock files this is acceptable since they don't contain such patterns. If broader
|
|
343
|
-
* JSONC support is needed, consider using a proper parser like `jsonc-parser`.
|
|
344
339
|
*/
|
|
345
340
|
private parseJsonc(content: string): Record<string, any> {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
// Remove multi-line comments (/* ... */)
|
|
349
|
-
stripped = stripped.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
350
|
-
// Remove trailing commas before ] or }
|
|
351
|
-
stripped = stripped.replace(/,(\s*[}\]])/g, '$1');
|
|
352
|
-
return JSON.parse(stripped);
|
|
341
|
+
const {parse} = require('jsonc-parser');
|
|
342
|
+
return parse(content);
|
|
353
343
|
}
|
|
354
344
|
|
|
355
345
|
/**
|
|
@@ -23,12 +23,18 @@ import {
|
|
|
23
23
|
readNpmrcConfigs
|
|
24
24
|
} from "./node-resolution-result";
|
|
25
25
|
import {replaceMarkerByKind} from "../markers";
|
|
26
|
-
import {Json} from "../json";
|
|
26
|
+
import {Json, JsonParser, JsonVisitor} from "../json";
|
|
27
|
+
import {isDocuments, Yaml, YamlParser, YamlVisitor} from "../yaml";
|
|
28
|
+
import {PlainTextParser} from "../text";
|
|
29
|
+
import {SourceFile} from "../tree";
|
|
30
|
+
import {TreeVisitor} from "../visitor";
|
|
31
|
+
import {ExecutionContext} from "../execution";
|
|
27
32
|
import * as fs from "fs";
|
|
28
33
|
import * as fsp from "fs/promises";
|
|
29
34
|
import * as path from "path";
|
|
30
35
|
import * as os from "os";
|
|
31
36
|
import {spawnSync} from "child_process";
|
|
37
|
+
import * as YAML from "yaml";
|
|
32
38
|
|
|
33
39
|
/**
|
|
34
40
|
* Configuration for each package manager.
|
|
@@ -114,6 +120,34 @@ const LOCK_FILE_DETECTION: ReadonlyArray<LockFileDetectionConfig> = [
|
|
|
114
120
|
},
|
|
115
121
|
];
|
|
116
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Lock file names that should be parsed as JSON/JSONC format.
|
|
125
|
+
*/
|
|
126
|
+
export const JSON_LOCK_FILE_NAMES = ['bun.lock', 'package-lock.json'] as const;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Lock file names that should be parsed as YAML format.
|
|
130
|
+
*/
|
|
131
|
+
export const YAML_LOCK_FILE_NAMES = ['pnpm-lock.yaml'] as const;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Lock file names that should be parsed as plain text (custom formats like yarn.lock v1).
|
|
135
|
+
* Note: yarn.lock for Yarn Berry (v2+) is actually YAML format and should be parsed as such.
|
|
136
|
+
* Use `getLockFileFormat` with content to determine the correct format for yarn.lock.
|
|
137
|
+
*/
|
|
138
|
+
export const TEXT_LOCK_FILE_NAMES = ['yarn.lock'] as const;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Detects if a yarn.lock file is Yarn Berry (v2+) format based on content.
|
|
142
|
+
* Yarn Berry lock files contain a `__metadata:` key which is not present in Classic.
|
|
143
|
+
*
|
|
144
|
+
* @param content The yarn.lock file content
|
|
145
|
+
* @returns true if this is a Yarn Berry lock file (YAML format), false for Classic
|
|
146
|
+
*/
|
|
147
|
+
export function isYarnBerryLockFile(content: string): boolean {
|
|
148
|
+
return content.includes('__metadata:');
|
|
149
|
+
}
|
|
150
|
+
|
|
117
151
|
/**
|
|
118
152
|
* Result of running a package manager command.
|
|
119
153
|
*/
|
|
@@ -339,6 +373,75 @@ export function getUpdatedLockFileContent<T>(
|
|
|
339
373
|
return undefined;
|
|
340
374
|
}
|
|
341
375
|
|
|
376
|
+
/**
|
|
377
|
+
* Determines the appropriate parser for a lock file based on its filename and optionally content.
|
|
378
|
+
*
|
|
379
|
+
* For yarn.lock files, the format depends on the Yarn version:
|
|
380
|
+
* - Yarn Classic (v1): Custom plain text format
|
|
381
|
+
* - Yarn Berry (v2+): YAML format
|
|
382
|
+
*
|
|
383
|
+
* If content is provided for yarn.lock, it will be used to detect the format.
|
|
384
|
+
* Otherwise, defaults to 'text' (Yarn Classic).
|
|
385
|
+
*
|
|
386
|
+
* @param lockFileName The lock file name (e.g., "pnpm-lock.yaml", "package-lock.json", "yarn.lock")
|
|
387
|
+
* @param content Optional file content (used for yarn.lock format detection)
|
|
388
|
+
* @returns 'yaml' for YAML lock files, 'json' for JSON lock files, 'text' for plain text
|
|
389
|
+
*/
|
|
390
|
+
export function getLockFileFormat(lockFileName: string, content?: string): 'yaml' | 'json' | 'text' {
|
|
391
|
+
if ((YAML_LOCK_FILE_NAMES as readonly string[]).includes(lockFileName)) {
|
|
392
|
+
return 'yaml';
|
|
393
|
+
}
|
|
394
|
+
if (lockFileName === 'yarn.lock') {
|
|
395
|
+
// Yarn Berry (v2+) uses YAML format, Classic uses custom text format
|
|
396
|
+
if (content && isYarnBerryLockFile(content)) {
|
|
397
|
+
return 'yaml';
|
|
398
|
+
}
|
|
399
|
+
return 'text';
|
|
400
|
+
}
|
|
401
|
+
if ((TEXT_LOCK_FILE_NAMES as readonly string[]).includes(lockFileName)) {
|
|
402
|
+
return 'text';
|
|
403
|
+
}
|
|
404
|
+
// package-lock.json, bun.lock
|
|
405
|
+
return 'json';
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Re-parses updated lock file content using the appropriate parser.
|
|
410
|
+
* This is used by dependency recipes to create the updated lock file SourceFile.
|
|
411
|
+
*
|
|
412
|
+
* For yarn.lock files, the content is used to detect whether it's Yarn Berry (YAML)
|
|
413
|
+
* or Yarn Classic (plain text) format.
|
|
414
|
+
*
|
|
415
|
+
* @param content The updated lock file content
|
|
416
|
+
* @param sourcePath The source path of the lock file
|
|
417
|
+
* @param lockFileName The lock file name (e.g., "pnpm-lock.yaml", "yarn.lock")
|
|
418
|
+
* @returns The parsed SourceFile (Json.Document, Yaml.Documents, or PlainText)
|
|
419
|
+
*/
|
|
420
|
+
export async function parseLockFileContent(
|
|
421
|
+
content: string,
|
|
422
|
+
sourcePath: string,
|
|
423
|
+
lockFileName: string
|
|
424
|
+
): Promise<SourceFile> {
|
|
425
|
+
// Pass content to getLockFileFormat for yarn.lock detection
|
|
426
|
+
const format = getLockFileFormat(lockFileName, content);
|
|
427
|
+
|
|
428
|
+
switch (format) {
|
|
429
|
+
case 'yaml': {
|
|
430
|
+
const parser = new YamlParser({});
|
|
431
|
+
return await parser.parseOne({text: content, sourcePath}) as Yaml.Documents;
|
|
432
|
+
}
|
|
433
|
+
case 'text': {
|
|
434
|
+
const parser = new PlainTextParser({});
|
|
435
|
+
return await parser.parseOne({text: content, sourcePath});
|
|
436
|
+
}
|
|
437
|
+
case 'json':
|
|
438
|
+
default: {
|
|
439
|
+
const parser = new JsonParser({});
|
|
440
|
+
return await parser.parseOne({text: content, sourcePath}) as Json.Document;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
342
445
|
/**
|
|
343
446
|
* Base interface for project update info used by dependency recipes.
|
|
344
447
|
* Recipes extend this with additional fields specific to their needs.
|
|
@@ -438,7 +541,19 @@ export async function updateNodeResolutionMarker<T extends BaseProjectUpdateInfo
|
|
|
438
541
|
|
|
439
542
|
if (updatedLockFile) {
|
|
440
543
|
try {
|
|
441
|
-
|
|
544
|
+
// Parse lock file based on format
|
|
545
|
+
if (updateInfo.packageManager === PackageManager.Pnpm) {
|
|
546
|
+
// pnpm-lock.yaml is YAML format
|
|
547
|
+
lockContent = YAML.parse(updatedLockFile);
|
|
548
|
+
} else if (updateInfo.packageManager === PackageManager.YarnClassic ||
|
|
549
|
+
updateInfo.packageManager === PackageManager.YarnBerry) {
|
|
550
|
+
// yarn.lock has a custom format - skip parsing here
|
|
551
|
+
// The marker will still be updated with package.json info
|
|
552
|
+
lockContent = undefined;
|
|
553
|
+
} else {
|
|
554
|
+
// npm (package-lock.json) and bun (bun.lock) use JSON
|
|
555
|
+
lockContent = JSON.parse(updatedLockFile);
|
|
556
|
+
}
|
|
442
557
|
} catch {
|
|
443
558
|
// Continue without lock file content
|
|
444
559
|
}
|
|
@@ -533,9 +648,21 @@ export async function runInstallInTempDir(
|
|
|
533
648
|
});
|
|
534
649
|
|
|
535
650
|
if (!result.success) {
|
|
651
|
+
// Combine error message with stderr for more useful diagnostics
|
|
652
|
+
const errorParts: string[] = [];
|
|
653
|
+
if (result.error) {
|
|
654
|
+
errorParts.push(result.error);
|
|
655
|
+
}
|
|
656
|
+
if (result.stderr) {
|
|
657
|
+
// Trim and limit stderr to avoid excessively long error messages
|
|
658
|
+
const stderr = result.stderr.trim();
|
|
659
|
+
if (stderr) {
|
|
660
|
+
errorParts.push(stderr.length > 2000 ? stderr.slice(0, 2000) + '...' : stderr);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
536
663
|
return {
|
|
537
664
|
success: false,
|
|
538
|
-
error:
|
|
665
|
+
error: errorParts.length > 0 ? errorParts.join('\n\n') : 'Unknown error'
|
|
539
666
|
};
|
|
540
667
|
}
|
|
541
668
|
|
|
@@ -560,3 +687,52 @@ export async function runInstallInTempDir(
|
|
|
560
687
|
}
|
|
561
688
|
}
|
|
562
689
|
}
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* Creates a lock file visitor that handles updating YAML lock files (pnpm-lock.yaml).
|
|
693
|
+
* This is a reusable component for dependency recipes.
|
|
694
|
+
*
|
|
695
|
+
* @param acc The recipe accumulator containing updated lock file content
|
|
696
|
+
* @returns A YamlVisitor that updates YAML lock files
|
|
697
|
+
*/
|
|
698
|
+
export function createYamlLockFileVisitor<T>(
|
|
699
|
+
acc: DependencyRecipeAccumulator<T>
|
|
700
|
+
): YamlVisitor<ExecutionContext> {
|
|
701
|
+
return new class extends YamlVisitor<ExecutionContext> {
|
|
702
|
+
protected async visitDocuments(docs: Yaml.Documents, _ctx: ExecutionContext): Promise<Yaml | undefined> {
|
|
703
|
+
const sourcePath = docs.sourcePath;
|
|
704
|
+
const updatedLockContent = getUpdatedLockFileContent(sourcePath, acc);
|
|
705
|
+
if (updatedLockContent) {
|
|
706
|
+
const lockFileName = path.basename(sourcePath);
|
|
707
|
+
return await parseLockFileContent(updatedLockContent, sourcePath, lockFileName) as Yaml.Documents;
|
|
708
|
+
}
|
|
709
|
+
return docs;
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
/**
|
|
715
|
+
* Creates a composite visitor that delegates to the appropriate editor based on tree type.
|
|
716
|
+
* This handles both JSON (package-lock.json, bun.lock) and YAML (pnpm-lock.yaml) lock files.
|
|
717
|
+
*
|
|
718
|
+
* @param jsonEditor The JSON visitor for handling JSON files
|
|
719
|
+
* @param acc The recipe accumulator for YAML lock file handling
|
|
720
|
+
* @returns A TreeVisitor that handles both JSON and YAML files
|
|
721
|
+
*/
|
|
722
|
+
export function createLockFileEditor<T>(
|
|
723
|
+
jsonEditor: JsonVisitor<ExecutionContext>,
|
|
724
|
+
acc: DependencyRecipeAccumulator<T>
|
|
725
|
+
): TreeVisitor<any, ExecutionContext> {
|
|
726
|
+
const yamlEditor = createYamlLockFileVisitor(acc);
|
|
727
|
+
|
|
728
|
+
return new class extends TreeVisitor<any, ExecutionContext> {
|
|
729
|
+
async visit(tree: any, ctx: ExecutionContext): Promise<any> {
|
|
730
|
+
if (isDocuments(tree)) {
|
|
731
|
+
return yamlEditor.visit(tree, ctx);
|
|
732
|
+
} else if (tree && tree.kind === Json.Kind.Document) {
|
|
733
|
+
return jsonEditor.visit(tree, ctx);
|
|
734
|
+
}
|
|
735
|
+
return tree;
|
|
736
|
+
}
|
|
737
|
+
};
|
|
738
|
+
}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
import {Option, ScanningRecipe} from "../../recipe";
|
|
18
18
|
import {ExecutionContext} from "../../execution";
|
|
19
19
|
import {TreeVisitor} from "../../visitor";
|
|
20
|
-
import {detectIndent, getMemberKeyName, isObject, Json,
|
|
20
|
+
import {detectIndent, getMemberKeyName, isObject, Json, JsonVisitor, rightPadded, space} from "../../json";
|
|
21
21
|
import {
|
|
22
22
|
allDependencyScopes,
|
|
23
23
|
DependencyScope,
|
|
@@ -28,8 +28,10 @@ import {emptyMarkers, markupWarn} from "../../markers";
|
|
|
28
28
|
import {TreePrinters} from "../../print";
|
|
29
29
|
import {
|
|
30
30
|
createDependencyRecipeAccumulator,
|
|
31
|
+
createLockFileEditor,
|
|
31
32
|
DependencyRecipeAccumulator,
|
|
32
|
-
|
|
33
|
+
getAllLockFileNames,
|
|
34
|
+
parseLockFileContent,
|
|
33
35
|
runInstallIfNeeded,
|
|
34
36
|
runInstallInTempDir,
|
|
35
37
|
storeInstallResult,
|
|
@@ -154,7 +156,8 @@ export class AddDependency extends ScanningRecipe<Accumulator> {
|
|
|
154
156
|
async editorWithData(acc: Accumulator): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
155
157
|
const recipe = this;
|
|
156
158
|
|
|
157
|
-
|
|
159
|
+
// Create JSON visitor that handles both package.json and JSON lock files
|
|
160
|
+
const jsonEditor = new class extends JsonVisitor<ExecutionContext> {
|
|
158
161
|
protected async visitDocument(doc: Json.Document, ctx: ExecutionContext): Promise<Json | undefined> {
|
|
159
162
|
const sourcePath = doc.sourcePath;
|
|
160
163
|
|
|
@@ -189,18 +192,21 @@ export class AddDependency extends ScanningRecipe<Accumulator> {
|
|
|
189
192
|
return updateNodeResolutionMarker(modifiedDoc, updateInfo, acc);
|
|
190
193
|
}
|
|
191
194
|
|
|
192
|
-
// Handle lock files
|
|
193
|
-
const
|
|
194
|
-
if (
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
sourcePath
|
|
198
|
-
}
|
|
195
|
+
// Handle JSON lock files (package-lock.json, bun.lock)
|
|
196
|
+
const lockFileName = path.basename(sourcePath);
|
|
197
|
+
if (getAllLockFileNames().includes(lockFileName)) {
|
|
198
|
+
const updatedLockContent = acc.updatedLockFiles.get(sourcePath);
|
|
199
|
+
if (updatedLockContent) {
|
|
200
|
+
return await parseLockFileContent(updatedLockContent, sourcePath, lockFileName) as Json.Document;
|
|
201
|
+
}
|
|
199
202
|
}
|
|
200
203
|
|
|
201
204
|
return doc;
|
|
202
205
|
}
|
|
203
206
|
};
|
|
207
|
+
|
|
208
|
+
// Return composite visitor that handles both JSON and YAML lock files
|
|
209
|
+
return createLockFileEditor(jsonEditor, acc);
|
|
204
210
|
}
|
|
205
211
|
|
|
206
212
|
/**
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
import {Option, ScanningRecipe} from "../../recipe";
|
|
18
18
|
import {ExecutionContext} from "../../execution";
|
|
19
19
|
import {TreeVisitor} from "../../visitor";
|
|
20
|
-
import {getMemberKeyName, isLiteral, Json,
|
|
20
|
+
import {getMemberKeyName, isLiteral, Json, JsonVisitor} from "../../json";
|
|
21
21
|
import {
|
|
22
22
|
allDependencyScopes,
|
|
23
23
|
DependencyScope,
|
|
@@ -30,8 +30,10 @@ import {markupWarn, replaceMarkerByKind} from "../../markers";
|
|
|
30
30
|
import {TreePrinters} from "../../print";
|
|
31
31
|
import {
|
|
32
32
|
createDependencyRecipeAccumulator,
|
|
33
|
+
createLockFileEditor,
|
|
33
34
|
DependencyRecipeAccumulator,
|
|
34
|
-
|
|
35
|
+
getAllLockFileNames,
|
|
36
|
+
parseLockFileContent,
|
|
35
37
|
runInstallIfNeeded,
|
|
36
38
|
runInstallInTempDir,
|
|
37
39
|
storeInstallResult,
|
|
@@ -210,7 +212,8 @@ export class UpgradeDependencyVersion extends ScanningRecipe<Accumulator> {
|
|
|
210
212
|
async editorWithData(acc: Accumulator): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
211
213
|
const recipe = this;
|
|
212
214
|
|
|
213
|
-
|
|
215
|
+
// Create JSON visitor that handles both package.json and JSON lock files
|
|
216
|
+
const jsonEditor = new class extends JsonVisitor<ExecutionContext> {
|
|
214
217
|
protected async visitDocument(doc: Json.Document, ctx: ExecutionContext): Promise<Json | undefined> {
|
|
215
218
|
const sourcePath = doc.sourcePath;
|
|
216
219
|
|
|
@@ -252,18 +255,21 @@ export class UpgradeDependencyVersion extends ScanningRecipe<Accumulator> {
|
|
|
252
255
|
return updateNodeResolutionMarker(modifiedDoc, updateInfo, acc);
|
|
253
256
|
}
|
|
254
257
|
|
|
255
|
-
// Handle lock files
|
|
256
|
-
const
|
|
257
|
-
if (
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
sourcePath
|
|
261
|
-
}
|
|
258
|
+
// Handle JSON lock files (package-lock.json, bun.lock)
|
|
259
|
+
const lockFileName = path.basename(sourcePath);
|
|
260
|
+
if (getAllLockFileNames().includes(lockFileName)) {
|
|
261
|
+
const updatedLockContent = acc.updatedLockFiles.get(sourcePath);
|
|
262
|
+
if (updatedLockContent) {
|
|
263
|
+
return await parseLockFileContent(updatedLockContent, sourcePath, lockFileName) as Json.Document;
|
|
264
|
+
}
|
|
262
265
|
}
|
|
263
266
|
|
|
264
267
|
return doc;
|
|
265
268
|
}
|
|
266
269
|
};
|
|
270
|
+
|
|
271
|
+
// Return composite visitor that handles both JSON and YAML lock files
|
|
272
|
+
return createLockFileEditor(jsonEditor, acc);
|
|
267
273
|
}
|
|
268
274
|
|
|
269
275
|
/**
|
|
@@ -30,8 +30,10 @@ import {markupWarn} from "../../markers";
|
|
|
30
30
|
import {TreePrinters} from "../../print";
|
|
31
31
|
import {
|
|
32
32
|
createDependencyRecipeAccumulator,
|
|
33
|
+
createLockFileEditor,
|
|
33
34
|
DependencyRecipeAccumulator,
|
|
34
|
-
|
|
35
|
+
getAllLockFileNames,
|
|
36
|
+
parseLockFileContent,
|
|
35
37
|
runInstallIfNeeded,
|
|
36
38
|
runInstallInTempDir,
|
|
37
39
|
storeInstallResult,
|
|
@@ -187,7 +189,8 @@ export class UpgradeTransitiveDependencyVersion extends ScanningRecipe<Accumulat
|
|
|
187
189
|
async editorWithData(acc: Accumulator): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
188
190
|
const recipe = this;
|
|
189
191
|
|
|
190
|
-
|
|
192
|
+
// Create JSON visitor that handles both package.json and JSON lock files
|
|
193
|
+
const jsonEditor = new class extends JsonVisitor<ExecutionContext> {
|
|
191
194
|
protected async visitDocument(doc: Json.Document, ctx: ExecutionContext): Promise<Json | undefined> {
|
|
192
195
|
const sourcePath = doc.sourcePath;
|
|
193
196
|
|
|
@@ -217,13 +220,13 @@ export class UpgradeTransitiveDependencyVersion extends ScanningRecipe<Accumulat
|
|
|
217
220
|
return updateNodeResolutionMarker(modifiedDoc, updateInfo, acc);
|
|
218
221
|
}
|
|
219
222
|
|
|
220
|
-
// Handle lock files
|
|
221
|
-
const
|
|
222
|
-
if (
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
sourcePath
|
|
226
|
-
}
|
|
223
|
+
// Handle JSON lock files (package-lock.json, bun.lock)
|
|
224
|
+
const lockFileName = path.basename(sourcePath);
|
|
225
|
+
if (getAllLockFileNames().includes(lockFileName)) {
|
|
226
|
+
const updatedLockContent = acc.updatedLockFiles.get(sourcePath);
|
|
227
|
+
if (updatedLockContent) {
|
|
228
|
+
return await parseLockFileContent(updatedLockContent, sourcePath, lockFileName) as Json.Document;
|
|
229
|
+
}
|
|
227
230
|
}
|
|
228
231
|
|
|
229
232
|
return doc;
|
|
@@ -271,6 +274,9 @@ export class UpgradeTransitiveDependencyVersion extends ScanningRecipe<Accumulat
|
|
|
271
274
|
};
|
|
272
275
|
}
|
|
273
276
|
};
|
|
277
|
+
|
|
278
|
+
// Return composite visitor that handles both JSON and YAML lock files
|
|
279
|
+
return createLockFileEditor(jsonEditor, acc);
|
|
274
280
|
}
|
|
275
281
|
|
|
276
282
|
/**
|