@signageos/cli 2.7.1 → 2.9.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 (43) hide show
  1. package/README.md +12 -6
  2. package/dist/Applet/Build/appletBuildCommand.js +2 -0
  3. package/dist/Applet/Generate/appletGenerateCommand.js +5 -1
  4. package/dist/Applet/Start/appletStartCommand.js +3 -1
  5. package/dist/Applet/Test/Upload/appletTestRunCommand.js +9 -5
  6. package/dist/Applet/Test/Upload/appletTestUploadCommand.js +5 -3
  7. package/dist/Applet/Upload/appletUploadCommand.d.ts +15 -1
  8. package/dist/Applet/Upload/appletUploadCommand.js +82 -13
  9. package/dist/Applet/Upload/appletUploadCommandHelper.js +3 -3
  10. package/dist/Applet/Upload/appletUploadFacade.js +124 -52
  11. package/dist/Applet/Upload/appletUploadFacadeHelper.d.ts +1 -1
  12. package/dist/Applet/Upload/appletUploadFacadeHelper.js +3 -3
  13. package/dist/Applet/appletErrors.d.ts +3 -0
  14. package/dist/Applet/appletErrors.js +8 -1
  15. package/dist/Applet/appletFacade.js +9 -0
  16. package/dist/Applet/appletValidation.d.ts +17 -0
  17. package/dist/Applet/appletValidation.js +62 -0
  18. package/dist/Auth/loginCommand.js +30 -4
  19. package/dist/Command/commandProcessor.js +5 -0
  20. package/dist/Command/globalArgs.d.ts +21 -0
  21. package/dist/Command/globalArgs.js +30 -0
  22. package/dist/CommandLine/progressBarFactory.js +51 -10
  23. package/dist/CustomScript/Upload/customScriptUploadCommand.js +2 -2
  24. package/dist/Device/Content/setContentCommand.js +2 -2
  25. package/dist/Device/deviceFacade.js +8 -0
  26. package/dist/Emulator/emulatorFacade.js +7 -2
  27. package/dist/Emulator/emulatorFactory.js +18 -0
  28. package/dist/Firmware/Upload/firmwareUploadCommand.js +7 -10
  29. package/dist/Firmware/Upload/firmwareUploadFacade.js +15 -6
  30. package/dist/Organization/organizationFacade.d.ts +1 -1
  31. package/dist/Organization/organizationFacade.js +50 -13
  32. package/dist/Plugin/Upload/pluginUploadCommand.js +2 -1
  33. package/dist/RunControl/runControlHelper.d.ts +7 -1
  34. package/dist/RunControl/runControlHelper.js +19 -1
  35. package/dist/Runner/Upload/runnerUploadCommand.js +2 -1
  36. package/dist/Timing/List/timingListCommand.js +1 -3
  37. package/dist/helper.d.ts +19 -0
  38. package/dist/helper.js +45 -3
  39. package/dist/parameters.d.ts +0 -1
  40. package/dist/parameters.js +3 -6
  41. package/docs/applet/upload/index.md +15 -1
  42. package/docs/index.md +13 -0
  43. package/package.json +11 -7
@@ -31,26 +31,59 @@ exports.NO_DEFAULT_ORGANIZATION_OPTION = {
31
31
  description: 'Prevent using the defaultOrganizationUid from ~/.sosrc',
32
32
  };
33
33
  exports.ORGANIZATION_OPTIONS = [exports.ORGANIZATION_UID_OPTION, exports.NO_DEFAULT_ORGANIZATION_OPTION];
