Skip to content

Commit f8d81f7

Browse files
authored
Merge branch 'main' into fix/issue-1156-prevent-cleartext-credentials
2 parents a69cf2f + 25f3d81 commit f8d81f7

44 files changed

Lines changed: 797 additions & 180 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

examples/simple-generator/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# @sap-ux/generator-simple-fe
22

3+
## 1.1.237
4+
5+
### Patch Changes
6+
7+
- @sap-ux/fiori-elements-writer@2.8.120
8+
- @sap-ux/fiori-freestyle-writer@2.5.90
9+
310
## 1.1.236
411

512
### Patch Changes

examples/simple-generator/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@sap-ux/generator-simple-fe",
3-
"version": "1.1.236",
3+
"version": "1.1.237",
44
"description": "Simple example of a yeoman generator for Fiori elements.",
55
"license": "Apache-2.0",
66
"private": true,

packages/app-config-writer/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# @sap-ux/app-config-writer
22

3+
## 0.6.134
4+
5+
### Patch Changes
6+
7+
- 75bed3b: fix(app-config-writer): convert eslint-config to be aware of plugins used by eslint-plugin-fiori-tools
8+
39
## 0.6.133
410

511
### Patch Changes

packages/app-config-writer/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@sap-ux/app-config-writer",
33
"description": "Add or update configuration for SAP Fiori tools application",
4-
"version": "0.6.133",
4+
"version": "0.6.134",
55
"repository": {
66
"type": "git",
77
"url": "https://github.com/SAP/open-ux-tools.git",

packages/app-config-writer/src/eslint-config/convert.ts

Lines changed: 123 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ export type EslintRcJson = {
2222
* Either a string that represents a single configuration or an array of strings that represents multiple configurations.
2323
*/
2424
extends?: string | string[];
25+
26+
/**
27+
* Glob patterns indicating the files the configuration applies to.
28+
* Not a standard legacy eslintrc property, but some configs include it and `@eslint/migrate-config` preserves it.
29+
*/
30+
files?: string | string[];
2531
};
2632

2733
const packageName = {
@@ -31,6 +37,22 @@ const packageName = {
3137
ESLINT_PLUGIN_FIORI_CUSTOM: 'eslint-plugin-fiori-custom'
3238
} as const;
3339

40+
/**
41+
* Extends entries that have a direct native ESLint 9 flat config equivalent and are also
42+
* spread natively inside `@sap-ux/eslint-plugin-fiori-tools`. These must be stripped from
43+
* the legacy config before migration to avoid the FlatCompat compat shim producing a different
44+
* rule source identity than the native registration inside the fiori-tools plugin, which would
45+
* cause ESLint to throw a rule source conflict error (e.g. "sources mismatch for no-irregular-whitespace").
46+
*/
47+
const NATIVE_FLAT_CONFIG_EXTENDS = ['eslint:recommended'] as const;
48+
49+
/**
50+
* Legacy `plugin:@typescript-eslint/*` extends entries that are stripped before migration
51+
* to prevent FlatCompat from wrapping them, which would cause rule source conflicts with
52+
* the native registrations inside `@sap-ux/eslint-plugin-fiori-tools`.
53+
*/
54+
const TYPESCRIPT_ESLINT_EXTENDS_PREFIX = 'plugin:@typescript-eslint/';
55+
3456
const MIGRATION_ERROR_TEXT = `Migration to eslint version 9 failed. Check if there are error messages above. You can also delete the existing eslint \`devDependency\` and run \`create add eslint\` to create a eslint.config.mjs file with the flat config where you can transfer your old eslint config manually.\` For more information, see [https://eslint.org/docs/latest/use/migrate-to-9.0.0#flat-config](Migrate to v9.x).`;
3557

3658
/**
@@ -58,8 +80,8 @@ export async function convertEslintConfig(
5880
throw new Error('The prerequisites are not met. For more information, see the log messages above.');
5981
}
6082

61-
await removeFioriToolsFromExistingConfig(basePath, fs, logger);
62-
await runMigrationCommand(basePath, fs);
83+
const strippedConfig = await removeFioriToolsFromExistingConfig(basePath, fs, logger);
84+
await runMigrationCommand(basePath, fs, strippedConfig);
6385
await injectFioriToolsIntoMigratedConfig(basePath, fs, options.config, logger);
6486
await updatePackageJson(basePath, fs, logger);
6587

@@ -107,15 +129,99 @@ async function checkPrerequisites(basePath: string, fs: Editor, logger?: ToolsLo
107129
}
108130

109131
/**
110-
* Removes all traces of the SAP Fiori tools plugin from the existing legacy eslint configuration,
111-
* so that the migration tool does not attempt to translate it and produce broken output.
132+
* Strips `eslint:recommended`, `plugin:@typescript-eslint/*`, and Fiori Tools plugin entries
133+
* from the `extends` field of a legacy eslint config object (mutates in place).
134+
* Other extends entries (e.g. third-party plugins) are left untouched for `@eslint/migrate-config`.
135+
*
136+
* @param eslintConfig - the legacy eslint config object to modify
137+
* @returns flags indicating which known entries were stripped (used to warn when a `files` scope is dropped)
138+
*/
139+
function stripNativeExtendsFromConfig(eslintConfig: EslintRcJson): { eslintRecommended: boolean; tsStripped: boolean } {
140+
const extendsArray =
141+
typeof eslintConfig.extends === 'string' ? [eslintConfig.extends] : (eslintConfig.extends ?? []);
142+
143+
let eslintRecommended = false;
144+
let tsStripped = false;
145+
const remaining: string[] = [];
146+
147+
for (const ext of extendsArray) {
148+
if (ext.includes(packageName.ESLINT_PLUGIN_FIORI_TOOLS)) {
149+
// drop — covered by @sap-ux/eslint-plugin-fiori-tools
150+
} else if (ext.startsWith(TYPESCRIPT_ESLINT_EXTENDS_PREFIX)) {
151+
tsStripped = true;
152+
} else if (NATIVE_FLAT_CONFIG_EXTENDS.includes(ext as (typeof NATIVE_FLAT_CONFIG_EXTENDS)[number])) {
153+
eslintRecommended = true;
154+
} else {
155+
remaining.push(ext);
156+
}
157+
}
158+
159+
if (extendsArray.length > 0) {
160+
if (remaining.length === 0) {
161+
delete eslintConfig.extends;
162+
} else if (typeof eslintConfig.extends === 'string') {
163+
eslintConfig.extends = remaining[0];
164+
} else {
165+
eslintConfig.extends = remaining;
166+
}
167+
}
168+
169+
return { eslintRecommended, tsStripped };
170+
}
171+
172+
/**
173+
* Logs a warning when native extends entries were stripped from the legacy config,
174+
* and additionally notes when a `files` scope cannot be automatically preserved.
175+
*
176+
* @param files - the `files` property from the legacy config, if any
177+
* @param eslintRecommended - whether `eslint:recommended` was stripped
178+
* @param tsStripped - whether any `plugin:@typescript-eslint/*` entries were stripped
179+
* @param logger - logger to report info to the user
180+
*/
181+
function warnIfFileScopeDropped(
182+
files: string | string[] | undefined,
183+
eslintRecommended: boolean,
184+
tsStripped: boolean,
185+
logger?: ToolsLogger
186+
): void {
187+
if (!eslintRecommended && !tsStripped) {
188+
return;
189+
}
190+
const removed: string[] = [];
191+
if (eslintRecommended) {
192+
removed.push("'eslint:recommended'");
193+
}
194+
if (tsStripped) {
195+
removed.push("'plugin:@typescript-eslint/*'");
196+
}
197+
const baseMessage = `${removed.join(' and ')} ${removed.length > 1 ? 'were' : 'was'} removed from the legacy config and will not be re-injected. Its rules are already covered by '@sap-ux/eslint-plugin-fiori-tools', so no manual re-addition is needed.`;
198+
const fileScopeMessage = files
199+
? ` The legacy config had a 'files' scope (${JSON.stringify(files)}) that cannot be automatically preserved.`
200+
: '';
201+
logger?.warn(baseMessage + fileScopeMessage);
202+
}
203+
204+
/**
205+
* Removes all traces of the SAP Fiori tools plugin
206+
* (e.g. `eslint:recommended`, `plugin:@typescript-eslint/recommended`) from the existing legacy
207+
* eslint configuration, so that the migration tool does not wrap them in a FlatCompat compat shim
208+
* that would conflict with the native rule registrations inside `@sap-ux/eslint-plugin-fiori-tools`.
209+
*
210+
* If the legacy config had a `files` property together with `eslint:recommended` or
211+
* `plugin:@typescript-eslint/*` entries, a warning is logged because that file scope cannot be
212+
* automatically preserved — the converted project uses `@sap-ux/eslint-plugin-fiori-tools` which
213+
* already applies the equivalent rules scoped to the webapp directory.
214+
*
215+
* The modified config is returned as a serialized JSON string and is not written back to
216+
* mem-fs, so the original legacy config file is never staged for a disk write.
112217
*
113218
* @param basePath - base path to be used for the conversion
114219
* @param fs - file system reference
115220
* @param logger - logger to report info to the user
221+
* @returns the stripped config serialized as a JSON string, ready to be passed to the migration command
116222
* @throws {Error} if the existing .eslintrc.json file is not a valid JSON object
117223
*/
118-
async function removeFioriToolsFromExistingConfig(basePath: string, fs: Editor, logger?: ToolsLogger): Promise<void> {
224+
async function removeFioriToolsFromExistingConfig(basePath: string, fs: Editor, logger?: ToolsLogger): Promise<string> {
119225
const eslintrcJsonPath = join(basePath, '.eslintrc.json');
120226
const eslintrcPath = join(basePath, '.eslintrc');
121227
const configPath = fs.exists(eslintrcJsonPath) ? eslintrcJsonPath : eslintrcPath;
@@ -135,30 +241,19 @@ async function removeFioriToolsFromExistingConfig(basePath: string, fs: Editor,
135241
}
136242
}
137243

138-
// Remove fiori-tools entries from extends
139-
if (typeof eslintConfig.extends === 'string') {
140-
if (eslintConfig.extends.includes(packageName.ESLINT_PLUGIN_FIORI_TOOLS)) {
141-
delete eslintConfig.extends;
142-
}
143-
} else if (Array.isArray(eslintConfig.extends)) {
144-
eslintConfig.extends = eslintConfig.extends.filter(
145-
(ext) => !ext.includes(packageName.ESLINT_PLUGIN_FIORI_TOOLS)
146-
);
147-
if (eslintConfig.extends.length === 0) {
148-
delete eslintConfig.extends;
149-
}
150-
}
244+
const { eslintRecommended, tsStripped } = stripNativeExtendsFromConfig(eslintConfig);
245+
warnIfFileScopeDropped(eslintConfig.files, eslintRecommended, tsStripped, logger);
151246

152-
fs.writeJSON(configPath, eslintConfig);
153247
logger?.debug(`Removed SAP Fiori tools plugin references from ${configPath}`);
248+
return JSON.stringify(eslintConfig, null, 2);
154249
}
155250

156251
/**
157252
* Injects the SAP Fiori tools plugin import and config spread into the migrated flat-config file.
158253
*
159254
* After the migration tool produces `eslint.config.mjs`, this function:
160255
* 1. Prepends `import fioriTools from '@sap-ux/eslint-plugin-fiori-tools';` to the imports section.
161-
* 2. Inserts `...fioriTools.configs.recommended` (or the requested config variant) as the last
256+
* 2. Inserts `...fioriTools.configs['recommended']` (or the requested config variant) as the last
162257
* element of the exported config array, right before the closing `]);`.
163258
*
164259
* @param basePath - base path of the project
@@ -185,12 +280,11 @@ async function injectFioriToolsIntoMigratedConfig(
185280
throw new Error(
186281
'Unexpected format of migrated eslint config. Could not inject the SAP Fiori tools plugin configuration.'
187282
);
188-
} else {
189-
content =
190-
content.slice(0, lastBracketIndex) +
191-
`,\n ...fioriTools.configs['${config}'],\n` +
192-
content.slice(lastBracketIndex);
193283
}
284+
content =
285+
content.slice(0, lastBracketIndex) +
286+
`,\n ...fioriTools.configs['${config}'],\n` +
287+
content.slice(lastBracketIndex);
194288

195289
fs.write(migratedConfigPath, content);
196290
logger?.debug(`Injected SAP Fiori tools plugin into ${migratedConfigPath}`);
@@ -202,21 +296,19 @@ async function injectFioriToolsIntoMigratedConfig(
202296
*
203297
* @param basePath - base path to be used for the conversion
204298
* @param fs - file system reference
299+
* @param strippedConfigContent - the stripped legacy eslint config content as a JSON string
205300
* @returns a promise that resolves when the migration command finishes successfully, or rejects if the command fails
206301
*/
207-
async function runMigrationCommand(basePath: string, fs: Editor): Promise<void> {
302+
async function runMigrationCommand(basePath: string, fs: Editor, strippedConfigContent: string): Promise<void> {
208303
const tempDir = mkdtempSync(join(tmpdir(), 'eslint-migration-'));
209304

210305
try {
211306
// 1. Copy necessary files to temp directory
212307
const eslintrcJsonPath = join(basePath, '.eslintrc.json');
213-
const eslintrcPath = join(basePath, '.eslintrc');
214-
const configPath = fs.exists(eslintrcJsonPath) ? eslintrcJsonPath : eslintrcPath;
215308
const configFileName = fs.exists(eslintrcJsonPath) ? '.eslintrc.json' : '.eslintrc';
216309

217-
// Read from mem-fs (which has the modified content) and write to temp directory
218-
const eslintrcContent = fs.read(configPath);
219-
writeFileSync(join(tempDir, configFileName), eslintrcContent, 'utf-8');
310+
// Write the already-stripped config content (never staged in mem-fs) to temp directory
311+
writeFileSync(join(tempDir, configFileName), strippedConfigContent, 'utf-8');
220312

221313
const eslintignorePath = join(basePath, '.eslintignore');
222314
if (existsSync(eslintignorePath)) {

0 commit comments

Comments
 (0)