@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
@@ -60,9 +60,17 @@ function updateSingleFileApplet(parameters) {
60
60
  return __awaiter(this, void 0, void 0, function* () {
61
61
  const { restApi, applet } = parameters;
62
62
  const appletBinary = fs.createReadStream(applet.binaryFilePath, { encoding: 'utf8' });
63
- yield restApi.applet.version.update(applet.uid, applet.version, {
63
+ yield restApi.applet.version
64
+ .update(applet.uid, applet.version, {
64
65
  binary: appletBinary,
65
66
  frontAppletVersion: applet.frontAppletVersion,
67
+ })
68
+ .catch((error) => {
69
+ if (error instanceof Error) {
70
+ throw new Error(`Failed to update applet version "${applet.version}" with binary file "${applet.binaryFilePath}": ${error.message}\n` +
71
+ `Please check that the file exists, is readable, and that you have permissions to update the applet.`);
72
+ }
73
+ throw error;
66
74
  });
67
75
  });
68
76
  }
@@ -91,40 +99,44 @@ const updateMultiFileApplet = (parameters) => __awaiter(void 0, void 0, void 0,
91
99
  }
92
100
  else {
93
101
  changedFilesCounter++;
94
- (0, log_1.log)('info', chalk_1.default.yellow(` Uploading ${fileAbsolutePath}`));
95
102
  }
96
103
  if (progressBar) {
97
- progressBar.init({ size: fileSize, name: fileRelativePath });
104
+ progressBar.init({ size: fileSize, name: fileRelativePosixPath });
98
105
  }
99
106
  const fileStream = fs.createReadStream(fileAbsolutePath);
100
107
  fileStream.pause();
101
108
  fileStream.on('data', (chunk) => {
102
109
  if (progressBar) {
103
- progressBar.update({ add: chunk.length });
110
+ progressBar.update({ add: chunk.length, name: fileRelativePosixPath });
104
111
  }
105
112
  });
106
- try {
107
- // update file is just alias to create file (both are idempotent)
108
- yield restApi.applet.version.file.update(applet.uid, applet.version, fileRelativePosixPath, {
109
- content: fileStream,
110
- hash: fileHash,
111
- size: fileSize,
112
- type: fileType,
113
- }, { build: false });
114
- }
115
- catch (error) {
113
+ // update file is just alias to create file (both are idempotent)
114
+ yield restApi.applet.version.file
115
+ .update(applet.uid, applet.version, fileRelativePosixPath, {
116
+ content: fileStream,
117
+ hash: fileHash,
118
+ size: fileSize,
119
+ type: fileType,
120
+ }, { build: false })
121
+ .catch((error) => {
116
122
  if (fileSize === 0) {
117
123
  throw new Error(`Empty files are temporarily disallowed ${fileAbsolutePath}`);
118
124
  }
125
+ // Enhance error message with more context
126
+ if (error instanceof Error) {
127
+ const enhancedError = new Error(`Failed to upload file "${fileAbsolutePath}": ${error.message}\n` + `File details: Size=${fileSize} bytes, Type=${fileType}`);
128
+ // Preserve the original stack trace if available
129
+ if (error.stack) {
130
+ enhancedError.stack = error.stack;
131
+ }
132
+ throw enhancedError;
133
+ }
119
134
  throw error;
120
- }
135
+ });
121
136
  }
122
137
  for (const fileRelativePath in currentAppletFiles) {
123
138
  if (Object.prototype.hasOwnProperty.call(currentAppletFiles, fileRelativePath)) {
124
- try {
125
- yield restApi.applet.version.file.remove(applet.uid, applet.version, fileRelativePath, { build: false });
126
- }
127
- catch (error) {
139
+ yield restApi.applet.version.file.remove(applet.uid, applet.version, fileRelativePath, { build: false }).catch((error) => {
128
140
  if (error instanceof NotFoundError_1.default) {
129
141
  /*
130
142
  * This means that the file we are trying to remove somehow already got removed.
@@ -134,9 +146,17 @@ const updateMultiFileApplet = (parameters) => __awaiter(void 0, void 0, void 0,
134
146
  Debug(`remove old file ${fileRelativePath} failed`);
135
147
  }
136
148
  else {
149
+ // Add more context to file removal errors
150
+ if (error instanceof Error) {
151
+ const enhancedError = new Error(`Failed to remove obsolete file "${fileRelativePath}": ${error.message}`);
152
+ if (error.stack) {
153
+ enhancedError.stack = error.stack;
154
+ }
155
+ throw enhancedError;
156
+ }
137
157
  throw error;
138
158
  }
139
- }
159
+ });
140
160
  changedFilesCounter++;
141
161
  }
142
162
  }
@@ -144,8 +164,16 @@ const updateMultiFileApplet = (parameters) => __awaiter(void 0, void 0, void 0,
144
164
  const appletEntryFilePosixPath = path.posix.normalize(applet.entryFilePath.replace(/\\/g, '/'));
145
165
  if (changedFilesCounter > 0 || appletVersion.entryFile !== appletEntryFilePosixPath) {
146
166
  // The update applet version has to be the last after upload all files to trigger applet version build
147
- yield restApi.applet.version.update(applet.uid, applet.version, {
167
+ yield restApi.applet.version
168
+ .update(applet.uid, applet.version, {
148
169
  entryFile: appletEntryFilePosixPath,
170
+ })
171
+ .catch((error) => {
172
+ if (error instanceof Error) {
173
+ throw new Error(`Failed to update applet version with entry file "${appletEntryFilePosixPath}": ${error.message}\n` +
174
+ `This usually happens when the entry file is missing, has errors, or the applet version cannot be built.`);
175
+ }
176
+ throw error;
149
177
  });
150
178
  }
151
179
  if (progressBar) {
@@ -159,10 +187,18 @@ exports.updateMultiFileApplet = updateMultiFileApplet;
159
187
  const createSingleFileApplet = (parameters) => __awaiter(void 0, void 0, void 0, function* () {
160
188
  const { restApi, applet } = parameters;
161
189
  const appletBinary = fs.createReadStream(applet.binaryFilePath, { encoding: 'utf8' });
162
- yield restApi.applet.version.create(applet.uid, {
190
+ yield restApi.applet.version
191
+ .create(applet.uid, {
163
192
  binary: appletBinary,
164
193
  version: applet.version,
165
194
  frontAppletVersion: applet.frontAppletVersion,
195
+ })
196
+ .catch((error) => {
197
+ if (error instanceof Error) {
198
+ throw new Error(`Failed to create applet version "${applet.version}" with binary file "${applet.binaryFilePath}": ${error.message}\n` +
199
+ `Please check that the file exists, is readable, and that you have permissions to create applet versions.`);
200
+ }
201
+ throw error;
166
202
  });
167
203
  });
168
204
  exports.createSingleFileApplet = createSingleFileApplet;
@@ -170,39 +206,71 @@ const createMultiFileFileApplet = (parameters) => __awaiter(void 0, void 0, void
170
206
  const { restApi, applet, progressBar } = parameters;
171
207
  const appletEntryFilePosixPath = path.posix.normalize(applet.entryFilePath.replace(/\\/g, '/'));
172
208
  try {
173
- yield restApi.applet.version.create(applet.uid, {
209
+ yield restApi.applet.version
210
+ .create(applet.uid, {
174
211
  version: applet.version,
175
212
  entryFile: appletEntryFilePosixPath,
176
- });
177
- yield Promise.all(applet.files.map((fileAbsolutePath) => __awaiter(void 0, void 0, void 0, function* () {
178
- const fileRelativePath = (0, appletUploadFacadeHelper_1.getAppletFileRelativePath)(fileAbsolutePath, applet.directoryPath);
179
- const fileHash = yield (0, fileSystem_1.getFileMD5Checksum)(fileAbsolutePath);
180
- const fileType = yield (0, fileSystem_1.getFileType)(fileAbsolutePath);
181
- const fileSize = (yield fs.stat(fileAbsolutePath)).size;
182
- if (fileSize === 0) {
183
- throw new Error(`Empty files are temporarily disallowed ${fileAbsolutePath}`);
184
- }
185
- if (progressBar) {
186
- progressBar.init({ size: fileSize, name: fileRelativePath });
213
+ })
214
+ .catch((error) => {
215
+ if (error instanceof Error) {
216
+ throw new Error(`Failed to create applet version "${applet.version}": ${error.message}\n` +
217
+ `Please check that you have permissions to create applet versions and that the version is valid.`);
187
218
  }
188
- const fileStream = fs.createReadStream(fileAbsolutePath);
189
- fileStream.pause();
190
- fileStream.on('data', (chunk) => {
219
+ throw error;
220
+ });
221
+ // Process files sequentially to avoid concurrent progress bar issues
222
+ for (const fileAbsolutePath of applet.files) {
223
+ try {
224
+ const fileRelativePath = (0, appletUploadFacadeHelper_1.getAppletFileRelativePath)(fileAbsolutePath, applet.directoryPath);
225
+ const fileHash = yield (0, fileSystem_1.getFileMD5Checksum)(fileAbsolutePath);
226
+ const fileType = yield (0, fileSystem_1.getFileType)(fileAbsolutePath);
227
+ const fileSize = (yield fs.stat(fileAbsolutePath)).size;
228
+ if (fileSize === 0) {
229
+ (0, log_1.log)('info', chalk_1.default.yellow(`Skipping empty file ${fileAbsolutePath}`));
230
+ continue;
231
+ }
232
+ const filePosixPath = path.posix.normalize(fileRelativePath.replace(/\\/g, '/'));
191
233
  if (progressBar) {
192
- progressBar.update({ add: chunk.length });
234
+ progressBar.init({ size: fileSize, name: filePosixPath });
193
235
  }
194
- });
195
- const filePosixPath = path.posix.normalize(fileRelativePath.replace(/\\/g, '/'));
196
- (0, log_1.log)('info', chalk_1.default.yellow(` Uploading ${fileAbsolutePath}`));
197
- return restApi.applet.version.file.create(applet.uid, applet.version, {
198
- name: path.basename(filePosixPath),
199
- path: filePosixPath,
200
- type: fileType,
201
- hash: fileHash,
202
- content: fileStream,
203
- size: fileSize,
204
- }, { build: false });
205
- })));
236
+ const fileStream = fs.createReadStream(fileAbsolutePath);
237
+ fileStream.pause();
238
+ fileStream.on('data', (chunk) => {
239
+ if (progressBar) {
240
+ progressBar.update({ add: chunk.length, name: filePosixPath });
241
+ }
242
+ });
243
+ yield restApi.applet.version.file
244
+ .create(applet.uid, applet.version, {
245
+ name: path.basename(filePosixPath),
246
+ path: filePosixPath,
247
+ type: fileType,
248
+ hash: fileHash,
249
+ content: fileStream,
250
+ size: fileSize,
251
+ }, { build: false })
252
+ .catch((error) => {
253
+ if (error instanceof Error) {
254
+ throw new Error(`Failed to upload file "${fileAbsolutePath}": ${error.message}\n` +
255
+ `File details: Size=${fileSize} bytes, Type=${fileType}`);
256
+ }
257
+ throw error;
258
+ });
259
+ }
260
+ catch (error) {
261
+ if (error instanceof Error) {
262
+ if (fileAbsolutePath) {
263
+ const enhancedError = new Error(`Failed to upload file "${fileAbsolutePath}": ${error.message}`);
264
+ // Preserve the original stack trace if available
265
+ if (error.stack) {
266
+ enhancedError.stack = error.stack;
267
+ }
268
+ throw enhancedError;
269
+ }
270
+ }
271
+ throw error;
272
+ }
273
+ }
206
274
  }
207
275
  finally {
208
276
  if (progressBar) {
@@ -210,8 +278,12 @@ const createMultiFileFileApplet = (parameters) => __awaiter(void 0, void 0, void
210
278
  }
211
279
  }
212
280
  // The extra update applet version which has to be after upload all files to trigger applet version build
213
- yield restApi.applet.version.update(applet.uid, applet.version, {
214
- entryFile: appletEntryFilePosixPath,
281
+ yield restApi.applet.version.update(applet.uid, applet.version, { entryFile: appletEntryFilePosixPath }).catch((error) => {
282
+ if (error instanceof Error) {
283
+ throw new Error(`Failed to finalize applet version with entry file "${appletEntryFilePosixPath}": ${error.message}\n` +
284
+ `This usually happens when there were issues with one or more uploaded files or when building the applet.`);
285
+ }
286
+ throw error;
215
287
  });
216
288
  });
217
289
  exports.createMultiFileFileApplet = createMultiFileFileApplet;
@@ -2,5 +2,5 @@ import RestApi from '@signageos/sdk/dist/RestApi/RestApi';
2
2
  import IAppletVersionFile from '@signageos/sdk/dist/RestApi/Applet/Version/File/IAppletVersionFile';
3
3
  export declare function getAppletFileRelativePath(fileAbsolutePath: string, directoryAbsolutePath: string): string;
4
4
  export declare function getAppletFilesDictionary(restApi: RestApi, appletUid: string, appletVersion: string): Promise<{
5
- [fileRelativePath: string]: IAppletVersionFile;
5
+ [path: string]: IAppletVersionFile;
6
6
  }>;
@@ -45,6 +45,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
45
45
  exports.getAppletFileRelativePath = getAppletFileRelativePath;
46
46
  exports.getAppletFilesDictionary = getAppletFilesDictionary;
47
47
  const path = __importStar(require("path"));
48
+ const paginationHelper_1 = require("../../helper/paginationHelper");
48
49
  function getAppletFileRelativePath(fileAbsolutePath, directoryAbsolutePath) {
49
50
  const directoryAbsolutePathNormalized = path.normalize(directoryAbsolutePath);
50
51
  const fileAbsolutePathNormalized = path.normalize(fileAbsolutePath);
@@ -66,10 +67,10 @@ function getAppletFileRelativePath(fileAbsolutePath, directoryAbsolutePath) {
66
67
  function getAppletFilesDictionary(restApi, appletUid, appletVersion) {
67
68
  return __awaiter(this, void 0, void 0, function* () {
68
69
  const filesDictionary = {};
69
- const currentAppletFiles = yield restApi.applet.version.file.list(appletUid, appletVersion);
70
- currentAppletFiles.forEach((file) => {
70
+ const files = yield (0, paginationHelper_1.getAllPages)(yield restApi.applet.version.file.list(appletUid, appletVersion));
71
+ for (const file of files) {
71
72
  filesDictionary[file.path] = file;
72
- });
73
+ }
73
74
  return filesDictionary;
74
75
  });
75
76
  }
@@ -1,3 +1,6 @@
1
1
  export declare class AppletDoesNotExistError extends Error {
2
2
  constructor(message: string);
3
3
  }
4
+ export declare class AppletSelectionCancelledError extends Error {
5
+ constructor(message?: string);
6
+ }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.AppletDoesNotExistError = void 0;
3
+ exports.AppletSelectionCancelledError = exports.AppletDoesNotExistError = void 0;
4
4
  class AppletDoesNotExistError extends Error {
5
5
  constructor(message) {
6
6
  super(message);
@@ -8,3 +8,10 @@ class AppletDoesNotExistError extends Error {
8
8
  }
9
9
  }
10
10
  exports.AppletDoesNotExistError = AppletDoesNotExistError;
11
+ class AppletSelectionCancelledError extends Error {
12
+ constructor(message = 'Applet selection was cancelled') {
13
+ super(message);
14
+ Object.setPrototypeOf(this, AppletSelectionCancelledError.prototype);
15
+ }
16
+ }
17
+ exports.AppletSelectionCancelledError = AppletSelectionCancelledError;
@@ -56,6 +56,8 @@ const chalk_1 = __importDefault(require("chalk"));
56
56
  const parameters_1 = require("../parameters");
57
57
  const packageConfig_1 = require("@signageos/sdk/dist/FileSystem/packageConfig");
58
58
  const appletErrors_1 = require("./appletErrors");
59
+ const helper_1 = require("../helper");
60
+ const paginationHelper_1 = require("../helper/paginationHelper");
59
61
  exports.APPLET_UID_OPTION = { name: 'applet-uid', type: String, description: 'Applet UID' };
60
62
  function getApplet(directoryPath) {
61
63
  return __awaiter(this, void 0, void 0, function* () {
@@ -96,7 +98,8 @@ function getAppletUid(restApi_1, options_1) {
96
98
  const currentApplet = yield getApplet(currentDirectory);
97
99
  let appletUid = options['applet-uid'] || currentApplet.uid;
98
100
  if (!appletUid) {
99
- const applets = yield restApi.applet.list();
101
+ // Fetch all pages to ensure we don't miss any applets
102
+ const applets = yield (0, paginationHelper_1.getAllPages)(yield restApi.applet.list());
100
103
  const candidatesOfApplets = applets.filter((applet) => applet.name === currentApplet.name);
101
104
  if (candidatesOfApplets.length === 0) {
102
105
  appletUid = undefined;
@@ -114,7 +117,11 @@ function getAppletUid(restApi_1, options_1) {
114
117
  title: `${applet.name} (${applet.uid})`,
115
118
  value: applet.uid,
116
119
  })),
120
+ suggest: helper_1.autocompleteSuggest,
117
121
  });
122
+ if (!response.appletUid) {
123
+ throw new appletErrors_1.AppletSelectionCancelledError();
124
+ }
118
125
  appletUid = response.appletUid;
119
126
  }
120
127
  }
@@ -132,7 +139,7 @@ function getAppletVersionFromApi(restApi_1, appletUid_1) {
132
139
  return __awaiter(this, arguments, void 0, function* (restApi, appletUid, skipConfirmation = false) {
133
140
  var _a;
134
141
  let appletVersion;
135
- const appletVersions = yield restApi.applet.version.list(appletUid);
142
+ const appletVersions = yield (0, paginationHelper_1.getAllPages)(yield restApi.applet.version.list(appletUid));
136
143
  if (appletVersions.length === 1 && ((_a = appletVersions[0]) === null || _a === void 0 ? void 0 : _a.version)) {
137
144
  appletVersion = appletVersions[0].version;
138
145
  }
@@ -148,7 +155,11 @@ function getAppletVersionFromApi(restApi_1, appletUid_1) {
148
155
  title: applet.version,
149
156
  value: applet.version,
150
157
  })),
158
+ suggest: helper_1.autocompleteSuggest,
151
159
  });
160
+ if (!response.appletVersion) {
161
+ throw new Error('Applet version selection was cancelled');
162
+ }
152
163
  appletVersion = response.appletVersion;
153
164
  }
154
165
  return appletVersion;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Checks if the given directory appears to be a valid signageOS applet directory.
3
+ *
4
+ * A valid applet has @signageos/front-applet as a dependency.
5
+ * This is the definitive marker that distinguishes applets from other projects.
6
+ *
7
+ * @param directoryPath - The directory path to validate
8
+ * @returns true if the directory appears to be a valid applet, false otherwise
9
+ */
10
+ export declare function isValidAppletDirectory(directoryPath: string): Promise<boolean>;
11
+ /**
12
+ * Validates that the directory is a valid applet directory and throws an error if not.
13
+ *
14
+ * @param directoryPath - The directory path to validate
15
+ * @throws {Error} If the directory is not a valid applet directory
16
+ */
17
+ export declare function validateAppletDirectory(directoryPath: string): Promise<void>;
@@ -0,0 +1,62 @@
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
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.isValidAppletDirectory = isValidAppletDirectory;
16
+ exports.validateAppletDirectory = validateAppletDirectory;
17
+ const packageConfig_1 = require("@signageos/sdk/dist/FileSystem/packageConfig");
18
+ const chalk_1 = __importDefault(require("chalk"));
19
+ /**
20
+ * Checks if the given directory appears to be a valid signageOS applet directory.
21
+ *
22
+ * A valid applet has @signageos/front-applet as a dependency.
23
+ * This is the definitive marker that distinguishes applets from other projects.
24
+ *
25
+ * @param directoryPath - The directory path to validate
26
+ * @returns true if the directory appears to be a valid applet, false otherwise
27
+ */
28
+ function isValidAppletDirectory(directoryPath) {
29
+ return __awaiter(this, void 0, void 0, function* () {
30
+ var _a, _b;
31
+ try {
32
+ const packageJSONObject = yield (0, packageConfig_1.loadPackage)(directoryPath);
33
+ if (!packageJSONObject) {
34
+ return false;
35
+ }
36
+ // Check for @signageos/front-applet dependency
37
+ // If package has it, it's an applet
38
+ const hasFrontApplet = !!(((_a = packageJSONObject.dependencies) === null || _a === void 0 ? void 0 : _a['@signageos/front-applet']) || ((_b = packageJSONObject.devDependencies) === null || _b === void 0 ? void 0 : _b['@signageos/front-applet']));
39
+ return hasFrontApplet;
40
+ }
41
+ catch (_c) {
42
+ // If we can't load package.json or it's malformed, it's not a valid applet
43
+ return false;
44
+ }
45
+ });
46
+ }
47
+ /**
48
+ * Validates that the directory is a valid applet directory and throws an error if not.
49
+ *
50
+ * @param directoryPath - The directory path to validate
51
+ * @throws {Error} If the directory is not a valid applet directory
52
+ */
53
+ function validateAppletDirectory(directoryPath) {
54
+ return __awaiter(this, void 0, void 0, function* () {
55
+ const isValid = yield isValidAppletDirectory(directoryPath);
56
+ if (!isValid) {
57
+ throw new Error(chalk_1.default.red(`\nThe directory does not appear to be a valid signageOS applet:\n`) +
58
+ chalk_1.default.white(` ${directoryPath}\n\n`) +
59
+ chalk_1.default.yellow(`A valid applet must have ${chalk_1.default.green('@signageos/front-applet')} as a dependency.\n\n`));
60
+ }
61
+ });
62
+ }
@@ -50,12 +50,14 @@ const chalk_1 = __importDefault(require("chalk"));
50
50
  const prompts_1 = __importDefault(require("prompts"));
51
51
  const debug_1 = __importDefault(require("debug"));
52
52
  const os = __importStar(require("os"));
53
+ const fs_extra_1 = __importDefault(require("fs-extra"));
53
54
  const apiVersions_1 = require("@signageos/sdk/dist/RestApi/apiVersions");
54
55
  const log_1 = require("@signageos/sdk/dist/Console/log");
55
56
  const sosControlHelper_1 = require("@signageos/sdk/dist/SosHelper/sosControlHelper");
56
57
  const helper_1 = require("../helper");
57
58
  const runControlHelper_1 = require("../RunControl/runControlHelper");
58
59
  const parameters_1 = require("../parameters");
60
+ const globalArgs_1 = require("../Command/globalArgs");
59
61
  const commandDefinition_1 = require("../Command/commandDefinition");
60
62
  const Debug = (0, debug_1.default)('@signageos/cli:Auth:login');
61
63
  const OPTION_LIST = [{ name: 'username', type: String, description: `Username or e-mail used for authentication` }];
@@ -100,11 +102,35 @@ exports.login = (0, commandDefinition_1.createCommandDefinition)({
100
102
  run(options) {
101
103
  return __awaiter(this, void 0, void 0, function* () {
102
104
  let identification = options.username;
105
+ const profile = (0, globalArgs_1.getGlobalProfile)();
106
+ const configFilePath = (0, sosControlHelper_1.getConfigFilePath)();
107
+ // Detect a new (non-existent) named profile and prompt for the API URL
108
+ let promptedApiUrl;
109
+ if (profile) {
110
+ let profileExists = false;
111
+ if (yield fs_extra_1.default.pathExists(configFilePath)) {
112
+ const content = (yield fs_extra_1.default.readFile(configFilePath)).toString();
113
+ profileExists = content.includes(`[profile ${profile}]`);
114
+ }
115
+ if (!profileExists) {
116
+ (0, log_1.log)('info', `Profile "${profile}" does not exist in ${configFilePath}. Please enter the server API URL to create it.`);
117
+ const { inputApiUrl } = yield (0, prompts_1.default)({
118
+ type: 'text',
119
+ name: 'inputApiUrl',
120
+ message: 'Server API URL',
121
+ initial: 'https://api.signageos.io',
122
+ validate: (v) => (v.startsWith('http') ? true : 'Must be a valid URL starting with http'),
123
+ });
124
+ if (!inputApiUrl) {
125
+ throw new Error('API URL is required to log in.');
126
+ }
127
+ promptedApiUrl = inputApiUrl.replace(/\/+$/, '');
128
+ }
129
+ }
103
130
  const config = yield (0, runControlHelper_1.loadConfig)();
104
- const apiUrl = (0, helper_1.getApiUrl)(config);
131
+ const apiUrl = promptedApiUrl !== null && promptedApiUrl !== void 0 ? promptedApiUrl : (0, helper_1.getApiUrl)(config);
105
132
  // Extract domain from API URL to show in prompts
106
- const apiUrlObj = new URL(apiUrl);
107
- const hostToDisplay = apiUrlObj.hostname;
133
+ const hostToDisplay = new URL(apiUrl).hostname;
108
134
  if (!identification) {
109
135
  const response = yield (0, prompts_1.default)({
110
136
  type: 'text',
@@ -126,7 +152,7 @@ exports.login = (0, commandDefinition_1.createCommandDefinition)({
126
152
  password,
127
153
  apiUrl }, authQueryParams));
128
154
  yield (0, runControlHelper_1.saveConfig)({
129
- apiUrl: apiUrl !== parameters_1.parameters.apiUrl ? apiUrl : undefined,
155
+ apiUrl: profile || apiUrl !== parameters_1.parameters.apiUrl ? apiUrl : undefined,
130
156
  identification: tokenId,
131
157
  apiSecurityToken,
132
158
  });
@@ -20,6 +20,7 @@ const command_line_usage_1 = __importDefault(require("command-line-usage"));
20
20
  const command_line_args_1 = __importDefault(require("command-line-args"));
21
21
  const packageVersion_1 = require("../Cli/packageVersion");
22
22
  const log_1 = require("@signageos/sdk/dist/Console/log");
23
+ const globalArgs_1 = require("./globalArgs");
23
24
  const Debug = (0, debug_1.default)('@signageos/cli:Command:processor');
24
25
  // Preprocess argv to detect and handle multi-character single-dash options
25
26
  function preprocessArgv(argv) {
@@ -40,6 +41,10 @@ function preprocessArgv(argv) {
40
41
  }
41
42
  function processCommand(currentCommand_1) {
42
43
  return __awaiter(this, arguments, void 0, function* (currentCommand, parentOptionList = [], commandIndex = 0) {
44
+ // Validate mutually exclusive global options once at the root invocation
45
+ if (commandIndex === 0) {
46
+ (0, globalArgs_1.validateProfileAndApiUrl)();
47
+ }
43
48
  const nestedOptionList = [...parentOptionList, ...currentCommand.optionList];
44
49
  // Preprocess argv to handle multi-character single-dash options that should be treated as unknown
45
50
  // rather than being split into individual characters
@@ -31,3 +31,24 @@ export declare function getGlobalApiUrl(): string | undefined;
31
31
  * ```
32
32
  */
33
33
  export declare function getGlobalProfile(): string;
34
+ /**
35
+ * Validate that --profile and --api-url are not used together.
36
+ * These options are mutually exclusive: --profile selects a fully pre-configured
37
+ * connection from ~/.sosrc (which already includes an api-url), while --api-url
38
+ * overrides the endpoint directly (implying the default profile).
39
+ *
40
+ * @throws {Error} When both --profile and --api-url are present on the command line
41
+ *
42
+ * @example
43
+ * ```bash
44
+ * # Valid: only --profile
45
+ * sos --profile staging login
46
+ *
47
+ * # Valid: only --api-url
48
+ * sos --api-url https://custom-api.com login
49
+ *
50
+ * # Invalid: both together
51
+ * sos --profile staging --api-url https://custom-api.com login
52
+ * ```
53
+ */
54
+ export declare function validateProfileAndApiUrl(): void;
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.getGlobalApiUrl = getGlobalApiUrl;
7
7
  exports.getGlobalProfile = getGlobalProfile;
8
+ exports.validateProfileAndApiUrl = validateProfileAndApiUrl;
8
9
  const command_line_args_1 = __importDefault(require("command-line-args"));
9
10
  const generalCommand_1 = require("../generalCommand");
10
11
  /**
@@ -46,3 +47,32 @@ function getGlobalProfile() {
46
47
  const options = (0, command_line_args_1.default)([generalCommand_1.PROFILE_OPTION], { partial: true });
47
48
  return options[generalCommand_1.PROFILE_OPTION.name];
48
49
  }
50
+ /**
51
+ * Validate that --profile and --api-url are not used together.
52
+ * These options are mutually exclusive: --profile selects a fully pre-configured
53
+ * connection from ~/.sosrc (which already includes an api-url), while --api-url
54
+ * overrides the endpoint directly (implying the default profile).
55
+ *
56
+ * @throws {Error} When both --profile and --api-url are present on the command line
57
+ *
58
+ * @example
59
+ * ```bash
60
+ * # Valid: only --profile
61
+ * sos --profile staging login
62
+ *
63
+ * # Valid: only --api-url
64
+ * sos --api-url https://custom-api.com login
65
+ *
66
+ * # Invalid: both together
67
+ * sos --profile staging --api-url https://custom-api.com login
68
+ * ```
69
+ */
70
+ function validateProfileAndApiUrl() {
71
+ const profile = getGlobalProfile();
72
+ const apiUrl = getGlobalApiUrl();
73
+ if (profile !== undefined && apiUrl !== undefined) {
74
+ throw new Error(`Options --profile and --api-url are mutually exclusive. ` +
75
+ `--profile selects a pre-configured connection from ~/.sosrc (which already includes an API URL), ` +
76
+ `while --api-url overrides the API endpoint directly. Use one or the other, not both.`);
77
+ }
78
+ }