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
4 changes: 2 additions & 2 deletions internal/benchmark/lib/ConfigurationLoader.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import yaml from "js-yaml";
import {load as yamlLoad} from "js-yaml";
import Configuration from "./benchmark/Configuration.js";

/**
Expand Down Expand Up @@ -37,7 +37,7 @@ export default class ConfigurationLoader {

let parsedYaml;
try {
parsedYaml = yaml.load(fileContents);
parsedYaml = yamlLoad(fileContents);
} catch (error) {
throw new Error(`Failed to parse YAML configuration: ${error.message}`);
}
Expand Down
2 changes: 1 addition & 1 deletion internal/benchmark/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@
},
"devDependencies": {
"eslint": "^10.11.0",
"js-yaml": "^4.3.1"
"js-yaml": "^5.4.2"
}
}
74 changes: 71 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions packages/cli/lib/cli/commands/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ initCommand.handler = async function() {
const {default: init} = await import("../../init/init.js");
const {default: path} = await import("node:path");
const {writeFile} = await import("node:fs/promises");
const {default: jsYaml} = await import("js-yaml");
const {dump: jsYamlDump} = await import("js-yaml");

const yamlPath = path.resolve("./ui5.yaml");
if (await exists(yamlPath)) {
throw new Error("Initialization not possible: ui5.yaml already exists");
}

const projectConfig = await init();
const yaml = jsYaml.dump(projectConfig, {quotingType: `"`});
const yaml = jsYamlDump(projectConfig, {quoteStyle: "double"});

await writeFile(yamlPath, yaml);
process.stdout.write(`Wrote ui5.yaml to ${yamlPath}:`);
Expand Down
8 changes: 8 additions & 0 deletions packages/cli/lib/framework/ui5YamlSchema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import {CORE_SCHEMA, mergeTag, timestampTag} from "js-yaml";

// js-yaml v5 defaults to CORE_SCHEMA (YAML 1.2), which drops merge key ("<<:")
// and implicit timestamp support that were present in v4's DEFAULT_SCHEMA.
// Restore both by extending CORE_SCHEMA with the two tags.
// YAML11_SCHEMA is deliberately avoided: it additionally changes scalar resolution
// (yes/no/on/off → boolean, 0-prefixed numbers → octal).
export default CORE_SCHEMA.withTags([mergeTag, timestampTag]);
6 changes: 4 additions & 2 deletions packages/cli/lib/framework/updateYaml.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import path from "node:path";
import {readFile, writeFile} from "node:fs/promises";
import {loadAll, dump} from "js-yaml";
import ui5YamlSchema from "./ui5YamlSchema.js";
import {fromYaml, getPosition, getValue, getKind} from "data-with-position";
import {getLogger} from "@ui5/logger";

const log = getLogger("cli:framework:updateYaml");

function getProjectYamlDocument({project, configFile, configPath}) {
const configs = loadAll(configFile, undefined, {
filename: configPath
filename: configPath,
schema: ui5YamlSchema
});

const projectDocumentIndex = configs.findIndex((config) => {
Expand Down Expand Up @@ -277,7 +279,7 @@ export default async function({project, configPathOverride, data}) {

// Validate content before writing
try {
loadAll(adoptedYaml);
loadAll(adoptedYaml, undefined, {schema: ui5YamlSchema});
} catch (err) {
const error = new Error("Failed to update YAML file: " + err.message);
error.name = "FrameworkUpdateYamlFailed";
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
"chalk": "^6.0.0",
"data-with-position": "^0.5.0",
"import-local": "^3.2.0",
"js-yaml": "^4.3.1",
"js-yaml": "^5.4.2",
"open": "^11.0.4",
"pretty-hrtime": "^1.0.3",
"semver": "^7.8.5",
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/test/lib/cli/commands/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ test.serial("Writes ui5.yaml to fs", async (t) => {

t.is(fsWriteFileStub.getCall(0).args[0], ui5YamlPath, "Passes yaml path to write the yaml file to");
t.is(fsWriteFileStub.getCall(0).args[1], ui5Yaml, "Passes yaml content to write to fs");
t.deepEqual(jsyamlDumpStub.getCall(0).args[1], {quotingType: `"`}, "Enforce usage of double quotes in yaml files");
t.deepEqual(jsyamlDumpStub.getCall(0).args[1],
{quoteStyle: "double"}, "Enforce usage of double quotes in yaml files");
});

test.serial("Error: throws if ui5.yaml already exists", async (t) => {
Expand Down
12 changes: 6 additions & 6 deletions packages/project/lib/graph/Module.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import fs from "graceful-fs";
import path from "node:path";
import {promisify} from "node:util";
const readFile = promisify(fs.readFile);
import jsyaml from "js-yaml";
import {loadAll as jsyamlLoadAll} from "js-yaml";
import ui5YamlSchema from "./helpers/ui5YamlSchema.js";
import {createReader} from "@ui5/fs/resourceFactory";
import Specification from "../specifications/Specification.js";
import {validate} from "../validation/validator.js";
Expand Down Expand Up @@ -318,12 +319,11 @@ class Module {
let configs;

try {
// Using loadAll with DEFAULT_SAFE_SCHEMA instead of safeLoadAll to pass "filename".
// safeLoadAll doesn't handle its parameters properly.
// See https://github.com/nodeca/js-yaml/issues/456 and https://github.com/nodeca/js-yaml/pull/381
configs = jsyaml.loadAll(configFile, undefined, {
// Use ui5YamlSchema (CORE_SCHEMA + mergeTag) to preserve v4 DEFAULT_SCHEMA
// behaviour.
configs = jsyamlLoadAll(configFile, undefined, {
Comment thread
d3xter666 marked this conversation as resolved.
filename: configPath,
schema: jsyaml.DEFAULT_SAFE_SCHEMA
schema: ui5YamlSchema
});
} catch (err) {
if (err.name === "YAMLException") {
Expand Down
4 changes: 3 additions & 1 deletion packages/project/lib/graph/graph.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,14 +199,16 @@ const utils = {
const {promisify} = await import("util");
const readFile = promisify(fs.readFile);
const parseYaml =(await import("js-yaml")).load;
const {default: ui5YamlSchema} = await import("./helpers/ui5YamlSchema.js");

filePath = utils.resolveConfigPath(cwd, filePath);

let dependencyTree;
try {
const contents = await readFile(filePath, {encoding: "utf-8"});
dependencyTree = parseYaml(contents, {
filename: filePath
filename: filePath,
schema: ui5YamlSchema
});
utils.resolveProjectPaths(cwd, dependencyTree);
} catch (err) {
Expand Down
6 changes: 4 additions & 2 deletions packages/project/lib/graph/helpers/createWorkspace.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ async function readWorkspaceConfigFile(filePath) {
} = await import("graceful-fs");
const {promisify} = await import("node:util");
const readFile = promisify(fs.readFile);
const jsyaml = await import("js-yaml");
const {loadAll: jsyamlLoadAll} = await import("js-yaml");
const {default: ui5YamlSchema} = await import("./ui5YamlSchema.js");

let fileContent;
try {
Expand All @@ -87,8 +88,9 @@ async function readWorkspaceConfigFile(filePath) {
}
let configs;
try {
configs = jsyaml.loadAll(fileContent, undefined, {
configs = jsyamlLoadAll(fileContent, undefined, {
filename: filePath,
schema: ui5YamlSchema
});
} catch (err) {
throw new Error(`Failed to parse workspace configuration at ${filePath}\nError: ${err.message}`);
Expand Down
8 changes: 8 additions & 0 deletions packages/project/lib/graph/helpers/ui5YamlSchema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import {CORE_SCHEMA, mergeTag, timestampTag} from "js-yaml";

// js-yaml v5 defaults to CORE_SCHEMA (YAML 1.2), which drops merge key ("<<:")
// and implicit timestamp support that were present in v4's DEFAULT_SCHEMA.
// Restore both by extending CORE_SCHEMA with the two tags.
// YAML11_SCHEMA is deliberately avoided: it additionally changes scalar resolution
// (yes/no/on/off → boolean, 0-prefixed numbers → octal).
export default CORE_SCHEMA.withTags([mergeTag, timestampTag]);
2 changes: 1 addition & 1 deletion packages/project/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
"escape-string-regexp": "^5.0.0",
"globby": "^16.2.4",
"graceful-fs": "^4.2.11",
"js-yaml": "^4.3.1",
"js-yaml": "^5.4.2",
"lockfile": "^1.0.4",
"make-fetch-happen": "^16.0.1",
"micromatch": "^4.0.8",
Expand Down
33 changes: 33 additions & 0 deletions packages/project/test/fixtures/application.a/ui5-merge-keys.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
specVersion: "4.0"
metadata:
name: application.a
type: application
# Shared configuration anchor — used to verify that merge keys (<<:) are parsed
# correctly by js-yaml v5 with the ui5YamlSchema (CORE_SCHEMA + mergeTag).
server:
customMiddleware:
- name: middleware-base
afterMiddleware: compression
configuration: &sharedCfg
debug: true
port: 3000
timeout: 30
- name: middleware-extended
afterMiddleware: middleware-base
configuration:
<<: *sharedCfg
timeout: 60
builder:
customTasks:
- name: task-base
afterTask: replaceVersion
configuration: &taskCfg
debug: true
minify: false
- name: task-extended
afterTask: task-base
configuration:
<<: *taskCfg
minify: true
extraKey: added-by-local-block
34 changes: 34 additions & 0 deletions packages/project/test/lib/graph/Module.js
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,40 @@ test("Corrupt configuration in file", async (t) => {
"Threw with parsing error");
});

test("Merge keys (<<:) in ui5.yaml are resolved and not left as literal keys", async (t) => {
// Regression test: js-yaml v5 CORE_SCHEMA drops merge key ("<<:") and timestamp
// support from v4's DEFAULT_SCHEMA. ui5YamlSchema restores both with mergeTag and
// timestampTag on top of CORE_SCHEMA.
const ui5Module = new Module({
id: "application.a.id",
version: "1.0.0",
modulePath: applicationAPath,
configPath: "ui5-merge-keys.yaml"
});
const {project} = await ui5Module.getSpecifications();
const cfg = project.getConfig();

// --- middleware: anchor defined on middleware-base, merged into middleware-extended ---
const mwExtended = cfg.server.customMiddleware[1].configuration;
// Keys from anchor must be merged in
t.is(mwExtended.debug, true, "Anchor key 'debug' merged into middleware configuration");
t.is(mwExtended.port, 3000, "Anchor key 'port' merged into middleware configuration");
// Local key overrides anchor value
t.is(mwExtended.timeout, 60, "Local 'timeout' overrides merged anchor value");
// No literal "<<" key must remain in the parsed object
t.false("<<" in mwExtended, "Merge key '<<' is resolved, not left as a literal mapping key");

// --- tasks: anchor defined on task-base, merged into task-extended ---
const taskExtended = cfg.builder.customTasks[1].configuration;
t.is(taskExtended.debug, true, "Anchor key 'debug' merged into task configuration");
// Local key overrides anchor value
t.true(taskExtended.minify, "Local 'minify: true' overrides merged anchor value 'false'");
// Extra key added in the local block must also be present
t.is(taskExtended.extraKey, "added-by-local-block", "Additional local key is preserved");
t.false("<<" in taskExtended, "Merge key '<<' is resolved, not left as a literal mapping key");
});


test("Empty configuration in file", async (t) => {
const ui5Module = new Module({
id: "application.a.id",
Expand Down
Loading