@travetto/manifest 3.0.0-rc.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +14 -0
- package/__index__.ts +11 -0
- package/bin/context.d.ts +10 -0
- package/bin/context.js +116 -0
- package/package.json +48 -0
- package/src/delta.ts +104 -0
- package/src/dependencies.ts +163 -0
- package/src/manifest-index.ts +318 -0
- package/src/module.ts +213 -0
- package/src/package.ts +220 -0
- package/src/path.ts +17 -0
- package/src/root-index.ts +133 -0
- package/src/types.ts +111 -0
- package/src/typings.d.ts +3 -0
- package/src/util.ts +115 -0
- package/src/watch.ts +56 -0
- package/support/transformer.function-metadata.ts +137 -0
package/src/package.ts
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import { createRequire } from 'module';
|
|
4
|
+
import { execSync } from 'child_process';
|
|
5
|
+
|
|
6
|
+
import { ManifestContext, Package, PackageDigest, PackageRel, PackageVisitor, PackageVisitReq, PackageWorkspaceEntry } from './types';
|
|
7
|
+
import { path } from './path';
|
|
8
|
+
|
|
9
|
+
export class PackageUtil {
|
|
10
|
+
|
|
11
|
+
static #req = createRequire(path.resolve('node_modules'));
|
|
12
|
+
static #framework: Package;
|
|
13
|
+
static #cache: Record<string, Package> = {};
|
|
14
|
+
static #workspaces: Record<string, PackageWorkspaceEntry[]> = {};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Clear out cached package file reads
|
|
18
|
+
*/
|
|
19
|
+
static clearCache(): void {
|
|
20
|
+
this.#cache = {};
|
|
21
|
+
this.#workspaces = {};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static resolveImport = (library: string): string => this.#req.resolve(library);
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Resolve version path, if file: url
|
|
28
|
+
*/
|
|
29
|
+
static resolveVersionPath(rootPath: string, ver: string): string | undefined {
|
|
30
|
+
if (ver.startsWith('file:')) {
|
|
31
|
+
return path.resolve(rootPath, ver.replace('file:', ''));
|
|
32
|
+
} else {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Find package.json folder for a given dependency
|
|
39
|
+
*/
|
|
40
|
+
static resolvePackagePath(name: string): string {
|
|
41
|
+
try {
|
|
42
|
+
return path.dirname(this.resolveImport(`${name}/package.json`));
|
|
43
|
+
} catch {
|
|
44
|
+
try {
|
|
45
|
+
const resolved = this.resolveImport(name);
|
|
46
|
+
return path.join(resolved.split(name)[0], name);
|
|
47
|
+
} catch { }
|
|
48
|
+
}
|
|
49
|
+
throw new Error(`Unable to resolve: ${name}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Build a package visit req
|
|
54
|
+
*/
|
|
55
|
+
static packageReq<T>(sourcePath: string, rel: PackageRel): PackageVisitReq<T> {
|
|
56
|
+
return { pkg: this.readPackage(sourcePath), sourcePath, rel };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Extract all dependencies from a package
|
|
61
|
+
*/
|
|
62
|
+
static getAllDependencies<T = unknown>(modulePath: string, rootPath: string): PackageVisitReq<T>[] {
|
|
63
|
+
const pkg = this.readPackage(modulePath);
|
|
64
|
+
const children: Record<string, PackageVisitReq<T>> = {};
|
|
65
|
+
const local = modulePath === rootPath && !modulePath.includes('node_modules');
|
|
66
|
+
for (const [deps, rel] of [
|
|
67
|
+
[pkg.dependencies, 'prod'],
|
|
68
|
+
[pkg.peerDependencies, 'peer'],
|
|
69
|
+
[pkg.optionalDependencies, 'opt'],
|
|
70
|
+
...(local ? [[pkg.devDependencies, 'dev'] as const] : []),
|
|
71
|
+
] as const) {
|
|
72
|
+
for (const [name, version] of Object.entries(deps ?? {})) {
|
|
73
|
+
try {
|
|
74
|
+
const depPath = this.resolveVersionPath(modulePath, version) ?? this.resolvePackagePath(name);
|
|
75
|
+
children[`${name}#${version}`] = this.packageReq<T>(depPath, rel);
|
|
76
|
+
} catch (err) {
|
|
77
|
+
if (rel === 'opt' || (rel === 'peer' && !!pkg.peerDependenciesMeta?.[name].optional)) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
throw err;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return Object.values(children).sort((a, b) => a.pkg.name.localeCompare(b.pkg.name));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Read a package.json from a given folder
|
|
89
|
+
*/
|
|
90
|
+
static readPackage(modulePath: string, forceRead = false): Package {
|
|
91
|
+
if (forceRead) {
|
|
92
|
+
delete this.#cache[modulePath];
|
|
93
|
+
}
|
|
94
|
+
return this.#cache[modulePath] ??= JSON.parse(readFileSync(
|
|
95
|
+
modulePath.endsWith('.json') ? modulePath : path.resolve(modulePath, 'package.json'),
|
|
96
|
+
'utf8'
|
|
97
|
+
));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* import a package.json from a given module name
|
|
102
|
+
*/
|
|
103
|
+
static importPackage(moduleName: string): Package {
|
|
104
|
+
return this.readPackage(this.resolvePackagePath(moduleName));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Write package
|
|
109
|
+
*/
|
|
110
|
+
static async writePackageIfChanged(modulePath: string, pkg: Package): Promise<void> {
|
|
111
|
+
const final = JSON.stringify(pkg, null, 2);
|
|
112
|
+
const target = path.resolve(modulePath, 'package.json');
|
|
113
|
+
const current = (await fs.readFile(target, 'utf8').catch(() => '')).trim();
|
|
114
|
+
if (final !== current) {
|
|
115
|
+
await fs.writeFile(target, `${final}\n`, 'utf8');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Visit packages with ability to track duplicates
|
|
121
|
+
*/
|
|
122
|
+
static async visitPackages<T>(
|
|
123
|
+
rootOrPath: PackageVisitReq<T> | string,
|
|
124
|
+
visitor: PackageVisitor<T>
|
|
125
|
+
): Promise<Set<T>> {
|
|
126
|
+
|
|
127
|
+
const root = typeof rootOrPath === 'string' ?
|
|
128
|
+
this.packageReq<T>(rootOrPath, 'root') :
|
|
129
|
+
rootOrPath;
|
|
130
|
+
|
|
131
|
+
const seen = new Map<string, T>();
|
|
132
|
+
const queue: PackageVisitReq<T>[] = [...await visitor.init?.(root) ?? [], root];
|
|
133
|
+
const out = new Set<T>();
|
|
134
|
+
|
|
135
|
+
while (queue.length) {
|
|
136
|
+
const req = queue.pop();
|
|
137
|
+
|
|
138
|
+
if (!req || (visitor.valid && !visitor.valid(req))) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const key = req.sourcePath;
|
|
143
|
+
if (seen.has(key)) {
|
|
144
|
+
await visitor.visit?.(req, seen.get(key)!);
|
|
145
|
+
} else {
|
|
146
|
+
const dep = await visitor.create(req);
|
|
147
|
+
out.add(dep);
|
|
148
|
+
await visitor.visit?.(req, dep);
|
|
149
|
+
seen.set(key, dep);
|
|
150
|
+
const children = this.getAllDependencies<T>(req.sourcePath, root.sourcePath);
|
|
151
|
+
queue.push(...children.map(x => ({ ...x, parent: dep })));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return (await visitor.complete?.(out)) ?? out;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Get version of manifest package
|
|
159
|
+
*/
|
|
160
|
+
static getFrameworkVersion(): string {
|
|
161
|
+
return (this.#framework ??= this.importPackage('@travetto/manifest')).version;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Produce simple digest of
|
|
166
|
+
*/
|
|
167
|
+
static digest(pkg: Package): PackageDigest {
|
|
168
|
+
const { main, name, author, license, version } = pkg;
|
|
169
|
+
return { name, main, author, license, version, framework: this.getFrameworkVersion() };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Find workspace values from rootPath
|
|
174
|
+
*/
|
|
175
|
+
static async resolveWorkspaces(ctx: ManifestContext, rootPath: string): Promise<PackageWorkspaceEntry[]> {
|
|
176
|
+
if (!this.#workspaces[rootPath]) {
|
|
177
|
+
await fs.mkdir(path.resolve(ctx.workspacePath, ctx.outputFolder), { recursive: true });
|
|
178
|
+
const cache = path.resolve(ctx.workspacePath, ctx.outputFolder, 'workspaces.json');
|
|
179
|
+
try {
|
|
180
|
+
return JSON.parse(await fs.readFile(cache, 'utf8'));
|
|
181
|
+
} catch {
|
|
182
|
+
const text = execSync('npm query .workspace', { cwd: rootPath, encoding: 'utf8', env: { PATH: process.env.PATH, NODE_PATH: process.env.NODE_PATH } });
|
|
183
|
+
const res: { location: string, name: string }[] = JSON.parse(text);
|
|
184
|
+
const out = this.#workspaces[rootPath] = res.map(d => ({ sourcePath: d.location, name: d.name }));
|
|
185
|
+
await fs.writeFile(cache, JSON.stringify(out), 'utf8');
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return this.#workspaces[rootPath];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Sync versions across a series of folders
|
|
193
|
+
*/
|
|
194
|
+
static async syncVersions(folders: string[], versionMapping: Record<string, string> = {}): Promise<void> {
|
|
195
|
+
const packages = folders.map(folder => {
|
|
196
|
+
const pkg = this.readPackage(folder, true);
|
|
197
|
+
versionMapping[pkg.name] = `^${pkg.version}`;
|
|
198
|
+
return { folder, pkg };
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
for (const { pkg } of packages) {
|
|
202
|
+
for (const group of [
|
|
203
|
+
pkg.dependencies ?? {},
|
|
204
|
+
pkg.devDependencies ?? {},
|
|
205
|
+
pkg.optionalDependencies ?? {},
|
|
206
|
+
pkg.peerDependencies ?? {}
|
|
207
|
+
]) {
|
|
208
|
+
for (const [mod, ver] of Object.entries(versionMapping)) {
|
|
209
|
+
if (mod in group && !/^[*]|(file:.*)$/.test(group[mod])) {
|
|
210
|
+
group[mod] = ver;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
for (const { folder, pkg } of packages) {
|
|
217
|
+
await this.writePackageIfChanged(folder, pkg);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
package/src/path.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { extname, dirname, resolve, basename, delimiter, join, sep } from 'path';
|
|
2
|
+
|
|
3
|
+
const posix = (file: string): string => file.replaceAll('\\', '/');
|
|
4
|
+
|
|
5
|
+
const cwd = (): string => posix(process.cwd());
|
|
6
|
+
|
|
7
|
+
export const path = {
|
|
8
|
+
cwd,
|
|
9
|
+
toPosix: posix,
|
|
10
|
+
delimiter,
|
|
11
|
+
basename: (file: string): string => posix(basename(file)),
|
|
12
|
+
extname: (file: string): string => posix(extname(file)),
|
|
13
|
+
dirname: (file: string): string => posix(dirname(file)),
|
|
14
|
+
resolve: (...args: string[]): string => posix(resolve(cwd(), ...args.map(f => posix(f)))),
|
|
15
|
+
join: (root: string, ...args: string[]): string => posix(join(posix(root), ...args.map(f => posix(f)))),
|
|
16
|
+
toNative: (file: string): string => file.replaceAll('/', sep)
|
|
17
|
+
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { path } from './path';
|
|
2
|
+
import { IndexedModule, ManifestIndex } from './manifest-index';
|
|
3
|
+
import { FunctionMetadata, Package, PackageDigest } from './types';
|
|
4
|
+
import { PackageUtil } from './package';
|
|
5
|
+
|
|
6
|
+
const METADATA = Symbol.for('@travetto/manifest:metadata');
|
|
7
|
+
type Metadated = { [METADATA]: FunctionMetadata };
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Extended manifest index geared for application execution
|
|
11
|
+
*/
|
|
12
|
+
class $RootIndex extends ManifestIndex {
|
|
13
|
+
|
|
14
|
+
#config: Package | undefined;
|
|
15
|
+
#metadata = new Map<string, FunctionMetadata>();
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* **WARNING**: This is a destructive operation, and should only be called before loading any code
|
|
19
|
+
* @private
|
|
20
|
+
*/
|
|
21
|
+
reinitForModule(module: string): void {
|
|
22
|
+
this.init(`${this.outputRoot}/node_modules/${module}`);
|
|
23
|
+
this.#config = undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Determines if the manifest root is the root for a monorepo
|
|
28
|
+
*/
|
|
29
|
+
isMonoRepoRoot(): boolean {
|
|
30
|
+
return !!this.manifest.monoRepo && !this.manifest.mainFolder;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Asynchronously load all source files from manifest
|
|
35
|
+
*/
|
|
36
|
+
async loadSource(): Promise<void> {
|
|
37
|
+
for (const { import: imp } of this.findSrc()) {
|
|
38
|
+
await import(imp);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get internal id from file name and optionally, class name
|
|
44
|
+
*/
|
|
45
|
+
getId(filename: string, clsName?: string): string {
|
|
46
|
+
filename = path.toPosix(filename);
|
|
47
|
+
const id = this.getEntry(filename)?.id ?? filename;
|
|
48
|
+
return clsName ? `${id}○${clsName}` : id;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get main module for manifest
|
|
53
|
+
*/
|
|
54
|
+
get mainModule(): IndexedModule {
|
|
55
|
+
return this.getModule(this.mainPackage.name)!;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get main package for manifest
|
|
60
|
+
*/
|
|
61
|
+
get mainPackage(): Package {
|
|
62
|
+
if (!this.#config) {
|
|
63
|
+
const { outputPath } = this.getModule(this.manifest.mainModule)!;
|
|
64
|
+
this.#config = {
|
|
65
|
+
...{
|
|
66
|
+
name: 'untitled',
|
|
67
|
+
description: 'A Travetto application',
|
|
68
|
+
version: '0.0.0',
|
|
69
|
+
},
|
|
70
|
+
...PackageUtil.readPackage(outputPath)
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return this.#config;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
mainDigest(): PackageDigest {
|
|
77
|
+
return PackageUtil.digest(this.mainPackage);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get source file from import location
|
|
82
|
+
* @param outputFile
|
|
83
|
+
*/
|
|
84
|
+
getSourceFile(importFile: string): string {
|
|
85
|
+
return this.getFromImport(importFile)?.sourceFile ?? importFile;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Initialize the meta data for a function/class
|
|
90
|
+
* @param cls Class
|
|
91
|
+
* @param `file` Filename
|
|
92
|
+
* @param `hash` Hash of class contents
|
|
93
|
+
* @param `methods` Methods and their hashes
|
|
94
|
+
* @param `abstract` Is the class abstract
|
|
95
|
+
*/
|
|
96
|
+
registerFunction(cls: Function, fileOrImport: string, hash: number, methods?: Record<string, { hash: number }>, abstract?: boolean, synthetic?: boolean): boolean {
|
|
97
|
+
const source = this.getSourceFile(fileOrImport);
|
|
98
|
+
const id = this.getId(source, cls.name);
|
|
99
|
+
Object.defineProperty(cls, 'Ⲑid', { value: id });
|
|
100
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
101
|
+
(cls as unknown as Metadated)[METADATA] = { id, source, hash, methods, abstract, synthetic };
|
|
102
|
+
this.#metadata.set(id, { id, source, hash, methods, abstract, synthetic });
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Retrieve function metadata by function, or function id
|
|
108
|
+
*/
|
|
109
|
+
getFunctionMetadataFromClass(cls: Function | undefined): FunctionMetadata | undefined {
|
|
110
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
111
|
+
return (cls as unknown as Metadated)?.[METADATA];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Retrieve function metadata by function, or function id
|
|
116
|
+
*/
|
|
117
|
+
getFunctionMetadata(clsId: string | Function): FunctionMetadata | undefined {
|
|
118
|
+
const id = clsId === undefined ? '' : typeof clsId === 'string' ? clsId : clsId.Ⲑid;
|
|
119
|
+
return this.#metadata.get(id);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
let index: $RootIndex | undefined;
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
index = new $RootIndex(process.env.TRV_MANIFEST!);
|
|
127
|
+
} catch (err) {
|
|
128
|
+
if (/prod/i.test(process.env.NODE_ENV ?? '')) {
|
|
129
|
+
throw err;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export const RootIndex: $RootIndex = index!;
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
export type ManifestModuleFileType = 'typings' | 'ts' | 'js' | 'json' | 'package-json' | 'unknown' | 'fixture' | 'md';
|
|
2
|
+
export type ManifestModuleFolderType =
|
|
3
|
+
'$root' | '$index' | '$package' |
|
|
4
|
+
'src' | 'bin' | 'support' | 'resources' | 'test' | 'doc' |
|
|
5
|
+
'test/fixtures' | 'support/fixtures' | 'support/resources' |
|
|
6
|
+
'$other' | '$transformer';
|
|
7
|
+
|
|
8
|
+
export type ManifestProfile = 'compile' | 'test' | 'doc' | 'build' | 'std';
|
|
9
|
+
export type PackageRel = 'dev' | 'prod' | 'peer' | 'opt' | 'root' | 'global';
|
|
10
|
+
|
|
11
|
+
export type ManifestModuleFile = [string, ManifestModuleFileType, number] | [string, ManifestModuleFileType, number, ManifestProfile];
|
|
12
|
+
export type ManifestModuleCore = {
|
|
13
|
+
name: string;
|
|
14
|
+
main?: boolean;
|
|
15
|
+
local?: boolean;
|
|
16
|
+
version: string;
|
|
17
|
+
sourceFolder: string;
|
|
18
|
+
outputFolder: string;
|
|
19
|
+
profiles: ManifestProfile[];
|
|
20
|
+
parents: string[];
|
|
21
|
+
internal?: boolean;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type ManifestModule = ManifestModuleCore & {
|
|
25
|
+
files: Partial<Record<ManifestModuleFolderType, ManifestModuleFile[]>>;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type ManifestContext = {
|
|
29
|
+
mainModule: string;
|
|
30
|
+
mainFolder: string;
|
|
31
|
+
workspacePath: string;
|
|
32
|
+
outputFolder: string;
|
|
33
|
+
toolFolder: string;
|
|
34
|
+
compilerFolder: string;
|
|
35
|
+
monoRepo?: boolean;
|
|
36
|
+
moduleType: 'module' | 'commonjs';
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type ManifestRoot = ManifestContext & {
|
|
40
|
+
generated: number;
|
|
41
|
+
modules: Record<string, ManifestModule>;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type Package = {
|
|
45
|
+
name: string;
|
|
46
|
+
type?: 'module' | 'commonjs';
|
|
47
|
+
version: string;
|
|
48
|
+
description?: string;
|
|
49
|
+
license?: string;
|
|
50
|
+
repository?: {
|
|
51
|
+
url: string;
|
|
52
|
+
directory?: string;
|
|
53
|
+
};
|
|
54
|
+
author?: {
|
|
55
|
+
email?: string;
|
|
56
|
+
name?: string;
|
|
57
|
+
};
|
|
58
|
+
main: string;
|
|
59
|
+
homepage?: string;
|
|
60
|
+
files?: string[];
|
|
61
|
+
bin?: Record<string, string>;
|
|
62
|
+
scripts?: Record<string, string>;
|
|
63
|
+
engines?: Record<string, string>;
|
|
64
|
+
keywords?: string[];
|
|
65
|
+
|
|
66
|
+
dependencies?: Record<string, string>;
|
|
67
|
+
devDependencies?: Record<string, string>;
|
|
68
|
+
peerDependencies?: Record<string, string>;
|
|
69
|
+
peerDependenciesMeta?: Record<string, { optional?: boolean }>;
|
|
70
|
+
optionalDependencies?: Record<string, string>;
|
|
71
|
+
travetto?: {
|
|
72
|
+
isolated?: boolean;
|
|
73
|
+
displayName?: string;
|
|
74
|
+
profiles?: ManifestProfile[];
|
|
75
|
+
globalModules?: string[];
|
|
76
|
+
mainSource?: string[];
|
|
77
|
+
docOutput?: string[];
|
|
78
|
+
docRoot?: string;
|
|
79
|
+
docBaseUrl?: string;
|
|
80
|
+
docOutputs?: string[];
|
|
81
|
+
outputFolder?: string;
|
|
82
|
+
};
|
|
83
|
+
workspaces?: string[];
|
|
84
|
+
private?: boolean;
|
|
85
|
+
publishConfig?: { access?: 'restricted' | 'public' };
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export type PackageDigestField = 'name' | 'main' | 'author' | 'license' | 'version';
|
|
89
|
+
export type PackageDigest = Pick<Package, PackageDigestField> & { framework: string };
|
|
90
|
+
|
|
91
|
+
type OrProm<T> = T | Promise<T>;
|
|
92
|
+
|
|
93
|
+
export type PackageVisitReq<T> = { pkg: Package, rel: PackageRel, sourcePath: string, parent?: T };
|
|
94
|
+
export type PackageVisitor<T> = {
|
|
95
|
+
init?(req: PackageVisitReq<T>): OrProm<undefined | void | PackageVisitReq<T>[]>;
|
|
96
|
+
valid?(req: PackageVisitReq<T>): boolean;
|
|
97
|
+
create(req: PackageVisitReq<T>): OrProm<T>;
|
|
98
|
+
visit?(req: PackageVisitReq<T>, item: T): OrProm<void>;
|
|
99
|
+
complete?(values: Set<T>): OrProm<Set<T> | undefined>;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export type PackageWorkspaceEntry = { name: string, sourcePath: string };
|
|
103
|
+
|
|
104
|
+
export type FunctionMetadata = {
|
|
105
|
+
id: string;
|
|
106
|
+
source: string;
|
|
107
|
+
hash?: number;
|
|
108
|
+
methods?: Record<string, { hash: number }>;
|
|
109
|
+
synthetic?: boolean;
|
|
110
|
+
abstract?: boolean;
|
|
111
|
+
};
|
package/src/typings.d.ts
ADDED
package/src/util.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
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
|
+
const MANIFEST_FILE = 'manifest.json';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Manifest utils
|
|
13
|
+
*/
|
|
14
|
+
export class ManifestUtil {
|
|
15
|
+
/**
|
|
16
|
+
* Write file and copy over when ready
|
|
17
|
+
*/
|
|
18
|
+
static async #writeJsonWithBuffer(ctx: ManifestContext, filename: string, obj: object): Promise<string> {
|
|
19
|
+
const tempName = path.resolve(ctx.workspacePath, ctx.mainFolder, filename).replace(/[\/\\: ]/g, '_');
|
|
20
|
+
const file = path.resolve(ctx.workspacePath, ctx.outputFolder, 'node_modules', ctx.mainModule, filename);
|
|
21
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
22
|
+
const temp = path.resolve(os.tmpdir(), `${tempName}.${Date.now()}`);
|
|
23
|
+
await fs.writeFile(temp, JSON.stringify(obj), 'utf8');
|
|
24
|
+
await fs.copyFile(temp, file);
|
|
25
|
+
fs.unlink(temp);
|
|
26
|
+
return file;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Build a manifest context
|
|
31
|
+
* @param folder
|
|
32
|
+
*/
|
|
33
|
+
static async buildContext(folder?: string): Promise<ManifestContext> {
|
|
34
|
+
const { getManifestContext } = await import('../bin/context.js');
|
|
35
|
+
return getManifestContext(folder);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Produce manifest in memory
|
|
40
|
+
*/
|
|
41
|
+
static async buildManifest(ctx: ManifestContext): Promise<ManifestRoot> {
|
|
42
|
+
return {
|
|
43
|
+
modules: await ManifestModuleUtil.produceModules(ctx),
|
|
44
|
+
generated: Date.now(),
|
|
45
|
+
...ctx
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Produce a production manifest from a given manifest
|
|
51
|
+
*/
|
|
52
|
+
static createProductionManifest(manifest: ManifestRoot): ManifestRoot {
|
|
53
|
+
return {
|
|
54
|
+
...manifest,
|
|
55
|
+
// If in prod mode, only include std modules
|
|
56
|
+
modules: Object.fromEntries(
|
|
57
|
+
Object.values(manifest.modules)
|
|
58
|
+
.filter(x => x.profiles.includes('std'))
|
|
59
|
+
.map(m => [m.name, m])
|
|
60
|
+
),
|
|
61
|
+
// Mark output folder/workspace path as portable
|
|
62
|
+
outputFolder: '',
|
|
63
|
+
workspacePath: '',
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Read manifest, synchronously
|
|
69
|
+
*
|
|
70
|
+
* @param file
|
|
71
|
+
* @returns
|
|
72
|
+
*/
|
|
73
|
+
static readManifestSync(file: string): { manifest: ManifestRoot, file: string } {
|
|
74
|
+
file = path.resolve(file);
|
|
75
|
+
if (!file.endsWith('.json')) {
|
|
76
|
+
file = path.resolve(file, MANIFEST_FILE);
|
|
77
|
+
}
|
|
78
|
+
const manifest: ManifestRoot = JSON.parse(readFileSync(file, 'utf8'));
|
|
79
|
+
if (!manifest.outputFolder) {
|
|
80
|
+
manifest.outputFolder = path.cwd();
|
|
81
|
+
manifest.workspacePath = path.cwd();
|
|
82
|
+
}
|
|
83
|
+
return { manifest, file };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Write manifest for a given context, return location
|
|
88
|
+
*/
|
|
89
|
+
static writeManifest(ctx: ManifestContext, manifest: ManifestRoot): Promise<string> {
|
|
90
|
+
return this.#writeJsonWithBuffer(ctx, MANIFEST_FILE, manifest);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Write a manifest to a specific file, if no file extension provided, the file is assumed to be a folder
|
|
95
|
+
*/
|
|
96
|
+
static async writeManifestToFile(location: string, manifest: ManifestRoot): Promise<string> {
|
|
97
|
+
if (!location.endsWith('.json')) {
|
|
98
|
+
location = path.resolve(location, MANIFEST_FILE);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
await fs.mkdir(path.dirname(location), { recursive: true });
|
|
102
|
+
await fs.writeFile(location, JSON.stringify(manifest), 'utf8');
|
|
103
|
+
|
|
104
|
+
return location;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Rewrite manifest for a given folder
|
|
109
|
+
*/
|
|
110
|
+
static async rewriteManifest(source: string): Promise<void> {
|
|
111
|
+
const subCtx = await this.buildContext(source);
|
|
112
|
+
const subManifest = await this.buildManifest(subCtx);
|
|
113
|
+
await this.writeManifest(subCtx, subManifest);
|
|
114
|
+
}
|
|
115
|
+
}
|
package/src/watch.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import { path } from './path';
|
|
3
|
+
|
|
4
|
+
export type WatchEvent = { action: 'create' | 'update' | 'delete', file: string };
|
|
5
|
+
|
|
6
|
+
export type WatchEventListener = (ev: WatchEvent, folder: string) => void;
|
|
7
|
+
type EventFilter = (ev: WatchEvent) => boolean;
|
|
8
|
+
type WatchConfig = { filter?: EventFilter, ignore?: string[], createMissing?: boolean };
|
|
9
|
+
|
|
10
|
+
async function getWatcher(): Promise<typeof import('@parcel/watcher')> {
|
|
11
|
+
try {
|
|
12
|
+
return await import('@parcel/watcher');
|
|
13
|
+
} catch (err) {
|
|
14
|
+
console.error('@parcel/watcher must be installed to use watching functionality');
|
|
15
|
+
throw err;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Leverages @parcel/watcher to watch a series of folders
|
|
21
|
+
* @param folders
|
|
22
|
+
* @param onEvent
|
|
23
|
+
* @private
|
|
24
|
+
*/
|
|
25
|
+
export async function watchFolders(folders: string[], onEvent: WatchEventListener, config: WatchConfig = {}): Promise<() => Promise<void>> {
|
|
26
|
+
const lib = await getWatcher();
|
|
27
|
+
const createMissing = config.createMissing ?? false;
|
|
28
|
+
const validFolders = new Set(folders);
|
|
29
|
+
|
|
30
|
+
const subs = await Promise.all(folders.map(async folder => {
|
|
31
|
+
if (await fs.stat(folder).then(() => true, () => createMissing)) {
|
|
32
|
+
await fs.mkdir(folder, { recursive: true });
|
|
33
|
+
const ignore = (await fs.readdir(folder)).filter(x => x.startsWith('.') && x.length > 2);
|
|
34
|
+
return lib.subscribe(folder, (err, events) => {
|
|
35
|
+
for (const ev of events) {
|
|
36
|
+
if (ev.type === 'delete' && validFolders.has(path.toPosix(ev.path))) {
|
|
37
|
+
return process.exit(0); // Exit when watched folder is removed
|
|
38
|
+
}
|
|
39
|
+
const finalEv = { action: ev.type, file: ev.path };
|
|
40
|
+
if (!config.filter || config.filter(finalEv)) {
|
|
41
|
+
onEvent(finalEv, folder);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}, { ignore: [...ignore, ...config.ignore ?? []] });
|
|
45
|
+
}
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
// Allow for multiple calls
|
|
49
|
+
let finalProm: Promise<void> | undefined;
|
|
50
|
+
const remove = (): Promise<void> => finalProm ??= Promise.all(subs.map(x => x?.unsubscribe())).then(() => { });
|
|
51
|
+
|
|
52
|
+
// Cleanup on exit
|
|
53
|
+
process.on('exit', remove);
|
|
54
|
+
|
|
55
|
+
return remove;
|
|
56
|
+
}
|