@skalfa/skalfa-cli 1.0.12 → 1.0.13
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/skalfa.js +9 -6
- package/dist/commands/agent.js +27 -0
- package/dist/commands/create-api.js +107 -9
- package/dist/commands/create-app.js +64 -5
- package/dist/commands/init.js +10 -1
- package/package.json +1 -1
package/dist/bin/skalfa.js
CHANGED
|
@@ -63,22 +63,25 @@ program
|
|
|
63
63
|
.command("init")
|
|
64
64
|
.description("Initialize a new Skalfa monorepo project containing both API and App.")
|
|
65
65
|
.argument("[name]", "project folder name")
|
|
66
|
-
.
|
|
67
|
-
|
|
66
|
+
.option("-a, --auth <type>", "Authentication type: username or email")
|
|
67
|
+
.action(async (name, options) => {
|
|
68
|
+
await runCommand(() => (0, init_1.initProject)(name, options?.auth ? { authType: options.auth } : undefined));
|
|
68
69
|
});
|
|
69
70
|
program
|
|
70
71
|
.command("create:api")
|
|
71
72
|
.description("Create a new Skalfa API project.")
|
|
72
73
|
.argument("<name>", "project folder and package name")
|
|
73
|
-
.
|
|
74
|
-
|
|
74
|
+
.option("-a, --auth <type>", "Authentication type: username or email")
|
|
75
|
+
.action(async (name, options) => {
|
|
76
|
+
await runCommand(() => (0, create_api_1.createApi)(name, options.auth ? { authType: options.auth } : undefined));
|
|
75
77
|
});
|
|
76
78
|
program
|
|
77
79
|
.command("create:app")
|
|
78
80
|
.description("Create a new Skalfa App Next.js project.")
|
|
79
81
|
.argument("<name>", "project folder and package name")
|
|
80
|
-
.
|
|
81
|
-
|
|
82
|
+
.option("-a, --auth <type>", "Authentication type: username or email")
|
|
83
|
+
.action(async (name, options) => {
|
|
84
|
+
await runCommand(() => (0, create_app_1.createApp)(name, options.auth ? { authType: options.auth } : undefined));
|
|
82
85
|
});
|
|
83
86
|
program
|
|
84
87
|
.command("add")
|
package/dist/commands/agent.js
CHANGED
|
@@ -67,6 +67,33 @@ async function installAgent(overrideType, targetProjectDir = process.cwd()) {
|
|
|
67
67
|
console.log(`Created initial record: ${file}`);
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
|
+
if (type === "app") {
|
|
71
|
+
// Detect auth type from page.tsx or similar
|
|
72
|
+
const loginPagePath = node_path_1.default.join(targetProjectDir, "app", "auth", "login", "page.tsx");
|
|
73
|
+
let isEmail = true;
|
|
74
|
+
if (node_fs_1.default.existsSync(loginPagePath)) {
|
|
75
|
+
const loginContent = node_fs_1.default.readFileSync(loginPagePath, "utf8");
|
|
76
|
+
isEmail = loginContent.includes("email");
|
|
77
|
+
}
|
|
78
|
+
const updateFeaturesFile = (filePath) => {
|
|
79
|
+
if (node_fs_1.default.existsSync(filePath)) {
|
|
80
|
+
let content = node_fs_1.default.readFileSync(filePath, "utf8");
|
|
81
|
+
if (isEmail) {
|
|
82
|
+
content = content
|
|
83
|
+
.replace(/\|\s*`admin`\s*\|/g, "| `admin@mail.com` |")
|
|
84
|
+
.replace(/\|\s*`user`\s*\|/g, "| `user@mail.com` |");
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
content = content
|
|
88
|
+
.replace(/\|\s*`admin`\s*\|/g, "| `admin` |")
|
|
89
|
+
.replace(/\|\s*`user`\s*\|/g, "| `user` |");
|
|
90
|
+
}
|
|
91
|
+
node_fs_1.default.writeFileSync(filePath, content, "utf8");
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
updateFeaturesFile(node_path_1.default.join(templatesDir, "features.md"));
|
|
95
|
+
updateFeaturesFile(node_path_1.default.join(recordsDir, "features.md"));
|
|
96
|
+
}
|
|
70
97
|
}
|
|
71
98
|
// 3. Add /.agents/ to project's .gitignore
|
|
72
99
|
const gitignorePath = node_path_1.default.join(targetProjectDir, ".gitignore");
|
|
@@ -48,10 +48,13 @@ async function createApi(projectName, options) {
|
|
|
48
48
|
let hasCron = options?.cron ?? false;
|
|
49
49
|
let hasDa = options?.da ?? false;
|
|
50
50
|
let hasSocket = options?.socket ?? false;
|
|
51
|
+
let authType = options?.authType ?? "username";
|
|
51
52
|
if (!options) {
|
|
52
53
|
// Ask interactive questions sequentially using a single readline interface
|
|
53
54
|
const q = new Questioner();
|
|
54
55
|
try {
|
|
56
|
+
const authChoice = (await q.ask("Choose authentication type (username/email) [default: username]: ", "username")).toLowerCase();
|
|
57
|
+
authType = authChoice === "email" ? "email" : "username";
|
|
55
58
|
hasRedis = (await q.ask("Do you need Redis? (y/N): ", "No")).toLowerCase().startsWith("y");
|
|
56
59
|
hasQueue = (await q.ask("Do you need Queue? (y/N): ", "No")).toLowerCase().startsWith("y");
|
|
57
60
|
hasCache = (await q.ask("Do you need Cache? (y/N): ", "No")).toLowerCase().startsWith("y");
|
|
@@ -188,7 +191,8 @@ If you are an AI coding agent assisting with this project, please make sure to r
|
|
|
188
191
|
cache: hasCache,
|
|
189
192
|
cron: hasCron,
|
|
190
193
|
da: hasDa,
|
|
191
|
-
socket: hasSocket
|
|
194
|
+
socket: hasSocket,
|
|
195
|
+
authType: authType
|
|
192
196
|
});
|
|
193
197
|
spinner.update("Installing dependencies (this may take a moment)...");
|
|
194
198
|
await (0, installer_1.installDependenciesAsync)(target);
|
|
@@ -221,35 +225,37 @@ function customizeProject(target, opts) {
|
|
|
221
225
|
const pkg = JSON.parse(node_fs_1.default.readFileSync(packageJsonPath, "utf8"));
|
|
222
226
|
pkg.dependencies = pkg.dependencies || {};
|
|
223
227
|
pkg.scripts = pkg.scripts || {};
|
|
228
|
+
const isMonorepo = node_path_1.default.basename(target) === "api" || node_path_1.default.basename(target) === "app";
|
|
229
|
+
const devPathPrefix = isMonorepo ? "file:../../" : "file:../";
|
|
224
230
|
// Base ORM integration (always included)
|
|
225
|
-
pkg.dependencies["@skalfa/skalfa-orm"] = isDev ?
|
|
231
|
+
pkg.dependencies["@skalfa/skalfa-orm"] = isDev ? `${devPathPrefix}skalfa-orm` : "^1.0.0";
|
|
226
232
|
if (isDev) {
|
|
227
|
-
pkg.dependencies["@skalfa/skalfa-api-core"] =
|
|
233
|
+
pkg.dependencies["@skalfa/skalfa-api-core"] = `${devPathPrefix}skalfa-api-core`;
|
|
228
234
|
}
|
|
229
235
|
const devCommands = ["bun run --watch app/app.ts", "bun skalfa watch:barrels"];
|
|
230
236
|
if (opts.redis) {
|
|
231
|
-
pkg.dependencies["@skalfa/skalfa-redis"] = isDev ?
|
|
237
|
+
pkg.dependencies["@skalfa/skalfa-redis"] = isDev ? `${devPathPrefix}skalfa-redis` : "^1.0.0";
|
|
232
238
|
pkg.dependencies["ioredis"] = "^5.4.1";
|
|
233
239
|
}
|
|
234
240
|
if (opts.queue) {
|
|
235
|
-
pkg.dependencies["@skalfa/skalfa-queue"] = isDev ?
|
|
241
|
+
pkg.dependencies["@skalfa/skalfa-queue"] = isDev ? `${devPathPrefix}skalfa-queue` : "^1.0.0";
|
|
236
242
|
pkg.scripts["start:queue"] = "bun run app/jobs/queues/worker.queue.ts";
|
|
237
243
|
devCommands.push("bun start:queue");
|
|
238
244
|
}
|
|
239
245
|
if (opts.cache) {
|
|
240
|
-
pkg.dependencies["@skalfa/skalfa-cache"] = isDev ?
|
|
246
|
+
pkg.dependencies["@skalfa/skalfa-cache"] = isDev ? `${devPathPrefix}skalfa-cache` : "^1.0.0";
|
|
241
247
|
}
|
|
242
248
|
if (opts.cron) {
|
|
243
|
-
pkg.dependencies["@skalfa/skalfa-cron"] = isDev ?
|
|
249
|
+
pkg.dependencies["@skalfa/skalfa-cron"] = isDev ? `${devPathPrefix}skalfa-cron` : "^1.0.0";
|
|
244
250
|
pkg.scripts["start:cron"] = "bun run app/jobs/crons/worker.cron.ts";
|
|
245
251
|
devCommands.push("bun start:cron");
|
|
246
252
|
}
|
|
247
253
|
if (opts.da) {
|
|
248
|
-
pkg.dependencies["@skalfa/skalfa-da"] = isDev ?
|
|
254
|
+
pkg.dependencies["@skalfa/skalfa-da"] = isDev ? `${devPathPrefix}skalfa-da` : "^1.0.0";
|
|
249
255
|
pkg.dependencies["@clickhouse/client"] = "^1.6.0";
|
|
250
256
|
}
|
|
251
257
|
if (opts.socket) {
|
|
252
|
-
pkg.dependencies["@skalfa/skalfa-socket"] = isDev ?
|
|
258
|
+
pkg.dependencies["@skalfa/skalfa-socket"] = isDev ? `${devPathPrefix}skalfa-socket` : "^1.0.0";
|
|
253
259
|
pkg.dependencies["socket.io"] = "^4.7.5";
|
|
254
260
|
pkg.scripts["start:socket"] = "bun run app/jobs/sockets/worker.socket.ts";
|
|
255
261
|
devCommands.push("bun start:socket");
|
|
@@ -349,6 +355,98 @@ function customizeProject(target, opts) {
|
|
|
349
355
|
}
|
|
350
356
|
node_fs_1.default.writeFileSync(appTsPath, content, "utf8");
|
|
351
357
|
}
|
|
358
|
+
// 6. Handle authentication type customization
|
|
359
|
+
if (opts.authType === "username") {
|
|
360
|
+
// A. Modify migration: database/migrations/0000_00/users.ts
|
|
361
|
+
const migrationDir = node_path_1.default.join(target, "database", "migrations", "0000_00");
|
|
362
|
+
const migrationUsersPath = node_path_1.default.join(migrationDir, "users.ts");
|
|
363
|
+
if (node_fs_1.default.existsSync(migrationUsersPath)) {
|
|
364
|
+
let content = node_fs_1.default.readFileSync(migrationUsersPath, "utf8");
|
|
365
|
+
// Replace table.string("email").unique().notNullable() with table.string("username").unique().notNullable()
|
|
366
|
+
content = content.replace(/table\.string\("email"\)\.unique\(\)\.notNullable\(\)/g, 'table.string("username").unique().notNullable()');
|
|
367
|
+
// Remove table.timestamp("email_verification_at")
|
|
368
|
+
content = content.replace(/table\.timestamp\("email_verification_at"\)\r?\n?/g, '');
|
|
369
|
+
// Delete the user_mail_tokens table schema creation
|
|
370
|
+
content = content.replace(/await\s+knex\.schema\.createTable\("user_mail_tokens",\s*\((table|t)\)\s*=>\s*\{[\s\S]*?\}\)\r?\n?/g, '');
|
|
371
|
+
node_fs_1.default.writeFileSync(migrationUsersPath, content, "utf8");
|
|
372
|
+
}
|
|
373
|
+
// B. Modify model: app/models/iam/user.model.ts
|
|
374
|
+
const userModelPath = node_path_1.default.join(target, "app", "models", "iam", "user.model.ts");
|
|
375
|
+
if (node_fs_1.default.existsSync(userModelPath)) {
|
|
376
|
+
let content = node_fs_1.default.readFileSync(userModelPath, "utf8");
|
|
377
|
+
// Replace email field with username field
|
|
378
|
+
content = content.replace(/@Field\(\s*\[\s*"fillable"\s*,\s*"selectable"\s*,\s*"searchable"\s*\]\s*\)\s*\r?\n?\s*email!:\s*string/g, '@Field(["fillable", "selectable", "searchable"])\n username!: string');
|
|
379
|
+
// Remove email_verification_at field
|
|
380
|
+
content = content.replace(/@Field\(\s*\[\s*"fillable"\s*,\s*"selectable"\s*,\s*"searchable"\s*\]\s*\)\s*\r?\n?\s*email_verification_at!:\s*Date/g, '');
|
|
381
|
+
node_fs_1.default.writeFileSync(userModelPath, content, "utf8");
|
|
382
|
+
}
|
|
383
|
+
// C. Modify controller: app/controllers/iam/auth.controller.ts
|
|
384
|
+
const authControllerPath = node_path_1.default.join(target, "app", "controllers", "iam", "auth.controller.ts");
|
|
385
|
+
if (node_fs_1.default.existsSync(authControllerPath)) {
|
|
386
|
+
let content = node_fs_1.default.readFileSync(authControllerPath, "utf8");
|
|
387
|
+
// Remove import { UserMailToken } from "app/outputs/mails";
|
|
388
|
+
content = content.replace(/import\s*\{\s*UserMailToken\s*\}\s*from\s*["']app\/outputs\/mails["'];?\r?\n?/g, '');
|
|
389
|
+
// Update validation and logic in login
|
|
390
|
+
content = content.replace(/email\s*:\s*["']required["'],/g, 'username : "required",');
|
|
391
|
+
content = content.replace(/const\s*\{\s*email\s*,\s*password\s*\}\s*=\s*c\.body/g, 'const { username, password } = c.body');
|
|
392
|
+
content = content.replace(/const user\s*=\s*await\s*User\.query\(\)\.where\("email",\s*email\)\.whereNotNull\("email_verification_at"\)\.first\(\);/g, 'const user = await User.query().where("username", username).first();');
|
|
393
|
+
content = content.replace(/if\s*\(!user\)\s*return\s*c\.responseErrorValidation\(\{\s*email\s*:\s*\[\s*["']E-mail not found!["']\s*\]\s*\}\)/g, 'if (!user) return c.responseErrorValidation({username: ["Username not found!"]})');
|
|
394
|
+
// Remove register and verify methods
|
|
395
|
+
content = content.replace(/\/\/\s*=+\s*>\s*\/\/\s*## Register new account\.[\s\S]*?(?=\/\/\s*=+\s*>\s*\/\/\s*## Get logged account)/g, '');
|
|
396
|
+
// Replace update method validation: email: "required" -> username: "required"
|
|
397
|
+
content = content.replace(/email\s*:\s*["']required["'],/g, 'username : "required",');
|
|
398
|
+
node_fs_1.default.writeFileSync(authControllerPath, content, "utf8");
|
|
399
|
+
}
|
|
400
|
+
// D. Modify router: app/routes/base.routes.ts
|
|
401
|
+
const baseRoutesPath = node_path_1.default.join(target, "app", "routes", "base.routes.ts");
|
|
402
|
+
if (node_fs_1.default.existsSync(baseRoutesPath)) {
|
|
403
|
+
let content = node_fs_1.default.readFileSync(baseRoutesPath, "utf8");
|
|
404
|
+
// Comment out register and verify routes
|
|
405
|
+
content = content.replace(/route\.post\('\/register',\s*AuthController\.register\)/g, '// route.post(\'/register\', AuthController.register) - disabled in username auth');
|
|
406
|
+
content = content.replace(/route\.post\('\/verify',\s*AuthController\.verify\)/g, '// route.post(\'/verify\', AuthController.verify) - disabled in username auth');
|
|
407
|
+
node_fs_1.default.writeFileSync(baseRoutesPath, content, "utf8");
|
|
408
|
+
}
|
|
409
|
+
// E. Modify controller: app/controllers/iam/user.controller.ts
|
|
410
|
+
const userControllerPath = node_path_1.default.join(target, "app", "controllers", "iam", "user.controller.ts");
|
|
411
|
+
if (node_fs_1.default.existsSync(userControllerPath)) {
|
|
412
|
+
let content = node_fs_1.default.readFileSync(userControllerPath, "utf8");
|
|
413
|
+
// Replace validation in store: email : ["required", "email"], -> username : ["required"],
|
|
414
|
+
content = content.replace(/email\s*:\s*\[\s*["']required["']\s*,\s*["']email["']\s*\]/g, 'username : ["required"]');
|
|
415
|
+
// Replace validation in update: email : "required", -> username : "required",
|
|
416
|
+
content = content.replace(/email\s*:\s*["']required["']/g, 'username : "required"');
|
|
417
|
+
node_fs_1.default.writeFileSync(userControllerPath, content, "utf8");
|
|
418
|
+
}
|
|
419
|
+
// F. Modify seeder: database/seeders/user.seeder.ts
|
|
420
|
+
const userSeederPath = node_path_1.default.join(target, "database", "seeders", "user.seeder.ts");
|
|
421
|
+
if (node_fs_1.default.existsSync(userSeederPath)) {
|
|
422
|
+
let content = node_fs_1.default.readFileSync(userSeederPath, "utf8");
|
|
423
|
+
// Seed roles: Admin and User
|
|
424
|
+
content = content.replace(/\{"name": "Petugas"\}/g, '{"name": "User"}');
|
|
425
|
+
// Seed users with username instead of email
|
|
426
|
+
content = content.replace(/\{"name": "Admin", "email": "admin@skalfa.id",/g, '{"name": "Admin", "username": "admin",');
|
|
427
|
+
content = content.replace(/\{"name": "Petugas", "email": "petugas@skalfa.id",/g, '{"name": "User", "username": "user",');
|
|
428
|
+
node_fs_1.default.writeFileSync(userSeederPath, content, "utf8");
|
|
429
|
+
}
|
|
430
|
+
// G. Delete app/outputs/mails folder
|
|
431
|
+
const mailsDir = node_path_1.default.join(target, "app", "outputs", "mails");
|
|
432
|
+
if (node_fs_1.default.existsSync(mailsDir)) {
|
|
433
|
+
node_fs_1.default.rmSync(mailsDir, { recursive: true, force: true });
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
// If authType === "email":
|
|
438
|
+
// Change seeder from petugas@skalfa.id to user@mail.com and admin@skalfa.id to admin@mail.com, role Petugas to User
|
|
439
|
+
const userSeederPath = node_path_1.default.join(target, "database", "seeders", "user.seeder.ts");
|
|
440
|
+
if (node_fs_1.default.existsSync(userSeederPath)) {
|
|
441
|
+
let content = node_fs_1.default.readFileSync(userSeederPath, "utf8");
|
|
442
|
+
content = content.replace(/\{"name": "Petugas"\}/g, '{"name": "User"}');
|
|
443
|
+
content = content.replace(/admin@skalfa.id/g, 'admin@mail.com');
|
|
444
|
+
content = content.replace(/\{"name": "Petugas", "email": "petugas@mail.com",/g, '{"name": "User", "email": "user@mail.com",');
|
|
445
|
+
content = content.replace(/\{"name": "Petugas", "email": "petugas@skalfa.id",/g, '{"name": "User", "email": "user@mail.com",');
|
|
446
|
+
content = content.replace(/petugas@skalfa.id/g, 'user@mail.com');
|
|
447
|
+
node_fs_1.default.writeFileSync(userSeederPath, content, "utf8");
|
|
448
|
+
}
|
|
449
|
+
}
|
|
352
450
|
}
|
|
353
451
|
function addTsconfigPath(tsconfigPath, packageName) {
|
|
354
452
|
if (!node_fs_1.default.existsSync(tsconfigPath))
|
|
@@ -47,10 +47,13 @@ async function createApp(projectName, options) {
|
|
|
47
47
|
let hasPwa = options?.pwa ?? false;
|
|
48
48
|
let hasTauriDesktop = options?.tauriDesktop ?? false;
|
|
49
49
|
let hasTauriMobile = options?.tauriMobile ?? false;
|
|
50
|
+
let authType = options?.authType ?? "username";
|
|
50
51
|
if (!options) {
|
|
51
52
|
// Ask interactive questions sequentially
|
|
52
53
|
const q = new Questioner();
|
|
53
54
|
try {
|
|
55
|
+
const authChoice = (await q.ask("Choose authentication type (username/email) [default: username]: ", "username")).toLowerCase();
|
|
56
|
+
authType = authChoice === "email" ? "email" : "username";
|
|
54
57
|
hasIdb = (await q.ask("Do you need IndexedDB (IDB)? (y/N): ", "No")).toLowerCase().startsWith("y");
|
|
55
58
|
hasSocket = (await q.ask("Do you need Socket Client? (y/N): ", "No")).toLowerCase().startsWith("y");
|
|
56
59
|
hasDocument = (await q.ask("Do you need Document Export/Viewer (PDF/Excel)? (y/N): ", "No")).toLowerCase().startsWith("y");
|
|
@@ -174,7 +177,8 @@ If you are an AI coding agent assisting with this project, please make sure to r
|
|
|
174
177
|
document: hasDocument,
|
|
175
178
|
pwa: hasPwa,
|
|
176
179
|
tauriDesktop: hasTauriDesktop,
|
|
177
|
-
tauriMobile: hasTauriMobile
|
|
180
|
+
tauriMobile: hasTauriMobile,
|
|
181
|
+
authType: authType
|
|
178
182
|
});
|
|
179
183
|
spinner.update("Installing dependencies (this may take a moment)...");
|
|
180
184
|
await (0, installer_1.installDependenciesAsync)(target);
|
|
@@ -200,6 +204,8 @@ function customizeProject(target, opts) {
|
|
|
200
204
|
const packageJsonPath = node_path_1.default.join(target, "package.json");
|
|
201
205
|
const baseComponentsIndexPath = node_path_1.default.join(target, "components", "base.components", "index.ts");
|
|
202
206
|
const isDev = !!process.env[TEMPLATE_ENV_KEY];
|
|
207
|
+
const isMonorepo = node_path_1.default.basename(target) === "api" || node_path_1.default.basename(target) === "app";
|
|
208
|
+
const devPathPrefix = isMonorepo ? "file:../../" : "file:../";
|
|
203
209
|
// 1. Update dependencies and scripts in package.json
|
|
204
210
|
if (node_fs_1.default.existsSync(packageJsonPath)) {
|
|
205
211
|
const pkg = JSON.parse(node_fs_1.default.readFileSync(packageJsonPath, "utf8"));
|
|
@@ -207,10 +213,13 @@ function customizeProject(target, opts) {
|
|
|
207
213
|
pkg.devDependencies = pkg.devDependencies || {};
|
|
208
214
|
pkg.scripts = pkg.scripts || {};
|
|
209
215
|
// Core dependency
|
|
210
|
-
pkg.dependencies["@skalfa/skalfa-app-core"] = isDev ?
|
|
216
|
+
pkg.dependencies["@skalfa/skalfa-app-core"] = isDev ? `${devPathPrefix}skalfa-app-core` : "^1.0.0";
|
|
217
|
+
if (isDev && pkg.dependencies["@skalfa/skalfa-component"]) {
|
|
218
|
+
pkg.dependencies["@skalfa/skalfa-component"] = `${devPathPrefix}skalfa-component`;
|
|
219
|
+
}
|
|
211
220
|
// A. IndexedDB Option
|
|
212
221
|
if (opts.idb) {
|
|
213
|
-
pkg.dependencies["@skalfa/skalfa-idb"] = isDev ?
|
|
222
|
+
pkg.dependencies["@skalfa/skalfa-idb"] = isDev ? `${devPathPrefix}skalfa-idb` : "^1.0.0";
|
|
214
223
|
}
|
|
215
224
|
else {
|
|
216
225
|
// Delete schema directory
|
|
@@ -242,12 +251,12 @@ function customizeProject(target, opts) {
|
|
|
242
251
|
}
|
|
243
252
|
// B. Socket Option
|
|
244
253
|
if (opts.socket) {
|
|
245
|
-
pkg.dependencies["@skalfa/skalfa-socket-client"] = isDev ?
|
|
254
|
+
pkg.dependencies["@skalfa/skalfa-socket-client"] = isDev ? `${devPathPrefix}skalfa-socket-client` : "^1.0.0";
|
|
246
255
|
pkg.dependencies["socket.io-client"] = "^4.8.1";
|
|
247
256
|
}
|
|
248
257
|
// C. Document Option
|
|
249
258
|
if (opts.document) {
|
|
250
|
-
pkg.dependencies["@skalfa/skalfa-document"] = isDev ?
|
|
259
|
+
pkg.dependencies["@skalfa/skalfa-document"] = isDev ? `${devPathPrefix}skalfa-document` : "^1.0.0";
|
|
251
260
|
pkg.dependencies["exceljs"] = "^4.4.0";
|
|
252
261
|
pkg.dependencies["pdf-lib"] = "^1.17.1";
|
|
253
262
|
pkg.dependencies["pdfjs-dist"] = "^4.4.168";
|
|
@@ -316,4 +325,54 @@ function customizeProject(target, opts) {
|
|
|
316
325
|
}
|
|
317
326
|
node_fs_1.default.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2), "utf8");
|
|
318
327
|
}
|
|
328
|
+
// 6. Handle authentication type customization
|
|
329
|
+
if (opts.authType === "username") {
|
|
330
|
+
// A. Modify app/auth/login/page.tsx
|
|
331
|
+
const loginPagePath = node_path_1.default.join(target, "app", "auth", "login", "page.tsx");
|
|
332
|
+
if (node_fs_1.default.existsSync(loginPagePath)) {
|
|
333
|
+
let content = node_fs_1.default.readFileSync(loginPagePath, "utf8");
|
|
334
|
+
// Replace email field with username field
|
|
335
|
+
content = content.replace(/\{\s*construction:\s*\{\s*name:\s*["']email["'],\s*label:\s*["']E-mail["'],\s*placeholder:\s*["']Ex:\s*example@mail\.com["'],\s*validations:\s*["']required\|min:10\|max:50\|email["']\s*\}\s*\}/g, `{\n construction: {\n name: "username",\n label: "Username",\n placeholder: "Ex: joko.gunawan",\n validations: "required|min:3|max:50"\n }\n }`);
|
|
336
|
+
// Remove Create Account link in login page
|
|
337
|
+
content = content.replace(/<p className="mt-4 text-center">Don't have an account yet\? <Link href="\/auth\/register" className="text-primary underline">Create Account<\/Link><\/p>/g, '');
|
|
338
|
+
node_fs_1.default.writeFileSync(loginPagePath, content, "utf8");
|
|
339
|
+
}
|
|
340
|
+
// B. Modify app/auth/edit/page.tsx
|
|
341
|
+
const editPagePath = node_path_1.default.join(target, "app", "auth", "edit", "page.tsx");
|
|
342
|
+
if (node_fs_1.default.existsSync(editPagePath)) {
|
|
343
|
+
let content = node_fs_1.default.readFileSync(editPagePath, "utf8");
|
|
344
|
+
// Replace email field with username field
|
|
345
|
+
content = content.replace(/\{\s*construction:\s*\{\s*name:\s*["']email["'],\s*label:\s*["']E-mail["'],\s*placeholder:\s*["']Ex:\s*example@mail\.com["'],\s*\}\s*\}/g, `{\n construction: {\n name: "username",\n label: "Username",\n placeholder: "Ex: joko.gunawan",\n }\n }`);
|
|
346
|
+
node_fs_1.default.writeFileSync(editPagePath, content, "utf8");
|
|
347
|
+
}
|
|
348
|
+
// C. Modify app/auth/me/page.tsx
|
|
349
|
+
const mePagePath = node_path_1.default.join(target, "app", "auth", "me", "page.tsx");
|
|
350
|
+
if (node_fs_1.default.existsSync(mePagePath)) {
|
|
351
|
+
let content = node_fs_1.default.readFileSync(mePagePath, "utf8");
|
|
352
|
+
// Replace email display with username
|
|
353
|
+
content = content.replace(/<div>\s*<p className="text-xs font-semibold text-light-foreground">\s*Email\s*<\/p>\s*<p>\{user\?\.email\}<\/p>\s*<\/div>/g, `<div>\n <p className="text-xs font-semibold text-light-foreground">\n Username\n </p>\n <p>{user?.username}</p>\n </div>`);
|
|
354
|
+
node_fs_1.default.writeFileSync(mePagePath, content, "utf8");
|
|
355
|
+
}
|
|
356
|
+
// D. Modify app/dashboard/user/page.tsx
|
|
357
|
+
const userPagePath = node_path_1.default.join(target, "app", "dashboard", "user", "page.tsx");
|
|
358
|
+
if (node_fs_1.default.existsSync(userPagePath)) {
|
|
359
|
+
let content = node_fs_1.default.readFileSync(userPagePath, "utf8");
|
|
360
|
+
// Replace table column for email
|
|
361
|
+
content = content.replace(/\{\s*selector:\s*["']email["'],\s*label:\s*["']Email["'],\s*sortable:\s*true,\s*width:\s*["']250px["'],\s*\}/g, `{\n selector: "username",\n label: "Username",\n sortable: true,\n width: "250px",\n }`);
|
|
362
|
+
// Replace detail panel for email
|
|
363
|
+
content = content.replace(/\{\s*label:\s*["']Email["'],\s*item:\s*["']email["'],\s*\}/g, `{\n label: "Username",\n item: "username",\n }`);
|
|
364
|
+
// Replace form field for email
|
|
365
|
+
content = content.replace(/\{\s*construction:\s*\{\s*name:\s*["']email["'],\s*label:\s*["']E-mail["'],\s*placeholder:\s*["']Ex:\s*example@mail\.com["'],\s*validations:\s*\[\s*["']required["']\s*\]\s*,\s*\}\s*,?\s*\}/g, `{\n construction: {\n name: "username",\n label: "Username",\n placeholder: "Ex: joko.gunawan",\n validations: ["required"],\n },\n }`);
|
|
366
|
+
node_fs_1.default.writeFileSync(userPagePath, content, "utf8");
|
|
367
|
+
}
|
|
368
|
+
// E. Delete app/auth/register and app/auth/verify folders (self-registration and verification disabled/removed)
|
|
369
|
+
const registerDir = node_path_1.default.join(target, "app", "auth", "register");
|
|
370
|
+
if (node_fs_1.default.existsSync(registerDir)) {
|
|
371
|
+
node_fs_1.default.rmSync(registerDir, { recursive: true, force: true });
|
|
372
|
+
}
|
|
373
|
+
const verifyDir = node_path_1.default.join(target, "app", "auth", "verify");
|
|
374
|
+
if (node_fs_1.default.existsSync(verifyDir)) {
|
|
375
|
+
node_fs_1.default.rmSync(verifyDir, { recursive: true, force: true });
|
|
376
|
+
}
|
|
377
|
+
}
|
|
319
378
|
}
|
package/dist/commands/init.js
CHANGED
|
@@ -30,7 +30,7 @@ class Questioner {
|
|
|
30
30
|
this.rl.close();
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
-
async function initProject(projectName) {
|
|
33
|
+
async function initProject(projectName, options) {
|
|
34
34
|
const cwd = process.cwd();
|
|
35
35
|
const isCurrentDir = !projectName || projectName === ".";
|
|
36
36
|
const target = isCurrentDir ? cwd : node_path_1.default.resolve(cwd, projectName);
|
|
@@ -62,7 +62,14 @@ async function initProject(projectName) {
|
|
|
62
62
|
let appPwa = false;
|
|
63
63
|
let appTauriDesktop = false;
|
|
64
64
|
let appTauriMobile = false;
|
|
65
|
+
let authType = options?.authType ?? "username";
|
|
66
|
+
const shouldPromptAuth = !options || !options.authType;
|
|
65
67
|
try {
|
|
68
|
+
if (shouldPromptAuth) {
|
|
69
|
+
console.log("\n--- Configure Authentication ---");
|
|
70
|
+
const authChoice = (await q.ask("Choose authentication type (username/email) [default: username]: ", "username")).toLowerCase();
|
|
71
|
+
authType = authChoice === "email" ? "email" : "username";
|
|
72
|
+
}
|
|
66
73
|
console.log("\n--- Configure API (Backend) ---");
|
|
67
74
|
apiRedis = (await q.ask("Do you need Redis? (y/N): ", "No")).toLowerCase().startsWith("y");
|
|
68
75
|
apiQueue = (await q.ask("Do you need Queue? (y/N): ", "No")).toLowerCase().startsWith("y");
|
|
@@ -93,6 +100,7 @@ async function initProject(projectName) {
|
|
|
93
100
|
cron: apiCron,
|
|
94
101
|
da: apiDa,
|
|
95
102
|
socket: apiSocket,
|
|
103
|
+
authType: authType,
|
|
96
104
|
});
|
|
97
105
|
console.log("\nCreating App...");
|
|
98
106
|
await (0, create_app_1.createApp)(appDir, {
|
|
@@ -102,6 +110,7 @@ async function initProject(projectName) {
|
|
|
102
110
|
pwa: appPwa,
|
|
103
111
|
tauriDesktop: appTauriDesktop,
|
|
104
112
|
tauriMobile: appTauriMobile,
|
|
113
|
+
authType: authType,
|
|
105
114
|
});
|
|
106
115
|
console.log("\nInstalling AI Agents...");
|
|
107
116
|
try {
|
package/package.json
CHANGED