@aifabrix/builder 2.44.6 → 2.45.0
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/.cursor/rules/cli-layout.mdc +7 -3
- package/jest.projects.js +56 -0
- package/lib/app/helpers.js +3 -3
- package/lib/app/index.js +3 -3
- package/lib/app/register.js +7 -6
- package/lib/app/restart-display.js +52 -21
- package/lib/app/rotate-secret.js +7 -6
- package/lib/app/run-helpers.js +15 -8
- package/lib/app/run.js +57 -9
- package/lib/app/show-display.js +7 -0
- package/lib/app/show.js +87 -5
- package/lib/build/index.js +9 -5
- package/lib/cli/infra-guided.js +42 -27
- package/lib/cli/installation-log-command.js +73 -0
- package/lib/cli/setup-app.js +11 -1
- package/lib/cli/setup-auth.js +94 -49
- package/lib/cli/setup-infra-up-dataplane-action.js +111 -0
- package/lib/cli/setup-infra-up-platform-action.js +131 -0
- package/lib/cli/setup-infra.js +60 -119
- package/lib/cli/setup-platform.js +1 -1
- package/lib/cli/setup-utility-resolve.js +132 -0
- package/lib/cli/setup-utility.js +65 -51
- package/lib/commands/app-logs.js +81 -33
- package/lib/commands/auth-config.js +116 -18
- package/lib/commands/setup-modes.js +19 -6
- package/lib/commands/setup-prompts.js +41 -8
- package/lib/commands/setup.js +114 -9
- package/lib/commands/teardown.js +54 -5
- package/lib/commands/up-common.js +48 -14
- package/lib/commands/up-dataplane.js +21 -18
- package/lib/commands/up-miso.js +12 -8
- package/lib/commands/upload.js +5 -3
- package/lib/core/audit-logger.js +1 -34
- package/lib/core/config-admin-email.js +56 -0
- package/lib/core/config-normalize.js +60 -0
- package/lib/core/config-registered-controller-urls.js +54 -0
- package/lib/core/config.js +33 -50
- package/lib/core/secrets-ensure-infra.js +1 -1
- package/lib/core/secrets-env-content.js +86 -90
- package/lib/core/secrets-env-declarative-expand.js +170 -0
- package/lib/core/secrets-env-write.js +2 -0
- package/lib/core/secrets-load.js +106 -102
- package/lib/external-system/deploy.js +5 -1
- package/lib/internal/node-fs.js +2 -0
- package/lib/schema/application-schema.json +4 -0
- package/lib/schema/infra.parameter.yaml +10 -0
- package/lib/utils/app-config-resolver.js +24 -1
- package/lib/utils/applications-config-defaults.js +206 -0
- package/lib/utils/auth-config-validator.js +2 -12
- package/lib/utils/bash-secret-env.js +1 -1
- package/lib/utils/compose-generate-docker-compose.js +111 -6
- package/lib/utils/compose-generator.js +17 -8
- package/lib/utils/controller-url.js +50 -7
- package/lib/utils/env-copy.js +99 -14
- package/lib/utils/env-template.js +5 -1
- package/lib/utils/health-check-url.js +18 -15
- package/lib/utils/health-check.js +7 -5
- package/lib/utils/infra-optional-service-flags.js +69 -0
- package/lib/utils/installation-log-core.js +282 -0
- package/lib/utils/installation-log-record.js +237 -0
- package/lib/utils/installation-log.js +123 -0
- package/lib/utils/log-redaction.js +105 -0
- package/lib/utils/manifest-location.js +164 -0
- package/lib/utils/manifest-source-emit.js +162 -0
- package/lib/utils/paths.js +238 -89
- package/lib/utils/remote-secrets-loader.js +7 -1
- package/lib/utils/run-cli-flags.js +29 -0
- package/lib/utils/secrets-canonical.js +10 -3
- package/lib/utils/secrets-path.js +3 -4
- package/lib/utils/secrets-utils.js +20 -10
- package/lib/utils/system-builder-root.js +10 -2
- package/lib/utils/url-declarative-public-base.js +80 -12
- package/lib/utils/url-declarative-resolve-build-urls.js +238 -0
- package/lib/utils/url-declarative-resolve-build.js +24 -393
- package/lib/utils/url-declarative-resolve-expand-token.js +189 -0
- package/lib/utils/url-declarative-resolve-load-doc.js +12 -3
- package/lib/utils/url-declarative-resolve-surface-state.js +102 -0
- package/lib/utils/url-declarative-resolve.js +47 -7
- package/lib/utils/url-declarative-runtime-base-path.js +21 -1
- package/lib/utils/urls-local-registry-scan.js +103 -0
- package/lib/utils/urls-local-registry.js +161 -90
- package/package.json +3 -1
- package/templates/applications/dataplane/application.yaml +4 -0
- package/templates/applications/miso-controller/application.yaml +2 -0
- package/templates/applications/miso-controller/env.template +27 -29
- package/.npmrc.token +0 -1
package/lib/utils/paths.js
CHANGED
|
@@ -44,13 +44,15 @@ function getConfigDirForPaths() {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
|
-
* User-owned `secrets.local.yaml`
|
|
48
|
-
* `
|
|
47
|
+
* User-owned `secrets.local.yaml` beside `config.yaml` (same directory as {@link getConfigDirForPaths}).
|
|
48
|
+
* When `aifabrix-home` is the POSIX home (e.g. `/home/user`), secrets stay under `~/.aifabrix/`, not
|
|
49
|
+
* in the home root. {@link getAifabrixHome} remains for applications base, builder parent, and
|
|
50
|
+
* legacy secrets migration reads in {@link module:lib/utils/secrets-utils}.
|
|
49
51
|
*
|
|
50
52
|
* @returns {string} Absolute path to secrets.local.yaml
|
|
51
53
|
*/
|
|
52
54
|
function getPrimaryUserSecretsLocalPath() {
|
|
53
|
-
return path.join(
|
|
55
|
+
return path.join(getConfigDirForPaths(), 'secrets.local.yaml');
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
/**
|
|
@@ -315,7 +317,8 @@ function getDevDirectory(appName, developerId) {
|
|
|
315
317
|
|
|
316
318
|
/**
|
|
317
319
|
* Gets the application path (builder or integration folder).
|
|
318
|
-
* Matches getBuilderPath / getIntegrationPath
|
|
320
|
+
* Matches {@link getBuilderPath} / {@link getIntegrationPath} (cwd `integration/` / `builder/`, then
|
|
321
|
+
* material `(aifabrix-work | aifabrix-home)` trees).
|
|
319
322
|
* @param {string} appName - Application name
|
|
320
323
|
* @param {string} [appType] - Application type ('external' or other)
|
|
321
324
|
* @returns {string} Absolute path to application directory
|
|
@@ -325,65 +328,99 @@ function getAppPath(appName, appType) {
|
|
|
325
328
|
throw new Error('App name is required and must be a string');
|
|
326
329
|
}
|
|
327
330
|
if (appType === 'external') {
|
|
328
|
-
return getIntegrationPath(appName);
|
|
331
|
+
return module.exports.getIntegrationPath(appName);
|
|
329
332
|
}
|
|
330
|
-
return getBuilderPath(appName);
|
|
333
|
+
return module.exports.getBuilderPath(appName);
|
|
331
334
|
}
|
|
332
335
|
|
|
333
336
|
/**
|
|
334
|
-
*
|
|
335
|
-
*
|
|
336
|
-
*
|
|
337
|
-
*
|
|
337
|
+
* Apps materialization / default repo root: **`aifabrix-work`** (or `AIFABRIX_WORK`) when set, else
|
|
338
|
+
* {@link getAifabrixHome}. Used for `integration/` and `builder/` under that parent (not the CLI
|
|
339
|
+
* install tree). Aligns with setup / `up-platform` / `up-miso` / `up-dataplane` template targets.
|
|
340
|
+
*
|
|
341
|
+
* @returns {string} Absolute directory (no trailing `integration/` or `builder/`)
|
|
338
342
|
*/
|
|
339
|
-
function
|
|
343
|
+
function getAppsMaterializationParent() {
|
|
340
344
|
const work = getAifabrixWork();
|
|
341
345
|
if (work) {
|
|
342
|
-
|
|
343
|
-
const integrationUnderWork = path.join(workNorm, 'integration');
|
|
344
|
-
try {
|
|
345
|
-
if (nodeFs().existsSync(integrationUnderWork)) {
|
|
346
|
-
return workNorm;
|
|
347
|
-
}
|
|
348
|
-
} catch {
|
|
349
|
-
// ignore fs errors
|
|
350
|
-
}
|
|
346
|
+
return path.resolve(work);
|
|
351
347
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
348
|
+
return path.resolve(getAifabrixHome());
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Base directory for legacy callers that meant “non-cwd app tree”: same as {@link getAppsMaterializationParent}.
|
|
353
|
+
*
|
|
354
|
+
* @returns {string}
|
|
355
|
+
*/
|
|
356
|
+
function getIntegrationBuilderBaseDir() {
|
|
357
|
+
return getAppsMaterializationParent();
|
|
359
358
|
}
|
|
360
359
|
|
|
361
360
|
/**
|
|
362
|
-
* Returns the integration root
|
|
361
|
+
* Returns the default integration root under {@link getAppsMaterializationParent} (listing may also
|
|
362
|
+
* scan {@link getCwdIntegrationRoot}; see {@link listIntegrationAppNames}).
|
|
363
|
+
*
|
|
363
364
|
* @returns {string} Absolute path to integration/ directory
|
|
364
365
|
*/
|
|
365
366
|
function getIntegrationRoot() {
|
|
366
|
-
return path.join(
|
|
367
|
+
return path.join(getAppsMaterializationParent(), 'integration');
|
|
367
368
|
}
|
|
368
369
|
|
|
369
370
|
/**
|
|
370
|
-
*
|
|
371
|
+
* Absolute `integration/` next to {@link process.cwd} when that directory exists.
|
|
372
|
+
*
|
|
373
|
+
* @returns {string|null}
|
|
374
|
+
*/
|
|
375
|
+
function getCwdIntegrationRoot() {
|
|
376
|
+
const p = path.join(path.resolve(process.cwd()), 'integration');
|
|
377
|
+
try {
|
|
378
|
+
if (nodeFs().existsSync(p)) {
|
|
379
|
+
const st = nodeFs().statSync(p);
|
|
380
|
+
if (st && typeof st.isDirectory === 'function' && st.isDirectory()) {
|
|
381
|
+
return p;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
} catch {
|
|
385
|
+
// ignore
|
|
386
|
+
}
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Absolute `builder/` next to {@link process.cwd} when that directory exists.
|
|
392
|
+
*
|
|
393
|
+
* @returns {string|null}
|
|
394
|
+
*/
|
|
395
|
+
function getCwdBuilderRoot() {
|
|
396
|
+
const p = path.join(path.resolve(process.cwd()), 'builder');
|
|
397
|
+
try {
|
|
398
|
+
if (nodeFs().existsSync(p)) {
|
|
399
|
+
const st = nodeFs().statSync(p);
|
|
400
|
+
if (st && typeof st.isDirectory === 'function' && st.isDirectory()) {
|
|
401
|
+
return p;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
} catch {
|
|
405
|
+
// ignore
|
|
406
|
+
}
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Returns the material `builder/` root: {@link getAppsMaterializationParent}`/builder`
|
|
412
|
+
* (same as {@link getSystemBuilderRoot}). Does not use `AIFABRIX_BUILDER_DIR` — app paths follow
|
|
413
|
+
* cwd + `integration/` / `builder/` or this root only.
|
|
414
|
+
*
|
|
371
415
|
* @returns {string} Absolute path to builder/ directory
|
|
372
416
|
*/
|
|
373
417
|
function getBuilderRoot() {
|
|
374
|
-
|
|
375
|
-
? process.env.AIFABRIX_BUILDER_DIR.trim()
|
|
376
|
-
: null;
|
|
377
|
-
if (envDir) {
|
|
378
|
-
return path.resolve(envDir);
|
|
379
|
-
}
|
|
380
|
-
return path.join(getIntegrationBuilderBaseDir(), 'builder');
|
|
418
|
+
return path.join(getAppsMaterializationParent(), 'builder');
|
|
381
419
|
}
|
|
382
420
|
|
|
383
421
|
/**
|
|
384
|
-
* Platform system apps
|
|
385
|
-
*
|
|
386
|
-
* {@link getSystemBuilderRoot}).
|
|
422
|
+
* Platform system apps (`up-platform` / `up-miso` / `up-dataplane` / `setup`): materialize under
|
|
423
|
+
* {@link getSystemBuilderRoot} (= {@link getAppsMaterializationParent}`/builder`), never the global CLI package tree.
|
|
387
424
|
* @readonly
|
|
388
425
|
*/
|
|
389
426
|
const SYSTEM_BUILDER_APP_KEYS = Object.freeze(['keycloak', 'miso-controller', 'dataplane']);
|
|
@@ -398,7 +435,8 @@ function isSystemBuilderAppName(appName) {
|
|
|
398
435
|
}
|
|
399
436
|
|
|
400
437
|
/**
|
|
401
|
-
*
|
|
438
|
+
* `builder/<appName>` under {@link getAppsMaterializationParent} (same tree as {@link getSystemBuilderRoot}).
|
|
439
|
+
*
|
|
402
440
|
* @param {string} appName
|
|
403
441
|
* @returns {string}
|
|
404
442
|
*/
|
|
@@ -407,20 +445,30 @@ function getProjectBuilderAppPath(appName) {
|
|
|
407
445
|
}
|
|
408
446
|
|
|
409
447
|
/**
|
|
410
|
-
*
|
|
411
|
-
*
|
|
412
|
-
*
|
|
448
|
+
* Same parent as {@link getAppsMaterializationParent} (exported for diagnostics / allowlist checks).
|
|
449
|
+
*
|
|
450
|
+
* @returns {string} Absolute path (no trailing `builder/`)
|
|
451
|
+
*/
|
|
452
|
+
function getSystemPlatformMaterializationParent() {
|
|
453
|
+
return getAppsMaterializationParent();
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Default `builder/` root for materialized platform apps and Tier‑2 manifest discovery:
|
|
458
|
+
* {@link getAppsMaterializationParent}`/builder`.
|
|
413
459
|
*
|
|
414
460
|
* @returns {string}
|
|
415
461
|
*/
|
|
416
462
|
function getSystemBuilderRoot() {
|
|
417
|
-
return path.join(
|
|
418
|
-
resolveSystemBuilderParentDir(getAifabrixSystemDir(), getAifabrixHome()),
|
|
419
|
-
'builder'
|
|
420
|
-
);
|
|
463
|
+
return path.join(getAppsMaterializationParent(), 'builder');
|
|
421
464
|
}
|
|
422
465
|
|
|
423
466
|
/**
|
|
467
|
+
* True when `projectAppPath` is a directory that contains a resolvable application config
|
|
468
|
+
* (`application.yaml` / `.json` / legacy `variables.yaml`). Empty `builder/<platformApp>`
|
|
469
|
+
* stubs must not win over {@link getSystemBuilderRoot} or secrets/run would read the wrong tree
|
|
470
|
+
* (missing `env.template` → skipped ensure → "Missing secrets" on first platform boot).
|
|
471
|
+
*
|
|
424
472
|
* @param {string} projectAppPath - Absolute `.../builder/<app>`
|
|
425
473
|
* @returns {boolean}
|
|
426
474
|
*/
|
|
@@ -428,7 +476,10 @@ function isProjectBuilderAppDirectory(projectAppPath) {
|
|
|
428
476
|
try {
|
|
429
477
|
if (!projectAppPath || !nodeFs().existsSync(projectAppPath)) return false;
|
|
430
478
|
const st = nodeFs().statSync(projectAppPath);
|
|
431
|
-
|
|
479
|
+
if (!st || typeof st.isDirectory !== 'function' || !st.isDirectory()) return false;
|
|
480
|
+
const { resolveApplicationConfigPath } = require('./app-config-resolver');
|
|
481
|
+
resolveApplicationConfigPath(projectAppPath);
|
|
482
|
+
return true;
|
|
432
483
|
} catch {
|
|
433
484
|
return false;
|
|
434
485
|
}
|
|
@@ -458,21 +509,14 @@ function isAppSubdirSync(root, name) {
|
|
|
458
509
|
*/
|
|
459
510
|
function listIntegrationAppNames() {
|
|
460
511
|
const disk = nodeFs();
|
|
461
|
-
const
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
let rootStat;
|
|
466
|
-
try {
|
|
467
|
-
rootStat = disk.statSync(root);
|
|
468
|
-
} catch {
|
|
469
|
-
return [];
|
|
470
|
-
}
|
|
471
|
-
if (!rootStat || typeof rootStat.isDirectory !== 'function' || !rootStat.isDirectory()) {
|
|
472
|
-
return [];
|
|
512
|
+
const names = new Set();
|
|
513
|
+
const cwdRoot = getCwdIntegrationRoot();
|
|
514
|
+
if (cwdRoot) {
|
|
515
|
+
addBuilderSubdirNamesToSet(disk, cwdRoot, names, null);
|
|
473
516
|
}
|
|
474
|
-
const
|
|
475
|
-
|
|
517
|
+
const matInt = getIntegrationRoot();
|
|
518
|
+
addBuilderSubdirNamesToSet(disk, matInt, names, null);
|
|
519
|
+
return [...names].sort();
|
|
476
520
|
}
|
|
477
521
|
|
|
478
522
|
/**
|
|
@@ -503,21 +547,29 @@ function addBuilderSubdirNamesToSet(disk, builderRootDir, names, nameFilter) {
|
|
|
503
547
|
}
|
|
504
548
|
|
|
505
549
|
/**
|
|
506
|
-
* Lists app names (directories) under builder
|
|
507
|
-
* Merges
|
|
550
|
+
* Lists app names (directories) under builder roots. Excludes dot-prefixed entries.
|
|
551
|
+
* Merges `cwd/builder` and material `(aifabrix-work | aifabrix-home)/builder` (deduped).
|
|
508
552
|
* @returns {string[]} Sorted list of app directory names
|
|
509
553
|
*/
|
|
510
554
|
function listBuilderAppNames() {
|
|
511
555
|
const disk = nodeFs();
|
|
512
556
|
const names = new Set();
|
|
513
|
-
const
|
|
514
|
-
|
|
515
|
-
|
|
557
|
+
const roots = new Set();
|
|
558
|
+
const cwdRoot = getCwdBuilderRoot();
|
|
559
|
+
if (cwdRoot) {
|
|
560
|
+
roots.add(path.resolve(cwdRoot));
|
|
561
|
+
}
|
|
562
|
+
roots.add(path.resolve(getSystemBuilderRoot()));
|
|
563
|
+
for (const r of roots) {
|
|
564
|
+
addBuilderSubdirNamesToSet(disk, r, names, null);
|
|
565
|
+
}
|
|
516
566
|
return [...names].sort();
|
|
517
567
|
}
|
|
518
568
|
|
|
519
569
|
/**
|
|
520
|
-
* Gets the integration folder path
|
|
570
|
+
* Gets the integration folder path: **`cwd/integration/<appName>`** when that directory exists, else
|
|
571
|
+
* **`aifabrix-work` or `aifabrix-home` + `/integration/<appName>`**.
|
|
572
|
+
*
|
|
521
573
|
* @param {string} appName - Application name
|
|
522
574
|
* @returns {string} Absolute path to integration directory
|
|
523
575
|
*/
|
|
@@ -525,8 +577,18 @@ function getIntegrationPath(appName) {
|
|
|
525
577
|
if (!appName || typeof appName !== 'string') {
|
|
526
578
|
throw new Error('App name is required and must be a string');
|
|
527
579
|
}
|
|
528
|
-
const
|
|
529
|
-
|
|
580
|
+
const cwdInt = path.join(path.resolve(process.cwd()), 'integration', appName);
|
|
581
|
+
try {
|
|
582
|
+
if (nodeFs().existsSync(cwdInt)) {
|
|
583
|
+
const st = nodeFs().statSync(cwdInt);
|
|
584
|
+
if (st && typeof st.isDirectory === 'function' && st.isDirectory()) {
|
|
585
|
+
return cwdInt;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
} catch {
|
|
589
|
+
// ignore
|
|
590
|
+
}
|
|
591
|
+
return path.join(getAppsMaterializationParent(), 'integration', appName);
|
|
530
592
|
}
|
|
531
593
|
|
|
532
594
|
/**
|
|
@@ -544,7 +606,39 @@ function resolveBuildContext(configDir, buildContext) {
|
|
|
544
606
|
}
|
|
545
607
|
|
|
546
608
|
/**
|
|
547
|
-
*
|
|
609
|
+
* `cwd/builder/<appName>` when present as a directory and allowed (platform empty stubs skipped).
|
|
610
|
+
*
|
|
611
|
+
* @param {string} appName
|
|
612
|
+
* @returns {string|null}
|
|
613
|
+
*/
|
|
614
|
+
function tryCwdBuilderPathOrNull(appName) {
|
|
615
|
+
const cwdApp = path.join(path.resolve(process.cwd()), 'builder', appName);
|
|
616
|
+
try {
|
|
617
|
+
if (!nodeFs().existsSync(cwdApp)) {
|
|
618
|
+
return null;
|
|
619
|
+
}
|
|
620
|
+
const st = nodeFs().statSync(cwdApp);
|
|
621
|
+
if (!st || typeof st.isDirectory !== 'function' || !st.isDirectory()) {
|
|
622
|
+
return null;
|
|
623
|
+
}
|
|
624
|
+
if (isSystemBuilderAppName(appName) && !isProjectBuilderAppDirectory(cwdApp)) {
|
|
625
|
+
return null;
|
|
626
|
+
}
|
|
627
|
+
return cwdApp;
|
|
628
|
+
} catch {
|
|
629
|
+
return null;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Gets the builder folder path:
|
|
635
|
+
* 1. Plan 141 Tier‑1: `cwd/builder/<app>` or `cwd/integration/<app>` when a resolvable application manifest exists.
|
|
636
|
+
* 2. Else **`cwd/builder/<app>`** when that directory exists. For **platform** apps (`keycloak`,
|
|
637
|
+
* `miso-controller`, `dataplane`), an **empty** cwd stub is ignored so materialization under
|
|
638
|
+
* `(work|home)/builder` still wins.
|
|
639
|
+
* 3. Else **`(aifabrix-work or aifabrix-home)/builder/<appName>`** — used by setup / `up-platform` /
|
|
640
|
+
* `up-miso` / `up-dataplane` for template materialization.
|
|
641
|
+
*
|
|
548
642
|
* @param {string} appName - Application name
|
|
549
643
|
* @returns {string} Absolute path to builder directory
|
|
550
644
|
*/
|
|
@@ -552,21 +646,20 @@ function getBuilderPath(appName) {
|
|
|
552
646
|
if (!appName || typeof appName !== 'string') {
|
|
553
647
|
throw new Error('App name is required and must be a string');
|
|
554
648
|
}
|
|
555
|
-
const
|
|
556
|
-
|
|
557
|
-
:
|
|
558
|
-
|
|
559
|
-
|
|
649
|
+
const { resolveApplicationManifestPathSync } = require('./manifest-location');
|
|
650
|
+
const manifestHit = resolveApplicationManifestPathSync({
|
|
651
|
+
targetKey: appName,
|
|
652
|
+
mode: 'auto',
|
|
653
|
+
cwd: process.cwd()
|
|
654
|
+
});
|
|
655
|
+
if (manifestHit && (manifestHit.tier === 'cwd-builder' || manifestHit.tier === 'cwd-integration')) {
|
|
656
|
+
return manifestHit.absolutePath;
|
|
560
657
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
return projectAppPath;
|
|
565
|
-
}
|
|
566
|
-
return path.join(getSystemBuilderRoot(), appName);
|
|
658
|
+
const cwdHit = tryCwdBuilderPathOrNull(appName);
|
|
659
|
+
if (cwdHit) {
|
|
660
|
+
return cwdHit;
|
|
567
661
|
}
|
|
568
|
-
|
|
569
|
-
return path.join(base, 'builder', appName);
|
|
662
|
+
return path.join(getAppsMaterializationParent(), 'builder', appName);
|
|
570
663
|
}
|
|
571
664
|
|
|
572
665
|
/**
|
|
@@ -717,16 +810,66 @@ async function getResolveAppPath(appName) {
|
|
|
717
810
|
return { appPath: integrationPath, envOnly: true };
|
|
718
811
|
}
|
|
719
812
|
}
|
|
813
|
+
const { resolveApplicationManifestPathSync } = require('./manifest-location');
|
|
814
|
+
const manifestHit = resolveApplicationManifestPathSync({
|
|
815
|
+
targetKey: appName,
|
|
816
|
+
mode: 'auto',
|
|
817
|
+
cwd: process.cwd()
|
|
818
|
+
});
|
|
819
|
+
if (manifestHit) {
|
|
820
|
+
return { appPath: manifestHit.absolutePath, envOnly: false };
|
|
821
|
+
}
|
|
720
822
|
const result = await detectAppType(appName);
|
|
721
823
|
return { appPath: result.appPath, envOnly: false };
|
|
722
824
|
}
|
|
723
825
|
|
|
724
|
-
/**
|
|
826
|
+
/**
|
|
827
|
+
* @param {string} walkDir - Candidate workspace root (walk upward from cwd)
|
|
828
|
+
* @param {string} cwd - Resolved process.cwd()
|
|
829
|
+
* @param {ReturnType<typeof nodeFs>} disk
|
|
830
|
+
* @returns {string|null}
|
|
831
|
+
*/
|
|
832
|
+
function tryIntegrationAppKeyAtWalkStep(walkDir, cwd, disk) {
|
|
833
|
+
const integrationDir = path.join(walkDir, 'integration');
|
|
834
|
+
try {
|
|
835
|
+
if (!disk.existsSync(integrationDir)) {
|
|
836
|
+
return null;
|
|
837
|
+
}
|
|
838
|
+
const intNorm = path.resolve(integrationDir);
|
|
839
|
+
if (cwd !== intNorm && !cwd.startsWith(intNorm + path.sep)) {
|
|
840
|
+
return null;
|
|
841
|
+
}
|
|
842
|
+
const rel = path.relative(intNorm, cwd);
|
|
843
|
+
if (!rel || rel.startsWith('..') || path.isAbsolute(rel)) {
|
|
844
|
+
return null;
|
|
845
|
+
}
|
|
846
|
+
return rel.split(path.sep)[0] || null;
|
|
847
|
+
} catch {
|
|
848
|
+
return null;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/**
|
|
853
|
+
* Resolve app folder name when cwd is under some ancestor's `integration/<systemKey>/`.
|
|
854
|
+
* Walks up from cwd (does not use {@link getIntegrationBuilderBaseDir}) so detection still works
|
|
855
|
+
* when the canonical base is config/home but the shell is inside a checkout's integration tree.
|
|
856
|
+
*/
|
|
725
857
|
function resolveIntegrationAppKeyFromCwd() {
|
|
726
|
-
const integrationNorm = path.resolve(path.join(getIntegrationBuilderBaseDir(), 'integration'));
|
|
727
858
|
const cwd = path.resolve(process.cwd());
|
|
728
|
-
|
|
729
|
-
|
|
859
|
+
const disk = nodeFs();
|
|
860
|
+
let p = cwd;
|
|
861
|
+
for (let i = 0; i < 64; i += 1) {
|
|
862
|
+
const key = tryIntegrationAppKeyAtWalkStep(p, cwd, disk);
|
|
863
|
+
if (key) {
|
|
864
|
+
return key;
|
|
865
|
+
}
|
|
866
|
+
const parent = path.dirname(p);
|
|
867
|
+
if (parent === p) {
|
|
868
|
+
break;
|
|
869
|
+
}
|
|
870
|
+
p = parent;
|
|
871
|
+
}
|
|
872
|
+
return null;
|
|
730
873
|
}
|
|
731
874
|
|
|
732
875
|
module.exports = {
|
|
@@ -738,14 +881,20 @@ module.exports = {
|
|
|
738
881
|
getApplicationsBaseDir,
|
|
739
882
|
getDevDirectory,
|
|
740
883
|
getAppPath,
|
|
884
|
+
getAppsMaterializationParent,
|
|
885
|
+
getCwdIntegrationRoot,
|
|
886
|
+
getCwdBuilderRoot,
|
|
741
887
|
getProjectRoot,
|
|
888
|
+
findProjectRootFromCwd,
|
|
742
889
|
getIntegrationPath,
|
|
743
890
|
getBuilderPath,
|
|
744
891
|
getIntegrationRoot,
|
|
745
892
|
getBuilderRoot,
|
|
893
|
+
getIntegrationBuilderBaseDir,
|
|
746
894
|
SYSTEM_BUILDER_APP_KEYS,
|
|
747
895
|
isSystemBuilderAppName,
|
|
748
896
|
getProjectBuilderAppPath,
|
|
897
|
+
getSystemPlatformMaterializationParent,
|
|
749
898
|
getSystemBuilderRoot,
|
|
750
899
|
resolveSystemBuilderParentDir,
|
|
751
900
|
listIntegrationAppNames,
|
|
@@ -54,14 +54,20 @@ async function loadRemoteSharedSecrets() {
|
|
|
54
54
|
* Merges remote shared secrets with user secrets. User wins on same key.
|
|
55
55
|
* @param {Object} userSecrets - User secrets object
|
|
56
56
|
* @param {Object} remoteSecrets - Remote API secrets (key-value)
|
|
57
|
+
* @param {Record<string, string>} [keySources] - Mutated: winning file/API label per key (decrypt hints)
|
|
58
|
+
* @param {string} [remoteSourceLabel] - Human-readable source for keys taken from remote
|
|
57
59
|
* @returns {Object} Merged object
|
|
58
60
|
*/
|
|
59
|
-
function mergeUserWithRemoteSecrets(userSecrets, remoteSecrets) {
|
|
61
|
+
function mergeUserWithRemoteSecrets(userSecrets, remoteSecrets, keySources, remoteSourceLabel) {
|
|
60
62
|
const merged = { ...userSecrets };
|
|
61
63
|
if (!remoteSecrets || typeof remoteSecrets !== 'object') return merged;
|
|
64
|
+
const label = remoteSourceLabel || 'shared secrets API';
|
|
62
65
|
for (const key of Object.keys(remoteSecrets)) {
|
|
63
66
|
if (!(key in merged) || merged[key] === undefined || merged[key] === null || merged[key] === '') {
|
|
64
67
|
merged[key] = remoteSecrets[key];
|
|
68
|
+
if (keySources) {
|
|
69
|
+
keySources[key] = label;
|
|
70
|
+
}
|
|
65
71
|
}
|
|
66
72
|
}
|
|
67
73
|
return merged;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize Commander flags for `aifabrix run`.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Commander v11 pairs `--no-proxy` with a default-true `--proxy` flag as `options.proxy === false`.
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* True when the user disabled proxy hints: explicit `--no-proxy` or `proxy === false` when supported.
|
|
13
|
+
*
|
|
14
|
+
* @param {Object} [options] - Commander action options
|
|
15
|
+
* @returns {boolean}
|
|
16
|
+
*/
|
|
17
|
+
function isRunCliNoProxy(options) {
|
|
18
|
+
if (!options || typeof options !== 'object') {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
if (options.proxy === false) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
return options.noProxy === true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = {
|
|
28
|
+
isRunCliNoProxy
|
|
29
|
+
};
|
|
@@ -31,11 +31,14 @@ function readYamlAtPath(filePath) {
|
|
|
31
31
|
* @param {string} key - Secret key
|
|
32
32
|
* @param {*} canonicalValue - Value from canonical secrets
|
|
33
33
|
*/
|
|
34
|
-
function mergeSecretValue(result, key, canonicalValue) {
|
|
34
|
+
function mergeSecretValue(result, key, canonicalValue, keySources, canonicalSourcePath) {
|
|
35
35
|
const currentValue = result[key];
|
|
36
36
|
// Fill missing, empty, or undefined values
|
|
37
37
|
if (!(key in result) || currentValue === undefined || currentValue === null || currentValue === '') {
|
|
38
38
|
result[key] = canonicalValue;
|
|
39
|
+
if (keySources && canonicalSourcePath) {
|
|
40
|
+
keySources[key] = canonicalSourcePath;
|
|
41
|
+
}
|
|
39
42
|
return;
|
|
40
43
|
}
|
|
41
44
|
// Only replace values that are encrypted (have secure:// prefix)
|
|
@@ -43,6 +46,9 @@ function mergeSecretValue(result, key, canonicalValue) {
|
|
|
43
46
|
if (typeof currentValue === 'string' && typeof canonicalValue === 'string') {
|
|
44
47
|
if (currentValue.startsWith('secure://')) {
|
|
45
48
|
result[key] = canonicalValue;
|
|
49
|
+
if (keySources && canonicalSourcePath) {
|
|
50
|
+
keySources[key] = canonicalSourcePath;
|
|
51
|
+
}
|
|
46
52
|
}
|
|
47
53
|
}
|
|
48
54
|
}
|
|
@@ -52,9 +58,10 @@ function mergeSecretValue(result, key, canonicalValue) {
|
|
|
52
58
|
* @async
|
|
53
59
|
* @function applyCanonicalSecretsOverride
|
|
54
60
|
* @param {Object} currentSecrets - Current secrets map
|
|
61
|
+
* @param {Record<string, string>} [keySources] - Mutated: per-key source path when a value is taken from canonical YAML
|
|
55
62
|
* @returns {Promise<Object>} Possibly overridden secrets
|
|
56
63
|
*/
|
|
57
|
-
async function applyCanonicalSecretsOverride(currentSecrets) {
|
|
64
|
+
async function applyCanonicalSecretsOverride(currentSecrets, keySources) {
|
|
58
65
|
let mergedSecrets = currentSecrets || {};
|
|
59
66
|
try {
|
|
60
67
|
const canonicalPath = await config.getSecretsPath();
|
|
@@ -78,7 +85,7 @@ async function applyCanonicalSecretsOverride(currentSecrets) {
|
|
|
78
85
|
// - Replace encrypted values (secure://) with canonical plaintext
|
|
79
86
|
const result = { ...mergedSecrets };
|
|
80
87
|
for (const [key, canonicalValue] of Object.entries(configSecrets)) {
|
|
81
|
-
mergeSecretValue(result, key, canonicalValue);
|
|
88
|
+
mergeSecretValue(result, key, canonicalValue, keySources, resolvedCanonical);
|
|
82
89
|
}
|
|
83
90
|
mergedSecrets = result;
|
|
84
91
|
} catch {
|
|
@@ -41,9 +41,8 @@ function resolveSecretsPath(secretsPath) {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
|
-
* Determines
|
|
45
|
-
*
|
|
46
|
-
* Uses config.yaml for default secrets path as fallback
|
|
44
|
+
* Determines paths used for default `loadSecrets()` (no explicit path): primary user secrets file
|
|
45
|
+
* and configured `aifabrix-secrets` (shared YAML file path or remote API URL).
|
|
47
46
|
*
|
|
48
47
|
* @async
|
|
49
48
|
* @function getActualSecretsPath
|
|
@@ -65,7 +64,7 @@ async function getActualSecretsPath(secretsPath, _appName) {
|
|
|
65
64
|
};
|
|
66
65
|
}
|
|
67
66
|
|
|
68
|
-
//
|
|
67
|
+
// Default lookup: primary user file plus aifabrix-secrets (file or https URL)
|
|
69
68
|
const userSecretsPath = paths.getPrimaryUserSecretsLocalPath();
|
|
70
69
|
|
|
71
70
|
let buildSecretsPath = null;
|
|
@@ -58,18 +58,14 @@ async function loadSecretsFromFile(filePath) {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
|
-
* Loads user secrets from
|
|
62
|
-
*
|
|
63
|
-
*
|
|
61
|
+
* Loads user secrets from {@link pathsUtil.getPrimaryUserSecretsLocalPath} (beside `config.yaml`).
|
|
62
|
+
* If that file is missing, reads a legacy file at `getAifabrixHome()/secrets.local.yaml` when paths
|
|
63
|
+
* differ (older CLI when `aifabrix-home` was POSIX home).
|
|
64
64
|
*
|
|
65
65
|
* @function loadPrimaryUserSecrets
|
|
66
66
|
* @returns {Object} Loaded secrets object or empty object
|
|
67
67
|
*/
|
|
68
|
-
function
|
|
69
|
-
const userSecretsPath = pathsUtil.getPrimaryUserSecretsLocalPath();
|
|
70
|
-
if (!fs.existsSync(userSecretsPath)) {
|
|
71
|
-
return {};
|
|
72
|
-
}
|
|
68
|
+
function readPrimaryUserSecretsAtPath(userSecretsPath) {
|
|
73
69
|
ensureSecureFilePermissions(userSecretsPath);
|
|
74
70
|
|
|
75
71
|
try {
|
|
@@ -84,8 +80,22 @@ function loadPrimaryUserSecrets() {
|
|
|
84
80
|
throw error;
|
|
85
81
|
}
|
|
86
82
|
logger.warn(`Warning: Could not read secrets file ${userSecretsPath}: ${error.message}`);
|
|
87
|
-
return
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function loadPrimaryUserSecrets() {
|
|
88
|
+
const primaryPath = pathsUtil.getPrimaryUserSecretsLocalPath();
|
|
89
|
+
if (fs.existsSync(primaryPath)) {
|
|
90
|
+
const fromPrimary = readPrimaryUserSecretsAtPath(primaryPath);
|
|
91
|
+
return fromPrimary !== null ? fromPrimary : {};
|
|
92
|
+
}
|
|
93
|
+
const legacyPath = path.join(pathsUtil.getAifabrixHome(), 'secrets.local.yaml');
|
|
94
|
+
if (path.resolve(legacyPath) !== path.resolve(primaryPath) && fs.existsSync(legacyPath)) {
|
|
95
|
+
const fromLegacy = readPrimaryUserSecretsAtPath(legacyPath);
|
|
96
|
+
return fromLegacy !== null ? fromLegacy : {};
|
|
88
97
|
}
|
|
98
|
+
return {};
|
|
89
99
|
}
|
|
90
100
|
|
|
91
101
|
/**
|
|
@@ -128,7 +138,7 @@ function loadDefaultSecrets() {
|
|
|
128
138
|
|
|
129
139
|
/**
|
|
130
140
|
* Creates the primary user secrets file if missing (empty map) for first-run installs.
|
|
131
|
-
* Uses the same directory as {@link loadPrimaryUserSecrets} (
|
|
141
|
+
* Uses the same directory as {@link loadPrimaryUserSecrets} (beside `config.yaml`).
|
|
132
142
|
*
|
|
133
143
|
* @function ensurePrimaryUserSecretsFileExists
|
|
134
144
|
*/
|