@travetto/manifest 7.0.0-rc.0 → 7.0.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/context.ts +5 -5
- package/src/delta.ts +23 -23
- package/src/dependencies.ts +39 -38
- package/src/file.ts +0 -7
- package/src/manifest-index.ts +43 -26
- package/src/module.ts +12 -12
- package/src/package.ts +19 -18
- package/src/path.ts +2 -2
- package/src/types/common.ts +3 -1
- package/src/types/manifest.ts +1 -1
- package/src/types/package.ts +1 -1
- package/src/util.ts +5 -5
package/package.json
CHANGED
package/src/context.ts
CHANGED
|
@@ -8,25 +8,25 @@ import type { ManifestContext } from './types/context.ts';
|
|
|
8
8
|
type Pkg = Package & { path: string };
|
|
9
9
|
|
|
10
10
|
// eslint-disable-next-line no-bitwise
|
|
11
|
-
const toPort = (
|
|
12
|
-
const toPosix = (
|
|
11
|
+
const toPort = (location: string): number => (Math.abs([...location].reduce((a, b) => (a * 33) ^ b.charCodeAt(0), 5381)) % 29000) + 20000;
|
|
12
|
+
const toPosix = (location: string): string => location.replaceAll('\\', '/');
|
|
13
13
|
const readPackage = (file: string): Pkg => ({ ...JSON.parse(readFileSync(file, 'utf8')), path: toPosix(path.dirname(file)) });
|
|
14
14
|
|
|
15
15
|
/** Find package */
|
|
16
16
|
function findPackage(base: string, pred: (_p?: Pkg) => boolean): Pkg {
|
|
17
17
|
let folder = `${base}/.`;
|
|
18
|
-
let
|
|
18
|
+
let previous: string;
|
|
19
19
|
let pkg: Pkg | undefined;
|
|
20
20
|
const packages: Pkg[] = [];
|
|
21
21
|
|
|
22
22
|
do {
|
|
23
23
|
pkg && packages.push(pkg);
|
|
24
|
-
|
|
24
|
+
previous = folder;
|
|
25
25
|
folder = path.dirname(folder);
|
|
26
26
|
const folderPkg = path.resolve(folder, 'package.json');
|
|
27
27
|
pkg = existsSync(folderPkg) ? readPackage(folderPkg) : pkg;
|
|
28
28
|
} while (
|
|
29
|
-
|
|
29
|
+
previous !== folder && // Not at root
|
|
30
30
|
!pred(pkg) && // Matches criteria
|
|
31
31
|
!existsSync(path.resolve(folder, '.git')) // Not at source root
|
|
32
32
|
);
|
package/src/delta.ts
CHANGED
|
@@ -4,10 +4,10 @@ import { ManifestModuleUtil } from './module.ts';
|
|
|
4
4
|
import { path } from './path.ts';
|
|
5
5
|
|
|
6
6
|
import type { ManifestModule, ManifestModuleCore, ManifestModuleFile, ManifestRoot } from './types/manifest.ts';
|
|
7
|
-
import type { ManifestModuleFileType, ManifestModuleFolderType } from './types/common.ts';
|
|
7
|
+
import type { ChangeEventType, ManifestModuleFileType, ManifestModuleFolderType } from './types/common.ts';
|
|
8
8
|
import type { ManifestContext } from './types/context.ts';
|
|
9
9
|
|
|
10
|
-
type DeltaEventType =
|
|
10
|
+
type DeltaEventType = ChangeEventType | 'missing' | 'dirty';
|
|
11
11
|
type DeltaModule = ManifestModuleCore & { files: Record<string, ManifestModuleFile> };
|
|
12
12
|
export type DeltaEvent = { file: string, type: DeltaEventType, module: string, sourceFile: string };
|
|
13
13
|
|
|
@@ -15,7 +15,7 @@ const VALID_SOURCE_FOLDERS = new Set<ManifestModuleFolderType>(['bin', 'src', 't
|
|
|
15
15
|
const VALID_SOURCE_TYPE = new Set<ManifestModuleFileType>(['js', 'ts', 'package-json']);
|
|
16
16
|
const VALID_OUTPUT_TYPE = new Set<ManifestModuleFileType>([...VALID_SOURCE_TYPE, 'typings']);
|
|
17
17
|
|
|
18
|
-
const TypedObject: { keys<T = unknown, K extends keyof T = keyof T>(
|
|
18
|
+
const TypedObject: { keys<T = unknown, K extends keyof T = keyof T>(item: T): K[] } & ObjectConstructor = Object;
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Produce delta for the manifest
|
|
@@ -38,47 +38,47 @@ export class ManifestDeltaUtil {
|
|
|
38
38
|
const root = path.resolve(ctx.workspace.path, ctx.build.outputFolder, left.outputFolder);
|
|
39
39
|
const right = new Set(
|
|
40
40
|
(await ManifestModuleUtil.scanFolder(ctx, root, left.main))
|
|
41
|
-
.filter(
|
|
42
|
-
const type = ManifestModuleUtil.getFileType(
|
|
41
|
+
.filter(file => {
|
|
42
|
+
const type = ManifestModuleUtil.getFileType(file);
|
|
43
43
|
return VALID_SOURCE_TYPE.has(type);
|
|
44
44
|
})
|
|
45
|
-
.map(
|
|
45
|
+
.map(file => ManifestModuleUtil.withoutSourceExtension(file.replace(`${root}/`, '')))
|
|
46
46
|
);
|
|
47
47
|
|
|
48
|
-
for (const
|
|
49
|
-
const output = ManifestModuleUtil.withOutputExtension(`${outputFolder}/${left.outputFolder}/${
|
|
50
|
-
const [, ,
|
|
48
|
+
for (const file of Object.keys(left.files)) {
|
|
49
|
+
const output = ManifestModuleUtil.withOutputExtension(`${outputFolder}/${left.outputFolder}/${file}`);
|
|
50
|
+
const [, , leftTimestamp] = left.files[file];
|
|
51
51
|
const stat = await fs.stat(output).catch(() => undefined);
|
|
52
|
-
right.delete(ManifestModuleUtil.withoutSourceExtension(
|
|
52
|
+
right.delete(ManifestModuleUtil.withoutSourceExtension(file));
|
|
53
53
|
|
|
54
54
|
if (!stat) {
|
|
55
|
-
add(
|
|
55
|
+
add(file, 'create');
|
|
56
56
|
} else {
|
|
57
|
-
const
|
|
58
|
-
if (
|
|
59
|
-
add(
|
|
57
|
+
const rightTimestamp = this.#getNewest(stat);
|
|
58
|
+
if (leftTimestamp > rightTimestamp) {
|
|
59
|
+
add(file, 'update');
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
// Deleted
|
|
64
|
-
for (const
|
|
65
|
-
add(
|
|
64
|
+
for (const file of right) {
|
|
65
|
+
add(file, 'delete');
|
|
66
66
|
}
|
|
67
67
|
return out;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
/**
|
|
71
71
|
* Collapse all files in a module
|
|
72
|
-
* @param {ManifestModule}
|
|
72
|
+
* @param {ManifestModule} mod
|
|
73
73
|
* @returns {}
|
|
74
74
|
*/
|
|
75
|
-
static #flattenModuleFiles(
|
|
75
|
+
static #flattenModuleFiles(mod: ManifestModule): Record<string, ManifestModuleFile> {
|
|
76
76
|
const out: Record<string, ManifestModuleFile> = {};
|
|
77
|
-
for (const key of TypedObject.keys(
|
|
77
|
+
for (const key of TypedObject.keys(mod.files)) {
|
|
78
78
|
if (!VALID_SOURCE_FOLDERS.has(key)) {
|
|
79
79
|
continue;
|
|
80
80
|
}
|
|
81
|
-
for (const [name, type, date] of
|
|
81
|
+
for (const [name, type, date] of mod.files?.[key] ?? []) {
|
|
82
82
|
if (VALID_OUTPUT_TYPE.has(type)) {
|
|
83
83
|
out[name] = [name, type, date];
|
|
84
84
|
}
|
|
@@ -93,14 +93,14 @@ export class ManifestDeltaUtil {
|
|
|
93
93
|
static async produceDelta(manifest: ManifestRoot): Promise<DeltaEvent[]> {
|
|
94
94
|
const deltaLeft = Object.fromEntries(
|
|
95
95
|
Object.values(manifest.modules)
|
|
96
|
-
.map(
|
|
96
|
+
.map(mod => [mod.name, { ...mod, files: this.#flattenModuleFiles(mod) }])
|
|
97
97
|
);
|
|
98
98
|
|
|
99
99
|
const out: DeltaEvent[] = [];
|
|
100
100
|
const outputFolder = path.resolve(manifest.workspace.path, manifest.build.outputFolder);
|
|
101
101
|
|
|
102
|
-
for (const
|
|
103
|
-
out.push(...await this.#deltaModules(manifest, outputFolder,
|
|
102
|
+
for (const leftMod of Object.values(deltaLeft)) {
|
|
103
|
+
out.push(...await this.#deltaModules(manifest, outputFolder, leftMod));
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
return out;
|
package/src/dependencies.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { PackageUtil } from './package.ts';
|
|
2
2
|
import { path } from './path.ts';
|
|
3
3
|
|
|
4
|
-
import type { Package,
|
|
4
|
+
import type { Package, PackageDependencyType } from './types/package.ts';
|
|
5
5
|
import type { ManifestContext } from './types/context.ts';
|
|
6
6
|
import type { PackageModule } from './types/manifest.ts';
|
|
7
7
|
|
|
8
8
|
type CreateOpts = Partial<Pick<PackageModule, 'main' | 'workspace' | 'prod'>> & { roleRoot?: boolean, parent?: PackageModule };
|
|
9
9
|
|
|
10
|
-
type
|
|
10
|
+
type VisitableNode = {
|
|
11
11
|
/** Request package */
|
|
12
12
|
pkg: Package;
|
|
13
13
|
/** Children to visit */
|
|
@@ -24,7 +24,8 @@ type Req = {
|
|
|
24
24
|
export class PackageModuleVisitor {
|
|
25
25
|
|
|
26
26
|
static async visit(ctx: ManifestContext): Promise<Iterable<PackageModule>> {
|
|
27
|
-
const visitor = new PackageModuleVisitor(ctx, Object.fromEntries((await PackageUtil.resolveWorkspaces(ctx))
|
|
27
|
+
const visitor = new PackageModuleVisitor(ctx, Object.fromEntries((await PackageUtil.resolveWorkspaces(ctx))
|
|
28
|
+
.map(workspace => [workspace.name, workspace.path])));
|
|
28
29
|
return visitor.visit();
|
|
29
30
|
}
|
|
30
31
|
|
|
@@ -42,7 +43,7 @@ export class PackageModuleVisitor {
|
|
|
42
43
|
/**
|
|
43
44
|
* Build a package module
|
|
44
45
|
*/
|
|
45
|
-
#create(sourcePath: string, { main, workspace, prod = false, roleRoot = false, parent }: CreateOpts = {}):
|
|
46
|
+
#create(sourcePath: string, { main, workspace, prod = false, roleRoot = false, parent }: CreateOpts = {}): VisitableNode {
|
|
46
47
|
const pkg = PackageUtil.readPackage(sourcePath);
|
|
47
48
|
const value = this.#cache[sourcePath] ??= {
|
|
48
49
|
main,
|
|
@@ -55,31 +56,31 @@ export class PackageModuleVisitor {
|
|
|
55
56
|
outputFolder: `node_modules/${pkg.name}`,
|
|
56
57
|
state: {
|
|
57
58
|
childSet: new Set(), parentSet: new Set(), roleSet: new Set(), roleRoot,
|
|
58
|
-
travetto: pkg.travetto,
|
|
59
|
+
travetto: pkg.travetto, prodDependencies: new Set(Object.keys(pkg.dependencies ?? {}))
|
|
59
60
|
}
|
|
60
61
|
};
|
|
61
62
|
|
|
62
|
-
const
|
|
63
|
-
const children = Object.fromEntries(
|
|
63
|
+
const dependencies: PackageDependencyType[] = ['dependencies', ...(value.main ? ['devDependencies'] as const : [])];
|
|
64
|
+
const children = Object.fromEntries(dependencies.flatMap(dependency => Object.entries(pkg[dependency] ?? {})));
|
|
64
65
|
return { pkg, value, children, parent };
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
/**
|
|
68
69
|
* Get monorepo root includes
|
|
69
70
|
*/
|
|
70
|
-
#getMonoRootIncludes(parent:
|
|
71
|
+
#getMonoRootIncludes(parent: VisitableNode): VisitableNode[] {
|
|
71
72
|
if (!(this.#ctx.workspace.mono && !this.#ctx.main.folder)) { // If not mono root, bail
|
|
72
73
|
return [];
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
return Object.values(this.#workspaceModules)
|
|
76
|
-
.map(
|
|
77
|
+
.map(folder => this.#create(folder, { main: true, workspace: true, roleRoot: true, parent: parent.value }));
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
/**
|
|
80
81
|
* Determine default includes
|
|
81
82
|
*/
|
|
82
|
-
#getIncludes(parent:
|
|
83
|
+
#getIncludes(parent: VisitableNode): VisitableNode[] {
|
|
83
84
|
if (this.#ctx.workspace.mono && !this.#ctx.main.folder) { // If mono and not at mono root, bail
|
|
84
85
|
return [];
|
|
85
86
|
}
|
|
@@ -91,8 +92,8 @@ export class PackageModuleVisitor {
|
|
|
91
92
|
);
|
|
92
93
|
} else {
|
|
93
94
|
return Object.values(this.#workspaceModules)
|
|
94
|
-
.filter((
|
|
95
|
-
.map(
|
|
95
|
+
.filter((folder) => PackageUtil.readPackage(folder).travetto?.workspaceInclude)
|
|
96
|
+
.map(folder => this.#create(folder, { workspace: true, parent: parent.value }));
|
|
96
97
|
}
|
|
97
98
|
}
|
|
98
99
|
|
|
@@ -100,50 +101,50 @@ export class PackageModuleVisitor {
|
|
|
100
101
|
* Propagate prod, role information through graph
|
|
101
102
|
*/
|
|
102
103
|
async #complete(mods: Iterable<PackageModule>): Promise<PackageModule[]> {
|
|
103
|
-
const mapping = new Map([...mods].map(
|
|
104
|
+
const mapping = new Map([...mods].map(item => [item.name, { parent: new Set(item.state.parentSet), item }]));
|
|
104
105
|
|
|
105
106
|
// All first-level dependencies should have role filled in (for propagation)
|
|
106
|
-
for (const
|
|
107
|
-
|
|
108
|
-
for (const
|
|
109
|
-
const
|
|
110
|
-
if (
|
|
107
|
+
for (const dependency of [...mods].filter(mod => mod.state.roleRoot)) {
|
|
108
|
+
dependency.state.roleSet.clear(); // Ensure the roleRoot is empty
|
|
109
|
+
for (const child of dependency.state.childSet) { // Visit children
|
|
110
|
+
const childDependency = mapping.get(child)!.item;
|
|
111
|
+
if (childDependency.state.roleRoot) { continue; }
|
|
111
112
|
// Set roles for all top level modules
|
|
112
|
-
|
|
113
|
+
childDependency.state.roleSet = new Set(childDependency.state.travetto?.roles ?? ['std']);
|
|
113
114
|
}
|
|
114
115
|
}
|
|
115
116
|
|
|
116
117
|
// Visit all nodes
|
|
117
118
|
while (mapping.size > 0) {
|
|
118
|
-
const toProcess = [...mapping.values()].filter(
|
|
119
|
+
const toProcess = [...mapping.values()].filter(item => item.parent.size === 0);
|
|
119
120
|
if (!toProcess.length) {
|
|
120
121
|
throw new Error(`We have reached a cycle for ${[...mapping.keys()]}`);
|
|
121
122
|
}
|
|
122
123
|
// Propagate to children
|
|
123
|
-
for (const {
|
|
124
|
-
for (const
|
|
125
|
-
const child = mapping.get(
|
|
124
|
+
for (const { item } of toProcess) {
|
|
125
|
+
for (const childName of item.state.childSet) {
|
|
126
|
+
const child = mapping.get(childName);
|
|
126
127
|
if (!child) { continue; }
|
|
127
|
-
child.parent.delete(
|
|
128
|
+
child.parent.delete(item.name);
|
|
128
129
|
// Propagate roles from parent to child
|
|
129
|
-
if (!child.
|
|
130
|
-
for (const role of
|
|
131
|
-
child.
|
|
130
|
+
if (!child.item.state.roleRoot) {
|
|
131
|
+
for (const role of item.state.roleSet) {
|
|
132
|
+
child.item.state.roleSet.add(role);
|
|
132
133
|
}
|
|
133
134
|
}
|
|
134
135
|
// Allow prod to trickle down as needed
|
|
135
|
-
child.
|
|
136
|
+
child.item.prod ||= (item.prod && item.state.prodDependencies.has(childName));
|
|
136
137
|
}
|
|
137
138
|
}
|
|
138
139
|
// Remove from mapping
|
|
139
|
-
for (const {
|
|
140
|
-
mapping.delete(
|
|
140
|
+
for (const { item } of toProcess) {
|
|
141
|
+
mapping.delete(item.name);
|
|
141
142
|
}
|
|
142
143
|
}
|
|
143
144
|
|
|
144
145
|
// Mark as standard at the end
|
|
145
|
-
for (const
|
|
146
|
-
|
|
146
|
+
for (const dependency of [...mods].filter(mod => mod.state.roleRoot)) {
|
|
147
|
+
dependency.state.roleSet = new Set(['std']);
|
|
147
148
|
}
|
|
148
149
|
|
|
149
150
|
return [...mods].toSorted((a, b) => a.name.localeCompare(b.name));
|
|
@@ -154,12 +155,12 @@ export class PackageModuleVisitor {
|
|
|
154
155
|
*/
|
|
155
156
|
async visit(): Promise<Iterable<PackageModule>> {
|
|
156
157
|
const seen = new Set<PackageModule>();
|
|
157
|
-
const
|
|
158
|
+
const mainRequire = this.#create(this.#mainSourcePath, { main: true, workspace: true, roleRoot: true, prod: true });
|
|
158
159
|
|
|
159
160
|
const queue = [
|
|
160
|
-
|
|
161
|
-
...this.#getMonoRootIncludes(
|
|
162
|
-
...this.#getIncludes(
|
|
161
|
+
mainRequire,
|
|
162
|
+
...this.#getMonoRootIncludes(mainRequire),
|
|
163
|
+
...this.#getIncludes(mainRequire)
|
|
163
164
|
];
|
|
164
165
|
|
|
165
166
|
while (queue.length) {
|
|
@@ -181,8 +182,8 @@ export class PackageModuleVisitor {
|
|
|
181
182
|
}
|
|
182
183
|
|
|
183
184
|
const next = Object.entries(children)
|
|
184
|
-
.map(([
|
|
185
|
-
.map(
|
|
185
|
+
.map(([name, location]) => PackageUtil.resolveVersionPath(pkg, location) ?? PackageUtil.resolvePackagePath(name))
|
|
186
|
+
.map(location => this.#create(location, { parent: node }));
|
|
186
187
|
|
|
187
188
|
queue.push(...next);
|
|
188
189
|
}
|
package/src/file.ts
CHANGED
|
@@ -16,13 +16,6 @@ export class ManifestFileUtil {
|
|
|
16
16
|
await fs.rm(temp, { force: true });
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
/**
|
|
20
|
-
* Read as json
|
|
21
|
-
*/
|
|
22
|
-
static async readAsJson<T = unknown>(file: string): Promise<T> {
|
|
23
|
-
return JSON.parse(await fs.readFile(file, 'utf8'));
|
|
24
|
-
}
|
|
25
|
-
|
|
26
19
|
/**
|
|
27
20
|
* Read as json, sync
|
|
28
21
|
*/
|
package/src/manifest-index.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { ManifestUtil } from './util.ts';
|
|
|
7
7
|
import type { ManifestModule, ManifestRoot, ManifestModuleFile, IndexedModule, IndexedFile, FindConfig } from './types/manifest.ts';
|
|
8
8
|
|
|
9
9
|
const TypedObject: {
|
|
10
|
-
keys<T = unknown, K extends keyof T = keyof T>(
|
|
10
|
+
keys<T = unknown, K extends keyof T = keyof T>(item: T): K[];
|
|
11
11
|
fromEntries<K extends string | symbol, V>(items: ([K, V] | readonly [K, V])[]): Record<K, V>;
|
|
12
12
|
entries<K extends Record<symbol | string, unknown>>(record: K): [keyof K, K[keyof K]][];
|
|
13
13
|
} & ObjectConstructor = Object;
|
|
@@ -56,19 +56,19 @@ export class ManifestIndex {
|
|
|
56
56
|
this.init(`${this.outputRoot}/node_modules/${module}`);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
#moduleFiles(
|
|
60
|
-
return files.map(([
|
|
59
|
+
#moduleFiles(mod: ManifestModule, files: ManifestModuleFile[]): IndexedFile[] {
|
|
60
|
+
return files.map(([file, type, _ts, role = 'std']) => {
|
|
61
61
|
const isSource = type === 'ts' || type === 'js';
|
|
62
|
-
const sourceFile = path.resolve(this.#manifest.workspace.path,
|
|
63
|
-
const js = isSource ? ManifestModuleUtil.withOutputExtension(
|
|
64
|
-
const outputFile = this.#resolveOutput(
|
|
65
|
-
const modImport = `${
|
|
66
|
-
let id = `${
|
|
62
|
+
const sourceFile = path.resolve(this.#manifest.workspace.path, mod.sourceFolder, file);
|
|
63
|
+
const js = isSource ? ManifestModuleUtil.withOutputExtension(file) : file;
|
|
64
|
+
const outputFile = this.#resolveOutput(mod.outputFolder, js);
|
|
65
|
+
const modImport = `${mod.name}/${file}`;
|
|
66
|
+
let id = `${mod.name}:${file}`;
|
|
67
67
|
if (isSource) {
|
|
68
68
|
id = ManifestModuleUtil.withoutSourceExtension(id);
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
return { id, type, sourceFile, outputFile, import: modImport, role, relativeFile:
|
|
71
|
+
return { id, type, sourceFile, outputFile, import: modImport, role, relativeFile: file, module: mod.name };
|
|
72
72
|
});
|
|
73
73
|
}
|
|
74
74
|
|
|
@@ -82,13 +82,13 @@ export class ManifestIndex {
|
|
|
82
82
|
this.#arbitraryLookup = undefined;
|
|
83
83
|
|
|
84
84
|
this.#modules = Object.values(this.#manifest.modules)
|
|
85
|
-
.map(
|
|
86
|
-
...
|
|
87
|
-
outputPath: this.#resolveOutput(
|
|
88
|
-
sourcePath: path.resolve(this.#manifest.workspace.path,
|
|
85
|
+
.map(mod => ({
|
|
86
|
+
...mod,
|
|
87
|
+
outputPath: this.#resolveOutput(mod.outputFolder),
|
|
88
|
+
sourcePath: path.resolve(this.#manifest.workspace.path, mod.sourceFolder),
|
|
89
89
|
children: new Set(),
|
|
90
90
|
files: TypedObject.fromEntries(
|
|
91
|
-
TypedObject.entries(
|
|
91
|
+
TypedObject.entries(mod.files).map(([folder, files]) => [folder, this.#moduleFiles(mod, files ?? [])])
|
|
92
92
|
)
|
|
93
93
|
}));
|
|
94
94
|
|
|
@@ -103,12 +103,12 @@ export class ManifestIndex {
|
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
|
-
this.#modulesByName = Object.fromEntries(this.#modules.map(
|
|
106
|
+
this.#modulesByName = Object.fromEntries(this.#modules.map(mod => [mod.name, mod]));
|
|
107
107
|
|
|
108
108
|
// Store child information
|
|
109
109
|
for (const mod of this.#modules) {
|
|
110
|
-
for (const
|
|
111
|
-
this.#modulesByName[
|
|
110
|
+
for (const parent of mod.parents) {
|
|
111
|
+
this.#modulesByName[parent]?.children.add(mod.name);
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
}
|
|
@@ -125,7 +125,7 @@ export class ManifestIndex {
|
|
|
125
125
|
* @returns
|
|
126
126
|
*/
|
|
127
127
|
getWorkspaceModules(): IndexedModule[] {
|
|
128
|
-
return this.#modules.filter(
|
|
128
|
+
return this.#modules.filter(mod => mod.workspace);
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
/**
|
|
@@ -134,9 +134,9 @@ export class ManifestIndex {
|
|
|
134
134
|
*/
|
|
135
135
|
find(config: FindConfig): IndexedFile[] {
|
|
136
136
|
const searchSpace: IndexedFile[] = [];
|
|
137
|
-
for (const
|
|
138
|
-
if (config.module?.(
|
|
139
|
-
for (const [folder, files] of TypedObject.entries(
|
|
137
|
+
for (const mod of this.#modules) {
|
|
138
|
+
if (config.module?.(mod) ?? true) {
|
|
139
|
+
for (const [folder, files] of TypedObject.entries(mod.files)) {
|
|
140
140
|
if (config.folder?.(folder) ?? true) {
|
|
141
141
|
for (const file of files) {
|
|
142
142
|
if (
|
|
@@ -192,6 +192,14 @@ export class ManifestIndex {
|
|
|
192
192
|
return this.#importToEntry.get(imp);
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
+
/**
|
|
196
|
+
* Get from import path or source file
|
|
197
|
+
* @param importOrSource
|
|
198
|
+
*/
|
|
199
|
+
getFromImportOrSource(importOrSource: string): IndexedFile | undefined {
|
|
200
|
+
return this.getFromImport(importOrSource) ?? this.getFromSource(path.resolve(importOrSource));
|
|
201
|
+
}
|
|
202
|
+
|
|
195
203
|
/**
|
|
196
204
|
* Get module from source file
|
|
197
205
|
* @param source
|
|
@@ -206,14 +214,14 @@ export class ManifestIndex {
|
|
|
206
214
|
*/
|
|
207
215
|
getModuleList(mode: 'workspace' | 'all', exprList: string = ''): Set<string> {
|
|
208
216
|
const allMods = Object.keys(this.#manifest.modules);
|
|
209
|
-
const active = new Set<string>(mode === 'workspace' ? this.getWorkspaceModules().map(
|
|
217
|
+
const active = new Set<string>(mode === 'workspace' ? this.getWorkspaceModules().map(item => item.name) : allMods);
|
|
210
218
|
|
|
211
219
|
for (const expr of exprList.split(/,/g)) {
|
|
212
|
-
const [,
|
|
220
|
+
const [, negative, mod] = expr.trim().match(/(-|[+])?([^+\- ]{1,150})$/) ?? [];
|
|
213
221
|
if (mod) {
|
|
214
222
|
const pattern = new RegExp(`^${mod.replace(/[*]/g, '.*')}$`);
|
|
215
|
-
for (const
|
|
216
|
-
active[
|
|
223
|
+
for (const moduleName of allMods.filter(item => pattern.test(item))) {
|
|
224
|
+
active[negative ? 'delete' : 'add'](moduleName);
|
|
217
225
|
}
|
|
218
226
|
}
|
|
219
227
|
}
|
|
@@ -248,7 +256,7 @@ export class ManifestIndex {
|
|
|
248
256
|
const base = this.#manifest.workspace.path;
|
|
249
257
|
const lookup = this.#arbitraryLookup ??= ManifestUtil.lookupTrie(
|
|
250
258
|
Object.values(this.#manifest.modules),
|
|
251
|
-
|
|
259
|
+
mod => mod.sourceFolder.split('/'),
|
|
252
260
|
sub =>
|
|
253
261
|
!existsSync(path.resolve(base, ...sub, 'package.json')) &&
|
|
254
262
|
!existsSync(path.resolve(base, ...sub, '.git'))
|
|
@@ -256,6 +264,15 @@ export class ManifestIndex {
|
|
|
256
264
|
return lookup(file.replace(`${base}/`, '').split('/'));
|
|
257
265
|
}
|
|
258
266
|
|
|
267
|
+
/**
|
|
268
|
+
* Find the module for an arbitrary import
|
|
269
|
+
*/
|
|
270
|
+
findModuleForArbitraryImport(imp: string): IndexedModule | undefined {
|
|
271
|
+
const importParts = imp.split('/');
|
|
272
|
+
const module = imp.startsWith('@') ? importParts.slice(0, 2).join('/') : importParts[0];
|
|
273
|
+
return this.getModule(module);
|
|
274
|
+
}
|
|
275
|
+
|
|
259
276
|
/**
|
|
260
277
|
* Get manifest module by name
|
|
261
278
|
*/
|
package/src/module.ts
CHANGED
|
@@ -18,8 +18,8 @@ const EXT_MAPPING: Record<string, ManifestModuleFileType> = {
|
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
const INDEX_FILES = new Set(
|
|
21
|
-
['__index__', '__index', 'index', 'jsx-runtime'].flatMap(
|
|
22
|
-
['ts', 'tsx', 'js'].map(ext => `${
|
|
21
|
+
['__index__', '__index', 'index', 'jsx-runtime'].flatMap(file =>
|
|
22
|
+
['ts', 'tsx', 'js'].map(ext => `${file}.${ext}`)
|
|
23
23
|
)
|
|
24
24
|
);
|
|
25
25
|
|
|
@@ -37,16 +37,16 @@ const SUPPORT_FILE_MAP: Record<string, ManifestModuleRole> = {
|
|
|
37
37
|
build: 'build'
|
|
38
38
|
};
|
|
39
39
|
|
|
40
|
-
const
|
|
40
|
+
const SUPPORT_FILE_REGEX = new RegExp(`^support[/](?<name>${Object.keys(SUPPORT_FILE_MAP).join('|')})[./]`);
|
|
41
41
|
|
|
42
42
|
export class ManifestModuleUtil {
|
|
43
43
|
|
|
44
44
|
static TYPINGS_EXT = '.d.ts';
|
|
45
45
|
static OUTPUT_EXT = '.js';
|
|
46
46
|
static SOURCE_DEF_EXT = '.ts';
|
|
47
|
-
static
|
|
48
|
-
static
|
|
49
|
-
static
|
|
47
|
+
static SOURCE_EXT_REGEX = /[.][cm]?[tj]sx?$/;
|
|
48
|
+
static TYPINGS_EXT_REGEX = /[.]d[.][cm]?ts$/;
|
|
49
|
+
static TYPINGS_WITH_MAP_EXT_REGEX = /[.]d[.][cm]?ts([.]map)?$/;
|
|
50
50
|
|
|
51
51
|
static #scanCache: Record<string, string[]> = {};
|
|
52
52
|
|
|
@@ -58,7 +58,7 @@ export class ManifestModuleUtil {
|
|
|
58
58
|
* Replace a source file's extension with a given value
|
|
59
59
|
*/
|
|
60
60
|
static #pathToExtension(sourceFile: string, ext: string): string {
|
|
61
|
-
return sourceFile.replace(ManifestModuleUtil.
|
|
61
|
+
return sourceFile.replace(ManifestModuleUtil.SOURCE_EXT_REGEX, ext);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
/**
|
|
@@ -146,7 +146,7 @@ export class ManifestModuleUtil {
|
|
|
146
146
|
* Get file type for a file name
|
|
147
147
|
*/
|
|
148
148
|
static getFileRole(moduleFile: string): ManifestModuleRole | undefined {
|
|
149
|
-
const matched = SUPPORT_FILE_MAP[moduleFile.match(
|
|
149
|
+
const matched = SUPPORT_FILE_MAP[moduleFile.match(SUPPORT_FILE_REGEX)?.groups?.name ?? ''];
|
|
150
150
|
if (matched) {
|
|
151
151
|
return matched;
|
|
152
152
|
} else if (moduleFile.startsWith('test/')) {
|
|
@@ -199,9 +199,9 @@ export class ManifestModuleUtil {
|
|
|
199
199
|
*/
|
|
200
200
|
static async transformFile(moduleFile: string, full: string): Promise<ManifestModuleFile> {
|
|
201
201
|
const updated = this.#getNewest(await fs.stat(full).catch(() => ({ mtimeMs: 0, ctimeMs: 0 })));
|
|
202
|
-
const
|
|
202
|
+
const moduleFileTuple: ManifestModuleFile = [moduleFile, this.getFileType(moduleFile), updated];
|
|
203
203
|
const role = this.getFileRole(moduleFile);
|
|
204
|
-
return role ? [...
|
|
204
|
+
return role ? [...moduleFileTuple, role] : moduleFileTuple;
|
|
205
205
|
}
|
|
206
206
|
|
|
207
207
|
/**
|
|
@@ -234,8 +234,8 @@ export class ManifestModuleUtil {
|
|
|
234
234
|
*/
|
|
235
235
|
static async produceModules(ctx: ManifestContext): Promise<Record<string, ManifestModule>> {
|
|
236
236
|
const pkgs = await PackageModuleVisitor.visit(ctx);
|
|
237
|
-
const modules = await Promise.all([...pkgs].map(
|
|
238
|
-
return Object.fromEntries(modules.map(
|
|
237
|
+
const modules = await Promise.all([...pkgs].map(mod => this.describeModule(ctx, mod)));
|
|
238
|
+
return Object.fromEntries(modules.map(mod => [mod.name, mod]));
|
|
239
239
|
}
|
|
240
240
|
|
|
241
241
|
/**
|
package/src/package.ts
CHANGED
|
@@ -18,9 +18,9 @@ export class PackageUtil {
|
|
|
18
18
|
static #cache: Record<string, Package> = {};
|
|
19
19
|
static #workspaces: Record<string, PackageWorkspaceEntry[]> = {};
|
|
20
20
|
|
|
21
|
-
static #exec<T>(
|
|
21
|
+
static #exec<T>(workingDirectory: string, cmd: string): Promise<T> {
|
|
22
22
|
const env = { PATH: process.env.PATH, NODE_PATH: process.env.NODE_PATH };
|
|
23
|
-
const text = execSync(cmd, { cwd, encoding: 'utf8', env, stdio: ['pipe', 'pipe'] }).toString().trim();
|
|
23
|
+
const text = execSync(cmd, { cwd: workingDirectory, encoding: 'utf8', env, stdio: ['pipe', 'pipe'] }).toString().trim();
|
|
24
24
|
return JSON.parse(text);
|
|
25
25
|
}
|
|
26
26
|
|
|
@@ -55,13 +55,13 @@ export class PackageUtil {
|
|
|
55
55
|
return path.join(resolved.split(name)[0], name);
|
|
56
56
|
} catch { // When import lookup fails
|
|
57
57
|
let folder = root ?? process.cwd();
|
|
58
|
-
let
|
|
59
|
-
while (folder !==
|
|
58
|
+
let previous = '';
|
|
59
|
+
while (folder !== previous) {
|
|
60
60
|
const pkg = path.resolve(folder, 'node_modules', name, 'package.json');
|
|
61
61
|
if (existsSync(pkg)) {
|
|
62
62
|
return pkg;
|
|
63
63
|
}
|
|
64
|
-
|
|
64
|
+
previous = folder;
|
|
65
65
|
folder = path.dirname(folder);
|
|
66
66
|
}
|
|
67
67
|
}
|
|
@@ -99,20 +99,21 @@ export class PackageUtil {
|
|
|
99
99
|
static async resolveWorkspaces(ctx: ManifestContext): Promise<PackageWorkspaceEntry[]> {
|
|
100
100
|
const rootPath = ctx.workspace.path;
|
|
101
101
|
const cache = path.resolve(rootPath, ctx.build.outputFolder, 'workspaces.json');
|
|
102
|
-
|
|
103
|
-
.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
102
|
+
try {
|
|
103
|
+
return this.#workspaces[rootPath] ??= ManifestFileUtil.readAsJsonSync<PackageWorkspaceEntry[]>(cache);
|
|
104
|
+
} catch {
|
|
105
|
+
let out: PackageWorkspaceEntry[];
|
|
106
|
+
switch (ctx.workspace.manager) {
|
|
107
|
+
case 'yarn':
|
|
108
|
+
case 'npm': {
|
|
109
|
+
const workspaces = await this.#exec<{ location: string, name: string }[]>(rootPath, 'npm query .workspace');
|
|
110
|
+
out = workspaces.map(mod => ({ path: path.resolve(ctx.workspace.path, mod.location), name: mod.name }));
|
|
111
|
+
break;
|
|
112
112
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
}
|
|
114
|
+
await ManifestFileUtil.bufferedFileWrite(cache, JSON.stringify(out));
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
116
117
|
}
|
|
117
118
|
|
|
118
119
|
/**
|
package/src/path.ts
CHANGED
|
@@ -29,7 +29,7 @@ export const path: posix.PlatformPath & {
|
|
|
29
29
|
isAbsolute: (file) => native.isAbsolute(toPosix(file)),
|
|
30
30
|
normalize: (file) => toPosix(native.normalize(toPosix(file))),
|
|
31
31
|
parse: (file) => native.parse(toPosix(file)),
|
|
32
|
-
format: (
|
|
32
|
+
format: (value) => toPosix(native.format(value)),
|
|
33
33
|
toNamespacedPath: (file) => toPosix(native.toNamespacedPath(toPosix(file))),
|
|
34
34
|
} : {
|
|
35
35
|
relative: (from, to) => posix.relative(toPosix(from), toPosix(to)),
|
|
@@ -38,7 +38,7 @@ export const path: posix.PlatformPath & {
|
|
|
38
38
|
isAbsolute: file => posix.isAbsolute(toPosix(file)),
|
|
39
39
|
normalize: file => posix.normalize(toPosix(file)),
|
|
40
40
|
parse: file => posix.parse(toPosix(file)),
|
|
41
|
-
format:
|
|
41
|
+
format: value => posix.format(value),
|
|
42
42
|
toNamespacedPath: file => toPosix(file),
|
|
43
43
|
}
|
|
44
44
|
};
|
package/src/types/common.ts
CHANGED
|
@@ -8,4 +8,6 @@ export type ManifestModuleFolderType =
|
|
|
8
8
|
'test/fixtures' | 'support/fixtures' | 'support/resources' |
|
|
9
9
|
'$transformer';
|
|
10
10
|
|
|
11
|
-
export type ManifestModuleRole = 'std' | 'test' | 'doc' | 'compile' | 'build';
|
|
11
|
+
export type ManifestModuleRole = 'std' | 'test' | 'doc' | 'compile' | 'build';
|
|
12
|
+
|
|
13
|
+
export type ChangeEventType = 'create' | 'update' | 'delete';
|
package/src/types/manifest.ts
CHANGED
|
@@ -70,7 +70,7 @@ export type PackageModule = Omit<ManifestModule, 'files' | 'parents' | 'roles'>
|
|
|
70
70
|
/** Travetto package info */
|
|
71
71
|
travetto?: Package['travetto'];
|
|
72
72
|
/** Prod dependencies */
|
|
73
|
-
|
|
73
|
+
prodDependencies: Set<string>;
|
|
74
74
|
/** Set of parent package names */
|
|
75
75
|
parentSet: Set<string>;
|
|
76
76
|
/** Set of child package names */
|
package/src/types/package.ts
CHANGED
|
@@ -55,6 +55,6 @@ export type Package = {
|
|
|
55
55
|
publishConfig?: { access?: 'restricted' | 'public' };
|
|
56
56
|
};
|
|
57
57
|
|
|
58
|
-
export type
|
|
58
|
+
export type PackageDependencyType = 'dependencies' | 'devDependencies' | 'optionalDependencies' | 'peerDependencies';
|
|
59
59
|
|
|
60
60
|
export type PackageWorkspaceEntry = { name: string, path: string };
|
package/src/util.ts
CHANGED
|
@@ -36,8 +36,8 @@ export class ManifestUtil {
|
|
|
36
36
|
* Produce a production manifest from a given manifest
|
|
37
37
|
*/
|
|
38
38
|
static createProductionManifest(manifest: ManifestRoot): ManifestRoot {
|
|
39
|
-
const prodModules = Object.values(manifest.modules).filter(
|
|
40
|
-
const prodModNames = new Set([...prodModules.map(
|
|
39
|
+
const prodModules = Object.values(manifest.modules).filter(mod => mod.prod);
|
|
40
|
+
const prodModNames = new Set([...prodModules.map(mod => mod.name)]);
|
|
41
41
|
return {
|
|
42
42
|
generated: manifest.generated,
|
|
43
43
|
workspace: manifest.workspace,
|
|
@@ -48,8 +48,8 @@ export class ManifestUtil {
|
|
|
48
48
|
},
|
|
49
49
|
main: manifest.main,
|
|
50
50
|
modules: Object.fromEntries(
|
|
51
|
-
prodModules.map(
|
|
52
|
-
parents:
|
|
51
|
+
prodModules.map(mod => [mod.name, Object.assign(mod, {
|
|
52
|
+
parents: mod.parents.filter(parent => prodModNames.has(parent))
|
|
53
53
|
})])
|
|
54
54
|
),
|
|
55
55
|
};
|
|
@@ -136,7 +136,7 @@ export class ManifestUtil {
|
|
|
136
136
|
* Efficient lookup for path-based graphs
|
|
137
137
|
*/
|
|
138
138
|
static lookupTrie<T>(
|
|
139
|
-
inputs: T[], getPath: (
|
|
139
|
+
inputs: T[], getPath: (value: T) => string[], validateUnknown?: (pth: string[]) => boolean
|
|
140
140
|
): (pth: string[]) => T | undefined {
|
|
141
141
|
type TrieNode = { value?: T, subs: Record<string, TrieNode> };
|
|
142
142
|
const root: TrieNode = { subs: {} };
|