@microtronics/studio-cli 0.48.0 → 0.50.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.
@@ -1843,7 +1843,12 @@ declare class DloCompiler {
1843
1843
  diagnostics: Diagnostics.File[];
1844
1844
  }>;
1845
1845
  /**
1846
- * Get a symbol compiler instance
1846
+ * Run the symbol compiler and return its output lines.
1847
+ * Handles WASM-specific path patching (`.` prefix for PROXYFS, `-R` flag).
1848
+ */
1849
+ getDloSymbols(mainFile: string, compileOptions: string[]): Promise<string[] | null>;
1850
+ /**
1851
+ * Get a symbol compiler instance with direct disk access via CUSTOM_WASM_FS.
1847
1852
  */
1848
1853
  getSymbolCompiler(): Promise<{
1849
1854
  compiler: any;
@@ -91,15 +91,36 @@ class DloCompiler {
91
91
  diagnostics: [...CustomWasmFS_1.precompilerDiagnostics, ...parseWarningsAndErrors(cwd, this.stderr, mainFile)]
92
92
  };
93
93
  }
94
+ /**
95
+ * Run the symbol compiler and return its output lines.
96
+ * Handles WASM-specific path patching (`.` prefix for PROXYFS, `-R` flag).
97
+ */
98
+ async getDloSymbols(mainFile, compileOptions) {
99
+ const symbolOptions = compileOptions.map(option => {
100
+ const command = option.substring(0, 2);
101
+ if (['-D', '-e', '-i', '-o', '-p', '-r', '-T'].includes(command)) {
102
+ return `${command}.${option.substring(2)}`;
103
+ }
104
+ return option;
105
+ });
106
+ const { compiler: symbolCompiler, print } = await this.getSymbolCompiler();
107
+ try {
108
+ const res = await symbolCompiler.callMain(['.' + mainFile, ...symbolOptions, '-R']);
109
+ if (res)
110
+ return null;
111
+ }
112
+ catch (e) {
113
+ if (e.stack?.startsWith('RuntimeError'))
114
+ return null;
115
+ throw e;
116
+ }
117
+ return [...print.stdout, ...print.stderr];
118
+ }
94
119
  //Symbol Compiler handling
95
120
  /**
96
- * Get a symbol compiler instance
121
+ * Get a symbol compiler instance with direct disk access via CUSTOM_WASM_FS.
97
122
  */
98
123
  async getSymbolCompiler() {
99
- // generate a none exiting wasm compiler runtime as main filesystem
100
- if (!this.dloCC) {
101
- await this.initDloCC(true);
102
- }
103
124
  const compilerLog = {
104
125
  stdout: [],
105
126
  stderr: []
@@ -113,13 +134,14 @@ class DloCompiler {
113
134
  compilerLog.stderr.push(err);
114
135
  }
115
136
  });
