@aifabrix/builder 2.32.2 → 2.32.3
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/lib/api/index.js +10 -5
- package/lib/api/wizard.api.js +52 -21
- package/lib/app/list.js +62 -28
- package/lib/app/prompts.js +9 -5
- package/lib/app/rotate-secret.js +2 -1
- package/lib/cli.js +33 -4
- package/lib/commands/auth-status.js +262 -0
- package/lib/commands/login-device.js +17 -12
- package/lib/commands/login.js +16 -9
- package/lib/commands/wizard.js +63 -69
- package/lib/datasource/deploy.js +5 -2
- package/lib/external-system/deploy.js +3 -2
- package/lib/external-system/download.js +2 -1
- package/lib/external-system/test-auth.js +2 -1
- package/lib/schema/application-schema.json +1 -1
- package/lib/schema/external-datasource.schema.json +301 -15
- package/lib/schema/external-system.schema.json +1 -1
- package/lib/utils/api.js +20 -7
- package/lib/utils/app-register-display.js +2 -1
- package/lib/utils/cli-utils.js +3 -1
- package/lib/utils/controller-url.js +67 -0
- package/lib/utils/env-map.js +2 -1
- package/lib/utils/error-formatter.js +100 -28
- package/lib/utils/token-manager.js +60 -0
- package/lib/validation/validator.js +2 -1
- package/package.json +1 -1
- package/templates/applications/README.md.hbs +2 -2
- package/templates/external-system/external-system.json.hbs +1 -1
|
@@ -22,6 +22,7 @@ const { detectAppType } = require('../utils/paths');
|
|
|
22
22
|
const logger = require('../utils/logger');
|
|
23
23
|
const { generateEnvTemplate } = require('../utils/external-system-env-helpers');
|
|
24
24
|
const { generateVariablesYaml, generateReadme } = require('./download-helpers');
|
|
25
|
+
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
25
26
|
|
|
26
27
|
/**
|
|
27
28
|
* Validates system type from downloaded application
|
|
@@ -126,7 +127,7 @@ function handlePartialDownload(systemKey, systemData, datasourceErrors) {
|
|
|
126
127
|
*/
|
|
127
128
|
async function setupAuthenticationAndDataplane(systemKey, options, config) {
|
|
128
129
|
const environment = options.environment || 'dev';
|
|
129
|
-
const controllerUrl = options
|
|
130
|
+
const controllerUrl = await resolveControllerUrl(options, config);
|
|
130
131
|
const authConfig = await getDeploymentAuth(controllerUrl, environment, systemKey);
|
|
131
132
|
|
|
132
133
|
if (!authConfig.token && !authConfig.clientId) {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
const { getDeploymentAuth } = require('../utils/token-manager');
|
|
12
12
|
const { getDataplaneUrl } = require('../datasource/deploy');
|
|
13
|
+
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Setup authentication and get dataplane URL for integration tests
|
|
@@ -22,7 +23,7 @@ const { getDataplaneUrl } = require('../datasource/deploy');
|
|
|
22
23
|
*/
|
|
23
24
|
async function setupIntegrationTestAuth(appName, options, config) {
|
|
24
25
|
const environment = options.environment || 'dev';
|
|
25
|
-
const controllerUrl = options
|
|
26
|
+
const controllerUrl = await resolveControllerUrl(options, config);
|
|
26
27
|
const authConfig = await getDeploymentAuth(controllerUrl, environment, appName);
|
|
27
28
|
|
|
28
29
|
if (!authConfig.token && !authConfig.clientId) {
|
|
@@ -3,21 +3,21 @@
|
|
|
3
3
|
"$id":"https://raw.githubusercontent.com/esystemsdev/aifabrix-builder/refs/heads/main/lib/schema/external-datasource.schema.json",
|
|
4
4
|
"title":"External Data Source",
|
|
5
5
|
"description":"Configuration for AI Fabrix ExternalDataSource entities. Includes metadata schema, data dimensions, transformation mappings, OpenAPI/MCP exposure, execution logic, and sync behavior.",
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
6
|
+
"metadata":{
|
|
7
|
+
"key":"external-datasource-schema",
|
|
8
|
+
"name":"External Data Source Configuration Schema",
|
|
9
|
+
"description":"JSON schema for validating ExternalDataSource configuration files",
|
|
10
|
+
"version":"2.1.0",
|
|
11
|
+
"type":"schema",
|
|
12
|
+
"category":"integration",
|
|
13
|
+
"author":"AI Fabrix Team",
|
|
14
|
+
"createdAt":"2024-01-01T00:00:00Z",
|
|
15
|
+
"updatedAt":"2026-01-19T00:00:00Z",
|
|
16
|
+
"compatibility":{
|
|
17
|
+
"minVersion":"1.0.0",
|
|
18
|
+
"maxVersion":"3.0.0",
|
|
19
|
+
"deprecated":false
|
|
20
|
+
},
|
|
21
21
|
"tags":[
|
|
22
22
|
"schema",
|
|
23
23
|
"external-datasource",
|
|
@@ -80,6 +80,17 @@
|
|
|
80
80
|
"Updated descriptions to reflect dimension-first data model approach"
|
|
81
81
|
],
|
|
82
82
|
"breaking":true
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"version":"2.1.0",
|
|
86
|
+
"date":"2026-01-19T00:00:00Z",
|
|
87
|
+
"changes":[
|
|
88
|
+
"Added error semantics (onError object) to all CIP step types with error classification, retry, compensation, and failPipeline",
|
|
89
|
+
"Added idempotency configuration to CIP definition for execution-level replay guarantees",
|
|
90
|
+
"Added lineage configuration to field mappings for field-level explainability and compliance",
|
|
91
|
+
"Added contract versioning configuration to datasource root for CI/CD safety and agent stability"
|
|
92
|
+
],
|
|
93
|
+
"breaking":false
|
|
83
94
|
}
|
|
84
95
|
]
|
|
85
96
|
},
|
|
@@ -223,6 +234,9 @@
|
|
|
223
234
|
}
|
|
224
235
|
},
|
|
225
236
|
"additionalProperties":false
|
|
237
|
+
},
|
|
238
|
+
"lineage":{
|
|
239
|
+
"$ref":"#/$defs/fieldMappingLineage"
|
|
226
240
|
}
|
|
227
241
|
}
|
|
228
242
|
}
|
|
@@ -846,6 +860,9 @@
|
|
|
846
860
|
}
|
|
847
861
|
},
|
|
848
862
|
"additionalProperties":true
|
|
863
|
+
},
|
|
864
|
+
"contract":{
|
|
865
|
+
"$ref":"#/$defs/contractConfig"
|
|
849
866
|
}
|
|
850
867
|
},
|
|
851
868
|
"$defs":{
|
|
@@ -931,6 +948,9 @@
|
|
|
931
948
|
}
|
|
932
949
|
},
|
|
933
950
|
"additionalProperties":false
|
|
951
|
+
},
|
|
952
|
+
"idempotency":{
|
|
953
|
+
"$ref":"#/$defs/idempotencyConfig"
|
|
934
954
|
}
|
|
935
955
|
},
|
|
936
956
|
"additionalProperties":false
|
|
@@ -1103,6 +1123,14 @@
|
|
|
1103
1123
|
},
|
|
1104
1124
|
"lineage":{
|
|
1105
1125
|
"$ref":"#/$defs/cipLineage"
|
|
1126
|
+
},
|
|
1127
|
+
"stepId":{
|
|
1128
|
+
"type":"string",
|
|
1129
|
+
"pattern":"^[a-z0-9-_]+$",
|
|
1130
|
+
"description":"Unique identifier for this step within the operation"
|
|
1131
|
+
},
|
|
1132
|
+
"onError":{
|
|
1133
|
+
"$ref":"#/$defs/cipOnErrorConfig"
|
|
1106
1134
|
}
|
|
1107
1135
|
},
|
|
1108
1136
|
"additionalProperties":false
|
|
@@ -1160,6 +1188,14 @@
|
|
|
1160
1188
|
},
|
|
1161
1189
|
"lineage":{
|
|
1162
1190
|
"$ref":"#/$defs/cipLineage"
|
|
1191
|
+
},
|
|
1192
|
+
"stepId":{
|
|
1193
|
+
"type":"string",
|
|
1194
|
+
"pattern":"^[a-z0-9-_]+$",
|
|
1195
|
+
"description":"Unique identifier for this step within the operation"
|
|
1196
|
+
},
|
|
1197
|
+
"onError":{
|
|
1198
|
+
"$ref":"#/$defs/cipOnErrorConfig"
|
|
1163
1199
|
}
|
|
1164
1200
|
},
|
|
1165
1201
|
"additionalProperties":false
|
|
@@ -1197,6 +1233,14 @@
|
|
|
1197
1233
|
},
|
|
1198
1234
|
"lineage":{
|
|
1199
1235
|
"$ref":"#/$defs/cipLineage"
|
|
1236
|
+
},
|
|
1237
|
+
"stepId":{
|
|
1238
|
+
"type":"string",
|
|
1239
|
+
"pattern":"^[a-z0-9-_]+$",
|
|
1240
|
+
"description":"Unique identifier for this step within the operation"
|
|
1241
|
+
},
|
|
1242
|
+
"onError":{
|
|
1243
|
+
"$ref":"#/$defs/cipOnErrorConfig"
|
|
1200
1244
|
}
|
|
1201
1245
|
},
|
|
1202
1246
|
"additionalProperties":false
|
|
@@ -1236,6 +1280,14 @@
|
|
|
1236
1280
|
},
|
|
1237
1281
|
"lineage":{
|
|
1238
1282
|
"$ref":"#/$defs/cipLineage"
|
|
1283
|
+
},
|
|
1284
|
+
"stepId":{
|
|
1285
|
+
"type":"string",
|
|
1286
|
+
"pattern":"^[a-z0-9-_]+$",
|
|
1287
|
+
"description":"Unique identifier for this step within the operation"
|
|
1288
|
+
},
|
|
1289
|
+
"onError":{
|
|
1290
|
+
"$ref":"#/$defs/cipOnErrorConfig"
|
|
1239
1291
|
}
|
|
1240
1292
|
},
|
|
1241
1293
|
"additionalProperties":false
|
|
@@ -1272,6 +1324,14 @@
|
|
|
1272
1324
|
},
|
|
1273
1325
|
"lineage":{
|
|
1274
1326
|
"$ref":"#/$defs/cipLineage"
|
|
1327
|
+
},
|
|
1328
|
+
"stepId":{
|
|
1329
|
+
"type":"string",
|
|
1330
|
+
"pattern":"^[a-z0-9-_]+$",
|
|
1331
|
+
"description":"Unique identifier for this step within the operation"
|
|
1332
|
+
},
|
|
1333
|
+
"onError":{
|
|
1334
|
+
"$ref":"#/$defs/cipOnErrorConfig"
|
|
1275
1335
|
}
|
|
1276
1336
|
},
|
|
1277
1337
|
"additionalProperties":false
|
|
@@ -1298,6 +1358,232 @@
|
|
|
1298
1358
|
},
|
|
1299
1359
|
"lineage":{
|
|
1300
1360
|
"$ref":"#/$defs/cipLineage"
|
|
1361
|
+
},
|
|
1362
|
+
"stepId":{
|
|
1363
|
+
"type":"string",
|
|
1364
|
+
"pattern":"^[a-z0-9-_]+$",
|
|
1365
|
+
"description":"Unique identifier for this step within the operation"
|
|
1366
|
+
},
|
|
1367
|
+
"onError":{
|
|
1368
|
+
"$ref":"#/$defs/cipOnErrorConfig"
|
|
1369
|
+
}
|
|
1370
|
+
},
|
|
1371
|
+
"additionalProperties":false
|
|
1372
|
+
},
|
|
1373
|
+
"cipRetryConfig":{
|
|
1374
|
+
"type":"object",
|
|
1375
|
+
"description":"Retry configuration for error handling.",
|
|
1376
|
+
"properties":{
|
|
1377
|
+
"attempts":{
|
|
1378
|
+
"type":"integer",
|
|
1379
|
+
"minimum":1,
|
|
1380
|
+
"maximum":10,
|
|
1381
|
+
"default":3,
|
|
1382
|
+
"description":"Maximum number of retry attempts"
|
|
1383
|
+
},
|
|
1384
|
+
"backoff":{
|
|
1385
|
+
"type":"string",
|
|
1386
|
+
"enum":["exponential","linear","fixed"],
|
|
1387
|
+
"default":"exponential",
|
|
1388
|
+
"description":"Backoff strategy: exponential (2^attempt), linear (attempt * base), fixed (constant)"
|
|
1389
|
+
},
|
|
1390
|
+
"maxDelaySeconds":{
|
|
1391
|
+
"type":"number",
|
|
1392
|
+
"minimum":1.0,
|
|
1393
|
+
"maximum":300.0,
|
|
1394
|
+
"default":60.0,
|
|
1395
|
+
"description":"Maximum delay between retries in seconds"
|
|
1396
|
+
}
|
|
1397
|
+
},
|
|
1398
|
+
"additionalProperties":false
|
|
1399
|
+
},
|
|
1400
|
+
"cipCompensationConfig":{
|
|
1401
|
+
"type":"object",
|
|
1402
|
+
"description":"Compensation (rollback) configuration for error handling.",
|
|
1403
|
+
"properties":{
|
|
1404
|
+
"enabled":{
|
|
1405
|
+
"type":"boolean",
|
|
1406
|
+
"default":false,
|
|
1407
|
+
"description":"Whether compensation is enabled"
|
|
1408
|
+
},
|
|
1409
|
+
"stepRef":{
|
|
1410
|
+
"type":"string",
|
|
1411
|
+
"pattern":"^[a-z0-9-_]+$",
|
|
1412
|
+
"description":"Reference to a previously executed step by stepId to execute as compensation"
|
|
1413
|
+
}
|
|
1414
|
+
},
|
|
1415
|
+
"additionalProperties":false
|
|
1416
|
+
},
|
|
1417
|
+
"cipOnErrorConfig":{
|
|
1418
|
+
"type":"object",
|
|
1419
|
+
"description":"Enhanced error handling configuration for CIP steps.",
|
|
1420
|
+
"required":["class"],
|
|
1421
|
+
"properties":{
|
|
1422
|
+
"class":{
|
|
1423
|
+
"type":"string",
|
|
1424
|
+
"enum":["transient","permanent","validation","auth"],
|
|
1425
|
+
"description":"Error classification: transient (retryable), permanent (non-retryable), validation (data validation failure), auth (authentication/authorization failure)"
|
|
1426
|
+
},
|
|
1427
|
+
"retry":{
|
|
1428
|
+
"$ref":"#/$defs/cipRetryConfig",
|
|
1429
|
+
"description":"Retry configuration (only applies when class='transient')"
|
|
1430
|
+
},
|
|
1431
|
+
"compensate":{
|
|
1432
|
+
"$ref":"#/$defs/cipCompensationConfig",
|
|
1433
|
+
"description":"Compensation (rollback) configuration"
|
|
1434
|
+
},
|
|
1435
|
+
"failPipeline":{
|
|
1436
|
+
"type":"boolean",
|
|
1437
|
+
"default":true,
|
|
1438
|
+
"description":"Whether step failure should fail the entire pipeline (false allows partial success)"
|
|
1439
|
+
}
|
|
1440
|
+
},
|
|
1441
|
+
"additionalProperties":false
|
|
1442
|
+
},
|
|
1443
|
+
"idempotencyConfig":{
|
|
1444
|
+
"type":"object",
|
|
1445
|
+
"description":"Idempotency configuration for CIP execution.",
|
|
1446
|
+
"required":["key"],
|
|
1447
|
+
"properties":{
|
|
1448
|
+
"key":{
|
|
1449
|
+
"type":"string",
|
|
1450
|
+
"description":"Idempotency key template. Supports variables: {{actor.userId}}, {{raw.requestId}}, {{correlationId}}, {{execution.operation}}, {{execution.datasourceId}}"
|
|
1451
|
+
},
|
|
1452
|
+
"scope":{
|
|
1453
|
+
"type":"string",
|
|
1454
|
+
"enum":["datasource","system"],
|
|
1455
|
+
"default":"datasource",
|
|
1456
|
+
"description":"Idempotency scope: datasource (same key within datasource) or system (same key across all datasources)"
|
|
1457
|
+
},
|
|
1458
|
+
"ttlSeconds":{
|
|
1459
|
+
"type":"integer",
|
|
1460
|
+
"minimum":1,
|
|
1461
|
+
"maximum":604800,
|
|
1462
|
+
"default":86400,
|
|
1463
|
+
"description":"Time to live for idempotency records in seconds (default: 86400 = 24 hours)"
|
|
1464
|
+
},
|
|
1465
|
+
"enforcement":{
|
|
1466
|
+
"type":"string",
|
|
1467
|
+
"enum":["strict","best-effort"],
|
|
1468
|
+
"default":"strict",
|
|
1469
|
+
"description":"Enforcement mode: strict (return previous result, no execution) or best-effort (warn + continue)"
|
|
1470
|
+
}
|
|
1471
|
+
},
|
|
1472
|
+
"additionalProperties":false
|
|
1473
|
+
},
|
|
1474
|
+
"transformationRule":{
|
|
1475
|
+
"type":"object",
|
|
1476
|
+
"description":"Transformation rule applied to a field.",
|
|
1477
|
+
"required":["type"],
|
|
1478
|
+
"properties":{
|
|
1479
|
+
"type":{
|
|
1480
|
+
"type":"string",
|
|
1481
|
+
"description":"Type of transformation: 'mapping', 'function', 'filter', 'aggregate'"
|
|
1482
|
+
},
|
|
1483
|
+
"function":{
|
|
1484
|
+
"type":"string",
|
|
1485
|
+
"description":"Function name (e.g., 'mapStatus', 'toLower', 'trim')"
|
|
1486
|
+
},
|
|
1487
|
+
"rule":{
|
|
1488
|
+
"type":"string",
|
|
1489
|
+
"description":"Human-readable rule description (e.g., 'open → OPEN, closed → CLOSED')"
|
|
1490
|
+
}
|
|
1491
|
+
},
|
|
1492
|
+
"additionalProperties":false
|
|
1493
|
+
},
|
|
1494
|
+
"fieldMappingLineage":{
|
|
1495
|
+
"type":"object",
|
|
1496
|
+
"description":"Lineage metadata for a field mapping.",
|
|
1497
|
+
"required":["sourceFields"],
|
|
1498
|
+
"properties":{
|
|
1499
|
+
"sourceFields":{
|
|
1500
|
+
"type":"array",
|
|
1501
|
+
"minItems":1,
|
|
1502
|
+
"items":{
|
|
1503
|
+
"type":"string"
|
|
1504
|
+
},
|
|
1505
|
+
"description":"Source field paths that contribute to this mapped field (e.g., ['raw.fields.state'])"
|
|
1506
|
+
},
|
|
1507
|
+
"transformations":{
|
|
1508
|
+
"type":"array",
|
|
1509
|
+
"items":{
|
|
1510
|
+
"$ref":"#/$defs/transformationRule"
|
|
1511
|
+
},
|
|
1512
|
+
"description":"List of transformations applied to source fields"
|
|
1513
|
+
},
|
|
1514
|
+
"confidence":{
|
|
1515
|
+
"type":"number",
|
|
1516
|
+
"minimum":0.0,
|
|
1517
|
+
"maximum":1.0,
|
|
1518
|
+
"default":1.0,
|
|
1519
|
+
"description":"Confidence score for this mapping (0.0-1.0). Default: 1.0 for deterministic transformations."
|
|
1520
|
+
}
|
|
1521
|
+
},
|
|
1522
|
+
"additionalProperties":false
|
|
1523
|
+
},
|
|
1524
|
+
"compatibilityConfig":{
|
|
1525
|
+
"type":"object",
|
|
1526
|
+
"description":"Compatibility configuration for contract versioning.",
|
|
1527
|
+
"properties":{
|
|
1528
|
+
"backwardCompatible":{
|
|
1529
|
+
"type":"boolean",
|
|
1530
|
+
"default":true,
|
|
1531
|
+
"description":"Whether this version is backward compatible with previous versions"
|
|
1532
|
+
},
|
|
1533
|
+
"breakingChanges":{
|
|
1534
|
+
"type":"array",
|
|
1535
|
+
"items":{
|
|
1536
|
+
"type":"string"
|
|
1537
|
+
},
|
|
1538
|
+
"description":"List of breaking change types: removeField, changeType, changeSemantics"
|
|
1539
|
+
}
|
|
1540
|
+
},
|
|
1541
|
+
"additionalProperties":false
|
|
1542
|
+
},
|
|
1543
|
+
"deprecationConfig":{
|
|
1544
|
+
"type":"object",
|
|
1545
|
+
"description":"Deprecation configuration for contract versioning.",
|
|
1546
|
+
"properties":{
|
|
1547
|
+
"sunsetDate":{
|
|
1548
|
+
"type":"string",
|
|
1549
|
+
"pattern":"^\\d{4}-\\d{2}-\\d{2}$",
|
|
1550
|
+
"description":"Date when this contract version will be sunset (YYYY-MM-DD)"
|
|
1551
|
+
},
|
|
1552
|
+
"replacedBy":{
|
|
1553
|
+
"type":"string",
|
|
1554
|
+
"description":"Contract name/version that replaces this deprecated version"
|
|
1555
|
+
}
|
|
1556
|
+
},
|
|
1557
|
+
"additionalProperties":false
|
|
1558
|
+
},
|
|
1559
|
+
"contractConfig":{
|
|
1560
|
+
"type":"object",
|
|
1561
|
+
"description":"Contract versioning configuration for datasource.",
|
|
1562
|
+
"required":["name","version"],
|
|
1563
|
+
"properties":{
|
|
1564
|
+
"name":{
|
|
1565
|
+
"type":"string",
|
|
1566
|
+
"pattern":"^[a-z0-9.-]+$",
|
|
1567
|
+
"description":"Contract identifier (e.g., 'jira.issue', separate from datasource key)"
|
|
1568
|
+
},
|
|
1569
|
+
"version":{
|
|
1570
|
+
"type":"string",
|
|
1571
|
+
"pattern":"^[0-9]+\\.[0-9]+\\.[0-9]+$",
|
|
1572
|
+
"description":"Semantic version of the contract (e.g., '2.1.0')"
|
|
1573
|
+
},
|
|
1574
|
+
"status":{
|
|
1575
|
+
"type":"string",
|
|
1576
|
+
"enum":["stable","deprecated","experimental"],
|
|
1577
|
+
"default":"stable",
|
|
1578
|
+
"description":"Contract status: stable (production-ready), deprecated (being phased out), experimental (testing)"
|
|
1579
|
+
},
|
|
1580
|
+
"compatibility":{
|
|
1581
|
+
"$ref":"#/$defs/compatibilityConfig",
|
|
1582
|
+
"description":"Compatibility information for this contract version"
|
|
1583
|
+
},
|
|
1584
|
+
"deprecation":{
|
|
1585
|
+
"$ref":"#/$defs/deprecationConfig",
|
|
1586
|
+
"description":"Deprecation information if status is 'deprecated'"
|
|
1301
1587
|
}
|
|
1302
1588
|
},
|
|
1303
1589
|
"additionalProperties":false
|
package/lib/utils/api.js
CHANGED
|
@@ -209,10 +209,20 @@ function extractControllerUrl(url) {
|
|
|
209
209
|
* Automatically refreshes device token on 401 errors if refresh token is available
|
|
210
210
|
* @param {string} url - API endpoint URL
|
|
211
211
|
* @param {Object} options - Fetch options
|
|
212
|
-
* @param {string}
|
|
212
|
+
* @param {string|Object} tokenOrAuthConfig - Bearer token string or authConfig object
|
|
213
|
+
* @param {string} [tokenOrAuthConfig.token] - Bearer token (if object)
|
|
214
|
+
* @param {string} [tokenOrAuthConfig.controller] - Controller URL for token refresh (if object)
|
|
213
215
|
* @returns {Promise<Object>} Response object
|
|
214
216
|
*/
|
|
215
|
-
async function authenticatedApiCall(url, options = {},
|
|
217
|
+
async function authenticatedApiCall(url, options = {}, tokenOrAuthConfig) {
|
|
218
|
+
// Support both string token (backward compat) and authConfig object
|
|
219
|
+
const token = typeof tokenOrAuthConfig === 'string'
|
|
220
|
+
? tokenOrAuthConfig
|
|
221
|
+
: tokenOrAuthConfig?.token;
|
|
222
|
+
const authControllerUrl = typeof tokenOrAuthConfig === 'object'
|
|
223
|
+
? tokenOrAuthConfig?.controller
|
|
224
|
+
: null;
|
|
225
|
+
|
|
216
226
|
const headers = {
|
|
217
227
|
'Content-Type': 'application/json',
|
|
218
228
|
...options.headers
|
|
@@ -230,12 +240,15 @@ async function authenticatedApiCall(url, options = {}, token) {
|
|
|
230
240
|
// Handle 401 errors with automatic token refresh for device tokens
|
|
231
241
|
if (!response.success && response.status === 401) {
|
|
232
242
|
try {
|
|
233
|
-
//
|
|
234
|
-
|
|
243
|
+
// Use controller URL from authConfig if available, otherwise extract from request URL
|
|
244
|
+
// This is important when the request URL is a dataplane URL but the token
|
|
245
|
+
// is stored under the controller URL
|
|
246
|
+
const controllerUrl = authControllerUrl || extractControllerUrl(url);
|
|
235
247
|
|
|
236
|
-
// Try to
|
|
237
|
-
|
|
238
|
-
const
|
|
248
|
+
// Try to force refresh device token on 401 (regardless of local expiry time)
|
|
249
|
+
// because the server rejected the token
|
|
250
|
+
const { forceRefreshDeviceToken } = require('./token-manager');
|
|
251
|
+
const refreshedToken = await forceRefreshDeviceToken(controllerUrl);
|
|
239
252
|
|
|
240
253
|
if (refreshedToken && refreshedToken.token) {
|
|
241
254
|
// Retry request with new token
|
|
@@ -48,7 +48,8 @@ function displayRegistrationResults(data, apiUrl, environment) {
|
|
|
48
48
|
logger.log(chalk.bold('📋 Application Details:'));
|
|
49
49
|
logger.log(` ID: ${data.application.id}`);
|
|
50
50
|
logger.log(` Key: ${data.application.key}`);
|
|
51
|
-
logger.log(` Display Name: ${data.application.displayName}
|
|
51
|
+
logger.log(` Display Name: ${data.application.displayName}`);
|
|
52
|
+
logger.log(` Controller: ${apiUrl}\n`);
|
|
52
53
|
|
|
53
54
|
logger.log(chalk.bold.yellow('🔑 CREDENTIALS (save these immediately):'));
|
|
54
55
|
logger.log(chalk.yellow(` Client ID: ${data.credentials.clientId}`));
|
package/lib/utils/cli-utils.js
CHANGED
|
@@ -135,7 +135,9 @@ function formatAzureError(errorMsg) {
|
|
|
135
135
|
' Use format: *.azurecr.io (e.g., myacr.azurecr.io)'
|
|
136
136
|
];
|
|
137
137
|
}
|
|
138
|
-
|
|
138
|
+
// Only match ACR-specific authentication errors, not general authentication failures
|
|
139
|
+
if (errorMsg.includes('ACR') || errorMsg.includes('azurecr.io') ||
|
|
140
|
+
(errorMsg.includes('authenticate') && (errorMsg.includes('registry') || errorMsg.includes('container')))) {
|
|
139
141
|
return [
|
|
140
142
|
' Azure Container Registry authentication failed.',
|
|
141
143
|
' Run: az acr login --name <registry-name>',
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Controller URL Resolution Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides utilities for resolving controller URLs with developer ID-based defaults
|
|
5
|
+
* and fallback chain support.
|
|
6
|
+
*
|
|
7
|
+
* @fileoverview Controller URL resolution utilities
|
|
8
|
+
* @author AI Fabrix Team
|
|
9
|
+
* @version 2.0.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { getDeveloperIdNumber } = require('./env-map');
|
|
13
|
+
const devConfig = require('./dev-config');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Calculate default controller URL based on developer ID
|
|
17
|
+
* Uses getDevPorts to get the app port which is adjusted by developer ID
|
|
18
|
+
* Developer ID 0 = http://localhost:3000
|
|
19
|
+
* Developer ID 1 = http://localhost:3100
|
|
20
|
+
* Developer ID 2 = http://localhost:3200
|
|
21
|
+
* @async
|
|
22
|
+
* @function getDefaultControllerUrl
|
|
23
|
+
* @returns {Promise<string>} Default controller URL
|
|
24
|
+
*/
|
|
25
|
+
async function getDefaultControllerUrl() {
|
|
26
|
+
const developerId = await getDeveloperIdNumber(null);
|
|
27
|
+
const ports = devConfig.getDevPorts(developerId);
|
|
28
|
+
return `http://localhost:${ports.app}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Resolve controller URL with fallback chain
|
|
33
|
+
* Priority:
|
|
34
|
+
* 1. options.controller (explicit option)
|
|
35
|
+
* 2. config.deployment?.controllerUrl (from config)
|
|
36
|
+
* 3. getDefaultControllerUrl() (developer ID-based default)
|
|
37
|
+
* @async
|
|
38
|
+
* @function resolveControllerUrl
|
|
39
|
+
* @param {Object} options - Command options
|
|
40
|
+
* @param {string} [options.controller] - Explicit controller URL option
|
|
41
|
+
* @param {Object} config - Configuration object
|
|
42
|
+
* @param {Object} [config.deployment] - Deployment configuration
|
|
43
|
+
* @param {string} [config.deployment.controllerUrl] - Controller URL from config
|
|
44
|
+
* @returns {Promise<string>} Resolved controller URL
|
|
45
|
+
*/
|
|
46
|
+
async function resolveControllerUrl(options, config) {
|
|
47
|
+
// Priority 1: Explicit option
|
|
48
|
+
if (options && (options.controller || options.url)) {
|
|
49
|
+
const explicitUrl = options.controller || options.url;
|
|
50
|
+
if (explicitUrl) {
|
|
51
|
+
return explicitUrl.replace(/\/$/, '');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Priority 2: Config file
|
|
56
|
+
if (config?.deployment?.controllerUrl) {
|
|
57
|
+
return config.deployment.controllerUrl.replace(/\/$/, '');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Priority 3: Developer ID-based default
|
|
61
|
+
return await getDefaultControllerUrl();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
module.exports = {
|
|
65
|
+
getDefaultControllerUrl,
|
|
66
|
+
resolveControllerUrl
|
|
67
|
+
};
|