From 175f114ff8ca1b35a58801e1f873846acd5fcf0b Mon Sep 17 00:00:00 2001 From: dankeboy36 Date: Sun, 13 Oct 2024 12:42:05 +0200 Subject: [PATCH 1/6] chore: task to update new VSIX version in IDE It could come handy when you develop this extension and want to try out and debug into the code while running in Arduino IDE. Signed-off-by: dankeboy36 --- .vscode/tasks.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index bf9a1bc..24c4165 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -33,6 +33,12 @@ "npm: watch-tests" ], "problemMatcher": [] + }, + { + // This task expects the arduino-ide repository to be checked out as a sibling folder of this repository. + "label": "Update VSIX in Arduino IDE", + "type": "shell", + "command": "rm -rf ../arduino-ide/electron-app/plugins/teensysecurity && mkdir -p ../arduino-ide/electron-app/plugins/teensysecurity && vsce package && unzip ./teensysecurity-0.0.1.vsix -d ../arduino-ide/electron-app/plugins/teensysecurity", } ] } From 95438a35bdf52e86ba5255ebf7a6f6090490c799 Mon Sep 17 00:00:00 2001 From: dankeboy36 Date: Mon, 14 Oct 2024 14:22:05 +0200 Subject: [PATCH 2/6] feat: dynamic setup page Signed-off-by: dankeboy36 --- package.json | 13 +++++++- src/extension.ts | 84 ++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 87 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 74cbeea..bc843d3 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,18 @@ "viewsWelcome": [ { "view": "teensysecurity.setupView", - "contents": "1. Encryption Setup\n\nCreate a new encryption key. The key will be written at the path below. Keep this file secret. Anyone who obtains key.pem could decrypt your code. Make backups, as no way exists to recover this file.\n[Generate Key](command:teensysecurity.createKey)\n\n[Click](command:teensysecurity.showKeyPath) to show the location of the key.pem file.\n\nNormal code is stored in a \".HEX\" file an encrypted code is stored in an \".EHEX\" file. Both are created with every compile when key.pem exists at this path.\n\n2. Teensy Hardware Setup\n\nWrite your encryption key to Teensy's permanent fuse memory. After the key written, Teensy can run both normal and encrypted programs.\n[Fuse Write Sketch](command:teensysecurity.fuseWriteSketch)\n\nVerify an encrypted program runs properly.\n[Verify Sketch](command:teensysecurity.verifySketch)\n\nPermanently lock secure mode. Once locked, Teensy will only be able to run programs encrypted by your key, and JTAG access is disabled. This step is required for full security.\n[Lock Security Sketch](command:teensysecurity.lockSecuritySketch)" + "contents": "1. Encryption Setup\n\nCreate a new encryption key. The key will be written at the path below. Keep this file secret. Anyone who obtains key.pem could decrypt your code. Make backups, as no way exists to recover this file.\n[Generate Key](command:teensysecurity.createKey)", + "enablement": "teensysecurity.state == 'installed'" + }, + { + "view": "teensysecurity.setupView", + "contents": "[Click](command:teensysecurity.showKeyPath) to show the location of the key.pem file.\n\nNormal code is stored in a \".HEX\" file an encrypted code is stored in an \".EHEX\" file. Both are created with every compile when key.pem exists at this path.", + "enablement": "true" + }, + { + "view": "teensysecurity.setupView", + "contents": "2. Teensy Hardware Setup\n\nWrite your encryption key to Teensy's permanent fuse memory. After the key written, Teensy can run both normal and encrypted programs.\n[Fuse Write Sketch](command:teensysecurity.fuseWriteSketch)\n\nVerify an encrypted program runs properly.\n[Verify Sketch](command:teensysecurity.verifySketch)\n\nPermanently lock secure mode. Once locked, Teensy will only be able to run programs encrypted by your key, and JTAG access is disabled. This step is required for full security.\n[Lock Security Sketch](command:teensysecurity.lockSecuritySketch)", + "enablement": "teensysecurity.state == 'installed' && teensysecurity.hasKeyFile" } ] }, diff --git a/src/extension.ts b/src/extension.ts index c3de56c..0186b75 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -23,16 +23,81 @@ // https://www.svgrepo.com/svg/34405/key -import * as vscode from 'vscode'; -import type { Terminal, ExtensionTerminalOptions } from 'vscode'; -import type { ArduinoContext } from 'vscode-arduino-api'; -import * as path from 'path'; +import * as cp from 'node:child_process'; import * as fs from 'node:fs'; -import * as child_process from 'child_process'; -import { tmpdir, platform } from 'node:os'; +import { platform, tmpdir } from 'node:os'; +import * as path from 'node:path'; +import type { ExtensionTerminalOptions, Terminal } from 'vscode'; +import * as vscode from 'vscode'; +import type { ArduinoContext, BoardDetails } from 'vscode-arduino-api'; import { activateSetupView } from './setupView'; +// https://code.visualstudio.com/api/references/when-clause-contexts +function setWhenContext(contextKey: string, contextValue: unknown) { + return vscode.commands.executeCommand('setContext', `teensysecurity.${contextKey}`, contextValue); +} + +// The VS Code API for Arduino IDE supports the FQBN (Fully Qualified Board Name) of the currently selected board in two different ways: +// - fqbn (string|undefined) -> This is when the user selects a board from the dialog (it does not mean the platform is installed). +// - boardDetails?.fqbn (string|undefined) -> This is when a board has been selected by the user and the IDE runs the `board details` command. When the platform is not installed, this value will be undefined. +// Currently, it is not possible to retrieve any information from the CLI via the extension APIs, so extensions cannot check whether a particular platform is installed. +// Extensions can work around this by listening to both the FQBN (when the user selects a board) and board detail (when the IDE resolves the selected board via the CLI) change events. +// If the FQBN changes and is "teensy.avr," the extension knows that the currently selected board is a Teensy. +// If the board details are undefined, the extension can deduce that the platform is not yet installed. +// This trick works only when the Teensy platform is installed via the "Boards Manager" and the platform name arch is `teensy.avr`. If the FQBN starts with a different vendor-arch pair, the string matching will not work. +// Such when context values should be provided by vscode-arduino-api as a feature and IDEs should implement it: https://github.com/dankeboy36/vscode-arduino-api/issues/17. + +let hasKeyFile = false; +let availabilityState: + 'selected' // when selected by user + | 'installed' // when selected by user + board details is available + | undefined // rest (loading, other board is selected, etc.) + = undefined; +let selectedBoardFqbn: string | undefined; + +function activateWhenContext(arduinoContext: ArduinoContext): vscode.Disposable[] { + updateTeensySelectedWhenContext(arduinoContext.fqbn); + updateTeensyInstalledWhenContext(arduinoContext.boardDetails); + getKeyPath(arduinoContext); // will trigger when context update + return [ + arduinoContext.onDidChange('fqbn')(updateTeensySelectedWhenContext), + arduinoContext.onDidChange('boardDetails')(updateTeensyInstalledWhenContext) + ]; +} + +function updateTeensySelectedWhenContext(fqbn: string | undefined) { + selectedBoardFqbn = fqbn; + const isTeensy = selectedBoardFqbn?.startsWith('teensy.avr'); + if (availabilityState === 'installed' && isTeensy) { + return; + } + availabilityState = isTeensy ? 'selected' : undefined; + return setWhenContext('state', availabilityState); +} + +function updateTeensyInstalledWhenContext(details: BoardDetails | undefined) { + // board details change events always come after an FQBN change + if (availabilityState === 'selected' && details?.fqbn?.startsWith('teensy.avr')) { + availabilityState = 'installed'; + return setWhenContext('state', availabilityState); + } +} + +function getKeyPath(arduinoContext: ArduinoContext): string | undefined { + const program = programpath(arduinoContext); + if (!program) { return; } + const keyPath = keyfilename(program); + updateKeyFilePathContext(keyPath); // whenever the keypath is queried, update the when context + return keyPath; +} + +function updateKeyFilePathContext(keyPath: string | undefined) { + // Although it is recommended not to store the .pem file location, this logic does not care the value, only it's existence. + hasKeyFile = !!keyPath; + return setWhenContext('hasKeyFile', hasKeyFile); +} + export function activate(context: vscode.ExtensionContext) { const acontext: ArduinoContext = vscode.extensions.getExtension( @@ -46,6 +111,7 @@ export function activate(context: vscode.ExtensionContext) { activateSetupView(context); context.subscriptions.push( + ...activateWhenContext(acontext), vscode.commands.registerCommand('teensysecurity.createKey', () => { var program = programpath(acontext); if (!program) {return;} @@ -125,7 +191,7 @@ function createTempFolder(sketchname: string) : string { function makeCode(program: string, keyfile: string, operation: string, pathname: string) : boolean { // https://stackoverflow.com/questions/14332721 - var child = child_process.spawnSync(program, [operation, keyfile]); + var child = cp.spawnSync(program, [operation, keyfile]); if (child.error) {return false;} if (child.status != 0) {return false;} if (child.stdout.length <= 0) {return false;} @@ -169,7 +235,7 @@ async function createKey(program: string, keyfile: string) { term.show(); // start teensy_secure running with keygen - var child = child_process.spawn(program, ['keygen', keyfile]); + var child = cp.spawn(program, ['keygen', keyfile]); // as stdout and stderr arrive, send to the terminal child.stdout.on('data', function(data:string) { @@ -205,7 +271,7 @@ function programpath(acontext: ArduinoContext) : string | undefined { // key.pem files, which file teensy_secure uses may change. function keyfilename(program: string) : string | undefined { // https://stackoverflow.com/questions/14332721 - var child = child_process.spawnSync(program, ['keyfile']); + var child = cp.spawnSync(program, ['keyfile']); if (child.error) {return undefined;} if (child.status != 0) { vscode.window.showErrorMessage("Found old version of teensy_secure utility. Please use Boards Manager to install Teensy 1.60.0 or later."); From af5c2b3907c21daf19fb152dcce335e46f926270 Mon Sep 17 00:00:00 2001 From: dankeboy36 Date: Mon, 14 Oct 2024 14:22:51 +0200 Subject: [PATCH 3/6] chore: check in VSIX for Theia bug report Signed-off-by: dankeboy36 --- .gitignore | 2 +- teensysecurity-0.0.1.vsix | Bin 0 -> 10905 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 teensysecurity-0.0.1.vsix diff --git a/.gitignore b/.gitignore index 6e145d9..65237b5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ out dist .vscode-test/ -*.vsix + *.swp node_modules diff --git a/teensysecurity-0.0.1.vsix b/teensysecurity-0.0.1.vsix new file mode 100644 index 0000000000000000000000000000000000000000..e85906b086e8da048fed5baab1d0373b566a0589 GIT binary patch literal 10905 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 z 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 OUjA?TBpAi-PyYiq1OUna literal 0 HcmV?d00001 From efef022dbe8e389de879b2e25695f58b03c788df Mon Sep 17 00:00:00 2001 From: dankeboy36 Date: Sat, 26 Oct 2024 15:48:36 +0200 Subject: [PATCH 4/6] fix: when context handling Signed-off-by: dankeboy36 --- package.json | 6 +- src/extension.ts | 182 +++++++++++++++++++++++++---------------------- 2 files changed, 99 insertions(+), 89 deletions(-) diff --git a/package.json b/package.json index bc843d3..5e37b8c 100644 --- a/package.json +++ b/package.json @@ -66,17 +66,17 @@ { "view": "teensysecurity.setupView", "contents": "1. Encryption Setup\n\nCreate a new encryption key. The key will be written at the path below. Keep this file secret. Anyone who obtains key.pem could decrypt your code. Make backups, as no way exists to recover this file.\n[Generate Key](command:teensysecurity.createKey)", - "enablement": "teensysecurity.state == 'installed'" + "enablement": "teensysecurity.state == installed" }, { "view": "teensysecurity.setupView", "contents": "[Click](command:teensysecurity.showKeyPath) to show the location of the key.pem file.\n\nNormal code is stored in a \".HEX\" file an encrypted code is stored in an \".EHEX\" file. Both are created with every compile when key.pem exists at this path.", - "enablement": "true" + "enablement": "teensysecurity.state == installed && teensysecurity.hasKeyFile" }, { "view": "teensysecurity.setupView", "contents": "2. Teensy Hardware Setup\n\nWrite your encryption key to Teensy's permanent fuse memory. After the key written, Teensy can run both normal and encrypted programs.\n[Fuse Write Sketch](command:teensysecurity.fuseWriteSketch)\n\nVerify an encrypted program runs properly.\n[Verify Sketch](command:teensysecurity.verifySketch)\n\nPermanently lock secure mode. Once locked, Teensy will only be able to run programs encrypted by your key, and JTAG access is disabled. This step is required for full security.\n[Lock Security Sketch](command:teensysecurity.lockSecuritySketch)", - "enablement": "teensysecurity.state == 'installed' && teensysecurity.hasKeyFile" + "enablement": "teensysecurity.state == installed && teensysecurity.hasKeyFile" } ] }, diff --git a/src/extension.ts b/src/extension.ts index 0186b75..cbd0d6c 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -43,12 +43,11 @@ function setWhenContext(contextKey: string, contextValue: unknown) { // - boardDetails?.fqbn (string|undefined) -> This is when a board has been selected by the user and the IDE runs the `board details` command. When the platform is not installed, this value will be undefined. // Currently, it is not possible to retrieve any information from the CLI via the extension APIs, so extensions cannot check whether a particular platform is installed. // Extensions can work around this by listening to both the FQBN (when the user selects a board) and board detail (when the IDE resolves the selected board via the CLI) change events. -// If the FQBN changes and is "teensy.avr," the extension knows that the currently selected board is a Teensy. +// If the FQBN changes and is "teensy:avr," the extension knows that the currently selected board is a Teensy. // If the board details are undefined, the extension can deduce that the platform is not yet installed. -// This trick works only when the Teensy platform is installed via the "Boards Manager" and the platform name arch is `teensy.avr`. If the FQBN starts with a different vendor-arch pair, the string matching will not work. +// This trick works only when the Teensy platform is installed via the "Boards Manager" and the platform name arch is `teensy:avr`. If the FQBN starts with a different vendor-arch pair, the string matching will not work. // Such when context values should be provided by vscode-arduino-api as a feature and IDEs should implement it: https://github.com/dankeboy36/vscode-arduino-api/issues/17. -let hasKeyFile = false; let availabilityState: 'selected' // when selected by user | 'installed' // when selected by user + board details is available @@ -59,16 +58,17 @@ let selectedBoardFqbn: string | undefined; function activateWhenContext(arduinoContext: ArduinoContext): vscode.Disposable[] { updateTeensySelectedWhenContext(arduinoContext.fqbn); updateTeensyInstalledWhenContext(arduinoContext.boardDetails); - getKeyPath(arduinoContext); // will trigger when context update + updateHasKeyFileWhenContext(arduinoContext.boardDetails); return [ arduinoContext.onDidChange('fqbn')(updateTeensySelectedWhenContext), - arduinoContext.onDidChange('boardDetails')(updateTeensyInstalledWhenContext) + arduinoContext.onDidChange('boardDetails')(updateTeensyInstalledWhenContext), + arduinoContext.onDidChange('boardDetails')(updateHasKeyFileWhenContext), ]; } function updateTeensySelectedWhenContext(fqbn: string | undefined) { selectedBoardFqbn = fqbn; - const isTeensy = selectedBoardFqbn?.startsWith('teensy.avr'); + const isTeensy = selectedBoardFqbn?.startsWith('teensy:avr'); if (availabilityState === 'installed' && isTeensy) { return; } @@ -78,32 +78,31 @@ function updateTeensySelectedWhenContext(fqbn: string | undefined) { function updateTeensyInstalledWhenContext(details: BoardDetails | undefined) { // board details change events always come after an FQBN change - if (availabilityState === 'selected' && details?.fqbn?.startsWith('teensy.avr')) { + if (availabilityState === 'selected' && details?.fqbn?.startsWith('teensy:avr')) { availabilityState = 'installed'; return setWhenContext('state', availabilityState); } } -function getKeyPath(arduinoContext: ArduinoContext): string | undefined { - const program = programpath(arduinoContext); - if (!program) { return; } - const keyPath = keyfilename(program); - updateKeyFilePathContext(keyPath); // whenever the keypath is queried, update the when context - return keyPath; -} - -function updateKeyFilePathContext(keyPath: string | undefined) { - // Although it is recommended not to store the .pem file location, this logic does not care the value, only it's existence. - hasKeyFile = !!keyPath; - return setWhenContext('hasKeyFile', hasKeyFile); +function updateHasKeyFileWhenContext(boardDetails: BoardDetails | undefined) { + if (boardDetails?.fqbn.startsWith('teensy:avr')) { + const program = programPath(boardDetails, false); + if (program) { + const keyPath = keyFilename(program); + if (keyPath) { + setWhenContext('hasKeyFile', fs.existsSync(keyPath)); + return; + } + } + } + setWhenContext('hasKeyFile', undefined); } export function activate(context: vscode.ExtensionContext) { - - const acontext: ArduinoContext = vscode.extensions.getExtension( + const arduinoContext: ArduinoContext = vscode.extensions.getExtension( 'dankeboy36.vscode-arduino-api' )?.exports; - if (!acontext) { + if (!arduinoContext) { console.log('teensysecurity Failed to load the Arduino API'); return; } @@ -111,31 +110,40 @@ export function activate(context: vscode.ExtensionContext) { activateSetupView(context); context.subscriptions.push( - ...activateWhenContext(acontext), + ...activateWhenContext(arduinoContext), vscode.commands.registerCommand('teensysecurity.createKey', () => { - var program = programpath(acontext); - if (!program) {return;} - var keyfile = keyfilename(program); - if (!keyfile) {return;} + var program = programPath(arduinoContext.boardDetails); + if (!program) { return; } + var keyfile = keyFilename(program); + if (!keyfile) { return; } createKey(program, keyfile); + setWhenContext('hasKeyFile', true); // trigger a when context update after the command execution }) ); context.subscriptions.push( - vscode.commands.registerCommand('teensysecurity.showKeyPath', () => { - var program = programpath(acontext); - if (!program) {return;} - var keyfile = keyfilename(program); - if (!keyfile) {return;} - vscode.window.showInformationMessage('key.pem location: ' + keyfile); + vscode.commands.registerCommand('teensysecurity.showKeyPath', async () => { + var program = programPath(arduinoContext.boardDetails); + if (!program) { return; } + var keyfile = keyFilename(program); + if (!keyfile) { return; } + const openKeyFileAction = 'Open Key File'; + const actions = []; + if (fs.existsSync(keyfile)) { + actions.push(openKeyFileAction); + } + const action = await vscode.window.showInformationMessage('key.pem location: ' + keyfile, ...actions); + if (action === openKeyFileAction) { + vscode.commands.executeCommand('vscode.open', vscode.Uri.file(keyfile)); + } }) ); context.subscriptions.push( vscode.commands.registerCommand('teensysecurity.fuseWriteSketch', async () => { - var program = programpath(acontext); - if (!program) {return;} - var keyfile = keyfilename(program); - if (!keyfile) {return;} - if (!keyfileexists(keyfile)) {return;} + var program = programPath(arduinoContext.boardDetails); + if (!program) { return; } + var keyfile = keyFilename(program); + if (!keyfile) { return; } + if (!keyfileexists(keyfile)) { return; } console.log('teensysecurity.fuseWriteSketch (Fuse Write Sketch) callback'); var mydir = createTempFolder("FuseWrite"); makeCode(program, keyfile, "fuseino", path.join(mydir, "FuseWrite.ino")); @@ -146,11 +154,11 @@ export function activate(context: vscode.ExtensionContext) { context.subscriptions.push( vscode.commands.registerCommand('teensysecurity.verifySketch', async () => { console.log('teensysecurity.verifySketch (Verify Sketch) callback'); - var program = programpath(acontext); - if (!program) {return;} - var keyfile = keyfilename(program); - if (!keyfile) {return;} - if (!keyfileexists(keyfile)) {return;} + var program = programPath(arduinoContext.boardDetails); + if (!program) { return; } + var keyfile = keyFilename(program); + if (!keyfile) { return; } + if (!keyfileexists(keyfile)) { return; } var mydir = createTempFolder("VerifySecure"); makeCode(program, keyfile, "verifyino", path.join(mydir, "VerifySecure.ino")); openSketch(mydir); @@ -159,11 +167,11 @@ export function activate(context: vscode.ExtensionContext) { context.subscriptions.push( vscode.commands.registerCommand('teensysecurity.lockSecuritySketch', async () => { console.log('teensysecurity.lockSecuritySketch (Lock Security Sketch) callback'); - var program = programpath(acontext); - if (!program) {return;} - var keyfile = keyfilename(program); - if (!keyfile) {return;} - if (!keyfileexists(keyfile)) {return;} + var program = programPath(arduinoContext.boardDetails); + if (!program) { return; } + var keyfile = keyFilename(program); + if (!keyfile) { return; } + if (!keyfileexists(keyfile)) { return; } var mydir = createTempFolder("LockSecureMode"); makeCode(program, keyfile, "lockino", path.join(mydir, "LockSecureMode.ino")); openSketch(mydir); @@ -181,20 +189,20 @@ export function activate(context: vscode.ExtensionContext) { console.log('extension "teensysecurity" is now active!'); } -function createTempFolder(sketchname: string) : string { +function createTempFolder(sketchname: string): string { var mytmpdir = fs.mkdtempSync(path.join(tmpdir(), 'teensysecure-')); - var mydir:string = path.join(mytmpdir, sketchname); + var mydir: string = path.join(mytmpdir, sketchname); console.log("temporary sketch directory: " + mydir); fs.mkdirSync(mydir); return mydir; } -function makeCode(program: string, keyfile: string, operation: string, pathname: string) : boolean { +function makeCode(program: string, keyfile: string, operation: string, pathname: string): boolean { // https://stackoverflow.com/questions/14332721 var child = cp.spawnSync(program, [operation, keyfile]); - if (child.error) {return false;} - if (child.status != 0) {return false;} - if (child.stdout.length <= 0) {return false;} + if (child.error) { return false; } + if (child.status !== 0) { return false; } + if (child.stdout.length <= 0) { return false; } fs.writeFileSync(pathname, child.stdout); return true; } @@ -203,7 +211,7 @@ async function openSketch(sketchpath: string) { // Thanks to dankeboy36 // https://github.com/dankeboy36/vscode-arduino-api/discussions/16 const uri = vscode.Uri.file(sketchpath); - vscode.commands.executeCommand('vscode.openFolder', uri , { forceNewWindow: true }); + vscode.commands.executeCommand('vscode.openFolder', uri, { forceNewWindow: true }); } async function createKey(program: string, keyfile: string) { @@ -211,8 +219,8 @@ async function createKey(program: string, keyfile: string) { // https://code.visualstudio.com/api/references/vscode-api#EventEmitter<T> var wevent = new vscode.EventEmitter(); var isopen = false; - var buffer:string = ''; - function tprint(s: string) : void { + var buffer: string = ''; + function tprint(s: string): void { var s2 = String(s).replace(/\n/g, "\r\n"); if (isopen) { wevent.fire(s2); @@ -223,7 +231,7 @@ async function createKey(program: string, keyfile: string) { // open a terminal which will receive the keygen output messages // https://code.visualstudio.com/api/references/vscode-api#ExtensionTerminalOptions - const opt : ExtensionTerminalOptions = { + const opt: ExtensionTerminalOptions = { name: "New Key", pty: { onDidWrite: wevent.event, @@ -231,17 +239,17 @@ async function createKey(program: string, keyfile: string) { close: () => { isopen = false; buffer = ''; }, } }; - const term : Terminal = (vscode.window).createTerminal(opt); + const term: Terminal = (vscode.window).createTerminal(opt); term.show(); // start teensy_secure running with keygen var child = cp.spawn(program, ['keygen', keyfile]); // as stdout and stderr arrive, send to the terminal - child.stdout.on('data', function(data:string) { + child.stdout.on('data', function (data: string) { tprint(data); }); - child.stderr.on('data', function(data:string) { + child.stderr.on('data', function (data: string) { tprint(data); // TODO: red text like esp-exception decoder }); @@ -253,14 +261,16 @@ async function createKey(program: string, keyfile: string) { // calling functions should NOT store this, only use if for immediate needs // if Boards Manager is used to upgrade, downgrade or uninstall Teensy, // this pathname can be expected to change or even become undefined -function programpath(acontext: ArduinoContext) : string | undefined { - var tool = findTool(acontext, "runtime.tools.teensy-tools"); +function programPath(boardDetails: BoardDetails | undefined, showsErrorMessage = true): string | undefined { + var tool = findTool(boardDetails, "runtime.tools.teensy-tools"); if (!tool) { - vscode.window.showErrorMessage("Could not find teensy_secure utility. Please use Boards Manager to install Teensy."); + if (showsErrorMessage) { + vscode.window.showErrorMessage("Could not find teensy_secure utility. Please use Boards Manager to install Teensy."); + } return undefined; } var filename = 'teensy_secure'; - if (platform() === 'win32') {filename += '.exe';} + if (platform() === 'win32') { filename += '.exe'; } return path.join(tool, filename); } @@ -269,43 +279,43 @@ function programpath(acontext: ArduinoContext) : string | undefined { // teensy_secure can look for key.pem in multiple locations and choose which // to use based on its internal rules. If the user moves or deletes their // key.pem files, which file teensy_secure uses may change. -function keyfilename(program: string) : string | undefined { +function keyFilename(program: string): string | undefined { // https://stackoverflow.com/questions/14332721 var child = cp.spawnSync(program, ['keyfile']); - if (child.error) {return undefined;} - if (child.status != 0) { + if (child.error) { return undefined; } + if (child.status !== 0) { vscode.window.showErrorMessage("Found old version of teensy_secure utility. Please use Boards Manager to install Teensy 1.60.0 or later."); return undefined; } - if (child.stdout.length <= 0) {return undefined;} - var out:string = child.stdout.toString(); - var out2:string = out.replace(/\s+$/gm,''); // remove trailing newline + if (child.stdout.length <= 0) { return undefined; } + var out: string = child.stdout.toString(); + var out2: string = out.replace(/\s+$/gm, ''); // remove trailing newline return out2; } -function keyfileexists(keyfile: string) : boolean { - if (fs.existsSync(keyfile)) {return true;} +function keyfileexists(keyfile: string): boolean { + if (fs.existsSync(keyfile)) { return true; } vscode.window.showErrorMessage('This command requires a key.pem file (' + keyfile + '). Please use "Teensy Security: Generate Key" to create your key.pem file.'); return false; } // from arduino-littlefs-upload -function findTool(ctx: ArduinoContext, match : string) : string | undefined { - var found = false; - var ret = undefined; - if (ctx.boardDetails !== undefined) { - Object.keys(ctx.boardDetails.buildProperties).forEach( (elem) => { - if (elem.startsWith(match) && !found && (ctx.boardDetails?.buildProperties[elem] !== undefined)) { - ret = ctx.boardDetails.buildProperties[elem]; - found = true; - } - }); - } - return ret; +function findTool(boardDetails: BoardDetails | undefined, match: string): string | undefined { + var found = false; + var ret = undefined; + if (boardDetails !== undefined) { + Object.keys(boardDetails.buildProperties).forEach((elem) => { + if (elem.startsWith(match) && !found && (boardDetails?.buildProperties[elem] !== undefined)) { + ret = boardDetails.buildProperties[elem]; + found = true; + } + }); + } + return ret; } // This method is called when your extension is deactivated // TODO: should keep a list of all files create and delete them here -export function deactivate() {} +export function deactivate() { } From fb562ccf36d917f419189c6bfb1e65ff64ce456b Mon Sep 17 00:00:00 2001 From: dankeboy36 Date: Sat, 26 Oct 2024 15:50:30 +0200 Subject: [PATCH 5/6] fix(dev): remove VSIX from git Signed-off-by: dankeboy36 --- teensysecurity-0.0.1.vsix | Bin 10905 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 teensysecurity-0.0.1.vsix diff --git a/teensysecurity-0.0.1.vsix b/teensysecurity-0.0.1.vsix deleted file mode 100644 index e85906b086e8da048fed5baab1d0373b566a0589..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10905 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 z 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 OUjA?TBpAi-PyYiq1OUna From f49927aa38ca84b2cc07d37841e8863e3b53387a Mon Sep 17 00:00:00 2001 From: dankeboy36 Date: Sat, 26 Oct 2024 15:51:10 +0200 Subject: [PATCH 6/6] chore: ignore VSIX Signed-off-by: dankeboy36 --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 65237b5..6e145d9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ out dist .vscode-test/ - +*.vsix *.swp node_modules