@restforgejs/platform 5.1.4 → 5.1.6
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/data/pull.js +16 -7
- package/generators/cli/data/push.js +17 -7
- package/generators/cli/init.js +347 -97
- package/generators/lib/data/data-scope.js +138 -0
- package/generators/lib/data/envelope.js +25 -9
- package/generators/lib/data/pull-runner.js +44 -37
- package/generators/lib/data/push-runner.js +64 -46
- 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/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/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
|
@@ -11,14 +11,17 @@
|
|
|
11
11
|
* (amandemen arah B, lihat docs/plan/data-pull-push/data-pull-push-09-amendments.md).
|
|
12
12
|
* Handler tetap tipis: seluruh logika berada di lib/data/pull-runner.js.
|
|
13
13
|
*
|
|
14
|
-
* Catatan:
|
|
15
|
-
*
|
|
14
|
+
* Catatan: scope tabel dipilih lewat tepat satu dari `--table` / `--schema` /
|
|
15
|
+
* `--all-schemas`. Flag `--schema` di sini adalah FILTER scope (bukan penentu
|
|
16
|
+
* namespace) — namespace tabel tetap diambil dari SDF IR (amandemen A4). Lokasi
|
|
17
|
+
* SDF ditentukan `--schema-path`. Layout output: bersarang `<schema>/<table>.json`
|
|
18
|
+
* untuk tabel ber-schema, flat `<table>.json` untuk tabel tanpa schema.
|
|
16
19
|
*/
|
|
17
20
|
|
|
18
21
|
module.exports = {
|
|
19
22
|
resource: 'data',
|
|
20
23
|
verb: 'pull',
|
|
21
|
-
description: 'Export isi tabel database ke file envelope JSON (data-storage/<table>.json) berdasarkan metadata SDF',
|
|
24
|
+
description: 'Export isi tabel database ke file envelope JSON (data-storage/<schema>/<table>.json) berdasarkan metadata SDF',
|
|
22
25
|
category: 'introspection',
|
|
23
26
|
flags: {
|
|
24
27
|
table: {
|
|
@@ -27,11 +30,17 @@ module.exports = {
|
|
|
27
30
|
default: null,
|
|
28
31
|
description: 'Nama tabel sumber yang akan di-pull (harus terdaftar di SDF)'
|
|
29
32
|
},
|
|
30
|
-
|
|
33
|
+
schema: {
|
|
34
|
+
type: 'string',
|
|
35
|
+
required: false,
|
|
36
|
+
default: null,
|
|
37
|
+
description: 'Filter schema: satu atau comma-separated (mutual-exclusive dengan --table/--all-schemas)'
|
|
38
|
+
},
|
|
39
|
+
'all-schemas': {
|
|
31
40
|
type: 'boolean',
|
|
32
41
|
required: false,
|
|
33
42
|
default: false,
|
|
34
|
-
description: 'Pull seluruh tabel
|
|
43
|
+
description: 'Pull seluruh tabel di semua schema (mutual-exclusive dengan --table/--schema)'
|
|
35
44
|
},
|
|
36
45
|
config: {
|
|
37
46
|
type: 'string',
|
|
@@ -84,8 +93,8 @@ module.exports = {
|
|
|
84
93
|
},
|
|
85
94
|
examples: [
|
|
86
95
|
'npx restforge data pull --table=visitors',
|
|
87
|
-
'npx restforge data pull --
|
|
88
|
-
'npx restforge data pull --all-
|
|
96
|
+
'npx restforge data pull --schema=public --config=db.env',
|
|
97
|
+
'npx restforge data pull --all-schemas --config=db.env --json',
|
|
89
98
|
'npx restforge data pull --table=visitors --config=db.env --limit=500',
|
|
90
99
|
'npx restforge data pull --table=visitors --config=db.env --json'
|
|
91
100
|
],
|
|
@@ -19,14 +19,18 @@
|
|
|
19
19
|
* commit per batch). `--mode=...`/`--force` kini menjadi unknown flag → ditolak
|
|
20
20
|
* arg-parser dengan exit 2 (perilaku standar).
|
|
21
21
|
*
|
|
22
|
-
* Catatan:
|
|
23
|
-
*
|
|
22
|
+
* Catatan: scope tabel dipilih lewat tepat satu dari `--table` / `--schema` /
|
|
23
|
+
* `--all-schemas`. Flag `--schema` adalah FILTER scope (bukan penentu namespace) —
|
|
24
|
+
* namespace tabel tetap diambil dari SDF IR (amandemen A4). Lokasi SDF ditentukan
|
|
25
|
+
* `--schema-path`. Path input mengikuti aturan IDENTIK pull (bersarang
|
|
26
|
+
* `<schema>/<table>.json` untuk tabel ber-schema, flat `<table>.json` untuk tabel
|
|
27
|
+
* tanpa schema).
|
|
24
28
|
*/
|
|
25
29
|
|
|
26
30
|
module.exports = {
|
|
27
31
|
resource: 'data',
|
|
28
32
|
verb: 'push',
|
|
29
|
-
description: 'Muat isi file envelope JSON (data-storage/<table>.json) ke tabel tujuan via INSERT batch berdasarkan metadata SDF',
|
|
33
|
+
description: 'Muat isi file envelope JSON (data-storage/<schema>/<table>.json) ke tabel tujuan via INSERT batch berdasarkan metadata SDF',
|
|
30
34
|
category: 'management',
|
|
31
35
|
flags: {
|
|
32
36
|
table: {
|
|
@@ -35,11 +39,17 @@ module.exports = {
|
|
|
35
39
|
default: null,
|
|
36
40
|
description: 'Nama tabel tujuan yang akan di-push (harus terdaftar di SDF)'
|
|
37
41
|
},
|
|
38
|
-
|
|
42
|
+
schema: {
|
|
43
|
+
type: 'string',
|
|
44
|
+
required: false,
|
|
45
|
+
default: null,
|
|
46
|
+
description: 'Filter schema: satu atau comma-separated (mutual-exclusive dengan --table/--all-schemas)'
|
|
47
|
+
},
|
|
48
|
+
'all-schemas': {
|
|
39
49
|
type: 'boolean',
|
|
40
50
|
required: false,
|
|
41
51
|
default: false,
|
|
42
|
-
description: 'Push seluruh tabel
|
|
52
|
+
description: 'Push seluruh tabel di semua schema, urut topological FK parent→child (mutual-exclusive dengan --table/--schema)'
|
|
43
53
|
},
|
|
44
54
|
config: {
|
|
45
55
|
type: 'string',
|
|
@@ -74,8 +84,8 @@ module.exports = {
|
|
|
74
84
|
},
|
|
75
85
|
examples: [
|
|
76
86
|
'npx restforge data push --table=visitors',
|
|
77
|
-
'npx restforge data push --
|
|
78
|
-
'npx restforge data push --all-
|
|
87
|
+
'npx restforge data push --schema=public --config=db.env',
|
|
88
|
+
'npx restforge data push --all-schemas --config=db.env --json',
|
|
79
89
|
'npx restforge data push --table=visitors --config=db.env --batch-size=500',
|
|
80
90
|
'npx restforge data push --table=visitors --config=db.env --json'
|
|
81
91
|
],
|
package/generators/cli/init.js
CHANGED
|
@@ -1,97 +1,347 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Contract: init (verb global)
|
|
5
|
-
*
|
|
6
|
-
* Generate starter config file di working directory. Membuat
|
|
7
|
-
* `config
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Contract: init (verb global)
|
|
5
|
+
*
|
|
6
|
+
* Generate starter config file di working directory. Membuat
|
|
7
|
+
* `config/<nama-file>.env` sebagai template starting point.
|
|
8
|
+
*
|
|
9
|
+
* Dua mode operasi:
|
|
10
|
+
* 1. Interaktif (default, hanya saat stdin TTY dan TANPA --force):
|
|
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.
|
|
14
|
+
* 2. Non-interaktif (saat --force diberikan ATAU stdin bukan TTY):
|
|
15
|
+
* menulis template apa adanya ke config/db-connection.env (perilaku legacy).
|
|
16
|
+
* Jalur ini dipakai oleh orkestrator (fast-track) dan playbook yang
|
|
17
|
+
* memanggil `npx restforge init --force`, sehingga harus tetap deterministik
|
|
18
|
+
* tanpa prompt.
|
|
19
|
+
*
|
|
20
|
+
* Catatan kompatibilitas: `--force` sengaja mempertahankan semantik non-interaktif
|
|
21
|
+
* agar pemanggil existing (fast-track.js, restforge-playbook) tidak menggantung.
|
|
22
|
+
* Tidak ada flag baru ditambahkan sehingga snapshot contract tidak berubah.
|
|
23
|
+
*
|
|
24
|
+
* Catatan: contract ini global verb (snapshot key = `init`, bukan
|
|
25
|
+
* `<resource>/init`). Struktur file di `generators/cli/init.js` (root cli/
|
|
26
|
+
* directory, bukan subdirectory resource).
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
const path = require('node:path');
|
|
30
|
+
const fs = require('node:fs');
|
|
31
|
+
const readline = require('node:readline');
|
|
32
|
+
const { DB_CONNECTION_ENV_TEMPLATE: TEMPLATE_ENV } = require('../lib/templates/db-connection-env');
|
|
33
|
+
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Default konfigurasi (selaras dengan placeholder pada template db-connection.env)
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
const DEFAULT_CONFIG_FILE = 'db-connection.env';
|
|
39
|
+
|
|
40
|
+
const DB_DEFAULTS = {
|
|
41
|
+
LICENSE: 'XXXX-XXXX-XXXX-XXXX',
|
|
42
|
+
DB_TYPE: 'postgresql',
|
|
43
|
+
DB_HOST: '127.0.0.1',
|
|
44
|
+
DB_PORT: '5432',
|
|
45
|
+
DB_USER: 'postgres',
|
|
46
|
+
DB_PASSWORD: 'your_password_here',
|
|
47
|
+
DB_NAME: 'your_database_name',
|
|
48
|
+
DB_FILE: './data/myapp.db'
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Override DB_PORT/DB_USER/DB_NAME menyesuaikan DB_TYPE yang dipilih; bila tidak
|
|
52
|
+
// terdaftar, fallback ke DB_DEFAULTS (selaras dengan fast-track).
|
|
53
|
+
const DB_TYPE_DEFAULTS = {
|
|
54
|
+
postgresql: { DB_PORT: '5432', DB_USER: 'postgres' },
|
|
55
|
+
postgres: { DB_PORT: '5432', DB_USER: 'postgres' },
|
|
56
|
+
mysql: { DB_PORT: '3306', DB_USER: 'root' },
|
|
57
|
+
oracle: { DB_PORT: '1521', DB_USER: 'oracle', DB_NAME: 'ORCL' }
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const SUPPORTED_DB_TYPES = ['postgresql', 'mysql', 'oracle', 'sqlite'];
|
|
61
|
+
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// Helper
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Prompter sederhana berbasis readline.question. init interaktif hanya berjalan
|
|
68
|
+
* pada TTY (lihat handler), sehingga tidak butuh penanganan input pipe seperti
|
|
69
|
+
* fast-track. `ask` dapat di-inject pada test via test seam.
|
|
70
|
+
*/
|
|
71
|
+
function createPrompter() {
|
|
72
|
+
const rl = readline.createInterface({
|
|
73
|
+
input: process.stdin,
|
|
74
|
+
output: process.stdout
|
|
75
|
+
});
|
|
76
|
+
const ask = (message) => new Promise((resolve) => {
|
|
77
|
+
rl.question(message, (answer) => resolve(answer));
|
|
78
|
+
});
|
|
79
|
+
return { ask, close: () => rl.close() };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Masking license untuk tampilan review: grup pertama dan terakhir terlihat,
|
|
84
|
+
* grup tengah disamarkan (mis. `8ECD-****-****-698Q`). Nilai asli tetap dipakai
|
|
85
|
+
* untuk penulisan file.
|
|
86
|
+
*/
|
|
87
|
+
function maskLicense(license) {
|
|
88
|
+
if (!license || typeof license !== 'string') return license;
|
|
89
|
+
const groups = license.split('-');
|
|
90
|
+
if (groups.length >= 3) {
|
|
91
|
+
return groups
|
|
92
|
+
.map((g, i) => (i === 0 || i === groups.length - 1) ? g : '*'.repeat(g.length))
|
|
93
|
+
.join('-');
|
|
94
|
+
}
|
|
95
|
+
if (license.length > 8) {
|
|
96
|
+
return `${license.slice(0, 4)}${'*'.repeat(license.length - 8)}${license.slice(-4)}`;
|
|
97
|
+
}
|
|
98
|
+
return license;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Default atribut database berdasarkan DB_TYPE (merge DB_DEFAULTS + override). */
|
|
102
|
+
function dbDefaultsFor(dbType) {
|
|
103
|
+
const override = DB_TYPE_DEFAULTS[dbType] || {};
|
|
104
|
+
return { ...DB_DEFAULTS, ...override };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Representasi database untuk baris review, berdasarkan konfigurasi input. */
|
|
108
|
+
function describeDatabase(cfg) {
|
|
109
|
+
if (cfg.DB_TYPE === 'sqlite') return `sqlite @ ${cfg.DB_FILE}`;
|
|
110
|
+
return `${cfg.DB_TYPE} @ ${cfg.DB_HOST}:${cfg.DB_PORT}/${cfg.DB_NAME}`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Fase input konfigurasi interaktif: LICENSE lalu tipe database beserta
|
|
115
|
+
* atributnya. Mengembalikan objek cfg. `ask` di-inject agar dapat diuji.
|
|
116
|
+
*/
|
|
117
|
+
async function collectConfig(ask) {
|
|
118
|
+
const askField = async (label, def) => {
|
|
119
|
+
const input = (await ask(` ${label} (${def}): `)).trim();
|
|
120
|
+
return input || def;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
console.log('');
|
|
124
|
+
console.log('Enter configuration (press Enter to accept the default value):');
|
|
125
|
+
console.log('');
|
|
126
|
+
|
|
127
|
+
const cfg = {};
|
|
128
|
+
|
|
129
|
+
// 1) LICENSE
|
|
130
|
+
cfg.LICENSE = await askField('LICENSE', DB_DEFAULTS.LICENSE);
|
|
131
|
+
|
|
132
|
+
// 2) Tipe database + atributnya
|
|
133
|
+
console.log('');
|
|
134
|
+
console.log(` Supported DB_TYPE: ${SUPPORTED_DB_TYPES.join(', ')}`);
|
|
135
|
+
cfg.DB_TYPE = (await askField('DB_TYPE', DB_DEFAULTS.DB_TYPE)).toLowerCase();
|
|
136
|
+
|
|
137
|
+
if (cfg.DB_TYPE === 'sqlite') {
|
|
138
|
+
console.log('');
|
|
139
|
+
console.log(' SQLite mode: DB_HOST, DB_PORT, DB_USER, DB_PASSWORD are ignored.');
|
|
140
|
+
console.log(' The database file path is set in DB_NAME (via DB_FILE).');
|
|
141
|
+
console.log('');
|
|
142
|
+
cfg.DB_FILE = await askField('DB_FILE (.db file path)', DB_DEFAULTS.DB_FILE);
|
|
143
|
+
} else {
|
|
144
|
+
const d = dbDefaultsFor(cfg.DB_TYPE);
|
|
145
|
+
cfg.DB_HOST = await askField('DB_HOST', d.DB_HOST);
|
|
146
|
+
cfg.DB_PORT = await askField('DB_PORT', d.DB_PORT);
|
|
147
|
+
cfg.DB_USER = await askField('DB_USER', d.DB_USER);
|
|
148
|
+
cfg.DB_PASSWORD = await askField('DB_PASSWORD', d.DB_PASSWORD);
|
|
149
|
+
cfg.DB_NAME = await askField('DB_NAME', d.DB_NAME);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return cfg;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Susun map nilai env yang akan di-patch ke template dari konfigurasi input. */
|
|
156
|
+
function envValuesFromCfg(cfg) {
|
|
157
|
+
const v = {
|
|
158
|
+
LICENSE: cfg.LICENSE,
|
|
159
|
+
DB_TYPE: cfg.DB_TYPE
|
|
160
|
+
};
|
|
161
|
+
if (cfg.DB_TYPE === 'sqlite') {
|
|
162
|
+
// Untuk SQLite, path file disimpan pada DB_NAME (dikonsumsi runtime).
|
|
163
|
+
v.DB_NAME = cfg.DB_FILE;
|
|
164
|
+
} else {
|
|
165
|
+
v.DB_HOST = cfg.DB_HOST;
|
|
166
|
+
v.DB_PORT = cfg.DB_PORT;
|
|
167
|
+
v.DB_USER = cfg.DB_USER;
|
|
168
|
+
v.DB_PASSWORD = cfg.DB_PASSWORD;
|
|
169
|
+
v.DB_NAME = cfg.DB_NAME;
|
|
170
|
+
}
|
|
171
|
+
return v;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Patch VALUE sejumlah KEY pada teks template tanpa mengubah baris lain
|
|
176
|
+
* (komentar, section Redis/Kafka/dll. tetap utuh). KEY yang belum ada di
|
|
177
|
+
* template ditambahkan di akhir.
|
|
178
|
+
*/
|
|
179
|
+
function applyConfigToTemplate(template, values) {
|
|
180
|
+
const eol = template.includes('\r\n') ? '\r\n' : '\n';
|
|
181
|
+
const lines = template.split(/\r?\n/);
|
|
182
|
+
const remaining = new Set(Object.keys(values));
|
|
183
|
+
for (let i = 0; i < lines.length; i++) {
|
|
184
|
+
const m = lines[i].match(/^(\s*)([A-Za-z_][A-Za-z0-9_]*)\s*=/);
|
|
185
|
+
if (m && remaining.has(m[2])) {
|
|
186
|
+
lines[i] = `${m[1]}${m[2]}=${values[m[2]]}`;
|
|
187
|
+
remaining.delete(m[2]);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
for (const k of remaining) lines.push(`${k}=${values[k]}`);
|
|
191
|
+
return lines.join(eol);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Tulis file config ke config/<configFileName>, buat folder bila perlu. */
|
|
195
|
+
function writeConfigFile(workingDir, configFileName, content) {
|
|
196
|
+
const configDir = path.resolve(workingDir, 'config');
|
|
197
|
+
if (!fs.existsSync(configDir)) {
|
|
198
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
199
|
+
}
|
|
200
|
+
const absolutePath = path.resolve(configDir, configFileName);
|
|
201
|
+
fs.writeFileSync(absolutePath, content, 'utf8');
|
|
202
|
+
console.log(` Created: ${path.join('config', configFileName)}`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Cetak ringkasan akhir + next steps. */
|
|
206
|
+
function printDone(configFileName) {
|
|
207
|
+
console.log('');
|
|
208
|
+
console.log('Done! Sample files generated successfully.');
|
|
209
|
+
console.log('');
|
|
210
|
+
console.log('Next Steps:');
|
|
211
|
+
console.log('');
|
|
212
|
+
console.log(` 1. Edit config/${configFileName}`);
|
|
213
|
+
console.log(' Update database credentials (DB_HOST, DB_USER, DB_PASSWORD, DB_NAME)');
|
|
214
|
+
console.log('');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Review + konfirmasi sebelum menulis (mode interaktif). */
|
|
218
|
+
async function confirmWrite(ctx, ask) {
|
|
219
|
+
console.log('');
|
|
220
|
+
console.log('====================================================');
|
|
221
|
+
console.log(' Review configuration');
|
|
222
|
+
console.log('====================================================');
|
|
223
|
+
console.log(` License : ${maskLicense(ctx.cfg.LICENSE)}`);
|
|
224
|
+
console.log(` Database : ${describeDatabase(ctx.cfg)}`);
|
|
225
|
+
const existsNote = ctx.exists ? ' (EXISTING — will be overwritten)' : '';
|
|
226
|
+
console.log(` Target : config/${ctx.configFileName}${existsNote}`);
|
|
227
|
+
console.log('');
|
|
228
|
+
const answer = (await ask(' Write this config? (Y/n): ')).trim().toLowerCase();
|
|
229
|
+
return !(answer === 'n' || answer === 'no');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Jalur non-interaktif: tulis template apa adanya ke config/db-connection.env.
|
|
234
|
+
* Mempertahankan perilaku legacy (termasuk guard file existing tanpa --force).
|
|
235
|
+
*/
|
|
236
|
+
function runNonInteractive(workingDir, force) {
|
|
237
|
+
const configFileName = DEFAULT_CONFIG_FILE;
|
|
238
|
+
const relativePath = path.join('config', configFileName);
|
|
239
|
+
const absolutePath = path.resolve(workingDir, relativePath);
|
|
240
|
+
|
|
241
|
+
if (fs.existsSync(absolutePath) && !force) {
|
|
242
|
+
console.log('WARNING: The following files already exist:');
|
|
243
|
+
console.log('');
|
|
244
|
+
console.log(` ${relativePath}`);
|
|
245
|
+
console.log('');
|
|
246
|
+
console.log('Use --force to overwrite:');
|
|
247
|
+
console.log(' npx restforge init --force');
|
|
248
|
+
console.log('');
|
|
249
|
+
throw new Error('1 file(s) already exist. Use --force to overwrite.');
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
writeConfigFile(workingDir, configFileName, TEMPLATE_ENV);
|
|
253
|
+
printDone(configFileName);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** Jalur interaktif: kumpulkan input, konfirmasi, lalu tulis template ter-patch. */
|
|
257
|
+
async function runInteractive(workingDir, prompter) {
|
|
258
|
+
const { ask } = prompter;
|
|
259
|
+
|
|
260
|
+
// 1) LICENSE + 2) tipe database & atributnya.
|
|
261
|
+
const cfg = await collectConfig(ask);
|
|
262
|
+
|
|
263
|
+
// 3) Nama file config (terakhir; default db-connection.env, bisa di-custom).
|
|
264
|
+
console.log('');
|
|
265
|
+
const fnameInput = (await ask(` Config file name (${DEFAULT_CONFIG_FILE}): `)).trim();
|
|
266
|
+
// basename untuk mencegah path traversal; nama selalu relatif ke config/.
|
|
267
|
+
const configFileName = path.basename(fnameInput || DEFAULT_CONFIG_FILE);
|
|
268
|
+
|
|
269
|
+
const absolutePath = path.resolve(workingDir, 'config', configFileName);
|
|
270
|
+
const exists = fs.existsSync(absolutePath);
|
|
271
|
+
|
|
272
|
+
const ok = await confirmWrite({ cfg, configFileName, exists }, ask);
|
|
273
|
+
if (!ok) {
|
|
274
|
+
console.log('');
|
|
275
|
+
console.log(' Aborted. No changes were made.');
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const content = applyConfigToTemplate(TEMPLATE_ENV, envValuesFromCfg(cfg));
|
|
280
|
+
console.log('');
|
|
281
|
+
writeConfigFile(workingDir, configFileName, content);
|
|
282
|
+
printDone(configFileName);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// ---------------------------------------------------------------------------
|
|
286
|
+
// Contract
|
|
287
|
+
// ---------------------------------------------------------------------------
|
|
288
|
+
|
|
289
|
+
module.exports = {
|
|
290
|
+
verb: 'init',
|
|
291
|
+
description: 'Generate starter config file (config/db-connection.env) di working directory. Interaktif di terminal; non-interaktif dengan --force',
|
|
292
|
+
category: 'utility',
|
|
293
|
+
flags: {
|
|
294
|
+
force: {
|
|
295
|
+
type: 'boolean',
|
|
296
|
+
required: false,
|
|
297
|
+
default: false,
|
|
298
|
+
description: 'Timpa file existing tanpa konfirmasi'
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
examples: [
|
|
302
|
+
'npx restforge init',
|
|
303
|
+
'npx restforge init --force'
|
|
304
|
+
],
|
|
305
|
+
async handler(args) {
|
|
306
|
+
const workingDir = process.cwd();
|
|
307
|
+
|
|
308
|
+
console.log('');
|
|
309
|
+
console.log('RESTForge - Init');
|
|
310
|
+
console.log('====================');
|
|
311
|
+
|
|
312
|
+
// Interaktif hanya bila TTY dan TANPA --force. `--force` dan konteks
|
|
313
|
+
// non-TTY (pipe/CI/spawn dari orkestrator) memakai jalur non-interaktif
|
|
314
|
+
// deterministik agar tidak menggantung menunggu input.
|
|
315
|
+
const interactive = args.force !== true && Boolean(process.stdin.isTTY);
|
|
316
|
+
|
|
317
|
+
if (!interactive) {
|
|
318
|
+
runNonInteractive(workingDir, args.force === true);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const prompter = createPrompter();
|
|
323
|
+
try {
|
|
324
|
+
await runInteractive(workingDir, prompter);
|
|
325
|
+
} finally {
|
|
326
|
+
prompter.close();
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
// Test seam (hanya aktif via env): mengekspos helper internal agar alur
|
|
332
|
+
// interaktif dapat diuji dengan `ask` ter-inject tanpa stdin TTY. Pada
|
|
333
|
+
// penggunaan CLI normal env ini tidak di-set sehingga export tetap contract murni.
|
|
334
|
+
if (process.env.INIT_TEST === '1') {
|
|
335
|
+
module.exports.__test = {
|
|
336
|
+
collectConfig,
|
|
337
|
+
applyConfigToTemplate,
|
|
338
|
+
envValuesFromCfg,
|
|
339
|
+
dbDefaultsFor,
|
|
340
|
+
describeDatabase,
|
|
341
|
+
maskLicense,
|
|
342
|
+
confirmWrite,
|
|
343
|
+
runInteractive,
|
|
344
|
+
DEFAULT_CONFIG_FILE,
|
|
345
|
+
SUPPORTED_DB_TYPES
|
|
346
|
+
};
|
|
347
|
+
}
|