feat(js): remove nx property from generated package.json files (#29705)

This PR updates our generators to no longer generate with `nx` in
`package.json` by default. The only times it is needed is if you pass
add `tags` or `implicitDependencies` to the project config.

This PR replaces our `projectType` checks to use the `getProjectType`
util from `@nx/js` to prefer the project config, but otherwise will
check for our conventions (e.g. using `exports` for libs,
`tsconfig.lib.json` vs `tsconfig.app.json`).

## Impact
- There shouldn't be any behavioral changes to existing projects that
have explicit `projectType`, `name`, etc. in with `project.json` or
`package.json` (via `nx` property).
- For new projects created under the new TS setup, the `nx` property
will no longer be there. Generators with logic that depend on
`projectType` will now check for `tsconfig.lib.json` and
`tsconfig.app.json` (so all of our generators are covered). If none of
those tsconfig files are found, then we check `package.json`, since
libraries are required to have `exports` to be consumed.
This commit is contained in:
Jack Hsu 2025-01-23 20:03:28 -05:00 committed by GitHub
parent ae9aa5ac76
commit 45847a6754
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
73 changed files with 652 additions and 383 deletions

View File

@ -114,70 +114,70 @@ ${content}`
// check build
expect(runCLI(`build ${esbuildParentLib}`)).toContain(
`Successfully ran target build for project ${esbuildParentLib} and 5 tasks it depends on`
`Successfully ran target build for project @proj/${esbuildParentLib} and 5 tasks it depends on`
);
expect(runCLI(`build ${rollupParentLib}`)).toContain(
`Successfully ran target build for project ${rollupParentLib} and 5 tasks it depends on`
`Successfully ran target build for project @proj/${rollupParentLib} and 5 tasks it depends on`
);
expect(runCLI(`build ${swcParentLib}`)).toContain(
`Successfully ran target build for project ${swcParentLib} and 5 tasks it depends on`
`Successfully ran target build for project @proj/${swcParentLib} and 5 tasks it depends on`
);
expect(runCLI(`build ${tscParentLib}`)).toContain(
`Successfully ran target build for project ${tscParentLib} and 5 tasks it depends on`
`Successfully ran target build for project @proj/${tscParentLib} and 5 tasks it depends on`
);
expect(runCLI(`build ${viteParentLib}`)).toContain(
`Successfully ran target build for project ${viteParentLib} and 5 tasks it depends on`
`Successfully ran target build for project @proj/${viteParentLib} and 5 tasks it depends on`
);
// check typecheck
expect(runCLI(`typecheck ${esbuildParentLib}`)).toContain(
`Successfully ran target typecheck for project ${esbuildParentLib} and 5 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 ${rollupParentLib} and 5 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 ${swcParentLib} and 5 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 ${tscParentLib} and 5 tasks it depends on`
`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 ${viteParentLib} and 5 tasks it depends on`
`Successfully ran target typecheck for project @proj/${viteParentLib} and 5 tasks it depends on`
);
// check lint
expect(runCLI(`lint ${esbuildParentLib}`)).toContain(
`Successfully ran target lint for project ${esbuildParentLib}`
`Successfully ran target lint for project @proj/${esbuildParentLib}`
);
expect(runCLI(`lint ${rollupParentLib}`)).toContain(
`Successfully ran target lint for project ${rollupParentLib}`
`Successfully ran target lint for project @proj/${rollupParentLib}`
);
expect(runCLI(`lint ${swcParentLib}`)).toContain(
`Successfully ran target lint for project ${swcParentLib}`
`Successfully ran target lint for project @proj/${swcParentLib}`
);
expect(runCLI(`lint ${tscParentLib}`)).toContain(
`Successfully ran target lint for project ${tscParentLib}`
`Successfully ran target lint for project @proj/${tscParentLib}`
);
expect(runCLI(`lint ${viteParentLib}`)).toContain(
`Successfully ran target lint for project ${viteParentLib}`
`Successfully ran target lint for project @proj/${viteParentLib}`
);
// check test
expect(runCLI(`test ${esbuildParentLib}`)).toContain(
`Successfully ran target test for project ${esbuildParentLib}`
`Successfully ran target test for project @proj/${esbuildParentLib}`
);
expect(runCLI(`test ${rollupParentLib}`)).toContain(
`Successfully ran target test for project ${rollupParentLib}`
`Successfully ran target test for project @proj/${rollupParentLib}`
);
expect(runCLI(`test ${swcParentLib}`)).toContain(
`Successfully ran target test for project ${swcParentLib}`
`Successfully ran target test for project @proj/${swcParentLib}`
);
expect(runCLI(`test ${tscParentLib}`)).toContain(
`Successfully ran target test for project ${tscParentLib}`
`Successfully ran target test for project @proj/${tscParentLib}`
);
expect(runCLI(`test ${viteParentLib}`)).toContain(
`Successfully ran target test for project ${viteParentLib}`
`Successfully ran target test for project @proj/${viteParentLib}`
);
}, 300_000);
});

View File

@ -42,14 +42,14 @@ describe('Nx Plugin (TS solution)', () => {
`generate @nx/plugin:migration packages/${plugin}/src/migrations/update-${migrationVersion}/update-${migrationVersion} --packageVersion=${migrationVersion} --packageJsonUpdates=false`
);
expect(runCLI(`lint ${plugin}`)).toContain(
`Successfully ran target lint for project ${plugin}`
expect(runCLI(`lint @proj/${plugin}`)).toContain(
`Successfully ran target lint for project @proj/${plugin}`
);
expect(runCLI(`typecheck ${plugin}`)).toContain(
`Successfully ran target typecheck for project ${plugin}`
expect(runCLI(`typecheck @proj/${plugin}`)).toContain(
`Successfully ran target typecheck for project @proj/${plugin}`
);
expect(runCLI(`build ${plugin}`)).toContain(
`Successfully ran target build for project ${plugin}`
expect(runCLI(`build @proj/${plugin}`)).toContain(
`Successfully ran target build for project @proj/${plugin}`
);
checkFilesExist(
// entry point
@ -71,11 +71,11 @@ describe('Nx Plugin (TS solution)', () => {
`packages/${plugin}/dist/migrations/update-${migrationVersion}/update-${migrationVersion}.js`,
`packages/${plugin}/dist/migrations/update-${migrationVersion}/update-${migrationVersion}.d.ts`
);
expect(runCLI(`test ${plugin}`)).toContain(
`Successfully ran target test for project ${plugin}`
expect(runCLI(`test @proj/${plugin}`)).toContain(
`Successfully ran target test for project @proj/${plugin}`
);
expect(runCLI(`e2e ${plugin}-e2e`)).toContain(
`Successfully ran target e2e for project ${plugin}-e2e`
expect(runCLI(`e2e @proj/${plugin}-e2e`)).toContain(
`Successfully ran target e2e for project @proj/${plugin}-e2e`
);
}, 90000);

View File

@ -32,13 +32,27 @@ describe('Vite - TS solution setup', () => {
const viteLib = uniq('vite-lib');
const noBundlerLib = uniq('no-bundler-lib');
runCLI(`generate @nx/react:app apps/${reactApp} --bundler=vite`);
runCLI(`generate @nx/js:lib packages/${esbuildLib} --bundler=esbuild`);
runCLI(`generate @nx/js:lib packages/${rollupLib} --bundler=rollup`);
runCLI(`generate @nx/js:lib packages/${swcLib} --bundler=swc`);
runCLI(`generate @nx/js:lib packages/${tscLib} --bundler=tsc`);
runCLI(`generate @nx/js:lib packages/${viteLib} --bundler=vite`);
runCLI(`generate @nx/js:lib packages/${noBundlerLib} --bundler=none`);
runCLI(
`generate @nx/react:app apps/${reactApp} --bundler=vite --e2eTestRunner=none`
);
runCLI(
`generate @nx/js:lib packages/${esbuildLib} --bundler=esbuild --e2eTestRunner=none`
);
runCLI(
`generate @nx/js:lib packages/${rollupLib} --bundler=rollup --e2eTestRunner=none`
);
runCLI(
`generate @nx/js:lib packages/${swcLib} --bundler=swc --e2eTestRunner=none`
);
runCLI(
`generate @nx/js:lib packages/${tscLib} --bundler=tsc --e2eTestRunner=none`
);
runCLI(
`generate @nx/js:lib packages/${viteLib} --bundler=vite --e2eTestRunner=none`
);
runCLI(
`generate @nx/js:lib packages/${noBundlerLib} --bundler=none --e2eTestRunner=none`
);
// import all libs from the app
updateFile(
@ -87,12 +101,12 @@ ${content}`
// check build
expect(runCLI(`build ${reactApp}`)).toContain(
`Successfully ran target build for project ${reactApp} and 5 tasks it depends on`
`Successfully ran target build for project @proj/${reactApp} and 5 tasks it depends on`
);
// check typecheck
expect(runCLI(`typecheck ${reactApp}`)).toContain(
`Successfully ran target typecheck for project ${reactApp} and 6 tasks it depends on`
`Successfully ran target typecheck for project @proj/${reactApp} and 6 tasks it depends on`
);
}, 300_000);
});

View File

