@bluealba/platform-cli 1.1.0 → 1.2.0-alpha.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/dist/index.js
CHANGED
|
@@ -204,8 +204,9 @@ function ConfirmPrompt({ value }) {
|
|
|
204
204
|
import { Fzf } from "fzf";
|
|
205
205
|
|
|
206
206
|
// src/commands/create-application/create-application.command.ts
|
|
207
|
-
import { join as
|
|
208
|
-
import {
|
|
207
|
+
import { join as join12, resolve as resolve2 } from "path";
|
|
208
|
+
import { cwd as cwd4 } from "process";
|
|
209
|
+
import { mkdir as mkdir3 } from "fs/promises";
|
|
209
210
|
|
|
210
211
|
// src/commands/create-application/scaffold-application-monorepo.ts
|
|
211
212
|
import { fileURLToPath } from "url";
|
|
@@ -454,7 +455,7 @@ function buildBootstrapBlock(platformName, applicationName) {
|
|
|
454
455
|
const bootstrapName = `${platformName}-${applicationName}-bootstrap-service`;
|
|
455
456
|
return ` ${bootstrapName}:
|
|
456
457
|
build:
|
|
457
|
-
context:
|
|
458
|
+
context: ./services/${bootstrapName}
|
|
458
459
|
dockerfile: Dockerfile.development
|
|
459
460
|
environment:
|
|
460
461
|
- NODE_TLS_REJECT_UNAUTHORIZED=0
|
|
@@ -473,7 +474,7 @@ function buildUiBlock(platformName, applicationName, uiPort) {
|
|
|
473
474
|
const uiName = `${platformName}-${applicationName}-ui`;
|
|
474
475
|
return ` ${uiName}:
|
|
475
476
|
build:
|
|
476
|
-
context:
|
|
477
|
+
context: ./ui/${uiName}
|
|
477
478
|
dockerfile: Dockerfile.development
|
|
478
479
|
ports:
|
|
479
480
|
- ${uiPort}:80
|
|
@@ -485,7 +486,7 @@ function buildBackendBlock(platformName, applicationName, servicePort) {
|
|
|
485
486
|
const serviceName = `${platformName}-${applicationName}-service`;
|
|
486
487
|
return ` ${serviceName}:
|
|
487
488
|
build:
|
|
488
|
-
context:
|
|
489
|
+
context: ./services/${serviceName}
|
|
489
490
|
dockerfile: Dockerfile.development
|
|
490
491
|
ports:
|
|
491
492
|
- ${servicePort}:80
|
|
@@ -514,25 +515,44 @@ async function createDockerCompose(dockerComposePath, applicationName, platformN
|
|
|
514
515
|
}
|
|
515
516
|
|
|
516
517
|
// src/commands/create-application/port-allocator.ts
|
|
517
|
-
import { join as join8 } from "path";
|
|
518
|
-
import { readdir as readdir2, readFile as readFile3 } from "fs/promises";
|
|
519
|
-
async function
|
|
518
|
+
import { join as join8, resolve } from "path";
|
|
519
|
+
import { readdir as readdir2, readFile as readFile3, access } from "fs/promises";
|
|
520
|
+
async function extractPortsFromFile(filePath) {
|
|
521
|
+
try {
|
|
522
|
+
const content = await readFile3(filePath, "utf-8");
|
|
523
|
+
const regex = /^\s*-\s*"?(\d+):\d+"?\s*$/gm;
|
|
524
|
+
const ports = [];
|
|
525
|
+
let match;
|
|
526
|
+
while ((match = regex.exec(content)) !== null) {
|
|
527
|
+
ports.push(parseInt(match[1], 10));
|
|
528
|
+
}
|
|
529
|
+
return ports;
|
|
530
|
+
} catch {
|
|
531
|
+
return [];
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
async function getNextAvailablePort(localDir, manifest, coreDir) {
|
|
520
535
|
const allPorts = [];
|
|
521
536
|
try {
|
|
522
537
|
const files = await readdir2(localDir);
|
|
523
538
|
const ymlFiles = files.filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
|
|
524
539
|
for (const file of ymlFiles) {
|
|
540
|
+
const ports = await extractPortsFromFile(join8(localDir, file));
|
|
541
|
+
allPorts.push(...ports);
|
|
542
|
+
}
|
|
543
|
+
} catch {
|
|
544
|
+
}
|
|
545
|
+
if (manifest && coreDir) {
|
|
546
|
+
for (const app of manifest.applications) {
|
|
547
|
+
const appDir = resolve(join8(coreDir), app.localPath);
|
|
548
|
+
const composePath = join8(appDir, "docker-compose.yml");
|
|
525
549
|
try {
|
|
526
|
-
|
|
527
|
-
const
|
|
528
|
-
|
|
529
|
-
while ((match = regex.exec(content)) !== null) {
|
|
530
|
-
allPorts.push(parseInt(match[1], 10));
|
|
531
|
-
}
|
|
550
|
+
await access(composePath);
|
|
551
|
+
const ports = await extractPortsFromFile(composePath);
|
|
552
|
+
allPorts.push(...ports);
|
|
532
553
|
} catch {
|
|
533
554
|
}
|
|
534
555
|
}
|
|
535
|
-
} catch {
|
|
536
556
|
}
|
|
537
557
|
return allPorts.length > 0 ? Math.max(...allPorts) + 1 : 9e3;
|
|
538
558
|
}
|
|
@@ -543,7 +563,7 @@ function formatError(err) {
|
|
|
543
563
|
}
|
|
544
564
|
|
|
545
565
|
// src/utils/platform-layout.ts
|
|
546
|
-
import { access, readdir as readdir3 } from "fs/promises";
|
|
566
|
+
import { access as access2, readdir as readdir3 } from "fs/promises";
|
|
547
567
|
import { join as join9, dirname as dirname7 } from "path";
|
|
548
568
|
import { cwd as cwd2 } from "process";
|
|
549
569
|
async function findCoreDirIn(dir) {
|
|
@@ -557,7 +577,7 @@ async function findCoreDirIn(dir) {
|
|
|
557
577
|
for (const entry of dirs) {
|
|
558
578
|
if (entry.name.endsWith("-core")) {
|
|
559
579
|
try {
|
|
560
|
-
await
|
|
580
|
+
await access2(join9(dir, entry.name, "product.manifest.json"));
|
|
561
581
|
return entry.name;
|
|
562
582
|
} catch {
|
|
563
583
|
}
|
|
@@ -566,7 +586,7 @@ async function findCoreDirIn(dir) {
|
|
|
566
586
|
const coreEntry = dirs.find((e) => e.name === "core");
|
|
567
587
|
if (coreEntry) {
|
|
568
588
|
try {
|
|
569
|
-
await
|
|
589
|
+
await access2(join9(dir, "core", "product.manifest.json"));
|
|
570
590
|
return "core";
|
|
571
591
|
} catch {
|
|
572
592
|
}
|
|
@@ -600,7 +620,7 @@ async function isPlatformInitialized() {
|
|
|
600
620
|
}
|
|
601
621
|
|
|
602
622
|
// src/utils/manifest.ts
|
|
603
|
-
import { readFile as readFile4, writeFile as writeFile4, access as
|
|
623
|
+
import { readFile as readFile4, writeFile as writeFile4, access as access3 } from "fs/promises";
|
|
604
624
|
import { join as join10 } from "path";
|
|
605
625
|
import { cwd as cwd3 } from "process";
|
|
606
626
|
function manifestPath(rootDir, coreDirName = "core") {
|
|
@@ -633,12 +653,101 @@ function addApplicationToManifest(manifest, app) {
|
|
|
633
653
|
};
|
|
634
654
|
}
|
|
635
655
|
|
|
656
|
+
// src/commands/standalone/standalone-config.ts
|
|
657
|
+
import { readFile as readFile5, writeFile as writeFile5, mkdir as mkdir2, rename } from "fs/promises";
|
|
658
|
+
import { join as join11 } from "path";
|
|
659
|
+
import { homedir as homedir2 } from "os";
|
|
660
|
+
var CACHE_DIR = join11(homedir2(), ".ba-platform");
|
|
661
|
+
var CACHE_FILE = join11(CACHE_DIR, "standalone.json");
|
|
662
|
+
function getCachePath() {
|
|
663
|
+
return CACHE_FILE;
|
|
664
|
+
}
|
|
665
|
+
async function readCacheFile() {
|
|
666
|
+
try {
|
|
667
|
+
const content = await readFile5(CACHE_FILE, "utf-8");
|
|
668
|
+
const parsed = JSON.parse(content);
|
|
669
|
+
const defaults = parsed.defaults != null && typeof parsed.defaults === "object" ? parsed.defaults : {};
|
|
670
|
+
const apps = parsed.apps != null && typeof parsed.apps === "object" ? parsed.apps : {};
|
|
671
|
+
return { defaults, apps };
|
|
672
|
+
} catch (err) {
|
|
673
|
+
if (err instanceof SyntaxError) {
|
|
674
|
+
console.warn(`Warning: ~/.ba-platform/standalone.json is corrupt \u2014 starting with empty config.`);
|
|
675
|
+
}
|
|
676
|
+
return { defaults: {}, apps: {} };
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
async function writeCacheFile(cache) {
|
|
680
|
+
await mkdir2(CACHE_DIR, { recursive: true });
|
|
681
|
+
const tmpFile = CACHE_FILE + ".tmp";
|
|
682
|
+
await writeFile5(tmpFile, JSON.stringify(cache, null, 2), { encoding: "utf-8", mode: 384 });
|
|
683
|
+
await rename(tmpFile, CACHE_FILE);
|
|
684
|
+
}
|
|
685
|
+
function buildAppKey(platformName, applicationName) {
|
|
686
|
+
return `${platformName}/${applicationName}`;
|
|
687
|
+
}
|
|
688
|
+
async function resolveAppConfig(platformName, applicationName) {
|
|
689
|
+
const cache = await readCacheFile();
|
|
690
|
+
const key = buildAppKey(platformName, applicationName);
|
|
691
|
+
const app = cache.apps[key];
|
|
692
|
+
if (!app) return null;
|
|
693
|
+
return mergeConfig(cache.defaults, app);
|
|
694
|
+
}
|
|
695
|
+
function mergeConfig(defaults, app) {
|
|
696
|
+
return {
|
|
697
|
+
platformName: app.platformName ?? defaults.platformName ?? "",
|
|
698
|
+
organizationName: app.organizationName ?? defaults.organizationName ?? "",
|
|
699
|
+
applicationName: app.applicationName,
|
|
700
|
+
applicationDisplayName: app.applicationDisplayName,
|
|
701
|
+
idp: app.idp ?? defaults.idp,
|
|
702
|
+
adminUsers: app.adminUsers ?? defaults.adminUsers
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
async function saveDefaults(partial) {
|
|
706
|
+
const cache = await readCacheFile();
|
|
707
|
+
cache.defaults = { ...cache.defaults, ...partial };
|
|
708
|
+
await writeCacheFile(cache);
|
|
709
|
+
}
|
|
710
|
+
async function saveAppConfig(appKey, override) {
|
|
711
|
+
const cache = await readCacheFile();
|
|
712
|
+
cache.apps[appKey] = { ...cache.apps[appKey], ...override };
|
|
713
|
+
await writeCacheFile(cache);
|
|
714
|
+
}
|
|
715
|
+
async function deleteAppConfig(appKey) {
|
|
716
|
+
const cache = await readCacheFile();
|
|
717
|
+
if (!(appKey in cache.apps)) return false;
|
|
718
|
+
delete cache.apps[appKey];
|
|
719
|
+
await writeCacheFile(cache);
|
|
720
|
+
return true;
|
|
721
|
+
}
|
|
722
|
+
async function listAppConfigs() {
|
|
723
|
+
const cache = await readCacheFile();
|
|
724
|
+
return Object.entries(cache.apps).map(([key, app]) => ({
|
|
725
|
+
key,
|
|
726
|
+
resolved: mergeConfig(cache.defaults, app)
|
|
727
|
+
}));
|
|
728
|
+
}
|
|
729
|
+
async function getDefaults() {
|
|
730
|
+
const cache = await readCacheFile();
|
|
731
|
+
return cache.defaults;
|
|
732
|
+
}
|
|
733
|
+
var LEGACY_CONFIG_FILE = "standalone.json";
|
|
734
|
+
async function readLegacyConfig(appDir) {
|
|
735
|
+
try {
|
|
736
|
+
const content = await readFile5(join11(appDir, LEGACY_CONFIG_FILE), "utf-8");
|
|
737
|
+
const parsed = JSON.parse(content);
|
|
738
|
+
if (!parsed.platformName || !parsed.applicationName) return null;
|
|
739
|
+
return parsed;
|
|
740
|
+
} catch {
|
|
741
|
+
return null;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
636
745
|
// src/commands/create-application/create-application.command.ts
|
|
637
746
|
var CREATE_APPLICATION_COMMAND_NAME = "create-application";
|
|
638
747
|
var createApplicationCommand = {
|
|
639
748
|
name: CREATE_APPLICATION_COMMAND_NAME,
|
|
640
|
-
description: "Create an application
|
|
641
|
-
hidden: (
|
|
749
|
+
description: "Create an application",
|
|
750
|
+
hidden: () => false
|
|
642
751
|
};
|
|
643
752
|
async function createApplication(params, logger) {
|
|
644
753
|
const {
|
|
@@ -649,23 +758,39 @@ async function createApplication(params, logger) {
|
|
|
649
758
|
hasBackendService
|
|
650
759
|
} = params;
|
|
651
760
|
const layout = await findPlatformLayout();
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
761
|
+
const insidePlatform = !!layout;
|
|
762
|
+
let platformName;
|
|
763
|
+
let organizationName;
|
|
764
|
+
let applicationDir;
|
|
765
|
+
let localDir;
|
|
766
|
+
let rootDir;
|
|
767
|
+
let coreDirName;
|
|
657
768
|
let manifest;
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
769
|
+
if (insidePlatform) {
|
|
770
|
+
rootDir = layout.rootDir;
|
|
771
|
+
coreDirName = layout.coreDirName;
|
|
772
|
+
localDir = layout.localDir;
|
|
773
|
+
try {
|
|
774
|
+
manifest = await readManifest(rootDir, coreDirName);
|
|
775
|
+
} catch (err) {
|
|
776
|
+
logger.log(`Error: Could not read product manifest \u2014 ${formatError(err)}`);
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
platformName = manifest.product.name;
|
|
780
|
+
organizationName = manifest.product.organization;
|
|
781
|
+
const localPath = `../${platformName}-${applicationName}`;
|
|
782
|
+
applicationDir = resolve2(join12(rootDir, coreDirName), localPath);
|
|
783
|
+
} else {
|
|
784
|
+
if (!params.platformName || !params.organizationName) {
|
|
785
|
+
logger.log("Error: platformName and organizationName are required when creating an application outside a platform.");
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
platformName = params.platformName;
|
|
789
|
+
organizationName = params.organizationName;
|
|
790
|
+
applicationDir = join12(cwd4(), `${platformName}-${applicationName}`);
|
|
663
791
|
}
|
|
664
|
-
const { organization: organizationName, name: platformName } = manifest.product;
|
|
665
|
-
const localPath = `../${platformName}-${applicationName}`;
|
|
666
|
-
const applicationDir = resolve(join11(rootDir, coreDirName), localPath);
|
|
667
792
|
const bootstrapServiceName = `${platformName}-${applicationName}-bootstrap-service`;
|
|
668
|
-
const bootstrapServiceDir =
|
|
793
|
+
const bootstrapServiceDir = join12(applicationDir, "services", bootstrapServiceName);
|
|
669
794
|
logger.log(`Creating application monorepo "${applicationName}"...`);
|
|
670
795
|
try {
|
|
671
796
|
await scaffoldApplicationMonorepo(applicationDir, organizationName, platformName, applicationName, logger);
|
|
@@ -673,8 +798,8 @@ async function createApplication(params, logger) {
|
|
|
673
798
|
logger.log(`Error: Could not scaffold application monorepo \u2014 ${formatError(err)}`);
|
|
674
799
|
return;
|
|
675
800
|
}
|
|
676
|
-
await
|
|
677
|
-
await
|
|
801
|
+
await mkdir3(join12(applicationDir, "services"), { recursive: true });
|
|
802
|
+
await mkdir3(join12(applicationDir, "ui"), { recursive: true });
|
|
678
803
|
logger.log(`Creating bootstrap service "${bootstrapServiceName}"...`);
|
|
679
804
|
const bootstrapServiceBaseDir = `${platformName}-${applicationName}/services`;
|
|
680
805
|
try {
|
|
@@ -708,7 +833,7 @@ async function createApplication(params, logger) {
|
|
|
708
833
|
}
|
|
709
834
|
if (hasUserInterface) {
|
|
710
835
|
const uiName = `${platformName}-${applicationName}-ui`;
|
|
711
|
-
const uiDir =
|
|
836
|
+
const uiDir = join12(applicationDir, "ui", uiName);
|
|
712
837
|
const uiBaseDir = `${platformName}-${applicationName}/ui`;
|
|
713
838
|
try {
|
|
714
839
|
await scaffoldUiModule(uiDir, organizationName, platformName, applicationName, applicationDisplayName, uiBaseDir, logger);
|
|
@@ -719,7 +844,7 @@ async function createApplication(params, logger) {
|
|
|
719
844
|
}
|
|
720
845
|
if (hasBackendService) {
|
|
721
846
|
const serviceName = `${platformName}-${applicationName}-service`;
|
|
722
|
-
const serviceDir =
|
|
847
|
+
const serviceDir = join12(applicationDir, "services", serviceName);
|
|
723
848
|
const serviceBaseDir = `${platformName}-${applicationName}/services`;
|
|
724
849
|
try {
|
|
725
850
|
await scaffoldNestjsService(serviceDir, organizationName, platformName, applicationName, applicationDisplayName, serviceBaseDir, logger);
|
|
@@ -728,10 +853,17 @@ async function createApplication(params, logger) {
|
|
|
728
853
|
return;
|
|
729
854
|
}
|
|
730
855
|
}
|
|
731
|
-
const dockerComposePath =
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
856
|
+
const dockerComposePath = join12(applicationDir, "docker-compose.yml");
|
|
857
|
+
let uiPort;
|
|
858
|
+
let servicePort;
|
|
859
|
+
if (insidePlatform && localDir && manifest) {
|
|
860
|
+
const basePort = await getNextAvailablePort(localDir, manifest, layout.coreDir);
|
|
861
|
+
uiPort = hasUserInterface ? basePort : 0;
|
|
862
|
+
servicePort = hasBackendService ? hasUserInterface ? basePort + 1 : basePort : 0;
|
|
863
|
+
} else {
|
|
864
|
+
uiPort = hasUserInterface ? 9e3 : 0;
|
|
865
|
+
servicePort = hasBackendService ? hasUserInterface ? 9001 : 9e3 : 0;
|
|
866
|
+
}
|
|
735
867
|
await createDockerCompose(
|
|
736
868
|
dockerComposePath,
|
|
737
869
|
applicationName,
|
|
@@ -742,25 +874,40 @@ async function createApplication(params, logger) {
|
|
|
742
874
|
servicePort,
|
|
743
875
|
logger
|
|
744
876
|
);
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
877
|
+
if (insidePlatform && manifest && rootDir && coreDirName) {
|
|
878
|
+
const localPath = `../${platformName}-${applicationName}`;
|
|
879
|
+
const updatedManifest = addApplicationToManifest(manifest, {
|
|
880
|
+
name: applicationName,
|
|
881
|
+
displayName: applicationDisplayName,
|
|
882
|
+
description: applicationDescription,
|
|
883
|
+
localPath,
|
|
884
|
+
repository: null
|
|
885
|
+
});
|
|
886
|
+
try {
|
|
887
|
+
await writeManifest(updatedManifest, rootDir, coreDirName);
|
|
888
|
+
logger.log(`Updated product manifest with application "${applicationName}".`);
|
|
889
|
+
} catch (err) {
|
|
890
|
+
logger.log(`Warning: Could not update product manifest \u2014 ${formatError(err)}`);
|
|
891
|
+
}
|
|
892
|
+
} else {
|
|
893
|
+
try {
|
|
894
|
+
await saveDefaults({ platformName, organizationName });
|
|
895
|
+
const appKey = buildAppKey(platformName, applicationName);
|
|
896
|
+
await saveAppConfig(appKey, { applicationName, applicationDisplayName });
|
|
897
|
+
logger.log(`Saved standalone config to ~/.ba-platform/standalone.json`);
|
|
898
|
+
} catch (err) {
|
|
899
|
+
logger.log(`Warning: Could not save standalone config \u2014 ${formatError(err)}`);
|
|
900
|
+
}
|
|
757
901
|
}
|
|
758
902
|
logger.log(`Done! Application "${applicationName}" created at ${applicationDir}`);
|
|
903
|
+
if (!insidePlatform) {
|
|
904
|
+
logger.log(`Next: cd ${platformName}-${applicationName} && platform standalone`);
|
|
905
|
+
}
|
|
759
906
|
}
|
|
760
907
|
|
|
761
908
|
// src/commands/init/init.command.ts
|
|
762
|
-
import { join as
|
|
763
|
-
import { cwd as
|
|
909
|
+
import { join as join18 } from "path";
|
|
910
|
+
import { cwd as cwd5 } from "process";
|
|
764
911
|
|
|
765
912
|
// src/utils/string.ts
|
|
766
913
|
function camelize(name) {
|
|
@@ -769,8 +916,8 @@ function camelize(name) {
|
|
|
769
916
|
|
|
770
917
|
// src/commands/init/scaffold-platform.ts
|
|
771
918
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
772
|
-
import { join as
|
|
773
|
-
var templateDir =
|
|
919
|
+
import { join as join13, dirname as dirname8 } from "path";
|
|
920
|
+
var templateDir = join13(
|
|
774
921
|
dirname8(fileURLToPath6(import.meta.url)),
|
|
775
922
|
"..",
|
|
776
923
|
"templates",
|
|
@@ -782,8 +929,8 @@ async function scaffoldPlatform(outputDir, variables, logger) {
|
|
|
782
929
|
|
|
783
930
|
// src/commands/init/scaffold-platform-bootstrap.ts
|
|
784
931
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
785
|
-
import { join as
|
|
786
|
-
var templateDir2 =
|
|
932
|
+
import { join as join14, dirname as dirname9 } from "path";
|
|
933
|
+
var templateDir2 = join14(
|
|
787
934
|
dirname9(fileURLToPath7(import.meta.url)),
|
|
788
935
|
"..",
|
|
789
936
|
"templates",
|
|
@@ -795,8 +942,8 @@ async function scaffoldPlatformBootstrap(outputDir, variables, logger) {
|
|
|
795
942
|
|
|
796
943
|
// src/commands/init/scaffold-customization-ui.ts
|
|
797
944
|
import { fileURLToPath as fileURLToPath8 } from "url";
|
|
798
|
-
import { join as
|
|
799
|
-
var templateDir3 =
|
|
945
|
+
import { join as join15, dirname as dirname10 } from "path";
|
|
946
|
+
var templateDir3 = join15(
|
|
800
947
|
dirname10(fileURLToPath8(import.meta.url)),
|
|
801
948
|
"..",
|
|
802
949
|
"templates",
|
|
@@ -807,10 +954,10 @@ async function scaffoldCustomizationUi(outputDir, variables, logger) {
|
|
|
807
954
|
}
|
|
808
955
|
|
|
809
956
|
// src/commands/init/register-customization-module.ts
|
|
810
|
-
import { join as
|
|
811
|
-
import { readFile as
|
|
957
|
+
import { join as join16 } from "path";
|
|
958
|
+
import { readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
|
|
812
959
|
async function registerCustomizationModule(bootstrapServiceDir, organizationName, logger, platformName = "platform") {
|
|
813
|
-
const modulesJsonPath =
|
|
960
|
+
const modulesJsonPath = join16(bootstrapServiceDir, "src", "data", "platform", "modules.json");
|
|
814
961
|
const customizationUiName = `${platformName}-customization-ui`;
|
|
815
962
|
const entry = {
|
|
816
963
|
name: `@${organizationName}/${customizationUiName}`,
|
|
@@ -826,15 +973,15 @@ async function registerCustomizationModule(bootstrapServiceDir, organizationName
|
|
|
826
973
|
},
|
|
827
974
|
dependsOn: []
|
|
828
975
|
};
|
|
829
|
-
const existing = JSON.parse(await
|
|
976
|
+
const existing = JSON.parse(await readFile6(modulesJsonPath, "utf-8"));
|
|
830
977
|
existing.push(entry);
|
|
831
|
-
await
|
|
978
|
+
await writeFile6(modulesJsonPath, JSON.stringify(existing, null, 2) + "\n", "utf-8");
|
|
832
979
|
logger.log(`Registered customization module in ${modulesJsonPath}`);
|
|
833
980
|
}
|
|
834
981
|
|
|
835
982
|
// src/commands/init/generate-local-env.ts
|
|
836
|
-
import { readFile as
|
|
837
|
-
import { join as
|
|
983
|
+
import { readFile as readFile7, writeFile as writeFile7 } from "fs/promises";
|
|
984
|
+
import { join as join17 } from "path";
|
|
838
985
|
|
|
839
986
|
// src/utils/random.ts
|
|
840
987
|
import { randomBytes } from "crypto";
|
|
@@ -844,14 +991,14 @@ function generateRandomSecret() {
|
|
|
844
991
|
|
|
845
992
|
// src/commands/init/generate-local-env.ts
|
|
846
993
|
async function generateLocalEnv(outputDir, logger, coreDirName = "core") {
|
|
847
|
-
const examplePath =
|
|
848
|
-
const envPath =
|
|
994
|
+
const examplePath = join17(outputDir, coreDirName, "local", ".env.example");
|
|
995
|
+
const envPath = join17(outputDir, coreDirName, "local", ".env");
|
|
849
996
|
logger.log(`Generating ${coreDirName}/local/.env with random secrets...`);
|
|
850
|
-
const content = await
|
|
997
|
+
const content = await readFile7(examplePath, "utf-8");
|
|
851
998
|
const result = content.replace(/^(PAE_AUTH_JWT_SECRET|PAE_GATEWAY_SERVICE_ACCESS_SECRET|PAE_DB_PASSWORD)=$/gm, (_, key) => {
|
|
852
999
|
return `${key}=${generateRandomSecret()}`;
|
|
853
1000
|
});
|
|
854
|
-
await
|
|
1001
|
+
await writeFile7(envPath, result, "utf-8");
|
|
855
1002
|
}
|
|
856
1003
|
|
|
857
1004
|
// src/commands/init/init.command.ts
|
|
@@ -868,7 +1015,7 @@ async function init(params, logger) {
|
|
|
868
1015
|
}
|
|
869
1016
|
const { organizationName, platformName, platformDisplayName } = params;
|
|
870
1017
|
const platformTitle = camelize(platformName);
|
|
871
|
-
const outputDir =
|
|
1018
|
+
const outputDir = cwd5();
|
|
872
1019
|
logger.log(`Initializing ${platformTitle} platform for @${organizationName}...`);
|
|
873
1020
|
const coreDirName = `${platformName}-core`;
|
|
874
1021
|
const bootstrapSuffix = params.bootstrapServiceSuffix ?? "bootstrap-service";
|
|
@@ -907,7 +1054,7 @@ async function init(params, logger) {
|
|
|
907
1054
|
}
|
|
908
1055
|
try {
|
|
909
1056
|
await scaffoldPlatformBootstrap(
|
|
910
|
-
|
|
1057
|
+
join18(outputDir, coreDirName, "services", bootstrapServiceName),
|
|
911
1058
|
variables,
|
|
912
1059
|
logger
|
|
913
1060
|
);
|
|
@@ -917,7 +1064,7 @@ async function init(params, logger) {
|
|
|
917
1064
|
}
|
|
918
1065
|
try {
|
|
919
1066
|
await scaffoldCustomizationUi(
|
|
920
|
-
|
|
1067
|
+
join18(outputDir, coreDirName, "ui", customizationUiName),
|
|
921
1068
|
variables,
|
|
922
1069
|
logger
|
|
923
1070
|
);
|
|
@@ -927,7 +1074,7 @@ async function init(params, logger) {
|
|
|
927
1074
|
}
|
|
928
1075
|
try {
|
|
929
1076
|
await registerCustomizationModule(
|
|
930
|
-
|
|
1077
|
+
join18(outputDir, coreDirName, "services", bootstrapServiceName),
|
|
931
1078
|
organizationName,
|
|
932
1079
|
logger,
|
|
933
1080
|
platformName
|
|
@@ -940,15 +1087,15 @@ async function init(params, logger) {
|
|
|
940
1087
|
}
|
|
941
1088
|
|
|
942
1089
|
// src/commands/configure-idp/configure-idp.command.ts
|
|
943
|
-
import { join as
|
|
1090
|
+
import { join as join19 } from "path";
|
|
944
1091
|
import { fetch as undiciFetch, Agent } from "undici";
|
|
945
1092
|
|
|
946
1093
|
// src/utils/env-reader.ts
|
|
947
|
-
import { readFile as
|
|
1094
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
948
1095
|
async function readEnvFile(filePath) {
|
|
949
1096
|
let content;
|
|
950
1097
|
try {
|
|
951
|
-
content = await
|
|
1098
|
+
content = await readFile8(filePath, "utf-8");
|
|
952
1099
|
} catch (error) {
|
|
953
1100
|
if (error.code === "ENOENT") {
|
|
954
1101
|
throw new Error(`.env file not found at ${filePath}`);
|
|
@@ -1024,7 +1171,7 @@ async function configureIdp(params, logger) {
|
|
|
1024
1171
|
logger.log("Error: Cannot configure an IDP \u2014 no platform initialized in this directory.");
|
|
1025
1172
|
return;
|
|
1026
1173
|
}
|
|
1027
|
-
const envPath =
|
|
1174
|
+
const envPath = join19(layout.localDir, ".env");
|
|
1028
1175
|
let env;
|
|
1029
1176
|
try {
|
|
1030
1177
|
env = await readEnvFile(envPath);
|
|
@@ -1076,13 +1223,13 @@ async function configureIdp(params, logger) {
|
|
|
1076
1223
|
}
|
|
1077
1224
|
|
|
1078
1225
|
// src/commands/create-service-module/create-service-module.command.ts
|
|
1079
|
-
import { join as
|
|
1080
|
-
import { access as
|
|
1226
|
+
import { join as join21, resolve as resolve3 } from "path";
|
|
1227
|
+
import { access as access4 } from "fs/promises";
|
|
1081
1228
|
|
|
1082
1229
|
// src/commands/create-service-module/scaffold-service-module.ts
|
|
1083
1230
|
import { fileURLToPath as fileURLToPath9 } from "url";
|
|
1084
|
-
import { join as
|
|
1085
|
-
var nestjsServiceModuleTemplateDir2 =
|
|
1231
|
+
import { join as join20, dirname as dirname11 } from "path";
|
|
1232
|
+
var nestjsServiceModuleTemplateDir2 = join20(
|
|
1086
1233
|
dirname11(fileURLToPath9(import.meta.url)),
|
|
1087
1234
|
"..",
|
|
1088
1235
|
"templates",
|
|
@@ -1117,7 +1264,7 @@ function buildCustomServiceModuleEntry(organizationName, serviceName, serviceDis
|
|
|
1117
1264
|
}
|
|
1118
1265
|
|
|
1119
1266
|
// src/commands/create-service-module/append-docker-compose.ts
|
|
1120
|
-
import { readFile as
|
|
1267
|
+
import { readFile as readFile9, writeFile as writeFile8 } from "fs/promises";
|
|
1121
1268
|
async function appendServiceToDockerCompose(dockerComposePath, serviceName, platformName, applicationName, port, logger) {
|
|
1122
1269
|
const block = `
|
|
1123
1270
|
${serviceName}:
|
|
@@ -1132,8 +1279,8 @@ async function appendServiceToDockerCompose(dockerComposePath, serviceName, plat
|
|
|
1132
1279
|
- \${PWD}/:/app/out
|
|
1133
1280
|
`;
|
|
1134
1281
|
try {
|
|
1135
|
-
const existing = await
|
|
1136
|
-
await
|
|
1282
|
+
const existing = await readFile9(dockerComposePath, "utf-8");
|
|
1283
|
+
await writeFile8(dockerComposePath, existing + block, "utf-8");
|
|
1137
1284
|
logger.log(`Updated docker-compose: ${dockerComposePath}`);
|
|
1138
1285
|
} catch (err) {
|
|
1139
1286
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -1169,18 +1316,18 @@ async function createServiceModule(params, logger) {
|
|
|
1169
1316
|
logger.log(`Error: The specified application "${applicationName}" is not registered in the product manifest.`);
|
|
1170
1317
|
return;
|
|
1171
1318
|
}
|
|
1172
|
-
const applicationDir =
|
|
1319
|
+
const applicationDir = resolve3(join21(rootDir, coreDirName), appEntry.localPath);
|
|
1173
1320
|
try {
|
|
1174
|
-
await
|
|
1321
|
+
await access4(applicationDir);
|
|
1175
1322
|
} catch {
|
|
1176
1323
|
logger.log(`Error: The specified application "${applicationName}" does not exist in the platform.`);
|
|
1177
1324
|
return;
|
|
1178
1325
|
}
|
|
1179
1326
|
const suffix = serviceNameSuffix === void 0 ? "service" : serviceNameSuffix;
|
|
1180
1327
|
const fullServiceName = suffix ? `${platformName}-${serviceName}-${suffix}` : `${platformName}-${serviceName}`;
|
|
1181
|
-
const serviceDir =
|
|
1328
|
+
const serviceDir = join21(applicationDir, "services", fullServiceName);
|
|
1182
1329
|
try {
|
|
1183
|
-
await
|
|
1330
|
+
await access4(serviceDir);
|
|
1184
1331
|
logger.log(`Error: A service named "${fullServiceName}" already exists in application "${applicationName}".`);
|
|
1185
1332
|
return;
|
|
1186
1333
|
} catch {
|
|
@@ -1199,21 +1346,21 @@ async function createServiceModule(params, logger) {
|
|
|
1199
1346
|
logger.log(`Error: Could not scaffold NestJS service \u2014 ${formatError(err)}`);
|
|
1200
1347
|
return;
|
|
1201
1348
|
}
|
|
1202
|
-
const bootstrapServiceDir =
|
|
1349
|
+
const bootstrapServiceDir = join21(applicationDir, "services", `${platformName}-${applicationName}-bootstrap-service`);
|
|
1203
1350
|
const moduleEntry = buildCustomServiceModuleEntry(organizationName, fullServiceName, serviceDisplayName);
|
|
1204
1351
|
await addModuleEntry(bootstrapServiceDir, applicationName, moduleEntry, logger);
|
|
1205
|
-
const dockerComposePath =
|
|
1352
|
+
const dockerComposePath = join21(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
|
|
1206
1353
|
const port = await getNextAvailablePort(localDir);
|
|
1207
1354
|
await appendServiceToDockerCompose(dockerComposePath, fullServiceName, platformName, applicationName, port, logger);
|
|
1208
1355
|
logger.log(`Done! Service module "${fullServiceName}" added to application "${applicationName}".`);
|
|
1209
1356
|
}
|
|
1210
1357
|
|
|
1211
1358
|
// src/commands/create-ui-module/create-ui-module.command.ts
|
|
1212
|
-
import { join as
|
|
1213
|
-
import { access as
|
|
1359
|
+
import { join as join22, resolve as resolve4 } from "path";
|
|
1360
|
+
import { access as access5, readdir as readdir4 } from "fs/promises";
|
|
1214
1361
|
|
|
1215
1362
|
// src/commands/create-ui-module/append-ui-docker-compose.ts
|
|
1216
|
-
import { readFile as
|
|
1363
|
+
import { readFile as readFile10, writeFile as writeFile9 } from "fs/promises";
|
|
1217
1364
|
async function appendUiToDockerCompose(dockerComposePath, platformName, applicationName, port, logger, uiNameOverride) {
|
|
1218
1365
|
const uiName = uiNameOverride ?? `${platformName}-${applicationName}-ui`;
|
|
1219
1366
|
const block = `
|
|
@@ -1227,8 +1374,8 @@ async function appendUiToDockerCompose(dockerComposePath, platformName, applicat
|
|
|
1227
1374
|
- \${PWD}/:/app/out
|
|
1228
1375
|
`;
|
|
1229
1376
|
try {
|
|
1230
|
-
const existing = await
|
|
1231
|
-
await
|
|
1377
|
+
const existing = await readFile10(dockerComposePath, "utf-8");
|
|
1378
|
+
await writeFile9(dockerComposePath, existing + block, "utf-8");
|
|
1232
1379
|
logger.log(`Updated docker-compose: ${dockerComposePath}`);
|
|
1233
1380
|
} catch (err) {
|
|
1234
1381
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -1264,14 +1411,14 @@ async function createUiModule(params, logger) {
|
|
|
1264
1411
|
logger.log(`Error: The specified application "${applicationName}" is not registered in the product manifest.`);
|
|
1265
1412
|
return;
|
|
1266
1413
|
}
|
|
1267
|
-
const applicationDir =
|
|
1414
|
+
const applicationDir = resolve4(join22(rootDir, coreDirName), appEntry.localPath);
|
|
1268
1415
|
try {
|
|
1269
|
-
await
|
|
1416
|
+
await access5(applicationDir);
|
|
1270
1417
|
} catch {
|
|
1271
1418
|
logger.log(`Error: The specified application "${applicationName}" does not exist in the platform.`);
|
|
1272
1419
|
return;
|
|
1273
1420
|
}
|
|
1274
|
-
const uiDir =
|
|
1421
|
+
const uiDir = join22(applicationDir, "ui");
|
|
1275
1422
|
try {
|
|
1276
1423
|
const uiEntries = await readdir4(uiDir);
|
|
1277
1424
|
const existingUiModules = uiEntries.filter((e) => e !== ".gitkeep");
|
|
@@ -1283,7 +1430,7 @@ async function createUiModule(params, logger) {
|
|
|
1283
1430
|
}
|
|
1284
1431
|
const uiSuffix = uiModuleSuffix === void 0 ? "ui" : uiModuleSuffix;
|
|
1285
1432
|
const uiName = uiSuffix ? `${platformName}-${applicationName}-${uiSuffix}` : `${platformName}-${applicationName}`;
|
|
1286
|
-
const uiOutputDir =
|
|
1433
|
+
const uiOutputDir = join22(uiDir, uiName);
|
|
1287
1434
|
const uiBaseDir = `${platformName}-${applicationName}/ui`;
|
|
1288
1435
|
try {
|
|
1289
1436
|
await scaffoldUiModule(uiOutputDir, organizationName, platformName, applicationName, applicationDisplayName, uiBaseDir, logger);
|
|
@@ -1291,10 +1438,10 @@ async function createUiModule(params, logger) {
|
|
|
1291
1438
|
logger.log(`Error: Could not scaffold UI module \u2014 ${formatError(err)}`);
|
|
1292
1439
|
return;
|
|
1293
1440
|
}
|
|
1294
|
-
const bootstrapServiceDir =
|
|
1441
|
+
const bootstrapServiceDir = join22(applicationDir, "services", `${platformName}-${applicationName}-bootstrap-service`);
|
|
1295
1442
|
const moduleEntry = buildUiModuleEntry(organizationName, platformName, applicationName, applicationDisplayName, uiName);
|
|
1296
1443
|
await addModuleEntry(bootstrapServiceDir, applicationName, moduleEntry, logger);
|
|
1297
|
-
const dockerComposePath =
|
|
1444
|
+
const dockerComposePath = join22(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
|
|
1298
1445
|
const port = await getNextAvailablePort(localDir);
|
|
1299
1446
|
await appendUiToDockerCompose(dockerComposePath, platformName, applicationName, port, logger, uiName);
|
|
1300
1447
|
logger.log(`Done! UI module "${uiName}" added to application "${applicationName}".`);
|
|
@@ -1302,14 +1449,14 @@ async function createUiModule(params, logger) {
|
|
|
1302
1449
|
|
|
1303
1450
|
// src/commands/status/status-checks.ts
|
|
1304
1451
|
import { spawn as spawn3 } from "child_process";
|
|
1305
|
-
import { access as
|
|
1306
|
-
import { join as
|
|
1452
|
+
import { access as access8 } from "fs/promises";
|
|
1453
|
+
import { join as join25, resolve as resolve7 } from "path";
|
|
1307
1454
|
import { fetch as undiciFetch2, Agent as Agent2 } from "undici";
|
|
1308
1455
|
|
|
1309
1456
|
// src/commands/local-scripts/docker-compose-orchestrator.ts
|
|
1310
1457
|
import { spawn } from "child_process";
|
|
1311
|
-
import { access as
|
|
1312
|
-
import { join as
|
|
1458
|
+
import { access as access6 } from "fs/promises";
|
|
1459
|
+
import { join as join23, resolve as resolve5 } from "path";
|
|
1313
1460
|
function runDockerCompose(args2, logger, rootDir, signal) {
|
|
1314
1461
|
return new Promise((resolvePromise) => {
|
|
1315
1462
|
const child = spawn("docker", ["compose", ...args2], {
|
|
@@ -1372,20 +1519,29 @@ function captureDockerCompose(args2, rootDir) {
|
|
|
1372
1519
|
child.on("error", (err) => reject(err));
|
|
1373
1520
|
});
|
|
1374
1521
|
}
|
|
1375
|
-
async function getAppComposePaths(localDir, platformName, manifest) {
|
|
1522
|
+
async function getAppComposePaths(localDir, coreDir, platformName, manifest, logger) {
|
|
1376
1523
|
const results = [];
|
|
1377
1524
|
for (const app of manifest.applications) {
|
|
1378
|
-
const
|
|
1379
|
-
const
|
|
1525
|
+
const appDir = resolve5(join23(coreDir), app.localPath);
|
|
1526
|
+
const newPath = join23(appDir, "docker-compose.yml");
|
|
1527
|
+
const legacyPrefixedPath = join23(localDir, `${platformName}-${app.name}-docker-compose.yml`);
|
|
1528
|
+
const legacyUnprefixedPath = join23(localDir, `${app.name}-docker-compose.yml`);
|
|
1380
1529
|
let resolved = null;
|
|
1381
1530
|
try {
|
|
1382
|
-
await
|
|
1383
|
-
resolved =
|
|
1531
|
+
await access6(newPath);
|
|
1532
|
+
resolved = newPath;
|
|
1384
1533
|
} catch {
|
|
1385
1534
|
try {
|
|
1386
|
-
await
|
|
1387
|
-
resolved =
|
|
1535
|
+
await access6(legacyPrefixedPath);
|
|
1536
|
+
resolved = legacyPrefixedPath;
|
|
1537
|
+
logger?.log(`Warning: docker-compose for "${app.name}" found in legacy location (core/local/). Consider moving it to the app monorepo root.`);
|
|
1388
1538
|
} catch {
|
|
1539
|
+
try {
|
|
1540
|
+
await access6(legacyUnprefixedPath);
|
|
1541
|
+
resolved = legacyUnprefixedPath;
|
|
1542
|
+
logger?.log(`Warning: docker-compose for "${app.name}" found in legacy location (core/local/). Consider moving it to the app monorepo root.`);
|
|
1543
|
+
} catch {
|
|
1544
|
+
}
|
|
1389
1545
|
}
|
|
1390
1546
|
}
|
|
1391
1547
|
if (resolved) {
|
|
@@ -1395,51 +1551,51 @@ async function getAppComposePaths(localDir, platformName, manifest) {
|
|
|
1395
1551
|
return results;
|
|
1396
1552
|
}
|
|
1397
1553
|
async function buildFullComposeArgs(layout, manifest, logger) {
|
|
1398
|
-
const { coreDirName, localDir } = layout;
|
|
1554
|
+
const { coreDirName, localDir, coreDir } = layout;
|
|
1399
1555
|
const platformName = manifest.product.name;
|
|
1400
|
-
const prefixedCoreCompose =
|
|
1401
|
-
const unprefixedCoreCompose =
|
|
1556
|
+
const prefixedCoreCompose = join23(localDir, `${coreDirName}-docker-compose.yml`);
|
|
1557
|
+
const unprefixedCoreCompose = join23(localDir, "core-docker-compose.yml");
|
|
1402
1558
|
let coreComposePath;
|
|
1403
1559
|
try {
|
|
1404
|
-
await
|
|
1560
|
+
await access6(prefixedCoreCompose);
|
|
1405
1561
|
coreComposePath = prefixedCoreCompose;
|
|
1406
1562
|
} catch {
|
|
1407
1563
|
coreComposePath = unprefixedCoreCompose;
|
|
1408
1564
|
}
|
|
1409
1565
|
const fileArgs = [
|
|
1410
1566
|
"-f",
|
|
1411
|
-
|
|
1567
|
+
join23(localDir, "platform-docker-compose.yml"),
|
|
1412
1568
|
"-f",
|
|
1413
1569
|
coreComposePath
|
|
1414
1570
|
];
|
|
1415
|
-
const appEntries = await getAppComposePaths(localDir, platformName, manifest);
|
|
1571
|
+
const appEntries = await getAppComposePaths(localDir, coreDir, platformName, manifest, logger);
|
|
1416
1572
|
for (const { composePath } of appEntries) {
|
|
1417
1573
|
fileArgs.push("-f", composePath);
|
|
1418
1574
|
}
|
|
1419
1575
|
for (const app of manifest.applications) {
|
|
1420
1576
|
if (!appEntries.find((e) => e.appName === app.name)) {
|
|
1421
|
-
logger.log(`Warning: No docker-compose found for application "${app.name}"
|
|
1577
|
+
logger.log(`Warning: No docker-compose found for application "${app.name}" \u2014 skipping.`);
|
|
1422
1578
|
}
|
|
1423
1579
|
}
|
|
1424
1580
|
return fileArgs;
|
|
1425
1581
|
}
|
|
1426
1582
|
async function buildSelectedComposeFiles(layout, selectedManifest, includeCore) {
|
|
1427
|
-
const { coreDirName, localDir } = layout;
|
|
1583
|
+
const { coreDirName, localDir, coreDir } = layout;
|
|
1428
1584
|
const platformName = selectedManifest.product.name;
|
|
1429
1585
|
const files = [];
|
|
1430
1586
|
if (includeCore) {
|
|
1431
|
-
const prefixedCoreCompose =
|
|
1432
|
-
const unprefixedCoreCompose =
|
|
1587
|
+
const prefixedCoreCompose = join23(localDir, `${coreDirName}-docker-compose.yml`);
|
|
1588
|
+
const unprefixedCoreCompose = join23(localDir, "core-docker-compose.yml");
|
|
1433
1589
|
let coreComposePath;
|
|
1434
1590
|
try {
|
|
1435
|
-
await
|
|
1591
|
+
await access6(prefixedCoreCompose);
|
|
1436
1592
|
coreComposePath = prefixedCoreCompose;
|
|
1437
1593
|
} catch {
|
|
1438
1594
|
coreComposePath = unprefixedCoreCompose;
|
|
1439
1595
|
}
|
|
1440
|
-
files.push(
|
|
1596
|
+
files.push(join23(localDir, "platform-docker-compose.yml"), coreComposePath);
|
|
1441
1597
|
}
|
|
1442
|
-
const appEntries = await getAppComposePaths(localDir, platformName, selectedManifest);
|
|
1598
|
+
const appEntries = await getAppComposePaths(localDir, coreDir, platformName, selectedManifest);
|
|
1443
1599
|
for (const { composePath } of appEntries) {
|
|
1444
1600
|
files.push(composePath);
|
|
1445
1601
|
}
|
|
@@ -1481,7 +1637,7 @@ async function getServicesFromComposeFiles(selectedFiles, allFiles, rootDir) {
|
|
|
1481
1637
|
async function startEnvironment(layout, manifest, logger, signal, includeCore = true, fullManifest) {
|
|
1482
1638
|
const { rootDir, localDir } = layout;
|
|
1483
1639
|
const platformName = manifest.product.name;
|
|
1484
|
-
const envFile =
|
|
1640
|
+
const envFile = join23(localDir, ".env");
|
|
1485
1641
|
const isSelective = fullManifest !== void 0;
|
|
1486
1642
|
const fileArgs = await buildFullComposeArgs(layout, isSelective ? fullManifest : manifest, logger);
|
|
1487
1643
|
const projectArgs = ["-p", `${platformName}-platform`, "--project-directory", localDir, "--env-file", envFile];
|
|
@@ -1503,7 +1659,7 @@ async function startEnvironment(layout, manifest, logger, signal, includeCore =
|
|
|
1503
1659
|
async function stopEnvironment(layout, manifest, logger, signal, includeCore = true, fullManifest) {
|
|
1504
1660
|
const { rootDir, localDir } = layout;
|
|
1505
1661
|
const platformName = manifest.product.name;
|
|
1506
|
-
const envFile =
|
|
1662
|
+
const envFile = join23(localDir, ".env");
|
|
1507
1663
|
const isSelective = fullManifest !== void 0;
|
|
1508
1664
|
const fileArgs = await buildFullComposeArgs(layout, isSelective ? fullManifest : manifest, logger);
|
|
1509
1665
|
const projectArgs = ["-p", `${platformName}-platform`, "--project-directory", localDir, "--env-file", envFile];
|
|
@@ -1522,10 +1678,18 @@ async function stopEnvironment(layout, manifest, logger, signal, includeCore = t
|
|
|
1522
1678
|
await runDockerCompose([...projectArgs, ...fileArgs, "down"], logger, rootDir, signal);
|
|
1523
1679
|
}
|
|
1524
1680
|
}
|
|
1525
|
-
async function
|
|
1681
|
+
async function resetEnvironment(layout, manifest, logger, signal) {
|
|
1526
1682
|
const { rootDir, localDir } = layout;
|
|
1527
1683
|
const platformName = manifest.product.name;
|
|
1528
|
-
const envFile =
|
|
1684
|
+
const envFile = join23(localDir, ".env");
|
|
1685
|
+
const fileArgs = await buildFullComposeArgs(layout, manifest, logger);
|
|
1686
|
+
const projectArgs = ["-p", `${platformName}-platform`, "--project-directory", localDir, "--env-file", envFile];
|
|
1687
|
+
await runDockerCompose([...projectArgs, ...fileArgs, "down", "-v"], logger, rootDir, signal);
|
|
1688
|
+
}
|
|
1689
|
+
async function destroyEnvironment(layout, manifest, logger, signal, includeCore = true, fullManifest, removeImages = true) {
|
|
1690
|
+
const { rootDir, localDir } = layout;
|
|
1691
|
+
const platformName = manifest.product.name;
|
|
1692
|
+
const envFile = join23(localDir, ".env");
|
|
1529
1693
|
const isSelective = fullManifest !== void 0;
|
|
1530
1694
|
const fileArgs = await buildFullComposeArgs(layout, isSelective ? fullManifest : manifest, logger);
|
|
1531
1695
|
const projectArgs = ["-p", `${platformName}-platform`, "--project-directory", localDir, "--env-file", envFile];
|
|
@@ -1544,14 +1708,15 @@ async function destroyEnvironment(layout, manifest, logger, signal, includeCore
|
|
|
1544
1708
|
}
|
|
1545
1709
|
} else {
|
|
1546
1710
|
logger.log("Destroying environment...");
|
|
1547
|
-
|
|
1711
|
+
const downArgs = removeImages ? ["down", "-v", "--rmi", "local"] : ["down", "-v"];
|
|
1712
|
+
await runDockerCompose([...projectArgs, ...fileArgs, ...downArgs], logger, rootDir, signal);
|
|
1548
1713
|
}
|
|
1549
1714
|
}
|
|
1550
1715
|
|
|
1551
1716
|
// src/commands/local-scripts/npm-orchestrator.ts
|
|
1552
1717
|
import { spawn as spawn2 } from "child_process";
|
|
1553
|
-
import { access as
|
|
1554
|
-
import { resolve as
|
|
1718
|
+
import { access as access7 } from "fs/promises";
|
|
1719
|
+
import { resolve as resolve6, join as join24 } from "path";
|
|
1555
1720
|
function runCommand(command, args2, workDir, logger, signal) {
|
|
1556
1721
|
return new Promise((resolvePromise) => {
|
|
1557
1722
|
const child = spawn2(command, args2, {
|
|
@@ -1598,7 +1763,7 @@ function runCommand(command, args2, workDir, logger, signal) {
|
|
|
1598
1763
|
}
|
|
1599
1764
|
async function dirExists(dirPath) {
|
|
1600
1765
|
try {
|
|
1601
|
-
await
|
|
1766
|
+
await access7(dirPath);
|
|
1602
1767
|
return true;
|
|
1603
1768
|
} catch {
|
|
1604
1769
|
return false;
|
|
@@ -1608,7 +1773,7 @@ async function installDependencies(layout, manifest, logger, signal, includeCore
|
|
|
1608
1773
|
const { coreDir, coreDirName } = layout;
|
|
1609
1774
|
const appDirs = [];
|
|
1610
1775
|
for (const app of manifest.applications) {
|
|
1611
|
-
const appDir =
|
|
1776
|
+
const appDir = resolve6(join24(coreDir), app.localPath);
|
|
1612
1777
|
if (!await dirExists(appDir)) {
|
|
1613
1778
|
logger.log(`Warning: Application directory "${app.name}" not found at ${appDir} \u2014 skipping install.`);
|
|
1614
1779
|
continue;
|
|
@@ -1634,7 +1799,7 @@ async function buildAll(layout, manifest, logger, signal, includeCore = true) {
|
|
|
1634
1799
|
}
|
|
1635
1800
|
const appDirs = [];
|
|
1636
1801
|
for (const app of manifest.applications) {
|
|
1637
|
-
const appDir =
|
|
1802
|
+
const appDir = resolve6(join24(coreDir), app.localPath);
|
|
1638
1803
|
if (!await dirExists(appDir)) {
|
|
1639
1804
|
logger.log(`Warning: Application directory "${app.name}" not found at ${appDir} \u2014 skipping build.`);
|
|
1640
1805
|
continue;
|
|
@@ -1746,10 +1911,10 @@ async function runLocalScript(scriptName, logger, signal, appNames) {
|
|
|
1746
1911
|
}
|
|
1747
1912
|
|
|
1748
1913
|
// src/commands/status/status-checks.ts
|
|
1749
|
-
function spawnCapture(cmd, args2,
|
|
1914
|
+
function spawnCapture(cmd, args2, cwd11) {
|
|
1750
1915
|
return new Promise((resolvePromise) => {
|
|
1751
1916
|
const child = spawn3(cmd, args2, {
|
|
1752
|
-
cwd:
|
|
1917
|
+
cwd: cwd11,
|
|
1753
1918
|
shell: false,
|
|
1754
1919
|
stdio: ["ignore", "pipe", "pipe"]
|
|
1755
1920
|
});
|
|
@@ -1771,7 +1936,7 @@ function spawnCapture(cmd, args2, cwd5) {
|
|
|
1771
1936
|
}
|
|
1772
1937
|
async function pathExists(p) {
|
|
1773
1938
|
try {
|
|
1774
|
-
await
|
|
1939
|
+
await access8(p);
|
|
1775
1940
|
return true;
|
|
1776
1941
|
} catch {
|
|
1777
1942
|
return false;
|
|
@@ -1815,13 +1980,13 @@ async function checkInstalled(layout, manifest) {
|
|
|
1815
1980
|
const results = [];
|
|
1816
1981
|
const coreCheck = {
|
|
1817
1982
|
name: layout.coreDirName,
|
|
1818
|
-
path:
|
|
1819
|
-
exists: await pathExists(
|
|
1983
|
+
path: join25(layout.coreDir, "node_modules"),
|
|
1984
|
+
exists: await pathExists(join25(layout.coreDir, "node_modules"))
|
|
1820
1985
|
};
|
|
1821
1986
|
results.push(coreCheck);
|
|
1822
1987
|
for (const app of manifest.applications) {
|
|
1823
|
-
const appDir =
|
|
1824
|
-
const nodeModulesPath =
|
|
1988
|
+
const appDir = resolve7(layout.coreDir, app.localPath);
|
|
1989
|
+
const nodeModulesPath = join25(appDir, "node_modules");
|
|
1825
1990
|
results.push({
|
|
1826
1991
|
name: app.name,
|
|
1827
1992
|
path: nodeModulesPath,
|
|
@@ -1832,15 +1997,15 @@ async function checkInstalled(layout, manifest) {
|
|
|
1832
1997
|
}
|
|
1833
1998
|
async function checkBuilt(layout, manifest) {
|
|
1834
1999
|
const results = [];
|
|
1835
|
-
const coreTurboPath =
|
|
2000
|
+
const coreTurboPath = join25(layout.coreDir, ".turbo");
|
|
1836
2001
|
results.push({
|
|
1837
2002
|
name: layout.coreDirName,
|
|
1838
2003
|
path: coreTurboPath,
|
|
1839
2004
|
exists: await pathExists(coreTurboPath)
|
|
1840
2005
|
});
|
|
1841
2006
|
for (const app of manifest.applications) {
|
|
1842
|
-
const appDir =
|
|
1843
|
-
const appTurboPath =
|
|
2007
|
+
const appDir = resolve7(layout.coreDir, app.localPath);
|
|
2008
|
+
const appTurboPath = join25(appDir, ".turbo");
|
|
1844
2009
|
results.push({
|
|
1845
2010
|
name: app.name,
|
|
1846
2011
|
path: appTurboPath,
|
|
@@ -1852,13 +2017,13 @@ async function checkBuilt(layout, manifest) {
|
|
|
1852
2017
|
async function resolveComposeFiles(layout, manifest) {
|
|
1853
2018
|
const { localDir, coreDirName } = layout;
|
|
1854
2019
|
const platformName = manifest.product.name;
|
|
1855
|
-
const files = [
|
|
1856
|
-
const prefixedCore =
|
|
1857
|
-
const unprefixedCore =
|
|
2020
|
+
const files = [join25(localDir, "platform-docker-compose.yml")];
|
|
2021
|
+
const prefixedCore = join25(localDir, `${coreDirName}-docker-compose.yml`);
|
|
2022
|
+
const unprefixedCore = join25(localDir, "core-docker-compose.yml");
|
|
1858
2023
|
files.push(await pathExists(prefixedCore) ? prefixedCore : unprefixedCore);
|
|
1859
2024
|
for (const app of manifest.applications) {
|
|
1860
|
-
const prefixed =
|
|
1861
|
-
const unprefixed =
|
|
2025
|
+
const prefixed = join25(localDir, `${platformName}-${app.name}-docker-compose.yml`);
|
|
2026
|
+
const unprefixed = join25(localDir, `${app.name}-docker-compose.yml`);
|
|
1862
2027
|
if (await pathExists(prefixed)) files.push(prefixed);
|
|
1863
2028
|
else if (await pathExists(unprefixed)) files.push(unprefixed);
|
|
1864
2029
|
}
|
|
@@ -1964,15 +2129,15 @@ async function getServicesForComposeFiles(selectedFiles, allFiles, rootDir) {
|
|
|
1964
2129
|
async function checkContainersPerApp(layout, manifest, allContainers) {
|
|
1965
2130
|
const { localDir, coreDirName, rootDir } = layout;
|
|
1966
2131
|
const platformName = manifest.product.name;
|
|
1967
|
-
const prefixedCore =
|
|
1968
|
-
const unprefixedCore =
|
|
2132
|
+
const prefixedCore = join25(localDir, `${coreDirName}-docker-compose.yml`);
|
|
2133
|
+
const unprefixedCore = join25(localDir, "core-docker-compose.yml");
|
|
1969
2134
|
const coreComposePath = await pathExists(prefixedCore) ? prefixedCore : unprefixedCore;
|
|
1970
|
-
const platformComposePath =
|
|
2135
|
+
const platformComposePath = join25(localDir, "platform-docker-compose.yml");
|
|
1971
2136
|
const allFiles = [platformComposePath, coreComposePath];
|
|
1972
2137
|
const appComposeMap = /* @__PURE__ */ new Map();
|
|
1973
2138
|
for (const app of manifest.applications) {
|
|
1974
|
-
const prefixed =
|
|
1975
|
-
const unprefixed =
|
|
2139
|
+
const prefixed = join25(localDir, `${platformName}-${app.name}-docker-compose.yml`);
|
|
2140
|
+
const unprefixed = join25(localDir, `${app.name}-docker-compose.yml`);
|
|
1976
2141
|
if (await pathExists(prefixed)) {
|
|
1977
2142
|
appComposeMap.set(app.name, prefixed);
|
|
1978
2143
|
allFiles.push(prefixed);
|
|
@@ -2019,7 +2184,7 @@ async function checkContainersPerApp(layout, manifest, allContainers) {
|
|
|
2019
2184
|
return groups;
|
|
2020
2185
|
}
|
|
2021
2186
|
async function checkIdpProviders(layout) {
|
|
2022
|
-
const envPath =
|
|
2187
|
+
const envPath = join25(layout.localDir, ".env");
|
|
2023
2188
|
let env;
|
|
2024
2189
|
try {
|
|
2025
2190
|
env = await readEnvFile(envPath);
|
|
@@ -2138,7 +2303,7 @@ var statusCommand = {
|
|
|
2138
2303
|
};
|
|
2139
2304
|
|
|
2140
2305
|
// src/commands/manage-platform-admins/manage-platform-admins.command.ts
|
|
2141
|
-
import { join as
|
|
2306
|
+
import { join as join26 } from "path";
|
|
2142
2307
|
import { fetch as undiciFetch3, Agent as Agent3 } from "undici";
|
|
2143
2308
|
var MANAGE_PLATFORM_ADMINS_COMMAND_NAME = "manage-platform-admins";
|
|
2144
2309
|
var managePlatformAdminsCommand = {
|
|
@@ -2152,7 +2317,7 @@ async function getGatewayConfig(logger) {
|
|
|
2152
2317
|
logger.log("Error: Cannot manage platform admins \u2014 no platform initialized in this directory.");
|
|
2153
2318
|
return null;
|
|
2154
2319
|
}
|
|
2155
|
-
const envPath =
|
|
2320
|
+
const envPath = join26(layout.localDir, ".env");
|
|
2156
2321
|
let env;
|
|
2157
2322
|
try {
|
|
2158
2323
|
env = await readEnvFile(envPath);
|
|
@@ -2355,6 +2520,46 @@ async function installAiPlugin(params, context) {
|
|
|
2355
2520
|
}
|
|
2356
2521
|
}
|
|
2357
2522
|
|
|
2523
|
+
// src/commands/standalone/standalone.command.ts
|
|
2524
|
+
var STANDALONE_COMMAND_NAME = "standalone";
|
|
2525
|
+
var STANDALONE_STOP_COMMAND_NAME = "standalone-stop";
|
|
2526
|
+
var standaloneCommand = {
|
|
2527
|
+
name: STANDALONE_COMMAND_NAME,
|
|
2528
|
+
description: "Start platform in standalone mode for this application",
|
|
2529
|
+
hidden: (ctx) => !ctx.inAppMonorepo
|
|
2530
|
+
};
|
|
2531
|
+
var standaloneStopCommand = {
|
|
2532
|
+
name: STANDALONE_STOP_COMMAND_NAME,
|
|
2533
|
+
description: "Stop standalone platform",
|
|
2534
|
+
hidden: (ctx) => !ctx.inAppMonorepo
|
|
2535
|
+
};
|
|
2536
|
+
|
|
2537
|
+
// src/commands/standalone-config/standalone-config.command.ts
|
|
2538
|
+
var STANDALONE_CONFIG_SHOW_COMMAND_NAME = "standalone-config-show";
|
|
2539
|
+
var STANDALONE_CONFIG_SET_COMMAND_NAME = "standalone-config-set";
|
|
2540
|
+
var STANDALONE_CONFIG_DELETE_COMMAND_NAME = "standalone-config-delete";
|
|
2541
|
+
var STANDALONE_CONFIG_LIST_COMMAND_NAME = "standalone-config-list";
|
|
2542
|
+
var standaloneConfigShowCommand = {
|
|
2543
|
+
name: STANDALONE_CONFIG_SHOW_COMMAND_NAME,
|
|
2544
|
+
description: "Show resolved standalone config for current app",
|
|
2545
|
+
hidden: () => false
|
|
2546
|
+
};
|
|
2547
|
+
var standaloneConfigSetCommand = {
|
|
2548
|
+
name: STANDALONE_CONFIG_SET_COMMAND_NAME,
|
|
2549
|
+
description: "Edit standalone config defaults or app overrides",
|
|
2550
|
+
hidden: () => false
|
|
2551
|
+
};
|
|
2552
|
+
var standaloneConfigDeleteCommand = {
|
|
2553
|
+
name: STANDALONE_CONFIG_DELETE_COMMAND_NAME,
|
|
2554
|
+
description: "Delete a saved standalone app config entry",
|
|
2555
|
+
hidden: () => false
|
|
2556
|
+
};
|
|
2557
|
+
var standaloneConfigListCommand = {
|
|
2558
|
+
name: STANDALONE_CONFIG_LIST_COMMAND_NAME,
|
|
2559
|
+
description: "List all apps with standalone config",
|
|
2560
|
+
hidden: () => false
|
|
2561
|
+
};
|
|
2562
|
+
|
|
2358
2563
|
// src/commands/registry.ts
|
|
2359
2564
|
var CommandRegistry = class {
|
|
2360
2565
|
commands = /* @__PURE__ */ new Map();
|
|
@@ -2396,6 +2601,12 @@ for (const cmd of localScriptCommands) {
|
|
|
2396
2601
|
registry.register(statusCommand);
|
|
2397
2602
|
registry.register(managePlatformAdminsCommand);
|
|
2398
2603
|
registry.register(installAiPluginCommand);
|
|
2604
|
+
registry.register(standaloneCommand);
|
|
2605
|
+
registry.register(standaloneStopCommand);
|
|
2606
|
+
registry.register(standaloneConfigShowCommand);
|
|
2607
|
+
registry.register(standaloneConfigSetCommand);
|
|
2608
|
+
registry.register(standaloneConfigDeleteCommand);
|
|
2609
|
+
registry.register(standaloneConfigListCommand);
|
|
2399
2610
|
|
|
2400
2611
|
// src/app-state.ts
|
|
2401
2612
|
var APP_STATE = {
|
|
@@ -2415,9 +2626,20 @@ async function createApplicationService(params, logger) {
|
|
|
2415
2626
|
|
|
2416
2627
|
// src/controllers/ui/create-application.ui-controller.ts
|
|
2417
2628
|
async function createApplicationUiController(ctx) {
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2629
|
+
const insidePlatform = await isPlatformInitialized();
|
|
2630
|
+
let platformName;
|
|
2631
|
+
let organizationName;
|
|
2632
|
+
if (!insidePlatform) {
|
|
2633
|
+
platformName = await ctx.prompt("Platform name (e.g. myplatform):");
|
|
2634
|
+
if (!/^[a-z0-9-]+$/.test(platformName)) {
|
|
2635
|
+
ctx.log("Error: Platform name must use only lowercase letters, numbers, and hyphens.");
|
|
2636
|
+
return;
|
|
2637
|
+
}
|
|
2638
|
+
organizationName = await ctx.prompt("Organization name (npm scope, e.g. myorg):");
|
|
2639
|
+
if (!/^[a-z0-9-]+$/.test(organizationName)) {
|
|
2640
|
+
ctx.log("Error: Organization name must use only lowercase letters, numbers, and hyphens.");
|
|
2641
|
+
return;
|
|
2642
|
+
}
|
|
2421
2643
|
}
|
|
2422
2644
|
const applicationName = await ctx.prompt("Application name:");
|
|
2423
2645
|
if (!/^[a-z0-9-]+$/.test(applicationName)) {
|
|
@@ -2434,7 +2656,9 @@ async function createApplicationUiController(ctx) {
|
|
|
2434
2656
|
applicationDisplayName,
|
|
2435
2657
|
applicationDescription,
|
|
2436
2658
|
hasUserInterface,
|
|
2437
|
-
hasBackendService
|
|
2659
|
+
hasBackendService,
|
|
2660
|
+
platformName,
|
|
2661
|
+
organizationName
|
|
2438
2662
|
},
|
|
2439
2663
|
ctx
|
|
2440
2664
|
);
|
|
@@ -2913,29 +3137,29 @@ async function installAiPluginService(params, context) {
|
|
|
2913
3137
|
}
|
|
2914
3138
|
|
|
2915
3139
|
// src/commands/install-ai-plugin/providers/claude.provider.ts
|
|
2916
|
-
import { homedir as
|
|
2917
|
-
import { join as
|
|
2918
|
-
import { readFile as
|
|
3140
|
+
import { homedir as homedir3 } from "os";
|
|
3141
|
+
import { join as join27 } from "path";
|
|
3142
|
+
import { readFile as readFile11, writeFile as writeFile10, mkdir as mkdir4, rm } from "fs/promises";
|
|
2919
3143
|
var ClaudeProvider = class {
|
|
2920
3144
|
name = "claude";
|
|
2921
|
-
baseDir =
|
|
3145
|
+
baseDir = join27(homedir3(), ".claude");
|
|
2922
3146
|
getInstallPath(skillName) {
|
|
2923
|
-
return
|
|
3147
|
+
return join27(this.baseDir, "skills", skillName, "SKILL.md");
|
|
2924
3148
|
}
|
|
2925
3149
|
manifestPath(pluginName) {
|
|
2926
|
-
return
|
|
3150
|
+
return join27(this.baseDir, `${pluginName}.manifest.json`);
|
|
2927
3151
|
}
|
|
2928
3152
|
async readManifest(pluginName) {
|
|
2929
3153
|
try {
|
|
2930
|
-
const content = await
|
|
3154
|
+
const content = await readFile11(this.manifestPath(pluginName), "utf-8");
|
|
2931
3155
|
return JSON.parse(content);
|
|
2932
3156
|
} catch {
|
|
2933
3157
|
return null;
|
|
2934
3158
|
}
|
|
2935
3159
|
}
|
|
2936
3160
|
async writeManifest(manifest) {
|
|
2937
|
-
await
|
|
2938
|
-
await
|
|
3161
|
+
await mkdir4(this.baseDir, { recursive: true });
|
|
3162
|
+
await writeFile10(
|
|
2939
3163
|
this.manifestPath(manifest.plugin),
|
|
2940
3164
|
JSON.stringify(manifest, null, 2),
|
|
2941
3165
|
"utf-8"
|
|
@@ -2955,16 +3179,16 @@ var ClaudeProvider = class {
|
|
|
2955
3179
|
}
|
|
2956
3180
|
async installSkill(skill, docsSource, logger) {
|
|
2957
3181
|
const sourcePath = docsSource.resolve(skill.sourceFile);
|
|
2958
|
-
let content = await
|
|
3182
|
+
let content = await readFile11(sourcePath, "utf-8");
|
|
2959
3183
|
const docsPath = docsSource.resolve("docs");
|
|
2960
3184
|
content = content.replaceAll("{{docsPath}}", docsPath);
|
|
2961
3185
|
const installPath = this.getInstallPath(skill.name);
|
|
2962
|
-
await
|
|
2963
|
-
await
|
|
3186
|
+
await mkdir4(join27(installPath, ".."), { recursive: true });
|
|
3187
|
+
await writeFile10(installPath, content, "utf-8");
|
|
2964
3188
|
logger.log(` Installed skill: ${installPath}`);
|
|
2965
3189
|
}
|
|
2966
3190
|
async removeSkill(skillName, logger) {
|
|
2967
|
-
const skillDir =
|
|
3191
|
+
const skillDir = join27(this.baseDir, "skills", skillName);
|
|
2968
3192
|
try {
|
|
2969
3193
|
await rm(skillDir, { recursive: true, force: true });
|
|
2970
3194
|
logger.log(` Removed skill: ${skillDir}`);
|
|
@@ -2972,11 +3196,11 @@ var ClaudeProvider = class {
|
|
|
2972
3196
|
}
|
|
2973
3197
|
}
|
|
2974
3198
|
async updatePermissions(docsSource, logger) {
|
|
2975
|
-
const settingsPath =
|
|
3199
|
+
const settingsPath = join27(this.baseDir, "settings.json");
|
|
2976
3200
|
const docsPath = docsSource.resolve("docs");
|
|
2977
3201
|
let settings = {};
|
|
2978
3202
|
try {
|
|
2979
|
-
const content = await
|
|
3203
|
+
const content = await readFile11(settingsPath, "utf-8");
|
|
2980
3204
|
settings = JSON.parse(content);
|
|
2981
3205
|
} catch {
|
|
2982
3206
|
}
|
|
@@ -2987,8 +3211,8 @@ var ClaudeProvider = class {
|
|
|
2987
3211
|
}
|
|
2988
3212
|
permissions.additionalDirectories = [...additionalDirectories, docsPath];
|
|
2989
3213
|
settings.permissions = permissions;
|
|
2990
|
-
await
|
|
2991
|
-
await
|
|
3214
|
+
await mkdir4(this.baseDir, { recursive: true });
|
|
3215
|
+
await writeFile10(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
|
|
2992
3216
|
logger.log(` Granted read access to docs: ${docsPath}`);
|
|
2993
3217
|
}
|
|
2994
3218
|
};
|
|
@@ -3008,11 +3232,11 @@ function getProvider(name) {
|
|
|
3008
3232
|
|
|
3009
3233
|
// src/commands/install-ai-plugin/docs-source/local.docs-source.ts
|
|
3010
3234
|
import { fileURLToPath as fileURLToPath10 } from "url";
|
|
3011
|
-
import { join as
|
|
3012
|
-
var packageRoot =
|
|
3235
|
+
import { join as join28, dirname as dirname12 } from "path";
|
|
3236
|
+
var packageRoot = join28(dirname12(fileURLToPath10(import.meta.url)), "..");
|
|
3013
3237
|
var LocalDocsSource = class {
|
|
3014
3238
|
resolve(relativePath) {
|
|
3015
|
-
return
|
|
3239
|
+
return join28(packageRoot, relativePath);
|
|
3016
3240
|
}
|
|
3017
3241
|
};
|
|
3018
3242
|
|
|
@@ -3054,6 +3278,837 @@ async function installAiPluginUiController(ctx) {
|
|
|
3054
3278
|
);
|
|
3055
3279
|
}
|
|
3056
3280
|
|
|
3281
|
+
// src/controllers/ui/standalone.ui-controller.ts
|
|
3282
|
+
import { rm as rm3 } from "fs/promises";
|
|
3283
|
+
import { join as join32 } from "path";
|
|
3284
|
+
import { cwd as cwd7 } from "process";
|
|
3285
|
+
import { exec } from "child_process";
|
|
3286
|
+
|
|
3287
|
+
// src/commands/standalone/app-monorepo-check.ts
|
|
3288
|
+
import { access as access9, readdir as readdir5 } from "fs/promises";
|
|
3289
|
+
import { join as join29 } from "path";
|
|
3290
|
+
import { cwd as cwd6 } from "process";
|
|
3291
|
+
async function findAppMonorepoLayout(startDir = cwd6()) {
|
|
3292
|
+
const composePath = join29(startDir, "docker-compose.yml");
|
|
3293
|
+
const packageJsonPath = join29(startDir, "package.json");
|
|
3294
|
+
try {
|
|
3295
|
+
await access9(composePath);
|
|
3296
|
+
await access9(packageJsonPath);
|
|
3297
|
+
} catch {
|
|
3298
|
+
return null;
|
|
3299
|
+
}
|
|
3300
|
+
if (await hasCoreChildDir(startDir)) {
|
|
3301
|
+
return null;
|
|
3302
|
+
}
|
|
3303
|
+
return { appDir: startDir, composePath };
|
|
3304
|
+
}
|
|
3305
|
+
async function hasCoreChildDir(dir) {
|
|
3306
|
+
try {
|
|
3307
|
+
const entries = await readdir5(dir, { withFileTypes: true });
|
|
3308
|
+
for (const entry of entries) {
|
|
3309
|
+
if (entry.isDirectory() && (entry.name.endsWith("-core") || entry.name === "core")) {
|
|
3310
|
+
try {
|
|
3311
|
+
await access9(join29(dir, entry.name, "product.manifest.json"));
|
|
3312
|
+
return true;
|
|
3313
|
+
} catch {
|
|
3314
|
+
}
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
3317
|
+
} catch {
|
|
3318
|
+
}
|
|
3319
|
+
return false;
|
|
3320
|
+
}
|
|
3321
|
+
async function isInAppMonorepo() {
|
|
3322
|
+
return await findAppMonorepoLayout() !== null;
|
|
3323
|
+
}
|
|
3324
|
+
|
|
3325
|
+
// src/commands/standalone/standalone-auto-detect.ts
|
|
3326
|
+
import { readdir as readdir6, readFile as readFile12 } from "fs/promises";
|
|
3327
|
+
import { join as join30, basename as basename2 } from "path";
|
|
3328
|
+
async function autoDetectAppIdentity(appDir) {
|
|
3329
|
+
const fromBootstrap = await detectFromBootstrap(appDir);
|
|
3330
|
+
if (fromBootstrap) return fromBootstrap;
|
|
3331
|
+
return detectFromPackageJson(appDir);
|
|
3332
|
+
}
|
|
3333
|
+
async function detectFromBootstrap(appDir) {
|
|
3334
|
+
const servicesDir = join30(appDir, "services");
|
|
3335
|
+
let entries;
|
|
3336
|
+
try {
|
|
3337
|
+
entries = await readdir6(servicesDir);
|
|
3338
|
+
} catch {
|
|
3339
|
+
return null;
|
|
3340
|
+
}
|
|
3341
|
+
const bootstrapDirs = entries.filter((e) => e.endsWith("-bootstrap-service"));
|
|
3342
|
+
if (bootstrapDirs.length === 0) return null;
|
|
3343
|
+
for (const bootstrapDirName of bootstrapDirs) {
|
|
3344
|
+
const dataDir = join30(servicesDir, bootstrapDirName, "src", "data");
|
|
3345
|
+
let dataDirEntries;
|
|
3346
|
+
try {
|
|
3347
|
+
dataDirEntries = await readdir6(dataDir);
|
|
3348
|
+
} catch {
|
|
3349
|
+
continue;
|
|
3350
|
+
}
|
|
3351
|
+
for (const subDir of dataDirEntries) {
|
|
3352
|
+
if (subDir === "platform") continue;
|
|
3353
|
+
const appJsonPath = join30(dataDir, subDir, "application.json");
|
|
3354
|
+
try {
|
|
3355
|
+
const content = await readFile12(appJsonPath, "utf-8");
|
|
3356
|
+
const appData = JSON.parse(content);
|
|
3357
|
+
if (!appData.name) continue;
|
|
3358
|
+
const applicationName = appData.name;
|
|
3359
|
+
const applicationDisplayName = appData.displayName ?? applicationName;
|
|
3360
|
+
const suffix = `-${applicationName}-bootstrap-service`;
|
|
3361
|
+
const platformName = bootstrapDirName.endsWith(suffix) ? bootstrapDirName.slice(0, -suffix.length) : inferPlatformNameFromAppDir(appDir, applicationName);
|
|
3362
|
+
if (!platformName) continue;
|
|
3363
|
+
return { platformName, applicationName, applicationDisplayName };
|
|
3364
|
+
} catch {
|
|
3365
|
+
continue;
|
|
3366
|
+
}
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3369
|
+
return null;
|
|
3370
|
+
}
|
|
3371
|
+
async function detectFromPackageJson(appDir) {
|
|
3372
|
+
try {
|
|
3373
|
+
const content = await readFile12(join30(appDir, "package.json"), "utf-8");
|
|
3374
|
+
const pkg = JSON.parse(content);
|
|
3375
|
+
const rawName = pkg.name ?? "";
|
|
3376
|
+
const applicationName = rawName.includes("/") ? rawName.split("/")[1] ?? rawName : rawName;
|
|
3377
|
+
if (!applicationName) return null;
|
|
3378
|
+
const applicationDisplayName = pkg.description ?? applicationName;
|
|
3379
|
+
const platformName = inferPlatformNameFromAppDir(appDir, applicationName);
|
|
3380
|
+
if (!platformName) return null;
|
|
3381
|
+
return { platformName, applicationName, applicationDisplayName };
|
|
3382
|
+
} catch {
|
|
3383
|
+
return null;
|
|
3384
|
+
}
|
|
3385
|
+
}
|
|
3386
|
+
function inferPlatformNameFromAppDir(appDir, applicationName) {
|
|
3387
|
+
const dirName = basename2(appDir);
|
|
3388
|
+
const suffix = `-${applicationName}`;
|
|
3389
|
+
if (dirName.endsWith(suffix) && dirName.length > suffix.length) {
|
|
3390
|
+
return dirName.slice(0, -suffix.length);
|
|
3391
|
+
}
|
|
3392
|
+
return null;
|
|
3393
|
+
}
|
|
3394
|
+
|
|
3395
|
+
// src/commands/standalone/standalone-orchestrator.ts
|
|
3396
|
+
import { join as join31, dirname as dirname13 } from "path";
|
|
3397
|
+
import { mkdir as mkdir5, rm as rm2, readdir as readdir7, access as access10, writeFile as writeFile11, readFile as readFile13 } from "fs/promises";
|
|
3398
|
+
import { fetch as undiciFetch4, Agent as Agent4 } from "undici";
|
|
3399
|
+
var TMP_BASE = "/tmp";
|
|
3400
|
+
var STANDALONE_PREFIX = "platform-standalone-";
|
|
3401
|
+
var DETACH_MARKER = ".standalone-running";
|
|
3402
|
+
function getTmpDir(platformName) {
|
|
3403
|
+
return join31(TMP_BASE, `${STANDALONE_PREFIX}${platformName}`);
|
|
3404
|
+
}
|
|
3405
|
+
async function findRunningSingleton() {
|
|
3406
|
+
try {
|
|
3407
|
+
const entries = await readdir7(TMP_BASE, { withFileTypes: true });
|
|
3408
|
+
for (const entry of entries) {
|
|
3409
|
+
if (entry.isDirectory() && entry.name.startsWith(STANDALONE_PREFIX)) {
|
|
3410
|
+
const candidateDir = join31(TMP_BASE, entry.name);
|
|
3411
|
+
try {
|
|
3412
|
+
await access10(join31(candidateDir, DETACH_MARKER));
|
|
3413
|
+
return candidateDir;
|
|
3414
|
+
} catch {
|
|
3415
|
+
}
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
} catch {
|
|
3419
|
+
}
|
|
3420
|
+
return null;
|
|
3421
|
+
}
|
|
3422
|
+
async function cleanupTmpDir(tmpDir, logger) {
|
|
3423
|
+
logger.log(`Cleaning up temporary directory: ${tmpDir}`);
|
|
3424
|
+
try {
|
|
3425
|
+
await rm2(tmpDir, { recursive: true, force: true });
|
|
3426
|
+
logger.log("Temporary directory removed.");
|
|
3427
|
+
} catch (err) {
|
|
3428
|
+
logger.log(`Warning: Could not remove temporary directory \u2014 ${err.message}`);
|
|
3429
|
+
}
|
|
3430
|
+
}
|
|
3431
|
+
async function waitForGateway(gatewayUrl, logger, timeoutMs = 12e4) {
|
|
3432
|
+
const healthUrl = `${gatewayUrl}/health`;
|
|
3433
|
+
const agent = new Agent4({ connect: { rejectUnauthorized: false } });
|
|
3434
|
+
const interval = 3e3;
|
|
3435
|
+
const start = Date.now();
|
|
3436
|
+
logger.log("Waiting for gateway to be healthy...");
|
|
3437
|
+
while (Date.now() - start < timeoutMs) {
|
|
3438
|
+
try {
|
|
3439
|
+
const res = await undiciFetch4(healthUrl, { dispatcher: agent });
|
|
3440
|
+
if (res.ok) {
|
|
3441
|
+
logger.log("Gateway is healthy.");
|
|
3442
|
+
return true;
|
|
3443
|
+
}
|
|
3444
|
+
} catch {
|
|
3445
|
+
}
|
|
3446
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
3447
|
+
}
|
|
3448
|
+
logger.log("Warning: Gateway did not become healthy within the timeout.");
|
|
3449
|
+
return false;
|
|
3450
|
+
}
|
|
3451
|
+
async function configureIdpFromConfig(config, localDir, logger) {
|
|
3452
|
+
if (!config.idp) {
|
|
3453
|
+
logger.log("No IDP configured \u2014 run 'platform standalone-config set' to add an IDP. Skipping IDP setup.");
|
|
3454
|
+
return;
|
|
3455
|
+
}
|
|
3456
|
+
const envPath = join31(localDir, ".env");
|
|
3457
|
+
let env;
|
|
3458
|
+
try {
|
|
3459
|
+
env = await readEnvFile(envPath);
|
|
3460
|
+
} catch (err) {
|
|
3461
|
+
logger.log(`Warning: Could not read .env for IDP setup \u2014 ${err.message}`);
|
|
3462
|
+
return;
|
|
3463
|
+
}
|
|
3464
|
+
const gatewayUrl = env.get("PAE_GATEWAY_HOST_URL");
|
|
3465
|
+
const accessSecret = env.get("PAE_GATEWAY_SERVICE_ACCESS_SECRET");
|
|
3466
|
+
if (!gatewayUrl || !accessSecret) {
|
|
3467
|
+
logger.log("Warning: Missing PAE_GATEWAY_HOST_URL or PAE_GATEWAY_SERVICE_ACCESS_SECRET \u2014 skipping IDP setup.");
|
|
3468
|
+
return;
|
|
3469
|
+
}
|
|
3470
|
+
const provider = idpProviderRegistry.get(config.idp.provider);
|
|
3471
|
+
if (!provider) {
|
|
3472
|
+
logger.log(`Warning: Unknown IDP provider "${config.idp.provider}" \u2014 skipping IDP setup.`);
|
|
3473
|
+
return;
|
|
3474
|
+
}
|
|
3475
|
+
const payload = provider.buildPayload({
|
|
3476
|
+
providerType: config.idp.provider,
|
|
3477
|
+
name: config.idp.name,
|
|
3478
|
+
issuer: config.idp.issuer,
|
|
3479
|
+
clientId: config.idp.clientId,
|
|
3480
|
+
clientSecret: config.idp.clientSecret,
|
|
3481
|
+
extras: config.idp.extras ?? {}
|
|
3482
|
+
});
|
|
3483
|
+
const agent = new Agent4({ connect: { rejectUnauthorized: false } });
|
|
3484
|
+
try {
|
|
3485
|
+
const response = await undiciFetch4(`${gatewayUrl}/_/providers`, {
|
|
3486
|
+
method: "POST",
|
|
3487
|
+
headers: {
|
|
3488
|
+
"Content-Type": "application/json",
|
|
3489
|
+
Accept: "application/json",
|
|
3490
|
+
Authorization: `Bearer ${accessSecret}`
|
|
3491
|
+
},
|
|
3492
|
+
body: JSON.stringify(payload),
|
|
3493
|
+
dispatcher: agent
|
|
3494
|
+
});
|
|
3495
|
+
if (!response.ok) {
|
|
3496
|
+
const body = await response.text().catch(() => "");
|
|
3497
|
+
logger.log(`Warning: IDP configuration failed \u2014 ${response.status}${body ? `: ${body}` : ""}`);
|
|
3498
|
+
} else {
|
|
3499
|
+
logger.log(`IDP provider "${config.idp.name}" configured successfully.`);
|
|
3500
|
+
}
|
|
3501
|
+
} catch (err) {
|
|
3502
|
+
const error = err;
|
|
3503
|
+
const cause = error.cause instanceof Error ? ` (cause: ${error.cause.message})` : "";
|
|
3504
|
+
logger.log(`Warning: Could not reach gateway for IDP setup \u2014 ${error.message}${cause}`);
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
3507
|
+
async function configureAdminUsersFromConfig(config, localDir, logger) {
|
|
3508
|
+
if (!config.adminUsers || config.adminUsers.length === 0) {
|
|
3509
|
+
return;
|
|
3510
|
+
}
|
|
3511
|
+
const envPath = join31(localDir, ".env");
|
|
3512
|
+
let env;
|
|
3513
|
+
try {
|
|
3514
|
+
env = await readEnvFile(envPath);
|
|
3515
|
+
} catch (err) {
|
|
3516
|
+
logger.log(`Warning: Could not read .env for admin setup \u2014 ${err.message}`);
|
|
3517
|
+
return;
|
|
3518
|
+
}
|
|
3519
|
+
const gatewayUrl = env.get("PAE_GATEWAY_HOST_URL");
|
|
3520
|
+
const accessSecret = env.get("PAE_GATEWAY_SERVICE_ACCESS_SECRET");
|
|
3521
|
+
if (!gatewayUrl || !accessSecret) {
|
|
3522
|
+
logger.log("Warning: Missing PAE_GATEWAY_HOST_URL or PAE_GATEWAY_SERVICE_ACCESS_SECRET \u2014 skipping admin setup.");
|
|
3523
|
+
return;
|
|
3524
|
+
}
|
|
3525
|
+
const agent = new Agent4({ connect: { rejectUnauthorized: false } });
|
|
3526
|
+
for (const username of config.adminUsers) {
|
|
3527
|
+
try {
|
|
3528
|
+
const response = await undiciFetch4(`${gatewayUrl}/applications/platform/rules`, {
|
|
3529
|
+
method: "POST",
|
|
3530
|
+
headers: {
|
|
3531
|
+
"Content-Type": "application/json",
|
|
3532
|
+
Accept: "application/json",
|
|
3533
|
+
Authorization: `Bearer ${accessSecret}`
|
|
3534
|
+
},
|
|
3535
|
+
body: JSON.stringify({ subject: username, subjectType: "user", resourceType: "role", resource: "admin" }),
|
|
3536
|
+
dispatcher: agent
|
|
3537
|
+
});
|
|
3538
|
+
if (response.status === 409) {
|
|
3539
|
+
logger.log(`Admin user "${username}" already configured.`);
|
|
3540
|
+
} else if (!response.ok) {
|
|
3541
|
+
const body = await response.text().catch(() => "");
|
|
3542
|
+
logger.log(`Warning: Could not configure admin "${username}" \u2014 ${response.status}${body ? `: ${body}` : ""}`);
|
|
3543
|
+
} else {
|
|
3544
|
+
logger.log(`Admin user "${username}" configured successfully.`);
|
|
3545
|
+
}
|
|
3546
|
+
} catch (err) {
|
|
3547
|
+
const error = err;
|
|
3548
|
+
const cause = error.cause instanceof Error ? ` (cause: ${error.cause.message})` : "";
|
|
3549
|
+
logger.log(`Warning: Could not reach gateway for admin setup \u2014 ${error.message}${cause}`);
|
|
3550
|
+
}
|
|
3551
|
+
}
|
|
3552
|
+
}
|
|
3553
|
+
function buildLayout(tmpDir, coreDirName) {
|
|
3554
|
+
const coreDir = join31(tmpDir, coreDirName);
|
|
3555
|
+
return {
|
|
3556
|
+
rootDir: tmpDir,
|
|
3557
|
+
coreDir,
|
|
3558
|
+
coreDirName,
|
|
3559
|
+
localDir: join31(coreDir, "local")
|
|
3560
|
+
};
|
|
3561
|
+
}
|
|
3562
|
+
async function generateProxyAppCompose(appDir, localDir, platformName, applicationName, logger) {
|
|
3563
|
+
const appComposePath = join31(appDir, "docker-compose.yml");
|
|
3564
|
+
let content;
|
|
3565
|
+
try {
|
|
3566
|
+
content = await readFile13(appComposePath, "utf-8");
|
|
3567
|
+
} catch {
|
|
3568
|
+
logger.log(`Warning: No docker-compose.yml found at ${appComposePath} \u2014 app services won't start.`);
|
|
3569
|
+
return;
|
|
3570
|
+
}
|
|
3571
|
+
const withAbsContexts = content.replace(
|
|
3572
|
+
/^(\s+context:\s+)\.\/(.*)$/gm,
|
|
3573
|
+
`$1${appDir}/$2`
|
|
3574
|
+
);
|
|
3575
|
+
const appParentDir = dirname13(appDir);
|
|
3576
|
+
const rewritten = withAbsContexts.replace(/\$\{PWD\}/g, appParentDir);
|
|
3577
|
+
const proxyPath = join31(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
|
|
3578
|
+
await writeFile11(proxyPath, rewritten, "utf-8");
|
|
3579
|
+
logger.log(`Generated app compose with absolute build paths.`);
|
|
3580
|
+
}
|
|
3581
|
+
function buildComposeManifest(manifest) {
|
|
3582
|
+
return {
|
|
3583
|
+
...manifest,
|
|
3584
|
+
applications: manifest.applications.map((app) => ({ ...app, localPath: "." }))
|
|
3585
|
+
};
|
|
3586
|
+
}
|
|
3587
|
+
async function startStandalone(config, appDir, logger, signal, detach = false) {
|
|
3588
|
+
const { platformName, organizationName, applicationName, applicationDisplayName } = config;
|
|
3589
|
+
const platformTitle = camelize(platformName);
|
|
3590
|
+
const tmpDir = getTmpDir(platformName);
|
|
3591
|
+
const coreDirName = `${platformName}-core`;
|
|
3592
|
+
const bootstrapSuffix = "bootstrap-service";
|
|
3593
|
+
const customizationUiSuffix = "customization-ui";
|
|
3594
|
+
const bootstrapServiceName = `${platformName}-${bootstrapSuffix}`;
|
|
3595
|
+
const customizationUiName = `${platformName}-${customizationUiSuffix}`;
|
|
3596
|
+
const runningSingleton = await findRunningSingleton();
|
|
3597
|
+
if (runningSingleton) {
|
|
3598
|
+
logger.log(`Error: A standalone platform is already running at ${runningSingleton}.`);
|
|
3599
|
+
logger.log(`Run "platform standalone-stop" to stop it before starting a new one.`);
|
|
3600
|
+
return;
|
|
3601
|
+
}
|
|
3602
|
+
logger.log(`Starting standalone platform for "${applicationDisplayName}"...`);
|
|
3603
|
+
await mkdir5(tmpDir, { recursive: true });
|
|
3604
|
+
await writeFile11(join31(tmpDir, DETACH_MARKER), platformName, "utf-8");
|
|
3605
|
+
const variables = {
|
|
3606
|
+
organizationName,
|
|
3607
|
+
platformName,
|
|
3608
|
+
platformTitle,
|
|
3609
|
+
platformDisplayName: platformTitle,
|
|
3610
|
+
bootstrapName: platformName,
|
|
3611
|
+
bootstrapServiceName,
|
|
3612
|
+
customizationUiName,
|
|
3613
|
+
bootstrapServiceDir: `${coreDirName}/services`
|
|
3614
|
+
};
|
|
3615
|
+
logger.log("Scaffolding temporary platform core...");
|
|
3616
|
+
await scaffoldPlatform(tmpDir, variables, logger);
|
|
3617
|
+
await generateLocalEnv(tmpDir, logger, coreDirName);
|
|
3618
|
+
const npmManifest = addApplicationToManifest(
|
|
3619
|
+
createInitialManifest({ organizationName, platformName, platformDisplayName: platformTitle }),
|
|
3620
|
+
{
|
|
3621
|
+
name: applicationName,
|
|
3622
|
+
displayName: applicationDisplayName,
|
|
3623
|
+
description: "",
|
|
3624
|
+
localPath: appDir,
|
|
3625
|
+
repository: null
|
|
3626
|
+
}
|
|
3627
|
+
);
|
|
3628
|
+
await writeManifest(npmManifest, tmpDir, coreDirName);
|
|
3629
|
+
const bootstrapServiceDir = join31(tmpDir, coreDirName, "services", bootstrapServiceName);
|
|
3630
|
+
await scaffoldPlatformBootstrap(bootstrapServiceDir, variables, logger);
|
|
3631
|
+
const customizationUiDir = join31(tmpDir, coreDirName, "ui", customizationUiName);
|
|
3632
|
+
await scaffoldCustomizationUi(customizationUiDir, variables, logger);
|
|
3633
|
+
await registerCustomizationModule(bootstrapServiceDir, organizationName, logger, platformName);
|
|
3634
|
+
const layout = buildLayout(tmpDir, coreDirName);
|
|
3635
|
+
logger.log("Installing dependencies...");
|
|
3636
|
+
await installDependencies(layout, npmManifest, logger, signal, true);
|
|
3637
|
+
if (signal?.aborted) return;
|
|
3638
|
+
logger.log("Building...");
|
|
3639
|
+
await buildAll(layout, npmManifest, logger, signal, true);
|
|
3640
|
+
if (signal?.aborted) return;
|
|
3641
|
+
await generateProxyAppCompose(appDir, layout.localDir, platformName, applicationName, logger);
|
|
3642
|
+
const composeManifest = buildComposeManifest(npmManifest);
|
|
3643
|
+
await writeManifest(composeManifest, tmpDir, coreDirName);
|
|
3644
|
+
logger.log("Resetting previous environment (removing stale containers and volumes)...");
|
|
3645
|
+
await resetEnvironment(layout, composeManifest, logger, signal);
|
|
3646
|
+
if (signal?.aborted) return;
|
|
3647
|
+
logger.log("Starting containers...");
|
|
3648
|
+
await startEnvironment(layout, composeManifest, logger, signal);
|
|
3649
|
+
if (signal?.aborted) return;
|
|
3650
|
+
const env = await readEnvFile(join31(layout.localDir, ".env")).catch(() => /* @__PURE__ */ new Map());
|
|
3651
|
+
const gatewayUrl = env.get("PAE_GATEWAY_HOST_URL") ?? "https://localhost:443";
|
|
3652
|
+
const gatewayReady = await waitForGateway(gatewayUrl, logger);
|
|
3653
|
+
if (!gatewayReady) {
|
|
3654
|
+
logger.log("Error: Gateway did not become healthy. The platform may not be functioning correctly.");
|
|
3655
|
+
logger.log(`Check container logs with: docker compose -p ${platformName}-platform logs pae-nestjs-gateway-service`);
|
|
3656
|
+
}
|
|
3657
|
+
if (gatewayReady) {
|
|
3658
|
+
await configureIdpFromConfig(config, layout.localDir, logger);
|
|
3659
|
+
await configureAdminUsersFromConfig(config, layout.localDir, logger);
|
|
3660
|
+
}
|
|
3661
|
+
if (detach) {
|
|
3662
|
+
logger.log(`
|
|
3663
|
+
Standalone platform is running in the background.`);
|
|
3664
|
+
logger.log(`Run "platform standalone-stop" to stop and clean up.`);
|
|
3665
|
+
return;
|
|
3666
|
+
}
|
|
3667
|
+
logger.log("\nPlatform is running. Press Ctrl+C to stop and clean up.");
|
|
3668
|
+
const keepAlive = setInterval(() => {
|
|
3669
|
+
}, 60 * 6e4);
|
|
3670
|
+
const shutdown = () => {
|
|
3671
|
+
clearInterval(keepAlive);
|
|
3672
|
+
void (async () => {
|
|
3673
|
+
logger.log("\nShutting down standalone platform...");
|
|
3674
|
+
await destroyEnvironment(layout, composeManifest, logger);
|
|
3675
|
+
await cleanupTmpDir(tmpDir, logger);
|
|
3676
|
+
})();
|
|
3677
|
+
};
|
|
3678
|
+
process.once("SIGINT", shutdown);
|
|
3679
|
+
process.once("SIGTERM", shutdown);
|
|
3680
|
+
}
|
|
3681
|
+
async function stopStandalone(logger) {
|
|
3682
|
+
const runningDir = await findRunningSingleton();
|
|
3683
|
+
if (!runningDir) {
|
|
3684
|
+
logger.log("No detached standalone platform found. Nothing to stop.");
|
|
3685
|
+
return;
|
|
3686
|
+
}
|
|
3687
|
+
let coreDirName = null;
|
|
3688
|
+
try {
|
|
3689
|
+
const entries = await readdir7(runningDir, { withFileTypes: true });
|
|
3690
|
+
for (const entry of entries) {
|
|
3691
|
+
if (entry.isDirectory() && entry.name.endsWith("-core")) {
|
|
3692
|
+
try {
|
|
3693
|
+
await access10(join31(runningDir, entry.name, "product.manifest.json"));
|
|
3694
|
+
coreDirName = entry.name;
|
|
3695
|
+
break;
|
|
3696
|
+
} catch {
|
|
3697
|
+
}
|
|
3698
|
+
}
|
|
3699
|
+
}
|
|
3700
|
+
} catch {
|
|
3701
|
+
}
|
|
3702
|
+
if (!coreDirName) {
|
|
3703
|
+
logger.log("Error: Could not find platform core in standalone directory. Attempting raw cleanup...");
|
|
3704
|
+
await cleanupTmpDir(runningDir, logger);
|
|
3705
|
+
return;
|
|
3706
|
+
}
|
|
3707
|
+
const layout = buildLayout(runningDir, coreDirName);
|
|
3708
|
+
let manifest;
|
|
3709
|
+
try {
|
|
3710
|
+
manifest = await readManifest(runningDir, coreDirName);
|
|
3711
|
+
} catch {
|
|
3712
|
+
logger.log("Error: Could not read manifest. Attempting raw cleanup...");
|
|
3713
|
+
const platformName = coreDirName.replace("-core", "");
|
|
3714
|
+
await destroyEnvironment(
|
|
3715
|
+
layout,
|
|
3716
|
+
{ version: "1", product: { name: platformName, displayName: "", organization: "", scope: "" }, applications: [] },
|
|
3717
|
+
logger
|
|
3718
|
+
);
|
|
3719
|
+
await cleanupTmpDir(runningDir, logger);
|
|
3720
|
+
return;
|
|
3721
|
+
}
|
|
3722
|
+
logger.log("Stopping standalone platform...");
|
|
3723
|
+
await destroyEnvironment(layout, manifest, logger);
|
|
3724
|
+
await cleanupTmpDir(runningDir, logger);
|
|
3725
|
+
logger.log("Standalone platform stopped and cleaned up.");
|
|
3726
|
+
}
|
|
3727
|
+
|
|
3728
|
+
// src/controllers/ui/standalone.ui-controller.ts
|
|
3729
|
+
function getGitEmail() {
|
|
3730
|
+
return new Promise((resolve9) => {
|
|
3731
|
+
exec("git config user.email", (err, stdout) => {
|
|
3732
|
+
resolve9(err ? "" : stdout.trim());
|
|
3733
|
+
});
|
|
3734
|
+
});
|
|
3735
|
+
}
|
|
3736
|
+
async function collectAdminUsers(ctx) {
|
|
3737
|
+
ctx.log("\nAdd platform admin users (users who can access the admin UI).");
|
|
3738
|
+
const defaultEmail = await getGitEmail();
|
|
3739
|
+
const adminUsers = [];
|
|
3740
|
+
let addMore = true;
|
|
3741
|
+
while (addMore) {
|
|
3742
|
+
const suggestion = adminUsers.length === 0 ? defaultEmail : "";
|
|
3743
|
+
const username = await ctx.prompt("Admin user (email/username):", suggestion);
|
|
3744
|
+
if (username.trim()) {
|
|
3745
|
+
adminUsers.push(username.trim());
|
|
3746
|
+
}
|
|
3747
|
+
addMore = await ctx.confirm("Add another admin user?", false);
|
|
3748
|
+
}
|
|
3749
|
+
return adminUsers;
|
|
3750
|
+
}
|
|
3751
|
+
async function collectIdpConfig(ctx) {
|
|
3752
|
+
const providers2 = getAllProviders();
|
|
3753
|
+
const providerType = await ctx.select(
|
|
3754
|
+
"Select IDP provider:",
|
|
3755
|
+
providers2.map((p) => ({ label: p.displayName, value: p.type }))
|
|
3756
|
+
);
|
|
3757
|
+
const provider = providers2.find((p) => p.type === providerType);
|
|
3758
|
+
const name = await ctx.prompt("Provider name:");
|
|
3759
|
+
const issuer = await ctx.prompt("Issuer URL:");
|
|
3760
|
+
const clientId = await ctx.prompt("Client ID:");
|
|
3761
|
+
const clientSecret = await ctx.prompt("Client Secret:");
|
|
3762
|
+
const extras = {};
|
|
3763
|
+
for (const field of provider.fields) {
|
|
3764
|
+
extras[field.key] = await ctx.prompt(field.prompt);
|
|
3765
|
+
}
|
|
3766
|
+
return { provider: providerType, name, issuer, clientId, clientSecret, extras };
|
|
3767
|
+
}
|
|
3768
|
+
async function standaloneUiController(ctx) {
|
|
3769
|
+
const appDir = cwd7();
|
|
3770
|
+
const layout = await findAppMonorepoLayout(appDir);
|
|
3771
|
+
if (!layout) {
|
|
3772
|
+
ctx.log("Error: Not in an application monorepo. Run this command from the root of an application folder (must have docker-compose.yml and package.json).");
|
|
3773
|
+
return;
|
|
3774
|
+
}
|
|
3775
|
+
const detected = await autoDetectAppIdentity(appDir);
|
|
3776
|
+
const defaults = await getDefaults();
|
|
3777
|
+
let config = null;
|
|
3778
|
+
if (detected) {
|
|
3779
|
+
const pn = detected.platformName;
|
|
3780
|
+
const appName = detected.applicationName;
|
|
3781
|
+
config = await resolveAppConfig(pn, appName);
|
|
3782
|
+
}
|
|
3783
|
+
if (!config) {
|
|
3784
|
+
const legacy = await readLegacyConfig(appDir);
|
|
3785
|
+
if (legacy) {
|
|
3786
|
+
ctx.log("Found legacy standalone.json in this directory. Migrating to centralized config...");
|
|
3787
|
+
const appKey = buildAppKey(legacy.platformName, legacy.applicationName);
|
|
3788
|
+
const defaultsUpdate = {};
|
|
3789
|
+
if (!defaults.platformName) defaultsUpdate.platformName = legacy.platformName;
|
|
3790
|
+
if (!defaults.organizationName) defaultsUpdate.organizationName = legacy.organizationName;
|
|
3791
|
+
if (!defaults.idp && legacy.idp) defaultsUpdate.idp = legacy.idp;
|
|
3792
|
+
if (!defaults.adminUsers && legacy.adminUsers) defaultsUpdate.adminUsers = legacy.adminUsers;
|
|
3793
|
+
if (Object.keys(defaultsUpdate).length > 0) {
|
|
3794
|
+
await saveDefaults(defaultsUpdate);
|
|
3795
|
+
}
|
|
3796
|
+
await saveAppConfig(appKey, {
|
|
3797
|
+
applicationName: legacy.applicationName,
|
|
3798
|
+
applicationDisplayName: legacy.applicationDisplayName,
|
|
3799
|
+
// Save IDP/admins as app override only if different from what we put in defaults
|
|
3800
|
+
...legacy.idp && defaults.idp ? { idp: legacy.idp } : {},
|
|
3801
|
+
...legacy.adminUsers && defaults.adminUsers ? { adminUsers: legacy.adminUsers } : {}
|
|
3802
|
+
});
|
|
3803
|
+
config = await resolveAppConfig(legacy.platformName, legacy.applicationName);
|
|
3804
|
+
ctx.log(`Migrated config to centralized cache (~/.ba-platform/standalone.json).`);
|
|
3805
|
+
const deleteLegacy = await ctx.confirm("Delete the local standalone.json?", true);
|
|
3806
|
+
if (deleteLegacy) {
|
|
3807
|
+
try {
|
|
3808
|
+
await rm3(join32(appDir, "standalone.json"));
|
|
3809
|
+
ctx.log("Deleted standalone.json.");
|
|
3810
|
+
} catch {
|
|
3811
|
+
ctx.log("Warning: Could not delete standalone.json.");
|
|
3812
|
+
}
|
|
3813
|
+
}
|
|
3814
|
+
}
|
|
3815
|
+
}
|
|
3816
|
+
if (!config) {
|
|
3817
|
+
ctx.log("No standalone config found. Let's set it up.\n");
|
|
3818
|
+
const defaultPlatformName = detected?.platformName ?? defaults.platformName ?? "";
|
|
3819
|
+
const platformName = await ctx.prompt("Platform name (e.g. myplatform):", defaultPlatformName);
|
|
3820
|
+
if (!/^[a-z0-9-]+$/.test(platformName)) {
|
|
3821
|
+
ctx.log("Error: Platform name must use only lowercase letters, numbers, and hyphens.");
|
|
3822
|
+
return;
|
|
3823
|
+
}
|
|
3824
|
+
const defaultOrgName = defaults.organizationName ?? "";
|
|
3825
|
+
const organizationName = await ctx.prompt("Organization name (npm scope, e.g. myorg):", defaultOrgName);
|
|
3826
|
+
if (!/^[a-z0-9-]+$/.test(organizationName)) {
|
|
3827
|
+
ctx.log("Error: Organization name must use only lowercase letters, numbers, and hyphens.");
|
|
3828
|
+
return;
|
|
3829
|
+
}
|
|
3830
|
+
const defaultAppName = detected?.applicationName ?? "";
|
|
3831
|
+
const applicationName = await ctx.prompt("Application name:", defaultAppName);
|
|
3832
|
+
if (!/^[a-z0-9-]+$/.test(applicationName)) {
|
|
3833
|
+
ctx.log("Error: Application name must use only lowercase letters, numbers, and hyphens.");
|
|
3834
|
+
return;
|
|
3835
|
+
}
|
|
3836
|
+
const defaultDisplayName = detected?.applicationDisplayName ?? applicationName;
|
|
3837
|
+
const applicationDisplayName = await ctx.prompt("Application display name:", defaultDisplayName);
|
|
3838
|
+
let idp;
|
|
3839
|
+
let idpIsDefault = false;
|
|
3840
|
+
if (defaults.idp) {
|
|
3841
|
+
const useDefaultIdp = await ctx.confirm(`Use default IDP (${defaults.idp.provider}: ${defaults.idp.name})?`, true);
|
|
3842
|
+
if (useDefaultIdp) {
|
|
3843
|
+
idp = void 0;
|
|
3844
|
+
idpIsDefault = true;
|
|
3845
|
+
} else {
|
|
3846
|
+
ctx.log("\nConfigure a new IDP for this application.");
|
|
3847
|
+
idp = await collectIdpConfig(ctx);
|
|
3848
|
+
}
|
|
3849
|
+
} else {
|
|
3850
|
+
ctx.log("\nAn Identity Provider (IDP) is required to log into the platform.");
|
|
3851
|
+
idp = await collectIdpConfig(ctx);
|
|
3852
|
+
}
|
|
3853
|
+
let adminUsers;
|
|
3854
|
+
let adminsAreDefault = false;
|
|
3855
|
+
if (defaults.adminUsers && defaults.adminUsers.length > 0) {
|
|
3856
|
+
const useDefaultAdmins = await ctx.confirm(`Use default admin users (${defaults.adminUsers.join(", ")})?`, true);
|
|
3857
|
+
if (useDefaultAdmins) {
|
|
3858
|
+
adminUsers = void 0;
|
|
3859
|
+
adminsAreDefault = true;
|
|
3860
|
+
} else {
|
|
3861
|
+
adminUsers = await collectAdminUsers(ctx);
|
|
3862
|
+
}
|
|
3863
|
+
} else {
|
|
3864
|
+
adminUsers = await collectAdminUsers(ctx);
|
|
3865
|
+
}
|
|
3866
|
+
const defaultsUpdate = {};
|
|
3867
|
+
if (!defaults.platformName) defaultsUpdate.platformName = platformName;
|
|
3868
|
+
if (!defaults.organizationName) defaultsUpdate.organizationName = organizationName;
|
|
3869
|
+
if (!defaults.idp && idp && !idpIsDefault) defaultsUpdate.idp = idp;
|
|
3870
|
+
if (!defaults.adminUsers && adminUsers && !adminsAreDefault) defaultsUpdate.adminUsers = adminUsers;
|
|
3871
|
+
if (Object.keys(defaultsUpdate).length > 0) {
|
|
3872
|
+
await saveDefaults(defaultsUpdate);
|
|
3873
|
+
}
|
|
3874
|
+
const appKey = buildAppKey(platformName, applicationName);
|
|
3875
|
+
await saveAppConfig(appKey, {
|
|
3876
|
+
applicationName,
|
|
3877
|
+
applicationDisplayName,
|
|
3878
|
+
// Only store overrides if different from defaults
|
|
3879
|
+
...platformName !== defaults.platformName && defaults.platformName ? { platformName } : {},
|
|
3880
|
+
...organizationName !== defaults.organizationName && defaults.organizationName ? { organizationName } : {},
|
|
3881
|
+
...idp && !idpIsDefault ? { idp } : {},
|
|
3882
|
+
...adminUsers && !adminsAreDefault ? { adminUsers } : {}
|
|
3883
|
+
});
|
|
3884
|
+
config = await resolveAppConfig(platformName, applicationName);
|
|
3885
|
+
ctx.log("Saved standalone config.");
|
|
3886
|
+
}
|
|
3887
|
+
if (config && !config.idp) {
|
|
3888
|
+
ctx.log("IDP configuration is required but not found.");
|
|
3889
|
+
const idp = await collectIdpConfig(ctx);
|
|
3890
|
+
if (!defaults.idp) {
|
|
3891
|
+
await saveDefaults({ idp });
|
|
3892
|
+
} else {
|
|
3893
|
+
const appKey = buildAppKey(config.platformName, config.applicationName);
|
|
3894
|
+
const cache = await readCacheFile();
|
|
3895
|
+
const existing = cache.apps[appKey];
|
|
3896
|
+
if (existing) {
|
|
3897
|
+
await saveAppConfig(appKey, { ...existing, idp });
|
|
3898
|
+
}
|
|
3899
|
+
}
|
|
3900
|
+
config = await resolveAppConfig(config.platformName, config.applicationName);
|
|
3901
|
+
ctx.log("Saved IDP configuration.");
|
|
3902
|
+
}
|
|
3903
|
+
if (config && (!config.adminUsers || config.adminUsers.length === 0)) {
|
|
3904
|
+
ctx.log("No admin users configured.");
|
|
3905
|
+
const adminUsers = await collectAdminUsers(ctx);
|
|
3906
|
+
if (!defaults.adminUsers || defaults.adminUsers.length === 0) {
|
|
3907
|
+
await saveDefaults({ adminUsers });
|
|
3908
|
+
} else {
|
|
3909
|
+
const appKey = buildAppKey(config.platformName, config.applicationName);
|
|
3910
|
+
const cache = await readCacheFile();
|
|
3911
|
+
const existing = cache.apps[appKey];
|
|
3912
|
+
if (existing) {
|
|
3913
|
+
await saveAppConfig(appKey, { ...existing, adminUsers });
|
|
3914
|
+
}
|
|
3915
|
+
}
|
|
3916
|
+
config = await resolveAppConfig(config.platformName, config.applicationName);
|
|
3917
|
+
ctx.log("Saved admin users.");
|
|
3918
|
+
}
|
|
3919
|
+
if (!config) {
|
|
3920
|
+
ctx.log("Error: Could not resolve standalone config.");
|
|
3921
|
+
return;
|
|
3922
|
+
}
|
|
3923
|
+
const detach = await ctx.confirm("Run in detached mode (background)?", false);
|
|
3924
|
+
await startStandalone(config, appDir, ctx, ctx.signal, detach);
|
|
3925
|
+
}
|
|
3926
|
+
async function standaloneStopUiController(ctx) {
|
|
3927
|
+
await stopStandalone(ctx);
|
|
3928
|
+
}
|
|
3929
|
+
|
|
3930
|
+
// src/controllers/ui/standalone-config.ui-controller.ts
|
|
3931
|
+
import { cwd as cwd8 } from "process";
|
|
3932
|
+
import { exec as exec2 } from "child_process";
|
|
3933
|
+
async function standaloneConfigShowUiController(ctx) {
|
|
3934
|
+
const detected = await autoDetectAppIdentity(cwd8());
|
|
3935
|
+
if (detected) {
|
|
3936
|
+
const config = await resolveAppConfig(detected.platformName, detected.applicationName);
|
|
3937
|
+
if (config) {
|
|
3938
|
+
ctx.log(`Standalone config for ${detected.platformName}/${detected.applicationName}:
|
|
3939
|
+
`);
|
|
3940
|
+
ctx.log(JSON.stringify(config, null, 2));
|
|
3941
|
+
} else {
|
|
3942
|
+
ctx.log(`No config found for ${detected.platformName}/${detected.applicationName}.`);
|
|
3943
|
+
ctx.log(`Run 'platform standalone' or 'platform standalone-config set' to configure.`);
|
|
3944
|
+
}
|
|
3945
|
+
} else {
|
|
3946
|
+
await showAll(ctx);
|
|
3947
|
+
}
|
|
3948
|
+
}
|
|
3949
|
+
async function showAll(ctx) {
|
|
3950
|
+
const cache = await readCacheFile();
|
|
3951
|
+
const defaults = cache.defaults;
|
|
3952
|
+
const apps = await listAppConfigs();
|
|
3953
|
+
ctx.log(`Config file: ${getCachePath()}
|
|
3954
|
+
`);
|
|
3955
|
+
ctx.log("--- Defaults ---");
|
|
3956
|
+
if (Object.keys(defaults).length === 0) {
|
|
3957
|
+
ctx.log("(none)");
|
|
3958
|
+
} else {
|
|
3959
|
+
ctx.log(JSON.stringify(defaults, null, 2));
|
|
3960
|
+
}
|
|
3961
|
+
ctx.log("\n--- Apps ---");
|
|
3962
|
+
if (apps.length === 0) {
|
|
3963
|
+
ctx.log("(none)");
|
|
3964
|
+
} else {
|
|
3965
|
+
for (const { key, resolved } of apps) {
|
|
3966
|
+
ctx.log(`
|
|
3967
|
+
${key}:`);
|
|
3968
|
+
ctx.log(` Platform: ${resolved.platformName}`);
|
|
3969
|
+
ctx.log(` Organization: ${resolved.organizationName}`);
|
|
3970
|
+
ctx.log(` Display Name: ${resolved.applicationDisplayName}`);
|
|
3971
|
+
ctx.log(` IDP: ${resolved.idp ? `${resolved.idp.provider} (${resolved.idp.name})` : "(not configured)"}`);
|
|
3972
|
+
ctx.log(` Admin Users: ${resolved.adminUsers?.join(", ") ?? "(not configured)"}`);
|
|
3973
|
+
}
|
|
3974
|
+
}
|
|
3975
|
+
}
|
|
3976
|
+
async function standaloneConfigSetUiController(ctx) {
|
|
3977
|
+
const scope = await ctx.select("What do you want to edit?", [
|
|
3978
|
+
{ label: "Defaults (shared across all apps)", value: "defaults" },
|
|
3979
|
+
{ label: "App-specific config", value: "app" }
|
|
3980
|
+
]);
|
|
3981
|
+
if (scope === "defaults") {
|
|
3982
|
+
await editDefaults(ctx);
|
|
3983
|
+
} else {
|
|
3984
|
+
await editApp(ctx);
|
|
3985
|
+
}
|
|
3986
|
+
}
|
|
3987
|
+
async function editDefaults(ctx) {
|
|
3988
|
+
const defaults = await getDefaults();
|
|
3989
|
+
const platformName = await ctx.prompt("Platform name:", defaults.platformName ?? "");
|
|
3990
|
+
const organizationName = await ctx.prompt("Organization name:", defaults.organizationName ?? "");
|
|
3991
|
+
const update = {};
|
|
3992
|
+
if (platformName) update.platformName = platformName;
|
|
3993
|
+
if (organizationName) update.organizationName = organizationName;
|
|
3994
|
+
const editIdp = defaults.idp ? await ctx.confirm(`Edit IDP (currently: ${defaults.idp.provider} - ${defaults.idp.name})?`, false) : await ctx.confirm("Configure an IDP?", true);
|
|
3995
|
+
if (editIdp) {
|
|
3996
|
+
update.idp = await collectIdpConfig2(ctx, defaults.idp);
|
|
3997
|
+
}
|
|
3998
|
+
const editAdmins = defaults.adminUsers?.length ? await ctx.confirm(`Edit admin users (currently: ${defaults.adminUsers.join(", ")})?`, false) : await ctx.confirm("Configure admin users?", true);
|
|
3999
|
+
if (editAdmins) {
|
|
4000
|
+
update.adminUsers = await collectAdminUsers2(ctx);
|
|
4001
|
+
}
|
|
4002
|
+
await saveDefaults(update);
|
|
4003
|
+
ctx.log("Defaults saved.");
|
|
4004
|
+
}
|
|
4005
|
+
async function editApp(ctx) {
|
|
4006
|
+
const apps = await listAppConfigs();
|
|
4007
|
+
const detected = await autoDetectAppIdentity(cwd8());
|
|
4008
|
+
let appKey;
|
|
4009
|
+
if (apps.length > 0) {
|
|
4010
|
+
const options = apps.map(({ key }) => ({ label: key, value: key }));
|
|
4011
|
+
if (detected) {
|
|
4012
|
+
const detectedKey = buildAppKey(detected.platformName, detected.applicationName);
|
|
4013
|
+
if (!options.find((o) => o.value === detectedKey)) {
|
|
4014
|
+
options.unshift({ label: `${detectedKey} (new - detected)`, value: detectedKey });
|
|
4015
|
+
}
|
|
4016
|
+
}
|
|
4017
|
+
options.push({ label: "Add new app...", value: "__new__" });
|
|
4018
|
+
appKey = await ctx.select("Select app:", options);
|
|
4019
|
+
} else if (detected) {
|
|
4020
|
+
appKey = buildAppKey(detected.platformName, detected.applicationName);
|
|
4021
|
+
ctx.log(`Detected app: ${appKey}`);
|
|
4022
|
+
} else {
|
|
4023
|
+
appKey = "__new__";
|
|
4024
|
+
}
|
|
4025
|
+
if (appKey === "__new__") {
|
|
4026
|
+
const pn = await ctx.prompt("Platform name:");
|
|
4027
|
+
const an = await ctx.prompt("Application name:");
|
|
4028
|
+
appKey = buildAppKey(pn, an);
|
|
4029
|
+
}
|
|
4030
|
+
const cache = await readCacheFile();
|
|
4031
|
+
const existing = cache.apps[appKey];
|
|
4032
|
+
const applicationName = await ctx.prompt("Application name:", existing?.applicationName ?? appKey.split("/")[1] ?? "");
|
|
4033
|
+
const applicationDisplayName = await ctx.prompt("Application display name:", existing?.applicationDisplayName ?? applicationName);
|
|
4034
|
+
const overrideIdp = await ctx.confirm("Set app-specific IDP (override defaults)?", !!existing?.idp);
|
|
4035
|
+
let idp;
|
|
4036
|
+
if (overrideIdp) {
|
|
4037
|
+
idp = await collectIdpConfig2(ctx, existing?.idp);
|
|
4038
|
+
}
|
|
4039
|
+
const overrideAdmins = await ctx.confirm("Set app-specific admin users (override defaults)?", !!existing?.adminUsers);
|
|
4040
|
+
let adminUsers;
|
|
4041
|
+
if (overrideAdmins) {
|
|
4042
|
+
adminUsers = await collectAdminUsers2(ctx);
|
|
4043
|
+
}
|
|
4044
|
+
await saveAppConfig(appKey, {
|
|
4045
|
+
applicationName,
|
|
4046
|
+
applicationDisplayName,
|
|
4047
|
+
...idp ? { idp } : {},
|
|
4048
|
+
...adminUsers ? { adminUsers } : {}
|
|
4049
|
+
});
|
|
4050
|
+
ctx.log(`App config for "${appKey}" saved.`);
|
|
4051
|
+
}
|
|
4052
|
+
async function standaloneConfigDeleteUiController(ctx) {
|
|
4053
|
+
const apps = await listAppConfigs();
|
|
4054
|
+
if (apps.length === 0) {
|
|
4055
|
+
ctx.log("No app configs to delete.");
|
|
4056
|
+
return;
|
|
4057
|
+
}
|
|
4058
|
+
const appKey = await ctx.select(
|
|
4059
|
+
"Select app config to delete:",
|
|
4060
|
+
apps.map(({ key }) => ({ label: key, value: key }))
|
|
4061
|
+
);
|
|
4062
|
+
const confirmed = await ctx.confirm(`Delete config for "${appKey}"?`, false);
|
|
4063
|
+
if (!confirmed) {
|
|
4064
|
+
ctx.log("Cancelled.");
|
|
4065
|
+
return;
|
|
4066
|
+
}
|
|
4067
|
+
const deleted = await deleteAppConfig(appKey);
|
|
4068
|
+
ctx.log(deleted ? `Deleted config for "${appKey}".` : `Config for "${appKey}" not found.`);
|
|
4069
|
+
}
|
|
4070
|
+
async function standaloneConfigListUiController(ctx) {
|
|
4071
|
+
await showAll(ctx);
|
|
4072
|
+
}
|
|
4073
|
+
async function collectIdpConfig2(ctx, current) {
|
|
4074
|
+
const providers2 = getAllProviders();
|
|
4075
|
+
const providerType = await ctx.select(
|
|
4076
|
+
"Select IDP provider:",
|
|
4077
|
+
providers2.map((p) => ({ label: p.displayName, value: p.type }))
|
|
4078
|
+
);
|
|
4079
|
+
const provider = providers2.find((p) => p.type === providerType);
|
|
4080
|
+
const name = await ctx.prompt("Provider name:", current?.name ?? "");
|
|
4081
|
+
const issuer = await ctx.prompt("Issuer URL:", current?.issuer ?? "");
|
|
4082
|
+
const clientId = await ctx.prompt("Client ID:", current?.clientId ?? "");
|
|
4083
|
+
const clientSecret = await ctx.prompt("Client Secret:", current?.clientSecret ?? "");
|
|
4084
|
+
const extras = {};
|
|
4085
|
+
for (const field of provider.fields) {
|
|
4086
|
+
extras[field.key] = await ctx.prompt(field.prompt, current?.extras?.[field.key] ?? "");
|
|
4087
|
+
}
|
|
4088
|
+
return { provider: providerType, name, issuer, clientId, clientSecret, extras };
|
|
4089
|
+
}
|
|
4090
|
+
function getGitEmail2() {
|
|
4091
|
+
return new Promise((resolve9) => {
|
|
4092
|
+
exec2("git config user.email", (err, stdout) => {
|
|
4093
|
+
resolve9(err ? "" : stdout.trim());
|
|
4094
|
+
});
|
|
4095
|
+
});
|
|
4096
|
+
}
|
|
4097
|
+
async function collectAdminUsers2(ctx) {
|
|
4098
|
+
const defaultEmail = await getGitEmail2();
|
|
4099
|
+
const adminUsers = [];
|
|
4100
|
+
let addMore = true;
|
|
4101
|
+
while (addMore) {
|
|
4102
|
+
const suggestion = adminUsers.length === 0 ? defaultEmail : "";
|
|
4103
|
+
const username = await ctx.prompt("Admin user (email/username):", suggestion);
|
|
4104
|
+
if (username.trim()) {
|
|
4105
|
+
adminUsers.push(username.trim());
|
|
4106
|
+
}
|
|
4107
|
+
addMore = await ctx.confirm("Add another admin user?", false);
|
|
4108
|
+
}
|
|
4109
|
+
return adminUsers;
|
|
4110
|
+
}
|
|
4111
|
+
|
|
3057
4112
|
// src/controllers/ui/registry.ts
|
|
3058
4113
|
var uiControllers = /* @__PURE__ */ new Map([
|
|
3059
4114
|
[CREATE_APPLICATION_COMMAND_NAME, createApplicationUiController],
|
|
@@ -3068,7 +4123,13 @@ var uiControllers = /* @__PURE__ */ new Map([
|
|
|
3068
4123
|
[STOP_COMMAND_NAME, createLocalScriptUiController(STOP_COMMAND_NAME)],
|
|
3069
4124
|
[DESTROY_COMMAND_NAME, createLocalScriptUiController(DESTROY_COMMAND_NAME)],
|
|
3070
4125
|
[MANAGE_PLATFORM_ADMINS_COMMAND_NAME, managePlatformAdminsUiController],
|
|
3071
|
-
[INSTALL_AI_PLUGIN_COMMAND_NAME, installAiPluginUiController]
|
|
4126
|
+
[INSTALL_AI_PLUGIN_COMMAND_NAME, installAiPluginUiController],
|
|
4127
|
+
[STANDALONE_COMMAND_NAME, standaloneUiController],
|
|
4128
|
+
[STANDALONE_STOP_COMMAND_NAME, standaloneStopUiController],
|
|
4129
|
+
[STANDALONE_CONFIG_SHOW_COMMAND_NAME, standaloneConfigShowUiController],
|
|
4130
|
+
[STANDALONE_CONFIG_SET_COMMAND_NAME, standaloneConfigSetUiController],
|
|
4131
|
+
[STANDALONE_CONFIG_DELETE_COMMAND_NAME, standaloneConfigDeleteUiController],
|
|
4132
|
+
[STANDALONE_CONFIG_LIST_COMMAND_NAME, standaloneConfigListUiController]
|
|
3072
4133
|
]);
|
|
3073
4134
|
|
|
3074
4135
|
// src/hooks/use-command-runner.ts
|
|
@@ -3102,7 +4163,7 @@ function useCommandRunner({ appendStaticItem, setState, onCommandComplete }) {
|
|
|
3102
4163
|
}
|
|
3103
4164
|
},
|
|
3104
4165
|
prompt(message, defaultValue) {
|
|
3105
|
-
return new Promise((
|
|
4166
|
+
return new Promise((resolve9, reject) => {
|
|
3106
4167
|
if (controller.signal.aborted) {
|
|
3107
4168
|
reject(new DOMException("Aborted", "AbortError"));
|
|
3108
4169
|
return;
|
|
@@ -3110,7 +4171,7 @@ function useCommandRunner({ appendStaticItem, setState, onCommandComplete }) {
|
|
|
3110
4171
|
setPromptMessage(message);
|
|
3111
4172
|
setPromptValue(defaultValue ?? "");
|
|
3112
4173
|
setPromptMode({ kind: "text" });
|
|
3113
|
-
promptResolveRef.current =
|
|
4174
|
+
promptResolveRef.current = resolve9;
|
|
3114
4175
|
setState(APP_STATE.PROMPTING);
|
|
3115
4176
|
controller.signal.addEventListener(
|
|
3116
4177
|
"abort",
|
|
@@ -3120,7 +4181,7 @@ function useCommandRunner({ appendStaticItem, setState, onCommandComplete }) {
|
|
|
3120
4181
|
});
|
|
3121
4182
|
},
|
|
3122
4183
|
select(message, options) {
|
|
3123
|
-
return new Promise((
|
|
4184
|
+
return new Promise((resolve9, reject) => {
|
|
3124
4185
|
if (controller.signal.aborted) {
|
|
3125
4186
|
reject(new DOMException("Aborted", "AbortError"));
|
|
3126
4187
|
return;
|
|
@@ -3128,7 +4189,7 @@ function useCommandRunner({ appendStaticItem, setState, onCommandComplete }) {
|
|
|
3128
4189
|
setPromptMessage(message);
|
|
3129
4190
|
setPromptMode({ kind: "select", options });
|
|
3130
4191
|
setSelectIndex(0);
|
|
3131
|
-
promptResolveRef.current =
|
|
4192
|
+
promptResolveRef.current = resolve9;
|
|
3132
4193
|
setState(APP_STATE.PROMPTING);
|
|
3133
4194
|
controller.signal.addEventListener(
|
|
3134
4195
|
"abort",
|
|
@@ -3138,7 +4199,7 @@ function useCommandRunner({ appendStaticItem, setState, onCommandComplete }) {
|
|
|
3138
4199
|
});
|
|
3139
4200
|
},
|
|
3140
4201
|
multiselect(message, options) {
|
|
3141
|
-
return new Promise((
|
|
4202
|
+
return new Promise((resolve9, reject) => {
|
|
3142
4203
|
if (controller.signal.aborted) {
|
|
3143
4204
|
reject(new DOMException("Aborted", "AbortError"));
|
|
3144
4205
|
return;
|
|
@@ -3148,7 +4209,7 @@ function useCommandRunner({ appendStaticItem, setState, onCommandComplete }) {
|
|
|
3148
4209
|
setSelectIndex(0);
|
|
3149
4210
|
setMultiselectChecked(new Set(options.map((_, i) => i)));
|
|
3150
4211
|
promptResolveRef.current = (value) => {
|
|
3151
|
-
|
|
4212
|
+
resolve9(value ? value.split(",") : []);
|
|
3152
4213
|
};
|
|
3153
4214
|
setState(APP_STATE.PROMPTING);
|
|
3154
4215
|
controller.signal.addEventListener(
|
|
@@ -3159,7 +4220,7 @@ function useCommandRunner({ appendStaticItem, setState, onCommandComplete }) {
|
|
|
3159
4220
|
});
|
|
3160
4221
|
},
|
|
3161
4222
|
confirm(message, defaultValue) {
|
|
3162
|
-
return new Promise((
|
|
4223
|
+
return new Promise((resolve9, reject) => {
|
|
3163
4224
|
if (controller.signal.aborted) {
|
|
3164
4225
|
reject(new DOMException("Aborted", "AbortError"));
|
|
3165
4226
|
return;
|
|
@@ -3167,7 +4228,7 @@ function useCommandRunner({ appendStaticItem, setState, onCommandComplete }) {
|
|
|
3167
4228
|
setPromptMessage(message);
|
|
3168
4229
|
setPromptMode({ kind: "confirm" });
|
|
3169
4230
|
setConfirmValue(defaultValue ?? true);
|
|
3170
|
-
promptResolveRef.current = (value) =>
|
|
4231
|
+
promptResolveRef.current = (value) => resolve9(value === "true");
|
|
3171
4232
|
setState(APP_STATE.PROMPTING);
|
|
3172
4233
|
controller.signal.addEventListener(
|
|
3173
4234
|
"abort",
|
|
@@ -3201,11 +4262,11 @@ function useCommandRunner({ appendStaticItem, setState, onCommandComplete }) {
|
|
|
3201
4262
|
);
|
|
3202
4263
|
const handlePromptSubmit = useCallback(
|
|
3203
4264
|
(value) => {
|
|
3204
|
-
const
|
|
4265
|
+
const resolve9 = promptResolveRef.current;
|
|
3205
4266
|
promptResolveRef.current = null;
|
|
3206
4267
|
setPromptMode({ kind: "text" });
|
|
3207
4268
|
setState(APP_STATE.EXECUTING);
|
|
3208
|
-
|
|
4269
|
+
resolve9?.(value);
|
|
3209
4270
|
},
|
|
3210
4271
|
[setState]
|
|
3211
4272
|
);
|
|
@@ -3237,9 +4298,12 @@ function App() {
|
|
|
3237
4298
|
const [selectedIndex, setSelectedIndex] = useState3(0);
|
|
3238
4299
|
const [staticItems, setStaticItems] = useState3([{ kind: "banner", id: "banner" }]);
|
|
3239
4300
|
const [platformInitialized, setPlatformInitialized] = useState3(false);
|
|
4301
|
+
const [inAppMonorepo, setInAppMonorepo] = useState3(false);
|
|
3240
4302
|
useEffect2(() => {
|
|
3241
4303
|
isPlatformInitialized().then(setPlatformInitialized).catch(() => {
|
|
3242
4304
|
});
|
|
4305
|
+
isInAppMonorepo().then(setInAppMonorepo).catch(() => {
|
|
4306
|
+
});
|
|
3243
4307
|
}, []);
|
|
3244
4308
|
const appendStaticItem = useCallback2((item) => {
|
|
3245
4309
|
setStaticItems((prev) => [...prev, item]);
|
|
@@ -3247,6 +4311,8 @@ function App() {
|
|
|
3247
4311
|
const handleCommandComplete = useCallback2(() => {
|
|
3248
4312
|
isPlatformInitialized().then(setPlatformInitialized).catch(() => {
|
|
3249
4313
|
});
|
|
4314
|
+
isInAppMonorepo().then(setInAppMonorepo).catch(() => {
|
|
4315
|
+
});
|
|
3250
4316
|
}, []);
|
|
3251
4317
|
const {
|
|
3252
4318
|
runCommand: runCommand2,
|
|
@@ -3264,7 +4330,7 @@ function App() {
|
|
|
3264
4330
|
setMultiselectChecked
|
|
3265
4331
|
} = useCommandRunner({ appendStaticItem, setState, onCommandComplete: handleCommandComplete });
|
|
3266
4332
|
const query = inputValue.startsWith("/") ? inputValue.slice(1) : "";
|
|
3267
|
-
const filteredCommands = registry.searchVisible(query, { platformInitialized });
|
|
4333
|
+
const filteredCommands = registry.searchVisible(query, { platformInitialized, inAppMonorepo });
|
|
3268
4334
|
useInput(
|
|
3269
4335
|
(input, key) => {
|
|
3270
4336
|
if (key.ctrl && input === "c") {
|
|
@@ -3446,32 +4512,39 @@ function App() {
|
|
|
3446
4512
|
|
|
3447
4513
|
// src/controllers/cli/create-application.cli-controller.ts
|
|
3448
4514
|
async function createApplicationCliController(args2) {
|
|
3449
|
-
|
|
3450
|
-
console.error("Error: Cannot create an application \u2014 no platform initialized in this directory.");
|
|
3451
|
-
process.exit(1);
|
|
3452
|
-
}
|
|
4515
|
+
const insidePlatform = await isPlatformInitialized();
|
|
3453
4516
|
const {
|
|
3454
4517
|
applicationName,
|
|
3455
4518
|
applicationDisplayName,
|
|
3456
4519
|
applicationDescription,
|
|
3457
4520
|
hasUserInterface,
|
|
3458
|
-
hasBackendService
|
|
4521
|
+
hasBackendService,
|
|
4522
|
+
platformName,
|
|
4523
|
+
organizationName
|
|
3459
4524
|
} = args2;
|
|
3460
|
-
if (!applicationName
|
|
3461
|
-
console.error("Error: applicationName
|
|
4525
|
+
if (!applicationName) {
|
|
4526
|
+
console.error("Error: applicationName is required.");
|
|
3462
4527
|
process.exit(1);
|
|
3463
4528
|
}
|
|
3464
4529
|
if (!/^[a-z0-9-]+$/.test(applicationName)) {
|
|
3465
4530
|
console.error(`Error: Application name "${applicationName}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
|
|
3466
4531
|
process.exit(1);
|
|
3467
4532
|
}
|
|
4533
|
+
if (!insidePlatform) {
|
|
4534
|
+
if (!platformName || !organizationName) {
|
|
4535
|
+
console.error("Error: platformName and organizationName are required when creating an application outside a platform.");
|
|
4536
|
+
process.exit(1);
|
|
4537
|
+
}
|
|
4538
|
+
}
|
|
3468
4539
|
await createApplicationService(
|
|
3469
4540
|
{
|
|
3470
4541
|
applicationName,
|
|
3471
|
-
applicationDisplayName,
|
|
3472
|
-
applicationDescription,
|
|
3473
|
-
hasUserInterface: hasUserInterface === "yes",
|
|
3474
|
-
hasBackendService: hasBackendService === "yes"
|
|
4542
|
+
applicationDisplayName: applicationDisplayName || applicationName,
|
|
4543
|
+
applicationDescription: applicationDescription || "",
|
|
4544
|
+
hasUserInterface: insidePlatform ? hasUserInterface === "yes" : hasUserInterface !== "no",
|
|
4545
|
+
hasBackendService: insidePlatform ? hasBackendService === "yes" : hasBackendService !== "no",
|
|
4546
|
+
platformName,
|
|
4547
|
+
organizationName
|
|
3475
4548
|
},
|
|
3476
4549
|
{ log: console.log }
|
|
3477
4550
|
);
|
|
@@ -3725,6 +4798,160 @@ async function installAiPluginCliController(args2) {
|
|
|
3725
4798
|
);
|
|
3726
4799
|
}
|
|
3727
4800
|
|
|
4801
|
+
// src/controllers/cli/standalone.cli-controller.ts
|
|
4802
|
+
import { cwd as cwd9 } from "process";
|
|
4803
|
+
async function standaloneCliController(args2) {
|
|
4804
|
+
const logger = { log: console.log };
|
|
4805
|
+
const appDir = cwd9();
|
|
4806
|
+
const layout = await findAppMonorepoLayout(appDir);
|
|
4807
|
+
if (!layout) {
|
|
4808
|
+
console.error("Error: Not in an application monorepo. Run this command from the root of an application folder (must have docker-compose.yml and package.json).");
|
|
4809
|
+
process.exit(1);
|
|
4810
|
+
}
|
|
4811
|
+
const detected = await autoDetectAppIdentity(appDir);
|
|
4812
|
+
if (!detected) {
|
|
4813
|
+
console.error("Error: Could not auto-detect app identity. Run 'platform standalone' interactively or 'platform standalone-config set' to configure.");
|
|
4814
|
+
process.exit(1);
|
|
4815
|
+
}
|
|
4816
|
+
let config = await resolveAppConfig(detected.platformName, detected.applicationName);
|
|
4817
|
+
if (!config) {
|
|
4818
|
+
const defaults = await getDefaults();
|
|
4819
|
+
const appOverride = {
|
|
4820
|
+
applicationName: detected.applicationName,
|
|
4821
|
+
applicationDisplayName: detected.applicationDisplayName
|
|
4822
|
+
};
|
|
4823
|
+
config = mergeConfig(defaults, appOverride);
|
|
4824
|
+
if (!config.platformName) config.platformName = detected.platformName;
|
|
4825
|
+
if (!config.organizationName) config.organizationName = defaults.organizationName ?? "";
|
|
4826
|
+
if (!defaults.platformName) {
|
|
4827
|
+
await saveDefaults({ platformName: detected.platformName });
|
|
4828
|
+
}
|
|
4829
|
+
const appKey = buildAppKey(config.platformName, config.applicationName);
|
|
4830
|
+
await saveAppConfig(appKey, appOverride);
|
|
4831
|
+
logger.log(`Auto-configured standalone for ${appKey} (saved to ~/.ba-platform/standalone.json).`);
|
|
4832
|
+
}
|
|
4833
|
+
if (!config.idp) {
|
|
4834
|
+
console.error("Warning: No IDP configured. Users won't be able to log in. Run 'platform standalone-config set' to add an IDP.");
|
|
4835
|
+
}
|
|
4836
|
+
if (!config.adminUsers || config.adminUsers.length === 0) {
|
|
4837
|
+
console.error("Warning: No admin users configured. Run 'platform standalone-config set' to add admin users.");
|
|
4838
|
+
}
|
|
4839
|
+
let positional = [];
|
|
4840
|
+
try {
|
|
4841
|
+
positional = args2["_positional"] ? JSON.parse(args2["_positional"]) : [];
|
|
4842
|
+
} catch {
|
|
4843
|
+
}
|
|
4844
|
+
const detach = args2["detach"] === "true" || args2["d"] === "true" || positional.includes("--detach") || positional.includes("-d");
|
|
4845
|
+
await startStandalone(config, appDir, logger, void 0, detach);
|
|
4846
|
+
}
|
|
4847
|
+
async function standaloneStopCliController(_args) {
|
|
4848
|
+
const logger = { log: console.log };
|
|
4849
|
+
await stopStandalone(logger);
|
|
4850
|
+
}
|
|
4851
|
+
|
|
4852
|
+
// src/controllers/cli/standalone-config.cli-controller.ts
|
|
4853
|
+
import { cwd as cwd10 } from "process";
|
|
4854
|
+
async function standaloneConfigShowCliController(args2) {
|
|
4855
|
+
const appArg = args2["app"];
|
|
4856
|
+
if (appArg) {
|
|
4857
|
+
const parts = appArg.split("/");
|
|
4858
|
+
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
4859
|
+
console.error("Error: --app must be in format platformName/applicationName (exactly one slash).");
|
|
4860
|
+
process.exit(1);
|
|
4861
|
+
}
|
|
4862
|
+
const [pn, an] = parts;
|
|
4863
|
+
const config = await resolveAppConfig(pn, an);
|
|
4864
|
+
if (config) {
|
|
4865
|
+
console.log(JSON.stringify(config, null, 2));
|
|
4866
|
+
} else {
|
|
4867
|
+
console.error(`No config found for ${appArg}.`);
|
|
4868
|
+
process.exit(1);
|
|
4869
|
+
}
|
|
4870
|
+
return;
|
|
4871
|
+
}
|
|
4872
|
+
const detected = await autoDetectAppIdentity(cwd10());
|
|
4873
|
+
if (detected) {
|
|
4874
|
+
const config = await resolveAppConfig(detected.platformName, detected.applicationName);
|
|
4875
|
+
if (config) {
|
|
4876
|
+
console.log(JSON.stringify(config, null, 2));
|
|
4877
|
+
return;
|
|
4878
|
+
}
|
|
4879
|
+
}
|
|
4880
|
+
const cache = await readCacheFile();
|
|
4881
|
+
console.log(JSON.stringify(cache, null, 2));
|
|
4882
|
+
}
|
|
4883
|
+
async function standaloneConfigSetCliController(args2) {
|
|
4884
|
+
if (args2["defaults"] === "true") {
|
|
4885
|
+
const update = {};
|
|
4886
|
+
if (args2["platformName"]) update.platformName = args2["platformName"];
|
|
4887
|
+
if (args2["organizationName"]) update.organizationName = args2["organizationName"];
|
|
4888
|
+
if (Object.keys(update).length === 0) {
|
|
4889
|
+
console.error("Error: No fields provided. Use key=value pairs (e.g. platformName=myplatform organizationName=myorg).");
|
|
4890
|
+
process.exit(1);
|
|
4891
|
+
}
|
|
4892
|
+
await saveDefaults(update);
|
|
4893
|
+
console.log("Defaults updated.");
|
|
4894
|
+
return;
|
|
4895
|
+
}
|
|
4896
|
+
let appKey = args2["app"];
|
|
4897
|
+
if (!appKey) {
|
|
4898
|
+
const detected = await autoDetectAppIdentity(cwd10());
|
|
4899
|
+
if (detected) {
|
|
4900
|
+
appKey = buildAppKey(detected.platformName, detected.applicationName);
|
|
4901
|
+
} else {
|
|
4902
|
+
console.error("Error: Could not detect app. Provide --app=platformName/applicationName.");
|
|
4903
|
+
process.exit(1);
|
|
4904
|
+
}
|
|
4905
|
+
}
|
|
4906
|
+
const cache = await readCacheFile();
|
|
4907
|
+
const existing = cache.apps[appKey] ?? { applicationName: appKey.split("/")[1] ?? "", applicationDisplayName: "" };
|
|
4908
|
+
const applicationName = args2["applicationName"] ?? existing.applicationName;
|
|
4909
|
+
const applicationDisplayName = args2["applicationDisplayName"] ?? existing.applicationDisplayName ?? applicationName;
|
|
4910
|
+
await saveAppConfig(appKey, {
|
|
4911
|
+
...existing,
|
|
4912
|
+
applicationName,
|
|
4913
|
+
applicationDisplayName
|
|
4914
|
+
});
|
|
4915
|
+
console.log(`App config for "${appKey}" updated.`);
|
|
4916
|
+
}
|
|
4917
|
+
async function standaloneConfigDeleteCliController(args2) {
|
|
4918
|
+
const appKey = args2["app"];
|
|
4919
|
+
if (!appKey) {
|
|
4920
|
+
console.error("Error: --app=platformName/applicationName is required.");
|
|
4921
|
+
process.exit(1);
|
|
4922
|
+
}
|
|
4923
|
+
const deleted = await deleteAppConfig(appKey);
|
|
4924
|
+
if (deleted) {
|
|
4925
|
+
console.log(`Deleted config for "${appKey}".`);
|
|
4926
|
+
} else {
|
|
4927
|
+
console.error(`Config for "${appKey}" not found.`);
|
|
4928
|
+
process.exit(1);
|
|
4929
|
+
}
|
|
4930
|
+
}
|
|
4931
|
+
async function standaloneConfigListCliController(_args) {
|
|
4932
|
+
const defaults = await getDefaults();
|
|
4933
|
+
const apps = await listAppConfigs();
|
|
4934
|
+
console.log(`Config file: ${getCachePath()}
|
|
4935
|
+
`);
|
|
4936
|
+
console.log("Defaults:");
|
|
4937
|
+
if (Object.keys(defaults).length === 0) {
|
|
4938
|
+
console.log(" (none)");
|
|
4939
|
+
} else {
|
|
4940
|
+
console.log(` Platform: ${defaults.platformName ?? "(not set)"}`);
|
|
4941
|
+
console.log(` Organization: ${defaults.organizationName ?? "(not set)"}`);
|
|
4942
|
+
console.log(` IDP: ${defaults.idp ? `${defaults.idp.provider} (${defaults.idp.name})` : "(not set)"}`);
|
|
4943
|
+
console.log(` Admin Users: ${defaults.adminUsers?.join(", ") ?? "(not set)"}`);
|
|
4944
|
+
}
|
|
4945
|
+
console.log("\nApps:");
|
|
4946
|
+
if (apps.length === 0) {
|
|
4947
|
+
console.log(" (none)");
|
|
4948
|
+
} else {
|
|
4949
|
+
for (const { key, resolved } of apps) {
|
|
4950
|
+
console.log(` ${key} \u2014 ${resolved.applicationDisplayName} [IDP: ${resolved.idp ? "yes" : "no"}, Admins: ${resolved.adminUsers?.length ?? 0}]`);
|
|
4951
|
+
}
|
|
4952
|
+
}
|
|
4953
|
+
}
|
|
4954
|
+
|
|
3728
4955
|
// src/controllers/cli/registry.ts
|
|
3729
4956
|
var cliControllers = /* @__PURE__ */ new Map([
|
|
3730
4957
|
[CREATE_APPLICATION_COMMAND_NAME, createApplicationCliController],
|
|
@@ -3739,7 +4966,13 @@ var cliControllers = /* @__PURE__ */ new Map([
|
|
|
3739
4966
|
[STOP_COMMAND_NAME, createLocalScriptCliController(STOP_COMMAND_NAME)],
|
|
3740
4967
|
[DESTROY_COMMAND_NAME, createLocalScriptCliController(DESTROY_COMMAND_NAME)],
|
|
3741
4968
|
[MANAGE_PLATFORM_ADMINS_COMMAND_NAME, managePlatformAdminsCliController],
|
|
3742
|
-
[INSTALL_AI_PLUGIN_COMMAND_NAME, installAiPluginCliController]
|
|
4969
|
+
[INSTALL_AI_PLUGIN_COMMAND_NAME, installAiPluginCliController],
|
|
4970
|
+
[STANDALONE_COMMAND_NAME, standaloneCliController],
|
|
4971
|
+
[STANDALONE_STOP_COMMAND_NAME, standaloneStopCliController],
|
|
4972
|
+
[STANDALONE_CONFIG_SHOW_COMMAND_NAME, standaloneConfigShowCliController],
|
|
4973
|
+
[STANDALONE_CONFIG_SET_COMMAND_NAME, standaloneConfigSetCliController],
|
|
4974
|
+
[STANDALONE_CONFIG_DELETE_COMMAND_NAME, standaloneConfigDeleteCliController],
|
|
4975
|
+
[STANDALONE_CONFIG_LIST_COMMAND_NAME, standaloneConfigListCliController]
|
|
3743
4976
|
]);
|
|
3744
4977
|
|
|
3745
4978
|
// src/utils/parse-args.ts
|