one-file-cli
Version:
Run shadcn/ui React components instantly with zero config - perfect for quick prototypes
866 lines (726 loc) โข 27.5 kB
JavaScript
// one-file.js - Two-command system
// Usage:
// node one-file.js init (install dependencies once)
// node one-file.js main.tsx (run instantly)
const fs = require('fs');
const path = require('path');
const { spawn } = require('child_process');
const os = require('os');
const homeDir = os.homedir();
const templateDir = path.join(homeDir, '.one-file-template');
const sourceTemplateDir = path.join(__dirname, 'template');
function detectPackageManager(dir) {
// Check for lock files in priority order
if (fs.existsSync(path.join(dir, 'pnpm-lock.yaml'))) {
return 'pnpm';
}
if (fs.existsSync(path.join(dir, 'yarn.lock'))) {
return 'yarn';
}
if (fs.existsSync(path.join(dir, 'package-lock.json'))) {
return 'npm';
}
// Fallback to environment check
return process.env.npm_execpath && process.env.npm_execpath.includes('pnpm') ? 'pnpm' : 'npm';
}
// Detect package manager based on user's directory first
const packageManager = detectPackageManager(process.cwd());
function copyRecursive(src, dest) {
const stats = fs.statSync(src);
if (path.basename(src) === 'node_modules') {
return;
}
if (stats.isDirectory()) {
if (!fs.existsSync(dest)) {
fs.mkdirSync(dest, { recursive: true });
}
const items = fs.readdirSync(src);
items.forEach(item => {
copyRecursive(path.join(src, item), path.join(dest, item));
});
} else {
fs.copyFileSync(src, dest);
}
}
function createTemplate() {
console.log('๐ Creating template directory...');
if (!fs.existsSync(sourceTemplateDir)) {
console.error('โ Template directory not found. Please ensure ./template/ exists.');
process.exit(1);
}
copyRecursive(sourceTemplateDir, templateDir);
console.log(`โ
Template created at: ${templateDir}`);
}
async function installDeps() {
return new Promise((resolve, reject) => {
console.log('๐ฆ Installing dependencies (this may take a minute)...');
const install = spawn(packageManager, ['install'], {
cwd: templateDir,
stdio: 'inherit',
shell: true
});
install.on('close', (code) => {
if (code === 0) {
console.log('โ
Dependencies installed! You can now run files instantly.');
resolve();
} else {
reject(new Error(`${packageManager} install failed with code ${code}`));
}
});
install.on('error', reject);
});
}
function updateMainTsx(userFilePath) {
const mainTsxPath = path.join(templateDir, 'src', 'main.tsx');
const absoluteUserPath = path.resolve(userFilePath);
const relativePath = path.relative(path.join(templateDir, 'src'), absoluteUserPath).replace(/\\/g, '/');
// Read the original main.tsx
if (!fs.existsSync(mainTsxPath)) {
throw new Error('Template main.tsx not found');
}
let mainTsxContent = fs.readFileSync(mainTsxPath, 'utf8');
// Replace the App import with the user's file
const updatedContent = mainTsxContent.replace(
/import App from ["']\.\/App\.tsx?["'];?/,
`import App from '${relativePath}';`
);
fs.writeFileSync(mainTsxPath, updatedContent);
return mainTsxPath;
}
function updateViteConfig() {
const viteConfigPath = path.join(templateDir, 'vite.config.ts');
const vitestConfigPath = path.join(templateDir, 'vitest.config.ts');
if (!fs.existsSync(viteConfigPath)) {
console.warn('โ ๏ธ Vite config not found');
return null;
}
// Get the current working directory where the script is executed
const currentDir = process.cwd();
// Update Vite config
let viteConfigContent = `
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tailwindcss from '@tailwindcss/vite';
import path from 'path';
export default defineConfig({
plugins: [react(), tailwindcss()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
"~": "${currentDir.replace(/\\/g, '/')}"
},
modules: [
path.resolve(__dirname, "node_modules"),
"${currentDir.replace(/\\/g, '/')}/node_modules",
"${path.join(templateDir, 'node_modules').replace(/\\/g, '/')}"
]
}
});
`;
fs.writeFileSync(viteConfigPath, viteConfigContent);
// Update Vitest config if it exists
if (fs.existsSync(vitestConfigPath)) {
let vitestConfigContent = `
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
import path from 'path';
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom',
globals: true,
setupFiles: './src/setup-tests.ts',
},
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
"~": "${currentDir.replace(/\\/g, '/')}"
},
modules: [
path.resolve(__dirname, "node_modules"),
"${currentDir.replace(/\\/g, '/')}/node_modules",
"${path.join(templateDir, 'node_modules').replace(/\\/g, '/')}"
]
},
});
`;
fs.writeFileSync(vitestConfigPath, vitestConfigContent);
}
return viteConfigPath;
}
function createUserDirLink(userDir, templateDir) {
const userDirLink = path.join(templateDir, '~');
if (fs.existsSync(userDirLink)) {
// Remove existing link
if (process.platform === 'win32') {
fs.rmSync(userDirLink, { recursive: true, force: true });
} else {
fs.unlinkSync(userDirLink);
}
}
try {
if (process.platform === 'win32') {
const { execSync } = require('child_process');
execSync(`mklink /J "${userDirLink}" "${userDir}"`, { stdio: 'ignore' });
} else {
fs.symlinkSync(userDir, userDirLink);
}
return userDirLink;
} catch (error) {
console.warn('โ ๏ธ Could not create user directory link:', error.message);
return null;
}
}
function createNodeModulesLink(userDir, templateDir) {
if (!fs.existsSync(path.join(userDir, 'package.json'))) {
console.log('๐ฆ No package.json found in user directory, using template dependencies only');
return null;
}
if (!fs.existsSync(path.join(userDir, 'node_modules'))) {
console.log('โ ๏ธ No node_modules found in user directory. Please run your package manager\'s install command first.');
}
return null;
}
function createLocalTsConfig(userDir, templateDir) {
const templateTsConfigPath = path.join(templateDir, 'tsconfig.json');
const localTsConfigPath = path.join(userDir, 'tsconfig.json');
const config = {
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": [
"ES2020",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "node",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"baseUrl": "..\\..\\..\\..\\..\\.one-file-template",
"paths": {
"@/*": ["src/*"],
"~/*": ["../one-file-cli/test-demo/*"],
"*": ["node_modules/*"]
}
},
"include": [
"*.tsx",
"*.ts",
"..\\..\\..\\..\\..\\.one-file-template\\src"
]
};
fs.writeFileSync(localTsConfigPath, JSON.stringify(config, null, 2));
return localTsConfigPath;
}
async function installUserDependencies(userDir, templateDir) {
const userPackageJsonPath = path.join(userDir, 'package.json');
if (!fs.existsSync(userPackageJsonPath)) {
return null; // No user dependencies to install
}
try {
const userPackageJson = JSON.parse(fs.readFileSync(userPackageJsonPath, 'utf8'));
const templatePackageJson = JSON.parse(fs.readFileSync(path.join(templateDir, 'package.json'), 'utf8'));
// Get all dependencies that aren't already in the template
const newDeps = {};
['dependencies', 'devDependencies'].forEach(depType => {
if (userPackageJson[depType]) {
Object.entries(userPackageJson[depType]).forEach(([pkg, version]) => {
if (!templatePackageJson.dependencies?.[pkg] && !templatePackageJson.devDependencies?.[pkg]) {
newDeps[pkg] = version;
}
});
}
});
if (Object.keys(newDeps).length === 0) {
return null; // No new dependencies to install
}
// Create a temporary package.json with the new dependencies
const tempPackageJson = {
...templatePackageJson,
dependencies: {
...templatePackageJson.dependencies,
...newDeps
}
};
const tempPackageJsonPath = path.join(templateDir, 'package.json');
const originalPackageJson = fs.readFileSync(tempPackageJsonPath, 'utf8');
// Write temporary package.json with user dependencies
fs.writeFileSync(tempPackageJsonPath, JSON.stringify(tempPackageJson, null, 2));
console.log('๐ฆ Installing additional dependencies from user project...');
// Install the new dependencies
await new Promise((resolve, reject) => {
const install = spawn(packageManager, ['install'], {
cwd: templateDir,
stdio: 'inherit',
shell: true
});
install.on('close', (code) => {
if (code === 0) {
resolve();
} else {
reject(new Error(`Failed to install dependencies (code ${code})`));
}
});
install.on('error', reject);
});
return originalPackageJson; // Return original content for cleanup
} catch (error) {
console.error('โ ๏ธ Warning: Failed to process user dependencies:', error.message);
return null;
}
}
async function runFile(userFile) {
if (!fs.existsSync(templateDir)) {
console.error('โ Template not found. Please run: node one-file.js init');
process.exit(1);
}
if (!fs.existsSync(path.join(templateDir, 'node_modules'))) {
console.error('โ Dependencies not installed. Please run: node one-file.js init');
process.exit(1);
}
const absoluteUserFile = path.resolve(userFile);
const userDir = path.dirname(absoluteUserFile);
const mainTsxPath = path.join(templateDir, 'src', 'main.tsx');
const viteConfigPath = path.join(templateDir, 'vite.config.ts');
let originalMainTsx = null;
let originalViteConfig = null;
let localTsConfigPath = null;
let nodeModulesLinkPath = null;
let userDirLinkPath = null;
try {
// Backup original files
if (fs.existsSync(mainTsxPath)) {
originalMainTsx = fs.readFileSync(mainTsxPath, 'utf8');
}
if (fs.existsSync(viteConfigPath)) {
originalViteConfig = fs.readFileSync(viteConfigPath, 'utf8');
}
// Update main.tsx to import the user's file
updateMainTsx(absoluteUserFile);
console.log(`๐ Updated main.tsx to import ${userFile}`);
// Update Vite config to include ~ alias
updateViteConfig();
console.log('โ๏ธ Updated Vite config with ~ alias for user directory');
// Create user directory symlink as ~
userDirLinkPath = createUserDirLink(userDir, templateDir);
if (userDirLinkPath) {
console.log('๐ Created ~ symlink to user directory');
}
// Create TypeScript and node_modules support
localTsConfigPath = createLocalTsConfig(userDir, templateDir);
console.log('โ๏ธ Created local tsconfig.json with ~ support');
nodeModulesLinkPath = createNodeModulesLink(userDir, templateDir);
if (nodeModulesLinkPath) {
console.log('๐ Created node_modules link for type resolution');
}
console.log('๐ฅ Starting Vite with native hot reload...');
const vite = spawn(packageManager, ['run', 'dev'], {
cwd: templateDir,
stdio: 'inherit',
shell: true
});
const cleanup = () => {
console.log('\n๐ฅ Keeping files in place...');
vite.kill();
process.exit(0);
};
process.on('SIGINT', cleanup);
process.on('SIGTERM', cleanup);
vite.on('error', (error) => {
console.error('โ Failed to start dev server:', error.message);
cleanup();
});
} catch (error) {
console.error('โ Error:', error.message);
process.exit(1);
}
}
async function buildFile(userFile) {
if (!fs.existsSync(templateDir)) {
console.error('โ Template not found. Please run: node one-file.js init');
process.exit(1);
}
if (!fs.existsSync(path.join(templateDir, 'node_modules'))) {
console.error('โ Dependencies not installed. Please run: node one-file.js init');
process.exit(1);
}
const absoluteUserFile = path.resolve(userFile);
const userDir = path.dirname(absoluteUserFile);
const mainTsxPath = path.join(templateDir, 'src', 'main.tsx');
const viteConfigPath = path.join(templateDir, 'vite.config.ts');
const buildOutputDir = path.join(userDir, 'dist');
let originalMainTsx = null;
let originalViteConfig = null;
let localTsConfigPath = null;
let nodeModulesLinkPath = null;
let userDirLinkPath = null;
let originalPackageJson = null;
try {
// Backup original files
if (fs.existsSync(mainTsxPath)) {
originalMainTsx = fs.readFileSync(mainTsxPath, 'utf8');
}
if (fs.existsSync(viteConfigPath)) {
originalViteConfig = fs.readFileSync(viteConfigPath, 'utf8');
}
// Install user dependencies if any
originalPackageJson = await installUserDependencies(userDir, templateDir);
// Update main.tsx to import the user's file
updateMainTsx(absoluteUserFile);
console.log(`๐ Updated main.tsx to import ${userFile}`);
// Update Vite config to include ~ alias
updateViteConfig();
console.log('โ๏ธ Updated Vite config with ~ alias for user directory');
// Create user directory symlink as ~
userDirLinkPath = createUserDirLink(userDir, templateDir);
if (userDirLinkPath) {
console.log('๐ Created ~ symlink to user directory');
}
// Create TypeScript and node_modules support
localTsConfigPath = createLocalTsConfig(userDir, templateDir);
console.log('โ๏ธ Created local tsconfig.json with ~ support');
nodeModulesLinkPath = createNodeModulesLink(userDir, templateDir);
if (nodeModulesLinkPath) {
console.log('๐ Created merged package.json');
// Install dependencies from merged package.json
console.log('๐ฆ Installing all dependencies...');
await new Promise((resolve, reject) => {
const install = spawn(packageManager, ['install'], {
cwd: templateDir,
stdio: 'inherit',
shell: true
});
install.on('close', (code) => {
if (code === 0) {
console.log('โ
Successfully installed all dependencies');
resolve();
} else {
reject(new Error(`Failed to install dependencies (code ${code})`));
}
});
install.on('error', reject);
});
}
console.log('๐๏ธ Building production files...');
// Run the build command
const build = spawn(packageManager, ['run', 'build'], {
cwd: templateDir,
stdio: 'inherit',
shell: true
});
await new Promise((resolve, reject) => {
build.on('close', (code) => {
if (code === 0) {
resolve();
} else {
reject(new Error(`Build failed with code ${code}`));
}
});
build.on('error', reject);
});
// Copy build output to user directory
const templateDistDir = path.join(templateDir, 'dist');
if (fs.existsSync(templateDistDir)) {
if (fs.existsSync(buildOutputDir)) {
fs.rmSync(buildOutputDir, { recursive: true, force: true });
}
copyRecursive(templateDistDir, buildOutputDir);
console.log(`โ
Build complete! Output saved to: ${buildOutputDir}`);
} else {
throw new Error('Build output not found');
}
} catch (error) {
console.error('โ Build failed:', error.message);
process.exit(1);
} finally {
// Cleanup
console.log('๐งน Cleaning up...');
if (originalMainTsx !== null) {
fs.writeFileSync(mainTsxPath, originalMainTsx);
}
if (originalViteConfig !== null) {
fs.writeFileSync(viteConfigPath, originalViteConfig);
}
if (originalPackageJson !== null) {
fs.writeFileSync(path.join(templateDir, 'package.json'), originalPackageJson);
// Reinstall original dependencies
try {
await new Promise((resolve, reject) => {
const install = spawn(packageManager, ['install'], {
cwd: templateDir,
stdio: 'inherit',
shell: true
});
install.on('close', (code) => code === 0 ? resolve() : reject());
install.on('error', reject);
});
} catch (error) {
console.warn('โ ๏ธ Warning: Failed to restore original dependencies');
}
}
if (localTsConfigPath && fs.existsSync(localTsConfigPath)) {
fs.unlinkSync(localTsConfigPath);
}
if (nodeModulesLinkPath && fs.existsSync(nodeModulesLinkPath)) {
// Remove merged package.json
fs.unlinkSync(nodeModulesLinkPath);
// Restore original package.json and reinstall original dependencies
if (fs.existsSync(path.join(templateDir, 'package.json.backup'))) {
fs.copyFileSync(
path.join(templateDir, 'package.json.backup'),
path.join(templateDir, 'package.json')
);
fs.unlinkSync(path.join(templateDir, 'package.json.backup'));
// Reinstall original dependencies
try {
await new Promise((resolve, reject) => {
const install = spawn(packageManager, ['install'], {
cwd: templateDir,
stdio: 'inherit',
shell: true
});
install.on('close', (code) => code === 0 ? resolve() : reject());
install.on('error', reject);
});
} catch (error) {
console.warn('โ ๏ธ Warning: Failed to restore original dependencies');
}
}
}
if (userDirLinkPath && fs.existsSync(userDirLinkPath)) {
if (process.platform === 'win32') {
fs.rmSync(userDirLinkPath, { recursive: true, force: true });
} else {
fs.unlinkSync(userDirLinkPath);
}
}
}
}
async function initCommand() {
try {
if (fs.existsSync(templateDir)) {
console.log('๐งน Removing existing template...');
fs.rmSync(templateDir, { recursive: true, force: true });
}
createTemplate();
await installDeps();
console.log('\n๐ Setup complete! Now you can run:');
console.log(` node one-file.js main.tsx`);
console.log('\n๐ก Import aliases:');
console.log(' @ - template components (e.g., @/components/ui/button)');
console.log(' ~ - your local files (e.g., ~/components/hello)');
} catch (error) {
console.error('โ Init failed:', error.message);
process.exit(1);
}
}
async function main() {
const command = process.argv[2];
if (command === '--version' || command === '-v') {
const packageJson = require('./package.json');
console.log(packageJson.version);
process.exit(0);
}
if (!command) {
console.log(`
๐ฏ one-file - Run .tsx files with zero config
Commands:
node one-file.js init Install dependencies (run once)
node one-file.js <file.tsx> Run a .tsx file instantly
node one-file.js build <file.tsx> Build for production
node one-file.js test <file.tsx> Run tests
node one-file.js --version Show version number
Workflow:
1. node one-file.js init # Install deps once (~30 seconds)
2. node one-file.js main.tsx # Run instantly (< 3 seconds)
3. node one-file.js build main.tsx # Build for production
4. node one-file.js test main.tsx # Run tests
Features:
โ
Zero config React + TypeScript + Tailwind CSS v4
โ
Native Vite hot reload (no file copying!)
โ
Built-in UI components (Button, Input)
โ
Auto cleanup when done
โ
Direct file import - Vite handles everything
โ
Dual import aliases: @ (template) and ~ (local)
โ
Production build support with static file output
โ
Test support with Vitest
`);
process.exit(1);
}
if (command === 'init') {
await initCommand();
} else if (command === 'build') {
const file = process.argv[3];
if (!file) {
console.error('โ Please specify a file to build: node one-file.js build <file.tsx>');
process.exit(1);
}
await buildFile(file);
} else if (command === 'test') {
const file = process.argv[3];
if (!file) {
console.error('โ Please specify a file to test: node one-file.js test <file.tsx>');
process.exit(1);
}
await testFile(file);
} else {
await runFile(command);
}
}
async function testFile(userFile) {
if (!fs.existsSync(templateDir)) {
console.error('โ Template not found. Please run: node one-file.js init');
process.exit(1);
}
if (!fs.existsSync(path.join(templateDir, 'node_modules'))) {
console.error('โ Dependencies not installed. Please run: node one-file.js init');
process.exit(1);
}
const absoluteUserFile = path.resolve(userFile);
const userDir = path.dirname(absoluteUserFile);
const mainTsxPath = path.join(templateDir, 'src', 'main.tsx');
const viteConfigPath = path.join(templateDir, 'vite.config.ts');
const vitestConfigPath = path.join(templateDir, 'vitest.config.ts');
const setupFilePath = path.join(templateDir, 'src', 'setup-tests.ts');
const templateTestFile = path.join(templateDir, 'src', 'user-test', path.basename(userFile));
let originalMainTsx = null;
let originalViteConfig = null;
let originalVitestConfig = null;
let localTsConfigPath = null;
let nodeModulesLinkPath = null;
let userDirLinkPath = null;
try {
// Copy test file to template directory
if (!fs.existsSync(absoluteUserFile)) {
console.error(`โ Test file not found: ${absoluteUserFile}`);
process.exit(1);
}
// Create user-test directory if it doesn't exist
const userTestDir = path.dirname(templateTestFile);
if (!fs.existsSync(userTestDir)) {
fs.mkdirSync(userTestDir, { recursive: true });
}
fs.copyFileSync(absoluteUserFile, templateTestFile);
console.log('๐ Copied test file to template directory');
// Backup original files
if (fs.existsSync(mainTsxPath)) {
originalMainTsx = fs.readFileSync(mainTsxPath, 'utf8');
}
if (fs.existsSync(viteConfigPath)) {
originalViteConfig = fs.readFileSync(viteConfigPath, 'utf8');
}
if (fs.existsSync(vitestConfigPath)) {
originalVitestConfig = fs.readFileSync(vitestConfigPath, 'utf8');
// Update Vitest config to include the user's test file
const updatedVitestConfig = originalVitestConfig.replace(
/test:\s*{[^}]*}/,
`test: {
environment: 'jsdom',
globals: true,
setupFiles: '${setupFilePath.replace(/\\/g, '/')}',
include: ['${templateTestFile.replace(/\\/g, '/')}'],
root: '${templateDir.replace(/\\/g, '/')}'
}`
);
fs.writeFileSync(vitestConfigPath, updatedVitestConfig);
console.log('โ๏ธ Updated Vitest config to run user test file');
}
// Create merged package.json with user dependencies
nodeModulesLinkPath = await createNodeModulesLink(userDir, templateDir);
if (nodeModulesLinkPath) {
console.log('๐ฆ Created merged package.json with template and user dependencies');
// Install all dependencies
console.log('๐ฆ Installing all dependencies...');
await new Promise((resolve, reject) => {
const install = spawn(packageManager, ['install'], {
cwd: templateDir,
stdio: 'inherit',
shell: true
});
install.on('close', (code) => {
if (code === 0) {
console.log('โ
Successfully installed all dependencies');
resolve();
} else {
reject(new Error(`Failed to install dependencies (code ${code})`));
}
});
install.on('error', reject);
});
}
// Update Vite config to use current directory
updateViteConfig();
console.log('โ๏ธ Updated Vite config with current directory alias');
// Create TypeScript config with current directory support
localTsConfigPath = createLocalTsConfig(userDir, templateDir);
console.log('โ๏ธ Created local tsconfig.json with path support');
// Create user directory symlink as ~
userDirLinkPath = createUserDirLink(userDir, templateDir);
if (userDirLinkPath) {
console.log('๐ Created ~ symlink to user directory');
}
console.log('๐งช Running tests...');
// Run the test command
const test = spawn(packageManager, ['run', 'test'], {
cwd: templateDir,
stdio: 'inherit',
shell: true,
env: {
...process.env,
NODE_PATH: process.cwd()
}
});
await new Promise((resolve, reject) => {
test.on('close', (code) => {
if (code === 0) {
resolve();
} else {
reject(new Error(`Tests failed with code ${code}`));
}
});
test.on('error', reject);
});
console.log('โ
Tests completed successfully!');
} catch (error) {
console.error('โ Tests failed:', error.message);
process.exit(1);
} finally {
// Cleanup
console.log('๐งน Cleaning up...');
// Remove the copied test file and its directory
if (fs.existsSync(templateTestFile)) {
fs.unlinkSync(templateTestFile);
const userTestDir = path.dirname(templateTestFile);
if (fs.existsSync(userTestDir)) {
fs.rmdirSync(userTestDir, { recursive: true });
}
}
if (originalMainTsx !== null) {
fs.writeFileSync(mainTsxPath, originalMainTsx);
}
if (originalViteConfig !== null) {
fs.writeFileSync(viteConfigPath, originalViteConfig);
}
if (originalVitestConfig !== null) {
fs.writeFileSync(vitestConfigPath, originalVitestConfig);
}
if (localTsConfigPath && fs.existsSync(localTsConfigPath)) {
fs.unlinkSync(localTsConfigPath);
}
if (userDirLinkPath && fs.existsSync(userDirLinkPath)) {
if (process.platform === 'win32') {
fs.rmSync(userDirLinkPath, { recursive: true, force: true });
} else {
fs.unlinkSync(userDirLinkPath);
}
}
}
}
main();