From dbc5c01818971208b3fe1a7fad00674a959b9373 Mon Sep 17 00:00:00 2001 From: Ralph Chang Date: Sun, 31 May 2026 14:44:25 +0800 Subject: [PATCH] fix(package): publish JavaScript plugin entry --- CHANGELOG.md | 7 ++ docs/plan-package-js-entry-release.md | 118 ++++++++++++++++++++++ package.json | 15 +-- tests/smoke/memory-diag-packaging.test.ts | 91 ++++++++++++++++- tsconfig.memory-diag.json | 12 +-- 5 files changed, 221 insertions(+), 22 deletions(-) create mode 100644 docs/plan-package-js-entry-release.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 54960eb..d5f0f90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.6.7] - 2026-05-31 + +### Fixed + +- Fixed published OpenCode plugin loading under Node-based plugin loaders by compiling the runtime plugin entry to JavaScript and pointing package entry exports at `dist/`. +- Added package smoke coverage for bare, `./server`, and `./tui` imports from an installed tarball so TypeScript entry points cannot regress under `node_modules`. + ## [1.6.6] - 2026-05-20 ### Changed diff --git a/docs/plan-package-js-entry-release.md b/docs/plan-package-js-entry-release.md new file mode 100644 index 0000000..5966c90 --- /dev/null +++ b/docs/plan-package-js-entry-release.md @@ -0,0 +1,118 @@ +# Plan: Package JS Entry for OpenCode Node Loader + +## Goal + +Fix GitHub issue #6 by making the published `opencode-working-memory` package loadable through OpenCode's Node-based plugin loader. The package must not expose TypeScript source files as runtime entry points under `node_modules`. + +## Background + +`opencode-working-memory@1.6.6` currently publishes: + +- `main: "index.ts"` +- `exports["."]: "./index.ts"` +- `exports["./server"]: "./index.ts"` +- `exports["./tui"]: "./src/tui-plugin.ts"` + +Node can import the local repo `index.ts`, but refuses to strip TypeScript types for files under `node_modules`. This means local path testing passes while npm/opencode package-cache loading fails. + +## Proposed Changes + +1. Expand the existing dist build config into a full package build. + - Update `tsconfig.memory-diag.json` to include `index.ts`, `src/**/*.ts`, `scripts/memory-diag.ts`, and `scripts/memory-diag/**/*.ts`. + - Remove the current exclusions for runtime plugin dependencies: `src/plugin.ts`, `src/tui-plugin.ts`, `src/opencode.ts`, `src/session-state.ts`, `src/extractors.ts`, `src/pending-journal.ts`, `src/promotion-accounting.ts`, and `src/memory-visibility.ts`. + - Keep `rootDir: "."` and `outDir: "dist"` so `index.ts` emits to `dist/index.js` and `src/tui-plugin.ts` emits to `dist/src/tui-plugin.js`. + - Keep `rewriteRelativeImportExtensions: true` so emitted ESM imports point at `.js`. + +2. Update `package.json` runtime entry points. + - `main` should point to `dist/index.js`. + - `exports["."]` and `exports["./server"]` should point to `./dist/index.js`. + - `exports["./tui"]` should point to `./dist/src/tui-plugin.js`. + +3. Update build scripts. + - Keep one clean build path that emits both plugin runtime JS and `memory-diag` JS. + - Preserve the existing `memory-diag` binary wrapper behavior. + - Rename or alias the script so the unified dist build is not hidden behind a memory-diag-only name. + +4. Update packaging tests. + - Add a smoke test that builds/packs/installs the tarball into a temporary prefix and imports `opencode-working-memory` from `node_modules`. + - Assert the default export id is `working-memory`. + - Assert `opencode-working-memory/server` imports and exposes the same default plugin id. + - Assert `opencode-working-memory/tui` imports and exposes the TUI plugin id. + - Assert package manifest entry points do not point at `.ts`. + - Use an isolated npm cache under `/private/tmp` or the test temp root so local `~/.npm` permissions do not affect the smoke test. + +5. Update package file allowlist if needed. + - Ensure `dist/`, `scripts/memory-diag-bin.cjs`, `README.md`, and `LICENSE` are included. + - Keeping TypeScript source files in the tarball is acceptable only if runtime entry points resolve to JS. + +## Affected Files + +- `package.json` +- `tsconfig.memory-diag.json` or a new build tsconfig +- `tests/smoke/memory-diag-packaging.test.ts` or a new smoke test under `tests/smoke/` +- `CHANGELOG.md` +- Possibly `package-lock.json` if package metadata changes require npm to refresh it + +## Verification + +Run: + +```bash +npm run build +test -f dist/index.js +test -f dist/src/tui-plugin.js +node -e "import('./dist/index.js').then(m => console.log(m.default.id))" +node -e "import('./dist/src/tui-plugin.js').then(m => console.log(m.default.id))" +rg 'from\s+\".*\\.ts\"|import\s*\\(.*\\.ts' dist +npm run typecheck +npm test +npm run check:package-integrity +``` + +Package-path smoke: + +```bash +rm -rf /private/tmp/owm-pack /private/tmp/owm-install /private/tmp/npm-cache +mkdir -p /private/tmp/owm-pack /private/tmp/owm-install /private/tmp/npm-cache +npm pack --cache /private/tmp/npm-cache --pack-destination /private/tmp/owm-pack +npm install --cache /private/tmp/npm-cache --prefix /private/tmp/owm-install /private/tmp/owm-pack/opencode-working-memory-*.tgz +node -e "import('/private/tmp/owm-install/node_modules/opencode-working-memory').then(m => console.log(m.default.id))" +node -e "import('/private/tmp/owm-install/node_modules/opencode-working-memory/server').then(m => console.log(m.default.id))" +node -e "import('/private/tmp/owm-install/node_modules/opencode-working-memory/tui').then(m => console.log(m.default.id))" +``` + +Expected output for both import checks: + +```text +working-memory +``` + +For the TUI import, expected output is: + +```text +working-memory-tui +``` + +Also inspect the installed package manifest and assert `main`, `exports["."]`, `exports["./server"]`, and `exports["./tui"]` all point to `.js` files. + +## Release Preparation + +After implementation and verification: + +1. Inspect `git diff`. +2. Confirm the working tree contains only this release fix plus the pre-existing unrelated untracked plan. +3. Add a `CHANGELOG.md` entry for `1.6.7` describing the Node loader/package entry fix. +4. Run `npm version patch` or otherwise bump `package.json` and `package-lock.json` to `1.6.7` consistently. +5. If publishing from `main`, prepare the verified package for publish after the final git diff review. + +## Risks + +- Build config may expose TypeScript type-check errors in files that were previously excluded from `tsconfig.memory-diag.json`. +- Package smoke tests that call `npm pack` can fail on the developer machine if `~/.npm` has permission problems; tests should use an isolated cache under `/private/tmp`. +- OpenCode may resolve `./server` or `./tui` differently from bare package import, so both exports should be checked. +- `npm run check:package-integrity` currently checks package-lock version alignment only; entry-point assertions must live in packaging tests or a new entry-point check. +- Publishing both `src/` and `dist/` increases tarball size, but runtime correctness depends on the JS entry points rather than removing sources. + +## Rollback + +Revert the package entry/build changes and publish a corrected package if the JS entry causes a runtime regression. Existing local-path development remains unaffected by reverting because it can still import TypeScript from outside `node_modules`. diff --git a/package.json b/package.json index 1971d32..b6bb0be 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,13 @@ { "name": "opencode-working-memory", - "version": "1.6.6", + "version": "1.6.7", "description": "Three-layer memory architecture for OpenCode with workspace memory and hot session state", "type": "module", - "main": "index.ts", + "main": "dist/index.js", "exports": { - ".": "./index.ts", - "./server": "./index.ts", - "./tui": "./src/tui-plugin.ts" + ".": "./dist/index.js", + "./server": "./dist/index.js", + "./tui": "./dist/src/tui-plugin.js" }, "bin": { "memory-diag": "./scripts/memory-diag-bin.cjs" @@ -24,8 +24,9 @@ ], "scripts": { "clean:dist": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"", - "build:memory-diag": "npm run clean:dist && tsc -p tsconfig.memory-diag.json", - "build": "npm run build:memory-diag && node -e \"console.log('BUILD_PASS')\"", + "build:dist": "npm run clean:dist && tsc -p tsconfig.memory-diag.json", + "build:memory-diag": "npm run build:dist", + "build": "npm run build:dist && node -e \"console.log('BUILD_PASS')\"", "prepack": "npm run build", "diag": "npm run --silent build:memory-diag && node ./scripts/memory-diag-bin.cjs", "test:pack:memory-diag": "node --test --experimental-strip-types tests/smoke/memory-diag-packaging.test.ts", diff --git a/tests/smoke/memory-diag-packaging.test.ts b/tests/smoke/memory-diag-packaging.test.ts index 1aa3c20..81e10b3 100644 --- a/tests/smoke/memory-diag-packaging.test.ts +++ b/tests/smoke/memory-diag-packaging.test.ts @@ -1,7 +1,7 @@ import test from "node:test"; import assert from "node:assert/strict"; import { execFile } from "node:child_process"; -import { mkdir, mkdtemp, rm } from "node:fs/promises"; +import { mkdir, mkdtemp, readFile, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; @@ -15,18 +15,26 @@ function executable(name: "npm" | "npx"): string { return process.platform === "win32" ? `${name}.cmd` : name; } +function binPath(root: string, name: string): string { + return join(root, "node_modules", ".bin", process.platform === "win32" ? `${name}.cmd` : name); +} + test("packed memory-diag bin runs from a temp consumer project", async () => { const tempRoot = await mkdtemp(join(tmpdir(), "opencode-memory-diag-packaging-")); const packDir = join(tempRoot, "pack"); const consumerDir = join(tempRoot, "consumer"); + const cacheDir = join(tempRoot, "npm-cache"); try { await mkdir(packDir, { recursive: true }); await mkdir(consumerDir, { recursive: true }); + await mkdir(cacheDir, { recursive: true }); const packResult = await execFileAsync(executable("npm"), [ "pack", repoRoot, + "--cache", + cacheDir, "--pack-destination", packDir, "--silent", @@ -35,11 +43,18 @@ test("packed memory-diag bin runs from a temp consumer project", async () => { assert.ok(tarballName, `npm pack did not report a tarball name. stdout:\n${packResult.stdout}\nstderr:\n${packResult.stderr}`); const tarballPath = join(packDir, tarballName); - const runResult = await execFileAsync(executable("npx"), [ - "--yes", - "--package", + await execFileAsync(executable("npm"), [ + "install", + "--cache", + cacheDir, tarballPath, - "memory-diag", + "--legacy-peer-deps", + "--prefix", + consumerDir, + "--silent", + ], { cwd: tempRoot, maxBuffer }); + + const runResult = await execFileAsync(binPath(consumerDir, "memory-diag"), [ "--help", ], { cwd: consumerDir, maxBuffer }); @@ -49,3 +64,69 @@ test("packed memory-diag bin runs from a temp consumer project", async () => { await rm(tempRoot, { recursive: true, force: true }); } }); + +test("packed plugin runtime imports from node_modules JavaScript entries", async () => { + const tempRoot = await mkdtemp(join(tmpdir(), "opencode-working-memory-packaging-")); + const packDir = join(tempRoot, "pack"); + const consumerDir = join(tempRoot, "consumer"); + const cacheDir = join(tempRoot, "npm-cache"); + + try { + await mkdir(packDir, { recursive: true }); + await mkdir(consumerDir, { recursive: true }); + await mkdir(cacheDir, { recursive: true }); + + const packResult = await execFileAsync(executable("npm"), [ + "pack", + repoRoot, + "--cache", + cacheDir, + "--pack-destination", + packDir, + "--silent", + ], { cwd: tempRoot, maxBuffer }); + const tarballName = packResult.stdout.trim().split(/\r?\n/).filter(Boolean).at(-1); + assert.ok(tarballName, `npm pack did not report a tarball name. stdout:\n${packResult.stdout}\nstderr:\n${packResult.stderr}`); + + const tarballPath = join(packDir, tarballName); + await execFileAsync(executable("npm"), [ + "install", + "--cache", + cacheDir, + "--prefix", + consumerDir, + tarballPath, + "--legacy-peer-deps", + "--silent", + ], { cwd: tempRoot, maxBuffer }); + + const packageJsonPath = join(consumerDir, "node_modules", "opencode-working-memory", "package.json"); + const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8")) as { + main?: unknown; + exports?: Record; + }; + + assert.equal(packageJson.main, "dist/index.js"); + assert.equal(packageJson.exports?.["."], "./dist/index.js"); + assert.equal(packageJson.exports?.["./server"], "./dist/index.js"); + assert.equal(packageJson.exports?.["./tui"], "./dist/src/tui-plugin.js"); + + const importResult = await execFileAsync(process.execPath, [ + "-e", + [ + "const plugin = await import('opencode-working-memory');", + "const server = await import('opencode-working-memory/server');", + "const tui = await import('opencode-working-memory/tui');", + "console.log([plugin.default.id, server.default.id, tui.default.id].join('\\n'));", + ].join(" "), + ], { cwd: consumerDir, maxBuffer }); + + assert.deepEqual(importResult.stdout.trim().split(/\r?\n/), [ + "working-memory", + "working-memory", + "working-memory-tui", + ]); + } finally { + await rm(tempRoot, { recursive: true, force: true }); + } +}); diff --git a/tsconfig.memory-diag.json b/tsconfig.memory-diag.json index 5d329f5..01df5b7 100644 --- a/tsconfig.memory-diag.json +++ b/tsconfig.memory-diag.json @@ -10,18 +10,10 @@ "rewriteRelativeImportExtensions": true, "tsBuildInfoFile": "dist/.tsbuildinfo" }, - "include": ["scripts/memory-diag.ts", "scripts/memory-diag/**/*.ts", "src/**/*.ts"], + "include": ["index.ts", "scripts/memory-diag.ts", "scripts/memory-diag/**/*.ts", "src/**/*.ts"], "exclude": [ "node_modules", "dist", - "tests", - "src/plugin.ts", - "src/tui-plugin.ts", - "src/opencode.ts", - "src/session-state.ts", - "src/extractors.ts", - "src/pending-journal.ts", - "src/promotion-accounting.ts", - "src/memory-visibility.ts" + "tests" ] }