@solidactions/cli 0.3.0 → 0.4.1

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 CHANGED
@@ -35,7 +35,7 @@ solidactions deploy <project-name> <path>
35
35
  | `env:list [project]` | List environment variables |
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
- | `env:pull <project>` | Pull resolved env vars to a local file |
38
+ | `env:pull <project>` | Pull resolved env vars (including OAuth tokens) to a local file |
39
39
  | `schedule:set <project> <cron>` | Set a cron schedule for a workflow |
40
40
  | `schedule:list <project>` | List schedules for a project |
41
41
  | `schedule:delete <project> <id>` | Delete a schedule |
@@ -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 {
@@ -42,7 +42,7 @@ async function envPull(projectName, options = {}) {
42
42
  console.log(chalk_1.default.blue(`Pulling environment variables from "${projectName}" (${environment})...`));
43
43
  try {
44
44
  // First, check if there are any secrets
45
- const checkResponse = await axios_1.default.get(`${config.host}/api/v1/projects/${projectSlug}/variable-mappings`, {
45
+ const checkResponse = await axios_1.default.get(`${config.host}/api/v1/projects/${projectSlug}/variable-mappings?resolve_oauth=true`, {
46
46
  headers: {
47
47
  'Authorization': `Bearer ${config.apiKey}`,
48
48
  'Accept': 'application/json',
@@ -53,9 +53,9 @@ async function envPull(projectName, options = {}) {
53
53
  console.log(chalk_1.default.yellow('No environment variables found for this project.'));
54
54
  process.exit(0);
55
55
  }
56
- // Check for secrets
56
+ // Check for secrets (skip confirmation for --update-oauth since OAuth tokens are always secrets)
57
57
  const hasSecrets = mappings.some((m) => m.is_secret);
58
- if (hasSecrets && !options.yes) {
58
+ if (hasSecrets && !options.yes && !options.updateOauth) {
59
59
  console.log(chalk_1.default.yellow('\nThis project contains secret values.'));
60
60
  const confirmed = await confirm('This will expose secret values in plain text. Continue?');
61
61
  if (!confirmed) {
@@ -64,14 +64,97 @@ async function envPull(projectName, options = {}) {
64
64
  }
65
65
  }
66
66
  // Now fetch with reveal=true to get actual values
67
- const response = await axios_1.default.get(`${config.host}/api/v1/projects/${projectSlug}/variable-mappings?reveal=true`, {
67
+ const response = await axios_1.default.get(`${config.host}/api/v1/projects/${projectSlug}/variable-mappings?reveal=true&resolve_oauth=true`, {
68
68
  headers: {
69
69
  'Authorization': `Bearer ${config.apiKey}`,
70
70
  'Accept': 'application/json',
71
71
  },
72
72
  });
73
73
  const variables = response.data || [];
74
- // Build .env file content
74
+ // --update-oauth: Only pull OAuth tokens and merge into existing .env
75
+ if (options.updateOauth) {
76
+ const oauthVars = variables.filter((v) => v.source_type === 'oauth_connection');
77
+ if (oauthVars.length === 0) {
78
+ console.log(chalk_1.default.yellow('No OAuth variables found for this project.'));
79
+ process.exit(0);
80
+ }
81
+ // Build OAuth env var names set and their formatted lines
82
+ const oauthKeySet = new Set(oauthVars.map((v) => v.env_name));
83
+ const oauthLines = [];
84
+ for (const variable of oauthVars) {
85
+ const value = variable.resolved_value ?? variable.value;
86
+ if (value === null || value === undefined) {
87
+ continue;
88
+ }
89
+ // Add expiry comment
90
+ const connName = variable.oauth_connection_name || 'OAuth';
91
+ if (variable.token_expires_at) {
92
+ oauthLines.push(`# OAuth: ${connName} (expires ${variable.token_expires_at})`);
93
+ }
94
+ else {
95
+ oauthLines.push(`# OAuth: ${connName} (short-lived, re-pull to refresh)`);
96
+ }
97
+ // Format value
98
+ let formattedValue = value;
99
+ if (typeof value === 'string' && (value.includes(' ') || value.includes('"') || value.includes("'") ||
100
+ value.includes('\n') || value.includes('=') || value.includes('#'))) {
101
+ formattedValue = `"${value.replace(/"/g, '\\"')}"`;
102
+ }
103
+ oauthLines.push(`${variable.env_name}=${formattedValue}`);
104
+ }
105
+ if (!fs_1.default.existsSync(outputPath)) {
106
+ // No .env file — create with just OAuth vars
107
+ console.log(chalk_1.default.yellow('No .env file found — creating with OAuth vars only. Run a full env:pull for all variables.'));
108
+ const content = oauthLines.join('\n') + '\n';
109
+ fs_1.default.writeFileSync(outputPath, content);
110
+ }
111
+ else {
112
+ // Merge into existing .env file
113
+ const existingContent = fs_1.default.readFileSync(outputPath, 'utf-8');
114
+ const existingLines = existingContent.split('\n');
115
+ const preservedLines = [];
116
+ for (let i = 0; i < existingLines.length; i++) {
117
+ const line = existingLines[i];
118
+ const nextLine = existingLines[i + 1];
119
+ // Skip OAuth comment lines if the next line is a skipped OAuth var
120
+ if (line.startsWith('# OAuth:') && nextLine) {
121
+ const nextMatch = nextLine.match(/^([A-Za-z_][A-Za-z0-9_]*)=/);
122
+ if (nextMatch && oauthKeySet.has(nextMatch[1])) {
123
+ continue;
124
+ }
125
+ }
126
+ // Skip existing OAuth var lines
127
+ const varMatch = line.match(/^([A-Za-z_][A-Za-z0-9_]*)=/);
128
+ if (varMatch && oauthKeySet.has(varMatch[1])) {
129
+ continue;
130
+ }
131
+ // Skip previous "OAuth tokens (updated ...)" header
132
+ if (line.startsWith('# OAuth tokens (updated ')) {
133
+ continue;
134
+ }
135
+ preservedLines.push(line);
136
+ }
137
+ // Strip trailing blank lines from preserved content
138
+ while (preservedLines.length > 0 && preservedLines[preservedLines.length - 1].trim() === '') {
139
+ preservedLines.pop();
140
+ }
141
+ // Append OAuth section
142
+ preservedLines.push('');
143
+ preservedLines.push(`# OAuth tokens (updated ${new Date().toISOString()})`);
144
+ preservedLines.push(...oauthLines);
145
+ preservedLines.push('');
146
+ fs_1.default.writeFileSync(outputPath, preservedLines.join('\n'));
147
+ }
148
+ console.log(chalk_1.default.green(`\n✓ Updated ${oauthVars.length} OAuth token(s) in ${outputFile}`));
149
+ // Print any OAuth warnings
150
+ for (const variable of oauthVars) {
151
+ if (variable.oauth_warning) {
152
+ console.log(chalk_1.default.yellow(` ⚠ ${variable.env_name}: ${variable.oauth_warning}`));
153
+ }
154
+ }
155
+ return;
156
+ }
157
+ // Full pull: Build .env file content
75
158
  const lines = [];
76
159
  lines.push(`# Environment variables for ${projectName} (${environment})`);
77
160
  lines.push(`# Generated by solidactions env:pull on ${new Date().toISOString()}`);
@@ -80,12 +163,22 @@ async function envPull(projectName, options = {}) {
80
163
  let secretCount = 0;
81
164
  for (const variable of variables) {
82
165
  const key = variable.env_name;
83
- const value = variable.value;
166
+ const value = variable.resolved_value ?? variable.value;
84
167
  if (value === null || value === undefined) {
85
168
  // Skip variables with no value
86
169
  lines.push(`# ${key}= (no value configured)`);
87
170
  continue;
88
171
  }
172
+ // Add OAuth expiry comment above OAuth-sourced variables
173
+ if (variable.source_type === 'oauth_connection') {
174
+ const connName = variable.oauth_connection_name || 'OAuth';
175
+ if (variable.token_expires_at) {
176
+ lines.push(`# OAuth: ${connName} (expires ${variable.token_expires_at})`);
177
+ }
178
+ else {
179
+ lines.push(`# OAuth: ${connName} (short-lived, re-pull to refresh)`);
180
+ }
181
+ }
89
182
  // Quote values that contain special characters
90
183
  let formattedValue = value;
91
184
  if (typeof value === 'string' && (value.includes(' ') ||
@@ -110,6 +203,12 @@ async function envPull(projectName, options = {}) {
110
203
  if (secretCount > 0) {
111
204
  console.log(chalk_1.default.yellow(` (includes ${secretCount} secret value${secretCount > 1 ? 's' : ''})`));
112
205
  }
206
+ // Print any OAuth warnings
207
+ for (const variable of variables) {
208
+ if (variable.oauth_warning) {
209
+ console.log(chalk_1.default.yellow(` ⚠ ${variable.env_name}: ${variable.oauth_warning}`));
210
+ }
211
+ }
113
212
  }
114
213
  catch (error) {
115
214
  if (error.response) {
package/dist/index.js CHANGED
@@ -169,6 +169,7 @@ program
169
169
  .option('-e, --env <environment>', 'Environment (production/staging/dev)', 'dev')
170
170
  .option('-o, --output <file>', 'Output file path (defaults to .env or .env.{environment})')
171
171
  .option('-y, --yes', 'Skip confirmation for secrets')
172
+ .option('--update-oauth', 'Only pull OAuth tokens and merge into existing .env file')
172
173
  .action((project, options) => {
173
174
  (0, env_pull_1.envPull)(project, options);
174
175
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solidactions/cli",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
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",