@altronix/cli 0.6.10 → 0.7.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.
Files changed (79) hide show
  1. package/bin/{atx → atx-workspace} +0 -0
  2. package/build/build.d.ts +2 -0
  3. package/build/build.js +240 -0
  4. package/build/build.js.map +1 -0
  5. package/build/index.d.ts +1 -1
  6. package/build/index.js +39 -86
  7. package/build/index.js.map +1 -1
  8. package/build/seedle.d.ts +2 -0
  9. package/build/seedle.js +41 -0
  10. package/build/seedle.js.map +1 -0
  11. package/package.json +21 -21
  12. package/src/build.ts +354 -0
  13. package/src/index.ts +25 -86
  14. package/src/seedle.ts +39 -0
  15. package/tsconfig.json +1 -1
  16. package/tsconfig.lib.tsbuildinfo +1 -1
  17. package/build/about.d.ts +0 -3
  18. package/build/about.js +0 -38
  19. package/build/about.js.map +0 -1
  20. package/build/cloud.d.ts +0 -2
  21. package/build/cloud.js +0 -42
  22. package/build/cloud.js.map +0 -1
  23. package/build/common.d.ts +0 -5
  24. package/build/common.js +0 -49
  25. package/build/common.js.map +0 -1
  26. package/build/confirmDevice.d.ts +0 -5
  27. package/build/confirmDevice.js +0 -36
  28. package/build/confirmDevice.js.map +0 -1
  29. package/build/context.d.ts +0 -8
  30. package/build/context.js +0 -23
  31. package/build/context.js.map +0 -1
  32. package/build/dhcp.d.ts +0 -1
  33. package/build/dhcp.js +0 -10
  34. package/build/dhcp.js.map +0 -1
  35. package/build/exe.d.ts +0 -4
  36. package/build/exe.js +0 -43
  37. package/build/exe.js.map +0 -1
  38. package/build/ip.d.ts +0 -2
  39. package/build/ip.js +0 -18
  40. package/build/ip.js.map +0 -1
  41. package/build/linq.d.ts +0 -1
  42. package/build/linq.js +0 -3
  43. package/build/linq.js.map +0 -1
  44. package/build/listen.d.ts +0 -1
  45. package/build/listen.js +0 -24
  46. package/build/listen.js.map +0 -1
  47. package/build/net.d.ts +0 -3
  48. package/build/net.js +0 -47
  49. package/build/net.js.map +0 -1
  50. package/build/poe.d.ts +0 -1
  51. package/build/poe.js +0 -3
  52. package/build/poe.js.map +0 -1
  53. package/build/reboot.d.ts +0 -1
  54. package/build/reboot.js +0 -16
  55. package/build/reboot.js.map +0 -1
  56. package/build/site.d.ts +0 -2
  57. package/build/site.js +0 -19
  58. package/build/site.js.map +0 -1
  59. package/build/stress.d.ts +0 -1
  60. package/build/stress.js +0 -30
  61. package/build/stress.js.map +0 -1
  62. package/build/unix.d.ts +0 -1
  63. package/build/unix.js +0 -14
  64. package/build/unix.js.map +0 -1
  65. package/build/update.d.ts +0 -1
  66. package/build/update.js +0 -60
  67. package/build/update.js.map +0 -1
  68. package/build/west.d.ts +0 -1
  69. package/build/west.js +0 -3
  70. package/build/west.js.map +0 -1
  71. package/src/about.ts +0 -47
  72. package/src/cloud.ts +0 -55
  73. package/src/exe.ts +0 -54
  74. package/src/listen.ts +0 -23
  75. package/src/net.ts +0 -68
  76. package/src/poe.ts +0 -1
  77. package/src/stress.ts +0 -37
  78. package/src/update.ts +0 -84
  79. package/src/update.ts.bak +0 -98
