@skalfa/skalfa-app 1.0.0

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.
Files changed (112) hide show
  1. package/.env.example +44 -0
  2. package/README.md +28 -0
  3. package/app/auth/edit/page.tsx +65 -0
  4. package/app/auth/login/page.tsx +63 -0
  5. package/app/auth/me/page.tsx +58 -0
  6. package/app/auth/register/page.tsx +69 -0
  7. package/app/auth/verify/page.tsx +53 -0
  8. package/app/dashboard/layout.tsx +47 -0
  9. package/app/dashboard/page.tsx +9 -0
  10. package/app/dashboard/user/page.tsx +77 -0
  11. package/app/index.ts +14 -0
  12. package/app/layout.tsx +38 -0
  13. package/app/page.tsx +13 -0
  14. package/barrels.json +6 -0
  15. package/blueprints/starter.blueprint.json +103 -0
  16. package/components/base.components/accordion/Accordion.component.tsx +82 -0
  17. package/components/base.components/breadcrumb/Breadcrumb.component.tsx +80 -0
  18. package/components/base.components/button/Button.component.tsx +91 -0
  19. package/components/base.components/button/IconButton.component.tsx +88 -0
  20. package/components/base.components/button/button.decorate.ts +82 -0
  21. package/components/base.components/card/AlertCard.component.tsx +69 -0
  22. package/components/base.components/card/Card.component.tsx +25 -0
  23. package/components/base.components/card/DashboardCard.component.tsx +44 -0
  24. package/components/base.components/card/GalleryCard.component.tsx +50 -0
  25. package/components/base.components/card/ProductCard.component.tsx +65 -0
  26. package/components/base.components/card/ProfileCard.component.tsx +71 -0
  27. package/components/base.components/carousel/Carousel.component.tsx +113 -0
  28. package/components/base.components/chip/Chip.component.tsx +39 -0
  29. package/components/base.components/document/DocumentViewer.component.tsx +164 -0
  30. package/components/base.components/document/ExportExcel.component.tsx +340 -0
  31. package/components/base.components/document/ImportExcel.component.tsx +315 -0
  32. package/components/base.components/document/PrintTable.component.tsx +204 -0
  33. package/components/base.components/document/RenderPDF.component.tsx +416 -0
  34. package/components/base.components/index.ts +85 -0
  35. package/components/base.components/input/Checkbox.component.tsx +109 -0
  36. package/components/base.components/input/Input.component.tsx +332 -0
  37. package/components/base.components/input/InputCheckbox.component.tsx +174 -0
  38. package/components/base.components/input/InputCurrency.component.tsx +163 -0
  39. package/components/base.components/input/InputDate.component.tsx +352 -0
  40. package/components/base.components/input/InputDatetime.component.tsx +260 -0
  41. package/components/base.components/input/InputDocument.component.tsx +352 -0
  42. package/components/base.components/input/InputImage.component.tsx +533 -0
  43. package/components/base.components/input/InputMap.component.tsx +318 -0
  44. package/components/base.components/input/InputNumber.component.tsx +192 -0
  45. package/components/base.components/input/InputOtp.component.tsx +169 -0
  46. package/components/base.components/input/InputPassword.component.tsx +236 -0
  47. package/components/base.components/input/InputRadio.component.tsx +175 -0
  48. package/components/base.components/input/InputTime.component.tsx +276 -0
  49. package/components/base.components/input/InputValues.component.tsx +68 -0
  50. package/components/base.components/input/Radio.component.tsx +102 -0
  51. package/components/base.components/input/Select.component.tsx +541 -0
  52. package/components/base.components/modal/BottomSheet.component.tsx +246 -0
  53. package/components/base.components/modal/FloatingPage.component.tsx +104 -0
  54. package/components/base.components/modal/Modal.component.tsx +96 -0
  55. package/components/base.components/modal/ModalConfirm.component.tsx +218 -0
  56. package/components/base.components/modal/Toast.component.tsx +126 -0
  57. package/components/base.components/nav/Bottombar.component.tsx +116 -0
  58. package/components/base.components/nav/Footer.component.tsx +144 -0
  59. package/components/base.components/nav/Headbar.component.tsx +104 -0
  60. package/components/base.components/nav/Navbar.component.tsx +100 -0
  61. package/components/base.components/nav/Sidebar.component.tsx +301 -0
  62. package/components/base.components/nav/Tabbar.component.tsx +60 -0
  63. package/components/base.components/nav/Wizard.component.tsx +73 -0
  64. package/components/base.components/supervision/FormSupervision.component.tsx +434 -0
  65. package/components/base.components/supervision/TableSupervision.component.tsx +697 -0
  66. package/components/base.components/table/ControlBar.component.tsx +497 -0
  67. package/components/base.components/table/FilterComponent.tsx +518 -0
  68. package/components/base.components/table/Pagination.component.tsx +159 -0
  69. package/components/base.components/table/Table.component.tsx +469 -0
  70. package/components/base.components/typography/TypographyArticle.component.tsx +26 -0
  71. package/components/base.components/typography/TypographyColumn.component.tsx +20 -0
  72. package/components/base.components/typography/TypographyContent.component.tsx +20 -0
  73. package/components/base.components/typography/TypographyTips.component.tsx +20 -0
  74. package/components/base.components/wrap/Draggable.component.tsx +303 -0
  75. package/components/base.components/wrap/IDBProvider.tsx +12 -0
  76. package/components/base.components/wrap/Image.component.tsx +10 -0
  77. package/components/base.components/wrap/OutsideClick.component.tsx +48 -0
  78. package/components/base.components/wrap/ScrollContainer.component.tsx +104 -0
  79. package/components/base.components/wrap/ShortcutProvider.tsx +57 -0
  80. package/components/base.components/wrap/Swipe.component.tsx +93 -0
  81. package/components/construct.components/example.tsx +1 -0
  82. package/components/construct.components/index.ts +5 -0
  83. package/components/index.ts +3 -0
  84. package/components/structure.components/example.tsx +1 -0
  85. package/components/structure.components/index.ts +5 -0
  86. package/contexts/AppProvider.tsx +12 -0
  87. package/contexts/Auth.context.tsx +64 -0
  88. package/contexts/Toggle.context.tsx +44 -0
  89. package/contexts/index.ts +7 -0
  90. package/eslint.config.mjs +34 -0
  91. package/langs/index.ts +1 -0
  92. package/langs/validation.langs.ts +17 -0
  93. package/next.config.ts +17 -0
  94. package/package.json +43 -0
  95. package/postcss.config.mjs +12 -0
  96. package/public/204.svg +19 -0
  97. package/public/500.svg +39 -0
  98. package/public/images/avatar.jpg +0 -0
  99. package/public/images/example.png +0 -0
  100. package/schema/idb/app.schema.ts +9 -0
  101. package/schema/index.ts +5 -0
  102. package/styles/globals.css +231 -0
  103. package/styles/tailwind.safelist +69 -0
  104. package/tailwind.config.ts +10 -0
  105. package/tsconfig.json +35 -0
  106. package/utils/commands/barrels.ts +28 -0
  107. package/utils/commands/blueprint.ts +421 -0
  108. package/utils/commands/light.ts +21 -0
  109. package/utils/commands/logger.ts +42 -0
  110. package/utils/commands/stubs/table-blueprint.stub +13 -0
  111. package/utils/commands/use-pdf.ts +29 -0
  112. package/utils/index.ts +3 -0
