@restforgejs/platform 5.3.7 → 5.3.10

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 (171) hide show
  1. package/build-info.json +2 -2
  2. package/cli/consumer-deploy.js +1 -1
  3. package/cli/consumer.js +1 -1
  4. package/cli/designer.js +1 -1
  5. package/generators/cli/fast-track.js +230 -45
  6. package/generators/cli/init.js +37 -3
  7. package/generators/lib/templates/dashboard-catalog.js +1 -1
  8. package/generators/lib/templates/db-connection-env.js +1 -1
  9. package/generators/lib/templates/dbschema-catalog.js +1 -1
  10. package/generators/lib/templates/field-validation-catalog.js +1 -1
  11. package/generators/lib/templates/mysql-template.js +1 -1
  12. package/generators/lib/templates/oracle-template.js +1 -1
  13. package/generators/lib/templates/postgres-template.js +1 -1
  14. package/generators/lib/templates/query-declarative-catalog.js +1 -1
  15. package/generators/lib/templates/sqlite-template.js +1 -1
  16. package/integrity-manifest.json +18 -18
  17. package/package.json +2 -1
  18. package/scripts/verify-integrity.js +1 -1
  19. package/server.js +1 -1
  20. package/src/components/handlers/adjust_handler.js +1 -1
  21. package/src/components/handlers/audit_handler.js +1 -1
  22. package/src/components/handlers/delete_handler.js +1 -1
  23. package/src/components/handlers/export_handler.js +1 -1
  24. package/src/components/handlers/import_handler.js +1 -1
  25. package/src/components/handlers/insert_handler.js +1 -1
  26. package/src/components/handlers/update_handler.js +1 -1
  27. package/src/components/handlers/upload_handler.js +1 -1
  28. package/src/components/handlers/workflow_handler.js +1 -1
  29. package/src/components/integrations/webhook.js +1 -1
  30. package/src/consumers/baseConsumer.js +1 -1
  31. package/src/consumers/declarativeMapper.js +1 -1
  32. package/src/consumers/handlers/apiHandler.js +1 -1
  33. package/src/consumers/handlers/consoleHandler.js +1 -1
  34. package/src/consumers/handlers/databaseHandler.js +1 -1
  35. package/src/consumers/handlers/index.js +1 -1
  36. package/src/consumers/handlers/kafkaHandler.js +1 -1
  37. package/src/consumers/index.js +1 -1
  38. package/src/consumers/messageTransformer.js +1 -1
  39. package/src/consumers/validator.js +1 -1
  40. package/src/core/db/dialect/base-dialect.js +1 -1
  41. package/src/core/db/dialect/index.js +1 -1
  42. package/src/core/db/dialect/mysql-dialect.js +1 -1
  43. package/src/core/db/dialect/oracle-dialect.js +1 -1
  44. package/src/core/db/dialect/postgres-dialect.js +1 -1
  45. package/src/core/db/dialect/sqlite-dialect.js +1 -1
  46. package/src/core/db/flatten-helper.js +1 -1
  47. package/src/core/db/query-builder-error.js +1 -1
  48. package/src/core/db/query-builder.js +1 -1
  49. package/src/core/db/relation-helper.js +1 -1
  50. package/src/core/handlers/delete_handler.js +1 -1
  51. package/src/core/handlers/insert_handler.js +1 -1
  52. package/src/core/handlers/update_handler.js +1 -1
  53. package/src/core/models/base-model.js +1 -1
  54. package/src/core/utils/cache-manager.js +1 -1
  55. package/src/core/utils/component-engine.js +1 -1
  56. package/src/core/utils/context-builder.js +1 -1
  57. package/src/core/utils/datetime-formatter.js +1 -1
  58. package/src/core/utils/datetime-parser.js +1 -1
  59. package/src/core/utils/db.js +1 -1
  60. package/src/core/utils/logger.js +1 -1
  61. package/src/core/utils/payload-loader.js +1 -1
  62. package/src/core/utils/security-checks.js +1 -1
  63. package/src/middleware/body-options.js +1 -1
  64. package/src/middleware/cors.js +1 -1
  65. package/src/middleware/idempotency.js +1 -1
  66. package/src/middleware/rate-limiter.js +1 -1
  67. package/src/middleware/request-logger.js +1 -1
  68. package/src/middleware/security-headers.js +1 -1
  69. package/src/models/base-model-mysql.js +1 -1
  70. package/src/models/base-model-oracle.js +1 -1
  71. package/src/models/base-model-sqlite.js +1 -1
  72. package/src/models/base-model.js +1 -1
  73. package/src/pro/caching/redis-client.js +1 -1
  74. package/src/pro/caching/redis-helper.js +1 -1
  75. package/src/pro/consumers/baseConsumer.js +1 -1
  76. package/src/pro/consumers/declarativeMapper.js +1 -1
  77. package/src/pro/consumers/handlers/apiHandler.js +1 -1
  78. package/src/pro/consumers/handlers/consoleHandler.js +1 -1
  79. package/src/pro/consumers/handlers/databaseHandler.js +1 -1
  80. package/src/pro/consumers/handlers/index.js +1 -1
  81. package/src/pro/consumers/handlers/kafkaHandler.js +1 -1
  82. package/src/pro/consumers/index.js +1 -1
  83. package/src/pro/consumers/messageTransformer.js +1 -1
  84. package/src/pro/consumers/validator.js +1 -1
  85. package/src/pro/database/base-model-mysql.js +1 -1
  86. package/src/pro/database/base-model-oracle.js +1 -1
  87. package/src/pro/database/base-model-sqlite.js +1 -1
  88. package/src/pro/database/db-mysql.js +1 -1
  89. package/src/pro/database/db-oracle.js +1 -1
  90. package/src/pro/database/db-sqlite.js +1 -1
  91. package/src/pro/excel/excel-generator.js +1 -1
  92. package/src/pro/excel/excel-parser.js +1 -1
  93. package/src/pro/excel/export-service.js +1 -1
  94. package/src/pro/excel/export_handler.js +1 -1
  95. package/src/pro/excel/import-service.js +1 -1
  96. package/src/pro/excel/import-validator.js +1 -1
  97. package/src/pro/excel/import_handler.js +1 -1
  98. package/src/pro/excel/upsert-builder.js +1 -1
  99. package/src/pro/idgen/idgen-routes.js +1 -1
  100. package/src/pro/integrations/lookup-resolver.js +1 -1
  101. package/src/pro/integrations/upload-handler-v2.js +1 -1
  102. package/src/pro/integrations/upload-handler.js +1 -1
  103. package/src/pro/integrations/webhook.js +1 -1
  104. package/src/pro/locking/lock-routes.js +1 -1
  105. package/src/pro/locking/resource-lock-manager.js +1 -1
  106. package/src/pro/messaging/kafkaConsumerService.js +1 -1
  107. package/src/pro/messaging/kafkaService.js +1 -1
  108. package/src/pro/messaging/messagehubService.js +1 -1
  109. package/src/pro/messaging/rabbitmqService.js +1 -1
  110. package/src/pro/scheduler/job-manager.js +1 -1
  111. package/src/pro/scheduler/job-routes.js +1 -1
  112. package/src/pro/scheduler/job-validator.js +1 -1
  113. package/src/pro/storage/base-storage-provider.js +1 -1
  114. package/src/pro/storage/file-metadata-helper.js +1 -1
  115. package/src/pro/storage/index.js +1 -1
  116. package/src/pro/storage/local-storage-provider.js +1 -1
  117. package/src/pro/storage/s3-storage-provider.js +1 -1
  118. package/src/pro/storage/upload-cleanup-job.js +1 -1
  119. package/src/pro/storage/upload-cleanup-scheduler.js +1 -1
  120. package/src/pro/storage/upload-pending-tracker.js +1 -1
  121. package/src/pro/websocket/broadcast-helper.js +1 -1
  122. package/src/pro/websocket/index.js +1 -1
  123. package/src/pro/websocket/livesync-server.js +1 -1
  124. package/src/pro/websocket/ws-broadcaster.js +1 -1
  125. package/src/services/export-service.js +1 -1
  126. package/src/services/import-service.js +1 -1
  127. package/src/services/kafkaConsumerService.js +1 -1
  128. package/src/services/kafkaService.js +1 -1
  129. package/src/services/messagehubService.js +1 -1
  130. package/src/services/rabbitmqService.js +1 -1
  131. package/src/utils/cache-invalidation-registry.js +1 -1
  132. package/src/utils/cache-manager.js +1 -1
  133. package/src/utils/component-engine.js +1 -1
  134. package/src/utils/config-extractor.js +1 -1
  135. package/src/utils/consumerLogger.js +1 -1
  136. package/src/utils/context-builder.js +1 -1
  137. package/src/utils/dashboard-helpers.js +1 -1
  138. package/src/utils/dateHelper.js +1 -1
  139. package/src/utils/datetime-formatter.js +1 -1
  140. package/src/utils/datetime-parser.js +1 -1
  141. package/src/utils/db-bootstrap.js +1 -1
  142. package/src/utils/db-mysql.js +1 -1
  143. package/src/utils/db-oracle.js +1 -1
  144. package/src/utils/db-sqlite.js +1 -1
  145. package/src/utils/db.js +1 -1
  146. package/src/utils/demo-generator.js +1 -1
  147. package/src/utils/excel-generator.js +1 -1
  148. package/src/utils/excel-parser.js +1 -1
  149. package/src/utils/file-watcher.js +1 -1
  150. package/src/utils/id-generator.js +1 -1
  151. package/src/utils/idempotency-manager.js +1 -1
  152. package/src/utils/import-validator.js +1 -1
  153. package/src/utils/license-client.js +1 -1
  154. package/src/utils/lock-manager.js +1 -1
  155. package/src/utils/logger.js +1 -1
  156. package/src/utils/lookup-resolver.js +1 -1
  157. package/src/utils/payload-loader.js +1 -1
  158. package/src/utils/processor-response.js +1 -1
  159. package/src/utils/rabbitmq.js +1 -1
  160. package/src/utils/redis-client.js +1 -1
  161. package/src/utils/redis-helper.js +1 -1
  162. package/src/utils/request-scope.js +1 -1
  163. package/src/utils/security-checks.js +1 -1
  164. package/src/utils/service-resolver.js +1 -1
  165. package/src/utils/shutdown-coordinator.js +1 -1
  166. package/src/utils/soft-delete-dashboard-guard.js +1 -1
  167. package/src/utils/sql-table-extractor.js +1 -1
  168. package/src/utils/trusted-keys.js +1 -1
  169. package/src/utils/upload-handler.js +1 -1
  170. package/src/utils/upsert-builder.js +1 -1
  171. package/src/utils/workflow-hook-executor.js +1 -1
