@restforgejs/platform 5.2.13 → 5.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/drift-check-linux +0 -0
- package/bin/sdf-tools-linux +0 -0
- package/bin/sdf-tools.exe +0 -0
- package/build-info.json +2 -2
- package/cli/consumer-deploy.js +1 -1
- package/cli/consumer.js +1 -1
- package/generators/cli/endpoint/create.js +69 -6
- package/generators/cli/fast-track.js +4 -2
- package/generators/cli/payload/sync.js +16 -6
- package/generators/cli/project/auth.js +209 -0
- package/generators/cli/project/sdk.js +112 -0
- package/generators/lib/arg-parser.js +6 -0
- package/generators/lib/auth/component-generator.js +58 -0
- package/generators/lib/auth/dependency-checker.js +102 -0
- package/generators/lib/auth/env-injector.js +81 -0
- package/generators/lib/auth/migrate-runner.js +111 -0
- package/generators/lib/auth/prefix.js +22 -0
- package/generators/lib/auth/processor-generator.js +57 -0
- package/generators/lib/auth/sdf-generator.js +102 -0
- package/generators/lib/auth/template-renderer.js +29 -0
- package/generators/lib/auth/templates/processor/google.js.tmpl +178 -0
- package/generators/lib/auth/templates/processor/login.js.tmpl +152 -0
- package/generators/lib/auth/templates/processor/logout.js.tmpl +58 -0
- package/generators/lib/auth/templates/processor/me.js.tmpl +64 -0
- package/generators/lib/auth/templates/processor/refresh.js.tmpl +134 -0
- package/generators/lib/auth/templates/processor/register.js.tmpl +77 -0
- package/generators/lib/auth/templates/processor/reset-password.js.tmpl +106 -0
- package/generators/lib/auth/templates/rfx_auth-middleware.js.tmpl +79 -0
- package/generators/lib/auth/templates/rfx_auth.js.tmpl +107 -0
- package/generators/lib/dbschema-kit/schema-printer.js +10 -1
- package/generators/lib/generators/model-generator.js +46 -59
- package/generators/lib/help-generator.js +41 -3
- package/generators/lib/payload/endpoint-schema-validator.js +8 -3
- package/generators/lib/payload/field-projections.js +116 -0
- package/generators/lib/payload/payload-runner.js +164 -48
- package/generators/lib/payload/schema-diff.js +108 -0
- package/generators/lib/sdk/generator.js +719 -0
- package/generators/lib/sdk/naming.js +48 -0
- package/generators/lib/sdk/runtime/README.md.tmpl +207 -0
- package/generators/lib/sdk/runtime/auth-client.js +186 -0
- package/generators/lib/sdk/runtime/deploy.mjs.tmpl +85 -0
- package/generators/lib/sdk/runtime/http-client.js +81 -0
- package/generators/lib/sdk/runtime/resource-client.js +59 -0
- package/generators/lib/sdk/runtime/storage.js +31 -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/generators/lib/utils/cli-output.js +40 -0
- package/generators/lib/utils/config-resolver.js +61 -0
- package/generators/lib/utils/database-introspector.js +28 -5
- 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/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
|
@@ -34,6 +34,8 @@ const DemoGenerator = require('../../src/utils/demo-generator');
|
|
|
34
34
|
const projectRegistry = require('../../lib/utils/project-registry');
|
|
35
35
|
const cliOutput = require('../../lib/utils/cli-output');
|
|
36
36
|
const endpointSchemaValidator = require('../../lib/payload/endpoint-schema-validator');
|
|
37
|
+
const { deriveFieldProjections, augmentProjectionsForSoftDelete } = require('../../lib/payload/field-projections');
|
|
38
|
+
const configResolver = require('../../lib/utils/config-resolver');
|
|
37
39
|
|
|
38
40
|
function hasAuditRequired(payload) {
|
|
39
41
|
if (!payload || !payload.fieldPolicy) return false;
|
|
@@ -143,14 +145,12 @@ module.exports = {
|
|
|
143
145
|
async handler(args) {
|
|
144
146
|
const startTime = Date.now();
|
|
145
147
|
let muted = false;
|
|
148
|
+
let summary = null;
|
|
146
149
|
|
|
147
150
|
try {
|
|
148
151
|
const project = ArgumentValidator.validateProjectName(args.project);
|
|
149
152
|
const endpoint = ArgumentValidator.validateEndpointName(args.name);
|
|
150
153
|
const payloadFile = ArgumentValidator.validatePayloadName(args.payload);
|
|
151
|
-
const database = args.database
|
|
152
|
-
? ArgumentValidator.validateDatabaseType(args.database)
|
|
153
|
-
: 'postgres';
|
|
154
154
|
const force = !!args.force;
|
|
155
155
|
const createExamples = !!args['create-examples'];
|
|
156
156
|
const skipSqlValidation = !!args['skip-sql-validation'];
|
|
@@ -161,6 +161,30 @@ module.exports = {
|
|
|
161
161
|
? args.config.trim()
|
|
162
162
|
: null;
|
|
163
163
|
|
|
164
|
+
// Resolusi tipe database:
|
|
165
|
+
// 1. --database eksplisit → dipakai apa adanya (prioritas tertinggi)
|
|
166
|
+
// 2. Auto-deteksi DB_TYPE dari config aktif (--config, atau default
|
|
167
|
+
// config .restforge/defaults.json) → mengikuti DB_TYPE project
|
|
168
|
+
// 3. Fallback 'postgres' bila tidak ada config yang bisa di-resolve
|
|
169
|
+
let database;
|
|
170
|
+
let databaseSource;
|
|
171
|
+
if (args.database) {
|
|
172
|
+
database = ArgumentValidator.validateDatabaseType(args.database);
|
|
173
|
+
databaseSource = 'flag';
|
|
174
|
+
} else {
|
|
175
|
+
const resolvedCfg = configResolver.resolveConfig(configArg, process.cwd());
|
|
176
|
+
const detected = resolvedCfg
|
|
177
|
+
? configResolver.readDatabaseTypeFromConfig(resolvedCfg.path)
|
|
178
|
+
: null;
|
|
179
|
+
if (detected) {
|
|
180
|
+
database = detected;
|
|
181
|
+
databaseSource = resolvedCfg.source === 'default' ? 'config-default' : 'config';
|
|
182
|
+
} else {
|
|
183
|
+
database = 'postgres';
|
|
184
|
+
databaseSource = 'fallback';
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
164
188
|
if (!verbose) {
|
|
165
189
|
cliOutput.mute();
|
|
166
190
|
muted = true;
|
|
@@ -170,12 +194,17 @@ module.exports = {
|
|
|
170
194
|
console.log(` Project: ${project}`);
|
|
171
195
|
console.log(` Endpoint: ${endpoint}`);
|
|
172
196
|
console.log(` Payload: ${payloadFile}`);
|
|
173
|
-
|
|
197
|
+
const databaseNote = databaseSource === 'config'
|
|
198
|
+
? ' (auto-detected from --config)'
|
|
199
|
+
: databaseSource === 'config-default'
|
|
200
|
+
? ' (auto-detected from default config)'
|
|
201
|
+
: '';
|
|
202
|
+
console.log(` Database: ${database}${databaseNote}`);
|
|
174
203
|
console.log(` Force overwrite: ${force}`);
|
|
175
204
|
console.log('');
|
|
176
205
|
|
|
177
206
|
const cwd = process.cwd();
|
|
178
|
-
|
|
207
|
+
summary = {
|
|
179
208
|
config: { project, endpoint, database, force },
|
|
180
209
|
payload: null,
|
|
181
210
|
archive: null,
|
|
@@ -204,7 +233,7 @@ module.exports = {
|
|
|
204
233
|
const schemaResult = await endpointSchemaValidator.validateEndpointSchema({
|
|
205
234
|
payload,
|
|
206
235
|
payloadFileName: path.basename(payloadFile),
|
|
207
|
-
payloadFilePath:
|
|
236
|
+
payloadFilePath: rawPayload._payloadPath,
|
|
208
237
|
configArg,
|
|
209
238
|
skipSchemaCheck,
|
|
210
239
|
workingDir: cwd
|
|
@@ -216,6 +245,37 @@ module.exports = {
|
|
|
216
245
|
summary.schemaValidation = schemaResult;
|
|
217
246
|
summary.config.config = configArg || null;
|
|
218
247
|
|
|
248
|
+
// Derivasi field projections dan embed ke payload sebagai _fieldProjections.
|
|
249
|
+
// Template membaca ini untuk emit schemaFields/readableFields/datatablesFields.
|
|
250
|
+
// Fallback ke fieldName bila DB tidak tersedia (skipSchemaCheck atau projectionInputs absent).
|
|
251
|
+
{
|
|
252
|
+
const fn = payload.fieldName || [];
|
|
253
|
+
let fieldProjections;
|
|
254
|
+
if (schemaResult.status === 'ok' && schemaResult.projectionInputs) {
|
|
255
|
+
fieldProjections = deriveFieldProjections({
|
|
256
|
+
fieldName: fn,
|
|
257
|
+
physicalColumns: schemaResult.projectionInputs.physicalColumns,
|
|
258
|
+
readSourceColumns: schemaResult.projectionInputs.readSourceColumns,
|
|
259
|
+
datatablesColumns: schemaResult.projectionInputs.datatablesColumns,
|
|
260
|
+
overrides: {
|
|
261
|
+
readableFields: payload.readableFields,
|
|
262
|
+
datatablesFields: payload.datatablesFields
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
} else {
|
|
266
|
+
// skipSchemaCheck atau projectionInputs tidak tersedia:
|
|
267
|
+
// fallback langsung ke fieldName untuk semua proyeksi tanpa DB.
|
|
268
|
+
// Override payload.readableFields / datatablesFields diterapkan bila ada.
|
|
269
|
+
fieldProjections = {
|
|
270
|
+
schemaFields: fn.slice(),
|
|
271
|
+
readableFields: payload.readableFields || fn.slice(),
|
|
272
|
+
datatablesFields: payload.datatablesFields || fn.slice()
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
augmentProjectionsForSoftDelete(fieldProjections, payload);
|
|
276
|
+
payload._fieldProjections = fieldProjections;
|
|
277
|
+
}
|
|
278
|
+
|
|
219
279
|
const registry = projectRegistry.loadProjectRegistry();
|
|
220
280
|
if (registry.projects[project]) {
|
|
221
281
|
const existing = registry.projects[project];
|
|
@@ -312,6 +372,9 @@ module.exports = {
|
|
|
312
372
|
// cli-entry.js men-print `Error: <message>` ke stderr saat handler
|
|
313
373
|
// re-throw (lihat handler dispatch). Jangan double-print di sini.
|
|
314
374
|
if (muted) cliOutput.unmute();
|
|
375
|
+
if (summary && summary.config) {
|
|
376
|
+
cliOutput.printCreatePartial(summary);
|
|
377
|
+
}
|
|
315
378
|
throw error;
|
|
316
379
|
}
|
|
317
380
|
}
|
|
@@ -385,7 +385,9 @@ async function collectConfig(args, ask, fileCfg = {}) {
|
|
|
385
385
|
console.log(' SQLite mode: DB_HOST, DB_PORT, DB_USER, DB_PASSWORD are ignored.');
|
|
386
386
|
console.log(' The database file path is set in DB_FILE.');
|
|
387
387
|
console.log('');
|
|
388
|
-
|
|
388
|
+
// fileCfg.DB_NAME: file legacy hasil `restforge init` menyimpan path
|
|
389
|
+
// sqlite di DB_NAME, bukan DB_FILE (lihat init.js & server.js runtime).
|
|
390
|
+
cfg.DB_FILE = await askField('DB_FILE (.db file path)', fileCfg.DB_FILE || fileCfg.DB_NAME || DEFAULTS.DB_FILE);
|
|
389
391
|
cfg.DB_NAME = cfg.DB_FILE;
|
|
390
392
|
} else {
|
|
391
393
|
const dbDef = DB_TYPE_DEFAULTS[cfg.DB_TYPE] || {};
|
|
@@ -415,7 +417,7 @@ function defaultCfgFromFile(args, fileCfg = {}) {
|
|
|
415
417
|
cfg.DB_TYPE = (fileCfg.DB_TYPE || DEFAULTS.DB_TYPE).toLowerCase();
|
|
416
418
|
|
|
417
419
|
if (cfg.DB_TYPE === 'sqlite') {
|
|
418
|
-
cfg.DB_FILE = fileCfg.DB_FILE || DEFAULTS.DB_FILE;
|
|
420
|
+
cfg.DB_FILE = fileCfg.DB_FILE || fileCfg.DB_NAME || DEFAULTS.DB_FILE;
|
|
419
421
|
cfg.DB_NAME = cfg.DB_FILE;
|
|
420
422
|
} else {
|
|
421
423
|
const dbDef = DB_TYPE_DEFAULTS[cfg.DB_TYPE] || {};
|
|
@@ -32,10 +32,11 @@ module.exports = {
|
|
|
32
32
|
description: 'Sync only a specific table (default: all)'
|
|
33
33
|
},
|
|
34
34
|
'expand-fk': {
|
|
35
|
-
type: '
|
|
35
|
+
type: 'string',
|
|
36
36
|
required: false,
|
|
37
|
-
default:
|
|
38
|
-
|
|
37
|
+
default: null,
|
|
38
|
+
bareDefault: 'both',
|
|
39
|
+
description: 'Generate JOIN configuration from foreign keys: creates SQL file query/<table>-join.sql. Values: "both" (updates datatablesQuery and viewQuery) or "datatables-only" (updates datatablesQuery only, viewQuery unchanged). Bare --expand-fk (without value) defaults to "both". Requires --table. If --fk-columns is empty, display columns per FK are auto-selected (name → code → primary key)'
|
|
39
40
|
},
|
|
40
41
|
'fk-columns': {
|
|
41
42
|
type: 'string',
|
|
@@ -47,16 +48,25 @@ module.exports = {
|
|
|
47
48
|
examples: [
|
|
48
49
|
'npx restforge payload sync --config=db.env',
|
|
49
50
|
'npx restforge payload sync --config=db.env --table=users',
|
|
50
|
-
'npx restforge payload sync --table=visitors --expand-fk',
|
|
51
|
-
'npx restforge payload sync --table=visitors --expand-fk
|
|
51
|
+
'npx restforge payload sync --table=visitors --expand-fk=both',
|
|
52
|
+
'npx restforge payload sync --table=visitors --expand-fk=datatables-only',
|
|
53
|
+
'npx restforge payload sync --table=visitors --expand-fk=both --fk-columns=visitor_categories.category_code,visitor_categories.category_name'
|
|
52
54
|
],
|
|
53
55
|
async handler(args) {
|
|
56
|
+
const expandFkRaw = args['expand-fk'];
|
|
57
|
+
let expandFkMode = null;
|
|
58
|
+
if (expandFkRaw === 'both' || expandFkRaw === 'datatables-only') {
|
|
59
|
+
expandFkMode = expandFkRaw;
|
|
60
|
+
} else if (expandFkRaw !== null && expandFkRaw !== undefined) {
|
|
61
|
+
throw new Error(`Invalid --expand-fk value "${expandFkRaw}". Valid values: both, datatables-only`);
|
|
62
|
+
}
|
|
63
|
+
|
|
54
64
|
const generator = new PayloadGenerator();
|
|
55
65
|
await generator.run({
|
|
56
66
|
config: args.config,
|
|
57
67
|
table: args.table || null,
|
|
58
68
|
sync: true,
|
|
59
|
-
|
|
69
|
+
expandFkMode,
|
|
60
70
|
fkColumns: args['fk-columns'] || null
|
|
61
71
|
});
|
|
62
72
|
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Contract: project auth
|
|
5
|
+
*
|
|
6
|
+
* Memasang "auth extension" (SDF, tabel, component, processor) ke project
|
|
7
|
+
* RESTForge yang sudah ada. Setelah validasi prasyarat (phase 00), handler
|
|
8
|
+
* menulis dua file SDF auth ber-prefix `rfx` ke `--schema-path` (Fungsi 1),
|
|
9
|
+
* lalu membuat tabelnya di DB via primitif dbschema-kit langsung (Fungsi 2),
|
|
10
|
+
* lalu menulis component middleware + router auth termasuk route google (Fungsi 3a),
|
|
11
|
+
* lalu menulis ketujuh processor auth termasuk google (Fungsi 3b), lalu
|
|
12
|
+
* menginjeksi variabel env auth ke `--config` dan memverifikasi/mencatat
|
|
13
|
+
* dependency runtime `bcrypt`+`jsonwebtoken` (Fungsi tambahan, phase 05),
|
|
14
|
+
* lalu mencetak ringkasan akhir.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const fs = require('node:fs');
|
|
18
|
+
const path = require('node:path');
|
|
19
|
+
const { validateSafeName } = require('../../lib/utils/path-validator');
|
|
20
|
+
const projectRegistry = require('../../lib/utils/project-registry');
|
|
21
|
+
const { PREFIX } = require('../../lib/auth/prefix');
|
|
22
|
+
const { generateAuthSdf } = require('../../lib/auth/sdf-generator');
|
|
23
|
+
const { runAuthMigrate } = require('../../lib/auth/migrate-runner');
|
|
24
|
+
const { generateAuthComponents } = require('../../lib/auth/component-generator');
|
|
25
|
+
const { generateAuthProcessors } = require('../../lib/auth/processor-generator');
|
|
26
|
+
const { injectAuthEnv } = require('../../lib/auth/env-injector');
|
|
27
|
+
const { ensureAuthDependencies } = require('../../lib/auth/dependency-checker');
|
|
28
|
+
|
|
29
|
+
function projectExists(workingDir, projectName) {
|
|
30
|
+
const modulePath = path.join(workingDir, 'src', 'modules', `${projectName}.js`);
|
|
31
|
+
if (fs.existsSync(modulePath)) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const registry = projectRegistry.loadProjectRegistry();
|
|
36
|
+
return Boolean(registry.projects && registry.projects[projectName]);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = {
|
|
40
|
+
resource: 'project',
|
|
41
|
+
verb: 'auth',
|
|
42
|
+
description: 'Install auth extension (SDF, tabel, component, processor) ke project existing',
|
|
43
|
+
category: 'generation',
|
|
44
|
+
flags: {
|
|
45
|
+
create: {
|
|
46
|
+
type: 'boolean',
|
|
47
|
+
required: false,
|
|
48
|
+
default: false,
|
|
49
|
+
description: 'Trigger eksekusi instalasi auth extension (wajib disertakan)'
|
|
50
|
+
},
|
|
51
|
+
project: {
|
|
52
|
+
type: 'string',
|
|
53
|
+
required: false,
|
|
54
|
+
default: null,
|
|
55
|
+
description: 'Nama project target (kanonik; alias: --name)'
|
|
56
|
+
},
|
|
57
|
+
name: {
|
|
58
|
+
type: 'string',
|
|
59
|
+
required: false,
|
|
60
|
+
default: null,
|
|
61
|
+
description: 'Alias dari --project'
|
|
62
|
+
},
|
|
63
|
+
'schema-path': {
|
|
64
|
+
type: 'string',
|
|
65
|
+
required: false,
|
|
66
|
+
default: './schema',
|
|
67
|
+
description: 'Folder output file SDF auth'
|
|
68
|
+
},
|
|
69
|
+
config: {
|
|
70
|
+
type: 'string',
|
|
71
|
+
required: false,
|
|
72
|
+
default: 'config/db-connection.env',
|
|
73
|
+
description: 'File konfigurasi koneksi DB untuk langkah migrate'
|
|
74
|
+
},
|
|
75
|
+
force: {
|
|
76
|
+
type: 'boolean',
|
|
77
|
+
required: false,
|
|
78
|
+
default: false,
|
|
79
|
+
description: 'Timpa file yang sudah ada (backup tetap dibuat)'
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
examples: [
|
|
83
|
+
'npx restforge project auth --create --project=myapp',
|
|
84
|
+
'npx restforge project auth --create --name=myapp --schema-path=./schema'
|
|
85
|
+
],
|
|
86
|
+
async handler(args) {
|
|
87
|
+
if (args.create !== true) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
'Flag --create wajib disertakan untuk memicu instalasi auth extension. ' +
|
|
90
|
+
'Contoh: npx restforge project auth --create --project=<nama-project>'
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const rawName = args.project || args.name;
|
|
95
|
+
if (!rawName) {
|
|
96
|
+
throw new Error('Salah satu dari --project atau --name wajib diisi dengan nama project target');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const projectName = validateSafeName(rawName, 'project');
|
|
100
|
+
const workingDir = process.cwd();
|
|
101
|
+
|
|
102
|
+
if (!projectExists(workingDir, projectName)) {
|
|
103
|
+
throw new Error(
|
|
104
|
+
`Project "${projectName}" tidak ditemukan (src/modules/${projectName}.js tidak ada). ` +
|
|
105
|
+
'Buat project lebih dulu (mis. "npx restforge endpoint create") sebelum menjalankan project auth.'
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const schemaPath = path.resolve(workingDir, args['schema-path'] || './schema');
|
|
110
|
+
const { written, skipped } = generateAuthSdf({ schemaPath, force: args.force === true });
|
|
111
|
+
|
|
112
|
+
console.log('');
|
|
113
|
+
console.log(`Prasyarat OK untuk project "${projectName}" (prefix artefak: ${PREFIX}).`);
|
|
114
|
+
if (written.length > 0) {
|
|
115
|
+
console.log(`SDF auth ditulis: ${written.map((p) => path.basename(p)).join(', ')}`);
|
|
116
|
+
}
|
|
117
|
+
if (skipped.length > 0) {
|
|
118
|
+
console.log(
|
|
119
|
+
`SDF auth sudah ada, dilewati (gunakan --force untuk overwrite): ${skipped.map((p) => path.basename(p)).join(', ')}`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const configPath = args.config || 'config/db-connection.env';
|
|
124
|
+
const migrateResult = await runAuthMigrate({ schemaPath, configPath });
|
|
125
|
+
console.log(
|
|
126
|
+
`Tabel auth siap: ${migrateResult.tables.join(', ')} ` +
|
|
127
|
+
`(dialect: ${migrateResult.dialect}, ${migrateResult.statementsApplied} statement diterapkan).`
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const middlewareDir = path.join(workingDir, 'src', 'components', 'handlers');
|
|
131
|
+
const routerDir = path.join(workingDir, 'src', 'modules', projectName);
|
|
132
|
+
const { written: componentsWritten, skipped: componentsSkipped } = generateAuthComponents({
|
|
133
|
+
middlewareDir,
|
|
134
|
+
routerDir,
|
|
135
|
+
projectName,
|
|
136
|
+
force: args.force === true
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
if (componentsWritten.length > 0) {
|
|
140
|
+
console.log(
|
|
141
|
+
`Component/router auth ditulis: ${componentsWritten.map((p) => path.relative(workingDir, p)).join(', ')}`
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
if (componentsSkipped.length > 0) {
|
|
145
|
+
console.log(
|
|
146
|
+
`Component/router auth sudah ada, dilewati (gunakan --force untuk overwrite): ` +
|
|
147
|
+
`${componentsSkipped.map((p) => path.relative(workingDir, p)).join(', ')}`
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const processorDir = path.join(workingDir, 'src', 'modules', projectName, 'processor', 'auth');
|
|
152
|
+
const { written: processorsWritten, skipped: processorsSkipped } = generateAuthProcessors({
|
|
153
|
+
processorDir,
|
|
154
|
+
force: args.force === true
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
if (processorsWritten.length > 0) {
|
|
158
|
+
console.log(
|
|
159
|
+
`Processor auth ditulis: ${processorsWritten.map((p) => path.relative(workingDir, p)).join(', ')}`
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
if (processorsSkipped.length > 0) {
|
|
163
|
+
console.log(
|
|
164
|
+
`Processor auth sudah ada, dilewati (gunakan --force untuk overwrite): ` +
|
|
165
|
+
`${processorsSkipped.map((p) => path.relative(workingDir, p)).join(', ')}`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
const envResult = injectAuthEnv({ configPath });
|
|
169
|
+
const dependencyResult = ensureAuthDependencies({ workingDir });
|
|
170
|
+
|
|
171
|
+
console.log('');
|
|
172
|
+
console.log(`Environment auth (${path.relative(workingDir, envResult.filePath) || envResult.filePath}):`);
|
|
173
|
+
if (envResult.added.length > 0) {
|
|
174
|
+
console.log(` ditambahkan: ${envResult.added.join(', ')}`);
|
|
175
|
+
}
|
|
176
|
+
if (envResult.skipped.length > 0) {
|
|
177
|
+
console.log(` sudah ada, dilewati (nilai existing dipertahankan): ${envResult.skipped.join(', ')}`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
console.log('Dependency runtime (bcrypt, jsonwebtoken):');
|
|
181
|
+
for (const [depName, info] of Object.entries(dependencyResult.resolution)) {
|
|
182
|
+
console.log(` ${depName}: ${info.resolvable ? `resolvable (${info.resolvedPath})` : 'TIDAK resolvable dari project target'}`);
|
|
183
|
+
}
|
|
184
|
+
if (dependencyResult.packageJson.hasPackageJson) {
|
|
185
|
+
if (dependencyResult.packageJson.added.length > 0) {
|
|
186
|
+
console.log(` package.json diperbarui, dependencies ditambahkan: ${dependencyResult.packageJson.added.join(', ')}`);
|
|
187
|
+
} else {
|
|
188
|
+
console.log(' package.json project sudah mencantumkan bcrypt & jsonwebtoken (tidak diubah)');
|
|
189
|
+
}
|
|
190
|
+
} else {
|
|
191
|
+
console.log(' package.json tidak ditemukan di project target, tidak dicatat otomatis');
|
|
192
|
+
}
|
|
193
|
+
if (dependencyResult.needsInstallInstruction) {
|
|
194
|
+
console.log(' Jalankan "npm install bcrypt jsonwebtoken" di project target sebelum start server.');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
console.log('');
|
|
198
|
+
console.log('Auth extension terpasang.');
|
|
199
|
+
console.log('Langkah lanjutan:');
|
|
200
|
+
if (envResult.jwtSecretGenerated) {
|
|
201
|
+
console.log(' - JWT_SECRET baru di-generate acak; rotate berkala sesuai kebijakan keamanan bila perlu.');
|
|
202
|
+
}
|
|
203
|
+
if (dependencyResult.needsInstallInstruction) {
|
|
204
|
+
console.log(' - Jalankan npm install agar bcrypt/jsonwebtoken terpasang sebelum start server.');
|
|
205
|
+
}
|
|
206
|
+
console.log(' - Restart server agar perubahan environment dan route auth termuat.');
|
|
207
|
+
console.log('');
|
|
208
|
+
}
|
|
209
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Contract: project sdk
|
|
5
|
+
*
|
|
6
|
+
* Menghasilkan SDK JavaScript untuk satu project RESTForge — satu layer tipis di
|
|
7
|
+
* atas REST API hasil generate, dipakai frontend apa pun tanpa menulis ulang
|
|
8
|
+
* boilerplate fetch/$.ajax.
|
|
9
|
+
*
|
|
10
|
+
* Rujukan desain: docs/plan/sdk-generator-command.md.
|
|
11
|
+
*
|
|
12
|
+
* Versi awal:
|
|
13
|
+
* - TANPA auth (flag --with-auth & core/auth-client.js menyusul terpisah).
|
|
14
|
+
* - --generate hanya MENULIS source buildable (src/, package.json, tsup.config.js);
|
|
15
|
+
* `npm install && npm run build` adalah langkah terpisah milik user.
|
|
16
|
+
* - Sumber kebenaran resource = metadata/<project>.json (key endpoint = slug = segment route).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const { validateSafeName } = require('../../lib/utils/path-validator');
|
|
20
|
+
const { generateSdk, resolveBaseUrl } = require('../../lib/sdk/generator');
|
|
21
|
+
|
|
22
|
+
module.exports = {
|
|
23
|
+
resource: 'project',
|
|
24
|
+
verb: 'sdk',
|
|
25
|
+
description: 'Generate a JavaScript SDK for a project (derived from backend metadata + payload)',
|
|
26
|
+
category: 'generation',
|
|
27
|
+
flags: {
|
|
28
|
+
project: {
|
|
29
|
+
type: 'string',
|
|
30
|
+
required: true,
|
|
31
|
+
description: 'Target project name (also the SDK package name)'
|
|
32
|
+
},
|
|
33
|
+
generate: {
|
|
34
|
+
type: 'boolean',
|
|
35
|
+
required: true,
|
|
36
|
+
description: 'Trigger SDK source generation'
|
|
37
|
+
},
|
|
38
|
+
'sdk-path': {
|
|
39
|
+
type: 'string',
|
|
40
|
+
required: false,
|
|
41
|
+
default: null,
|
|
42
|
+
description: 'Output folder for the SDK source (default: <project-root>/sdk)'
|
|
43
|
+
},
|
|
44
|
+
'base-url': {
|
|
45
|
+
type: 'string',
|
|
46
|
+
required: false,
|
|
47
|
+
default: null,
|
|
48
|
+
description: 'Override the API base URL baked into sdk-client.js (default: derived from the project config)'
|
|
49
|
+
},
|
|
50
|
+
force: {
|
|
51
|
+
type: 'boolean',
|
|
52
|
+
required: false,
|
|
53
|
+
default: false,
|
|
54
|
+
description: 'Overwrite existing SDK source'
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
examples: [
|
|
58
|
+
'npx restforge project sdk --generate --project=myapp',
|
|
59
|
+
'npx restforge project sdk --generate --project=myapp --sdk-path=./client-sdk',
|
|
60
|
+
'npx restforge project sdk --generate --project=myapp --force'
|
|
61
|
+
],
|
|
62
|
+
async handler(args) {
|
|
63
|
+
const project = validateSafeName(args.project, 'project');
|
|
64
|
+
|
|
65
|
+
if (args.generate !== true) {
|
|
66
|
+
const err = new Error('The --generate flag must be set to run SDK generation.');
|
|
67
|
+
err.exitCode = 2;
|
|
68
|
+
throw err;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const workingDir = process.cwd();
|
|
72
|
+
|
|
73
|
+
console.log('');
|
|
74
|
+
console.log(`Generating SDK for project '${project}'...`);
|
|
75
|
+
console.log('');
|
|
76
|
+
|
|
77
|
+
const baseUrl = resolveBaseUrl({
|
|
78
|
+
workingDir,
|
|
79
|
+
project,
|
|
80
|
+
override: args['base-url'],
|
|
81
|
+
log: (line) => console.log(` ${line}`)
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const result = generateSdk({
|
|
85
|
+
workingDir,
|
|
86
|
+
project,
|
|
87
|
+
sdkPath: args['sdk-path'],
|
|
88
|
+
baseUrl,
|
|
89
|
+
force: args.force === true,
|
|
90
|
+
log: (line) => console.log(` ${line}`)
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
console.log('');
|
|
94
|
+
console.log('==========================================');
|
|
95
|
+
console.log('SDK GENERATION COMPLETE');
|
|
96
|
+
console.log('==========================================');
|
|
97
|
+
console.log(`Project : ${project}`);
|
|
98
|
+
console.log(`Output : ${result.outputDir}`);
|
|
99
|
+
console.log(`Base URL : ${result.baseUrl}`);
|
|
100
|
+
console.log(`Auth : ${result.hasAuth ? 'enabled (client.auth)' : 'none'}`);
|
|
101
|
+
console.log(`Resources : ${result.resources.length} (${result.resources.join(', ')})`);
|
|
102
|
+
console.log('');
|
|
103
|
+
console.log('Next steps (optional, owned by you):');
|
|
104
|
+
console.log(` cd ${result.outputDir}`);
|
|
105
|
+
console.log(' npm install');
|
|
106
|
+
console.log(' npm run build');
|
|
107
|
+
console.log(' npm run deploy # copy into a frontend app js/ folder (asks for target)');
|
|
108
|
+
console.log(' # or: node deploy.mjs <target-app-js-folder>');
|
|
109
|
+
console.log('==========================================');
|
|
110
|
+
console.log('');
|
|
111
|
+
}
|
|
112
|
+
};
|
|
@@ -119,6 +119,12 @@ function parseArgs(argv, contract) {
|
|
|
119
119
|
|
|
120
120
|
if (!valueProvided) {
|
|
121
121
|
if (i + 1 >= argv.length || argv[i + 1].startsWith('--')) {
|
|
122
|
+
if (flagDef.bareDefault !== undefined) {
|
|
123
|
+
args[flagName] = flagDef.bareDefault;
|
|
124
|
+
seenFlags.add(flagName);
|
|
125
|
+
i += 1;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
122
128
|
errors.push(`Flag --${flagName} requires a value`);
|
|
123
129
|
i += 1;
|
|
124
130
|
continue;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generator component middleware + router auth (Fungsi 3a). Merender aset
|
|
5
|
+
* template di `templates/` via template-renderer, lalu menulis ke lokasi
|
|
6
|
+
* target memakai writeFileWithBackup — perilaku force/skip identik Fungsi 1
|
|
7
|
+
* (sdf-generator.js): tanpa --force file existing di-skip, dengan --force
|
|
8
|
+
* di-overwrite + backup.
|
|
9
|
+
*
|
|
10
|
+
* Processor (Fungsi 3b, Phase 04) TIDAK ditulis di sini.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
|
|
16
|
+
const { renderTemplate } = require('./template-renderer');
|
|
17
|
+
const FileUtils = require('../utils/file-utils');
|
|
18
|
+
const { AUTH_MIDDLEWARE_NAME, AUTH_ROUTER_NAME } = require('./prefix');
|
|
19
|
+
|
|
20
|
+
const TEMPLATES_DIR = path.join(__dirname, 'templates');
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {{ middlewareDir: string, routerDir: string, projectName: string, force?: boolean }} options
|
|
24
|
+
* @returns {{ written: string[], skipped: string[] }}
|
|
25
|
+
*/
|
|
26
|
+
function generateAuthComponents({ middlewareDir, routerDir, projectName, force = false }) {
|
|
27
|
+
const targets = [
|
|
28
|
+
{
|
|
29
|
+
templateFile: 'rfx_auth-middleware.js.tmpl',
|
|
30
|
+
targetPath: path.join(middlewareDir, `${AUTH_MIDDLEWARE_NAME}.js`),
|
|
31
|
+
params: {}
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
templateFile: 'rfx_auth.js.tmpl',
|
|
35
|
+
targetPath: path.join(routerDir, `${AUTH_ROUTER_NAME}.js`),
|
|
36
|
+
params: { PROJECT_NAME: projectName }
|
|
37
|
+
}
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const written = [];
|
|
41
|
+
const skipped = [];
|
|
42
|
+
|
|
43
|
+
for (const target of targets) {
|
|
44
|
+
if (fs.existsSync(target.targetPath) && !force) {
|
|
45
|
+
skipped.push(target.targetPath);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const templatePath = path.join(TEMPLATES_DIR, target.templateFile);
|
|
50
|
+
const content = renderTemplate(templatePath, target.params);
|
|
51
|
+
FileUtils.writeFileWithBackup(target.targetPath, content, true);
|
|
52
|
+
written.push(target.targetPath);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return { written, skipped };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = { generateAuthComponents };
|