2022-04-25 17:46:08 +00:00
|
|
|
import esbuild from "esbuild";
|
2022-05-13 12:36:26 +00:00
|
|
|
import { mkdir, readFile, rm, symlink, unlink, writeFile } from "fs/promises";
|
2022-04-25 17:46:08 +00:00
|
|
|
import path from "path";
|
2022-05-13 12:36:26 +00:00
|
|
|
import { tmpdir } from "os";
|
|
|
|
import { nodeModulesDir } from "./environments/node_sandbox";
|
|
|
|
import { promisify } from "util";
|
|
|
|
import { execFile } from "child_process";
|
|
|
|
const execFilePromise = promisify(execFile);
|
2022-04-25 17:46:08 +00:00
|
|
|
|
|
|
|
export async function compile(
|
|
|
|
filePath: string,
|
2022-05-11 09:49:27 +00:00
|
|
|
functionName: string | undefined = undefined,
|
2022-04-25 17:46:08 +00:00
|
|
|
debug: boolean = false,
|
|
|
|
excludeModules: string[] = [],
|
|
|
|
meta = false
|
|
|
|
): Promise<string> {
|
2022-05-11 09:49:27 +00:00
|
|
|
let outFile = path.resolve(path.dirname(filePath), "_out.tmp");
|
2022-04-25 17:46:08 +00:00
|
|
|
let inFile = filePath;
|
|
|
|
|
|
|
|
if (functionName) {
|
|
|
|
// Generate a new file importing just this one function and exporting it
|
2022-05-11 09:49:27 +00:00
|
|
|
inFile = path.resolve(path.dirname(filePath), "_in.ts");
|
2022-04-25 17:46:08 +00:00
|
|
|
await writeFile(
|
|
|
|
inFile,
|
2022-05-11 09:49:27 +00:00
|
|
|
`import {${functionName}} from "./${path.basename(
|
|
|
|
filePath
|
|
|
|
)}";export default ${functionName};`
|
2022-04-25 17:46:08 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-07-06 10:17:14 +00:00
|
|
|
try {
|
|
|
|
// TODO: Figure out how to make source maps work correctly with eval() code
|
|
|
|
let result = await esbuild.build({
|
|
|
|
entryPoints: [path.basename(inFile)],
|
|
|
|
bundle: true,
|
|
|
|
format: "iife",
|
|
|
|
globalName: "mod",
|
|
|
|
platform: "browser",
|
|
|
|
sourcemap: false, //debug ? "inline" : false,
|
|
|
|
minify: !debug,
|
|
|
|
outfile: outFile,
|
|
|
|
metafile: true,
|
|
|
|
external: excludeModules,
|
2022-07-18 10:11:03 +00:00
|
|
|
loader: {
|
|
|
|
".css": "text",
|
|
|
|
".png": "dataurl",
|
|
|
|
".gif": "dataurl",
|
|
|
|
".jpg": "dataurl",
|
|
|
|
},
|
2022-07-06 10:17:14 +00:00
|
|
|
absWorkingDir: path.resolve(path.dirname(inFile)),
|
|
|
|
});
|
2022-04-25 17:46:08 +00:00
|
|
|
|
2022-07-06 10:17:14 +00:00
|
|
|
if (meta) {
|
|
|
|
let text = await esbuild.analyzeMetafile(result.metafile);
|
|
|
|
console.log("Bundle info for", functionName, text);
|
|
|
|
}
|
2022-04-25 17:46:08 +00:00
|
|
|
|
2022-07-06 10:17:14 +00:00
|
|
|
let jsCode = (await readFile(outFile)).toString();
|
|
|
|
await unlink(outFile);
|
|
|
|
return `(() => { ${jsCode} return mod;})()`;
|
|
|
|
} finally {
|
|
|
|
if (inFile !== filePath) {
|
|
|
|
await unlink(inFile);
|
|
|
|
}
|
2022-04-25 17:46:08 +00:00
|
|
|
}
|
2022-05-13 12:36:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export async function compileModule(
|
|
|
|
cwd: string,
|
|
|
|
moduleName: string
|
|
|
|
): Promise<string> {
|
|
|
|
let inFile = path.resolve(cwd, "_in.ts");
|
|
|
|
await writeFile(inFile, `export * from "${moduleName}";`);
|
|
|
|
let code = await compile(inFile);
|
|
|
|
await unlink(inFile);
|
|
|
|
return code;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Reconsider this later
|
|
|
|
const exposedModules = [
|
|
|
|
"@silverbulletmd/plugos-silverbullet-syscall",
|
|
|
|
"@plugos/plugos-syscall",
|
|
|
|
];
|
|
|
|
|
|
|
|
export async function sandboxCompile(
|
|
|
|
filename: string,
|
|
|
|
code: string,
|
|
|
|
functionName?: string,
|
2022-05-13 15:05:52 +00:00
|
|
|
debug: boolean = false,
|
2022-05-13 12:36:26 +00:00
|
|
|
installModules: string[] = [],
|
|
|
|
globalModules: string[] = []
|
|
|
|
): Promise<string> {
|
|
|
|
let tmpDir = `${tmpdir()}/plugos-${Math.random()}`;
|
|
|
|
await mkdir(tmpDir, { recursive: true });
|
|
|
|
|
|
|
|
const srcNodeModules = `${nodeModulesDir}/node_modules`;
|
|
|
|
const targetNodeModules = `${tmpDir}/node_modules`;
|
|
|
|
|
|
|
|
await mkdir(`${targetNodeModules}/@silverbulletmd`, { recursive: true });
|
|
|
|
await mkdir(`${targetNodeModules}/@plugos`, { recursive: true });
|
|
|
|
for (const exposedModule of exposedModules) {
|
|
|
|
await symlink(
|
|
|
|
`${srcNodeModules}/${exposedModule}`,
|
|
|
|
`${targetNodeModules}/${exposedModule}`,
|
|
|
|
"dir"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
for (let moduleName of installModules) {
|
|
|
|
await execFilePromise("npm", ["install", moduleName], {
|
|
|
|
cwd: tmpDir,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
await writeFile(`${tmpDir}/${filename}`, code);
|
|
|
|
let jsCode = await compile(
|
|
|
|
`${tmpDir}/${filename}`,
|
|
|
|
functionName,
|
2022-05-13 15:05:52 +00:00
|
|
|
debug,
|
2022-05-13 12:36:26 +00:00
|
|
|
globalModules
|
|
|
|
);
|
|
|
|
await rm(tmpDir, { recursive: true });
|
|
|
|
return jsCode;
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function sandboxCompileModule(
|
|
|
|
moduleName: string,
|
|
|
|
globalModules: string[] = []
|
|
|
|
): Promise<string> {
|
|
|
|
let [modulePart, path] = moduleName.split(":");
|
|
|
|
let modulePieces = modulePart.split("@");
|
|
|
|
let cleanModulesName = modulePieces
|
|
|
|
.slice(0, modulePieces.length - 1)
|
|
|
|
.join("@");
|
|
|
|
return sandboxCompile(
|
|
|
|
"module.ts",
|
|
|
|
// `export * from "${cleanModulesName}${path ? path : ""}";`,
|
|
|
|
`module.exports = require("${cleanModulesName}${path ? path : ""}");`,
|
|
|
|
undefined,
|
2022-05-13 15:05:52 +00:00
|
|
|
true,
|
2022-05-13 12:36:26 +00:00
|
|
|
[modulePart],
|
|
|
|
globalModules
|
|
|
|
);
|
2022-04-25 17:46:08 +00:00
|
|
|
}
|