Skip to content

Commit c612595

Browse files
committed
cli: Add command line interface to combine/split uhex files.
1 parent 0314f92 commit c612595

File tree

6 files changed

+238
-7
lines changed

6 files changed

+238
-7
lines changed

config/rollup.config.js

+17-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { resolve } from 'path';
2+
import builtins from 'builtin-modules';
23
import sourceMaps from 'rollup-plugin-sourcemaps';
34
import nodeResolve from '@rollup/plugin-node-resolve';
45
import commonjs from '@rollup/plugin-commonjs';
@@ -28,7 +29,7 @@ const plugins = /** @type {Plugin[]} */ ([
2829
commonjs(),
2930
// Allow node_modules resolution. Use 'external' to control
3031
// which external modules to include in the bundle
31-
// https://github.com/rollup/rollup-plugin-node-resolve#usage
32+
// https://github.com/rollup/plugins/blob/master/packages/node-resolve/
3233
nodeResolve(),
3334
sourceMaps(),
3435
babel({
@@ -99,4 +100,18 @@ const umdConfigMin = createUmdConfig({
99100
],
100101
});
101102

102-
export default [umdConfig, umdConfigMin];
103+
const cjsConfigCli = {
104+
inlineDynamicImports: true,
105+
external: [...external, ...Object.keys(pkg.dependencies), ...builtins],
106+
input: resolve(dist, 'esm5', 'cli.js'),
107+
output: {
108+
banner: '#!/usr/bin/env node',
109+
file: pkg.bin.uhex,
110+
format: 'cjs',
111+
name: pkg.config.umdName,
112+
sourcemap: true,
113+
},
114+
plugins: [commonjs(), nodeResolve(), sourceMaps()],
115+
};
116+
117+
export default [umdConfig, umdConfigMin, cjsConfigCli];

package-lock.json

+25-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+7-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
"universal hex",
1010
"uh"
1111
],
12+
"bin": {
13+
"uhex": "./dist/bundles/microbit-uh-cli.cjs.js"
14+
},
1215
"main": "./dist/bundles/microbit-uh.umd.js",
1316
"mainMin": "./dist/bundles/microbit-uh.umd.min.js",
1417
"module": "./dist/esm5/index.js",
@@ -46,6 +49,9 @@
4649
"lint:fix": "npm run lint -- --fix",
4750
"docs": "typedoc --options config/typedoc.json"
4851
},
52+
"dependencies": {
53+
"commander": "^12.0.0"
54+
},
4955
"devDependencies": {
5056
"@babel/core": "^7.23.9",
5157
"@babel/polyfill": "^7.12.1",
@@ -72,4 +78,4 @@
7278
"typedoc-neo-theme": "^1.1.1",
7379
"typescript": "^4.9.5"
7480
}
75-
}
81+
}

src/cli.ts

