Skip to content

Commit

Permalink
cli: Add command line interface to combine/split uhex files.
Browse files Browse the repository at this point in the history
  • Loading branch information
carlosperate committed Feb 29, 2024
1 parent 0314f92 commit f6390c2
Show file tree
Hide file tree
Showing 6 changed files with 216 additions and 7 deletions.
19 changes: 17 additions & 2 deletions config/rollup.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { resolve } from 'path';
import builtins from 'builtin-modules';
import sourceMaps from 'rollup-plugin-sourcemaps';
import nodeResolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
Expand Down Expand Up @@ -28,7 +29,7 @@ const plugins = /** @type {Plugin[]} */ ([
commonjs(),
// Allow node_modules resolution. Use 'external' to control
// which external modules to include in the bundle
// https://github.com/rollup/rollup-plugin-node-resolve#usage
// https://github.com/rollup/plugins/blob/master/packages/node-resolve/
nodeResolve(),
sourceMaps(),
babel({
Expand Down Expand Up @@ -99,4 +100,18 @@ const umdConfigMin = createUmdConfig({
],
});

export default [umdConfig, umdConfigMin];
const cjsConfigCli = {
inlineDynamicImports: true,
external: [...external, ...Object.keys(pkg.dependencies), ...builtins],
input: resolve(dist, 'esm5', 'cli.js'),
output: {
banner: '#!/usr/bin/env node',
file: pkg.bin.uhex,
format: 'cjs',
name: pkg.config.umdName,
sourcemap: true,
},
plugins: [commonjs(), nodeResolve(), sourceMaps()],
};

export default [umdConfig, umdConfigMin, cjsConfigCli];
29 changes: 25 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
"universal hex",
"uh"
],
"bin": {
"uhex": "./dist/bundles/microbit-uh-cli.cjs.js"
},
"main": "./dist/bundles/microbit-uh.umd.js",
"mainMin": "./dist/bundles/microbit-uh.umd.min.js",
"module": "./dist/esm5/index.js",
Expand Down Expand Up @@ -46,6 +49,9 @@
"lint:fix": "npm run lint -- --fix",
"docs": "typedoc --options config/typedoc.json"
},
"dependencies": {
"commander": "^12.0.0"
},
"devDependencies": {
"@babel/core": "^7.23.9",
"@babel/polyfill": "^7.12.1",
Expand All @@ -72,4 +78,4 @@
"typedoc-neo-theme": "^1.1.1",
"typescript": "^4.9.5"
}
}
}
163 changes: 163 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import * as fs from 'fs';
import { sep } from 'path';
import * as process from 'process';
import { Command } from 'commander';
import * as microbitUh from './universal-hex';

function combine(
v1IntelHexPath: string,
v2IntelHexPath: string,
universalHexPath: string | undefined,
overwrite: boolean | undefined
) {
console.log('Combining Intel Hex files into Universal Hex');
console.log(`V1 Intel hex file: ${fs.realpathSync(v1IntelHexPath)}`);
console.log(`V2 Intel hex file: ${fs.realpathSync(v2IntelHexPath)}`);

if (!universalHexPath) {
// If the output path is not specified, save it in the current working directory
universalHexPath = `${process.cwd()}${sep}universal.hex`;
}
if (!overwrite && fs.existsSync(universalHexPath)) {
throw new Error(
`Output file already exists: ${fs.realpathSync(universalHexPath)}\n` +
'\tUse "--overwrite" flag to replace it.'
);
}

const v1IntelHexStr = fs.readFileSync(v1IntelHexPath, 'ascii');
const v2IntelHexStr = fs.readFileSync(v2IntelHexPath, 'ascii');
const universalHexStr = microbitUh.createUniversalHex([
{
hex: v1IntelHexStr,
boardId: microbitUh.microbitBoardId.V1,
},
{
hex: v2IntelHexStr,
boardId: microbitUh.microbitBoardId.V2,
},
]);
fs.writeFileSync(universalHexPath, universalHexStr, { encoding: 'ascii' });

console.log(`Universal Hex saved to: ${fs.realpathSync(universalHexPath)}`);
}

