@restforgejs/platform 5.2.12 → 5.2.16

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 (190) hide show
  1. package/bin/drift-check-linux +0 -0
  2. package/bin/sdf-tools-linux +0 -0
  3. package/bin/sdf-tools.exe +0 -0
  4. package/build-info.json +2 -2
  5. package/cli/consumer-deploy.js +1 -1
  6. package/cli/consumer.js +1 -1
  7. package/generators/cli/fast-track.js +8 -9
  8. package/generators/cli/project/auth.js +209 -0
  9. package/generators/lib/auth/component-generator.js +58 -0
  10. package/generators/lib/auth/dependency-checker.js +102 -0
  11. package/generators/lib/auth/env-injector.js +81 -0
  12. package/generators/lib/auth/migrate-runner.js +111 -0
  13. package/generators/lib/auth/prefix.js +22 -0
  14. package/generators/lib/auth/processor-generator.js +55 -0
  15. package/generators/lib/auth/sdf-generator.js +102 -0
  16. package/generators/lib/auth/template-renderer.js +29 -0
  17. package/generators/lib/auth/templates/processor/login.js.tmpl +152 -0
  18. package/generators/lib/auth/templates/processor/logout.js.tmpl +58 -0
  19. package/generators/lib/auth/templates/processor/me.js.tmpl +64 -0
  20. package/generators/lib/auth/templates/processor/refresh.js.tmpl +134 -0
  21. package/generators/lib/auth/templates/processor/register.js.tmpl +77 -0
  22. package/generators/lib/auth/templates/processor/reset-password.js.tmpl +106 -0
  23. package/generators/lib/auth/templates/rfx_auth-middleware.js.tmpl +79 -0
  24. package/generators/lib/auth/templates/rfx_auth.js.tmpl +104 -0
  25. package/generators/lib/dbschema-kit/schema-printer.js +10 -1
  26. package/generators/lib/templates/dashboard-catalog.js +1 -1
  27. package/generators/lib/templates/db-connection-env.js +1 -1
  28. package/generators/lib/templates/dbschema-catalog.js +1 -1
  29. package/generators/lib/templates/field-validation-catalog.js +1 -1
  30. package/generators/lib/templates/mysql-template.js +1 -1
  31. package/generators/lib/templates/oracle-template.js +1 -1
  32. package/generators/lib/templates/postgres-template.js +1 -1
  33. package/generators/lib/templates/query-declarative-catalog.js +1 -1
  34. package/generators/lib/templates/sqlite-template.js +1 -1
  35. package/integrity-manifest.json +18 -18
  36. package/package.json +1 -1
  37. package/scripts/verify-integrity.js +1 -1
  38. package/server.js +1 -1
  39. package/src/components/handlers/adjust_handler.js +1 -1
  40. package/src/components/handlers/audit_handler.js +1 -1
  41. package/src/components/handlers/delete_handler.js +1 -1
  42. package/src/components/handlers/export_handler.js +1 -1
  43. package/src/components/handlers/import_handler.js +1 -1
  44. package/src/components/handlers/insert_handler.js +1 -1
  45. package/src/components/handlers/update_handler.js +1 -1
  46. package/src/components/handlers/upload_handler.js +1 -1
  47. package/src/components/handlers/workflow_handler.js +1 -1
  48. package/src/components/integrations/webhook.js +1 -1
  49. package/src/consumers/baseConsumer.js +1 -1
  50. package/src/consumers/declarativeMapper.js +1 -1
  51. package/src/consumers/handlers/apiHandler.js +1 -1
  52. package/src/consumers/handlers/consoleHandler.js +1 -1
  53. package/src/consumers/handlers/databaseHandler.js +1 -1
  54. package/src/consumers/handlers/index.js +1 -1
  55. package/src/consumers/handlers/kafkaHandler.js +1 -1
  56. package/src/consumers/index.js +1 -1
  57. package/src/consumers/messageTransformer.js +1 -1
  58. package/src/consumers/validator.js +1 -1
  59. package/src/core/db/dialect/base-dialect.js +1 -1
  60. package/src/core/db/dialect/index.js +1 -1
  61. package/src/core/db/dialect/mysql-dialect.js +1 -1
  62. package/src/core/db/dialect/oracle-dialect.js +1 -1
  63. package/src/core/db/dialect/postgres-dialect.js +1 -1
  64. package/src/core/db/dialect/sqlite-dialect.js +1 -1
  65. package/src/core/db/flatten-helper.js +1 -1
  66. package/src/core/db/query-builder-error.js +1 -1
  67. package/src/core/db/query-builder.js +1 -1
  68. package/src/core/db/relation-helper.js +1 -1
  69. package/src/core/handlers/delete_handler.js +1 -1
  70. package/src/core/handlers/insert_handler.js +1 -1
  71. package/src/core/handlers/update_handler.js +1 -1
  72. package/src/core/models/base-model.js +1 -1
  73. package/src/core/utils/cache-manager.js +1 -1
  74. package/src/core/utils/component-engine.js +1 -1
  75. package/src/core/utils/context-builder.js +1 -1
  76. package/src/core/utils/datetime-formatter.js +1 -1
  77. package/src/core/utils/datetime-parser.js +1 -1
  78. package/src/core/utils/db.js +1 -1
  79. package/src/core/utils/logger.js +1 -1
  80. package/src/core/utils/payload-loader.js +1 -1
  81. package/src/core/utils/security-checks.js +1 -1
  82. package/src/middleware/body-options.js +1 -1
  83. package/src/middleware/cors.js +1 -1
  84. package/src/middleware/idempotency.js +1 -1
  85. package/src/middleware/rate-limiter.js +1 -1
  86. package/src/middleware/request-logger.js +1 -1
  87. package/src/middleware/security-headers.js +1 -1
  88. package/src/models/base-model-mysql.js +1 -1
  89. package/src/models/base-model-oracle.js +1 -1
  90. package/src/models/base-model-sqlite.js +1 -1
  91. package/src/models/base-model.js +1 -1
  92. package/src/pro/caching/redis-client.js +1 -1
  93. package/src/pro/caching/redis-helper.js +1 -1
  94. package/src/pro/consumers/baseConsumer.js +1 -1
  95. package/src/pro/consumers/declarativeMapper.js +1 -1
  96. package/src/pro/consumers/handlers/apiHandler.js +1 -1
  97. package/src/pro/consumers/handlers/consoleHandler.js +1 -1
  98. package/src/pro/consumers/handlers/databaseHandler.js +1 -1
  99. package/src/pro/consumers/handlers/index.js +1 -1
  100. package/src/pro/consumers/handlers/kafkaHandler.js +1 -1
  101. package/src/pro/consumers/index.js +1 -1
  102. package/src/pro/consumers/messageTransformer.js +1 -1
  103. package/src/pro/consumers/validator.js +1 -1
  104. package/src/pro/database/base-model-mysql.js +1 -1
  105. package/src/pro/database/base-model-oracle.js +1 -1
  106. package/src/pro/database/base-model-sqlite.js +1 -1
  107. package/src/pro/database/db-mysql.js +1 -1
  108. package/src/pro/database/db-oracle.js +1 -1
  109. package/src/pro/database/db-sqlite.js +1 -1
  110. package/src/pro/excel/excel-generator.js +1 -1
  111. package/src/pro/excel/excel-parser.js +1 -1
  112. package/src/pro/excel/export-service.js +1 -1
  113. package/src/pro/excel/export_handler.js +1 -1
  114. package/src/pro/excel/import-service.js +1 -1
  115. package/src/pro/excel/import-validator.js +1 -1
  116. package/src/pro/excel/import_handler.js +1 -1
  117. package/src/pro/excel/upsert-builder.js +1 -1
  118. package/src/pro/idgen/idgen-routes.js +1 -1
  119. package/src/pro/integrations/lookup-resolver.js +1 -1
  120. package/src/pro/integrations/upload-handler-v2.js +1 -1
  121. package/src/pro/integrations/upload-handler.js +1 -1
  122. package/src/pro/integrations/webhook.js +1 -1
  123. package/src/pro/locking/lock-routes.js +1 -1
  124. package/src/pro/locking/resource-lock-manager.js +1 -1
  125. package/src/pro/messaging/kafkaConsumerService.js +1 -1
  126. package/src/pro/messaging/kafkaService.js +1 -1
  127. package/src/pro/messaging/messagehubService.js +1 -1
  128. package/src/pro/messaging/rabbitmqService.js +1 -1
  129. package/src/pro/scheduler/job-manager.js +1 -1
  130. package/src/pro/scheduler/job-routes.js +1 -1
  131. package/src/pro/scheduler/job-validator.js +1 -1
  132. package/src/pro/storage/base-storage-provider.js +1 -1
  133. package/src/pro/storage/file-metadata-helper.js +1 -1
  134. package/src/pro/storage/index.js +1 -1
  135. package/src/pro/storage/local-storage-provider.js +1 -1
  136. package/src/pro/storage/s3-storage-provider.js +1 -1
  137. package/src/pro/storage/upload-cleanup-job.js +1 -1
  138. package/src/pro/storage/upload-cleanup-scheduler.js +1 -1
  139. package/src/pro/storage/upload-pending-tracker.js +1 -1
  140. package/src/pro/websocket/broadcast-helper.js +1 -1
  141. package/src/pro/websocket/index.js +1 -1
  142. package/src/pro/websocket/livesync-server.js +1 -1
  143. package/src/pro/websocket/ws-broadcaster.js +1 -1
  144. package/src/services/export-service.js +1 -1
  145. package/src/services/import-service.js +1 -1
  146. package/src/services/kafkaConsumerService.js +1 -1
  147. package/src/services/kafkaService.js +1 -1
  148. package/src/services/messagehubService.js +1 -1
  149. package/src/services/rabbitmqService.js +1 -1
  150. package/src/utils/cache-invalidation-registry.js +1 -1
  151. package/src/utils/cache-manager.js +1 -1
  152. package/src/utils/component-engine.js +1 -1
  153. package/src/utils/config-extractor.js +1 -1
  154. package/src/utils/consumerLogger.js +1 -1
  155. package/src/utils/context-builder.js +1 -1
  156. package/src/utils/dashboard-helpers.js +1 -1
  157. package/src/utils/dateHelper.js +1 -1
  158. package/src/utils/datetime-formatter.js +1 -1
  159. package/src/utils/datetime-parser.js +1 -1
  160. package/src/utils/db-bootstrap.js +1 -1
  161. package/src/utils/db-mysql.js +1 -1
  162. package/src/utils/db-oracle.js +1 -1
  163. package/src/utils/db-sqlite.js +1 -1
  164. package/src/utils/db.js +1 -1
  165. package/src/utils/demo-generator.js +1 -1
  166. package/src/utils/excel-generator.js +1 -1
  167. package/src/utils/excel-parser.js +1 -1
  168. package/src/utils/file-watcher.js +1 -1
  169. package/src/utils/id-generator.js +1 -1
  170. package/src/utils/idempotency-manager.js +1 -1
  171. package/src/utils/import-validator.js +1 -1
  172. package/src/utils/license-client.js +1 -1
  173. package/src/utils/lock-manager.js +1 -1
  174. package/src/utils/logger.js +1 -1
  175. package/src/utils/lookup-resolver.js +1 -1
  176. package/src/utils/payload-loader.js +1 -1
  177. package/src/utils/processor-response.js +1 -1
  178. package/src/utils/rabbitmq.js +1 -1
  179. package/src/utils/redis-client.js +1 -1
  180. package/src/utils/redis-helper.js +1 -1
  181. package/src/utils/request-scope.js +1 -1
  182. package/src/utils/security-checks.js +1 -1
  183. package/src/utils/service-resolver.js +1 -1
  184. package/src/utils/shutdown-coordinator.js +1 -1
  185. package/src/utils/soft-delete-dashboard-guard.js +1 -1
  186. package/src/utils/sql-table-extractor.js +1 -1
  187. package/src/utils/trusted-keys.js +1 -1
  188. package/src/utils/upload-handler.js +1 -1
  189. package/src/utils/upsert-builder.js +1 -1
  190. package/src/utils/workflow-hook-executor.js +1 -1
