feat(sd): source files stay off the card - only project DATA files auto-copy
On real hardware your code lives in flash, not on the microSD; a sketch listing the card should see its data (csv, json, txt, wav...), not its own source. buildProjectSdImage now skips .ino/.h/.hpp/.c/.cpp/.py from the workspace auto-copy. Explicit uploads are exempt - an upload always lands whatever its extension. Panel hint updated; tests cover both the exclusion and the upload exemption (15 green).
This commit is contained in:
parent
2dcb3eb65f
commit
36c414df8f
|
|
@ -141,16 +141,34 @@ describe('buildFat16Image', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('buildProjectSdImage', () => {
|
describe('buildProjectSdImage', () => {
|
||||||
it('auto-copies workspace (text) files into the image', () => {
|
it('auto-copies workspace DATA files; source files stay off the card', () => {
|
||||||
const ws = [
|
const ws = [
|
||||||
{ name: 'sketch.ino', content: 'void setup(){}' },
|
{ name: 'sketch.ino', content: 'void setup(){}' },
|
||||||
|
{ name: 'helper.h', content: '#define X 1' },
|
||||||
|
{ name: 'impl.cpp', content: 'int x;' },
|
||||||
|
{ name: 'script.py', content: 'print(1)' },
|
||||||
{ name: 'notes.txt', content: 'hello from the card' },
|
{ name: 'notes.txt', content: 'hello from the card' },
|
||||||
|
{ name: 'config.json', content: '{}' },
|
||||||
];
|
];
|
||||||
const got = readFat16(buildProjectSdImage(ws));
|
const got = readFat16(buildProjectSdImage(ws));
|
||||||
|
const names = got.map((g) => g.name.toLowerCase());
|
||||||
const notes = got.find((g) => g.name.toLowerCase() === 'notes.txt');
|
const notes = got.find((g) => g.name.toLowerCase() === 'notes.txt');
|
||||||
expect(notes).toBeTruthy();
|
expect(notes).toBeTruthy();
|
||||||
expect(new TextDecoder().decode(notes!.data)).toBe('hello from the card');
|
expect(new TextDecoder().decode(notes!.data)).toBe('hello from the card');
|
||||||
expect(got.some((g) => g.name.toLowerCase() === 'sketch.ino')).toBe(true);
|
expect(names).toContain('config.json');
|
||||||
|
// Code lives in flash, not on the microSD.
|
||||||
|
expect(names).not.toContain('sketch.ino');
|
||||||
|
expect(names).not.toContain('helper.h');
|
||||||
|
expect(names).not.toContain('impl.cpp');
|
||||||
|
expect(names).not.toContain('script.py');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('an explicit upload lands even with a source extension', () => {
|
||||||
|
const uploaded = decodeSdFiles([
|
||||||
|
{ name: 'ref.py', contentB64: bytesToB64(new TextEncoder().encode('print(2)')) },
|
||||||
|
]);
|
||||||
|
const got = readFat16(buildProjectSdImage([], uploaded));
|
||||||
|
expect(got.some((g) => g.name.toLowerCase() === 'ref.py')).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('uploaded (paid) files override same-named project files', () => {
|
it('uploaded (paid) files override same-named project files', () => {
|
||||||
|
|
@ -161,11 +179,14 @@ describe('buildProjectSdImage', () => {
|
||||||
expect(Array.from(f.data)).toEqual([0xde, 0xad, 0xbe, 0xef]); // binary, not "TEXT"
|
expect(Array.from(f.data)).toEqual([0xde, 0xad, 0xbe, 0xef]); // binary, not "TEXT"
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds uploaded binary files alongside project files', () => {
|
it('adds uploaded binary files alongside project data files', () => {
|
||||||
const ws = [{ name: 'main.ino', content: 'x' }];
|
const ws = [
|
||||||
|
{ name: 'main.ino', content: 'x' }, // source: stays off the card
|
||||||
|
{ name: 'readme.txt', content: 'data' },
|
||||||
|
];
|
||||||
const uploaded = [{ name: 'logo.bmp', data: new Uint8Array(800).fill(0x42) }];
|
const uploaded = [{ name: 'logo.bmp', data: new Uint8Array(800).fill(0x42) }];
|
||||||
const got = readFat16(buildProjectSdImage(ws, uploaded));
|
const got = readFat16(buildProjectSdImage(ws, uploaded));
|
||||||
expect(got.length).toBe(2);
|
expect(got.length).toBe(2); // readme.txt + logo.bmp, no main.ino
|
||||||
const logo = got.find((g) => g.name.toLowerCase() === 'logo.bmp')!;
|
const logo = got.find((g) => g.name.toLowerCase() === 'logo.bmp')!;
|
||||||
expect(logo.data.length).toBe(800);
|
expect(logo.data.length).toBe(800);
|
||||||
expect(logo.data.every((b) => b === 0x42)).toBe(true);
|
expect(logo.data.every((b) => b === 0x42)).toBe(true);
|
||||||
|
|
|
||||||
|
|
@ -118,8 +118,9 @@ export const SdCardPanel: React.FC<SdCardPanelProps> = ({ files, onChange, board
|
||||||
</div>
|
</div>
|
||||||
{files.length === 0 && (
|
{files.length === 0 && (
|
||||||
<div className="sd-card-hint">
|
<div className="sd-card-hint">
|
||||||
Upload your own files (images, audio, data). Project files are added
|
Upload your own files (images, audio, data). The project's data files
|
||||||
automatically.
|
are added automatically; source files (.ino, .h, .cpp, .py) stay off
|
||||||
|
the card.
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{files.map((f) => (
|
{files.map((f) => (
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,12 @@ export function decodeSdFiles(raw: unknown): SdFile[] {
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Source files never land on the card: on real hardware your code lives in
|
||||||
|
* flash, not on the microSD, and a sketch listing the card should see its
|
||||||
|
* DATA (csv, json, txt, wav...), not its own source. Uploads are exempt —
|
||||||
|
* an explicit upload always lands, whatever its extension. */
|
||||||
|
const SD_EXCLUDED_SOURCES = /\.(ino|h|hpp|c|cpp|py)$/i;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build the SD image from the project workspace files (auto-copied, free) plus
|
* Build the SD image from the project workspace files (auto-copied, free) plus
|
||||||
* any uploaded files (paid). Uploaded files override same-named project files.
|
* any uploaded files (paid). Uploaded files override same-named project files.
|
||||||
|
|
@ -70,6 +76,7 @@ export function buildProjectSdImage(
|
||||||
): Uint8Array {
|
): Uint8Array {
|
||||||
const byName = new Map<string, SdFile>();
|
const byName = new Map<string, SdFile>();
|
||||||
for (const f of workspaceFiles) {
|
for (const f of workspaceFiles) {
|
||||||
|
if (SD_EXCLUDED_SOURCES.test(f.name)) continue;
|
||||||
byName.set(f.name.toLowerCase(), { name: f.name, data: new TextEncoder().encode(f.content) });
|
byName.set(f.name.toLowerCase(), { name: f.name, data: new TextEncoder().encode(f.content) });
|
||||||
}
|
}
|
||||||
for (const f of uploaded) {
|
for (const f of uploaded) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue