@skalfa/skalfa-api-core 1.0.3 → 1.0.8
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/CONTRIBUTING.md +45 -0
- package/LICENSE +21 -0
- package/README.md +60 -0
- package/dist/auth/auth.d.ts +18 -15
- package/dist/auth/auth.js +20 -203
- package/dist/auth/auth.js.map +1 -1
- package/dist/auth/create-access-token.d.ts +4 -0
- package/dist/auth/create-access-token.js +26 -0
- package/dist/auth/create-access-token.js.map +1 -0
- package/dist/auth/create-user-mail-token.d.ts +4 -0
- package/dist/auth/create-user-mail-token.js +19 -0
- package/dist/auth/create-user-mail-token.js.map +1 -0
- package/dist/auth/helpers/generate-agent-id.d.ts +1 -0
- package/dist/auth/helpers/generate-agent-id.js +7 -0
- package/dist/auth/helpers/generate-agent-id.js.map +1 -0
- package/dist/auth/helpers/get-request-ip.d.ts +1 -0
- package/dist/auth/helpers/get-request-ip.js +4 -0
- package/dist/auth/helpers/get-request-ip.js.map +1 -0
- package/dist/auth/helpers/get-user-permissions.d.ts +1 -0
- package/dist/auth/helpers/get-user-permissions.js +9 -0
- package/dist/auth/helpers/get-user-permissions.js.map +1 -0
- package/dist/auth/helpers/index.d.ts +3 -0
- package/dist/auth/helpers/index.js +4 -0
- package/dist/auth/helpers/index.js.map +1 -0
- package/dist/auth/list-user-sessions.d.ts +1 -0
- package/dist/auth/list-user-sessions.js +10 -0
- package/dist/auth/list-user-sessions.js.map +1 -0
- package/dist/auth/revalidate-user-permissions-by-role.d.ts +1 -0
- package/dist/auth/revalidate-user-permissions-by-role.js +12 -0
- package/dist/auth/revalidate-user-permissions-by-role.js.map +1 -0
- package/dist/auth/revalidate-user-permissions.d.ts +1 -0
- package/dist/auth/revalidate-user-permissions.js +21 -0
- package/dist/auth/revalidate-user-permissions.js.map +1 -0
- package/dist/auth/revoke-access-token.d.ts +1 -0
- package/dist/auth/revoke-access-token.js +5 -0
- package/dist/auth/revoke-access-token.js.map +1 -0
- package/dist/auth/verify-access-token.d.ts +1 -0
- package/dist/auth/verify-access-token.js +47 -0
- package/dist/auth/verify-access-token.js.map +1 -0
- package/dist/auth/verify-user-mail-token.d.ts +1 -0
- package/dist/auth/verify-user-mail-token.js +21 -0
- package/dist/auth/verify-user-mail-token.js.map +1 -0
- package/dist/commands/cli.js +18 -27
- package/dist/commands/cli.js.map +1 -1
- package/dist/commands/make/basic-controller.js +1 -1
- package/dist/commands/make/basic-controller.js.map +1 -1
- package/dist/commands/make/basic-migration.d.ts +1 -1
- package/dist/commands/make/basic-migration.js +2 -2
- package/dist/commands/make/basic-migration.js.map +1 -1
- package/dist/commands/make/basic-model.js +1 -1
- package/dist/commands/make/basic-model.js.map +1 -1
- package/dist/commands/make/basic-seeder.js +1 -1
- package/dist/commands/make/basic-seeder.js.map +1 -1
- package/dist/commands/make/blueprint.js +1 -1
- package/dist/commands/make/blueprint.js.map +1 -1
- package/dist/commands/make/da-migration.js +3 -3
- package/dist/commands/make/da-migration.js.map +1 -1
- package/dist/commands/make/mail.js +2 -2
- package/dist/commands/make/mail.js.map +1 -1
- package/dist/commands/make/notification.js +1 -1
- package/dist/commands/make/notification.js.map +1 -1
- package/dist/commands/make/queue.js +1 -1
- package/dist/commands/make/queue.js.map +1 -1
- package/dist/commands/make/resource.d.ts +2 -0
- package/dist/commands/make/resource.js +19 -0
- package/dist/commands/make/resource.js.map +1 -0
- package/dist/commands/make/skalfa-controller.d.ts +3 -0
- package/dist/commands/make/{light-controller.js → skalfa-controller.js} +9 -9
- package/dist/commands/make/skalfa-controller.js.map +1 -0
- package/dist/commands/make/skalfa-model.d.ts +3 -0
- package/dist/commands/make/{light-model.js → skalfa-model.js} +11 -11
- package/dist/commands/make/skalfa-model.js.map +1 -0
- package/dist/commands/runner/barrels.js.map +1 -1
- package/dist/commands/runner/blueprint/controller-generation.js +2 -2
- package/dist/commands/runner/blueprint/controller-generation.js.map +1 -1
- package/dist/commands/runner/blueprint/documentation-generation.js.map +1 -1
- package/dist/commands/runner/blueprint/migration-generation.js +3 -3
- package/dist/commands/runner/blueprint/migration-generation.js.map +1 -1
- package/dist/commands/runner/blueprint/model-generation.js +2 -2
- package/dist/commands/runner/blueprint/model-generation.js.map +1 -1
- package/dist/commands/runner/blueprint/runner.js +7 -8
- package/dist/commands/runner/blueprint/runner.js.map +1 -1
- package/dist/commands/runner/blueprint/seeder-generation.js +3 -3
- package/dist/commands/runner/blueprint/seeder-generation.js.map +1 -1
- package/dist/commands/runner/da-migration.js +1 -2
- package/dist/commands/runner/da-migration.js.map +1 -1
- package/dist/commands/runner/generate-docs.d.ts +2 -0
- package/dist/commands/runner/generate-docs.js +400 -0
- package/dist/commands/runner/generate-docs.js.map +1 -0
- package/dist/commands/runner/migration.js +1 -1
- package/dist/commands/runner/migration.js.map +1 -1
- package/dist/commands/runner/seeder.js +1 -1
- package/dist/commands/runner/seeder.js.map +1 -1
- package/dist/commands/stubs/index.d.ts +4 -4
- package/dist/commands/stubs/index.js +4 -4
- package/dist/commands/stubs/index.js.map +1 -1
- package/dist/context/context.js +6 -0
- package/dist/context/context.js.map +1 -1
- package/dist/controller/controller.d.ts +17 -30
- package/dist/controller/controller.js +39 -121
- package/dist/controller/controller.js.map +1 -1
- package/dist/controller/response.d.ts +6 -0
- package/dist/controller/response.js +63 -0
- package/dist/controller/response.js.map +1 -0
- package/dist/controller/storage.d.ts +9 -0
- package/dist/controller/storage.js +56 -0
- package/dist/controller/storage.js.map +1 -0
- package/dist/conversion/conversion.d.ts +3 -0
- package/dist/conversion/conversion.js +28 -4
- package/dist/conversion/conversion.js.map +1 -1
- package/dist/conversion/date.d.ts +1 -0
- package/dist/conversion/date.js +77 -0
- package/dist/conversion/date.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/logger/logger.js +33 -0
- package/dist/logger/logger.js.map +1 -1
- package/dist/mail/mail.js +6 -6
- package/dist/mail/mail.js.map +1 -1
- package/dist/middleware/access-log.d.ts +31 -0
- package/dist/middleware/access-log.js +13 -0
- package/dist/middleware/access-log.js.map +1 -0
- package/dist/middleware/auth.d.ts +37 -0
- package/dist/middleware/auth.js +16 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/body-parse.d.ts +35 -0
- package/dist/middleware/body-parse.js +87 -0
- package/dist/middleware/body-parse.js.map +1 -0
- package/dist/middleware/context.d.ts +29 -0
- package/dist/middleware/context.js +8 -0
- package/dist/middleware/context.js.map +1 -0
- package/dist/middleware/cors.d.ts +31 -0
- package/dist/middleware/cors.js +27 -0
- package/dist/middleware/cors.js.map +1 -0
- package/dist/middleware/error-handler.d.ts +33 -0
- package/dist/middleware/error-handler.js +17 -0
- package/dist/middleware/error-handler.js.map +1 -0
- package/dist/middleware/middleware.d.ts +31 -10
- package/dist/middleware/middleware.js +41 -209
- package/dist/middleware/middleware.js.map +1 -1
- package/dist/middleware/private.d.ts +29 -0
- package/dist/middleware/private.js +8 -0
- package/dist/middleware/private.js.map +1 -0
- package/dist/middleware/rate-limiter.d.ts +32 -0
- package/dist/middleware/rate-limiter.js +30 -0
- package/dist/middleware/rate-limiter.js.map +1 -0
- package/dist/notification/index.d.ts +1 -0
- package/dist/notification/index.js +2 -0
- package/dist/notification/index.js.map +1 -0
- package/dist/notification/notification.d.ts +16 -0
- package/dist/notification/notification.js +64 -0
- package/dist/notification/notification.js.map +1 -0
- package/dist/permission/permission.js +9 -0
- package/dist/permission/permission.js.map +1 -1
- package/dist/registry/registry.d.ts +0 -6
- package/dist/registry/registry.js +6 -6
- package/dist/registry/registry.js.map +1 -1
- package/dist/storage/storage.d.ts +3 -3
- package/dist/storage/storage.js.map +1 -1
- package/dist/validation/validation.js +43 -51
- package/dist/validation/validation.js.map +1 -1
- package/package.json +4 -4
- package/src/auth/auth.ts +21 -252
- package/src/auth/create-access-token.ts +29 -0
- package/src/auth/create-user-mail-token.ts +24 -0
- package/src/auth/helpers/generate-agent-id.ts +8 -0
- package/src/auth/helpers/get-request-ip.ts +3 -0
- package/src/auth/helpers/get-user-permissions.ts +15 -0
- package/src/auth/helpers/index.ts +3 -0
- package/src/auth/list-user-sessions.ts +11 -0
- package/src/auth/revalidate-user-permissions-by-role.ts +13 -0
- package/src/auth/revalidate-user-permissions.ts +26 -0
- package/src/auth/revoke-access-token.ts +5 -0
- package/src/auth/verify-access-token.ts +56 -0
- package/src/auth/verify-user-mail-token.ts +24 -0
- package/src/commands/cli.ts +19 -29
- package/src/commands/make/basic-controller.ts +4 -2
- package/src/commands/make/basic-migration.ts +5 -3
- package/src/commands/make/basic-model.ts +3 -1
- package/src/commands/make/basic-seeder.ts +3 -1
- package/src/commands/make/blueprint.ts +3 -1
- package/src/commands/make/da-migration.ts +6 -5
- package/src/commands/make/mail.ts +4 -2
- package/src/commands/make/notification.ts +3 -1
- package/src/commands/make/queue.ts +3 -1
- package/src/commands/make/resource.ts +21 -0
- package/src/commands/make/{light-controller.ts → skalfa-controller.ts} +10 -8
- package/src/commands/make/{light-model.ts → skalfa-model.ts} +12 -10
- package/src/commands/runner/barrels.ts +4 -0
- package/src/commands/runner/blueprint/controller-generation.ts +4 -2
- package/src/commands/runner/blueprint/documentation-generation.ts +2 -0
- package/src/commands/runner/blueprint/migration-generation.ts +5 -3
- package/src/commands/runner/blueprint/model-generation.ts +4 -2
- package/src/commands/runner/blueprint/runner.ts +15 -8
- package/src/commands/runner/blueprint/seeder-generation.ts +5 -3
- package/src/commands/runner/da-migration.ts +3 -2
- package/src/commands/runner/generate-docs.ts +495 -0
- package/src/commands/runner/migration.ts +1 -1
- package/src/commands/runner/seeder.ts +1 -1
- package/src/commands/stubs/index.ts +4 -4
- package/src/context/context.ts +23 -17
- package/src/controller/controller.ts +124 -239
- package/src/controller/response.ts +78 -0
- package/src/controller/storage.ts +78 -0
- package/src/conversion/conversion.ts +90 -64
- package/src/conversion/date.ts +74 -0
- package/src/index.ts +2 -0
- package/src/logger/logger.ts +217 -176
- package/src/mail/mail.ts +85 -85
- package/src/middleware/access-log.ts +15 -0
- package/src/middleware/auth.ts +19 -0
- package/src/middleware/body-parse.ts +83 -0
- package/src/middleware/context.ts +11 -0
- package/src/middleware/cors.ts +31 -0
- package/src/middleware/error-handler.ts +20 -0
- package/src/middleware/middleware.ts +91 -288
- package/src/middleware/private.ts +8 -0
- package/src/middleware/rate-limiter.ts +41 -0
- package/src/notification/index.ts +1 -0
- package/src/notification/notification.ts +86 -0
- package/src/permission/permission.ts +140 -136
- package/src/registry/registry.ts +17 -15
- package/src/route/route.ts +11 -11
- package/src/storage/storage.ts +104 -106
- package/src/validation/validation.ts +322 -346
- package/dist/auth.util.d.ts +0 -19
- package/dist/auth.util.js +0 -183
- package/dist/auth.util.js.map +0 -1
- package/dist/commands/make/light-controller.d.ts +0 -3
- package/dist/commands/make/light-controller.js.map +0 -1
- package/dist/commands/make/light-model.d.ts +0 -3
- package/dist/commands/make/light-model.js.map +0 -1
- package/dist/context.util.d.ts +0 -7
- package/dist/context.util.js +0 -11
- package/dist/context.util.js.map +0 -1
- package/dist/controller.util.d.ts +0 -118
- package/dist/controller.util.js +0 -144
- package/dist/controller.util.js.map +0 -1
- package/dist/conversion.util.d.ts +0 -8
- package/dist/conversion.util.js +0 -52
- package/dist/conversion.util.js.map +0 -1
- package/dist/db/db.d.ts +0 -84
- package/dist/db/db.js +0 -177
- package/dist/db/db.js.map +0 -1
- package/dist/db/index.d.ts +0 -1
- package/dist/db/index.js +0 -2
- package/dist/db/index.js.map +0 -1
- package/dist/db.util.d.ts +0 -84
- package/dist/db.util.js +0 -177
- package/dist/db.util.js.map +0 -1
- package/dist/logger.util.d.ts +0 -30
- package/dist/logger.util.js +0 -126
- package/dist/logger.util.js.map +0 -1
- package/dist/mail.util.d.ts +0 -21
- package/dist/mail.util.js +0 -53
- package/dist/mail.util.js.map +0 -1
- package/dist/middleware.util.d.ts +0 -263
- package/dist/middleware.util.js +0 -233
- package/dist/middleware.util.js.map +0 -1
- package/dist/model/index.d.ts +0 -3
- package/dist/model/index.js +0 -4
- package/dist/model/index.js.map +0 -1
- package/dist/model/model.d.ts +0 -204
- package/dist/model/model.js +0 -1495
- package/dist/model/model.js.map +0 -1
- package/dist/model.util.d.ts +0 -204
- package/dist/model.util.js +0 -1495
- package/dist/model.util.js.map +0 -1
- package/dist/permission.util.d.ts +0 -38
- package/dist/permission.util.js +0 -91
- package/dist/permission.util.js.map +0 -1
- package/dist/registry.util.d.ts +0 -28
- package/dist/registry.util.js +0 -19
- package/dist/registry.util.js.map +0 -1
- package/dist/route.util.d.ts +0 -1
- package/dist/route.util.js +0 -12
- package/dist/route.util.js.map +0 -1
- package/dist/storage.util.d.ts +0 -56
- package/dist/storage.util.js +0 -82
- package/dist/storage.util.js.map +0 -1
- package/dist/validation.util.d.ts +0 -7
- package/dist/validation.util.js +0 -237
- package/dist/validation.util.js.map +0 -1
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { db } from '@skalfa/skalfa-orm'
|
|
2
|
+
|
|
3
|
+
export async function getUserPermissions(userId: number): Promise<string[]> {
|
|
4
|
+
const roleIds = await db("user_roles").where("user_id", userId).pluck("role_id")
|
|
5
|
+
|
|
6
|
+
if (roleIds.length === 0) return []
|
|
7
|
+
|
|
8
|
+
const rows = await db("permissions").whereIn("role_id", roleIds).pluck("permissions")
|
|
9
|
+
|
|
10
|
+
return Array.from(
|
|
11
|
+
new Set(
|
|
12
|
+
rows.flatMap((p: any) => p ?? [])
|
|
13
|
+
)
|
|
14
|
+
)
|
|
15
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { db } from '@skalfa/skalfa-orm'
|
|
2
|
+
|
|
3
|
+
export async function listUserSessions(userId: number, currentTokenId?: number) {
|
|
4
|
+
const rows = await db("user_access_tokens").select(["id", "agent", "created_at", "last_used_at", "last_used_ip","expired_at"]).where("user_id", userId).orderBy("last_used_at", "desc")
|
|
5
|
+
|
|
6
|
+
return rows.map((r: any) => ({
|
|
7
|
+
...r,
|
|
8
|
+
is_active : r.revoked_at === null,
|
|
9
|
+
is_current : r.id === currentTokenId,
|
|
10
|
+
}))
|
|
11
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { db } from '@skalfa/skalfa-orm'
|
|
2
|
+
import { registry } from '@utils/registry'
|
|
3
|
+
|
|
4
|
+
export async function revalidateUserPermissionsByRole(roleId: number) {
|
|
5
|
+
const userIds = await db("user_roles").where("role_id", roleId).pluck("user_id")
|
|
6
|
+
|
|
7
|
+
const queue = registry.get('queue')
|
|
8
|
+
if (queue) {
|
|
9
|
+
for (const userId of userIds) {
|
|
10
|
+
await queue.add("auth:revalidate-permission", { userId })
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { db } from '@skalfa/skalfa-orm'
|
|
2
|
+
import { registry } from '@utils/registry'
|
|
3
|
+
import { AUTH_CACHE } from './auth'
|
|
4
|
+
import { getUserPermissions } from './helpers'
|
|
5
|
+
|
|
6
|
+
export async function revalidateUserPermissions(userId: number) {
|
|
7
|
+
const permissions = await getUserPermissions(userId)
|
|
8
|
+
|
|
9
|
+
const tokenIds = await db("user_access_tokens").where("user_id", userId).pluck("id")
|
|
10
|
+
|
|
11
|
+
if (tokenIds.length === 0) return
|
|
12
|
+
|
|
13
|
+
await db("user_access_tokens").whereIn("id", tokenIds).update({
|
|
14
|
+
permissions : JSON.stringify(permissions),
|
|
15
|
+
updated_at : new Date(),
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
if (AUTH_CACHE) {
|
|
19
|
+
const redis = registry.get('redis')
|
|
20
|
+
if (redis) {
|
|
21
|
+
await Promise.all(
|
|
22
|
+
tokenIds.map((id: any) => redis.del(`auth:token:${id}`))
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import bcrypt from "bcrypt";
|
|
2
|
+
import { db } from '@skalfa/skalfa-orm'
|
|
3
|
+
import { registry } from '@utils/registry'
|
|
4
|
+
import { AUTH_CACHE, AUTH_CACHE_TTL } from './auth'
|
|
5
|
+
import { generateAgentId, getRequestIp } from './helpers'
|
|
6
|
+
|
|
7
|
+
export async function verifyAccessToken(token: string, req?: Request) {
|
|
8
|
+
if (!token.includes("|")) return null
|
|
9
|
+
|
|
10
|
+
const [tokenId, plain] = token.split("|", 2)
|
|
11
|
+
const agent = req ? generateAgentId(req) : ""
|
|
12
|
+
const ip = req ? getRequestIp(req) : ""
|
|
13
|
+
|
|
14
|
+
const cacheKey = `auth:token:${tokenId}`
|
|
15
|
+
|
|
16
|
+
if (AUTH_CACHE) {
|
|
17
|
+
const redis = registry.get('redis')
|
|
18
|
+
if (redis) {
|
|
19
|
+
const cached = await redis.get(cacheKey)
|
|
20
|
+
if (cached) {
|
|
21
|
+
const session = JSON.parse(cached)
|
|
22
|
+
if (session.agent !== agent) return null
|
|
23
|
+
return session
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const tokenRecord = await db("user_access_tokens").where("id", tokenId).first()
|
|
29
|
+
|
|
30
|
+
if (!tokenRecord) return null
|
|
31
|
+
if (tokenRecord.agent !== agent) return null
|
|
32
|
+
|
|
33
|
+
const valid = await bcrypt.compare(plain, tokenRecord.token)
|
|
34
|
+
if (!valid) return null
|
|
35
|
+
|
|
36
|
+
await db("user_access_tokens").where("id", tokenRecord.id).update({ last_used_at: new Date(), last_used_ip: ip })
|
|
37
|
+
|
|
38
|
+
const user = await db("users").where("id", tokenRecord.user_id).first()
|
|
39
|
+
|
|
40
|
+
if (AUTH_CACHE) {
|
|
41
|
+
const redis = registry.get('redis')
|
|
42
|
+
if (redis) {
|
|
43
|
+
await redis.setex(
|
|
44
|
+
cacheKey,
|
|
45
|
+
AUTH_CACHE_TTL,
|
|
46
|
+
JSON.stringify({
|
|
47
|
+
user : user,
|
|
48
|
+
agent : tokenRecord.agent,
|
|
49
|
+
permissions : tokenRecord.permission,
|
|
50
|
+
})
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return { user, token: tokenRecord, permissions: tokenRecord.permission }
|
|
56
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import crypto from 'crypto'
|
|
2
|
+
import { db } from '@skalfa/skalfa-orm'
|
|
3
|
+
|
|
4
|
+
export async function verifyUserMailToken(userId: number, token: string) {
|
|
5
|
+
const hashedToken = crypto.createHash("sha256").update(token).digest("hex");
|
|
6
|
+
|
|
7
|
+
const record = await db.table("user_mail_tokens")
|
|
8
|
+
.where("user_id", userId)
|
|
9
|
+
.whereNull("used_at")
|
|
10
|
+
.orderBy("id", "desc")
|
|
11
|
+
.first();
|
|
12
|
+
|
|
13
|
+
if (!record) return false
|
|
14
|
+
|
|
15
|
+
if (record.token !== hashedToken) return false;
|
|
16
|
+
|
|
17
|
+
const createdAt = new Date(record.created_at);
|
|
18
|
+
const now = new Date();
|
|
19
|
+
const diffMinutes = (now.getTime() - createdAt.getTime()) / (1000 * 60);
|
|
20
|
+
|
|
21
|
+
if (diffMinutes > 10) return false;
|
|
22
|
+
|
|
23
|
+
return true;
|
|
24
|
+
}
|
package/src/commands/cli.ts
CHANGED
|
@@ -2,22 +2,19 @@ import fs from "fs";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
|
|
5
|
-
// Basic commands (always available)
|
|
6
5
|
import { makeControllerCommand } from "./make/basic-controller";
|
|
7
|
-
import {
|
|
6
|
+
import { makeSkalfaControllerCommand } from "./make/skalfa-controller";
|
|
8
7
|
import { barrelsCommand, watchBarrelsCommand } from "./runner/barrels";
|
|
9
|
-
|
|
10
|
-
// ORM commands (optional)
|
|
11
8
|
import { makeModelCommand } from "./make/basic-model";
|
|
12
9
|
import { makeSeederCommand } from "./make/basic-seeder";
|
|
13
10
|
import { makeMigrationCommand } from "./make/basic-migration";
|
|
14
|
-
import {
|
|
11
|
+
import { makeSkalfaModelCommand } from "./make/skalfa-model";
|
|
15
12
|
import { makeBlueprintCommand } from "./make/blueprint";
|
|
13
|
+
import { makeResourceCommand } from "./make/resource";
|
|
16
14
|
import { migrateCommand, migrateFreshCommand } from "./runner/migration";
|
|
17
15
|
import { seederCommand } from "./runner/seeder";
|
|
18
16
|
import { blueprintCommand } from "./runner/blueprint/runner";
|
|
19
|
-
|
|
20
|
-
// Extension-specific commands
|
|
17
|
+
import { generateDocsCommand } from "./runner/generate-docs";
|
|
21
18
|
import { makeQueueCommand } from "./make/queue";
|
|
22
19
|
import { makeMailCommand } from "./make/mail";
|
|
23
20
|
import { makeNotificationCommand } from "./make/notification";
|
|
@@ -25,7 +22,6 @@ import { makeDaMigrationCommand } from "./make/da-migration";
|
|
|
25
22
|
import { daMigrateCommand, daMigrateFreshCommand } from "./runner/da-migration";
|
|
26
23
|
|
|
27
24
|
export function runCli() {
|
|
28
|
-
// Read package.json to dynamically detect installed extensions
|
|
29
25
|
let dependencies: Record<string, string> = {};
|
|
30
26
|
try {
|
|
31
27
|
const pkgPath = path.join(process.cwd(), "package.json");
|
|
@@ -35,50 +31,44 @@ export function runCli() {
|
|
|
35
31
|
}
|
|
36
32
|
} catch {}
|
|
37
33
|
|
|
38
|
-
const hasOrm = !!dependencies["@skalfa/skalfa-orm"];
|
|
39
34
|
const hasMail = !!dependencies["@skalfa/mail"] || !!dependencies["skalfa-mail"];
|
|
40
35
|
const hasNotification = !!dependencies["@skalfa/notification"] || !!dependencies["skalfa-notification"];
|
|
41
36
|
const hasQueue = !!dependencies["@skalfa/queue"] || !!dependencies["@skalfa/redis"] || !!dependencies["skalfa-queue"] || !!dependencies["skalfa-redis"];
|
|
42
37
|
const hasDa = !!dependencies["@skalfa/da"] || !!dependencies["skalfa-da"] || !!dependencies["@clickhouse/client"];
|
|
43
38
|
|
|
44
39
|
const program = new Command();
|
|
45
|
-
program.name("skalfa").description("Skalfa
|
|
40
|
+
program.name("skalfa").description("Skalfa-api CLI").version("1.0.0");
|
|
46
41
|
|
|
47
|
-
// 1. Add Core / Basic commands
|
|
48
|
-
program.addCommand(makeControllerCommand);
|
|
49
|
-
program.addCommand(makeLightControllerCommand);
|
|
50
42
|
program.addCommand(barrelsCommand);
|
|
43
|
+
|
|
44
|
+
program.addCommand(makeControllerCommand);
|
|
45
|
+
program.addCommand(makeSkalfaControllerCommand);
|
|
51
46
|
program.addCommand(watchBarrelsCommand);
|
|
47
|
+
program.addCommand(makeModelCommand);
|
|
48
|
+
program.addCommand(makeMigrationCommand);
|
|
49
|
+
program.addCommand(makeSeederCommand);
|
|
50
|
+
program.addCommand(makeSkalfaModelCommand);
|
|
51
|
+
program.addCommand(makeBlueprintCommand);
|
|
52
|
+
program.addCommand(makeResourceCommand);
|
|
52
53
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
program.addCommand(makeLightModelCommand);
|
|
59
|
-
program.addCommand(makeBlueprintCommand);
|
|
60
|
-
program.addCommand(migrateCommand);
|
|
61
|
-
program.addCommand(migrateFreshCommand);
|
|
62
|
-
program.addCommand(seederCommand);
|
|
63
|
-
program.addCommand(blueprintCommand);
|
|
64
|
-
}
|
|
54
|
+
program.addCommand(migrateCommand);
|
|
55
|
+
program.addCommand(migrateFreshCommand);
|
|
56
|
+
program.addCommand(seederCommand);
|
|
57
|
+
program.addCommand(blueprintCommand);
|
|
58
|
+
program.addCommand(generateDocsCommand);
|
|
65
59
|
|
|
66
|
-
// 3. Add Mail commands if installed
|
|
67
60
|
if (hasMail) {
|
|
68
61
|
program.addCommand(makeMailCommand);
|
|
69
62
|
}
|
|
70
63
|
|
|
71
|
-
// 4. Add Notification commands if installed
|
|
72
64
|
if (hasNotification) {
|
|
73
65
|
program.addCommand(makeNotificationCommand);
|
|
74
66
|
}
|
|
75
67
|
|
|
76
|
-
// 5. Add Queue commands if installed
|
|
77
68
|
if (hasQueue) {
|
|
78
69
|
program.addCommand(makeQueueCommand);
|
|
79
70
|
}
|
|
80
71
|
|
|
81
|
-
// 6. Add Data Analytics / OLAP commands if installed
|
|
82
72
|
if (hasDa) {
|
|
83
73
|
program.addCommand(makeDaMigrationCommand);
|
|
84
74
|
program.addCommand(daMigrateCommand);
|
|
@@ -4,6 +4,8 @@ import { Command } from "commander";
|
|
|
4
4
|
import { conversion, logger } from "@utils";
|
|
5
5
|
import { basicControllerStub } from "../stubs";
|
|
6
6
|
|
|
7
|
+
|
|
8
|
+
|
|
7
9
|
// =====================================>
|
|
8
10
|
// ## Command: make:controller
|
|
9
11
|
// =====================================>
|
|
@@ -11,7 +13,7 @@ export const makeControllerCommand = new Command("make:controller")
|
|
|
11
13
|
.argument("<name>", "Name of controller")
|
|
12
14
|
.description("Create new controller")
|
|
13
15
|
.action((controllerName) => {
|
|
14
|
-
const basePath = path.join(process.cwd(), "
|
|
16
|
+
const basePath = path.join(process.cwd(), "app", "controllers");
|
|
15
17
|
|
|
16
18
|
if (!controllerName || controllerName.trim() === "") {
|
|
17
19
|
logger.error("Controller name invalid!");
|
|
@@ -41,7 +43,7 @@ export const makeControllerCommand = new Command("make:controller")
|
|
|
41
43
|
|
|
42
44
|
let content = basicControllerStub;
|
|
43
45
|
content = content.replace(/{{\s*name\s*}}/g, name || "");
|
|
44
|
-
|
|
46
|
+
|
|
45
47
|
writeFileSync(filePath, content);
|
|
46
48
|
|
|
47
49
|
logger.info(`Controller ${controllerName} created!`);
|
|
@@ -4,6 +4,8 @@ import { Command } from "commander";
|
|
|
4
4
|
import { conversion, logger } from "@utils";
|
|
5
5
|
import { basicMigrationStub } from "../stubs";
|
|
6
6
|
|
|
7
|
+
|
|
8
|
+
|
|
7
9
|
// =====================================>
|
|
8
10
|
// ## Command: make:migration
|
|
9
11
|
// =====================================>
|
|
@@ -13,8 +15,11 @@ export const makeMigrationCommand = new Command("make:migration")
|
|
|
13
15
|
.description("Membuat file migration baru")
|
|
14
16
|
.action((name, options) => {
|
|
15
17
|
makeMigration(name, options);
|
|
18
|
+
process.exit(0);
|
|
16
19
|
});
|
|
17
20
|
|
|
21
|
+
|
|
22
|
+
|
|
18
23
|
// =====================================>
|
|
19
24
|
// ## Command: migration helpers
|
|
20
25
|
// =====================================>
|
|
@@ -28,7 +33,6 @@ export const makeMigration = (
|
|
|
28
33
|
|
|
29
34
|
const baseDir = path.join(
|
|
30
35
|
process.cwd(),
|
|
31
|
-
"src",
|
|
32
36
|
"database",
|
|
33
37
|
"migrations"
|
|
34
38
|
);
|
|
@@ -66,8 +70,6 @@ export const makeMigration = (
|
|
|
66
70
|
writeFileSync(filePath, content);
|
|
67
71
|
|
|
68
72
|
logger.info(`Migration created: ${path.relative(baseDir, filePath)}`);
|
|
69
|
-
|
|
70
|
-
process.exit(0);
|
|
71
73
|
};
|
|
72
74
|
|
|
73
75
|
const migrationPrefixFile = (date: Date) => {
|
|
@@ -4,6 +4,8 @@ import { Command } from "commander";
|
|
|
4
4
|
import { conversion, logger } from "@utils";
|
|
5
5
|
import { basicModelStub } from "../stubs";
|
|
6
6
|
|
|
7
|
+
|
|
8
|
+
|
|
7
9
|
// =====================================>
|
|
8
10
|
// ## Command: make:model
|
|
9
11
|
// =====================================>
|
|
@@ -14,7 +16,7 @@ export const makeModelCommand = new Command("make:model")
|
|
|
14
16
|
const name = conversion.strPascal(modelName);
|
|
15
17
|
const filename = conversion.strSlug(modelName) + ".model.ts";
|
|
16
18
|
|
|
17
|
-
const filePath = path.join(process.cwd(), "
|
|
19
|
+
const filePath = path.join(process.cwd(), "app", "models", filename);
|
|
18
20
|
|
|
19
21
|
if (!existsSync(path.dirname(filePath))) {
|
|
20
22
|
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
@@ -4,6 +4,8 @@ import { Command } from "commander";
|
|
|
4
4
|
import { conversion, logger } from "@utils";
|
|
5
5
|
import { basicSeederStub } from "../stubs";
|
|
6
6
|
|
|
7
|
+
|
|
8
|
+
|
|
7
9
|
// =====================================>
|
|
8
10
|
// ## Command: make:seeder
|
|
9
11
|
// =====================================>
|
|
@@ -21,7 +23,7 @@ export const makeSeeder = (seederName: string, model?: string) => {
|
|
|
21
23
|
const filename = conversion.strSlug(seederName) + ".seeder.ts";
|
|
22
24
|
const modelName = model || conversion.strPascal(seederName);
|
|
23
25
|
|
|
24
|
-
const dir = path.join(process.cwd(), "
|
|
26
|
+
const dir = path.join(process.cwd(), "database", "seeders");
|
|
25
27
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
26
28
|
|
|
27
29
|
const filePath = path.join(dir, filename);
|
|
@@ -4,6 +4,8 @@ import { Command } from "commander";
|
|
|
4
4
|
import { conversion, logger } from "@utils";
|
|
5
5
|
import { blueprintStub } from "../stubs";
|
|
6
6
|
|
|
7
|
+
|
|
8
|
+
|
|
7
9
|
// =====================================>
|
|
8
10
|
// ## Command: make:blueprint
|
|
9
11
|
// =====================================>
|
|
@@ -11,7 +13,7 @@ export const makeBlueprintCommand = new Command("make:blueprint")
|
|
|
11
13
|
.argument("<name>", "Name of blueprint")
|
|
12
14
|
.description("Create new blueprint")
|
|
13
15
|
.action((name) => {
|
|
14
|
-
const basePath = path.join(process.cwd(), "
|
|
16
|
+
const basePath = path.join(process.cwd(), "blueprints");
|
|
15
17
|
|
|
16
18
|
if (!name || name.trim() === "") {
|
|
17
19
|
logger.error("Blueprint name invalid!");
|
|
@@ -4,10 +4,12 @@ import { Command } from "commander";
|
|
|
4
4
|
import { logger } from "@utils";
|
|
5
5
|
import { daMigrationStub } from "../stubs";
|
|
6
6
|
|
|
7
|
+
|
|
8
|
+
|
|
7
9
|
// =====================================>
|
|
8
10
|
// ## Command: make:da:migration
|
|
9
11
|
// =====================================>
|
|
10
|
-
export const makeDaMigrationCommand = new Command("make:da
|
|
12
|
+
export const makeDaMigrationCommand = new Command("make:da-migration")
|
|
11
13
|
.argument("<name>", "Nama migration")
|
|
12
14
|
.option("--init", "Buat migration init (0000_00)")
|
|
13
15
|
.description("Membuat file migration baru")
|
|
@@ -24,9 +26,8 @@ export const makeDaMigration = (
|
|
|
24
26
|
|
|
25
27
|
const baseDir = path.join(
|
|
26
28
|
process.cwd(),
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"da.migrations"
|
|
29
|
+
"analytic",
|
|
30
|
+
"migrations"
|
|
30
31
|
);
|
|
31
32
|
|
|
32
33
|
let targetDir: string;
|
|
@@ -62,7 +63,7 @@ export const makeDaMigration = (
|
|
|
62
63
|
writeFileSync(filePath, content);
|
|
63
64
|
|
|
64
65
|
logger.info(
|
|
65
|
-
`
|
|
66
|
+
`Analytics Migration created: ${path.relative(baseDir, filePath)}`
|
|
66
67
|
);
|
|
67
68
|
|
|
68
69
|
process.exit(0);
|
|
@@ -4,6 +4,8 @@ import { Command } from "commander";
|
|
|
4
4
|
import { conversion, logger } from "@utils";
|
|
5
5
|
import { mailStub } from "../stubs";
|
|
6
6
|
|
|
7
|
+
|
|
8
|
+
|
|
7
9
|
// =====================================>
|
|
8
10
|
// ## Command: make:mail
|
|
9
11
|
// =====================================>
|
|
@@ -11,8 +13,8 @@ export const makeMailCommand = new Command("make:mail")
|
|
|
11
13
|
.argument("<name>", "Name of mail")
|
|
12
14
|
.description("Create new mail")
|
|
13
15
|
.action((name) => {
|
|
14
|
-
const basePath = path.join(process.cwd(), "
|
|
15
|
-
const templatePath = path.join(process.cwd(), "
|
|
16
|
+
const basePath = path.join(process.cwd(), "app", "outputs", "mails");
|
|
17
|
+
const templatePath = path.join(process.cwd(), "app", "outputs", "mails", "templates");
|
|
16
18
|
|
|
17
19
|
if (!name || name.trim() === "") {
|
|
18
20
|
logger.error("Mail name invalid!");
|
|
@@ -4,6 +4,8 @@ import { Command } from "commander";
|
|
|
4
4
|
import { conversion, logger } from "@utils";
|
|
5
5
|
import { notificationStub } from "../stubs";
|
|
6
6
|
|
|
7
|
+
|
|
8
|
+
|
|
7
9
|
// =====================================>
|
|
8
10
|
// ## Command: make:notification
|
|
9
11
|
// =====================================>
|
|
@@ -11,7 +13,7 @@ export const makeNotificationCommand = new Command("make:notification")
|
|
|
11
13
|
.argument("<name>", "Name of notification")
|
|
12
14
|
.description("Create new notification")
|
|
13
15
|
.action((name) => {
|
|
14
|
-
const basePath = path.join(process.cwd(), "
|
|
16
|
+
const basePath = path.join(process.cwd(), "app", "outputs", "notifications");
|
|
15
17
|
|
|
16
18
|
if (!name || name.trim() === "") {
|
|
17
19
|
logger.error("Notification name invalid!");
|
|
@@ -4,6 +4,8 @@ import { Command } from "commander";
|
|
|
4
4
|
import { conversion, logger } from "@utils";
|
|
5
5
|
import { queueStub } from "../stubs";
|
|
6
6
|
|
|
7
|
+
|
|
8
|
+
|
|
7
9
|
// =====================================>
|
|
8
10
|
// ## Command: make:queue
|
|
9
11
|
// =====================================>
|
|
@@ -11,7 +13,7 @@ export const makeQueueCommand = new Command("make:queue")
|
|
|
11
13
|
.argument("<name>", "Name of queue")
|
|
12
14
|
.description("Create new queue")
|
|
13
15
|
.action((name) => {
|
|
14
|
-
const basePath = path.join(process.cwd(), "
|
|
16
|
+
const basePath = path.join(process.cwd(), "app", "jobs", "queues");
|
|
15
17
|
|
|
16
18
|
if (!name || name.trim() === "") {
|
|
17
19
|
logger.error("Queue name invalid!");
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { makeSkalfaModel } from "./skalfa-model";
|
|
3
|
+
import { makeSkalfaController } from "./skalfa-controller";
|
|
4
|
+
import { makeMigration } from "./basic-migration";
|
|
5
|
+
import { makeSeeder } from "./basic-seeder";
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
// =====================================>
|
|
10
|
+
// ## Command: make:resource
|
|
11
|
+
// =====================================>
|
|
12
|
+
export const makeResourceCommand = new Command("make:resource")
|
|
13
|
+
.argument("<name>", "Name of resource")
|
|
14
|
+
.description("Create a new model, migration, seeder, and controller for a resource")
|
|
15
|
+
.action((name) => {
|
|
16
|
+
makeSkalfaModel(name);
|
|
17
|
+
makeSkalfaController(name);
|
|
18
|
+
makeMigration("create_" + name, { init: true });
|
|
19
|
+
makeSeeder(name);
|
|
20
|
+
process.exit(0);
|
|
21
|
+
});
|
|
@@ -2,22 +2,24 @@ import path from "path";
|
|
|
2
2
|
import { writeFileSync, mkdirSync, existsSync } from "fs";
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
import { conversion, logger } from "@utils";
|
|
5
|
-
import {
|
|
5
|
+
import { skalfaControllerStub } from "../stubs";
|
|
6
|
+
|
|
7
|
+
|
|
6
8
|
|
|
7
9
|
// =====================================>
|
|
8
|
-
// ## Command: make:
|
|
10
|
+
// ## Command: make:skafa-controller
|
|
9
11
|
// =====================================>
|
|
10
|
-
export const
|
|
12
|
+
export const makeSkalfaControllerCommand = new Command("make:skalfa-controller")
|
|
11
13
|
.argument("<name>", "Name of controller")
|
|
12
14
|
.option("-m, --model <model>", "Attach model to controller")
|
|
13
|
-
.description("Make the
|
|
15
|
+
.description("Make the Skalfa Controller")
|
|
14
16
|
.action((name, options) => {
|
|
15
|
-
|
|
17
|
+
makeSkalfaController(name, options.model);
|
|
16
18
|
process.exit(0);
|
|
17
19
|
});
|
|
18
20
|
|
|
19
|
-
export const
|
|
20
|
-
const basePath = path.join(process.cwd(), "
|
|
21
|
+
export const makeSkalfaController = (controllerName: string, modelName?: string) => {
|
|
22
|
+
const basePath = path.join(process.cwd(), "app", "controllers");
|
|
21
23
|
|
|
22
24
|
if (!controllerName || controllerName.trim() === "") {
|
|
23
25
|
logger.error("Controller name invalid!");
|
|
@@ -46,7 +48,7 @@ export const makeLightController = (controllerName: string, modelName?: string)
|
|
|
46
48
|
logger.info(`Create folder ${targetDir}...`);
|
|
47
49
|
}
|
|
48
50
|
|
|
49
|
-
let stub =
|
|
51
|
+
let stub = skalfaControllerStub;
|
|
50
52
|
|
|
51
53
|
stub = stub.replace(
|
|
52
54
|
/{{\s*name\s*}}|{{\s*model\s*}}|{{\s*validations\s*}}|{{\s*marker\s*}}/g,
|
|
@@ -2,23 +2,25 @@ import path from "path";
|
|
|
2
2
|
import { writeFileSync, mkdirSync, existsSync } from "fs";
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
import { conversion, logger } from "@utils";
|
|
5
|
-
import {
|
|
5
|
+
import { makeSkalfaController } from "./skalfa-controller";
|
|
6
6
|
import { makeMigration } from "./basic-migration";
|
|
7
7
|
import { makeSeeder } from "./basic-seeder";
|
|
8
|
-
import {
|
|
8
|
+
import { skalfaModelStub } from "../stubs";
|
|
9
|
+
|
|
10
|
+
|
|
9
11
|
|
|
10
12
|
// =====================================>
|
|
11
|
-
// ## Command: make:
|
|
13
|
+
// ## Command: make:skalfa-model
|
|
12
14
|
// =====================================>
|
|
13
|
-
export const
|
|
15
|
+
export const makeSkalfaModelCommand = new Command("make:skalfa-model")
|
|
14
16
|
.argument("<name>", "Name of model")
|
|
15
17
|
.option("-r", "Generate all resource (controller, migration, seeder)")
|
|
16
|
-
.description("Make the
|
|
18
|
+
.description("Make the Skalfa Model")
|
|
17
19
|
.action((name, options) => {
|
|
18
|
-
|
|
20
|
+
makeSkalfaModel(name);
|
|
19
21
|
|
|
20
22
|
if (options.r) {
|
|
21
|
-
|
|
23
|
+
makeSkalfaController(name);
|
|
22
24
|
makeMigration("create_" + name, { init: true });
|
|
23
25
|
makeSeeder(name);
|
|
24
26
|
}
|
|
@@ -26,11 +28,11 @@ export const makeLightModelCommand = new Command("make:light-model")
|
|
|
26
28
|
process.exit(0);
|
|
27
29
|
});
|
|
28
30
|
|
|
29
|
-
export const
|
|
31
|
+
export const makeSkalfaModel = (modelName: string) => {
|
|
30
32
|
const name = conversion.strPascal(modelName);
|
|
31
33
|
const filename = conversion.strSlug(modelName) + ".model.ts";
|
|
32
34
|
|
|
33
|
-
const basePath = path.join(process.cwd(), "
|
|
35
|
+
const basePath = path.join(process.cwd(), "app", "models");
|
|
34
36
|
|
|
35
37
|
if (!existsSync(basePath)) {
|
|
36
38
|
mkdirSync(basePath, { recursive: true });
|
|
@@ -43,7 +45,7 @@ export const makeLightModel = (modelName: string) => {
|
|
|
43
45
|
return;
|
|
44
46
|
}
|
|
45
47
|
|
|
46
|
-
let stub =
|
|
48
|
+
let stub = skalfaModelStub;
|
|
47
49
|
|
|
48
50
|
stub = stub
|
|
49
51
|
.replace(/{{\s*name\s*}}/g, name)
|
|
@@ -4,6 +4,8 @@ import { exec, execSync } from "child_process";
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import { logger } from "@utils";
|
|
6
6
|
|
|
7
|
+
|
|
8
|
+
|
|
7
9
|
// =====================================>
|
|
8
10
|
// ## Command: barrels (run once)
|
|
9
11
|
// =====================================>
|
|
@@ -29,6 +31,8 @@ export const barrelsCommand = new Command("barrels")
|
|
|
29
31
|
}
|
|
30
32
|
});
|
|
31
33
|
|
|
34
|
+
|
|
35
|
+
|
|
32
36
|
// =====================================>
|
|
33
37
|
// ## Command: watch:barrels (file watcher)
|
|
34
38
|
// =====================================>
|
|
@@ -2,7 +2,9 @@ import fs from "fs";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { conversion } from "@utils";
|
|
4
4
|
import { resolveBlueprintPath } from "./runner";
|
|
5
|
-
import {
|
|
5
|
+
import { skalfaControllerStub, routeStub } from "../../stubs";
|
|
6
|
+
|
|
7
|
+
|
|
6
8
|
|
|
7
9
|
// ================================>
|
|
8
10
|
// ## Command: Blueprint controller generation
|
|
@@ -31,7 +33,7 @@ export async function controllerGeneration(
|
|
|
31
33
|
...generateRelationValidations(relations)
|
|
32
34
|
};
|
|
33
35
|
|
|
34
|
-
let stub =
|
|
36
|
+
let stub = skalfaControllerStub;
|
|
35
37
|
|
|
36
38
|
stub = stub
|
|
37
39
|
.replace(/{{\s*marker\s*}}/g, marker)
|
|
@@ -5,6 +5,8 @@ import { conversion } from "@utils";
|
|
|
5
5
|
const ERD_PATH = path.join(process.cwd(), "storage", "documentation", "entity");
|
|
6
6
|
const API_PATH = path.join(process.cwd(), "storage", "documentation", "api");
|
|
7
7
|
|
|
8
|
+
|
|
9
|
+
|
|
8
10
|
// =========================================>
|
|
9
11
|
// ## Command: Blueprint documentation generation
|
|
10
12
|
// =========================================>
|