package/src/build.ts ADDED
@@ -0,0 +1,354 @@
1
+ import dotenv from "dotenv";
2
+ import { Ajv, JSONSchemaType } from "ajv";
3
+ import { Command } from "commander";
4
+ import path from "node:path";
5
+ import fs from "node:fs";
6
+ import cp from "node:child_process";
7
+ import {
8
+ concat,
9
+ from,
10
+ lastValueFrom,
11
+ merge,
12
+ mergeMap,
13
+ Observable,
14
+ OperatorFunction,
15
+ } from "rxjs";
16
+
17
+ interface AtxConfig {
18
+ configs?: string[];
19
+ overlays?: string[];
20
+ }
21
+ interface AtxBoard {
22
+ [key: string]: AtxConfig;
23
+ }
24
+
25
+ interface AtxBuild {
26
+ sourceDir: string;
27
+ binaryDir: string;
28
+ installDir: string;
29
+ boards: { [key: string]: AtxBoard };
30
+ }
31
+ interface AtxWorkspace {
32
+ applications: { [key: string]: AtxBuild };
33
+ bootloaders: { [key: string]: AtxBuild };
34
+ }
35
+
36
+ const ajv = new Ajv({ allErrors: true, verbose: true });
37
+ const schemaBuild: JSONSchemaType<{ [key: string]: AtxBuild }> = {
38
+ type: "object",
39
+ required: [],
40
+ patternProperties: {
41
+ ".*": {
42
+ type: "object",
43
+ required: ["sourceDir", "binaryDir", "installDir", "boards"],
44
+ properties: {
45
+ sourceDir: { type: "string" },
46
+ binaryDir: { type: "string" },
47
+ installDir: { type: "string" },
48
+ boards: {
49
+ type: "object",
50
+ required: [],
51
+ patternProperties: {
52
+ ".*": {
53
+ type: "object",
54
+ required: [],
55
+ patternProperties: {
56
+ ".*": {
57
+ type: "object",
58
+ properties: {
59
+ configs: {
60
+ type: "array",
61
+ items: { type: "string" },
62
+ nullable: true,
63
+ },
64
+ overlays: {
65
+ type: "array",
66
+ items: { type: "string" },
67
+ nullable: true,
68
+ },
69
+ },
70
+ },
71
+ },
72
+ },
73
+ },
74
+ },
75
+ },
76
+ },
77
+ },
78
+ };
79
+ const schema: JSONSchemaType<AtxWorkspace> = {
80
+ type: "object",
81
+ required: ["applications", "bootloaders"],
82
+ additionalProperties: false,
83
+ properties: {
84
+ applications: schemaBuild,
85
+ bootloaders: schemaBuild,
86
+ },
87
+ };
88
+ const validate = ajv.compile(schema);
89
+
90
+ interface Version {
91
+ major: number;
92
+ minor: number;
93
+ patch: number;
94
+ tweak: number;
95
+ extra: string;
96
+ }
97
+ async function parseAppVersion(v: string): Promise<Version> {
98
+ const data = await fs.promises.readFile(v, "ascii");
99
+ const reMajor = data.matchAll(/^VERSION_MAJOR = ([0-9]+)/gm).next();
100
+ const reMinor = data.matchAll(/^VERSION_MINOR = ([0-9]+)/gm).next();
101
+ const rePatch = data.matchAll(/^PATCHLEVEL = ([0-9]+)/gm).next();
102
+ const reTweak = data.matchAll(/^VERSION_TWEAK = ([0-9]+)/gm).next();
103
+ const reExtra = data.matchAll(/^EXTRAVERSION = ([.a-zA-Z-]+)/gm).next();
104
+
105
+ return {
106
+ major: reMajor.value ? parseInt(reMajor.value[1]) : 0,
107
+ minor: reMinor.value ? parseInt(reMinor.value[1]) : 0,
108
+ patch: rePatch.value ? parseInt(rePatch.value[1]) : 0,
109
+ tweak: reTweak.value ? parseInt(reTweak.value[1]) : 0,
110
+ extra: reExtra.value ? reExtra.value[1] : "",
111
+ };
112
+ }
113
+
114
+ function formatVersion(ver: Version): string {
115
+ const { major, minor, patch, tweak, extra } = ver;
116
+ return extra.length
117
+ ? `${major}-${minor}-${patch}-${tweak}-${extra}`
118
+ : `${major}-${minor}-${patch}-${tweak}`;
119
+ }
120
+
121
+ interface WestOptions {
122
+ cwd: string;
123
+ name: string;
124
+ board: string;
125
+ config: string;
126
+ version: string;
127
+ sourceDir: string;
128
+ binaryDir: string;
129
+ installDir: string;
130
+ outputFile: string;
131
+ errorFile: string;
132
+ confs: string[];
133
+ overlays: string[];
134
+ }
135
+
136
+ async function normalizeWestOptions(
137
+ appName: string,
138
+ boardName: string,
139
+ configName: string,
140
+ app: AtxBuild,
141
+ cwd: string
142
+ ) {
143
+ const resolve = (dir: string, from?: string) =>
144
+ path.isAbsolute(dir) ? dir : path.resolve(from || cwd, dir);
145
+ const sourceDir = resolve(app.sourceDir);
146
+ const binaryDir = path.join(resolve(app.binaryDir), boardName, configName);
147
+ const installDir = resolve(app.installDir);
148
+ const confs = app.boards[boardName][configName].configs || [];
149
+ const versionFile = path.join(sourceDir, "VERSION");
150
+ const version = formatVersion(await parseAppVersion(versionFile));
151
+ const overlays = [];
152
+ return {
153
+ name: appName,
154
+ board: boardName,
155
+ config: configName,
156
+ cwd,
157
+ version,
158
+ sourceDir,
159
+ binaryDir,
160
+ installDir,
161
+ outputFile: path.join(binaryDir, "build.log"),
162
+ errorFile: path.join(binaryDir, "build.err"),
163
+ confs,
164
+ overlays,
165
+ };
166
+ }
167
+
168
+ function extraApplicationConfs(extraConfs: string): Observable<void> {
169
+ const key = process.env["ALTRONIX_RELEASE_KEY"];
170
+ if (!key) throw new Error("missing ALTRONIX_RELEASE_KEY from environment");
171
+ const extraConfsData = [
172
+ `CONFIG_MCUBOOT_SIGNATURE_KEY_FILE="${key}"`,
173
+ `CONFIG_BOOTLOADER_MCUBOOT=y`,
174
+ `CONFIG_ATX_UPDATE_ENABLE=y`,
175
+ ].join("\r\n");
176
+ return from(fs.promises.writeFile(extraConfs, extraConfsData));
177
+ }
178
+
179
+ function emulateBytePages(board: string) {
180
+ return (
181
+ board.startsWith("atsame54_xpro") ||
182
+ board.startsWith("netway4e1bt") ||
183
+ board.startsWith("netway4eb") ||
184
+ board.startsWith("netway5pq") ||
185
+ board.startsWith("oa2b")
186
+ );
187
+ }
188
+
189
+ function extraBootloaderConfs(
190
+ board: string,
191
+ extraConfs: string
192
+ ): Observable<void> {
193
+ const key = process.env["ALTRONIX_RELEASE_KEY"];
194
+ if (!key) throw new Error("missing ALTRONIX_RELEASE_KEY from environment");
195
+ const extraConfsData = [`CONFIG_BOOT_SIGNATURE_KEY_FILE="${key}"`];
196
+ if (emulateBytePages(board)) {
197
+ extraConfsData.push(`CONFIG_SOC_FLASH_SAM0_EMULATE_BYTE_PAGES=y`);
198
+ }
199
+ return from(fs.promises.writeFile(extraConfs, extraConfsData.join("\r\n")));
200
+ }
201
+
202
+ function west(args: WestOptions): Observable<void> {
203
+ const { cwd, board, sourceDir, binaryDir, confs, overlays } = args;
204
+ const westArgs = [
205
+ `build`,
206
+ `-b ${board}`,
207
+ `-s ${sourceDir}`,
208
+ `-d ${binaryDir}`,
209
+ `--`,
210
+ `-DEXTRA_CONF_FILE="${[...confs].join(";")}"`,
211
+ `-DEXTRA_DTC_OVERLAY_FILE="${[...overlays].join(";")}"`,
212
+ ];
213
+
214
+ return new Observable((subscriber) => {
215
+ const west = cp.spawn("west", westArgs, { cwd, shell: true });
216
+ const fout = fs.createWriteStream(args.outputFile);
217
+ const ferr = fs.createWriteStream(args.errorFile);
218
+ west.stdout.pipe(fout);
219
+ west.stderr.pipe(ferr);
220
+ west.on("error", (e) => {
221
+ subscriber.error(e);
222
+ fout.close();
223
+ ferr.close();
224
+ });
225
+ west.on("exit", () => {
226
+ fout.close();
227
+ ferr.close();
228
+ subscriber.next();
229
+ subscriber.complete();
230
+ });
231
+ });
232
+ }
233
+
234
+ function installSignedBin(args: WestOptions): Observable<void> {
235
+ const { name, config, version, board, binaryDir, installDir: dir } = args;
236
+ const src = path.join(binaryDir, "zephyr", "zephyr.signed.bin");
237
+ const dst = path.join(
238
+ dir,
239
+ `${board}-${name}-${config}-${version}.signed.bin`
240
+ );
241
+ return new Observable((subscriber) => {
242
+ fs.promises
243
+ .copyFile(src, dst)
244
+ .then(() => subscriber.next())
245
+ .catch((e) => subscriber.error(e))
246
+ .finally(() => subscriber.complete());
247
+ });
248
+ }
249
+
250
+ function installBin(args: WestOptions): Observable<void> {
251
+ const { name, config, version, board, binaryDir, installDir: dir } = args;
252
+ const src = path.join(binaryDir, "zephyr", "zephyr.bin");
253
+ const dst = path.join(dir, `${board}-${name}-${config}-${version}.bin`);
254
+ return new Observable((subscriber) => {
255
+ fs.promises
256
+ .copyFile(src, dst)
257
+ .then(() => subscriber.next())
258
+ .catch((e) => subscriber.error(e))
259
+ .finally(() => subscriber.complete());
260
+ });
261
+ }
262
+
263
+ function mkdir(): OperatorFunction<string, void> {
264
+ return (obs$) =>
265
+ obs$.pipe(
266
+ mergeMap(
267
+ (dir) =>
268
+ new Observable<void>((subscriber) => {
269
+ fs.promises
270
+ .mkdir(dir, { recursive: true })
271
+ .then(() => subscriber.next())
272
+ .catch((e) => subscriber.error(e))
273
+ .finally(() => subscriber.complete());
274
+ })
275
+ )
276
+ );
277
+ }
278
+
279
+ interface RunBuildOptions {
280
+ install$: Observable<void>;
281
+ config$: Observable<void>;
282
+ }
283
+ function runBuild({
284
+ install$,
285
+ config$,
286
+ }: RunBuildOptions): OperatorFunction<WestOptions, void> {
287
+ return (obs$) => {
288
+ // Create all the directories for our build
289
+ const dirs$ = obs$.pipe(
290
+ mergeMap(({ binaryDir, installDir }) => from([binaryDir, installDir])),
291
+ mkdir()
292
+ );
293
+ // Run west commands
294
+ const build$ = obs$.pipe(mergeMap((opts) => west(opts)));
295
+ return concat(dirs$, config$, build$, install$);
296
+ };
297
+ }
298
+
299
+ export async function build(this: Command): Promise<void> {
300
+ const config = this.opts().config || path.resolve("./", "atx.json");
301
+ const cwd = path.resolve(path.dirname(config));
302
+ const data = await fs.promises.readFile(config, "ascii");
303
+ const atx: AtxWorkspace = JSON.parse(data);
304
+ const extraAppConfFile = "application.conf";
305
+ const extraBootConfFile = "bootloader.conf";
306
+ if (!validate(atx)) throw validate.errors;
307
+ const env = path.resolve(cwd, ".env");
308
+ dotenv.config({ path: env });
309
+ const apps = Object.keys(atx.applications).flatMap((app) => {
310
+ return Object.keys(atx.applications[app].boards).flatMap((board) => {
311
+ return Object.keys(atx.applications[app].boards[board]).map((config) =>
312
+ normalizeWestOptions(app, board, config, atx.applications[app], cwd)
313
+ );
314
+ });
315
+ });
316
+
317
+ const bootloaders = Object.keys(atx.bootloaders).flatMap((app) => {
318
+ return Object.keys(atx.bootloaders[app].boards).flatMap((board) => {
319
+ return Object.keys(atx.bootloaders[app].boards[board]).map((config) =>
320
+ normalizeWestOptions(app, board, config, atx.bootloaders[app], cwd)
321
+ );
322
+ });
323
+ });
324
+
325
+ const apps$ = from(await Promise.all(apps));
326
+ const bootloaders$ = from(await Promise.all(bootloaders));
327
+ return lastValueFrom(
328
+ merge(
329
+ bootloaders$.pipe(
330
+ runBuild({
331
+ install$: bootloaders$.pipe(mergeMap((opts) => installBin(opts))),
332
+ config$: bootloaders$.pipe(
333
+ mergeMap(({ board, binaryDir }) =>
334
+ extraBootloaderConfs(
335
+ board,
336
+ path.join(binaryDir, extraBootConfFile)
337
+ )
338
+ )
339
+ ),
340
+ })
341
+ ),
342
+ apps$.pipe(
343
+ runBuild({
344
+ install$: apps$.pipe(mergeMap((opts) => installSignedBin(opts))),
345
+ config$: apps$.pipe(
346
+ mergeMap(({ binaryDir }) =>
347
+ extraApplicationConfs(path.join(binaryDir, extraAppConfFile))
348
+ )
349
+ ),
350
+ })
351
+ )
352
+ )
353
+ );
354
+ }
package/src/index.ts CHANGED
@@ -1,101 +1,40 @@
1
- import { log } from "@altronix/device";
2
1
  import { program } from "commander";
