@altronix/cli 0.12.0 → 0.13.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.
package/dist/about.js CHANGED
@@ -1,4 +1,4 @@
1
- import { About, cborDecodeStr } from '@altronix/zdk';
1
+ import { About, cborDecodeStr } from '@altronix/zdk/common';
2
2
  import { Linq } from '@altronix/device';
3
3
  import { EmptyError, finalize, firstValueFrom, map, switchMap } from 'rxjs';
4
4
  import logger from './logger.js';
package/dist/cloud.js CHANGED
@@ -1,4 +1,4 @@
1
- import { cborDecodeNum, NetCloud } from '@altronix/zdk';
1
+ import { cborDecodeNum, NetCloud } from '@altronix/zdk/common';
2
2
  import { Linq } from '@altronix/device';
3
3
  import { concat, EmptyError, finalize, lastValueFrom, map, reduce, switchMap } from 'rxjs';
4
4
  import logger from './logger.js';
package/dist/index.js CHANGED
@@ -2,17 +2,8 @@
2
2
  import { program } from 'commander';
3
3
  import path from 'path';
4
4
  import fs from 'fs';
5
- import { build } from './build.js';
6
- import { getAbout, getSite, setSite } from './about.js';
7
- import { reboot, save, saveAndReboot } from './exe.js';
8
- import { getNet, setNet, setDhcp } from './net.js';
5
+ import { build } from '@altronix/build';
9
6
  import { fileURLToPath } from 'url';
10
- import { getCloud, setCloud } from './cloud.js';
11
- import { stress } from './stress.js';
12
- import { update } from './update.js';
13
- import { listen } from './listen.js';
14
- import { getDemo, getHello } from './sample.js';
15
- import { concurrent } from './concurrent.js';
16
7
  // https://stackoverflow.com/questions/8817423/why-is-dirname-not-defined-in-node-repl
17
8
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
18
9
  // Parse package.json to get version
@@ -51,95 +42,117 @@ program
51
42
  .option('--matches-config [regex]', 'config regex match expr')
52
43
  .option('-y, --yes', 'answer yes automatically')
53
44
  .action(build);
