kaven-cli 0.1.0-alpha.1 → 0.3.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.
Files changed (36) hide show
  1. package/README.md +221 -45
  2. package/dist/commands/auth/login.js +97 -19
  3. package/dist/commands/auth/logout.js +4 -6
  4. package/dist/commands/auth/whoami.js +12 -11
  5. package/dist/commands/cache/index.js +43 -0
  6. package/dist/commands/config/index.js +128 -0
  7. package/dist/commands/init/index.js +209 -0
  8. package/dist/commands/init-ci/index.js +153 -0
  9. package/dist/commands/license/index.js +10 -0
  10. package/dist/commands/license/status.js +44 -0
  11. package/dist/commands/license/tier-table.js +46 -0
  12. package/dist/commands/marketplace/browse.js +219 -0
  13. package/dist/commands/marketplace/install.js +233 -29
  14. package/dist/commands/marketplace/list.js +94 -16
  15. package/dist/commands/module/doctor.js +143 -38
  16. package/dist/commands/module/publish.js +291 -0
  17. package/dist/commands/upgrade/check.js +162 -0
  18. package/dist/commands/upgrade/index.js +218 -0
  19. package/dist/core/AuthService.js +207 -14
  20. package/dist/core/CacheManager.js +151 -0
  21. package/dist/core/ConfigManager.js +165 -0
  22. package/dist/core/EnvManager.js +196 -0
  23. package/dist/core/ErrorRecovery.js +191 -0
  24. package/dist/core/LicenseService.js +118 -0
  25. package/dist/core/ModuleDoctor.js +286 -0
  26. package/dist/core/ModuleInstaller.js +136 -2
  27. package/dist/core/ProjectInitializer.js +117 -0
  28. package/dist/core/RegistryResolver.js +94 -0
  29. package/dist/core/ScriptRunner.js +72 -0
  30. package/dist/core/SignatureVerifier.js +72 -0
  31. package/dist/index.js +265 -20
  32. package/dist/infrastructure/MarketplaceClient.js +388 -64
  33. package/dist/infrastructure/errors.js +61 -0
  34. package/dist/types/auth.js +2 -0
  35. package/dist/types/marketplace.js +2 -0
  36. package/package.json +15 -2
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.ModuleDoctor = void 0;
7
7
  const fs_extra_1 = __importDefault(require("fs-extra"));
8
8
  const path_1 = __importDefault(require("path"));
