refactor: split shared/mod.ts into pure and TUI-dependent barrels (#1807)
This commit is contained in:
parent
28a3387e2b
commit
b486177066
17 changed files with 35 additions and 74 deletions
|
|
@ -18,7 +18,7 @@ import {
|
|||
type Question,
|
||||
type QuestionOption,
|
||||
type RoundResult,
|
||||
} from "./shared/mod.js";
|
||||
} from "./shared/tui.js";
|
||||
|
||||
// ─── Types ────────────────────────────────────────────────────────────────────
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ import { resolve } from "node:path";
|
|||
import type { ExtensionAPI, Theme } from "@gsd/pi-coding-agent";
|
||||
import { Editor, type EditorTheme, Key, matchesKey, Text, truncateToWidth, wrapTextWithAnsi } from "@gsd/pi-tui";
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import { makeUI, maskEditorLine, type ProgressStatus } from "./shared/mod.js";
|
||||
import { makeUI } from "./shared/tui.js";
|
||||
import { maskEditorLine, type ProgressStatus } from "./shared/mod.js";
|
||||
import { parseSecretsManifest, formatSecretsManifest } from "./gsd/files.js";
|
||||
import { resolveMilestoneFile } from "./gsd/paths.js";
|
||||
import type { SecretsManifestEntry } from "./gsd/types.js";
|
||||
|
|
@ -234,7 +235,7 @@ export async function showSecretsSummary(
|
|||
|
||||
const existingSet = new Set(existingKeys);
|
||||
|
||||
await ctx.ui.custom((tui: any, theme: Theme, _kb: any, done: (r: null) => void) => {
|
||||
await ctx.ui.custom((_tui: any, theme: Theme, _kb: any, done: (r: null) => void) => {
|
||||
let cachedLines: string[] | undefined;
|
||||
|
||||
function handleInput(_data: string) {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ import { parseRoadmap, parsePlan } from "./files.js";
|
|||
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { truncateToWidth, visibleWidth } from "@gsd/pi-tui";
|
||||
import { makeUI, GLYPH, INDENT } from "../shared/mod.js";
|
||||
import { makeUI } from "../shared/tui.js";
|
||||
import { GLYPH, INDENT } from "../shared/mod.js";
|
||||
import { computeProgressScore } from "./progress-score.js";
|
||||
import { getActiveWorktreeName } from "./worktree-command.js";
|
||||
import { loadEffectiveGSDPreferences, getGlobalGSDPreferencesPath } from "./preferences.js";
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent
|
|||
import { checkRemoteAutoSession, isAutoActive, isAutoPaused, stopAutoRemote } from "../auto.js";
|
||||
import { assertSafeDirectory } from "../validate-directory.js";
|
||||
import { resolveProjectRoot } from "../worktree.js";
|
||||
import { showNextAction } from "../../shared/mod.js";
|
||||
import { showNextAction } from "../../shared/tui.js";
|
||||
import { handleStatus } from "./handlers/core.js";
|
||||
|
||||
export interface GsdDispatchContext {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
||||
import { showNextAction } from "../shared/mod.js";
|
||||
import { showNextAction } from "../shared/tui.js";
|
||||
import { setQueuePhaseActive } from "./index.js";
|
||||
import { loadFile } from "./files.js";
|
||||
import { loadPrompt, inlineTemplate } from "./prompt-loader.js";
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import type { ExtensionAPI, ExtensionContext, ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
||||
import { showNextAction } from "../shared/mod.js";
|
||||
import { showNextAction } from "../shared/tui.js";
|
||||
import { loadFile, parseRoadmap } from "./files.js";
|
||||
import { loadPrompt, inlineTemplate } from "./prompt-loader.js";
|
||||
import { buildSkillActivationBlock } from "./auto-prompts.js";
|
||||
|
|
@ -31,7 +31,7 @@ import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|||
import { detectProjectState } from "./detection.js";
|
||||
import { showProjectInit, offerMigration } from "./init-wizard.js";
|
||||
import { validateDirectory } from "./validate-directory.js";
|
||||
import { showConfirm } from "../shared/mod.js";
|
||||
import { showConfirm } from "../shared/tui.js";
|
||||
import { debugLog } from "./debug-logger.js";
|
||||
import { findMilestoneIds, nextMilestoneId, reserveMilestoneId, getReservedMilestoneIds } from "./milestone-ids.js";
|
||||
import { parkMilestone, discardMilestone } from "./milestone-actions.js";
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
||||
import { existsSync, mkdirSync, writeFileSync, readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { showNextAction } from "../shared/mod.js";
|
||||
import { showNextAction } from "../shared/tui.js";
|
||||
import { nativeIsRepo, nativeInit } from "./native-git-bridge.js";
|
||||
import { ensureGitignore, untrackRuntimeFiles } from "./gitignore.js";
|
||||
import { gsdRoot } from "./paths.js";
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import { existsSync, readFileSync } from "node:fs";
|
|||
import { resolve, join, dirname } from "node:path";
|
||||
import { gsdRoot } from "../paths.js";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { showNextAction } from "../../shared/mod.js";
|
||||
import { showNextAction } from "../../shared/tui.js";
|
||||
import {
|
||||
validatePlanningDirectory,
|
||||
parsePlanningDirectory,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@
|
|||
import type { ExtensionContext } from "@gsd/pi-coding-agent";
|
||||
import { type Theme } from "@gsd/pi-coding-agent";
|
||||
import { Key, matchesKey, truncateToWidth, type TUI } from "@gsd/pi-tui";
|
||||
import { makeUI, GLYPH } from "../shared/mod.js";
|
||||
import { makeUI } from "../shared/tui.js";
|
||||
import { GLYPH } from "../shared/mod.js";
|
||||
import { validateQueueOrder, type DependencyValidation } from "./queue-order.js";
|
||||
|
||||
export interface ReorderItem {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
// Verifies that shared/ui.ts does NOT eagerly import @gsd/pi-tui at the
|
||||
// module level. An eager top-level import causes /exit (and any other
|
||||
// command that transitively loads shared/mod → shared/ui) to blow up when
|
||||
// @gsd/pi-tui cannot be resolved — e.g. extensions copied to
|
||||
// ~/.gsd/agent/extensions/ where no node_modules tree exists.
|
||||
// Structural contract: shared/mod.ts must never import @gsd/pi-tui.
|
||||
// TUI-dependent exports live in shared/tui.ts instead.
|
||||
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
|
@ -11,36 +8,8 @@ import { join, dirname } from "node:path";
|
|||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const uiSrc = readFileSync(join(__dirname, "../../shared/ui.ts"), "utf-8");
|
||||
|
||||
test("shared/ui.ts has no top-level import from @gsd/pi-tui", () => {
|
||||
// Match lines like: import { ... } from "@gsd/pi-tui";
|
||||
// But ignore type-only imports (import type / import("@gsd/pi-tui").X)
|
||||
// and comments.
|
||||
const lines = uiSrc.split("\n");
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
// Skip comments and type-only references
|
||||
if (trimmed.startsWith("//") || trimmed.startsWith("*") || trimmed.startsWith("/*")) continue;
|
||||
// Skip type-only import statements
|
||||
if (trimmed.startsWith("import type ")) continue;
|
||||
// Skip inline import() type annotations (erased at runtime)
|
||||
if (/import\(["']@gsd\/pi-tui["']\)/.test(trimmed) && !trimmed.startsWith("import ")) continue;
|
||||
|
||||
// Flag any eager import statement pulling runtime values from @gsd/pi-tui
|
||||
if (/^\s*import\s+\{/.test(line) && line.includes("@gsd/pi-tui")) {
|
||||
assert.fail(
|
||||
`Found eager top-level import from @gsd/pi-tui — this must be lazy.\n` +
|
||||
`Line: ${trimmed}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("shared/ui.ts lazily resolves @gsd/pi-tui inside makeUI", () => {
|
||||
// The lazy accessor pattern: require("@gsd/pi-tui") inside a function body
|
||||
assert.ok(
|
||||
uiSrc.includes('require("@gsd/pi-tui")'),
|
||||
"Expected a lazy require(\"@gsd/pi-tui\") call inside a function body",
|
||||
);
|
||||
test("shared/mod.ts has no import from @gsd/pi-tui", () => {
|
||||
const src = readFileSync(join(__dirname, "../../shared/mod.ts"), "utf-8");
|
||||
assert.ok(!src.includes("@gsd/pi-tui"), "mod.ts must not import @gsd/pi-tui");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
*/
|
||||
|
||||
import type { ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
||||
import { showNextAction } from "../shared/mod.js";
|
||||
import { showNextAction } from "../shared/tui.js";
|
||||
import type { CaptureEntry, Classification, TriageResult } from "./captures.js";
|
||||
import { markCaptureResolved } from "./captures.js";
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent
|
|||
import { loadPrompt } from "./prompt-loader.js";
|
||||
import { autoCommitCurrentBranch, getMainBranch, resolveGitHeadPath, nudgeGitBranchCache } from "./worktree.js";
|
||||
import { runWorktreePostCreateHook } from "./auto-worktree.js";
|
||||
import { showConfirm } from "../shared/mod.js";
|
||||
import { showConfirm } from "../shared/tui.js";
|
||||
import { gsdRoot, milestonesDir } from "./paths.js";
|
||||
import {
|
||||
createWorktree,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
// Barrel file — re-exports consumed by external modules
|
||||
|
||||
export {
|
||||
makeUI,
|
||||
GLYPH,
|
||||
INDENT,
|
||||
STATUS_GLYPH,
|
||||
|
|
@ -27,10 +26,6 @@ export {
|
|||
|
||||
export { shortcutDesc } from "./terminal.js";
|
||||
export { toPosixPath } from "./path-display.js";
|
||||
export { showInterviewRound } from "./interview-ui.js";
|
||||
export type { Question, QuestionOption, RoundResult } from "./interview-ui.js";
|
||||
export { showNextAction } from "./next-action-ui.js";
|
||||
export { showConfirm } from "./confirm-ui.js";
|
||||
export { sanitizeError, maskEditorLine } from "./sanitize.js";
|
||||
export { formatDateShort, truncateWithEllipsis } from "./format-utils.js";
|
||||
export { splitFrontmatter, parseFrontmatterMap } from "./frontmatter.js";
|
||||
|
|
|
|||
11
src/resources/extensions/shared/tui.ts
Normal file
11
src/resources/extensions/shared/tui.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// Barrel — TUI-dependent exports.
|
||||
// Import from here when your code needs makeUI, showInterviewRound,
|
||||
// showNextAction, or showConfirm. These all have a transitive dependency
|
||||
// on @gsd/pi-tui and must not be imported from shared/mod.
|
||||
|
||||
export { makeUI } from "./ui.js";
|
||||
export type { UI } from "./ui.js";
|
||||
export { showInterviewRound } from "./interview-ui.js";
|
||||
export type { Question, QuestionOption, RoundResult } from "./interview-ui.js";
|
||||
export { showNextAction } from "./next-action-ui.js";
|
||||
export { showConfirm } from "./confirm-ui.js";
|
||||
|
|
@ -29,23 +29,7 @@
|
|||
*/
|
||||
|
||||
import { type Theme } from "@gsd/pi-coding-agent";
|
||||
|
||||
// ─── Lazy @gsd/pi-tui resolution ─────────────────────────────────────────────
|
||||
// Deferred to first makeUI() call so that importing this module (via the
|
||||
// shared/mod barrel) does not blow up when @gsd/pi-tui cannot be resolved —
|
||||
// e.g. for commands like /exit that never render TUI components.
|
||||
|
||||
import { createRequire } from "node:module";
|
||||
|
||||
type PiTuiFns = typeof import("@gsd/pi-tui");
|
||||
let _piTui: PiTuiFns | undefined;
|
||||
function piTui(): PiTuiFns {
|
||||
if (!_piTui) {
|
||||
const _require = createRequire(import.meta.url);
|
||||
_piTui = _require("@gsd/pi-tui") as PiTuiFns;
|
||||
}
|
||||
return _piTui;
|
||||
}
|
||||
import { truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@gsd/pi-tui";
|
||||
|
||||
// ─── Glyphs ───────────────────────────────────────────────────────────────────
|
||||
// Change these to restyle every cursor, checkbox, and indicator at once.
|
||||
|
|
@ -217,7 +201,6 @@ export interface UI {
|
|||
export function makeUI(theme: Theme, width: number): UI {
|
||||
// ── Internal helpers ───────────────────────────────────────────────────────
|
||||
|
||||
const { truncateToWidth, visibleWidth, wrapTextWithAnsi } = piTui();
|
||||
|
||||
const add = (s: string): string => truncateToWidth(s, width);
|
||||
const wrap = (s: string): string[] => wrapTextWithAnsi(s, width);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { ExtensionAPI } from "@gsd/pi-coding-agent";
|
||||
import { showInterviewRound, type Question, type RoundResult } from "../shared/mod.js";
|
||||
import { showInterviewRound, type Question, type RoundResult } from "../shared/tui.js";
|
||||
|
||||
export default function createExtension(pi: ExtensionAPI) {
|
||||
pi.registerCommand("create-extension", {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { ExtensionAPI } from "@gsd/pi-coding-agent";
|
||||
import { showInterviewRound, type Question, type RoundResult } from "../shared/mod.js";
|
||||
import { showInterviewRound, type Question, type RoundResult } from "../shared/tui.js";
|
||||
|
||||
export default function createSlashCommand(pi: ExtensionAPI) {
|
||||
pi.registerCommand("create-slash-command", {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue