mirror of
https://github.com/HKUDS/OpenSpace.git
synced 2026-08-28 05:15:00 +00:00
fix(packaging): restore missing copy-dist-to-packaged script
apps/dashboard and apps/tui call scripts/copy-dist-to-packaged.mjs from build:packaged, but the script was missing and scripts/ was gitignored. Add the copier, Node smoke tests, and CI checks so packaged UI/TUI artifacts can ship in wheels again.
This commit is contained in:
parent
becf819e8c
commit
4df52989fa
4 changed files with 238 additions and 2 deletions
22
.github/workflows/ci.yml
vendored
22
.github/workflows/ci.yml
vendored
|
|
@ -29,7 +29,27 @@ jobs:
|
|||
- name: Verify PyYAML is importable
|
||||
run: python -c "import yaml; print(yaml.__version__)"
|
||||
|
||||
- name: Run test suite
|
||||
- name: Run Python test suite
|
||||
run: >
|
||||
pytest tests/ -q --tb=short
|
||||
--ignore=tests/benchmarks/terminal_bench/test_openspace_harbor_agent_config.py
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
|
||||
- name: Run packaging copy-script tests
|
||||
run: node --test tests/packaging/test_copy_dist_to_packaged.mjs
|
||||
|
||||
- name: Verify wheel can include packaged frontend paths
|
||||
run: |
|
||||
python - <<'PY'
|
||||
from pathlib import Path
|
||||
pyproject = Path("pyproject.toml").read_text(encoding="utf-8")
|
||||
assert 'packaged/dashboard/**/*' in pyproject
|
||||
assert 'packaged/tui/**/*' in pyproject
|
||||
script = Path("scripts/copy-dist-to-packaged.mjs")
|
||||
assert script.is_file(), "missing packaging copy script"
|
||||
print("packaging contract ok")
|
||||
PY
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
|
|
@ -68,7 +68,12 @@ tests/entrypoints/*
|
|||
!tests/entrypoints/dashboard/
|
||||
tests/entrypoints/dashboard/*
|
||||
!tests/entrypoints/dashboard/test_dashboard_auth.py
|
||||
scripts/
|
||||
!tests/packaging/
|
||||
tests/packaging/*
|
||||
!tests/packaging/test_copy_dist_to_packaged.mjs
|
||||
scripts/*
|
||||
!scripts/
|
||||
!scripts/copy-dist-to-packaged.mjs
|
||||
|
||||
# Local agent/project memory
|
||||
OPENSPACE.md
|
||||
|
|
|
|||
131
scripts/copy-dist-to-packaged.mjs
Normal file
131
scripts/copy-dist-to-packaged.mjs
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* Copy a frontend build `dist/` into openspace/packaged/<target>.
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/copy-dist-to-packaged.mjs <distDir> <destDir> \
|
||||
* [--node-modules <dir>] [--package-json <file>]
|
||||
*
|
||||
* Dashboard only needs the Vite dist tree.
|
||||
* TUI also copies production node_modules + package.json so the packaged
|
||||
* Ink entrypoint can run without a separate npm install after pip install.
|
||||
*/
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
function usage(message) {
|
||||
if (message) {
|
||||
console.error(message);
|
||||
}
|
||||
console.error(
|
||||
"Usage: node scripts/copy-dist-to-packaged.mjs <distDir> <destDir> " +
|
||||
"[--node-modules <dir>] [--package-json <file>]",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function parseArgs(argv) {
|
||||
const positional = [];
|
||||
let nodeModules = null;
|
||||
let packageJson = null;
|
||||
|
||||
for (let i = 0; i < argv.length; i += 1) {
|
||||
const arg = argv[i];
|
||||
if (arg === "--node-modules") {
|
||||
nodeModules = argv[++i];
|
||||
if (!nodeModules) usage("Missing value for --node-modules");
|
||||
continue;
|
||||
}
|
||||
if (arg === "--package-json") {
|
||||
packageJson = argv[++i];
|
||||
if (!packageJson) usage("Missing value for --package-json");
|
||||
continue;
|
||||
}
|
||||
if (arg.startsWith("-")) {
|
||||
usage(`Unknown option: ${arg}`);
|
||||
}
|
||||
positional.push(arg);
|
||||
}
|
||||
|
||||
if (positional.length !== 2) {
|
||||
usage("Expected exactly <distDir> and <destDir>");
|
||||
}
|
||||
|
||||
return {
|
||||
distDir: path.resolve(positional[0]),
|
||||
destDir: path.resolve(positional[1]),
|
||||
nodeModules: nodeModules ? path.resolve(nodeModules) : null,
|
||||
packageJson: packageJson ? path.resolve(packageJson) : null,
|
||||
};
|
||||
}
|
||||
|
||||
function assertDirectory(dirPath, label) {
|
||||
if (!fs.existsSync(dirPath) || !fs.statSync(dirPath).isDirectory()) {
|
||||
throw new Error(`${label} is missing or not a directory: ${dirPath}`);
|
||||
}
|
||||
}
|
||||
|
||||
function assertFile(filePath, label) {
|
||||
if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
|
||||
throw new Error(`${label} is missing or not a file: ${filePath}`);
|
||||
}
|
||||
}
|
||||
|
||||
function emptyDirectory(dirPath) {
|
||||
fs.mkdirSync(dirPath, { recursive: true });
|
||||
for (const entry of fs.readdirSync(dirPath)) {
|
||||
fs.rmSync(path.join(dirPath, entry), { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
function copyTree(source, destination) {
|
||||
fs.cpSync(source, destination, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
errorOnExist: false,
|
||||
});
|
||||
}
|
||||
|
||||
function main(argv = process.argv.slice(2)) {
|
||||
const options = parseArgs(argv);
|
||||
assertDirectory(options.distDir, "distDir");
|
||||
|
||||
if (options.nodeModules) {
|
||||
assertDirectory(options.nodeModules, "nodeModules");
|
||||
}
|
||||
if (options.packageJson) {
|
||||
assertFile(options.packageJson, "packageJson");
|
||||
}
|
||||
|
||||
emptyDirectory(options.destDir);
|
||||
copyTree(options.distDir, options.destDir);
|
||||
|
||||
if (options.nodeModules) {
|
||||
copyTree(options.nodeModules, path.join(options.destDir, "node_modules"));
|
||||
}
|
||||
if (options.packageJson) {
|
||||
fs.copyFileSync(
|
||||
options.packageJson,
|
||||
path.join(options.destDir, "package.json"),
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`Packaged assets copied to ${options.destDir}`);
|
||||
return options.destDir;
|
||||
}
|
||||
|
||||
const isDirectRun =
|
||||
process.argv[1] &&
|
||||
path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
||||
|
||||
if (isDirectRun) {
|
||||
try {
|
||||
main();
|
||||
} catch (error) {
|
||||
console.error(error instanceof Error ? error.message : String(error));
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
export { main, parseArgs };
|
||||
80
tests/packaging/test_copy_dist_to_packaged.mjs
Normal file
80
tests/packaging/test_copy_dist_to_packaged.mjs
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
* Smoke tests for scripts/copy-dist-to-packaged.mjs
|
||||
*/
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import test from "node:test";
|
||||
|
||||
import { main } from "../../scripts/copy-dist-to-packaged.mjs";
|
||||
|
||||
const repoRoot = path.resolve(
|
||||
path.dirname(fileURLToPath(import.meta.url)),
|
||||
"../..",
|
||||
);
|
||||
|
||||
test("copies dist tree into packaged destination", () => {
|
||||
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "openspace-pack-"));
|
||||
const dist = path.join(tmp, "dist");
|
||||
const dest = path.join(tmp, "packaged");
|
||||
fs.mkdirSync(dist, { recursive: true });
|
||||
fs.writeFileSync(path.join(dist, "index.html"), "<html>ok</html>\n");
|
||||
fs.mkdirSync(path.join(dist, "assets"), { recursive: true });
|
||||
fs.writeFileSync(path.join(dist, "assets", "app.js"), "console.log(1);\n");
|
||||
|
||||
main([dist, dest]);
|
||||
|
||||
assert.equal(
|
||||
fs.readFileSync(path.join(dest, "index.html"), "utf8"),
|
||||
"<html>ok</html>\n",
|
||||
);
|
||||
assert.equal(
|
||||
fs.readFileSync(path.join(dest, "assets", "app.js"), "utf8"),
|
||||
"console.log(1);\n",
|
||||
);
|
||||
});
|
||||
|
||||
test("copies optional node_modules and package.json for TUI", () => {
|
||||
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "openspace-pack-tui-"));
|
||||
const dist = path.join(tmp, "dist");
|
||||
const dest = path.join(tmp, "packaged");
|
||||
const nodeModules = path.join(tmp, "node_modules");
|
||||
const packageJson = path.join(tmp, "package.json");
|
||||
|
||||
fs.mkdirSync(path.join(nodeModules, "ink"), { recursive: true });
|
||||
fs.writeFileSync(path.join(nodeModules, "ink", "index.js"), "export {};\n");
|
||||
fs.writeFileSync(packageJson, JSON.stringify({ name: "openspace-tui" }));
|
||||
fs.mkdirSync(dist, { recursive: true });
|
||||
fs.writeFileSync(path.join(dist, "index.js"), "console.log('tui');\n");
|
||||
|
||||
main([
|
||||
dist,
|
||||
dest,
|
||||
"--node-modules",
|
||||
nodeModules,
|
||||
"--package-json",
|
||||
packageJson,
|
||||
]);
|
||||
|
||||
assert.ok(fs.existsSync(path.join(dest, "index.js")));
|
||||
assert.ok(fs.existsSync(path.join(dest, "node_modules", "ink", "index.js")));
|
||||
assert.equal(
|
||||
JSON.parse(fs.readFileSync(path.join(dest, "package.json"), "utf8")).name,
|
||||
"openspace-tui",
|
||||
);
|
||||
});
|
||||
|
||||
test("fails when dist directory is missing", () => {
|
||||
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "openspace-pack-miss-"));
|
||||
assert.throws(
|
||||
() => main([path.join(tmp, "missing-dist"), path.join(tmp, "out")]),
|
||||
/distDir is missing/,
|
||||
);
|
||||
});
|
||||
|
||||
test("script exists at the path apps/* package.json expects", () => {
|
||||
const scriptPath = path.join(repoRoot, "scripts", "copy-dist-to-packaged.mjs");
|
||||
assert.ok(fs.existsSync(scriptPath), `missing ${scriptPath}`);
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue