chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:25:07 +08:00
commit a26e856398
1681 changed files with 296950 additions and 0 deletions
+31
View File
@@ -0,0 +1,31 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import * as dotenv from "dotenv";
import { get_scope } from "./sh_perspective.mjs";
import { execSync } from "node:child_process";
import "zx/globals";
dotenv.config({ path: "./.perspectiverc", quiet: true });
const scope = get_scope();
if (scope.includes("viewer-charts")) {
await $`pnpm run --recursive --filter bench bench_charts`.pipe(
process.stdout,
);
} else if (scope.includes("client")) {
await $`pnpm run --recursive --filter bench bench_js`.pipe(process.stdout);
} else if (scope.includes("python")) {
await $`pnpm run --recursive --filter bench bench_python`.pipe(
process.stdout,
);
}
+25
View File
@@ -0,0 +1,25 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import * as fs from "fs";
import * as dotenv from "dotenv";
import { run_with_scope } from "./sh_perspective.mjs";
dotenv.config({ path: "./.perspectiverc", quiet: true });
if (!fs.existsSync("./.perspectiverc") && !process.env.PACKAGE) {
console.error("No .perspectiverc, running setup");
process.env.PSP_BUILD_IMMEDIATELY = 1;
await import("./setup.mjs");
} else {
await run_with_scope`build`;
}
+56
View File
@@ -0,0 +1,56 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import { clean, get_scope } from "./sh_perspective.mjs";
import { execSync } from "child_process";
import * as fs from "node:fs";
const PACKAGES = get_scope();
const JS_PKGS = [];
const RUST_PKGS = [];
const CRATE_NAMES = fs.readdirSync("rust");
for (const pkg of PACKAGES) {
if (pkg === "perspective-server") {
console.log("-- Cleaning perspective-server");
clean("rust/perspective-server/dist");
} else if (CRATE_NAMES.indexOf(pkg) > -1) {
RUST_PKGS.push(pkg);
} else {
JS_PKGS.push(pkg);
}
}
if (JS_PKGS.length > 0 || RUST_PKGS.length > 0) {
console.log(`-- Cleaning ${JS_PKGS.join(", ")} via pnpm`);
const flags = JS_PKGS.concat(RUST_PKGS)
.map(
(x) =>
`--filter @perspective-dev/${x} --if-present --filter ${x} --if-present`,
)
.join(" ");
execSync(`pnpm run ${flags} clean`, { stdio: "inherit" });
}
if (RUST_PKGS.length > 0) {
if (process.env.PACKAGE?.length > 1) {
console.log(`-- Cleaning ${RUST_PKGS.join(", ")} via cargo`);
execSync(`cargo clean ${RUST_PKGS.map((x) => `-p ${x}`).join(" ")}`);
} else {
console.log(`-- Cleaning all crates via cargo`);
execSync(`cargo clean`);
}
}
clean("docs/build", "docs/python", "docs/obj");
+21
View File
@@ -0,0 +1,21 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import * as dotenv from "dotenv";
import "zx/globals";
import { run_with_scope } from "./sh_perspective.mjs";
dotenv.config({ path: "./.perspectiverc", quiet: true });
const HOST = /host\: (.+?)$/gm.exec(await $`rustc -vV`)[1];
await $`cargo doc --no-deps --target=${HOST}`;
await run_with_scope`docs`;
+26
View File
@@ -0,0 +1,26 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import * as url from "url";
import * as dotenv from "dotenv";
import { lint_js, lint_python } from "./lint.mjs";
if (import.meta.url.startsWith("file:")) {
if (process.argv[1] === url.fileURLToPath(import.meta.url)) {
const { default: run } = await import("./lint_headers.mjs");
await run(true);
dotenv.config({ path: "./.perspectiverc", quiet: true });
await lint_js(true);
lint_python(true);
}
}
+66
View File
@@ -0,0 +1,66 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import path from "path";
import os from "os";
import fs from "fs";
import * as dotenv from "dotenv";
import { getWorkspaceRoot, getWorkspacePackageJson } from "./workspace.mjs";
import "zx/globals";
const pkg = getWorkspacePackageJson();
const emscripten = pkg.emscripten;
dotenv.config({ path: "./.perspectiverc", quiet: true });
function base() {
return path.resolve(getWorkspaceRoot(), ".emsdk").replace(/\\/g, "/");
}
function emsdk_checkout() {
$.sync`git clone https://github.com/emscripten-core/emsdk.git ${base()}`;
}
function emsdk(...args) {
const basedir = base();
const suffix = os.type() == "Windows_NT" ? ".bat" : "";
const emsdkBin = path.join(basedir, "emsdk" + suffix).replace(/\\/g, "/");
$.sync`${emsdkBin} ${args}`;
}
function toolchain_install() {
console.log(`-- Installing Emscripten ${emscripten}`);
$.sync({ cwd: ".emsdk" })`git pull`;
emsdk("install", emscripten);
emsdk("activate", emscripten);
console.log(`-- Emscripten ${emscripten} installed`);
}
function repo_check() {
return fs.existsSync(path.join(base(), "emsdk_env.sh"));
}
if (!process.env.PSP_SKIP_EMSDK_INSTALL) {
// if a stale toolchain is still activated in the shell, these vars break
// emsdk install in a confusing way. ensure they are unset
for (let ev of ["EMSDK", "EMSDK_NODE", "EMSDK_PYTHON", "SSL_CERT_FILE"]) {
delete process.env[ev];
}
if (!repo_check()) {
emsdk_checkout();
}
toolchain_install();
}
+72
View File
@@ -0,0 +1,72 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import os from "os";
import * as fs from "node:fs";
import { mkdirSync } from "fs";
import { execSync } from "child_process";
import path from "path";
import { getWorkspaceRoot, getWorkspacePackageJson } from "./workspace.mjs";
const pkg = getWorkspacePackageJson();
const LLVM_VERSION = pkg.llvm;
const DOWNLOAD_DIR = path.join(getWorkspaceRoot(), ".llvm", "llvm-toolchain");
function getLLVMPackageName() {
const system = os.platform();
const arch = os.arch();
if (arch === "arm64" && system === "linux") {
return `clang+llvm-${LLVM_VERSION}-aarch64-linux-gnu.tar.xz`;
} else if (arch === "x64" && system === "linux") {
return `clang+llvm-${LLVM_VERSION}-x86_64-linux-gnu-ubuntu-22.04.tar.xz`;
} else if (arch === "x64" && system === "darwin") {
return `clang+llvm-${LLVM_VERSION}-x86_64-apple-darwin21.0.tar.xz`;
} else if (arch === "arm64" && system === "darwin") {
return `clang+llvm-${LLVM_VERSION}-arm64-apple-darwin22.0.tar.xz`;
} else {
throw new Error(`Unsupported system: ${arch} on ${system}`);
}
}
async function downloadLLVM(packageName) {
const llvmUrl = `https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VERSION}/${packageName}`;
const outputPath = path.join(DOWNLOAD_DIR, packageName);
console.log(`Downloading LLVM ${LLVM_VERSION} from ${llvmUrl}...`);
mkdirSync(DOWNLOAD_DIR, { recursive: true });
fs.writeFileSync(`${DOWNLOAD_DIR}/.llvm-version`, LLVM_VERSION);
execSync(`wget -O ${outputPath} ${llvmUrl}`, { stdio: "inherit" });
execSync(`tar -xvf ${outputPath} -C ${DOWNLOAD_DIR} --strip-components=1`, {
stdio: "inherit",
});
execSync(`rm ${outputPath}`, { stdio: "inherit" });
}
const llvmPackageName = getLLVMPackageName();
if (fs.existsSync(DOWNLOAD_DIR)) {
console.log(`LLVM ${LLVM_VERSION} already downloaded`);
if (
!fs.existsSync(`${DOWNLOAD_DIR}/.llvm-version`) ||
fs.readFileSync(`${DOWNLOAD_DIR}/.llvm-version`).toString().trim() !==
LLVM_VERSION
) {
console.log(`LLVM version mismatch, re-downloading...`);
fs.rmdirSync(DOWNLOAD_DIR, { recursive: true });
downloadLLVM(llvmPackageName);
}
} else {
downloadLLVM(llvmPackageName);
}
+57
View File
@@ -0,0 +1,57 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
// Download a full Pyodide distribution from github and extract it to rust/target dir for
// use in integration tests
import { getPyodideVersion, getPyodideDownloadDir } from "./pyodide.mjs";
import assert from "node:assert";
import fs from "node:fs";
import path from "node:path";
import { execFileSync } from "node:child_process";
const pyodideVersion = getPyodideVersion();
function downloadPyodide() {
const pyodideUrl = `https://github.com/pyodide/pyodide/releases/download/${pyodideVersion}/pyodide-${pyodideVersion}.tar.bz2`;
const downloadDir = getPyodideDownloadDir(); // the download dir is versioned
const tarball = path.join(downloadDir, `pyodide-${pyodideVersion}.tar.bz2`);
const buildStamp = path.join(downloadDir, "psp-build-stamp.txt");
const pyodideLock = path.join(downloadDir, "pyodide", "pyodide-lock.json");
if (fs.existsSync(buildStamp) && fs.existsSync(pyodideLock)) {
console.log(
`Pyodide ${pyodideVersion} already extracted to ${downloadDir}`,
);
} else {
fs.rmSync(buildStamp, { force: true });
console.log(
`Downloading Pyodide ${pyodideVersion} from ${pyodideUrl}...`,
);
fs.mkdirSync(downloadDir, { recursive: true });
execFileSync("wget", ["-O", tarball, pyodideUrl], {
stdio: "inherit",
});
console.log(`Extracting ${tarball}...`);
execFileSync("tar", ["-xvf", tarball, "-C", downloadDir], {
stdio: "inherit",
});
console.log(`Removing ${tarball}...`);
fs.rmSync(tarball);
// assert presence of a known file
assert(fs.existsSync(pyodideLock), `${pyodideLock} not found`);
console.log(`Extracted to ${downloadDir}`);
fs.writeFileSync(buildStamp, ""); // prevent re-download/extract
}
}
downloadPyodide();
+76
View File
@@ -0,0 +1,76 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import { get_scope } from "./sh_perspective.mjs";
import * as url from "url";
import * as dotenv from "dotenv";
import "zx/globals";
export async function lint_js(is_fix = false) {
const eslint_flags = is_fix ? ["--fix"] : [];
await $`eslint ${eslint_flags} .`.verbose();
const prettier_flags = is_fix ? "--write" : "--check";
await $`prettier ${prettier_flags} \
"examples/**/*.js" \
"examples/**/*.tsx" \
"tools/scripts/*.mjs" \
"rust/**/*.ts" \
"rust/**/*.js" \
"packages/**/*.js" \
"packages/**/*.ts" \
"**/css/**/*.css" \
"**/themes/**/*.css" \
"**/html/*.html" \
"packages/**/package.json" \
"rust/**/package.json" \
"examples/**/package.json" \
"docs/package.json" \
`.verbose();
await $`prettier --prose-wrap=always ${prettier_flags} "rust/*/docs/**/*.md"`.verbose();
const check = is_fix ? [] : ["--check"];
const dirty = is_fix ? ["--allow-dirty"] : [];
const staged = is_fix ? ["--allow-staged"] : [];
const fix = is_fix ? ["--fix"] : [];
await $`cargo build -p perspective-lint --features=yew-fmt`.verbose();
await $`cargo clippy ${fix} ${dirty} ${staged} -p perspective-viewer -- -Dwarnings`.verbose();
await $`RUSTFMT="rust/target/debug/lint" cargo fmt ${check}`.verbose();
}
export function lint_python(is_fix = false) {
if (get_scope().indexOf("perspective-python") > -1) {
if (is_fix) {
$.sync`ruff check --fix`;
} else {
$.sync`ruff check`;
}
}
}
if (import.meta.url.startsWith("file:")) {
if (process.argv[1] === url.fileURLToPath(import.meta.url)) {
dotenv.config({ path: "./.perspectiverc", quiet: true });
const { default: run } = await import("./lint_headers.mjs");
const exit_code = await run(false);
// if (process.env.PSP_PROJECT === "python") {
// await import("./lint_python.mjs");
// } else {
await lint_js();
lint_python();
// }
process.exit(exit_code);
}
}
+119
View File
@@ -0,0 +1,119 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import { glob } from "glob-gitignore";
import { promises as fs } from "fs";
import * as fs_sync from "fs";
const IGNORE_PATHS = fs_sync
.readFileSync(".gitignore")
.toString()
.split("\n")
.map((x) => x.trim())
.filter((x) => x.length > 0 && !x.startsWith("#"))
.concat(["llvm/*", "cmake/*", "pnpm-lock.yaml", "pnpm-workspace.yaml"]);
const FIX_PATHS = [
["**/*.rs", ["//", "/*", " *", " */"]],
["**/*.js", ["//", "/*", " *", " */"]],
["**/*.mjs", ["//", "/*", " *", " */"]],
// ["**/*.css", ["//", "/*", " *", " */"]],
["**/*.scss", ["//", "/*", " *", " */"]],
["**/*.toml", ["# ", "#"]],
["**/*.yaml", ["# ", "#"]],
["**/*.py", ["# ", "#"]],
["**/*.ts", ["//", "/*", " *", " */"]],
["**/*.tsx", ["//", "/*", " *", " */"]],
["**/*.cpp", ["//", "/*", " *", " */"]],
["**/*.h", ["//", "/*", " *", " */"]],
[".github/**/*.yaml", ["# ", "#"]],
];
function header_text(c, ...args) {
return (
`
${c} ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
${c} ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
${c} ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
${c} ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
${c} ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
${c} ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
${c} ┃ Copyright (c) 2017, the Perspective Authors. ┃
${c} ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
${c} ┃ This file is part of the Perspective library, distributed under the terms ┃
${c} ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
${c} ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
`.trim() + args.join("")
);
}
async function check(is_write, pattern, comment) {
const matches = await glob(pattern, {
ignore: IGNORE_PATHS,
});
let exit_code = 0;
for (const match of matches) {
if (fs_sync.lstatSync(match).isDirectory()) {
continue;
}
const data = await fs.readFile(match);
let contents = data.toString();
let default_comment = comment;
if (Array.isArray(comment)) {
default_comment = comment[0];
} else {
comment = [comment];
}
const expected_header = header_text(default_comment);
let seen_whitespace = false;
// Nightly builds modify the version of metadata scripts in place to
// tag the nightly, so lint will fail if we don't make an exception
// for these files.
const is_nightly =
process.argv.indexOf("--nightly") > -1 &&
(match.indexOf("Cargo.toml") > -1 ||
match.indexOf("pyproject.toml") > -1);
if (!contents.startsWith(expected_header) && !is_nightly) {
console.error(`Missing header in file ${match}`);
while (
contents.length > 0 &&
((!seen_whitespace &&
comment.some((x) => contents.startsWith(x))) ||
(seen_whitespace = contents.startsWith("\n")))
) {
contents = contents.substring(contents.indexOf("\n") + 1);
}
if (is_write) {
await fs.writeFile(match, `${expected_header}\n\n${contents}`);
} else {
exit_code = 1;
}
}
}
return exit_code;
}
export default async function run(is_write) {
let exit_code = 0;
for (const [patt, comment] of FIX_PATHS) {
const result = await check(is_write, patt, comment);
exit_code = exit_code || result;
}
return exit_code;
}
+28
View File
@@ -0,0 +1,28 @@
{
"name": "@perspective-dev/scripts",
"version": "4.5.2",
"description": "Build scripts based on perspective",
"private": true,
"files": [
"**/*"
],
"repository": {
"type": "git",
"url": "https://github.com/perspective-dev/perspective/tools/scripts"
},
"author": "",
"license": "Apache-2.0",
"devDependencies": {
"auto-changelog": "catalog:",
"fs-extra": "catalog:",
"inquirer": "catalog:",
"glob-gitignore": "catalog:",
"octokit": "catalog:",
"typescript": "catalog:",
"tsx": "catalog:",
"@iarna/toml": "catalog:",
"@playwright/experimental-ct-react": "catalog:",
"@playwright/test": "catalog:",
"zx": "catalog:"
}
}
+70
View File
@@ -0,0 +1,70 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import "zx/globals";
function lint_git(sha) {
if (!sha || typeof sha !== "string") {
throw new Error(`invalid sha: ${sha}`);
}
const result = $.sync`git log -1 ${sha} | grep -F "Signed-off-by: "`;
if (result.exitCode !== 0) {
console.error(
"`git log -1 " +
sha +
"` is missing a Signed-off-by: and DCO check will surely fail.",
);
console.error("To sign off, run:\ngit commit --amend --edit --sign");
process.exit(1);
}
}
async function readPrePushInput() {
// Git supplies information about the push to the hook on stdin.
// https://git-scm.com/docs/githooks#_pre_push
const chunks = [];
if (process.stdin.isTTY) {
// Makes developing the pre-push script more convenient. In particular
// when you run `pnpm run prepush` from a shell terminal you don't have
// to send EOF on stdin.
return [];
}
for await (const chunk of process.stdin) {
chunks.push(chunk);
}
const input = Buffer.concat(chunks).toString();
const lines = input.split("\n").filter((l) => l.length > 0);
return lines.map((line) => {
const parts = line.trim().split(" ");
return {
local_ref: parts[0],
local_object_name: parts[1],
remote_ref: parts[2],
remote_object_name: parts[3],
};
});
}
if (import.meta.main) {
// Does not actually run all pre-push hook checks (it does not run the repo
// lint script). These are lints which run only in pre-push. The
// `prepush` script defined in package.json is responsible for running the
// repo lint script.
const pushes = await readPrePushInput();
for (const push of pushes) {
const { local_object_name } = push;
lint_git(local_object_name);
}
}
+144
View File
@@ -0,0 +1,144 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import { Octokit } from "octokit";
import fs from "node:fs/promises";
import { execSync } from "child_process";
import "zx/globals";
// GitHub API Wrapper
const OCTOKIT = new Octokit({
auth: process.env.GITHUB_TOKEN,
});
const CURRENT_TAG = $.sync`git describe --exact-match --tags`.toString().trim();
const IS_DIRTY =
(await $`git status --untracked-files=no --porcelain`).stdout.trim()
.length > 0;
async function get_release_assets() {
const resp = await OCTOKIT.request("GET /repos/{owner}/{repo}/releases", {
owner: "perspective-dev",
repo: "perspective",
});
for (const release of resp.data) {
if (release.tag_name === CURRENT_TAG) {
return release.assets;
}
}
throw new Error(`No release ${CURRENT_TAG} found`);
}
async function download_release_assets(releases) {
await Promise.all(
releases.map(async (release) => {
const resp = await OCTOKIT.request(
"GET /repos/{owner}/{repo}/releases/assets/{asset_id}",
{
owner: "perspective-dev",
repo: "perspective",
asset_id: release.id,
headers: {
Accept: "application/octet-stream",
},
},
);
console.log(`Writing ${release.name}`);
await fs.writeFile(release.name, Buffer.from(resp.data));
}),
);
}
const SH_ENV = {
env: process.env,
stdio: "inherit",
};
async function publish_release_assets(releases) {
if (process.env.COMMIT) {
for (const release of releases) {
if (
(release.name.endsWith("whl") ||
release.name.endsWith("tar.gz")) &&
release.name.indexOf("wasm") === -1
) {
execSync(`twine upload ${release.name}`, SH_ENV);
} else if (release.name.endsWith(".tgz")) {
execSync(`npm publish ${release.name}`, SH_ENV);
} else {
console.log(`Skipping "${release.name}"`);
}
}
await $`mkdir -p rust/target/package && mv *.crate rust/target/package`;
execSync(
`cargo publish -p perspective-server --allow-dirty --no-verify`,
SH_ENV,
);
execSync(
`cargo publish -p perspective-client --allow-dirty --no-verify`,
SH_ENV,
);
execSync(
`cargo publish -p perspective-python --allow-dirty --no-verify`,
SH_ENV,
);
execSync(
`cargo publish -p perspective-js --allow-dirty --no-verify`,
SH_ENV,
);
execSync(
`cargo publish -p perspective-viewer --allow-dirty --no-verify`,
SH_ENV,
);
execSync(
`cargo publish -p perspective --allow-dirty --no-verify`,
SH_ENV,
);
} else {
console.warn(`COMMIT not specified, aborting`);
}
}
if (!process.env.GITHUB_TOKEN) {
throw new Error("Missing Personal Access Token (GITHUB_TOKEN)");
}
if (!process.env.COMMIT) {
console.warn(
"Running a dry run, this WILL NOT publish. Set the env var COMMIT to publish.",
);
}
if (IS_DIRTY) {
throw new Error("Working tree dirty, aborting");
}
const releases = await get_release_assets();
console.log(`Found ${releases.length} artifacts for ${CURRENT_TAG}`);
for (const release of releases) {
console.log(` ${release.name}`);
}
await download_release_assets(releases);
await publish_release_assets(releases);
+41
View File
@@ -0,0 +1,41 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
// Pyodide information:
// - Pyodide version we build against
// - Pyodide dist directory
import path from "node:path";
import { getWorkspacePackageJson, getRustTargetDir } from "./workspace.mjs";
export function getPyodideVersion() {
const pyodideVersion = getWorkspacePackageJson().pyodide;
if (!pyodideVersion) {
throw new Error(`"pyodide" not set in package.json`);
}
return pyodideVersion;
}
/**
* @returns pyodide download directory for the current version
*/
export function getPyodideDownloadDir() {
return path.join(getRustTargetDir(), "pyodide", getPyodideVersion());
}
/**
* @returns pyodide dist directory for the current version
*/
export function getPyodideDistDir() {
return path.join(getPyodideDownloadDir(), "pyodide");
}
+32
View File
@@ -0,0 +1,32 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import { execSync } from "node:child_process";
import * as fs from "node:fs";
import { getWorkspacePackageJson } from "./workspace.mjs";
const pkg = getWorkspacePackageJson();
const wheel_file = fs.readdirSync(".").filter((x) => x.endsWith(".whl"))[0];
execSync(`wheel unpack ${wheel_file}`);
const pkg_name = wheel_file.split("-").slice(0, 2).join("-");
const version = pkg.version.replace(/-(rc|alpha|beta)\.\d+/, (x) =>
x.replace("-", "").replace(".", ""),
);
const dest = `${pkg_name}/perspective_python-${version}.data`;
const src = `rust/perspective-python/perspective_python-${version}.data`;
fs.cpSync(src, dest, {
recursive: true,
});
execSync(`wheel pack ${pkg_name}`);
+253
View File
@@ -0,0 +1,253 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import inquirer from "inquirer";
import fs from "fs";
import * as dotenv from "dotenv";
import { execSync } from "child_process";
const original = dotenv.config({
path: "./.perspectiverc",
quiet: true,
}).parsed;
const CONFIG = new Proxy(
new (class {
constructor() {
this.config = [];
this._values = { ...original } || {};
if (this._values.PACKAGE && this._values.PACKAGE.startsWith("@")) {
this._values.PACKAGE = this._values.PACKAGE.slice(
2,
this._values.PACKAGE.length - 1,
).replace(/\|/g, ",");
}
}
remove(keyname) {
const idx = this.config.find((x) => s.startsWith(keyname));
if (idx >= 0) {
this.config.splice(idx);
}
}
add(new_config) {
for (const key in new_config) {
const val = new_config[key];
if (val !== "" && !!val) {
this._values[key] = val;
this.config.push(`${key}=${val}`);
}
}
}
write() {
fs.writeFileSync("./.perspectiverc", this.config.join("\n"));
if (process.env.PSP_BUILD_IMMEDIATELY || process.env.PSP_ONCE) {
execSync("node tools/scripts/build.mjs", { stdio: "inherit" });
if (process.env.PSP_ONCE) {
while (this.config.length > 0) {
this.config.pop();
}
for (const key in this._values) {
if (key in original) {
this._values[key] = original[key];
} else {
delete this._values[key];
}
}
if (
this._values.PACKAGE &&
this._values.PACKAGE.startsWith("@")
) {
this._values.PACKAGE = this._values.PACKAGE.slice(
2,
this._values.PACKAGE.length - 1,
).replace(/\|/g, ",");
}
const config = [];
for (const key in this._values) {
config.push(`${key}=${this._values[key]}`);
}
fs.writeFileSync("./.perspectiverc", config.join("\n"));
}
}
}
})(),
{
set: function (target, name, val) {
target.add({
[name]: val,
});
},
get: function (target, name) {
if (name in target._values) {
return target._values[name];
} else {
return target[name];
}
},
},
);
const PROMPT_DEBUG = {
type: "confirm",
name: "PSP_DEBUG",
message: "Run debug build?",
default: CONFIG["PSP_DEBUG"] || false,
};
const PROMPT_DOCKER = {
type: "confirm",
name: "PSP_DOCKER",
message: "Use docker for build env?",
default: CONFIG["PSP_DOCKER"] || false,
};
async function choose_docker() {
const answers = await inquirer.prompt([PROMPT_DOCKER]);
CONFIG.add(answers);
CONFIG.write();
}
async function focus_package() {
const choices = [
{
key: "r",
name: "docs",
value: "docs",
},
{
key: "m",
name: "metadata",
value: "metadata",
},
{
key: "y",
name: "perspective-python",
value: "python",
},
{
key: "q",
name: "perspective-python (pyodide)",
value: "pyodide",
},
{
key: "r",
name: "perspective (rust)",
value: "rust",
},
{
key: "c",
name: "@perspective-dev/server",
value: "server",
},
{
key: "p",
name: "@perspective-dev/client",
value: "client",
},
{
key: "v",
name: "@perspective-dev/viewer",
value: "viewer",
},
{
key: "e",
name: "@perspective-dev/viewer-datagrid",
value: "viewer-datagrid",
},
{
key: "g",
name: "@perspective-dev/viewer-charts",
value: "viewer-charts",
},
{
key: "i",
name: "@perspective-dev/jupyterlab",
value: "jupyterlab",
},
{
key: "w",
name: "@perspective-dev/workspace",
value: "workspace",
},
{
key: "a",
name: "@perspective-dev/react",
value: "react",
},
];
const new_config = await inquirer.prompt([
{
type: "checkbox",
name: "PACKAGE",
message: "Focus NPM package(s)?",
default: () => {
if (CONFIG["PACKAGE"]) {
const packages = CONFIG["PACKAGE"].split(",");
if (CONFIG["PSP_PYODIDE"] === "1") {
const py = packages.indexOf("python");
if (py >= 0) {
packages[py] = "pyodide";
}
}
return packages;
} else {
return [""];
}
},
filter: (answer) => {
if (!answer || answer.length === choices.length) {
return "";
} else {
return answer;
}
},
loop: false,
pageSize: 20,
choices,
},
]);
if (Array.isArray(new_config.PACKAGE)) {
if (new_config.PACKAGE.length > 0) {
let pyodide = new_config.PACKAGE.indexOf("pyodide");
if (pyodide >= 0) {
new_config.PACKAGE.splice(pyodide, 1);
new_config.PSP_PYODIDE = 1;
new_config.CI = 1;
new_config.PACKAGE.push("python");
} else {
CONFIG.remove("PSP_PYODIDE");
CONFIG.remove("CI");
}
new_config.PACKAGE = `${new_config.PACKAGE.join(",")}`;
} else {
new_config.PACKAGE = undefined;
}
}
CONFIG.add(new_config);
await javascript_options();
}
async function javascript_options() {
const new_config = await inquirer.prompt([PROMPT_DEBUG, PROMPT_DOCKER]);
CONFIG.add(new_config);
CONFIG.write();
}
focus_package();
+115
View File
@@ -0,0 +1,115 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import * as dotenv from "dotenv";
import * as path from "path";
import * as fs from "fs";
import { execSync } from "child_process";
import "zx/globals";
dotenv.config({ path: "./.perspectiverc", quiet: true });
process.env.FORCE_COLOR = true;
/**
* Calls `path.resolve` on each of the input path arguments, then removes the
* path if it exists.
*
* @param {string} dirs paths to clean.
* @example
* clean("a/b/c"); // Cleans this dir
* clean("a/b/c", "d/e/f"); // Cleans both dirs
*/
export function clean(...dirs) {
for (const dir of dirs) {
const resolved = path.resolve(dir);
if (fs.existsSync(resolved)) {
fs.rmSync(resolved, { recursive: true, force: true });
}
}
}
/**
* Returns the value after this command-line flag, or `true` if it is the last
* arg. This makes it easy to null-pun for boolean flags, and capture the
* argument for argument-providing flags, and respect quotes and parens, in
* one function. Can be used as a template literal - not sure why, 2 less
* characters?
*
* @param {string} flag The command line flag name. Returns all arguments if
* this param is `undefined`.
* @returns {string} The next argument after this flag in the command args, or
* `true.
* @example
* console.assert(getarg`--debug`);
*/
export function getarg(flag, ...args) {
if (Array.isArray(flag)) {
flag = flag.map((x, i) => x + (args[i] || "")).join("");
}
const argv = process.argv.slice(2);
if (flag) {
const index = argv.indexOf(flag);
if (index > -1) {
const next = argv[index + 1];
if (next) {
return next;
} else {
return true;
}
}
} else {
return argv
.map(function (arg) {
return "'" + arg.replace(/'/g, "'\\''") + "'";
})
.join(" ");
}
}
export function get_scope() {
const package_venn = (process.env.PACKAGE || "").split(",").reduce(
(acc, x) => {
if (x.startsWith("!")) {
acc.exclude.push(x);
} else if (x != "") {
acc.include.push(x);
}
return acc;
},
{ include: [], exclude: [] },
);
let packages;
if (package_venn.include.length === 0) {
packages = JSON.parse($.sync`pnpm m ls --json --depth=-1`.toString())
.filter((x) => x.name !== undefined)
.map((x) => x.name.replace("@perspective-dev/", ""))
.filter((x) => package_venn.exclude.indexOf(`!${x}`) === -1);
} else {
packages = package_venn.include.filter(
(x) => package_venn.exclude.indexOf(`!${x}`) === -1,
);
}
return packages;
}
export const run_with_scope = async function run_recursive(strings, ...args) {
let scope = get_scope();
const cmd = strings[0].split(" ")[0];
const filters = scope.map((x) => `--filter ${x} --if-present`).join(" ");
execSync(`pnpm run --sequential --recursive ${filters} ${cmd}`, {
stdio: "inherit",
});
};
+24
View File
@@ -0,0 +1,24 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import * as fs from "fs";
import dotenv from "dotenv";
dotenv.config({ path: "./.perspectiverc", quiet: true });
if (!fs.existsSync("./.perspectiverc") && !process.env.PACKAGE) {
console.error("No .perspectiverc, running setup");
process.env.PSP_BUILD_IMMEDIATELY = 1;
await import("./setup");
} else {
await import("./test_js.mjs");
}
+166
View File
@@ -0,0 +1,166 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import { execSync } from "child_process";
import { getarg, run_with_scope, get_scope } from "./sh_perspective.mjs";
const IS_NEEDS_BUILD = get_scope().some((x) => x === "jupyterlab");
// Unfortunately we have to handle parts of the Jupyter test case here,
// as the Jupyter server needs to be run outside of the main Jest process.
const IS_JUPYTER = !!getarg("--jupyter") && IS_NEEDS_BUILD;
if (getarg("--debug")) {
console.log("-- Running tests in debug mode.");
}
const IS_PLAYWRIGHT = get_scope().reduce(
(is_playwright, pkg) =>
is_playwright ||
[
"docs",
"client",
"react",
"viewer",
"viewer-datagrid",
"viewer-charts",
"viewer-workspace",
"workspace",
"jupyterlab",
].includes(pkg),
false,
);
const IS_RUST = get_scope().reduce(
(is_playwright, pkg) => is_playwright || ["rust"].includes(pkg),
false,
);
const IS_CI = process.env.CI || getarg("--ci") ? "CI=1" : "";
if (IS_CI) {
console.log("-- Running tests in CI mode.");
}
function playwright(pkg, is_jlab) {
const pkg_name = pkg ? `"${pkg}" ` : "";
console.log(`-- Running ${pkg_name}Playwright test suite`);
const args = process.argv
.slice(2)
.filter(
(x) =>
x !== "--ci" && x !== "--jupyter" && x !== "--fetch-snapshots",
);
const env = { ...process.env, TZ: "UTC" };
if (is_jlab) {
env.PSP_JUPYTERLAB_TESTS = "1";
env.__JUPYTERLAB_PORT__ = "6538";
}
if (getarg("--fetch-snapshots")) {
env.PSP_FETCH_SNAPSHOTS = "1";
}
if (getarg("--update-snapshots")) {
env.PSP_UPDATE_SNAPSHOTS = "1";
}
if (IS_CI) {
env.CI = "1";
}
if (pkg) {
env.PACKAGE = pkg;
}
const cmd = [
"npx",
"playwright",
"test",
"--config=tools/test/playwright.config.ts",
...args,
].join(" ");
execSync(cmd, { stdio: "inherit", env });
}
if (!IS_JUPYTER) {
// test:build irrelevant for jupyter tests
await run_with_scope`test:build`;
}
if (process.env.PACKAGE) {
if (IS_NEEDS_BUILD) {
await run_with_scope`test:jupyter:build`;
}
if (IS_JUPYTER) {
// Jupyterlab is guaranteed to have started at this point, so
// copy the test files over and run the tests.
playwright("jupyterlab", true);
process.exit(0);
}
if (IS_PLAYWRIGHT) {
playwright(process.env.PACKAGE);
}
if (
process.env.PACKAGE.indexOf("python") >= 0 &&
process.env.PACKAGE.indexOf("!python") === -1
) {
// Support `pnpm test -- --my_cool --test_arguments`
const args = process.argv.slice(2);
execSync(
`pnpm run --recursive --filter @perspective-dev/python test ${args.join(" ")}`,
{ stdio: "inherit" },
);
}
if (IS_RUST) {
let target = "";
let flags = "--release";
if (!!process.env.PSP_DEBUG) {
flags = "";
}
if (
process.env.PSP_ARCH === "x86_64" &&
process.platform === "darwin"
) {
target = "--target=x86_64-apple-darwin";
} else if (
process.env.PSP_ARCH === "aarch64" &&
process.platform === "darwin"
) {
target = "--target=aarch64-apple-darwin";
} else if (
process.env.PSP_ARCH === "x86_64" &&
process.platform === "linux"
) {
target =
"--target=x86_64-unknown-linux-gnu --compatibility manylinux_2_28";
} else if (
process.env.PSP_ARCH === "aarch64" &&
process.platform === "linux"
) {
target = "--target=aarch64-unknown-linux-gnu";
}
execSync(
`cargo test ${flags} ${target} -p perspective -p perspective-client`,
{ stdio: "inherit" },
);
}
} else {
console.log("-- Running all tests");
playwright();
}
+246
View File
@@ -0,0 +1,246 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import fs from "fs";
import { Octokit } from "octokit";
import { parseReleases } from "auto-changelog/src/releases.js";
import { fetchTags } from "auto-changelog/src/tags.js";
import { fetchRemote } from "auto-changelog/src/remote.js";
import * as toml from "@iarna/toml";
import "zx/globals";
// if (!process.env.GITHUB_TOKEN) {
// throw new Error("Missing GITHUB_TOKEN");
// }
const IS_NIGHTLY = process.argv.indexOf("--nightly") > -1;
const PKG_VERSION = JSON.parse(fs.readFileSync("./package.json")).version.match(
/[0-9]+\.[0-9]+\.[0-9]+/,
)[0];
const GIT_REV = await $`git show --no-patch --format=%ct HEAD`;
console.log();
const NEW_VERSION = IS_NIGHTLY
? `${PKG_VERSION}-dev${+new Date(parseInt(GIT_REV.toString()))}`
: PKG_VERSION;
/**
* A Github data fetching cache designed to run in parallel with changelog formatting.
* When a SHA is requested, it is looked up in a local cache, and if it isn't there,
* the next Github API page is awaited. Requests are always triggered in advance.
*/
class GithubPRCache {
constructor() {
this.prs = new Map();
this.page = 1;
this.octokit = new Octokit({
auth: process.env.GITHUB_TOKEN,
});
this.next();
}
async next() {
let ret;
if (this._next_req) {
ret = await this._next_req;
}
console.log(
`Fetching Github commits ${(this.page - 1) * 100} - ${
this.page * 100 - 1
}`,
);
this._next_req = this.octokit.request(
"GET /repos/{owner}/{repo}/pulls?state=closed&per_page={per_page}&page={page}",
{
owner: "perspective-dev",
repo: "perspective",
per_page: 100,
page: this.page++,
},
);
return ret;
}
async get(sha) {
while (!this.prs.has(sha) && !this.failed) {
const resp = await this.next();
if (resp.data.length == 0) {
this.failed = true;
}
for (const pr of resp.data) {
this.prs.set(pr.merge_commit_sha, pr);
}
}
return this.prs.get(sha);
}
}
function template_group(label, rows, prs) {
if (rows.length > 0) {
const group_prs = rows
.map((merge) => `- ${merge.message} [#${merge.id}](${merge.href})`)
.join("\n");
return `${label}\n\n${group_prs}\n`;
} else {
return "";
}
}
function template_release(args) {
const prs = Object.keys(args.row)
.map((label) => template_group(label, args.row[label]))
.filter((x) => x.length > 0)
.join("\n");
return `# [${args.release.title}](https://github.com/perspective-dev/perspective/releases/tag/${args.release.title})
_${args.release.niceDate}_ ([Full changelog](${args.release.href}))
${prs}
`;
}
/**
* Generate the changelog text from the output of `auto-changelog`, iteratively calling
* Github's API when necessary to get label information for grouping.
*
* @param {*} json the output data from `auto-changelog`
* @returns
*/
async function template(json) {
const cache = new GithubPRCache();
let changelog = "";
for (const release of json) {
const row = {
"**Breaking**": [],
Features: [],
Fixes: [],
Misc: [],
};
for (const merge of release.merges) {
const pr = await cache.get(merge.commit.hash);
merge.pr = pr;
if (pr) {
const labels = pr.labels.map((x) => x.name);
if (labels.indexOf("breaking") > -1) {
row["**Breaking**"].push(merge);
} else if (labels.indexOf("enhancement") > -1) {
row.Features.push(merge);
} else if (labels.indexOf("bug") > -1) {
row.Fixes.push(merge);
} else {
row.Misc.push(merge);
}
} else {
row.Misc.push(merge);
}
}
changelog += template_release({
row,
release,
});
}
return changelog;
}
/**
* Update the `CHANGELOG.md` file from Github API.
*/
async function update_changelog() {
let options = {
remote: "origin",
commitLimit: 10000,
backfillLimit: 10000,
tagPrefix: "",
sortCommits: "relevance",
appendGitLog: "",
appendGitTag: "",
latestVersion: NEW_VERSION,
};
const remote = await fetchRemote(options);
options = {
...options,
...remote,
};
console.log("Fetching tags…");
const tags = await fetchTags(options);
console.log(`${tags.length} version tags found…`);
const onParsed = ({ title }) => console.log(`Fetched ${title}`);
const json = await parseReleases(tags, options, onParsed);
const changelog = await template(json);
fs.writeFileSync("./CHANGELOG.md", changelog);
$.sync`git add CHANGELOG.md`;
}
/**
* Update all project `package.json` files.
*/
async function update_package_jsons() {
const pkg = JSON.parse(fs.readFileSync("./package.json"));
pkg.version = NEW_VERSION;
const pkg_json = `${JSON.stringify(pkg, undefined, 4)}\n`;
fs.writeFileSync("./package.json", pkg_json);
const packages = {};
for (const ws of pkg.workspaces) {
if (ws === "packages/jupyterlab" && IS_NIGHTLY) {
continue;
}
for (const path of glob.sync(`${ws}/package.json`, {
sync: true,
})) {
const pkg = JSON.parse(fs.readFileSync(path));
pkg.version = NEW_VERSION;
fs.writeFileSync(path, JSON.stringify(pkg, undefined, 4) + "\n");
packages[pkg.name] = {
pkg,
path,
};
}
}
const cargo = toml.parse(fs.readFileSync("./Cargo.toml"));
for (const pkg of cargo.workspace.members) {
const crate = toml.parse(fs.readFileSync(`./${pkg}/Cargo.toml`));
if (crate?.package.version) {
crate.package.version = NEW_VERSION;
}
for (const key of Object.keys(crate?.dependencies)) {
if (key.startsWith("perspective")) {
crate.dependencies[key].version = NEW_VERSION;
}
}
fs.writeFileSync(`./${pkg}/Cargo.toml`, toml.stringify(crate));
}
// if (!IS_NIGHTLY) {
const pyproject = toml.parse(
fs.readFileSync(`./rust/perspective-python/pyproject.toml`),
);
pyproject.tool.maturin.data = `perspective_python-${NEW_VERSION}.data`;
fs.writeFileSync(
`./rust/perspective-python/pyproject.toml`,
toml.stringify(pyproject),
);
// }
}
if (!IS_NIGHTLY) {
if (!process.env.GITHUB_TOKEN) {
throw new Error("Missing GITHUB_TOKEN");
}
await update_changelog();
}
await update_package_jsons();
+57
View File
@@ -0,0 +1,57 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import fs from "node:fs";
import url from "node:url";
import path from "node:path";
const __dirname = url.fileURLToPath(new URL(".", import.meta.url)).slice(0, -1);
const workspaceRoot = path.normalize(path.join(__dirname, "..", ".."));
const rustTargetDir = path.join(workspaceRoot, "rust", "target");
const rustWheelsDir = path.join(rustTargetDir, "wheels");
const memoize = (f) => {
let val = undefined;
return () => {
if (typeof val !== "undefined") return val;
val = f();
return val;
};
};
export function getWorkspaceRoot() {
return workspaceRoot;
}
export function getRustTargetDir() {
return rustTargetDir;
}
export function getRustWheelsDir() {
return rustWheelsDir;
}
export function getEmscriptenWheelPath() {
const pspVersion = getWorkspacePackageJson().version.replace("-", ".");
const wheeljunk = "cp311-abi3-pyemscripten_2025_0_wasm32";
return path.join(
rustWheelsDir,
`perspective_python-${pspVersion}-${wheeljunk}.whl`,
);
}
/**
* @returns memoized, deserialized contents of workspace package.json
*/
export const getWorkspacePackageJson = memoize(() =>
JSON.parse(fs.readFileSync(path.join(workspaceRoot, "package.json"))),
);