From 79ced7128278b044cb270029df7a9c52c947736d Mon Sep 17 00:00:00 2001 From: Kevin Codex Date: Mon, 12 Oct 2026 12:39:24 +0800 Subject: [PATCH] chore(rebrand): remove one-time rename scaffolding, fix X links and install-page Node version (#2270) The npm rename has shipped: @twigpine/openclaude@0.32.0 and the one final @gitlawb/openclaude@0.32.0 are both published, and the old package is deprecated. The pieces that existed only to get that final old-name release out can go. Release workflow - Remove the legacy-scope-gate job, the legacy steps in install-verify and the publish-npm-legacy-scope job. The job graph is back to release-please, install-verify, publish-npm and docker, so a release no longer depends on a registry lookup for the old package. - Keep what proved itself in the 0.32.0 release: skipping a version that is already published, publishing the verified dist/ with --ignore-scripts, and the provenance and trusted-publisher line in the run summary. App code - Remove src/utils/packageRename.ts, the package-renamed startup notice, and the early exits in `openclaude update` and `/update`. They only fire in a build published under the old name, and no such build will be made again. - Remove the tests that covered them, including the release guard test that held the legacy wiring together. Docs and website - X links point at x.com/twigpine (README badge and community link, the website community list and llms.txt). - The website install page said Node.js 20 or newer; the package requires 22. - The upgrade notes for people coming from @gitlawb/openclaude stay. Co-authored-by: OpenClaude --- .github/workflows/release.yml | 199 +----------------- README.md | 4 +- scripts/build.ts | 6 +- scripts/package-rename-release-guard.test.ts | 54 ----- scripts/verify-clean-install.ts | 6 +- src/cli/update.test.ts | 36 ---- src/cli/update.ts | 44 ---- src/commands/update/update.test.ts | 23 -- src/commands/update/update.tsx | 11 - src/utils/packageRename.test.ts | 55 ----- src/utils/packageRename.ts | 51 ----- ...sNoticeDefinitions.packageRenamed.test.tsx | 49 ----- src/utils/statusNoticeDefinitions.tsx | 18 +- web/public/llms.txt | 2 +- web/src/data/partners.ts | 2 +- web/src/pages/docs/installation.astro | 2 +- 16 files changed, 13 insertions(+), 549 deletions(-) delete mode 100644 scripts/package-rename-release-guard.test.ts delete mode 100644 src/utils/packageRename.test.ts delete mode 100644 src/utils/packageRename.ts delete mode 100644 src/utils/statusNoticeDefinitions.packageRenamed.test.tsx diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f82df1ea6..195116dff 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,56 +29,13 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} release-type: node - # The @gitlawb -> @twigpine package rename owes the old name exactly one - # final release (see publish-npm-legacy-scope below). This decides whether - # that is still owed, so it can never be published twice: it is owed only - # while the old name's latest version is still the last pre-rename one, or is - # this very release (a re-run after its publish already went through). - legacy-scope-gate: - name: Check whether the final @gitlawb release is still owed - needs: release-please - if: ${{ needs.release-please.outputs.release_created == 'true' }} - runs-on: ubuntu-latest - permissions: {} - outputs: - owed: ${{ steps.gate.outputs.owed }} - steps: - - name: Compare the old package's latest version - id: gate - env: - EXPECTED_VERSION: ${{ needs.release-please.outputs.version }} - LAST_PRE_RENAME_VERSION: 0.31.0 - run: | - set -euo pipefail - - latest="" - for attempt in 1 2 3; do - latest="$(npm view @gitlawb/openclaude@latest version 2>/dev/null || true)" - if [ -n "$latest" ]; then - break - fi - sleep 10 - done - if [ -z "$latest" ]; then - echo "Could not read @gitlawb/openclaude@latest from the registry" >&2 - exit 1 - fi - - if [ "$latest" = "$LAST_PRE_RENAME_VERSION" ] || [ "$latest" = "$EXPECTED_VERSION" ]; then - owed=true - else - owed=false - fi - echo "@gitlawb/openclaude@latest is ${latest}; final legacy release owed: ${owed}" - echo "owed=${owed}" >> "$GITHUB_OUTPUT" - # Zero-warning install gate: pack the release tag and install it globally in # a sandbox (cold + upgrade scenarios) on the supported Node floor (22 → # npm 10) and current (24 → npm 11), whose warning output differs. Publish # is blocked unless the real `npm install -g` experience is clean. install-verify: name: Verify clean npm install (Node ${{ matrix.node-version }}) - needs: [release-please, legacy-scope-gate] + needs: release-please if: ${{ needs.release-please.outputs.release_created == 'true' }} runs-on: ubuntu-latest permissions: @@ -113,24 +70,6 @@ jobs: - name: Verify zero-warning global install run: bun run install:verify - # The final @gitlawb release is this same tree with only the package name - # changed. Verify it here too, before anything is published: once the new - # name is out it cannot be taken back, and in the first release this is - # the only real upgrade test (nothing is published under the new name yet, - # so its own upgrade scenario is skipped). - - name: Rebuild under the legacy package name - if: ${{ needs.legacy-scope-gate.outputs.owed == 'true' }} - run: | - npm pkg set name=@gitlawb/openclaude - bun run build - - - name: Verify the legacy build installs clean and announces the move - if: ${{ needs.legacy-scope-gate.outputs.owed == 'true' }} - run: | - set -euo pipefail - bun run install:verify - node dist/cli.mjs update | grep -F "OpenClaude has moved to @twigpine/openclaude" - publish-npm: name: Publish to npm needs: [release-please, install-verify] @@ -183,10 +122,7 @@ jobs: echo "NODE_AUTH_TOKEN=" >> "$GITHUB_ENV" # Tokenless: npm authenticates this job through trusted publishing (OIDC) - # and attaches provenance from the same identity. That only works for a - # package that already exists with a trusted publisher attached, so - # @twigpine/openclaude was created once by hand as an empty 0.0.0 - # placeholder before its first release from here. + # and attaches provenance from the same identity. # # --ignore-scripts publishes the dist/ that was built and verified above # instead of rebuilding it in `prepack`. Skipping a version that is @@ -249,137 +185,6 @@ jobs: bun scripts/npm-publish-report.ts @twigpine/openclaude "${EXPECTED_VERSION}" } | tee -a "$GITHUB_STEP_SUMMARY" - # The one final release under the old @gitlawb name, for the @gitlawb -> - # @twigpine package rename. Shipped builds do not auto-update, so people - # update by re-running the install command — and anyone still typing the old - # name (habit, shell history, an old tutorial) lands on whatever is latest - # there. This publishes the same release once under that name, so what they - # land on tells them at every launch that the package moved and how to switch - # (src/utils/packageRename.ts). The build bakes in whatever name package.json - # carries, so renaming before the build is all it takes. - # - # legacy-scope-gate keeps this to a single release: once the old name's - # latest is no longer the pre-rename version, this job never runs again. - # After it ships, `npm deprecate` the old package (that warning is what - # people see when they run the old install command), then delete this job, - # the gate, and the legacy steps in install-verify. - publish-npm-legacy-scope: - name: Publish final release to npm (legacy @gitlawb scope) - needs: [release-please, legacy-scope-gate, publish-npm] - if: ${{ needs.release-please.outputs.release_created == 'true' && needs.legacy-scope-gate.outputs.owed == 'true' }} - runs-on: ubuntu-latest - environment: release - permissions: - contents: read - id-token: write - steps: - - name: Checkout release tag - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - ref: ${{ needs.release-please.outputs.tag_name }} - fetch-depth: 0 - - - name: Set up Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: 24 - registry-url: https://registry.npmjs.org - - - name: Set up Bun - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 - with: - bun-version-file: .bun-version - - - name: Install dependencies - run: bun install --frozen-lockfile - - - name: Rename package to the legacy scope - run: npm pkg set name=@gitlawb/openclaude - - # Unit tests already ran against this exact source in publish-npm; only - # the package name differs here, so rebuild and re-verify the artifact. - - name: Smoke test - run: bun run smoke - - # The upgrade scenario is the path people on the old name actually take: - # the previously published @gitlawb version, then this one over it. - - name: Verify zero-warning global install - run: bun run install:verify - - # Proves the old name really got baked in: only then does the build say - # the package moved, instead of behaving like a normal release. - - name: Verify the build announces the move - run: | - set -euo pipefail - node dist/cli.mjs update | grep -F "OpenClaude has moved to @twigpine/openclaude" - - - name: Clear token auth for trusted publishing - run: | - unset NODE_AUTH_TOKEN - echo "NODE_AUTH_TOKEN=" >> "$GITHUB_ENV" - - # Same tokenless publish and re-run rules as publish-npm. The old - # package's trusted publisher was set up before the GitHub org was renamed - # from Gitlawb to Twigpine, so it needs an entry for Twigpine/openclaude - # for this to authenticate. - - name: Publish to npm - run: | - set -euo pipefail - - version="$(node -p "require('./package.json').version")" - if [ "$(npm view "@gitlawb/openclaude@${version}" version 2>/dev/null || true)" = "${version}" ]; then - echo "@gitlawb/openclaude@${version} is already on the registry; not publishing again." - exit 0 - fi - - npm publish --access public --provenance --ignore-scripts - - - name: Verify npm latest dist-tag - env: - EXPECTED_VERSION: ${{ needs.release-please.outputs.version }} - run: | - set -euo pipefail - - if [ -z "${EXPECTED_VERSION}" ]; then - echo "release-please did not provide an expected version" >&2 - exit 1 - fi - - for attempt in $(seq 1 30); do - published_version="$(npm view @gitlawb/openclaude version 2>/dev/null || true)" - latest_tag="$(npm view @gitlawb/openclaude dist-tags.latest 2>/dev/null || true)" - latest_version="$(npm view @gitlawb/openclaude@latest version 2>/dev/null || true)" - - echo "Attempt ${attempt}: version=${published_version:-} latest=${latest_tag:-} @latest=${latest_version:-} expected=${EXPECTED_VERSION}" - - if [ "$published_version" = "$EXPECTED_VERSION" ] && \ - [ "$latest_tag" = "$EXPECTED_VERSION" ] && \ - [ "$latest_version" = "$EXPECTED_VERSION" ]; then - echo "npm latest verified for @gitlawb/openclaude@${EXPECTED_VERSION}" - exit 0 - fi - - sleep 10 - done - - echo "npm latest dist-tag did not resolve to ${EXPECTED_VERSION}" >&2 - echo "Observed package version: ${published_version:-}" >&2 - echo "Observed dist-tags.latest: ${latest_tag:-}" >&2 - echo "Observed @latest version: ${latest_version:-}" >&2 - exit 1 - - - name: Release summary - env: - EXPECTED_VERSION: ${{ needs.release-please.outputs.version }} - run: | - { - echo "## Final legacy-scope release ${{ needs.release-please.outputs.tag_name }}" - echo - echo "- npm (legacy scope): https://www.npmjs.com/package/@gitlawb/openclaude" - bun scripts/npm-publish-report.ts @gitlawb/openclaude "${EXPECTED_VERSION}" - echo "- Next: run \`npm deprecate @gitlawb/openclaude\` with the switch commands, then delete this job and its gate" - } | tee -a "$GITHUB_STEP_SUMMARY" - docker: name: Build & Push Docker Image needs: release-please diff --git a/README.md b/README.md index a79911e4a..03455548c 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Use OpenAI-compatible APIs, Gemini, GitHub Models, Codex OAuth, Codex, Ollama, A [![npm downloads](https://img.shields.io/npm/dm/@twigpine/openclaude)](https://www.npmjs.com/package/@twigpine/openclaude) [![Discussions](https://img.shields.io/badge/discussions-open-7c3aed)](https://github.com/Twigpine/openclaude/discussions) [![Discord](https://img.shields.io/badge/Discord-join-5865F2?logo=discord&logoColor=white)](https://discord.gg/k68zFR6AcB) -[![X](https://img.shields.io/badge/X-@gitlawb-000000?logo=x&logoColor=white)](https://x.com/gitlawb) +[![X](https://img.shields.io/badge/X-@twigpine-000000?logo=x&logoColor=white)](https://x.com/twigpine) [![Security Policy](https://img.shields.io/badge/security-policy-0f766e)](SECURITY.md) [![License](https://img.shields.io/badge/license-MIT-2563eb)](LICENSE) @@ -534,7 +534,7 @@ If you believe you found a security issue, see [SECURITY.md](SECURITY.md). - Use [GitHub Discussions](https://github.com/Twigpine/openclaude/discussions) for Q&A, ideas, and community conversation - Use [GitHub Issues](https://github.com/Twigpine/openclaude/issues) for confirmed bugs and actionable feature work - Join the [Discord](https://discord.gg/k68zFR6AcB) to chat with the community in real time -- Follow [@gitlawb on X](https://x.com/gitlawb) for updates and announcements +- Follow [@twigpine on X](https://x.com/twigpine) for updates and announcements ## Contributing diff --git a/scripts/build.ts b/scripts/build.ts index a40e9869c..d46b23363 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -18,9 +18,9 @@ import { canonicalStub, collectBundleStubs } from './stubMarkerGuard.js' const nodeRequire = createRequire(import.meta.url) const pkg = JSON.parse(readFileSync('./package.json', 'utf-8')) const version = pkg.version -// Baked into the update hints, the doctor checks and the renamed-package -// notice, so it has to be the name the tarball is actually published under — -// derive it instead of repeating it. +// Baked into the update hints and the doctor checks, so it has to be the +// name the tarball is actually published under — derive it instead of +// repeating it. const packageName = pkg.name const reactPackageDir = dirname(nodeRequire.resolve('react/package.json')) const reactReconcilerPackageDir = dirname( diff --git a/scripts/package-rename-release-guard.test.ts b/scripts/package-rename-release-guard.test.ts deleted file mode 100644 index 0930d92ad..000000000 --- a/scripts/package-rename-release-guard.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { expect, test } from 'bun:test' -import { readFileSync } from 'node:fs' -import { join } from 'node:path' - -import { - CURRENT_PACKAGE_NAME, - LEGACY_PACKAGE_NAME, -} from '../src/utils/packageRename.js' - -// The @gitlawb -> @twigpine rename is wired across files that nothing else -// ties together. A mismatch between them only surfaces at release time, after -// the tag and GitHub Release already exist, so pin the contract here. - -const ROOT = join(import.meta.dir, '..') -const read = (path: string): string => readFileSync(join(ROOT, path), 'utf8') - -test('package.json publishes under the current package name', () => { - expect(JSON.parse(read('package.json')).name).toBe(CURRENT_PACKAGE_NAME) -}) - -// The final legacy-scope release is this same tree rebuilt after -// `npm pkg set name=…`. It only announces the move if the build bakes in the -// manifest name; a hardcoded name would ship it as a silent normal release. -test('the build bakes in the manifest name, not a hardcoded scope', () => { - const defines = read('scripts/build.ts').match(/'MACRO\.PACKAGE_URL':[^\n]*/g) ?? [] - - expect(defines.length).toBeGreaterThan(0) - for (const define of defines) { - expect(define).toContain('JSON.stringify(packageName)') - } -}) - -test('the release workflow publishes and verifies the current package name', () => { - expect(read('.github/workflows/release.yml')).toContain( - `npm view ${CURRENT_PACKAGE_NAME}@latest version`, - ) -}) - -// Only meaningful while the one-time legacy job still exists; once it has -// shipped and been deleted there is nothing left to hold together, and this -// test should be deleted with it rather than the job restored. -test('while the final legacy-scope job exists, it is gated to a single release', () => { - const workflow = read('.github/workflows/release.yml') - if (!workflow.includes('publish-npm-legacy-scope:')) return - - expect(workflow).toContain(`npm pkg set name=${LEGACY_PACKAGE_NAME}`) - expect(workflow).toContain(`npm view ${LEGACY_PACKAGE_NAME}@latest version`) - // The gate is what makes it a hard stop: without it the job would publish - // to the old name again on every later release. - expect(workflow).toContain('legacy-scope-gate:') - expect(workflow).toMatch( - /publish-npm-legacy-scope:[\s\S]*?if: \$\{\{[^\n]*needs\.legacy-scope-gate\.outputs\.owed == 'true'/, - ) -}) diff --git a/scripts/verify-clean-install.ts b/scripts/verify-clean-install.ts index a151111e1..3d0676d6b 100644 --- a/scripts/verify-clean-install.ts +++ b/scripts/verify-clean-install.ts @@ -46,10 +46,8 @@ import { validateRuntimeDependencyContract, } from './externalsValidation.js' -// Read from the manifest instead of repeated here: the release workflow also -// verifies and publishes this tree once under the legacy @gitlawb scope (the -// publish-npm-legacy-scope job in release.yml), and every check below has to -// look for whichever name the tarball was packed with. +// Read from the manifest instead of repeated here, so every check below looks +// for the name the tarball was actually packed with. const PACKAGE_NAME: string = JSON.parse( readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf8'), ).name diff --git a/src/cli/update.test.ts b/src/cli/update.test.ts index d95a91c34..1bfdfb3b6 100644 --- a/src/cli/update.test.ts +++ b/src/cli/update.test.ts @@ -3,7 +3,6 @@ import { withMockMacro } from 'src/test/mockMacro.js' import { getGlobalUpdateFailureHint, writePackageManagerUpdateGuidance, - writePackageRenamedNote, } from './update.js' describe('getGlobalUpdateFailureHint', () => { @@ -60,38 +59,3 @@ describe('writePackageManagerUpdateGuidance', () => { }, ) }) - -describe('writePackageRenamedNote', () => { - function capture(packageUrl: string): { output: string; wrote: boolean } { - let output = '' - const wrote = withMockMacro({ PACKAGE_URL: packageUrl }, () => - writePackageRenamedNote({ - write: value => { - output += value - }, - bold: value => value, - yellow: value => value, - }), - ) - return { output, wrote } - } - - test('tells legacy-scope installs where the package moved', () => { - const { output, wrote } = capture('@gitlawb/openclaude') - - // update() stops after the note when this is true. - expect(wrote).toBe(true) - expect(output).toContain('OpenClaude has moved to @twigpine/openclaude.') - // Uninstall first: installing the new package over the old one fails. - expect(output.indexOf('npm uninstall -g @gitlawb/openclaude')).toBeGreaterThan(-1) - expect(output.indexOf('npm uninstall -g @gitlawb/openclaude')).toBeLessThan( - output.indexOf('npm install -g @twigpine/openclaude@latest'), - ) - // The npm commands are wrong for other global package managers; say so. - expect(output).toContain('bun, pnpm or yarn') - }) - - test('writes nothing and lets the update proceed for the current package', () => { - expect(capture('@twigpine/openclaude')).toEqual({ output: '', wrote: false }) - }) -}) diff --git a/src/cli/update.ts b/src/cli/update.ts index 1bb199dff..426635ccf 100644 --- a/src/cli/update.ts +++ b/src/cli/update.ts @@ -32,13 +32,6 @@ import { getPackageManagerUpdateGuidance, type PackageManagerUpdateGuidance, } from 'src/utils/packageManagerUpdateGuidance.js' -import { - isLegacyPackageBuild, - PACKAGE_RENAMED_DETAIL, - PACKAGE_RENAMED_HEADLINE, - PACKAGE_SWITCH_COMMANDS, - PACKAGE_SWITCH_OTHER_MANAGERS, -} from 'src/utils/packageRename.js' import { writeToStdout } from 'src/utils/process.js' import { gte } from 'src/utils/semver.js' import { shouldRemoveInstalledSymlinkForNpmUpdate } from 'src/utils/autoUpdaterRouting.js' @@ -56,35 +49,6 @@ export function getGlobalUpdateFailureHint( : `Or update manually with:\n npm install -g ${MACRO.PACKAGE_URL}@latest\n` } -/** - * The final build published under the pre-rename package name has nothing to - * update to, so say where the package went. Returns whether it wrote the note - * (false, and no output, for every other build). - */ -export function writePackageRenamedNote( - deps: { - isLegacyBuild?: boolean - write?: (value: string) => void - bold?: (value: string) => string - yellow?: (value: string) => string - } = {}, -): boolean { - if (!(deps.isLegacyBuild ?? isLegacyPackageBuild())) { - return false - } - const write = deps.write ?? writeToStdout - const bold = deps.bold ?? chalk.bold - const yellow = deps.yellow ?? chalk.yellow - - write(yellow(`${PACKAGE_RENAMED_HEADLINE}\n`)) - write(`${PACKAGE_RENAMED_DETAIL}\n`) - for (const command of PACKAGE_SWITCH_COMMANDS) { - write(bold(` ${command}`) + '\n') - } - write(`${PACKAGE_SWITCH_OTHER_MANAGERS}\n`) - return true -} - export async function writePackageManagerUpdateGuidance( manager: PackageManager, channel: ReleaseChannel, @@ -122,14 +86,6 @@ export async function writePackageManagerUpdateGuidance( } export async function update() { - // The final build published under the pre-rename package name has nothing to - // update to. Say where the package went and stop, rather than run a check - // that can only fail or point back at the old name. - if (writePackageRenamedNote()) { - await gracefulShutdown(0) - return - } - // Block updates for third-party providers using upstream Anthropic builds. // The update mechanism downloads from the first-party distribution bucket, // which would silently replace the OpenClaude build with the upstream diff --git a/src/commands/update/update.test.ts b/src/commands/update/update.test.ts index d3f47a3bb..10cb2dbd8 100644 --- a/src/commands/update/update.test.ts +++ b/src/commands/update/update.test.ts @@ -66,26 +66,3 @@ describe('removeStaleNativeLauncherForNpmUpdate', () => { expect(removed).toBe(1) }) }) - -describe('/update on the final legacy-scope build', () => { - // There is nothing to update to under the old name; reporting a successful - // no-op update would hide that the package moved. - test('reports where the package moved instead of running an update', async () => { - const results: string[] = [] - - const rendered = await withMockMacro( - { PACKAGE_URL: '@gitlawb/openclaude' }, - async () => { - const { call } = await importFreshUpdateCommand() - return call((result: string) => results.push(result), {}, '') - }, - ) - - expect(rendered).toBeNull() - expect(results).toHaveLength(1) - expect(results[0]).toContain('OpenClaude has moved to @twigpine/openclaude.') - expect(results[0]).toContain('npm uninstall -g @gitlawb/openclaude') - expect(results[0]).toContain('npm install -g @twigpine/openclaude@latest') - }) -}) - diff --git a/src/commands/update/update.tsx b/src/commands/update/update.tsx index 011709427..e2fd89f3c 100644 --- a/src/commands/update/update.tsx +++ b/src/commands/update/update.tsx @@ -22,10 +22,6 @@ import { } from '../../utils/nativeInstaller/index.js' import type { PackageManager } from '../../utils/nativeInstaller/packageManagers.js' import { getPackageManagerUpdateGuidance } from '../../utils/packageManagerUpdateGuidance.js' -import { - getPackageRenamedMessage, - isLegacyPackageBuild, -} from '../../utils/packageRename.js' import { shouldRemoveInstalledSymlinkForNpmUpdate } from '../../utils/autoUpdaterRouting.js' import { resolveUpdateStrategy } from '../../utils/updateStrategy.js' @@ -362,13 +358,6 @@ export async function call( _context: unknown, args: string, ): Promise { - // The final build published under the pre-rename package name has nothing to - // update to; say where the package went instead of reporting a no-op update. - if (isLegacyPackageBuild()) { - onDone(getPackageRenamedMessage()) - return null - } - const tokens = (args ?? '').trim().split(/\s+/).filter(Boolean) const force = tokens.includes('--force') const nonFlag = tokens.filter(token => !token.startsWith('--')) diff --git a/src/utils/packageRename.test.ts b/src/utils/packageRename.test.ts deleted file mode 100644 index bd5eb4139..000000000 --- a/src/utils/packageRename.test.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { describe, expect, test } from 'bun:test' -import { withMockMacro } from 'src/test/mockMacro.js' -import { - CURRENT_PACKAGE_NAME, - getPackageRenamedMessage, - isLegacyPackageBuild, - isLegacyPackageName, - LEGACY_PACKAGE_NAME, - PACKAGE_SWITCH_COMMANDS, -} from './packageRename.js' - -describe('isLegacyPackageName', () => { - test('matches only the pre-rename package name', () => { - expect(isLegacyPackageName(LEGACY_PACKAGE_NAME)).toBe(true) - expect(isLegacyPackageName(CURRENT_PACKAGE_NAME)).toBe(false) - expect(isLegacyPackageName('@example/custom-cli')).toBe(false) - expect(isLegacyPackageName(undefined)).toBe(false) - }) -}) - -describe('isLegacyPackageBuild', () => { - test('follows the package name baked into the build', () => { - withMockMacro({ PACKAGE_URL: LEGACY_PACKAGE_NAME }, () => { - expect(isLegacyPackageBuild()).toBe(true) - }) - withMockMacro({ PACKAGE_URL: CURRENT_PACKAGE_NAME }, () => { - expect(isLegacyPackageBuild()).toBe(false) - }) - }) -}) - -describe('PACKAGE_SWITCH_COMMANDS', () => { - // Both packages own the `openclaude` bin. Installing the new one first fails - // with EEXIST, and forcing it lets the later uninstall delete the shared - // bin, so the uninstall has to come first. - test('uninstalls the old package before installing the new one', () => { - expect(PACKAGE_SWITCH_COMMANDS).toEqual([ - `npm uninstall -g ${LEGACY_PACKAGE_NAME}`, - `npm install -g ${CURRENT_PACKAGE_NAME}@latest`, - ]) - }) -}) - -describe('getPackageRenamedMessage', () => { - test('carries the headline, both commands in order, and the other-managers hint', () => { - const message = getPackageRenamedMessage() - const [uninstall, install] = PACKAGE_SWITCH_COMMANDS - - expect(message).toContain(`OpenClaude has moved to ${CURRENT_PACKAGE_NAME}.`) - expect(message.indexOf(uninstall!)).toBeGreaterThan(-1) - expect(message.indexOf(uninstall!)).toBeLessThan(message.indexOf(install!)) - expect(message).toContain('bun, pnpm or yarn') - }) -}) - diff --git a/src/utils/packageRename.ts b/src/utils/packageRename.ts deleted file mode 100644 index 09caf2f5a..000000000 --- a/src/utils/packageRename.ts +++ /dev/null @@ -1,51 +0,0 @@ -/** - * The npm package moved from the `@gitlawb` scope to `@twigpine`. - * - * Shipped builds have no working auto-updater (scripts/no-telemetry-plugin.ts - * stubs it out), so people update by re-running the install command by hand — - * and anyone doing that from habit, shell history or an old tutorial still - * types the old name. `npm deprecate` on the old package warns them at that - * moment; the single final release published under the old name (the - * `publish-npm-legacy-scope` job in `.github/workflows/release.yml`) is what - * they land on, and it keeps telling them where the package went. Nothing is - * published to the old scope after that. - * - * There is deliberately no in-place migration. Both packages own the - * `openclaude` bin, so npm refuses to install one over the other (EEXIST), and - * forcing it lets a later uninstall of the old package delete the shared bin. - * The only safe order is "uninstall old, then install new", run by the user. - */ -export const LEGACY_PACKAGE_NAME = '@gitlawb/openclaude' -export const CURRENT_PACKAGE_NAME = '@twigpine/openclaude' - -export const PACKAGE_RENAMED_HEADLINE = `OpenClaude has moved to ${CURRENT_PACKAGE_NAME}.` -export const PACKAGE_RENAMED_DETAIL = `${LEGACY_PACKAGE_NAME} is no longer updated. To keep getting updates, quit OpenClaude and run:` - -/** Order matters: installing the new package first fails with EEXIST. */ -export const PACKAGE_SWITCH_COMMANDS: readonly string[] = [ - `npm uninstall -g ${LEGACY_PACKAGE_NAME}`, - `npm install -g ${CURRENT_PACKAGE_NAME}@latest`, -] - -/** The commands above only fit an npm global install. */ -export const PACKAGE_SWITCH_OTHER_MANAGERS = - 'Installed with bun, pnpm or yarn? Use its global remove and add commands instead.' - -/** The whole note as plain text, for surfaces that take a single string. */ -export function getPackageRenamedMessage(): string { - return [ - PACKAGE_RENAMED_HEADLINE, - PACKAGE_RENAMED_DETAIL, - ...PACKAGE_SWITCH_COMMANDS.map(command => ` ${command}`), - PACKAGE_SWITCH_OTHER_MANAGERS, - ].join('\n') -} - -export function isLegacyPackageName(packageName: string | undefined): boolean { - return packageName === LEGACY_PACKAGE_NAME -} - -/** True when this build was published under the pre-rename package name. */ -export function isLegacyPackageBuild(): boolean { - return isLegacyPackageName(MACRO.PACKAGE_URL) -} diff --git a/src/utils/statusNoticeDefinitions.packageRenamed.test.tsx b/src/utils/statusNoticeDefinitions.packageRenamed.test.tsx deleted file mode 100644 index 9a5d879ce..000000000 --- a/src/utils/statusNoticeDefinitions.packageRenamed.test.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { describe, expect, test } from 'bun:test' -import { withMockMacro } from 'src/test/mockMacro.js' -import { - CURRENT_PACKAGE_NAME, - LEGACY_PACKAGE_NAME, - PACKAGE_SWITCH_COMMANDS, -} from './packageRename.js' -import type { StatusNoticeContext } from './statusNoticeDefinitions.js' -import { statusNoticeDefinitions } from './statusNoticeDefinitions.js' -import { renderToString } from './staticRender.js' - -// The final build published under the pre-rename package name is the only -// build that should tell users the package moved. - -const context: StatusNoticeContext = { - config: {} as StatusNoticeContext['config'], - memoryFiles: [], -} - -function packageRenamedNotice() { - const notice = statusNoticeDefinitions.find(n => n.id === 'package-renamed') - expect(notice).toBeDefined() - return notice! -} - -describe('package renamed notice', () => { - test('is active only for the legacy-scope build', () => { - withMockMacro({ PACKAGE_URL: LEGACY_PACKAGE_NAME }, () => { - expect(packageRenamedNotice().isActive(context)).toBe(true) - }) - withMockMacro({ PACKAGE_URL: CURRENT_PACKAGE_NAME }, () => { - expect(packageRenamedNotice().isActive(context)).toBe(false) - }) - }) - - test('shows where the package moved and the switch commands in order', async () => { - const output = await renderToString( - packageRenamedNotice().render(context), - 100, - ) - - expect(output).toContain(`OpenClaude has moved to ${CURRENT_PACKAGE_NAME}.`) - const [uninstall, install] = PACKAGE_SWITCH_COMMANDS - expect(output).toContain(uninstall!) - expect(output).toContain(install!) - expect(output.indexOf(uninstall!)).toBeLessThan(output.indexOf(install!)) - expect(output).toContain('bun, pnpm or yarn') - }) -}) diff --git a/src/utils/statusNoticeDefinitions.tsx b/src/utils/statusNoticeDefinitions.tsx index 9cbc287f3..f104ac7af 100644 --- a/src/utils/statusNoticeDefinitions.tsx +++ b/src/utils/statusNoticeDefinitions.tsx @@ -17,7 +17,6 @@ import { isDangerousPermissionMode, type PermissionMode } from './permissions/Pe import { modelSupportsAutoMode } from './betas.js'; import { getAPIProvider, isFirstPartyAnthropicBaseUrl } from './model/providers.js'; import { logForDebugging } from './debug.js'; -import { isLegacyPackageBuild, PACKAGE_RENAMED_DETAIL, PACKAGE_RENAMED_HEADLINE, PACKAGE_SWITCH_COMMANDS, PACKAGE_SWITCH_OTHER_MANAGERS } from './packageRename.js'; // Types export type StatusNoticeType = 'warning' | 'info'; @@ -334,23 +333,8 @@ const dangerouslySkipPermissionsNotice: StatusNoticeDefinition = { }, }; -// Only the final build published under the pre-rename package name shows this -// (see packageRename.ts). Nothing newer is ever published under that name, so -// startup is where its users keep being told the package moved. -const packageRenamedNotice: StatusNoticeDefinition = { - id: 'package-renamed', - type: 'warning', - isActive: () => isLegacyPackageBuild(), - render: () => - {PACKAGE_RENAMED_HEADLINE} - {PACKAGE_RENAMED_DETAIL} - {PACKAGE_SWITCH_COMMANDS.map(command => {` ${command}`})} - {PACKAGE_SWITCH_OTHER_MANAGERS} - , -}; - // All notice definitions -export const statusNoticeDefinitions: StatusNoticeDefinition[] = [largeMemoryFilesNotice, largeAgentDescriptionsNotice, localModelContextLoadNotice, claudeAiSubscriberExternalTokenNotice, apiKeyConflictNotice, bothAuthMethodsNotice, jetbrainsPluginNotice, thirdPartyPermissiveModeNotice, dangerouslySkipPermissionsNotice, packageRenamedNotice]; +export const statusNoticeDefinitions: StatusNoticeDefinition[] = [largeMemoryFilesNotice, largeAgentDescriptionsNotice, localModelContextLoadNotice, claudeAiSubscriberExternalTokenNotice, apiKeyConflictNotice, bothAuthMethodsNotice, jetbrainsPluginNotice, thirdPartyPermissiveModeNotice, dangerouslySkipPermissionsNotice]; // Helper functions for external use export function getActiveNotices(context: StatusNoticeContext): StatusNoticeDefinition[] { diff --git a/web/public/llms.txt b/web/public/llms.txt index 3918897f0..df2200091 100644 --- a/web/public/llms.txt +++ b/web/public/llms.txt @@ -35,4 +35,4 @@ OpenClaude originated from the Claude Code codebase and has since been substanti - [Discord](https://discord.gg/k68zFR6AcB) - [GitHub Discussions](https://github.com/Twigpine/openclaude/discussions) -- [X / Twitter](https://x.com/gitlawb) \ No newline at end of file +- [X / Twitter](https://x.com/twigpine) \ No newline at end of file diff --git a/web/src/data/partners.ts b/web/src/data/partners.ts index 55bb9ba22..c7edc4147 100644 --- a/web/src/data/partners.ts +++ b/web/src/data/partners.ts @@ -57,7 +57,7 @@ export const partners: Partner[] = [ export const community = [ { name: 'github', url: 'https://github.com/Twigpine/openclaude', label: 'star the repo' }, { name: 'discord', url: 'https://discord.gg/k68zFR6AcB', label: 'join the discord' }, - { name: 'x', url: 'https://x.com/gitlawb', label: 'follow @gitlawb' }, + { name: 'x', url: 'https://x.com/twigpine', label: 'follow @twigpine' }, { name: 'discussions', url: 'https://github.com/Twigpine/openclaude/discussions', label: 'open a discussion' }, { name: 'trendshift', url: 'https://trendshift.io/repositories/25807', label: 'featured on trendshift' }, ] diff --git a/web/src/pages/docs/installation.astro b/web/src/pages/docs/installation.astro index 8e24604ee..656d078ac 100644 --- a/web/src/pages/docs/installation.astro +++ b/web/src/pages/docs/installation.astro @@ -22,7 +22,7 @@ const toc = [ >

requirements

    -
  • Node.js 20 or newer — grab it from nodejs.org and confirm with node --version.
  • +
  • Node.js 22 or newer — grab it from nodejs.org and confirm with node --version.
  • A terminal: Terminal/iTerm on macOS, any shell on Linux, PowerShell or Windows Terminal on Windows.
  • ripgrep is bundled; if startup reports ripgrep not found, install it system-wide and confirm rg --version works.