@altronix/cli 0.7.11 → 0.7.13

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 { 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 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,59 +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",
177
- name: `${board}-${name}-${config}-${version}`
176
+ kind: 'west',
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
- const item = westItem(args).name;
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();
197
+ const err = new PassThrough();
198
+ let error = '';
196
199
  west.stdout.pipe(fout);
197
200
  west.stdout.pipe(out);
198
201
  west.stderr.pipe(ferr);
199
- west.on("error", (e) => {
200
- subscriber.error(e);
202
+ west.stderr.pipe(err);
203
+ west.on('error', (e) => {
201
204
  fout.close();
202
205
  ferr.close();
203
206
  out.destroy();
207
+ err.destroy();
208
+ subscriber.next({ item, error: e.name });
204
209
  });
205
- west.on("exit", () => {
210
+ west.on('exit', (_code) => {
206
211
  fout.close();
207
212
  ferr.close();
208
213
  out.destroy();
209
- subscriber.next({ item, complete: true });
210
- subscriber.complete();
214
+ err.destroy();
215
+ fs.stat(expect, (e) => {
216
+ e
217
+ ? subscriber.next({ item, error })
218
+ : subscriber.next({ item, complete: true });
219
+ subscriber.complete();
220
+ });
211
221
  });
212
- out.on("data", (data) => subscriber.next({ item, output: data.toString() }));
222
+ out.on('data', (d) => subscriber.next({ item, output: d.toString() }));
223
+ err.on('data', (d) => (error += d));
213
224
  })));
214
225
  }
215
226
  async function seedleOptionsNormalize(seedle, cwd, verbose) {
216
227
  const name = seedle.__key;
217
228
  const resolve = await resolver(cwd);
218
- const types = resolve("<atx>/lib/atx/types.cddl");
229
+ const types = resolve('<atx>/lib/atx/types.cddl');
219
230
  const installDir = resolve(seedle.installDir);
220
231
  const buildDir = path.join(installDir, name);
221
232
  const files = [...seedle.files, types].map((file) => path.resolve(cwd, file));
@@ -228,40 +239,40 @@ async function seedleOptionsNormalize(seedle, cwd, verbose) {
228
239
  cddl: path.join(installDir, name, `${name}.cddl`),
229
240
  outputFile: path.join(installDir, `${name}-build.log`),
230
241
  errorFile: path.join(installDir, `${name}-build.err`),
231
- prefix: seedle.prefix || "",
232
- namespace: seedle.namespace || "altronix",
233
- verbose,
242
+ prefix: seedle.prefix || '',
243
+ namespace: seedle.namespace || 'altronix',
244
+ verbose
234
245
  };
235
246
  }