package/cli/designer.js CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- 'use strict';const a0_0x5c9a7d=a0_0x236a;(function(_0xd30869,_0x3fbe52){const _0x564c38=a0_0x236a,_0x103c81=_0xd30869();while(!![]){try{const _0x7b0e0=parseInt(_0x564c38(0x174))/0x1+parseInt(_0x564c38(0x15f))/0x2*(parseInt(_0x564c38(0x16f))/0x3)+parseInt(_0x564c38(0x164))/0x4*(-parseInt(_0x564c38(0x167))/0x5)+parseInt(_0x564c38(0x16c))/0x6+parseInt(_0x564c38(0x15c))/0x7+parseInt(_0x564c38(0x161))/0x8+-parseInt(_0x564c38(0x16e))/0x9;if(_0x7b0e0===_0x3fbe52)break;else _0x103c81['push'](_0x103c81['shift']());}catch(_0x5e04bc){_0x103c81['push'](_0x103c81['shift']());}}}(a0_0x21dd,0x72bb3));const os=require('os'),path=require(a0_0x5c9a7d(0x173)),fs=require('fs'),{spawn}=require('child_process');function resolveBinaryPath(){const _0x40e5af=a0_0x5c9a7d,_0x360cb6={'obiKl':function(_0xc94ff9,_0x14e727){return _0xc94ff9===_0x14e727;}},_0x3435e4=os['platform'](),_0x186c55=path[_0x40e5af(0x168)](__dirname,'..','bin');if(_0x360cb6['obiKl'](_0x3435e4,'win32'))return path[_0x40e5af(0x160)](_0x186c55,_0x40e5af(0x15b));if(_0x360cb6[_0x40e5af(0x16a)](_0x3435e4,_0x40e5af(0x165)))return path['join'](_0x186c55,_0x40e5af(0x172));return null;}const binaryPath=resolveBinaryPath();function a0_0x21dd(){const _0xf2ee89=['mtuXnZmXtM9QAu9y','C2XPy2u','CMvZDgzVCMDLlwrLC2LNBMvYlMv4zq','mtC4ntC0ou9vwu9TAG','Aw5OzxjPDa','zxjYB3i','nty3mJy4DxL1DeHZ','AM9PBG','nty3nZeYz0HiyNz4','zxHPC3rZu3LUyW','sw5ZDgfSBcb1BgfUzYbaCMvZDgzVCMDLANmVCgXHDgzVCM0GDw50DwSGBwvUzgfWyxrRyw4GyMLUyxj5ihLHBMCGC2vZDwfPlG','ofrxqwDsCG','BgLUDxG','y2HTB2rtEw5J','mJi3ndm2nuHszvvgAW','CMvZB2X2zq','rxjYB3i6ihjLC3rMB3jNzs1KzxnPz25LCIb0AwrHAYbKAwr1A3vUzYbKAsbWBgf0zM9YBsbPBMKGka','B2jPs2W','A2LSBa','mZCXmZe5mhrVthrryq','CgLK','ntu0nenmsen4CW','m0fIwKzkCq','BwvZC2fNzq','zxHPDa','CMvZDgzVCMDLlwrLC2LNBMvYlwXPBNv4','Cgf0Aa'];a0_0x21dd=function(){return _0xf2ee89;};return a0_0x21dd();}function a0_0x236a(_0x569c58,_0x5b0331){_0x569c58=_0x569c58-0x15a;const _0x21dd50=a0_0x21dd();let _0x236a0b=_0x21dd50[_0x569c58];if(a0_0x236a['wTJqNa']===undefined){var _0xcd6aa0=function(_0x5c2f71){const _0x3e09e3='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x4ab395='',_0x2132ac='';for(let _0x2538c7=0x0,_0x39be92,_0x41a3c7,_0x405ded=0x0;_0x41a3c7=_0x5c2f71['charAt'](_0x405ded++);~_0x41a3c7&&(_0x39be92=_0x2538c7%0x4?_0x39be92*0x40+_0x41a3c7:_0x41a3c7,_0x2538c7++%0x4)?_0x4ab395+=String['fromCharCode'](0xff&_0x39be92>>(-0x2*_0x2538c7&0x6)):0x0){_0x41a3c7=_0x3e09e3['indexOf'](_0x41a3c7);}for(let _0xa2141=0x0,_0x59b3ce=_0x4ab395['length'];_0xa2141<_0x59b3ce;_0xa2141++){_0x2132ac+='%'+('00'+_0x4ab395['charCodeAt'](_0xa2141)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x2132ac);};a0_0x236a['wpZREI']=_0xcd6aa0,a0_0x236a['zVeeZR']={},a0_0x236a['wTJqNa']=!![];}const _0x1d6c11=_0x21dd50[0x0],_0x233368=_0x569c58+_0x1d6c11,_0x3b4ad9=a0_0x236a['zVeeZR'][_0x233368];return!_0x3b4ad9?(_0x236a0b=a0_0x236a['wpZREI'](_0x236a0b),a0_0x236a['zVeeZR'][_0x233368]=_0x236a0b):_0x236a0b=_0x3b4ad9,_0x236a0b;}!binaryPath&&(console[a0_0x5c9a7d(0x15e)](a0_0x5c9a7d(0x169)+os['platform']()+').'),process['exit'](0x1));!fs[a0_0x5c9a7d(0x162)](binaryPath)&&(console[a0_0x5c9a7d(0x15e)]('Error:\x20binary\x20restforge-designer\x20tidak\x20ditemukan\x20di\x20'+binaryPath+'.'),console['error'](a0_0x5c9a7d(0x163)),process[a0_0x5c9a7d(0x171)](0x1));if(os['platform']()!=='win32')try{fs[a0_0x5c9a7d(0x166)](binaryPath,0x1ed);}catch{}const child=spawn(binaryPath,process['argv'][a0_0x5c9a7d(0x15a)](0x2),{'stdio':a0_0x5c9a7d(0x15d)});child['on']('error',_0x1fb584=>{const _0xc2b3e8=a0_0x5c9a7d;console['error']('Error\x20menjalankan\x20restforge-designer:\x20'+_0x1fb584[_0xc2b3e8(0x170)]),process['exit'](0x1);}),child['on'](a0_0x5c9a7d(0x171),(_0x35f0bb,_0x216a00)=>{const _0x4404df=a0_0x5c9a7d,_0x5bfe6d={'ntiIm':function(_0x3b72fb,_0x1ce886){return _0x3b72fb??_0x1ce886;}};if(_0x216a00)process[_0x4404df(0x16b)](process[_0x4404df(0x16d)],_0x216a00);else process[_0x4404df(0x171)](_0x5bfe6d['ntiIm'](_0x35f0bb,0x0));});
3
+ 'use strict';const a0_0x230784=a0_0x216a;(function(_0x4e45da,_0x2cdfa3){const _0xf63be7=a0_0x216a,_0x174723=_0x4e45da();while(!![]){try{const _0x171e51=-parseInt(_0xf63be7(0x12b))/0x1*(parseInt(_0xf63be7(0x127))/0x2)+parseInt(_0xf63be7(0x12a))/0x3*(parseInt(_0xf63be7(0x131))/0x4)+-parseInt(_0xf63be7(0x12c))/0x5*(parseInt(_0xf63be7(0x129))/0x6)+-parseInt(_0xf63be7(0x13a))/0x7*(-parseInt(_0xf63be7(0x133))/0x8)+parseInt(_0xf63be7(0x125))/0x9+parseInt(_0xf63be7(0x134))/0xa+parseInt(_0xf63be7(0x130))/0xb*(-parseInt(_0xf63be7(0x132))/0xc);if(_0x171e51===_0x2cdfa3)break;else _0x174723['push'](_0x174723['shift']());}catch(_0x1f791b){_0x174723['push'](_0x174723['shift']());}}}(a0_0x12bb,0x4a02e));function a0_0x216a(_0x363aca,_0x4874f0){_0x363aca=_0x363aca-0x124;const _0x12bb6e=a0_0x12bb();let _0x216a13=_0x12bb6e[_0x363aca];if(a0_0x216a['OvGUrM']===undefined){var _0x3f73a2=function(_0x1184df){const _0x4f844d='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x537a67='',_0x1e9fde='';for(let _0x52eff1=0x0,_0x28f863,_0x4d8c9b,_0x1a77c5=0x0;_0x4d8c9b=_0x1184df['charAt'](_0x1a77c5++);~_0x4d8c9b&&(_0x28f863=_0x52eff1%0x4?_0x28f863*0x40+_0x4d8c9b:_0x4d8c9b,_0x52eff1++%0x4)?_0x537a67+=String['fromCharCode'](0xff&_0x28f863>>(-0x2*_0x52eff1&0x6)):0x0){_0x4d8c9b=_0x4f844d['indexOf'](_0x4d8c9b);}for(let _0x31c0e7=0x0,_0x2882f4=_0x537a67['length'];_0x31c0e7<_0x2882f4;_0x31c0e7++){_0x1e9fde+='%'+('00'+_0x537a67['charCodeAt'](_0x31c0e7)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x1e9fde);};a0_0x216a['fQrvpc']=_0x3f73a2,a0_0x216a['vbuVwH']={},a0_0x216a['OvGUrM']=!![];}const _0x207509=_0x12bb6e[0x0],_0x4f07bf=_0x363aca+_0x207509,_0x51fd17=a0_0x216a['vbuVwH'][_0x4f07bf];return!_0x51fd17?(_0x216a13=a0_0x216a['fQrvpc'](_0x216a13),a0_0x216a['vbuVwH'][_0x4f07bf]=_0x216a13):_0x216a13=_0x51fd17,_0x216a13;}const os=require('os'),path=require('path'),fs=require('fs'),{spawn}=require(a0_0x230784(0x12d));function resolveBinaryPath(){const _0x1a7e3d=a0_0x230784,_0x26c228={'kRQGc':'bin','giueu':'win32','yAxDd':function(_0x154a70,_0x53ccda){return _0x154a70===_0x53ccda;},'iZskE':_0x1a7e3d(0x124)},_0x483547=os[_0x1a7e3d(0x13b)](),_0x275336=path['resolve'](__dirname,'..',_0x26c228[_0x1a7e3d(0x135)]);if(_0x483547===_0x26c228['giueu'])return path['join'](_0x275336,'restforge-designer.exe');if(_0x26c228['yAxDd'](_0x483547,_0x1a7e3d(0x12e)))return path[_0x1a7e3d(0x139)](_0x275336,_0x26c228[_0x1a7e3d(0x126)]);return null;}const binaryPath=resolveBinaryPath();!binaryPath&&(console['error']('Error:\x20restforge-designer\x20tidak\x20didukung\x20di\x20platform\x20ini\x20('+os['platform']()+').'),process['exit'](0x1));!fs['existsSync'](binaryPath)&&(console['error']('Error:\x20binary\x20restforge-designer\x20tidak\x20ditemukan\x20di\x20'+binaryPath+'.'),console['error']('Install\x20ulang\x20@restforgejs/platform\x20untuk\x20mendapatkan\x20binary\x20yang\x20sesuai.'),process[a0_0x230784(0x137)](0x1));function a0_0x12bb(){const _0x3847d3=['mvvND2jwuG','ntqZntvWCxjezfq','y2HPBgrFChjVy2vZCW','BgLUDxG','zxjYB3i','nJC2mZL3C0nqzem','mZu2q3Lkyvb2','mJC2qMTgA3nO','nda4BLj1wMvM','ndm2mZq5mgXgq2DAyG','A1jrr2m','Aw5OzxjPDa','zxHPDa','C2XPy2u','AM9PBG','mte2nJjzsNf2A2e','CgXHDgzVCM0','CMvZDgzVCMDLlwrLC2LNBMvYlwXPBNv4','nte0mZyXn05UBLrLrq','AvPZA0u','mtiWmZC4mLfVt214wa','D2LUmZi','nZHbwLburei','mZiWmvvWq3vHtq'];a0_0x12bb=function(){return _0x3847d3;};return a0_0x12bb();}if(os['platform']()!==a0_0x230784(0x128))try{fs['chmodSync'](binaryPath,0x1ed);}catch{}const child=spawn(binaryPath,process['argv'][a0_0x230784(0x138)](0x2),{'stdio':a0_0x230784(0x136)});child['on'](a0_0x230784(0x12f),_0xf76e73=>{console['error']('Error\x20menjalankan\x20restforge-designer:\x20'+_0xf76e73['message']),process['exit'](0x1);}),child['on'](a0_0x230784(0x137),(_0x38f8c1,_0x5ef93c)=>{const _0x1122e3=a0_0x230784;if(_0x5ef93c)process['kill'](process['pid'],_0x5ef93c);else process[_0x1122e3(0x137)](_0x38f8c1??0x0);});
@@ -54,7 +54,8 @@ const DEFAULTS = {
54
54
  DB_USER: 'postgres',
55
55
  DB_PASSWORD: 'postgres1234',
56
56
  DB_NAME: 'dbsandbox',
57
- DB_FILE: './data/sandbox.db'
57
+ DB_FILE: './data/sandbox.db',
58
+ CORS_ENABLED: 'true'
58
59
  };
59
60
 
60
61
  const DEFAULT_CONFIG_FILE = 'db-connection.env';
@@ -208,6 +209,16 @@ function describeDatabase(cfg) {
208
209
  return `${cfg.DB_TYPE} @ ${cfg.DB_HOST}:${cfg.DB_PORT}/${cfg.DB_NAME}`;
209
210
  }
210
211
 
212
+ /**
213
+ * Default CORS_ORIGINS diturunkan dari origin web server (frontend) yang sudah
214
+ * diinput, mis. SERVER_ADDRESS=127.0.0.1 + WEB_SERVER_PORT=8000 →
215
+ * `http://127.0.0.1:8000`. Origin frontend inilah yang harus di-whitelist CORS
216
+ * agar aplikasi web bisa memanggil REST API dari browser.
217
+ */
218
+ function defaultCorsOrigins(cfg) {
219
+ return `http://${cfg.SERVER_ADDRESS}:${cfg.WEB_SERVER_PORT}`;
220
+ }
221
+
211
222
  // ---------------------------------------------------------------------------
212
223
  // Pengumpulan data preview (read-only, tanpa efek samping)
213
224
  // ---------------------------------------------------------------------------
@@ -341,6 +352,34 @@ function resolveSourceConfig(cwd, explicitConfig) {
341
352
  return { configName, fileCfg: data || {} };
342
353
  }
343
354
 
355
+ // ---------------------------------------------------------------------------
356
+ // Normalisasi path file SQLite
357
+ // ---------------------------------------------------------------------------
358
+
359
+ /**
360
+ * Normalisasi input DB_FILE SQLite dari user menjadi path yang lengkap.
361
+ *
362
+ * Aturan:
363
+ * - Tanpa komponen direktori (mis. "myapp" atau "myapp.db")
364
+ * → dimasukkan ke folder ./data/ → "./data/myapp.db"
365
+ * - Dengan direktori custom (mis. "./dataku/myapp")
366
+ * → path-nya dihormati, hanya ekstensi yang ditambahkan → "./dataku/myapp.db"
367
+ * - Ekstensi .db selalu ditambahkan jika belum ada.
368
+ *
369
+ * Contoh:
370
+ * "myapp" → "./data/myapp.db"
371
+ * "myapp.db" → "./data/myapp.db"
372
+ * "./dataku/myapp" → "./dataku/myapp.db"
373
+ * "./data/myapp.db" → "./data/myapp.db" (tidak berubah)
374
+ */
375
+ function normalizeDbFilePath(input) {
376
+ let result = input.trim();
377
+ if (!result.endsWith('.db')) result += '.db';
378
+ const hasDir = result.includes('/') || result.includes('\\');
379
+ if (!hasDir) result = './data/' + result;
380
+ return result;
381
+ }
382
+
344
383
  // ---------------------------------------------------------------------------
345
384
  // Fase input konfigurasi (LICENSE + database), mengikuti fast-track.mjs
346
385
  // ---------------------------------------------------------------------------
@@ -375,6 +414,11 @@ async function collectConfig(args, ask, fileCfg = {}) {
375
414
  cfg.SERVER_PORT = await askField('REST_API_PORT', fileCfg.SERVER_PORT || DEFAULTS.SERVER_PORT);
376
415
  cfg.WEB_SERVER_PORT = await askField('WEB_SERVER_PORT', DEFAULTS.WEB_SERVER_PORT);
377
416
 
417
+ // CORS: CORS_ENABLED diikutkan apa adanya (default true, tidak diprompt edit);
418
+ // CORS_ORIGINS dapat diedit dengan default origin web server yang baru diisi.
419
+ cfg.CORS_ENABLED = fileCfg.CORS_ENABLED || DEFAULTS.CORS_ENABLED;
420
+ cfg.CORS_ORIGINS = await askField('CORS_ORIGINS', fileCfg.CORS_ORIGINS || defaultCorsOrigins(cfg));
421
+
378
422
  // DB_TYPE menentukan atribut yang diminta berikutnya.
379
423
  console.log('');
380
424
  console.log(' Available DB_TYPE: postgresql, mysql, oracle, sqlite');
@@ -387,7 +431,8 @@ async function collectConfig(args, ask, fileCfg = {}) {
387
431
  console.log('');
388
432
  // fileCfg.DB_NAME: file legacy hasil `restforge init` menyimpan path
389
433
  // 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);
434
+ const dbFileRaw = await askField('DB_FILE (.db file path)', fileCfg.DB_FILE || fileCfg.DB_NAME || DEFAULTS.DB_FILE);
435
+ cfg.DB_FILE = normalizeDbFilePath(dbFileRaw);
391
436
  cfg.DB_NAME = cfg.DB_FILE;
392
437
  } else {
393
438
  const dbDef = DB_TYPE_DEFAULTS[cfg.DB_TYPE] || {};
@@ -414,6 +459,8 @@ function defaultCfgFromFile(args, fileCfg = {}) {
414
459
  cfg.SERVER_ADDRESS = fileCfg.SERVER_ADDRESS || DEFAULTS.SERVER_ADDRESS;
415
460
  cfg.SERVER_PORT = fileCfg.SERVER_PORT || DEFAULTS.SERVER_PORT;
416
461
  cfg.WEB_SERVER_PORT = DEFAULTS.WEB_SERVER_PORT;
462
+ cfg.CORS_ENABLED = fileCfg.CORS_ENABLED || DEFAULTS.CORS_ENABLED;
463
+ cfg.CORS_ORIGINS = fileCfg.CORS_ORIGINS || defaultCorsOrigins(cfg);
417
464
  cfg.DB_TYPE = (fileCfg.DB_TYPE || DEFAULTS.DB_TYPE).toLowerCase();
418
465
 
419
466
  if (cfg.DB_TYPE === 'sqlite') {
@@ -447,6 +494,8 @@ function printExistingConfigSummary({ project, schemaFlag, configName, cfg, over
447
494
  console.log(` License : ${maskLicense(cfg.LICENSE)}`);
448
495
  console.log(` REST API : ${cfg.SERVER_ADDRESS}:${cfg.SERVER_PORT}`);
449
496
  console.log(` Web server : ${cfg.SERVER_ADDRESS}:${cfg.WEB_SERVER_PORT}`);
497
+ console.log(` CORS : ${cfg.CORS_ENABLED}`);
498
+ console.log(` CORS Origin: ${cfg.CORS_ORIGINS}`);
450
499
  console.log(` Mode : ${overwrite ? 'overwrite' : 'sync'}`);
451
500
  console.log('');
452
501
  console.log(' Database Configuration');
@@ -909,6 +958,8 @@ function envValuesFromCfg(cfg) {
909
958
  SERVER_PORT: cfg.SERVER_PORT,
910
959
  DB_TYPE: cfg.DB_TYPE
911
960
  };
961
+ if (cfg.CORS_ENABLED !== undefined) v.CORS_ENABLED = cfg.CORS_ENABLED;
962
+ if (cfg.CORS_ORIGINS !== undefined) v.CORS_ORIGINS = cfg.CORS_ORIGINS;
912
963
  if (cfg.DB_TYPE === 'sqlite') {
913
964
  v.DB_FILE = cfg.DB_FILE;
914
965
  v.DB_NAME = cfg.DB_NAME;
@@ -1262,16 +1313,25 @@ function runFrontendPipeline(ctx) {
1262
1313
  * biasa membiarkan NODE_ENV kosong sehingga logger memakai pino-pretty (rapi).
1263
1314
  */
1264
1315
  function writeServerStartScript(ctx) {
1265
- const serveCmd = `npx restforge serve --project=${ctx.project} --config=${ctx.configFlag} --watch`;
1266
1316
  const isWin = process.platform === 'win32';
1267
1317
  const file = path.join(ctx.cwd, isWin ? 'server-start.bat' : 'server-start.sh');
1268
1318
  let content;
1269
- if (isWin) {
1319
+ if (isWin && ctx.runMode === 'pm2') {
1320
+ const appName = pm2Name(ctx.project, 'server');
1321
+ content = [
1322
+ '@echo off',
1323
+ 'REM Start RESTForge REST API via pm2. Generated by fast-track.',
1324
+ 'cd /d "%~dp0"',
1325
+ `call pm2 delete ${appName} >nul 2>&1`,
1326
+ `call pm2 start ecosystem.config.js --only ${appName}`,
1327
+ ''
1328
+ ].join('\r\n');
1329
+ } else if (isWin) {
1270
1330
  content = [
1271
1331
  '@echo off',
1272
1332
  'REM Start RESTForge REST API (runtime server). Generated by fast-track.',
1273
1333
  'cd /d "%~dp0"',
1274
- `call ${serveCmd}`,
1334
+ `call npx restforge serve --project=${ctx.project} --config=${ctx.configFlag} --watch`,
1275
1335
  ''
1276
1336
  ].join('\r\n');
1277
1337
  } else {
@@ -1280,7 +1340,7 @@ function writeServerStartScript(ctx) {
1280
1340
  '# Start RESTForge REST API (runtime server). Generated by fast-track.',
1281
1341
  'set -e',
1282
1342
  'cd "$(dirname "$0")"',
1283
- serveCmd,
1343
+ `npx restforge serve --project=${ctx.project} --config=${ctx.configFlag} --watch`,
1284
1344
  ''
1285
1345
  ].join('\n');
1286
1346
  }
@@ -1297,17 +1357,26 @@ function writeServerStartScript(ctx) {
1297
1357
  * writeServerStartScript, dipakai juga sebagai target `pm2 start` di non-Windows.
1298
1358
  */
1299
1359
  function writeFrontendStartScript(ctx) {
1300
- const serveCmd = `npx serve . -l ${ctx.cfg.WEB_SERVER_PORT}`;
1301
1360
  const isWin = process.platform === 'win32';
1302
1361
  const appDir = path.join(ctx.cwd, 'frontend', 'apps', ctx.project);
1303
1362
  const file = path.join(appDir, isWin ? 'frontend-start.bat' : 'frontend-start.sh');
1304
1363
  let content;
1305
- if (isWin) {
1364
+ if (isWin && ctx.runMode === 'pm2') {
1365
+ const appName = pm2Name(ctx.project, 'frontend');
1366
+ content = [
1367
+ '@echo off',
1368
+ 'REM Start RESTForge frontend app via pm2. Generated by fast-track.',
1369
+ 'cd /d "%~dp0"',
1370
+ `call pm2 delete ${appName} >nul 2>&1`,
1371
+ `call pm2 start ecosystem.config.js --only ${appName}`,
1372
+ ''
1373
+ ].join('\r\n');
1374
+ } else if (isWin) {
1306
1375
  content = [
1307
1376
  '@echo off',
1308
1377
  'REM Start RESTForge frontend app. Generated by fast-track.',
1309
1378
  'cd /d "%~dp0"',
1310
- `call ${serveCmd}`,
1379
+ `call npx serve . -l ${ctx.cfg.WEB_SERVER_PORT}`,
1311
1380
  ''
1312
1381
  ].join('\r\n');
1313
1382
  } else {
@@ -1316,7 +1385,7 @@ function writeFrontendStartScript(ctx) {
1316
1385
  '# Start RESTForge frontend app. Generated by fast-track.',
1317
1386
  'set -e',
1318
1387
  'cd "$(dirname "$0")"',
1319
- serveCmd,
1388
+ `npx serve . -l ${ctx.cfg.WEB_SERVER_PORT}`,
1320
1389
  ''
1321
1390
  ].join('\n');
1322
1391
  }
@@ -1337,23 +1406,144 @@ function pm2Name(project, kind) {
1337
1406
  return `${project}-${kind}`;
1338
1407
  }
1339
1408
 
1409
+ /**
1410
+ * Resolusi path entry executable `serve` yang BENAR-BENAR terpasang di
1411
+ * node_modules project, version-agnostic. Membaca `bin` dari package.json serve
1412
+ * (mis. serve v14 → `build/main.js`) lalu mengembalikan path absolut entry-nya.
1413
+ *
1414
+ * Mengembalikan null bila serve tidak resolvable (belum terpasang). Lebih andal
1415
+ * daripada `fs.existsSync` pada path hardcoded: mendeteksi keberadaan paket yang
1416
+ * sebenarnya (bukan sekadar asumsi struktur folder) dan ikut menyesuaikan bila
1417
+ * versi serve mengubah lokasi entry.
1418
+ */
1419
+ function resolveServeEntry(cwd) {
1420
+ try {
1421
+ const pkgPath = require.resolve('serve/package.json', { paths: [cwd] });
1422
+ const pkg = require(pkgPath);
1423
+ let bin = pkg.bin;
1424
+ if (bin && typeof bin === 'object') bin = bin.serve || Object.values(bin)[0];
1425
+ if (!bin || typeof bin !== 'string') return null;
1426
+ const entry = path.resolve(path.dirname(pkgPath), bin);
1427
+ return fs.existsSync(entry) ? entry : null;
1428
+ } catch (_err) {
1429
+ return null;
1430
+ }
1431
+ }
1432
+
1340
1433
  /**
1341
1434
  * Start (ulang) script launcher via pm2. Idempotent: hapus instance lama dulu
1342
1435
  * (silent, tolerant bila belum ada) supaya re-run fast-track tidak gagal
1343
1436
  * dengan "already launched". Return false bila pm2 tidak tersedia/gagal.
1344
1437
  */
1345
- function pm2StartScript(name, scriptPath, cwd) {
1438
+ /**
1439
+ * Start (ulang) satu app dari ecosystem.config.js via pm2. Idempotent: hapus
1440
+ * instance lama dulu (silent) supaya re-run fast-track tidak gagal "already
1441
+ * launched". Menggunakan `--only <name>` agar hanya app yang dimaksud yang
1442
+ * di-start dari file ecosystem yang bisa berisi beberapa app.
1443
+ *
1444
+ * Pendekatan ecosystem + `interpreter: none` (dideklarasikan di
1445
+ * writeEcosystemConfig) menghindari masalah pm2 default ke Node.js sebagai
1446
+ * interpreter saat diberikan file .bat / .sh secara langsung.
1447
+ */
1448
+ function pm2StartScript(name, ecosystemPath) {
1346
1449
  spawnSync(`pm2 delete ${name} --silent`, { shell: true, stdio: 'ignore' });
1347
- // Windows menjalankan .bat via cmd.exe secara otomatis; --interpreter bash
1348
- // hanya diperlukan untuk .sh di Linux/macOS.
1349
- const interpreterFlag = process.platform !== 'win32' ? ' --interpreter bash' : '';
1350
1450
  const r = spawnSync(
1351
- `pm2 start "${scriptPath}" --name ${name} --cwd "${cwd}"${interpreterFlag}`,
1451
+ `pm2 start "${ecosystemPath}" --only ${name}`,
1352
1452
  { shell: true, stdio: 'inherit' }
1353
1453
  );
1354
1454
  return !r.error && r.status === 0;
1355
1455
  }
1356
1456
 
1457
+ /**
1458
+ * Generate ecosystem.config.js di root project. pm2 membaca file ini untuk
1459
+ * mengetahui cara menjalankan tiap app tanpa bergantung pada file launcher
1460
+ * (.bat / .sh) yang rentan salah interpreter.
1461
+ *
1462
+ * `script: "npx"` + `interpreter: "none"` + `args: "..."` adalah pola yang
1463
+ * bekerja seragam di Windows dan Linux: pm2 memanggil npx langsung sebagai
1464
+ * executable OS, bukan membungkusnya dengan Node.
1465
+ */
1466
+ /**
1467
+ * Tulis file launcher Node.js kecil untuk satu proses pm2.
1468
+ *
1469
+ * pm2 menjalankan file .js dengan `node` secara default — tidak perlu
1470
+ * interpreter khusus. Launcher ini menggunakan spawn({ shell: true }) agar
1471
+ * `npx` resolves dengan benar di Windows (npx.cmd) maupun Linux, tanpa
1472
+ * membuka window terminal baru.
1473
+ */
1474
+ function writeEcosystemConfig(ctx) {
1475
+ const apps = [];
1476
+
1477
+ if (ctx.scope.backend) {
1478
+ apps.push({
1479
+ name: pm2Name(ctx.project, 'server'),
1480
+ script: 'node_modules/@restforgejs/platform/server.js',
1481
+ args: `serve --project=${ctx.project} --config=${ctx.configFlag} --watch`,
1482
+ cwd: ctx.cwd,
1483
+ instances: 1,
1484
+ exec_mode: 'fork',
1485
+ autorestart: true,
1486
+ watch: false,
1487
+ max_memory_restart: '1G',
1488
+ node_args: '--max-old-space-size=4096',
1489
+ kill_timeout: 3000,
1490
+ windowsHide: true,
1491
+ env: {
1492
+ NODE_ENV: 'production',
1493
+ PM2_USAGE_METRICS: 'false'
1494
+ }
1495
+ });
1496
+ }
1497
+
1498
+ if (ctx.scope.frontend) {
1499
+ // `serve` ikut sebagai dependency @restforgejs/platform, jadi normalnya
1500
+ // sudah ter-hoist di node_modules/serve setiap project yang memasang
1501
+ // platform. resolveServeEntry mendeteksi keberadaannya secara
1502
+ // version-agnostic. Install lazy di bawah hanya fallback untuk project
1503
+ // lama yang ter-install sebelum serve ditambahkan ke deps platform.
1504
+ let serveEntry = resolveServeEntry(ctx.cwd);
1505
+ if (!serveEntry) {
1506
+ console.log('\n Installing serve (static file server for frontend)...');
1507
+ const r = spawnSync('npm install serve', { cwd: ctx.cwd, shell: true, stdio: 'inherit' });
1508
+ if (r.error || r.status !== 0) {
1509
+ console.log(' [WARN] serve install failed — frontend pm2 entry mungkin tidak jalan.');
1510
+ }
1511
+ serveEntry = resolveServeEntry(ctx.cwd);
1512
+ }
1513
+ if (!serveEntry) {
1514
+ console.log(' [WARN] serve entry not found after install — frontend pm2 app may fail to start.');
1515
+ console.log(' Fallback: start frontend manually with `npx serve . -l ' + ctx.cfg.WEB_SERVER_PORT + '`.');
1516
+ }
1517
+ // Path script pm2 relatif ke cwd (forward slash), dari entry yang
1518
+ // ter-resolve; fallback ke path kanonik serve v14 bila resolusi gagal.
1519
+ const serveScript = serveEntry
1520
+ ? path.relative(ctx.cwd, serveEntry).split(path.sep).join('/')
1521
+ : 'node_modules/serve/build/main.js';
1522
+ apps.push({
1523
+ name: pm2Name(ctx.project, 'frontend'),
1524
+ script: serveScript,
1525
+ args: `./frontend/apps/${ctx.project} -l ${ctx.cfg.WEB_SERVER_PORT}`,
1526
+ cwd: ctx.cwd,
1527
+ instances: 1,
1528
+ exec_mode: 'fork',
1529
+ autorestart: true,
1530
+ watch: false,
1531
+ max_memory_restart: '512M',
1532
+ kill_timeout: 3000,
1533
+ windowsHide: true,
1534
+ env: {
1535
+ NODE_ENV: 'production',
1536
+ PM2_USAGE_METRICS: 'false'
1537
+ }
1538
+ });
1539
+ }
1540
+
1541
+ const content = 'module.exports = ' + JSON.stringify({ apps }, null, 2) + ';\n';
1542
+ const filePath = path.join(ctx.cwd, 'ecosystem.config.js');
1543
+ fs.writeFileSync(filePath, content);
1544
+ return filePath;
1545
+ }
1546
+
1357
1547
  // ---------------------------------------------------------------------------
1358
1548
  // Pemilihan mode run service (Windows only): terminal baru atau pm2
1359
1549
  // ---------------------------------------------------------------------------
@@ -1443,7 +1633,7 @@ async function startServerNow(ctx, runMode = 'terminal') {
1443
1633
  if (runMode === 'pm2') {
1444
1634
  console.log(`\n Starting via pm2: "${title}"`);
1445
1635
  const name = pm2Name(ctx.project, 'server');
1446
- const ok = pm2StartScript(name, ctx.serverStartFile, ctx.cwd);
1636
+ const ok = pm2StartScript(name, ctx.ecosystemPath);
1447
1637
  if (!ok) {
1448
1638
  console.log(' Failed to start via pm2. Is pm2 installed? (npm install -g pm2)');
1449
1639
  return false;
@@ -1465,7 +1655,7 @@ async function startServerNow(ctx, runMode = 'terminal') {
1465
1655
  // pakai pm2 supaya proses bisa dikelola normal (pm2 list/logs/reload/stop).
1466
1656
  console.log(`\n Starting via pm2: "${title}"`);
1467
1657
  const name = pm2Name(ctx.project, 'server');
1468
- const ok = pm2StartScript(name, ctx.serverStartFile, ctx.cwd);
1658
+ const ok = pm2StartScript(name, ctx.ecosystemPath);
1469
1659
  if (!ok) {
1470
1660
  console.log(' Failed to start via pm2. Is pm2 installed? (npm install -g pm2)');
1471
1661
  return false;
@@ -1490,7 +1680,7 @@ async function startServerNow(ctx, runMode = 'terminal') {
1490
1680
  }
1491
1681
 
1492
1682
  /** Konfirmasi lalu jalankan runtime server. Dipakai scope REST API Only. */
1493
- async function maybeRunServer(ctx, ask, prompter) {
1683
+ async function maybeRunServer(ctx, ask) {
1494
1684
  const serveCmd = `npx restforge serve --project=${ctx.project} --config=${ctx.configFlag} --watch`;
1495
1685
  console.log('');
1496
1686
  const answer = (await ask(' Run Runtime Server now? (Y/n): ')).trim().toLowerCase();
@@ -1498,8 +1688,7 @@ async function maybeRunServer(ctx, ask, prompter) {
1498
1688
  console.log(` Skipped. Start later: ${serveCmd}`);
1499
1689
  return;
1500
1690
  }
1501
- const runMode = process.platform === 'win32' ? await selectRunMode(prompter) : 'pm2';
1502
- await startServerNow(ctx, runMode);
1691
+ await startServerNow(ctx, ctx.runMode);
1503
1692
  }
1504
1693
 
1505
1694
  /**
@@ -1526,7 +1715,7 @@ async function startFrontendNow(ctx, runMode = 'terminal') {
1526
1715
  if (runMode === 'pm2') {
1527
1716
  console.log(`\n Starting via pm2: "${title}"`);
1528
1717
  const name = pm2Name(ctx.project, 'frontend');
1529
- const ok = pm2StartScript(name, ctx.frontendStartFile, appDir);
1718
+ const ok = pm2StartScript(name, ctx.ecosystemPath);
1530
1719
  if (!ok) {
1531
1720
  console.log(' Failed to start via pm2. Is pm2 installed? (npm install -g pm2)');
1532
1721
  return false;
@@ -1546,7 +1735,7 @@ async function startFrontendNow(ctx, runMode = 'terminal') {
1546
1735
  } else {
1547
1736
  console.log(`\n Starting via pm2: "${title}"`);
1548
1737
  const name = pm2Name(ctx.project, 'frontend');
1549
- const ok = pm2StartScript(name, ctx.frontendStartFile, appDir);
1738
+ const ok = pm2StartScript(name, ctx.ecosystemPath);
1550
1739
  if (!ok) {
1551
1740
  console.log(' Failed to start via pm2. Is pm2 installed? (npm install -g pm2)');
1552
1741
  return false;
@@ -1587,7 +1776,7 @@ async function startFrontendNow(ctx, runMode = 'terminal') {
1587
1776
  * butuh API sudah hidup), baru lanjut frontend setelah server window
1588
1777
  * terbuka + health check selesai.
1589
1778
  */
1590
- async function maybeRunServerAndFrontend(ctx, ask, prompter) {
1779
+ async function maybeRunServerAndFrontend(ctx, ask) {
1591
1780
  const serveCmd = `npx restforge serve --project=${ctx.project} --config=${ctx.configFlag} --watch`;
1592
1781
  const frontendCmd = `npx serve . -l ${ctx.cfg.WEB_SERVER_PORT}`;
1593
1782
  console.log('');
@@ -1597,9 +1786,8 @@ async function maybeRunServerAndFrontend(ctx, ask, prompter) {
1597
1786
  console.log(` Skipped. Start later (in frontend/apps/${ctx.project}): ${frontendCmd}`);
1598
1787
  return;
1599
1788
  }
1600
- const runMode = process.platform === 'win32' ? await selectRunMode(prompter) : 'pm2';
1601
- await startServerNow(ctx, runMode);
1602
- await startFrontendNow(ctx, runMode);
1789
+ await startServerNow(ctx, ctx.runMode);
1790
+ await startFrontendNow(ctx, ctx.runMode);
1603
1791
  }
1604
1792
 
1605
1793
  // ---------------------------------------------------------------------------
@@ -1731,30 +1919,27 @@ module.exports = {
1731
1919
  await confirmDefaultMode(ctx, prompter.ask);
1732
1920
  }
1733
1921
 
1734
- // 6) Eksekusi sesuai scope.
1735
- if (ctx.scope.backend) {
1736
- runBackendPipeline(ctx);
1737
- // Launcher start REST API mandiri (sesuai OS), di folder kerja.
1738
- ctx.serverStartFile = writeServerStartScript(ctx);
1739
- }
1740
- if (ctx.scope.frontend) {
1741
- runFrontendPipeline(ctx);
1742
- // Launcher start frontend mandiri (sesuai OS), dipakai juga sebagai
1743
- // target `pm2 start` di non-Windows.
1744
- ctx.frontendStartFile = writeFrontendStartScript(ctx);
1745
- }
1922
+ // 6) Eksekusi pipeline.
1923
+ if (ctx.scope.backend) runBackendPipeline(ctx);
1924
+ if (ctx.scope.frontend) runFrontendPipeline(ctx);
1925
+
1926
+ // ecosystem.config.js dibuat setelah pipeline selesai.
1927
+ ctx.ecosystemPath = writeEcosystemConfig(ctx);
1928
+
1929
+ // Pilih run mode di Windows sebelum menulis bat file agar konten bat
1930
+ // sesuai pilihan (pm2 start ecosystem / npx langsung).
1931
+ ctx.runMode = process.platform === 'win32' ? await selectRunMode(prompter) : 'pm2';
1932
+
1933
+ if (ctx.scope.backend) ctx.serverStartFile = writeServerStartScript(ctx);
1934
+ if (ctx.scope.frontend) ctx.frontendStartFile = writeFrontendStartScript(ctx);
1746
1935
 
1747
1936
  printFinalSummary(ctx);
1748
1937
 
1749
- // 7) Tawarkan menjalankan service. Scope REST API + Frontend -> SATU
1750
- // dialog konfirmasi gabungan, tapi eksekusi tetap wajib runtime
1751
- // server lebih dulu baru frontend (frontend butuh API hidup).
1752
- // Scope REST API Only -> dialog server saja (tidak ada frontend
1753
- // yang di-generate, SCOPES tidak punya opsi frontend-only).
1938
+ // 7) Tawarkan menjalankan service.
1754
1939
  if (ctx.scope.backend && ctx.scope.frontend) {
1755
- await maybeRunServerAndFrontend(ctx, prompter.ask, prompter);
1940
+ await maybeRunServerAndFrontend(ctx, prompter.ask);
1756
1941
  } else if (ctx.scope.backend) {
1757
- await maybeRunServer(ctx, prompter.ask, prompter);
1942
+ await maybeRunServer(ctx, prompter.ask);
1758
1943
  }
1759
1944
  } finally {
1760
1945
  prompter.close();
@@ -9,8 +9,9 @@
9
9
  * Dua mode operasi:
10
10
  * 1. Interaktif (default, hanya saat stdin TTY dan TANPA --force):
11
11
  * dialog konfirmasi gaya fast-track. Meminta input LICENSE, tipe database
12
- * beserta atributnya, dan nama file config (default db-connection.env, bisa
13
- * di-custom). Nilai input di-patch ke template lalu ditulis.
12
+ * beserta atributnya, CORS_ORIGINS (default '*'), dan nama file config
13
+ * (default db-connection.env, bisa di-custom). Nilai input di-patch ke
14
+ * template lalu ditulis.
14
15
  * 2. Non-interaktif (saat --force diberikan ATAU stdin bukan TTY):
15
16
  * menulis template apa adanya ke config/db-connection.env (perilaku legacy).
16
17
  * Jalur ini dipakai oleh orkestrator (fast-track) dan playbook yang
@@ -104,12 +105,35 @@ function dbDefaultsFor(dbType) {
104
105
  return { ...DB_DEFAULTS, ...override };
105
106
  }
106
107
 
108
+ // Default CORS_ORIGINS: '*' (semua origin). Selaras dengan default template dan
109
+ // perilaku backward-compatible middleware. Pengguna mengganti dengan daftar
110
+ // origin frontend spesifik untuk production saat prompt.
111
+ const DEFAULT_CORS_ORIGINS = '*';
112
+
107
113
  /** Representasi database untuk baris review, berdasarkan konfigurasi input. */
108
114
  function describeDatabase(cfg) {
109
115
  if (cfg.DB_TYPE === 'sqlite') return `sqlite @ ${cfg.DB_FILE}`;
110
116
  return `${cfg.DB_TYPE} @ ${cfg.DB_HOST}:${cfg.DB_PORT}/${cfg.DB_NAME}`;
111
117
  }
112
118
 
119
+ /**
120
+ * Normalisasi input DB_FILE SQLite dari user menjadi path yang lengkap.
121
+ *
122
+ * Aturan:
123
+ * - Tanpa komponen direktori (mis. "guestbook" atau "guestbook.db")
124
+ * → dimasukkan ke ./data/ → "./data/guestbook.db"
125
+ * - Dengan direktori custom (mis. "./dataku/guestbook")
126
+ * → path dihormati, hanya ekstensi yang ditambahkan → "./dataku/guestbook.db"
127
+ * - Ekstensi .db selalu ditambahkan jika belum ada.
128
+ */
129
+ function normalizeDbFilePath(input) {
130
+ let result = input.trim();
131
+ if (!result.endsWith('.db')) result += '.db';
132
+ const hasDir = result.includes('/') || result.includes('\\');
133
+ if (!hasDir) result = './data/' + result;
134
+ return result;
135
+ }
136
+
113
137
  /**
114
138
  * Fase input konfigurasi interaktif: LICENSE lalu tipe database beserta
115
139
  * atributnya. Mengembalikan objek cfg. `ask` di-inject agar dapat diuji.
@@ -139,7 +163,8 @@ async function collectConfig(ask) {
139
163
  console.log(' SQLite mode: DB_HOST, DB_PORT, DB_USER, DB_PASSWORD are ignored.');
140
164
  console.log(' The database file path is set in DB_NAME (via DB_FILE).');
141
165
  console.log('');
142
- cfg.DB_FILE = await askField('DB_FILE (.db file path)', DB_DEFAULTS.DB_FILE);
166
+ const dbFileRaw = await askField('DB_FILE (.db file path)', DB_DEFAULTS.DB_FILE);
167
+ cfg.DB_FILE = normalizeDbFilePath(dbFileRaw);
143
168
  } else {
144
169
  const d = dbDefaultsFor(cfg.DB_TYPE);
145
170
  cfg.DB_HOST = await askField('DB_HOST', d.DB_HOST);
@@ -149,6 +174,13 @@ async function collectConfig(ask) {
149
174
  cfg.DB_NAME = await askField('DB_NAME', d.DB_NAME);
150
175
  }
151
176
 
177
+ // 3) CORS — origin frontend yang diizinkan mengakses API dari browser.
178
+ // Default '*' (semua origin); isi daftar origin spesifik untuk production.
179
+ console.log('');
180
+ console.log(' CORS_ORIGINS: frontend origin(s) allowed to call the API');
181
+ console.log(" (comma-separated, or '*' for all origins).");
182
+ cfg.CORS_ORIGINS = await askField('CORS_ORIGINS', DEFAULT_CORS_ORIGINS);
183
+
152
184
  return cfg;
153
185
  }
154
186
 
@@ -158,6 +190,7 @@ function envValuesFromCfg(cfg) {
158
190
  LICENSE: cfg.LICENSE,
159
191
  DB_TYPE: cfg.DB_TYPE
160
192
  };
193
+ if (cfg.CORS_ORIGINS) v.CORS_ORIGINS = cfg.CORS_ORIGINS;
161
194
  if (cfg.DB_TYPE === 'sqlite') {
162
195
  // Untuk SQLite, path file disimpan pada DB_NAME (dikonsumsi runtime).
163
196
  v.DB_NAME = cfg.DB_FILE;
@@ -222,6 +255,7 @@ async function confirmWrite(ctx, ask) {
222
255
  console.log('====================================================');
223
256
  console.log(` License : ${maskLicense(ctx.cfg.LICENSE)}`);
224
257
  console.log(` Database : ${describeDatabase(ctx.cfg)}`);
258
+ console.log(` CORS : ${ctx.cfg.CORS_ORIGINS}`);
225
259
  const existsNote = ctx.exists ? ' (EXISTING — will be overwritten)' : '';
226
260
  console.log(` Target : config/${ctx.configFileName}${existsNote}`);
227
261
  console.log('');