@restforgejs/platform 5.2.13 → 5.3.5

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 (212) 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/endpoint/create.js +69 -6
  8. package/generators/cli/fast-track.js +4 -2
  9. package/generators/cli/payload/sync.js +16 -6
  10. package/generators/cli/project/auth.js +209 -0
  11. package/generators/cli/project/sdk.js +112 -0
  12. package/generators/lib/arg-parser.js +6 -0
  13. package/generators/lib/auth/component-generator.js +58 -0
  14. package/generators/lib/auth/dependency-checker.js +102 -0
  15. package/generators/lib/auth/env-injector.js +81 -0
  16. package/generators/lib/auth/migrate-runner.js +111 -0
  17. package/generators/lib/auth/prefix.js +22 -0
  18. package/generators/lib/auth/processor-generator.js +57 -0
  19. package/generators/lib/auth/sdf-generator.js +102 -0
  20. package/generators/lib/auth/template-renderer.js +29 -0
  21. package/generators/lib/auth/templates/processor/google.js.tmpl +178 -0
  22. package/generators/lib/auth/templates/processor/login.js.tmpl +152 -0
  23. package/generators/lib/auth/templates/processor/logout.js.tmpl +58 -0
  24. package/generators/lib/auth/templates/processor/me.js.tmpl +64 -0
  25. package/generators/lib/auth/templates/processor/refresh.js.tmpl +134 -0
  26. package/generators/lib/auth/templates/processor/register.js.tmpl +77 -0
  27. package/generators/lib/auth/templates/processor/reset-password.js.tmpl +106 -0
  28. package/generators/lib/auth/templates/rfx_auth-middleware.js.tmpl +79 -0
  29. package/generators/lib/auth/templates/rfx_auth.js.tmpl +107 -0
  30. package/generators/lib/dbschema-kit/schema-printer.js +10 -1
  31. package/generators/lib/generators/model-generator.js +46 -59
  32. package/generators/lib/help-generator.js +41 -3
  33. package/generators/lib/payload/endpoint-schema-validator.js +8 -3
  34. package/generators/lib/payload/field-projections.js +116 -0
  35. package/generators/lib/payload/payload-runner.js +164 -48
  36. package/generators/lib/payload/schema-diff.js +108 -0
  37. package/generators/lib/sdk/generator.js +719 -0
  38. package/generators/lib/sdk/naming.js +48 -0
  39. package/generators/lib/sdk/runtime/README.md.tmpl +207 -0
  40. package/generators/lib/sdk/runtime/auth-client.js +186 -0
  41. package/generators/lib/sdk/runtime/deploy.mjs.tmpl +85 -0
  42. package/generators/lib/sdk/runtime/http-client.js +81 -0
  43. package/generators/lib/sdk/runtime/resource-client.js +59 -0
  44. package/generators/lib/sdk/runtime/storage.js +31 -0
  45. package/generators/lib/templates/dashboard-catalog.js +1 -1
  46. package/generators/lib/templates/db-connection-env.js +1 -1
  47. package/generators/lib/templates/dbschema-catalog.js +1 -1
  48. package/generators/lib/templates/field-validation-catalog.js +1 -1
  49. package/generators/lib/templates/mysql-template.js +1 -1
  50. package/generators/lib/templates/oracle-template.js +1 -1
  51. package/generators/lib/templates/postgres-template.js +1 -1
  52. package/generators/lib/templates/query-declarative-catalog.js +1 -1
  53. package/generators/lib/templates/sqlite-template.js +1 -1
  54. package/generators/lib/utils/cli-output.js +40 -0
  55. package/generators/lib/utils/config-resolver.js +61 -0
  56. package/generators/lib/utils/database-introspector.js +28 -5
  57. package/integrity-manifest.json +18 -18
  58. package/package.json +1 -1
  59. package/scripts/verify-integrity.js +1 -1
  60. package/server.js +1 -1
  61. package/src/components/handlers/adjust_handler.js +1 -1
  62. package/src/components/handlers/audit_handler.js +1 -1
  63. package/src/components/handlers/delete_handler.js +1 -1
  64. package/src/components/handlers/export_handler.js +1 -1
  65. package/src/components/handlers/import_handler.js +1 -1
  66. package/src/components/handlers/insert_handler.js +1 -1
  67. package/src/components/handlers/update_handler.js +1 -1
  68. package/src/components/handlers/upload_handler.js +1 -1
  69. package/src/components/handlers/workflow_handler.js +1 -1
  70. package/src/components/integrations/webhook.js +1 -1
  71. package/src/consumers/baseConsumer.js +1 -1
  72. package/src/consumers/declarativeMapper.js +1 -1
  73. package/src/consumers/handlers/apiHandler.js +1 -1
  74. package/src/consumers/handlers/consoleHandler.js +1 -1
  75. package/src/consumers/handlers/databaseHandler.js +1 -1
  76. package/src/consumers/handlers/index.js +1 -1
  77. package/src/consumers/handlers/kafkaHandler.js +1 -1
  78. package/src/consumers/index.js +1 -1
  79. package/src/consumers/messageTransformer.js +1 -1
  80. package/src/consumers/validator.js +1 -1
  81. package/src/core/db/dialect/base-dialect.js +1 -1
  82. package/src/core/db/dialect/index.js +1 -1
  83. package/src/core/db/dialect/mysql-dialect.js +1 -1
  84. package/src/core/db/dialect/oracle-dialect.js +1 -1
  85. package/src/core/db/dialect/postgres-dialect.js +1 -1
  86. package/src/core/db/dialect/sqlite-dialect.js +1 -1
  87. package/src/core/db/flatten-helper.js +1 -1
  88. package/src/core/db/query-builder-error.js +1 -1
  89. package/src/core/db/query-builder.js +1 -1
  90. package/src/core/db/relation-helper.js +1 -1
  91. package/src/core/handlers/delete_handler.js +1 -1
  92. package/src/core/handlers/insert_handler.js +1 -1
  93. package/src/core/handlers/update_handler.js +1 -1
  94. package/src/core/models/base-model.js +1 -1
  95. package/src/core/utils/cache-manager.js +1 -1
  96. package/src/core/utils/component-engine.js +1 -1
  97. package/src/core/utils/context-builder.js +1 -1
  98. package/src/core/utils/datetime-formatter.js +1 -1
  99. package/src/core/utils/datetime-parser.js +1 -1
  100. package/src/core/utils/db.js +1 -1
  101. package/src/core/utils/logger.js +1 -1
  102. package/src/core/utils/payload-loader.js +1 -1
  103. package/src/core/utils/security-checks.js +1 -1
  104. package/src/middleware/body-options.js +1 -1
  105. package/src/middleware/cors.js +1 -1
  106. package/src/middleware/idempotency.js +1 -1
  107. package/src/middleware/rate-limiter.js +1 -1
  108. package/src/middleware/request-logger.js +1 -1
  109. package/src/middleware/security-headers.js +1 -1
  110. package/src/models/base-model-mysql.js +1 -1
  111. package/src/models/base-model-oracle.js +1 -1
  112. package/src/models/base-model-sqlite.js +1 -1
  113. package/src/models/base-model.js +1 -1
  114. package/src/pro/caching/redis-client.js +1 -1
  115. package/src/pro/caching/redis-helper.js +1 -1
  116. package/src/pro/consumers/baseConsumer.js +1 -1
  117. package/src/pro/consumers/declarativeMapper.js +1 -1
  118. package/src/pro/consumers/handlers/apiHandler.js +1 -1
  119. package/src/pro/consumers/handlers/consoleHandler.js +1 -1
  120. package/src/pro/consumers/handlers/databaseHandler.js +1 -1
  121. package/src/pro/consumers/handlers/index.js +1 -1
  122. package/src/pro/consumers/handlers/kafkaHandler.js +1 -1
  123. package/src/pro/consumers/index.js +1 -1
  124. package/src/pro/consumers/messageTransformer.js +1 -1
  125. package/src/pro/consumers/validator.js +1 -1
  126. package/src/pro/database/base-model-mysql.js +1 -1
  127. package/src/pro/database/base-model-oracle.js +1 -1
  128. package/src/pro/database/base-model-sqlite.js +1 -1
  129. package/src/pro/database/db-mysql.js +1 -1
  130. package/src/pro/database/db-oracle.js +1 -1
  131. package/src/pro/database/db-sqlite.js +1 -1
  132. package/src/pro/excel/excel-generator.js +1 -1
  133. package/src/pro/excel/excel-parser.js +1 -1
  134. package/src/pro/excel/export-service.js +1 -1
  135. package/src/pro/excel/export_handler.js +1 -1
  136. package/src/pro/excel/import-service.js +1 -1
  137. package/src/pro/excel/import-validator.js +1 -1
  138. package/src/pro/excel/import_handler.js +1 -1
  139. package/src/pro/excel/upsert-builder.js +1 -1
  140. package/src/pro/idgen/idgen-routes.js +1 -1
  141. package/src/pro/integrations/lookup-resolver.js +1 -1
  142. package/src/pro/integrations/upload-handler-v2.js +1 -1
  143. package/src/pro/integrations/upload-handler.js +1 -1
  144. package/src/pro/integrations/webhook.js +1 -1
  145. package/src/pro/locking/lock-routes.js +1 -1
  146. package/src/pro/locking/resource-lock-manager.js +1 -1
  147. package/src/pro/messaging/kafkaConsumerService.js +1 -1
  148. package/src/pro/messaging/kafkaService.js +1 -1
  149. package/src/pro/messaging/messagehubService.js +1 -1
  150. package/src/pro/messaging/rabbitmqService.js +1 -1
  151. package/src/pro/scheduler/job-manager.js +1 -1
  152. package/src/pro/scheduler/job-routes.js +1 -1
  153. package/src/pro/scheduler/job-validator.js +1 -1
  154. package/src/pro/storage/base-storage-provider.js +1 -1
  155. package/src/pro/storage/file-metadata-helper.js +1 -1
  156. package/src/pro/storage/index.js +1 -1
  157. package/src/pro/storage/local-storage-provider.js +1 -1
  158. package/src/pro/storage/s3-storage-provider.js +1 -1
  159. package/src/pro/storage/upload-cleanup-job.js +1 -1
  160. package/src/pro/storage/upload-cleanup-scheduler.js +1 -1
  161. package/src/pro/storage/upload-pending-tracker.js +1 -1
  162. package/src/pro/websocket/broadcast-helper.js +1 -1
  163. package/src/pro/websocket/index.js +1 -1
  164. package/src/pro/websocket/livesync-server.js +1 -1
  165. package/src/pro/websocket/ws-broadcaster.js +1 -1
  166. package/src/services/export-service.js +1 -1
  167. package/src/services/import-service.js +1 -1
  168. package/src/services/kafkaConsumerService.js +1 -1
  169. package/src/services/kafkaService.js +1 -1
  170. package/src/services/messagehubService.js +1 -1
  171. package/src/services/rabbitmqService.js +1 -1
  172. package/src/utils/cache-invalidation-registry.js +1 -1
  173. package/src/utils/cache-manager.js +1 -1
  174. package/src/utils/component-engine.js +1 -1
  175. package/src/utils/config-extractor.js +1 -1
  176. package/src/utils/consumerLogger.js +1 -1
  177. package/src/utils/context-builder.js +1 -1
  178. package/src/utils/dashboard-helpers.js +1 -1
  179. package/src/utils/dateHelper.js +1 -1
  180. package/src/utils/datetime-formatter.js +1 -1
  181. package/src/utils/datetime-parser.js +1 -1
  182. package/src/utils/db-bootstrap.js +1 -1
  183. package/src/utils/db-mysql.js +1 -1
  184. package/src/utils/db-oracle.js +1 -1
  185. package/src/utils/db-sqlite.js +1 -1
  186. package/src/utils/db.js +1 -1
  187. package/src/utils/demo-generator.js +1 -1
  188. package/src/utils/excel-generator.js +1 -1
  189. package/src/utils/excel-parser.js +1 -1
  190. package/src/utils/file-watcher.js +1 -1
  191. package/src/utils/id-generator.js +1 -1
  192. package/src/utils/idempotency-manager.js +1 -1
  193. package/src/utils/import-validator.js +1 -1
  194. package/src/utils/license-client.js +1 -1
  195. package/src/utils/lock-manager.js +1 -1
  196. package/src/utils/logger.js +1 -1
  197. package/src/utils/lookup-resolver.js +1 -1
  198. package/src/utils/payload-loader.js +1 -1
  199. package/src/utils/processor-response.js +1 -1
  200. package/src/utils/rabbitmq.js +1 -1
  201. package/src/utils/redis-client.js +1 -1
  202. package/src/utils/redis-helper.js +1 -1
  203. package/src/utils/request-scope.js +1 -1
  204. package/src/utils/security-checks.js +1 -1
  205. package/src/utils/service-resolver.js +1 -1
  206. package/src/utils/shutdown-coordinator.js +1 -1
  207. package/src/utils/soft-delete-dashboard-guard.js +1 -1
  208. package/src/utils/sql-table-extractor.js +1 -1
  209. package/src/utils/trusted-keys.js +1 -1
  210. package/src/utils/upload-handler.js +1 -1
  211. package/src/utils/upsert-builder.js +1 -1
  212. package/src/utils/workflow-hook-executor.js +1 -1
