@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.
@@ -1,153 +1,134 @@
1
1
  import { PackageUtil } from './package';
2
2
  import { path } from './path';
3
- import { ManifestContext, ManifestModuleRole, PackageVisitor, PackageVisitReq, Package } from './types';
4
-
5
- export type ModuleDep = {
6
- pkg: Package;
7
- version: string;
8
- name: string;
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 ModuleDependencyVisitor implements PackageVisitor<ModuleDep> {
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.workspacePath, this.ctx.mainFolder);
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(req: PackageVisitReq<ModuleDep>): Promise<PackageVisitReq<ModuleDep>[]> {
56
- const pkg = PackageUtil.readPackage(req.sourcePath);
57
- const workspacePkg = PackageUtil.readPackage(this.ctx.workspacePath);
58
- const workspaceModules = pkg.workspaces?.length ? (await PackageUtil.resolveWorkspaces(this.ctx, req.sourcePath)) : [];
59
-
60
- this.#mainPatterns = ModuleDependencyVisitor.getMainPatterns(pkg.name, [
61
- ...pkg.travetto?.mainSource ?? [],
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
- const workspaceModuleDeps = workspaceModules
70
- .map(entry => PackageUtil.packageReq<ModuleDep>(path.resolve(req.sourcePath, entry.sourcePath), false, true));
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 [...globals, ...workspaceModuleDeps];
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(req: PackageVisitReq<ModuleDep>): boolean {
79
- return req.sourcePath === this.#mainSourcePath || (
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
- * Create dependency from request
60
+ * Build a package module
86
61
  */
87
- create(req: PackageVisitReq<ModuleDep>): ModuleDep {
88
- const { pkg, sourcePath } = req;
89
- const { name, version } = pkg;
90
- const main = name === this.ctx.mainModule;
91
- const mainSource = main || this.#mainPatterns.some(x => x.test(name));
92
- const internal = pkg.private === true;
93
- const local = internal || mainSource || !sourcePath.includes('node_modules');
94
-
95
- const dep = {
96
- name, version, sourcePath, main, mainSource, local, internal, pkg: req.pkg,
97
- parentSet: new Set([]), childSet: new Set([]), roleSet: new Set([]), prod: req.prod, topLevel: req.topLevel
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
- return dep;
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(req: PackageVisitReq<ModuleDep>, dep: ModuleDep): void {
107
- const { parent } = req;
108
- if (parent && dep.name !== this.ctx.mainModule) {
109
- dep.parentSet.add(parent.name);
110
- parent.childSet.add(dep.name);
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(deps: Set<ModuleDep>): Set<ModuleDep> {
118
- const mapping = new Map<string, { parent: Set<string>, child: Set<string>, el: ModuleDep }>();
119
- for (const el of deps) {
120
- mapping.set(el.name, { parent: new Set(el.parentSet), child: new Set(el.childSet), el });
121
- }
122
-
123
- const main = mapping.get(this.ctx.mainModule)!;
124
-
125
- // Visit all direct dependencies and mark
126
- for (const { el } of mapping.values()) {
127
- if (el.topLevel) {
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, child } of toProcess) {
144
- for (const c of child.keys()) {
145
- const { el: cDep, parent } = mapping.get(c)!;
146
- parent.delete(el.name); // Remove from child
147
- for (const role of el.roleSet) {
148
- cDep.roleSet.add(role);
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
- cDep.prod ||= el.prod; // Allow prod to trickle down as needed
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
- // Color parent as final step
160
- main.el.prod = true;
161
- main.el.roleSet.add('std');
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 deps;
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
  }
@@ -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
- export type FindConfig = {
12
- folder?: (folder: ManifestModuleFolderType) => boolean;
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
- #manifestFile: string;
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
- const { manifest, file } = ManifestUtil.readManifestSync(manifestInput);
78
- this.#manifest = manifest;
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.workspacePath, m.sourceFolder, f);
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.workspacePath, m.sourceFolder),
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 local modules
119
+ * Get all workspace modules
142
120
  * @returns
143
121
  */
144
- getLocalModules(): IndexedModule[] {
145
- return this.#modules.filter(x => x.local);
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: 'local' | 'all', exprList: string = ''): Set<string> {
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) that depend on this module
235
+ * Get all modules, parents or children, (transitively) of the provided root, in a DFS fashion
261
236
  */
262
- getDependentModules(root: IndexedModule): Set<IndexedModule> {
237
+ getDependentModules(root: IndexedModule | string, field: 'parents' | 'children'): IndexedModule[] {
263
238
  const seen = new Set<string>();
264
- const out = new Set<IndexedModule>();
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
- continue;
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, mainSource = false): Promise<string[]> {
48
- if (!mainSource && folder in this.#scanCache) {
49
- return this.#scanCache[folder];
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 (!sub.startsWith('.') && (depth > 0 || !topFiles.size || topFiles.has(sub))) {
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') && !sub.startsWith('.') && (depth > 0 || !topFolders.size || topFolders.has(sub))) {
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
- if (!mainSource) {
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, dep: ModuleDep): Promise<ManifestModule> {
179
- const { main, mainSource, local, name, version, sourcePath, roleSet, prod, parentSet, internal } = dep;
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, mainSource)) {
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
- // Refine non-main source
192
- if (!mainSource) {
193
- files.$root = files.$root?.filter(([file, type]) => type !== 'ts');
194
- }
195
-
196
- const roles = [...roleSet ?? []].sort();
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 visitor = new ModuleDependencyVisitor(ctx);
210
- const mainPath = path.resolve(ctx.workspacePath, ctx.mainFolder);
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