@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.
- package/build-info.json +2 -2
- package/cli/consumer-deploy.js +1 -1
- package/cli/consumer.js +1 -1
- package/cli/designer.js +1 -1
- package/generators/cli/fast-track.js +230 -45
- package/generators/cli/init.js +37 -3
- package/generators/lib/templates/dashboard-catalog.js +1 -1
- package/generators/lib/templates/db-connection-env.js +1 -1
- package/generators/lib/templates/dbschema-catalog.js +1 -1
- package/generators/lib/templates/field-validation-catalog.js +1 -1
- package/generators/lib/templates/mysql-template.js +1 -1
- package/generators/lib/templates/oracle-template.js +1 -1
- package/generators/lib/templates/postgres-template.js +1 -1
- package/generators/lib/templates/query-declarative-catalog.js +1 -1
- package/generators/lib/templates/sqlite-template.js +1 -1
- package/integrity-manifest.json +18 -18
- package/package.json +2 -1
- package/scripts/verify-integrity.js +1 -1
- package/server.js +1 -1
- package/src/components/handlers/adjust_handler.js +1 -1
- package/src/components/handlers/audit_handler.js +1 -1
- package/src/components/handlers/delete_handler.js +1 -1
- package/src/components/handlers/export_handler.js +1 -1
- package/src/components/handlers/import_handler.js +1 -1
- package/src/components/handlers/insert_handler.js +1 -1
- package/src/components/handlers/update_handler.js +1 -1
- package/src/components/handlers/upload_handler.js +1 -1
- package/src/components/handlers/workflow_handler.js +1 -1
- package/src/components/integrations/webhook.js +1 -1
- package/src/consumers/baseConsumer.js +1 -1
- package/src/consumers/declarativeMapper.js +1 -1
- package/src/consumers/handlers/apiHandler.js +1 -1
- package/src/consumers/handlers/consoleHandler.js +1 -1
- package/src/consumers/handlers/databaseHandler.js +1 -1
- package/src/consumers/handlers/index.js +1 -1
- package/src/consumers/handlers/kafkaHandler.js +1 -1
- package/src/consumers/index.js +1 -1
- package/src/consumers/messageTransformer.js +1 -1
- package/src/consumers/validator.js +1 -1
- package/src/core/db/dialect/base-dialect.js +1 -1
- package/src/core/db/dialect/index.js +1 -1
- package/src/core/db/dialect/mysql-dialect.js +1 -1
- package/src/core/db/dialect/oracle-dialect.js +1 -1
- package/src/core/db/dialect/postgres-dialect.js +1 -1
- package/src/core/db/dialect/sqlite-dialect.js +1 -1
- package/src/core/db/flatten-helper.js +1 -1
- package/src/core/db/query-builder-error.js +1 -1
- package/src/core/db/query-builder.js +1 -1
- package/src/core/db/relation-helper.js +1 -1
- package/src/core/handlers/delete_handler.js +1 -1
- package/src/core/handlers/insert_handler.js +1 -1
- package/src/core/handlers/update_handler.js +1 -1
- package/src/core/models/base-model.js +1 -1
- package/src/core/utils/cache-manager.js +1 -1
- package/src/core/utils/component-engine.js +1 -1
- package/src/core/utils/context-builder.js +1 -1
- package/src/core/utils/datetime-formatter.js +1 -1
- package/src/core/utils/datetime-parser.js +1 -1
- package/src/core/utils/db.js +1 -1
- package/src/core/utils/logger.js +1 -1
- package/src/core/utils/payload-loader.js +1 -1
- package/src/core/utils/security-checks.js +1 -1
- package/src/middleware/body-options.js +1 -1
- package/src/middleware/cors.js +1 -1
- package/src/middleware/idempotency.js +1 -1
- package/src/middleware/rate-limiter.js +1 -1
- package/src/middleware/request-logger.js +1 -1
- package/src/middleware/security-headers.js +1 -1
- package/src/models/base-model-mysql.js +1 -1
- package/src/models/base-model-oracle.js +1 -1
- package/src/models/base-model-sqlite.js +1 -1
- package/src/models/base-model.js +1 -1
- package/src/pro/caching/redis-client.js +1 -1
- package/src/pro/caching/redis-helper.js +1 -1
- package/src/pro/consumers/baseConsumer.js +1 -1
- package/src/pro/consumers/declarativeMapper.js +1 -1
- package/src/pro/consumers/handlers/apiHandler.js +1 -1
- package/src/pro/consumers/handlers/consoleHandler.js +1 -1
- package/src/pro/consumers/handlers/databaseHandler.js +1 -1
- package/src/pro/consumers/handlers/index.js +1 -1
- package/src/pro/consumers/handlers/kafkaHandler.js +1 -1
- package/src/pro/consumers/index.js +1 -1
- package/src/pro/consumers/messageTransformer.js +1 -1
- package/src/pro/consumers/validator.js +1 -1
- package/src/pro/database/base-model-mysql.js +1 -1
- package/src/pro/database/base-model-oracle.js +1 -1
- package/src/pro/database/base-model-sqlite.js +1 -1
- package/src/pro/database/db-mysql.js +1 -1
- package/src/pro/database/db-oracle.js +1 -1
- package/src/pro/database/db-sqlite.js +1 -1
- package/src/pro/excel/excel-generator.js +1 -1
- package/src/pro/excel/excel-parser.js +1 -1
- package/src/pro/excel/export-service.js +1 -1
- package/src/pro/excel/export_handler.js +1 -1
- package/src/pro/excel/import-service.js +1 -1
- package/src/pro/excel/import-validator.js +1 -1
- package/src/pro/excel/import_handler.js +1 -1
- package/src/pro/excel/upsert-builder.js +1 -1
- package/src/pro/idgen/idgen-routes.js +1 -1
- package/src/pro/integrations/lookup-resolver.js +1 -1
- package/src/pro/integrations/upload-handler-v2.js +1 -1
- package/src/pro/integrations/upload-handler.js +1 -1
- package/src/pro/integrations/webhook.js +1 -1
- package/src/pro/locking/lock-routes.js +1 -1
- package/src/pro/locking/resource-lock-manager.js +1 -1
- package/src/pro/messaging/kafkaConsumerService.js +1 -1
- package/src/pro/messaging/kafkaService.js +1 -1
- package/src/pro/messaging/messagehubService.js +1 -1
- package/src/pro/messaging/rabbitmqService.js +1 -1
- package/src/pro/scheduler/job-manager.js +1 -1
- package/src/pro/scheduler/job-routes.js +1 -1
- package/src/pro/scheduler/job-validator.js +1 -1
- package/src/pro/storage/base-storage-provider.js +1 -1
- package/src/pro/storage/file-metadata-helper.js +1 -1
- package/src/pro/storage/index.js +1 -1
- package/src/pro/storage/local-storage-provider.js +1 -1
- package/src/pro/storage/s3-storage-provider.js +1 -1
- package/src/pro/storage/upload-cleanup-job.js +1 -1
- package/src/pro/storage/upload-cleanup-scheduler.js +1 -1
- package/src/pro/storage/upload-pending-tracker.js +1 -1
- package/src/pro/websocket/broadcast-helper.js +1 -1
- package/src/pro/websocket/index.js +1 -1
- package/src/pro/websocket/livesync-server.js +1 -1
- package/src/pro/websocket/ws-broadcaster.js +1 -1
- package/src/services/export-service.js +1 -1
- package/src/services/import-service.js +1 -1
- package/src/services/kafkaConsumerService.js +1 -1
- package/src/services/kafkaService.js +1 -1
- package/src/services/messagehubService.js +1 -1
- package/src/services/rabbitmqService.js +1 -1
- package/src/utils/cache-invalidation-registry.js +1 -1
- package/src/utils/cache-manager.js +1 -1
- package/src/utils/component-engine.js +1 -1
- package/src/utils/config-extractor.js +1 -1
- package/src/utils/consumerLogger.js +1 -1
- package/src/utils/context-builder.js +1 -1
- package/src/utils/dashboard-helpers.js +1 -1
- package/src/utils/dateHelper.js +1 -1
- package/src/utils/datetime-formatter.js +1 -1
- package/src/utils/datetime-parser.js +1 -1
- package/src/utils/db-bootstrap.js +1 -1
- package/src/utils/db-mysql.js +1 -1
- package/src/utils/db-oracle.js +1 -1
- package/src/utils/db-sqlite.js +1 -1
- package/src/utils/db.js +1 -1
- package/src/utils/demo-generator.js +1 -1
- package/src/utils/excel-generator.js +1 -1
- package/src/utils/excel-parser.js +1 -1
- package/src/utils/file-watcher.js +1 -1
- package/src/utils/id-generator.js +1 -1
- package/src/utils/idempotency-manager.js +1 -1
- package/src/utils/import-validator.js +1 -1
- package/src/utils/license-client.js +1 -1
- package/src/utils/lock-manager.js +1 -1
- package/src/utils/logger.js +1 -1
- package/src/utils/lookup-resolver.js +1 -1
- package/src/utils/payload-loader.js +1 -1
- package/src/utils/processor-response.js +1 -1
- package/src/utils/rabbitmq.js +1 -1
- package/src/utils/redis-client.js +1 -1
- package/src/utils/redis-helper.js +1 -1
- package/src/utils/request-scope.js +1 -1
- package/src/utils/security-checks.js +1 -1
- package/src/utils/service-resolver.js +1 -1
- package/src/utils/shutdown-coordinator.js +1 -1
- package/src/utils/soft-delete-dashboard-guard.js +1 -1
- package/src/utils/sql-table-extractor.js +1 -1
- package/src/utils/trusted-keys.js +1 -1
- package/src/utils/upload-handler.js +1 -1
- package/src/utils/upsert-builder.js +1 -1
- 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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 ${
|
|
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
|
-
|
|
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
|
-
|
|
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 "${
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
1601
|
-
await
|
|
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
|
|
1735
|
-
if (ctx.scope.backend)
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
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.
|
|
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
|
|
1940
|
+
await maybeRunServerAndFrontend(ctx, prompter.ask);
|
|
1756
1941
|
} else if (ctx.scope.backend) {
|
|
1757
|
-
await maybeRunServer(ctx, prompter.ask
|
|
1942
|
+
await maybeRunServer(ctx, prompter.ask);
|
|
1758
1943
|
}
|
|
1759
1944
|
} finally {
|
|
1760
1945
|
prompter.close();
|
package/generators/cli/init.js
CHANGED
|
@@ -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
|
|
13
|
-
* di-custom). Nilai input di-patch ke
|
|
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
|
-
|
|
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('');
|