Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/generated/devkit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ It only uses language primitives and immutable objects
- [extractLayoutDirectory](/reference/core-api/devkit/documents/extractLayoutDirectory)
- [formatFiles](/reference/core-api/devkit/documents/formatFiles)
- [generateFiles](/reference/core-api/devkit/documents/generateFiles)
- [getDependencyVersionFromPackageJson](/reference/core-api/devkit/documents/getDependencyVersionFromPackageJson)
- [getOutputsForTargetAndConfiguration](/reference/core-api/devkit/documents/getOutputsForTargetAndConfiguration)
- [getPackageManagerCommand](/reference/core-api/devkit/documents/getPackageManagerCommand)
- [getPackageManagerVersion](/reference/core-api/devkit/documents/getPackageManagerVersion)
Expand Down
137 changes: 137 additions & 0 deletions docs/generated/devkit/getDependencyVersionFromPackageJson.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# Function: getDependencyVersionFromPackageJson

▸ **getDependencyVersionFromPackageJson**(`tree`, `packageName`, `packageJsonPath?`, `dependencyLookup?`): `string` \| `null`

Get the resolved version of a dependency from package.json.

Retrieves a package version and automatically resolves PNPM catalog references
(e.g., "catalog:default") to their actual version strings. By default, searches
`dependencies` first, then falls back to `devDependencies`.

**Tree-based usage** (generators and migrations):
Use when you have a `Tree` object, which is typical in Nx generators and migrations.

**Filesystem-based usage** (CLI commands and scripts):
Use when reading directly from the filesystem without a `Tree` object.

#### Parameters

| Name | Type | Description |
| :------------------ | :-------------------------------------------------- | :---------------------------------------------------------------------------------------------- |
| `tree` | [`Tree`](/reference/core-api/devkit/documents/Tree) | - |
| `packageName` | `string` | - |
| `packageJsonPath?` | `string` | - |
| `dependencyLookup?` | `PackageJsonDependencySection`[] | Array of dependency sections to check in order. Defaults to ['dependencies', 'devDependencies'] |

#### Returns

`string` \| `null`

The resolved version string, or `null` if the package is not found in any of the specified sections

**`Example`**

```typescript
// Tree-based - from root package.json (checks dependencies then devDependencies)
const reactVersion = getDependencyVersionFromPackageJson(tree, 'react');
// Returns: "^18.0.0" (resolves "catalog:default" if present)

// Tree-based - check only dependencies section
const version = getDependencyVersionFromPackageJson(
tree,
'react',
'package.json',
['dependencies']
);

// Tree-based - check only devDependencies section
const version = getDependencyVersionFromPackageJson(
tree,
'jest',
'package.json',
['devDependencies']
);

// Tree-based - custom lookup order
const version = getDependencyVersionFromPackageJson(
tree,
'pkg',
'package.json',
['devDependencies', 'dependencies', 'peerDependencies']
);

// Tree-based - with pre-loaded package.json
const packageJson = readJson(tree, 'package.json');
const version = getDependencyVersionFromPackageJson(
tree,
'react',
packageJson,
['dependencies']
);
```

**`Example`**

```typescript
// Filesystem-based - from current directory
const reactVersion = getDependencyVersionFromPackageJson('react');

// Filesystem-based - with workspace root
const version = getDependencyVersionFromPackageJson(
'react',
'/path/to/workspace'
);

// Filesystem-based - with specific package.json and section
const version = getDependencyVersionFromPackageJson(
'react',
'/path/to/workspace',
'apps/my-app/package.json',
['dependencies']
);
```

▸ **getDependencyVersionFromPackageJson**(`tree`, `packageName`, `packageJson?`, `dependencyLookup?`): `string` \| `null`

#### Parameters

| Name | Type |
| :------------------ | :-------------------------------------------------- |
| `tree` | [`Tree`](/reference/core-api/devkit/documents/Tree) |
| `packageName` | `string` |
| `packageJson?` | `PackageJson` |
| `dependencyLookup?` | `PackageJsonDependencySection`[] |

#### Returns

`string` \| `null`

▸ **getDependencyVersionFromPackageJson**(`packageName`, `workspaceRootPath?`, `packageJsonPath?`, `dependencyLookup?`): `string` \| `null`

#### Parameters

| Name | Type |
| :------------------- | :------------------------------- |
| `packageName` | `string` |
| `workspaceRootPath?` | `string` |
| `packageJsonPath?` | `string` |
| `dependencyLookup?` | `PackageJsonDependencySection`[] |

#### Returns

`string` \| `null`

