From be737cad5ad6bbf5b3ee4f6fd3311b4c50d806de Mon Sep 17 00:00:00 2001 From: Shane Garrity Date: Sat, 3 Jan 2026 17:52:24 -0500 Subject: [PATCH 1/8] ci: enforce timeout for github actions jobs --- .github/workflows/build.yml | 1 + .github/workflows/deploy.yml | 1 + .github/workflows/e2e-test.yml | 1 + .github/workflows/install.yml | 1 + .github/workflows/lint.yml | 1 + .github/workflows/type-check.yml | 1 + .github/workflows/unit-test.yml | 1 + 7 files changed, 7 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a7f01a3b..59dcbdc4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,6 +6,7 @@ on: jobs: build: runs-on: ubuntu-22.04 + timeout-minutes: 5 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 4c89a0a9..61e653b4 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -13,6 +13,7 @@ on: jobs: deploy: runs-on: ubuntu-22.04 + timeout-minutes: 10 environment: name: ${{ inputs.environment }} url: ${{ inputs.url }} diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index 1fbbe15b..3c6a96e6 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -6,6 +6,7 @@ on: jobs: e2e-test: runs-on: ubuntu-22.04 + timeout-minutes: 5 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/install.yml b/.github/workflows/install.yml index e6129390..83680322 100644 --- a/.github/workflows/install.yml +++ b/.github/workflows/install.yml @@ -6,6 +6,7 @@ on: jobs: install: runs-on: ubuntu-22.04 + timeout-minutes: 5 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 3b94f162..2b6a79b5 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -6,6 +6,7 @@ on: jobs: lint: runs-on: ubuntu-22.04 + timeout-minutes: 5 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/type-check.yml b/.github/workflows/type-check.yml index c5017cf4..c57e4ad7 100644 --- a/.github/workflows/type-check.yml +++ b/.github/workflows/type-check.yml @@ -6,6 +6,7 @@ on: jobs: type-check: runs-on: ubuntu-22.04 + timeout-minutes: 5 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index e6b186b6..aa3cbb2b 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -6,6 +6,7 @@ on: jobs: unit-test: runs-on: ubuntu-22.04 + timeout-minutes: 5 steps: - uses: actions/checkout@v4 From fa04c31d95e5d9dc6d24410c72537da72306c3d6 Mon Sep 17 00:00:00 2001 From: Shane Garrity Date: Sun, 4 Jan 2026 03:48:08 -0500 Subject: [PATCH 2/8] feat(typescript): add shared base tsconfig --- .vscode/settings.json | 3 ++- common/config/rush/pnpm-lock.yaml | 9 +++++++++ common/config/rush/repo-state.json | 2 +- rush.json | 4 ++++ typescript/README.md | 16 +++++++++++++++ typescript/lib/tsconfig.json | 20 +++++++++++++++++++ typescript/package.json | 31 ++++++++++++++++++++++++++++++ 7 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 typescript/README.md create mode 100644 typescript/lib/tsconfig.json create mode 100644 typescript/package.json diff --git a/.vscode/settings.json b/.vscode/settings.json index f4c402a0..ed4f89be 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,7 +10,8 @@ "eslint-config", "jest-stdout-reporter", "markdownlint-config", - "scaffold" + "scaffold", + "typescript" ], "files.associations": { "rush.json": "jsonc", diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index fb43bfc4..e9b4f15e 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -463,6 +463,15 @@ importers: specifier: ~4.0.1 version: 4.0.4(@types/node@20.19.27) + ../../typescript: + devDependencies: + '@trshcmpctr/markdownlint-config': + specifier: workspace:* + version: link:../markdownlint-config + markdownlint-cli2: + specifier: ~0.20.0 + version: 0.20.0 + packages: '@adobe/css-tools@4.4.4': diff --git a/common/config/rush/repo-state.json b/common/config/rush/repo-state.json index 4a78f1ee..ffe815fe 100644 --- a/common/config/rush/repo-state.json +++ b/common/config/rush/repo-state.json @@ -1,5 +1,5 @@ // DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush. { - "pnpmShrinkwrapHash": "e74465cc56271a5aeb955ebb0a1858135df32e7a", + "pnpmShrinkwrapHash": "1da6e0dffa3dad54bff621a733dd845a06cf2562", "preferredVersionsHash": "bf21a9e8fbc5a3846fb05b4fa0859e0917b2202f" } diff --git a/rush.json b/rush.json index 5ed28c91..6e9dc794 100644 --- a/rush.json +++ b/rush.json @@ -355,6 +355,10 @@ * 3. It's useful to have a centralized inventory of all projects and their important metadata. */ "projects": [ + { + "packageName": "@trshcmpctr/typescript", + "projectFolder": "typescript" + }, { "packageName": "@trshcmpctr/eslint-config-node", "projectFolder": "eslint-config-node" diff --git a/typescript/README.md b/typescript/README.md new file mode 100644 index 00000000..39921718 --- /dev/null +++ b/typescript/README.md @@ -0,0 +1,16 @@ +# @trshcmpctr/typescript + +Shared base typescript config + +## Usage + +In `tsconfig.json`: + +```json +{ + "extends": "@trshcmpctr/typescript", + "include": [ + "src" + ] +} +``` diff --git a/typescript/lib/tsconfig.json b/typescript/lib/tsconfig.json new file mode 100644 index 00000000..8c06707f --- /dev/null +++ b/typescript/lib/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + // Treat `import x from 'y'` as `import * as x from 'y'` + // when y has no default export + "esModuleInterop": true, + + // Apply stricter rules around properties which have a ? prefix + "exactOptionalPropertyTypes": true, + + // Enforce file case sensitivity + "forceConsistentCasingInFileNames": true, + + // Ignore inconsistent global definitions, i.e. from Jest and Cypress + "skipLibCheck": true, + + // Turn on all strict mode family options for + // "stronger guarantees of program correctness" + "strict": true + } +} diff --git a/typescript/package.json b/typescript/package.json new file mode 100644 index 00000000..e46b029b --- /dev/null +++ b/typescript/package.json @@ -0,0 +1,31 @@ +{ + "name": "@trshcmpctr/typescript", + "version": "1.0.0", + "private": true, + "description": "Shared base typescript config", + "homepage": "https://github.com/shanedg/trshcmpctr/tree/main/typescript#readme", + "repository": { + "type": "git", + "url": "https://github.com/shanedg/trshcmpctr.git", + "directory": "typescript" + }, + "license": "UNLICENSED", + "type": "module", + "exports": "./lib/tsconfig.json", + "main": "./lib/tsconfig.json", + "files": [ + "lib" + ], + "scripts": { + "build": "", + "lint:md": "markdownlint-cli2 --config node_modules/@trshcmpctr/markdownlint-config/.markdownlint-cli2.jsonc", + "test": "" + }, + "devDependencies": { + "@trshcmpctr/markdownlint-config": "workspace:*", + "markdownlint-cli2": "~0.20.0" + }, + "engines": { + "node": ">=20.19.5" + } +} From 95df8e8a63836b51c3c7aed0050bfdfc2624a1f1 Mon Sep 17 00:00:00 2001 From: Shane Garrity Date: Sun, 4 Jan 2026 03:52:04 -0500 Subject: [PATCH 3/8] build(client): use shared tsconfig Duplicate shared tsconfig strict checks in Cypress tsconfig to work around crash/hang when extending tsconfig from a package --- client/cypress/tsconfig.json | 25 ++++++++++++++++++++++++- client/package.json | 1 + client/tsconfig.base.json | 23 ----------------------- client/tsconfig.json | 30 +++++++++++++++--------------- common/config/rush/pnpm-lock.yaml | 3 +++ common/config/rush/repo-state.json | 2 +- 6 files changed, 44 insertions(+), 40 deletions(-) delete mode 100644 client/tsconfig.base.json diff --git a/client/cypress/tsconfig.json b/client/cypress/tsconfig.json index 014f1083..a5c06f85 100644 --- a/client/cypress/tsconfig.json +++ b/client/cypress/tsconfig.json @@ -1,13 +1,36 @@ { - "extends": "../tsconfig.base.json", + // Cypress throws and hangs if using "extends" with a package reference. + // Issue https://github.com/cypress-io/cypress/issues/28385 + // supposedly fixed in Cypress 13.6.2 but reproducible here with: + // - Cypress 13.17.0 + // - Node 20.19.5 + // - Typescript 5.9.3 + // "extends": "@trshcmpctr/typescript", "compilerOptions": { + // Treat `import x from 'y'` as `import * as x from 'y'` + // when y has no default export + "esModuleInterop": true, + + // Apply stricter rules around properties which have a ? prefix + "exactOptionalPropertyTypes": true, + + // Enforce file case sensitivity + "forceConsistentCasingInFileNames": true, + // Core definitions + DOM definitions "lib": [ "es5", "dom" ], + // Ignore inconsistent global definitions, i.e. from Jest and Cypress + "skipLibCheck": true, + + // Turn on all strict mode family options for + // "stronger guarantees of program correctness" + "strict": true, + // Downlevel for Cypress test runner (?) "target": "es5", diff --git a/client/package.json b/client/package.json index 607bb255..96f8ce2e 100644 --- a/client/package.json +++ b/client/package.json @@ -65,6 +65,7 @@ "@trshcmpctr/eslint-config-typescript": "workspace:*", "@trshcmpctr/jest-stdout-reporter": "workspace:*", "@trshcmpctr/markdownlint-config": "workspace:*", + "@trshcmpctr/typescript": "workspace:*", "@types/jest": "~30.0.0", "@types/node": "~20.19.25", "@types/prop-types": "~15.7.12", diff --git a/client/tsconfig.base.json b/client/tsconfig.base.json deleted file mode 100644 index df8eeb44..00000000 --- a/client/tsconfig.base.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "compilerOptions": { - // Treat `import x from 'y'` as `import * as x from 'y'` - // when y has no default export - "esModuleInterop": true, - - // Apply stricter rules around properties which have a ? prefix - "exactOptionalPropertyTypes": true, - - // Enforce file case sensitivity - "forceConsistentCasingInFileNames": true, - - // Support dynamic imports and import.meta - "module": "ES2020", - - // Ignore inconsistent global definitions from Jest and Cypress - "skipLibCheck": true, - - // Turn on all strict mode family options for - // "stronger guarantees of program correctness" - "strict": true, - } -} diff --git a/client/tsconfig.json b/client/tsconfig.json index eeb76fdc..b8e4be6e 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -1,28 +1,28 @@ { - "extends": "./tsconfig.base.json", + "extends": "@trshcmpctr/typescript", "compilerOptions": { // Leave JSX transformation to Babel "jsx": "preserve", - // Latest supported APIs + DOM definitions - "lib": ["ESNext", "DOM"], + "lib": [ + // Package code runs in the browser + "DOM", + // Node 20.19.5 supports 100% of ES2023 features + // See + "ES2023" + ], - // Node.js' CommonJS implementation - "moduleResolution": "node", - // TODO: `"moduleResolution": "Node16"`: Node.js' ECMAScript Module Support from TypeScript 4.5 onwards. - // > Relative import paths need explicit file extensions in EcmaScript imports when '--moduleResolution' is 'node16' or 'nodenext'. - // But! - // > An import path cannot end with a '.tsx' extension. ...ts(2691) - // Maybe use `extensionAlias` released in webpack@5.74.0: - // https://github.com/webpack/webpack/releases/tag/v5.74.0 - // See https://github.com/webpack/webpack/issues/13252 + // Package code is resolved by Webpack + // "moduleResolution" is "bundler" by default if "module" is "preserve" + "module": "preserve", - // Leave transpilation to Babel + // Package is built with Babel "noEmit": true, - // Ensure module is set to at least "ES6/ES2015" - "target": "ESNext", + // Package is built with Babel but Node 20.19.5 supports 100% of ES2023 features + // See + "target": "es2023", }, "include": [ diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index e9b4f15e..178fcd8f 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -79,6 +79,9 @@ importers: '@trshcmpctr/markdownlint-config': specifier: workspace:* version: link:../markdownlint-config + '@trshcmpctr/typescript': + specifier: workspace:* + version: link:../typescript '@types/jest': specifier: ~30.0.0 version: 30.0.0 diff --git a/common/config/rush/repo-state.json b/common/config/rush/repo-state.json index ffe815fe..ca18b747 100644 --- a/common/config/rush/repo-state.json +++ b/common/config/rush/repo-state.json @@ -1,5 +1,5 @@ // DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush. { - "pnpmShrinkwrapHash": "1da6e0dffa3dad54bff621a733dd845a06cf2562", + "pnpmShrinkwrapHash": "fb6defa8028dc10ad4b07b7a9fab0089bfc4b86d", "preferredVersionsHash": "bf21a9e8fbc5a3846fb05b4fa0859e0917b2202f" } From c29b24664eee0e3185dc23be586113647ed74700 Mon Sep 17 00:00:00 2001 From: Shane Garrity Date: Sun, 4 Jan 2026 04:09:06 -0500 Subject: [PATCH 4/8] build(client): support transforming more files and add compilation targets --- client/babel.config.js | 23 +++++++++++++---------- client/babel.config.lib.js | 5 +++++ client/jest.config.js | 30 +++++++++++------------------- client/package.json | 2 -- 4 files changed, 29 insertions(+), 31 deletions(-) diff --git a/client/babel.config.js b/client/babel.config.js index ef65177f..8e0ee1e6 100644 --- a/client/babel.config.js +++ b/client/babel.config.js @@ -2,21 +2,20 @@ export default { env: { test: { plugins: [ + // Jest still expects CommonJS '@babel/plugin-transform-modules-commonjs', ], }, }, - only: [ - // Only files in src/ are built & transpiled by Babel - './src', - // jest-setup.ts is the only .ts module in the package root, - // needs to be transformed by Babel for Jest to understand it - './jest-setup.ts', - ], - - plugins: [ - '@babel/plugin-syntax-dynamic-import', + /** + * Most files built & transpiled by Babel are just in src/ + * but other local projects and some config file extensions may also need processing: + * i.e. @trshcmpctr/components, jest-setup.ts + */ + ignore: [ + 'common/temp/node_modules/.pnpm', + 'node_modules', ], presets: [ @@ -38,4 +37,8 @@ export default { } ], ], + + targets: { + browsers: 'defaults', + }, }; diff --git a/client/babel.config.lib.js b/client/babel.config.lib.js index 87c385a4..fa54bf92 100644 --- a/client/babel.config.lib.js +++ b/client/babel.config.lib.js @@ -4,6 +4,7 @@ * Differences from babel.config.js: * Does not need dynamic imports * Does not need preset-env + corejs + * Does not need to target browsers */ export default { only: [ @@ -18,4 +19,8 @@ export default { } ], ], + targets: { + // Compile against the current node version + node: 'current', + }, }; diff --git a/client/jest.config.js b/client/jest.config.js index 39034b56..bac5cb91 100644 --- a/client/jest.config.js +++ b/client/jest.config.js @@ -1,26 +1,18 @@ export default { - rootDir: './', - transform: { - // Only files in src/ are built & transpiled by Babel, so only these should be transformed. - // Note that the transform pattern is matched against the full path. - // This creates some trouble in CI because Drone's default workspace already includes src/ (/drone/src). - // To workaround this, include the package directory in the transform pattern (/client/src) - // as a defense against transforming unintended files. - // Unfortunately, we can't use in this pattern. - '\\/client\\/src\\/.+\\.[t|j]sx?$': [ - 'babel-jest', - ], - // jest-setup.ts needs to be .ts to include types for - // extended Jest matchers from @testing-library/jest-dom - '\\/client\\/jest-setup\\.ts$': [ - 'babel-jest', - ], - }, collectCoverage: true, - testEnvironment: 'jsdom', - setupFilesAfterEnv: ['/jest-setup.ts'], moduleNameMapper: { // See https://jestjs.io/docs/webpack#handling-static-assets '\\.(css|less)$': '/__mocks__/styleMock.cjs', }, + setupFilesAfterEnv: ['/jest-setup.ts'], + testEnvironment: 'jsdom', + /** + * Most files built & transpiled by Babel are just in src/ + * but other local projects and some config file extensions may also need processing: + * i.e. @trshcmpctr/components, jest-setup.ts + */ + transformIgnorePatterns: [ + 'common/temp/node_modules/.pnpm', + 'node_modules', + ], }; diff --git a/client/package.json b/client/package.json index 96f8ce2e..c260662f 100644 --- a/client/package.json +++ b/client/package.json @@ -50,7 +50,6 @@ }, "devDependencies": { "@babel/core": "~7.28.5", - "@babel/plugin-syntax-dynamic-import": "~7.8.3", "@babel/plugin-transform-modules-commonjs": "~7.27.1", "@babel/preset-env": "~7.28.5", "@babel/preset-react": "~7.28.5", @@ -73,7 +72,6 @@ "@types/react-dom": "~19.2.3", "babel-jest": "~30.2.0", "babel-loader": "~10.0.0", - "babel-plugin-syntax-dynamic-import": "~6.18.0", "css-loader": "~7.1.1", "cypress": "13.17.0", "discord-api-types": "~0.38.33", From ece896ddd37cc835442db3bc15e9b71ae9cb9fe9 Mon Sep 17 00:00:00 2001 From: Shane Garrity Date: Sun, 4 Jan 2026 04:10:24 -0500 Subject: [PATCH 5/8] feat(eslint-config): enforce type-only import ordering --- eslint-config/lib/eslint-config.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/eslint-config/lib/eslint-config.js b/eslint-config/lib/eslint-config.js index bf4e9199..23430f11 100644 --- a/eslint-config/lib/eslint-config.js +++ b/eslint-config/lib/eslint-config.js @@ -60,6 +60,8 @@ export default defineConfig([ 'type', ], 'newlines-between': 'always', + // Mirror groups for `import type` statements + sortTypesGroup: true, }], 'indent': ['warn', 2], // Warn on unused variables unless underscore-prefixed args From a50a8c91f4afa106bc7c132e6f06a06e3b7486a9 Mon Sep 17 00:00:00 2001 From: Shane Garrity Date: Sun, 4 Jan 2026 04:14:20 -0500 Subject: [PATCH 6/8] feat(client): use shared nav component --- client/src/App/components/Home.tsx | 17 ++--------------- client/src/App/components/Nav.tsx | 21 +++++++++++++++++++++ client/src/App/components/NewWorld.tsx | 15 ++------------- client/src/App/components/Worlds.tsx | 13 ++----------- 4 files changed, 27 insertions(+), 39 deletions(-) create mode 100644 client/src/App/components/Nav.tsx diff --git a/client/src/App/components/Home.tsx b/client/src/App/components/Home.tsx index 9a9963af..e42db354 100644 --- a/client/src/App/components/Home.tsx +++ b/client/src/App/components/Home.tsx @@ -1,9 +1,8 @@ import { type APIGuildMember } from 'discord-api-types/v10'; -import { Link } from 'react-router'; import { ErrorCard } from './ErrorCard'; import { LoadingContent } from './LoadingContent'; -import { LogoutLink } from './LogoutLink'; +import { Nav } from './Nav'; import { Unauthorized } from './Unauthorized'; import { Welcome } from './Welcome'; import { useLatestRequest } from '../hooks/use-latest-request'; @@ -28,19 +27,7 @@ export const Home = () => { if (guildUser) { return ( <> - +