helix-lang 11.0.0
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/README.md +168 -0
- package/dist/architect.d.ts +14 -0
- package/dist/architect.d.ts.map +1 -0
- package/dist/architect.js +127 -0
- package/dist/architect.js.map +1 -0
- package/dist/bin/helix.d.ts +20 -0
- package/dist/bin/helix.d.ts.map +1 -0
- package/dist/bin/helix.js +921 -0
- package/dist/bin/helix.js.map +1 -0
- package/dist/commands/collaborate/index.d.ts +2 -0
- package/dist/commands/collaborate/index.d.ts.map +1 -0
- package/dist/commands/collaborate/index.js +129 -0
- package/dist/commands/collaborate/index.js.map +1 -0
- package/dist/commands/collaborate/server.d.ts +31 -0
- package/dist/commands/collaborate/server.d.ts.map +1 -0
- package/dist/commands/collaborate/server.js +159 -0
- package/dist/commands/collaborate/server.js.map +1 -0
- package/dist/commands/deploy/index.d.ts +25 -0
- package/dist/commands/deploy/index.d.ts.map +1 -0
- package/dist/commands/deploy/index.js +130 -0
- package/dist/commands/deploy/index.js.map +1 -0
- package/dist/commands/deploy/platforms/fly.d.ts +9 -0
- package/dist/commands/deploy/platforms/fly.d.ts.map +1 -0
- package/dist/commands/deploy/platforms/fly.js +68 -0
- package/dist/commands/deploy/platforms/fly.js.map +1 -0
- package/dist/commands/deploy/platforms/railway.d.ts +9 -0
- package/dist/commands/deploy/platforms/railway.d.ts.map +1 -0
- package/dist/commands/deploy/platforms/railway.js +115 -0
- package/dist/commands/deploy/platforms/railway.js.map +1 -0
- package/dist/commands/deploy/platforms/vercel.d.ts +10 -0
- package/dist/commands/deploy/platforms/vercel.d.ts.map +1 -0
- package/dist/commands/deploy/platforms/vercel.js +126 -0
- package/dist/commands/deploy/platforms/vercel.js.map +1 -0
- package/dist/commands/deploy.d.ts +6 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +56 -0
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/evolve/analyzers/performance.d.ts +13 -0
- package/dist/commands/evolve/analyzers/performance.d.ts.map +1 -0
- package/dist/commands/evolve/analyzers/performance.js +591 -0
- package/dist/commands/evolve/analyzers/performance.js.map +1 -0
- package/dist/commands/evolve/analyzers/security.d.ts +21 -0
- package/dist/commands/evolve/analyzers/security.d.ts.map +1 -0
- package/dist/commands/evolve/analyzers/security.js +280 -0
- package/dist/commands/evolve/analyzers/security.js.map +1 -0
- package/dist/commands/evolve/index.d.ts +2 -0
- package/dist/commands/evolve/index.d.ts.map +1 -0
- package/dist/commands/evolve/index.js +122 -0
- package/dist/commands/evolve/index.js.map +1 -0
- package/dist/commands/generate.d.ts +6 -0
- package/dist/commands/generate.d.ts.map +1 -0
- package/dist/commands/generate.js +277 -0
- package/dist/commands/generate.js.map +1 -0
- package/dist/commands/install.d.ts +2 -0
- package/dist/commands/install.d.ts.map +1 -0
- package/dist/commands/install.js +176 -0
- package/dist/commands/install.js.map +1 -0
- package/dist/commands/library/index.d.ts +27 -0
- package/dist/commands/library/index.d.ts.map +1 -0
- package/dist/commands/library/index.js +126 -0
- package/dist/commands/library/index.js.map +1 -0
- package/dist/commands/migrate.d.ts +5 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +258 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/new.d.ts +6 -0
- package/dist/commands/new.d.ts.map +1 -0
- package/dist/commands/new.js +195 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/preflight.d.ts +20 -0
- package/dist/commands/preflight.d.ts.map +1 -0
- package/dist/commands/preflight.js +182 -0
- package/dist/commands/preflight.js.map +1 -0
- package/dist/commands/preview.d.ts +13 -0
- package/dist/commands/preview.d.ts.map +1 -0
- package/dist/commands/preview.js +260 -0
- package/dist/commands/preview.js.map +1 -0
- package/dist/commands/run.d.ts +6 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/run.js +96 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/spawn.d.ts +11 -0
- package/dist/commands/spawn.d.ts.map +1 -0
- package/dist/commands/spawn.js +916 -0
- package/dist/commands/spawn.js.map +1 -0
- package/dist/compiler.d.ts +12 -0
- package/dist/compiler.d.ts.map +1 -0
- package/dist/compiler.js +92 -0
- package/dist/compiler.js.map +1 -0
- package/dist/core/file-writer.d.ts +36 -0
- package/dist/core/file-writer.d.ts.map +1 -0
- package/dist/core/file-writer.js +268 -0
- package/dist/core/file-writer.js.map +1 -0
- package/dist/core/registry.d.ts +57 -0
- package/dist/core/registry.d.ts.map +1 -0
- package/dist/core/registry.js +222 -0
- package/dist/core/registry.js.map +1 -0
- package/dist/core/self-healing.d.ts +47 -0
- package/dist/core/self-healing.d.ts.map +1 -0
- package/dist/core/self-healing.js +250 -0
- package/dist/core/self-healing.js.map +1 -0
- package/dist/core/types.d.ts +126 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +7 -0
- package/dist/core/types.js.map +1 -0
- package/dist/generators/databases/mongodb.d.ts +10 -0
- package/dist/generators/databases/mongodb.d.ts.map +1 -0
- package/dist/generators/databases/mongodb.js +83 -0
- package/dist/generators/databases/mongodb.js.map +1 -0
- package/dist/generators/databases/redis.d.ts +2 -0
- package/dist/generators/databases/redis.d.ts.map +1 -0
- package/dist/generators/databases/redis.js +140 -0
- package/dist/generators/databases/redis.js.map +1 -0
- package/dist/generators/flutter.d.ts +32 -0
- package/dist/generators/flutter.d.ts.map +1 -0
- package/dist/generators/flutter.js +628 -0
- package/dist/generators/flutter.js.map +1 -0
- package/dist/openrouter.d.ts +68 -0
- package/dist/openrouter.d.ts.map +1 -0
- package/dist/openrouter.js +241 -0
- package/dist/openrouter.js.map +1 -0
- package/dist/page-generator.d.ts +22 -0
- package/dist/page-generator.d.ts.map +1 -0
- package/dist/page-generator.js +192 -0
- package/dist/page-generator.js.map +1 -0
- package/dist/parser.d.ts +76 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +691 -0
- package/dist/parser.js.map +1 -0
- package/dist/prompts/master-architect.d.ts +9 -0
- package/dist/prompts/master-architect.d.ts.map +1 -0
- package/dist/prompts/master-architect.js +150 -0
- package/dist/prompts/master-architect.js.map +1 -0
- package/dist/researcher.d.ts +12 -0
- package/dist/researcher.d.ts.map +1 -0
- package/dist/researcher.js +85 -0
- package/dist/researcher.js.map +1 -0
- package/dist/self-heal.d.ts +29 -0
- package/dist/self-heal.d.ts.map +1 -0
- package/dist/self-heal.js +260 -0
- package/dist/self-heal.js.map +1 -0
- package/dist/services/SupabaseDeployer.d.ts +9 -0
- package/dist/services/SupabaseDeployer.d.ts.map +1 -0
- package/dist/services/SupabaseDeployer.js +50 -0
- package/dist/services/SupabaseDeployer.js.map +1 -0
- package/dist/test-generator.d.ts +18 -0
- package/dist/test-generator.d.ts.map +1 -0
- package/dist/test-generator.js +180 -0
- package/dist/test-generator.js.map +1 -0
- package/dist/themes/index.d.ts +52 -0
- package/dist/themes/index.d.ts.map +1 -0
- package/dist/themes/index.js +273 -0
- package/dist/themes/index.js.map +1 -0
- package/dist/types.d.ts +9 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +81 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/constitutional-validator.d.ts +73 -0
- package/dist/utils/constitutional-validator.d.ts.map +1 -0
- package/dist/utils/constitutional-validator.js +249 -0
- package/dist/utils/constitutional-validator.js.map +1 -0
- package/library/auth-flow/app/api/auth/[...nextauth]/route.ts +31 -0
- package/library/auth-flow/app/api/auth/login/route.ts +90 -0
- package/library/auth-flow/app/api/auth/register/route.ts +91 -0
- package/library/auth-flow/components/auth/AuthMiddleware.tsx +139 -0
- package/library/auth-flow/components/auth/LoginForm.tsx +125 -0
- package/library/auth-flow/components/auth/RegisterForm.tsx +168 -0
- package/library/auth-flow/components/auth/nextauth-config.ts +99 -0
- package/library/auth-flow/manifest.json +29 -0
- package/library/auth-flow/schema.prisma +45 -0
- package/library/dashboard-analytics/components/dashboard/ActivityFeed.tsx +109 -0
- package/library/dashboard-analytics/components/dashboard/LineChart.tsx +180 -0
- package/library/dashboard-analytics/components/dashboard/StatsCard.tsx +47 -0
- package/library/dashboard-analytics/components/dashboard/SummaryGrid.tsx +39 -0
- package/library/dashboard-analytics/manifest.json +19 -0
- package/library/data-table/components/table/BulkActions.tsx +59 -0
- package/library/data-table/components/table/ColumnToggle.tsx +65 -0
- package/library/data-table/components/table/DataTable.tsx +318 -0
- package/library/data-table/components/table/ExportCSV.tsx +52 -0
- package/library/data-table/components/table/SearchFilter.tsx +48 -0
- package/library/data-table/manifest.json +20 -0
- package/library/file-upload/app/api/upload/route.ts +107 -0
- package/library/file-upload/components/upload/DropZone.tsx +268 -0
- package/library/file-upload/components/upload/FilePreview.tsx +82 -0
- package/library/file-upload/components/upload/UploadProgress.tsx +92 -0
- package/library/file-upload/components/upload/fileStorage.ts +142 -0
- package/library/file-upload/manifest.json +21 -0
- package/library/notification-system/app/api/notifications/route.ts +121 -0
- package/library/notification-system/components/notifications/NotificationBell.tsx +154 -0
- package/library/notification-system/components/notifications/NotificationProvider.tsx +161 -0
- package/library/notification-system/components/notifications/Toast.tsx +112 -0
- package/library/notification-system/manifest.json +20 -0
- package/package.json +66 -0
|
@@ -0,0 +1,916 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Helix Command: spawn
|
|
4
|
+
* ONE-SHOT GENERATION - Full-stack app from natural language with ZERO intervention
|
|
5
|
+
* v11.0 - Clean Factory: All builds isolated to builds/{app_name}/
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
41
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
42
|
+
};
|
|
43
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
exports.spawnApp = spawnApp;
|
|
45
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
46
|
+
const fs = __importStar(require("fs-extra"));
|
|
47
|
+
const path = __importStar(require("path"));
|
|
48
|
+
const execa = require("execa");
|
|
49
|
+
const ora_1 = __importDefault(require("ora"));
|
|
50
|
+
const openrouter_1 = require("../openrouter");
|
|
51
|
+
const parser_1 = require("../parser");
|
|
52
|
+
const types_1 = require("../types");
|
|
53
|
+
const constitutional_validator_1 = require("../utils/constitutional-validator");
|
|
54
|
+
const themes_1 = require("../themes");
|
|
55
|
+
const self_heal_1 = require("../self-heal");
|
|
56
|
+
const test_generator_1 = require("../test-generator");
|
|
57
|
+
const page_generator_1 = require("../page-generator");
|
|
58
|
+
const MAX_RETRY_ATTEMPTS = 3;
|
|
59
|
+
// =============================================================================
|
|
60
|
+
// CLEAN FACTORY: All projects scoped to builds/
|
|
61
|
+
// =============================================================================
|
|
62
|
+
const HELIX_ROOT = path.resolve(__dirname, "..", "..");
|
|
63
|
+
const BUILDS_DIR = path.join(HELIX_ROOT, "builds");
|
|
64
|
+
const MASTER_ENV = path.join(HELIX_ROOT, ".env");
|
|
65
|
+
/**
|
|
66
|
+
* Spawn a complete full-stack application from a natural language prompt
|
|
67
|
+
*/
|
|
68
|
+
async function spawnApp(prompt, options = {}, constitution, connectionString) {
|
|
69
|
+
console.log(chalk_1.default.cyan("\n🧬 HELIX SPAWN v11.0 - Clean Factory\n"));
|
|
70
|
+
console.log(chalk_1.default.gray(`Prompt: "${prompt}"\n`));
|
|
71
|
+
if (connectionString) {
|
|
72
|
+
console.log(chalk_1.default.gray(`Supabase Autopilot: ENABLED\n`));
|
|
73
|
+
}
|
|
74
|
+
// =========================================================================
|
|
75
|
+
// CONSTITUTIONAL VALIDATION (V10.2)
|
|
76
|
+
// =========================================================================
|
|
77
|
+
if (!options.noConstitution) {
|
|
78
|
+
console.log(chalk_1.default.cyan("📜 Validating Constitutional Compliance...\n"));
|
|
79
|
+
const report = (0, constitutional_validator_1.validateConstitution)(prompt, options);
|
|
80
|
+
(0, constitutional_validator_1.printConstitutionalReport)(report);
|
|
81
|
+
if (report.violations.length > 0) {
|
|
82
|
+
const autoFixableCount = report.violations.filter(v => v.autoFixable).length;
|
|
83
|
+
if (autoFixableCount > 0) {
|
|
84
|
+
console.log(chalk_1.default.yellow(`\n✨ Auto-correcting ${autoFixableCount} violations...\n`));
|
|
85
|
+
options = (0, constitutional_validator_1.autoCorrectOptions)(options, report.violations);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
prompt = (0, constitutional_validator_1.enhancePromptWithConstitution)(prompt);
|
|
89
|
+
console.log(chalk_1.default.green("✅ Constitutional validation complete\n"));
|
|
90
|
+
}
|
|
91
|
+
// =========================================================================
|
|
92
|
+
// CLEAN FACTORY: Enforce builds/ isolation
|
|
93
|
+
// =========================================================================
|
|
94
|
+
const projectName = generateProjectName(prompt);
|
|
95
|
+
await fs.ensureDir(BUILDS_DIR);
|
|
96
|
+
const projectPath = path.join(BUILDS_DIR, projectName);
|
|
97
|
+
if (fs.existsSync(projectPath)) {
|
|
98
|
+
console.error(chalk_1.default.red(`❌ Build "${projectName}" already exists in builds/`));
|
|
99
|
+
console.error(chalk_1.default.gray(` Path: ${projectPath}`));
|
|
100
|
+
console.error(chalk_1.default.gray(` Remove it first or use a different prompt.`));
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
console.log(chalk_1.default.cyan(`📂 Clean Factory: builds/${projectName}/\n`));
|
|
104
|
+
try {
|
|
105
|
+
// =========================================================================
|
|
106
|
+
// PHASE 1: Scaffolding
|
|
107
|
+
// =========================================================================
|
|
108
|
+
console.log(chalk_1.default.cyan("\n📦 Phase 1: Scaffolding Next.js project...\n"));
|
|
109
|
+
const createNextArgs = [
|
|
110
|
+
"create-next-app@latest",
|
|
111
|
+
projectPath,
|
|
112
|
+
"--typescript",
|
|
113
|
+
"--tailwind",
|
|
114
|
+
"--eslint",
|
|
115
|
+
"--app",
|
|
116
|
+
"--src-dir",
|
|
117
|
+
"--use-npm",
|
|
118
|
+
"--no-git",
|
|
119
|
+
"--yes",
|
|
120
|
+
];
|
|
121
|
+
console.log(chalk_1.default.gray(`⬇️ Executing: npx ${createNextArgs.join(" ")}\n`));
|
|
122
|
+
try {
|
|
123
|
+
await execa("npx", createNextArgs, { stdio: "inherit" });
|
|
124
|
+
}
|
|
125
|
+
catch (scaffoldError) {
|
|
126
|
+
console.error(chalk_1.default.red(`\n❌ Scaffolding failed: ${scaffoldError.message}`));
|
|
127
|
+
if (scaffoldError.stderr) {
|
|
128
|
+
console.error(chalk_1.default.red(scaffoldError.stderr));
|
|
129
|
+
}
|
|
130
|
+
await cleanupOnFailure(projectPath);
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
console.log(chalk_1.default.green("\n✓ Next.js project created\n"));
|
|
134
|
+
// =========================================================================
|
|
135
|
+
// SELF-CONTAINMENT: .env, Dockerfile, docker-compose, .gitignore
|
|
136
|
+
// =========================================================================
|
|
137
|
+
console.log(chalk_1.default.cyan("🔒 Self-Containment: Injecting isolation files...\n"));
|
|
138
|
+
// Clone master .env + add project-specific vars
|
|
139
|
+
let masterEnvContent = "";
|
|
140
|
+
if (fs.existsSync(MASTER_ENV)) {
|
|
141
|
+
masterEnvContent = await fs.readFile(MASTER_ENV, "utf-8");
|
|
142
|
+
}
|
|
143
|
+
const projectEnv = `# Helix Clean Factory - Project Environment
|
|
144
|
+
# Cloned from master .env at ${new Date().toISOString()}
|
|
145
|
+
${masterEnvContent}
|
|
146
|
+
# Project-specific
|
|
147
|
+
DATABASE_URL="file:./dev.db"
|
|
148
|
+
NEXT_PUBLIC_APP_NAME="${projectName}"
|
|
149
|
+
`;
|
|
150
|
+
await fs.writeFile(path.join(projectPath, ".env"), projectEnv);
|
|
151
|
+
console.log(chalk_1.default.green(" ✅ .env (cloned from master)"));
|
|
152
|
+
// Dockerfile
|
|
153
|
+
await fs.writeFile(path.join(projectPath, "Dockerfile"), `# Helix Clean Factory - Auto-generated Dockerfile
|
|
154
|
+
FROM node:20-alpine AS base
|
|
155
|
+
|
|
156
|
+
FROM base AS deps
|
|
157
|
+
WORKDIR /app
|
|
158
|
+
COPY package.json package-lock.json* ./
|
|
159
|
+
RUN npm ci --only=production
|
|
160
|
+
|
|
161
|
+
FROM base AS builder
|
|
162
|
+
WORKDIR /app
|
|
163
|
+
COPY --from=deps /app/node_modules ./node_modules
|
|
164
|
+
COPY . .
|
|
165
|
+
RUN npx prisma generate
|
|
166
|
+
RUN npm run build
|
|
167
|
+
|
|
168
|
+
FROM base AS runner
|
|
169
|
+
WORKDIR /app
|
|
170
|
+
ENV NODE_ENV=production
|
|
171
|
+
RUN addgroup --system --gid 1001 nodejs
|
|
172
|
+
RUN adduser --system --uid 1001 nextjs
|
|
173
|
+
COPY --from=builder /app/public ./public
|
|
174
|
+
COPY --from=builder /app/.next/standalone ./
|
|
175
|
+
COPY --from=builder /app/.next/static ./.next/static
|
|
176
|
+
COPY --from=builder /app/prisma ./prisma
|
|
177
|
+
USER nextjs
|
|
178
|
+
EXPOSE 3000
|
|
179
|
+
ENV PORT=3000
|
|
180
|
+
CMD ["node", "server.js"]
|
|
181
|
+
`);
|
|
182
|
+
console.log(chalk_1.default.green(" ✅ Dockerfile"));
|
|
183
|
+
// docker-compose.yml
|
|
184
|
+
await fs.writeFile(path.join(projectPath, "docker-compose.yml"), `# Helix Clean Factory - Auto-generated
|
|
185
|
+
version: "3.8"
|
|
186
|
+
services:
|
|
187
|
+
app:
|
|
188
|
+
build: .
|
|
189
|
+
ports:
|
|
190
|
+
- "3000:3000"
|
|
191
|
+
env_file:
|
|
192
|
+
- .env
|
|
193
|
+
volumes:
|
|
194
|
+
- app-data:/app/prisma
|
|
195
|
+
restart: unless-stopped
|
|
196
|
+
|
|
197
|
+
volumes:
|
|
198
|
+
app-data:
|
|
199
|
+
`);
|
|
200
|
+
console.log(chalk_1.default.green(" ✅ docker-compose.yml"));
|
|
201
|
+
// .gitignore
|
|
202
|
+
await fs.writeFile(path.join(projectPath, ".gitignore"), `node_modules/
|
|
203
|
+
.next/
|
|
204
|
+
.env
|
|
205
|
+
*.db
|
|
206
|
+
*.db-journal
|
|
207
|
+
dist/
|
|
208
|
+
.turbo/
|
|
209
|
+
`);
|
|
210
|
+
console.log(chalk_1.default.green(" ✅ .gitignore"));
|
|
211
|
+
// next.config.ts - LAN-friendly
|
|
212
|
+
await fs.writeFile(path.join(projectPath, "next.config.ts"), [
|
|
213
|
+
'import type { NextConfig } from "next";',
|
|
214
|
+
'',
|
|
215
|
+
'const nextConfig: NextConfig = {',
|
|
216
|
+
' allowedDevOrigins: ["*"],',
|
|
217
|
+
'};',
|
|
218
|
+
'',
|
|
219
|
+
'export default nextConfig;',
|
|
220
|
+
''
|
|
221
|
+
].join("\n"));
|
|
222
|
+
console.log(chalk_1.default.green(" ✅ next.config.ts (LAN-friendly)"));
|
|
223
|
+
// Override layout.tsx - no Google Fonts (blocks hydration over LAN)
|
|
224
|
+
const layoutPath = path.join(projectPath, "src", "app", "layout.tsx");
|
|
225
|
+
await fs.writeFile(layoutPath, [
|
|
226
|
+
'import type { Metadata } from "next";',
|
|
227
|
+
'import "./globals.css";',
|
|
228
|
+
'',
|
|
229
|
+
'export const metadata: Metadata = {',
|
|
230
|
+
' title: "' + projectName + '",',
|
|
231
|
+
' description: "Built by Helix v11.0",',
|
|
232
|
+
'};',
|
|
233
|
+
'',
|
|
234
|
+
'export default function RootLayout({',
|
|
235
|
+
' children,',
|
|
236
|
+
'}: Readonly<{',
|
|
237
|
+
' children: React.ReactNode;',
|
|
238
|
+
'}>) {',
|
|
239
|
+
' return (',
|
|
240
|
+
' <html lang="en">',
|
|
241
|
+
' <body>{children}</body>',
|
|
242
|
+
' </html>',
|
|
243
|
+
' );',
|
|
244
|
+
'}',
|
|
245
|
+
''
|
|
246
|
+
].join("\n"));
|
|
247
|
+
console.log(chalk_1.default.green(" ✅ layout.tsx (no Google Fonts)"));
|
|
248
|
+
// =========================================================================
|
|
249
|
+
// Prisma Setup
|
|
250
|
+
// =========================================================================
|
|
251
|
+
const prismaSpinner = (0, ora_1.default)("Setting up Prisma@5.22.0...").start();
|
|
252
|
+
await execa("npm", ["install", "-D", "prisma@5.22.0"], {
|
|
253
|
+
cwd: projectPath,
|
|
254
|
+
stdio: "pipe",
|
|
255
|
+
});
|
|
256
|
+
await execa("npm", ["install", "@prisma/client@5.22.0"], {
|
|
257
|
+
cwd: projectPath,
|
|
258
|
+
stdio: "pipe",
|
|
259
|
+
});
|
|
260
|
+
// Install test dependencies
|
|
261
|
+
await execa("npm", ["install", "-D", "vitest", "@vitejs/plugin-react", "jsdom", "@testing-library/react", "@testing-library/jest-dom"], {
|
|
262
|
+
cwd: projectPath,
|
|
263
|
+
stdio: "pipe",
|
|
264
|
+
});
|
|
265
|
+
const prismaDir = path.join(projectPath, "prisma");
|
|
266
|
+
await fs.ensureDir(prismaDir);
|
|
267
|
+
await fs.writeFile(path.join(prismaDir, "schema.prisma"), `// Helix Generated Prisma Schema
|
|
268
|
+
generator client {
|
|
269
|
+
provider = "prisma-client-js"
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
datasource db {
|
|
273
|
+
provider = "sqlite"
|
|
274
|
+
url = env("DATABASE_URL")
|
|
275
|
+
}
|
|
276
|
+
`);
|
|
277
|
+
prismaSpinner.succeed("Prisma setup complete (v5.22.0)");
|
|
278
|
+
// Install additional dependencies
|
|
279
|
+
const depsSpinner = (0, ora_1.default)("Installing additional dependencies...").start();
|
|
280
|
+
await execa("npm", [
|
|
281
|
+
"install",
|
|
282
|
+
"lucide-react",
|
|
283
|
+
"clsx",
|
|
284
|
+
"tailwind-merge",
|
|
285
|
+
], {
|
|
286
|
+
cwd: projectPath,
|
|
287
|
+
stdio: "pipe",
|
|
288
|
+
});
|
|
289
|
+
depsSpinner.succeed("Dependencies installed");
|
|
290
|
+
// Create Prisma client helper
|
|
291
|
+
const libDir = path.join(projectPath, "src", "lib");
|
|
292
|
+
await fs.ensureDir(libDir);
|
|
293
|
+
await fs.writeFile(path.join(libDir, "prisma.ts"), `import { PrismaClient } from '@prisma/client';
|
|
294
|
+
|
|
295
|
+
const globalForPrisma = globalThis as unknown as {
|
|
296
|
+
prisma: PrismaClient | undefined;
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
export const prisma = globalForPrisma.prisma ?? new PrismaClient();
|
|
300
|
+
|
|
301
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
302
|
+
globalForPrisma.prisma = prisma;
|
|
303
|
+
}
|
|
304
|
+
`);
|
|
305
|
+
// Create lib/utils.ts
|
|
306
|
+
await fs.writeFile(path.join(libDir, "utils.ts"), `import { clsx, type ClassValue } from "clsx";
|
|
307
|
+
import { twMerge } from "tailwind-merge";
|
|
308
|
+
|
|
309
|
+
export function cn(...inputs: ClassValue[]) {
|
|
310
|
+
return twMerge(clsx(inputs));
|
|
311
|
+
}
|
|
312
|
+
`);
|
|
313
|
+
// Apply Helix theme (V2: data-driven theme engine)
|
|
314
|
+
// Theme is resolved later after blueprint parsing; write default for now
|
|
315
|
+
// The actual theme CSS will be written in the post-blueprint phase
|
|
316
|
+
const defaultThemeCSS = (0, themes_1.generateThemeCSS)((0, themes_1.resolveTheme)(options.theme));
|
|
317
|
+
await fs.writeFile(path.join(projectPath, "src", "app", "globals.css"), defaultThemeCSS);
|
|
318
|
+
// Tailwind v4: config via CSS @theme, remove scaffold-generated tailwind.config.ts
|
|
319
|
+
const twConfigPath = path.join(projectPath, "tailwind.config.ts");
|
|
320
|
+
if (fs.existsSync(twConfigPath)) {
|
|
321
|
+
await fs.remove(twConfigPath);
|
|
322
|
+
}
|
|
323
|
+
// Helix config
|
|
324
|
+
await fs.writeJSON(path.join(projectPath, "helix.config.json"), { version: "10.3.0", spawned: true, cleanFactory: true, prompt, generatedAt: new Date().toISOString() }, { spaces: 2 });
|
|
325
|
+
console.log(chalk_1.default.green("✓ Project scaffolded\n"));
|
|
326
|
+
// =========================================================================
|
|
327
|
+
// PHASE 1.5: SCOPE Stage (V2 — conditional, for complex prompts only)
|
|
328
|
+
// =========================================================================
|
|
329
|
+
let scopeRequirements = null;
|
|
330
|
+
const isComplexPrompt = prompt.split(/\s+/).length > 30 || countEntityMentions(prompt) >= 3;
|
|
331
|
+
if (isComplexPrompt) {
|
|
332
|
+
const spinnerScope = (0, ora_1.default)("Analyzing requirements (complex prompt detected)...").start();
|
|
333
|
+
try {
|
|
334
|
+
scopeRequirements = await generateRequirements(prompt);
|
|
335
|
+
spinnerScope.succeed("Requirements analyzed");
|
|
336
|
+
// Save for reference
|
|
337
|
+
await fs.writeFile(path.join(projectPath, "requirements.json"), scopeRequirements);
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
spinnerScope.warn("SCOPE stage failed — proceeding with direct blueprint generation");
|
|
341
|
+
scopeRequirements = null;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
// =========================================================================
|
|
345
|
+
// PHASE 2: Blueprint Generation with Self-Healing Retry
|
|
346
|
+
// =========================================================================
|
|
347
|
+
let helixBlueprint = "";
|
|
348
|
+
let blueprintAttempts = 0;
|
|
349
|
+
const maxBlueprintRetries = MAX_RETRY_ATTEMPTS;
|
|
350
|
+
let blueprintSuccess = false;
|
|
351
|
+
// Enrich prompt with SCOPE requirements if available
|
|
352
|
+
const enrichedPrompt = scopeRequirements
|
|
353
|
+
? `${prompt}\n\n=== REQUIREMENTS ANALYSIS ===\n${scopeRequirements}\n=== END REQUIREMENTS ===`
|
|
354
|
+
: prompt;
|
|
355
|
+
while (blueprintAttempts < maxBlueprintRetries && !blueprintSuccess) {
|
|
356
|
+
blueprintAttempts++;
|
|
357
|
+
const spinner2 = (0, ora_1.default)(`Designing blueprint... (attempt ${blueprintAttempts}/${maxBlueprintRetries})`).start();
|
|
358
|
+
try {
|
|
359
|
+
helixBlueprint = await generateBlueprint(enrichedPrompt, constitution);
|
|
360
|
+
// Validate the blueprint is parseable
|
|
361
|
+
(0, parser_1.parseHelix)(helixBlueprint);
|
|
362
|
+
blueprintSuccess = true;
|
|
363
|
+
spinner2.succeed("Blueprint designed");
|
|
364
|
+
}
|
|
365
|
+
catch (error) {
|
|
366
|
+
const errMsg = error.message || String(error);
|
|
367
|
+
if (blueprintAttempts < maxBlueprintRetries) {
|
|
368
|
+
spinner2.warn(`Blueprint parse failed, self-healing... (${errMsg.substring(0, 80)})`);
|
|
369
|
+
// Feed error back to LLM for retry
|
|
370
|
+
helixBlueprint = await repairBlueprint(helixBlueprint, errMsg);
|
|
371
|
+
try {
|
|
372
|
+
(0, parser_1.parseHelix)(helixBlueprint);
|
|
373
|
+
blueprintSuccess = true;
|
|
374
|
+
console.log(chalk_1.default.green(" ✅ Self-healed blueprint"));
|
|
375
|
+
}
|
|
376
|
+
catch {
|
|
377
|
+
// Will retry in next loop iteration
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
spinner2.fail("Blueprint generation failed after retries");
|
|
382
|
+
await cleanupOnFailure(projectPath);
|
|
383
|
+
throw error;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
// Save blueprint for reference
|
|
388
|
+
await fs.writeFile(path.join(projectPath, "blueprint.helix"), helixBlueprint);
|
|
389
|
+
// =========================================================================
|
|
390
|
+
// PHASE 3: Database Generation with Self-Healing
|
|
391
|
+
// =========================================================================
|
|
392
|
+
const spinner3 = (0, ora_1.default)("Building database...").start();
|
|
393
|
+
let ast;
|
|
394
|
+
try {
|
|
395
|
+
ast = (0, parser_1.parseHelix)(helixBlueprint);
|
|
396
|
+
}
|
|
397
|
+
catch (error) {
|
|
398
|
+
spinner3.fail("Failed to parse blueprint");
|
|
399
|
+
await cleanupOnFailure(projectPath);
|
|
400
|
+
throw error;
|
|
401
|
+
}
|
|
402
|
+
// V2: Re-apply theme from blueprint AST (overrides default if theme specified in view)
|
|
403
|
+
const blueprintThemeName = ast.views.find(v => v.properties.theme)?.properties.theme;
|
|
404
|
+
if (blueprintThemeName) {
|
|
405
|
+
const resolvedTheme = (0, themes_1.resolveTheme)(blueprintThemeName);
|
|
406
|
+
await fs.writeFile(path.join(projectPath, "src", "app", "globals.css"), (0, themes_1.generateThemeCSS)(resolvedTheme));
|
|
407
|
+
console.log(chalk_1.default.cyan(` Theme: ${resolvedTheme.name} (from blueprint)`));
|
|
408
|
+
}
|
|
409
|
+
let attempts = 0;
|
|
410
|
+
let lastError = "";
|
|
411
|
+
let currentSchema = (0, parser_1.generatePrismaSchema)(ast);
|
|
412
|
+
while (attempts < MAX_RETRY_ATTEMPTS) {
|
|
413
|
+
attempts++;
|
|
414
|
+
try {
|
|
415
|
+
await fs.writeFile(path.join(projectPath, "prisma", "schema.prisma"), currentSchema);
|
|
416
|
+
// CLEANUP: Remove any hallucinated prisma.config.ts
|
|
417
|
+
const badConfigPath = path.join(projectPath, "prisma.config.ts");
|
|
418
|
+
if (fs.existsSync(badConfigPath)) {
|
|
419
|
+
await fs.remove(badConfigPath);
|
|
420
|
+
}
|
|
421
|
+
if (connectionString) {
|
|
422
|
+
const spinnerMigrate = (0, ora_1.default)("Supabase: Deploying Schema...").start();
|
|
423
|
+
try {
|
|
424
|
+
const envPath = path.join(projectPath, ".env");
|
|
425
|
+
let envContent = await fs.readFile(envPath, 'utf-8');
|
|
426
|
+
envContent = envContent.replace(/DATABASE_URL=".*"/, `DATABASE_URL="${connectionString}"`);
|
|
427
|
+
await fs.writeFile(envPath, envContent);
|
|
428
|
+
await execa("npm", ["exec", "--", "prisma", "db", "push", "--accept-data-loss"], {
|
|
429
|
+
cwd: projectPath,
|
|
430
|
+
stdio: "pipe",
|
|
431
|
+
});
|
|
432
|
+
spinnerMigrate.succeed("Supabase: Schema Deployed Successfully");
|
|
433
|
+
}
|
|
434
|
+
catch (err) {
|
|
435
|
+
spinnerMigrate.fail("Supabase Deployment Failed");
|
|
436
|
+
console.error(chalk_1.default.red(err.message));
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
await execa("npm", ["exec", "--", "prisma", "db", "push", "--accept-data-loss"], {
|
|
441
|
+
cwd: projectPath,
|
|
442
|
+
stdio: "pipe",
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
await execa("npm", ["exec", "--", "prisma", "generate"], {
|
|
446
|
+
cwd: projectPath,
|
|
447
|
+
stdio: "pipe",
|
|
448
|
+
});
|
|
449
|
+
break;
|
|
450
|
+
}
|
|
451
|
+
catch (error) {
|
|
452
|
+
lastError = error.stderr || error.message || String(error);
|
|
453
|
+
if (attempts < MAX_RETRY_ATTEMPTS) {
|
|
454
|
+
spinner3.text = `Building database... (self-healing attempt ${attempts + 1}/${MAX_RETRY_ATTEMPTS})`;
|
|
455
|
+
currentSchema = await fixPrismaSchema(currentSchema, lastError);
|
|
456
|
+
}
|
|
457
|
+
else {
|
|
458
|
+
spinner3.fail(`Database build failed after ${MAX_RETRY_ATTEMPTS} attempts`);
|
|
459
|
+
console.error(chalk_1.default.red(lastError));
|
|
460
|
+
await cleanupOnFailure(projectPath);
|
|
461
|
+
throw new Error("Failed to build database");
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
spinner3.succeed("Database built");
|
|
466
|
+
// =========================================================================
|
|
467
|
+
// PHASE 4: Generate API & UI
|
|
468
|
+
// =========================================================================
|
|
469
|
+
const spinner4 = (0, ora_1.default)("Generating application...").start();
|
|
470
|
+
// Resolve the active theme for component class generation
|
|
471
|
+
const activeThemeName = blueprintThemeName || options.theme;
|
|
472
|
+
const activeThemeClasses = (0, themes_1.getThemeClasses)(activeThemeName);
|
|
473
|
+
for (const strand of ast.strands) {
|
|
474
|
+
const apiDir = path.join(projectPath, "src", "app", "api", strand.name.toLowerCase());
|
|
475
|
+
await fs.ensureDir(apiDir);
|
|
476
|
+
await fs.writeFile(path.join(apiDir, "route.ts"), (0, parser_1.generateAPIRoute)(strand));
|
|
477
|
+
}
|
|
478
|
+
for (const view of ast.views) {
|
|
479
|
+
const strandName = view.properties["list"]?.split(".")[0] || ast.strands[0]?.name;
|
|
480
|
+
const strand = ast.strands.find((s) => s.name === strandName) || ast.strands[0];
|
|
481
|
+
if (strand) {
|
|
482
|
+
const viewDir = path.join(projectPath, "src", "app", view.name.toLowerCase());
|
|
483
|
+
await fs.ensureDir(viewDir);
|
|
484
|
+
await fs.writeFile(path.join(viewDir, "page.tsx"), (0, parser_1.generateUIPage)(view, strand, ast.strands, activeThemeClasses));
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
// Multi-page generation: if blueprint defines pages, generate per-page routes + layout
|
|
488
|
+
if (ast.pages && ast.pages.length > 0) {
|
|
489
|
+
// Generate layout based on page config
|
|
490
|
+
const useSidebar = ast.pages.some(p => p.layout === 'sidebar');
|
|
491
|
+
const layoutContent = useSidebar
|
|
492
|
+
? (0, page_generator_1.generateSidebarLayout)(ast.pages, prompt.split(' ').slice(0, 4).join(' '))
|
|
493
|
+
: (0, page_generator_1.generateLayout)(ast.pages, prompt.split(' ').slice(0, 4).join(' '));
|
|
494
|
+
await fs.writeFile(path.join(projectPath, "src", "app", "layout.tsx"), layoutContent);
|
|
495
|
+
// Generate each page route
|
|
496
|
+
for (const page of ast.pages) {
|
|
497
|
+
const pageDir = path.join(projectPath, "src", "app", page.route.replace(/^\//, ''));
|
|
498
|
+
await fs.ensureDir(pageDir);
|
|
499
|
+
await fs.writeFile(path.join(pageDir, "page.tsx"), (0, page_generator_1.generatePageComponent)(page, ast.strands));
|
|
500
|
+
}
|
|
501
|
+
// Root redirect to first page
|
|
502
|
+
await fs.writeFile(path.join(projectPath, "src", "app", "page.tsx"), (0, page_generator_1.generateRootRedirect)(ast.pages[0].route));
|
|
503
|
+
}
|
|
504
|
+
else {
|
|
505
|
+
// Single-page default
|
|
506
|
+
await fs.writeFile(path.join(projectPath, "src", "app", "page.tsx"), generateSpawnHomePage(prompt, ast, activeThemeClasses));
|
|
507
|
+
}
|
|
508
|
+
// Generate test files for each strand
|
|
509
|
+
const testApiDir = path.join(projectPath, "__tests__", "api");
|
|
510
|
+
const testCompDir = path.join(projectPath, "__tests__", "components");
|
|
511
|
+
await fs.ensureDir(testApiDir);
|
|
512
|
+
await fs.ensureDir(testCompDir);
|
|
513
|
+
for (const strand of ast.strands) {
|
|
514
|
+
await fs.writeFile(path.join(testApiDir, `${strand.name.toLowerCase()}.test.ts`), (0, test_generator_1.generateAPITests)(strand));
|
|
515
|
+
await fs.writeFile(path.join(testCompDir, `${strand.name.toLowerCase()}.test.tsx`), (0, test_generator_1.generateComponentTests)(strand));
|
|
516
|
+
}
|
|
517
|
+
await fs.writeFile(path.join(projectPath, "vitest.config.ts"), (0, test_generator_1.generateTestConfig)());
|
|
518
|
+
// Exclude vitest.config.ts from Next.js tsconfig so `next build` doesn't type-check it
|
|
519
|
+
const tsconfigPath = path.join(projectPath, "tsconfig.json");
|
|
520
|
+
try {
|
|
521
|
+
const tsconfigRaw = await fs.readFile(tsconfigPath, "utf-8");
|
|
522
|
+
const tsconfig = JSON.parse(tsconfigRaw);
|
|
523
|
+
if (tsconfig.exclude && !tsconfig.exclude.includes("vitest.config.ts")) {
|
|
524
|
+
tsconfig.exclude.push("vitest.config.ts");
|
|
525
|
+
}
|
|
526
|
+
else if (!tsconfig.exclude) {
|
|
527
|
+
tsconfig.exclude = ["node_modules", "vitest.config.ts"];
|
|
528
|
+
}
|
|
529
|
+
await fs.writeFile(tsconfigPath, JSON.stringify(tsconfig, null, 2));
|
|
530
|
+
}
|
|
531
|
+
catch {
|
|
532
|
+
// Non-fatal — vitest config will still work, just might cause build warnings
|
|
533
|
+
}
|
|
534
|
+
spinner4.succeed("Application generated (with test suite)");
|
|
535
|
+
// =========================================================================
|
|
536
|
+
// PHASE 5: Post-Build Cleanup
|
|
537
|
+
// =========================================================================
|
|
538
|
+
const cleanupSpinner = (0, ora_1.default)("Cleaning temp files...").start();
|
|
539
|
+
await postBuildCleanup(projectPath);
|
|
540
|
+
cleanupSpinner.succeed("Clean build verified");
|
|
541
|
+
// =========================================================================
|
|
542
|
+
// PHASE 5.5: Build Verification with Self-Heal
|
|
543
|
+
// =========================================================================
|
|
544
|
+
const buildOk = await (0, self_heal_1.verifyBuild)(projectPath);
|
|
545
|
+
// =========================================================================
|
|
546
|
+
// PHASE 6: Final Report (NO auto-launch on server)
|
|
547
|
+
// =========================================================================
|
|
548
|
+
console.log(chalk_1.default.green("\n✅ App spawned successfully!\n"));
|
|
549
|
+
console.log(chalk_1.default.white(`📂 Project: ${projectPath}`));
|
|
550
|
+
console.log(chalk_1.default.white(`📦 Docker: cd builds/${projectName} && docker compose up`));
|
|
551
|
+
console.log(chalk_1.default.white(`🔗 Dev: cd builds/${projectName} && npm run dev`));
|
|
552
|
+
console.log(chalk_1.default.white(`🌐 URL: http://localhost:3000\n`));
|
|
553
|
+
// Do NOT auto-launch dev server or open browser on headless server
|
|
554
|
+
// The user can start it manually
|
|
555
|
+
}
|
|
556
|
+
catch (error) {
|
|
557
|
+
console.error(chalk_1.default.red(`\n❌ Spawn failed: ${error.message}`));
|
|
558
|
+
await cleanupOnFailure(projectPath);
|
|
559
|
+
process.exit(1);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
// =============================================================================
|
|
563
|
+
// HELPERS
|
|
564
|
+
// =============================================================================
|
|
565
|
+
function generateProjectName(prompt) {
|
|
566
|
+
return prompt
|
|
567
|
+
.toLowerCase()
|
|
568
|
+
.replace(/[^a-z0-9\s]/g, "")
|
|
569
|
+
.split(/\s+/)
|
|
570
|
+
.slice(0, 3)
|
|
571
|
+
.join("-")
|
|
572
|
+
.substring(0, 30)
|
|
573
|
+
|| "helix-app";
|
|
574
|
+
}
|
|
575
|
+
async function generateBlueprint(prompt, constitution) {
|
|
576
|
+
let constitutionSection = '';
|
|
577
|
+
if (constitution) {
|
|
578
|
+
constitutionSection = `
|
|
579
|
+
=== CONSTITUTION / PROJECT CONTEXT ===
|
|
580
|
+
The following guidelines MUST be followed when designing the application:
|
|
581
|
+
|
|
582
|
+
${constitution}
|
|
583
|
+
|
|
584
|
+
=== END CONSTITUTION ===
|
|
585
|
+
|
|
586
|
+
`;
|
|
587
|
+
}
|
|
588
|
+
const systemPrompt = `${constitutionSection}You are the Helix Architect v11.0.
|
|
589
|
+
Your task is to convert a natural language app description into a valid Helix blueprint.
|
|
590
|
+
|
|
591
|
+
${types_1.HELIX_SYNTAX_GUIDE}
|
|
592
|
+
|
|
593
|
+
RULES:
|
|
594
|
+
- Create appropriate strands for the data models needed
|
|
595
|
+
- Create views that make sense for the user's request
|
|
596
|
+
- Keep it simple but complete
|
|
597
|
+
- Output ONLY valid Helix code, no markdown fences or explanations
|
|
598
|
+
${constitution ? '- IMPORTANT: Follow ALL guidelines from the CONSTITUTION section above' : ''}
|
|
599
|
+
|
|
600
|
+
Example output:
|
|
601
|
+
strand Task {
|
|
602
|
+
field title: String
|
|
603
|
+
field is_completed: Boolean
|
|
604
|
+
field priority: Int
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
view TaskList {
|
|
608
|
+
list: Task.all()
|
|
609
|
+
theme: Glassmorphism
|
|
610
|
+
}
|
|
611
|
+
`;
|
|
612
|
+
return await (0, openrouter_1.createCompletion)(systemPrompt, `Create a Helix blueprint for: ${prompt}`, { model: openrouter_1.DEFAULT_MODEL, maxTokens: 2048 });
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Self-healing: Repair a broken blueprint by feeding error back to LLM
|
|
616
|
+
*/
|
|
617
|
+
// Self-healing with Qwen Thinking Mode for deeper reasoning
|
|
618
|
+
async function repairBlueprint(blueprint, error) {
|
|
619
|
+
const systemPrompt = `You are a Helix blueprint repair specialist.
|
|
620
|
+
Fix the syntax errors in the provided .helix blueprint.
|
|
621
|
+
|
|
622
|
+
RULES:
|
|
623
|
+
- Output ONLY valid Helix code, no markdown fences or explanations
|
|
624
|
+
- Keep the same strands and views, just fix the syntax
|
|
625
|
+
- strands use: strand Name { field fieldName: Type }
|
|
626
|
+
- views use: view Name { list: StrandName.all() }
|
|
627
|
+
- Valid types: String, Int, Float, Boolean, DateTime
|
|
628
|
+
`;
|
|
629
|
+
return await (0, openrouter_1.createCompletion)(systemPrompt, `Fix this broken Helix blueprint:
|
|
630
|
+
|
|
631
|
+
ERROR: ${error}
|
|
632
|
+
|
|
633
|
+
BLUEPRINT:
|
|
634
|
+
${blueprint}
|
|
635
|
+
|
|
636
|
+
Output the corrected blueprint:`, { model: openrouter_1.DEFAULT_MODEL, maxTokens: 2048, thinking: true });
|
|
637
|
+
}
|
|
638
|
+
async function fixPrismaSchema(schema, error) {
|
|
639
|
+
const systemPrompt = `You are a Prisma schema repair assistant.
|
|
640
|
+
Your task is to fix syntax errors in Prisma schema files.
|
|
641
|
+
|
|
642
|
+
CRITICAL RULES:
|
|
643
|
+
- You are STRICTLY FORBIDDEN from generating or referencing prisma.config.ts
|
|
644
|
+
- Standard Prisma uses ONLY schema.prisma - nothing else
|
|
645
|
+
- Output ONLY the fixed schema content, no configuration code
|
|
646
|
+
- Output ONLY the fixed schema, no explanations or markdown
|
|
647
|
+
- Keep the same models and fields, just fix the syntax
|
|
648
|
+
- Ensure all types are valid Prisma types (String, Int, Float, Boolean, DateTime)
|
|
649
|
+
- Ensure proper formatting with @id, @default, etc.
|
|
650
|
+
- Do NOT include any TypeScript/JavaScript code
|
|
651
|
+
`;
|
|
652
|
+
return await (0, openrouter_1.createCompletion)(systemPrompt, `Fix this Prisma schema that has an error:
|
|
653
|
+
|
|
654
|
+
ERROR:
|
|
655
|
+
${error}
|
|
656
|
+
|
|
657
|
+
SCHEMA:
|
|
658
|
+
${schema}
|
|
659
|
+
|
|
660
|
+
Output the corrected schema:`, { model: openrouter_1.DEFAULT_MODEL, maxTokens: 2048, thinking: true });
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Clean up orphaned/temp files after a successful build
|
|
664
|
+
*/
|
|
665
|
+
async function postBuildCleanup(projectPath) {
|
|
666
|
+
const junkPatterns = [
|
|
667
|
+
"prisma.config.ts",
|
|
668
|
+
"*.tmp",
|
|
669
|
+
"*.log",
|
|
670
|
+
".npm",
|
|
671
|
+
"tsconfig.tsbuildinfo",
|
|
672
|
+
];
|
|
673
|
+
for (const pattern of junkPatterns) {
|
|
674
|
+
if (pattern.includes("*"))
|
|
675
|
+
continue; // Skip glob patterns for now
|
|
676
|
+
const junkPath = path.join(projectPath, pattern);
|
|
677
|
+
if (fs.existsSync(junkPath)) {
|
|
678
|
+
await fs.remove(junkPath);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Clean up failed build directory
|
|
684
|
+
*/
|
|
685
|
+
async function cleanupOnFailure(projectPath) {
|
|
686
|
+
if (fs.existsSync(projectPath)) {
|
|
687
|
+
console.log(chalk_1.default.yellow(`🧹 Cleaning up failed build: ${projectPath}`));
|
|
688
|
+
await fs.remove(projectPath);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
// =============================================================================
|
|
692
|
+
// HOME PAGE GENERATOR (unchanged logic, same smart view detection)
|
|
693
|
+
// =============================================================================
|
|
694
|
+
function generateSpawnHomePage(prompt, ast, themeClasses) {
|
|
695
|
+
const tc = themeClasses || (0, themes_1.getThemeClasses)();
|
|
696
|
+
const appTitle = prompt.split(" ").slice(0, 5).join(" ");
|
|
697
|
+
if (ast.strands.length === 0) {
|
|
698
|
+
return `// Spawned by Helix v11.0 - Clean Factory\nexport default function Home() { return (<main className="min-h-screen p-8 flex items-center justify-center"><div className="text-center"><h1 className="text-4xl font-bold ${tc.heading} mb-4">🧬 ${appTitle}</h1><p className="${tc.textMuted}">No strands</p></div></main>); }`;
|
|
699
|
+
}
|
|
700
|
+
const interfaces = ast.strands.map(s => {
|
|
701
|
+
const f = s.fields.map(f => `${f.name}: ${f.type === 'String' ? 'string' : f.type === 'Int' || f.type === 'Float' ? 'number' : 'string'}`).join('; ');
|
|
702
|
+
return `interface ${s.name} { id: string; ${f}; createdAt: string; }`;
|
|
703
|
+
}).join('\n');
|
|
704
|
+
const states = ast.strands.map(s => {
|
|
705
|
+
const l = s.name.toLowerCase();
|
|
706
|
+
const init = s.fields.map(f => `${f.name}: ${f.type === 'Int' || f.type === 'Float' ? '0' : "''"}`).join(', ');
|
|
707
|
+
return `const [${l}s, set${s.name}s] = useState<${s.name}[]>([]);
|
|
708
|
+
const [show${s.name}Form, setShow${s.name}Form] = useState(false);
|
|
709
|
+
const [${l}Form, set${s.name}Form] = useState({ ${init} });`;
|
|
710
|
+
}).join('\n ');
|
|
711
|
+
const funcs = ast.strands.map(s => {
|
|
712
|
+
const l = s.name.toLowerCase();
|
|
713
|
+
const resetForm = s.fields.map(ff => `${ff.name}: ${ff.type === 'Int' || ff.type === 'Float' ? '0' : "''"}`).join(', ');
|
|
714
|
+
return `const fetch${s.name}s = async () => { try { const r = await fetch('/api/${l}'); const j = await r.json(); set${s.name}s(j.data || j); } catch { setError('Failed to load ${l}s'); } };
|
|
715
|
+
const submit${s.name} = async (e: React.FormEvent) => { e.preventDefault(); try { const r = await fetch('/api/${l}', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(${l}Form) }); if (!r.ok) { const err = await r.json(); setError(err.details?.join(', ') || err.error || 'Failed'); return; } setShow${s.name}Form(false); set${s.name}Form({ ${resetForm} }); fetch${s.name}s(); } catch { setError('Failed to create ${l}'); } };
|
|
716
|
+
const del${s.name} = async (id: string) => { if (!confirm('Delete?')) return; try { await fetch('/api/${l}?id=' + id, { method: 'DELETE' }); fetch${s.name}s(); } catch { setError('Failed to delete ${l}'); } };`;
|
|
717
|
+
}).join('\n ');
|
|
718
|
+
const fetchAll = ast.strands.map(s => `fetch${s.name}s()`).join('; ');
|
|
719
|
+
const detectViewType = (fields) => {
|
|
720
|
+
const fieldNames = fields.map(f => f.name.toLowerCase());
|
|
721
|
+
if (fieldNames.some(n => ['image', 'photo', 'avatar', 'thumbnail', 'cover', 'picture', 'img'].includes(n)))
|
|
722
|
+
return 'gallery';
|
|
723
|
+
if (fieldNames.some(n => ['status', 'stage', 'phase', 'state', 'progress'].includes(n)))
|
|
724
|
+
return 'kanban';
|
|
725
|
+
const hasTitle = fieldNames.some(n => ['title', 'name', 'headline'].includes(n));
|
|
726
|
+
const hasContent = fieldNames.some(n => ['body', 'content', 'description', 'message', 'text', 'note'].includes(n));
|
|
727
|
+
if (hasTitle && hasContent)
|
|
728
|
+
return 'feed';
|
|
729
|
+
return 'grid';
|
|
730
|
+
};
|
|
731
|
+
const sections = ast.strands.map(s => {
|
|
732
|
+
const l = s.name.toLowerCase();
|
|
733
|
+
const viewType = detectViewType(s.fields);
|
|
734
|
+
const inputs = s.fields.map(f => {
|
|
735
|
+
const t = f.type === 'Int' || f.type === 'Float' ? 'number' : 'text';
|
|
736
|
+
return `<div className="mb-3"><label className="block ${tc.textMuted} text-sm mb-1">${f.name}</label><input type="${t}" value={${l}Form.${f.name} || ''} onChange={e => set${s.name}Form({...${l}Form, ${f.name}: ${t === 'number' ? 'Number(e.target.value)' : 'e.target.value'}})} className="w-full rounded-md p-3 transition-colors" /></div>`;
|
|
737
|
+
}).join('\n ');
|
|
738
|
+
const titleField = s.fields.find(f => ['title', 'name', 'headline', 'codename'].includes(f.name.toLowerCase()))?.name || s.fields[0]?.name || 'id';
|
|
739
|
+
const statusField = s.fields.find(f => ['status', 'stage', 'phase', 'state', 'progress'].includes(f.name.toLowerCase()))?.name;
|
|
740
|
+
const contentField = s.fields.find(f => ['body', 'content', 'description', 'message', 'text', 'note'].includes(f.name.toLowerCase()))?.name;
|
|
741
|
+
let itemsLayout = '';
|
|
742
|
+
if (viewType === 'kanban' && statusField) {
|
|
743
|
+
itemsLayout = `<div className="flex gap-4 overflow-x-auto pb-4">
|
|
744
|
+
{['Todo', 'In Progress', 'Done', 'Pending', 'Active', 'Complete'].filter(status => ${l}s.some(i => i.${statusField}?.toLowerCase().includes(status.toLowerCase()))).map(status => (
|
|
745
|
+
<div key={status} className="min-w-[280px] glass rounded-xl p-4">
|
|
746
|
+
<h4 className="${tc.heading} font-bold mb-3 flex items-center gap-2">
|
|
747
|
+
<span className="w-3 h-3 rounded-full" style={{background: status === 'Done' || status === 'Complete' ? '${tc.statusColors.success}' : status === 'In Progress' || status === 'Active' ? '${tc.statusColors.warning}' : '${tc.statusColors.info}'}}></span>
|
|
748
|
+
{status}
|
|
749
|
+
</h4>
|
|
750
|
+
<div className="space-y-2">
|
|
751
|
+
{${l}s.filter(i => i.${statusField}?.toLowerCase().includes(status.toLowerCase())).map(item => (
|
|
752
|
+
<div key={item.id} className="bg-white/5 rounded-lg p-3 group">
|
|
753
|
+
<div className="${tc.text} text-sm font-medium">{item.${titleField}}</div>
|
|
754
|
+
<button onClick={() => del${s.name}(item.id)} className="opacity-0 group-hover:opacity-100 text-red-400 text-xs mt-1">Delete</button>
|
|
755
|
+
</div>
|
|
756
|
+
))}
|
|
757
|
+
</div>
|
|
758
|
+
</div>
|
|
759
|
+
))}
|
|
760
|
+
</div>`;
|
|
761
|
+
}
|
|
762
|
+
else if (viewType === 'feed' && contentField) {
|
|
763
|
+
itemsLayout = `<div className="space-y-4">
|
|
764
|
+
{${l}s.map(item => (
|
|
765
|
+
<article key={item.id} className="glass rounded-xl p-5 group">
|
|
766
|
+
<div className="flex justify-between items-start mb-2">
|
|
767
|
+
<h3 className="text-xl font-bold ${tc.heading}">{item.${titleField}}</h3>
|
|
768
|
+
<button onClick={() => del${s.name}(item.id)} className="opacity-0 group-hover:opacity-100 text-red-400">🗑️</button>
|
|
769
|
+
</div>
|
|
770
|
+
<p className="${tc.textMuted} leading-relaxed">{item.${contentField}}</p>
|
|
771
|
+
<div className="mt-3 ${tc.textMuted} text-sm">{new Date(item.createdAt).toLocaleString()}</div>
|
|
772
|
+
</article>
|
|
773
|
+
))}
|
|
774
|
+
</div>`;
|
|
775
|
+
}
|
|
776
|
+
else {
|
|
777
|
+
const display = s.fields.slice(0, 4).map(f => `<div><span className="${tc.textMuted} text-xs">${f.name}</span><div className="${tc.text} text-sm">{String(item.${f.name})}</div></div>`).join('\n ');
|
|
778
|
+
itemsLayout = `<div className="space-y-2">
|
|
779
|
+
{${l}s.map(item => (
|
|
780
|
+
<div key={item.id} className="glass rounded-lg p-4 group flex justify-between hover:bg-white/5">
|
|
781
|
+
<div className="grid grid-cols-4 gap-4 flex-1">${display}</div>
|
|
782
|
+
<button onClick={() => del${s.name}(item.id)} className="opacity-0 group-hover:opacity-100 text-red-400">🗑️</button>
|
|
783
|
+
</div>
|
|
784
|
+
))}
|
|
785
|
+
</div>`;
|
|
786
|
+
}
|
|
787
|
+
const viewLabel = viewType === 'gallery' ? '🖼️ Gallery' : viewType === 'kanban' ? '📋 Board' : viewType === 'feed' ? '📰 Feed' : '📊 Grid';
|
|
788
|
+
return `
|
|
789
|
+
<section className="mb-10">
|
|
790
|
+
<div className="flex justify-between items-center mb-4">
|
|
791
|
+
<div>
|
|
792
|
+
<h2 className="text-2xl font-bold ${tc.heading}">${s.name}s</h2>
|
|
793
|
+
<span className="text-xs ${tc.textMuted}">${viewLabel}</span>
|
|
794
|
+
</div>
|
|
795
|
+
<button onClick={() => setShow${s.name}Form(true)} className="${tc.primaryButton} px-4 py-2 rounded-lg">+ Add</button>
|
|
796
|
+
</div>
|
|
797
|
+
{show${s.name}Form && (
|
|
798
|
+
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
|
799
|
+
<div className="bg-gray-900 border border-white/10 rounded-xl p-6 w-full max-w-md">
|
|
800
|
+
<h3 className="text-xl font-bold ${tc.heading} mb-4">Add ${s.name}</h3>
|
|
801
|
+
<form onSubmit={submit${s.name}}>
|
|
802
|
+
${inputs}
|
|
803
|
+
<div className="flex gap-3 mt-4">
|
|
804
|
+
<button type="button" onClick={() => setShow${s.name}Form(false)} className="flex-1 ${tc.secondaryButton} py-2 rounded-lg">Cancel</button>
|
|
805
|
+
<button type="submit" className="flex-1 ${tc.primaryButton} py-2 rounded-lg">Create</button>
|
|
806
|
+
</div>
|
|
807
|
+
</form>
|
|
808
|
+
</div>
|
|
809
|
+
</div>
|
|
810
|
+
)}
|
|
811
|
+
{${l}s.length === 0 ? <div className="glass rounded-lg p-6 text-center ${tc.textMuted}">No ${l}s yet</div> : ${itemsLayout}}
|
|
812
|
+
</section>`;
|
|
813
|
+
}).join('\n');
|
|
814
|
+
return `// Spawned by Helix v11.0 — With error handling, loading states, pagination support
|
|
815
|
+
'use client';
|
|
816
|
+
import { useState, useEffect } from 'react';
|
|
817
|
+
${interfaces}
|
|
818
|
+
|
|
819
|
+
// Error Boundary Component
|
|
820
|
+
function ErrorBoundaryFallback({ error, reset }: { error: string; reset: () => void }) {
|
|
821
|
+
return (
|
|
822
|
+
<div className="glass rounded-xl p-6 text-center">
|
|
823
|
+
<p className="text-red-400 mb-4">{error}</p>
|
|
824
|
+
<button onClick={reset} className="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-lg">Try Again</button>
|
|
825
|
+
</div>
|
|
826
|
+
);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
export default function Home() {
|
|
830
|
+
const [loading, setLoading] = useState(true);
|
|
831
|
+
const [error, setError] = useState<string | null>(null);
|
|
832
|
+
${states}
|
|
833
|
+
${funcs}
|
|
834
|
+
useEffect(() => { ${fetchAll}; setLoading(false); }, []);
|
|
835
|
+
|
|
836
|
+
// Auto-dismiss error toast after 5 seconds
|
|
837
|
+
useEffect(() => { if (error) { const t = setTimeout(() => setError(null), 5000); return () => clearTimeout(t); } }, [error]);
|
|
838
|
+
|
|
839
|
+
if (loading) return (
|
|
840
|
+
<main className="min-h-screen p-8 flex items-center justify-center">
|
|
841
|
+
<div className="text-center">
|
|
842
|
+
<div className="w-8 h-8 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin mx-auto mb-4"></div>
|
|
843
|
+
<p className="${tc.textMuted}">Loading...</p>
|
|
844
|
+
</div>
|
|
845
|
+
</main>
|
|
846
|
+
);
|
|
847
|
+
|
|
848
|
+
return (
|
|
849
|
+
<main className="min-h-screen p-8">
|
|
850
|
+
{/* Error Toast */}
|
|
851
|
+
{error && (
|
|
852
|
+
<div className="fixed top-4 right-4 z-50 bg-red-900/90 border border-red-500/50 text-red-200 px-4 py-3 rounded-lg shadow-lg flex items-center gap-3 animate-in">
|
|
853
|
+
<span>{error}</span>
|
|
854
|
+
<button onClick={() => setError(null)} className="text-red-400 hover:text-red-300">✕</button>
|
|
855
|
+
</div>
|
|
856
|
+
)}
|
|
857
|
+
<div className="max-w-6xl mx-auto">
|
|
858
|
+
<div className="mb-8">
|
|
859
|
+
<span className="text-sm text-indigo-400 font-mono">🧬 Helix v11.0</span>
|
|
860
|
+
<h1 className="text-4xl font-bold ${tc.heading} mt-1">${appTitle}</h1>
|
|
861
|
+
<p className="${tc.textMuted}">${ast.strands.length} data types</p>
|
|
862
|
+
</div>
|
|
863
|
+
${sections}
|
|
864
|
+
</div>
|
|
865
|
+
</main>
|
|
866
|
+
);
|
|
867
|
+
}
|
|
868
|
+
`;
|
|
869
|
+
}
|
|
870
|
+
// =============================================================================
|
|
871
|
+
// SCOPE STAGE (V2) - Requirements generation for complex prompts
|
|
872
|
+
// =============================================================================
|
|
873
|
+
/**
|
|
874
|
+
* Count potential entity/model mentions in a prompt.
|
|
875
|
+
* Heuristic: capitalized nouns that could be data models.
|
|
876
|
+
*/
|
|
877
|
+
function countEntityMentions(prompt) {
|
|
878
|
+
const entityPattern = /\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+)*\b/g;
|
|
879
|
+
const matches = prompt.match(entityPattern) || [];
|
|
880
|
+
// Deduplicate
|
|
881
|
+
const unique = new Set(matches.map(m => m.toLowerCase()));
|
|
882
|
+
return unique.size;
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* SCOPE Stage: Generate structured requirements from a complex prompt.
|
|
886
|
+
* Uses the fast/cheap model (Qwen Flash) to produce a JSON requirements doc.
|
|
887
|
+
*/
|
|
888
|
+
async function generateRequirements(prompt) {
|
|
889
|
+
const systemPrompt = `You are a requirements analyst for a code generation system.
|
|
890
|
+
Analyze the user's app description and produce a structured JSON requirements document.
|
|
891
|
+
|
|
892
|
+
Output ONLY valid JSON with this structure:
|
|
893
|
+
{
|
|
894
|
+
"models": [
|
|
895
|
+
{
|
|
896
|
+
"name": "ModelName",
|
|
897
|
+
"fields": [{ "name": "fieldName", "type": "String|Int|Float|Boolean|DateTime" }],
|
|
898
|
+
"relations": [{ "name": "relName", "target": "OtherModel", "type": "belongs_to|has_many" }]
|
|
899
|
+
}
|
|
900
|
+
],
|
|
901
|
+
"views": [
|
|
902
|
+
{ "name": "ViewName", "displays": "ModelName", "type": "list|kanban|feed|gallery" }
|
|
903
|
+
],
|
|
904
|
+
"theme": "glassmorphism|professional|minimal|vibrant",
|
|
905
|
+
"complexity": "simple|moderate|complex"
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
Rules:
|
|
909
|
+
- Identify ALL data models and their relationships
|
|
910
|
+
- Choose appropriate field types
|
|
911
|
+
- Detect the best view type for each model
|
|
912
|
+
- Suggest a theme that matches the domain
|
|
913
|
+
- Output ONLY JSON, no markdown fences or explanations`;
|
|
914
|
+
return await (0, openrouter_1.createCompletion)(systemPrompt, `Analyze requirements for: ${prompt}`, { model: openrouter_1.LOCAL_MODEL, maxTokens: 2048 });
|
|
915
|
+
}
|
|
916
|
+
//# sourceMappingURL=spawn.js.map
|