@skalfa/skalfa-cli 1.0.3 → 1.0.5

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Skalfa
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,69 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/skalfa-framework/skalfa/main/logo/logo-skalfa-full.png" alt="Skalfa Logo" width="300" />
3
+ </p>
4
+
5
+ # @skalfa/skalfa-cli
6
+
7
+ > Command Line Interface tool for scaffolding Skalfa projects, managing extensions, and ejecting core utilities.
8
+
9
+ ---
10
+
11
+ ## About this Package
12
+
13
+ This package is part of the **Skalfa Framework**, a premium development ecosystem designed to build high-performance, modular web applications and APIs.
14
+
15
+ ---
16
+
17
+ ## Documentation
18
+
19
+ See the usage documentation at [Documentation](https://skalfa.sejedigital.com).
20
+
21
+ ---
22
+
23
+ ## Installation
24
+
25
+ You can install this command line tool globally using your preferred package manager:
26
+
27
+ ```bash
28
+ # Using npm (Global)
29
+ npm install -g @skalfa/skalfa-cli
30
+
31
+ # Using bun (Global)
32
+ bun install -g @skalfa/skalfa-cli
33
+ ```
34
+
35
+ ---
36
+
37
+ ## Command Line Interface (CLI) Guide
38
+
39
+ This package provides the core `skalfa` developer CLI. The following commands are available:
40
+
41
+ ### 🚀 Scaffolding Commands
42
+ * **`skalfa create-api <name>`**: Scaffolds a new high-performance backend API project powered by Elysia, Bun, and modular utility extensions. It prompts sequentially for optional databases, queues, caches, and real-time sockets.
43
+ * **`skalfa create-app <name>`**: Scaffolds a new modern Next.js frontend application with pre-configured templates, styles, PWA integrations, and Tauri mobile/desktop wrappers.
44
+
45
+ ### 🔌 Extension Commands
46
+ * **`skalfa add <extension-name>`**: Automatically installs and configures an optional extension in the current project root. It is project-aware:
47
+ * In a backend project, it adds utilities like `redis`, `queue`, `cache`, `cron`, `da`, `socket`, or `orm`.
48
+ * In a frontend project, it adds extensions like `idb`, `socket`, `document`, `pwa`, `tauri-desktop`, or `tauri-mobile`.
49
+
50
+ ### 🎛️ Ejection Commands
51
+ * **`skalfa pick <utility-name>`**: Ejects a core utility from the compiled core engine directly into your local `utils/` folder, allowing full local customization while maintaining compatibility.
52
+
53
+ ---
54
+
55
+ ## Pre-installed Dependencies
56
+
57
+ The following key dependencies are packaged and managed within this project:
58
+
59
+ | Dependency | Scope | Version |
60
+ | :--- | :--- | :--- |
61
+ | `commander` | runtime | `^12.1.0` |
62
+ | `@types/node` | development | `^26.0.0` |
63
+ | `typescript` | development | `^6.0.3` |
64
+
65
+ ---
66
+
67
+ ## License
68
+
69
+ This package is licensed under the **MIT License**. For full license text, see the [LICENSE](LICENSE) file.
@@ -10,11 +10,12 @@ const node_path_1 = __importDefault(require("node:path"));
10
10
  const node_fs_1 = __importDefault(require("node:fs"));
11
11
  const add_extension_1 = require("../commands/add-extension");
12
12
  const create_api_1 = require("../commands/create-api");
13
+ const create_app_1 = require("../commands/create-app");
13
14
  const pick_1 = require("../commands/pick");
14
15
  const fs_1 = require("../utils/fs");
15
16
  // Dynamic routing / forwarding logic
16
17
  const args = process.argv.slice(2);
17
- const knownCommands = ["create-api", "add", "pick"];
18
+ const knownCommands = ["create-api", "create-app", "add", "pick"];
18
19
  if (args.length > 0 && !knownCommands.includes(args[0]) && !["-h", "--help", "-v", "--version", "help"].includes(args[0])) {
19
20
  const projectRoot = (0, fs_1.findProjectRoot)(process.cwd());
20
21
  if (projectRoot) {
@@ -43,6 +44,13 @@ program
43
44
  .action(async (name) => {
44
45
  await runCommand(() => (0, create_api_1.createApi)(name));
45
46
  });
47
+ program
48
+ .command("create-app")
49
+ .description("Create a new Skalfa App Next.js project.")
50
+ .argument("<name>", "project folder and package name")
51
+ .action(async (name) => {
52
+ await runCommand(() => (0, create_app_1.createApp)(name));
53
+ });
46
54
  program
47
55
  .command("add")
48
56
  .description("Install an optional Skalfa extension into the current project.")
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.extensionNames = exports.extensions = void 0;
6
+ exports.frontendExtensions = exports.extensionNames = exports.extensions = void 0;
7
7
  exports.addExtension = addExtension;
8
8
  const node_path_1 = __importDefault(require("node:path"));
9
9
  const node_fs_1 = __importDefault(require("node:fs"));
@@ -20,18 +20,138 @@ exports.extensions = {
20
20
  cron: "@skalfa/skalfa-cron",
21
21
  da: "@skalfa/skalfa-da",
22
22
  socket: "@skalfa/skalfa-socket",
23
- notification: "@skalfa/notification",
24
23
  orm: "@skalfa/skalfa-orm"
25
24
  };
26
25
  exports.extensionNames = Object.keys(exports.extensions);
26
+ exports.frontendExtensions = ["idb", "socket", "document", "pwa", "tauri-desktop", "tauri-mobile"];
27
27
  async function addExtension(extensionName) {
28
- const packageName = exports.extensions[extensionName];
29
- if (!packageName) {
30
- throw new Error(`Unknown extension "${extensionName}". Available extensions: ${exports.extensionNames.join(", ")}`);
31
- }
32
28
  const projectRoot = (0, fs_1.findProjectRoot)(process.cwd());
33
29
  if (!projectRoot) {
34
- throw new Error("No package.json found. Run this command inside a Skalfa API project.");
30
+ throw new Error("No package.json found. Run this command inside a Skalfa project.");
31
+ }
32
+ // Detect project type by checking package.json dependencies
33
+ const packageJsonPath = node_path_1.default.join(projectRoot, "package.json");
34
+ const pkg = JSON.parse(node_fs_1.default.readFileSync(packageJsonPath, "utf8"));
35
+ const isFrontend = pkg.dependencies && pkg.dependencies["next"];
36
+ if (isFrontend) {
37
+ if (!exports.frontendExtensions.includes(extensionName)) {
38
+ throw new Error(`Unknown frontend extension "${extensionName}". Available frontend extensions: ${exports.frontendExtensions.join(", ")}`);
39
+ }
40
+ const isDev = !!process.env["SKALFA_APP_TEMPLATE"];
41
+ if (extensionName === "idb") {
42
+ console.log("Installing Skalfa IndexedDB extension...");
43
+ (0, installer_1.installPackage)(projectRoot, isDev ? "file:../skalfa-idb" : "@skalfa/skalfa-idb");
44
+ addTsconfigPath(node_path_1.default.join(projectRoot, "tsconfig.json"), "@skalfa/skalfa-idb");
45
+ addUtilExport(node_path_1.default.join(projectRoot, "utils", "index.ts"), "@skalfa/skalfa-idb");
46
+ }
47
+ else if (extensionName === "socket") {
48
+ console.log("Installing Skalfa Socket.io client extension...");
49
+ (0, installer_1.installPackage)(projectRoot, isDev ? "file:../skalfa-socket-client" : "@skalfa/skalfa-socket-client");
50
+ (0, installer_1.installPackage)(projectRoot, "socket.io-client");
51
+ addTsconfigPath(node_path_1.default.join(projectRoot, "tsconfig.json"), "@skalfa/skalfa-socket-client");
52
+ addUtilExport(node_path_1.default.join(projectRoot, "utils", "index.ts"), "@skalfa/skalfa-socket-client");
53
+ }
54
+ else if (extensionName === "document") {
55
+ console.log("Installing Skalfa Document export extension...");
56
+ (0, installer_1.installPackage)(projectRoot, isDev ? "file:../skalfa-document" : "@skalfa/skalfa-document");
57
+ (0, installer_1.installPackage)(projectRoot, "exceljs");
58
+ (0, installer_1.installPackage)(projectRoot, "pdf-lib");
59
+ (0, installer_1.installPackage)(projectRoot, "pdfjs-dist");
60
+ addTsconfigPath(node_path_1.default.join(projectRoot, "tsconfig.json"), "@skalfa/skalfa-document");
61
+ addUtilExport(node_path_1.default.join(projectRoot, "utils", "index.ts"), "@skalfa/skalfa-document");
62
+ // Copy public worker
63
+ console.log("Scaffolding pdf worker file...");
64
+ const { templateSource, cleanup } = await getAppTemplateSource(projectRoot);
65
+ try {
66
+ const workerSrc = node_path_1.default.join(templateSource, "public", "pdf.worker.min.mjs");
67
+ const workerDest = node_path_1.default.join(projectRoot, "public", "pdf.worker.min.mjs");
68
+ if ((0, fs_1.exists)(workerSrc)) {
69
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(workerDest), { recursive: true });
70
+ node_fs_1.default.copyFileSync(workerSrc, workerDest);
71
+ }
72
+ }
73
+ finally {
74
+ cleanup();
75
+ }
76
+ // Add exports back to components/base.components/index.ts to keep @components compatibility
77
+ const baseComponentsIndexPath = node_path_1.default.join(projectRoot, "components", "base.components", "index.ts");
78
+ if (node_fs_1.default.existsSync(baseComponentsIndexPath)) {
79
+ let content = node_fs_1.default.readFileSync(baseComponentsIndexPath, "utf8");
80
+ if (!content.includes("@skalfa/skalfa-document")) {
81
+ content += `\nexport * from "@skalfa/skalfa-document";\n`;
82
+ node_fs_1.default.writeFileSync(baseComponentsIndexPath, content, "utf8");
83
+ }
84
+ }
85
+ }
86
+ else if (extensionName === "pwa") {
87
+ console.log("Installing Skalfa PWA extension...");
88
+ (0, installer_1.installPackage)(projectRoot, "@ducanh2912/next-pwa");
89
+ // Copy manifest.ts from template if it doesn't exist
90
+ const manifestPath = node_path_1.default.join(projectRoot, "app", "manifest.ts");
91
+ if (!node_fs_1.default.existsSync(manifestPath)) {
92
+ console.log("Scaffolding PWA manifest file...");
93
+ const { templateSource, cleanup } = await getAppTemplateSource(projectRoot);
94
+ try {
95
+ const manifestSrc = node_path_1.default.join(templateSource, "app", "manifest.ts");
96
+ if ((0, fs_1.exists)(manifestSrc)) {
97
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(manifestPath), { recursive: true });
98
+ node_fs_1.default.copyFileSync(manifestSrc, manifestPath);
99
+ }
100
+ }
101
+ finally {
102
+ cleanup();
103
+ }
104
+ }
105
+ // Wrap next.config.ts with withPWA
106
+ const nextConfigPath = node_path_1.default.join(projectRoot, "next.config.ts");
107
+ if (node_fs_1.default.existsSync(nextConfigPath)) {
108
+ let content = node_fs_1.default.readFileSync(nextConfigPath, "utf8");
109
+ if (!content.includes("@ducanh2912/next-pwa")) {
110
+ content = `import withPWAInit from "@ducanh2912/next-pwa";\n` + content;
111
+ content = content.replace(/export default nextConfig;/, `const withPWA = withPWAInit({\n dest: "public",\n disable: process.env.NODE_ENV === "development",\n});\n\nexport default withPWA(nextConfig);`);
112
+ node_fs_1.default.writeFileSync(nextConfigPath, content, "utf8");
113
+ }
114
+ }
115
+ }
116
+ else if (extensionName === "tauri-desktop" || extensionName === "tauri-mobile") {
117
+ console.log(`Installing Skalfa ${extensionName === "tauri-desktop" ? "Tauri Desktop" : "Tauri Mobile"} extension...`);
118
+ (0, installer_1.installPackage)(projectRoot, "@tauri-apps/api");
119
+ (0, installer_1.installPackage)(projectRoot, "@tauri-apps/cli", true); // devDependency
120
+ (0, installer_1.installPackage)(projectRoot, "cross-env", true); // devDependency
121
+ // Copy src-tauri folder
122
+ console.log("Scaffolding Tauri configuration...");
123
+ const tauriDest = node_path_1.default.join(projectRoot, "src-tauri");
124
+ if (!node_fs_1.default.existsSync(tauriDest)) {
125
+ const { templateSource, cleanup } = await getAppTemplateSource(projectRoot);
126
+ try {
127
+ const tauriSrc = node_path_1.default.join(templateSource, "src-tauri");
128
+ if ((0, fs_1.exists)(tauriSrc)) {
129
+ (0, copier_1.copyTemplate)(tauriSrc, tauriDest);
130
+ }
131
+ }
132
+ finally {
133
+ cleanup();
134
+ }
135
+ }
136
+ // Add scripts to package.json
137
+ if (node_fs_1.default.existsSync(packageJsonPath)) {
138
+ const pkg = JSON.parse(node_fs_1.default.readFileSync(packageJsonPath, "utf8"));
139
+ pkg.scripts = pkg.scripts || {};
140
+ pkg.scripts["tauri"] = "cross-env IS_TAURI=true tauri";
141
+ if (extensionName === "tauri-mobile") {
142
+ pkg.scripts["tauri:android"] = "cross-env IS_TAURI=true tauri android";
143
+ pkg.scripts["tauri:ios"] = "cross-env IS_TAURI=true tauri ios";
144
+ }
145
+ node_fs_1.default.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2), "utf8");
146
+ }
147
+ }
148
+ console.log(`✓ Frontend extension "${extensionName}" successfully installed and configured.`);
149
+ return;
150
+ }
151
+ // Backend scaffolding logic
152
+ const packageName = exports.extensions[extensionName];
153
+ if (!packageName) {
154
+ throw new Error(`Unknown backend extension "${extensionName}". Available extensions: ${exports.extensionNames.join(", ")}`);
35
155
  }
