@altronix/cli 0.7.12 → 0.7.14

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/dist/build.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { Command } from "commander";
1
+ import { Command } from 'commander';
2
2
  export declare function build(this: Command): Promise<void>;
package/dist/build.js CHANGED
@@ -1,82 +1,82 @@
1
- import dotenv from "dotenv";
2
- import { Ajv } from "ajv";
3
- import path from "node:path";
4
- import fs from "node:fs";
5
- import cp from "node:child_process";
6
- import inquirer from "@inquirer/confirm";
7
- import { catchError, concat, concatMap, EMPTY, from, last, lastValueFrom, map, merge, mergeMap, Observable, of, share, tap, toArray, } from "rxjs";
8
- import { PassThrough } from "node:stream";
9
- import { render } from "ink";
10
- import React from "react";
11
- import Ui, { BuildError } from "./build.ui.js";
12
- import { keys } from "./keys.js";
1
+ import dotenv from 'dotenv';
2
+ import { Ajv } from 'ajv';
3
+ import path from 'node:path';
4
+ import fs from 'node:fs';
5
+ import cp from 'node:child_process';
6
+ import inquirer from '@inquirer/confirm';
7
+ import { catchError, concat, concatMap, EMPTY, EmptyError, from, last, lastValueFrom, map, merge, mergeMap, Observable, of, share, tap, toArray } from 'rxjs';
8
+ import { PassThrough } from 'node:stream';
9
+ import { render } from 'ink';
10
+ import React from 'react';
11
+ import Ui, { BuildError } from './build.ui.js';
12
+ import { keys } from './keys.js';
13
13
  const schemaBuild = {
14
- type: "object",
14
+ type: 'object',
15
15
  required: [],
16
16
  patternProperties: {
17
- ".*": {
18
- type: "object",
19
- required: ["sourceDir", "binaryDir", "installDir", "boards"],
17
+ '.*': {
18
+ type: 'object',
19
+ required: ['sourceDir', 'binaryDir', 'installDir', 'boards'],
20
20
  properties: {
21
- sourceDir: { type: "string" },
22
- binaryDir: { type: "string" },
23
- installDir: { type: "string" },
21
+ sourceDir: { type: 'string' },
22
+ binaryDir: { type: 'string' },
23
+ installDir: { type: 'string' },
24
24
  boards: {
25
- type: "object",
25
+ type: 'object',
26
26
  required: [],
27
27
  patternProperties: {
28
- ".*": {
29
- type: "object",
28
+ '.*': {
29
+ type: 'object',
30
30
  required: [],
31
31
  patternProperties: {
32
- ".*": {
33
- type: "object",
32
+ '.*': {
33
+ type: 'object',
34
34
  properties: {
35
35
  configs: {
36
- type: "array",
37
- items: { type: "string" },
38
- nullable: true,
36
+ type: 'array',
37
+ items: { type: 'string' },
38
+ nullable: true
39
39
  },
40
40
  overlays: {
41
- type: "array",
42
- items: { type: "string" },
43
- nullable: true,
44
- },
45
- },
46
- },
47
- },
48
- },
49
- },
50
- },
51
- },
52
- },
53
- },
41
+ type: 'array',
42
+ items: { type: 'string' },
43
+ nullable: true
44
+ }
45
+ }
46
+ }
47
+ }
48
+ }
49
+ }
50
+ }
51
+ }
52
+ }
53
+ }
54
54
  };
55
55
  const schemaSeedle = {
56
- type: "object",
56
+ type: 'object',
57
57
  required: [],
58
58
  patternProperties: {
59
- ".*": {
60
- type: "object",
61
- required: ["installDir", "files"],
59
+ '.*': {
60
+ type: 'object',
61
+ required: ['installDir', 'files'],
62
62
  properties: {
63
- namespace: { type: "string", nullable: true },
64
- prefix: { type: "string", nullable: true },
65
- installDir: { type: "string" },
66
- files: { type: "array", items: { type: "string" } },
67
- },
68
- },
69
- },
63
+ namespace: { type: 'string', nullable: true },
64
+ prefix: { type: 'string', nullable: true },
65
+ installDir: { type: 'string' },
66
+ files: { type: 'array', items: { type: 'string' } }
67
+ }
68
+ }
69
+ }
70
70
  };
71
71
  const schema = {
72
- type: "object",
73
- required: ["applications", "bootloaders", "wasm"],
72
+ type: 'object',
73
+ required: ['applications', 'bootloaders', 'wasm'],
74
74
  additionalProperties: false,
75
75
  properties: {
76
76
  applications: schemaBuild,
77
77
  bootloaders: schemaBuild,
78
- wasm: schemaSeedle,
79
- },
78
+ wasm: schemaSeedle
79
+ }
80
80
  };
81
81
  const ajv = new Ajv({ allErrors: true, verbose: true });
82
82
  const validate = ajv.compile(schema);
