@restforgejs/platform 5.1.16 → 5.1.20
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/generators/cli/catalog/dbschema.js +2 -1
- package/generators/cli/endpoint/list.js +264 -0
- package/generators/cli/fast-track.js +395 -37
- package/generators/cli/processor/create.js +7 -7
- package/generators/cli/processor/list.js +229 -0
- package/generators/lib/generators/dashboard-generator.js +5 -5
- package/generators/lib/payload/payload-runner.js +63 -0
- 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 +1 -1
- package/scripts/check-install.js +8 -8
- 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
|
@@ -33,6 +33,12 @@ const { spawnSync } = require('node:child_process');
|
|
|
33
33
|
// mengembalikan IR model dengan relasi ter-normalisasi (sumber FK yang andal).
|
|
34
34
|
const { loadSchemaPath } = require('../lib/dbschema-kit/loader');
|
|
35
35
|
|
|
36
|
+
// Pembaca .env (parse KEY=VALUE, preserve diabaikan) dan resolver default config.
|
|
37
|
+
// Dipakai agar nilai fast-track mengikuti file config yang sudah ada, bukan
|
|
38
|
+
// semata DEFAULTS hardcode.
|
|
39
|
+
const { readEnvFile } = require('../lib/utils/env-manager');
|
|
40
|
+
const { getDefaultConfig, resolveConfigFilePath } = require('../lib/utils/config-resolver');
|
|
41
|
+
|
|
36
42
|
// ---------------------------------------------------------------------------
|
|
37
43
|
// Default konfigurasi (selaras dengan restforge-playbook/fast-track.mjs)
|
|
38
44
|
// ---------------------------------------------------------------------------
|
|
@@ -162,7 +168,7 @@ function createPrompter() {
|
|
|
162
168
|
});
|
|
163
169
|
};
|
|
164
170
|
|
|
165
|
-
return { ask, close: () => rl.close() };
|
|
171
|
+
return { ask, close: () => rl.close(), rl };
|
|
166
172
|
}
|
|
167
173
|
|
|
168
174
|
/**
|
|
@@ -275,11 +281,70 @@ function checkDesigner() {
|
|
|
275
281
|
console.log(leader(' > restforge-designer', `${version || 'installed'} OK`, 32));
|
|
276
282
|
}
|
|
277
283
|
|
|
284
|
+
// ---------------------------------------------------------------------------
|
|
285
|
+
// Resolusi file config sumber nilai (mengikuti file config yang sudah ada)
|
|
286
|
+
// ---------------------------------------------------------------------------
|
|
287
|
+
|
|
288
|
+
/** Daftar nama file .env di folder config/ (non-rekursif). */
|
|
289
|
+
function listConfigEnvFiles(cwd) {
|
|
290
|
+
const dir = path.join(cwd, 'config');
|
|
291
|
+
if (!fs.existsSync(dir)) return [];
|
|
292
|
+
return fs.readdirSync(dir, { withFileTypes: true })
|
|
293
|
+
.filter((e) => e.isFile() && e.name.endsWith('.env'))
|
|
294
|
+
.map((e) => e.name);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Tentukan file config yang menjadi SUMBER nilai prompt fast-track, lalu baca
|
|
299
|
+
* nilainya. Prioritas:
|
|
300
|
+
* 1. --config eksplisit -> hormati nama itu.
|
|
301
|
+
* 2. Tepat 1 file .env di config/ -> pakai file itu.
|
|
302
|
+
* 3. >1 file .env di config/ -> pakai default (config get-default /
|
|
303
|
+
* .restforge/defaults.json); bila default belum di-set -> berhenti.
|
|
304
|
+
* 4. 0 file -> fallback DEFAULT_CONFIG_FILE tanpa
|
|
305
|
+
* pre-fill (init akan membuat file ini, perilaku lama).
|
|
306
|
+
*
|
|
307
|
+
* @returns {{ configName: string, fileCfg: Object }} fileCfg = nilai env
|
|
308
|
+
* terbaca ({} bila file belum ada).
|
|
309
|
+
*/
|
|
310
|
+
function resolveSourceConfig(cwd, explicitConfig) {
|
|
311
|
+
let configName;
|
|
312
|
+
|
|
313
|
+
if (explicitConfig) {
|
|
314
|
+
configName = explicitConfig;
|
|
315
|
+
} else {
|
|
316
|
+
const files = listConfigEnvFiles(cwd);
|
|
317
|
+
if (files.length === 1) {
|
|
318
|
+
configName = files[0];
|
|
319
|
+
} else if (files.length > 1) {
|
|
320
|
+
const def = getDefaultConfig(cwd);
|
|
321
|
+
if (!def) {
|
|
322
|
+
console.log('');
|
|
323
|
+
console.log(` ERROR: ${files.length} config files found in config/ but no default is set.`);
|
|
324
|
+
console.log(' Set which one fast-track should follow:');
|
|
325
|
+
console.log('');
|
|
326
|
+
console.log(' npx restforge config set-default --config=<file>');
|
|
327
|
+
console.log('');
|
|
328
|
+
console.log(' Or pass it explicitly: fast-track ... --config=<file>');
|
|
329
|
+
console.log('');
|
|
330
|
+
throw stop('multiple config files, no default set');
|
|
331
|
+
}
|
|
332
|
+
configName = def.config;
|
|
333
|
+
} else {
|
|
334
|
+
// 0 file: pertahankan perilaku lama (init akan membuat file ini).
|
|
335
|
+
return { configName: DEFAULT_CONFIG_FILE, fileCfg: {} };
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const { data } = readEnvFile(resolveConfigFilePath(configName, cwd));
|
|
340
|
+
return { configName, fileCfg: data || {} };
|
|
341
|
+
}
|
|
342
|
+
|
|
278
343
|
// ---------------------------------------------------------------------------
|
|
279
344
|
// Fase input konfigurasi (LICENSE + database), mengikuti fast-track.mjs
|
|
280
345
|
// ---------------------------------------------------------------------------
|
|
281
346
|
|
|
282
|
-
async function collectConfig(args, ask) {
|
|
347
|
+
async function collectConfig(args, ask, fileCfg = {}) {
|
|
283
348
|
console.log('');
|
|
284
349
|
console.log(rule('='));
|
|
285
350
|
console.log(' RESTForge Fast-Track — Configuration');
|
|
@@ -295,37 +360,39 @@ async function collectConfig(args, ask) {
|
|
|
295
360
|
|
|
296
361
|
const cfg = {};
|
|
297
362
|
|
|
298
|
-
// LICENSE: bila --license diberikan
|
|
299
|
-
|
|
363
|
+
// LICENSE: bila --license diberikan dipakai sebagai default; jika tidak,
|
|
364
|
+
// ikuti file config bila ada, baru jatuh ke DEFAULTS.
|
|
365
|
+
const licenseDefault = args.license || fileCfg.LICENSE || DEFAULTS.LICENSE;
|
|
300
366
|
cfg.LICENSE = await askField('LICENSE', licenseDefault);
|
|
301
367
|
|
|
302
368
|
// Alamat & port. Label user-facing REST_API_PORT, namun TETAP disimpan dan
|
|
303
369
|
// ditulis ke db-connection.env sebagai SERVER_PORT (key yang dikonsumsi
|
|
304
370
|
// `restforge serve`). WEB_SERVER_PORT adalah port frontend (`npx serve`),
|
|
305
|
-
// dialirkan via `payload migrate --port` — tidak masuk runtime config
|
|
306
|
-
|
|
307
|
-
cfg.
|
|
371
|
+
// dialirkan via `payload migrate --port` — tidak masuk runtime config, jadi
|
|
372
|
+
// selalu dari DEFAULTS (tidak tersimpan di file env).
|
|
373
|
+
cfg.SERVER_ADDRESS = await askField('SERVER_ADDRESS', fileCfg.SERVER_ADDRESS || DEFAULTS.SERVER_ADDRESS);
|
|
374
|
+
cfg.SERVER_PORT = await askField('REST_API_PORT', fileCfg.SERVER_PORT || DEFAULTS.SERVER_PORT);
|
|
308
375
|
cfg.WEB_SERVER_PORT = await askField('WEB_SERVER_PORT', DEFAULTS.WEB_SERVER_PORT);
|
|
309
376
|
|
|
310
377
|
// DB_TYPE menentukan atribut yang diminta berikutnya.
|
|
311
378
|
console.log('');
|
|
312
379
|
console.log(' Available DB_TYPE: postgresql, mysql, oracle, sqlite');
|
|
313
|
-
cfg.DB_TYPE = (await askField('DB_TYPE', DEFAULTS.DB_TYPE)).toLowerCase();
|
|
380
|
+
cfg.DB_TYPE = (await askField('DB_TYPE', fileCfg.DB_TYPE || DEFAULTS.DB_TYPE)).toLowerCase();
|
|
314
381
|
|
|
315
382
|
if (cfg.DB_TYPE === 'sqlite') {
|
|
316
383
|
console.log('');
|
|
317
384
|
console.log(' SQLite mode: DB_HOST, DB_PORT, DB_USER, DB_PASSWORD are ignored.');
|
|
318
385
|
console.log(' The database file path is set in DB_FILE.');
|
|
319
386
|
console.log('');
|
|
320
|
-
cfg.DB_FILE = await askField('DB_FILE (.db file path)', DEFAULTS.DB_FILE);
|
|
387
|
+
cfg.DB_FILE = await askField('DB_FILE (.db file path)', fileCfg.DB_FILE || DEFAULTS.DB_FILE);
|
|
321
388
|
cfg.DB_NAME = cfg.DB_FILE;
|
|
322
389
|
} else {
|
|
323
390
|
const dbDef = DB_TYPE_DEFAULTS[cfg.DB_TYPE] || {};
|
|
324
|
-
cfg.DB_HOST = await askField('DB_HOST', DEFAULTS.DB_HOST);
|
|
325
|
-
cfg.DB_PORT = await askField('DB_PORT', dbDef.DB_PORT || DEFAULTS.DB_PORT);
|
|
326
|
-
cfg.DB_USER = await askField('DB_USER', dbDef.DB_USER || DEFAULTS.DB_USER);
|
|
327
|
-
cfg.DB_PASSWORD = await askField('DB_PASSWORD', DEFAULTS.DB_PASSWORD);
|
|
328
|
-
cfg.DB_NAME = await askField('DB_NAME', dbDef.DB_NAME || DEFAULTS.DB_NAME);
|
|
391
|
+
cfg.DB_HOST = await askField('DB_HOST', fileCfg.DB_HOST || DEFAULTS.DB_HOST);
|
|
392
|
+
cfg.DB_PORT = await askField('DB_PORT', fileCfg.DB_PORT || dbDef.DB_PORT || DEFAULTS.DB_PORT);
|
|
393
|
+
cfg.DB_USER = await askField('DB_USER', fileCfg.DB_USER || dbDef.DB_USER || DEFAULTS.DB_USER);
|
|
394
|
+
cfg.DB_PASSWORD = await askField('DB_PASSWORD', fileCfg.DB_PASSWORD || DEFAULTS.DB_PASSWORD);
|
|
395
|
+
cfg.DB_NAME = await askField('DB_NAME', fileCfg.DB_NAME || dbDef.DB_NAME || DEFAULTS.DB_NAME);
|
|
329
396
|
}
|
|
330
397
|
|
|
331
398
|
return cfg;
|
|
@@ -335,21 +402,277 @@ async function collectConfig(args, ask) {
|
|
|
335
402
|
// Fase pemilihan scope generate (menu)
|
|
336
403
|
// ---------------------------------------------------------------------------
|
|
337
404
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
405
|
+
// Item menu scope (teks tampil sama persis dengan menu angka lama agar perubahan
|
|
406
|
+
// hanya pada CARA memilih, bukan label). `key` memetakan ke SCOPES.
|
|
407
|
+
const SCOPE_MENU = [
|
|
408
|
+
{ key: '1', text: 'Generate REST API Only' },
|
|
409
|
+
{ key: '2', text: 'Generate REST API with Frontend Application' }
|
|
410
|
+
];
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Selektor menu interaktif gaya CLI installer (create-next-app): tombol panah
|
|
414
|
+
* ↑/↓ menggeser sorotan, Enter mengeksekusi pilihan. HANYA untuk TTY interaktif;
|
|
415
|
+
* pemanggil wajib menyediakan fallback non-TTY (lihat selectScope).
|
|
416
|
+
*
|
|
417
|
+
* Handoff stdin: `createPrompter` memegang readline di process.stdin. Selama menu
|
|
418
|
+
* aktif, readline di-pause DAN listener `keypress`-nya dilepas sementara (disimpan
|
|
419
|
+
* lalu dipulihkan) agar tidak ada line-editing yang ikut bereaksi terhadap tombol.
|
|
420
|
+
* Setelah pilihan dibuat, raw mode, listener, dan readline dikembalikan ke semula
|
|
421
|
+
* sehingga prompt-prompt berikutnya tetap berfungsi normal.
|
|
422
|
+
*
|
|
423
|
+
* @param {Object} opts
|
|
424
|
+
* @param {string} opts.title - Judul menu
|
|
425
|
+
* @param {string[]} opts.items - Teks tiap opsi
|
|
426
|
+
* @param {number} opts.initialIndex - Indeks sorotan awal
|
|
427
|
+
* @param {Object} opts.prompter - Hasil createPrompter (untuk pause/resume rl)
|
|
428
|
+
* @returns {Promise<number>} indeks opsi yang dipilih
|
|
429
|
+
*/
|
|
430
|
+
function arrowSelect({ title, items, initialIndex, prompter }) {
|
|
431
|
+
return new Promise((resolve) => {
|
|
432
|
+
const stdin = process.stdin;
|
|
433
|
+
const rl = prompter && prompter.rl;
|
|
434
|
+
let index = Math.max(0, Math.min(initialIndex || 0, items.length - 1));
|
|
435
|
+
|
|
436
|
+
// Suspend prompter: pause readline + lepas listener keypress-nya agar hanya
|
|
437
|
+
// handler menu yang aktif (mencegah echo/line-edit ganda).
|
|
438
|
+
if (rl && typeof rl.pause === 'function') rl.pause();
|
|
439
|
+
readline.emitKeypressEvents(stdin);
|
|
440
|
+
const savedKeypress = stdin.listeners('keypress').slice();
|
|
441
|
+
for (const l of savedKeypress) stdin.removeListener('keypress', l);
|
|
442
|
+
|
|
443
|
+
const prevRaw = stdin.isRaw === true;
|
|
444
|
+
if (typeof stdin.setRawMode === 'function') stdin.setRawMode(true);
|
|
445
|
+
stdin.resume();
|
|
446
|
+
|
|
447
|
+
console.log('');
|
|
448
|
+
console.log(` ${title}`);
|
|
449
|
+
console.log('');
|
|
450
|
+
|
|
451
|
+
const renderLines = () => {
|
|
452
|
+
for (let i = 0; i < items.length; i++) {
|
|
453
|
+
const active = i === index;
|
|
454
|
+
const pointer = active ? '>' : ' ';
|
|
455
|
+
const label = active ? `\x1b[36m${items[i]}\x1b[0m` : items[i];
|
|
456
|
+
process.stdout.write(`\x1b[2K ${pointer} ${label}\n`);
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
const moveCursorUp = () => process.stdout.write(`\x1b[${items.length}A`);
|
|
460
|
+
|
|
461
|
+
renderLines();
|
|
462
|
+
|
|
463
|
+
const cleanup = () => {
|
|
464
|
+
stdin.removeListener('keypress', onKeypress);
|
|
465
|
+
if (typeof stdin.setRawMode === 'function') stdin.setRawMode(prevRaw);
|
|
466
|
+
for (const l of savedKeypress) stdin.on('keypress', l);
|
|
467
|
+
if (rl && typeof rl.resume === 'function') rl.resume();
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
const onKeypress = (_str, key) => {
|
|
471
|
+
if (!key) return;
|
|
472
|
+
if (key.name === 'up') {
|
|
473
|
+
index = (index - 1 + items.length) % items.length;
|
|
474
|
+
moveCursorUp();
|
|
475
|
+
renderLines();
|
|
476
|
+
} else if (key.name === 'down') {
|
|
477
|
+
index = (index + 1) % items.length;
|
|
478
|
+
moveCursorUp();
|
|
479
|
+
renderLines();
|
|
480
|
+
} else if (key.name === 'return' || key.name === 'enter') {
|
|
481
|
+
cleanup();
|
|
482
|
+
process.stdout.write('\n');
|
|
483
|
+
resolve(index);
|
|
484
|
+
} else if (key.ctrl && key.name === 'c') {
|
|
485
|
+
cleanup();
|
|
486
|
+
process.stdout.write('\n');
|
|
487
|
+
process.exit(130); // konvensi SIGINT
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
stdin.on('keypress', onKeypress);
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
async function selectScope(prompter) {
|
|
496
|
+
const ask = prompter.ask;
|
|
497
|
+
|
|
498
|
+
// Fallback non-TTY (input di-pipe: smoke/CI/transcript): pertahankan PERSIS
|
|
499
|
+
// prompt angka lama. Selektor panah butuh raw TTY mode yang tidak tersedia di
|
|
500
|
+
// sini, jadi fallback menjaga alur non-interaktif tetap berfungsi byte-identik.
|
|
501
|
+
if (!process.stdin.isTTY) {
|
|
502
|
+
console.log('');
|
|
503
|
+
console.log(' Select what to generate:');
|
|
504
|
+
console.log('');
|
|
505
|
+
for (const m of SCOPE_MENU) console.log(` ${m.key}. ${m.text}`);
|
|
506
|
+
console.log('');
|
|
507
|
+
|
|
508
|
+
let scope = null;
|
|
509
|
+
while (!scope) {
|
|
510
|
+
const choice = (await ask(` Choice (1-2) [${DEFAULT_SCOPE}]: `)).trim() || DEFAULT_SCOPE;
|
|
511
|
+
scope = SCOPES[choice];
|
|
512
|
+
if (!scope) console.log(' Invalid choice. Enter 1 or 2.');
|
|
513
|
+
}
|
|
514
|
+
return scope;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// TTY interaktif: selektor panah ↑/↓ + Enter.
|
|
518
|
+
const initialIndex = SCOPE_MENU.findIndex((m) => m.key === DEFAULT_SCOPE);
|
|
519
|
+
const chosen = await arrowSelect({
|
|
520
|
+
title: 'Select what to generate:',
|
|
521
|
+
items: SCOPE_MENU.map((m) => m.text),
|
|
522
|
+
initialIndex: initialIndex >= 0 ? initialIndex : 0,
|
|
523
|
+
prompter
|
|
524
|
+
});
|
|
525
|
+
return SCOPES[SCOPE_MENU[chosen].key];
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// ---------------------------------------------------------------------------
|
|
529
|
+
// Fase pemilihan plugin designer + toggle authentication
|
|
530
|
+
// ---------------------------------------------------------------------------
|
|
531
|
+
|
|
532
|
+
// Plugin default bila pilihan tidak tersedia / parsing list gagal.
|
|
533
|
+
const DESIGNER_DEFAULT_PLUGIN = 'vanilla-js-basic';
|
|
534
|
+
|
|
535
|
+
// Default konfigurasi auth, selaras dengan `restforge-designer init`
|
|
536
|
+
// (mod.rs: auth_app_code/auth_api_url/idle_timeout). Dipakai saat user memilih
|
|
537
|
+
// mengaktifkan authentication pada plugin yang mendukung (Auth != No).
|
|
538
|
+
const DESIGNER_AUTH_DEFAULTS = {
|
|
539
|
+
appCode: 'K5BK0H3ATT',
|
|
540
|
+
authApiUrl: 'https://restforge.dev/api/auth',
|
|
541
|
+
idleTimeoutMinutes: 30
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
const ANSI_RE = /\x1b\[[0-9;]*m/g;
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Jalankan `restforge-designer plugins list` dan kembalikan stdout mentah, atau
|
|
548
|
+
* null bila gagal (binary tidak ada / exit non-zero). Tidak butuh aktivasi license
|
|
549
|
+
* (list hanya membaca metadata plugin built-in).
|
|
550
|
+
*/
|
|
551
|
+
function runDesignerPluginsList() {
|
|
552
|
+
const r = spawnSync('cmd', ['/S', '/C', 'restforge-designer plugins list'], { encoding: 'utf8' });
|
|
553
|
+
if (r.error || r.status !== 0) return null;
|
|
554
|
+
return r.stdout || '';
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Parse output tabel `plugins list` (comfy-table UTF8_FULL) menjadi array
|
|
559
|
+
* `{ id, auth }`. Baris data ditandai border luar `│` + separator kolom `┆`;
|
|
560
|
+
* baris pemisah (├ ╞ ┌ └) dan header (`ID`) dilewati. ANSI di-strip dulu. Kolom
|
|
561
|
+
* Auth (indeks 3) menentukan apakah plugin mendukung authentication.
|
|
562
|
+
*
|
|
563
|
+
* Catatan: tidak ada output JSON di designer, jadi parsing tabel adalah satu-satunya
|
|
564
|
+
* antarmuka. Parser defensif: bila 0 baris terbaca, pemanggil fallback ke default.
|
|
565
|
+
*/
|
|
566
|
+
function parseDesignerPlugins(stdout) {
|
|
567
|
+
const out = [];
|
|
568
|
+
const seen = new Set();
|
|
569
|
+
for (const rawLine of String(stdout || '').split(/\r?\n/)) {
|
|
570
|
+
const line = rawLine.replace(ANSI_RE, '');
|
|
571
|
+
// Baris data wajib punya border luar `│`; separator inner bisa `┆` (UTF8_FULL)
|
|
572
|
+
// atau `│`. Baris pemisah (├ ┌ └ ╞) tidak mengandung `│`, jadi otomatis lewat.
|
|
573
|
+
if (!line.includes('│')) continue;
|
|
574
|
+
const cells = line.split(/[│┆]/).map((c) => c.trim()).filter((c) => c.length > 0);
|
|
575
|
+
// Minimal 4 kolom (ID, Name, Version, Auth). Baris wrap continuation berkolom
|
|
576
|
+
// sedikit tersaring di sini.
|
|
577
|
+
if (cells.length < 4) continue;
|
|
578
|
+
const id = cells[0];
|
|
579
|
+
if (id === 'ID' || seen.has(id)) continue;
|
|
580
|
+
seen.add(id);
|
|
581
|
+
out.push({ id, auth: cells[3] || '' });
|
|
582
|
+
}
|
|
583
|
+
return out;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/** Plugin mendukung authentication bila kolom Auth bukan kosong dan bukan "No". */
|
|
587
|
+
function pluginHasAuth(auth) {
|
|
588
|
+
const v = String(auth || '').trim().toLowerCase();
|
|
589
|
+
return v !== '' && v !== 'no';
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Pilih plugin designer (sebelum generate) lalu tentukan toggle auth. Menetapkan
|
|
594
|
+
* `ctx.plugin` dan `ctx.authEnabled`. Menampilkan tabel `plugins list` asli sebagai
|
|
595
|
+
* konteks, lalu selektor panah (TTY) atau prompt angka (non-TTY). Untuk plugin yang
|
|
596
|
+
* mendukung auth (mis. vanilla-js-auth/custom), tanyakan apakah auth diaktifkan;
|
|
597
|
+
* vanilla-js-basic (Auth No) langsung tanpa pertanyaan.
|
|
598
|
+
*
|
|
599
|
+
* Fallback aman: bila list gagal di-parse, pakai DESIGNER_DEFAULT_PLUGIN tanpa auth.
|
|
600
|
+
*/
|
|
601
|
+
async function selectDesignerPlugin(ctx, prompter) {
|
|
602
|
+
const raw = runDesignerPluginsList();
|
|
603
|
+
const plugins = raw ? parseDesignerPlugins(raw) : [];
|
|
604
|
+
|
|
344
605
|
console.log('');
|
|
606
|
+
if (raw && raw.trim()) console.log(raw.trimEnd());
|
|
345
607
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
608
|
+
if (plugins.length === 0) {
|
|
609
|
+
console.log(` (could not read plugin list; using default '${DESIGNER_DEFAULT_PLUGIN}')`);
|
|
610
|
+
ctx.plugin = DESIGNER_DEFAULT_PLUGIN;
|
|
611
|
+
ctx.authEnabled = false;
|
|
612
|
+
return;
|
|
351
613
|
}
|
|
352
|
-
|
|
614
|
+
|
|
615
|
+
const defaultIdx = Math.max(0, plugins.findIndex((p) => p.id === DESIGNER_DEFAULT_PLUGIN));
|
|
616
|
+
const items = plugins.map((p) => `${p.id} (auth: ${p.auth || 'No'})`);
|
|
617
|
+
|
|
618
|
+
let chosenIdx;
|
|
619
|
+
if (process.stdin.isTTY) {
|
|
620
|
+
chosenIdx = await arrowSelect({
|
|
621
|
+
title: 'Select a designer plugin:',
|
|
622
|
+
items,
|
|
623
|
+
initialIndex: defaultIdx,
|
|
624
|
+
prompter
|
|
625
|
+
});
|
|
626
|
+
} else {
|
|
627
|
+
console.log('');
|
|
628
|
+
console.log(' Select a designer plugin:');
|
|
629
|
+
console.log('');
|
|
630
|
+
items.forEach((it, i) => console.log(` ${i + 1}. ${it}`));
|
|
631
|
+
console.log('');
|
|
632
|
+
const def = String(defaultIdx + 1);
|
|
633
|
+
chosenIdx = null;
|
|
634
|
+
while (chosenIdx === null) {
|
|
635
|
+
const c = (await prompter.ask(` Choice (1-${plugins.length}) [${def}]: `)).trim() || def;
|
|
636
|
+
const idx = parseInt(c, 10) - 1;
|
|
637
|
+
if (idx >= 0 && idx < plugins.length) chosenIdx = idx;
|
|
638
|
+
else console.log(` Invalid choice. Enter 1-${plugins.length}.`);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
const chosen = plugins[chosenIdx];
|
|
643
|
+
ctx.plugin = chosen.id;
|
|
644
|
+
|
|
645
|
+
if (pluginHasAuth(chosen.auth)) {
|
|
646
|
+
const ans = (await prompter.ask(` Use authentication for '${chosen.id}'? (Y/n): `)).trim().toLowerCase();
|
|
647
|
+
ctx.authEnabled = !(ans === 'n' || ans === 'no');
|
|
648
|
+
} else {
|
|
649
|
+
ctx.authEnabled = false;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Suntikkan blok auth ke payload agregator frontend agar plugin auth (deteksi via
|
|
655
|
+
* `payload.auth.is_some()`) men-generate aplikasi dengan authentication. Bentuk blok
|
|
656
|
+
* mengikuti `restforge-designer init` (init.rs): top-level `auth` + `appConfig.authAppCode`.
|
|
657
|
+
* Mengembalikan true bila berhasil ditulis.
|
|
658
|
+
*/
|
|
659
|
+
function injectDesignerAuth(aggregatorPath) {
|
|
660
|
+
if (!fs.existsSync(aggregatorPath)) return false;
|
|
661
|
+
let payload;
|
|
662
|
+
try {
|
|
663
|
+
payload = JSON.parse(fs.readFileSync(aggregatorPath, 'utf8'));
|
|
664
|
+
} catch (_err) {
|
|
665
|
+
return false;
|
|
666
|
+
}
|
|
667
|
+
if (!payload.appConfig || typeof payload.appConfig !== 'object') payload.appConfig = {};
|
|
668
|
+
payload.appConfig.authAppCode = DESIGNER_AUTH_DEFAULTS.appCode;
|
|
669
|
+
payload.auth = {
|
|
670
|
+
appCode: DESIGNER_AUTH_DEFAULTS.appCode,
|
|
671
|
+
authApiUrl: DESIGNER_AUTH_DEFAULTS.authApiUrl,
|
|
672
|
+
idleTimeoutMinutes: DESIGNER_AUTH_DEFAULTS.idleTimeoutMinutes
|
|
673
|
+
};
|
|
674
|
+
fs.writeFileSync(aggregatorPath, JSON.stringify(payload, null, 2));
|
|
675
|
+
return true;
|
|
353
676
|
}
|
|
354
677
|
|
|
355
678
|
// ---------------------------------------------------------------------------
|
|
@@ -370,6 +693,9 @@ async function confirmDefaultMode(ctx, ask) {
|
|
|
370
693
|
console.log(` REST API : ${ctx.cfg.SERVER_ADDRESS}:${ctx.cfg.SERVER_PORT} (SERVER_PORT)`);
|
|
371
694
|
console.log(` Web server : ${ctx.cfg.SERVER_ADDRESS}:${ctx.cfg.WEB_SERVER_PORT}`);
|
|
372
695
|
console.log(` Database : ${describeDatabase(ctx.cfg)}`);
|
|
696
|
+
if (ctx.scope.frontend) {
|
|
697
|
+
console.log(` Plugin : ${ctx.plugin || DESIGNER_DEFAULT_PLUGIN}${ctx.authEnabled ? ' (auth enabled)' : ''}`);
|
|
698
|
+
}
|
|
373
699
|
console.log(' Mode : sync (use --overwrite to drop & regenerate)');
|
|
374
700
|
console.log('');
|
|
375
701
|
const answer = (await ask(' Continue? (Y/n): ')).trim().toLowerCase();
|
|
@@ -390,6 +716,9 @@ async function confirmOverwriteMode(ctx, ask) {
|
|
|
390
716
|
console.log(` Generate : ${ctx.scope.label}`);
|
|
391
717
|
console.log(` License : ${maskLicense(ctx.cfg.LICENSE)}`);
|
|
392
718
|
console.log(` Database : ${describeDatabase(ctx.cfg)}`);
|
|
719
|
+
if (ctx.scope.frontend) {
|
|
720
|
+
console.log(` Plugin : ${ctx.plugin || DESIGNER_DEFAULT_PLUGIN}${ctx.authEnabled ? ' (auth enabled)' : ''}`);
|
|
721
|
+
}
|
|
393
722
|
console.log('');
|
|
394
723
|
console.log(' The following actions will run:');
|
|
395
724
|
if (ctx.scope.backend) {
|
|
@@ -682,6 +1011,9 @@ function runFrontendPipeline(ctx) {
|
|
|
682
1011
|
const cfgArg = `--config=${ctx.configFlag}`;
|
|
683
1012
|
const frontendDir = path.join(ctx.cwd, 'frontend');
|
|
684
1013
|
const appCode = ctx.project;
|
|
1014
|
+
// Plugin terpilih (default basic bila selectDesignerPlugin tidak dijalankan).
|
|
1015
|
+
const plugin = ctx.plugin || DESIGNER_DEFAULT_PLUGIN;
|
|
1016
|
+
const pluginArg = `--plugin=${plugin}`;
|
|
685
1017
|
|
|
686
1018
|
phase('[F1/3] Migrate RDF -> UDF (per tabel, agregator di-akumulasi)');
|
|
687
1019
|
fs.mkdirSync(path.join(frontendDir, 'payload'), { recursive: true });
|
|
@@ -693,7 +1025,17 @@ function runFrontendPipeline(ctx) {
|
|
|
693
1025
|
console.log(` (skip ${t.kebab}: auto-discovered via JOIN from child)`);
|
|
694
1026
|
continue;
|
|
695
1027
|
}
|
|
696
|
-
run(`npx restforge payload migrate --project=${ctx.project} --name=${t.kebab}.json --output=frontend/payload ${cfgArg} --port=${ctx.cfg.WEB_SERVER_PORT} --overwrite`, ctx.cwd);
|
|
1028
|
+
run(`npx restforge payload migrate --project=${ctx.project} --name=${t.kebab}.json --output=frontend/payload ${cfgArg} --port=${ctx.cfg.WEB_SERVER_PORT} ${pluginArg} --overwrite`, ctx.cwd);
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
// Auth: suntik blok auth ke agregator sebelum generate bila user mengaktifkannya.
|
|
1032
|
+
// Plugin auth mendeteksi mode lewat keberadaan blok `auth` di payload (init.rs).
|
|
1033
|
+
if (ctx.authEnabled) {
|
|
1034
|
+
const aggregatorPath = path.join(frontendDir, 'payload', `${appCode}.json`);
|
|
1035
|
+
const ok = injectDesignerAuth(aggregatorPath);
|
|
1036
|
+
console.log(ok
|
|
1037
|
+
? ` ~ authentication enabled in ${appCode}.json (authAppCode=${DESIGNER_AUTH_DEFAULTS.appCode})`
|
|
1038
|
+
: ` [WARN] could not inject auth block into ${aggregatorPath}; app will generate without auth`);
|
|
697
1039
|
}
|
|
698
1040
|
|
|
699
1041
|
phase('[F2/3] Activate restforge-designer');
|
|
@@ -702,7 +1044,7 @@ function runFrontendPipeline(ctx) {
|
|
|
702
1044
|
phase('[F3/3] Generate frontend application');
|
|
703
1045
|
// Hapus index.html lama agar landing page diregenerasi sesuai set page terbaru.
|
|
704
1046
|
run(`if exist apps\\${ctx.project}\\index.html del /Q apps\\${ctx.project}\\index.html`, frontendDir, { allowNonZero: true });
|
|
705
|
-
run(`restforge-designer generate --payload=payload/${appCode}.json --output=./apps/${ctx.project} --overwrite`, frontendDir);
|
|
1047
|
+
run(`restforge-designer generate --payload=payload/${appCode}.json --output=./apps/${ctx.project} ${pluginArg} --overwrite`, frontendDir);
|
|
706
1048
|
}
|
|
707
1049
|
|
|
708
1050
|
/**
|
|
@@ -859,8 +1201,8 @@ module.exports = {
|
|
|
859
1201
|
config: {
|
|
860
1202
|
type: 'string',
|
|
861
1203
|
required: false,
|
|
862
|
-
default:
|
|
863
|
-
description: 'Nama file env target di folder config
|
|
1204
|
+
default: null,
|
|
1205
|
+
description: 'Nama file env target di folder config/. Bila kosong, fast-track mengikuti file config yang ada: 1 file dipakai langsung, >1 file pakai config get-default'
|
|
864
1206
|
},
|
|
865
1207
|
license: {
|
|
866
1208
|
type: 'string',
|
|
@@ -887,11 +1229,16 @@ module.exports = {
|
|
|
887
1229
|
|
|
888
1230
|
const prompter = createPrompter();
|
|
889
1231
|
try {
|
|
1232
|
+
// 0) Resolusi file config sumber: tentukan file mana yang diikuti dan
|
|
1233
|
+
// baca nilainya untuk pre-fill default prompt.
|
|
1234
|
+
const { configName, fileCfg } = resolveSourceConfig(cwd, args.config);
|
|
1235
|
+
|
|
890
1236
|
// 1) Input konfigurasi (LICENSE + database), gaya fast-track.mjs.
|
|
891
|
-
|
|
1237
|
+
// Default tiap field mengikuti fileCfg bila tersedia.
|
|
1238
|
+
const cfg = await collectConfig(args, prompter.ask, fileCfg);
|
|
892
1239
|
|
|
893
1240
|
// 2) Menu pemilihan scope generate (REST API / frontend / all).
|
|
894
|
-
const scope = await selectScope(prompter
|
|
1241
|
+
const scope = await selectScope(prompter);
|
|
895
1242
|
|
|
896
1243
|
// 3) Preflight designer hanya bila scope mencakup frontend.
|
|
897
1244
|
if (scope.frontend) {
|
|
@@ -902,7 +1249,7 @@ module.exports = {
|
|
|
902
1249
|
cwd,
|
|
903
1250
|
project: args.project,
|
|
904
1251
|
schemaFlag: args['schema-path'],
|
|
905
|
-
configFlag:
|
|
1252
|
+
configFlag: configName,
|
|
906
1253
|
tables,
|
|
907
1254
|
cfg,
|
|
908
1255
|
scope,
|
|
@@ -929,14 +1276,21 @@ module.exports = {
|
|
|
929
1276
|
}
|
|
930
1277
|
}
|
|
931
1278
|
|
|
932
|
-
// 4)
|
|
1279
|
+
// 4) Pemilihan plugin designer + toggle auth (hanya bila frontend).
|
|
1280
|
+
// Sebelum preview agar pilihan ikut tampil di ringkasan & dipakai
|
|
1281
|
+
// pipeline frontend (payload migrate --plugin + generate --plugin).
|
|
1282
|
+
if (ctx.scope.frontend) {
|
|
1283
|
+
await selectDesignerPlugin(ctx, prompter);
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
// 5) Preview + konfirmasi (sesuai mode).
|
|
933
1287
|
if (ctx.overwrite) {
|
|
934
1288
|
await confirmOverwriteMode(ctx, prompter.ask);
|
|
935
1289
|
} else {
|
|
936
1290
|
await confirmDefaultMode(ctx, prompter.ask);
|
|
937
1291
|
}
|
|
938
1292
|
|
|
939
|
-
//
|
|
1293
|
+
// 6) Eksekusi sesuai scope.
|
|
940
1294
|
if (ctx.scope.backend) {
|
|
941
1295
|
runBackendPipeline(ctx);
|
|
942
1296
|
// Launcher start REST API mandiri (sesuai OS), di folder kerja.
|
|
@@ -948,7 +1302,7 @@ module.exports = {
|
|
|
948
1302
|
|
|
949
1303
|
printFinalSummary(ctx);
|
|
950
1304
|
|
|
951
|
-
//
|
|
1305
|
+
// 7) Tawarkan menjalankan service: runtime server (backend) lalu
|
|
952
1306
|
// aplikasi frontend, masing-masing di window CMD baru.
|
|
953
1307
|
if (ctx.scope.backend) {
|
|
954
1308
|
await maybeRunServer(ctx, prompter.ask);
|
|
@@ -966,5 +1320,9 @@ module.exports = {
|
|
|
966
1320
|
// terpisah tanpa menjalankan pipeline penuh. Pada penggunaan CLI normal env ini
|
|
967
1321
|
// tidak di-set sehingga export tetap berupa contract murni.
|
|
968
1322
|
if (process.env.FASTTRACK_TEST === '1') {
|
|
969
|
-
module.exports.__test = {
|
|
1323
|
+
module.exports.__test = {
|
|
1324
|
+
loadModels, buildTableEntries, fkColumnsForEntry, tableToKebab,
|
|
1325
|
+
parseDesignerPlugins, pluginHasAuth, injectDesignerAuth,
|
|
1326
|
+
DESIGNER_DEFAULT_PLUGIN, DESIGNER_AUTH_DEFAULTS
|
|
1327
|
+
};
|
|
970
1328
|
}
|
|
@@ -403,7 +403,7 @@ function createEndpointFile(moduleDir, moduleName, endpointName, payload) {
|
|
|
403
403
|
|
|
404
404
|
const endpointExists = fs.existsSync(endpointFilePath);
|
|
405
405
|
if (endpointExists) {
|
|
406
|
-
console.log(` [UPDATE] ${endpointName}.js
|
|
406
|
+
console.log(` [UPDATE] ${endpointName}.js — endpoint router di-regenerate.`);
|
|
407
407
|
}
|
|
408
408
|
|
|
409
409
|
const importProcessors = payload.processor.map(proc => {
|
|
@@ -438,7 +438,7 @@ function createEndpointFile(moduleDir, moduleName, endpointName, payload) {
|
|
|
438
438
|
const reason = isMutationMethod
|
|
439
439
|
? `method ${method.toUpperCase()} is a mutation operation`
|
|
440
440
|
: `SQL contains mutation statement`;
|
|
441
|
-
console.log(` [WARN] cache.enabled ignored for processor "${proc.name}"
|
|
441
|
+
console.log(` [WARN] cache.enabled ignored for processor "${proc.name}" — ${reason}. Cache only applies to read (SELECT) operations.`);
|
|
442
442
|
hasCache = false;
|
|
443
443
|
}
|
|
444
444
|
}
|
|
@@ -523,7 +523,7 @@ module.exports = router;`;
|
|
|
523
523
|
|
|
524
524
|
fs.writeFileSync(endpointFilePath, endpointContent);
|
|
525
525
|
if (!endpointExists) {
|
|
526
|
-
console.log(` [NEW] ${endpointName}.js
|
|
526
|
+
console.log(` [NEW] ${endpointName}.js — endpoint router created successfully.`);
|
|
527
527
|
}
|
|
528
528
|
}
|
|
529
529
|
|
|
@@ -562,17 +562,17 @@ function createProcessorFiles(moduleDir, moduleName, endpointName, payload, forc
|
|
|
562
562
|
|
|
563
563
|
if (fs.existsSync(processorFilePath)) {
|
|
564
564
|
if (!force) {
|
|
565
|
-
console.log(` [SKIP] ${proc.name}.js
|
|
565
|
+
console.log(` [SKIP] ${proc.name}.js — file already exists, custom code preserved.`);
|
|
566
566
|
skippedCount++;
|
|
567
567
|
continue;
|
|
568
568
|
}
|
|
569
|
-
console.log(` [ARCHIVE+OVERWRITE] ${proc.name}.js
|
|
569
|
+
console.log(` [ARCHIVE+OVERWRITE] ${proc.name}.js — file archived then regenerated.`);
|
|
570
570
|
archivedCount++;
|
|
571
571
|
}
|
|
572
572
|
|
|
573
573
|
const processorContent = generateProcessor(proc, payloadDir);
|
|
574
574
|
fs.writeFileSync(processorFilePath, processorContent);
|
|
575
|
-
console.log(` [NEW] ${proc.name}.js
|
|
575
|
+
console.log(` [NEW] ${proc.name}.js — scaffold created successfully.`);
|
|
576
576
|
createdCount++;
|
|
577
577
|
}
|
|
578
578
|
|
|
@@ -745,7 +745,7 @@ ${validationCode}
|
|
|
745
745
|
// { sql: 'UPDATE ...', params: [...] }
|
|
746
746
|
// ]);
|
|
747
747
|
|
|
748
|
-
// Placeholder
|
|
748
|
+
// Placeholder — ganti dengan implementasi sebenarnya
|
|
749
749
|
const result = [];
|
|
750
750
|
|
|
751
751
|
return createResponse(200, '${successMessage}', result);
|