Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file removed .freebuff/project-id
Empty file.
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -129,15 +129,15 @@ jobs:
export NODE_AUTH_TOKEN=${{ secrets.GITHUB_TOKEN }}
PUBLISH_REGISTRY=https://npm.pkg.github.com node --experimental-strip-types scripts/publish-scoped.ts
# The release claim, checked instead of assumed. Without this the job
# reported success while `@viztor/dsh-opencode-patch` sat four versions behind
# reported success while `@viztor/dsh-opencode*` sat four versions behind
# the canonical package, because a publisher that was never registered
# fails with the same E404 as a transient fault.
- name: verify every published target
run: |
VER=$(node -p "require('./package.json').version")
# One line: scripts/check.ts reads it to prove this list equals what
# publish-scoped.ts publishes (scripts/scoped-targets.ts).
pending="dsh-opencode-patch@$VER @viztor/dsh-opencode-patch@$VER"
pending="dsh-opencode-patch@$VER @viztor/dsh-opencode-patch@$VER @viztor/dsh-opencode@$VER"

# Polls, because a successful `npm publish` does not mean the version
# is readable yet: npm answers "Your package is being processed and
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ cd ~/.dsh/profiles/web
npm install dsh-opencode-patch
```

The package is `dsh-opencode-patch` under every name it is used by: the dependency you install, the bundle entry in your profile, and the row the host resolves. It is also published as [`@viztor/dsh-opencode-patch`](https://www.npmjs.com/package/@viztor/dsh-opencode-patch); the retired name `@viztor/dsh-opencode` is no longer published, so an install still on it should move to either of those.
The package is `dsh-opencode-patch` under every name it is used by: the dependency you install, the bundle entry in your profile, and the row the host resolves. It is also published as [`@viztor/dsh-opencode-patch`](https://www.npmjs.com/package/@viztor/dsh-opencode-patch), and the older name [`@viztor/dsh-opencode`](https://www.npmjs.com/package/@viztor/dsh-opencode) keeps publishing as a deprecated thin wrapper that re-exports it — an install still on that name keeps working, and moving to either of the other two is a rename rather than a fix.

**2. Enable the bundle** — add the package to the profile's `dsh.profile.bundles` array:

Expand Down Expand Up @@ -429,7 +429,7 @@ These exist in the schema but render no control — each is a literal, a marker,
- **A smaller public API.** The package's entry exports `apply`, `Config`, `inject` and `name` — the names the host reads — and the config types (`PluginConfig`, `ResolvedPluginConfig`, `ModelOverrideConfig`, `ModelRouteConfig`, `EndpointFamily`, `KeySourcePolicy`). Everything else it used to re-export is internal now.
- **`./src/*` is no longer an export.** `src/` was never in the published files, so the subpath resolved in the repository and nowhere else.
- **`internalRoutes` was removed.** The per-protocol routes always mount. A profile row that still carries `internalRoutes` validates and the key is ignored.
- **`@viztor/dsh-opencode` is no longer published.** Its last version's "Renamed to @viztor/dsh-opencode-patch" deprecation stands as the final message; install `dsh-opencode-patch` or `@viztor/dsh-opencode-patch` instead.
- **`@viztor/dsh-opencode` is deprecated, and still published.** It is a thin wrapper that re-exports `dsh-opencode-patch`, so an install on the old name keeps resolving; the registry marks it deprecated and points at the canonical name. New installs should use `dsh-opencode-patch` or `@viztor/dsh-opencode-patch`.

## 📜 Compatibility & Verification

Expand Down
4 changes: 2 additions & 2 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ cd ~/.dsh/profiles/web
npm install dsh-opencode-patch
```

包名在所有用到它的地方都是 `dsh-opencode-patch`:你安装的依赖、profile 里的 bundle 条目,以及宿主解析的那一行。它也以 [`@viztor/dsh-opencode-patch`](https://www.npmjs.com/package/@viztor/dsh-opencode-patch) 发布;旧名 `@viztor/dsh-opencode` 已不再发布,仍在使用它的安装请改用上述任一名称。
包名在所有用到它的地方都是 `dsh-opencode-patch`:你安装的依赖、profile 里的 bundle 条目,以及宿主解析的那一行。它也以 [`@viztor/dsh-opencode-patch`](https://www.npmjs.com/package/@viztor/dsh-opencode-patch) 发布,而旧名 [`@viztor/dsh-opencode`](https://www.npmjs.com/package/@viztor/dsh-opencode) 继续以**已弃用的薄包装**发布、转导出本包——仍在使用旧名的安装照常可用,换到另外两个名字只是改名,不是修问题。

**2. 启用 bundle**——把该包加入 profile 的 `dsh.profile.bundles` 数组:

Expand Down Expand Up @@ -423,7 +423,7 @@ Zen 路线上不出现任何 Go 的数字:根本不画圆环(没有可量的
- **更小的公开 API。** 包入口只导出 `apply`、`Config`、`inject` 和 `name`(宿主读取的名称),以及配置类型(`PluginConfig`、`ResolvedPluginConfig`、`ModelOverrideConfig`、`ModelRouteConfig`、`EndpointFamily`、`KeySourcePolicy`)。此前额外重新导出的其余内容现在都是内部实现。
- **`./src/*` 不再是导出路径。** `src/` 从未包含在发布文件中,所以这个子路径只在仓库内可解析,安装后并不存在。
- **`internalRoutes` 已移除。** 按协议划分的内部路由总是会挂载。仍带有 `internalRoutes` 的 profile 配置行可以通过校验,该键会被忽略。
- **`@viztor/dsh-opencode` 不再发布。** 其最后一个版本上的 "Renamed to @viztor/dsh-opencode-patch" 弃用提示即为最终信息;请改装 `dsh-opencode-patch` 或 `@viztor/dsh-opencode-patch`。
- **`@viztor/dsh-opencode` 已弃用,但仍在发布。** 它是一个转导出 `dsh-opencode-patch` 的薄包装,所以用旧名的安装照常解析;registry 上标记为弃用并指向正式名称。新安装请用 `dsh-opencode-patch` 或 `@viztor/dsh-opencode-patch`。

## 📜 兼容性与验证

Expand Down
97 changes: 88 additions & 9 deletions scripts/publish-scoped.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,21 @@
* The package ships as `dsh-opencode-patch` — the unscoped name DSH resolves,
* the name the docs use, the name release-please versions.
*
* It also publishes under `@viztor/dsh-opencode-patch` — the CANONICAL scoped
* name. Same tree, same bundle, same version as the unscoped one; this is the
* name new installs should use, and the one the client bundle registers its
* card under (`SCOPED_PKG`). The list is `SCOPED_TARGETS`
* (`scripts/scoped-targets.ts`), which `scripts/check.ts` holds in step with
* release.yml's verify step.
* It also publishes under:
* - `@viztor/dsh-opencode-patch` — the CANONICAL scoped name. Same tree, same
* bundle, same version as the unscoped one; this is the name new installs
* should use, and the one the client bundle registers its card under
* (`SCOPED_PKG`).
* - `@viztor/dsh-opencode` — DEPRECATED, and still published. A thin wrapper
* that ships no `lib/client.js` and forwards its row to `dsh-opencode-patch`.
* It is kept publishing so an existing install keeps resolving — a name that
* stops publishing breaks the installs that still use it, which is the one
* thing a compatibility alias must not do. Nothing in the source tree
* registers or advertises it, which is what stops one package answering to
* two spellings.
*
* The retired name `@viztor/dsh-opencode` is no longer published: its last
* version's "Renamed to @viztor/dsh-opencode-patch" deprecation stands as the
* final message on the registry.
* The list is `SCOPED_TARGETS` (`scripts/scoped-targets.ts`), which
* `scripts/check.ts` holds in step with release.yml's verify step.
*
* Usage: node --experimental-strip-types scripts/publish-scoped.ts
* PUBLISH_DRY_RUN=1 node --experimental-strip-types scripts/publish-scoped.ts
Expand All @@ -37,6 +42,7 @@ import { tmpdir } from "node:os";
import path from "node:path";

import { SCOPED_TARGETS } from "./scoped-targets.ts";
import { wrapperForwarder } from "./wrapper-forwarder.ts";

const ROOT = path.resolve(import.meta.dirname, "..");

Expand Down Expand Up @@ -119,6 +125,79 @@ for (const target of SCOPED_TARGETS) {
'name: "@viztor/dsh-opencode-patch"'
)
);
} else if (target === "@viztor/dsh-opencode") {
// Point at the CANONICAL name, not the other alias: the message a user
// reads on the registry is the one place that should not offer a choice
// between two spellings of the same package.
manifest.deprecated =
"Renamed to @viztor/dsh-opencode-patch. Please install that instead: https://www.npmjs.com/package/@viztor/dsh-opencode-patch";
const existingDeps =
(manifest.dependencies as Record<string, string> | undefined) ?? {};
manifest.dependencies = {
...existingDeps,
"dsh-opencode-patch": `^${version}`,
};

// Thin wrapper entrypoints that re-export dsh-opencode-patch directly.
//
// **The names the canonical package HAS, and no `default`:** it exports
// none, and re-exporting one is a link error on every load — which is
// what this used to ship. The text comes from one helper so the runtime
// entry and the type declaration cannot drift.
const forwarder = wrapperForwarder();
writeFileSync(path.join(scratch, "lib", "index.mjs"), `${forwarder}\n`);
writeFileSync(path.join(scratch, "lib", "index.d.mts"), `${forwarder}\n`);

// The wrapper carries no client half of its own.
//
// Its patch forwards to the `dsh-opencode-patch` row, and that row's own
// package supplies the browser bundle. Shipping a second copy here — the
// manifest's whole-`lib` `files` entry copies it in — would hand the page
// the same bundle twice under the same module ids, mounting the client
// half twice. A ~38 kB duplicate in a package whose stated job is to
// forward two lines is also simply not thin.
const dsh = manifest.dsh as Record<string, unknown> | undefined;
if (dsh !== undefined) delete dsh.client;
const wrapperExports = manifest.exports as
| Record<string, unknown>
| undefined;
if (wrapperExports !== undefined) delete wrapperExports["./client"];
rmSync(path.join(scratch, "lib", "client.js"), { force: true });

// Forwarding cordis patch to mount dsh-opencode-patch
const forwarderPatch = [
"# Thin wrapper patch forwarding to dsh-opencode-patch",
"- insert:",
" - id: dsh-opencode-patch",
' name: "dsh-opencode-patch"',
].join("\n");
writeFileSync(
path.join(scratch, "cordis.patch.yml"),
`${forwarderPatch}\n`
);

// The page leads with the CANONICAL name. A retired name may appear as the
// reason the reader is here — never as the package's own title, which is
// what made three npm pages look like three packages.
const redirectReadme = [
"# dsh-opencode-patch",
"",
"> ⚠️ **This npm name is deprecated.** The package is `dsh-opencode-patch`,",
"> scoped as [`@viztor/dsh-opencode-patch`](https://www.npmjs.com/package/@viztor/dsh-opencode-patch).",
"",
"You are on the page for the old name `@viztor/dsh-opencode`. It is a **thin compatibility",
"wrapper**: it depends on and re-exports `dsh-opencode-patch` and ships no code of its own.",
"",
"### How to migrate",
"",
"```sh",
'# DSH Web UI (recommended): Settings → Plugins → Install Plugin → "dsh-opencode-patch"',
"",
"# Or from your profile directory:",
"npm install dsh-opencode-patch",
"```",
].join("\n");
writeFileSync(path.join(scratch, "README.md"), redirectReadme);
}
writeFileSync(
path.join(scratch, "package.json"),
Expand Down
12 changes: 8 additions & 4 deletions scripts/scoped-targets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@
* from `publish-scoped.ts` because that script publishes at import time, so the
* check and its test could not read the list from there.
*
* `@viztor/dsh-opencode` is no longer published (retired in 1.x, D9): its last
* version's "Renamed to @viztor/dsh-opencode-patch" deprecation stands as the
* final message on the registry.
* `@viztor/dsh-opencode` is DEPRECATED and still published: a thin wrapper that
* re-exports the canonical package, kept so an existing install keeps resolving.
* A compatibility name that stops publishing breaks the installs still using it,
* which is the one thing it must not do.
*
* @module scripts/scoped-targets
*/
export const SCOPED_TARGETS: readonly string[] = ["@viztor/dsh-opencode-patch"];
export const SCOPED_TARGETS: readonly string[] = [
"@viztor/dsh-opencode-patch",
"@viztor/dsh-opencode",
];
37 changes: 37 additions & 0 deletions scripts/wrapper-forwarder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* The retired alias's forwarder, in one place.
*
* **The wrapper re-exports the canonical package's NAMES, and nothing else.**
* It used to add `export { default } from "dsh-opencode-patch";` — and the
* canonical package has no `default` export, so every load of the wrapper was a
* link error:
*
* ```
* SyntaxError: The requested module 'dsh-opencode-patch'
* does not provide an export named 'default'
* ```
*
* That is a hard failure before any of the plugin runs: the alias is a
* published package, and the thing it exists for — letting an existing install
* keep resolving — is exactly what it prevented.
*
* The re-export lives here rather than inline in the publish script because it
* is written to TWO files (the runtime entry and the type declaration), and
* the two must not drift: a `.d.mts` that re-exports a name the package does
* not have is a type error for every consumer, and one that differs from the
* runtime entry is a lie in the declarations.
*
* @module scripts/wrapper-forwarder
*/