116
- // mount the main filesystem
117
- dloSymbol.FS.mkdir('/baseDir');
118
- dloSymbol.FS.mount(dloSymbol.PROXYFS, {
119
- root: '/',
120
- fs: this.dloCC.FS
121
- }, '/baseDir');
122
- dloSymbol.FS.chdir('baseDir');
137
+ // Mount CUSTOM_WASM_FS for direct disk access (same as initDloCC)
138
+ const currentCWD = (0, CustomWasmFS_1.uriToMemFsPath)(this.cwd);
139
+ const [rootDir] = currentCWD.split('/');
140
+ const FS = dloSymbol.FS;
141
+ const manifest = await manifest_1.Manifest.read(this.cwd, this.fs);
142
+ FS.filesystems.NODEFS_TS = (0, CustomWasmFS_1.CUSTOM_WASM_FS)(FS, this.cwd, manifest, this.logger);
143
+ FS.mkdir(rootDir);
144
+ FS.mount(FS.filesystems.NODEFS_TS, { root: '/' }, rootDir);
123
145
  return {
124
146
  compiler: dloSymbol,
125
147
  print: compilerLog
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@microtronics/studio-cli",
3
- "version": "0.48.0",
3
+ "version": "0.50.0",
4
4
  "description": "Microtronics Studio CLI Tool",
5
5
  "main": "./out/api.js",
6
6
  "typings": "./dist/studio-cli.d.ts",
@@ -13,8 +13,9 @@
13
13
  "scripts": {
14
14
  "release": "release-it --ci",
15
15
  "compile": "npm run build:registryApiTypings && tsc",
16
+ "build:cli": "node build.mjs",
16
17
  "api": "api-extractor run --local --verbose",
17
- "build": "npm run compile && npm run api",
18
+ "build": "npm run compile && npm run build:cli && npm run api",
18
19
  "watch:build": "npm run compile -- --watch",
19
20
  "test": "cross-env STUDIO_ENV=lucky mocha",
20
21
  "test-ci": "cross-env STUDIO_ENV=lucky mocha --reporter mocha-junit-reporter",
@@ -25,7 +26,6 @@
25
26
  "author": "Microtronics",
26
27
  "license": "ISC",
27
28
  "dependencies": {
28
- "uuid": "^9.0.1",
29
29
  "@gera2ld/tarjs": "^0.3.1",
30
30
  "ajv": "^8.17.1",
31
31
  "ajv-formats": "^3.0.1",
@@ -47,7 +47,7 @@
47
47
  "tinytar-fix": "^0.1.1",
48
48
  "update-notifier": "^7.3.1",
49
49
  "usb": "^2.14.0",
50
- "uto-master": "bitbucket:microtronics-core/wasm-uto-master",
50
+ "uuid": "^9.0.1",
51
51
  "vscode-uri": "^3.0.8",
52
52
  "yaml": "^2.6.1"
53
53
  },
@@ -67,13 +67,15 @@
67
67
  "chai": "^4.3.8",
68
68
  "chai-as-promised": "^7.1.2",
69
69
  "cross-env": "^7.0.3",
70
+ "esbuild": "^0.27.3",
70
71
  "extract-zip": "^2.0.1",
72
+ "uto-master": "bitbucket:microtronics-core/wasm-uto-master",
71
73
  "mocha": "^10.8.2",
72
74
  "mocha-junit-reporter": "^2.2.1",
73
75
  "openapi-typescript": "^7.6.0",
76
+ "tar": "^7.5.7",
74
77
  "ts-node": "^10.9.2",
75
- "typescript": "^5.6.3",
76
- "tar": "^7.5.7"
78
+ "typescript": "^5.6.3"
77
79
  },
78
80
  "mocha": {
79
81
  "require": [
package/studio-cli CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- require('./out/main')(process.argv);
2
+ require('./dist/main')(process.argv);
@@ -1,64 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.parseUnknownLongOptions = parseUnknownLongOptions;
4
- /**
5
- * Set a nested value in an object by a dot-separated path.
6
- * Example: setByDotPath(obj, 'dlo.mainFile', 'x') => obj = { dlo: { mainFile: 'x' } }
7
- */
8
- function setByDotPath(target, path, value) {
9
- const parts = path.split('.').filter(Boolean);
10
- let cur = target;
11
- for (let i = 0; i < parts.length; i++) {
12
- const key = parts[i];
13
- const isLeaf = i === parts.length - 1;
14
- if (isLeaf) {
15
- cur[key] = value;
16
- }
17
- else {
18
- if (typeof cur[key] !== 'object' || cur[key] === null || Array.isArray(cur[key])) {
19
- cur[key] = {};
20
- }
21
- cur = cur[key];
22
- }
23
- }
24
- }
25
- /**
26
- * Parse unknown long options (starting with `--`) into a key/value object.
27
- * - Supports `--key=value` and `--key value`
28
- */
29
- function parseUnknownLongOptions(command) {
30
- const argv = command.args;
31
- const result = {};
32
- for (let i = 0; i < argv.length; i++) {
33
- const token = argv[i];
34
- // Only process long options that start with `--`
35
- if (!token || !token.startsWith('--')) {
36
- continue;
37
- }
38
- // Split inline assignment: --key=value
39
- const [flag, inlineVal] = token.split('=', 2);
40
- // Unknown option: collect it
41
- const key = flag.slice(2); // remove leading '--'
42
- let value = true;
43
- if (inlineVal !== undefined) {
44
- value = inlineVal;
45
- }
46
- else {
47
- // If next token exists and doesn't look like another flag, use it as the value
48
- const next = argv[i + 1];
49
- if (next && !next.startsWith('-')) {
50
- value = next;
51
- i++; // consume the value
52
- }
53
- }
54
- // Dot-notation => nested object
55
- if (key.includes('.')) {
56
- setByDotPath(result, key, value);
57
- }
58
- else {
59
- result[key] = value;
60
- }
61
- }
62
- return result;
63
- }
64
- //# sourceMappingURL=argumentParser.js.map
package/out/cliFS.js DELETED
@@ -1,74 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.cliFS = void 0;
7
- const promises_1 = __importDefault(require("node:fs/promises"));
8
- const FS_1 = require("./FS");
9
- const vscode_uri_1 = require("vscode-uri");
10
- const glob_1 = require("glob");
11
- exports.cliFS = {
12
- readFile(path) {
13
- return promises_1.default.readFile(path.fsPath);
14
- },
15
- async rm(path) {
16
- if (await this.stat(path)) {
17
- return promises_1.default.rm(path.fsPath, { recursive: true });
18
- }
19
- },
20
- async stat(path) {
21
- try {
22
- const stat = await promises_1.default.stat(path.fsPath);
23
- const fileType = (stat.isDirectory() ? FS_1.FileType.Directory : 0) |
24
- (stat.isFile() ? FS_1.FileType.File : 0) |
25
- (stat.isSymbolicLink() ? FS_1.FileType.SymbolicLink : 0);
26
- return {
27
- ctime: stat.ctimeMs,
28
- mtime: stat.mtimeMs,
29
- permissions: undefined,
30
- size: stat.size,
31
- type: fileType
32
- };
33
- }
34
- catch (e) {
35
- return null;
36
- }
37
- },
38
- async writeFile(path, content) {
39
- if (typeof content === 'string') {
40
- content = new TextEncoder().encode(content);
41
- }
42
- const dirPath = vscode_uri_1.Utils.dirname(path);
43
- await promises_1.default.mkdir(dirPath.fsPath, { recursive: true });
44
- return promises_1.default.writeFile(path.fsPath, content, { flag: 'w' });
45
- },
46
- async findFiles(cwd, include, exclude) {
47
- const options = {
48
- ignore: exclude,
49
- nodir: true,
50
- absolute: true,
51
- dot: true,
52
- cwd: cwd.fsPath
53
- };
54
- const files = await (0, glob_1.glob)(include, options);
55
- return files.map(file => {
56
- const posix = file.replace(/\\/g, '/');
57
- return vscode_uri_1.URI.parse(`file:///${posix}`);
58
- });
59
- },
60
- async copy(source, target) {
61
- const stat = await promises_1.default.stat(source.fsPath);
62
- await promises_1.default.mkdir(vscode_uri_1.Utils.dirname(target).fsPath, { recursive: true });
63
- if (stat.isDirectory()) {
64
- return promises_1.default.cp(source.fsPath, target.fsPath, { force: true, recursive: true });
65
- }
66
- else {
67
- return promises_1.default.copyFile(source.fsPath, target.fsPath);
68
- }
69
- },
70
- async mkdir(path) {
71
- await promises_1.default.mkdir(path.fsPath, { recursive: true });
72
- }
73
- };
74
- //# sourceMappingURL=cliFS.js.map
package/out/main.js DELETED
@@ -1,251 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const commander_1 = require("commander");
7
- const package_json_1 = __importDefault(require("../package.json"));
8
- const cliFS_1 = require("./cliFS");
9
- const vscode_uri_1 = require("vscode-uri");
10
- const apm_1 = require("./package/apm");
11
- const globals_1 = require("./globals");
12
- const update_notifier_1 = __importDefault(require("update-notifier"));
13
- const api_1 = require("./api");
14
- const log_1 = require("./log");
15
- const argumentParser_1 = require("./argumentParser");
16
- const usb_1 = require("./usb");
17
- const run_1 = require("./run");
18
- const program = new commander_1.Command();
19
- const notifier = (0, update_notifier_1.default)({ pkg: package_json_1.default });
20
- const logger = new log_1.Log.Logger();
21
- // dummy cwd
22
- const devPath = () => {
23
- if (process.env.NODE_ENV === 'local-dev-app') {
24
- return './dev/app';
25
- }
26
- else if (process.env.NODE_ENV === 'local-dev-lib') {
27
- return './dev/modbus-master';
28
- }
29
- else if (process.env.NODE_ENV === 'local-dev-addon') {
30
- return './dev/addon';
31
- }
32
- else if (process.env.NODE_ENV === 'local-dev-analytics') {
33
- return './dev/analytics';
34
- }
35
- return '';
36
- };
37
- const cwd = vscode_uri_1.Utils.joinPath(vscode_uri_1.URI.file(process.cwd()), devPath());
38
- program.version(package_json_1.default.version).usage('<command>');
39
- program.description('This tool is used to manage the project dependencies, build, package and publish the project.\n' +
40
- 'The manifest options can be overwritten with the --option=value syntax.\n' +
41
- 'Example: "studio build --dlo.mainFile="./dlo/test.dlo" will overwrite the dlo.mainFile option of the manifest.\n' +
42
- '\nUse "studio --help" for more information.');
43
- /**
44
- * Executes a task and ensures that the update available notification is sent after the task completes,
45
- * regardless of whether it succeeds or fails.
46
- *
47
- * @param command
48
- * @param {Promise<void>} task - A promise representing the task to be executed.
49
- * @return {void} This function does not return a value.
50
- */
51
- function main(command, task) {
52
- const manifestOptions = (0, argumentParser_1.parseUnknownLongOptions)(command);
53
- api_1.Manifest.setOverwrites(manifestOptions);
54
- // todo add task catch handler like: https://github.com/microsoft/vscode-vsce/blob/5518f2f242983d23f1983f6dd0350474f2011543/src/main.ts#L51
55
- task.finally(() => {
56
- notifier.notify();
57
- });
58
- }
59
- function handleStudioEnv(env) {
60
- process.env.STUDIO_ENV = env || globals_1.Globals.ENV.production;
61
- if (env && env !== globals_1.Globals.ENV.production) {
62
- console.log(`Using STUDIO_ENV=${env}`);
63
- }
64
- return process.env.STUDIO_ENV;
65
- }
66
- function getStudioApiToken(opts) {
67
- return opts?.token || null;
68
- }
69
- function getDefaultOptions(opts) {
70
- return {
71
- env: handleStudioEnv(opts.env),
72
- token: getStudioApiToken(opts)
73
- };
74
- }
75
- /**
76
- * Generic option for the STUDIO_ENV
77
- */
78
- const optionStudioEnv = new commander_1.Option('-e, --env <environment>', 'CLI target environment (defaults to STUDIO_ENV environment variable)')
79
- .default(process.env['STUDIO_ENV'])
80
- .choices(Object.values(globals_1.Globals.ENV));
81
- /**
82
- * Generic option for the STUDIO_TOKEN
83
- */
84
- const optionStudioToken = new commander_1.Option('--token <API-token>', 'API Access Token (defaults to STUDIO_TOKEN environment variable)').default(process.env['STUDIO_TOKEN']);
85
- /**
86
- * Represents a command-line option for specifying the allowed backends deployment for a version.
87
- *
88
- * This variable represents an instance of an `Option` that determines which backends are permitted
89
- * during the deployment process. The backends can be specified as*/
90
- const optionAllowedBackends = new commander_1.Option('--allowedBackends <null|*|server.xz;server.yz>', 'specify the allowedBackends deployment for this version.').default(null);
91
- const releasePhaseArguments = Object.values(apm_1.APM.TagPhase);
92
- /**
93
- * Represents a command-line option for specifying a release phase in a software deployment process.
94
- *
95
- * The option includes a flag `-ph` or `--phase`, followed by the user-defined release phase value. It offers predefined
96
- * choices for acceptable release phase arguments, ensuring input validation. If no value is explicitly provided,
97
- * the default release phase is set to 'release'.
98
- */
99
- const optionReleasePhase = new commander_1.Option('-ph --phase <release phase>', 'specify a release phase')
100
- .choices(releasePhaseArguments)
101
- .default('release');
102
- /**
103
- * Option for specifying the APM part to build
104
- */
105
- const optionApmPart = new commander_1.Option('-p --part <APM part>', 'APM part that should be built').choices(Object.values(apm_1.APM.Part));
106
- /**
107
- * Option for device serial number (16 characters hex)
108
- */
109
- const optionDeviceSerial = new commander_1.Option('-s, --serial <serial>', 'Device serial number (16 characters hex)');
110
- /**
111
- * Option for enabling debug logging
112
- */
113
- const optionDebug = new commander_1.Option('--debug', 'Enable debug logging').default(false);
114
- /**
115
- * Option for myDatanet instance URL
116
- */
117
- const optionMdnServer = new commander_1.Option('--server <url>', 'myDatanet instance URL (https:// prefix optional)').default(process.env['MYDATANET_HOST']);
118
- /**
119
- * Option for myDatanet customer UID or ID
120
- */
121
- const optionMdnCustomer = new commander_1.Option('--customer <customerId>', 'myDatanet customer UID or ID').default(process.env['MYDATANET_CUSTOMER']);
122
- /**
123
- * Option for myDatanet API token
124
- */
125
- const optionMdnToken = new commander_1.Option('--mdn-token <token>', 'myDatanet API token').default(process.env['MYDATANET_TOKEN']);
126
- program
127
- .command('install')
128
- .aliases(['i'])
129
- .description('install all the dependencies in your project')
130
- .arguments('[dependency...]')
131
- .allowExcessArguments(true)
132
- .addOption(optionStudioEnv)
133
- .addOption(optionStudioToken)
134
- .allowUnknownOption(true)
135
- .action((dependencies, opts, command) => {
136
- const { env, token } = getDefaultOptions(opts);
137
- main(command, (0, api_1.install)(cwd, cliFS_1.cliFS, env, token, dependencies));
138
- });
139
- program
140
- .command('build')
141
- .addOption(optionApmPart)
142
- .allowExcessArguments(true)
143
- .addOption(optionStudioEnv)
144
- .addOption(optionStudioToken)
145
- .allowUnknownOption(true)
146
- .action((opts, command) => {
147
- const { env, token } = getDefaultOptions(opts);
148
- main(command, (0, api_1.build)(cwd, cliFS_1.cliFS, logger, env, token, opts.part));
149
- });
150
- program
151
- .command('publish')
152
- .addOption(optionReleasePhase)
153
- .addOption(optionAllowedBackends)
154
- .addOption(optionStudioEnv)
155
- .addOption(optionStudioToken)
156
- .allowUnknownOption(true)
157
- .option('-i, --packagePath <path>', 'Publish the provided library or app package. Use "*" for autodetection.')
158
- .action(async (opts, command) => {
159
- const { env, token } = getDefaultOptions(opts);
160
- main(command, (0, api_1.publish)(cwd, cliFS_1.cliFS, logger, env, token, opts.allowedBackends, opts.phase, opts.packagePath));
161
- });
162
- program
163
- .command('package')
164
- .alias('pack')
165
- .description('Package the project')
166
- .addOption(optionReleasePhase)
167
- .addOption(optionStudioEnv)
168
- .addOption(optionStudioToken)
169
- .allowUnknownOption(true)
170
- .action(async (opts, command) => {
171
- const { env, token } = getDefaultOptions(opts);
172
- main(command, (0, api_1.pack)(cwd, cliFS_1.cliFS, logger, env, token, opts.phase));
173
- });
174
- // Run command - Build, upload to device, and create development site
175
- program
176
- .command('run')
177
- .description('Build, upload to USB device, and create development site on myDatanet')
178
- .addOption(optionDeviceSerial.makeOptionMandatory(true))
179
- .addOption(optionMdnServer)
180
- .addOption(optionMdnCustomer)
181
- .addOption(optionMdnToken)
182
- .addOption(optionApmPart)
183
- .addOption(optionStudioEnv)
184
- .addOption(optionStudioToken)
185
- .addOption(optionDebug)
186
- .allowUnknownOption(true)
187
- .action(async (opts, command) => {
188
- const { env, token } = getDefaultOptions(opts);
189
- const mdnToken = opts.mdnToken;
190
- const server = opts.server;
191
- // Validate required options
192
- if (!mdnToken) {
193
- logger.error('MYDATANET_TOKEN is required. Set via --mdn-token or MYDATANET_TOKEN environment variable');
194
- notifier.notify();
195
- process.exit(run_1.RunExitCode.AuthenticationFailed);
196
- }
197
- if (!server) {
198
- logger.error('Server URL is required. Set via --server or MYDATANET_HOST environment variable');
199
- notifier.notify();
200
- process.exit(run_1.RunExitCode.AuthenticationFailed);
201
- }
202
- const manifestOptions = (0, argumentParser_1.parseUnknownLongOptions)(command);
203
- api_1.Manifest.setOverwrites(manifestOptions);
204
- const result = await (0, run_1.run)(cwd, cliFS_1.cliFS, logger, env, token, {
205
- serial: opts.serial,
206
- server: server,
207
- customer: opts.customer,
208
- mdnToken: mdnToken,
209
- debug: opts.debug,
210
- part: opts.part ? [opts.part] : undefined
211
- });
212
- notifier.notify();
213
- if (result.success) {
214
- process.exit(run_1.RunExitCode.Success);
215
- }
216
- else {
217
- logger.error(result.message);
218
- process.exit(result.errorCode || 1);
219
- }
220
- });
221
- // USB commands
222
- const usbCommand = program.command('usb').description('USB device management commands');
223
- usbCommand
224
- .command('list')
225
- .description('List available USB devices')
226
- .action(async () => {
227
- await (0, usb_1.printUsbDevices)(logger);
228
- notifier.notify();
229
- });
230
- usbCommand
231
- .command('upload')
232
- .description('Upload an AMX binary to a USB device')
233
- .addOption(optionDeviceSerial.makeOptionMandatory(true))
234
- .requiredOption('-f, --file <path>', 'Path to the AMX file (relative to current directory)')
235
- .addOption(optionDebug)
236
- .action(async (opts) => {
237
- const fileUri = vscode_uri_1.Utils.joinPath(cwd, opts.file);
238
- const result = await (0, usb_1.uploadToDevice)(opts.serial, fileUri, cliFS_1.cliFS, logger, opts.debug);
239
- notifier.notify();
240
- if (!result.success) {
241
- logger.error(result.message);
242
- process.exit(1);
243
- }
244
- else {
245
- process.exit(0);
246
- }
247
- });
248
- module.exports = function (argv) {
249
- program.parse(argv);
250
- };
251
- //# sourceMappingURL=main.js.map