Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 143 additions & 5 deletions bundles/@yarnpkg/plugin-catalogs.js
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like the bundle file is not minified. Could you please check? (Unlike yarn build, test creates a non-minified bundle.)

Large diffs are not rendered by default.

6 changes: 2 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,19 @@
"@yarnpkg/cli": "^4.8.0",
"@yarnpkg/core": "^4.3.0",
"@yarnpkg/fslib": "^3.1.1",
"@yarnpkg/parsers": "^3.0.2",
"@yarnpkg/plugin-essentials": "^4.3.2",
"@yarnpkg/plugin-git": "^3.1.1",
"@yarnpkg/plugin-pack": "^4.0.1",
"chalk": "^5.4.1",
"clipanion": "^4.0.0-rc.2",
"picomatch": "^4.0.2"
"picomatch": "^4.0.2",
"yaml": "^2.8.1"
},
"devDependencies": {
"@types/js-yaml": "^4.0.9",
"@types/node": "^22.0.0",
"@types/picomatch": "^4",
"@types/tmp": "^0.2.6",
"@yarnpkg/builder": "^4.2.0",
"js-yaml": "^4.1.0",
"rimraf": "5.0.0",
"tmp-promise": "^3.0.3",
"typescript": "^5.5.2",
Expand Down
94 changes: 94 additions & 0 deletions sources/__tests__/apply-command.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,4 +328,98 @@ describe("catalogs apply command", () => {
const yarnrc = await workspace.readYarnrc();
expect(yarnrc.catalogs).toBeUndefined();
});

it("should preserve comments and formatting when updating catalogs", async () => {
workspace = await createTestWorkspace();

const existingYarnrc = await workspace.readYarnrcRaw();
await workspace.writeYarnrcRaw(`${existingYarnrc}
# Yarn configuration
nodeLinker: node-modules # inline comment

# Registry configuration
npmRegistryServer: https://registry.npmjs.org

# Catalog configuration
catalogs:
stable:
react: npm:17.0.0
`);

await workspace.writeCatalogsYml({
list: {
stable: {
react: "npm:18.0.0",
},
},
});

await workspace.yarn.catalogs.apply();

const rawContent = await workspace.readYarnrcRaw();
expect(rawContent).toContain("# Yarn configuration");
expect(rawContent).toContain("# inline comment");
expect(rawContent).toContain("# Registry configuration");
expect(rawContent).toContain("# Catalog configuration");
});

it("should preserve comments when updating both root and named catalogs", async () => {
workspace = await createTestWorkspace();

const existingYarnrc = await workspace.readYarnrcRaw();
await workspace.writeYarnrcRaw(`${existingYarnrc}
# Root catalog
catalog:
lodash: npm:4.0.0

# Named catalogs
catalogs:
stable:
react: npm:17.0.0
`);

await workspace.writeCatalogsYml({
list: {
root: {
lodash: "npm:4.17.21",
},
stable: {
react: "npm:18.0.0",
},
},
});

await workspace.yarn.catalogs.apply();

const rawContent = await workspace.readYarnrcRaw();
expect(rawContent).toContain("# Root catalog");
expect(rawContent).toContain("# Named catalogs");
});

it("should preserve comments when removing catalogs", async () => {
workspace = await createTestWorkspace();

const existingYarnrc = await workspace.readYarnrcRaw();
await workspace.writeYarnrcRaw(`${existingYarnrc}
# Config comment
nodeLinker: node-modules

catalogs:
stable:
react: npm:18.0.0

# End comment
`);

await workspace.writeCatalogsYml({
list: {},
});

await workspace.yarn.catalogs.apply();

const rawContent = await workspace.readYarnrcRaw();
expect(rawContent).toContain("# Config comment");
expect(rawContent).toContain("# End comment");
expect(rawContent).not.toContain("catalogs:");
});
});
22 changes: 18 additions & 4 deletions sources/__tests__/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { execFile } from "node:child_process";
import { promisify } from "node:util";
import { type PortablePath, npath, ppath, xfs } from "@yarnpkg/fslib";
import { dump as yamlDump, load as yamlLoad } from "js-yaml";
import { parse as yamlParse, stringify as yamlStringify } from "yaml";
import { dir as tmpDir } from "tmp-promise";

