UNPKG

@orchard9ai/create-vite-vike-tauri

Version:
743 lines (737 loc) 26.2 kB
var __defProp = Object.defineProperty; var __getOwnPropNames = Object.getOwnPropertyNames; var __esm = (fn, res) => function __init() { return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; }; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; // ../../node_modules/.pnpm/tsup@8.5.0_jiti@2.4.2_postcss@8.5.3_typescript@5.8.3_yaml@2.8.0/node_modules/tsup/assets/esm_shims.js import path from "path"; import { fileURLToPath } from "url"; var init_esm_shims = __esm({ "../../node_modules/.pnpm/tsup@8.5.0_jiti@2.4.2_postcss@8.5.3_typescript@5.8.3_yaml@2.8.0/node_modules/tsup/assets/esm_shims.js"() { "use strict"; } }); // src/utils/TemplateEngine.ts import path3 from "path"; import fs2 from "fs-extra"; import { fileURLToPath as fileURLToPath2 } from "url"; var TemplateEngine; var init_TemplateEngine = __esm({ "src/utils/TemplateEngine.ts"() { "use strict"; init_esm_shims(); TemplateEngine = class { constructor(context) { const __dirname2 = path3.dirname(fileURLToPath2(import.meta.url)); this.templatesDir = __dirname2.includes("/dist") ? path3.join(__dirname2, "../templates") : path3.join(__dirname2, "../../templates"); this.context = context; } /** * Process a template file and substitute variables */ processTemplate(templateContent) { let processed = templateContent; Object.entries(this.context).forEach(([key, value]) => { const placeholder = `{{${key}}}`; processed = processed.replace(new RegExp(placeholder, "g"), value); }); return processed; } /** * Copy base template files to target directory */ async copyBaseTemplate(targetDir) { const baseTemplateDir = path3.join(this.templatesDir, "base"); await this.copyTemplateDirectory(baseTemplateDir, targetDir); } /** * Copy Vike template files to target directory */ async copyVikeTemplate(targetDir) { const vikeTemplateDir = path3.join(this.templatesDir, "vike"); await this.copyTemplateDirectory(vikeTemplateDir, targetDir); await this.mergePackageJsonAdditions(targetDir, "vike"); } /** * Copy mobile template files to target directory */ async copyMobileTemplate(targetDir) { const mobileTemplateDir = path3.join(this.templatesDir, "mobile"); if (await fs2.pathExists(mobileTemplateDir)) { await this.copyTemplateDirectory(mobileTemplateDir, targetDir); await this.mergePackageJsonAdditions(targetDir, "mobile"); } } /** * Copy notification template files to target directory */ async copyNotificationTemplate(targetDir) { const notificationTemplateDir = path3.join(this.templatesDir, "notifications"); if (await fs2.pathExists(notificationTemplateDir)) { await this.copyTemplateDirectory(notificationTemplateDir, targetDir); await this.addNotificationDependencies(targetDir); await this.addNotificationCapabilities(targetDir); } } /** * Copy a template directory recursively, processing templates */ async copyTemplateDirectory(sourceDir, targetDir) { if (!await fs2.pathExists(sourceDir)) { return; } const items = await fs2.readdir(sourceDir, { withFileTypes: true }); for (const item of items) { const sourcePath = path3.join(sourceDir, item.name); const targetPath = path3.join(targetDir, this.getTargetFilename(item.name)); if (item.isDirectory()) { await fs2.ensureDir(targetPath); await this.copyTemplateDirectory(sourcePath, targetPath); } else { await fs2.ensureDir(path3.dirname(targetPath)); if (this.isTemplateFile(item.name)) { const templateContent = await fs2.readFile(sourcePath, "utf-8"); const processedContent = this.processTemplate(templateContent); await fs2.writeFile(targetPath, processedContent); } else { await fs2.copy(sourcePath, targetPath); } } } } /** * Merge package.json additions for a specific template type */ async mergePackageJsonAdditions(targetDir, templateType) { const additionsPath = path3.join(this.templatesDir, templateType, "package.json.additions.json"); if (await fs2.pathExists(additionsPath)) { const packageJsonPath = path3.join(targetDir, "package.json"); const additions = await fs2.readJson(additionsPath); const existingPackageJson = await fs2.readJson(packageJsonPath); if (additions.scripts) { existingPackageJson.scripts = { ...existingPackageJson.scripts, ...additions.scripts }; } if (additions.dependencies) { existingPackageJson.dependencies = { ...existingPackageJson.dependencies, ...additions.dependencies }; } if (additions.devDependencies) { existingPackageJson.devDependencies = { ...existingPackageJson.devDependencies, ...additions.devDependencies }; } await fs2.writeJson(packageJsonPath, existingPackageJson, { spaces: 2 }); } } /** * Add notification dependencies to package.json and Cargo.toml */ async addNotificationDependencies(targetDir) { const packageJsonPath = path3.join(targetDir, "package.json"); if (await fs2.pathExists(packageJsonPath)) { const packageJson = await fs2.readJson(packageJsonPath); if (!packageJson.dependencies) { packageJson.dependencies = {}; } packageJson.dependencies["@tauri-apps/plugin-notification"] = "^2.0.0"; await fs2.writeJson(packageJsonPath, packageJson, { spaces: 2 }); } const cargoTomlPath = path3.join(targetDir, "src-tauri", "Cargo.toml"); if (await fs2.pathExists(cargoTomlPath)) { let cargoContent = await fs2.readFile(cargoTomlPath, "utf-8"); if (!cargoContent.includes("tauri-plugin-notification")) { const dependenciesMatch = cargoContent.match(/\[dependencies\]/); if (dependenciesMatch) { const insertIndex = dependenciesMatch.index + dependenciesMatch[0].length; const beforeDeps = cargoContent.slice(0, insertIndex); const afterDeps = cargoContent.slice(insertIndex); cargoContent = beforeDeps + '\ntauri-plugin-notification = "2"' + afterDeps; await fs2.writeFile(cargoTomlPath, cargoContent); } } } const libRsPath = path3.join(targetDir, "src-tauri", "src", "lib.rs"); if (await fs2.pathExists(libRsPath)) { let libContent = await fs2.readFile(libRsPath, "utf-8"); if (!libContent.includes("tauri_plugin_notification::init()")) { libContent = libContent.replace( /\.plugin\(tauri_plugin_os::init\(\)\)/g, ".plugin(tauri_plugin_os::init())\n .plugin(tauri_plugin_notification::init())" ); await fs2.writeFile(libRsPath, libContent); } } } /** * Add notification capabilities by overwriting existing capabilities files */ async addNotificationCapabilities(targetDir) { const capabilitiesDir = path3.join(targetDir, "src-tauri", "capabilities"); const notificationCapabilitiesDir = path3.join( this.templatesDir, "notifications", "capabilities" ); if (await fs2.pathExists(notificationCapabilitiesDir)) { const files = await fs2.readdir(notificationCapabilitiesDir); for (const file of files) { const sourcePath = path3.join(notificationCapabilitiesDir, file); const targetPath = path3.join(capabilitiesDir, file); await fs2.copy(sourcePath, targetPath); } } } /** * Check if a file is a template file (ends with .template) */ isTemplateFile(filename) { return filename.endsWith(".template"); } /** * Get the target filename by removing .template extension */ getTargetFilename(filename) { if (this.isTemplateFile(filename)) { return filename.replace(/\.template$/, ""); } return filename; } /** * Get list of files that would be created (for dry run) */ async getFilesToCreate(options) { const files = []; await this.collectFiles(path3.join(this.templatesDir, "base"), "", files); if (options.vike) { await this.collectFiles(path3.join(this.templatesDir, "vike"), "", files); } if (options.mobile) { const mobileDir = path3.join(this.templatesDir, "mobile"); if (await fs2.pathExists(mobileDir)) { await this.collectFiles(mobileDir, "", files); } } if (options.notifications) { const notificationDir = path3.join(this.templatesDir, "notifications"); if (await fs2.pathExists(notificationDir)) { await this.collectFiles(notificationDir, "", files); } } return files.map((file) => this.getTargetFilename(file)); } /** * Recursively collect all files in a directory */ async collectFiles(dir, relativePath, files) { if (!await fs2.pathExists(dir)) { return; } const items = await fs2.readdir(dir, { withFileTypes: true }); for (const item of items) { const itemPath = path3.join(relativePath, item.name); if (item.isDirectory()) { await this.collectFiles(path3.join(dir, item.name), itemPath, files); } else if (!item.name.endsWith(".additions.json")) { files.push(itemPath); } } } }; } }); // src/generators/ProjectGenerator.ts var ProjectGenerator_exports = {}; __export(ProjectGenerator_exports, { ProjectGenerator: () => ProjectGenerator }); import path4 from "path"; import fs3 from "fs-extra"; import { spawn as spawn2 } from "child_process"; var ProjectGenerator; var init_ProjectGenerator = __esm({ "src/generators/ProjectGenerator.ts"() { "use strict"; init_esm_shims(); init_TemplateEngine(); ProjectGenerator = class { constructor(options) { this.options = options; const context = { PROJECT_NAME: options.name, PROJECT_IDENTIFIER: options.name.replace(/[^a-zA-Z0-9]/g, ""), PACKAGE_MANAGER: options.packageManager || "pnpm", BEFORE_DEV_COMMAND: options.vike ? "pnpm dev:vike" : "pnpm dev", BEFORE_BUILD_COMMAND: options.vike ? "pnpm build:vike" : "pnpm build", PROJECT_TITLE: options.seo?.title || "", PROJECT_DESCRIPTION: options.seo?.description || "", PROJECT_KEYWORDS: options.seo?.keywords || "", PROJECT_AUTHOR: options.seo?.author || "" }; this.templateEngine = new TemplateEngine(context); } async generate() { try { const { targetDir, dryRun } = this.options; if (dryRun) { return this.dryRun(); } await fs3.ensureDir(targetDir); await this.templateEngine.copyBaseTemplate(targetDir); if (this.options.vike) { await this.templateEngine.copyVikeTemplate(targetDir); } if (this.options.mobile) { await this.templateEngine.copyMobileTemplate(targetDir); } if (this.options.notifications) { await this.templateEngine.copyNotificationTemplate(targetDir); } if (!this.options.skipInstall) { await this.installDependencies(); } if (this.options.mobile && !this.options.skipInstall) { await this.initializeMobilePlatforms(); } if (!this.options.skipGit) { await this.initializeGit(); } return { projectPath: targetDir, success: true, nextSteps: this.getNextSteps() }; } catch (error) { return { projectPath: this.options.targetDir, success: false, errors: [error instanceof Error ? error.message : "Unknown error"], nextSteps: [] }; } } async dryRun() { const files = await this.templateEngine.getFilesToCreate({ vike: Boolean(this.options.vike), mobile: Boolean(this.options.mobile), notifications: Boolean(this.options.notifications) }); console.info("\n\u{1F4C1} Files that would be created:"); files.forEach((file) => { console.info(` ${path4.join(this.options.targetDir, file)}`); }); return { projectPath: this.options.targetDir, success: true, nextSteps: this.getNextSteps() }; } async installDependencies() { try { await this.runPackageCommand("install", []); } catch (error) { throw new Error(`Failed to install dependencies: ${error}`); } } async runPackageCommand(command, packages, isDev = false) { const { packageManager = "pnpm", targetDir } = this.options; const args = [command]; if (isDev) { args.push("-D"); } args.push(...packages); if (packageManager === "pnpm") { args.push("--ignore-workspace"); } const child = spawn2(packageManager, args, { cwd: targetDir, stdio: "inherit", shell: true }); return new Promise((resolve, reject) => { child.on("close", (code) => { if (code === 0) { resolve(); } else { reject(new Error(`${packageManager} ${command} failed with code ${code}`)); } }); }); } async initializeMobilePlatforms() { console.info("\n\u{1F4F1} Initializing mobile platforms..."); if (process.platform === "darwin") { try { console.info(" Initializing iOS platform..."); await this.runTauriCommand(["ios", "init"]); console.info(" \u2705 iOS platform initialized"); } catch (error) { console.warn( ` \u26A0\uFE0F iOS initialization failed: ${error instanceof Error ? error.message : String(error)}` ); console.warn(" Make sure Xcode is installed and try running: pnpm tauri ios init"); } } if (process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT) { try { console.info(" Initializing Android platform..."); await this.runTauriCommand(["android", "init"]); console.info(" \u2705 Android platform initialized"); } catch (error) { console.warn( ` \u26A0\uFE0F Android initialization failed: ${error instanceof Error ? error.message : String(error)}` ); console.warn(" Make sure Android SDK is installed and ANDROID_HOME is set"); console.warn(" Then try running: pnpm tauri android init"); } } else { console.warn(" \u26A0\uFE0F Android SDK not found (ANDROID_HOME not set)"); console.warn(" Install Android Studio and set ANDROID_HOME environment variable"); console.warn(" See MOBILE_SETUP.md for detailed setup instructions"); console.warn(" Then run: pnpm tauri android init"); } } async runTauriCommand(args) { const { packageManager = "pnpm", targetDir } = this.options; const command = packageManager === "npm" ? "npx" : packageManager; const fullArgs = command === "npx" ? ["tauri", ...args] : ["tauri", ...args]; const child = spawn2(command, fullArgs, { cwd: targetDir, stdio: "inherit", shell: true }); return new Promise((resolve, reject) => { child.on("close", (code) => { if (code === 0) { resolve(); } else { reject(new Error(`tauri ${args.join(" ")} failed with code ${code}`)); } }); }); } async initializeGit() { try { const child = spawn2("git", ["init"], { cwd: this.options.targetDir, stdio: "inherit", shell: true }); return new Promise((resolve, reject) => { child.on("close", (code) => { if (code === 0) { resolve(); } else { reject(new Error(`git init failed with code ${code}`)); } }); }); } catch (error) { throw new Error(`Failed to initialize git: ${error}`); } } getNextSteps() { const { name, packageManager = "pnpm", vike, mobile, skipInstall } = this.options; const steps = [`cd ${name}`]; if (skipInstall) { steps.push(`${packageManager} install`); } if (mobile && skipInstall) { if (process.platform === "darwin") { steps.push(`${packageManager} tauri ios init # Initialize iOS platform`); } steps.push(`${packageManager} tauri android init # Initialize Android platform`); } if (vike) { steps.push(`${packageManager} run dev:vike # Start Vike dev server`); steps.push(`${packageManager} run tauri:dev # Start Tauri app`); } else { steps.push(`${packageManager} run tauri:dev # Start development`); } if (mobile) { steps.push(""); steps.push("\u{1F4F1} Mobile Development:"); if (process.platform === "darwin") { steps.push(`${packageManager} tauri ios dev # Run on iOS simulator/device`); } steps.push(`${packageManager} tauri android dev # Run on Android emulator/device`); } steps.push(""); steps.push(`${packageManager} run tauri:build # Build for production`); return steps; } }; } }); // src/index.ts init_esm_shims(); // src/utils/validators.ts init_esm_shims(); import path2 from "path"; import fs from "fs-extra"; import { spawn } from "child_process"; import validateNpmName from "validate-npm-package-name"; function validateProjectName(name) { const errors = []; const warnings = []; if (!name || typeof name !== "string") { return { valid: false, errors: ["Project name is required"], warnings: [] }; } if (name.trim() !== name) { errors.push("Project name cannot have leading or trailing whitespace"); } if (name.length === 0) { errors.push("Project name cannot be empty"); } if (name.length > 214) { errors.push("Project name cannot be longer than 214 characters"); } const reservedNames = [ "node_modules", "favicon.ico", "package.json", "package-lock.json", "pnpm-lock.yaml", "yarn.lock", "npm", "pnpm", "yarn", "node", "git", "src", "dist", "build", "public", "static", "assets", "components", "pages", "styles", "scripts", "config", "test", "tests", "__tests__", "spec", "specs", "__spec__", ".git", ".gitignore", ".env", ".env.local", ".env.development", ".env.production", "readme", "readme.md", "license", "license.md", "changelog", "changelog.md", "contributing", "contributing.md" ]; if (reservedNames.includes(name.toLowerCase())) { errors.push(`Project name "${name}" is reserved and cannot be used`); } const invalidChars = /[<>:"/\\|?*\x00-\x1f]/; if (invalidChars.test(name)) { errors.push("Project name contains invalid characters for file systems"); } const windowsReserved = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i; if (windowsReserved.test(name)) { errors.push(`Project name "${name}" is reserved on Windows systems`); } const npmValidation = validateNpmName(name); if (!npmValidation.validForNewPackages) { errors.push(...npmValidation.errors || []); warnings.push(...npmValidation.warnings || []); } return { valid: errors.length === 0, errors, warnings }; } async function validateTargetPath(targetPath) { const errors = []; const warnings = []; if (!targetPath || typeof targetPath !== "string") { return { valid: false, errors: ["Target path is required"], warnings: [] }; } try { const resolvedPath = path2.resolve(targetPath); if (resolvedPath.length > 250) { warnings.push("Target path is very long and may cause issues on some systems"); } const parentDir = path2.dirname(resolvedPath); try { const parentStats = await fs.stat(parentDir); if (!parentStats.isDirectory()) { errors.push("Parent directory is not a directory"); } else { try { await fs.access(parentDir, fs.constants.W_OK); } catch { errors.push("Parent directory is not writable"); } } } catch { errors.push("Parent directory does not exist or is not accessible"); } if (await fs.pathExists(resolvedPath)) { const stats = await fs.stat(resolvedPath); if (stats.isDirectory()) { const items = await fs.readdir(resolvedPath); if (items.length > 0) { warnings.push("Target directory exists and is not empty"); } } else { errors.push("Target path exists but is not a directory"); } } const pathComponents = resolvedPath.split(path2.sep); for (const component of pathComponents) { if (component.startsWith(".") && component !== "." && component !== "..") { warnings.push("Path contains hidden directories"); break; } } } catch (error) { errors.push( `Path validation failed: ${error instanceof Error ? error.message : "Unknown error"}` ); } return { valid: errors.length === 0, errors, warnings }; } async function checkCommand(command) { return new Promise((resolve) => { const child = spawn(command, ["--version"], { stdio: "ignore", shell: true }); child.on("close", (code) => { resolve(code === 0); }); child.on("error", () => { resolve(false); }); setTimeout(() => { child.kill(); resolve(false); }, 5e3); }); } async function verifySystemRequirements(packageManager = "pnpm") { const errors = []; const warnings = []; let node = false; let rust = false; let packageManagerAvailable = false; try { const nodeVersion = process.version; const versionParts = nodeVersion.split("."); if (versionParts.length > 0 && versionParts[0]) { const major = parseInt(versionParts[0].substring(1)); if (major >= 18) { node = true; if (major < 20) { warnings.push(`Node.js ${nodeVersion} works but Node.js 20+ is recommended`); } } else { errors.push(`Node.js 18+ required (found ${nodeVersion})`); } } else { errors.push("Could not parse Node.js version"); } } catch { errors.push("Could not determine Node.js version"); } try { rust = await checkCommand("rustc"); if (!rust) { errors.push("Rust is required for Tauri development. Install from https://rustup.rs/"); } else { const cargoAvailable = await checkCommand("cargo"); if (!cargoAvailable) { warnings.push("Cargo not found, but Rust is available"); } } } catch { errors.push("Could not check Rust installation"); } try { packageManagerAvailable = await checkCommand(packageManager); if (!packageManagerAvailable) { errors.push(`${packageManager} is not installed or not available in PATH`); if (packageManager === "pnpm") { warnings.push("You can install pnpm with: npm install -g pnpm"); } else if (packageManager === "yarn") { warnings.push("You can install yarn with: npm install -g yarn"); } } } catch { errors.push(`Could not check ${packageManager} installation`); } if (process.platform === "linux") { warnings.push( "On Linux, you may need additional dependencies. See: https://tauri.app/v1/guides/getting-started/prerequisites#setting-up-linux" ); } return { node, rust, packageManager: packageManagerAvailable, errors, warnings }; } async function validateProjectOptions(options) { const errors = []; const warnings = []; const nameValidation = validateProjectName(options.name); errors.push(...nameValidation.errors); warnings.push(...nameValidation.warnings); const pathValidation = await validateTargetPath(options.targetDir); errors.push(...pathValidation.errors); warnings.push(...pathValidation.warnings); const systemValidation = await verifySystemRequirements(options.packageManager); errors.push(...systemValidation.errors); warnings.push(...systemValidation.warnings); return { valid: errors.length === 0, errors, warnings }; } // src/index.ts async function createApp(options) { const { ProjectGenerator: ProjectGenerator2 } = await Promise.resolve().then(() => (init_ProjectGenerator(), ProjectGenerator_exports)); const generator = new ProjectGenerator2(options); return await generator.generate(); } export { createApp, validateProjectName, validateProjectOptions, validateTargetPath, verifySystemRequirements }; //# sourceMappingURL=index.js.map