diff --git a/e2e/react/src/react.test.ts b/e2e/react/src/react.test.ts index 8e5db024e982e..182d38cf56736 100644 --- a/e2e/react/src/react.test.ts +++ b/e2e/react/src/react.test.ts @@ -8,6 +8,7 @@ import { listFiles, newProject, readFile, + readJson, runCLI, runCLIAsync, runE2ETests, @@ -20,7 +21,6 @@ import { join } from 'path'; describe('React Applications', () => { let proj: string; - describe('Crystal Supported Tests', () => { beforeAll(() => { proj = newProject({ packages: ['@nx/react'] }); @@ -28,7 +28,6 @@ describe('React Applications', () => { }); afterAll(() => cleanupProject()); - it('should be able to use Vite to build and test apps', async () => { const appName = uniq('app'); const libName = uniq('lib'); @@ -68,13 +67,13 @@ describe('React Applications', () => { const libName = uniq('lib'); runCLI( - `generate @nx/react:app apps/${appName} --name=${appName} --useTsSolution true --bundler=vite --no-interactive --skipFormat --linter=eslint --unitTestRunner=vitest` + `generate @nx/react:app apps/${appName} --name=${appName} --useTsSolution=true --bundler=vite --no-interactive --skipFormat --linter=eslint --unitTestRunner=vitest` ); runCLI( `generate @nx/react:lib ${libName} --bundler=none --no-interactive --unit-test-runner=vitest --skipFormat --linter=eslint` ); - const nxJson = JSON.parse(readFile('nx.json')); + const nxJson = readJson('nx.json'); const jsTypescriptPlugin = nxJson.plugins.find( (plugin) => plugin.plugin === '@nx/js/typescript' diff --git a/e2e/utils/create-project-utils.ts b/e2e/utils/create-project-utils.ts index 7199e95e8623c..24c0866163692 100644 --- a/e2e/utils/create-project-utils.ts +++ b/e2e/utils/create-project-utils.ts @@ -76,10 +76,12 @@ export function newProject({ name = uniq('proj'), packageManager = getSelectedPackageManager(), packages, + preset = 'apps', }: { name?: string; packageManager?: 'npm' | 'yarn' | 'pnpm' | 'bun'; readonly packages?: Array; + preset?: string; } = {}): string { const newProjectStart = performance.mark('new-project:start'); try { @@ -93,7 +95,7 @@ export function newProject({ 'create-nx-workspace:start' ); runCreateWorkspace(projScope, { - preset: 'apps', + preset, packageManager, }); const createNxWorkspaceEnd = performance.mark('create-nx-workspace:end'); @@ -256,6 +258,7 @@ export function runCreateWorkspace( const pm = getPackageManagerCommand({ packageManager }); let command = `${pm.createWorkspace} ${name} --preset=${preset} --nxCloud=skip --no-interactive`; + if (appName) { command += ` --appName=${appName}`; } diff --git a/packages/expo/src/generators/application/application.ts b/packages/expo/src/generators/application/application.ts index 4c971cbc324b1..a854ca39f2871 100644 --- a/packages/expo/src/generators/application/application.ts +++ b/packages/expo/src/generators/application/application.ts @@ -6,7 +6,10 @@ import { Tree, } from '@nx/devkit'; import { initGenerator as jsInitGenerator } from '@nx/js'; -import { updateTsconfigFiles } from '@nx/js/src/utils/typescript/ts-solution-setup'; +import { + addProjectToTsSolutionWorkspace, + updateTsconfigFiles, +} from '@nx/js/src/utils/typescript/ts-solution-setup'; import { addLinting } from '../../utils/add-linting'; import { addJest } from '../../utils/add-jest'; @@ -95,6 +98,12 @@ export async function expoApplicationGeneratorInternal( : undefined ); + // If we are using the new TS solution + // We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project + if (options.useTsSolution) { + addProjectToTsSolutionWorkspace(host, options.appProjectRoot); + } + if (!options.skipFormat) { await formatFiles(host); } diff --git a/packages/expo/src/generators/library/library.ts b/packages/expo/src/generators/library/library.ts index 83743bf5a6e44..f301d815dbbc6 100644 --- a/packages/expo/src/generators/library/library.ts +++ b/packages/expo/src/generators/library/library.ts @@ -36,7 +36,10 @@ import { ensureDependencies } from '../../utils/ensure-dependencies'; import { initRootBabelConfig } from '../../utils/init-root-babel-config'; import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils'; import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command'; -import { updateTsconfigFiles } from '@nx/js/src/utils/typescript/ts-solution-setup'; +import { + addProjectToTsSolutionWorkspace, + updateTsconfigFiles, +} from '@nx/js/src/utils/typescript/ts-solution-setup'; import { getImportPath } from '@nx/js/src/utils/get-import-path'; export async function expoLibraryGenerator( @@ -130,6 +133,10 @@ export async function expoLibraryGeneratorInternal( : undefined ); + if (options.isUsingTsSolutionConfig) { + addProjectToTsSolutionWorkspace(host, `${options.projectRoot}/*`); + } + if (!options.skipFormat) { await formatFiles(host); } diff --git a/packages/js/src/utils/typescript/ts-solution-setup.ts b/packages/js/src/utils/typescript/ts-solution-setup.ts index c726556004337..fda55e24a6ef0 100644 --- a/packages/js/src/utils/typescript/ts-solution-setup.ts +++ b/packages/js/src/utils/typescript/ts-solution-setup.ts @@ -1,4 +1,5 @@ import { + detectPackageManager, joinPathFragments, offsetFromRoot, output, @@ -186,3 +187,36 @@ export function updateTsconfigFiles( }); } } + +export function addProjectToTsSolutionWorkspace( + tree: Tree, + projectDir: string +) { + if (detectPackageManager() === 'pnpm') { + const { load, dump } = require('@zkochan/js-yaml'); + if (tree.exists('pnpm-workspace.yaml')) { + const workspaceFile = tree.read('pnpm-workspace.yaml', 'utf-8'); + const yamlData = load(workspaceFile); + + if (!yamlData?.packages) { + yamlData.packages = []; + } + + if (!yamlData.packages.includes(projectDir)) { + yamlData.packages.push(projectDir); + tree.write('pnpm-workspace.yaml', dump(yamlData, { indent: 2 })); + } + } + } else { + // Update package.json + const packageJson = readJson(tree, 'package.json'); + if (!packageJson.workspaces) { + packageJson.workspaces = []; + } + + if (!packageJson.workspaces.includes(projectDir)) { + packageJson.workspaces.push(projectDir); + tree.write('package.json', JSON.stringify(packageJson, null, 2)); + } + } +} diff --git a/packages/next/src/generators/application/application.ts b/packages/next/src/generators/application/application.ts index c17d968e74df5..e9b3adb33f244 100644 --- a/packages/next/src/generators/application/application.ts +++ b/packages/next/src/generators/application/application.ts @@ -30,7 +30,10 @@ import { updateCypressTsConfig } from './lib/update-cypress-tsconfig'; import { showPossibleWarnings } from './lib/show-possible-warnings'; import { tsLibVersion } from '../../utils/versions'; import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command'; -import { updateTsconfigFiles } from '@nx/js/src/utils/typescript/ts-solution-setup'; +import { + addProjectToTsSolutionWorkspace, + updateTsconfigFiles, +} from '@nx/js/src/utils/typescript/ts-solution-setup'; export async function applicationGenerator(host: Tree, schema: Schema) { return await applicationGeneratorInternal(host, { @@ -132,6 +135,12 @@ export async function applicationGeneratorInternal(host: Tree, schema: Schema) { options.src ? 'src' : '.' ); + // If we are using the new TS solution + // We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project + if (options.useTsSolution) { + addProjectToTsSolutionWorkspace(host, options.appProjectRoot); + } + if (!options.skipFormat) { await formatFiles(host); } diff --git a/packages/next/src/generators/library/lib/normalize-options.ts b/packages/next/src/generators/library/lib/normalize-options.ts index a77b07a9623fe..cc2d85876ecc5 100644 --- a/packages/next/src/generators/library/lib/normalize-options.ts +++ b/packages/next/src/generators/library/lib/normalize-options.ts @@ -4,10 +4,12 @@ import { ensureProjectName, } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { Schema } from '../schema'; +import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; export interface NormalizedSchema extends Schema { importPath: string; projectRoot: string; + isUsingTsSolutionConfig: boolean; } export async function normalizeOptions( @@ -35,5 +37,6 @@ export async function normalizeOptions( ...options, importPath, projectRoot, + isUsingTsSolutionConfig: isUsingTsSolutionSetup(host), }; } diff --git a/packages/next/src/generators/library/library.ts b/packages/next/src/generators/library/library.ts index 0269a3cf36a2c..c2c10e8746f81 100644 --- a/packages/next/src/generators/library/library.ts +++ b/packages/next/src/generators/library/library.ts @@ -15,7 +15,10 @@ import { nextInitGenerator } from '../init/init'; import { Schema } from './schema'; import { normalizeOptions } from './lib/normalize-options'; import { eslintConfigNextVersion, tsLibVersion } from '../../utils/versions'; -import { updateTsconfigFiles } from '@nx/js/src/utils/typescript/ts-solution-setup'; +import { + addProjectToTsSolutionWorkspace, + updateTsconfigFiles, +} from '@nx/js/src/utils/typescript/ts-solution-setup'; export async function libraryGenerator(host: Tree, rawOptions: Schema) { return await libraryGeneratorInternal(host, { @@ -154,6 +157,10 @@ export async function libraryGeneratorInternal(host: Tree, rawOptions: Schema) { : undefined ); + if (options.isUsingTsSolutionConfig) { + addProjectToTsSolutionWorkspace(host, `${options.projectRoot}/*`); + } + if (!options.skipFormat) { await formatFiles(host); } diff --git a/packages/react-native/src/generators/application/application.ts b/packages/react-native/src/generators/application/application.ts index 5518a43a0347a..bec9a0421c8a9 100644 --- a/packages/react-native/src/generators/application/application.ts +++ b/packages/react-native/src/generators/application/application.ts @@ -25,7 +25,10 @@ import { Schema } from './schema'; import { ensureDependencies } from '../../utils/ensure-dependencies'; import { syncDeps } from '../../executors/sync-deps/sync-deps.impl'; import { PackageJson } from 'nx/src/utils/package-json'; -import { updateTsconfigFiles } from '@nx/js/src/utils/typescript/ts-solution-setup'; +import { + addProjectToTsSolutionWorkspace, + updateTsconfigFiles, +} from '@nx/js/src/utils/typescript/ts-solution-setup'; export async function reactNativeApplicationGenerator( host: Tree, @@ -143,6 +146,12 @@ export async function reactNativeApplicationGeneratorInternal( : undefined ); + // If we are using the new TS solution + // We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project + if (options.useTsSolution) { + addProjectToTsSolutionWorkspace(host, options.appProjectRoot); + } + if (!options.skipFormat) { await formatFiles(host); } diff --git a/packages/react-native/src/generators/library/library.ts b/packages/react-native/src/generators/library/library.ts index 227cd2d302431..0b981e9243142 100644 --- a/packages/react-native/src/generators/library/library.ts +++ b/packages/react-native/src/generators/library/library.ts @@ -35,7 +35,7 @@ import { Schema } from './schema'; import { ensureDependencies } from '../../utils/ensure-dependencies'; import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command'; import { - isUsingTsSolutionSetup, + addProjectToTsSolutionWorkspace, updateTsconfigFiles, } from '@nx/js/src/utils/typescript/ts-solution-setup'; import { getImportPath } from '@nx/js/src/utils/get-import-path'; @@ -130,6 +130,10 @@ export async function reactNativeLibraryGeneratorInternal( : undefined ); + if (options.isUsingTsSolutionConfig) { + addProjectToTsSolutionWorkspace(host, `${options.projectRoot}/*`); + } + if (!options.skipFormat) { await formatFiles(host); } diff --git a/packages/react/src/generators/application/application.spec.ts b/packages/react/src/generators/application/application.spec.ts index 874abcde48376..10dfefaab6401 100644 --- a/packages/react/src/generators/application/application.spec.ts +++ b/packages/react/src/generators/application/application.spec.ts @@ -15,6 +15,7 @@ import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; import { Linter } from '@nx/eslint'; import { applicationGenerator } from './application'; import { Schema } from './schema'; +const { load } = require('@zkochan/js-yaml'); // need to mock cypress otherwise it'll use the nx installed version from package.json // which is v9 while we are testing for the new v10 version jest.mock('@nx/cypress/src/utils/cypress-version'); @@ -1247,6 +1248,7 @@ describe('app', () => { describe('TS solution setup', () => { beforeEach(() => { appTree = createTreeWithEmptyWorkspace(); + appTree.write('pnpm-workspace.yaml', `packages:`); updateJson(appTree, 'package.json', (json) => { json.workspaces = ['packages/*', 'apps/*']; return json; @@ -1418,5 +1420,22 @@ describe('app', () => { } `); }); + + it('should add project to workspaces when using TS solution', async () => { + await applicationGenerator(appTree, { + directory: 'myapp', + addPlugin: true, + linter: Linter.EsLint, + style: 'none', + bundler: 'vite', + unitTestRunner: 'none', + e2eTestRunner: 'none', + }); + + const pnpmContent = appTree.read('pnpm-workspace.yaml', 'utf-8'); + const pnpmWorkspaceFile = load(pnpmContent); + + expect(pnpmWorkspaceFile.packages).toEqual(['myapp']); + }); }); }); diff --git a/packages/react/src/generators/application/application.ts b/packages/react/src/generators/application/application.ts index c9a421896e46a..3248278822092 100644 --- a/packages/react/src/generators/application/application.ts +++ b/packages/react/src/generators/application/application.ts @@ -10,6 +10,7 @@ import { setDefaults } from './lib/set-defaults'; import { addStyledModuleDependencies } from '../../rules/add-styled-dependencies'; import { addDependenciesToPackageJson, + detectPackageManager, ensurePackage, formatFiles, GeneratorCallback, @@ -41,7 +42,11 @@ import { initGenerator as jsInitGenerator } from '@nx/js'; import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command'; import { setupTailwindGenerator } from '../setup-tailwind/setup-tailwind'; import { useFlatConfig } from '@nx/eslint/src/utils/flat-config'; -import { updateTsconfigFiles } from '@nx/js/src/utils/typescript/ts-solution-setup'; +import { + addProjectToTsSolutionWorkspace, + isUsingTsSolutionSetup, + updateTsconfigFiles, +} from '@nx/js/src/utils/typescript/ts-solution-setup'; async function addLinting(host: Tree, options: NormalizedSchema) { const tasks: GeneratorCallback[] = []; @@ -115,7 +120,7 @@ export async function applicationGeneratorInternal( ...schema, tsConfigName: schema.rootProject ? 'tsconfig.json' : 'tsconfig.base.json', skipFormat: true, - addTsPlugin: schema.useTsSolution, + addTsPlugin: schema.useTsSolution ?? isUsingTsSolutionSetup(host), formatter: schema.formatter, }); tasks.push(jsInitTask); @@ -367,6 +372,12 @@ export async function applicationGeneratorInternal( moduleResolution: 'bundler', }); + // If we are using the new TS solution + // We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project + if (options.isUsingTsSolutionConfig) { + addProjectToTsSolutionWorkspace(host, options.appProjectRoot); + } + if (!options.skipFormat) { await formatFiles(host); } diff --git a/packages/react/src/generators/library/lib/normalize-options.ts b/packages/react/src/generators/library/lib/normalize-options.ts index 76233f3dd833b..9110d3f1a4d7f 100644 --- a/packages/react/src/generators/library/lib/normalize-options.ts +++ b/packages/react/src/generators/library/lib/normalize-options.ts @@ -13,7 +13,6 @@ import { import { assertValidStyle } from '../../../utils/assertion'; import { NormalizedSchema, Schema } from '../schema'; import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; -import { promptWhenInteractive } from '@nx/devkit/src/generators/prompt'; export async function normalizeOptions( host: Tree, diff --git a/packages/react/src/generators/library/library.spec.ts b/packages/react/src/generators/library/library.spec.ts index be647296211fb..119171fad35c8 100644 --- a/packages/react/src/generators/library/library.spec.ts +++ b/packages/react/src/generators/library/library.spec.ts @@ -15,6 +15,7 @@ import { nxVersion } from '../../utils/versions'; import applicationGenerator from '../application/application'; import libraryGenerator from './library'; import { Schema } from './schema'; +const { load } = require('@zkochan/js-yaml'); // need to mock cypress otherwise it'll use the nx installed version from package.json // which is v9 while we are testing for the new v10 version jest.mock('@nx/cypress/src/utils/cypress-version'); @@ -910,6 +911,7 @@ module.exports = withNx( describe('TS solution setup', () => { beforeEach(() => { tree = createTreeWithEmptyWorkspace(); + tree.write('pnpm-workspace.yaml', `packages:`); updateJson(tree, 'package.json', (json) => { json.workspaces = ['packages/*', 'apps/*']; return json; @@ -1186,5 +1188,20 @@ module.exports = withNx( " `); }); + + it('should add project to workspaces when using TS solution', async () => { + await libraryGenerator(tree, { + ...defaultSchema, + bundler: 'rollup', + unitTestRunner: 'none', + directory: 'mylib', + name: 'mylib', + }); + + const pnpmContent = tree.read('pnpm-workspace.yaml', 'utf-8'); + const pnpmWorkspaceFile = load(pnpmContent); + + expect(pnpmWorkspaceFile.packages).toEqual(['mylib/*']); + }); }); }); diff --git a/packages/react/src/generators/library/library.ts b/packages/react/src/generators/library/library.ts index cfa3e4832c3a5..90ecfb37445b2 100644 --- a/packages/react/src/generators/library/library.ts +++ b/packages/react/src/generators/library/library.ts @@ -31,7 +31,10 @@ import { createFiles } from './lib/create-files'; import { extractTsConfigBase } from '../../utils/create-ts-config'; import { installCommonDependencies } from './lib/install-common-dependencies'; import { setDefaults } from './lib/set-defaults'; -import { updateTsconfigFiles } from '@nx/js/src/utils/typescript/ts-solution-setup'; +import { + addProjectToTsSolutionWorkspace, + updateTsconfigFiles, +} from '@nx/js/src/utils/typescript/ts-solution-setup'; import { ensureProjectIsExcludedFromPluginRegistrations } from '@nx/js/src/utils/typescript/plugin'; export async function libraryGenerator(host: Tree, schema: Schema) { @@ -278,6 +281,9 @@ export async function libraryGeneratorInternal(host: Tree, schema: Schema) { : undefined ); + if (options.isUsingTsSolutionConfig) { + addProjectToTsSolutionWorkspace(host, `${options.projectRoot}/*`); + } if (!options.skipFormat) { await formatFiles(host); } diff --git a/packages/remix/src/generators/application/application.impl.ts b/packages/remix/src/generators/application/application.impl.ts index e66b13fd2885d..a2dd0a4aa8b2a 100644 --- a/packages/remix/src/generators/application/application.impl.ts +++ b/packages/remix/src/generators/application/application.impl.ts @@ -45,6 +45,7 @@ import { } from './lib'; import { NxRemixGeneratorSchema } from './schema'; import { + addProjectToTsSolutionWorkspace, isUsingTsSolutionSetup, updateTsconfigFiles, } from '@nx/js/src/utils/typescript/ts-solution-setup'; @@ -374,6 +375,12 @@ export default {...nxPreset}; '.' ); + // If we are using the new TS solution + // We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project + if (options.useTsSolution) { + addProjectToTsSolutionWorkspace(tree, options.projectRoot); + } + tasks.push(() => { logShowProjectCommand(options.projectName); }); diff --git a/packages/remix/src/generators/library/lib/normalize-options.ts b/packages/remix/src/generators/library/lib/normalize-options.ts index ebbf1ff26d0d7..1dac5a6758083 100644 --- a/packages/remix/src/generators/library/lib/normalize-options.ts +++ b/packages/remix/src/generators/library/lib/normalize-options.ts @@ -4,10 +4,12 @@ import { ensureProjectName, } from '@nx/devkit/src/generators/project-name-and-root-utils'; import type { NxRemixGeneratorSchema } from '../schema'; +import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; export interface RemixLibraryOptions extends NxRemixGeneratorSchema { projectName: string; projectRoot: string; + isUsingTsSolutionConfig: boolean; } export async function normalizeOptions( @@ -35,5 +37,6 @@ export async function normalizeOptions( unitTestRunner: options.unitTestRunner ?? 'vitest', projectName, projectRoot, + isUsingTsSolutionConfig: isUsingTsSolutionSetup(tree), }; } diff --git a/packages/remix/src/generators/library/library.impl.ts b/packages/remix/src/generators/library/library.impl.ts index 93bb3a565948a..b19eb35c767b8 100644 --- a/packages/remix/src/generators/library/library.impl.ts +++ b/packages/remix/src/generators/library/library.impl.ts @@ -9,7 +9,10 @@ import { updateBuildableConfig, } from './lib'; import type { NxRemixGeneratorSchema } from './schema'; -import { updateTsconfigFiles } from '@nx/js/src/utils/typescript/ts-solution-setup'; +import { + addProjectToTsSolutionWorkspace, + updateTsconfigFiles, +} from '@nx/js/src/utils/typescript/ts-solution-setup'; export async function remixLibraryGenerator( tree: Tree, @@ -73,6 +76,10 @@ export async function remixLibraryGeneratorInternal( : undefined ); + if (options.isUsingTsSolutionConfig) { + addProjectToTsSolutionWorkspace(tree, `${options.projectRoot}/*`); + } + if (!options.skipFormat) { await formatFiles(tree); } diff --git a/packages/workspace/src/generators/new/generate-workspace-files.spec.ts b/packages/workspace/src/generators/new/generate-workspace-files.spec.ts index a53182eebf46f..bbb0ce981a8b5 100644 --- a/packages/workspace/src/generators/new/generate-workspace-files.spec.ts +++ b/packages/workspace/src/generators/new/generate-workspace-files.spec.ts @@ -332,8 +332,8 @@ describe('@nx/workspace:generateWorkspaceFiles', () => { const packageJson = tree.read('/proj/pnpm-workspace.yaml', 'utf-8'); expect(packageJson).toMatchInlineSnapshot(` "packages: - - apps/** - - packages/** + - "apps/**" + - "packages/**" " `); }); diff --git a/packages/workspace/src/generators/new/generate-workspace-files.ts b/packages/workspace/src/generators/new/generate-workspace-files.ts index da8bb75720680..951aa263dc475 100644 --- a/packages/workspace/src/generators/new/generate-workspace-files.ts +++ b/packages/workspace/src/generators/new/generate-workspace-files.ts @@ -429,7 +429,7 @@ function setUpWorkspacesInPackageJson(tree: Tree, options: NormalizedSchema) { tree.write( join(options.directory, 'pnpm-workspace.yaml'), `packages: - - ${workspaces.join('\n - ')} + ${workspaces.map((workspace) => `- "${workspace}"`).join('\n ')} ` ); } else {