@orchard9ai/create-vite-vike-tauri
Version:
Create Vite + Vike + Tauri applications with Orchard9 design system
743 lines (737 loc) • 26.2 kB
JavaScript
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