genbox 1.0.64 → 1.0.65
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/commands/create.js +14 -294
- package/dist/commands/rebuild.js +19 -253
- package/dist/utils/branch-prompt.js +231 -0
- package/dist/utils/env-parser.js +127 -0
- package/dist/utils/git-utils.js +49 -0
- package/dist/utils/index.js +24 -0
- package/dist/utils/ssh-keys.js +98 -0
- package/package.json +1 -1
package/dist/commands/create.js
CHANGED
|
@@ -43,15 +43,15 @@ const chalk_1 = __importDefault(require("chalk"));
|
|
|
43
43
|
const ora_1 = __importDefault(require("ora"));
|
|
44
44
|
const fs = __importStar(require("fs"));
|
|
45
45
|
const path = __importStar(require("path"));
|
|
46
|
-
const
|
|
46
|
+
const child_process_1 = require("child_process");
|
|
47
47
|
const config_loader_1 = require("../config-loader");
|
|
48
48
|
const profile_resolver_1 = require("../profile-resolver");
|
|
49
49
|
const api_1 = require("../api");
|
|
50
50
|
const ssh_config_1 = require("../ssh-config");
|
|
51
51
|
const schema_v4_1 = require("../schema-v4");
|
|
52
|
-
const child_process_1 = require("child_process");
|
|
53
52
|
const random_name_1 = require("../random-name");
|
|
54
53
|
const db_utils_1 = require("../db-utils");
|
|
54
|
+
const utils_1 = require("../utils");
|
|
55
55
|
// Credits consumed per hour for each size (matches API billing.config.ts)
|
|
56
56
|
const CREDITS_PER_HOUR = {
|
|
57
57
|
cx22: 1,
|
|
@@ -104,55 +104,6 @@ async function provisionGenbox(payload) {
|
|
|
104
104
|
body: JSON.stringify(payload),
|
|
105
105
|
});
|
|
106
106
|
}
|
|
107
|
-
function getPublicSshKey() {
|
|
108
|
-
const home = os.homedir();
|
|
109
|
-
const potentialKeys = [
|
|
110
|
-
path.join(home, '.ssh', 'id_ed25519.pub'),
|
|
111
|
-
path.join(home, '.ssh', 'id_rsa.pub'),
|
|
112
|
-
];
|
|
113
|
-
for (const keyPath of potentialKeys) {
|
|
114
|
-
if (fs.existsSync(keyPath)) {
|
|
115
|
-
const content = fs.readFileSync(keyPath, 'utf-8').trim();
|
|
116
|
-
if (content)
|
|
117
|
-
return content;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
throw new Error('No public SSH key found in ~/.ssh/');
|
|
121
|
-
}
|
|
122
|
-
function getPrivateSshKey() {
|
|
123
|
-
const home = os.homedir();
|
|
124
|
-
const potentialKeys = [
|
|
125
|
-
path.join(home, '.ssh', 'id_ed25519'),
|
|
126
|
-
path.join(home, '.ssh', 'id_rsa'),
|
|
127
|
-
];
|
|
128
|
-
for (const keyPath of potentialKeys) {
|
|
129
|
-
if (fs.existsSync(keyPath)) {
|
|
130
|
-
return fs.readFileSync(keyPath, 'utf-8');
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
return undefined;
|
|
134
|
-
}
|
|
135
|
-
/**
|
|
136
|
-
* Get local git config for commits on genbox
|
|
137
|
-
*/
|
|
138
|
-
function getGitConfig() {
|
|
139
|
-
const { execSync } = require('child_process');
|
|
140
|
-
let userName;
|
|
141
|
-
let userEmail;
|
|
142
|
-
try {
|
|
143
|
-
userName = execSync('git config --global user.name', { encoding: 'utf-8' }).trim();
|
|
144
|
-
}
|
|
145
|
-
catch {
|
|
146
|
-
// Git config not set
|
|
147
|
-
}
|
|
148
|
-
try {
|
|
149
|
-
userEmail = execSync('git config --global user.email', { encoding: 'utf-8' }).trim();
|
|
150
|
-
}
|
|
151
|
-
catch {
|
|
152
|
-
// Git config not set
|
|
153
|
-
}
|
|
154
|
-
return { userName, userEmail };
|
|
155
|
-
}
|
|
156
107
|
/**
|
|
157
108
|
* Prompt user for environment name
|
|
158
109
|
*/
|
|
@@ -343,7 +294,7 @@ exports.createCommand = new commander_1.Command('create')
|
|
|
343
294
|
// Interactive branch selection if no branch options were specified
|
|
344
295
|
// Skip if: -b (existing branch), -f (new branch from source), -n (explicit new branch name), or -y (skip prompts)
|
|
345
296
|
if (!options.branch && !options.fromBranch && !options.newBranch && !options.yes && resolved.repos.length > 0) {
|
|
346
|
-
resolved = await
|
|
297
|
+
resolved = await (0, utils_1.promptForBranchOptionsCreate)(resolved, config, name);
|
|
347
298
|
}
|
|
348
299
|
// Default behavior when -y (non-interactive) and no branch options: create new branch from configured default
|
|
349
300
|
if (!options.branch && !options.fromBranch && !options.newBranch && options.yes && resolved.repos.length > 0) {
|
|
@@ -412,7 +363,7 @@ exports.createCommand = new commander_1.Command('create')
|
|
|
412
363
|
}
|
|
413
364
|
}
|
|
414
365
|
// Get SSH keys
|
|
415
|
-
const publicKey = getPublicSshKey();
|
|
366
|
+
const publicKey = (0, utils_1.getPublicSshKey)();
|
|
416
367
|
// Check if SSH auth is needed for git
|
|
417
368
|
let privateKeyContent;
|
|
418
369
|
const v3Config = config;
|
|
@@ -424,7 +375,7 @@ exports.createCommand = new commander_1.Command('create')
|
|
|
424
375
|
default: true,
|
|
425
376
|
});
|
|
426
377
|
if (injectKey) {
|
|
427
|
-
privateKeyContent = getPrivateSshKey();
|
|
378
|
+
privateKeyContent = (0, utils_1.getPrivateSshKey)();
|
|
428
379
|
if (privateKeyContent) {
|
|
429
380
|
console.log(chalk_1.default.dim(' Using local SSH private key'));
|
|
430
381
|
}
|
|
@@ -432,7 +383,7 @@ exports.createCommand = new commander_1.Command('create')
|
|
|
432
383
|
}
|
|
433
384
|
// Validate git credentials and warn if missing
|
|
434
385
|
const envVarsForValidation = configLoader.loadEnvVars(process.cwd());
|
|
435
|
-
const gitValidation = validateGitCredentials(resolved.repos.map(r => ({ url: r.url, name: r.name })), envVarsForValidation.GIT_TOKEN, privateKeyContent);
|
|
386
|
+
const gitValidation = (0, utils_1.validateGitCredentials)(resolved.repos.map(r => ({ url: r.url, name: r.name })), envVarsForValidation.GIT_TOKEN, privateKeyContent);
|
|
436
387
|
if (gitValidation.warnings.length > 0) {
|
|
437
388
|
console.log('');
|
|
438
389
|
console.log(chalk_1.default.yellow('⚠ Git Authentication Warnings:'));
|
|
@@ -736,225 +687,6 @@ function displayResolvedConfig(resolved) {
|
|
|
736
687
|
console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
|
|
737
688
|
console.log('');
|
|
738
689
|
}
|
|
739
|
-
/**
|
|
740
|
-
* Prompt for branch options interactively
|
|
741
|
-
* Default behavior: create new branch from configured default (or 'main') with branch name = environment name
|
|
742
|
-
*/
|
|
743
|
-
async function promptForBranchOptions(resolved, config, envName) {
|
|
744
|
-
// Get the default/base branch from config (used as source for new branches)
|
|
745
|
-
const baseBranch = config.defaults?.branch || 'main';
|
|
746
|
-
console.log(chalk_1.default.blue('=== Branch Configuration ==='));
|
|
747
|
-
console.log('');
|
|
748
|
-
const branchChoice = await prompts.select({
|
|
749
|
-
message: 'Branch option:',
|
|
750
|
-
choices: [
|
|
751
|
-
{
|
|
752
|
-
name: `${chalk_1.default.green('Create new branch')} '${chalk_1.default.cyan(envName)}' from '${baseBranch}' ${chalk_1.default.dim('(recommended)')}`,
|
|
753
|
-
value: 'new-from-default',
|
|
754
|
-
},
|
|
755
|
-
{
|
|
756
|
-
name: `Create new branch from a different source`,
|
|
757
|
-
value: 'new-custom',
|
|
758
|
-
},
|
|
759
|
-
{
|
|
760
|
-
name: 'Use an existing branch',
|
|
761
|
-
value: 'existing',
|
|
762
|
-
},
|
|
763
|
-
{
|
|
764
|
-
name: `Use '${baseBranch}' directly without creating new branch`,
|
|
765
|
-
value: 'default',
|
|
766
|
-
},
|
|
767
|
-
],
|
|
768
|
-
default: 'new-from-default',
|
|
769
|
-
});
|
|
770
|
-
if (branchChoice === 'new-from-default') {
|
|
771
|
-
// Create new branch from configured default with name = environment name
|
|
772
|
-
return {
|
|
773
|
-
...resolved,
|
|
774
|
-
repos: resolved.repos.map(repo => ({
|
|
775
|
-
...repo,
|
|
776
|
-
branch: envName,
|
|
777
|
-
newBranch: envName,
|
|
778
|
-
sourceBranch: baseBranch,
|
|
779
|
-
})),
|
|
780
|
-
};
|
|
781
|
-
}
|
|
782
|
-
if (branchChoice === 'new-custom') {
|
|
783
|
-
const newBranchName = await prompts.input({
|
|
784
|
-
message: 'New branch name:',
|
|
785
|
-
default: envName,
|
|
786
|
-
validate: (value) => {
|
|
787
|
-
if (!value.trim())
|
|
788
|
-
return 'Branch name is required';
|
|
789
|
-
if (!/^[\w\-./]+$/.test(value))
|
|
790
|
-
return 'Invalid branch name (use letters, numbers, -, _, /, .)';
|
|
791
|
-
return true;
|
|
792
|
-
},
|
|
793
|
-
});
|
|
794
|
-
const sourceBranch = await prompts.input({
|
|
795
|
-
message: 'Create from branch:',
|
|
796
|
-
default: 'main',
|
|
797
|
-
validate: (value) => {
|
|
798
|
-
if (!value.trim())
|
|
799
|
-
return 'Source branch is required';
|
|
800
|
-
return true;
|
|
801
|
-
},
|
|
802
|
-
});
|
|
803
|
-
// Update all repos with new branch info
|
|
804
|
-
return {
|
|
805
|
-
...resolved,
|
|
806
|
-
repos: resolved.repos.map(repo => ({
|
|
807
|
-
...repo,
|
|
808
|
-
branch: newBranchName.trim(),
|
|
809
|
-
newBranch: newBranchName.trim(),
|
|
810
|
-
sourceBranch: sourceBranch.trim(),
|
|
811
|
-
})),
|
|
812
|
-
};
|
|
813
|
-
}
|
|
814
|
-
if (branchChoice === 'existing') {
|
|
815
|
-
const branchName = await prompts.input({
|
|
816
|
-
message: 'Enter branch name:',
|
|
817
|
-
default: baseBranch,
|
|
818
|
-
validate: (value) => {
|
|
819
|
-
if (!value.trim())
|
|
820
|
-
return 'Branch name is required';
|
|
821
|
-
return true;
|
|
822
|
-
},
|
|
823
|
-
});
|
|
824
|
-
// Update all repos with the selected branch
|
|
825
|
-
return {
|
|
826
|
-
...resolved,
|
|
827
|
-
repos: resolved.repos.map(repo => ({
|
|
828
|
-
...repo,
|
|
829
|
-
branch: branchName.trim(),
|
|
830
|
-
newBranch: undefined,
|
|
831
|
-
sourceBranch: undefined,
|
|
832
|
-
})),
|
|
833
|
-
};
|
|
834
|
-
}
|
|
835
|
-
if (branchChoice === 'default') {
|
|
836
|
-
// Keep resolved repos as-is (no new branch)
|
|
837
|
-
return resolved;
|
|
838
|
-
}
|
|
839
|
-
return resolved;
|
|
840
|
-
}
|
|
841
|
-
/**
|
|
842
|
-
* Parse .env.genbox file into segregated sections
|
|
843
|
-
*/
|
|
844
|
-
function parseEnvGenboxSections(content) {
|
|
845
|
-
const sections = new Map();
|
|
846
|
-
let currentSection = 'GLOBAL';
|
|
847
|
-
let currentContent = [];
|
|
848
|
-
for (const line of content.split('\n')) {
|
|
849
|
-
const sectionMatch = line.match(/^# === ([^=]+) ===$/);
|
|
850
|
-
if (sectionMatch) {
|
|
851
|
-
// Save previous section
|
|
852
|
-
if (currentContent.length > 0) {
|
|
853
|
-
sections.set(currentSection, currentContent.join('\n').trim());
|
|
854
|
-
}
|
|
855
|
-
currentSection = sectionMatch[1].trim();
|
|
856
|
-
currentContent = [];
|
|
857
|
-
}
|
|
858
|
-
else if (currentSection !== 'END') {
|
|
859
|
-
currentContent.push(line);
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
// Save last section
|
|
863
|
-
if (currentContent.length > 0 && currentSection !== 'END') {
|
|
864
|
-
sections.set(currentSection, currentContent.join('\n').trim());
|
|
865
|
-
}
|
|
866
|
-
return sections;
|
|
867
|
-
}
|
|
868
|
-
/**
|
|
869
|
-
* Build a map of service URL variables based on connection type
|
|
870
|
-
* e.g., if connectTo=staging: GATEWAY_URL → STAGING_GATEWAY_URL value
|
|
871
|
-
*/
|
|
872
|
-
function buildServiceUrlMap(envVarsFromFile, connectTo) {
|
|
873
|
-
const urlMap = {};
|
|
874
|
-
const prefix = connectTo ? `${connectTo.toUpperCase()}_` : 'LOCAL_';
|
|
875
|
-
// Find all service URL variables (LOCAL_*_URL and STAGING_*_URL patterns)
|
|
876
|
-
const serviceNames = new Set();
|
|
877
|
-
for (const key of Object.keys(envVarsFromFile)) {
|
|
878
|
-
const match = key.match(/^(LOCAL|STAGING|PRODUCTION)_(.+_URL)$/);
|
|
879
|
-
if (match) {
|
|
880
|
-
serviceNames.add(match[2]);
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
// Build mapping: VARNAME → value from appropriate prefix
|
|
884
|
-
for (const serviceName of serviceNames) {
|
|
885
|
-
const prefixedKey = `${prefix}${serviceName}`;
|
|
886
|
-
const localKey = `LOCAL_${serviceName}`;
|
|
887
|
-
// Use prefixed value if available, otherwise fall back to local
|
|
888
|
-
const value = envVarsFromFile[prefixedKey] || envVarsFromFile[localKey];
|
|
889
|
-
if (value) {
|
|
890
|
-
urlMap[serviceName] = value;
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
// Also handle legacy API_URL for backwards compatibility
|
|
894
|
-
if (!urlMap['API_URL']) {
|
|
895
|
-
const apiUrl = envVarsFromFile[`${prefix}API_URL`] ||
|
|
896
|
-
envVarsFromFile['LOCAL_API_URL'] ||
|
|
897
|
-
envVarsFromFile['STAGING_API_URL'];
|
|
898
|
-
if (apiUrl) {
|
|
899
|
-
urlMap['API_URL'] = apiUrl;
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
return urlMap;
|
|
903
|
-
}
|
|
904
|
-
/**
|
|
905
|
-
* Build env content for a specific app by combining GLOBAL + app-specific sections
|
|
906
|
-
*/
|
|
907
|
-
function buildAppEnvContent(sections, appName, serviceUrlMap) {
|
|
908
|
-
const parts = [];
|
|
909
|
-
// Always include GLOBAL section
|
|
910
|
-
const globalSection = sections.get('GLOBAL');
|
|
911
|
-
if (globalSection) {
|
|
912
|
-
parts.push(globalSection);
|
|
913
|
-
}
|
|
914
|
-
// Include app-specific section if exists
|
|
915
|
-
const appSection = sections.get(appName);
|
|
916
|
-
if (appSection) {
|
|
917
|
-
parts.push(appSection);
|
|
918
|
-
}
|
|
919
|
-
let envContent = parts.join('\n\n');
|
|
920
|
-
// Expand all ${VARNAME} references using the service URL map
|
|
921
|
-
for (const [varName, value] of Object.entries(serviceUrlMap)) {
|
|
922
|
-
const pattern = new RegExp(`\\$\\{${varName}\\}`, 'g');
|
|
923
|
-
envContent = envContent.replace(pattern, value);
|
|
924
|
-
}
|
|
925
|
-
// Keep only actual env vars (filter out pure comment lines but keep var definitions)
|
|
926
|
-
envContent = envContent
|
|
927
|
-
.split('\n')
|
|
928
|
-
.filter(line => {
|
|
929
|
-
const trimmed = line.trim();
|
|
930
|
-
// Keep empty lines, lines with = (even if commented), and non-comment lines
|
|
931
|
-
return trimmed === '' || trimmed.includes('=') || !trimmed.startsWith('#');
|
|
932
|
-
})
|
|
933
|
-
.join('\n')
|
|
934
|
-
.replace(/\n{3,}/g, '\n\n')
|
|
935
|
-
.trim();
|
|
936
|
-
return envContent;
|
|
937
|
-
}
|
|
938
|
-
/**
|
|
939
|
-
* Validate git configuration and warn about missing credentials
|
|
940
|
-
*/
|
|
941
|
-
function validateGitCredentials(repos, gitToken, privateKey) {
|
|
942
|
-
const warnings = [];
|
|
943
|
-
const errors = [];
|
|
944
|
-
for (const repo of repos) {
|
|
945
|
-
const isHttps = repo.url.startsWith('https://');
|
|
946
|
-
const isSsh = repo.url.startsWith('git@') || repo.url.includes('ssh://');
|
|
947
|
-
const isPrivateRepo = repo.url.includes('github.com') && !repo.url.includes('/public/');
|
|
948
|
-
if (isHttps && !gitToken && isPrivateRepo) {
|
|
949
|
-
warnings.push(`Repository '${repo.name}' uses HTTPS URL but GIT_TOKEN is not set.`);
|
|
950
|
-
warnings.push(` Add GIT_TOKEN=<your-github-token> to .env.genbox for private repos.`);
|
|
951
|
-
}
|
|
952
|
-
if (isSsh && !privateKey) {
|
|
953
|
-
warnings.push(`Repository '${repo.name}' uses SSH URL but no SSH key was injected.`);
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
return { warnings, errors };
|
|
957
|
-
}
|
|
958
690
|
/**
|
|
959
691
|
* Build API payload from resolved config
|
|
960
692
|
*/
|
|
@@ -977,22 +709,10 @@ function buildPayload(resolved, config, publicKey, privateKey, configLoader) {
|
|
|
977
709
|
if (fs.existsSync(envGenboxPath)) {
|
|
978
710
|
const rawEnvContent = fs.readFileSync(envGenboxPath, 'utf-8');
|
|
979
711
|
// Parse into sections
|
|
980
|
-
const sections = parseEnvGenboxSections(rawEnvContent);
|
|
712
|
+
const sections = (0, utils_1.parseEnvGenboxSections)(rawEnvContent);
|
|
981
713
|
// Parse GLOBAL section to get API URL values
|
|
982
714
|
const globalSection = sections.get('GLOBAL') || '';
|
|
983
|
-
const envVarsFromFile =
|
|
984
|
-
for (const line of globalSection.split('\n')) {
|
|
985
|
-
const match = line.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
|
|
986
|
-
if (match) {
|
|
987
|
-
let value = match[2].trim();
|
|
988
|
-
// Remove quotes if present
|
|
989
|
-
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
990
|
-
(value.startsWith("'") && value.endsWith("'"))) {
|
|
991
|
-
value = value.slice(1, -1);
|
|
992
|
-
}
|
|
993
|
-
envVarsFromFile[match[1]] = value;
|
|
994
|
-
}
|
|
995
|
-
}
|
|
715
|
+
const envVarsFromFile = (0, utils_1.parseEnvVarsFromSection)(globalSection);
|
|
996
716
|
// Determine connection type from profile's connect_to (v3) or default_connection (v4)
|
|
997
717
|
let connectTo;
|
|
998
718
|
if (resolved.profile && config.profiles?.[resolved.profile]) {
|
|
@@ -1002,7 +722,7 @@ function buildPayload(resolved, config, publicKey, privateKey, configLoader) {
|
|
|
1002
722
|
// Build service URL map for variable expansion
|
|
1003
723
|
// This maps GATEWAY_URL → STAGING_GATEWAY_URL value (if connectTo=staging)
|
|
1004
724
|
// or GATEWAY_URL → LOCAL_GATEWAY_URL value (if local)
|
|
1005
|
-
const serviceUrlMap = buildServiceUrlMap(envVarsFromFile, connectTo);
|
|
725
|
+
const serviceUrlMap = (0, utils_1.buildServiceUrlMap)(envVarsFromFile, connectTo);
|
|
1006
726
|
// Log what's being expanded for debugging
|
|
1007
727
|
if (connectTo && Object.keys(serviceUrlMap).length > 0) {
|
|
1008
728
|
console.log(chalk_1.default.dim(` Using ${connectTo} URLs for variable expansion`));
|
|
@@ -1019,7 +739,7 @@ function buildPayload(resolved, config, publicKey, privateKey, configLoader) {
|
|
|
1019
739
|
for (const serviceSectionName of servicesSections) {
|
|
1020
740
|
const serviceName = serviceSectionName.split('/')[1];
|
|
1021
741
|
// Build service-specific env content (GLOBAL + service section)
|
|
1022
|
-
const serviceEnvContent = buildAppEnvContent(sections, serviceSectionName, serviceUrlMap);
|
|
742
|
+
const serviceEnvContent = (0, utils_1.buildAppEnvContent)(sections, serviceSectionName, serviceUrlMap);
|
|
1023
743
|
const stagingName = `${app.name}-${serviceName}.env`;
|
|
1024
744
|
const targetPath = `${repoPath}/apps/${serviceName}/.env`;
|
|
1025
745
|
files.push({
|
|
@@ -1032,7 +752,7 @@ function buildPayload(resolved, config, publicKey, privateKey, configLoader) {
|
|
|
1032
752
|
}
|
|
1033
753
|
else {
|
|
1034
754
|
// Regular app - build app-specific env content (GLOBAL + app section)
|
|
1035
|
-
const appEnvContent = buildAppEnvContent(sections, app.name, serviceUrlMap);
|
|
755
|
+
const appEnvContent = (0, utils_1.buildAppEnvContent)(sections, app.name, serviceUrlMap);
|
|
1036
756
|
files.push({
|
|
1037
757
|
path: `/home/dev/.env-staging/${app.name}.env`,
|
|
1038
758
|
content: appEnvContent,
|
|
@@ -1071,7 +791,7 @@ function buildPayload(resolved, config, publicKey, privateKey, configLoader) {
|
|
|
1071
791
|
};
|
|
1072
792
|
}
|
|
1073
793
|
// Get local git config for commits
|
|
1074
|
-
const gitConfig = getGitConfig();
|
|
794
|
+
const gitConfig = (0, utils_1.getGitConfig)();
|
|
1075
795
|
// Load project cache to get project ID
|
|
1076
796
|
const projectCache = loadProjectCache(process.cwd());
|
|
1077
797
|
return {
|
|
@@ -1211,7 +931,7 @@ async function createLegacy(name, options) {
|
|
|
1211
931
|
const { loadConfig, loadEnvVars } = await Promise.resolve().then(() => __importStar(require('../config')));
|
|
1212
932
|
const config = loadConfig();
|
|
1213
933
|
const size = options.size || config.system?.server_size || 'small';
|
|
1214
|
-
const publicKey = getPublicSshKey();
|
|
934
|
+
const publicKey = (0, utils_1.getPublicSshKey)();
|
|
1215
935
|
// Check if SSH auth needed
|
|
1216
936
|
let privateKeyContent;
|
|
1217
937
|
const usesSSH = config.git_auth?.method === 'ssh' ||
|
|
@@ -1222,7 +942,7 @@ async function createLegacy(name, options) {
|
|
|
1222
942
|
default: true,
|
|
1223
943
|
});
|
|
1224
944
|
if (injectKey) {
|
|
1225
|
-
privateKeyContent = getPrivateSshKey();
|
|
945
|
+
privateKeyContent = (0, utils_1.getPrivateSshKey)();
|
|
1226
946
|
}
|
|
1227
947
|
}
|
|
1228
948
|
const spinner = (0, ora_1.default)(`Creating Genbox '${name}'...`).start();
|
package/dist/commands/rebuild.js
CHANGED
|
@@ -51,22 +51,10 @@ const api_1 = require("../api");
|
|
|
51
51
|
const genbox_selector_1 = require("../genbox-selector");
|
|
52
52
|
const schema_v4_1 = require("../schema-v4");
|
|
53
53
|
const db_utils_1 = require("../db-utils");
|
|
54
|
+
const utils_1 = require("../utils");
|
|
54
55
|
// ============================================================================
|
|
55
56
|
// SSH Utilities for Soft Rebuild
|
|
56
57
|
// ============================================================================
|
|
57
|
-
function getPrivateSshKeyPath() {
|
|
58
|
-
const home = os.homedir();
|
|
59
|
-
const potentialKeys = [
|
|
60
|
-
path.join(home, '.ssh', 'id_ed25519'),
|
|
61
|
-
path.join(home, '.ssh', 'id_rsa'),
|
|
62
|
-
];
|
|
63
|
-
for (const keyPath of potentialKeys) {
|
|
64
|
-
if (fs.existsSync(keyPath)) {
|
|
65
|
-
return keyPath;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
throw new Error('No SSH private key found in ~/.ssh/');
|
|
69
|
-
}
|
|
70
58
|
function sshExec(ip, keyPath, command, timeoutSecs = 30) {
|
|
71
59
|
const sshOpts = `-i ${keyPath} -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -o ConnectTimeout=${timeoutSecs}`;
|
|
72
60
|
try {
|
|
@@ -389,125 +377,12 @@ async function runSoftRebuild(options) {
|
|
|
389
377
|
return { success: false, error: error.message };
|
|
390
378
|
}
|
|
391
379
|
}
|
|
392
|
-
function getPublicSshKey() {
|
|
393
|
-
const home = os.homedir();
|
|
394
|
-
const potentialKeys = [
|
|
395
|
-
path.join(home, '.ssh', 'id_ed25519.pub'),
|
|
396
|
-
path.join(home, '.ssh', 'id_rsa.pub'),
|
|
397
|
-
];
|
|
398
|
-
for (const keyPath of potentialKeys) {
|
|
399
|
-
if (fs.existsSync(keyPath)) {
|
|
400
|
-
const content = fs.readFileSync(keyPath, 'utf-8').trim();
|
|
401
|
-
if (content)
|
|
402
|
-
return content;
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
throw new Error('No public SSH key found in ~/.ssh/');
|
|
406
|
-
}
|
|
407
|
-
function getPrivateSshKey() {
|
|
408
|
-
const home = os.homedir();
|
|
409
|
-
const potentialKeys = [
|
|
410
|
-
path.join(home, '.ssh', 'id_ed25519'),
|
|
411
|
-
path.join(home, '.ssh', 'id_rsa'),
|
|
412
|
-
];
|
|
413
|
-
for (const keyPath of potentialKeys) {
|
|
414
|
-
if (fs.existsSync(keyPath)) {
|
|
415
|
-
return fs.readFileSync(keyPath, 'utf-8');
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
return undefined;
|
|
419
|
-
}
|
|
420
380
|
async function rebuildGenbox(id, payload) {
|
|
421
381
|
return (0, api_1.fetchApi)(`/genboxes/${id}/rebuild`, {
|
|
422
382
|
method: 'POST',
|
|
423
383
|
body: JSON.stringify(payload),
|
|
424
384
|
});
|
|
425
385
|
}
|
|
426
|
-
/**
|
|
427
|
-
* Parse .env.genbox file into segregated sections
|
|
428
|
-
*/
|
|
429
|
-
function parseEnvGenboxSections(content) {
|
|
430
|
-
const sections = new Map();
|
|
431
|
-
let currentSection = 'GLOBAL';
|
|
432
|
-
let currentContent = [];
|
|
433
|
-
for (const line of content.split('\n')) {
|
|
434
|
-
const sectionMatch = line.match(/^# === ([^=]+) ===$/);
|
|
435
|
-
if (sectionMatch) {
|
|
436
|
-
if (currentContent.length > 0) {
|
|
437
|
-
sections.set(currentSection, currentContent.join('\n').trim());
|
|
438
|
-
}
|
|
439
|
-
currentSection = sectionMatch[1].trim();
|
|
440
|
-
currentContent = [];
|
|
441
|
-
}
|
|
442
|
-
else if (currentSection !== 'END') {
|
|
443
|
-
currentContent.push(line);
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
if (currentContent.length > 0 && currentSection !== 'END') {
|
|
447
|
-
sections.set(currentSection, currentContent.join('\n').trim());
|
|
448
|
-
}
|
|
449
|
-
return sections;
|
|
450
|
-
}
|
|
451
|
-
/**
|
|
452
|
-
* Build a map of service URL variables based on connection type
|
|
453
|
-
*/
|
|
454
|
-
function buildServiceUrlMap(envVarsFromFile, connectTo) {
|
|
455
|
-
const urlMap = {};
|
|
456
|
-
const prefix = connectTo ? `${connectTo.toUpperCase()}_` : 'LOCAL_';
|
|
457
|
-
const serviceNames = new Set();
|
|
458
|
-
for (const key of Object.keys(envVarsFromFile)) {
|
|
459
|
-
const match = key.match(/^(LOCAL|STAGING|PRODUCTION)_(.+_URL)$/);
|
|
460
|
-
if (match) {
|
|
461
|
-
serviceNames.add(match[2]);
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
for (const serviceName of serviceNames) {
|
|
465
|
-
const prefixedKey = `${prefix}${serviceName}`;
|
|
466
|
-
const localKey = `LOCAL_${serviceName}`;
|
|
467
|
-
const value = envVarsFromFile[prefixedKey] || envVarsFromFile[localKey];
|
|
468
|
-
if (value) {
|
|
469
|
-
urlMap[serviceName] = value;
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
if (!urlMap['API_URL']) {
|
|
473
|
-
const apiUrl = envVarsFromFile[`${prefix}API_URL`] ||
|
|
474
|
-
envVarsFromFile['LOCAL_API_URL'] ||
|
|
475
|
-
envVarsFromFile['STAGING_API_URL'];
|
|
476
|
-
if (apiUrl) {
|
|
477
|
-
urlMap['API_URL'] = apiUrl;
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
return urlMap;
|
|
481
|
-
}
|
|
482
|
-
/**
|
|
483
|
-
* Build env content for a specific app
|
|
484
|
-
*/
|
|
485
|
-
function buildAppEnvContent(sections, appName, serviceUrlMap) {
|
|
486
|
-
const parts = [];
|
|
487
|
-
const globalSection = sections.get('GLOBAL');
|
|
488
|
-
if (globalSection) {
|
|
489
|
-
parts.push(globalSection);
|
|
490
|
-
}
|
|
491
|
-
const appSection = sections.get(appName);
|
|
492
|
-
if (appSection) {
|
|
493
|
-
parts.push(appSection);
|
|
494
|
-
}
|
|
495
|
-
let envContent = parts.join('\n\n');
|
|
496
|
-
for (const [varName, value] of Object.entries(serviceUrlMap)) {
|
|
497
|
-
const pattern = new RegExp(`\\$\\{${varName}\\}`, 'g');
|
|
498
|
-
envContent = envContent.replace(pattern, value);
|
|
499
|
-
}
|
|
500
|
-
envContent = envContent
|
|
501
|
-
.split('\n')
|
|
502
|
-
.filter(line => {
|
|
503
|
-
const trimmed = line.trim();
|
|
504
|
-
return trimmed === '' || trimmed.includes('=') || !trimmed.startsWith('#');
|
|
505
|
-
})
|
|
506
|
-
.join('\n')
|
|
507
|
-
.replace(/\n{3,}/g, '\n\n')
|
|
508
|
-
.trim();
|
|
509
|
-
return envContent;
|
|
510
|
-
}
|
|
511
386
|
/**
|
|
512
387
|
* Build rebuild payload from resolved config
|
|
513
388
|
*/
|
|
@@ -527,26 +402,15 @@ function buildRebuildPayload(resolved, config, publicKey, privateKey, configLoad
|
|
|
527
402
|
const envGenboxPath = path.join(process.cwd(), '.env.genbox');
|
|
528
403
|
if (fs.existsSync(envGenboxPath)) {
|
|
529
404
|
const rawEnvContent = fs.readFileSync(envGenboxPath, 'utf-8');
|
|
530
|
-
const sections = parseEnvGenboxSections(rawEnvContent);
|
|
405
|
+
const sections = (0, utils_1.parseEnvGenboxSections)(rawEnvContent);
|
|
531
406
|
const globalSection = sections.get('GLOBAL') || '';
|
|
532
|
-
const envVarsFromFile =
|
|
533
|
-
for (const line of globalSection.split('\n')) {
|
|
534
|
-
const match = line.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
|
|
535
|
-
if (match) {
|
|
536
|
-
let value = match[2].trim();
|
|
537
|
-
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
538
|
-
(value.startsWith("'") && value.endsWith("'"))) {
|
|
539
|
-
value = value.slice(1, -1);
|
|
540
|
-
}
|
|
541
|
-
envVarsFromFile[match[1]] = value;
|
|
542
|
-
}
|
|
543
|
-
}
|
|
407
|
+
const envVarsFromFile = (0, utils_1.parseEnvVarsFromSection)(globalSection);
|
|
544
408
|
let connectTo;
|
|
545
409
|
if (resolved.profile && config.profiles?.[resolved.profile]) {
|
|
546
410
|
const profile = config.profiles[resolved.profile];
|
|
547
411
|
connectTo = (0, config_loader_1.getProfileConnection)(profile);
|
|
548
412
|
}
|
|
549
|
-
const serviceUrlMap = buildServiceUrlMap(envVarsFromFile, connectTo);
|
|
413
|
+
const serviceUrlMap = (0, utils_1.buildServiceUrlMap)(envVarsFromFile, connectTo);
|
|
550
414
|
if (connectTo && Object.keys(serviceUrlMap).length > 0) {
|
|
551
415
|
console.log(chalk_1.default.dim(` Using ${connectTo} URLs for variable expansion`));
|
|
552
416
|
}
|
|
@@ -558,7 +422,7 @@ function buildRebuildPayload(resolved, config, publicKey, privateKey, configLoad
|
|
|
558
422
|
if (servicesSections.length > 0) {
|
|
559
423
|
for (const serviceSectionName of servicesSections) {
|
|
560
424
|
const serviceName = serviceSectionName.split('/')[1];
|
|
561
|
-
const serviceEnvContent = buildAppEnvContent(sections, serviceSectionName, serviceUrlMap);
|
|
425
|
+
const serviceEnvContent = (0, utils_1.buildAppEnvContent)(sections, serviceSectionName, serviceUrlMap);
|
|
562
426
|
const stagingName = `${app.name}-${serviceName}.env`;
|
|
563
427
|
const targetPath = `${repoPath}/apps/${serviceName}/.env`;
|
|
564
428
|
files.push({
|
|
@@ -570,7 +434,7 @@ function buildRebuildPayload(resolved, config, publicKey, privateKey, configLoad
|
|
|
570
434
|
}
|
|
571
435
|
}
|
|
572
436
|
else {
|
|
573
|
-
const appEnvContent = buildAppEnvContent(sections, app.name, serviceUrlMap);
|
|
437
|
+
const appEnvContent = (0, utils_1.buildAppEnvContent)(sections, app.name, serviceUrlMap);
|
|
574
438
|
files.push({
|
|
575
439
|
path: `/home/dev/.env-staging/${app.name}.env`,
|
|
576
440
|
content: appEnvContent,
|
|
@@ -600,6 +464,8 @@ function buildRebuildPayload(resolved, config, publicKey, privateKey, configLoad
|
|
|
600
464
|
sourceBranch: repo.sourceBranch,
|
|
601
465
|
};
|
|
602
466
|
}
|
|
467
|
+
// Get local git config for commits
|
|
468
|
+
const gitConfig = (0, utils_1.getGitConfig)();
|
|
603
469
|
return {
|
|
604
470
|
publicKey,
|
|
605
471
|
services,
|
|
@@ -608,110 +474,10 @@ function buildRebuildPayload(resolved, config, publicKey, privateKey, configLoad
|
|
|
608
474
|
repos,
|
|
609
475
|
privateKey,
|
|
610
476
|
gitToken: envVars.GIT_TOKEN,
|
|
477
|
+
gitUserName: gitConfig.userName,
|
|
478
|
+
gitUserEmail: gitConfig.userEmail,
|
|
611
479
|
};
|
|
612
480
|
}
|
|
613
|
-
/**
|
|
614
|
-
* Validate git configuration and warn about missing credentials
|
|
615
|
-
*/
|
|
616
|
-
function validateGitCredentials(repos, gitToken, privateKey) {
|
|
617
|
-
const warnings = [];
|
|
618
|
-
const errors = [];
|
|
619
|
-
for (const repo of repos) {
|
|
620
|
-
const isHttps = repo.url.startsWith('https://');
|
|
621
|
-
const isSsh = repo.url.startsWith('git@') || repo.url.includes('ssh://');
|
|
622
|
-
const isPrivateRepo = repo.url.includes('github.com') && !repo.url.includes('/public/');
|
|
623
|
-
if (isHttps && !gitToken && isPrivateRepo) {
|
|
624
|
-
warnings.push(`Repository '${repo.name}' uses HTTPS URL but GIT_TOKEN is not set.`);
|
|
625
|
-
warnings.push(` Add GIT_TOKEN=<your-github-token> to .env.genbox for private repos.`);
|
|
626
|
-
}
|
|
627
|
-
if (isSsh && !privateKey) {
|
|
628
|
-
warnings.push(`Repository '${repo.name}' uses SSH URL but no SSH key was injected.`);
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
return { warnings, errors };
|
|
632
|
-
}
|
|
633
|
-
/**
|
|
634
|
-
* Prompt for branch options interactively
|
|
635
|
-
*/
|
|
636
|
-
async function promptForBranchOptions(resolved, config) {
|
|
637
|
-
// Get the default branch from config or first repo
|
|
638
|
-
const defaultBranch = config.defaults?.branch || resolved.repos[0]?.branch || 'main';
|
|
639
|
-
console.log(chalk_1.default.blue('=== Branch Configuration ==='));
|
|
640
|
-
console.log(chalk_1.default.dim(`Default branch: ${defaultBranch}`));
|
|
641
|
-
console.log('');
|
|
642
|
-
const branchChoice = await prompts.select({
|
|
643
|
-
message: 'Branch option:',
|
|
644
|
-
choices: [
|
|
645
|
-
{
|
|
646
|
-
name: `Use default branch (${defaultBranch})`,
|
|
647
|
-
value: 'default',
|
|
648
|
-
},
|
|
649
|
-
{
|
|
650
|
-
name: 'Use a different existing branch',
|
|
651
|
-
value: 'existing',
|
|
652
|
-
},
|
|
653
|
-
{
|
|
654
|
-
name: 'Create a new branch',
|
|
655
|
-
value: 'new',
|
|
656
|
-
},
|
|
657
|
-
],
|
|
658
|
-
default: 'default',
|
|
659
|
-
});
|
|
660
|
-
if (branchChoice === 'default') {
|
|
661
|
-
return resolved;
|
|
662
|
-
}
|
|
663
|
-
if (branchChoice === 'existing') {
|
|
664
|
-
const branchName = await prompts.input({
|
|
665
|
-
message: 'Enter branch name:',
|
|
666
|
-
default: defaultBranch,
|
|
667
|
-
validate: (value) => {
|
|
668
|
-
if (!value.trim())
|
|
669
|
-
return 'Branch name is required';
|
|
670
|
-
return true;
|
|
671
|
-
},
|
|
672
|
-
});
|
|
673
|
-
return {
|
|
674
|
-
...resolved,
|
|
675
|
-
repos: resolved.repos.map(repo => ({
|
|
676
|
-
...repo,
|
|
677
|
-
branch: branchName.trim(),
|
|
678
|
-
newBranch: undefined,
|
|
679
|
-
sourceBranch: undefined,
|
|
680
|
-
})),
|
|
681
|
-
};
|
|
682
|
-
}
|
|
683
|
-
if (branchChoice === 'new') {
|
|
684
|
-
const newBranchName = await prompts.input({
|
|
685
|
-
message: 'New branch name:',
|
|
686
|
-
validate: (value) => {
|
|
687
|
-
if (!value.trim())
|
|
688
|
-
return 'Branch name is required';
|
|
689
|
-
if (!/^[\w\-./]+$/.test(value))
|
|
690
|
-
return 'Invalid branch name (use letters, numbers, -, _, /, .)';
|
|
691
|
-
return true;
|
|
692
|
-
},
|
|
693
|
-
});
|
|
694
|
-
const sourceBranch = await prompts.input({
|
|
695
|
-
message: 'Create from branch:',
|
|
696
|
-
default: defaultBranch,
|
|
697
|
-
validate: (value) => {
|
|
698
|
-
if (!value.trim())
|
|
699
|
-
return 'Source branch is required';
|
|
700
|
-
return true;
|
|
701
|
-
},
|
|
702
|
-
});
|
|
703
|
-
return {
|
|
704
|
-
...resolved,
|
|
705
|
-
repos: resolved.repos.map(repo => ({
|
|
706
|
-
...repo,
|
|
707
|
-
branch: newBranchName.trim(),
|
|
708
|
-
newBranch: newBranchName.trim(),
|
|
709
|
-
sourceBranch: sourceBranch.trim(),
|
|
710
|
-
})),
|
|
711
|
-
};
|
|
712
|
-
}
|
|
713
|
-
return resolved;
|
|
714
|
-
}
|
|
715
481
|
exports.rebuildCommand = new commander_1.Command('rebuild')
|
|
716
482
|
.description('Rebuild an existing Genbox environment with updated configuration')
|
|
717
483
|
.argument('[name]', 'Name of the Genbox to rebuild (optional - will prompt if not provided)')
|
|
@@ -822,7 +588,7 @@ exports.rebuildCommand = new commander_1.Command('rebuild')
|
|
|
822
588
|
// Interactive branch selection only if no branch options specified and no stored branch
|
|
823
589
|
// Skip if: -b, -f, -n, stored branch, or -y
|
|
824
590
|
if (!options.branch && !options.fromBranch && !storedBranch && !options.newBranch && !options.yes && resolved.repos.length > 0) {
|
|
825
|
-
resolved = await
|
|
591
|
+
resolved = await (0, utils_1.promptForBranchOptionsRebuild)(resolved, config);
|
|
826
592
|
}
|
|
827
593
|
// Display what will be rebuilt
|
|
828
594
|
console.log(chalk_1.default.bold('Rebuild Configuration:'));
|
|
@@ -882,7 +648,7 @@ exports.rebuildCommand = new commander_1.Command('rebuild')
|
|
|
882
648
|
}
|
|
883
649
|
}
|
|
884
650
|
// Get SSH keys
|
|
885
|
-
const publicKey = getPublicSshKey();
|
|
651
|
+
const publicKey = (0, utils_1.getPublicSshKey)();
|
|
886
652
|
// Check if SSH auth is needed for git
|
|
887
653
|
let privateKeyContent;
|
|
888
654
|
const v3Config = config;
|
|
@@ -894,7 +660,7 @@ exports.rebuildCommand = new commander_1.Command('rebuild')
|
|
|
894
660
|
default: true,
|
|
895
661
|
});
|
|
896
662
|
if (injectKey) {
|
|
897
|
-
privateKeyContent = getPrivateSshKey();
|
|
663
|
+
privateKeyContent = (0, utils_1.getPrivateSshKey)();
|
|
898
664
|
if (privateKeyContent) {
|
|
899
665
|
console.log(chalk_1.default.dim(' Using local SSH private key'));
|
|
900
666
|
}
|
|
@@ -1021,7 +787,7 @@ exports.rebuildCommand = new commander_1.Command('rebuild')
|
|
|
1021
787
|
}
|
|
1022
788
|
// Validate git credentials before rebuilding
|
|
1023
789
|
const envVarsForValidation = configLoader.loadEnvVars(process.cwd());
|
|
1024
|
-
const gitValidation = validateGitCredentials(resolved.repos.map(r => ({ url: r.url, name: r.name })), envVarsForValidation.GIT_TOKEN, privateKeyContent);
|
|
790
|
+
const gitValidation = (0, utils_1.validateGitCredentials)(resolved.repos.map(r => ({ url: r.url, name: r.name })), envVarsForValidation.GIT_TOKEN, privateKeyContent);
|
|
1025
791
|
if (gitValidation.warnings.length > 0) {
|
|
1026
792
|
console.log('');
|
|
1027
793
|
console.log(chalk_1.default.yellow('⚠ Git Authentication Warnings:'));
|
|
@@ -1058,7 +824,7 @@ exports.rebuildCommand = new commander_1.Command('rebuild')
|
|
|
1058
824
|
// Get SSH key path
|
|
1059
825
|
let sshKeyPath;
|
|
1060
826
|
try {
|
|
1061
|
-
sshKeyPath = getPrivateSshKeyPath();
|
|
827
|
+
sshKeyPath = (0, utils_1.getPrivateSshKeyPath)();
|
|
1062
828
|
}
|
|
1063
829
|
catch (error) {
|
|
1064
830
|
console.log(chalk_1.default.red(error.message));
|
|
@@ -1080,7 +846,7 @@ exports.rebuildCommand = new commander_1.Command('rebuild')
|
|
|
1080
846
|
const envGenboxPath = path.join(process.cwd(), '.env.genbox');
|
|
1081
847
|
if (fs.existsSync(envGenboxPath)) {
|
|
1082
848
|
const rawEnvContent = fs.readFileSync(envGenboxPath, 'utf-8');
|
|
1083
|
-
const sections = parseEnvGenboxSections(rawEnvContent);
|
|
849
|
+
const sections = (0, utils_1.parseEnvGenboxSections)(rawEnvContent);
|
|
1084
850
|
const globalSection = sections.get('GLOBAL') || '';
|
|
1085
851
|
const envVarsFromFile = {};
|
|
1086
852
|
for (const line of globalSection.split('\n')) {
|
|
@@ -1099,7 +865,7 @@ exports.rebuildCommand = new commander_1.Command('rebuild')
|
|
|
1099
865
|
const profile = config.profiles[resolved.profile];
|
|
1100
866
|
connectTo = (0, config_loader_1.getProfileConnection)(profile);
|
|
1101
867
|
}
|
|
1102
|
-
const serviceUrlMap = buildServiceUrlMap(envVarsFromFile, connectTo);
|
|
868
|
+
const serviceUrlMap = (0, utils_1.buildServiceUrlMap)(envVarsFromFile, connectTo);
|
|
1103
869
|
for (const app of resolved.apps) {
|
|
1104
870
|
const appConfig = config.apps[app.name];
|
|
1105
871
|
const appPath = appConfig?.path || app.name;
|
|
@@ -1109,7 +875,7 @@ exports.rebuildCommand = new commander_1.Command('rebuild')
|
|
|
1109
875
|
if (servicesSections.length > 0) {
|
|
1110
876
|
for (const serviceSectionName of servicesSections) {
|
|
1111
877
|
const serviceName = serviceSectionName.split('/')[1];
|
|
1112
|
-
const serviceEnvContent = buildAppEnvContent(sections, serviceSectionName, serviceUrlMap);
|
|
878
|
+
const serviceEnvContent = (0, utils_1.buildAppEnvContent)(sections, serviceSectionName, serviceUrlMap);
|
|
1113
879
|
envFilesForSoftRebuild.push({
|
|
1114
880
|
stagingName: `${app.name}-${serviceName}.env`,
|
|
1115
881
|
remotePath: `${repoPath}/apps/${serviceName}/.env`,
|
|
@@ -1118,7 +884,7 @@ exports.rebuildCommand = new commander_1.Command('rebuild')
|
|
|
1118
884
|
}
|
|
1119
885
|
}
|
|
1120
886
|
else {
|
|
1121
|
-
const appEnvContent = buildAppEnvContent(sections, app.name, serviceUrlMap);
|
|
887
|
+
const appEnvContent = (0, utils_1.buildAppEnvContent)(sections, app.name, serviceUrlMap);
|
|
1122
888
|
envFilesForSoftRebuild.push({
|
|
1123
889
|
stagingName: `${app.name}.env`,
|
|
1124
890
|
remotePath: `${repoPath}/.env`,
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Branch Prompt Utilities
|
|
4
|
+
* Shared functions for interactive branch selection
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
40
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
41
|
+
};
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.promptForBranchOptionsCreate = promptForBranchOptionsCreate;
|
|
44
|
+
exports.promptForBranchOptionsRebuild = promptForBranchOptionsRebuild;
|
|
45
|
+
const prompts = __importStar(require("@inquirer/prompts"));
|
|
46
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
47
|
+
/**
|
|
48
|
+
* Prompt for branch options interactively (for create command)
|
|
49
|
+
* Default behavior: create new branch from configured default (or 'main') with branch name = environment name
|
|
50
|
+
*/
|
|
51
|
+
async function promptForBranchOptionsCreate(resolved, config, envName) {
|
|
52
|
+
// Get the default/base branch from config (used as source for new branches)
|
|
53
|
+
const baseBranch = config.defaults?.branch || 'main';
|
|
54
|
+
console.log(chalk_1.default.blue('=== Branch Configuration ==='));
|
|
55
|
+
console.log('');
|
|
56
|
+
const branchChoice = await prompts.select({
|
|
57
|
+
message: 'Branch option:',
|
|
58
|
+
choices: [
|
|
59
|
+
{
|
|
60
|
+
name: `${chalk_1.default.green('Create new branch')} '${chalk_1.default.cyan(envName)}' from '${baseBranch}' ${chalk_1.default.dim('(recommended)')}`,
|
|
61
|
+
value: 'new-from-default',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: `Create new branch from a different source`,
|
|
65
|
+
value: 'new-custom',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'Use an existing branch',
|
|
69
|
+
value: 'existing',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: `Use '${baseBranch}' directly without creating new branch`,
|
|
73
|
+
value: 'default',
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
default: 'new-from-default',
|
|
77
|
+
});
|
|
78
|
+
if (branchChoice === 'new-from-default') {
|
|
79
|
+
// Create new branch from configured default with name = environment name
|
|
80
|
+
return {
|
|
81
|
+
...resolved,
|
|
82
|
+
repos: resolved.repos.map(repo => ({
|
|
83
|
+
...repo,
|
|
84
|
+
branch: envName,
|
|
85
|
+
newBranch: envName,
|
|
86
|
+
sourceBranch: baseBranch,
|
|
87
|
+
})),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
if (branchChoice === 'new-custom') {
|
|
91
|
+
const newBranchName = await prompts.input({
|
|
92
|
+
message: 'New branch name:',
|
|
93
|
+
default: envName,
|
|
94
|
+
validate: (value) => {
|
|
95
|
+
if (!value.trim())
|
|
96
|
+
return 'Branch name is required';
|
|
97
|
+
if (!/^[\w\-./]+$/.test(value))
|
|
98
|
+
return 'Invalid branch name (use letters, numbers, -, _, /, .)';
|
|
99
|
+
return true;
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
const sourceBranch = await prompts.input({
|
|
103
|
+
message: 'Create from branch:',
|
|
104
|
+
default: 'main',
|
|
105
|
+
validate: (value) => {
|
|
106
|
+
if (!value.trim())
|
|
107
|
+
return 'Source branch is required';
|
|
108
|
+
return true;
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
// Update all repos with new branch info
|
|
112
|
+
return {
|
|
113
|
+
...resolved,
|
|
114
|
+
repos: resolved.repos.map(repo => ({
|
|
115
|
+
...repo,
|
|
116
|
+
branch: newBranchName.trim(),
|
|
117
|
+
newBranch: newBranchName.trim(),
|
|
118
|
+
sourceBranch: sourceBranch.trim(),
|
|
119
|
+
})),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
if (branchChoice === 'existing') {
|
|
123
|
+
const branchName = await prompts.input({
|
|
124
|
+
message: 'Enter branch name:',
|
|
125
|
+
default: baseBranch,
|
|
126
|
+
validate: (value) => {
|
|
127
|
+
if (!value.trim())
|
|
128
|
+
return 'Branch name is required';
|
|
129
|
+
return true;
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
// Update all repos with the selected branch
|
|
133
|
+
return {
|
|
134
|
+
...resolved,
|
|
135
|
+
repos: resolved.repos.map(repo => ({
|
|
136
|
+
...repo,
|
|
137
|
+
branch: branchName.trim(),
|
|
138
|
+
newBranch: undefined,
|
|
139
|
+
sourceBranch: undefined,
|
|
140
|
+
})),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
if (branchChoice === 'default') {
|
|
144
|
+
// Keep resolved repos as-is (no new branch)
|
|
145
|
+
return resolved;
|
|
146
|
+
}
|
|
147
|
+
return resolved;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Prompt for branch options interactively (for rebuild command)
|
|
151
|
+
* Simpler version - just switch branches or create new
|
|
152
|
+
*/
|
|
153
|
+
async function promptForBranchOptionsRebuild(resolved, config) {
|
|
154
|
+
// Get the default branch from config or first repo
|
|
155
|
+
const defaultBranch = config.defaults?.branch || resolved.repos[0]?.branch || 'main';
|
|
156
|
+
console.log(chalk_1.default.blue('=== Branch Configuration ==='));
|
|
157
|
+
console.log(chalk_1.default.dim(`Default branch: ${defaultBranch}`));
|
|
158
|
+
console.log('');
|
|
159
|
+
const branchChoice = await prompts.select({
|
|
160
|
+
message: 'Branch option:',
|
|
161
|
+
choices: [
|
|
162
|
+
{
|
|
163
|
+
name: `Use default branch (${defaultBranch})`,
|
|
164
|
+
value: 'default',
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
name: 'Use a different existing branch',
|
|
168
|
+
value: 'existing',
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
name: 'Create a new branch',
|
|
172
|
+
value: 'new',
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
default: 'default',
|
|
176
|
+
});
|
|
177
|
+
if (branchChoice === 'default') {
|
|
178
|
+
return resolved;
|
|
179
|
+
}
|
|
180
|
+
if (branchChoice === 'existing') {
|
|
181
|
+
const branchName = await prompts.input({
|
|
182
|
+
message: 'Enter branch name:',
|
|
183
|
+
default: defaultBranch,
|
|
184
|
+
validate: (value) => {
|
|
185
|
+
if (!value.trim())
|
|
186
|
+
return 'Branch name is required';
|
|
187
|
+
return true;
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
return {
|
|
191
|
+
...resolved,
|
|
192
|
+
repos: resolved.repos.map(repo => ({
|
|
193
|
+
...repo,
|
|
194
|
+
branch: branchName.trim(),
|
|
195
|
+
newBranch: undefined,
|
|
196
|
+
sourceBranch: undefined,
|
|
197
|
+
})),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
if (branchChoice === 'new') {
|
|
201
|
+
const newBranchName = await prompts.input({
|
|
202
|
+
message: 'New branch name:',
|
|
203
|
+
validate: (value) => {
|
|
204
|
+
if (!value.trim())
|
|
205
|
+
return 'Branch name is required';
|
|
206
|
+
if (!/^[\w\-./]+$/.test(value))
|
|
207
|
+
return 'Invalid branch name (use letters, numbers, -, _, /, .)';
|
|
208
|
+
return true;
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
const sourceBranch = await prompts.input({
|
|
212
|
+
message: 'Create from branch:',
|
|
213
|
+
default: defaultBranch,
|
|
214
|
+
validate: (value) => {
|
|
215
|
+
if (!value.trim())
|
|
216
|
+
return 'Source branch is required';
|
|
217
|
+
return true;
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
return {
|
|
221
|
+
...resolved,
|
|
222
|
+
repos: resolved.repos.map(repo => ({
|
|
223
|
+
...repo,
|
|
224
|
+
branch: newBranchName.trim(),
|
|
225
|
+
newBranch: newBranchName.trim(),
|
|
226
|
+
sourceBranch: sourceBranch.trim(),
|
|
227
|
+
})),
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
return resolved;
|
|
231
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Environment File Parser Utilities
|
|
4
|
+
* Shared functions for parsing and processing .env.genbox files
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.parseEnvGenboxSections = parseEnvGenboxSections;
|
|
8
|
+
exports.buildServiceUrlMap = buildServiceUrlMap;
|
|
9
|
+
exports.buildAppEnvContent = buildAppEnvContent;
|
|
10
|
+
exports.parseEnvVarsFromSection = parseEnvVarsFromSection;
|
|
11
|
+
/**
|
|
12
|
+
* Parse .env.genbox file into segregated sections
|
|
13
|
+
* Sections are marked with: # === SECTION_NAME ===
|
|
14
|
+
*/
|
|
15
|
+
function parseEnvGenboxSections(content) {
|
|
16
|
+
const sections = new Map();
|
|
17
|
+
let currentSection = 'GLOBAL';
|
|
18
|
+
let currentContent = [];
|
|
19
|
+
for (const line of content.split('\n')) {
|
|
20
|
+
const sectionMatch = line.match(/^# === ([^=]+) ===$/);
|
|
21
|
+
if (sectionMatch) {
|
|
22
|
+
// Save previous section
|
|
23
|
+
if (currentContent.length > 0) {
|
|
24
|
+
sections.set(currentSection, currentContent.join('\n').trim());
|
|
25
|
+
}
|
|
26
|
+
currentSection = sectionMatch[1].trim();
|
|
27
|
+
currentContent = [];
|
|
28
|
+
}
|
|
29
|
+
else if (currentSection !== 'END') {
|
|
30
|
+
currentContent.push(line);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// Save last section
|
|
34
|
+
if (currentContent.length > 0 && currentSection !== 'END') {
|
|
35
|
+
sections.set(currentSection, currentContent.join('\n').trim());
|
|
36
|
+
}
|
|
37
|
+
return sections;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Build a map of service URL variables based on connection type
|
|
41
|
+
* e.g., if connectTo=staging: GATEWAY_URL → STAGING_GATEWAY_URL value
|
|
42
|
+
*/
|
|
43
|
+
function buildServiceUrlMap(envVarsFromFile, connectTo) {
|
|
44
|
+
const urlMap = {};
|
|
45
|
+
const prefix = connectTo ? `${connectTo.toUpperCase()}_` : 'LOCAL_';
|
|
46
|
+
// Find all service URL variables (LOCAL_*_URL and STAGING_*_URL patterns)
|
|
47
|
+
const serviceNames = new Set();
|
|
48
|
+
for (const key of Object.keys(envVarsFromFile)) {
|
|
49
|
+
const match = key.match(/^(LOCAL|STAGING|PRODUCTION)_(.+_URL)$/);
|
|
50
|
+
if (match) {
|
|
51
|
+
serviceNames.add(match[2]);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Build mapping: VARNAME → value from appropriate prefix
|
|
55
|
+
for (const serviceName of serviceNames) {
|
|
56
|
+
const prefixedKey = `${prefix}${serviceName}`;
|
|
57
|
+
const localKey = `LOCAL_${serviceName}`;
|
|
58
|
+
// Use prefixed value if available, otherwise fall back to local
|
|
59
|
+
const value = envVarsFromFile[prefixedKey] || envVarsFromFile[localKey];
|
|
60
|
+
if (value) {
|
|
61
|
+
urlMap[serviceName] = value;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// Also handle legacy API_URL for backwards compatibility
|
|
65
|
+
if (!urlMap['API_URL']) {
|
|
66
|
+
const apiUrl = envVarsFromFile[`${prefix}API_URL`] ||
|
|
67
|
+
envVarsFromFile['LOCAL_API_URL'] ||
|
|
68
|
+
envVarsFromFile['STAGING_API_URL'];
|
|
69
|
+
if (apiUrl) {
|
|
70
|
+
urlMap['API_URL'] = apiUrl;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return urlMap;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Build env content for a specific app by combining GLOBAL + app-specific sections
|
|
77
|
+
*/
|
|
78
|
+
function buildAppEnvContent(sections, appName, serviceUrlMap) {
|
|
79
|
+
const parts = [];
|
|
80
|
+
// Always include GLOBAL section
|
|
81
|
+
const globalSection = sections.get('GLOBAL');
|
|
82
|
+
if (globalSection) {
|
|
83
|
+
parts.push(globalSection);
|
|
84
|
+
}
|
|
85
|
+
// Include app-specific section if exists
|
|
86
|
+
const appSection = sections.get(appName);
|
|
87
|
+
if (appSection) {
|
|
88
|
+
parts.push(appSection);
|
|
89
|
+
}
|
|
90
|
+
let envContent = parts.join('\n\n');
|
|
91
|
+
// Expand all ${VARNAME} references using the service URL map
|
|
92
|
+
for (const [varName, value] of Object.entries(serviceUrlMap)) {
|
|
93
|
+
const pattern = new RegExp(`\\$\\{${varName}\\}`, 'g');
|
|
94
|
+
envContent = envContent.replace(pattern, value);
|
|
95
|
+
}
|
|
96
|
+
// Keep only actual env vars (filter out pure comment lines but keep var definitions)
|
|
97
|
+
envContent = envContent
|
|
98
|
+
.split('\n')
|
|
99
|
+
.filter(line => {
|
|
100
|
+
const trimmed = line.trim();
|
|
101
|
+
// Keep empty lines, lines with = (even if commented), and non-comment lines
|
|
102
|
+
return trimmed === '' || trimmed.includes('=') || !trimmed.startsWith('#');
|
|
103
|
+
})
|
|
104
|
+
.join('\n')
|
|
105
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
106
|
+
.trim();
|
|
107
|
+
return envContent;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Parse env vars from a section content string
|
|
111
|
+
*/
|
|
112
|
+
function parseEnvVarsFromSection(sectionContent) {
|
|
113
|
+
const envVars = {};
|
|
114
|
+
for (const line of sectionContent.split('\n')) {
|
|
115
|
+
const match = line.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
|
|
116
|
+
if (match) {
|
|
117
|
+
let value = match[2].trim();
|
|
118
|
+
// Remove quotes if present
|
|
119
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
120
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
121
|
+
value = value.slice(1, -1);
|
|
122
|
+
}
|
|
123
|
+
envVars[match[1]] = value;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return envVars;
|
|
127
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Git Utilities
|
|
4
|
+
* Shared functions for git configuration and validation
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.validateGitCredentials = validateGitCredentials;
|
|
8
|
+
exports.getGitConfig = getGitConfig;
|
|
9
|
+
const child_process_1 = require("child_process");
|
|
10
|
+
/**
|
|
11
|
+
* Validate git configuration and warn about missing credentials
|
|
12
|
+
*/
|
|
13
|
+
function validateGitCredentials(repos, gitToken, privateKey) {
|
|
14
|
+
const warnings = [];
|
|
15
|
+
const errors = [];
|
|
16
|
+
for (const repo of repos) {
|
|
17
|
+
const isHttps = repo.url.startsWith('https://');
|
|
18
|
+
const isSsh = repo.url.startsWith('git@') || repo.url.includes('ssh://');
|
|
19
|
+
const isPrivateRepo = repo.url.includes('github.com') && !repo.url.includes('/public/');
|
|
20
|
+
if (isHttps && !gitToken && isPrivateRepo) {
|
|
21
|
+
warnings.push(`Repository '${repo.name}' uses HTTPS URL but GIT_TOKEN is not set.`);
|
|
22
|
+
warnings.push(` Add GIT_TOKEN=<your-github-token> to .env.genbox for private repos.`);
|
|
23
|
+
}
|
|
24
|
+
if (isSsh && !privateKey) {
|
|
25
|
+
warnings.push(`Repository '${repo.name}' uses SSH URL but no SSH key was injected.`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return { warnings, errors };
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Get local git config for commits on genbox
|
|
32
|
+
*/
|
|
33
|
+
function getGitConfig() {
|
|
34
|
+
let userName;
|
|
35
|
+
let userEmail;
|
|
36
|
+
try {
|
|
37
|
+
userName = (0, child_process_1.execSync)('git config --global user.name', { encoding: 'utf-8' }).trim();
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// Git config not set
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
userEmail = (0, child_process_1.execSync)('git config --global user.email', { encoding: 'utf-8' }).trim();
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// Git config not set
|
|
47
|
+
}
|
|
48
|
+
return { userName, userEmail };
|
|
49
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shared Utilities
|
|
4
|
+
* Re-exports all utility modules for convenient importing
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
18
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
19
|
+
};
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
__exportStar(require("./ssh-keys"), exports);
|
|
22
|
+
__exportStar(require("./env-parser"), exports);
|
|
23
|
+
__exportStar(require("./git-utils"), exports);
|
|
24
|
+
__exportStar(require("./branch-prompt"), exports);
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SSH Key Utilities
|
|
4
|
+
* Shared functions for reading SSH keys
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.getPublicSshKey = getPublicSshKey;
|
|
41
|
+
exports.getPrivateSshKey = getPrivateSshKey;
|
|
42
|
+
exports.getPrivateSshKeyPath = getPrivateSshKeyPath;
|
|
43
|
+
const fs = __importStar(require("fs"));
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
const os = __importStar(require("os"));
|
|
46
|
+
/**
|
|
47
|
+
* Get the public SSH key content
|
|
48
|
+
* Tries ed25519 first, then RSA
|
|
49
|
+
*/
|
|
50
|
+
function getPublicSshKey() {
|
|
51
|
+
const home = os.homedir();
|
|
52
|
+
const potentialKeys = [
|
|
53
|
+
path.join(home, '.ssh', 'id_ed25519.pub'),
|
|
54
|
+
path.join(home, '.ssh', 'id_rsa.pub'),
|
|
55
|
+
];
|
|
56
|
+
for (const keyPath of potentialKeys) {
|
|
57
|
+
if (fs.existsSync(keyPath)) {
|
|
58
|
+
const content = fs.readFileSync(keyPath, 'utf-8').trim();
|
|
59
|
+
if (content)
|
|
60
|
+
return content;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
throw new Error('No public SSH key found in ~/.ssh/');
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Get the private SSH key content (for injection into remote server)
|
|
67
|
+
* Returns undefined if no key found
|
|
68
|
+
*/
|
|
69
|
+
function getPrivateSshKey() {
|
|
70
|
+
const home = os.homedir();
|
|
71
|
+
const potentialKeys = [
|
|
72
|
+
path.join(home, '.ssh', 'id_ed25519'),
|
|
73
|
+
path.join(home, '.ssh', 'id_rsa'),
|
|
74
|
+
];
|
|
75
|
+
for (const keyPath of potentialKeys) {
|
|
76
|
+
if (fs.existsSync(keyPath)) {
|
|
77
|
+
return fs.readFileSync(keyPath, 'utf-8');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Get the path to the private SSH key (for local SSH commands)
|
|
84
|
+
* Throws if no key found
|
|
85
|
+
*/
|
|
86
|
+
function getPrivateSshKeyPath() {
|
|
87
|
+
const home = os.homedir();
|
|
88
|
+
const potentialKeys = [
|
|
89
|
+
path.join(home, '.ssh', 'id_ed25519'),
|
|
90
|
+
path.join(home, '.ssh', 'id_rsa'),
|
|
91
|
+
];
|
|
92
|
+
for (const keyPath of potentialKeys) {
|
|
93
|
+
if (fs.existsSync(keyPath)) {
|
|
94
|
+
return keyPath;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
throw new Error('No SSH private key found in ~/.ssh/');
|
|
98
|
+
}
|