@travetto/manifest 3.4.0 → 4.0.0-rc.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/LICENSE +1 -1
- package/README.md +35 -24
- package/__index__.ts +6 -3
- package/bin/context.d.ts +1 -1
- package/bin/context.js +68 -58
- package/package.json +4 -2
- package/src/delta.ts +15 -14
- package/src/dependencies.ts +88 -106
- package/src/file.ts +4 -24
- package/src/manifest-index.ts +49 -57
- package/src/module.ts +29 -40
- package/src/package.ts +59 -93
- package/src/path.ts +3 -3
- package/src/{root-index.ts → runtime.ts} +53 -39
- package/src/types/common.ts +20 -0
- package/src/types/context.ts +40 -0
- package/src/types/manifest.ts +79 -0
- package/src/types/package.ts +75 -0
- package/src/util.ts +107 -35
- package/support/transformer.function-metadata.ts +8 -8
- package/src/types.ts +0 -118
- /package/src/{typings.d.ts → global.d.ts} +0 -0
package/src/dependencies.ts
CHANGED
|
@@ -1,153 +1,134 @@
|
|
|
1
1
|
import { PackageUtil } from './package';
|
|
2
2
|
import { path } from './path';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
main?: boolean;
|
|
10
|
-
mainSource?: boolean;
|
|
11
|
-
local?: boolean;
|
|
12
|
-
internal?: boolean;
|
|
13
|
-
sourcePath: string;
|
|
14
|
-
childSet: Set<string>;
|
|
15
|
-
parentSet: Set<string>;
|
|
16
|
-
roleSet: Set<ManifestModuleRole>;
|
|
17
|
-
prod: boolean;
|
|
18
|
-
topLevel?: boolean;
|
|
19
|
-
};
|
|
3
|
+
|
|
4
|
+
import type { Package, PackageDepType, PackageVisitReq, PackageVisitor } from './types/package';
|
|
5
|
+
import type { ManifestContext } from './types/context';
|
|
6
|
+
import type { PackageModule } from './types/manifest';
|
|
7
|
+
|
|
8
|
+
type CreateOpts = Partial<Pick<PackageModule, 'main' | 'workspace' | 'prod'>> & { roleRoot?: boolean };
|
|
20
9
|
|
|
21
10
|
/**
|
|
22
11
|
* Used for walking dependencies for collecting modules for the manifest
|
|
23
12
|
*/
|
|
24
|
-
export class
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Get main patterns for detecting if a module should be treated as main
|
|
28
|
-
*/
|
|
29
|
-
static getMainPatterns(mainModule: string, mergeModules: string[]): RegExp[] {
|
|
30
|
-
const groups: Record<string, string[]> = { [mainModule]: [] };
|
|
31
|
-
for (const el of mergeModules) {
|
|
32
|
-
if (el.includes('/')) {
|
|
33
|
-
const [grp, sub] = el.split('/');
|
|
34
|
-
(groups[`${grp}/`] ??= []).push(sub);
|
|
35
|
-
} else {
|
|
36
|
-
(groups[el] ??= []);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return Object.entries(groups)
|
|
41
|
-
.map(([root, subs]) => subs.length ? `${root}(${subs.join('|')})` : root)
|
|
42
|
-
.map(x => new RegExp(`^${x.replace(/[*]/g, '.*?')}$`));
|
|
43
|
-
}
|
|
13
|
+
export class PackageModuleVisitor implements PackageVisitor<PackageModule> {
|
|
44
14
|
|
|
45
15
|
constructor(public ctx: ManifestContext) {
|
|
46
|
-
this.#mainSourcePath = path.resolve(this.ctx.
|
|
16
|
+
this.#mainSourcePath = path.resolve(this.ctx.workspace.path, this.ctx.main.folder);
|
|
47
17
|
}
|
|
48
18
|
|
|
49
|
-
#mainPatterns: RegExp[] = [];
|
|
50
19
|
#mainSourcePath: string;
|
|
20
|
+
#cache: Record<string, PackageModule> = {};
|
|
21
|
+
#workspaceModules: Map<string, string>;
|
|
51
22
|
|
|
52
23
|
/**
|
|
53
24
|
* Initialize visitor, and provide global dependencies
|
|
54
25
|
*/
|
|
55
|
-
async init(
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
// Add workspace folders, for tests and docs
|
|
63
|
-
...workspaceModules.map(x => x.name)
|
|
64
|
-
]);
|
|
65
|
-
|
|
66
|
-
const globals = (workspacePkg.travetto?.globalModules ?? [])
|
|
67
|
-
.map(name => PackageUtil.packageReq<ModuleDep>(PackageUtil.resolvePackagePath(name), name in (workspacePkg.dependencies ?? {}), true));
|
|
26
|
+
async init(): Promise<Iterable<PackageVisitReq<PackageModule>>> {
|
|
27
|
+
const mainPkg = PackageUtil.readPackage(this.#mainSourcePath);
|
|
28
|
+
const mainReq = this.create(mainPkg, { main: true, workspace: true, roleRoot: true, prod: true });
|
|
29
|
+
const globals = [mainReq];
|
|
30
|
+
this.#workspaceModules = new Map(
|
|
31
|
+
(await PackageUtil.resolveWorkspaces(this.ctx)).map(x => [x.name, x.path])
|
|
32
|
+
);
|
|
68
33
|
|
|
69
|
-
|
|
70
|
-
|
|
34
|
+
// Treat all workspace modules as main modules
|
|
35
|
+
if (this.ctx.workspace.mono && !this.ctx.main.folder) {
|
|
36
|
+
for (const [, loc] of this.#workspaceModules) {
|
|
37
|
+
const depPkg = PackageUtil.readPackage(loc);
|
|
38
|
+
globals.push(this.create(depPkg, { main: true, workspace: true, roleRoot: true }));
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
// If we have 'withModules' at workspace root
|
|
42
|
+
const root = PackageUtil.readPackage(this.ctx.workspace.path);
|
|
43
|
+
for (const [name, type] of Object.entries(root.travetto?.build?.withModules ?? {})) {
|
|
44
|
+
const depPkg = PackageUtil.readPackage(PackageUtil.resolvePackagePath(name));
|
|
45
|
+
globals.push(this.create(depPkg, { main: type === 'main', workspace: true }));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
71
48
|
|
|
72
|
-
return
|
|
49
|
+
return globals.map((x, i) => i === 0 ? x : { ...x, parent: mainReq.value });
|
|
73
50
|
}
|
|
74
51
|
|
|
75
52
|
/**
|
|
76
53
|
* Is valid dependency for searching
|
|
77
54
|
*/
|
|
78
|
-
valid(
|
|
79
|
-
return
|
|
80
|
-
(!!req.pkg.travetto || req.pkg.private === true || !req.sourcePath.includes('node_modules'))
|
|
81
|
-
);
|
|
55
|
+
valid({ value: node }: PackageVisitReq<PackageModule>): boolean {
|
|
56
|
+
return node.workspace || !!node.state.travetto; // Workspace or travetto module
|
|
82
57
|
}
|
|
83
58
|
|
|
84
59
|
/**
|
|
85
|
-
*
|
|
60
|
+
* Build a package module
|
|
86
61
|
*/
|
|
87
|
-
create(
|
|
88
|
-
const
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
62
|
+
create(pkg: Package, { main, workspace, prod = false, roleRoot = false }: CreateOpts = {}): PackageVisitReq<PackageModule> {
|
|
63
|
+
const sourcePath = PackageUtil.getPackagePath(pkg);
|
|
64
|
+
const value = this.#cache[sourcePath] ??= {
|
|
65
|
+
main,
|
|
66
|
+
prod,
|
|
67
|
+
name: pkg.name,
|
|
68
|
+
version: pkg.version,
|
|
69
|
+
workspace: workspace ?? this.#workspaceModules.has(pkg.name),
|
|
70
|
+
internal: pkg.private === true,
|
|
71
|
+
sourceFolder: sourcePath === this.ctx.workspace.path ? '' : sourcePath.replace(`${this.ctx.workspace.path}/`, ''),
|
|
72
|
+
outputFolder: `node_modules/${pkg.name}`,
|
|
73
|
+
state: {
|
|
74
|
+
childSet: new Set(), parentSet: new Set(), roleSet: new Set(), roleRoot,
|
|
75
|
+
travetto: pkg.travetto, prodDeps: new Set(Object.keys(pkg.dependencies ?? {}))
|
|
76
|
+
}
|
|
98
77
|
};
|
|
99
78
|
|
|
100
|
-
|
|
79
|
+
const deps: PackageDepType[] = ['dependencies', ...(value.main ? ['devDependencies'] as const : [])];
|
|
80
|
+
const children = Object.fromEntries(deps.flatMap(x => Object.entries(pkg[x] ?? {})));
|
|
81
|
+
return { pkg, value, children };
|
|
101
82
|
}
|
|
102
83
|
|
|
103
84
|
/**
|
|
104
85
|
* Visit dependency
|
|
105
86
|
*/
|
|
106
|
-
visit(
|
|
107
|
-
|
|
108
|
-
if (parent
|
|
109
|
-
|
|
110
|
-
parent.childSet.add(
|
|
87
|
+
visit({ value: mod, parent }: PackageVisitReq<PackageModule>): void {
|
|
88
|
+
if (mod.name === this.ctx.main.name) { return; } // Skip root
|
|
89
|
+
if (parent) {
|
|
90
|
+
mod.state.parentSet.add(parent.name);
|
|
91
|
+
parent.state.childSet.add(mod.name);
|
|
111
92
|
}
|
|
112
93
|
}
|
|
113
94
|
|
|
114
95
|
/**
|
|
115
96
|
* Propagate prod, role information through graph
|
|
116
97
|
*/
|
|
117
|
-
complete(
|
|
118
|
-
const mapping = new Map
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
el.roleSet = new Set(el.pkg.travetto?.roles ?? []);
|
|
129
|
-
if (!el.roleSet.size) {
|
|
130
|
-
el.roleSet.add('std');
|
|
131
|
-
}
|
|
132
|
-
} else if (!main.child.has(el.name)) { // Not a direct descendent
|
|
133
|
-
el.prod = false;
|
|
98
|
+
async complete(mods: Iterable<PackageModule>): Promise<PackageModule[]> {
|
|
99
|
+
const mapping = new Map([...mods].map(el => [el.name, { parent: new Set(el.state.parentSet), el }]));
|
|
100
|
+
|
|
101
|
+
// All first-level dependencies should have role filled in (for propagation)
|
|
102
|
+
for (const dep of [...mods].filter(x => x.state.roleRoot)) {
|
|
103
|
+
dep.state.roleSet.clear(); // Ensure the roleRoot is empty
|
|
104
|
+
for (const c of dep.state.childSet) { // Visit children
|
|
105
|
+
const cDep = mapping.get(c)!.el;
|
|
106
|
+
if (cDep.state.roleRoot) { continue; }
|
|
107
|
+
// Set roles for all top level modules
|
|
108
|
+
cDep.state.roleSet = new Set(cDep.state.travetto?.roles ?? ['std']);
|
|
134
109
|
}
|
|
135
110
|
}
|
|
136
111
|
|
|
112
|
+
// Visit all nodes
|
|
137
113
|
while (mapping.size > 0) {
|
|
138
114
|
const toProcess = [...mapping.values()].filter(x => x.parent.size === 0);
|
|
139
115
|
if (!toProcess.length) {
|
|
140
116
|
throw new Error(`We have reached a cycle for ${[...mapping.keys()]}`);
|
|
141
117
|
}
|
|
142
|
-
// Propagate
|
|
143
|
-
for (const { el
|
|
144
|
-
for (const c of
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
118
|
+
// Propagate to children
|
|
119
|
+
for (const { el } of toProcess) {
|
|
120
|
+
for (const c of el.state.childSet) {
|
|
121
|
+
const child = mapping.get(c);
|
|
122
|
+
if (!child) { continue; }
|
|
123
|
+
child.parent.delete(el.name);
|
|
124
|
+
// Propagate roles from parent to child
|
|
125
|
+
if (!child.el.state.roleRoot) {
|
|
126
|
+
for (const role of el.state.roleSet) {
|
|
127
|
+
child.el.state.roleSet.add(role);
|
|
128
|
+
}
|
|
149
129
|
}
|
|
150
|
-
|
|
130
|
+
// Allow prod to trickle down as needed
|
|
131
|
+
child.el.prod ||= (el.prod && el.state.prodDeps.has(c));
|
|
151
132
|
}
|
|
152
133
|
}
|
|
153
134
|
// Remove from mapping
|
|
@@ -156,10 +137,11 @@ export class ModuleDependencyVisitor implements PackageVisitor<ModuleDep> {
|
|
|
156
137
|
}
|
|
157
138
|
}
|
|
158
139
|
|
|
159
|
-
//
|
|
160
|
-
|
|
161
|
-
|
|
140
|
+
// Mark as standard at the end
|
|
141
|
+
for (const dep of [...mods].filter(x => x.state.roleRoot)) {
|
|
142
|
+
dep.state.roleSet = new Set(['std']);
|
|
143
|
+
}
|
|
162
144
|
|
|
163
|
-
return
|
|
145
|
+
return [...mods].sort((a, b) => a.name.localeCompare(b.name));
|
|
164
146
|
}
|
|
165
147
|
}
|
package/src/file.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import os from 'os';
|
|
2
|
-
import fs from 'fs/promises';
|
|
3
|
-
import { readFileSync } from 'fs';
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
4
|
|
|
5
5
|
import { path } from './path';
|
|
6
|
-
import type { ManifestContext } from './types';
|
|
7
6
|
|
|
8
7
|
export class ManifestFileUtil {
|
|
9
8
|
/**
|
|
@@ -16,7 +15,7 @@ export class ManifestFileUtil {
|
|
|
16
15
|
const temp = path.resolve(os.tmpdir(), tempName);
|
|
17
16
|
await fs.writeFile(temp, typeof content === 'string' ? content : JSON.stringify(content), 'utf8');
|
|
18
17
|
await fs.copyFile(temp, file);
|
|
19
|
-
fs.unlink(temp);
|
|
18
|
+
fs.unlink(temp); // Don't wait for completion
|
|
20
19
|
return file;
|
|
21
20
|
}
|
|
22
21
|
|
|
@@ -33,23 +32,4 @@ export class ManifestFileUtil {
|
|
|
33
32
|
static readAsJsonSync<T = unknown>(file: string): T {
|
|
34
33
|
return JSON.parse(readFileSync(file, 'utf8'));
|
|
35
34
|
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Stat file
|
|
39
|
-
*/
|
|
40
|
-
static statFile(file: string): Promise<{ mtimeMs: number, ctimeMs: number } | undefined> {
|
|
41
|
-
return fs.stat(file).catch(() => undefined);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Resolve tool path for usage
|
|
46
|
-
*/
|
|
47
|
-
static toolPath(ctx: ManifestContext | { manifest: ManifestContext }, rel: string, moduleSpecific = false): string {
|
|
48
|
-
ctx = 'manifest' in ctx ? ctx.manifest : ctx;
|
|
49
|
-
const parts = [rel];
|
|
50
|
-
if (moduleSpecific) {
|
|
51
|
-
parts.unshift('node_modules', ctx.mainModule);
|
|
52
|
-
}
|
|
53
|
-
return path.resolve(ctx.workspacePath, ctx.toolFolder, ...parts);
|
|
54
|
-
}
|
|
55
35
|
}
|
package/src/manifest-index.ts
CHANGED
|
@@ -1,36 +1,11 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
|
|
1
3
|
import { ManifestModuleUtil } from './module';
|
|
2
4
|
import { path } from './path';
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
ManifestModule, ManifestModuleCore, ManifestModuleFile,
|
|
6
|
-
ManifestModuleFileType, ManifestModuleFolderType, ManifestModuleRole, ManifestRoot
|
|
7
|
-
} from './types';
|
|
8
|
-
|
|
9
5
|
import { ManifestUtil } from './util';
|
|
10
6
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
module?: (module: IndexedModule) => boolean;
|
|
14
|
-
file?: (file: IndexedFile) => boolean;
|
|
15
|
-
sourceOnly?: boolean;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export type IndexedFile = {
|
|
19
|
-
id: string;
|
|
20
|
-
import: string;
|
|
21
|
-
module: string;
|
|
22
|
-
sourceFile: string;
|
|
23
|
-
outputFile: string;
|
|
24
|
-
relativeFile: string;
|
|
25
|
-
role: ManifestModuleRole;
|
|
26
|
-
type: ManifestModuleFileType;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export type IndexedModule = ManifestModuleCore & {
|
|
30
|
-
sourcePath: string;
|
|
31
|
-
outputPath: string;
|
|
32
|
-
files: Record<ManifestModuleFolderType, IndexedFile[]>;
|
|
33
|
-
};
|
|
7
|
+
import type { ManifestModuleFolderType } from './types/common';
|
|
8
|
+
import type { ManifestModule, ManifestRoot, ManifestModuleFile, IndexedModule, IndexedFile, FindConfig } from './types/manifest';
|
|
34
9
|
|
|
35
10
|
const TypedObject: {
|
|
36
11
|
keys<T = unknown, K extends keyof T = keyof T>(o: T): K[];
|
|
@@ -43,7 +18,7 @@ const TypedObject: {
|
|
|
43
18
|
*/
|
|
44
19
|
export class ManifestIndex {
|
|
45
20
|
|
|
46
|
-
#
|
|
21
|
+
#arbitraryLookup?: (parts: string[]) => ManifestModule | undefined;
|
|
47
22
|
#manifest: ManifestRoot;
|
|
48
23
|
#modules: IndexedModule[];
|
|
49
24
|
#modulesByName: Record<string, IndexedModule> = {};
|
|
@@ -69,22 +44,16 @@ export class ManifestIndex {
|
|
|
69
44
|
return this.#outputRoot;
|
|
70
45
|
}
|
|
71
46
|
|
|
72
|
-
get manifestFile(): string {
|
|
73
|
-
return this.#manifestFile;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
47
|
init(manifestInput: string): void {
|
|
77
|
-
|
|
78
|
-
this.#
|
|
79
|
-
this.#manifestFile = file;
|
|
80
|
-
this.#outputRoot = path.resolve(this.#manifest.workspacePath, this.#manifest.outputFolder);
|
|
48
|
+
this.#manifest = ManifestUtil.readManifestSync(manifestInput);
|
|
49
|
+
this.#outputRoot = path.resolve(this.#manifest.workspace.path, this.#manifest.build.outputFolder);
|
|
81
50
|
this.#index();
|
|
82
51
|
}
|
|
83
52
|
|
|
84
53
|
#moduleFiles(m: ManifestModule, files: ManifestModuleFile[]): IndexedFile[] {
|
|
85
54
|
return files.map(([f, type, ts, role = 'std']) => {
|
|
86
55
|
const isSource = type === 'ts' || type === 'js';
|
|
87
|
-
const sourceFile = path.resolve(this.#manifest.
|
|
56
|
+
const sourceFile = path.resolve(this.#manifest.workspace.path, m.sourceFolder, f);
|
|
88
57
|
const js = isSource ? ManifestModuleUtil.sourceToOutputExt(f) : f;
|
|
89
58
|
const outputFile = this.#resolveOutput(m.outputFolder, js);
|
|
90
59
|
const modImport = `${m.name}/${js}`;
|
|
@@ -104,12 +73,14 @@ export class ManifestIndex {
|
|
|
104
73
|
this.#outputToEntry.clear();
|
|
105
74
|
this.#importToEntry.clear();
|
|
106
75
|
this.#sourceToEntry.clear();
|
|
76
|
+
this.#arbitraryLookup = undefined;
|
|
107
77
|
|
|
108
78
|
this.#modules = Object.values(this.#manifest.modules)
|
|
109
79
|
.map(m => ({
|
|
110
80
|
...m,
|
|
111
81
|
outputPath: this.#resolveOutput(m.outputFolder),
|
|
112
|
-
sourcePath: path.resolve(this.#manifest.
|
|
82
|
+
sourcePath: path.resolve(this.#manifest.workspace.path, m.sourceFolder),
|
|
83
|
+
children: new Set(),
|
|
113
84
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
114
85
|
files: Object.fromEntries(
|
|
115
86
|
Object.entries(m.files).map(([folder, files]) => [folder, this.#moduleFiles(m, files ?? [])])
|
|
@@ -128,6 +99,13 @@ export class ManifestIndex {
|
|
|
128
99
|
}
|
|
129
100
|
this.#modulesByName = Object.fromEntries(this.#modules.map(x => [x.name, x]));
|
|
130
101
|
this.#modulesByFolder = Object.fromEntries(this.#modules.map(x => [x.sourceFolder, x]));
|
|
102
|
+
|
|
103
|
+
// Store child information
|
|
104
|
+
for (const mod of this.#modules) {
|
|
105
|
+
for (const p of mod.parents) {
|
|
106
|
+
this.#modulesByName[p]?.children.add(mod.name);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
131
109
|
}
|
|
132
110
|
|
|
133
111
|
/**
|
|
@@ -138,11 +116,11 @@ export class ManifestIndex {
|
|
|
138
116
|
}
|
|
139
117
|
|
|
140
118
|
/**
|
|
141
|
-
* Get all
|
|
119
|
+
* Get all workspace modules
|
|
142
120
|
* @returns
|
|
143
121
|
*/
|
|
144
|
-
|
|
145
|
-
return this.#modules.filter(x => x.
|
|
122
|
+
getWorkspaceModules(): IndexedModule[] {
|
|
123
|
+
return this.#modules.filter(x => x.workspace);
|
|
146
124
|
}
|
|
147
125
|
|
|
148
126
|
/**
|
|
@@ -237,12 +215,9 @@ export class ManifestIndex {
|
|
|
237
215
|
/**
|
|
238
216
|
* Build module list from an expression list (e.g. `@travetto/rest,-@travetto/log)
|
|
239
217
|
*/
|
|
240
|
-
getModuleList(mode: '
|
|
218
|
+
getModuleList(mode: 'workspace' | 'all', exprList: string = ''): Set<string> {
|
|
241
219
|
const allMods = Object.keys(this.#manifest.modules);
|
|
242
|
-
const active = new Set<string>(
|
|
243
|
-
mode === 'local' ? this.getLocalModules().map(x => x.name) :
|
|
244
|
-
(mode === 'all' ? allMods : [])
|
|
245
|
-
);
|
|
220
|
+
const active = new Set<string>(mode === 'workspace' ? this.getWorkspaceModules().map(x => x.name) : allMods);
|
|
246
221
|
|
|
247
222
|
for (const expr of exprList.split(/\s*,\s*/g)) {
|
|
248
223
|
const [, neg, mod] = expr.match(/(-|[+])?([^+\- ]+)$/) ?? [];
|
|
@@ -257,21 +232,38 @@ export class ManifestIndex {
|
|
|
257
232
|
}
|
|
258
233
|
|
|
259
234
|
/**
|
|
260
|
-
* Get all modules (transitively)
|
|
235
|
+
* Get all modules, parents or children, (transitively) of the provided root, in a DFS fashion
|
|
261
236
|
*/
|
|
262
|
-
getDependentModules(root: IndexedModule):
|
|
237
|
+
getDependentModules(root: IndexedModule | string, field: 'parents' | 'children'): IndexedModule[] {
|
|
263
238
|
const seen = new Set<string>();
|
|
264
|
-
const out =
|
|
265
|
-
const toProcess = [root.name];
|
|
239
|
+
const out: IndexedModule[] = [];
|
|
240
|
+
const toProcess = [typeof root === 'string' ? root : root.name];
|
|
266
241
|
while (toProcess.length) {
|
|
267
242
|
const next = toProcess.shift()!;
|
|
268
|
-
if (seen.has(next)) {
|
|
269
|
-
|
|
243
|
+
if (!seen.has(next)) {
|
|
244
|
+
seen.add(next);
|
|
245
|
+
const mod = this.getModule(next)!;
|
|
246
|
+
toProcess.push(...mod[field]);
|
|
247
|
+
if (next !== this.#manifest.main.name) { // Do not include self
|
|
248
|
+
out.push(mod);
|
|
249
|
+
}
|
|
270
250
|
}
|
|
271
|
-
const mod = this.getModule(next)!;
|
|
272
|
-
toProcess.push(...mod.parents);
|
|
273
|
-
out.add(mod);
|
|
274
251
|
}
|
|
275
252
|
return out;
|
|
276
253
|
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Find the module for an arbitrary source file, if it falls under a given workspace module
|
|
257
|
+
*/
|
|
258
|
+
findModuleForArbitraryFile(file: string): ManifestModule | undefined {
|
|
259
|
+
const base = this.#manifest.workspace.path;
|
|
260
|
+
const lookup = this.#arbitraryLookup ??= ManifestUtil.lookupTrie(
|
|
261
|
+
Object.values(this.#manifest.modules),
|
|
262
|
+
x => x.sourceFolder.split('/'),
|
|
263
|
+
sub =>
|
|
264
|
+
!existsSync(path.resolve(base, ...sub, 'package.json')) &&
|
|
265
|
+
!existsSync(path.resolve(base, ...sub, '.git'))
|
|
266
|
+
);
|
|
267
|
+
return lookup(file.replace(`${base}/`, '').split('/'));
|
|
268
|
+
}
|
|
277
269
|
}
|
package/src/module.ts
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
import fs from 'fs/promises';
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
2
|
|
|
3
3
|
import { path } from './path';
|
|
4
|
-
import {
|
|
5
|
-
ManifestContext,
|
|
6
|
-
ManifestModule, ManifestModuleFile, ManifestModuleFileType,
|
|
7
|
-
ManifestModuleFolderType,
|
|
8
|
-
ManifestModuleRole
|
|
9
|
-
} from './types';
|
|
10
|
-
import { ModuleDep, ModuleDependencyVisitor } from './dependencies';
|
|
11
4
|
import { PackageUtil } from './package';
|
|
5
|
+
import { PackageModuleVisitor } from './dependencies';
|
|
6
|
+
|
|
7
|
+
import type { ManifestModuleFileType, ManifestModuleRole, ManifestModuleFolderType } from './types/common';
|
|
8
|
+
import type { ManifestModuleFile, ManifestModule, PackageModule } from './types/manifest';
|
|
9
|
+
import type { ManifestContext } from './types/context';
|
|
12
10
|
|
|
13
11
|
const EXT_MAPPING: Record<string, ManifestModuleFileType> = {
|
|
14
12
|
'.js': 'js',
|
|
@@ -26,6 +24,9 @@ const INDEX_FILES = new Set(
|
|
|
26
24
|
)
|
|
27
25
|
);
|
|
28
26
|
|
|
27
|
+
const STD_TOP_FOLDERS = new Set(['src', 'bin', 'support']);
|
|
28
|
+
const STD_TOP_FILES = new Set([...INDEX_FILES, 'package.json']);
|
|
29
|
+
|
|
29
30
|
export class ManifestModuleUtil {
|
|
30
31
|
|
|
31
32
|
static #scanCache: Record<string, string[]> = {};
|
|
@@ -44,17 +45,16 @@ export class ManifestModuleUtil {
|
|
|
44
45
|
/**
|
|
45
46
|
* Simple file scanning
|
|
46
47
|
*/
|
|
47
|
-
static async scanFolder(folder: string,
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
static async scanFolder(folder: string, full = false): Promise<string[]> {
|
|
49
|
+
const key = `${folder}|${full}`;
|
|
50
|
+
if (key in this.#scanCache) {
|
|
51
|
+
return this.#scanCache[key];
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
if (!await fs.stat(folder).catch(() => false)) {
|
|
53
55
|
return [];
|
|
54
56
|
}
|
|
55
57
|
|
|
56
|
-
const topFolders = new Set(mainSource ? [] : ['src', 'bin', 'support']);
|
|
57
|
-
const topFiles = new Set(mainSource ? [] : [...INDEX_FILES, 'package.json']);
|
|
58
58
|
const out: string[] = [];
|
|
59
59
|
|
|
60
60
|
const stack: [string, number][] = [[folder, 0]];
|
|
@@ -72,24 +72,21 @@ export class ManifestModuleUtil {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
for (const sub of await fs.readdir(top)) {
|
|
75
|
+
const valid = !sub.startsWith('.') && (depth > 0 || full);
|
|
75
76
|
const stat = await fs.stat(`${top}/${sub}`);
|
|
76
77
|
if (stat.isFile()) {
|
|
77
|
-
if (
|
|
78
|
+
if (valid || STD_TOP_FILES.has(sub)) {
|
|
78
79
|
out.push(`${top}/${sub}`);
|
|
79
80
|
}
|
|
80
81
|
} else {
|
|
81
|
-
if (!sub.includes('node_modules') &&
|
|
82
|
+
if (!sub.includes('node_modules') && (valid || STD_TOP_FOLDERS.has(sub))) {
|
|
82
83
|
stack.push([`${top}/${sub}`, depth + 1]);
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
86
|
}
|
|
86
87
|
}
|
|
87
88
|
|
|
88
|
-
|
|
89
|
-
this.#scanCache[folder] = out;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return out;
|
|
89
|
+
return this.#scanCache[key] = out;
|
|
93
90
|
}
|
|
94
91
|
|
|
95
92
|
/**
|
|
@@ -175,12 +172,13 @@ export class ManifestModuleUtil {
|
|
|
175
172
|
/**
|
|
176
173
|
* Visit a module and describe files, and metadata
|
|
177
174
|
*/
|
|
178
|
-
static async describeModule(ctx: ManifestContext,
|
|
179
|
-
const {
|
|
175
|
+
static async describeModule(ctx: ManifestContext, mod: PackageModule): Promise<ManifestModule> {
|
|
176
|
+
const { state, ...rest } = mod;
|
|
177
|
+
const sourcePath = path.resolve(ctx.workspace.path, rest.sourceFolder);
|
|
180
178
|
|
|
181
179
|
const files: ManifestModule['files'] = {};
|
|
182
180
|
|
|
183
|
-
for (const file of await this.scanFolder(sourcePath,
|
|
181
|
+
for (const file of await this.scanFolder(sourcePath, rest.main)) {
|
|
184
182
|
// Group by top folder
|
|
185
183
|
const moduleFile = file.replace(`${sourcePath}/`, '');
|
|
186
184
|
const entry = await this.transformFile(moduleFile, file);
|
|
@@ -188,29 +186,20 @@ export class ManifestModuleUtil {
|
|
|
188
186
|
(files[key] ??= []).push(entry);
|
|
189
187
|
}
|
|
190
188
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const parents = [...parentSet].sort();
|
|
198
|
-
const outputFolder = `node_modules/${name}`;
|
|
199
|
-
const sourceFolder = sourcePath === ctx.workspacePath ? '' : sourcePath.replace(`${ctx.workspacePath}/`, '');
|
|
200
|
-
|
|
201
|
-
const res = { main, name, version, local, internal, sourceFolder, outputFolder, roles, parents, prod, files };
|
|
202
|
-
return res;
|
|
189
|
+
return {
|
|
190
|
+
...rest,
|
|
191
|
+
roles: [...state.roleSet].sort(),
|
|
192
|
+
parents: [...state.parentSet].sort(),
|
|
193
|
+
files
|
|
194
|
+
};
|
|
203
195
|
}
|
|
204
196
|
|
|
205
197
|
/**
|
|
206
198
|
* Produce all modules for a given manifest folder, adding in some given modules when developing framework
|
|
207
199
|
*/
|
|
208
200
|
static async produceModules(ctx: ManifestContext): Promise<Record<string, ManifestModule>> {
|
|
209
|
-
const
|
|
210
|
-
const
|
|
211
|
-
const declared = await PackageUtil.visitPackages(mainPath, visitor);
|
|
212
|
-
const sorted = [...declared].sort((a, b) => a.name.localeCompare(b.name));
|
|
213
|
-
const modules = await Promise.all(sorted.map(x => this.describeModule(ctx, x)));
|
|
201
|
+
const pkgs = await PackageUtil.visitPackages(new PackageModuleVisitor(ctx));
|
|
202
|
+
const modules = await Promise.all([...pkgs].map(x => this.describeModule(ctx, x)));
|
|
214
203
|
return Object.fromEntries(modules.map(m => [m.name, m]));
|
|
215
204
|
}
|
|
216
205
|
|