@skalfa/skalfa-cli 1.0.5 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/bin/skalfa.js +29 -3
- package/dist/commands/create-api.js +84 -68
- package/dist/commands/create-app.js +81 -65
- package/dist/commands/pick.js +7 -0
- package/dist/commands/update.js +76 -0
- package/dist/utils/installer.js +23 -0
- package/dist/utils/npm.js +33 -0
- package/dist/utils/spinner.js +41 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="https://
|
|
2
|
+
<img src="https://skalfa.sejedigital.com/images/logo-skalfa.png" alt="Skalfa Logo" width="300" />
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
# @skalfa/skalfa-cli
|
package/dist/bin/skalfa.js
CHANGED
|
@@ -12,10 +12,11 @@ const add_extension_1 = require("../commands/add-extension");
|
|
|
12
12
|
const create_api_1 = require("../commands/create-api");
|
|
13
13
|
const create_app_1 = require("../commands/create-app");
|
|
14
14
|
const pick_1 = require("../commands/pick");
|
|
15
|
+
const update_1 = require("../commands/update");
|
|
15
16
|
const fs_1 = require("../utils/fs");
|
|
16
17
|
// Dynamic routing / forwarding logic
|
|
17
18
|
const args = process.argv.slice(2);
|
|
18
|
-
const knownCommands = ["create-api", "create-app", "add", "pick"];
|
|
19
|
+
const knownCommands = ["create-api", "create-app", "add", "pick", "update"];
|
|
19
20
|
if (args.length > 0 && !knownCommands.includes(args[0]) && !["-h", "--help", "-v", "--version", "help"].includes(args[0])) {
|
|
20
21
|
const projectRoot = (0, fs_1.findProjectRoot)(process.cwd());
|
|
21
22
|
if (projectRoot) {
|
|
@@ -32,11 +33,30 @@ if (args.length > 0 && !knownCommands.includes(args[0]) && !["-h", "--help", "-v
|
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
35
|
}
|
|
36
|
+
const packageJsonPath = node_path_1.default.join(__dirname, "..", "..", "package.json");
|
|
37
|
+
let version = "1.0.0";
|
|
38
|
+
try {
|
|
39
|
+
const packageJson = JSON.parse(node_fs_1.default.readFileSync(packageJsonPath, "utf8"));
|
|
40
|
+
version = packageJson.version;
|
|
41
|
+
}
|
|
42
|
+
catch (e) {
|
|
43
|
+
// fallback if package.json is not found
|
|
44
|
+
}
|
|
35
45
|
const program = new commander_1.Command();
|
|
46
|
+
const banner = `
|
|
47
|
+
############ WELCOME TO ###############
|
|
48
|
+
_____ _____ _____ __ _____ _____
|
|
49
|
+
| __| | | _ | | | __| _ |
|
|
50
|
+
|__ | -| | |__| __| |
|
|
51
|
+
|_____|__|__|__|__|_____|__| |__|__|
|
|
52
|
+
|
|
53
|
+
#######################################
|
|
54
|
+
`;
|
|
36
55
|
program
|
|
37
56
|
.name("skalfa")
|
|
38
|
-
.description("
|
|
39
|
-
.version(
|
|
57
|
+
.description("Start building with skalfa ecosystem.")
|
|
58
|
+
.version(version)
|
|
59
|
+
.addHelpText("before", banner);
|
|
40
60
|
program
|
|
41
61
|
.command("create-api")
|
|
42
62
|
.description("Create a new Skalfa API project.")
|
|
@@ -65,6 +85,12 @@ program
|
|
|
65
85
|
.action(async (utility) => {
|
|
66
86
|
await runCommand(() => (0, pick_1.pickUtility)(utility));
|
|
67
87
|
});
|
|
88
|
+
program
|
|
89
|
+
.command("update")
|
|
90
|
+
.description("Update skalfa-cli to the latest version.")
|
|
91
|
+
.action(async () => {
|
|
92
|
+
await runCommand(() => (0, update_1.updateCli)());
|
|
93
|
+
});
|
|
68
94
|
program.parse(process.argv);
|
|
69
95
|
async function runCommand(command) {
|
|
70
96
|
try {
|
|
@@ -11,6 +11,7 @@ const node_child_process_1 = require("node:child_process");
|
|
|
11
11
|
const node_readline_1 = __importDefault(require("node:readline"));
|
|
12
12
|
const npm_1 = require("../utils/npm");
|
|
13
13
|
const installer_1 = require("../utils/installer");
|
|
14
|
+
const spinner_1 = require("../utils/spinner");
|
|
14
15
|
const fs_1 = require("../utils/fs");
|
|
15
16
|
const copier_1 = require("../utils/copier");
|
|
16
17
|
const TEMPLATE_ENV_KEY = "SKALFA_API_TEMPLATE";
|
|
@@ -22,10 +23,10 @@ class Questioner {
|
|
|
22
23
|
output: process.stdout,
|
|
23
24
|
});
|
|
24
25
|
}
|
|
25
|
-
ask(query) {
|
|
26
|
+
ask(query, defaultValue = "") {
|
|
26
27
|
return new Promise((resolve) => {
|
|
27
28
|
this.rl.question(query, (answer) => {
|
|
28
|
-
resolve(answer);
|
|
29
|
+
resolve(answer.trim() || defaultValue);
|
|
29
30
|
});
|
|
30
31
|
});
|
|
31
32
|
}
|
|
@@ -50,85 +51,100 @@ async function createApi(projectName) {
|
|
|
50
51
|
let hasDa = false;
|
|
51
52
|
let hasSocket = false;
|
|
52
53
|
try {
|
|
53
|
-
hasRedis = (await q.ask("Do you need Redis? (y/N): ")).toLowerCase().startsWith("y");
|
|
54
|
-
hasQueue = (await q.ask("Do you need Queue? (y/N): ")).toLowerCase().startsWith("y");
|
|
55
|
-
hasCache = (await q.ask("Do you need Cache? (y/N): ")).toLowerCase().startsWith("y");
|
|
56
|
-
hasCron = (await q.ask("Do you need Cron? (y/N): ")).toLowerCase().startsWith("y");
|
|
57
|
-
hasDa = (await q.ask("Do you need
|
|
58
|
-
hasSocket = (await q.ask("Do you need Socket
|
|
54
|
+
hasRedis = (await q.ask("Do you need Redis? (y/N): ", "No")).toLowerCase().startsWith("y");
|
|
55
|
+
hasQueue = (await q.ask("Do you need Queue? (y/N): ", "No")).toLowerCase().startsWith("y");
|
|
56
|
+
hasCache = (await q.ask("Do you need Cache? (y/N): ", "No")).toLowerCase().startsWith("y");
|
|
57
|
+
hasCron = (await q.ask("Do you need Cron? (y/N): ", "No")).toLowerCase().startsWith("y");
|
|
58
|
+
hasDa = (await q.ask("Do you need Data Analytics? (y/N): ", "No")).toLowerCase().startsWith("y");
|
|
59
|
+
hasSocket = (await q.ask("Do you need Socket? (y/N): ", "No")).toLowerCase().startsWith("y");
|
|
59
60
|
}
|
|
60
61
|
finally {
|
|
61
62
|
q.close();
|
|
62
63
|
}
|
|
63
64
|
// Dependency relation: Queue or Cache requires Redis
|
|
64
65
|
const finalRedis = hasRedis || hasQueue || hasCache;
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
66
|
+
const spinner = new spinner_1.Spinner("Preparing project...");
|
|
67
|
+
spinner.start();
|
|
68
|
+
try {
|
|
69
|
+
const envTemplateSource = process.env[TEMPLATE_ENV_KEY];
|
|
70
|
+
if (envTemplateSource) {
|
|
71
|
+
// Local copy mode (e.g. for development / override)
|
|
72
|
+
const templateSource = node_path_1.default.resolve(envTemplateSource);
|
|
73
|
+
spinner.update(`Copying template from ${templateSource}...`);
|
|
74
|
+
if (!(0, fs_1.exists)(templateSource)) {
|
|
75
|
+
throw new Error(`Template source override not found: ${templateSource}`);
|
|
76
|
+
}
|
|
77
|
+
(0, copier_1.copyTemplate)(templateSource, target);
|
|
72
78
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
else {
|
|
80
|
+
// Dynamic download from npm registry
|
|
81
|
+
const templatePackageName = "@skalfa/skalfa-api";
|
|
82
|
+
spinner.update(`Fetching latest template info for ${templatePackageName}...`);
|
|
83
|
+
const tarballUrl = await (0, npm_1.fetchLatestTarballUrl)(templatePackageName);
|
|
84
|
+
// Create a temporary extraction directory inside parent folder of target
|
|
85
|
+
const parentDir = node_path_1.default.dirname(target);
|
|
86
|
+
const tempExtractDir = node_path_1.default.join(parentDir, `${projectName}-temp-extract`);
|
|
87
|
+
if ((0, fs_1.exists)(tempExtractDir)) {
|
|
88
|
+
node_fs_1.default.rmSync(tempExtractDir, { recursive: true, force: true });
|
|
89
|
+
}
|
|
90
|
+
node_fs_1.default.mkdirSync(tempExtractDir, { recursive: true });
|
|
91
|
+
const tarballPath = node_path_1.default.join(tempExtractDir, "template.tgz");
|
|
92
|
+
spinner.update("Downloading template tarball...");
|
|
93
|
+
await (0, npm_1.downloadTarball)(tarballUrl, tarballPath);
|
|
94
|
+
spinner.update("Extracting template...");
|
|
95
|
+
try {
|
|
96
|
+
(0, node_child_process_1.execSync)(`tar -xzf "${tarballPath}" -C "${tempExtractDir}"`, { stdio: "ignore" });
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
node_fs_1.default.rmSync(tempExtractDir, { recursive: true, force: true });
|
|
100
|
+
throw new Error(`Failed to extract template tarball. Please ensure 'tar' command is available: ${err.message}`);
|
|
101
|
+
}
|
|
102
|
+
const packageDir = node_path_1.default.join(tempExtractDir, "package");
|
|
103
|
+
if (!(0, fs_1.exists)(packageDir)) {
|
|
104
|
+
node_fs_1.default.rmSync(tempExtractDir, { recursive: true, force: true });
|
|
105
|
+
throw new Error("Invalid template structure: 'package' folder not found inside tarball.");
|
|
106
|
+
}
|
|
107
|
+
// Rename/move extracted folder to target path
|
|
108
|
+
node_fs_1.default.renameSync(packageDir, target);
|
|
109
|
+
// Cleanup temp extract folder
|
|
84
110
|
node_fs_1.default.rmSync(tempExtractDir, { recursive: true, force: true });
|
|
85
111
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
112
|
+
spinner.update("Customizing project files...");
|
|
113
|
+
// Cleanup git and github directories if present and rename package
|
|
114
|
+
(0, fs_1.removeDirectory)(node_path_1.default.join(target, ".git"));
|
|
115
|
+
(0, fs_1.removeDirectory)(node_path_1.default.join(target, ".github"));
|
|
116
|
+
const filesToDelete = ["CONTRIBUTING.md", "LICENSE"];
|
|
117
|
+
for (const file of filesToDelete) {
|
|
118
|
+
const filePath = node_path_1.default.join(target, file);
|
|
119
|
+
if (node_fs_1.default.existsSync(filePath)) {
|
|
120
|
+
node_fs_1.default.unlinkSync(filePath);
|
|
121
|
+
}
|
|
93
122
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
123
|
+
renamePackage(target, packageName);
|
|
124
|
+
// Rename .npmignore to .gitignore if it exists (npm renames .gitignore to .npmignore during pack/publish)
|
|
125
|
+
const npmignorePath = node_path_1.default.join(target, ".npmignore");
|
|
126
|
+
const gitignorePath = node_path_1.default.join(target, ".gitignore");
|
|
127
|
+
if (node_fs_1.default.existsSync(npmignorePath) && !node_fs_1.default.existsSync(gitignorePath)) {
|
|
128
|
+
node_fs_1.default.renameSync(npmignorePath, gitignorePath);
|
|
97
129
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
130
|
+
// Customize project with selected options
|
|
131
|
+
customizeProject(target, {
|
|
132
|
+
redis: finalRedis,
|
|
133
|
+
queue: hasQueue,
|
|
134
|
+
cache: hasCache,
|
|
135
|
+
cron: hasCron,
|
|
136
|
+
da: hasDa,
|
|
137
|
+
socket: hasSocket
|
|
138
|
+
});
|
|
139
|
+
spinner.update("Installing dependencies (this may take a moment)...");
|
|
140
|
+
await (0, installer_1.installDependenciesAsync)(target);
|
|
141
|
+
spinner.stop(true, "Skalfa API project is ready.");
|
|
142
|
+
console.log(`\nNext steps:\n cd ${projectName}\n bun run dev`);
|
|
107
143
|
}
|
|
108
|
-
|
|
109
|
-
|
|
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);
|
|
144
|
+
catch (error) {
|
|
145
|
+
spinner.stop(false, `Failed to prepare project: ${error.message}`);
|
|
146
|
+
throw error;
|
|
117
147
|
}
|
|
118
|
-
// Customize project with selected options
|
|
119
|
-
customizeProject(target, {
|
|
120
|
-
redis: finalRedis,
|
|
121
|
-
queue: hasQueue,
|
|
122
|
-
cache: hasCache,
|
|
123
|
-
cron: hasCron,
|
|
124
|
-
da: hasDa,
|
|
125
|
-
socket: hasSocket
|
|
126
|
-
});
|
|
127
|
-
console.log("Installing dependencies...");
|
|
128
|
-
(0, installer_1.installDependencies)(target);
|
|
129
|
-
console.log("");
|
|
130
|
-
console.log("✓ Skalfa API project is ready.");
|
|
131
|
-
console.log(`Next steps:\n cd ${projectName}\n bun run dev`);
|
|
132
148
|
}
|
|
133
149
|
function renamePackage(target, packageName) {
|
|
134
150
|
const packageJsonPath = node_path_1.default.join(target, "package.json");
|
|
@@ -10,6 +10,7 @@ const node_child_process_1 = require("node:child_process");
|
|
|
10
10
|
const node_readline_1 = __importDefault(require("node:readline"));
|
|
11
11
|
const npm_1 = require("../utils/npm");
|
|
12
12
|
const installer_1 = require("../utils/installer");
|
|
13
|
+
const spinner_1 = require("../utils/spinner");
|
|
13
14
|
const fs_1 = require("../utils/fs");
|
|
14
15
|
const copier_1 = require("../utils/copier");
|
|
15
16
|
const TEMPLATE_ENV_KEY = "SKALFA_APP_TEMPLATE";
|
|
@@ -21,10 +22,10 @@ class Questioner {
|
|
|
21
22
|
output: process.stdout,
|
|
22
23
|
});
|
|
23
24
|
}
|
|
24
|
-
ask(query) {
|
|
25
|
+
ask(query, defaultValue = "") {
|
|
25
26
|
return new Promise((resolve) => {
|
|
26
27
|
this.rl.question(query, (answer) => {
|
|
27
|
-
resolve(answer);
|
|
28
|
+
resolve(answer.trim() || defaultValue);
|
|
28
29
|
});
|
|
29
30
|
});
|
|
30
31
|
}
|
|
@@ -49,80 +50,95 @@ async function createApp(projectName) {
|
|
|
49
50
|
let hasTauriDesktop = false;
|
|
50
51
|
let hasTauriMobile = false;
|
|
51
52
|
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
|
|
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");
|
|
53
|
+
hasIdb = (await q.ask("Do you need IndexedDB (IDB)? (y/N): ", "No")).toLowerCase().startsWith("y");
|
|
54
|
+
hasSocket = (await q.ask("Do you need Socket Client? (y/N): ", "No")).toLowerCase().startsWith("y");
|
|
55
|
+
hasDocument = (await q.ask("Do you need Document Export/Viewer (PDF/Excel)? (y/N): ", "No")).toLowerCase().startsWith("y");
|
|
56
|
+
hasPwa = (await q.ask("Do you want to enable Progressive Web App (PWA)? (y/N): ", "No")).toLowerCase().startsWith("y");
|
|
57
|
+
hasTauriDesktop = (await q.ask("Do you want to enable Tauri Desktop support (Windows/macOS/Linux)? (y/N): ", "No")).toLowerCase().startsWith("y");
|
|
58
|
+
hasTauriMobile = (await q.ask("Do you want to enable Tauri Mobile support (Android/iOS)? (y/N): ", "No")).toLowerCase().startsWith("y");
|
|
58
59
|
}
|
|
59
60
|
finally {
|
|
60
61
|
q.close();
|
|
61
62
|
}
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
const spinner = new spinner_1.Spinner("Preparing project...");
|
|
64
|
+
spinner.start();
|
|
65
|
+
try {
|
|
66
|
+
const envTemplateSource = process.env[TEMPLATE_ENV_KEY];
|
|
67
|
+
if (envTemplateSource) {
|
|
68
|
+
// Local copy mode
|
|
69
|
+
const templateSource = node_path_1.default.resolve(envTemplateSource);
|
|
70
|
+
spinner.update(`Copying template from ${templateSource}...`);
|
|
71
|
+
if (!(0, fs_1.exists)(templateSource)) {
|
|
72
|
+
throw new Error(`Template source override not found: ${templateSource}`);
|
|
73
|
+
}
|
|
74
|
+
(0, copier_1.copyTemplate)(templateSource, target);
|
|
69
75
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
else {
|
|
77
|
+
// Dynamic download from npm registry
|
|
78
|
+
const templatePackageName = "@skalfa/skalfa-app";
|
|
79
|
+
spinner.update(`Fetching latest template info for ${templatePackageName}...`);
|
|
80
|
+
const tarballUrl = await (0, npm_1.fetchLatestTarballUrl)(templatePackageName);
|
|
81
|
+
const parentDir = node_path_1.default.dirname(target);
|
|
82
|
+
const tempExtractDir = node_path_1.default.join(parentDir, `${projectName}-temp-extract`);
|
|
83
|
+
if ((0, fs_1.exists)(tempExtractDir)) {
|
|
84
|
+
node_fs_1.default.rmSync(tempExtractDir, { recursive: true, force: true });
|
|
85
|
+
}
|
|
86
|
+
node_fs_1.default.mkdirSync(tempExtractDir, { recursive: true });
|
|
87
|
+
const tarballPath = node_path_1.default.join(tempExtractDir, "template.tgz");
|
|
88
|
+
spinner.update("Downloading template tarball...");
|
|
89
|
+
await (0, npm_1.downloadTarball)(tarballUrl, tarballPath);
|
|
90
|
+
spinner.update("Extracting template...");
|
|
91
|
+
try {
|
|
92
|
+
(0, node_child_process_1.execSync)(`tar -xzf "${tarballPath}" -C "${tempExtractDir}"`, { stdio: "ignore" });
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
node_fs_1.default.rmSync(tempExtractDir, { recursive: true, force: true });
|
|
96
|
+
throw new Error(`Failed to extract template tarball. Please ensure 'tar' command is available: ${err.message}`);
|
|
97
|
+
}
|
|
98
|
+
const packageDir = node_path_1.default.join(tempExtractDir, "package");
|
|
99
|
+
if (!(0, fs_1.exists)(packageDir)) {
|
|
100
|
+
node_fs_1.default.rmSync(tempExtractDir, { recursive: true, force: true });
|
|
101
|
+
throw new Error("Invalid template structure: 'package' folder not found inside tarball.");
|
|
102
|
+
}
|
|
103
|
+
node_fs_1.default.renameSync(packageDir, target);
|
|
80
104
|
node_fs_1.default.rmSync(tempExtractDir, { recursive: true, force: true });
|
|
81
105
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
throw new Error(`Failed to extract template tarball. Please ensure 'tar' command is available: ${err.message}`);
|
|
106
|
+
spinner.update("Customizing project files...");
|
|
107
|
+
// Cleanup git and github directories
|
|
108
|
+
(0, fs_1.removeDirectory)(node_path_1.default.join(target, ".git"));
|
|
109
|
+
(0, fs_1.removeDirectory)(node_path_1.default.join(target, ".github"));
|
|
110
|
+
const filesToDelete = ["CONTRIBUTING.md", "LICENSE"];
|
|
111
|
+
for (const file of filesToDelete) {
|
|
112
|
+
const filePath = node_path_1.default.join(target, file);
|
|
113
|
+
if (node_fs_1.default.existsSync(filePath)) {
|
|
114
|
+
node_fs_1.default.unlinkSync(filePath);
|
|
115
|
+
}
|
|
93
116
|
}
|
|
94
|
-
|
|
95
|
-
if
|
|
96
|
-
|
|
97
|
-
|
|
117
|
+
renamePackage(target, packageName);
|
|
118
|
+
// Rename .npmignore to .gitignore if it exists
|
|
119
|
+
const npmignorePath = node_path_1.default.join(target, ".npmignore");
|
|
120
|
+
const gitignorePath = node_path_1.default.join(target, ".gitignore");
|
|
121
|
+
if (node_fs_1.default.existsSync(npmignorePath) && !node_fs_1.default.existsSync(gitignorePath)) {
|
|
122
|
+
node_fs_1.default.renameSync(npmignorePath, gitignorePath);
|
|
98
123
|
}
|
|
99
|
-
|
|
100
|
-
|
|
124
|
+
// Customize project with selected options
|
|
125
|
+
customizeProject(target, {
|
|
126
|
+
idb: hasIdb,
|
|
127
|
+
socket: hasSocket,
|
|
128
|
+
document: hasDocument,
|
|
129
|
+
pwa: hasPwa,
|
|
130
|
+
tauriDesktop: hasTauriDesktop,
|
|
131
|
+
tauriMobile: hasTauriMobile
|
|
132
|
+
});
|
|
133
|
+
spinner.update("Installing dependencies (this may take a moment)...");
|
|
134
|
+
await (0, installer_1.installDependenciesAsync)(target);
|
|
135
|
+
spinner.stop(true, "Skalfa App Next.js project is ready.");
|
|
136
|
+
console.log(`\nNext steps:\n cd ${projectName}\n bun run dev`);
|
|
101
137
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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);
|
|
138
|
+
catch (error) {
|
|
139
|
+
spinner.stop(false, `Failed to prepare project: ${error.message}`);
|
|
140
|
+
throw error;
|
|
111
141
|
}
|
|
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
142
|
}
|
|
127
143
|
function renamePackage(target, packageName) {
|
|
128
144
|
const packageJsonPath = node_path_1.default.join(target, "package.json");
|
package/dist/commands/pick.js
CHANGED
|
@@ -51,6 +51,13 @@ function pickUtility(utilityName) {
|
|
|
51
51
|
// 2. Salin folder dari node_modules ke lokal proyek secara rekursif
|
|
52
52
|
console.log(`Copying ${utilityName} folder from @skalfa/skalfa-api-core to utils/ ...`);
|
|
53
53
|
node_fs_1.default.cpSync(sourceDir, targetDir, { recursive: true });
|
|
54
|
+
const filesToDelete = ["CONTRIBUTING.md", "LICENSE"];
|
|
55
|
+
for (const file of filesToDelete) {
|
|
56
|
+
const filePath = node_path_1.default.join(targetDir, file);
|
|
57
|
+
if (node_fs_1.default.existsSync(filePath)) {
|
|
58
|
+
node_fs_1.default.unlinkSync(filePath);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
54
61
|
console.log(`✓ Copied ${utilityName} folder`);
|
|
55
62
|
// 3. Perbarui utils/index.ts untuk mereferensikan folder lokal
|
|
56
63
|
console.log("Updating utils/index.ts with explicit local export override ...");
|
|
@@ -0,0 +1,76 @@
|
|
|
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.updateCli = updateCli;
|
|
7
|
+
const node_child_process_1 = require("node:child_process");
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const npm_1 = require("../utils/npm");
|
|
11
|
+
const PACKAGE_NAME = "@skalfa/skalfa-cli";
|
|
12
|
+
async function updateCli() {
|
|
13
|
+
// 1. Get current version
|
|
14
|
+
const packageJsonPath = node_path_1.default.join(__dirname, "..", "..", "package.json");
|
|
15
|
+
let currentVersion = "0.0.0";
|
|
16
|
+
try {
|
|
17
|
+
const packageJson = JSON.parse(node_fs_1.default.readFileSync(packageJsonPath, "utf8"));
|
|
18
|
+
currentVersion = packageJson.version;
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
throw new Error(`Failed to read local package.json from ${packageJsonPath}: ${err.message}`);
|
|
22
|
+
}
|
|
23
|
+
console.log(`Current version: v${currentVersion}`);
|
|
24
|
+
console.log("Checking for updates...");
|
|
25
|
+
// 2. Fetch latest version from registry
|
|
26
|
+
let latestVersion;
|
|
27
|
+
try {
|
|
28
|
+
latestVersion = await (0, npm_1.fetchLatestVersion)(PACKAGE_NAME);
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
throw new Error(`Failed to fetch latest version: ${err.message}`);
|
|
32
|
+
}
|
|
33
|
+
console.log(`Latest version: v${latestVersion}`);
|
|
34
|
+
if (currentVersion === latestVersion) {
|
|
35
|
+
console.log(`\n✓ ${PACKAGE_NAME} is already up to date.`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
console.log(`\nUpdating ${PACKAGE_NAME} from v${currentVersion} to v${latestVersion}...`);
|
|
39
|
+
// 3. Detect package manager and install
|
|
40
|
+
const scriptPath = process.argv[1] || "";
|
|
41
|
+
const normalizedPath = scriptPath.toLowerCase();
|
|
42
|
+
let packageManager = "npm";
|
|
43
|
+
if (normalizedPath.includes(".bun") || normalizedPath.includes("bun")) {
|
|
44
|
+
packageManager = "bun";
|
|
45
|
+
}
|
|
46
|
+
else if (normalizedPath.includes("yarn")) {
|
|
47
|
+
packageManager = "yarn";
|
|
48
|
+
}
|
|
49
|
+
else if (normalizedPath.includes("pnpm")) {
|
|
50
|
+
packageManager = "pnpm";
|
|
51
|
+
}
|
|
52
|
+
else if (typeof process.versions?.bun !== "undefined") {
|
|
53
|
+
packageManager = "bun";
|
|
54
|
+
}
|
|
55
|
+
let updateCommand = "";
|
|
56
|
+
if (packageManager === "bun") {
|
|
57
|
+
updateCommand = `bun install -g ${PACKAGE_NAME}@latest`;
|
|
58
|
+
}
|
|
59
|
+
else if (packageManager === "yarn") {
|
|
60
|
+
updateCommand = `yarn global add ${PACKAGE_NAME}@latest`;
|
|
61
|
+
}
|
|
62
|
+
else if (packageManager === "pnpm") {
|
|
63
|
+
updateCommand = `pnpm add -g ${PACKAGE_NAME}@latest`;
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
updateCommand = `npm install -g ${PACKAGE_NAME}@latest`;
|
|
67
|
+
}
|
|
68
|
+
console.log(`Running: ${updateCommand}`);
|
|
69
|
+
try {
|
|
70
|
+
(0, node_child_process_1.execSync)(updateCommand, { stdio: "inherit" });
|
|
71
|
+
console.log(`\n✓ Successfully updated ${PACKAGE_NAME} to v${latestVersion}!`);
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
throw new Error(`Failed to run update command: ${err.message}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
package/dist/utils/installer.js
CHANGED
|
@@ -4,14 +4,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.installDependencies = installDependencies;
|
|
7
|
+
exports.installDependenciesAsync = installDependenciesAsync;
|
|
7
8
|
exports.installPackage = installPackage;
|
|
8
9
|
exports.runInstall = runInstall;
|
|
10
|
+
exports.runInstallAsync = runInstallAsync;
|
|
9
11
|
const node_child_process_1 = require("node:child_process");
|
|
10
12
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
11
13
|
const node_path_1 = __importDefault(require("node:path"));
|
|
12
14
|
function installDependencies(target) {
|
|
13
15
|
runInstall(target);
|
|
14
16
|
}
|
|
17
|
+
function installDependenciesAsync(target) {
|
|
18
|
+
return runInstallAsync(target);
|
|
19
|
+
}
|
|
15
20
|
function installPackage(target, packageName, isDev = false) {
|
|
16
21
|
runInstall(target, [packageName], isDev);
|
|
17
22
|
}
|
|
@@ -29,3 +34,21 @@ function runInstall(target, packages = [], isDev = false) {
|
|
|
29
34
|
stdio: "inherit"
|
|
30
35
|
});
|
|
31
36
|
}
|
|
37
|
+
function runInstallAsync(target, packages = [], isDev = false) {
|
|
38
|
+
return new Promise((resolve, reject) => {
|
|
39
|
+
const useBun = node_fs_1.default.existsSync(node_path_1.default.join(target, "bun.lock"));
|
|
40
|
+
const pm = useBun ? "bun" : "npm";
|
|
41
|
+
const action = useBun ? "add" : "install";
|
|
42
|
+
const devFlag = isDev ? (useBun ? "-d" : "--save-dev") : "";
|
|
43
|
+
const command = packages.length > 0
|
|
44
|
+
? [pm, action, devFlag, ...packages].filter(Boolean).join(" ")
|
|
45
|
+
: [pm, "install"].join(" ");
|
|
46
|
+
(0, node_child_process_1.exec)(command, { cwd: target }, (error, stdout, stderr) => {
|
|
47
|
+
if (error) {
|
|
48
|
+
reject(new Error(`Installation failed: ${stderr || stdout || error.message}`));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
resolve();
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
}
|
package/dist/utils/npm.js
CHANGED
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.fetchLatestTarballUrl = fetchLatestTarballUrl;
|
|
7
7
|
exports.downloadTarball = downloadTarball;
|
|
8
|
+
exports.fetchLatestVersion = fetchLatestVersion;
|
|
8
9
|
const node_https_1 = __importDefault(require("node:https"));
|
|
9
10
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
10
11
|
function fetchLatestTarballUrl(packageName) {
|
|
@@ -61,3 +62,35 @@ function downloadTarball(url, destPath) {
|
|
|
61
62
|
});
|
|
62
63
|
});
|
|
63
64
|
}
|
|
65
|
+
function fetchLatestVersion(packageName) {
|
|
66
|
+
return new Promise((resolve, reject) => {
|
|
67
|
+
const encodedPackageName = packageName.replace("/", "%2f");
|
|
68
|
+
const url = `https://registry.npmjs.org/${encodedPackageName}/latest`;
|
|
69
|
+
node_https_1.default.get(url, { headers: { "User-Agent": "skalfa-cli" } }, (res) => {
|
|
70
|
+
if (res.statusCode !== 200) {
|
|
71
|
+
reject(new Error(`Failed to fetch latest version info for ${packageName} from npm registry. Status: ${res.statusCode} ${res.statusMessage}`));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
let data = "";
|
|
75
|
+
res.on("data", (chunk) => {
|
|
76
|
+
data += chunk;
|
|
77
|
+
});
|
|
78
|
+
res.on("end", () => {
|
|
79
|
+
try {
|
|
80
|
+
const json = JSON.parse(data);
|
|
81
|
+
const version = json.version;
|
|
82
|
+
if (!version) {
|
|
83
|
+
reject(new Error(`Could not find version in registry response for ${packageName}.`));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
resolve(version);
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
reject(err);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}).on("error", (err) => {
|
|
93
|
+
reject(new Error(`Network error while contacting npm registry: ${err.message}`));
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Spinner = void 0;
|
|
4
|
+
class Spinner {
|
|
5
|
+
timer = null;
|
|
6
|
+
message;
|
|
7
|
+
frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
8
|
+
currentFrame = 0;
|
|
9
|
+
constructor(message) {
|
|
10
|
+
this.message = message;
|
|
11
|
+
}
|
|
12
|
+
start() {
|
|
13
|
+
// Hide cursor
|
|
14
|
+
process.stdout.write("\x1B[?25l");
|
|
15
|
+
this.timer = setInterval(() => {
|
|
16
|
+
const frame = this.frames[this.currentFrame];
|
|
17
|
+
process.stdout.write(`\r\x1B[35m${frame}\x1B[0m ${this.message}`);
|
|
18
|
+
this.currentFrame = (this.currentFrame + 1) % this.frames.length;
|
|
19
|
+
}, 80);
|
|
20
|
+
}
|
|
21
|
+
update(message) {
|
|
22
|
+
this.message = message;
|
|
23
|
+
}
|
|
24
|
+
stop(success = true, statusMessage) {
|
|
25
|
+
if (this.timer) {
|
|
26
|
+
clearInterval(this.timer);
|
|
27
|
+
this.timer = null;
|
|
28
|
+
}
|
|
29
|
+
// Clear line
|
|
30
|
+
process.stdout.write("\r\x1B[K");
|
|
31
|
+
// Show cursor
|
|
32
|
+
process.stdout.write("\x1B[?25h");
|
|
33
|
+
if (success) {
|
|
34
|
+
console.log(`\x1B[32m✓\x1B[0m ${statusMessage || this.message}`);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
console.log(`\x1B[31m✗\x1B[0m ${statusMessage || this.message}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.Spinner = Spinner;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skalfa/skalfa-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
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": {
|
|
@@ -29,4 +29,4 @@
|
|
|
29
29
|
"@types/node": "^26.0.0",
|
|
30
30
|
"typescript": "^6.0.3"
|
|
31
31
|
}
|
|
32
|
-
}
|
|
32
|
+
}
|