@@ -16,7 +16,7 @@
16
16
  * CMD baru (Windows).
17
17
  *
18
18
  * Scope Frontend (dan bagian frontend pada ALL) juga dieksekusi NYATA: payload
19
- * migrate (RDF -> UDF) per tabel -> restforge-designer activate -> generate.
19
+ * migrate (RDF -> UDF) per tabel -> restforge-designer generate.
20
20
  *
21
21
  * Catatan: contract ini global verb (snapshot key = `fast-track`), file berada
22
22
  * di root `generators/cli/` sehingga ter-discover otomatis sejajar dengan `init`.
@@ -385,7 +385,9 @@ async function collectConfig(args, ask, fileCfg = {}) {
385
385
  console.log(' SQLite mode: DB_HOST, DB_PORT, DB_USER, DB_PASSWORD are ignored.');
386
386
  console.log(' The database file path is set in DB_FILE.');
387
387
  console.log('');
388
- cfg.DB_FILE = await askField('DB_FILE (.db file path)', fileCfg.DB_FILE || DEFAULTS.DB_FILE);
388
+ // fileCfg.DB_NAME: file legacy hasil `restforge init` menyimpan path
389
+ // sqlite di DB_NAME, bukan DB_FILE (lihat init.js & server.js runtime).
390
+ cfg.DB_FILE = await askField('DB_FILE (.db file path)', fileCfg.DB_FILE || fileCfg.DB_NAME || DEFAULTS.DB_FILE);
389
391
  cfg.DB_NAME = cfg.DB_FILE;
390
392
  } else {
391
393
  const dbDef = DB_TYPE_DEFAULTS[cfg.DB_TYPE] || {};
@@ -415,7 +417,7 @@ function defaultCfgFromFile(args, fileCfg = {}) {
415
417
  cfg.DB_TYPE = (fileCfg.DB_TYPE || DEFAULTS.DB_TYPE).toLowerCase();
416
418
 
417
419
  if (cfg.DB_TYPE === 'sqlite') {
418
- cfg.DB_FILE = fileCfg.DB_FILE || DEFAULTS.DB_FILE;
420
+ cfg.DB_FILE = fileCfg.DB_FILE || fileCfg.DB_NAME || DEFAULTS.DB_FILE;
419
421
  cfg.DB_NAME = cfg.DB_FILE;
420
422
  } else {
421
423
  const dbDef = DB_TYPE_DEFAULTS[cfg.DB_TYPE] || {};
@@ -1207,7 +1209,7 @@ function runBackendPipeline(ctx) {
1207
1209
 
1208
1210
  /**
1209
1211
  * Pipeline frontend nyata: migrate RDF->UDF per tabel (agregator di-akumulasi),
1210
- * lalu designer activate + generate. Urutan mengikuti fast-track.mjs/quick-start.
1212
+ * lalu designer generate. Urutan mengikuti fast-track.mjs/quick-start.
1211
1213
  * appCode default = project, sehingga agregator = frontend/payload/<project>.json.
1212
1214
  */
1213
1215
  function runFrontendPipeline(ctx) {
@@ -1218,7 +1220,7 @@ function runFrontendPipeline(ctx) {
1218
1220
  const plugin = ctx.plugin || DESIGNER_DEFAULT_PLUGIN;
1219
1221
  const pluginArg = `--plugin=${plugin}`;
1220
1222
 
1221
- phase('[F1/3] Migrate RDF -> UDF (per tabel, agregator di-akumulasi)');
1223
+ phase('[F1/2] Migrate RDF -> UDF (per tabel, agregator di-akumulasi)');
1222
1224
  fs.mkdirSync(path.join(frontendDir, 'payload'), { recursive: true });
1223
1225
  for (const t of ctx.tableEntries) {
1224
1226
  // Lewati tabel yang murni FK-parent (tanpa FK sendiri): page-nya dibuat
@@ -1241,10 +1243,7 @@ function runFrontendPipeline(ctx) {
1241
1243
  : ` [WARN] could not inject auth block into ${aggregatorPath}; app will generate without auth`);
1242
1244
  }
1243
1245
 
1244
- phase('[F2/3] Activate restforge-designer');
1245
- run(`restforge-designer activate --key=${ctx.cfg.LICENSE}`, frontendDir, { allowNonZero: true });
1246
-
1247
- phase('[F3/3] Generate frontend application');
1246
+ phase('[F2/2] Generate frontend application');
1248
1247
  // Hapus index.html lama agar landing page diregenerasi sesuai set page terbaru.
1249
1248
  // Pakai fs langsung (bukan shell command) supaya portable Windows/Linux/macOS.
1250
1249
  try {
@@ -0,0 +1,209 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Contract: project auth
5
+ *
6
+ * Memasang "auth extension" (SDF, tabel, component, processor) ke project
7
+ * RESTForge yang sudah ada. Setelah validasi prasyarat (phase 00), handler
8
+ * menulis dua file SDF auth ber-prefix `rfx` ke `--schema-path` (Fungsi 1),
9
+ * lalu membuat tabelnya di DB via primitif dbschema-kit langsung (Fungsi 2),
10
+ * lalu menulis component middleware + router auth tanpa google (Fungsi 3a),
11
+ * lalu menulis keenam processor auth tanpa google (Fungsi 3b), lalu
12
+ * menginjeksi variabel env auth ke `--config` dan memverifikasi/mencatat
13
+ * dependency runtime `bcrypt`+`jsonwebtoken` (Fungsi tambahan, phase 05),
14
+ * lalu mencetak ringkasan akhir.
15
+ */
16
+
17
+ const fs = require('node:fs');
18
+ const path = require('node:path');
19
+ const { validateSafeName } = require('../../lib/utils/path-validator');
20
+ const projectRegistry = require('../../lib/utils/project-registry');
21
+ const { PREFIX } = require('../../lib/auth/prefix');
22
+ const { generateAuthSdf } = require('../../lib/auth/sdf-generator');
23
+ const { runAuthMigrate } = require('../../lib/auth/migrate-runner');
24
+ const { generateAuthComponents } = require('../../lib/auth/component-generator');
25
+ const { generateAuthProcessors } = require('../../lib/auth/processor-generator');
26
+ const { injectAuthEnv } = require('../../lib/auth/env-injector');
27
+ const { ensureAuthDependencies } = require('../../lib/auth/dependency-checker');
28
+
29
+ function projectExists(workingDir, projectName) {
30
+ const modulePath = path.join(workingDir, 'src', 'modules', `${projectName}.js`);
31
+ if (fs.existsSync(modulePath)) {
32
+ return true;
33
+ }
34
+
35
+ const registry = projectRegistry.loadProjectRegistry();
36
+ return Boolean(registry.projects && registry.projects[projectName]);
37
+ }
38
+
39
+ module.exports = {
40
+ resource: 'project',
41
+ verb: 'auth',
42
+ description: 'Install auth extension (SDF, tabel, component, processor) ke project existing',
43
+ category: 'generation',
44
+ flags: {
45
+ create: {
46
+ type: 'boolean',
47
+ required: false,
48
+ default: false,
49
+ description: 'Trigger eksekusi instalasi auth extension (wajib disertakan)'
50
+ },
51
+ project: {
52
+ type: 'string',
53
+ required: false,
54
+ default: null,
55
+ description: 'Nama project target (kanonik; alias: --name)'
56
+ },
57
+ name: {
58
+ type: 'string',
59
+ required: false,
60
+ default: null,
61
+ description: 'Alias dari --project'
62
+ },
63
+ 'schema-path': {
64
+ type: 'string',
65
+ required: false,
66
+ default: './schema',
67
+ description: 'Folder output file SDF auth'
68
+ },
69
+ config: {
70
+ type: 'string',
71
+ required: false,
72
+ default: 'config/db-connection.env',
73
+ description: 'File konfigurasi koneksi DB untuk langkah migrate'
74
+ },
75
+ force: {
76
+ type: 'boolean',
77
+ required: false,
78
+ default: false,
79
+ description: 'Timpa file yang sudah ada (backup tetap dibuat)'
80
+ }
81
+ },
82
+ examples: [
83
+ 'npx restforge project auth --create --project=myapp',
84
+ 'npx restforge project auth --create --name=myapp --schema-path=./schema'
85
+ ],
86
+ async handler(args) {
87
+ if (args.create !== true) {
88
+ throw new Error(
89
+ 'Flag --create wajib disertakan untuk memicu instalasi auth extension. ' +
90
+ 'Contoh: npx restforge project auth --create --project=<nama-project>'
91
+ );
92
+ }
93
+
94
+ const rawName = args.project || args.name;
95
+ if (!rawName) {
96
+ throw new Error('Salah satu dari --project atau --name wajib diisi dengan nama project target');
97
+ }
98
+
99
+ const projectName = validateSafeName(rawName, 'project');
100
+ const workingDir = process.cwd();
101
+
102
+ if (!projectExists(workingDir, projectName)) {
103
+ throw new Error(
104
+ `Project "${projectName}" tidak ditemukan (src/modules/${projectName}.js tidak ada). ` +
105
+ 'Buat project lebih dulu (mis. "npx restforge endpoint create") sebelum menjalankan project auth.'
106
+ );
107
+ }
108
+
109
+ const schemaPath = path.resolve(workingDir, args['schema-path'] || './schema');
110
+ const { written, skipped } = generateAuthSdf({ schemaPath, force: args.force === true });
111
+
112
+ console.log('');
113
+ console.log(`Prasyarat OK untuk project "${projectName}" (prefix artefak: ${PREFIX}).`);
114
+ if (written.length > 0) {
115
+ console.log(`SDF auth ditulis: ${written.map((p) => path.basename(p)).join(', ')}`);
116
+ }
117
+ if (skipped.length > 0) {
118
+ console.log(
119
+ `SDF auth sudah ada, dilewati (gunakan --force untuk overwrite): ${skipped.map((p) => path.basename(p)).join(', ')}`
120
+ );
121
+ }
122
+
123
+ const configPath = args.config || 'config/db-connection.env';
124
+ const migrateResult = await runAuthMigrate({ schemaPath, configPath });
125
+ console.log(
126
+ `Tabel auth siap: ${migrateResult.tables.join(', ')} ` +
127
+ `(dialect: ${migrateResult.dialect}, ${migrateResult.statementsApplied} statement diterapkan).`
128
+ );
129
+
130
+ const middlewareDir = path.join(workingDir, 'src', 'components', 'handlers');
131
+ const routerDir = path.join(workingDir, 'src', 'modules', projectName);
132
+ const { written: componentsWritten, skipped: componentsSkipped } = generateAuthComponents({
133
+ middlewareDir,
134
+ routerDir,
135
+ projectName,
136
+ force: args.force === true
137
+ });
138
+
139
+ if (componentsWritten.length > 0) {
140
+ console.log(
141
+ `Component/router auth ditulis: ${componentsWritten.map((p) => path.relative(workingDir, p)).join(', ')}`
142
+ );
143
+ }
144
+ if (componentsSkipped.length > 0) {
145
+ console.log(
146
+ `Component/router auth sudah ada, dilewati (gunakan --force untuk overwrite): ` +
147
+ `${componentsSkipped.map((p) => path.relative(workingDir, p)).join(', ')}`
148
+ );
149
+ }
150
+
151
+ const processorDir = path.join(workingDir, 'src', 'modules', projectName, 'processor', 'auth');
152
+ const { written: processorsWritten, skipped: processorsSkipped } = generateAuthProcessors({
153
+ processorDir,
154
+ force: args.force === true
155
+ });
156
+
157
+ if (processorsWritten.length > 0) {
158
+ console.log(
159
+ `Processor auth ditulis: ${processorsWritten.map((p) => path.relative(workingDir, p)).join(', ')}`
160
+ );
161
+ }
162
+ if (processorsSkipped.length > 0) {
163
+ console.log(
164
+ `Processor auth sudah ada, dilewati (gunakan --force untuk overwrite): ` +
165
+ `${processorsSkipped.map((p) => path.relative(workingDir, p)).join(', ')}`
166
+ );
167
+ }
168
+ const envResult = injectAuthEnv({ configPath });
169
+ const dependencyResult = ensureAuthDependencies({ workingDir });
170
+
171
+ console.log('');
172
+ console.log(`Environment auth (${path.relative(workingDir, envResult.filePath) || envResult.filePath}):`);
173
+ if (envResult.added.length > 0) {
174
+ console.log(` ditambahkan: ${envResult.added.join(', ')}`);
175
+ }
176
+ if (envResult.skipped.length > 0) {
177
+ console.log(` sudah ada, dilewati (nilai existing dipertahankan): ${envResult.skipped.join(', ')}`);
178
+ }
179
+
180
+ console.log('Dependency runtime (bcrypt, jsonwebtoken):');
181
+ for (const [depName, info] of Object.entries(dependencyResult.resolution)) {
182
+ console.log(` ${depName}: ${info.resolvable ? `resolvable (${info.resolvedPath})` : 'TIDAK resolvable dari project target'}`);
183
+ }
184
+ if (dependencyResult.packageJson.hasPackageJson) {
185
+ if (dependencyResult.packageJson.added.length > 0) {
186
+ console.log(` package.json diperbarui, dependencies ditambahkan: ${dependencyResult.packageJson.added.join(', ')}`);
187
+ } else {
188
+ console.log(' package.json project sudah mencantumkan bcrypt & jsonwebtoken (tidak diubah)');
189
+ }
190
+ } else {
191
+ console.log(' package.json tidak ditemukan di project target, tidak dicatat otomatis');
192
+ }
193
+ if (dependencyResult.needsInstallInstruction) {
194
+ console.log(' Jalankan "npm install bcrypt jsonwebtoken" di project target sebelum start server.');
195
+ }
196
+
197
+ console.log('');
198
+ console.log('Auth extension terpasang.');
199
+ console.log('Langkah lanjutan:');
200
+ if (envResult.jwtSecretGenerated) {
201
+ console.log(' - JWT_SECRET baru di-generate acak; rotate berkala sesuai kebijakan keamanan bila perlu.');
202
+ }
203
+ if (dependencyResult.needsInstallInstruction) {
204
+ console.log(' - Jalankan npm install agar bcrypt/jsonwebtoken terpasang sebelum start server.');
205
+ }
206
+ console.log(' - Restart server agar perubahan environment dan route auth termuat.');
207
+ console.log('');
208
+ }
209
+ };
@@ -0,0 +1,58 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Generator component middleware + router auth (Fungsi 3a). Merender aset
5
+ * template di `templates/` via template-renderer, lalu menulis ke lokasi
6
+ * target memakai writeFileWithBackup — perilaku force/skip identik Fungsi 1
7
+ * (sdf-generator.js): tanpa --force file existing di-skip, dengan --force
8
+ * di-overwrite + backup.
9
+ *
10
+ * Processor (Fungsi 3b, Phase 04) TIDAK ditulis di sini.
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+
16
+ const { renderTemplate } = require('./template-renderer');
17
+ const FileUtils = require('../utils/file-utils');
18
+ const { AUTH_MIDDLEWARE_NAME, AUTH_ROUTER_NAME } = require('./prefix');
19
+
20
+ const TEMPLATES_DIR = path.join(__dirname, 'templates');
21
+
22
+ /**
23
+ * @param {{ middlewareDir: string, routerDir: string, projectName: string, force?: boolean }} options
24
+ * @returns {{ written: string[], skipped: string[] }}
25
+ */
26
+ function generateAuthComponents({ middlewareDir, routerDir, projectName, force = false }) {
27
+ const targets = [
28
+ {
29
+ templateFile: 'rfx_auth-middleware.js.tmpl',
30
+ targetPath: path.join(middlewareDir, `${AUTH_MIDDLEWARE_NAME}.js`),
31
+ params: {}
32
+ },
33
+ {
34
+ templateFile: 'rfx_auth.js.tmpl',
35
+ targetPath: path.join(routerDir, `${AUTH_ROUTER_NAME}.js`),
36
+ params: { PROJECT_NAME: projectName }
37
+ }
38
+ ];
39
+
40
+ const written = [];
41
+ const skipped = [];
42
+
43
+ for (const target of targets) {
44
+ if (fs.existsSync(target.targetPath) && !force) {
45
+ skipped.push(target.targetPath);
46
+ continue;
47
+ }
48
+
49
+ const templatePath = path.join(TEMPLATES_DIR, target.templateFile);
50
+ const content = renderTemplate(templatePath, target.params);
51
+ FileUtils.writeFileWithBackup(target.targetPath, content, true);
52
+ written.push(target.targetPath);
53
+ }
54
+
55
+ return { written, skipped };
56
+ }
57
+
58
+ module.exports = { generateAuthComponents };
@@ -0,0 +1,102 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Verifikasi + pencatatan dependency runtime auth extension (Fungsi tambahan,
5
+ * Phase 05, menutup keputusan #6 campaign): middleware butuh `jsonwebtoken`,
6
+ * processor butuh `bcrypt`.
7
+ *
8
+ * Verifikasi resolvability memakai `require.resolve(pkg, { paths: [...] })`
9
+ * persis algoritma resolusi Node — bukan sekadar cek folder `node_modules/`,
10
+ * agar hasilnya benar walau dependency ter-hoist ke level lain.
11
+ *
12
+ * Pencatatan ke `package.json` project bersifat idempoten (tidak menimpa
13
+ * versi yang sudah ada) dan TIDAK pernah menjalankan `npm install`.
14
+ */
15
+
16
+ const fs = require('node:fs');
17
+ const path = require('node:path');
18
+
19
+ const PLATFORM_PACKAGE_JSON = require('../../../package.json');
20
+
21
+ const REQUIRED_DEPENDENCIES = {
22
+ bcrypt: PLATFORM_PACKAGE_JSON.dependencies.bcrypt,
23
+ jsonwebtoken: PLATFORM_PACKAGE_JSON.dependencies.jsonwebtoken
24
+ };
25
+
26
+ /**
27
+ * @param {string} pkgName
28
+ * @param {string} fromDir
29
+ * @returns {string|null} Resolved entry path, atau null bila tidak resolvable.
30
+ */
31
+ function resolvePackage(pkgName, fromDir) {
32
+ try {
33
+ return require.resolve(pkgName, { paths: [fromDir] });
34
+ } catch (err) {
35
+ return null;
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Tambahkan `bcrypt`/`jsonwebtoken` ke `dependencies` package.json project
41
+ * bila belum ada. Tidak pernah menurunkan/menimpa versi yang sudah di-set.
42
+ *
43
+ * @param {string} workingDir
44
+ * @returns {{ hasPackageJson: boolean, pkgPath: string, updated: boolean, added: string[] }}
45
+ */
46
+ function ensurePackageJsonDependencies(workingDir) {
47
+ const pkgPath = path.join(workingDir, 'package.json');
48
+
49
+ if (!fs.existsSync(pkgPath)) {
50
+ return { hasPackageJson: false, pkgPath, updated: false, added: [] };
51
+ }
52
+
53
+ const raw = fs.readFileSync(pkgPath, 'utf8');
54
+ let pkg;
55
+ try {
56
+ pkg = JSON.parse(raw);
57
+ } catch (err) {
58
+ throw new Error(`Gagal parse ${pkgPath}: ${err.message}`);
59
+ }
60
+
61
+ pkg.dependencies = pkg.dependencies || {};
62
+ const added = [];
63
+
64
+ for (const [name, version] of Object.entries(REQUIRED_DEPENDENCIES)) {
65
+ if (!Object.prototype.hasOwnProperty.call(pkg.dependencies, name)) {
66
+ pkg.dependencies[name] = version;
67
+ added.push(name);
68
+ }
69
+ }
70
+
71
+ if (added.length > 0) {
72
+ fs.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`, 'utf8');
73
+ }
74
+
75
+ return { hasPackageJson: true, pkgPath, updated: added.length > 0, added };
76
+ }
77
+
78
+ /**
79
+ * @param {{ workingDir?: string }} options
80
+ * @returns {{
81
+ * resolution: Record<string, { resolvable: boolean, resolvedPath: string|null }>,
82
+ * allResolvable: boolean,
83
+ * packageJson: { hasPackageJson: boolean, pkgPath: string, updated: boolean, added: string[] },
84
+ * needsInstallInstruction: boolean
85
+ * }}
86
+ */
87
+ function ensureAuthDependencies({ workingDir = process.cwd() } = {}) {
88
+ const resolution = {};
89
+ for (const name of Object.keys(REQUIRED_DEPENDENCIES)) {
90
+ const resolvedPath = resolvePackage(name, workingDir);
91
+ resolution[name] = { resolvable: Boolean(resolvedPath), resolvedPath };
92
+ }
93
+ const allResolvable = Object.values(resolution).every((r) => r.resolvable);
94
+
95
+ const packageJson = ensurePackageJsonDependencies(workingDir);
96
+
97
+ const needsInstallInstruction = !allResolvable || !packageJson.hasPackageJson;
98
+
99
+ return { resolution, allResolvable, packageJson, needsInstallInstruction };
100
+ }
101
+
102
+ module.exports = { ensureAuthDependencies, REQUIRED_DEPENDENCIES, resolvePackage };
@@ -0,0 +1,81 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Injeksi variabel env auth (Fungsi tambahan, Phase 05) ke file `--config`,
5
+ * meniru blok Auth/JWT pada prototype `myapp-with-auth/config/db-connection.env`.
6
+ * Idempoten: hanya menambahkan key yang belum ada di file; tidak pernah
7
+ * menimpa nilai existing (termasuk JWT_SECRET yang sudah di-set user).
8
+ *
9
+ * Resolusi path config memakai `resolveConfig()` yang sama dipakai
10
+ * migrate-runner.js, agar blok Auth/JWT ditambahkan ke file yang sama dengan
11
+ * yang dipakai langkah migrate (cascade lookup cwd -> config/ -> +.env).
12
+ */
13
+
14
+ const crypto = require('node:crypto');
15
+ const path = require('node:path');
16
+
17
+ const { readEnvFile, writeEnvFile } = require('../utils/env-manager');
18
+ const { resolveConfig } = require('../utils/config-resolver');
19
+
20
+ const AUTH_ENV_DEFAULTS = {
21
+ JWT_ALGORITHM: 'HS256',
22
+ ACCESS_TOKEN_EXPIRY_MIN: '60',
23
+ REFRESH_TOKEN_EXPIRY_DAYS: '30',
24
+ GOOGLE_CLIENT_ID: ''
25
+ };
26
+
27
+ const AUTH_ENV_KEYS = [
28
+ 'JWT_SECRET',
29
+ 'JWT_ALGORITHM',
30
+ 'ACCESS_TOKEN_EXPIRY_MIN',
31
+ 'REFRESH_TOKEN_EXPIRY_DAYS',
32
+ 'GOOGLE_CLIENT_ID'
33
+ ];
34
+
35
+ const AUTH_ENV_HEADER_COMMENT = '# Auth / JWT Configuration (rfx_auth, generated by "project auth")';
36
+
37
+ function generateJwtSecret() {
38
+ return crypto.randomBytes(48).toString('hex');
39
+ }
40
+
41
+ /**
42
+ * @param {{ configPath: string, workingDir?: string }} options
43
+ * @returns {{
44
+ * filePath: string,
45
+ * added: string[],
46
+ * skipped: string[],
47
+ * jwtSecretGenerated: boolean
48
+ * }}
49
+ */
50
+ function injectAuthEnv({ configPath, workingDir = process.cwd() } = {}) {
51
+ const resolved = resolveConfig(configPath, workingDir);
52
+ const filePath = resolved ? resolved.path : path.resolve(workingDir, configPath || 'config/db-connection.env');
53
+
54
+ const { data, lines } = readEnvFile(filePath);
55
+ const added = [];
56
+ const skipped = [];
57
+
58
+ for (const key of AUTH_ENV_KEYS) {
59
+ if (Object.prototype.hasOwnProperty.call(data, key)) {
60
+ skipped.push(key);
61
+ continue;
62
+ }
63
+
64
+ data[key] = key === 'JWT_SECRET' ? generateJwtSecret() : AUTH_ENV_DEFAULTS[key];
65
+ added.push(key);
66
+ }
67
+
68
+ if (added.length > 0) {
69
+ const nextLines = lines.length > 0 ? [...lines, '', AUTH_ENV_HEADER_COMMENT] : [AUTH_ENV_HEADER_COMMENT];
70
+ writeEnvFile(filePath, data, nextLines);
71
+ }
72
+
73
+ return {
74
+ filePath,
75
+ added,
76
+ skipped,
77
+ jwtSecretGenerated: added.includes('JWT_SECRET')
78
+ };
79
+ }
80
+
81
+ module.exports = { injectAuthEnv, AUTH_ENV_KEYS };
@@ -0,0 +1,111 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Migrate runner auth extension (Fungsi 2). Memuat kedua SDF auth (`rfx_auth_user`
5
+ * & `rfx_auth_refresh_token`) dari `--schema-path`, memvalidasi cross-model (FK
6
+ * refresh_token -> user), men-generate DDL, lalu apply ke DB via primitif
7
+ * `dbschema-kit` langsung (in-process). TIDAK menyentuh `generators/cli/schema/migrate.js`
8
+ * (keputusan #3 campaign project-auth-command-v1) — primitif dipanggil ulang di sini.
9
+ *
10
+ * Test seam: sama seperti migrate.js, executor dimuat lewat loadApplyExecutor()
11
+ * yang menghormati DBSCHEMA_KIT_TEST_APPLY_STUB.
12
+ */
13
+
14
+ const path = require('path');
15
+
16
+ const { loadSchemaPath } = require('../dbschema-kit/loader');
17
+ const { validateCrossModel } = require('../dbschema-kit/validator/cross-model-validator');
18
+ const { generateDDL } = require('../dbschema-kit/ddl-generator');
19
+ const { loadConfig } = require('../dbschema-kit/connection');
20
+ const { splitStatements } = require('../dbschema-kit/statement-splitter');
21
+ const { applyIfNotExistsModifier } = require('../dbschema-kit/statement-modifier');
22
+ const { resolveConfig } = require('../utils/config-resolver');
23
+ const { AUTH_USER_TABLE, AUTH_REFRESH_TOKEN_TABLE } = require('./prefix');
24
+
25
+ function loadApplyExecutor() {
26
+ const stubPath = process.env.DBSCHEMA_KIT_TEST_APPLY_STUB;
27
+ if (stubPath) {
28
+ return require(path.resolve(stubPath));
29
+ }
30
+ return require('../dbschema-kit/apply-executor');
31
+ }
32
+
33
+ /**
34
+ * Muat kedua SDF auth dari `schemaPath` ke satu Map gabungan, agar
35
+ * validateCrossModel bisa memverifikasi FK lintas model.
36
+ *
37
+ * @param {string} schemaPath
38
+ * @returns {Map<string, object>}
39
+ */
40
+ function loadAuthModels(schemaPath) {
41
+ const models = new Map();
42
+ for (const tableName of [AUTH_USER_TABLE, AUTH_REFRESH_TOKEN_TABLE]) {
43
+ const filePath = path.join(schemaPath, `${tableName}.js`);
44
+ const fileModels = loadSchemaPath(filePath);
45
+ for (const [qualified, ir] of fileModels) {
46
+ models.set(qualified, ir);
47
+ }
48
+ }
49
+ return models;
50
+ }
51
+
52
+ /**
53
+ * @param {{ schemaPath: string, configPath: string }} options
54
+ * @returns {Promise<{ tables: string[], dialect: string, statementsApplied: number, result: object }>}
55
+ */
56
+ async function runAuthMigrate({ schemaPath, configPath }) {
57
+ const resolved = resolveConfig(configPath, process.cwd());
58
+ if (!resolved) {
59
+ throw new Error(
60
+ 'Migrate auth gagal: --config=<file> tidak ditemukan. Set default config ' +
61
+ "('npx restforge config set-default --config=<file>') atau sediakan --config eksplisit."
62
+ );
63
+ }
64
+
65
+ let config;
66
+ try {
67
+ config = loadConfig(resolved.path);
68
+ } catch (err) {
69
+ throw new Error(`Migrate auth gagal memuat config DB (${resolved.path}): ${err.message}`);
70
+ }
71
+
72
+ let models;
73
+ try {
74
+ models = loadAuthModels(schemaPath);
75
+ } catch (err) {
76
+ throw new Error(`Migrate auth gagal memuat SDF auth dari '${schemaPath}': ${err.message}`);
77
+ }
78
+
79
+ const crossModelIssues = validateCrossModel(models);
80
+ const crossModelErrors = crossModelIssues.filter((issue) => issue.severity === 'error');
81
+ if (crossModelErrors.length > 0) {
82
+ const messages = crossModelErrors.map((issue) => issue.message).join('; ');
83
+ throw new Error(`Migrate auth gagal: validasi cross-model menemukan error: ${messages}`);
84
+ }
85
+
86
+ const ddl = generateDDL(models, { dialect: config.dialect, drop: false });
87
+ const statements = splitStatements(ddl, config.dialect)
88
+ .map((statement) => applyIfNotExistsModifier(statement, config.dialect));
89
+
90
+ const executor = loadApplyExecutor();
91
+
92
+ let result;
93
+ try {
94
+ result = await executor.applyStatements({ statements, dialect: config.dialect, config });
95
+ } catch (err) {
96
+ throw new Error(`Migrate auth gagal menerapkan tabel ke database: ${err.message}`);
97
+ }
98
+
99
+ if (!result || result.status !== 'SUCCESS') {
100
+ throw new Error(`Migrate auth tidak SUCCESS (status: ${result ? result.status : 'UNKNOWN'}).`);
101
+ }
102
+
103
+ return {
104
+ tables: [AUTH_USER_TABLE, AUTH_REFRESH_TOKEN_TABLE],
105
+ dialect: config.dialect,
106
+ statementsApplied: statements.length,
107
+ result
108
+ };
109
+ }
110
+
111
+ module.exports = { runAuthMigrate, loadAuthModels };
@@ -0,0 +1,22 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Konstanta prefix `rfx` (RestForge eXtension) untuk seluruh artefak auth
5
+ * extension. Satu sumber dipakai ulang oleh Phase 01 (SDF), Phase 02
6
+ * (tabel hasil migrate), dan Phase 03-04 (component/router/processor).
7
+ */
8
+
9
+ const PREFIX = 'rfx';
10
+
11
+ const AUTH_USER_TABLE = `${PREFIX}_auth_user`;
12
+ const AUTH_REFRESH_TOKEN_TABLE = `${PREFIX}_auth_refresh_token`;
13
+ const AUTH_MIDDLEWARE_NAME = `${PREFIX}_auth-middleware`;
14
+ const AUTH_ROUTER_NAME = `${PREFIX}_auth`;
15
+
16
+ module.exports = {
17
+ PREFIX,
18
+ AUTH_USER_TABLE,
19
+ AUTH_REFRESH_TOKEN_TABLE,
20
+ AUTH_MIDDLEWARE_NAME,
21
+ AUTH_ROUTER_NAME
22
+ };