@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.
Files changed (44) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/commands/apps/builds/cancel.js +3 -3
  3. package/dist/commands/apps/builds/create.js +6 -6
  4. package/dist/commands/apps/builds/download.js +4 -4
  5. package/dist/commands/apps/builds/logs.js +3 -3
  6. package/dist/commands/apps/bundles/create.js +6 -6
  7. package/dist/commands/apps/bundles/delete.js +4 -4
  8. package/dist/commands/apps/bundles/delete.test.js +2 -2
  9. package/dist/commands/apps/bundles/update.js +3 -3
  10. package/dist/commands/apps/bundles/update.test.js +2 -2
  11. package/dist/commands/apps/channels/create.js +3 -3
  12. package/dist/commands/apps/channels/create.test.js +2 -2
  13. package/dist/commands/apps/channels/delete.js +14 -6
  14. package/dist/commands/apps/channels/delete.test.js +24 -9
  15. package/dist/commands/apps/channels/list.js +34 -2
  16. package/dist/commands/apps/channels/list.test.js +1 -1
  17. package/dist/commands/apps/channels/update.js +3 -3
  18. package/dist/commands/apps/channels/update.test.js +2 -2
  19. package/dist/commands/apps/create.js +3 -3
  20. package/dist/commands/apps/create.test.js +2 -2
  21. package/dist/commands/apps/delete.js +3 -3
  22. package/dist/commands/apps/delete.test.js +2 -2
  23. package/dist/commands/apps/deployments/cancel.js +3 -3
  24. package/dist/commands/apps/deployments/create.js +4 -4
  25. package/dist/commands/apps/deployments/logs.js +3 -3
  26. package/dist/commands/apps/devices/delete.js +4 -4
  27. package/dist/commands/apps/devices/delete.test.js +2 -2
  28. package/dist/commands/apps/environments/create.js +68 -0
  29. package/dist/commands/apps/environments/delete.js +87 -0
  30. package/dist/commands/apps/environments/list.js +69 -0
  31. package/dist/commands/apps/environments/set.js +126 -0
  32. package/dist/commands/apps/environments/unset.js +98 -0
  33. package/dist/commands/login.js +2 -2
  34. package/dist/commands/login.test.js +4 -4
  35. package/dist/commands/manifests/generate.js +2 -2
  36. package/dist/commands/manifests/generate.test.js +2 -2
  37. package/dist/commands/organizations/create.js +2 -2
  38. package/dist/commands/organizations/create.test.js +2 -2
  39. package/dist/index.js +5 -0
  40. package/dist/services/app-environments.js +67 -2
  41. package/dist/utils/app-environments.js +34 -0
  42. package/dist/utils/app-environments.ts.test.js +76 -0
  43. package/dist/utils/environment.js +20 -0
  44. 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 (!hasTTY) {
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('std-env', () => ({
15
- hasTTY: true,
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 { appId } = dto;
10
- const response = await this.httpClient.get(`/v1/apps/${appId}/environments`, {
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.10.1",
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
- "std-env": "3.9.0",
71
- "systeminformation": "5.25.11",
70
+ "systeminformation": "5.28.5",
72
71
  "zod": "4.0.17"
73
72
  },
74
73
  "devDependencies": {