@@ -97,18 +97,18 @@ async function resolver(cwd) {
97
97
  // workspace. We can have atx.json() pass this name in and resolve it
98
98
  // that way
99
99
  const project = path.resolve(cwd);
100
- const workspace = path.resolve(cwd, "..");
101
- const atx0 = path.resolve(cwd, "..", "atx");
102
- const atx1 = path.resolve(cwd, "..", "atx-zdk");
100
+ const workspace = path.resolve(cwd, '..');
101
+ const atx0 = path.resolve(cwd, '..', 'atx');
102
+ const atx1 = path.resolve(cwd, '..', 'atx-zdk');
103
103
  const atx = (await stat(atx0)) ? atx0 : (await stat(atx1)) ? atx1 : project;
104
104
  return (dir, from) => {
105
- if (dir.startsWith("<workspace>")) {
105
+ if (dir.startsWith('<workspace>')) {
106
106
  return path.join(workspace, dir.substring(12));
107
107
  }
108
- else if (dir.startsWith("<project>")) {
108
+ else if (dir.startsWith('<project>')) {
109
109
  return path.join(project, dir.substring(10));
110
110
  }
111
- else if (dir.startsWith("<atx>")) {
111
+ else if (dir.startsWith('<atx>')) {
112
112
  return path.join(atx, dir.substring(6));
113
113
  }
114
114
  else if (path.isAbsolute(dir)) {
@@ -120,7 +120,7 @@ async function resolver(cwd) {
120
120
  };
121
121
  }
122
122
  async function parseAppVersion(v) {
123
- const data = await fs.promises.readFile(v, "ascii");
123
+ const data = await fs.promises.readFile(v, 'ascii');
124
124
  const reMajor = data.matchAll(/^VERSION_MAJOR = ([0-9]+)/gm).next();
125
125
  const reMinor = data.matchAll(/^VERSION_MINOR = ([0-9]+)/gm).next();
126
126
  const rePatch = data.matchAll(/^PATCHLEVEL = ([0-9]+)/gm).next();
@@ -131,7 +131,7 @@ async function parseAppVersion(v) {
131
131
  minor: reMinor.value ? parseInt(reMinor.value[1]) : 0,
132
132
  patch: rePatch.value ? parseInt(rePatch.value[1]) : 0,
133
133
  tweak: reTweak.value ? parseInt(reTweak.value[1]) : 0,
134
- extra: reExtra.value ? reExtra.value[1] : "",
134
+ extra: reExtra.value ? reExtra.value[1] : ''
135
135
  };
136
136
  }
137
137
  function formatVersion(ver) {
@@ -146,7 +146,7 @@ async function westOptionsNormalize(board, config, build, cwd, verbose) {
146
146
  const installDir = resolve(build.installDir);
147
147
  const sourceDir = resolve(build.sourceDir);
148
148
  const binaryDir = path.join(resolve(build.binaryDir), board, config.__key);
149
- const versionFile = path.join(sourceDir, "VERSION");
149
+ const versionFile = path.join(sourceDir, 'VERSION');
150
150
  const version = formatVersion(await parseAppVersion(versionFile));
151
151
  const confs = config.configs
152
152
  ? config.configs.map((f) => resolve(f, sourceDir))
@@ -163,67 +163,70 @@ async function westOptionsNormalize(board, config, build, cwd, verbose) {
163
163
  sourceDir,
164
164
  binaryDir,
165
165
  installDir,
166
- outputFile: path.join(binaryDir, "build.log"),
167
- errorFile: path.join(binaryDir, "build.err"),
166
+ outputFile: path.join(binaryDir, 'build.log'),
167
+ errorFile: path.join(binaryDir, 'build.err'),
168
168
  confs,
169
169
  overlays,
170
- verbose,
170
+ verbose
171
171
  };
172
172
  }
173
173
  function westItem(opts) {
174
174
  const { board, name, config, version } = opts;
175
175
  return {
176
- kind: "west",
176
+ kind: 'west',
177
177
  item: `${board}-${name}-${config}-${version}`
178
178
  };
179
179
  }
180
180
  function west(args) {
181
181
  const { cwd, board, sourceDir, binaryDir, confs, overlays } = args;
182
182
  const { item } = westItem(args);
183
+ const expect = path.join(binaryDir, 'zephyr', 'zephyr.bin');
183
184
  return of([
184
185
  `build`,
185
186
  `-b ${board}`,
186
187
  `-s ${sourceDir}`,
187
188
  `-d ${binaryDir}`,
188
189
  `--`,
189
- `-DEXTRA_CONF_FILE="${[...confs].join(";")}"`,
190
- `-DEXTRA_DTC_OVERLAY_FILE="${[...overlays].join(";")}"`,
190
+ `-DEXTRA_CONF_FILE="${[...confs].join(';')}"`,
191
+ `-DEXTRA_DTC_OVERLAY_FILE="${[...overlays].join(';')}"`
191
192
  ]).pipe(mergeMap((westArgs) => new Observable((subscriber) => {
192
- const west = cp.spawn("west", westArgs, { cwd, shell: true });
193
+ const west = cp.spawn('west', westArgs, { cwd, shell: true });
193
194
  const fout = fs.createWriteStream(args.outputFile);
194
195
  const ferr = fs.createWriteStream(args.errorFile);
195
196
  const out = new PassThrough();
196
197
  const err = new PassThrough();
197
- let error = "";
198
+ let error = '';
198
199
  west.stdout.pipe(fout);
199
200
  west.stdout.pipe(out);
200
201
  west.stderr.pipe(ferr);
201
202
  west.stderr.pipe(err);
202
- west.on("error", (e) => {
203
+ west.on('error', (e) => {
203
204
  fout.close();
204
205
  ferr.close();
205
206
  out.destroy();
206
207
  err.destroy();
207
208
  subscriber.next({ item, error: e.name });
208
209
  });
209
- west.on("exit", () => {
210
+ west.on('exit', (_code) => {
210
211
  fout.close();
211
212
  ferr.close();
212
213
  out.destroy();
213
214
  err.destroy();
214
- error.length ?
215
- subscriber.next({ item, error }) :
216
- subscriber.next({ item, complete: true });
217
- subscriber.complete();
215
+ fs.stat(expect, (e) => {
216
+ e
217
+ ? subscriber.next({ item, error })
218
+ : subscriber.next({ item, complete: true });
219
+ subscriber.complete();
220
+ });
218
221
  });
219
- out.on("data", (d) => subscriber.next({ item, output: d.toString() }));
220
- err.on("data", (d) => error += d);
222
+ out.on('data', (d) => subscriber.next({ item, output: d.toString() }));
223
+ err.on('data', (d) => (error += d));
221
224
  })));
222
225
  }
223
226
  async function seedleOptionsNormalize(seedle, cwd, verbose) {
224
227
  const name = seedle.__key;
225
228
  const resolve = await resolver(cwd);
226
- const types = resolve("<atx>/lib/atx/types.cddl");
229
+ const types = resolve('<atx>/lib/atx/types.cddl');
227
230
  const installDir = resolve(seedle.installDir);
228
231
  const buildDir = path.join(installDir, name);
229
232
  const files = [...seedle.files, types].map((file) => path.resolve(cwd, file));
@@ -236,40 +239,40 @@ async function seedleOptionsNormalize(seedle, cwd, verbose) {
236
239
  cddl: path.join(installDir, name, `${name}.cddl`),
237
240
  outputFile: path.join(installDir, `${name}-build.log`),
238
241
  errorFile: path.join(installDir, `${name}-build.err`),
239
- prefix: seedle.prefix || "",
240
- namespace: seedle.namespace || "altronix",
241
- verbose,
242
+ prefix: seedle.prefix || '',
243
+ namespace: seedle.namespace || 'altronix',
244
+ verbose
242
245
  };
243
246
  }
244
247
  function seedle(opts) {
245
248
  const { name, namespace, cddl, cwd, installDir } = opts;
246
- const templatePath = path.resolve(cwd, "..", "seedle-template");
247
- const seedlePath = path.resolve(cwd, "..", "seedle", "seedle");
249
+ const templatePath = path.resolve(cwd, '..', 'seedle-template');
250
+ const seedlePath = path.resolve(cwd, '..', 'seedle', 'seedle');
248
251
  return of([
249
- "generate",
250
- "--force",
252
+ 'generate',
253
+ '--force',
251
254
  `--destination=${installDir}`,
252
255
  `--path=${templatePath}`,
253
256
  `--name=${name}`,
254
- "--overwrite",
257
+ '--overwrite',
255
258
  `-dnamespace=${namespace}`,
256
- `-dseedle-manifest-path=${seedlePath.replace(/\\/g, "\\\\")}`,
257
- `-dcddl=${cddl.replace(/\\/g, "\\\\")}`,
259
+ `-dseedle-manifest-path=${seedlePath.replace(/\\/g, '\\\\')}`,
260
+ `-dcddl=${cddl.replace(/\\/g, '\\\\')}`
258
261
  ]).pipe(mergeMap((seedleArgs) => new Observable((subscriber) => {
259
- const wasm = cp.spawn("cargo", seedleArgs, {
262
+ const wasm = cp.spawn('cargo', seedleArgs, {
260
263
  cwd: installDir,
261
- shell: true,
264
+ shell: true
262
265
  });
263
266
  const fout = fs.createWriteStream(opts.outputFile);
264
267
  const ferr = fs.createWriteStream(opts.errorFile);
265
268
  wasm.stdout.pipe(fout);
266
269
  wasm.stderr.pipe(ferr);
267
- wasm.on("error", (e) => {
270
+ wasm.on('error', (e) => {
268
271
  subscriber.error(e);
269
272
  fout.close();
270
273
  ferr.close();
271
274
  });
272
- wasm.on("exit", () => {
275
+ wasm.on('exit', () => {
273
276
  fout.close();
274
277
  ferr.close();
275
278
  subscriber.next();
@@ -279,15 +282,15 @@ function seedle(opts) {
279
282
  }
280
283
  function cmakeItem(opts) {
281
284
  const { name: item } = opts;
282
- return { kind: "wasm", item };
285
+ return { kind: 'wasm', item };
283
286
  }
284
287
  function cmakeConfigure(opts) {
285
- const { buildDir, outputFile, errorFile, } = opts;
288
+ const { buildDir, outputFile, errorFile } = opts;
286
289
  const { item } = cmakeItem(opts);
287
290
  return of([`-B${buildDir}`, `-S${buildDir}`]).pipe(mergeMap((cmakeArgs) => new Observable((subscriber) => {
288
- const wasm = cp.spawn("cmake", cmakeArgs, {
291
+ const wasm = cp.spawn('cmake', cmakeArgs, {
289
292
  cwd: buildDir,
290
- shell: true,
293
+ shell: true
291
294
  });
292
295
  const fout = fs.createWriteStream(outputFile);
293
296
  const ferr = fs.createWriteStream(errorFile);
@@ -295,28 +298,28 @@ function cmakeConfigure(opts) {
295
298
  wasm.stdout.pipe(fout);
296
299
  wasm.stdout.pipe(out);
297
300
  wasm.stderr.pipe(ferr);
298
- wasm.on("error", (e) => {
301
+ wasm.on('error', (e) => {
299
302
  fout.close();
300
303
  ferr.close();
301
304
  out.destroy();
302
- subscriber.error(new BuildError(item, "wasm", e.name));
305
+ subscriber.error(new BuildError(item, 'wasm', e.name));
303
306
  });
304
- wasm.on("exit", () => {
307
+ wasm.on('exit', () => {
305
308
  fout.close();
306
309
  ferr.close();
307
310
  out.destroy();
308
311
  subscriber.complete();
309
312
  });
310
- out.on("data", (data) => subscriber.next({ item, output: data.toString() }));
313
+ out.on('data', (data) => subscriber.next({ item, output: data.toString() }));
311
314
  })));
312
315
  }
313
316
  function cmakeBuild(opts) {
314
- const { buildDir, outputFile, errorFile, } = opts;
317
+ const { buildDir, outputFile, errorFile } = opts;
315
318
  const { item } = cmakeItem(opts);
316
319
  return of([`--build`, `${buildDir}`, `--target`, ` wasm`]).pipe(mergeMap((cmakeArgs) => new Observable((subscriber) => {
317
- const wasm = cp.spawn("cmake", cmakeArgs, {
320
+ const wasm = cp.spawn('cmake', cmakeArgs, {
318
321
  cwd: buildDir,
319
- shell: true,
322
+ shell: true
320
323
  });
321
324
  const fout = fs.createWriteStream(outputFile);
322
325
  const ferr = fs.createWriteStream(errorFile);
@@ -324,69 +327,67 @@ function cmakeBuild(opts) {
324
327
  wasm.stdout.pipe(fout);
325
328
  wasm.stdout.pipe(out);
326
329
  wasm.stderr.pipe(ferr);
327
- wasm.on("error", (e) => {
330
+ wasm.on('error', (e) => {
328
331
  fout.close();
329
332
  ferr.close();
330
333
  out.destroy();
331
- subscriber.error(new BuildError(item, "wasm", e.name));
334
+ subscriber.error(new BuildError(item, 'wasm', e.name));
332
335
  });
333
- wasm.on("exit", () => {
336
+ wasm.on('exit', () => {
334
337
  fout.close();
335
338
  ferr.close();
336
339
  out.destroy();
337
340
  subscriber.complete();
338
341
  });
339
- out.on("data", (data) => subscriber.next({ item, output: data.toString() }));
342
+ out.on('data', (data) => subscriber.next({ item, output: data.toString() }));
340
343
  })));
341
344
  }
342
345
  function cmake(opts) {
343
346
  const { item } = cmakeItem(opts);
344
- return concat(cmakeConfigure(opts), cmakeBuild(opts), of({ item, complete: true })).pipe(catchError(e => of(e)));
347
+ return concat(cmakeConfigure(opts), cmakeBuild(opts), of({ item, complete: true })).pipe(catchError(of));
345
348
  }
346
349
  function emulateBytePages(board) {
347
- return (board.startsWith("atsame54_xpro") ||
348
- board.startsWith("netway4e1bt") ||
349
- board.startsWith("netway4eb") ||
350
- board.startsWith("netway5pq") ||
351
- board.startsWith("oa2b"));
350
+ return (board.startsWith('atsame54_xpro') ||
351
+ board.startsWith('netway4e1bt') ||
352
+ board.startsWith('netway4eb') ||
353
+ board.startsWith('netway5pq') ||
354
+ board.startsWith('oa2b'));
352
355
  }
353
- function extraApplicationConfs(extraConfs) {
354
- const key = process.env["ALTRONIX_RELEASE_KEY"];
356
+ function extraAppConfs(extraConfs) {
357
+ const key = process.env['ALTRONIX_RELEASE_KEY'];
355
358
  if (!key)
356
- throw new Error("missing ALTRONIX_RELEASE_KEY from environment");
359
+ throw new Error('missing ALTRONIX_RELEASE_KEY from environment');
357
360
  const extraConfsData = [
358
361
  `CONFIG_MCUBOOT_SIGNATURE_KEY_FILE="${key}"`,
359
362
  `CONFIG_BOOTLOADER_MCUBOOT=y`,
360
- `CONFIG_ATX_UPDATE_ENABLE=y`,
361
- ].join("\r\n");
363
+ `CONFIG_ATX_UPDATE_ENABLE=y`
364
+ ].join('\r\n');
362
365
  return from(fs.promises.writeFile(extraConfs, extraConfsData));
363
366
  }
364
- function extraBootloaderConfs(board, extraConfs) {
365
- const key = process.env["ALTRONIX_RELEASE_KEY"];
367
+ function extraBootConfs(board, extraConfs) {
368
+ const key = process.env['ALTRONIX_RELEASE_KEY'];
366
369
  if (!key)
367
- throw new Error("missing ALTRONIX_RELEASE_KEY from environment");
370
+ throw new Error('missing ALTRONIX_RELEASE_KEY from environment');
368
371
  const extraConfsData = emulateBytePages(board)
369
372
  ? [
370
373
  `CONFIG_BOOT_SIGNATURE_KEY_FILE="${key}"`,
371
- `CONFIG_SOC_FLASH_SAM0_EMULATE_BYTE_PAGES=y`,
374
+ `CONFIG_SOC_FLASH_SAM0_EMULATE_BYTE_PAGES=y`
372
375
  ]
373
376
  : [`CONFIG_BOOT_SIGNATURE_KEY_FILE="${key}"`];
374
- return from(fs.promises.writeFile(extraConfs, extraConfsData.join("\r\n")));
377
+ return from(fs.promises.writeFile(extraConfs, extraConfsData.join('\r\n')));
375
378
  }
376
379
  function copy() {
377
380
  return (obs$) => obs$.pipe(mergeMap((opts) => {
378
381
  const { src, dst } = opts;
379
- return new Observable((subscriber) => {
380
- fs.promises
381
- .copyFile(src, dst)
382
- .then(() => subscriber.next(opts))
383
- .catch(() => subscriber.next({ ...opts, err: dst }))
384
- .finally(() => subscriber.complete());
385
- });
382
+ const promise = fs.promises
383
+ .copyFile(src, dst)
384
+ .then(() => opts)
385
+ .catch(() => ({ ...opts, err: dst }));
386
+ return from(promise);
386
387
  }));
387
388
  }
388
389
  function concatFiles(src, dest) {
389
- return from(src).pipe(concatMap((src) => from(fs.promises.readFile(src, "ascii"))), toArray(), mergeMap((arr) => from(fs.promises.writeFile(dest, arr.join("\r\n")))));
390
+ return from(src).pipe(concatMap((src) => from(fs.promises.readFile(src, 'ascii'))), toArray(), mergeMap((arr) => from(fs.promises.writeFile(dest, arr.join('\r\n')))));
390
391
  }
391
392
  function mkdir() {
392
393
  return (obs$) => obs$.pipe(mergeMap((dir) => new Observable((subscriber) => {
@@ -406,14 +407,14 @@ function rmdir(dir) {
406
407
  .finally(() => subscriber.complete());
407
408
  });
408
409
  }
409
- function exists() {
410
- return (obs$) => obs$.pipe(mergeMap((dir) => new Observable((subscriber) => {
410
+ function exists(dir) {
411
+ return new Observable((subscriber) => {
411
412
  fs.promises
412
413
  .stat(dir)
413
- .then(() => subscriber.next(dir))
414
- .catch(() => subscriber.next(undefined))
414
+ .then(() => subscriber.next(true))
415
+ .catch(() => subscriber.next(false))
415
416
  .finally(() => subscriber.complete());
416
- })));
417
+ });
417
418
  }
418
419
  function confirm(force) {
419
420
  return (obs$) => force
@@ -427,26 +428,26 @@ function throwIf(predicate, message) {
427
428
  });
428
429
  }
429
430
  function clean(force) {
430
- return (obs$) => obs$.pipe(exists(), concatMap((dir) => {
431
- return dir
432
- ? of(dir).pipe(confirm(force), throwIf((confirm) => !confirm, "User rejected delete"), mergeMap((_) => rmdir(dir)), map(() => void 0))
433
- : of(void 0);
434
- }));
431
+ return (obs$) => obs$.pipe(concatMap((dir) => exists(dir).pipe(concatMap((exists) => {
432
+ return exists
433
+ ? of(dir).pipe(confirm(force), throwIf((confirm) => !confirm, 'User rejected delete'), mergeMap((_) => rmdir(dir)), map(() => dir))
434
+ : of(dir);
435
+ }))));
435
436
  }
436
437
  function tovoid() {
437
438
  return (obs$) => obs$.pipe(map(() => void 0));
438
439
  }
439
440
  export async function build() {
440
- const config = this.opts()["config"] || path.resolve("./", "atx.json");
441
- const verbose = this.optsWithGlobals()["verbose"];
441
+ const config = this.opts()['config'] || path.resolve('./', 'atx.json');
442
+ const verbose = this.optsWithGlobals()['verbose'];
442
443
  const cwd = path.resolve(path.dirname(config));
443
- const data = await fs.promises.readFile(config, "ascii");
444
+ const data = await fs.promises.readFile(config, 'ascii');
444
445
  const atx = JSON.parse(data);
445
- const extraAppConfFile = "application.conf";
446
- const extraBootConfFile = "bootloader.conf";
446
+ const extraAppConfFile = 'application.conf';
447
+ const extraBootConfFile = 'bootloader.conf';
447
448
  if (!validate(atx))
448
449
  throw validate.errors;
449
- const env = path.resolve(cwd, ".env");
450
+ const env = path.resolve(cwd, '.env');
450
451
  dotenv.config({ path: env });
451
452
  const apps = keys(atx.applications).flatMap((app) => {
452
453
  return keys(app.boards).flatMap((board) => {
@@ -459,12 +460,12 @@ export async function build() {
459
460
  });
460
461
  });
461
462
  const wasm = keys(atx.wasm).map((w) => seedleOptionsNormalize(w, cwd, verbose));
462
- let concurrent = this.opts()["concurrent"]
463
- ? parseInt(this.opts()["concurrent"])
463
+ let concurrent = this.opts()['concurrent']
464
+ ? parseInt(this.opts()['concurrent'])
464
465
  : Infinity;
465
- let buildApps = this.opts()["application"];
466
- let buildBoot = this.opts()["bootloader"];
467
- let buildWasm = this.opts()["wasm"];
466
+ let buildApps = this.opts()['application'];
467
+ let buildBoot = this.opts()['bootloader'];
468
+ let buildWasm = this.opts()['wasm'];
468
469
  if (!(buildApps || buildBoot || buildWasm)) {
469
470
  buildApps = buildBoot = buildWasm = true;
470
471
  }
@@ -472,7 +473,8 @@ export async function build() {
472
473
  const boot$ = buildBoot ? from(await Promise.all(bootloaders)) : EMPTY;
473
474
  const wasm$ = buildWasm ? from(await Promise.all(wasm)) : EMPTY;
474
475
  // Handle all the preliminary stuff before building
475
- const ready$ = concat(merge(apps$.pipe(mergeMap(({ installDir: s, binaryDir: b }) => from([s, b]))), boot$.pipe(mergeMap(({ installDir: s, binaryDir: b }) => from([s, b]))), wasm$.pipe(map(({ installDir: dir }) => dir))).pipe(mkdir()), wasm$.pipe(map(({ name, installDir }) => path.join(installDir, name)), clean(this.opts()["yes"])), apps$.pipe(mergeMap(({ binaryDir }) => extraApplicationConfs(path.join(binaryDir, extraAppConfFile)))), boot$.pipe(mergeMap(({ board, binaryDir }) => extraBootloaderConfs(board, path.join(binaryDir, extraBootConfFile)))), wasm$.pipe(mergeMap(seedle)), wasm$.pipe(mergeMap(({ files, cddl: dest }) => concatFiles(files, dest)))).pipe(last(), share());
476
+ const ready$ = concat(merge(apps$.pipe(mergeMap(({ installDir: s, binaryDir: b }) => from([s, b]))), boot$.pipe(mergeMap(({ installDir: s, binaryDir: b }) => from([s, b]))), wasm$.pipe(map(({ installDir }) => installDir))).pipe(toArray(), mergeMap((arr) => from([...new Set(arr)])), // dedupe
477
+ clean(this.opts()['yes']), mkdir()), apps$.pipe(mergeMap(({ binaryDir }) => extraAppConfs(path.join(binaryDir, extraAppConfFile)))), boot$.pipe(mergeMap(({ board, binaryDir }) => extraBootConfs(board, path.join(binaryDir, extraBootConfFile)))), wasm$.pipe(mergeMap(seedle)), wasm$.pipe(mergeMap(({ files, cddl: dest }) => concatFiles(files, dest)))).pipe(last(), share());
476
478
  // Get an array of every build item.
477
479
  const items$ = merge(apps$.pipe(map(westItem)), boot$.pipe(map(westItem)), wasm$.pipe(map(cmakeItem))).pipe(toArray());
478
480
  // Run build commands for all bootloaders, applications and wasm concurrently
@@ -480,19 +482,22 @@ export async function build() {
480
482
  // Install everything into installDir
481
483
  const install$ = merge(apps$.pipe(map(({ name, config, version: ver, board, binaryDir, installDir: d }) => {
482
484
  return {
483
- src: path.join(binaryDir, "zephyr", "zephyr.signed.bin"),
484
- dst: path.join(d, `${board}-${name}-${config}-${ver}.signed.bin`),
485
+ src: path.join(binaryDir, 'zephyr', 'zephyr.signed.bin'),
486
+ dst: path.join(d, `${board}-${name}-${config}-${ver}.signed.bin`)
485
487
  };
486
488
  })), boot$.pipe(map(({ name, config, board, binaryDir, installDir: d }) => {
487
489
  return {
488
- src: path.join(binaryDir, "zephyr", "zephyr.bin"),
489
- dst: path.join(d, `${board}-${name}-${config}.bin`),
490
+ src: path.join(binaryDir, 'zephyr', 'zephyr.bin'),
491
+ dst: path.join(d, `${board}-${name}-${config}.bin`)
490
492
  };
491
493
  }))).pipe(copy(), tovoid());
492
494
  await lastValueFrom(ready$);
493
495
  const items = await lastValueFrom(items$);
494
- const renderer = render(React.createElement(Ui, { items: items, "progress$": build$ }));
495
- await lastValueFrom(build$);
496
+ const renderer = render(React.createElement(Ui, { items: items, "progress$": build$, onComplete: () => renderer.unmount() }));
497
+ await renderer.waitUntilExit();
496
498
  renderer.cleanup();
497
- return lastValueFrom(install$);
499
+ return lastValueFrom(install$).catch((e) => {
500
+ if (!(e instanceof EmptyError))
501
+ throw e;
502
+ });
498
503
  }
@@ -2,12 +2,12 @@ import React from 'react';
2
2
  import { Observable } from 'rxjs';
3
3
  export declare class BuildError extends Error implements BuildItem {
4
4
  item: string;
5
- kind: "west" | "wasm";
6
- constructor(item: string, kind: "west" | "wasm", message: string);
5
+ kind: 'west' | 'wasm';
6
+ constructor(item: string, kind: 'west' | 'wasm', message: string);
7
7
  }
8
8
  export interface BuildItem {
9
9
  item: string;
10
- kind: "west" | "wasm";
10
+ kind: 'west' | 'wasm';
11
11
  }
12
12
  export interface BuildProgress<K extends string = string> {
13
13
  item: K;
@@ -18,5 +18,10 @@ export interface BuildProgress<K extends string = string> {
18
18
  export interface Options {
19
19
  items: BuildItem[];
20
20
  progress$: Observable<BuildProgress>;
21
+ onComplete: BuildEffectCallback;
21
22
  }
22
- export default function ({ items, progress$ }: Options): React.JSX.Element;
23
+ export default function ({ items, progress$, onComplete }: Options): React.JSX.Element;
24
+ interface BuildEffectCallback<E extends Error = Error> {
25
+ (e?: E): void;
26
+ }
27
+ export {};
package/dist/build.ui.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import React, { useLayoutEffect, useEffect, useState } from 'react';
2
- import { Box, Text, useStdout } from "ink";
2
+ import { Box, Text, useStdout } from 'ink';
3
3
  import { scan } from 'rxjs';
4
4
  export class BuildError extends Error {
5
5
  constructor(item, kind, message) {
@@ -18,14 +18,12 @@ export class BuildError extends Error {
18
18
  });
19
19
  }
20
20
  }
21
- export default function ({ items, progress$ }) {
21
+ export default function ({ items, progress$, onComplete }) {
22
22
  const [col, _rows] = useStdoutDimensions();
23
23
  const [width, setWidth] = useState(0);
24
- const progress = useBuildEffect(progress$, items.map(({ item }) => item));
24
+ const progress = useBuildEffect(progress$, items.map(({ item: i }) => i), onComplete);
25
25
  useLayoutEffect(() => {
26
- const width = items
27
- .map(({ item }) => item)
28
- .reduce(calculateItemWidth, 0);
26
+ const width = items.map(({ item }) => item).reduce(calculateItemWidth, 0);
29
27
  setWidth(width);
30
28
  }, [items]);
31
29
  return (React.createElement(Box, { flexDirection: "column" }, items.map(({ item, kind }) => (React.createElement(Box, { key: item },
@@ -38,58 +36,68 @@ export default function ({ items, progress$ }) {
38
36
  React.createElement(Text, { wrap: "truncate", dimColor: !progressComplete(progress[item]), color: progressColor(progress[item]) }, progress[item])))))));
39
37
  }
40
38
  function buildColor(kind) {
41
- return kind === "west" ? "blue" : "magenta";
39
+ return kind === 'west' ? 'blue' : 'magenta';
42
40
  }
43
41
  function progressComplete(progress) {
44
- return progress === "OK!";
42
+ return progress === 'OK!';
45
43
  }
46
44
  function progressColor(progress) {
47
- return (!progress || (progress.charAt(0) == "|" ||
48
- progress.charAt(0) == "/" ||
49
- progress.charAt(0) == "-" ||
50
- progress.charAt(0) == "\\")) ? "white" : progressComplete(progress) ? "green" : "red";
45
+ return !progress ||
46
+ progress.charAt(0) == '|' ||
47
+ progress.charAt(0) == '/' ||
48
+ progress.charAt(0) == '-' ||
49
+ progress.charAt(0) == '\\'
50
+ ? 'white'
51
+ : progressComplete(progress)
52
+ ? 'green'
53
+ : 'red';
51
54
  }
52
55
  function initProgress(items) {
53
- return items.reduce((acc, curr) => ({ ...acc, [curr]: "" }), {});
56
+ return items.reduce((acc, curr) => ({ ...acc, [curr]: '' }), {});
54
57
  }
55
58
  function calculateItemWidth(acc, next) {
56
59
  return next.length > acc ? next.length : acc;
57
60
  }
58
61
  function progressInc(progress) {
59
- if (progress.charAt(0) == "|") {
60
- return "/";
62
+ if (progress.charAt(0) == '|') {
63
+ return '/';
61
64
  }
62
- else if (progress.charAt(0) == "/") {
63
- return "-";
65
+ else if (progress.charAt(0) == '/') {
66
+ return '-';
64
67
  }
65
- else if (progress.charAt(0) == "-") {
66
- return "\\";
68
+ else if (progress.charAt(0) == '-') {
69
+ return '\\';
67
70
  }
68
71
  else {
69
- return "|";
72
+ return '|';
70
73
  }
71
74
  }
72
75
  function progressReducer(acc, next) {
73
76
  acc[next.item] = next.complete
74
- ? "OK!"
77
+ ? 'OK!'
75
78
  : next.error
76
- ? next.error.replace(/\r?\n/g, "")
79
+ ? next.error.replace(/\r?\n/g, '')
77
80
  : progressInc(acc[next.item]);
78
81
  return acc;
79
82
  }
80
- function useBuildEffect(obs$, items) {
83
+ function useBuildEffect(obs$, items, cb) {
81
84
  const [progress, setProgress] = useState(initProgress(items));
82
85
  useLayoutEffect(() => {
83
- const s = obs$
84
- .pipe(scan(progressReducer, progress))
85
- .subscribe(progress => setProgress({ ...progress }));
86
+ const s = obs$.pipe(scan(progressReducer, progress)).subscribe({
87
+ next: (progress) => setProgress({ ...progress }),
88
+ complete: cb,
89
+ error: cb
90
+ });
86
91
  return () => s.unsubscribe();
87
- }, [obs$, items]);
92
+ }, [obs$]);
88
93
  return progress;
89
94
  }
90
95
  function useStdoutDimensions() {
91
96
  const { stdout } = useStdout();
92
- const [dimensions, setDimensions] = useState([stdout.columns, stdout.rows]);
97
+ const [dimensions, setDimensions] = useState([
98
+ stdout.columns,
99
+ stdout.rows
100
+ ]);
93
101
  useEffect(() => {
94
102
  const handler = () => setDimensions([stdout.columns, stdout.rows]);
95
103
  stdout.on('resize', handler);
package/dist/index.js CHANGED
@@ -1,24 +1,23 @@
1
1
  #!/usr/bin/env node
2
- import { program } from "commander";
3
- import path from "path";
4
- import fs from "fs";
5
- import { build } from "./build.js";
2
+ import { program } from 'commander';
3
+ import path from 'path';
4
+ import fs from 'fs';
5
+ import { build } from './build.js';
6
6
  //import { plugins } from "./plugin.js";
7
- import { fileURLToPath } from "url";
7
+ import { fileURLToPath } from 'url';
8
8
  // https://stackoverflow.com/questions/8817423/why-is-dirname-not-defined-in-node-repl
9
9
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
- (async function main() {
11
- // Parse package.json to get version
12
- const pkg = path.resolve(__dirname, "..", "package.json");
13
- const ver = JSON.parse(await fs.promises.readFile(pkg, "ascii")).version;
14
- // const cwd = path.resolve('./');
15
- program
16
- .name("atx")
17
- .description("build atx zdk projects")
18
- .version(ver)
19
- .option("-VV, --verbose", "extra logging");
20
- // Scan command
21
- /*
10
+ // Parse package.json to get version
11
+ const pkg = path.resolve(__dirname, '..', 'package.json');
12
+ const ver = JSON.parse(fs.readFileSync(pkg, 'ascii')).version;
13
+ // const cwd = path.resolve('./');
14
+ program
15
+ .name('atx')
16
+ .description('build atx zdk projects')
17
+ .version(ver)
18
+ .option('-VV, --verbose', 'extra logging');
19
+ // Scan command
20
+ /*
22
21
  const ignore = 'node_modules;target;build;.git';
23
22
  program
24
23
  .command('scan')
@@ -28,24 +27,18 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
28
27
  .option('-i, --ignores <REGEX>', 'ignore directories', ignore)
29
28
  .action(seedle.scan);
30
29
  */
31
- // Build command
32
- // TODO - detect if west is available and fail early
33
- program
34
- .command("build")
35
- .description("build atx zdk application")
36
- .option("-c, --config <CONFIG>", "workspace config file")
37
- .option("-C, --concurrent <NUMBER>", "how many builds to run concurrently")
38
- .option("-A, --application", "build applications")
39
- .option("-B, --bootloader", "build bootloaders")
40
- .option("-W, --wasm", "build wasm")
41
- .option("-y, --yes", "answer yes automatically")
42
- .action(build);
43
- // Load plugins
44
- // (await plugins()).forEach(({ plugin: _, path: __, description: ___ }) => {});
45
- try {
46
- await program.parseAsync();
47
- }
48
- catch (e) {
49
- console.error(e);
50
- }
51
- })();
30
+ // Build command
31
+ // TODO - detect if west is available and fail early
32
+ program
33
+ .command('build')
34
+ .description('build atx zdk application')
35
+ .option('-c, --config <CONFIG>', 'workspace config file')
36
+ .option('-C, --concurrent <NUMBER>', 'how many builds to run concurrently')
37
+ .option('-A, --application', 'build applications')
38
+ .option('-B, --bootloader', 'build bootloaders')
39
+ .option('-W, --wasm', 'build wasm')
40
+ .option('-y, --yes', 'answer yes automatically')
41
+ .action(build);
42
+ // Load plugins
43
+ // (await plugins()).forEach(({ plugin: _, path: __, description: ___ }) => {});
44
+ program.parseAsync().catch((e) => console.error(e));
package/dist/keys.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export function keys(map) {
2
2
  return Object.keys(map)
3
- .filter((key) => key !== "__key")
3
+ .filter((key) => key !== '__key')
4
4
  .map((key) => ({ ...map[key], __key: key }));
5
5
  }
package/dist/plugin.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import cp from 'node:child_process';
2
2
  import path from 'node:path';
3
- import { filter, from, lastValueFrom, map, merge, mergeMap, Observable, of, toArray, } from 'rxjs';
3
+ import { filter, from, lastValueFrom, map, merge, mergeMap, Observable, of, toArray } from 'rxjs';
4
4
  import { keys } from './keys.js';
5
5
  function npmls(g) {
6
- return of(['ls', `${g ? '-g' : ''}`, '-l', '--json', '--depth 0']).pipe(mergeMap(args => new Observable(subscriber => {
6
+ return of(['ls', `${g ? '-g' : ''}`, '-l', '--json', '--depth 0']).pipe(mergeMap((args) => new Observable((subscriber) => {
7
7
  const ls = cp.spawn('npm', args, { shell: true });
8
8
  let data = '';
9
- ls.stdout.on('data', next => (data = data.concat(data, next)));
10
- ls.on('error', e => subscriber.error(e));
9
+ ls.stdout.on('data', (next) => (data = data.concat(data, next)));
10
+ ls.on('error', (e) => subscriber.error(e));
11
11
  ls.on('exit', () => {
12
12
  try {
13
13
  subscriber.next(JSON.parse(data));
@@ -20,12 +20,12 @@ function npmls(g) {
20
20
  })));
21
21
  }
22
22
  function dependencies() {
23
- return obs$ => obs$.pipe(mergeMap(({ path: npmRoot, dependencies: deps }) => {
24
- return from(keys(deps)).pipe(filter(dep => dep.__key.startsWith('@altronix')), mergeMap(dep => from(Object.keys(dep.bin)).pipe(filter(bin => bin.startsWith('atx-')), map(bin => {
23
+ return (obs$) => obs$.pipe(mergeMap(({ path: npmRoot, dependencies: deps }) => {
24
+ return from(keys(deps)).pipe(filter((dep) => dep.__key.startsWith('@altronix')), mergeMap((dep) => from(Object.keys(dep.bin)).pipe(filter((bin) => bin.startsWith('atx-')), map((bin) => {
25
25
  return {
26
26
  path: path.join(npmRoot, 'node_modules', dep.__key, dep.bin[bin] || ''),
27
27
  description: dep.description,
28
- plugin: bin.substring(4),
28
+ plugin: bin.substring(4)
29
29
  };
30
30
  }))));
31
31
  }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@altronix/cli",
3
- "version": "0.7.12",
3
+ "version": "0.7.14",
4
4
  "license": "MIT",
5
5
  "bin": {
6
6
  "atx": "./dist/index.js"
@@ -13,21 +13,26 @@
13
13
  "dist"
14
14
  ],
15
15
  "dependencies": {
16
- "ink": "^4.1.0",
17
- "react": "^18.2.0",
18
16
  "@inquirer/confirm": "^3.1.14",
19
17
  "ajv": "^8.16.0",
20
18
  "commander": "^10.0.1",
21
19
  "dotenv": "^16.4.5",
20
+ "ink": "^4.1.0",
22
21
  "jsonc-parser": "^3.3.1",
22
+ "react": "^18.2.0",
23
23
  "rxjs": "^7.8.1"
24
24
  },
25
25
  "devDependencies": {
26
26
  "@sindresorhus/tsconfig": "^3.0.1",
27
27
  "@types/react": "^18.0.32",
28
+ "@typescript-eslint/eslint-plugin": "~5.44.0",
29
+ "@typescript-eslint/parser": "~5.44.0",
28
30
  "ava": "^5.2.0",
29
31
  "chalk": "^5.2.0",
32
+ "eslint": "~8.28.0",
33
+ "eslint-config-prettier": "~8.5.0",
30
34
  "eslint-config-xo-react": "^0.27.0",
35
+ "eslint-plugin-jest": "~27.1.7",
31
36
  "eslint-plugin-react": "^7.32.2",
32
37
  "eslint-plugin-react-hooks": "^4.6.0",
33
38
  "ink-testing-library": "^3.0.0",