3
- import { getAbout, getSite, setSite } from "./about";
4
- import { getCloud, setCloud } from "./cloud";
5
- import { getNet, setDhcp, setNet } from "./net";
6
- import { reboot, save, saveAndReboot } from "./exe";
7
- import { update } from "./update";
8
- import { stress } from "./stress";
9
- import { listen } from "./listen";
2
+ import path from "path";
3
+ import fs from "fs";
4
+ import * as seedle from "./seedle";
5
+ import { build } from "./build";
10
6
 
11
7
  (async function main() {
12
- log.info("running cli");
13
-
14
- program.name("atx").description("access linq device via cli").version("TODO");
15
-
16
- program
17
- .command("get-about")
18
- .description("get about data on the device")
19
- .action(getAbout);
20
-
21
- program
22
- .command("get-site")
23
- .description("get site id from the device")
24
- .action(getSite);
25
-
26
- program
27
- .command("set-site")
28
- .description("set site id from the device")
29
- .argument("<site>", "new site id")
30
- .action(setSite);
31
-
32
- program
33
- .command("get-net")
34
- .description("get network configuration from the device")
35
- .action(getNet);
36
-
37
- program
38
- .command("set-net")
39
- .description("set network interface into static ip mode")
40
- .argument("<ip>", "the new IP address")
41
- .argument("<sn>", "the new SUBNET mask")
42
- .argument("<gw>", "the new GATEWAY address")
43
- .action(setNet);
44
-
45
- program
46
- .command("set-dhcp")
47
- .description("set network interface into DHCP mode")
48
- .action(setDhcp);
49
-
50
- program
51
- .command("get-cloud")
52
- .description("get cloud endpoint on the device")
53
- .action(getCloud);
54
-
55
- program
56
- .command("set-cloud")
57
- .description("set cloud endpoint on the device")
58
- .argument("<endpoint>", "cloud service location")
59
- .action(setCloud);
60
-
61
- program
62
- .command("save")
63
- .description("save data to persistant storage")
64
- .action(save);
65
-
66
- program
67
- .command("save-reboot")
68
- .description("save data to persistant storage and reboot the device")
69
- .action(saveAndReboot);
70
-
71
- program.command("reboot").description("reboot the device").action(reboot);
72
-
73
- program
74
- .command("erase")
75
- .description("erase customer settings")
76
- .action(reboot);
8
+ // Parse package.json to get version
9
+ const pkg = path.resolve(__dirname, "..", "package.json");
10
+ const ver = JSON.parse(await fs.promises.readFile(pkg, "ascii")).version;
11
+ const cwd = path.resolve("./");
77
12
 
78
13
  program
79
- .command("stress")
80
- .description("run a stress test on a device")
81
- .argument("<count>", "how many requests to make")
82
- .action(stress);
14
+ .name("atx")
15
+ .description("build atx zdk projects")
16
+ .version(ver);
83
17
 
18
+ // Scan command
19
+ const ignore = "node_modules;target;build;.git";
84
20
  program
85
- .command("listen")
86
- .description("listen for alerts and heartbeats")
87
- .argument("<duration>", "how long to listen")
88
- .action(listen);
21
+ .command("scan")
22
+ .description("scan for *.cddl files")
23
+ .option("-p, --path <PATH>", "root directory to start scan", cwd)
24
+ .option("-m, --matches <REGEX>", "match expression", ".*cddl$")
25
+ .option("-i, --ignores <REGEX>", "ignore directories", ignore)
26
+ .action(seedle.scan);
89
27
 
28
+ // Build command
90
29
  program
91
- .command("update")
92
- .description("run firmware update from file")
93
- .argument("<file>", "file to update device with")
94
- .action(update);
30
+ .command("build")
31
+ .description("build atx zdk application")
32
+ .option("-c, --config <CONFIG>", "workspace config file")
33
+ .action(build);
95
34
 
96
35
  try {
97
36
  await program.parseAsync();
98
37
  } catch (e) {
99
- log.error(e.message, { ...e });
38
+ console.error(e);
100
39
  }
101
40
  })();
