@microtronics/studio-cli 1.2.0-alpha.1 → 1.2.0-alpha.3

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.
@@ -1,9 +1,19 @@
1
+ import { AtCommandHandler } from '@microtronics/device-master';
2
+ import { DdeModel } from '@microtronics/device-master';
3
+ import { DdeRecord } from '@microtronics/device-master';
1
4
  import { Document as Document_2 } from 'yaml';
5
+ import { EventEmitter } from 'node:events';
6
+ import { ITimeSource } from '@microtronics/device-master';
7
+ import { ITransport } from '@microtronics/device-master';
2
8
  import { LineCounter } from 'yaml';
9
+ import { LocalApiRequest } from '@microtronics/device-master';
10
+ import { LocalApiResponse } from '@microtronics/device-master';
11
+ import { MemoryStorage } from '@microtronics/device-master';
3
12
  import { ParsedNode } from 'yaml';
4
13
  import { Range as Range_2 } from 'yaml';
5
14
  import { SemVer } from 'semver';
6
15
  import { URI } from 'vscode-uri';
16
+ import { UtoMaster } from '@microtronics/device-master';
7
17
 
8
18
  export declare namespace APM {
9
19
  export enum TagPhase {
@@ -395,6 +405,18 @@ declare interface components {
395
405
  pathItems: never;
396
406
  }
397
407
 
408
+ /** One tag whose device stamp overtook the mirror and was therefore pulled. */
409
+ export declare interface ContainerChange {
410
+ tag: number;
411
+ /** DDE container name, when the tag maps to one. */
412
+ container: string | null;
413
+ alias?: string;
414
+ /** Device stamp40 the mirror now holds. */
415
+ stamp: number;
416
+ /** Decoded record, for tags that map to a DDE container. */
417
+ decoded?: DdeRecord | null;
418
+ }
419
+
398
420
  declare const createPawnCC: PawnModuleFactory;
399
421
 
400
422
  declare const createPawnDbg: PawnModuleFactory;
@@ -1776,6 +1798,8 @@ export declare namespace DefaultFiles {
1776
1798
  legacyAutoUplinkInc: string;
1777
1799
  reportTemplate: string;
1778
1800
  dloCfg: string;
1801
+ devMirror: string;
1802
+ devDeviceLog: string;
1779
1803
  };
1780
1804
  export const filePaths: {
1781
1805
  studio: {
@@ -1801,6 +1825,11 @@ export declare namespace DefaultFiles {
1801
1825
  };
1802
1826
  };
1803
1827
  libdeps: string;
1828
+ dev: {
1829
+ path: string;
1830
+ mirror: string;
1831
+ deviceLog: string;
1832
+ };
1804
1833
  };
1805
1834
  dist: {
1806
1835
  path: string;
@@ -1985,6 +2014,15 @@ export declare namespace Dependencies {
1985
2014
  * @param logger
1986
2015
  */
1987
2016
  export function normalizeInstalledLibraryParts(cwd: URI, fs: LocalFS, logger: Log.Logger): Promise<void>;
2017
+ /**
2018
+ * Validate that every installed library dependency (direct and transitive, including dev dependencies)
2019
+ * requires a minimum server version ("engines.backend") that the current project (application or library)
2020
+ * already satisfies. Does not modify the manifest - raising "engines.backend" stays an explicit developer decision.
2021
+ * @param cwd
2022
+ * @param fs
2023
+ * @param logger
2024
+ */
2025
+ export function validateMinimumBackendVersion(cwd: URI, fs: LocalFS, logger: Log.Logger): Promise<void>;
1988
2026
  /**
1989
2027
  * Install npm dependencies in pov and blo working paths if they exist and contain a package.json.
1990
2028
  * This ensures that a single `studio install` command sets up the entire project.
@@ -2028,6 +2066,23 @@ export declare namespace Dependencies {
2028
2066
  export function unlinkLibraryApmParts(cwd: URI, fs: LocalFS, logger: Log.Logger, libraryName: string): Promise<void>;
2029
2067
  }
2030
2068
 
2069
+ /**
2070
+ * Start a `studio dev` session and its local HTTP server.
2071
+ *
2072
+ * Build → connect USB → flash → parse `dist/dde/dde.xml` → serve the myDatanet-compatible
2073
+ * `/api/**` surface plus `/dev/**` and `/events` on localhost.
2074
+ */
2075
+ export declare function dev(cwd: URI, fs: LocalFS, options: DevOptions): Promise<DevResult>;
2076
+
2077
+ /** Diagnostics of the last `build()`, as served by `/dev/status` and `/dev/build`. */
2078
+ export declare interface DevBuildState {
2079
+ running: boolean;
2080
+ ok: boolean | null;
2081
+ at: number | null;
2082
+ message?: string;
2083
+ diagnostics?: Package.MergedDiagnostics;
2084
+ }
2085
+
2031
2086
  /**
2032
2087
  * Development installation information
2033
2088
  */
@@ -2045,8 +2100,261 @@ export declare interface DevelopmentInstallInfo {
2045
2100
  registrationCodeNeeded?: boolean;
2046
2101
  }
2047
2102
 
2103
+ /** An error carrying the process exit code the CLI should terminate with. */
2104
+ export declare class DevError extends Error {
2105
+ readonly exitCode: DevExitCode;
2106
+ constructor(message: string, exitCode: DevExitCode);
2107
+ }
2108
+
2109
+ /**
2110
+ * Exit codes for `studio dev` / `studio device`. 0–3 mirror
2111
+ * {@link import('../myDatanetClient').RunExitCode}; 7–9 are dev-mode specific.
2112
+ */
2113
+ export declare enum DevExitCode {
2114
+ Success = 0,
2115
+ BuildFailed = 1,
2116
+ DeviceNotFound = 2,
2117
+ UsbFailed = 3,
2118
+ ModelMissing = 7,
2119
+ UnknownContainer = 8,
2120
+ WriteForbidden = 9
2121
+ }
2122
+
2048
2123
  export declare const DeviceProfiles: any;
2049
2124
 
2125
+ /**
2126
+ * The pair of transports (UTO data + AT debug) needed to talk to a connected device,
2127
+ * plus a way to release the underlying link.
2128
+ */
2129
+ declare interface DeviceTransports {
2130
+ uto: ITransport;
2131
+ /** Absent when the caller asked for the UTO interface only (`needDebug: false`). */
2132
+ debug?: ITransport;
2133
+ close(): Promise<void>;
2134
+ }
2135
+
2136
+ /** One ring-buffer record, as served by `GET /dev/logs` and the SSE `log` event. */
2137
+ export declare interface DevLogRecord {
2138
+ /** Monotonic sequence number; `GET /dev/logs?since=<seq>` returns everything after it. */
2139
+ seq: number;
2140
+ /** Wall-clock ms. */
2141
+ ts: number;
2142
+ source: DevLogSource;
2143
+ text: string;
2144
+ /** Base64 payload, only for records whose bytes could not be decoded to text. */
2145
+ raw?: string;
2146
+ }
2147
+
2148
+ /** Where a log record came from. */
2149
+ export declare type DevLogSource = 'rm2mlog' | 'at' | 'uto' | 'pov';
2150
+
2151
+ export declare interface DevOptions {
2152
+ serial: string;
2153
+ host?: string;
2154
+ port?: number;
2155
+ /** Allow a non-loopback `host` without a bearer token. */
2156
+ allowRemote?: boolean;
2157
+ /** Rebuild, flash and sync on source changes. */
2158
+ watch?: boolean;
2159
+ /** Build before connecting (default `true`). */
2160
+ build?: boolean;
2161
+ /** Flash `dist/dlo/main.amx` after connecting (default `true`). */
2162
+ flash?: boolean;
2163
+ pollIntervalMs?: number;
2164
+ /** How long a write waits for the device to confirm, in ms. */
2165
+ waitMs?: number;
2166
+ logFile?: string;
2167
+ /** Bearer token required by the server; absent ⇒ `Authorization` is ignored. */
2168
+ token?: string;
2169
+ part?: APM.Part[];
2170
+ debug?: boolean;
2171
+ env?: Globals.ENV;
2172
+ apiToken?: string | null;
2173
+ logger?: Log.Logger;
2174
+ /** Spawn `npm run dev -w pov/<name>` with `MYDATANET_HOST` pointing at this server. */
2175
+ pov?: string | boolean;
2176
+ /** Persist the mirror to `.studio/dev/mirror.json` (default `true`). */
2177
+ persistMirror?: boolean;
2178
+ /** Test/replay seams, forwarded to {@link DevSession}. */
2179
+ transportFactory?: TransportFactory;
2180
+ createDevice?: DevSessionOptions['createDevice'];
2181
+ }
2182
+
2183
+ export declare interface DevResult {
2184
+ url: string;
2185
+ port: number;
2186
+ session: DevSession;
2187
+ apmId: string;
2188
+ close(): Promise<void>;
2189
+ }
2190
+
2191
+ export declare interface DevServer {
2192
+ url: string;
2193
+ port: number;
2194
+ close(): Promise<void>;
2195
+ }
2196
+
2197
+ /**
2198
+ * One `studio dev` session: owns the USB link, the project's DDE model, the `IStorage`
2199
+ * mirror and the myDatanet-compatible API handler, and fans device activity out as events.
2200
+ *
2201
+ * Events: `log` ({@link DevLogRecord}), `state`, `sync`, `build`, `uto`,
2202
+ * `container-changed` ({@link ContainerChange}), `error`.
2203
+ */
2204
+ export declare class DevSession extends EventEmitter {
2205
+ readonly logStream: LogStream;
2206
+ private readonly opts;
2207
+ private readonly logger;
2208
+ private readonly timeSource;
2209
+ private device;
2210
+ private mirror;
2211
+ private model;
2212
+ private mapper;
2213
+ private apiHandler;
2214
+ private poller;
2215
+ private stateManager;
2216
+ private localData;
2217
+ private offlineSync;
2218
+ private buildState;
2219
+ private lastSyncAt;
2220
+ private firmwareVersion;
2221
+ private mirrorSaveTimer;
2222
+ private stopped;
2223
+ private readonly lock;
2224
+ constructor(options: DevSessionOptions);
2225
+ get serial(): string;
2226
+ get connected(): boolean;
2227
+ /**
2228
+ * Read by {@link StampPoller}: true while a flash, sync, write or AT command owns the
2229
+ * UTO link, so a poll tick cannot interleave with a multi-frame transfer.
2230
+ */
2231
+ get busy(): boolean;
2232
+ /** The parsed project DDE. */
2233
+ get ddeModel(): DdeModel;
2234
+ get storage(): MemoryStorage;
2235
+ /**
2236
+ * Build (optional) → load the model → connect USB → flash (optional) → start the stamp
2237
+ * poller. Rejects with a {@link DevError} whose `exitCode` the CLI exits with.
2238
+ */
2239
+ start(): Promise<void>;
2240
+ /** Read and parse `dist/dde/dde.xml`. Missing → {@link DevExitCode.ModelMissing}. */
2241
+ private loadModel;
2242
+ private connect;
2243
+ private startPoller;
2244
+ private tagLabel;
2245
+ private get utoMaster();
2246
+ private get atHandler();
2247
+ /** Assemble the {@link LocalApiContext} and bind the router to it. */
2248
+ private buildApiHandler;
2249
+ /**
2250
+ * Serve one `/api/**` request. A successful write additionally triggers an immediate
2251
+ * poll tick and waits (bounded by `waitMs`) until the device's own stamp for the
2252
+ * written tag has reached the pushed stamp — proof the device accepted the frame.
2253
+ * When it does not, `x-mt-mirror: stale` is added; `?async=1` skips the wait entirely.
2254
+ */
2255
+ handleApi(req: LocalApiRequest): Promise<LocalApiResponse>;
2256
+ /**
2257
+ * The `uto_data` tag a request writes, or `null` for a read / an `?async=1` write /
2258
+ * a path whose container cannot be resolved.
2259
+ */
2260
+ private writtenTag;
2261
+ /**
2262
+ * Poll until the device reports a stamp ≥ `pushedStamp` for `tag`, or the wait budget
2263
+ * runs out. Returns `false` on timeout.
2264
+ */
2265
+ private awaitDeviceStamp;
2266
+ /** Run the project build; records diagnostics in {@link DevBuildState}. */
2267
+ build(): Promise<DevBuildState>;
2268
+ /**
2269
+ * Upload an AMX to the device (defaults to `dist/dlo/main.amx`).
2270
+ *
2271
+ * `uploadToDevice` owns its own connection, and a USB device is single-owner, so the
2272
+ * session's link is released for the duration and reopened afterwards. The tag table
2273
+ * can change with a new app, so DEVINFO is re-read before the poller resumes.
2274
+ */
2275
+ flash(file?: string): Promise<UploadResult>;
2276
+ /** Reopen the USB link after a flash; a failure is logged and leaves `connected: false`. */
2277
+ private reconnect;
2278
+ /** Full `OfflineSyncManager` sync, or a targeted pull of the given tags. */
2279
+ sync(tags?: number[]): Promise<{
2280
+ tags: number[];
2281
+ at: number;
2282
+ }>;
2283
+ /** One AT command; queued behind a flash, which closes the AT handler's transport. */
2284
+ at(cmd: string): Promise<string>;
2285
+ /** Last known rm2m state; fetches one when nothing has been read yet. */
2286
+ state(): Promise<unknown>;
2287
+ /** Live local-data values. */
2288
+ localDataSnapshot(): unknown;
2289
+ /** Trigger a measurement and return its outcome plus the fresh values. */
2290
+ measure(): Promise<unknown>;
2291
+ status(): DevStatus;
2292
+ /** Force one poll pass (used by the watcher and by `POST /dev/sync`). */
2293
+ tick(): Promise<ContainerChange[]>;
2294
+ /** The mirror's current stamp40 for a tag, or 0. */
2295
+ mirrorStamp(tag: number): Promise<number>;
2296
+ /** Run `fn` as the sole owner of the UTO link (see {@link UtoLock}). */
2297
+ private withLink;
2298
+ private closeDevice;
2299
+ stop(): Promise<void>;
2300
+ }
2301
+
2302
+ export declare interface DevSessionOptions {
2303
+ cwd: URI;
2304
+ fs: LocalFS;
2305
+ /** 16-character hex device serial. */
2306
+ serial: string;
2307
+ /** Overrides how the USB transports are obtained (tests, later `--replay`). */
2308
+ transportFactory?: TransportFactory;
2309
+ /**
2310
+ * Test seam: supply the connected device (UtoMaster + AtCommandHandler) instead of
2311
+ * opening USB. The unit tests use it because `UtoMaster.init()` needs the real WASM
2312
+ * UTO wire protocol, which a byte-level mock cannot produce.
2313
+ */
2314
+ createDevice?: (serial: string) => Promise<OpenDevice>;
2315
+ /** Stamp-poll interval in ms; `0` disables polling. */
2316
+ pollIntervalMs?: number;
2317
+ /** Flash `dist/dlo/main.amx` to the device on start. */
2318
+ flash: boolean;
2319
+ /** Build the project on start. */
2320
+ build: boolean;
2321
+ part?: APM.Part[];
2322
+ logger: Log.Logger;
2323
+ timeSource?: ITimeSource;
2324
+ env?: Globals.ENV;
2325
+ apiToken?: string | null;
2326
+ /** How long a write waits for the device to confirm, in ms. `0` disables the wait. */
2327
+ waitMs?: number;
2328
+ /** JSONL log file for every {@link DevLogRecord}. */
2329
+ logFile?: string;
2330
+ /** Persist the mirror to `.studio/dev/mirror.json`. */
2331
+ persistMirror?: boolean;
2332
+ debug?: boolean;
2333
+ /** Test seam: replaces the AMX upload {@link flash} performs. */
2334
+ uploadToDevice?: typeof uploadToDevice;
2335
+ /** How long UTO work waits for the link while a flash owns it, in ms. */
2336
+ lockWaitMs?: number;
2337
+ }
2338
+
2339
+ /** `GET /dev/status` payload. */
2340
+ export declare interface DevStatus {
2341
+ serial: string;
2342
+ connected: boolean;
2343
+ apmId: string;
2344
+ firmwareVersion: string | null;
2345
+ model: {
2346
+ containers: Array<{
2347
+ name: string;
2348
+ alias?: string;
2349
+ kind: string;
2350
+ tag: number | null;
2351
+ }>;
2352
+ };
2353
+ lastSync: number | null;
2354
+ buildState: DevBuildState;
2355
+ poll: StampPollerStatus;
2356
+ }
2357
+
2050
2358
  export declare namespace DFILES {
2051
2359
  export enum Type {
2052
2360
  static = "static",
@@ -2615,6 +2923,53 @@ export declare namespace Log {
2615
2923
  }
2616
2924
  }
2617
2925
 
2926
+ /**
2927
+ * Ring buffer of device log records with SSE fan-out.
2928
+ *
2929
+ * Fed by two taps:
2930
+ * - `UtoMaster`'s `'utoFrame'` event, filtered to {@link UtoTag.LOG} (rm2mlog).
2931
+ * - `AtCommandHandler`'s `'data'` event, which the handler emits for every decoded
2932
+ * chunk of debug-channel RX (its documented "raw monitoring" hook) — this is where
2933
+ * a DLO's live `printf` output arrives.
2934
+ *
2935
+ * The LOG payload layout is not modelled: the record's leading bytes are decoded
2936
+ * best-effort (cp1252, then UTF-8) and cut at the first NUL. A payload that does not
2937
+ * decode to text is stored as base64 in {@link DevLogRecord.raw} with a hex preview as
2938
+ * its `text`, so nothing is silently dropped.
2939
+ */
2940
+ declare class LogStream extends EventEmitter {
2941
+ private readonly buffer;
2942
+ private readonly capacity;
2943
+ private readonly logFile;
2944
+ private readonly logger;
2945
+ private seq;
2946
+ private disposers;
2947
+ /** Serialises the JSONL appends so records cannot interleave mid-line. */
2948
+ private writeChain;
2949
+ /** `mkdir -p` of the log file's directory, done once instead of per record. */
2950
+ private logDirReady;
2951
+ constructor(opts?: {
2952
+ capacity?: number;
2953
+ logFile?: string;
2954
+ logger?: Log.Logger;
2955
+ });
2956
+ /** Append a record, evict the oldest when full, emit `record` and mirror to `--log-file`. */
2957
+ push(source: DevLogSource, text: string, raw?: Uint8Array): DevLogRecord;
2958
+ private appendToFile;
2959
+ /** Records after `since`, newest last, at most `limit` (`<= 0` ⇒ the default limit). */
2960
+ since(since?: number, limit?: number): DevLogRecord[];
2961
+ /** The highest sequence number handed out so far. */
2962
+ get lastSeq(): number;
2963
+ /** Tap rm2mlog frames off the UTO channel. */
2964
+ attachUtoMaster(utoMaster: UtoMaster): void;
2965
+ /** Tap the AT/debug channel RX, where a DLO's live `printf` output arrives. */
2966
+ attachAtHandler(atHandler: AtCommandHandler): void;
2967
+ /** Decode a device payload best-effort; keep the bytes when it is not text. */
2968
+ private pushDecoded;
2969
+ /** Detach the taps and await the pending JSONL appends, so no record is lost. */
2970
+ dispose(): Promise<void>;
2971
+ }
2972
+
2618
2973
  export declare namespace Manifest {
2619
2974
  /**
2620
2975
  * The source filename where the information is stored in
@@ -2771,6 +3126,16 @@ export declare namespace Manifest {
2771
3126
  export function validateRegistryAllowedApplications(allowedApplications: undefined | null | string | string[]): Promise<string | null>;
2772
3127
  export function validateApplicationIcon(cwd: URI, fs: LocalFS, manifest: Manifest.Manifest): Promise<void>;
2773
3128
  export function validateEngineSettings(manifest: Manifest.Manifest): void;
3129
+ /**
3130
+ * Compare two "engines.backend" version strings (format like "52v006").
3131
+ * The major and minor parts are compared numerically so that values outside the zero-padded 2-digit
3132
+ * major format (e.g. "100v001") are still ordered correctly. Values that do not match the format fall
3133
+ * back to plain string ordering.
3134
+ * @param a
3135
+ * @param b
3136
+ * @returns a negative number if a < b, 0 if equal, a positive number if a > b
3137
+ */
3138
+ export function compareBackendVersion(a: string, b: string): number;
2774
3139
  /**
2775
3140
  * Validate if the given apm part is used
2776
3141
  * @param cwd
@@ -2909,6 +3274,20 @@ export declare interface MyDatanetUserInfo {
2909
3274
  customers?: MyDatanetCustomer[];
2910
3275
  }
2911
3276
 
3277
+ /**
3278
+ * A connected device: the UTO/AT primitives plus the raw transports, ready to use.
3279
+ * Call `close()` exactly once when done to dispose the AtCommandHandler/UtoMaster and
3280
+ * release the underlying transports.
3281
+ */
3282
+ declare interface OpenDevice {
3283
+ utoMaster: UtoMaster;
3284
+ /** Only present when the debug interface was claimed (`needDebug`, the default). */
3285
+ atHandler?: AtCommandHandler;
3286
+ transports: DeviceTransports;
3287
+ serial: string;
3288
+ close(): Promise<void>;
3289
+ }
3290
+
2912
3291
  /**
2913
3292
  * Packs the given directory into a deployable package using the specified configuration.
2914
3293
  *
@@ -5316,6 +5695,40 @@ export declare namespace Registry {
5316
5695
  } | {};
5317
5696
  }
5318
5697
 
5698
+ /** `GET /dev/status`'s `poll` section. */
5699
+ export declare interface StampPollerStatus {
5700
+ intervalMs: number;
5701
+ lastTick: number | null;
5702
+ lastChange: number | null;
5703
+ errors: number;
5704
+ paused: boolean;
5705
+ }
5706
+
5707
+ /**
5708
+ * Opens the transports for a device given its serial number. The default (WebUSB) factory
5709
+ * is used by {@link openDevice} unless a test/consumer supplies its own.
5710
+ */
5711
+ declare type TransportFactory = (serial: string, needDebug?: boolean) => Promise<DeviceTransports>;
5712
+
5713
+ /**
5714
+ * Upload result interface
5715
+ */
5716
+ declare interface UploadResult {
5717
+ success: boolean;
5718
+ message: string;
5719
+ }
5720
+
5721
+ /**
5722
+ * Upload an AMX binary file to a USB device
5723
+ * @param serialNumber - The serial number of the target device
5724
+ * @param fileUri - The URI to the AMX file
5725
+ * @param fs - The filesystem interface for file operations
5726
+ * @param logger - The logger instance for output
5727
+ * @param debugLog - Enable debug logging
5728
+ * @returns Promise resolving to an UploadResult
5729
+ */
5730
+ declare function uploadToDevice(serialNumber: string, fileUri: URI, fs: LocalFS, logger: Log.Logger, debugLog?: boolean): Promise<UploadResult>;
5731
+
5319
5732
  declare function uriToMemFsPath(path: URI): string;
5320
5733
 
5321
5734
  declare interface XMLField {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@microtronics/studio-cli",
3
- "version": "1.2.0-alpha.1",
3
+ "version": "1.2.0-alpha.3",
4
4
  "description": "Microtronics Studio CLI Tool",
5
5
  "main": "./out/api.js",
6
6
  "typings": "./out/studio-cli.d.ts",
@@ -56,7 +56,7 @@
56
56
  },
57
57
  "devDependencies": {
58
58
  "@microsoft/api-extractor": "^7.47.11",
59
- "@microtronics/device-master": "bitbucket:microtronics-core/device-master#v3.0.0",
59
+ "@microtronics/device-master": "bitbucket:microtronics-core/device-master#v4.1.0-alpha.1",
60
60
  "@types/chai": "^4.3.6",
61
61
  "@types/chai-as-promised": "^7.1.5",
62
62
  "@types/ini": "^4.1.1",