@softarc/native-federation 4.3.2 → 4.4.1
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/README.md +71 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.js +2 -0
- package/dist/internal.d.ts +3 -2
- package/dist/internal.js +5 -1
- package/dist/lib/config/expand-mappings.d.ts +24 -0
- package/dist/lib/config/expand-mappings.js +88 -0
- package/dist/lib/config/get-used-dependencies.d.ts +0 -2
- package/dist/lib/config/get-used-dependencies.js +4 -40
- package/dist/lib/config/mapped-paths.d.ts +7 -2
- package/dist/lib/config/mapped-paths.js +21 -4
- package/dist/lib/config/mapping-utils.d.ts +32 -0
- package/dist/lib/config/mapping-utils.js +68 -0
- package/dist/lib/config/match-mapping.d.ts +9 -0
- package/dist/lib/config/match-mapping.js +46 -0
- package/dist/lib/config/remove-unused-deps.d.ts +2 -1
- package/dist/lib/config/remove-unused-deps.js +35 -2
- package/dist/lib/config/validate-mappings.d.ts +15 -0
- package/dist/lib/config/validate-mappings.js +28 -0
- package/dist/lib/config/with-native-federation.js +18 -6
- package/dist/lib/core/build/assemble-federation-info.d.ts +8 -0
- package/dist/lib/core/build/assemble-federation-info.js +34 -0
- package/dist/lib/core/build/build-for-federation.js +7 -45
- package/dist/lib/core/build/bundle-exposed-and-mappings.d.ts +1 -3
- package/dist/lib/core/build/bundle-exposed-and-mappings.js +20 -32
- package/dist/lib/core/build/bundle-shared.js +9 -3
- package/dist/lib/core/build/rebuild-for-federation.js +6 -33
- package/dist/lib/core/build/resolve-shared-dirs.d.ts +16 -0
- package/dist/lib/core/build/resolve-shared-dirs.js +8 -4
- package/dist/lib/core/cache/cache-persistence.d.ts +4 -2
- package/dist/lib/core/cache/cache-persistence.js +27 -8
- package/dist/lib/core/normalize-options.d.ts +2 -2
- package/dist/lib/core/normalize-options.js +23 -11
- package/dist/lib/core/output/write-federation-outputs.d.ts +7 -0
- package/dist/lib/core/output/write-federation-outputs.js +9 -0
- package/dist/lib/domain/config/federation-config.contract.d.ts +27 -2
- package/dist/lib/domain/utils/file-watcher.contract.d.ts +12 -0
- package/dist/lib/domain/utils/io-port.contract.d.ts +6 -3
- package/dist/lib/utils/file-watcher.d.ts +15 -4
- package/dist/lib/utils/file-watcher.js +112 -19
- package/dist/lib/utils/io/node-io-adapter.js +10 -5
- package/dist/lib/utils/package/entry-point-resolver.js +2 -5
- package/dist/lib/utils/package/package-info.d.ts +13 -0
- package/dist/lib/utils/package/package-info.js +30 -4
- package/dist/lib/utils/package/resolve-wildcard-keys.js +8 -2
- package/dist/lib/utils/path-patterns.d.ts +14 -0
- package/dist/lib/utils/path-patterns.js +12 -0
- package/package.json +5 -5
|
@@ -1,16 +1,40 @@
|
|
|
1
1
|
import type { PreparedSkipList, SkipList } from './skip-list.contract.js';
|
|
2
2
|
import type { PathToImport } from '../utils/mapped-path.contract.js';
|
|
3
|
-
import type { NormalizedSharedExternalsConfig, SharedExternalsConfig } from './external-config.contract.js';
|
|
3
|
+
import type { ExternalConfig, NormalizedSharedExternalsConfig, SharedExternalsConfig } from './external-config.contract.js';
|
|
4
4
|
export type ExposeEntry = {
|
|
5
5
|
file: string;
|
|
6
6
|
element?: string;
|
|
7
7
|
};
|
|
8
|
+
export type SharedMappingEntry = string | [string[], ExternalConfig];
|
|
9
|
+
/** Selection pattern -> config, kept in declaration order: first match wins. */
|
|
10
|
+
export type SharedMappingConfigs = Record<string, ExternalConfig>;
|
|
11
|
+
/**
|
|
12
|
+
* Only the subset of `ExternalConfig` a mapping can act on — mappings all share the
|
|
13
|
+
* single `mapping-or-exposed` bundle, so `build`/`platform`/`chunks`/`packageInfo`
|
|
14
|
+
* have nothing to select. `requiredVersion` and `version` stay optional because their
|
|
15
|
+
* defaults are read from the mapped lib's package.json at build time.
|
|
16
|
+
*
|
|
17
|
+
* `includeSecondaries` collapses to a boolean exactly as it does for a shared external:
|
|
18
|
+
* it means "exempt from `ignoreUnusedDeps` pruning". `resolveGlob` is lifted out of it
|
|
19
|
+
* because a mapping has no secondary entry points for it to apply to.
|
|
20
|
+
*/
|
|
21
|
+
export interface NormalizedMappingConfig {
|
|
22
|
+
singleton: boolean;
|
|
23
|
+
strictVersion: boolean;
|
|
24
|
+
requiredVersion?: string;
|
|
25
|
+
version?: string;
|
|
26
|
+
shareScope?: string;
|
|
27
|
+
pool?: string;
|
|
28
|
+
includeSecondaries?: boolean;
|
|
29
|
+
resolveGlob?: boolean;
|
|
30
|
+
}
|
|
31
|
+
export type NormalizedSharedMappingConfigs = Record<string, NormalizedMappingConfig>;
|
|
8
32
|
export interface FederationConfig {
|
|
9
33
|
name?: string;
|
|
10
34
|
exposes?: Record<string, string | ExposeEntry>;
|
|
11
35
|
shared?: SharedExternalsConfig;
|
|
12
36
|
platform?: 'browser' | 'node';
|
|
13
|
-
sharedMappings?: Array<
|
|
37
|
+
sharedMappings?: Array<SharedMappingEntry>;
|
|
14
38
|
chunks?: boolean;
|
|
15
39
|
skip?: SkipList;
|
|
16
40
|
externals?: string[];
|
|
@@ -30,6 +54,7 @@ export interface NormalizedFederationConfig {
|
|
|
30
54
|
exposes: Record<string, ExposeEntry>;
|
|
31
55
|
shared: NormalizedSharedExternalsConfig;
|
|
32
56
|
sharedMappings: PathToImport;
|
|
57
|
+
sharedMappingsConfig: NormalizedSharedMappingConfigs;
|
|
33
58
|
skip: PreparedSkipList;
|
|
34
59
|
chunks: boolean;
|
|
35
60
|
externals: string[];
|
|
@@ -2,6 +2,18 @@ export interface NfFileWatcherOptions {
|
|
|
2
2
|
onChange?: (path: string) => void;
|
|
3
3
|
pollIntervalMs?: number;
|
|
4
4
|
debounceMs?: number;
|
|
5
|
+
/** Drop events whose mtime is unchanged since the last one seen for that path.
|
|
6
|
+
* macOS FSEvents re-delivers 'changed' for recently edited files roughly every
|
|
7
|
+
* 30s; with a watch list of a few thousand sources that replay alone keeps a
|
|
8
|
+
* rebuild loop awake forever. Default: true. */
|
|
9
|
+
dedupeReplays?: boolean;
|
|
10
|
+
/** Grace period after a path's recorded mtime during which an event whose mtime
|
|
11
|
+
* and byte length both match still passes, covering a second save inside one
|
|
12
|
+
* mtime tick. The 2000 default is twice the coarsest mtime granularity in play
|
|
13
|
+
* (1s on gRPC-FUSE/NFS/WSL2 drvfs/HFS+); raise it for a filesystem coarser than
|
|
14
|
+
* that, or for a network mount whose server clock runs behind the client.
|
|
15
|
+
* See AGENTS.md "Replay dedupe". Default: 2000. */
|
|
16
|
+
replayGraceMs?: number;
|
|
5
17
|
}
|
|
6
18
|
interface AddPathsOptions {
|
|
7
19
|
poll?: boolean;
|
|
@@ -5,6 +5,8 @@ export interface Digest {
|
|
|
5
5
|
}
|
|
6
6
|
export interface StatInfo {
|
|
7
7
|
mtimeMs: number;
|
|
8
|
+
/** Byte length; 0 for directories. */
|
|
9
|
+
size: number;
|
|
8
10
|
isSymbolicLink: boolean;
|
|
9
11
|
}
|
|
10
12
|
export interface FileReaderPort {
|
|
@@ -29,6 +31,7 @@ export interface FileWriterPort {
|
|
|
29
31
|
export interface GlobPort {
|
|
30
32
|
globFiles(pattern: string, opts: {
|
|
31
33
|
cwd: string;
|
|
34
|
+
ignore?: string[];
|
|
32
35
|
}): string[];
|
|
33
36
|
}
|
|
34
37
|
export interface HashPort {
|
|
@@ -45,9 +48,9 @@ interface WatchOptions {
|
|
|
45
48
|
}
|
|
46
49
|
export interface WatchPort {
|
|
47
50
|
/**
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
+
* `onEvent` receives the changed entry's filename relative to `path` (null when
|
|
52
|
+
* the platform omits it). Watching a file reports that file's own basename, so a
|
|
53
|
+
* caller that registered a file can ignore the argument.
|
|
51
54
|
*/
|
|
52
55
|
watch(path: string, opts: WatchOptions, onEvent: (filename: string | null) => void): WatchHandle;
|
|
53
56
|
}
|
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
import type { WatchPort, FileReaderPort } from '../domain/utils/io-port.contract.js';
|
|
2
2
|
import type { NfFileWatcher, NfFileWatcherOptions } from '../domain/utils/file-watcher.contract.js';
|
|
3
3
|
export declare function createNfWatcher(options?: NfFileWatcherOptions): NfFileWatcher;
|
|
4
|
-
export declare function createNfWatcherCore(io: WatchPort & FileReaderPort, options?: NfFileWatcherOptions): NfFileWatcher;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
export declare function createNfWatcherCore(io: WatchPort & FileReaderPort, options?: NfFileWatcherOptions, now?: () => number): NfFileWatcher;
|
|
5
|
+
/** A bundler cache keyed by input path, or the input paths themselves. */
|
|
6
|
+
export type WatchSources = Iterable<string> | {
|
|
7
|
+
keys(): Iterable<string>;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Subscribe the watcher to the inputs the last build compiled.
|
|
11
|
+
*
|
|
12
|
+
* Passing a bundler cache only works when it is keyed by input path. A cache that
|
|
13
|
+
* records its inputs elsewhere yields an empty watch set and, silently, a dev server
|
|
14
|
+
* that serves stale bundles until restart — Angular's `SourceFileCache` extends `Map`
|
|
15
|
+
* but keeps its inputs in `typeScriptFileCache`/`referencedFiles`, which is
|
|
16
|
+
* angular-adapter#94. Adapters over such a cache must expand it and pass the paths.
|
|
17
|
+
*/
|
|
18
|
+
export declare function syncNfFileWatcher(watcher: NfFileWatcher, sources: WatchSources, linkedDirs?: readonly string[]): void;
|
|
@@ -1,29 +1,82 @@
|
|
|
1
|
-
import { join } from "path";
|
|
1
|
+
import { dirname, join } from "path";
|
|
2
2
|
import { nodeIo } from "./io/node-io-adapter.js";
|
|
3
3
|
import { logger } from "./logger.js";
|
|
4
|
-
import { toPosix } from "./path-patterns.js";
|
|
4
|
+
import { isUnderDir, toPosix } from "./path-patterns.js";
|
|
5
5
|
function createNfWatcher(options = {}) {
|
|
6
6
|
return createNfWatcherCore(nodeIo, options);
|
|
7
7
|
}
|
|
8
|
-
function createNfWatcherCore(io, options = {}) {
|
|
8
|
+
function createNfWatcherCore(io, options = {}, now = Date.now) {
|
|
9
9
|
const { onChange } = options;
|
|
10
10
|
const pollIntervalMs = options.pollIntervalMs ?? 300;
|
|
11
11
|
const debounceMs = options.debounceMs ?? 0;
|
|
12
|
+
const dedupeReplays = options.dedupeReplays ?? true;
|
|
13
|
+
const replayGraceMs = options.replayGraceMs ?? 2e3;
|
|
12
14
|
const watchers = /* @__PURE__ */ new Map();
|
|
13
15
|
const dirtyPaths = /* @__PURE__ */ new Set();
|
|
14
|
-
const
|
|
16
|
+
const lastSeen = /* @__PURE__ */ new Map();
|
|
17
|
+
const trackedFiles = /* @__PURE__ */ new Set();
|
|
18
|
+
const fileDirWatchers = /* @__PURE__ */ new Map();
|
|
19
|
+
const dirKey = (p) => {
|
|
20
|
+
const posix = toPosix(p);
|
|
21
|
+
return posix.length > 1 ? posix.replace(/\/+$/, "") : posix;
|
|
22
|
+
};
|
|
23
|
+
const covers = (path, poll) => {
|
|
24
|
+
for (const [dir, watch] of watchers) {
|
|
25
|
+
if ((watch.poll || !poll) && isUnderDir(path, dir)) return true;
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
};
|
|
29
|
+
const supersede = (dir, poll) => {
|
|
30
|
+
for (const map of [watchers, fileDirWatchers]) {
|
|
31
|
+
for (const [key, watch] of map) {
|
|
32
|
+
if (watch.poll && !poll) continue;
|
|
33
|
+
if (map === watchers && key === dir) continue;
|
|
34
|
+
if (!isUnderDir(key, dir)) continue;
|
|
35
|
+
watch.handle.close();
|
|
36
|
+
map.delete(key);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
const identityOf = (path) => {
|
|
41
|
+
let stat = io.stat(path);
|
|
42
|
+
if (stat?.isSymbolicLink) stat = io.stat(io.realpath(path));
|
|
43
|
+
return stat ? { mtimeMs: stat.mtimeMs, size: stat.size } : null;
|
|
44
|
+
};
|
|
45
|
+
const isReplay = (path, at) => {
|
|
46
|
+
const current = identityOf(path);
|
|
47
|
+
if (!current) {
|
|
48
|
+
lastSeen.delete(path);
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
const previous = lastSeen.get(path);
|
|
52
|
+
lastSeen.set(path, current);
|
|
53
|
+
if (!previous || previous.mtimeMs !== current.mtimeMs || previous.size !== current.size) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
return at - current.mtimeMs >= replayGraceMs;
|
|
57
|
+
};
|
|
58
|
+
let watchFailures = 0;
|
|
59
|
+
const watchFailed = (path) => {
|
|
60
|
+
if (++watchFailures > 1) return logger.debug(`Could not watch path '${path}'.`);
|
|
61
|
+
logger.warn(
|
|
62
|
+
`Could not watch '${path}'. Changes there will not trigger a rebuild; run with verbose logging to see any further watch failures.`
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
const deliver = (path, at) => {
|
|
66
|
+
if (dedupeReplays && isReplay(path, at)) return;
|
|
67
|
+
dirtyPaths.add(path);
|
|
15
68
|
if (onChange) onChange(path);
|
|
16
|
-
else dirtyPaths.add(path);
|
|
17
69
|
};
|
|
18
|
-
const pending = /* @__PURE__ */ new
|
|
70
|
+
const pending = /* @__PURE__ */ new Map();
|
|
19
71
|
let flushTimer;
|
|
20
72
|
const flush = () => {
|
|
21
|
-
for (const p of pending) deliver(p);
|
|
73
|
+
for (const [p, at] of pending) deliver(p, at);
|
|
22
74
|
pending.clear();
|
|
23
75
|
};
|
|
24
76
|
const notify = (path) => {
|
|
25
|
-
|
|
26
|
-
|
|
77
|
+
const at = now();
|
|
78
|
+
if (debounceMs <= 0) return deliver(path, at);
|
|
79
|
+
if (!pending.has(path)) pending.set(path, at);
|
|
27
80
|
if (flushTimer) clearTimeout(flushTimer);
|
|
28
81
|
flushTimer = setTimeout(flush, debounceMs);
|
|
29
82
|
flushTimer.unref?.();
|
|
@@ -31,16 +84,47 @@ function createNfWatcherCore(io, options = {}) {
|
|
|
31
84
|
return {
|
|
32
85
|
addPaths(paths, opts) {
|
|
33
86
|
const list = typeof paths === "string" ? [paths] : [...paths];
|
|
34
|
-
const
|
|
87
|
+
const shouldPoll = !!opts?.poll;
|
|
88
|
+
const poll = shouldPoll ? { intervalMs: pollIntervalMs } : void 0;
|
|
35
89
|
for (const p of list) {
|
|
36
|
-
if (
|
|
90
|
+
if (io.isDirectory(p)) {
|
|
91
|
+
const dir2 = dirKey(p);
|
|
92
|
+
if (watchers.has(dir2) || covers(dir2, shouldPoll)) continue;
|
|
93
|
+
try {
|
|
94
|
+
watchers.set(dir2, {
|
|
95
|
+
handle: io.watch(p, { recursive: true, poll }, (filename) => {
|
|
96
|
+
if (filename) notify(toPosix(join(p, filename)));
|
|
97
|
+
}),
|
|
98
|
+
poll: shouldPoll
|
|
99
|
+
});
|
|
100
|
+
} catch {
|
|
101
|
+
watchFailed(p);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
supersede(dir2, shouldPoll);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const key = toPosix(p);
|
|
108
|
+
if (trackedFiles.has(key)) continue;
|
|
109
|
+
trackedFiles.add(key);
|
|
110
|
+
if (dedupeReplays && !lastSeen.has(key)) {
|
|
111
|
+
const identity = identityOf(key);
|
|
112
|
+
if (identity) lastSeen.set(key, identity);
|
|
113
|
+
}
|
|
114
|
+
if (covers(key, shouldPoll)) continue;
|
|
115
|
+
const dir = dirKey(dirname(p));
|
|
116
|
+
if (fileDirWatchers.has(dir)) continue;
|
|
37
117
|
try {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
118
|
+
fileDirWatchers.set(dir, {
|
|
119
|
+
handle: io.watch(dir, { recursive: false, poll }, (filename) => {
|
|
120
|
+
if (!filename) return;
|
|
121
|
+
const changed = toPosix(join(dir, filename));
|
|
122
|
+
if (trackedFiles.has(changed)) notify(changed);
|
|
123
|
+
}),
|
|
124
|
+
poll: shouldPoll
|
|
125
|
+
});
|
|
42
126
|
} catch {
|
|
43
|
-
|
|
127
|
+
watchFailed(dir);
|
|
44
128
|
}
|
|
45
129
|
}
|
|
46
130
|
},
|
|
@@ -49,15 +133,24 @@ function createNfWatcherCore(io, options = {}) {
|
|
|
49
133
|
mutate: (fn) => fn(dirtyPaths),
|
|
50
134
|
async close() {
|
|
51
135
|
if (flushTimer) clearTimeout(flushTimer);
|
|
52
|
-
for (const handle of watchers.values()) {
|
|
136
|
+
for (const { handle } of [...watchers.values(), ...fileDirWatchers.values()]) {
|
|
53
137
|
handle.close();
|
|
54
138
|
}
|
|
55
139
|
watchers.clear();
|
|
140
|
+
fileDirWatchers.clear();
|
|
141
|
+
trackedFiles.clear();
|
|
142
|
+
lastSeen.clear();
|
|
143
|
+
watchFailures = 0;
|
|
56
144
|
}
|
|
57
145
|
};
|
|
58
146
|
}
|
|
59
|
-
function
|
|
60
|
-
|
|
147
|
+
function toPaths(sources) {
|
|
148
|
+
if (Array.isArray(sources)) return sources;
|
|
149
|
+
const cache = sources;
|
|
150
|
+
return typeof cache.keys === "function" ? cache.keys() : sources;
|
|
151
|
+
}
|
|
152
|
+
function syncNfFileWatcher(watcher, sources, linkedDirs = []) {
|
|
153
|
+
const files = [...toPaths(sources)].filter((k) => !k.includes("node_modules"));
|
|
61
154
|
if (files.length) watcher.addPaths(files);
|
|
62
155
|
if (linkedDirs.length) watcher.addPaths(linkedDirs, { poll: true });
|
|
63
156
|
}
|
|
@@ -43,7 +43,7 @@ const nodeIo = {
|
|
|
43
43
|
stat(path2) {
|
|
44
44
|
try {
|
|
45
45
|
const s = fs.lstatSync(path2);
|
|
46
|
-
return { mtimeMs: s.mtimeMs, isSymbolicLink: s.isSymbolicLink() };
|
|
46
|
+
return { mtimeMs: s.mtimeMs, size: s.size, isSymbolicLink: s.isSymbolicLink() };
|
|
47
47
|
} catch {
|
|
48
48
|
return null;
|
|
49
49
|
}
|
|
@@ -61,7 +61,12 @@ const nodeIo = {
|
|
|
61
61
|
fs.unlinkSync(path2);
|
|
62
62
|
},
|
|
63
63
|
globFiles(pattern, opts) {
|
|
64
|
-
return fg.sync(pattern, {
|
|
64
|
+
return fg.sync(pattern, {
|
|
65
|
+
cwd: opts.cwd,
|
|
66
|
+
ignore: opts.ignore,
|
|
67
|
+
onlyFiles: true,
|
|
68
|
+
deep: Infinity
|
|
69
|
+
});
|
|
65
70
|
},
|
|
66
71
|
hash(algorithm, data) {
|
|
67
72
|
const sum = crypto.createHash(algorithm).update(data);
|
|
@@ -72,11 +77,11 @@ const nodeIo = {
|
|
|
72
77
|
},
|
|
73
78
|
watch(watchPath, opts, onEvent) {
|
|
74
79
|
if (opts.poll) return pollWatch(watchPath, opts.recursive, opts.poll.intervalMs, onEvent);
|
|
75
|
-
const watcher =
|
|
80
|
+
const watcher = fs.watch(
|
|
76
81
|
watchPath,
|
|
77
|
-
{ recursive:
|
|
82
|
+
{ recursive: opts.recursive },
|
|
78
83
|
(_event, filename) => onEvent(filename ? filename.toString() : null)
|
|
79
|
-
)
|
|
84
|
+
);
|
|
80
85
|
return { close: () => watcher.close() };
|
|
81
86
|
}
|
|
82
87
|
};
|
|
@@ -44,7 +44,7 @@ function resolvePackageInfo(repo, packageName, directory) {
|
|
|
44
44
|
const version = mainPkgJson["version"];
|
|
45
45
|
const esm = mainPkgJson["type"] === "module";
|
|
46
46
|
if (!version) {
|
|
47
|
-
logger.
|
|
47
|
+
logger.debug("No version found for " + packageName + " in " + directory);
|
|
48
48
|
return null;
|
|
49
49
|
}
|
|
50
50
|
const pathToSecondary = path.relative(mainPkgName, packageName);
|
|
@@ -67,10 +67,7 @@ function resolvePackageInfo(repo, packageName, directory) {
|
|
|
67
67
|
const result = strategy(ctx);
|
|
68
68
|
if (result) return result;
|
|
69
69
|
}
|
|
70
|
-
logger.
|
|
71
|
-
logger.warn(
|
|
72
|
-
"If you don't need this package, skip it in your federation.config.js or consider moving it into depDependencies in your package.json"
|
|
73
|
-
);
|
|
70
|
+
logger.debug("No entry point found for " + packageName + " in " + directory);
|
|
74
71
|
return null;
|
|
75
72
|
}
|
|
76
73
|
export {
|
|
@@ -2,6 +2,19 @@ import type { PackageInfo, PackageJsonRepository, VersionMap } from '../../domai
|
|
|
2
2
|
export { sharedPackageJsonRepository } from '../io/package-json-repository.js';
|
|
3
3
|
export type { PackageInfo, VersionMap, ExportCondition, ExportEntry, } from '../../domain/utils/package-json.contract.js';
|
|
4
4
|
export { isESMExport } from './esm-detection.js';
|
|
5
|
+
export declare function tryGetPackageInfo(packageName: string, workspaceRoot: string, repo?: PackageJsonRepository): PackageInfo | null;
|
|
6
|
+
/**
|
|
7
|
+
* Resolve a package that is expected to be resolvable, warning once if it is not.
|
|
8
|
+
* Only use this where a failure is genuinely actionable, i.e. for packages the user
|
|
9
|
+
* asked to share.
|
|
10
|
+
*/
|
|
5
11
|
export declare function getPackageInfo(packageName: string, workspaceRoot: string, repo?: PackageJsonRepository): PackageInfo | null;
|
|
12
|
+
/**
|
|
13
|
+
* Installed version per key, from each key's package root. Resolution is deduped by root:
|
|
14
|
+
* `findDepPackageJson` trims to the root anyway, and that root package.json is also where
|
|
15
|
+
* `resolvePackageInfo` reads the `version` it records as `PackageInfo.version`, so a secondary
|
|
16
|
+
* yields the same string as its main entry point. Unresolvable keys map to ''.
|
|
17
|
+
*/
|
|
18
|
+
export declare function installedVersions(packageNames: string[], workspaceRoot: string, repo?: PackageJsonRepository): Record<string, string>;
|
|
6
19
|
export declare function getVersionMaps(project: string, workspace: string, repo?: PackageJsonRepository): VersionMap[];
|
|
7
20
|
export declare function findDepPackageJson(packageName: string, projectRoot: string, repo?: PackageJsonRepository): string | null;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { logger } from "../logger.js";
|
|
2
2
|
import { normalize } from "../normalize.js";
|
|
3
|
-
import { sharedPackageJsonRepository } from "../io/package-json-repository.js";
|
|
3
|
+
import { getPkgFolder, sharedPackageJsonRepository } from "../io/package-json-repository.js";
|
|
4
4
|
import { resolvePackageInfo } from "./entry-point-resolver.js";
|
|
5
5
|
import { getVersionMaps as getVersionMapsFromRepo } from "./version-maps.js";
|
|
6
6
|
import { sharedPackageJsonRepository as sharedPackageJsonRepository2 } from "../io/package-json-repository.js";
|
|
7
7
|
import { isESMExport } from "./esm-detection.js";
|
|
8
|
-
function
|
|
8
|
+
function tryGetPackageInfo(packageName, workspaceRoot, repo = sharedPackageJsonRepository) {
|
|
9
9
|
workspaceRoot = normalize(workspaceRoot, true);
|
|
10
10
|
for (const info of repo.getPackageJsonFiles(workspaceRoot, workspaceRoot)) {
|
|
11
11
|
const cand = resolvePackageInfo(repo, packageName, info.directory);
|
|
@@ -13,9 +13,33 @@ function getPackageInfo(packageName, workspaceRoot, repo = sharedPackageJsonRepo
|
|
|
13
13
|
return cand;
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
|
-
logger.warn("No meta data found for shared lib " + packageName);
|
|
17
16
|
return null;
|
|
18
17
|
}
|
|
18
|
+
function getPackageInfo(packageName, workspaceRoot, repo = sharedPackageJsonRepository) {
|
|
19
|
+
const info = tryGetPackageInfo(packageName, workspaceRoot, repo);
|
|
20
|
+
if (!info) {
|
|
21
|
+
logger.warn("No meta data found for shared lib " + packageName);
|
|
22
|
+
logger.warn(
|
|
23
|
+
"If you don't need this package, skip it in your federation.config.js or consider moving it into depDependencies in your package.json"
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
return info;
|
|
27
|
+
}
|
|
28
|
+
function installedVersions(packageNames, workspaceRoot, repo = sharedPackageJsonRepository) {
|
|
29
|
+
workspaceRoot = normalize(workspaceRoot, true);
|
|
30
|
+
const byRoot = /* @__PURE__ */ new Map();
|
|
31
|
+
const result = {};
|
|
32
|
+
for (const packageName of packageNames) {
|
|
33
|
+
const root = getPkgFolder(packageName);
|
|
34
|
+
if (!byRoot.has(root)) {
|
|
35
|
+
const pkgJsonPath = repo.findDepPackageJson(root, workspaceRoot);
|
|
36
|
+
const version = pkgJsonPath ? repo.readJson(pkgJsonPath)["version"] : "";
|
|
37
|
+
byRoot.set(root, version ?? "");
|
|
38
|
+
}
|
|
39
|
+
result[packageName] = byRoot.get(root);
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
19
43
|
function getVersionMaps(project, workspace, repo = sharedPackageJsonRepository) {
|
|
20
44
|
return getVersionMapsFromRepo(repo, project, workspace);
|
|
21
45
|
}
|
|
@@ -26,6 +50,8 @@ export {
|
|
|
26
50
|
findDepPackageJson,
|
|
27
51
|
getPackageInfo,
|
|
28
52
|
getVersionMaps,
|
|
53
|
+
installedVersions,
|
|
29
54
|
isESMExport,
|
|
30
|
-
sharedPackageJsonRepository2 as sharedPackageJsonRepository
|
|
55
|
+
sharedPackageJsonRepository2 as sharedPackageJsonRepository,
|
|
56
|
+
tryGetPackageInfo
|
|
31
57
|
};
|
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
captureWildcard,
|
|
3
|
+
parseWildcard,
|
|
4
|
+
substituteWildcard,
|
|
5
|
+
toGlobPattern,
|
|
6
|
+
toPosix
|
|
7
|
+
} from "../path-patterns.js";
|
|
2
8
|
function resolvePackageJsonExportsWildcardCore(io, keyPattern, valuePattern, cwd) {
|
|
3
9
|
const pattern = parseWildcard(valuePattern.replace(/^\.?\/+/, ""));
|
|
4
10
|
if (!pattern.hasWildcard) {
|
|
5
11
|
return [];
|
|
6
12
|
}
|
|
7
|
-
const files = io.globFiles(pattern
|
|
13
|
+
const files = io.globFiles(toGlobPattern(pattern), { cwd, ignore: ["**/node_modules/**"] });
|
|
8
14
|
const keys = [];
|
|
9
15
|
for (const file of files) {
|
|
10
16
|
const relPath = toPosix(file).replace(/^\.\//, "");
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
export declare const toPosix: (p: string) => string;
|
|
2
|
+
/**
|
|
3
|
+
* True when `file` is `dir` itself or lives under it. Both sides are normalized, so a
|
|
4
|
+
* caller cannot splice in `path.sep` and get a predicate that is silently always-false
|
|
5
|
+
* on Windows -- `linkedSharedDirs` and the file watcher both emit posix paths.
|
|
6
|
+
*/
|
|
7
|
+
export declare function isUnderDir(file: string, dir: string): boolean;
|
|
8
|
+
export declare const isUnderAnyDir: (file: string, dirs: readonly string[]) => boolean;
|
|
2
9
|
export interface WildcardPattern {
|
|
3
10
|
prefix: string;
|
|
4
11
|
suffix: string;
|
|
@@ -12,3 +19,10 @@ export declare function matchesWildcard(value: string, pattern: string): boolean
|
|
|
12
19
|
*/
|
|
13
20
|
export declare function captureWildcard(value: string, pattern: WildcardPattern): string | null;
|
|
14
21
|
export declare function substituteWildcard(template: string, captured: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* A glob that is a superset of the pattern, for callers that re-check the match themselves
|
|
24
|
+
* (`captureWildcard`, `matchMapping`). `**` only acts as a globstar on a segment of its own,
|
|
25
|
+
* so a prefix stopping mid-segment (`libs/ui-`) has to be widened back to its directory --
|
|
26
|
+
* `libs/ui-**` reads as `libs/ui-*` and silently matches nothing one level down.
|
|
27
|
+
*/
|
|
28
|
+
export declare function toGlobPattern({ prefix, suffix }: WildcardPattern): string;
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
const toPosix = (p) => p.replace(/\\/g, "/");
|
|
2
|
+
function isUnderDir(file, dir) {
|
|
3
|
+
const f = toPosix(file);
|
|
4
|
+
const d = toPosix(dir).replace(/\/+$/, "");
|
|
5
|
+
return f === d || f.startsWith(d + "/");
|
|
6
|
+
}
|
|
7
|
+
const isUnderAnyDir = (file, dirs) => dirs.some((d) => isUnderDir(file, d));
|
|
2
8
|
function parseWildcard(pattern) {
|
|
3
9
|
const i = pattern.indexOf("*");
|
|
4
10
|
if (i === -1) return { prefix: pattern, suffix: "", hasWildcard: false };
|
|
@@ -19,10 +25,16 @@ function captureWildcard(value, pattern) {
|
|
|
19
25
|
function substituteWildcard(template, captured) {
|
|
20
26
|
return template.replace("*", captured);
|
|
21
27
|
}
|
|
28
|
+
function toGlobPattern({ prefix, suffix }) {
|
|
29
|
+
return prefix.slice(0, prefix.lastIndexOf("/") + 1) + "**/*" + suffix;
|
|
30
|
+
}
|
|
22
31
|
export {
|
|
23
32
|
captureWildcard,
|
|
33
|
+
isUnderAnyDir,
|
|
34
|
+
isUnderDir,
|
|
24
35
|
matchesWildcard,
|
|
25
36
|
parseWildcard,
|
|
26
37
|
substituteWildcard,
|
|
38
|
+
toGlobPattern,
|
|
27
39
|
toPosix
|
|
28
40
|
};
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softarc/native-federation",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.4.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"packageManager": "pnpm@11.
|
|
6
|
+
"packageManager": "pnpm@11.18.0",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"build": "node esbuild.config.mjs && tsc -p tsconfig.build.json",
|
|
9
9
|
"lint": "eslint src",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@softarc/sheriff-core": "^0.19.6",
|
|
16
|
-
"chalk": "^
|
|
16
|
+
"chalk": "^6.0.0",
|
|
17
17
|
"esbuild": "^0.28.0",
|
|
18
18
|
"fast-glob": "^3.3.3",
|
|
19
19
|
"json5": "^2.2.3"
|
|
@@ -27,12 +27,12 @@
|
|
|
27
27
|
"globals": "^17.3.0",
|
|
28
28
|
"jiti": "^2.6.1",
|
|
29
29
|
"jsdom": "^29.0.0",
|
|
30
|
-
"knip": "^6.
|
|
30
|
+
"knip": "^6.26.0",
|
|
31
31
|
"prettier": "^3.9.4",
|
|
32
32
|
"tslib": "^2.3.0",
|
|
33
33
|
"typescript": "~6.0.0",
|
|
34
34
|
"typescript-eslint": "^8.61.0",
|
|
35
|
-
"vite": "^8.
|
|
35
|
+
"vite": "^8.2.0",
|
|
36
36
|
"vitest": "^4.0.0"
|
|
37
37
|
},
|
|
38
38
|
"exports": {
|