@capawesome/cli 3.10.1 → 3.11.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/CHANGELOG.md +9 -0
- package/dist/commands/apps/builds/cancel.js +3 -3
- package/dist/commands/apps/builds/create.js +6 -6
- package/dist/commands/apps/builds/download.js +4 -4
- package/dist/commands/apps/builds/logs.js +3 -3
- package/dist/commands/apps/bundles/create.js +6 -6
- package/dist/commands/apps/bundles/delete.js +4 -4
- package/dist/commands/apps/bundles/delete.test.js +2 -2
- package/dist/commands/apps/bundles/update.js +3 -3
- package/dist/commands/apps/bundles/update.test.js +2 -2
- package/dist/commands/apps/channels/create.js +3 -3
- package/dist/commands/apps/channels/create.test.js +2 -2
- package/dist/commands/apps/channels/delete.js +14 -6
- package/dist/commands/apps/channels/delete.test.js +24 -9
- package/dist/commands/apps/channels/list.js +34 -2
- package/dist/commands/apps/channels/list.test.js +1 -1
- package/dist/commands/apps/channels/update.js +3 -3
- package/dist/commands/apps/channels/update.test.js +2 -2
- package/dist/commands/apps/create.js +3 -3
- package/dist/commands/apps/create.test.js +2 -2
- package/dist/commands/apps/delete.js +3 -3
- package/dist/commands/apps/delete.test.js +2 -2
- package/dist/commands/apps/deployments/cancel.js +3 -3
- package/dist/commands/apps/deployments/create.js +4 -4
- package/dist/commands/apps/deployments/logs.js +3 -3
- package/dist/commands/apps/devices/delete.js +4 -4
- package/dist/commands/apps/devices/delete.test.js +2 -2
- package/dist/commands/apps/environments/create.js +68 -0
- package/dist/commands/apps/environments/delete.js +87 -0
- package/dist/commands/apps/environments/list.js +69 -0
- package/dist/commands/apps/environments/set.js +126 -0
- package/dist/commands/apps/environments/unset.js +98 -0
- package/dist/commands/login.js +2 -2
- package/dist/commands/login.test.js +4 -4
- package/dist/commands/manifests/generate.js +2 -2
- package/dist/commands/manifests/generate.test.js +2 -2
- package/dist/commands/organizations/create.js +2 -2
- package/dist/commands/organizations/create.test.js +2 -2
- package/dist/index.js +5 -0
- package/dist/services/app-environments.js +67 -2
- package/dist/utils/app-environments.js +34 -0
- package/dist/utils/app-environments.ts.test.js +76 -0
- package/dist/utils/environment.js +20 -0
- package/package.json +2 -3
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import authorizationService from '../../services/authorization-service.js';
|
|
2
2
|
import organizationsService from '../../services/organizations.js';
|
|
3
|
+
import { isInteractive } from '../../utils/environment.js';
|
|
3
4
|
import { prompt } from '../../utils/prompt.js';
|
|
4
5
|
import { defineCommand, defineOptions } from '@robingenz/zli';
|
|
5
6
|
import consola from 'consola';
|
|
6
|
-
import { hasTTY } from 'std-env';
|
|
7
7
|
import { z } from 'zod';
|
|
8
8
|
export default defineCommand({
|
|
9
9
|
description: 'Create a new organization.',
|
|
@@ -17,7 +17,7 @@ export default defineCommand({
|
|
|
17
17
|
process.exit(1);
|
|
18
18
|
}
|
|
19
19
|
if (!name) {
|
|
20
|
-
if (!
|
|
20
|
+
if (!isInteractive()) {
|
|
21
21
|
consola.error('You must provide the organization name when running in non-interactive environment.');
|
|
22
22
|
process.exit(1);
|
|
23
23
|
}
|
|
@@ -11,8 +11,8 @@ vi.mock('@/utils/user-config.js');
|
|
|
11
11
|
vi.mock('@/utils/prompt.js');
|
|
12
12
|
vi.mock('@/services/authorization-service.js');
|
|
13
13
|
vi.mock('consola');
|
|
14
|
-
vi.mock('
|
|
15
|
-
|
|
14
|
+
vi.mock('@/utils/environment.js', () => ({
|
|
15
|
+
isInteractive: () => true,
|
|
16
16
|
}));
|
|
17
17
|
describe('organizations-create', () => {
|
|
18
18
|
const mockUserConfig = vi.mocked(userConfig);
|
package/dist/index.js
CHANGED
|
@@ -35,6 +35,11 @@ const config = defineConfig({
|
|
|
35
35
|
'apps:channels:get': await import('./commands/apps/channels/get.js').then((mod) => mod.default),
|
|
36
36
|
'apps:channels:list': await import('./commands/apps/channels/list.js').then((mod) => mod.default),
|
|
37
37
|
'apps:channels:update': await import('./commands/apps/channels/update.js').then((mod) => mod.default),
|
|
38
|
+
'apps:environments:create': await import('./commands/apps/environments/create.js').then((mod) => mod.default),
|
|
39
|
+
'apps:environments:delete': await import('./commands/apps/environments/delete.js').then((mod) => mod.default),
|
|
40
|
+
'apps:environments:list': await import('./commands/apps/environments/list.js').then((mod) => mod.default),
|
|
41
|
+
'apps:environments:set': await import('./commands/apps/environments/set.js').then((mod) => mod.default),
|
|
42
|
+
'apps:environments:unset': await import('./commands/apps/environments/unset.js').then((mod) => mod.default),
|
|
38
43
|
'apps:deployments:create': await import('./commands/apps/deployments/create.js').then((mod) => mod.default),
|
|
39
44
|
'apps:deployments:cancel': await import('./commands/apps/deployments/cancel.js').then((mod) => mod.default),
|
|
40
45
|
'apps:deployments:logs': await import('./commands/apps/deployments/logs.js').then((mod) => mod.default),
|
|
@@ -5,15 +5,80 @@ class AppEnvironmentsServiceImpl {
|
|
|
5
5
|
constructor(httpClient) {
|
|
6
6
|
this.httpClient = httpClient;
|
|
7
7
|
}
|
|
8
|
+
async create(dto) {
|
|
9
|
+
const response = await this.httpClient.post(`/v1/apps/${dto.appId}/environments`, dto, {
|
|
10
|
+
headers: {
|
|
11
|
+
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
return response.data;
|
|
15
|
+
}
|
|
16
|
+
async delete(dto) {
|
|
17
|
+
if (dto.id) {
|
|
18
|
+
await this.httpClient.delete(`/v1/apps/${dto.appId}/environments/${dto.id}`, {
|
|
19
|
+
headers: {
|
|
20
|
+
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
else if (dto.name) {
|
|
25
|
+
await this.httpClient.delete(`/v1/apps/${dto.appId}/environments`, {
|
|
26
|
+
headers: {
|
|
27
|
+
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
28
|
+
},
|
|
29
|
+
params: {
|
|
30
|
+
name: dto.name,
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
8
35
|
async findAll(dto) {
|
|
9
|
-
const
|
|
10
|
-
|
|
36
|
+
const queryParams = new URLSearchParams();
|
|
37
|
+
if (dto.limit) {
|
|
38
|
+
queryParams.append('limit', dto.limit.toString());
|
|
39
|
+
}
|
|
40
|
+
if (dto.offset) {
|
|
41
|
+
queryParams.append('offset', dto.offset.toString());
|
|
42
|
+
}
|
|
43
|
+
const queryString = queryParams.toString();
|
|
44
|
+
const url = queryString
|
|
45
|
+
? `/v1/apps/${dto.appId}/environments?${queryString}`
|
|
46
|
+
: `/v1/apps/${dto.appId}/environments`;
|
|
47
|
+
const response = await this.httpClient.get(url, {
|
|
11
48
|
headers: {
|
|
12
49
|
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
13
50
|
},
|
|
14
51
|
});
|
|
15
52
|
return response.data;
|
|
16
53
|
}
|
|
54
|
+
async setVariables(dto) {
|
|
55
|
+
await this.httpClient.post(`/v1/apps/${dto.appId}/environments/${dto.environmentId}/variables/set`, dto.variables, {
|
|
56
|
+
headers: {
|
|
57
|
+
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
async setSecrets(dto) {
|
|
62
|
+
await this.httpClient.post(`/v1/apps/${dto.appId}/environments/${dto.environmentId}/secrets/set`, dto.secrets, {
|
|
63
|
+
headers: {
|
|
64
|
+
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
async unsetVariables(dto) {
|
|
69
|
+
await this.httpClient.post(`/v1/apps/${dto.appId}/environments/${dto.environmentId}/variables/unset`, dto.keys, {
|
|
70
|
+
headers: {
|
|
71
|
+
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
async unsetSecrets(dto) {
|
|
76
|
+
await this.httpClient.post(`/v1/apps/${dto.appId}/environments/${dto.environmentId}/secrets/unset`, dto.keys, {
|
|
77
|
+
headers: {
|
|
78
|
+
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
}
|
|
17
82
|
}
|
|
18
83
|
const appEnvironmentsService = new AppEnvironmentsServiceImpl(httpClient);
|
|
19
84
|
export default appEnvironmentsService;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse key-value pairs from content string.
|
|
3
|
+
*
|
|
4
|
+
* Format: KEY=value (one per line)
|
|
5
|
+
* - Empty lines are ignored
|
|
6
|
+
* - Lines starting with # are ignored (comments)
|
|
7
|
+
* - Lines without = are skipped
|
|
8
|
+
* - Keys and values are trimmed
|
|
9
|
+
* - Values can contain = characters
|
|
10
|
+
* - Lines with empty keys are skipped
|
|
11
|
+
*
|
|
12
|
+
* @param content - Content string to parse
|
|
13
|
+
* @returns Array of key-value pairs
|
|
14
|
+
*/
|
|
15
|
+
export function parseKeyValuePairs(content) {
|
|
16
|
+
const lines = content.split('\n');
|
|
17
|
+
const pairs = [];
|
|
18
|
+
for (const line of lines) {
|
|
19
|
+
const trimmed = line.trim();
|
|
20
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const separatorIndex = trimmed.indexOf('=');
|
|
24
|
+
if (separatorIndex === -1) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const key = trimmed.slice(0, separatorIndex).trim();
|
|
28
|
+
const value = trimmed.slice(separatorIndex + 1).trim();
|
|
29
|
+
if (key) {
|
|
30
|
+
pairs.push({ key, value });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return pairs;
|
|
34
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { parseKeyValuePairs } from './app-environments.js';
|
|
3
|
+
describe('parseKeyValuePairs', () => {
|
|
4
|
+
it('should parse valid key-value pairs', () => {
|
|
5
|
+
const result = parseKeyValuePairs('KEY1=value1\nKEY2=value2');
|
|
6
|
+
expect(result).toEqual([
|
|
7
|
+
{ key: 'KEY1', value: 'value1' },
|
|
8
|
+
{ key: 'KEY2', value: 'value2' },
|
|
9
|
+
]);
|
|
10
|
+
});
|
|
11
|
+
it('should handle empty lines', () => {
|
|
12
|
+
const result = parseKeyValuePairs('KEY1=value1\n\nKEY2=value2\n\n');
|
|
13
|
+
expect(result).toEqual([
|
|
14
|
+
{ key: 'KEY1', value: 'value1' },
|
|
15
|
+
{ key: 'KEY2', value: 'value2' },
|
|
16
|
+
]);
|
|
17
|
+
});
|
|
18
|
+
it('should ignore comments', () => {
|
|
19
|
+
const result = parseKeyValuePairs('# Comment\nKEY1=value1\n# Another comment\nKEY2=value2');
|
|
20
|
+
expect(result).toEqual([
|
|
21
|
+
{ key: 'KEY1', value: 'value1' },
|
|
22
|
+
{ key: 'KEY2', value: 'value2' },
|
|
23
|
+
]);
|
|
24
|
+
});
|
|
25
|
+
it('should handle values with = characters', () => {
|
|
26
|
+
const result = parseKeyValuePairs('KEY1=value=with=equals\nKEY2=a=b');
|
|
27
|
+
expect(result).toEqual([
|
|
28
|
+
{ key: 'KEY1', value: 'value=with=equals' },
|
|
29
|
+
{ key: 'KEY2', value: 'a=b' },
|
|
30
|
+
]);
|
|
31
|
+
});
|
|
32
|
+
it('should trim whitespace from keys and values', () => {
|
|
33
|
+
const result = parseKeyValuePairs(' KEY1 = value1 \n KEY2 = value2 ');
|
|
34
|
+
expect(result).toEqual([
|
|
35
|
+
{ key: 'KEY1', value: 'value1' },
|
|
36
|
+
{ key: 'KEY2', value: 'value2' },
|
|
37
|
+
]);
|
|
38
|
+
});
|
|
39
|
+
it('should skip lines without = separator', () => {
|
|
40
|
+
const result = parseKeyValuePairs('KEY1=value1\nINVALID_LINE\nKEY2=value2');
|
|
41
|
+
expect(result).toEqual([
|
|
42
|
+
{ key: 'KEY1', value: 'value1' },
|
|
43
|
+
{ key: 'KEY2', value: 'value2' },
|
|
44
|
+
]);
|
|
45
|
+
});
|
|
46
|
+
it('should skip lines with empty keys', () => {
|
|
47
|
+
const result = parseKeyValuePairs('KEY1=value1\n=value2\nKEY3=value3');
|
|
48
|
+
expect(result).toEqual([
|
|
49
|
+
{ key: 'KEY1', value: 'value1' },
|
|
50
|
+
{ key: 'KEY3', value: 'value3' },
|
|
51
|
+
]);
|
|
52
|
+
});
|
|
53
|
+
it('should skip lines with empty values', () => {
|
|
54
|
+
const result = parseKeyValuePairs('KEY1=value1\nKEY2=\nKEY3=value3');
|
|
55
|
+
expect(result).toEqual([
|
|
56
|
+
{ key: 'KEY1', value: 'value1' },
|
|
57
|
+
{ key: 'KEY3', value: 'value3' },
|
|
58
|
+
]);
|
|
59
|
+
});
|
|
60
|
+
it('should handle empty content', () => {
|
|
61
|
+
const result = parseKeyValuePairs('');
|
|
62
|
+
expect(result).toEqual([]);
|
|
63
|
+
});
|
|
64
|
+
it('should handle content with only comments', () => {
|
|
65
|
+
const result = parseKeyValuePairs('# Comment 1\n# Comment 2');
|
|
66
|
+
expect(result).toEqual([]);
|
|
67
|
+
});
|
|
68
|
+
it('should handle mixed valid and invalid lines', () => {
|
|
69
|
+
const result = parseKeyValuePairs('KEY1=value1\nINVALID\n=nokey\nKEY2=\nKEY3=value3\n# comment\nKEY4=value4');
|
|
70
|
+
expect(result).toEqual([
|
|
71
|
+
{ key: 'KEY1', value: 'value1' },
|
|
72
|
+
{ key: 'KEY3', value: 'value3' },
|
|
73
|
+
{ key: 'KEY4', value: 'value4' },
|
|
74
|
+
]);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detects if the current environment supports interactive prompts.
|
|
3
|
+
*
|
|
4
|
+
* For interactive prompts to work, we need:
|
|
5
|
+
* 1. stdin to be a TTY (to read user input)
|
|
6
|
+
* 2. stdout to be a TTY (to display the prompt)
|
|
7
|
+
* 3. Not running in a CI environment
|
|
8
|
+
*
|
|
9
|
+
* This is more robust than just checking stdout.isTTY (like std-env's hasTTY),
|
|
10
|
+
* because interactive prompts require BOTH input and output TTYs.
|
|
11
|
+
*/
|
|
12
|
+
export const isInteractive = () => {
|
|
13
|
+
// Check if both stdin AND stdout are TTYs
|
|
14
|
+
const hasInputTTY = Boolean(process.stdin?.isTTY);
|
|
15
|
+
const hasOutputTTY = Boolean(process.stdout?.isTTY);
|
|
16
|
+
// Check for CI environment
|
|
17
|
+
const isCI = Boolean(process.env.CI);
|
|
18
|
+
// Need BOTH input and output TTY, and not in CI
|
|
19
|
+
return hasInputTTY && hasOutputTTY && !isCI;
|
|
20
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capawesome/cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.11.0",
|
|
4
4
|
"description": "The Capawesome Cloud Command Line Interface (CLI) to manage Live Updates and more.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -67,8 +67,7 @@
|
|
|
67
67
|
"open": "10.2.0",
|
|
68
68
|
"rc9": "2.1.2",
|
|
69
69
|
"semver": "7.6.3",
|
|
70
|
-
"
|
|
71
|
-
"systeminformation": "5.25.11",
|
|
70
|
+
"systeminformation": "5.28.5",
|
|
72
71
|
"zod": "4.0.17"
|
|
73
72
|
},
|
|
74
73
|
"devDependencies": {
|