/** The package the wrapper forwards to. */
export const FORWARDED_PACKAGE = "dsh-opencode-patch";

/**
* The forwarder's source, for the runtime entry and the type declaration.
*
* `export *` and nothing else: a star re-export carries whatever names the
* target has, so it cannot name one it does not.
*/
export const wrapperForwarder = (): string =>
`export * from "${FORWARDED_PACKAGE}";`;
18 changes: 14 additions & 4 deletions test/package-checks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,14 +129,22 @@ describe("package checks · the publish list and the verify list agree", () => {
expect(publishListMismatch(["p", "a"], ["p", "a"])).toEqual([]);
});

it("the scoped publish list and release.yml's verify list agree, and neither names the retired wrapper", () => {
it("the scoped publish list and release.yml's verify list agree, deprecated alias included", () => {
// **The deprecated alias is published, so it is verified.** A compatibility
// name that stops publishing breaks the installs still on it, which is the
// one thing it must not do — and a name that publishes without being
// verified is how a release reports success while one of its three packages
// sits behind.
const pkg = readPackage();
const pending = releasePendingTargets(
readFileSync(join(ROOT, ".github", "workflows", "release.yml"), "utf8")
);
expect(pending).toEqual([pkg.name, ...SCOPED_TARGETS]);
expect(SCOPED_TARGETS).toEqual(["@viztor/dsh-opencode-patch"]);
expect(pending).not.toContain("@viztor/dsh-opencode");
expect(SCOPED_TARGETS).toEqual([
"@viztor/dsh-opencode-patch",
"@viztor/dsh-opencode",
]);
expect(pending).toContain("@viztor/dsh-opencode");
});

it("the gate and the publish script read those lists rather than keeping their own", () => {
Expand All @@ -157,7 +165,9 @@ describe("package checks · the publish list and the verify list agree", () => {
expect(
[...publish.matchAll(/for \(const \w+ of (\w+)\)/gu)].map((m) => m[1])
).toContain("SCOPED_TARGETS");
expect(publish).not.toContain('"@viztor/dsh-opencode"');
// The wrapper branch names the deprecated alias on purpose: it is the one
// target whose manifest is rewritten rather than copied.
expect(publish).toContain('"@viztor/dsh-opencode"');
const check = code("check.ts");
expect(check).toMatch(/\bexportsOutsideFiles\(/u);
expect(check).toMatch(/\bpublishListMismatch\(/u);
Expand Down
Loading
Loading