54
- const device = program
55
- .command('device')
56
- .description('manage zephyr device settings')
57
- .option('-p, --port <PORT>', 'listen port for incoming device connections')
58
- .option('-v, --verbose', 'print extra debug informating during request')
59
- .option('-f, --first', 'perform request on first device we see (any device)');
60
- device
61
- .command('get-about')
62
- .description('get about data on the device')
63
- .action(getAbout);
64
- device
65
- .command('get-site')
66
- .description('get the site ID of the device')
67
- .action(getSite);
68
- device
69
- .command('set-site')
70
- .description('set the site ID of the device')
71
- .argument('<site>', 'new site id')
72
- .action(setSite);
73
- device
74
- .command('save')
75
- .description('save data to persistant storage. (does not reboot)')
76
- .action(save);
77
- device
78
- .command('reboot')
79
- .description('reboot the device. (does not save)')
80
- .action(reboot);
81
- device
82
- .command('save-reboot')
83
- .description('save data to persistant storage and reboot the device')
84
- .action(saveAndReboot);
85
- device
86
- .command('get-net')
87
- .description('get network configuration from the device')
88
- .action(getNet);
89
- device
90
- .command('set-net')
91
- .description('set network interface into static ip mode')
92
- .argument('<ip>', 'the new IP address')
93
- .argument('<sn>', 'the new SUBNET mask')
94
- .argument('<gw>', 'the new GATEWAY address')
95
- .action(setNet);
96
- device
97
- .command('set-dhcp')
98
- .description('set network interface into DHCP mode')
99
- .action(setDhcp);
100
- device
101
- .command('get-cloud')
102
- .description('get cloud endpoint on the device')
103
- .action(getCloud);
104
- device
105
- .command('set-cloud')
106
- .description('set cloud endpoint on the device')
107
- .argument('<endpoint>', 'cloud service location')
108
- .action(setCloud);
109
- device
110
- .command('stress')
111
- .description('run a stress test on a device')
112
- .argument('<count>', 'how many requests to make')
113
- .action(stress);
114
- device
115
- .command('update')
116
- .description('run firmware update from file')
117
- .argument('<file>', 'file to update device with')
118
- .action(update);
119
- device
120
- .command('listen')
121
- .description('listen for alerts and heartbeats')
122
- .argument('<duration>', 'how long to listen')
123
- .action(listen);
124
- device
125
- .command('concurrent')
126
- .description('make a number of requests concurrently')
127
- .argument('<count>', 'how many requests to make concurrently')
128
- .action(concurrent);
129
- const sample = program
130
- .command('sample')
131
- .description('manage zephyr device settings')
132
- .option('-p, --port <PORT>', 'listen port for incoming device connections')
133
- .option('-v, --verbose', 'print extra debug informating during request')
134
- .option('-f, --first', 'perform request on first device we see (any device)');
135
- sample
136
- .command('get-demo')
137
- .description('read the demo data from the sample')
138
- .action(getDemo);
139
- sample
140
- .command('get-hello')
141
- .description('read the hello data from the sample')
142
- .action(getHello);
45
+ // Device commands are optional - only available when @altronix/device can be loaded
46
+ async function registerDeviceCommands() {
47
+ try {
48
+ // Test if device package is available
49
+ await import('@altronix/device');
50
+ // Dynamically import device-dependent modules
51
+ const { getAbout, getSite, setSite } = await import('./about.js');
52
+ const { reboot, save, saveAndReboot } = await import('./exe.js');
53
+ const { getNet, setNet, setDhcp } = await import('./net.js');
54
+ const { getCloud, setCloud } = await import('./cloud.js');
55
+ const { stress } = await import('./stress.js');
56
+ const { update } = await import('./update.js');
57
+ const { listen } = await import('./listen.js');
58
+ const { getDemo, getHello } = await import('./sample.js');
59
+ const { concurrent } = await import('./concurrent.js');
60
+ const device = program
61
+ .command('device')
62
+ .description('manage zephyr device settings')
63
+ .option('-p, --port <PORT>', 'listen port for incoming device connections')
64
+ .option('-v, --verbose', 'print extra debug informating during request')
65
+ .option('-f, --first', 'perform request on first device we see (any device)');
66
+ device
67
+ .command('get-about')
68
+ .description('get about data on the device')
69
+ .action(getAbout);
70
+ device
71
+ .command('get-site')
72
+ .description('get the site ID of the device')
73
+ .action(getSite);
74
+ device
75
+ .command('set-site')
76
+ .description('set the site ID of the device')
77
+ .argument('<site>', 'new site id')
78
+ .action(setSite);
79
+ device
80
+ .command('save')
81
+ .description('save data to persistant storage. (does not reboot)')
82
+ .action(save);
83
+ device
84
+ .command('reboot')
85
+ .description('reboot the device. (does not save)')
86
+ .action(reboot);
87
+ device
88
+ .command('save-reboot')
89
+ .description('save data to persistant storage and reboot the device')
90
+ .action(saveAndReboot);
91
+ device
92
+ .command('get-net')
93
+ .description('get network configuration from the device')
94
+ .action(getNet);
95
+ device
96
+ .command('set-net')
97
+ .description('set network interface into static ip mode')
98
+ .argument('<ip>', 'the new IP address')
99
+ .argument('<sn>', 'the new SUBNET mask')
100
+ .argument('<gw>', 'the new GATEWAY address')
101
+ .action(setNet);
102
+ device
103
+ .command('set-dhcp')
104
+ .description('set network interface into DHCP mode')
105
+ .action(setDhcp);
106
+ device
107
+ .command('get-cloud')
108
+ .description('get cloud endpoint on the device')
109
+ .action(getCloud);
110
+ device
111
+ .command('set-cloud')
112
+ .description('set cloud endpoint on the device')
113
+ .argument('<endpoint>', 'cloud service location')
114
+ .action(setCloud);
115
+ device
116
+ .command('stress')
117
+ .description('run a stress test on a device')
118
+ .argument('<count>', 'how many requests to make')
119
+ .action(stress);
120
+ device
121
+ .command('update')
122
+ .description('run firmware update from file')
123
+ .argument('<file>', 'file to update device with')
124
+ .action(update);
125
+ device
126
+ .command('listen')
127
+ .description('listen for alerts and heartbeats')
128
+ .argument('<duration>', 'how long to listen')
129
+ .action(listen);
130
+ device
131
+ .command('concurrent')
132
+ .description('make a number of requests concurrently')
133
+ .argument('<count>', 'how many requests to make concurrently')
134
+ .action(concurrent);
135
+ const sample = program
136
+ .command('sample')
137
+ .description('manage zephyr device settings')
138
+ .option('-p, --port <PORT>', 'listen port for incoming device connections')
139
+ .option('-v, --verbose', 'print extra debug informating during request')
140
+ .option('-f, --first', 'perform request on first device we see (any device)');
141
+ sample
142
+ .command('get-demo')
143
+ .description('read the demo data from the sample')
144
+ .action(getDemo);
145
+ sample
146
+ .command('get-hello')
147
+ .description('read the hello data from the sample')
148
+ .action(getHello);
149
+ }
150
+ catch {
151
+ // Device package not available - device commands will not be registered
152
+ }
153
+ }
143
154
  // Load plugins
144
155
  // (await plugins()).forEach(({ plugin: _, path: __, description: ___ }) => {});
145
- program.parseAsync().catch((e) => console.error(e));
156
+ registerDeviceCommands()
157
+ .then(() => program.parseAsync())
158
+ .catch((e) => console.error(e));
package/dist/net.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { concat, EmptyError, finalize, firstValueFrom, lastValueFrom, map, switchMap } from 'rxjs';
2
2
  import { Linq } from '@altronix/device';
3
- import { NetIp } from '@altronix/zdk';
3
+ import { NetIp } from '@altronix/zdk/common';
4
4
  import logger from './logger.js';
5
5
  import { select, first } from './select.js';