package/src/seedle.ts ADDED
@@ -0,0 +1,39 @@
1
+ import { Command } from "commander";
2
+ import path from "path";
3
+ import fs from "fs";
4
+ import dotenv from "dotenv";
5
+
6
+ export async function scan(this: Command) {
7
+ // parse matches
8
+ const matches = this.opts()
9
+ .matches.split(";")
10
+ .map((s: string) => new RegExp(s, "g"));
11
+
12
+ // parse ignores
13
+ const ignores = this.opts()
14
+ .ignores.split(";")
15
+ .map((s: string) => new RegExp(s, "g"));
16
+
17
+ // parse start directory and load env.
18
+ const start = this.opts().path;
19
+ const env = path.join(start, ".env");
20
+ dotenv.config({ path: env });
21
+
22
+ // Log out matches
23
+ for await (const f of walk(start, matches, ignores)) {
24
+ console.log(f);
25
+ }
26
+ }
27
+
28
+ async function* walk(dir: string, matches: RegExp[], ignores: RegExp[]) {
29
+ for await (const d of await fs.promises.opendir(dir)) {
30
+ const entry = path.join(dir, d.name);
31
+ if (d.isDirectory()) {
32
+ const i = ignores.map((i) => entry.match(i)).filter((r) => r);
33
+ if (i.length == 0) yield* await walk(entry, matches, ignores);
34
+ } else if (d.isFile()) {
35
+ const m = matches.map((m) => entry.match(m)).filter((r) => r);
36
+ if (m.length > 0) yield entry;
37
+ }
38
+ }
39
+ }
package/tsconfig.json CHANGED
@@ -18,7 +18,7 @@
18
18
  "noUnusedParameters": true,
19
19
  "noImplicitAny": false,
20
20
  "noImplicitThis": false,
21
- "strictNullChecks": false,
21
+ "strictNullChecks": true,
22
22
  "paths":{
23
23
  "@altronix/wasm/zdk": ["../wasm/zdk"]
24
24
  }