@softarc/native-federation 4.0.0-RC8 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/index.d.ts +1 -2
- package/src/index.js +1 -2
- package/src/internal.d.ts +4 -1
- package/src/internal.js +2 -0
- package/src/lib/config/default-skip-list.js +3 -15
- package/src/lib/config/remove-unused-deps.d.ts +3 -0
- package/src/lib/config/remove-unused-deps.js +10 -0
- package/src/lib/config/share-utils.d.ts +0 -1
- package/src/lib/config/share-utils.js +7 -13
- package/src/lib/config/with-native-federation.js +40 -61
- package/src/lib/core/build-for-federation.js +21 -22
- package/src/lib/core/bundle-exposed-and-mappings.js +15 -16
- package/src/lib/core/bundle-shared.d.ts +1 -0
- package/src/lib/core/bundle-shared.js +8 -7
- package/src/lib/core/federation-builder.d.ts +6 -1
- package/src/lib/core/federation-builder.js +18 -8
- package/src/lib/core/get-externals.js +1 -1
- package/src/lib/core/normalize-options.d.ts +12 -0
- package/src/lib/core/normalize-options.js +58 -0
- package/src/lib/core/rebuild-for-federation.js +2 -2
- package/src/lib/domain/config/external-config.contract.d.ts +3 -1
- package/src/lib/domain/config/federation-config.contract.d.ts +8 -2
- package/src/lib/domain/config/with-native-federation.contract.d.ts +2 -0
- package/src/lib/domain/config/with-native-federation.contract.js +1 -0
- package/src/lib/domain/core/build-adapter.contract.d.ts +12 -6
- package/src/lib/domain/core/federation-options.contract.d.ts +5 -5
- package/src/lib/domain/core/index.d.ts +1 -1
- package/src/lib/domain/utils/file-watcher.contract.d.ts +10 -0
- package/src/lib/domain/utils/file-watcher.contract.js +1 -0
- package/src/lib/domain/utils/index.d.ts +1 -1
- package/src/lib/domain/utils/mapped-path.contract.d.ts +1 -4
- package/src/lib/domain/utils/mapped-path.contract.js +4 -0
- package/src/lib/domain/utils/used-dependencies.contract.d.ts +5 -0
- package/src/lib/domain/utils/used-dependencies.contract.js +1 -0
- package/src/lib/utils/file-watcher.d.ts +5 -0
- package/src/lib/utils/file-watcher.js +51 -0
- package/src/lib/utils/get-used-dependencies.d.ts +7 -0
- package/src/lib/utils/get-used-dependencies.js +123 -0
- package/src/lib/utils/mapped-paths.d.ts +7 -7
- package/src/lib/utils/mapped-paths.js +14 -12
- package/src/lib/utils/resolve-wildcard-keys.d.ts +29 -2
- package/src/lib/utils/resolve-wildcard-keys.js +105 -38
- package/src/lib/core/default-server-deps-list.d.ts +0 -2
- package/src/lib/core/default-server-deps-list.js +0 -6
- package/src/lib/core/load-federation-config.d.ts +0 -3
- package/src/lib/core/load-federation-config.js +0 -18
- package/src/lib/core/normalize-federation-options.d.ts +0 -4
- package/src/lib/core/normalize-federation-options.js +0 -10
- package/src/lib/core/remove-unused-deps.d.ts +0 -2
- package/src/lib/core/remove-unused-deps.js +0 -90
- package/src/lib/utils/config-utils.d.ts +0 -2
- package/src/lib/utils/config-utils.js +0 -9
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { NormalizedFederationConfig } from '../domain/config/federation-config.contract.js';
|
|
2
|
+
import type { FederationOptions, NormalizedFederationOptions } from '../domain/core/federation-options.contract.js';
|
|
3
|
+
import { type FederationCache } from '../../domain.js';
|
|
4
|
+
export declare function normalizeFederationOptions(options: FederationOptions): Promise<{
|
|
5
|
+
config: NormalizedFederationConfig;
|
|
6
|
+
options: NormalizedFederationOptions<undefined>;
|
|
7
|
+
}>;
|
|
8
|
+
export declare function normalizeFederationOptions<TBundlerCache>(options: FederationOptions, cache: FederationCache<TBundlerCache>): Promise<{
|
|
9
|
+
config: NormalizedFederationConfig;
|
|
10
|
+
options: NormalizedFederationOptions<TBundlerCache>;
|
|
11
|
+
}>;
|
|
12
|
+
export declare function resolveProjectName(name?: string): string;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import { pathToFileURL } from 'url';
|
|
4
|
+
import { removeUnusedDeps } from '../config/remove-unused-deps.js';
|
|
5
|
+
import { createFederationCache } from './federation-cache.js';
|
|
6
|
+
import { getDefaultCachePath } from '../utils/cache-persistence.js';
|
|
7
|
+
import { getUsedDependenciesFactory } from '../utils/get-used-dependencies.js';
|
|
8
|
+
import { logger } from '../utils/logger.js';
|
|
9
|
+
import { normalizePackageName } from '../utils/normalize.js';
|
|
10
|
+
export async function normalizeFederationOptions(options, cache) {
|
|
11
|
+
/**
|
|
12
|
+
* Step 1: normalizing config
|
|
13
|
+
*/
|
|
14
|
+
const fullConfigPath = path.join(options.workspaceRoot, options.federationConfig);
|
|
15
|
+
const getUsedDeps = getUsedDependenciesFactory(options.workspaceRoot, options.entryPoints);
|
|
16
|
+
if (!fs.existsSync(fullConfigPath)) {
|
|
17
|
+
throw new Error('Expected ' + fullConfigPath);
|
|
18
|
+
}
|
|
19
|
+
let config = (await import(pathToFileURL(fullConfigPath).href))
|
|
20
|
+
?.default;
|
|
21
|
+
/**
|
|
22
|
+
* Step 2: normalizing options
|
|
23
|
+
*/
|
|
24
|
+
const federationCache = cache ??
|
|
25
|
+
createFederationCache(getDefaultCachePath(options.workspaceRoot));
|
|
26
|
+
const normalizedOptions = {
|
|
27
|
+
...options,
|
|
28
|
+
entryPoints: options.entryPoints ?? Object.values(config.exposes ?? {}),
|
|
29
|
+
projectName: resolveProjectName(options.projectName ?? config.name),
|
|
30
|
+
cacheExternalArtifacts: options.cacheExternalArtifacts ?? true,
|
|
31
|
+
federationCache,
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Step 3: Remove unused deps
|
|
35
|
+
*/
|
|
36
|
+
if (config.features.ignoreUnusedDeps) {
|
|
37
|
+
config = removeUnusedDeps(getUsedDeps(config), config);
|
|
38
|
+
logger.info('Removed unused dependencies.');
|
|
39
|
+
logger.debug('This can be disabled per dependency/external using the "includeSecondaries: {keepAll: true}" property. Or in general by disabling the "ignoreUnusedDeps" feature. ');
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
const withWildcard = Object.keys(config.sharedMappings).some(m => m.includes('*'));
|
|
43
|
+
if (withWildcard) {
|
|
44
|
+
logger.warn('Sharing mapped paths with wildcards (*) is only supported with ignoreUnusedDeps feature.');
|
|
45
|
+
config.sharedMappings = Object.entries(config.sharedMappings)
|
|
46
|
+
.filter(([_path]) => !_path.includes('*'))
|
|
47
|
+
.reduce((acc, [_path, _import]) => ({ ...acc, [_path]: _import }), {});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return { config, options: normalizedOptions };
|
|
51
|
+
}
|
|
52
|
+
export function resolveProjectName(name) {
|
|
53
|
+
if (!name || name.length < 1) {
|
|
54
|
+
logger.warn("Project name in 'federation.config.js' is empty, defaulting to 'shell' cache folder (could collide with other projects in the workspace).");
|
|
55
|
+
return 'shell';
|
|
56
|
+
}
|
|
57
|
+
return normalizePackageName(name);
|
|
58
|
+
}
|
|
@@ -5,10 +5,10 @@ import { logger } from '../utils/logger.js';
|
|
|
5
5
|
import { AbortedError } from '../utils/errors.js';
|
|
6
6
|
export async function rebuildForFederation(config, fedOptions, externals, modifiedFiles, signal) {
|
|
7
7
|
const federationCache = fedOptions.federationCache;
|
|
8
|
+
logger.info(`Re-bundling all internal libraries and exposed modules..'`);
|
|
8
9
|
const start = process.hrtime();
|
|
9
|
-
// Shared mappings and exposed modules
|
|
10
10
|
const artifactInfo = await bundleExposedAndMappings(config, fedOptions, externals, modifiedFiles, signal);
|
|
11
|
-
logger.measure(start, '
|
|
11
|
+
logger.measure(start, 'To re-bundle all internal libraries and exposed modules.');
|
|
12
12
|
if (signal?.aborted)
|
|
13
13
|
throw new AbortedError('[buildForFederation] After exposed-and-mappings bundle');
|
|
14
14
|
const exposedInfo = !artifactInfo ? describeExposed(config, fedOptions) : artifactInfo.exposes;
|
|
@@ -5,7 +5,8 @@ export interface ExternalConfig {
|
|
|
5
5
|
version?: string;
|
|
6
6
|
includeSecondaries?: boolean;
|
|
7
7
|
platform?: 'browser' | 'node';
|
|
8
|
-
build?: '
|
|
8
|
+
build?: 'separate' | 'package';
|
|
9
|
+
chunks?: boolean;
|
|
9
10
|
shareScope?: string;
|
|
10
11
|
packageInfo?: {
|
|
11
12
|
entryPoint: string;
|
|
@@ -20,6 +21,7 @@ export interface NormalizedExternalConfig {
|
|
|
20
21
|
version?: string;
|
|
21
22
|
includeSecondaries?: boolean;
|
|
22
23
|
shareScope?: string;
|
|
24
|
+
chunks: boolean;
|
|
23
25
|
platform: 'browser' | 'node';
|
|
24
26
|
build: 'default' | 'separate' | 'package';
|
|
25
27
|
packageInfo?: {
|
|
@@ -1,29 +1,35 @@
|
|
|
1
1
|
import type { PreparedSkipList, SkipList } from './skip-list.contract.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { PathToImport } from '../utils/mapped-path.contract.js';
|
|
3
3
|
import type { NormalizedSharedExternalsConfig, SharedExternalsConfig } from './external-config.contract.js';
|
|
4
4
|
export interface FederationConfig {
|
|
5
5
|
name?: string;
|
|
6
6
|
exposes?: Record<string, string>;
|
|
7
7
|
shared?: SharedExternalsConfig;
|
|
8
|
+
platform?: 'browser' | 'node';
|
|
8
9
|
sharedMappings?: Array<string>;
|
|
10
|
+
chunks?: boolean;
|
|
9
11
|
skip?: SkipList;
|
|
10
12
|
externals?: string[];
|
|
11
13
|
shareScope?: string;
|
|
12
14
|
features?: {
|
|
13
15
|
mappingVersion?: boolean;
|
|
14
16
|
ignoreUnusedDeps?: boolean;
|
|
17
|
+
denseChunking?: boolean;
|
|
15
18
|
};
|
|
16
19
|
}
|
|
17
20
|
export interface NormalizedFederationConfig {
|
|
21
|
+
$type: 'classic';
|
|
18
22
|
name: string;
|
|
19
23
|
exposes: Record<string, string>;
|
|
20
24
|
shared: NormalizedSharedExternalsConfig;
|
|
21
|
-
sharedMappings:
|
|
25
|
+
sharedMappings: PathToImport;
|
|
22
26
|
skip: PreparedSkipList;
|
|
27
|
+
chunks: boolean;
|
|
23
28
|
externals: string[];
|
|
24
29
|
shareScope?: string;
|
|
25
30
|
features: {
|
|
26
31
|
mappingVersion: boolean;
|
|
27
32
|
ignoreUnusedDeps: boolean;
|
|
33
|
+
denseChunking: boolean;
|
|
28
34
|
};
|
|
29
35
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { PathToImport } from '../utils/mapped-path.contract.js';
|
|
2
2
|
import type { FederationCache } from './federation-cache.contract.js';
|
|
3
|
+
export interface NFBuildAdapterContext<TBundlerContext = unknown> {
|
|
4
|
+
ctx: TBundlerContext;
|
|
5
|
+
outdir: string;
|
|
6
|
+
dev: boolean;
|
|
7
|
+
name: string;
|
|
8
|
+
isMappingOrExposed: boolean;
|
|
9
|
+
}
|
|
3
10
|
export interface NFBuildAdapter {
|
|
4
|
-
setup(options: NFBuildAdapterOptions): Promise<void>;
|
|
11
|
+
setup(name: string, options: NFBuildAdapterOptions): Promise<void>;
|
|
5
12
|
build(name: string, opts?: {
|
|
6
|
-
|
|
13
|
+
modifiedFiles?: string[];
|
|
7
14
|
signal?: AbortSignal;
|
|
8
15
|
}): Promise<NFBuildAdapterResult[]>;
|
|
9
16
|
dispose(name?: string): Promise<void>;
|
|
@@ -18,9 +25,8 @@ export interface NFBuildAdapterOptions<TBundlerCache = unknown> {
|
|
|
18
25
|
tsConfigPath?: string;
|
|
19
26
|
external: string[];
|
|
20
27
|
outdir: string;
|
|
21
|
-
mappedPaths:
|
|
22
|
-
|
|
23
|
-
isNodeModules: boolean;
|
|
28
|
+
mappedPaths: PathToImport;
|
|
29
|
+
isMappingOrExposed: boolean;
|
|
24
30
|
dev?: boolean;
|
|
25
31
|
watch?: boolean;
|
|
26
32
|
chunks?: boolean;
|
|
@@ -4,19 +4,19 @@ export interface FederationOptions {
|
|
|
4
4
|
workspaceRoot: string;
|
|
5
5
|
outputPath: string;
|
|
6
6
|
federationConfig: string;
|
|
7
|
+
projectName?: string;
|
|
7
8
|
cacheExternalArtifacts?: boolean;
|
|
8
|
-
chunks?: boolean | {
|
|
9
|
-
enable: boolean;
|
|
10
|
-
dense?: boolean;
|
|
11
|
-
};
|
|
12
9
|
tsConfig?: string;
|
|
13
10
|
verbose?: boolean;
|
|
14
11
|
dev?: boolean;
|
|
15
12
|
watch?: boolean;
|
|
16
13
|
packageJson?: string;
|
|
17
|
-
|
|
14
|
+
entryPoints?: string[];
|
|
18
15
|
buildNotifications?: BuildNotificationOptions;
|
|
19
16
|
}
|
|
20
17
|
export interface NormalizedFederationOptions<TBundlerCache = unknown> extends FederationOptions {
|
|
21
18
|
federationCache: FederationCache<TBundlerCache>;
|
|
19
|
+
entryPoints: string[];
|
|
20
|
+
projectName: string;
|
|
21
|
+
cacheExternalArtifacts: boolean;
|
|
22
22
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type { SharedInfo, FederationInfo, ExposesInfo, ArtifactInfo, ChunkInfo, } from './federation-info.contract.js';
|
|
2
2
|
export { type BuildNotificationOptions, BuildNotificationType, } from './build-notification-options.contract.js';
|
|
3
3
|
export type { FederationOptions, NormalizedFederationOptions, } from './federation-options.contract.js';
|
|
4
|
-
export type { EntryPoint, NFBuildAdapterOptions, NFBuildAdapter, NFBuildAdapterResult, } from './build-adapter.contract.js';
|
|
4
|
+
export type { EntryPoint, NFBuildAdapterOptions, NFBuildAdapter, NFBuildAdapterResult, NFBuildAdapterContext, } from './build-adapter.contract.js';
|
|
5
5
|
export { CHUNK_PREFIX, toChunkImport } from './chunk.js';
|
|
6
6
|
export type { FederationCache } from './federation-cache.contract.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface NfFileWatcherOptions {
|
|
2
|
+
onChange?: (path: string) => void;
|
|
3
|
+
}
|
|
4
|
+
export interface NfFileWatcher {
|
|
5
|
+
addPaths(paths: string | readonly string[]): void;
|
|
6
|
+
close(): Promise<void>;
|
|
7
|
+
get(): ReadonlySet<string>;
|
|
8
|
+
clear(): void;
|
|
9
|
+
mutate(fn: (dirtyPaths: Set<string>) => void): void;
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export type { KeyValuePair } from './keyvaluepair.contract.js';
|
|
2
|
-
export type {
|
|
2
|
+
export type { PathToImport } from './mapped-path.contract.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { NfFileWatcher, NfFileWatcherOptions } from '../domain/utils/file-watcher.contract.js';
|
|
2
|
+
export declare function createNfWatcher(options?: NfFileWatcherOptions): NfFileWatcher;
|
|
3
|
+
export declare function syncNfFileWatcher(watcher: NfFileWatcher, bundlerCache: {
|
|
4
|
+
keys(): IterableIterator<string>;
|
|
5
|
+
}): void;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { logger } from '@softarc/native-federation/internal';
|
|
2
|
+
import { watch, statSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
const toUnix = (p) => p.replace(/\\/g, '/');
|
|
5
|
+
export function createNfWatcher(options = {}) {
|
|
6
|
+
const { onChange } = options;
|
|
7
|
+
const watchers = new Map();
|
|
8
|
+
const dirtyPaths = new Set();
|
|
9
|
+
const notify = (path) => {
|
|
10
|
+
if (onChange)
|
|
11
|
+
onChange(path);
|
|
12
|
+
else
|
|
13
|
+
dirtyPaths.add(path);
|
|
14
|
+
};
|
|
15
|
+
return {
|
|
16
|
+
addPaths(paths) {
|
|
17
|
+
const list = typeof paths === 'string' ? [paths] : [...paths];
|
|
18
|
+
for (const p of list) {
|
|
19
|
+
if (watchers.has(p))
|
|
20
|
+
continue;
|
|
21
|
+
try {
|
|
22
|
+
const isDir = statSync(p).isDirectory();
|
|
23
|
+
const w = isDir
|
|
24
|
+
? watch(p, { recursive: true }, (_, filename) => {
|
|
25
|
+
if (filename)
|
|
26
|
+
notify(toUnix(join(p, filename)));
|
|
27
|
+
})
|
|
28
|
+
: watch(p, () => notify(toUnix(p)));
|
|
29
|
+
watchers.set(p, w);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
logger.debug(`Could not watch path '${p}'.`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
get: () => dirtyPaths,
|
|
37
|
+
clear: () => dirtyPaths.clear(),
|
|
38
|
+
mutate: fn => fn(dirtyPaths),
|
|
39
|
+
async close() {
|
|
40
|
+
for (const w of watchers.values()) {
|
|
41
|
+
w.close();
|
|
42
|
+
}
|
|
43
|
+
watchers.clear();
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export function syncNfFileWatcher(watcher, bundlerCache) {
|
|
48
|
+
const files = [...bundlerCache.keys()].filter(k => !k.includes('node_modules'));
|
|
49
|
+
if (files.length)
|
|
50
|
+
watcher.addPaths(files);
|
|
51
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type PathToImport } from '../domain/utils/mapped-path.contract.js';
|
|
2
|
+
import { type UsedDependencies } from '../domain/utils/used-dependencies.contract.js';
|
|
3
|
+
export declare function getUsedDependenciesFactory(workspaceRoot: string, fallbackEntryPoints?: string[]): (config: {
|
|
4
|
+
name?: string;
|
|
5
|
+
exposes?: Record<string, string>;
|
|
6
|
+
sharedMappings: PathToImport;
|
|
7
|
+
}) => UsedDependencies;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { getProjectData } from '@softarc/sheriff-core';
|
|
2
|
+
import { cwd } from 'process';
|
|
3
|
+
import { getPackageInfo } from './package-info.js';
|
|
4
|
+
import { getExternalImports as extractExternalImports } from './get-external-imports.js';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
export function getUsedDependenciesFactory(workspaceRoot, fallbackEntryPoints) {
|
|
7
|
+
return config => {
|
|
8
|
+
let entryPoints = Object.values(config.exposes ?? {});
|
|
9
|
+
if (entryPoints.length < 1)
|
|
10
|
+
entryPoints = fallbackEntryPoints;
|
|
11
|
+
if (!entryPoints || entryPoints.length < 1)
|
|
12
|
+
throw new Error('[removeUnusedDeps] native-federation is missing an entryPoint! You can set it using the Federation options or by setting an exposed module in the Federation config file.');
|
|
13
|
+
const fileInfos = Object.values(entryPoints ?? []).reduce((acc, entryPoint) => ({
|
|
14
|
+
...acc,
|
|
15
|
+
...getProjectData(entryPoint, cwd(), {
|
|
16
|
+
includeExternalLibraries: true,
|
|
17
|
+
}),
|
|
18
|
+
}), {});
|
|
19
|
+
const usedPackageNames = new Set();
|
|
20
|
+
for (const fileInfo of Object.values(fileInfos)) {
|
|
21
|
+
for (const pckg of [
|
|
22
|
+
...(fileInfo?.externalLibraries || []),
|
|
23
|
+
...(fileInfo?.unresolvedImports || []),
|
|
24
|
+
]) {
|
|
25
|
+
usedPackageNames.add(pckg);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
external: addTransientDeps(usedPackageNames, workspaceRoot),
|
|
30
|
+
internal: resolveUsedMappings(fileInfos, workspaceRoot, config.sharedMappings),
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function addTransientDeps(packages, workspaceRoot) {
|
|
35
|
+
const packagesAndPeers = new Set([...packages]);
|
|
36
|
+
const discovered = new Set(packagesAndPeers);
|
|
37
|
+
const stack = [...packagesAndPeers];
|
|
38
|
+
while (stack.length > 0) {
|
|
39
|
+
const dep = stack.pop();
|
|
40
|
+
if (!dep) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const pInfo = getPackageInfo(dep, workspaceRoot);
|
|
44
|
+
if (!pInfo) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const peerDeps = extractExternalImports(pInfo.entryPoint);
|
|
48
|
+
for (const peerDep of peerDeps) {
|
|
49
|
+
if (!discovered.has(peerDep)) {
|
|
50
|
+
discovered.add(peerDep);
|
|
51
|
+
stack.push(peerDep);
|
|
52
|
+
packagesAndPeers.add(peerDep);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return packagesAndPeers;
|
|
57
|
+
}
|
|
58
|
+
function resolveUsedMappings(fileInfos, workspaceRoot, sharedMappings) {
|
|
59
|
+
const usedMappings = {};
|
|
60
|
+
for (const fileName of Object.keys(fileInfos)) {
|
|
61
|
+
const fullFileName = path.join(workspaceRoot, fileName);
|
|
62
|
+
if (isSharedMapping(fullFileName, sharedMappings))
|
|
63
|
+
continue;
|
|
64
|
+
const fileInfo = fileInfos[fileName];
|
|
65
|
+
if (!fileInfo)
|
|
66
|
+
continue;
|
|
67
|
+
// Check if any of this file's imports land in a shared mapping
|
|
68
|
+
for (const imp of fileInfo.imports ?? []) {
|
|
69
|
+
const fullImport = path.join(workspaceRoot, imp);
|
|
70
|
+
const match = matchMapping(fullImport, sharedMappings);
|
|
71
|
+
if (match)
|
|
72
|
+
usedMappings[fullImport] = match;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return usedMappings;
|
|
76
|
+
}
|
|
77
|
+
function isSharedMapping(filePath, sharedMappings) {
|
|
78
|
+
for (const sharedPath of Object.keys(sharedMappings)) {
|
|
79
|
+
const asteriskIndex = sharedPath.indexOf('*');
|
|
80
|
+
if (asteriskIndex !== -1) {
|
|
81
|
+
const prefix = sharedPath.substring(0, asteriskIndex);
|
|
82
|
+
if (filePath.startsWith(prefix))
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
else if (filePath.startsWith(sharedPath + path.sep) || filePath === sharedPath) {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
function matchMapping(filePath, sharedMappings) {
|
|
92
|
+
for (const [sharedPath, sharedImport] of Object.entries(sharedMappings)) {
|
|
93
|
+
const asteriskIndex = sharedPath.indexOf('*');
|
|
94
|
+
if (asteriskIndex !== -1) {
|
|
95
|
+
const prefix = sharedPath.substring(0, asteriskIndex);
|
|
96
|
+
const suffix = sharedPath.substring(asteriskIndex + 1);
|
|
97
|
+
if (!filePath.startsWith(prefix))
|
|
98
|
+
continue;
|
|
99
|
+
if (suffix && !filePath.includes(suffix))
|
|
100
|
+
continue;
|
|
101
|
+
const captured = suffix
|
|
102
|
+
? filePath.slice(prefix.length, filePath.indexOf(suffix, prefix.length))
|
|
103
|
+
: filePath.slice(prefix.length);
|
|
104
|
+
return sharedImport.replace('*', toImportPath(captured));
|
|
105
|
+
}
|
|
106
|
+
else if (filePath === sharedPath || isIndexOf(filePath, sharedPath)) {
|
|
107
|
+
return sharedImport;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Detect if it's a barrel file which is inferred by typescript
|
|
114
|
+
*/
|
|
115
|
+
const INDEX_PATTERN = /\/index\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/;
|
|
116
|
+
function isIndexOf(filePath, dirPath) {
|
|
117
|
+
return filePath.startsWith(dirPath + path.sep) && INDEX_PATTERN.test(filePath);
|
|
118
|
+
}
|
|
119
|
+
function toImportPath(filePath) {
|
|
120
|
+
const withoutExt = filePath.replace(/\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/, '');
|
|
121
|
+
const normalized = withoutExt.replace(/\\/g, '/');
|
|
122
|
+
return normalized.endsWith('/index') ? normalized.slice(0, -6) : normalized;
|
|
123
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export declare function
|
|
1
|
+
import type { PathToImport } from '../domain/utils/mapped-path.contract.js';
|
|
2
|
+
/**
|
|
3
|
+
* Will return user defined and tsconfig defined paths including their imports, might contain wildcards
|
|
4
|
+
* @param param0
|
|
5
|
+
* @returns
|
|
6
|
+
*/
|
|
7
|
+
export declare function getRawMappedPaths(rootTsConfigPath: string, configuredSharedMappings?: string[], rootPath?: string): PathToImport;
|
|
@@ -1,31 +1,33 @@
|
|
|
1
1
|
import * as path from 'path';
|
|
2
2
|
import * as fs from 'fs';
|
|
3
3
|
import JSON5 from 'json5';
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Will return user defined and tsconfig defined paths including their imports, might contain wildcards
|
|
6
|
+
* @param param0
|
|
7
|
+
* @returns
|
|
8
|
+
*/
|
|
9
|
+
export function getRawMappedPaths(rootTsConfigPath, configuredSharedMappings, rootPath) {
|
|
10
|
+
const mappedPaths = {};
|
|
6
11
|
if (!path.isAbsolute(rootTsConfigPath)) {
|
|
7
12
|
throw new Error('SharedMappings.register: tsConfigPath needs to be an absolute path!');
|
|
8
13
|
}
|
|
9
14
|
if (!rootPath) {
|
|
10
15
|
rootPath = path.normalize(path.dirname(rootTsConfigPath));
|
|
11
16
|
}
|
|
12
|
-
const shareAll = !
|
|
13
|
-
if (!
|
|
14
|
-
|
|
17
|
+
const shareAll = !configuredSharedMappings;
|
|
18
|
+
if (!configuredSharedMappings) {
|
|
19
|
+
configuredSharedMappings = [];
|
|
15
20
|
}
|
|
16
21
|
const tsConfig = JSON5.parse(fs.readFileSync(rootTsConfigPath, { encoding: 'utf-8' }));
|
|
17
22
|
const mappings = tsConfig?.compilerOptions?.paths;
|
|
18
23
|
if (!mappings) {
|
|
19
|
-
return
|
|
24
|
+
return mappedPaths;
|
|
20
25
|
}
|
|
21
26
|
for (const key in mappings) {
|
|
22
27
|
const libPath = path.normalize(path.join(rootPath, mappings[key][0]));
|
|
23
|
-
if (
|
|
24
|
-
|
|
25
|
-
key,
|
|
26
|
-
path: libPath,
|
|
27
|
-
});
|
|
28
|
+
if (configuredSharedMappings.includes(key) || shareAll) {
|
|
29
|
+
mappedPaths[libPath] = key;
|
|
28
30
|
}
|
|
29
31
|
}
|
|
30
|
-
return
|
|
32
|
+
return mappedPaths;
|
|
31
33
|
}
|
|
@@ -1,2 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
export type KeyValuePair = {
|
|
2
|
+
key: string;
|
|
3
|
+
value: string;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Resolves tsconfig wildcard paths.
|
|
7
|
+
*
|
|
8
|
+
* In tsconfig.json, paths like `@features/*` → `libs/features/src/*` work as follows:
|
|
9
|
+
* - The `*` captures a single path segment (the module name)
|
|
10
|
+
* - When importing `@features/feature-a`, TypeScript captures `feature-a`
|
|
11
|
+
* - It then replaces `*` in the value pattern: `libs/features/src/feature-a`
|
|
12
|
+
*
|
|
13
|
+
* For discovery, we find all directories at the wildcard position that TypeScript
|
|
14
|
+
* would recognize as valid modules (directories with index files or package.json).
|
|
15
|
+
*
|
|
16
|
+
// @see https://www.typescriptlang.org/docs/handbook/modules/theory.html#module-resolution
|
|
17
|
+
*/
|
|
18
|
+
export declare function resolveTsConfigWildcard(keyPattern: string, valuePattern: string, cwd: string): KeyValuePair[];
|
|
19
|
+
/**
|
|
20
|
+
* Resolves package.json exports wildcard patterns.
|
|
21
|
+
*
|
|
22
|
+
* In package.json exports, patterns like `./features/*.js` → `./src/features/*.js` work as follows:
|
|
23
|
+
* - The `*` is a literal string replacement that can include path separators
|
|
24
|
+
* - Importing `pkg/features/a/b.js` captures `a/b` and replaces `*` → `./src/features/a/b.js`
|
|
25
|
+
* - This matches actual files, not directories
|
|
26
|
+
*
|
|
27
|
+
* @see https://nodejs.org/api/packages.html#subpath-patterns
|
|
28
|
+
*/
|
|
29
|
+
export declare function resolvePackageJsonExportsWildcard(keyPattern: string, valuePattern: string, cwd: string): KeyValuePair[];
|