23daefbe24
Rename the npm wrapper directory and package from `deepseek-tui` to
`codewhale`. Move under `npm/codewhale/`:
- `package.json` renamed (name, bin, internal field) — keeps a
`deepseekBinaryVersion` fallback so old metadata still works.
- Bin entry points renamed to `bin/codewhale.js` and
`bin/codewhale-tui.js`; they spawn the corresponding canonical
binaries via the wrapper.
- `scripts/artifacts.js` switches to the canonical asset-name matrix
(`codewhale-*`, `codewhale-tui-*`) and `codewhale-artifacts-sha256.txt`.
- `scripts/run.js` exports `runCodewhale` and `runCodewhaleTui`; the
legacy `runDeepseek` exports are gone since nothing else inside the
package depended on them.
- `scripts/install.js`, `verify-release-assets.js`, `preflight-glibc.js`
update brand-mention strings + User-Agent headers. Env vars
(`DEEPSEEK_TUI_*`, `DEEPSEEK_*`) are explicitly anti-scope and are
left in place.
- Tests retargeted at the canonical asset names; all 19 still pass.
- README rewritten with the new install command and a deprecation
note about the old package.
Create a one-release deprecation shim at `npm/deepseek-tui/`:
- `package.json` with no `bin`, just a postinstall script that
prints a clear message telling the user to install `codewhale`
instead.
- `README.md` with the same migration note.
- Will be removed in v0.9.0 (or whenever Hunter retires the shims).
Release-side scripts in `scripts/release/` follow the rename:
- `prepare-local-release-assets.js` now requires `npm/codewhale/...`
and copies the canonical `codewhale*` binaries.
- `npm-wrapper-smoke.js` smokes the renamed package.
- `check-versions.sh` reads `npm/codewhale/package.json` for the
primary check and additionally pins the legacy shim package to
the same version.
- `check-published.sh` queries `codewhale@<version>` (with
`codewhaleBinaryVersion` lookup that falls back to the legacy
`deepseekBinaryVersion` field).
- `.github/workflows/auto-tag.yml` watches both `npm/codewhale/` and
`npm/deepseek-tui/` package.json for auto-tag triggers.
Verified:
- `npm test` inside `npm/codewhale/` passes 19/19.
- `npm install --dry-run --ignore-scripts` succeeds for both
`npm/codewhale/` and `npm/deepseek-tui/`.
- `scripts/release/check-versions.sh` reports OK.
- Rust gates re-run: `cargo check`, `cargo fmt --check`,
`cargo clippy -- -D warnings`, all clean.
No `npm publish` is run from this change — Hunter publishes manually
when the rebrand is ready to ship.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
194 lines
5.5 KiB
JavaScript
194 lines
5.5 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
const fs = require("fs");
|
|
const fsp = require("fs/promises");
|
|
const http = require("http");
|
|
const os = require("os");
|
|
const path = require("path");
|
|
const { spawn } = require("child_process");
|
|
|
|
const repoRoot = path.resolve(__dirname, "..", "..");
|
|
const packageDir = path.join(repoRoot, "npm", "codewhale");
|
|
const prepareAssetsScript = path.join(
|
|
repoRoot,
|
|
"scripts",
|
|
"release",
|
|
"prepare-local-release-assets.js",
|
|
);
|
|
|
|
function shellQuote(value) {
|
|
return /\s/.test(value) ? JSON.stringify(value) : value;
|
|
}
|
|
|
|
function usesWindowsCommandShim(command) {
|
|
return process.platform === "win32" && (command === "npm" || command === "npx");
|
|
}
|
|
|
|
function runCommand(command, args, options = {}) {
|
|
const cwd = options.cwd || repoRoot;
|
|
console.log(`$ ${[command, ...args].map(shellQuote).join(" ")}`);
|
|
const child = spawn(command, args, {
|
|
cwd,
|
|
env: {
|
|
...process.env,
|
|
...(options.env || {}),
|
|
},
|
|
encoding: "utf8",
|
|
shell: usesWindowsCommandShim(command),
|
|
stdio: options.capture ? ["ignore", "pipe", "pipe"] : "inherit",
|
|
windowsHide: true,
|
|
});
|
|
|
|
if (!options.capture) {
|
|
return new Promise((resolve, reject) => {
|
|
child.once("error", reject);
|
|
child.once("close", (status) => {
|
|
if (status === 0) {
|
|
resolve({ stdout: "", stderr: "" });
|
|
} else {
|
|
reject(new Error(`${command} exited with status ${status}`));
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
let stdout = "";
|
|
let stderr = "";
|
|
child.stdout.setEncoding("utf8");
|
|
child.stderr.setEncoding("utf8");
|
|
child.stdout.on("data", (chunk) => {
|
|
stdout += chunk;
|
|
});
|
|
child.stderr.on("data", (chunk) => {
|
|
stderr += chunk;
|
|
});
|
|
return new Promise((resolve, reject) => {
|
|
child.once("error", reject);
|
|
child.once("close", (status) => {
|
|
if (status === 0) {
|
|
resolve({ stdout, stderr });
|
|
return;
|
|
}
|
|
process.stdout.write(stdout);
|
|
process.stderr.write(stderr);
|
|
reject(new Error(`${command} exited with status ${status}`));
|
|
});
|
|
});
|
|
}
|
|
|
|
function serveDirectory(root) {
|
|
const server = http.createServer(async (request, response) => {
|
|
try {
|
|
const requestUrl = new URL(request.url || "/", "http://127.0.0.1");
|
|
const decodedPath = decodeURIComponent(requestUrl.pathname);
|
|
const filePath = path.resolve(root, `.${decodedPath}`);
|
|
const relative = path.relative(root, filePath);
|
|
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
response.writeHead(403);
|
|
response.end("forbidden");
|
|
return;
|
|
}
|
|
|
|
const fileStat = await fsp.stat(filePath);
|
|
if (!fileStat.isFile()) {
|
|
response.writeHead(404);
|
|
response.end("not found");
|
|
return;
|
|
}
|
|
|
|
response.writeHead(200, {
|
|
"Content-Length": fileStat.size,
|
|
"Content-Type": "application/octet-stream",
|
|
});
|
|
fs.createReadStream(filePath).pipe(response);
|
|
} catch (error) {
|
|
response.writeHead(error && error.code === "ENOENT" ? 404 : 500);
|
|
response.end(error && error.message ? error.message : "not found");
|
|
}
|
|
});
|
|
|
|
return new Promise((resolve, reject) => {
|
|
server.once("error", reject);
|
|
server.listen(0, "127.0.0.1", () => {
|
|
const address = server.address();
|
|
resolve({
|
|
baseUrl: `http://127.0.0.1:${address.port}/`,
|
|
server,
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
function parsePackJson(stdout) {
|
|
const trimmed = stdout.trim();
|
|
if (!trimmed) {
|
|
throw new Error("npm pack did not return package metadata");
|
|
}
|
|
const parsed = JSON.parse(trimmed);
|
|
const first = Array.isArray(parsed) ? parsed[0] : parsed;
|
|
if (!first || !first.filename) {
|
|
throw new Error(`npm pack metadata did not include a filename: ${trimmed}`);
|
|
}
|
|
return first.filename;
|
|
}
|
|
|
|
async function main() {
|
|
const tempRoot = await fsp.mkdtemp(path.join(os.tmpdir(), "codewhale-npm-smoke-"));
|
|
const releaseAssetsDir = path.join(tempRoot, "release-assets");
|
|
const packDir = path.join(tempRoot, "pack");
|
|
const installDir = path.join(tempRoot, "install");
|
|
let keepTemp = process.env.DEEPSEEK_TUI_KEEP_SMOKE_DIR === "1";
|
|
let server;
|
|
|
|
try {
|
|
await fsp.mkdir(packDir, { recursive: true });
|
|
await fsp.mkdir(installDir, { recursive: true });
|
|
|
|
await runCommand(process.execPath, [prepareAssetsScript, releaseAssetsDir]);
|
|
const served = await serveDirectory(releaseAssetsDir);
|
|
server = served.server;
|
|
|
|
const env = {
|
|
DEEPSEEK_TUI_FORCE_DOWNLOAD: "1",
|
|
DEEPSEEK_TUI_RELEASE_BASE_URL: served.baseUrl,
|
|
};
|
|
const pack = await runCommand(
|
|
"npm",
|
|
["pack", "--json", "--pack-destination", packDir],
|
|
{
|
|
capture: true,
|
|
cwd: packageDir,
|
|
env,
|
|
},
|
|
);
|
|
const tarball = path.join(packDir, parsePackJson(pack.stdout));
|
|
|
|
await runCommand("npm", ["init", "-y"], { cwd: installDir });
|
|
await runCommand("npm", ["install", tarball], { cwd: installDir, env });
|
|
await runCommand("npx", ["--no-install", "codewhale", "doctor", "--help"], {
|
|
cwd: installDir,
|
|
env,
|
|
});
|
|
await runCommand("npx", ["--no-install", "codewhale-tui", "--help"], {
|
|
cwd: installDir,
|
|
env,
|
|
});
|
|
|
|
console.log(`npm wrapper smoke passed with local assets from ${served.baseUrl}`);
|
|
} catch (error) {
|
|
keepTemp = true;
|
|
console.error(`npm wrapper smoke failed: ${error.message}`);
|
|
console.error(`Smoke workspace retained at ${tempRoot}`);
|
|
process.exitCode = 1;
|
|
} finally {
|
|
if (server) {
|
|
await new Promise((resolve) => server.close(resolve));
|
|
}
|
|
if (!keepTemp) {
|
|
await fsp.rm(tempRoot, { force: true, recursive: true });
|
|
}
|
|
}
|
|
}
|
|
|
|
main();
|