icuewidget-cli 0.4.47 → 0.4.49
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/README.md +13 -75
- package/package.json +10 -15
- package/src-node/cli.js +57 -3
- package/src-node/constants.js +7 -3
- package/src-node/manifest-update.js +17 -14
- package/src-node/prompts.js +21 -1
- package/src-node/validation.js +21 -4
package/README.md
CHANGED
|
@@ -4,9 +4,9 @@ This repository contains the `icuewidget` command-line tool for creating, valida
|
|
|
4
4
|
|
|
5
5
|
The CLI uses:
|
|
6
6
|
|
|
7
|
-
- Node.js so it
|
|
7
|
+
- Node.js 18 or newer, so it runs through `npx` or a global npm install
|
|
8
8
|
- built-in Node APIs for prompts, validation, and `.icuewidget` packaging
|
|
9
|
-
-
|
|
9
|
+
- no runtime dependencies
|
|
10
10
|
|
|
11
11
|
## Commands
|
|
12
12
|
|
|
@@ -21,18 +21,16 @@ The CLI uses:
|
|
|
21
21
|
|
|
22
22
|
```text
|
|
23
23
|
.github/workflows/
|
|
24
|
-
installer/
|
|
25
24
|
node-bin/
|
|
26
|
-
scripts/
|
|
27
25
|
src-node/
|
|
28
26
|
test-node/
|
|
29
27
|
package-lock.json
|
|
30
28
|
package.json
|
|
31
29
|
```
|
|
32
30
|
|
|
33
|
-
##
|
|
31
|
+
## Install
|
|
34
32
|
|
|
35
|
-
|
|
33
|
+
Run without installing:
|
|
36
34
|
|
|
37
35
|
```bash
|
|
38
36
|
npx icuewidget-cli --help
|
|
@@ -42,6 +40,13 @@ npx icuewidget-cli update ./MyWidget --min-app-version 5.45
|
|
|
42
40
|
npx icuewidget-cli package ./MyWidget
|
|
43
41
|
```
|
|
44
42
|
|
|
43
|
+
Or install globally:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npm install -g icuewidget-cli
|
|
47
|
+
icuewidget --help
|
|
48
|
+
```
|
|
49
|
+
|
|
45
50
|
## Run locally without publishing
|
|
46
51
|
|
|
47
52
|
```bash
|
|
@@ -92,72 +97,5 @@ icuewidget init ./HelloWidget
|
|
|
92
97
|
|
|
93
98
|
## Versioning
|
|
94
99
|
|
|
95
|
-
The
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
```bash
|
|
99
|
-
CLI_BUILD_PATCH=123 npm run build
|
|
100
|
-
CLI_BUILD_VERSION=0.4.123 npm run build
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
`CLI_BUILD_PATCH`, `BUILD_NUMBER`, or `GITHUB_RUN_NUMBER` produces
|
|
104
|
-
`0.4.<patch>` for installer filenames and the bundled CLI `--version` output.
|
|
105
|
-
|
|
106
|
-
For npm publishing, publish a full semantic version, for example:
|
|
107
|
-
|
|
108
|
-
```bash
|
|
109
|
-
npm version 0.4.123
|
|
110
|
-
npm publish --access public
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
## npm publishing
|
|
114
|
-
|
|
115
|
-
Before publishing, verify the package contents and tests:
|
|
116
|
-
|
|
117
|
-
```bash
|
|
118
|
-
npm test
|
|
119
|
-
npm pack --dry-run
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
Then publish from this package directory:
|
|
123
|
-
|
|
124
|
-
```bash
|
|
125
|
-
npm publish --access public
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
Only the CLI runtime files listed in `package.json` are published to npm. The
|
|
129
|
-
installer assets and build scripts stay in the repository and are excluded from
|
|
130
|
-
the npm package.
|
|
131
|
-
|
|
132
|
-
## Native installers
|
|
133
|
-
|
|
134
|
-
The repository can also build native installers for users who do not want to use
|
|
135
|
-
`npx` or `npm install -g`.
|
|
136
|
-
|
|
137
|
-
Build standalone binaries first, then the platform installer:
|
|
138
|
-
|
|
139
|
-
```bash
|
|
140
|
-
npm run build
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
Outputs are written to:
|
|
144
|
-
|
|
145
|
-
```text
|
|
146
|
-
release/
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
Platform requirements:
|
|
150
|
-
|
|
151
|
-
- Windows `.exe`: build on Windows with NSIS `makensis` on `PATH`
|
|
152
|
-
- macOS `.pkg`: build on macOS with `pkgbuild` and `productbuild`
|
|
153
|
-
|
|
154
|
-
By default, installer builds package the checked-out local CLI source. To build
|
|
155
|
-
from a published npm package or tarball, set `CLI_PACKAGE_SPEC`:
|
|
156
|
-
|
|
157
|
-
```bash
|
|
158
|
-
CLI_PACKAGE_SPEC=icuewidget-cli@latest npm run build
|
|
159
|
-
CLI_PACKAGE_SPEC=./corsair-icuewidget-cli-1.0.0.tgz npm run build
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
The installers include best-effort cleanup for older Python-distributed versions
|
|
163
|
-
of `icuewidget-cli` before installing the standalone Node CLI binary.
|
|
100
|
+
The CLI follows semantic versioning. The version lives only in `package.json`,
|
|
101
|
+
and `icuewidget --version` reads it from there.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "icuewidget-cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.49",
|
|
4
4
|
"description": "Create, validate, update, and package CORSAIR iCUE widgets.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"corsair",
|
|
@@ -8,38 +8,33 @@
|
|
|
8
8
|
"widget",
|
|
9
9
|
"cli"
|
|
10
10
|
],
|
|
11
|
-
"homepage": "https://
|
|
11
|
+
"homepage": "https://github.com/Corsair-Labs/icuewidget-cli#readme",
|
|
12
12
|
"bugs": {
|
|
13
|
-
"url": "https://
|
|
13
|
+
"url": "https://github.com/Corsair-Labs/icuewidget-cli/issues"
|
|
14
14
|
},
|
|
15
15
|
"repository": {
|
|
16
16
|
"type": "git",
|
|
17
|
-
"url": "git+https://
|
|
17
|
+
"url": "git+https://github.com/Corsair-Labs/icuewidget-cli.git"
|
|
18
18
|
},
|
|
19
19
|
"license": "Apache-2.0",
|
|
20
20
|
"author": "CORSAIR",
|
|
21
21
|
"type": "module",
|
|
22
|
-
"main": "index.js",
|
|
23
22
|
"bin": {
|
|
24
23
|
"icuewidget": "node-bin/icuewidget.js"
|
|
25
24
|
},
|
|
26
25
|
"files": [
|
|
27
26
|
"node-bin/",
|
|
28
|
-
"src-node/"
|
|
29
|
-
"README.md"
|
|
27
|
+
"src-node/"
|
|
30
28
|
],
|
|
31
29
|
"scripts": {
|
|
32
30
|
"test": "node --test test-node/*.test.js",
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"build": "npm run build:binaries && npm run build:installers",
|
|
36
|
-
"check": "node --check scripts/build-binaries.mjs && node --check scripts/build-installers.mjs"
|
|
37
|
-
},
|
|
38
|
-
"dependencies": {},
|
|
39
|
-
"devDependencies": {
|
|
40
|
-
"@yao-pkg/pkg": "^6.5.1"
|
|
31
|
+
"pack:check": "npm pack --dry-run",
|
|
32
|
+
"prepublishOnly": "npm test"
|
|
41
33
|
},
|
|
42
34
|
"engines": {
|
|
43
35
|
"node": ">=18.0.0"
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
44
39
|
}
|
|
45
40
|
}
|
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);
|
|
@@ -26,11 +28,18 @@ export async function main(argv = process.argv) {
|
|
|
26
28
|
await runUpdateCheckWorker();
|
|
27
29
|
return;
|
|
28
30
|
}
|
|
29
|
-
if (args.length === 0
|
|
31
|
+
if (args.length === 0) {
|
|
30
32
|
printHelp();
|
|
31
33
|
return;
|
|
32
34
|
}
|
|
33
|
-
if (has(args, "--
|
|
35
|
+
if (has(args, "--help") || has(args, "-h")) {
|
|
36
|
+
if (COMMANDS.includes(args[0])) printCommandHelp(args[0]);
|
|
37
|
+
else printHelp();
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
// `update <widget> --version <semver>` uses --version as a subcommand option, so only
|
|
41
|
+
// treat it as the global flag when no subcommand leads the argument list.
|
|
42
|
+
if (!COMMANDS.includes(args[0]) && (has(args, "--version") || has(args, "-v"))) {
|
|
34
43
|
console.log(CLI_VERSION);
|
|
35
44
|
return;
|
|
36
45
|
}
|
|
@@ -165,9 +174,11 @@ function printHelp() {
|
|
|
165
174
|
panel("Commands", [
|
|
166
175
|
`${color.cyan("init")} Creates a new widget from the starter template.`,
|
|
167
176
|
`${color.cyan("validate")} Validates widget files and manifest requirements.`,
|
|
168
|
-
`${color.cyan("update")}
|
|
177
|
+
`${color.cyan("update")} Sets the minimum iCUE version and/or widget version, then regenerates the package.`,
|
|
169
178
|
`${color.cyan("package")} Validates widget and creates an .icuewidget package.`,
|
|
170
179
|
]);
|
|
180
|
+
console.log(`Run ${color.bold(`${APP_NAME} <command> --help`)} for command-specific options.`);
|
|
181
|
+
console.log();
|
|
171
182
|
panel("Examples", [
|
|
172
183
|
`${APP_NAME} init HelloWidget`,
|
|
173
184
|
`${APP_NAME} validate HelloWidget`,
|
|
@@ -177,6 +188,49 @@ function printHelp() {
|
|
|
177
188
|
], color.magenta);
|
|
178
189
|
}
|
|
179
190
|
|
|
191
|
+
const COMMAND_HELP = {
|
|
192
|
+
init: {
|
|
193
|
+
usage: "init [<dir>]",
|
|
194
|
+
summary: "Creates a new widget from the starter template, prompting for its details.",
|
|
195
|
+
options: [],
|
|
196
|
+
examples: ["init HelloWidget"],
|
|
197
|
+
},
|
|
198
|
+
validate: {
|
|
199
|
+
usage: "validate [<widget-dir>]",
|
|
200
|
+
summary: "Validates widget files and manifest requirements.",
|
|
201
|
+
options: [],
|
|
202
|
+
examples: ["validate HelloWidget"],
|
|
203
|
+
},
|
|
204
|
+
update: {
|
|
205
|
+
usage: "update [<widget-dir>] [--min-app-version <ver>] [--version <semver>] [-o <file>]",
|
|
206
|
+
summary: "Updates the manifest and regenerates the package. Prompts for min_app_version if it is missing.",
|
|
207
|
+
options: [
|
|
208
|
+
["--min-app-version <ver>", "Set manifest.min_app_version (e.g. 5.45 or 5.45.104). Bumps the patch version unless --version is given."],
|
|
209
|
+
["--version <semver>", "Set manifest.version (e.g. 1.0.1)."],
|
|
210
|
+
["-o, --output <file>", "Write the package to this path."],
|
|
211
|
+
],
|
|
212
|
+
examples: ["update HelloWidget --min-app-version 5.45", "update HelloWidget --version 1.0.1"],
|
|
213
|
+
},
|
|
214
|
+
package: {
|
|
215
|
+
usage: "package [<widget-dir>] [-o <file>]",
|
|
216
|
+
summary: "Validates the widget and creates an .icuewidget package.",
|
|
217
|
+
options: [["-o, --output <file>", "Write the package to this path."]],
|
|
218
|
+
examples: ["package HelloWidget", "package HelloWidget -o dist/hello.icuewidget"],
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
function printCommandHelp(command) {
|
|
223
|
+
const { usage, summary, options, examples } = COMMAND_HELP[command];
|
|
224
|
+
header(APP_NAME, CLI_VERSION);
|
|
225
|
+
console.log();
|
|
226
|
+
console.log(summary);
|
|
227
|
+
console.log();
|
|
228
|
+
console.log(`${color.yellow("Usage:")} ${color.bold(APP_NAME)} ${usage}`);
|
|
229
|
+
console.log();
|
|
230
|
+
if (options.length) panel("Options", options.map(([flag, text]) => `${color.cyan(flag)} ${text}`));
|
|
231
|
+
panel("Examples", examples.map((example) => `${APP_NAME} ${example}`), color.magenta);
|
|
232
|
+
}
|
|
233
|
+
|
|
180
234
|
function parseArgs(args, specs = {}) {
|
|
181
235
|
const options = {};
|
|
182
236
|
const positionals = [];
|
package/src-node/constants.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
4
|
+
|
|
1
5
|
export const APP_NAME = "icuewidget";
|
|
2
|
-
export const NPM_PACKAGE_NAME =
|
|
3
|
-
export const CLI_VERSION =
|
|
6
|
+
export const NPM_PACKAGE_NAME = packageJson.name;
|
|
7
|
+
export const CLI_VERSION = packageJson.version;
|
|
4
8
|
export const DEFAULT_APP_VERSION = "5.45";
|
|
5
|
-
export const DEFAULT_FRAMEWORK_VERSION = "1.
|
|
9
|
+
export const DEFAULT_FRAMEWORK_VERSION = "1.4.0";
|
|
6
10
|
export const DEFAULT_PLATFORM = "windows";
|
|
7
11
|
export const DEFAULT_DEVICE_TYPE = "dashboard_lcd";
|
|
8
12
|
|
|
@@ -14,20 +14,23 @@ export async function updateManifestToLatest(widgetDir, options = {}) {
|
|
|
14
14
|
const changes = [];
|
|
15
15
|
const requirementChanges = [];
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
|
|
17
|
+
const currentMinAppVersion = typeof manifest.min_app_version === "string" ? manifest.min_app_version.trim() : "";
|
|
18
|
+
const currentIsValid = APP_VERSION_RE.test(currentMinAppVersion);
|
|
19
|
+
let minAppVersion;
|
|
20
|
+
if (options.minAppVersion !== undefined) {
|
|
21
|
+
// An explicit flag is a setter: validate it and apply it whenever it differs from the manifest.
|
|
22
|
+
minAppVersion = options.minAppVersion.trim();
|
|
23
|
+
if (!APP_VERSION_RE.test(minAppVersion)) throw new Error("--min-app-version must use iCUE version format like 5.45 or 5.45.104.");
|
|
24
|
+
} else if (!currentIsValid) {
|
|
25
|
+
minAppVersion = await promptValidated(
|
|
26
|
+
"Minimum iCUE version required",
|
|
27
|
+
DEFAULT_APP_VERSION,
|
|
28
|
+
(answer) => APP_VERSION_RE.test(answer),
|
|
29
|
+
"Use iCUE version format like 5.45 or 5.45.104.",
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
if (minAppVersion !== undefined && minAppVersion !== manifest.min_app_version) {
|
|
33
|
+
manifest.min_app_version = minAppVersion;
|
|
31
34
|
requirementChanges.push("manifest.min_app_version");
|
|
32
35
|
}
|
|
33
36
|
|
package/src-node/prompts.js
CHANGED
|
@@ -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
|
|
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);
|
package/src-node/validation.js
CHANGED
|
@@ -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"))
|
|
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) {
|