▸ **getDependencyVersionFromPackageJson**(`packageName`, `workspaceRootPath?`, `packageJson?`, `dependencyLookup?`): `string` \| `null`

#### Parameters

| Name | Type |
| :------------------- | :------------------------------- |
| `packageName` | `string` |
| `workspaceRootPath?` | `string` |
| `packageJson?` | `PackageJson` |
| `dependencyLookup?` | `PackageJsonDependencySection`[] |

#### Returns

`string` \| `null`
1 change: 1 addition & 0 deletions docs/generated/packages/devkit/documents/nx_devkit.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ It only uses language primitives and immutable objects
- [extractLayoutDirectory](/reference/core-api/devkit/documents/extractLayoutDirectory)
- [formatFiles](/reference/core-api/devkit/documents/formatFiles)
- [generateFiles](/reference/core-api/devkit/documents/generateFiles)
- [getDependencyVersionFromPackageJson](/reference/core-api/devkit/documents/getDependencyVersionFromPackageJson)
- [getOutputsForTargetAndConfiguration](/reference/core-api/devkit/documents/getOutputsForTargetAndConfiguration)
- [getPackageManagerCommand](/reference/core-api/devkit/documents/getPackageManagerCommand)
- [getPackageManagerVersion](/reference/core-api/devkit/documents/getPackageManagerVersion)
Expand Down
175 changes: 175 additions & 0 deletions e2e/nx/src/misc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
e2eCwd,
getPackageManagerCommand,
getPublishedVersion,
getSelectedPackageManager,
isNotWindows,
killProcessAndPorts,
newProject,
Expand Down Expand Up @@ -537,6 +538,9 @@ describe('migrate', () => {
'migrate-child-package-3': {version: '9.0.0', addToPackageJson: false},
'migrate-child-package-4': {version: '9.0.0', addToPackageJson: 'dependencies'},
'migrate-child-package-5': {version: '9.0.0', addToPackageJson: 'devDependencies'},
'react': {version: '18.2.0', addToPackageJson: false},
'react-dom': {version: '18.2.0', addToPackageJson: false},
'lodash': {version: '4.17.21', addToPackageJson: false},
}},
}
});
Expand All @@ -549,6 +553,12 @@ describe('migrate', () => {
}
}
});
} else if (packageName === 'react') {
return Promise.resolve({version: '18.2.0'});
} else if (packageName === 'react-dom') {
return Promise.resolve({version: '18.2.0'});
} else if (packageName === 'lodash') {
return Promise.resolve({version: '4.17.21'});
} else {
return Promise.resolve({version: '9.0.0'});
}
Expand Down Expand Up @@ -903,6 +913,171 @@ describe('migrate', () => {
],
});
});

