snapshot of https://github.com/mountain-loop/yaak.git (main, 2026-09-23; blobs > 131072B omitted)
This commit is contained in:
1
scripts/.gitignore
vendored
Normal file
1
scripts/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
tmp-*
|
||||
46
scripts/create-migration.cjs
Normal file
46
scripts/create-migration.cjs
Normal file
@@ -0,0 +1,46 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const readline = require("readline");
|
||||
const slugify = require("slugify");
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
});
|
||||
|
||||
function generateTimestamp() {
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = String(now.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(now.getDate()).padStart(2, "0");
|
||||
const hours = String(now.getHours()).padStart(2, "0");
|
||||
const minutes = String(now.getMinutes()).padStart(2, "0");
|
||||
const seconds = String(now.getSeconds()).padStart(2, "0");
|
||||
return `${year}${month}${day}${hours}${minutes}${seconds}`;
|
||||
}
|
||||
|
||||
async function createMigration() {
|
||||
try {
|
||||
const migrationName = await new Promise((resolve) => {
|
||||
rl.question("Enter migration name: ", resolve);
|
||||
});
|
||||
|
||||
const timestamp = generateTimestamp();
|
||||
const fileName = `${timestamp}_${slugify(String(migrationName), { lower: true })}.sql`;
|
||||
const migrationsDir = path.join(__dirname, "../crates/yaak-models/migrations");
|
||||
const filePath = path.join(migrationsDir, fileName);
|
||||
|
||||
if (!fs.existsSync(migrationsDir)) {
|
||||
fs.mkdirSync(migrationsDir, { recursive: true });
|
||||
}
|
||||
|
||||
fs.writeFileSync(filePath, "-- Add migration SQL here\n");
|
||||
console.log(`Created migration file: ${fileName}`);
|
||||
} catch (error) {
|
||||
console.error("Error creating migration:", error);
|
||||
} finally {
|
||||
rl.close();
|
||||
}
|
||||
}
|
||||
|
||||
createMigration().catch(console.error);
|
||||
77
scripts/extract-archive.cjs
Normal file
77
scripts/extract-archive.cjs
Normal file
@@ -0,0 +1,77 @@
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const tar = require("tar");
|
||||
const yauzl = require("yauzl");
|
||||
|
||||
// Resolve an archive entry against destDir, refusing anything that escapes it.
|
||||
function safeJoin(destDir, entryName) {
|
||||
const root = path.resolve(destDir);
|
||||
const resolved = path.resolve(root, entryName);
|
||||
if (resolved !== root && !resolved.startsWith(root + path.sep)) {
|
||||
throw new Error(`Archive entry escapes destination directory: ${entryName}`);
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
function extractZip(filePath, destDir) {
|
||||
return new Promise((resolve, reject) => {
|
||||
yauzl.open(filePath, { lazyEntries: true }, (err, zip) => {
|
||||
if (err) return reject(err);
|
||||
zip.on("error", reject);
|
||||
zip.on("end", resolve);
|
||||
zip.on("entry", (entry) => {
|
||||
let dst;
|
||||
try {
|
||||
dst = safeJoin(destDir, entry.fileName);
|
||||
} catch (e) {
|
||||
return reject(e);
|
||||
}
|
||||
|
||||
// Unix mode lives in the high 16 bits of the external attributes
|
||||
const rawMode = (entry.externalFileAttributes >>> 16) & 0xffff;
|
||||
const isSymlink = (rawMode & 0o170000) === 0o120000;
|
||||
if (isSymlink) {
|
||||
return reject(new Error(`Refusing to extract symlink from archive: ${entry.fileName}`));
|
||||
}
|
||||
|
||||
if (entry.fileName.endsWith("/")) {
|
||||
fs.mkdirSync(dst, { recursive: true });
|
||||
return zip.readEntry();
|
||||
}
|
||||
|
||||
zip.openReadStream(entry, (err2, stream) => {
|
||||
if (err2) return reject(err2);
|
||||
fs.mkdirSync(path.dirname(dst), { recursive: true });
|
||||
const out = fs.createWriteStream(dst);
|
||||
stream.on("error", reject);
|
||||
out.on("error", reject);
|
||||
out.on("close", () => {
|
||||
const mode = rawMode & 0o7777;
|
||||
if (mode !== 0) fs.chmodSync(dst, mode);
|
||||
zip.readEntry();
|
||||
});
|
||||
stream.pipe(out);
|
||||
});
|
||||
});
|
||||
zip.readEntry();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a `.zip` or `.tar.gz` archive into destDir, preserving file modes.
|
||||
* Entries that would land outside destDir are rejected.
|
||||
*/
|
||||
async function extractArchive(filePath, destDir) {
|
||||
fs.mkdirSync(destDir, { recursive: true });
|
||||
if (filePath.endsWith(".zip")) {
|
||||
await extractZip(filePath, destDir);
|
||||
} else if (filePath.endsWith(".tar.gz") || filePath.endsWith(".tgz")) {
|
||||
// oxlint-disable-next-line await-thenable -- tar.x() returns a promise when `file` is set
|
||||
await tar.x({ file: filePath, cwd: destDir });
|
||||
} else {
|
||||
throw new Error(`Unsupported archive format: ${path.basename(filePath)}`);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { extractArchive };
|
||||
219
scripts/git-hooks/post-checkout.mjs
Normal file
219
scripts/git-hooks/post-checkout.mjs
Normal file
@@ -0,0 +1,219 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Git post-checkout hook for auto-configuring worktree environments.
|
||||
* This runs after 'git checkout' or 'git worktree add'.
|
||||
*
|
||||
* Args from git:
|
||||
* process.argv[2] - previous HEAD ref
|
||||
* process.argv[3] - new HEAD ref
|
||||
* process.argv[4] - flag (1 = branch checkout, 0 = file checkout)
|
||||
*/
|
||||
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { execSync, execFileSync } from "child_process";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
// Git supplies three arguments when invoking this as a post-checkout hook. When run directly,
|
||||
// assume the caller wants to configure the current worktree instead of requiring placeholder refs.
|
||||
const isManualRun = process.argv.length === 2;
|
||||
const isBranchCheckout = isManualRun || process.argv[4] === "1";
|
||||
|
||||
if (!isBranchCheckout) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Check if we're in a worktree by looking for .git file (not directory)
|
||||
const gitPath = path.join(process.cwd(), ".git");
|
||||
const isWorktree = fs.existsSync(gitPath) && fs.statSync(gitPath).isFile();
|
||||
|
||||
if (!isWorktree) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const envLocalPath = path.join(process.cwd(), ".env.local");
|
||||
|
||||
// Don't overwrite existing .env.local
|
||||
if (fs.existsSync(envLocalPath)) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
console.log("Detected new worktree - configuring ports in .env.local");
|
||||
|
||||
const CLIENT_PORT_BASE = 1420;
|
||||
const PROXY_PORT_BASE = 2420; // Keep proxy +1000 from client to avoid cross-app conflicts
|
||||
|
||||
// Find the highest worktree index and MCP port in use across all worktrees.
|
||||
// Main worktree (first in list) is assumed to use index 0:
|
||||
// client=1420, proxy=2420, mcp=64343.
|
||||
let maxMcpPort = 64343;
|
||||
let maxWorktreeIndex = 0;
|
||||
|
||||
try {
|
||||
const worktreeList = execSync("git worktree list --porcelain", {
|
||||
encoding: "utf8",
|
||||
});
|
||||
const worktreePaths = worktreeList
|
||||
.split("\n")
|
||||
.filter((line) => line.startsWith("worktree "))
|
||||
.map((line) => line.replace("worktree ", "").trim());
|
||||
|
||||
// Skip the first worktree (main) since it uses default ports
|
||||
for (let i = 1; i < worktreePaths.length; i++) {
|
||||
const worktreePath = worktreePaths[i];
|
||||
const envPath = path.join(worktreePath, ".env.local");
|
||||
|
||||
if (fs.existsSync(envPath)) {
|
||||
const content = fs.readFileSync(envPath, "utf8");
|
||||
|
||||
const mcpMatch = content.match(/^YAAK_PLUGIN_MCP_SERVER_PORT=(\d+)/m);
|
||||
if (mcpMatch) {
|
||||
const port = parseInt(mcpMatch[1], 10);
|
||||
if (port > maxMcpPort) {
|
||||
maxMcpPort = port;
|
||||
}
|
||||
}
|
||||
|
||||
const clientDevMatch = content.match(/^(?:YAAK_CLIENT_DEV_PORT|YAAK_DEV_PORT)=(\d+)/m);
|
||||
if (clientDevMatch) {
|
||||
const port = parseInt(clientDevMatch[1], 10);
|
||||
const index = port - CLIENT_PORT_BASE;
|
||||
if (index > maxWorktreeIndex) {
|
||||
maxWorktreeIndex = index;
|
||||
}
|
||||
}
|
||||
|
||||
const proxyDevMatch = content.match(/^YAAK_PROXY_DEV_PORT=(\d+)/m);
|
||||
if (proxyDevMatch) {
|
||||
const port = parseInt(proxyDevMatch[1], 10);
|
||||
const index = port - PROXY_PORT_BASE;
|
||||
if (index > maxWorktreeIndex) {
|
||||
maxWorktreeIndex = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Increment MCP to get the next available port
|
||||
maxMcpPort++;
|
||||
} catch (err) {
|
||||
console.error("Warning: Could not check other worktrees for port conflicts:", err.message);
|
||||
// Continue with default ports
|
||||
}
|
||||
|
||||
const nextWorktreeIndex = maxWorktreeIndex + 1;
|
||||
const nextClientDevPort = CLIENT_PORT_BASE + nextWorktreeIndex;
|
||||
const nextProxyDevPort = PROXY_PORT_BASE + nextWorktreeIndex;
|
||||
|
||||
// Get worktree name from current directory
|
||||
const worktreeName = path.basename(process.cwd());
|
||||
|
||||
// Create .env.local with unique ports
|
||||
const envContent = `# Auto-generated by git post-checkout hook
|
||||
# This file configures ports for this worktree to avoid conflicts
|
||||
|
||||
# Vite dev server port for the client app (main worktree uses ${CLIENT_PORT_BASE})
|
||||
YAAK_CLIENT_DEV_PORT=${nextClientDevPort}
|
||||
|
||||
# Vite dev server port for the proxy app (main worktree uses ${PROXY_PORT_BASE})
|
||||
YAAK_PROXY_DEV_PORT=${nextProxyDevPort}
|
||||
|
||||
# MCP Server port (main worktree uses 64343)
|
||||
YAAK_PLUGIN_MCP_SERVER_PORT=${maxMcpPort}
|
||||
`;
|
||||
|
||||
fs.writeFileSync(envLocalPath, envContent, "utf8");
|
||||
console.log(
|
||||
`Created .env.local with YAAK_CLIENT_DEV_PORT=${nextClientDevPort}, YAAK_PROXY_DEV_PORT=${nextProxyDevPort}, and YAAK_PLUGIN_MCP_SERVER_PORT=${maxMcpPort}`,
|
||||
);
|
||||
|
||||
// Create tauri.worktree.conf.json with unique app identifier for complete isolation
|
||||
// This gives each worktree its own app data directory, avoiding the need for DB path prefixes
|
||||
const clientTauriWorktreeConfig = {
|
||||
identifier: `app.yaak.desktop.dev.${worktreeName}`,
|
||||
productName: `Daak (${worktreeName})`,
|
||||
};
|
||||
|
||||
const clientTauriConfigPath = path.join(
|
||||
process.cwd(),
|
||||
"crates-tauri",
|
||||
"yaak-app-client",
|
||||
"tauri.worktree.conf.json",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
clientTauriConfigPath,
|
||||
JSON.stringify(clientTauriWorktreeConfig, null, 2) + "\n",
|
||||
"utf8",
|
||||
);
|
||||
console.log(
|
||||
`Created client tauri.worktree.conf.json with identifier: ${clientTauriWorktreeConfig.identifier}`,
|
||||
);
|
||||
|
||||
const proxyTauriWorktreeConfig = {
|
||||
identifier: `app.yaak.proxy.dev.${worktreeName}`,
|
||||
productName: `Yaak Proxy (${worktreeName})`,
|
||||
};
|
||||
|
||||
const proxyTauriConfigPath = path.join(
|
||||
process.cwd(),
|
||||
"crates-tauri",
|
||||
"yaak-app-proxy",
|
||||
"tauri.worktree.conf.json",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
proxyTauriConfigPath,
|
||||
JSON.stringify(proxyTauriWorktreeConfig, null, 2) + "\n",
|
||||
"utf8",
|
||||
);
|
||||
console.log(
|
||||
`Created proxy tauri.worktree.conf.json with identifier: ${proxyTauriWorktreeConfig.identifier}`,
|
||||
);
|
||||
|
||||
// Copy gitignored editor config folders from main worktree (.zed, .vscode, .claude, etc.)
|
||||
// This ensures your editor settings, tasks, and configurations are available in the new worktree
|
||||
// without needing to manually copy them or commit them to git.
|
||||
try {
|
||||
const worktreeList = execSync("git worktree list --porcelain", {
|
||||
encoding: "utf8",
|
||||
});
|
||||
const mainWorktreePath = worktreeList
|
||||
.split("\n")
|
||||
.find((line) => line.startsWith("worktree "))
|
||||
?.replace("worktree ", "")
|
||||
.trim();
|
||||
|
||||
if (mainWorktreePath) {
|
||||
// Find all .* folders in main worktree root that are gitignored
|
||||
const entries = fs.readdirSync(mainWorktreePath, { withFileTypes: true });
|
||||
const dotFolders = entries
|
||||
.filter((entry) => entry.isDirectory() && entry.name.startsWith("."))
|
||||
.map((entry) => entry.name);
|
||||
|
||||
for (const folder of dotFolders) {
|
||||
const sourcePath = path.join(mainWorktreePath, folder);
|
||||
const destPath = path.join(process.cwd(), folder);
|
||||
|
||||
try {
|
||||
// Check if it's gitignored - run from main worktree directory
|
||||
execFileSync("git", ["check-ignore", "-q", folder], {
|
||||
stdio: "pipe",
|
||||
cwd: mainWorktreePath,
|
||||
});
|
||||
|
||||
// It's gitignored, copy it
|
||||
fs.cpSync(sourcePath, destPath, { recursive: true });
|
||||
console.log(`Copied ${folder} from main worktree`);
|
||||
} catch {
|
||||
// Not gitignored or doesn't exist, skip
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn("Warning: Could not copy files from main worktree:", err.message);
|
||||
// Continue anyway
|
||||
}
|
||||
|
||||
console.log("\n✓ Worktree setup complete! Run `npm run init` to install dependencies.");
|
||||
18
scripts/install-wasm-pack.cjs
Normal file
18
scripts/install-wasm-pack.cjs
Normal file
@@ -0,0 +1,18 @@
|
||||
const { execSync } = require("node:child_process");
|
||||
|
||||
const version = tryExecSync("wasm-pack --version");
|
||||
if (version.startsWith("wasm-pack ")) {
|
||||
console.log("wasm-pack already installed");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Installing wasm-pack via cargo...");
|
||||
execSync("cargo install wasm-pack --locked", { stdio: "inherit" });
|
||||
|
||||
function tryExecSync(cmd) {
|
||||
try {
|
||||
return execSync(cmd, { stdio: "pipe" }).toString("utf-8");
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
19
scripts/publish-core-plugins.cjs
Normal file
19
scripts/publish-core-plugins.cjs
Normal file
@@ -0,0 +1,19 @@
|
||||
const { readdirSync } = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const { execSync } = require("node:child_process");
|
||||
|
||||
const pluginsDir = path.join(__dirname, "..", "plugins");
|
||||
|
||||
console.log("Publishing core Yaak plugins");
|
||||
|
||||
for (const name of readdirSync(pluginsDir)) {
|
||||
const dir = path.join(pluginsDir, name);
|
||||
if (name.startsWith(".")) continue;
|
||||
console.log("Building plugin", dir);
|
||||
execSync("npm run build", { stdio: "inherit", cwd: dir });
|
||||
execSync("yaakcli publish", {
|
||||
stdio: "inherit",
|
||||
cwd: dir,
|
||||
env: { ...process.env, ENVIRONMENT: "development" },
|
||||
});
|
||||
}
|
||||
15
scripts/replace-version.cjs
Normal file
15
scripts/replace-version.cjs
Normal file
@@ -0,0 +1,15 @@
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
|
||||
const version = process.env.YAAK_VERSION?.replace("v", "");
|
||||
if (!version) {
|
||||
throw new Error("YAAK_VERSION environment variable not set");
|
||||
}
|
||||
|
||||
const tauriConfigPath = path.join(__dirname, "../crates-tauri/yaak-app-client/tauri.conf.json");
|
||||
const tauriConfig = JSON.parse(fs.readFileSync(tauriConfigPath, "utf8"));
|
||||
|
||||
tauriConfig.version = version;
|
||||
|
||||
console.log("Writing version " + version + " to " + tauriConfigPath);
|
||||
fs.writeFileSync(tauriConfigPath, JSON.stringify(tauriConfig, null, 2));
|
||||
62
scripts/run-build.mjs
Normal file
62
scripts/run-build.mjs
Normal file
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Runs a Tauri app build from the app directory so relative config paths and
|
||||
* beforeBuildCommand entries resolve consistently.
|
||||
*/
|
||||
|
||||
import { spawnSync } from "child_process";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const rootDir = path.join(__dirname, "..");
|
||||
|
||||
const [appName = "client", ...additionalArgs] = process.argv.slice(2);
|
||||
const appConfigs = {
|
||||
client: {
|
||||
appDir: "crates-tauri/yaak-app-client",
|
||||
tauriConfig: "tauri.conf.json",
|
||||
},
|
||||
proxy: {
|
||||
appDir: "crates-tauri/yaak-app-proxy",
|
||||
tauriConfig: "tauri.conf.json",
|
||||
},
|
||||
};
|
||||
|
||||
const appConfig = appConfigs[appName];
|
||||
if (appConfig == null) {
|
||||
console.error(`Unknown Tauri app "${appName}"`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Normalize extra `--config` path args to absolute paths from repo root so
|
||||
// callers can keep passing root-relative config files.
|
||||
const normalizedAdditionalArgs = [];
|
||||
for (let i = 0; i < additionalArgs.length; i++) {
|
||||
const arg = additionalArgs[i];
|
||||
if (arg === "--config" && i + 1 < additionalArgs.length) {
|
||||
const value = additionalArgs[i + 1];
|
||||
const isInlineJson = value.trimStart().startsWith("{");
|
||||
normalizedAdditionalArgs.push("--config");
|
||||
normalizedAdditionalArgs.push(
|
||||
!isInlineJson && !path.isAbsolute(value) ? path.join(rootDir, value) : value,
|
||||
);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
normalizedAdditionalArgs.push(arg);
|
||||
}
|
||||
|
||||
const tauriJs = path.join(rootDir, "node_modules", "@tauri-apps", "cli", "tauri.js");
|
||||
const result = spawnSync(
|
||||
process.execPath,
|
||||
[tauriJs, "build", "--config", appConfig.tauriConfig, ...normalizedAdditionalArgs],
|
||||
{
|
||||
cwd: path.join(rootDir, appConfig.appDir),
|
||||
stdio: "inherit",
|
||||
env: process.env,
|
||||
},
|
||||
);
|
||||
|
||||
process.exit(result.status || 0);
|
||||
121
scripts/run-dev.mjs
Normal file
121
scripts/run-dev.mjs
Normal file
@@ -0,0 +1,121 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Script to run a Tauri app dev server with dynamic port configuration.
|
||||
*/
|
||||
|
||||
import { spawnSync } from "child_process";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const rootDir = path.join(__dirname, "..");
|
||||
|
||||
// Load .env.local if it exists
|
||||
const envLocalPath = path.join(rootDir, ".env.local");
|
||||
if (fs.existsSync(envLocalPath)) {
|
||||
const envContent = fs.readFileSync(envLocalPath, "utf8");
|
||||
const envVars = envContent
|
||||
.split("\n")
|
||||
.filter((line) => line && !line.startsWith("#"))
|
||||
.reduce((acc, line) => {
|
||||
const [key, value] = line.split("=");
|
||||
if (key && value) {
|
||||
acc[key.trim()] = value.trim();
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
Object.assign(process.env, envVars);
|
||||
}
|
||||
|
||||
const [appName = "client", ...additionalArgs] = process.argv.slice(2);
|
||||
const appConfigs = {
|
||||
client: {
|
||||
appDir: "crates-tauri/yaak-app-client",
|
||||
devPortEnv: "YAAK_CLIENT_DEV_PORT",
|
||||
fallbackDevPortEnv: "YAAK_DEV_PORT",
|
||||
defaultPort: "1420",
|
||||
tauriConfig: "tauri.conf.json",
|
||||
tauriDevConfig: "tauri.development.conf.json",
|
||||
tauriWorktreeConfig: "tauri.worktree.conf.json",
|
||||
},
|
||||
proxy: {
|
||||
appDir: "crates-tauri/yaak-app-proxy",
|
||||
devPortEnv: "YAAK_PROXY_DEV_PORT",
|
||||
fallbackDevPortEnv: null,
|
||||
defaultPort: "2420",
|
||||
tauriConfig: "tauri.conf.json",
|
||||
tauriDevConfig: "tauri.development.conf.json",
|
||||
tauriWorktreeConfig: "tauri.worktree.conf.json",
|
||||
},
|
||||
};
|
||||
|
||||
const appConfig = appConfigs[appName];
|
||||
if (appConfig == null) {
|
||||
console.error(`Unknown Tauri app "${appName}"`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const port =
|
||||
process.env[appConfig.devPortEnv] ||
|
||||
(appConfig.fallbackDevPortEnv ? process.env[appConfig.fallbackDevPortEnv] : undefined) ||
|
||||
appConfig.defaultPort;
|
||||
const config = JSON.stringify({
|
||||
build: { devUrl: `http://localhost:${port}` },
|
||||
});
|
||||
|
||||
// Normalize extra `--config` path args to absolute paths from repo root so
|
||||
// callers can keep passing root-relative config files.
|
||||
const normalizedAdditionalArgs = [];
|
||||
for (let i = 0; i < additionalArgs.length; i++) {
|
||||
const arg = additionalArgs[i];
|
||||
if (arg === "--") {
|
||||
normalizedAdditionalArgs.push(arg, ...additionalArgs.slice(i + 1));
|
||||
break;
|
||||
}
|
||||
if (arg === "--config" && i + 1 < additionalArgs.length) {
|
||||
const value = additionalArgs[i + 1];
|
||||
const isInlineJson = value.trimStart().startsWith("{");
|
||||
normalizedAdditionalArgs.push("--config");
|
||||
normalizedAdditionalArgs.push(
|
||||
!isInlineJson && !path.isAbsolute(value) ? path.join(rootDir, value) : value,
|
||||
);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
normalizedAdditionalArgs.push(arg);
|
||||
}
|
||||
|
||||
// Tauri only auto-merges platform-specific config names, so the per-worktree config
|
||||
// written by the post-checkout hook has to be passed explicitly. It's gitignored, so
|
||||
// it's normally absent in the main checkout.
|
||||
const worktreeConfigArgs = [];
|
||||
if (fs.existsSync(path.join(rootDir, appConfig.appDir, appConfig.tauriWorktreeConfig))) {
|
||||
worktreeConfigArgs.push("--config", appConfig.tauriWorktreeConfig);
|
||||
}
|
||||
|
||||
const args = [
|
||||
"dev",
|
||||
"--no-watch",
|
||||
"--config",
|
||||
appConfig.tauriConfig,
|
||||
"--config",
|
||||
appConfig.tauriDevConfig,
|
||||
...worktreeConfigArgs,
|
||||
"--config",
|
||||
config,
|
||||
...normalizedAdditionalArgs,
|
||||
];
|
||||
|
||||
// Invoke the tauri CLI JS entry point directly via node to avoid shell escaping issues on Windows
|
||||
const tauriJs = path.join(rootDir, "node_modules", "@tauri-apps", "cli", "tauri.js");
|
||||
|
||||
const result = spawnSync(process.execPath, [tauriJs, ...args], {
|
||||
cwd: path.join(rootDir, appConfig.appDir),
|
||||
stdio: "inherit",
|
||||
env: process.env,
|
||||
});
|
||||
|
||||
process.exit(result.status || 0);
|
||||
120
scripts/run-web.mjs
Normal file
120
scripts/run-web.mjs
Normal file
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Run Yaak in a browser: the client built for the web target, and the server
|
||||
* that executes its sends.
|
||||
*
|
||||
* `YAAK_TARGET=web` is what resolves `@yaakapp-internal/platform` to its browser
|
||||
* entry (see apps/yaak-client/vite.config.ts). Setting it in a node script rather
|
||||
* than inline in an npm script keeps this working on Windows.
|
||||
*/
|
||||
|
||||
import { spawn, spawnSync } from "child_process";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const rootDir = path.join(__dirname, "..");
|
||||
|
||||
// The post-checkout hook writes a per-worktree dev port in here, and the client's
|
||||
// vite config reads YAAK_CLIENT_DEV_PORT from it. Without this, two worktrees both
|
||||
// try to serve on 1420. (run-dev.mjs reads the same file, but with Object.assign,
|
||||
// so there an explicit YAAK_CLIENT_DEV_PORT is silently ignored.)
|
||||
const envLocalPath = path.join(rootDir, ".env.local");
|
||||
if (fs.existsSync(envLocalPath)) {
|
||||
for (const line of fs.readFileSync(envLocalPath, "utf8").split("\n")) {
|
||||
if (!line || line.startsWith("#")) continue;
|
||||
const [key, value] = line.split("=");
|
||||
if (!key || !value) continue;
|
||||
// Defaults, not overrides: a variable set on the command line is the more
|
||||
// deliberate of the two and has to win, or `PORT=x vp run web:dev` silently
|
||||
// lands somewhere else.
|
||||
process.env[key.trim()] ??= value.trim();
|
||||
}
|
||||
}
|
||||
|
||||
const DIST = "dist/apps/yaak-client";
|
||||
|
||||
const [mode] = process.argv.slice(2);
|
||||
|
||||
// Invoke the Vite+ CLI JS entry point directly via node, the way run-dev.mjs invokes
|
||||
// the Tauri CLI. The `.bin/vp` shim is a POSIX script that npm pairs with `vp.cmd` and
|
||||
// `vp.ps1` on Windows, so spawning it without a shell only works on one platform.
|
||||
const vp = path.join(rootDir, "node_modules", "vite-plus", "bin", "vp");
|
||||
|
||||
const runVite = (args) => [process.execPath, [vp, ...args]];
|
||||
|
||||
const env = (extra = {}) => ({ ...process.env, YAAK_TARGET: "web", ...extra });
|
||||
|
||||
/** Run a build step to completion. A failure ends the script. */
|
||||
function run(command, args, extra = {}) {
|
||||
const result = spawnSync(command, args, { cwd: rootDir, stdio: "inherit", env: env(extra) });
|
||||
if (result.status !== 0) process.exit(result.status ?? 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a server until it stops, and take it down when this process is stopped.
|
||||
*
|
||||
* Servers are started as a direct child rather than through `cargo run`, and
|
||||
* signals are forwarded to them, because neither happens by itself: `cargo run`
|
||||
* is a wrapper that does not pass a terminating signal on to the binary it
|
||||
* spawned, and a child is not killed by its parent exiting. Either one on its
|
||||
* own leaves a server holding the port after the terminal that started it has
|
||||
* gone, and the next run fails with "address already in use".
|
||||
*/
|
||||
function serve(command, args, extra = {}) {
|
||||
const child = spawn(command, args, { cwd: rootDir, stdio: "inherit", env: env(extra) });
|
||||
|
||||
const stop = (signal) => child.killed || child.kill(signal);
|
||||
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) process.on(signal, () => stop(signal));
|
||||
process.on("exit", () => stop("SIGTERM"));
|
||||
|
||||
child.on("exit", (code, signal) => process.exit(code ?? (signal ? 1 : 0)));
|
||||
child.on("error", (err) => {
|
||||
console.error(`Failed to start ${command}: ${err.message}`);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
/** Build the server and hand back the binary, so it can be run without a wrapper. */
|
||||
function buildServer() {
|
||||
run("cargo", ["build", "-p", "yaak-web"]);
|
||||
const target = process.env.CARGO_TARGET_DIR ?? path.join(rootDir, "target");
|
||||
return path.join(target, "debug", "yaak-web");
|
||||
}
|
||||
|
||||
switch (mode) {
|
||||
// The app, with hot reload, on one origin: its own `/v1` is passed through to
|
||||
// the server `web:dev` starts alongside it. Nothing to build first and one
|
||||
// address to open.
|
||||
case "dev":
|
||||
serve(...runVite(["-C", "apps/yaak-client", "dev", "--force"]));
|
||||
break;
|
||||
|
||||
// The send executor behind the dev server, on the port a dev build looks for.
|
||||
case "proxy":
|
||||
serve(buildServer(), [], {
|
||||
// So opening the send server's port in a browser lands on the app instead
|
||||
// of an explanation of why the app is not there.
|
||||
YAAK_WEB_APP_PORT: process.env.YAAK_CLIENT_DEV_PORT ?? process.env.YAAK_DEV_PORT ?? "1420",
|
||||
});
|
||||
break;
|
||||
|
||||
case "build":
|
||||
run(...runVite(["-C", "apps/yaak-client", "build"]));
|
||||
break;
|
||||
|
||||
// One process serving both, the shape the Docker image runs. The build comes
|
||||
// first because serving a stale `dist` silently tests the last change but one.
|
||||
case "serve": {
|
||||
const server = buildServer();
|
||||
run(...runVite(["-C", "apps/yaak-client", "build"]));
|
||||
serve(server, ["--serve", DIST]);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
console.error(`Unknown mode ${JSON.stringify(mode)}; expected dev, proxy, build or serve`);
|
||||
process.exit(1);
|
||||
}
|
||||
85
scripts/run-workspaces-dev.mjs
Normal file
85
scripts/run-workspaces-dev.mjs
Normal file
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Runs `npm run dev` in parallel for the provided workspaces, or all
|
||||
* workspaces with a dev script when none are specified.
|
||||
* Handles cleanup of child processes on exit.
|
||||
*/
|
||||
|
||||
import { spawn } from "child_process";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const rootDir = path.join(__dirname, "..");
|
||||
|
||||
// Read root package.json to get workspaces
|
||||
const rootPkg = JSON.parse(fs.readFileSync(path.join(rootDir, "package.json"), "utf8"));
|
||||
const workspaces = rootPkg.workspaces || [];
|
||||
|
||||
const requestedWorkspaces = process.argv.slice(2);
|
||||
const workspaceCandidates = requestedWorkspaces.length > 0 ? requestedWorkspaces : workspaces;
|
||||
|
||||
// Find all candidate workspaces with a dev script
|
||||
const workspacesWithDev = workspaceCandidates.filter((ws) => {
|
||||
const pkgPath = path.join(rootDir, ws, "package.json");
|
||||
if (!fs.existsSync(pkgPath)) return false;
|
||||
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
||||
return pkg.scripts?.dev != null;
|
||||
});
|
||||
|
||||
if (workspacesWithDev.length === 0) {
|
||||
console.log("No workspaces with dev script found");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
console.log(`Starting dev for ${workspacesWithDev.length} workspaces...`);
|
||||
|
||||
const children = [];
|
||||
|
||||
// Spawn all dev processes
|
||||
for (const ws of workspacesWithDev) {
|
||||
const cwd = path.join(rootDir, ws);
|
||||
const child = spawn("npm", ["run", "dev"], {
|
||||
cwd,
|
||||
stdio: "inherit",
|
||||
shell: process.platform === "win32",
|
||||
});
|
||||
|
||||
child.on("error", (err) => {
|
||||
console.error(`Error in ${ws}:`, err.message);
|
||||
});
|
||||
|
||||
children.push({ ws, child });
|
||||
}
|
||||
|
||||
// Cleanup function to kill all children
|
||||
function cleanup() {
|
||||
for (const { ws: _ws, child } of children) {
|
||||
if (child.exitCode === null) {
|
||||
// Process still running
|
||||
if (process.platform === "win32") {
|
||||
spawn("taskkill", ["/pid", child.pid, "/f", "/t"], { shell: true });
|
||||
} else {
|
||||
child.kill("SIGTERM");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle various exit signals
|
||||
process.on("SIGINT", () => {
|
||||
cleanup();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.on("SIGTERM", () => {
|
||||
cleanup();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.on("exit", cleanup);
|
||||
|
||||
// Keep the process running
|
||||
process.stdin.resume();
|
||||
114
scripts/vendor-node.cjs
Normal file
114
scripts/vendor-node.cjs
Normal file
@@ -0,0 +1,114 @@
|
||||
const path = require("node:path");
|
||||
const crypto = require("node:crypto");
|
||||
const fs = require("node:fs");
|
||||
const Downloader = require("nodejs-file-downloader");
|
||||
const { extractArchive } = require("./extract-archive.cjs");
|
||||
const { rmSync, cpSync, mkdirSync, existsSync } = require("node:fs");
|
||||
const { execSync } = require("node:child_process");
|
||||
|
||||
const nodeVersionFile = path.join(__dirname, "..", "packages", "plugin-runtime", ".node-version");
|
||||
const NODE_VERSION = `v${fs.readFileSync(nodeVersionFile, "utf8").trim().replace(/^v/, "")}`;
|
||||
|
||||
// `${process.platform}_${process.arch}`
|
||||
const MAC_ARM = "darwin_arm64";
|
||||
const MAC_X64 = "darwin_x64";
|
||||
const LNX_ARM = "linux_arm64";
|
||||
const LNX_X64 = "linux_x64";
|
||||
const WIN_X64 = "win32_x64";
|
||||
const WIN_ARM = "win32_arm64";
|
||||
|
||||
const URL_MAP = {
|
||||
[MAC_ARM]: `https://nodejs.org/download/release/${NODE_VERSION}/node-${NODE_VERSION}-darwin-arm64.tar.gz`,
|
||||
[MAC_X64]: `https://nodejs.org/download/release/${NODE_VERSION}/node-${NODE_VERSION}-darwin-x64.tar.gz`,
|
||||
[LNX_ARM]: `https://nodejs.org/download/release/${NODE_VERSION}/node-${NODE_VERSION}-linux-arm64.tar.gz`,
|
||||
[LNX_X64]: `https://nodejs.org/download/release/${NODE_VERSION}/node-${NODE_VERSION}-linux-x64.tar.gz`,
|
||||
[WIN_X64]: `https://nodejs.org/download/release/${NODE_VERSION}/node-${NODE_VERSION}-win-x64.zip`,
|
||||
[WIN_ARM]: `https://nodejs.org/download/release/${NODE_VERSION}/node-${NODE_VERSION}-win-arm64.zip`,
|
||||
};
|
||||
|
||||
const SRC_BIN_MAP = {
|
||||
[MAC_ARM]: `node-${NODE_VERSION}-darwin-arm64/bin/node`,
|
||||
[MAC_X64]: `node-${NODE_VERSION}-darwin-x64/bin/node`,
|
||||
[LNX_ARM]: `node-${NODE_VERSION}-linux-arm64/bin/node`,
|
||||
[LNX_X64]: `node-${NODE_VERSION}-linux-x64/bin/node`,
|
||||
[WIN_X64]: `node-${NODE_VERSION}-win-x64/node.exe`,
|
||||
[WIN_ARM]: `node-${NODE_VERSION}-win-arm64/node.exe`,
|
||||
};
|
||||
|
||||
const DST_BIN_MAP = {
|
||||
[MAC_ARM]: "yaaknode",
|
||||
[MAC_X64]: "yaaknode",
|
||||
[LNX_ARM]: "yaaknode",
|
||||
[LNX_X64]: "yaaknode",
|
||||
[WIN_X64]: "yaaknode.exe",
|
||||
[WIN_ARM]: "yaaknode.exe",
|
||||
};
|
||||
|
||||
const SHA256_MAP = {
|
||||
[MAC_ARM]: "b05aa3a66efe680023f930bd5af3fdbbd542794da5644ca2ad711d68cbd4dc35",
|
||||
[MAC_X64]: "096081b6d6fcdd3f5ba0f5f1d44a47e83037ad2e78eada26671c252fe64dd111",
|
||||
[LNX_ARM]: "0dc93ec5c798b0d347f068db6d205d03dea9a71765e6a53922b682b91265d71f",
|
||||
[LNX_X64]: "58a5ff5cc8f2200e458bea22e329d5c1994aa1b111d499ca46ec2411d58239ca",
|
||||
[WIN_X64]: "5355ae6d7c49eddcfde7d34ac3486820600a831bf81dc3bdca5c8db6a9bb0e76",
|
||||
[WIN_ARM]: "ce9ee4e547ebdff355beb48e309b166c24df6be0291c9eaf103ce15f3de9e5b4",
|
||||
};
|
||||
|
||||
const key = `${process.platform}_${process.env.YAAK_TARGET_ARCH ?? process.arch}`;
|
||||
|
||||
const destDir = path.join(__dirname, `..`, "crates-tauri", "yaak-app-client", "vendored", "node");
|
||||
const binDest = path.join(destDir, DST_BIN_MAP[key]);
|
||||
console.log(`Vendoring NodeJS ${NODE_VERSION} for ${key}`);
|
||||
|
||||
if (existsSync(binDest) && tryExecSync(`${binDest} --version`).trim() === NODE_VERSION) {
|
||||
console.log("NodeJS already vendored");
|
||||
return;
|
||||
}
|
||||
|
||||
rmSync(destDir, { recursive: true, force: true });
|
||||
mkdirSync(destDir, { recursive: true });
|
||||
|
||||
const url = URL_MAP[key];
|
||||
const tmpDir = path.join(__dirname, "tmp-node");
|
||||
rmSync(tmpDir, { recursive: true, force: true });
|
||||
|
||||
(async function () {
|
||||
// Download GitHub release artifact
|
||||
console.log("Downloading NodeJS at", url);
|
||||
const { filePath } = await new Downloader({
|
||||
url,
|
||||
directory: tmpDir,
|
||||
timeout: 1000 * 60 * 2,
|
||||
}).download();
|
||||
|
||||
// Verify SHA256
|
||||
const expectedHash = SHA256_MAP[key];
|
||||
const fileBuffer = fs.readFileSync(filePath);
|
||||
const actualHash = crypto.createHash("sha256").update(fileBuffer).digest("hex");
|
||||
if (actualHash !== expectedHash) {
|
||||
throw new Error(
|
||||
`SHA256 mismatch for ${path.basename(filePath)}\n expected: ${expectedHash}\n actual: ${actualHash}`,
|
||||
);
|
||||
}
|
||||
console.log("SHA256 verified:", actualHash);
|
||||
|
||||
// Decompress to the same directory
|
||||
await extractArchive(filePath, tmpDir);
|
||||
|
||||
// Copy binary
|
||||
const binSrc = path.join(tmpDir, SRC_BIN_MAP[key]);
|
||||
cpSync(binSrc, binDest);
|
||||
rmSync(tmpDir, { recursive: true, force: true });
|
||||
|
||||
console.log("Downloaded NodeJS to", binDest);
|
||||
})().catch((err) => {
|
||||
console.log("Script failed:", err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
function tryExecSync(cmd) {
|
||||
try {
|
||||
return execSync(cmd, { stdio: "pipe" }).toString("utf-8");
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
41
scripts/vendor-plugins.cjs
Normal file
41
scripts/vendor-plugins.cjs
Normal file
@@ -0,0 +1,41 @@
|
||||
const { readdirSync, cpSync, existsSync, mkdirSync } = require("node:fs");
|
||||
const path = require("node:path");
|
||||
|
||||
const pluginsDir = path.join(__dirname, "..", "plugins");
|
||||
const externalPluginsDir = path.join(__dirname, "..", "plugins-external");
|
||||
|
||||
// Get list of external (non-bundled) plugins
|
||||
const externalPlugins = new Set();
|
||||
if (existsSync(externalPluginsDir)) {
|
||||
for (const name of readdirSync(externalPluginsDir)) {
|
||||
if (!name.startsWith(".")) {
|
||||
externalPlugins.add(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Copying Yaak plugins to", pluginsDir);
|
||||
|
||||
for (const name of readdirSync(pluginsDir)) {
|
||||
const dir = path.join(pluginsDir, name);
|
||||
if (name.startsWith(".")) continue;
|
||||
if (externalPlugins.has(name)) {
|
||||
console.log(`Skipping ${name} (external plugin)`);
|
||||
continue;
|
||||
}
|
||||
const destDir = path.join(
|
||||
__dirname,
|
||||
"..",
|
||||
"crates-tauri",
|
||||
"yaak-app-client",
|
||||
"vendored",
|
||||
"plugins",
|
||||
name,
|
||||
);
|
||||
mkdirSync(destDir, { recursive: true });
|
||||
console.log(`Copying ${name} to ${destDir}`);
|
||||
cpSync(path.join(dir, "package.json"), path.join(destDir, "package.json"));
|
||||
cpSync(path.join(dir, "build"), path.join(destDir, "build"), {
|
||||
recursive: true,
|
||||
});
|
||||
}
|
||||
114
scripts/vendor-protoc.cjs
Normal file
114
scripts/vendor-protoc.cjs
Normal file
@@ -0,0 +1,114 @@
|
||||
const crypto = require("node:crypto");
|
||||
const fs = require("node:fs");
|
||||
const Downloader = require("nodejs-file-downloader");
|
||||
const { extractArchive } = require("./extract-archive.cjs");
|
||||
const path = require("node:path");
|
||||
const { rmSync, mkdirSync, cpSync, existsSync, statSync, chmodSync } = require("node:fs");
|
||||
const { execSync } = require("node:child_process");
|
||||
|
||||
const VERSION = "33.1";
|
||||
|
||||
// `${process.platform}_${process.arch}`
|
||||
const MAC_ARM = "darwin_arm64";
|
||||
const MAC_X64 = "darwin_x64";
|
||||
const LNX_ARM = "linux_arm64";
|
||||
const LNX_X64 = "linux_x64";
|
||||
const WIN_X64 = "win32_x64";
|
||||
const WIN_ARM = "win32_arm64";
|
||||
|
||||
const URL_MAP = {
|
||||
[MAC_ARM]: `https://github.com/protocolbuffers/protobuf/releases/download/v${VERSION}/protoc-${VERSION}-osx-aarch_64.zip`,
|
||||
[MAC_X64]: `https://github.com/protocolbuffers/protobuf/releases/download/v${VERSION}/protoc-${VERSION}-osx-x86_64.zip`,
|
||||
[LNX_ARM]: `https://github.com/protocolbuffers/protobuf/releases/download/v${VERSION}/protoc-${VERSION}-linux-aarch_64.zip`,
|
||||
[LNX_X64]: `https://github.com/protocolbuffers/protobuf/releases/download/v${VERSION}/protoc-${VERSION}-linux-x86_64.zip`,
|
||||
[WIN_X64]: `https://github.com/protocolbuffers/protobuf/releases/download/v${VERSION}/protoc-${VERSION}-win64.zip`,
|
||||
[WIN_ARM]: `https://github.com/protocolbuffers/protobuf/releases/download/v${VERSION}/protoc-${VERSION}-win64.zip`,
|
||||
};
|
||||
|
||||
const SRC_BIN_MAP = {
|
||||
[MAC_ARM]: "bin/protoc",
|
||||
[MAC_X64]: "bin/protoc",
|
||||
[LNX_ARM]: "bin/protoc",
|
||||
[LNX_X64]: "bin/protoc",
|
||||
[WIN_X64]: "bin/protoc.exe",
|
||||
[WIN_ARM]: "bin/protoc.exe",
|
||||
};
|
||||
|
||||
const DST_BIN_MAP = {
|
||||
[MAC_ARM]: "yaakprotoc",
|
||||
[MAC_X64]: "yaakprotoc",
|
||||
[LNX_ARM]: "yaakprotoc",
|
||||
[LNX_X64]: "yaakprotoc",
|
||||
[WIN_X64]: "yaakprotoc.exe",
|
||||
[WIN_ARM]: "yaakprotoc.exe",
|
||||
};
|
||||
|
||||
const SHA256_MAP = {
|
||||
[MAC_ARM]: "db7e66ff7f9080614d0f5505a6b0ac488cf89a15621b6a361672d1332ec2e14e",
|
||||
[MAC_X64]: "e20b5f930e886da85e7402776a4959efb1ed60c57e72794bcade765e67abaa82",
|
||||
[LNX_ARM]: "6018147740548e0e0f764408c87f4cd040e6e1c1203e13aeacaf811892b604f3",
|
||||
[LNX_X64]: "f3340e28a83d1c637d8bafdeed92b9f7db6a384c26bca880a6e5217b40a4328b",
|
||||
[WIN_X64]: "d7a207fb6eec0e4b1b6613be3b7d11905375b6fd1147a071116eb8e9f24ac53b",
|
||||
[WIN_ARM]: "d7a207fb6eec0e4b1b6613be3b7d11905375b6fd1147a071116eb8e9f24ac53b",
|
||||
};
|
||||
|
||||
const dstDir = path.join(__dirname, `..`, "crates-tauri", "yaak-app-client", "vendored", "protoc");
|
||||
const key = `${process.platform}_${process.env.YAAK_TARGET_ARCH ?? process.arch}`;
|
||||
console.log(`Vendoring protoc ${VERSION} for ${key}`);
|
||||
|
||||
const url = URL_MAP[key];
|
||||
const tmpDir = path.join(__dirname, "tmp-protoc");
|
||||
const binSrc = path.join(tmpDir, SRC_BIN_MAP[key]);
|
||||
const binDst = path.join(dstDir, DST_BIN_MAP[key]);
|
||||
|
||||
if (existsSync(binDst) && tryExecSync(`${binDst} --version`).trim().includes(VERSION)) {
|
||||
console.log("Protoc already vendored");
|
||||
return;
|
||||
}
|
||||
|
||||
rmSync(tmpDir, { recursive: true, force: true });
|
||||
rmSync(dstDir, { recursive: true, force: true });
|
||||
mkdirSync(dstDir, { recursive: true });
|
||||
|
||||
(async function () {
|
||||
// Download GitHub release artifact
|
||||
const { filePath } = await new Downloader({ url, directory: tmpDir }).download();
|
||||
|
||||
// Verify SHA256
|
||||
const expectedHash = SHA256_MAP[key];
|
||||
const fileBuffer = fs.readFileSync(filePath);
|
||||
const actualHash = crypto.createHash("sha256").update(fileBuffer).digest("hex");
|
||||
if (actualHash !== expectedHash) {
|
||||
throw new Error(
|
||||
`SHA256 mismatch for ${path.basename(filePath)}\n expected: ${expectedHash}\n actual: ${actualHash}`,
|
||||
);
|
||||
}
|
||||
console.log("SHA256 verified:", actualHash);
|
||||
|
||||
// Decompress to the same directory
|
||||
await extractArchive(filePath, tmpDir);
|
||||
|
||||
// Copy binary
|
||||
cpSync(binSrc, binDst);
|
||||
|
||||
// Copy other files
|
||||
const includeSrc = path.join(tmpDir, "include");
|
||||
const includeDst = path.join(dstDir, "include");
|
||||
cpSync(includeSrc, includeDst, { recursive: true });
|
||||
rmSync(tmpDir, { recursive: true, force: true });
|
||||
|
||||
// Make binary writable, so we can sign it during release
|
||||
const stat = statSync(binDst);
|
||||
const newMode = stat.mode | 0o200;
|
||||
chmodSync(binDst, newMode);
|
||||
|
||||
console.log("Downloaded protoc to", binDst);
|
||||
})().catch((err) => console.log("Script failed:", err));
|
||||
|
||||
function tryExecSync(cmd) {
|
||||
try {
|
||||
return execSync(cmd, { stdio: "pipe" }).toString("utf-8");
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user