← back to Kickbacks Ai Upstream
scripts/package.mjs
116 lines
import { spawnSync } from "node:child_process";
import {
copyFileSync,
cpSync,
existsSync,
mkdirSync,
readFileSync,
readdirSync,
rmSync,
unlinkSync,
writeFileSync,
} from "node:fs";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
// Packages the extension into TWO files:
// - kickbacks.vsix — the canonical, STABLE artifact. The deploy
// uploads this to the fixed GCS object gs://kickbacks-vsix/kickbacks.vsix,
// so every running extension's self-update URL stays constant. Never
// rename this one.
// - kickbacks-<version>.vsix — a versioned copy with the version baked into
// the file name, so a built artifact on disk is instantly identifiable.
// The version is the single source of truth in package.json (the deploy bumps
// it before packaging, so this picks up the new number automatically).
/** The versioned artifact name for a given semver. Pure; kept tiny but
* separate so the naming convention has one definition. */
export function versionedVsixName(version) {
return `kickbacks-${version}.vsix`;
}
const STABLE = "kickbacks.vsix";
// scripts/package.mjs -> extension/
const extDir = join(fileURLToPath(import.meta.url), "..", "..");
const stageDir = join(extDir, ".vsce-stage");
function packageManifestForVsce(pkg) {
const pj = JSON.parse(JSON.stringify(pkg));
pj.author ??= {
name: "Andrew McCalip",
url: "https://github.com/andrewmccalip",
};
pj.license ??= "SEE LICENSE IN LICENSE";
pj.homepage ??= "https://kickbacks.ai";
pj.bugs ??= { url: "https://github.com/andrewmccalip/kickbacks/issues" };
const c = pj.contributes?.commands;
if (c && !c.some((x) => x.command === "kickbacks.signOut")) {
const i = c.findIndex((x) => x.command === "kickbacks.signIn");
if (i >= 0) c.splice(i + 1, 0,
{ command: "kickbacks.signOut", title: "Kickbacks: Sign out" });
}
pj.description = "Get paid while you code. Subtle, clickable ads in the Claude Code and Codex spinners — 50/50 revenue split to users.";
return pj;
}
function stagePackage(pkg) {
rmSync(stageDir, { recursive: true, force: true });
mkdirSync(stageDir, { recursive: true });
writeFileSync(join(stageDir, "package.json"),
JSON.stringify(packageManifestForVsce(pkg), null, 2) + "\n");
const readme = existsSync(join(extDir, "dist", "README.md"))
? readFileSync(join(extDir, "dist", "README.md"), "utf8")
: readFileSync(join(extDir, "readme_extension.md"), "utf8");
writeFileSync(join(stageDir, "README.md"), readme);
copyFileSync(join(extDir, "LICENSE"), join(stageDir, "LICENSE"));
cpSync(join(extDir, "dist"), join(stageDir, "dist"), { recursive: true });
rmSync(join(stageDir, "dist", "README.md"), { force: true });
cpSync(join(extDir, "media"), join(stageDir, "media"), { recursive: true });
writeFileSync(join(stageDir, ".vscodeignore"), [
"node_modules/**",
"src/**",
"test/**",
"scripts/**",
"*.vsix",
"package-lock.json",
"tsconfig.json",
"vitest.config.ts",
"esbuild.mjs",
".vsce-stage/**",
"",
].join("\n"));
}
function run() {
const pkg = JSON.parse(readFileSync(join(extDir, "package.json"), "utf8"));
const { version } = pkg;
const versioned = versionedVsixName(version);
// Drop any stale versioned vsix from prior builds so the dir always holds
// exactly the current one (plus the stable kickbacks.vsix). Best-effort.
for (const f of readdirSync(extDir)) {
if (/^kickbacks-.*\.vsix$/.test(f) && f !== versioned) {
try { unlinkSync(join(extDir, f)); } catch { /* ignore */ }
}
}
stagePackage(pkg);
// shell:true so the vsce .cmd wrapper resolves on Windows (node_modules/.bin
// is on PATH under `npm run`). Package from a temporary staged root so build
// metadata never mutates tracked package.json / README.md.
const r = spawnSync("vsce",
["package", "--no-dependencies", "-o", join(extDir, STABLE)],
{ cwd: stageDir, stdio: "inherit", shell: true });
if ((r.status ?? 1) !== 0) process.exit(r.status ?? 1);
rmSync(stageDir, { recursive: true, force: true });
copyFileSync(join(extDir, STABLE), join(extDir, versioned));
console.error(`packaged ${STABLE} + ${versioned} (v${version})`);
}
run();