@vorplex/compiler 0.0.8 → 0.0.11
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/dist/compiler.util.d.ts +14 -0
- package/dist/compiler.util.js +88 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +8 -0
- package/dist/module-loader/bundler.d.ts +20 -0
- package/dist/module-loader/bundler.js +106 -0
- package/dist/module-loader/module-loader.util.d.ts +10 -0
- package/dist/module-loader/module-loader.util.js +60 -0
- package/dist/npm/jsdelivr/jsdelivr.util.d.ts +37 -0
- package/dist/npm/jsdelivr/jsdelivr.util.js +156 -0
- package/dist/npm/npm.util.d.ts +24 -0
- package/dist/npm/npm.util.js +132 -0
- package/dist/npm/package-json.type.d.ts +19 -0
- package/dist/npm/package-json.type.js +0 -0
- package/dist/script-analyzer.util.d.ts +3 -0
- package/dist/script-analyzer.util.js +26 -0
- package/package.json +3 -3
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Task } from '@vorplex/core';
|
|
2
|
+
import { type DependencyTree } from './npm/npm.util';
|
|
3
|
+
export interface CompilerOptions {
|
|
4
|
+
files: Record<string, {
|
|
5
|
+
content: string;
|
|
6
|
+
dependencyTree?: DependencyTree;
|
|
7
|
+
}>;
|
|
8
|
+
entryFilePath: string;
|
|
9
|
+
sourcemaps?: boolean;
|
|
10
|
+
task?: Task;
|
|
11
|
+
}
|
|
12
|
+
export declare class Compiler {
|
|
13
|
+
static compile(options: CompilerOptions): Promise<string>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Task } from '@vorplex/core';
|
|
2
|
+
import { stringify } from 'yaml';
|
|
3
|
+
import { Bundler } from './module-loader/bundler';
|
|
4
|
+
import { JsDelivr } from './npm/jsdelivr/jsdelivr.util';
|
|
5
|
+
import { NPM } from './npm/npm.util';
|
|
6
|
+
export class Compiler {
|
|
7
|
+
static async compile(options) {
|
|
8
|
+
const task = options.task ?? new Task('Compile');
|
|
9
|
+
try {
|
|
10
|
+
task.log(`Compiling $[files] using entry file path (${options.entryFilePath})`, { attachments: { files: { type: 'yaml', value: stringify(options.files) } } });
|
|
11
|
+
return await Bundler.bundle({
|
|
12
|
+
path: options.entryFilePath,
|
|
13
|
+
script: options.files[options.entryFilePath].content,
|
|
14
|
+
sourcemaps: options.sourcemaps,
|
|
15
|
+
resolve: async ({ importerPath, importPath, importStack }) => {
|
|
16
|
+
return await task.do(`Resolving: ${importPath}`, async (task) => {
|
|
17
|
+
if (importPath in options.files) {
|
|
18
|
+
task.log('Returning local file $[content]', { attachments: { content: { type: 'typescript', value: options.files[importPath].content } } });
|
|
19
|
+
return { content: options.files[importPath].content };
|
|
20
|
+
}
|
|
21
|
+
if (importPath.startsWith('https://')) {
|
|
22
|
+
if (importPath.startsWith(JsDelivr.url)) {
|
|
23
|
+
const { name, version, path } = JsDelivr.parseUrl(importPath);
|
|
24
|
+
try {
|
|
25
|
+
const file = await JsDelivr.getFile(name, version, path);
|
|
26
|
+
task.log('Returning file $[content] from CDN', { attachments: { content: { type: 'text', value: file.content } } });
|
|
27
|
+
return {
|
|
28
|
+
path: file.url,
|
|
29
|
+
content: file.content,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
task.fail(error);
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
task.log('Fetching external file content');
|
|
39
|
+
const response = await fetch(importPath);
|
|
40
|
+
if (!response.ok)
|
|
41
|
+
throw new Error(`Failed to resolve import (${importPath}). ${response.statusText}`);
|
|
42
|
+
const content = await response.text();
|
|
43
|
+
task.log('Returning external file $[content]', { attachments: { content: { type: 'text', value: content } } });
|
|
44
|
+
return { content };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const dependencyTree = options.files[importStack.find(importPath => options.files[importPath]?.dependencyTree)]?.dependencyTree ?? options.files[importPath].dependencyTree ?? {};
|
|
49
|
+
const string = NPM.parseImportString(importPath);
|
|
50
|
+
task.log(`Package ${string.packageName} subpath ${string.subpath} version ${string.version}`);
|
|
51
|
+
if (!string.version) {
|
|
52
|
+
if (importerPath in options.files) {
|
|
53
|
+
string.version = options.files[importerPath].dependencyTree[string.packageName]?.version;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
const importer = JsDelivr.parseUrl(importerPath);
|
|
57
|
+
string.version = importer.name !== string.packageName ? NPM.resolveDependencyVersion(dependencyTree, importer.name, importer.version, string.packageName) : importer.version;
|
|
58
|
+
if (!string.version) {
|
|
59
|
+
task.log(`Returning mock module for package (${string.packageName}) as import (${importer.name}) doesn't have it as a dependency.`, { level: 'warning' });
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (!string.version)
|
|
65
|
+
throw new Error(`Unable to determine package (${string.packageName}) version.`);
|
|
66
|
+
const filePath = await JsDelivr.resolveImportFilePath(string.packageName, string.version, string.subpath);
|
|
67
|
+
task.log(`Import resolved to file path (${filePath})`);
|
|
68
|
+
const file = await JsDelivr.getFile(string.packageName, string.version, filePath);
|
|
69
|
+
task.log('Returning file $[content] from CDN', { attachments: { content: { type: 'text', value: file.content } } });
|
|
70
|
+
return { path: file.url, content: file.content };
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
task.fail(error);
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
task.fail(error);
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
task.complete();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from './compiler.util';
|
|
2
|
+
export * from './script-analyzer.util';
|
|
3
|
+
export * from './module-loader/bundler';
|
|
4
|
+
export * from './module-loader/module-loader.util';
|
|
5
|
+
export * from './npm/npm.util';
|
|
6
|
+
export * from './npm/package-json.type';
|
|
7
|
+
export * from './npm/jsdelivr/jsdelivr.util';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// **/*.ts
|
|
2
|
+
export * from './compiler.util';
|
|
3
|
+
export * from './script-analyzer.util';
|
|
4
|
+
export * from './module-loader/bundler';
|
|
5
|
+
export * from './module-loader/module-loader.util';
|
|
6
|
+
export * from './npm/npm.util';
|
|
7
|
+
export * from './npm/package-json.type';
|
|
8
|
+
export * from './npm/jsdelivr/jsdelivr.util';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export declare class Bundler {
|
|
2
|
+
private static initialize;
|
|
3
|
+
static bundle(options: {
|
|
4
|
+
path: string;
|
|
5
|
+
script: string;
|
|
6
|
+
sourcemaps?: boolean;
|
|
7
|
+
resolve: (event: {
|
|
8
|
+
namespace: string;
|
|
9
|
+
importerPath: string;
|
|
10
|
+
importPath: string;
|
|
11
|
+
relative: boolean;
|
|
12
|
+
importStack: string[];
|
|
13
|
+
}) => Promise<{
|
|
14
|
+
namespace: string;
|
|
15
|
+
} | {
|
|
16
|
+
path?: string;
|
|
17
|
+
content: string;
|
|
18
|
+
}>;
|
|
19
|
+
}): Promise<string>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { $Path, State } from '@vorplex/core';
|
|
2
|
+
import { build, initialize } from 'esbuild-wasm';
|
|
3
|
+
import { JsDelivr } from '../npm/jsdelivr/jsdelivr.util';
|
|
4
|
+
export class Bundler {
|
|
5
|
+
static async initialize() {
|
|
6
|
+
const initialized = (Bundler.initialize[':state'] ??= new State());
|
|
7
|
+
if (initialized.value === undefined) {
|
|
8
|
+
initialized.update('initializing');
|
|
9
|
+
try {
|
|
10
|
+
await initialize({
|
|
11
|
+
wasmURL: $Path.join(JsDelivr.url, 'esbuild-wasm@0.25.12', 'esbuild.wasm'),
|
|
12
|
+
});
|
|
13
|
+
initialized.update('initialized');
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
initialized.update(undefined);
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
await initialized.until((initialized) => initialized.value === 'initialized');
|
|
21
|
+
}
|
|
22
|
+
static async bundle(options) {
|
|
23
|
+
await Bundler.initialize();
|
|
24
|
+
const result = await build({
|
|
25
|
+
stdin: {
|
|
26
|
+
contents: options.script,
|
|
27
|
+
resolveDir: '/',
|
|
28
|
+
loader: 'ts',
|
|
29
|
+
},
|
|
30
|
+
plugins: [
|
|
31
|
+
{
|
|
32
|
+
name: 'module-loader-resolver',
|
|
33
|
+
setup: (build) => {
|
|
34
|
+
const files = {};
|
|
35
|
+
const importParent = {};
|
|
36
|
+
const importPathAlias = {};
|
|
37
|
+
build.onResolve({ filter: /.*/ }, async (args) => {
|
|
38
|
+
const relative = args.path.startsWith('.') || args.path.startsWith('/');
|
|
39
|
+
const importerPath = args.importer === '<stdin>' ? options.path : args.importer;
|
|
40
|
+
const importPath = relative ? $Path.join(importerPath, `../${args.path}`) : args.path;
|
|
41
|
+
importParent[importPath] = importerPath;
|
|
42
|
+
let importStack = [];
|
|
43
|
+
let parentPath = importerPath;
|
|
44
|
+
while (parentPath) {
|
|
45
|
+
importStack.push(parentPath);
|
|
46
|
+
parentPath = importParent[importPathAlias[parentPath]] ?? importParent[parentPath];
|
|
47
|
+
}
|
|
48
|
+
const resolved = await options.resolve({
|
|
49
|
+
namespace: args.path,
|
|
50
|
+
importerPath,
|
|
51
|
+
importPath,
|
|
52
|
+
relative,
|
|
53
|
+
importStack
|
|
54
|
+
});
|
|
55
|
+
if (!resolved)
|
|
56
|
+
return { path: importPath, namespace: 'mock' };
|
|
57
|
+
if ('content' in resolved) {
|
|
58
|
+
files[resolved.path ?? importPath] = resolved.content;
|
|
59
|
+
if (resolved.path)
|
|
60
|
+
importPathAlias[resolved.path] = importPath;
|
|
61
|
+
return {
|
|
62
|
+
path: resolved.path ?? importPath,
|
|
63
|
+
namespace: 'virtual',
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
else
|
|
67
|
+
return {
|
|
68
|
+
path: resolved.namespace,
|
|
69
|
+
external: true,
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
build.onLoad({ filter: /.*/, namespace: 'virtual' }, (args) => {
|
|
73
|
+
const loaders = {
|
|
74
|
+
'.tsx': 'tsx',
|
|
75
|
+
'.ts': 'ts',
|
|
76
|
+
'.js': 'js',
|
|
77
|
+
'.jsx': 'jsx',
|
|
78
|
+
'.css': 'css',
|
|
79
|
+
'.json': 'json',
|
|
80
|
+
};
|
|
81
|
+
const extension = $Path.getExtension(args.path);
|
|
82
|
+
const loader = loaders[extension];
|
|
83
|
+
const file = files[args.path];
|
|
84
|
+
delete files[args.path];
|
|
85
|
+
return { contents: file, loader };
|
|
86
|
+
});
|
|
87
|
+
build.onLoad({ filter: /.*/, namespace: 'mock' }, (args) => {
|
|
88
|
+
return { contents: 'export default {}' };
|
|
89
|
+
});
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
sourcemap: options.sourcemaps ? 'inline' : false,
|
|
94
|
+
mainFields: ['module', 'main'],
|
|
95
|
+
conditions: ['import', 'module'],
|
|
96
|
+
bundle: true,
|
|
97
|
+
format: 'cjs',
|
|
98
|
+
target: 'esnext',
|
|
99
|
+
platform: 'browser',
|
|
100
|
+
minify: !options.sourcemaps,
|
|
101
|
+
treeShaking: !options.sourcemaps,
|
|
102
|
+
logLevel: 'silent',
|
|
103
|
+
});
|
|
104
|
+
return result.outputFiles[0].text;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare class ModuleLoader {
|
|
2
|
+
static resolver?: (namespace: string) => any;
|
|
3
|
+
static registry: Record<string, Record<string, any>>;
|
|
4
|
+
static evaluate(code: string, context?: Record<string, any>): Record<string, any>;
|
|
5
|
+
static evaluateAsync(code: string, context?: Record<string, any>): Promise<Record<string, any>>;
|
|
6
|
+
static import(bundle: string): Promise<Record<string, any>>;
|
|
7
|
+
static resolve(namespace: string): any;
|
|
8
|
+
static registerModule(name: string, version: string, module: any): void;
|
|
9
|
+
static registerBundle(name: string, version: string, bundle: string): void;
|
|
10
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { $String } from '@vorplex/core';
|
|
2
|
+
import { maxSatisfying } from 'semver';
|
|
3
|
+
import { NPM } from '../npm/npm.util';
|
|
4
|
+
export class ModuleLoader {
|
|
5
|
+
static resolver;
|
|
6
|
+
static registry = {};
|
|
7
|
+
static evaluate(code, context = {}) {
|
|
8
|
+
if (!code)
|
|
9
|
+
return null;
|
|
10
|
+
const func = new Function('context', 'module', `with (context) {${code}}`);
|
|
11
|
+
const module = { exports: {} };
|
|
12
|
+
const require = (namespace) => ModuleLoader.resolve(namespace);
|
|
13
|
+
const define = (deps, factory) => {
|
|
14
|
+
factory(require, module.exports);
|
|
15
|
+
};
|
|
16
|
+
func.call(context, { require, define, exports: module.exports, ...context }, module);
|
|
17
|
+
return module.exports;
|
|
18
|
+
}
|
|
19
|
+
static async evaluateAsync(code, context = {}) {
|
|
20
|
+
code = `return (async function() {${code}})();`;
|
|
21
|
+
return ModuleLoader.evaluate(code, context);
|
|
22
|
+
}
|
|
23
|
+
static async import(bundle) {
|
|
24
|
+
const blob = new Blob([bundle], { type: 'application/javascript' });
|
|
25
|
+
const url = URL.createObjectURL(blob);
|
|
26
|
+
try {
|
|
27
|
+
return await import(url);
|
|
28
|
+
}
|
|
29
|
+
finally {
|
|
30
|
+
URL.revokeObjectURL(url);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
static resolve(namespace) {
|
|
34
|
+
const string = NPM.parseImportString(namespace);
|
|
35
|
+
if (ModuleLoader.resolver) {
|
|
36
|
+
const module = ModuleLoader.resolver(namespace);
|
|
37
|
+
if (module) {
|
|
38
|
+
ModuleLoader.registerModule(string.packageName, string.version ?? '<default>', module);
|
|
39
|
+
return module;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const versions = ModuleLoader.registry[string.packageName];
|
|
43
|
+
if (versions) {
|
|
44
|
+
if (versions) {
|
|
45
|
+
const version = versions[string.version] ?? versions['latest'] ?? versions[maxSatisfying(Object.keys(versions), '*')] ?? versions['<default>'];
|
|
46
|
+
const subpath = string.subpath ? `${$String.toAlphanumeric(string.packageName, '_')}__${$String.toAlphanumeric(string.subpath, '_')}` : null;
|
|
47
|
+
if (version)
|
|
48
|
+
return subpath ? version[subpath] : version;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
throw new Error(`No package was registered for import (${namespace})`);
|
|
52
|
+
}
|
|
53
|
+
static registerModule(name, version, module) {
|
|
54
|
+
ModuleLoader.registry[name] = Object.assign({}, ModuleLoader.registry[name], { [version]: module });
|
|
55
|
+
}
|
|
56
|
+
static registerBundle(name, version, bundle) {
|
|
57
|
+
const module = ModuleLoader.evaluate(bundle);
|
|
58
|
+
ModuleLoader.registerModule(name, version, module);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { PackageJson } from '../package-json.type';
|
|
2
|
+
export interface JsDelivrData {
|
|
3
|
+
name: string;
|
|
4
|
+
version: string;
|
|
5
|
+
default: string;
|
|
6
|
+
files: JsDelivrFileEntry[];
|
|
7
|
+
}
|
|
8
|
+
export interface JsDelivrFileEntry {
|
|
9
|
+
type: 'directory' | 'file';
|
|
10
|
+
name: string;
|
|
11
|
+
files: JsDelivrFileEntry[];
|
|
12
|
+
}
|
|
13
|
+
export declare class JsDelivr {
|
|
14
|
+
static readonly url: "https://cdn.jsdelivr.net/npm";
|
|
15
|
+
static readonly dataUrl: "https://data.jsdelivr.com/v1/packages/npm";
|
|
16
|
+
static readonly resolveUrl: "https://data.jsdelivr.com/v1/package/resolve/npm";
|
|
17
|
+
static packageVersionCache: Record<string, Record<string, string>>;
|
|
18
|
+
static resolveVersion(name: string, semanticVersion?: string): Promise<string>;
|
|
19
|
+
static getPackageVersions(name: string): Promise<string[]>;
|
|
20
|
+
static getData(name: string, semanticVersion: string): Promise<JsDelivrData>;
|
|
21
|
+
static getFilePaths(name: string, semanticVersion: string, regex?: RegExp): Promise<string[]>;
|
|
22
|
+
static resolveFilePath(name: string, semanticVersion: string, path: string): Promise<string>;
|
|
23
|
+
static getFile(name: string, semanticVersion: string, path?: string): Promise<{
|
|
24
|
+
packageName: string;
|
|
25
|
+
packageVersion: string;
|
|
26
|
+
url: string;
|
|
27
|
+
path?: string;
|
|
28
|
+
content: string;
|
|
29
|
+
}>;
|
|
30
|
+
static getPackageJson(name: string, semanticVersion: string): Promise<PackageJson>;
|
|
31
|
+
static parseUrl(url: string): {
|
|
32
|
+
name: string;
|
|
33
|
+
version: string;
|
|
34
|
+
path?: string;
|
|
35
|
+
};
|
|
36
|
+
static resolveImportFilePath(packageName: string, semanticVersion: string, subpath?: string): Promise<string>;
|
|
37
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { $Path, $String } from '@vorplex/core';
|
|
2
|
+
export class JsDelivr {
|
|
3
|
+
static url = 'https://cdn.jsdelivr.net/npm';
|
|
4
|
+
static dataUrl = 'https://data.jsdelivr.com/v1/packages/npm';
|
|
5
|
+
static resolveUrl = 'https://data.jsdelivr.com/v1/package/resolve/npm';
|
|
6
|
+
static packageVersionCache = {};
|
|
7
|
+
static async resolveVersion(name, semanticVersion) {
|
|
8
|
+
semanticVersion ??= 'latest';
|
|
9
|
+
if (this.packageVersionCache[name]?.[semanticVersion])
|
|
10
|
+
return this.packageVersionCache[name][semanticVersion];
|
|
11
|
+
const url = $Path.join(this.resolveUrl, `${name}@${semanticVersion}`);
|
|
12
|
+
const response = await fetch(url);
|
|
13
|
+
if (!response.ok)
|
|
14
|
+
throw new Error(`Failed to fetch package (${name}) version (${semanticVersion}). ${response.statusText}`);
|
|
15
|
+
const data = (await response.json());
|
|
16
|
+
this.packageVersionCache[name] = Object.assign({}, this.packageVersionCache[name], { [semanticVersion]: data.version });
|
|
17
|
+
return data.version;
|
|
18
|
+
}
|
|
19
|
+
static async getPackageVersions(name) {
|
|
20
|
+
const url = $Path.join(this.dataUrl, name);
|
|
21
|
+
const response = await fetch(url);
|
|
22
|
+
if (!response.ok)
|
|
23
|
+
throw new Error(`Failed to fetch package (${name}) versions. ${response.statusText}`);
|
|
24
|
+
const data = (await response.json());
|
|
25
|
+
return data.versions.map((version) => version.version);
|
|
26
|
+
}
|
|
27
|
+
static async getData(name, semanticVersion) {
|
|
28
|
+
const version = await this.resolveVersion(name, semanticVersion);
|
|
29
|
+
const url = $Path.join(this.dataUrl, `${name}@${version}`);
|
|
30
|
+
const response = await fetch(url);
|
|
31
|
+
if (!response.ok)
|
|
32
|
+
throw new Error(`Failed to fetch package (${name}) metadata. ${response.statusText}`);
|
|
33
|
+
return await response.json();
|
|
34
|
+
}
|
|
35
|
+
static async getFilePaths(name, semanticVersion, regex) {
|
|
36
|
+
const version = await this.resolveVersion(name, semanticVersion);
|
|
37
|
+
const metadata = await this.getData(name, version);
|
|
38
|
+
function getFiles(target, path) {
|
|
39
|
+
const files = [];
|
|
40
|
+
for (const entry of target.files) {
|
|
41
|
+
const entryPath = $Path.join(path, entry.name);
|
|
42
|
+
if (entry.type === 'directory')
|
|
43
|
+
files.push(...getFiles(entry, entryPath));
|
|
44
|
+
else if (entryPath.match(regex))
|
|
45
|
+
files.push(entryPath);
|
|
46
|
+
}
|
|
47
|
+
return files;
|
|
48
|
+
}
|
|
49
|
+
return getFiles(metadata, '/');
|
|
50
|
+
}
|
|
51
|
+
static async resolveFilePath(name, semanticVersion, path) {
|
|
52
|
+
const paths = await this.getFilePaths(name, semanticVersion, new RegExp('^' + $String.sanitizeForRegex($Path.absolute(path)) + '(?:\\.js|/index.js)?$'));
|
|
53
|
+
function getPathPriority(filePath) {
|
|
54
|
+
if (filePath === path)
|
|
55
|
+
return 4;
|
|
56
|
+
if (filePath === path + '.js')
|
|
57
|
+
return 3;
|
|
58
|
+
if (filePath === path + '/index.js')
|
|
59
|
+
return 2;
|
|
60
|
+
return 1;
|
|
61
|
+
}
|
|
62
|
+
return paths.sort((a, b) => getPathPriority(b) - getPathPriority(a))[0];
|
|
63
|
+
}
|
|
64
|
+
static async getFile(name, semanticVersion, path) {
|
|
65
|
+
const resolvedPath = path ? await this.resolveFilePath(name, semanticVersion, path) : null;
|
|
66
|
+
if (path && !resolvedPath)
|
|
67
|
+
throw new Error(`Failed to resolve path (${path}) from package (${name}) version (${semanticVersion}). Not Found.`);
|
|
68
|
+
const url = $Path.join(this.url, `${name}@${semanticVersion ?? 'latest'}`, resolvedPath);
|
|
69
|
+
const response = await fetch(url);
|
|
70
|
+
if (!response.ok)
|
|
71
|
+
throw new Error(`Failed to fetch package (${name}) file (${resolvedPath ?? '<default>'}). ${response.statusText}`);
|
|
72
|
+
const version = response.headers.get('x-jsd-version');
|
|
73
|
+
return {
|
|
74
|
+
packageName: name,
|
|
75
|
+
packageVersion: version,
|
|
76
|
+
url: $Path.join(this.url, `${name}@${version}`, resolvedPath),
|
|
77
|
+
path: resolvedPath,
|
|
78
|
+
content: await response.text(),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
static async getPackageJson(name, semanticVersion) {
|
|
82
|
+
const { content } = await this.getFile(name, semanticVersion, 'package.json');
|
|
83
|
+
return JSON.parse(content);
|
|
84
|
+
}
|
|
85
|
+
static parseUrl(url) {
|
|
86
|
+
return url.match(/https:\/\/cdn\.jsdelivr\.net\/npm\/(?<name>@?[^@]+)@(?<version>[^/]+)(?:\/(?<path>.*))?/)?.groups;
|
|
87
|
+
}
|
|
88
|
+
static async resolveImportFilePath(packageName, semanticVersion, subpath) {
|
|
89
|
+
const packageJson = await this.getPackageJson(packageName, semanticVersion);
|
|
90
|
+
if (packageJson.exports) {
|
|
91
|
+
const resolveExports = async (exports) => {
|
|
92
|
+
const result = {};
|
|
93
|
+
if (typeof exports === 'string')
|
|
94
|
+
return { '.': $Path.relative(exports) };
|
|
95
|
+
const resolveExportPath = (path) => {
|
|
96
|
+
if (!path)
|
|
97
|
+
return;
|
|
98
|
+
if (typeof path === 'string')
|
|
99
|
+
return path;
|
|
100
|
+
if (typeof path !== 'object')
|
|
101
|
+
return;
|
|
102
|
+
const priorities = ['browser', 'import', 'es2015', 'module', 'default', 'node', 'require'];
|
|
103
|
+
for (const priority of priorities) {
|
|
104
|
+
const resolvedPath = resolveExportPath(path[priority]);
|
|
105
|
+
if (resolvedPath)
|
|
106
|
+
return resolvedPath;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
for (const [path, value] of Object.entries(exports)) {
|
|
110
|
+
let targetPath = resolveExportPath(value);
|
|
111
|
+
if (!targetPath || targetPath === './')
|
|
112
|
+
continue;
|
|
113
|
+
if (targetPath.startsWith('./'))
|
|
114
|
+
targetPath = targetPath.slice(2);
|
|
115
|
+
if ($Path.getExtension(targetPath) === '.cjs')
|
|
116
|
+
continue;
|
|
117
|
+
if (targetPath.includes('*')) {
|
|
118
|
+
const regex = new RegExp(`^${$String.sanitizeForRegex(targetPath).replace('\\*', '(.+)')}$`);
|
|
119
|
+
const files = await this.getFilePaths(packageName, semanticVersion, regex);
|
|
120
|
+
for (const file of files) {
|
|
121
|
+
const fileExtension = $Path.getExtension(file);
|
|
122
|
+
if (!['.js', '.mjs', '.json', '.jsx'].includes(fileExtension))
|
|
123
|
+
continue;
|
|
124
|
+
const wildcard = file.match(regex)[1];
|
|
125
|
+
const resolvedPath = path.replaceAll('*', wildcard);
|
|
126
|
+
const resolvedTargetPath = targetPath.replaceAll('*', wildcard);
|
|
127
|
+
if (Object.values(result).includes($Path.relative(resolvedTargetPath)))
|
|
128
|
+
continue;
|
|
129
|
+
result[resolvedPath] = $Path.relative(resolvedTargetPath);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
if (Object.values(result).includes($Path.relative(targetPath)))
|
|
134
|
+
continue;
|
|
135
|
+
result[path] = $Path.relative(targetPath);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return result;
|
|
139
|
+
};
|
|
140
|
+
const exports = await resolveExports(packageJson.exports);
|
|
141
|
+
if (!subpath && exports['.'])
|
|
142
|
+
return $Path.absolute(exports['.']);
|
|
143
|
+
if (subpath && exports[$Path.relative(subpath)])
|
|
144
|
+
return $Path.absolute(exports[$Path.relative(subpath)]);
|
|
145
|
+
}
|
|
146
|
+
if (subpath) {
|
|
147
|
+
const file = await this.resolveFilePath(packageName, semanticVersion, subpath);
|
|
148
|
+
if (file)
|
|
149
|
+
return file;
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
return $Path.absolute(packageJson.module || packageJson.main || 'index.js');
|
|
153
|
+
}
|
|
154
|
+
throw new Error(`Failed to resolve file path for import (${packageName}${subpath ? `/${subpath}` : ''}@${semanticVersion})`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Task } from '@vorplex/core';
|
|
2
|
+
import type { PackageJson } from './package-json.type';
|
|
3
|
+
export type DependencyTree = Record<string, DependencyNode>;
|
|
4
|
+
export interface DependencyNode {
|
|
5
|
+
version: string;
|
|
6
|
+
dependencies: Record<string, DependencyNode>;
|
|
7
|
+
}
|
|
8
|
+
export declare class NPM {
|
|
9
|
+
static readonly url: "https://registry.npmjs.org";
|
|
10
|
+
static getData(name: string): Promise<any>;
|
|
11
|
+
static gePackageJson(name: string, version: string): Promise<PackageJson>;
|
|
12
|
+
static resolveDependencyTree(packages: Record<string, string>, resolver: {
|
|
13
|
+
getPackageJson: (name: string, version: string) => Promise<PackageJson>;
|
|
14
|
+
getPackageVersions: (name: string) => Promise<string[]>;
|
|
15
|
+
}, task?: Task): Promise<DependencyTree>;
|
|
16
|
+
static flattenDependencyTree(tree: DependencyTree): Record<string, string[]>;
|
|
17
|
+
static resolveDependencyVersion(tree: DependencyTree, parent: string, version: string, name: string): string;
|
|
18
|
+
static getScriptImports(script: string): string[];
|
|
19
|
+
static parseImportString(string: string): {
|
|
20
|
+
packageName: string;
|
|
21
|
+
subpath?: string;
|
|
22
|
+
version?: string;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { $Path, Task } from '@vorplex/core';
|
|
2
|
+
import { maxSatisfying, satisfies } from 'semver';
|
|
3
|
+
import { stringify } from 'yaml';
|
|
4
|
+
export class NPM {
|
|
5
|
+
static url = 'https://registry.npmjs.org';
|
|
6
|
+
static async getData(name) {
|
|
7
|
+
const response = await fetch($Path.join(NPM.url, name));
|
|
8
|
+
if (!response.ok)
|
|
9
|
+
throw new Error(`Failed to get package (${name}) data. ${response.statusText}`);
|
|
10
|
+
return await response.json();
|
|
11
|
+
}
|
|
12
|
+
static async gePackageJson(name, version) {
|
|
13
|
+
const response = await fetch($Path.join(NPM.url, name, version));
|
|
14
|
+
if (!response.ok)
|
|
15
|
+
throw new Error(`Failed to get package (${name}) data. ${response.statusText}`);
|
|
16
|
+
return await response.json();
|
|
17
|
+
}
|
|
18
|
+
static async resolveDependencyTree(packages, resolver, task = new Task('Resolve Dependency Tree')) {
|
|
19
|
+
try {
|
|
20
|
+
const seen = {};
|
|
21
|
+
async function resolvePackage(name, range, ancestors = [], task) {
|
|
22
|
+
task.log(`Resolving package (${name}@${range})`);
|
|
23
|
+
if (range === 'latest')
|
|
24
|
+
range = '*';
|
|
25
|
+
task.log(`Searching for package in ancestors`);
|
|
26
|
+
for (const ancestor of ancestors) {
|
|
27
|
+
const existing = ancestor.dependencies?.[name];
|
|
28
|
+
if (existing && satisfies(existing.version, range, { includePrerelease: true })) {
|
|
29
|
+
task.log(`Existing package found. Deduping`);
|
|
30
|
+
return existing;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
task.log(`No existing package found in ancestors`);
|
|
34
|
+
task.log('Resolving package versions');
|
|
35
|
+
const available = await resolver.getPackageVersions(name);
|
|
36
|
+
const version = maxSatisfying(available, range, { includePrerelease: true });
|
|
37
|
+
if (!version) {
|
|
38
|
+
throw new Error(`No version found for ${name}@${range}`);
|
|
39
|
+
}
|
|
40
|
+
task.log(`Version (${version}) was resolved for range (${range})`);
|
|
41
|
+
const key = `${name}@${version}`;
|
|
42
|
+
if (seen[key]) {
|
|
43
|
+
task.log(`Reusing globally deduped package`);
|
|
44
|
+
return seen[key];
|
|
45
|
+
}
|
|
46
|
+
task.log(`Resolving package.json`);
|
|
47
|
+
const pkg = await resolver.getPackageJson(name, version);
|
|
48
|
+
task.log(`$[package.json] resolved`, { attachments: { 'package.json': { type: 'yaml', value: stringify(pkg) } } });
|
|
49
|
+
const node = { version, dependencies: {} };
|
|
50
|
+
seen[key] = node;
|
|
51
|
+
if (pkg.dependencies) {
|
|
52
|
+
task.log('Resolving packages dependencies.');
|
|
53
|
+
for (const [depName, depRange] of Object.entries(pkg.dependencies)) {
|
|
54
|
+
await task.do(`${depName}@${depRange}`, async (task) => {
|
|
55
|
+
node.dependencies[depName] = await resolvePackage(depName, depRange, [node, ...ancestors], task);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
task.log('Done resolving packages dependencies.');
|
|
59
|
+
}
|
|
60
|
+
if (pkg.peerDependencies) {
|
|
61
|
+
task.log('Resolving packages peer-dependencies.');
|
|
62
|
+
for (const [peerName, peerRange] of Object.entries(pkg.peerDependencies)) {
|
|
63
|
+
await task.do(`${peerName}@${peerRange}`, async (task) => {
|
|
64
|
+
node.dependencies[peerName] = await resolvePackage(peerName, peerRange, ancestors, task);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
task.log('Done resolving packages peer-dependencies.');
|
|
68
|
+
}
|
|
69
|
+
return node;
|
|
70
|
+
}
|
|
71
|
+
const tree = {};
|
|
72
|
+
task.log('Resolving packages');
|
|
73
|
+
for (const [name, range] of Object.entries(packages)) {
|
|
74
|
+
await task.do(`${name}@${range}`, async (task) => {
|
|
75
|
+
tree[name] = await resolvePackage(name, range, [], task);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
task.log('Done resolving packages');
|
|
79
|
+
task.log('Done resolving dependency tree');
|
|
80
|
+
return tree;
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
task.fail(error);
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
task.complete();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
static flattenDependencyTree(tree) {
|
|
91
|
+
function flatten(tree, visited = new Set()) {
|
|
92
|
+
const flattened = {};
|
|
93
|
+
for (const [name, data] of Object.entries(tree)) {
|
|
94
|
+
const key = `${name}@${data.version}`;
|
|
95
|
+
if (visited.has(key))
|
|
96
|
+
continue;
|
|
97
|
+
visited.add(key);
|
|
98
|
+
flattened[name] = [...(flattened[name] ?? []), data.version];
|
|
99
|
+
if (data.dependencies) {
|
|
100
|
+
for (const [dependency, version] of Object.entries(flatten(data.dependencies, visited))) {
|
|
101
|
+
flattened[dependency] = [...(flattened[dependency] ?? []), ...version];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return flattened;
|
|
106
|
+
}
|
|
107
|
+
return flatten(tree);
|
|
108
|
+
}
|
|
109
|
+
static resolveDependencyVersion(tree, parent, version, name) {
|
|
110
|
+
const resolve = (tree) => {
|
|
111
|
+
for (const [packName, pack] of Object.entries(tree)) {
|
|
112
|
+
if (packName === parent && pack.version === version)
|
|
113
|
+
return pack.dependencies[name]?.version;
|
|
114
|
+
const dependency = resolve(pack.dependencies);
|
|
115
|
+
if (dependency)
|
|
116
|
+
return dependency;
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
return resolve(tree);
|
|
120
|
+
}
|
|
121
|
+
static getScriptImports(script) {
|
|
122
|
+
return Array.from(script.matchAll(/import\s+[\s\S]*?\s+from\s+['"]([^'"]+)['"]|require\(['"]([^'"]+)['"]\)/g)).map((match) => match[1] ?? match[2]);
|
|
123
|
+
}
|
|
124
|
+
static parseImportString(string) {
|
|
125
|
+
if (!string)
|
|
126
|
+
throw new Error('Invalid import string');
|
|
127
|
+
const match = string.match(/^(?<packageName>(?:@[\w-]+\/[\w-]+|[\w-]+))(?:\/(?<subpath>.+?))?(?:\/?@(?<version>\d+(?:\.\d+)*(?:-[\w.]+)?(?:\+[\w.]+)?))?$/);
|
|
128
|
+
if (!match)
|
|
129
|
+
throw new Error(`Invalid import string (${string})`);
|
|
130
|
+
return match?.groups;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type PackageJsonExports = string | Record<string, {
|
|
2
|
+
import?: string;
|
|
3
|
+
browser?: string;
|
|
4
|
+
require?: string;
|
|
5
|
+
default?: string;
|
|
6
|
+
} | string> | PackageJsonExports[];
|
|
7
|
+
export interface PackageJson {
|
|
8
|
+
name: string;
|
|
9
|
+
version: string;
|
|
10
|
+
main?: string;
|
|
11
|
+
module?: string;
|
|
12
|
+
dependencies?: Record<string, string>;
|
|
13
|
+
peerDependencies?: Record<string, string>;
|
|
14
|
+
types?: string;
|
|
15
|
+
typings?: string;
|
|
16
|
+
typescript?: string;
|
|
17
|
+
browser?: string | Record<string, string | false>;
|
|
18
|
+
exports?: PackageJsonExports | Record<string, PackageJsonExports>;
|
|
19
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
export class $ScriptAnalyzer {
|
|
3
|
+
static getDefaultExportPublicMethodNames(code) {
|
|
4
|
+
const sourceFile = ts.createSourceFile('script.ts', code, ts.ScriptTarget.Latest, true);
|
|
5
|
+
const methods = [];
|
|
6
|
+
ts.forEachChild(sourceFile, node => {
|
|
7
|
+
if (ts.isClassDeclaration(node)) {
|
|
8
|
+
const modifiers = ts.getModifiers(node);
|
|
9
|
+
const hasExport = modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword);
|
|
10
|
+
const hasDefault = modifiers?.some(m => m.kind === ts.SyntaxKind.DefaultKeyword);
|
|
11
|
+
if (hasExport && hasDefault) {
|
|
12
|
+
for (const member of node.members) {
|
|
13
|
+
if (ts.isMethodDeclaration(member) && member.name) {
|
|
14
|
+
const modifiers = member.modifiers ?? [];
|
|
15
|
+
if (modifiers.find(modifier => modifier.kind === ts.SyntaxKind.ProtectedKeyword || modifier.kind === ts.SyntaxKind.PrivateKeyword))
|
|
16
|
+
return;
|
|
17
|
+
const name = ts.isIdentifier(member.name) ? member.name.text : member.name.getText(sourceFile);
|
|
18
|
+
methods.push(name);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
return methods;
|
|
25
|
+
}
|
|
26
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vorplex/compiler",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.11",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"files": [
|
|
@@ -15,12 +15,12 @@
|
|
|
15
15
|
"types": "./dist/index.d.ts",
|
|
16
16
|
"main": "./dist/index.js",
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"esbuild-wasm": "
|
|
18
|
+
"esbuild-wasm": "0.25.12",
|
|
19
19
|
"semver": "^7.7.3",
|
|
20
20
|
"tslib": "^2.8.1",
|
|
21
21
|
"typescript": "^5.9.3",
|
|
22
22
|
"yaml": "^2.8.2",
|
|
23
|
-
"@vorplex/core": "0.0.
|
|
23
|
+
"@vorplex/core": "0.0.11"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@types/jest": "^29.5.2",
|