if (getSelectedPackageManager() === 'pnpm') {
it('should handle pnpm catalog references and update catalog definitions during migration', () => {
// Setup pnpm-workspace.yaml with both default and named catalogs. Include
// packages that WILL be updated and packages that SHOULD remain unchanged
// to test both scenarios.
updateFile(
'pnpm-workspace.yaml',
`
packages:
- packages/*

catalog:
migrate-parent-package: ^1.0.0
migrate-child-package: ^1.0.0
typescript: ^5.3.0

catalogs:
react17:
react: ^17.0.2
react-dom: ^17.0.2

tools:
eslint: ^8.0.0
prettier: ^3.0.0
`
);
// Update package.json to use MIXED catalog references and explicit versions
updateJson('package.json', (json) => {
json.dependencies = {
'migrate-parent-package': 'catalog:',
react: 'catalog:react17',
'react-dom': 'catalog:react17',
typescript: 'catalog:',
eslint: 'catalog:tools',
lodash: '^4.17.0', // explicit version that WILL be updated
axios: '^1.6.0', // explicit version that SHOULD stay unchanged
};
json.devDependencies = {
'migrate-child-package': 'catalog:',
prettier: 'catalog:tools',
};
return json;
});
// Create mock node_modules with RESOLVED versions for packages that will be updated
updateFile(
`./node_modules/react/package.json`,
JSON.stringify({
name: 'react',
version: '17.0.2',
})
);
updateFile(
`./node_modules/react-dom/package.json`,
JSON.stringify({
name: 'react-dom',
version: '17.0.2',
})
);
// Create mock node_modules for packages that should stay unchanged
updateFile(
`./node_modules/typescript/package.json`,
JSON.stringify({
name: 'typescript',
version: '5.3.0',
})
);
updateFile(
`./node_modules/eslint/package.json`,
JSON.stringify({
name: 'eslint',
version: '8.0.0',
})
);
updateFile(
`./node_modules/prettier/package.json`,
JSON.stringify({
name: 'prettier',
version: '3.0.0',
})
);
// Create mock node_modules for explicit version packages
updateFile(
`./node_modules/lodash/package.json`,
JSON.stringify({
name: 'lodash',
version: '4.17.0',
})
);
updateFile(
`./node_modules/axios/package.json`,
JSON.stringify({
name: 'axios',
version: '1.6.0',
})
);

// Run the migration
runCLI(
'migrate migrate-parent-package@2.0.0 --from="migrate-parent-package@1.0.0"',
{
env: {
NX_MIGRATE_SKIP_INSTALL: 'true',
NX_MIGRATE_USE_LOCAL: 'true',
},
}
);

// Verify ALL catalog references are PRESERVED in package.json
const packageJson = readJson('package.json');
expect(packageJson.dependencies['migrate-parent-package']).toEqual(
'catalog:'
);
expect(packageJson.devDependencies['migrate-child-package']).toEqual(
'catalog:'
);
expect(packageJson.dependencies['typescript']).toEqual('catalog:');
expect(packageJson.dependencies['react']).toEqual('catalog:react17');
expect(packageJson.dependencies['react-dom']).toEqual('catalog:react17');
expect(packageJson.dependencies['eslint']).toEqual('catalog:tools');
expect(packageJson.devDependencies['prettier']).toEqual('catalog:tools');

// Verify catalog definitions in pnpm-workspace.yaml
const workspaceYaml = readFile('pnpm-workspace.yaml');
// UPDATED packages (no ^ prefix as migrations provide resolved versions)
expect(workspaceYaml).toContain('migrate-parent-package: "2.0.0"');
expect(workspaceYaml).toContain('migrate-child-package: "9.0.0"');
expect(workspaceYaml).toContain('react: "18.2.0"');
expect(workspaceYaml).toContain('react-dom: "18.2.0"');
// PRESERVED packages (retain original format with ^ prefix)
expect(workspaceYaml).toContain('typescript: "^5.3.0"');
expect(workspaceYaml).toContain('eslint: "^8.0.0"');
expect(workspaceYaml).toContain('prettier: "^3.0.0"');

// Verify explicit version packages: updated and preserved
expect(packageJson.dependencies['lodash']).toEqual('4.17.21');
expect(packageJson.dependencies['axios']).toEqual('^1.6.0');

// Verify migrations.json was created correctly
const migrationsJson = readJson('migrations.json');
expect(migrationsJson.migrations).toEqual([
{
package: 'migrate-parent-package',
version: '1.1.0',
name: 'run11',
},
{
package: 'migrate-parent-package',
version: '2.0.0',
name: 'run20',
cli: 'nx',
},
]);

// Run migrations to ensure they execute successfully
runCLI('migrate --run-migrations=migrations.json', {
env: {
NX_MIGRATE_SKIP_INSTALL: 'true',
NX_MIGRATE_USE_LOCAL: 'true',
},
});

expect(readFile('file-20')).toEqual('content20');
});
}
});

describe('global installation', () => {
Expand Down
4 changes: 2 additions & 2 deletions packages/angular/src/generators/init/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
ensurePackage,
formatFiles,
type GeneratorCallback,
getDependencyVersionFromPackageJson,
logger,
readNxJson,
type Tree,
Expand All @@ -13,7 +14,6 @@ import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-so
import { createNodesV2 } from '../../plugins/plugin';
import {
getInstalledAngularDevkitVersion,
getInstalledPackageVersion,
versions,
} from '../utils/version-utils';
import { Schema } from './schema';
Expand Down Expand Up @@ -57,7 +57,7 @@ function installAngularDevkitCoreIfMissing(
tree: Tree,
options: Schema
): GeneratorCallback {
const packageVersion = getInstalledPackageVersion(
const packageVersion = getDependencyVersionFromPackageJson(
tree,
'@angular-devkit/core'
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ export function normalizeOptions(
let rxjsVersion: string;
try {
rxjsVersion = checkAndCleanWithSemver(
tree,
'rxjs',
readJson(tree, 'package.json').dependencies['rxjs']
);
} catch {
rxjsVersion = checkAndCleanWithSemver('rxjs', defaultRxjsVersion);
rxjsVersion = checkAndCleanWithSemver(tree, 'rxjs', defaultRxjsVersion);
}
const rxjsMajorVersion = major(rxjsVersion);

Expand Down
Loading
Loading