@solidactions/cli 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/commands/deploy.js +2 -153
- package/dist/commands/dev.js +7 -0
- package/dist/commands/env-push.js +208 -0
- package/dist/index.js +13 -0
- package/dist/utils/env.js +93 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -36,6 +36,7 @@ solidactions deploy <project-name> <path>
|
|
|
36
36
|
| `env:delete <key>` | Delete an environment variable |
|
|
37
37
|
| `env:map <project> <key> <global-key>` | Map a global variable to a project |
|
|
38
38
|
| `env:pull <project>` | Pull resolved env vars (including OAuth tokens) to a local file |
|
|
39
|
+
| `env:push <project> [path]` | Push .env values to a project |
|
|
39
40
|
| `schedule:set <project> <cron>` | Set a cron schedule for a workflow |
|
|
40
41
|
| `schedule:list <project>` | List schedules for a project |
|
|
41
42
|
| `schedule:delete <project> <id>` | Delete a schedule |
|
package/dist/commands/deploy.js
CHANGED
|
@@ -12,6 +12,7 @@ const form_data_1 = __importDefault(require("form-data"));
|
|
|
12
12
|
const chalk_1 = __importDefault(require("chalk"));
|
|
13
13
|
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
14
14
|
const init_1 = require("./init");
|
|
15
|
+
const env_1 = require("../utils/env");
|
|
15
16
|
/**
|
|
16
17
|
* Validate project structure before deployment.
|
|
17
18
|
* Checks for required files and SDK dependency.
|
|
@@ -84,94 +85,12 @@ function validateProject(sourceDir) {
|
|
|
84
85
|
}
|
|
85
86
|
return { valid: errors.length === 0, errors, warnings };
|
|
86
87
|
}
|
|
87
|
-
/**
|
|
88
|
-
* Parse a .env file into a key-value map.
|
|
89
|
-
*/
|
|
90
|
-
function parseEnvFile(filePath) {
|
|
91
|
-
const envMap = new Map();
|
|
92
|
-
if (!fs_1.default.existsSync(filePath)) {
|
|
93
|
-
return envMap;
|
|
94
|
-
}
|
|
95
|
-
const content = fs_1.default.readFileSync(filePath, 'utf8');
|
|
96
|
-
const lines = content.split('\n');
|
|
97
|
-
for (const line of lines) {
|
|
98
|
-
const trimmed = line.trim();
|
|
99
|
-
// Skip empty lines and comments
|
|
100
|
-
if (!trimmed || trimmed.startsWith('#')) {
|
|
101
|
-
continue;
|
|
102
|
-
}
|
|
103
|
-
const equalsIndex = trimmed.indexOf('=');
|
|
104
|
-
if (equalsIndex === -1) {
|
|
105
|
-
continue;
|
|
106
|
-
}
|
|
107
|
-
const key = trimmed.substring(0, equalsIndex).trim();
|
|
108
|
-
let value = trimmed.substring(equalsIndex + 1).trim();
|
|
109
|
-
// Remove surrounding quotes if present
|
|
110
|
-
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
111
|
-
(value.startsWith("'") && value.endsWith("'"))) {
|
|
112
|
-
value = value.slice(1, -1);
|
|
113
|
-
}
|
|
114
|
-
envMap.set(key, value);
|
|
115
|
-
}
|
|
116
|
-
return envMap;
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* Parse YAML env declarations into structured format.
|
|
120
|
-
* Handles the new simplified format:
|
|
121
|
-
* - VAR_NAME -> { key: "VAR_NAME", mappedTo: null }
|
|
122
|
-
* - VAR_NAME: GLOBAL -> { key: "VAR_NAME", mappedTo: "GLOBAL" }
|
|
123
|
-
*/
|
|
124
|
-
function parseYamlEnvVars(config) {
|
|
125
|
-
const parsedVars = [];
|
|
126
|
-
if (!config.env || !Array.isArray(config.env)) {
|
|
127
|
-
return parsedVars;
|
|
128
|
-
}
|
|
129
|
-
for (const item of config.env) {
|
|
130
|
-
if (typeof item === 'string') {
|
|
131
|
-
// Simple string: - VAR_NAME (declared only, needs configuration)
|
|
132
|
-
parsedVars.push({ key: item, mappedTo: null, oauthName: null });
|
|
133
|
-
}
|
|
134
|
-
else if (typeof item === 'object' && item !== null) {
|
|
135
|
-
// Object: - VAR_NAME: GLOBAL_NAME or - VAR_NAME: { oauth: connection-name }
|
|
136
|
-
const keys = Object.keys(item);
|
|
137
|
-
if (keys.length === 1) {
|
|
138
|
-
const key = keys[0];
|
|
139
|
-
const value = item[key];
|
|
140
|
-
if (typeof value === 'object' && value !== null && 'oauth' in value) {
|
|
141
|
-
if (typeof value.oauth !== 'string' || !value.oauth) {
|
|
142
|
-
throw new Error(`Invalid env config for ${key}: 'oauth' must be a non-empty string`);
|
|
143
|
-
}
|
|
144
|
-
parsedVars.push({ key, mappedTo: null, oauthName: value.oauth });
|
|
145
|
-
}
|
|
146
|
-
else {
|
|
147
|
-
parsedVars.push({ key, mappedTo: value || null, oauthName: null });
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
return parsedVars;
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* Extract declared variable keys from solidactions.yaml env config.
|
|
156
|
-
* Returns a set of env var keys that are declared in YAML.
|
|
157
|
-
*/
|
|
158
|
-
function getYamlDeclaredVars(config) {
|
|
159
|
-
const parsedVars = parseYamlEnvVars(config);
|
|
160
|
-
return new Set(parsedVars.map(v => v.key));
|
|
161
|
-
}
|
|
162
|
-
/**
|
|
163
|
-
* Check if deployEnv is enabled in the config.
|
|
164
|
-
* New format has deployEnv at top level.
|
|
165
|
-
*/
|
|
166
|
-
function isDeployEnvEnabled(config) {
|
|
167
|
-
return config.deployEnv === true;
|
|
168
|
-
}
|
|
169
88
|
/**
|
|
170
89
|
* Push YAML env declarations to the project.
|
|
171
90
|
* This registers all YAML-declared vars and their mappings.
|
|
172
91
|
*/
|
|
173
92
|
async function pushYamlDeclarations(host, apiKey, projectSlug, yamlConfig) {
|
|
174
|
-
const parsedVars = parseYamlEnvVars(yamlConfig);
|
|
93
|
+
const parsedVars = (0, env_1.parseYamlEnvVars)(yamlConfig);
|
|
175
94
|
if (parsedVars.length === 0) {
|
|
176
95
|
return;
|
|
177
96
|
}
|
|
@@ -196,71 +115,6 @@ async function pushYamlDeclarations(host, apiKey, projectSlug, yamlConfig) {
|
|
|
196
115
|
console.error(chalk_1.default.yellow('Warning: Failed to sync YAML declarations:'), error.response?.data?.message || error.message);
|
|
197
116
|
}
|
|
198
117
|
}
|
|
199
|
-
/**
|
|
200
|
-
* Push environment variables from .env file to project.
|
|
201
|
-
* Only pushes vars that are declared in solidactions.yaml.
|
|
202
|
-
*/
|
|
203
|
-
async function pushEnvVars(host, apiKey, projectSlug, sourceDir, environment, yamlConfig) {
|
|
204
|
-
// Get vars declared in YAML
|
|
205
|
-
const declaredVars = getYamlDeclaredVars(yamlConfig);
|
|
206
|
-
if (declaredVars.size === 0) {
|
|
207
|
-
console.log(chalk_1.default.gray('No environment variables declared in solidactions.yaml'));
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
// Read the .env file for this environment
|
|
211
|
-
const envFileName = environment === 'production' ? '.env' : `.env.${environment}`;
|
|
212
|
-
const envFilePath = path_1.default.join(sourceDir, envFileName);
|
|
213
|
-
if (!fs_1.default.existsSync(envFilePath)) {
|
|
214
|
-
console.log(chalk_1.default.yellow(`⚠ ${envFileName} not found, skipping env push`));
|
|
215
|
-
// Warn about each missing declared var
|
|
216
|
-
for (const key of declaredVars) {
|
|
217
|
-
console.log(chalk_1.default.yellow(` ⚠ ${key}: no value (not in ${envFileName})`));
|
|
218
|
-
}
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
// Parse the .env file
|
|
222
|
-
const envValues = parseEnvFile(envFilePath);
|
|
223
|
-
// Filter to only YAML-declared vars
|
|
224
|
-
const variables = [];
|
|
225
|
-
const missing = [];
|
|
226
|
-
for (const key of declaredVars) {
|
|
227
|
-
const value = envValues.get(key);
|
|
228
|
-
if (value !== undefined) {
|
|
229
|
-
// Mark vars containing "secret", "key", "token", "password" as secrets
|
|
230
|
-
const isSecret = /secret|key|token|password|credential/i.test(key);
|
|
231
|
-
variables.push({ key, value, is_secret: isSecret });
|
|
232
|
-
}
|
|
233
|
-
else {
|
|
234
|
-
missing.push(key);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
// Warn about missing vars
|
|
238
|
-
for (const key of missing) {
|
|
239
|
-
console.log(chalk_1.default.yellow(`⚠ ${key}: not found in ${envFileName}`));
|
|
240
|
-
}
|
|
241
|
-
if (variables.length === 0) {
|
|
242
|
-
console.log(chalk_1.default.yellow('No matching environment variables to push'));
|
|
243
|
-
return;
|
|
244
|
-
}
|
|
245
|
-
// Push to API
|
|
246
|
-
try {
|
|
247
|
-
const response = await axios_1.default.post(`${host}/api/v1/projects/${projectSlug}/variable-mappings/bulk`, { variables }, {
|
|
248
|
-
headers: {
|
|
249
|
-
'Authorization': `Bearer ${apiKey}`,
|
|
250
|
-
'Accept': 'application/json',
|
|
251
|
-
'Content-Type': 'application/json',
|
|
252
|
-
},
|
|
253
|
-
});
|
|
254
|
-
const { created, updated } = response.data;
|
|
255
|
-
console.log(chalk_1.default.green(`✓ Pushed ${variables.length} environment variables from ${envFileName}`));
|
|
256
|
-
if (created > 0 || updated > 0) {
|
|
257
|
-
console.log(chalk_1.default.gray(` (${created} created, ${updated} updated)`));
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
catch (error) {
|
|
261
|
-
console.error(chalk_1.default.red('Failed to push environment variables:'), error.response?.data?.message || error.message);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
118
|
async function deploy(projectName, sourcePath, options = {}) {
|
|
265
119
|
const config = (0, init_1.getConfig)();
|
|
266
120
|
if (!config?.apiKey) {
|
|
@@ -399,11 +253,6 @@ async function deploy(projectName, sourcePath, options = {}) {
|
|
|
399
253
|
if (yamlConfig) {
|
|
400
254
|
await pushYamlDeclarations(config.host, config.apiKey, projectSlug, yamlConfig);
|
|
401
255
|
}
|
|
402
|
-
// Push .env values if deployEnv is enabled
|
|
403
|
-
if (yamlConfig && isDeployEnvEnabled(yamlConfig)) {
|
|
404
|
-
console.log(chalk_1.default.blue('\nPushing environment variables...'));
|
|
405
|
-
await pushEnvVars(config.host, config.apiKey, projectSlug, sourceDir, environment, yamlConfig);
|
|
406
|
-
}
|
|
407
256
|
fs_1.default.unlinkSync(zipPath);
|
|
408
257
|
process.exit(0);
|
|
409
258
|
}
|
package/dist/commands/dev.js
CHANGED
|
@@ -8,6 +8,7 @@ const child_process_1 = require("child_process");
|
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const fs_1 = __importDefault(require("fs"));
|
|
10
10
|
const chalk_1 = __importDefault(require("chalk"));
|
|
11
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
11
12
|
async function dev(file, options) {
|
|
12
13
|
// Resolve the workflow file path
|
|
13
14
|
const filePath = path_1.default.resolve(file);
|
|
@@ -35,6 +36,12 @@ async function dev(file, options) {
|
|
|
35
36
|
console.log(chalk_1.default.gray('Update to the latest SDK version: npm install @solidactions/sdk@latest'));
|
|
36
37
|
process.exit(1);
|
|
37
38
|
}
|
|
39
|
+
// Load .env file from project root
|
|
40
|
+
const envPath = path_1.default.join(projectDir, '.env');
|
|
41
|
+
if (fs_1.default.existsSync(envPath)) {
|
|
42
|
+
dotenv_1.default.config({ path: envPath });
|
|
43
|
+
console.log(chalk_1.default.gray(`Loaded .env from ${projectDir}`));
|
|
44
|
+
}
|
|
38
45
|
// Validate input JSON if provided
|
|
39
46
|
const input = options.input || '{}';
|
|
40
47
|
try {
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.envPush = envPush;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const axios_1 = __importDefault(require("axios"));
|
|
10
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
11
|
+
const prompts_1 = __importDefault(require("prompts"));
|
|
12
|
+
const init_1 = require("./init");
|
|
13
|
+
const env_1 = require("../utils/env");
|
|
14
|
+
async function envPush(projectName, sourcePath, options = {}) {
|
|
15
|
+
const config = (0, init_1.getConfig)();
|
|
16
|
+
if (!config?.apiKey) {
|
|
17
|
+
console.error(chalk_1.default.red('Not initialized. Run "solidactions init <api-key>" first.'));
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
const sourceDir = path_1.default.resolve(sourcePath);
|
|
21
|
+
const environment = options.env || 'dev';
|
|
22
|
+
// Read solidactions.yaml
|
|
23
|
+
const yamlPath = path_1.default.join(sourceDir, 'solidactions.yaml');
|
|
24
|
+
if (!fs_1.default.existsSync(yamlPath)) {
|
|
25
|
+
console.error(chalk_1.default.red('solidactions.yaml not found in ' + sourceDir));
|
|
26
|
+
console.log(chalk_1.default.gray('Make sure you\'re in a SolidActions project directory.'));
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
let yamlConfig;
|
|
30
|
+
try {
|
|
31
|
+
yamlConfig = (0, env_1.loadSolidActionsConfig)(yamlPath);
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
console.error(chalk_1.default.red('Failed to parse solidactions.yaml:'), err.message);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
// Get YAML-declared vars
|
|
38
|
+
const declaredVars = (0, env_1.getYamlDeclaredVars)(yamlConfig);
|
|
39
|
+
// Determine env file
|
|
40
|
+
const envFileName = environment === 'production' ? '.env' : `.env.${environment}`;
|
|
41
|
+
const envFilePath = path_1.default.join(sourceDir, envFileName);
|
|
42
|
+
if (!fs_1.default.existsSync(envFilePath)) {
|
|
43
|
+
console.error(chalk_1.default.red(`${envFileName} not found in ${sourceDir}`));
|
|
44
|
+
console.log(chalk_1.default.gray(`Create ${envFileName} with your environment variables, or use a different --env.`));
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
// Parse the .env file
|
|
48
|
+
const envValues = (0, env_1.parseEnvFile)(envFilePath);
|
|
49
|
+
if (envValues.size === 0) {
|
|
50
|
+
console.log(chalk_1.default.yellow(`${envFileName} is empty — nothing to push.`));
|
|
51
|
+
process.exit(0);
|
|
52
|
+
}
|
|
53
|
+
// Filter to YAML-declared vars only (unless --include-undeclared)
|
|
54
|
+
let keysToProcess;
|
|
55
|
+
if (options.includeUndeclared) {
|
|
56
|
+
keysToProcess = Array.from(envValues.keys());
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
if (declaredVars.size === 0) {
|
|
60
|
+
console.log(chalk_1.default.yellow('No environment variables declared in solidactions.yaml.'));
|
|
61
|
+
console.log(chalk_1.default.gray('Use --include-undeclared to push all vars from the .env file.'));
|
|
62
|
+
process.exit(0);
|
|
63
|
+
}
|
|
64
|
+
keysToProcess = Array.from(envValues.keys()).filter(k => declaredVars.has(k));
|
|
65
|
+
// Warn about YAML-declared vars missing from .env
|
|
66
|
+
for (const key of declaredVars) {
|
|
67
|
+
if (!envValues.has(key)) {
|
|
68
|
+
console.log(chalk_1.default.yellow(`⚠ ${key}: declared in YAML but not found in ${envFileName}`));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (keysToProcess.length === 0) {
|
|
73
|
+
console.log(chalk_1.default.yellow('No matching variables to push.'));
|
|
74
|
+
if (!options.includeUndeclared && envValues.size > 0) {
|
|
75
|
+
console.log(chalk_1.default.gray('Your .env has variables not declared in solidactions.yaml. Use --include-undeclared to push them.'));
|
|
76
|
+
}
|
|
77
|
+
process.exit(0);
|
|
78
|
+
}
|
|
79
|
+
// Build project slug
|
|
80
|
+
const projectSlug = environment === 'production'
|
|
81
|
+
? projectName
|
|
82
|
+
: `${projectName}-${environment}`;
|
|
83
|
+
console.log(chalk_1.default.blue(`Pushing env vars to "${projectName}" (${environment})...`));
|
|
84
|
+
// Fetch current server state
|
|
85
|
+
let serverMappings = [];
|
|
86
|
+
try {
|
|
87
|
+
const response = await axios_1.default.get(`${config.host}/api/v1/projects/${projectSlug}/variable-mappings`, {
|
|
88
|
+
headers: {
|
|
89
|
+
'Authorization': `Bearer ${config.apiKey}`,
|
|
90
|
+
'Accept': 'application/json',
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
serverMappings = response.data || [];
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
if (error.response?.status === 401) {
|
|
97
|
+
console.error(chalk_1.default.red('Authentication failed. Run "solidactions init <api-key>" to re-configure.'));
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
else if (error.response?.status === 404) {
|
|
101
|
+
console.error(chalk_1.default.red(`Project "${projectSlug}" not found.`));
|
|
102
|
+
console.log(chalk_1.default.gray(`Deploy first with: solidactions deploy ${projectName} --env ${environment}` + (environment !== 'production' ? ' --create' : '')));
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
// Build a lookup of server state by env_name
|
|
108
|
+
const serverState = new Map();
|
|
109
|
+
for (const mapping of serverMappings) {
|
|
110
|
+
serverState.set(mapping.env_name, mapping);
|
|
111
|
+
}
|
|
112
|
+
const entries = [];
|
|
113
|
+
for (const key of keysToProcess) {
|
|
114
|
+
const value = envValues.get(key);
|
|
115
|
+
const isSecret = /secret|key|token|password|credential/i.test(key);
|
|
116
|
+
const existing = serverState.get(key);
|
|
117
|
+
let action;
|
|
118
|
+
if (!existing || existing.status === 'not_configured' || existing.has_value === false) {
|
|
119
|
+
action = 'create';
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
action = 'update';
|
|
123
|
+
}
|
|
124
|
+
// If --new-only, skip updates
|
|
125
|
+
if (options.newOnly && action === 'update') {
|
|
126
|
+
action = 'skip';
|
|
127
|
+
}
|
|
128
|
+
entries.push({ key, value, is_secret: isSecret, action });
|
|
129
|
+
}
|
|
130
|
+
const toCreate = entries.filter(e => e.action === 'create');
|
|
131
|
+
const toUpdate = entries.filter(e => e.action === 'update');
|
|
132
|
+
const toSkip = entries.filter(e => e.action === 'skip');
|
|
133
|
+
const toPush = entries.filter(e => e.action !== 'skip');
|
|
134
|
+
if (toPush.length === 0) {
|
|
135
|
+
console.log(chalk_1.default.yellow('Nothing to push — all variables are already configured.'));
|
|
136
|
+
if (toSkip.length > 0) {
|
|
137
|
+
console.log(chalk_1.default.gray(`${toSkip.length} variable(s) skipped (--new-only).`));
|
|
138
|
+
}
|
|
139
|
+
process.exit(0);
|
|
140
|
+
}
|
|
141
|
+
// Display diff table
|
|
142
|
+
console.log('');
|
|
143
|
+
console.log(chalk_1.default.bold(' KEY'.padEnd(36) + 'ACTION'.padEnd(12) + 'NOTES'));
|
|
144
|
+
console.log(chalk_1.default.gray(' ' + '─'.repeat(56)));
|
|
145
|
+
for (const entry of entries) {
|
|
146
|
+
const keyCol = (' ' + entry.key).padEnd(36);
|
|
147
|
+
let actionCol;
|
|
148
|
+
let notesCol;
|
|
149
|
+
switch (entry.action) {
|
|
150
|
+
case 'create':
|
|
151
|
+
actionCol = chalk_1.default.green('create'.padEnd(12));
|
|
152
|
+
notesCol = chalk_1.default.gray('(new)');
|
|
153
|
+
break;
|
|
154
|
+
case 'update':
|
|
155
|
+
actionCol = chalk_1.default.yellow('update'.padEnd(12));
|
|
156
|
+
notesCol = chalk_1.default.gray('(has existing value)');
|
|
157
|
+
break;
|
|
158
|
+
case 'skip':
|
|
159
|
+
actionCol = chalk_1.default.gray('skip'.padEnd(12));
|
|
160
|
+
notesCol = chalk_1.default.gray('(--new-only)');
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
console.log(keyCol + actionCol + notesCol);
|
|
164
|
+
}
|
|
165
|
+
console.log('');
|
|
166
|
+
// Confirm unless --yes
|
|
167
|
+
if (!options.yes) {
|
|
168
|
+
const summary = [];
|
|
169
|
+
if (toCreate.length > 0)
|
|
170
|
+
summary.push(`${toCreate.length} create`);
|
|
171
|
+
if (toUpdate.length > 0)
|
|
172
|
+
summary.push(`${toUpdate.length} update`);
|
|
173
|
+
if (toSkip.length > 0)
|
|
174
|
+
summary.push(`${toSkip.length} skip`);
|
|
175
|
+
const response = await (0, prompts_1.default)({
|
|
176
|
+
type: 'confirm',
|
|
177
|
+
name: 'confirm',
|
|
178
|
+
message: `Push ${toPush.length} variable(s) to ${projectSlug}? (${summary.join(', ')})`,
|
|
179
|
+
initial: true,
|
|
180
|
+
});
|
|
181
|
+
if (!response.confirm) {
|
|
182
|
+
console.log(chalk_1.default.gray('Cancelled.'));
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// Push via bulk API
|
|
187
|
+
const variables = toPush.map(e => ({
|
|
188
|
+
key: e.key,
|
|
189
|
+
value: e.value,
|
|
190
|
+
is_secret: e.is_secret,
|
|
191
|
+
}));
|
|
192
|
+
try {
|
|
193
|
+
const response = await axios_1.default.post(`${config.host}/api/v1/projects/${projectSlug}/variable-mappings/bulk`, { variables }, {
|
|
194
|
+
headers: {
|
|
195
|
+
'Authorization': `Bearer ${config.apiKey}`,
|
|
196
|
+
'Accept': 'application/json',
|
|
197
|
+
'Content-Type': 'application/json',
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
const { created, updated } = response.data;
|
|
201
|
+
console.log(chalk_1.default.green(`\n✓ Pushed ${toPush.length} variable(s) to ${projectSlug}`));
|
|
202
|
+
console.log(chalk_1.default.gray(` ${created} created, ${updated} updated` + (toSkip.length > 0 ? `, ${toSkip.length} skipped` : '')));
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
console.error(chalk_1.default.red('Failed to push environment variables:'), error.response?.data?.message || error.message);
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -14,6 +14,7 @@ const env_list_1 = require("./commands/env-list");
|
|
|
14
14
|
const env_delete_1 = require("./commands/env-delete");
|
|
15
15
|
const env_map_1 = require("./commands/env-map");
|
|
16
16
|
const env_pull_1 = require("./commands/env-pull");
|
|
17
|
+
const env_push_1 = require("./commands/env-push");
|
|
17
18
|
const schedule_set_1 = require("./commands/schedule-set");
|
|
18
19
|
const schedule_list_1 = require("./commands/schedule-list");
|
|
19
20
|
const schedule_delete_1 = require("./commands/schedule-delete");
|
|
@@ -173,6 +174,18 @@ program
|
|
|
173
174
|
.action((project, options) => {
|
|
174
175
|
(0, env_pull_1.envPull)(project, options);
|
|
175
176
|
});
|
|
177
|
+
program
|
|
178
|
+
.command('env:push')
|
|
179
|
+
.description('Push environment variables from .env file to a project')
|
|
180
|
+
.argument('<project>', 'Project name')
|
|
181
|
+
.argument('[path]', 'Source directory with solidactions.yaml and .env file', '.')
|
|
182
|
+
.option('-e, --env <environment>', 'Target environment (production/staging/dev)', 'dev')
|
|
183
|
+
.option('--new-only', 'Only push new or empty variables (skip existing)')
|
|
184
|
+
.option('--include-undeclared', 'Also push vars not declared in solidactions.yaml')
|
|
185
|
+
.option('-y, --yes', 'Skip confirmation prompt')
|
|
186
|
+
.action((project, path, options) => {
|
|
187
|
+
(0, env_push_1.envPush)(project, path, options);
|
|
188
|
+
});
|
|
176
189
|
// =============================================================================
|
|
177
190
|
// Scheduling
|
|
178
191
|
// =============================================================================
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.parseEnvFile = parseEnvFile;
|
|
7
|
+
exports.parseYamlEnvVars = parseYamlEnvVars;
|
|
8
|
+
exports.getYamlDeclaredVars = getYamlDeclaredVars;
|
|
9
|
+
exports.loadSolidActionsConfig = loadSolidActionsConfig;
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
12
|
+
/**
|
|
13
|
+
* Parse a .env file into a key-value map.
|
|
14
|
+
*/
|
|
15
|
+
function parseEnvFile(filePath) {
|
|
16
|
+
const envMap = new Map();
|
|
17
|
+
if (!fs_1.default.existsSync(filePath)) {
|
|
18
|
+
return envMap;
|
|
19
|
+
}
|
|
20
|
+
const content = fs_1.default.readFileSync(filePath, 'utf8');
|
|
21
|
+
const lines = content.split('\n');
|
|
22
|
+
for (const line of lines) {
|
|
23
|
+
const trimmed = line.trim();
|
|
24
|
+
// Skip empty lines and comments
|
|
25
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
const equalsIndex = trimmed.indexOf('=');
|
|
29
|
+
if (equalsIndex === -1) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const key = trimmed.substring(0, equalsIndex).trim();
|
|
33
|
+
let value = trimmed.substring(equalsIndex + 1).trim();
|
|
34
|
+
// Remove surrounding quotes if present
|
|
35
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
36
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
37
|
+
value = value.slice(1, -1);
|
|
38
|
+
}
|
|
39
|
+
envMap.set(key, value);
|
|
40
|
+
}
|
|
41
|
+
return envMap;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Parse YAML env declarations into structured format.
|
|
45
|
+
* Handles the new simplified format:
|
|
46
|
+
* - VAR_NAME -> { key: "VAR_NAME", mappedTo: null }
|
|
47
|
+
* - VAR_NAME: GLOBAL -> { key: "VAR_NAME", mappedTo: "GLOBAL" }
|
|
48
|
+
*/
|
|
49
|
+
function parseYamlEnvVars(config) {
|
|
50
|
+
const parsedVars = [];
|
|
51
|
+
if (!config.env || !Array.isArray(config.env)) {
|
|
52
|
+
return parsedVars;
|
|
53
|
+
}
|
|
54
|
+
for (const item of config.env) {
|
|
55
|
+
if (typeof item === 'string') {
|
|
56
|
+
// Simple string: - VAR_NAME (declared only, needs configuration)
|
|
57
|
+
parsedVars.push({ key: item, mappedTo: null, oauthName: null });
|
|
58
|
+
}
|
|
59
|
+
else if (typeof item === 'object' && item !== null) {
|
|
60
|
+
// Object: - VAR_NAME: GLOBAL_NAME or - VAR_NAME: { oauth: connection-name }
|
|
61
|
+
const keys = Object.keys(item);
|
|
62
|
+
if (keys.length === 1) {
|
|
63
|
+
const key = keys[0];
|
|
64
|
+
const value = item[key];
|
|
65
|
+
if (typeof value === 'object' && value !== null && 'oauth' in value) {
|
|
66
|
+
if (typeof value.oauth !== 'string' || !value.oauth) {
|
|
67
|
+
throw new Error(`Invalid env config for ${key}: 'oauth' must be a non-empty string`);
|
|
68
|
+
}
|
|
69
|
+
parsedVars.push({ key, mappedTo: null, oauthName: value.oauth });
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
parsedVars.push({ key, mappedTo: value || null, oauthName: null });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return parsedVars;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Extract declared variable keys from solidactions.yaml env config.
|
|
81
|
+
* Returns a set of env var keys that are declared in YAML.
|
|
82
|
+
*/
|
|
83
|
+
function getYamlDeclaredVars(config) {
|
|
84
|
+
const parsedVars = parseYamlEnvVars(config);
|
|
85
|
+
return new Set(parsedVars.map(v => v.key));
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Load and parse a solidactions.yaml config file.
|
|
89
|
+
*/
|
|
90
|
+
function loadSolidActionsConfig(yamlPath) {
|
|
91
|
+
const content = fs_1.default.readFileSync(yamlPath, 'utf8');
|
|
92
|
+
return js_yaml_1.default.load(content);
|
|
93
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidactions/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "SolidActions CLI - Deploy and manage workflow automation",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"axios": "^1.6.0",
|
|
42
42
|
"chalk": "^4.1.2",
|
|
43
43
|
"commander": "^11.0.0",
|
|
44
|
+
"dotenv": "^17.3.1",
|
|
44
45
|
"form-data": "^4.0.0",
|
|
45
46
|
"fs-extra": "^11.0.0",
|
|
46
47
|
"js-yaml": "^4.1.0",
|