@agentuity/cli 1.0.6 → 1.0.7
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/cmd/cloud/env/env-diff.d.ts +35 -0
- package/dist/cmd/cloud/env/env-diff.d.ts.map +1 -0
- package/dist/cmd/cloud/env/env-diff.js +145 -0
- package/dist/cmd/cloud/env/env-diff.js.map +1 -0
- package/dist/cmd/cloud/env/pull.d.ts.map +1 -1
- package/dist/cmd/cloud/env/pull.js +51 -7
- package/dist/cmd/cloud/env/pull.js.map +1 -1
- package/dist/cmd/cloud/env/push.d.ts.map +1 -1
- package/dist/cmd/cloud/env/push.js +73 -4
- package/dist/cmd/cloud/env/push.js.map +1 -1
- package/dist/cmd/cloud/env/set.d.ts.map +1 -1
- package/dist/cmd/cloud/env/set.js +141 -45
- package/dist/cmd/cloud/env/set.js.map +1 -1
- package/package.json +6 -6
- package/src/cmd/cloud/env/env-diff.ts +180 -0
- package/src/cmd/cloud/env/pull.ts +69 -9
- package/src/cmd/cloud/env/push.ts +93 -4
- package/src/cmd/cloud/env/set.ts +178 -51
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { createSubcommand } from '../../../types';
|
|
3
3
|
import * as tui from '../../../tui';
|
|
4
|
-
import { projectEnvUpdate, orgEnvUpdate } from '@agentuity/server';
|
|
4
|
+
import { projectEnvUpdate, orgEnvUpdate, projectGet, orgEnvGet } from '@agentuity/server';
|
|
5
5
|
import {
|
|
6
6
|
findExistingEnvFile,
|
|
7
7
|
readEnvFile,
|
|
@@ -11,14 +11,19 @@ import {
|
|
|
11
11
|
} from '../../../env-util';
|
|
12
12
|
import { getCommand } from '../../../command-prefix';
|
|
13
13
|
import { resolveOrgId, isOrgScope } from './org-util';
|
|
14
|
+
import { computeEnvDiff, displayEnvDiff } from './env-diff';
|
|
14
15
|
|
|
15
16
|
const EnvPushResponseSchema = z.object({
|
|
16
17
|
success: z.boolean().describe('Whether push succeeded'),
|
|
17
18
|
pushed: z.number().describe('Number of items pushed'),
|
|
18
19
|
envCount: z.number().describe('Number of env vars pushed'),
|
|
19
20
|
secretCount: z.number().describe('Number of secrets pushed'),
|
|
21
|
+
newCount: z.number().describe('Number of new variables added'),
|
|
22
|
+
changedCount: z.number().describe('Number of existing variables overwritten'),
|
|
23
|
+
unchangedCount: z.number().describe('Number of unchanged variables'),
|
|
20
24
|
source: z.string().describe('Source file path'),
|
|
21
25
|
scope: z.enum(['project', 'org']).describe('The scope where variables were pushed'),
|
|
26
|
+
force: z.boolean().describe('Whether force mode was used'),
|
|
22
27
|
});
|
|
23
28
|
|
|
24
29
|
export const pushSubcommand = createSubcommand({
|
|
@@ -39,6 +44,7 @@ export const pushSubcommand = createSubcommand({
|
|
|
39
44
|
.union([z.boolean(), z.string()])
|
|
40
45
|
.optional()
|
|
41
46
|
.describe('push to organization level (use --org for default org)'),
|
|
47
|
+
force: z.boolean().default(false).describe('overwrite remote values without confirmation'),
|
|
42
48
|
}),
|
|
43
49
|
response: EnvPushResponseSchema,
|
|
44
50
|
},
|
|
@@ -59,6 +65,8 @@ export const pushSubcommand = createSubcommand({
|
|
|
59
65
|
// Filter out reserved AGENTUITY_ prefixed keys
|
|
60
66
|
const filteredEnv = filterAgentuitySdkKeys(localEnv);
|
|
61
67
|
|
|
68
|
+
const forceMode = opts?.force ?? false;
|
|
69
|
+
|
|
62
70
|
if (Object.keys(filteredEnv).length === 0) {
|
|
63
71
|
tui.warning('No variables to push');
|
|
64
72
|
return {
|
|
@@ -66,8 +74,12 @@ export const pushSubcommand = createSubcommand({
|
|
|
66
74
|
pushed: 0,
|
|
67
75
|
envCount: 0,
|
|
68
76
|
secretCount: 0,
|
|
77
|
+
newCount: 0,
|
|
78
|
+
changedCount: 0,
|
|
79
|
+
unchangedCount: 0,
|
|
69
80
|
source: envFilePath,
|
|
70
81
|
scope: useOrgScope ? ('org' as const) : ('project' as const),
|
|
82
|
+
force: forceMode,
|
|
71
83
|
};
|
|
72
84
|
}
|
|
73
85
|
|
|
@@ -93,6 +105,38 @@ export const pushSubcommand = createSubcommand({
|
|
|
93
105
|
// Organization scope
|
|
94
106
|
const orgId = await resolveOrgId(apiClient, config, opts!.org!);
|
|
95
107
|
|
|
108
|
+
// Fetch remote state to compute diff
|
|
109
|
+
const orgData = await tui.spinner('Fetching remote variables', () => {
|
|
110
|
+
return orgEnvGet(apiClient, { id: orgId, mask: false });
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const diff = computeEnvDiff(env, secrets, orgData.env || {}, orgData.secrets || {});
|
|
114
|
+
|
|
115
|
+
displayEnvDiff(diff, { direction: 'push' });
|
|
116
|
+
|
|
117
|
+
// Prompt for confirmation if there are changes to existing variables
|
|
118
|
+
if (diff.changedEntries.length > 0 && !forceMode) {
|
|
119
|
+
const confirmed = await tui.confirm(
|
|
120
|
+
`${diff.changedEntries.length} existing variable${diff.changedEntries.length !== 1 ? 's' : ''} will be overwritten. Continue?`,
|
|
121
|
+
true
|
|
122
|
+
);
|
|
123
|
+
if (!confirmed) {
|
|
124
|
+
tui.info('Push cancelled');
|
|
125
|
+
return {
|
|
126
|
+
success: false,
|
|
127
|
+
pushed: 0,
|
|
128
|
+
envCount: 0,
|
|
129
|
+
secretCount: 0,
|
|
130
|
+
newCount: 0,
|
|
131
|
+
changedCount: 0,
|
|
132
|
+
unchangedCount: 0,
|
|
133
|
+
source: envFilePath,
|
|
134
|
+
scope: 'org' as const,
|
|
135
|
+
force: forceMode,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
96
140
|
await tui.spinner('Pushing variables to organization', () => {
|
|
97
141
|
return orgEnvUpdate(apiClient, {
|
|
98
142
|
id: orgId,
|
|
@@ -106,7 +150,7 @@ export const pushSubcommand = createSubcommand({
|
|
|
106
150
|
const totalCount = envCount + secretCount;
|
|
107
151
|
|
|
108
152
|
tui.success(
|
|
109
|
-
`Pushed ${totalCount} variable${totalCount !== 1 ? 's' : ''} to organization (${
|
|
153
|
+
`Pushed ${totalCount} variable${totalCount !== 1 ? 's' : ''} to organization (${diff.newEntries.length} new, ${diff.changedEntries.length} updated, ${diff.unchangedEntries.length} unchanged)`
|
|
110
154
|
);
|
|
111
155
|
|
|
112
156
|
return {
|
|
@@ -114,17 +158,58 @@ export const pushSubcommand = createSubcommand({
|
|
|
114
158
|
pushed: totalCount,
|
|
115
159
|
envCount,
|
|
116
160
|
secretCount,
|
|
161
|
+
newCount: diff.newEntries.length,
|
|
162
|
+
changedCount: diff.changedEntries.length,
|
|
163
|
+
unchangedCount: diff.unchangedEntries.length,
|
|
117
164
|
source: envFilePath,
|
|
118
165
|
scope: 'org' as const,
|
|
166
|
+
force: forceMode,
|
|
119
167
|
};
|
|
120
168
|
} else {
|
|
121
|
-
// Project scope
|
|
169
|
+
// Project scope
|
|
122
170
|
if (!project) {
|
|
123
171
|
tui.fatal(
|
|
124
172
|
'Project context required. Run from a project directory or use --org for organization scope.'
|
|
125
173
|
);
|
|
126
174
|
}
|
|
127
175
|
|
|
176
|
+
// Fetch remote state to compute diff
|
|
177
|
+
const projectData = await tui.spinner('Fetching remote variables', () => {
|
|
178
|
+
return projectGet(apiClient, { id: project.projectId, mask: false });
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const diff = computeEnvDiff(
|
|
182
|
+
env,
|
|
183
|
+
secrets,
|
|
184
|
+
projectData.env || {},
|
|
185
|
+
projectData.secrets || {}
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
displayEnvDiff(diff, { direction: 'push' });
|
|
189
|
+
|
|
190
|
+
// Prompt for confirmation if there are changes to existing variables
|
|
191
|
+
if (diff.changedEntries.length > 0 && !forceMode) {
|
|
192
|
+
const confirmed = await tui.confirm(
|
|
193
|
+
`${diff.changedEntries.length} existing variable${diff.changedEntries.length !== 1 ? 's' : ''} will be overwritten. Continue?`,
|
|
194
|
+
true
|
|
195
|
+
);
|
|
196
|
+
if (!confirmed) {
|
|
197
|
+
tui.info('Push cancelled');
|
|
198
|
+
return {
|
|
199
|
+
success: false,
|
|
200
|
+
pushed: 0,
|
|
201
|
+
envCount: 0,
|
|
202
|
+
secretCount: 0,
|
|
203
|
+
newCount: 0,
|
|
204
|
+
changedCount: 0,
|
|
205
|
+
unchangedCount: 0,
|
|
206
|
+
source: envFilePath,
|
|
207
|
+
scope: 'project' as const,
|
|
208
|
+
force: forceMode,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
128
213
|
await tui.spinner('Pushing variables to cloud', () => {
|
|
129
214
|
return projectEnvUpdate(apiClient, {
|
|
130
215
|
id: project.projectId,
|
|
@@ -138,7 +223,7 @@ export const pushSubcommand = createSubcommand({
|
|
|
138
223
|
const totalCount = envCount + secretCount;
|
|
139
224
|
|
|
140
225
|
tui.success(
|
|
141
|
-
`Pushed ${totalCount} variable${totalCount !== 1 ? 's' : ''} to cloud (${
|
|
226
|
+
`Pushed ${totalCount} variable${totalCount !== 1 ? 's' : ''} to cloud (${diff.newEntries.length} new, ${diff.changedEntries.length} updated, ${diff.unchangedEntries.length} unchanged)`
|
|
142
227
|
);
|
|
143
228
|
|
|
144
229
|
return {
|
|
@@ -146,8 +231,12 @@ export const pushSubcommand = createSubcommand({
|
|
|
146
231
|
pushed: totalCount,
|
|
147
232
|
envCount,
|
|
148
233
|
secretCount,
|
|
234
|
+
newCount: diff.newEntries.length,
|
|
235
|
+
changedCount: diff.changedEntries.length,
|
|
236
|
+
unchangedCount: diff.unchangedEntries.length,
|
|
149
237
|
source: envFilePath,
|
|
150
238
|
scope: 'project' as const,
|
|
239
|
+
force: forceMode,
|
|
151
240
|
};
|
|
152
241
|
}
|
|
153
242
|
},
|
package/src/cmd/cloud/env/set.ts
CHANGED
|
@@ -13,20 +13,80 @@ import {
|
|
|
13
13
|
import { getCommand } from '../../../command-prefix';
|
|
14
14
|
import { resolveOrgId, isOrgScope } from './org-util';
|
|
15
15
|
|
|
16
|
+
interface ParsedEnvPair {
|
|
17
|
+
key: string;
|
|
18
|
+
value: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Parse env set arguments into key-value pairs.
|
|
23
|
+
* Supports two formats:
|
|
24
|
+
* - Legacy: KEY VALUE (exactly 2 args)
|
|
25
|
+
* - KEY=VALUE format: KEY1=VALUE1 [KEY2=VALUE2 ...]
|
|
26
|
+
*/
|
|
27
|
+
function parseEnvArgs(rawArgs: string[]): ParsedEnvPair[] {
|
|
28
|
+
if (rawArgs.length === 0) {
|
|
29
|
+
tui.fatal(
|
|
30
|
+
'No arguments provided. Usage: env set KEY VALUE or env set KEY=VALUE [KEY2=VALUE2 ...]'
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Check if first arg contains '=' — if so, treat ALL args as KEY=VALUE format
|
|
35
|
+
const firstArg = rawArgs[0]!;
|
|
36
|
+
if (firstArg.includes('=')) {
|
|
37
|
+
const pairs: ParsedEnvPair[] = [];
|
|
38
|
+
for (const arg of rawArgs) {
|
|
39
|
+
const eqIndex = arg.indexOf('=');
|
|
40
|
+
if (eqIndex === -1 || eqIndex === 0) {
|
|
41
|
+
tui.fatal(`Invalid format: '${arg}'. Expected KEY=VALUE format.`);
|
|
42
|
+
}
|
|
43
|
+
const key = arg.substring(0, eqIndex);
|
|
44
|
+
const value = arg.substring(eqIndex + 1);
|
|
45
|
+
if (!key) {
|
|
46
|
+
tui.fatal(`Invalid format: '${arg}'. Key cannot be empty.`);
|
|
47
|
+
}
|
|
48
|
+
pairs.push({ key, value });
|
|
49
|
+
}
|
|
50
|
+
return pairs;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Legacy format: exactly 2 args = KEY VALUE
|
|
54
|
+
if (rawArgs.length === 2) {
|
|
55
|
+
const key = rawArgs[0]!.trim();
|
|
56
|
+
if (!key) {
|
|
57
|
+
tui.fatal(
|
|
58
|
+
'Invalid format: key cannot be empty. Usage: env set KEY VALUE or env set KEY=VALUE'
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
return [{ key, value: rawArgs[1]! }];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Ambiguous: 1 arg without '=' or 3+ args without '='
|
|
65
|
+
if (rawArgs.length === 1) {
|
|
66
|
+
tui.fatal(`Missing value for '${rawArgs[0]}'. Usage: env set KEY VALUE or env set KEY=VALUE`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 3+ args without '=' in first arg
|
|
70
|
+
tui.fatal(
|
|
71
|
+
'Multiple variables must use KEY=VALUE format. Usage: env set KEY1=VALUE1 KEY2=VALUE2 ...'
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
16
75
|
const EnvSetResponseSchema = z.object({
|
|
17
76
|
success: z.boolean().describe('Whether the operation succeeded'),
|
|
18
|
-
|
|
77
|
+
keys: z.array(z.string()).describe('Environment variable keys that were set'),
|
|
19
78
|
path: z
|
|
20
79
|
.string()
|
|
21
80
|
.optional()
|
|
22
|
-
.describe('Local file path where env
|
|
23
|
-
|
|
24
|
-
|
|
81
|
+
.describe('Local file path where env vars were saved (project scope only)'),
|
|
82
|
+
secretKeys: z.array(z.string()).describe('Keys that were stored as secrets'),
|
|
83
|
+
envKeys: z.array(z.string()).describe('Keys that were stored as env vars'),
|
|
84
|
+
scope: z.enum(['project', 'org']).describe('The scope where the variables were set'),
|
|
25
85
|
});
|
|
26
86
|
|
|
27
87
|
export const setSubcommand = createSubcommand({
|
|
28
88
|
name: 'set',
|
|
29
|
-
description: 'Set
|
|
89
|
+
description: 'Set one or more environment variables or secrets',
|
|
30
90
|
tags: ['mutating', 'updates-resource', 'slow', 'requires-auth'],
|
|
31
91
|
idempotent: true,
|
|
32
92
|
requires: { auth: true, apiClient: true },
|
|
@@ -36,7 +96,15 @@ export const setSubcommand = createSubcommand({
|
|
|
36
96
|
command: getCommand('env set NODE_ENV production'),
|
|
37
97
|
description: 'Set environment variable',
|
|
38
98
|
},
|
|
99
|
+
{
|
|
100
|
+
command: getCommand('env set NODE_ENV=production'),
|
|
101
|
+
description: 'Set using KEY=VALUE format',
|
|
102
|
+
},
|
|
39
103
|
{ command: getCommand('env set PORT 3000'), description: 'Set port number' },
|
|
104
|
+
{
|
|
105
|
+
command: getCommand('env set NODE_ENV=production LOG_LEVEL=info PORT=3000'),
|
|
106
|
+
description: 'Set multiple variables at once',
|
|
107
|
+
},
|
|
40
108
|
{
|
|
41
109
|
command: getCommand('env set API_KEY "sk_..." --secret'),
|
|
42
110
|
description: 'Set a secret value',
|
|
@@ -48,8 +116,7 @@ export const setSubcommand = createSubcommand({
|
|
|
48
116
|
],
|
|
49
117
|
schema: {
|
|
50
118
|
args: z.object({
|
|
51
|
-
|
|
52
|
-
value: z.string().describe('the environment variable value'),
|
|
119
|
+
args: z.array(z.string()).describe('KEY VALUE or KEY=VALUE [KEY2=VALUE2 ...]'),
|
|
53
120
|
}),
|
|
54
121
|
options: z.object({
|
|
55
122
|
secret: z
|
|
@@ -67,8 +134,9 @@ export const setSubcommand = createSubcommand({
|
|
|
67
134
|
},
|
|
68
135
|
|
|
69
136
|
async handler(ctx) {
|
|
70
|
-
const { args, opts, apiClient, project, projectDir, config } = ctx;
|
|
137
|
+
const { args: cmdArgs, opts, apiClient, project, projectDir, config } = ctx;
|
|
71
138
|
const useOrgScope = isOrgScope(opts?.org);
|
|
139
|
+
const forceSecret = opts?.secret ?? false;
|
|
72
140
|
|
|
73
141
|
// Require project context if not using org scope
|
|
74
142
|
if (!useOrgScope && !project) {
|
|
@@ -77,86 +145,145 @@ export const setSubcommand = createSubcommand({
|
|
|
77
145
|
);
|
|
78
146
|
}
|
|
79
147
|
|
|
80
|
-
|
|
81
|
-
const isPublic = isPublicVarKey(args.key);
|
|
148
|
+
const pairs = parseEnvArgs(cmdArgs.args);
|
|
82
149
|
|
|
83
|
-
// Validate
|
|
84
|
-
|
|
85
|
-
|
|
150
|
+
// Validate all keys first
|
|
151
|
+
for (const pair of pairs) {
|
|
152
|
+
if (isReservedAgentuityKey(pair.key)) {
|
|
153
|
+
tui.fatal(
|
|
154
|
+
`Cannot set AGENTUITY_ prefixed variables: '${pair.key}'. These are reserved for system use.`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
86
157
|
}
|
|
87
158
|
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
159
|
+
// Reject duplicate keys
|
|
160
|
+
const seenKeys = new Set<string>();
|
|
161
|
+
for (const pair of pairs) {
|
|
162
|
+
if (seenKeys.has(pair.key)) {
|
|
163
|
+
tui.fatal(`Duplicate key '${pair.key}'. Each variable may only be specified once.`);
|
|
164
|
+
}
|
|
165
|
+
seenKeys.add(pair.key);
|
|
93
166
|
}
|
|
94
167
|
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
168
|
+
// Classify each pair as env or secret
|
|
169
|
+
const envPairs: Record<string, string> = {};
|
|
170
|
+
const secretPairs: Record<string, string> = {};
|
|
171
|
+
const secretKeysList: string[] = [];
|
|
172
|
+
const envKeysList: string[] = [];
|
|
173
|
+
|
|
174
|
+
for (const pair of pairs) {
|
|
175
|
+
const isPublic = isPublicVarKey(pair.key);
|
|
176
|
+
let isSecret = forceSecret;
|
|
99
177
|
|
|
100
|
-
|
|
178
|
+
// Validate public vars cannot be secrets
|
|
179
|
+
if (isSecret && isPublic) {
|
|
180
|
+
tui.fatal(
|
|
181
|
+
`Cannot set public variables as secrets. '${pair.key}' (prefix ${PUBLIC_VAR_PREFIXES.join(', ')}) is exposed to the frontend.`
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Auto-detect if this looks like a secret and offer to store as secret
|
|
186
|
+
// Skip auto-detect for public vars since they can never be secrets
|
|
187
|
+
if (!isSecret && !isPublic && looksLikeSecret(pair.key, pair.value)) {
|
|
188
|
+
if (pairs.length === 1) {
|
|
189
|
+
// Single pair: offer interactive prompt (existing behavior)
|
|
190
|
+
tui.warning(`The variable '${pair.key}' looks like it should be a secret.`);
|
|
191
|
+
const storeAsSecret = await tui.confirm('Store as a secret instead?', true);
|
|
192
|
+
if (storeAsSecret) {
|
|
193
|
+
isSecret = true;
|
|
194
|
+
}
|
|
195
|
+
} else {
|
|
196
|
+
// Multiple pairs: auto-detect silently
|
|
197
|
+
isSecret = true;
|
|
198
|
+
tui.info(`Auto-detected '${pair.key}' as a secret`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
101
201
|
|
|
102
|
-
if (
|
|
103
|
-
|
|
202
|
+
if (isSecret) {
|
|
203
|
+
secretPairs[pair.key] = pair.value;
|
|
204
|
+
secretKeysList.push(pair.key);
|
|
205
|
+
} else {
|
|
206
|
+
envPairs[pair.key] = pair.value;
|
|
207
|
+
envKeysList.push(pair.key);
|
|
104
208
|
}
|
|
105
209
|
}
|
|
106
210
|
|
|
107
|
-
const
|
|
211
|
+
const totalCount = pairs.length;
|
|
212
|
+
const allKeys = [...envKeysList, ...secretKeysList];
|
|
213
|
+
const secretSuffix =
|
|
214
|
+
secretKeysList.length > 0
|
|
215
|
+
? ` (${secretKeysList.length} secret${secretKeysList.length !== 1 ? 's' : ''})`
|
|
216
|
+
: '';
|
|
108
217
|
|
|
109
218
|
if (useOrgScope) {
|
|
110
219
|
// Organization scope
|
|
111
220
|
const orgId = await resolveOrgId(apiClient, config, opts!.org!);
|
|
112
221
|
|
|
113
|
-
const updatePayload
|
|
114
|
-
|
|
115
|
-
|
|
222
|
+
const updatePayload: {
|
|
223
|
+
id: string;
|
|
224
|
+
env?: Record<string, string>;
|
|
225
|
+
secrets?: Record<string, string>;
|
|
226
|
+
} = { id: orgId };
|
|
227
|
+
if (Object.keys(envPairs).length > 0) updatePayload.env = envPairs;
|
|
228
|
+
if (Object.keys(secretPairs).length > 0) updatePayload.secrets = secretPairs;
|
|
116
229
|
|
|
117
|
-
await tui.spinner(
|
|
118
|
-
|
|
119
|
-
|
|
230
|
+
await tui.spinner(
|
|
231
|
+
`Setting ${totalCount} organization variable${totalCount !== 1 ? 's' : ''} in cloud`,
|
|
232
|
+
() => {
|
|
233
|
+
return orgEnvUpdate(apiClient, updatePayload);
|
|
234
|
+
}
|
|
235
|
+
);
|
|
120
236
|
|
|
121
237
|
tui.success(
|
|
122
|
-
`Organization ${
|
|
238
|
+
`Organization variable${totalCount !== 1 ? 's' : ''} set successfully: ${allKeys.join(', ')}${secretSuffix}`
|
|
123
239
|
);
|
|
124
240
|
|
|
125
241
|
return {
|
|
126
242
|
success: true,
|
|
127
|
-
|
|
128
|
-
|
|
243
|
+
keys: allKeys,
|
|
244
|
+
secretKeys: secretKeysList,
|
|
245
|
+
envKeys: envKeysList,
|
|
129
246
|
scope: 'org' as const,
|
|
130
247
|
};
|
|
131
248
|
} else {
|
|
132
|
-
// Project scope
|
|
133
|
-
const updatePayload
|
|
134
|
-
|
|
135
|
-
|
|
249
|
+
// Project scope
|
|
250
|
+
const updatePayload: {
|
|
251
|
+
id: string;
|
|
252
|
+
env?: Record<string, string>;
|
|
253
|
+
secrets?: Record<string, string>;
|
|
254
|
+
} = { id: project!.projectId };
|
|
255
|
+
if (Object.keys(envPairs).length > 0) updatePayload.env = envPairs;
|
|
256
|
+
if (Object.keys(secretPairs).length > 0) updatePayload.secrets = secretPairs;
|
|
136
257
|
|
|
137
|
-
await tui.spinner(
|
|
138
|
-
|
|
139
|
-
|
|
258
|
+
await tui.spinner(
|
|
259
|
+
`Setting ${totalCount} variable${totalCount !== 1 ? 's' : ''} in cloud`,
|
|
260
|
+
() => {
|
|
261
|
+
return projectEnvUpdate(apiClient, updatePayload);
|
|
262
|
+
}
|
|
263
|
+
);
|
|
140
264
|
|
|
141
265
|
// Update local .env file only if we have a project directory
|
|
142
|
-
// (not when using --project-id without being in a project folder)
|
|
143
266
|
let envFilePath: string | undefined;
|
|
144
267
|
if (projectDir) {
|
|
145
268
|
envFilePath = await findExistingEnvFile(projectDir);
|
|
146
|
-
|
|
147
|
-
|
|
269
|
+
const allPairsForLocal: Record<string, string> = {
|
|
270
|
+
...envPairs,
|
|
271
|
+
...secretPairs,
|
|
272
|
+
};
|
|
273
|
+
await writeEnvFile(envFilePath, allPairsForLocal);
|
|
148
274
|
}
|
|
149
275
|
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
276
|
+
const locationMsg = envFilePath ? ` (cloud + ${envFilePath})` : ' (cloud only)';
|
|
277
|
+
tui.success(
|
|
278
|
+
`Variable${totalCount !== 1 ? 's' : ''} set successfully: ${allKeys.join(', ')}${secretSuffix}${locationMsg}`
|
|
279
|
+
);
|
|
154
280
|
|
|
155
281
|
return {
|
|
156
282
|
success: true,
|
|
157
|
-
|
|
283
|
+
keys: allKeys,
|
|
158
284
|
path: envFilePath,
|
|
159
|
-
|
|
285
|
+
secretKeys: secretKeysList,
|
|
286
|
+
envKeys: envKeysList,
|
|
160
287
|
scope: 'project' as const,
|
|
161
288
|
};
|
|
162
289
|
}
|