+185
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
import * as fs from 'fs';
2+
import { sep } from 'path';
3+
import * as process from 'process';
4+
import { Command } from 'commander';
5+
import * as microbitUh from './universal-hex';
6+
7+
/**
8+
* Combines two micro:bit Intel Hex files into a Universal Hex file.
9+
*
10+
* @param v1IntelHexPath - Path to the input micro:bit V1 Intel Hex file.
11+
* @param v2IntelHexPath - Path to the input micro:bit V2 Intel Hex file.
12+
* @param universalHexPath - Path to the output Universal Hex file.
13+
* If not specified, it will be saved in the current working directory.
14+
* @param overwrite - Flag indicating whether to overwrite the output file
15+
* if it already exists.
16+
*/
17+
function combine(
18+
v1IntelHexPath: string,
19+
v2IntelHexPath: string,
20+
universalHexPath: string | undefined,
21+
overwrite: boolean | undefined
22+
) {
23+
console.log('Combining Intel Hex files into Universal Hex');
24+
console.log(`V1 Intel hex file: ${fs.realpathSync(v1IntelHexPath)}`);
25+
console.log(`V2 Intel hex file: ${fs.realpathSync(v2IntelHexPath)}`);
26+
27+
if (!universalHexPath) {
28+
// If the output path is not specified, save it in the current working directory
29+
universalHexPath = `${process.cwd()}${sep}universal.hex`;
30+
}
31+
if (!overwrite && fs.existsSync(universalHexPath)) {
32+
throw new Error(
33+
`Output file already exists: ${fs.realpathSync(universalHexPath)}\n` +
34+
'\tUse "--overwrite" flag to replace it.'
35+
);
36+
}
37+
38+
const v1IntelHexStr = fs.readFileSync(v1IntelHexPath, 'ascii');
39+
const v2IntelHexStr = fs.readFileSync(v2IntelHexPath, 'ascii');
40+
const universalHexStr = microbitUh.createUniversalHex([
41+
{
42+
hex: v1IntelHexStr,
43+
boardId: microbitUh.microbitBoardId.V1,
44+
},
45+
{
46+
hex: v2IntelHexStr,
47+
boardId: microbitUh.microbitBoardId.V2,
48+
},
49+
]);
50+
fs.writeFileSync(universalHexPath, universalHexStr, { encoding: 'ascii' });
51+
52+
console.log(`Universal Hex saved to: ${fs.realpathSync(universalHexPath)}`);
53+
}
54+
55+
/**
56+
* Separates a Universal Hex file into two micro:bit Intel Hex files.
57+
*
58+
* @param universalHexPath - Path to the input Universal Hex file to split.
59+
* @param v1IntelHexPath - Path to the output micro:bit V1 Intel Hex file to
60+
* save. If not specified, it will be saved in the current working directory.
61+
* @param v2IntelHexPath - Path to the output micro:bit V2 Intel Hex file to
62+
* save. If not specified, it will be saved in the current working directory.
63+
* @param overwrite - Flag indicating whether to overwrite the output files if
64+
* they already exist.
65+
*/
66+
function split(
67+
universalHexPath: string,
68+
v1IntelHexPath: string | undefined,
69+
v2IntelHexPath: string | undefined,
70+
overwrite: boolean | undefined
71+
) {
72+
console.log(
73+
`Splitting Universal Hex file: ${fs.realpathSync(universalHexPath)}`
74+
);
75+
if (!v1IntelHexPath) {
76+
v1IntelHexPath = `${process.cwd()}${sep}v1-intel.hex`;
77+
}
78+
if (!v2IntelHexPath) {
79+
v2IntelHexPath = `${process.cwd()}${sep}v2-intel.hex`;
80+
}
81+
if (!overwrite && fs.existsSync(v1IntelHexPath)) {
82+
throw new Error(
83+
`Output V1 file already exists: ${fs.realpathSync(v1IntelHexPath)}\n` +
84+
'\tUse "--overwrite" flag to replace it.'
85+
);
86+
}
87+
if (!overwrite && fs.existsSync(v2IntelHexPath)) {
88+
throw new Error(
89+
`Output V2 file already exists: ${fs.realpathSync(v2IntelHexPath)}\n` +
90+
'\tUse "--overwrite" flag to replace it.'
91+
);
92+
}
93+
94+
const universalHexStr = fs.readFileSync(universalHexPath, 'ascii');
95+
const separatedHexes = microbitUh.separateUniversalHex(universalHexStr);
96+
if (separatedHexes.length !== 2) {
97+
const boardIds = separatedHexes.map((hexObj) => hexObj.boardId);
98+
const errorMsg =
99+
'Universal Hex should contain only two micro:bit Intel Hexes.\n' +
100+
`Found ${separatedHexes.length}: ${boardIds.join(', ')}`;
101+
throw new Error(errorMsg);
102+
}
103+
104+
let intelHexV1Str = '';
105+
let intelHexV2Str = '';
106+
separatedHexes.forEach((hexObj) => {
107+
if (microbitUh.V1_BOARD_IDS.includes(hexObj.boardId)) {
108+
intelHexV1Str = hexObj.hex;
109+
} else if (microbitUh.V2_BOARD_IDS.includes(hexObj.boardId)) {
110+
intelHexV2Str = hexObj.hex;
111+
}
112+
});
113+
if (!intelHexV1Str || !intelHexV2Str) {
114+
const boardIds = separatedHexes.map((hexObj) => hexObj.boardId);
115+
const errorMsg =
116+
'Universal Hex does not contain both micro:bit Intel Hexes.\n' +
117+
`Found hexes for following board IDs: ${boardIds.join(', ')}`;
118+
throw new Error(errorMsg);
119+
}
120+
fs.writeFileSync(v1IntelHexPath, intelHexV1Str, { encoding: 'ascii' });
121+
fs.writeFileSync(v2IntelHexPath, intelHexV2Str, { encoding: 'ascii' });
122+
123+
console.log(`V1 Intel Hex saved to: ${fs.realpathSync(v1IntelHexPath)}`);
124+
console.log(`V2 Intel Hex saved to: ${fs.realpathSync(v2IntelHexPath)}`);
125+
}
126+
127+
/**
128+
* Command line interface for the Universal Hex tool.
129+
*
130+
* @param args - Command line arguments.
131+
* @returns Exit code.
132+
*/
133+
function cli(args: string[]): number {
134+
const uHexCli = new Command();
135+
136+
uHexCli
137+
.command('combine')
138+
.requiredOption('-v1, --v1 <path>', 'Path to input micro:bit V1 Intel Hex')
139+
.requiredOption('-v2, --v2 <path>', 'Path to input micro:bit V2 Intel Hex')
140+
.option('-u, --universal <path>', 'Path to output Universal Hex')
141+
.option('-o, --overwrite', 'Overwrite output file if it exists', false)
142+
.action(
143+
(options: {
144+
v1: string;
145+
v2: string;
146+
universal?: string;
147+
overwrite: boolean;
148+
}) => {
149+
try {
150+
combine(options.v1, options.v2, options.universal, options.overwrite);
151+
} catch (e) {
152+
console.error('Error:', e.message);
153+
process.exit(1);
154+
}
155+
}
156+
);
157+
158+
uHexCli
159+
.command('split')
160+
.requiredOption('-u, --universal <path>', 'Path to input Universal Hex')
161+
.option('-v1, --v1 <path>', 'Path to output micro:bit V1 Intel Hex')
162+
.option('-v2, --v2 <path>', 'Path to output micro:bit V2 Intel Hex')
163+
.option('-o, --overwrite', 'Overwrite output files if they exist', false)
164+
.action(
165+
(options: {
166+
v1?: string;
167+
v2?: string;
168+
universal: string;
169+
overwrite: boolean;
170+
}) => {
171+
try {
172+
split(options.universal, options.v1, options.v2, options.overwrite);
173+
} catch (e) {
174+
console.error('Error:', e.message);
175+
process.exit(1);
176+
}
177+
}
178+
);
179+
180+
uHexCli.parse(args);
181+
182+
return 0;
183+
}
184+
185+
process.exit(cli(process.argv));

src/universal-hex.ts

+3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import * as ihex from './ihex';
2020

2121
const V1_BOARD_IDS = [0x9900, 0x9901];
22+
const V2_BOARD_IDS = [0x9903, 0x9904, 0x9905, 0x9906];
2223
const BLOCK_SIZE = 512;
2324

2425
/**
@@ -490,6 +491,8 @@ function separateUniversalHex(universalHexStr: string): IndividualHex[] {
490491
}
491492

492493
export {
494+
V1_BOARD_IDS,
495+
V2_BOARD_IDS,
493496
microbitBoardId,
494497
IndividualHex,
495498
iHexToCustomFormatBlocks,

tslint.json

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
},
1717
"object-literal-sort-keys": false,
1818
"interface-over-type-literal": false,
19+
"no-console": false,
1920
"no-bitwise": false
2021
},
2122
"jsRules": true

0 commit comments

Comments
 (0)