diff --git a/.github/codeql/codeql-configuration.yml b/.github/codeql/codeql-configuration.yml new file mode 100644 index 00000000..912999c8 --- /dev/null +++ b/.github/codeql/codeql-configuration.yml @@ -0,0 +1,7 @@ +name : CodeQL Configuration + +paths-ignore: + # Contains syntax errors. + - 'packages/ts-twoslasher/test/fixtures/**' + # Contains code examples with syntax errors. + - '**/copy/en/**' diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 00000000..8d9350f4 --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,120 @@ +name: CI +on: + pull_request: + branches: + - v2 + +# Ensure scripts are run with pipefail. See: +# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#exit-codes-and-error-action-preference +defaults: + run: + shell: bash + +jobs: + tests: + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + - windows-latest + - macos-latest + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: "18.x" + - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + + - run: pnpm install + + # Grab localizations + - run: pnpm docs-sync pull microsoft/TypeScript-Website-localizations#main 1 + + # Build the packages + - run: pnpm bootstrap + - run: pnpm build + + # Verify it compiles + - run: pnpm build-site + + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + if: github.event_name == 'pull_request' && matrix.os == 'ubuntu-latest' + with: + name: site + path: packages/typescriptlang-org/public + + # Run all the package's tests + - run: pnpm test + + # danger for PR builds + - if: github.event_name == 'pull_request' && github.event.pull_request.base.repo.id == github.event.pull_request.head.repo.id && matrix.os == 'ubuntu-latest' + run: "pnpm danger ci" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - run: | + git add . + if ! git diff --staged --exit-code --quiet; then + echo "This PR is missing some generated changes. Please update locally or merge the patch artifact." + echo "" + git diff --staged + git diff --staged > missing.patch + exit 1 + fi + name: Check for uncommitted changes + id: check-diff + if: github.event_name == 'pull_request' + + - name: Upload diff artifact + if: ${{ failure() && steps.check-diff.conclusion == 'failure' }} + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: missing.patch + path: missing.patch + + changesets: + name: changesets + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + fetch-depth: 0 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: 'lts/*' + - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + + - run: pnpm install + + - name: Check for missing changesets + run: | + PR_CHANGESETS=$(ls .changeset | (grep -v -E 'README\.md|config\.json' || true) | wc -l) + MAIN_CHANGESETS=$(git ls-tree -r origin/v2 .changeset | (grep -v -E 'README\.md|config\.json' || true) | wc -l) + + # If the PR has no changesets, but main has changesets, assume this is PR is a versioning PR and exit + if [[ $PR_CHANGESETS -eq 0 && $MAIN_CHANGESETS -gt 0 ]]; then + echo "This PR is a versioning PR, exiting" + exit 0 + fi + + # git switch -c changesets-temp + # git checkout origin/v2 -- + pnpm changeset status --since=origin/v2 + + required: + runs-on: ubuntu-latest + if: ${{ always() }} + needs: + - tests + - changesets + + steps: + - name: Check required jobs + env: + NEEDS: ${{ toJson(needs) }} + run: | + ! echo $NEEDS | jq -e 'to_entries[] | { job: .key, result: .value.result } | select(.result != "success")' diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 00000000..ff2b106b --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,71 @@ +name: 'Code Scanning - Action' + +on: + push: + branches: + - v2 + pull_request: + branches: + - v2 + schedule: + # ┌───────────── minute (0 - 59) + # │ ┌───────────── hour (0 - 23) + # │ │ ┌───────────── day of the month (1 - 31) + # │ │ │ ┌───────────── month (1 - 12 or JAN-DEC) + # │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT) + # │ │ │ │ │ + # │ │ │ │ │ + # │ │ │ │ │ + # * * * * * + - cron: '30 1 * * 0' + +permissions: + contents: read + +# Ensure scripts are run with pipefail. See: +# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#exit-codes-and-error-action-preference +defaults: + run: + shell: bash + +jobs: + CodeQL-Build: + # CodeQL runs on ubuntu-latest, windows-latest, and macos-latest + runs-on: ubuntu-latest + if: github.repository == 'microsoft/TypeScript-Website' + + permissions: + # required for all workflows + security-events: write + + steps: + - name: Checkout repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@df559355d593797519d70b90fc8edd5db049e7a2 # v3.29.9 + with: + config-file: ./.github/codeql/codeql-configuration.yml + # Override language selection by uncommenting this and choosing your languages + # with: + # languages: go, javascript, csharp, python, cpp, java + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below). + - name: Autobuild + uses: github/codeql-action/autobuild@df559355d593797519d70b90fc8edd5db049e7a2 # v3.29.9 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # ✏️ If the Autobuild fails above, remove it and uncomment the following + # three lines and modify them (or add more) to build your code if your + # project uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@df559355d593797519d70b90fc8edd5db049e7a2 # v3.29.9 diff --git a/.github/workflows/deploy-preview.yml b/.github/workflows/deploy-preview.yml new file mode 100644 index 00000000..315d84f5 --- /dev/null +++ b/.github/workflows/deploy-preview.yml @@ -0,0 +1,188 @@ +name: Deploy preview environment +on: + workflow_run: + workflows: [CI] + types: [completed] + workflow_dispatch: + inputs: + pr: + required: true + type: string + description: PR number + pull_request_target: + types: [labeled] + +permissions: + contents: read + pull-requests: write + actions: read + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Get PR/workflow run info + id: get-info + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + script: | + console.dir(context, { depth: null }); + + let pr; + let workflowRun; + let isLabel = false; + switch (context.eventName) { + case "workflow_dispatch": + console.log("Workflow dispatch event"); + pr = (await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: +context.payload.inputs.pr, + })).data; + break; + case "pull_request_target": + console.log("Pull request target event"); + pr = context.payload.pull_request; + break; + case "workflow_run": + console.log("Workflow run event"); + workflowRun = context.payload.workflow_run; + pr = workflowRun.pull_requests[0]; + if (pr) { + console.log("PR found in workflow run"); + // Reload the PR to get the labels. + pr = (await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: pr.number, + })).data; + } else { + const q = `is:pr is:open repo:${context.repo.owner}/${context.repo.repo} sha:${workflowRun.head_sha}`; + console.log(`PR not found in workflow run, searching for it with ${q}`); + // PRs sent from forks do not get the pull_requests field set. + // https://github.com/orgs/community/discussions/25220 + const response = await github.rest.search.issuesAndPullRequests({ q }); + if (response.data.items.length > 0) { + pr = response.data.items[0]; + } + } + } + + if (!pr) { + console.log("Could not find PR"); + return null; + } + + console.log(`Found PR ${pr.html_url}`); + console.dir(pr, { depth: null }); + + if (pr.state !== "open") { + console.log(`PR ${pr.number} is not open`); + return null; + } + + if (!pr.labels.some((label) => label.name === "deploy-preview")) { + console.log(`PR ${pr.number} does not have the deploy-preview label`); + return null; + } + + if (!workflowRun) { + console.log(`No workflow run found in event, searching for it with ${pr.head.sha}`); + try { + workflowRun = (await github.rest.actions.listWorkflowRuns({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: "CI.yml", + head_sha: pr.head.sha, + per_page: 1, + })).data.workflow_runs[0]; + } catch (e) { + console.log(e); + } + } + + if (!workflowRun) { + console.log(`Could not find workflow run for PR ${pr.number}`); + return null; + } + + console.log(`Found workflow run ${workflowRun.html_url}`); + console.dir(workflowRun, { depth: null }); + + try { + const artifacts = await github.rest.actions.listWorkflowRunArtifacts({ + owner: "microsoft", + repo: "TypeScript-Website", + run_id: workflowRun.id, + }); + console.dir(artifacts, { depth: null }); + if (!artifacts.data.artifacts.some(x => x.name === "site")) { + console.log("No artifact found in workflow run"); + return null; + } + } catch (e) { + console.log(e); + return null; + } + + const result = { pr: pr.number, runId: workflowRun.id }; + console.log(result); + return result; + + - name: Download site build from PR + if: ${{ steps.get-info.outputs.result != 'null' }} + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 + with: + name: site + path: site + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ fromJson(steps.get-info.outputs.result).runId }} + + - name: Deploy + id: deploy + if: ${{ steps.get-info.outputs.result != 'null' }} + uses: Azure/static-web-apps-deploy@v1 + with: + azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_PREVIEW }} + # Unsetting the repo token so we can control commenting ourselves. + # repo_token: ${{ secrets.GITHUB_TOKEN }} + action: "upload" + app_location: "site" + skip_app_build: true + production_branch: v2 + deployment_environment: ${{ fromJson(steps.get-info.outputs.result).pr }} + + - name: Comment on PR + if: ${{ steps.get-info.outputs.result != 'null' }} + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + env: + PR_NUMBER: ${{ fromJson(steps.get-info.outputs.result).pr }} + SITE_URL: ${{ steps.deploy.outputs.static_web_app_url }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const prefix = "Azure Static Web Apps: Your stage site is ready!"; + const comments = await github.paginate(github.rest.issues.listComments, { + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: +process.env.PR_NUMBER, + per_page: 100, + }); + + for (const comment of comments) { + if (comment.user?.login === "github-actions[bot]" && comment.body?.startsWith(prefix)) { + console.log(`Deleting comment ${comment.id}`); + await github.rest.issues.deleteComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: comment.id, + }); + } + } + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: +process.env.PR_NUMBER, + body: `${prefix} Visit it here: ${process.env.SITE_URL}` + }); diff --git a/.github/workflows/deploy-prod-static.yml b/.github/workflows/deploy-prod-static.yml new file mode 100644 index 00000000..a9962eae --- /dev/null +++ b/.github/workflows/deploy-prod-static.yml @@ -0,0 +1,70 @@ +name: Deploy to GitHub Pages + +on: + push: + branches: + - v2 + workflow_dispatch: + schedule: + # https://crontab.guru/#0_12_*_*_1 + - cron: "0 12 * * 1" + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + # Fetch the full history, to build attribution.json + fetch-depth: 0 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: "18.x" + + - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + + # Builds the modules, and boostraps the other modules + - name: Build website + run: | + pnpm install + pnpm docs-sync pull microsoft/TypeScript-Website-localizations#main 1 + pnpm bootstrap + pnpm run --filter=typescriptlang-org setup-playground-cache-bust + pnpm build + + - name: Makes the site + run: | + pnpm build-site + cp -r packages/typescriptlang-org/public site + + - name: Setup Pages + uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5 + + - name: Upload artifact + uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3 + with: + path: './site' + + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4 diff --git a/.github/workflows/keepalive.yml b/.github/workflows/keepalive.yml new file mode 100644 index 00000000..62f4a46f --- /dev/null +++ b/.github/workflows/keepalive.yml @@ -0,0 +1,21 @@ +name: Keep alive +on: + schedule: + - cron: "0 0 * * *" + +jobs: + keepalive: + runs-on: ubuntu-latest + + permissions: + contents: write + + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - run: | + if [[ $(git log --format="%H" --since "50 days" | head -c1 | wc -c) == 0 ]]; then + git config user.email "typescriptbot@microsoft.com" + git config user.name "TypeScript Bot" + git commit --allow-empty -m "Automated commit to keep GitHub Actions active" + git push + fi diff --git a/.github/workflows/publish-packages.yml b/.github/workflows/publish-packages.yml new file mode 100644 index 00000000..76c0938f --- /dev/null +++ b/.github/workflows/publish-packages.yml @@ -0,0 +1,41 @@ +name: Publish packages + +on: + push: + branches: [v2] + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +concurrency: ${{ github.workflow }}-${{ github.ref }} + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + fetch-depth: 0 + filter: blob:none + token: ${{ secrets.TS_BOT_TOKEN }} + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: "18.x" + registry-url: "https://registry.npmjs.org/" + - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 + + - name: Prepare website v2 + run: | + pnpm install + pnpm bootstrap + pnpm build + + - uses: changesets/action@e0145edc7d9d8679003495b11f87bd8ef63c0cba # v1.5.3 + with: + publish: pnpm ci:publish + env: + GITHUB_TOKEN: ${{ secrets.TS_BOT_TOKEN }} + NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} diff --git a/packages/documentation/copy/en/modules-reference/Theory.md b/packages/documentation/copy/en/modules-reference/Theory.md index 3c54f7f9..5e64354c 100644 --- a/packages/documentation/copy/en/modules-reference/Theory.md +++ b/packages/documentation/copy/en/modules-reference/Theory.md @@ -180,11 +180,11 @@ Can an ES module `import` a CommonJS module? If so, does a default import link t 1. **ESM-only.** Some runtimes, like browser engines, only support what’s actually a part of the language: ECMAScript Modules. 2. **Bundler-like.** Before any major JavaScript engine could run ES modules, Babel allowed developers to write them by transpiling them to CommonJS. The way these ESM-transpiled-to-CJS files interacted with hand-written-CJS files implied a set of permissive interoperability rules that have become the de facto standard for bundlers and transpilers. -3. **Node.js.** Until Node.js v22.12.0, CommonJS modules could not load ES modules synchronously (with `require`); they could only load them asynchronously with dynamic `import()` calls. ES modules can default-import CJS modules, which always binds to `exports`. (This means that a default import of a Babel-like CJS output with `__esModule` behaves differently between Node.js and some bundlers.) +3. **Node.js.** Until Node.js v20.19.0, CommonJS modules could not load ES modules synchronously (with `require`); they could only load them asynchronously with dynamic `import()` calls. ES modules can default-import CJS modules, which always binds to `exports`. (This means that a default import of a Babel-like CJS output with `__esModule` behaves differently between Node.js and some bundlers.) TypeScript needs to know which of these rule sets to assume in order to provide correct types on (particularly `default`) imports and to error on imports that will crash at runtime. When the `module` compiler option is set to `node16`, `node18`, or `nodenext`, Node.js’s version-specific rules are enforced.[^1] All other `module` settings, combined with the [`esModuleInterop`](/docs/handbook/modules/reference.html#esModuleInterop) option, result in bundler-like interop in TypeScript. (While using `--module esnext` does prevent you from _writing_ CommonJS modules, it does not prevent you from _importing_ them as dependencies. There’s currently no TypeScript setting that can guard against an ES module importing a CommonJS module, as would be appropriate for direct-to-browser code.) -[^1]: In Node.js v22.12.0 and later, a `require` of an ES module is allowed, but only if the resolved module and its top-level imports don’t use top-level `await`. TypeScript does not try to enforce this rule, as it lacks the ability to tell from a declaration file whether the corresponding JavaScript file contains top-level `await`. +[^1]: In Node.js v20.19.0 and later, a `require` of an ES module is allowed, but only if the resolved module and its top-level imports don’t use top-level `await`. TypeScript does not try to enforce this rule, as it lacks the ability to tell from a declaration file whether the corresponding JavaScript file contains top-level `await`. ### Module specifiers are not transformed by default @@ -341,7 +341,7 @@ import { add } from "./math.ts"; This restriction applies since TypeScript [won’t rewrite the extension](#module-specifiers-are-not-transformed) to `.js`, and if `"./math.ts"` appears in an output JS file, that import won’t resolve to another JS file at runtime. TypeScript really wants to prevent you from generating an unsafe output JS file. But what if there _is_ no output JS file? What if you’re in one of these situations: - You’re bundling this code, the bundler is configured to transpile TypeScript files in-memory, and it will eventually consume and erase all the imports you’ve written to produce a bundle. -- You’re running this code directly in a TypeScript runtime like Deno or Bun. +- You’re running this code directly in a TypeScript runtime like Node, Deno or Bun. - You’re using `ts-node`, `tsx`, or another transpiling loader for Node. In these cases, you can turn on `noEmit` (or `emitDeclarationOnly`) and `allowImportingTsExtensions` to disable emitting unsafe JavaScript files and silence the error on `.ts`-extensioned imports. diff --git a/packages/documentation/copy/en/project-config/Project References.md b/packages/documentation/copy/en/project-config/Project References.md index f7daf7c9..17bcedce 100644 --- a/packages/documentation/copy/en/project-config/Project References.md +++ b/packages/documentation/copy/en/project-config/Project References.md @@ -172,7 +172,7 @@ Another good practice is to have a "solution" `tsconfig.json` file that simply h This presents a simple entry point; e.g. in the TypeScript repo we simply run `tsc -b src` to build all endpoints because we list all the subprojects in `src/tsconfig.json` -You can see these patterns in the TypeScript repo - see `src/tsconfig_base.json`, `src/tsconfig.json`, and `src/tsc/tsconfig.json` as key examples. +You can see these patterns in the TypeScript repo - see `src/tsconfig-base.json`, `src/tsconfig.json`, and `src/tsc/tsconfig.json` as key examples. ### Structuring for relative modules