@skalfa/skalfa-cli 1.0.2
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/dist/bin/aluna.js +44 -0
- package/dist/bin/kava.js +70 -0
- package/dist/bin/skalfa.js +70 -0
- package/dist/commands/add-extension.js +349 -0
- package/dist/commands/create-api.js +317 -0
- package/dist/commands/pick.js +68 -0
- package/dist/utils/copier.js +41 -0
- package/dist/utils/fs.js +53 -0
- package/dist/utils/installer.js +30 -0
- package/dist/utils/npm.js +63 -0
- package/package.json +32 -0
|
@@ -0,0 +1,44 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
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", "skalfa.ts");
|
|
22
|
+
if (node_fs_1.default.existsSync(localCliPath)) {
|
|
23
|
+
try {
|
|
24
|
+
// Forward arguments directly to local skalfa.ts using Bun
|
|
25
|
+
(0, node_child_process_1.execSync)(`bun run utils/commands/skalfa.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("skalfa")
|
|
37
|
+
.description("Create Skalfa API projects and install optional extensions.")
|
|
38
|
+
.version("0.1.0");
|
|
39
|
+
program
|
|
40
|
+
.command("create-api")
|
|
41
|
+
.description("Create a new Skalfa 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 Skalfa 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 @skalfa/skalfa-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
|
+
}
|
|
@@ -0,0 +1,349 @@
|
|
|
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.extensionNames = exports.extensions = void 0;
|
|
7
|
+
exports.addExtension = addExtension;
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
10
|
+
const node_child_process_1 = require("node:child_process");
|
|
11
|
+
const npm_1 = require("../utils/npm");
|
|
12
|
+
const copier_1 = require("../utils/copier");
|
|
13
|
+
const fs_1 = require("../utils/fs");
|
|
14
|
+
const installer_1 = require("../utils/installer");
|
|
15
|
+
exports.extensions = {
|
|
16
|
+
mail: "@skalfa/mail",
|
|
17
|
+
redis: "@skalfa/skalfa-redis",
|
|
18
|
+
queue: "@skalfa/skalfa-queue",
|
|
19
|
+
cache: "@skalfa/skalfa-cache",
|
|
20
|
+
cron: "@skalfa/skalfa-cron",
|
|
21
|
+
da: "@skalfa/skalfa-da",
|
|
22
|
+
socket: "@skalfa/skalfa-socket",
|
|
23
|
+
notification: "@skalfa/notification",
|
|
24
|
+
orm: "@skalfa/skalfa-orm"
|
|
25
|
+
};
|
|
26
|
+
exports.extensionNames = Object.keys(exports.extensions);
|
|
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
|
+
const projectRoot = (0, fs_1.findProjectRoot)(process.cwd());
|
|
33
|
+
if (!projectRoot) {
|
|
34
|
+
throw new Error("No package.json found. Run this command inside a Skalfa API project.");
|
|
35
|
+
}
|
|
36
|
+
const isDev = !!process.env["SKALFA_API_TEMPLATE"];
|
|
37
|
+
if (extensionName === "orm") {
|
|
38
|
+
console.log("Installing Skalfa ORM and database dependencies...");
|
|
39
|
+
(0, installer_1.installPackage)(projectRoot, isDev ? "file:../skalfa-orm" : "@skalfa/skalfa-orm");
|
|
40
|
+
(0, installer_1.installPackage)(projectRoot, "knex");
|
|
41
|
+
(0, installer_1.installPackage)(projectRoot, "pg");
|
|
42
|
+
await scaffoldOrmExtension(projectRoot);
|
|
43
|
+
}
|
|
44
|
+
else if (["redis", "queue", "cache", "cron", "da", "socket"].includes(extensionName)) {
|
|
45
|
+
console.log(`Installing Skalfa extension: ${extensionName}...`);
|
|
46
|
+
// Install the main package
|
|
47
|
+
(0, installer_1.installPackage)(projectRoot, isDev ? `file:../${packageName.split("/")[1]}` : packageName);
|
|
48
|
+
// Install peer dependencies if any
|
|
49
|
+
if (extensionName === "redis") {
|
|
50
|
+
(0, installer_1.installPackage)(projectRoot, "ioredis");
|
|
51
|
+
}
|
|
52
|
+
else if (extensionName === "da") {
|
|
53
|
+
(0, installer_1.installPackage)(projectRoot, "@clickhouse/client");
|
|
54
|
+
}
|
|
55
|
+
else if (extensionName === "socket") {
|
|
56
|
+
(0, installer_1.installPackage)(projectRoot, "socket.io");
|
|
57
|
+
}
|
|
58
|
+
// Auto-install Redis if Queue or Cache is added
|
|
59
|
+
if (extensionName === "queue" || extensionName === "cache") {
|
|
60
|
+
console.log(`Extension ${extensionName} requires Redis. Automatically installing @skalfa/skalfa-redis...`);
|
|
61
|
+
(0, installer_1.installPackage)(projectRoot, isDev ? "file:../skalfa-redis" : "@skalfa/skalfa-redis");
|
|
62
|
+
(0, installer_1.installPackage)(projectRoot, "ioredis");
|
|
63
|
+
}
|
|
64
|
+
await scaffoldUtilityExtension(projectRoot, extensionName);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
console.log(`Installing Skalfa extension: ${extensionName}`);
|
|
68
|
+
(0, installer_1.installPackage)(projectRoot, packageName);
|
|
69
|
+
console.log(`Installed ${packageName}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function getTemplateSource(projectRoot) {
|
|
73
|
+
const envTemplateSource = process.env["SKALFA_API_TEMPLATE"];
|
|
74
|
+
let tempExtractDir = null;
|
|
75
|
+
let templateSource = "";
|
|
76
|
+
if (envTemplateSource) {
|
|
77
|
+
templateSource = node_path_1.default.resolve(envTemplateSource);
|
|
78
|
+
if (!(0, fs_1.exists)(templateSource)) {
|
|
79
|
+
throw new Error(`Template source override not found: ${templateSource}`);
|
|
80
|
+
}
|
|
81
|
+
return { templateSource, cleanup: () => { } };
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
// Dynamic download from npm registry
|
|
85
|
+
const templatePackageName = "@skalfa/skalfa-api";
|
|
86
|
+
console.log(`Fetching latest template info for ${templatePackageName} from npm registry...`);
|
|
87
|
+
const tarballUrl = await (0, npm_1.fetchLatestTarballUrl)(templatePackageName);
|
|
88
|
+
const parentDir = node_path_1.default.dirname(projectRoot);
|
|
89
|
+
tempExtractDir = node_path_1.default.join(parentDir, `skalfa-temp-extract-${Date.now()}`);
|
|
90
|
+
node_fs_1.default.mkdirSync(tempExtractDir, { recursive: true });
|
|
91
|
+
const tarballPath = node_path_1.default.join(tempExtractDir, "template.tgz");
|
|
92
|
+
console.log("Downloading template tarball...");
|
|
93
|
+
await (0, npm_1.downloadTarball)(tarballUrl, tarballPath);
|
|
94
|
+
console.log("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
|
+
templateSource = node_path_1.default.join(tempExtractDir, "package");
|
|
103
|
+
if (!(0, fs_1.exists)(templateSource)) {
|
|
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
|
+
return {
|
|
108
|
+
templateSource,
|
|
109
|
+
cleanup: () => {
|
|
110
|
+
if (tempExtractDir && (0, fs_1.exists)(tempExtractDir)) {
|
|
111
|
+
node_fs_1.default.rmSync(tempExtractDir, { recursive: true, force: true });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async function scaffoldOrmExtension(projectRoot) {
|
|
118
|
+
const { templateSource, cleanup } = await getTemplateSource(projectRoot);
|
|
119
|
+
try {
|
|
120
|
+
// 1. Copy database/ folder and app/models/ folder from template
|
|
121
|
+
console.log("Scaffolding database and model directories...");
|
|
122
|
+
const dbSrc = node_path_1.default.join(templateSource, "database");
|
|
123
|
+
const dbDest = node_path_1.default.join(projectRoot, "database");
|
|
124
|
+
if ((0, fs_1.exists)(dbSrc)) {
|
|
125
|
+
(0, copier_1.copyTemplate)(dbSrc, dbDest);
|
|
126
|
+
}
|
|
127
|
+
const modelsSrc = node_path_1.default.join(templateSource, "app", "models");
|
|
128
|
+
const modelsDest = node_path_1.default.join(projectRoot, "app", "models");
|
|
129
|
+
if ((0, fs_1.exists)(modelsSrc)) {
|
|
130
|
+
(0, copier_1.copyTemplate)(modelsSrc, modelsDest);
|
|
131
|
+
}
|
|
132
|
+
// 2. Copy database-enabled UserController and AuthController from template
|
|
133
|
+
console.log("Restoring database-enabled controllers...");
|
|
134
|
+
const authControllerSrc = node_path_1.default.join(templateSource, "app", "controllers", "iam", "auth.controller.ts");
|
|
135
|
+
const authControllerDest = node_path_1.default.join(projectRoot, "app", "controllers", "iam", "auth.controller.ts");
|
|
136
|
+
if ((0, fs_1.exists)(authControllerSrc)) {
|
|
137
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(authControllerDest), { recursive: true });
|
|
138
|
+
node_fs_1.default.copyFileSync(authControllerSrc, authControllerDest);
|
|
139
|
+
}
|
|
140
|
+
const userControllerSrc = node_path_1.default.join(templateSource, "app", "controllers", "iam", "user.controller.ts");
|
|
141
|
+
const userControllerDest = node_path_1.default.join(projectRoot, "app", "controllers", "iam", "user.controller.ts");
|
|
142
|
+
if ((0, fs_1.exists)(userControllerSrc)) {
|
|
143
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(userControllerDest), { recursive: true });
|
|
144
|
+
node_fs_1.default.copyFileSync(userControllerSrc, userControllerDest);
|
|
145
|
+
}
|
|
146
|
+
// 3. Restore database CLI commands loader if missing
|
|
147
|
+
console.log("Restoring database CLI commands...");
|
|
148
|
+
const commandsSrc = node_path_1.default.join(templateSource, "utils", "commands");
|
|
149
|
+
const commandsDest = node_path_1.default.join(projectRoot, "utils", "commands");
|
|
150
|
+
if ((0, fs_1.exists)(commandsSrc)) {
|
|
151
|
+
const skalfaCliSrc = node_path_1.default.join(commandsSrc, "skalfa.ts");
|
|
152
|
+
const skalfaCliDest = node_path_1.default.join(commandsDest, "skalfa.ts");
|
|
153
|
+
if ((0, fs_1.exists)(skalfaCliSrc) && !(0, fs_1.exists)(skalfaCliDest)) {
|
|
154
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(skalfaCliDest), { recursive: true });
|
|
155
|
+
node_fs_1.default.copyFileSync(skalfaCliSrc, skalfaCliDest);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// 4. Restore the database initialization block and imports in app/app.ts
|
|
159
|
+
console.log("Restoring database initialization in app/app.ts...");
|
|
160
|
+
const appTsSrc = node_path_1.default.join(templateSource, "app", "app.ts");
|
|
161
|
+
const appTsDest = node_path_1.default.join(projectRoot, "app", "app.ts");
|
|
162
|
+
if ((0, fs_1.exists)(appTsSrc) && (0, fs_1.exists)(appTsDest)) {
|
|
163
|
+
const templateAppTs = node_fs_1.default.readFileSync(appTsSrc, "utf8");
|
|
164
|
+
const regex = /(\/\/ ## Init: database\s*\r?\n\/\/ =====================================>\r?\n)([\s\S]*?)(\r?\n\/\/ =====================================>)/;
|
|
165
|
+
const match = templateAppTs.match(regex);
|
|
166
|
+
if (match) {
|
|
167
|
+
const dbBlock = match[2];
|
|
168
|
+
let targetAppTs = node_fs_1.default.readFileSync(appTsDest, "utf8");
|
|
169
|
+
targetAppTs = targetAppTs.replace(/(\/\/ ## Init: database\s*\r?\n\/\/ =====================================>\r?\n)([\s\S]*?)(\r?\n\/\/ =====================================>)/, `$1${dbBlock}$3`);
|
|
170
|
+
if (!targetAppTs.match(/\bdb\b/)) {
|
|
171
|
+
targetAppTs = targetAppTs.replace(/(\bcontroller\b|\blogger\b)/, "db, $1");
|
|
172
|
+
}
|
|
173
|
+
node_fs_1.default.writeFileSync(appTsDest, targetAppTs, "utf8");
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// 5. Update tsconfig.json path mappings
|
|
177
|
+
console.log("Updating tsconfig.json paths...");
|
|
178
|
+
const tsconfigPath = node_path_1.default.join(projectRoot, "tsconfig.json");
|
|
179
|
+
if ((0, fs_1.exists)(tsconfigPath)) {
|
|
180
|
+
let content = node_fs_1.default.readFileSync(tsconfigPath, "utf8");
|
|
181
|
+
if (!content.includes("@skalfa/skalfa-orm")) {
|
|
182
|
+
content = content.replace(/("@utils\/\*"\s*:\s*\[\s*"utils\/\*",)/, `$1\n "node_modules/@skalfa/skalfa-orm/dist/*",`);
|
|
183
|
+
node_fs_1.default.writeFileSync(tsconfigPath, content, "utf8");
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// 6. Update utils/index.ts exports
|
|
187
|
+
console.log("Updating utils/index.ts exports...");
|
|
188
|
+
const utilsIndexPath = node_path_1.default.join(projectRoot, "utils", "index.ts");
|
|
189
|
+
if ((0, fs_1.exists)(utilsIndexPath)) {
|
|
190
|
+
let content = node_fs_1.default.readFileSync(utilsIndexPath, "utf8");
|
|
191
|
+
if (!content.includes("@skalfa/skalfa-orm")) {
|
|
192
|
+
content = content.replace(/export \* from "@skalfa\/skalfa-api-core";/, `export * from "@skalfa/skalfa-api-core";\nexport * from "@skalfa/skalfa-orm";`);
|
|
193
|
+
content = content.replace(/export const db: any = null;\r?\n/, "");
|
|
194
|
+
content = content.replace(/export const Model: any = null;\r?\n/, "");
|
|
195
|
+
node_fs_1.default.writeFileSync(utilsIndexPath, content, "utf8");
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
finally {
|
|
200
|
+
cleanup();
|
|
201
|
+
}
|
|
202
|
+
console.log("✓ ORM initialization and scaffolding successfully restored!");
|
|
203
|
+
}
|
|
204
|
+
async function scaffoldUtilityExtension(projectRoot, ext) {
|
|
205
|
+
const { templateSource, cleanup } = await getTemplateSource(projectRoot);
|
|
206
|
+
try {
|
|
207
|
+
const tsconfigPath = node_path_1.default.join(projectRoot, "tsconfig.json");
|
|
208
|
+
const utilsIndexPath = node_path_1.default.join(projectRoot, "utils", "index.ts");
|
|
209
|
+
const appTsPath = node_path_1.default.join(projectRoot, "app", "app.ts");
|
|
210
|
+
// 1. Copy relevant template folders
|
|
211
|
+
if (ext === "queue") {
|
|
212
|
+
console.log("Copying Queue worker examples...");
|
|
213
|
+
const src = node_path_1.default.join(templateSource, "app", "jobs", "queues");
|
|
214
|
+
const dest = node_path_1.default.join(projectRoot, "app", "jobs", "queues");
|
|
215
|
+
(0, copier_1.copyTemplate)(src, dest);
|
|
216
|
+
// Check if project has DA or Notification packages
|
|
217
|
+
const packageJsonPath = node_path_1.default.join(projectRoot, "package.json");
|
|
218
|
+
let hasDa = false;
|
|
219
|
+
let hasNotification = false;
|
|
220
|
+
if (node_fs_1.default.existsSync(packageJsonPath)) {
|
|
221
|
+
const pkg = JSON.parse(node_fs_1.default.readFileSync(packageJsonPath, "utf8"));
|
|
222
|
+
const deps = pkg.dependencies || {};
|
|
223
|
+
hasDa = !!deps["@skalfa/skalfa-da"];
|
|
224
|
+
hasNotification = !!deps["@skalfa/notification"] || !!deps["skalfa-notification"];
|
|
225
|
+
}
|
|
226
|
+
cleanQueueWorkers(projectRoot, hasDa, hasNotification);
|
|
227
|
+
}
|
|
228
|
+
else if (ext === "cron") {
|
|
229
|
+
console.log("Copying Cron job examples...");
|
|
230
|
+
const src = node_path_1.default.join(templateSource, "app", "jobs", "crons");
|
|
231
|
+
const dest = node_path_1.default.join(projectRoot, "app", "jobs", "crons");
|
|
232
|
+
(0, copier_1.copyTemplate)(src, dest);
|
|
233
|
+
}
|
|
234
|
+
else if (ext === "socket") {
|
|
235
|
+
console.log("Copying Socket.io handler examples...");
|
|
236
|
+
const src = node_path_1.default.join(templateSource, "app", "jobs", "sockets");
|
|
237
|
+
const dest = node_path_1.default.join(projectRoot, "app", "jobs", "sockets");
|
|
238
|
+
(0, copier_1.copyTemplate)(src, dest);
|
|
239
|
+
}
|
|
240
|
+
else if (ext === "da") {
|
|
241
|
+
console.log("Copying Data Analytics OLAP migrations...");
|
|
242
|
+
const src = node_path_1.default.join(templateSource, "database", "da.migrations");
|
|
243
|
+
const dest = node_path_1.default.join(projectRoot, "database", "da.migrations");
|
|
244
|
+
(0, copier_1.copyTemplate)(src, dest);
|
|
245
|
+
}
|
|
246
|
+
// 2. Update tsconfig.json path mappings
|
|
247
|
+
addTsconfigPath(tsconfigPath, `@skalfa/skalfa-${ext}`);
|
|
248
|
+
if (ext === "queue" || ext === "cache") {
|
|
249
|
+
addTsconfigPath(tsconfigPath, "@skalfa/skalfa-redis");
|
|
250
|
+
}
|
|
251
|
+
// 3. Update utils/index.ts exports
|
|
252
|
+
addUtilExport(utilsIndexPath, `@skalfa/skalfa-${ext}`);
|
|
253
|
+
if (ext === "queue" || ext === "cache") {
|
|
254
|
+
addUtilExport(utilsIndexPath, "@skalfa/skalfa-redis");
|
|
255
|
+
}
|
|
256
|
+
// 4. Uncomment initialization blocks and update imports in app/app.ts
|
|
257
|
+
if (node_fs_1.default.existsSync(appTsPath)) {
|
|
258
|
+
let content = node_fs_1.default.readFileSync(appTsPath, "utf8");
|
|
259
|
+
const importsToAdd = [];
|
|
260
|
+
if (ext === "redis" || ext === "queue" || ext === "cache") {
|
|
261
|
+
importsToAdd.push("redis");
|
|
262
|
+
// Uncomment Redis block
|
|
263
|
+
content = content.replace(/\/\/ if \(process\.env\.REDIS_HOST[\s\S]*?\/\/ \}/g, (match) => match.replace(/^\/\/ ?/gm, ""));
|
|
264
|
+
}
|
|
265
|
+
if (ext === "da") {
|
|
266
|
+
importsToAdd.push("daClient");
|
|
267
|
+
// Uncomment DA block
|
|
268
|
+
content = content.replace(/\/\/ if \(process\.env\.DA_HOST[\s\S]*?\/\/ }/g, (match) => match.replace(/^\/\/ ?/gm, ""));
|
|
269
|
+
}
|
|
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
|
+
if (importsToAdd.length > 0) {
|
|
282
|
+
const importRegex = /import\s*\{\s*([\s\S]*?)\s*\}\s*from\s*["']@utils["']/;
|
|
283
|
+
const match = content.match(importRegex);
|
|
284
|
+
if (match) {
|
|
285
|
+
const currentImports = match[1].split(",").map(i => i.trim()).filter(Boolean);
|
|
286
|
+
const finalImports = Array.from(new Set([...currentImports, ...importsToAdd]));
|
|
287
|
+
const newImportLine = `import { ${finalImports.join(", ")} } from "@utils"`;
|
|
288
|
+
content = content.replace(importRegex, newImportLine);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
node_fs_1.default.writeFileSync(appTsPath, content, "utf8");
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
finally {
|
|
295
|
+
cleanup();
|
|
296
|
+
}
|
|
297
|
+
console.log(`✓ Extension "${ext}" successfully configured!`);
|
|
298
|
+
}
|
|
299
|
+
function addTsconfigPath(tsconfigPath, packageName) {
|
|
300
|
+
if (!node_fs_1.default.existsSync(tsconfigPath))
|
|
301
|
+
return;
|
|
302
|
+
let content = node_fs_1.default.readFileSync(tsconfigPath, "utf8");
|
|
303
|
+
if (!content.includes(packageName)) {
|
|
304
|
+
content = content.replace(/("@utils\/\*"\s*:\s*\[\s*"utils\/\*",)/, `$1\n "node_modules/${packageName}/dist/*",`);
|
|
305
|
+
node_fs_1.default.writeFileSync(tsconfigPath, content, "utf8");
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
function addUtilExport(utilsIndexPath, packageName) {
|
|
309
|
+
if (!node_fs_1.default.existsSync(utilsIndexPath))
|
|
310
|
+
return;
|
|
311
|
+
let content = node_fs_1.default.readFileSync(utilsIndexPath, "utf8");
|
|
312
|
+
if (!content.includes(packageName)) {
|
|
313
|
+
content += `export * from "${packageName}";\n`;
|
|
314
|
+
node_fs_1.default.writeFileSync(utilsIndexPath, content, "utf8");
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
function cleanQueueWorkers(targetDir, hasDa, hasNotification) {
|
|
318
|
+
const queuesDir = node_path_1.default.join(targetDir, "app", "jobs", "queues");
|
|
319
|
+
const workerQueuePath = node_path_1.default.join(queuesDir, "worker.queue.ts");
|
|
320
|
+
if (!node_fs_1.default.existsSync(workerQueuePath))
|
|
321
|
+
return;
|
|
322
|
+
let content = node_fs_1.default.readFileSync(workerQueuePath, "utf8");
|
|
323
|
+
if (!hasDa) {
|
|
324
|
+
const daFiles = [
|
|
325
|
+
"access-log.queue.worker.ts",
|
|
326
|
+
"activity-log.queue.worker.ts",
|
|
327
|
+
"error-log.queue.worker.ts"
|
|
328
|
+
];
|
|
329
|
+
for (const file of daFiles) {
|
|
330
|
+
const p = node_path_1.default.join(queuesDir, file);
|
|
331
|
+
if (node_fs_1.default.existsSync(p))
|
|
332
|
+
node_fs_1.default.unlinkSync(p);
|
|
333
|
+
}
|
|
334
|
+
content = content.replace(/import\s*\{\s*activityLogQueueWorker\s*\}\s*from\s*["']\.\/activity-log\.queue\.worker["'];?\r?\n?/g, "");
|
|
335
|
+
content = content.replace(/import\s*\{\s*accessLogQueueWorker\s*\}\s*from\s*["']\.\/access-log\.queue\.worker["'];?\r?\n?/g, "");
|
|
336
|
+
content = content.replace(/import\s*\{\s*errorLogQueueWorker\s*\}\s*from\s*["']\.\/error-log\.queue\.worker["'];?\r?\n?/g, "");
|
|
337
|
+
content = content.replace(/activityLogQueueWorker\(\)\r?\n?/g, "");
|
|
338
|
+
content = content.replace(/accessLogQueueWorker\(\)\r?\n?/g, "");
|
|
339
|
+
content = content.replace(/errorLogQueueWorker\(\)\r?\n?/g, "");
|
|
340
|
+
}
|
|
341
|
+
if (!hasNotification) {
|
|
342
|
+
const p = node_path_1.default.join(queuesDir, "notification.queue.worker.ts");
|
|
343
|
+
if (node_fs_1.default.existsSync(p))
|
|
344
|
+
node_fs_1.default.unlinkSync(p);
|
|
345
|
+
content = content.replace(/import\s*\{\s*notificationQueueWorker\s*\}\s*from\s*["']\.\/notification\.queue\.worker["'];?\r?\n?/g, "");
|
|
346
|
+
content = content.replace(/notificationQueueWorker\(\)\r?\n?/g, "");
|
|
347
|
+
}
|
|
348
|
+
node_fs_1.default.writeFileSync(workerQueuePath, content, "utf8");
|
|
349
|
+
}
|
|
@@ -0,0 +1,317 @@
|
|
|
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.createApi = createApi;
|
|
7
|
+
exports.renamePackage = renamePackage;
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
10
|
+
const node_child_process_1 = require("node:child_process");
|
|
11
|
+
const node_readline_1 = __importDefault(require("node:readline"));
|
|
12
|
+
const npm_1 = require("../utils/npm");
|
|
13
|
+
const installer_1 = require("../utils/installer");
|
|
14
|
+
const fs_1 = require("../utils/fs");
|
|
15
|
+
const copier_1 = require("../utils/copier");
|
|
16
|
+
const TEMPLATE_ENV_KEY = "SKALFA_API_TEMPLATE";
|
|
17
|
+
class Questioner {
|
|
18
|
+
rl;
|
|
19
|
+
constructor() {
|
|
20
|
+
this.rl = node_readline_1.default.createInterface({
|
|
21
|
+
input: process.stdin,
|
|
22
|
+
output: process.stdout,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
ask(query) {
|
|
26
|
+
return new Promise((resolve) => {
|
|
27
|
+
this.rl.question(query, (answer) => {
|
|
28
|
+
resolve(answer);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
close() {
|
|
33
|
+
this.rl.close();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async function createApi(projectName) {
|
|
37
|
+
const cwd = process.cwd();
|
|
38
|
+
const target = node_path_1.default.resolve(cwd, projectName);
|
|
39
|
+
const packageName = node_path_1.default.basename(target);
|
|
40
|
+
(0, fs_1.assertInsideDirectory)(cwd, target);
|
|
41
|
+
if ((0, fs_1.exists)(target)) {
|
|
42
|
+
throw new Error(`Target directory already exists: ${target}`);
|
|
43
|
+
}
|
|
44
|
+
// Ask interactive questions sequentially using a single readline interface
|
|
45
|
+
const q = new Questioner();
|
|
46
|
+
let hasRedis = false;
|
|
47
|
+
let hasQueue = false;
|
|
48
|
+
let hasCache = false;
|
|
49
|
+
let hasCron = false;
|
|
50
|
+
let hasDa = false;
|
|
51
|
+
let hasSocket = false;
|
|
52
|
+
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 Clickhouse Data Analytics? (y/N): ")).toLowerCase().startsWith("y");
|
|
58
|
+
hasSocket = (await q.ask("Do you need Socket.io? (y/N): ")).toLowerCase().startsWith("y");
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
q.close();
|
|
62
|
+
}
|
|
63
|
+
// Dependency relation: Queue or Cache requires Redis
|
|
64
|
+
const finalRedis = hasRedis || hasQueue || hasCache;
|
|
65
|
+
const envTemplateSource = process.env[TEMPLATE_ENV_KEY];
|
|
66
|
+
if (envTemplateSource) {
|
|
67
|
+
// Local copy mode (e.g. for development / override)
|
|
68
|
+
const templateSource = node_path_1.default.resolve(envTemplateSource);
|
|
69
|
+
console.log(`Creating Skalfa API project from local template override: ${templateSource}`);
|
|
70
|
+
if (!(0, fs_1.exists)(templateSource)) {
|
|
71
|
+
throw new Error(`Template source override not found: ${templateSource}`);
|
|
72
|
+
}
|
|
73
|
+
(0, copier_1.copyTemplate)(templateSource, target);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
// Dynamic download from npm registry
|
|
77
|
+
const templatePackageName = "@skalfa/skalfa-api";
|
|
78
|
+
console.log(`Fetching latest template info for ${templatePackageName} from npm registry...`);
|
|
79
|
+
const tarballUrl = await (0, npm_1.fetchLatestTarballUrl)(templatePackageName);
|
|
80
|
+
// Create a temporary extraction directory inside parent folder of target
|
|
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
|
+
console.log("Downloading template tarball...");
|
|
89
|
+
await (0, npm_1.downloadTarball)(tarballUrl, tarballPath);
|
|
90
|
+
console.log("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
|
+
// Rename/move extracted folder to target path
|
|
104
|
+
node_fs_1.default.renameSync(packageDir, target);
|
|
105
|
+
// Cleanup temp extract folder
|
|
106
|
+
node_fs_1.default.rmSync(tempExtractDir, { recursive: true, force: true });
|
|
107
|
+
}
|
|
108
|
+
// Cleanup git directory if present and rename package
|
|
109
|
+
(0, fs_1.removeDirectory)(node_path_1.default.join(target, ".git"));
|
|
110
|
+
renamePackage(target, packageName);
|
|
111
|
+
// Customize project with selected options
|
|
112
|
+
customizeProject(target, {
|
|
113
|
+
redis: finalRedis,
|
|
114
|
+
queue: hasQueue,
|
|
115
|
+
cache: hasCache,
|
|
116
|
+
cron: hasCron,
|
|
117
|
+
da: hasDa,
|
|
118
|
+
socket: hasSocket
|
|
119
|
+
});
|
|
120
|
+
console.log("Installing dependencies...");
|
|
121
|
+
(0, installer_1.installDependencies)(target);
|
|
122
|
+
console.log("");
|
|
123
|
+
console.log("✓ Skalfa API project is ready.");
|
|
124
|
+
console.log(`Next steps:\n cd ${projectName}\n bun run dev`);
|
|
125
|
+
}
|
|
126
|
+
function renamePackage(target, packageName) {
|
|
127
|
+
const packageJsonPath = node_path_1.default.join(target, "package.json");
|
|
128
|
+
if (!(0, fs_1.exists)(packageJsonPath)) {
|
|
129
|
+
console.warn("Skipped package rename: package.json was not found.");
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const packageJson = (0, fs_1.readJsonFile)(packageJsonPath);
|
|
133
|
+
packageJson.name = packageName;
|
|
134
|
+
(0, fs_1.writeJsonFile)(packageJsonPath, packageJson);
|
|
135
|
+
}
|
|
136
|
+
function customizeProject(target, opts) {
|
|
137
|
+
const packageJsonPath = node_path_1.default.join(target, "package.json");
|
|
138
|
+
const tsconfigPath = node_path_1.default.join(target, "tsconfig.json");
|
|
139
|
+
const utilsIndexPath = node_path_1.default.join(target, "utils", "index.ts");
|
|
140
|
+
const appTsPath = node_path_1.default.join(target, "app", "app.ts");
|
|
141
|
+
const isDev = !!process.env[TEMPLATE_ENV_KEY];
|
|
142
|
+
// 1. Update dependencies in package.json
|
|
143
|
+
if (node_fs_1.default.existsSync(packageJsonPath)) {
|
|
144
|
+
const pkg = JSON.parse(node_fs_1.default.readFileSync(packageJsonPath, "utf8"));
|
|
145
|
+
pkg.dependencies = pkg.dependencies || {};
|
|
146
|
+
// Base ORM integration (always included)
|
|
147
|
+
pkg.dependencies["@skalfa/skalfa-orm"] = isDev ? "file:../skalfa-orm" : "^1.0.0";
|
|
148
|
+
if (isDev) {
|
|
149
|
+
pkg.dependencies["@skalfa/skalfa-api-core"] = "file:../skalfa-api-core";
|
|
150
|
+
}
|
|
151
|
+
if (opts.redis) {
|
|
152
|
+
pkg.dependencies["@skalfa/skalfa-redis"] = isDev ? "file:../skalfa-redis" : "^1.0.0";
|
|
153
|
+
pkg.dependencies["ioredis"] = "^5.4.1";
|
|
154
|
+
}
|
|
155
|
+
if (opts.queue) {
|
|
156
|
+
pkg.dependencies["@skalfa/skalfa-queue"] = isDev ? "file:../skalfa-queue" : "^1.0.0";
|
|
157
|
+
}
|
|
158
|
+
if (opts.cache) {
|
|
159
|
+
pkg.dependencies["@skalfa/skalfa-cache"] = isDev ? "file:../skalfa-cache" : "^1.0.0";
|
|
160
|
+
}
|
|
161
|
+
if (opts.cron) {
|
|
162
|
+
pkg.dependencies["@skalfa/skalfa-cron"] = isDev ? "file:../skalfa-cron" : "^1.0.0";
|
|
163
|
+
}
|
|
164
|
+
if (opts.da) {
|
|
165
|
+
pkg.dependencies["@skalfa/skalfa-da"] = isDev ? "file:../skalfa-da" : "^1.0.0";
|
|
166
|
+
pkg.dependencies["@clickhouse/client"] = "^1.6.0";
|
|
167
|
+
}
|
|
168
|
+
if (opts.socket) {
|
|
169
|
+
pkg.dependencies["@skalfa/skalfa-socket"] = isDev ? "file:../skalfa-socket" : "^1.0.0";
|
|
170
|
+
pkg.dependencies["socket.io"] = "^4.7.5";
|
|
171
|
+
}
|
|
172
|
+
node_fs_1.default.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2), "utf8");
|
|
173
|
+
}
|
|
174
|
+
// 2. Add path mappings in tsconfig.json
|
|
175
|
+
addTsconfigPath(tsconfigPath, "@skalfa/skalfa-orm");
|
|
176
|
+
if (opts.redis)
|
|
177
|
+
addTsconfigPath(tsconfigPath, "@skalfa/skalfa-redis");
|
|
178
|
+
if (opts.queue)
|
|
179
|
+
addTsconfigPath(tsconfigPath, "@skalfa/skalfa-queue");
|
|
180
|
+
if (opts.cache)
|
|
181
|
+
addTsconfigPath(tsconfigPath, "@skalfa/skalfa-cache");
|
|
182
|
+
if (opts.cron)
|
|
183
|
+
addTsconfigPath(tsconfigPath, "@skalfa/skalfa-cron");
|
|
184
|
+
if (opts.da)
|
|
185
|
+
addTsconfigPath(tsconfigPath, "@skalfa/skalfa-da");
|
|
186
|
+
if (opts.socket)
|
|
187
|
+
addTsconfigPath(tsconfigPath, "@skalfa/skalfa-socket");
|
|
188
|
+
// 3. Write exports to utils/index.ts
|
|
189
|
+
if (node_fs_1.default.existsSync(utilsIndexPath)) {
|
|
190
|
+
let exports = `export * from "@skalfa/skalfa-api-core";\nexport * from "@skalfa/skalfa-orm";\n`;
|
|
191
|
+
if (opts.redis)
|
|
192
|
+
exports += `export * from "@skalfa/skalfa-redis";\n`;
|
|
193
|
+
if (opts.queue)
|
|
194
|
+
exports += `export * from "@skalfa/skalfa-queue";\n`;
|
|
195
|
+
if (opts.cache)
|
|
196
|
+
exports += `export * from "@skalfa/skalfa-cache";\n`;
|
|
197
|
+
if (opts.cron)
|
|
198
|
+
exports += `export * from "@skalfa/skalfa-cron";\n`;
|
|
199
|
+
if (opts.da)
|
|
200
|
+
exports += `export * from "@skalfa/skalfa-da";\n`;
|
|
201
|
+
if (opts.socket)
|
|
202
|
+
exports += `export * from "@skalfa/skalfa-socket";\n`;
|
|
203
|
+
node_fs_1.default.writeFileSync(utilsIndexPath, exports, "utf8");
|
|
204
|
+
}
|
|
205
|
+
// 4. Clean up folders/files that are not wanted
|
|
206
|
+
if (!opts.queue) {
|
|
207
|
+
const queueDir = node_path_1.default.join(target, "app", "jobs", "queues");
|
|
208
|
+
if (node_fs_1.default.existsSync(queueDir)) {
|
|
209
|
+
node_fs_1.default.rmSync(queueDir, { recursive: true, force: true });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
cleanQueueWorkers(target, opts.da, false);
|
|
214
|
+
}
|
|
215
|
+
if (!opts.cron) {
|
|
216
|
+
const cronDir = node_path_1.default.join(target, "app", "jobs", "crons");
|
|
217
|
+
if (node_fs_1.default.existsSync(cronDir)) {
|
|
218
|
+
node_fs_1.default.rmSync(cronDir, { recursive: true, force: true });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (!opts.socket) {
|
|
222
|
+
const socketDir = node_path_1.default.join(target, "app", "jobs", "sockets");
|
|
223
|
+
if (node_fs_1.default.existsSync(socketDir)) {
|
|
224
|
+
node_fs_1.default.rmSync(socketDir, { recursive: true, force: true });
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (!opts.da) {
|
|
228
|
+
const daDir = node_path_1.default.join(target, "database", "da.migrations");
|
|
229
|
+
if (node_fs_1.default.existsSync(daDir)) {
|
|
230
|
+
node_fs_1.default.rmSync(daDir, { recursive: true, force: true });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// Clean up parent jobs folder if completely empty
|
|
234
|
+
const jobsDir = node_path_1.default.join(target, "app", "jobs");
|
|
235
|
+
if (node_fs_1.default.existsSync(jobsDir)) {
|
|
236
|
+
const files = node_fs_1.default.readdirSync(jobsDir);
|
|
237
|
+
if (files.length === 0) {
|
|
238
|
+
node_fs_1.default.rmSync(jobsDir, { recursive: true, force: true });
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// 5. Uncomment initialization blocks and update imports in app/app.ts
|
|
242
|
+
if (node_fs_1.default.existsSync(appTsPath)) {
|
|
243
|
+
let content = node_fs_1.default.readFileSync(appTsPath, "utf8");
|
|
244
|
+
const importsToAdd = [];
|
|
245
|
+
if (opts.redis) {
|
|
246
|
+
importsToAdd.push("redis");
|
|
247
|
+
// Uncomment Redis block
|
|
248
|
+
content = content.replace(/\/\/ if \(process\.env\.REDIS_HOST[\s\S]*?\/\/ \}/g, (match) => match.replace(/^\/\/ ?/gm, ""));
|
|
249
|
+
}
|
|
250
|
+
if (opts.da) {
|
|
251
|
+
importsToAdd.push("daClient");
|
|
252
|
+
// Uncomment DA block
|
|
253
|
+
content = content.replace(/\/\/ if \(process\.env\.DA_HOST[\s\S]*?\/\/ }/g, (match) => match.replace(/^\/\/ ?/gm, ""));
|
|
254
|
+
}
|
|
255
|
+
if (opts.cron) {
|
|
256
|
+
importsToAdd.push("cron");
|
|
257
|
+
// Uncomment Cron block
|
|
258
|
+
content = content.replace(/\/\/ cron\.worker\(\)/g, "cron.worker()");
|
|
259
|
+
}
|
|
260
|
+
if (opts.socket) {
|
|
261
|
+
importsToAdd.push("socket");
|
|
262
|
+
// Uncomment Socket block
|
|
263
|
+
content = content.replace(/\/\/ if \(process\.env\.SOCKET_PORT[\s\S]*?\/\/ }/g, (match) => match.replace(/^\/\/ ?/gm, ""));
|
|
264
|
+
}
|
|
265
|
+
// Update import statement at the top of app.ts
|
|
266
|
+
if (importsToAdd.length > 0) {
|
|
267
|
+
const baseImports = ["controller", "db", "logger", "middleware", "storage", "registry"];
|
|
268
|
+
const finalImports = [...baseImports, ...importsToAdd];
|
|
269
|
+
const targetRegex = /import\s*\{\s*controller,\s*db,\s*logger,\s*middleware,\s*storage,\s*registry\s*\}\s*from\s*["']@utils["']/;
|
|
270
|
+
const newImportLine = `import { ${finalImports.join(", ")} } from "@utils"`;
|
|
271
|
+
content = content.replace(targetRegex, newImportLine);
|
|
272
|
+
}
|
|
273
|
+
node_fs_1.default.writeFileSync(appTsPath, content, "utf8");
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
function addTsconfigPath(tsconfigPath, packageName) {
|
|
277
|
+
if (!node_fs_1.default.existsSync(tsconfigPath))
|
|
278
|
+
return;
|
|
279
|
+
let content = node_fs_1.default.readFileSync(tsconfigPath, "utf8");
|
|
280
|
+
if (!content.includes(packageName)) {
|
|
281
|
+
content = content.replace(/("@utils\/\*"\s*:\s*\[\s*"utils\/\*",)/, `$1\n "node_modules/${packageName}/dist/*",`);
|
|
282
|
+
node_fs_1.default.writeFileSync(tsconfigPath, content, "utf8");
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function cleanQueueWorkers(targetDir, hasDa, hasNotification) {
|
|
286
|
+
const queuesDir = node_path_1.default.join(targetDir, "app", "jobs", "queues");
|
|
287
|
+
const workerQueuePath = node_path_1.default.join(queuesDir, "worker.queue.ts");
|
|
288
|
+
if (!node_fs_1.default.existsSync(workerQueuePath))
|
|
289
|
+
return;
|
|
290
|
+
let content = node_fs_1.default.readFileSync(workerQueuePath, "utf8");
|
|
291
|
+
if (!hasDa) {
|
|
292
|
+
const daFiles = [
|
|
293
|
+
"access-log.queue.worker.ts",
|
|
294
|
+
"activity-log.queue.worker.ts",
|
|
295
|
+
"error-log.queue.worker.ts"
|
|
296
|
+
];
|
|
297
|
+
for (const file of daFiles) {
|
|
298
|
+
const p = node_path_1.default.join(queuesDir, file);
|
|
299
|
+
if (node_fs_1.default.existsSync(p))
|
|
300
|
+
node_fs_1.default.unlinkSync(p);
|
|
301
|
+
}
|
|
302
|
+
content = content.replace(/import\s*\{\s*activityLogQueueWorker\s*\}\s*from\s*["']\.\/activity-log\.queue\.worker["'];?\r?\n?/g, "");
|
|
303
|
+
content = content.replace(/import\s*\{\s*accessLogQueueWorker\s*\}\s*from\s*["']\.\/access-log\.queue\.worker["'];?\r?\n?/g, "");
|
|
304
|
+
content = content.replace(/import\s*\{\s*errorLogQueueWorker\s*\}\s*from\s*["']\.\/error-log\.queue\.worker["'];?\r?\n?/g, "");
|
|
305
|
+
content = content.replace(/activityLogQueueWorker\(\)\r?\n?/g, "");
|
|
306
|
+
content = content.replace(/accessLogQueueWorker\(\)\r?\n?/g, "");
|
|
307
|
+
content = content.replace(/errorLogQueueWorker\(\)\r?\n?/g, "");
|
|
308
|
+
}
|
|
309
|
+
if (!hasNotification) {
|
|
310
|
+
const p = node_path_1.default.join(queuesDir, "notification.queue.worker.ts");
|
|
311
|
+
if (node_fs_1.default.existsSync(p))
|
|
312
|
+
node_fs_1.default.unlinkSync(p);
|
|
313
|
+
content = content.replace(/import\s*\{\s*notificationQueueWorker\s*\}\s*from\s*["']\.\/notification\.queue\.worker["'];?\r?\n?/g, "");
|
|
314
|
+
content = content.replace(/notificationQueueWorker\(\)\r?\n?/g, "");
|
|
315
|
+
}
|
|
316
|
+
node_fs_1.default.writeFileSync(workerQueuePath, content, "utf8");
|
|
317
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
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.UTILITIES = exports.UTILITY_EXPORTS = void 0;
|
|
7
|
+
exports.pickUtility = pickUtility;
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const fs_1 = require("../utils/fs");
|
|
11
|
+
exports.UTILITY_EXPORTS = {
|
|
12
|
+
auth: ["auth"],
|
|
13
|
+
context: ["context", "AppContext"],
|
|
14
|
+
controller: ["controller", "ControllerContext", "ControllerSchema", "controllerSchema"],
|
|
15
|
+
conversion: ["conversion"],
|
|
16
|
+
db: ["db", "useDB", "closeAllDB"],
|
|
17
|
+
logger: ["logger"],
|
|
18
|
+
mail: ["sendMail", "renderMailTemplate", "SendMailOptions"],
|
|
19
|
+
middleware: ["middleware"],
|
|
20
|
+
model: ["Model", "softDelete", "foreignIdFor"],
|
|
21
|
+
permission: ["permission", "KeyPermission"],
|
|
22
|
+
route: ["route", "api"],
|
|
23
|
+
storage: ["storage"],
|
|
24
|
+
validation: ["validate", "ValidationRules", "ValidationRule"],
|
|
25
|
+
};
|
|
26
|
+
exports.UTILITIES = Object.keys(exports.UTILITY_EXPORTS);
|
|
27
|
+
function pickUtility(utilityName) {
|
|
28
|
+
const utilitySymbols = exports.UTILITY_EXPORTS[utilityName];
|
|
29
|
+
if (!utilitySymbols) {
|
|
30
|
+
throw new Error(`Unknown utility "${utilityName}". Available utilities: ${exports.UTILITIES.join(", ")}`);
|
|
31
|
+
}
|
|
32
|
+
const projectRoot = (0, fs_1.findProjectRoot)(process.cwd());
|
|
33
|
+
if (!projectRoot) {
|
|
34
|
+
throw new Error("No package.json found. Run this command inside a Skalfa API project.");
|
|
35
|
+
}
|
|
36
|
+
const utilsDir = node_path_1.default.join(projectRoot, "utils");
|
|
37
|
+
const indexPath = node_path_1.default.join(utilsDir, "index.ts");
|
|
38
|
+
if (!(0, fs_1.exists)(utilsDir) || !(0, fs_1.exists)(indexPath)) {
|
|
39
|
+
throw new Error("Folder utils or utils/index.ts not found. Make sure you are at the project root.");
|
|
40
|
+
}
|
|
41
|
+
// 1. Tentukan path source dari node_modules dan target di lokal proyek
|
|
42
|
+
const corePackagePath = node_path_1.default.join(projectRoot, "node_modules", "@skalfa", "skalfa-api-core");
|
|
43
|
+
const sourceDir = node_path_1.default.join(corePackagePath, "src", utilityName);
|
|
44
|
+
const targetDir = node_path_1.default.join(utilsDir, utilityName);
|
|
45
|
+
if (!(0, fs_1.exists)(sourceDir)) {
|
|
46
|
+
throw new Error(`Source folder for "${utilityName}" not found at ${sourceDir}. Make sure @skalfa/skalfa-api-core is installed.`);
|
|
47
|
+
}
|
|
48
|
+
if ((0, fs_1.exists)(targetDir)) {
|
|
49
|
+
throw new Error(`Utility folder "${utilityName}" is already present in your local utils folder.`);
|
|
50
|
+
}
|
|
51
|
+
// 2. Salin folder dari node_modules ke lokal proyek secara rekursif
|
|
52
|
+
console.log(`Copying ${utilityName} folder from @skalfa/skalfa-api-core to utils/ ...`);
|
|
53
|
+
node_fs_1.default.cpSync(sourceDir, targetDir, { recursive: true });
|
|
54
|
+
console.log(`✓ Copied ${utilityName} folder`);
|
|
55
|
+
// 3. Perbarui utils/index.ts untuk mereferensikan folder lokal
|
|
56
|
+
console.log("Updating utils/index.ts with explicit local export override ...");
|
|
57
|
+
let indexContent = node_fs_1.default.readFileSync(indexPath, "utf8").trim();
|
|
58
|
+
const localExportLine = `export { ${utilitySymbols.join(", ")} } from "./${utilityName}";`;
|
|
59
|
+
if (!indexContent.includes(`./${utilityName}`)) {
|
|
60
|
+
indexContent += `\n${localExportLine}\n`;
|
|
61
|
+
node_fs_1.default.writeFileSync(indexPath, indexContent, "utf8");
|
|
62
|
+
console.log("✓ Updated utils/index.ts with override.");
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
console.log("⚠️ Info: Local export for this utility already exists in utils/index.ts");
|
|
66
|
+
}
|
|
67
|
+
console.log(`\nSuccess! You can now customize files under: utils/${utilityName}/`);
|
|
68
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
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.ignoredNames = void 0;
|
|
7
|
+
exports.copyTemplate = copyTemplate;
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const fs_1 = require("./fs");
|
|
11
|
+
exports.ignoredNames = new Set(["node_modules", ".git"]);
|
|
12
|
+
function copyTemplate(source, target) {
|
|
13
|
+
copyRecursive(source, target);
|
|
14
|
+
}
|
|
15
|
+
function copyRecursive(source, target) {
|
|
16
|
+
const stat = node_fs_1.default.lstatSync(source);
|
|
17
|
+
if (stat.isDirectory()) {
|
|
18
|
+
copyDirectory(source, target);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (stat.isSymbolicLink()) {
|
|
22
|
+
copySymbolicLink(source, target);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
node_fs_1.default.copyFileSync(source, target);
|
|
26
|
+
}
|
|
27
|
+
function copyDirectory(source, target) {
|
|
28
|
+
(0, fs_1.ensureDirectoryExists)(target);
|
|
29
|
+
for (const entry of node_fs_1.default.readdirSync(source, { withFileTypes: true })) {
|
|
30
|
+
if (exports.ignoredNames.has(entry.name)) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const sourcePath = node_path_1.default.join(source, entry.name);
|
|
34
|
+
const targetPath = node_path_1.default.join(target, entry.name);
|
|
35
|
+
copyRecursive(sourcePath, targetPath);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function copySymbolicLink(source, target) {
|
|
39
|
+
const linkTarget = node_fs_1.default.readlinkSync(source);
|
|
40
|
+
node_fs_1.default.symlinkSync(linkTarget, target);
|
|
41
|
+
}
|
package/dist/utils/fs.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
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.exists = exists;
|
|
7
|
+
exports.ensureDirectoryExists = ensureDirectoryExists;
|
|
8
|
+
exports.readJsonFile = readJsonFile;
|
|
9
|
+
exports.writeJsonFile = writeJsonFile;
|
|
10
|
+
exports.removeDirectory = removeDirectory;
|
|
11
|
+
exports.assertInsideDirectory = assertInsideDirectory;
|
|
12
|
+
exports.findProjectRoot = findProjectRoot;
|
|
13
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
14
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
15
|
+
function exists(targetPath) {
|
|
16
|
+
return node_fs_1.default.existsSync(targetPath);
|
|
17
|
+
}
|
|
18
|
+
function ensureDirectoryExists(targetPath) {
|
|
19
|
+
node_fs_1.default.mkdirSync(targetPath, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
function readJsonFile(targetPath) {
|
|
22
|
+
return JSON.parse(node_fs_1.default.readFileSync(targetPath, "utf8"));
|
|
23
|
+
}
|
|
24
|
+
function writeJsonFile(targetPath, value) {
|
|
25
|
+
node_fs_1.default.writeFileSync(targetPath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
26
|
+
}
|
|
27
|
+
function removeDirectory(targetPath) {
|
|
28
|
+
if (!exists(targetPath)) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
node_fs_1.default.rmSync(targetPath, { recursive: true, force: true });
|
|
32
|
+
}
|
|
33
|
+
function assertInsideDirectory(parent, child) {
|
|
34
|
+
const resolvedParent = node_path_1.default.resolve(parent);
|
|
35
|
+
const resolvedChild = node_path_1.default.resolve(child);
|
|
36
|
+
const relativePath = node_path_1.default.relative(resolvedParent, resolvedChild);
|
|
37
|
+
if (relativePath.startsWith("..") || node_path_1.default.isAbsolute(relativePath)) {
|
|
38
|
+
throw new Error(`Path is outside expected directory: ${child}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function findProjectRoot(startPath) {
|
|
42
|
+
let currentPath = node_path_1.default.resolve(startPath);
|
|
43
|
+
while (true) {
|
|
44
|
+
if (exists(node_path_1.default.join(currentPath, "package.json"))) {
|
|
45
|
+
return currentPath;
|
|
46
|
+
}
|
|
47
|
+
const parentPath = node_path_1.default.dirname(currentPath);
|
|
48
|
+
if (parentPath === currentPath) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
currentPath = parentPath;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
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.installDependencies = installDependencies;
|
|
7
|
+
exports.installPackage = installPackage;
|
|
8
|
+
exports.runInstall = runInstall;
|
|
9
|
+
const node_child_process_1 = require("node:child_process");
|
|
10
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
11
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
12
|
+
function installDependencies(target) {
|
|
13
|
+
runInstall(target);
|
|
14
|
+
}
|
|
15
|
+
function installPackage(target, packageName) {
|
|
16
|
+
runInstall(target, [packageName]);
|
|
17
|
+
}
|
|
18
|
+
function runInstall(target, packages = []) {
|
|
19
|
+
const useBun = node_fs_1.default.existsSync(node_path_1.default.join(target, "bun.lock"));
|
|
20
|
+
const pm = useBun ? "bun" : "npm";
|
|
21
|
+
const action = useBun ? "add" : "install";
|
|
22
|
+
const command = packages.length > 0
|
|
23
|
+
? [pm, action, ...packages].join(" ")
|
|
24
|
+
: [pm, "install"].join(" ");
|
|
25
|
+
console.log(`Running: ${command}`);
|
|
26
|
+
(0, node_child_process_1.execSync)(command, {
|
|
27
|
+
cwd: target,
|
|
28
|
+
stdio: "inherit"
|
|
29
|
+
});
|
|
30
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
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.fetchLatestTarballUrl = fetchLatestTarballUrl;
|
|
7
|
+
exports.downloadTarball = downloadTarball;
|
|
8
|
+
const node_https_1 = __importDefault(require("node:https"));
|
|
9
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
10
|
+
function fetchLatestTarballUrl(packageName) {
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
const encodedPackageName = packageName.replace("/", "%2f");
|
|
13
|
+
const url = `https://registry.npmjs.org/${encodedPackageName}/latest`;
|
|
14
|
+
node_https_1.default.get(url, { headers: { "User-Agent": "skalfa-cli" } }, (res) => {
|
|
15
|
+
if (res.statusCode !== 200) {
|
|
16
|
+
reject(new Error(`Failed to fetch latest version info for ${packageName} from npm registry. Status: ${res.statusCode} ${res.statusMessage}`));
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
let data = "";
|
|
20
|
+
res.on("data", (chunk) => {
|
|
21
|
+
data += chunk;
|
|
22
|
+
});
|
|
23
|
+
res.on("end", () => {
|
|
24
|
+
try {
|
|
25
|
+
const json = JSON.parse(data);
|
|
26
|
+
const tarballUrl = json.dist?.tarball;
|
|
27
|
+
if (!tarballUrl) {
|
|
28
|
+
reject(new Error(`Could not find tarball URL in registry response for ${packageName}.`));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
resolve(tarballUrl);
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
reject(err);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}).on("error", (err) => {
|
|
38
|
+
reject(new Error(`Network error while contacting npm registry: ${err.message}`));
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function downloadTarball(url, destPath) {
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
const file = node_fs_1.default.createWriteStream(destPath);
|
|
45
|
+
node_https_1.default.get(url, { headers: { "User-Agent": "skalfa-cli" } }, (res) => {
|
|
46
|
+
if (res.statusCode !== 200) {
|
|
47
|
+
file.close();
|
|
48
|
+
node_fs_1.default.unlink(destPath, () => { });
|
|
49
|
+
reject(new Error(`Failed to download tarball from ${url}. Status: ${res.statusCode} ${res.statusMessage}`));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
res.pipe(file);
|
|
53
|
+
file.on("finish", () => {
|
|
54
|
+
file.close();
|
|
55
|
+
resolve();
|
|
56
|
+
});
|
|
57
|
+
}).on("error", (err) => {
|
|
58
|
+
file.close();
|
|
59
|
+
node_fs_1.default.unlink(destPath, () => { });
|
|
60
|
+
reject(err);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@skalfa/skalfa-cli",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Skalfa API scaffolding and extension installer CLI.",
|
|
5
|
+
"main": "dist/bin/skalfa.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"skalfa": "./dist/bin/skalfa.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc -p tsconfig.json",
|
|
14
|
+
"start": "node ./dist/bin/skalfa.js",
|
|
15
|
+
"check": "npm run build && node ./dist/bin/skalfa.js --help",
|
|
16
|
+
"prepare": "npm run build"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"aluna",
|
|
20
|
+
"cli",
|
|
21
|
+
"scaffold"
|
|
22
|
+
],
|
|
23
|
+
"author": "",
|
|
24
|
+
"license": "UNLICENSED",
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"commander": "^12.1.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^26.0.0",
|
|
30
|
+
"typescript": "^6.0.3"
|
|
31
|
+
}
|
|
32
|
+
}
|