@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 +21 -0
- package/README.md +69 -0
- package/dist/bin/skalfa.js +9 -1
- package/dist/commands/add-extension.js +205 -32
- package/dist/commands/create-api.js +8 -1
- package/dist/commands/create-app.js +257 -0
- package/dist/utils/installer.js +5 -4
- package/package.json +3 -3
- package/dist/bin/aluna.js +0 -44
- package/dist/bin/kava.js +0 -70
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.
|
package/dist/bin/skalfa.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
+
}
|
package/dist/utils/installer.js
CHANGED
|
@@ -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.
|
|
4
|
-
"description": "
|
|
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
|
-
}
|