From 57d9ba79897882bf312eece8fca5c5ff96571bbb Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 25 Jun 2026 04:15:04 -0400 Subject: [PATCH] fix(cli): replace stale data/scripts pointer files on install (#386) On Windows, an older install (or a git checkout that materialized the repo's symlinked data/scripts as plain "pointer" files) leaves a regular file at /data or /scripts. copyDataAndScripts then calls mkdir on that path, which throws EEXIST; under `uipro init --ai all` the per-platform error is swallowed, leaving e.g. codex with the stale pointer files and no real directories. Add ensureCleanDir(): before mkdir, lstat the target and remove it if it is not already a directory. Existing real directories are preserved (re-install is unaffected). Verified with a repro harness. Closes #237 --- cli/src/utils/template.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/cli/src/utils/template.ts b/cli/src/utils/template.ts index d671a4d..21b6099 100755 --- a/cli/src/utils/template.ts +++ b/cli/src/utils/template.ts @@ -1,4 +1,4 @@ -import { readFile, mkdir, writeFile, cp, access, readdir } from 'node:fs/promises'; +import { readFile, mkdir, writeFile, cp, access, readdir, lstat, rm } from 'node:fs/promises'; import { join, dirname } from 'node:path'; import { homedir } from 'node:os'; import { fileURLToPath } from 'node:url'; @@ -156,6 +156,23 @@ export async function renderSkillFile(config: PlatformConfig, isGlobal = false): return frontmatter + content; } +/** + * Replace a pre-existing non-directory at `path` so a real directory can be + * created there. Older CLI installs (and Windows checkouts of the repo's + * symlinked data/scripts) can leave plain "pointer" files at these paths; + * mkdir then throws EEXIST and the install silently leaves stale files. + */ +async function ensureCleanDir(path: string): Promise { + try { + const stat = await lstat(path); + if (!stat.isDirectory()) { + await rm(path, { recursive: true, force: true }); + } + } catch { + // Nothing exists at the path yet — mkdir will create it. + } +} + /** * Copy data and scripts to target directory */ @@ -168,12 +185,14 @@ async function copyDataAndScripts(targetSkillDir: string): Promise { // Copy data if (await exists(dataSource)) { + await ensureCleanDir(dataTarget); await mkdir(dataTarget, { recursive: true }); await cp(dataSource, dataTarget, { recursive: true }); } // Copy scripts if (await exists(scriptsSource)) { + await ensureCleanDir(scriptsTarget); await mkdir(scriptsTarget, { recursive: true }); await cp(scriptsSource, scriptsTarget, { recursive: true }); }