6
6
  export async function getNet() {
package/dist/plugin.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import cp from 'node:child_process';
2
2
  import path from 'node:path';
3
3
  import { filter, from, lastValueFrom, map, merge, mergeMap, Observable, of, toArray } from 'rxjs';
4
- import { keys } from './keys.js';
4
+ import { keys } from '@altronix/build';
5
5
  function npmls(g) {
6
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 });
@@ -1,7 +1,7 @@
1
1
  import React, { useLayoutEffect, useState } from 'react';
2
2
  import { scan } from 'rxjs';
3
3
  import { Box, Text } from 'ink';
4
- import useDimensions from './useStdoutDimensions.js';
4
+ import { useStdoutDimensions as useDimensions } from '@altronix/build';
5
5
  export default function Progress({ total, response$, onComplete }) {
6
6
  const [cols] = useDimensions();
7
7
  const [width, setWidth] = useState(Math.min(cols - 4, 76));
@@ -1,4 +1,4 @@
1
- import { AboutProps } from '@altronix/zdk';
1
+ import { AboutProps } from '@altronix/zdk/common';
2
2
  import React from 'react';
3
3
  import { Observable } from 'rxjs';
4
4
  export interface OnSelectCallback {
package/dist/stress.js CHANGED
@@ -1,4 +1,4 @@
1
- import { METH_CONSTANTS } from '@altronix/zdk';
1
+ import { METH_CONSTANTS } from '@altronix/zdk/common';
2
2
  import { Linq } from '@altronix/device';
3
3
  import { EmptyError, finalize, lastValueFrom, switchMap, pipe, repeat } from 'rxjs';
4
4
  import progress from './progress.js';
package/dist/update.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Linq } from '@altronix/device';
2
- import { Update } from '@altronix/zdk';
2
+ import { Update } from '@altronix/zdk/common';
3
3
  import { concat, defer, EmptyError, finalize, from, lastValueFrom, map, mergeScan, switchMap } from 'rxjs';
4
4
  import fs from 'fs';
5
5
  import logger from './logger.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@altronix/cli",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "bin": {
@@ -13,17 +13,17 @@
13
13
  "dist"
14
14
  ],
