fix: break remaining shared/mod.js barrel imports in report generation chain (#1588)

PR #1527 fixed metrics.ts but missed several other paths that still
reach shared/mod.js → ui.js → @gsd/pi-tui during report generation
via native dynamic import() (which bypasses jiti alias resolution).

Remaining chains fixed:
- preferences.ts, preferences-validation.ts, export.ts, forensics.ts,
  migrate/parsers.ts: import from shared/format-utils.js directly
- state.ts, visualizer-data.ts, files.ts: import from milestone-ids.js
  instead of guided-flow.js (which pulls in shared/mod.js)
- files.ts: import checkExistingEnvKeys from new env-utils.ts instead
  of get-secrets-from-user.ts (which imports @gsd/pi-tui)

New file: env-utils.ts extracts the pure checkExistingEnvKeys function.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jeremy McSpadden 2026-03-20 09:28:53 -05:00 committed by GitHub
parent e35ad9d194
commit 21a9ab2bcf
10 changed files with 45 additions and 33 deletions

View file

@ -0,0 +1,31 @@
// GSD Extension — Environment variable utilities
// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
//
// Pure utility for checking existing env keys in .env files and process.env.
// Extracted from get-secrets-from-user.ts to avoid pulling in @gsd/pi-tui
// when only env-checking is needed (e.g. from files.ts during report generation).
import { readFile } from "node:fs/promises";
/**
* Check which keys already exist in a .env file or process.env.
* Returns the subset of `keys` that are already set.
*/
export async function checkExistingEnvKeys(keys: string[], envFilePath: string): Promise<string[]> {
let fileContent = "";
try {
fileContent = await readFile(envFilePath, "utf8");
} catch {
// ENOENT or other read error — proceed with empty content
}
const existing: string[] = [];
for (const key of keys) {
const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const regex = new RegExp(`^${escaped}\\s*=`, "m");
if (regex.test(fileContent) || key in process.env) {
existing.push(key);
}
}
return existing;
}

View file

@ -67,30 +67,11 @@ async function writeEnvKey(filePath: string, key: string, value: string): Promis
// ─── Exported utilities ───────────────────────────────────────────────────────
/**
* Check which keys already exist in the .env file or process.env.
* Returns the subset of `keys` that are already set.
* Handles ENOENT gracefully (still checks process.env).
* Empty-string values count as existing.
*/
export async function checkExistingEnvKeys(keys: string[], envFilePath: string): Promise<string[]> {
let fileContent = "";
try {
fileContent = await readFile(envFilePath, "utf8");
} catch {
// ENOENT or other read error — proceed with empty content
}
const existing: string[] = [];
for (const key of keys) {
const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const regex = new RegExp(`^${escaped}\\s*=`, "m");
if (regex.test(fileContent) || key in process.env) {
existing.push(key);
}
}
return existing;
}
// Re-export from env-utils.ts so existing consumers still work.
// The implementation lives in env-utils.ts to avoid pulling @gsd/pi-tui
// into modules that only need env-checking (e.g. files.ts during reports).
import { checkExistingEnvKeys } from "./env-utils.js";
export { checkExistingEnvKeys };
/**
* Detect the write destination based on project files in basePath.

View file

@ -11,7 +11,7 @@ import {
} from "./metrics.js";
import type { UnitMetrics } from "./metrics.js";
import { gsdRoot } from "./paths.js";
import { formatDuration, fileLink } from "../shared/mod.js";
import { formatDuration, fileLink } from "../shared/format-utils.js";
import { getErrorMessage } from "./error-utils.js";
/**

View file

@ -7,7 +7,7 @@ import { promises as fs } from 'node:fs';
import { resolve } from 'node:path';
import { atomicWriteAsync } from './atomic-write.js';
import { resolveMilestoneFile, relMilestoneFile, resolveGsdRootFile } from './paths.js';
import { milestoneIdSort, findMilestoneIds } from './guided-flow.js';
import { milestoneIdSort, findMilestoneIds } from './milestone-ids.js';
import type {
Roadmap, BoundaryMapEntry,
@ -20,7 +20,7 @@ import type {
ManifestStatus,
} from './types.js';
import { checkExistingEnvKeys } from '../get-secrets-from-user.js';
import { checkExistingEnvKeys } from '../env-utils.js';
import { parseRoadmapSlices } from './roadmap-slices.js';
import { nativeParseRoadmap, nativeExtractSection, nativeParsePlanFile, nativeParseSummaryFile, NATIVE_UNAVAILABLE } from './native-parser-bridge.js';
import { debugTime, debugCount } from './debug-logger.js';

View file

@ -27,7 +27,7 @@ import { deriveState } from "./state.js";
import { isAutoActive } from "./auto.js";
import { loadPrompt } from "./prompt-loader.js";
import { gsdRoot } from "./paths.js";
import { formatDuration } from "../shared/mod.js";
import { formatDuration } from "../shared/format-utils.js";
import { getAutoWorktreePath } from "./auto-worktree.js";
// ─── Types ────────────────────────────────────────────────────────────────────

View file

@ -3,7 +3,7 @@
// Zero Pi dependencies — uses only exported helpers from files.ts.
import { splitFrontmatter, parseFrontmatterMap, extractBoldField } from '../files.js';
import { normalizeStringArray } from '../../shared/mod.js';
import { normalizeStringArray } from '../../shared/format-utils.js';
import type {
PlanningRoadmap,

View file

@ -10,7 +10,7 @@ import type { GitPreferences } from "./git-service.js";
import type { PostUnitHookConfig, PreDispatchHookConfig, TokenProfile, PhaseSkipPreferences } from "./types.js";
import type { DynamicRoutingConfig } from "./model-router.js";
import { VALID_BRANCH_NAME } from "./git-service.js";
import { normalizeStringArray } from "../shared/mod.js";
import { normalizeStringArray } from "../shared/format-utils.js";
import {
KNOWN_PREFERENCE_KEYS,

View file

@ -17,7 +17,7 @@ import { gsdRoot } from "./paths.js";
import { parse as parseYaml } from "yaml";
import type { PostUnitHookConfig, PreDispatchHookConfig, TokenProfile } from "./types.js";
import type { DynamicRoutingConfig } from "./model-router.js";
import { normalizeStringArray } from "../shared/mod.js";
import { normalizeStringArray } from "../shared/format-utils.js";
import { resolveProfileDefaults as _resolveProfileDefaults } from "./preferences-models.js";
import {

View file

@ -31,7 +31,7 @@ import {
gsdRoot,
} from './paths.js';
import { milestoneIdSort, findMilestoneIds } from './guided-flow.js';
import { milestoneIdSort, findMilestoneIds } from './milestone-ids.js';
import { nativeBatchParseGsdFiles, type BatchParsedFile } from './native-parser-bridge.js';
import { join, resolve } from 'path';

View file

@ -3,7 +3,7 @@
import { existsSync, readFileSync, statSync } from 'node:fs';
import { deriveState } from './state.js';
import { parseRoadmap, parsePlan, parseSummary, loadFile } from './files.js';
import { findMilestoneIds } from './guided-flow.js';
import { findMilestoneIds } from './milestone-ids.js';
import { resolveMilestoneFile, resolveSliceFile, resolveGsdRootFile } from './paths.js';
import {
getLedger,