@ -50,11 +50,11 @@ describe('Vue Plugin', () => {
`
);
expect(() => runCLI(`lint ${app}`)).not.toThrow();
expect(() => runCLI(`test ${app}`)).not.toThrow();
expect(() => runCLI(`build ${app}`)).not.toThrow();
expect(() => runCLI(`lint ${lib}`)).not.toThrow();
expect(() => runCLI(`test ${lib}`)).not.toThrow();
expect(() => runCLI(`build ${lib}`)).not.toThrow();
expect(() => runCLI(`lint @proj/${app}`)).not.toThrow();
expect(() => runCLI(`test @proj/${app}`)).not.toThrow();
expect(() => runCLI(`build @proj/${app}`)).not.toThrow();
expect(() => runCLI(`lint @proj/${lib}`)).not.toThrow();
expect(() => runCLI(`test @proj/${lib}`)).not.toThrow();
expect(() => runCLI(`build @proj/${lib}`)).not.toThrow();
}, 300_000);
});

View File

@ -202,14 +202,13 @@ export function updateTsConfigForComponentTesting(
tree: Tree,
projectConfig: ProjectConfiguration
) {
const tsConfigPath = joinPathFragments(
projectConfig.root,
projectConfig.projectType === 'library'
? 'tsconfig.lib.json'
: 'tsconfig.app.json'
);
let tsConfigPath: string | null = null;
for (const candidate of ['tsconfig.lib.json', 'tsconfig.app.json']) {
const p = joinPathFragments(projectConfig.root, candidate);
if (tree.exists(p)) tsConfigPath = p;
}
if (tree.exists(tsConfigPath)) {
if (tsConfigPath !== null) {
updateJson(tree, tsConfigPath, (json) => {
const excluded = new Set([
...(json.exclude || []),

View File

@ -4,7 +4,7 @@ export function updateTsConfigsToJs(
tree: Tree,
options: { projectRoot: string }
): void {
let updateConfigPath: string;
let updateConfigPath: string | null = null;
const paths = {
tsConfig: `${options.projectRoot}/tsconfig.json`,
@ -12,19 +12,6 @@ export function updateTsConfigsToJs(
tsConfigApp: `${options.projectRoot}/tsconfig.app.json`,
};
const getProjectType = (tree: Tree) => {
if (tree.exists(paths.tsConfigApp)) {
return 'application';
}
if (tree.exists(paths.tsConfigLib)) {
return 'library';
}
throw new Error(
`project is missing tsconfig.lib.json or tsconfig.app.json`
);
};
updateJson(tree, paths.tsConfig, (json) => {
if (json.compilerOptions) {
json.compilerOptions.allowJs = true;
@ -34,25 +21,30 @@ export function updateTsConfigsToJs(
return json;
});
const projectType = getProjectType(tree);
if (projectType === 'library') {
if (tree.exists(paths.tsConfigLib)) {
updateConfigPath = paths.tsConfigLib;
}
if (projectType === 'application') {
if (tree.exists(paths.tsConfigApp)) {
updateConfigPath = paths.tsConfigApp;
}
updateJson(tree, updateConfigPath, (json) => {
json.include = uniq([...json.include, 'src/**/*.js']);
json.exclude = uniq([
...json.exclude,
'src/**/*.spec.js',
'src/**/*.test.js',
]);
if (updateConfigPath) {
updateJson(tree, updateConfigPath, (json) => {
json.include = uniq([...json.include, 'src/**/*.js']);
json.exclude = uniq([
...json.exclude,
'src/**/*.spec.js',
'src/**/*.test.js',
]);
return json;
});
return json;
});
} else {
throw new Error(
`project is missing tsconfig.lib.json or tsconfig.app.json`
);
}
}
const uniq = <T extends string[]>(value: T) => [...new Set(value)] as T;

View File

@ -39,6 +39,7 @@ import {
} from '../../utils/config-file';
import { hasEslintPlugin } from '../utils/plugin';
import { setupRootEsLint } from './setup-root-eslint';
import { getProjectType } from '@nx/js/src/utils/typescript/ts-solution-setup';
interface LintProjectOptions {
project: string;
@ -99,7 +100,7 @@ export async function lintProjectGeneratorInternal(
lintFilePatterns &&
lintFilePatterns.length &&
!lintFilePatterns.includes('{projectRoot}') &&
isBuildableLibraryProject(projectConfig)
isBuildableLibraryProject(tree, projectConfig)
) {
lintFilePatterns.push(`{projectRoot}/package.json`);
}
@ -175,7 +176,7 @@ export async function lintProjectGeneratorInternal(
// Buildable libs need source analysis enabled for linting `package.json`.
if (
isBuildableLibraryProject(projectConfig) &&
isBuildableLibraryProject(tree, projectConfig) &&
!isJsAnalyzeSourceFilesEnabled(tree)
) {
updateJson(tree, 'nx.json', (json) => {
@ -225,7 +226,7 @@ function createEsLintConfiguration(
const addDependencyChecks =
options.addPackageJsonDependencyChecks ||
isBuildableLibraryProject(projectConfig);
isBuildableLibraryProject(tree, projectConfig);
const overrides: Linter.ConfigOverride<Linter.RulesRecord>[] = useFlatConfig(
tree
@ -328,10 +329,12 @@ function isJsAnalyzeSourceFilesEnabled(tree: Tree): boolean {
}
function isBuildableLibraryProject(
tree: Tree,
projectConfig: ProjectConfiguration
): boolean {
return (
projectConfig.projectType === 'library' &&
getProjectType(tree, projectConfig.root, projectConfig.projectType) ===
'library' &&
projectConfig.targets?.build &&
!!projectConfig.targets.build
);

View File

@ -11,6 +11,7 @@ import {
import { NormalizedSchema, normalizeOptions } from './lib/normalize-options';
import { addImport } from './lib/add-import';
import { dirname, join, parse, relative } from 'path';
import { getProjectType } from '@nx/js/src/utils/typescript/ts-solution-setup';
export async function expoComponentGenerator(host: Tree, schema: Schema) {
const options = await normalizeOptions(host, schema);
@ -46,8 +47,9 @@ function createComponentFiles(host: Tree, options: NormalizedSchema) {
function addExportsToBarrel(host: Tree, options: NormalizedSchema) {
const workspace = getProjects(host);
const proj = workspace.get(options.projectName);
const isApp =
workspace.get(options.projectName).projectType === 'application';
getProjectType(host, proj.root, proj.projectType) === 'application';
if (options.export && !isApp) {
const indexFilePath = joinPathFragments(

View File

@ -4,6 +4,7 @@ import {
type FileExtensionType,
} from '@nx/devkit/src/generators/artifact-name-and-directory-utils';
import { Schema } from '../schema';
import { getProjectType } from '@nx/js/src/utils/typescript/ts-solution-setup';
export interface NormalizedSchema extends Omit<Schema, 'js'> {
directory: string;
@ -38,9 +39,12 @@ export async function normalizeOptions(
const { className } = names(name);
const project = getProjects(host).get(projectName);
const { sourceRoot: projectSourceRoot, projectType } = project;
const { root, sourceRoot: projectSourceRoot, projectType } = project;
if (options.export && projectType === 'application') {
if (
options.export &&
getProjectType(host, root, projectType) === 'application'
) {
logger.warn(
`The "--export" option should not be used with applications and will do nothing.`
);

View File

@ -13,6 +13,7 @@ import {
type JestPresetExtension,
} from '../../../utils/config/config-file';
import type { NormalizedJestProjectSchema } from '../schema';
import { getProjectType } from '@nx/js/src/utils/typescript/ts-solution-setup';
export async function createJestConfig(
tree: Tree,
@ -91,7 +92,13 @@ module.exports = { ...nxPreset };`
}
const jestProjectConfig = `jest.config.${
rootProjectConfig.projectType === 'application' ? 'app' : 'lib'
getProjectType(
tree,
rootProjectConfig.root,
rootProjectConfig.projectType
) === 'application'
? 'app'
: 'lib'
}.${options.js ? 'js' : 'ts'}`;
tree.rename(rootJestPath, jestProjectConfig);

View File

@ -6,12 +6,16 @@ import {
type Tree,
} from '@nx/devkit';
import type { NormalizedJestProjectSchema } from '../schema';
import { getProjectType } from '@nx/js/src/utils/typescript/ts-solution-setup';
export function updateTsConfig(
host: Tree,
options: NormalizedJestProjectSchema
) {
const { root, projectType } = readProjectConfiguration(host, options.project);
const { root, projectType: _projectType } = readProjectConfiguration(
host,
options.project
);
if (!host.exists(joinPathFragments(root, 'tsconfig.json'))) {
throw new Error(
`Expected ${joinPathFragments(
@ -31,6 +35,7 @@ export function updateTsConfig(
}
return json;
});
const projectType = getProjectType(host, root, _projectType);
// fall-back runtime tsconfig file path in case the user didn't provide one
let runtimeTsconfigPath = joinPathFragments(

View File

@ -2146,7 +2146,7 @@ describe('lib', () => {
swcJestConfig.swcrc = false;
export default {
displayName: 'my-lib',
displayName: '@proj/my-lib',
preset: '../jest.preset.js',
transform: {
'^.+\\\\.[tj]s$': ['@swc/jest', swcJestConfig],

View File

@ -68,6 +68,7 @@ import type {
NormalizedLibraryGeneratorOptions,
} from './schema';
import { sortPackageJsonFields } from '../../utils/package-json/sort-fields';
import { getImportPath } from '../../utils/get-import-path';
const defaultOutputDirectory = 'dist';
@ -100,6 +101,12 @@ export async function libraryGeneratorInternal(
);
const options = await normalizeOptions(tree, schema);
// 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(tree, options.projectRoot);
}
createFiles(tree, options);
await configureProject(tree, options);
@ -234,12 +241,6 @@ export async function libraryGeneratorInternal(
);
}
// 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(tree, options.projectRoot);
}
sortPackageJsonFields(tree, options.projectRoot);
if (!options.skipFormat) {
@ -369,8 +370,24 @@ async function configureProject(
delete projectConfiguration.tags;
}
// We want a minimal setup, so unless targets and tags are set, just skip the `nx` property in `package.json`.
if (options.isUsingTsSolutionConfig) {
delete projectConfiguration.projectType;
// SWC executor has logic around sourceRoot and `--strip-leading-paths`. If it is not set then dist will contain the `src` folder rather than being flat.
// TODO(leo): Look at how we can remove the dependency on sourceRoot for SWC.
if (options.bundler !== 'swc') {
delete projectConfiguration.sourceRoot;
}
}
// empty targets are cleaned up automatically by `updateProjectConfiguration`
updateProjectConfiguration(tree, options.name, projectConfiguration);
updateProjectConfiguration(
tree,
options.isUsingTsSolutionConfig
? options.importPath ?? options.name
: options.name,
projectConfiguration
);
} else if (options.config === 'workspace' || options.config === 'project') {
addProjectConfiguration(tree, options.name, projectConfiguration);
} else {
@ -897,7 +914,9 @@ async function normalizeOptions(
return {
...options,
fileName,
name: projectName,
name: isUsingTsSolutionConfig
? getImportPath(tree, projectName)
: projectName,
projectNames,
projectRoot,
parsedTags,

View File

@ -244,3 +244,22 @@ export function addProjectToTsSolutionWorkspace(
}
}
}
export function getProjectType(
tree: Tree,
projectRoot: string,
projectType?: 'library' | 'application'
): 'library' | 'application' {
if (projectType) return projectType;
if (tree.exists(joinPathFragments(projectRoot, 'tsconfig.lib.json')))
return 'library';
if (tree.exists(joinPathFragments(projectRoot, 'tsconfig.app.json')))
return 'application';
// If there are no exports, assume it is an application since both buildable and non-buildable libraries have exports.
const packageJsonPath = joinPathFragments(projectRoot, 'package.json');
const packageJson = tree.exists(packageJsonPath)
? readJson(tree, joinPathFragments(projectRoot, 'package.json'))
: null;
if (!packageJson?.exports) return 'application';
return 'library';
}

View File

@ -7,6 +7,8 @@ import { getNpmScope } from '@nx/js/src/utils/package-json/get-npm-scope';
import type { LibraryGeneratorSchema as JsLibraryGeneratorSchema } from '@nx/js/src/generators/library/schema';
import { Linter } from '@nx/eslint';
import type { LibraryGeneratorOptions, NormalizedOptions } from '../schema';
import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
import { getImportPath } from '@nx/js/src/utils/get-import-path';
export async function normalizeOptions(
tree: Tree,
@ -47,7 +49,9 @@ export async function normalizeOptions(
linter: options.linter ?? Linter.EsLint,
parsedTags,
prefix: getNpmScope(tree), // we could also allow customizing this
projectName,
projectName: isUsingTsSolutionSetup(tree)
? getImportPath(tree, projectName)
: projectName,
projectRoot,
importPath,
service: options.service ?? false,

View File

@ -563,7 +563,6 @@ describe('lib', () => {
"main",
"types",
"exports",
"nx",
"dependencies",
]
`);
@ -580,11 +579,6 @@ describe('lib', () => {
},
"main": "./src/index.ts",
"name": "@proj/mylib",
"nx": {
"name": "mylib",
"projectType": "library",
"sourceRoot": "mylib/src",
},
"private": true,
"types": "./src/index.ts",
"version": "0.0.1",
@ -684,9 +678,6 @@ describe('lib', () => {
"module": "./dist/index.js",
"name": "@proj/mylib",
"nx": {
"name": "mylib",
"projectType": "library",
"sourceRoot": "mylib/src",
"targets": {
"build": {
"executor": "@nx/js:swc",

View File

@ -54,6 +54,13 @@ export async function libraryGenerator(tree: Tree, schema: Schema) {
export async function libraryGeneratorInternal(tree: Tree, schema: Schema) {
const options = await normalizeOptions(tree, schema);
// 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(tree, options.projectRoot);
}
const tasks: GeneratorCallback[] = [];
if (options.publishable === true && !schema.importPath) {
@ -112,12 +119,6 @@ export async function libraryGeneratorInternal(tree: Tree, schema: Schema) {
tasks.push(() => installPackagesTask(tree, true));
}
// 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(tree, options.projectRoot);
}
sortPackageJsonFields(tree, options.projectRoot);
if (!schema.skipFormat) {
@ -163,14 +164,17 @@ async function normalizeOptions(
? options.tags.split(',').map((s) => s.trim())
: [];
const isUsingTsSolutionConfig = isUsingTsSolutionSetup(tree);
return {
...options,
fileName,
projectName,
projectName: isUsingTsSolutionConfig
? getImportPath(tree, projectName)
: projectName,
projectRoot,
parsedTags,
importPath,
isUsingTsSolutionConfig: isUsingTsSolutionSetup(tree),
isUsingTsSolutionConfig,
};
}

View File

@ -190,7 +190,7 @@ async function normalizeOptions(
propertyName,
description,
projectRoot,
projectSourceRoot,
projectSourceRoot: projectSourceRoot ?? join(projectRoot, 'src'),
isTsSolutionSetup: isUsingTsSolutionSetup(tree),
};
}

View File

@ -69,7 +69,7 @@ async function normalizeOptions(
propertyName,
description,
projectRoot,
projectSourceRoot,
projectSourceRoot: projectSourceRoot ?? join(projectRoot, 'src'),
isTsSolutionSetup: isUsingTsSolutionSetup(tree),
};
}

View File

@ -57,7 +57,7 @@ async function normalizeOptions(
name,
description,
projectRoot,
projectSourceRoot,
projectSourceRoot: projectSourceRoot ?? join(projectRoot, 'src'),
isTsSolutionSetup: isUsingTsSolutionSetup(tree),
};

View File

@ -330,6 +330,7 @@ describe('NxPlugin Plugin Generator', () => {
describe('TS solution setup', () => {
beforeEach(() => {
tree = createTreeWithEmptyWorkspace();
tree.write('.gitignore', '');
updateJson(tree, 'package.json', (json) => {
json.workspaces = ['packages/*'];
return json;
@ -371,7 +372,7 @@ describe('NxPlugin Plugin Generator', () => {
swcJestConfig.swcrc = false;
export default {
displayName: 'my-plugin',
displayName: '@proj/my-plugin',
preset: '../jest.preset.js',
testEnvironment: 'node',
transform: {
@ -412,11 +413,119 @@ describe('NxPlugin Plugin Generator', () => {
const projectTargets = readProjectConfiguration(
tree,
'my-plugin'
'@proj/my-plugin'
).targets;
expect(projectTargets.test).toBeDefined();
expect(projectTargets.test?.executor).toEqual('@nx/jest:jest');
});
it('should add project references when using TS solution', async () => {
await pluginGenerator(
tree,
getSchema({
e2eTestRunner: 'jest',
})
);
expect(readJson(tree, 'tsconfig.json').references).toMatchInlineSnapshot(`
[
{
"path": "./my-plugin",
},
{
"path": "./my-plugin-e2e",
},
]
`);
expect(readJson(tree, 'my-plugin/package.json')).toMatchInlineSnapshot(`
{
"dependencies": {
"tslib": "^2.3.0",
},
"exports": {
".": {
"default": "./dist/index.js",
"import": "./dist/index.js",
"types": "./dist/index.d.ts",
},
"./package.json": "./package.json",
},
"main": "./dist/index.js",
"module": "./dist/index.js",
"name": "@proj/my-plugin",
"types": "./dist/index.d.ts",
"version": "0.0.1",
}
`);
expect(readJson(tree, 'my-plugin/tsconfig.json')).toMatchInlineSnapshot(`
{
"extends": "../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json",
},
{
"path": "./tsconfig.spec.json",
},
],
}
`);
expect(readJson(tree, 'my-plugin/tsconfig.lib.json'))
.toMatchInlineSnapshot(`
{
"compilerOptions": {
"baseUrl": ".",
"emitDeclarationOnly": false,
"module": "nodenext",
"moduleResolution": "nodenext",
"outDir": "dist",
"rootDir": "src",
"tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo",
"types": [
"node",
],
},
"exclude": [
"jest.config.ts",
"src/**/*.spec.ts",
"src/**/*.test.ts",
],
"extends": "../tsconfig.base.json",
"include": [
"src/**/*.ts",
],
"references": [],
}
`);
expect(readJson(tree, 'my-plugin/tsconfig.spec.json'))
.toMatchInlineSnapshot(`
{
"compilerOptions": {
"module": "nodenext",
"moduleResolution": "nodenext",
"outDir": "./out-tsc/jest",
"types": [
"jest",
"node",
],
},
"extends": "../tsconfig.base.json",
"include": [
"jest.config.ts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.d.ts",
],
"references": [
{
"path": "./tsconfig.lib.json",
},
],
}
`);
});
});
});

View File

@ -41,11 +41,12 @@ async function addFiles(host: Tree, options: NormalizedSchema) {
}
function updatePluginConfig(host: Tree, options: NormalizedSchema) {
const project = readProjectConfiguration(host, options.name);
const project = readProjectConfiguration(host, options.projectName);
if (project.targets.build) {
if (options.isTsSolutionSetup && options.bundler === 'tsc') {
project.targets.build.options.rootDir = project.sourceRoot;
project.targets.build.options.rootDir =
project.sourceRoot ?? joinPathFragments(project.root, 'src');
project.targets.build.options.generatePackageJson = false;
}
@ -69,7 +70,7 @@ function updatePluginConfig(host: Tree, options: NormalizedSchema) {
);
}
updateProjectConfiguration(host, options.name, project);
updateProjectConfiguration(host, options.projectName, project);
}
}
@ -144,7 +145,7 @@ export async function pluginGeneratorInternal(host: Tree, schema: Schema) {
if (options.e2eTestRunner !== 'none') {
tasks.push(
await e2eProjectGenerator(host, {
pluginName: options.name,
pluginName: options.projectName,
projectDirectory: options.projectDirectory,
pluginOutputPath: joinPathFragments(
'dist',
@ -161,7 +162,7 @@ export async function pluginGeneratorInternal(host: Tree, schema: Schema) {
}
if (options.linter === Linter.EsLint && !options.skipLintChecks) {
await pluginLintCheckGenerator(host, { projectName: options.name });
await pluginLintCheckGenerator(host, { projectName: options.projectName });
}
if (!options.skipFormat) {

View File

@ -10,9 +10,11 @@ import {
} from '@nx/js/src/utils/generator-prompts';
import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
import type { Schema } from '../schema';
import { getImportPath } from '@nx/js/src/utils/get-import-path';
export interface NormalizedSchema extends Schema {
name: string;
projectName: string;
fileName: string;
projectRoot: string;
projectDirectory: string;
@ -71,6 +73,9 @@ export async function normalizeOptions(
bundler: options.compiler ?? 'tsc',
fileName: projectName,
name: projectName,
projectName: isTsSolutionSetup
? getImportPath(host, projectName)
: projectName,
projectRoot,
projectDirectory,
parsedTags,

View File

@ -82,13 +82,14 @@ export async function reactNativeApplicationGeneratorInternal(
options.appProjectRoot,
options.js,
options.skipPackageJson,
options.addPlugin
options.addPlugin,
'tsconfig.app.json'
);
tasks.push(jestTask);
const webTask = await webConfigurationGenerator(host, {
...options,
project: options.name,
project: options.projectName,
skipFormat: true,
});
tasks.push(webTask);

View File

@ -29,13 +29,10 @@ export function addProject(host: Tree, options: NormalizedSchema) {
if (isUsingTsSolutionSetup(host)) {
writeJson(host, joinPathFragments(options.appProjectRoot, 'package.json'), {
name: getImportPath(host, options.name),
name: options.projectName,
version: '0.0.1',
private: true,
nx: {
name: options.name,
projectType: 'application',
sourceRoot: `${options.appProjectRoot}/src`,
targets: hasPlugin ? {} : getTargets(options),
tags: options.parsedTags?.length ? options.parsedTags : undefined,
},

View File

@ -5,6 +5,7 @@ import {
} 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';
import { getImportPath } from '@nx/js/src/utils/get-import-path';
export interface NormalizedSchema extends Schema {
className: string; // app name in class case
@ -56,6 +57,8 @@ export async function normalizeOptions(
const entryFile = options.js ? 'src/main.js' : 'src/main.tsx';
const isTsSolutionSetup = isUsingTsSolutionSetup(host);
return {
...options,
name: projectNames.projectSimpleName,
@ -63,7 +66,9 @@ export async function normalizeOptions(
fileName,
lowerCaseName: className.toLowerCase(),
displayName: options.displayName || className,
projectName: appProjectName,
projectName: isTsSolutionSetup
? getImportPath(host, appProjectName)
: appProjectName,
appProjectRoot,
iosProjectRoot,
androidProjectRoot,
@ -72,6 +77,6 @@ export async function normalizeOptions(
rootProject,
e2eProjectName,
e2eProjectRoot,
isTsSolutionSetup: isUsingTsSolutionSetup(host),
isTsSolutionSetup,
};
}

View File

@ -11,6 +11,7 @@ import { NormalizedSchema, normalizeOptions } from './lib/normalize-options';
import { addImport } from './lib/add-import';
import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript';
import { dirname, join, parse, relative } from 'path';
import { getProjectType } from '@nx/js/src/utils/typescript/ts-solution-setup';
export async function reactNativeComponentGenerator(
host: Tree,
@ -52,8 +53,9 @@ function addExportsToBarrel(host: Tree, options: NormalizedSchema) {
tsModule = ensureTypescript();
}
const workspace = getProjects(host);
const proj = workspace.get(options.projectName);
const isApp =
workspace.get(options.projectName).projectType === 'application';
getProjectType(host, proj.root, proj.projectType) === 'application';
if (options.export && !isApp) {
const indexFilePath = joinPathFragments(

View File

@ -4,6 +4,7 @@ import {
type FileExtensionType,
} from '@nx/devkit/src/generators/artifact-name-and-directory-utils';
import { Schema } from '../schema';
import { getProjectType } from '@nx/js/src/utils/typescript/ts-solution-setup';
export interface NormalizedSchema extends Omit<Schema, 'js'> {
directory: string;
@ -40,9 +41,12 @@ export async function normalizeOptions(
const { className } = names(name);
const { sourceRoot: projectSourceRoot, projectType } = project;
const { root, sourceRoot: projectSourceRoot, projectType } = project;
if (options.export && projectType === 'application') {
if (
options.export &&
getProjectType(host, root, projectType) === 'application'
) {
logger.warn(
`The "--export" option should not be used with applications and will do nothing.`
);

View File

@ -5,6 +5,7 @@ import {
} 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';
import { getImportPath } from '@nx/js/src/utils/get-import-path';
export interface NormalizedSchema extends Schema {
name: string;
@ -44,15 +45,18 @@ export async function normalizeOptions(
? options.tags.split(',').map((s) => s.trim())
: [];
const isUsingTsSolutionConfig = isUsingTsSolutionSetup(host);
const normalized: NormalizedSchema = {
...options,
fileName: projectName,
routePath: `/${projectNames.projectSimpleName}`,
name: projectName,
name: isUsingTsSolutionConfig
? getImportPath(host, projectName)
: projectName,
projectRoot,
parsedTags,
importPath,
isUsingTsSolutionConfig: isUsingTsSolutionSetup(host),
isUsingTsSolutionConfig,
};
return normalized;

View File

@ -101,7 +101,8 @@ export async function reactNativeLibraryGeneratorInternal(
options.projectRoot,
options.js,
options.skipPackageJson,
options.addPlugin
options.addPlugin,
'tsconfig.lib.json'
);
tasks.push(jestTask);
@ -171,14 +172,11 @@ async function addProject(
'package.json'
);
if (options.isUsingTsSolutionConfig) {
writeJson(host, packageJsonPath, {
name: getImportPath(host, options.name),
writeJson(host, joinPathFragments(options.projectRoot, 'package.json'), {
name: options.name,
version: '0.0.1',
...determineEntryFields(options),
nx: {
name: options.name,
sourceRoot: joinPathFragments(options.projectRoot, 'src'),
projectType: 'library',
tags: options.parsedTags?.length ? options.parsedTags : undefined,
},
});

View File

@ -8,7 +8,8 @@ export async function addJest(
appProjectRoot: string,
js: boolean,
skipPackageJson: boolean,
addPlugin: boolean
addPlugin: boolean,
runtimeTsconfigFileName: string
) {
if (unitTestRunner !== 'jest') {
return () => {};
@ -24,6 +25,7 @@ export async function addJest(
skipPackageJson,
skipFormat: true,
addPlugin,
runtimeTsconfigFileName,
});
// overwrite the jest.config.ts file because react native needs to have special transform property

View File

@ -1331,7 +1331,6 @@ describe('app', () => {
"name",
"version",
"private",
"nx",
]
`);
expect(readJson(appTree, 'myapp/tsconfig.json')).toMatchInlineSnapshot(`

View File

@ -72,6 +72,13 @@ export async function applicationGeneratorInternal(
tasks.push(jsInitTask);
const options = await normalizeOptions(tree, schema);
// 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(tree, options.appProjectRoot);
}
showPossibleWarnings(tree, options);
const initTask = await reactInitGenerator(tree, {
@ -179,12 +186,6 @@ export async function applicationGeneratorInternal(
: 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.isUsingTsSolutionConfig) {
addProjectToTsSolutionWorkspace(tree, options.appProjectRoot);
}
sortPackageJsonFields(tree, options.appProjectRoot);
if (!options.skipFormat) {

View File

@ -40,15 +40,14 @@ export function addProject(host: Tree, options: NormalizedSchema) {
if (options.isUsingTsSolutionConfig) {
writeJson(host, joinPathFragments(options.appProjectRoot, 'package.json'), {
name: getImportPath(host, options.name),
name: options.projectName,
version: '0.0.1',
private: true,
nx: {
name: options.name,
projectType: 'application',
sourceRoot: `${options.appProjectRoot}/src`,
tags: options.parsedTags?.length ? options.parsedTags : undefined,
},
nx: options.parsedTags?.length
? {
tags: options.parsedTags,
}
: undefined,
});
}

View File

@ -7,6 +7,7 @@ import { assertValidStyle } from '../../../utils/assertion';
import { NormalizedSchema, Schema } from '../schema';
import { findFreePort } from './find-free-port';
import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
import { getImportPath } from '@nx/js/src/utils/get-import-path';
export function normalizeDirectory(options: Schema) {
options.directory = options.directory?.replace(/\\{1,2}/g, '/');
@ -57,10 +58,13 @@ export async function normalizeOptions<T extends Schema = Schema>(
assertValidStyle(options.style);
const isUsingTsSolutionConfig = isUsingTsSolutionSetup(host);
const normalized = {
...options,
name: names(options.name).fileName,
projectName: appProjectName,
name: appProjectName,
projectName: isUsingTsSolutionConfig
? getImportPath(host, appProjectName)
: appProjectName,
appProjectRoot,
e2eProjectName,
e2eProjectRoot,
@ -68,7 +72,7 @@ export async function normalizeOptions<T extends Schema = Schema>(
fileName,
styledModule,
hasStyles: options.style !== 'none',
isUsingTsSolutionConfig: isUsingTsSolutionSetup(host),
isUsingTsSolutionConfig,
} as NormalizedSchema;
normalized.routing = normalized.routing ?? false;

View File

@ -4,6 +4,7 @@ import { determineArtifactNameAndDirectoryOptions } from '@nx/devkit/src/generat
import { assertValidStyle } from '../../../utils/assertion';
import { NormalizedSchema, Schema } from '../schema';
import { getProjectType } from '@nx/js/src/utils/typescript/ts-solution-setup';
export async function normalizeOptions(
tree: Tree,
@ -41,7 +42,10 @@ export async function normalizeOptions(
? null
: options.style;
if (options.export && projectType === 'application') {
if (
options.export &&
getProjectType(tree, projectRoot, projectType) === 'application'
) {
logger.warn(
`The "--export" option should not be used with applications and will do nothing.`
);

View File

@ -17,6 +17,7 @@ import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript'
import { join } from 'path';
import { addImport } from '../../utils/ast-utils';
import { Schema } from './schema';
import { getProjectType } from '@nx/js/src/utils/typescript/ts-solution-setup';
interface NormalizedSchema extends Omit<Schema, 'js'> {
projectSourceRoot: string;
@ -127,9 +128,12 @@ async function normalizeOptions(
const hookTypeName = names(hookName).className;
const project = getProjects(host).get(projectName);
const { sourceRoot: projectSourceRoot, projectType } = project;
const { root, sourceRoot: projectSourceRoot, projectType } = project;
if (options.export && projectType === 'application') {
if (
options.export &&
getProjectType(host, root, projectType) === 'application'
) {
logger.warn(
`The "--export" option should not be used with applications and will do nothing.`
);

View File

@ -1,4 +1,3 @@
import * as devkit from '@nx/devkit';
import { Tree, updateJson, writeJson } from '@nx/devkit';
import { ProjectGraph, readJson } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';

View File

@ -64,7 +64,7 @@ export async function hostGenerator(
const initTask = await applicationGenerator(host, {
...options,
directory: options.appProjectRoot,
name: options.projectName,
name: options.name,
// The target use-case is loading remotes as child routes, thus always enable routing.
routing: true,
skipFormat: true,

View File

@ -70,7 +70,7 @@ export async function normalizeOptions(
bundler,
fileName,
routePath: `/${projectNames.projectSimpleName}`,
name: projectName,
name: isUsingTsSolutionConfig ? importPath : projectName,
projectRoot,
parsedTags,
importPath,

View File

@ -932,7 +932,7 @@ module.exports = withNx(
beforeEach(() => {
tree = createTreeWithEmptyWorkspace();
updateJson(tree, 'package.json', (json) => {
json.workspaces = ['packages/*', 'apps/*'];
json.workspaces = ['packages/*', 'apps/*', 'libs/*'];
return json;
});
writeJson(tree, 'tsconfig.base.json', {
@ -953,11 +953,11 @@ module.exports = withNx(
...defaultSchema,
bundler: 'vite',
unitTestRunner: 'vitest',
directory: 'mylib',
name: 'mylib',
directory: 'libs/mylib',
});
expect(tree.read('mylib/vite.config.ts', 'utf-8')).toMatchInlineSnapshot(`
expect(tree.read('libs/mylib/vite.config.ts', 'utf-8'))
.toMatchInlineSnapshot(`
"/// <reference types='vitest' />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
@ -966,7 +966,7 @@ module.exports = withNx(
export default defineConfig({
root: __dirname,
cacheDir: '../node_modules/.vite/mylib',
cacheDir: '../../node_modules/.vite/libs/mylib',
plugins: [react(), dts({ entryRoot: 'src', tsconfigPath: path.join(__dirname, 'tsconfig.lib.json') })],
// Uncomment this if you are using workers.
// worker: {
@ -984,7 +984,7 @@ module.exports = withNx(
lib: {
// Could also be a dictionary or array of multiple entry points.
entry: 'src/index.ts',
name: 'mylib',
name: '@proj/mylib',
fileName: 'index',
// Change this to the formats you want to support.
// Don't forget to update your package.json as well.
@ -1013,12 +1013,12 @@ module.exports = withNx(
expect(readJson(tree, 'tsconfig.json').references).toMatchInlineSnapshot(`
[
{
"path": "./mylib",
"path": "./libs/mylib",
},
]
`);
// Make sure keys are in idiomatic order
expect(Object.keys(readJson(tree, 'mylib/package.json')))
expect(Object.keys(readJson(tree, 'libs/mylib/package.json')))
.toMatchInlineSnapshot(`
[
"name",
@ -1028,12 +1028,11 @@ module.exports = withNx(
"module",
"types",
"exports",
"nx",
]
`);
expect(readJson(tree, 'mylib/tsconfig.json')).toMatchInlineSnapshot(`
expect(readJson(tree, 'libs/mylib/tsconfig.json')).toMatchInlineSnapshot(`
{
"extends": "../tsconfig.base.json",
"extends": "../../tsconfig.base.json",
"files": [],
"include": [],
"references": [
@ -1046,7 +1045,8 @@ module.exports = withNx(
],
}
`);
expect(readJson(tree, 'mylib/tsconfig.lib.json')).toMatchInlineSnapshot(`
expect(readJson(tree, 'libs/mylib/tsconfig.lib.json'))
.toMatchInlineSnapshot(`
{
"compilerOptions": {
"jsx": "react-jsx",
@ -1089,7 +1089,7 @@ module.exports = withNx(
"eslint.config.cjs",
"eslint.config.mjs",
],
"extends": "../tsconfig.base.json",
"extends": "../../tsconfig.base.json",
"include": [
"src/**/*.js",
"src/**/*.jsx",
@ -1098,7 +1098,8 @@ module.exports = withNx(
],
}
`);
expect(readJson(tree, 'mylib/tsconfig.spec.json')).toMatchInlineSnapshot(`
expect(readJson(tree, 'libs/mylib/tsconfig.spec.json'))
.toMatchInlineSnapshot(`
{
"compilerOptions": {
"jsx": "react-jsx",
@ -1113,7 +1114,7 @@ module.exports = withNx(
"vitest",
],
},
"extends": "../tsconfig.base.json",
"extends": "../../tsconfig.base.json",
"include": [
"vite.config.ts",
"vite.config.mts",
@ -1143,20 +1144,18 @@ module.exports = withNx(
...defaultSchema,
bundler: 'none',
unitTestRunner: 'none',
directory: 'mylib',
name: 'mylib',
directory: 'libs/mylib',
});
await libraryGenerator(tree, {
...defaultSchema,
bundler: 'none',
unitTestRunner: 'none',
directory: 'myjslib',
name: 'myjslib',
directory: 'libs/myjslib',
js: true,
});
expect(readJson(tree, 'mylib/package.json')).toMatchInlineSnapshot(`
expect(readJson(tree, 'libs/mylib/package.json')).toMatchInlineSnapshot(`
{
"exports": {
".": {
@ -1168,16 +1167,12 @@ module.exports = withNx(
},
"main": "./src/index.ts",
"name": "@proj/mylib",
"nx": {
"name": "mylib",
"projectType": "library",
"sourceRoot": "mylib/src",
},
"types": "./src/index.ts",
"version": "0.0.1",
}
`);
expect(readJson(tree, 'myjslib/package.json')).toMatchInlineSnapshot(`
expect(readJson(tree, 'libs/myjslib/package.json'))
.toMatchInlineSnapshot(`
{
"exports": {
".": "./src/index.js",
@ -1185,11 +1180,6 @@ module.exports = withNx(
},
"main": "./src/index.js",
"name": "@proj/myjslib",
"nx": {
"name": "myjslib",
"projectType": "library",
"sourceRoot": "myjslib/src",
},
"types": "./src/index.js",
"version": "0.0.1",
}
@ -1201,11 +1191,10 @@ module.exports = withNx(
...defaultSchema,
bundler: 'rollup',
unitTestRunner: 'none',
directory: 'mylib',
name: 'mylib',
directory: 'libs/mylib',
});
expect(tree.read('mylib/rollup.config.cjs', 'utf-8'))
expect(tree.read('libs/mylib/rollup.config.cjs', 'utf-8'))
.toMatchInlineSnapshot(`
"const { withNx } = require('@nx/rollup/with-nx');
const url = require('@rollup/plugin-url');
@ -1242,13 +1231,12 @@ module.exports = withNx(
await libraryGenerator(tree, {
...defaultSchema,
bundler: 'rollup',
directory: 'mylib',
name: 'mylib',
directory: 'libs/mylib',
publishable: true,
importPath: '@acme/mylib',
});
expect(readJson(tree, 'mylib/package.json')).toMatchInlineSnapshot(`
expect(readJson(tree, 'libs/mylib/package.json')).toMatchInlineSnapshot(`
{
"exports": {
".": {
@ -1265,11 +1253,6 @@ module.exports = withNx(
"main": "./dist/index.esm.js",
"module": "./dist/index.esm.js",
"name": "@acme/mylib",
"nx": {
"name": "mylib",
"projectType": "library",
"sourceRoot": "mylib/src",
},
"type": "module",
"types": "./dist/index.esm.d.ts",
"version": "0.0.1",

View File

@ -55,6 +55,11 @@ export async function libraryGeneratorInternal(host: Tree, schema: Schema) {
tasks.push(jsInitTask);
const options = await normalizeOptions(host, schema);
if (options.isUsingTsSolutionConfig) {
addProjectToTsSolutionWorkspace(host, options.projectRoot);
}
if (options.publishable === true && !schema.importPath) {
throw new Error(
`For publishable libs you have to provide a proper "--importPath" which needs to be a valid npm package name (e.g. my-awesome-lib or @myorg/my-lib)`
@ -75,12 +80,11 @@ export async function libraryGeneratorInternal(host: Tree, schema: Schema) {
name: options.importPath,
version: '0.0.1',
...determineEntryFields(options),
nx: {
name: options.importPath === options.name ? undefined : options.name,
projectType: 'library',
sourceRoot: `${options.projectRoot}/src`,
tags: options.parsedTags?.length ? options.parsedTags : undefined,
},
nx: options.parsedTags?.length
? {
tags: options.parsedTags,
}
: undefined,
files: options.publishable ? ['dist', '!**/*.tsbuildinfo'] : undefined,
});
} else {
@ -273,10 +277,6 @@ export async function libraryGeneratorInternal(host: Tree, schema: Schema) {
: undefined
);
if (options.isUsingTsSolutionConfig) {
addProjectToTsSolutionWorkspace(host, options.projectRoot);
}
sortPackageJsonFields(host, options.projectRoot);
if (!options.skipFormat) {

View File

@ -20,6 +20,7 @@ import {
import { getRootTsConfigPathInTree } from '@nx/js';
import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript';
import { determineArtifactNameAndDirectoryOptions } from '@nx/devkit/src/generators/artifact-name-and-directory-utils';
import { getProjectType } from '@nx/js/src/utils/typescript/ts-solution-setup';
let tsModule: typeof import('typescript');
@ -165,14 +166,14 @@ async function normalizeOptions(
const projects = getProjects(host);
const project = projects.get(projectName);
const { sourceRoot, projectType } = project;
const { root, sourceRoot, projectType } = project;
const tsConfigJson = readJson(host, getRootTsConfigPathInTree(host));
const tsPaths: { [module: string]: string[] } = tsConfigJson.compilerOptions
? tsConfigJson.compilerOptions.paths || {}
: {};
const modulePath =
projectType === 'application'
getProjectType(host, root, projectType) === 'application'
? options.path
? `./app/${options.path}/${extraNames.fileName}.slice`
: `./app/${extraNames.fileName}.slice`

View File

@ -111,10 +111,10 @@ export async function remoteGenerator(host: Tree, schema: Schema) {
if (options.dynamic) {
// Dynamic remotes generate with library { type: 'var' } by default.
// We need to ensure that the remote name is a valid variable name.
const isValidRemote = isValidVariable(options.projectName);
const isValidRemote = isValidVariable(options.name);
if (!isValidRemote.isValid) {
throw new Error(
`Invalid remote name provided: ${options.projectName}. ${isValidRemote.message}`
`Invalid remote name provided: ${options.name}. ${isValidRemote.message}`
);
}
}
@ -122,9 +122,9 @@ export async function remoteGenerator(host: Tree, schema: Schema) {
await ensureProjectName(host, options, 'application');
const REMOTE_NAME_REGEX = '^[a-zA-Z_$][a-zA-Z_$0-9]*$';
const remoteNameRegex = new RegExp(REMOTE_NAME_REGEX);
if (!remoteNameRegex.test(options.projectName)) {
if (!remoteNameRegex.test(options.name)) {
throw new Error(
stripIndents`Invalid remote name: ${options.projectName}. Remote project names must:
stripIndents`Invalid remote name: ${options.name}. Remote project names must:
- Start with a letter, dollar sign ($) or underscore (_)
- Followed by any valid character (letters, digits, underscores, or dollar signs)
The regular expression used is ${REMOTE_NAME_REGEX}.`
@ -132,14 +132,14 @@ export async function remoteGenerator(host: Tree, schema: Schema) {
}
const initAppTask = await applicationGenerator(host, {
...options,
name: options.projectName,
name: options.name,
skipFormat: true,
alwaysGenerateProjectJson: true,
});
tasks.push(initAppTask);
if (schema.host) {
updateHostWithRemote(host, schema.host, options.projectName);
if (options.host) {
updateHostWithRemote(host, options.host, options.name);
}
// Module federation requires bootstrap code to be dynamically imported.

View File

@ -19,6 +19,7 @@ import { basename, join } from 'path';
import { minimatch } from 'minimatch';
import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript';
import { nxVersion } from '../../utils/versions';
import { getProjectType } from '@nx/js/src/utils/typescript/ts-solution-setup';
let tsModule: typeof import('typescript');
@ -35,7 +36,7 @@ export async function projectRootPath(
config: ProjectConfiguration
): Promise<string> {
let projectDir: string;
if (config.projectType === 'application') {
if (getProjectType(tree, config.root, config.projectType) === 'application') {
const isNextJs = await isNextJsProject(tree, config);
if (isNextJs) {
// Next.js apps

View File

@ -379,10 +379,8 @@ describe('Remix Application', () => {
"engines": {
"node": ">=20",
},
"name": "myapp",
"name": "@proj/myapp",
"nx": {
"projectType": "application",
"sourceRoot": "myapp",
"tags": [
"foo",
],
@ -541,6 +539,41 @@ describe('Remix Application', () => {
`);
});
it('should skip nx property in package.json when no tags are provided', async () => {
await applicationGenerator(tree, {
directory: 'apps/myapp',
e2eTestRunner: 'playwright',
unitTestRunner: 'jest',
addPlugin: true,
});
expect(readJson(tree, 'apps/myapp/package.json')).toMatchInlineSnapshot(`
{
"dependencies": {
"@remix-run/node": "^2.14.0",
"@remix-run/react": "^2.14.0",
"@remix-run/serve": "^2.14.0",
"isbot": "^4.4.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
},
"devDependencies": {
"@remix-run/dev": "^2.14.0",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
},
"engines": {
"node": ">=20",
},
"name": "@proj/myapp",
"private": true,
"scripts": {},
"sideEffects": false,
"type": "module",
}
`);
});
it('should generate valid package.json without formatting', async () => {
await applicationGenerator(tree, {
directory: 'myapp',

View File

@ -12,12 +12,10 @@ import {
Tree,
updateJson,
updateProjectConfiguration,
visitNotIgnoredFiles,
} from '@nx/devkit';
import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command';
import { initGenerator as jsInitGenerator } from '@nx/js';
import { extractTsConfigBase } from '@nx/js/src/utils/typescript/create-ts-config';
import { dirname } from 'node:path';
import {
createNxCloudOnboardingURLForWelcomeApp,
getNxCloudAppOnBoardingUrl,
@ -39,14 +37,13 @@ import initGenerator from '../init/init';
import { updateDependencies } from '../utils/update-dependencies';
import {
addE2E,
addViteTempFilesToGitIgnore,
normalizeOptions,
updateUnitTestConfig,
addViteTempFilesToGitIgnore,
} from './lib';
import { NxRemixGeneratorSchema } from './schema';
import {
addProjectToTsSolutionWorkspace,
isUsingTsSolutionSetup,
updateTsconfigFiles,
} from '@nx/js/src/utils/typescript/ts-solution-setup';
import { sortPackageJsonFields } from '@nx/js/src/utils/package-json/sort-fields';
@ -85,9 +82,13 @@ export async function remixApplicationGeneratorInternal(
);
}
const isUsingTsSolution = isUsingTsSolutionSetup(tree);
// 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(tree, options.projectRoot);
}
if (!isUsingTsSolution) {
if (!options.isUsingTsSolutionConfig) {
addProjectConfiguration(tree, options.projectName, {
root: options.projectRoot,
sourceRoot: `${options.projectRoot}`,
@ -122,7 +123,6 @@ export async function remixApplicationGeneratorInternal(
eslintVersion,
typescriptVersion,
viteVersion,
isUsingTsSolution,
};
generateFiles(
@ -154,7 +154,7 @@ export async function remixApplicationGeneratorInternal(
);
}
if (isUsingTsSolution) {
if (options.isUsingTsSolutionConfig) {
generateFiles(
tree,
joinPathFragments(__dirname, 'files/ts-solution'),
@ -328,12 +328,6 @@ 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);
}
sortPackageJsonFields(tree, options.projectRoot);
if (!options.skipFormat) {

View File

@ -19,13 +19,8 @@
"engines": {
"node": ">=20"
},
"sideEffects": false<% if (isUsingTsSolution) { %>,
"sideEffects": false<% if (isUsingTsSolutionConfig && parsedTags?.length) { %>,
"nx": {
<%_ if (name !== projectName) { _%>
"name": "<%= name %>",<%_ } _%>
"projectType": "application",
"sourceRoot": "<%- projectRoot %>"<%_ if (parsedTags?.length) { _%>,
"tags": <%- JSON.stringify(parsedTags) %>
<%_ } _%>
}<% } %>
}

View File

@ -6,6 +6,7 @@ import {
import { Linter } from '@nx/eslint';
import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
import { type NxRemixGeneratorSchema } from '../schema';
import { getImportPath } from '@nx/js/src/utils/get-import-path';
export interface NormalizedSchema extends NxRemixGeneratorSchema {
projectName: string;
@ -13,6 +14,7 @@ export interface NormalizedSchema extends NxRemixGeneratorSchema {
e2eProjectName: string;
e2eProjectRoot: string;
parsedTags: string[];
isUsingTsSolutionConfig: boolean;
}
export async function normalizeOptions(
@ -43,14 +45,18 @@ export async function normalizeOptions(
? options.tags.split(',').map((s) => s.trim())
: [];
const isUsingTsSolutionConfig = isUsingTsSolutionSetup(tree);
return {
...options,
linter: options.linter ?? Linter.EsLint,
projectName,
projectName: isUsingTsSolutionConfig
? getImportPath(tree, projectName)
: projectName,
projectRoot,
e2eProjectName,
e2eProjectRoot,
parsedTags,
useTsSolution: options.useTsSolution ?? isUsingTsSolutionSetup(tree),
useTsSolution: options.useTsSolution ?? isUsingTsSolutionConfig,
isUsingTsSolutionConfig,
};
}

View File

@ -16,7 +16,7 @@ export function addTsconfigEntryPoints(
tree,
options.projectName
);
const serverFilePath = joinPathFragments(sourceRoot, 'server.ts');
const serverFilePath = joinPathFragments(sourceRoot ?? 'src', 'server.ts');
tree.write(
serverFilePath,

View File

@ -5,6 +5,7 @@ import {
} 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';
import { getImportPath } from '@nx/js/src/utils/get-import-path';
export interface RemixLibraryOptions extends NxRemixGeneratorSchema {
projectName: string;
@ -32,11 +33,14 @@ export async function normalizeOptions(
nxJson.useInferencePlugins !== false;
options.addPlugin ??= addPluginDefault;
const isUsingTsSolutionConfig = isUsingTsSolutionSetup(tree);
return {
...options,
unitTestRunner: options.unitTestRunner ?? 'vitest',
projectName,
projectName: isUsingTsSolutionConfig
? getImportPath(tree, projectName)
: projectName,
projectRoot,
isUsingTsSolutionConfig: isUsingTsSolutionSetup(tree),
isUsingTsSolutionConfig,
};
}

View File

@ -173,7 +173,6 @@ describe('Remix Library Generator', () => {
"main",
"types",
"exports",
"nx",
]
`);
expect(readJson(tree, 'packages/foo/package.json'))
@ -189,15 +188,27 @@ describe('Remix Library Generator', () => {
},
"main": "./src/index.ts",
"name": "@proj/foo",
"nx": {
"name": "foo",
"projectType": "library",
"sourceRoot": "packages/foo/src",
},
"types": "./src/index.ts",
"version": "0.0.1",
}
`);
});
it('should generate server entrypoint', async () => {
await libraryGenerator(tree, {
directory: 'test',
style: 'css',
addPlugin: true,
});
expect(tree.exists(`test/src/server.ts`));
expect(tree.children(`test/src/lib`).sort()).toMatchInlineSnapshot(`
[
"test.module.css",
"test.spec.tsx",
"test.tsx",
]
`);
}, 25_000);
});
});

View File

@ -29,6 +29,10 @@ export async function remixLibraryGeneratorInternal(
const tasks: GeneratorCallback[] = [];
const options = await normalizeOptions(tree, schema);
if (options.isUsingTsSolutionConfig) {
addProjectToTsSolutionWorkspace(tree, options.projectRoot);
}
const jsInitTask = await jsInitGenerator(tree, {
js: options.js,
skipFormat: true,
@ -77,10 +81,6 @@ export async function remixLibraryGeneratorInternal(
: undefined
);
if (options.isUsingTsSolutionConfig) {
addProjectToTsSolutionWorkspace(tree, options.projectRoot);
}
sortPackageJsonFields(tree, options.projectRoot);
if (!options.skipFormat) {

View File

@ -20,6 +20,7 @@ import {
import { editTsConfig } from '../application/lib/create-ts-config';
import rspackInitGenerator from '../init/init';
import { ConfigurationSchema } from './schema';
import { getProjectType } from '@nx/js/src/utils/typescript/ts-solution-setup';
export async function configurationGenerator(
tree: Tree,
@ -73,7 +74,7 @@ export async function configurationGenerator(
if (
alreadyHasNxRspackTargets.build &&
(alreadyHasNxRspackTargets.serve ||
projectType === 'library' ||
getProjectType(tree, options.project, projectType) === 'library' ||
options.framework === 'nest')
) {
throw new Error(

View File

@ -825,7 +825,7 @@ describe('@nx/storybook:configuration for Storybook v7', () => {
});
await configurationGenerator(tree, {
project: 'mylib',
project: '@proj/mylib',
standaloneConfig: false,
uiFramework: '@storybook/react-vite',
addPlugin: true,

View File

@ -47,6 +47,7 @@ import {
import { interactionTestsDependencies } from './lib/interaction-testing.utils';
import { ensureDependencies } from './lib/ensure-dependencies';
import { editRootTsConfig } from './lib/edit-root-tsconfig';
import { getProjectType } from '@nx/js/src/utils/typescript/ts-solution-setup';
export function configurationGenerator(
tree: Tree,
@ -126,7 +127,8 @@ export async function configurationGeneratorInternal(
);
const mainDir =
!!nextConfigFilePath && projectType === 'application'
!!nextConfigFilePath &&
getProjectType(tree, root, projectType) === 'application'
? 'components'
: 'src';

View File

@ -30,6 +30,7 @@ import { findEslintFile } from '@nx/eslint/src/generators/utils/eslint-file';
import { useFlatConfig } from '@nx/eslint/src/utils/flat-config';
import {
findRuntimeTsConfigName,
getProjectType,
isUsingTsSolutionSetup,
} from '@nx/js/src/utils/typescript/ts-solution-setup';
@ -581,7 +582,8 @@ export function createProjectStorybookDir(
usesReactNative?: boolean
) {
let projectDirectory =
projectType === 'application'
getProjectType(tree, root, projectType as 'application' | 'library') ===
'application'
? isNextJs
? 'components'
: 'src/app'
@ -616,7 +618,10 @@ export function createProjectStorybookDir(
projectType,
interactionTests,
mainDir,
isNextJs: isNextJs && projectType === 'application',
isNextJs:
isNextJs &&
getProjectType(tree, root, projectType as 'application' | 'library') ===
'application',
usesSwc,
usesVite,
isRootProject: projectIsRootProjectInStandaloneWorkspace,
@ -651,7 +656,7 @@ export function getTsConfigPath(
root,
path?.length > 0
? path
: projectType === 'application'
: getProjectType(tree, root, projectType) === 'application'
? 'tsconfig.app.json'
: 'tsconfig.lib.json'
);

View File

@ -15,7 +15,10 @@ import {
initGenerator as jsInitGenerator,
} from '@nx/js';
import { getImportPath } from '@nx/js/src/utils/get-import-path';
import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
import {
getProjectType,
isUsingTsSolutionSetup,
} from '@nx/js/src/utils/typescript/ts-solution-setup';
import { join } from 'node:path/posix';
import type { PackageJson } from 'nx/src/utils/package-json';
import { ensureDependencies } from '../../utils/ensure-dependencies';
@ -50,8 +53,11 @@ export async function viteConfigurationGeneratorInternal(
const projectConfig = readProjectConfiguration(tree, schema.project);
const { targets, root: projectRoot } = projectConfig;
const projectType =
schema.projectType ?? projectConfig.projectType ?? 'library';
const projectType = getProjectType(
tree,
projectConfig.root,
schema.projectType ?? projectConfig.projectType
);
schema.includeLib ??= projectType === 'library';
@ -196,7 +202,7 @@ function updatePackageJson(
name: getImportPath(tree, options.project),
version: '0.0.1',
};
if (projectType === 'application') {
if (getProjectType(tree, project.root, projectType) === 'application') {
packageJson.private = true;
}
}

View File

@ -16,12 +16,13 @@ import {
handleUnknownConfiguration,
moveAndEditIndexHtml,
} from '../../../utils/generator-utils';
import { getProjectType } from '@nx/js/src/utils/typescript/ts-solution-setup';
export async function convertNonVite(
tree: Tree,
schema: ViteConfigurationGeneratorSchema,
projectRoot: string,
projectType: string,
_projectType: string,
targets: {
[targetName: string]: TargetConfiguration<any>;
}
@ -34,6 +35,11 @@ export async function convertNonVite(
// Check if it has webpack
const hasWebpackConfig = findWebpackConfig(tree, projectRoot);
const projectType = getProjectType(
tree,
projectRoot,
_projectType as 'application' | 'library'
);
if (hasWebpackConfig) {
if (projectType === 'application') {
moveAndEditIndexHtml(tree, schema);

View File

@ -15,7 +15,10 @@ import {
updateNxJson,
} from '@nx/devkit';
import { initGenerator as jsInitGenerator } from '@nx/js';
import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
import {
getProjectType,
isUsingTsSolutionSetup,
} from '@nx/js/src/utils/typescript/ts-solution-setup';
import { join } from 'path';
import { ensureDependencies } from '../../utils/ensure-dependencies';
import {
@ -118,7 +121,7 @@ getTestBed().initTestEnvironment(
tree,
{
project: schema.project,
includeLib: projectType === 'library',
includeLib: getProjectType(tree, root, projectType) === 'library',
includeVitest: true,
inSourceTests: schema.inSourceTests,
rollupOptionsExternal: [
@ -142,7 +145,7 @@ getTestBed().initTestEnvironment(
{
...schema,
includeVitest: true,
includeLib: projectType === 'library',
includeLib: getProjectType(tree, root, projectType) === 'library',
},
true
);
@ -265,7 +268,9 @@ function updateTsConfig(
let runtimeTsconfigPath = joinPathFragments(
projectRoot,
projectType === 'application' ? 'tsconfig.app.json' : 'tsconfig.lib.json'
getProjectType(tree, projectRoot, projectType) === 'application'
? 'tsconfig.app.json'
: 'tsconfig.lib.json'
);
if (options.runtimeTsconfigFileName) {
runtimeTsconfigPath = joinPathFragments(

View File

@ -52,6 +52,13 @@ export async function applicationGeneratorInternal(
);
const options = await normalizeOptions(tree, _options);
// 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(tree, options.appProjectRoot);
}
const nxJson = readNxJson(tree);
options.addPlugin ??=
@ -60,15 +67,14 @@ export async function applicationGeneratorInternal(
if (options.isUsingTsSolutionConfig) {
writeJson(tree, joinPathFragments(options.appProjectRoot, 'package.json'), {
name: getImportPath(tree, options.name),
name: options.projectName,
version: '0.0.1',
private: true,
nx: {
name: options.name,
projectType: 'application',
sourceRoot: `${options.appProjectRoot}/src`,
tags: options.parsedTags?.length ? options.parsedTags : undefined,
},
nx: options.parsedTags?.length
? {
tags: options.parsedTags,
}
: undefined,
});
} else {
addProjectConfiguration(tree, options.projectName, {
@ -108,6 +114,7 @@ export async function applicationGeneratorInternal(
setParserOptionsProject: options.setParserOptionsProject,
rootProject: options.rootProject,
addPlugin: options.addPlugin,
projectName: options.projectName,
},
'app'
)
@ -146,12 +153,6 @@ export async function applicationGeneratorInternal(
);
}
// 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(tree, options.appProjectRoot);
}
sortPackageJsonFields(tree, options.appProjectRoot);
if (!options.skipFormat) await formatFiles(tree);

View File

@ -5,6 +5,7 @@ import {
} from '@nx/devkit/src/generators/project-name-and-root-utils';
import { NormalizedSchema, Schema } from '../schema';
import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
import { getImportPath } from '@nx/js/src/utils/get-import-path';
export async function normalizeOptions(
host: Tree,
@ -27,20 +28,24 @@ export async function normalizeOptions(
? options.tags.split(',').map((s) => s.trim())
: [];
const isUsingTsSolutionConfig = isUsingTsSolutionSetup(host);
const normalized = {
...options,
projectName: appProjectName,
projectName: isUsingTsSolutionConfig
? getImportPath(host, appProjectName)
: appProjectName,
appProjectRoot,
e2eProjectName,
e2eProjectRoot,
parsedTags,
isUsingTsSolutionConfig,
} as NormalizedSchema;
normalized.style = options.style ?? 'css';
normalized.routing = normalized.routing ?? false;
normalized.unitTestRunner ??= 'vitest';
normalized.e2eTestRunner = normalized.e2eTestRunner ?? 'playwright';
normalized.isUsingTsSolutionConfig = isUsingTsSolutionSetup(host);
normalized.bundler = normalized.bundler ?? 'vite';
return normalized;

View File

@ -12,6 +12,7 @@ import { determineArtifactNameAndDirectoryOptions } from '@nx/devkit/src/generat
import { NormalizedSchema, ComponentGeneratorSchema } from '../schema';
import { addImport } from '../../../utils/ast-utils';
import { getProjectType } from '@nx/js/src/utils/typescript/ts-solution-setup';
let tsModule: typeof import('typescript');
@ -33,9 +34,12 @@ export async function normalizeOptions(
let { className } = names(fileName);
const componentFileName = fileName;
const project = getProjects(host).get(projectName);
const { sourceRoot: projectSourceRoot, projectType } = project;
const { root, sourceRoot: projectSourceRoot, projectType } = project;
if (options.export && projectType === 'application') {
if (
options.export &&
getProjectType(host, root, projectType) === 'application'
) {
logger.warn(
`The "--export" option should not be used with applications and will do nothing.`
);
@ -60,12 +64,13 @@ export function addExportsToBarrel(host: Tree, options: NormalizedSchema) {
tsModule = ensureTypescript();
}
const workspace = getProjects(host);
const proj = workspace.get(options.projectName);
const isApp =
workspace.get(options.projectName).projectType === 'application';
getProjectType(host, proj.root, proj.projectType) === 'application';
if (options.export && !isApp) {
const indexFilePath = joinPathFragments(
options.projectSourceRoot,
options.projectSourceRoot ?? 'src',
options.js ? 'index.js' : 'index.ts'
);
const indexSource = host.read(indexFilePath, 'utf-8');

View File

@ -18,7 +18,7 @@ export async function addVite(
ensurePackage<typeof import('@nx/vite')>('@nx/vite', nxVersion);
const viteTask = await viteConfigurationGenerator(tree, {
uiFramework: 'none',
project: options.name,
project: options.projectName,
newProject: true,
includeLib: true,
inSourceTests: options.inSourceTests,
@ -32,7 +32,7 @@ export async function addVite(
createOrEditViteConfig(
tree,
{
project: options.name,
project: options.projectName,
includeLib: true,
includeVitest: options.unitTestRunner === 'vitest',
inSourceTests: options.inSourceTests,
@ -53,7 +53,7 @@ export async function addVite(
>('@nx/vite', nxVersion);
const vitestTask = await vitestGenerator(tree, {
uiFramework: 'none',
project: options.name,
project: options.projectName,
coverageProvider: 'v8',
inSourceTests: options.inSourceTests,
skipFormat: true,
@ -66,7 +66,7 @@ export async function addVite(
createOrEditViteConfig(
tree,
{
project: options.name,
project: options.projectName,
includeLib: true,
includeVitest: true,
inSourceTests: options.inSourceTests,

View File

@ -11,6 +11,7 @@ import {
} from '@nx/devkit/src/generators/project-name-and-root-utils';
import { NormalizedSchema, Schema } from '../schema';
import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
import { getImportPath } from '@nx/js/src/utils/get-import-path';
export async function normalizeOptions(
host: Tree,
@ -51,9 +52,14 @@ export async function normalizeOptions(
process.env.NX_ADD_PLUGINS !== 'false' &&
nxJson.useInferencePlugins !== false;
const isUsingTsSolutionConfig = isUsingTsSolutionSetup(host);
const normalized = {
addPlugin,
...options,
projectName: isUsingTsSolutionConfig
? getImportPath(host, projectName)
: projectName,
bundler,
fileName,
routePath: `/${projectNames.projectFileName}`,
@ -61,7 +67,7 @@ export async function normalizeOptions(
projectRoot,
parsedTags,
importPath,
isUsingTsSolutionConfig: isUsingTsSolutionSetup(host),
isUsingTsSolutionConfig,
} as NormalizedSchema;
// Libraries with a bundler or is publishable must also be buildable.

View File

@ -47,6 +47,12 @@ export async function libraryGeneratorInternal(tree: Tree, schema: Schema) {
);
}
// 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(tree, options.projectRoot);
}
if (options.isUsingTsSolutionConfig) {
writeJson(tree, joinPathFragments(options.projectRoot, 'package.json'), {
name: getImportPath(tree, options.name),
@ -54,12 +60,11 @@ export async function libraryGeneratorInternal(tree: Tree, schema: Schema) {
private: true,
...determineEntryFields(options),
files: options.publishable ? ['dist', '!**/*.tsbuildinfo'] : undefined,
nx: {
name: options.name,
projectType: 'library',
sourceRoot: `${options.projectRoot}/src`,
tags: options.parsedTags?.length ? options.parsedTags : undefined,
},
nx: options.parsedTags?.length
? {
tags: options.parsedTags,
}
: undefined,
});
} else {
addProjectConfiguration(tree, options.name, {
@ -149,12 +154,6 @@ export async function libraryGeneratorInternal(tree: Tree, schema: Schema) {
);
}
// 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(tree, options.projectRoot);
}
sortPackageJsonFields(tree, options.projectRoot);
if (!options.skipFormat) await formatFiles(tree);

View File

@ -27,6 +27,7 @@ export interface Schema {
export interface NormalizedSchema extends Schema {
js: boolean;
name: string;
projectName: string;
linter: Linter | LinterType;
fileName: string;
projectRoot: string;

View File

@ -30,6 +30,7 @@ export async function addLinting(
skipPackageJson?: boolean;
rootProject?: boolean;
addPlugin?: boolean;
projectName: string;
},
projectType: 'lib' | 'app'
) {
@ -37,7 +38,7 @@ export async function addLinting(
const tasks: GeneratorCallback[] = [];
const lintTask = await lintProjectGenerator(host, {
linter: options.linter,
project: options.name,
project: options.projectName,
tsConfigPaths: [
joinPathFragments(options.projectRoot, `tsconfig.${projectType}.json`),
],

View File

@ -247,12 +247,11 @@ async function addProject(tree: Tree, options: NormalizedSchema) {
name: getImportPath(tree, options.name),
version: '0.0.1',
private: true,
nx: {
name: options.name,
projectType: 'application',
sourceRoot: `${options.appProjectRoot}/src`,
tags: options.parsedTags?.length ? options.parsedTags : undefined,
},
nx: options.parsedTags?.length
? {
tags: options.parsedTags,
}
: undefined,
});
} else {
addProjectConfiguration(tree, options.projectName, {
@ -288,6 +287,10 @@ export async function applicationGenerator(host: Tree, schema: Schema) {
export async function applicationGeneratorInternal(host: Tree, schema: Schema) {
const options = await normalizeOptions(host, schema);
if (options.isUsingTsSolutionConfig) {
addProjectToTsSolutionWorkspace(host, options.appProjectRoot);
}
const tasks: GeneratorCallback[] = [];
const jsInitTask = await jsInitGenerator(host, {
@ -666,10 +669,6 @@ export async function applicationGeneratorInternal(host: Tree, schema: Schema) {
: undefined
);
if (options.isUsingTsSolutionConfig) {
addProjectToTsSolutionWorkspace(host, options.appProjectRoot);
}
if (!options.skipFormat) {
await formatFiles(host);
}
@ -719,7 +718,9 @@ async function normalizeOptions(
name: names(options.name).fileName,
compiler: options.compiler ?? 'babel',
bundler: options.bundler ?? 'webpack',
projectName: appProjectName,
projectName: isUsingTsSolutionConfig
? getImportPath(host, appProjectName)
: appProjectName,
strict: options.strict ?? true,
appProjectRoot,
e2eProjectRoot,

View File

@ -9,6 +9,7 @@ import {
writeJson,
} from '@nx/devkit';
import { moveGenerator } from '../move/move';
import { getProjectType } from '../../utils/ts-solution-setup';
export async function monorepoGenerator(tree: Tree, options: {}) {
const projects = getProjects(tree);
@ -32,7 +33,9 @@ export async function monorepoGenerator(tree: Tree, options: {}) {
// Currently, Nx only handles apps+libs or packages. You cannot mix and match them.
// If the standalone project is an app (React, Angular, etc), then use apps+libs.
// Otherwise, for TS standalone (lib), use packages.
const isRootProjectApp = rootProject.projectType === 'application';
const isRootProjectApp =
getProjectType(tree, rootProject.root, rootProject.projectType) ===
'application';
const appsDir = isRootProjectApp ? 'apps' : 'packages';
const libsDir = isRootProjectApp ? 'libs' : 'packages';
@ -50,11 +53,12 @@ export async function monorepoGenerator(tree: Tree, options: {}) {
}
for (const project of projectsToMove) {
const projectType = getProjectType(tree, project.root, project.projectType);
await moveGenerator(tree, {
projectName: project.name,
newProjectName: project.name,
destination:
project.projectType === 'application'
projectType === 'application'
? joinPathFragments(
appsDir,
project.root === '.' ? project.name : project.root
@ -63,7 +67,7 @@ export async function monorepoGenerator(tree: Tree, options: {}) {
libsDir,
project.root === '.' ? project.name : project.root
),
updateImportPath: project.projectType === 'library',
updateImportPath: projectType === 'library',
});
}
}

View File

@ -1,7 +1,12 @@
import { type ProjectConfiguration, type Tree } from '@nx/devkit';
import {
joinPathFragments,
type ProjectConfiguration,
type Tree,
} from '@nx/devkit';
import { getNpmScope } from '../../../utilities/get-import-path';
import type { NormalizedSchema, Schema } from '../schema';
import { normalizePathSlashes } from './utils';
import { getProjectType } from '../../../utils/ts-solution-setup';
export async function normalizeSchema(
tree: Tree,
@ -35,7 +40,7 @@ async function determineProjectNameAndRootOptions(
options: Schema,
projectConfiguration: ProjectConfiguration
): Promise<ProjectNameAndRootOptions> {
validateName(options.newProjectName, projectConfiguration);
validateName(tree, options.newProjectName, projectConfiguration);
const projectNameAndRootOptions = getProjectNameAndRootOptions(
tree,
options,
@ -46,6 +51,7 @@ async function determineProjectNameAndRootOptions(
}
function validateName(
tree: Tree,
name: string | undefined,
projectConfiguration: ProjectConfiguration
): void {
@ -66,15 +72,26 @@ function validateName(
const libraryPattern =
'(?:^@[a-zA-Z0-9-*~][a-zA-Z0-9-*._~]*\\/[a-zA-Z0-9-~][a-zA-Z0-9-._~]*|^[a-zA-Z][^:]*)$';
const appPattern = '^[a-zA-Z][^:]*$';
const projectType = getProjectType(
tree,
projectConfiguration.root,
projectConfiguration.projectType
);
if (projectConfiguration.projectType === 'application') {
if (projectType === 'application') {
const validationRegex = new RegExp(appPattern);
if (!validationRegex.test(name)) {
throw new Error(
`The new project name should match the pattern "${appPattern}". The provided value "${name}" does not match.`
);
}
} else if (projectConfiguration.projectType === 'library') {
} else if (
getProjectType(
tree,
projectConfiguration.root,
projectConfiguration.projectType
) === 'library'
) {
const validationRegex = new RegExp(libraryPattern);
if (!validationRegex.test(name)) {
throw new Error(

View File

@ -1,48 +1,4 @@
import {
getWorkspaceLayout,
joinPathFragments,
normalizePath,
ProjectConfiguration,
Tree,
} from '@nx/devkit';
import { Schema } from '../schema';
/**
* This helper function ensures that we don't move libs or apps
* outside of the folders they should be in.
*
* This will break if someone isn't using the default libs/apps
* folders. In that case, they're on their own :/
*/
export function getDestination(
host: Tree,
schema: Schema,
project: ProjectConfiguration
): string {
const projectType = project.projectType;
const workspaceLayout = getWorkspaceLayout(host);
let rootFolder = workspaceLayout.libsDir;
if (projectType === 'application') {
rootFolder = workspaceLayout.appsDir;
}
return joinPathFragments(rootFolder, schema.destination);
}
/**
* Joins path segments replacing slashes with dashes
*
* @param path
*/
export function getNewProjectName(path: string): string {
// strip leading '/' or './' or '../' and trailing '/' and replaces '/' with '-'
return normalizePath(path)
.replace(/(^\.{0,2}\/|\.{1,2}\/|\/$)/g, '')
.split('/')
.filter((x) => !!x)
.join('-');
}
import { normalizePath } from '@nx/devkit';
/**
* Normalizes slashes (removes duplicates)

View File

@ -0,0 +1,19 @@
import { joinPathFragments, readJson, type Tree } from '@nx/devkit';
// This is copied from `@nx/js` to avoid circular dependencies.
export function getProjectType(
tree: Tree,
projectRoot: string,
projectType?: 'library' | 'application'
): 'library' | 'application' {
if (projectType) return projectType;
if (joinPathFragments(projectRoot, 'tsconfig.lib.json')) return 'library';
if (joinPathFragments(projectRoot, 'tsconfig.app.json')) return 'application';
// If there are no exports, assume it is an application since both buildable and non-buildable libraries have exports.
const packageJsonPath = joinPathFragments(projectRoot, 'package.json');
const packageJson = tree.exists(packageJsonPath)
? readJson(tree, joinPathFragments(projectRoot, 'package.json'))
: null;
if (!packageJson?.exports) return 'application';
return 'library';
}