@signageos/cli 2.8.0 → 3.0.0-rc.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 (52) hide show
  1. package/README.md +0 -6
  2. package/dist/Applet/Build/appletBuildCommand.js +2 -0
  3. package/dist/Applet/Generate/appletGenerateCommand.js +51 -59
  4. package/dist/Applet/Start/appletStartCommand.js +3 -1
  5. package/dist/Applet/Test/Upload/appletTestRunCommand.js +11 -6
  6. package/dist/Applet/Test/Upload/appletTestUploadCommand.js +7 -4
  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 +4 -3
  13. package/dist/Applet/appletErrors.d.ts +3 -0
  14. package/dist/Applet/appletErrors.js +8 -1
  15. package/dist/Applet/appletFacade.js +13 -2
  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 +10 -1
  26. package/dist/Emulator/emulatorFacade.js +9 -2
  27. package/dist/Organization/organizationFacade.d.ts +1 -1
  28. package/dist/Organization/organizationFacade.js +50 -13
  29. package/dist/Plugin/Upload/pluginUploadCommand.js +2 -1
  30. package/dist/RunControl/runControlHelper.d.ts +7 -1
  31. package/dist/RunControl/runControlHelper.js +19 -1
  32. package/dist/Runner/Upload/runnerUploadCommand.js +2 -1
  33. package/dist/Timing/List/timingListCommand.js +1 -3
  34. package/dist/helper/paginationHelper.d.ts +7 -0
  35. package/dist/helper/paginationHelper.js +32 -0
  36. package/dist/helper.d.ts +18 -1
  37. package/dist/helper.js +31 -23
  38. package/dist/index.js +1 -2
  39. package/dist/parameters.d.ts +0 -1
  40. package/dist/parameters.js +3 -6
  41. package/docs/applet/generate/index.md +1 -1
  42. package/docs/applet/upload/index.md +15 -1
  43. package/package.json +12 -11
  44. package/.env +0 -3
  45. package/dist/Firmware/Upload/firmwareUploadCommand.d.ts +0 -90
  46. package/dist/Firmware/Upload/firmwareUploadCommand.js +0 -210
  47. package/dist/Firmware/Upload/firmwareUploadFacade.d.ts +0 -10
  48. package/dist/Firmware/Upload/firmwareUploadFacade.js +0 -90
  49. package/dist/Firmware/Upload/firmwareUploadHelper.d.ts +0 -1
  50. package/dist/Firmware/Upload/firmwareUploadHelper.js +0 -48
  51. package/dist/Firmware/firmwareCommand.d.ts +0 -76
  52. package/dist/Firmware/firmwareCommand.js +0 -42
@@ -36,23 +36,64 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.createProgressBar = createProgressBar;
37
37
  const cliProgress = __importStar(require("cli-progress"));