const execFileAsync = promisify(execFile);
Expand All @@ -12,7 +12,9 @@ export interface TestWorkspace {
writeJson: (path: string, content: unknown) => Promise<void>;
readPackageJson: () => Promise<any>;
readYarnrc: () => Promise<any>;
readYarnrcRaw: () => Promise<string>;
writeYarnrc: (content: unknown) => Promise<void>;
writeYarnrcRaw: (content: string) => Promise<void>;
writeCatalogsYml: (content: unknown) => Promise<void>;
yarn: {
(args: string[]): Promise<{ stdout: string; stderr: string }>;
Expand Down Expand Up @@ -76,7 +78,7 @@ export async function createTestWorkspace(): Promise<TestWorkspace> {
.catch(() => "");
await xfs.writeFilePromise(
yarnrcPath,
`${existingContent}\n${yamlDump(content)}`,
`${existingContent}\n${yamlStringify(content)}`,
);
};

Expand All @@ -85,7 +87,7 @@ export async function createTestWorkspace(): Promise<TestWorkspace> {
portablePath,
"catalogs.yml" as PortablePath,
);
await xfs.writeFilePromise(catalogsYmlPath, yamlDump(content));
await xfs.writeFilePromise(catalogsYmlPath, yamlStringify(content));
};

const readPackageJson = async () => {
Expand All @@ -97,7 +99,17 @@ export async function createTestWorkspace(): Promise<TestWorkspace> {
const readYarnrc = async () => {
const yarnrcPath = ppath.join(portablePath, ".yarnrc.yml" as PortablePath);
const content = await xfs.readFilePromise(yarnrcPath, "utf8");
return yamlLoad(content);
return yamlParse(content);
};

const readYarnrcRaw = async () => {
const yarnrcPath = ppath.join(portablePath, ".yarnrc.yml" as PortablePath);
return await xfs.readFilePromise(yarnrcPath, "utf8");
};

const writeYarnrcRaw = async (content: string) => {
const yarnrcPath = ppath.join(portablePath, ".yarnrc.yml" as PortablePath);
await xfs.writeFilePromise(yarnrcPath, content);
};

return {
Expand All @@ -106,7 +118,9 @@ export async function createTestWorkspace(): Promise<TestWorkspace> {
writeJson,
readPackageJson,
readYarnrc,
readYarnrcRaw,
writeYarnrc: writeYaml,
writeYarnrcRaw,
writeCatalogsYml,
yarn,
};
Expand Down
2 changes: 1 addition & 1 deletion sources/__tests__/validation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ describe("validation", () => {

const { stderr } = await workspace.yarn.add("[email protected]");
expect(stderr).toContain("react");
expect(stderr).toContain("beta, stable");
expect(stderr).toContain("stable, beta");
expect(stderr).toContain("react@catalog:");
});

Expand Down
9 changes: 5 additions & 4 deletions sources/commands/apply.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { BaseCommand } from "@yarnpkg/cli";
import { Configuration, Project, StreamReport } from "@yarnpkg/core";
import { type Filename, type PortablePath, ppath, xfs } from "@yarnpkg/fslib";
import { parseSyml, stringifySyml } from "@yarnpkg/parsers";
import chalk from "chalk";
import { Command, Option } from "clipanion";
import { parseDocument, stringify } from "yaml";
import { configReader } from "../configuration";

export class ApplyCommand extends BaseCommand {
Expand Down Expand Up @@ -140,7 +140,8 @@ export async function readExistingYarnrc(
}

const content = await xfs.readFilePromise(yarnrcPath, "utf8");
return (parseSyml(content) as Record<string, unknown>) || {};
const doc = parseDocument(content);
return (doc.toJSON() as Record<string, unknown>) || {};
}

/**
Expand All @@ -167,8 +168,8 @@ export function checkForChanges(
newConfig.catalogs = undefined;
}

const oldContent = stringifySyml(existingConfig);
const newContent = stringifySyml(newConfig);
const oldContent = stringify(existingConfig);
const newContent = stringify(newConfig);

return oldContent !== newContent;
}
Expand Down
22 changes: 11 additions & 11 deletions sources/configuration/reader.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { Project, Workspace } from "@yarnpkg/core";
import { structUtils } from "@yarnpkg/core";
import { type Filename, type PortablePath, ppath, xfs } from "@yarnpkg/fslib";
import { parseSyml, stringifySyml } from "@yarnpkg/parsers";
import { isMatch } from "picomatch";
import { parse, parseDocument } from "yaml";
import { ROOT_ALIAS_GROUP } from "../constants";
import { CatalogConfigurationError } from "../errors";
import type { CatalogsConfiguration } from "./types";
Expand Down Expand Up @@ -43,7 +43,7 @@ export class CatalogsConfigurationReader {
}

const content = await xfs.readFilePromise(catalogsYmlPath, "utf8");
const parsed: unknown = parseSyml(content);
const parsed: unknown = parse(content);

if (!isValidCatalogsYml(parsed)) {
throw new CatalogConfigurationError(
Expand Down Expand Up @@ -158,26 +158,26 @@ export class CatalogsConfigurationReader {
".yarnrc.yml" as Filename as PortablePath,
);

let existingConfig: Record<string, unknown> = {};
let content = "";
if (await xfs.existsPromise(yarnrcPath)) {
const content = await xfs.readFilePromise(yarnrcPath, "utf8");
existingConfig = (parseSyml(content) as Record<string, unknown>) || {};
content = await xfs.readFilePromise(yarnrcPath, "utf8");
}

const doc = parseDocument(content);

if (catalogs.root && Object.keys(catalogs.root).length > 0) {
existingConfig.catalog = catalogs.root;
doc.set("catalog", doc.createNode(catalogs.root));
} else {
existingConfig.catalog = undefined;
doc.delete("catalog");
}

if (Object.keys(catalogs.named).length > 0) {
existingConfig.catalogs = catalogs.named;
doc.set("catalogs", doc.createNode(catalogs.named));
} else {
existingConfig.catalogs = undefined;
doc.delete("catalogs");
}

const newContent = stringifySyml(existingConfig);
await xfs.writeFilePromise(yarnrcPath, newContent);
await xfs.writeFilePromise(yarnrcPath, doc.toString());
}

/**
Expand Down
38 changes: 10 additions & 28 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -610,13 +610,6 @@ __metadata:
languageName: node
linkType: hard

"@types/js-yaml@npm:^4.0.9":
version: 4.0.9
resolution: "@types/js-yaml@npm:4.0.9"
checksum: 10c0/24de857aa8d61526bbfbbaa383aa538283ad17363fcd5bb5148e2c7f604547db36646440e739d78241ed008702a8920665d1add5618687b6743858fae00da211
languageName: node
linkType: hard

"@types/keyv@npm:^3.1.4":
version: 3.1.4
resolution: "@types/keyv@npm:3.1.4"
Expand Down Expand Up @@ -1964,13 +1957,6 @@ __metadata:
languageName: node
linkType: hard

"argparse@npm:^2.0.1":
version: 2.0.1
resolution: "argparse@npm:2.0.1"
checksum: 10c0/c5640c2d89045371c7cedd6a70212a04e360fd34d6edeae32f6952c63949e3525ea77dbec0289d8213a99bbaeab5abfa860b5c12cf88a2e6cf8106e90dd27a7e
languageName: node
linkType: hard

"assertion-error@npm:^1.1.0":
version: 1.1.0
resolution: "assertion-error@npm:1.1.0"
Expand Down Expand Up @@ -3021,17 +3007,6 @@ __metadata:
languageName: node
linkType: hard

"js-yaml@npm:^4.1.0":
version: 4.1.0
resolution: "js-yaml@npm:4.1.0"
dependencies:
argparse: "npm:^2.0.1"
bin:
js-yaml: bin/js-yaml.js
checksum: 10c0/184a24b4eaacfce40ad9074c64fd42ac83cf74d8c8cd137718d456ced75051229e5061b8633c3366b8aada17945a7a356b337828c19da92b51ae62126575018f
languageName: node
linkType: hard

"jsbn@npm:1.1.0":
version: 1.1.0
resolution: "jsbn@npm:1.1.0"
Expand Down Expand Up @@ -4570,30 +4545,37 @@ __metadata:
languageName: node
linkType: hard

"yaml@npm:^2.8.1":
version: 2.8.1
resolution: "yaml@npm:2.8.1"
bin:
yaml: bin.mjs
checksum: 10c0/7c587be00d9303d2ae1566e03bc5bc7fe978ba0d9bf39cc418c3139d37929dfcb93a230d9749f2cb578b6aa5d9ebebc322415e4b653cb83acd8bc0bc321707f3
languageName: node
linkType: hard

"yarn-plugin-catalogs@workspace:.":
version: 0.0.0-use.local
resolution: "yarn-plugin-catalogs@workspace:."
dependencies:
"@types/js-yaml": "npm:^4.0.9"
"@types/node": "npm:^22.0.0"
"@types/picomatch": "npm:^4"
"@types/tmp": "npm:^0.2.6"
"@yarnpkg/builder": "npm:^4.2.0"
"@yarnpkg/cli": "npm:^4.8.0"
"@yarnpkg/core": "npm:^4.3.0"
"@yarnpkg/fslib": "npm:^3.1.1"
"@yarnpkg/parsers": "npm:^3.0.2"
"@yarnpkg/plugin-essentials": "npm:^4.3.2"
"@yarnpkg/plugin-git": "npm:^3.1.1"
"@yarnpkg/plugin-pack": "npm:^4.0.1"
chalk: "npm:^5.4.1"
clipanion: "npm:^4.0.0-rc.2"
js-yaml: "npm:^4.1.0"
picomatch: "npm:^4.0.2"
rimraf: "npm:5.0.0"
tmp-promise: "npm:^3.0.3"
typescript: "npm:^5.5.2"
vitest: "npm:^1.3.1"
yaml: "npm:^2.8.1"
languageName: unknown
linkType: soft

Expand Down