15
15
  "dependencies": {
16
- "@inquirer/confirm": "^3.1.14",
17
- "ajv": "^8.16.0",
16
+ "@altronix/zdk": "^0.9.0",
18
17
  "commander": "^10.0.1",
19
- "dotenv": "^16.4.5",
20
18
  "ink": "^4.1.0",
21
19
  "jsonc-parser": "^3.3.1",
22
20
  "react": "^18.2.0",
23
21
  "rxjs": "^7.8.1",
24
22
  "winston": "^3.13.0",
25
- "@altronix/device": "0.9.0",
26
- "@altronix/zdk": "0.9.0"
23
+ "@altronix/build": "0.13.0"
24
+ },
25
+ "optionalDependencies": {
26
+ "@altronix/device": "0.9.0"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@sindresorhus/tsconfig": "^3.0.1",
package/dist/build.d.ts DELETED
@@ -1,2 +0,0 @@
1
- import { Command } from 'commander';
2
- export declare function build(this: Command): Promise<void>;
package/dist/build.js DELETED
@@ -1,525 +0,0 @@
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
- const schemaBoard = {
14
- type: 'object',
15
- required: ['images'],
16
- properties: {
17
- version: { type: 'string', nullable: true },
18
- soc: { type: 'string', nullable: true },
19
- cpu: { type: 'string', nullable: true },
20
- variant: { type: 'string', nullable: true },
21
- images: {
22
- type: 'object',
23
- required: [],
24
- additionalProperties: {
25
- type: 'object',
26
- properties: {
27
- configs: {
28
- type: 'array',
29
- items: { type: 'string' },
30
- nullable: true
31
- },
32
- overlays: {
33
- type: 'array',
34
- items: { type: 'string' },
35
- nullable: true
36
- }
37
- }
38
- }
39
- }
40
- }
41
- };
42
- const schemaBuild = {
43
- type: 'object',
44
- required: [],
45
- additionalProperties: {
46
- type: 'object',
47
- required: ['sourceDir', 'binaryDir', 'installDir', 'boards'],
48
- properties: {
49
- sourceDir: { type: 'string' },
50
- binaryDir: { type: 'string' },
51
- installDir: { type: 'string' },
52
- boards: {
53
- type: 'object',
54
- required: [],
55
- additionalProperties: {
56
- anyOf: [{ ...schemaBoard }, { type: 'array', items: schemaBoard }]
57
- }
58
- }
59
- }
60
- }
61
- };
62
- const schemaSeedle = {
63
- type: 'object',
64
- required: [],
65
- additionalProperties: {
66
- type: 'object',
67
- required: ['installDir', 'files'],
68
- properties: {
69
- namespace: { type: 'string', nullable: true },
70
- prefix: { type: 'string', nullable: true },
71
- installDir: { type: 'string' },
72
- files: { type: 'array', items: { type: 'string' } }
73
- }
74
- }
75
- };
76
- const schema = {
77
- type: 'object',
78
- required: ['applications', 'bootloaders', 'wasm'],
79
- additionalProperties: false,
80
- properties: {
81
- applications: schemaBuild,
82
- bootloaders: schemaBuild,
83
- wasm: schemaSeedle
84
- }
85
- };
86
- const ajv = new Ajv({ allErrors: true, verbose: true });
87
- const validate = ajv.compile(schema);
88
- async function stat(path) {
89
- return new Promise((resolve) => fs.stat(path, (err, stat) => {
90
- if (err) {
91
- resolve(false);
92
- }
93
- else {
94
- resolve(stat);
95
- }
96
- }));
97
- }
98
- async function resolver(cwd) {
99
- // NOTE this assumes atx-zdk is named in the west.yml file as either atx or
100
- // atx-zdk. The default case is atx-zdk. Most people never rename the
101
- // yaml. However, if somebody wants to rename atx-zdk repo in their
102
- // workspace. We can have atx.json() pass this name in and resolve it
103
- // that way
104
- const project = path.resolve(cwd);
105
- const workspace = path.resolve(cwd, '..');
106
- const atx0 = path.resolve(cwd, '..', 'atx');
107
- const atx1 = path.resolve(cwd, '..', 'atx-zdk');
108
- const atx = (await stat(atx0)) ? atx0 : (await stat(atx1)) ? atx1 : project;
109
- return (dir, from) => {
110
- if (dir.startsWith('<workspace>')) {
111
- return path.join(workspace, dir.substring(12));
112
- }
113
- else if (dir.startsWith('<project>')) {
114
- return path.join(project, dir.substring(10));
115
- }
116
- else if (dir.startsWith('<atx>')) {
117
- return path.join(atx, dir.substring(6));
118
- }
119
- else if (path.isAbsolute(dir)) {
120
- return dir;
121
- }
122
- else {
123
- return path.resolve(from || cwd, dir);
124
- }
125
- };
126
- }
127
- async function parseAppVersion(v) {
128
- const data = await fs.promises.readFile(v, 'ascii');
129
- const reMajor = data.matchAll(/^VERSION_MAJOR = ([0-9]+)/gm).next();
130
- const reMinor = data.matchAll(/^VERSION_MINOR = ([0-9]+)/gm).next();
131
- const rePatch = data.matchAll(/^PATCHLEVEL = ([0-9]+)/gm).next();
132
- const reTweak = data.matchAll(/^VERSION_TWEAK = ([0-9]+)/gm).next();
133
- const reExtra = data.matchAll(/^EXTRAVERSION = ([\.a-zA-Z0-9-]+)/gm).next();
134
- return {
135
- major: reMajor.value ? parseInt(reMajor.value[1]) : 0,
136
- minor: reMinor.value ? parseInt(reMinor.value[1]) : 0,
137
- patch: rePatch.value ? parseInt(rePatch.value[1]) : 0,
138
- tweak: reTweak.value ? parseInt(reTweak.value[1]) : 0,
139
- extra: reExtra.value ? reExtra.value[1] : ''
140
- };
141
- }
142
- function formatVersion(ver) {
143
- const { major, minor, patch, tweak, extra } = ver;
144
- return extra.length
145
- ? `${major}-${minor}-${patch}-${tweak}-${extra}`
146
- : `${major}-${minor}-${patch}-${tweak}`;
147
- }
148
- async function westOptionsNormalize(board, config, build, cwd, verbose) {
149
- const resolve = await resolver(cwd);
150
- const name = build.__key;
151
- const boardTarget = [board.__key, board.soc, board.cpu, board.variant]
152
- .filter((item) => !!item)
153
- .join('/');
154
- const installDir = resolve(build.installDir);
155
- const sourceDir = resolve(build.sourceDir);
156
- const binaryDir = path.join(resolve(build.binaryDir), boardTarget.replaceAll('/', '-'), config.__key);
157
- const versionFile = path.join(sourceDir, 'VERSION');
158
- const version = formatVersion(await parseAppVersion(versionFile));
159
- const confs = config.configs
160
- ? config.configs.map((f) => resolve(f, sourceDir))
161
- : [];
162
- const overlays = config.overlays
163
- ? config.overlays.map((f) => resolve(f, sourceDir))
164
- : [];
165
- return {
166
- name,
167
- board: boardTarget.replaceAll('/', '-'),
168
- boardTarget,
169
- config: config.__key,
170
- cwd,
171
- version,
172
- sourceDir,
173
- binaryDir,
174
- installDir,
175
- outputFile: path.join(binaryDir, 'build.log'),
176
- errorFile: path.join(binaryDir, 'build.err'),
177
- confs,
178
- overlays,
179
- verbose
180
- };
181
- }
182
- function westItem(opts) {
183
- const { board, name, config, version } = opts;
184
- return {
185
- kind: 'west',
186
- item: `${board}-${name}-${config}-${version}`
187
- };
188
- }
189
- function west(args) {
190
- const { cwd, boardTarget, sourceDir, binaryDir, confs, overlays } = args;
191
- const { item } = westItem(args);
192
- const expect = path.join(binaryDir, 'zephyr', 'zephyr.bin');
193
- return of([
194
- `build`,
195
- `-b ${boardTarget}`,
196
- `-s ${sourceDir}`,
197
- `-d ${binaryDir}`,
198
- `--`,
199
- `-DEXTRA_CONF_FILE="${[...confs].join(';')}"`,
200
- `-DEXTRA_DTC_OVERLAY_FILE="${[...overlays].join(';')}"`
201
- ]).pipe(mergeMap((westArgs) => new Observable((subscriber) => {
202
- const west = cp.spawn('west', westArgs, { cwd, shell: true });
203
- const fout = fs.createWriteStream(args.outputFile);
204
- const ferr = fs.createWriteStream(args.errorFile);
205
- const out = new PassThrough();
206
- const err = new PassThrough();
207
- let error = '';
208
- west.stdout.pipe(fout);
209
- west.stdout.pipe(out);
210
- west.stderr.pipe(ferr);
211
- west.stderr.pipe(err);
212
- west.on('error', (e) => {
213
- fout.close();
214
- ferr.close();
215
- out.destroy();
216
- err.destroy();
217
- subscriber.next({ item, error: e.name });
218
- });
219
- west.on('exit', (_code) => {
220
- fout.close();
221
- ferr.close();
222
- out.destroy();
223
- err.destroy();
224
- fs.stat(expect, (e) => {
225
- e
226
- ? subscriber.next({ item, error })
227
- : subscriber.next({ item, complete: true });
228
- subscriber.complete();
229
- });
230
- });
231
- out.on('data', (d) => subscriber.next({ item, output: d.toString() }));
232
- err.on('data', (d) => (error += d));
233
- })));
234
- }
235
- async function seedleOptionsNormalize(seedle, cwd, verbose) {
236
- const name = seedle.__key;
237
- const resolve = await resolver(cwd);
238
- const types = resolve('<atx>/lib/atx/types.cddl');
239
- const installDir = resolve(seedle.installDir);
240
- const buildDir = path.join(installDir, name);
241
- const files = [...seedle.files, types].map((file) => path.resolve(cwd, file));
242
- return {
243
- name: seedle.__key,
244
- cwd,
245
- installDir,
246
- buildDir,
247
- files,
248
- cddl: path.join(installDir, name, `${name}.cddl`),
249
- outputFile: path.join(installDir, `${name}-build.log`),
250
- errorFile: path.join(installDir, `${name}-build.err`),
251
- prefix: seedle.prefix || '',
252
- namespace: seedle.namespace || 'altronix',
253
- verbose
254
- };
255
- }
256
- function seedle(opts) {
257
- const { name, namespace, cddl, cwd, installDir } = opts;
258
- const templatePath = path.resolve(cwd, '..', 'seedle-template');
259
- const seedlePath = path.resolve(cwd, '..', 'seedle', 'seedle');
260
- return of([
261
- 'generate',
262
- '--force',
263
- `--destination=${installDir}`,
264
- `--path=${templatePath}`,
265
- `--name=${name}`,
266
- '--overwrite',
267
- `-dnamespace=${namespace}`,
268
- `-dseedle-manifest-path=${seedlePath.replace(/\\/g, '\\\\')}`,
269
- `-dcddl=${cddl.replace(/\\/g, '\\\\')}`
270
- ]).pipe(mergeMap((seedleArgs) => new Observable((subscriber) => {
271
- const wasm = cp.spawn('cargo', seedleArgs, {
272
- cwd: installDir,
273
- shell: true
274
- });
275
- const fout = fs.createWriteStream(opts.outputFile);
276
- const ferr = fs.createWriteStream(opts.errorFile);
277
- wasm.stdout.pipe(fout);
278
- wasm.stderr.pipe(ferr);
279
- wasm.on('error', (e) => {
280
- subscriber.error(e);
281
- fout.close();
282
- ferr.close();
283
- });
284
- wasm.on('exit', () => {
285
- fout.close();
286
- ferr.close();
287
- subscriber.next();
288
- subscriber.complete();
289
- });
290
- })));
291
- }
292
- function cmakeItem(opts) {
293
- const { name: item } = opts;
294
- return { kind: 'wasm', item };
295
- }
296
- function cmakeConfigure(opts) {
297
- const { buildDir, outputFile, errorFile } = opts;
298
- const { item } = cmakeItem(opts);
299
- return of([`-B${buildDir}`, `-S${buildDir}`]).pipe(mergeMap((cmakeArgs) => new Observable((subscriber) => {
300
- const wasm = cp.spawn('cmake', cmakeArgs, {
301
- cwd: buildDir,
302
- shell: true
303
- });
304
- const fout = fs.createWriteStream(outputFile);
305
- const ferr = fs.createWriteStream(errorFile);
306
- const out = new PassThrough();
307
- wasm.stdout.pipe(fout);
308
- wasm.stdout.pipe(out);
309
- wasm.stderr.pipe(ferr);
310
- wasm.on('error', (e) => {
311
- fout.close();
312
- ferr.close();
313
- out.destroy();
314
- subscriber.error(new BuildError(item, 'wasm', e.name));
315
- });
316
- wasm.on('exit', () => {
317
- fout.close();
318
- ferr.close();
319
- out.destroy();
320
- subscriber.complete();
321
- });
322
- out.on('data', (data) => subscriber.next({ item, output: data.toString() }));
323
- })));
324
- }
325
- function cmakeBuild(opts) {
326
- const { buildDir, outputFile, errorFile } = opts;
327
- const { item } = cmakeItem(opts);
328
- return of([`--build`, `${buildDir}`, `--target`, ` wasm`]).pipe(mergeMap((cmakeArgs) => new Observable((subscriber) => {
329
- const wasm = cp.spawn('cmake', cmakeArgs, {
330
- cwd: buildDir,
331
- shell: true
332
- });
333
- const fout = fs.createWriteStream(outputFile);
334
- const ferr = fs.createWriteStream(errorFile);
335
- const out = new PassThrough();
336
- wasm.stdout.pipe(fout);
337
- wasm.stdout.pipe(out);
338
- wasm.stderr.pipe(ferr);
339
- wasm.on('error', (e) => {
340
- fout.close();
341
- ferr.close();
342
- out.destroy();
343
- subscriber.error(new BuildError(item, 'wasm', e.name));
344
- });
345
- wasm.on('exit', () => {
346
- fout.close();
347
- ferr.close();
348
- out.destroy();
349
- subscriber.complete();
350
- });
351
- out.on('data', (data) => subscriber.next({ item, output: data.toString() }));
352
- })));
353
- }
354
- function cmake(opts) {
355
- const { item } = cmakeItem(opts);
356
- return concat(cmakeConfigure(opts), cmakeBuild(opts), of({ item, complete: true })).pipe(catchError(of));
357
- }
358
- function emulateBytePages(board) {
359
- return (board.startsWith('same54_xpro') ||
360
- board.startsWith('netway') ||
361
- board.startsWith('oa2b'));
362
- }
363
- function extraAppConfs(extraConfs, config) {
364
- const key = process.env['ALTRONIX_RELEASE_KEY'];
365
- if (!key)
366
- throw new Error('missing ALTRONIX_RELEASE_KEY from environment');
367
- const extraConfsData = [
368
- `CONFIG_MCUBOOT_SIGNATURE_KEY_FILE="${key}"`,
369
- `CONFIG_BOOTLOADER_MCUBOOT=y`,
370
- `CONFIG_ATX_UPDATE_ENABLE=y`,
371
- `CONFIG_ATX_CONFIG="${config}"`
372
- ].join('\r\n');
373
- return from(fs.promises.writeFile(extraConfs, extraConfsData));
374
- }
375
- function extraBootConfs(board, extraConfs) {
376
- const key = process.env['ALTRONIX_RELEASE_KEY'];
377
- if (!key)
378
- throw new Error('missing ALTRONIX_RELEASE_KEY from environment');
379
- const extraConfsData = emulateBytePages(board)
380
- ? [`CONFIG_BOOT_SIGNATURE_KEY_FILE="${key}"`]
381
- : [`CONFIG_BOOT_SIGNATURE_KEY_FILE="${key}"`];
382
- return from(fs.promises.writeFile(extraConfs, extraConfsData.join('\r\n')));
383
- }
384
- function copy() {
385
- return (obs$) => obs$.pipe(mergeMap((opts) => {
386
- const { src, dst } = opts;
387
- const promise = fs.promises
388
- .copyFile(src, dst)
389
- .then(() => opts)
390
- .catch(() => ({ ...opts, err: dst }));
391
- return from(promise);
392
- }));
393
- }
394
- function concatFiles(src, dest) {
395
- return from(src).pipe(concatMap((src) => from(fs.promises.readFile(src, 'ascii'))), toArray(), mergeMap((arr) => from(fs.promises.writeFile(dest, arr.join('\r\n')))));
396
- }
397
- function mkdir() {
398
- return (obs$) => obs$.pipe(mergeMap((dir) => new Observable((subscriber) => {
399
- fs.promises
400
- .mkdir(dir, { recursive: true })
401
- .then(() => subscriber.next())
402
- .catch((e) => subscriber.error(e))
403
- .finally(() => subscriber.complete());
404
- })));
405
- }
406
- function rmdir(dir) {
407
- return new Observable((subscriber) => {
408
- fs.promises
409
- .rm(dir, { recursive: true })
410
- .then(() => subscriber.next(dir))
411
- .catch((e) => subscriber.error(e))
412
- .finally(() => subscriber.complete());
413
- });
414
- }
415
- function exists(dir) {
416
- return new Observable((subscriber) => {
417
- fs.promises
418
- .stat(dir)
419
- .then(() => subscriber.next(true))
420
- .catch(() => subscriber.next(false))
421
- .finally(() => subscriber.complete());
422
- });
423
- }
424
- function confirm(force) {
425
- return (obs$) => force
426
- ? of(true)
427
- : obs$.pipe(concatMap((dir) => from(inquirer({ message: `Delete ${dir}?` }))));
428
- }
429
- function throwIf(predicate, message) {
430
- return tap((v) => {
431
- if (predicate(v))
432
- throw new Error(message);
433
- });
434
- }
435
- function clean(force) {
436
- return (obs$) => obs$.pipe(concatMap((dir) => exists(dir).pipe(concatMap((exists) => {
437
- return exists
438
- ? of(dir).pipe(confirm(force), throwIf((confirm) => !confirm, 'User rejected delete'), mergeMap((_) => rmdir(dir)), map(() => dir))
439
- : of(dir);
440
- }))));
441
- }
442
- function tovoid() {
443
- return (obs$) => obs$.pipe(map(() => void 0));
444
- }
445
- export async function build() {
446
- const config = this.opts()['config'] || path.resolve('./', 'atx.json');
447
- const verbose = this.optsWithGlobals()['verbose'];
448
- const mBoard = new RegExp(this.opts()['matchesBoard'] || '(.*)');
449
- const mApplication = new RegExp(this.opts()['matchesApplication'] || '(.*)');
450
- const mConfig = new RegExp(this.opts()['matchesConfig'] || '(.*)');
451
- const cwd = path.resolve(path.dirname(config));
452
- const data = await fs.promises.readFile(config, 'ascii');
453
- const atx = JSON.parse(data);
454
- const extraAppConfFile = 'application.conf';
455
- const extraBootConfFile = 'bootloader.conf';
456
- if (!validate(atx))
457
- throw validate.errors;
458
- const env = path.resolve(cwd, '.env');
459
- dotenv.config({ path: env });
460
- const apps = keys(atx.applications)
461
- .filter(({ __key }) => __key.match(mApplication))
462
- .flatMap((app) => {
463
- return keys(app.boards)
464
- .filter(({ __key }) => __key.match(mBoard))
465
- .flatMap((board) => {
466
- return keys(board.images)
467
- .filter(({ __key }) => __key.match(mConfig))
468
- .map((config) => {
469
- return westOptionsNormalize(board, config, app, cwd, verbose);
470
- });
471
- });
472
- });
473
- const bootloaders = keys(atx.bootloaders).flatMap((app) => {
474
- return keys(app.boards)
475
- .filter(({ __key }) => __key.match(mBoard))
476
- .flatMap((board) => {
477
- return keys(board.images).map((config) => westOptionsNormalize(board, config, app, cwd, verbose));
478
- });
479
- });
480
- const wasm = keys(atx.wasm).map((w) => seedleOptionsNormalize(w, cwd, verbose));
481
- let concurrent = this.opts()['concurrent']
482
- ? parseInt(this.opts()['concurrent'])
483
- : Infinity;
484
- let buildApps = this.opts()['application'];
485
- let buildBoot = this.opts()['bootloader'];
486
- let buildWasm = this.opts()['wasm'];
487
- if (!(buildApps || buildBoot || buildWasm)) {
488
- buildApps = buildBoot = buildWasm = true;
489
- }
490
- const apps$ = buildApps ? from(await Promise.all(apps)) : EMPTY;
491
- const boot$ = buildBoot ? from(await Promise.all(bootloaders)) : EMPTY;
492
- const wasm$ = buildWasm ? from(await Promise.all(wasm)) : EMPTY;
493
- // Handle all the preliminary stuff before building
494
- 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
495
- clean(this.opts()['yes']), mkdir()), apps$.pipe(mergeMap(({ binaryDir, config }) => extraAppConfs(path.join(binaryDir, extraAppConfFile), config))), 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());
496
- // Get an array of every build item.
497
- const items$ = merge(apps$.pipe(map(westItem)), boot$.pipe(map(westItem)), wasm$.pipe(map(cmakeItem))).pipe(toArray());
498
- // Run build commands for all bootloaders, applications and wasm concurrently
499
- const build$ = merge(apps$.pipe(map(west)), boot$.pipe(map(west)), wasm$.pipe(map(cmake))).pipe(mergeMap((obs) => obs, concurrent), share());
500
- // Install everything into installDir
501
- const install$ = merge(apps$.pipe(map(({ name, config, version: ver, board, binaryDir, installDir: d }) => {
502
- return {
503
- src: path.join(binaryDir, 'zephyr', 'zephyr.signed.bin'),
504
- dst: path.join(d, `${board}-${name}-${config}-${ver}.signed.bin`)
505
- };
506
- })), boot$.pipe(map(({ name, config, board, binaryDir, installDir: d }) => {
507
- return {
508
- src: path.join(binaryDir, 'zephyr', 'zephyr.bin'),
509
- dst: path.join(d, `${board}-${name}-${config}.bin`)
510
- };
511
- }))).pipe(copy(), tovoid());
512
- try {
513
- await lastValueFrom(ready$);
514
- const items = await lastValueFrom(items$);
515
- const renderer = render(React.createElement(Ui, { items: items, "progress$": build$, onComplete: () => renderer.unmount() }));
516
- await renderer.waitUntilExit();
517
- renderer.cleanup();
518
- return lastValueFrom(install$);
519
- }
520
- catch (e) {
521
- if (!(e instanceof EmptyError))
522
- throw e;
523
- console.log('nothing to build');
524
- }
525
- }
@@ -1,27 +0,0 @@
1
- import React from 'react';
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
- }
8
- export interface BuildItem {
9
- item: string;
10
- kind: 'west' | 'wasm';
11
- }
12
- export interface BuildProgress<K extends string = string> {
13
- item: K;
14
- output?: string;
15
- error?: string;
16
- complete?: boolean;
17
- }
18
- export interface Options {
19
- items: BuildItem[];
20
- progress$: Observable<BuildProgress>;
21
- onComplete: BuildEffectCallback;
22
- }
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 DELETED
@@ -1,95 +0,0 @@
1
- import React, { useLayoutEffect, useState } from 'react';
2
- import { Box, Text } from 'ink';
3
- import { scan } from 'rxjs';
4
- import useStdoutDimensions from './useStdoutDimensions.js';
5
- export class BuildError extends Error {
6
- constructor(item, kind, message) {
7
- super(message);
8
- Object.defineProperty(this, "item", {
9
- enumerable: true,
10
- configurable: true,
11
- writable: true,
12
- value: item
13
- });
14
- Object.defineProperty(this, "kind", {
15
- enumerable: true,
16
- configurable: true,
17
- writable: true,
18
- value: kind
19
- });
20
- }
21
- }
22
- export default function ({ items, progress$, onComplete }) {
23
- const [col, _rows] = useStdoutDimensions();
24
- const [width, setWidth] = useState(0);
25
- const progress = useBuildEffect(progress$, items.map(({ item: i }) => i), onComplete);
26
- useLayoutEffect(() => {
27
- const width = items.map(({ item }) => item).reduce(calculateItemWidth, 0);
28
- setWidth(width);
29
- }, [items]);
30
- return (React.createElement(Box, { flexDirection: "column" }, items.map(({ item, kind }) => (React.createElement(Box, { key: item },
31
- React.createElement(Box, { width: width, marginRight: 1 },
32
- React.createElement(Text, { wrap: "truncate", bold: true }, item.padStart(width))),
33
- React.createElement(Box, { width: 4, marginRight: 1 },
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])))))));
38
- }
39
- function buildColor(kind) {
40
- return kind === 'west' ? 'blue' : 'magenta';
41
- }
42
- function progressComplete(progress) {
43
- return progress === 'OK!';
44
- }
45
- function progressColor(progress) {
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';
55
- }
56
- function initProgress(items) {
57
- return items.reduce((acc, curr) => ({ ...acc, [curr]: '' }), {});
58
- }
59
- function calculateItemWidth(acc, next) {
60
- return next.length > acc ? next.length : acc;
61
- }
62
- function progressInc(progress) {
63
- if (progress.charAt(0) == '|') {
64
- return '/';
65
- }
66
- else if (progress.charAt(0) == '/') {
67
- return '-';
68
- }
69
- else if (progress.charAt(0) == '-') {
70
- return '\\';
71
- }
72
- else {
73
- return '|';
74
- }
75
- }
76
- function progressReducer(acc, next) {
77
- acc[next.item] = next.complete
78
- ? 'OK!'
79
- : next.error
80
- ? next.error.replace(/\r?\n/g, '')
81
- : progressInc(acc[next.item]);
82
- return acc;
83
- }
84
- function useBuildEffect(obs$, items, cb) {
85
- const [progress, setProgress] = useState(initProgress(items));
86
- useLayoutEffect(() => {
87
- const s = obs$.pipe(scan(progressReducer, progress)).subscribe({
88
- next: (progress) => setProgress({ ...progress }),
89
- complete: cb,
90
- error: cb
91
- });
92
- return () => s.unsubscribe();
93
- }, [obs$]);
94
- return progress;
95
- }
package/dist/keys.d.ts DELETED
@@ -1,7 +0,0 @@
1
- export type WithKey<T> = T & {
2
- __key: string;
3
- };
4
- export type FlattenWithKey<Type> = Type extends Array<infer Item> ? Array<WithKey<Item>> : Array<WithKey<Type>>;
5
- export declare function keys<T>(map: {
6
- [key: string]: T;
7
- }): FlattenWithKey<T>;
package/dist/keys.js DELETED
@@ -1,14 +0,0 @@
1
- export function keys(map) {
2
- let ret = [];
3
- Object.keys(map)
4
- .filter((key) => key !== '__key')
5
- .forEach((key) => {
6
- if (Array.isArray(map[key])) {
7
- map[key].forEach((item) => ret.push({ ...item, __key: key }));
8
- }
9
- else {
10
- ret.push({ ...map[key], __key: key });
11
- }
12
- });
13
- return ret;
14
- }
@@ -1 +0,0 @@
1
- export default function useStdoutDimensions(): [number, number];
@@ -1,17 +0,0 @@
1
- import { useState, useEffect } from 'react';
2
- import { useStdout } from 'ink';
3
- export default function useStdoutDimensions() {
4
- const { stdout } = useStdout();
5
- const [dimensions, setDimensions] = useState([
6
- stdout.columns,
7
- stdout.rows
8
- ]);
9
- useEffect(() => {
10
- const handler = () => setDimensions([stdout.columns, stdout.rows]);
11
- stdout.on('resize', handler);
12
- return () => {
13
- stdout.off('resize', handler);
14
- };
15
- }, [stdout]);
16
- return dimensions;
17
- }