partitions-tool-esp
v0.1.2
Published
Pure TypeScript library to generate and parse ESP-IDF partition tables, NVS, SPIFFS and FatFS partition images (Node + Browser).
Maintainers
Readme
partitions-tool-esp
A pure JavaScript / TypeScript implementation for generating and parsing ESP-IDF partition tables and their common partition contents.
Currently supports generation and parsing of the following partition formats:
- Partition Table
partition_table - NVS
nvs- Plain-text NVS v1 / v2
- multipage blob
- FAT
data.fat- FAT12 / FAT16 / FAT32
- SFN(8.3) or LFN
- Wear leveling
- SPIFFS
data.spiffs
Installation
pnpm add partitions-tool-esp # or use your preferred package managerQuick Start
[!TIP]
When the partition size of NVS, SPIFFS, or FATFS cannot accommodate the provided files, the related functions will throw an error.
Partition Table
The partition table supports mutual conversion between CSV format files and partitions.bin.
// PartitionTable is a class; import it from the subpath export.
import { PartitionTable } from 'partitions-tool-esp/partition-table';
const csv = `# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, , 0x6000,
phy_init, data, phy, , 0x1000,
factory, app, factory, , 1M,
`;
const table = PartitionTable.fromCSV(csv, { flashSize: 4 * 1024 * 1024 });
const bin = table.toBinary(); // Uint8Array, suitable for writing to partitions.bin
const roundtrip = PartitionTable.fromBinary(bin);
console.log(roundtrip.entries[0].name); // "nvs"
console.log(roundtrip.toCSV()); // restore CSVThe Partition Table generation logic is implemented based on gen_esp32part.py.
NVS
NVS partitions support three equivalent construction methods:
import { NVS } from 'partitions-tool-esp';
// 1) Using ESP-IDF `nvs_partition_gen.py` tool-style CSV
const csvBin = NVS.generate(
NVS.parseCSV(`key,type,encoding,value
storage,namespace,,
greeting,data,string,hello world
counter,data,u32,42
`),
{ size: 0x6000 },
);
// 2) Chained Builder
const builderBin = NVS.generate(
new NVS.NvsBuilder()
.namespace('storage')
.string('greeting', 'hello world')
.u32('counter', 42)
.binary('blob', new Uint8Array([0xde, 0xad, 0xbe, 0xef]))
.namespace('settings')
.u8('flag', 1)
.build(),
{ size: 0x6000 },
);
// 3) Structured object: namespace -> { key: value }
const objectBin = NVS.generate(
NVS.fromObject({
storage: {
greeting: 'hello world',
counter: 42, // inferred as u32
signed: -1, // inferred as i32
blob: new Uint8Array([0xde, 0xad]), // inferred as binary
flag: { type: 'u8', value: 1 }, // explicit width
hex: { type: 'binary', value: 'deadbeef', encoding: 'hex2bin' },
},
settings: {
bigCounter: 1n << 40n, // inferred as u64
},
}),
{ size: 0x6000 },
);
// Reverse parsing
const dump = NVS.parse(builderBin);
for (const page of dump.pages) {
for (const entry of page.entries) {
if (entry.state === 'Written') console.log(entry);
}
}In structured object mode, when a type is not explicitly specified, the library will automatically attempt to infer the data type. You can also specify it manually.
| JS Type | NVS Encoding |
| --------------------------------------- | -------------------------------------------------- |
| number (non-negative integer) | u32 |
| number (negative integer) | i32 |
| bigint (non-negative) | u64 |
| bigint (negative) | i64 |
| string | string |
| Uint8Array | binary (raw) |
| { type: 'u8'\|'i8'\|...'i64', value } | explicit integer type |
| { type: 'string', value } | explicit string |
| { type: 'binary', value, encoding? } | encoding can be 'raw' \| 'hex2bin' \| 'base64' |
The NVS tool is implemented based on nvs_partition_tool.
SPIFFS
import { SPIFFS, createDir, createFile } from 'partitions-tool-esp';
const image = SPIFFS.generate({
imageSize: 0x10000,
pageSize: 256,
objNameLen: 32,
metaLen: 4,
useMagic: true,
useMagicLength: true,
source: createDir('root', [createFile('hello.txt', new TextEncoder().encode('hello\n'))]),
});
const parsed = SPIFFS.parse(image, { pageSize: 256, objNameLen: 32, metaLen: 4 });
for (const f of parsed.files) {
console.log(f.path, new TextDecoder().decode(f.content));
}The SPIFFS functionality is implemented based on spiffsgen.py.
FatFS
Supports generation and parsing of FAT12 / FAT16 / FAT32, with optional wear leveling support.
import { FatFS, createDir, createFile } from 'partitions-tool-esp';
const image = FatFS.generate({
size: 512 * 1024,
source: createDir('', [
createFile('HELLO.TXT', new TextEncoder().encode('hello\n')),
// LFN (long filename) support is enabled by default, so you can use it directly
createFile('Hello Long Name.txt', new TextEncoder().encode('long\n')),
createDir('SUB', [createFile('INNER.TXT', new Uint8Array([1, 2, 3]))]),
]),
// volumeUuid: 0x12345678, // Volume UUID: optional, auto-assigned if omitted; specify for deterministic output
// explicitFatType: 32, // FAT Type: defaults to auto-select 12/16 by cluster count; pass 32 to force FAT32
// longFilenames: false, // Long filename support: only 8.3 filenames supported when LFN is disabled
// espIdfCompat: false, // IDF style preference: defaults to true
});
const parsed = FatFS.parse(image);
for (const { path, content } of FatFS.flatten(parsed.root)) {
console.log(path, content.byteLength);
}Parameter description:
longFilenamesLFN (long filename) support, enabled by default. When disabled, only 8.3 filenames are supported, and an error is thrown when encountering overly long filenames.espIdfCompatIDF style preference, enabled by default. The IDF built-infatfsgen.pyhas some preferences when generating FAT images, including UTF-16 lowercase byte conversion,-Nshort aliases, etc. SettingespIdfCompattotrueproduces output similar to IDF'sfatfsgen.py, while setting it tofalseproduces output preferred byfsck.fat.
To use with the wear_levelling component (i.e., enabling wear leveling), enable the wearLeveling option. The generated FATFS image will automatically reserve 1 dummy sector + 2 state sectors + 1 config sector (safe mode adds 2 more dump sectors), consistent with wl_fatfsgen.py:
import { FatFS, createDir, createFile } from 'partitions-tool-esp';
const image = FatFS.generate({
size: 1024 * 1024,
sectorSize: FatFS.WL_SECTOR_SIZE, // WL requires 4096
source: createDir('', [createFile('HELLO.TXT', new TextEncoder().encode('hi\n'))]),
wearLeveling: true, // or { mode: 'safe', deviceId: 0xdeadbeef }
});
// When parsing, declare wearLeveling: true, and the library will strip WL metadata before parsing.
const parsed = FatFS.parse(image, { wearLeveling: true });
// You can also manually unwrap / wrap:
const plain = FatFS.removeWearLeveling(image);
const wrapped = FatFS.wrapWearLeveling(plain, image.byteLength, { mode: 'perf' });IO Utility Helpers
This project provides some Node.js / browser I/O utility helpers.
// Node.js
import { readDir, writeDir } from 'partitions-tool-esp/io/node';
const tree = await readDir('./assets');
await writeDir(tree, './out');
// Browser
import { fromFileList, fromDirectoryHandle } from 'partitions-tool-esp/io/browser';
const dir1 = await fromFileList(inputElement.files!);
const dir2 = await fromDirectoryHandle(await showDirectoryPicker());Package Exports
| Entry | Purpose |
| ------------------------------------- | -------------------------------------------------------------------------------------------- |
| partitions-tool-esp | Aggregate export, equivalent to import * as PartitionTable from '.../partition-table' etc. |
| partitions-tool-esp/partition-table | Partition table only |
| partitions-tool-esp/nvs | NVS only |
| partitions-tool-esp/spiffs | SPIFFS only |
| partitions-tool-esp/fatfs | FatFS only |
| partitions-tool-esp/io/node | Node.js filesystem bridge |
| partitions-tool-esp/io/browser | Browser FileList / File System Access API bridge |
Development
pnpm install
pnpm prepare # Initialize husky pre-commit hook
pnpm format
pnpm lint
pnpm typecheck
pnpm test # run tests, including byte-level comparison tests against ESP-IDF built-in scripts
pnpm buildRegenerating Fixtures
The comparison files in tests/fixtures/ come from official ESP-IDF Python tools. When upstream formats change or new cases are needed:
# Enable ESP-IDF environment
source $IDF_PATH/export.sh
# Run the script (IDF_PATH environment variable should be available)
pnpm fixtures
# or
IDF_PATH=/path/to/esp-idf OUT=tests/fixtures bash scripts/build-fixtures.shscripts/build-fixtures.sh will invoke:
components/partition_table/gen_esp32part.pycomponents/nvs_flash/nvs_partition_generator/nvs_partition_gen.pycomponents/spiffs/spiffsgen.pycomponents/fatfs/fatfsgen.py
and write the results back to tests/fixtures/.
The
BS_VolID(4 bytes at offset 39) in the FatFS image is randomly generated byfatfsgen.py; test cases first read this value from the test sample and pass it asvolumeUuidtoFatFS.generate, achieving byte-level consistency.
Current Limitations and Roadmap
- FatFS: Supports FAT12 / FAT16 / FAT32, long filenames (LFN), wear leveling (
perf/safe). ESP-IDF itself only supports 4096B sectors for WL (the 512 sector_size WL path is a Python-side special case), and this library similarly requiressectorSize === 4096. - NVS: Encryption (
encrypted_partition) and version detection patches are not yet implemented.
License
Apache-2.0