package/tsconfig.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "include" : ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", ".next/dev/types/**/*.ts"],
3
+ "exclude" : ["node_modules", ".next"],
4
+ "compilerOptions" : {
5
+ "target" : "ES2017",
6
+ "lib" : ["dom", "dom.iterable", "esnext"],
7
+ "allowJs" : true,
8
+ "skipLibCheck" : true,
9
+ "strict" : true,
10
+ "noEmit" : true,
11
+ "esModuleInterop" : true,
12
+ "module" : "esnext",
13
+ "moduleResolution" : "bundler",
14
+ "resolveJsonModule" : true,
15
+ "isolatedModules" : true,
16
+ "jsx" : "react-jsx",
17
+ "incremental" : true,
18
+ "paths" : {
19
+ "@/*" : ["./*"],
20
+ "@utils" : ["./utils/index.ts"],
21
+ "@utils/*" : ["./utils/*"],
22
+ "@components" : ["./components/index.ts"],
23
+ "@components/*" : ["./components/*", "./components/base.components/*", "./components/construct.components/*", "./components/structure.components/*"],
24
+ "@contexts" : ["./contexts/index.ts"],
25
+ "@contexts/*" : ["./contexts/*"],
26
+ "@app" : ["./app/index.ts"],
27
+ "@app/*" : ["./app/*"],
28
+ "@schema" : ["./schema/index.ts"],
29
+ "@schema/*" : ["./schema/*"],
30
+ "@styles/*" : ["./styles/*"],
31
+ "@public/*" : ["./public/*"]
32
+ },
33
+ "plugins" : [{ "name": "next" }]
34
+ }
35
+ }
@@ -0,0 +1,28 @@
1
+ import path from "path";
2
+ import fs from "fs";
3
+ import { exec } from "child_process";
4
+ import { logger } from "./logger";
5
+
6
+ const rootDir = path.resolve();
7
+ const configText = fs.readFileSync("barrels.json", "utf8");
8
+ const config = JSON.parse(configText);
9
+ const directories: string[] = Array.isArray(config.directory) ? config.directory : [config.directory];
10
+
11
+
12
+ directories.forEach((dir) => {
13
+ const absoluteDir = path.join(rootDir, dir);
14
+
15
+ if (!fs.existsSync(absoluteDir)) {
16
+ logger.error(`Barrels error: ${absoluteDir} directory not found`)
17
+ return;
18
+ }
19
+
20
+ fs.watch(absoluteDir, { recursive: true }, (_, filename) => {
21
+ if (filename && (filename.endsWith(".ts") || filename.endsWith(".tsx")) && filename !== "index.ts") {
22
+ exec("npx barrelsby -c barrels.json", { cwd: rootDir })
23
+ logger.info("Barrels updated " + absoluteDir + "/index.ts")
24
+ }
25
+ });
26
+ });
27
+
28
+ logger.start("Barrels watched " + directories.join(", "))
@@ -0,0 +1,421 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { ValidationRules, conversion } from "@skalfa/skalfa-app-core";
4
+ import { logger } from "./logger";
5
+
6
+
7
+
8
+ type SchemaMap = Record<string, string>
9
+
10
+ type ColumnItem = {
11
+ selector ?: string
12
+ label : string
13
+ sortable ?: boolean
14
+ item ?: string
15
+ }
16
+
17
+ type FormItem = {
18
+ type ?: string
19
+ col ?: number
20
+ construction : {
21
+ name : string
22
+ label : string
23
+ placeholder : string
24
+ validations : ValidationRules[]
25
+ serverOptionControl ?: {
26
+ path : string
27
+ }
28
+ fields ?: FormItem[]
29
+ wrap ?: boolean
30
+ }
31
+ }
32
+
33
+ type DetailItem = {
34
+ label : string
35
+ item : string
36
+ }
37
+
38
+ type ParsedSchema = {
39
+ columns : ColumnItem[]
40
+ forms : FormItem[]
41
+ details : DetailItem[]
42
+ }
43
+
44
+ type BlueprintPage = {
45
+ features ?: string
46
+ path ?: string
47
+ schema ?: Record<string, string>
48
+ }
49
+
50
+ type BlueprintStruct = {
51
+ model : string
52
+ controllers ?: string[]
53
+ schema ?: SchemaMap
54
+ pages ?: false | Record<string, BlueprintPage>
55
+ [key: string]: any
56
+ }
57
+
58
+ type LoadedBlueprintFile = {
59
+ file : string
60
+ blueprints : BlueprintStruct[]
61
+ }
62
+
63
+
64
+
65
+ const renderJS = (value: unknown, indent = 0): string => {
66
+ const pad = " ".repeat(indent)
67
+
68
+ if (Array.isArray(value)) {
69
+ if (!value.length) return "[]"
70
+
71
+ if (value.every(v => typeof v === "string")) {
72
+ return `[${value.map(v => JSON.stringify(v)).join(", ")}]`
73
+ }
74
+
75
+ return `[\n${value
76
+ .map(v => pad + " " + renderJS(v, indent + 2))
77
+ .join(",\n")}\n${pad}]`
78
+ }
79
+
80
+ if (value && typeof value === "object") {
81
+ const entries = Object.entries(value as Record<string, unknown>)
82
+ if (!entries.length) return "{}"
83
+
84
+ return `{\n${entries
85
+ .map(([k, v]) => `${pad} ${k}: ${renderJS(v, indent + 2)}`)
86
+ .join(",\n")}\n${pad}}`
87
+ }
88
+
89
+ if (typeof value === "string") return JSON.stringify(value)
90
+
91
+ return String(value)
92
+ }
93
+
94
+ function extractValidationArray(def: string = ""): ValidationRules[] {
95
+ const rules: ValidationRules[] = []
96
+
97
+ if (def.includes("required")) rules.push("required")
98
+
99
+ if (def.includes("email")) rules.push("email")
100
+
101
+ if (def.includes("url")) rules.push("url")
102
+
103
+ const min = def.match(/min,(\d+)/)
104
+ if (min) rules.push(`min:${min[1]}`)
105
+
106
+ const max = def.match(/max,(\d+)/)
107
+ if (max) rules.push(`max:${max[1]}`)
108
+
109
+ return rules
110
+ }
111
+
112
+ function extractFormType(rules: string[]): string | undefined {
113
+ const rule = rules.find(r => r.startsWith("form:"))
114
+ return rule ? rule.replace("form:", "") : undefined
115
+ }
116
+
117
+ function inferFormType(pageDef = "", modelDef = ""): string {
118
+ const explicit = pageDef.split("|")
119
+ if (explicit && explicit[0] && explicit[0] != "text") return explicit[0]
120
+
121
+ if (modelDef.includes("type:integer") || modelDef.includes("type:float")) {
122
+ return "number"
123
+ }
124
+
125
+ if (modelDef.includes("type:date")) return "date"
126
+ if (modelDef.includes("type:datetime")) return "datetime"
127
+ if (modelDef.includes("type:image")) return "image"
128
+
129
+ return "default"
130
+ }
131
+
132
+ function parseFeatures(features?: string): { controlBar: string[]; action: string[] } {
133
+ const controlBar: string[] = []
134
+ const action: string[] = []
135
+
136
+ if (!features) {
137
+ return { controlBar: ["CREATE"], action: ["EDIT", "DELETE"] }
138
+ }
139
+
140
+ const list = features.split(" ")
141
+
142
+ if (list.includes("create")) controlBar.push("CREATE")
143
+
144
+ controlBar.push("SEARCH", "SORT", "SELECTABLE")
145
+
146
+ if (list.includes("import")) controlBar.push("IMPORT")
147
+ if (list.includes("export")) controlBar.push("EXPORT")
148
+ if (list.includes("print")) controlBar.push("PRINT")
149
+
150
+ if (list.includes("update") || list.includes("edit")) action.push("EDIT")
151
+ if (list.includes("delete")) action.push("DELETE")
152
+ if (list.includes("detail")) action.push("DETAIL")
153
+
154
+ return { controlBar, action }
155
+ }
156
+
157
+ function parseModelSchema(schema: SchemaMap = {}): ParsedSchema {
158
+ const columns: ColumnItem[] = []
159
+ const forms: FormItem[] = []
160
+ const details: DetailItem[] = []
161
+
162
+ for (const [field, def] of Object.entries(schema)) {
163
+ const label = conversion.strPascal(field, " ")
164
+
165
+ if (def.includes("selectable")) {
166
+ columns.push({ selector: field, label })
167
+ details.push({ label, item: field })
168
+ }
169
+
170
+ if (def.includes("fillable")) {
171
+ const fieldType = inferFormType("", def);
172
+ forms.push({
173
+ ...(fieldType != "default" ? { type: fieldType } : {}),
174
+ construction: {
175
+ name: field,
176
+ label,
177
+ placeholder: "Masukkan " + label.toLowerCase(),
178
+ validations: extractValidationArray(def)
179
+ }
180
+ })
181
+ }
182
+ }
183
+
184
+ return { columns, forms, details }
185
+ }
186
+
187
+ function resolvePath(page: BlueprintPage, controllers: string[] | boolean | undefined, model: string): string {
188
+ if (page.path) return page.path
189
+
190
+ if (Array.isArray(controllers)) {
191
+ const match = controllers
192
+ if (match) return "/" + match[1]
193
+ }
194
+
195
+ return "/" + model.split("/").pop()
196
+ }
197
+
198
+ function parsePageSchema(pageSchema: Record<string, string>, modelSchema: SchemaMap = {}): ParsedSchema {
199
+ const columns: ColumnItem[] = []
200
+ const forms: FormItem[] = []
201
+ const details: DetailItem[] = []
202
+
203
+ for (const [field, def] of Object.entries(pageSchema)) {
204
+ const rules = def.replace(/\|/g, " ").split(" ").filter(Boolean)
205
+
206
+ const defaultLabel = conversion.strPascal(field, " ")
207
+
208
+ const colLabelRule = rules.find(r => r.includes("column:label,"))
209
+ const colLabel = conversion.strPascal(colLabelRule ? colLabelRule.split(",")[1] : defaultLabel, " ")
210
+
211
+ const formLabelRule = rules.find(r => r.includes("form:label,"))
212
+ const formLabel = conversion.strPascal(formLabelRule ? formLabelRule.split(",")[1] : (colLabelRule ? colLabel : defaultLabel), " ")
213
+
214
+ const detailLabel = colLabelRule ? colLabel : (formLabelRule ? formLabel : defaultLabel)
215
+
216
+ const hasColumn = rules.some(r => r.includes("column:"))
217
+ const hasForm = rules.some(r => r.includes("form:")) || rules.every(r => !r.includes("column:"))
218
+
219
+ const hasDetail = rules.includes("detail")
220
+
221
+ if (hasColumn) {
222
+ columns.push({
223
+ selector: field,
224
+ label: colLabel,
225
+ ...(rules.includes("column:sortable") || rules.includes("sortable") ? { sortable: true } : {})
226
+ })
227
+ if (!hasDetail) details.push({ label: detailLabel, item: field })
228
+ }
229
+
230
+ if (hasForm) {
231
+ const typeRules = rules.filter(r => !r.startsWith("form:label,"))
232
+ const typeRule = extractFormType(typeRules)
233
+ let fieldType = inferFormType(typeRule, modelSchema[field] || "");
234
+
235
+ let selectPath = ""
236
+ const selectRule = rules.find(r => r.startsWith("select,") || r.startsWith("form:select,"))
237
+ if (selectRule) {
238
+ fieldType = "select"
239
+ selectPath = selectRule.split(",")[1]
240
+ }
241
+
242
+ if (typeRule === "check") fieldType = "boolean"
243
+ if (typeRule === "currency") fieldType = "currency"
244
+ if (typeRule === "image") fieldType = "image"
245
+ if (typeRule === "date") fieldType = "date"
246
+ if (typeRule === "time") fieldType = "time"
247
+
248
+ let col: number | undefined
249
+ const colRule = rules.find(r => /col,(\d+)/.test(r))
250
+
251
+ if (colRule) {
252
+ const n = Number(colRule.split(',').pop())
253
+ if (n >= 1 && n <= 12) col = n
254
+ }
255
+
256
+ forms.push({
257
+ ...(fieldType != "default" && fieldType != "text" ? { type: fieldType } : {}),
258
+ ...(col ? { col } : {}),
259
+ construction: {
260
+ name: field,
261
+ label: formLabel,
262
+ placeholder: "Masukkan " + formLabel.toLowerCase(),
263
+ validations: extractValidationArray(modelSchema[field] || ""),
264
+ ...(selectPath ? { serverOptionControl: { path: selectPath } } : {}),
265
+ ...(rules.includes("wrap") ? { wrap: true } : {})
266
+ }
267
+ })
268
+ }
269
+
270
+ if (hasDetail) details.push({ label: detailLabel, item: field })
271
+ }
272
+
273
+ const formMap = new Map<string, FormItem>()
274
+ forms.forEach(f => formMap.set(f.construction.name, f))
275
+
276
+ const nestedForms: FormItem[] = []
277
+
278
+ forms.forEach(f => {
279
+ const name = f.construction.name
280
+ if (name.includes(".")) {
281
+ const parts = name.split(".")
282
+ const selfName = parts.pop() as string
283
+ const parentName = parts.join(".")
284
+ const parent = formMap.get(parentName)
285
+
286
+ if (parent) {
287
+ if (!parent.type) parent.type = "cluster"
288
+ if (!parent.construction.fields) parent.construction.fields = []
289
+
290
+ f.construction.name = selfName
291
+
292
+ parent.construction.fields.push(f)
293
+ } else {
294
+ nestedForms.push(f)
295
+ }
296
+ } else {
297
+ nestedForms.push(f)
298
+ }
299
+ })
300
+
301
+ return { columns, forms: nestedForms, details }
302
+ }
303
+
304
+
305
+
306
+ function loadBlueprintFiles(dir: string = "blueprints"): LoadedBlueprintFile[] {
307
+ const basePath = path.join(process.cwd(), dir)
308
+
309
+ if (!fs.existsSync(basePath)) {
310
+ throw new Error("Blueprint folder not found")
311
+ }
312
+
313
+ return fs.readdirSync(basePath)
314
+ .filter(f => f.endsWith(".blueprint.json"))
315
+ .map(file => {
316
+ const fullPath = path.join(basePath, file)
317
+ const content = JSON.parse(fs.readFileSync(fullPath, "utf-8"))
318
+
319
+ if (!Array.isArray(content)) {
320
+ throw new Error(`${file} must export array of blueprints`)
321
+ }
322
+
323
+ return {
324
+ file: file.replace(".blueprint.json", ""),
325
+ blueprints: content as BlueprintStruct[],
326
+ }
327
+ })
328
+ }
329
+
330
+ const blueprintMarker = `// ============================================
331
+ // ## file THIS FILE IS AUTO-GENERATED BY BLUEPRINT
332
+ // ?? Blueprint : {{ blueprint }}
333
+ // !! If this comment is removed, blueprint engine WILL NOT override this file.
334
+ // ============================================
335
+
336
+
337
+ `
338
+
339
+
340
+ // ===============================
341
+ // ## Main generator
342
+ // ===============================
343
+
344
+ export async function blueprint(options?: { only?: string[] }): Promise<void> {
345
+ const stub = fs.readFileSync(path.join(process.cwd(), "/utils/commands/stubs/table-blueprint.stub"), "utf-8")
346
+
347
+ const loaded = loadBlueprintFiles()
348
+
349
+ for (const file of loaded) {
350
+ for (const bp of file.blueprints) {
351
+
352
+ const pagesToGenerate: Record<string, BlueprintPage> = { ...(bp.pages || {}) }
353
+
354
+ for (const [key, val] of Object.entries(bp)) {
355
+ if (typeof val === "object" && val["schema"]) {
356
+ pagesToGenerate[key] = val as BlueprintPage
357
+ }
358
+ }
359
+
360
+ for (const [key, page] of Object.entries(pagesToGenerate)) {
361
+
362
+ const route = key;
363
+ const name = conversion.strPascal(route.split("/").pop() as string)
364
+
365
+ if (options?.only && !options.only.includes(name)) continue
366
+
367
+ const outDir = path.join(process.cwd(), "app", route)
368
+ fs.mkdirSync(outDir, { recursive: true })
369
+
370
+ const filePath = path.join(outDir, "page.tsx");
371
+
372
+ if (fs.existsSync(filePath)) {
373
+ const content = fs.readFileSync(filePath, "utf-8")
374
+
375
+ if (!content.includes("AUTO-GENERATED BY BLUEPRINT")) {
376
+ logger.info(`Skip overridden file: ${filePath}`)
377
+ continue
378
+ }
379
+ }
380
+
381
+ const schema = bp.schema ?? {}
382
+
383
+ const parsed: ParsedSchema = page?.schema ? parsePageSchema(page.schema, schema) : parseModelSchema(schema)
384
+
385
+ const { controlBar, action } = parseFeatures(page?.features)
386
+
387
+ const fetchPath = resolvePath(page, bp.controllers, bp.model)
388
+
389
+ let properties = `
390
+ fetchControl={{
391
+ path: "${fetchPath}"
392
+ }}
393
+ columnControl={${renderJS(parsed.columns, 8)}}
394
+ formControl={{
395
+ fields: ${renderJS(parsed.forms, 10)}
396
+ }}
397
+ `
398
+ if (parsed.details) {
399
+ properties += ` detailControl={${renderJS(parsed.details, 8)}}\n`
400
+ }
401
+
402
+ if (controlBar.length) {
403
+ properties += ` controlBar={${JSON.stringify(controlBar)}}\n`
404
+ }
405
+
406
+ if (action.length) {
407
+ properties += ` actionControl={${JSON.stringify(action)}}\n`
408
+ }
409
+
410
+ const content = stub
411
+ .replace(/{{ marker }}/g, blueprintMarker.replace(/{{ blueprint }}/g, file.file + ".blueprint.json"))
412
+ .replace(/{{ name }}/g, name)
413
+ .replace(/{{ title }}/g, name)
414
+ .replace(/{{ properties }}/g, properties)
415
+
416
+ fs.writeFileSync(path.join(outDir, "page.tsx"), content)
417
+ logger.info(`Generated: ${filePath}`)
418
+ }
419
+ }
420
+ }
421
+ }
@@ -0,0 +1,21 @@
1
+ import { Command } from "commander";
2
+ import { usePdf } from "./use-pdf";
3
+ import { blueprint } from "./blueprint";
4
+ import { logger } from "./logger";
5
+
6
+ const program = new Command();
7
+
8
+ program.name("light").description("Next Light CLI").version("1.0.0");
9
+
10
+ program.command("use-pdf").description("Copy pdf.worker.min.mjs ke folder public/").action(usePdf );
11
+ program.command("blueprint")
12
+ .option("-o, --only <names...>", "Run only specific blueprints")
13
+ .description("Generate blueprint")
14
+ .action(async (opts) => {
15
+ await blueprint({ only: opts.only })
16
+
17
+ logger.info("Success run all blueprints!")
18
+ process.exit(0);
19
+ });
20
+
21
+ program.parse();
@@ -0,0 +1,42 @@
1
+ /* eslint-disable no-console */
2
+ type LogType = "start" | "info" | "error" | "warning" | "cavity" | "socket" | "cavityError" | "socketError";
3
+
4
+ const colors: Record<LogType | "default", string> = {
5
+ default : "\x1b[0m", // default
6
+ start : "\x1b[32m", // green
7
+ info : "\x1b[36m", // cyan
8
+ error : "\x1b[31m", // red
9
+ warning : "\x1b[33m", // yellow
10
+ cavity : "\x1b[34m", // blue
11
+ cavityError : "\x1b[31m", // red
12
+ socket : "\x1b[35m", // magenta
13
+ socketError : "\x1b[31m", // red
14
+ };
15
+
16
+ const prefixes: Record<LogType, string> = {
17
+ start : "START",
18
+ info : "INFO",
19
+ error : "ERROR",
20
+ warning : "WARNING",
21
+ cavity : "CAVITY",
22
+ socket : "SOCKET",
23
+ cavityError : "CAVITY ERROR",
24
+ socketError : "SOCKET ERROR",
25
+ };
26
+
27
+ function log(type: LogType, ...msg: unknown[]) {
28
+ const color = colors[type];
29
+ const prefix = prefixes[type];
30
+ console.log(`${color}[${prefix}]${colors.default}`, ...msg);
31
+ }
32
+
33
+ export const logger = {
34
+ start : (...msg: unknown[]) => log("start", ...msg),
35
+ info : (...msg: unknown[]) => log("info", ...msg),
36
+ error : (...msg: unknown[]) => log("error", ...msg),
37
+ warning : (...msg: unknown[]) => log("warning", ...msg),
38
+ cavity : (...msg: unknown[]) => log("cavity", ...msg),
39
+ cavityError : (...msg: unknown[]) => log("cavityError", ...msg),
40
+ socket : (...msg: unknown[]) => log("socket", ...msg),
41
+ socketError : (...msg: unknown[]) => log("socketError", ...msg),
42
+ };
@@ -0,0 +1,13 @@
1
+ {{ marker }}
2
+ import { TableSupervisionComponent } from "@components";
3
+
4
+ export default function {{ name }}Page() {
5
+ return (
6
+ <>
7
+ <TableSupervisionComponent
8
+ title="{{ title }}"
9
+ {{ properties }}
10
+ />
11
+ </>
12
+ );
13
+ }
@@ -0,0 +1,29 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { fileURLToPath } from "url";
4
+ import { logger } from "./logger";
5
+
6
+ export function usePdf() {
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+ const projectRoot = path.join(__dirname, "..", "..");
10
+
11
+ const source = path.join(
12
+ projectRoot,
13
+ "node_modules",
14
+ "pdfjs-dist",
15
+ "legacy",
16
+ "build",
17
+ "pdf.worker.min.mjs"
18
+ );
19
+
20
+ const target = path.join(projectRoot, "public", "pdf.worker.min.mjs");
21
+
22
+ if (!fs.existsSync(source)) {
23
+ logger.error(`Gagal: pdf.worker.min.mjs tidak ditemukan.`)
24
+ process.exit(1);
25
+ }
26
+
27
+ fs.copyFileSync(source, target);
28
+ logger.info("Berhasil memindahkan worker ke public/pdf.worker.min.mjs")
29
+ }
package/utils/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ "use client"
2
+
3
+ export * from "@skalfa/skalfa-app-core";