@travetto/manifest 3.3.2 → 3.4.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -65
- package/__index__.ts +2 -2
- package/bin/context.js +113 -83
- package/package.json +1 -9
- package/src/delta.ts +3 -4
- package/src/dependencies.ts +34 -35
- package/src/file.ts +55 -0
- package/src/manifest-index.ts +32 -59
- package/src/module.ts +8 -10
- package/src/package.ts +15 -27
- package/src/path.ts +1 -1
- package/src/root-index.ts +0 -9
- package/src/types.ts +15 -9
- package/src/util.ts +5 -23
- package/src/watch.ts +0 -208
package/README.md
CHANGED
|
@@ -19,7 +19,6 @@ This module aims to be the boundary between the file system and the code. The m
|
|
|
19
19
|
* Class and Function Metadata
|
|
20
20
|
* Runtime Indexing
|
|
21
21
|
* Path Normalization
|
|
22
|
-
* File Watching
|
|
23
22
|
|
|
24
23
|
## Project Manifesting
|
|
25
24
|
The project manifest fulfills two main goals: Compile-time Support, and Runtime Knowledge of the project.
|
|
@@ -31,7 +30,7 @@ During the compilation process, the compiler needs to know every file that is el
|
|
|
31
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.
|
|
32
31
|
|
|
33
32
|
## Manifest Delta
|
|
34
|
-
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#L20) 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.
|
|
35
34
|
|
|
36
35
|
## Class and Function Metadata
|
|
37
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.
|
|
@@ -87,75 +86,25 @@ Once the manifest is created, the application runtime can now read this manifest
|
|
|
87
86
|
## Path Normalization
|
|
88
87
|
By default, all paths within the framework are assumed to be in a POSIX style, and all input paths are converted to the POSIX style. This works appropriately within a Unix and a Windows environment. This module offers up [path](https://github.com/travetto/travetto/tree/main/module/manifest/src/path.ts#L21) as an equivalent to [Node](https://nodejs.org)'s [http](https://nodejs.org/api/path.html) library. This allows for consistent behavior across all file-interactions, and also allows for easy analysis if [Node](https://nodejs.org)'s [http](https://nodejs.org/api/path.html) library is ever imported.
|
|
89
88
|
|
|
90
|
-
## File Watching
|
|
91
|
-
The module also leverages [@parcel/watcher](https://www.npmjs.com/package/@parcel/watcher), to expose a single function of `watchFolders`. Only the [Compiler](https://github.com/travetto/travetto/tree/main/module/compiler#readme "The compiler infrastructure for the Travetto framework") module packages [@parcel/watcher](https://www.npmjs.com/package/@parcel/watcher) as a direct dependency. This means, that in production, by default all watch operations will fail with a missing dependency.
|
|
92
|
-
|
|
93
|
-
**Code: Watch Configuration**
|
|
94
|
-
```typescript
|
|
95
|
-
export type WatchEvent = { action: 'create' | 'update' | 'delete', file: string, folder: string };
|
|
96
|
-
|
|
97
|
-
export type WatchFolder = {
|
|
98
|
-
/**
|
|
99
|
-
* Source folder
|
|
100
|
-
*/
|
|
101
|
-
src: string;
|
|
102
|
-
/**
|
|
103
|
-
* Target folder name, useful for deconstructing
|
|
104
|
-
*/
|
|
105
|
-
target?: string;
|
|
106
|
-
/**
|
|
107
|
-
* Filter events
|
|
108
|
-
*/
|
|
109
|
-
filter?: (ev: WatchEvent) => boolean;
|
|
110
|
-
/**
|
|
111
|
-
* Only look at immediate folder
|
|
112
|
-
*/
|
|
113
|
-
immediate?: boolean;
|
|
114
|
-
/**
|
|
115
|
-
* List of top level folders to ignore
|
|
116
|
-
*/
|
|
117
|
-
ignore?: string[];
|
|
118
|
-
/**
|
|
119
|
-
* If watching a folder that doesn't exist, should it be created?
|
|
120
|
-
*/
|
|
121
|
-
createMissing?: boolean;
|
|
122
|
-
/**
|
|
123
|
-
* Include files that start with '.'
|
|
124
|
-
*/
|
|
125
|
-
includeHidden?: boolean;
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
export type WatchStream = AsyncIterable<WatchEvent> & { close: () => Promise<void>, add: (item: WatchEvent | WatchEvent[]) => void };
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
This method allows for watching one or more folders, and registering a callback that will fire every time a file changes, and which of the registered folders it was triggered within. The return of the `watchFolders` is a cleanup method, that when invoked will remove and stop all watching behavior.
|
|
132
|
-
|
|
133
|
-
**Code: Watch Configuration**
|
|
134
|
-
```typescript
|
|
135
|
-
export function watchFolders(
|
|
136
|
-
folders: string[] | WatchFolder[],
|
|
137
|
-
config: Omit<WatchFolder, 'src' | 'target'> = {}
|
|
138
|
-
): WatchStream {
|
|
139
|
-
```
|
|
140
|
-
|
|
141
89
|
## Anatomy of a Manifest
|
|
142
90
|
|
|
143
91
|
**Code: Manifest for @travetto/manifest**
|
|
144
92
|
```typescript
|
|
145
93
|
{
|
|
146
94
|
"generated": 1868155200000,
|
|
147
|
-
"moduleType": "commonjs",
|
|
148
|
-
"mainModule": "@travetto/manifest",
|
|
149
|
-
"mainFolder": "module/manifest",
|
|
150
95
|
"workspacePath": "<generated>",
|
|
151
96
|
"monoRepo": true,
|
|
152
|
-
"outputFolder": ".trv_output",
|
|
153
|
-
"toolFolder": ".trv_build",
|
|
154
|
-
"compilerFolder": ".trv_compiler",
|
|
155
97
|
"packageManager": "npm",
|
|
98
|
+
"moduleType": "commonjs",
|
|
99
|
+
"outputFolder": ".trv/output",
|
|
100
|
+
"toolFolder": ".trv/tool",
|
|
101
|
+
"compilerFolder": ".trv/compiler",
|
|
102
|
+
"compilerUrl": "http://127.0.0.1:26803",
|
|
103
|
+
"frameworkVersion": "x.x.x",
|
|
104
|
+
"mainModule": "@travetto/manifest",
|
|
105
|
+
"mainFolder": "module/manifest",
|
|
156
106
|
"version": "x.x.x",
|
|
157
107
|
"description": "Support for project indexing, manifesting, along with file watching",
|
|
158
|
-
"frameworkVersion": "x.x.x",
|
|
159
108
|
"modules": {
|
|
160
109
|
"@travetto/manifest": {
|
|
161
110
|
"main": true,
|
|
@@ -165,6 +114,9 @@ export function watchFolders(
|
|
|
165
114
|
"internal": false,
|
|
166
115
|
"sourceFolder": "module/manifest",
|
|
167
116
|
"outputFolder": "node_modules/@travetto/manifest",
|
|
117
|
+
"roles": [ "std" ],
|
|
118
|
+
"parents": [],
|
|
119
|
+
"prod": true,
|
|
168
120
|
"files": {
|
|
169
121
|
"$root": [
|
|
170
122
|
[ "DOC.html", "unknown", 1868155200000 ],
|
|
@@ -195,6 +147,7 @@ export function watchFolders(
|
|
|
195
147
|
"src": [
|
|
196
148
|
[ "src/delta.ts", "ts", 1868155200000 ],
|
|
197
149
|
[ "src/dependencies.ts", "ts", 1868155200000 ],
|
|
150
|
+
[ "src/file.ts", "ts", 1868155200000 ],
|
|
198
151
|
[ "src/manifest-index.ts", "ts", 1868155200000 ],
|
|
199
152
|
[ "src/module.ts", "ts", 1868155200000 ],
|
|
200
153
|
[ "src/package.ts", "ts", 1868155200000 ],
|
|
@@ -202,16 +155,13 @@ export function watchFolders(
|
|
|
202
155
|
[ "src/root-index.ts", "ts", 1868155200000 ],
|
|
203
156
|
[ "src/types.ts", "ts", 1868155200000 ],
|
|
204
157
|
[ "src/typings.d.ts", "typings", 1868155200000 ],
|
|
205
|
-
[ "src/util.ts", "ts", 1868155200000 ]
|
|
206
|
-
[ "src/watch.ts", "ts", 1868155200000 ]
|
|
158
|
+
[ "src/util.ts", "ts", 1868155200000 ]
|
|
207
159
|
],
|
|
208
160
|
"bin": [
|
|
209
161
|
[ "bin/context.d.ts", "typings", 1868155200000 ],
|
|
210
162
|
[ "bin/context.js", "js", 1868155200000 ]
|
|
211
163
|
]
|
|
212
|
-
}
|
|
213
|
-
"profiles": [ "std" ],
|
|
214
|
-
"parents": []
|
|
164
|
+
}
|
|
215
165
|
}
|
|
216
166
|
}
|
|
217
167
|
}
|
package/__index__.ts
CHANGED
package/bin/context.js
CHANGED
|
@@ -1,131 +1,161 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* @typedef {import('../src/types').Package} Pkg
|
|
4
|
+
* @typedef {import('../src/types').Package & { path:string }} Pkg
|
|
5
|
+
* @typedef {Pkg & { mono: boolean, manager: 'yarn'|'npm', resolve: (file:string) => string}} Workspace
|
|
5
6
|
* @typedef {import('../src/types').ManifestContext} ManifestContext
|
|
6
7
|
*/
|
|
7
8
|
import fs from 'fs/promises';
|
|
8
9
|
import path from 'path';
|
|
9
10
|
import { createRequire } from 'module';
|
|
10
11
|
|
|
12
|
+
/** @type {Record<string, Workspace>} */ const WS_ROOT = {};
|
|
13
|
+
const TOOL_FOLDER = '.trv/tool';
|
|
14
|
+
const COMPILER_FOLDER = '.trv/compiler';
|
|
15
|
+
const OUTPUT_FOLDER = '.trv/output';
|
|
16
|
+
|
|
11
17
|
/**
|
|
12
|
-
*
|
|
13
|
-
* @param {string}
|
|
14
|
-
* @returns {Promise<Pkg>}
|
|
18
|
+
* Read package.json or return undefined if missing
|
|
19
|
+
* @param {string} dir
|
|
20
|
+
* @returns {Promise<Pkg|undefined>}
|
|
15
21
|
*/
|
|
16
|
-
async function $
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
return JSON.parse(await fs.readFile(inputFolder, 'utf8'));
|
|
22
|
+
async function $readPackage(dir) {
|
|
23
|
+
dir = dir.endsWith('.json') ? path.dirname(dir) : dir;
|
|
24
|
+
return await fs.readFile(path.resolve(dir, 'package.json'), 'utf8')
|
|
25
|
+
.then(v => ({ ...JSON.parse(v), path: path.resolve(dir) }), () => undefined);
|
|
21
26
|
}
|
|
22
27
|
|
|
23
|
-
const WS_ROOT = {};
|
|
24
|
-
|
|
25
28
|
/**
|
|
26
|
-
*
|
|
29
|
+
* Find package.json for a given folder
|
|
27
30
|
* @param {string} dir
|
|
28
|
-
* @return {Promise<
|
|
31
|
+
* @return {Promise<Pkg>}
|
|
29
32
|
*/
|
|
30
|
-
async function $
|
|
33
|
+
async function $findPackage(dir) {
|
|
31
34
|
let prev;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
let pkg, curr = path.resolve(dir);
|
|
36
|
+
while (!pkg && curr !== prev) {
|
|
37
|
+
pkg = await $readPackage(curr);
|
|
38
|
+
[prev, curr] = [curr, path.dirname(curr)];
|
|
39
|
+
}
|
|
40
|
+
if (!pkg) {
|
|
41
|
+
throw new Error('Could not find a package.json');
|
|
42
|
+
} else {
|
|
43
|
+
return pkg;
|
|
35
44
|
}
|
|
36
|
-
return dir;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Get module name from a given file
|
|
42
|
-
* @param {string} file
|
|
43
|
-
* @return {Promise<string|void>}
|
|
44
|
-
*/
|
|
45
|
-
async function $getModuleFromFile(file) {
|
|
46
|
-
return $getPkg(await $getModuleRoot(path.dirname(file))).then(v => v.name, () => { });
|
|
47
45
|
}
|
|
48
46
|
|
|
49
47
|
/**
|
|
50
48
|
* Get workspace root
|
|
51
|
-
* @return {Promise<
|
|
49
|
+
* @return {Promise<Workspace>}
|
|
52
50
|
*/
|
|
53
|
-
async function $
|
|
54
|
-
if (base in WS_ROOT) {
|
|
55
|
-
return WS_ROOT[base];
|
|
56
|
-
}
|
|
57
|
-
|
|
51
|
+
async function $resolveWorkspace(base = process.cwd()) {
|
|
52
|
+
if (base in WS_ROOT) { return WS_ROOT[base]; }
|
|
58
53
|
let folder = base;
|
|
59
|
-
let
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
54
|
+
let prev;
|
|
55
|
+
/** @type {Pkg|undefined} */
|
|
56
|
+
let prevPkg, pkg;
|
|
57
|
+
|
|
58
|
+
while (prev !== folder) {
|
|
59
|
+
[prev, prevPkg] = [folder, pkg];
|
|
60
|
+
pkg = await $readPackage(folder) ?? pkg;
|
|
61
|
+
if (
|
|
62
|
+
(pkg && (!!pkg.workspaces || !!pkg.travetto?.isolated)) || // if we have a monorepo root, or we are isolated
|
|
63
|
+
await fs.stat(path.resolve(folder, '.git')).catch(() => { }) // we made it to the source repo root
|
|
64
|
+
) {
|
|
68
65
|
break;
|
|
69
66
|
}
|
|
70
|
-
prevFolder = folder;
|
|
71
67
|
folder = path.dirname(folder);
|
|
72
68
|
}
|
|
73
|
-
return WS_ROOT[base] = base;
|
|
74
|
-
}
|
|
75
69
|
|
|
70
|
+
if (!pkg) {
|
|
71
|
+
throw new Error('Could not find a package.json');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return WS_ROOT[base] = {
|
|
75
|
+
...pkg,
|
|
76
|
+
manager: await fs.stat(path.resolve(pkg.path, 'yarn.lock')).catch(() => { }) ? 'yarn' : 'npm',
|
|
77
|
+
resolve: createRequire(`${pkg.path}/node_modules`).resolve.bind(null),
|
|
78
|
+
mono: !!pkg.workspaces || (!pkg.travetto?.isolated && !!prevPkg) // Workspaces or nested projects
|
|
79
|
+
};
|
|
80
|
+
}
|
|
76
81
|
|
|
77
82
|
/**
|
|
78
|
-
*
|
|
79
|
-
* @param {
|
|
80
|
-
* @return {Promise<ManifestContext>}
|
|
83
|
+
* Get Compiler url
|
|
84
|
+
* @param {Workspace} ws
|
|
81
85
|
*/
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
86
|
+
async function $getCompilerUrl(ws) {
|
|
87
|
+
let out = ws.travetto?.compilerUrl;
|
|
88
|
+
if (!out) {
|
|
89
|
+
const file = path.resolve(ws.path, ws.travetto?.toolFolder ?? TOOL_FOLDER, 'compiler.url');
|
|
90
|
+
// eslint-disable-next-line no-bitwise
|
|
91
|
+
const port = (Math.abs([...file].reduce((a, b) => (a * 33) ^ b.charCodeAt(0), 5381)) % 29000) + 20000;
|
|
92
|
+
out = `http://localhost:${port}`;
|
|
93
|
+
try { await fs.stat(file); } catch {
|
|
94
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
95
|
+
await fs.writeFile(file, out, 'utf8');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return out.replace('localhost', '127.0.0.1');
|
|
99
|
+
}
|
|
85
100
|
|
|
86
|
-
|
|
101
|
+
/**
|
|
102
|
+
* Resolve module folder
|
|
103
|
+
* @param {Workspace} workspace
|
|
104
|
+
* @param {string|undefined} folder
|
|
105
|
+
*/
|
|
106
|
+
async function $resolveModule(workspace, folder) {
|
|
107
|
+
let mod;
|
|
87
108
|
if (!folder && process.env.TRV_MODULE) {
|
|
88
|
-
|
|
89
|
-
if (/[.](t|j)s$/.test(
|
|
90
|
-
process.env.TRV_MODULE = await $
|
|
109
|
+
mod = process.env.TRV_MODULE;
|
|
110
|
+
if (/[.](t|j)s$/.test(mod)) { // Rewrite from file to module
|
|
111
|
+
process.env.TRV_MODULE = mod = await $findPackage(path.dirname(mod))
|
|
112
|
+
.then(v => v.name, () => '');
|
|
91
113
|
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (mod) { // If module provided in lieu of folder
|
|
92
117
|
try {
|
|
93
|
-
folder = path.dirname(
|
|
118
|
+
folder = path.dirname(workspace.resolve(`${mod}/package.json`));
|
|
94
119
|
} catch {
|
|
95
|
-
const workspacePkg =
|
|
96
|
-
if (workspacePkg
|
|
97
|
-
folder =
|
|
120
|
+
const workspacePkg = await $readPackage(workspace.path);
|
|
121
|
+
if (workspacePkg?.name === mod) {
|
|
122
|
+
folder = workspace.path;
|
|
98
123
|
} else {
|
|
99
124
|
throw new Error(`Unable to resolve location for ${folder}`);
|
|
100
125
|
}
|
|
101
126
|
}
|
|
102
127
|
}
|
|
103
128
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const monoRepo = workspacePath !== mainPath || !!workspaces;
|
|
107
|
-
const outputFolder = travetto?.outputFolder ?? '.trv_output';
|
|
129
|
+
return $findPackage(folder ?? '.');
|
|
130
|
+
}
|
|
108
131
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
132
|
+
/**
|
|
133
|
+
* Gets build context
|
|
134
|
+
* @param {string} [folder]
|
|
135
|
+
* @return {Promise<ManifestContext>}
|
|
136
|
+
*/
|
|
137
|
+
export async function getManifestContext(folder) {
|
|
138
|
+
const workspace = await $resolveWorkspace(folder);
|
|
113
139
|
|
|
114
|
-
const
|
|
140
|
+
const [mod, framework, compilerUrl] = await Promise.all([
|
|
141
|
+
$resolveModule(workspace, folder),
|
|
142
|
+
$readPackage(workspace.resolve('@travetto/manifest/package.json')),
|
|
143
|
+
$getCompilerUrl(workspace),
|
|
144
|
+
]);
|
|
115
145
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
146
|
+
return {
|
|
147
|
+
workspacePath: workspace.path,
|
|
148
|
+
monoRepo: workspace.mono,
|
|
149
|
+
packageManager: workspace.manager,
|
|
150
|
+
moduleType: workspace.type ?? 'commonjs',
|
|
151
|
+
outputFolder: workspace.travetto?.outputFolder ?? OUTPUT_FOLDER,
|
|
152
|
+
toolFolder: workspace.travetto?.toolFolder ?? TOOL_FOLDER,
|
|
153
|
+
compilerFolder: workspace.travetto?.compilerFolder ?? COMPILER_FOLDER,
|
|
154
|
+
compilerUrl,
|
|
155
|
+
frameworkVersion: framework?.version ?? '1.0.0',
|
|
156
|
+
mainModule: mod.name ?? 'untitled',
|
|
157
|
+
mainFolder: mod.path === workspace.path ? '' : mod.path.replace(`${workspace.path}/`, ''),
|
|
158
|
+
version: mod.version,
|
|
159
|
+
description: mod.description
|
|
129
160
|
};
|
|
130
|
-
return res;
|
|
131
161
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/manifest",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.0-rc.1",
|
|
4
4
|
"description": "Support for project indexing, manifesting, along with file watching",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"path",
|
|
@@ -30,14 +30,6 @@
|
|
|
30
30
|
"url": "https://github.com/travetto/travetto.git",
|
|
31
31
|
"directory": "module/manifest"
|
|
32
32
|
},
|
|
33
|
-
"peerDependencies": {
|
|
34
|
-
"@parcel/watcher": "^2.1.0"
|
|
35
|
-
},
|
|
36
|
-
"peerDependenciesMeta": {
|
|
37
|
-
"@parcel/watcher": {
|
|
38
|
-
"optional": true
|
|
39
|
-
}
|
|
40
|
-
},
|
|
41
33
|
"travetto": {
|
|
42
34
|
"displayName": "Manifest",
|
|
43
35
|
"docBaseUrl": "https://github.com/travetto/travetto/tree/main"
|
package/src/delta.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import fs from 'fs/promises';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
|
|
4
1
|
import {
|
|
5
2
|
ManifestContext, ManifestModule, ManifestModuleCore, ManifestModuleFile,
|
|
6
3
|
ManifestModuleFileType, ManifestModuleFolderType, ManifestRoot
|
|
7
4
|
} from './types';
|
|
8
5
|
|
|
9
6
|
import { ManifestModuleUtil } from './module';
|
|
7
|
+
import { ManifestFileUtil } from './file';
|
|
8
|
+
import { path } from './path';
|
|
10
9
|
|
|
11
10
|
type DeltaEventType = 'added' | 'changed' | 'removed' | 'missing' | 'dirty';
|
|
12
11
|
type DeltaModule = ManifestModuleCore & { files: Record<string, ManifestModuleFile> };
|
|
@@ -46,7 +45,7 @@ export class ManifestDeltaUtil {
|
|
|
46
45
|
for (const el of Object.keys(left.files)) {
|
|
47
46
|
const output = ManifestModuleUtil.sourceToOutputExt(`${outputFolder}/${left.outputFolder}/${el}`);
|
|
48
47
|
const [, , leftTs] = left.files[el];
|
|
49
|
-
const stat = await
|
|
48
|
+
const stat = await ManifestFileUtil.statFile(output);
|
|
50
49
|
right.delete(ManifestModuleUtil.sourceToBlankExt(el));
|
|
51
50
|
|
|
52
51
|
if (!stat) {
|
package/src/dependencies.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { PackageUtil } from './package';
|
|
2
2
|
import { path } from './path';
|
|
3
|
-
import { ManifestContext,
|
|
3
|
+
import { ManifestContext, ManifestModuleRole, PackageVisitor, PackageVisitReq, Package } from './types';
|
|
4
4
|
|
|
5
5
|
export type ModuleDep = {
|
|
6
|
+
pkg: Package;
|
|
6
7
|
version: string;
|
|
7
8
|
name: string;
|
|
8
9
|
main?: boolean;
|
|
@@ -10,9 +11,11 @@ export type ModuleDep = {
|
|
|
10
11
|
local?: boolean;
|
|
11
12
|
internal?: boolean;
|
|
12
13
|
sourcePath: string;
|
|
13
|
-
childSet:
|
|
14
|
+
childSet: Set<string>;
|
|
14
15
|
parentSet: Set<string>;
|
|
15
|
-
|
|
16
|
+
roleSet: Set<ManifestModuleRole>;
|
|
17
|
+
prod: boolean;
|
|
18
|
+
topLevel?: boolean;
|
|
16
19
|
};
|
|
17
20
|
|
|
18
21
|
/**
|
|
@@ -60,19 +63,13 @@ export class ModuleDependencyVisitor implements PackageVisitor<ModuleDep> {
|
|
|
60
63
|
...workspaceModules.map(x => x.name)
|
|
61
64
|
]);
|
|
62
65
|
|
|
63
|
-
const globals = [
|
|
64
|
-
|
|
65
|
-
...(pkg.travetto?.globalModules ?? [])
|
|
66
|
-
]
|
|
67
|
-
.map(f => PackageUtil.resolvePackagePath(f));
|
|
66
|
+
const globals = (workspacePkg.travetto?.globalModules ?? [])
|
|
67
|
+
.map(name => PackageUtil.packageReq<ModuleDep>(PackageUtil.resolvePackagePath(name), name in (workspacePkg.dependencies ?? {}), true));
|
|
68
68
|
|
|
69
69
|
const workspaceModuleDeps = workspaceModules
|
|
70
|
-
.map(entry => path.resolve(req.sourcePath, entry.sourcePath));
|
|
70
|
+
.map(entry => PackageUtil.packageReq<ModuleDep>(path.resolve(req.sourcePath, entry.sourcePath), false, true));
|
|
71
71
|
|
|
72
|
-
return [
|
|
73
|
-
...globals,
|
|
74
|
-
...workspaceModuleDeps
|
|
75
|
-
].map(s => PackageUtil.packageReq(s, 'global'));
|
|
72
|
+
return [...globals, ...workspaceModuleDeps];
|
|
76
73
|
}
|
|
77
74
|
|
|
78
75
|
/**
|
|
@@ -80,8 +77,7 @@ export class ModuleDependencyVisitor implements PackageVisitor<ModuleDep> {
|
|
|
80
77
|
*/
|
|
81
78
|
valid(req: PackageVisitReq<ModuleDep>): boolean {
|
|
82
79
|
return req.sourcePath === this.#mainSourcePath || (
|
|
83
|
-
req.
|
|
84
|
-
(!!req.pkg.travetto || req.pkg.private === true || !req.sourcePath.includes('node_modules') || req.rel === 'global')
|
|
80
|
+
(!!req.pkg.travetto || req.pkg.private === true || !req.sourcePath.includes('node_modules'))
|
|
85
81
|
);
|
|
86
82
|
}
|
|
87
83
|
|
|
@@ -89,18 +85,16 @@ export class ModuleDependencyVisitor implements PackageVisitor<ModuleDep> {
|
|
|
89
85
|
* Create dependency from request
|
|
90
86
|
*/
|
|
91
87
|
create(req: PackageVisitReq<ModuleDep>): ModuleDep {
|
|
92
|
-
const { pkg
|
|
93
|
-
const
|
|
94
|
-
...profiles ?? []
|
|
95
|
-
]);
|
|
88
|
+
const { pkg, sourcePath } = req;
|
|
89
|
+
const { name, version } = pkg;
|
|
96
90
|
const main = name === this.ctx.mainModule;
|
|
97
91
|
const mainSource = main || this.#mainPatterns.some(x => x.test(name));
|
|
98
92
|
const internal = pkg.private === true;
|
|
99
93
|
const local = internal || mainSource || !sourcePath.includes('node_modules');
|
|
100
94
|
|
|
101
95
|
const dep = {
|
|
102
|
-
name, version, sourcePath, main, mainSource, local, internal,
|
|
103
|
-
parentSet: new Set([]), childSet: new
|
|
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
|
|
104
98
|
};
|
|
105
99
|
|
|
106
100
|
return dep;
|
|
@@ -113,28 +107,30 @@ export class ModuleDependencyVisitor implements PackageVisitor<ModuleDep> {
|
|
|
113
107
|
const { parent } = req;
|
|
114
108
|
if (parent && dep.name !== this.ctx.mainModule) {
|
|
115
109
|
dep.parentSet.add(parent.name);
|
|
116
|
-
|
|
117
|
-
parent.childSet.set(dep.name, set);
|
|
118
|
-
set.add(req.rel);
|
|
110
|
+
parent.childSet.add(dep.name);
|
|
119
111
|
}
|
|
120
112
|
}
|
|
121
113
|
|
|
122
114
|
/**
|
|
123
|
-
* Propagate
|
|
115
|
+
* Propagate prod, role information through graph
|
|
124
116
|
*/
|
|
125
117
|
complete(deps: Set<ModuleDep>): Set<ModuleDep> {
|
|
126
|
-
const mapping = new Map<string, { parent: Set<string>, child:
|
|
118
|
+
const mapping = new Map<string, { parent: Set<string>, child: Set<string>, el: ModuleDep }>();
|
|
127
119
|
for (const el of deps) {
|
|
128
|
-
mapping.set(el.name, { parent: new Set(el.parentSet), child: new
|
|
120
|
+
mapping.set(el.name, { parent: new Set(el.parentSet), child: new Set(el.childSet), el });
|
|
129
121
|
}
|
|
130
122
|
|
|
131
123
|
const main = mapping.get(this.ctx.mainModule)!;
|
|
132
124
|
|
|
133
125
|
// Visit all direct dependencies and mark
|
|
134
|
-
for (const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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;
|
|
138
134
|
}
|
|
139
135
|
}
|
|
140
136
|
|
|
@@ -148,9 +144,10 @@ export class ModuleDependencyVisitor implements PackageVisitor<ModuleDep> {
|
|
|
148
144
|
for (const c of child.keys()) {
|
|
149
145
|
const { el: cDep, parent } = mapping.get(c)!;
|
|
150
146
|
parent.delete(el.name); // Remove from child
|
|
151
|
-
for (const
|
|
152
|
-
cDep.
|
|
147
|
+
for (const role of el.roleSet) {
|
|
148
|
+
cDep.roleSet.add(role);
|
|
153
149
|
}
|
|
150
|
+
cDep.prod ||= el.prod; // Allow prod to trickle down as needed
|
|
154
151
|
}
|
|
155
152
|
}
|
|
156
153
|
// Remove from mapping
|
|
@@ -159,8 +156,10 @@ export class ModuleDependencyVisitor implements PackageVisitor<ModuleDep> {
|
|
|
159
156
|
}
|
|
160
157
|
}
|
|
161
158
|
|
|
162
|
-
// Color
|
|
163
|
-
main.el.
|
|
159
|
+
// Color parent as final step
|
|
160
|
+
main.el.prod = true;
|
|
161
|
+
main.el.roleSet.add('std');
|
|
162
|
+
|
|
164
163
|
return deps;
|
|
165
164
|
}
|
|
166
165
|
}
|
package/src/file.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import os from 'os';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import { readFileSync } from 'fs';
|
|
4
|
+
|
|
5
|
+
import { path } from './path';
|
|
6
|
+
import type { ManifestContext } from './types';
|
|
7
|
+
|
|
8
|
+
export class ManifestFileUtil {
|
|
9
|
+
/**
|
|
10
|
+
* Write file and copy over when ready
|
|
11
|
+
*/
|
|
12
|
+
static async bufferedFileWrite(file: string, content: string | object): Promise<string> {
|
|
13
|
+
const ext = path.extname(file);
|
|
14
|
+
const tempName = `${path.basename(file, ext)}.${process.ppid}.${process.pid}.${Date.now()}.${Math.random()}${ext}`;
|
|
15
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
16
|
+
const temp = path.resolve(os.tmpdir(), tempName);
|
|
17
|
+
await fs.writeFile(temp, typeof content === 'string' ? content : JSON.stringify(content), 'utf8');
|
|
18
|
+
await fs.copyFile(temp, file);
|
|
19
|
+
fs.unlink(temp);
|
|
20
|
+
return file;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Read as json
|
|
25
|
+
*/
|
|
26
|
+
static async readAsJson<T = unknown>(file: string): Promise<T> {
|
|
27
|
+
return JSON.parse(await fs.readFile(file, 'utf8'));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Read as json, sync
|
|
32
|
+
*/
|
|
33
|
+
static readAsJsonSync<T = unknown>(file: string): T {
|
|
34
|
+
return JSON.parse(readFileSync(file, 'utf8'));
|
|
35
|
+
}
|
|
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
|
+
}
|
package/src/manifest-index.ts
CHANGED
|
@@ -3,18 +3,16 @@ import { path } from './path';
|
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
ManifestModule, ManifestModuleCore, ManifestModuleFile,
|
|
6
|
-
ManifestModuleFileType, ManifestModuleFolderType,
|
|
6
|
+
ManifestModuleFileType, ManifestModuleFolderType, ManifestModuleRole, ManifestRoot
|
|
7
7
|
} from './types';
|
|
8
8
|
|
|
9
9
|
import { ManifestUtil } from './util';
|
|
10
10
|
|
|
11
|
-
type ScanTest = ((full: string) => boolean) | { test: (full: string) => boolean };
|
|
12
11
|
export type FindConfig = {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
checkProfile?: boolean;
|
|
12
|
+
folder?: (folder: ManifestModuleFolderType) => boolean;
|
|
13
|
+
module?: (module: IndexedModule) => boolean;
|
|
14
|
+
file?: (file: IndexedFile) => boolean;
|
|
15
|
+
sourceOnly?: boolean;
|
|
18
16
|
};
|
|
19
17
|
|
|
20
18
|
export type IndexedFile = {
|
|
@@ -24,7 +22,7 @@ export type IndexedFile = {
|
|
|
24
22
|
sourceFile: string;
|
|
25
23
|
outputFile: string;
|
|
26
24
|
relativeFile: string;
|
|
27
|
-
|
|
25
|
+
role: ManifestModuleRole;
|
|
28
26
|
type: ManifestModuleFileType;
|
|
29
27
|
};
|
|
30
28
|
|
|
@@ -34,6 +32,12 @@ export type IndexedModule = ManifestModuleCore & {
|
|
|
34
32
|
files: Record<ManifestModuleFolderType, IndexedFile[]>;
|
|
35
33
|
};
|
|
36
34
|
|
|
35
|
+
const TypedObject: {
|
|
36
|
+
keys<T = unknown, K extends keyof T = keyof T>(o: T): K[];
|
|
37
|
+
fromEntries<K extends string | symbol, V>(items: ([K, V] | readonly [K, V])[]): Record<K, V>;
|
|
38
|
+
entries<K extends Record<symbol | string, unknown>>(record: K): [keyof K, K[keyof K]][];
|
|
39
|
+
} & ObjectConstructor = Object;
|
|
40
|
+
|
|
37
41
|
/**
|
|
38
42
|
* Manifest index
|
|
39
43
|
*/
|
|
@@ -78,7 +82,7 @@ export class ManifestIndex {
|
|
|
78
82
|
}
|
|
79
83
|
|
|
80
84
|
#moduleFiles(m: ManifestModule, files: ManifestModuleFile[]): IndexedFile[] {
|
|
81
|
-
return files.map(([f, type, ts,
|
|
85
|
+
return files.map(([f, type, ts, role = 'std']) => {
|
|
82
86
|
const isSource = type === 'ts' || type === 'js';
|
|
83
87
|
const sourceFile = path.resolve(this.#manifest.workspacePath, m.sourceFolder, f);
|
|
84
88
|
const js = isSource ? ManifestModuleUtil.sourceToOutputExt(f) : f;
|
|
@@ -89,7 +93,7 @@ export class ManifestIndex {
|
|
|
89
93
|
id = ManifestModuleUtil.sourceToBlankExt(id);
|
|
90
94
|
}
|
|
91
95
|
|
|
92
|
-
return { id, type, sourceFile, outputFile, import: modImport,
|
|
96
|
+
return { id, type, sourceFile, outputFile, import: modImport, role, relativeFile: f, module: m.name };
|
|
93
97
|
});
|
|
94
98
|
}
|
|
95
99
|
|
|
@@ -143,58 +147,27 @@ export class ManifestIndex {
|
|
|
143
147
|
|
|
144
148
|
/**
|
|
145
149
|
* Find files from the index
|
|
146
|
-
* @param
|
|
147
|
-
* @param filter The filter to determine if this is a valid support file
|
|
150
|
+
* @param config The configuration for controlling the find process
|
|
148
151
|
*/
|
|
149
152
|
find(config: FindConfig): IndexedFile[] {
|
|
150
|
-
const
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if (checkProfile) {
|
|
168
|
-
searchSpace = searchSpace.filter(fi => activeProfiles.has(fi.profile));
|
|
153
|
+
const searchSpace: IndexedFile[] = [];
|
|
154
|
+
for (const m of this.#modules) {
|
|
155
|
+
if (config.module?.(m) ?? true) {
|
|
156
|
+
for (const [folder, files] of TypedObject.entries(m.files)) {
|
|
157
|
+
if (config.folder?.(folder) ?? true) {
|
|
158
|
+
for (const file of files) {
|
|
159
|
+
if (
|
|
160
|
+
(config.file?.(file) ?? true) &&
|
|
161
|
+
(config.sourceOnly === false || file.type === 'ts')
|
|
162
|
+
) {
|
|
163
|
+
searchSpace.push(file);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
169
|
}
|
|
170
|
-
|
|
171
|
-
return searchSpace
|
|
172
|
-
.filter(({ type }) => type === 'ts')
|
|
173
|
-
.filter(({ sourceFile: source }) => filter?.(source) ?? true);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Find files from the index
|
|
178
|
-
* @param filter The filter to determine if this is a valid support file
|
|
179
|
-
*/
|
|
180
|
-
findSupport(config: Omit<FindConfig, 'folder'>): IndexedFile[] {
|
|
181
|
-
return this.find({ ...config, folders: ['support'] });
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Find files from the index
|
|
186
|
-
* @param filter The filter to determine if this is a valid support file
|
|
187
|
-
*/
|
|
188
|
-
findSrc(config: Omit<FindConfig, 'folder'> = {}): IndexedFile[] {
|
|
189
|
-
return this.find({ ...config, includeIndex: true, folders: ['src'] });
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Find files from the index
|
|
194
|
-
* @param filter The filter to determine if this is a valid support file
|
|
195
|
-
*/
|
|
196
|
-
findTest(config: Omit<FindConfig, 'folder'>): IndexedFile[] {
|
|
197
|
-
return this.find({ ...config, folders: ['test'] });
|
|
170
|
+
return searchSpace;
|
|
198
171
|
}
|
|
199
172
|
|
|
200
173
|
/**
|
package/src/module.ts
CHANGED
|
@@ -4,7 +4,8 @@ import { path } from './path';
|
|
|
4
4
|
import {
|
|
5
5
|
ManifestContext,
|
|
6
6
|
ManifestModule, ManifestModuleFile, ManifestModuleFileType,
|
|
7
|
-
ManifestModuleFolderType,
|
|
7
|
+
ManifestModuleFolderType,
|
|
8
|
+
ManifestModuleRole
|
|
8
9
|
} from './types';
|
|
9
10
|
import { ModuleDep, ModuleDependencyVisitor } from './dependencies';
|
|
10
11
|
import { PackageUtil } from './package';
|
|
@@ -56,9 +57,6 @@ export class ManifestModuleUtil {
|
|
|
56
57
|
const topFiles = new Set(mainSource ? [] : [...INDEX_FILES, 'package.json']);
|
|
57
58
|
const out: string[] = [];
|
|
58
59
|
|
|
59
|
-
if (!fs.stat(folder).catch(() => false)) {
|
|
60
|
-
return out;
|
|
61
|
-
}
|
|
62
60
|
const stack: [string, number][] = [[folder, 0]];
|
|
63
61
|
while (stack.length) {
|
|
64
62
|
const popped = stack.pop();
|
|
@@ -117,7 +115,7 @@ export class ManifestModuleUtil {
|
|
|
117
115
|
/**
|
|
118
116
|
* Get file type for a file name
|
|
119
117
|
*/
|
|
120
|
-
static
|
|
118
|
+
static getFileRole(moduleFile: string): ManifestModuleRole | undefined {
|
|
121
119
|
if (moduleFile.startsWith('support/transform')) {
|
|
122
120
|
return 'compile';
|
|
123
121
|
} else if (moduleFile.startsWith('support/test/') || moduleFile.startsWith('test/')) {
|
|
@@ -170,15 +168,15 @@ export class ManifestModuleUtil {
|
|
|
170
168
|
*/
|
|
171
169
|
static async transformFile(moduleFile: string, full: string): Promise<ManifestModuleFile> {
|
|
172
170
|
const res: ManifestModuleFile = [moduleFile, this.getFileType(moduleFile), this.#getNewest(await fs.stat(full))];
|
|
173
|
-
const
|
|
174
|
-
return
|
|
171
|
+
const role = this.getFileRole(moduleFile);
|
|
172
|
+
return role ? [...res, role] : res;
|
|
175
173
|
}
|
|
176
174
|
|
|
177
175
|
/**
|
|
178
176
|
* Visit a module and describe files, and metadata
|
|
179
177
|
*/
|
|
180
178
|
static async describeModule(ctx: ManifestContext, dep: ModuleDep): Promise<ManifestModule> {
|
|
181
|
-
const { main, mainSource, local, name, version, sourcePath,
|
|
179
|
+
const { main, mainSource, local, name, version, sourcePath, roleSet, prod, parentSet, internal } = dep;
|
|
182
180
|
|
|
183
181
|
const files: ManifestModule['files'] = {};
|
|
184
182
|
|
|
@@ -195,12 +193,12 @@ export class ManifestModuleUtil {
|
|
|
195
193
|
files.$root = files.$root?.filter(([file, type]) => type !== 'ts');
|
|
196
194
|
}
|
|
197
195
|
|
|
198
|
-
const
|
|
196
|
+
const roles = [...roleSet ?? []].sort();
|
|
199
197
|
const parents = [...parentSet].sort();
|
|
200
198
|
const outputFolder = `node_modules/${name}`;
|
|
201
199
|
const sourceFolder = sourcePath === ctx.workspacePath ? '' : sourcePath.replace(`${ctx.workspacePath}/`, '');
|
|
202
200
|
|
|
203
|
-
const res = { main, name, version, local, internal, sourceFolder, outputFolder,
|
|
201
|
+
const res = { main, name, version, local, internal, sourceFolder, outputFolder, roles, parents, prod, files };
|
|
204
202
|
return res;
|
|
205
203
|
}
|
|
206
204
|
|
package/src/package.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { readFileSync } from 'fs';
|
|
2
|
-
import fs from 'fs/promises';
|
|
3
1
|
import { createRequire } from 'module';
|
|
4
2
|
import { execSync } from 'child_process';
|
|
5
3
|
|
|
6
|
-
import { ManifestContext, Package,
|
|
4
|
+
import { ManifestContext, Package, PackageVisitor, PackageVisitReq, PackageWorkspaceEntry } from './types';
|
|
7
5
|
import { path } from './path';
|
|
6
|
+
import { ManifestFileUtil } from './file';
|
|
8
7
|
|
|
9
8
|
/**
|
|
10
9
|
* Utilities for querying, traversing and reading package.json files.
|
|
@@ -60,8 +59,8 @@ export class PackageUtil {
|
|
|
60
59
|
/**
|
|
61
60
|
* Build a package visit req
|
|
62
61
|
*/
|
|
63
|
-
static packageReq<T>(sourcePath: string,
|
|
64
|
-
return { pkg: this.readPackage(sourcePath), sourcePath,
|
|
62
|
+
static packageReq<T>(sourcePath: string, prod: boolean, topLevel?: boolean): PackageVisitReq<T> {
|
|
63
|
+
return { pkg: this.readPackage(sourcePath), sourcePath, prod, topLevel };
|
|
65
64
|
}
|
|
66
65
|
|
|
67
66
|
/**
|
|
@@ -71,22 +70,13 @@ export class PackageUtil {
|
|
|
71
70
|
const pkg = this.readPackage(modulePath);
|
|
72
71
|
const children: Record<string, PackageVisitReq<T>> = {};
|
|
73
72
|
const local = modulePath === rootPath && !modulePath.includes('node_modules');
|
|
74
|
-
for (const [deps,
|
|
75
|
-
[pkg.dependencies,
|
|
76
|
-
[pkg.
|
|
77
|
-
[pkg.optionalDependencies, 'opt'],
|
|
78
|
-
...(local ? [[pkg.devDependencies, 'dev'] as const] : []),
|
|
73
|
+
for (const [deps, prod] of [
|
|
74
|
+
[pkg.dependencies, true],
|
|
75
|
+
...(local ? [[pkg.devDependencies, false] as const] : []),
|
|
79
76
|
] as const) {
|
|
80
77
|
for (const [name, version] of Object.entries(deps ?? {})) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
children[`${name}#${version}`] = this.packageReq<T>(depPath, rel);
|
|
84
|
-
} catch (err) {
|
|
85
|
-
if (rel === 'opt' || (rel === 'peer' && !!pkg.peerDependenciesMeta?.[name].optional)) {
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
throw err;
|
|
89
|
-
}
|
|
78
|
+
const depPath = this.resolveVersionPath(modulePath, version) ?? this.resolvePackagePath(name);
|
|
79
|
+
children[`${name}#${version}`] = this.packageReq<T>(depPath, prod, false);
|
|
90
80
|
}
|
|
91
81
|
}
|
|
92
82
|
return Object.values(children).sort((a, b) => a.pkg.name.localeCompare(b.pkg.name));
|
|
@@ -99,10 +89,9 @@ export class PackageUtil {
|
|
|
99
89
|
if (forceRead) {
|
|
100
90
|
delete this.#cache[modulePath];
|
|
101
91
|
}
|
|
102
|
-
const res = this.#cache[modulePath] ??=
|
|
92
|
+
const res = this.#cache[modulePath] ??= ManifestFileUtil.readAsJsonSync(
|
|
103
93
|
modulePath.endsWith('.json') ? modulePath : path.resolve(modulePath, 'package.json'),
|
|
104
|
-
|
|
105
|
-
));
|
|
94
|
+
);
|
|
106
95
|
|
|
107
96
|
res.name ??= 'untitled'; // If a package.json (root-only) is missing a name, allows for npx execution
|
|
108
97
|
|
|
@@ -125,7 +114,7 @@ export class PackageUtil {
|
|
|
125
114
|
): Promise<Set<T>> {
|
|
126
115
|
|
|
127
116
|
const root = typeof rootOrPath === 'string' ?
|
|
128
|
-
this.packageReq<T>(rootOrPath,
|
|
117
|
+
this.packageReq<T>(rootOrPath, false, true) :
|
|
129
118
|
rootOrPath;
|
|
130
119
|
|
|
131
120
|
const seen = new Map<string, T>();
|
|
@@ -159,11 +148,10 @@ export class PackageUtil {
|
|
|
159
148
|
*/
|
|
160
149
|
static async resolveWorkspaces(ctx: ManifestContext, rootPath: string): Promise<PackageWorkspaceEntry[]> {
|
|
161
150
|
if (!this.#workspaces[rootPath]) {
|
|
162
|
-
await fs.mkdir(path.resolve(ctx.workspacePath, ctx.outputFolder), { recursive: true });
|
|
163
151
|
const cache = path.resolve(ctx.workspacePath, ctx.outputFolder, 'workspaces.json');
|
|
164
152
|
try {
|
|
165
|
-
return
|
|
166
|
-
} catch {
|
|
153
|
+
return await ManifestFileUtil.readAsJson(cache);
|
|
154
|
+
} catch (err) {
|
|
167
155
|
let out: PackageWorkspaceEntry[];
|
|
168
156
|
switch (ctx.packageManager) {
|
|
169
157
|
case 'npm': {
|
|
@@ -180,7 +168,7 @@ export class PackageUtil {
|
|
|
180
168
|
|
|
181
169
|
this.#workspaces[rootPath] = out;
|
|
182
170
|
|
|
183
|
-
await
|
|
171
|
+
await ManifestFileUtil.bufferedFileWrite(cache, out);
|
|
184
172
|
}
|
|
185
173
|
}
|
|
186
174
|
return this.#workspaces[rootPath];
|
package/src/path.ts
CHANGED
|
@@ -9,7 +9,7 @@ const toPosix = (file: string): string => file.replaceAll('\\', '/');
|
|
|
9
9
|
/**
|
|
10
10
|
* Converts a given file name by replace all slashes, with platform dependent path separators
|
|
11
11
|
*/
|
|
12
|
-
const toNative = (file: string): string => file.
|
|
12
|
+
const toNative = (file: string): string => file.replace(/[\\\/]+/g, sep);
|
|
13
13
|
|
|
14
14
|
const cwd = (): string => toPosix(process.cwd());
|
|
15
15
|
|
package/src/root-index.ts
CHANGED
|
@@ -27,15 +27,6 @@ class $RootIndex extends ManifestIndex {
|
|
|
27
27
|
return !!this.manifest.monoRepo && !this.manifest.mainFolder;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
/**
|
|
31
|
-
* Asynchronously load all source files from manifest
|
|
32
|
-
*/
|
|
33
|
-
async loadSource(): Promise<void> {
|
|
34
|
-
for (const { import: imp } of this.findSrc()) {
|
|
35
|
-
await import(imp);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
30
|
/**
|
|
40
31
|
* Get internal id from file name and optionally, class name
|
|
41
32
|
*/
|
package/src/types.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export type NodeModuleType = 'module' | 'commonjs';
|
|
2
|
+
|
|
1
3
|
export type ManifestModuleFileType = 'typings' | 'ts' | 'js' | 'json' | 'package-json' | 'unknown' | 'fixture' | 'md';
|
|
2
4
|
export type ManifestModuleFolderType =
|
|
3
5
|
'$root' | '$index' | '$package' |
|
|
@@ -5,10 +7,9 @@ export type ManifestModuleFolderType =
|
|
|
5
7
|
'test/fixtures' | 'support/fixtures' | 'support/resources' |
|
|
6
8
|
'$other' | '$transformer';
|
|
7
9
|
|
|
8
|
-
export type
|
|
9
|
-
export type PackageRel = 'dev' | 'prod' | 'peer' | 'opt' | 'root' | 'global';
|
|
10
|
+
export type ManifestModuleRole = 'std' | 'test' | 'doc' | 'compile' | 'build';
|
|
10
11
|
|
|
11
|
-
export type ManifestModuleFile = [string, ManifestModuleFileType, number] | [string, ManifestModuleFileType, number,
|
|
12
|
+
export type ManifestModuleFile = [string, ManifestModuleFileType, number] | [string, ManifestModuleFileType, number, ManifestModuleRole];
|
|
12
13
|
export type ManifestModuleCore = {
|
|
13
14
|
name: string;
|
|
14
15
|
main?: boolean;
|
|
@@ -16,7 +17,8 @@ export type ManifestModuleCore = {
|
|
|
16
17
|
version: string;
|
|
17
18
|
sourceFolder: string;
|
|
18
19
|
outputFolder: string;
|
|
19
|
-
|
|
20
|
+
prod: boolean;
|
|
21
|
+
roles: ManifestModuleRole[];
|
|
20
22
|
parents: string[];
|
|
21
23
|
internal?: boolean;
|
|
22
24
|
};
|
|
@@ -33,11 +35,12 @@ export type ManifestContext = {
|
|
|
33
35
|
toolFolder: string;
|
|
34
36
|
compilerFolder: string;
|
|
35
37
|
monoRepo?: boolean;
|
|
36
|
-
moduleType:
|
|
38
|
+
moduleType: NodeModuleType;
|
|
37
39
|
packageManager: 'yarn' | 'npm';
|
|
38
40
|
frameworkVersion: string;
|
|
39
|
-
description
|
|
41
|
+
description?: string;
|
|
40
42
|
version: string;
|
|
43
|
+
compilerUrl: string;
|
|
41
44
|
};
|
|
42
45
|
|
|
43
46
|
export type ManifestRoot = ManifestContext & {
|
|
@@ -47,7 +50,7 @@ export type ManifestRoot = ManifestContext & {
|
|
|
47
50
|
|
|
48
51
|
export type Package = {
|
|
49
52
|
name: string;
|
|
50
|
-
type?:
|
|
53
|
+
type?: NodeModuleType;
|
|
51
54
|
version: string;
|
|
52
55
|
description?: string;
|
|
53
56
|
license?: string;
|
|
@@ -75,7 +78,7 @@ export type Package = {
|
|
|
75
78
|
travetto?: {
|
|
76
79
|
isolated?: boolean;
|
|
77
80
|
displayName?: string;
|
|
78
|
-
|
|
81
|
+
roles?: ManifestModuleRole[];
|
|
79
82
|
globalModules?: string[];
|
|
80
83
|
mainSource?: string[];
|
|
81
84
|
docOutput?: string[];
|
|
@@ -83,6 +86,9 @@ export type Package = {
|
|
|
83
86
|
docBaseUrl?: string;
|
|
84
87
|
docOutputs?: string[];
|
|
85
88
|
outputFolder?: string;
|
|
89
|
+
toolFolder?: string;
|
|
90
|
+
compilerFolder?: string;
|
|
91
|
+
compilerUrl?: string;
|
|
86
92
|
};
|
|
87
93
|
workspaces?: string[];
|
|
88
94
|
private?: boolean;
|
|
@@ -91,7 +97,7 @@ export type Package = {
|
|
|
91
97
|
|
|
92
98
|
type OrProm<T> = T | Promise<T>;
|
|
93
99
|
|
|
94
|
-
export type PackageVisitReq<T> = { pkg: Package,
|
|
100
|
+
export type PackageVisitReq<T> = { pkg: Package, prod: boolean, sourcePath: string, parent?: T, topLevel?: boolean };
|
|
95
101
|
export type PackageVisitor<T> = {
|
|
96
102
|
init?(req: PackageVisitReq<T>): OrProm<undefined | void | PackageVisitReq<T>[]>;
|
|
97
103
|
valid?(req: PackageVisitReq<T>): boolean;
|
package/src/util.ts
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
import { readFileSync } from 'fs';
|
|
2
|
-
import fs from 'fs/promises';
|
|
3
|
-
import os from 'os';
|
|
4
|
-
|
|
5
1
|
import { path } from './path';
|
|
6
2
|
import { ManifestContext, ManifestRoot } from './types';
|
|
7
3
|
import { ManifestModuleUtil } from './module';
|
|
4
|
+
import { ManifestFileUtil } from './file';
|
|
8
5
|
|
|
9
6
|
const MANIFEST_FILE = 'manifest.json';
|
|
10
7
|
|
|
@@ -12,20 +9,6 @@ const MANIFEST_FILE = 'manifest.json';
|
|
|
12
9
|
* Manifest utils
|
|
13
10
|
*/
|
|
14
11
|
export class ManifestUtil {
|
|
15
|
-
/**
|
|
16
|
-
* Write file and copy over when ready
|
|
17
|
-
*/
|
|
18
|
-
static async writeFileWithBuffer(file: string, content: string): Promise<string> {
|
|
19
|
-
const ext = path.extname(file);
|
|
20
|
-
const tempName = `${path.basename(file, ext)}.${process.ppid}.${process.pid}.${Date.now()}.${Math.random()}${ext}`;
|
|
21
|
-
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
22
|
-
const temp = path.resolve(os.tmpdir(), tempName);
|
|
23
|
-
await fs.writeFile(temp, content, 'utf8');
|
|
24
|
-
await fs.copyFile(temp, file);
|
|
25
|
-
fs.unlink(temp);
|
|
26
|
-
return file;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
12
|
/**
|
|
30
13
|
* Build a manifest context
|
|
31
14
|
* @param folder
|
|
@@ -55,7 +38,7 @@ export class ManifestUtil {
|
|
|
55
38
|
// If in prod mode, only include std modules
|
|
56
39
|
modules: Object.fromEntries(
|
|
57
40
|
Object.values(manifest.modules)
|
|
58
|
-
.filter(x => x.
|
|
41
|
+
.filter(x => x.prod)
|
|
59
42
|
.map(m => [m.name, m])
|
|
60
43
|
),
|
|
61
44
|
// Mark output folder/workspace path as portable
|
|
@@ -75,7 +58,7 @@ export class ManifestUtil {
|
|
|
75
58
|
if (!file.endsWith('.json')) {
|
|
76
59
|
file = path.resolve(file, MANIFEST_FILE);
|
|
77
60
|
}
|
|
78
|
-
const manifest: ManifestRoot =
|
|
61
|
+
const manifest: ManifestRoot = ManifestFileUtil.readAsJsonSync(file);
|
|
79
62
|
// Support packaged environments, by allowing empty outputFolder
|
|
80
63
|
if (!manifest.outputFolder) {
|
|
81
64
|
manifest.outputFolder = path.cwd();
|
|
@@ -88,7 +71,7 @@ export class ManifestUtil {
|
|
|
88
71
|
* Write manifest for a given context, return location
|
|
89
72
|
*/
|
|
90
73
|
static writeManifest(ctx: ManifestContext, manifest: ManifestRoot): Promise<string> {
|
|
91
|
-
return
|
|
74
|
+
return ManifestFileUtil.bufferedFileWrite(
|
|
92
75
|
path.resolve(ctx.workspacePath, ctx.outputFolder, 'node_modules', ctx.mainModule, MANIFEST_FILE),
|
|
93
76
|
JSON.stringify(manifest)
|
|
94
77
|
);
|
|
@@ -102,8 +85,7 @@ export class ManifestUtil {
|
|
|
102
85
|
location = path.resolve(location, MANIFEST_FILE);
|
|
103
86
|
}
|
|
104
87
|
|
|
105
|
-
await
|
|
106
|
-
await fs.writeFile(location, JSON.stringify(manifest), 'utf8');
|
|
88
|
+
await ManifestFileUtil.bufferedFileWrite(location, JSON.stringify(manifest));
|
|
107
89
|
|
|
108
90
|
return location;
|
|
109
91
|
}
|
package/src/watch.ts
DELETED
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
import { watch, Stats } from 'fs';
|
|
2
|
-
import fs from 'fs/promises';
|
|
3
|
-
|
|
4
|
-
import { path } from './path';
|
|
5
|
-
|
|
6
|
-
async function getWatcher(): Promise<typeof import('@parcel/watcher')> {
|
|
7
|
-
try {
|
|
8
|
-
return await import('@parcel/watcher');
|
|
9
|
-
} catch (err) {
|
|
10
|
-
console.error('@parcel/watcher must be installed to use watching functionality');
|
|
11
|
-
throw err;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export type WatchEvent = { action: 'create' | 'update' | 'delete', file: string, folder: string };
|
|
16
|
-
|
|
17
|
-
export type WatchFolder = {
|
|
18
|
-
/**
|
|
19
|
-
* Source folder
|
|
20
|
-
*/
|
|
21
|
-
src: string;
|
|
22
|
-
/**
|
|
23
|
-
* Target folder name, useful for deconstructing
|
|
24
|
-
*/
|
|
25
|
-
target?: string;
|
|
26
|
-
/**
|
|
27
|
-
* Filter events
|
|
28
|
-
*/
|
|
29
|
-
filter?: (ev: WatchEvent) => boolean;
|
|
30
|
-
/**
|
|
31
|
-
* Only look at immediate folder
|
|
32
|
-
*/
|
|
33
|
-
immediate?: boolean;
|
|
34
|
-
/**
|
|
35
|
-
* List of top level folders to ignore
|
|
36
|
-
*/
|
|
37
|
-
ignore?: string[];
|
|
38
|
-
/**
|
|
39
|
-
* If watching a folder that doesn't exist, should it be created?
|
|
40
|
-
*/
|
|
41
|
-
createMissing?: boolean;
|
|
42
|
-
/**
|
|
43
|
-
* Include files that start with '.'
|
|
44
|
-
*/
|
|
45
|
-
includeHidden?: boolean;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
export type WatchStream = AsyncIterable<WatchEvent> & { close: () => Promise<void>, add: (item: WatchEvent | WatchEvent[]) => void };
|
|
49
|
-
|
|
50
|
-
const DEDUPE_THRESHOLD = 50;
|
|
51
|
-
|
|
52
|
-
class Queue<X> implements AsyncIterator<X>, AsyncIterable<X> {
|
|
53
|
-
#queue: X[] = [];
|
|
54
|
-
#done = false;
|
|
55
|
-
#ready: Promise<void>;
|
|
56
|
-
#fire: (() => void);
|
|
57
|
-
#onClose: (() => (void | Promise<void>))[] = [];
|
|
58
|
-
#recentKeys = new Map<string, number>();
|
|
59
|
-
|
|
60
|
-
constructor() {
|
|
61
|
-
this.#ready = new Promise(r => this.#fire = r);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Allow for iteration
|
|
65
|
-
[Symbol.asyncIterator](): AsyncIterator<X> { return this; }
|
|
66
|
-
|
|
67
|
-
async next(): Promise<IteratorResult<X>> {
|
|
68
|
-
while (!this.#done && !this.#queue.length) {
|
|
69
|
-
this.#recentKeys = new Map([...this.#recentKeys.entries()] // Cull before waiting
|
|
70
|
-
.filter(([, time]) => (Date.now() - time) < DEDUPE_THRESHOLD));
|
|
71
|
-
await this.#ready;
|
|
72
|
-
this.#ready = new Promise(r => this.#fire = r);
|
|
73
|
-
}
|
|
74
|
-
return { value: this.#queue.shift()!, done: this.#done };
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
add(item: X | X[]): void {
|
|
78
|
-
const now = Date.now();
|
|
79
|
-
for (const value of Array.isArray(item) ? item : [item]) {
|
|
80
|
-
const key = JSON.stringify(value);
|
|
81
|
-
if ((now - (this.#recentKeys.get(key) ?? 0)) > DEDUPE_THRESHOLD) {
|
|
82
|
-
this.#queue.push(value);
|
|
83
|
-
this.#recentKeys.set(key, now);
|
|
84
|
-
this.#fire();
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
registerOnClose(handler: () => (void | Promise<void>)): void {
|
|
90
|
-
this.#onClose.push(handler);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
async close(): Promise<void> {
|
|
94
|
-
this.#done = true;
|
|
95
|
-
this.#fire();
|
|
96
|
-
await Promise.all(this.#onClose.map(x => x()));
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Watch immediate files for a given folder
|
|
102
|
-
*/
|
|
103
|
-
async function watchFolderImmediate(queue: Queue<WatchEvent>, options: WatchFolder): Promise<void> {
|
|
104
|
-
const watchPath = path.resolve(options.src);
|
|
105
|
-
const watcher = watch(watchPath, { persistent: true, encoding: 'utf8' });
|
|
106
|
-
const lastStats: Record<string, Stats | undefined> = {};
|
|
107
|
-
const invalidFilter = (el: string): boolean =>
|
|
108
|
-
(el === '.' || el === '..' || (!options.includeHidden && el.startsWith('.')) || !!options.ignore?.includes(el));
|
|
109
|
-
|
|
110
|
-
for (const el of await fs.readdir(watchPath)) {
|
|
111
|
-
if (invalidFilter(el)) {
|
|
112
|
-
continue;
|
|
113
|
-
}
|
|
114
|
-
const file = path.resolve(watchPath, el);
|
|
115
|
-
lastStats[file] = await fs.stat(file);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const target = options.target ?? options.src;
|
|
119
|
-
|
|
120
|
-
watcher.on('change', async (type: string, file: string): Promise<void> => {
|
|
121
|
-
if (invalidFilter(file)) {
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
file = path.resolve(watchPath, file);
|
|
126
|
-
|
|
127
|
-
const stat = await fs.stat(file).catch(() => undefined);
|
|
128
|
-
const prevStat = lastStats[file];
|
|
129
|
-
lastStats[file] = stat;
|
|
130
|
-
|
|
131
|
-
if (prevStat?.mtimeMs === stat?.mtimeMs) {
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
let ev: WatchEvent;
|
|
135
|
-
if (prevStat && !stat) {
|
|
136
|
-
ev = { action: 'delete', file, folder: target };
|
|
137
|
-
} else if (!prevStat && stat) {
|
|
138
|
-
ev = { action: 'create', file, folder: target };
|
|
139
|
-
} else {
|
|
140
|
-
ev = { action: 'update', file, folder: target };
|
|
141
|
-
}
|
|
142
|
-
if (!options.filter || options.filter(ev)) {
|
|
143
|
-
queue.add(ev);
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
queue.registerOnClose(() => watcher.close());
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Watch recursive files for a given folder
|
|
152
|
-
*/
|
|
153
|
-
async function watchFolderRecursive(queue: Queue<WatchEvent>, options: WatchFolder): Promise<void> {
|
|
154
|
-
const lib = await getWatcher();
|
|
155
|
-
const target = options.target ?? options.src;
|
|
156
|
-
|
|
157
|
-
if (await fs.stat(options.src).then(() => true, () => options.createMissing)) {
|
|
158
|
-
await fs.mkdir(options.src, { recursive: true });
|
|
159
|
-
const ignore = (await fs.readdir(options.src)).filter(x => x.startsWith('.') && x.length > 2);
|
|
160
|
-
const cleanup = await lib.subscribe(options.src, async (err, events) => {
|
|
161
|
-
if (err) {
|
|
162
|
-
process.send?.({ type: 'log', message: `Watch error: ${err}` });
|
|
163
|
-
}
|
|
164
|
-
for (const ev of events) {
|
|
165
|
-
const finalEv = { action: ev.type, file: path.toPosix(ev.path), folder: target };
|
|
166
|
-
if (ev.type !== 'delete') {
|
|
167
|
-
const stats = await fs.stat(finalEv.file);
|
|
168
|
-
if ((stats.ctimeMs - Date.now()) < DEDUPE_THRESHOLD) {
|
|
169
|
-
ev.type = 'create'; // Force create on newly stated files
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
if (ev.type === 'delete' && finalEv.file === options.src) {
|
|
174
|
-
return queue.close();
|
|
175
|
-
}
|
|
176
|
-
const isHidden = !options.includeHidden && finalEv.file.replace(target, '').includes('/.');
|
|
177
|
-
const matches = !isHidden && (!options.filter || options.filter(finalEv));
|
|
178
|
-
if (matches) {
|
|
179
|
-
queue.add(finalEv);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}, { ignore: [...ignore, ...options.ignore ?? []] });
|
|
183
|
-
queue.registerOnClose(() => cleanup.unsubscribe());
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Watch a series of folders
|
|
189
|
-
* @param folders
|
|
190
|
-
* @param onEvent
|
|
191
|
-
* @param options
|
|
192
|
-
*/
|
|
193
|
-
export function watchFolders(
|
|
194
|
-
folders: string[] | WatchFolder[],
|
|
195
|
-
config: Omit<WatchFolder, 'src' | 'target'> = {}
|
|
196
|
-
): WatchStream {
|
|
197
|
-
const queue = new Queue<WatchEvent>();
|
|
198
|
-
for (const folder of folders) {
|
|
199
|
-
if (typeof folder === 'string') {
|
|
200
|
-
watchFolderRecursive(queue, { ...config, src: folder });
|
|
201
|
-
} else if (!folder.immediate) {
|
|
202
|
-
watchFolderRecursive(queue, { ...config, ...folder });
|
|
203
|
-
} else {
|
|
204
|
-
watchFolderImmediate(queue, { ...config, ...folder });
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
return queue;
|
|
208
|
-
}
|