diff --git a/cdk/.eslintrc.json b/cdk/.eslintrc.json deleted file mode 100644 index 296cfcc4..00000000 --- a/cdk/.eslintrc.json +++ /dev/null @@ -1,385 +0,0 @@ -{ - "env": { - "jest": true, - "node": true - }, - "root": true, - "plugins": [ - "@typescript-eslint", - "import", - "@stylistic", - "@cdklabs/eslint-plugin", - "license-header", - "jsdoc", - "jest" - ], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaVersion": 2018, - "sourceType": "module", - "project": "./tsconfig.dev.json" - }, - "extends": [ - "plugin:import/typescript" - ], - "settings": { - "import/parsers": { - "@typescript-eslint/parser": [ - ".ts", - ".tsx" - ] - }, - "import/resolver": { - "node": {}, - "typescript": { - "project": "./tsconfig.dev.json", - "alwaysTryTypes": true - } - } - }, - "ignorePatterns": [ - "*.js", - "*.d.ts", - "node_modules/", - "*.generated.ts", - "coverage" - ], - "rules": { - "@stylistic/indent": [ - "error", - 2 - ], - "@stylistic/quotes": [ - "error", - "single", - { - "avoidEscape": true - } - ], - "@stylistic/comma-dangle": [ - "error", - "always-multiline" - ], - "@stylistic/comma-spacing": [ - "error", - { - "before": false, - "after": true - } - ], - "@stylistic/no-multi-spaces": [ - "error", - { - "ignoreEOLComments": false - } - ], - "@stylistic/array-bracket-spacing": [ - "error", - "never" - ], - "@stylistic/array-bracket-newline": [ - "error", - "consistent" - ], - "@stylistic/object-curly-spacing": [ - "error", - "always" - ], - "@stylistic/object-curly-newline": [ - "error", - { - "multiline": true, - "consistent": true - } - ], - "@stylistic/object-property-newline": [ - "error", - { - "allowAllPropertiesOnSameLine": true - } - ], - "@stylistic/keyword-spacing": [ - "error" - ], - "@stylistic/brace-style": [ - "error", - "1tbs", - { - "allowSingleLine": true - } - ], - "@stylistic/space-before-blocks": [ - "error" - ], - "@stylistic/member-delimiter-style": [ - "error" - ], - "@stylistic/semi": [ - "error", - "always" - ], - "@stylistic/max-len": [ - "error", - { - "code": 150, - "ignoreUrls": true, - "ignoreStrings": true, - "ignoreTemplateLiterals": true, - "ignoreComments": true, - "ignoreRegExpLiterals": true - } - ], - "@stylistic/quote-props": [ - "error", - "consistent-as-needed" - ], - "@stylistic/key-spacing": [ - "error" - ], - "@stylistic/no-multiple-empty-lines": [ - "error" - ], - "@stylistic/no-trailing-spaces": [ - "error" - ], - "curly": [ - "error", - "multi-line", - "consistent" - ], - "@typescript-eslint/no-require-imports": "error", - "import/no-extraneous-dependencies": [ - "error", - { - "devDependencies": [ - "**/test/**", - "**/build-tools/**" - ], - "optionalDependencies": false, - "peerDependencies": true - } - ], - "import/no-unresolved": [ - "error" - ], - "import/order": [ - "error", - { - "groups": [ - "builtin", - "external" - ], - "alphabetize": { - "order": "asc", - "caseInsensitive": true - } - } - ], - "import/no-duplicates": [ - "error" - ], - "no-shadow": [ - "off" - ], - "@typescript-eslint/no-shadow": "error", - "@typescript-eslint/no-floating-promises": [ - "error" - ], - "no-return-await": "off", - "@typescript-eslint/return-await": "error", - "dot-notation": [ - "error" - ], - "no-bitwise": [ - "error" - ], - "@typescript-eslint/member-ordering": [ - "error", - { - "default": [ - "public-static-field", - "public-static-method", - "protected-static-field", - "protected-static-method", - "private-static-field", - "private-static-method", - "field", - "constructor", - "method" - ] - } - ], - "@cdklabs/no-core-construct": [ - "error" - ], - "@cdklabs/invalid-cfn-imports": [ - "error" - ], - "@cdklabs/no-literal-partition": [ - "error" - ], - "@cdklabs/no-invalid-path": [ - "error" - ], - "@cdklabs/promiseall-no-unbounded-parallelism": [ - "error" - ], - "license-header/header": [ - "error", - "header.js" - ], - "no-throw-literal": [ - "error" - ], - "quotes": [ - "error", - "single", - { - "avoidEscape": true - } - ], - "@stylistic/no-extra-semi": [ - "error" - ], - "comma-spacing": [ - "error", - { - "before": false, - "after": true - } - ], - "no-multi-spaces": [ - "error", - { - "ignoreEOLComments": false - } - ], - "array-bracket-spacing": [ - "error", - "never" - ], - "array-bracket-newline": [ - "error", - "consistent" - ], - "object-curly-spacing": [ - "error", - "always" - ], - "object-curly-newline": [ - "error", - { - "multiline": true, - "consistent": true - } - ], - "object-property-newline": [ - "error", - { - "allowAllPropertiesOnSameLine": true - } - ], - "keyword-spacing": [ - "error" - ], - "brace-style": [ - "error", - "1tbs", - { - "allowSingleLine": true - } - ], - "space-before-blocks": "error", - "eol-last": [ - "error", - "always" - ], - "@stylistic/spaced-comment": [ - "error", - "always", - { - "exceptions": [ - "/", - "*" - ], - "markers": [ - "/" - ] - } - ], - "@stylistic/padded-blocks": [ - "error", - { - "classes": "never", - "blocks": "never", - "switches": "never" - } - ], - "jsdoc/require-param-description": [ - "error" - ], - "jsdoc/require-property-description": [ - "error" - ], - "jsdoc/require-returns-description": [ - "error" - ], - "jsdoc/check-alignment": [ - "error" - ], - "no-duplicate-imports": [ - "error" - ], - "key-spacing": [ - "error" - ], - "semi": [ - "error", - "always" - ], - "quote-props": [ - "error", - "consistent-as-needed" - ], - "no-multiple-empty-lines": [ - "error", - { - "max": 1 - } - ], - "max-len": [ - "error", - { - "code": 150, - "ignoreUrls": true, - "ignoreStrings": true, - "ignoreTemplateLiterals": true, - "ignoreComments": true, - "ignoreRegExpLiterals": true - } - ], - "no-console": [ - "error" - ], - "no-trailing-spaces": [ - "error" - ], - "no-restricted-syntax": [ - "error", - { - "selector": "CallExpression:matches([callee.name='createHash'], [callee.property.name='createHash']) Literal[value='md5']", - "message": "Use the md5hash() function from the core library if you want md5" - } - ], - "@typescript-eslint/unbound-method": "error", - "jest/expect-expect": "off", - "jest/no-conditional-expect": "off", - "jest/no-done-callback": "off", - "jest/no-standalone-expect": "off", - "jest/valid-expect": "off", - "jest/valid-title": "off", - "jest/no-identical-title": "off", - "jest/no-disabled-tests": "error", - "jest/no-focused-tests": "error" - } -} diff --git a/cdk/eslint.config.mjs b/cdk/eslint.config.mjs new file mode 100644 index 00000000..c748c80e --- /dev/null +++ b/cdk/eslint.config.mjs @@ -0,0 +1,222 @@ +/** + * MIT No Attribution + * + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +import tseslint from '@typescript-eslint/eslint-plugin'; +import tsparser from '@typescript-eslint/parser'; +import stylistic from '@stylistic/eslint-plugin'; +import * as cdklabs from '@cdklabs/eslint-plugin'; +import importX from 'eslint-plugin-import-x'; +import jest from 'eslint-plugin-jest'; +import jsdoc from 'eslint-plugin-jsdoc'; +import licenseHeader from 'eslint-plugin-license-header'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +export default [ + // Global ignores + { + ignores: [ + '**/*.js', + '**/*.d.ts', + '**/node_modules/', + '**/*.generated.ts', + '**/coverage/', + '**/cdk.out*/', + '!eslint.config.mjs', + ], + }, + + // TypeScript source, test, and scripts files + { + files: ['src/**/*.ts', 'test/**/*.ts'], + languageOptions: { + parser: tsparser, + parserOptions: { + ecmaVersion: 2018, + sourceType: 'module', + project: './tsconfig.dev.json', + }, + }, + plugins: { + '@typescript-eslint': tseslint, + '@stylistic': stylistic, + '@cdklabs': cdklabs, + 'import-x': importX, + 'jest': jest, + 'jsdoc': jsdoc, + 'license-header': licenseHeader, + }, + settings: { + 'import-x/parsers': { + '@typescript-eslint/parser': ['.ts', '.tsx'], + }, + 'import-x/resolver': { + node: {}, + typescript: { + project: './tsconfig.dev.json', + alwaysTryTypes: true, + }, + }, + }, + rules: { + // Stylistic rules + '@stylistic/indent': ['error', 2], + '@stylistic/quotes': ['error', 'single', { avoidEscape: true }], + '@stylistic/comma-dangle': ['error', 'always-multiline'], + '@stylistic/comma-spacing': ['error', { before: false, after: true }], + '@stylistic/no-multi-spaces': ['error', { ignoreEOLComments: false }], + '@stylistic/array-bracket-spacing': ['error', 'never'], + '@stylistic/array-bracket-newline': ['error', 'consistent'], + '@stylistic/object-curly-spacing': ['error', 'always'], + '@stylistic/object-curly-newline': ['error', { multiline: true, consistent: true }], + '@stylistic/object-property-newline': ['error', { allowAllPropertiesOnSameLine: true }], + '@stylistic/keyword-spacing': ['error'], + '@stylistic/brace-style': ['error', '1tbs', { allowSingleLine: true }], + '@stylistic/space-before-blocks': ['error'], + '@stylistic/member-delimiter-style': ['error'], + '@stylistic/semi': ['error', 'always'], + '@stylistic/max-len': ['error', { + code: 150, + ignoreUrls: true, + ignoreStrings: true, + ignoreTemplateLiterals: true, + ignoreComments: true, + ignoreRegExpLiterals: true, + }], + '@stylistic/quote-props': ['error', 'consistent-as-needed'], + '@stylistic/key-spacing': ['error'], + '@stylistic/no-multiple-empty-lines': ['error'], + '@stylistic/no-trailing-spaces': ['error'], + '@stylistic/no-extra-semi': ['error'], + '@stylistic/spaced-comment': ['error', 'always', { + exceptions: ['/', '*'], + markers: ['/'], + }], + '@stylistic/padded-blocks': ['error', { + classes: 'never', + blocks: 'never', + switches: 'never', + }], + + // Core ESLint rules + 'curly': ['error', 'multi-line', 'consistent'], + 'no-shadow': ['off'], + 'no-return-await': 'off', + 'dot-notation': ['error'], + 'no-bitwise': ['error'], + 'no-throw-literal': ['error'], + 'quotes': ['error', 'single', { avoidEscape: true }], + 'comma-spacing': ['error', { before: false, after: true }], + 'no-multi-spaces': ['error', { ignoreEOLComments: false }], + 'array-bracket-spacing': ['error', 'never'], + 'array-bracket-newline': ['error', 'consistent'], + 'object-curly-spacing': ['error', 'always'], + 'object-curly-newline': ['error', { multiline: true, consistent: true }], + 'object-property-newline': ['error', { allowAllPropertiesOnSameLine: true }], + 'keyword-spacing': ['error'], + 'brace-style': ['error', '1tbs', { allowSingleLine: true }], + 'space-before-blocks': 'error', + 'eol-last': ['error', 'always'], + 'no-duplicate-imports': ['error'], + 'key-spacing': ['error'], + 'semi': ['error', 'always'], + 'quote-props': ['error', 'consistent-as-needed'], + 'no-multiple-empty-lines': ['error', { max: 1 }], + 'max-len': ['error', { + code: 150, + ignoreUrls: true, + ignoreStrings: true, + ignoreTemplateLiterals: true, + ignoreComments: true, + ignoreRegExpLiterals: true, + }], + 'no-console': ['error'], + 'no-trailing-spaces': ['error'], + 'no-restricted-syntax': ['error', { + selector: "CallExpression:matches([callee.name='createHash'], [callee.property.name='createHash']) Literal[value='md5']", + message: 'Use the md5hash() function from the core library if you want md5', + }], + + // TypeScript rules + '@typescript-eslint/no-require-imports': 'error', + '@typescript-eslint/no-shadow': 'error', + '@typescript-eslint/no-floating-promises': ['error'], + '@typescript-eslint/return-await': 'error', + '@typescript-eslint/member-ordering': ['error', { + default: [ + 'public-static-field', + 'public-static-method', + 'protected-static-field', + 'protected-static-method', + 'private-static-field', + 'private-static-method', + 'field', + 'constructor', + 'method', + ], + }], + '@typescript-eslint/unbound-method': 'error', + + // Import rules (import/ -> import-x/) + 'import-x/no-extraneous-dependencies': ['error', { + devDependencies: ['**/test/**', '**/build-tools/**'], + optionalDependencies: false, + peerDependencies: true, + }], + 'import-x/no-unresolved': ['error'], + 'import-x/order': ['error', { + groups: ['builtin', 'external'], + alphabetize: { order: 'asc', caseInsensitive: true }, + }], + 'import-x/no-duplicates': ['error'], + + // CDK Labs rules + // NOTE: The following 4 rules use context.getFilename() which was removed in ESLint 10. + // They are disabled until @cdklabs/eslint-plugin adds ESLint 10 support. + // Track: https://github.com/cdklabs/eslint-plugin/issues + '@cdklabs/no-core-construct': ['off'], + '@cdklabs/invalid-cfn-imports': ['off'], + '@cdklabs/no-literal-partition': ['off'], + '@cdklabs/no-invalid-path': ['off'], + '@cdklabs/promiseall-no-unbounded-parallelism': ['error'], + + // License header + 'license-header/header': ['error', join(__dirname, 'header.js')], + + // JSDoc rules + 'jsdoc/require-param-description': ['error'], + 'jsdoc/require-property-description': ['error'], + 'jsdoc/require-returns-description': ['error'], + 'jsdoc/check-alignment': ['error'], + + // Jest rules + 'jest/expect-expect': 'off', + 'jest/no-conditional-expect': 'off', + 'jest/no-done-callback': 'off', + 'jest/no-standalone-expect': 'off', + 'jest/valid-expect': 'off', + 'jest/valid-title': 'off', + 'jest/no-identical-title': 'off', + 'jest/no-disabled-tests': 'error', + 'jest/no-focused-tests': 'error', + }, + }, +]; diff --git a/cdk/package.json b/cdk/package.json index 48668607..cc9ba08a 100644 --- a/cdk/package.json +++ b/cdk/package.json @@ -9,7 +9,7 @@ "compile": "tsc --build tsconfig.json", "watch": "tsc --build -w tsconfig.json", "test": "jest", - "eslint": "ESLINT_USE_FLAT_CONFIG=false eslint --ext .ts,.tsx --fix --no-error-on-unmatched-pattern src test", + "eslint": "eslint --fix src test", "synth": "npx cdk synth", "synth:quiet": "npx cdk synth -q" }, @@ -37,7 +37,7 @@ "ulid": "^3.0.2" }, "devDependencies": { - "@cdklabs/eslint-plugin": "^1.5.10", + "@cdklabs/eslint-plugin": "^2", "@stylistic/eslint-plugin": "^2", "@types/aws-lambda": "^8.10.161", "@types/jest": "^30.0.0", @@ -48,9 +48,9 @@ "@typescript-eslint/parser": "^8", "aws-cdk": "^2", "esbuild": "^0.27.4", - "eslint": "^9", - "eslint-import-resolver-typescript": "^4.4.4", - "eslint-plugin-import": "^2.32.0", + "eslint": "^10", + "eslint-import-resolver-typescript": "^4", + "eslint-plugin-import-x": "^4", "eslint-plugin-jest": "^29.15.1", "eslint-plugin-jsdoc": "^62.8.1", "eslint-plugin-license-header": "^0.9.0", @@ -60,10 +60,7 @@ "ts-node": "^10.9.2", "typescript": "^5.9.3" }, - "resolutions": { - "eslint-plugin-import/minimatch": "^3.1.2" - }, - "optionalDependencies": {}, + "resolutions": {}, "engines": { "node": ">= 20.x <= 24.x" }, diff --git a/cdk/src/bootstrap/policies/application.ts b/cdk/src/bootstrap/policies/application.ts index 22632dc0..d9902005 100644 --- a/cdk/src/bootstrap/policies/application.ts +++ b/cdk/src/bootstrap/policies/application.ts @@ -17,7 +17,6 @@ * SOFTWARE. */ -/* eslint-disable @cdklabs/no-literal-partition */ // ARN partitions are intentionally literal — this policy is a bootstrap // template matching the exact resource patterns in DEPLOYMENT_ROLES.md. diff --git a/cdk/src/bootstrap/policies/infrastructure.ts b/cdk/src/bootstrap/policies/infrastructure.ts index 7ede06d0..1e9b1d16 100644 --- a/cdk/src/bootstrap/policies/infrastructure.ts +++ b/cdk/src/bootstrap/policies/infrastructure.ts @@ -17,7 +17,6 @@ * SOFTWARE. */ -/* eslint-disable @cdklabs/no-literal-partition */ // ARN partitions are intentionally literal — this policy is a bootstrap // template matching the exact resource patterns in DEPLOYMENT_ROLES.md. diff --git a/cdk/src/bootstrap/policies/observability.ts b/cdk/src/bootstrap/policies/observability.ts index 89f2a397..ac23b72b 100644 --- a/cdk/src/bootstrap/policies/observability.ts +++ b/cdk/src/bootstrap/policies/observability.ts @@ -17,7 +17,6 @@ * SOFTWARE. */ -/* eslint-disable @cdklabs/no-literal-partition */ // ARN partitions are intentionally literal — this policy is a bootstrap // template matching the exact resource patterns in DEPLOYMENT_ROLES.md. diff --git a/cdk/src/handlers/fanout-task-events.ts b/cdk/src/handlers/fanout-task-events.ts index d4e9845f..64dbd9ee 100644 --- a/cdk/src/handlers/fanout-task-events.ts +++ b/cdk/src/handlers/fanout-task-events.ts @@ -805,7 +805,7 @@ export async function routeEvent( } // Parallelism is bounded by the dispatcher list (at most 3 channels), // not by program input, so the unbounded-parallelism lint does not apply. - // eslint-disable-next-line @cdklabs/promiseall-no-unbounded-parallelism + const results = await Promise.allSettled(tasks); const dispatched: NotificationChannel[] = []; diff --git a/cdk/src/handlers/shared/attachment-screening.ts b/cdk/src/handlers/shared/attachment-screening.ts index 10ee2a7c..8ffa39cf 100644 --- a/cdk/src/handlers/shared/attachment-screening.ts +++ b/cdk/src/handlers/shared/attachment-screening.ts @@ -238,7 +238,7 @@ const PDF_EXTRACT_TIMEOUT_MS = 15_000; async function extractPdfText(content: Buffer, filename: string): Promise { // Dynamic import — pdf-parse is only used for PDF attachments. - // eslint-disable-next-line @typescript-eslint/no-explicit-any + let pdfParseFn: (data: Buffer, options?: { max?: number }) => Promise<{ text: string }>; try { // pdf-parse uses a default export; handle both CJS and ESM module shapes. diff --git a/cdk/src/handlers/shared/context-hydration.ts b/cdk/src/handlers/shared/context-hydration.ts index 74e4e3e6..b00cbc80 100644 --- a/cdk/src/handlers/shared/context-hydration.ts +++ b/cdk/src/handlers/shared/context-hydration.ts @@ -465,7 +465,6 @@ async function fetchReviewCommentsGraphQL( let cursor: string | null = null; try { - // eslint-disable-next-line no-constant-condition while (true) { const resp = await fetch('https://api.github.com/graphql', { method: 'POST', diff --git a/cdk/src/handlers/shared/preflight.ts b/cdk/src/handlers/shared/preflight.ts index f852240e..0fd62d47 100644 --- a/cdk/src/handlers/shared/preflight.ts +++ b/cdk/src/handlers/shared/preflight.ts @@ -351,7 +351,7 @@ export async function runPreflightChecks( } // Run reachability + repo access checks in parallel - // eslint-disable-next-line @cdklabs/promiseall-no-unbounded-parallelism + const results = await Promise.allSettled([ checkGitHubReachability(token), checkRepoAccess(repo, token, taskType), diff --git a/cdk/src/handlers/shared/validation.ts b/cdk/src/handlers/shared/validation.ts index c1a24584..474a2780 100644 --- a/cdk/src/handlers/shared/validation.ts +++ b/cdk/src/handlers/shared/validation.ts @@ -221,7 +221,7 @@ export function computeTtlEpoch(retentionDays: number): number { /** Valid task type values. Compile-time check ensures this stays in sync with TaskType. */ const TASK_TYPE_LIST = ['new_task', 'pr_iteration', 'pr_review'] as const satisfies readonly TaskType[]; type _AssertExhaustive = Exclude extends never ? true : never; -const _exhaustiveCheck: _AssertExhaustive = true; // eslint-disable-line @typescript-eslint/no-unused-vars +const _exhaustiveCheck: _AssertExhaustive = true; export const VALID_TASK_TYPES = new Set(TASK_TYPE_LIST); /** @@ -266,7 +266,7 @@ export const MAX_TOTAL_ATTACHMENT_SIZE_BYTES = 50 * 1024 * 1024; /** Compile-time exhaustiveness check for AttachmentType. */ const ATTACHMENT_TYPE_LIST = ['image', 'file', 'url'] as const satisfies readonly AttachmentType[]; type _AssertAttachmentExhaustive = Exclude extends never ? true : never; -const _attachmentExhaustiveCheck: _AssertAttachmentExhaustive = true; // eslint-disable-line @typescript-eslint/no-unused-vars +const _attachmentExhaustiveCheck: _AssertAttachmentExhaustive = true; const VALID_ATTACHMENT_TYPES = new Set(ATTACHMENT_TYPE_LIST); /** Allowed image MIME types (PNG and JPEG only — passed directly to Bedrock). */ diff --git a/cdk/test/bootstrap/bootstrap-template.test.ts b/cdk/test/bootstrap/bootstrap-template.test.ts index 2aa55935..d4bae496 100644 --- a/cdk/test/bootstrap/bootstrap-template.test.ts +++ b/cdk/test/bootstrap/bootstrap-template.test.ts @@ -25,7 +25,7 @@ import * as yaml from 'js-yaml'; import { BOOTSTRAP_VERSION, computeBootstrapHash } from '../../src/bootstrap/version'; const templatePath = join(__dirname, '..', '..', 'bootstrap', 'bootstrap-template.yaml'); -// eslint-disable-next-line @typescript-eslint/no-explicit-any + const template: any = yaml.load(readFileSync(templatePath, 'utf-8')); describe('Bootstrap template', () => { @@ -116,7 +116,7 @@ describe('Bootstrap template', () => { // ECS should be conditional const ecsEntry = fallback.find( - // eslint-disable-next-line @typescript-eslint/no-explicit-any + (item: any) => item['Fn::If'] && item['Fn::If'][0] === 'IncludeComputeEcs', ); expect(ecsEntry).toBeDefined(); @@ -156,7 +156,7 @@ describe('Bootstrap template', () => { expect(items).toContain('Compute-Agentcore'); // ECS should be conditional - // eslint-disable-next-line @typescript-eslint/no-explicit-any + const ecsItem = items.find((item: any) => item['Fn::If']); expect(ecsItem).toBeDefined(); expect(ecsItem['Fn::If'][0]).toBe('IncludeComputeEcs'); diff --git a/cdk/test/handlers/reconcile-stranded-tasks.test.ts b/cdk/test/handlers/reconcile-stranded-tasks.test.ts index 01ef75f9..d3dec495 100644 --- a/cdk/test/handlers/reconcile-stranded-tasks.test.ts +++ b/cdk/test/handlers/reconcile-stranded-tasks.test.ts @@ -260,7 +260,7 @@ describe('reconcile-stranded-tasks', () => { // Spy on the logger module used by the handler. We import the logger // directly and replace the three level methods with jest.fn before // each test so we can assert exactly which level was called. - // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports + // eslint-disable-next-line @typescript-eslint/no-require-imports const loggerModule = require('../../src/handlers/shared/logger') as { logger: { info: (m: string, d?: Record) => void; diff --git a/cdk/test/handlers/shared/sanitization.test.ts b/cdk/test/handlers/shared/sanitization.test.ts index 9874aaf2..52432205 100644 --- a/cdk/test/handlers/shared/sanitization.test.ts +++ b/cdk/test/handlers/shared/sanitization.test.ts @@ -153,9 +153,8 @@ describe('sanitizeExternalContent', () => { }); test('returns empty string for undefined/null input', () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any expect(sanitizeExternalContent(undefined as any)).toBe(''); - // eslint-disable-next-line @typescript-eslint/no-explicit-any + expect(sanitizeExternalContent(null as any)).toBe(''); }); diff --git a/cdk/test/handlers/shared/validation.test.ts b/cdk/test/handlers/shared/validation.test.ts index 13475e6a..3b845e37 100644 --- a/cdk/test/handlers/shared/validation.test.ts +++ b/cdk/test/handlers/shared/validation.test.ts @@ -384,11 +384,10 @@ describe('isValidUlid', () => { }); test('rejects non-string input', () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any expect(isValidUlid(42 as any)).toBe(false); - // eslint-disable-next-line @typescript-eslint/no-explicit-any + expect(isValidUlid(null as any)).toBe(false); - // eslint-disable-next-line @typescript-eslint/no-explicit-any + expect(isValidUlid(undefined as any)).toBe(false); }); }); diff --git a/cli/.eslintrc.json b/cli/.eslintrc.json deleted file mode 100644 index 6d4080b3..00000000 --- a/cli/.eslintrc.json +++ /dev/null @@ -1,379 +0,0 @@ -{ - "env": { - "jest": true, - "node": true - }, - "root": true, - "plugins": [ - "@typescript-eslint", - "import", - "@stylistic", - "license-header", - "jsdoc", - "jest" - ], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaVersion": 2018, - "sourceType": "module", - "project": "./tsconfig.dev.json" - }, - "extends": [ - "plugin:import/typescript" - ], - "settings": { - "import/parsers": { - "@typescript-eslint/parser": [ - ".ts", - ".tsx" - ] - }, - "import/resolver": { - "node": {}, - "typescript": { - "project": "./tsconfig.dev.json", - "alwaysTryTypes": true - } - } - }, - "ignorePatterns": [ - "*.js", - "*.d.ts", - "node_modules/", - "*.generated.ts", - "coverage" - ], - "rules": { - "@stylistic/indent": [ - "error", - 2 - ], - "@stylistic/quotes": [ - "error", - "single", - { - "avoidEscape": true - } - ], - "@stylistic/comma-dangle": [ - "error", - "always-multiline" - ], - "@stylistic/comma-spacing": [ - "error", - { - "before": false, - "after": true - } - ], - "@stylistic/no-multi-spaces": [ - "error", - { - "ignoreEOLComments": false - } - ], - "@stylistic/array-bracket-spacing": [ - "error", - "never" - ], - "@stylistic/array-bracket-newline": [ - "error", - "consistent" - ], - "@stylistic/object-curly-spacing": [ - "error", - "always" - ], - "@stylistic/object-curly-newline": [ - "error", - { - "multiline": true, - "consistent": true - } - ], - "@stylistic/object-property-newline": [ - "error", - { - "allowAllPropertiesOnSameLine": true - } - ], - "@stylistic/keyword-spacing": [ - "error" - ], - "@stylistic/brace-style": [ - "error", - "1tbs", - { - "allowSingleLine": true - } - ], - "@stylistic/space-before-blocks": [ - "error" - ], - "@stylistic/member-delimiter-style": [ - "error" - ], - "@stylistic/semi": [ - "error", - "always" - ], - "@stylistic/max-len": [ - "error", - { - "code": 150, - "ignoreUrls": true, - "ignoreStrings": true, - "ignoreTemplateLiterals": true, - "ignoreComments": true, - "ignoreRegExpLiterals": true - } - ], - "@stylistic/quote-props": [ - "error", - "consistent-as-needed" - ], - "@stylistic/key-spacing": [ - "error" - ], - "@stylistic/no-multiple-empty-lines": [ - "error" - ], - "@stylistic/no-trailing-spaces": [ - "error" - ], - "curly": [ - "error", - "multi-line", - "consistent" - ], - "@typescript-eslint/no-require-imports": "error", - "import/no-extraneous-dependencies": [ - "error", - { - "devDependencies": [ - "**/test/**", - "**/build-tools/**" - ], - "optionalDependencies": false, - "peerDependencies": true - } - ], - "import/no-unresolved": [ - "error" - ], - "import/order": [ - "error", - { - "groups": [ - "builtin", - "external" - ], - "alphabetize": { - "order": "asc", - "caseInsensitive": true - } - } - ], - "import/no-duplicates": [ - "error" - ], - "no-shadow": [ - "off" - ], - "@typescript-eslint/no-shadow": "error", - "@typescript-eslint/no-floating-promises": [ - "error" - ], - "no-return-await": "off", - "@typescript-eslint/return-await": "error", - "dot-notation": [ - "error" - ], - "no-bitwise": [ - "error" - ], - "@typescript-eslint/member-ordering": [ - "error", - { - "default": [ - "public-static-field", - "public-static-method", - "protected-static-field", - "protected-static-method", - "private-static-field", - "private-static-method", - "field", - "constructor", - "method" - ] - } - ], - "license-header/header": [ - "error", - "header.js" - ], - "no-throw-literal": [ - "error" - ], - "quotes": [ - "error", - "single", - { - "avoidEscape": true - } - ], - "@stylistic/no-extra-semi": [ - "error" - ], - "comma-spacing": [ - "error", - { - "before": false, - "after": true - } - ], - "no-multi-spaces": [ - "error", - { - "ignoreEOLComments": false - } - ], - "array-bracket-spacing": [ - "error", - "never" - ], - "array-bracket-newline": [ - "error", - "consistent" - ], - "object-curly-spacing": [ - "error", - "always" - ], - "object-curly-newline": [ - "error", - { - "multiline": true, - "consistent": true - } - ], - "object-property-newline": [ - "error", - { - "allowAllPropertiesOnSameLine": true - } - ], - "keyword-spacing": [ - "error" - ], - "brace-style": [ - "error", - "1tbs", - { - "allowSingleLine": true - } - ], - "space-before-blocks": "error", - "eol-last": [ - "error", - "always" - ], - "@stylistic/spaced-comment": [ - "error", - "always", - { - "exceptions": [ - "/", - "*" - ], - "markers": [ - "/" - ] - } - ], - "@stylistic/padded-blocks": [ - "error", - { - "classes": "never", - "blocks": "never", - "switches": "never" - } - ], - "jsdoc/require-param-description": [ - "error" - ], - "jsdoc/require-property-description": [ - "error" - ], - "jsdoc/require-returns-description": [ - "error" - ], - "jsdoc/check-alignment": [ - "error" - ], - "no-duplicate-imports": [ - "error" - ], - "key-spacing": [ - "error" - ], - "semi": [ - "error", - "always" - ], - "quote-props": [ - "error", - "consistent-as-needed" - ], - "no-multiple-empty-lines": [ - "error", - { - "max": 1 - } - ], - "max-len": [ - "error", - { - "code": 150, - "ignoreUrls": true, - "ignoreStrings": true, - "ignoreTemplateLiterals": true, - "ignoreComments": true, - "ignoreRegExpLiterals": true - } - ], - "no-console": [ - "error" - ], - "no-trailing-spaces": [ - "error" - ], - "no-restricted-syntax": [ - "error", - { - "selector": "CallExpression:matches([callee.name='createHash'], [callee.property.name='createHash']) Literal[value='md5']", - "message": "Use the md5hash() function from the core library if you want md5" - } - ], - "@typescript-eslint/unbound-method": "error", - "jest/expect-expect": "off", - "jest/no-conditional-expect": "off", - "jest/no-done-callback": "off", - "jest/no-standalone-expect": "off", - "jest/valid-expect": "off", - "jest/valid-title": "off", - "jest/no-identical-title": "off", - "jest/no-disabled-tests": "error", - "jest/no-focused-tests": "error" - }, - "overrides": [ - { - "files": [ - "src/**/*.ts" - ], - "rules": { - "no-console": "off" - } - } - ] -} diff --git a/cli/eslint.config.mjs b/cli/eslint.config.mjs new file mode 100644 index 00000000..6b4e2156 --- /dev/null +++ b/cli/eslint.config.mjs @@ -0,0 +1,225 @@ +/** + * MIT No Attribution + * + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +import tseslint from '@typescript-eslint/eslint-plugin'; +import tsparser from '@typescript-eslint/parser'; +import stylistic from '@stylistic/eslint-plugin'; +import importX from 'eslint-plugin-import-x'; +import jest from 'eslint-plugin-jest'; +import jsdoc from 'eslint-plugin-jsdoc'; +import licenseHeader from 'eslint-plugin-license-header'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +export default [ + // Global ignores + { + ignores: [ + '**/*.js', + '**/*.d.ts', + '**/node_modules/', + '**/*.generated.ts', + '**/coverage/', + '!eslint.config.mjs', + ], + }, + + // TypeScript source, test, and build-tools files + { + files: ['src/**/*.ts', 'test/**/*.ts', 'build-tools/**/*.ts'], + languageOptions: { + parser: tsparser, + parserOptions: { + ecmaVersion: 2018, + sourceType: 'module', + project: './tsconfig.dev.json', + }, + }, + plugins: { + '@typescript-eslint': tseslint, + '@stylistic': stylistic, + 'import-x': importX, + 'jest': jest, + 'jsdoc': jsdoc, + 'license-header': licenseHeader, + }, + settings: { + 'import-x/parsers': { + '@typescript-eslint/parser': ['.ts', '.tsx'], + }, + 'import-x/resolver': { + node: {}, + typescript: { + project: './tsconfig.dev.json', + alwaysTryTypes: true, + }, + }, + }, + rules: { + // Stylistic rules + '@stylistic/indent': ['error', 2], + '@stylistic/quotes': ['error', 'single', { avoidEscape: true }], + '@stylistic/comma-dangle': ['error', 'always-multiline'], + '@stylistic/comma-spacing': ['error', { before: false, after: true }], + '@stylistic/no-multi-spaces': ['error', { ignoreEOLComments: false }], + '@stylistic/array-bracket-spacing': ['error', 'never'], + '@stylistic/array-bracket-newline': ['error', 'consistent'], + '@stylistic/object-curly-spacing': ['error', 'always'], + '@stylistic/object-curly-newline': ['error', { multiline: true, consistent: true }], + '@stylistic/object-property-newline': ['error', { allowAllPropertiesOnSameLine: true }], + '@stylistic/keyword-spacing': ['error'], + '@stylistic/brace-style': ['error', '1tbs', { allowSingleLine: true }], + '@stylistic/space-before-blocks': ['error'], + '@stylistic/member-delimiter-style': ['error'], + '@stylistic/semi': ['error', 'always'], + '@stylistic/max-len': ['error', { + code: 150, + ignoreUrls: true, + ignoreStrings: true, + ignoreTemplateLiterals: true, + ignoreComments: true, + ignoreRegExpLiterals: true, + }], + '@stylistic/quote-props': ['error', 'consistent-as-needed'], + '@stylistic/key-spacing': ['error'], + '@stylistic/no-multiple-empty-lines': ['error'], + '@stylistic/no-trailing-spaces': ['error'], + '@stylistic/no-extra-semi': ['error'], + '@stylistic/spaced-comment': ['error', 'always', { + exceptions: ['/', '*'], + markers: ['/'], + }], + '@stylistic/padded-blocks': ['error', { + classes: 'never', + blocks: 'never', + switches: 'never', + }], + + // Core ESLint rules + 'curly': ['error', 'multi-line', 'consistent'], + 'no-shadow': ['off'], + 'no-return-await': 'off', + 'dot-notation': ['error'], + 'no-bitwise': ['error'], + 'no-throw-literal': ['error'], + 'quotes': ['error', 'single', { avoidEscape: true }], + 'comma-spacing': ['error', { before: false, after: true }], + 'no-multi-spaces': ['error', { ignoreEOLComments: false }], + 'array-bracket-spacing': ['error', 'never'], + 'array-bracket-newline': ['error', 'consistent'], + 'object-curly-spacing': ['error', 'always'], + 'object-curly-newline': ['error', { multiline: true, consistent: true }], + 'object-property-newline': ['error', { allowAllPropertiesOnSameLine: true }], + 'keyword-spacing': ['error'], + 'brace-style': ['error', '1tbs', { allowSingleLine: true }], + 'space-before-blocks': 'error', + 'eol-last': ['error', 'always'], + 'no-duplicate-imports': ['error'], + 'key-spacing': ['error'], + 'semi': ['error', 'always'], + 'quote-props': ['error', 'consistent-as-needed'], + 'no-multiple-empty-lines': ['error', { max: 1 }], + 'max-len': ['error', { + code: 150, + ignoreUrls: true, + ignoreStrings: true, + ignoreTemplateLiterals: true, + ignoreComments: true, + ignoreRegExpLiterals: true, + }], + 'no-console': ['error'], + 'no-trailing-spaces': ['error'], + 'no-restricted-syntax': ['error', { + selector: "CallExpression:matches([callee.name='createHash'], [callee.property.name='createHash']) Literal[value='md5']", + message: 'Use the md5hash() function from the core library if you want md5', + }], + + // TypeScript rules + '@typescript-eslint/no-require-imports': 'error', + '@typescript-eslint/no-shadow': 'error', + '@typescript-eslint/no-floating-promises': ['error'], + '@typescript-eslint/return-await': 'error', + '@typescript-eslint/member-ordering': ['error', { + default: [ + 'public-static-field', + 'public-static-method', + 'protected-static-field', + 'protected-static-method', + 'private-static-field', + 'private-static-method', + 'field', + 'constructor', + 'method', + ], + }], + '@typescript-eslint/unbound-method': 'error', + + // Import rules (import/ -> import-x/) + 'import-x/no-extraneous-dependencies': ['error', { + devDependencies: ['**/test/**', '**/build-tools/**'], + optionalDependencies: false, + peerDependencies: true, + }], + 'import-x/no-unresolved': ['error'], + 'import-x/order': ['error', { + groups: ['builtin', 'external'], + alphabetize: { order: 'asc', caseInsensitive: true }, + }], + 'import-x/no-duplicates': ['error'], + + // License header + 'license-header/header': ['error', join(__dirname, 'header.js')], + + // JSDoc rules + 'jsdoc/require-param-description': ['error'], + 'jsdoc/require-property-description': ['error'], + 'jsdoc/require-returns-description': ['error'], + 'jsdoc/check-alignment': ['error'], + + // Jest rules + 'jest/expect-expect': 'off', + 'jest/no-conditional-expect': 'off', + 'jest/no-done-callback': 'off', + 'jest/no-standalone-expect': 'off', + 'jest/valid-expect': 'off', + 'jest/valid-title': 'off', + 'jest/no-identical-title': 'off', + 'jest/no-disabled-tests': 'error', + 'jest/no-focused-tests': 'error', + }, + }, + + // Override: allow console.log in CLI source files (console output is the product) + { + files: ['src/**/*.ts'], + rules: { + 'no-console': 'off', + }, + }, + + // Override: shebang files can't have the license header at line 1 + { + files: ['src/bin/**/*.ts'], + rules: { + 'license-header/header': 'off', + }, + }, +]; diff --git a/cli/mise.toml b/cli/mise.toml index 71585efc..da6a9774 100644 --- a/cli/mise.toml +++ b/cli/mise.toml @@ -21,4 +21,4 @@ run = "yarn jest --passWithNoTests --updateSnapshot" [tasks.eslint] description = "Run ESLint" -run = "ESLINT_USE_FLAT_CONFIG=false ../node_modules/.bin/eslint --ext .ts,.tsx --fix --no-error-on-unmatched-pattern src test build-tools" +run = "../node_modules/.bin/eslint --fix --no-error-on-unmatched-pattern src test build-tools" diff --git a/cli/package.json b/cli/package.json index ca9a1efb..f94616dc 100644 --- a/cli/package.json +++ b/cli/package.json @@ -5,7 +5,7 @@ }, "scripts": { "compile": "tsc --build tsconfig.json", - "eslint": "eslint --ext .ts,.tsx --fix --no-error-on-unmatched-pattern src test build-tools", + "eslint": "eslint --fix --no-error-on-unmatched-pattern src test build-tools", "test": "jest --passWithNoTests --updateSnapshot", "test:watch": "jest --watch", "build": "npm run compile && npm run test && npm run eslint", @@ -17,9 +17,9 @@ "@types/node": "^25.5.0", "@typescript-eslint/eslint-plugin": "^8", "@typescript-eslint/parser": "^8", - "eslint": "^9", - "eslint-import-resolver-typescript": "^4.4.4", - "eslint-plugin-import": "^2.32.0", + "eslint": "^10", + "eslint-import-resolver-typescript": "^4", + "eslint-plugin-import-x": "^4", "eslint-plugin-jest": "^29.15.1", "eslint-plugin-jsdoc": "^62.8.1", "eslint-plugin-license-header": "^0.9.0", @@ -39,9 +39,7 @@ "@aws-sdk/lib-dynamodb": "3.1024.0", "commander": "^14.0.3" }, - "resolutions": { - "eslint-plugin-import/minimatch": "^3.1.2" - }, + "resolutions": {}, "main": "lib/index.js", "license": "MIT-0", "version": "0.0.0", diff --git a/cli/src/commands/watch.ts b/cli/src/commands/watch.ts index fccd30a4..c3d03927 100644 --- a/cli/src/commands/watch.ts +++ b/cli/src/commands/watch.ts @@ -393,7 +393,7 @@ async function withTransientRetry( label: string, ): Promise { let attempt = 0; - // eslint-disable-next-line no-constant-condition + while (true) { try { return await op(); diff --git a/cli/src/wait.ts b/cli/src/wait.ts index 5a007e5a..663d24fc 100644 --- a/cli/src/wait.ts +++ b/cli/src/wait.ts @@ -30,7 +30,6 @@ export async function waitForTask(client: ApiClient, taskId: string): Promise