@travetto/manifest 4.0.0-rc.0 → 4.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/README.md +13 -8
- package/__index__.ts +4 -1
- package/bin/context.d.ts +1 -1
- package/bin/context.js +39 -30
- package/package.json +1 -1
- package/src/delta.ts +10 -9
- package/src/dependencies.ts +123 -97
- package/src/file.ts +1 -21
- package/src/manifest-index.ts +46 -54
- package/src/module.ts +22 -37
- package/src/package.ts +19 -90
- package/src/runtime.ts +19 -2
- 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 +56 -0
- package/src/util.ts +70 -15
- package/src/types.ts +0 -140
package/README.md
CHANGED
|
@@ -30,12 +30,12 @@ During the compilation process, the compiler needs to know every file that is el
|
|
|
30
30
|
Additionally, once the code has been compiled (or even bundled after that), the executing process needs to know what files are available for loading, and any patterns necessary for knowing which files to load versus which ones to ignore. This allows for dynamic loading of modules/files without knowledge/access to the file system, and in a more performant manner.
|
|
31
31
|
|
|
32
32
|
## Manifest Delta
|
|
33
|
-
During the compilation process, it is helpful to know how the output content differs from the manifest, which is produced from the source input. The [ManifestDeltaUtil](https://github.com/travetto/travetto/tree/main/module/manifest/src/delta.ts#
|
|
33
|
+
During the compilation process, it is helpful to know how the output content differs from the manifest, which is produced from the source input. The [ManifestDeltaUtil](https://github.com/travetto/travetto/tree/main/module/manifest/src/delta.ts#L21) provides the functionality for a given manifest, and will produce a stream of changes grouped by module. This is the primary input into the [Compiler](https://github.com/travetto/travetto/tree/main/module/compiler#readme "The compiler infrastructure for the Travetto framework")'s incremental behavior to know when a file has changed and needs to be recompiled.
|
|
34
34
|
|
|
35
35
|
## Class and Function Metadata
|
|
36
36
|
For the framework to work properly, metadata needs to be collected about files, classes and functions to uniquely identify them, with support for detecting changes during live reloads. To achieve this, every `class` is decorated with an additional field of `Ⲑid`. `Ⲑid` represents a computed id that is tied to the file/class combination.
|
|
37
37
|
|
|
38
|
-
`Ⲑid` is used heavily throughout the framework for determining which classes are owned by the framework, and being able to lookup the needed data from the [RuntimeIndex](https://github.com/travetto/travetto/tree/main/module/manifest/src/runtime.ts#
|
|
38
|
+
`Ⲑid` is used heavily throughout the framework for determining which classes are owned by the framework, and being able to lookup the needed data from the [RuntimeIndex](https://github.com/travetto/travetto/tree/main/module/manifest/src/runtime.ts#L14) using the `getFunctionMetadata` method.
|
|
39
39
|
|
|
40
40
|
**Code: Test Class**
|
|
41
41
|
```typescript
|
|
@@ -97,13 +97,15 @@ By default, all paths within the framework are assumed to be in a POSIX style, a
|
|
|
97
97
|
"path": "<generated>",
|
|
98
98
|
"mono": true,
|
|
99
99
|
"manager": "npm",
|
|
100
|
-
"type": "commonjs"
|
|
100
|
+
"type": "commonjs",
|
|
101
|
+
"defaultEnv": "local"
|
|
101
102
|
},
|
|
102
103
|
"build": {
|
|
103
104
|
"compilerFolder": ".trv/compiler",
|
|
104
105
|
"compilerUrl": "http://127.0.0.1:26803",
|
|
105
106
|
"compilerModuleFolder": "module/compiler",
|
|
106
|
-
"outputFolder": ".trv/output"
|
|
107
|
+
"outputFolder": ".trv/output",
|
|
108
|
+
"toolFolder": ".trv/tool"
|
|
107
109
|
},
|
|
108
110
|
"main": {
|
|
109
111
|
"name": "@travetto/manifest",
|
|
@@ -114,15 +116,15 @@ By default, all paths within the framework are assumed to be in a POSIX style, a
|
|
|
114
116
|
"modules": {
|
|
115
117
|
"@travetto/manifest": {
|
|
116
118
|
"main": true,
|
|
119
|
+
"prod": true,
|
|
117
120
|
"name": "@travetto/manifest",
|
|
118
121
|
"version": "x.x.x",
|
|
119
|
-
"
|
|
122
|
+
"workspace": true,
|
|
120
123
|
"internal": false,
|
|
121
124
|
"sourceFolder": "module/manifest",
|
|
122
125
|
"outputFolder": "node_modules/@travetto/manifest",
|
|
123
126
|
"roles": [ "std" ],
|
|
124
127
|
"parents": [],
|
|
125
|
-
"prod": true,
|
|
126
128
|
"files": {
|
|
127
129
|
"$root": [
|
|
128
130
|
[ "DOC.html", "unknown", 1868155200000 ],
|
|
@@ -160,8 +162,11 @@ By default, all paths within the framework are assumed to be in a POSIX style, a
|
|
|
160
162
|
[ "src/package.ts", "ts", 1868155200000 ],
|
|
161
163
|
[ "src/path.ts", "ts", 1868155200000 ],
|
|
162
164
|
[ "src/runtime.ts", "ts", 1868155200000 ],
|
|
163
|
-
[ "src/
|
|
164
|
-
[ "src/
|
|
165
|
+
[ "src/util.ts", "ts", 1868155200000 ],
|
|
166
|
+
[ "src/types/common.ts", "ts", 1868155200000 ],
|
|
167
|
+
[ "src/types/context.ts", "ts", 1868155200000 ],
|
|
168
|
+
[ "src/types/manifest.ts", "ts", 1868155200000 ],
|
|
169
|
+
[ "src/types/package.ts", "ts", 1868155200000 ]
|
|
165
170
|
],
|
|
166
171
|
"bin": [
|
|
167
172
|
[ "bin/context.d.ts", "typings", 1868155200000 ],
|
package/__index__.ts
CHANGED
|
@@ -8,4 +8,7 @@ export * from './src/runtime';
|
|
|
8
8
|
export * from './src/package';
|
|
9
9
|
export * from './src/util';
|
|
10
10
|
export * from './src/file';
|
|
11
|
-
export * from './src/types';
|
|
11
|
+
export * from './src/types/context';
|
|
12
|
+
export * from './src/types/package';
|
|
13
|
+
export * from './src/types/manifest';
|
|
14
|
+
export * from './src/types/common';
|
package/bin/context.d.ts
CHANGED
package/bin/context.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* @typedef {import('../src/types').Package & { path:string }} Pkg
|
|
4
|
+
* @typedef {import('../src/types/package').Package & { path:string }} Pkg
|
|
5
5
|
* @typedef {Pkg & { mono: boolean, manager: 'yarn'|'npm', resolve: (file:string) => string}} Workspace
|
|
6
|
-
* @typedef {import('../src/types').ManifestContext} ManifestContext
|
|
6
|
+
* @typedef {import('../src/types/context').ManifestContext} ManifestContext
|
|
7
7
|
*/
|
|
8
|
-
import
|
|
8
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import { createRequire } from 'node:module';
|
|
11
11
|
|
|
@@ -17,24 +17,26 @@ const OUTPUT_FOLDER = '.trv/output';
|
|
|
17
17
|
/**
|
|
18
18
|
* Read package.json or return undefined if missing
|
|
19
19
|
* @param {string} dir
|
|
20
|
-
* @returns {
|
|
20
|
+
* @returns {Pkg|undefined}
|
|
21
21
|
*/
|
|
22
|
-
|
|
22
|
+
function $readPackage(dir) {
|
|
23
23
|
dir = dir.endsWith('.json') ? path.dirname(dir) : dir;
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
try {
|
|
25
|
+
const v = readFileSync(path.resolve(dir, 'package.json'), 'utf8');
|
|
26
|
+
return ({ ...JSON.parse(v), path: path.resolve(dir) });
|
|
27
|
+
} catch { }
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
/**
|
|
29
31
|
* Find package.json for a given folder
|
|
30
32
|
* @param {string} dir
|
|
31
|
-
* @return {
|
|
33
|
+
* @return {Pkg}
|
|
32
34
|
*/
|
|
33
|
-
|
|
35
|
+
function $findPackage(dir) {
|
|
34
36
|
let prev;
|
|
35
37
|
let pkg, curr = path.resolve(dir);
|
|
36
38
|
while (!pkg && curr !== prev) {
|
|
37
|
-
pkg =
|
|
39
|
+
pkg = $readPackage(curr);
|
|
38
40
|
[prev, curr] = [curr, path.dirname(curr)];
|
|
39
41
|
}
|
|
40
42
|
if (!pkg) {
|
|
@@ -46,9 +48,9 @@ async function $findPackage(dir) {
|
|
|
46
48
|
|
|
47
49
|
/**
|
|
48
50
|
* Get workspace root
|
|
49
|
-
* @return {
|
|
51
|
+
* @return {Workspace}
|
|
50
52
|
*/
|
|
51
|
-
|
|
53
|
+
function $resolveWorkspace(base = process.cwd()) {
|
|
52
54
|
if (base in WS_ROOT) { return WS_ROOT[base]; }
|
|
53
55
|
let folder = base;
|
|
54
56
|
let prev;
|
|
@@ -57,10 +59,10 @@ async function $resolveWorkspace(base = process.cwd()) {
|
|
|
57
59
|
|
|
58
60
|
while (prev !== folder) {
|
|
59
61
|
[prev, prevPkg] = [folder, pkg];
|
|
60
|
-
pkg =
|
|
62
|
+
pkg = $readPackage(folder) ?? pkg;
|
|
61
63
|
if (
|
|
62
64
|
(pkg && (!!pkg.workspaces || !!pkg.travetto?.build?.isolated)) || // if we have a monorepo root, or we are isolated
|
|
63
|
-
|
|
65
|
+
existsSync(path.resolve(folder, '.git')) // we made it to the source repo root
|
|
64
66
|
) {
|
|
65
67
|
break;
|
|
66
68
|
}
|
|
@@ -75,7 +77,7 @@ async function $resolveWorkspace(base = process.cwd()) {
|
|
|
75
77
|
...pkg,
|
|
76
78
|
name: pkg.name ?? 'untitled',
|
|
77
79
|
type: pkg.type,
|
|
78
|
-
manager:
|
|
80
|
+
manager: existsSync(path.resolve(pkg.path, 'yarn.lock')) ? 'yarn' : 'npm',
|
|
79
81
|
resolve: createRequire(`${pkg.path}/node_modules`).resolve.bind(null),
|
|
80
82
|
mono: !!pkg.workspaces || (!pkg.travetto?.build?.isolated && !!prevPkg) // Workspaces or nested projects
|
|
81
83
|
};
|
|
@@ -84,15 +86,16 @@ async function $resolveWorkspace(base = process.cwd()) {
|
|
|
84
86
|
/**
|
|
85
87
|
* Get Compiler url
|
|
86
88
|
* @param {Workspace} ws
|
|
89
|
+
* @param {string} toolFolder
|
|
87
90
|
*/
|
|
88
|
-
|
|
89
|
-
const file = path.resolve(ws.path,
|
|
91
|
+
function $getCompilerUrl(ws, toolFolder) {
|
|
92
|
+
const file = path.resolve(ws.path, toolFolder, 'build.compilerUrl');
|
|
90
93
|
// eslint-disable-next-line no-bitwise
|
|
91
94
|
const port = (Math.abs([...file].reduce((a, b) => (a * 33) ^ b.charCodeAt(0), 5381)) % 29000) + 20000;
|
|
92
95
|
const out = `http://localhost:${port}`;
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
+
if (!existsSync(file)) {
|
|
97
|
+
mkdirSync(path.dirname(file), { recursive: true });
|
|
98
|
+
writeFileSync(file, out, 'utf8');
|
|
96
99
|
}
|
|
97
100
|
return out;
|
|
98
101
|
}
|
|
@@ -102,13 +105,16 @@ async function $getCompilerUrl(ws) {
|
|
|
102
105
|
* @param {Workspace} workspace
|
|
103
106
|
* @param {string|undefined} folder
|
|
104
107
|
*/
|
|
105
|
-
|
|
108
|
+
function $resolveModule(workspace, folder) {
|
|
106
109
|
let mod;
|
|
107
110
|
if (!folder && process.env.TRV_MODULE) {
|
|
108
111
|
mod = process.env.TRV_MODULE;
|
|
109
112
|
if (/[.](t|j)sx?$/.test(mod)) { // Rewrite from file to module
|
|
110
|
-
|
|
111
|
-
.
|
|
113
|
+
try {
|
|
114
|
+
process.env.TRV_MODULE = mod = $findPackage(path.dirname(mod)).name;
|
|
115
|
+
} catch {
|
|
116
|
+
process.env.TRV_MODULE = mod = '';
|
|
117
|
+
}
|
|
112
118
|
}
|
|
113
119
|
}
|
|
114
120
|
|
|
@@ -116,7 +122,7 @@ async function $resolveModule(workspace, folder) {
|
|
|
116
122
|
try {
|
|
117
123
|
folder = path.dirname(workspace.resolve(`${mod}/package.json`));
|
|
118
124
|
} catch {
|
|
119
|
-
const workspacePkg =
|
|
125
|
+
const workspacePkg = $readPackage(workspace.path);
|
|
120
126
|
if (workspacePkg?.name === mod) {
|
|
121
127
|
folder = workspace.path;
|
|
122
128
|
} else {
|
|
@@ -131,12 +137,13 @@ async function $resolveModule(workspace, folder) {
|
|
|
131
137
|
/**
|
|
132
138
|
* Gets build context
|
|
133
139
|
* @param {string} [folder]
|
|
134
|
-
* @return {
|
|
140
|
+
* @return {ManifestContext}
|
|
135
141
|
*/
|
|
136
|
-
export
|
|
137
|
-
const workspace =
|
|
138
|
-
const mod =
|
|
142
|
+
export function getManifestContext(folder) {
|
|
143
|
+
const workspace = $resolveWorkspace(folder);
|
|
144
|
+
const mod = $resolveModule(workspace, folder);
|
|
139
145
|
const build = workspace.travetto?.build ?? {};
|
|
146
|
+
const toolFolder = build.toolFolder ?? TOOL_FOLDER;
|
|
140
147
|
|
|
141
148
|
return {
|
|
142
149
|
workspace: {
|
|
@@ -144,13 +151,15 @@ export async function getManifestContext(folder) {
|
|
|
144
151
|
path: workspace.path,
|
|
145
152
|
mono: workspace.mono,
|
|
146
153
|
manager: workspace.manager,
|
|
147
|
-
type: workspace.type ?? 'commonjs'
|
|
154
|
+
type: workspace.type ?? 'commonjs',
|
|
155
|
+
defaultEnv: workspace.travetto?.defaultEnv ?? 'local'
|
|
148
156
|
},
|
|
149
157
|
build: {
|
|
150
158
|
compilerFolder: build.compilerFolder ?? COMPILER_FOLDER,
|
|
151
|
-
compilerUrl: build.compilerUrl ??
|
|
159
|
+
compilerUrl: build.compilerUrl ?? $getCompilerUrl(workspace, toolFolder),
|
|
152
160
|
compilerModuleFolder: path.dirname(workspace.resolve('@travetto/compiler/package.json')).replace(`${workspace.path}/`, ''),
|
|
153
161
|
outputFolder: build.outputFolder ?? OUTPUT_FOLDER,
|
|
162
|
+
toolFolder
|
|
154
163
|
},
|
|
155
164
|
main: {
|
|
156
165
|
name: mod.name ?? 'untitled',
|
package/package.json
CHANGED
package/src/delta.ts
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
import
|
|
2
|
-
ManifestContext, ManifestModule, ManifestModuleCore, ManifestModuleFile,
|
|
3
|
-
ManifestModuleFileType, ManifestModuleFolderType, ManifestRoot
|
|
4
|
-
} from './types';
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
5
2
|
|
|
6
3
|
import { ManifestModuleUtil } from './module';
|
|
7
|
-
import { ManifestFileUtil } from './file';
|
|
8
4
|
import { path } from './path';
|
|
9
5
|
|
|
6
|
+
import type { ManifestModule, ManifestModuleCore, ManifestModuleFile, ManifestRoot } from './types/manifest';
|
|
7
|
+
import type { ManifestModuleFileType, ManifestModuleFolderType } from './types/common';
|
|
8
|
+
import type { ManifestContext } from './types/context';
|
|
9
|
+
|
|
10
10
|
type DeltaEventType = 'added' | 'changed' | 'removed' | '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
|
|
|
14
14
|
const VALID_SOURCE_FOLDERS = new Set<ManifestModuleFolderType>(['bin', 'src', 'test', 'support', '$index', '$package', 'doc']);
|
|
15
|
-
const
|
|
15
|
+
const VALID_OUTPUT_TYPE = new Set<ManifestModuleFileType>(['js', 'ts', 'package-json']);
|
|
16
|
+
const VALID_SOURCE_TYPE = new Set<ManifestModuleFileType>([...VALID_OUTPUT_TYPE, 'typings']);
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Produce delta for the manifest
|
|
@@ -37,7 +38,7 @@ export class ManifestDeltaUtil {
|
|
|
37
38
|
(await ManifestModuleUtil.scanFolder(root, left.main))
|
|
38
39
|
.filter(x => {
|
|
39
40
|
const type = ManifestModuleUtil.getFileType(x);
|
|
40
|
-
return type
|
|
41
|
+
return VALID_SOURCE_TYPE.has(type);
|
|
41
42
|
})
|
|
42
43
|
.map(x => ManifestModuleUtil.sourceToBlankExt(x.replace(`${root}/`, '')))
|
|
43
44
|
);
|
|
@@ -45,7 +46,7 @@ export class ManifestDeltaUtil {
|
|
|
45
46
|
for (const el of Object.keys(left.files)) {
|
|
46
47
|
const output = ManifestModuleUtil.sourceToOutputExt(`${outputFolder}/${left.outputFolder}/${el}`);
|
|
47
48
|
const [, , leftTs] = left.files[el];
|
|
48
|
-
const stat = await
|
|
49
|
+
const stat = await fs.stat(output).catch(() => undefined);
|
|
49
50
|
right.delete(ManifestModuleUtil.sourceToBlankExt(el));
|
|
50
51
|
|
|
51
52
|
if (!stat) {
|
|
@@ -77,7 +78,7 @@ export class ManifestDeltaUtil {
|
|
|
77
78
|
continue;
|
|
78
79
|
}
|
|
79
80
|
for (const [name, type, date] of m.files?.[key] ?? []) {
|
|
80
|
-
if (
|
|
81
|
+
if (VALID_OUTPUT_TYPE.has(type)) {
|
|
81
82
|
out[name] = [name, type, date];
|
|
82
83
|
}
|
|
83
84
|
}
|
package/src/dependencies.ts
CHANGED
|
@@ -1,137 +1,126 @@
|
|
|
1
1
|
import { PackageUtil } from './package';
|
|
2
2
|
import { path } from './path';
|
|
3
|
-
import { ManifestContext, ManifestModuleRole, PackageVisitor, PackageVisitReq, Package, ManifestDepCore } from './types';
|
|
4
3
|
|
|
5
|
-
|
|
4
|
+
import type { Package, PackageDepType } 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, parent?: PackageModule };
|
|
9
|
+
|
|
10
|
+
type Req = {
|
|
11
|
+
/** Request package */
|
|
6
12
|
pkg: Package;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
/** Children to visit */
|
|
14
|
+
children: Record<string, string>;
|
|
15
|
+
/** Value */
|
|
16
|
+
value: PackageModule;
|
|
17
|
+
/** Parent */
|
|
18
|
+
parent?: PackageModule;
|
|
13
19
|
};
|
|
14
20
|
|
|
15
21
|
/**
|
|
16
22
|
* Used for walking dependencies for collecting modules for the manifest
|
|
17
23
|
*/
|
|
18
|
-
export class
|
|
24
|
+
export class PackageModuleVisitor {
|
|
19
25
|
|
|
20
26
|
constructor(public ctx: ManifestContext) {
|
|
21
27
|
this.#mainSourcePath = path.resolve(this.ctx.workspace.path, this.ctx.main.folder);
|
|
22
28
|
}
|
|
23
29
|
|
|
24
|
-
#mainLikeModules = new Set<string>();
|
|
25
30
|
#mainSourcePath: string;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
* Main source path for searching
|
|
29
|
-
*/
|
|
30
|
-
get rootPath(): string {
|
|
31
|
-
return this.#mainSourcePath;
|
|
32
|
-
}
|
|
31
|
+
#cache: Record<string, PackageModule> = {};
|
|
32
|
+
#workspaceModules: Map<string, string>;
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
35
|
* Initialize visitor, and provide global dependencies
|
|
36
36
|
*/
|
|
37
|
-
async init(
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
pkg.name,
|
|
44
|
-
...Object.entries(pkg.travetto?.build?.withModules ?? []).filter(x => x[1] === 'main').map(x => x[0]),
|
|
45
|
-
// Add workspace folders, for tests and docs
|
|
46
|
-
...workspaceModules.map(x => x.name)
|
|
47
|
-
]);
|
|
48
|
-
|
|
49
|
-
const globals = Object.keys(workspacePkg.travetto?.build?.withModules ?? [])
|
|
50
|
-
.map(name => PackageUtil.packageReq<ModuleDep>(PackageUtil.resolvePackagePath(name), name in (workspacePkg.dependencies ?? {}), true));
|
|
51
|
-
|
|
52
|
-
const workspaceModuleDeps = workspaceModules
|
|
53
|
-
.map(entry => PackageUtil.packageReq<ModuleDep>(path.resolve(req.sourcePath, entry.sourcePath), false, true));
|
|
37
|
+
async init(): Promise<Iterable<Req>> {
|
|
38
|
+
const mainReq = this.#create(this.#mainSourcePath, { main: true, workspace: true, roleRoot: true, prod: true });
|
|
39
|
+
const globals = [mainReq];
|
|
40
|
+
this.#workspaceModules = new Map(
|
|
41
|
+
(await PackageUtil.resolveWorkspaces(this.ctx)).map(x => [x.name, x.path])
|
|
42
|
+
);
|
|
54
43
|
|
|
55
|
-
|
|
56
|
-
|
|
44
|
+
// Treat all workspace modules as main modules
|
|
45
|
+
if (this.ctx.workspace.mono && !this.ctx.main.folder) {
|
|
46
|
+
for (const [, loc] of this.#workspaceModules) {
|
|
47
|
+
globals.push(this.#create(loc, { main: true, workspace: true, roleRoot: true, parent: mainReq.value }));
|
|
48
|
+
}
|
|
49
|
+
} else {
|
|
50
|
+
// If we have 'withModules' at workspace root
|
|
51
|
+
const root = PackageUtil.readPackage(this.ctx.workspace.path);
|
|
52
|
+
for (const [name, type] of Object.entries(root.travetto?.build?.withModules ?? {})) {
|
|
53
|
+
globals.push(this.#create(PackageUtil.resolvePackagePath(name),
|
|
54
|
+
{ main: type === 'main', workspace: true, parent: mainReq.value }
|
|
55
|
+
));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
57
58
|
|
|
58
|
-
|
|
59
|
-
* Is valid dependency for searching
|
|
60
|
-
*/
|
|
61
|
-
valid(req: PackageVisitReq<ModuleDep>): boolean {
|
|
62
|
-
return req.sourcePath === this.#mainSourcePath || (
|
|
63
|
-
(!!req.pkg.travetto || req.pkg.private === true || !req.sourcePath.includes('node_modules'))
|
|
64
|
-
);
|
|
59
|
+
return globals;
|
|
65
60
|
}
|
|
66
61
|
|
|
67
62
|
/**
|
|
68
|
-
*
|
|
63
|
+
* Build a package module
|
|
69
64
|
*/
|
|
70
|
-
create(
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
65
|
+
#create(sourcePath: string, { main, workspace, prod = false, roleRoot = false, parent }: CreateOpts = {}): Req {
|
|
66
|
+
const pkg = PackageUtil.readPackage(sourcePath);
|
|
67
|
+
const value = this.#cache[sourcePath] ??= {
|
|
68
|
+
main,
|
|
69
|
+
prod,
|
|
70
|
+
name: pkg.name,
|
|
71
|
+
version: pkg.version,
|
|
72
|
+
workspace: workspace ?? this.#workspaceModules.has(pkg.name),
|
|
73
|
+
internal: pkg.private === true,
|
|
74
|
+
sourceFolder: sourcePath === this.ctx.workspace.path ? '' : sourcePath.replace(`${this.ctx.workspace.path}/`, ''),
|
|
75
|
+
outputFolder: `node_modules/${pkg.name}`,
|
|
76
|
+
state: {
|
|
77
|
+
childSet: new Set(), parentSet: new Set(), roleSet: new Set(), roleRoot,
|
|
78
|
+
travetto: pkg.travetto, prodDeps: new Set(Object.keys(pkg.dependencies ?? {}))
|
|
79
|
+
}
|
|
81
80
|
};
|
|
82
81
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Visit dependency
|
|
88
|
-
*/
|
|
89
|
-
visit(req: PackageVisitReq<ModuleDep>, dep: ModuleDep): void {
|
|
90
|
-
const { parent } = req;
|
|
91
|
-
if (parent && dep.name !== this.ctx.main.name) {
|
|
92
|
-
dep.parentSet.add(parent.name);
|
|
93
|
-
parent.childSet.add(dep.name);
|
|
94
|
-
}
|
|
82
|
+
const deps: PackageDepType[] = ['dependencies', ...(value.main ? ['devDependencies'] as const : [])];
|
|
83
|
+
const children = Object.fromEntries(deps.flatMap(x => Object.entries(pkg[x] ?? {})));
|
|
84
|
+
return { pkg, value, children, parent };
|
|
95
85
|
}
|
|
96
86
|
|
|
97
87
|
/**
|
|
98
88
|
* Propagate prod, role information through graph
|
|
99
89
|
*/
|
|
100
|
-
complete(
|
|
101
|
-
const mapping = new Map
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
el.prod = false;
|
|
112
|
-
}
|
|
113
|
-
if (main.child.has(el.name) || (el.topLevel && el !== main.el)) { // Direct descendant
|
|
114
|
-
el.roleSet = new Set(el.pkg.travetto?.roles ?? []);
|
|
115
|
-
if (!el.roleSet.size) {
|
|
116
|
-
el.roleSet.add('std');
|
|
117
|
-
}
|
|
90
|
+
async #complete(mods: Iterable<PackageModule>): Promise<PackageModule[]> {
|
|
91
|
+
const mapping = new Map([...mods].map(el => [el.name, { parent: new Set(el.state.parentSet), el }]));
|
|
92
|
+
|
|
93
|
+
// All first-level dependencies should have role filled in (for propagation)
|
|
94
|
+
for (const dep of [...mods].filter(x => x.state.roleRoot)) {
|
|
95
|
+
dep.state.roleSet.clear(); // Ensure the roleRoot is empty
|
|
96
|
+
for (const c of dep.state.childSet) { // Visit children
|
|
97
|
+
const cDep = mapping.get(c)!.el;
|
|
98
|
+
if (cDep.state.roleRoot) { continue; }
|
|
99
|
+
// Set roles for all top level modules
|
|
100
|
+
cDep.state.roleSet = new Set(cDep.state.travetto?.roles ?? ['std']);
|
|
118
101
|
}
|
|
119
102
|
}
|
|
120
103
|
|
|
104
|
+
// Visit all nodes
|
|
121
105
|
while (mapping.size > 0) {
|
|
122
106
|
const toProcess = [...mapping.values()].filter(x => x.parent.size === 0);
|
|
123
107
|
if (!toProcess.length) {
|
|
124
108
|
throw new Error(`We have reached a cycle for ${[...mapping.keys()]}`);
|
|
125
109
|
}
|
|
126
|
-
// Propagate
|
|
127
|
-
for (const { el
|
|
128
|
-
for (const c of
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
110
|
+
// Propagate to children
|
|
111
|
+
for (const { el } of toProcess) {
|
|
112
|
+
for (const c of el.state.childSet) {
|
|
113
|
+
const child = mapping.get(c);
|
|
114
|
+
if (!child) { continue; }
|
|
115
|
+
child.parent.delete(el.name);
|
|
116
|
+
// Propagate roles from parent to child
|
|
117
|
+
if (!child.el.state.roleRoot) {
|
|
118
|
+
for (const role of el.state.roleSet) {
|
|
119
|
+
child.el.state.roleSet.add(role);
|
|
120
|
+
}
|
|
133
121
|
}
|
|
134
|
-
|
|
122
|
+
// Allow prod to trickle down as needed
|
|
123
|
+
child.el.prod ||= (el.prod && el.state.prodDeps.has(c));
|
|
135
124
|
}
|
|
136
125
|
}
|
|
137
126
|
// Remove from mapping
|
|
@@ -140,10 +129,47 @@ export class ModuleDependencyVisitor implements PackageVisitor<ModuleDep> {
|
|
|
140
129
|
}
|
|
141
130
|
}
|
|
142
131
|
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
|
|
132
|
+
// Mark as standard at the end
|
|
133
|
+
for (const dep of [...mods].filter(x => x.state.roleRoot)) {
|
|
134
|
+
dep.state.roleSet = new Set(['std']);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return [...mods].sort((a, b) => a.name.localeCompare(b.name));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Visit packages with ability to track duplicates
|
|
143
|
+
*/
|
|
144
|
+
async visit(): Promise<Iterable<PackageModule>> {
|
|
145
|
+
const seen = new Set<PackageModule>();
|
|
146
|
+
const queue = [...await this.init()];
|
|
147
|
+
|
|
148
|
+
while (queue.length) {
|
|
149
|
+
const { value: node, parent, children, pkg } = queue.shift()!; // Visit initial set first
|
|
150
|
+
if (!node || (!node.workspace && !node.state.travetto)) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Track parentage
|
|
155
|
+
if (node.name !== this.ctx.main.name && parent) {
|
|
156
|
+
node.state.parentSet.add(parent.name);
|
|
157
|
+
parent.state.childSet.add(node.name);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (seen.has(node)) {
|
|
161
|
+
continue;
|
|
162
|
+
} else {
|
|
163
|
+
seen.add(node);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const next = Object.entries(children)
|
|
167
|
+
.map(([n, v]) => PackageUtil.resolveVersionPath(pkg, v) ?? PackageUtil.resolvePackagePath(n))
|
|
168
|
+
.map(loc => this.#create(loc, { parent: node }));
|
|
169
|
+
|
|
170
|
+
queue.push(...next);
|
|
171
|
+
}
|
|
146
172
|
|
|
147
|
-
return
|
|
173
|
+
return await this.#complete(seen);
|
|
148
174
|
}
|
|
149
175
|
}
|
package/src/file.ts
CHANGED
|
@@ -3,7 +3,6 @@ import fs from 'node:fs/promises';
|
|
|
3
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.main.name);
|
|
52
|
-
}
|
|
53
|
-
return path.resolve(ctx.workspace.path, '.trv/tool', ...parts);
|
|
54
|
-
}
|
|
55
35
|
}
|