function separate(
universalHexPath: string,
v1IntelHexPath: string | undefined,
v2IntelHexPath: string | undefined,
overwrite: boolean | undefined
) {
console.log(
`Splitting Universal Hex file: ${fs.realpathSync(universalHexPath)}`
);
if (!v1IntelHexPath) {
v1IntelHexPath = `${process.cwd()}${sep}v1-intel.hex`;
}
if (!v2IntelHexPath) {
v2IntelHexPath = `${process.cwd()}${sep}v2-intel.hex`;
}
if (!overwrite && fs.existsSync(v1IntelHexPath)) {
throw new Error(
`Output V1 file already exists: ${fs.realpathSync(v1IntelHexPath)}\n` +
'\tUse "--overwrite" flag to replace it.'
);
}
if (!overwrite && fs.existsSync(v2IntelHexPath)) {
throw new Error(
`Output V2 file already exists: ${fs.realpathSync(v2IntelHexPath)}\n` +
'\tUse "--overwrite" flag to replace it.'
);
}

const universalHexStr = fs.readFileSync(universalHexPath, 'ascii');
const separatedHexes = microbitUh.separateUniversalHex(universalHexStr);
if (separatedHexes.length !== 2) {
const boardIds = separatedHexes.map((hexObj) => hexObj.boardId);
const errorMsg =
'Universal Hex should contain only two micro:bit Intel Hexes.\n' +
`Found ${separatedHexes.length}: ${boardIds.join(', ')}`;
throw new Error(errorMsg);
}

let intelHexV1Str = '';
let intelHexV2Str = '';
separatedHexes.forEach((hexObj) => {
if (microbitUh.V1_BOARD_IDS.includes(hexObj.boardId)) {
intelHexV1Str = hexObj.hex;
} else if (microbitUh.V2_BOARD_IDS.includes(hexObj.boardId)) {
intelHexV2Str = hexObj.hex;
}
});
if (!intelHexV1Str || !intelHexV2Str) {
const boardIds = separatedHexes.map((hexObj) => hexObj.boardId);
const errorMsg =
'Universal Hex does not contain both micro:bit Intel Hexes.\n' +
`Found hexes for following board IDs: ${boardIds.join(', ')}`;
throw new Error(errorMsg);
}
fs.writeFileSync(v1IntelHexPath, intelHexV1Str, { encoding: 'ascii' });
fs.writeFileSync(v2IntelHexPath, intelHexV2Str, { encoding: 'ascii' });

console.log(`V1 Intel Hex saved to: ${fs.realpathSync(v1IntelHexPath)}`);
console.log(`V2 Intel Hex saved to: ${fs.realpathSync(v2IntelHexPath)}`);
}

function cli(args: string[]): number {
const uHexCli = new Command();

uHexCli
.command('combine')
.requiredOption('-v1, --v1 <path>', 'Path to micro:bit V1 input Intel Hex')
.requiredOption('-v2, --v2 <path>', 'Path to micro:bit V2 input Intel Hex')
.option('-u, --universal <path>', 'Path to output Universal Hex')
.option('-o, --overwrite', 'Overwrite output file if it exists', false)
.action(
(options: {
v1: string;
v2: string;
universal?: string;
overwrite: boolean;
}) => {
try {
combine(options.v1, options.v2, options.universal, options.overwrite);
} catch (e) {
console.error('Error:', e.message);
process.exit(1);
}
}
);

uHexCli
.command('split')
.requiredOption('-u, --universal <path>', 'Path to input Universal Hex')
.option('-v1, --v1 <path>', 'Path to micro:bit V1 output Intel Hex')
.option('-v2, --v2 <path>', 'Path to micro:bit V2 output Intel Hex')
.option('-o, --overwrite', 'Overwrite output files if they exist', false)
.action(
(options: {
v1?: string;
v2?: string;
universal: string;
overwrite: boolean;
}) => {
try {
separate(
options.universal,
options.v1,
options.v2,
options.overwrite
);
} catch (e) {
console.error('Error:', e.message);
process.exit(1);
}
}
);

uHexCli.parse(args);

return 0;
}

process.exit(cli(process.argv));
3 changes: 3 additions & 0 deletions src/universal-hex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import * as ihex from './ihex';

const V1_BOARD_IDS = [0x9900, 0x9901];
const V2_BOARD_IDS = [0x9903, 0x9904, 0x9905, 0x9906];
const BLOCK_SIZE = 512;

/**
Expand Down Expand Up @@ -490,6 +491,8 @@ function separateUniversalHex(universalHexStr: string): IndividualHex[] {
}

export {
V1_BOARD_IDS,
V2_BOARD_IDS,
microbitBoardId,
IndividualHex,
iHexToCustomFormatBlocks,
Expand Down
1 change: 1 addition & 0 deletions tslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
},
"object-literal-sort-keys": false,
"interface-over-type-literal": false,
"no-console": false,
"no-bitwise": false
},
"jsRules": true
Expand Down

0 comments on commit f6390c2

Please sign in to comment.