@travetto/manifest 3.0.0-rc.10
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 +21 -0
- package/README.md +14 -0
- package/__index__.ts +11 -0
- package/bin/context.d.ts +10 -0
- package/bin/context.js +116 -0
- package/package.json +48 -0
- package/src/delta.ts +104 -0
- package/src/dependencies.ts +163 -0
- package/src/manifest-index.ts +318 -0
- package/src/module.ts +213 -0
- package/src/package.ts +220 -0
- package/src/path.ts +17 -0
- package/src/root-index.ts +133 -0
- package/src/types.ts +111 -0
- package/src/typings.d.ts +3 -0
- package/src/util.ts +115 -0
- package/src/watch.ts +56 -0
- package/support/transformer.function-metadata.ts +137 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { path } from './path';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
ManifestModule, ManifestModuleCore, ManifestModuleFile,
|
|
5
|
+
ManifestModuleFileType, ManifestModuleFolderType, ManifestProfile, ManifestRoot
|
|
6
|
+
} from './types';
|
|
7
|
+
|
|
8
|
+
import { ManifestUtil } from './util';
|
|
9
|
+
|
|
10
|
+
type ScanTest = ((full: string) => boolean) | { test: (full: string) => boolean };
|
|
11
|
+
export type FindConfig = {
|
|
12
|
+
folders?: ManifestModuleFolderType[];
|
|
13
|
+
filter?: ScanTest;
|
|
14
|
+
includeIndex?: boolean;
|
|
15
|
+
profiles?: string[];
|
|
16
|
+
checkProfile?: boolean;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type IndexedFile = {
|
|
20
|
+
id: string;
|
|
21
|
+
import: string;
|
|
22
|
+
module: string;
|
|
23
|
+
sourceFile: string;
|
|
24
|
+
outputFile: string;
|
|
25
|
+
relativeFile: string;
|
|
26
|
+
profile: ManifestProfile;
|
|
27
|
+
type: ManifestModuleFileType;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type IndexedModule = ManifestModuleCore & {
|
|
31
|
+
sourcePath: string;
|
|
32
|
+
outputPath: string;
|
|
33
|
+
files: Record<ManifestModuleFolderType, IndexedFile[]>;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Manifest index
|
|
38
|
+
*/
|
|
39
|
+
export class ManifestIndex {
|
|
40
|
+
|
|
41
|
+
#manifestFile: string;
|
|
42
|
+
#manifest: ManifestRoot;
|
|
43
|
+
#modules: IndexedModule[];
|
|
44
|
+
#modulesByName: Record<string, IndexedModule> = {};
|
|
45
|
+
#modulesByFolder: Record<string, IndexedModule> = {};
|
|
46
|
+
#outputRoot: string;
|
|
47
|
+
#outputToEntry = new Map<string, IndexedFile>();
|
|
48
|
+
#sourceToEntry = new Map<string, IndexedFile>();
|
|
49
|
+
#importToEntry = new Map<string, IndexedFile>();
|
|
50
|
+
|
|
51
|
+
constructor(manifest: string) {
|
|
52
|
+
this.init(manifest);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
#resolveOutput(...parts: string[]): string {
|
|
56
|
+
return path.resolve(this.#outputRoot, ...parts);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
get manifest(): ManifestRoot {
|
|
60
|
+
return this.#manifest;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
get outputRoot(): string {
|
|
64
|
+
return this.#outputRoot;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
get manifestFile(): string {
|
|
68
|
+
return this.#manifestFile;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
init(manifestInput: string): void {
|
|
72
|
+
const { manifest, file } = ManifestUtil.readManifestSync(manifestInput);
|
|
73
|
+
this.#manifest = manifest;
|
|
74
|
+
this.#manifestFile = file;
|
|
75
|
+
this.#outputRoot = path.resolve(this.#manifest.workspacePath, this.#manifest.outputFolder);
|
|
76
|
+
this.#index();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
#moduleFiles(m: ManifestModule, files: ManifestModuleFile[]): IndexedFile[] {
|
|
80
|
+
return files.map(([f, type, ts, profile = 'std']) => {
|
|
81
|
+
const sourceFile = path.resolve(this.#manifest.workspacePath, m.sourceFolder, f);
|
|
82
|
+
const js = (type === 'ts' ? f.replace(/[.]ts$/, '.js') : f);
|
|
83
|
+
const outputFile = this.#resolveOutput(m.outputFolder, js);
|
|
84
|
+
const modImport = `${m.name}/${js}`;
|
|
85
|
+
let id = modImport.replace(`${m.name}/`, _ => _.replace(/[/]$/, ':'));
|
|
86
|
+
if (type === 'ts' || type === 'js') {
|
|
87
|
+
id = id.replace(/[.]js$/, '');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return { id, type, sourceFile, outputFile, import: modImport, profile, relativeFile: f, module: m.name };
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Get index of all source files
|
|
96
|
+
*/
|
|
97
|
+
#index(): void {
|
|
98
|
+
this.#outputToEntry.clear();
|
|
99
|
+
this.#importToEntry.clear();
|
|
100
|
+
this.#sourceToEntry.clear();
|
|
101
|
+
|
|
102
|
+
this.#modules = Object.values(this.#manifest.modules)
|
|
103
|
+
.map(m => ({
|
|
104
|
+
...m,
|
|
105
|
+
outputPath: this.#resolveOutput(m.outputFolder),
|
|
106
|
+
sourcePath: path.resolve(this.#manifest.workspacePath, m.sourceFolder),
|
|
107
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
108
|
+
files: Object.fromEntries(
|
|
109
|
+
Object.entries(m.files).map(([folder, files]) => [folder, this.#moduleFiles(m, files ?? [])])
|
|
110
|
+
) as Record<ManifestModuleFolderType, IndexedFile[]>
|
|
111
|
+
}));
|
|
112
|
+
|
|
113
|
+
for (const mod of this.#modules) {
|
|
114
|
+
for (const files of Object.values(mod.files ?? {})) {
|
|
115
|
+
for (const entry of files) {
|
|
116
|
+
this.#outputToEntry.set(entry.outputFile, entry);
|
|
117
|
+
this.#sourceToEntry.set(entry.sourceFile, entry);
|
|
118
|
+
this.#importToEntry.set(entry.import, entry);
|
|
119
|
+
this.#importToEntry.set(entry.import.replace(/[.]js$/, ''), entry);
|
|
120
|
+
this.#importToEntry.set(entry.import.replace(/[.]js$/, '.ts'), entry);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
this.#modulesByName = Object.fromEntries(this.#modules.map(x => [x.name, x]));
|
|
125
|
+
this.#modulesByFolder = Object.fromEntries(this.#modules.map(x => [x.sourceFolder, x]));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get entry by file (input or output)
|
|
130
|
+
*/
|
|
131
|
+
getEntry(file: string): IndexedFile | undefined {
|
|
132
|
+
return this.#outputToEntry.get(file) ?? this.#sourceToEntry.get(file);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Get all local modules
|
|
137
|
+
* @returns
|
|
138
|
+
*/
|
|
139
|
+
getLocalModules(): IndexedModule[] {
|
|
140
|
+
return this.#modules.filter(x => x.local);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Find files from the index
|
|
145
|
+
* @param folder The sub-folder to check into
|
|
146
|
+
* @param filter The filter to determine if this is a valid support file
|
|
147
|
+
*/
|
|
148
|
+
find(config: FindConfig): IndexedFile[] {
|
|
149
|
+
const { filter: f, folders } = config;
|
|
150
|
+
const filter = f ? 'test' in f ? f.test.bind(f) : f : f;
|
|
151
|
+
|
|
152
|
+
let idx = this.#modules;
|
|
153
|
+
|
|
154
|
+
const checkProfile = config.checkProfile ?? true;
|
|
155
|
+
|
|
156
|
+
const activeProfiles = new Set(['std', ...(config.profiles ?? process.env.TRV_PROFILES?.split(/\s*,\s*/g) ?? [])]);
|
|
157
|
+
|
|
158
|
+
if (checkProfile) {
|
|
159
|
+
idx = idx.filter(m => m.profiles.length === 0 || m.profiles.some(p => activeProfiles.has(p)));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
let searchSpace = folders ?
|
|
163
|
+
idx.flatMap(m => [...folders.flatMap(fo => m.files[fo] ?? []), ...(config.includeIndex ? (m.files.$index ?? []) : [])]) :
|
|
164
|
+
idx.flatMap(m => [...Object.values(m.files)].flat());
|
|
165
|
+
|
|
166
|
+
if (checkProfile) {
|
|
167
|
+
searchSpace = searchSpace.filter(fi => activeProfiles.has(fi.profile));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return searchSpace
|
|
171
|
+
.filter(({ type }) => type === 'ts')
|
|
172
|
+
.filter(({ sourceFile: source }) => filter?.(source) ?? true);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Find files from the index
|
|
177
|
+
* @param filter The filter to determine if this is a valid support file
|
|
178
|
+
*/
|
|
179
|
+
findSupport(config: Omit<FindConfig, 'folder'>): IndexedFile[] {
|
|
180
|
+
return this.find({ ...config, folders: ['support'] });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Find files from the index
|
|
185
|
+
* @param filter The filter to determine if this is a valid support file
|
|
186
|
+
*/
|
|
187
|
+
findSrc(config: Omit<FindConfig, 'folder'> = {}): IndexedFile[] {
|
|
188
|
+
return this.find({ ...config, includeIndex: true, folders: ['src'] });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Find files from the index
|
|
193
|
+
* @param filter The filter to determine if this is a valid support file
|
|
194
|
+
*/
|
|
195
|
+
findTest(config: Omit<FindConfig, 'folder'>): IndexedFile[] {
|
|
196
|
+
return this.find({ ...config, folders: ['test'] });
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Is module installed?
|
|
201
|
+
*/
|
|
202
|
+
hasModule(name: string): boolean {
|
|
203
|
+
return name in this.#manifest.modules;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Get module
|
|
208
|
+
*/
|
|
209
|
+
getModule(name: string): IndexedModule | undefined {
|
|
210
|
+
return this.#modulesByName[name];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Get module by folder
|
|
215
|
+
*/
|
|
216
|
+
getModuleByFolder(folder: string): IndexedModule | undefined {
|
|
217
|
+
return this.#modulesByFolder[folder];
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Resolve import
|
|
222
|
+
*/
|
|
223
|
+
resolveFileImport(name: string): string {
|
|
224
|
+
return this.#importToEntry.get(name)?.outputFile ?? name;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Get indexed module from source file
|
|
229
|
+
* @param source
|
|
230
|
+
*/
|
|
231
|
+
getFromSource(source: string): IndexedFile | undefined {
|
|
232
|
+
return this.#sourceToEntry.get(source);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Get indexed module from source file
|
|
237
|
+
* @param source
|
|
238
|
+
*/
|
|
239
|
+
getFromImport(imp: string): IndexedFile | undefined {
|
|
240
|
+
return this.#importToEntry.get(imp);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Get module from source file
|
|
245
|
+
* @param source
|
|
246
|
+
*/
|
|
247
|
+
getModuleFromSource(source: string): IndexedModule | undefined {
|
|
248
|
+
const name = this.getFromSource(source)?.module;
|
|
249
|
+
return name ? this.getModule(name) : undefined;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Get module from import name
|
|
254
|
+
* @param importName
|
|
255
|
+
*/
|
|
256
|
+
getModuleFromImport(importName: string): IndexedModule | undefined {
|
|
257
|
+
const name = this.getFromImport(importName)?.module;
|
|
258
|
+
return name ? this.getModule(name) : undefined;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Build module list from an expression list (e.g. `@travetto/app,-@travetto/log)
|
|
262
|
+
*/
|
|
263
|
+
getModuleList(mode: 'local' | 'all', exprList: string = ''): Set<string> {
|
|
264
|
+
const allMods = Object.keys(this.#manifest.modules);
|
|
265
|
+
const active = new Set<string>(
|
|
266
|
+
mode === 'local' ? this.getLocalModules().map(x => x.name) :
|
|
267
|
+
(mode === 'all' ? allMods : [])
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
for (const expr of exprList.split(/\s*,\s*/g)) {
|
|
271
|
+
const [, neg, mod] = expr.match(/(-|[+])?([^+\- ]+)$/) ?? [];
|
|
272
|
+
if (mod) {
|
|
273
|
+
const patt = new RegExp(`^${mod.replace(/[*]/g, '.*')}$`);
|
|
274
|
+
for (const m of allMods.filter(x => patt.test(x))) {
|
|
275
|
+
active[neg ? 'delete' : 'add'](m);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return active;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Get all modules (transitively) that depend on this module
|
|
284
|
+
*/
|
|
285
|
+
getDependentModules(root: IndexedModule): Set<IndexedModule> {
|
|
286
|
+
const seen = new Set<string>();
|
|
287
|
+
const out = new Set<IndexedModule>();
|
|
288
|
+
const toProcess = [root.name];
|
|
289
|
+
while (toProcess.length) {
|
|
290
|
+
const next = toProcess.shift()!;
|
|
291
|
+
if (seen.has(next)) {
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
const mod = this.getModule(next)!;
|
|
295
|
+
toProcess.push(...mod.parents);
|
|
296
|
+
out.add(mod);
|
|
297
|
+
}
|
|
298
|
+
return out;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Get local folders that represent the user's controlled input
|
|
303
|
+
*/
|
|
304
|
+
getLocalInputFolders(): string[] {
|
|
305
|
+
return this.getLocalModules()
|
|
306
|
+
.flatMap(x =>
|
|
307
|
+
(!this.manifest.monoRepo || x.sourcePath !== this.manifest.workspacePath) ?
|
|
308
|
+
[x.sourcePath] : [...Object.keys(x.files)].filter(y => !y.startsWith('$')).map(y => path.resolve(x.sourcePath, y))
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Get local output folders
|
|
314
|
+
*/
|
|
315
|
+
getLocalOutputFolders(): string[] {
|
|
316
|
+
return this.getLocalModules().map(x => x.outputPath);
|
|
317
|
+
}
|
|
318
|
+
}
|
package/src/module.ts
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
|
|
3
|
+
import { path } from './path';
|
|
4
|
+
import {
|
|
5
|
+
ManifestContext,
|
|
6
|
+
ManifestModule, ManifestModuleFile, ManifestModuleFileType,
|
|
7
|
+
ManifestModuleFolderType, ManifestProfile
|
|
8
|
+
} from './types';
|
|
9
|
+
import { ModuleDep, ModuleDependencyVisitor } from './dependencies';
|
|
10
|
+
import { PackageUtil } from './package';
|
|
11
|
+
|
|
12
|
+
const EXT_MAPPING: Record<string, ManifestModuleFileType> = {
|
|
13
|
+
'.js': 'js',
|
|
14
|
+
'.mjs': 'js',
|
|
15
|
+
'.cjs': 'js',
|
|
16
|
+
'.json': 'json',
|
|
17
|
+
'.ts': 'ts',
|
|
18
|
+
'.md': 'md'
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const INDEX_FILES = new Set([
|
|
22
|
+
'index.ts',
|
|
23
|
+
'index.js',
|
|
24
|
+
'__index__.ts',
|
|
25
|
+
'__index__.js',
|
|
26
|
+
'__index.ts',
|
|
27
|
+
'__index.js'
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
export class ManifestModuleUtil {
|
|
31
|
+
|
|
32
|
+
static #scanCache: Record<string, string[]> = {};
|
|
33
|
+
|
|
34
|
+
static #getNewest(stat: { mtimeMs: number, ctimeMs: number }): number {
|
|
35
|
+
return Math.max(stat.mtimeMs, stat.ctimeMs);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Simple file scanning
|
|
40
|
+
*/
|
|
41
|
+
static async scanFolder(folder: string, mainSource = false): Promise<string[]> {
|
|
42
|
+
if (!mainSource && folder in this.#scanCache) {
|
|
43
|
+
return this.#scanCache[folder];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!await fs.stat(folder).catch(() => false)) {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const topFolders = new Set(mainSource ? [] : ['src', 'bin', 'support']);
|
|
51
|
+
const topFiles = new Set(mainSource ? [] : [...INDEX_FILES, 'package.json']);
|
|
52
|
+
const out: string[] = [];
|
|
53
|
+
|
|
54
|
+
if (!fs.stat(folder).catch(() => false)) {
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
const stack: [string, number][] = [[folder, 0]];
|
|
58
|
+
while (stack.length) {
|
|
59
|
+
const popped = stack.pop();
|
|
60
|
+
if (!popped) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const [top, depth] = popped;
|
|
65
|
+
|
|
66
|
+
// Don't navigate into sub-folders with package.json's
|
|
67
|
+
if (top !== folder && await fs.stat(`${top}/package.json`).catch(() => false)) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
for (const sub of await fs.readdir(top)) {
|
|
72
|
+
const stat = await fs.stat(`${top}/${sub}`);
|
|
73
|
+
if (stat.isFile()) {
|
|
74
|
+
if (!sub.startsWith('.') && (depth > 0 || !topFiles.size || topFiles.has(sub))) {
|
|
75
|
+
out.push(`${top}/${sub}`);
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
if (!sub.includes('node_modules') && !sub.startsWith('.') && (depth > 0 || !topFolders.size || topFolders.has(sub))) {
|
|
79
|
+
stack.push([`${top}/${sub}`, depth + 1]);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!mainSource) {
|
|
86
|
+
this.#scanCache[folder] = out;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get file type for a file name
|
|
94
|
+
*/
|
|
95
|
+
static getFileType(moduleFile: string): ManifestModuleFileType {
|
|
96
|
+
if (moduleFile === 'package.json') {
|
|
97
|
+
return 'package-json';
|
|
98
|
+
} else if (
|
|
99
|
+
moduleFile.startsWith('support/fixtures/') ||
|
|
100
|
+
moduleFile.startsWith('test/fixtures/') ||
|
|
101
|
+
moduleFile.startsWith('support/resources/')
|
|
102
|
+
) {
|
|
103
|
+
return 'fixture';
|
|
104
|
+
} else if (moduleFile.endsWith('.d.ts')) {
|
|
105
|
+
return 'typings';
|
|
106
|
+
} else {
|
|
107
|
+
const ext = path.extname(moduleFile);
|
|
108
|
+
return EXT_MAPPING[ext] ?? 'unknown';
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get file type for a file name
|
|
114
|
+
*/
|
|
115
|
+
static getFileProfile(moduleFile: string): ManifestProfile | undefined {
|
|
116
|
+
if (moduleFile.startsWith('support/transform')) {
|
|
117
|
+
return 'compile';
|
|
118
|
+
} else if (moduleFile.startsWith('support/test/') || moduleFile.startsWith('test/')) {
|
|
119
|
+
return 'test';
|
|
120
|
+
} else if (moduleFile.startsWith('doc/') || moduleFile === 'DOC.ts') {
|
|
121
|
+
return 'doc';
|
|
122
|
+
} else {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Get folder key
|
|
129
|
+
*/
|
|
130
|
+
static getFolderKey(moduleFile: string): ManifestModuleFolderType {
|
|
131
|
+
const folderLocation = moduleFile.indexOf('/');
|
|
132
|
+
if (folderLocation > 0) {
|
|
133
|
+
if (moduleFile.startsWith('test/fixtures/')) {
|
|
134
|
+
return 'test/fixtures';
|
|
135
|
+
} else if (moduleFile.startsWith('support/fixtures/')) {
|
|
136
|
+
return 'support/fixtures';
|
|
137
|
+
} else if (moduleFile.startsWith('support/resources/')) {
|
|
138
|
+
return 'support/resources';
|
|
139
|
+
} else if (moduleFile.startsWith('support/transform')) {
|
|
140
|
+
return '$transformer';
|
|
141
|
+
}
|
|
142
|
+
const key = moduleFile.substring(0, folderLocation);
|
|
143
|
+
switch (key) {
|
|
144
|
+
case 'src':
|
|
145
|
+
case 'bin':
|
|
146
|
+
case 'test':
|
|
147
|
+
case 'doc':
|
|
148
|
+
case 'resources':
|
|
149
|
+
case 'support': return key;
|
|
150
|
+
default: return '$other';
|
|
151
|
+
}
|
|
152
|
+
} else if (moduleFile === 'DOC.ts') {
|
|
153
|
+
return 'doc';
|
|
154
|
+
} else if (INDEX_FILES.has(moduleFile)) {
|
|
155
|
+
return '$index';
|
|
156
|
+
} else if (moduleFile === 'package.json') {
|
|
157
|
+
return '$package';
|
|
158
|
+
} else {
|
|
159
|
+
return '$root';
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Convert file (by ext) to a known file type and also retrieve its latest timestamp
|
|
165
|
+
*/
|
|
166
|
+
static async transformFile(moduleFile: string, full: string): Promise<ManifestModuleFile> {
|
|
167
|
+
const res: ManifestModuleFile = [moduleFile, this.getFileType(moduleFile), this.#getNewest(await fs.stat(full))];
|
|
168
|
+
const profile = this.getFileProfile(moduleFile);
|
|
169
|
+
return profile ? [...res, profile] : res;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Visit a module and describe files, and metadata
|
|
174
|
+
*/
|
|
175
|
+
static async describeModule(ctx: ManifestContext, dep: ModuleDep): Promise<ManifestModule> {
|
|
176
|
+
const { main, mainSource, local, name, version, sourcePath, profileSet, parentSet, internal } = dep;
|
|
177
|
+
|
|
178
|
+
const files: ManifestModule['files'] = {};
|
|
179
|
+
|
|
180
|
+
for (const file of await this.scanFolder(sourcePath, mainSource)) {
|
|
181
|
+
// Group by top folder
|
|
182
|
+
const moduleFile = file.replace(`${sourcePath}/`, '');
|
|
183
|
+
const entry = await this.transformFile(moduleFile, file);
|
|
184
|
+
const key = this.getFolderKey(moduleFile);
|
|
185
|
+
(files[key] ??= []).push(entry);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Refine non-main source
|
|
189
|
+
if (!mainSource) {
|
|
190
|
+
files.$root = files.$root?.filter(([file, type]) => type !== 'ts');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const profiles = [...profileSet].sort();
|
|
194
|
+
const parents = [...parentSet].sort();
|
|
195
|
+
const outputFolder = `node_modules/${name}`;
|
|
196
|
+
const sourceFolder = sourcePath === ctx.workspacePath ? '' : sourcePath.replace(`${ctx.workspacePath}/`, '');
|
|
197
|
+
|
|
198
|
+
const res = { main, name, version, local, internal, sourceFolder, outputFolder, files, profiles, parents, };
|
|
199
|
+
return res;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Produce all modules for a given manifest folder, adding in some given modules when developing framework
|
|
204
|
+
*/
|
|
205
|
+
static async produceModules(ctx: ManifestContext): Promise<Record<string, ManifestModule>> {
|
|
206
|
+
const visitor = new ModuleDependencyVisitor(ctx);
|
|
207
|
+
const mainPath = path.resolve(ctx.workspacePath, ctx.mainFolder);
|
|
208
|
+
const declared = await PackageUtil.visitPackages(mainPath, visitor);
|
|
209
|
+
const sorted = [...declared].sort((a, b) => a.name.localeCompare(b.name));
|
|
210
|
+
const modules = await Promise.all(sorted.map(x => this.describeModule(ctx, x)));
|
|
211
|
+
return Object.fromEntries(modules.map(m => [m.name, m]));
|
|
212
|
+
}
|
|
213
|
+
}
|