38
38
  function createProgressBar() {
39
- const progressBar = new cliProgress.Bar({
40
- format: '[{bar}] {name} {percentage}% | ETA: {eta}s | {value}/{total}',
39
+ // Create a MultiBar instance to manage multiple progress bars
40
+ const multiBar = new cliProgress.MultiBar({
41
+ format: '[{bar}] {name} {percentage}% | ETA: {eta}s | {valueKB}/{totalKB} kB',
42
+ forceRedraw: true, // Force redraw to ensure the display updates correctly
43
+ linewrap: false, // Disable line wrapping to keep the output clean
44
+ autopadding: true, // Automatically adjust padding for better alignment
45
+ etaBuffer: 5, // Reduced buffer for faster ETA calculation on small files
46
+ etaAsynchronousUpdate: false, // Disable asynchronous ETA updates to avoid NULL/Infinite states
41
47
  }, cliProgress.Presets.rect);
42
- let current = 0;
43
- let currentName = 'preparing';
48
+ // Keep track of all bars to clean up properly
49
+ const bars = new Map();
50
+ const barValues = new Map();
44
51
  return {
45
52
  init({ size, name }) {
46
- currentName = name;
47
- progressBar.start(size, current, { name });
53
+ // If a bar already exists with this name, stop and remove it
54
+ if (bars.has(name)) {
55
+ const existingBar = bars.get(name);
56
+ if (existingBar) {
57
+ existingBar.stop();
58
+ bars.delete(name);
59
+ barValues.delete(name);
60
+ }
61
+ }
62
+ // Create a new bar for this file
63
+ const totalKB = Math.round((size / 1024) * 100) / 100; // Round to 2 decimal places
64
+ const bar = multiBar.create(size, 0, {
65
+ name,
66
+ totalKB: totalKB,
67
+ valueKB: 0,
68
+ });
69
+ bars.set(name, bar);
70
+ barValues.set(name, { current: 0, total: size });
48
71
  },
49
72
  update({ add, name }) {
50
- current += add;
51
- currentName = name || currentName;
52
- progressBar.update(current + add, { name: currentName });
73
+ if (name && bars.has(name)) {
74
+ const bar = bars.get(name);
75
+ const values = barValues.get(name);
76
+ if (bar && values) {
77
+ // Update tracked values
78
+ values.current += add;
79
+ // Calculate kB values
80
+ const valueKB = Math.round((values.current / 1024) * 100) / 100;
81
+ const totalKB = Math.round((values.total / 1024) * 100) / 100;
82
+ // Update bar with new values
83
+ bar.update(values.current, {
84
+ name,
85
+ totalKB: totalKB,
86
+ valueKB: valueKB,
87
+ });
88
+ // Force ETA update
89
+ bar.updateETA();
90
+ }
91
+ }
53
92
  },
54
93
  end() {
55
- progressBar.stop();
94
+ multiBar.stop();
95
+ bars.clear();
96
+ barValues.clear();
56
97
  },
57
98
  };
58
99
  }
@@ -68,11 +68,11 @@ exports.customScriptUpload = (0, commandDefinition_1.createCommandDefinition)({
68
68
  run(options) {
69
69
  return __awaiter(this, void 0, void 0, function* () {
70
70
  const currentDirectory = process.cwd();
71
- const organizationUid = yield (0, organizationFacade_1.getOrganizationUidOrDefaultOrSelect)(options);
71
+ const skipConfirmation = options.yes;
72
+ const organizationUid = yield (0, organizationFacade_1.getOrganizationUidOrDefaultOrSelect)(options, skipConfirmation);
72
73
  const organization = yield (0, organizationFacade_1.getOrganization)(organizationUid);
73
74
  const restApi = yield (0, helper_1.createOrganizationRestApi)(organization);
74
75
  const config = yield (0, customScriptFacade_1.getConfig)(currentDirectory);
75
- const skipConfirmation = options.yes;
76
76
  const customScriptVersion = yield (0, customScriptFacade_1.ensureCustomScriptVersion)(restApi, config, skipConfirmation);
77
77
  for (const platform of Object.keys(config.platforms)) {
78
78
  const platformConfig = config.platforms[platform];
@@ -66,10 +66,10 @@ exports.setContent = (0, commandDefinition_1.createCommandDefinition)({
66
66
  commands: [],
67
67
  run(options) {
68
68
  return __awaiter(this, void 0, void 0, function* () {
69
- const organizationUid = yield (0, organizationFacade_1.getOrganizationUidOrDefaultOrSelect)(options);
69
+ const skipConfirmation = !!options.yes;
70
+ const organizationUid = yield (0, organizationFacade_1.getOrganizationUidOrDefaultOrSelect)(options, skipConfirmation);
70
71
  const organization = yield (0, organizationFacade_1.getOrganization)(organizationUid);
71
72
  const restApi = yield (0, helper_1.createOrganizationRestApi)(organization);
72
- const skipConfirmation = !!options.yes;
73
73
  const appletUid = yield (0, appletFacade_1.getAppletUid)(restApi, options, skipConfirmation);
74
74
  const appletVersion = yield (0, appletFacade_1.getAppletVersionFromApi)(restApi, appletUid, skipConfirmation);
75
75
  const deviceUid = yield (0, deviceFacade_1.getDeviceUid)(restApi, options, skipConfirmation);
@@ -22,6 +22,7 @@ const helper_1 = require("../helper");
22
22
  const IPowerAction_1 = require("@signageos/sdk/dist/RestApi/Device/PowerAction/IPowerAction");
23
23
  const apiVersions_1 = require("@signageos/sdk/dist/RestApi/apiVersions");
24
24
  const runControlHelper_1 = require("../RunControl/runControlHelper");
25
+ const paginationHelper_1 = require("../helper/paginationHelper");
25
26
  const Debug = (0, debug_1.default)('@signageos/cli:Device:facade');
26
27
  exports.typeMap = new Map([
27
28
  ['reboot', { name: 'Reboot Device', action: IPowerAction_1.DevicePowerAction.SystemReboot }],
@@ -43,7 +44,7 @@ function getDeviceUid(restApi_1, options_1) {
43
44
  throw new Error('Device UID is required. Please specify --device-uid argument.');
44
45
  }
45
46
  else {
46
- const devices = yield restApi.device.list();
47
+ const devices = yield (0, paginationHelper_1.getAllPages)(yield restApi.device.list());
47
48
  const response = yield (0, prompts_1.default)({
48
49
  type: 'autocomplete',
49
50
  name: 'deviceUid',
@@ -55,7 +56,11 @@ function getDeviceUid(restApi_1, options_1) {
55
56
  value: dev.uid,
56
57
  });
57
58
  }),
59
+ suggest: helper_1.autocompleteSuggest,
58
60
  });
61
+ if (!response.deviceUid) {
62
+ throw new Error('Device selection was cancelled');
63
+ }
59
64
  Debug('Device selected', response.deviceUid);
60
65
  deviceUid = response.deviceUid;
61
66
  }
@@ -78,7 +83,11 @@ function getActionType(options) {
78
83
  title: item[1].name,
79
84
  value: item[0],
80
85
  })),
86
+ suggest: helper_1.autocompleteSuggest,
81
87
  });
88
+ if (!response.type) {
89
+ throw new Error('Power action selection was cancelled');
90
+ }
82
91
  action = response.type;
83
92
  }
84
93
  if (!action) {
@@ -21,6 +21,7 @@ const AuthenticationError_1 = __importDefault(require("@signageos/sdk/dist/RestA
21
21
  const apiVersions_1 = require("@signageos/sdk/dist/RestApi/apiVersions");
22
22
  const helper_1 = require("../helper");
23
23
  const log_1 = require("@signageos/sdk/dist/Console/log");
24
+ const paginationHelper_1 = require("../helper/paginationHelper");
24
25
  const createRestApi = (config) => {
25
26
  var _a, _b;
26
27
  const options = {
@@ -37,7 +38,9 @@ const createRestApi = (config) => {
37
38
  function getListOfEmulators(restApi, organizationUid) {
38
39
  return __awaiter(this, void 0, void 0, function* () {
39
40
  try {
40
- return yield restApi.emulator.list({ organizationUid });
41
+ // Fetch all pages to ensure we get all emulators
42
+ const emulators = yield (0, paginationHelper_1.getAllPages)(yield restApi.emulator.list({ organizationUid }));
43
+ return emulators;
41
44
  }
42
45
  catch (e) {
43
46
  if (e instanceof AuthenticationError_1.default) {
@@ -84,14 +87,18 @@ function loadEmulatorOrCreateNewAndReturnUid(organizationUid) {
84
87
  }
85
88
  else if (listOfEmulatorsResponse.length > 1) {
86
89
  const selectedEmulator = yield (0, prompts_1.default)({
87
- type: 'select',
90
+ type: 'autocomplete',
88
91
  name: 'duid',
89
92
  message: 'Select emulator to use',
90
93
  choices: listOfEmulatorsResponse.map((emu) => ({
91
94
  title: `${emu.name} (${emu.duid})`,
92
95
  value: emu.duid,
93
96
  })),
97
+ suggest: helper_1.autocompleteSuggest,
94
98
  });
99
+ if (!selectedEmulator.duid) {
100
+ throw new Error('Emulator selection was cancelled');
101
+ }
95
102
  emulatorUid = selectedEmulator.duid;
96
103
  }
97
104
  else {
@@ -26,7 +26,7 @@ export declare const ORGANIZATION_OPTIONS: ({
26
26
  readonly type: BooleanConstructor;
27
27
  readonly description: "Prevent using the defaultOrganizationUid from ~/.sosrc";
28
28
  })[];
29
- export declare function getOrganizationUidOrDefaultOrSelect(options: CommandLineOptions<[typeof ORGANIZATION_UID_OPTION, typeof NO_DEFAULT_ORGANIZATION_OPTION]>): Promise<string>;
29
+ export declare function getOrganizationUidOrDefaultOrSelect(options: CommandLineOptions<[typeof ORGANIZATION_UID_OPTION, typeof NO_DEFAULT_ORGANIZATION_OPTION]>, skipPrompts?: boolean): Promise<string>;
30
30
  export declare function selectOrganizationUid(options: CommandLineOptions<[typeof ORGANIZATION_UID_OPTION]>): Promise<string>;
31
31
  export declare function getOrganizations(): Promise<IOrganization[]>;
32
32
  export declare function getOrganization(organizationUid: string): Promise<IOrganization>;
@@ -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
  },
@@ -0,0 +1,7 @@
1
+ import { IPaginatedList } from '@signageos/sdk';
2
+ /**
3
+ * Fetches all pages from a paginated list.
4
+ * @param firstPage The first page returned from a list() method
5
+ * @returns Array containing all items from all pages
6
+ */
7
+ export declare function getAllPages<T>(firstPage: IPaginatedList<T>): Promise<T[]>;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.getAllPages = getAllPages;
13
+ /**
14
+ * Fetches all pages from a paginated list.
15
+ * @param firstPage The first page returned from a list() method
16
+ * @returns Array containing all items from all pages
17
+ */
18
+ function getAllPages(firstPage) {
19
+ return __awaiter(this, void 0, void 0, function* () {
20
+ const allItems = [...firstPage];
21
+ let currentPage = firstPage;
22
+ while (true) {
23
+ const nextPage = yield currentPage.getNextPage();
24
+ if (!nextPage) {
25
+ break;
26
+ }
27
+ allItems.push(...nextPage);
28
+ currentPage = nextPage;
29
+ }
30
+ return allItems;
31
+ });
32
+ }
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';
@@ -12,7 +13,6 @@ export declare function createClientVersions(): {
12
13
  signageOS_CLI: any;
13
14
  };
14
15
  export declare function createOrganizationRestApi(credentials: ICredentials): Promise<RestApi>;
15
- export declare function createFirmwareVersionRestApi(): Promise<RestApi>;
16
16
  export declare const AUTH_HEADER = "X-Auth";
17
17
  export interface IOptions {
18
18
  url: string;
@@ -33,4 +33,21 @@ export declare function putResource(options: IOptions, path: string, query?: any
33
33
  export declare function deleteResource(options: IOptions, path: string): Promise<Response>;
34
34
  export declare function deserializeJSON(_key: string, value: any): any;
35
35
  export declare function getErrorMessageFromUnknownError(error: unknown): unknown;
36
+ /**
37
+ * Custom suggest function for autocomplete prompts.
38
+ * Searches in both the title (display text) and value (actual value) fields.
39
+ * This allows users to search by UID even when it's shown in parentheses in the title.
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * const response = await prompts({
44
+ * type: 'autocomplete',
45
+ * name: 'deviceUid',
46
+ * message: 'Select device',
47
+ * choices: devices.map(d => ({ title: `${d.name} (${d.uid})`, value: d.uid })),
48
+ * suggest: autocompleteSuggest,
49
+ * });
50
+ * ```
51
+ */
52
+ export declare function autocompleteSuggest<T extends prompts.Choice>(input: string, choices: T[]): Promise<T[]>;
36
53
  export {};
package/dist/helper.js CHANGED
@@ -17,7 +17,6 @@ exports.loadApiUrl = loadApiUrl;
17
17
  exports.getApiUrl = getApiUrl;
18
18
  exports.createClientVersions = createClientVersions;
19
19
  exports.createOrganizationRestApi = createOrganizationRestApi;
20
- exports.createFirmwareVersionRestApi = createFirmwareVersionRestApi;
21
20
  exports.createOptions = createOptions;
22
21
  exports.createUri = createUri;
23
22
  exports.getResource = getResource;
@@ -26,6 +25,7 @@ exports.putResource = putResource;
26
25
  exports.deleteResource = deleteResource;
27
26
  exports.deserializeJSON = deserializeJSON;
28
27
  exports.getErrorMessageFromUnknownError = getErrorMessageFromUnknownError;
28
+ exports.autocompleteSuggest = autocompleteSuggest;
29
29
  const querystring_1 = require("querystring");
30
30
  const RestApi_1 = __importDefault(require("@signageos/sdk/dist/RestApi/RestApi"));
31
31
  const runControlHelper_1 = require("./RunControl/runControlHelper");
@@ -39,11 +39,18 @@ function loadApiUrl() {
39
39
  });
40
40
  }
41
41
  function getApiUrl(config) {
42
- const apiUrl = (0, globalArgs_1.getGlobalApiUrl)() || config.apiUrl;
43
- if (!apiUrl) {
42
+ // Precedence:
43
+ // 1. Explicit global CLI argument (--api-url)
44
+ // 2. Environment variable (SOS_API_URL) - for CI/CD and testing
45
+ // 3. Stored profile configuration (config.apiUrl from ~/.sosrc)
46
+ const cliUrl = (0, globalArgs_1.getGlobalApiUrl)();
47
+ const profileUrl = config.apiUrl;
48
+ const rawApiUrl = cliUrl || profileUrl;
49
+ if (!rawApiUrl) {
44
50
  throw new Error(`No API URL is defined. Please use --api-url or set SOS_API_URL environment variable.`);
45
51
  }
46
- return apiUrl;
52
+ // Normalize: remove trailing slashes to avoid double '//'
53
+ return rawApiUrl.replace(/\/+$/, '');
47
54
  }
48
55
  function createClientVersions() {
49
56
  return {
@@ -65,25 +72,6 @@ function createOrganizationRestApi(credentials) {
65
72
  return new RestApi_1.default(options, accountOptions);
66
73
  });
67
74
  }
68
- function createFirmwareVersionRestApi() {
69
- return __awaiter(this, void 0, void 0, function* () {
70
- const config = yield (0, runControlHelper_1.loadConfig)();
71
- if (!config.identification || !config.apiSecurityToken) {
72
- throw new Error('Identification or token is missing.');
73
- }
74
- const options = {
75
- url: getApiUrl(config),
76
- auth: {
77
- clientId: config.identification,
78
- secret: config.apiSecurityToken,
79
- },
80
- version: apiVersions_1.ApiVersions.V1,
81
- clientVersions: createClientVersions(),
82
- };
83
- const accountOptions = Object.assign({}, options);
84
- return new RestApi_1.default(options, accountOptions);
85
- });
86
- }
87
75
  exports.AUTH_HEADER = 'X-Auth';
88
76
  function createOptions(method, options, data) {
89
77
  return {
@@ -129,3 +117,23 @@ function getErrorMessageFromUnknownError(error) {
129
117
  return null;
130
118
  }
131
119
  }
120
+ /**
121
+ * Custom suggest function for autocomplete prompts.
122
+ * Searches in both the title (display text) and value (actual value) fields.
123
+ * This allows users to search by UID even when it's shown in parentheses in the title.
124
+ *
125
+ * @example
126
+ * ```typescript
127
+ * const response = await prompts({
128
+ * type: 'autocomplete',
129
+ * name: 'deviceUid',
130
+ * message: 'Select device',
131
+ * choices: devices.map(d => ({ title: `${d.name} (${d.uid})`, value: d.uid })),
132
+ * suggest: autocompleteSuggest,
133
+ * });
134
+ * ```
135
+ */
136
+ function autocompleteSuggest(input, choices) {
137
+ const searchTerm = input.toLowerCase();
138
+ 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)); }));
139
+ }
package/dist/index.js CHANGED
@@ -15,7 +15,6 @@ const loginCommand_1 = require("./Auth/loginCommand");
15
15
  const organizationCommand_1 = require("./Organization/organizationCommand");
16
16
  const timingCommand_1 = require("./Timing/timingCommand");
17
17
  const commandProcessor_1 = require("./Command/commandProcessor");
18
- const firmwareCommand_1 = require("./Firmware/firmwareCommand");
19
18
  const deviceCommand_1 = require("./Device/deviceCommand");
20
19
  const generalCommand_1 = require("./generalCommand");
21
20
  const commandDefinition_1 = require("./Command/commandDefinition");
@@ -49,7 +48,7 @@ const index = (0, commandDefinition_1.createCommandDefinition)({
49
48
  name: 'sos',
50
49
  description: 'SignageOS CLI - The central command-line tool for deploying, managing, and debugging signageOS projects and devices',
51
50
  optionList: generalCommand_1.GENERAL_OPTION_LIST,
52
- commands: [appletCommand_1.applet, loginCommand_1.login, organizationCommand_1.organization, timingCommand_1.timing, firmwareCommand_1.firmware, deviceCommand_1.device, customScriptCommand_1.customScript, pluginCommand_1.plugin, runnerCommand_1.runner, autocompleteCommand_1.autocomplete],
51
+ commands: [appletCommand_1.applet, loginCommand_1.login, organizationCommand_1.organization, timingCommand_1.timing, deviceCommand_1.device, customScriptCommand_1.customScript, pluginCommand_1.plugin, runnerCommand_1.runner, autocompleteCommand_1.autocomplete],
53
52
  run() {
54
53
  return __awaiter(this, void 0, void 0, function* () {
55
54
  throw new Error('Unknown command');
@@ -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,
@@ -25,7 +25,7 @@ sos applet generate [options]
25
25
 
26
26
  | Option | Description | Default |
27
27
  | ------------------ | ------------------------------------------------------------------------------- | ------- |
28
- | `--name` | Applet name. Match RegExp: /^\w(\w|\d|-)*\w$/ (string) | |
28
+ | `--name` | (string) | |
29
29
  | `--applet-version` | Applet initial version. Use semantic version (string) | `0.0.0` |
30
30
  | `--target-dir` | Directory where will be the applet generated to (string) | |
31
31
  | `--git` | Init applet as git repository "no" (default) or "yes" (string) | |
@@ -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