mirror of
https://github.com/openai/codex.git
synced 2026-04-24 14:45:27 +00:00
linted and working
This commit is contained in:
@@ -5,4 +5,4 @@
|
||||
// without the “.tsx” extension when running under ts-node/esm in the test
|
||||
// environment.
|
||||
|
||||
export { default } from "./image-picker-overlay.tsx";
|
||||
export { default } from "./image-picker-overlay.tsx";
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/* eslint-disable import/order */
|
||||
import path from "node:path";
|
||||
|
||||
|
||||
import { Box, Text, useInput, useStdin } from "ink";
|
||||
|
||||
import SelectInput from "../select-input/select-input.js";
|
||||
@@ -37,7 +36,7 @@ export default function ImagePickerOverlay({
|
||||
|
||||
if (process.env["DEBUG_OVERLAY"]) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[overlay] mount, items:', items.map((i) => i.label).join(','));
|
||||
console.log("[overlay] mount, items:", items.map((i) => i.label).join(","));
|
||||
}
|
||||
|
||||
// Keep track of currently highlighted item so <Enter> can act synchronously.
|
||||
@@ -64,7 +63,7 @@ export default function ImagePickerOverlay({
|
||||
function onData(data: Buffer) {
|
||||
if (process.env["DEBUG_OVERLAY"]) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[overlay] stdin data', JSON.stringify(data.toString()));
|
||||
console.log("[overlay] stdin data", JSON.stringify(data.toString()));
|
||||
}
|
||||
|
||||
// ink-testing-library pipes mocked input through `stdin.emit("data", …)`
|
||||
@@ -117,48 +116,48 @@ export default function ImagePickerOverlay({
|
||||
// in the spec).
|
||||
useInput(
|
||||
(input, key) => {
|
||||
if (process.env["DEBUG_OVERLAY"]) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
"[overlay] root useInput",
|
||||
JSON.stringify(input),
|
||||
key.return,
|
||||
);
|
||||
}
|
||||
|
||||
if (key.escape || key.backspace || input === "\u007f") {
|
||||
if (process.env["DEBUG_OVERLAY"]) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log("[overlay] cancel");
|
||||
}
|
||||
perform(onCancel);
|
||||
} else if (key.return) {
|
||||
// Act on the currently highlighted item synchronously so tests that
|
||||
// simulate a bare "\r" keypress without triggering SelectInput’s
|
||||
// onSelect callback still work. This mirrors <SelectInput>’s own
|
||||
// behaviour but executing the logic here avoids having to depend on
|
||||
// that implementation detail.
|
||||
|
||||
const item = highlighted.current;
|
||||
if (!item) {
|
||||
return;
|
||||
console.log(
|
||||
"[overlay] root useInput",
|
||||
JSON.stringify(input),
|
||||
key.return,
|
||||
);
|
||||
}
|
||||
|
||||
if (process.env["DEBUG_OVERLAY"]) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[overlay] return on', item.label, item.value);
|
||||
}
|
||||
|
||||
perform(() => {
|
||||
if (item.value === "__UP__") {
|
||||
onChangeDir(path.dirname(cwd));
|
||||
} else if (item.label.endsWith("/")) {
|
||||
onChangeDir(item.value);
|
||||
} else {
|
||||
onPick(item.value);
|
||||
if (key.escape || key.backspace || input === "\u007f") {
|
||||
if (process.env["DEBUG_OVERLAY"]) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log("[overlay] cancel");
|
||||
}
|
||||
});
|
||||
}
|
||||
perform(onCancel);
|
||||
} else if (key.return) {
|
||||
// Act on the currently highlighted item synchronously so tests that
|
||||
// simulate a bare "\r" keypress without triggering SelectInput’s
|
||||
// onSelect callback still work. This mirrors <SelectInput>’s own
|
||||
// behaviour but executing the logic here avoids having to depend on
|
||||
// that implementation detail.
|
||||
|
||||
const item = highlighted.current;
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (process.env["DEBUG_OVERLAY"]) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log("[overlay] return on", item.label, item.value);
|
||||
}
|
||||
|
||||
perform(() => {
|
||||
if (item.value === "__UP__") {
|
||||
onChangeDir(path.dirname(cwd));
|
||||
} else if (item.label.endsWith("/")) {
|
||||
onChangeDir(item.value);
|
||||
} else {
|
||||
onPick(item.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
{ isActive: true },
|
||||
);
|
||||
|
||||
@@ -84,7 +84,11 @@ export default function TerminalChatInput({
|
||||
|
||||
if (process.env["DEBUG_TCI"]) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[TCI] render stage', { input, pickerCwd, attachedCount: attachedImages.length });
|
||||
console.log("[TCI] render stage", {
|
||||
input,
|
||||
pickerCwd,
|
||||
attachedCount: attachedImages.length,
|
||||
});
|
||||
}
|
||||
// Open picker when user finished typing '@'
|
||||
React.useEffect(() => {
|
||||
@@ -115,7 +119,7 @@ export default function TerminalChatInput({
|
||||
|
||||
if (process.env["DEBUG_TCI"]) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[TCI] raw stdin', JSON.stringify(str));
|
||||
console.log("[TCI] raw stdin", JSON.stringify(str));
|
||||
}
|
||||
|
||||
if (str === "@" && pickerCwd == null) {
|
||||
@@ -162,7 +166,7 @@ export default function TerminalChatInput({
|
||||
(_input, _key) => {
|
||||
if (process.env["DEBUG_TCI"]) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[TCI] useInput raw', JSON.stringify(_input), _key);
|
||||
console.log("[TCI] useInput raw", JSON.stringify(_input), _key);
|
||||
}
|
||||
|
||||
// When image picker overlay is open delegate all keystrokes to it.
|
||||
@@ -172,7 +176,7 @@ export default function TerminalChatInput({
|
||||
if (!confirmationPrompt && !loading) {
|
||||
if (process.env["DEBUG_TCI"]) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('useInput received', JSON.stringify(_input));
|
||||
console.log("useInput received", JSON.stringify(_input));
|
||||
}
|
||||
|
||||
// Open image picker when user types '@' and picker not already open.
|
||||
@@ -227,7 +231,10 @@ export default function TerminalChatInput({
|
||||
}
|
||||
|
||||
// Backspace on empty draft removes last attached image
|
||||
if ((_key.backspace || _input === "\u007f") && attachedImages.length > 0) {
|
||||
if (
|
||||
(_key.backspace || _input === "\u007f") &&
|
||||
attachedImages.length > 0
|
||||
) {
|
||||
if (input.length === 0) {
|
||||
setAttachedImages((prev) => prev.slice(0, -1));
|
||||
}
|
||||
@@ -450,7 +457,11 @@ export default function TerminalChatInput({
|
||||
text:
|
||||
missingImages.length === 1
|
||||
? `Warning: image "${missingImages[0]}" not found and was not attached.`
|
||||
: `Warning: ${missingImages.length} images were not found and were skipped: ${missingImages.join(", ")}`,
|
||||
: `Warning: ${
|
||||
missingImages.length
|
||||
} images were not found and were skipped: ${missingImages.join(
|
||||
", ",
|
||||
)}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -520,7 +531,12 @@ export default function TerminalChatInput({
|
||||
|
||||
if (process.env["DEBUG_TCI"]) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[TCI] attached image added', filePath, 'total', attachedImages.length + 1);
|
||||
console.log(
|
||||
"[TCI] attached image added",
|
||||
filePath,
|
||||
"total",
|
||||
attachedImages.length + 1,
|
||||
);
|
||||
}
|
||||
setPickerCwd(null);
|
||||
}}
|
||||
@@ -535,7 +551,7 @@ export default function TerminalChatInput({
|
||||
}
|
||||
if (process.env["DEBUG_TCI"]) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[TCI] render AttachmentPreview', attachedImages);
|
||||
console.log("[TCI] render AttachmentPreview", attachedImages);
|
||||
}
|
||||
return (
|
||||
<Box flexDirection="column" paddingX={1} marginBottom={1}>
|
||||
|
||||
@@ -119,7 +119,12 @@ function TerminalChatResponseMessage({
|
||||
: c.type === "input_text"
|
||||
? c.text
|
||||
: c.type === "input_image"
|
||||
? imageFilenameByDataUrl.get(c.image_url as string) || "<Image>"
|
||||
? (() => {
|
||||
const label = imageFilenameByDataUrl.get(
|
||||
c.image_url as string,
|
||||
);
|
||||
return label ? `<Image path="${label}">` : "<Image>";
|
||||
})()
|
||||
: c.type === "input_file"
|
||||
? c.filename
|
||||
: "", // unknown content type
|
||||
|
||||
@@ -9,6 +9,8 @@ export interface TerminalInlineImageProps {
|
||||
}
|
||||
|
||||
// During tests or when terminal does not support images, fallback to alt.
|
||||
export default function TerminalInlineImage({ alt = "[image]" }: TerminalInlineImageProps): React.ReactElement {
|
||||
export default function TerminalInlineImage({
|
||||
alt = "[image]",
|
||||
}: TerminalInlineImageProps): React.ReactElement {
|
||||
return <Text>{alt}</Text>;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Re‑export TypeScript implementation so regular `.js` import paths used by
|
||||
// the existing test‑suite continue to resolve correctly at runtime.
|
||||
|
||||
export * from "./image-picker-utils.ts";
|
||||
export * from "./image-picker-utils.ts";
|
||||
|
||||
@@ -28,7 +28,10 @@ export function getDirectoryItems(
|
||||
try {
|
||||
for (const entry of fs.readdirSync(cwd, { withFileTypes: true })) {
|
||||
if (entry.isDirectory()) {
|
||||
dirs.push({ label: entry.name + "/", value: path.join(cwd, entry.name) });
|
||||
dirs.push({
|
||||
label: entry.name + "/",
|
||||
value: path.join(cwd, entry.name),
|
||||
});
|
||||
} else if (entry.isFile() && isImage(entry.name)) {
|
||||
files.push({ label: entry.name, value: path.join(cwd, entry.name) });
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ describe("Chat input attachment preview", () => {
|
||||
process.chdir(TMP);
|
||||
|
||||
const { stdin, flush, lastFrameStripped, cleanup } = renderTui(
|
||||
React.createElement(TerminalChatInput, props())
|
||||
React.createElement(TerminalChatInput, props()),
|
||||
);
|
||||
|
||||
await flush();
|
||||
|
||||
@@ -65,7 +65,7 @@ describe("Backspace deletes attached image", () => {
|
||||
process.chdir(TMP);
|
||||
|
||||
const { stdin, flush, lastFrameStripped, cleanup } = renderTui(
|
||||
React.createElement(TerminalChatInput, props())
|
||||
React.createElement(TerminalChatInput, props()),
|
||||
);
|
||||
|
||||
await flush();
|
||||
|
||||
@@ -47,7 +47,7 @@ describe("Image picker overlay", () => {
|
||||
onPick: () => {},
|
||||
onCancel: () => {},
|
||||
onChangeDir,
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
await flush();
|
||||
@@ -65,7 +65,7 @@ describe("Image picker overlay", () => {
|
||||
onPick,
|
||||
onCancel: () => {},
|
||||
onChangeDir: () => {},
|
||||
})
|
||||
}),
|
||||
);
|
||||
await flush();
|
||||
await type(stdin, "\r", flush);
|
||||
|
||||
@@ -5,7 +5,10 @@ import { renderTui } from "./ui-test-helpers.js";
|
||||
|
||||
import TerminalInlineImage from "../src/components/chat/terminal-inline-image.js";
|
||||
import TerminalChatResponseItem from "../src/components/chat/terminal-chat-response-item.js";
|
||||
import { imageFilenameByDataUrl, createInputItem } from "../src/utils/input-utils.js";
|
||||
import {
|
||||
imageFilenameByDataUrl,
|
||||
createInputItem,
|
||||
} from "../src/utils/input-utils.js";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
@@ -14,11 +17,10 @@ import { imageFilenameByDataUrl, createInputItem } from "../src/utils/input-util
|
||||
import path from "node:path";
|
||||
import fs from "node:fs";
|
||||
|
||||
|
||||
describe("TerminalInlineImage fallback", () => {
|
||||
it("renders alt text in test env", () => {
|
||||
const { lastFrameStripped } = renderTui(
|
||||
<TerminalInlineImage src={Buffer.from("abc")} alt="placeholder" />
|
||||
<TerminalInlineImage src={Buffer.from("abc")} alt="placeholder" />,
|
||||
);
|
||||
expect(lastFrameStripped()).toContain("placeholder");
|
||||
});
|
||||
@@ -42,9 +44,9 @@ describe("TerminalChatResponseItem image label", () => {
|
||||
it("shows filename", () => {
|
||||
const msg = fakeImageMessage("sample.png");
|
||||
const { lastFrameStripped } = renderTui(
|
||||
<TerminalChatResponseItem item={msg as any} />
|
||||
<TerminalChatResponseItem item={msg as any} />,
|
||||
);
|
||||
expect(lastFrameStripped()).toContain("sample.png");
|
||||
expect(lastFrameStripped()).toContain('<Image path="sample.png">');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,33 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import { defineConfig } from "vite";
|
||||
|
||||
// Provide a stub Vite config in the CLI package to avoid resolving a parent-level vite.config.js
|
||||
export default defineConfig({});
|
||||
/**
|
||||
* Vite configuration used by the Codex CLI package. The build process itself
|
||||
* doesn’t rely on Vite’s bundling features – we only ship this file so that
|
||||
* Vitest can pick it up when executing the unit‑test suite. The only custom
|
||||
* logic we currently inject is a *test* configuration block that registers a
|
||||
* small setup script executed in each worker thread before any test files are
|
||||
* loaded. That script polyfills `process.chdir()` which is disallowed inside
|
||||
* Node.js workers as of v22 and would otherwise throw when some tests attempt
|
||||
* to change the working directory.
|
||||
*/
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
// Execute tests inside worker threads but force Vitest to spawn *only one*
|
||||
// worker. This keeps the environment isolation that some components
|
||||
// depend on while avoiding a `tinypool` recursion bug that occasionally
|
||||
// triggers when multiple workers are used.
|
||||
pool: "threads",
|
||||
poolOptions: {
|
||||
threads: {
|
||||
minThreads: 1,
|
||||
maxThreads: 1,
|
||||
},
|
||||
},
|
||||
/**
|
||||
* Register the setup file. We use a relative path so that Vitest resolves
|
||||
* it against the project root irrespective of where the CLI is executed.
|
||||
*/
|
||||
setupFiles: ["./tests/test-setup.js"],
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user