icuewidget-cli 0.4.47 → 0.4.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "icuewidget-cli",
3
- "version": "0.4.47",
3
+ "version": "0.4.48",
4
4
  "description": "Create, validate, update, and package CORSAIR iCUE widgets.",
5
5
  "keywords": [
6
6
  "corsair",
package/src-node/cli.js CHANGED
@@ -19,6 +19,8 @@ import { notifyAboutUpdate, runUpdateCheckWorker } from "./update-check.js";
19
19
  import { ValidationError, validateWidget } from "./validation.js";
20
20
  import { createPackage } from "./zip.js";
21
21
 
22
+ const COMMANDS = ["init", "validate", "update", "package"];
23
+
22
24
  export async function main(argv = process.argv) {
23
25
  try {
24
26
  const args = argv.slice(2);
@@ -30,7 +32,9 @@ export async function main(argv = process.argv) {
30
32
  printHelp();
31
33
  return;
32
34
  }
33
- if (has(args, "--version") || has(args, "-v")) {
35
+ // `update <widget> --version <semver>` uses --version as a subcommand option, so only
36
+ // treat it as the global flag when no subcommand leads the argument list.
37
+ if (!COMMANDS.includes(args[0]) && (has(args, "--version") || has(args, "-v"))) {
34
38
  console.log(CLI_VERSION);
35
39
  return;
36
40
  }
@@ -1,6 +1,6 @@
1
1
  export const APP_NAME = "icuewidget";
2
2
  export const NPM_PACKAGE_NAME = "icuewidget-cli";
3
- export const CLI_VERSION = "0.4.47";
3
+ export const CLI_VERSION = "0.4.48";
4
4
  export const DEFAULT_APP_VERSION = "5.45";
5
5
  export const DEFAULT_FRAMEWORK_VERSION = "1.0.0";
6
6
  export const DEFAULT_PLATFORM = "windows";
@@ -10,10 +10,30 @@ function reader() {
10
10
 
11
11
  export async function prompt(label, defaultValue = "") {
12
12
  const suffix = defaultValue ? ` (${defaultValue})` : "";
13
- const answer = (await reader().question(`${label}${suffix}: `)).trim();
13
+ const answer = (await ask(`${label}${suffix}: `, label)).trim();
14
14
  return answer || defaultValue;
15
15
  }
16
16
 
17
+ // readline never settles a pending question when stdin ends (closed pipe, CI, /dev/null), so
18
+ // Node would exit 0 with the command half-run. Reject instead so the failure is visible.
19
+ function ask(question, label) {
20
+ const rl = reader();
21
+ return new Promise((resolve, reject) => {
22
+ const onClose = () => reject(new Error(`Input ended while waiting for "${label}". Pass the value as a flag or run interactively.`));
23
+ rl.once("close", onClose);
24
+ rl.question(question).then(
25
+ (answer) => {
26
+ rl.off("close", onClose);
27
+ resolve(answer);
28
+ },
29
+ (error) => {
30
+ rl.off("close", onClose);
31
+ reject(error);
32
+ },
33
+ );
34
+ });
35
+ }
36
+
17
37
  export async function promptValidated(label, defaultValue, validator, errorMessage) {
18
38
  while (true) {
19
39
  const answer = await prompt(label, defaultValue);
@@ -23,7 +23,7 @@ export async function validateWidget(widgetDir) {
23
23
  }
24
24
  }
25
25
 
26
- const manifest = await validateManifest(root);
26
+ const manifest = await validateManifest(root, warnings);
27
27
  if (await pathExists(path.join(root, "translation.json"))) {
28
28
  await readJson(path.join(root, "translation.json"));
29
29
  } else {
@@ -34,7 +34,7 @@ export async function validateWidget(widgetDir) {
34
34
  return { widgetDir: root, manifest, warnings };
35
35
  }
36
36
 
37
- async function validateManifest(root) {
37
+ async function validateManifest(root, warnings) {
38
38
  const manifest = await readJson(path.join(root, "manifest.json"));
39
39
  if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
40
40
  throw new ValidationError("manifest.json must contain a JSON object");
@@ -78,6 +78,18 @@ async function validateManifest(root) {
78
78
  if (manifest.interactive !== undefined && typeof manifest.interactive !== "boolean") {
79
79
  throw new ValidationError("manifest.interactive must be a boolean when provided");
80
80
  }
81
+ if (manifest.interactive === true) {
82
+ const nonDashboardTypes = manifest.supported_devices
83
+ .map((d) => d.type)
84
+ .filter((t) => t !== "dashboard_lcd");
85
+ if (nonDashboardTypes.length > 0) {
86
+ warnings.push(
87
+ `manifest.interactive is true, but supported_devices includes ${nonDashboardTypes.join(", ")}. ` +
88
+ `iCUE only recognizes interactive widgets on dashboard_lcd (Xeneon Edge). ` +
89
+ `The widget will not appear for ${nonDashboardTypes.join(" or ")} devices.`
90
+ );
91
+ }
92
+ }
81
93
  if (manifest.required_plugins !== undefined) validateStringArray(manifest.required_plugins, "manifest.required_plugins");
82
94
  if (manifest.modules !== undefined) {
83
95
  validateStringArray(manifest.modules, "manifest.modules");
@@ -91,19 +103,23 @@ async function validateIndex(root, warnings) {
91
103
  const html = await fs.readFile(path.join(root, "index.html"), "utf8");
92
104
  if (!/<title>[^<]+<\/title>/i.test(html)) warnings.push("index.html does not define a <title>.");
93
105
  if (!/rel=["'][^"']*icon/i.test(html)) warnings.push("index.html does not reference a favicon/icon.");
94
- if (!html.includes("icueEvents")) warnings.push("index.html or scripts do not reference icueEvents.");
95
106
 
96
107
  validateHeadWellFormed(html);
97
108
  validatePropertyTypes(html);
98
109
  validatePropertyGroups(html);
99
110
 
111
+ let referencesIcueEvents = html.includes("icueEvents");
100
112
  const assetRefs = [...extractAttributes(html, "href"), ...extractAttributes(html, "src")];
101
113
  for (const ref of assetRefs) {
102
114
  if (isRemote(ref) || ref.startsWith("data:") || ref.startsWith("#")) continue;
103
115
  await validateRelativeFile(root, stripQuery(ref), `index.html asset reference ${ref}`);
104
116
  if (ref.endsWith(".css")) await validateCss(path.join(root, stripQuery(ref)));
105
- if (ref.endsWith(".js")) await validateJs(path.join(root, stripQuery(ref)));
117
+ if (ref.endsWith(".js")) {
118
+ const code = await validateJs(path.join(root, stripQuery(ref)));
119
+ if (code.includes("icueEvents")) referencesIcueEvents = true;
120
+ }
106
121
  }
122
+ if (!referencesIcueEvents) warnings.push("index.html or scripts do not reference icueEvents.");
107
123
  }
108
124
 
109
125
  // iCUE imports a widget by parsing <head> with a strict XML parser (Qt QDomDocument).
@@ -215,6 +231,7 @@ async function validateJs(filePath) {
215
231
  } catch (error) {
216
232
  throw new ValidationError(`${path.basename(filePath)} has invalid JavaScript: ${error.message}`);
217
233
  }
234
+ return code;
218
235
  }
219
236
 
220
237
  async function validateRelativeFile(root, relativePath, label) {