genbox 1.0.23 → 1.0.24
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/api.js +7 -0
- package/dist/commands/create.js +122 -4
- package/dist/commands/init.js +0 -9
- package/dist/random-name.js +50 -0
- package/package.json +1 -1
package/dist/api.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.AuthenticationError = void 0;
|
|
|
7
7
|
exports.handleApiError = handleApiError;
|
|
8
8
|
exports.isAuthError = isAuthError;
|
|
9
9
|
exports.fetchApi = fetchApi;
|
|
10
|
+
exports.checkNameAvailability = checkNameAvailability;
|
|
10
11
|
const chalk_1 = __importDefault(require("chalk"));
|
|
11
12
|
const config_store_1 = require("./config-store");
|
|
12
13
|
const API_URL = process.env.GENBOX_API_URL || 'https://api.genbox.dev';
|
|
@@ -74,3 +75,9 @@ async function fetchApi(endpoint, options = {}) {
|
|
|
74
75
|
}
|
|
75
76
|
return response.json();
|
|
76
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Check if a genbox name is available in a workspace
|
|
80
|
+
*/
|
|
81
|
+
async function checkNameAvailability(name, workspace) {
|
|
82
|
+
return fetchApi(`/genboxes/check-name?name=${encodeURIComponent(name)}&workspace=${encodeURIComponent(workspace)}`);
|
|
83
|
+
}
|
package/dist/commands/create.js
CHANGED
|
@@ -50,6 +50,7 @@ const api_1 = require("../api");
|
|
|
50
50
|
const ssh_config_1 = require("../ssh-config");
|
|
51
51
|
const schema_v4_1 = require("../schema-v4");
|
|
52
52
|
const child_process_1 = require("child_process");
|
|
53
|
+
const random_name_1 = require("../random-name");
|
|
53
54
|
/**
|
|
54
55
|
* Spawn a background process to poll for IP and add SSH config
|
|
55
56
|
* This runs detached so the main process can exit immediately
|
|
@@ -99,9 +100,85 @@ function getPrivateSshKey() {
|
|
|
99
100
|
}
|
|
100
101
|
return undefined;
|
|
101
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Prompt user for environment name
|
|
105
|
+
*/
|
|
106
|
+
async function promptForName(skipPrompts) {
|
|
107
|
+
if (skipPrompts) {
|
|
108
|
+
return (0, random_name_1.generateRandomName)();
|
|
109
|
+
}
|
|
110
|
+
const suggestions = (0, random_name_1.generateNameSuggestions)(3);
|
|
111
|
+
const nameChoice = await prompts.select({
|
|
112
|
+
message: 'Environment name:',
|
|
113
|
+
choices: [
|
|
114
|
+
{
|
|
115
|
+
name: `${suggestions[0]} (random)`,
|
|
116
|
+
value: suggestions[0],
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: `${suggestions[1]} (random)`,
|
|
120
|
+
value: suggestions[1],
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: `${suggestions[2]} (random)`,
|
|
124
|
+
value: suggestions[2],
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
name: 'Enter custom name...',
|
|
128
|
+
value: '__custom__',
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
});
|
|
132
|
+
if (nameChoice === '__custom__') {
|
|
133
|
+
const customName = await prompts.input({
|
|
134
|
+
message: 'Enter environment name:',
|
|
135
|
+
validate: (value) => {
|
|
136
|
+
if (!value.trim())
|
|
137
|
+
return 'Name is required';
|
|
138
|
+
if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/.test(value.trim())) {
|
|
139
|
+
return 'Name must be lowercase, start/end with letter or number, and contain only letters, numbers, and hyphens';
|
|
140
|
+
}
|
|
141
|
+
return true;
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
return customName.trim();
|
|
145
|
+
}
|
|
146
|
+
return nameChoice;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Prompt user to select a profile
|
|
150
|
+
*/
|
|
151
|
+
async function promptForProfile(profiles) {
|
|
152
|
+
const profileNames = Object.keys(profiles);
|
|
153
|
+
// Build choices with profile descriptions
|
|
154
|
+
const choices = profileNames.map(name => {
|
|
155
|
+
const profile = profiles[name];
|
|
156
|
+
const apps = profile.apps ? profile.apps.join(', ') : 'default apps';
|
|
157
|
+
const description = profile.description || `Apps: ${apps}`;
|
|
158
|
+
return {
|
|
159
|
+
name: `${name} - ${chalk_1.default.dim(description)}`,
|
|
160
|
+
value: name,
|
|
161
|
+
};
|
|
162
|
+
});
|
|
163
|
+
// Add custom option
|
|
164
|
+
choices.push({
|
|
165
|
+
name: `${chalk_1.default.cyan('Custom')} - ${chalk_1.default.dim('Configure manually')}`,
|
|
166
|
+
value: '__custom__',
|
|
167
|
+
});
|
|
168
|
+
console.log(chalk_1.default.blue('=== Profile Selection ==='));
|
|
169
|
+
console.log('');
|
|
170
|
+
const selected = await prompts.select({
|
|
171
|
+
message: 'Select a profile:',
|
|
172
|
+
choices,
|
|
173
|
+
});
|
|
174
|
+
if (selected === '__custom__') {
|
|
175
|
+
return undefined; // No profile, will use default behavior
|
|
176
|
+
}
|
|
177
|
+
return selected;
|
|
178
|
+
}
|
|
102
179
|
exports.createCommand = new commander_1.Command('create')
|
|
103
180
|
.description('Create a new Genbox environment')
|
|
104
|
-
.argument('
|
|
181
|
+
.argument('[name]', 'Name of the Genbox (optional - will prompt if not provided)')
|
|
105
182
|
.option('-p, --profile <profile>', 'Use a predefined profile')
|
|
106
183
|
.option('-a, --apps <apps>', 'Comma-separated list of apps to include')
|
|
107
184
|
.option('--add-apps <apps>', 'Add apps to the profile')
|
|
@@ -114,7 +191,7 @@ exports.createCommand = new commander_1.Command('create')
|
|
|
114
191
|
.option('-f, --from-branch <branch>', 'Source branch to create new branch from (defaults to current/default branch)')
|
|
115
192
|
.option('-y, --yes', 'Skip interactive prompts')
|
|
116
193
|
.option('--dry-run', 'Show what would be created without actually creating')
|
|
117
|
-
.action(async (
|
|
194
|
+
.action(async (nameArg, options) => {
|
|
118
195
|
try {
|
|
119
196
|
// Load configuration
|
|
120
197
|
const configLoader = new config_loader_1.ConfigLoader();
|
|
@@ -122,12 +199,53 @@ exports.createCommand = new commander_1.Command('create')
|
|
|
122
199
|
const configVersion = (0, schema_v4_1.getConfigVersion)(loadResult.config);
|
|
123
200
|
if (!loadResult.config || configVersion === 'unknown') {
|
|
124
201
|
// Fall back to legacy v1/v2 handling
|
|
202
|
+
const name = nameArg || await promptForName(options.yes);
|
|
125
203
|
await createLegacy(name, options);
|
|
126
204
|
return;
|
|
127
205
|
}
|
|
128
206
|
// Support both v3 and v4 configs
|
|
129
207
|
const config = loadResult.config;
|
|
130
208
|
const profileResolver = new profile_resolver_1.ProfileResolver(configLoader);
|
|
209
|
+
// Interactive name prompt if not provided
|
|
210
|
+
let name = nameArg;
|
|
211
|
+
if (!name && !options.yes) {
|
|
212
|
+
name = await promptForName(false);
|
|
213
|
+
}
|
|
214
|
+
else if (!name && options.yes) {
|
|
215
|
+
// Auto-generate name in non-interactive mode
|
|
216
|
+
name = (0, random_name_1.generateRandomName)();
|
|
217
|
+
console.log(chalk_1.default.dim(` Auto-generated name: ${name}`));
|
|
218
|
+
}
|
|
219
|
+
// Interactive profile selection if profiles exist and no -p option provided
|
|
220
|
+
let selectedProfile = options.profile;
|
|
221
|
+
if (!selectedProfile && !options.yes && config.profiles && Object.keys(config.profiles).length > 0) {
|
|
222
|
+
selectedProfile = await promptForProfile(config.profiles);
|
|
223
|
+
}
|
|
224
|
+
// Check if name is available in workspace, add suffix if taken
|
|
225
|
+
const workspace = config.project?.name || 'default';
|
|
226
|
+
try {
|
|
227
|
+
let { available } = await (0, api_1.checkNameAvailability)(name, workspace);
|
|
228
|
+
if (!available) {
|
|
229
|
+
// Add random suffix and retry
|
|
230
|
+
const originalName = name;
|
|
231
|
+
const suffix = Math.random().toString(36).substring(2, 6);
|
|
232
|
+
name = `${originalName}-${suffix}`;
|
|
233
|
+
// Verify the new name is available
|
|
234
|
+
const recheck = await (0, api_1.checkNameAvailability)(name, workspace);
|
|
235
|
+
if (!recheck.available) {
|
|
236
|
+
// Extremely unlikely, but fall back to timestamp
|
|
237
|
+
name = `${originalName}-${Date.now().toString(36)}`;
|
|
238
|
+
}
|
|
239
|
+
console.log(chalk_1.default.yellow(` Name '${originalName}' is already in use, using '${name}' instead`));
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
// If check fails (e.g., not logged in), continue - API will catch it on create
|
|
244
|
+
if (error instanceof api_1.AuthenticationError) {
|
|
245
|
+
throw error;
|
|
246
|
+
}
|
|
247
|
+
// Silently continue for other errors - the create API will validate
|
|
248
|
+
}
|
|
131
249
|
// Validate branch options
|
|
132
250
|
if (options.newBranch && !options.fromBranch) {
|
|
133
251
|
console.error(chalk_1.default.red('Error: --new-branch (-n) requires --from-branch (-f) to specify the source branch'));
|
|
@@ -145,7 +263,7 @@ exports.createCommand = new commander_1.Command('create')
|
|
|
145
263
|
// Build create options
|
|
146
264
|
const createOptions = {
|
|
147
265
|
name,
|
|
148
|
-
profile:
|
|
266
|
+
profile: selectedProfile,
|
|
149
267
|
apps: options.apps ? options.apps.split(',').map((a) => a.trim()) : undefined,
|
|
150
268
|
addApps: options.addApps ? options.addApps.split(',').map((a) => a.trim()) : undefined,
|
|
151
269
|
api: options.api,
|
|
@@ -170,7 +288,7 @@ exports.createCommand = new commander_1.Command('create')
|
|
|
170
288
|
// Display resolved configuration
|
|
171
289
|
displayResolvedConfig(resolved);
|
|
172
290
|
// Ask to save as profile (if not using one already)
|
|
173
|
-
if (!
|
|
291
|
+
if (!selectedProfile && !options.yes && !options.dryRun) {
|
|
174
292
|
await profileResolver.askSaveProfile(config, resolved);
|
|
175
293
|
}
|
|
176
294
|
// Dry run mode
|
package/dist/commands/init.js
CHANGED
|
@@ -1174,9 +1174,6 @@ async function setupEnvironments(scan, config, isMultiRepo = false, existingEnvV
|
|
|
1174
1174
|
}
|
|
1175
1175
|
}
|
|
1176
1176
|
if (Object.keys(urls).length > 0) {
|
|
1177
|
-
// Add database URLs
|
|
1178
|
-
urls['mongodb'] = '${STAGING_MONGODB_URL}';
|
|
1179
|
-
urls['redis'] = '${STAGING_REDIS_URL}';
|
|
1180
1177
|
environments.staging = {
|
|
1181
1178
|
description: 'Staging environment',
|
|
1182
1179
|
urls,
|
|
@@ -1207,8 +1204,6 @@ async function setupEnvironments(scan, config, isMultiRepo = false, existingEnvV
|
|
|
1207
1204
|
description: 'Staging environment',
|
|
1208
1205
|
urls: {
|
|
1209
1206
|
api: stagingApiUrl,
|
|
1210
|
-
mongodb: '${STAGING_MONGODB_URL}',
|
|
1211
|
-
redis: '${STAGING_REDIS_URL}',
|
|
1212
1207
|
},
|
|
1213
1208
|
};
|
|
1214
1209
|
}
|
|
@@ -1238,8 +1233,6 @@ async function setupEnvironments(scan, config, isMultiRepo = false, existingEnvV
|
|
|
1238
1233
|
description: 'Staging environment',
|
|
1239
1234
|
urls: {
|
|
1240
1235
|
api: stagingApiUrl,
|
|
1241
|
-
mongodb: '${STAGING_MONGODB_URL}',
|
|
1242
|
-
redis: '${STAGING_REDIS_URL}',
|
|
1243
1236
|
},
|
|
1244
1237
|
};
|
|
1245
1238
|
}
|
|
@@ -1279,7 +1272,6 @@ async function setupEnvironments(scan, config, isMultiRepo = false, existingEnvV
|
|
|
1279
1272
|
}
|
|
1280
1273
|
}
|
|
1281
1274
|
if (Object.keys(prodUrls).length > 0) {
|
|
1282
|
-
prodUrls['mongodb'] = '${PROD_MONGODB_URL}';
|
|
1283
1275
|
environments.production = {
|
|
1284
1276
|
description: 'Production (use with caution)',
|
|
1285
1277
|
urls: prodUrls,
|
|
@@ -1314,7 +1306,6 @@ async function setupEnvironments(scan, config, isMultiRepo = false, existingEnvV
|
|
|
1314
1306
|
description: 'Production (use with caution)',
|
|
1315
1307
|
urls: {
|
|
1316
1308
|
api: prodApiUrl,
|
|
1317
|
-
mongodb: '${PROD_MONGODB_URL}',
|
|
1318
1309
|
},
|
|
1319
1310
|
safety: {
|
|
1320
1311
|
read_only: true,
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Random name generator for Genbox environments
|
|
4
|
+
* Generates memorable names using adjective-noun combinations
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.generateRandomName = generateRandomName;
|
|
8
|
+
exports.generateNameSuggestions = generateNameSuggestions;
|
|
9
|
+
const adjectives = [
|
|
10
|
+
'swift', 'calm', 'bold', 'keen', 'warm',
|
|
11
|
+
'cool', 'bright', 'quick', 'smart', 'fresh',
|
|
12
|
+
'wild', 'free', 'pure', 'soft', 'sharp',
|
|
13
|
+
'kind', 'brave', 'proud', 'fair', 'wise',
|
|
14
|
+
'gold', 'silver', 'azure', 'coral', 'amber',
|
|
15
|
+
'jade', 'ruby', 'pearl', 'ivory', 'onyx',
|
|
16
|
+
'misty', 'sunny', 'windy', 'rainy', 'snowy',
|
|
17
|
+
'lucky', 'happy', 'merry', 'jolly', 'lively',
|
|
18
|
+
];
|
|
19
|
+
const nouns = [
|
|
20
|
+
'fox', 'owl', 'wolf', 'hawk', 'bear',
|
|
21
|
+
'deer', 'hare', 'lynx', 'seal', 'dove',
|
|
22
|
+
'oak', 'pine', 'elm', 'ash', 'birch',
|
|
23
|
+
'maple', 'cedar', 'willow', 'sage', 'fern',
|
|
24
|
+
'river', 'lake', 'creek', 'brook', 'pond',
|
|
25
|
+
'peak', 'vale', 'ridge', 'cliff', 'mesa',
|
|
26
|
+
'moon', 'star', 'sun', 'cloud', 'wind',
|
|
27
|
+
'storm', 'frost', 'dawn', 'dusk', 'wave',
|
|
28
|
+
];
|
|
29
|
+
function randomElement(array) {
|
|
30
|
+
return array[Math.floor(Math.random() * array.length)];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Generate a random environment name
|
|
34
|
+
* Format: adjective-noun (e.g., "swift-fox", "calm-river")
|
|
35
|
+
*/
|
|
36
|
+
function generateRandomName() {
|
|
37
|
+
const adjective = randomElement(adjectives);
|
|
38
|
+
const noun = randomElement(nouns);
|
|
39
|
+
return `${adjective}-${noun}`;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Generate multiple random name suggestions
|
|
43
|
+
*/
|
|
44
|
+
function generateNameSuggestions(count = 3) {
|
|
45
|
+
const names = new Set();
|
|
46
|
+
while (names.size < count) {
|
|
47
|
+
names.add(generateRandomName());
|
|
48
|
+
}
|
|
49
|
+
return Array.from(names);
|
|
50
|
+
}
|