34
- function getOrganizationUidOrDefaultOrSelect(options) {
35
- return __awaiter(this, void 0, void 0, function* () {
34
+ function getOrganizationUidOrDefaultOrSelect(options_1) {
35
+ return __awaiter(this, arguments, void 0, function* (options, skipPrompts = false) {
36
36
  const config = yield (0, runControlHelper_1.loadConfig)();
37
37
  let organizationUid = options['organization-uid'];
38
38
  if (!organizationUid && !options['no-default-organization']) {
39
39
  organizationUid = config.defaultOrganizationUid;
40
40
  }
41
41
  if (!organizationUid) {
42
- organizationUid = yield selectOrganizationUid(options);
43
- if (organizationUid && !options['no-default-organization']) {
44
- const response = yield (0, prompts_1.default)({
45
- type: 'confirm',
46
- name: 'setDefault',
47
- message: `Do you want to set the organization as a default for current profile?`,
48
- initial: false,
49
- });
50
- if (response.setDefault) {
51
- yield (0, runControlHelper_1.updateConfig)({
52
- defaultOrganizationUid: organizationUid,
42
+ // If skipPrompts is true (e.g., --yes flag), try to auto-select
43
+ if (skipPrompts) {
44
+ const organizations = yield getOrganizations();
45
+ if (organizations.length === 0) {
46
+ throw new Error('No organizations available. Please ensure you have access to at least one organization.');
47
+ }
48
+ if (organizations.length === 1) {
49
+ // Auto-select the only available organization
50
+ const org = organizations[0]; // Safe: we just checked length === 1
51
+ organizationUid = org.uid;
52
+ console.info(chalk_1.default.yellow(`Auto-selected organization: ${org.title} (${org.name}, ${org.uid})`));
53
+ // Set as default to avoid prompts in future
54
+ if (!options['no-default-organization']) {
55
+ yield (0, runControlHelper_1.updateConfig)({
56
+ defaultOrganizationUid: organizationUid,
57
+ });
58
+ console.info(chalk_1.default.green('Organization has been set as default for current profile.'));
59
+ }
60
+ }
61
+ else {
62
+ // Multiple organizations available - cannot auto-select safely
63
+ throw new Error(`Cannot auto-select organization: Multiple organizations available (${organizations.length} found).\n` +
64
+ `Please specify one of the following:\n` +
65
+ ` 1. Use --organization-uid <uid> flag\n` +
66
+ ` 2. Set default organization: sos organization set-default\n` +
67
+ ` 3. Remove --yes flag for interactive selection\n\n` +
68
+ `Available organizations:\n` +
69
+ organizations.map((org) => ` - ${org.title} (${org.name}, ${org.uid})`).join('\n'));
70
+ }
71
+ }
72
+ else {
73
+ // Interactive mode - prompt user to select
74
+ organizationUid = yield selectOrganizationUid(options);
75
+ if (organizationUid && !options['no-default-organization']) {
76
+ const response = yield (0, prompts_1.default)({
77
+ type: 'confirm',
78
+ name: 'setDefault',
79
+ message: `Do you want to set the organization as a default for current profile?`,
80
+ initial: false,
53
81
  });
82
+ if (response.setDefault) {
83
+ yield (0, runControlHelper_1.updateConfig)({
84
+ defaultOrganizationUid: organizationUid,
85
+ });
86
+ }
54
87
  }
55
88
  }
56
89
  }
@@ -70,7 +103,11 @@ function selectOrganizationUid(options) {
70
103
  title: `${org.title} (${org.name}, ${org.uid})`,
71
104
  value: org.uid,
72
105
  })),
106
+ suggest: helper_1.autocompleteSuggest,
73
107
  });
108
+ if (!response.organizationUid) {
109
+ throw new Error('Organization selection was cancelled');
110
+ }
74
111
  Debug('Organization selected', response.organizationUid);
75
112
  organizationUid = response.organizationUid;
76
113
  }
@@ -60,7 +60,8 @@ exports.pluginUpload = (0, commandDefinition_1.createCommandDefinition)({
60
60
  run(options) {
61
61
  return __awaiter(this, void 0, void 0, function* () {
62
62
  const currentDirectory = process.cwd();
63
- const organizationUid = yield (0, organizationFacade_1.getOrganizationUidOrDefaultOrSelect)(options);
63
+ const skipPrompts = options.yes;
64
+ const organizationUid = yield (0, organizationFacade_1.getOrganizationUidOrDefaultOrSelect)(options, skipPrompts);
64
65
  const organization = yield (0, organizationFacade_1.getOrganization)(organizationUid);
65
66
  const restApi = yield (0, helper_1.createOrganizationRestApi)(organization);
66
67
  const config = yield (0, customScriptFacade_1.getConfig)(currentDirectory);
@@ -1,6 +1,12 @@
1
1
  import { IConfig } from '@signageos/sdk/dist/SosHelper/sosControlHelper';
2
2
  /** The same as loadConfig in SDK, but respect CLI --profile argument */
3
- export declare function loadConfig(): Promise<IConfig & Required<Pick<IConfig, "apiUrl">>>;
3
+ export declare function loadConfig(): Promise<{
4
+ apiUrl: string;
5
+ identification?: string;
6
+ apiSecurityToken?: string;
7
+ defaultOrganizationUid?: string;
8
+ emulatorUid?: string;
9
+ }>;
4
10
  /** The same as saveConfig in SDK, but respect CLI --profile argument */
5
11
  export declare function saveConfig(newConfig: IConfig): Promise<void>;
6
12
  /** The same as updateConfig in SDK, but respect CLI --profile argument */
@@ -18,7 +18,25 @@ const globalArgs_1 = require("../Command/globalArgs");
18
18
  function loadConfig() {
19
19
  return __awaiter(this, void 0, void 0, function* () {
20
20
  const profile = (0, globalArgs_1.getGlobalProfile)();
21
- return yield (0, sosControlHelper_1.loadConfig)({ profile });
21
+ const config = yield (0, sosControlHelper_1.loadConfig)({ profile });
22
+ // Override with environment variables if they exist.
23
+ // When --profile is explicitly given, skip the SOS_API_URL override so the
24
+ // profile's own apiUrl is used instead of the ambient environment variable.
25
+ const envOverride = {};
26
+ if (process.env.SOS_API_IDENTIFICATION) {
27
+ envOverride.identification = process.env.SOS_API_IDENTIFICATION;
28
+ }
29
+ if (process.env.SOS_API_SECURITY_TOKEN) {
30
+ envOverride.apiSecurityToken = process.env.SOS_API_SECURITY_TOKEN;
31
+ }
32
+ if (process.env.SOS_ORGANIZATION_UID) {
33
+ envOverride.defaultOrganizationUid = process.env.SOS_ORGANIZATION_UID;
34
+ }
35
+ if (process.env.SOS_API_URL && !profile) {
36
+ envOverride.apiUrl = process.env.SOS_API_URL;
37
+ }
38
+ const finalConfig = Object.assign(Object.assign({}, config), envOverride);
39
+ return finalConfig;
22
40
  });
23
41
  }
24
42
  /** The same as saveConfig in SDK, but respect CLI --profile argument */
@@ -60,7 +60,8 @@ exports.runnerUpload = (0, commandDefinition_1.createCommandDefinition)({
60
60
  run(options) {
61
61
  return __awaiter(this, void 0, void 0, function* () {
62
62
  const currentDirectory = process.cwd();
63
- const organizationUid = yield (0, organizationFacade_1.getOrganizationUidOrDefaultOrSelect)(options);
63
+ const skipPrompts = options.yes;
64
+ const organizationUid = yield (0, organizationFacade_1.getOrganizationUidOrDefaultOrSelect)(options, skipPrompts);
64
65
  const organization = yield (0, organizationFacade_1.getOrganization)(organizationUid);
65
66
  const restApi = yield (0, helper_1.createOrganizationRestApi)(organization);
66
67
  const config = yield (0, customScriptFacade_1.getConfig)(currentDirectory);
@@ -57,9 +57,7 @@ exports.timingList = (0, commandDefinition_1.createCommandDefinition)({
57
57
  const organization = yield (0, organizationFacade_1.getOrganization)(organizationUid);
58
58
  const restApi = yield (0, helper_1.createOrganizationRestApi)(organization);
59
59
  const deviceUid = yield (0, deviceFacade_1.getDeviceUid)(restApi, options);
60
- const timings = yield restApi.timing.getList({
61
- deviceUid,
62
- });
60
+ const timings = yield restApi.timing.getList({ deviceUid });
63
61
  console.info(chalk_1.default.yellow(JSON.stringify(timings, undefined, 2)));
64
62
  });
65
63
  },
package/dist/helper.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import prompts from 'prompts';
1
2
  import RestApi from '@signageos/sdk/dist/RestApi/RestApi';
2
3
  import { ApiVersions } from '@signageos/sdk/dist/RestApi/apiVersions';
3
4
  import { IConfig } from '@signageos/sdk/dist/SosHelper/sosControlHelper';
@@ -32,4 +33,22 @@ export declare function postResource(options: IOptions, path: string, query?: an
32
33
  export declare function putResource(options: IOptions, path: string, query?: any, data?: any): Promise<Response>;
33
34
  export declare function deleteResource(options: IOptions, path: string): Promise<Response>;
34
35
  export declare function deserializeJSON(_key: string, value: any): any;
36
+ export declare function getErrorMessageFromUnknownError(error: unknown): unknown;
37
+ /**
38
+ * Custom suggest function for autocomplete prompts.
39
+ * Searches in both the title (display text) and value (actual value) fields.
40
+ * This allows users to search by UID even when it's shown in parentheses in the title.
41
+ *
42
+ * @example
43
+ * ```typescript
44
+ * const response = await prompts({
45
+ * type: 'autocomplete',
46
+ * name: 'deviceUid',
47
+ * message: 'Select device',
48
+ * choices: devices.map(d => ({ title: `${d.name} (${d.uid})`, value: d.uid })),
49
+ * suggest: autocompleteSuggest,
50
+ * });
51
+ * ```
52
+ */
53
+ export declare function autocompleteSuggest<T extends prompts.Choice>(input: string, choices: T[]): Promise<T[]>;
35
54
  export {};
package/dist/helper.js CHANGED
@@ -25,6 +25,8 @@ exports.postResource = postResource;
25
25
  exports.putResource = putResource;
26
26
  exports.deleteResource = deleteResource;
27
27
  exports.deserializeJSON = deserializeJSON;
28
+ exports.getErrorMessageFromUnknownError = getErrorMessageFromUnknownError;
29
+ exports.autocompleteSuggest = autocompleteSuggest;
28
30
  const querystring_1 = require("querystring");
29
31
  const RestApi_1 = __importDefault(require("@signageos/sdk/dist/RestApi/RestApi"));
30
32
  const runControlHelper_1 = require("./RunControl/runControlHelper");
@@ -38,11 +40,18 @@ function loadApiUrl() {
38
40
  });
39
41
  }
40
42
  function getApiUrl(config) {
41
- const apiUrl = (0, globalArgs_1.getGlobalApiUrl)() || config.apiUrl;
42
- if (!apiUrl) {
43
+ // Precedence:
44
+ // 1. Explicit global CLI argument (--api-url)
45
+ // 2. Environment variable (SOS_API_URL) - for CI/CD and testing
46
+ // 3. Stored profile configuration (config.apiUrl from ~/.sosrc)
47
+ const cliUrl = (0, globalArgs_1.getGlobalApiUrl)();
48
+ const profileUrl = config.apiUrl;
49
+ const rawApiUrl = cliUrl || profileUrl;
50
+ if (!rawApiUrl) {
43
51
  throw new Error(`No API URL is defined. Please use --api-url or set SOS_API_URL environment variable.`);
44
52
  }
45
- return apiUrl;
53
+ // Normalize: remove trailing slashes to avoid double '//'
54
+ return rawApiUrl.replace(/\/+$/, '');
46
55
  }
47
56
  function createClientVersions() {
48
57
  return {
@@ -115,3 +124,36 @@ function deserializeJSON(_key, value) {
115
124
  }
116
125
  return value;
117
126
  }
127
+ function getErrorMessageFromUnknownError(error) {
128
+ if (error) {
129
+ if (typeof error === 'object' && 'message' in error) {
130
+ return error.message;
131
+ }
132
+ else {
133
+ return `${error}`;
134
+ }
135
+ }
136
+ else {
137
+ return null;
138
+ }
139
+ }
140
+ /**
141
+ * Custom suggest function for autocomplete prompts.
142
+ * Searches in both the title (display text) and value (actual value) fields.
143
+ * This allows users to search by UID even when it's shown in parentheses in the title.
144
+ *
145
+ * @example
146
+ * ```typescript
147
+ * const response = await prompts({
148
+ * type: 'autocomplete',
149
+ * name: 'deviceUid',
150
+ * message: 'Select device',
151
+ * choices: devices.map(d => ({ title: `${d.name} (${d.uid})`, value: d.uid })),
152
+ * suggest: autocompleteSuggest,
153
+ * });
154
+ * ```
155
+ */
156
+ function autocompleteSuggest(input, choices) {
157
+ const searchTerm = input.toLowerCase();
158
+ return Promise.resolve(choices.filter((choice) => { var _a, _b; return ((_a = choice.title) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes(searchTerm)) || ((_b = choice.value) === null || _b === void 0 ? void 0 : _b.toLowerCase().includes(searchTerm)); }));
159
+ }
@@ -4,7 +4,6 @@ export declare const parameters: {
4
4
  version: any;
5
5
  profile: string | undefined;
6
6
  apiUrl: string;
7
- boxHost: string;
8
7
  forwardServerUrl: string | undefined;
9
8
  applet: {
10
9
  uid: string | undefined;
@@ -7,7 +7,9 @@ const dotenv = require('dotenv');
7
7
  const packageConfig = require('../package.json');
8
8
  const environment = process.env.NODE_ENV || 'dev';
9
9
  const rootPath = path.normalize(__dirname + '/..');
10
- dotenv.config({ path: path.join(rootPath, '.env') });
10
+ // Load environment-specific .env file
11
+ const envFile = environment === 'test' ? '.env.test' : '.env';
12
+ dotenv.config({ path: path.join(rootPath, envFile) });
11
13
  const configurableEnvVars = [
12
14
  'SOS_PROFILE',
13
15
  'SOS_API_IDENTIFICATION',
@@ -23,20 +25,15 @@ for (const envVar of configurableEnvVars) {
23
25
  }
24
26
  }
25
27
  const apiUrl = process.env.SOS_API_URL;
26
- const boxHost = process.env.SOS_BOX_HOST;
27
28
  if (!apiUrl) {
28
29
  throw new Error(`Environment variable SOS_API_URL is required`);
29
30
  }
30
- if (!boxHost) {
31
- throw new Error(`Environment variable SOS_BOX_HOST is required`);
32
- }
33
31
  exports.parameters = {
34
32
  environment,
35
33
  name: packageConfig.name,
36
34
  version: packageConfig.version,
37
35
  profile: process.env.SOS_PROFILE,
38
36
  apiUrl,
39
- boxHost,
40
37
  forwardServerUrl: process.env.SOS_FORWARD_SERVER_URL,
41
38
  applet: {
42
39
  uid: process.env.SOS_APPLET_UID,
@@ -51,14 +51,28 @@ sos applet upload --entry-file-path src/main.js
51
51
  # Upload with organization override
52
52
  sos applet upload --organization-uid abc123def456
53
53
 
54
- # Skip confirmation prompts
54
+ # Skip confirmation prompts (auto-selects if only 1 org available)
55
55
  sos applet upload --yes
56
56
 
57
+ # CI/CD: Non-interactive upload with specific organization
58
+ sos applet upload --yes --organization-uid abc123def456
59
+
57
60
  # Verbose output with detailed file information
58
61
  sos applet upload --verbose
59
62
 
60
63
  # Update package.json with new applet UID
61
64
  sos applet upload --update-package-config
65
+
66
+ # Complete CI/CD example
67
+ sos applet upload --yes --organization-uid abc123def456 --update-package-config
68
+ ```
69
+
70
+ **Note for CI/CD and --yes flag:**
71
+ When using `--yes` with multiple organizations:
72
+ - If you have only 1 organization: it will be auto-selected
73
+ - If you have multiple organizations: you MUST specify `--organization-uid`
74
+ - Alternative: Set a default organization with `sos organization set-default`
75
+
62
76
  ```
63
77
 
64
78
  ## Advanced Usage
package/docs/index.md CHANGED
@@ -32,6 +32,19 @@ npm install -g @signageos/cli
32
32
  2. **Generate a new applet**: `sos applet generate --name my-applet`
33
33
  3. **Start development**: `cd my-applet && sos applet start`
34
34
 
35
+ ### Local Configuration
36
+
37
+ You can create a `sos.config.local.json` file in your applet directory to provide configuration values during local development:
38
+
39
+ ```json
40
+ {
41
+ "myConfigKey": "myConfigValue",
42
+ "anotherSetting": true
43
+ }
44
+ ```
45
+
46
+ This configuration is automatically loaded when running `sos applet start` and passed to your applet, making it easy to test different configuration scenarios without deploying to a device.
47
+
35
48
  ## Usage
36
49
 
37
50
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signageos/cli",
3
- "version": "2.7.1",
3
+ "version": "2.9.0",
4
4
  "main": "./dist/index.js",
5
5
  "author": "signageOS.io <dev@signageos.io>",
6
6
  "files": [
@@ -21,13 +21,17 @@
21
21
  "clean-build": "npm run clean && npm run build",
22
22
  "watch": "tsc --watch",
23
23
  "check-types": "tsc --noEmit",
24
- "test": "env NODE_ENV=test mocha",
25
- "test:coverage": "c8 npm run test",
26
- "test:windows": "cross-env NODE_ENV=test mocha",
27
24
  "lint": "eslint",
28
25
  "lint:fix": "eslint --fix",
29
26
  "lint:prettier": "prettier \"(src|tests|tools)/**/*.+(ts|tsx|json|js)\" --check",
30
27
  "lint:prettier:fix": "prettier \"(src|tests|tools)/**/*.+(ts|tsx|json|js)\" --write",
28
+ "test": "npm run test:unit && npm run test:integration",
29
+ "test:coverage": "c8 npm run test:all",
30
+ "test:unit": "env NODE_ENV=test mocha",
31
+ "test:unit:windows": "cross-env NODE_ENV=test mocha",
32
+ "test:integration": "npm run clean-build && env NODE_ENV=test mocha --config .mocharc.integration.json",
33
+ "test:integration:windows": "npm run build && cross-env NODE_ENV=test mocha --config .mocharc.integration.json",
34
+ "test:integration:generate": "env NODE_ENV=test mocha --config .mocharc.integration.json --grep appletGenerateCommand",
31
35
  "test:node-versions": "./tests/integration/test-node-versions.sh",
32
36
  "test:integration:node-versions": "env NODE_ENV=test mocha --config .mocharc.node-versions.json",
33
37
  "generate:dummy-applet": "node ./tools/generate-dummy-applet.mjs",
@@ -49,7 +53,7 @@
49
53
  },
50
54
  "devDependencies": {
51
55
  "@istanbuljs/nyc-config-typescript": "1.0.2",
52
- "@signageos/codestyle": "2.0.3",
56
+ "@signageos/codestyle": "2.1.0",
53
57
  "@types/archiver": "6.0.3",
54
58
  "@types/child-process-promise": "2.2.6",
55
59
  "@types/cli-progress": "3.11.6",
@@ -81,7 +85,7 @@
81
85
  },
82
86
  "dependencies": {
83
87
  "@signageos/file": "2.0.1",
84
- "@signageos/sdk": "2.0.2",
88
+ "@signageos/sdk": "2.3.0",
85
89
  "archiver": "7.0.1",
86
90
  "chalk": "2.4.2",
87
91
  "child-process-promise": "2.1.3",
@@ -92,7 +96,7 @@
92
96
  "debug": "4.4.0",
93
97
  "dotenv": "16.5.0",
94
98
  "express": "5.1.0",
95
- "fs-extra": "11.3.0",
99
+ "fs-extra": "11.3.2",
96
100
  "internal-ip": "8.0.0",
97
101
  "markdown-table": "3.0.4",
98
102
  "mime": "2.4.4",