mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix(ngcc): cope with packages following APF v14+ (#45833)
In PR #45405, the Angular Package Format (APF) was updated so that secondary entry-points (such as `@angular/common/http`) do not have their own `package.json` file, as they used to. Instead, the paths to their various formats and types are exposed via the primary `package.json` file's `exports` property. As an example, see the v13 [@angular/common/http/package.json][1] and compare it with the v14 [@angular/common/package.json > exports][2]. Previously, `ngcc` was not able to analyze such v14+ entry-points and would instead error as it considered such entry-points missing. This commit addresses the issue by detecting this situation and synthesizing a `package.json` file for the secondary entry-points based on the `exports` property of the primary `package.json` file. This data is only used by `ngcc` in order to determine that the entry-point does not need further processing, since it is already in Ivy format. [1]: https://unpkg.com/browse/@angular/common@13.3.5/http/package.json [2]: https://unpkg.com/browse/@angular/common@14.0.0-next.15/package.json PR Close #45833
This commit is contained in:
committed by
Andrew Kushnir
parent
400ec172ca
commit
d4e949f45c
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
import {AbsoluteFsPath, ReadonlyFileSystem} from '../../../src/ngtsc/file_system';
|
||||
import {PathMappings} from '../path_mappings';
|
||||
import {isRelativePath, resolveFileWithPostfixes} from '../utils';
|
||||
import {isRelativePath, loadJson, loadSecondaryEntryPointInfoForApfV14, resolveFileWithPostfixes} from '../utils';
|
||||
|
||||
/**
|
||||
* This is a very cut-down implementation of the TypeScript module resolution strategy.
|
||||
@@ -110,8 +110,8 @@ export class ModuleResolver {
|
||||
* Try to resolve the `moduleName` as an external entry-point by searching the `node_modules`
|
||||
* folders up the tree for a matching `.../node_modules/${moduleName}`.
|
||||
*
|
||||
* If a folder is found but the path does not contain a `package.json` then it is marked as a
|
||||
* "deep-import".
|
||||
* If a folder is found but the path is not considered an entry-point (see `isEntryPoint()`) then
|
||||
* it is marked as a "deep-import".
|
||||
*/
|
||||
private resolveAsEntryPoint(moduleName: string, fromPath: AbsoluteFsPath): ResolvedModule|null {
|
||||
let folder = fromPath;
|
||||
@@ -136,9 +136,25 @@ export class ModuleResolver {
|
||||
* Can we consider the given path as an entry-point to a package?
|
||||
*
|
||||
* This is achieved by checking for the existence of `${modulePath}/package.json`.
|
||||
* If there is no `package.json`, we check whether this is an APF v14+ secondary entry-point,
|
||||
* which does not have its own `package.json` but has an `exports` entry in the package's primary
|
||||
* `package.json`.
|
||||
*/
|
||||
private isEntryPoint(modulePath: AbsoluteFsPath): boolean {
|
||||
return this.fs.exists(this.fs.join(modulePath, 'package.json'));
|
||||
if (this.fs.exists(this.fs.join(modulePath, 'package.json'))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const packagePath = this.findPackagePath(modulePath);
|
||||
if (packagePath === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const packagePackageJson = loadJson(this.fs, this.fs.join(packagePath, 'package.json'));
|
||||
const entryPointInfoForApfV14 =
|
||||
loadSecondaryEntryPointInfoForApfV14(this.fs, packagePackageJson, packagePath, modulePath);
|
||||
|
||||
return entryPointInfoForApfV14 !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,7 +10,7 @@ import ts from 'typescript';
|
||||
import {AbsoluteFsPath, PathManipulation, ReadonlyFileSystem} from '../../../src/ngtsc/file_system';
|
||||
import {Logger} from '../../../src/ngtsc/logging';
|
||||
import {parseStatementForUmdModule} from '../host/umd_host';
|
||||
import {JsonObject, loadJson, resolveFileWithPostfixes} from '../utils';
|
||||
import {JsonObject, loadJson, loadSecondaryEntryPointInfoForApfV14, resolveFileWithPostfixes} from '../utils';
|
||||
|
||||
import {NgccConfiguration, NgccEntryPointConfig} from './configuration';
|
||||
|
||||
@@ -131,7 +131,8 @@ export function getEntryPointInfo(
|
||||
const loadedPackagePackageJson = loadJson<EntryPointPackageJson>(fs, packagePackageJsonPath);
|
||||
const loadedEntryPointPackageJson = (packagePackageJsonPath === entryPointPackageJsonPath) ?
|
||||
loadedPackagePackageJson :
|
||||
loadJson<EntryPointPackageJson>(fs, entryPointPackageJsonPath);
|
||||
loadOrSynthesizeSecondaryPackageJson(
|
||||
fs, packagePath, entryPointPath, entryPointPackageJsonPath, loadedPackagePackageJson);
|
||||
const {packageName, packageVersion} = getPackageNameAndVersion(
|
||||
fs, packagePath, loadedPackagePackageJson, loadedEntryPointPackageJson);
|
||||
const repositoryUrl = getRepositoryUrl(loadedPackagePackageJson);
|
||||
@@ -141,17 +142,17 @@ export function getEntryPointInfo(
|
||||
let entryPointPackageJson: EntryPointPackageJson;
|
||||
|
||||
if (entryPointConfig === undefined) {
|
||||
if (!fs.exists(entryPointPackageJsonPath)) {
|
||||
// No `package.json` and no config.
|
||||
if (loadedEntryPointPackageJson !== null) {
|
||||
entryPointPackageJson = loadedEntryPointPackageJson;
|
||||
} else if (!fs.exists(entryPointPackageJsonPath)) {
|
||||
// No entry-point `package.json` or package `package.json` with exports and no config.
|
||||
return NO_ENTRY_POINT;
|
||||
} else if (loadedEntryPointPackageJson === null) {
|
||||
} else {
|
||||
// `package.json` exists but could not be parsed and there is no redeeming config.
|
||||
logger.warn(`Failed to read entry point info from invalid 'package.json' file: ${
|
||||
entryPointPackageJsonPath}`);
|
||||
|
||||
return INCOMPATIBLE_ENTRY_POINT;
|
||||
} else {
|
||||
entryPointPackageJson = loadedEntryPointPackageJson;
|
||||
}
|
||||
} else if (entryPointConfig.ignore === true) {
|
||||
// Explicitly ignored entry-point.
|
||||
@@ -246,6 +247,69 @@ export function getEntryPointFormat(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the JSON from a secondary `package.json` file. If no such file exists, look for a
|
||||
* corresponding entry in the primary `package.json` file's `exports` property (if any) and
|
||||
* synthesize the JSON from that.
|
||||
*
|
||||
* @param packagePath The absolute path to the containing npm package.
|
||||
* @param entryPointPath The absolute path to the secondary entry-point.
|
||||
* @param secondaryPackageJsonPath The absolute path to the secondary `package.json` file.
|
||||
* @param primaryPackageJson The parsed JSON of the primary `package.json` (or `null` if it failed
|
||||
* to be loaded).
|
||||
* @returns Parsed JSON (either loaded from a secondary `package.json` file or synthesized from a
|
||||
* primary one) if it is valid, `null` otherwise.
|
||||
*/
|
||||
function loadOrSynthesizeSecondaryPackageJson(
|
||||
fs: ReadonlyFileSystem, packagePath: AbsoluteFsPath, entryPointPath: AbsoluteFsPath,
|
||||
secondaryPackageJsonPath: AbsoluteFsPath,
|
||||
primaryPackageJson: EntryPointPackageJson|null): EntryPointPackageJson|null {
|
||||
// If a secondary `package.json` exists and is valid, load and return that.
|
||||
const loadedPackageJson = loadJson<EntryPointPackageJson>(fs, secondaryPackageJsonPath);
|
||||
if (loadedPackageJson !== null) {
|
||||
return loadedPackageJson;
|
||||
}
|
||||
|
||||
// Try to load the entry-point info from the primary `package.json` data.
|
||||
const entryPointInfo =
|
||||
loadSecondaryEntryPointInfoForApfV14(fs, primaryPackageJson, packagePath, entryPointPath);
|
||||
if (entryPointInfo === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Create a synthesized `package.json`.
|
||||
//
|
||||
// NOTE:
|
||||
// We do not care about being able to update the synthesized `package.json` (for example, updating
|
||||
// its `__processed_by_ivy_ngcc__` property), because these packages are generated with Angular
|
||||
// v14+ (following the Angular Package Format v14+) and thus are already in Ivy format and do not
|
||||
// require processing by `ngcc`.
|
||||
const synthesizedPackageJson: EntryPointPackageJson = {
|
||||
synthesized: true,
|
||||
name: `${primaryPackageJson!.name}/${fs.relative(packagePath, entryPointPath)}`,
|
||||
};
|
||||
|
||||
// Update the synthesized `package.json` with any of the supported format and types properties,
|
||||
// changing paths to make them relative to the entry-point directory. This makes the synthesized
|
||||
// `package.json` similar to how a `package.json` inside the entry-point directory would look
|
||||
// like.
|
||||
for (const prop of [...SUPPORTED_FORMAT_PROPERTIES, 'types', 'typings']) {
|
||||
const packageRelativePath = entryPointInfo[prop];
|
||||
|
||||
if (typeof packageRelativePath === 'string') {
|
||||
const absolutePath = fs.resolve(packagePath, packageRelativePath);
|
||||
const entryPointRelativePath = fs.relative(entryPointPath, absolutePath);
|
||||
synthesizedPackageJson[prop] =
|
||||
(fs.isRooted(entryPointRelativePath) || entryPointRelativePath.startsWith('.')) ?
|
||||
entryPointRelativePath :
|
||||
`./${entryPointRelativePath}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the synthesized JSON.
|
||||
return synthesizedPackageJson;
|
||||
}
|
||||
|
||||
function sniffModuleFormat(
|
||||
fs: ReadonlyFileSystem, sourceFilePath: AbsoluteFsPath): EntryPointFormat|undefined {
|
||||
const resolvedPath = resolveFileWithPostfixes(fs, sourceFilePath, ['', '.js', '/index.js']);
|
||||
|
||||
@@ -186,3 +186,57 @@ export function loadJson<T extends JsonObject = JsonObject>(
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the parsed JSON of a `package.json` file, try to extract info for a secondary entry-point
|
||||
* from the `exports` property. Such info will only be present for packages following Angular
|
||||
* Package Format v14+.
|
||||
*
|
||||
* @param primaryPackageJson The parsed JSON of the primary `package.json` (or `null` if it failed
|
||||
* to be loaded).
|
||||
* @param packagePath The absolute path to the containing npm package.
|
||||
* @param entryPointPath The absolute path to the secondary entry-point.
|
||||
* @returns The `exports` info for the specified entry-point if it exists, `null` otherwise.
|
||||
*/
|
||||
export function loadSecondaryEntryPointInfoForApfV14(
|
||||
fs: ReadonlyFileSystem, primaryPackageJson: JsonObject|null, packagePath: AbsoluteFsPath,
|
||||
entryPointPath: AbsoluteFsPath): JsonObject|null {
|
||||
// Check if primary `package.json` has been loaded and has an `exports` property that is an
|
||||
// object.
|
||||
const exportMap = primaryPackageJson?.exports;
|
||||
if (!isExportObject(exportMap)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Find the `exports` key for the secondary entry-point.
|
||||
const relativeEntryPointPath = fs.relative(packagePath, entryPointPath);
|
||||
const entryPointExportKey = `./${relativeEntryPointPath}`;
|
||||
|
||||
// Read the info for the entry-point.
|
||||
const entryPointInfo = exportMap[entryPointExportKey];
|
||||
|
||||
// Check whether the entry-point info exists and is an export map.
|
||||
return isExportObject(entryPointInfo) ? entryPointInfo : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a value read from a JSON file is a Node.js export map (either the top-level one or
|
||||
* one for a subpath).
|
||||
*
|
||||
* In `package.json` files, the `exports` field can be of type `Object | string | string[]`, but APF
|
||||
* v14+ uses an object with subpath exports for each entry-point, which in turn are conditional
|
||||
* exports (see references below). This function verifies that a value read from the top-level
|
||||
* `exports` field or a subpath is of type `Object` (and not `string` or `string[]`).
|
||||
*
|
||||
* References:
|
||||
* - https://nodejs.org/api/packages.html#exports
|
||||
* - https://nodejs.org/api/packages.html#subpath-exports
|
||||
* - https://nodejs.org/api/packages.html#conditional-exports
|
||||
* - https://v14.angular.io/guide/angular-package-format#exports
|
||||
*
|
||||
* @param thing The value read from the JSON file
|
||||
* @returns True if the value is an `Object` (and not an `Array`).
|
||||
*/
|
||||
function isExportObject(thing: JsonValue): thing is JsonObject {
|
||||
return (typeof thing === 'object') && (thing !== null) && !Array.isArray(thing);
|
||||
}
|
||||
|
||||
@@ -101,6 +101,7 @@ export class PackageJsonUpdate {
|
||||
*/
|
||||
writeChanges(packageJsonPath: AbsoluteFsPath, parsedJson?: JsonObject): void {
|
||||
this.ensureNotApplied();
|
||||
this.ensureNotSynthesized(parsedJson);
|
||||
this.writeChangesImpl(this.changes, packageJsonPath, parsedJson);
|
||||
this.applied = true;
|
||||
}
|
||||
@@ -110,6 +111,15 @@ export class PackageJsonUpdate {
|
||||
throw new Error('Trying to apply a `PackageJsonUpdate` that has already been applied.');
|
||||
}
|
||||
}
|
||||
|
||||
private ensureNotSynthesized(parsedJson?: JsonObject) {
|
||||
if (parsedJson?.synthesized) {
|
||||
// Theoretically, this should never happen, because synthesized `package.json` files should
|
||||
// only be created for libraries following the Angular Package Format v14+, which means they
|
||||
// should already be in Ivy format and not require processing by `ngcc`.
|
||||
throw new Error('Trying to update a non-existent (synthesized) `package.json` file.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** A `PackageJsonUpdater` that writes directly to the file-system. */
|
||||
|
||||
@@ -64,6 +64,25 @@ runInEachFileSystem(() => {
|
||||
name: _('/node_modules/top-package/package.json'),
|
||||
contents: 'PACKAGE.JSON for top-package'
|
||||
},
|
||||
{
|
||||
name: _('/node_modules/apf-v14-package/package.json'),
|
||||
contents: `{
|
||||
"name": "apf-v14-package",
|
||||
"exports": {
|
||||
"./second/ary": {
|
||||
"main": "./second/ary/index.js"
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: _('/node_modules/apf-v14-package/index.js'),
|
||||
contents: `export const type = 'primary';`,
|
||||
},
|
||||
{
|
||||
name: _('/node_modules/apf-v14-package/second/ary/index.js'),
|
||||
contents: `export const type = 'secondary';`,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -144,6 +163,17 @@ runInEachFileSystem(() => {
|
||||
.toEqual(new ResolvedDeepImport(
|
||||
_('/libs/local-package/node_modules/package-1/sub-folder')));
|
||||
});
|
||||
|
||||
it('should resolve to APF v14+ secondary entry-points', () => {
|
||||
const resolver = new ModuleResolver(getFileSystem());
|
||||
|
||||
expect(resolver.resolveModuleImport(
|
||||
'apf-v14-package/second/ary', _('/libs/local-package/index.js')))
|
||||
.toEqual(new ResolvedExternalModule(_('/node_modules/apf-v14-package/second/ary')));
|
||||
expect(resolver.resolveModuleImport(
|
||||
'apf-v14-package/second/ary', _('/libs/local-package/sub-folder/index.js')))
|
||||
.toEqual(new ResolvedExternalModule(_('/node_modules/apf-v14-package/second/ary')));
|
||||
});
|
||||
});
|
||||
|
||||
describe('with mapped path external modules', () => {
|
||||
@@ -256,6 +286,20 @@ runInEachFileSystem(() => {
|
||||
'package-4/secondary-entry-point', _('/dist/package-4/index.js')))
|
||||
.toEqual(new ResolvedExternalModule(_('/dist/package-4/secondary-entry-point')));
|
||||
});
|
||||
|
||||
it('should resolve APF v14+ secondary entry-points', () => {
|
||||
const resolver = new ModuleResolver(getFileSystem(), {
|
||||
baseUrl: '/node_modules',
|
||||
paths: {'package-42': ['apf-v14-package'], 'package-42/*': ['apf-v14-package/*']},
|
||||
});
|
||||
|
||||
expect(resolver.resolveModuleImport(
|
||||
'package-42/second/ary', _('/libs/local-package/index.js')))
|
||||
.toEqual(new ResolvedExternalModule(_('/node_modules/apf-v14-package/second/ary')));
|
||||
expect(resolver.resolveModuleImport(
|
||||
'package-42/second/ary', _('/libs/local-package/sub-folder/index.js')))
|
||||
.toEqual(new ResolvedExternalModule(_('/node_modules/apf-v14-package/second/ary')));
|
||||
});
|
||||
});
|
||||
|
||||
describe('with mapped path relative paths', () => {
|
||||
|
||||
@@ -197,6 +197,14 @@ runInEachFileSystem(() => {
|
||||
expect(() => update.writeChanges(_('/bar/package.json')))
|
||||
.toThrowError('Trying to apply a `PackageJsonUpdate` that has already been applied.');
|
||||
});
|
||||
|
||||
it('should throw, if trying to update a synthesized `package.json` file', () => {
|
||||
const update = updater.createUpdate().addChange(['foo'], 'updated');
|
||||
|
||||
expect(() => update.writeChanges(_('/foo/package.json'), {
|
||||
synthesized: true
|
||||
})).toThrowError('Trying to update a non-existent (synthesized) `package.json` file.');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -707,6 +707,125 @@ runInEachFileSystem(() => {
|
||||
_('/project/node_modules/some_package/unexpected_symbols'));
|
||||
expect(entryPoint).toBe(INCOMPATIBLE_ENTRY_POINT);
|
||||
});
|
||||
|
||||
describe('when processing APF v14+ secondary entry-points', () => {
|
||||
it('should retrieve info from primary `package.json`', () => {
|
||||
const packagePath = _('/project/node_modules/primary');
|
||||
const entryPointPath = fs.join(packagePath, 'second/ary');
|
||||
|
||||
loadTestFiles([
|
||||
{
|
||||
name: fs.join(packagePath, 'package.json'),
|
||||
contents: `{
|
||||
"name": "primary-package",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "/something/primary.d.ts"
|
||||
},
|
||||
"./second/ary": {
|
||||
"types": "/something/secondary.d.ts"
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
]);
|
||||
|
||||
const config = new NgccConfiguration(fs, _('/project'));
|
||||
const entryPoint =
|
||||
getEntryPointInfo(fs, config, new MockLogger(), packagePath, entryPointPath);
|
||||
|
||||
expect(entryPoint).toEqual({
|
||||
name: 'primary-package/second/ary',
|
||||
path: entryPointPath,
|
||||
packageName: 'primary-package',
|
||||
packagePath,
|
||||
repositoryUrl: '',
|
||||
packageJson: {
|
||||
synthesized: true,
|
||||
name: 'primary-package/second/ary',
|
||||
types: jasmine.any(String),
|
||||
},
|
||||
typings: _('/something/secondary.d.ts'),
|
||||
compiledByAngular: false,
|
||||
ignoreMissingDependencies: false,
|
||||
generateDeepReexports: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should only keep supported format properties', () => {
|
||||
const packagePath = _('/project/node_modules/primary');
|
||||
const entryPointPath = fs.join(packagePath, 'second/ary');
|
||||
|
||||
loadTestFiles([
|
||||
{
|
||||
name: fs.join(packagePath, 'package.json'),
|
||||
contents: `{
|
||||
"name": "primary-package",
|
||||
"exports": {
|
||||
"./second/ary": {
|
||||
"types": "/types/index.d.ts",
|
||||
"typings": "/typings/index.d.ts",
|
||||
"default": "/default/index.js",
|
||||
"other": "/other/index.js",
|
||||
${
|
||||
SUPPORTED_FORMAT_PROPERTIES.map(prop => `"${prop}": "/${prop}/index.js"`)
|
||||
.join(', ')}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
]);
|
||||
|
||||
const config = new NgccConfiguration(fs, _('/project'));
|
||||
const entryPoint =
|
||||
getEntryPointInfo(fs, config, new MockLogger(), packagePath, entryPointPath) as
|
||||
EntryPoint;
|
||||
const packageJsonProps = Object.keys(entryPoint.packageJson);
|
||||
|
||||
expect(packageJsonProps).not.toContain('default');
|
||||
expect(packageJsonProps).not.toContain('other');
|
||||
expect(packageJsonProps).toEqual(jasmine.arrayWithExactContents([
|
||||
'synthesized',
|
||||
'name',
|
||||
'types',
|
||||
'typings',
|
||||
...SUPPORTED_FORMAT_PROPERTIES,
|
||||
]));
|
||||
});
|
||||
|
||||
it('should convert format paths to be relative to entry-point directory', () => {
|
||||
const packagePath = _('/project/node_modules/primary');
|
||||
const entryPointPath = fs.join(packagePath, 'second/ary');
|
||||
|
||||
loadTestFiles([
|
||||
{
|
||||
name: fs.join(packagePath, 'package.json'),
|
||||
contents: `{
|
||||
"name": "primary-package",
|
||||
"main": "./bundles/index.js",
|
||||
"exports": {
|
||||
"./second/ary": {
|
||||
"fesm2015": "./fesm2015/secondary.js",
|
||||
"main": "./bundles/secondary.js",
|
||||
"types": "./second/ary/secondary.d.ts"
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
]);
|
||||
|
||||
const config = new NgccConfiguration(fs, _('/project'));
|
||||
const entryPoint =
|
||||
getEntryPointInfo(fs, config, new MockLogger(), packagePath, entryPointPath) as
|
||||
EntryPoint;
|
||||
|
||||
expect(entryPoint.packageJson).toEqual(jasmine.objectContaining({
|
||||
fesm2015: '../../fesm2015/secondary.js',
|
||||
main: '../../bundles/secondary.js',
|
||||
types: './secondary.d.ts',
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getEntryPointFormat()', () => {
|
||||
|
||||
@@ -12,7 +12,7 @@ import {absoluteFrom as _abs, FileSystem, getFileSystem} from '../../src/ngtsc/f
|
||||
import {runInEachFileSystem} from '../../src/ngtsc/file_system/testing';
|
||||
import {KnownDeclaration} from '../../src/ngtsc/reflection';
|
||||
import {loadTestFiles} from '../../src/ngtsc/testing';
|
||||
import {FactoryMap, getTsHelperFnFromDeclaration, getTsHelperFnFromIdentifier, isRelativePath, loadJson, stripExtension} from '../src/utils';
|
||||
import {FactoryMap, getTsHelperFnFromDeclaration, getTsHelperFnFromIdentifier, isRelativePath, loadJson, loadSecondaryEntryPointInfoForApfV14, stripExtension} from '../src/utils';
|
||||
|
||||
describe('FactoryMap', () => {
|
||||
it('should return an existing value', () => {
|
||||
@@ -276,3 +276,114 @@ runInEachFileSystem(() => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
runInEachFileSystem(() => {
|
||||
let fs: FileSystem;
|
||||
|
||||
beforeEach(() => fs = getFileSystem());
|
||||
|
||||
describe('loadSecondaryEntryPointInfoForApfV14()', () => {
|
||||
it('should return `null` if the primary `package.json` failed to be loaded', () => {
|
||||
expect(loadSecondaryEntryPointInfoForApfV14(fs, null, _abs('/foo'), _abs('/foo/bar')))
|
||||
.toBe(null);
|
||||
});
|
||||
|
||||
it('should return `null` if the primary `package.json` has no `exports` property', () => {
|
||||
const primaryPackageJson = {
|
||||
name: 'some-package',
|
||||
version: '1.33.7',
|
||||
main: './index.js',
|
||||
};
|
||||
|
||||
expect(loadSecondaryEntryPointInfoForApfV14(
|
||||
fs, primaryPackageJson, _abs('/foo'), _abs('/foo/bar')))
|
||||
.toBe(null);
|
||||
});
|
||||
|
||||
it('should return `null` if the primary `package.json`\'s `exports` property is a string',
|
||||
() => {
|
||||
const primaryPackageJson = {
|
||||
name: 'some-package',
|
||||
exports: './index.js',
|
||||
};
|
||||
|
||||
expect(loadSecondaryEntryPointInfoForApfV14(
|
||||
fs, primaryPackageJson, _abs('/foo'), _abs('/foo/bar')))
|
||||
.toBe(null);
|
||||
});
|
||||
|
||||
it('should return `null` if the primary `package.json`\'s `exports` property is a string array',
|
||||
() => {
|
||||
const primaryPackageJson = {
|
||||
name: 'some-package',
|
||||
exports: [
|
||||
'./foo.js',
|
||||
'./bar.js',
|
||||
],
|
||||
};
|
||||
|
||||
expect(loadSecondaryEntryPointInfoForApfV14(
|
||||
fs, primaryPackageJson, _abs('/foo'), _abs('/foo/bar')))
|
||||
.toBe(null);
|
||||
});
|
||||
|
||||
it('should return `null` if there is no info for the specified entry-point', () => {
|
||||
const primaryPackageJson = {
|
||||
name: 'some-package',
|
||||
exports: {
|
||||
'./baz': {
|
||||
main: './baz/index.js',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(loadSecondaryEntryPointInfoForApfV14(
|
||||
fs, primaryPackageJson, _abs('/foo'), _abs('/foo/bar')))
|
||||
.toBe(null);
|
||||
});
|
||||
|
||||
it('should return `null` if the entry-point info is a string', () => {
|
||||
const primaryPackageJson = {
|
||||
name: 'some-package',
|
||||
exports: {
|
||||
'./bar': './bar/index.js',
|
||||
},
|
||||
};
|
||||
|
||||
expect(loadSecondaryEntryPointInfoForApfV14(
|
||||
fs, primaryPackageJson, _abs('/foo'), _abs('/foo/bar')))
|
||||
.toBe(null);
|
||||
});
|
||||
|
||||
it('should return `null` if the entry-point info is a string array', () => {
|
||||
const primaryPackageJson = {
|
||||
name: 'some-package',
|
||||
exports: {
|
||||
'./bar': [
|
||||
'./bar/a.js',
|
||||
'./bar/b.js',
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
expect(loadSecondaryEntryPointInfoForApfV14(
|
||||
fs, primaryPackageJson, _abs('/foo'), _abs('/foo/bar')))
|
||||
.toBe(null);
|
||||
});
|
||||
|
||||
it('should return the entry-point info if it exists and is an object', () => {
|
||||
const primaryPackageJson = {
|
||||
name: 'some-package',
|
||||
exports: {
|
||||
'./bar': {
|
||||
main: './bar/index.js',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(loadSecondaryEntryPointInfoForApfV14(
|
||||
fs, primaryPackageJson, _abs('/foo'), _abs('/foo/bar')))
|
||||
.toEqual({main: './bar/index.js'});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -153,6 +153,14 @@ runInEachFileSystem(() => {
|
||||
.toThrowError('Trying to apply a `PackageJsonUpdate` that has already been applied.');
|
||||
});
|
||||
|
||||
it('should throw, if trying to update a synthesized `package.json` file', () => {
|
||||
const update = updater.createUpdate().addChange(['foo'], 'updated');
|
||||
|
||||
expect(() => update.writeChanges(_('/foo/package.json'), {
|
||||
synthesized: true
|
||||
})).toThrowError('Trying to update a non-existent (synthesized) `package.json` file.');
|
||||
});
|
||||
|
||||
describe('(property positioning)', () => {
|
||||
// Helpers
|
||||
const createJsonFile = (jsonObj: JsonObject) => {
|
||||
|
||||
Reference in New Issue
Block a user