mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-23 09:08:32 +02:00
From SEA to regular NodeJS
This commit is contained in:
@@ -17,6 +17,7 @@
|
|||||||
"build:js": "vite build",
|
"build:js": "vite build",
|
||||||
"build:vendor-protoc": "node scripts/vendor-protoc.cjs",
|
"build:vendor-protoc": "node scripts/vendor-protoc.cjs",
|
||||||
"build:vendor-plugins": "node scripts/vendor-plugins.cjs",
|
"build:vendor-plugins": "node scripts/vendor-plugins.cjs",
|
||||||
|
"build:vendor-node": "node scripts/vendor-node.cjs",
|
||||||
"build:plugin-runtime": "npm run --prefix plugin-runtime build",
|
"build:plugin-runtime": "npm run --prefix plugin-runtime build",
|
||||||
"test": "vitest",
|
"test": "vitest",
|
||||||
"coverage": "vitest run --coverage",
|
"coverage": "vitest run --coverage",
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
"name": "@yaak/plugin-runtime",
|
"name": "@yaak/plugin-runtime",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "nodemon",
|
"dev": "nodemon",
|
||||||
"build": "run-p build:* && node scripts/generate-sea.cjs",
|
"build": "run-p build:*",
|
||||||
"build:main": "esbuild src/index.ts --bundle --platform=node --outfile=build/index.js",
|
"build:main": "esbuild src/index.ts --bundle --platform=node --outfile=build/index.cjs",
|
||||||
"build:worker": "esbuild src/index.worker.ts --bundle --platform=node --outfile=build/index.worker.js",
|
"build:worker": "esbuild src/index.worker.ts --bundle --platform=node --outfile=build/index.worker.cjs",
|
||||||
"build:proto": "grpc_tools_node_protoc --ts_proto_out=src/gen --ts_proto_opt=outputServices=nice-grpc,outputServices=generic-definitions,useExactTypes=false --proto_path=../proto ../proto/plugins/*.proto"
|
"build:proto": "grpc_tools_node_protoc --ts_proto_out=src/gen --ts_proto_opt=outputServices=nice-grpc,outputServices=generic-definitions,useExactTypes=false --proto_path=../proto ../proto/plugins/*.proto"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,102 +0,0 @@
|
|||||||
const path = require('node:path');
|
|
||||||
const {execSync} = require('node:child_process');
|
|
||||||
const {cpSync, mkdirSync, chmodSync, unlinkSync, rmSync, readdirSync, statSync} = require('node:fs');
|
|
||||||
const pluginRuntimeDir = path.join(__dirname, '..');
|
|
||||||
const destDir = path.join(__dirname, '..', '..', 'src-tauri', 'vendored', 'plugin-runtime');
|
|
||||||
const blobPath = path.join(pluginRuntimeDir, 'yaak-plugins.blob');
|
|
||||||
|
|
||||||
const DST_BIN_MAP = {
|
|
||||||
darwin_arm64: 'yaakplugins-aarch64-apple-darwin',
|
|
||||||
darwin_x64: 'yaakplugins-x86_64-apple-darwin',
|
|
||||||
linux_x64: 'yaakplugins-x86_64-unknown-linux-gnu',
|
|
||||||
win32_x64: 'yaakplugins-x86_64-pc-windows-msvc.exe',
|
|
||||||
};
|
|
||||||
|
|
||||||
// Build the sea
|
|
||||||
console.log('Building SEA blob');
|
|
||||||
execSync('node --experimental-sea-config sea-config.json', {cwd: pluginRuntimeDir});
|
|
||||||
|
|
||||||
const tmp = path.join(__dirname, 'tmp', `${Math.random()}`);
|
|
||||||
mkdirSync(tmp, {recursive: true});
|
|
||||||
|
|
||||||
let tmpNodePath = process.platform === 'win32' ? path.join(tmp, 'node.exe') : path.join(tmp, 'node');
|
|
||||||
|
|
||||||
console.log('Copying Node.js binary');
|
|
||||||
cpSync(process.execPath, tmpNodePath);
|
|
||||||
|
|
||||||
console.log('Changing Node.js binary permissions');
|
|
||||||
chmodSync(tmpNodePath, 0o755);
|
|
||||||
|
|
||||||
console.log('Removing Node.js code signature');
|
|
||||||
try {
|
|
||||||
if (process.platform === 'darwin') execSync(`codesign --remove-signature ${tmpNodePath}`);
|
|
||||||
else if (process.platform === 'win32') execSync(`"${getSigntoolLocation()}" remove /s ${tmpNodePath}`);
|
|
||||||
/* Nothing for Linux */
|
|
||||||
} catch (err) {
|
|
||||||
console.log('Failed remove signature', err);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
console.log('Injecting sea blob into Node.js');
|
|
||||||
if (process.platform === 'win32') execSync(`npx postject ${tmpNodePath} NODE_SEA_BLOB ${blobPath} --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2`);
|
|
||||||
else if (process.platform === 'darwin') execSync(`npx postject ${tmpNodePath} NODE_SEA_BLOB ${blobPath} --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 --macho-segment-name NODE_SEA`);
|
|
||||||
else if (process.platform === 'linux') execSync(`npx postject ${tmpNodePath} NODE_SEA_BLOB ${blobPath} --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2`);
|
|
||||||
} catch (err) {
|
|
||||||
console.log('Failed to inject blob', err.stdout.toString());
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
unlinkSync(blobPath);
|
|
||||||
|
|
||||||
console.log('Re-signing Node.js');
|
|
||||||
try {
|
|
||||||
if (process.platform === 'darwin') execSync(`codesign --sign - ${tmpNodePath}`);
|
|
||||||
// NOTE: Don't need to resign, as Tauri will sign the sidecar binaries during release
|
|
||||||
// else if (process.platform === 'win32') execSync(`"${getSigntoolLocation()}" sign /fd SHA256 ${tmpNodePath}`);
|
|
||||||
/* Nothing for Linux */
|
|
||||||
} catch (err) {
|
|
||||||
console.log('Failed sign', err);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const key = `${process.platform}_${process.env.NODE_ARCH ?? process.arch}`;
|
|
||||||
const dstPath = path.join(destDir, DST_BIN_MAP[key]);
|
|
||||||
cpSync(tmpNodePath, dstPath);
|
|
||||||
|
|
||||||
rmSync(tmp, {recursive: true, force: true});
|
|
||||||
|
|
||||||
console.log(`Copied sea to ${dstPath}`)
|
|
||||||
|
|
||||||
|
|
||||||
// https://github.com/skymatic/code-sign-action/blob/a2a8833d4e9202556539b564a2a4af5b6da3e8b2/index.ts
|
|
||||||
function getSigntoolLocation() {
|
|
||||||
const windowsKitsFolder = 'C:/Program Files (x86)/Windows Kits/10/bin/';
|
|
||||||
const folders = readdirSync(windowsKitsFolder);
|
|
||||||
let fileName = '';
|
|
||||||
let maxVersion = 0;
|
|
||||||
for (const folder of folders) {
|
|
||||||
if (!folder.endsWith('.0')) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const folderVersion = parseInt(folder.replace(/\./g, ''));
|
|
||||||
if (folderVersion > maxVersion) {
|
|
||||||
const signtoolFilename = `${windowsKitsFolder}${folder}/x64/signtool.exe`;
|
|
||||||
try {
|
|
||||||
const stat = statSync(signtoolFilename);
|
|
||||||
if (stat.isFile()) {
|
|
||||||
fileName = signtoolFilename;
|
|
||||||
maxVersion = folderVersion;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
console.warn('Skipping %s due to error.', signtoolFilename);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (fileName === '') {
|
|
||||||
throw new Error('Unable to find signtool.exe in ' + windowsKitsFolder);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Signtool location is ${fileName}.`);
|
|
||||||
return fileName;
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"main": "build/index.js",
|
|
||||||
"disableExperimentalSEAWarning": true,
|
|
||||||
"output": "yaak-plugins.blob",
|
|
||||||
"assets": {
|
|
||||||
"worker": "build/index.worker.js"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { randomUUID } from 'node:crypto';
|
import { randomUUID } from 'node:crypto';
|
||||||
|
import path from 'node:path';
|
||||||
import { Worker } from 'node:worker_threads';
|
import { Worker } from 'node:worker_threads';
|
||||||
import { PluginInfo } from './plugins';
|
import { PluginInfo } from './plugins';
|
||||||
|
|
||||||
@@ -24,10 +25,11 @@ export class PluginHandle {
|
|||||||
readonly pluginDir: string;
|
readonly pluginDir: string;
|
||||||
readonly #worker: Worker;
|
readonly #worker: Worker;
|
||||||
|
|
||||||
constructor({ pluginDir, workerJsPath }: { pluginDir: string; workerJsPath: string }) {
|
constructor(pluginDir: string) {
|
||||||
this.pluginDir = pluginDir;
|
this.pluginDir = pluginDir;
|
||||||
|
|
||||||
this.#worker = new Worker(workerJsPath, {
|
const workerPath = path.join(__dirname, 'index.worker.cjs');
|
||||||
|
this.#worker = new Worker(workerPath, {
|
||||||
workerData: {
|
workerData: {
|
||||||
pluginDir: this.pluginDir,
|
pluginDir: this.pluginDir,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
import { existsSync, writeFileSync } from 'node:fs';
|
|
||||||
import { tmpdir } from 'node:os';
|
|
||||||
import path from 'node:path';
|
|
||||||
import { getAsset } from 'node:sea';
|
|
||||||
import { PluginHandle } from './PluginHandle';
|
import { PluginHandle } from './PluginHandle';
|
||||||
import { loadPlugins, PluginInfo } from './plugins';
|
import { loadPlugins, PluginInfo } from './plugins';
|
||||||
|
|
||||||
export class PluginManager {
|
export class PluginManager {
|
||||||
#handles: PluginHandle[] | null = null;
|
#handles: PluginHandle[] | null = null;
|
||||||
static #instance: PluginManager | null = null;
|
static #instance: PluginManager | null = null;
|
||||||
static #workerPath = path.join(tmpdir(), `index.${Math.random()}.worker.js`);
|
|
||||||
|
|
||||||
public static instance(): PluginManager {
|
public static instance(): PluginManager {
|
||||||
if (PluginManager.#instance == null) {
|
if (PluginManager.#instance == null) {
|
||||||
@@ -19,22 +14,10 @@ export class PluginManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async plugins(): Promise<PluginHandle[]> {
|
async plugins(): Promise<PluginHandle[]> {
|
||||||
await this.#ensureWorkerForSea();
|
this.#handles = this.#handles ?? loadPlugins();
|
||||||
this.#handles = this.#handles ?? loadPlugins(PluginManager.#workerPath);
|
|
||||||
return this.#handles;
|
return this.#handles;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy worker JS asset to filesystem if we're in single-executable-application (SEA)
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
async #ensureWorkerForSea() {
|
|
||||||
if (existsSync(PluginManager.#workerPath)) return;
|
|
||||||
|
|
||||||
console.log('Writing worker file to', PluginManager.#workerPath);
|
|
||||||
writeFileSync(PluginManager.#workerPath, getAsset('worker', 'utf8'));
|
|
||||||
}
|
|
||||||
|
|
||||||
async #pluginsWithInfo(): Promise<{ plugin: PluginHandle; info: PluginInfo }[]> {
|
async #pluginsWithInfo(): Promise<{ plugin: PluginHandle; info: PluginInfo }[]> {
|
||||||
const plugins = await this.plugins();
|
const plugins = await this.plugins();
|
||||||
return Promise.all(plugins.map(async (plugin) => ({ plugin, info: await plugin.getInfo() })));
|
return Promise.all(plugins.map(async (plugin) => ({ plugin, info: await plugin.getInfo() })));
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ export interface PluginInfo {
|
|||||||
capabilities: ('import' | 'export' | 'filter')[];
|
capabilities: ('import' | 'export' | 'filter')[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loadPlugins(workerJsPath: string): PluginHandle[] {
|
export function loadPlugins(): PluginHandle[] {
|
||||||
const pluginsDir = process.env.PLUGINS_DIR;
|
const pluginsDir = process.env.PLUGINS_DIR;
|
||||||
if (!pluginsDir) throw new Error('PLUGINS_DIR is not set');
|
if (!pluginsDir) throw new Error('PLUGINS_DIR is not set');
|
||||||
console.log('Loading plugins from', pluginsDir);
|
console.log('Loading plugins from', pluginsDir);
|
||||||
|
|
||||||
const pluginDirs = fs.readdirSync(pluginsDir).map((p) => path.join(pluginsDir, p));
|
const pluginDirs = fs.readdirSync(pluginsDir).map((p) => path.join(pluginsDir, p));
|
||||||
return pluginDirs.map((pluginDir) => new PluginHandle({ pluginDir, workerJsPath }));
|
return pluginDirs.map((pluginDir) => new PluginHandle(pluginDir));
|
||||||
}
|
}
|
||||||
|
|||||||
22
scripts/vendor-node.cjs
Normal file
22
scripts/vendor-node.cjs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
const path = require('node:path');
|
||||||
|
const {cpSync} = require('node:fs');
|
||||||
|
const destDir = path.join(__dirname, '..', 'src-tauri', 'vendored', 'node');
|
||||||
|
|
||||||
|
const DST_BIN_MAP = {
|
||||||
|
darwin_arm64: 'node-aarch64-apple-darwin',
|
||||||
|
darwin_x64: 'node-x86_64-apple-darwin',
|
||||||
|
linux_x64: 'node-x86_64-unknown-linux-gnu',
|
||||||
|
win32_x64: 'node-x86_64-pc-windows-msvc.exe',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Build the sea
|
||||||
|
console.log('Vendoring NodeJS binary');
|
||||||
|
|
||||||
|
// console.log('Changing Node.js binary permissions');
|
||||||
|
// chmodSync(tmpNodePath, 0o755);
|
||||||
|
|
||||||
|
const key = `${process.platform}_${process.env.NODE_ARCH ?? process.arch}`;
|
||||||
|
const dstPath = path.join(destDir, DST_BIN_MAP[key]);
|
||||||
|
cpSync(process.execPath, dstPath);
|
||||||
|
|
||||||
|
console.log(`Copied NodeJS to ${dstPath}`)
|
||||||
@@ -23,10 +23,16 @@ pub async fn node_start<R: Runtime>(app: &AppHandle<R>, temp_dir: &PathBuf) -> S
|
|||||||
let plugins_dir = app
|
let plugins_dir = app
|
||||||
.path()
|
.path()
|
||||||
.resolve("plugins", BaseDirectory::Resource)
|
.resolve("plugins", BaseDirectory::Resource)
|
||||||
.expect("failed to resolve plugin directory resource");
|
.expect("failed to resolve plugin directory resource")
|
||||||
let plugins_dir = plugins_dir.to_string_lossy().to_string();
|
.to_string_lossy()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
// Remove UNC prefix for Windows paths
|
let plugin_runtime_dir = app
|
||||||
|
.path()
|
||||||
|
.resolve("plugin-runtime", BaseDirectory::Resource)
|
||||||
|
.expect("failed to resolve plugin runtime resource");
|
||||||
|
|
||||||
|
// HACK: Remove UNC prefix for Windows paths
|
||||||
let plugins_dir = plugins_dir.replace("\\\\?\\", "");
|
let plugins_dir = plugins_dir.replace("\\\\?\\", "");
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
@@ -37,10 +43,11 @@ pub async fn node_start<R: Runtime>(app: &AppHandle<R>, temp_dir: &PathBuf) -> S
|
|||||||
|
|
||||||
let (mut rx, _child) = app
|
let (mut rx, _child) = app
|
||||||
.shell()
|
.shell()
|
||||||
.sidecar("yaakplugins")
|
.sidecar("node")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.env("GRPC_PORT_FILE_PATH", port_file_path.clone())
|
.env("GRPC_PORT_FILE_PATH", port_file_path.clone())
|
||||||
.env("PLUGINS_DIR", plugins_dir)
|
.env("PLUGINS_DIR", plugins_dir)
|
||||||
|
.args(&[plugin_runtime_dir.join("index.cjs")])
|
||||||
.spawn()
|
.spawn()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
"category": "DeveloperTool",
|
"category": "DeveloperTool",
|
||||||
"externalBin": [
|
"externalBin": [
|
||||||
"vendored/protoc/protoc",
|
"vendored/protoc/protoc",
|
||||||
"vendored/plugin-runtime/yaakplugins"
|
"vendored/node/node"
|
||||||
],
|
],
|
||||||
"icon": [
|
"icon": [
|
||||||
"icons/release/32x32.png",
|
"icons/release/32x32.png",
|
||||||
@@ -55,7 +55,8 @@
|
|||||||
"resources": {
|
"resources": {
|
||||||
"migrations": "migrations",
|
"migrations": "migrations",
|
||||||
"vendored/protoc/include": "protoc-include",
|
"vendored/protoc/include": "protoc-include",
|
||||||
"vendored/plugins": "plugins"
|
"vendored/plugins": "plugins",
|
||||||
|
"../plugin-runtime/build": "plugin-runtime"
|
||||||
},
|
},
|
||||||
"shortDescription": "Play with APIs, intuitively",
|
"shortDescription": "Play with APIs, intuitively",
|
||||||
"targets": [
|
"targets": [
|
||||||
|
|||||||
Reference in New Issue
Block a user