Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9ea3e0dab2 | |||
| 74006809be | |||
| 13f1e03047 | |||
| 3bb6abea9f | |||
| bf8848da95 | |||
| 90ff03d42d | |||
| 4a3a8241c3 | |||
| d194248f53 | |||
| ea04a2ed72 | |||
| 3587aeabb7 | |||
| 4039970495 | |||
| 415979e554 | |||
| c8e4e0f9ef | |||
| d12a4f4b12 | |||
| e08f3479e6 | |||
| 9fa8930afe | |||
| bae3acd48e | |||
| e4e9973db3 | |||
| 1a235d7236 | |||
| b371512462 | |||
| b21af21657 | |||
| e6d8b3913b | |||
| ab311c0f07 | |||
| 9cd7579040 | |||
| 4bd64770a5 | |||
| 9dd4766059 | |||
| 50561ff009 | |||
| 5dbe040374 | |||
| 30781f7fe3 | |||
| 2c55685492 | |||
| cfe32c2560 | |||
| 3d710ce923 | |||
| cecd60710b | |||
| 3be73ede0f | |||
| 12af01453a | |||
| 8575fa1495 | |||
| 013da9ea1c | |||
| 1c238c0a74 | |||
| 176c792e34 | |||
| 533e9ffc25 | |||
| 487aa6fa78 |
@@ -60,7 +60,7 @@ body:
|
||||
label: Package Manager Version
|
||||
description: |
|
||||
If `nx report` doesn't work, please provide the name and the version of your package manager.
|
||||
You can get version information by running `yarn --version`, `pnpm --version` or `npm --version`, depending on the package manager used.
|
||||
You can get version information by running `PACKAGE_MANAGER --version`, where PACKAGE_MANAGER is any of `yarn`, `pnpm`, `bun` or `npm`, depending on the package manager used.
|
||||
- type: checkboxes
|
||||
id: os
|
||||
attributes:
|
||||
|
||||
@@ -24,24 +24,33 @@ jobs:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
- windows-latest
|
||||
node_version:
|
||||
- 18
|
||||
- 20
|
||||
- 22
|
||||
# - 23
|
||||
exclude:
|
||||
# run just node v20 on macos
|
||||
# run just node v20 on macos and windows
|
||||
- os: macos-latest
|
||||
node_version: 18
|
||||
- os: macos-latest
|
||||
node_version: 22
|
||||
# - os: macos-latest
|
||||
# node_version: 23
|
||||
- os: windows-latest
|
||||
node_version: 18
|
||||
- os: windows-latest
|
||||
node_version: 22
|
||||
# - os: windows-latest
|
||||
# node_version: 23
|
||||
|
||||
name: Cache install (${{ matrix.os }}, node v${{ matrix.node_version }})
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
@@ -99,300 +108,41 @@ jobs:
|
||||
if: steps.cache-cypress.outputs.cache-hit != 'true'
|
||||
run: npx cypress install
|
||||
|
||||
prepare-matrix:
|
||||
name: Prepare matrix combinations
|
||||
if: ${{ github.repository_owner == 'nrwl' }}
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.process-json.outputs.MATRIX }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Process matrix data
|
||||
id: process-json
|
||||
run:
|
||||
echo "MATRIX=$(npx tsx .github/workflows/nightly/process-matrix.ts | jq -c .)" >> $GITHUB_OUTPUT
|
||||
|
||||
e2e:
|
||||
if: ${{ github.repository_owner == 'nrwl' }}
|
||||
needs: preinstall
|
||||
needs:
|
||||
- preinstall
|
||||
- prepare-matrix
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 90
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
node_version:
|
||||
- 18
|
||||
- 20
|
||||
- 22
|
||||
# - 23
|
||||
package_manager:
|
||||
- npm
|
||||
- yarn
|
||||
- pnpm
|
||||
project:
|
||||
- e2e-angular
|
||||
- e2e-cypress
|
||||
- e2e-detox
|
||||
- e2e-esbuild
|
||||
- e2e-eslint
|
||||
- e2e-expo
|
||||
- e2e-gradle
|
||||
- e2e-jest
|
||||
- e2e-js
|
||||
- e2e-lerna-smoke-tests
|
||||
- e2e-next
|
||||
- e2e-node
|
||||
- e2e-nuxt
|
||||
- e2e-nx-init
|
||||
- e2e-nx
|
||||
- e2e-playwright
|
||||
- e2e-plugin
|
||||
- e2e-react
|
||||
- e2e-react-native
|
||||
- e2e-release
|
||||
- e2e-remix
|
||||
- e2e-rollup
|
||||
- e2e-storybook
|
||||
- e2e-vite
|
||||
- e2e-vue
|
||||
- e2e-web
|
||||
- e2e-webpack
|
||||
- e2e-workspace-create
|
||||
include:
|
||||
# os short names
|
||||
- os: ubuntu-latest
|
||||
os_name: 'Linux'
|
||||
- os: macos-latest
|
||||
os_name: 'MacOS'
|
||||
# test timeouts
|
||||
- os: ubuntu-latest
|
||||
os_timeout: 60
|
||||
- os: macos-latest
|
||||
os_timeout: 90
|
||||
# codeowner groups
|
||||
- project: e2e-angular
|
||||
codeowners: 'S04SS457V38'
|
||||
- project: e2e-cypress
|
||||
codeowners: 'S04T16BTJJY'
|
||||
- project: e2e-detox
|
||||
codeowners: 'S04TNCNJG5N'
|
||||
- project: e2e-esbuild
|
||||
codeowners: 'S04SJ6HHP0X'
|
||||
- project: e2e-expo
|
||||
codeowners: 'S04TNCNJG5N'
|
||||
- project: e2e-gradle
|
||||
codeowners: 'S04TNCNJG5N'
|
||||
- project: e2e-jest
|
||||
codeowners: 'S04T16BTJJY'
|
||||
- project: e2e-js
|
||||
codeowners: 'S04SJ6HHP0X'
|
||||
- project: e2e-lerna-smoke-tests
|
||||
codeowners: 'S04TNCVEETS'
|
||||
- project: e2e-eslint
|
||||
codeowners: 'S04SYJGKSCT'
|
||||
- project: e2e-next
|
||||
codeowners: 'S04TNCNJG5N'
|
||||
- project: e2e-node
|
||||
codeowners: 'S04SJ6HHP0X'
|
||||
- project: e2e-nx-init
|
||||
codeowners: 'S04SYHYKGNP'
|
||||
- project: e2e-nx
|
||||
codeowners: 'S04SYHYKGNP'
|
||||
- project: e2e-plugin
|
||||
codeowners: 'S04SYHYKGNP'
|
||||
- project: e2e-release
|
||||
codeowners: 'S04SYHYKGNP'
|
||||
- project: e2e-react
|
||||
codeowners: 'S04TNCNJG5N'
|
||||
- project: e2e-react-native
|
||||
codeowners: 'S04TNCNJG5N'
|
||||
- project: e2e-web
|
||||
codeowners: 'S04SJ6PL98X'
|
||||
- project: e2e-rollup
|
||||
codeowners: 'S04SJ6PL98X'
|
||||
- project: e2e-storybook
|
||||
codeowners: 'S04SVQ8H0G5'
|
||||
- project: e2e-playwright
|
||||
codeowners: 'S04SVQ8H0G5'
|
||||
- project: e2e-remix
|
||||
codeowners: 'S04SVQ8H0G5'
|
||||
- project: e2e-vite
|
||||
codeowners: 'S04SJ6PL98X'
|
||||
- project: e2e-vue
|
||||
codeowners: 'S04SJ6PL98X'
|
||||
- project: e2e-nuxt
|
||||
codeowners: 'S04SJ6PL98X'
|
||||
- project: e2e-webpack
|
||||
codeowners: 'S04SJ6PL98X'
|
||||
- project: e2e-workspace-create
|
||||
codeowners: 'S04SYHYKGNP'
|
||||
exclude:
|
||||
# exclude react-native tests from ubuntu
|
||||
- os: ubuntu-latest
|
||||
project: e2e-react-native
|
||||
- os: ubuntu-latest
|
||||
project: e2e-detox
|
||||
- os: ubuntu-latest
|
||||
project: e2e-expo
|
||||
# exclude non-CNW/Lerna tests from non-LTS node versions
|
||||
- node_version: 18
|
||||
project: e2e-angular
|
||||
- node_version: 18
|
||||
project: e2e-cypress
|
||||
- node_version: 18
|
||||
project: e2e-detox
|
||||
- node_version: 18
|
||||
project: e2e-esbuild
|
||||
- node_version: 18
|
||||
project: e2e-expo
|
||||
- node_version: 18
|
||||
project: e2e-gradle
|
||||
- node_version: 18
|
||||
project: e2e-jest
|
||||
- node_version: 18
|
||||
project: e2e-js
|
||||
- node_version: 18
|
||||
project: e2e-eslint
|
||||
- node_version: 18
|
||||
project: e2e-next
|
||||
- node_version: 18
|
||||
project: e2e-node
|
||||
- node_version: 18
|
||||
project: e2e-nuxt
|
||||
- node_version: 18
|
||||
project: e2e-nx-init
|
||||
- node_version: 18
|
||||
project: e2e-nx
|
||||
- node_version: 18
|
||||
project: e2e-plugin
|
||||
- node_version: 18
|
||||
project: e2e-playwright
|
||||
- node_version: 18
|
||||
project: e2e-react
|
||||
- node_version: 18
|
||||
project: e2e-react-native
|
||||
- node_version: 18
|
||||
project: e2e-web
|
||||
- node_version: 18
|
||||
project: e2e-remix
|
||||
- node_version: 18
|
||||
project: e2e-rollup
|
||||
- node_version: 18
|
||||
project: e2e-storybook
|
||||
- node_version: 18
|
||||
project: e2e-vite
|
||||
- node_version: 18
|
||||
project: e2e-vue
|
||||
- node_version: 18
|
||||
project: e2e-webpack
|
||||
- node_version: 22
|
||||
project: e2e-angular
|
||||
- node_version: 22
|
||||
project: e2e-cypress
|
||||
- node_version: 22
|
||||
project: e2e-detox
|
||||
- node_version: 22
|
||||
project: e2e-esbuild
|
||||
- node_version: 22
|
||||
project: e2e-expo
|
||||
- node_version: 22
|
||||
project: e2e-gradle
|
||||
- node_version: 22
|
||||
project: e2e-jest
|
||||
- node_version: 22
|
||||
project: e2e-js
|
||||
- node_version: 22
|
||||
project: e2e-eslint
|
||||
- node_version: 22
|
||||
project: e2e-next
|
||||
- node_version: 22
|
||||
project: e2e-node
|
||||
- node_version: 22
|
||||
project: e2e-nuxt
|
||||
- node_version: 22
|
||||
project: e2e-nx-init
|
||||
- node_version: 22
|
||||
project: e2e-nx
|
||||
- node_version: 22
|
||||
project: e2e-plugin
|
||||
- node_version: 22
|
||||
project: e2e-playwright
|
||||
- node_version: 22
|
||||
project: e2e-react
|
||||
- node_version: 22
|
||||
project: e2e-react-native
|
||||
- node_version: 22
|
||||
project: e2e-web
|
||||
- node_version: 22
|
||||
project: e2e-remix
|
||||
- node_version: 22
|
||||
project: e2e-rollup
|
||||
- node_version: 22
|
||||
project: e2e-storybook
|
||||
- node_version: 22
|
||||
project: e2e-vite
|
||||
- node_version: 22
|
||||
project: e2e-vue
|
||||
- node_version: 22
|
||||
project: e2e-webpack
|
||||
# - node_version: 23
|
||||
# project: e2e-angular
|
||||
# - node_version: 23
|
||||
# project: e2e-cypress
|
||||
# - node_version: 23
|
||||
# project: e2e-detox
|
||||
# - node_version: 23
|
||||
# project: e2e-esbuild
|
||||
# - node_version: 23
|
||||
# project: e2e-expo
|
||||
# - node_version: 23
|
||||
# project: e2e-gradle
|
||||
# - node_version: 23
|
||||
# project: e2e-jest
|
||||
# - node_version: 23
|
||||
# project: e2e-js
|
||||
# - node_version: 23
|
||||
# project: e2e-eslint
|
||||
# - node_version: 23
|
||||
# project: e2e-next
|
||||
# - node_version: 23
|
||||
# project: e2e-node
|
||||
# - node_version: 23
|
||||
# project: e2e-nuxt
|
||||
# - node_version: 23
|
||||
# project: e2e-nx-init
|
||||
# - node_version: 23
|
||||
# project: e2e-nx
|
||||
# - node_version: 23
|
||||
# project: e2e-plugin
|
||||
# - node_version: 23
|
||||
# project: e2e-playwright
|
||||
# - node_version: 23
|
||||
# project: e2e-react
|
||||
# - node_version: 23
|
||||
# project: e2e-react-native
|
||||
# - node_version: 23
|
||||
# project: e2e-web
|
||||
# - node_version: 23
|
||||
# project: e2e-remix
|
||||
# - node_version: 23
|
||||
# project: e2e-rollup
|
||||
# - node_version: 23
|
||||
# project: e2e-storybook
|
||||
# - node_version: 23
|
||||
# project: e2e-vite
|
||||
# - node_version: 23
|
||||
# project: e2e-vue
|
||||
# - node_version: 23
|
||||
# project: e2e-webpack
|
||||
# run just npm v20 on macos
|
||||
- os: macos-latest
|
||||
package_manager: yarn
|
||||
- os: macos-latest
|
||||
package_manager: pnpm
|
||||
- os: macos-latest
|
||||
node_version: 18
|
||||
- os: macos-latest
|
||||
node_version: 22
|
||||
# - os: macos-latest
|
||||
# node_version: 23
|
||||
matrix: ${{fromJson(needs.prepare-matrix.outputs.matrix)}} # Load matrix from previous job
|
||||
fail-fast: false
|
||||
|
||||
name: ${{ matrix.os_name }}/${{ matrix.package_manager }}/${{ matrix.node_version }} ${{ join(matrix.project) }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Prepare dir for output
|
||||
run: mkdir -p outputs
|
||||
@@ -470,30 +220,28 @@ jobs:
|
||||
|
||||
- name: Set starting timestamp
|
||||
id: before-e2e
|
||||
shell: bash
|
||||
run: |
|
||||
echo "timestamp=$(date +%s)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Run e2e tests
|
||||
id: e2e-run
|
||||
run: pnpm nx run-many -t e2e-local -p ${{ matrix.project }}
|
||||
run: pnpm nx run ${{ matrix.project }}:e2e-local
|
||||
shell: bash
|
||||
timeout-minutes: ${{ matrix.os_timeout }}
|
||||
env:
|
||||
GIT_AUTHOR_EMAIL: test@test.com
|
||||
GIT_AUTHOR_NAME: Test
|
||||
GIT_COMMITTER_EMAIL: test@test.com
|
||||
GIT_COMMITTER_NAME: Test
|
||||
NX_E2E_CI_CACHE_KEY: e2e-gha-${{ matrix.os }}-${{ matrix.node_version }}-${{ matrix.package_manager }}
|
||||
NX_DAEMON: 'true'
|
||||
NX_PERF_LOGGING: 'false'
|
||||
NX_E2E_VERBOSE_LOGGING: 'true'
|
||||
NX_NATIVE_LOGGING: 'false'
|
||||
NX_E2E_RUN_E2E: 'true'
|
||||
NX_CLOUD_NO_TIMEOUTS: 'true'
|
||||
NX_E2E_SKIP_CLEANUP: 'true'
|
||||
NODE_OPTIONS: --max_old_space_size=8192
|
||||
SELECTED_PM: ${{ matrix.package_manager }}
|
||||
npm_config_registry: http://localhost:4872
|
||||
YARN_REGISTRY: http://localhost:4872
|
||||
NX_CACHE_DIRECTORY: 'tmp'
|
||||
NX_E2E_SKIP_CLEANUP: 'true'
|
||||
NX_E2E_RUN_E2E: 'true'
|
||||
NX_E2E_VERBOSE_LOGGING: 'true'
|
||||
NX_PERF_LOGGING: 'false'
|
||||
NX_DAEMON: 'true'
|
||||
NX_SKIP_LOG_GROUPING: 'true'
|
||||
|
||||
- name: Save matrix config in file
|
||||
if: ${{ always() }}
|
||||
@@ -521,6 +269,8 @@ jobs:
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled && failure() }}
|
||||
uses: mxschmitt/action-tmate@v3.8
|
||||
timeout-minutes: 15
|
||||
with:
|
||||
sudo: ${{ matrix.os != 'windows-latest' }} # disable sudo for windows debugging
|
||||
|
||||
process-result:
|
||||
if: ${{ always() && github.repository_owner == 'nrwl' }}
|
||||
@@ -580,6 +330,7 @@ jobs:
|
||||
});
|
||||
result += `\`\`\``;
|
||||
core.setOutput('SLACK_MESSAGE', trimSpace(result));
|
||||
console.log(trimSpace(result));
|
||||
|
||||
function humanizeDuration(num) {
|
||||
let res = '';
|
||||
@@ -607,7 +358,7 @@ jobs:
|
||||
};
|
||||
const macosProjects = ['e2e-detox', 'e2e-expo', 'e2e-react-native'];
|
||||
combined.forEach((matrix) => {
|
||||
if (matrix.os_name === 'Linux' && matrix.node_version === 18) {
|
||||
if (matrix.os_name === 'Linux' && matrix.node_version === 20) {
|
||||
pmReport[matrix.package_manager] += matrix.duration;
|
||||
}
|
||||
if (matrix.os_name === 'Linux' || macosProjects.includes(matrix.project)) {
|
||||
|
||||
@@ -1,470 +0,0 @@
|
||||
name: E2E matrix (Windows)
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 5 * * *"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
debug_enabled:
|
||||
type: boolean
|
||||
description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
|
||||
required: false
|
||||
default: false
|
||||
|
||||
env:
|
||||
CYPRESS_CACHE_FOLDER: ${{ github.workspace }}/.cypress
|
||||
|
||||
permissions: { }
|
||||
jobs:
|
||||
preinstall:
|
||||
if: ${{ github.repository_owner == 'nrwl' }}
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node_version:
|
||||
- 18
|
||||
- 20
|
||||
- 22
|
||||
# - 23
|
||||
|
||||
name: Cache install (node v${{ matrix.node_version }})
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 9.8.0
|
||||
run_install: false
|
||||
|
||||
- name: Set node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node_version }}
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Cache node_modules
|
||||
id: cache-modules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
lookup-only: true
|
||||
path: '**/node_modules'
|
||||
key: ${{ runner.os }}-modules-${{ matrix.node_version }}-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
|
||||
- name: Install packages
|
||||
if: steps.cache-modules.outputs.cache-hit != 'true'
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Cache Cypress
|
||||
id: cache-cypress
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
lookup-only: true
|
||||
path: '${{ github.workspace }}/.cypress'
|
||||
key: windows-cypress
|
||||
|
||||
- name: Install Cypress
|
||||
if: steps.cache-cypress.outputs.cache-hit != 'true'
|
||||
run: pnpm cypress install
|
||||
|
||||
e2e:
|
||||
if: ${{ github.repository_owner == 'nrwl' }}
|
||||
needs: preinstall
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node_version:
|
||||
- 18
|
||||
- 20
|
||||
- 22
|
||||
# - 23
|
||||
package_manager:
|
||||
- npm
|
||||
project:
|
||||
- e2e-angular
|
||||
- e2e-cypress
|
||||
- e2e-esbuild
|
||||
- e2e-eslint
|
||||
- e2e-jest
|
||||
- e2e-js
|
||||
- e2e-lerna-smoke-tests
|
||||
- e2e-next
|
||||
- e2e-node
|
||||
- e2e-nuxt
|
||||
- e2e-nx-init
|
||||
- e2e-nx
|
||||
- e2e-playwright
|
||||
- e2e-plugin
|
||||
- e2e-react
|
||||
- e2e-release
|
||||
- e2e-remix
|
||||
- e2e-rollup
|
||||
- e2e-storybook
|
||||
- e2e-vite
|
||||
- e2e-vue
|
||||
- e2e-web
|
||||
- e2e-webpack
|
||||
- e2e-workspace-create
|
||||
include:
|
||||
# codeowner groups
|
||||
- project: e2e-angular
|
||||
codeowners: 'S04SS457V38'
|
||||
- project: e2e-cypress
|
||||
codeowners: 'S04T16BTJJY'
|
||||
- project: e2e-esbuild
|
||||
codeowners: 'S04SJ6HHP0X'
|
||||
- project: e2e-jest
|
||||
codeowners: 'S04T16BTJJY'
|
||||
- project: e2e-js
|
||||
codeowners: 'S04SJ6HHP0X'
|
||||
- project: e2e-lerna-smoke-tests
|
||||
codeowners: 'S04TNCVEETS'
|
||||
- project: e2e-eslint
|
||||
codeowners: 'S04SYJGKSCT'
|
||||
- project: e2e-next
|
||||
codeowners: 'S04TNCNJG5N'
|
||||
- project: e2e-node
|
||||
codeowners: 'S04SJ6HHP0X'
|
||||
- project: e2e-nx-init
|
||||
codeowners: 'S04SYHYKGNP'
|
||||
- project: e2e-nx
|
||||
codeowners: 'S04SYHYKGNP'
|
||||
- project: e2e-plugin
|
||||
codeowners: 'S04SYHYKGNP'
|
||||
- project: e2e-release
|
||||
codeowners: 'S04SYHYKGNP'
|
||||
- project: e2e-react
|
||||
codeowners: 'S04TNCNJG5N'
|
||||
- project: e2e-web
|
||||
codeowners: 'S04SJ6PL98X'
|
||||
- project: e2e-rollup
|
||||
codeowners: 'S04SJ6PL98X'
|
||||
- project: e2e-storybook
|
||||
codeowners: 'S04SVQ8H0G5'
|
||||
- project: e2e-playwright
|
||||
codeowners: 'S04SVQ8H0G5'
|
||||
- project: e2e-remix
|
||||
codeowners: 'S04SVQ8H0G5'
|
||||
- project: e2e-vite
|
||||
codeowners: 'S04SJ6PL98X'
|
||||
- project: e2e-vue
|
||||
codeowners: 'S04SJ6PL98X'
|
||||
- project: e2e-nuxt
|
||||
codeowners: 'S04SJ6PL98X'
|
||||
- project: e2e-webpack
|
||||
codeowners: 'S04SJ6PL98X'
|
||||
- project: e2e-workspace-create
|
||||
codeowners: 'S04SYHYKGNP'
|
||||
exclude:
|
||||
# exclude non-CNW/Lerna tests from non-LTS node versions
|
||||
- node_version: 18
|
||||
project: e2e-angular
|
||||
- node_version: 18
|
||||
project: e2e-cypress
|
||||
- node_version: 18
|
||||
project: e2e-esbuild
|
||||
- node_version: 18
|
||||
project: e2e-jest
|
||||
- node_version: 18
|
||||
project: e2e-js
|
||||
- node_version: 18
|
||||
project: e2e-eslint
|
||||
- node_version: 18
|
||||
project: e2e-next
|
||||
- node_version: 18
|
||||
project: e2e-node
|
||||
- node_version: 18
|
||||
project: e2e-nuxt
|
||||
- node_version: 18
|
||||
project: e2e-nx-init
|
||||
- node_version: 18
|
||||
project: e2e-nx
|
||||
- node_version: 18
|
||||
project: e2e-plugin
|
||||
- node_version: 18
|
||||
project: e2e-playwright
|
||||
- node_version: 18
|
||||
project: e2e-react
|
||||
- node_version: 18
|
||||
project: e2e-web
|
||||
- node_version: 18
|
||||
project: e2e-remix
|
||||
- node_version: 18
|
||||
project: e2e-rollup
|
||||
- node_version: 18
|
||||
project: e2e-storybook
|
||||
- node_version: 18
|
||||
project: e2e-vite
|
||||
- node_version: 18
|
||||
project: e2e-vue
|
||||
- node_version: 18
|
||||
project: e2e-webpack
|
||||
- node_version: 22
|
||||
project: e2e-angular
|
||||
- node_version: 22
|
||||
project: e2e-cypress
|
||||
- node_version: 22
|
||||
project: e2e-esbuild
|
||||
- node_version: 22
|
||||
project: e2e-jest
|
||||
- node_version: 22
|
||||
project: e2e-js
|
||||
- node_version: 22
|
||||
project: e2e-eslint
|
||||
- node_version: 22
|
||||
project: e2e-next
|
||||
- node_version: 22
|
||||
project: e2e-node
|
||||
- node_version: 22
|
||||
project: e2e-nuxt
|
||||
- node_version: 22
|
||||
project: e2e-nx-init
|
||||
- node_version: 22
|
||||
project: e2e-nx
|
||||
- node_version: 22
|
||||
project: e2e-plugin
|
||||
- node_version: 22
|
||||
project: e2e-playwright
|
||||
- node_version: 22
|
||||
project: e2e-react
|
||||
- node_version: 22
|
||||
project: e2e-web
|
||||
- node_version: 22
|
||||
project: e2e-remix
|
||||
- node_version: 22
|
||||
project: e2e-rollup
|
||||
- node_version: 22
|
||||
project: e2e-storybook
|
||||
- node_version: 22
|
||||
project: e2e-vite
|
||||
- node_version: 22
|
||||
project: e2e-vue
|
||||
- node_version: 22
|
||||
project: e2e-webpack
|
||||
# - node_version: 23
|
||||
# project: e2e-angular
|
||||
# - node_version: 23
|
||||
# project: e2e-cypress
|
||||
# - node_version: 23
|
||||
# project: e2e-esbuild
|
||||
# - node_version: 23
|
||||
# project: e2e-jest
|
||||
# - node_version: 23
|
||||
# project: e2e-js
|
||||
# - node_version: 23
|
||||
# project: e2e-eslint
|
||||
# - node_version: 23
|
||||
# project: e2e-next
|
||||
# - node_version: 23
|
||||
# project: e2e-node
|
||||
# - node_version: 23
|
||||
# project: e2e-nuxt
|
||||
# - node_version: 23
|
||||
# project: e2e-nx-init
|
||||
# - node_version: 23
|
||||
# project: e2e-nx
|
||||
# - node_version: 23
|
||||
# project: e2e-plugin
|
||||
# - node_version: 23
|
||||
# project: e2e-playwright
|
||||
# - node_version: 23
|
||||
# project: e2e-react
|
||||
# - node_version: 23
|
||||
# project: e2e-web
|
||||
# - node_version: 23
|
||||
# project: e2e-remix
|
||||
# - node_version: 23
|
||||
# project: e2e-rollup
|
||||
# - node_version: 23
|
||||
# project: e2e-storybook
|
||||
# - node_version: 23
|
||||
# project: e2e-vite
|
||||
# - node_version: 23
|
||||
# project: e2e-vue
|
||||
# - node_version: 23
|
||||
# project: e2e-webpack
|
||||
fail-fast: false
|
||||
|
||||
name: ${{ matrix.project }} (v${{ matrix.node_version }})
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Prepare dir for output
|
||||
run: mkdir -p outputs
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 9.8.0
|
||||
run_install: false
|
||||
|
||||
- name: Use Node.js ${{ matrix.node_version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node_version }}
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Cache node_modules
|
||||
id: cache-modules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: '**/node_modules'
|
||||
key: ${{ runner.os }}-modules-${{ matrix.node_version }}-${{ github.run_id }}
|
||||
|
||||
- name: Install packages
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Cache Cypress
|
||||
id: cache-cypress
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: '${{ github.workspace }}/.cypress'
|
||||
key: ${{ runner.os }}-cypress
|
||||
|
||||
- name: Install Cypress
|
||||
if: steps.cache-cypress.outputs.cache-hit != 'true'
|
||||
run: npx cypress install
|
||||
|
||||
- name: Configure git metadata (needed for lerna smoke tests)
|
||||
run: |
|
||||
git config --global user.email test@test.com
|
||||
git config --global user.name "Test Test"
|
||||
|
||||
- name: Run e2e tests
|
||||
id: e2e-run
|
||||
run: pnpm nx run ${{ matrix.project }}:e2e-local
|
||||
shell: bash
|
||||
timeout-minutes: 180
|
||||
env:
|
||||
GIT_AUTHOR_EMAIL: test@test.com
|
||||
GIT_AUTHOR_NAME: Test
|
||||
GIT_COMMITTER_EMAIL: test@test.com
|
||||
GIT_COMMITTER_NAME: Test
|
||||
NX_E2E_CI_CACHE_KEY: e2e-gha-windows-${{ matrix.node_version }}-${{ matrix.package_manager }}
|
||||
NODE_OPTIONS: --max_old_space_size=8192
|
||||
SELECTED_PM: ${{ matrix.package_manager }}
|
||||
npm_config_registry: http://localhost:4872
|
||||
NX_CACHE_DIRECTORY: 'tmp'
|
||||
NX_E2E_SKIP_CLEANUP: 'true'
|
||||
NX_E2E_RUN_E2E: 'true'
|
||||
NX_E2E_VERBOSE_LOGGING: 'true'
|
||||
NX_PERF_LOGGING: 'false'
|
||||
NX_DAEMON: 'true'
|
||||
NX_SKIP_LOG_GROUPING: 'true'
|
||||
|
||||
- name: Save matrix config in file
|
||||
if: ${{ always() }}
|
||||
id: save-matrix
|
||||
shell: bash
|
||||
run: |
|
||||
matrix=$((
|
||||
echo '${{ toJSON(matrix) }}'
|
||||
) | jq -c '. + { "status": "${{ steps.e2e-run.outcome}}" }')
|
||||
echo "$matrix" > 'outputs/matrix.json'
|
||||
|
||||
- name: Upload matrix config
|
||||
uses: actions/upload-artifact@v4
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
name: windows-${{ matrix.node_version}}-${{ matrix.package_manager}}-${{ matrix.project }}
|
||||
overwrite: true
|
||||
if-no-files-found: 'ignore'
|
||||
path: 'outputs/matrix.json'
|
||||
|
||||
- name: Setup tmate session
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled && failure() }}
|
||||
uses: mxschmitt/action-tmate@v3.8
|
||||
timeout-minutes: 15
|
||||
with:
|
||||
sudo: false # disable sudo for windows debugging
|
||||
|
||||
process-result:
|
||||
if: ${{ always() && github.repository_owner == 'nrwl' }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: e2e
|
||||
outputs:
|
||||
message: ${{ steps.process-json.outputs.SLACK_MESSAGE }}
|
||||
codeowners: ${{ steps.process-json.outputs.CODEOWNERS }}
|
||||
steps:
|
||||
- name: Prepare dir for output
|
||||
run: mkdir -p outputs
|
||||
|
||||
- name: Load outputs
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: outputs
|
||||
|
||||
- name: Join and stringify matrix configs
|
||||
id: combine-json
|
||||
shell: bash
|
||||
run: |
|
||||
combined=$((jq -s . outputs/*/matrix.json) | jq tostring)
|
||||
echo "combined=$combined" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Make slack outputs
|
||||
id: process-json
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
with:
|
||||
script: |
|
||||
const combined = JSON.parse(${{ steps.combine-json.outputs.combined }});
|
||||
const failedProjects = combined.filter(c => c.status === 'failure').sort((a, b) => a.project.localeCompare(b.project));
|
||||
|
||||
// codeowners
|
||||
const codeowners = new Set();
|
||||
failedProjects.forEach(c => {
|
||||
codeowners.add(c.codeowners);
|
||||
});
|
||||
core.setOutput('CODEOWNERS', Array.from(codeowners).join(','));
|
||||
|
||||
// message
|
||||
let result = `
|
||||
*OS* Windows
|
||||
*Package manager* npm
|
||||
\`\`\`
|
||||
| Failed project | Node |
|
||||
|--------------------------------|------|`;
|
||||
failedProjects.forEach(matrix => {
|
||||
result += `\n| ${matrix.project.padEnd(30)} | v${matrix.node_version} |`
|
||||
});
|
||||
result += `\`\`\``;
|
||||
const message = result.split('\n').map(l => l.trim()).join('\n');
|
||||
core.setOutput('SLACK_MESSAGE', message);
|
||||
|
||||
report-failure:
|
||||
if: ${{ failure() && github.repository_owner == 'nrwl' && github.event_name != 'workflow_dispatch' }}
|
||||
needs: process-result
|
||||
runs-on: ubuntu-latest
|
||||
name: Report failure
|
||||
steps:
|
||||
- name: Send notification
|
||||
uses: ravsamhq/notify-slack-action@v2
|
||||
with:
|
||||
status: 'failure'
|
||||
message_format: '{emoji} Workflow has {status_message} ${{ needs.process-result.outputs.message }}'
|
||||
notification_title: '{workflow}'
|
||||
footer: '<{run_url}|View Run> / Last commit <{commit_url}|{commit_sha}>'
|
||||
mention_groups: ${{ needs.process-result.outputs.codeowners }}
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.ACTION_MONITORING_SLACK }}
|
||||
|
||||
report-success:
|
||||
if: ${{ success() && github.repository_owner == 'nrwl' && github.event_name != 'workflow_dispatch' }}
|
||||
needs: e2e
|
||||
runs-on: ubuntu-latest
|
||||
name: Report success
|
||||
steps:
|
||||
- name: Send notification
|
||||
uses: ravsamhq/notify-slack-action@v2
|
||||
with:
|
||||
status: ${{ needs.e2e.result }}
|
||||
message_format: '{emoji} Workflow has {status_message}'
|
||||
notification_title: '{workflow}'
|
||||
footer: '<{run_url}|View Run> / Last commit <{commit_url}|{commit_sha}>'
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.ACTION_MONITORING_SLACK }}
|
||||
@@ -0,0 +1,117 @@
|
||||
type MatrixDataProject = {
|
||||
name: string,
|
||||
codeowners: string,
|
||||
};
|
||||
|
||||
type MatrixDataOS = {
|
||||
os: string, // GH runner machine name: e.g. ubuntu-latest
|
||||
os_name: string, // short name that will be printed in the report and on the action
|
||||
os_timeout: number, // 60
|
||||
package_managers: string[], // package managers to run on this OS
|
||||
node_versions: number[], // node versions to run on this OS
|
||||
excluded?: string[], // projects to exclude from running on this OS
|
||||
};
|
||||
|
||||
type MatrixData = {
|
||||
coreProjects: MatrixDataProject[],
|
||||
projects: MatrixDataProject[],
|
||||
nodeTLS: number,
|
||||
setup: MatrixDataOS[],
|
||||
}
|
||||
|
||||
// TODO: Extract Slack groups into named groups for easier maintenance
|
||||
const matrixData: MatrixData = {
|
||||
coreProjects: [
|
||||
{ name: 'e2e-lerna-smoke-tests', codeowners: 'S04TNCVEETS' },
|
||||
{ name: 'e2e-js', codeowners: 'S04SJ6HHP0X' },
|
||||
{ name: 'e2e-nx-init', codeowners: 'S04SYHYKGNP' },
|
||||
{ name: 'e2e-nx', codeowners: 'S04SYHYKGNP' },
|
||||
{ name: 'e2e-release', codeowners: 'S04SYHYKGNP' },
|
||||
{ name: 'e2e-workspace-create', codeowners: 'S04SYHYKGNP' }
|
||||
],
|
||||
projects: [
|
||||
{ name: 'e2e-angular', codeowners: 'S04SS457V38' },
|
||||
{ name: 'e2e-cypress', codeowners: 'S04T16BTJJY' },
|
||||
{ name: 'e2e-detox', codeowners: 'S04TNCNJG5N' },
|
||||
{ name: 'e2e-esbuild', codeowners: 'S04SJ6HHP0X' },
|
||||
{ name: 'e2e-expo', codeowners: 'S04TNCNJG5N' },
|
||||
{ name: 'e2e-gradle', codeowners: 'S04TNCNJG5N' },
|
||||
{ name: 'e2e-jest', codeowners: 'S04T16BTJJY' },
|
||||
{ name: 'e2e-eslint', codeowners: 'S04SYJGKSCT' },
|
||||
{ name: 'e2e-next', codeowners: 'S04TNCNJG5N' },
|
||||
{ name: 'e2e-node', codeowners: 'S04SJ6HHP0X' },
|
||||
{ name: 'e2e-plugin', codeowners: 'S04SYHYKGNP' },
|
||||
{ name: 'e2e-react', codeowners: 'S04TNCNJG5N' },
|
||||
{ name: 'e2e-react-native', codeowners: 'S04TNCNJG5N' },
|
||||
{ name: 'e2e-web', codeowners: 'S04SJ6PL98X' },
|
||||
{ name: 'e2e-rollup', codeowners: 'S04SJ6PL98X' },
|
||||
{ name: 'e2e-storybook', codeowners: 'S04SVQ8H0G5' },
|
||||
{ name: 'e2e-playwright', codeowners: 'S04SVQ8H0G5' },
|
||||
{ name: 'e2e-remix', codeowners: 'S04SVQ8H0G5' },
|
||||
{ name: 'e2e-rspack', codeowners: 'S04SJ6HHP0X' },
|
||||
{ name: 'e2e-vite', codeowners: 'S04SJ6PL98X' },
|
||||
{ name: 'e2e-vue', codeowners: 'S04SJ6PL98X' },
|
||||
{ name: 'e2e-nuxt', codeowners: 'S04SJ6PL98X' },
|
||||
{ name: 'e2e-webpack', codeowners: 'S04SJ6PL98X' }
|
||||
],
|
||||
nodeTLS: 20,
|
||||
setup: [
|
||||
{ os: 'ubuntu-latest', os_name: 'Linux', os_timeout: 60, package_managers: ['npm', 'pnpm', 'yarn'], node_versions: [18, 20, 22], excluded: ['e2e-detox', 'e2e-react-native', 'e2e-expo'] },
|
||||
{ os: 'macos-latest', os_name: 'MacOS', os_timeout: 90, package_managers: ['npm'], node_versions: [20] },
|
||||
{ os: 'windows-latest', os_name: 'WinOS', os_timeout: 180, package_managers: ['npm'], node_versions: [20], excluded: ['e2e-detox', 'e2e-react-native', 'e2e-expo'] }
|
||||
]
|
||||
};
|
||||
|
||||
const matrix: Array<{
|
||||
project: string,
|
||||
codeowners: string,
|
||||
node_version: number,
|
||||
package_manager: string,
|
||||
os: string,
|
||||
os_name: string,
|
||||
os_timeout: number
|
||||
}> = [];
|
||||
|
||||
function addMatrixCombo(project: MatrixDataProject, nodeVersion: number, pm: number, os: number) {
|
||||
matrix.push({
|
||||
project: project.name,
|
||||
codeowners: project.codeowners,
|
||||
node_version: nodeVersion,
|
||||
package_manager: matrixData.setup[os].package_managers[pm],
|
||||
os: matrixData.setup[os].os,
|
||||
os_name: matrixData.setup[os].os_name,
|
||||
os_timeout: matrixData.setup[os].os_timeout,
|
||||
});
|
||||
}
|
||||
|
||||
function processProject(project: MatrixDataProject, nodeVersion?: number) {
|
||||
for (let os = 0; os < matrixData.setup.length; os++) {
|
||||
for (let pm = 0; pm < matrixData.setup[os].package_managers.length; pm++) {
|
||||
if (!matrixData.setup[os].excluded || !matrixData.setup[os].excluded?.includes(project.name)) {
|
||||
if (nodeVersion) {
|
||||
addMatrixCombo(project, nodeVersion, pm, os);
|
||||
} else {
|
||||
for (let n = 0; n < matrixData.setup[os].node_versions.length; n++) {
|
||||
addMatrixCombo(project, matrixData.setup[os].node_versions[n], pm, os);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// process core projects
|
||||
for (let p = 0; p < matrixData.coreProjects.length; p++) {
|
||||
processProject(matrixData.coreProjects[p]);
|
||||
}
|
||||
// process other projects
|
||||
for (let p = 0; p < matrixData.projects.length; p++) {
|
||||
processProject(matrixData.projects[p], matrixData.nodeTLS);
|
||||
}
|
||||
|
||||
if (matrix.length > 256) {
|
||||
throw new Error('You have exceeded the size of the matrix. GitHub allows only 256 jobs in a matrix. Found ${matrix.length} jobs.');
|
||||
}
|
||||
|
||||
// print result to stdout for pipeline to consume
|
||||
process.stdout.write(JSON.stringify({ include: matrix }, null, 0));
|
||||
@@ -46,6 +46,7 @@ Install `create-nx-workspace` globally to invoke the command directly, or use `n
|
||||
| `--style` | string | Stylesheet type to be used with certain stacks. |
|
||||
| `--unitTestRunner` | `jest`, `vitest`, `none` | Test runner to use for unit tests. |
|
||||
| `--useGitHub` | boolean | Will you be using GitHub as your git hosting provider? (Default: `false`) |
|
||||
| `--useProjectJson` | boolean | Use a 'project.json' file for the Nx configuration instead of a 'package.json' file. This defaults to 'true' when '--no-workspaces' is used. Otherwise, it defaults to 'false'. |
|
||||
| `--useReactRouter` | boolean | Generate a Server-Side Rendered (SSR) React app using React Router. |
|
||||
| `--version` | boolean | Show version number. |
|
||||
| `--workspaces` | boolean | Use package manager workspaces. (Default: `true`) |
|
||||
|
||||
@@ -1727,6 +1727,16 @@
|
||||
}
|
||||
},
|
||||
"migrations": {
|
||||
"/nx-api/eslint/migrations/20.7.0-package-updates": {
|
||||
"description": "",
|
||||
"file": "generated/packages/eslint/migrations/20.7.0-package-updates.json",
|
||||
"hidden": false,
|
||||
"name": "20.7.0-package-updates",
|
||||
"version": "20.7.0-beta.4",
|
||||
"originalFilePath": "/packages/eslint",
|
||||
"path": "/nx-api/eslint/migrations/20.7.0-package-updates",
|
||||
"type": "migration"
|
||||
},
|
||||
"/nx-api/eslint/migrations/20.4.0-typescript-eslint-package-updates": {
|
||||
"description": "",
|
||||
"file": "generated/packages/eslint/migrations/20.4.0-typescript-eslint-package-updates.json",
|
||||
|
||||
@@ -1715,6 +1715,16 @@
|
||||
}
|
||||
],
|
||||
"migrations": [
|
||||
{
|
||||
"description": "",
|
||||
"file": "generated/packages/eslint/migrations/20.7.0-package-updates.json",
|
||||
"hidden": false,
|
||||
"name": "20.7.0-package-updates",
|
||||
"version": "20.7.0-beta.4",
|
||||
"originalFilePath": "/packages/eslint",
|
||||
"path": "eslint/migrations/20.7.0-package-updates",
|
||||
"type": "migration"
|
||||
},
|
||||
{
|
||||
"description": "",
|
||||
"file": "generated/packages/eslint/migrations/20.4.0-typescript-eslint-package-updates.json",
|
||||
|
||||
@@ -37,6 +37,10 @@
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"@angular/cdk": { "version": "~19.0.0", "alwaysAddToPackageJson": false },
|
||||
"@angular/google-maps": {
|
||||
"version": "~19.0.0",
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"ng-packagr": { "version": "~19.0.0", "alwaysAddToPackageJson": false },
|
||||
"zone.js": { "version": "~0.15.0", "alwaysAddToPackageJson": false }
|
||||
},
|
||||
|
||||
@@ -37,6 +37,10 @@
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"@angular/cdk": { "version": "~19.1.0", "alwaysAddToPackageJson": false },
|
||||
"@angular/google-maps": {
|
||||
"version": "~19.1.0",
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"ng-packagr": { "version": "~19.1.0", "alwaysAddToPackageJson": false }
|
||||
},
|
||||
"aliases": [],
|
||||
|
||||
@@ -37,6 +37,10 @@
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"@angular/cdk": { "version": "~19.2.1", "alwaysAddToPackageJson": false },
|
||||
"@angular/google-maps": {
|
||||
"version": "~19.2.1",
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"ng-packagr": { "version": "~19.2.0", "alwaysAddToPackageJson": false }
|
||||
},
|
||||
"aliases": [],
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "20.7.0-package-updates",
|
||||
"version": "20.7.0-beta.4",
|
||||
"packages": { "eslint-config-prettier": { "version": "^10.0.0" } },
|
||||
"aliases": [],
|
||||
"description": "",
|
||||
"hidden": false,
|
||||
"implementation": "",
|
||||
"path": "/packages/eslint",
|
||||
"schema": null,
|
||||
"type": "migration"
|
||||
}
|
||||
@@ -231,13 +231,53 @@ Next.js applications can be build with:
|
||||
nx build my-new-app
|
||||
```
|
||||
|
||||
And if you generated a library with --buildable, then you can build a library as well:
|
||||
And if you generated a library with `--bundler`, then you can build a library as well:
|
||||
|
||||
```shell
|
||||
nx build my-new-lib
|
||||
```
|
||||
|
||||
After running a build, the output will be in the `dist` folder. You can customize the output folder by setting `outputPath` in the project's `project.json` file.
|
||||
After running a build, the output will be in the `{workspaceRoot}/dist/{projectRoot}` folder.
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Using inferred tasks" %}
|
||||
|
||||
You can customize the output folder path by update the bundler's config. For example vite's config can be updated in `vite.config.ts`:
|
||||
|
||||
```typescript {% fileName="apps/my-next-app/vite.config.ts" highlightLines=[4]%}
|
||||
import { defineConfig } from 'vite';
|
||||
export default defineConfig(() => ({
|
||||
build: {
|
||||
outDir: 'dist/my-next-app',
|
||||
},
|
||||
}));
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Using the @nx/next:build executor" %}
|
||||
|
||||
You can customize the output folder by setting `outputPath` in the project's `project.json` file
|
||||
|
||||
```json {% fileName="apps/my-next-app/project.json" highlightLines=[9]%}
|
||||
{
|
||||
"root": "apps/my-next-app",
|
||||
"sourceRoot": "apps/my-next-app/src",
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nx/next:build",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"outputPath": "dist/my-next-app"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
The library in `dist` is publishable to npm or a private registry.
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ Install `create-nx-workspace` globally to invoke the command directly, or use `n
|
||||
| `--style` | string | Stylesheet type to be used with certain stacks. |
|
||||
| `--unitTestRunner` | `jest`, `vitest`, `none` | Test runner to use for unit tests. |
|
||||
| `--useGitHub` | boolean | Will you be using GitHub as your git hosting provider? (Default: `false`) |
|
||||
| `--useProjectJson` | boolean | Use a 'project.json' file for the Nx configuration instead of a 'package.json' file. This defaults to 'true' when '--no-workspaces' is used. Otherwise, it defaults to 'false'. |
|
||||
| `--useReactRouter` | boolean | Generate a Server-Side Rendered (SSR) React app using React Router. |
|
||||
| `--version` | boolean | Show version number. |
|
||||
| `--workspaces` | boolean | Use package manager workspaces. (Default: `true`) |
|
||||
|
||||
@@ -108,6 +108,10 @@
|
||||
"description": "Whether to use package manager workspaces.",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"useProjectJson": {
|
||||
"type": "boolean",
|
||||
"description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file."
|
||||
}
|
||||
},
|
||||
"additionalProperties": true,
|
||||
|
||||
@@ -125,6 +125,10 @@
|
||||
"description": "Whether to use package manager workspaces.",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"useProjectJson": {
|
||||
"type": "boolean",
|
||||
"description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file."
|
||||
}
|
||||
},
|
||||
"required": ["preset", "name"],
|
||||
|
||||
@@ -231,13 +231,53 @@ Next.js applications can be build with:
|
||||
nx build my-new-app
|
||||
```
|
||||
|
||||
And if you generated a library with --buildable, then you can build a library as well:
|
||||
And if you generated a library with `--bundler`, then you can build a library as well:
|
||||
|
||||
```shell
|
||||
nx build my-new-lib
|
||||
```
|
||||
|
||||
After running a build, the output will be in the `dist` folder. You can customize the output folder by setting `outputPath` in the project's `project.json` file.
|
||||
After running a build, the output will be in the `{workspaceRoot}/dist/{projectRoot}` folder.
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Using inferred tasks" %}
|
||||
|
||||
You can customize the output folder path by update the bundler's config. For example vite's config can be updated in `vite.config.ts`:
|
||||
|
||||
```typescript {% fileName="apps/my-next-app/vite.config.ts" highlightLines=[4]%}
|
||||
import { defineConfig } from 'vite';
|
||||
export default defineConfig(() => ({
|
||||
build: {
|
||||
outDir: 'dist/my-next-app',
|
||||
},
|
||||
}));
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Using the @nx/next:build executor" %}
|
||||
|
||||
You can customize the output folder by setting `outputPath` in the project's `project.json` file
|
||||
|
||||
```json {% fileName="apps/my-next-app/project.json" highlightLines=[9]%}
|
||||
{
|
||||
"root": "apps/my-next-app",
|
||||
"sourceRoot": "apps/my-next-app/src",
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nx/next:build",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"outputPath": "dist/my-next-app"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
The library in `dist` is publishable to npm or a private registry.
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ description: Learn how to ensure all Nx plugins in your repository are using the
|
||||
|
||||
# Keep Nx Versions in Sync
|
||||
|
||||
If your Nx plugin versions do not match the version of `nx` in your repository, you can encounter some difficult to debug errors. To get your Nx plugins back in sync, follow the steps below:
|
||||
If your Nx plugin versions do not match the version of `nx` in your repository, you may encounter some difficulties when debugging errors. To get your Nx plugins back in sync, follow the steps below:
|
||||
|
||||
1. Identify all the official Nx plugins that are used in your repo. This includes `nx` and any packages in the `@nx/` organization scope, except for plugins that are still in [nx-labs](https://github.com/nrwl/nx-labs). Also, `nx-cloud` does not need to match the other package versions.
|
||||
2. Run `nx report` and identify the minimum and maximum version numbers for all the packages that need to by in sync.
|
||||
|
||||
@@ -48,8 +48,6 @@ title="Nx React Monorepo Tutorial Walkthrough"
|
||||
|
||||
## Creating a new React Monorepo
|
||||
|
||||
{% video-link link="https://youtu.be/gc4N7kxiA50?t=66" /%}
|
||||
|
||||
Create a new React monorepo with the following command:
|
||||
|
||||
```{% command="npx create-nx-workspace@latest react-monorepo --preset=react-monorepo" path="~" %}
|
||||
@@ -59,7 +57,7 @@ NX Let's create a new workspace [https://nx.dev/getting-started/intro]
|
||||
✔ Application name · react-store
|
||||
✔ Which bundler would you like to use? · vite
|
||||
✔ Which unit test runner would you like to use? · vitest
|
||||
✔ Test runner to use for end to end (E2E) tests · cypress
|
||||
✔ Test runner to use for end to end (E2E) tests · playwright
|
||||
✔ Default stylesheet format · css
|
||||
✔ Would you like to use ESLint? · Yes
|
||||
✔ Would you like to use Prettier for code formatting? · Yes
|
||||
@@ -70,7 +68,6 @@ Let's name the initial application `react-store`. In this tutorial we're going t
|
||||
|
||||
```
|
||||
└─ react-monorepo
|
||||
├─ ...
|
||||
├─ apps
|
||||
│ ├─ react-store
|
||||
│ │ ├─ public
|
||||
@@ -92,12 +89,15 @@ Let's name the initial application `react-store`. In this tutorial we're going t
|
||||
│ │ └─ vite.config.ts
|
||||
│ └─ react-store-e2e
|
||||
│ └─ ...
|
||||
├─ ...
|
||||
├─ package.json
|
||||
├─ nx.json
|
||||
├─ tsconfig.base.json
|
||||
└─ package.json
|
||||
├─ tsconfig.json
|
||||
└─ vitest.workspace.ts
|
||||
```
|
||||
|
||||
The setup includes..
|
||||
The setup includes:
|
||||
|
||||
- a new React application (`apps/react-store/`)
|
||||
- a Playwright based set of e2e tests (`apps/react-store-e2e/`)
|
||||
@@ -111,8 +111,6 @@ The [`nx.json` file](/reference/nx-json) contains configuration settings for Nx
|
||||
|
||||
## Serving the App
|
||||
|
||||
{% video-link link="https://youtu.be/gc4N7kxiA50?t=120" /%}
|
||||
|
||||
To serve your new React application, just run:
|
||||
|
||||
```shell
|
||||
@@ -127,8 +125,6 @@ Nx uses the following syntax to run tasks:
|
||||
|
||||
### Inferred Tasks
|
||||
|
||||
{% video-link link="https://youtu.be/gc4N7kxiA50?t=158" /%}
|
||||
|
||||
Nx identifies available tasks for your project from [tooling configuration files](/concepts/inferred-tasks), `package.json` scripts and the targets defined in `project.json`. To view the tasks that Nx has detected, look in the [Nx Console](/getting-started/editor-setup) project detail view or run:
|
||||
|
||||
```shell
|
||||
@@ -245,37 +241,36 @@ export default defineConfig({
|
||||
|
||||
## Add Another Application
|
||||
|
||||
{% video-link link="https://youtu.be/gc4N7kxiA50?t=259" /%}
|
||||
|
||||
Nx plugins usually provide [generators](/features/generate-code) that allow you to easily scaffold code, configuration or entire projects. To see what capabilities the `@nx/react` plugin provides, run the following command and inspect the output:
|
||||
|
||||
```{% command="npx nx list @nx/react" path="react-monorepo" %}
|
||||
|
||||
NX Capabilities in @nx/react:
|
||||
|
||||
GENERATORS
|
||||
GENERATORS
|
||||
|
||||
init : Initialize the `@nx/react` plugin.
|
||||
application : Create a React application.
|
||||
library : Create a React library.
|
||||
component : Create a React component.
|
||||
redux : Create a Redux slice for a project.
|
||||
storybook-configuration : Set up storybook for a React app or library.
|
||||
component-story : Generate storybook story for a React component
|
||||
stories : Create stories/specs for all components declared in an app or library.
|
||||
hook : Create a hook.
|
||||
cypress-component-configuration : Setup Cypress component testing for a React project
|
||||
component-test : Generate a Cypress component test for a React component
|
||||
setup-tailwind : Set up Tailwind configuration for a project.
|
||||
setup-ssr : Set up SSR configuration for a project.
|
||||
host : Generate a host react application
|
||||
remote : Generate a remote react application
|
||||
federate-module : Federate a module.
|
||||
init : Initialize the `@nx/react` plugin.
|
||||
application : Create a React application.
|
||||
library : Create a React library.
|
||||
component : Create a React component.
|
||||
redux : Create a Redux slice for a project.
|
||||
storybook-configuration : Set up storybook for a React app or library.
|
||||
component-story : Generate storybook story for a React component
|
||||
stories : Create stories/specs for all components declared in an app or library.
|
||||
hook : Create a hook.
|
||||
host : Generate a host react application
|
||||
remote : Generate a remote react application
|
||||
cypress-component-configuration : Setup Cypress component testing for a React project
|
||||
component-test : Generate a Cypress component test for a React component
|
||||
setup-tailwind : Set up Tailwind configuration for a project.
|
||||
setup-ssr : Set up SSR configuration for a project.
|
||||
federate-module : Federate a module.
|
||||
|
||||
EXECUTORS/BUILDERS
|
||||
EXECUTORS/BUILDERS
|
||||
|
||||
module-federation-dev-server : Serve a host or remote application.
|
||||
module-federation-ssr-dev-server : Serve a host application along with it's known remotes.
|
||||
module-federation-dev-server : Serve a host or remote application.
|
||||
module-federation-ssr-dev-server : Serve a host application along with it's known remotes.
|
||||
module-federation-static-server : Serve a host and its remotes statically.
|
||||
```
|
||||
|
||||
{% callout type="info" title="Prefer a more visual UI?" %}
|
||||
@@ -291,9 +286,9 @@ Run the following command to generate a new `inventory` application. Note how we
|
||||
```{% command="npx nx g @nx/react:app apps/inventory" path="react-monorepo" %}
|
||||
NX Generating @nx/react:application
|
||||
|
||||
✔ Would you like to add React Router to this application? (y/N) · false
|
||||
✔ Would you like to add routing to this application? (y/N) · false
|
||||
✔ What unit test runner should be used? · vitest
|
||||
✔ Which E2E test runner would you like to use? · cypress
|
||||
✔ Which E2E test runner would you like to use? · playwright
|
||||
CREATE apps/inventory/index.html
|
||||
CREATE apps/inventory/public/favicon.ico
|
||||
CREATE apps/inventory/src/app/app.spec.tsx
|
||||
@@ -306,19 +301,15 @@ CREATE apps/inventory/src/app/app.tsx
|
||||
CREATE apps/inventory/src/styles.css
|
||||
CREATE apps/inventory/tsconfig.json
|
||||
CREATE apps/inventory/package.json
|
||||
CREATE apps/inventory/eslint.config.mjs
|
||||
UPDATE nx.json
|
||||
CREATE apps/inventory/tsconfig.spec.json
|
||||
CREATE apps/inventory/vite.config.ts
|
||||
CREATE apps/inventory/eslint.config.mjs
|
||||
CREATE apps/inventory-e2e/package.json
|
||||
CREATE apps/inventory-e2e/src/e2e/app.cy.ts
|
||||
CREATE apps/inventory-e2e/src/support/app.po.ts
|
||||
CREATE apps/inventory-e2e/src/support/e2e.ts
|
||||
CREATE apps/inventory-e2e/src/fixtures/example.json
|
||||
CREATE apps/inventory-e2e/src/support/commands.ts
|
||||
CREATE apps/inventory-e2e/cypress.config.ts
|
||||
CREATE apps/inventory-e2e/tsconfig.json
|
||||
CREATE apps/inventory-e2e/src/example.spec.ts
|
||||
CREATE apps/inventory-e2e/playwright.config.ts
|
||||
UPDATE tsconfig.json
|
||||
CREATE apps/inventory-e2e/tsconfig.json
|
||||
CREATE apps/inventory-e2e/eslint.config.mjs
|
||||
|
||||
NOTE: The "dryRun" flag means no changes were made.
|
||||
@@ -332,8 +323,6 @@ npx nx g @nx/react:app apps/inventory
|
||||
|
||||
## Share Code with Local Libraries
|
||||
|
||||
{% video-link link="https://youtu.be/gc4N7kxiA50?t=324" /%}
|
||||
|
||||
When you develop your React application, usually all your logic sits in the `app` folder. Ideally separated by various folder names which represent your "domains". As your app grows, however, the app becomes more and more monolithic and the code is unable to be shared with other applications.
|
||||
|
||||
```
|
||||
@@ -367,8 +356,6 @@ Nx allows you to separate this logic into "local libraries". The main benefits i
|
||||
|
||||
### Create Local Libraries
|
||||
|
||||
{% video-link link="https://youtu.be/gc4N7kxiA50?t=366" /%}
|
||||
|
||||
Let's assume our domain areas include `products`, `orders` and some more generic design system components, called `ui`. We can generate a new library for each of these areas using the React library generator:
|
||||
|
||||
```
|
||||
@@ -426,8 +413,6 @@ Each of these libraries
|
||||
|
||||
### Import Libraries into the React Applications
|
||||
|
||||
{% video-link link="https://youtu.be/gc4N7kxiA50?t=456" /%}
|
||||
|
||||
All libraries that we generate are automatically included in the `workspaces` defined in the root-level `package.json`.
|
||||
|
||||
```json {% fileName="package.json" %}
|
||||
@@ -509,7 +494,7 @@ import { Route, Routes } from 'react-router-dom';
|
||||
import { Products } from '@react-monorepo/products';
|
||||
|
||||
function Home() {
|
||||
return <h1>Home</h1>;
|
||||
return <h1>Welcome react-store</h1>;
|
||||
}
|
||||
|
||||
export function App() {
|
||||
@@ -570,8 +555,6 @@ export default App;
|
||||
|
||||
## Visualize your Project Structure
|
||||
|
||||
{% video-link link="https://youtu.be/gc4N7kxiA50?t=530" /%}
|
||||
|
||||
Nx automatically detects the dependencies between the various parts of your workspace and builds a [project graph](/features/explore-graph). This graph is used by Nx to perform various optimizations such as determining the correct order of execution when running tasks like `npx nx build`, identifying [affected projects](/features/run-tasks#run-tasks-on-projects-affected-by-a-pr) and more. Interestingly you can also visualize it.
|
||||
|
||||
Just run:
|
||||
@@ -675,9 +658,7 @@ Exercise for you: change the codebase such that `ui` is used by `orders` and `pr
|
||||
|
||||
## Testing and Linting - Running Multiple Tasks
|
||||
|
||||
{% video-link link="https://youtu.be/gc4N7kxiA50?t=589" /%}
|
||||
|
||||
Our current setup doesn't just come with targets for serving and building the React application, but also has targets for unit testing, e2e testing and linting. Again, these are defined in the `project.json` file. We can use the same syntax as before to run these tasks:
|
||||
Our current setup doesn't just come with targets for serving and building the React application, but also has targets for unit testing, e2e testing and linting. We can use the same syntax as before to run these tasks:
|
||||
|
||||
```bash
|
||||
npx nx test react-store # runs the tests for react-store
|
||||
@@ -691,6 +672,17 @@ More conveniently, we can also run tasks in parallel using the following syntax:
|
||||
npx nx run-many -t test
|
||||
```
|
||||
|
||||
These tasks are automatically configured by through [Nx plugins](/concepts/inferred-tasks). You can view available tasks for a project by using the `nx show project` command.
|
||||
|
||||
```shell
|
||||
npx nx show project react-store
|
||||
npx nx show project react-store-e2e
|
||||
npx nx show project inventory
|
||||
|
||||
# To list all projects
|
||||
npx nx show projects
|
||||
```
|
||||
|
||||
### Cache Tasks
|
||||
|
||||
One thing to highlight is that Nx is able to [cache the tasks you run](/features/cache-task-results).
|
||||
@@ -714,8 +706,6 @@ Not all tasks might be cacheable though. You can configure the `cache` settings
|
||||
|
||||
### Test Affected Projects
|
||||
|
||||
{% video-link link="https://youtu.be/gc4N7kxiA50?t=614" /%}
|
||||
|
||||
Commit your changes to git.
|
||||
|
||||
```shell
|
||||
@@ -848,8 +838,6 @@ npx nx graph --affected
|
||||
|
||||
## Build the Apps for Deployment
|
||||
|
||||
{% video-link link="https://youtu.be/gc4N7kxiA50?t=713" /%}
|
||||
|
||||
If you're ready and want to ship your applications, you can build them using
|
||||
|
||||
```{% command="npx nx run-many -t build" path="react-monorepo" %}
|
||||
@@ -861,7 +849,7 @@ If you're ready and want to ship your applications, you can build them using
|
||||
NX Successfully ran target build for 2 projects (1s)
|
||||
```
|
||||
|
||||
All the required files will be placed in `/apps/react-store/dist` and `/apps/inventory/dist` and can be deployed to your favorite hosting provider.
|
||||
All the required files will be placed in `apps/react-store/dist` and `apps/inventory/dist` and can be deployed to your favorite hosting provider.
|
||||
|
||||
Nx will run any script defined in `package.json`, so you can create a `deploy` task that sends the build output to your hosting provider.
|
||||
|
||||
@@ -917,8 +905,6 @@ npx nx affected -t deploy
|
||||
|
||||
## Imposing Constraints with Module Boundary Rules
|
||||
|
||||
{% video-link link="https://youtu.be/gc4N7kxiA50?t=791" /%}
|
||||
|
||||
Once you modularize your codebase you want to make sure that the libs are not coupled to each other in an uncontrolled way. Here are some examples of how we might want to guard our small demo workspace:
|
||||
|
||||
- we might want to allow `orders` to import from `shared-ui` but not the other way around
|
||||
@@ -1091,8 +1077,6 @@ Learn more about how to [enforce module boundaries](/features/enforce-module-bou
|
||||
Make sure you have completed the previous sections of this tutorial before starting this one. If you want a clean starting point, you can check out the [reference code](https://github.com/nrwl/nx-recipes/tree/main/react-monorepo) as a starting point.
|
||||
{% /callout %}
|
||||
|
||||
{% video-link link="https://youtu.be/gc4N7kxiA50?t=907" /%}
|
||||
|
||||
This tutorial walked you through how Nx can improve the local development experience, but the biggest difference Nx makes is in CI. As repositories get bigger, making sure that the CI is fast, reliable and maintainable can get very challenging. Nx provides a solution.
|
||||
|
||||
- Nx reduces wasted time in CI with the [`affected` command](/ci/features/affected).
|
||||
@@ -1130,7 +1114,7 @@ npx nx generate ci-workflow
|
||||
|
||||
The key lines in the CI pipeline are:
|
||||
|
||||
```yml {% fileName=".github/workflows/ci.yml" highlightLines=["10-14", "21-23"] %}
|
||||
```yml {% fileName=".github/workflows/ci.yml" highlightLines=["11-15", "27-31"] %}
|
||||
name: CI
|
||||
# ...
|
||||
jobs:
|
||||
@@ -1154,7 +1138,7 @@ jobs:
|
||||
cache: 'npm'
|
||||
|
||||
- run: npm ci --legacy-peer-deps
|
||||
- run: npx cypress install
|
||||
- run: npx playwright install --with-deps
|
||||
- uses: nrwl/nx-set-shas@v4
|
||||
|
||||
# Prepend any command with "nx-cloud record --" to record its logs to Nx Cloud
|
||||
|
||||
@@ -132,16 +132,19 @@ ${content}`
|
||||
|
||||
// check typecheck
|
||||
expect(runCLI(`typecheck ${esbuildParentLib}`)).toContain(
|
||||
`Successfully ran target typecheck for project @proj/${esbuildParentLib} and 4 tasks it depends on`
|
||||
`Successfully ran target typecheck for project @proj/${esbuildParentLib} and 5 tasks it depends on`
|
||||
);
|
||||
expect(runCLI(`typecheck ${rollupParentLib}`)).toContain(
|
||||
`Successfully ran target typecheck for project @proj/${rollupParentLib} and 4 tasks it depends on`
|
||||
`Successfully ran target typecheck for project @proj/${rollupParentLib} and 5 tasks it depends on`
|
||||
);
|
||||
expect(runCLI(`typecheck ${swcParentLib}`)).toContain(
|
||||
`Successfully ran target typecheck for project @proj/${swcParentLib} and 4 tasks it depends on`
|
||||
`Successfully ran target typecheck for project @proj/${swcParentLib} and 5 tasks it depends on`
|
||||
);
|
||||
expect(runCLI(`typecheck ${tscParentLib}`)).toContain(
|
||||
`Successfully ran target typecheck for project @proj/${tscParentLib} and 5 tasks it depends on`
|
||||
);
|
||||
expect(runCLI(`typecheck ${viteParentLib}`)).toContain(
|
||||
`Successfully ran target typecheck for project @proj/${viteParentLib} and 4 tasks it depends on`
|
||||
`Successfully ran target typecheck for project @proj/${viteParentLib} and 5 tasks it depends on`
|
||||
);
|
||||
|
||||
// check lint
|
||||
|
||||
@@ -106,6 +106,7 @@ describe('Node Applications', () => {
|
||||
expect(() => runCLI(`lint ${nodelib}`)).not.toThrow();
|
||||
expect(() => runCLI(`test ${nodelib}`)).not.toThrow();
|
||||
expect(() => runCLI(`build ${nodelib}`)).not.toThrow();
|
||||
expect(() => runCLI(`typecheck ${nodelib}`)).not.toThrow();
|
||||
|
||||
const p = await runCommandUntil(
|
||||
`serve ${nodeapp}`,
|
||||
|
||||
@@ -47,6 +47,9 @@ describe('Nx Plugin (TS solution)', () => {
|
||||
expect(runCLI(`lint @proj/${plugin}`)).toContain(
|
||||
`Successfully ran target lint for project @proj/${plugin}`
|
||||
);
|
||||
expect(runCLI(`typecheck @proj/${plugin}`)).toContain(
|
||||
`Successfully ran target typecheck for project @proj/${plugin}`
|
||||
);
|
||||
expect(runCLI(`build @proj/${plugin}`)).toContain(
|
||||
`Successfully ran target build for project @proj/${plugin}`
|
||||
);
|
||||
|
||||
@@ -9,60 +9,117 @@ import {
|
||||
} from '@nx/e2e/utils';
|
||||
|
||||
describe('React Router Applications', () => {
|
||||
beforeAll(() => {
|
||||
newProject({ packages: ['@nx/react'] });
|
||||
ensureCypressInstallation();
|
||||
});
|
||||
|
||||
afterAll(() => cleanupProject());
|
||||
|
||||
it('should generate a react-router application', async () => {
|
||||
describe('TS paths', () => {
|
||||
const appName = uniq('app');
|
||||
runCLI(
|
||||
`generate @nx/react:app ${appName} --use-react-router --routing --no-interactive`
|
||||
);
|
||||
beforeAll(() => {
|
||||
newProject({ packages: ['@nx/react'] });
|
||||
ensureCypressInstallation();
|
||||
runCLI(
|
||||
`generate @nx/react:app ${appName} --use-react-router --routing --linter=eslint --unit-test-runner=vitest --no-interactive`
|
||||
);
|
||||
});
|
||||
|
||||
const packageJson = JSON.parse(readFile('package.json'));
|
||||
expect(packageJson.dependencies['react-router']).toBeDefined();
|
||||
expect(packageJson.dependencies['@react-router/node']).toBeDefined();
|
||||
expect(packageJson.dependencies['@react-router/serve']).toBeDefined();
|
||||
expect(packageJson.dependencies['isbot']).toBeDefined();
|
||||
afterAll(() => cleanupProject());
|
||||
|
||||
checkFilesExist(`${appName}/app/app.tsx`);
|
||||
checkFilesExist(`${appName}/app/entry.client.tsx`);
|
||||
checkFilesExist(`${appName}/app/entry.server.tsx`);
|
||||
checkFilesExist(`${appName}/app/routes.tsx`);
|
||||
checkFilesExist(`${appName}/react-router.config.ts`);
|
||||
checkFilesExist(`${appName}/vite.config.ts`);
|
||||
it('should generate a react-router application', async () => {
|
||||
const packageJson = JSON.parse(readFile('package.json'));
|
||||
expect(packageJson.dependencies['react-router']).toBeDefined();
|
||||
expect(packageJson.dependencies['@react-router/node']).toBeDefined();
|
||||
expect(packageJson.dependencies['@react-router/serve']).toBeDefined();
|
||||
expect(packageJson.dependencies['isbot']).toBeDefined();
|
||||
|
||||
checkFilesExist(`${appName}/app/app.tsx`);
|
||||
checkFilesExist(`${appName}/app/entry.client.tsx`);
|
||||
checkFilesExist(`${appName}/app/entry.server.tsx`);
|
||||
checkFilesExist(`${appName}/app/routes.tsx`);
|
||||
checkFilesExist(`${appName}/react-router.config.ts`);
|
||||
checkFilesExist(`${appName}/vite.config.ts`);
|
||||
});
|
||||
|
||||
it('should be able to build a react-router application', async () => {
|
||||
const buildResult = runCLI(`build ${appName}`);
|
||||
expect(buildResult).toContain('Successfully ran target build');
|
||||
});
|
||||
|
||||
it('should be able to lint a react-router application', async () => {
|
||||
const lintResult = runCLI(`lint ${appName}`);
|
||||
expect(lintResult).toContain('Successfully ran target lint');
|
||||
});
|
||||
|
||||
it('should be able to test and typecheck a react-router application', async () => {
|
||||
const typeCheckResult = runCLI(`typecheck ${appName}`);
|
||||
expect(typeCheckResult).toContain('Successfully ran target typecheck');
|
||||
});
|
||||
|
||||
it('should be able to test and typecheck a react-router application with jest', async () => {
|
||||
const jestApp = uniq('jestApp');
|
||||
runCLI(
|
||||
`generate @nx/react:app ${jestApp} --use-react-router --routing --unit-test-runner=jest --no-interactive`
|
||||
);
|
||||
|
||||
const testResult = runCLI(`test ${jestApp}`);
|
||||
expect(testResult).toContain('Successfully ran target test');
|
||||
|
||||
const typeCheckResult = runCLI(`typecheck ${jestApp}`);
|
||||
expect(typeCheckResult).toContain('Successfully ran target typecheck');
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to build a react-router application', async () => {
|
||||
describe('TS Solution', () => {
|
||||
const appName = uniq('app');
|
||||
runCLI(
|
||||
`generate @nx/react:app ${appName} --use-react-router --routing --no-interactive`
|
||||
);
|
||||
beforeAll(() => {
|
||||
newProject({ preset: 'ts', packages: ['@nx/react'] });
|
||||
ensureCypressInstallation();
|
||||
runCLI(
|
||||
`generate @nx/react:app ${appName} --use-react-router --routing --linter=eslint --unit-test-runner=vitest --no-interactive`
|
||||
);
|
||||
});
|
||||
|
||||
const buildResult = runCLI(`build ${appName}`);
|
||||
expect(buildResult).toContain('Successfully ran target build');
|
||||
});
|
||||
afterAll(() => cleanupProject());
|
||||
|
||||
it('should be able to lint a react-router application', async () => {
|
||||
const appName = uniq('app');
|
||||
runCLI(
|
||||
`generate @nx/react:app ${appName} --use-react-router --routing --linter=eslint --no-interactive`
|
||||
);
|
||||
it('should generate a react-router application', async () => {
|
||||
const packageJson = JSON.parse(readFile('package.json'));
|
||||
expect(packageJson.dependencies['react-router']).toBeDefined();
|
||||
expect(packageJson.dependencies['@react-router/node']).toBeDefined();
|
||||
expect(packageJson.dependencies['@react-router/serve']).toBeDefined();
|
||||
expect(packageJson.dependencies['isbot']).toBeDefined();
|
||||
|
||||
const buildResult = runCLI(`lint ${appName}`);
|
||||
expect(buildResult).toContain('Successfully ran target lint');
|
||||
});
|
||||
checkFilesExist(`${appName}/app/app.tsx`);
|
||||
checkFilesExist(`${appName}/app/entry.client.tsx`);
|
||||
checkFilesExist(`${appName}/app/entry.server.tsx`);
|
||||
checkFilesExist(`${appName}/app/routes.tsx`);
|
||||
checkFilesExist(`${appName}/react-router.config.ts`);
|
||||
checkFilesExist(`${appName}/vite.config.ts`);
|
||||
});
|
||||
|
||||
it('should be able to test a react-router application', async () => {
|
||||
const appName = uniq('app');
|
||||
runCLI(
|
||||
`generate @nx/react:app ${appName} --use-react-router --routing --unit-test-runner=vitest --no-interactive`
|
||||
);
|
||||
it('should be able to build a react-router application', async () => {
|
||||
const buildResult = runCLI(`build ${appName}`);
|
||||
expect(buildResult).toContain('Successfully ran target build');
|
||||
});
|
||||
|
||||
const buildResult = runCLI(`test ${appName}`);
|
||||
expect(buildResult).toContain('Successfully ran target test');
|
||||
it('should be able to lint a react-router application', async () => {
|
||||
const lintResult = runCLI(`lint ${appName}`);
|
||||
expect(lintResult).toContain('Successfully ran target lint');
|
||||
});
|
||||
|
||||
it('should be able to test and typecheck a react-router application', async () => {
|
||||
const testResult = runCLI(`test ${appName}`);
|
||||
expect(testResult).toContain('Successfully ran target test');
|
||||
|
||||
const typeCheckResult = runCLI(`typecheck ${appName}`);
|
||||
expect(typeCheckResult).toContain('Successfully ran target typecheck');
|
||||
});
|
||||
|
||||
it('should be able to test and typecheck a react-router application with jest', async () => {
|
||||
const jestApp = uniq('jestApp');
|
||||
runCLI(
|
||||
`generate @nx/react:app ${jestApp} --use-react-router --routing --unit-test-runner=jest --no-interactive`
|
||||
);
|
||||
|
||||
const testResult = runCLI(`test ${jestApp}`);
|
||||
expect(testResult).toContain('Successfully ran target test');
|
||||
|
||||
const typeCheckResult = runCLI(`typecheck ${jestApp}`);
|
||||
expect(typeCheckResult).toContain('Successfully ran target typecheck');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import {
|
||||
checkFilesExist,
|
||||
cleanupProject,
|
||||
getPackageManagerCommand,
|
||||
newProject,
|
||||
readFile,
|
||||
readJson,
|
||||
runCLI,
|
||||
runCommand,
|
||||
uniq,
|
||||
updateFile,
|
||||
updateJson,
|
||||
} from '@nx/e2e/utils';
|
||||
|
||||
describe('React (TS solution)', () => {
|
||||
@@ -38,4 +44,47 @@ describe('React (TS solution)', () => {
|
||||
`Successfully ran target test for project ${lib}`
|
||||
);
|
||||
}, 90000);
|
||||
|
||||
it('should be able to use Webpack to build apps with an imported lib', async () => {
|
||||
const appName = uniq('app');
|
||||
const libName = uniq('lib');
|
||||
|
||||
runCLI(
|
||||
`generate @nx/react:app packages/${appName} --bundler=webpack --no-interactive --skipFormat --linter=eslint --unitTestRunner=none`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nx/js:lib libs/${libName} --bundler=none --no-interactive --unit-test-runner=none --skipFormat --linter=eslint`
|
||||
);
|
||||
|
||||
const mainPath = `packages/${appName}/src/main.tsx`;
|
||||
updateFile(
|
||||
mainPath,
|
||||
`
|
||||
import {${libName}} from '@${workspaceName}/${libName}';
|
||||
${readFile(mainPath)}
|
||||
console.log(${libName}());
|
||||
`
|
||||
);
|
||||
|
||||
runCLI('sync');
|
||||
|
||||
// Add library to package.json to make sure it is linked (not needed for npm package manager)
|
||||
updateJson(`packages/${appName}/package.json`, (json) => {
|
||||
return {
|
||||
...json,
|
||||
devDependencies: {
|
||||
...(json.devDependencies || {}),
|
||||
[`@${workspaceName}/${libName}`]: 'workspace:*',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
runCommand(
|
||||
`cd packages/${appName} && ${getPackageManagerCommand().install}`
|
||||
);
|
||||
|
||||
runCLI(`build ${appName}`);
|
||||
|
||||
checkFilesExist(`packages/${appName}/dist/index.html`);
|
||||
}, 90_000);
|
||||
});
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
updateFile,
|
||||
} from '@nx/e2e/utils';
|
||||
|
||||
describe('Build React applications and libraries with Vite', () => {
|
||||
describe('Build React applications and libraries with Webpack', () => {
|
||||
beforeAll(() => {
|
||||
newProject({
|
||||
packages: ['@nx/react'],
|
||||
|
||||
@@ -106,7 +106,7 @@ ${content}`
|
||||
|
||||
// check typecheck
|
||||
expect(runCLI(`typecheck ${reactApp}`)).toContain(
|
||||
`Successfully ran target typecheck for project @proj/${reactApp} and 5 tasks it depends on`
|
||||
`Successfully ran target typecheck for project @proj/${reactApp} and 6 tasks it depends on`
|
||||
);
|
||||
}, 300_000);
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
import { readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { TagsApi } from './tags.api';
|
||||
// The rewriteMarkdownLinks function is used by routes, not by this API
|
||||
|
||||
interface StaticDocumentPaths {
|
||||
params: { segments: string[] };
|
||||
@@ -13,6 +14,11 @@ interface StaticDocumentPaths {
|
||||
|
||||
export class DocumentsApi {
|
||||
private readonly manifest: Record<string, DocumentMetadata>;
|
||||
private readonly processContent: boolean;
|
||||
private readonly id: string;
|
||||
private readonly prefix: string;
|
||||
private readonly publicDocsRoot: string;
|
||||
private readonly tagsApi: TagsApi;
|
||||
|
||||
constructor(
|
||||
private readonly options: {
|
||||
@@ -21,6 +27,7 @@ export class DocumentsApi {
|
||||
prefix: string;
|
||||
publicDocsRoot: string;
|
||||
tagsApi: TagsApi;
|
||||
processContent?: boolean;
|
||||
}
|
||||
) {
|
||||
if (!options.id) {
|
||||
@@ -46,6 +53,11 @@ export class DocumentsApi {
|
||||
this.getAngularRspackPackage()
|
||||
);
|
||||
}
|
||||
this.id = options.id;
|
||||
this.prefix = options.prefix;
|
||||
this.publicDocsRoot = options.publicDocsRoot;
|
||||
this.tagsApi = options.tagsApi;
|
||||
this.processContent = options.processContent !== false;
|
||||
}
|
||||
|
||||
private getManifestKey(path: string): string {
|
||||
@@ -99,28 +111,29 @@ export class DocumentsApi {
|
||||
}
|
||||
|
||||
getFilePath(path: string): string {
|
||||
return join(this.options.publicDocsRoot, `${path}.md`);
|
||||
return join(this.publicDocsRoot, `${path}.md`);
|
||||
}
|
||||
|
||||
getParamsStaticDocumentPaths(): StaticDocumentPaths[] {
|
||||
return Object.keys(this.manifest).map((path) => ({
|
||||
params: {
|
||||
segments: !!this.options.prefix
|
||||
? [this.options.prefix].concat(path.split('/').filter(Boolean).flat())
|
||||
segments: !!this.prefix
|
||||
? [this.prefix].concat(path.split('/').filter(Boolean).flat())
|
||||
: path.split('/').filter(Boolean).flat(),
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
getSlugsStaticDocumentPaths(): string[] {
|
||||
if (this.options.prefix)
|
||||
return Object.keys(this.manifest).map(
|
||||
(path) => `/${this.options.prefix}` + path
|
||||
);
|
||||
if (this.prefix)
|
||||
return Object.keys(this.manifest).map((path) => `/${this.prefix}` + path);
|
||||
return Object.keys(this.manifest);
|
||||
}
|
||||
|
||||
getDocument(path: string[]): ProcessedDocument {
|
||||
path = path.map((p) => p.replace('.md', ''));
|
||||
console.log('🔥 ath', path);
|
||||
|
||||
const document: DocumentMetadata | null =
|
||||
this.manifest[this.getManifestKey(path.join('/'))] || null;
|
||||
|
||||
@@ -131,8 +144,12 @@ export class DocumentsApi {
|
||||
path[2] === 'documents'
|
||||
) {
|
||||
const file = `generated/devkit/${path.slice(3).join('/')}`;
|
||||
let content = readFileSync(this.getFilePath(file), 'utf8');
|
||||
|
||||
// Add code to process markdown links if needed
|
||||
|
||||
return {
|
||||
content: readFileSync(this.getFilePath(file), 'utf8'),
|
||||
content,
|
||||
description: '',
|
||||
filePath: this.getFilePath(file),
|
||||
id: path.at(-1) || '',
|
||||
@@ -146,8 +163,13 @@ export class DocumentsApi {
|
||||
);
|
||||
}
|
||||
if (this.isDocumentIndex(document)) return this.getDocumentIndex(path);
|
||||
|
||||
let content = readFileSync(this.getFilePath(document.file), 'utf8');
|
||||
|
||||
// Add code to process markdown links if needed
|
||||
|
||||
return {
|
||||
content: readFileSync(this.getFilePath(document.file), 'utf8'),
|
||||
content,
|
||||
description: document.description,
|
||||
filePath: this.getFilePath(document.file),
|
||||
id: document.id,
|
||||
@@ -182,8 +204,7 @@ export class DocumentsApi {
|
||||
getRelatedDocuments(tags: string[]): Record<string, RelatedDocument[]> {
|
||||
const relatedDocuments = {};
|
||||
tags.forEach(
|
||||
(tag) =>
|
||||
(relatedDocuments[tag] = this.options.tagsApi.getAssociatedItems(tag))
|
||||
(tag) => (relatedDocuments[tag] = this.tagsApi.getAssociatedItems(tag))
|
||||
);
|
||||
|
||||
return relatedDocuments;
|
||||
@@ -204,9 +225,7 @@ export class DocumentsApi {
|
||||
(card) =>
|
||||
`{% card title="${card.title}" description="${
|
||||
card.description
|
||||
}" url="${[this.options.prefix, card.url]
|
||||
.filter(Boolean)
|
||||
.join('/')}" /%}\n`
|
||||
}" url="${[this.prefix, card.url].filter(Boolean).join('/')}" /%}\n`
|
||||
)
|
||||
.join('');
|
||||
return [
|
||||
@@ -226,9 +245,10 @@ export class DocumentsApi {
|
||||
`Document not found in manifest with: "${path.join('/')}"`
|
||||
);
|
||||
|
||||
if (!!document.file)
|
||||
if (!!document.file) {
|
||||
const content = readFileSync(this.getFilePath(document.file), 'utf8');
|
||||
return {
|
||||
content: readFileSync(this.getFilePath(document.file), 'utf8'),
|
||||
content: content,
|
||||
description: document.description,
|
||||
filePath: this.getFilePath(document.file),
|
||||
id: document.id,
|
||||
@@ -236,9 +256,11 @@ export class DocumentsApi {
|
||||
relatedDocuments: this.getRelatedDocuments(document.tags),
|
||||
tags: document.tags,
|
||||
};
|
||||
}
|
||||
|
||||
const content = this.generateDocumentIndexTemplate(document);
|
||||
return {
|
||||
content: this.generateDocumentIndexTemplate(document),
|
||||
content: content,
|
||||
description: document.description,
|
||||
filePath: '',
|
||||
id: document.id,
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { DefaultLayout } from '@nx/nx-dev/ui-common';
|
||||
import {
|
||||
CallToAction,
|
||||
Features,
|
||||
FeatureSections,
|
||||
GettingStarted,
|
||||
Hero,
|
||||
Resources,
|
||||
} from '@nx/nx-dev/ui-gradle';
|
||||
|
||||
import type { Metadata } from 'next';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Nx For Java',
|
||||
description:
|
||||
'Add Nx to your Java project for distributed task execution, intelligent caching, and affected commands.',
|
||||
alternates: {
|
||||
canonical: 'https://nx.dev/java',
|
||||
},
|
||||
openGraph: {
|
||||
url: 'https://nx.dev/java',
|
||||
title: 'Nx For Java',
|
||||
description:
|
||||
'Add Nx to your Java project for distributed task execution, intelligent caching, and affected commands.',
|
||||
images: [
|
||||
{
|
||||
url: 'https://nx.dev/socials/nx-gradle-media.png',
|
||||
width: 800,
|
||||
height: 421,
|
||||
alt: 'Nx For Java',
|
||||
type: 'image/jpeg',
|
||||
},
|
||||
],
|
||||
siteName: 'Nx',
|
||||
type: 'website',
|
||||
},
|
||||
};
|
||||
|
||||
export default function JavaPage(): JSX.Element {
|
||||
return (
|
||||
<DefaultLayout>
|
||||
<Hero />
|
||||
|
||||
<div className="mx-auto max-w-7xl px-6 lg:px-8">
|
||||
<div className="mt-16 scroll-mt-16 lg:mt-32" id="features">
|
||||
<Features />
|
||||
</div>
|
||||
|
||||
{/* <div className="mt-16 scroll-mt-16 lg:mt-32" id="resources"> */}
|
||||
{/* <Resources /> */}
|
||||
{/* </div> */}
|
||||
</div>
|
||||
|
||||
{/* <div className="mt-16 scroll-mt-16 lg:mt-32" id="feature-sections"> */}
|
||||
{/* <FeatureSections /> */}
|
||||
{/* </div> */}
|
||||
|
||||
<div className="mt-16 scroll-mt-16 lg:mt-32">
|
||||
<GettingStarted />
|
||||
</div>
|
||||
<div className="overflow-hidden py-8 sm:py-8">
|
||||
<CallToAction />
|
||||
</div>
|
||||
</DefaultLayout>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
interface DocumentMetadata {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
path: string;
|
||||
file: string;
|
||||
isExternal: boolean;
|
||||
itemList: DocumentMetadata[];
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
interface MenuItem {
|
||||
id: string;
|
||||
name: string;
|
||||
path: string;
|
||||
isExternal: boolean;
|
||||
disableCollapsible: boolean;
|
||||
children: MenuItem[];
|
||||
}
|
||||
|
||||
interface MenuSection {
|
||||
id: string;
|
||||
menu: MenuItem[];
|
||||
}
|
||||
|
||||
// Markdown content for llms.txt - this is the static part
|
||||
const llmsContent = `# Nx and LLMs
|
||||
|
||||
Nx provides excellent support for Large Language Models (LLMs) integration in your development workflow.
|
||||
|
||||
## Features
|
||||
|
||||
- AI-powered code generation
|
||||
- Smart dependency analysis
|
||||
- Automated refactoring suggestions
|
||||
- Context-aware project scaffolding
|
||||
|
||||
## Getting Started
|
||||
|
||||
To get started with Nx LLM features, check out our documentation at [nx.dev/ai](https://nx.dev/ai).
|
||||
|
||||
## Supported Models
|
||||
|
||||
Nx works with various language models including:
|
||||
- GPT-4
|
||||
- Claude
|
||||
- Llama
|
||||
- And more
|
||||
|
||||
## Community
|
||||
|
||||
Join our community to learn more about how developers are using Nx with LLMs to supercharge their productivity.
|
||||
|
||||
`;
|
||||
|
||||
/**
|
||||
* Reads all manifest files to create a mapping of paths to descriptions
|
||||
*/
|
||||
function loadDocumentDescriptions(): Record<string, string> {
|
||||
try {
|
||||
const descriptions: Record<string, string> = {};
|
||||
const manifestsDir = path.join(
|
||||
process.cwd(),
|
||||
'public/documentation/generated/manifests'
|
||||
);
|
||||
|
||||
// Load primary manifests (nx, extending-nx, ci)
|
||||
const manifestFiles = ['nx.json', 'extending-nx.json', 'ci.json'];
|
||||
|
||||
manifestFiles.forEach((file) => {
|
||||
try {
|
||||
const manifestPath = path.join(manifestsDir, file);
|
||||
if (fs.existsSync(manifestPath)) {
|
||||
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
||||
|
||||
// Each manifest is a dictionary where keys are paths and values are document metadata
|
||||
Object.values(manifest).forEach((doc: DocumentMetadata) => {
|
||||
if (doc.path && doc.description) {
|
||||
descriptions[doc.path] = doc.description;
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error loading manifest file ${file}:`, error);
|
||||
}
|
||||
});
|
||||
|
||||
return descriptions;
|
||||
} catch (error) {
|
||||
console.error('Error loading document descriptions:', error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates markdown for a menu item and its children
|
||||
*/
|
||||
function generateMenuItemMarkdown(
|
||||
item: MenuItem,
|
||||
descriptions: Record<string, string>,
|
||||
indent: number = 0
|
||||
): string {
|
||||
if (item.isExternal) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const spacing = ' '.repeat(indent * 2);
|
||||
const description = descriptions[item.path] || '';
|
||||
const link = item.path.startsWith('http')
|
||||
? `[${item.name}](${item.path})`
|
||||
: `[${item.name}](/m${item.path})`;
|
||||
|
||||
// Place description after the link, not inside it
|
||||
let markdown = `${spacing}- ${link}${
|
||||
description ? ': ' + description : ''
|
||||
}\n`;
|
||||
|
||||
if (item.children && item.children.length > 0) {
|
||||
item.children.forEach((child) => {
|
||||
markdown += generateMenuItemMarkdown(child, descriptions, indent + 1);
|
||||
});
|
||||
}
|
||||
|
||||
return markdown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a sitemap from menu structure with .md extensions and descriptions
|
||||
*/
|
||||
async function generateSitemapFromMenus(): Promise<string> {
|
||||
try {
|
||||
const menusPath = path.join(
|
||||
process.cwd(),
|
||||
'public/documentation/generated/manifests/menus.json'
|
||||
);
|
||||
|
||||
if (!fs.existsSync(menusPath)) {
|
||||
throw new Error(`Menus file not found at: ${menusPath}`);
|
||||
}
|
||||
|
||||
const menus: MenuSection[] = JSON.parse(fs.readFileSync(menusPath, 'utf8'));
|
||||
const descriptions = loadDocumentDescriptions();
|
||||
|
||||
let sitemap = '## Documentation Structure\n\n';
|
||||
|
||||
// Process main sections (nx, extending-nx, ci)
|
||||
['nx', 'extending-nx', 'ci'].forEach((sectionId) => {
|
||||
const section = menus.find((m) => m.id === sectionId);
|
||||
if (!section) return;
|
||||
|
||||
// Add section header
|
||||
sitemap += `- ${
|
||||
sectionId === 'nx'
|
||||
? 'Nx'
|
||||
: sectionId === 'extending-nx'
|
||||
? 'Extending-nx'
|
||||
: sectionId === 'ci'
|
||||
? 'CI'
|
||||
: sectionId
|
||||
}\n\n`;
|
||||
|
||||
// Process all menu items in the section
|
||||
section.menu.forEach((item) => {
|
||||
sitemap += generateMenuItemMarkdown(item, descriptions, 1);
|
||||
});
|
||||
|
||||
sitemap += '\n';
|
||||
});
|
||||
|
||||
return sitemap;
|
||||
} catch (error) {
|
||||
console.error('Error generating sitemap from menus:', error);
|
||||
// Fall back to modifying existing sitemap
|
||||
return await generateModifiedSitemap();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the sitemap markdown file and modifies links adding .md extension
|
||||
* and document descriptions
|
||||
*/
|
||||
async function generateModifiedSitemap(): Promise<string> {
|
||||
try {
|
||||
const sitemapPath = path.join(
|
||||
process.cwd(),
|
||||
'public/documentation/shared/reference/sitemap.md'
|
||||
);
|
||||
|
||||
if (!fs.existsSync(sitemapPath)) {
|
||||
throw new Error(`Sitemap file not found at: ${sitemapPath}`);
|
||||
}
|
||||
|
||||
const originalContent = fs.readFileSync(sitemapPath, 'utf8');
|
||||
const descriptions = loadDocumentDescriptions();
|
||||
|
||||
// Transform links from [Name](/path) to [Name](/path.md): Description
|
||||
const modifiedContent = originalContent.replace(
|
||||
/\[([^\]]+)\]\(([^)]+)\)/g,
|
||||
(match, linkText, linkUrl) => {
|
||||
// Skip external links
|
||||
if (linkUrl.startsWith('http')) {
|
||||
return match;
|
||||
}
|
||||
|
||||
// Add .md extension to the link and place description after the link
|
||||
const description = descriptions[linkUrl] || '';
|
||||
return `[${linkText}](${linkUrl}.md)${
|
||||
description ? ': ' + description : ''
|
||||
}`;
|
||||
}
|
||||
);
|
||||
|
||||
return '## Documentation Structure\n\n' + modifiedContent;
|
||||
} catch (error) {
|
||||
console.error('Error generating modified sitemap:', error);
|
||||
return `## Documentation Structure\n\nError generating sitemap: ${error.message}`;
|
||||
}
|
||||
}
|
||||
|
||||
// GET handler for the route
|
||||
export async function GET(request: Request) {
|
||||
try {
|
||||
// Always start with the static content
|
||||
let fullContent = llmsContent;
|
||||
|
||||
// Then append the sitemap
|
||||
let sitemapContent;
|
||||
try {
|
||||
sitemapContent = await generateSitemapFromMenus();
|
||||
} catch (error) {
|
||||
sitemapContent = await generateModifiedSitemap();
|
||||
}
|
||||
|
||||
fullContent += sitemapContent;
|
||||
|
||||
return new NextResponse(fullContent, {
|
||||
headers: {
|
||||
'Content-Type': 'text/plain; charset=utf-8',
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
return new NextResponse(`Error: ${error.message}`, {
|
||||
status: 500,
|
||||
headers: {
|
||||
'Content-Type': 'text/plain; charset=utf-8',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// We can make this static since the content is generated at build time
|
||||
export const dynamic = 'force-static';
|
||||
@@ -0,0 +1,55 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { nxDocumentationApi } from '../../../lib/nx.api';
|
||||
import { rewriteMarkdownLinks } from '../../../lib/markdown-utils';
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: { path: string[] } }
|
||||
) {
|
||||
try {
|
||||
// Get the document using the document API
|
||||
const document = nxDocumentationApi.getDocument(params.path);
|
||||
|
||||
// Process the content to rewrite local links with the /m prefix
|
||||
const processedContent = rewriteMarkdownLinks(document.content);
|
||||
|
||||
// Return the processed markdown content with proper headers
|
||||
return new NextResponse(processedContent, {
|
||||
headers: {
|
||||
'Content-Type': 'text/markdown; charset=utf-8',
|
||||
'Content-Disposition': 'inline; filename="document.md"',
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
// Return a markdown 404 message
|
||||
return new NextResponse(
|
||||
`# Not Found\n\nThe requested document could not be found.`,
|
||||
{
|
||||
status: 404,
|
||||
headers: {
|
||||
'Content-Type': 'text/markdown; charset=utf-8',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Define which paths should be statically generated at build time
|
||||
export function generateStaticParams() {
|
||||
const reservedPaths = ['/ci', '/nx-api', '/changelog'];
|
||||
|
||||
// Get all document paths and filter out reserved ones
|
||||
const documentPaths = nxDocumentationApi
|
||||
.getSlugsStaticDocumentPaths()
|
||||
.filter(
|
||||
(path) => !reservedPaths.some((reserved) => path.startsWith(reserved))
|
||||
);
|
||||
|
||||
// Convert paths to segment arrays for Next.js App Router
|
||||
return documentPaths.map((path) => ({
|
||||
path: path.split('/').filter(Boolean),
|
||||
}));
|
||||
}
|
||||
|
||||
// Make this route static at build time for performance
|
||||
export const dynamic = 'force-static';
|
||||
@@ -0,0 +1,44 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { changeLogApi } from '../../../lib/changelog.api';
|
||||
import { rewriteMarkdownLinks } from '../../../lib/markdown-utils';
|
||||
|
||||
export async function GET(request: Request) {
|
||||
try {
|
||||
// Get the changelog entries
|
||||
const entries = changeLogApi.getChangelogEntries();
|
||||
|
||||
// Create a markdown document from the entries
|
||||
let content = '# Nx Changelog\n\n';
|
||||
|
||||
// Add entries to the content in markdown format
|
||||
entries.forEach((entry) => {
|
||||
content += `## ${entry.version}\n\n`;
|
||||
if (entry.content) {
|
||||
// Add processed content
|
||||
content += rewriteMarkdownLinks(entry.content) + '\n\n';
|
||||
}
|
||||
});
|
||||
|
||||
// Return the processed markdown content with proper headers
|
||||
return new NextResponse(content, {
|
||||
headers: {
|
||||
'Content-Type': 'text/markdown; charset=utf-8',
|
||||
'Content-Disposition': 'inline; filename="changelog.md"',
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
// Return a markdown 404 message
|
||||
return new NextResponse(
|
||||
`# Not Found\n\nThe changelog could not be found.`,
|
||||
{
|
||||
status: 404,
|
||||
headers: {
|
||||
'Content-Type': 'text/markdown; charset=utf-8',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Make this route static at build time for performance
|
||||
export const dynamic = 'force-static';
|
||||
@@ -0,0 +1,51 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { ciApi } from '../../../../lib/ci.api';
|
||||
import { rewriteMarkdownLinks } from '../../../../lib/markdown-utils';
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: { path: string[] } }
|
||||
) {
|
||||
try {
|
||||
// Get the document using the document API
|
||||
const document = ciApi.getDocument(['ci', ...params.path]);
|
||||
|
||||
// Process the content to rewrite local links with the /m prefix
|
||||
const processedContent = rewriteMarkdownLinks(document.content);
|
||||
|
||||
// Return the processed markdown content with proper headers
|
||||
return new NextResponse(processedContent, {
|
||||
headers: {
|
||||
'Content-Type': 'text/markdown; charset=utf-8',
|
||||
'Content-Disposition': 'inline; filename="document.md"',
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
// Return a markdown 404 message
|
||||
return new NextResponse(
|
||||
`# Not Found\n\nThe requested document could not be found.`,
|
||||
{
|
||||
status: 404,
|
||||
headers: {
|
||||
'Content-Type': 'text/markdown; charset=utf-8',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Define which paths should be statically generated at build time
|
||||
export function generateStaticParams() {
|
||||
// Get all document paths for the CI section
|
||||
const documentPaths = ciApi
|
||||
.getSlugsStaticDocumentPaths()
|
||||
.map((path) => path.replace(/^\/ci\//, '')); // Remove the /ci/ prefix
|
||||
|
||||
// Convert paths to segment arrays for Next.js App Router
|
||||
return documentPaths.map((path) => ({
|
||||
path: path.split('/').filter(Boolean),
|
||||
}));
|
||||
}
|
||||
|
||||
// Make this route static at build time for performance
|
||||
export const dynamic = 'force-static';
|
||||
@@ -0,0 +1,51 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { nxPluginsApi } from '../../../../lib/plugins.api';
|
||||
import { rewriteMarkdownLinks } from '../../../../lib/markdown-utils';
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: { path: string[] } }
|
||||
) {
|
||||
try {
|
||||
// Get the document using the document API
|
||||
const document = nxPluginsApi.getDocument(['extending-nx', ...params.path]);
|
||||
|
||||
// Process the content to rewrite local links with the /m prefix
|
||||
const processedContent = rewriteMarkdownLinks(document.content);
|
||||
|
||||
// Return the processed markdown content with proper headers
|
||||
return new NextResponse(processedContent, {
|
||||
headers: {
|
||||
'Content-Type': 'text/markdown; charset=utf-8',
|
||||
'Content-Disposition': 'inline; filename="document.md"',
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
// Return a markdown 404 message
|
||||
return new NextResponse(
|
||||
`# Not Found\n\nThe requested document could not be found.`,
|
||||
{
|
||||
status: 404,
|
||||
headers: {
|
||||
'Content-Type': 'text/markdown; charset=utf-8',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Define which paths should be statically generated at build time
|
||||
export function generateStaticParams() {
|
||||
// Get all document paths for the extending-nx section
|
||||
const documentPaths = nxPluginsApi
|
||||
.getSlugsStaticDocumentPaths()
|
||||
.map((path) => path.replace(/^\/extending-nx\//, '')); // Remove the /extending-nx/ prefix
|
||||
|
||||
// Convert paths to segment arrays for Next.js App Router
|
||||
return documentPaths.map((path) => ({
|
||||
path: path.split('/').filter(Boolean),
|
||||
}));
|
||||
}
|
||||
|
||||
// Make this route static at build time for performance
|
||||
export const dynamic = 'force-static';
|
||||
@@ -0,0 +1,61 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { nxPackagesApi } from '../../../../../../lib/packages.api';
|
||||
import { tagsApi } from '../../../../../../lib/tags.api';
|
||||
import { rewriteMarkdownLinks } from '../../../../../../lib/markdown-utils';
|
||||
import { readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: { name: string; path: string[] } }
|
||||
) {
|
||||
try {
|
||||
// Get the package documents and file path
|
||||
const packageDocuments = nxPackagesApi.getPackageDocuments(params.name);
|
||||
const segments = ['nx-api', params.name, 'documents', ...params.path];
|
||||
const docPath = '/' + segments.join('/');
|
||||
|
||||
// Find the document in the package documents
|
||||
const documentMeta = packageDocuments[docPath];
|
||||
|
||||
if (!documentMeta) {
|
||||
throw new Error(`Document not found: ${docPath}`);
|
||||
}
|
||||
|
||||
// Read the file content
|
||||
const filePath = join('public/documentation', `${documentMeta.file}.md`);
|
||||
const content = readFileSync(filePath, 'utf8');
|
||||
|
||||
// Process the content to rewrite local links with the /m prefix
|
||||
const processedContent = rewriteMarkdownLinks(content);
|
||||
|
||||
// Return the processed markdown content with proper headers
|
||||
return new NextResponse(processedContent, {
|
||||
headers: {
|
||||
'Content-Type': 'text/markdown; charset=utf-8',
|
||||
'Content-Disposition': 'inline; filename="document.md"',
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error serving nx-api document:', error);
|
||||
// Return a markdown 404 message
|
||||
return new NextResponse(
|
||||
`# Not Found\n\nThe requested document could not be found.`,
|
||||
{
|
||||
status: 404,
|
||||
headers: {
|
||||
'Content-Type': 'text/markdown; charset=utf-8',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Define which paths should be statically generated at build time
|
||||
export function generateStaticParams() {
|
||||
// For simplicity, we don't pre-generate these - they'll be generated on-demand
|
||||
return [];
|
||||
}
|
||||
|
||||
// Make this route dynamic for on-demand generation
|
||||
export const dynamic = 'force-dynamic';
|
||||
@@ -426,4 +426,29 @@ export const champions: Champion[] = [
|
||||
],
|
||||
location: 'London, United Kingdom',
|
||||
},
|
||||
{
|
||||
name: 'Kevin Oliveira',
|
||||
expertise: 'Frontend Architect. Nx Plugins, DDD, Angular, NestJS, DevOps',
|
||||
imageUrl: '/images/champions/kevin-oliveira.webp',
|
||||
contact: [
|
||||
{
|
||||
label: '@koliveira',
|
||||
link: 'https://twitter.com/koliveira',
|
||||
},
|
||||
],
|
||||
location: 'Rhode Island, USA',
|
||||
},
|
||||
{
|
||||
name: 'Guilherme Siquinelli',
|
||||
expertise:
|
||||
'Talks, Conferences, Community, Monorepos with NX / Angular / NestJS Architecture, Repository Structure, Writing Plugins, Dev Tools, Releases, Blog Posts, Youtube Videos, Open Source',
|
||||
imageUrl: '/images/champions/guilherme-siquinelli.webp',
|
||||
contact: [
|
||||
{
|
||||
label: 'email@guiseek.dev',
|
||||
link: 'mailto:email@guiseek.dev',
|
||||
},
|
||||
],
|
||||
location: 'Paraná, Brazil',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Rewrites local links in markdown content to include the /m prefix
|
||||
* @param content Markdown content
|
||||
* @returns Processed markdown content with rewritten links
|
||||
*/
|
||||
export function rewriteMarkdownLinks(content: string): string {
|
||||
// Process regular markdown links [text](url)
|
||||
let processedContent = content.replace(
|
||||
/\[([^\]]+)\]\(([^)]+)\)/g,
|
||||
(match, linkText, linkUrl) => {
|
||||
// Skip external links and non-path links (like anchors)
|
||||
if (linkUrl.startsWith('http') || linkUrl.startsWith('#')) {
|
||||
return match;
|
||||
}
|
||||
|
||||
// Add /m prefix to local links that start with /
|
||||
if (linkUrl.startsWith('/')) {
|
||||
return `[${linkText}](/m${linkUrl})`;
|
||||
}
|
||||
|
||||
return match;
|
||||
}
|
||||
);
|
||||
|
||||
// Process image links 
|
||||
processedContent = processedContent.replace(
|
||||
/!\[([^\]]*)\]\(([^)]+)\)/g,
|
||||
(match, altText, imageUrl) => {
|
||||
// Skip external image links
|
||||
if (imageUrl.startsWith('http')) {
|
||||
return match;
|
||||
}
|
||||
|
||||
// Add /m prefix to local image links that start with /
|
||||
if (imageUrl.startsWith('/')) {
|
||||
return ``;
|
||||
}
|
||||
|
||||
return match;
|
||||
}
|
||||
);
|
||||
|
||||
return processedContent;
|
||||
}
|
||||
@@ -92,11 +92,12 @@ export const getStaticProps: GetStaticProps = async ({
|
||||
},
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
notFound: true,
|
||||
props: {
|
||||
statusCode: 404,
|
||||
},
|
||||
};
|
||||
throw e;
|
||||
// return {
|
||||
// notFound: true,
|
||||
// props: {
|
||||
// statusCode: 404,
|
||||
// },
|
||||
// };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -119,7 +119,7 @@ export default function Packages({
|
||||
<img
|
||||
className="h-5 w-5 object-cover opacity-75 dark:invert"
|
||||
loading="lazy"
|
||||
src={iconsMap[pkg.name]}
|
||||
src={iconsMap[pkg.name] || iconsMap['nx']}
|
||||
alt={pkg.name + ' illustration'}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
|
After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 593 KiB |
@@ -18,7 +18,9 @@ export function Layout({
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<main className="py-24 sm:py-32">{children}</main>
|
||||
<main className="py-24 sm:py-32" data-document="main">
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -31,7 +31,10 @@ export function DefaultLayout({
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<main className={isHome || hideHeader ? '' : 'py-24 sm:py-32'}>
|
||||
<main
|
||||
data-document="main"
|
||||
className={isHome || hideHeader ? '' : 'py-24 sm:py-32'}
|
||||
>
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
'use client';
|
||||
import { Fragment, ReactElement } from 'react';
|
||||
import { Fragment, type MouseEvent, ReactElement } from 'react';
|
||||
import { NxCloudAnimatedIcon, NxIcon } from '@nx/nx-dev/ui-icons';
|
||||
import {
|
||||
Bars3Icon,
|
||||
@@ -74,6 +74,11 @@ export function DocumentationHeader({
|
||||
!isChangelog &&
|
||||
!isAiChat;
|
||||
|
||||
const handleContextMenu = (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
router.push('/brands');
|
||||
};
|
||||
|
||||
const sections = [
|
||||
{ name: 'Nx', href: '/getting-started/intro', current: isNx },
|
||||
{
|
||||
@@ -217,8 +222,11 @@ export function DocumentationHeader({
|
||||
href="/"
|
||||
className="flex flex-grow items-center px-4 text-slate-900 lg:px-0 dark:text-white"
|
||||
prefetch={false}
|
||||
onContextMenu={handleContextMenu}
|
||||
>
|
||||
<span className="sr-only">Nx</span>
|
||||
<span className="sr-only">
|
||||
Nx – Left-click: Home. Right-click: Brands.
|
||||
</span>
|
||||
<NxIcon aria-hidden="true" className="h-8 w-8" />
|
||||
</Link>
|
||||
<Link
|
||||
|
||||
@@ -17,13 +17,20 @@ import {
|
||||
} from '@heroicons/react/24/outline';
|
||||
import cx from 'classnames';
|
||||
import Link from 'next/link';
|
||||
import { Fragment, ReactElement, useEffect, useState } from 'react';
|
||||
import {
|
||||
Fragment,
|
||||
type MouseEvent,
|
||||
ReactElement,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { ButtonLink, ButtonLinkProps } from '../button';
|
||||
import { resourceMenuItems } from './menu-items';
|
||||
import { MobileMenuItem } from './mobile-menu-item';
|
||||
import { SectionsMenu } from './sections-menu';
|
||||
import { AlgoliaSearch } from '@nx/nx-dev/feature-search';
|
||||
import { GitHubIcon, NxIcon } from '@nx/nx-dev/ui-icons';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
interface HeaderProps {
|
||||
ctaButtons?: ButtonLinkProps[];
|
||||
@@ -32,6 +39,13 @@ interface HeaderProps {
|
||||
export function Header({ ctaButtons }: HeaderProps): ReactElement {
|
||||
let [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const handleContextMenu = (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
router.push('/brands');
|
||||
};
|
||||
|
||||
// We need to close the popover if the route changes or the window is resized to prevent the popover from being stuck open.
|
||||
const checkSizeAndClosePopover = () => {
|
||||
const breakpoint = 1024; // This is the standard Tailwind lg breakpoint value
|
||||
@@ -78,8 +92,11 @@ export function Header({ ctaButtons }: HeaderProps): ReactElement {
|
||||
href="/"
|
||||
className="mr-4 flex items-center text-slate-900 dark:text-white"
|
||||
prefetch={false}
|
||||
onContextMenu={handleContextMenu}
|
||||
>
|
||||
<span className="sr-only">Nx</span>
|
||||
<span className="sr-only">
|
||||
Nx – Left-click: Home. Right-click: Brands.
|
||||
</span>
|
||||
<NxIcon aria-hidden="true" className="h-8 w-8" />
|
||||
</Link>
|
||||
<nav
|
||||
|
||||
@@ -21,7 +21,9 @@ export function Layout({
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<main className="py-24 sm:py-32">{children}</main>
|
||||
<main className="py-24 sm:py-32" data-document="main">
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
<Footer />
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@ export function EnterpriseLayout({
|
||||
return (
|
||||
<div className="w-full dark:bg-slate-950">
|
||||
<Header />
|
||||
<main>{children}</main>
|
||||
<main data-document="main">{children}</main>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@nx/react/babel",
|
||||
{
|
||||
"runtime": "automatic",
|
||||
"useBuiltIns": "usage"
|
||||
}
|
||||
]
|
||||
],
|
||||
"plugins": []
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": ["plugin:@nx/react", "../../.eslintrc.json"],
|
||||
"ignorePatterns": ["!**/*"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
# nx-dev-ui-gradle
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `nx test nx-dev-ui-gradle` to execute the unit tests via [Jest](https://jestjs.io).
|
||||
@@ -0,0 +1,10 @@
|
||||
/* eslint-disable */
|
||||
export default {
|
||||
displayName: 'nx-dev-ui-gradle',
|
||||
preset: '../../jest.preset.js',
|
||||
transform: {
|
||||
'^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/react/babel'] }],
|
||||
},
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
|
||||
coverageDirectory: '../../coverage/nx-dev/ui-gradle',
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "nx-dev-ui-gradle",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "nx-dev/ui-gradle/src",
|
||||
"projectType": "library",
|
||||
"tags": [],
|
||||
"targets": {}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './lib';
|
||||
@@ -0,0 +1,130 @@
|
||||
import { ButtonLink, SectionHeading } from '@nx/nx-dev/ui-common';
|
||||
import { GradleIcon } from '@nx/nx-dev/ui-icons';
|
||||
import { cx } from '@nx/nx-dev/ui-primitives';
|
||||
import React, { ReactElement } from 'react';
|
||||
import Link from 'next/link';
|
||||
|
||||
export function CallToAction(): ReactElement {
|
||||
return (
|
||||
<section className="relative isolate px-6 py-32 sm:py-40 lg:px-8">
|
||||
{/* Background pattern */}
|
||||
<svg
|
||||
className="absolute inset-0 -z-10 h-full w-full stroke-black/10 [mask-image:radial-gradient(100%_100%_at_top_right,white,transparent)] dark:stroke-white/10"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<defs>
|
||||
<pattern
|
||||
id="gradle-cta-pattern"
|
||||
width={200}
|
||||
height={200}
|
||||
x="50%"
|
||||
y={0}
|
||||
patternUnits="userSpaceOnUse"
|
||||
>
|
||||
<path d="M.5 200V.5H200" fill="none" />
|
||||
</pattern>
|
||||
</defs>
|
||||
<svg
|
||||
x="50%"
|
||||
y={0}
|
||||
className="overflow-visible fill-slate-200/20 dark:fill-slate-800/20"
|
||||
>
|
||||
<path
|
||||
d="M-200 0h201v201h-201Z M600 0h201v201h-201Z M-400 600h201v201h-201Z M200 800h201v201h-201Z"
|
||||
strokeWidth={0}
|
||||
/>
|
||||
</svg>
|
||||
<rect
|
||||
width="100%"
|
||||
height="100%"
|
||||
strokeWidth={0}
|
||||
fill="url(#gradle-cta-pattern)"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
{/* Gradient background */}
|
||||
<div
|
||||
className="pointer-events-none absolute inset-x-0 top-10 -z-10 flex transform-gpu justify-center overflow-hidden blur-3xl"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
className="aspect-[1108/632] w-[69.25rem] flex-none bg-gradient-to-r from-blue-500 to-blue-600 opacity-20"
|
||||
style={{
|
||||
clipPath:
|
||||
'polygon(73.6% 51.7%, 91.7% 11.8%, 100% 46.4%, 97.4% 82.2%, 92.5% 84.9%, 75.7% 64%, 55.3% 47.5%, 46.5% 49.4%, 45% 62.9%, 50.3% 87.2%, 21.3% 64.1%, 0.1% 100%, 5.4% 51.1%, 21.4% 63.9%, 58.9% 0.2%, 73.6% 51.7%)',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="mx-auto max-w-2xl text-center">
|
||||
<div className="mb-8 flex justify-center">
|
||||
<GradleIcon className="h-16 w-16 text-slate-900 dark:text-white" />
|
||||
</div>
|
||||
|
||||
<h2 className="text-3xl font-medium tracking-tight text-slate-950 sm:text-5xl dark:text-white">
|
||||
Get Started With Gradle
|
||||
</h2>
|
||||
<p className="mt-8">
|
||||
Check out the documentation for the <code>@nx/gradle</code> plugin to
|
||||
learn more
|
||||
</p>
|
||||
<div className="mt-10 flex items-center justify-center gap-x-6">
|
||||
<Link
|
||||
href="/nx-api/gradle/documents/overview"
|
||||
title="Get started with the tutorial"
|
||||
prefetch={false}
|
||||
className="rounded-md bg-slate-950 px-3.5 py-2.5 text-sm font-semibold text-slate-100 shadow-sm hover:bg-slate-800 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white dark:bg-white dark:text-slate-900 dark:hover:bg-slate-100"
|
||||
>
|
||||
Read the Docs{' '}
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="inline-block transition group-hover:translate-x-1"
|
||||
>
|
||||
→
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
{/* <Link
|
||||
href="/packages/gradle/gradle-plugin"
|
||||
title="Read the documentation"
|
||||
prefetch={false}
|
||||
className="group text-sm font-semibold leading-6 text-slate-950 dark:text-white"
|
||||
>
|
||||
Read documentation{' '}
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="inline-block transition group-hover:translate-x-1"
|
||||
>
|
||||
→
|
||||
</span>
|
||||
</Link> */}
|
||||
</div>
|
||||
|
||||
<h3 className="mt-16 text-3xl font-medium tracking-tight text-slate-950 dark:text-white">
|
||||
Looking for Maven?
|
||||
</h3>
|
||||
<p className="mt-8"> Maven support is coming soon. </p>
|
||||
<p className="mt-4">
|
||||
Join the Nx Community Discord to be notified when it is released.{' '}
|
||||
</p>
|
||||
<div className="mt-10 flex items-center justify-center gap-x-6">
|
||||
<Link
|
||||
href="https://go.nx.dev/community"
|
||||
title="Join the Nx Community Discord"
|
||||
prefetch={false}
|
||||
className="rounded-md bg-slate-950 px-3.5 py-2.5 text-sm font-semibold text-slate-100 shadow-sm hover:bg-slate-800 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white dark:bg-white dark:text-slate-900 dark:hover:bg-slate-100"
|
||||
>
|
||||
Join Us On Discord{' '}
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="inline-block transition group-hover:translate-x-1"
|
||||
>
|
||||
→
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
import { ReactElement } from 'react';
|
||||
import { ButtonLink, SectionHeading } from '@nx/nx-dev/ui-common';
|
||||
|
||||
export function FeatureSections(): ReactElement {
|
||||
return (
|
||||
<section className="feature-sections py-16 sm:py-24">
|
||||
<div className="mx-auto px-6 lg:max-w-7xl">
|
||||
<SectionHeading id="nx-gradle-features" as="h2" variant="title">
|
||||
Supercharge your Java Projects{' '}
|
||||
<span className="rounded-lg bg-gradient-to-r from-cyan-500 to-blue-500 bg-clip-text text-transparent">
|
||||
with Nx
|
||||
</span>
|
||||
</SectionHeading>
|
||||
|
||||
<div className="mt-10 grid grid-cols-1 gap-8 sm:mt-16 md:grid-cols-2 lg:grid-cols-3 ">
|
||||
{/* Affected Section */}
|
||||
<FeatureSection
|
||||
title="Affected"
|
||||
description="Run tasks only on projects affected by your changes, saving time and resources. Nx understands your project's dependency graph and automatically determines which projects need to be rebuilt."
|
||||
imageSrc="/images/enterprise/nx-affected.avif"
|
||||
alt="Nx Affected: Run tasks only on affected projects"
|
||||
tag="Affected"
|
||||
href="/ci/features/affected"
|
||||
/>
|
||||
|
||||
{/* Remote Caching Section */}
|
||||
<FeatureSection
|
||||
title="Never build the same code twice"
|
||||
description="Nx Replay (Remote Caching) ensures tasks are never rebuilt unnecessarily. Share cached results across your team and CI pipelines to dramatically reduce build times and resource usage."
|
||||
imageSrc="/images/enterprise/nx-replay.avif"
|
||||
alt="Nx Replay: Remote caching"
|
||||
tag="Nx Replay"
|
||||
href="/features/cache-task-results"
|
||||
/>
|
||||
|
||||
{/* Distribution Section */}
|
||||
<FeatureSection
|
||||
title="Distribute tasks across machines"
|
||||
description="Nx Agents intelligently distribute your Gradle tasks across multiple machines, significantly reducing build times. Dynamically allocate agents based on PR size to balance speed and cost."
|
||||
imageSrc="/images/enterprise/nx-agents.avif"
|
||||
alt="Nx Agents: Task distribution"
|
||||
tag="Nx Agents"
|
||||
href="/ci/features/distribute-task-execution"
|
||||
/>
|
||||
|
||||
{/* Atomizer Section */}
|
||||
<FeatureSection
|
||||
title="Break tests down, speed execution up"
|
||||
description="Atomizer automatically splits large Gradle test tasks into smaller, atomized units, enabling lightning fast parallel testing. No code changes required - just configure and run."
|
||||
imageSrc="/images/enterprise/nx-atomizer.avif"
|
||||
alt="Nx Atomizer: Split large test tasks"
|
||||
tag="Atomizer"
|
||||
href="/ci/features/split-e2e-tasks"
|
||||
/>
|
||||
|
||||
{/* Flaky Test Retries Section */}
|
||||
<FeatureSection
|
||||
title="Automatically handle flaky tests"
|
||||
description="Nx detects and automatically retries flaky tests, enhancing the reliability of your CI processes and minimizing time spent debugging intermittent failures."
|
||||
imageSrc="/images/enterprise/nx-flaky-tasks-detection.avif"
|
||||
alt="Nx flaky task detection & rerun"
|
||||
tag="Flaky test retries"
|
||||
href="/nx-api/gradle/documents/overview"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
interface FeatureSectionProps {
|
||||
title: string;
|
||||
description: string;
|
||||
imageSrc: string;
|
||||
alt: string;
|
||||
tag: string;
|
||||
href: string;
|
||||
reversed?: boolean;
|
||||
}
|
||||
|
||||
function FeatureSection({
|
||||
title,
|
||||
description,
|
||||
imageSrc,
|
||||
alt,
|
||||
tag,
|
||||
href,
|
||||
}: FeatureSectionProps): ReactElement {
|
||||
return (
|
||||
<div
|
||||
className={`feature-section flex flex-col overflow-hidden rounded-2xl bg-white shadow ring-1 ring-black/5 dark:bg-slate-950 dark:ring-white/10 `}
|
||||
>
|
||||
<div className="h-0 w-full md:h-72">
|
||||
<img
|
||||
alt={alt}
|
||||
src={imageSrc}
|
||||
className="h-full w-full object-cover object-center"
|
||||
/>
|
||||
</div>
|
||||
<div className="relative flex flex-1 flex-col justify-center p-8 lg:p-10">
|
||||
<div>
|
||||
<span className="mb-3 inline-flex items-center rounded-full bg-slate-400/10 px-3 py-1 text-sm font-medium text-slate-600 ring-1 ring-inset ring-slate-400/20 dark:text-slate-300">
|
||||
{tag}
|
||||
</span>
|
||||
<h3 className="mt-2 text-xl font-medium tracking-tight text-slate-950 lg:text-2xl dark:text-white">
|
||||
{title}
|
||||
</h3>
|
||||
<p className="mt-4 text-base/relaxed text-slate-600 dark:text-slate-300">
|
||||
{description}
|
||||
</p>
|
||||
<div className="mt-6">
|
||||
<ButtonLink
|
||||
href={href}
|
||||
title={`Learn more about ${title}`}
|
||||
variant="secondary"
|
||||
size="default"
|
||||
>
|
||||
Learn more
|
||||
<svg
|
||||
className="ml-2 h-4 w-4"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M3 10a.75.75 0 01.75-.75h10.638L10.23 5.29a.75.75 0 111.04-1.08l5.5 5.25a.75.75 0 010 1.08l-5.5 5.25a.75.75 0 11-1.04-1.08l4.158-3.96H3.75A.75.75 0 013 10z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</ButtonLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
import { SectionHeading } from '@nx/nx-dev/ui-common';
|
||||
import { cx } from '@nx/nx-dev/ui-primitives';
|
||||
import React, { ReactElement } from 'react';
|
||||
|
||||
export function Features(): ReactElement {
|
||||
return (
|
||||
<>
|
||||
<SectionHeading as="h2" variant="title">
|
||||
Features
|
||||
</SectionHeading>
|
||||
|
||||
<p className="mt-4 text-lg text-slate-500 dark:text-slate-400">
|
||||
The Nx Plugin for Java integrates seamlessly with your existing Gradle
|
||||
builds, adding powerful capabilities to your workflow.
|
||||
</p>
|
||||
|
||||
<div className="mt-12 grid grid-cols-1 gap-6 sm:gap-8 md:grid-cols-2 lg:grid-cols-3">
|
||||
<FeatureCard
|
||||
title="Task Caching"
|
||||
description="Cache task results locally and remotely, avoiding redundant builds and speeding up your development workflow."
|
||||
href="/features/cache-task-results"
|
||||
icon={
|
||||
<svg
|
||||
className="h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
/>
|
||||
<polyline
|
||||
points="12 6 12 12 16 14"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
}
|
||||
/>
|
||||
<FeatureCard
|
||||
title="Distributed Task Execution"
|
||||
description="Run your project tasks across multiple machines, dramatically reducing build times for large repositories."
|
||||
href="/ci/features/distribute-task-execution"
|
||||
icon={
|
||||
<svg
|
||||
className="h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect
|
||||
x="2"
|
||||
y="7"
|
||||
width="20"
|
||||
height="14"
|
||||
rx="2"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
/>
|
||||
<path
|
||||
d="M6 7V4.5C6 3.67157 6.67157 3 7.5 3H16.5C17.3284 3 18 3.67157 18 4.5V7"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
/>
|
||||
<path
|
||||
d="M12 14L12 10"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M9 12L15 12"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
</svg>
|
||||
}
|
||||
/>
|
||||
<FeatureCard
|
||||
title="Affected Targets"
|
||||
description="Run tasks only on projects affected by your changes, saving time and computing resources."
|
||||
href="/ci/features/affected"
|
||||
icon={
|
||||
<svg
|
||||
className="h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M22 11.08V12C21.9988 14.1564 21.3005 16.2547 20.0093 17.9818C18.7182 19.709 16.9033 20.9725 14.8354 21.5839C12.7674 22.1953 10.5573 22.1219 8.53447 21.3746C6.51168 20.6273 4.78465 19.2461 3.61096 17.4371C2.43727 15.628 1.87979 13.4881 2.02168 11.3363C2.16356 9.18455 2.99721 7.13631 4.39828 5.49706C5.79935 3.85781 7.69279 2.71537 9.79619 2.24013C11.8996 1.7649 14.1003 1.98232 16.07 2.85999"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M22 4L12 14L9 11"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
}
|
||||
/>
|
||||
<FeatureCard
|
||||
title="Project Graph"
|
||||
description="Nx automatically infers your project graph from project's configuration, providing visualization and dependency analysis."
|
||||
href="/features/explore-graph"
|
||||
icon={
|
||||
<svg
|
||||
className="h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle
|
||||
cx="5"
|
||||
cy="5"
|
||||
r="2"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
/>
|
||||
<circle
|
||||
cx="19"
|
||||
cy="5"
|
||||
r="2"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
/>
|
||||
<circle
|
||||
cx="5"
|
||||
cy="19"
|
||||
r="2"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
/>
|
||||
<circle
|
||||
cx="19"
|
||||
cy="19"
|
||||
r="2"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
/>
|
||||
<path d="M7 5H17" stroke="currentColor" strokeWidth="2" />
|
||||
<path d="M5 7V17" stroke="currentColor" strokeWidth="2" />
|
||||
<path d="M7 19H17" stroke="currentColor" strokeWidth="2" />
|
||||
<path d="M19 7V17" stroke="currentColor" strokeWidth="2" />
|
||||
</svg>
|
||||
}
|
||||
/>
|
||||
<FeatureCard
|
||||
title="Split E2E Tests"
|
||||
description="Automatically split your E2E tests for faster parallel execution in CI environments with Atomizer."
|
||||
href="/ci/features/split-e2e-tasks"
|
||||
icon={
|
||||
<svg
|
||||
className="h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M14 3V7C14 7.55228 14.4477 8 15 8H19"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M17 21H7C5.89543 21 5 20.1046 5 19V5C5 3.89543 5.89543 3 7 3H14L19 8V19C19 20.1046 18.1046 21 17 21Z"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M10 12L8 14L10 16"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M14 12L16 14L14 16"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
}
|
||||
/>
|
||||
<FeatureCard
|
||||
title="Zero Configuration"
|
||||
description="Add Nx to your project in minutes with no changes to your existing setup required."
|
||||
href="/nx-api/gradle/documents/overview"
|
||||
icon={
|
||||
<svg
|
||||
className="h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 2L2 7L12 12L22 7L12 2Z"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M2 17L12 22L22 17"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M2 12L12 17L22 12"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
interface FeatureCardProps {
|
||||
title: string;
|
||||
description: string;
|
||||
icon: JSX.Element;
|
||||
href?: string;
|
||||
}
|
||||
|
||||
function FeatureCard({
|
||||
title,
|
||||
description,
|
||||
icon,
|
||||
href = '#',
|
||||
}: FeatureCardProps): ReactElement {
|
||||
return (
|
||||
<a href={href} className="block h-full transform-gpu">
|
||||
<div
|
||||
className={cx(
|
||||
'group relative h-full w-full overflow-hidden rounded-lg border border-slate-200 bg-white p-6',
|
||||
'dark:border-slate-800 dark:bg-slate-900 dark:hover:border-blue-900 dark:hover:shadow-blue-900/20',
|
||||
'before:absolute before:inset-0 before:z-0 before:bg-gradient-to-br before:from-blue-50 before:to-transparent before:opacity-0 before:transition-opacity',
|
||||
'transition-all duration-300 ease-out',
|
||||
'hover:-translate-y-2 hover:border-blue-200 hover:shadow-xl hover:shadow-blue-100/50',
|
||||
'hover:before:opacity-100 dark:before:from-blue-950'
|
||||
)}
|
||||
>
|
||||
<div className="relative z-10">
|
||||
<div className="mb-4 flex h-12 w-12 items-center justify-center rounded-lg bg-blue-50 text-blue-600 transition-transform duration-300 group-hover:scale-110 dark:bg-slate-800 dark:text-blue-400 dark:group-hover:bg-blue-900">
|
||||
{icon}
|
||||
</div>
|
||||
<h3 className="mb-2 text-lg font-medium">{title}</h3>
|
||||
<p className="text-slate-500 dark:text-slate-400">{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
/* eslint-disable @nx/enforce-module-boundaries */
|
||||
import {
|
||||
ButtonLink,
|
||||
SectionHeading,
|
||||
Strong,
|
||||
SectionDescription,
|
||||
} from '@nx/nx-dev/ui-common';
|
||||
import { JavaIcon } from '@nx/nx-dev/ui-icons';
|
||||
/* eslint-enable @nx/enforce-module-boundaries */
|
||||
import { ReactElement } from 'react';
|
||||
|
||||
export function Hero(): ReactElement {
|
||||
return (
|
||||
<div className="mx-auto max-w-7xl px-6 lg:px-8">
|
||||
<div className="mx-auto max-w-2xl text-center">
|
||||
{/* Logo displayed above the title */}
|
||||
<div className="mb-6 flex justify-center">
|
||||
<div className="rounded-full bg-slate-100 p-4 dark:bg-slate-800">
|
||||
<JavaIcon className="h-20 w-20" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SectionHeading as="h1" variant="title" className="mt-6">
|
||||
Supercharge Your Java Projects
|
||||
</SectionHeading>
|
||||
<SectionDescription as="h2" className="mt-6">
|
||||
Get distributed tasks, intelligent caching, and target affected
|
||||
packages to optimize your build process and CI pipeline. Support for{' '}
|
||||
<Strong>Gradle</Strong> and <Strong>Maven</Strong> (coming soon)
|
||||
</SectionDescription>
|
||||
|
||||
<div className="mt-10 flex flex-col items-center justify-center gap-6 sm:flex-row">
|
||||
<ButtonLink
|
||||
href="https://nx.dev/getting-started/tutorials/gradle-tutorial"
|
||||
variant="primary"
|
||||
size="default"
|
||||
title="Get Started"
|
||||
>
|
||||
Get Started with Nx for Java
|
||||
</ButtonLink>
|
||||
|
||||
<a
|
||||
href="https://github.com/nrwl/nx-recipes/tree/main/gradle"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="group inline-flex items-center text-sm font-semibold leading-6 text-slate-800 dark:text-white"
|
||||
>
|
||||
View Example Project{' '}
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="ml-1 inline-block transition-transform group-hover:translate-x-1"
|
||||
>
|
||||
→
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function GettingStarted(): ReactElement {
|
||||
return (
|
||||
<section className="relative py-16 sm:py-24">
|
||||
<div className="absolute inset-0 bg-white dark:bg-slate-900"></div>
|
||||
<div className="relative mx-auto max-w-7xl px-6 lg:px-8">
|
||||
<div className="mx-auto max-w-2xl text-center">
|
||||
<h2 className="text-3xl font-bold tracking-tight text-slate-900 sm:text-4xl dark:text-white">
|
||||
Add Nx To An Existing Project
|
||||
</h2>
|
||||
<p className="mt-8">
|
||||
Currently Nx only supports Gradle. Support for Maven will come soon
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-12 grid grid-cols-1 gap-6 sm:gap-8 md:grid-cols-2 ">
|
||||
<GetStartedCard
|
||||
title="Install Nx"
|
||||
command="brew install nx"
|
||||
description="Get the <code>nx</code> executable from homebrew"
|
||||
/>
|
||||
<GetStartedCard
|
||||
title="Add Nx To Your Project"
|
||||
command="nx init"
|
||||
description="Select <code>@nx/gradle</code> when prompted."
|
||||
/>
|
||||
|
||||
<GetStartedCard
|
||||
title="Run Tasks With Nx"
|
||||
command="nx build <project>"
|
||||
description="Nx will automatically infers tasks from your projects."
|
||||
/>
|
||||
|
||||
<GetStartedCard
|
||||
title="Run Affected Tasks"
|
||||
command="nx affected -t build"
|
||||
description="Nx adds caching, distribution, and affected commands without changing your setup."
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* GitHub example project link */}
|
||||
<div className="mt-12 flex justify-center">
|
||||
<a
|
||||
href="https://github.com/nrwl/nx-recipes/tree/main/gradle"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-2 rounded-lg border border-slate-200 bg-white px-6 py-3 font-medium text-slate-900 shadow-sm transition-all hover:-translate-y-1 hover:border-blue-400 hover:shadow-md dark:border-slate-700 dark:bg-slate-800 dark:text-white dark:hover:border-blue-500"
|
||||
>
|
||||
<svg
|
||||
className="h-5 w-5"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 2C6.477 2 2 6.477 2 12C2 16.418 4.865 20.166 8.839 21.489C9.339 21.581 9.521 21.284 9.521 21.029C9.521 20.799 9.513 20.065 9.508 19.27C6.726 19.891 6.139 17.962 6.139 17.962C5.685 16.812 5.029 16.516 5.029 16.516C4.121 15.89 5.098 15.902 5.098 15.902C6.101 15.97 6.629 16.925 6.629 16.925C7.521 18.43 8.97 18.008 9.539 17.762C9.631 17.097 9.889 16.676 10.175 16.419C7.954 16.159 5.62 15.304 5.62 11.453C5.62 10.355 6.009 9.457 6.649 8.758C6.546 8.514 6.203 7.543 6.746 6.161C6.746 6.161 7.585 5.902 9.497 7.203C10.295 6.988 11.15 6.88 12 6.876C12.85 6.88 13.705 6.988 14.505 7.203C16.415 5.902 17.253 6.161 17.253 6.161C17.798 7.543 17.455 8.514 17.352 8.758C17.994 9.457 18.38 10.355 18.38 11.453C18.38 15.313 16.042 16.156 13.815 16.411C14.174 16.726 14.495 17.347 14.495 18.297C14.495 19.648 14.483 20.709 14.483 21.029C14.483 21.287 14.661 21.586 15.171 21.487C19.138 20.161 22 16.417 22 12C22 6.477 17.523 2 12 2Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
View Example Project on GitHub
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
interface GetStartedCardProps {
|
||||
title: string;
|
||||
command: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
function GetStartedCard({
|
||||
title,
|
||||
command,
|
||||
description,
|
||||
}: GetStartedCardProps): ReactElement {
|
||||
return (
|
||||
<div className="h-full rounded-xl border border-slate-200 bg-white p-6 shadow-sm transition-all hover:shadow-md dark:border-slate-700 dark:bg-slate-800">
|
||||
<h3 className="text-lg font-medium text-slate-900 dark:text-white">
|
||||
{title}
|
||||
</h3>
|
||||
<div className="mt-3 overflow-hidden rounded-md bg-slate-800 px-4 py-3 text-sm text-white dark:bg-slate-950">
|
||||
<code style={{ whiteSpace: 'pre-line' }}>{command}</code>
|
||||
</div>
|
||||
<p
|
||||
className="mt-3 text-sm text-slate-600 dark:text-slate-300"
|
||||
dangerouslySetInnerHTML={{ __html: description }}
|
||||
></p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export * from './hero';
|
||||
export * from './features';
|
||||
export * from './resources';
|
||||
export * from './call-to-action';
|
||||
export * from './feature-sections';
|
||||
@@ -0,0 +1,71 @@
|
||||
import { ButtonLink, SectionHeading, TextLink } from '@nx/nx-dev/ui-common';
|
||||
import { cx } from '@nx/nx-dev/ui-primitives';
|
||||
import React, { ReactElement } from 'react';
|
||||
|
||||
export function Resources(): ReactElement {
|
||||
return (
|
||||
<>
|
||||
<SectionHeading as="h2" variant="title">
|
||||
Resources
|
||||
</SectionHeading>
|
||||
<p className="mt-4 text-lg text-slate-500 dark:text-slate-400">
|
||||
Learn more about using Nx with Java projects with these resources.
|
||||
</p>
|
||||
|
||||
<div className="mt-12 grid grid-cols-1 gap-6 sm:gap-8 md:grid-cols-3">
|
||||
<ResourceCard
|
||||
title="Gradle Tutorial"
|
||||
description="Follow this step-by-step tutorial to add Nx to an existing Gradle project."
|
||||
linkText="View tutorial"
|
||||
href="/tutorials/gradle"
|
||||
/>
|
||||
|
||||
<ResourceCard
|
||||
title="Gradle Plugin Documentation"
|
||||
description="Learn all the details about the Nx Gradle plugin configuration and usage."
|
||||
linkText="View docs"
|
||||
href="/packages/gradle/gradle-plugin"
|
||||
/>
|
||||
|
||||
<ResourceCard
|
||||
title="CI with Nx"
|
||||
description="Learn how to configure Continuous Integration with Nx for faster builds."
|
||||
linkText="View CI docs"
|
||||
href="/ci/intro/ci-with-nx"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
interface ResourceCardProps {
|
||||
title: string;
|
||||
description: string;
|
||||
linkText: string;
|
||||
href: string;
|
||||
}
|
||||
|
||||
function ResourceCard({
|
||||
title,
|
||||
description,
|
||||
linkText,
|
||||
href,
|
||||
}: ResourceCardProps): ReactElement {
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
'flex h-full flex-col justify-between rounded-lg border border-slate-200 bg-white p-6 shadow-sm transition-all hover:shadow-md dark:border-slate-800 dark:bg-slate-900'
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
<h3 className="mb-2 text-lg font-medium">{title}</h3>
|
||||
<p className="text-slate-500 dark:text-slate-400">{description}</p>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<ButtonLink href={href} size="small" variant="primary" title={linkText}>
|
||||
{linkText}
|
||||
</ButtonLink>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": false,
|
||||
"esModuleInterop": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
],
|
||||
"extends": "../../tsconfig.base.json"
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"types": [
|
||||
"node",
|
||||
"@nx/react/typings/cssmodule.d.ts",
|
||||
"@nx/react/typings/image.d.ts"
|
||||
],
|
||||
"lib": ["DOM", "es2019"]
|
||||
},
|
||||
"exclude": [
|
||||
"jest.config.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.spec.tsx",
|
||||
"src/**/*.test.tsx",
|
||||
"src/**/*.spec.js",
|
||||
"src/**/*.test.js",
|
||||
"src/**/*.spec.jsx",
|
||||
"src/**/*.test.jsx"
|
||||
],
|
||||
"include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"]
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"include": [
|
||||
"jest.config.ts",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.test.tsx",
|
||||
"src/**/*.spec.tsx",
|
||||
"src/**/*.test.js",
|
||||
"src/**/*.spec.js",
|
||||
"src/**/*.test.jsx",
|
||||
"src/**/*.spec.jsx",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -105,7 +105,7 @@ export function PlansDisplay(): ReactElement {
|
||||
</Link>
|
||||
</span>
|
||||
</li>
|
||||
<li className="flex items-start justify-start gap-x-2 py-2.5">
|
||||
<li className="flex items-center justify-start gap-x-2 py-2.5">
|
||||
<CheckCircleIcon
|
||||
aria-hidden="true"
|
||||
className="h-6 w-5 flex-none text-blue-600 dark:text-sky-500"
|
||||
@@ -178,28 +178,28 @@ export function PlansDisplay(): ReactElement {
|
||||
<li className="py-2.5">
|
||||
<span className="font-medium">Included for free</span>
|
||||
</li>
|
||||
<li className="flex items-start justify-start gap-x-2 py-2.5">
|
||||
<li className="flex items-center justify-start gap-x-2 py-2.5">
|
||||
<CheckCircleIcon
|
||||
aria-hidden="true"
|
||||
className="h-6 w-5 flex-none text-blue-600 dark:text-sky-500"
|
||||
/>
|
||||
<span>5 active contributors¹</span>
|
||||
</li>
|
||||
<li className="flex items-start justify-start gap-x-2 py-2.5">
|
||||
<li className="flex items-center justify-start gap-x-2 py-2.5">
|
||||
<CheckCircleIcon
|
||||
aria-hidden="true"
|
||||
className="h-6 w-5 flex-none text-blue-600 dark:text-sky-500"
|
||||
/>
|
||||
<span>50,000 monthly credits</span>
|
||||
</li>
|
||||
<li className="flex items-start justify-start gap-x-2 py-2.5">
|
||||
<li className="flex items-center justify-start gap-x-2 py-2.5">
|
||||
<CheckCircleIcon
|
||||
aria-hidden="true"
|
||||
className="h-6 w-5 flex-none text-blue-600 dark:text-sky-500"
|
||||
/>
|
||||
<span>10 concurrent CI connections</span>
|
||||
</li>
|
||||
<li className="flex items-start justify-start gap-x-2 py-2.5">
|
||||
<li className="flex items-center justify-start gap-x-2 py-2.5">
|
||||
<CheckCircleIcon
|
||||
aria-hidden="true"
|
||||
className="h-6 w-5 flex-none text-blue-600 dark:text-sky-500"
|
||||
@@ -220,7 +220,7 @@ export function PlansDisplay(): ReactElement {
|
||||
AI integrations
|
||||
</Link>
|
||||
</li>
|
||||
<li className="flex items-start justify-start gap-x-2 py-2.5">
|
||||
<li className="flex items-center justify-start gap-x-2 py-2.5">
|
||||
<CheckCircleIcon
|
||||
aria-hidden="true"
|
||||
className="h-6 w-5 flex-none text-blue-600 dark:text-sky-500"
|
||||
@@ -230,21 +230,21 @@ export function PlansDisplay(): ReactElement {
|
||||
<li className="py-2.5">
|
||||
<span className="font-medium">Add-ons</span>
|
||||
</li>
|
||||
<li className="flex items-start justify-start gap-x-2 py-2.5">
|
||||
<li className="flex items-center justify-start gap-x-2 py-2.5">
|
||||
<PlusIcon
|
||||
aria-hidden="true"
|
||||
className="h-6 w-5 flex-none text-blue-600 dark:text-sky-500"
|
||||
/>
|
||||
<span>$19 per active contributor¹ / month</span>
|
||||
</li>
|
||||
<li className="flex items-start justify-start gap-x-2 py-2.5">
|
||||
<li className="flex items-center justify-start gap-x-2 py-2.5">
|
||||
<PlusIcon
|
||||
aria-hidden="true"
|
||||
className="h-6 w-5 flex-none text-blue-600 dark:text-sky-500"
|
||||
/>
|
||||
<span>$5.50 per 10,000 credits</span>
|
||||
</li>
|
||||
<li className="flex items-start justify-start gap-x-2 py-2.5">
|
||||
<li className="flex items-center justify-start gap-x-2 py-2.5">
|
||||
<PlusIcon
|
||||
aria-hidden="true"
|
||||
className="h-6 w-5 flex-none text-blue-600 dark:text-sky-500"
|
||||
@@ -297,21 +297,21 @@ export function PlansDisplay(): ReactElement {
|
||||
<li className="py-2.5">
|
||||
<span className="font-medium">Includes</span>
|
||||
</li>
|
||||
<li className="flex items-start justify-start gap-x-2 py-2.5">
|
||||
<li className="flex items-center justify-start gap-x-2 py-2.5">
|
||||
<CheckCircleIcon
|
||||
aria-hidden="true"
|
||||
className="h-6 w-5 flex-none text-blue-600 dark:text-sky-500"
|
||||
/>
|
||||
<span>Volume discounts on credits available</span>
|
||||
</li>
|
||||
<li className="flex items-start justify-start gap-x-2 py-2.5">
|
||||
<li className="flex items-center justify-start gap-x-2 py-2.5">
|
||||
<CheckCircleIcon
|
||||
aria-hidden="true"
|
||||
className="h-6 w-5 flex-none text-blue-600 dark:text-sky-500"
|
||||
/>
|
||||
<span>White glove onboarding</span>
|
||||
</li>
|
||||
<li className="flex items-start justify-start gap-x-2 py-2.5">
|
||||
<li className="flex items-center justify-start gap-x-2 py-2.5">
|
||||
<CheckCircleIcon
|
||||
aria-hidden="true"
|
||||
className="h-6 w-5 flex-none text-blue-600 dark:text-sky-500"
|
||||
@@ -335,7 +335,7 @@ export function PlansDisplay(): ReactElement {
|
||||
: a suite of premium extensions for the Nx CLI
|
||||
</span>
|
||||
</li>
|
||||
<li className="flex items-start justify-start gap-x-2 py-2.5">
|
||||
<li className="flex items-center justify-start gap-x-2 py-2.5">
|
||||
<CheckCircleIcon
|
||||
aria-hidden="true"
|
||||
className="h-6 w-5 flex-none text-blue-600 dark:text-sky-500"
|
||||
@@ -344,7 +344,7 @@ export function PlansDisplay(): ReactElement {
|
||||
Work hand-in-hand with the Nx team for continual improvement
|
||||
</span>
|
||||
</li>
|
||||
<li className="flex items-start justify-start gap-x-2 py-2.5">
|
||||
<li className="flex items-center justify-start gap-x-2 py-2.5">
|
||||
<CheckCircleIcon
|
||||
aria-hidden="true"
|
||||
className="h-6 w-5 flex-none text-blue-600 dark:text-sky-500"
|
||||
@@ -354,14 +354,14 @@ export function PlansDisplay(): ReactElement {
|
||||
self-contained, on-prem
|
||||
</span>
|
||||
</li>
|
||||
<li className="flex items-start justify-start gap-x-2 py-2.5">
|
||||
<li className="flex items-center justify-start gap-x-2 py-2.5">
|
||||
<CheckCircleIcon
|
||||
aria-hidden="true"
|
||||
className="h-6 w-5 flex-none text-blue-600 dark:text-sky-500"
|
||||
/>
|
||||
<span>SSO / SAML Login</span>
|
||||
</li>
|
||||
<li className="flex items-start justify-start gap-x-2 py-2.5">
|
||||
<li className="flex items-center justify-start gap-x-2 py-2.5">
|
||||
<CheckCircleIcon
|
||||
aria-hidden="true"
|
||||
className="h-6 w-5 flex-none text-blue-600 dark:text-sky-500"
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
export const iconsMap: Record<string, string> = {
|
||||
angular: '/images/icons/angular.svg',
|
||||
'angular-rsbuild': '/images/icons/rsbuild.svg',
|
||||
'angular-rspack': '/images/icons/rspack.svg',
|
||||
cli: '/images/icons/nx.svg',
|
||||
cypress: '/images/icons/cypress.svg',
|
||||
detox: '/images/icons/react.svg',
|
||||
@@ -32,6 +34,7 @@ export const iconsMap: Record<string, string> = {
|
||||
'react-native': '/images/icons/react.svg',
|
||||
remix: '/images/icons/remix.svg',
|
||||
rollup: '/images/icons/rollup.svg',
|
||||
rsbuild: '/images/icons/rsbuild.svg',
|
||||
rspack: '/images/icons/rspack.svg',
|
||||
storybook: '/images/icons/storybook.svg',
|
||||
vite: '/images/icons/vite.svg',
|
||||
|
||||
@@ -189,7 +189,7 @@
|
||||
"esbuild": "0.25.0",
|
||||
"eslint": "8.57.0",
|
||||
"eslint-config-next": "14.2.16",
|
||||
"eslint-config-prettier": "9.1.0",
|
||||
"eslint-config-prettier": "^10.0.0",
|
||||
"eslint-plugin-cypress": "2.14.0",
|
||||
"eslint-plugin-import": "2.31.0",
|
||||
"eslint-plugin-jsx-a11y": "6.10.1",
|
||||
@@ -262,6 +262,7 @@
|
||||
"parse-markdown-links": "^1.0.4",
|
||||
"parse5": "4.0.0",
|
||||
"picocolors": "^1.1.0",
|
||||
"picomatch": "4.0.2",
|
||||
"piscina": "^4.4.0",
|
||||
"postcss": "8.4.38",
|
||||
"postcss-import": "~14.1.0",
|
||||
|
||||
@@ -1341,6 +1341,10 @@
|
||||
"version": "~19.0.0",
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"@angular/google-maps": {
|
||||
"version": "~19.0.0",
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"ng-packagr": {
|
||||
"version": "~19.0.0",
|
||||
"alwaysAddToPackageJson": false
|
||||
@@ -1537,6 +1541,10 @@
|
||||
"version": "~19.1.0",
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"@angular/google-maps": {
|
||||
"version": "~19.1.0",
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"ng-packagr": {
|
||||
"version": "~19.1.0",
|
||||
"alwaysAddToPackageJson": false
|
||||
@@ -1598,6 +1606,10 @@
|
||||
"version": "~19.2.1",
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"@angular/google-maps": {
|
||||
"version": "~19.2.1",
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"ng-packagr": {
|
||||
"version": "~19.2.0",
|
||||
"alwaysAddToPackageJson": false
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"@typescript-eslint/",
|
||||
"picocolors",
|
||||
"ignore",
|
||||
"minimatch",
|
||||
"picomatch",
|
||||
"rxjs-for-await",
|
||||
"webpack-merge",
|
||||
"ts-node",
|
||||
|
||||
@@ -52,8 +52,8 @@
|
||||
"@typescript-eslint/type-utils": "^8.0.0",
|
||||
"enquirer": "~2.3.6",
|
||||
"picocolors": "^1.1.0",
|
||||
"picomatch": "4.0.2",
|
||||
"magic-string": "~0.30.2",
|
||||
"minimatch": "9.0.3",
|
||||
"semver": "^7.5.3",
|
||||
"tslib": "^2.3.0",
|
||||
"webpack-merge": "^5.8.0",
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { ExecutorContext } from '@nx/devkit';
|
||||
import { joinPathFragments, parseTargetString, runExecutor } from '@nx/devkit';
|
||||
import {
|
||||
calculateProjectBuildableDependencies,
|
||||
checkDependentProjectsHaveBeenBuilt,
|
||||
createTmpTsConfig,
|
||||
} from '@nx/js/src/utils/buildable-libs-utils';
|
||||
import type { DelegateBuildExecutorSchema } from './schema';
|
||||
@@ -27,17 +26,6 @@ export async function* delegateBuildExecutor(
|
||||
dependencies
|
||||
);
|
||||
|
||||
if (
|
||||
!checkDependentProjectsHaveBeenBuilt(
|
||||
context.root,
|
||||
context.projectName,
|
||||
context.targetName,
|
||||
dependencies
|
||||
)
|
||||
) {
|
||||
return { success: false };
|
||||
}
|
||||
|
||||
const { buildTarget, ...targetOptions } = options;
|
||||
const delegateTarget = parseTargetString(buildTarget, context);
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { ExecutorContext } from '@nx/devkit';
|
||||
import { eachValueFrom } from '@nx/devkit/src/utils/rxjs-for-await';
|
||||
import {
|
||||
calculateProjectBuildableDependencies,
|
||||
checkDependentProjectsHaveBeenBuilt,
|
||||
createTmpTsConfig,
|
||||
type DependentBuildableProjectNode,
|
||||
} from '@nx/js/src/utils/buildable-libs-utils';
|
||||
@@ -65,25 +64,14 @@ export function createLibraryExecutor(
|
||||
);
|
||||
}
|
||||
|
||||
const { target, dependencies, topLevelDependencies } =
|
||||
calculateProjectBuildableDependencies(
|
||||
context.taskGraph,
|
||||
context.projectGraph,
|
||||
context.root,
|
||||
context.projectName,
|
||||
context.targetName,
|
||||
context.configurationName
|
||||
);
|
||||
if (
|
||||
!checkDependentProjectsHaveBeenBuilt(
|
||||
context.root,
|
||||
context.projectName,
|
||||
context.targetName,
|
||||
dependencies
|
||||
)
|
||||
) {
|
||||
return Promise.resolve({ success: false });
|
||||
}
|
||||
const { dependencies } = calculateProjectBuildableDependencies(
|
||||
context.taskGraph,
|
||||
context.projectGraph,
|
||||
context.root,
|
||||
context.projectName,
|
||||
context.targetName,
|
||||
context.configurationName
|
||||
);
|
||||
|
||||
if (options.watch) {
|
||||
return yield* eachValueFrom(
|
||||
|
||||
@@ -484,4 +484,164 @@ describe('convert-to-rspack', () => {
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should configure ts-node in the tsconfig.json file', async () => {
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
addProjectConfiguration(tree, 'app', {
|
||||
root: 'apps/app',
|
||||
sourceRoot: 'apps/app/src',
|
||||
projectType: 'application',
|
||||
targets: {
|
||||
build: {
|
||||
executor: '@angular-devkit/build-angular:browser',
|
||||
options: {
|
||||
outputPath: 'dist/apps/app',
|
||||
index: 'apps/app/src/index.html',
|
||||
main: 'apps/app/src/main.ts',
|
||||
polyfills: ['tslib'], // zone.js is not in nx repo's node_modules so simulating it with a package that is
|
||||
tsConfig: 'apps/app/tsconfig.app.json',
|
||||
assets: [
|
||||
'apps/app/src/favicon.ico',
|
||||
'apps/app/src/assets',
|
||||
{ input: 'apps/app/public', glob: '**/*' },
|
||||
],
|
||||
styles: ['apps/app/src/styles.scss'],
|
||||
scripts: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
writeJson(tree, 'apps/app/tsconfig.json', {});
|
||||
updateJson(tree, 'package.json', (json) => {
|
||||
json.scripts ??= {};
|
||||
json.scripts.build = 'nx build';
|
||||
return json;
|
||||
});
|
||||
|
||||
await convertToRspack(tree, { project: 'app', skipFormat: true });
|
||||
|
||||
expect(tree.read('apps/app/tsconfig.json', 'utf-8')).toMatchInlineSnapshot(`
|
||||
"{
|
||||
"ts-node": {
|
||||
"compilerOptions": {
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "Node10"
|
||||
}
|
||||
}
|
||||
}
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should configure ts-node in the tsconfig.json file to unset "customConditions" when it is defined in the root tsconfig.json file', async () => {
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
updateJson(tree, 'tsconfig.base.json', (json) => {
|
||||
json.compilerOptions ??= {};
|
||||
json.compilerOptions.customConditions = ['development'];
|
||||
return json;
|
||||
});
|
||||
addProjectConfiguration(tree, 'app', {
|
||||
root: 'apps/app',
|
||||
sourceRoot: 'apps/app/src',
|
||||
projectType: 'application',
|
||||
targets: {
|
||||
build: {
|
||||
executor: '@angular-devkit/build-angular:browser',
|
||||
options: {
|
||||
outputPath: 'dist/apps/app',
|
||||
index: 'apps/app/src/index.html',
|
||||
main: 'apps/app/src/main.ts',
|
||||
polyfills: ['tslib'], // zone.js is not in nx repo's node_modules so simulating it with a package that is
|
||||
tsConfig: 'apps/app/tsconfig.app.json',
|
||||
assets: [
|
||||
'apps/app/src/favicon.ico',
|
||||
'apps/app/src/assets',
|
||||
{ input: 'apps/app/public', glob: '**/*' },
|
||||
],
|
||||
styles: ['apps/app/src/styles.scss'],
|
||||
scripts: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
writeJson(tree, 'apps/app/tsconfig.json', {});
|
||||
updateJson(tree, 'package.json', (json) => {
|
||||
json.scripts ??= {};
|
||||
json.scripts.build = 'nx build';
|
||||
return json;
|
||||
});
|
||||
|
||||
await convertToRspack(tree, { project: 'app', skipFormat: true });
|
||||
|
||||
expect(tree.read('apps/app/tsconfig.json', 'utf-8')).toMatchInlineSnapshot(`
|
||||
"{
|
||||
"ts-node": {
|
||||
"compilerOptions": {
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "Node10",
|
||||
"customConditions": null
|
||||
}
|
||||
}
|
||||
}
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should configure ts-node in the tsconfig.json file to unset "customConditions" when it is defined in the project tsconfig.json file', async () => {
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
addProjectConfiguration(tree, 'app', {
|
||||
root: 'apps/app',
|
||||
sourceRoot: 'apps/app/src',
|
||||
projectType: 'application',
|
||||
targets: {
|
||||
build: {
|
||||
executor: '@angular-devkit/build-angular:browser',
|
||||
options: {
|
||||
outputPath: 'dist/apps/app',
|
||||
index: 'apps/app/src/index.html',
|
||||
main: 'apps/app/src/main.ts',
|
||||
polyfills: ['tslib'], // zone.js is not in nx repo's node_modules so simulating it with a package that is
|
||||
tsConfig: 'apps/app/tsconfig.app.json',
|
||||
assets: [
|
||||
'apps/app/src/favicon.ico',
|
||||
'apps/app/src/assets',
|
||||
{ input: 'apps/app/public', glob: '**/*' },
|
||||
],
|
||||
styles: ['apps/app/src/styles.scss'],
|
||||
scripts: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
writeJson(tree, 'apps/app/tsconfig.json', {
|
||||
compilerOptions: {
|
||||
customConditions: ['development'],
|
||||
},
|
||||
});
|
||||
updateJson(tree, 'package.json', (json) => {
|
||||
json.scripts ??= {};
|
||||
json.scripts.build = 'nx build';
|
||||
return json;
|
||||
});
|
||||
|
||||
await convertToRspack(tree, { project: 'app', skipFormat: true });
|
||||
|
||||
expect(tree.read('apps/app/tsconfig.json', 'utf-8')).toMatchInlineSnapshot(`
|
||||
"{
|
||||
"compilerOptions": {
|
||||
"customConditions": [
|
||||
"development"
|
||||
]
|
||||
},
|
||||
"ts-node": {
|
||||
"compilerOptions": {
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "Node10",
|
||||
"customConditions": null
|
||||
}
|
||||
}
|
||||
}
|
||||
"
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,12 +1,26 @@
|
||||
import { joinPathFragments, readJson, Tree, writeJson } from '@nx/devkit';
|
||||
import { getRootTsConfigFileName } from '@nx/js';
|
||||
|
||||
export function updateTsconfig(tree: Tree, projectRoot: string) {
|
||||
const tsconfigPath = joinPathFragments(projectRoot, 'tsconfig.json');
|
||||
const tsconfig = readJson(tree, tsconfigPath);
|
||||
tsconfig['ts-node'] = {
|
||||
compilerOptions: {
|
||||
module: 'CommonJS',
|
||||
},
|
||||
};
|
||||
|
||||
tsconfig['ts-node'] ??= {};
|
||||
tsconfig['ts-node'].compilerOptions ??= {};
|
||||
tsconfig['ts-node'].compilerOptions.module = 'CommonJS';
|
||||
tsconfig['ts-node'].compilerOptions.moduleResolution = 'Node10';
|
||||
|
||||
if (tsconfig.compilerOptions?.customConditions) {
|
||||
tsconfig['ts-node'].compilerOptions.customConditions = null;
|
||||
} else {
|
||||
const rootTsconfigFile = getRootTsConfigFileName(tree);
|
||||
if (rootTsconfigFile) {
|
||||
const rootTsconfigJson = readJson(tree, rootTsconfigFile);
|
||||
if (rootTsconfigJson.compilerOptions?.customConditions) {
|
||||
tsconfig['ts-node'].compilerOptions.customConditions = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writeJson(tree, tsconfigPath, tsconfig);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
import { getProjectEntryPoints } from '../utils/storybook-ast/entry-point';
|
||||
import { getModuleFilePaths } from '../utils/storybook-ast/module-info';
|
||||
import type { StoriesGeneratorOptions } from './schema';
|
||||
import { minimatch } from 'minimatch';
|
||||
import picomatch = require('picomatch');
|
||||
import { nxVersion } from '../../utils/versions';
|
||||
|
||||
export async function angularStoriesGenerator(
|
||||
@@ -37,13 +37,12 @@ export async function angularStoriesGenerator(
|
||||
const componentInfos = componentsInfo.filter(
|
||||
(f) =>
|
||||
!options.ignorePaths?.some((pattern) => {
|
||||
const shouldIgnorePath = minimatch(
|
||||
const shouldIgnorePath = picomatch(pattern)(
|
||||
joinPathFragments(
|
||||
f.moduleFolderPath,
|
||||
f.path,
|
||||
`${f.componentFileName}.ts`
|
||||
),
|
||||
pattern
|
||||
)
|
||||
);
|
||||
return shouldIgnorePath;
|
||||
})
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { type Tree, formatFiles, visitNotIgnoredFiles } from '@nx/devkit';
|
||||
import { forEachExecutorOptions } from '@nx/devkit/src/generators/executor-options-utils';
|
||||
import { minimatch } from 'minimatch';
|
||||
import picomatch = require('picomatch');
|
||||
import { tsquery } from '@phenomnomnominal/tsquery';
|
||||
|
||||
export default async function (tree: Tree) {
|
||||
visitNotIgnoredFiles(tree, '', (path) => {
|
||||
const webpackConfigGlob = '**/webpack*.config*.{js,ts,mjs,cjs}';
|
||||
const result = minimatch(path, webpackConfigGlob);
|
||||
const result = picomatch(webpackConfigGlob)(path);
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ interface BaseArguments extends CreateWorkspaceOptions {
|
||||
linter?: 'none' | 'eslint';
|
||||
formatter?: 'none' | 'prettier';
|
||||
workspaces?: boolean;
|
||||
useProjectJson?: boolean;
|
||||
}
|
||||
|
||||
interface NoneArguments extends BaseArguments {
|
||||
@@ -175,6 +176,10 @@ export const commandsObject: yargs.Argv<Arguments> = yargs
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
})
|
||||
.option('useProjectJson', {
|
||||
describe: chalk.dim`Use a 'project.json' file for the Nx configuration instead of a 'package.json' file. This defaults to 'true' when '--no-workspaces' is used. Otherwise, it defaults to 'false'.`,
|
||||
type: 'boolean',
|
||||
})
|
||||
.option('formatter', {
|
||||
describe: chalk.dim`Code formatter to use.`,
|
||||
type: 'string',
|
||||
@@ -288,6 +293,7 @@ async function normalizeArgsMiddleware(
|
||||
});
|
||||
|
||||
argv.workspaces ??= true;
|
||||
argv.useProjectJson ??= !argv.workspaces;
|
||||
|
||||
try {
|
||||
argv.name = await determineFolder(argv);
|
||||
|
||||
@@ -167,6 +167,9 @@ function updatePackageJson(
|
||||
esbuildOptions,
|
||||
} = mergedTarget.options;
|
||||
|
||||
// the file must exist in the TS solution setup
|
||||
const tsconfigBase = readJson(tree, 'tsconfig.base.json');
|
||||
|
||||
// can't use the declarationRootDir as rootDir because it only affects the typings,
|
||||
// not the runtime entry point
|
||||
packageJson = getUpdatedPackageJsonContent(packageJson, {
|
||||
@@ -186,6 +189,10 @@ function updatePackageJson(
|
||||
outputFileExtensionForEsm: getOutExtension('esm', {
|
||||
userDefinedBuildOptions: esbuildOptions,
|
||||
}),
|
||||
skipDevelopmentExports:
|
||||
!tsconfigBase.compilerOptions?.customConditions?.includes(
|
||||
'development'
|
||||
),
|
||||
});
|
||||
|
||||
if (declarationRootDir !== dirname(main)) {
|
||||
|
||||
@@ -36,7 +36,6 @@
|
||||
"typescript",
|
||||
// Installed to workspace by plugins
|
||||
"@typescript-eslint/parser",
|
||||
"eslint-config-prettier",
|
||||
"@angular-eslint/eslint-plugin",
|
||||
"angular-eslint",
|
||||
"typescript-eslint",
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"homepage": "https://nx.dev",
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^6.13.2 || ^7.0.0 || ^8.0.0",
|
||||
"eslint-config-prettier": "^9.0.0"
|
||||
"eslint-config-prettier": "^10.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"eslint-config-prettier": {
|
||||
|
||||
@@ -182,6 +182,14 @@
|
||||
"alwaysAddToPackageJson": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"20.7.0": {
|
||||
"version": "20.7.0-beta.4",
|
||||
"packages": {
|
||||
"eslint-config-prettier": {
|
||||
"version": "^10.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ describe('convert-to-flat-config generator', () => {
|
||||
"@nx/eslint": "0.0.1",
|
||||
"@nx/eslint-plugin": "0.0.1",
|
||||
"eslint": "^9.8.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-config-prettier": "^10.0.0",
|
||||
"typescript-eslint": "^8.19.0"
|
||||
}
|
||||
}
|
||||
@@ -682,7 +682,7 @@ describe('convert-to-flat-config generator', () => {
|
||||
"@nx/eslint": "0.0.1",
|
||||
"@nx/eslint-plugin": "0.0.1",
|
||||
"eslint": "^9.8.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-config-prettier": "^10.0.0",
|
||||
"typescript-eslint": "^8.19.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ export const nxVersion = require('../../package.json').version;
|
||||
|
||||
export const eslintVersion = '~8.57.0';
|
||||
export const eslintrcVersion = '^2.1.1';
|
||||
export const eslintConfigPrettierVersion = '^9.0.0';
|
||||
export const eslintConfigPrettierVersion = '^10.0.0';
|
||||
export const typescriptESLintVersion = '^7.16.0';
|
||||
export const jsoncEslintParserVersion = '^2.1.0';
|
||||
|
||||
|
||||
@@ -467,6 +467,7 @@ describe('lib', () => {
|
||||
compilerOptions: {
|
||||
composite: true,
|
||||
declaration: true,
|
||||
customConditions: ['development'],
|
||||
},
|
||||
});
|
||||
writeJson(appTree, 'tsconfig.json', {
|
||||
@@ -630,13 +631,14 @@ describe('lib', () => {
|
||||
{
|
||||
"exports": {
|
||||
".": {
|
||||
"default": "./src/index.ts",
|
||||
"import": "./src/index.ts",
|
||||
"default": "./dist/index.cjs.js",
|
||||
"development": "./src/index.ts",
|
||||
"import": "./dist/index.esm.js",
|
||||
"types": "./dist/index.esm.d.ts",
|
||||
},
|
||||
"./package.json": "./package.json",
|
||||
},
|
||||
"main": "./src/index.ts",
|
||||
"main": "./dist/index.cjs.js",
|
||||
"module": "./dist/index.esm.js",
|
||||
"name": "@proj/my-lib",
|
||||
"peerDependencies": {
|
||||
@@ -649,6 +651,25 @@ describe('lib', () => {
|
||||
`);
|
||||
});
|
||||
|
||||
it('should not set the "development" condition in exports when it does not exist in tsconfig.base.json', async () => {
|
||||
updateJson(appTree, 'tsconfig.base.json', (json) => {
|
||||
delete json.compilerOptions.customConditions;
|
||||
return json;
|
||||
});
|
||||
|
||||
await expoLibraryGenerator(appTree, {
|
||||
...defaultSchema,
|
||||
buildable: true,
|
||||
strict: false,
|
||||
useProjectJson: false,
|
||||
skipFormat: true,
|
||||
});
|
||||
|
||||
expect(
|
||||
readJson(appTree, 'my-lib/package.json').exports['.']
|
||||
).not.toHaveProperty('development');
|
||||
});
|
||||
|
||||
it('should set "nx.name" in package.json when the user provides a name that is different than the package name', async () => {
|
||||
await expoLibraryGenerator(appTree, {
|
||||
...defaultSchema,
|
||||
|
||||
@@ -295,6 +295,15 @@ function createFiles(host: Tree, options: NormalizedSchema) {
|
||||
function determineEntryFields(
|
||||
options: NormalizedSchema
|
||||
): Pick<PackageJson, 'main' | 'types' | 'exports'> {
|
||||
if (
|
||||
options.buildable ||
|
||||
options.publishable ||
|
||||
!options.isUsingTsSolutionConfig
|
||||
) {
|
||||
// For buildable libraries, the entries are configured by the bundler (i.e. Rollup).
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
main: options.js ? './src/index.js' : './src/index.ts',
|
||||
types: options.js ? './src/index.js' : './src/index.ts',
|
||||
|
||||
@@ -23,7 +23,12 @@ export async function jestExecutor(
|
||||
): Promise<{ success: boolean }> {
|
||||
// Jest registers ts-node with module CJS https://github.com/SimenB/jest/blob/v29.6.4/packages/jest-config/src/readConfigFileAndSetRootDir.ts#L117-L119
|
||||
// We want to support of ESM via 'module':'nodenext', we need to override the resolution until Jest supports it.
|
||||
process.env.TS_NODE_COMPILER_OPTIONS ??= '{"moduleResolution":"node10"}';
|
||||
const existingValue = process.env['TS_NODE_COMPILER_OPTIONS'];
|
||||
process.env['TS_NODE_COMPILER_OPTIONS'] = JSON.stringify({
|
||||
...(existingValue ? JSON.parse(existingValue) : {}),
|
||||
moduleResolution: 'Node10',
|
||||
customConditions: null,
|
||||
});
|
||||
|
||||
const config = await parseJestConfig(options, context);
|
||||
|
||||
|
||||
@@ -1 +1,7 @@
|
||||
<% if(setupFile === 'react-native') { %>import '@testing-library/jest-native/extend-expect';<% } %>
|
||||
<% if(setupFile === 'react-native') { %>import '@testing-library/jest-native/extend-expect';<% } %>
|
||||
<%_ if(setupFile === 'react-router') { _%>
|
||||
import { TextEncoder, TextDecoder as NodeTextDecoder } from "util";
|
||||
|
||||
global.TextEncoder = TextEncoder;
|
||||
global.TextDecoder = NodeTextDecoder as typeof TextDecoder; // necessary because there is a mismatch between ts type and node type
|
||||
<%_ } _%>
|
||||
@@ -6,7 +6,12 @@ export interface JestProjectSchema {
|
||||
* @deprecated use setupFile instead
|
||||
*/
|
||||
skipSetupFile?: boolean;
|
||||
setupFile?: 'angular' | 'web-components' | 'react-native' | 'none';
|
||||
setupFile?:
|
||||
| 'angular'
|
||||
| 'web-components'
|
||||
| 'react-native'
|
||||
| 'react-router'
|
||||
| 'none';
|
||||
skipSerializers?: boolean;
|
||||
testEnvironment?: 'node' | 'jsdom' | 'none';
|
||||
/**
|
||||
|
||||
@@ -98,7 +98,7 @@ describe.each([true, false])('@nx/jest/plugin', (disableJestRuntime) => {
|
||||
"options": {
|
||||
"cwd": "proj",
|
||||
"env": {
|
||||
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}",
|
||||
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10","customConditions":null}",
|
||||
},
|
||||
},
|
||||
"outputs": [
|
||||
@@ -179,7 +179,7 @@ describe.each([true, false])('@nx/jest/plugin', (disableJestRuntime) => {
|
||||
"options": {
|
||||
"cwd": "proj",
|
||||
"env": {
|
||||
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}",
|
||||
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10","customConditions":null}",
|
||||
},
|
||||
},
|
||||
"outputs": [
|
||||
@@ -249,7 +249,7 @@ describe.each([true, false])('@nx/jest/plugin', (disableJestRuntime) => {
|
||||
"options": {
|
||||
"cwd": "proj",
|
||||
"env": {
|
||||
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}",
|
||||
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10","customConditions":null}",
|
||||
},
|
||||
},
|
||||
"outputs": [
|
||||
@@ -318,7 +318,7 @@ describe.each([true, false])('@nx/jest/plugin', (disableJestRuntime) => {
|
||||
"options": {
|
||||
"cwd": "proj",
|
||||
"env": {
|
||||
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}",
|
||||
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10","customConditions":null}",
|
||||
},
|
||||
},
|
||||
"outputs": [
|
||||
@@ -398,7 +398,7 @@ describe.each([true, false])('@nx/jest/plugin', (disableJestRuntime) => {
|
||||
"options": {
|
||||
"cwd": "proj",
|
||||
"env": {
|
||||
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}",
|
||||
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10","customConditions":null}",
|
||||
},
|
||||
},
|
||||
"outputs": [
|
||||
@@ -482,7 +482,7 @@ describe.each([true, false])('@nx/jest/plugin', (disableJestRuntime) => {
|
||||
"options": {
|
||||
"cwd": "proj",
|
||||
"env": {
|
||||
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}",
|
||||
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10","customConditions":null}",
|
||||
},
|
||||
},
|
||||
"outputs": [
|
||||
@@ -552,7 +552,7 @@ describe.each([true, false])('@nx/jest/plugin', (disableJestRuntime) => {
|
||||
"options": {
|
||||
"cwd": "proj",
|
||||
"env": {
|
||||
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}",
|
||||
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10","customConditions":null}",
|
||||
},
|
||||
},
|
||||
"outputs": [
|
||||
@@ -632,7 +632,7 @@ describe.each([true, false])('@nx/jest/plugin', (disableJestRuntime) => {
|
||||
"options": {
|
||||
"cwd": "proj",
|
||||
"env": {
|
||||
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}",
|
||||
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10","customConditions":null}",
|
||||
},
|
||||
},
|
||||
"outputs": [
|
||||
@@ -702,7 +702,7 @@ describe.each([true, false])('@nx/jest/plugin', (disableJestRuntime) => {
|
||||
"options": {
|
||||
"cwd": "proj",
|
||||
"env": {
|
||||
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}",
|
||||
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10","customConditions":null}",
|
||||
},
|
||||
},
|
||||
"outputs": [
|
||||
@@ -782,7 +782,7 @@ describe.each([true, false])('@nx/jest/plugin', (disableJestRuntime) => {
|
||||
"options": {
|
||||
"cwd": "proj",
|
||||
"env": {
|
||||
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}",
|
||||
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10","customConditions":null}",
|
||||
},
|
||||
},
|
||||
"outputs": [
|
||||
@@ -852,7 +852,7 @@ describe.each([true, false])('@nx/jest/plugin', (disableJestRuntime) => {
|
||||
"options": {
|
||||
"cwd": "proj",
|
||||
"env": {
|
||||
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}",
|
||||
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10","customConditions":null}",
|
||||
},
|
||||
},
|
||||
"outputs": [
|
||||
|
||||
@@ -253,13 +253,21 @@ async function buildJestTargets(
|
||||
const targets: Record<string, TargetConfiguration> = {};
|
||||
const namedInputs = getNamedInputs(projectRoot, context);
|
||||
|
||||
const existingTsNodeCompilerOptions = process.env['TS_NODE_COMPILER_OPTIONS'];
|
||||
const tsNodeCompilerOptions = JSON.stringify({
|
||||
...(existingTsNodeCompilerOptions
|
||||
? JSON.parse(existingTsNodeCompilerOptions)
|
||||
: {}),
|
||||
moduleResolution: 'node10',
|
||||
customConditions: null,
|
||||
});
|
||||
const target: TargetConfiguration = (targets[options.targetName] = {
|
||||
command: 'jest',
|
||||
options: {
|
||||
cwd: projectRoot,
|
||||
// Jest registers ts-node with module CJS https://github.com/SimenB/jest/blob/v29.6.4/packages/jest-config/src/readConfigFileAndSetRootDir.ts#L117-L119
|
||||
// We want to support of ESM via 'module':'nodenext', we need to override the resolution until Jest supports it.
|
||||
env: { TS_NODE_COMPILER_OPTIONS: '{"moduleResolution":"node10"}' },
|
||||
env: { TS_NODE_COMPILER_OPTIONS: tsNodeCompilerOptions },
|
||||
},
|
||||
metadata: {
|
||||
technologies: ['jest'],
|
||||
@@ -341,7 +349,7 @@ async function buildJestTargets(
|
||||
outputs,
|
||||
options: {
|
||||
cwd: projectRoot,
|
||||
env: { TS_NODE_COMPILER_OPTIONS: '{"moduleResolution":"node10"}' },
|
||||
env: { TS_NODE_COMPILER_OPTIONS: tsNodeCompilerOptions },
|
||||
},
|
||||
metadata: {
|
||||
technologies: ['jest'],
|
||||
@@ -481,9 +489,7 @@ async function buildJestTargets(
|
||||
outputs,
|
||||
options: {
|
||||
cwd: projectRoot,
|
||||
env: {
|
||||
TS_NODE_COMPILER_OPTIONS: '{"moduleResolution":"node10"}',
|
||||
},
|
||||
env: { TS_NODE_COMPILER_OPTIONS: tsNodeCompilerOptions },
|
||||
},
|
||||
metadata: {
|
||||
technologies: ['jest'],
|
||||
|
||||
@@ -60,8 +60,6 @@
|
||||
"semver": "^7.5.3",
|
||||
"source-map-support": "0.5.19",
|
||||
"tinyglobby": "^0.2.12",
|
||||
"ts-node": "10.9.1",
|
||||
"tsconfig-paths": "^4.1.2",
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"noUnusedLocals": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"target": "es2022"
|
||||
"target": "es2022",
|
||||
"customConditions": ["development"]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,7 +208,8 @@ describe('js init generator', () => {
|
||||
"noUnusedLocals": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"target": "es2022"
|
||||
"target": "es2022",
|
||||
"customConditions": ["development"]
|
||||
}
|
||||
}
|
||||
"
|
||||
|
||||
@@ -1817,6 +1817,7 @@ describe('lib', () => {
|
||||
compilerOptions: {
|
||||
composite: true,
|
||||
declaration: true,
|
||||
customConditions: ['development'],
|
||||
},
|
||||
});
|
||||
writeJson(tree, 'tsconfig.json', {
|
||||
@@ -1954,6 +1955,7 @@ describe('lib', () => {
|
||||
"exports": {
|
||||
".": {
|
||||
"default": "./dist/index.js",
|
||||
"development": "./src/index.ts",
|
||||
"import": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
},
|
||||
@@ -1987,6 +1989,7 @@ describe('lib', () => {
|
||||
"exports": {
|
||||
".": {
|
||||
"default": "./dist/index.js",
|
||||
"development": "./src/index.ts",
|
||||
"import": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
},
|
||||
@@ -2344,24 +2347,6 @@ describe('lib', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should add nx.addTypecheckTarget to tsconfig.json when using tsc to build to avoid duplicated typechecks', async () => {
|
||||
await libraryGenerator(tree, {
|
||||
...defaultOptions,
|
||||
useProjectJson: false,
|
||||
directory: 'my-ts-lib',
|
||||
bundler: 'tsc',
|
||||
unitTestRunner: 'none',
|
||||
linter: 'none',
|
||||
});
|
||||
|
||||
expect(readJson(tree, 'my-ts-lib/tsconfig.json').nx)
|
||||
.toMatchInlineSnapshot(`
|
||||
{
|
||||
"addTypecheckTarget": false,
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should set "nx.name" in package.json when the user provides a name that is different than the package name and "useProjectJson" is "false"', async () => {
|
||||
await libraryGenerator(tree, {
|
||||
...defaultOptions,
|
||||
@@ -2428,5 +2413,26 @@ describe('lib', () => {
|
||||
expect(readJson(tree, 'my-lib/project.json').name).toBe('my-lib');
|
||||
expect(readJson(tree, 'my-lib/package.json').nx).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should not set the "development" condition in exports when it does not exist in tsconfig.base.json', async () => {
|
||||
updateJson(tree, 'tsconfig.base.json', (json) => {
|
||||
delete json.compilerOptions.customConditions;
|
||||
return json;
|
||||
});
|
||||
|
||||
await libraryGenerator(tree, {
|
||||
...defaultOptions,
|
||||
directory: 'my-lib',
|
||||
name: 'my-lib',
|
||||
useProjectJson: true,
|
||||
bundler: 'tsc',
|
||||
addPlugin: true,
|
||||
skipFormat: true,
|
||||
});
|
||||
|
||||
expect(
|
||||
readJson(tree, 'my-lib/package.json').exports['.']
|
||||
).not.toHaveProperty('development');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
names,
|
||||
offsetFromRoot,
|
||||
ProjectConfiguration,
|
||||
readJson,
|
||||
readNxJson,
|
||||
readProjectConfiguration,
|
||||
runTasksInSerial,
|
||||
@@ -631,6 +632,9 @@ function createFiles(tree: Tree, options: NormalizedLibraryGeneratorOptions) {
|
||||
options.isUsingTsSolutionConfig &&
|
||||
!['none', 'rollup', 'vite'].includes(options.bundler)
|
||||
) {
|
||||
// the file must exist in the TS solution setup
|
||||
const tsconfigBase = readJson(tree, 'tsconfig.base.json');
|
||||
|
||||
return getUpdatedPackageJsonContent(updatedPackageJson, {
|
||||
main: join(options.projectRoot, 'src/index.ts'),
|
||||
outputPath: joinPathFragments(options.projectRoot, 'dist'),
|
||||
@@ -639,6 +643,10 @@ function createFiles(tree: Tree, options: NormalizedLibraryGeneratorOptions) {
|
||||
generateExportsField: true,
|
||||
packageJsonPath,
|
||||
format: ['esm'],
|
||||
skipDevelopmentExports:
|
||||
!tsconfigBase.compilerOptions?.customConditions?.includes(
|
||||
'development'
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -664,6 +672,8 @@ function createFiles(tree: Tree, options: NormalizedLibraryGeneratorOptions) {
|
||||
options.isUsingTsSolutionConfig &&
|
||||
!['none', 'rollup', 'vite'].includes(options.bundler)
|
||||
) {
|
||||
const tsconfigBase = readJson(tree, 'tsconfig.base.json');
|
||||
|
||||
packageJson = getUpdatedPackageJsonContent(packageJson, {
|
||||
main: join(options.projectRoot, 'src/index.ts'),
|
||||
outputPath: joinPathFragments(options.projectRoot, 'dist'),
|
||||
@@ -672,6 +682,10 @@ function createFiles(tree: Tree, options: NormalizedLibraryGeneratorOptions) {
|
||||
generateExportsField: true,
|
||||
packageJsonPath,
|
||||
format: ['esm'],
|
||||
skipDevelopmentExports:
|
||||
!tsconfigBase.compilerOptions?.customConditions?.includes(
|
||||
'development'
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1043,12 +1057,6 @@ function createProjectTsConfigs(
|
||||
json.references.push({
|
||||
path: './tsconfig.lib.json',
|
||||
});
|
||||
// If using `tsc` to build, then we do not want a typecheck target that duplicates the work, since both run `tsc`.
|
||||
// This applies to `@nx/js/typescript` plugin only.
|
||||
if (options.bundler === 'tsc') {
|
||||
json['nx'] ??= {};
|
||||
json['nx'].addTypecheckTarget = false;
|
||||
}
|
||||
return json;
|
||||
});
|
||||
} else {
|
||||
@@ -1059,12 +1067,6 @@ function createProjectTsConfigs(
|
||||
include: [],
|
||||
references: [{ path: './tsconfig.lib.json' }],
|
||||
};
|
||||
// If using `tsc` to build, then we do not want a typecheck target that duplicates the work, since both run `tsc`.
|
||||
// This applies to `@nx/js/typescript` plugin only.
|
||||
if (options.bundler === 'tsc') {
|
||||
tsconfig['nx'] ??= {};
|
||||
tsconfig['nx'].addTypecheckTarget = false;
|
||||
}
|
||||
writeJson(
|
||||
tree,
|
||||
joinPathFragments(options.projectRoot, 'tsconfig.json'),
|
||||
|
||||
@@ -325,6 +325,11 @@ function updatePackageJson(
|
||||
version: '0.0.1',
|
||||
};
|
||||
}
|
||||
|
||||
// the file must exist in the TS solution setup, which is the only case this
|
||||
// function is called
|
||||
const tsconfigBase = readJson(tree, 'tsconfig.base.json');
|
||||
|
||||
packageJson = getUpdatedPackageJsonContent(packageJson, {
|
||||
main,
|
||||
outputPath,
|
||||
@@ -333,6 +338,8 @@ function updatePackageJson(
|
||||
packageJsonPath,
|
||||
rootDir,
|
||||
format,
|
||||
skipDevelopmentExports:
|
||||
!tsconfigBase.compilerOptions?.customConditions?.includes('development'),
|
||||
});
|
||||
writeJson(tree, packageJsonPath, packageJson);
|
||||
}
|
||||
|
||||
@@ -49,24 +49,28 @@ export function startLocalRegistry({
|
||||
);
|
||||
|
||||
const registry = `http://${listenAddress}:${port}`;
|
||||
const authToken = 'secretVerdaccioToken';
|
||||
|
||||
console.log(`Local registry started on ${registry}`);
|
||||
|
||||
process.env.npm_config_registry = registry;
|
||||
execSync(
|
||||
`npm config set //${listenAddress}:${port}/:_authToken "secretVerdaccioToken" --ws=false`,
|
||||
`npm config set //${listenAddress}:${port}/:_authToken "${authToken}" --ws=false`,
|
||||
{
|
||||
windowsHide: false,
|
||||
}
|
||||
);
|
||||
|
||||
// bun
|
||||
process.env.BUN_CONFIG_REGISTRY = registry;
|
||||
process.env.BUN_CONFIG_TOKEN = authToken;
|
||||
// yarnv1
|
||||
process.env.YARN_REGISTRY = registry;
|
||||
// yarnv2
|
||||
process.env.YARN_NPM_REGISTRY_SERVER = registry;
|
||||
process.env.YARN_UNSAFE_HTTP_WHITELIST = listenAddress;
|
||||
|
||||
console.log('Set npm and yarn config registry to ' + registry);
|
||||
console.log('Set npm, bun, and yarn config registry to ' + registry);
|
||||
|
||||
resolve(() => {
|
||||
childProcess.kill();
|
||||
|
||||
@@ -795,6 +795,307 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
|
||||
`);
|
||||
});
|
||||
|
||||
it('should normalize and add directories in `include` with the ts extensions', async () => {
|
||||
await applyFilesToTempFsAndContext(tempFs, context, {
|
||||
'libs/my-lib/tsconfig.json': JSON.stringify({
|
||||
include: ['src'],
|
||||
// set this to keep outputs smaller
|
||||
compilerOptions: { outDir: 'dist' },
|
||||
}),
|
||||
'libs/my-lib/package.json': `{}`,
|
||||
});
|
||||
|
||||
expect(await invokeCreateNodesOnMatchingFiles(context, {}))
|
||||
.toMatchInlineSnapshot(`
|
||||
{
|
||||
"projects": {
|
||||
"libs/my-lib": {
|
||||
"projectType": "library",
|
||||
"targets": {
|
||||
"typecheck": {
|
||||
"cache": true,
|
||||
"command": "tsc --build --emitDeclarationOnly",
|
||||
"dependsOn": [
|
||||
"^typecheck",
|
||||
],
|
||||
"inputs": [
|
||||
"{projectRoot}/package.json",
|
||||
"{projectRoot}/tsconfig.json",
|
||||
"{projectRoot}/src/**/*.ts",
|
||||
"{projectRoot}/src/**/*.tsx",
|
||||
"{projectRoot}/src/**/*.d.ts",
|
||||
"{projectRoot}/src/**/*.cts",
|
||||
"{projectRoot}/src/**/*.d.cts",
|
||||
"{projectRoot}/src/**/*.mts",
|
||||
"{projectRoot}/src/**/*.d.mts",
|
||||
"^production",
|
||||
{
|
||||
"externalDependencies": [
|
||||
"typescript",
|
||||
],
|
||||
},
|
||||
],
|
||||
"metadata": {
|
||||
"description": "Runs type-checking for the project.",
|
||||
"help": {
|
||||
"command": "npx tsc --build --help",
|
||||
"example": {
|
||||
"args": [
|
||||
"--force",
|
||||
],
|
||||
},
|
||||
},
|
||||
"technologies": [
|
||||
"typescript",
|
||||
],
|
||||
},
|
||||
"options": {
|
||||
"cwd": "libs/my-lib",
|
||||
},
|
||||
"outputs": [
|
||||
"{projectRoot}/dist",
|
||||
],
|
||||
"syncGenerators": [
|
||||
"@nx/js:typescript-sync",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should normalize and add directories in `include` with the ts and js extensions when `allowJs` is true', async () => {
|
||||
await applyFilesToTempFsAndContext(tempFs, context, {
|
||||
'libs/my-lib/tsconfig.json': JSON.stringify({
|
||||
include: ['src'],
|
||||
// set this to keep outputs smaller
|
||||
compilerOptions: { outDir: 'dist', allowJs: true },
|
||||
}),
|
||||
'libs/my-lib/package.json': `{}`,
|
||||
});
|
||||
|
||||
expect(await invokeCreateNodesOnMatchingFiles(context, {}))
|
||||
.toMatchInlineSnapshot(`
|
||||
{
|
||||
"projects": {
|
||||
"libs/my-lib": {
|
||||
"projectType": "library",
|
||||
"targets": {
|
||||
"typecheck": {
|
||||
"cache": true,
|
||||
"command": "tsc --build --emitDeclarationOnly",
|
||||
"dependsOn": [
|
||||
"^typecheck",
|
||||
],
|
||||
"inputs": [
|
||||
"{projectRoot}/package.json",
|
||||
"{projectRoot}/tsconfig.json",
|
||||
"{projectRoot}/src/**/*.ts",
|
||||
"{projectRoot}/src/**/*.tsx",
|
||||
"{projectRoot}/src/**/*.d.ts",
|
||||
"{projectRoot}/src/**/*.js",
|
||||
"{projectRoot}/src/**/*.jsx",
|
||||
"{projectRoot}/src/**/*.cts",
|
||||
"{projectRoot}/src/**/*.d.cts",
|
||||
"{projectRoot}/src/**/*.cjs",
|
||||
"{projectRoot}/src/**/*.mts",
|
||||
"{projectRoot}/src/**/*.d.mts",
|
||||
"{projectRoot}/src/**/*.mjs",
|
||||
"^production",
|
||||
{
|
||||
"externalDependencies": [
|
||||
"typescript",
|
||||
],
|
||||
},
|
||||
],
|
||||
"metadata": {
|
||||
"description": "Runs type-checking for the project.",
|
||||
"help": {
|
||||
"command": "npx tsc --build --help",
|
||||
"example": {
|
||||
"args": [
|
||||
"--force",
|
||||
],
|
||||
},
|
||||
},
|
||||
"technologies": [
|
||||
"typescript",
|
||||
],
|
||||
},
|
||||
"options": {
|
||||
"cwd": "libs/my-lib",
|
||||
},
|
||||
"outputs": [
|
||||
"{projectRoot}/dist",
|
||||
],
|
||||
"syncGenerators": [
|
||||
"@nx/js:typescript-sync",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should normalize and add directories in `include` with the ts and json extensions when `resolveJsonModule` is true', async () => {
|
||||
await applyFilesToTempFsAndContext(tempFs, context, {
|
||||
'libs/my-lib/tsconfig.json': JSON.stringify({
|
||||
include: ['src'],
|
||||
// set this to keep outputs smaller
|
||||
compilerOptions: {
|
||||
outDir: 'dist',
|
||||
resolveJsonModule: true,
|
||||
},
|
||||
}),
|
||||
'libs/my-lib/package.json': `{}`,
|
||||
});
|
||||
|
||||
expect(await invokeCreateNodesOnMatchingFiles(context, {}))
|
||||
.toMatchInlineSnapshot(`
|
||||
{
|
||||
"projects": {
|
||||
"libs/my-lib": {
|
||||
"projectType": "library",
|
||||
"targets": {
|
||||
"typecheck": {
|
||||
"cache": true,
|
||||
"command": "tsc --build --emitDeclarationOnly",
|
||||
"dependsOn": [
|
||||
"^typecheck",
|
||||
],
|
||||
"inputs": [
|
||||
"{projectRoot}/package.json",
|
||||
"{projectRoot}/tsconfig.json",
|
||||
"{projectRoot}/src/**/*.ts",
|
||||
"{projectRoot}/src/**/*.tsx",
|
||||
"{projectRoot}/src/**/*.d.ts",
|
||||
"{projectRoot}/src/**/*.cts",
|
||||
"{projectRoot}/src/**/*.d.cts",
|
||||
"{projectRoot}/src/**/*.mts",
|
||||
"{projectRoot}/src/**/*.d.mts",
|
||||
"{projectRoot}/src/**/*.json",
|
||||
"^production",
|
||||
{
|
||||
"externalDependencies": [
|
||||
"typescript",
|
||||
],
|
||||
},
|
||||
],
|
||||
"metadata": {
|
||||
"description": "Runs type-checking for the project.",
|
||||
"help": {
|
||||
"command": "npx tsc --build --help",
|
||||
"example": {
|
||||
"args": [
|
||||
"--force",
|
||||
],
|
||||
},
|
||||
},
|
||||
"technologies": [
|
||||
"typescript",
|
||||
],
|
||||
},
|
||||
"options": {
|
||||
"cwd": "libs/my-lib",
|
||||
},
|
||||
"outputs": [
|
||||
"{projectRoot}/dist",
|
||||
],
|
||||
"syncGenerators": [
|
||||
"@nx/js:typescript-sync",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should normalize and add directories in `include` with the ts, js and json extensions when `allowJs` and `resolveJsonModule` are true', async () => {
|
||||
await applyFilesToTempFsAndContext(tempFs, context, {
|
||||
'libs/my-lib/tsconfig.json': JSON.stringify({
|
||||
include: ['src'],
|
||||
// set this to keep outputs smaller
|
||||
compilerOptions: {
|
||||
outDir: 'dist',
|
||||
allowJs: true,
|
||||
resolveJsonModule: true,
|
||||
},
|
||||
}),
|
||||
'libs/my-lib/package.json': `{}`,
|
||||
});
|
||||
|
||||
expect(await invokeCreateNodesOnMatchingFiles(context, {}))
|
||||
.toMatchInlineSnapshot(`
|
||||
{
|
||||
"projects": {
|
||||
"libs/my-lib": {
|
||||
"projectType": "library",
|
||||
"targets": {
|
||||
"typecheck": {
|
||||
"cache": true,
|
||||
"command": "tsc --build --emitDeclarationOnly",
|
||||
"dependsOn": [
|
||||
"^typecheck",
|
||||
],
|
||||
"inputs": [
|
||||
"{projectRoot}/package.json",
|
||||
"{projectRoot}/tsconfig.json",
|
||||
"{projectRoot}/src/**/*.ts",
|
||||
"{projectRoot}/src/**/*.tsx",
|
||||
"{projectRoot}/src/**/*.d.ts",
|
||||
"{projectRoot}/src/**/*.js",
|
||||
"{projectRoot}/src/**/*.jsx",
|
||||
"{projectRoot}/src/**/*.cts",
|
||||
"{projectRoot}/src/**/*.d.cts",
|
||||
"{projectRoot}/src/**/*.cjs",
|
||||
"{projectRoot}/src/**/*.mts",
|
||||
"{projectRoot}/src/**/*.d.mts",
|
||||
"{projectRoot}/src/**/*.mjs",
|
||||
"{projectRoot}/src/**/*.json",
|
||||
"^production",
|
||||
{
|
||||
"externalDependencies": [
|
||||
"typescript",
|
||||
],
|
||||
},
|
||||
],
|
||||
"metadata": {
|
||||
"description": "Runs type-checking for the project.",
|
||||
"help": {
|
||||
"command": "npx tsc --build --help",
|
||||
"example": {
|
||||
"args": [
|
||||
"--force",
|
||||
],
|
||||
},
|
||||
},
|
||||
"technologies": [
|
||||
"typescript",
|
||||
],
|
||||
},
|
||||
"options": {
|
||||
"cwd": "libs/my-lib",
|
||||
},
|
||||
"outputs": [
|
||||
"{projectRoot}/dist",
|
||||
],
|
||||
"syncGenerators": [
|
||||
"@nx/js:typescript-sync",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should add extended config files', async () => {
|
||||
await applyFilesToTempFsAndContext(tempFs, context, {
|
||||
'tsconfig.base.json': JSON.stringify({
|
||||
@@ -1107,6 +1408,103 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
|
||||
`);
|
||||
});
|
||||
|
||||
it('should normalize and add directories in `include` from internal project references', async () => {
|
||||
await applyFilesToTempFsAndContext(tempFs, context, {
|
||||
'libs/my-lib/tsconfig.json': JSON.stringify({
|
||||
files: [],
|
||||
include: [],
|
||||
references: [
|
||||
{ path: './tsconfig.lib.json' },
|
||||
{ path: './tsconfig.spec.json' },
|
||||
],
|
||||
// set this to keep outputs smaller
|
||||
compilerOptions: { outDir: 'dist' },
|
||||
}),
|
||||
'libs/my-lib/tsconfig.lib.json': JSON.stringify({
|
||||
include: ['src/**/*.ts'],
|
||||
exclude: ['tests'], // should be ignored because another referenced internal project includes this same pattern
|
||||
// set this to keep outputs smaller
|
||||
compilerOptions: { outDir: 'dist' },
|
||||
}),
|
||||
'libs/my-lib/tsconfig.spec.json': JSON.stringify({
|
||||
include: ['tests'], // directory pattern that should be normalized
|
||||
references: [{ path: './tsconfig.lib.json' }],
|
||||
compilerOptions: {
|
||||
outDir: 'dist', // set this to keep outputs smaller
|
||||
allowJs: true, // should result in including js extensions in the normalized include paths
|
||||
},
|
||||
}),
|
||||
'libs/my-lib/package.json': `{}`,
|
||||
});
|
||||
|
||||
expect(await invokeCreateNodesOnMatchingFiles(context, {}))
|
||||
.toMatchInlineSnapshot(`
|
||||
{
|
||||
"projects": {
|
||||
"libs/my-lib": {
|
||||
"projectType": "library",
|
||||
"targets": {
|
||||
"typecheck": {
|
||||
"cache": true,
|
||||
"command": "tsc --build --emitDeclarationOnly",
|
||||
"dependsOn": [
|
||||
"^typecheck",
|
||||
],
|
||||
"inputs": [
|
||||
"{projectRoot}/package.json",
|
||||
"{projectRoot}/tsconfig.json",
|
||||
"{projectRoot}/tsconfig.lib.json",
|
||||
"{projectRoot}/tsconfig.spec.json",
|
||||
"{projectRoot}/src/**/*.ts",
|
||||
"{projectRoot}/tests/**/*.ts",
|
||||
"{projectRoot}/tests/**/*.tsx",
|
||||
"{projectRoot}/tests/**/*.d.ts",
|
||||
"{projectRoot}/tests/**/*.js",
|
||||
"{projectRoot}/tests/**/*.jsx",
|
||||
"{projectRoot}/tests/**/*.cts",
|
||||
"{projectRoot}/tests/**/*.d.cts",
|
||||
"{projectRoot}/tests/**/*.cjs",
|
||||
"{projectRoot}/tests/**/*.mts",
|
||||
"{projectRoot}/tests/**/*.d.mts",
|
||||
"{projectRoot}/tests/**/*.mjs",
|
||||
"^production",
|
||||
{
|
||||
"externalDependencies": [
|
||||
"typescript",
|
||||
],
|
||||
},
|
||||
],
|
||||
"metadata": {
|
||||
"description": "Runs type-checking for the project.",
|
||||
"help": {
|
||||
"command": "npx tsc --build --help",
|
||||
"example": {
|
||||
"args": [
|
||||
"--force",
|
||||
],
|
||||
},
|
||||
},
|
||||
"technologies": [
|
||||
"typescript",
|
||||
],
|
||||
},
|
||||
"options": {
|
||||
"cwd": "libs/my-lib",
|
||||
},
|
||||
"outputs": [
|
||||
"{projectRoot}/dist",
|
||||
],
|
||||
"syncGenerators": [
|
||||
"@nx/js:typescript-sync",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should only add exclude paths that are not part of other tsconfig files include paths', async () => {
|
||||
// exact match
|
||||
await applyFilesToTempFsAndContext(tempFs, context, {
|
||||
@@ -2206,6 +2604,111 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
|
||||
`);
|
||||
});
|
||||
|
||||
it('should not create build target when the entry points point to source files', async () => {
|
||||
// Sibling package.json
|
||||
await applyFilesToTempFsAndContext(tempFs, context, {
|
||||
'libs/my-lib/tsconfig.json': `{}`,
|
||||
'libs/my-lib/tsconfig.lib.json': `{"compilerOptions": {"outDir": "dist"}}`,
|
||||
'libs/my-lib/package.json': JSON.stringify({
|
||||
exports: {
|
||||
'.': {
|
||||
default: './src/index.ts',
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
expect(
|
||||
await invokeCreateNodesOnMatchingFiles(context, {
|
||||
// Reduce noise in build snapshots by disabling default typecheck target
|
||||
typecheck: false,
|
||||
build: true,
|
||||
})
|
||||
).toMatchInlineSnapshot(`
|
||||
{
|
||||
"projects": {
|
||||
"libs/my-lib": {
|
||||
"projectType": "library",
|
||||
"targets": {},
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should create build target when the entry points point to dist files', async () => {
|
||||
// Sibling package.json
|
||||
await applyFilesToTempFsAndContext(tempFs, context, {
|
||||
'libs/my-lib/tsconfig.json': `{}`,
|
||||
'libs/my-lib/tsconfig.lib.json': `{"compilerOptions": {"outDir": "dist"}}`,
|
||||
'libs/my-lib/package.json': JSON.stringify({
|
||||
exports: {
|
||||
'.': {
|
||||
// should ignore the fact that the development condition points to source
|
||||
development: './src/index.ts',
|
||||
types: './dist/index.d.ts',
|
||||
default: './dist/index.js',
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
expect(
|
||||
await invokeCreateNodesOnMatchingFiles(context, {
|
||||
// Reduce noise in build snapshots by disabling default typecheck target
|
||||
typecheck: false,
|
||||
build: true,
|
||||
})
|
||||
).toMatchInlineSnapshot(`
|
||||
{
|
||||
"projects": {
|
||||
"libs/my-lib": {
|
||||
"projectType": "library",
|
||||
"targets": {
|
||||
"build": {
|
||||
"cache": true,
|
||||
"command": "tsc --build tsconfig.lib.json",
|
||||
"dependsOn": [
|
||||
"^build",
|
||||
],
|
||||
"inputs": [
|
||||
"production",
|
||||
"^production",
|
||||
{
|
||||
"externalDependencies": [
|
||||
"typescript",
|
||||
],
|
||||
},
|
||||
],
|
||||
"metadata": {
|
||||
"description": "Builds the project with \`tsc\`.",
|
||||
"help": {
|
||||
"command": "npx tsc --build --help",
|
||||
"example": {
|
||||
"args": [
|
||||
"--force",
|
||||
],
|
||||
},
|
||||
},
|
||||
"technologies": [
|
||||
"typescript",
|
||||
],
|
||||
},
|
||||
"options": {
|
||||
"cwd": "libs/my-lib",
|
||||
},
|
||||
"outputs": [
|
||||
"{projectRoot}/dist",
|
||||
],
|
||||
"syncGenerators": [
|
||||
"@nx/js:typescript-sync",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should create a node with a build target when enabled, for a project level tsconfig.lib.json build file by default', async () => {
|
||||
// Sibling package.json
|
||||
await applyFilesToTempFsAndContext(tempFs, context, {
|
||||
@@ -2740,6 +3243,320 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
|
||||
`);
|
||||
});
|
||||
|
||||
it('should normalize and add directories in `include` with the ts extensions', async () => {
|
||||
await applyFilesToTempFsAndContext(tempFs, context, {
|
||||
'libs/my-lib/tsconfig.lib.json': JSON.stringify({
|
||||
compilerOptions: { outDir: 'dist' },
|
||||
include: ['src'],
|
||||
}),
|
||||
'libs/my-lib/tsconfig.json': `{}`,
|
||||
'libs/my-lib/package.json': `{"main": "dist/index.js"}`,
|
||||
});
|
||||
|
||||
expect(
|
||||
await invokeCreateNodesOnMatchingFiles(context, {
|
||||
typecheck: false,
|
||||
build: true,
|
||||
})
|
||||
).toMatchInlineSnapshot(`
|
||||
{
|
||||
"projects": {
|
||||
"libs/my-lib": {
|
||||
"projectType": "library",
|
||||
"targets": {
|
||||
"build": {
|
||||
"cache": true,
|
||||
"command": "tsc --build tsconfig.lib.json",
|
||||
"dependsOn": [
|
||||
"^build",
|
||||
],
|
||||
"inputs": [
|
||||
"{projectRoot}/package.json",
|
||||
"{projectRoot}/tsconfig.lib.json",
|
||||
"{projectRoot}/src/**/*.ts",
|
||||
"{projectRoot}/src/**/*.tsx",
|
||||
"{projectRoot}/src/**/*.d.ts",
|
||||
"{projectRoot}/src/**/*.cts",
|
||||
"{projectRoot}/src/**/*.d.cts",
|
||||
"{projectRoot}/src/**/*.mts",
|
||||
"{projectRoot}/src/**/*.d.mts",
|
||||
"^production",
|
||||
{
|
||||
"externalDependencies": [
|
||||
"typescript",
|
||||
],
|
||||
},
|
||||
],
|
||||
"metadata": {
|
||||
"description": "Builds the project with \`tsc\`.",
|
||||
"help": {
|
||||
"command": "npx tsc --build --help",
|
||||
"example": {
|
||||
"args": [
|
||||
"--force",
|
||||
],
|
||||
},
|
||||
},
|
||||
"technologies": [
|
||||
"typescript",
|
||||
],
|
||||
},
|
||||
"options": {
|
||||
"cwd": "libs/my-lib",
|
||||
},
|
||||
"outputs": [
|
||||
"{projectRoot}/dist",
|
||||
],
|
||||
"syncGenerators": [
|
||||
"@nx/js:typescript-sync",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should normalize and add directories in `include` with the ts and js extensions when `allowJs` is true', async () => {
|
||||
await applyFilesToTempFsAndContext(tempFs, context, {
|
||||
'libs/my-lib/tsconfig.lib.json': JSON.stringify({
|
||||
compilerOptions: { outDir: 'dist', allowJs: true },
|
||||
include: ['src'],
|
||||
}),
|
||||
'libs/my-lib/tsconfig.json': `{}`,
|
||||
'libs/my-lib/package.json': `{"main": "dist/index.js"}`,
|
||||
});
|
||||
|
||||
expect(
|
||||
await invokeCreateNodesOnMatchingFiles(context, {
|
||||
typecheck: false,
|
||||
build: true,
|
||||
})
|
||||
).toMatchInlineSnapshot(`
|
||||
{
|
||||
"projects": {
|
||||
"libs/my-lib": {
|
||||
"projectType": "library",
|
||||
"targets": {
|
||||
"build": {
|
||||
"cache": true,
|
||||
"command": "tsc --build tsconfig.lib.json",
|
||||
"dependsOn": [
|
||||
"^build",
|
||||
],
|
||||
"inputs": [
|
||||
"{projectRoot}/package.json",
|
||||
"{projectRoot}/tsconfig.lib.json",
|
||||
"{projectRoot}/src/**/*.ts",
|
||||
"{projectRoot}/src/**/*.tsx",
|
||||
"{projectRoot}/src/**/*.d.ts",
|
||||
"{projectRoot}/src/**/*.js",
|
||||
"{projectRoot}/src/**/*.jsx",
|
||||
"{projectRoot}/src/**/*.cts",
|
||||
"{projectRoot}/src/**/*.d.cts",
|
||||
"{projectRoot}/src/**/*.cjs",
|
||||
"{projectRoot}/src/**/*.mts",
|
||||
"{projectRoot}/src/**/*.d.mts",
|
||||
"{projectRoot}/src/**/*.mjs",
|
||||
"^production",
|
||||
{
|
||||
"externalDependencies": [
|
||||
"typescript",
|
||||
],
|
||||
},
|
||||
],
|
||||
"metadata": {
|
||||
"description": "Builds the project with \`tsc\`.",
|
||||
"help": {
|
||||
"command": "npx tsc --build --help",
|
||||
"example": {
|
||||
"args": [
|
||||
"--force",
|
||||
],
|
||||
},
|
||||
},
|
||||
"technologies": [
|
||||
"typescript",
|
||||
],
|
||||
},
|
||||
"options": {
|
||||
"cwd": "libs/my-lib",
|
||||
},
|
||||
"outputs": [
|
||||
"{projectRoot}/dist",
|
||||
],
|
||||
"syncGenerators": [
|
||||
"@nx/js:typescript-sync",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should normalize and add directories in `include` with the ts and json extensions when `resolveJsonModule` is true', async () => {
|
||||
await applyFilesToTempFsAndContext(tempFs, context, {
|
||||
'libs/my-lib/tsconfig.lib.json': JSON.stringify({
|
||||
compilerOptions: { outDir: 'dist', resolveJsonModule: true },
|
||||
include: ['src'],
|
||||
}),
|
||||
'libs/my-lib/tsconfig.json': `{}`,
|
||||
'libs/my-lib/package.json': `{"main": "dist/index.js"}`,
|
||||
});
|
||||
|
||||
expect(
|
||||
await invokeCreateNodesOnMatchingFiles(context, {
|
||||
typecheck: false,
|
||||
build: true,
|
||||
})
|
||||
).toMatchInlineSnapshot(`
|
||||
{
|
||||
"projects": {
|
||||
"libs/my-lib": {
|
||||
"projectType": "library",
|
||||
"targets": {
|
||||
"build": {
|
||||
"cache": true,
|
||||
"command": "tsc --build tsconfig.lib.json",
|
||||
"dependsOn": [
|
||||
"^build",
|
||||
],
|
||||
"inputs": [
|
||||
"{projectRoot}/package.json",
|
||||
"{projectRoot}/tsconfig.lib.json",
|
||||
"{projectRoot}/src/**/*.ts",
|
||||
"{projectRoot}/src/**/*.tsx",
|
||||
"{projectRoot}/src/**/*.d.ts",
|
||||
"{projectRoot}/src/**/*.cts",
|
||||
"{projectRoot}/src/**/*.d.cts",
|
||||
"{projectRoot}/src/**/*.mts",
|
||||
"{projectRoot}/src/**/*.d.mts",
|
||||
"{projectRoot}/src/**/*.json",
|
||||
"^production",
|
||||
{
|
||||
"externalDependencies": [
|
||||
"typescript",
|
||||
],
|
||||
},
|
||||
],
|
||||
"metadata": {
|
||||
"description": "Builds the project with \`tsc\`.",
|
||||
"help": {
|
||||
"command": "npx tsc --build --help",
|
||||
"example": {
|
||||
"args": [
|
||||
"--force",
|
||||
],
|
||||
},
|
||||
},
|
||||
"technologies": [
|
||||
"typescript",
|
||||
],
|
||||
},
|
||||
"options": {
|
||||
"cwd": "libs/my-lib",
|
||||
},
|
||||
"outputs": [
|
||||
"{projectRoot}/dist",
|
||||
],
|
||||
"syncGenerators": [
|
||||
"@nx/js:typescript-sync",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should normalize and add directories in `include` with the ts, js and json extensions when `allowJs` and `resolveJsonModule` are true', async () => {
|
||||
await applyFilesToTempFsAndContext(tempFs, context, {
|
||||
'libs/my-lib/tsconfig.lib.json': JSON.stringify({
|
||||
compilerOptions: {
|
||||
outDir: 'dist',
|
||||
allowJs: true,
|
||||
resolveJsonModule: true,
|
||||
},
|
||||
include: ['src'],
|
||||
}),
|
||||
'libs/my-lib/tsconfig.json': `{}`,
|
||||
'libs/my-lib/package.json': `{"main": "dist/index.js"}`,
|
||||
});
|
||||
|
||||
expect(
|
||||
await invokeCreateNodesOnMatchingFiles(context, {
|
||||
typecheck: false,
|
||||
build: true,
|
||||
})
|
||||
).toMatchInlineSnapshot(`
|
||||
{
|
||||
"projects": {
|
||||
"libs/my-lib": {
|
||||
"projectType": "library",
|
||||
"targets": {
|
||||
"build": {
|
||||
"cache": true,
|
||||
"command": "tsc --build tsconfig.lib.json",
|
||||
"dependsOn": [
|
||||
"^build",
|
||||
],
|
||||
"inputs": [
|
||||
"{projectRoot}/package.json",
|
||||
"{projectRoot}/tsconfig.lib.json",
|
||||
"{projectRoot}/src/**/*.ts",
|
||||
"{projectRoot}/src/**/*.tsx",
|
||||
"{projectRoot}/src/**/*.d.ts",
|
||||
"{projectRoot}/src/**/*.js",
|
||||
"{projectRoot}/src/**/*.jsx",
|
||||
"{projectRoot}/src/**/*.cts",
|
||||
"{projectRoot}/src/**/*.d.cts",
|
||||
"{projectRoot}/src/**/*.cjs",
|
||||
"{projectRoot}/src/**/*.mts",
|
||||
"{projectRoot}/src/**/*.d.mts",
|
||||
"{projectRoot}/src/**/*.mjs",
|
||||
"{projectRoot}/src/**/*.json",
|
||||
"^production",
|
||||
{
|
||||
"externalDependencies": [
|
||||
"typescript",
|
||||
],
|
||||
},
|
||||
],
|
||||
"metadata": {
|
||||
"description": "Builds the project with \`tsc\`.",
|
||||
"help": {
|
||||
"command": "npx tsc --build --help",
|
||||
"example": {
|
||||
"args": [
|
||||
"--force",
|
||||
],
|
||||
},
|
||||
},
|
||||
"technologies": [
|
||||
"typescript",
|
||||
],
|
||||
},
|
||||
"options": {
|
||||
"cwd": "libs/my-lib",
|
||||
},
|
||||
"outputs": [
|
||||
"{projectRoot}/dist",
|
||||
],
|
||||
"syncGenerators": [
|
||||
"@nx/js:typescript-sync",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should be able to extended config files', async () => {
|
||||
await applyFilesToTempFsAndContext(tempFs, context, {
|
||||
'tsconfig.base.json': JSON.stringify({
|
||||
@@ -2995,6 +3812,94 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
|
||||
`);
|
||||
});
|
||||
|
||||
it('should normalize and add directories in `include` from internal project references', async () => {
|
||||
await applyFilesToTempFsAndContext(tempFs, context, {
|
||||
'libs/my-lib/tsconfig.json': '{}',
|
||||
'libs/my-lib/tsconfig.lib.json': JSON.stringify({
|
||||
compilerOptions: { outDir: 'dist' },
|
||||
include: ['src/**/*.ts'],
|
||||
exclude: ['src/**/foo.ts'], // should be ignored because a referenced internal project includes this same pattern
|
||||
references: [{ path: './tsconfig.other.json' }],
|
||||
}),
|
||||
'libs/my-lib/tsconfig.other.json': JSON.stringify({
|
||||
include: ['other', 'src/**/foo.ts'],
|
||||
compilerOptions: {
|
||||
outDir: 'dist', // set this to keep outputs smaller
|
||||
resolveJsonModule: true, // should result in including json extensions in the normalized include paths
|
||||
},
|
||||
}),
|
||||
'libs/my-lib/package.json': `{"main": "dist/index.js"}`, // Should be defined so that the project is considered buildable
|
||||
});
|
||||
|
||||
expect(
|
||||
await invokeCreateNodesOnMatchingFiles(context, {
|
||||
typecheck: false,
|
||||
build: true,
|
||||
})
|
||||
).toMatchInlineSnapshot(`
|
||||
{
|
||||
"projects": {
|
||||
"libs/my-lib": {
|
||||
"projectType": "library",
|
||||
"targets": {
|
||||
"build": {
|
||||
"cache": true,
|
||||
"command": "tsc --build tsconfig.lib.json",
|
||||
"dependsOn": [
|
||||
"^build",
|
||||
],
|
||||
"inputs": [
|
||||
"{projectRoot}/package.json",
|
||||
"{projectRoot}/tsconfig.lib.json",
|
||||
"{projectRoot}/tsconfig.other.json",
|
||||
"{projectRoot}/src/**/*.ts",
|
||||
"{projectRoot}/other/**/*.ts",
|
||||
"{projectRoot}/other/**/*.tsx",
|
||||
"{projectRoot}/other/**/*.d.ts",
|
||||
"{projectRoot}/other/**/*.cts",
|
||||
"{projectRoot}/other/**/*.d.cts",
|
||||
"{projectRoot}/other/**/*.mts",
|
||||
"{projectRoot}/other/**/*.d.mts",
|
||||
"{projectRoot}/other/**/*.json",
|
||||
"{projectRoot}/src/**/foo.ts",
|
||||
"^production",
|
||||
{
|
||||
"externalDependencies": [
|
||||
"typescript",
|
||||
],
|
||||
},
|
||||
],
|
||||
"metadata": {
|
||||
"description": "Builds the project with \`tsc\`.",
|
||||
"help": {
|
||||
"command": "npx tsc --build --help",
|
||||
"example": {
|
||||
"args": [
|
||||
"--force",
|
||||
],
|
||||
},
|
||||
},
|
||||
"technologies": [
|
||||
"typescript",
|
||||
],
|
||||
},
|
||||
"options": {
|
||||
"cwd": "libs/my-lib",
|
||||
},
|
||||
"outputs": [
|
||||
"{projectRoot}/dist",
|
||||
],
|
||||
"syncGenerators": [
|
||||
"@nx/js:typescript-sync",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should only add exclude paths that are not part of other tsconfig files include paths', async () => {
|
||||
// exact match
|
||||
await applyFilesToTempFsAndContext(tempFs, context, {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
CreateNodesContextV2,
|
||||
createNodesFromFiles,
|
||||
detectPackageManager,
|
||||
getPackageManagerCommand,
|
||||
@@ -11,18 +10,17 @@ import {
|
||||
type CreateDependencies,
|
||||
type CreateNodes,
|
||||
type CreateNodesContext,
|
||||
type CreateNodesContextV2,
|
||||
type CreateNodesV2,
|
||||
type NxJsonConfiguration,
|
||||
type ProjectConfiguration,
|
||||
type TargetConfiguration,
|
||||
} from '@nx/devkit';
|
||||
import { getNamedInputs } from '@nx/devkit/src/utils/get-named-inputs';
|
||||
import picomatch = require('picomatch');
|
||||
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
||||
import {
|
||||
basename,
|
||||
dirname,
|
||||
extname,
|
||||
join,
|
||||
normalize,
|
||||
relative,
|
||||
@@ -31,10 +29,11 @@ import {
|
||||
} from 'node:path';
|
||||
import * as posix from 'node:path/posix';
|
||||
import { hashArray, hashFile, hashObject } from 'nx/src/hasher/file-hasher';
|
||||
import picomatch = require('picomatch');
|
||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
||||
import { getLockFileName } from 'nx/src/plugins/js/lock-file/lock-file';
|
||||
import { workspaceDataDirectory } from 'nx/src/utils/cache-directory';
|
||||
import type { ParsedCommandLine, System } from 'typescript';
|
||||
import type { Extension, ParsedCommandLine, System } from 'typescript';
|
||||
import {
|
||||
addBuildAndWatchDepsTargets,
|
||||
isValidPackageJsonBuildConfig,
|
||||
@@ -83,6 +82,7 @@ type TsconfigCacheData = {
|
||||
extendedFilesHash: string;
|
||||
};
|
||||
|
||||
let ts: typeof import('typescript');
|
||||
const pmc = getPackageManagerCommand();
|
||||
|
||||
let tsConfigCache: Record<string, TsconfigCacheData>;
|
||||
@@ -320,6 +320,9 @@ async function getConfigFileHash(
|
||||
lockFileHash,
|
||||
optionsHash,
|
||||
...(packageJson ? [hashObject(packageJson)] : []),
|
||||
// change this to bust the cache when making changes that would yield
|
||||
// different results for the same hash
|
||||
hashObject({ bust: 1 }),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -534,12 +537,65 @@ function getInputs(
|
||||
...Object.entries(internalProjectReferences),
|
||||
];
|
||||
const absoluteProjectRoot = join(workspaceRoot, projectRoot);
|
||||
|
||||
if (!ts) {
|
||||
ts = require('typescript');
|
||||
}
|
||||
// https://github.com/microsoft/TypeScript/blob/19b777260b26aac5707b1efd34202054164d4a9d/src/compiler/utilities.ts#L9869
|
||||
const supportedTSExtensions: readonly Extension[] = [
|
||||
ts.Extension.Ts,
|
||||
ts.Extension.Tsx,
|
||||
ts.Extension.Dts,
|
||||
ts.Extension.Cts,
|
||||
ts.Extension.Dcts,
|
||||
ts.Extension.Mts,
|
||||
ts.Extension.Dmts,
|
||||
];
|
||||
// https://github.com/microsoft/TypeScript/blob/19b777260b26aac5707b1efd34202054164d4a9d/src/compiler/utilities.ts#L9878
|
||||
const allSupportedExtensions: readonly Extension[] = [
|
||||
ts.Extension.Ts,
|
||||
ts.Extension.Tsx,
|
||||
ts.Extension.Dts,
|
||||
ts.Extension.Js,
|
||||
ts.Extension.Jsx,
|
||||
ts.Extension.Cts,
|
||||
ts.Extension.Dcts,
|
||||
ts.Extension.Cjs,
|
||||
ts.Extension.Mts,
|
||||
ts.Extension.Dmts,
|
||||
ts.Extension.Mjs,
|
||||
];
|
||||
|
||||
const normalizeInput = (
|
||||
input: string,
|
||||
config: ParsedTsconfigData
|
||||
): string[] => {
|
||||
const extensions = config.options.allowJs
|
||||
? [...allSupportedExtensions]
|
||||
: [...supportedTSExtensions];
|
||||
if (config.options.resolveJsonModule) {
|
||||
extensions.push(ts.Extension.Json);
|
||||
}
|
||||
|
||||
const segments = input.split('/');
|
||||
// An "includes" path "foo" is implicitly a glob "foo/**/*" if its last
|
||||
// segment has no extension, and does not contain any glob characters
|
||||
// itself.
|
||||
// https://github.com/microsoft/TypeScript/blob/19b777260b26aac5707b1efd34202054164d4a9d/src/compiler/utilities.ts#L9577-L9585
|
||||
if (!/[.*?]/.test(segments.at(-1))) {
|
||||
return extensions.map((ext) => `${segments.join('/')}/**/*${ext}`);
|
||||
}
|
||||
|
||||
return [input];
|
||||
};
|
||||
|
||||
projectTsConfigFiles.forEach(([configPath, config]) => {
|
||||
configFiles.add(configPath);
|
||||
const offset = relative(absoluteProjectRoot, dirname(configPath));
|
||||
(config.raw?.include ?? []).forEach((p: string) =>
|
||||
includePaths.add(join(offset, p))
|
||||
);
|
||||
(config.raw?.include ?? []).forEach((p: string) => {
|
||||
const normalized = normalizeInput(join(offset, p), config);
|
||||
normalized.forEach((input) => includePaths.add(input));
|
||||
});
|
||||
|
||||
if (config.raw?.exclude) {
|
||||
/**
|
||||
@@ -1071,7 +1127,6 @@ function getExtendedFilesHash(
|
||||
return hashes.join('|');
|
||||
}
|
||||
|
||||
let ts: typeof import('typescript');
|
||||
function readTsConfig(
|
||||
tsConfigPath: string,
|
||||
workspaceRoot: string
|
||||
|
||||
@@ -95,8 +95,8 @@ export function isValidPackageJsonBuildConfig(
|
||||
return isPathSourceFile(value);
|
||||
} else if (typeof value === 'object') {
|
||||
return Object.entries(value).some(([currentKey, subValue]) => {
|
||||
// Skip types field
|
||||
if (currentKey === 'types') {
|
||||
// Skip types and development conditions
|
||||
if (currentKey === 'types' || currentKey === 'development') {
|
||||
return false;
|
||||
}
|
||||
if (typeof subValue === 'string') {
|
||||
|
||||