@altronix/cli 0.7.2 → 0.7.4
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/app.d.ts +6 -0
- package/dist/app.js +7 -0
- package/dist/build.js +491 -0
- package/dist/build.ui.d.ts +17 -0
- package/dist/build.ui.js +62 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +24 -0
- package/dist/index.js +50 -0
- package/dist/keys.d.ts +6 -0
- package/dist/keys.js +5 -0
- package/dist/plugin.d.ts +7 -0
- package/dist/plugin.js +36 -0
- package/package.json +47 -31
- package/readme.md +25 -0
- package/bin/atx +0 -2
- package/build/build.js +0 -240
- package/build/build.js.map +0 -1
- package/build/index.js +0 -40
- package/build/index.js.map +0 -1
- package/build/seedle.d.ts +0 -2
- package/build/seedle.js +0 -41
- package/build/seedle.js.map +0 -1
- package/jest.config.js +0 -6
- package/src/build.ts +0 -354
- package/src/index.ts +0 -40
- package/src/seedle.ts +0 -39
- package/tsconfig.json +0 -29
- package/tsconfig.lib.json +0 -8
- package/tsconfig.lib.tsbuildinfo +0 -1
- /package/{build → dist}/build.d.ts +0 -0
- /package/{build → dist}/index.d.ts +0 -0
package/dist/app.d.ts
ADDED
package/dist/app.js
ADDED
package/dist/build.js
ADDED
|
@@ -0,0 +1,491 @@
|
|
|
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";
|
|
13
|
+
const schemaBuild = {
|
|
14
|
+
type: "object",
|
|
15
|
+
required: [],
|
|
16
|
+
patternProperties: {
|
|
17
|
+
".*": {
|
|
18
|
+
type: "object",
|
|
19
|
+
required: ["sourceDir", "binaryDir", "installDir", "boards"],
|
|
20
|
+
properties: {
|
|
21
|
+
sourceDir: { type: "string" },
|
|
22
|
+
binaryDir: { type: "string" },
|
|
23
|
+
installDir: { type: "string" },
|
|
24
|
+
boards: {
|
|
25
|
+
type: "object",
|
|
26
|
+
required: [],
|
|
27
|
+
patternProperties: {
|
|
28
|
+
".*": {
|
|
29
|
+
type: "object",
|
|
30
|
+
required: [],
|
|
31
|
+
patternProperties: {
|
|
32
|
+
".*": {
|
|
33
|
+
type: "object",
|
|
34
|
+
properties: {
|
|
35
|
+
configs: {
|
|
36
|
+
type: "array",
|
|
37
|
+
items: { type: "string" },
|
|
38
|
+
nullable: true,
|
|
39
|
+
},
|
|
40
|
+
overlays: {
|
|
41
|
+
type: "array",
|
|
42
|
+
items: { type: "string" },
|
|
43
|
+
nullable: true,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
const schemaSeedle = {
|
|
56
|
+
type: "object",
|
|
57
|
+
required: [],
|
|
58
|
+
patternProperties: {
|
|
59
|
+
".*": {
|
|
60
|
+
type: "object",
|
|
61
|
+
required: ["installDir", "files"],
|
|
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
|
+
},
|
|
70
|
+
};
|
|
71
|
+
const schema = {
|
|
72
|
+
type: "object",
|
|
73
|
+
required: ["applications", "bootloaders", "wasm"],
|
|
74
|
+
additionalProperties: false,
|
|
75
|
+
properties: {
|
|
76
|
+
applications: schemaBuild,
|
|
77
|
+
bootloaders: schemaBuild,
|
|
78
|
+
wasm: schemaSeedle,
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
const ajv = new Ajv({ allErrors: true, verbose: true });
|
|
82
|
+
const validate = ajv.compile(schema);
|
|
83
|
+
async function stat(path) {
|
|
84
|
+
return new Promise((resolve) => fs.stat(path, (err, stat) => {
|
|
85
|
+
if (err) {
|
|
86
|
+
resolve(false);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
resolve(stat);
|
|
90
|
+
}
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
async function resolver(cwd) {
|
|
94
|
+
// NOTE this assumes atx-zdk is named in the west.yml file as either atx or
|
|
95
|
+
// atx-zdk. The default case is atx-zdk. Most people never rename the
|
|
96
|
+
// yaml. However, if somebody wants to rename atx-zdk repo in their
|
|
97
|
+
// workspace. We can have atx.json() pass this name in and resolve it
|
|
98
|
+
// that way
|
|
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");
|
|
103
|
+
const atx = (await stat(atx0)) ? atx0 : (await stat(atx1)) ? atx1 : project;
|
|
104
|
+
return (dir, from) => {
|
|
105
|
+
if (dir.startsWith("<workspace>")) {
|
|
106
|
+
return path.join(workspace, dir.substring(12));
|
|
107
|
+
}
|
|
108
|
+
else if (dir.startsWith("<project>")) {
|
|
109
|
+
return path.join(project, dir.substring(10));
|
|
110
|
+
}
|
|
111
|
+
else if (dir.startsWith("<atx>")) {
|
|
112
|
+
return path.join(atx, dir.substring(6));
|
|
113
|
+
}
|
|
114
|
+
else if (path.isAbsolute(dir)) {
|
|
115
|
+
return dir;
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
return path.resolve(from || cwd, dir);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
async function parseAppVersion(v) {
|
|
123
|
+
const data = await fs.promises.readFile(v, "ascii");
|
|
124
|
+
const reMajor = data.matchAll(/^VERSION_MAJOR = ([0-9]+)/gm).next();
|
|
125
|
+
const reMinor = data.matchAll(/^VERSION_MINOR = ([0-9]+)/gm).next();
|
|
126
|
+
const rePatch = data.matchAll(/^PATCHLEVEL = ([0-9]+)/gm).next();
|
|
127
|
+
const reTweak = data.matchAll(/^VERSION_TWEAK = ([0-9]+)/gm).next();
|
|
128
|
+
const reExtra = data.matchAll(/^EXTRAVERSION = ([.a-zA-Z-]+)/gm).next();
|
|
129
|
+
return {
|
|
130
|
+
major: reMajor.value ? parseInt(reMajor.value[1]) : 0,
|
|
131
|
+
minor: reMinor.value ? parseInt(reMinor.value[1]) : 0,
|
|
132
|
+
patch: rePatch.value ? parseInt(rePatch.value[1]) : 0,
|
|
133
|
+
tweak: reTweak.value ? parseInt(reTweak.value[1]) : 0,
|
|
134
|
+
extra: reExtra.value ? reExtra.value[1] : "",
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function formatVersion(ver) {
|
|
138
|
+
const { major, minor, patch, tweak, extra } = ver;
|
|
139
|
+
return extra.length
|
|
140
|
+
? `${major}-${minor}-${patch}-${tweak}-${extra}`
|
|
141
|
+
: `${major}-${minor}-${patch}-${tweak}`;
|
|
142
|
+
}
|
|
143
|
+
async function westOptionsNormalize(board, config, build, cwd, verbose) {
|
|
144
|
+
const resolve = await resolver(cwd);
|
|
145
|
+
const name = build.__key;
|
|
146
|
+
const installDir = resolve(build.installDir);
|
|
147
|
+
const sourceDir = resolve(build.sourceDir);
|
|
148
|
+
const binaryDir = path.join(resolve(build.binaryDir), board, config.__key);
|
|
149
|
+
const versionFile = path.join(sourceDir, "VERSION");
|
|
150
|
+
const version = formatVersion(await parseAppVersion(versionFile));
|
|
151
|
+
const confs = config.configs
|
|
152
|
+
? config.configs.map((f) => resolve(f, sourceDir))
|
|
153
|
+
: [];
|
|
154
|
+
const overlays = config.overlays
|
|
155
|
+
? config.overlays.map((f) => resolve(f, sourceDir))
|
|
156
|
+
: [];
|
|
157
|
+
return {
|
|
158
|
+
name,
|
|
159
|
+
board,
|
|
160
|
+
config: config.__key,
|
|
161
|
+
cwd,
|
|
162
|
+
version,
|
|
163
|
+
sourceDir,
|
|
164
|
+
binaryDir,
|
|
165
|
+
installDir,
|
|
166
|
+
outputFile: path.join(binaryDir, "build.log"),
|
|
167
|
+
errorFile: path.join(binaryDir, "build.err"),
|
|
168
|
+
confs,
|
|
169
|
+
overlays,
|
|
170
|
+
verbose,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
function westItem(opts) {
|
|
174
|
+
const { board, name, config, version } = opts;
|
|
175
|
+
return {
|
|
176
|
+
kind: "west",
|
|
177
|
+
name: `${board}-${name}-${config}-${version}`
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function west(args) {
|
|
181
|
+
const { cwd, board, sourceDir, binaryDir, confs, overlays } = args;
|
|
182
|
+
const item = westItem(args).name;
|
|
183
|
+
return of([
|
|
184
|
+
`build`,
|
|
185
|
+
`-b ${board}`,
|
|
186
|
+
`-s ${sourceDir}`,
|
|
187
|
+
`-d ${binaryDir}`,
|
|
188
|
+
`--`,
|
|
189
|
+
`-DEXTRA_CONF_FILE="${[...confs].join(";")}"`,
|
|
190
|
+
`-DEXTRA_DTC_OVERLAY_FILE="${[...overlays].join(";")}"`,
|
|
191
|
+
]).pipe(mergeMap((westArgs) => new Observable((subscriber) => {
|
|
192
|
+
const west = cp.spawn("west", westArgs, { cwd, shell: true });
|
|
193
|
+
const fout = fs.createWriteStream(args.outputFile);
|
|
194
|
+
const ferr = fs.createWriteStream(args.errorFile);
|
|
195
|
+
const out = new PassThrough();
|
|
196
|
+
west.stdout.pipe(fout);
|
|
197
|
+
west.stdout.pipe(out);
|
|
198
|
+
west.stderr.pipe(ferr);
|
|
199
|
+
west.on("error", (e) => {
|
|
200
|
+
subscriber.error(e);
|
|
201
|
+
fout.close();
|
|
202
|
+
ferr.close();
|
|
203
|
+
out.destroy();
|
|
204
|
+
});
|
|
205
|
+
west.on("exit", () => {
|
|
206
|
+
fout.close();
|
|
207
|
+
ferr.close();
|
|
208
|
+
out.destroy();
|
|
209
|
+
subscriber.next({ item, complete: true });
|
|
210
|
+
subscriber.complete();
|
|
211
|
+
});
|
|
212
|
+
out.on("data", (data) => subscriber.next({ item, output: data.toString() }));
|
|
213
|
+
})));
|
|
214
|
+
}
|
|
215
|
+
async function seedleOptionsNormalize(seedle, cwd, verbose) {
|
|
216
|
+
const name = seedle.__key;
|
|
217
|
+
const resolve = await resolver(cwd);
|
|
218
|
+
const types = resolve("<atx>/lib/atx/types.cddl");
|
|
219
|
+
const installDir = resolve(seedle.installDir);
|
|
220
|
+
const buildDir = path.join(installDir, name);
|
|
221
|
+
const files = [...seedle.files, types].map((file) => path.resolve(cwd, file));
|
|
222
|
+
return {
|
|
223
|
+
name: seedle.__key,
|
|
224
|
+
cwd,
|
|
225
|
+
installDir,
|
|
226
|
+
buildDir,
|
|
227
|
+
files,
|
|
228
|
+
cddl: path.join(installDir, name, `${name}.cddl`),
|
|
229
|
+
outputFile: path.join(installDir, `${name}-build.log`),
|
|
230
|
+
errorFile: path.join(installDir, `${name}-build.err`),
|
|
231
|
+
prefix: seedle.prefix || "",
|
|
232
|
+
namespace: seedle.namespace || "altronix",
|
|
233
|
+
verbose,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
function seedle(opts) {
|
|
237
|
+
const { name, namespace, cddl, cwd, installDir } = opts;
|
|
238
|
+
const templatePath = path.resolve(cwd, "..", "seedle-template");
|
|
239
|
+
const seedlePath = path.resolve(cwd, "..", "seedle", "seedle");
|
|
240
|
+
return of([
|
|
241
|
+
"generate",
|
|
242
|
+
"--force",
|
|
243
|
+
`--destination=${installDir}`,
|
|
244
|
+
`--path=${templatePath}`,
|
|
245
|
+
`--name=${name}`,
|
|
246
|
+
"--overwrite",
|
|
247
|
+
`-dnamespace=${namespace}`,
|
|
248
|
+
`-dseedle-manifest-path=${seedlePath.replace(/\\/g, "\\\\")}`,
|
|
249
|
+
`-dcddl=${cddl.replace(/\\/g, "\\\\")}`,
|
|
250
|
+
]).pipe(mergeMap((seedleArgs) => new Observable((subscriber) => {
|
|
251
|
+
const wasm = cp.spawn("cargo", seedleArgs, {
|
|
252
|
+
cwd: installDir,
|
|
253
|
+
shell: true,
|
|
254
|
+
});
|
|
255
|
+
const fout = fs.createWriteStream(opts.outputFile);
|
|
256
|
+
const ferr = fs.createWriteStream(opts.errorFile);
|
|
257
|
+
wasm.stdout.pipe(fout);
|
|
258
|
+
wasm.stderr.pipe(ferr);
|
|
259
|
+
wasm.on("error", (e) => {
|
|
260
|
+
subscriber.error(e);
|
|
261
|
+
fout.close();
|
|
262
|
+
ferr.close();
|
|
263
|
+
});
|
|
264
|
+
wasm.on("exit", () => {
|
|
265
|
+
fout.close();
|
|
266
|
+
ferr.close();
|
|
267
|
+
subscriber.next();
|
|
268
|
+
subscriber.complete();
|
|
269
|
+
});
|
|
270
|
+
})));
|
|
271
|
+
}
|
|
272
|
+
function cmakeItem(opts) {
|
|
273
|
+
const { name } = opts;
|
|
274
|
+
return { kind: "wasm", name };
|
|
275
|
+
}
|
|
276
|
+
function cmakeConfigure(opts) {
|
|
277
|
+
const { buildDir, outputFile, errorFile, } = opts;
|
|
278
|
+
const item = cmakeItem(opts).name;
|
|
279
|
+
return of([`-B${buildDir}`, `-S${buildDir}`]).pipe(mergeMap((cmakeArgs) => new Observable((subscriber) => {
|
|
280
|
+
const wasm = cp.spawn("cmake", cmakeArgs, {
|
|
281
|
+
cwd: buildDir,
|
|
282
|
+
shell: true,
|
|
283
|
+
});
|
|
284
|
+
const fout = fs.createWriteStream(outputFile);
|
|
285
|
+
const ferr = fs.createWriteStream(errorFile);
|
|
286
|
+
const out = new PassThrough();
|
|
287
|
+
wasm.stdout.pipe(fout);
|
|
288
|
+
wasm.stdout.pipe(out);
|
|
289
|
+
wasm.stderr.pipe(ferr);
|
|
290
|
+
wasm.on("error", (e) => {
|
|
291
|
+
subscriber.error(e);
|
|
292
|
+
fout.close();
|
|
293
|
+
ferr.close();
|
|
294
|
+
out.destroy();
|
|
295
|
+
});
|
|
296
|
+
wasm.on("exit", () => {
|
|
297
|
+
fout.close();
|
|
298
|
+
ferr.close();
|
|
299
|
+
out.destroy();
|
|
300
|
+
subscriber.next({ item, complete: true });
|
|
301
|
+
subscriber.complete();
|
|
302
|
+
});
|
|
303
|
+
out.on("data", (data) => subscriber.next({ item, output: data.toString() }));
|
|
304
|
+
})));
|
|
305
|
+
}
|
|
306
|
+
function cmakeBuild(opts) {
|
|
307
|
+
const { buildDir, outputFile, errorFile, } = opts;
|
|
308
|
+
const item = cmakeItem(opts).name;
|
|
309
|
+
return of([`--build`, `${buildDir}`, `--target`, ` wasm`]).pipe(mergeMap((cmakeArgs) => new Observable((subscriber) => {
|
|
310
|
+
const wasm = cp.spawn("cmake", cmakeArgs, {
|
|
311
|
+
cwd: buildDir,
|
|
312
|
+
shell: true,
|
|
313
|
+
});
|
|
314
|
+
const fout = fs.createWriteStream(outputFile);
|
|
315
|
+
const ferr = fs.createWriteStream(errorFile);
|
|
316
|
+
const out = new PassThrough();
|
|
317
|
+
wasm.stdout.pipe(fout);
|
|
318
|
+
wasm.stdout.pipe(out);
|
|
319
|
+
wasm.stderr.pipe(ferr);
|
|
320
|
+
wasm.on("error", (e) => {
|
|
321
|
+
subscriber.error(e);
|
|
322
|
+
fout.close();
|
|
323
|
+
ferr.close();
|
|
324
|
+
out.destroy();
|
|
325
|
+
});
|
|
326
|
+
wasm.on("exit", () => {
|
|
327
|
+
fout.close();
|
|
328
|
+
ferr.close();
|
|
329
|
+
out.destroy();
|
|
330
|
+
subscriber.next({ item, complete: true });
|
|
331
|
+
subscriber.complete();
|
|
332
|
+
});
|
|
333
|
+
out.on("data", (data) => subscriber.next({ item, output: data.toString() }));
|
|
334
|
+
})));
|
|
335
|
+
}
|
|
336
|
+
function cmake(opts) {
|
|
337
|
+
return concat(cmakeConfigure(opts), cmakeBuild(opts));
|
|
338
|
+
}
|
|
339
|
+
function emulateBytePages(board) {
|
|
340
|
+
return (board.startsWith("atsame54_xpro") ||
|
|
341
|
+
board.startsWith("netway4e1bt") ||
|
|
342
|
+
board.startsWith("netway4eb") ||
|
|
343
|
+
board.startsWith("netway5pq") ||
|
|
344
|
+
board.startsWith("oa2b"));
|
|
345
|
+
}
|
|
346
|
+
function extraApplicationConfs(extraConfs) {
|
|
347
|
+
const key = process.env["ALTRONIX_RELEASE_KEY"];
|
|
348
|
+
if (!key)
|
|
349
|
+
throw new Error("missing ALTRONIX_RELEASE_KEY from environment");
|
|
350
|
+
const extraConfsData = [
|
|
351
|
+
`CONFIG_MCUBOOT_SIGNATURE_KEY_FILE="${key}"`,
|
|
352
|
+
`CONFIG_BOOTLOADER_MCUBOOT=y`,
|
|
353
|
+
`CONFIG_ATX_UPDATE_ENABLE=y`,
|
|
354
|
+
].join("\r\n");
|
|
355
|
+
return from(fs.promises.writeFile(extraConfs, extraConfsData));
|
|
356
|
+
}
|
|
357
|
+
function extraBootloaderConfs(board, extraConfs) {
|
|
358
|
+
const key = process.env["ALTRONIX_RELEASE_KEY"];
|
|
359
|
+
if (!key)
|
|
360
|
+
throw new Error("missing ALTRONIX_RELEASE_KEY from environment");
|
|
361
|
+
const extraConfsData = emulateBytePages(board)
|
|
362
|
+
? [
|
|
363
|
+
`CONFIG_BOOT_SIGNATURE_KEY_FILE="${key}"`,
|
|
364
|
+
`CONFIG_SOC_FLASH_SAM0_EMULATE_BYTE_PAGES=y`,
|
|
365
|
+
]
|
|
366
|
+
: [`CONFIG_BOOT_SIGNATURE_KEY_FILE="${key}"`];
|
|
367
|
+
return from(fs.promises.writeFile(extraConfs, extraConfsData.join("\r\n")));
|
|
368
|
+
}
|
|
369
|
+
function copy() {
|
|
370
|
+
return (obs$) => obs$.pipe(mergeMap((opts) => {
|
|
371
|
+
const { src, dst } = opts;
|
|
372
|
+
return new Observable((subscriber) => {
|
|
373
|
+
fs.promises
|
|
374
|
+
.copyFile(src, dst)
|
|
375
|
+
.then(() => subscriber.next(opts))
|
|
376
|
+
.catch(() => subscriber.next({ ...opts, err: dst }))
|
|
377
|
+
.finally(() => subscriber.complete());
|
|
378
|
+
});
|
|
379
|
+
}));
|
|
380
|
+
}
|
|
381
|
+
function concatFiles(src, dest) {
|
|
382
|
+
return from(src).pipe(concatMap((src) => from(fs.promises.readFile(src, "ascii"))), toArray(), mergeMap((arr) => from(fs.promises.writeFile(dest, arr.join("\r\n")))));
|
|
383
|
+
}
|
|
384
|
+
function mkdir() {
|
|
385
|
+
return (obs$) => obs$.pipe(mergeMap((dir) => new Observable((subscriber) => {
|
|
386
|
+
fs.promises
|
|
387
|
+
.mkdir(dir, { recursive: true })
|
|
388
|
+
.then(() => subscriber.next())
|
|
389
|
+
.catch((e) => subscriber.error(e))
|
|
390
|
+
.finally(() => subscriber.complete());
|
|
391
|
+
})));
|
|
392
|
+
}
|
|
393
|
+
function rmdir(dir) {
|
|
394
|
+
return new Observable((subscriber) => {
|
|
395
|
+
fs.promises
|
|
396
|
+
.rm(dir, { recursive: true })
|
|
397
|
+
.then(() => subscriber.next(dir))
|
|
398
|
+
.catch((e) => subscriber.error(e))
|
|
399
|
+
.finally(() => subscriber.complete());
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
function exists() {
|
|
403
|
+
return (obs$) => obs$.pipe(mergeMap((dir) => new Observable((subscriber) => {
|
|
404
|
+
fs.promises
|
|
405
|
+
.stat(dir)
|
|
406
|
+
.then(() => subscriber.next(dir))
|
|
407
|
+
.catch(() => subscriber.next(undefined))
|
|
408
|
+
.finally(() => subscriber.complete());
|
|
409
|
+
})));
|
|
410
|
+
}
|
|
411
|
+
function confirm(force) {
|
|
412
|
+
return (obs$) => force
|
|
413
|
+
? of(true)
|
|
414
|
+
: obs$.pipe(concatMap((dir) => from(inquirer({ message: `Delete ${dir}?` }))));
|
|
415
|
+
}
|
|
416
|
+
function throwIf(predicate, message) {
|
|
417
|
+
return tap((v) => {
|
|
418
|
+
if (predicate(v))
|
|
419
|
+
throw new Error(message);
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
function clean(force) {
|
|
423
|
+
return (obs$) => obs$.pipe(exists(), concatMap((dir) => {
|
|
424
|
+
return dir
|
|
425
|
+
? of(dir).pipe(confirm(force), throwIf((confirm) => !confirm, "User rejected delete"), mergeMap((_) => rmdir(dir)), map(() => void 0))
|
|
426
|
+
: of(void 0);
|
|
427
|
+
}));
|
|
428
|
+
}
|
|
429
|
+
function tovoid() {
|
|
430
|
+
return (obs$) => obs$.pipe(map(() => void 0));
|
|
431
|
+
}
|
|
432
|
+
export async function build() {
|
|
433
|
+
const config = this.opts()["config"] || path.resolve("./", "atx.json");
|
|
434
|
+
const verbose = this.optsWithGlobals()["verbose"];
|
|
435
|
+
const cwd = path.resolve(path.dirname(config));
|
|
436
|
+
const data = await fs.promises.readFile(config, "ascii");
|
|
437
|
+
const atx = JSON.parse(data);
|
|
438
|
+
const extraAppConfFile = "application.conf";
|
|
439
|
+
const extraBootConfFile = "bootloader.conf";
|
|
440
|
+
if (!validate(atx))
|
|
441
|
+
throw validate.errors;
|
|
442
|
+
const env = path.resolve(cwd, ".env");
|
|
443
|
+
dotenv.config({ path: env });
|
|
444
|
+
const apps = keys(atx.applications).flatMap((app) => {
|
|
445
|
+
return keys(app.boards).flatMap((board) => {
|
|
446
|
+
return keys(board).map((config) => westOptionsNormalize(board.__key, config, app, cwd, verbose));
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
const bootloaders = keys(atx.bootloaders).flatMap((app) => {
|
|
450
|
+
return keys(app.boards).flatMap((board) => {
|
|
451
|
+
return keys(board).map((config) => westOptionsNormalize(board.__key, config, app, cwd, verbose));
|
|
452
|
+
});
|
|
453
|
+
});
|
|
454
|
+
const wasm = keys(atx.wasm).map((w) => seedleOptionsNormalize(w, cwd, verbose));
|
|
455
|
+
let concurrent = this.opts()["concurrent"]
|
|
456
|
+
? parseInt(this.opts()["concurrent"])
|
|
457
|
+
: Infinity;
|
|
458
|
+
let buildApps = this.opts()["application"];
|
|
459
|
+
let buildBoot = this.opts()["bootloader"];
|
|
460
|
+
let buildWasm = this.opts()["wasm"];
|
|
461
|
+
if (!(buildApps || buildBoot || buildWasm)) {
|
|
462
|
+
buildApps = buildBoot = buildWasm = true;
|
|
463
|
+
}
|
|
464
|
+
const apps$ = buildApps ? from(await Promise.all(apps)) : EMPTY;
|
|
465
|
+
const boot$ = buildBoot ? from(await Promise.all(bootloaders)) : EMPTY;
|
|
466
|
+
const wasm$ = buildWasm ? from(await Promise.all(wasm)) : EMPTY;
|
|
467
|
+
// Handle all the preliminary stuff before building
|
|
468
|
+
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());
|
|
469
|
+
// Get an array of every build item.
|
|
470
|
+
const items$ = merge(apps$.pipe(map(westItem)), boot$.pipe(map(westItem)), wasm$.pipe(map(cmakeItem))).pipe(toArray());
|
|
471
|
+
// Run build commands for all bootloaders, applications and wasm concurrently
|
|
472
|
+
const build$ = merge(apps$.pipe(map(west)), boot$.pipe(map(west)), wasm$.pipe(map(cmake))).pipe(mergeMap((obs) => obs, concurrent), share());
|
|
473
|
+
// Install everything into installDir
|
|
474
|
+
const install$ = merge(apps$.pipe(map(({ name, config, version: ver, board, binaryDir, installDir: d }) => {
|
|
475
|
+
return {
|
|
476
|
+
src: path.join(binaryDir, "zephyr", "zephyr.signed.bin"),
|
|
477
|
+
dst: path.join(d, `${board}-${name}-${config}-${ver}.signed.bin`),
|
|
478
|
+
};
|
|
479
|
+
})), boot$.pipe(map(({ name, config, board, binaryDir, installDir: d }) => {
|
|
480
|
+
return {
|
|
481
|
+
src: path.join(binaryDir, "zephyr", "zephyr.bin"),
|
|
482
|
+
dst: path.join(d, `${board}-${name}-${config}.bin`),
|
|
483
|
+
};
|
|
484
|
+
}))).pipe(copy(), tovoid());
|
|
485
|
+
await lastValueFrom(ready$);
|
|
486
|
+
const items = await lastValueFrom(items$);
|
|
487
|
+
const renderer = render(React.createElement(Ui, { items: items, "progress$": build$ }));
|
|
488
|
+
await lastValueFrom(build$);
|
|
489
|
+
renderer.cleanup();
|
|
490
|
+
return lastValueFrom(install$);
|
|
491
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Observable } from 'rxjs';
|
|
3
|
+
export interface BuildItem {
|
|
4
|
+
name: string;
|
|
5
|
+
kind: "west" | "wasm";
|
|
6
|
+
}
|
|
7
|
+
export interface BuildProgress<K extends string = string> {
|
|
8
|
+
item: K;
|
|
9
|
+
output?: string;
|
|
10
|
+
error?: string;
|
|
11
|
+
complete?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface Options {
|
|
14
|
+
items: BuildItem[];
|
|
15
|
+
progress$: Observable<BuildProgress>;
|
|
16
|
+
}
|
|
17
|
+
export default function ({ items, progress$ }: Options): React.JSX.Element;
|
package/dist/build.ui.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import React, { useLayoutEffect, useState } from 'react';
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import { scan } from 'rxjs';
|
|
4
|
+
export default function ({ items, progress$ }) {
|
|
5
|
+
const [width, setWidth] = useState(0);
|
|
6
|
+
const progress = useBuildEffect(progress$, items.map(({ name }) => name));
|
|
7
|
+
useLayoutEffect(() => {
|
|
8
|
+
const width = items
|
|
9
|
+
.map(({ name }) => name)
|
|
10
|
+
.reduce(calculateItemWidth, 0);
|
|
11
|
+
setWidth(width);
|
|
12
|
+
}, [items]);
|
|
13
|
+
return (React.createElement(Box, { flexDirection: "column" }, items.map((item) => (React.createElement(Box, { key: item.name },
|
|
14
|
+
React.createElement(Box, { width: width, marginRight: 2 },
|
|
15
|
+
React.createElement(Text, { wrap: "truncate", bold: true }, item.name.padStart(width))),
|
|
16
|
+
React.createElement(Box, { width: 4, marginRight: 2 },
|
|
17
|
+
React.createElement(Text, { wrap: "truncate", bold: true, color: buildColor(item.kind) }, item.kind.toUpperCase().padStart(4))),
|
|
18
|
+
React.createElement(Text, { dimColor: !progressComplete(progress[item.name]), color: progressColor(progress[item.name]) }, progress[item.name]))))));
|
|
19
|
+
}
|
|
20
|
+
function buildColor(kind) {
|
|
21
|
+
return kind === "west" ? "blue" : "magenta";
|
|
22
|
+
}
|
|
23
|
+
function progressComplete(progress) {
|
|
24
|
+
return progress === "OK!";
|
|
25
|
+
}
|
|
26
|
+
function progressColor(progress) {
|
|
27
|
+
return progressComplete(progress) ? "green" : "white";
|
|
28
|
+
}
|
|
29
|
+
function initProgress(items) {
|
|
30
|
+
return items.reduce((acc, curr) => ({ ...acc, [curr]: "" }), {});
|
|
31
|
+
}
|
|
32
|
+
function calculateItemWidth(acc, next) {
|
|
33
|
+
return next.length > acc ? next.length : acc;
|
|
34
|
+
}
|
|
35
|
+
function progressInc(progress) {
|
|
36
|
+
if (progress.charAt(0) == "|") {
|
|
37
|
+
return "/";
|
|
38
|
+
}
|
|
39
|
+
else if (progress.charAt(0) == "/") {
|
|
40
|
+
return "-";
|
|
41
|
+
}
|
|
42
|
+
else if (progress.charAt(0) == "-") {
|
|
43
|
+
return "\\";
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
return "|";
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function progressReducer(acc, next) {
|
|
50
|
+
acc[next.item] = next.complete ? "OK!" : progressInc(acc[next.item]);
|
|
51
|
+
return acc;
|
|
52
|
+
}
|
|
53
|
+
function useBuildEffect(obs$, items) {
|
|
54
|
+
const [progress, setProgress] = useState(initProgress(items));
|
|
55
|
+
useLayoutEffect(() => {
|
|
56
|
+
const s = obs$
|
|
57
|
+
.pipe(scan(progressReducer, progress))
|
|
58
|
+
.subscribe(progress => setProgress({ ...progress }));
|
|
59
|
+
return () => s.unsubscribe();
|
|
60
|
+
}, [obs$, items]);
|
|
61
|
+
return progress;
|
|
62
|
+
}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { render } from 'ink';
|
|
4
|
+
import meow from 'meow';
|
|
5
|
+
import App from './app.js';
|
|
6
|
+
const cli = meow(`
|
|
7
|
+
Usage
|
|
8
|
+
$ cli
|
|
9
|
+
|
|
10
|
+
Options
|
|
11
|
+
--name Your name
|
|
12
|
+
|
|
13
|
+
Examples
|
|
14
|
+
$ cli --name=Jane
|
|
15
|
+
Hello, Jane
|
|
16
|
+
`, {
|
|
17
|
+
importMeta: import.meta,
|
|
18
|
+
flags: {
|
|
19
|
+
name: {
|
|
20
|
+
type: 'string',
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
render(React.createElement(App, { name: cli.flags.name }));
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { program } from "commander";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import { build } from "./build.js";
|
|
5
|
+
//import { plugins } from "./plugin.js";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
// https://stackoverflow.com/questions/8817423/why-is-dirname-not-defined-in-node-repl
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
(async function main() {
|
|
10
|
+
// Parse package.json to get version
|
|
11
|
+
const pkg = path.resolve(__dirname, "..", "package.json");
|
|
12
|
+
const ver = JSON.parse(await fs.promises.readFile(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
|
+
/*
|
|
21
|
+
const ignore = 'node_modules;target;build;.git';
|
|
22
|
+
program
|
|
23
|
+
.command('scan')
|
|
24
|
+
.description('scan for *.cddl files')
|
|
25
|
+
.option('-p, --path <PATH>', 'root directory to start scan', cwd)
|
|
26
|
+
.option('-m, --matches <REGEX>', 'match expression', '.*cddl$')
|
|
27
|
+
.option('-i, --ignores <REGEX>', 'ignore directories', ignore)
|
|
28
|
+
.action(seedle.scan);
|
|
29
|
+
*/
|
|
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
|
+
try {
|
|
45
|
+
await program.parseAsync();
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
console.error(e);
|
|
49
|
+
}
|
|
50
|
+
})();
|
package/dist/keys.d.ts
ADDED
package/dist/keys.js
ADDED