mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-04-19 07:04:40 +00:00
fix: tfcode web fails after npm install — copy app dist in postinstall
The web command crashed because postinstall scripts never copied the app/dist directory from the platform package. Added copyAppDir() to both postinstall scripts, multi-path resolution in findAppDir() and server static serving, and updated branding to use local favicon. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,171 +1,225 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const { spawn, execSync } = require('child_process');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { spawn, execSync } = require("child_process")
|
||||
const fs = require("fs")
|
||||
const path = require("path")
|
||||
|
||||
const RESET = '\x1b[0m';
|
||||
const BOLD = '\x1b[1m';
|
||||
const GREEN = '\x1b[32m';
|
||||
const YELLOW = '\x1b[33m';
|
||||
const RED = '\x1b[31m';
|
||||
const CYAN = '\x1b[36m';
|
||||
const DIM = '\x1b[90m';
|
||||
const RESET = "\x1b[0m"
|
||||
const BOLD = "\x1b[1m"
|
||||
const GREEN = "\x1b[32m"
|
||||
const YELLOW = "\x1b[33m"
|
||||
const RED = "\x1b[31m"
|
||||
const CYAN = "\x1b[36m"
|
||||
const DIM = "\x1b[90m"
|
||||
|
||||
function log(msg) {
|
||||
console.log(msg);
|
||||
console.log(msg)
|
||||
}
|
||||
|
||||
function logSuccess(msg) {
|
||||
console.log(`${GREEN}✓${RESET} ${msg}`);
|
||||
console.log(`${GREEN}✓${RESET} ${msg}`)
|
||||
}
|
||||
|
||||
function logError(msg) {
|
||||
console.error(`${RED}✗${RESET} ${msg}`);
|
||||
console.error(`${RED}✗${RESET} ${msg}`)
|
||||
}
|
||||
|
||||
function logInfo(msg) {
|
||||
console.log(`${CYAN}ℹ${RESET} ${msg}`);
|
||||
console.log(`${CYAN}ℹ${RESET} ${msg}`)
|
||||
}
|
||||
|
||||
function logWarning(msg) {
|
||||
console.log(`${YELLOW}!${RESET} ${msg}`);
|
||||
console.log(`${YELLOW}!${RESET} ${msg}`)
|
||||
}
|
||||
|
||||
function checkPython() {
|
||||
const commands = ['python3', 'python'];
|
||||
|
||||
const commands = ["python3", "python"]
|
||||
|
||||
for (const cmd of commands) {
|
||||
try {
|
||||
const result = execSync(`${cmd} --version`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] });
|
||||
const match = result.match(/Python (\d+)\.(\d+)/);
|
||||
const result = execSync(`${cmd} --version`, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
|
||||
const match = result.match(/Python (\d+)\.(\d+)/)
|
||||
if (match) {
|
||||
const major = parseInt(match[1]);
|
||||
const minor = parseInt(match[2]);
|
||||
const major = parseInt(match[1])
|
||||
const minor = parseInt(match[2])
|
||||
if (major >= 3 && minor >= 10) {
|
||||
return { cmd, version: result.trim() };
|
||||
return { cmd, version: result.trim() }
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function installPythonDeps(pythonCmd) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const packages = ['toothfairyai', 'pydantic', 'httpx', 'rich'];
|
||||
|
||||
log(`${DIM}Installing Python packages: ${packages.join(', ')}...${RESET}`);
|
||||
|
||||
const packages = ["toothfairyai", "pydantic", "httpx", "rich"]
|
||||
|
||||
log(`${DIM}Installing Python packages: ${packages.join(", ")}...${RESET}`)
|
||||
|
||||
// Try with --user first, then --break-system-packages if needed
|
||||
const args = ['-m', 'pip', 'install', '--user', ...packages];
|
||||
|
||||
const args = ["-m", "pip", "install", "--user", ...packages]
|
||||
|
||||
const proc = spawn(pythonCmd, args, {
|
||||
stdio: 'inherit',
|
||||
shell: process.platform === 'win32'
|
||||
});
|
||||
|
||||
proc.on('close', (code) => {
|
||||
stdio: "inherit",
|
||||
shell: process.platform === "win32",
|
||||
})
|
||||
|
||||
proc.on("close", (code) => {
|
||||
if (code === 0) {
|
||||
resolve();
|
||||
resolve()
|
||||
} else {
|
||||
// Try with --break-system-packages flag
|
||||
log(`${DIM}Retrying with --break-system-packages...${RESET}`);
|
||||
const retryArgs = ['-m', 'pip', 'install', '--user', '--break-system-packages', ...packages];
|
||||
log(`${DIM}Retrying with --break-system-packages...${RESET}`)
|
||||
const retryArgs = ["-m", "pip", "install", "--user", "--break-system-packages", ...packages]
|
||||
const retry = spawn(pythonCmd, retryArgs, {
|
||||
stdio: 'inherit',
|
||||
shell: process.platform === 'win32'
|
||||
});
|
||||
|
||||
retry.on('close', (retryCode) => {
|
||||
stdio: "inherit",
|
||||
shell: process.platform === "win32",
|
||||
})
|
||||
|
||||
retry.on("close", (retryCode) => {
|
||||
if (retryCode === 0) {
|
||||
resolve();
|
||||
resolve()
|
||||
} else {
|
||||
reject(new Error(`pip install exited with code ${retryCode}`));
|
||||
reject(new Error(`pip install exited with code ${retryCode}`))
|
||||
}
|
||||
});
|
||||
|
||||
retry.on('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
})
|
||||
|
||||
retry.on("error", (err) => {
|
||||
reject(err)
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
proc.on('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
})
|
||||
|
||||
proc.on("error", (err) => {
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function copyAppDir() {
|
||||
let platform
|
||||
switch (process.platform) {
|
||||
case "darwin":
|
||||
platform = "darwin"
|
||||
break
|
||||
case "linux":
|
||||
platform = "linux"
|
||||
break
|
||||
case "win32":
|
||||
platform = "windows"
|
||||
break
|
||||
default:
|
||||
platform = process.platform
|
||||
}
|
||||
|
||||
let arch
|
||||
switch (process.arch) {
|
||||
case "x64":
|
||||
arch = "x64"
|
||||
break
|
||||
case "arm64":
|
||||
arch = "arm64"
|
||||
break
|
||||
default:
|
||||
arch = process.arch
|
||||
}
|
||||
|
||||
const pkgName = `@toothfairyai/tfcode-${platform}-${arch}`
|
||||
const appDir = path.join(__dirname, "app")
|
||||
|
||||
if (fs.existsSync(path.join(appDir, "dist", "index.html"))) {
|
||||
logSuccess("Web app already present")
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const pkgDir = path.dirname(require.resolve(`${pkgName}/package.json`))
|
||||
const srcAppDir = path.join(pkgDir, "bin", "app")
|
||||
|
||||
if (!fs.existsSync(path.join(srcAppDir, "dist", "index.html"))) {
|
||||
logWarning("Web app dist not found in platform package")
|
||||
return
|
||||
}
|
||||
|
||||
fs.cpSync(srcAppDir, appDir, { recursive: true })
|
||||
logSuccess("Web app copied from platform package")
|
||||
} catch (e) {
|
||||
logWarning(`Could not copy web app: ${e.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
log('');
|
||||
log(`${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);
|
||||
log(`${BOLD} tfcode - ToothFairyAI's official coding agent${RESET}`);
|
||||
log(`${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);
|
||||
log('');
|
||||
|
||||
log("")
|
||||
log(`${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`)
|
||||
log(`${BOLD} tfcode - ToothFairyAI's official coding agent${RESET}`)
|
||||
log(`${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`)
|
||||
log("")
|
||||
|
||||
copyAppDir()
|
||||
|
||||
// Check for Python
|
||||
logInfo('Checking Python installation...');
|
||||
const python = checkPython();
|
||||
|
||||
logInfo("Checking Python installation...")
|
||||
const python = checkPython()
|
||||
|
||||
if (!python) {
|
||||
log('');
|
||||
logError('Python 3.10+ is required but not found.');
|
||||
log('');
|
||||
log(`${BOLD}Please install Python 3.10 or later:${RESET}`);
|
||||
log('');
|
||||
log(` ${CYAN}macOS:${RESET} brew install python@3.12`);
|
||||
log(` ${CYAN}Ubuntu:${RESET} sudo apt-get install python3.12`);
|
||||
log(` ${CYAN}Windows:${RESET} Download from https://python.org/downloads`);
|
||||
log('');
|
||||
log(`${DIM}After installing Python, run: npm rebuild tfcode${RESET}`);
|
||||
log('');
|
||||
process.exit(1);
|
||||
log("")
|
||||
logError("Python 3.10+ is required but not found.")
|
||||
log("")
|
||||
log(`${BOLD}Please install Python 3.10 or later:${RESET}`)
|
||||
log("")
|
||||
log(` ${CYAN}macOS:${RESET} brew install python@3.12`)
|
||||
log(` ${CYAN}Ubuntu:${RESET} sudo apt-get install python3.12`)
|
||||
log(` ${CYAN}Windows:${RESET} Download from https://python.org/downloads`)
|
||||
log("")
|
||||
log(`${DIM}After installing Python, run: npm rebuild tfcode${RESET}`)
|
||||
log("")
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
logSuccess(`Found ${python.version} (${python.cmd})`);
|
||||
log('');
|
||||
|
||||
|
||||
logSuccess(`Found ${python.version} (${python.cmd})`)
|
||||
log("")
|
||||
|
||||
// Install Python dependencies
|
||||
logInfo('Installing ToothFairyAI Python SDK...');
|
||||
logInfo("Installing ToothFairyAI Python SDK...")
|
||||
try {
|
||||
await installPythonDeps(python.cmd);
|
||||
logSuccess('Python dependencies installed');
|
||||
await installPythonDeps(python.cmd)
|
||||
logSuccess("Python dependencies installed")
|
||||
} catch (err) {
|
||||
logWarning(`Failed to install Python dependencies: ${err.message}`);
|
||||
log('');
|
||||
log(`${DIM}You can install them manually with:${RESET}`);
|
||||
log(` ${CYAN}${python.cmd} -m pip install toothfairyai pydantic httpx rich${RESET}`);
|
||||
log('');
|
||||
logWarning(`Failed to install Python dependencies: ${err.message}`)
|
||||
log("")
|
||||
log(`${DIM}You can install them manually with:${RESET}`)
|
||||
log(` ${CYAN}${python.cmd} -m pip install toothfairyai pydantic httpx rich${RESET}`)
|
||||
log("")
|
||||
// Don't exit - user might install manually later
|
||||
}
|
||||
|
||||
log('');
|
||||
log(`${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);
|
||||
log(`${GREEN}✓ tfcode installed successfully!${RESET}`);
|
||||
log(`${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`);
|
||||
log('');
|
||||
log(`${BOLD}Quick Start:${RESET}`);
|
||||
log('');
|
||||
log(` ${CYAN}1.${RESET} Set your ToothFairyAI credentials:`);
|
||||
log(` ${DIM}export TF_WORKSPACE_ID="your-workspace-id"${RESET}`);
|
||||
log(` ${DIM}export TF_API_KEY="your-api-key"${RESET}`);
|
||||
log('');
|
||||
log(` ${CYAN}2.${RESET} Validate your credentials:`);
|
||||
log(` ${DIM}tfcode validate${RESET}`);
|
||||
log('');
|
||||
log(` ${CYAN}3.${RESET} Sync tools from your workspace:`);
|
||||
log(` ${DIM}tfcode sync${RESET}`);
|
||||
log('');
|
||||
log(` ${CYAN}4.${RESET} Start coding:`);
|
||||
log(` ${DIM}tfcode${RESET}`);
|
||||
log('');
|
||||
log(`${DIM}Documentation: https://toothfairyai.com/developers/tfcode${RESET}`);
|
||||
log('');
|
||||
|
||||
log("")
|
||||
log(`${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`)
|
||||
log(`${GREEN}✓ tfcode installed successfully!${RESET}`)
|
||||
log(`${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}`)
|
||||
log("")
|
||||
log(`${BOLD}Quick Start:${RESET}`)
|
||||
log("")
|
||||
log(` ${CYAN}1.${RESET} Set your ToothFairyAI credentials:`)
|
||||
log(` ${DIM}export TF_WORKSPACE_ID="your-workspace-id"${RESET}`)
|
||||
log(` ${DIM}export TF_API_KEY="your-api-key"${RESET}`)
|
||||
log("")
|
||||
log(` ${CYAN}2.${RESET} Validate your credentials:`)
|
||||
log(` ${DIM}tfcode validate${RESET}`)
|
||||
log("")
|
||||
log(` ${CYAN}3.${RESET} Sync tools from your workspace:`)
|
||||
log(` ${DIM}tfcode sync${RESET}`)
|
||||
log("")
|
||||
log(` ${CYAN}4.${RESET} Start coding:`)
|
||||
log(` ${DIM}tfcode${RESET}`)
|
||||
log("")
|
||||
log(`${DIM}Documentation: https://toothfairyai.com/developers/tfcode${RESET}`)
|
||||
log("")
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
logError(`Installation failed: ${err.message}`);
|
||||
process.exit(1);
|
||||
});
|
||||
logError(`Installation failed: ${err.message}`)
|
||||
process.exit(1)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user