From dc240998effbb718c4d3af16ac0e85a787725701 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 00:20:38 +0000 Subject: [PATCH 1/8] Initial plan From 983fff8e5c449424bb90365a8eb0dcd8e4aa7bf8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 00:24:08 +0000 Subject: [PATCH 2/8] Add essential GitHub workflows for security, quality, and automation Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- .github/labeler.yml | 46 ++++++++++++++++++++ .github/workflows/codeql.yml | 45 +++++++++++++++++++ .github/workflows/dependency-review.yml | 27 ++++++++++++ .github/workflows/labeler.yml | 23 ++++++++++ .github/workflows/stale.yml | 52 ++++++++++++++++++++++ .github/workflows/typecheck.yml | 36 ++++++++++++++++ .github/workflows/validate-metadata.yml | 57 +++++++++++++++++++++++++ 7 files changed, 286 insertions(+) create mode 100644 .github/labeler.yml create mode 100644 .github/workflows/codeql.yml create mode 100644 .github/workflows/dependency-review.yml create mode 100644 .github/workflows/labeler.yml create mode 100644 .github/workflows/stale.yml create mode 100644 .github/workflows/typecheck.yml create mode 100644 .github/workflows/validate-metadata.yml diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 00000000..49aeaf71 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,46 @@ +# Configuration for PR auto-labeling +# Labels are applied based on file path patterns + +'๐Ÿ“ฆ dependencies': + - changed-files: + - any-glob-to-any-file: ['package.json', 'pnpm-lock.yaml', '**/package.json'] + +'๐Ÿ“š documentation': + - changed-files: + - any-glob-to-any-file: ['docs/**/*', '**/*.md', 'README.md'] + +'๐Ÿ”ง tooling': + - changed-files: + - any-glob-to-any-file: ['packages/tools/**/*', '.github/**/*', 'scripts/**/*'] + +'๐Ÿ—๏ธ foundation': + - changed-files: + - any-glob-to-any-file: ['packages/foundation/**/*'] + +'๐Ÿ”Œ drivers': + - changed-files: + - any-glob-to-any-file: ['packages/drivers/**/*'] + +'๐Ÿš€ runtime': + - changed-files: + - any-glob-to-any-file: ['packages/runtime/**/*'] + +'๐Ÿ“ examples': + - changed-files: + - any-glob-to-any-file: ['examples/**/*'] + +'โšก starters': + - changed-files: + - any-glob-to-any-file: ['packages/starters/**/*'] + +'๐Ÿงช tests': + - changed-files: + - any-glob-to-any-file: ['**/*.test.ts', '**/*.spec.ts', '**/jest.config.js'] + +'๐Ÿ”’ security': + - changed-files: + - any-glob-to-any-file: ['**/*.permission.yml', '**/security/**/*'] + +'โš™๏ธ configuration': + - changed-files: + - any-glob-to-any-file: ['tsconfig*.json', '.gitignore', '.npmrc', 'pnpm-workspace.yaml'] diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 00000000..c10bdb49 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,45 @@ +name: "CodeQL Security Scan" + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + schedule: + # Run every Monday at 00:00 UTC + - cron: '0 0 * * 1' + +jobs: + analyze: + name: Analyze Code + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'javascript' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + # If you want to specify custom queries, add them here + # queries: security-extended,security-and-quality + + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 00000000..50c5a1ba --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,27 @@ +name: "Dependency Review" + +on: + pull_request: + branches: [ "main" ] + +permissions: + contents: read + pull-requests: write + +jobs: + dependency-review: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Dependency Review + uses: actions/dependency-review-action@v4 + with: + # Fail the action if vulnerabilities with severity >= moderate are detected + fail-on-severity: moderate + # Warn about deprecated packages + warn-on-deprecated: true + # Comment on the PR with the review results + comment-summary-in-pr: on-failure diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml new file mode 100644 index 00000000..d581832b --- /dev/null +++ b/.github/workflows/labeler.yml @@ -0,0 +1,23 @@ +name: "Pull Request Labeler" + +on: + pull_request: + types: [opened, synchronize, reopened] + +permissions: + contents: read + pull-requests: write + +jobs: + labeler: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Run Labeler + uses: actions/labeler@v5 + with: + repo-token: "${{ secrets.GITHUB_TOKEN }}" + configuration-path: .github/labeler.yml diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 00000000..2d1fbc95 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,52 @@ +name: "Stale Issue Management" + +on: + schedule: + # Run every day at 00:00 UTC + - cron: '0 0 * * *' + workflow_dispatch: + +permissions: + issues: write + pull-requests: write + +jobs: + stale: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Mark/Close Stale Issues and PRs + uses: actions/stale@v9 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + + # Issues + stale-issue-message: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed in 14 days if no further activity occurs. + Thank you for your contributions. + close-issue-message: > + This issue was automatically closed because it has not had activity for 30 days. + Please feel free to reopen if you believe this issue is still relevant. + days-before-issue-stale: 60 + days-before-issue-close: 14 + stale-issue-label: 'stale' + exempt-issue-labels: 'pinned,security,roadmap,help wanted' + + # Pull Requests + stale-pr-message: > + This pull request has been automatically marked as stale because it has not had + recent activity. It will be closed in 7 days if no further activity occurs. + Please address any review comments or conflicts. + close-pr-message: > + This pull request was automatically closed because it has not had activity for 30 days. + Please feel free to reopen and address the review comments if you wish to continue. + days-before-pr-stale: 30 + days-before-pr-close: 7 + stale-pr-label: 'stale' + exempt-pr-labels: 'pinned,security,in progress' + + # General settings + operations-per-run: 100 + remove-stale-when-updated: true + ascending: false diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml new file mode 100644 index 00000000..b2207c81 --- /dev/null +++ b/.github/workflows/typecheck.yml @@ -0,0 +1,36 @@ +name: Type Check + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + typecheck: + name: TypeScript Type Check + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v3 + with: + version: 10 + + - name: Use Node.js 20.x + uses: actions/setup-node@v4 + with: + node-version: 20.x + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + timeout-minutes: 5 + + - name: Run TypeScript type check + run: | + echo "Running TypeScript compiler in type-check mode..." + pnpm tsc -b --noEmit + timeout-minutes: 5 diff --git a/.github/workflows/validate-metadata.yml b/.github/workflows/validate-metadata.yml new file mode 100644 index 00000000..c93da1b0 --- /dev/null +++ b/.github/workflows/validate-metadata.yml @@ -0,0 +1,57 @@ +name: Validate Metadata Files + +on: + push: + branches: [ "main" ] + paths: + - '**/*.object.yml' + - '**/*.validation.yml' + - '**/*.permission.yml' + - '**/*.app.yml' + - '**/*.page.yml' + - '**/*.menu.yml' + pull_request: + branches: [ "main" ] + paths: + - '**/*.object.yml' + - '**/*.validation.yml' + - '**/*.permission.yml' + - '**/*.app.yml' + - '**/*.page.yml' + - '**/*.menu.yml' + +jobs: + validate: + name: Validate YAML Metadata + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v3 + with: + version: 10 + + - name: Use Node.js 20.x + uses: actions/setup-node@v4 + with: + node-version: 20.x + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + timeout-minutes: 5 + + - name: Build packages + run: pnpm run build + timeout-minutes: 10 + + - name: Validate YAML syntax + run: | + echo "Checking YAML syntax for metadata files..." + find . -type f \( -name "*.object.yml" -o -name "*.validation.yml" -o -name "*.permission.yml" -o -name "*.app.yml" -o -name "*.page.yml" -o -name "*.menu.yml" \) | while read file; do + echo "Validating: $file" + node -e "const yaml = require('js-yaml'); const fs = require('fs'); try { yaml.load(fs.readFileSync('$file', 'utf8')); console.log('โœ“ Valid'); } catch(e) { console.error('โœ— Invalid:', e.message); process.exit(1); }" + done + timeout-minutes: 5 From 6be76461b07362eb1d4ccf765ea1729aa8f7bc69 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 00:27:01 +0000 Subject: [PATCH 3/8] Address code review feedback: optimize validation workflow and fix stale message Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- .github/workflows/stale.yml | 3 +- .github/workflows/validate-metadata.yml | 9 +-- scripts/validate-yaml.js | 99 +++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 9 deletions(-) create mode 100755 scripts/validate-yaml.js diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 2d1fbc95..feeabc46 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -39,7 +39,8 @@ jobs: recent activity. It will be closed in 7 days if no further activity occurs. Please address any review comments or conflicts. close-pr-message: > - This pull request was automatically closed because it has not had activity for 30 days. + This pull request was automatically closed because it has not had activity for 37 days + (marked stale after 30 days, then closed after 7 more days of inactivity). Please feel free to reopen and address the review comments if you wish to continue. days-before-pr-stale: 30 days-before-pr-close: 7 diff --git a/.github/workflows/validate-metadata.yml b/.github/workflows/validate-metadata.yml index c93da1b0..fd41a821 100644 --- a/.github/workflows/validate-metadata.yml +++ b/.github/workflows/validate-metadata.yml @@ -42,16 +42,9 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile timeout-minutes: 5 - - - name: Build packages - run: pnpm run build - timeout-minutes: 10 - name: Validate YAML syntax run: | echo "Checking YAML syntax for metadata files..." - find . -type f \( -name "*.object.yml" -o -name "*.validation.yml" -o -name "*.permission.yml" -o -name "*.app.yml" -o -name "*.page.yml" -o -name "*.menu.yml" \) | while read file; do - echo "Validating: $file" - node -e "const yaml = require('js-yaml'); const fs = require('fs'); try { yaml.load(fs.readFileSync('$file', 'utf8')); console.log('โœ“ Valid'); } catch(e) { console.error('โœ— Invalid:', e.message); process.exit(1); }" - done + pnpm exec node scripts/validate-yaml.js timeout-minutes: 5 diff --git a/scripts/validate-yaml.js b/scripts/validate-yaml.js new file mode 100755 index 00000000..df15ad45 --- /dev/null +++ b/scripts/validate-yaml.js @@ -0,0 +1,99 @@ +#!/usr/bin/env node +/** + * Validate YAML syntax for ObjectQL metadata files + * + * This script validates all metadata files in the repository: + * - *.object.yml + * - *.validation.yml + * - *.permission.yml + * - *.app.yml + * - *.page.yml + * - *.menu.yml + */ + +const yaml = require('js-yaml'); +const fs = require('fs'); +const path = require('path'); + +// Define metadata file extensions +const extensions = [ + '.object.yml', + '.validation.yml', + '.permission.yml', + '.app.yml', + '.page.yml', + '.menu.yml' +]; + +// Directories to exclude +const excludeDirs = ['node_modules', 'dist', '.git']; + +/** + * Recursively find all files matching the specified extensions + */ +function findFiles(dir, fileList = []) { + const files = fs.readdirSync(dir); + + files.forEach(file => { + const filePath = path.join(dir, file); + const stat = fs.statSync(filePath); + + if (stat.isDirectory()) { + // Skip excluded directories + if (!excludeDirs.includes(file)) { + findFiles(filePath, fileList); + } + } else { + // Check if file matches any of our extensions + if (extensions.some(ext => file.endsWith(ext))) { + fileList.push(filePath); + } + } + }); + + return fileList; +} + +let hasErrors = false; +let validCount = 0; +let errorCount = 0; + +console.log('๐Ÿ” Validating ObjectQL metadata YAML files...\n'); + +// Find and validate all metadata files +const files = findFiles(process.cwd()); + +if (files.length === 0) { + console.log('โ„น๏ธ No metadata files found to validate.'); + process.exit(0); +} + +files.forEach(file => { + try { + const content = fs.readFileSync(file, 'utf8'); + yaml.load(content); + console.log(`โœ“ ${path.relative(process.cwd(), file)}`); + validCount++; + } catch (error) { + console.error(`โœ— ${path.relative(process.cwd(), file)}`); + console.error(` Error: ${error.message}`); + if (error.mark) { + console.error(` Line ${error.mark.line + 1}, Column ${error.mark.column + 1}`); + } + console.error(''); + hasErrors = true; + errorCount++; + } +}); + +// Print summary +console.log('\n' + '='.repeat(60)); +if (hasErrors) { + console.error(`โŒ Validation failed: ${errorCount} error(s) found`); + console.log(`โœ“ Valid files: ${validCount}`); + console.error(`โœ— Invalid files: ${errorCount}`); + process.exit(1); +} else { + console.log(`โœ… All ${validCount} YAML metadata file(s) are valid!`); + process.exit(0); +} From 90cc66bf5e5f19c1c1d3ec8e221c6cd7a47aa182 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 00:29:17 +0000 Subject: [PATCH 4/8] Optimize validation script with concurrent file processing Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- scripts/validate-yaml.js | 70 +++++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 26 deletions(-) diff --git a/scripts/validate-yaml.js b/scripts/validate-yaml.js index df15ad45..659ecbfb 100755 --- a/scripts/validate-yaml.js +++ b/scripts/validate-yaml.js @@ -60,7 +60,7 @@ let errorCount = 0; console.log('๐Ÿ” Validating ObjectQL metadata YAML files...\n'); -// Find and validate all metadata files +// Find all metadata files const files = findFiles(process.cwd()); if (files.length === 0) { @@ -68,32 +68,50 @@ if (files.length === 0) { process.exit(0); } -files.forEach(file => { - try { - const content = fs.readFileSync(file, 'utf8'); - yaml.load(content); - console.log(`โœ“ ${path.relative(process.cwd(), file)}`); - validCount++; - } catch (error) { - console.error(`โœ— ${path.relative(process.cwd(), file)}`); - console.error(` Error: ${error.message}`); - if (error.mark) { - console.error(` Line ${error.mark.line + 1}, Column ${error.mark.column + 1}`); +// Validate all files concurrently +async function validateFiles() { + const results = await Promise.allSettled( + files.map(async (file) => { + const content = await fs.promises.readFile(file, 'utf8'); + yaml.load(content); + return file; + }) + ); + + results.forEach((result, index) => { + const file = files[index]; + const relativePath = path.relative(process.cwd(), file); + + if (result.status === 'fulfilled') { + console.log(`โœ“ ${relativePath}`); + validCount++; + } else { + const error = result.reason; + console.error(`โœ— ${relativePath}`); + console.error(` Error: ${error.message}`); + if (error.mark) { + console.error(` Line ${error.mark.line + 1}, Column ${error.mark.column + 1}`); + } + console.error(''); + hasErrors = true; + errorCount++; } - console.error(''); - hasErrors = true; - errorCount++; + }); + + // Print summary + console.log('\n' + '='.repeat(60)); + if (hasErrors) { + console.error(`โŒ Validation failed: ${errorCount} error(s) found`); + console.log(`โœ“ Valid files: ${validCount}`); + console.error(`โœ— Invalid files: ${errorCount}`); + process.exit(1); + } else { + console.log(`โœ… All ${validCount} YAML metadata file(s) are valid!`); + process.exit(0); } -}); +} -// Print summary -console.log('\n' + '='.repeat(60)); -if (hasErrors) { - console.error(`โŒ Validation failed: ${errorCount} error(s) found`); - console.log(`โœ“ Valid files: ${validCount}`); - console.error(`โœ— Invalid files: ${errorCount}`); +validateFiles().catch((error) => { + console.error('Unexpected error during validation:', error); process.exit(1); -} else { - console.log(`โœ… All ${validCount} YAML metadata file(s) are valid!`); - process.exit(0); -} +}); \ No newline at end of file From c4b831efd7763f7c13c58c1eadbda964d04d8b5d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 00:30:50 +0000 Subject: [PATCH 5/8] Fix stale issue message accuracy (74 days total) Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- .github/workflows/stale.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index feeabc46..adf9c32a 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -26,7 +26,8 @@ jobs: recent activity. It will be closed in 14 days if no further activity occurs. Thank you for your contributions. close-issue-message: > - This issue was automatically closed because it has not had activity for 30 days. + This issue was automatically closed because it has not had activity for 74 days + (marked stale after 60 days, then closed after 14 more days of inactivity). Please feel free to reopen if you believe this issue is still relevant. days-before-issue-stale: 60 days-before-issue-close: 14 From 3e7a7d7dfbae3393dcd72572fe9c3154c56268cb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 00:41:33 +0000 Subject: [PATCH 6/8] Address code review feedback: add path triggers and improve security Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com> --- .github/workflows/labeler.yml | 3 +++ .github/workflows/validate-metadata.yml | 2 ++ scripts/validate-yaml.js | 6 +++++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index d581832b..ec922871 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -3,6 +3,9 @@ name: "Pull Request Labeler" on: pull_request: types: [opened, synchronize, reopened] + paths: + - '**/*' + - '.github/labeler.yml' permissions: contents: read diff --git a/.github/workflows/validate-metadata.yml b/.github/workflows/validate-metadata.yml index fd41a821..b31fa0b3 100644 --- a/.github/workflows/validate-metadata.yml +++ b/.github/workflows/validate-metadata.yml @@ -10,6 +10,7 @@ on: - '**/*.app.yml' - '**/*.page.yml' - '**/*.menu.yml' + - 'scripts/validate-yaml.js' pull_request: branches: [ "main" ] paths: @@ -19,6 +20,7 @@ on: - '**/*.app.yml' - '**/*.page.yml' - '**/*.menu.yml' + - 'scripts/validate-yaml.js' jobs: validate: diff --git a/scripts/validate-yaml.js b/scripts/validate-yaml.js index 659ecbfb..f666ef84 100755 --- a/scripts/validate-yaml.js +++ b/scripts/validate-yaml.js @@ -9,6 +9,10 @@ * - *.app.yml * - *.page.yml * - *.menu.yml + * + * Dependencies: + * - Requires 'js-yaml' from devDependencies + * - Run 'pnpm install' before executing this script */ const yaml = require('js-yaml'); @@ -73,7 +77,7 @@ async function validateFiles() { const results = await Promise.allSettled( files.map(async (file) => { const content = await fs.promises.readFile(file, 'utf8'); - yaml.load(content); + yaml.load(content, { schema: yaml.DEFAULT_SAFE_SCHEMA }); return file; }) ); From fb423426a75a7e131b2a79d0fc4dfcf033d587e6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 00:42:51 +0000 Subject: [PATCH 7/8] Remove redundant path filter from labeler workflow Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com> --- .github/workflows/labeler.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index ec922871..d581832b 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -3,9 +3,6 @@ name: "Pull Request Labeler" on: pull_request: types: [opened, synchronize, reopened] - paths: - - '**/*' - - '.github/labeler.yml' permissions: contents: read From b518bfdaf6df4b0476009167b2b3e3ed9df4d231 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 00:52:06 +0000 Subject: [PATCH 8/8] Fix typecheck workflow: remove --noEmit flag incompatible with tsc -b Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com> --- .github/workflows/typecheck.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index b2207c81..1ec30737 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -31,6 +31,6 @@ jobs: - name: Run TypeScript type check run: | - echo "Running TypeScript compiler in type-check mode..." - pnpm tsc -b --noEmit + echo "Running TypeScript compiler in build mode for type checking..." + pnpm tsc -b timeout-minutes: 5