@solidactions/cli 0.1.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 +56 -0
- package/dist/commands/deploy.js +454 -0
- package/dist/commands/env-create.js +87 -0
- package/dist/commands/env-delete.js +123 -0
- package/dist/commands/env-list.js +178 -0
- package/dist/commands/env-map.js +66 -0
- package/dist/commands/env-pull.js +134 -0
- package/dist/commands/init.js +97 -0
- package/dist/commands/logs-build.js +50 -0
- package/dist/commands/logs.js +103 -0
- package/dist/commands/pull.js +59 -0
- package/dist/commands/run.js +96 -0
- package/dist/commands/runs.js +97 -0
- package/dist/commands/schedule-delete.js +73 -0
- package/dist/commands/schedule-list.js +88 -0
- package/dist/commands/schedule-set.js +74 -0
- package/dist/index.js +191 -0
- package/package.json +58 -0
package/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# SolidActions CLI
|
|
2
|
+
|
|
3
|
+
Deploy and manage workflow automation with SolidActions.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @solidactions/cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Initialize with your API key
|
|
15
|
+
solidactions init <api-key>
|
|
16
|
+
|
|
17
|
+
# Deploy a project
|
|
18
|
+
solidactions deploy <project-name> <path>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Commands
|
|
22
|
+
|
|
23
|
+
| Command | Description |
|
|
24
|
+
|---------|-------------|
|
|
25
|
+
| `init <api-key>` | Initialize CLI with your API key |
|
|
26
|
+
| `logout` | Remove saved credentials |
|
|
27
|
+
| `whoami` | Show current configuration |
|
|
28
|
+
| `deploy <project> [path]` | Deploy a project to SolidActions |
|
|
29
|
+
| `pull <project> [path]` | Pull project source from SolidActions |
|
|
30
|
+
| `run <project> <workflow>` | Trigger a workflow run |
|
|
31
|
+
| `runs [project]` | List recent workflow runs |
|
|
32
|
+
| `logs <run-id>` | View logs for a workflow run |
|
|
33
|
+
| `logs:build <project>` | View build/deployment logs |
|
|
34
|
+
| `env:create <key> <value>` | Create a global environment variable |
|
|
35
|
+
| `env:list [project]` | List environment variables |
|
|
36
|
+
| `env:delete <key>` | Delete an environment variable |
|
|
37
|
+
| `env:map <project> <key> <global-key>` | Map a global variable to a project |
|
|
38
|
+
| `env:pull <project>` | Pull resolved env vars to a local file |
|
|
39
|
+
| `schedule:set <project> <cron>` | Set a cron schedule for a workflow |
|
|
40
|
+
| `schedule:list <project>` | List schedules for a project |
|
|
41
|
+
| `schedule:delete <project> <id>` | Delete a schedule |
|
|
42
|
+
|
|
43
|
+
See [docs/cli.md](docs/cli.md) for full documentation.
|
|
44
|
+
|
|
45
|
+
## Development
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
git clone https://github.com/SolidActions/solidactions-cli.git
|
|
49
|
+
cd solidactions-cli
|
|
50
|
+
npm install
|
|
51
|
+
npm run build
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## License
|
|
55
|
+
|
|
56
|
+
MIT
|
|
@@ -0,0 +1,454 @@
|
|
|
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.deploy = deploy;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const archiver_1 = __importDefault(require("archiver"));
|
|
10
|
+
const axios_1 = __importDefault(require("axios"));
|
|
11
|
+
const form_data_1 = __importDefault(require("form-data"));
|
|
12
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
13
|
+
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
14
|
+
const init_1 = require("./init");
|
|
15
|
+
/**
|
|
16
|
+
* Validate project structure before deployment.
|
|
17
|
+
* Checks for required files and SDK dependency.
|
|
18
|
+
*/
|
|
19
|
+
function validateProject(sourceDir) {
|
|
20
|
+
const errors = [];
|
|
21
|
+
const warnings = [];
|
|
22
|
+
// Check for solidactions.yaml
|
|
23
|
+
const solidactionsPath = path_1.default.join(sourceDir, 'solidactions.yaml');
|
|
24
|
+
if (!fs_1.default.existsSync(solidactionsPath)) {
|
|
25
|
+
errors.push('solidactions.yaml not found. This file defines your workflows.');
|
|
26
|
+
return { valid: false, errors, warnings };
|
|
27
|
+
}
|
|
28
|
+
// Check for package.json
|
|
29
|
+
const packageJsonPath = path_1.default.join(sourceDir, 'package.json');
|
|
30
|
+
if (!fs_1.default.existsSync(packageJsonPath)) {
|
|
31
|
+
errors.push('package.json not found. Initialize with: npm init');
|
|
32
|
+
return { valid: false, errors, warnings };
|
|
33
|
+
}
|
|
34
|
+
// Read and validate package.json
|
|
35
|
+
const packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf8'));
|
|
36
|
+
const allDeps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
37
|
+
// Check for SDK - either @solidactions/sdk or bundled sdk folder
|
|
38
|
+
const hasSdkPackage = '@solidactions/sdk' in allDeps;
|
|
39
|
+
const hasBundledSdk = fs_1.default.existsSync(path_1.default.join(sourceDir, 'sdk'));
|
|
40
|
+
if (!hasSdkPackage && !hasBundledSdk) {
|
|
41
|
+
errors.push('Missing SDK in package.json dependencies. Run: npm install @solidactions/sdk');
|
|
42
|
+
}
|
|
43
|
+
// Check for TypeScript config if .ts files are used
|
|
44
|
+
const srcDir = path_1.default.join(sourceDir, 'src');
|
|
45
|
+
const hasTypeScriptFiles = fs_1.default.existsSync(srcDir) &&
|
|
46
|
+
fs_1.default.readdirSync(srcDir).some(f => f.endsWith('.ts') || f.endsWith('.tsx'));
|
|
47
|
+
if (hasTypeScriptFiles) {
|
|
48
|
+
const tsconfigPath = path_1.default.join(sourceDir, 'tsconfig.json');
|
|
49
|
+
if (!fs_1.default.existsSync(tsconfigPath)) {
|
|
50
|
+
warnings.push('TypeScript files detected but tsconfig.json not found. Build may fail.');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// Parse and validate solidactions.yaml
|
|
54
|
+
try {
|
|
55
|
+
const configContent = fs_1.default.readFileSync(solidactionsPath, 'utf8');
|
|
56
|
+
const config = js_yaml_1.default.load(configContent);
|
|
57
|
+
if (!config || !config.workflows || !Array.isArray(config.workflows)) {
|
|
58
|
+
errors.push('Invalid solidactions.yaml: workflows section is required.');
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
// Validate each workflow
|
|
62
|
+
for (const wf of config.workflows) {
|
|
63
|
+
if (!wf.name) {
|
|
64
|
+
errors.push(`Workflow missing required 'name' field.`);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
// Must have either command: or file:
|
|
68
|
+
if (!wf.command && !wf.file) {
|
|
69
|
+
errors.push(`Workflow "${wf.name}": must specify either 'command:' or 'file:'.`);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
// If file: is specified, verify the file exists
|
|
73
|
+
if (wf.file) {
|
|
74
|
+
const filePath = path_1.default.join(sourceDir, wf.file);
|
|
75
|
+
if (!fs_1.default.existsSync(filePath)) {
|
|
76
|
+
errors.push(`Workflow "${wf.name}": file "${wf.file}" not found.`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
errors.push(`Failed to parse solidactions.yaml: ${err.message}`);
|
|
84
|
+
}
|
|
85
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
86
|
+
}
|
|
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 });
|
|
133
|
+
}
|
|
134
|
+
else if (typeof item === 'object' && item !== null) {
|
|
135
|
+
// Object: - VAR_NAME: GLOBAL_NAME (mapped to global)
|
|
136
|
+
const keys = Object.keys(item);
|
|
137
|
+
if (keys.length === 1) {
|
|
138
|
+
const key = keys[0];
|
|
139
|
+
const mappedTo = item[key];
|
|
140
|
+
parsedVars.push({ key, mappedTo: mappedTo || null });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return parsedVars;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Extract declared variable keys from solidactions.yaml env config.
|
|
148
|
+
* Returns a set of env var keys that are declared in YAML.
|
|
149
|
+
*/
|
|
150
|
+
function getYamlDeclaredVars(config) {
|
|
151
|
+
const parsedVars = parseYamlEnvVars(config);
|
|
152
|
+
return new Set(parsedVars.map(v => v.key));
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Check if deployEnv is enabled in the config.
|
|
156
|
+
* New format has deployEnv at top level.
|
|
157
|
+
*/
|
|
158
|
+
function isDeployEnvEnabled(config) {
|
|
159
|
+
return config.deployEnv === true;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Push YAML env declarations to the project.
|
|
163
|
+
* This registers all YAML-declared vars and their mappings.
|
|
164
|
+
*/
|
|
165
|
+
async function pushYamlDeclarations(host, apiKey, projectSlug, yamlConfig) {
|
|
166
|
+
const parsedVars = parseYamlEnvVars(yamlConfig);
|
|
167
|
+
if (parsedVars.length === 0) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
// Build the declarations array
|
|
171
|
+
const declarations = parsedVars.map(v => ({
|
|
172
|
+
env_name: v.key,
|
|
173
|
+
yaml_default_global_key: v.mappedTo,
|
|
174
|
+
source: 'yaml',
|
|
175
|
+
}));
|
|
176
|
+
try {
|
|
177
|
+
await axios_1.default.post(`${host}/api/v1/projects/${projectSlug}/variable-mappings/sync-yaml`, { declarations }, {
|
|
178
|
+
headers: {
|
|
179
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
180
|
+
'Accept': 'application/json',
|
|
181
|
+
'Content-Type': 'application/json',
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
console.log(chalk_1.default.gray(`Synced ${declarations.length} YAML env declarations`));
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
console.error(chalk_1.default.yellow('Warning: Failed to sync YAML declarations:'), error.response?.data?.message || error.message);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Push environment variables from .env file to project.
|
|
192
|
+
* Only pushes vars that are declared in solidactions.yaml.
|
|
193
|
+
*/
|
|
194
|
+
async function pushEnvVars(host, apiKey, projectSlug, sourceDir, environment, yamlConfig) {
|
|
195
|
+
// Get vars declared in YAML
|
|
196
|
+
const declaredVars = getYamlDeclaredVars(yamlConfig);
|
|
197
|
+
if (declaredVars.size === 0) {
|
|
198
|
+
console.log(chalk_1.default.gray('No environment variables declared in solidactions.yaml'));
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
// Read the .env file for this environment
|
|
202
|
+
const envFileName = environment === 'production' ? '.env' : `.env.${environment}`;
|
|
203
|
+
const envFilePath = path_1.default.join(sourceDir, envFileName);
|
|
204
|
+
if (!fs_1.default.existsSync(envFilePath)) {
|
|
205
|
+
console.log(chalk_1.default.yellow(`⚠ ${envFileName} not found, skipping env push`));
|
|
206
|
+
// Warn about each missing declared var
|
|
207
|
+
for (const key of declaredVars) {
|
|
208
|
+
console.log(chalk_1.default.yellow(` ⚠ ${key}: no value (not in ${envFileName})`));
|
|
209
|
+
}
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
// Parse the .env file
|
|
213
|
+
const envValues = parseEnvFile(envFilePath);
|
|
214
|
+
// Filter to only YAML-declared vars
|
|
215
|
+
const variables = [];
|
|
216
|
+
const missing = [];
|
|
217
|
+
for (const key of declaredVars) {
|
|
218
|
+
const value = envValues.get(key);
|
|
219
|
+
if (value !== undefined) {
|
|
220
|
+
// Mark vars containing "secret", "key", "token", "password" as secrets
|
|
221
|
+
const isSecret = /secret|key|token|password|credential/i.test(key);
|
|
222
|
+
variables.push({ key, value, is_secret: isSecret });
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
missing.push(key);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// Warn about missing vars
|
|
229
|
+
for (const key of missing) {
|
|
230
|
+
console.log(chalk_1.default.yellow(`⚠ ${key}: not found in ${envFileName}`));
|
|
231
|
+
}
|
|
232
|
+
if (variables.length === 0) {
|
|
233
|
+
console.log(chalk_1.default.yellow('No matching environment variables to push'));
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
// Push to API
|
|
237
|
+
try {
|
|
238
|
+
const response = await axios_1.default.post(`${host}/api/v1/projects/${projectSlug}/variable-mappings/bulk`, { variables }, {
|
|
239
|
+
headers: {
|
|
240
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
241
|
+
'Accept': 'application/json',
|
|
242
|
+
'Content-Type': 'application/json',
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
const { created, updated } = response.data;
|
|
246
|
+
console.log(chalk_1.default.green(`✓ Pushed ${variables.length} environment variables from ${envFileName}`));
|
|
247
|
+
if (created > 0 || updated > 0) {
|
|
248
|
+
console.log(chalk_1.default.gray(` (${created} created, ${updated} updated)`));
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
catch (error) {
|
|
252
|
+
console.error(chalk_1.default.red('Failed to push environment variables:'), error.response?.data?.message || error.message);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
async function deploy(projectName, sourcePath, options = {}) {
|
|
256
|
+
const config = (0, init_1.getConfig)();
|
|
257
|
+
if (!config?.apiKey) {
|
|
258
|
+
console.error(chalk_1.default.red('Not initialized. Run `solidactions init <api-key>` first.'));
|
|
259
|
+
process.exit(1);
|
|
260
|
+
}
|
|
261
|
+
const sourceDir = sourcePath ? path_1.default.resolve(sourcePath) : process.cwd();
|
|
262
|
+
const environment = options.env || 'production';
|
|
263
|
+
if (!fs_1.default.existsSync(sourceDir)) {
|
|
264
|
+
console.error(chalk_1.default.red(`Source directory not found: ${sourceDir}`));
|
|
265
|
+
process.exit(1);
|
|
266
|
+
}
|
|
267
|
+
const envLabel = environment !== 'production' ? ` (${environment})` : '';
|
|
268
|
+
console.log(chalk_1.default.blue(`Deploying to project "${projectName}"${envLabel}...`));
|
|
269
|
+
console.log(chalk_1.default.gray(`Source: ${sourceDir}`));
|
|
270
|
+
// Validate project structure before deploying
|
|
271
|
+
console.log(chalk_1.default.gray('Validating project structure...'));
|
|
272
|
+
const validation = validateProject(sourceDir);
|
|
273
|
+
// Parse solidactions.yaml for env config
|
|
274
|
+
const solidactionsPath = path_1.default.join(sourceDir, 'solidactions.yaml');
|
|
275
|
+
let yamlConfig = null;
|
|
276
|
+
try {
|
|
277
|
+
const configContent = fs_1.default.readFileSync(solidactionsPath, 'utf8');
|
|
278
|
+
yamlConfig = js_yaml_1.default.load(configContent);
|
|
279
|
+
}
|
|
280
|
+
catch {
|
|
281
|
+
// Config parsing errors are handled by validateProject
|
|
282
|
+
}
|
|
283
|
+
if (validation.warnings.length > 0) {
|
|
284
|
+
for (const warning of validation.warnings) {
|
|
285
|
+
console.log(chalk_1.default.yellow(`⚠ ${warning}`));
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (!validation.valid) {
|
|
289
|
+
console.error(chalk_1.default.red('\nDeployment failed - validation errors:\n'));
|
|
290
|
+
for (const error of validation.errors) {
|
|
291
|
+
console.error(chalk_1.default.red(` ✗ ${error}`));
|
|
292
|
+
}
|
|
293
|
+
console.error('');
|
|
294
|
+
process.exit(1);
|
|
295
|
+
}
|
|
296
|
+
console.log(chalk_1.default.green('✓ Project structure validated'));
|
|
297
|
+
// Check if project exists, create if not
|
|
298
|
+
let projectSlug = projectName;
|
|
299
|
+
try {
|
|
300
|
+
// For non-production environments, append the environment to the slug for lookup
|
|
301
|
+
const lookupSlug = environment === 'production'
|
|
302
|
+
? projectName
|
|
303
|
+
: `${projectName}-${environment}`;
|
|
304
|
+
const checkResponse = await axios_1.default.get(`${config.host}/api/v1/projects/${lookupSlug}`, {
|
|
305
|
+
headers: {
|
|
306
|
+
'Authorization': `Bearer ${config.apiKey}`,
|
|
307
|
+
'Accept': 'application/json',
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
projectSlug = checkResponse.data.slug || checkResponse.data.name;
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
if (error.response?.status === 404) {
|
|
314
|
+
// For non-production environments, check if we should create
|
|
315
|
+
if (environment !== 'production' && !options.create) {
|
|
316
|
+
console.log(chalk_1.default.yellow(`Project "${projectName}" doesn't have a ${environment} environment.`));
|
|
317
|
+
console.log(chalk_1.default.gray('Use --create to create it, or deploy to production first.'));
|
|
318
|
+
process.exit(1);
|
|
319
|
+
}
|
|
320
|
+
console.log(chalk_1.default.yellow(`Project "${projectName}"${envLabel} not found. Creating...`));
|
|
321
|
+
try {
|
|
322
|
+
const createResponse = await axios_1.default.post(`${config.host}/api/v1/projects`, {
|
|
323
|
+
name: projectName,
|
|
324
|
+
slug: projectName.toLowerCase().replace(/[^a-z0-9-]/g, '-') + (environment !== 'production' ? `-${environment}` : ''),
|
|
325
|
+
environment: environment,
|
|
326
|
+
}, {
|
|
327
|
+
headers: {
|
|
328
|
+
'Authorization': `Bearer ${config.apiKey}`,
|
|
329
|
+
'Accept': 'application/json',
|
|
330
|
+
'Content-Type': 'application/json',
|
|
331
|
+
},
|
|
332
|
+
});
|
|
333
|
+
projectSlug = createResponse.data.slug || createResponse.data.name;
|
|
334
|
+
console.log(chalk_1.default.green(`Project "${projectName}"${envLabel} created.`));
|
|
335
|
+
}
|
|
336
|
+
catch (createError) {
|
|
337
|
+
console.error(chalk_1.default.red('Failed to create project:'), createError.response?.data?.message || createError.message);
|
|
338
|
+
process.exit(1);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
console.error(chalk_1.default.red('Failed to check project:'), error.response?.data?.message || error.message);
|
|
343
|
+
process.exit(1);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
const zipPath = path_1.default.join(sourceDir, '.steps-deploy.zip');
|
|
347
|
+
const output = fs_1.default.createWriteStream(zipPath);
|
|
348
|
+
const archive = (0, archiver_1.default)('zip', { zlib: { level: 9 } });
|
|
349
|
+
output.on('close', async () => {
|
|
350
|
+
console.log(chalk_1.default.gray(`Zipped ${archive.pointer()} total bytes`));
|
|
351
|
+
try {
|
|
352
|
+
const form = new form_data_1.default();
|
|
353
|
+
form.append('source', fs_1.default.createReadStream(zipPath));
|
|
354
|
+
console.log(chalk_1.default.yellow('Uploading...'));
|
|
355
|
+
await axios_1.default.post(`${config.host}/api/v1/projects/${projectSlug}/deploy`, form, {
|
|
356
|
+
headers: {
|
|
357
|
+
...form.getHeaders(),
|
|
358
|
+
'Accept': 'application/json',
|
|
359
|
+
'Authorization': `Bearer ${config.apiKey}`,
|
|
360
|
+
},
|
|
361
|
+
maxContentLength: Infinity,
|
|
362
|
+
maxBodyLength: Infinity
|
|
363
|
+
});
|
|
364
|
+
console.log(chalk_1.default.green('Deployment successfully queued!'));
|
|
365
|
+
console.log(chalk_1.default.yellow('Waiting for build to complete...\n'));
|
|
366
|
+
// Poll for completion
|
|
367
|
+
let attempts = 0;
|
|
368
|
+
const maxAttempts = 120; // 2 minutes timeout
|
|
369
|
+
let lastLogLength = 0;
|
|
370
|
+
const poll = setInterval(async () => {
|
|
371
|
+
try {
|
|
372
|
+
attempts++;
|
|
373
|
+
const statusRes = await axios_1.default.get(`${config.host}/api/v1/projects/${projectSlug}`, {
|
|
374
|
+
headers: { 'Authorization': `Bearer ${config.apiKey}` }
|
|
375
|
+
});
|
|
376
|
+
const { status, build_log } = statusRes.data;
|
|
377
|
+
// Stream new log content
|
|
378
|
+
if (build_log && build_log.length > lastLogLength) {
|
|
379
|
+
const newContent = build_log.substring(lastLogLength);
|
|
380
|
+
process.stdout.write(chalk_1.default.gray(newContent));
|
|
381
|
+
if (!newContent.endsWith('\n')) {
|
|
382
|
+
process.stdout.write('\n');
|
|
383
|
+
}
|
|
384
|
+
lastLogLength = build_log.length;
|
|
385
|
+
}
|
|
386
|
+
if (status === 'deployed') {
|
|
387
|
+
clearInterval(poll);
|
|
388
|
+
console.log(chalk_1.default.green(`\n✓ Deployed to ${projectSlug}${envLabel}!`));
|
|
389
|
+
// Always sync YAML declarations (registers env vars and their mappings)
|
|
390
|
+
if (yamlConfig) {
|
|
391
|
+
await pushYamlDeclarations(config.host, config.apiKey, projectSlug, yamlConfig);
|
|
392
|
+
}
|
|
393
|
+
// Push .env values if deployEnv is enabled
|
|
394
|
+
if (yamlConfig && isDeployEnvEnabled(yamlConfig)) {
|
|
395
|
+
console.log(chalk_1.default.blue('\nPushing environment variables...'));
|
|
396
|
+
await pushEnvVars(config.host, config.apiKey, projectSlug, sourceDir, environment, yamlConfig);
|
|
397
|
+
}
|
|
398
|
+
fs_1.default.unlinkSync(zipPath);
|
|
399
|
+
process.exit(0);
|
|
400
|
+
}
|
|
401
|
+
else if (status === 'error') {
|
|
402
|
+
clearInterval(poll);
|
|
403
|
+
console.error(chalk_1.default.red('\n✗ Build Failed!'));
|
|
404
|
+
if (build_log) {
|
|
405
|
+
console.log(chalk_1.default.yellow('\n--- Full Build Log ---'));
|
|
406
|
+
console.log(chalk_1.default.gray(build_log));
|
|
407
|
+
console.log(chalk_1.default.yellow('--- End Build Log ---\n'));
|
|
408
|
+
}
|
|
409
|
+
fs_1.default.unlinkSync(zipPath);
|
|
410
|
+
process.exit(1);
|
|
411
|
+
}
|
|
412
|
+
else if (attempts >= maxAttempts) {
|
|
413
|
+
clearInterval(poll);
|
|
414
|
+
console.error(chalk_1.default.red('\nTimeout waiting for build. It might still finish.'));
|
|
415
|
+
fs_1.default.unlinkSync(zipPath);
|
|
416
|
+
process.exit(1);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
catch {
|
|
420
|
+
// Ignore transient errors
|
|
421
|
+
}
|
|
422
|
+
}, 1000);
|
|
423
|
+
}
|
|
424
|
+
catch (error) {
|
|
425
|
+
console.error(chalk_1.default.red('Deployment failed:'));
|
|
426
|
+
if (error.response) {
|
|
427
|
+
if (error.response.status === 404) {
|
|
428
|
+
console.error("Project not found.");
|
|
429
|
+
}
|
|
430
|
+
else {
|
|
431
|
+
console.error(error.response.status, JSON.stringify(error.response.data, null, 2));
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
console.error(error.message);
|
|
436
|
+
}
|
|
437
|
+
if (fs_1.default.existsSync(zipPath))
|
|
438
|
+
fs_1.default.unlinkSync(zipPath);
|
|
439
|
+
process.exit(1);
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
archive.on('error', (err) => {
|
|
443
|
+
throw err;
|
|
444
|
+
});
|
|
445
|
+
archive.pipe(output);
|
|
446
|
+
// Glob patterns to ignore
|
|
447
|
+
const ignore = ['node_modules/**', '.git/**', '.steps-deploy.zip', 'dist/**', 'vendor/**', '**/node_modules/**'];
|
|
448
|
+
archive.glob('**/*', {
|
|
449
|
+
cwd: sourceDir,
|
|
450
|
+
ignore: ignore,
|
|
451
|
+
dot: true
|
|
452
|
+
});
|
|
453
|
+
await archive.finalize();
|
|
454
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
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.envCreate = envCreate;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const init_1 = require("./init");
|
|
10
|
+
async function envCreate(key, value, options = {}) {
|
|
11
|
+
const config = (0, init_1.getConfig)();
|
|
12
|
+
if (!config?.apiKey) {
|
|
13
|
+
console.error(chalk_1.default.red('Not initialized. Run "solidactions init <api-key>" first.'));
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
console.log(chalk_1.default.blue(`Creating global variable "${key}"...`));
|
|
17
|
+
// Build the request body with per-environment values
|
|
18
|
+
const body = {
|
|
19
|
+
key,
|
|
20
|
+
production_value: value,
|
|
21
|
+
is_secret: options.secret || false,
|
|
22
|
+
};
|
|
23
|
+
// Handle staging value and inheritance
|
|
24
|
+
if (options.stagingInherit) {
|
|
25
|
+
body.staging_source = 'inherit_production';
|
|
26
|
+
}
|
|
27
|
+
else if (options.stagingValue !== undefined) {
|
|
28
|
+
body.staging_value = options.stagingValue;
|
|
29
|
+
body.staging_source = 'value';
|
|
30
|
+
}
|
|
31
|
+
// Handle dev value and inheritance
|
|
32
|
+
if (options.devInheritStaging) {
|
|
33
|
+
body.dev_source = 'inherit_staging';
|
|
34
|
+
}
|
|
35
|
+
else if (options.devInherit) {
|
|
36
|
+
body.dev_source = 'inherit_production';
|
|
37
|
+
}
|
|
38
|
+
else if (options.devValue !== undefined) {
|
|
39
|
+
body.dev_value = options.devValue;
|
|
40
|
+
body.dev_source = 'value';
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
await axios_1.default.post(`${config.host}/api/v1/variables`, body, {
|
|
44
|
+
headers: {
|
|
45
|
+
'Authorization': `Bearer ${config.apiKey}`,
|
|
46
|
+
'Accept': 'application/json',
|
|
47
|
+
'Content-Type': 'application/json',
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
const typeLabel = options.secret ? 'secret' : 'variable';
|
|
51
|
+
console.log(chalk_1.default.green(`Global ${typeLabel} "${key}" created successfully!`));
|
|
52
|
+
// Show summary of what was set
|
|
53
|
+
console.log(chalk_1.default.gray(` Production: ${options.secret ? '********' : value}`));
|
|
54
|
+
if (options.stagingValue) {
|
|
55
|
+
console.log(chalk_1.default.gray(` Staging: ${options.secret ? '********' : options.stagingValue}`));
|
|
56
|
+
}
|
|
57
|
+
else if (options.stagingInherit) {
|
|
58
|
+
console.log(chalk_1.default.gray(' Staging: (inherits from production)'));
|
|
59
|
+
}
|
|
60
|
+
if (options.devValue) {
|
|
61
|
+
console.log(chalk_1.default.gray(` Dev: ${options.secret ? '********' : options.devValue}`));
|
|
62
|
+
}
|
|
63
|
+
else if (options.devInheritStaging) {
|
|
64
|
+
console.log(chalk_1.default.gray(' Dev: (inherits from staging)'));
|
|
65
|
+
}
|
|
66
|
+
else if (options.devInherit) {
|
|
67
|
+
console.log(chalk_1.default.gray(' Dev: (inherits from production)'));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
if (error.response) {
|
|
72
|
+
if (error.response.status === 401) {
|
|
73
|
+
console.error(chalk_1.default.red('Authentication failed. Run "solidactions init <api-key>" to re-configure.'));
|
|
74
|
+
}
|
|
75
|
+
else if (error.response.status === 422) {
|
|
76
|
+
console.error(chalk_1.default.red('Validation error:'), error.response.data.message || error.response.data.errors);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
console.error(chalk_1.default.red(`Failed: ${error.response.status}`), error.response.data);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
console.error(chalk_1.default.red('Connection failed:'), error.message);
|
|
84
|
+
}
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
}
|