create-lego-one 2.0.19 → 2.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +48 -10
- package/dist/index.cjs.map +1 -1
- package/package.json +1 -1
- package/template/.cursor/rules/rules.mdc +3 -3
- package/template/CLAUDE.md +3 -3
- package/template/PROMPT.md +3 -3
- package/template/docs/framework/setup/02-development-workflow.md +10 -2
- package/template/docs/framework/setup/03-environment-setup.md +2 -2
- package/template/docs/framework/setup/04-kernel-architecture.md +1 -1
- package/template/docs/framework/setup/05-plugin-system.md +2 -2
- package/template/docs/framework/setup/README.md +4 -0
- package/template/host/package.json +4 -3
- package/template/host/scripts/generate-plugin-manifest.js +88 -0
- package/template/host/src/kernel/plugins/hooks.ts +165 -0
- package/template/host/src/kernel/plugins/plugin-manifest.ts +15 -0
- package/template/host/src/kernel/providers/PocketBaseProvider.tsx +9 -24
- package/template/host/src/layout/Sidebar.tsx +23 -4
- package/template/host/src/routes/page.tsx +32 -34
- package/template/package.json +2 -3
- package/template/packages/plugins/@lego/plugin-dashboard/package.json +1 -1
- package/template/packages/plugins/@lego/plugin-todo/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -405,7 +405,7 @@ async function updateProject(projectDir, options) {
|
|
|
405
405
|
}
|
|
406
406
|
}
|
|
407
407
|
} else if (item.strategy === "merge-gitignore") {
|
|
408
|
-
const sourceGitignorePath = await import_fs_extra.default.pathExists(sourcePath) ? sourcePath : await import_fs_extra.default.pathExists(sourcePath
|
|
408
|
+
const sourceGitignorePath = await import_fs_extra.default.pathExists(sourcePath + ".template") ? sourcePath + ".template" : await import_fs_extra.default.pathExists(sourcePath) ? sourcePath : null;
|
|
409
409
|
if (sourceGitignorePath) {
|
|
410
410
|
if (targetExists) {
|
|
411
411
|
await mergeGitignoreFile(sourceGitignorePath, targetPath, updates, options.dryRun, item.target);
|
|
@@ -415,6 +415,8 @@ async function updateProject(projectDir, options) {
|
|
|
415
415
|
await import_fs_extra.default.copy(sourceGitignorePath, targetPath);
|
|
416
416
|
}
|
|
417
417
|
}
|
|
418
|
+
} else {
|
|
419
|
+
console.warn(kleur.yellow(` \u26A0 Skipping ${item.target} (source not found)`));
|
|
418
420
|
}
|
|
419
421
|
} else if (item.strategy === "add-if-missing") {
|
|
420
422
|
if (!targetExists) {
|
|
@@ -477,9 +479,21 @@ async function mergeDirectory(sourceDir, targetDir, updates, dryRun = false) {
|
|
|
477
479
|
await mergeDirectory(sourcePath, targetPath, updates, dryRun);
|
|
478
480
|
} else {
|
|
479
481
|
const targetExists = await import_fs_extra.default.pathExists(targetPath);
|
|
480
|
-
if (entry.name === ".gitignore"
|
|
481
|
-
const
|
|
482
|
-
|
|
482
|
+
if (entry.name === ".gitignore" || entry.name === ".gitignore.template") {
|
|
483
|
+
const gitignoreTargetPath = import_path.default.join(targetDir, ".gitignore");
|
|
484
|
+
const gitignoreTargetExists = await import_fs_extra.default.pathExists(gitignoreTargetPath);
|
|
485
|
+
const actualSourcePath = entry.name === ".gitignore.template" ? sourcePath : sourcePath;
|
|
486
|
+
if (gitignoreTargetExists) {
|
|
487
|
+
const relativePath = import_path.default.relative(targetDir, gitignoreTargetPath).replace(/\\/g, "/");
|
|
488
|
+
await mergeGitignoreFile(actualSourcePath, gitignoreTargetPath, updates, dryRun, relativePath);
|
|
489
|
+
} else {
|
|
490
|
+
const relativePath = import_path.default.relative(targetDir, gitignoreTargetPath);
|
|
491
|
+
updates.push({ file: relativePath, action: "created" });
|
|
492
|
+
if (!dryRun) {
|
|
493
|
+
await import_fs_extra.default.copy(actualSourcePath, gitignoreTargetPath);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
continue;
|
|
483
497
|
} else if (targetExists) {
|
|
484
498
|
const sourceContent = await import_fs_extra.default.readFile(sourcePath, "utf-8");
|
|
485
499
|
const targetContent = await import_fs_extra.default.readFile(targetPath, "utf-8");
|
|
@@ -520,9 +534,21 @@ async function mergeHostDirectory(sourceDir, targetDir, updates, dryRun = false,
|
|
|
520
534
|
await mergeHostDirectory(sourcePath, targetPath, updates, dryRun, hostRootDir);
|
|
521
535
|
} else {
|
|
522
536
|
const targetExists = await import_fs_extra.default.pathExists(targetPath);
|
|
523
|
-
if (entry.name === ".gitignore"
|
|
524
|
-
const
|
|
525
|
-
|
|
537
|
+
if (entry.name === ".gitignore" || entry.name === ".gitignore.template") {
|
|
538
|
+
const gitignoreTargetPath = import_path.default.join(targetDir, ".gitignore");
|
|
539
|
+
const gitignoreTargetExists = await import_fs_extra.default.pathExists(gitignoreTargetPath);
|
|
540
|
+
const actualSourcePath = entry.name === ".gitignore.template" ? sourcePath : sourcePath;
|
|
541
|
+
if (gitignoreTargetExists) {
|
|
542
|
+
const relativeTargetPath = import_path.default.relative(targetDir, gitignoreTargetPath).replace(/\\/g, "/");
|
|
543
|
+
await mergeGitignoreFile(actualSourcePath, gitignoreTargetPath, updates, dryRun, relativeTargetPath);
|
|
544
|
+
} else {
|
|
545
|
+
const relativeTargetPath = import_path.default.relative(targetDir, gitignoreTargetPath).replace(/\\/g, "/");
|
|
546
|
+
updates.push({ file: relativeTargetPath, action: "created" });
|
|
547
|
+
if (!dryRun) {
|
|
548
|
+
await import_fs_extra.default.copy(actualSourcePath, gitignoreTargetPath);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
continue;
|
|
526
552
|
} else if (targetExists) {
|
|
527
553
|
const relativeTargetPath = import_path.default.relative(targetDir, targetPath).replace(/\\/g, "/");
|
|
528
554
|
const sourceContent = await import_fs_extra.default.readFile(sourcePath, "utf-8");
|
|
@@ -562,9 +588,21 @@ async function mergePocketBaseDirectory(sourceDir, targetDir, updates, dryRun =
|
|
|
562
588
|
await mergePocketBaseDirectory(sourcePath, targetPath, updates, dryRun);
|
|
563
589
|
} else {
|
|
564
590
|
const targetExists = await import_fs_extra.default.pathExists(targetPath);
|
|
565
|
-
if (entry.name === ".gitignore"
|
|
566
|
-
const
|
|
567
|
-
|
|
591
|
+
if (entry.name === ".gitignore" || entry.name === ".gitignore.template") {
|
|
592
|
+
const gitignoreTargetPath = import_path.default.join(targetDir, ".gitignore");
|
|
593
|
+
const gitignoreTargetExists = await import_fs_extra.default.pathExists(gitignoreTargetPath);
|
|
594
|
+
const actualSourcePath = entry.name === ".gitignore.template" ? sourcePath : sourcePath;
|
|
595
|
+
if (gitignoreTargetExists) {
|
|
596
|
+
const relativePath = import_path.default.relative(targetDir, gitignoreTargetPath).replace(/\\/g, "/");
|
|
597
|
+
await mergeGitignoreFile(actualSourcePath, gitignoreTargetPath, updates, dryRun, relativePath);
|
|
598
|
+
} else {
|
|
599
|
+
const relativePath = import_path.default.relative(targetDir, gitignoreTargetPath);
|
|
600
|
+
updates.push({ file: relativePath, action: "created" });
|
|
601
|
+
if (!dryRun) {
|
|
602
|
+
await import_fs_extra.default.copy(actualSourcePath, gitignoreTargetPath);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
continue;
|
|
568
606
|
} else if (!targetExists) {
|
|
569
607
|
const relativePath = import_path.default.relative(targetDir, targetPath);
|
|
570
608
|
updates.push({ file: relativePath, action: "created" });
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../node_modules/.pnpm/tsup@8.5.1_@swc+core@1.15.8_4981d554a2757e4ed25c3ad5a773b01f/node_modules/tsup/assets/cjs_shims.js","../src/index.ts"],"sourcesContent":["// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () => \n typeof document === \"undefined\" \n ? new URL(`file:${__filename}`).href \n : (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT') \n ? document.currentScript.src \n : new URL(\"main.js\", document.baseURI).href;\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n","#!/usr/bin/env node\n\n/**\n * create-lego-one CLI\n * Create a new Lego-One SaaS application from template\n */\n\nimport path from 'path';\nimport fs from 'fs';\nimport { fileURLToPath } from 'url';\nimport { program } from 'commander';\nimport * as kleur from 'kleur';\nimport fsExtra from 'fs-extra';\nimport validateProjectName from 'validate-npm-package-name';\nimport prompts from 'prompts';\nimport { execSync } from 'child_process';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\ninterface CreateOptions {\n appName?: string;\n interactive: boolean;\n git: boolean;\n branch?: string;\n template?: string;\n description?: string;\n author?: string;\n}\n\ninterface ProjectConfig {\n appName: string;\n description: string;\n author: string;\n git: boolean;\n}\n\n// ASCII art banner\nconst banner = `\n${kleur.cyan('┌─────────────────────────────────────────────────────────┐')}\n${kleur.cyan('│')} ${kleur.bold().white('Lego-One')} SaaS Boilerplate ${kleur.cyan('│')}\n${kleur.cyan('│')} ${kleur.gray('Microkernel architecture with Modern.js + Garfish')} ${kleur.cyan('│')}\n${kleur.cyan('└─────────────────────────────────────────────────────────┘')}\n`;\n\nasync function init() {\n console.log(banner);\n\n const version = getVersion();\n console.log(kleur.gray(`create-lego-one v${version}\\n`));\n\n program\n .name('create-lego-one')\n .description('Create a new Lego-One SaaS application')\n .version(version)\n .argument('[app-name]', 'Name of your application')\n .option('-i, --interactive', 'Use interactive prompts (default when no app name provided)')\n .option('--no-interactive', 'Skip interactive prompts')\n .option('--git', 'Initialize git repository', true)\n .option('--no-git', 'Skip git initialization')\n .option('--description <description>', 'Project description')\n .option('--author <author>', 'Author name')\n .option('--branch <branch>', 'Use specific branch')\n .option('--template <template>', 'Use specific template')\n .action(async (appName: string | undefined, options) => {\n // Default to interactive mode if no app name provided\n const isInteractive = options.interactive !== false && (!appName || options.interactive === true);\n \n const opts: CreateOptions = {\n appName,\n interactive: isInteractive,\n git: options.git !== false,\n branch: options.branch,\n template: options.template,\n description: options.description,\n author: options.author,\n };\n await createApp(opts);\n });\n\n // Add update command\n program\n .command('update')\n .description('Update existing Lego-One project with latest framework changes')\n .option('--dry-run', 'Show what would be updated without making changes')\n .option('--force', 'Force update even if conflicts detected')\n .action(async (options) => {\n await updateProject(process.cwd(), options);\n });\n\n await program.parseAsync(process.argv);\n}\n\nfunction getVersion(): string {\n const pkgPath = path.join(__dirname, '../package.json');\n try {\n const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));\n return pkg.version || '1.0.0';\n } catch {\n return '1.0.0';\n }\n}\n\nasync function createApp(options: CreateOptions) {\n try {\n let projectConfig: ProjectConfig;\n\n // Interactive mode - collect all project information\n if (options.interactive) {\n const responses = await prompts([\n {\n type: 'text',\n name: 'appName',\n message: 'What is your app named?',\n initial: options.appName || 'my-saas-app',\n validate: (value: string) => {\n if (!value) return 'App name is required';\n const validation = validateProjectName(value);\n if (validation.validForNewPackages) return true;\n const errorMsg = validation.errors?.join(', ') || validation.warnings?.join(', ') || 'unknown error';\n return 'Invalid app name: ' + errorMsg;\n },\n },\n {\n type: 'text',\n name: 'description',\n message: 'Project description (optional)',\n initial: options.description || 'A SaaS application built with Lego-One',\n },\n {\n type: 'text',\n name: 'author',\n message: 'Author name (optional)',\n initial: options.author || '',\n },\n {\n type: 'confirm',\n name: 'git',\n message: 'Initialize git repository?',\n initial: options.git !== false,\n },\n ]);\n\n projectConfig = {\n appName: responses.appName || options.appName || 'my-saas-app',\n description: responses.description || options.description || 'A SaaS application built with Lego-One',\n author: responses.author || options.author || '',\n git: responses.git !== false,\n };\n } else {\n // Non-interactive mode - use provided values or defaults\n if (!options.appName) {\n console.error(kleur.red('Please specify an app name or use interactive mode (default)'));\n process.exit(1);\n }\n\n projectConfig = {\n appName: options.appName,\n description: options.description || 'A SaaS application built with Lego-One',\n author: options.author || '',\n git: options.git !== false,\n };\n }\n\n // Validate app name\n const validation = validateProjectName(projectConfig.appName);\n if (!validation.validForNewPackages) {\n const errorMsg = validation.errors?.join(', ') || validation.warnings?.join(', ') || 'unknown error';\n console.error(kleur.red(`Invalid app name: ${errorMsg}`));\n if (validation.warnings && validation.warnings.length > 0) {\n console.error(kleur.yellow(`Warnings: ${validation.warnings.join(', ')}`));\n }\n process.exit(1);\n }\n\n const appName = projectConfig.appName;\n\n const targetDir = path.resolve(process.cwd(), appName);\n\n // Check if directory exists\n if (await fsExtra.pathExists(targetDir)) {\n const files = await fsExtra.readdir(targetDir);\n if (files.length > 0) {\n console.error(kleur.red(`Directory \"${appName}\" already exists and is not empty`));\n process.exit(1);\n }\n }\n\n console.log(kleur.cyan('Creating your Lego-One application...'));\n\n // Create target directory\n await fsExtra.ensureDir(targetDir);\n\n // Copy template files\n console.log(kleur.gray('Copying template files...'));\n const templateDir = path.join(__dirname, '../template');\n if (await fsExtra.pathExists(templateDir)) {\n await fsExtra.copy(templateDir, targetDir);\n // Rename .gitignore.template files back to .gitignore (npm excludes .gitignore by default)\n await restoreGitignoreFiles(targetDir);\n } else {\n // Fallback: copy from root (development mode)\n const rootDir = path.join(__dirname, '../../..');\n await copyTemplateFiles(rootDir, targetDir);\n }\n\n // Update package.json with project configuration\n console.log(kleur.gray('Configuring project...'));\n await updatePackageJson(targetDir, projectConfig);\n\n // Update README.md with project-specific information\n await updateReadme(targetDir, projectConfig);\n\n // Ensure PocketBase directory structure exists\n await ensurePocketBaseStructure(targetDir);\n\n // Initialize git if requested\n if (projectConfig.git) {\n console.log(kleur.gray('Initializing git repository...'));\n try {\n execSync('git init', { cwd: targetDir });\n execSync('git add .', { cwd: targetDir });\n execSync('git commit -m \"Initial commit from Lego-One\"', {\n cwd: targetDir,\n });\n } catch (error) {\n console.warn(kleur.yellow('Could not initialize git repository'));\n }\n }\n\n console.log(kleur.green('Application created successfully!'));\n\n // Print success message\n console.log('\\n' + kleur.bold('Next steps:'));\n console.log(` ${kleur.cyan('cd')} ${appName}`);\n console.log(` ${kleur.cyan('pnpm install')}`);\n console.log(` ${kleur.cyan('pnpm run dev')}`);\n console.log('\\n' + kleur.gray('To start developing:'));\n console.log(` ${kleur.cyan('pnpm run dev')} - Start development server`);\n console.log(` ${kleur.cyan('pnpm run dev:all')} - Start all services`);\n console.log('\\n' + kleur.gray('To build for production:'));\n console.log(` ${kleur.cyan('pnpm run build')} - Build application`);\n console.log(` ${kleur.cyan('pnpm run start')} - Start production server`);\n console.log('\\n' + kleur.gray('Documentation:'));\n console.log(` ${kleur.cyan('https://lego-one.dev/docs')} - Full documentation`);\n console.log('\\n' + kleur.yellow('Happy coding! 🚀\\n'));\n } catch (error) {\n console.error(kleur.red('Failed to create application'));\n if (error instanceof Error) {\n console.error(kleur.red(error.message));\n }\n process.exit(1);\n }\n}\n\nasync function updatePackageJson(targetDir: string, config: ProjectConfig) {\n const packageJsonPath = path.join(targetDir, 'package.json');\n \n if (await fsExtra.pathExists(packageJsonPath)) {\n const packageJson = JSON.parse(await fsExtra.readFile(packageJsonPath, 'utf-8'));\n \n // Update package name\n packageJson.name = config.appName;\n \n // Update description if provided\n if (config.description) {\n packageJson.description = config.description;\n }\n \n // Update author if provided\n if (config.author) {\n packageJson.author = config.author;\n }\n \n // Write updated package.json\n await fsExtra.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\\n', 'utf-8');\n }\n}\n\nasync function updateReadme(targetDir: string, config: ProjectConfig) {\n const readmePath = path.join(targetDir, 'README.md');\n \n if (await fsExtra.pathExists(readmePath)) {\n let readmeContent = await fsExtra.readFile(readmePath, 'utf-8');\n \n // Replace placeholders\n readmeContent = readmeContent.replace(/{APP_NAME}/g, config.appName);\n readmeContent = readmeContent.replace(/{DESCRIPTION}/g, config.description);\n \n // Write updated README\n await fsExtra.writeFile(readmePath, readmeContent, 'utf-8');\n }\n}\n\nasync function ensurePocketBaseStructure(targetDir: string) {\n const pocketbaseDir = path.join(targetDir, 'pocketbase');\n \n // Ensure directory exists\n await fsExtra.ensureDir(pocketbaseDir);\n \n // Create .gitignore for PocketBase data\n const gitignorePath = path.join(pocketbaseDir, '.gitignore');\n const gitignoreContent = `# PocketBase data files (do not commit)\npb_data/\n*.db\n*.db-shm\n*.db-wal\nlogs.db\nlogs.db-shm\nlogs.db-wal\n\n# PocketBase executable (users download separately)\npocketbase\npocketbase.exe\npocketbase.exe.gz\n*.zip\n`;\n \n if (!(await fsExtra.pathExists(gitignorePath))) {\n await fsExtra.writeFile(gitignorePath, gitignoreContent, 'utf-8');\n }\n}\n\nasync function restoreGitignoreFiles(targetDir: string) {\n // Find all .gitignore.template files and rename them back to .gitignore\n async function findAndRestore(dir: string, baseDir: string = dir) {\n const entries = await fsExtra.readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n\n if (entry.isDirectory()) {\n await findAndRestore(fullPath, baseDir);\n } else if (entry.name === '.gitignore.template') {\n const gitignorePath = path.join(dir, '.gitignore');\n // Only rename if .gitignore doesn't already exist\n if (!(await fsExtra.pathExists(gitignorePath))) {\n await fsExtra.move(fullPath, gitignorePath, { overwrite: false });\n const relativePath = path.relative(baseDir, gitignorePath);\n console.log(kleur.gray(` ✓ Restored ${relativePath}`));\n } else {\n // If .gitignore exists, remove the template file\n await fsExtra.remove(fullPath);\n }\n }\n }\n }\n\n await findAndRestore(targetDir);\n}\n\nasync function copyTemplateFiles(sourceDir: string, targetDir: string) {\n // Files to copy\n const filesToCopy = [\n 'host',\n 'packages/plugins',\n 'package.json',\n 'pnpm-lock.yaml',\n 'pnpm-workspace.yaml',\n '.gitignore',\n 'tsconfig.json',\n ];\n\n // Files to exclude\n const excludePatterns = [\n 'node_modules',\n 'dist',\n '.next',\n 'coverage',\n '.cache',\n '*.log',\n ];\n\n for (const file of filesToCopy) {\n const srcPath = path.join(sourceDir, file);\n const destPath = path.join(targetDir, file);\n\n if (await fsExtra.pathExists(srcPath)) {\n await fsExtra.copy(srcPath, destPath, {\n filter: (src) => {\n const relativePath = path.relative(sourceDir, src);\n return !excludePatterns.some((pattern) =>\n relativePath.includes(pattern)\n );\n },\n });\n }\n }\n}\n\nasync function updateProject(projectDir: string, options: { dryRun?: boolean; force?: boolean }) {\n try {\n console.log(kleur.cyan('🔄 Updating Lego-One project...\\n'));\n\n // Check if this is a lego-one project\n const packageJsonPath = path.join(projectDir, 'package.json');\n if (!(await fsExtra.pathExists(packageJsonPath))) {\n console.error(kleur.red('❌ This does not appear to be a Lego-One project (package.json not found)'));\n process.exit(1);\n }\n\n const packageJson = JSON.parse(await fsExtra.readFile(packageJsonPath, 'utf-8'));\n const isLegoOneProject = \n packageJson.name === 'lego-one' || \n packageJson.description?.includes('Lego-One') ||\n await fsExtra.pathExists(path.join(projectDir, 'host')) ||\n await fsExtra.pathExists(path.join(projectDir, 'packages/plugins/@lego'));\n\n if (!isLegoOneProject) {\n console.error(kleur.red('❌ This does not appear to be a Lego-One project'));\n process.exit(1);\n }\n\n // Get template directory from installed package\n const templateDir = path.join(__dirname, '../template');\n \n if (!(await fsExtra.pathExists(templateDir))) {\n console.error(kleur.red('❌ Template directory not found. Please reinstall create-lego-one.'));\n process.exit(1);\n }\n\n if (options.dryRun) {\n console.log(kleur.yellow('🔍 Dry run mode - no changes will be made\\n'));\n }\n\n // Files/directories to update (must match sync-template.js filesToSync)\n const updateItems = [\n // Core application structure\n { source: 'host', target: 'host', strategy: 'merge-host' }, // Merge host updates (skip user configs)\n { source: 'packages/plugins/@lego', target: 'packages/plugins/@lego', strategy: 'merge' }, // Add new plugins only\n \n // Root files\n { source: 'README.md', target: 'README.md', strategy: 'add-if-missing' }, // Add if doesn't exist (user-specific)\n \n // Configuration files\n { source: 'package.json', target: 'package.json', strategy: 'merge-json' }, // Merge package.json\n { source: 'pnpm-workspace.yaml', target: 'pnpm-workspace.yaml', strategy: 'replace' }, // Replace workspace config\n { source: '.gitignore', target: '.gitignore', strategy: 'merge-gitignore' }, // Merge gitignore intelligently\n { source: 'tsconfig.json', target: 'tsconfig.json', strategy: 'merge-json' }, // Merge tsconfig\n { source: 'tsconfig.base.json', target: 'tsconfig.base.json', strategy: 'replace' }, // Replace base config\n { source: '.eslintrc.js', target: '.eslintrc.js', strategy: 'replace' }, // Replace ESLint config\n { source: '.eslintignore', target: '.eslintignore', strategy: 'replace' }, // Replace ESLint ignore\n { source: '.prettierrc', target: '.prettierrc', strategy: 'replace' }, // Replace Prettier config\n { source: '.prettierignore', target: '.prettierignore', strategy: 'replace' }, // Replace Prettier ignore\n \n // Environment files (examples - users create their own from these)\n { source: '.env.example', target: '.env.example', strategy: 'replace' }, // Replace example file\n { source: '.env.development', target: '.env.development', strategy: 'add-if-missing' }, // Add if doesn't exist\n { source: '.env.production', target: '.env.production', strategy: 'add-if-missing' }, // Add if doesn't exist\n \n // Deployment files\n { source: 'Dockerfile', target: 'Dockerfile', strategy: 'replace' }, // Replace Dockerfile\n { source: 'docker-compose.yml', target: 'docker-compose.yml', strategy: 'replace' }, // Replace docker-compose\n { source: 'docker-entrypoint.sh', target: 'docker-entrypoint.sh', strategy: 'replace' }, // Replace entrypoint\n { source: 'nginx.conf', target: 'nginx.conf', strategy: 'replace' }, // Replace nginx config\n { source: '.dockerignore', target: '.dockerignore', strategy: 'replace' }, // Replace dockerignore\n \n // Documentation (for AI-assisted development)\n { source: 'docs', target: 'docs', strategy: 'merge' }, // Merge docs (add new, don't overwrite user docs)\n { source: 'CLAUDE.md', target: 'CLAUDE.md', strategy: 'add-if-missing' }, // Add if doesn't exist\n { source: 'PROMPT.md', target: 'PROMPT.md', strategy: 'add-if-missing' }, // Add if doesn't exist\n { source: '.cursor', target: '.cursor', strategy: 'merge' }, // Merge cursor rules\n \n // Scripts (for users to create plugins)\n { source: 'scripts', target: 'scripts', strategy: 'merge' }, // Merge scripts (add new, don't overwrite user scripts)\n \n // PocketBase structure\n { source: 'pocketbase', target: 'pocketbase', strategy: 'merge-pocketbase' }, // Merge PocketBase (skip executable)\n ];\n\n const updates: Array<{ file: string; action: string }> = [];\n\n for (const item of updateItems) {\n const sourcePath = path.join(templateDir, item.source);\n const targetPath = path.join(projectDir, item.target);\n\n if (!(await fsExtra.pathExists(sourcePath))) {\n continue;\n }\n\n const sourceStat = await fsExtra.stat(sourcePath);\n const targetExists = await fsExtra.pathExists(targetPath);\n\n if (item.strategy === 'skip') {\n continue;\n }\n\n if (item.strategy === 'merge' || item.strategy === 'merge-host' || item.strategy === 'merge-pocketbase') {\n // For directories, add new files only (don't overwrite existing)\n if (sourceStat.isDirectory()) {\n if (item.strategy === 'merge-host') {\n await mergeHostDirectory(sourcePath, targetPath, updates, options.dryRun);\n } else if (item.strategy === 'merge-pocketbase') {\n await mergePocketBaseDirectory(sourcePath, targetPath, updates, options.dryRun);\n } else {\n await mergeDirectory(sourcePath, targetPath, updates, options.dryRun);\n }\n }\n } else if (item.strategy === 'merge-gitignore') {\n // Merge .gitignore intelligently (combine entries, avoid duplicates)\n // Check if source has .gitignore.template (npm excludes .gitignore by default)\n const sourceGitignorePath = await fsExtra.pathExists(sourcePath) ? sourcePath : \n (await fsExtra.pathExists(sourcePath + '.template') ? sourcePath + '.template' : null);\n \n if (sourceGitignorePath) {\n if (targetExists) {\n await mergeGitignoreFile(sourceGitignorePath, targetPath, updates, options.dryRun, item.target);\n } else {\n updates.push({ file: item.target, action: 'created' });\n if (!options.dryRun) {\n await fsExtra.copy(sourceGitignorePath, targetPath);\n }\n }\n }\n } else if (item.strategy === 'add-if-missing') {\n // Only add if file doesn't exist\n if (!targetExists) {\n updates.push({ file: item.target, action: 'created' });\n if (!options.dryRun) {\n await fsExtra.copy(sourcePath, targetPath);\n }\n }\n } else if (item.strategy === 'merge-json') {\n if (targetExists) {\n await mergeJsonFile(sourcePath, targetPath, updates, options.dryRun);\n } else {\n updates.push({ file: item.target, action: 'created' });\n if (!options.dryRun) {\n await fsExtra.copy(sourcePath, targetPath);\n }\n }\n } else if (item.strategy === 'replace') {\n updates.push({ file: item.target, action: 'updated' });\n if (!options.dryRun) {\n await fsExtra.copy(sourcePath, targetPath, { overwrite: true });\n }\n }\n }\n\n // Summary\n console.log('\\n' + kleur.bold('Update Summary:'));\n if (updates.length === 0) {\n console.log(kleur.gray(' No updates needed'));\n } else {\n updates.forEach(({ file, action }) => {\n const icon = action === 'created' ? '➕' : action === 'updated' ? '🔄' : '✨';\n console.log(` ${icon} ${file} (${action})`);\n });\n }\n\n // Restore .gitignore files from .gitignore.template (npm excludes .gitignore by default)\n if (!options.dryRun) {\n await restoreGitignoreFiles(projectDir);\n }\n\n if (options.dryRun) {\n console.log('\\n' + kleur.yellow('💡 Run without --dry-run to apply updates'));\n } else {\n console.log('\\n' + kleur.green('✅ Project updated successfully!'));\n console.log(kleur.gray('\\nNext steps:'));\n console.log(` ${kleur.cyan('pnpm install')} - Install/update dependencies`);\n console.log(` ${kleur.cyan('pnpm run dev')} - Start development server`);\n }\n } catch (error) {\n console.error(kleur.red('❌ Failed to update project'));\n if (error instanceof Error) {\n console.error(kleur.red(error.message));\n }\n process.exit(1);\n }\n}\n\nasync function mergeDirectory(\n sourceDir: string,\n targetDir: string,\n updates: Array<{ file: string; action: string }>,\n dryRun: boolean = false\n) {\n await fsExtra.ensureDir(targetDir);\n const entries = await fsExtra.readdir(sourceDir, { withFileTypes: true });\n\n for (const entry of entries) {\n const sourcePath = path.join(sourceDir, entry.name);\n const targetPath = path.join(targetDir, entry.name);\n\n if (entry.isDirectory()) {\n await mergeDirectory(sourcePath, targetPath, updates, dryRun);\n } else {\n const targetExists = await fsExtra.pathExists(targetPath);\n \n // Special handling for .gitignore files - merge them even if they exist\n if (entry.name === '.gitignore' && targetExists) {\n const relativePath = path.relative(targetDir, targetPath).replace(/\\\\/g, '/');\n await mergeGitignoreFile(sourcePath, targetPath, updates, dryRun, relativePath);\n } else if (targetExists) {\n // For built-in plugins, update existing files to get framework updates\n // Check if file has changed by comparing content\n const sourceContent = await fsExtra.readFile(sourcePath, 'utf-8');\n const targetContent = await fsExtra.readFile(targetPath, 'utf-8');\n \n if (sourceContent !== targetContent) {\n const relativePath = path.relative(targetDir, targetPath).replace(/\\\\/g, '/');\n updates.push({ file: relativePath, action: 'updated' });\n if (!dryRun) {\n await fsExtra.copy(sourcePath, targetPath, { overwrite: true });\n }\n }\n } else {\n // Only add files that don't exist (new plugins, new kernel files)\n const relativePath = path.relative(targetDir, targetPath);\n updates.push({ file: relativePath, action: 'created' });\n if (!dryRun) {\n await fsExtra.copy(sourcePath, targetPath);\n }\n }\n }\n }\n}\n\nasync function mergeHostDirectory(\n sourceDir: string,\n targetDir: string,\n updates: Array<{ file: string; action: string }>,\n dryRun: boolean = false,\n hostRootDir?: string\n) {\n // Track the host root directory for relative path calculation\n if (!hostRootDir) {\n hostRootDir = sourceDir;\n }\n\n await fsExtra.ensureDir(targetDir);\n const entries = await fsExtra.readdir(sourceDir, { withFileTypes: true });\n\n for (const entry of entries) {\n const sourcePath = path.join(sourceDir, entry.name);\n const targetPath = path.join(targetDir, entry.name);\n const relativeToHostRoot = path.relative(hostRootDir, sourcePath).replace(/\\\\/g, '/');\n\n // Skip user-specific config files\n // modern.config.ts is at host root\n // saas.config.ts is at host/src/\n if (relativeToHostRoot === 'modern.config.ts' || relativeToHostRoot === 'src/saas.config.ts') {\n continue;\n }\n\n // Skip user routes - these are user-customizable\n if (relativeToHostRoot.startsWith('src/routes/')) {\n continue;\n }\n\n if (entry.isDirectory()) {\n await mergeHostDirectory(sourcePath, targetPath, updates, dryRun, hostRootDir);\n } else {\n const targetExists = await fsExtra.pathExists(targetPath);\n \n // Special handling for .gitignore files - merge them even if they exist\n if (entry.name === '.gitignore' && targetExists) {\n const relativePath = path.relative(targetDir, targetPath).replace(/\\\\/g, '/');\n await mergeGitignoreFile(sourcePath, targetPath, updates, dryRun, relativePath);\n } else if (targetExists) {\n // For existing kernel files, update them from framework\n // This ensures users get bug fixes and improvements to kernel files\n const relativeTargetPath = path.relative(targetDir, targetPath).replace(/\\\\/g, '/');\n \n // Check if file has changed by comparing content\n const sourceContent = await fsExtra.readFile(sourcePath, 'utf-8');\n const targetContent = await fsExtra.readFile(targetPath, 'utf-8');\n \n if (sourceContent !== targetContent) {\n updates.push({ file: relativeTargetPath, action: 'updated' });\n if (!dryRun) {\n await fsExtra.copy(sourcePath, targetPath, { overwrite: true });\n }\n }\n } else {\n // Only add files that don't exist\n const relativeTargetPath = path.relative(targetDir, targetPath).replace(/\\\\/g, '/');\n updates.push({ file: relativeTargetPath, action: 'created' });\n if (!dryRun) {\n await fsExtra.copy(sourcePath, targetPath);\n }\n }\n }\n }\n}\n\nasync function mergePocketBaseDirectory(\n sourceDir: string,\n targetDir: string,\n updates: Array<{ file: string; action: string }>,\n dryRun: boolean = false\n) {\n // Files to skip (executable and data)\n const skipFiles = [\n 'pocketbase.exe',\n 'pocketbase',\n 'pocketbase.exe.gz',\n 'pb_data',\n ];\n\n await fsExtra.ensureDir(targetDir);\n const entries = await fsExtra.readdir(sourceDir, { withFileTypes: true });\n\n for (const entry of entries) {\n const sourcePath = path.join(sourceDir, entry.name);\n const targetPath = path.join(targetDir, entry.name);\n\n // Skip executable and data files\n if (skipFiles.includes(entry.name)) {\n continue;\n }\n\n if (entry.isDirectory()) {\n await mergePocketBaseDirectory(sourcePath, targetPath, updates, dryRun);\n } else {\n const targetExists = await fsExtra.pathExists(targetPath);\n \n // Special handling for .gitignore files - merge them even if they exist\n if (entry.name === '.gitignore' && targetExists) {\n const relativePath = path.relative(targetDir, targetPath).replace(/\\\\/g, '/');\n await mergeGitignoreFile(sourcePath, targetPath, updates, dryRun, relativePath);\n } else if (!targetExists) {\n // Only add files that don't exist\n const relativePath = path.relative(targetDir, targetPath);\n updates.push({ file: relativePath, action: 'created' });\n if (!dryRun) {\n await fsExtra.copy(sourcePath, targetPath);\n }\n }\n }\n }\n}\n\nasync function mergeJsonFile(\n sourcePath: string,\n targetPath: string,\n updates: Array<{ file: string; action: string }>,\n dryRun: boolean = false\n) {\n const sourceJson = JSON.parse(await fsExtra.readFile(sourcePath, 'utf-8'));\n const targetJson = JSON.parse(await fsExtra.readFile(targetPath, 'utf-8'));\n\n // Framework script names that should always be updated from framework\n const frameworkScripts = [\n 'dev',\n 'dev:host',\n 'dev:plugins',\n 'dev:all',\n 'build',\n 'lint',\n 'lint:fix',\n 'format',\n 'format:check',\n 'typecheck',\n 'test',\n 'test:e2e',\n 'clean',\n 'create-plugin',\n ];\n\n // Merge dependencies and devDependencies\n const merged = { ...targetJson };\n \n if (sourceJson.dependencies) {\n merged.dependencies = { ...targetJson.dependencies, ...sourceJson.dependencies };\n }\n if (sourceJson.devDependencies) {\n merged.devDependencies = { ...targetJson.devDependencies, ...sourceJson.devDependencies };\n }\n \n if (sourceJson.scripts) {\n // Start with user's scripts (preserves custom scripts)\n merged.scripts = { ...targetJson.scripts };\n \n // Update framework scripts from source (framework takes precedence for these)\n for (const scriptName of frameworkScripts) {\n if (sourceJson.scripts[scriptName]) {\n merged.scripts[scriptName] = sourceJson.scripts[scriptName];\n }\n }\n \n // Add any new scripts from framework that don't exist in user's scripts\n for (const [scriptName, scriptValue] of Object.entries(sourceJson.scripts)) {\n if (!merged.scripts[scriptName]) {\n merged.scripts[scriptName] = scriptValue;\n }\n }\n }\n\n // Merge other top-level fields that should be updated from framework\n if (sourceJson.engines) {\n merged.engines = sourceJson.engines;\n }\n if (sourceJson.type) {\n merged.type = sourceJson.type;\n }\n\n // Check if there are actual changes\n const hasChanges = JSON.stringify(merged) !== JSON.stringify(targetJson);\n \n if (hasChanges) {\n updates.push({ file: path.basename(targetPath), action: 'updated' });\n if (!dryRun) {\n await fsExtra.writeFile(targetPath, JSON.stringify(merged, null, 2) + '\\n', 'utf-8');\n }\n }\n}\n\nasync function mergeGitignoreFile(\n sourcePath: string,\n targetPath: string,\n updates: Array<{ file: string; action: string }>,\n dryRun: boolean = false,\n relativePath?: string\n) {\n const sourceContent = await fsExtra.readFile(sourcePath, 'utf-8');\n const targetContent = await fsExtra.readFile(targetPath, 'utf-8');\n\n // Split into lines and normalize\n const sourceLines = sourceContent.split('\\n').map(line => line.trim()).filter(line => line && !line.startsWith('#'));\n const targetLines = targetContent.split('\\n').map(line => line.trim()).filter(line => line && !line.startsWith('#'));\n\n // Get existing entries as a set\n const existingEntries = new Set(targetLines);\n\n // Find new entries from source\n const newEntries = sourceLines.filter(line => !existingEntries.has(line));\n\n if (newEntries.length > 0) {\n const displayPath = relativePath || path.basename(targetPath);\n updates.push({ file: displayPath, action: 'updated' });\n if (!dryRun) {\n // Append new entries to target file\n const updatedContent = targetContent + (targetContent.endsWith('\\n') ? '' : '\\n') + \n (newEntries.length > 0 ? '\\n# Added by framework update\\n' + newEntries.join('\\n') + '\\n' : '');\n await fsExtra.writeFile(targetPath, updatedContent, 'utf-8');\n }\n }\n}\n\n// Run the CLI\ninit().catch((error) => {\n console.error(kleur.red('Unexpected error:'));\n console.error(error);\n process.exit(1);\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAKA,IAAM,mBAAmB,MACvB,OAAO,aAAa,cAChB,IAAI,IAAI,QAAQ,UAAU,EAAE,EAAE,OAC7B,SAAS,iBAAiB,SAAS,cAAc,QAAQ,YAAY,MAAM,WAC1E,SAAS,cAAc,MACvB,IAAI,IAAI,WAAW,SAAS,OAAO,EAAE;AAEtC,IAAM,gBAAgC,iCAAiB;;;ACL9D,kBAAiB;AACjB,gBAAe;AACf,iBAA8B;AAC9B,uBAAwB;AACxB,YAAuB;AACvB,sBAAoB;AACpB,uCAAgC;AAChC,qBAAoB;AACpB,2BAAyB;AAEzB,IAAM,YAAY,YAAAA,QAAK,YAAQ,0BAAc,aAAe,CAAC;AAoB7D,IAAM,SAAS;AAAA,EACP,WAAK,oWAA6D,CAAC;AAAA,EACnE,WAAK,QAAG,CAAC,IAAU,WAAK,EAAE,MAAM,UAAU,CAAC,8CAAoD,WAAK,QAAG,CAAC;AAAA,EACxG,WAAK,QAAG,CAAC,IAAU,WAAK,mDAAmD,CAAC,SAAe,WAAK,QAAG,CAAC;AAAA,EACpG,WAAK,oWAA6D,CAAC;AAAA;AAG3E,eAAe,OAAO;AACpB,UAAQ,IAAI,MAAM;AAElB,QAAM,UAAU,WAAW;AAC3B,UAAQ,IAAU,WAAK,oBAAoB,OAAO;AAAA,CAAI,CAAC;AAEvD,2BACG,KAAK,iBAAiB,EACtB,YAAY,wCAAwC,EACpD,QAAQ,OAAO,EACf,SAAS,cAAc,0BAA0B,EACjD,OAAO,qBAAqB,6DAA6D,EACzF,OAAO,oBAAoB,0BAA0B,EACrD,OAAO,SAAS,6BAA6B,IAAI,EACjD,OAAO,YAAY,yBAAyB,EAC5C,OAAO,+BAA+B,qBAAqB,EAC3D,OAAO,qBAAqB,aAAa,EACzC,OAAO,qBAAqB,qBAAqB,EACjD,OAAO,yBAAyB,uBAAuB,EACvD,OAAO,OAAO,SAA6B,YAAY;AAEtD,UAAM,gBAAgB,QAAQ,gBAAgB,UAAU,CAAC,WAAW,QAAQ,gBAAgB;AAE5F,UAAM,OAAsB;AAAA,MAC1B;AAAA,MACA,aAAa;AAAA,MACb,KAAK,QAAQ,QAAQ;AAAA,MACrB,QAAQ,QAAQ;AAAA,MAChB,UAAU,QAAQ;AAAA,MAClB,aAAa,QAAQ;AAAA,MACrB,QAAQ,QAAQ;AAAA,IAClB;AACA,UAAM,UAAU,IAAI;AAAA,EACtB,CAAC;AAGH,2BACG,QAAQ,QAAQ,EAChB,YAAY,gEAAgE,EAC5E,OAAO,aAAa,mDAAmD,EACvE,OAAO,WAAW,yCAAyC,EAC3D,OAAO,OAAO,YAAY;AACzB,UAAM,cAAc,QAAQ,IAAI,GAAG,OAAO;AAAA,EAC5C,CAAC;AAEH,QAAM,yBAAQ,WAAW,QAAQ,IAAI;AACvC;AAEA,SAAS,aAAqB;AAC5B,QAAM,UAAU,YAAAA,QAAK,KAAK,WAAW,iBAAiB;AACtD,MAAI;AACF,UAAM,MAAM,KAAK,MAAM,UAAAC,QAAG,aAAa,SAAS,OAAO,CAAC;AACxD,WAAO,IAAI,WAAW;AAAA,EACxB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAe,UAAU,SAAwB;AAC/C,MAAI;AACF,QAAI;AAGJ,QAAI,QAAQ,aAAa;AACvB,YAAM,YAAY,UAAM,eAAAC,SAAQ;AAAA,QAC9B;AAAA,UACE,MAAM;AAAA,UACN,MAAM;AAAA,UACN,SAAS;AAAA,UACT,SAAS,QAAQ,WAAW;AAAA,UAC5B,UAAU,CAAC,UAAkB;AAC3B,gBAAI,CAAC,MAAO,QAAO;AACnB,kBAAMC,kBAAa,iCAAAC,SAAoB,KAAK;AAC5C,gBAAID,YAAW,oBAAqB,QAAO;AAC3C,kBAAM,WAAWA,YAAW,QAAQ,KAAK,IAAI,KAAKA,YAAW,UAAU,KAAK,IAAI,KAAK;AACrF,mBAAO,uBAAuB;AAAA,UAChC;AAAA,QACF;AAAA,QACA;AAAA,UACE,MAAM;AAAA,UACN,MAAM;AAAA,UACN,SAAS;AAAA,UACT,SAAS,QAAQ,eAAe;AAAA,QAClC;AAAA,QACA;AAAA,UACE,MAAM;AAAA,UACN,MAAM;AAAA,UACN,SAAS;AAAA,UACT,SAAS,QAAQ,UAAU;AAAA,QAC7B;AAAA,QACA;AAAA,UACE,MAAM;AAAA,UACN,MAAM;AAAA,UACN,SAAS;AAAA,UACT,SAAS,QAAQ,QAAQ;AAAA,QAC3B;AAAA,MACF,CAAC;AAED,sBAAgB;AAAA,QACd,SAAS,UAAU,WAAW,QAAQ,WAAW;AAAA,QACjD,aAAa,UAAU,eAAe,QAAQ,eAAe;AAAA,QAC7D,QAAQ,UAAU,UAAU,QAAQ,UAAU;AAAA,QAC9C,KAAK,UAAU,QAAQ;AAAA,MACzB;AAAA,IACF,OAAO;AAEL,UAAI,CAAC,QAAQ,SAAS;AACpB,gBAAQ,MAAY,UAAI,8DAA8D,CAAC;AACvF,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAEA,sBAAgB;AAAA,QACd,SAAS,QAAQ;AAAA,QACjB,aAAa,QAAQ,eAAe;AAAA,QACpC,QAAQ,QAAQ,UAAU;AAAA,QAC1B,KAAK,QAAQ,QAAQ;AAAA,MACvB;AAAA,IACF;AAGA,UAAM,iBAAa,iCAAAC,SAAoB,cAAc,OAAO;AAC5D,QAAI,CAAC,WAAW,qBAAqB;AACnC,YAAM,WAAW,WAAW,QAAQ,KAAK,IAAI,KAAK,WAAW,UAAU,KAAK,IAAI,KAAK;AACrF,cAAQ,MAAY,UAAI,qBAAqB,QAAQ,EAAE,CAAC;AACxD,UAAI,WAAW,YAAY,WAAW,SAAS,SAAS,GAAG;AACzD,gBAAQ,MAAY,aAAO,aAAa,WAAW,SAAS,KAAK,IAAI,CAAC,EAAE,CAAC;AAAA,MAC3E;AACA,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,UAAM,UAAU,cAAc;AAE9B,UAAM,YAAY,YAAAJ,QAAK,QAAQ,QAAQ,IAAI,GAAG,OAAO;AAGrD,QAAI,MAAM,gBAAAK,QAAQ,WAAW,SAAS,GAAG;AACvC,YAAM,QAAQ,MAAM,gBAAAA,QAAQ,QAAQ,SAAS;AAC7C,UAAI,MAAM,SAAS,GAAG;AACpB,gBAAQ,MAAY,UAAI,cAAc,OAAO,mCAAmC,CAAC;AACjF,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAAA,IACF;AAEA,YAAQ,IAAU,WAAK,uCAAuC,CAAC;AAG/D,UAAM,gBAAAA,QAAQ,UAAU,SAAS;AAGjC,YAAQ,IAAU,WAAK,2BAA2B,CAAC;AACnD,UAAM,cAAc,YAAAL,QAAK,KAAK,WAAW,aAAa;AACtD,QAAI,MAAM,gBAAAK,QAAQ,WAAW,WAAW,GAAG;AACzC,YAAM,gBAAAA,QAAQ,KAAK,aAAa,SAAS;AAEzC,YAAM,sBAAsB,SAAS;AAAA,IACvC,OAAO;AAEL,YAAM,UAAU,YAAAL,QAAK,KAAK,WAAW,UAAU;AAC/C,YAAM,kBAAkB,SAAS,SAAS;AAAA,IAC5C;AAGA,YAAQ,IAAU,WAAK,wBAAwB,CAAC;AAChD,UAAM,kBAAkB,WAAW,aAAa;AAGhD,UAAM,aAAa,WAAW,aAAa;AAG3C,UAAM,0BAA0B,SAAS;AAGzC,QAAI,cAAc,KAAK;AACrB,cAAQ,IAAU,WAAK,gCAAgC,CAAC;AACxD,UAAI;AACF,2CAAS,YAAY,EAAE,KAAK,UAAU,CAAC;AACvC,2CAAS,aAAa,EAAE,KAAK,UAAU,CAAC;AACxC,2CAAS,gDAAgD;AAAA,UACvD,KAAK;AAAA,QACP,CAAC;AAAA,MACH,SAAS,OAAO;AACd,gBAAQ,KAAW,aAAO,qCAAqC,CAAC;AAAA,MAClE;AAAA,IACF;AAEA,YAAQ,IAAU,YAAM,mCAAmC,CAAC;AAG5D,YAAQ,IAAI,OAAa,WAAK,aAAa,CAAC;AAC5C,YAAQ,IAAI,KAAW,WAAK,IAAI,CAAC,IAAI,OAAO,EAAE;AAC9C,YAAQ,IAAI,KAAW,WAAK,cAAc,CAAC,EAAE;AAC7C,YAAQ,IAAI,KAAW,WAAK,cAAc,CAAC,EAAE;AAC7C,YAAQ,IAAI,OAAa,WAAK,sBAAsB,CAAC;AACrD,YAAQ,IAAI,KAAW,WAAK,cAAc,CAAC,6BAA6B;AACxE,YAAQ,IAAI,KAAW,WAAK,kBAAkB,CAAC,uBAAuB;AACtE,YAAQ,IAAI,OAAa,WAAK,0BAA0B,CAAC;AACzD,YAAQ,IAAI,KAAW,WAAK,gBAAgB,CAAC,sBAAsB;AACnE,YAAQ,IAAI,KAAW,WAAK,gBAAgB,CAAC,4BAA4B;AACzE,YAAQ,IAAI,OAAa,WAAK,gBAAgB,CAAC;AAC/C,YAAQ,IAAI,KAAW,WAAK,2BAA2B,CAAC,uBAAuB;AAC/E,YAAQ,IAAI,OAAa,aAAO,2BAAoB,CAAC;AAAA,EACvD,SAAS,OAAO;AACd,YAAQ,MAAY,UAAI,8BAA8B,CAAC;AACvD,QAAI,iBAAiB,OAAO;AAC1B,cAAQ,MAAY,UAAI,MAAM,OAAO,CAAC;AAAA,IACxC;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AAEA,eAAe,kBAAkB,WAAmB,QAAuB;AACzE,QAAM,kBAAkB,YAAAA,QAAK,KAAK,WAAW,cAAc;AAE3D,MAAI,MAAM,gBAAAK,QAAQ,WAAW,eAAe,GAAG;AAC7C,UAAM,cAAc,KAAK,MAAM,MAAM,gBAAAA,QAAQ,SAAS,iBAAiB,OAAO,CAAC;AAG/E,gBAAY,OAAO,OAAO;AAG1B,QAAI,OAAO,aAAa;AACtB,kBAAY,cAAc,OAAO;AAAA,IACnC;AAGA,QAAI,OAAO,QAAQ;AACjB,kBAAY,SAAS,OAAO;AAAA,IAC9B;AAGA,UAAM,gBAAAA,QAAQ,UAAU,iBAAiB,KAAK,UAAU,aAAa,MAAM,CAAC,IAAI,MAAM,OAAO;AAAA,EAC/F;AACF;AAEA,eAAe,aAAa,WAAmB,QAAuB;AACpE,QAAM,aAAa,YAAAL,QAAK,KAAK,WAAW,WAAW;AAEnD,MAAI,MAAM,gBAAAK,QAAQ,WAAW,UAAU,GAAG;AACxC,QAAI,gBAAgB,MAAM,gBAAAA,QAAQ,SAAS,YAAY,OAAO;AAG9D,oBAAgB,cAAc,QAAQ,eAAe,OAAO,OAAO;AACnE,oBAAgB,cAAc,QAAQ,kBAAkB,OAAO,WAAW;AAG1E,UAAM,gBAAAA,QAAQ,UAAU,YAAY,eAAe,OAAO;AAAA,EAC5D;AACF;AAEA,eAAe,0BAA0B,WAAmB;AAC1D,QAAM,gBAAgB,YAAAL,QAAK,KAAK,WAAW,YAAY;AAGvD,QAAM,gBAAAK,QAAQ,UAAU,aAAa;AAGrC,QAAM,gBAAgB,YAAAL,QAAK,KAAK,eAAe,YAAY;AAC3D,QAAM,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgBzB,MAAI,CAAE,MAAM,gBAAAK,QAAQ,WAAW,aAAa,GAAI;AAC9C,UAAM,gBAAAA,QAAQ,UAAU,eAAe,kBAAkB,OAAO;AAAA,EAClE;AACF;AAEA,eAAe,sBAAsB,WAAmB;AAEtD,iBAAe,eAAe,KAAa,UAAkB,KAAK;AAChE,UAAM,UAAU,MAAM,gBAAAA,QAAQ,QAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;AAElE,eAAW,SAAS,SAAS;AAC3B,YAAM,WAAW,YAAAL,QAAK,KAAK,KAAK,MAAM,IAAI;AAE1C,UAAI,MAAM,YAAY,GAAG;AACvB,cAAM,eAAe,UAAU,OAAO;AAAA,MACxC,WAAW,MAAM,SAAS,uBAAuB;AAC/C,cAAM,gBAAgB,YAAAA,QAAK,KAAK,KAAK,YAAY;AAEjD,YAAI,CAAE,MAAM,gBAAAK,QAAQ,WAAW,aAAa,GAAI;AAC9C,gBAAM,gBAAAA,QAAQ,KAAK,UAAU,eAAe,EAAE,WAAW,MAAM,CAAC;AAChE,gBAAM,eAAe,YAAAL,QAAK,SAAS,SAAS,aAAa;AACzD,kBAAQ,IAAU,WAAK,qBAAgB,YAAY,EAAE,CAAC;AAAA,QACxD,OAAO;AAEL,gBAAM,gBAAAK,QAAQ,OAAO,QAAQ;AAAA,QAC/B;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,eAAe,SAAS;AAChC;AAEA,eAAe,kBAAkB,WAAmB,WAAmB;AAErE,QAAM,cAAc;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAGA,QAAM,kBAAkB;AAAA,IACtB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,aAAW,QAAQ,aAAa;AAC9B,UAAM,UAAU,YAAAL,QAAK,KAAK,WAAW,IAAI;AACzC,UAAM,WAAW,YAAAA,QAAK,KAAK,WAAW,IAAI;AAE1C,QAAI,MAAM,gBAAAK,QAAQ,WAAW,OAAO,GAAG;AACrC,YAAM,gBAAAA,QAAQ,KAAK,SAAS,UAAU;AAAA,QACpC,QAAQ,CAAC,QAAQ;AACf,gBAAM,eAAe,YAAAL,QAAK,SAAS,WAAW,GAAG;AACjD,iBAAO,CAAC,gBAAgB;AAAA,YAAK,CAAC,YAC5B,aAAa,SAAS,OAAO;AAAA,UAC/B;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAEA,eAAe,cAAc,YAAoB,SAAgD;AAC/F,MAAI;AACF,YAAQ,IAAU,WAAK,0CAAmC,CAAC;AAG3D,UAAM,kBAAkB,YAAAA,QAAK,KAAK,YAAY,cAAc;AAC5D,QAAI,CAAE,MAAM,gBAAAK,QAAQ,WAAW,eAAe,GAAI;AAChD,cAAQ,MAAY,UAAI,+EAA0E,CAAC;AACnG,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,UAAM,cAAc,KAAK,MAAM,MAAM,gBAAAA,QAAQ,SAAS,iBAAiB,OAAO,CAAC;AAC/E,UAAM,mBACJ,YAAY,SAAS,cACrB,YAAY,aAAa,SAAS,UAAU,KAC5C,MAAM,gBAAAA,QAAQ,WAAW,YAAAL,QAAK,KAAK,YAAY,MAAM,CAAC,KACtD,MAAM,gBAAAK,QAAQ,WAAW,YAAAL,QAAK,KAAK,YAAY,wBAAwB,CAAC;AAE1E,QAAI,CAAC,kBAAkB;AACrB,cAAQ,MAAY,UAAI,sDAAiD,CAAC;AAC1E,cAAQ,KAAK,CAAC;AAAA,IAChB;AAGA,UAAM,cAAc,YAAAA,QAAK,KAAK,WAAW,aAAa;AAEtD,QAAI,CAAE,MAAM,gBAAAK,QAAQ,WAAW,WAAW,GAAI;AAC5C,cAAQ,MAAY,UAAI,wEAAmE,CAAC;AAC5F,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,QAAI,QAAQ,QAAQ;AAClB,cAAQ,IAAU,aAAO,oDAA6C,CAAC;AAAA,IACzE;AAGA,UAAM,cAAc;AAAA;AAAA,MAElB,EAAE,QAAQ,QAAQ,QAAQ,QAAQ,UAAU,aAAa;AAAA;AAAA,MACzD,EAAE,QAAQ,0BAA0B,QAAQ,0BAA0B,UAAU,QAAQ;AAAA;AAAA;AAAA,MAGxF,EAAE,QAAQ,aAAa,QAAQ,aAAa,UAAU,iBAAiB;AAAA;AAAA;AAAA,MAGvE,EAAE,QAAQ,gBAAgB,QAAQ,gBAAgB,UAAU,aAAa;AAAA;AAAA,MACzE,EAAE,QAAQ,uBAAuB,QAAQ,uBAAuB,UAAU,UAAU;AAAA;AAAA,MACpF,EAAE,QAAQ,cAAc,QAAQ,cAAc,UAAU,kBAAkB;AAAA;AAAA,MAC1E,EAAE,QAAQ,iBAAiB,QAAQ,iBAAiB,UAAU,aAAa;AAAA;AAAA,MAC3E,EAAE,QAAQ,sBAAsB,QAAQ,sBAAsB,UAAU,UAAU;AAAA;AAAA,MAClF,EAAE,QAAQ,gBAAgB,QAAQ,gBAAgB,UAAU,UAAU;AAAA;AAAA,MACtE,EAAE,QAAQ,iBAAiB,QAAQ,iBAAiB,UAAU,UAAU;AAAA;AAAA,MACxE,EAAE,QAAQ,eAAe,QAAQ,eAAe,UAAU,UAAU;AAAA;AAAA,MACpE,EAAE,QAAQ,mBAAmB,QAAQ,mBAAmB,UAAU,UAAU;AAAA;AAAA;AAAA,MAG5E,EAAE,QAAQ,gBAAgB,QAAQ,gBAAgB,UAAU,UAAU;AAAA;AAAA,MACtE,EAAE,QAAQ,oBAAoB,QAAQ,oBAAoB,UAAU,iBAAiB;AAAA;AAAA,MACrF,EAAE,QAAQ,mBAAmB,QAAQ,mBAAmB,UAAU,iBAAiB;AAAA;AAAA;AAAA,MAGnF,EAAE,QAAQ,cAAc,QAAQ,cAAc,UAAU,UAAU;AAAA;AAAA,MAClE,EAAE,QAAQ,sBAAsB,QAAQ,sBAAsB,UAAU,UAAU;AAAA;AAAA,MAClF,EAAE,QAAQ,wBAAwB,QAAQ,wBAAwB,UAAU,UAAU;AAAA;AAAA,MACtF,EAAE,QAAQ,cAAc,QAAQ,cAAc,UAAU,UAAU;AAAA;AAAA,MAClE,EAAE,QAAQ,iBAAiB,QAAQ,iBAAiB,UAAU,UAAU;AAAA;AAAA;AAAA,MAGxE,EAAE,QAAQ,QAAQ,QAAQ,QAAQ,UAAU,QAAQ;AAAA;AAAA,MACpD,EAAE,QAAQ,aAAa,QAAQ,aAAa,UAAU,iBAAiB;AAAA;AAAA,MACvE,EAAE,QAAQ,aAAa,QAAQ,aAAa,UAAU,iBAAiB;AAAA;AAAA,MACvE,EAAE,QAAQ,WAAW,QAAQ,WAAW,UAAU,QAAQ;AAAA;AAAA;AAAA,MAG1D,EAAE,QAAQ,WAAW,QAAQ,WAAW,UAAU,QAAQ;AAAA;AAAA;AAAA,MAG1D,EAAE,QAAQ,cAAc,QAAQ,cAAc,UAAU,mBAAmB;AAAA;AAAA,IAC7E;AAEA,UAAM,UAAmD,CAAC;AAE1D,eAAW,QAAQ,aAAa;AAC9B,YAAM,aAAa,YAAAL,QAAK,KAAK,aAAa,KAAK,MAAM;AACrD,YAAM,aAAa,YAAAA,QAAK,KAAK,YAAY,KAAK,MAAM;AAEpD,UAAI,CAAE,MAAM,gBAAAK,QAAQ,WAAW,UAAU,GAAI;AAC3C;AAAA,MACF;AAEA,YAAM,aAAa,MAAM,gBAAAA,QAAQ,KAAK,UAAU;AAChD,YAAM,eAAe,MAAM,gBAAAA,QAAQ,WAAW,UAAU;AAExD,UAAI,KAAK,aAAa,QAAQ;AAC5B;AAAA,MACF;AAEA,UAAI,KAAK,aAAa,WAAW,KAAK,aAAa,gBAAgB,KAAK,aAAa,oBAAoB;AAEvG,YAAI,WAAW,YAAY,GAAG;AAC5B,cAAI,KAAK,aAAa,cAAc;AAClC,kBAAM,mBAAmB,YAAY,YAAY,SAAS,QAAQ,MAAM;AAAA,UAC1E,WAAW,KAAK,aAAa,oBAAoB;AAC/C,kBAAM,yBAAyB,YAAY,YAAY,SAAS,QAAQ,MAAM;AAAA,UAChF,OAAO;AACL,kBAAM,eAAe,YAAY,YAAY,SAAS,QAAQ,MAAM;AAAA,UACtE;AAAA,QACF;AAAA,MACF,WAAW,KAAK,aAAa,mBAAmB;AAG9C,cAAM,sBAAsB,MAAM,gBAAAA,QAAQ,WAAW,UAAU,IAAI,aAChE,MAAM,gBAAAA,QAAQ,WAAW,aAAa,WAAW,IAAI,aAAa,cAAc;AAEnF,YAAI,qBAAqB;AACvB,cAAI,cAAc;AAChB,kBAAM,mBAAmB,qBAAqB,YAAY,SAAS,QAAQ,QAAQ,KAAK,MAAM;AAAA,UAChG,OAAO;AACL,oBAAQ,KAAK,EAAE,MAAM,KAAK,QAAQ,QAAQ,UAAU,CAAC;AACrD,gBAAI,CAAC,QAAQ,QAAQ;AACnB,oBAAM,gBAAAA,QAAQ,KAAK,qBAAqB,UAAU;AAAA,YACpD;AAAA,UACF;AAAA,QACF;AAAA,MACF,WAAW,KAAK,aAAa,kBAAkB;AAE7C,YAAI,CAAC,cAAc;AACjB,kBAAQ,KAAK,EAAE,MAAM,KAAK,QAAQ,QAAQ,UAAU,CAAC;AACrD,cAAI,CAAC,QAAQ,QAAQ;AACnB,kBAAM,gBAAAA,QAAQ,KAAK,YAAY,UAAU;AAAA,UAC3C;AAAA,QACF;AAAA,MACF,WAAW,KAAK,aAAa,cAAc;AACzC,YAAI,cAAc;AAChB,gBAAM,cAAc,YAAY,YAAY,SAAS,QAAQ,MAAM;AAAA,QACrE,OAAO;AACL,kBAAQ,KAAK,EAAE,MAAM,KAAK,QAAQ,QAAQ,UAAU,CAAC;AACrD,cAAI,CAAC,QAAQ,QAAQ;AACnB,kBAAM,gBAAAA,QAAQ,KAAK,YAAY,UAAU;AAAA,UAC3C;AAAA,QACF;AAAA,MACF,WAAW,KAAK,aAAa,WAAW;AACtC,gBAAQ,KAAK,EAAE,MAAM,KAAK,QAAQ,QAAQ,UAAU,CAAC;AACrD,YAAI,CAAC,QAAQ,QAAQ;AACnB,gBAAM,gBAAAA,QAAQ,KAAK,YAAY,YAAY,EAAE,WAAW,KAAK,CAAC;AAAA,QAChE;AAAA,MACF;AAAA,IACF;AAGA,YAAQ,IAAI,OAAa,WAAK,iBAAiB,CAAC;AAChD,QAAI,QAAQ,WAAW,GAAG;AACxB,cAAQ,IAAU,WAAK,qBAAqB,CAAC;AAAA,IAC/C,OAAO;AACL,cAAQ,QAAQ,CAAC,EAAE,MAAM,OAAO,MAAM;AACpC,cAAM,OAAO,WAAW,YAAY,WAAM,WAAW,YAAY,cAAO;AACxE,gBAAQ,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,MAAM,GAAG;AAAA,MAC7C,CAAC;AAAA,IACH;AAGA,QAAI,CAAC,QAAQ,QAAQ;AACnB,YAAM,sBAAsB,UAAU;AAAA,IACxC;AAEA,QAAI,QAAQ,QAAQ;AAClB,cAAQ,IAAI,OAAa,aAAO,kDAA2C,CAAC;AAAA,IAC9E,OAAO;AACL,cAAQ,IAAI,OAAa,YAAM,sCAAiC,CAAC;AACjE,cAAQ,IAAU,WAAK,eAAe,CAAC;AACvC,cAAQ,IAAI,KAAW,WAAK,cAAc,CAAC,gCAAgC;AAC3E,cAAQ,IAAI,KAAW,WAAK,cAAc,CAAC,6BAA6B;AAAA,IAC1E;AAAA,EACF,SAAS,OAAO;AACd,YAAQ,MAAY,UAAI,iCAA4B,CAAC;AACrD,QAAI,iBAAiB,OAAO;AAC1B,cAAQ,MAAY,UAAI,MAAM,OAAO,CAAC;AAAA,IACxC;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AAEA,eAAe,eACb,WACA,WACA,SACA,SAAkB,OAClB;AACA,QAAM,gBAAAA,QAAQ,UAAU,SAAS;AACjC,QAAM,UAAU,MAAM,gBAAAA,QAAQ,QAAQ,WAAW,EAAE,eAAe,KAAK,CAAC;AAExE,aAAW,SAAS,SAAS;AAC3B,UAAM,aAAa,YAAAL,QAAK,KAAK,WAAW,MAAM,IAAI;AAClD,UAAM,aAAa,YAAAA,QAAK,KAAK,WAAW,MAAM,IAAI;AAElD,QAAI,MAAM,YAAY,GAAG;AACvB,YAAM,eAAe,YAAY,YAAY,SAAS,MAAM;AAAA,IAC9D,OAAO;AACL,YAAM,eAAe,MAAM,gBAAAK,QAAQ,WAAW,UAAU;AAGxD,UAAI,MAAM,SAAS,gBAAgB,cAAc;AAC/C,cAAM,eAAe,YAAAL,QAAK,SAAS,WAAW,UAAU,EAAE,QAAQ,OAAO,GAAG;AAC5E,cAAM,mBAAmB,YAAY,YAAY,SAAS,QAAQ,YAAY;AAAA,MAChF,WAAW,cAAc;AAGvB,cAAM,gBAAgB,MAAM,gBAAAK,QAAQ,SAAS,YAAY,OAAO;AAChE,cAAM,gBAAgB,MAAM,gBAAAA,QAAQ,SAAS,YAAY,OAAO;AAEhE,YAAI,kBAAkB,eAAe;AACnC,gBAAM,eAAe,YAAAL,QAAK,SAAS,WAAW,UAAU,EAAE,QAAQ,OAAO,GAAG;AAC5E,kBAAQ,KAAK,EAAE,MAAM,cAAc,QAAQ,UAAU,CAAC;AACtD,cAAI,CAAC,QAAQ;AACX,kBAAM,gBAAAK,QAAQ,KAAK,YAAY,YAAY,EAAE,WAAW,KAAK,CAAC;AAAA,UAChE;AAAA,QACF;AAAA,MACF,OAAO;AAEL,cAAM,eAAe,YAAAL,QAAK,SAAS,WAAW,UAAU;AACxD,gBAAQ,KAAK,EAAE,MAAM,cAAc,QAAQ,UAAU,CAAC;AACtD,YAAI,CAAC,QAAQ;AACX,gBAAM,gBAAAK,QAAQ,KAAK,YAAY,UAAU;AAAA,QAC3C;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEA,eAAe,mBACb,WACA,WACA,SACA,SAAkB,OAClB,aACA;AAEA,MAAI,CAAC,aAAa;AAChB,kBAAc;AAAA,EAChB;AAEA,QAAM,gBAAAA,QAAQ,UAAU,SAAS;AACjC,QAAM,UAAU,MAAM,gBAAAA,QAAQ,QAAQ,WAAW,EAAE,eAAe,KAAK,CAAC;AAExE,aAAW,SAAS,SAAS;AAC3B,UAAM,aAAa,YAAAL,QAAK,KAAK,WAAW,MAAM,IAAI;AAClD,UAAM,aAAa,YAAAA,QAAK,KAAK,WAAW,MAAM,IAAI;AAClD,UAAM,qBAAqB,YAAAA,QAAK,SAAS,aAAa,UAAU,EAAE,QAAQ,OAAO,GAAG;AAKpF,QAAI,uBAAuB,sBAAsB,uBAAuB,sBAAsB;AAC5F;AAAA,IACF;AAGA,QAAI,mBAAmB,WAAW,aAAa,GAAG;AAChD;AAAA,IACF;AAEA,QAAI,MAAM,YAAY,GAAG;AACvB,YAAM,mBAAmB,YAAY,YAAY,SAAS,QAAQ,WAAW;AAAA,IAC/E,OAAO;AACL,YAAM,eAAe,MAAM,gBAAAK,QAAQ,WAAW,UAAU;AAGxD,UAAI,MAAM,SAAS,gBAAgB,cAAc;AAC/C,cAAM,eAAe,YAAAL,QAAK,SAAS,WAAW,UAAU,EAAE,QAAQ,OAAO,GAAG;AAC5E,cAAM,mBAAmB,YAAY,YAAY,SAAS,QAAQ,YAAY;AAAA,MAChF,WAAW,cAAc;AAGvB,cAAM,qBAAqB,YAAAA,QAAK,SAAS,WAAW,UAAU,EAAE,QAAQ,OAAO,GAAG;AAGlF,cAAM,gBAAgB,MAAM,gBAAAK,QAAQ,SAAS,YAAY,OAAO;AAChE,cAAM,gBAAgB,MAAM,gBAAAA,QAAQ,SAAS,YAAY,OAAO;AAEhE,YAAI,kBAAkB,eAAe;AACnC,kBAAQ,KAAK,EAAE,MAAM,oBAAoB,QAAQ,UAAU,CAAC;AAC5D,cAAI,CAAC,QAAQ;AACX,kBAAM,gBAAAA,QAAQ,KAAK,YAAY,YAAY,EAAE,WAAW,KAAK,CAAC;AAAA,UAChE;AAAA,QACF;AAAA,MACF,OAAO;AAEL,cAAM,qBAAqB,YAAAL,QAAK,SAAS,WAAW,UAAU,EAAE,QAAQ,OAAO,GAAG;AAClF,gBAAQ,KAAK,EAAE,MAAM,oBAAoB,QAAQ,UAAU,CAAC;AAC5D,YAAI,CAAC,QAAQ;AACX,gBAAM,gBAAAK,QAAQ,KAAK,YAAY,UAAU;AAAA,QAC3C;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEA,eAAe,yBACb,WACA,WACA,SACA,SAAkB,OAClB;AAEA,QAAM,YAAY;AAAA,IAChB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,QAAM,gBAAAA,QAAQ,UAAU,SAAS;AACjC,QAAM,UAAU,MAAM,gBAAAA,QAAQ,QAAQ,WAAW,EAAE,eAAe,KAAK,CAAC;AAExE,aAAW,SAAS,SAAS;AAC3B,UAAM,aAAa,YAAAL,QAAK,KAAK,WAAW,MAAM,IAAI;AAClD,UAAM,aAAa,YAAAA,QAAK,KAAK,WAAW,MAAM,IAAI;AAGlD,QAAI,UAAU,SAAS,MAAM,IAAI,GAAG;AAClC;AAAA,IACF;AAEA,QAAI,MAAM,YAAY,GAAG;AACvB,YAAM,yBAAyB,YAAY,YAAY,SAAS,MAAM;AAAA,IACxE,OAAO;AACL,YAAM,eAAe,MAAM,gBAAAK,QAAQ,WAAW,UAAU;AAGxD,UAAI,MAAM,SAAS,gBAAgB,cAAc;AAC/C,cAAM,eAAe,YAAAL,QAAK,SAAS,WAAW,UAAU,EAAE,QAAQ,OAAO,GAAG;AAC5E,cAAM,mBAAmB,YAAY,YAAY,SAAS,QAAQ,YAAY;AAAA,MAChF,WAAW,CAAC,cAAc;AAExB,cAAM,eAAe,YAAAA,QAAK,SAAS,WAAW,UAAU;AACxD,gBAAQ,KAAK,EAAE,MAAM,cAAc,QAAQ,UAAU,CAAC;AACtD,YAAI,CAAC,QAAQ;AACX,gBAAM,gBAAAK,QAAQ,KAAK,YAAY,UAAU;AAAA,QAC3C;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEA,eAAe,cACb,YACA,YACA,SACA,SAAkB,OAClB;AACA,QAAM,aAAa,KAAK,MAAM,MAAM,gBAAAA,QAAQ,SAAS,YAAY,OAAO,CAAC;AACzE,QAAM,aAAa,KAAK,MAAM,MAAM,gBAAAA,QAAQ,SAAS,YAAY,OAAO,CAAC;AAGzE,QAAM,mBAAmB;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAGA,QAAM,SAAS,EAAE,GAAG,WAAW;AAE/B,MAAI,WAAW,cAAc;AAC3B,WAAO,eAAe,EAAE,GAAG,WAAW,cAAc,GAAG,WAAW,aAAa;AAAA,EACjF;AACA,MAAI,WAAW,iBAAiB;AAC9B,WAAO,kBAAkB,EAAE,GAAG,WAAW,iBAAiB,GAAG,WAAW,gBAAgB;AAAA,EAC1F;AAEA,MAAI,WAAW,SAAS;AAEtB,WAAO,UAAU,EAAE,GAAG,WAAW,QAAQ;AAGzC,eAAW,cAAc,kBAAkB;AACzC,UAAI,WAAW,QAAQ,UAAU,GAAG;AAClC,eAAO,QAAQ,UAAU,IAAI,WAAW,QAAQ,UAAU;AAAA,MAC5D;AAAA,IACF;AAGA,eAAW,CAAC,YAAY,WAAW,KAAK,OAAO,QAAQ,WAAW,OAAO,GAAG;AAC1E,UAAI,CAAC,OAAO,QAAQ,UAAU,GAAG;AAC/B,eAAO,QAAQ,UAAU,IAAI;AAAA,MAC/B;AAAA,IACF;AAAA,EACF;AAGA,MAAI,WAAW,SAAS;AACtB,WAAO,UAAU,WAAW;AAAA,EAC9B;AACA,MAAI,WAAW,MAAM;AACnB,WAAO,OAAO,WAAW;AAAA,EAC3B;AAGA,QAAM,aAAa,KAAK,UAAU,MAAM,MAAM,KAAK,UAAU,UAAU;AAEvE,MAAI,YAAY;AACd,YAAQ,KAAK,EAAE,MAAM,YAAAL,QAAK,SAAS,UAAU,GAAG,QAAQ,UAAU,CAAC;AACnE,QAAI,CAAC,QAAQ;AACX,YAAM,gBAAAK,QAAQ,UAAU,YAAY,KAAK,UAAU,QAAQ,MAAM,CAAC,IAAI,MAAM,OAAO;AAAA,IACrF;AAAA,EACF;AACF;AAEA,eAAe,mBACb,YACA,YACA,SACA,SAAkB,OAClB,cACA;AACA,QAAM,gBAAgB,MAAM,gBAAAA,QAAQ,SAAS,YAAY,OAAO;AAChE,QAAM,gBAAgB,MAAM,gBAAAA,QAAQ,SAAS,YAAY,OAAO;AAGhE,QAAM,cAAc,cAAc,MAAM,IAAI,EAAE,IAAI,UAAQ,KAAK,KAAK,CAAC,EAAE,OAAO,UAAQ,QAAQ,CAAC,KAAK,WAAW,GAAG,CAAC;AACnH,QAAM,cAAc,cAAc,MAAM,IAAI,EAAE,IAAI,UAAQ,KAAK,KAAK,CAAC,EAAE,OAAO,UAAQ,QAAQ,CAAC,KAAK,WAAW,GAAG,CAAC;AAGnH,QAAM,kBAAkB,IAAI,IAAI,WAAW;AAG3C,QAAM,aAAa,YAAY,OAAO,UAAQ,CAAC,gBAAgB,IAAI,IAAI,CAAC;AAExE,MAAI,WAAW,SAAS,GAAG;AACzB,UAAM,cAAc,gBAAgB,YAAAL,QAAK,SAAS,UAAU;AAC5D,YAAQ,KAAK,EAAE,MAAM,aAAa,QAAQ,UAAU,CAAC;AACrD,QAAI,CAAC,QAAQ;AAEX,YAAM,iBAAiB,iBAAiB,cAAc,SAAS,IAAI,IAAI,KAAK,SACzE,WAAW,SAAS,IAAI,oCAAoC,WAAW,KAAK,IAAI,IAAI,OAAO;AAC9F,YAAM,gBAAAK,QAAQ,UAAU,YAAY,gBAAgB,OAAO;AAAA,IAC7D;AAAA,EACF;AACF;AAGA,KAAK,EAAE,MAAM,CAAC,UAAU;AACtB,UAAQ,MAAY,UAAI,mBAAmB,CAAC;AAC5C,UAAQ,MAAM,KAAK;AACnB,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":["path","fs","prompts","validation","validateProjectName","fsExtra"]}
|
|
1
|
+
{"version":3,"sources":["../../../node_modules/.pnpm/tsup@8.5.1_@swc+core@1.15.8_4981d554a2757e4ed25c3ad5a773b01f/node_modules/tsup/assets/cjs_shims.js","../src/index.ts"],"sourcesContent":["// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () => \n typeof document === \"undefined\" \n ? new URL(`file:${__filename}`).href \n : (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT') \n ? document.currentScript.src \n : new URL(\"main.js\", document.baseURI).href;\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n","#!/usr/bin/env node\n\n/**\n * create-lego-one CLI\n * Create a new Lego-One SaaS application from template\n */\n\nimport path from 'path';\nimport fs from 'fs';\nimport { fileURLToPath } from 'url';\nimport { program } from 'commander';\nimport * as kleur from 'kleur';\nimport fsExtra from 'fs-extra';\nimport validateProjectName from 'validate-npm-package-name';\nimport prompts from 'prompts';\nimport { execSync } from 'child_process';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\ninterface CreateOptions {\n appName?: string;\n interactive: boolean;\n git: boolean;\n branch?: string;\n template?: string;\n description?: string;\n author?: string;\n}\n\ninterface ProjectConfig {\n appName: string;\n description: string;\n author: string;\n git: boolean;\n}\n\n// ASCII art banner\nconst banner = `\n${kleur.cyan('┌─────────────────────────────────────────────────────────┐')}\n${kleur.cyan('│')} ${kleur.bold().white('Lego-One')} SaaS Boilerplate ${kleur.cyan('│')}\n${kleur.cyan('│')} ${kleur.gray('Microkernel architecture with Modern.js + Garfish')} ${kleur.cyan('│')}\n${kleur.cyan('└─────────────────────────────────────────────────────────┘')}\n`;\n\nasync function init() {\n console.log(banner);\n\n const version = getVersion();\n console.log(kleur.gray(`create-lego-one v${version}\\n`));\n\n program\n .name('create-lego-one')\n .description('Create a new Lego-One SaaS application')\n .version(version)\n .argument('[app-name]', 'Name of your application')\n .option('-i, --interactive', 'Use interactive prompts (default when no app name provided)')\n .option('--no-interactive', 'Skip interactive prompts')\n .option('--git', 'Initialize git repository', true)\n .option('--no-git', 'Skip git initialization')\n .option('--description <description>', 'Project description')\n .option('--author <author>', 'Author name')\n .option('--branch <branch>', 'Use specific branch')\n .option('--template <template>', 'Use specific template')\n .action(async (appName: string | undefined, options) => {\n // Default to interactive mode if no app name provided\n const isInteractive = options.interactive !== false && (!appName || options.interactive === true);\n \n const opts: CreateOptions = {\n appName,\n interactive: isInteractive,\n git: options.git !== false,\n branch: options.branch,\n template: options.template,\n description: options.description,\n author: options.author,\n };\n await createApp(opts);\n });\n\n // Add update command\n program\n .command('update')\n .description('Update existing Lego-One project with latest framework changes')\n .option('--dry-run', 'Show what would be updated without making changes')\n .option('--force', 'Force update even if conflicts detected')\n .action(async (options) => {\n await updateProject(process.cwd(), options);\n });\n\n await program.parseAsync(process.argv);\n}\n\nfunction getVersion(): string {\n const pkgPath = path.join(__dirname, '../package.json');\n try {\n const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));\n return pkg.version || '1.0.0';\n } catch {\n return '1.0.0';\n }\n}\n\nasync function createApp(options: CreateOptions) {\n try {\n let projectConfig: ProjectConfig;\n\n // Interactive mode - collect all project information\n if (options.interactive) {\n const responses = await prompts([\n {\n type: 'text',\n name: 'appName',\n message: 'What is your app named?',\n initial: options.appName || 'my-saas-app',\n validate: (value: string) => {\n if (!value) return 'App name is required';\n const validation = validateProjectName(value);\n if (validation.validForNewPackages) return true;\n const errorMsg = validation.errors?.join(', ') || validation.warnings?.join(', ') || 'unknown error';\n return 'Invalid app name: ' + errorMsg;\n },\n },\n {\n type: 'text',\n name: 'description',\n message: 'Project description (optional)',\n initial: options.description || 'A SaaS application built with Lego-One',\n },\n {\n type: 'text',\n name: 'author',\n message: 'Author name (optional)',\n initial: options.author || '',\n },\n {\n type: 'confirm',\n name: 'git',\n message: 'Initialize git repository?',\n initial: options.git !== false,\n },\n ]);\n\n projectConfig = {\n appName: responses.appName || options.appName || 'my-saas-app',\n description: responses.description || options.description || 'A SaaS application built with Lego-One',\n author: responses.author || options.author || '',\n git: responses.git !== false,\n };\n } else {\n // Non-interactive mode - use provided values or defaults\n if (!options.appName) {\n console.error(kleur.red('Please specify an app name or use interactive mode (default)'));\n process.exit(1);\n }\n\n projectConfig = {\n appName: options.appName,\n description: options.description || 'A SaaS application built with Lego-One',\n author: options.author || '',\n git: options.git !== false,\n };\n }\n\n // Validate app name\n const validation = validateProjectName(projectConfig.appName);\n if (!validation.validForNewPackages) {\n const errorMsg = validation.errors?.join(', ') || validation.warnings?.join(', ') || 'unknown error';\n console.error(kleur.red(`Invalid app name: ${errorMsg}`));\n if (validation.warnings && validation.warnings.length > 0) {\n console.error(kleur.yellow(`Warnings: ${validation.warnings.join(', ')}`));\n }\n process.exit(1);\n }\n\n const appName = projectConfig.appName;\n\n const targetDir = path.resolve(process.cwd(), appName);\n\n // Check if directory exists\n if (await fsExtra.pathExists(targetDir)) {\n const files = await fsExtra.readdir(targetDir);\n if (files.length > 0) {\n console.error(kleur.red(`Directory \"${appName}\" already exists and is not empty`));\n process.exit(1);\n }\n }\n\n console.log(kleur.cyan('Creating your Lego-One application...'));\n\n // Create target directory\n await fsExtra.ensureDir(targetDir);\n\n // Copy template files\n console.log(kleur.gray('Copying template files...'));\n const templateDir = path.join(__dirname, '../template');\n if (await fsExtra.pathExists(templateDir)) {\n await fsExtra.copy(templateDir, targetDir);\n // Rename .gitignore.template files back to .gitignore (npm excludes .gitignore by default)\n await restoreGitignoreFiles(targetDir);\n } else {\n // Fallback: copy from root (development mode)\n const rootDir = path.join(__dirname, '../../..');\n await copyTemplateFiles(rootDir, targetDir);\n }\n\n // Update package.json with project configuration\n console.log(kleur.gray('Configuring project...'));\n await updatePackageJson(targetDir, projectConfig);\n\n // Update README.md with project-specific information\n await updateReadme(targetDir, projectConfig);\n\n // Ensure PocketBase directory structure exists\n await ensurePocketBaseStructure(targetDir);\n\n // Initialize git if requested\n if (projectConfig.git) {\n console.log(kleur.gray('Initializing git repository...'));\n try {\n execSync('git init', { cwd: targetDir });\n execSync('git add .', { cwd: targetDir });\n execSync('git commit -m \"Initial commit from Lego-One\"', {\n cwd: targetDir,\n });\n } catch (error) {\n console.warn(kleur.yellow('Could not initialize git repository'));\n }\n }\n\n console.log(kleur.green('Application created successfully!'));\n\n // Print success message\n console.log('\\n' + kleur.bold('Next steps:'));\n console.log(` ${kleur.cyan('cd')} ${appName}`);\n console.log(` ${kleur.cyan('pnpm install')}`);\n console.log(` ${kleur.cyan('pnpm run dev')}`);\n console.log('\\n' + kleur.gray('To start developing:'));\n console.log(` ${kleur.cyan('pnpm run dev')} - Start development server`);\n console.log(` ${kleur.cyan('pnpm run dev:all')} - Start all services`);\n console.log('\\n' + kleur.gray('To build for production:'));\n console.log(` ${kleur.cyan('pnpm run build')} - Build application`);\n console.log(` ${kleur.cyan('pnpm run start')} - Start production server`);\n console.log('\\n' + kleur.gray('Documentation:'));\n console.log(` ${kleur.cyan('https://lego-one.dev/docs')} - Full documentation`);\n console.log('\\n' + kleur.yellow('Happy coding! 🚀\\n'));\n } catch (error) {\n console.error(kleur.red('Failed to create application'));\n if (error instanceof Error) {\n console.error(kleur.red(error.message));\n }\n process.exit(1);\n }\n}\n\nasync function updatePackageJson(targetDir: string, config: ProjectConfig) {\n const packageJsonPath = path.join(targetDir, 'package.json');\n \n if (await fsExtra.pathExists(packageJsonPath)) {\n const packageJson = JSON.parse(await fsExtra.readFile(packageJsonPath, 'utf-8'));\n \n // Update package name\n packageJson.name = config.appName;\n \n // Update description if provided\n if (config.description) {\n packageJson.description = config.description;\n }\n \n // Update author if provided\n if (config.author) {\n packageJson.author = config.author;\n }\n \n // Write updated package.json\n await fsExtra.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\\n', 'utf-8');\n }\n}\n\nasync function updateReadme(targetDir: string, config: ProjectConfig) {\n const readmePath = path.join(targetDir, 'README.md');\n \n if (await fsExtra.pathExists(readmePath)) {\n let readmeContent = await fsExtra.readFile(readmePath, 'utf-8');\n \n // Replace placeholders\n readmeContent = readmeContent.replace(/{APP_NAME}/g, config.appName);\n readmeContent = readmeContent.replace(/{DESCRIPTION}/g, config.description);\n \n // Write updated README\n await fsExtra.writeFile(readmePath, readmeContent, 'utf-8');\n }\n}\n\nasync function ensurePocketBaseStructure(targetDir: string) {\n const pocketbaseDir = path.join(targetDir, 'pocketbase');\n \n // Ensure directory exists\n await fsExtra.ensureDir(pocketbaseDir);\n \n // Create .gitignore for PocketBase data\n const gitignorePath = path.join(pocketbaseDir, '.gitignore');\n const gitignoreContent = `# PocketBase data files (do not commit)\npb_data/\n*.db\n*.db-shm\n*.db-wal\nlogs.db\nlogs.db-shm\nlogs.db-wal\n\n# PocketBase executable (users download separately)\npocketbase\npocketbase.exe\npocketbase.exe.gz\n*.zip\n`;\n \n if (!(await fsExtra.pathExists(gitignorePath))) {\n await fsExtra.writeFile(gitignorePath, gitignoreContent, 'utf-8');\n }\n}\n\nasync function restoreGitignoreFiles(targetDir: string) {\n // Find all .gitignore.template files and rename them back to .gitignore\n async function findAndRestore(dir: string, baseDir: string = dir) {\n const entries = await fsExtra.readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n\n if (entry.isDirectory()) {\n await findAndRestore(fullPath, baseDir);\n } else if (entry.name === '.gitignore.template') {\n const gitignorePath = path.join(dir, '.gitignore');\n // Only rename if .gitignore doesn't already exist\n if (!(await fsExtra.pathExists(gitignorePath))) {\n await fsExtra.move(fullPath, gitignorePath, { overwrite: false });\n const relativePath = path.relative(baseDir, gitignorePath);\n console.log(kleur.gray(` ✓ Restored ${relativePath}`));\n } else {\n // If .gitignore exists, remove the template file\n await fsExtra.remove(fullPath);\n }\n }\n }\n }\n\n await findAndRestore(targetDir);\n}\n\nasync function copyTemplateFiles(sourceDir: string, targetDir: string) {\n // Files to copy\n const filesToCopy = [\n 'host',\n 'packages/plugins',\n 'package.json',\n 'pnpm-lock.yaml',\n 'pnpm-workspace.yaml',\n '.gitignore',\n 'tsconfig.json',\n ];\n\n // Files to exclude\n const excludePatterns = [\n 'node_modules',\n 'dist',\n '.next',\n 'coverage',\n '.cache',\n '*.log',\n ];\n\n for (const file of filesToCopy) {\n const srcPath = path.join(sourceDir, file);\n const destPath = path.join(targetDir, file);\n\n if (await fsExtra.pathExists(srcPath)) {\n await fsExtra.copy(srcPath, destPath, {\n filter: (src) => {\n const relativePath = path.relative(sourceDir, src);\n return !excludePatterns.some((pattern) =>\n relativePath.includes(pattern)\n );\n },\n });\n }\n }\n}\n\nasync function updateProject(projectDir: string, options: { dryRun?: boolean; force?: boolean }) {\n try {\n console.log(kleur.cyan('🔄 Updating Lego-One project...\\n'));\n\n // Check if this is a lego-one project\n const packageJsonPath = path.join(projectDir, 'package.json');\n if (!(await fsExtra.pathExists(packageJsonPath))) {\n console.error(kleur.red('❌ This does not appear to be a Lego-One project (package.json not found)'));\n process.exit(1);\n }\n\n const packageJson = JSON.parse(await fsExtra.readFile(packageJsonPath, 'utf-8'));\n const isLegoOneProject = \n packageJson.name === 'lego-one' || \n packageJson.description?.includes('Lego-One') ||\n await fsExtra.pathExists(path.join(projectDir, 'host')) ||\n await fsExtra.pathExists(path.join(projectDir, 'packages/plugins/@lego'));\n\n if (!isLegoOneProject) {\n console.error(kleur.red('❌ This does not appear to be a Lego-One project'));\n process.exit(1);\n }\n\n // Get template directory from installed package\n const templateDir = path.join(__dirname, '../template');\n \n if (!(await fsExtra.pathExists(templateDir))) {\n console.error(kleur.red('❌ Template directory not found. Please reinstall create-lego-one.'));\n process.exit(1);\n }\n\n if (options.dryRun) {\n console.log(kleur.yellow('🔍 Dry run mode - no changes will be made\\n'));\n }\n\n // Files/directories to update (must match sync-template.js filesToSync)\n const updateItems = [\n // Core application structure\n { source: 'host', target: 'host', strategy: 'merge-host' }, // Merge host updates (skip user configs)\n { source: 'packages/plugins/@lego', target: 'packages/plugins/@lego', strategy: 'merge' }, // Add new plugins only\n \n // Root files\n { source: 'README.md', target: 'README.md', strategy: 'add-if-missing' }, // Add if doesn't exist (user-specific)\n \n // Configuration files\n { source: 'package.json', target: 'package.json', strategy: 'merge-json' }, // Merge package.json\n { source: 'pnpm-workspace.yaml', target: 'pnpm-workspace.yaml', strategy: 'replace' }, // Replace workspace config\n { source: '.gitignore', target: '.gitignore', strategy: 'merge-gitignore' }, // Merge gitignore intelligently\n { source: 'tsconfig.json', target: 'tsconfig.json', strategy: 'merge-json' }, // Merge tsconfig\n { source: 'tsconfig.base.json', target: 'tsconfig.base.json', strategy: 'replace' }, // Replace base config\n { source: '.eslintrc.js', target: '.eslintrc.js', strategy: 'replace' }, // Replace ESLint config\n { source: '.eslintignore', target: '.eslintignore', strategy: 'replace' }, // Replace ESLint ignore\n { source: '.prettierrc', target: '.prettierrc', strategy: 'replace' }, // Replace Prettier config\n { source: '.prettierignore', target: '.prettierignore', strategy: 'replace' }, // Replace Prettier ignore\n \n // Environment files (examples - users create their own from these)\n { source: '.env.example', target: '.env.example', strategy: 'replace' }, // Replace example file\n { source: '.env.development', target: '.env.development', strategy: 'add-if-missing' }, // Add if doesn't exist\n { source: '.env.production', target: '.env.production', strategy: 'add-if-missing' }, // Add if doesn't exist\n \n // Deployment files\n { source: 'Dockerfile', target: 'Dockerfile', strategy: 'replace' }, // Replace Dockerfile\n { source: 'docker-compose.yml', target: 'docker-compose.yml', strategy: 'replace' }, // Replace docker-compose\n { source: 'docker-entrypoint.sh', target: 'docker-entrypoint.sh', strategy: 'replace' }, // Replace entrypoint\n { source: 'nginx.conf', target: 'nginx.conf', strategy: 'replace' }, // Replace nginx config\n { source: '.dockerignore', target: '.dockerignore', strategy: 'replace' }, // Replace dockerignore\n \n // Documentation (for AI-assisted development)\n { source: 'docs', target: 'docs', strategy: 'merge' }, // Merge docs (add new, don't overwrite user docs)\n { source: 'CLAUDE.md', target: 'CLAUDE.md', strategy: 'add-if-missing' }, // Add if doesn't exist\n { source: 'PROMPT.md', target: 'PROMPT.md', strategy: 'add-if-missing' }, // Add if doesn't exist\n { source: '.cursor', target: '.cursor', strategy: 'merge' }, // Merge cursor rules\n \n // Scripts (for users to create plugins)\n { source: 'scripts', target: 'scripts', strategy: 'merge' }, // Merge scripts (add new, don't overwrite user scripts)\n \n // PocketBase structure\n { source: 'pocketbase', target: 'pocketbase', strategy: 'merge-pocketbase' }, // Merge PocketBase (skip executable)\n ];\n\n const updates: Array<{ file: string; action: string }> = [];\n\n for (const item of updateItems) {\n const sourcePath = path.join(templateDir, item.source);\n const targetPath = path.join(projectDir, item.target);\n\n if (!(await fsExtra.pathExists(sourcePath))) {\n continue;\n }\n\n const sourceStat = await fsExtra.stat(sourcePath);\n const targetExists = await fsExtra.pathExists(targetPath);\n\n if (item.strategy === 'skip') {\n continue;\n }\n\n if (item.strategy === 'merge' || item.strategy === 'merge-host' || item.strategy === 'merge-pocketbase') {\n // For directories, add new files only (don't overwrite existing)\n if (sourceStat.isDirectory()) {\n if (item.strategy === 'merge-host') {\n await mergeHostDirectory(sourcePath, targetPath, updates, options.dryRun);\n } else if (item.strategy === 'merge-pocketbase') {\n await mergePocketBaseDirectory(sourcePath, targetPath, updates, options.dryRun);\n } else {\n await mergeDirectory(sourcePath, targetPath, updates, options.dryRun);\n }\n }\n } else if (item.strategy === 'merge-gitignore') {\n // Merge .gitignore intelligently (combine entries, avoid duplicates)\n // Check if source has .gitignore.template (npm excludes .gitignore by default)\n const sourceGitignorePath = await fsExtra.pathExists(sourcePath + '.template') \n ? sourcePath + '.template' \n : (await fsExtra.pathExists(sourcePath) ? sourcePath : null);\n \n if (sourceGitignorePath) {\n if (targetExists) {\n // Merge existing .gitignore with template\n await mergeGitignoreFile(sourceGitignorePath, targetPath, updates, options.dryRun, item.target);\n } else {\n // Create new .gitignore from template\n updates.push({ file: item.target, action: 'created' });\n if (!options.dryRun) {\n // Copy from .gitignore.template if it exists, otherwise from .gitignore\n await fsExtra.copy(sourceGitignorePath, targetPath);\n }\n }\n } else {\n console.warn(kleur.yellow(` ⚠ Skipping ${item.target} (source not found)`));\n }\n } else if (item.strategy === 'add-if-missing') {\n // Only add if file doesn't exist\n if (!targetExists) {\n updates.push({ file: item.target, action: 'created' });\n if (!options.dryRun) {\n await fsExtra.copy(sourcePath, targetPath);\n }\n }\n } else if (item.strategy === 'merge-json') {\n if (targetExists) {\n await mergeJsonFile(sourcePath, targetPath, updates, options.dryRun);\n } else {\n updates.push({ file: item.target, action: 'created' });\n if (!options.dryRun) {\n await fsExtra.copy(sourcePath, targetPath);\n }\n }\n } else if (item.strategy === 'replace') {\n updates.push({ file: item.target, action: 'updated' });\n if (!options.dryRun) {\n await fsExtra.copy(sourcePath, targetPath, { overwrite: true });\n }\n }\n }\n\n // Summary\n console.log('\\n' + kleur.bold('Update Summary:'));\n if (updates.length === 0) {\n console.log(kleur.gray(' No updates needed'));\n } else {\n updates.forEach(({ file, action }) => {\n const icon = action === 'created' ? '➕' : action === 'updated' ? '🔄' : '✨';\n console.log(` ${icon} ${file} (${action})`);\n });\n }\n\n // Restore .gitignore files from .gitignore.template (npm excludes .gitignore by default)\n if (!options.dryRun) {\n await restoreGitignoreFiles(projectDir);\n }\n\n if (options.dryRun) {\n console.log('\\n' + kleur.yellow('💡 Run without --dry-run to apply updates'));\n } else {\n console.log('\\n' + kleur.green('✅ Project updated successfully!'));\n console.log(kleur.gray('\\nNext steps:'));\n console.log(` ${kleur.cyan('pnpm install')} - Install/update dependencies`);\n console.log(` ${kleur.cyan('pnpm run dev')} - Start development server`);\n }\n } catch (error) {\n console.error(kleur.red('❌ Failed to update project'));\n if (error instanceof Error) {\n console.error(kleur.red(error.message));\n }\n process.exit(1);\n }\n}\n\nasync function mergeDirectory(\n sourceDir: string,\n targetDir: string,\n updates: Array<{ file: string; action: string }>,\n dryRun: boolean = false\n) {\n await fsExtra.ensureDir(targetDir);\n const entries = await fsExtra.readdir(sourceDir, { withFileTypes: true });\n\n for (const entry of entries) {\n const sourcePath = path.join(sourceDir, entry.name);\n const targetPath = path.join(targetDir, entry.name);\n\n if (entry.isDirectory()) {\n await mergeDirectory(sourcePath, targetPath, updates, dryRun);\n } else {\n const targetExists = await fsExtra.pathExists(targetPath);\n \n // Special handling for .gitignore files - check for .gitignore.template first (npm excludes .gitignore)\n if (entry.name === '.gitignore' || entry.name === '.gitignore.template') {\n const gitignoreTargetPath = path.join(targetDir, '.gitignore');\n const gitignoreTargetExists = await fsExtra.pathExists(gitignoreTargetPath);\n \n // Check if source is .gitignore.template (what npm includes)\n const actualSourcePath = entry.name === '.gitignore.template' ? sourcePath : sourcePath;\n \n if (gitignoreTargetExists) {\n // Merge existing .gitignore with template\n const relativePath = path.relative(targetDir, gitignoreTargetPath).replace(/\\\\/g, '/');\n await mergeGitignoreFile(actualSourcePath, gitignoreTargetPath, updates, dryRun, relativePath);\n } else {\n // Create new .gitignore from template\n const relativePath = path.relative(targetDir, gitignoreTargetPath);\n updates.push({ file: relativePath, action: 'created' });\n if (!dryRun) {\n await fsExtra.copy(actualSourcePath, gitignoreTargetPath);\n }\n }\n // Skip the original entry since we've handled it as .gitignore\n continue;\n } else if (targetExists) {\n // For built-in plugins, update existing files to get framework updates\n // Check if file has changed by comparing content\n const sourceContent = await fsExtra.readFile(sourcePath, 'utf-8');\n const targetContent = await fsExtra.readFile(targetPath, 'utf-8');\n \n if (sourceContent !== targetContent) {\n const relativePath = path.relative(targetDir, targetPath).replace(/\\\\/g, '/');\n updates.push({ file: relativePath, action: 'updated' });\n if (!dryRun) {\n await fsExtra.copy(sourcePath, targetPath, { overwrite: true });\n }\n }\n } else {\n // Only add files that don't exist (new plugins, new kernel files)\n const relativePath = path.relative(targetDir, targetPath);\n updates.push({ file: relativePath, action: 'created' });\n if (!dryRun) {\n await fsExtra.copy(sourcePath, targetPath);\n }\n }\n }\n }\n}\n\nasync function mergeHostDirectory(\n sourceDir: string,\n targetDir: string,\n updates: Array<{ file: string; action: string }>,\n dryRun: boolean = false,\n hostRootDir?: string\n) {\n // Track the host root directory for relative path calculation\n if (!hostRootDir) {\n hostRootDir = sourceDir;\n }\n\n await fsExtra.ensureDir(targetDir);\n const entries = await fsExtra.readdir(sourceDir, { withFileTypes: true });\n\n for (const entry of entries) {\n const sourcePath = path.join(sourceDir, entry.name);\n const targetPath = path.join(targetDir, entry.name);\n const relativeToHostRoot = path.relative(hostRootDir, sourcePath).replace(/\\\\/g, '/');\n\n // Skip user-specific config files\n // modern.config.ts is at host root\n // saas.config.ts is at host/src/\n if (relativeToHostRoot === 'modern.config.ts' || relativeToHostRoot === 'src/saas.config.ts') {\n continue;\n }\n\n // Skip user routes - these are user-customizable\n if (relativeToHostRoot.startsWith('src/routes/')) {\n continue;\n }\n\n if (entry.isDirectory()) {\n await mergeHostDirectory(sourcePath, targetPath, updates, dryRun, hostRootDir);\n } else {\n const targetExists = await fsExtra.pathExists(targetPath);\n \n // Special handling for .gitignore files - check for .gitignore.template first (npm excludes .gitignore)\n if (entry.name === '.gitignore' || entry.name === '.gitignore.template') {\n const gitignoreTargetPath = path.join(targetDir, '.gitignore');\n const gitignoreTargetExists = await fsExtra.pathExists(gitignoreTargetPath);\n \n // Check if source is .gitignore.template (what npm includes)\n const actualSourcePath = entry.name === '.gitignore.template' ? sourcePath : sourcePath;\n \n if (gitignoreTargetExists) {\n // Merge existing .gitignore with template\n const relativeTargetPath = path.relative(targetDir, gitignoreTargetPath).replace(/\\\\/g, '/');\n await mergeGitignoreFile(actualSourcePath, gitignoreTargetPath, updates, dryRun, relativeTargetPath);\n } else {\n // Create new .gitignore from template\n const relativeTargetPath = path.relative(targetDir, gitignoreTargetPath).replace(/\\\\/g, '/');\n updates.push({ file: relativeTargetPath, action: 'created' });\n if (!dryRun) {\n await fsExtra.copy(actualSourcePath, gitignoreTargetPath);\n }\n }\n // Skip the original entry since we've handled it as .gitignore\n continue;\n } else if (targetExists) {\n // For existing kernel files, update them from framework\n // This ensures users get bug fixes and improvements to kernel files\n const relativeTargetPath = path.relative(targetDir, targetPath).replace(/\\\\/g, '/');\n \n // Check if file has changed by comparing content\n const sourceContent = await fsExtra.readFile(sourcePath, 'utf-8');\n const targetContent = await fsExtra.readFile(targetPath, 'utf-8');\n \n if (sourceContent !== targetContent) {\n updates.push({ file: relativeTargetPath, action: 'updated' });\n if (!dryRun) {\n await fsExtra.copy(sourcePath, targetPath, { overwrite: true });\n }\n }\n } else {\n // Only add files that don't exist\n const relativeTargetPath = path.relative(targetDir, targetPath).replace(/\\\\/g, '/');\n updates.push({ file: relativeTargetPath, action: 'created' });\n if (!dryRun) {\n await fsExtra.copy(sourcePath, targetPath);\n }\n }\n }\n }\n}\n\nasync function mergePocketBaseDirectory(\n sourceDir: string,\n targetDir: string,\n updates: Array<{ file: string; action: string }>,\n dryRun: boolean = false\n) {\n // Files to skip (executable and data)\n const skipFiles = [\n 'pocketbase.exe',\n 'pocketbase',\n 'pocketbase.exe.gz',\n 'pb_data',\n ];\n\n await fsExtra.ensureDir(targetDir);\n const entries = await fsExtra.readdir(sourceDir, { withFileTypes: true });\n\n for (const entry of entries) {\n const sourcePath = path.join(sourceDir, entry.name);\n const targetPath = path.join(targetDir, entry.name);\n\n // Skip executable and data files\n if (skipFiles.includes(entry.name)) {\n continue;\n }\n\n if (entry.isDirectory()) {\n await mergePocketBaseDirectory(sourcePath, targetPath, updates, dryRun);\n } else {\n const targetExists = await fsExtra.pathExists(targetPath);\n \n // Special handling for .gitignore files - check for .gitignore.template first (npm excludes .gitignore)\n if (entry.name === '.gitignore' || entry.name === '.gitignore.template') {\n const gitignoreTargetPath = path.join(targetDir, '.gitignore');\n const gitignoreTargetExists = await fsExtra.pathExists(gitignoreTargetPath);\n \n // Check if source is .gitignore.template (what npm includes)\n const actualSourcePath = entry.name === '.gitignore.template' ? sourcePath : sourcePath;\n \n if (gitignoreTargetExists) {\n // Merge existing .gitignore with template\n const relativePath = path.relative(targetDir, gitignoreTargetPath).replace(/\\\\/g, '/');\n await mergeGitignoreFile(actualSourcePath, gitignoreTargetPath, updates, dryRun, relativePath);\n } else {\n // Create new .gitignore from template\n const relativePath = path.relative(targetDir, gitignoreTargetPath);\n updates.push({ file: relativePath, action: 'created' });\n if (!dryRun) {\n await fsExtra.copy(actualSourcePath, gitignoreTargetPath);\n }\n }\n // Skip the original entry since we've handled it as .gitignore\n continue;\n } else if (!targetExists) {\n // Only add files that don't exist\n const relativePath = path.relative(targetDir, targetPath);\n updates.push({ file: relativePath, action: 'created' });\n if (!dryRun) {\n await fsExtra.copy(sourcePath, targetPath);\n }\n }\n }\n }\n}\n\nasync function mergeJsonFile(\n sourcePath: string,\n targetPath: string,\n updates: Array<{ file: string; action: string }>,\n dryRun: boolean = false\n) {\n const sourceJson = JSON.parse(await fsExtra.readFile(sourcePath, 'utf-8'));\n const targetJson = JSON.parse(await fsExtra.readFile(targetPath, 'utf-8'));\n\n // Framework script names that should always be updated from framework\n const frameworkScripts = [\n 'dev',\n 'dev:host',\n 'dev:plugins',\n 'dev:all',\n 'build',\n 'lint',\n 'lint:fix',\n 'format',\n 'format:check',\n 'typecheck',\n 'test',\n 'test:e2e',\n 'clean',\n 'create-plugin',\n ];\n\n // Merge dependencies and devDependencies\n const merged = { ...targetJson };\n \n if (sourceJson.dependencies) {\n merged.dependencies = { ...targetJson.dependencies, ...sourceJson.dependencies };\n }\n if (sourceJson.devDependencies) {\n merged.devDependencies = { ...targetJson.devDependencies, ...sourceJson.devDependencies };\n }\n \n if (sourceJson.scripts) {\n // Start with user's scripts (preserves custom scripts)\n merged.scripts = { ...targetJson.scripts };\n \n // Update framework scripts from source (framework takes precedence for these)\n for (const scriptName of frameworkScripts) {\n if (sourceJson.scripts[scriptName]) {\n merged.scripts[scriptName] = sourceJson.scripts[scriptName];\n }\n }\n \n // Add any new scripts from framework that don't exist in user's scripts\n for (const [scriptName, scriptValue] of Object.entries(sourceJson.scripts)) {\n if (!merged.scripts[scriptName]) {\n merged.scripts[scriptName] = scriptValue;\n }\n }\n }\n\n // Merge other top-level fields that should be updated from framework\n if (sourceJson.engines) {\n merged.engines = sourceJson.engines;\n }\n if (sourceJson.type) {\n merged.type = sourceJson.type;\n }\n\n // Check if there are actual changes\n const hasChanges = JSON.stringify(merged) !== JSON.stringify(targetJson);\n \n if (hasChanges) {\n updates.push({ file: path.basename(targetPath), action: 'updated' });\n if (!dryRun) {\n await fsExtra.writeFile(targetPath, JSON.stringify(merged, null, 2) + '\\n', 'utf-8');\n }\n }\n}\n\nasync function mergeGitignoreFile(\n sourcePath: string,\n targetPath: string,\n updates: Array<{ file: string; action: string }>,\n dryRun: boolean = false,\n relativePath?: string\n) {\n const sourceContent = await fsExtra.readFile(sourcePath, 'utf-8');\n const targetContent = await fsExtra.readFile(targetPath, 'utf-8');\n\n // Split into lines and normalize\n const sourceLines = sourceContent.split('\\n').map(line => line.trim()).filter(line => line && !line.startsWith('#'));\n const targetLines = targetContent.split('\\n').map(line => line.trim()).filter(line => line && !line.startsWith('#'));\n\n // Get existing entries as a set\n const existingEntries = new Set(targetLines);\n\n // Find new entries from source\n const newEntries = sourceLines.filter(line => !existingEntries.has(line));\n\n if (newEntries.length > 0) {\n const displayPath = relativePath || path.basename(targetPath);\n updates.push({ file: displayPath, action: 'updated' });\n if (!dryRun) {\n // Append new entries to target file\n const updatedContent = targetContent + (targetContent.endsWith('\\n') ? '' : '\\n') + \n (newEntries.length > 0 ? '\\n# Added by framework update\\n' + newEntries.join('\\n') + '\\n' : '');\n await fsExtra.writeFile(targetPath, updatedContent, 'utf-8');\n }\n }\n}\n\n// Run the CLI\ninit().catch((error) => {\n console.error(kleur.red('Unexpected error:'));\n console.error(error);\n process.exit(1);\n});\n"],"mappings":"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","names":["path","fs","prompts","validation","validateProjectName","fsExtra"]}
|
package/package.json
CHANGED
|
@@ -609,9 +609,9 @@ Stay pragmatic. Stay reliable. Keep learning.
|
|
|
609
609
|
|
|
610
610
|
```bash
|
|
611
611
|
# Development
|
|
612
|
-
pnpm run dev
|
|
613
|
-
|
|
614
|
-
cd packages/plugins/@lego/plugin-<name> && pnpm run dev # Start plugin
|
|
612
|
+
pnpm run dev # Start host + all plugins (default)
|
|
613
|
+
pnpm run dev:host # Start host only (:8080) - plugins won't work without their dev servers
|
|
614
|
+
cd packages/plugins/@lego/plugin-<name> && pnpm run dev # Start specific plugin (requires host running)
|
|
615
615
|
|
|
616
616
|
# Building
|
|
617
617
|
pnpm run build # Build entire project
|
package/template/CLAUDE.md
CHANGED
|
@@ -604,9 +604,9 @@ Stay pragmatic. Stay reliable. Keep learning.
|
|
|
604
604
|
|
|
605
605
|
```bash
|
|
606
606
|
# Development
|
|
607
|
-
pnpm run dev
|
|
608
|
-
|
|
609
|
-
cd packages/plugins/@lego/plugin-<name> && pnpm run dev # Start plugin
|
|
607
|
+
pnpm run dev # Start host + all plugins (default)
|
|
608
|
+
pnpm run dev:host # Start host only (:8080) - plugins won't work without their dev servers
|
|
609
|
+
cd packages/plugins/@lego/plugin-<name> && pnpm run dev # Start specific plugin (requires host running)
|
|
610
610
|
|
|
611
611
|
# Building
|
|
612
612
|
pnpm run build # Build entire project
|
package/template/PROMPT.md
CHANGED
|
@@ -435,9 +435,9 @@ Use the template from `/docs/checkpoints/.template.md`
|
|
|
435
435
|
**Key Commands:**
|
|
436
436
|
```bash
|
|
437
437
|
# Development
|
|
438
|
-
pnpm run dev
|
|
439
|
-
pnpm run dev:host
|
|
440
|
-
pnpm --filter './packages/plugins/@lego/plugin-<name>' dev
|
|
438
|
+
pnpm run dev # Start all services (default)
|
|
439
|
+
pnpm run dev:host # Start host only - plugins won't work without their dev servers
|
|
440
|
+
pnpm --filter './packages/plugins/@lego/plugin-<name>' dev # Start specific plugin (requires host running)
|
|
441
441
|
|
|
442
442
|
# Building
|
|
443
443
|
pnpm run build # Build all
|
|
@@ -13,8 +13,8 @@ This guide covers the complete development workflow for Lego-One, including runn
|
|
|
13
13
|
## Quick Start Commands
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
# Start everything (host + all plugins)
|
|
17
|
-
pnpm run dev
|
|
16
|
+
# Start everything (host + all plugins) - default
|
|
17
|
+
pnpm run dev
|
|
18
18
|
|
|
19
19
|
# Start host only
|
|
20
20
|
pnpm run dev:host
|
|
@@ -39,6 +39,10 @@ pnpm test:e2e
|
|
|
39
39
|
### Running All Services
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
|
+
# Start host and all plugins (default)
|
|
43
|
+
pnpm run dev
|
|
44
|
+
|
|
45
|
+
# Alternative (same as above)
|
|
42
46
|
pnpm run dev:all
|
|
43
47
|
```
|
|
44
48
|
|
|
@@ -57,12 +61,16 @@ pnpm run dev:host
|
|
|
57
61
|
cd host && pnpm run dev
|
|
58
62
|
```
|
|
59
63
|
|
|
64
|
+
**Note:** When running host only, plugins will appear in navigation but won't work without their dev servers running. Use `pnpm run dev` (default) to run everything together.
|
|
65
|
+
|
|
60
66
|
**Dashboard plugin only:**
|
|
61
67
|
```bash
|
|
62
68
|
cd packages/plugins/@lego/plugin-dashboard
|
|
63
69
|
pnpm run dev
|
|
64
70
|
```
|
|
65
71
|
|
|
72
|
+
**Note:** Plugins are micro-frontends that require the host to load them. Running a plugin dev server alone won't work - you need the host running separately.
|
|
73
|
+
|
|
66
74
|
**Todo plugin only:**
|
|
67
75
|
```bash
|
|
68
76
|
cd packages/plugins/@lego/plugin-todo
|
|
@@ -201,8 +201,8 @@ export default SidebarWidget;
|
|
|
201
201
|
### Dev Mode (Separate Servers)
|
|
202
202
|
|
|
203
203
|
```bash
|
|
204
|
-
# Each plugin runs independently
|
|
205
|
-
pnpm run dev
|
|
204
|
+
# Each plugin runs independently (default)
|
|
205
|
+
pnpm run dev
|
|
206
206
|
|
|
207
207
|
# Results in:
|
|
208
208
|
# Host: http://localhost:8080
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "host",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.21",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"dev": "modern dev",
|
|
8
|
-
"build": "modern build",
|
|
7
|
+
"dev": "node scripts/generate-plugin-manifest.js && modern dev",
|
|
8
|
+
"build": "node scripts/generate-plugin-manifest.js && modern build",
|
|
9
|
+
"generate:plugin-manifest": "node scripts/generate-plugin-manifest.js",
|
|
9
10
|
"start": "modern start",
|
|
10
11
|
"lint": "eslint src --ext .ts,.tsx",
|
|
11
12
|
"typecheck": "tsc --noEmit",
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { readdir, readFile, writeFile } from 'fs/promises';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = dirname(__filename);
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Scan plugins directory and extract dev server ports from modern.config.ts files
|
|
10
|
+
*/
|
|
11
|
+
async function scanPlugins() {
|
|
12
|
+
const pluginsDir = join(__dirname, '../../packages/plugins/@lego');
|
|
13
|
+
const entries = {};
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const pluginDirs = await readdir(pluginsDir, { withFileTypes: true });
|
|
17
|
+
|
|
18
|
+
for (const dirent of pluginDirs) {
|
|
19
|
+
if (!dirent.isDirectory() || !dirent.name.startsWith('plugin-')) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const pluginName = `@lego/${dirent.name}`;
|
|
24
|
+
const configPath = join(pluginsDir, dirent.name, 'modern.config.ts');
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const configContent = await readFile(configPath, 'utf-8');
|
|
28
|
+
|
|
29
|
+
// Extract port from config (look for port: number)
|
|
30
|
+
const portMatch = configContent.match(/port:\s*(\d+)/);
|
|
31
|
+
if (portMatch) {
|
|
32
|
+
const port = parseInt(portMatch[1], 10);
|
|
33
|
+
entries[pluginName] = {
|
|
34
|
+
port,
|
|
35
|
+
url: `http://localhost:${port}`,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.warn(`[Plugin Scanner] Could not read config for ${pluginName}:`, error);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error('[Plugin Scanner] Error scanning plugins:', error);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return entries;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Generate TypeScript file with plugin dev entries
|
|
51
|
+
*/
|
|
52
|
+
async function generatePluginManifest() {
|
|
53
|
+
const entries = await scanPlugins();
|
|
54
|
+
|
|
55
|
+
const content = `/**
|
|
56
|
+
* Auto-generated plugin dev server manifest
|
|
57
|
+
* This file is generated by scripts/generate-plugin-manifest.js
|
|
58
|
+
* DO NOT EDIT MANUALLY - Run "pnpm run generate:plugin-manifest" to regenerate
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
export const PLUGIN_DEV_ENTRIES: Record<string, string> = ${JSON.stringify(
|
|
62
|
+
Object.fromEntries(
|
|
63
|
+
Object.entries(entries).map(([name, { url }]) => [name, url])
|
|
64
|
+
),
|
|
65
|
+
null,
|
|
66
|
+
2
|
|
67
|
+
)};
|
|
68
|
+
|
|
69
|
+
export const PLUGIN_PORTS: Record<string, number> = ${JSON.stringify(
|
|
70
|
+
Object.fromEntries(
|
|
71
|
+
Object.entries(entries).map(([name, { port }]) => [name, port])
|
|
72
|
+
),
|
|
73
|
+
null,
|
|
74
|
+
2
|
|
75
|
+
)};
|
|
76
|
+
`;
|
|
77
|
+
|
|
78
|
+
const outputPath = join(__dirname, '../src/kernel/plugins/plugin-manifest.ts');
|
|
79
|
+
await writeFile(outputPath, content, 'utf-8');
|
|
80
|
+
|
|
81
|
+
console.log(`[Plugin Scanner] ✅ Generated manifest with ${Object.keys(entries).length} plugins`);
|
|
82
|
+
if (Object.keys(entries).length > 0) {
|
|
83
|
+
console.log('[Plugin Scanner] Plugins:', Object.keys(entries).join(', '));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Run if executed directly
|
|
88
|
+
generatePluginManifest().catch(console.error);
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { useMemo, useEffect, useState } from 'react';
|
|
2
|
+
import { saasConfig } from '../../saas.config';
|
|
3
|
+
import { usePluginStore } from './store';
|
|
4
|
+
import { PLUGIN_DEV_ENTRIES } from './plugin-manifest';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Plugin route mapping - maps plugin names to their routes
|
|
8
|
+
* This should match modern.runtime.ts configuration
|
|
9
|
+
*/
|
|
10
|
+
const PLUGIN_ROUTES: Record<string, { path: string; label: string; icon: string }> = {
|
|
11
|
+
'@lego/plugin-dashboard': {
|
|
12
|
+
path: '/dashboard',
|
|
13
|
+
label: 'Dashboard',
|
|
14
|
+
icon: 'LayoutDashboard',
|
|
15
|
+
},
|
|
16
|
+
'@lego/plugin-todo': {
|
|
17
|
+
path: '/todos',
|
|
18
|
+
label: 'Todos',
|
|
19
|
+
icon: 'CheckSquare',
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const isDev = import.meta.env.MODE === 'development';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Check if a plugin dev server is running
|
|
27
|
+
*/
|
|
28
|
+
async function checkPluginServerAvailable(entryUrl: string): Promise<boolean> {
|
|
29
|
+
try {
|
|
30
|
+
const controller = new AbortController();
|
|
31
|
+
const timeoutId = setTimeout(() => controller.abort(), 2000); // 2 second timeout
|
|
32
|
+
|
|
33
|
+
const response = await fetch(entryUrl, {
|
|
34
|
+
method: 'GET',
|
|
35
|
+
signal: controller.signal,
|
|
36
|
+
cache: 'no-cache',
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
clearTimeout(timeoutId);
|
|
40
|
+
// If we get any response (even 404), the server is running
|
|
41
|
+
return response.status < 500;
|
|
42
|
+
} catch (error) {
|
|
43
|
+
// Network error or timeout means server is not running
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Hook to check plugin dev server availability
|
|
50
|
+
*/
|
|
51
|
+
function usePluginAvailability() {
|
|
52
|
+
const [availability, setAvailability] = useState<Record<string, boolean>>({});
|
|
53
|
+
const [isChecking, setIsChecking] = useState(true);
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (!isDev) {
|
|
57
|
+
// In production, all enabled plugins are available (bundled)
|
|
58
|
+
const allAvailable: Record<string, boolean> = {};
|
|
59
|
+
saasConfig.plugins.forEach((plugin) => {
|
|
60
|
+
allAvailable[plugin.name] = plugin.enabled;
|
|
61
|
+
});
|
|
62
|
+
setAvailability(allAvailable);
|
|
63
|
+
setIsChecking(false);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// In dev mode, check each plugin's dev server
|
|
68
|
+
const checkAllPlugins = async () => {
|
|
69
|
+
const results: Record<string, boolean> = {};
|
|
70
|
+
|
|
71
|
+
for (const plugin of saasConfig.plugins) {
|
|
72
|
+
if (!plugin.enabled) {
|
|
73
|
+
results[plugin.name] = false;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Get dev server URL for this plugin
|
|
78
|
+
const devEntry = PLUGIN_DEV_ENTRIES[plugin.name];
|
|
79
|
+
|
|
80
|
+
if (devEntry) {
|
|
81
|
+
const isAvailable = await checkPluginServerAvailable(devEntry);
|
|
82
|
+
results[plugin.name] = isAvailable;
|
|
83
|
+
} else {
|
|
84
|
+
// Plugin not configured for dev mode
|
|
85
|
+
results[plugin.name] = false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
setAvailability(results);
|
|
90
|
+
setIsChecking(false);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
checkAllPlugins();
|
|
94
|
+
|
|
95
|
+
// Poll every 5 seconds to check if servers come online
|
|
96
|
+
const interval = setInterval(checkAllPlugins, 5000);
|
|
97
|
+
|
|
98
|
+
return () => clearInterval(interval);
|
|
99
|
+
}, []);
|
|
100
|
+
|
|
101
|
+
return { availability, isChecking };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Hook to get enabled plugins with their route information
|
|
106
|
+
* Only returns plugins that are:
|
|
107
|
+
* 1. Enabled in saas.config.ts
|
|
108
|
+
* 2. Have their dev servers running (in dev mode) OR are bundled (in production)
|
|
109
|
+
*/
|
|
110
|
+
export function useEnabledPlugins() {
|
|
111
|
+
const plugins = usePluginStore((state) => state.plugins);
|
|
112
|
+
const { availability, isChecking } = usePluginAvailability();
|
|
113
|
+
|
|
114
|
+
return useMemo(() => {
|
|
115
|
+
// Don't filter while checking (to avoid flickering)
|
|
116
|
+
if (isChecking && isDev) {
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return saasConfig.plugins
|
|
121
|
+
.filter((plugin) => {
|
|
122
|
+
// Only show plugins that are enabled in saas.config.ts
|
|
123
|
+
if (!plugin.enabled) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Check if plugin was explicitly disabled in store (admin override)
|
|
128
|
+
const pluginState = plugins[plugin.name];
|
|
129
|
+
if (pluginState && pluginState.enabled === false) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// In dev mode, only show if dev server is available
|
|
134
|
+
if (isDev) {
|
|
135
|
+
return availability[plugin.name] === true;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// In production, show if enabled
|
|
139
|
+
return true;
|
|
140
|
+
})
|
|
141
|
+
.map((plugin) => {
|
|
142
|
+
const route = PLUGIN_ROUTES[plugin.name];
|
|
143
|
+
const pluginState = plugins[plugin.name];
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
name: plugin.name,
|
|
147
|
+
path: route?.path || `/${plugin.name.replace('@lego/plugin-', '')}`,
|
|
148
|
+
label: route?.label || pluginState?.manifest?.displayName || plugin.name,
|
|
149
|
+
icon: route?.icon,
|
|
150
|
+
enabled: plugin.enabled,
|
|
151
|
+
loaded: pluginState?.loaded || false,
|
|
152
|
+
available: availability[plugin.name] ?? false,
|
|
153
|
+
manifest: pluginState?.manifest,
|
|
154
|
+
};
|
|
155
|
+
})
|
|
156
|
+
.filter((plugin) => plugin.path && plugin.label); // Only include plugins with valid routes
|
|
157
|
+
}, [plugins, availability, isChecking]);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Get plugin route info by name
|
|
162
|
+
*/
|
|
163
|
+
export function getPluginRoute(pluginName: string) {
|
|
164
|
+
return PLUGIN_ROUTES[pluginName];
|
|
165
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-generated plugin dev server manifest
|
|
3
|
+
* This file is generated by scripts/generate-plugin-manifest.js
|
|
4
|
+
* DO NOT EDIT MANUALLY - Run "pnpm run generate:plugin-manifest" to regenerate
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const PLUGIN_DEV_ENTRIES: Record<string, string> = {
|
|
8
|
+
"@lego/plugin-dashboard": "http://localhost:3001",
|
|
9
|
+
"@lego/plugin-todo": "http://localhost:3002"
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const PLUGIN_PORTS: Record<string, number> = {
|
|
13
|
+
"@lego/plugin-dashboard": 3001,
|
|
14
|
+
"@lego/plugin-todo": 3002
|
|
15
|
+
};
|
|
@@ -20,37 +20,22 @@ export function PocketBaseProvider({ children }: { children: React.ReactNode })
|
|
|
20
20
|
|
|
21
21
|
const client = new PocketBase(pbUrl);
|
|
22
22
|
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
// Try to fetch the API root to verify connection
|
|
27
|
-
const response = await fetch(`${pbUrl}/api/health`, {
|
|
28
|
-
method: 'GET',
|
|
29
|
-
signal: AbortSignal.timeout(5000) // 5 second timeout
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
if (!response.ok && response.status !== 404) {
|
|
33
|
-
throw new Error(`PocketBase returned status ${response.status}`);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
console.log('[PocketBaseProvider] ✅ PocketBase connection successful');
|
|
37
|
-
} catch (healthError) {
|
|
38
|
-
// If health check fails, PocketBase is likely not running
|
|
39
|
-
const errorMsg = 'PocketBase is not running or not accessible. Please start PocketBase first.';
|
|
40
|
-
console.error('[PocketBaseProvider] ❌ Connection check failed:', healthError);
|
|
41
|
-
setError(errorMsg);
|
|
42
|
-
setIsChecking(false);
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
23
|
+
// Skip health check - let PocketBase SDK handle connection errors naturally
|
|
24
|
+
// The client will throw errors when actually used if PocketBase is not available
|
|
25
|
+
setIsChecking(false);
|
|
45
26
|
|
|
46
27
|
// Load stored token
|
|
47
28
|
const storedToken = localStorage.getItem('pocketbase_auth');
|
|
48
29
|
if (storedToken) {
|
|
49
|
-
|
|
30
|
+
try {
|
|
31
|
+
client.authStore.save(storedToken, null);
|
|
32
|
+
} catch (tokenError) {
|
|
33
|
+
console.warn('[PocketBaseProvider] Failed to restore auth token:', tokenError);
|
|
34
|
+
localStorage.removeItem('pocketbase_auth');
|
|
35
|
+
}
|
|
50
36
|
}
|
|
51
37
|
|
|
52
38
|
setPb(client);
|
|
53
|
-
setIsChecking(false);
|
|
54
39
|
console.log('[PocketBaseProvider] ✅ PocketBase client initialized');
|
|
55
40
|
|
|
56
41
|
// Set up auth cleanup on token invalidation
|
|
@@ -1,25 +1,44 @@
|
|
|
1
1
|
import { Link, NavLink } from '@modern-js/runtime/router';
|
|
2
|
+
import { useMemo } from 'react';
|
|
2
3
|
import { useGlobalKernelState } from '../kernel/shared-state';
|
|
3
4
|
import { Slot } from '../kernel/plugins/Slot';
|
|
4
5
|
import { SlotName } from '../kernel/plugins/types';
|
|
6
|
+
import { useEnabledPlugins } from '../kernel/plugins/hooks';
|
|
5
7
|
import {
|
|
6
8
|
Home,
|
|
7
|
-
LayoutDashboard,
|
|
8
9
|
Settings,
|
|
9
10
|
ChevronLeft,
|
|
10
11
|
ChevronRight,
|
|
12
|
+
LayoutDashboard,
|
|
11
13
|
CheckSquare,
|
|
14
|
+
type LucideIcon,
|
|
12
15
|
} from 'lucide-react';
|
|
13
16
|
import { cn } from '../kernel/lib/utils';
|
|
14
17
|
|
|
15
|
-
|
|
18
|
+
// Icon mapping for plugin icons
|
|
19
|
+
const iconMap: Record<string, LucideIcon> = {
|
|
20
|
+
LayoutDashboard,
|
|
21
|
+
CheckSquare,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Base navigation items (always shown)
|
|
25
|
+
const baseNavItems = [
|
|
16
26
|
{ to: '/', icon: Home, label: 'Home' },
|
|
17
|
-
{ to: '/dashboard', icon: LayoutDashboard, label: 'Dashboard' },
|
|
18
|
-
{ to: '/todos', icon: CheckSquare, label: 'Todos' },
|
|
19
27
|
];
|
|
20
28
|
|
|
21
29
|
export function Sidebar() {
|
|
22
30
|
const { sidebarOpen, toggleSidebar, mobileMenuOpen } = useGlobalKernelState();
|
|
31
|
+
const enabledPlugins = useEnabledPlugins();
|
|
32
|
+
|
|
33
|
+
// Build navigation items: base items + enabled plugins
|
|
34
|
+
const navItems = useMemo(() => {
|
|
35
|
+
const pluginNavItems = enabledPlugins.map((plugin) => ({
|
|
36
|
+
to: plugin.path,
|
|
37
|
+
icon: plugin.icon ? iconMap[plugin.icon] : LayoutDashboard,
|
|
38
|
+
label: plugin.label,
|
|
39
|
+
}));
|
|
40
|
+
return [...baseNavItems, ...pluginNavItems];
|
|
41
|
+
}, [enabledPlugins]);
|
|
23
42
|
|
|
24
43
|
return (
|
|
25
44
|
<>
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import { Link } from '@modern-js/runtime/router';
|
|
2
|
-
import { ArrowRight, LayoutDashboard, CheckSquare, Shield } from 'lucide-react';
|
|
2
|
+
import { ArrowRight, LayoutDashboard, CheckSquare, Shield, type LucideIcon } from 'lucide-react';
|
|
3
3
|
import { useGlobalKernelState } from '../kernel/shared-state';
|
|
4
|
+
import { useEnabledPlugins } from '../kernel/plugins/hooks';
|
|
5
|
+
|
|
6
|
+
// Icon mapping for plugin icons
|
|
7
|
+
const iconMap: Record<string, LucideIcon> = {
|
|
8
|
+
LayoutDashboard,
|
|
9
|
+
CheckSquare,
|
|
10
|
+
};
|
|
4
11
|
|
|
5
12
|
export default function HomePage() {
|
|
6
13
|
const { isAuthenticated, user } = useGlobalKernelState();
|
|
14
|
+
const enabledPlugins = useEnabledPlugins();
|
|
7
15
|
|
|
8
16
|
return (
|
|
9
17
|
<div className="mx-auto max-w-4xl">
|
|
@@ -36,40 +44,30 @@ export default function HomePage() {
|
|
|
36
44
|
|
|
37
45
|
{/* Features */}
|
|
38
46
|
<div className="grid gap-6 md:grid-cols-3">
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
<p className="mt-2 text-sm text-muted-foreground">
|
|
62
|
-
Manage your tasks with this example plugin
|
|
63
|
-
</p>
|
|
64
|
-
<Link
|
|
65
|
-
to="/todos"
|
|
66
|
-
className="mt-4 inline-flex items-center text-sm font-medium text-primary hover:underline"
|
|
67
|
-
>
|
|
68
|
-
View Todos
|
|
69
|
-
<ArrowRight className="ml-1 h-4 w-4" />
|
|
70
|
-
</Link>
|
|
71
|
-
</div>
|
|
47
|
+
{/* Dynamic plugin cards - only show enabled plugins */}
|
|
48
|
+
{enabledPlugins.map((plugin) => {
|
|
49
|
+
const Icon = plugin.icon ? iconMap[plugin.icon] : LayoutDashboard;
|
|
50
|
+
return (
|
|
51
|
+
<div key={plugin.name} className="rounded-xl border bg-card p-6">
|
|
52
|
+
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10 text-primary">
|
|
53
|
+
<Icon className="h-6 w-6" />
|
|
54
|
+
</div>
|
|
55
|
+
<h3 className="mt-4 text-lg font-semibold">{plugin.label}</h3>
|
|
56
|
+
<p className="mt-2 text-sm text-muted-foreground">
|
|
57
|
+
{plugin.manifest?.description || `Access the ${plugin.label} plugin`}
|
|
58
|
+
</p>
|
|
59
|
+
<Link
|
|
60
|
+
to={plugin.path}
|
|
61
|
+
className="mt-4 inline-flex items-center text-sm font-medium text-primary hover:underline"
|
|
62
|
+
>
|
|
63
|
+
View {plugin.label}
|
|
64
|
+
<ArrowRight className="ml-1 h-4 w-4" />
|
|
65
|
+
</Link>
|
|
66
|
+
</div>
|
|
67
|
+
);
|
|
68
|
+
})}
|
|
72
69
|
|
|
70
|
+
{/* Multi-Tenancy card (always shown) */}
|
|
73
71
|
<div className="rounded-xl border bg-card p-6">
|
|
74
72
|
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10 text-primary">
|
|
75
73
|
<Shield className="h-6 w-6" />
|
package/template/package.json
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lego-one",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.21",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Microkernel SaaS OS with Modern.js + Garfish + PocketBase",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"dev": "pnpm --filter host dev",
|
|
8
|
+
"dev": "pnpm --filter host dev & pnpm -r --filter './packages/plugins/@lego/*' dev",
|
|
9
9
|
"dev:host": "pnpm --filter host dev",
|
|
10
|
-
"dev:plugins": "pnpm -r --filter './packages/plugins/@lego/*' dev",
|
|
11
10
|
"dev:all": "pnpm --filter host dev & pnpm -r --filter './packages/plugins/@lego/*' dev",
|
|
12
11
|
"build": "pnpm --filter host build && pnpm -r --filter './packages/plugins/@lego/*' build",
|
|
13
12
|
"lint": "pnpm -r lint",
|