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
14 changes: 7 additions & 7 deletions clients/cli/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 11 additions & 11 deletions clients/mcpdo/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion clients/mcpdo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"@types/node": "^24.12.4",
"esbuild": "^0.28.2",
"sucrase": {
"commander": "^13.1.0"
"commander": "^15.0.0"
}
}
}
16 changes: 0 additions & 16 deletions clients/tui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,19 +211,3 @@ declaring a `react` peer must be in `noExternal` unless it is listed as external
by design, each exempt package must also be a root dependency (external means
consumers install it), and the root `react` range must stay open to the major.
Add a React-rendering dependency, and that test tells you to bundle it.

### The `ink-form` label patch

Bundling `ink-form` also makes it patchable, which one label needs: the hint
under an incomplete form reads "you have not **competed** yet". It is upstream's
string, hardcoded in `ink-form/lib/SubmitButton.js` with no prop to override,
and `ink-form` was last published in 2024 — so `tsup.config.ts` corrects it with
an esbuild `onLoad` hook as the file enters the bundle. It is reported upstream
as [lukasbach/ink-form#14](https://github.com/lukasbach/ink-form/issues/14); if
a release ever carries the fix, drop the patch.

The hook **throws** when the string isn't found rather than passing the source
through. A silent no-op would let an `ink-form` upgrade retire the patch without
anyone noticing it had stopped applying — or leave a patch aimed at a string
that no longer exists. If the build fails there, check whether upstream fixed
the typo and delete the patch instead of re-targeting it.
94 changes: 1 addition & 93 deletions clients/tui/__tests__/tsupConfig.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,7 @@ import { readFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import type { Options } from "tsup";
import tsupConfig, {
INK_FORM_INCOMPLETE_HINT,
INK_FORM_SUBMIT_BUTTON,
fixInkFormIncompleteHint,
inkFormLabelPatch,
} from "../tsup.config.js";
import tsupConfig from "../tsup.config.js";

const clientDir = path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
Expand Down Expand Up @@ -94,15 +89,6 @@ function reactRenderingDependencies(): string[] {
);
}

/** The real `ink-form` module the label patch targets. */
const submitButtonPath = path.join(
clientDir,
"node_modules",
"ink-form",
"lib",
"SubmitButton.js",
);

const config = singleConfig(tsupConfig);
const noExternal = stringEntries(config.noExternal);
const external = stringEntries(config.external);
Expand Down Expand Up @@ -176,84 +162,6 @@ describe("tui tsup config", () => {
expect(noExternal).not.toContain("react");
});

it("corrects ink-form's misspelled incomplete-form hint", () => {
// Read the real dependency, so an `ink-form` upgrade that fixes or rewords
// the label fails here — the patch must then be removed, not left silently
// matching nothing.
const patched = fixInkFormIncompleteHint(
readFileSync(submitButtonPath, "utf8"),
submitButtonPath,
);
expect(patched).toContain(INK_FORM_INCOMPLETE_HINT.fixed);
expect(patched).not.toContain(INK_FORM_INCOMPLETE_HINT.typo);
});

it("routes the real module through the plugin, not just the helper", async () => {
// The helper is only reached if the plugin's onLoad filter matches. Drive
// the plugin as esbuild would — register, then invoke — so a filter that
// stops matching the real module's path fails here instead of no-opping
// through a green build (the exact silence this patch exists to prevent).
type OnLoadCallback = (args: {
path: string;
}) => Promise<{ contents: string }>;
const registered: { filter: RegExp; callback: OnLoadCallback }[] = [];

const build = {
onLoad: (options: { filter: RegExp }, callback: OnLoadCallback) =>
registered.push({ filter: options.filter, callback }),
};
// esbuild's `PluginBuild` carries far more than this patch touches, and the
// stub above deliberately implements only the one hook it registers — so
// the double cast is bridging a real structural gap, not hiding a mismatch.
// A hook the patch called but the stub lacks fails as undefined here rather
// than passing silently.
// esbuild types `setup` as `void | Promise<void>`; await it so the
// assertions below can't run before an async setup has registered.
await inkFormLabelPatch.setup(
build as unknown as Parameters<typeof inkFormLabelPatch.setup>[0],
);

expect(registered).toHaveLength(1);
const [{ filter, callback }] = registered;
expect(filter.test(submitButtonPath)).toBe(true);

const { contents } = await callback({ path: submitButtonPath });
expect(contents).toContain(INK_FORM_INCOMPLETE_HINT.fixed);
expect(contents).not.toContain(INK_FORM_INCOMPLETE_HINT.typo);
});

it("scopes the patch to ink-form's SubmitButton and nothing else", () => {
expect(INK_FORM_SUBMIT_BUTTON.test(submitButtonPath)).toBe(true);
// A Windows-style path must match too — the filter runs against whatever
// esbuild resolved, and its separator is the platform's.
expect(
INK_FORM_SUBMIT_BUTTON.test(
"C:\\repo\\node_modules\\ink-form\\lib\\SubmitButton.js",
),
).toBe(true);
for (const other of [
"/repo/node_modules/ink-form/lib/Form.js",
"/repo/node_modules/ink-select-input/build/SubmitButton.js",
"/repo/src/SubmitButton.jsx",
]) {
expect(INK_FORM_SUBMIT_BUTTON.test(other), other).toBe(false);
}
});

it("fails loudly rather than silently skipping a label it cannot find", () => {
expect(() =>
fixInkFormIncompleteHint("no such label here", "SubmitButton.js"),
).toThrow(/no longer contains the ink-form label/);
});

it("only rewrites the misspelling", () => {
// The two strings must differ by exactly the fix, or the patch is silently
// changing copy nobody reviewed.
expect(INK_FORM_INCOMPLETE_HINT.fixed).toBe(
INK_FORM_INCOMPLETE_HINT.typo.replace("competed", "completed"),
);
});

it("verifies the deps it exempts are still declared", () => {
// If `ink` ever leaves the dependency list, the exemption above is stale
// and would silently excuse a future package that took its name.
Expand Down
Loading
Loading