diff --git a/AGENTS.md b/AGENTS.md index dd68863..c7fe7a1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -247,6 +247,8 @@ Fallback entrypoints: Alias publishing: - `publish-alias-package.mjs` builds the `@gonkagate/claude-code-setup` alias package from the current root build output +- if that alias package is not visible on npm yet, the script skips alias publishing so the primary `@gonkagate/claude-code` release can complete; bootstrap the alias package with `--allow-create-package` or `ALLOW_ALIAS_PACKAGE_CREATE=1`, then configure Trusted Publishing before requiring alias publish success +- alias package publishing uses provenance only in GitHub Actions OIDC; local bootstrap publishes without provenance because npm cannot generate it outside a supported CI provider They must not replace `npx` as the primary public UX. diff --git a/CHANGELOG.md b/CHANGELOG.md index ccf69d3..83ad5f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Made local alias package bootstrap publish without npm provenance because provenance requires a supported CI/OIDC provider. +- Made alias package publishing skip cleanly until `@gonkagate/claude-code-setup` is bootstrapped on npm, with an explicit override for the first package publish. - Added `@gonkagate/claude-code-setup` as a setup-style alias for the existing Claude Code installer. - Improved npm package metadata and README copy for better package-page clarity, discovery, and onboarding. - Added a curated model registry and model picker to the public installer flow. diff --git a/scripts/publish-alias-package.mjs b/scripts/publish-alias-package.mjs index 8ed73b6..d64867d 100644 --- a/scripts/publish-alias-package.mjs +++ b/scripts/publish-alias-package.mjs @@ -8,6 +8,14 @@ import { fileURLToPath } from "node:url"; const ALIAS_PACKAGE_NAME = "@gonkagate/claude-code-setup"; const ALIAS_BIN_NAME = "claude-code-setup"; const dryRun = process.argv.includes("--dry-run"); +const allowCreatePackage = process.argv.includes("--allow-create-package") + || process.env.ALLOW_ALIAS_PACKAGE_CREATE === "1"; +const requireExistingPackage = process.argv.includes("--require-package") + || process.env.REQUIRE_ALIAS_PACKAGE === "1"; +const disableProvenance = process.argv.includes("--no-provenance") + || process.env.DISABLE_ALIAS_PACKAGE_PROVENANCE === "1"; +const canUseGitHubActionsProvenance = process.env.GITHUB_ACTIONS === "true" + && process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN !== undefined; const scriptDir = dirname(fileURLToPath(import.meta.url)); const repoRoot = resolve(scriptDir, ".."); @@ -28,20 +36,30 @@ function run(command, args, options = {}) { } } -function isPublished(packageName, version) { - const result = spawnSync("npm", ["view", `${packageName}@${version}`, "version"], { +function isNpmNotFound(output) { + return output.includes("E404") || output.includes("404 Not Found"); +} + +function npmView(args) { + const result = spawnSync("npm", ["view", ...args], { cwd: repoRoot, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }); if (result.status === 0) { - return true; + return { + exists: true, + output: result.stdout + }; } const output = `${result.stdout}\n${result.stderr}`; - if (output.includes("E404") || output.includes("404 Not Found")) { - return false; + if (isNpmNotFound(output)) { + return { + exists: false, + output + }; } process.stdout.write(result.stdout); @@ -49,6 +67,14 @@ function isPublished(packageName, version) { process.exit(result.status ?? 1); } +function canViewPackage(packageName) { + return npmView([packageName, "name"]).exists; +} + +function isVersionPublished(packageName, version) { + return npmView([`${packageName}@${version}`, "version"]).exists; +} + async function assertExists(path) { await access(path, fsConstants.R_OK); } @@ -56,7 +82,23 @@ async function assertExists(path) { const rootPackage = JSON.parse(await readFile(join(repoRoot, "package.json"), "utf8")); const packageVersion = rootPackage.version; -if (!dryRun && isPublished(ALIAS_PACKAGE_NAME, packageVersion)) { +if (!dryRun && !canViewPackage(ALIAS_PACKAGE_NAME)) { + console.warn(`${ALIAS_PACKAGE_NAME} is not visible on npm yet, or this publisher cannot access it.`); + + if (allowCreatePackage) { + console.warn("Attempting first publish because alias package creation was explicitly allowed."); + } else if (requireExistingPackage) { + console.warn(`Bootstrap ${ALIAS_PACKAGE_NAME} on npm and configure Trusted Publishing for this workflow, then rerun npm run publish:alias.`); + console.warn("Alias package publishing is required for this run, so failing now."); + process.exit(1); + } else { + console.warn(`Bootstrap ${ALIAS_PACKAGE_NAME} on npm and configure Trusted Publishing for this workflow, then rerun npm run publish:alias.`); + console.warn("Skipping alias publish so the primary @gonkagate/claude-code release can complete."); + process.exit(0); + } +} + +if (!dryRun && isVersionPublished(ALIAS_PACKAGE_NAME, packageVersion)) { console.log(`${ALIAS_PACKAGE_NAME}@${packageVersion} is already published; skipping alias publish.`); process.exit(0); } @@ -98,8 +140,10 @@ try { const publishArgs = ["publish", "--access", "public"]; if (dryRun) { publishArgs.push("--dry-run"); - } else { + } else if (canUseGitHubActionsProvenance && !disableProvenance) { publishArgs.push("--provenance"); + } else { + console.warn("Publishing without provenance because this is not a GitHub Actions OIDC environment."); } run("npm", publishArgs, { cwd: packageRoot });