@@ -34,6 +34,8 @@ const DemoGenerator = require('../../src/utils/demo-generator');
34
34
  const projectRegistry = require('../../lib/utils/project-registry');
35
35
  const cliOutput = require('../../lib/utils/cli-output');
36
36
  const endpointSchemaValidator = require('../../lib/payload/endpoint-schema-validator');
37
+ const { deriveFieldProjections, augmentProjectionsForSoftDelete } = require('../../lib/payload/field-projections');
38
+ const configResolver = require('../../lib/utils/config-resolver');
37
39
 
38
40
  function hasAuditRequired(payload) {
39
41
  if (!payload || !payload.fieldPolicy) return false;
@@ -143,14 +145,12 @@ module.exports = {
143
145
  async handler(args) {
144
146
  const startTime = Date.now();
145
147
  let muted = false;
148
+ let summary = null;
146
149
 
147
150
  try {
148
151
  const project = ArgumentValidator.validateProjectName(args.project);
149
152
  const endpoint = ArgumentValidator.validateEndpointName(args.name);
150
153
  const payloadFile = ArgumentValidator.validatePayloadName(args.payload);
151
- const database = args.database
152
- ? ArgumentValidator.validateDatabaseType(args.database)
153
- : 'postgres';
154
154
  const force = !!args.force;
155
155
  const createExamples = !!args['create-examples'];
156
156
  const skipSqlValidation = !!args['skip-sql-validation'];
@@ -161,6 +161,30 @@ module.exports = {
161
161
  ? args.config.trim()
162
162
  : null;
163
163
 
164
+ // Resolusi tipe database:
165
+ // 1. --database eksplisit → dipakai apa adanya (prioritas tertinggi)
166
+ // 2. Auto-deteksi DB_TYPE dari config aktif (--config, atau default
167
+ // config .restforge/defaults.json) → mengikuti DB_TYPE project
168
+ // 3. Fallback 'postgres' bila tidak ada config yang bisa di-resolve
169
+ let database;
170
+ let databaseSource;
171
+ if (args.database) {
172
+ database = ArgumentValidator.validateDatabaseType(args.database);
173
+ databaseSource = 'flag';
174
+ } else {
175
+ const resolvedCfg = configResolver.resolveConfig(configArg, process.cwd());
176
+ const detected = resolvedCfg
177
+ ? configResolver.readDatabaseTypeFromConfig(resolvedCfg.path)
178
+ : null;
179
+ if (detected) {
180
+ database = detected;
181
+ databaseSource = resolvedCfg.source === 'default' ? 'config-default' : 'config';
182
+ } else {
183
+ database = 'postgres';
184
+ databaseSource = 'fallback';
185
+ }
186
+ }
187
+
164
188
  if (!verbose) {
165
189
  cliOutput.mute();
166
190
  muted = true;
@@ -170,12 +194,17 @@ module.exports = {
170
194
  console.log(` Project: ${project}`);
171
195
  console.log(` Endpoint: ${endpoint}`);
172
196
  console.log(` Payload: ${payloadFile}`);
173
- console.log(` Database: ${database}`);
197
+ const databaseNote = databaseSource === 'config'
198
+ ? ' (auto-detected from --config)'
199
+ : databaseSource === 'config-default'
200
+ ? ' (auto-detected from default config)'
201
+ : '';
202
+ console.log(` Database: ${database}${databaseNote}`);
174
203
  console.log(` Force overwrite: ${force}`);
175
204
  console.log('');
176
205
 
177
206
  const cwd = process.cwd();
178
- const summary = {
207
+ summary = {
179
208
  config: { project, endpoint, database, force },
180
209
  payload: null,
181
210
  archive: null,
@@ -204,7 +233,7 @@ module.exports = {
204
233
  const schemaResult = await endpointSchemaValidator.validateEndpointSchema({
205
234
  payload,
206
235
  payloadFileName: path.basename(payloadFile),
207
- payloadFilePath: payloadFile,
236
+ payloadFilePath: rawPayload._payloadPath,
208
237
  configArg,
209
238
  skipSchemaCheck,
210
239
  workingDir: cwd
@@ -216,6 +245,37 @@ module.exports = {
216
245
  summary.schemaValidation = schemaResult;
217
246
  summary.config.config = configArg || null;
218
247
 
248
+ // Derivasi field projections dan embed ke payload sebagai _fieldProjections.
249
+ // Template membaca ini untuk emit schemaFields/readableFields/datatablesFields.
250
+ // Fallback ke fieldName bila DB tidak tersedia (skipSchemaCheck atau projectionInputs absent).
251
+ {
252
+ const fn = payload.fieldName || [];
253
+ let fieldProjections;
254
+ if (schemaResult.status === 'ok' && schemaResult.projectionInputs) {
255
+ fieldProjections = deriveFieldProjections({
256
+ fieldName: fn,
257
+ physicalColumns: schemaResult.projectionInputs.physicalColumns,
258
+ readSourceColumns: schemaResult.projectionInputs.readSourceColumns,
259
+ datatablesColumns: schemaResult.projectionInputs.datatablesColumns,
260
+ overrides: {
261
+ readableFields: payload.readableFields,
262
+ datatablesFields: payload.datatablesFields
263
+ }
264
+ });
265
+ } else {
266
+ // skipSchemaCheck atau projectionInputs tidak tersedia:
267
+ // fallback langsung ke fieldName untuk semua proyeksi tanpa DB.
268
+ // Override payload.readableFields / datatablesFields diterapkan bila ada.
269
+ fieldProjections = {
270
+ schemaFields: fn.slice(),
271
+ readableFields: payload.readableFields || fn.slice(),
272
+ datatablesFields: payload.datatablesFields || fn.slice()
273
+ };
274
+ }
275
+ augmentProjectionsForSoftDelete(fieldProjections, payload);
276
+ payload._fieldProjections = fieldProjections;
277
+ }
278
+
219
279
  const registry = projectRegistry.loadProjectRegistry();
220
280
  if (registry.projects[project]) {
221
281
  const existing = registry.projects[project];
@@ -312,6 +372,9 @@ module.exports = {
312
372
  // cli-entry.js men-print `Error: <message>` ke stderr saat handler
313
373
  // re-throw (lihat handler dispatch). Jangan double-print di sini.
314
374
  if (muted) cliOutput.unmute();
375
+ if (summary && summary.config) {
376
+ cliOutput.printCreatePartial(summary);
377
+ }
315
378
  throw error;
316
379
  }
317
380
  }
@@ -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] || {};
@@ -32,10 +32,11 @@ module.exports = {
32
32
  description: 'Sync only a specific table (default: all)'
33
33
  },
34
34
  'expand-fk': {
35
- type: 'boolean',
35
+ type: 'string',
36
36
  required: false,
37
- default: false,
38
- description: 'Generate JOIN configuration from foreign keys: creates SQL file query/<table>-join.sql and sets datatablesQuery/viewQuery to that file. Opt-in; without this flag sync behavior is unchanged. Requires --table. If --fk-columns is empty, display columns per FK are auto-selected (name → code → primary key)'
37
+ default: null,
38
+ bareDefault: 'both',
39
+ description: 'Generate JOIN configuration from foreign keys: creates SQL file query/<table>-join.sql. Values: "both" (updates datatablesQuery and viewQuery) or "datatables-only" (updates datatablesQuery only, viewQuery unchanged). Bare --expand-fk (without value) defaults to "both". Requires --table. If --fk-columns is empty, display columns per FK are auto-selected (name → code → primary key)'
39
40
  },
40
41
  'fk-columns': {
41
42
  type: 'string',
@@ -47,16 +48,25 @@ module.exports = {
47
48
  examples: [
48
49
  'npx restforge payload sync --config=db.env',
49
50
  'npx restforge payload sync --config=db.env --table=users',
50
- 'npx restforge payload sync --table=visitors --expand-fk',
51
- 'npx restforge payload sync --table=visitors --expand-fk --fk-columns=visitor_categories.category_code,visitor_categories.category_name'
51
+ 'npx restforge payload sync --table=visitors --expand-fk=both',
52
+ 'npx restforge payload sync --table=visitors --expand-fk=datatables-only',
53
+ 'npx restforge payload sync --table=visitors --expand-fk=both --fk-columns=visitor_categories.category_code,visitor_categories.category_name'
52
54
  ],
53
55
  async handler(args) {
56
+ const expandFkRaw = args['expand-fk'];
57
+ let expandFkMode = null;
58
+ if (expandFkRaw === 'both' || expandFkRaw === 'datatables-only') {
59
+ expandFkMode = expandFkRaw;
60
+ } else if (expandFkRaw !== null && expandFkRaw !== undefined) {
61
+ throw new Error(`Invalid --expand-fk value "${expandFkRaw}". Valid values: both, datatables-only`);
62
+ }
63
+
54
64
  const generator = new PayloadGenerator();
55
65
  await generator.run({
56
66
  config: args.config,
57
67
  table: args.table || null,
58
68
  sync: true,
59
- expandFk: args['expand-fk'] === true,
69
+ expandFkMode,
60
70
  fkColumns: args['fk-columns'] || null
61
71
  });
62
72
  }
@@ -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 termasuk route google (Fungsi 3a),
11
+ * lalu menulis ketujuh processor auth termasuk 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,112 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Contract: project sdk
5
+ *
6
+ * Menghasilkan SDK JavaScript untuk satu project RESTForge — satu layer tipis di
7
+ * atas REST API hasil generate, dipakai frontend apa pun tanpa menulis ulang
8
+ * boilerplate fetch/$.ajax.
9
+ *
10
+ * Rujukan desain: docs/plan/sdk-generator-command.md.
11
+ *
12
+ * Versi awal:
13
+ * - TANPA auth (flag --with-auth & core/auth-client.js menyusul terpisah).
14
+ * - --generate hanya MENULIS source buildable (src/, package.json, tsup.config.js);
15
+ * `npm install && npm run build` adalah langkah terpisah milik user.
16
+ * - Sumber kebenaran resource = metadata/<project>.json (key endpoint = slug = segment route).
17
+ */
18
+
19
+ const { validateSafeName } = require('../../lib/utils/path-validator');
20
+ const { generateSdk, resolveBaseUrl } = require('../../lib/sdk/generator');
21
+
22
+ module.exports = {
23
+ resource: 'project',
24
+ verb: 'sdk',
25
+ description: 'Generate a JavaScript SDK for a project (derived from backend metadata + payload)',
26
+ category: 'generation',
27
+ flags: {
28
+ project: {
29
+ type: 'string',
30
+ required: true,
31
+ description: 'Target project name (also the SDK package name)'
32
+ },
33
+ generate: {
34
+ type: 'boolean',
35
+ required: true,
36
+ description: 'Trigger SDK source generation'
37
+ },
38
+ 'sdk-path': {
39
+ type: 'string',
40
+ required: false,
41
+ default: null,
42
+ description: 'Output folder for the SDK source (default: <project-root>/sdk)'
43
+ },
44
+ 'base-url': {
45
+ type: 'string',
46
+ required: false,
47
+ default: null,
48
+ description: 'Override the API base URL baked into sdk-client.js (default: derived from the project config)'
49
+ },
50
+ force: {
51
+ type: 'boolean',
52
+ required: false,
53
+ default: false,
54
+ description: 'Overwrite existing SDK source'
55
+ }
56
+ },
57
+ examples: [
58
+ 'npx restforge project sdk --generate --project=myapp',
59
+ 'npx restforge project sdk --generate --project=myapp --sdk-path=./client-sdk',
60
+ 'npx restforge project sdk --generate --project=myapp --force'
61
+ ],
62
+ async handler(args) {
63
+ const project = validateSafeName(args.project, 'project');
64
+
65
+ if (args.generate !== true) {
66
+ const err = new Error('The --generate flag must be set to run SDK generation.');
67
+ err.exitCode = 2;
68
+ throw err;
69
+ }
70
+
71
+ const workingDir = process.cwd();
72
+
73
+ console.log('');
74
+ console.log(`Generating SDK for project '${project}'...`);
75
+ console.log('');
76
+
77
+ const baseUrl = resolveBaseUrl({
78
+ workingDir,
79
+ project,
80
+ override: args['base-url'],
81
+ log: (line) => console.log(` ${line}`)
82
+ });
83
+
84
+ const result = generateSdk({
85
+ workingDir,
86
+ project,
87
+ sdkPath: args['sdk-path'],
88
+ baseUrl,
89
+ force: args.force === true,
90
+ log: (line) => console.log(` ${line}`)
91
+ });
92
+
93
+ console.log('');
94
+ console.log('==========================================');
95
+ console.log('SDK GENERATION COMPLETE');
96
+ console.log('==========================================');
97
+ console.log(`Project : ${project}`);
98
+ console.log(`Output : ${result.outputDir}`);
99
+ console.log(`Base URL : ${result.baseUrl}`);
100
+ console.log(`Auth : ${result.hasAuth ? 'enabled (client.auth)' : 'none'}`);
101
+ console.log(`Resources : ${result.resources.length} (${result.resources.join(', ')})`);
102
+ console.log('');
103
+ console.log('Next steps (optional, owned by you):');
104
+ console.log(` cd ${result.outputDir}`);
105
+ console.log(' npm install');
106
+ console.log(' npm run build');
107
+ console.log(' npm run deploy # copy into a frontend app js/ folder (asks for target)');
108
+ console.log(' # or: node deploy.mjs <target-app-js-folder>');
109
+ console.log('==========================================');
110
+ console.log('');
111
+ }
112
+ };
@@ -119,6 +119,12 @@ function parseArgs(argv, contract) {
119
119
 
120
120
  if (!valueProvided) {
121
121
  if (i + 1 >= argv.length || argv[i + 1].startsWith('--')) {
122
+ if (flagDef.bareDefault !== undefined) {
123
+ args[flagName] = flagDef.bareDefault;
124
+ seenFlags.add(flagName);
125
+ i += 1;
126
+ continue;
127
+ }
122
128
  errors.push(`Flag --${flagName} requires a value`);
123
129
  i += 1;
124
130
  continue;
@@ -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 };