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 <skill>/data or <skill>/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
This commit is contained in:
Alexander 2026-06-25 04:15:04 -04:00 committed by GitHub
parent 3ebb9c8fd5
commit 57d9ba7989
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -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<void> {
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<void> {
// 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 });
}