@skalfa/skalfa-cli 1.0.4 → 1.0.6
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 +171 -23
- package/dist/commands/create-app.js +257 -0
- package/dist/utils/installer.js +5 -4
- package/package.json +3 -3
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://skalfa.sejedigital.com/images/logo-skalfa.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"));
|
|
@@ -23,14 +23,135 @@ exports.extensions = {
|
|
|
23
23
|
orm: "@skalfa/skalfa-orm"
|
|
24
24
|
};
|
|
25
25
|
exports.extensionNames = Object.keys(exports.extensions);
|
|
26
|
+
exports.frontendExtensions = ["idb", "socket", "document", "pwa", "tauri-desktop", "tauri-mobile"];
|
|
26
27
|
async function addExtension(extensionName) {
|
|
27
|
-
const packageName = exports.extensions[extensionName];
|
|
28
|
-
if (!packageName) {
|
|
29
|
-
throw new Error(`Unknown extension "${extensionName}". Available extensions: ${exports.extensionNames.join(", ")}`);
|
|
30
|
-
}
|
|
31
28
|
const projectRoot = (0, fs_1.findProjectRoot)(process.cwd());
|
|
32
29
|
if (!projectRoot) {
|
|
33
|
-
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(", ")}`);
|
|
34
155
|
}
|
|
35
156
|
const isDev = !!process.env["SKALFA_API_TEMPLATE"];
|
|
36
157
|
if (extensionName === "orm") {
|
|
@@ -80,7 +201,6 @@ async function getTemplateSource(projectRoot) {
|
|
|
80
201
|
return { templateSource, cleanup: () => { } };
|
|
81
202
|
}
|
|
82
203
|
else {
|
|
83
|
-
// Dynamic download from npm registry
|
|
84
204
|
const templatePackageName = "@skalfa/skalfa-api";
|
|
85
205
|
console.log(`Fetching latest template info for ${templatePackageName} from npm registry...`);
|
|
86
206
|
const tarballUrl = await (0, npm_1.fetchLatestTarballUrl)(templatePackageName);
|
|
@@ -113,10 +233,53 @@ async function getTemplateSource(projectRoot) {
|
|
|
113
233
|
};
|
|
114
234
|
}
|
|
115
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
|
+
}
|
|
116
280
|
async function scaffoldOrmExtension(projectRoot) {
|
|
117
281
|
const { templateSource, cleanup } = await getTemplateSource(projectRoot);
|
|
118
282
|
try {
|
|
119
|
-
// 1. Copy database/ folder and app/models/ folder from template
|
|
120
283
|
console.log("Scaffolding database and model directories...");
|
|
121
284
|
const dbSrc = node_path_1.default.join(templateSource, "database");
|
|
122
285
|
const dbDest = node_path_1.default.join(projectRoot, "database");
|
|
@@ -128,7 +291,6 @@ async function scaffoldOrmExtension(projectRoot) {
|
|
|
128
291
|
if ((0, fs_1.exists)(modelsSrc)) {
|
|
129
292
|
(0, copier_1.copyTemplate)(modelsSrc, modelsDest);
|
|
130
293
|
}
|
|
131
|
-
// 2. Copy database-enabled UserController and AuthController from template
|
|
132
294
|
console.log("Restoring database-enabled controllers...");
|
|
133
295
|
const authControllerSrc = node_path_1.default.join(templateSource, "app", "controllers", "iam", "auth.controller.ts");
|
|
134
296
|
const authControllerDest = node_path_1.default.join(projectRoot, "app", "controllers", "iam", "auth.controller.ts");
|
|
@@ -142,7 +304,6 @@ async function scaffoldOrmExtension(projectRoot) {
|
|
|
142
304
|
node_fs_1.default.mkdirSync(node_path_1.default.dirname(userControllerDest), { recursive: true });
|
|
143
305
|
node_fs_1.default.copyFileSync(userControllerSrc, userControllerDest);
|
|
144
306
|
}
|
|
145
|
-
// 3. Restore database CLI commands loader if missing
|
|
146
307
|
console.log("Restoring database CLI commands...");
|
|
147
308
|
const commandsSrc = node_path_1.default.join(templateSource, "utils", "commands");
|
|
148
309
|
const commandsDest = node_path_1.default.join(projectRoot, "utils", "commands");
|
|
@@ -154,7 +315,6 @@ async function scaffoldOrmExtension(projectRoot) {
|
|
|
154
315
|
node_fs_1.default.copyFileSync(skalfaCliSrc, skalfaCliDest);
|
|
155
316
|
}
|
|
156
317
|
}
|
|
157
|
-
// 4. Restore the database initialization block and imports in app/app.ts
|
|
158
318
|
console.log("Restoring database initialization in app/app.ts...");
|
|
159
319
|
const appTsSrc = node_path_1.default.join(templateSource, "app", "app.ts");
|
|
160
320
|
const appTsDest = node_path_1.default.join(projectRoot, "app", "app.ts");
|
|
@@ -172,7 +332,6 @@ async function scaffoldOrmExtension(projectRoot) {
|
|
|
172
332
|
node_fs_1.default.writeFileSync(appTsDest, targetAppTs, "utf8");
|
|
173
333
|
}
|
|
174
334
|
}
|
|
175
|
-
// 5. Update tsconfig.json path mappings
|
|
176
335
|
console.log("Updating tsconfig.json paths...");
|
|
177
336
|
const tsconfigPath = node_path_1.default.join(projectRoot, "tsconfig.json");
|
|
178
337
|
if ((0, fs_1.exists)(tsconfigPath)) {
|
|
@@ -182,7 +341,6 @@ async function scaffoldOrmExtension(projectRoot) {
|
|
|
182
341
|
node_fs_1.default.writeFileSync(tsconfigPath, content, "utf8");
|
|
183
342
|
}
|
|
184
343
|
}
|
|
185
|
-
// 6. Update utils/index.ts exports
|
|
186
344
|
console.log("Updating utils/index.ts exports...");
|
|
187
345
|
const utilsIndexPath = node_path_1.default.join(projectRoot, "utils", "index.ts");
|
|
188
346
|
if ((0, fs_1.exists)(utilsIndexPath)) {
|
|
@@ -206,13 +364,11 @@ async function scaffoldUtilityExtension(projectRoot, ext) {
|
|
|
206
364
|
const tsconfigPath = node_path_1.default.join(projectRoot, "tsconfig.json");
|
|
207
365
|
const utilsIndexPath = node_path_1.default.join(projectRoot, "utils", "index.ts");
|
|
208
366
|
const appTsPath = node_path_1.default.join(projectRoot, "app", "app.ts");
|
|
209
|
-
// 1. Copy relevant template folders
|
|
210
367
|
if (ext === "queue") {
|
|
211
368
|
console.log("Copying Queue worker examples...");
|
|
212
369
|
const src = node_path_1.default.join(templateSource, "app", "jobs", "queues");
|
|
213
370
|
const dest = node_path_1.default.join(projectRoot, "app", "jobs", "queues");
|
|
214
371
|
(0, copier_1.copyTemplate)(src, dest);
|
|
215
|
-
// Check if project has DA or Notification packages
|
|
216
372
|
const packageJsonPath = node_path_1.default.join(projectRoot, "package.json");
|
|
217
373
|
let hasDa = false;
|
|
218
374
|
let hasNotification = false;
|
|
@@ -242,31 +398,25 @@ async function scaffoldUtilityExtension(projectRoot, ext) {
|
|
|
242
398
|
const dest = node_path_1.default.join(projectRoot, "database", "da.migrations");
|
|
243
399
|
(0, copier_1.copyTemplate)(src, dest);
|
|
244
400
|
}
|
|
245
|
-
// 2. Update tsconfig.json path mappings
|
|
246
401
|
addTsconfigPath(tsconfigPath, `@skalfa/skalfa-${ext}`);
|
|
247
402
|
if (ext === "queue" || ext === "cache") {
|
|
248
403
|
addTsconfigPath(tsconfigPath, "@skalfa/skalfa-redis");
|
|
249
404
|
}
|
|
250
|
-
// 3. Update utils/index.ts exports
|
|
251
405
|
addUtilExport(utilsIndexPath, `@skalfa/skalfa-${ext}`);
|
|
252
406
|
if (ext === "queue" || ext === "cache") {
|
|
253
407
|
addUtilExport(utilsIndexPath, "@skalfa/skalfa-redis");
|
|
254
408
|
}
|
|
255
|
-
// 4. Uncomment initialization blocks and update imports in app/app.ts
|
|
256
409
|
if (node_fs_1.default.existsSync(appTsPath)) {
|
|
257
410
|
let content = node_fs_1.default.readFileSync(appTsPath, "utf8");
|
|
258
411
|
const importsToAdd = [];
|
|
259
412
|
if (ext === "redis" || ext === "queue" || ext === "cache") {
|
|
260
413
|
importsToAdd.push("redis");
|
|
261
|
-
// Uncomment Redis block
|
|
262
414
|
content = content.replace(/\/\/ if \(process\.env\.REDIS_HOST[\s\S]*?\/\/ \}/g, (match) => match.replace(/^\/\/ ?/gm, ""));
|
|
263
415
|
}
|
|
264
416
|
if (ext === "da") {
|
|
265
417
|
importsToAdd.push("daClient");
|
|
266
|
-
// Uncomment DA block
|
|
267
418
|
content = content.replace(/\/\/ if \(process\.env\.DA_HOST[\s\S]*?\/\/ }/g, (match) => match.replace(/^\/\/ ?/gm, ""));
|
|
268
419
|
}
|
|
269
|
-
// Update import statement at the top of app.ts
|
|
270
420
|
if (importsToAdd.length > 0) {
|
|
271
421
|
const importRegex = /import\s*\{\s*([\s\S]*?)\s*\}\s*from\s*["']@utils["']/;
|
|
272
422
|
const match = content.match(importRegex);
|
|
@@ -279,7 +429,6 @@ async function scaffoldUtilityExtension(projectRoot, ext) {
|
|
|
279
429
|
}
|
|
280
430
|
node_fs_1.default.writeFileSync(appTsPath, content, "utf8");
|
|
281
431
|
}
|
|
282
|
-
// 5. Update package.json scripts for workers
|
|
283
432
|
const packageJsonPath = node_path_1.default.join(projectRoot, "package.json");
|
|
284
433
|
if (node_fs_1.default.existsSync(packageJsonPath) && ["cron", "queue", "socket"].includes(ext)) {
|
|
285
434
|
const pkg = JSON.parse(node_fs_1.default.readFileSync(packageJsonPath, "utf8"));
|
|
@@ -300,7 +449,6 @@ async function scaffoldUtilityExtension(projectRoot, ext) {
|
|
|
300
449
|
}
|
|
301
450
|
if (scriptKey) {
|
|
302
451
|
pkg.scripts[scriptKey] = scriptVal;
|
|
303
|
-
// Update dev concurrently command
|
|
304
452
|
const devScript = pkg.scripts["dev"] || "";
|
|
305
453
|
const runCmd = `bun ${scriptKey}`;
|
|
306
454
|
if (devScript.includes("concurrently")) {
|
|
@@ -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.6",
|
|
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
|
+
}
|