@travetto/manifest 3.0.0-rc.5 → 3.0.0-rc.7
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/__index__.ts +4 -3
- package/bin/context.d.ts +10 -0
- package/bin/context.js +94 -0
- package/package.json +11 -2
- package/src/delta.ts +41 -51
- package/src/dependencies.ts +1 -1
- package/src/manifest-index.ts +37 -27
- package/src/module.ts +31 -10
- package/src/package.ts +18 -8
- package/src/root-index.ts +31 -49
- package/src/types.ts +6 -15
- package/src/util.ts +81 -0
- package/src/watch.ts +48 -0
- package/src/manifest.ts +0 -110
package/__index__.ts
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
export * from './src/path';
|
|
4
4
|
export * from './src/module';
|
|
5
|
+
export * from './src/delta';
|
|
5
6
|
export * from './src/manifest-index';
|
|
6
7
|
export * from './src/root-index';
|
|
7
|
-
export * from './src/delta';
|
|
8
8
|
export * from './src/package';
|
|
9
|
-
export * from './src/
|
|
10
|
-
export * from './src/types';
|
|
9
|
+
export * from './src/util';
|
|
10
|
+
export * from './src/types';
|
|
11
|
+
export * from './src/watch';
|
package/bin/context.d.ts
ADDED
package/bin/context.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import('../src/types').Package} Pkg
|
|
5
|
+
* @typedef {import('../src/types').ManifestContext} ManifestContext
|
|
6
|
+
*/
|
|
7
|
+
import fs from 'fs/promises';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { createRequire } from 'module';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Returns the package.json
|
|
13
|
+
* @param {string} inputFolder
|
|
14
|
+
* @returns {Promise<Pkg>}
|
|
15
|
+
*/
|
|
16
|
+
async function $getPkg(inputFolder) {
|
|
17
|
+
if (!inputFolder.endsWith('.json')) {
|
|
18
|
+
inputFolder = path.resolve(inputFolder, 'package.json');
|
|
19
|
+
}
|
|
20
|
+
return JSON.parse(await fs.readFile(inputFolder, 'utf8'));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const WS_ROOT = {};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get workspace root
|
|
27
|
+
* @return {Promise<string>}
|
|
28
|
+
*/
|
|
29
|
+
async function $getWorkspaceRoot(base = process.cwd()) {
|
|
30
|
+
if (base in WS_ROOT) {
|
|
31
|
+
return WS_ROOT[base];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let folder = base;
|
|
35
|
+
let prevFolder = '';
|
|
36
|
+
while (folder !== prevFolder) {
|
|
37
|
+
try {
|
|
38
|
+
const pkg = await $getPkg(folder);
|
|
39
|
+
if (!!pkg.workspaces || !!pkg.travetto?.isolated) {
|
|
40
|
+
return (WS_ROOT[base] = folder);
|
|
41
|
+
}
|
|
42
|
+
} catch { }
|
|
43
|
+
if (await fs.stat(path.resolve(folder, '.git')).catch(() => { })) {
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
prevFolder = folder;
|
|
47
|
+
folder = path.dirname(folder);
|
|
48
|
+
}
|
|
49
|
+
return WS_ROOT[base] = base;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Gets build context
|
|
55
|
+
* @param {string} [folder]
|
|
56
|
+
* @return {Promise<ManifestContext>}
|
|
57
|
+
*/
|
|
58
|
+
export async function getManifestContext(folder) {
|
|
59
|
+
const workspacePath = path.resolve(await $getWorkspaceRoot(folder));
|
|
60
|
+
|
|
61
|
+
// If manifest specified via env var, and is a package name
|
|
62
|
+
if (!folder && process.env.TRV_MODULE) {
|
|
63
|
+
const req = createRequire(`${workspacePath}/node_modules`);
|
|
64
|
+
try {
|
|
65
|
+
folder = path.dirname(req.resolve(`${process.env.TRV_MODULE}/package.json`));
|
|
66
|
+
} catch {
|
|
67
|
+
const workspacePkg = JSON.parse(await fs.readFile(path.resolve(workspacePath, 'package.json'), 'utf8'));
|
|
68
|
+
if (workspacePkg.name === process.env.TRV_MODULE) {
|
|
69
|
+
folder = workspacePath;
|
|
70
|
+
} else {
|
|
71
|
+
throw new Error(`Unable to resolve location for ${folder}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const mainPath = path.resolve(folder ?? '.');
|
|
77
|
+
const { name: mainModule, workspaces, travetto } = (await $getPkg(mainPath));
|
|
78
|
+
const monoRepo = workspacePath !== mainPath || !!workspaces;
|
|
79
|
+
const outputFolder = travetto?.outputFolder ?? '.trv_output';
|
|
80
|
+
|
|
81
|
+
const moduleType = (await $getPkg(workspacePath)).type ?? 'commonjs';
|
|
82
|
+
const mainFolder = mainPath === workspacePath ? '' : mainPath.replace(`${workspacePath}/`, '');
|
|
83
|
+
|
|
84
|
+
const res = {
|
|
85
|
+
moduleType,
|
|
86
|
+
mainModule,
|
|
87
|
+
mainFolder,
|
|
88
|
+
workspacePath,
|
|
89
|
+
monoRepo,
|
|
90
|
+
outputFolder,
|
|
91
|
+
compilerFolder: '.trv_compiler'
|
|
92
|
+
};
|
|
93
|
+
return res;
|
|
94
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/manifest",
|
|
3
|
-
"version": "3.0.0-rc.
|
|
3
|
+
"version": "3.0.0-rc.7",
|
|
4
4
|
"description": "Manifest support",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"path",
|
|
@@ -15,8 +15,10 @@
|
|
|
15
15
|
"email": "travetto.framework@gmail.com",
|
|
16
16
|
"name": "Travetto Framework"
|
|
17
17
|
},
|
|
18
|
+
"type": "module",
|
|
18
19
|
"files": [
|
|
19
20
|
"__index__.ts",
|
|
21
|
+
"bin",
|
|
20
22
|
"src",
|
|
21
23
|
"support"
|
|
22
24
|
],
|
|
@@ -28,7 +30,14 @@
|
|
|
28
30
|
"url": "https://github.com/travetto/travetto.git",
|
|
29
31
|
"directory": "module/manifest"
|
|
30
32
|
},
|
|
31
|
-
"
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"@parcel/watcher": "^2.1.0"
|
|
35
|
+
},
|
|
36
|
+
"peerDependenciesMeta": {
|
|
37
|
+
"@parcel/watcher": {
|
|
38
|
+
"optional": true
|
|
39
|
+
}
|
|
40
|
+
},
|
|
32
41
|
"travetto": {
|
|
33
42
|
"displayName": "Manifest",
|
|
34
43
|
"docBaseUrl": "https://github.com/travetto/travetto/tree/main"
|
package/src/delta.ts
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
|
-
import { type Stats } from 'fs';
|
|
2
1
|
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
3
4
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
ManifestRoot
|
|
5
|
+
ManifestContext, ManifestModule, ManifestModuleCore, ManifestModuleFile,
|
|
6
|
+
ManifestModuleFileType, ManifestModuleFolderType, ManifestRoot
|
|
7
7
|
} from './types';
|
|
8
8
|
|
|
9
|
+
import { ManifestModuleUtil } from './module';
|
|
10
|
+
|
|
11
|
+
type DeltaEventType = 'added' | 'changed' | 'removed' | 'missing' | 'dirty';
|
|
12
|
+
type DeltaModule = ManifestModuleCore & { files: Record<string, ManifestModuleFile> };
|
|
13
|
+
export type DeltaEvent = { file: string, type: DeltaEventType, module: string };
|
|
14
|
+
|
|
9
15
|
const VALID_SOURCE_FOLDERS = new Set<ManifestModuleFolderType>(['bin', 'src', 'test', 'support', '$index', '$package', 'doc']);
|
|
16
|
+
const VALID_SOURCE_TYPE = new Set<ManifestModuleFileType>(['js', 'ts', 'package-json']);
|
|
10
17
|
|
|
11
18
|
/**
|
|
12
19
|
* Produce delta for the manifest
|
|
@@ -20,50 +27,37 @@ export class ManifestDeltaUtil {
|
|
|
20
27
|
/**
|
|
21
28
|
* Produce delta between two manifest modules, relative to an output folder
|
|
22
29
|
*/
|
|
23
|
-
static async #deltaModules(
|
|
24
|
-
const out:
|
|
25
|
-
const getStat = (f: string): Promise<Stats | void> =>
|
|
26
|
-
fs.stat(`${outputFolder}/${left.output}/${f.replace(/[.]ts$/, '.js')}`).catch(() => { });
|
|
30
|
+
static async #deltaModules(ctx: ManifestContext, outputFolder: string, left: DeltaModule): Promise<DeltaEvent[]> {
|
|
31
|
+
const out: DeltaEvent[] = [];
|
|
27
32
|
|
|
28
|
-
const add = (file: string, type:
|
|
33
|
+
const add = (file: string, type: DeltaEvent['type']): unknown =>
|
|
29
34
|
out.push({ file, module: left.name, type });
|
|
30
35
|
|
|
36
|
+
const root = `${ctx.workspacePath}/${ctx.outputFolder}/${left.outputFolder}`;
|
|
37
|
+
const right = new Set(
|
|
38
|
+
(await ManifestModuleUtil.scanFolder(root, left.main))
|
|
39
|
+
.filter(x => (x.endsWith('.ts') || x.endsWith('.js') || x.endsWith('package.json')))
|
|
40
|
+
.map(x => x.replace(`${root}/`, '').replace(/[.][tj]s$/, ''))
|
|
41
|
+
);
|
|
42
|
+
|
|
31
43
|
for (const el of Object.keys(left.files)) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
44
|
+
const output = `${outputFolder}/${left.outputFolder}/${el.replace(/[.]ts$/, '.js')}`;
|
|
45
|
+
const [, , leftTs] = left.files[el];
|
|
46
|
+
const stat = await fs.stat(output).catch(() => { });
|
|
47
|
+
right.delete(el.replace(/[.][tj]s$/, ''));
|
|
48
|
+
|
|
49
|
+
if (!stat) {
|
|
39
50
|
add(el, 'added');
|
|
40
51
|
} else {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const stat = await getStat(el);
|
|
45
|
-
if (!stat) {
|
|
46
|
-
add(el, 'missing');
|
|
47
|
-
} else if (leftTs > this.#getNewest(stat!)) {
|
|
48
|
-
add(el, 'changed');
|
|
49
|
-
}
|
|
50
|
-
} else {
|
|
51
|
-
const stat = await getStat(el);
|
|
52
|
-
if (!stat) {
|
|
53
|
-
add(el, 'missing');
|
|
54
|
-
} else if (this.#getNewest(stat!) < leftTs) {
|
|
55
|
-
add(el, 'dirty');
|
|
56
|
-
}
|
|
52
|
+
const rightTs = this.#getNewest(stat);
|
|
53
|
+
if (leftTs > rightTs) {
|
|
54
|
+
add(el, 'changed');
|
|
57
55
|
}
|
|
58
56
|
}
|
|
59
57
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (stat) {
|
|
64
|
-
add(el, 'removed');
|
|
65
|
-
}
|
|
66
|
-
}
|
|
58
|
+
// Deleted
|
|
59
|
+
for (const el of right) {
|
|
60
|
+
add(el, 'removed');
|
|
67
61
|
}
|
|
68
62
|
return out;
|
|
69
63
|
}
|
|
@@ -81,7 +75,7 @@ export class ManifestDeltaUtil {
|
|
|
81
75
|
continue;
|
|
82
76
|
}
|
|
83
77
|
for (const [name, type, date] of m.files?.[key] ?? []) {
|
|
84
|
-
if (type
|
|
78
|
+
if (VALID_SOURCE_TYPE.has(type)) {
|
|
85
79
|
out[name] = [name, type, date];
|
|
86
80
|
}
|
|
87
81
|
}
|
|
@@ -90,23 +84,19 @@ export class ManifestDeltaUtil {
|
|
|
90
84
|
}
|
|
91
85
|
|
|
92
86
|
/**
|
|
93
|
-
* Produce delta between
|
|
87
|
+
* Produce delta between manifest root and the output folder
|
|
94
88
|
*/
|
|
95
|
-
static async produceDelta(
|
|
89
|
+
static async produceDelta(ctx: ManifestContext, manifest: ManifestRoot): Promise<DeltaEvent[]> {
|
|
96
90
|
const deltaLeft = Object.fromEntries(
|
|
97
|
-
Object.values(
|
|
98
|
-
.map(m => [m.name, { ...m, files: this.#flattenModuleFiles(m) }])
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
const deltaRight = Object.fromEntries(
|
|
102
|
-
Object.values(right.modules)
|
|
91
|
+
Object.values(manifest.modules)
|
|
103
92
|
.map(m => [m.name, { ...m, files: this.#flattenModuleFiles(m) }])
|
|
104
93
|
);
|
|
105
94
|
|
|
106
|
-
const out:
|
|
95
|
+
const out: DeltaEvent[] = [];
|
|
96
|
+
const outputFolder = path.resolve(ctx.workspacePath, ctx.outputFolder);
|
|
107
97
|
|
|
108
|
-
for (const
|
|
109
|
-
out
|
|
98
|
+
for (const lMod of Object.values(deltaLeft)) {
|
|
99
|
+
out.push(...await this.#deltaModules(manifest, outputFolder, lMod));
|
|
110
100
|
}
|
|
111
101
|
|
|
112
102
|
return out;
|
package/src/dependencies.ts
CHANGED
|
@@ -49,7 +49,7 @@ export class ModuleDependencyVisitor implements PackageVisitor<ModuleDep> {
|
|
|
49
49
|
async init(req: PackageVisitReq<ModuleDep>): Promise<PackageVisitReq<ModuleDep>[]> {
|
|
50
50
|
const pkg = PackageUtil.readPackage(req.sourcePath);
|
|
51
51
|
const workspacePkg = PackageUtil.readPackage(this.ctx.workspacePath);
|
|
52
|
-
const workspaceModules = pkg.workspaces?.length ? (await PackageUtil.resolveWorkspaces(req.sourcePath)) : [];
|
|
52
|
+
const workspaceModules = pkg.workspaces?.length ? (await PackageUtil.resolveWorkspaces(this.ctx, req.sourcePath)) : [];
|
|
53
53
|
|
|
54
54
|
this.#mainPatterns = ModuleDependencyVisitor.getMainPatterns(pkg.name, [
|
|
55
55
|
...pkg.travetto?.mainSource ?? [],
|
package/src/manifest-index.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
|
|
3
1
|
import { path } from './path';
|
|
4
2
|
|
|
5
3
|
import {
|
|
@@ -7,6 +5,8 @@ import {
|
|
|
7
5
|
ManifestModuleFileType, ManifestModuleFolderType, ManifestProfile, ManifestRoot
|
|
8
6
|
} from './types';
|
|
9
7
|
|
|
8
|
+
import { ManifestUtil } from './util';
|
|
9
|
+
|
|
10
10
|
type ScanTest = ((full: string) => boolean) | { test: (full: string) => boolean };
|
|
11
11
|
export type FindConfig = {
|
|
12
12
|
folders?: ManifestModuleFolderType[];
|
|
@@ -20,16 +20,17 @@ export type IndexedFile = {
|
|
|
20
20
|
id: string;
|
|
21
21
|
import: string;
|
|
22
22
|
module: string;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
sourceFile: string;
|
|
24
|
+
outputFile: string;
|
|
25
|
+
relativeFile: string;
|
|
26
26
|
profile: ManifestProfile;
|
|
27
27
|
type: ManifestModuleFileType;
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
export type IndexedModule = ManifestModuleCore & {
|
|
31
|
+
sourcePath: string;
|
|
32
|
+
outputPath: string;
|
|
31
33
|
files: Record<ManifestModuleFolderType, IndexedFile[]>;
|
|
32
|
-
workspaceRelative: string;
|
|
33
34
|
};
|
|
34
35
|
|
|
35
36
|
/**
|
|
@@ -42,50 +43,51 @@ export class ManifestIndex {
|
|
|
42
43
|
#modules: IndexedModule[];
|
|
43
44
|
#modulesByName: Record<string, IndexedModule> = {};
|
|
44
45
|
#modulesByFolder: Record<string, IndexedModule> = {};
|
|
45
|
-
#
|
|
46
|
+
#outputRoot: string;
|
|
46
47
|
#outputToEntry = new Map<string, IndexedFile>();
|
|
47
48
|
#sourceToEntry = new Map<string, IndexedFile>();
|
|
48
49
|
#importToEntry = new Map<string, IndexedFile>();
|
|
49
50
|
|
|
50
|
-
constructor(
|
|
51
|
-
this.init(
|
|
51
|
+
constructor(manifest: string) {
|
|
52
|
+
this.init(manifest);
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
#resolveOutput(...parts: string[]): string {
|
|
55
|
-
return path.resolve(this.#
|
|
56
|
+
return path.resolve(this.#outputRoot, ...parts);
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
get manifest(): ManifestRoot {
|
|
59
60
|
return this.#manifest;
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
get
|
|
63
|
-
return this.#
|
|
63
|
+
get outputRoot(): string {
|
|
64
|
+
return this.#outputRoot;
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
get manifestFile(): string {
|
|
67
68
|
return this.#manifestFile;
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
init(
|
|
71
|
-
|
|
72
|
-
this.#
|
|
73
|
-
this.#
|
|
71
|
+
init(manifestInput: string): void {
|
|
72
|
+
const { manifest, file } = ManifestUtil.readManifestSync(manifestInput);
|
|
73
|
+
this.#manifest = manifest;
|
|
74
|
+
this.#manifestFile = file;
|
|
75
|
+
this.#outputRoot = path.resolve(this.#manifest.workspacePath, this.#manifest.outputFolder);
|
|
74
76
|
this.#index();
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
#moduleFiles(m: ManifestModule, files: ManifestModuleFile[]): IndexedFile[] {
|
|
78
80
|
return files.map(([f, type, ts, profile = 'std']) => {
|
|
79
|
-
const
|
|
81
|
+
const sourceFile = path.resolve(this.#manifest.workspacePath, m.sourceFolder, f);
|
|
80
82
|
const js = (type === 'ts' ? f.replace(/[.]ts$/, '.js') : f);
|
|
81
|
-
const
|
|
83
|
+
const outputFile = this.#resolveOutput(m.outputFolder, js);
|
|
82
84
|
const modImport = `${m.name}/${js}`;
|
|
83
85
|
let id = modImport.replace(`${m.name}/`, _ => _.replace(/[/]$/, ':'));
|
|
84
86
|
if (type === 'ts' || type === 'js') {
|
|
85
87
|
id = id.replace(/[.]js$/, '');
|
|
86
88
|
}
|
|
87
89
|
|
|
88
|
-
return { id, type,
|
|
90
|
+
return { id, type, sourceFile, outputFile, import: modImport, profile, relativeFile: f, module: m.name };
|
|
89
91
|
});
|
|
90
92
|
}
|
|
91
93
|
|
|
@@ -100,8 +102,8 @@ export class ManifestIndex {
|
|
|
100
102
|
this.#modules = Object.values(this.manifest.modules)
|
|
101
103
|
.map(m => ({
|
|
102
104
|
...m,
|
|
103
|
-
|
|
104
|
-
|
|
105
|
+
outputPath: this.#resolveOutput(m.outputFolder),
|
|
106
|
+
sourcePath: path.resolve(this.manifest.workspacePath, m.sourceFolder),
|
|
105
107
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
106
108
|
files: Object.fromEntries(
|
|
107
109
|
Object.entries(m.files).map(([folder, files]) => [folder, this.#moduleFiles(m, files ?? [])])
|
|
@@ -111,15 +113,16 @@ export class ManifestIndex {
|
|
|
111
113
|
for (const mod of this.#modules) {
|
|
112
114
|
for (const files of Object.values(mod.files ?? {})) {
|
|
113
115
|
for (const entry of files) {
|
|
114
|
-
this.#outputToEntry.set(entry.
|
|
115
|
-
this.#sourceToEntry.set(entry.
|
|
116
|
+
this.#outputToEntry.set(entry.outputFile, entry);
|
|
117
|
+
this.#sourceToEntry.set(entry.sourceFile, entry);
|
|
116
118
|
this.#importToEntry.set(entry.import, entry);
|
|
117
119
|
this.#importToEntry.set(entry.import.replace(/[.]js$/, ''), entry);
|
|
120
|
+
this.#importToEntry.set(entry.import.replace(/[.]js$/, '.ts'), entry);
|
|
118
121
|
}
|
|
119
122
|
}
|
|
120
123
|
}
|
|
121
124
|
this.#modulesByName = Object.fromEntries(this.#modules.map(x => [x.name, x]));
|
|
122
|
-
this.#modulesByFolder = Object.fromEntries(this.#modules.map(x => [x.
|
|
125
|
+
this.#modulesByFolder = Object.fromEntries(this.#modules.map(x => [x.sourceFolder, x]));
|
|
123
126
|
}
|
|
124
127
|
|
|
125
128
|
/**
|
|
@@ -166,7 +169,7 @@ export class ManifestIndex {
|
|
|
166
169
|
|
|
167
170
|
return searchSpace
|
|
168
171
|
.filter(({ type }) => type === 'ts')
|
|
169
|
-
.filter(({ source }) => filter?.(source) ?? true);
|
|
172
|
+
.filter(({ sourceFile: source }) => filter?.(source) ?? true);
|
|
170
173
|
}
|
|
171
174
|
|
|
172
175
|
/**
|
|
@@ -218,8 +221,7 @@ export class ManifestIndex {
|
|
|
218
221
|
* Resolve import
|
|
219
222
|
*/
|
|
220
223
|
resolveFileImport(name: string): string {
|
|
221
|
-
|
|
222
|
-
return this.#importToEntry.get(name)?.output ?? name;
|
|
224
|
+
return this.#importToEntry.get(name)?.outputFile ?? name;
|
|
223
225
|
}
|
|
224
226
|
|
|
225
227
|
/**
|
|
@@ -247,6 +249,14 @@ export class ManifestIndex {
|
|
|
247
249
|
return name ? this.getModule(name) : undefined;
|
|
248
250
|
}
|
|
249
251
|
|
|
252
|
+
/**
|
|
253
|
+
* Get module from import name
|
|
254
|
+
* @param importName
|
|
255
|
+
*/
|
|
256
|
+
getModuleFromImport(importName: string): IndexedModule | undefined {
|
|
257
|
+
const name = this.getFromImport(importName)?.module;
|
|
258
|
+
return name ? this.getModule(name) : undefined;
|
|
259
|
+
}
|
|
250
260
|
/**
|
|
251
261
|
* Build module list from an expression list (e.g. `@travetto/app,-@travetto/log)
|
|
252
262
|
*/
|
package/src/module.ts
CHANGED
|
@@ -29,6 +29,8 @@ const INDEX_FILES = new Set([
|
|
|
29
29
|
|
|
30
30
|
export class ManifestModuleUtil {
|
|
31
31
|
|
|
32
|
+
static #scanCache: Record<string, string[]> = {};
|
|
33
|
+
|
|
32
34
|
static #getNewest(stat: { mtimeMs: number, ctimeMs: number }): number {
|
|
33
35
|
return Math.max(stat.mtimeMs, stat.ctimeMs);
|
|
34
36
|
}
|
|
@@ -36,8 +38,19 @@ export class ManifestModuleUtil {
|
|
|
36
38
|
/**
|
|
37
39
|
* Simple file scanning
|
|
38
40
|
*/
|
|
39
|
-
static async
|
|
41
|
+
static async scanFolder(folder: string, mainSource = false): Promise<string[]> {
|
|
42
|
+
if (!mainSource && folder in this.#scanCache) {
|
|
43
|
+
return this.#scanCache[folder];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!await fs.stat(folder).catch(() => false)) {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const topFolders = new Set(mainSource ? [] : ['src', 'bin', 'support']);
|
|
51
|
+
const topFiles = new Set(mainSource ? [] : [...INDEX_FILES, 'package.json']);
|
|
40
52
|
const out: string[] = [];
|
|
53
|
+
|
|
41
54
|
if (!fs.stat(folder).catch(() => false)) {
|
|
42
55
|
return out;
|
|
43
56
|
}
|
|
@@ -68,6 +81,11 @@ export class ManifestModuleUtil {
|
|
|
68
81
|
}
|
|
69
82
|
}
|
|
70
83
|
}
|
|
84
|
+
|
|
85
|
+
if (!mainSource) {
|
|
86
|
+
this.#scanCache[folder] = out;
|
|
87
|
+
}
|
|
88
|
+
|
|
71
89
|
return out;
|
|
72
90
|
}
|
|
73
91
|
|
|
@@ -118,6 +136,8 @@ export class ManifestModuleUtil {
|
|
|
118
136
|
return 'support/fixtures';
|
|
119
137
|
} else if (moduleFile.startsWith('support/resources/')) {
|
|
120
138
|
return 'support/resources';
|
|
139
|
+
} else if (moduleFile.startsWith('support/transform')) {
|
|
140
|
+
return '$transformer';
|
|
121
141
|
}
|
|
122
142
|
const key = moduleFile.substring(0, folderLocation);
|
|
123
143
|
switch (key) {
|
|
@@ -152,13 +172,12 @@ export class ManifestModuleUtil {
|
|
|
152
172
|
/**
|
|
153
173
|
* Visit a module and describe files, and metadata
|
|
154
174
|
*/
|
|
155
|
-
static async describeModule(dep: ModuleDep): Promise<ManifestModule> {
|
|
175
|
+
static async describeModule(ctx: ManifestContext, dep: ModuleDep): Promise<ManifestModule> {
|
|
156
176
|
const { main, mainSource, local, name, version, sourcePath, profileSet, parentSet, internal } = dep;
|
|
177
|
+
|
|
157
178
|
const files: ManifestModule['files'] = {};
|
|
158
|
-
const folderSet = new Set<ManifestModuleFolderType>(mainSource ? [] : ['src', 'bin', 'support']);
|
|
159
|
-
const fileSet = new Set(mainSource ? [] : [...INDEX_FILES, 'package.json']);
|
|
160
179
|
|
|
161
|
-
for (const file of await this
|
|
180
|
+
for (const file of await this.scanFolder(sourcePath, mainSource)) {
|
|
162
181
|
// Group by top folder
|
|
163
182
|
const moduleFile = file.replace(`${sourcePath}/`, '');
|
|
164
183
|
const entry = await this.transformFile(moduleFile, file);
|
|
@@ -173,9 +192,11 @@ export class ManifestModuleUtil {
|
|
|
173
192
|
|
|
174
193
|
const profiles = [...profileSet].sort();
|
|
175
194
|
const parents = [...parentSet].sort();
|
|
176
|
-
const
|
|
195
|
+
const outputFolder = `node_modules/${name}`;
|
|
196
|
+
const sourceFolder = sourcePath === ctx.workspacePath ? '' : sourcePath.replace(`${ctx.workspacePath}/`, '');
|
|
177
197
|
|
|
178
|
-
|
|
198
|
+
const res = { main, name, version, local, internal, sourceFolder, outputFolder, files, profiles, parents, };
|
|
199
|
+
return res;
|
|
179
200
|
}
|
|
180
201
|
|
|
181
202
|
/**
|
|
@@ -183,10 +204,10 @@ export class ManifestModuleUtil {
|
|
|
183
204
|
*/
|
|
184
205
|
static async produceModules(ctx: ManifestContext): Promise<Record<string, ManifestModule>> {
|
|
185
206
|
const visitor = new ModuleDependencyVisitor(ctx);
|
|
186
|
-
const
|
|
207
|
+
const mainPath = path.resolve(ctx.workspacePath, ctx.mainFolder);
|
|
208
|
+
const declared = await PackageUtil.visitPackages(mainPath, visitor);
|
|
187
209
|
const sorted = [...declared].sort((a, b) => a.name.localeCompare(b.name));
|
|
188
|
-
|
|
189
|
-
const modules = await Promise.all(sorted.map(x => this.describeModule(x)));
|
|
210
|
+
const modules = await Promise.all(sorted.map(x => this.describeModule(ctx, x)));
|
|
190
211
|
return Object.fromEntries(modules.map(m => [m.name, m]));
|
|
191
212
|
}
|
|
192
213
|
}
|
package/src/package.ts
CHANGED
|
@@ -3,12 +3,12 @@ import fs from 'fs/promises';
|
|
|
3
3
|
import { createRequire } from 'module';
|
|
4
4
|
import { execSync } from 'child_process';
|
|
5
5
|
|
|
6
|
-
import { Package, PackageDigest, PackageRel, PackageVisitor, PackageVisitReq, PackageWorkspaceEntry } from './types';
|
|
6
|
+
import { ManifestContext, Package, PackageDigest, PackageRel, PackageVisitor, PackageVisitReq, PackageWorkspaceEntry } from './types';
|
|
7
7
|
import { path } from './path';
|
|
8
8
|
|
|
9
9
|
export class PackageUtil {
|
|
10
10
|
|
|
11
|
-
static #req = createRequire(
|
|
11
|
+
static #req = createRequire(path.resolve('node_modules'));
|
|
12
12
|
static #framework: Package;
|
|
13
13
|
static #cache: Record<string, Package> = {};
|
|
14
14
|
static #workspaces: Record<string, PackageWorkspaceEntry[]> = {};
|
|
@@ -79,7 +79,10 @@ export class PackageUtil {
|
|
|
79
79
|
/**
|
|
80
80
|
* Read a package.json from a given folder
|
|
81
81
|
*/
|
|
82
|
-
static readPackage(modulePath: string): Package {
|
|
82
|
+
static readPackage(modulePath: string, forceRead = false): Package {
|
|
83
|
+
if (forceRead) {
|
|
84
|
+
delete this.#cache[modulePath];
|
|
85
|
+
}
|
|
83
86
|
return this.#cache[modulePath] ??= JSON.parse(readFileSync(
|
|
84
87
|
modulePath.endsWith('.json') ? modulePath : path.resolve(modulePath, 'package.json'),
|
|
85
88
|
'utf8'
|
|
@@ -161,11 +164,18 @@ export class PackageUtil {
|
|
|
161
164
|
/**
|
|
162
165
|
* Find workspace values from rootPath
|
|
163
166
|
*/
|
|
164
|
-
static resolveWorkspaces(rootPath: string): PackageWorkspaceEntry[] {
|
|
167
|
+
static async resolveWorkspaces(ctx: ManifestContext, rootPath: string): Promise<PackageWorkspaceEntry[]> {
|
|
165
168
|
if (!this.#workspaces[rootPath]) {
|
|
166
|
-
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
+
await fs.mkdir(path.resolve(ctx.workspacePath, ctx.outputFolder), { recursive: true });
|
|
170
|
+
const cache = path.resolve(ctx.workspacePath, ctx.outputFolder, 'workspaces.json');
|
|
171
|
+
try {
|
|
172
|
+
return JSON.parse(await fs.readFile(cache, 'utf8'));
|
|
173
|
+
} catch {
|
|
174
|
+
const text = execSync('npm query .workspace', { cwd: rootPath, encoding: 'utf8', env: {} });
|
|
175
|
+
const res: { location: string, name: string }[] = JSON.parse(text);
|
|
176
|
+
const out = this.#workspaces[rootPath] = res.map(d => ({ sourcePath: d.location, name: d.name }));
|
|
177
|
+
await fs.writeFile(cache, JSON.stringify(out), 'utf8');
|
|
178
|
+
}
|
|
169
179
|
}
|
|
170
180
|
return this.#workspaces[rootPath];
|
|
171
181
|
}
|
|
@@ -175,7 +185,7 @@ export class PackageUtil {
|
|
|
175
185
|
*/
|
|
176
186
|
static async syncVersions(folders: string[], versionMapping: Record<string, string> = {}): Promise<void> {
|
|
177
187
|
const packages = folders.map(folder => {
|
|
178
|
-
const pkg = this.readPackage(folder);
|
|
188
|
+
const pkg = this.readPackage(folder, true);
|
|
179
189
|
versionMapping[pkg.name] = `^${pkg.version}`;
|
|
180
190
|
return { folder, pkg };
|
|
181
191
|
});
|
package/src/root-index.ts
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import { fileURLToPath } from 'url';
|
|
2
|
-
import { createRequire } from 'module';
|
|
3
|
-
|
|
4
1
|
import { path } from './path';
|
|
5
2
|
import { IndexedModule, ManifestIndex } from './manifest-index';
|
|
6
3
|
import { FunctionMetadata, Package, PackageDigest } from './types';
|
|
@@ -13,40 +10,16 @@ type Metadated = { [METADATA]: FunctionMetadata };
|
|
|
13
10
|
* Extended manifest index geared for application execution
|
|
14
11
|
*/
|
|
15
12
|
class $RootIndex extends ManifestIndex {
|
|
16
|
-
/**
|
|
17
|
-
* Load all source modules
|
|
18
|
-
*/
|
|
19
|
-
static resolveManifestJSON(root: string, file?: string): string {
|
|
20
|
-
file = file || path.resolve(root, 'manifest.json');
|
|
21
|
-
|
|
22
|
-
// IF not a file
|
|
23
|
-
if (!file.endsWith('.json')) {
|
|
24
|
-
try {
|
|
25
|
-
// Try to resolve
|
|
26
|
-
const req = createRequire(path.resolve(root, 'node_modules'));
|
|
27
|
-
file = req.resolve(`${file}/manifest.json`);
|
|
28
|
-
} catch {
|
|
29
|
-
// Fallback to assumed node_modules pattern
|
|
30
|
-
file = `${root}/node_modules/${file}/manifest.json`;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
return file;
|
|
34
|
-
}
|
|
35
13
|
|
|
36
14
|
#config: Package | undefined;
|
|
37
|
-
#srcCache = new Map();
|
|
38
15
|
#metadata = new Map<string, FunctionMetadata>();
|
|
39
16
|
|
|
40
|
-
constructor(output: string = process.env.TRV_OUTPUT ?? process.cwd()) {
|
|
41
|
-
super(output, $RootIndex.resolveManifestJSON(output, process.env.TRV_MANIFEST));
|
|
42
|
-
}
|
|
43
|
-
|
|
44
17
|
/**
|
|
45
18
|
* **WARNING**: This is a destructive operation, and should only be called before loading any code
|
|
46
19
|
* @private
|
|
47
20
|
*/
|
|
48
21
|
reinitForModule(module: string): void {
|
|
49
|
-
this.init(this.
|
|
22
|
+
this.init(`${this.outputRoot}/node_modules/${module}`);
|
|
50
23
|
this.#config = undefined;
|
|
51
24
|
}
|
|
52
25
|
|
|
@@ -54,15 +27,15 @@ class $RootIndex extends ManifestIndex {
|
|
|
54
27
|
* Determines if the manifest root is the root for a monorepo
|
|
55
28
|
*/
|
|
56
29
|
isMonoRepoRoot(): boolean {
|
|
57
|
-
return !!this.manifest.monoRepo && this.manifest.
|
|
30
|
+
return !!this.manifest.monoRepo && !this.manifest.mainFolder;
|
|
58
31
|
}
|
|
59
32
|
|
|
60
33
|
/**
|
|
61
34
|
* Asynchronously load all source files from manifest
|
|
62
35
|
*/
|
|
63
36
|
async loadSource(): Promise<void> {
|
|
64
|
-
for (const {
|
|
65
|
-
await import(
|
|
37
|
+
for (const { import: imp } of this.findSrc()) {
|
|
38
|
+
await import(imp);
|
|
66
39
|
}
|
|
67
40
|
}
|
|
68
41
|
|
|
@@ -87,14 +60,14 @@ class $RootIndex extends ManifestIndex {
|
|
|
87
60
|
*/
|
|
88
61
|
get mainPackage(): Package {
|
|
89
62
|
if (!this.#config) {
|
|
90
|
-
const {
|
|
63
|
+
const { outputPath } = this.getModule(this.manifest.mainModule)!;
|
|
91
64
|
this.#config = {
|
|
92
65
|
...{
|
|
93
66
|
name: 'untitled',
|
|
94
67
|
description: 'A Travetto application',
|
|
95
68
|
version: '0.0.0',
|
|
96
69
|
},
|
|
97
|
-
...PackageUtil.readPackage(
|
|
70
|
+
...PackageUtil.readPackage(outputPath)
|
|
98
71
|
};
|
|
99
72
|
}
|
|
100
73
|
return this.#config;
|
|
@@ -105,20 +78,11 @@ class $RootIndex extends ManifestIndex {
|
|
|
105
78
|
}
|
|
106
79
|
|
|
107
80
|
/**
|
|
108
|
-
* Get source file from
|
|
81
|
+
* Get source file from import location
|
|
109
82
|
* @param outputFile
|
|
110
83
|
*/
|
|
111
|
-
getSourceFile(
|
|
112
|
-
|
|
113
|
-
if (file.startsWith('file:')) {
|
|
114
|
-
this.#srcCache.set(file, path.toPosix(fileURLToPath(file)));
|
|
115
|
-
} else {
|
|
116
|
-
this.#srcCache.set(file, path.toPosix(file));
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const outputFile = this.#srcCache.get(file)!;
|
|
121
|
-
return this.getEntry(outputFile)?.source ?? outputFile;
|
|
84
|
+
getSourceFile(importFile: string): string {
|
|
85
|
+
return this.getFromImport(importFile)?.sourceFile ?? importFile;
|
|
122
86
|
}
|
|
123
87
|
|
|
124
88
|
/**
|
|
@@ -129,8 +93,8 @@ class $RootIndex extends ManifestIndex {
|
|
|
129
93
|
* @param `methods` Methods and their hashes
|
|
130
94
|
* @param `abstract` Is the class abstract
|
|
131
95
|
*/
|
|
132
|
-
registerFunction(cls: Function,
|
|
133
|
-
const source = this.getSourceFile(
|
|
96
|
+
registerFunction(cls: Function, fileOrImport: string, hash: number, methods?: Record<string, { hash: number }>, abstract?: boolean, synthetic?: boolean): boolean {
|
|
97
|
+
const source = this.getSourceFile(fileOrImport);
|
|
134
98
|
const id = this.getId(source, cls.name);
|
|
135
99
|
Object.defineProperty(cls, 'Ⲑid', { value: id });
|
|
136
100
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
@@ -154,14 +118,32 @@ class $RootIndex extends ManifestIndex {
|
|
|
154
118
|
const id = clsId === undefined ? '' : typeof clsId === 'string' ? clsId : clsId.Ⲑid;
|
|
155
119
|
return this.#metadata.get(id);
|
|
156
120
|
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Get local folders that represent the user's controlled input
|
|
124
|
+
*/
|
|
125
|
+
getLocalInputFolders(): string[] {
|
|
126
|
+
return this.getLocalModules()
|
|
127
|
+
.flatMap(x =>
|
|
128
|
+
(!this.manifest.monoRepo || x.sourcePath !== this.manifest.workspacePath) ?
|
|
129
|
+
[x.sourcePath] : [...Object.keys(x.files)].filter(y => !y.startsWith('$')).map(y => path.resolve(x.sourcePath, y))
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Get local output folders
|
|
135
|
+
*/
|
|
136
|
+
getLocalOutputFolders(): string[] {
|
|
137
|
+
return this.getLocalModules().map(x => x.outputPath);
|
|
138
|
+
}
|
|
157
139
|
}
|
|
158
140
|
|
|
159
141
|
let index: $RootIndex | undefined;
|
|
160
142
|
|
|
161
143
|
try {
|
|
162
|
-
index = new $RootIndex();
|
|
144
|
+
index = new $RootIndex(process.env.TRV_MANIFEST!);
|
|
163
145
|
} catch (err) {
|
|
164
|
-
if (process.env.
|
|
146
|
+
if (/prod/i.test(process.env.NODE_ENV ?? '')) {
|
|
165
147
|
throw err;
|
|
166
148
|
}
|
|
167
149
|
}
|
package/src/types.ts
CHANGED
|
@@ -3,7 +3,7 @@ export type ManifestModuleFolderType =
|
|
|
3
3
|
'$root' | '$index' | '$package' |
|
|
4
4
|
'src' | 'bin' | 'support' | 'resources' | 'test' | 'doc' |
|
|
5
5
|
'test/fixtures' | 'support/fixtures' | 'support/resources' |
|
|
6
|
-
'$other';
|
|
6
|
+
'$other' | '$transformer';
|
|
7
7
|
|
|
8
8
|
export type ManifestProfile = 'compile' | 'test' | 'doc' | 'build' | 'std';
|
|
9
9
|
export type PackageRel = 'dev' | 'prod' | 'peer' | 'opt' | 'root' | 'global';
|
|
@@ -14,8 +14,8 @@ export type ManifestModuleCore = {
|
|
|
14
14
|
main?: boolean;
|
|
15
15
|
local?: boolean;
|
|
16
16
|
version: string;
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
sourceFolder: string;
|
|
18
|
+
outputFolder: string;
|
|
19
19
|
profiles: ManifestProfile[];
|
|
20
20
|
parents: string[];
|
|
21
21
|
internal?: boolean;
|
|
@@ -26,13 +26,13 @@ export type ManifestModule = ManifestModuleCore & {
|
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
export type ManifestContext = {
|
|
29
|
-
manifestFile: string;
|
|
30
29
|
mainModule: string;
|
|
31
|
-
|
|
30
|
+
mainFolder: string;
|
|
32
31
|
workspacePath: string;
|
|
33
32
|
outputFolder: string;
|
|
34
33
|
compilerFolder: string;
|
|
35
34
|
monoRepo?: boolean;
|
|
35
|
+
moduleType: 'module' | 'commonjs';
|
|
36
36
|
};
|
|
37
37
|
|
|
38
38
|
export type ManifestRoot = ManifestContext & {
|
|
@@ -40,15 +40,6 @@ export type ManifestRoot = ManifestContext & {
|
|
|
40
40
|
modules: Record<string, ManifestModule>;
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
-
export type ManifestDeltaEventType = 'added' | 'changed' | 'removed' | 'missing' | 'dirty';
|
|
44
|
-
export type ManifestDeltaModule = ManifestModuleCore & { files: Record<string, ManifestModuleFile> };
|
|
45
|
-
export type ManifestDeltaEvent = { file: string, type: ManifestDeltaEventType, module: string };
|
|
46
|
-
export type ManifestDelta = Record<string, ManifestDeltaEvent[]>;
|
|
47
|
-
export type ManifestState = {
|
|
48
|
-
manifest: ManifestRoot;
|
|
49
|
-
delta: ManifestDelta;
|
|
50
|
-
};
|
|
51
|
-
|
|
52
43
|
export type Package = {
|
|
53
44
|
name: string;
|
|
54
45
|
type?: 'module' | 'commonjs';
|
|
@@ -116,4 +107,4 @@ export type FunctionMetadata = {
|
|
|
116
107
|
methods?: Record<string, { hash: number }>;
|
|
117
108
|
synthetic?: boolean;
|
|
118
109
|
abstract?: boolean;
|
|
119
|
-
};
|
|
110
|
+
};
|
package/src/util.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
|
|
5
|
+
import { path } from './path';
|
|
6
|
+
import { ManifestContext, ManifestRoot } from './types';
|
|
7
|
+
import { ManifestModuleUtil } from './module';
|
|
8
|
+
|
|
9
|
+
export const MANIFEST_FILE = 'manifest.json';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Manifest utils
|
|
13
|
+
*/
|
|
14
|
+
export class ManifestUtil {
|
|
15
|
+
|
|
16
|
+
static async writeJsonWithBuffer(ctx: ManifestContext, filename: string, obj: object): Promise<string> {
|
|
17
|
+
const tempName = path.resolve(ctx.workspacePath, ctx.mainFolder, filename).replace(/[\/\\: ]/g, '_');
|
|
18
|
+
const file = path.resolve(ctx.workspacePath, ctx.outputFolder, 'node_modules', ctx.mainModule, filename);
|
|
19
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
20
|
+
const temp = path.resolve(os.tmpdir(), `${tempName}.${Date.now()}`);
|
|
21
|
+
await fs.writeFile(temp, JSON.stringify(obj), 'utf8');
|
|
22
|
+
await fs.copyFile(temp, file);
|
|
23
|
+
fs.unlink(temp);
|
|
24
|
+
return file;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Build a manifest context
|
|
29
|
+
* @param folder
|
|
30
|
+
*/
|
|
31
|
+
static async buildContext(folder?: string): Promise<ManifestContext> {
|
|
32
|
+
const { getManifestContext } = await import('../bin/context.js');
|
|
33
|
+
return getManifestContext(folder);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Produce manifest in memory
|
|
38
|
+
*/
|
|
39
|
+
static async buildManifest(ctx: ManifestContext): Promise<ManifestRoot> {
|
|
40
|
+
return {
|
|
41
|
+
modules: await ManifestModuleUtil.produceModules(ctx),
|
|
42
|
+
generated: Date.now(),
|
|
43
|
+
...ctx
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Read manifest, synchronously
|
|
49
|
+
*
|
|
50
|
+
* @param file
|
|
51
|
+
* @returns
|
|
52
|
+
*/
|
|
53
|
+
static readManifestSync(file: string): { manifest: ManifestRoot, file: string } {
|
|
54
|
+
file = path.resolve(file);
|
|
55
|
+
if (!file.endsWith('.json')) {
|
|
56
|
+
file = path.resolve(file, MANIFEST_FILE);
|
|
57
|
+
}
|
|
58
|
+
const manifest: ManifestRoot = JSON.parse(readFileSync(file, 'utf8'));
|
|
59
|
+
if (!manifest.outputFolder) {
|
|
60
|
+
manifest.outputFolder = path.cwd();
|
|
61
|
+
manifest.workspacePath = path.cwd();
|
|
62
|
+
}
|
|
63
|
+
return { manifest, file };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Write manifest for a given context, return location
|
|
68
|
+
*/
|
|
69
|
+
static writeManifest(ctx: ManifestContext, manifest: ManifestRoot): Promise<string> {
|
|
70
|
+
return this.writeJsonWithBuffer(ctx, MANIFEST_FILE, manifest);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Rewrite manifest for a given folder
|
|
75
|
+
*/
|
|
76
|
+
static async rewriteManifest(source: string): Promise<void> {
|
|
77
|
+
const subCtx = await this.buildContext(source);
|
|
78
|
+
const subManifest = await this.buildManifest(subCtx);
|
|
79
|
+
await this.writeManifest(subCtx, subManifest);
|
|
80
|
+
}
|
|
81
|
+
}
|
package/src/watch.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
|
|
3
|
+
export type WatchEvent = { action: 'create' | 'update' | 'delete', file: string };
|
|
4
|
+
|
|
5
|
+
type EventListener = (ev: WatchEvent, folder: string) => void;
|
|
6
|
+
type EventFilter = (ev: WatchEvent) => boolean;
|
|
7
|
+
type WatchConfig = { filter?: EventFilter, ignore?: string[] };
|
|
8
|
+
|
|
9
|
+
async function getWatcher(): Promise<typeof import('@parcel/watcher')> {
|
|
10
|
+
try {
|
|
11
|
+
return await import('@parcel/watcher');
|
|
12
|
+
} catch (err) {
|
|
13
|
+
console.error('@parcel/watcher must be installed to use watching functionality');
|
|
14
|
+
throw err;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Leverages @parcel/watcher to watch a series of folders
|
|
20
|
+
* @param folders
|
|
21
|
+
* @param onEvent
|
|
22
|
+
* @private
|
|
23
|
+
*/
|
|
24
|
+
export async function watchFolders(folders: string[], onEvent: EventListener, config: WatchConfig = {}): Promise<() => Promise<void>> {
|
|
25
|
+
const lib = await getWatcher();
|
|
26
|
+
const subs = await Promise.all(folders.map(async folder => {
|
|
27
|
+
if (await fs.stat(folder).then(() => true, () => false)) {
|
|
28
|
+
const ignore = (await fs.readdir(folder)).filter(x => x.startsWith('.') && x.length > 2);
|
|
29
|
+
return lib.subscribe(folder, (err, events) => {
|
|
30
|
+
for (const ev of events) {
|
|
31
|
+
const finalEv = { action: ev.type, file: ev.path };
|
|
32
|
+
if (!config.filter || config.filter(finalEv)) {
|
|
33
|
+
onEvent(finalEv, folder);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}, { ignore: [...ignore, ...config.ignore ?? []] });
|
|
37
|
+
}
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
// Allow for multiple calls
|
|
41
|
+
let finalProm: Promise<void> | undefined;
|
|
42
|
+
const remove = (): Promise<void> => finalProm ??= Promise.all(subs.map(x => x?.unsubscribe())).then(() => { });
|
|
43
|
+
|
|
44
|
+
// Cleanup on exit
|
|
45
|
+
process.on('exit', remove);
|
|
46
|
+
|
|
47
|
+
return remove;
|
|
48
|
+
}
|
package/src/manifest.ts
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import fs from 'fs/promises';
|
|
2
|
-
import os from 'os';
|
|
3
|
-
|
|
4
|
-
import { path } from './path';
|
|
5
|
-
import { ManifestContext, ManifestRoot, ManifestState } from './types';
|
|
6
|
-
|
|
7
|
-
import { ManifestModuleUtil } from './module';
|
|
8
|
-
import { ManifestDeltaUtil } from './delta';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Manifest utils
|
|
12
|
-
*/
|
|
13
|
-
export class ManifestUtil {
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Produce manifest in memory
|
|
17
|
-
*/
|
|
18
|
-
static async buildManifest(ctx: ManifestContext): Promise<ManifestRoot> {
|
|
19
|
-
return {
|
|
20
|
-
modules: await ManifestModuleUtil.produceModules(ctx),
|
|
21
|
-
generated: Date.now(),
|
|
22
|
-
...ctx
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Generate manifest for a given context, and persist
|
|
28
|
-
*/
|
|
29
|
-
static async createAndWriteManifest(ctx: ManifestContext): Promise<void> {
|
|
30
|
-
const manifest = await this.buildManifest(ctx);
|
|
31
|
-
await this.writeManifest(ctx, manifest);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Read manifest from a folder
|
|
36
|
-
*/
|
|
37
|
-
static async readManifest(ctx: ManifestContext): Promise<ManifestRoot> {
|
|
38
|
-
const file = path.resolve(ctx.workspacePath, ctx.outputFolder, ctx.manifestFile);
|
|
39
|
-
if (await fs.stat(file).catch(() => false)) {
|
|
40
|
-
try {
|
|
41
|
-
return JSON.parse(await fs.readFile(file, 'utf8'));
|
|
42
|
-
} catch (err) {
|
|
43
|
-
await fs.unlink(file).catch(() => { });
|
|
44
|
-
const final = new Error(`Corrupted manifest ${ctx.manifestFile}: file has been removed, retry`);
|
|
45
|
-
if (err instanceof Error) {
|
|
46
|
-
final.stack = [final.message, ...(err.stack ?? '').split('\n').slice(1)].join('\n');
|
|
47
|
-
}
|
|
48
|
-
throw final;
|
|
49
|
-
}
|
|
50
|
-
} else {
|
|
51
|
-
return {
|
|
52
|
-
modules: {},
|
|
53
|
-
generated: Date.now(),
|
|
54
|
-
...ctx,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Load state from disk
|
|
61
|
-
*/
|
|
62
|
-
static async readState(file: string): Promise<ManifestState> {
|
|
63
|
-
return JSON.parse(await fs.readFile(file, 'utf8'));
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Persist state to disk in a temp file, return said temp file
|
|
68
|
-
*/
|
|
69
|
-
static async writeState(state: ManifestState, file?: string): Promise<string> {
|
|
70
|
-
const manifestTemp = file ?? path.resolve(os.tmpdir(), `manifest-state.${Date.now()}${Math.random()}.json`);
|
|
71
|
-
await fs.writeFile(manifestTemp, JSON.stringify(state), 'utf8');
|
|
72
|
-
return manifestTemp;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Generate the manifest and delta as a single output
|
|
77
|
-
*/
|
|
78
|
-
static async produceState(ctx: ManifestContext): Promise<ManifestState> {
|
|
79
|
-
const manifest = await this.buildManifest(ctx);
|
|
80
|
-
const oldManifest = await this.readManifest(ctx);
|
|
81
|
-
const delta = await ManifestDeltaUtil.produceDelta(
|
|
82
|
-
path.resolve(manifest.workspacePath, ctx.outputFolder),
|
|
83
|
-
manifest,
|
|
84
|
-
oldManifest
|
|
85
|
-
);
|
|
86
|
-
return { manifest, delta };
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Resolves a module file, from a context and manifest
|
|
91
|
-
*/
|
|
92
|
-
static resolveFile(ctx: ManifestContext, manifest: ManifestRoot, module: string, file: string): string {
|
|
93
|
-
return path.resolve(
|
|
94
|
-
ctx.workspacePath,
|
|
95
|
-
ctx.compilerFolder,
|
|
96
|
-
manifest.modules[module].output,
|
|
97
|
-
file
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Write manifest for a given context
|
|
103
|
-
*/
|
|
104
|
-
static async writeManifest(ctx: ManifestContext, manifest: ManifestRoot): Promise<void> {
|
|
105
|
-
// Write manifest in the scenario we are in mono-repo state where everything pre-existed
|
|
106
|
-
const file = path.resolve(ctx.workspacePath, ctx.outputFolder, ctx.manifestFile);
|
|
107
|
-
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
108
|
-
await fs.writeFile(file, JSON.stringify(manifest));
|
|
109
|
-
}
|
|
110
|
-
}
|