236
247
  function seedle(opts) {
237
248
  const { name, namespace, cddl, cwd, installDir } = opts;
238
- const templatePath = path.resolve(cwd, "..", "seedle-template");
239
- const seedlePath = path.resolve(cwd, "..", "seedle", "seedle");
249
+ const templatePath = path.resolve(cwd, '..', 'seedle-template');
250
+ const seedlePath = path.resolve(cwd, '..', 'seedle', 'seedle');
240
251
  return of([
241
- "generate",
242
- "--force",
252
+ 'generate',
253
+ '--force',
243
254
  `--destination=${installDir}`,
244
255
  `--path=${templatePath}`,
245
256
  `--name=${name}`,
246
- "--overwrite",
257
+ '--overwrite',
247
258
  `-dnamespace=${namespace}`,
248
- `-dseedle-manifest-path=${seedlePath.replace(/\\/g, "\\\\")}`,
249
- `-dcddl=${cddl.replace(/\\/g, "\\\\")}`,
259
+ `-dseedle-manifest-path=${seedlePath.replace(/\\/g, '\\\\')}`,
260
+ `-dcddl=${cddl.replace(/\\/g, '\\\\')}`
250
261
  ]).pipe(mergeMap((seedleArgs) => new Observable((subscriber) => {
251
- const wasm = cp.spawn("cargo", seedleArgs, {
262
+ const wasm = cp.spawn('cargo', seedleArgs, {
252
263
  cwd: installDir,
253
- shell: true,
264
+ shell: true
254
265
  });
255
266
  const fout = fs.createWriteStream(opts.outputFile);
256
267
  const ferr = fs.createWriteStream(opts.errorFile);
257
268
  wasm.stdout.pipe(fout);
258
269
  wasm.stderr.pipe(ferr);
259
- wasm.on("error", (e) => {
270
+ wasm.on('error', (e) => {
260
271
  subscriber.error(e);
261
272
  fout.close();
262
273
  ferr.close();
263
274
  });
264
- wasm.on("exit", () => {
275
+ wasm.on('exit', () => {
265
276
  fout.close();
266
277
  ferr.close();
267
278
  subscriber.next();
@@ -270,16 +281,16 @@ function seedle(opts) {
270
281
  })));
271
282
  }
272
283
  function cmakeItem(opts) {
273
- const { name } = opts;
274
- return { kind: "wasm", name };
284
+ const { name: item } = opts;
285
+ return { kind: 'wasm', item };
275
286
  }
276
287
  function cmakeConfigure(opts) {
277
- const { buildDir, outputFile, errorFile, } = opts;
278
- const item = cmakeItem(opts).name;
288
+ const { buildDir, outputFile, errorFile } = opts;
289
+ const { item } = cmakeItem(opts);
279
290
  return of([`-B${buildDir}`, `-S${buildDir}`]).pipe(mergeMap((cmakeArgs) => new Observable((subscriber) => {
280
- const wasm = cp.spawn("cmake", cmakeArgs, {
291
+ const wasm = cp.spawn('cmake', cmakeArgs, {
281
292
  cwd: buildDir,
282
- shell: true,
293
+ shell: true
283
294
  });
284
295
  const fout = fs.createWriteStream(outputFile);
285
296
  const ferr = fs.createWriteStream(errorFile);
@@ -287,28 +298,28 @@ function cmakeConfigure(opts) {
287
298
  wasm.stdout.pipe(fout);
288
299
  wasm.stdout.pipe(out);
289
300
  wasm.stderr.pipe(ferr);
290
- wasm.on("error", (e) => {
291
- subscriber.error(e);
301
+ wasm.on('error', (e) => {
292
302
  fout.close();
293
303
  ferr.close();
294
304
  out.destroy();
305
+ subscriber.error(new BuildError(item, 'wasm', e.name));
295
306
  });
296
- wasm.on("exit", () => {
307
+ wasm.on('exit', () => {
297
308
  fout.close();
298
309
  ferr.close();
299
310
  out.destroy();
300
311
  subscriber.complete();
301
312
  });
302
- out.on("data", (data) => subscriber.next({ item, output: data.toString() }));
313
+ out.on('data', (data) => subscriber.next({ item, output: data.toString() }));
303
314
  })));
304
315
  }
305
316
  function cmakeBuild(opts) {
306
- const { buildDir, outputFile, errorFile, } = opts;
307
- const item = cmakeItem(opts).name;
317
+ const { buildDir, outputFile, errorFile } = opts;
318
+ const { item } = cmakeItem(opts);
308
319
  return of([`--build`, `${buildDir}`, `--target`, ` wasm`]).pipe(mergeMap((cmakeArgs) => new Observable((subscriber) => {
309
- const wasm = cp.spawn("cmake", cmakeArgs, {
320
+ const wasm = cp.spawn('cmake', cmakeArgs, {
310
321
  cwd: buildDir,
311
- shell: true,
322
+ shell: true
312
323
  });
313
324
  const fout = fs.createWriteStream(outputFile);
314
325
  const ferr = fs.createWriteStream(errorFile);
@@ -316,54 +327,54 @@ function cmakeBuild(opts) {
316
327
  wasm.stdout.pipe(fout);
317
328
  wasm.stdout.pipe(out);
318
329
  wasm.stderr.pipe(ferr);
319
- wasm.on("error", (e) => {
320
- subscriber.error(e);
330
+ wasm.on('error', (e) => {
321
331
  fout.close();
322
332
  ferr.close();
323
333
  out.destroy();
334
+ subscriber.error(new BuildError(item, 'wasm', e.name));
324
335
  });
325
- wasm.on("exit", () => {
336
+ wasm.on('exit', () => {
326
337
  fout.close();
327
338
  ferr.close();
328
339
  out.destroy();
329
340
  subscriber.complete();
330
341
  });
331
- out.on("data", (data) => subscriber.next({ item, output: data.toString() }));
342
+ out.on('data', (data) => subscriber.next({ item, output: data.toString() }));
332
343
  })));
333
344
  }
334
345
  function cmake(opts) {
335
- const { name: item } = cmakeItem(opts);
336
- return concat(cmakeConfigure(opts), cmakeBuild(opts), of({ item, complete: true }));
346
+ const { item } = cmakeItem(opts);
347
+ return concat(cmakeConfigure(opts), cmakeBuild(opts), of({ item, complete: true })).pipe(catchError(of));
337
348
  }
338
349
  function emulateBytePages(board) {
339
- return (board.startsWith("atsame54_xpro") ||
340
- board.startsWith("netway4e1bt") ||
341
- board.startsWith("netway4eb") ||
342
- board.startsWith("netway5pq") ||
343
- 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'));
344
355
  }
345
- function extraApplicationConfs(extraConfs) {
346
- const key = process.env["ALTRONIX_RELEASE_KEY"];
356
+ function extraAppConfs(extraConfs) {
357
+ const key = process.env['ALTRONIX_RELEASE_KEY'];
347
358
  if (!key)
348
- throw new Error("missing ALTRONIX_RELEASE_KEY from environment");
359
+ throw new Error('missing ALTRONIX_RELEASE_KEY from environment');
349
360
  const extraConfsData = [
350
361
  `CONFIG_MCUBOOT_SIGNATURE_KEY_FILE="${key}"`,
351
362
  `CONFIG_BOOTLOADER_MCUBOOT=y`,
352
- `CONFIG_ATX_UPDATE_ENABLE=y`,
353
- ].join("\r\n");
363
+ `CONFIG_ATX_UPDATE_ENABLE=y`
364
+ ].join('\r\n');
354
365
  return from(fs.promises.writeFile(extraConfs, extraConfsData));
355
366
  }
356
- function extraBootloaderConfs(board, extraConfs) {
357
- const key = process.env["ALTRONIX_RELEASE_KEY"];
367
+ function extraBootConfs(board, extraConfs) {
368
+ const key = process.env['ALTRONIX_RELEASE_KEY'];
358
369
  if (!key)
359
- throw new Error("missing ALTRONIX_RELEASE_KEY from environment");
370
+ throw new Error('missing ALTRONIX_RELEASE_KEY from environment');
360
371
  const extraConfsData = emulateBytePages(board)
361
372
  ? [
362
373
  `CONFIG_BOOT_SIGNATURE_KEY_FILE="${key}"`,
363
- `CONFIG_SOC_FLASH_SAM0_EMULATE_BYTE_PAGES=y`,
374
+ `CONFIG_SOC_FLASH_SAM0_EMULATE_BYTE_PAGES=y`
364
375
  ]
365
376
  : [`CONFIG_BOOT_SIGNATURE_KEY_FILE="${key}"`];
366
- return from(fs.promises.writeFile(extraConfs, extraConfsData.join("\r\n")));
377
+ return from(fs.promises.writeFile(extraConfs, extraConfsData.join('\r\n')));
367
378
  }
368
379
  function copy() {
369
380
  return (obs$) => obs$.pipe(mergeMap((opts) => {
@@ -378,7 +389,7 @@ function copy() {
378
389
  }));
379
390
  }
380
391
  function concatFiles(src, dest) {
381
- return from(src).pipe(concatMap((src) => from(fs.promises.readFile(src, "ascii"))), toArray(), mergeMap((arr) => from(fs.promises.writeFile(dest, arr.join("\r\n")))));
392
+ return from(src).pipe(concatMap((src) => from(fs.promises.readFile(src, 'ascii'))), toArray(), mergeMap((arr) => from(fs.promises.writeFile(dest, arr.join('\r\n')))));
382
393
  }
383
394
  function mkdir() {
384
395
  return (obs$) => obs$.pipe(mergeMap((dir) => new Observable((subscriber) => {
@@ -398,14 +409,14 @@ function rmdir(dir) {
398
409
  .finally(() => subscriber.complete());
399
410
  });
400
411
  }
401
- function exists() {
402
- return (obs$) => obs$.pipe(mergeMap((dir) => new Observable((subscriber) => {
412
+ function exists(dir) {
413
+ return new Observable((subscriber) => {
403
414
  fs.promises
404
415
  .stat(dir)
405
- .then(() => subscriber.next(dir))
406
- .catch(() => subscriber.next(undefined))
416
+ .then(() => subscriber.next(true))
417
+ .catch(() => subscriber.next(false))
407
418
  .finally(() => subscriber.complete());
408
- })));
419
+ });
409
420
  }
410
421
  function confirm(force) {
411
422
  return (obs$) => force
@@ -419,26 +430,26 @@ function throwIf(predicate, message) {
419
430
  });
420
431
  }
421
432
  function clean(force) {
422
- return (obs$) => obs$.pipe(exists(), concatMap((dir) => {
423
- return dir
424
- ? of(dir).pipe(confirm(force), throwIf((confirm) => !confirm, "User rejected delete"), mergeMap((_) => rmdir(dir)), map(() => void 0))
425
- : of(void 0);
426
- }));
433
+ return (obs$) => obs$.pipe(concatMap((dir) => exists(dir).pipe(concatMap((exists) => {
434
+ return exists
435
+ ? of(dir).pipe(confirm(force), throwIf((confirm) => !confirm, 'User rejected delete'), mergeMap((_) => rmdir(dir)), map(() => dir))
436
+ : of(dir);
437
+ }))));
427
438
  }
428
439
  function tovoid() {
429
440
  return (obs$) => obs$.pipe(map(() => void 0));
430
441
  }
431
442
  export async function build() {
432
- const config = this.opts()["config"] || path.resolve("./", "atx.json");
433
- const verbose = this.optsWithGlobals()["verbose"];
443
+ const config = this.opts()['config'] || path.resolve('./', 'atx.json');
444
+ const verbose = this.optsWithGlobals()['verbose'];
434
445
  const cwd = path.resolve(path.dirname(config));
435
- const data = await fs.promises.readFile(config, "ascii");
446
+ const data = await fs.promises.readFile(config, 'ascii');
436
447
  const atx = JSON.parse(data);
437
- const extraAppConfFile = "application.conf";
438
- const extraBootConfFile = "bootloader.conf";
448
+ const extraAppConfFile = 'application.conf';
449
+ const extraBootConfFile = 'bootloader.conf';
439
450
  if (!validate(atx))
440
451
  throw validate.errors;
441
- const env = path.resolve(cwd, ".env");
452
+ const env = path.resolve(cwd, '.env');
442
453
  dotenv.config({ path: env });
443
454
  const apps = keys(atx.applications).flatMap((app) => {
444
455
  return keys(app.boards).flatMap((board) => {
@@ -451,12 +462,12 @@ export async function build() {
451
462
  });
452
463
  });
453
464
  const wasm = keys(atx.wasm).map((w) => seedleOptionsNormalize(w, cwd, verbose));
454
- let concurrent = this.opts()["concurrent"]
455
- ? parseInt(this.opts()["concurrent"])
465
+ let concurrent = this.opts()['concurrent']
466
+ ? parseInt(this.opts()['concurrent'])
456
467
  : Infinity;
457
- let buildApps = this.opts()["application"];
458
- let buildBoot = this.opts()["bootloader"];
459
- let buildWasm = this.opts()["wasm"];
468
+ let buildApps = this.opts()['application'];
469
+ let buildBoot = this.opts()['bootloader'];
470
+ let buildWasm = this.opts()['wasm'];
460
471
  if (!(buildApps || buildBoot || buildWasm)) {
461
472
  buildApps = buildBoot = buildWasm = true;
462
473
  }
@@ -464,7 +475,8 @@ export async function build() {
464
475
  const boot$ = buildBoot ? from(await Promise.all(bootloaders)) : EMPTY;
465
476
  const wasm$ = buildWasm ? from(await Promise.all(wasm)) : EMPTY;
466
477
  // Handle all the preliminary stuff before building
467
- 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());
478
+ 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
479
+ 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());
468
480
  // Get an array of every build item.
469
481
  const items$ = merge(apps$.pipe(map(westItem)), boot$.pipe(map(westItem)), wasm$.pipe(map(cmakeItem))).pipe(toArray());
470
482
  // Run build commands for all bootloaders, applications and wasm concurrently
@@ -472,19 +484,22 @@ export async function build() {
472
484
  // Install everything into installDir
473
485
  const install$ = merge(apps$.pipe(map(({ name, config, version: ver, board, binaryDir, installDir: d }) => {
474
486
  return {
475
- src: path.join(binaryDir, "zephyr", "zephyr.signed.bin"),
476
- dst: path.join(d, `${board}-${name}-${config}-${ver}.signed.bin`),
487
+ src: path.join(binaryDir, 'zephyr', 'zephyr.signed.bin'),
488
+ dst: path.join(d, `${board}-${name}-${config}-${ver}.signed.bin`)
477
489
  };
478
490
  })), boot$.pipe(map(({ name, config, board, binaryDir, installDir: d }) => {
479
491
  return {
480
- src: path.join(binaryDir, "zephyr", "zephyr.bin"),
481
- dst: path.join(d, `${board}-${name}-${config}.bin`),
492
+ src: path.join(binaryDir, 'zephyr', 'zephyr.bin'),
493
+ dst: path.join(d, `${board}-${name}-${config}.bin`)
482
494
  };
483
495
  }))).pipe(copy(), tovoid());
484
496
  await lastValueFrom(ready$);
485
497
  const items = await lastValueFrom(items$);
486
- const renderer = render(React.createElement(Ui, { items: items, "progress$": build$ }));
487
- await lastValueFrom(build$);
498
+ const renderer = render(React.createElement(Ui, { items: items, "progress$": build$, onComplete: () => renderer.unmount() }));
499
+ await renderer.waitUntilExit();
488
500
  renderer.cleanup();
489
- return lastValueFrom(install$);
501
+ return lastValueFrom(install$).catch((e) => {
502
+ if (!(e instanceof EmptyError))
503
+ throw e;
504
+ });
490
505
  }
@@ -1,8 +1,13 @@
1
1
  import React from 'react';
2
2
  import { Observable } from 'rxjs';
3
+ export declare class BuildError extends Error implements BuildItem {
4
+ item: string;
5
+ kind: 'west' | 'wasm';
6
+ constructor(item: string, kind: 'west' | 'wasm', message: string);
7
+ }
3
8
  export interface BuildItem {
4
- name: string;
5
- kind: "west" | "wasm";
9
+ item: string;
10
+ kind: 'west' | 'wasm';
6
11
  }
7
12
  export interface BuildProgress<K extends string = string> {
8
13
  item: K;
@@ -13,5 +18,10 @@ export interface BuildProgress<K extends string = string> {
13
18
  export interface Options {
14
19
  items: BuildItem[];
15
20
  progress$: Observable<BuildProgress>;
21
+ onComplete: UseCompleteEffectCallback;
22
+ }
23
+ export default function ({ items, progress$, onComplete }: Options): React.JSX.Element;
24
+ interface UseCompleteEffectCallback<E extends Error = Error> {
25
+ (e?: E): void;
16
26
  }
17
- export default function ({ items, progress$ }: Options): React.JSX.Element;
27
+ export {};
package/dist/build.ui.js CHANGED
@@ -1,63 +1,114 @@
1
- import React, { useLayoutEffect, useState } from 'react';
2
- import { Box, Text } from "ink";
1
+ import React, { useLayoutEffect, useEffect, useState } from 'react';
2
+ import { Box, Text, useStdout } from 'ink';
3
3
  import { scan } from 'rxjs';
4
- export default function ({ items, progress$ }) {
4
+ export class BuildError extends Error {
5
+ constructor(item, kind, message) {
6
+ super(message);
7
+ Object.defineProperty(this, "item", {
8
+ enumerable: true,
9
+ configurable: true,
10
+ writable: true,
11
+ value: item
12
+ });
13
+ Object.defineProperty(this, "kind", {
14
+ enumerable: true,
15
+ configurable: true,
16
+ writable: true,
17
+ value: kind
18
+ });
19
+ }
20
+ }
21
+ export default function ({ items, progress$, onComplete }) {
22
+ const [col, _rows] = useStdoutDimensions();
5
23
  const [width, setWidth] = useState(0);
6
- const progress = useBuildEffect(progress$, items.map(({ name }) => name));
24
+ const progress = useBuildEffect(progress$, items.map(({ item: i }) => i));
25
+ useCompleteEffect(progress$, onComplete);
7
26
  useLayoutEffect(() => {
8
- const width = items
9
- .map(({ name }) => name)
10
- .reduce(calculateItemWidth, 0);
27
+ const width = items.map(({ item }) => item).reduce(calculateItemWidth, 0);
11
28
  setWidth(width);
12
29
  }, [items]);
13
- return (React.createElement(Box, { flexDirection: "column" }, items.map((item) => (React.createElement(Box, { key: item.name },
30
+ return (React.createElement(Box, { flexDirection: "column" }, items.map(({ item, kind }) => (React.createElement(Box, { key: item },
14
31
  React.createElement(Box, { width: width, marginRight: 1 },
15
- React.createElement(Text, { wrap: "truncate", bold: true }, item.name.padStart(width))),
32
+ React.createElement(Text, { wrap: "truncate", bold: true }, item.padStart(width))),
16
33
  React.createElement(Box, { width: 4, marginRight: 1 },
17
- React.createElement(Text, { wrap: "truncate", bold: true, color: buildColor(item.kind) }, item.kind.toUpperCase().padStart(4))),
18
- React.createElement(Box, { width: 2, marginRight: 1 }, progress[item.name] && React.createElement(Text, { color: "cyan" }, "=>")),
19
- React.createElement(Text, { dimColor: !progressComplete(progress[item.name]), color: progressColor(progress[item.name]) }, progress[item.name]))))));
34
+ React.createElement(Text, { wrap: "truncate", bold: true, color: buildColor(kind) }, kind.toUpperCase().padStart(4))),
35
+ React.createElement(Box, { width: 2, marginRight: 1 }, progress[item] && React.createElement(Text, { color: "cyan" }, "=>")),
36
+ React.createElement(Box, { width: col - width - 4 - 2 - 1 - 1 - 1 },
37
+ React.createElement(Text, { wrap: "truncate", dimColor: !progressComplete(progress[item]), color: progressColor(progress[item]) }, progress[item])))))));
20
38
  }
21
39
  function buildColor(kind) {
22
- return kind === "west" ? "blue" : "magenta";
40
+ return kind === 'west' ? 'blue' : 'magenta';
23
41
  }
24
42
  function progressComplete(progress) {
25
- return progress === "OK!";
43
+ return progress === 'OK!';
26
44
  }
27
45
  function progressColor(progress) {
28
- return progressComplete(progress) ? "green" : "white";
46
+ return !progress ||
47
+ progress.charAt(0) == '|' ||
48
+ progress.charAt(0) == '/' ||
49
+ progress.charAt(0) == '-' ||
50
+ progress.charAt(0) == '\\'
51
+ ? 'white'
52
+ : progressComplete(progress)
53
+ ? 'green'
54
+ : 'red';
29
55
  }
30
56
  function initProgress(items) {
31
- return items.reduce((acc, curr) => ({ ...acc, [curr]: "" }), {});
57
+ return items.reduce((acc, curr) => ({ ...acc, [curr]: '' }), {});
32
58
  }
33
59
  function calculateItemWidth(acc, next) {
34
60
  return next.length > acc ? next.length : acc;
35
61
  }
36
62
  function progressInc(progress) {
37
- if (progress.charAt(0) == "|") {
38
- return "/";
63
+ if (progress.charAt(0) == '|') {
64
+ return '/';
39
65
  }
40
- else if (progress.charAt(0) == "/") {
41
- return "-";
66
+ else if (progress.charAt(0) == '/') {
67
+ return '-';
42
68
  }
43
- else if (progress.charAt(0) == "-") {
44
- return "\\";
69
+ else if (progress.charAt(0) == '-') {
70
+ return '\\';
45
71
  }
46
72
  else {
47
- return "|";
73
+ return '|';
48
74
  }
49
75
  }
50
76
  function progressReducer(acc, next) {
51
- acc[next.item] = next.complete ? "OK!" : progressInc(acc[next.item]);
77
+ acc[next.item] = next.complete
78
+ ? 'OK!'
79
+ : next.error
80
+ ? next.error.replace(/\r?\n/g, '')
81
+ : progressInc(acc[next.item]);
52
82
  return acc;
53
83
  }
84
+ function useCompleteEffect(obs$, cb) {
85
+ useLayoutEffect(() => {
86
+ const s = obs$.subscribe({ complete: cb, error: cb });
87
+ return s.unsubscribe();
88
+ });
89
+ }
54
90
  function useBuildEffect(obs$, items) {
55
91
  const [progress, setProgress] = useState(initProgress(items));
56
92
  useLayoutEffect(() => {
57
- const s = obs$
58
- .pipe(scan(progressReducer, progress))
59
- .subscribe(progress => setProgress({ ...progress }));
93
+ const s = obs$.pipe(scan(progressReducer, progress)).subscribe({
94
+ next: (progress) => setProgress({ ...progress })
95
+ });
60
96
  return () => s.unsubscribe();
61
- }, [obs$, items]);
97
+ }, [obs$]);
62
98
  return progress;
63
99
  }
100
+ function useStdoutDimensions() {
101
+ const { stdout } = useStdout();
102
+ const [dimensions, setDimensions] = useState([
103
+ stdout.columns,
104
+ stdout.rows
105
+ ]);
106
+ useEffect(() => {
107
+ const handler = () => setDimensions([stdout.columns, stdout.rows]);
108
+ stdout.on('resize', handler);
109
+ return () => {
110
+ stdout.off('resize', handler);
111
+ };
112
+ }, [stdout]);
113
+ return dimensions;
114
+ }
package/dist/index.js CHANGED
@@ -1,44 +1,44 @@
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
10
  (async function main() {
11
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;
12
+ const pkg = path.resolve(__dirname, '..', 'package.json');
13
+ const ver = JSON.parse(await fs.promises.readFile(pkg, 'ascii')).version;
14
14
  // const cwd = path.resolve('./');
15
15
  program
16
- .name("atx")
17
- .description("build atx zdk projects")
16
+ .name('atx')
17
+ .description('build atx zdk projects')
18
18
  .version(ver)
19
- .option("-VV, --verbose", "extra logging");
19
+ .option('-VV, --verbose', 'extra logging');
20
20
  // Scan command
21
21
  /*
22
- const ignore = 'node_modules;target;build;.git';
23
- program
24
- .command('scan')
25
- .description('scan for *.cddl files')
26
- .option('-p, --path <PATH>', 'root directory to start scan', cwd)
27
- .option('-m, --matches <REGEX>', 'match expression', '.*cddl$')
28
- .option('-i, --ignores <REGEX>', 'ignore directories', ignore)
29
- .action(seedle.scan);
30
- */
22
+ const ignore = 'node_modules;target;build;.git';
23
+ program
24
+ .command('scan')
25
+ .description('scan for *.cddl files')
26
+ .option('-p, --path <PATH>', 'root directory to start scan', cwd)
27
+ .option('-m, --matches <REGEX>', 'match expression', '.*cddl$')
28
+ .option('-i, --ignores <REGEX>', 'ignore directories', ignore)
29
+ .action(seedle.scan);
30
+ */
31
31
  // Build command
32
32
  // TODO - detect if west is available and fail early
33
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")
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
42
  .action(build);
43
43
  // Load plugins
44
44
  // (await plugins()).forEach(({ plugin: _, path: __, description: ___ }) => {});
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.11",
3
+ "version": "0.7.13",
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",