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
|
- name: Verify PyYAML is importable
|
||||||
run: python -c "import yaml; print(yaml.__version__)"
|
run: python -c "import yaml; print(yaml.__version__)"
|
||||||
|
|
||||||
- name: Run test suite
|
- name: Run Python test suite
|
||||||
run: >
|
run: >
|
||||||
pytest tests/ -q --tb=short
|
pytest tests/ -q --tb=short
|
||||||
--ignore=tests/benchmarks/terminal_bench/test_openspace_harbor_agent_config.py
|
--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/*
|
tests/entrypoints/dashboard/*
|
||||||
!tests/entrypoints/dashboard/test_dashboard_auth.py
|
!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
|
# Local agent/project memory
|
||||||
OPENSPACE.md
|
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