36
156
  const isDev = !!process.env["SKALFA_API_TEMPLATE"];
37
157
  if (extensionName === "orm") {
@@ -81,7 +201,6 @@ async function getTemplateSource(projectRoot) {
81
201
  return { templateSource, cleanup: () => { } };
82
202
  }
83
203
  else {
84
- // Dynamic download from npm registry
85
204
  const templatePackageName = "@skalfa/skalfa-api";
86
205
  console.log(`Fetching latest template info for ${templatePackageName} from npm registry...`);
87
206
  const tarballUrl = await (0, npm_1.fetchLatestTarballUrl)(templatePackageName);
@@ -114,10 +233,53 @@ async function getTemplateSource(projectRoot) {
114
233
  };
115
234
  }
116
235
  }
236
+ async function getAppTemplateSource(projectRoot) {
237
+ const envTemplateSource = process.env["SKALFA_APP_TEMPLATE"];
238
+ let tempExtractDir = null;
239
+ let templateSource = "";
240
+ if (envTemplateSource) {
241
+ templateSource = node_path_1.default.resolve(envTemplateSource);
242
+ if (!(0, fs_1.exists)(templateSource)) {
243
+ throw new Error(`Template source override not found: ${templateSource}`);
244
+ }
245
+ return { templateSource, cleanup: () => { } };
246
+ }
247
+ else {
248
+ const templatePackageName = "@skalfa/skalfa-app";
249
+ console.log(`Fetching latest template info for ${templatePackageName} from npm registry...`);
250
+ const tarballUrl = await (0, npm_1.fetchLatestTarballUrl)(templatePackageName);
251
+ const parentDir = node_path_1.default.dirname(projectRoot);
252
+ tempExtractDir = node_path_1.default.join(parentDir, `skalfa-app-temp-extract-${Date.now()}`);
253
+ node_fs_1.default.mkdirSync(tempExtractDir, { recursive: true });
254
+ const tarballPath = node_path_1.default.join(tempExtractDir, "template.tgz");
255
+ console.log("Downloading template tarball...");
256
+ await (0, npm_1.downloadTarball)(tarballUrl, tarballPath);
257
+ console.log("Extracting template...");
258
+ try {
259
+ (0, node_child_process_1.execSync)(`tar -xzf "${tarballPath}" -C "${tempExtractDir}"`, { stdio: "ignore" });
260
+ }
261
+ catch (err) {
262
+ node_fs_1.default.rmSync(tempExtractDir, { recursive: true, force: true });
263
+ throw new Error(`Failed to extract template tarball. Please ensure 'tar' command is available: ${err.message}`);
264
+ }
265
+ templateSource = node_path_1.default.join(tempExtractDir, "package");
266
+ if (!(0, fs_1.exists)(templateSource)) {
267
+ node_fs_1.default.rmSync(tempExtractDir, { recursive: true, force: true });
268
+ throw new Error("Invalid template structure: 'package' folder not found inside tarball.");
269
+ }
270
+ return {
271
+ templateSource,
272
+ cleanup: () => {
273
+ if (tempExtractDir && (0, fs_1.exists)(tempExtractDir)) {
274
+ node_fs_1.default.rmSync(tempExtractDir, { recursive: true, force: true });
275
+ }
276
+ }
277
+ };
278
+ }
279
+ }
117
280
  async function scaffoldOrmExtension(projectRoot) {
118
281
  const { templateSource, cleanup } = await getTemplateSource(projectRoot);
119
282
  try {
120
- // 1. Copy database/ folder and app/models/ folder from template
121
283
  console.log("Scaffolding database and model directories...");
122
284
  const dbSrc = node_path_1.default.join(templateSource, "database");
123
285
  const dbDest = node_path_1.default.join(projectRoot, "database");
@@ -129,7 +291,6 @@ async function scaffoldOrmExtension(projectRoot) {
129
291
  if ((0, fs_1.exists)(modelsSrc)) {
130
292
  (0, copier_1.copyTemplate)(modelsSrc, modelsDest);
131
293
  }
132
- // 2. Copy database-enabled UserController and AuthController from template
133
294
  console.log("Restoring database-enabled controllers...");
134
295
  const authControllerSrc = node_path_1.default.join(templateSource, "app", "controllers", "iam", "auth.controller.ts");
135
296
  const authControllerDest = node_path_1.default.join(projectRoot, "app", "controllers", "iam", "auth.controller.ts");
@@ -143,7 +304,6 @@ async function scaffoldOrmExtension(projectRoot) {
143
304
  node_fs_1.default.mkdirSync(node_path_1.default.dirname(userControllerDest), { recursive: true });
144
305
  node_fs_1.default.copyFileSync(userControllerSrc, userControllerDest);
145
306
  }
146
- // 3. Restore database CLI commands loader if missing
147
307
  console.log("Restoring database CLI commands...");
148
308
  const commandsSrc = node_path_1.default.join(templateSource, "utils", "commands");
149
309
  const commandsDest = node_path_1.default.join(projectRoot, "utils", "commands");
@@ -155,7 +315,6 @@ async function scaffoldOrmExtension(projectRoot) {
155
315
  node_fs_1.default.copyFileSync(skalfaCliSrc, skalfaCliDest);
156
316
  }
157
317
  }
158
- // 4. Restore the database initialization block and imports in app/app.ts
159
318
  console.log("Restoring database initialization in app/app.ts...");
160
319
  const appTsSrc = node_path_1.default.join(templateSource, "app", "app.ts");
161
320
  const appTsDest = node_path_1.default.join(projectRoot, "app", "app.ts");
@@ -173,7 +332,6 @@ async function scaffoldOrmExtension(projectRoot) {
173
332
  node_fs_1.default.writeFileSync(appTsDest, targetAppTs, "utf8");
174
333
  }
175
334
  }
176
- // 5. Update tsconfig.json path mappings
177
335
  console.log("Updating tsconfig.json paths...");
178
336
  const tsconfigPath = node_path_1.default.join(projectRoot, "tsconfig.json");
179
337
  if ((0, fs_1.exists)(tsconfigPath)) {
@@ -183,7 +341,6 @@ async function scaffoldOrmExtension(projectRoot) {
183
341
  node_fs_1.default.writeFileSync(tsconfigPath, content, "utf8");
184
342
  }
185
343
  }
186
- // 6. Update utils/index.ts exports
187
344
  console.log("Updating utils/index.ts exports...");
188
345
  const utilsIndexPath = node_path_1.default.join(projectRoot, "utils", "index.ts");
189
346
  if ((0, fs_1.exists)(utilsIndexPath)) {
@@ -207,13 +364,11 @@ async function scaffoldUtilityExtension(projectRoot, ext) {
207
364
  const tsconfigPath = node_path_1.default.join(projectRoot, "tsconfig.json");
208
365
  const utilsIndexPath = node_path_1.default.join(projectRoot, "utils", "index.ts");
209
366
  const appTsPath = node_path_1.default.join(projectRoot, "app", "app.ts");
210
- // 1. Copy relevant template folders
211
367
  if (ext === "queue") {
212
368
  console.log("Copying Queue worker examples...");
213
369
  const src = node_path_1.default.join(templateSource, "app", "jobs", "queues");
214
370
  const dest = node_path_1.default.join(projectRoot, "app", "jobs", "queues");
215
371
  (0, copier_1.copyTemplate)(src, dest);
216
- // Check if project has DA or Notification packages
217
372
  const packageJsonPath = node_path_1.default.join(projectRoot, "package.json");
218
373
  let hasDa = false;
219
374
  let hasNotification = false;
@@ -243,41 +398,25 @@ async function scaffoldUtilityExtension(projectRoot, ext) {
243
398
  const dest = node_path_1.default.join(projectRoot, "database", "da.migrations");
244
399
  (0, copier_1.copyTemplate)(src, dest);
245
400
  }
246
- // 2. Update tsconfig.json path mappings
247
401
  addTsconfigPath(tsconfigPath, `@skalfa/skalfa-${ext}`);
248
402
  if (ext === "queue" || ext === "cache") {
249
403
  addTsconfigPath(tsconfigPath, "@skalfa/skalfa-redis");
250
404
  }
251
- // 3. Update utils/index.ts exports
252
405
  addUtilExport(utilsIndexPath, `@skalfa/skalfa-${ext}`);
253
406
  if (ext === "queue" || ext === "cache") {
254
407
  addUtilExport(utilsIndexPath, "@skalfa/skalfa-redis");
255
408
  }
256
- // 4. Uncomment initialization blocks and update imports in app/app.ts
257
409
  if (node_fs_1.default.existsSync(appTsPath)) {
258
410
  let content = node_fs_1.default.readFileSync(appTsPath, "utf8");
259
411
  const importsToAdd = [];
260
412
  if (ext === "redis" || ext === "queue" || ext === "cache") {
261
413
  importsToAdd.push("redis");
262
- // Uncomment Redis block
263
414
  content = content.replace(/\/\/ if \(process\.env\.REDIS_HOST[\s\S]*?\/\/ \}/g, (match) => match.replace(/^\/\/ ?/gm, ""));
264
415
  }
265
416
  if (ext === "da") {
266
417
  importsToAdd.push("daClient");
267
- // Uncomment DA block
268
418
  content = content.replace(/\/\/ if \(process\.env\.DA_HOST[\s\S]*?\/\/ }/g, (match) => match.replace(/^\/\/ ?/gm, ""));
269
419
  }
270
- if (ext === "cron") {
271
- importsToAdd.push("cron");
272
- // Uncomment Cron block
273
- content = content.replace(/\/\/ cron\.worker\(\)/g, "cron.worker()");
274
- }
275
- if (ext === "socket") {
276
- importsToAdd.push("socket");
277
- // Uncomment Socket block
278
- content = content.replace(/\/\/ if \(process\.env\.SOCKET_PORT[\s\S]*?\/\/ }/g, (match) => match.replace(/^\/\/ ?/gm, ""));
279
- }
280
- // Update import statement at the top of app.ts
281
420
  if (importsToAdd.length > 0) {
282
421
  const importRegex = /import\s*\{\s*([\s\S]*?)\s*\}\s*from\s*["']@utils["']/;
283
422
  const match = content.match(importRegex);
@@ -290,6 +429,40 @@ async function scaffoldUtilityExtension(projectRoot, ext) {
290
429
  }
291
430
  node_fs_1.default.writeFileSync(appTsPath, content, "utf8");
292
431
  }
432
+ const packageJsonPath = node_path_1.default.join(projectRoot, "package.json");
433
+ if (node_fs_1.default.existsSync(packageJsonPath) && ["cron", "queue", "socket"].includes(ext)) {
434
+ const pkg = JSON.parse(node_fs_1.default.readFileSync(packageJsonPath, "utf8"));
435
+ pkg.scripts = pkg.scripts || {};
436
+ let scriptKey = "";
437
+ let scriptVal = "";
438
+ if (ext === "cron") {
439
+ scriptKey = "start:cron";
440
+ scriptVal = "bun run app/jobs/crons/worker.cron.ts";
441
+ }
442
+ else if (ext === "queue") {
443
+ scriptKey = "start:queue";
444
+ scriptVal = "bun run app/jobs/queues/worker.queue.ts";
445
+ }
446
+ else if (ext === "socket") {
447
+ scriptKey = "start:socket";
448
+ scriptVal = "bun run app/jobs/sockets/worker.socket.ts";
449
+ }
450
+ if (scriptKey) {
451
+ pkg.scripts[scriptKey] = scriptVal;
452
+ const devScript = pkg.scripts["dev"] || "";
453
+ const runCmd = `bun ${scriptKey}`;
454
+ if (devScript.includes("concurrently")) {
455
+ if (!devScript.includes(runCmd)) {
456
+ const cleanDev = devScript.trim();
457
+ pkg.scripts["dev"] = `${cleanDev} "${runCmd}"`;
458
+ }
459
+ }
460
+ else {
461
+ pkg.scripts["dev"] = `concurrently --raw "bun run --watch app/app.ts" "bun skalfa watch:barrels" "${runCmd}"`;
462
+ }
463
+ }
464
+ node_fs_1.default.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2), "utf8");
465
+ }
293
466
  }
294
467
  finally {
295
468
  cleanup();
@@ -105,9 +105,16 @@ async function createApi(projectName) {
105
105
  // Cleanup temp extract folder
106
106
  node_fs_1.default.rmSync(tempExtractDir, { recursive: true, force: true });
107
107
  }
108
- // Cleanup git directory if present and rename package
108
+ // Cleanup git and github directories if present and rename package
109
109
  (0, fs_1.removeDirectory)(node_path_1.default.join(target, ".git"));
110
+ (0, fs_1.removeDirectory)(node_path_1.default.join(target, ".github"));
110
111
  renamePackage(target, packageName);
112
+ // Rename .npmignore to .gitignore if it exists (npm renames .gitignore to .npmignore during pack/publish)
113
+ const npmignorePath = node_path_1.default.join(target, ".npmignore");
114
+ const gitignorePath = node_path_1.default.join(target, ".gitignore");
115
+ if (node_fs_1.default.existsSync(npmignorePath) && !node_fs_1.default.existsSync(gitignorePath)) {
116
+ node_fs_1.default.renameSync(npmignorePath, gitignorePath);
117
+ }
111
118
  // Customize project with selected options
112
119
  customizeProject(target, {
113
120
  redis: finalRedis,
@@ -0,0 +1,257 @@
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.createApp = createApp;
7
+ const node_path_1 = __importDefault(require("node:path"));
8
+ const node_fs_1 = __importDefault(require("node:fs"));
9
+ const node_child_process_1 = require("node:child_process");
10
+ const node_readline_1 = __importDefault(require("node:readline"));
11
+ const npm_1 = require("../utils/npm");
12
+ const installer_1 = require("../utils/installer");
13
+ const fs_1 = require("../utils/fs");
14
+ const copier_1 = require("../utils/copier");
15
+ const TEMPLATE_ENV_KEY = "SKALFA_APP_TEMPLATE";
16
+ class Questioner {
17
+ rl;
18
+ constructor() {
19
+ this.rl = node_readline_1.default.createInterface({
20
+ input: process.stdin,
21
+ output: process.stdout,
22
+ });
23
+ }
24
+ ask(query) {
25
+ return new Promise((resolve) => {
26
+ this.rl.question(query, (answer) => {
27
+ resolve(answer);
28
+ });
29
+ });
30
+ }
31
+ close() {
32
+ this.rl.close();
33
+ }
34
+ }
35
+ async function createApp(projectName) {
36
+ const cwd = process.cwd();
37
+ const target = node_path_1.default.resolve(cwd, projectName);
38
+ const packageName = node_path_1.default.basename(target);
39
+ (0, fs_1.assertInsideDirectory)(cwd, target);
40
+ if ((0, fs_1.exists)(target)) {
41
+ throw new Error(`Target directory already exists: ${target}`);
42
+ }
43
+ // Ask interactive questions sequentially
44
+ const q = new Questioner();
45
+ let hasIdb = false;
46
+ let hasSocket = false;
47
+ let hasDocument = false;
48
+ let hasPwa = false;
49
+ let hasTauriDesktop = false;
50
+ let hasTauriMobile = false;
51
+ try {
52
+ hasIdb = (await q.ask("Do you need IndexedDB (IDB)? (y/N): ")).toLowerCase().startsWith("y");
53
+ hasSocket = (await q.ask("Do you need Socket.io Client? (y/N): ")).toLowerCase().startsWith("y");
54
+ hasDocument = (await q.ask("Do you need Document Export/Viewer (PDF/Excel)? (y/N): ")).toLowerCase().startsWith("y");
55
+ hasPwa = (await q.ask("Do you want to enable Progressive Web App (PWA)? (y/N): ")).toLowerCase().startsWith("y");
56
+ hasTauriDesktop = (await q.ask("Do you want to enable Tauri Desktop support (Windows/macOS/Linux)? (y/N): ")).toLowerCase().startsWith("y");
57
+ hasTauriMobile = (await q.ask("Do you want to enable Tauri Mobile support (Android/iOS)? (y/N): ")).toLowerCase().startsWith("y");
58
+ }
59
+ finally {
60
+ q.close();
61
+ }
62
+ const envTemplateSource = process.env[TEMPLATE_ENV_KEY];
63
+ if (envTemplateSource) {
64
+ // Local copy mode
65
+ const templateSource = node_path_1.default.resolve(envTemplateSource);
66
+ console.log(`Creating Skalfa App project from local template override: ${templateSource}`);
67
+ if (!(0, fs_1.exists)(templateSource)) {
68
+ throw new Error(`Template source override not found: ${templateSource}`);
69
+ }
70
+ (0, copier_1.copyTemplate)(templateSource, target);
71
+ }
72
+ else {
73
+ // Dynamic download from npm registry
74
+ const templatePackageName = "@skalfa/skalfa-app";
75
+ console.log(`Fetching latest template info for ${templatePackageName} from npm registry...`);
76
+ const tarballUrl = await (0, npm_1.fetchLatestTarballUrl)(templatePackageName);
77
+ const parentDir = node_path_1.default.dirname(target);
78
+ const tempExtractDir = node_path_1.default.join(parentDir, `${projectName}-temp-extract`);
79
+ if ((0, fs_1.exists)(tempExtractDir)) {
80
+ node_fs_1.default.rmSync(tempExtractDir, { recursive: true, force: true });
81
+ }
82
+ node_fs_1.default.mkdirSync(tempExtractDir, { recursive: true });
83
+ const tarballPath = node_path_1.default.join(tempExtractDir, "template.tgz");
84
+ console.log("Downloading template tarball...");
85
+ await (0, npm_1.downloadTarball)(tarballUrl, tarballPath);
86
+ console.log("Extracting template...");
87
+ try {
88
+ (0, node_child_process_1.execSync)(`tar -xzf "${tarballPath}" -C "${tempExtractDir}"`, { stdio: "ignore" });
89
+ }
90
+ catch (err) {
91
+ node_fs_1.default.rmSync(tempExtractDir, { recursive: true, force: true });
92
+ throw new Error(`Failed to extract template tarball. Please ensure 'tar' command is available: ${err.message}`);
93
+ }
94
+ const packageDir = node_path_1.default.join(tempExtractDir, "package");
95
+ if (!(0, fs_1.exists)(packageDir)) {
96
+ node_fs_1.default.rmSync(tempExtractDir, { recursive: true, force: true });
97
+ throw new Error("Invalid template structure: 'package' folder not found inside tarball.");
98
+ }
99
+ node_fs_1.default.renameSync(packageDir, target);
100
+ node_fs_1.default.rmSync(tempExtractDir, { recursive: true, force: true });
101
+ }
102
+ // Cleanup git and github directories
103
+ (0, fs_1.removeDirectory)(node_path_1.default.join(target, ".git"));
104
+ (0, fs_1.removeDirectory)(node_path_1.default.join(target, ".github"));
105
+ renamePackage(target, packageName);
106
+ // Rename .npmignore to .gitignore if it exists
107
+ const npmignorePath = node_path_1.default.join(target, ".npmignore");
108
+ const gitignorePath = node_path_1.default.join(target, ".gitignore");
109
+ if (node_fs_1.default.existsSync(npmignorePath) && !node_fs_1.default.existsSync(gitignorePath)) {
110
+ node_fs_1.default.renameSync(npmignorePath, gitignorePath);
111
+ }
112
+ // Customize project with selected options
113
+ customizeProject(target, {
114
+ idb: hasIdb,
115
+ socket: hasSocket,
116
+ document: hasDocument,
117
+ pwa: hasPwa,
118
+ tauriDesktop: hasTauriDesktop,
119
+ tauriMobile: hasTauriMobile
120
+ });
121
+ console.log("Installing dependencies...");
122
+ (0, installer_1.installDependencies)(target);
123
+ console.log("");
124
+ console.log("✓ Skalfa App Next.js project is ready.");
125
+ console.log(`Next steps:\n cd ${projectName}\n bun run dev`);
126
+ }
127
+ function renamePackage(target, packageName) {
128
+ const packageJsonPath = node_path_1.default.join(target, "package.json");
129
+ if (!(0, fs_1.exists)(packageJsonPath)) {
130
+ console.warn("Skipped package rename: package.json was not found.");
131
+ return;
132
+ }
133
+ const packageJson = (0, fs_1.readJsonFile)(packageJsonPath);
134
+ packageJson.name = packageName;
135
+ (0, fs_1.writeJsonFile)(packageJsonPath, packageJson);
136
+ }
137
+ function customizeProject(target, opts) {
138
+ const packageJsonPath = node_path_1.default.join(target, "package.json");
139
+ const baseComponentsIndexPath = node_path_1.default.join(target, "components", "base.components", "index.ts");
140
+ const isDev = !!process.env[TEMPLATE_ENV_KEY];
141
+ // 1. Update dependencies and scripts in package.json
142
+ if (node_fs_1.default.existsSync(packageJsonPath)) {
143
+ const pkg = JSON.parse(node_fs_1.default.readFileSync(packageJsonPath, "utf8"));
144
+ pkg.dependencies = pkg.dependencies || {};
145
+ pkg.devDependencies = pkg.devDependencies || {};
146
+ pkg.scripts = pkg.scripts || {};
147
+ // Core dependency
148
+ pkg.dependencies["@skalfa/skalfa-app-core"] = isDev ? "file:../skalfa-app-core" : "^1.0.0";
149
+ // A. IndexedDB Option
150
+ if (opts.idb) {
151
+ pkg.dependencies["@skalfa/skalfa-idb"] = isDev ? "file:../skalfa-idb" : "^1.0.0";
152
+ }
153
+ else {
154
+ // Delete schema directory
155
+ const schemaDir = node_path_1.default.join(target, "schema");
156
+ if (node_fs_1.default.existsSync(schemaDir)) {
157
+ node_fs_1.default.rmSync(schemaDir, { recursive: true, force: true });
158
+ }
159
+ // Delete IDBProvider component
160
+ const idbProviderPath = node_path_1.default.join(target, "components", "base.components", "wrap", "IDBProvider.tsx");
161
+ if (node_fs_1.default.existsSync(idbProviderPath)) {
162
+ node_fs_1.default.unlinkSync(idbProviderPath);
163
+ }
164
+ // Remove export from base.components/index.ts
165
+ if (node_fs_1.default.existsSync(baseComponentsIndexPath)) {
166
+ let content = node_fs_1.default.readFileSync(baseComponentsIndexPath, "utf8");
167
+ content = content.replace(/export \* from "\.\/wrap\/IDBProvider";\r?\n?/g, "");
168
+ node_fs_1.default.writeFileSync(baseComponentsIndexPath, content, "utf8");
169
+ }
170
+ // Modify app/layout.tsx to remove IDBProvider wrapper
171
+ const layoutPath = node_path_1.default.join(target, "app", "layout.tsx");
172
+ if (node_fs_1.default.existsSync(layoutPath)) {
173
+ let content = node_fs_1.default.readFileSync(layoutPath, "utf8");
174
+ content = content
175
+ .replace(/import\s*\{\s*IDBProvider\s*,\s*ShortcutProvider\s*\}\s*from\s*["']@components["'];?/g, 'import { ShortcutProvider } from "@components";')
176
+ .replace(/<IDBProvider>\s*\r?\n?/g, "")
177
+ .replace(/<\/IDBProvider>\s*\r?\n?/g, "");
178
+ node_fs_1.default.writeFileSync(layoutPath, content, "utf8");
179
+ }
180
+ }
181
+ // B. Socket Option
182
+ if (opts.socket) {
183
+ pkg.dependencies["@skalfa/skalfa-socket-client"] = isDev ? "file:../skalfa-socket-client" : "^1.0.0";
184
+ pkg.dependencies["socket.io-client"] = "^4.8.1";
185
+ }
186
+ // C. Document Option
187
+ if (opts.document) {
188
+ pkg.dependencies["@skalfa/skalfa-document"] = isDev ? "file:../skalfa-document" : "^1.0.0";
189
+ pkg.dependencies["exceljs"] = "^4.4.0";
190
+ pkg.dependencies["pdf-lib"] = "^1.17.1";
191
+ pkg.dependencies["pdfjs-dist"] = "^4.4.168";
192
+ }
193
+ else {
194
+ // Delete local document folder
195
+ const documentDir = node_path_1.default.join(target, "components", "base.components", "document");
196
+ if (node_fs_1.default.existsSync(documentDir)) {
197
+ node_fs_1.default.rmSync(documentDir, { recursive: true, force: true });
198
+ }
199
+ // Delete public pdf worker
200
+ const workerPath = node_path_1.default.join(target, "public", "pdf.worker.min.mjs");
201
+ if (node_fs_1.default.existsSync(workerPath)) {
202
+ node_fs_1.default.unlinkSync(workerPath);
203
+ }
204
+ // Remove exports from base.components/index.ts
205
+ if (node_fs_1.default.existsSync(baseComponentsIndexPath)) {
206
+ let content = node_fs_1.default.readFileSync(baseComponentsIndexPath, "utf8");
207
+ content = content
208
+ .replace(/export \* from "\.\/document\/DocumentViewer\.component";\r?\n?/g, "")
209
+ .replace(/export \* from "\.\/document\/ExportExcel\.component";\r?\n?/g, "")
210
+ .replace(/export \* from "\.\/document\/ImportExcel\.component";\r?\n?/g, "")
211
+ .replace(/export \* from "\.\/document\/PrintTable\.component";\r?\n?/g, "")
212
+ .replace(/export \* from "\.\/document\/RenderPDF\.component";\r?\n?/g, "");
213
+ node_fs_1.default.writeFileSync(baseComponentsIndexPath, content, "utf8");
214
+ }
215
+ }
216
+ // D. PWA Option
217
+ if (opts.pwa) {
218
+ pkg.dependencies["@ducanh2912/next-pwa"] = "^10.2.9";
219
+ // Wrap next.config.ts with withPWA
220
+ const nextConfigPath = node_path_1.default.join(target, "next.config.ts");
221
+ if (node_fs_1.default.existsSync(nextConfigPath)) {
222
+ let content = node_fs_1.default.readFileSync(nextConfigPath, "utf8");
223
+ if (!content.includes("@ducanh2912/next-pwa")) {
224
+ content = `import withPWAInit from "@ducanh2912/next-pwa";\n` + content;
225
+ content = content.replace(/export default nextConfig;/, `const withPWA = withPWAInit({\n dest: "public",\n disable: process.env.NODE_ENV === "development",\n});\n\nexport default withPWA(nextConfig);`);
226
+ node_fs_1.default.writeFileSync(nextConfigPath, content, "utf8");
227
+ }
228
+ }
229
+ }
230
+ else {
231
+ // Delete manifest.ts
232
+ const manifestPath = node_path_1.default.join(target, "app", "manifest.ts");
233
+ if (node_fs_1.default.existsSync(manifestPath)) {
234
+ node_fs_1.default.unlinkSync(manifestPath);
235
+ }
236
+ }
237
+ // E. Tauri Option
238
+ if (opts.tauriDesktop || opts.tauriMobile) {
239
+ pkg.dependencies["@tauri-apps/api"] = "^2.0.0";
240
+ pkg.devDependencies["@tauri-apps/cli"] = "^2.0.0";
241
+ pkg.devDependencies["cross-env"] = "^7.0.3";
242
+ pkg.scripts["tauri"] = "cross-env IS_TAURI=true tauri";
243
+ if (opts.tauriMobile) {
244
+ pkg.scripts["tauri:android"] = "cross-env IS_TAURI=true tauri android";
245
+ pkg.scripts["tauri:ios"] = "cross-env IS_TAURI=true tauri ios";
246
+ }
247
+ }
248
+ else {
249
+ // Delete src-tauri folder
250
+ const tauriDir = node_path_1.default.join(target, "src-tauri");
251
+ if (node_fs_1.default.existsSync(tauriDir)) {
252
+ node_fs_1.default.rmSync(tauriDir, { recursive: true, force: true });
253
+ }
254
+ }
255
+ node_fs_1.default.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2), "utf8");
256
+ }
257
+ }
@@ -12,15 +12,16 @@ const node_path_1 = __importDefault(require("node:path"));
12
12
  function installDependencies(target) {
13
13
  runInstall(target);
14
14
  }
15
- function installPackage(target, packageName) {
16
- runInstall(target, [packageName]);
15
+ function installPackage(target, packageName, isDev = false) {
16
+ runInstall(target, [packageName], isDev);
17
17
  }
18
- function runInstall(target, packages = []) {
18
+ function runInstall(target, packages = [], isDev = false) {
19
19
  const useBun = node_fs_1.default.existsSync(node_path_1.default.join(target, "bun.lock"));
20
20
  const pm = useBun ? "bun" : "npm";
21
21
  const action = useBun ? "add" : "install";
22
+ const devFlag = isDev ? (useBun ? "-d" : "--save-dev") : "";
22
23
  const command = packages.length > 0
23
- ? [pm, action, ...packages].join(" ")
24
+ ? [pm, action, devFlag, ...packages].filter(Boolean).join(" ")
24
25
  : [pm, "install"].join(" ");
25
26
  console.log(`Running: ${command}`);
26
27
  (0, node_child_process_1.execSync)(command, {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@skalfa/skalfa-cli",
3
- "version": "1.0.3",
4
- "description": "Skalfa API scaffolding and extension installer CLI.",
3
+ "version": "1.0.5",
4
+ "description": "Command Line Interface tool for scaffolding Skalfa projects, managing extensions, and ejecting core utilities.",
5
5
  "main": "dist/bin/skalfa.js",
6
6
  "bin": {
7
7
  "skalfa": "./dist/bin/skalfa.js"
@@ -29,4 +29,4 @@
29
29
  "@types/node": "^26.0.0",
30
30
  "typescript": "^6.0.3"
31
31
  }
32
- }
32
+ }
package/dist/bin/aluna.js DELETED
@@ -1,44 +0,0 @@
1
- #!/usr/bin/env node
2
- "use strict";
3
- Object.defineProperty(exports, "__esModule", { value: true });
4
- const commander_1 = require("commander");
5
- const add_extension_1 = require("../commands/add-extension");
6
- const create_api_1 = require("../commands/create-api");
7
- const pick_1 = require("../commands/pick");
8
- const program = new commander_1.Command();
9
- program
10
- .name("aluna")
11
- .description("Create Aluna API projects and install optional extensions.")
12
- .version("0.1.0");
13
- program
14
- .command("create-api")
15
- .description("Create a new API project from the local aluna-api template.")
16
- .argument("<name>", "project folder and package name")
17
- .action((name) => {
18
- runCommand(() => (0, create_api_1.createApi)(name));
19
- });
20
- program
21
- .command("add")
22
- .description("Install an optional Aluna extension into the current project.")
23
- .argument("<extension>", `extension name: ${add_extension_1.extensionNames.join(", ")}`)
24
- .action((extension) => {
25
- runCommand(() => (0, add_extension_1.addExtension)(extension));
26
- });
27
- program
28
- .command("pick")
29
- .description("Eject/copy a core utility from @aluna/aluna-api-core into your local utils folder for customization.")
30
- .argument("<utility>", `utility name: ${pick_1.UTILITIES.join(", ")}`)
31
- .action((utility) => {
32
- runCommand(() => (0, pick_1.pickUtility)(utility));
33
- });
34
- program.parse(process.argv);
35
- function runCommand(command) {
36
- try {
37
- command();
38
- }
39
- catch (error) {
40
- const message = error instanceof Error ? error.message : String(error);
41
- console.error(`Error: ${message}`);
42
- process.exitCode = 1;
43
- }
44
- }
package/dist/bin/kava.js DELETED
@@ -1,70 +0,0 @@
1
- #!/usr/bin/env node
2
- "use strict";
3
- var __importDefault = (this && this.__importDefault) || function (mod) {
4
- return (mod && mod.__esModule) ? mod : { "default": mod };
5
- };
6
- Object.defineProperty(exports, "__esModule", { value: true });
7
- const commander_1 = require("commander");
8
- const node_child_process_1 = require("node:child_process");
9
- const node_path_1 = __importDefault(require("node:path"));
10
- const node_fs_1 = __importDefault(require("node:fs"));
11
- const add_extension_1 = require("../commands/add-extension");
12
- const create_api_1 = require("../commands/create-api");
13
- const pick_1 = require("../commands/pick");
14
- const fs_1 = require("../utils/fs");
15
- // Dynamic routing / forwarding logic
16
- const args = process.argv.slice(2);
17
- const knownCommands = ["create-api", "add", "pick"];
18
- if (args.length > 0 && !knownCommands.includes(args[0]) && !["-h", "--help", "-v", "--version", "help"].includes(args[0])) {
19
- const projectRoot = (0, fs_1.findProjectRoot)(process.cwd());
20
- if (projectRoot) {
21
- const localCliPath = node_path_1.default.join(projectRoot, "utils", "commands", "kava.ts");
22
- if (node_fs_1.default.existsSync(localCliPath)) {
23
- try {
24
- // Forward arguments directly to local kava.ts using Bun
25
- (0, node_child_process_1.execSync)(`bun run utils/commands/kava.ts ${args.join(" ")}`, { stdio: "inherit" });
26
- process.exit(0);
27
- }
28
- catch (err) {
29
- process.exit(1);
30
- }
31
- }
32
- }
33
- }
34
- const program = new commander_1.Command();
35
- program
36
- .name("kava")
37
- .description("Create Kava API projects and install optional extensions.")
38
- .version("0.1.0");
39
- program
40
- .command("create-api")
41
- .description("Create a new Kava API project.")
42
- .argument("<name>", "project folder and package name")
43
- .action(async (name) => {
44
- await runCommand(() => (0, create_api_1.createApi)(name));
45
- });
46
- program
47
- .command("add")
48
- .description("Install an optional Kava extension into the current project.")
49
- .argument("<extension>", `extension name: ${add_extension_1.extensionNames.join(", ")}`)
50
- .action(async (extension) => {
51
- await runCommand(() => (0, add_extension_1.addExtension)(extension));
52
- });
53
- program
54
- .command("pick")
55
- .description("Eject/copy a core utility from @kava/kava-api-core into your local utils folder for customization.")
56
- .argument("<utility>", `utility name: ${pick_1.UTILITIES.join(", ")}`)
57
- .action(async (utility) => {
58
- await runCommand(() => (0, pick_1.pickUtility)(utility));
59
- });
60
- program.parse(process.argv);
61
- async function runCommand(command) {
62
- try {
63
- await command();
64
- }
65
- catch (error) {
66
- const message = error instanceof Error ? error.message : String(error);
67
- console.error(`Error: ${message}`);
68
- process.exit(1);
69
- }
70
- }