From afd7741de259aa68dd0af8281aab4259501dc0c7 Mon Sep 17 00:00:00 2001 From: Momo Kornher Date: Thu, 19 Feb 2026 19:25:13 +0000 Subject: [PATCH 1/7] feat(toolkit-lib): filter notices by guessed CDK app language\n\nAdd guessLanguage utility that detects the CDK app language from\nproject files (package.json, requirements.txt, pom.xml, etc.).\nNotices can now target specific languages using component names\nlike language-typescript or language-python with version '*'. --- .../toolkit-lib/lib/api/notices/filter.ts | 27 ++++++- .../toolkit-lib/lib/api/notices/notices.ts | 16 +++- .../toolkit-lib/lib/util/directories.ts | 25 ++++++ .../toolkit-lib/lib/util/guess-language.ts | 63 +++++++++++++++ .../test/api/guess-language.test.ts | 70 ++++++++++++++++ .../toolkit-lib/test/api/notices.test.ts | 80 +++++++++++++++++++ packages/aws-cdk/lib/cli/cli.ts | 1 + 7 files changed, 279 insertions(+), 3 deletions(-) create mode 100644 packages/@aws-cdk/toolkit-lib/lib/util/guess-language.ts create mode 100644 packages/@aws-cdk/toolkit-lib/test/api/guess-language.test.ts diff --git a/packages/@aws-cdk/toolkit-lib/lib/api/notices/filter.ts b/packages/@aws-cdk/toolkit-lib/lib/api/notices/filter.ts index 95e0aefd0..38ce9a748 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/api/notices/filter.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/api/notices/filter.ts @@ -1,4 +1,5 @@ import * as semver from 'semver'; +import { languageDisplayName } from '../../util/guess-language'; import type { IoHelper } from '../io/private'; import type { ConstructTreeNode } from '../tree'; import { loadTreeFromDir } from '../tree'; @@ -11,8 +12,15 @@ function normalizeComponents(xs: Array): Component[][] return xs.map(x => Array.isArray(x) ? x : [x]); } +function renderComponent(c: Component): string { + if (c.name.startsWith('language-')) { + return `${languageDisplayName(c.name.slice('language-'.length))} apps`; + } + return `${c.name}: ${c.version}`; +} + function renderConjunction(xs: Component[]): string { - return xs.map(c => `${c.name}: ${c.version}`).join(' AND '); + return xs.map(renderComponent).join(' AND '); } interface ActualComponent { @@ -40,7 +48,7 @@ interface ActualComponent { readonly dynamicName?: string; /** - * If matched, what we should put in the set of dynamic values insstead of the version. + * If matched, what we should put in the set of dynamic values instead of the version. * * Only used if `dynamicName` is set; by default we will add the actual version * of the component. @@ -55,6 +63,13 @@ export interface NoticesFilterFilterOptions { readonly cliVersion: string; readonly outDir: string; readonly bootstrappedEnvironments: BootstrappedEnvironment[]; + + /** + * The guessed language of the CDK app. + * + * @default - no language component is added + */ + readonly language?: string; } export class NoticesFilter { @@ -111,6 +126,14 @@ export class NoticesFilter { // Bootstrap environments ...bootstrappedEnvironments, + + // Language + ...(options.language ? [{ + name: `language-${options.language}`, + version: '0.0.0', + dynamicName: 'LANGUAGE', + dynamicValue: languageDisplayName(options.language), + }] : []), ]; } diff --git a/packages/@aws-cdk/toolkit-lib/lib/api/notices/notices.ts b/packages/@aws-cdk/toolkit-lib/lib/api/notices/notices.ts index 5afb83df6..5d3c3fa79 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/api/notices/notices.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/api/notices/notices.ts @@ -8,6 +8,7 @@ import type { FilteredNotice } from './filter'; import { NoticesFilter } from './filter'; import type { BootstrappedEnvironment, Notice, NoticeDataSource } from './types'; import { WebsiteNoticeDataSource } from './web-data-source'; +import { guessLanguage } from '../../util/guess-language'; import type { IoHelper } from '../io/private'; import { IO, asIoHelper } from '../io/private'; @@ -40,6 +41,15 @@ export interface NoticesProps { */ readonly output?: string; + /** + * The directory containing cdk.json (the project root). + * + * Used to guess the CDK app language for filtering notices. + * + * @default process.cwd() + */ + readonly projectDir?: string; + /** * Options for the HTTPS requests made by Notices */ @@ -113,6 +123,7 @@ export class Notices { private readonly context: Context; private readonly output: string; + private readonly projectDir: string; private readonly acknowledgedIssueNumbers: Set; private readonly httpOptions: NoticesHttpOptions; private readonly ioHelper: IoHelper; @@ -127,6 +138,7 @@ export class Notices { this.context = props.context; this.acknowledgedIssueNumbers = new Set(this.context.get('acknowledged-issue-numbers') ?? []); this.output = props.output ?? 'cdk.out'; + this.projectDir = props.projectDir ?? process.cwd(); this.httpOptions = props.httpOptions ?? {}; this.ioHelper = asIoHelper(props.ioHost, 'notices' as any /* forcing a CliAction to a ToolkitAction */); this.cliVersion = props.cliVersion; @@ -164,12 +176,14 @@ export class Notices { /** * Filter the data source for relevant notices */ - public filter(options: NoticesDisplayOptions = {}): Promise { + public async filter(options: NoticesDisplayOptions = {}): Promise { + const language = await guessLanguage(this.projectDir); return new NoticesFilter(this.ioHelper).filter({ data: this.noticesFromData(options.includeAcknowledged ?? false), cliVersion: this.cliVersion, outDir: this.output, bootstrappedEnvironments: Array.from(this.bootstrappedEnvironments.values()), + language, }); } diff --git a/packages/@aws-cdk/toolkit-lib/lib/util/directories.ts b/packages/@aws-cdk/toolkit-lib/lib/util/directories.ts index 4b69432bf..0017f6443 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/util/directories.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/util/directories.ts @@ -1,6 +1,7 @@ import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; +import * as fsExtra from 'fs-extra'; import { ToolkitError } from '../toolkit/toolkit-error'; /** @@ -63,3 +64,27 @@ export function bundledPackageRootDir(start: string, fail?: boolean) { return _rootDir(start); } + +/** + * Recursively lists all files in a directory up to the specified depth. + * + * @param dirName - The directory path to list files from + * @param depth - Maximum depth to traverse (1 = current directory only, 2 = one level deep, etc.) + * @returns Array of file names (not full paths) found within the depth limit + */ +export async function listFiles(dirName: string, depth: number): Promise { + const ret = await fsExtra.readdir(dirName, { encoding: 'utf-8', withFileTypes: true }); + + // unlikely to be unbound, it's a file system + // eslint-disable-next-line @cdklabs/promiseall-no-unbounded-parallelism + return (await Promise.all(ret.map(async (f) => { + if (f.isDirectory()) { + if (depth <= 1) { + return []; + } + return listFiles(path.join(dirName, f.name), depth - 1); + } else { + return [f.name]; + } + }))).flatMap(xs => xs); +} diff --git a/packages/@aws-cdk/toolkit-lib/lib/util/guess-language.ts b/packages/@aws-cdk/toolkit-lib/lib/util/guess-language.ts new file mode 100644 index 000000000..9f03421fe --- /dev/null +++ b/packages/@aws-cdk/toolkit-lib/lib/util/guess-language.ts @@ -0,0 +1,63 @@ +import * as path from 'path'; +import * as fs from 'fs-extra'; +import { listFiles } from './directories'; + +const DISPLAY_NAMES: Record = { + typescript: 'TypeScript', + javascript: 'JavaScript', + python: 'Python', + java: 'Java', + dotnet: '.NET', + go: 'Go', +}; + +/** + * Return the display name for a language identifier. + */ +export function languageDisplayName(language: string): string { + return DISPLAY_NAMES[language] ?? language; +} + +/** + * Guess the CDK app language based on the files in the given directory. + * + * Returns `undefined` if our guess fails. + */ +export async function guessLanguage(dir: string): Promise { + try { + const files = new Set(await listFiles(dir, 2)); + + if (files.has('package.json')) { + const pjContents = JSON.parse(await fs.readFile(path.join(dir, 'package.json'), 'utf-8')); + const deps = new Set([ + ...Object.keys(pjContents.dependencies ?? {}), + ...Object.keys(pjContents.devDependencies ?? {}), + ]); + if (deps.has('typescript') || deps.has('ts-node') || deps.has('tsx') || deps.has('swc')) { + return 'typescript'; + } else { + return 'javascript'; + } + } + + if (files.has('requirements.txt') || files.has('setup.py') || files.has('pyproject.toml')) { + return 'python'; + } + + if (files.has('pom.xml') || files.has('build.xml') || files.has('settings.gradle')) { + return 'java'; + } + + if (Array.from(files).some(n => n.endsWith('.sln') || n.endsWith('.csproj') || n.endsWith('.fsproj') || n.endsWith('.vbproj'))) { + return 'dotnet'; + } + + if (files.has('go.mod')) { + return 'go'; + } + } catch { + // Swallow failure + } + + return undefined; +} diff --git a/packages/@aws-cdk/toolkit-lib/test/api/guess-language.test.ts b/packages/@aws-cdk/toolkit-lib/test/api/guess-language.test.ts new file mode 100644 index 000000000..1e09b05e0 --- /dev/null +++ b/packages/@aws-cdk/toolkit-lib/test/api/guess-language.test.ts @@ -0,0 +1,70 @@ +import * as path from 'path'; +import * as fs from 'fs-extra'; +import { guessLanguage } from '../../lib/util/guess-language'; + +describe('guessLanguage', () => { + let tmpDir: string; + + beforeEach(async () => { + tmpDir = await fs.mkdtemp(path.join(__dirname, 'guess-lang-')); + }); + + afterEach(async () => { + await fs.remove(tmpDir); + }); + + test('returns typescript when package.json has typescript dependency', async () => { + await fs.writeJson(path.join(tmpDir, 'package.json'), { + dependencies: { typescript: '^5.0.0' }, + }); + expect(await guessLanguage(tmpDir)).toBe('typescript'); + }); + + test('returns typescript when package.json has ts-node devDependency', async () => { + await fs.writeJson(path.join(tmpDir, 'package.json'), { + devDependencies: { 'ts-node': '^10.0.0' }, + }); + expect(await guessLanguage(tmpDir)).toBe('typescript'); + }); + + test('returns javascript when package.json has no typescript indicators', async () => { + await fs.writeJson(path.join(tmpDir, 'package.json'), { + dependencies: { 'aws-cdk-lib': '^2.0.0' }, + }); + expect(await guessLanguage(tmpDir)).toBe('javascript'); + }); + + test('returns python for requirements.txt', async () => { + await fs.writeFile(path.join(tmpDir, 'requirements.txt'), ''); + expect(await guessLanguage(tmpDir)).toBe('python'); + }); + + test('returns python for pyproject.toml', async () => { + await fs.writeFile(path.join(tmpDir, 'pyproject.toml'), ''); + expect(await guessLanguage(tmpDir)).toBe('python'); + }); + + test('returns java for pom.xml', async () => { + await fs.writeFile(path.join(tmpDir, 'pom.xml'), ''); + expect(await guessLanguage(tmpDir)).toBe('java'); + }); + + test('returns dotnet for .csproj file', async () => { + await fs.writeFile(path.join(tmpDir, 'MyApp.csproj'), ''); + expect(await guessLanguage(tmpDir)).toBe('dotnet'); + }); + + test('returns go for go.mod', async () => { + await fs.writeFile(path.join(tmpDir, 'go.mod'), ''); + expect(await guessLanguage(tmpDir)).toBe('go'); + }); + + test('returns undefined for unknown project', async () => { + await fs.writeFile(path.join(tmpDir, 'README.md'), ''); + expect(await guessLanguage(tmpDir)).toBeUndefined(); + }); + + test('returns undefined for non-existent directory', async () => { + expect(await guessLanguage('/nonexistent/path')).toBeUndefined(); + }); +}); diff --git a/packages/@aws-cdk/toolkit-lib/test/api/notices.test.ts b/packages/@aws-cdk/toolkit-lib/test/api/notices.test.ts index a3b1d0b07..fbe70ecf5 100644 --- a/packages/@aws-cdk/toolkit-lib/test/api/notices.test.ts +++ b/packages/@aws-cdk/toolkit-lib/test/api/notices.test.ts @@ -488,6 +488,86 @@ describe(NoticesFilter, () => { expect((await filtered).map((f) => f.format()).join('\n')).toContain(`You are running ${nodeVersion}`); }); + test('language match', async () => { + const outDir = path.join(fixtures, 'built-with-2_12_0'); + const cliVersion = '1.0.0'; + + const filtered = await noticesFilter.filter({ + data: [ + { + title: 'title for typescript', + overview: 'This affects {resolve:LANGUAGE} users', + issueNumber: 1234, + schemaVersion: '1', + components: [{ name: 'language-typescript', version: '*' }], + }, + { + title: 'title for python', + overview: 'python issue', + issueNumber: 4321, + schemaVersion: '1', + components: [{ name: 'language-python', version: '*' }], + }, + ] satisfies Notice[], + cliVersion, + outDir, + bootstrappedEnvironments: [], + language: 'typescript', + }); + + expect(filtered.map((f) => f.notice.title)).toEqual(['title for typescript']); + expect(filtered.map((f) => f.format()).join('\n')).toContain('This affects TypeScript users'); + expect(filtered.map((f) => f.format()).join('\n')).toContain('TypeScript apps'); + }); + + test('no language match when language is not provided', async () => { + const outDir = path.join(fixtures, 'built-with-2_12_0'); + const cliVersion = '1.0.0'; + + const filtered = noticesFilter.filter({ + data: [ + { + title: 'typescript-only', + overview: 'ts issue', + issueNumber: 1, + schemaVersion: '1', + components: [{ name: 'language-typescript', version: '*' }], + }, + ] satisfies Notice[], + cliVersion, + outDir, + bootstrappedEnvironments: [], + }); + + expect((await filtered).map((f) => f.notice.title)).toEqual([]); + }); + + test('language combined with cli version in AND', async () => { + const outDir = path.join(fixtures, 'built-with-2_12_0'); + const cliVersion = '1.0.0'; + + const filtered = noticesFilter.filter({ + data: [ + { + title: 'combined', + overview: 'combined issue', + issueNumber: 1, + schemaVersion: '1', + components: [[ + { name: 'language-typescript', version: '*' }, + { name: 'cli', version: '<=1.0.0' }, + ]], + }, + ] satisfies Notice[], + cliVersion, + outDir, + bootstrappedEnvironments: [], + language: 'typescript', + }); + + expect((await filtered).map((f) => f.notice.title)).toEqual(['combined']); + }); + test.each([ // No components => doesnt match [[], false], diff --git a/packages/aws-cdk/lib/cli/cli.ts b/packages/aws-cdk/lib/cli/cli.ts index a0775adb0..f1735c402 100644 --- a/packages/aws-cdk/lib/cli/cli.ts +++ b/packages/aws-cdk/lib/cli/cli.ts @@ -146,6 +146,7 @@ export async function exec(args: string[], synthesizer?: Synthesizer): Promise Date: Thu, 19 Feb 2026 20:09:35 +0000 Subject: [PATCH 2/7] fixes --- .../toolkit-lib/lib/api/notices/filter.ts | 34 +++++++++++++++---- .../toolkit-lib/lib/api/notices/notices.ts | 4 +-- .../toolkit-lib/test/api/notices.test.ts | 1 + 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/packages/@aws-cdk/toolkit-lib/lib/api/notices/filter.ts b/packages/@aws-cdk/toolkit-lib/lib/api/notices/filter.ts index 38ce9a748..62f36d625 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/api/notices/filter.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/api/notices/filter.ts @@ -1,5 +1,5 @@ import * as semver from 'semver'; -import { languageDisplayName } from '../../util/guess-language'; +import { guessLanguage, languageDisplayName } from '../../util/guess-language'; import type { IoHelper } from '../io/private'; import type { ConstructTreeNode } from '../tree'; import { loadTreeFromDir } from '../tree'; @@ -64,10 +64,19 @@ export interface NoticesFilterFilterOptions { readonly outDir: string; readonly bootstrappedEnvironments: BootstrappedEnvironment[]; + /** + * The directory containing cdk.json (the project root). + * Used to guess the CDK app language when needed. + * + * @default - language detection is skipped + */ + readonly projectDir?: string; + /** * The guessed language of the CDK app. + * If provided, projectDir is ignored for language detection. * - * @default - no language component is added + * @default - auto-detected from projectDir when needed */ readonly language?: string; } @@ -80,9 +89,20 @@ export class NoticesFilter { } public async filter(options: NoticesFilterFilterOptions): Promise { + // Only guess language if any notice actually uses a language component + let language = options.language; + if (language === undefined && options.projectDir) { + const needsLanguage = options.data.some(n => + normalizeComponents(n.components).some(ands => + ands.some(c => c.name.startsWith('language-')))); + if (needsLanguage) { + language = await guessLanguage(options.projectDir); + } + } + const components = [ ...(await this.constructTreeComponents(options.outDir)), - ...(await this.otherComponents(options)), + ...(await this.otherComponents(options, language)), ]; return this.findForNamedComponents(options.data, components); @@ -91,7 +111,7 @@ export class NoticesFilter { /** * From a set of input options, return the notices components we are searching for */ - private async otherComponents(options: NoticesFilterFilterOptions): Promise { + private async otherComponents(options: NoticesFilterFilterOptions, language: string | undefined): Promise { // Bootstrap environments let bootstrappedEnvironments = []; for (const env of options.bootstrappedEnvironments) { @@ -128,11 +148,11 @@ export class NoticesFilter { ...bootstrappedEnvironments, // Language - ...(options.language ? [{ - name: `language-${options.language}`, + ...(language ? [{ + name: `language-${language}`, version: '0.0.0', dynamicName: 'LANGUAGE', - dynamicValue: languageDisplayName(options.language), + dynamicValue: languageDisplayName(language), }] : []), ]; } diff --git a/packages/@aws-cdk/toolkit-lib/lib/api/notices/notices.ts b/packages/@aws-cdk/toolkit-lib/lib/api/notices/notices.ts index 5d3c3fa79..ba0fafdbc 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/api/notices/notices.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/api/notices/notices.ts @@ -8,7 +8,6 @@ import type { FilteredNotice } from './filter'; import { NoticesFilter } from './filter'; import type { BootstrappedEnvironment, Notice, NoticeDataSource } from './types'; import { WebsiteNoticeDataSource } from './web-data-source'; -import { guessLanguage } from '../../util/guess-language'; import type { IoHelper } from '../io/private'; import { IO, asIoHelper } from '../io/private'; @@ -177,13 +176,12 @@ export class Notices { * Filter the data source for relevant notices */ public async filter(options: NoticesDisplayOptions = {}): Promise { - const language = await guessLanguage(this.projectDir); return new NoticesFilter(this.ioHelper).filter({ data: this.noticesFromData(options.includeAcknowledged ?? false), cliVersion: this.cliVersion, outDir: this.output, bootstrappedEnvironments: Array.from(this.bootstrappedEnvironments.values()), - language, + projectDir: this.projectDir, }); } diff --git a/packages/@aws-cdk/toolkit-lib/test/api/notices.test.ts b/packages/@aws-cdk/toolkit-lib/test/api/notices.test.ts index fbe70ecf5..1f292c452 100644 --- a/packages/@aws-cdk/toolkit-lib/test/api/notices.test.ts +++ b/packages/@aws-cdk/toolkit-lib/test/api/notices.test.ts @@ -981,6 +981,7 @@ describe(Notices, () => { cliVersion: '1.0.0', data: [], outDir: 'cdk.out', + projectDir: expect.any(String), }); }); }); From ec5514765ada43747c009003622b9e61e38be2ec Mon Sep 17 00:00:00 2001 From: Momo Kornher Date: Thu, 19 Feb 2026 20:18:38 +0000 Subject: [PATCH 3/7] fix test --- .../test/api/environment/environment-resources.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/@aws-cdk/toolkit-lib/test/api/environment/environment-resources.test.ts b/packages/@aws-cdk/toolkit-lib/test/api/environment/environment-resources.test.ts index 05b9e3d05..b5cacbe83 100644 --- a/packages/@aws-cdk/toolkit-lib/test/api/environment/environment-resources.test.ts +++ b/packages/@aws-cdk/toolkit-lib/test/api/environment/environment-resources.test.ts @@ -125,6 +125,7 @@ describe('validate version without bootstrap stack', () => { cliVersion: '1.0.0', data: [], outDir: 'cdk.out', + projectDir: expect.any(String), }); }); From e7a6905d4a3982ef5d4bbc17e0e61e2172cf5610 Mon Sep 17 00:00:00 2001 From: Momo Kornher Date: Fri, 20 Feb 2026 09:49:46 +0000 Subject: [PATCH 4/7] language:* --- packages/@aws-cdk/toolkit-lib/lib/api/notices/filter.ts | 8 ++++---- packages/@aws-cdk/toolkit-lib/test/api/notices.test.ts | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/@aws-cdk/toolkit-lib/lib/api/notices/filter.ts b/packages/@aws-cdk/toolkit-lib/lib/api/notices/filter.ts index 62f36d625..b7bc2c6b6 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/api/notices/filter.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/api/notices/filter.ts @@ -13,8 +13,8 @@ function normalizeComponents(xs: Array): Component[][] } function renderComponent(c: Component): string { - if (c.name.startsWith('language-')) { - return `${languageDisplayName(c.name.slice('language-'.length))} apps`; + if (c.name.startsWith('language:')) { + return `${languageDisplayName(c.name.slice('language:'.length))} apps`; } return `${c.name}: ${c.version}`; } @@ -94,7 +94,7 @@ export class NoticesFilter { if (language === undefined && options.projectDir) { const needsLanguage = options.data.some(n => normalizeComponents(n.components).some(ands => - ands.some(c => c.name.startsWith('language-')))); + ands.some(c => c.name.startsWith('language:')))); if (needsLanguage) { language = await guessLanguage(options.projectDir); } @@ -149,7 +149,7 @@ export class NoticesFilter { // Language ...(language ? [{ - name: `language-${language}`, + name: `language:${language}`, version: '0.0.0', dynamicName: 'LANGUAGE', dynamicValue: languageDisplayName(language), diff --git a/packages/@aws-cdk/toolkit-lib/test/api/notices.test.ts b/packages/@aws-cdk/toolkit-lib/test/api/notices.test.ts index 1f292c452..8f0999239 100644 --- a/packages/@aws-cdk/toolkit-lib/test/api/notices.test.ts +++ b/packages/@aws-cdk/toolkit-lib/test/api/notices.test.ts @@ -499,14 +499,14 @@ describe(NoticesFilter, () => { overview: 'This affects {resolve:LANGUAGE} users', issueNumber: 1234, schemaVersion: '1', - components: [{ name: 'language-typescript', version: '*' }], + components: [{ name: 'language:typescript', version: '*' }], }, { title: 'title for python', overview: 'python issue', issueNumber: 4321, schemaVersion: '1', - components: [{ name: 'language-python', version: '*' }], + components: [{ name: 'language:python', version: '*' }], }, ] satisfies Notice[], cliVersion, @@ -531,7 +531,7 @@ describe(NoticesFilter, () => { overview: 'ts issue', issueNumber: 1, schemaVersion: '1', - components: [{ name: 'language-typescript', version: '*' }], + components: [{ name: 'language:typescript', version: '*' }], }, ] satisfies Notice[], cliVersion, @@ -554,7 +554,7 @@ describe(NoticesFilter, () => { issueNumber: 1, schemaVersion: '1', components: [[ - { name: 'language-typescript', version: '*' }, + { name: 'language:typescript', version: '*' }, { name: 'cli', version: '<=1.0.0' }, ]], }, From 59652c98f774551433981ed7147a4fb9410b7494 Mon Sep 17 00:00:00 2001 From: Momo Kornher Date: Fri, 20 Feb 2026 11:10:01 +0000 Subject: [PATCH 5/7] always guess lang --- .../toolkit-lib/lib/api/notices/filter.ts | 36 +++++-------------- .../toolkit-lib/lib/api/notices/notices.ts | 14 ++++---- .../environment/environment-resources.test.ts | 2 +- .../toolkit-lib/test/api/notices.test.ts | 2 +- packages/aws-cdk/lib/cli/cli.ts | 5 +-- 5 files changed, 19 insertions(+), 40 deletions(-) diff --git a/packages/@aws-cdk/toolkit-lib/lib/api/notices/filter.ts b/packages/@aws-cdk/toolkit-lib/lib/api/notices/filter.ts index b7bc2c6b6..3c7239ea7 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/api/notices/filter.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/api/notices/filter.ts @@ -1,5 +1,5 @@ import * as semver from 'semver'; -import { guessLanguage, languageDisplayName } from '../../util/guess-language'; +import { languageDisplayName } from '../../util/guess-language'; import type { IoHelper } from '../io/private'; import type { ConstructTreeNode } from '../tree'; import { loadTreeFromDir } from '../tree'; @@ -65,18 +65,9 @@ export interface NoticesFilterFilterOptions { readonly bootstrappedEnvironments: BootstrappedEnvironment[]; /** - * The directory containing cdk.json (the project root). - * Used to guess the CDK app language when needed. + * The detected CDK app language. * - * @default - language detection is skipped - */ - readonly projectDir?: string; - - /** - * The guessed language of the CDK app. - * If provided, projectDir is ignored for language detection. - * - * @default - auto-detected from projectDir when needed + * @default - no language component is added */ readonly language?: string; } @@ -89,20 +80,9 @@ export class NoticesFilter { } public async filter(options: NoticesFilterFilterOptions): Promise { - // Only guess language if any notice actually uses a language component - let language = options.language; - if (language === undefined && options.projectDir) { - const needsLanguage = options.data.some(n => - normalizeComponents(n.components).some(ands => - ands.some(c => c.name.startsWith('language:')))); - if (needsLanguage) { - language = await guessLanguage(options.projectDir); - } - } - const components = [ ...(await this.constructTreeComponents(options.outDir)), - ...(await this.otherComponents(options, language)), + ...(await this.otherComponents(options)), ]; return this.findForNamedComponents(options.data, components); @@ -111,7 +91,7 @@ export class NoticesFilter { /** * From a set of input options, return the notices components we are searching for */ - private async otherComponents(options: NoticesFilterFilterOptions, language: string | undefined): Promise { + private async otherComponents(options: NoticesFilterFilterOptions): Promise { // Bootstrap environments let bootstrappedEnvironments = []; for (const env of options.bootstrappedEnvironments) { @@ -148,11 +128,11 @@ export class NoticesFilter { ...bootstrappedEnvironments, // Language - ...(language ? [{ - name: `language:${language}`, + ...(options.language ? [{ + name: `language:${options.language}`, version: '0.0.0', dynamicName: 'LANGUAGE', - dynamicValue: languageDisplayName(language), + dynamicValue: languageDisplayName(options.language), }] : []), ]; } diff --git a/packages/@aws-cdk/toolkit-lib/lib/api/notices/notices.ts b/packages/@aws-cdk/toolkit-lib/lib/api/notices/notices.ts index ba0fafdbc..fa5185321 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/api/notices/notices.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/api/notices/notices.ts @@ -41,13 +41,11 @@ export interface NoticesProps { readonly output?: string; /** - * The directory containing cdk.json (the project root). + * The detected CDK app language. * - * Used to guess the CDK app language for filtering notices. - * - * @default process.cwd() + * @default - no language filtering */ - readonly projectDir?: string; + readonly language?: string; /** * Options for the HTTPS requests made by Notices @@ -122,7 +120,7 @@ export class Notices { private readonly context: Context; private readonly output: string; - private readonly projectDir: string; + private readonly language?: string; private readonly acknowledgedIssueNumbers: Set; private readonly httpOptions: NoticesHttpOptions; private readonly ioHelper: IoHelper; @@ -137,7 +135,7 @@ export class Notices { this.context = props.context; this.acknowledgedIssueNumbers = new Set(this.context.get('acknowledged-issue-numbers') ?? []); this.output = props.output ?? 'cdk.out'; - this.projectDir = props.projectDir ?? process.cwd(); + this.language = props.language; this.httpOptions = props.httpOptions ?? {}; this.ioHelper = asIoHelper(props.ioHost, 'notices' as any /* forcing a CliAction to a ToolkitAction */); this.cliVersion = props.cliVersion; @@ -181,7 +179,7 @@ export class Notices { cliVersion: this.cliVersion, outDir: this.output, bootstrappedEnvironments: Array.from(this.bootstrappedEnvironments.values()), - projectDir: this.projectDir, + language: this.language, }); } diff --git a/packages/@aws-cdk/toolkit-lib/test/api/environment/environment-resources.test.ts b/packages/@aws-cdk/toolkit-lib/test/api/environment/environment-resources.test.ts index b5cacbe83..3e47bd08b 100644 --- a/packages/@aws-cdk/toolkit-lib/test/api/environment/environment-resources.test.ts +++ b/packages/@aws-cdk/toolkit-lib/test/api/environment/environment-resources.test.ts @@ -125,7 +125,7 @@ describe('validate version without bootstrap stack', () => { cliVersion: '1.0.0', data: [], outDir: 'cdk.out', - projectDir: expect.any(String), + language: undefined, }); }); diff --git a/packages/@aws-cdk/toolkit-lib/test/api/notices.test.ts b/packages/@aws-cdk/toolkit-lib/test/api/notices.test.ts index 8f0999239..46f4373dd 100644 --- a/packages/@aws-cdk/toolkit-lib/test/api/notices.test.ts +++ b/packages/@aws-cdk/toolkit-lib/test/api/notices.test.ts @@ -981,7 +981,7 @@ describe(Notices, () => { cliVersion: '1.0.0', data: [], outDir: 'cdk.out', - projectDir: expect.any(String), + language: undefined, }); }); }); diff --git a/packages/aws-cdk/lib/cli/cli.ts b/packages/aws-cdk/lib/cli/cli.ts index f1735c402..17e2d5cfb 100644 --- a/packages/aws-cdk/lib/cli/cli.ts +++ b/packages/aws-cdk/lib/cli/cli.ts @@ -2,6 +2,7 @@ import * as cxapi from '@aws-cdk/cx-api'; import type { ChangeSetDeployment, DeploymentMethod, DirectDeployment } from '@aws-cdk/toolkit-lib'; import { ToolkitError, Toolkit } from '@aws-cdk/toolkit-lib'; +import { guessLanguage } from '@aws-cdk/toolkit-lib/lib/util/guess-language'; import * as chalk from 'chalk'; import { CdkToolkit, AssetBuildTime } from './cdk-toolkit'; import { ciSystemIsStdErrSafe } from './ci-systems'; @@ -28,6 +29,7 @@ import { docs } from '../commands/docs'; import { doctor } from '../commands/doctor'; import { FlagCommandHandler } from '../commands/flags/flags'; import { cliInit, printAvailableTemplates } from '../commands/init'; +import { getLanguageFromAlias } from '../commands/language'; import { getMigrateScanType } from '../commands/migrate'; import { execProgram, CloudExecutable } from '../cxapp'; import type { StackSelector, Synthesizer } from '../cxapp'; @@ -36,7 +38,6 @@ import { cdkCliErrorName } from './telemetry/error'; import type { ErrorDetails } from './telemetry/schema'; import { isCI } from './util/ci'; import { isDeveloperBuildVersion, versionWithBuild, versionNumber } from './version'; -import { getLanguageFromAlias } from '../commands/language'; export async function exec(args: string[], synthesizer?: Synthesizer): Promise { const argv = await parseCommandLineArguments(args); @@ -146,7 +147,7 @@ export async function exec(args: string[], synthesizer?: Synthesizer): Promise Date: Fri, 20 Feb 2026 11:14:10 +0000 Subject: [PATCH 6/7] no node_modules --- packages/@aws-cdk/toolkit-lib/lib/util/directories.ts | 6 +++--- packages/@aws-cdk/toolkit-lib/lib/util/guess-language.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/toolkit-lib/lib/util/directories.ts b/packages/@aws-cdk/toolkit-lib/lib/util/directories.ts index 0017f6443..9d0d8f379 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/util/directories.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/util/directories.ts @@ -72,17 +72,17 @@ export function bundledPackageRootDir(start: string, fail?: boolean) { * @param depth - Maximum depth to traverse (1 = current directory only, 2 = one level deep, etc.) * @returns Array of file names (not full paths) found within the depth limit */ -export async function listFiles(dirName: string, depth: number): Promise { +export async function listFiles(dirName: string, depth: number, excludeDirs?: string[]): Promise { const ret = await fsExtra.readdir(dirName, { encoding: 'utf-8', withFileTypes: true }); // unlikely to be unbound, it's a file system // eslint-disable-next-line @cdklabs/promiseall-no-unbounded-parallelism return (await Promise.all(ret.map(async (f) => { if (f.isDirectory()) { - if (depth <= 1) { + if (depth <= 1 || excludeDirs?.includes(f.name)) { return []; } - return listFiles(path.join(dirName, f.name), depth - 1); + return listFiles(path.join(dirName, f.name), depth - 1, excludeDirs); } else { return [f.name]; } diff --git a/packages/@aws-cdk/toolkit-lib/lib/util/guess-language.ts b/packages/@aws-cdk/toolkit-lib/lib/util/guess-language.ts index 9f03421fe..0757f0eaa 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/util/guess-language.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/util/guess-language.ts @@ -25,7 +25,7 @@ export function languageDisplayName(language: string): string { */ export async function guessLanguage(dir: string): Promise { try { - const files = new Set(await listFiles(dir, 2)); + const files = new Set(await listFiles(dir, 2, ['node_modules'])); if (files.has('package.json')) { const pjContents = JSON.parse(await fs.readFile(path.join(dir, 'package.json'), 'utf-8')); From 2011fa4d153482599d5d2aecab792a532e3c9329 Mon Sep 17 00:00:00 2001 From: Momo Kornher Date: Fri, 20 Feb 2026 11:57:06 +0000 Subject: [PATCH 7/7] fix import --- packages/@aws-cdk/toolkit-lib/lib/util/index.ts | 3 ++- packages/aws-cdk/lib/cli/cli.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/toolkit-lib/lib/util/index.ts b/packages/@aws-cdk/toolkit-lib/lib/util/index.ts index 8af7c9ede..cb381791e 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/util/index.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/util/index.ts @@ -1,12 +1,13 @@ export * from './archive'; export * from './arrays'; -export * from './glob-matcher'; export * from './bool'; export * from './bytes'; export * from './cloudformation'; export * from './content-hash'; export * from './directories'; export * from './format-error'; +export * from './glob-matcher'; +export * from './guess-language'; export * from './json'; export * from './net'; export * from './objects'; diff --git a/packages/aws-cdk/lib/cli/cli.ts b/packages/aws-cdk/lib/cli/cli.ts index 17e2d5cfb..95c0d267c 100644 --- a/packages/aws-cdk/lib/cli/cli.ts +++ b/packages/aws-cdk/lib/cli/cli.ts @@ -2,8 +2,8 @@ import * as cxapi from '@aws-cdk/cx-api'; import type { ChangeSetDeployment, DeploymentMethod, DirectDeployment } from '@aws-cdk/toolkit-lib'; import { ToolkitError, Toolkit } from '@aws-cdk/toolkit-lib'; -import { guessLanguage } from '@aws-cdk/toolkit-lib/lib/util/guess-language'; import * as chalk from 'chalk'; +import { guessLanguage } from '../util'; import { CdkToolkit, AssetBuildTime } from './cdk-toolkit'; import { ciSystemIsStdErrSafe } from './ci-systems'; import { displayVersionMessage } from './display-version';