@microtronics/studio-cli 0.42.0 → 0.44.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.
- package/CHANGELOG.md +24 -9
- package/README.md +122 -1
- package/out/main.js +28 -0
- package/out/usb.js +189 -0
- package/package.json +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,14 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
## 0.44.0 (2026-01-19)
|
|
4
|
+
|
|
5
|
+
## 0.43.0 (2026-01-19)
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
* add debug option to enable mdnCom logging ([34dfd29](https://bitbucket.org/microtronics-core/vscode-studio/commits/34dfd29cf2f5e6776e31463a85f0484a6907b13b))
|
|
10
|
+
* add support for uploading any amx file to an usb device ([e89f108](https://bitbucket.org/microtronics-core/vscode-studio/commits/e89f1087e22e6c0cc88c8b68bbbcc4f1d813dc4f))
|
|
11
|
+
* add USB device detection and listing functionality ([906e3e1](https://bitbucket.org/microtronics-core/vscode-studio/commits/906e3e12bca647f5ceba8b4196f00d507be163d9))
|
|
12
|
+
* add USB device listing command ([fb16e1a](https://bitbucket.org/microtronics-core/vscode-studio/commits/fb16e1ab0c9a52e38467b664e4b2b6adb135a249)), closes [#close](https://bitbucket.org/microtronics-core/vscode-studio/issues/close)
|
|
13
|
+
|
|
14
|
+
### Bug Fixes
|
|
15
|
+
|
|
16
|
+
* ensure proper exit code on successful USB upload ([cc1ae63](https://bitbucket.org/microtronics-core/vscode-studio/commits/cc1ae632669e785017c2afaa689c3750ba22d7fb))
|
|
17
|
+
|
|
18
|
+
## 0.42.0 (2026-01-13)
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
* add isIotApp helper function to check IoT app project type ([98ce8a1](https://bitbucket.org/microtronics-core/vscode-studio/commits/98ce8a1358bdd4d2ba1635cd4ef1a3f6e8ec0004))
|
|
23
|
+
* add POST endpoint for addon timeseries data insertion ([ace6880](https://bitbucket.org/microtronics-core/vscode-studio/commits/ace6880f801faefc303a8020c7acf87b06db5709))
|
|
24
|
+
|
|
25
|
+
### Bug Fixes
|
|
26
|
+
|
|
12
27
|
* restrict write access rejection to IoT apps only ([13aa98f](https://bitbucket.org/microtronics-core/vscode-studio/commits/13aa98f261632b576681a894ecb375b7ab5a4b99))
|
|
13
28
|
|
|
14
29
|
## 0.41.0 (2026-01-12)
|
package/README.md
CHANGED
|
@@ -21,9 +21,130 @@ For a reference on all the available `studio-cli` commands, run `studio-cli --he
|
|
|
21
21
|
|
|
22
22
|
## Requirements
|
|
23
23
|
|
|
24
|
-
[Node.js](https://nodejs.org/en/)
|
|
24
|
+
[Node.js](https://nodejs.org/en/) version `22` or higher.
|
|
25
25
|
|
|
26
26
|
|
|
27
|
+
## Commands
|
|
28
|
+
|
|
29
|
+
### install (alias: i)
|
|
30
|
+
Install all dependencies in your project.
|
|
31
|
+
|
|
32
|
+
```console
|
|
33
|
+
$ studio-cli install [dependency...]
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Options:**
|
|
37
|
+
- `-e, --env <environment>` - CLI target environment (defaults to STUDIO_ENV environment variable)
|
|
38
|
+
- `--token <API-token>` - API Access Token (defaults to STUDIO_TOKEN environment variable)
|
|
39
|
+
|
|
40
|
+
**Examples:**
|
|
41
|
+
```console
|
|
42
|
+
$ studio-cli install
|
|
43
|
+
$ studio-cli i
|
|
44
|
+
$ studio-cli install dependency-name
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### build
|
|
48
|
+
Build the project.
|
|
49
|
+
|
|
50
|
+
```console
|
|
51
|
+
$ studio-cli build [options]
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Options:**
|
|
55
|
+
- `-p, --part <APM part>` - APM part that should be built
|
|
56
|
+
- Available parts: `dde`, `dlo`, `pov`, `blo`, `dfiles`
|
|
57
|
+
- `-e, --env <environment>` - CLI target environment
|
|
58
|
+
- Available environments: `lucky`, `wynni`, `production` (default)
|
|
59
|
+
- `--token <API-token>` - API Access Token
|
|
60
|
+
|
|
61
|
+
**Manifest Overrides:**
|
|
62
|
+
You can overwrite manifest options using the `--option=value` syntax.
|
|
63
|
+
|
|
64
|
+
**Examples:**
|
|
65
|
+
```console
|
|
66
|
+
$ studio-cli build
|
|
67
|
+
$ studio-cli build --part dlo
|
|
68
|
+
$ studio-cli build --env wynni
|
|
69
|
+
$ studio-cli build --dlo.mainFile="./dlo/test.dlo"
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### publish
|
|
73
|
+
Publish the project to the registry.
|
|
74
|
+
|
|
75
|
+
```console
|
|
76
|
+
$ studio-cli publish [options]
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Options:**
|
|
80
|
+
- `-ph, --phase <release phase>` - Specify a release phase (default: `release`)
|
|
81
|
+
- Available phases: `alpha`, `beta`, `rc`, `release`, `stage`, `passive`, `withdrawn`
|
|
82
|
+
- `--allowedBackends <null|*|server.xz;server.yz>` - Specify the allowedBackends deployment for this version
|
|
83
|
+
- `null` - No restrictions (default)
|
|
84
|
+
- `*` - All backends allowed
|
|
85
|
+
- `server.xz;server.yz` - Specific backend servers (semicolon-separated)
|
|
86
|
+
- `-e, --env <environment>` - CLI target environment
|
|
87
|
+
- Available environments: `lucky`, `wynni`, `production` (default)
|
|
88
|
+
- `--token <API-token>` - API Access Token
|
|
89
|
+
- `-i, --packagePath <path>` - Publish the provided library or app package. Use "*" for autodetection
|
|
90
|
+
|
|
91
|
+
**Examples:**
|
|
92
|
+
```console
|
|
93
|
+
$ studio-cli publish
|
|
94
|
+
$ studio-cli publish --phase beta
|
|
95
|
+
$ studio-cli publish --allowedBackends "*"
|
|
96
|
+
$ studio-cli publish --packagePath ./my-package.tar.gz --env wynni
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### package (alias: pack)
|
|
100
|
+
Package the project.
|
|
101
|
+
|
|
102
|
+
```console
|
|
103
|
+
$ studio-cli package [options]
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Options:**
|
|
107
|
+
- `-ph, --phase <release phase>` - Specify a release phase (default: `release`)
|
|
108
|
+
- Available phases: `alpha`, `beta`, `rc`, `release`, `stage`, `passive`, `withdrawn`
|
|
109
|
+
- `-e, --env <environment>` - CLI target environment
|
|
110
|
+
- Available environments: `lucky`, `wynni`, `production` (default)
|
|
111
|
+
- `--token <API-token>` - API Access Token
|
|
112
|
+
|
|
113
|
+
**Examples:**
|
|
114
|
+
```console
|
|
115
|
+
$ studio-cli package
|
|
116
|
+
$ studio-cli pack --phase alpha
|
|
117
|
+
$ studio-cli pack --env wynni
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### usb
|
|
121
|
+
USB device management commands.
|
|
122
|
+
|
|
123
|
+
#### usb list
|
|
124
|
+
List available USB devices.
|
|
125
|
+
|
|
126
|
+
```console
|
|
127
|
+
$ studio-cli usb list
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
#### usb upload
|
|
131
|
+
Upload an AMX binary to a USB device.
|
|
132
|
+
|
|
133
|
+
```console
|
|
134
|
+
$ studio-cli usb upload -s <serial> -f <path> [--debug]
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**Options:**
|
|
138
|
+
- `-s, --serial <serial>` - Device serial number (16 characters hex) **[required]**
|
|
139
|
+
- `-f, --file <path>` - Path to the AMX file (relative to current directory) **[required]**
|
|
140
|
+
- `--debug` - Enable debug logging
|
|
141
|
+
|
|
142
|
+
**Example:**
|
|
143
|
+
```console
|
|
144
|
+
$ studio-cli usb upload -s 1234567890ABCDEF -f ./app.amx
|
|
145
|
+
$ studio-cli usb upload -s 1234567890ABCDEF -f ./app.amx --debug
|
|
146
|
+
```
|
|
147
|
+
|
|
27
148
|
## Development
|
|
28
149
|
|
|
29
150
|
Execute commands from the root dir
|
package/out/main.js
CHANGED
|
@@ -13,6 +13,7 @@ const update_notifier_1 = __importDefault(require("update-notifier"));
|
|
|
13
13
|
const api_1 = require("./api");
|
|
14
14
|
const log_1 = require("./log");
|
|
15
15
|
const argumentParser_1 = require("./argumentParser");
|
|
16
|
+
const usb_1 = require("./usb");
|
|
16
17
|
const program = new commander_1.Command();
|
|
17
18
|
const notifier = (0, update_notifier_1.default)({ pkg: package_json_1.default });
|
|
18
19
|
const logger = new log_1.Log.Logger();
|
|
@@ -145,6 +146,33 @@ program
|
|
|
145
146
|
const { env, token } = getDefaultOptions(opts);
|
|
146
147
|
main(command, (0, api_1.pack)(cwd, cliFS_1.cliFS, logger, env, token, opts.phase));
|
|
147
148
|
});
|
|
149
|
+
// USB commands
|
|
150
|
+
const usbCommand = program.command('usb').description('USB device management commands');
|
|
151
|
+
usbCommand
|
|
152
|
+
.command('list')
|
|
153
|
+
.description('List available USB devices')
|
|
154
|
+
.action(async () => {
|
|
155
|
+
await (0, usb_1.printUsbDevices)(logger);
|
|
156
|
+
notifier.notify();
|
|
157
|
+
});
|
|
158
|
+
usbCommand
|
|
159
|
+
.command('upload')
|
|
160
|
+
.description('Upload an AMX binary to a USB device')
|
|
161
|
+
.requiredOption('-s, --serial <serial>', 'Device serial number (16 characters hex)')
|
|
162
|
+
.requiredOption('-f, --file <path>', 'Path to the AMX file (relative to current directory)')
|
|
163
|
+
.requiredOption('--debug', 'Enable debug logging', false)
|
|
164
|
+
.action(async (opts) => {
|
|
165
|
+
const fileUri = vscode_uri_1.Utils.joinPath(cwd, opts.file);
|
|
166
|
+
const result = await (0, usb_1.uploadToDevice)(opts.serial, fileUri, cliFS_1.cliFS, logger, opts.debug);
|
|
167
|
+
notifier.notify();
|
|
168
|
+
if (!result.success) {
|
|
169
|
+
logger.error(result.message);
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
process.exit(0);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
148
176
|
module.exports = function (argv) {
|
|
149
177
|
program.parse(argv);
|
|
150
178
|
};
|
package/out/usb.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.listUsbDevices = listUsbDevices;
|
|
4
|
+
exports.printUsbDevices = printUsbDevices;
|
|
5
|
+
exports.findDeviceBySerialNumber = findDeviceBySerialNumber;
|
|
6
|
+
exports.uploadToDevice = uploadToDevice;
|
|
7
|
+
const usb_1 = require("usb");
|
|
8
|
+
const uto_master_1 = require("uto-master");
|
|
9
|
+
const utoStack_1 = require("uto-master/dist/src/utilities/utoStack");
|
|
10
|
+
// Microtronics vendor ID
|
|
11
|
+
const MICROTRONICS_VENDOR_ID = 0x2544;
|
|
12
|
+
/**
|
|
13
|
+
* Custom WebUSB instance configured to allow Microtronics devices
|
|
14
|
+
*/
|
|
15
|
+
const customWebUSB = new usb_1.WebUSB({
|
|
16
|
+
// Bypass checking for authorized devices
|
|
17
|
+
allowedDevices: [
|
|
18
|
+
{
|
|
19
|
+
vendorId: MICROTRONICS_VENDOR_ID
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
});
|
|
23
|
+
/**
|
|
24
|
+
* Lists all available USB devices filtered by Microtronics vendor ID
|
|
25
|
+
* @returns Promise resolving to an array of USB device information
|
|
26
|
+
*/
|
|
27
|
+
async function listUsbDevices() {
|
|
28
|
+
const devices = await customWebUSB.getDevices();
|
|
29
|
+
return devices.map((device) => ({
|
|
30
|
+
productName: device.productName,
|
|
31
|
+
serialNumber: device.serialNumber || device.productName?.split(' ').pop(),
|
|
32
|
+
vendorId: device.vendorId,
|
|
33
|
+
productId: device.productId
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Prints USB device product names using the provided logger
|
|
38
|
+
* @param logger - The logger instance to use for output
|
|
39
|
+
*/
|
|
40
|
+
async function printUsbDevices(logger) {
|
|
41
|
+
const devices = await listUsbDevices();
|
|
42
|
+
if (devices.length === 0) {
|
|
43
|
+
logger.info('No USB devices found.');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
logger.info('Available USB devices:');
|
|
47
|
+
devices.forEach((device, index) => {
|
|
48
|
+
const name = device.productName ||
|
|
49
|
+
`Unknown Device (VID: 0x${device.vendorId.toString(16)}, PID: 0x${device.productId.toString(16)})`;
|
|
50
|
+
const serial = device.serialNumber ? ` [${device.serialNumber}]` : '';
|
|
51
|
+
logger.log(` ${index + 1}. ${name}${serial}`);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Find a USB device by its serial number
|
|
56
|
+
* @param serialNumber - The 16-character hex serial number of the device
|
|
57
|
+
* @returns The USB device or null if not found
|
|
58
|
+
*/
|
|
59
|
+
async function findDeviceBySerialNumber(serialNumber) {
|
|
60
|
+
const devices = await customWebUSB.getDevices();
|
|
61
|
+
for (const device of devices) {
|
|
62
|
+
// Check direct serial number or serial from product name
|
|
63
|
+
const deviceSerial = device.serialNumber || device.productName?.split(' ').pop();
|
|
64
|
+
if (deviceSerial?.toLowerCase() === serialNumber.toLowerCase()) {
|
|
65
|
+
return device;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Upload an AMX binary file to a USB device
|
|
72
|
+
* @param serialNumber - The serial number of the target device
|
|
73
|
+
* @param fileUri - The URI to the AMX file
|
|
74
|
+
* @param fs - The filesystem interface for file operations
|
|
75
|
+
* @param logger - The logger instance for output
|
|
76
|
+
* @param debugLog - Enable debug logging
|
|
77
|
+
* @returns Promise resolving to an UploadResult
|
|
78
|
+
*/
|
|
79
|
+
async function uploadToDevice(serialNumber, fileUri, fs, logger, debugLog = false) {
|
|
80
|
+
// Check if file exists
|
|
81
|
+
const fileStat = await fs.stat(fileUri);
|
|
82
|
+
if (!fileStat) {
|
|
83
|
+
return {
|
|
84
|
+
success: false,
|
|
85
|
+
message: `File not found: ${fileUri.fsPath}`
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// Find the device by serial number
|
|
89
|
+
logger.info(`Searching for device with serial number: ${serialNumber}`);
|
|
90
|
+
const device = await findDeviceBySerialNumber(serialNumber);
|
|
91
|
+
if (!device) {
|
|
92
|
+
return {
|
|
93
|
+
success: false,
|
|
94
|
+
message: `Device not found with serial number: ${serialNumber}`
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
logger.info(`Device found: ${device.productName}`);
|
|
98
|
+
// Read the file contents
|
|
99
|
+
const fileData = (await fs.readFile(fileUri));
|
|
100
|
+
// Initialize UTO Master
|
|
101
|
+
const utoMaster = new uto_master_1.UtoMaster();
|
|
102
|
+
let deviceConnected = false;
|
|
103
|
+
try {
|
|
104
|
+
await utoMaster.init();
|
|
105
|
+
// Set up UTO transmit callback
|
|
106
|
+
utoMaster.utoTx(async (data) => {
|
|
107
|
+
if (device.opened) {
|
|
108
|
+
await device.transferOut(uto_master_1.USB_CONFIGURATION.utoEndpoint, data);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
if (debugLog) {
|
|
112
|
+
utoMaster.on('mdnCom', (message) => {
|
|
113
|
+
logger.log('mdnCom', message);
|
|
114
|
+
});
|
|
115
|
+
utoMaster.on('mdnComError', (message) => {
|
|
116
|
+
logger.log('mdnComError', message);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
// Set up progress reporting
|
|
120
|
+
let lastProgress = -1;
|
|
121
|
+
utoMaster.on('installing', (data) => {
|
|
122
|
+
if (!data) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
// /*const progress = Math.round(data.progress);
|
|
126
|
+
if (data !== lastProgress) {
|
|
127
|
+
lastProgress = data;
|
|
128
|
+
logger.info(`Upload progress: ${data}%`);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
// Open device and claim interfaces
|
|
132
|
+
await device.open();
|
|
133
|
+
await device.selectConfiguration(uto_master_1.USB_CONFIGURATION.configurationNumber);
|
|
134
|
+
await device.claimInterface(uto_master_1.USB_CONFIGURATION.utoInterface);
|
|
135
|
+
deviceConnected = true;
|
|
136
|
+
logger.info('Device connected, starting upload...');
|
|
137
|
+
// Start UTO polling for receiving data
|
|
138
|
+
startUtoPolling(device, utoMaster, () => deviceConnected);
|
|
139
|
+
await utoMaster.requestDeviceInfo();
|
|
140
|
+
// Upload the file
|
|
141
|
+
await utoMaster.sendFile(utoStack_1.UtoFileType.pawn, new Uint8Array(fileData.buffer, fileData.byteOffset, fileData.length));
|
|
142
|
+
logger.done('Upload completed successfully!');
|
|
143
|
+
return {
|
|
144
|
+
success: true,
|
|
145
|
+
message: 'File uploaded successfully'
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
150
|
+
return {
|
|
151
|
+
success: false,
|
|
152
|
+
message: `Upload failed: ${errorMessage}`
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
finally {
|
|
156
|
+
// Clean up - close device
|
|
157
|
+
deviceConnected = false;
|
|
158
|
+
if (device.opened) {
|
|
159
|
+
try {
|
|
160
|
+
await device.close().catch();
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
// Ignore close errors
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Start polling for UTO data from the device
|
|
170
|
+
* @param device - The USB device
|
|
171
|
+
* @param utoMaster - The UTO Master instance
|
|
172
|
+
* @param isConnected - Function that returns connection status
|
|
173
|
+
*/
|
|
174
|
+
async function startUtoPolling(device, utoMaster, isConnected) {
|
|
175
|
+
while (isConnected()) {
|
|
176
|
+
try {
|
|
177
|
+
const result = await device.transferIn(uto_master_1.USB_CONFIGURATION.utoEndpoint, uto_master_1.USB_CONFIGURATION.utoSize);
|
|
178
|
+
if (result.status === 'ok' && result.data) {
|
|
179
|
+
const uint8Data = new Uint8Array(result.data.buffer);
|
|
180
|
+
await utoMaster.utoRx(uint8Data);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
// Connection lost or polling stopped
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=usb.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@microtronics/studio-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.44.0",
|
|
4
4
|
"description": "Microtronics Studio CLI Tool",
|
|
5
5
|
"main": "./out/api.js",
|
|
6
6
|
"typings": "./dist/studio-cli.d.ts",
|
|
@@ -45,6 +45,8 @@
|
|
|
45
45
|
"semver": "^7.7.2",
|
|
46
46
|
"tinytar-fix": "^0.1.1",
|
|
47
47
|
"update-notifier": "^7.3.1",
|
|
48
|
+
"usb": "^2.14.0",
|
|
49
|
+
"uto-master": "bitbucket:microtronics-core/wasm-uto-master",
|
|
48
50
|
"vscode-uri": "^3.0.8",
|
|
49
51
|
"yaml": "^2.6.1"
|
|
50
52
|
},
|