9
+ const os_1 = __importDefault(require("os"));
9
10
  class ModuleDoctor {
10
11
  constructor(projectRoot, markerService, manifestParser) {
11
12
  this.projectRoot = projectRoot;
@@ -17,6 +18,11 @@ class ModuleDoctor {
17
18
  results.push(...(await this.checkAnchors()));
18
19
  results.push(...(await this.checkMarkers()));
19
20
  results.push(...(await this.checkDependencies()));
21
+ results.push(...(await this.checkSchemaMerge()));
22
+ results.push(...(await this.checkEnvCompleteness()));
23
+ results.push(...(await this.checkLicense()));
24
+ results.push(...(await this.checkFrameworkVersion()));
25
+ results.push(...(await this.checkPrismaClientSync()));
20
26
  return results;
21
27
  }
22
28
  async checkAnchors() {
@@ -146,6 +152,286 @@ class ModuleDoctor {
146
152
  }
147
153
  return results;
148
154
  }
155
+ // ──────────────────────────────────────────────────────────
156
+ // C2.4: New enhanced checks
157
+ // ──────────────────────────────────────────────────────────
158
+ /** Check that the base Prisma schema exists and has no merge conflicts. */
159
+ async checkSchemaMerge() {
160
+ const results = [];
161
+ const baseSchemaPath = path_1.default.join(this.projectRoot, "packages/database/prisma/schema.base.prisma");
162
+ if (!(await fs_extra_1.default.pathExists(baseSchemaPath))) {
163
+ results.push({
164
+ type: "dependency",
165
+ severity: "warning",
166
+ message: "Prisma base schema not found: packages/database/prisma/schema.base.prisma",
167
+ file: "packages/database/prisma/schema.base.prisma",
168
+ fixable: false,
169
+ });
170
+ return results;
171
+ }
172
+ // Check for git merge conflict markers
173
+ const schemaDir = path_1.default.dirname(baseSchemaPath);
174
+ try {
175
+ const schemaFiles = await fs_extra_1.default.readdir(schemaDir);
176
+ for (const schemaFile of schemaFiles) {
177
+ if (!schemaFile.endsWith(".prisma"))
178
+ continue;
179
+ const filePath = path_1.default.join(schemaDir, schemaFile);
180
+ const content = await fs_extra_1.default.readFile(filePath, "utf-8");
181
+ if (content.includes("<<<<<<<")) {
182
+ results.push({
183
+ type: "marker",
184
+ severity: "error",
185
+ message: `Merge conflict detected in schema file: ${schemaFile}`,
186
+ file: path_1.default.join("packages/database/prisma", schemaFile),
187
+ fixable: false,
188
+ });
189
+ }
190
+ }
191
+ }
192
+ catch {
193
+ // Non-critical
194
+ }
195
+ if (results.length === 0) {
196
+ results.push({
197
+ type: "dependency",
198
+ severity: "info",
199
+ message: "Prisma schema integrity OK",
200
+ fixable: false,
201
+ });
202
+ }
203
+ return results;
204
+ }
205
+ /** Check that .env has all keys defined in .env.example (non-optional). */
206
+ async checkEnvCompleteness() {
207
+ const results = [];
208
+ const envExamplePath = path_1.default.join(this.projectRoot, ".env.example");
209
+ const envPath = path_1.default.join(this.projectRoot, ".env");
210
+ if (!(await fs_extra_1.default.pathExists(envExamplePath))) {
211
+ results.push({
212
+ type: "dependency",
213
+ severity: "info",
214
+ message: ".env.example not found — skipping env completeness check",
215
+ fixable: false,
216
+ });
217
+ return results;
218
+ }
219
+ if (!(await fs_extra_1.default.pathExists(envPath))) {
220
+ results.push({
221
+ type: "dependency",
222
+ severity: "warning",
223
+ message: ".env file not found. Copy .env.example to .env and fill in values",
224
+ file: ".env",
225
+ fixable: false,
226
+ });
227
+ return results;
228
+ }
229
+ const exampleContent = await fs_extra_1.default.readFile(envExamplePath, "utf-8");
230
+ const envContent = await fs_extra_1.default.readFile(envPath, "utf-8");
231
+ // Parse keys: lines that are KEY=VALUE (not comments, not blank)
232
+ const parseKeys = (content) => {
233
+ const keys = new Set();
234
+ for (const line of content.split("\n")) {
235
+ const trimmed = line.trim();
236
+ if (trimmed.startsWith("#") || !trimmed.includes("="))
237
+ continue;
238
+ const key = trimmed.split("=")[0].trim();
239
+ if (key)
240
+ keys.add(key);
241
+ }
242
+ return keys;
243
+ };
244
+ const exampleKeys = parseKeys(exampleContent);
245
+ const envKeys = parseKeys(envContent);
246
+ const missingKeys = [];
247
+ for (const key of exampleKeys) {
248
+ if (!envKeys.has(key)) {
249
+ missingKeys.push(key);
250
+ }
251
+ }
252
+ if (missingKeys.length > 0) {
253
+ results.push({
254
+ type: "dependency",
255
+ severity: "warning",
256
+ message: `Missing env vars in .env: ${missingKeys.join(", ")}`,
257
+ file: ".env",
258
+ fixable: true,
259
+ });
260
+ }
261
+ else {
262
+ results.push({
263
+ type: "dependency",
264
+ severity: "info",
265
+ message: "Env vars completeness OK",
266
+ fixable: false,
267
+ });
268
+ }
269
+ return results;
270
+ }
271
+ /** Check if the stored license is present and not expired. */
272
+ async checkLicense() {
273
+ const results = [];
274
+ const licensePath = path_1.default.join(os_1.default.homedir(), ".kaven", "license.json");
275
+ if (!(await fs_extra_1.default.pathExists(licensePath))) {
276
+ results.push({
277
+ type: "dependency",
278
+ severity: "warning",
279
+ message: "No license found at ~/.kaven/license.json. Run 'kaven license status' to set up.",
280
+ fixable: false,
281
+ });
282
+ return results;
283
+ }
284
+ try {
285
+ const licenseData = await fs_extra_1.default.readJson(licensePath);
286
+ if (licenseData.expiresAt) {
287
+ const expiresAt = new Date(licenseData.expiresAt).getTime();
288
+ if (Date.now() > expiresAt) {
289
+ results.push({
290
+ type: "dependency",
291
+ severity: "error",
292
+ message: `License expired on ${licenseData.expiresAt}. Run 'kaven upgrade' to renew.`,
293
+ fixable: false,
294
+ });
295
+ return results;
296
+ }
297
+ }
298
+ results.push({
299
+ type: "dependency",
300
+ severity: "info",
301
+ message: `License valid (tier: ${licenseData.tier || "unknown"})`,
302
+ fixable: false,
303
+ });
304
+ }
305
+ catch {
306
+ results.push({
307
+ type: "dependency",
308
+ severity: "warning",
309
+ message: "Could not read license file. Try 'kaven license status'.",
310
+ fixable: false,
311
+ });
312
+ }
313
+ return results;
314
+ }
315
+ /** Check if the framework version in package.json is compatible. */
316
+ async checkFrameworkVersion() {
317
+ const results = [];
318
+ const packageJsonPath = path_1.default.join(this.projectRoot, "package.json");
319
+ if (!(await fs_extra_1.default.pathExists(packageJsonPath))) {
320
+ results.push({
321
+ type: "dependency",
322
+ severity: "info",
323
+ message: "package.json not found — skipping framework version check",
324
+ fixable: false,
325
+ });
326
+ return results;
327
+ }
328
+ try {
329
+ const packageJson = await fs_extra_1.default.readJSON(packageJsonPath);
330
+ const kavenCoreVersion = packageJson.dependencies?.["@kaven/core"] ||
331
+ packageJson.devDependencies?.["@kaven/core"];
332
+ if (!kavenCoreVersion) {
333
+ results.push({
334
+ type: "dependency",
335
+ severity: "info",
336
+ message: "@kaven/core not found in dependencies — not a Kaven framework project",
337
+ fixable: false,
338
+ });
339
+ return results;
340
+ }
341
+ // Minimum required semver range
342
+ const MINIMUM_VERSION = "1.0.0";
343
+ const versionStr = kavenCoreVersion.replace(/[\^~>=<]/, "").split(" ")[0];
344
+ const parts = versionStr.split(".").map(Number);
345
+ const minParts = MINIMUM_VERSION.split(".").map(Number);
346
+ let compatible = true;
347
+ for (let i = 0; i < 3; i++) {
348
+ if ((parts[i] || 0) > (minParts[i] || 0))
349
+ break;
350
+ if ((parts[i] || 0) < (minParts[i] || 0)) {
351
+ compatible = false;
352
+ break;
353
+ }
354
+ }
355
+ if (!compatible) {
356
+ results.push({
357
+ type: "dependency",
358
+ severity: "warning",
359
+ message: `@kaven/core version ${kavenCoreVersion} may be outdated. Minimum: ^${MINIMUM_VERSION}`,
360
+ fixable: false,
361
+ });
362
+ }
363
+ else {
364
+ results.push({
365
+ type: "dependency",
366
+ severity: "info",
367
+ message: `Framework version OK (${kavenCoreVersion})`,
368
+ fixable: false,
369
+ });
370
+ }
371
+ }
372
+ catch {
373
+ results.push({
374
+ type: "dependency",
375
+ severity: "info",
376
+ message: "Could not determine framework version",
377
+ fixable: false,
378
+ });
379
+ }
380
+ return results;
381
+ }
382
+ /** Check if Prisma client is generated and up-to-date. */
383
+ async checkPrismaClientSync() {
384
+ const results = [];
385
+ const prismaClientPath = path_1.default.join(this.projectRoot, "node_modules/@prisma/client");
386
+ const schemaPath = path_1.default.join(this.projectRoot, "prisma/schema.prisma");
387
+ if (!(await fs_extra_1.default.pathExists(prismaClientPath))) {
388
+ results.push({
389
+ type: "dependency",
390
+ severity: "warning",
391
+ message: "@prisma/client not found. Run 'npx prisma generate' to generate the client.",
392
+ fixable: true,
393
+ });
394
+ return results;
395
+ }
396
+ if (!(await fs_extra_1.default.pathExists(schemaPath))) {
397
+ results.push({
398
+ type: "dependency",
399
+ severity: "info",
400
+ message: "prisma/schema.prisma not found — skipping Prisma sync check",
401
+ fixable: false,
402
+ });
403
+ return results;
404
+ }
405
+ try {
406
+ const schemaStat = await fs_extra_1.default.stat(schemaPath);
407
+ const clientStat = await fs_extra_1.default.stat(prismaClientPath);
408
+ if (schemaStat.mtime > clientStat.mtime) {
409
+ results.push({
410
+ type: "dependency",
411
+ severity: "warning",
412
+ message: "Prisma schema was modified after client generation. Run 'npx prisma generate'.",
413
+ fixable: true,
414
+ });
415
+ }
416
+ else {
417
+ results.push({
418
+ type: "dependency",
419
+ severity: "info",
420
+ message: "Prisma client is up to date",
421
+ fixable: false,
422
+ });
423
+ }
424
+ }
425
+ catch {
426
+ results.push({
427
+ type: "dependency",
428
+ severity: "info",
429
+ message: "Could not compare Prisma schema and client timestamps",
430
+ fixable: false,
431
+ });
432
+ }
433
+ return results;
434
+ }
149
435
  async readKavenConfig() {
150
436
  const configPath = path_1.default.join(this.projectRoot, "kaven.config.json");
151
437
  if (!(await fs_extra_1.default.pathExists(configPath))) {
@@ -1,18 +1,93 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
37
  };
5
38
  Object.defineProperty(exports, "__esModule", { value: true });
6
39
  exports.ModuleInstaller = void 0;
7
40
  const TransactionalFileSystem_1 = require("../infrastructure/TransactionalFileSystem");
41
+ const ScriptRunner_js_1 = require("./ScriptRunner.js");
8
42
  const fs_extra_1 = __importDefault(require("fs-extra"));
9
43
  const path_1 = __importDefault(require("path"));
44
+ const chalk_1 = __importDefault(require("chalk"));
10
45
  class ModuleInstaller {
11
46
  constructor(projectRoot, markerService) {
12
47
  this.projectRoot = projectRoot;
13
48
  this.markerService = markerService;
14
49
  }
15
- async install(manifest) {
50
+ /**
51
+ * Check whether the given module slug is already installed by scanning
52
+ * project files for its begin/end markers.
53
+ */
54
+ async isModuleInstalled(moduleName) {
55
+ try {
56
+ const filesToCheck = await this.findProjectFiles();
57
+ for (const filePath of filesToCheck) {
58
+ try {
59
+ const content = await fs_extra_1.default.readFile(filePath, "utf-8");
60
+ if (this.markerService.hasModule(content, moduleName)) {
61
+ return true;
62
+ }
63
+ }
64
+ catch {
65
+ // Skip unreadable files
66
+ }
67
+ }
68
+ return false;
69
+ }
70
+ catch {
71
+ return false;
72
+ }
73
+ }
74
+ /** Find text-based project source files to check for markers. */
75
+ async findProjectFiles() {
76
+ const { glob } = await Promise.resolve().then(() => __importStar(require("glob")));
77
+ const patterns = ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"];
78
+ const ignore = ["**/node_modules/**", "**/.next/**", "**/dist/**", "**/build/**"];
79
+ const files = [];
80
+ for (const pattern of patterns) {
81
+ const found = await glob(pattern, {
82
+ cwd: this.projectRoot,
83
+ absolute: true,
84
+ ignore,
85
+ });
86
+ files.push(...found);
87
+ }
88
+ return [...new Set(files)];
89
+ }
90
+ async install(manifest, options) {
16
91
  const tx = new TransactionalFileSystem_1.TransactionalFileSystem(this.projectRoot);
17
92
  try {
18
93
  const filesToModify = Array.from(new Set(manifest.injections.map((inj) => inj.file)));
@@ -21,6 +96,36 @@ class ModuleInstaller {
21
96
  await this.injectCode(injection);
22
97
  }
23
98
  await tx.commit();
99
+ // Run postInstall lifecycle scripts if defined in module.json
100
+ const moduleJson = await this.readManifest(this.projectRoot);
101
+ if (moduleJson?.scripts?.postInstall?.length) {
102
+ const runner = new ScriptRunner_js_1.ScriptRunner();
103
+ try {
104
+ await runner.runScripts(moduleJson.scripts.postInstall.map((s) => ({ ...s, cwd: this.projectRoot })), 'postInstall', false);
105
+ }
106
+ catch (err) {
107
+ const errMsg = err instanceof Error ? err.message : String(err);
108
+ console.warn(chalk_1.default.yellow(`
109
+ ⚠ PostInstall script failed: ${errMsg}`));
110
+ console.warn(chalk_1.default.dim(' The module is installed. Run the script manually if needed.'));
111
+ }
112
+ }
113
+ // Inject environment variables if defined in module.json
114
+ if (!options?.skipEnv && moduleJson?.env?.length) {
115
+ const { EnvManager } = await Promise.resolve().then(() => __importStar(require('./EnvManager.js')));
116
+ const envManager = new EnvManager();
117
+ const envVarDefs = moduleJson.env.map((e) => ({
118
+ name: e.key,
119
+ description: e.example ?? e.key,
120
+ required: e.required ?? false,
121
+ }));
122
+ await envManager.injectEnvVars(manifest.name, envVarDefs, {
123
+ projectDir: this.projectRoot,
124
+ envFile: options?.envFile,
125
+ skipEnv: options?.skipEnv,
126
+ skipConfirmation: options?.yes,
127
+ });
128
+ }
24
129
  }
25
130
  catch (error) {
26
131
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -30,11 +135,31 @@ class ModuleInstaller {
30
135
  throw error;
31
136
  }
32
137
  }
33
- async uninstall(manifest) {
138
+ async uninstall(manifest, options) {
34
139
  const tx = new TransactionalFileSystem_1.TransactionalFileSystem(this.projectRoot);
35
140
  try {
36
141
  const filesToModify = Array.from(new Set(manifest.injections.map((inj) => inj.file)));
37
142
  await tx.backup(filesToModify);
143
+ // Remove environment variables before removing files
144
+ const { EnvManager } = await Promise.resolve().then(() => __importStar(require('./EnvManager.js')));
145
+ const envManager = new EnvManager();
146
+ envManager.removeEnvVars(manifest.name, {
147
+ projectDir: this.projectRoot,
148
+ skipEnv: options?.skipEnv,
149
+ });
150
+ // Run preRemove lifecycle scripts if defined in module.json
151
+ const moduleJson = await this.readManifest(this.projectRoot);
152
+ if (moduleJson?.scripts?.preRemove?.length) {
153
+ const runner = new ScriptRunner_js_1.ScriptRunner();
154
+ try {
155
+ await runner.runScripts(moduleJson.scripts.preRemove.map((s) => ({ ...s, cwd: this.projectRoot })), 'preRemove', false);
156
+ }
157
+ catch (err) {
158
+ const errMsg = err instanceof Error ? err.message : String(err);
159
+ console.warn(chalk_1.default.yellow(`
160
+ ⚠ PreRemove script failed: ${errMsg}`));
161
+ }
162
+ }
38
163
  // Na desinstalação, removemos por arquivo para evitar múltiplas tentativas
39
164
  // de remover marcadores que o regex global já removeu.
40
165
  for (const fileName of filesToModify) {
@@ -62,5 +187,14 @@ class ModuleInstaller {
62
187
  const updated = this.markerService.removeModule(content, moduleName);
63
188
  await fs_extra_1.default.writeFile(filePath, updated);
64
189
  }
190
+ async readManifest(dir) {
191
+ try {
192
+ const raw = await fs_extra_1.default.readFile(path_1.default.join(dir, 'module.json'), 'utf-8');
193
+ return JSON.parse(raw);
194
+ }
195
+ catch {
196
+ return null;
197
+ }
198
+ }
65
199
  }
66
200
  exports.ModuleInstaller = ModuleInstaller;
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ProjectInitializer = void 0;
7
+ const fs_extra_1 = __importDefault(require("fs-extra"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const child_process_1 = require("child_process");
10
+ const TEMPLATE_REPO = "https://github.com/kaven-co/kaven-template.git";
11
+ /** Run a shell command via spawn, returning exit code. */
12
+ function runCommand(cmd, args, cwd, onData) {
13
+ return new Promise((resolve, reject) => {
14
+ const proc = (0, child_process_1.spawn)(cmd, args, { cwd, stdio: onData ? "pipe" : "inherit" });
15
+ if (onData && proc.stdout) {
16
+ proc.stdout.on("data", (d) => onData(d.toString()));
17
+ }
18
+ if (onData && proc.stderr) {
19
+ proc.stderr.on("data", (d) => onData(d.toString()));
20
+ }
21
+ proc.on("error", reject);
22
+ proc.on("close", (code) => resolve(code ?? 0));
23
+ });
24
+ }
25
+ class ProjectInitializer {
26
+ /** Validate that the project name only contains alphanumerics and hyphens. */
27
+ validateName(name) {
28
+ if (!name || name.trim().length === 0) {
29
+ return { valid: false, reason: "Project name cannot be empty" };
30
+ }
31
+ if (/\s/.test(name)) {
32
+ return { valid: false, reason: "Project name cannot contain spaces" };
33
+ }
34
+ if (!/^[a-z0-9-]+$/.test(name)) {
35
+ return {
36
+ valid: false,
37
+ reason: "Project name must only contain lowercase letters, numbers, and hyphens",
38
+ };
39
+ }
40
+ return { valid: true };
41
+ }
42
+ /** Clone the template repo with --depth 1 into targetDir. */
43
+ async cloneTemplate(targetDir) {
44
+ const exitCode = await runCommand("git", ["clone", "--depth", "1", TEMPLATE_REPO, targetDir], process.cwd());
45
+ if (exitCode !== 0) {
46
+ throw new Error(`git clone failed with exit code ${exitCode}`);
47
+ }
48
+ }
49
+ /** Remove the .git directory from the cloned project. */
50
+ async removeGitDir(targetDir) {
51
+ const gitDir = path_1.default.join(targetDir, ".git");
52
+ if (await fs_extra_1.default.pathExists(gitDir)) {
53
+ await fs_extra_1.default.remove(gitDir);
54
+ }
55
+ }
56
+ /** Replace placeholders in key project files. */
57
+ async replacePlaceholders(targetDir, values) {
58
+ const replacements = {
59
+ "{{PROJECT_NAME}}": values.projectName,
60
+ "{{DATABASE_URL}}": values.dbUrl,
61
+ "{{DEFAULT_LOCALE}}": values.locale,
62
+ "{{DEFAULT_CURRENCY}}": values.currency,
63
+ };
64
+ const filesToProcess = [
65
+ "package.json",
66
+ ".env.example",
67
+ "prisma/schema.prisma",
68
+ "apps/api/package.json",
69
+ "apps/admin/package.json",
70
+ "apps/tenant/package.json",
71
+ ];
72
+ for (const relFile of filesToProcess) {
73
+ const filePath = path_1.default.join(targetDir, relFile);
74
+ if (!(await fs_extra_1.default.pathExists(filePath)))
75
+ continue;
76
+ let content = await fs_extra_1.default.readFile(filePath, "utf-8");
77
+ for (const [placeholder, value] of Object.entries(replacements)) {
78
+ content = content.split(placeholder).join(value);
79
+ }
80
+ await fs_extra_1.default.writeFile(filePath, content, "utf-8");
81
+ }
82
+ }
83
+ /** Run pnpm install in the target directory. */
84
+ async runInstall(targetDir) {
85
+ const exitCode = await runCommand("pnpm", ["install"], targetDir);
86
+ if (exitCode !== 0) {
87
+ throw new Error(`pnpm install failed with exit code ${exitCode}`);
88
+ }
89
+ }
90
+ /** Initialize git and create an initial commit. */
91
+ async initGit(targetDir) {
92
+ await runCommand("git", ["init"], targetDir);
93
+ await runCommand("git", ["add", "."], targetDir);
94
+ await runCommand("git", ["commit", "-m", "chore: initial kaven setup"], targetDir);
95
+ }
96
+ /** Health check after project initialization. */
97
+ async healthCheck(targetDir) {
98
+ const issues = [];
99
+ // Check key files exist
100
+ const requiredFiles = ["package.json", ".env.example", "prisma/schema.prisma"];
101
+ for (const file of requiredFiles) {
102
+ if (!(await fs_extra_1.default.pathExists(path_1.default.join(targetDir, file)))) {
103
+ issues.push(`Missing required file: ${file}`);
104
+ }
105
+ }
106
+ // Check node_modules exists (if install was run)
107
+ const hasNodeModules = await fs_extra_1.default.pathExists(path_1.default.join(targetDir, "node_modules"));
108
+ if (!hasNodeModules) {
109
+ issues.push("Dependencies not installed. Run: pnpm install");
110
+ }
111
+ return {
112
+ healthy: issues.length === 0,
113
+ issues,
114
+ };
115
+ }
116
+ }
117
+ exports.ProjectInitializer = ProjectInitializer;