import * as vscode from 'vscode' ; import * as fs from 'fs-extra' ; import * as os from 'os' ; import * as child_process from 'child_process' ; import * as YAML from 'yaml' ; import * as INI from 'ini' ; import * as path from 'path' ; import * as crypto from 'crypto' ; import * as Debug from 'debug' ; import { TextEncoder , TextDecoder } from 'text-encoding' ; const DEBUG_NAMESPACE = '@signageos/vscode-sops' ; function enableDebug ( ) { const outputChannel = vscode . window . createOutputChannel ( DEBUG_NAMESPACE ) ; ( Debug as any ) . log = ( ... args ) => outputChannel . appendLine ( args . join ( ', ' ) ) ; Debug . enable ( DEBUG_NAMESPACE ) ; } ; const debug = Debug ( DEBUG_NAMESPACE ) ; const convertUtf8ToUint8Array = ( input ) => new TextEncoder ( "utf-8" ) . encode ( input ) ; const convertUint8ArrayToUtf8 = ( input ) => new TextDecoder ( "utf-8" ) . decode ( input ) ; const FAKE_DECRYPTED_EDITOR_SHELL = `#!/bin/sh
cat $VSCODE_SOPS_DECRYPTED_FILE_PATH > $1
` ; const FAKE_DECRYPTED_EDITOR_CMD = `
copy %VSCODE_SOPS_DECRYPTED_FILE_PATH% %1
` ; const CONFIG_BASE_SECTION = 'sops' ; enum ConfigName { enabled = 'enabled' , beta = 'beta' , creationEnabled = 'creationEnabled' , binPath = 'binPath' , defaultAwsProfile = 'defaults.awsProfile' , defaultGcpCredentialsPath = 'defaults.gcpCredentialsPath' , configPath = 'configPath' } interface IRunControl { awsProfile ? : string ; gcpCredentialsPath ? : string ; } const DEFAULT_RUN_CONTROL_FILENAME = '.sopsrc' ; const GCP_CREDENTIALS_ENV_VAR_NAME = 'GOOGLE_APPLICATION_CREDENTIALS' ; const AWS_PROFILE_ENV_VAR_NAME = 'AWS_PROFILE' ; enum Command { INFO_COMMAND = 'sops.info' , TOGGLE_ORIGINAL_FILE = 'sops.toggle_original_file' , ENABLE_BETA = 'sops.enable_beta' , DISABLE_BETA = 'sops.disable_beta' } const SOPS_CONFIG_FILENAME = '.sops.yaml' ; const DECRYPTED_PREFIX = '.decrypted~' ; const getSopsBinPath = ( ) => { const sopsPath = vscode . workspace . getConfiguration ( CONFIG_BASE_SECTION ) . get ( ConfigName . binPath ) ; return sopsPath ?? 'sops' ; } ; const getCurrentExtensionName = ( context ) : string => ( context . globalState as any ) . _id ?? 'signageos.signageos-vscode-sops' ; const isCurrentlyBetaInstance = ( context ) => { const extensionName = getCurrentExtensionName ( context ) ; debug ( 'extension name' , extensionName ) ; return extensionName . endsWith ( '-beta' ) ; } ; const isEnabled = ( context ) => { const enabled = vscode . workspace . getConfiguration ( CONFIG_BASE_SECTION ) . get ( ConfigName . enabled ) ?? true ; if ( ! enabled ) { debug ( 'Extension is disabled by configuration' ) ; return false ; } const beta = vscode . workspace . getConfiguration ( CONFIG_BASE_SECTION ) . get ( ConfigName . beta ) ?? false ; if ( beta ) { return isCurrentlyBetaInstance ( context ) ; } else { return ! isCurrentlyBetaInstance ( context ) ; } } ; let spawnOptions = { cwd : process . env . HOME , } ; function getToggleBarText ( toggleToEncDec = 'enc/dec' ) { return `SOPS: toggle ${ toggleToEncDec } file` ; } const toggleStatusBarItem = vscode . window . createStatusBarItem ( vscode . StatusBarAlignment . Left , 100 ) ; toggleStatusBarItem . command = Command . TOGGLE_ORIGINAL_FILE ; toggleStatusBarItem . text = getToggleBarText ( ) ; toggleStatusBarItem . tooltip = 'Toggle between original and decrypted file by SOPS' ; type IFileFormat = 'yaml' | 'json' | 'ini' ; function isIFileFormat ( languageId ) { return [ 'yaml' , 'json' , 'ini' ] . includes ( languageId ) ; } async function handleFile ( document , fileFormat ) { debug ( 'handleFile' , document , fileFormat ) ; if ( ! isDecryptedFile ( document . uri ) ) { const fileContent = await getFileContent ( document . uri ) ; if ( ! fileContent ) { debug ( 'skip empty files' , document . uri ) ; return ; } const parser = getParser ( fileFormat ) ; let fileData = parser ( fileContent ) ; debug ( 'File content' , fileData ) ; if ( fileData instanceof Array ) { fileData = fileData [ 0 ] ; } if ( typeof fileData === 'object' && typeof fileData . sops === 'object' && typeof fileData . sops . version === 'string' ) { const progressOptions = { location : vscode . ProgressLocation . Notification , } ; await vscode . window . withProgress ( progressOptions , async ( progress ) => { progress . report ( { message : `Decrypting "${ document . fileName }" SOPS file` } ) ; await ensureOpenDecryptedFile ( document . uri , document . languageId as IFileFormat ) ; } ) ; } } } async function handleSaveFile ( document , fileFormat ) { debug ( 'handleSaveFile' , document , fileFormat ) ; const decryptedUri = document . uri ; const progressOptions = { location : vscode . ProgressLocation . Notification , } ; const encryptedUri = getEncryptedFileUri ( decryptedUri ) ; if ( encryptedUri ) { debug ( 'Encrypted filename' , encryptedUri . path ) ; debug ( 'Encrypted uri' , encryptedUri ) ; await vscode . window . withProgress ( progressOptions , async ( progress ) => { progress . report ( { message : `Encrypting "${ encryptedUri . path }" SOPS file` } ) ; await overrideEncryptedFile ( decryptedUri , encryptedUri , fileFormat ) ; } ) ; } else if ( vscode . workspace . getConfiguration ( CONFIG_BASE_SECTION ) . get ( ConfigName . creationEnabled ) ) { await vscode . window . withProgress ( progressOptions , async ( progress ) => { progress . report ( { message : `Trying encrypting new "${ decryptedUri . path }" SOPS file` } ) ; await tryCreateEncryptedFile ( decryptedUri , fileFormat ) ; await handleFile ( document , fileFormat ) ; } ) ; } } async function overrideEncryptedFile ( decryptedUri , encryptedUri , fileFormat ) { const originalFileContent = await getDecryptedFileContent ( encryptedUri , fileFormat ) ; const currentFileContent = await getFileContent ( decryptedUri ) ; debug ( 'Comparing files' , { originalFileContent , currentFileContent } ) ; const encryptedContentChecksum = await getChecksum ( originalFileContent ) ; const decryptedContentChecksum = await getChecksum ( currentFileContent ) ; debug ( 'Content checksums' , { encryptedContentChecksum , decryptedContentChecksum } ) ; if ( encryptedContentChecksum !== decryptedContentChecksum ) { debug ( 'Updating encrypted' ) ; await encryptFileToFile ( decryptedUri , encryptedUri , fileFormat ) ; } } async function tryCreateEncryptedFile ( decryptedUri , fileFormat ) { try { const encryptedContent = await getNewEncryptedFileContent ( decryptedUri , fileFormat ) ; const encryptedUri = decryptedUri ; await vscode . workspace . fs . writeFile ( encryptedUri , convertUtf8ToUint8Array ( encryptedContent ) ) ; } catch ( error ) { if ( isNoMatchingRulesError ( error ) ) { debug ( 'No matching creation rules found' , decryptedUri . path ) ; } else { throw error ; } } } function isNoMatchingRulesError ( error ) { return error ?. message ?. includes ( 'no matching creation rules found' ) ; } class ParseError extends Error { constructor ( private originalError ) { super ( originalError . message ) ; this . stack = originalError . stack ; Object . setPrototypeOf ( this , ParseError . prototype ) ; } } type ParsedObject = string | number | boolean | { [ key ] : ParsedObject ; } ; function getParser ( fileFormat ) { return ( content ) => { try { switch ( fileFormat ) { case 'yaml' : return YAML . parseAllDocuments ( content ) . map ( ( doc ) => doc . toJSON ( ) ) ; case 'json' : return JSON . parse ( content ) ; case 'ini' : return INI . parse ( content ) ; } } catch ( error ) { throw new ParseError ( error ) ; } } ; } async function ensureOpenDecryptedFile ( encryptedUri , fileFormat ) { debug ( 'Opening' , encryptedUri . path ) ; const decryptedUri = getDecryptedFileUri ( encryptedUri ) ; if ( ! await fileExists ( decryptedUri ) ) { debug ( 'Not decrypted' , decryptedUri . path ) ; await decryptFileToFile ( encryptedUri , decryptedUri , fileFormat ) ; } const originalFileContent = await getDecryptedFileContent ( encryptedUri , fileFormat ) ; const currentFileContent = await getFileContent ( decryptedUri ) ; debug ( 'Comparing files' , { originalFileContent , currentFileContent } ) ; const encryptedContentChecksum = await getChecksum ( originalFileContent ) ; const decryptedContentChecksum = await getChecksum ( currentFileContent ) ; debug ( 'Content checksums' , { encryptedContentChecksum , decryptedContentChecksum } ) ; if ( encryptedContentChecksum !== decryptedContentChecksum ) { const encryptedStat = await vscode . workspace . fs . stat ( encryptedUri ) ; const decryptedStat = await vscode . workspace . fs . stat ( decryptedUri ) ; debug ( 'Content stats' , { encryptedStat , decryptedStat } ) ; if ( encryptedStat . mtime > decryptedStat . mtime ) { debug ( 'Updating decrypted' ) ; await decryptFileToFile ( encryptedUri , decryptedUri , fileFormat ) ; } else if ( encryptedStat . mtime < decryptedStat . mtime ) { debug ( 'Updating encrypted' ) ; await encryptFileToFile ( decryptedUri , encryptedUri , fileFormat ) ; } } if ( ! isFileOpen ( decryptedUri ) ) { await openFile ( decryptedUri ) ; } } function isFileOpen ( uri ) { return vscode . window . visibleTextEditors . some ( ( editor ) => editor . document . uri . path === uri . path ) ; } async function openFile ( uri ) { return await vscode . window . showTextDocument ( uri ) ; } async function closeAndDeleteFile ( uri ) { if ( await fileExists ( uri ) ) { await vscode . workspace . fs . delete ( uri ) ; } } async function decryptFileToFile ( encryptedUri , decryptedUri , fileFormat ) { const decryptedContent = await getDecryptedFileContent ( encryptedUri , fileFormat ) ; await vscode . workspace . fs . writeFile ( decryptedUri , convertUtf8ToUint8Array ( decryptedContent ) ) ; } async function encryptFileToFile ( decryptedUri , encryptedUri , fileFormat ) { const encryptedContent = await getEncryptedFileContent ( decryptedUri , encryptedUri , fileFormat ) ; debug ( 'Writing encrypted file' , encryptedUri , encryptedContent ) ; await vscode . workspace . fs . writeFile ( encryptedUri , convertUtf8ToUint8Array ( encryptedContent ) ) ; debug ( 'Wrote encrypted file' , encryptedUri , encryptedContent ) ; } async function getChecksum ( content ) { const md5sum = crypto . createHash ( 'md5' ) ; md5sum . update ( content ) ; return md5sum . digest ( 'hex' ) ; } async function getFileContent ( uri ) { const fileContent = await vscode . workspace . fs . readFile ( uri ) ; return convertUint8ArrayToUtf8 ( fileContent ) ; } async function getDecryptedFileContent ( uri , fileFormat ) { const encryptedContent = await getFileContent ( uri ) ; const tmpEncryptedFilePath = path . join ( os . tmpdir ( ) , await getChecksum ( Math . random ( ) . toString ( ) ) ) ; try { await fs . writeFile ( tmpEncryptedFilePath , encryptedContent , { mode : 0o600 } ) ; debug ( 'Decrypting' , uri . path , encryptedContent ) ; const { sopsGeneralArgs , sopsGeneralEnvVars } = await getSopsGeneralOptions ( ) ; const decryptProcess = child_process . spawnSync ( getSopsBinPath ( ) , [ ... sopsGeneralArgs , '--output-type' , fileFormat , '--input-type' , fileFormat , '--decrypt' , tmpEncryptedFilePath , ] , { ... spawnOptions , env : { ... process . env , ... sopsGeneralEnvVars , } , } ) ; if ( decryptProcess . error ) { throw decryptProcess . error ; } if ( decryptProcess . stderr . toString ( ) ) { console . warn ( decryptProcess . stderr . toString ( ) ) ; } const decryptedContent = decryptProcess . stdout . toString ( ) ; debug ( 'Decrypted' , uri . path , decryptedContent ) ; if ( ! decryptedContent ) { throw new Error ( `Could not decrypt file: ${ uri . path }, ${ decryptProcess . stderr . toString ( ) }` ) ; } return decryptedContent ; } finally { await fs . remove ( tmpEncryptedFilePath ) ; } } async function getEncryptedFileContent ( uri , originalEncryptedUri , fileFormat ) { const decryptedContent = await getFileContent ( uri ) ; const originalEncryptedContent = await getFileContent ( originalEncryptedUri ) ; const tmpDecryptedFilePath = path . join ( os . tmpdir ( ) , await getChecksum ( Math . random ( ) . toString ( ) ) ) ; const tmpEncryptedFilePath = path . join ( os . tmpdir ( ) , await getChecksum ( Math . random ( ) . toString ( ) ) ) ; let tmpFakeDecryptedEditorPath = path . join ( os . tmpdir ( ) , await getChecksum ( Math . random ( ) . toString ( ) ) ) ; let fakeDecryptedEditor = FAKE_DECRYPTED_EDITOR_SHELL ; if ( process . platform === 'win32' ) { tmpFakeDecryptedEditorPath = tmpFakeDecryptedEditorPath + '.cmd' ; fakeDecryptedEditor = FAKE_DECRYPTED_EDITOR_CMD ; } try { await fs . writeFile ( tmpFakeDecryptedEditorPath , fakeDecryptedEditor , { mode : 0o755 } ) ; await fs . writeFile ( tmpDecryptedFilePath , decryptedContent , { mode : 0o600 } ) ; await fs . writeFile ( tmpEncryptedFilePath , originalEncryptedContent , { mode : 0o600 } ) ; debug ( 'Encrypting' , uri . path , decryptedContent ) ; const { sopsGeneralArgs , sopsGeneralEnvVars } = await getSopsGeneralOptions ( ) ; const sopsBin = getSopsBinPath ( ) ; const cmds = [ ... sopsGeneralArgs , '--output-type' , fileFormat , '--input-type' , fileFormat , tmpEncryptedFilePath , ] ; const envs = { ... sopsGeneralEnvVars , EDITOR : normalizeCrossPlatformPath ( tmpFakeDecryptedEditorPath ) , VSCODE_SOPS_DECRYPTED_FILE_PATH : tmpDecryptedFilePath , } ; debug ( 'SOPS command' , sopsBin , cmds . join ( ' ' ) , JSON . stringify ( envs , undefined , 2 ) ) ; const encryptProcess = child_process . spawnSync ( sopsBin , cmds , { ... spawnOptions , env : { ... process . env , ... envs } , } ) ; if ( encryptProcess . error ) { throw encryptProcess . error ; } if ( encryptProcess . stderr . toString ( ) ) { console . warn ( encryptProcess . stderr . toString ( ) ) ; } const encryptedContent = ( await fs . readFile ( tmpEncryptedFilePath ) ) . toString ( ) ; debug ( 'Encrypted' , uri . path , encryptedContent ) ; if ( ! encryptedContent ) { throw new Error ( `Could not encrypt file: ${ uri . path }, ${ encryptProcess . stderr . toString ( ) }` ) ; } return encryptedContent ; } finally { await fs . remove ( tmpDecryptedFilePath ) ; await fs . remove ( tmpEncryptedFilePath ) ; await fs . remove ( tmpFakeDecryptedEditorPath ) ; } } async function getNewEncryptedFileContent ( decryptedUri , fileFormat ) { const tmpDirectoryPath = path . join ( os . tmpdir ( ) , await getChecksum ( Math . random ( ) . toString ( ) ) ) ; await fs . ensureDir ( tmpDirectoryPath ) ; try { const decryptedContent = await getFileContent ( decryptedUri ) ; const sopsConfigUri = await findSopsConfigRecursive ( decryptedUri . with ( { path : path . dirname ( decryptedUri . path ) } ) ) ; let sopsConfigArgs = [ ] ; let decryptedRelativePathToSopsConfig ; if ( sopsConfigUri ) { decryptedRelativePathToSopsConfig = path . relative ( path . dirname ( sopsConfigUri . path ) , decryptedUri . path ) ; const sopsConfigContent = await getFileContent ( sopsConfigUri ) ; debug ( 'SOP config content' , sopsConfigContent ) ; const tmpSopsConfigPath = path . join ( tmpDirectoryPath , path . basename ( sopsConfigUri . path ) ) ; await fs . writeFile ( tmpSopsConfigPath , sopsConfigContent , { mode : 0o600 } ) ; sopsConfigArgs = [ '--config' , tmpSopsConfigPath ] ; await fs . ensureDir ( path . join ( tmpDirectoryPath , path . dirname ( decryptedRelativePathToSopsConfig ) ) ) ; } else { decryptedRelativePathToSopsConfig = path . basename ( decryptedUri . path ) ; } debug ( 'Decrypted relative path to config' , decryptedRelativePathToSopsConfig ) ; const tmpDecryptedFilePath = path . join ( tmpDirectoryPath , decryptedRelativePathToSopsConfig ) ; await fs . writeFile ( tmpDecryptedFilePath , decryptedContent , { mode : 0o600 } ) ; debug ( 'Encrypting' , decryptedUri . path , decryptedContent , tmpDecryptedFilePath ) ; const { sopsGeneralArgs , sopsGeneralEnvVars } = await getSopsGeneralOptions ( ) ; const encryptProcess = child_process . spawnSync ( getSopsBinPath ( ) , [ ... sopsGeneralArgs , ... sopsConfigArgs , '--output-type' , fileFormat , '--input-type' , fileFormat , '--encrypt' , tmpDecryptedFilePath , ] , { ... spawnOptions , env : { ... process . env , ... sopsGeneralEnvVars , } , } ) ; if ( encryptProcess . error ) { throw encryptProcess . error ; } if ( encryptProcess . stderr . toString ( ) ) { console . warn ( encryptProcess . stderr . toString ( ) ) ; } const encryptedContent = encryptProcess . stdout . toString ( ) ; debug ( 'Encrypted' , decryptedUri . path , encryptedContent ) ; if ( ! encryptedContent ) { throw new Error ( `Could not encrypt new file: ${ decryptedUri . path }, ${ encryptProcess . stderr . toString ( ) }` ) ; } return encryptedContent ; } finally { await fs . remove ( tmpDirectoryPath ) ; } } async function findSopsConfigRecursive ( dirUri ) { const possibleSopsConfigUri = dirUri . with ( { path : path . join ( dirUri . path , SOPS_CONFIG_FILENAME ) } ) ; if ( await fileExists ( possibleSopsConfigUri ) ) { debug ( 'SOPS config' , possibleSopsConfigUri . path ) ; return possibleSopsConfigUri ; } else if ( path . dirname ( dirUri . path ) !== '.' ) { return await findSopsConfigRecursive ( dirUri . with ( { path : path . dirname ( dirUri . path ) } ) ) ; } else { return undefined ; } } async function fileExists ( uri ) { try { await vscode . workspace . fs . stat ( uri ) ; return true ; } catch ( error ) { return false ; } } async function getSopsGeneralOptions ( ) { const defaultAwsProfile = vscode . workspace . getConfiguration ( CONFIG_BASE_SECTION ) . get ( ConfigName . defaultAwsProfile ) ; const defaultGcpCredentialsPath = vscode . workspace . getConfiguration ( CONFIG_BASE_SECTION ) . get ( ConfigName . defaultGcpCredentialsPath ) ; debug ( 'config' , { defaultAwsProfile , defaultGcpCredentialsPath } ) ; const rc = await getRunControl ( ) ; const awsProfile = rc . awsProfile ?? defaultAwsProfile ; let gcpCredentialsPath = rc . gcpCredentialsPath ?? defaultGcpCredentialsPath ; const sopsGeneralArgs = [ ] ; const sopsGeneralEnvVars = { } ; if ( awsProfile ) { sopsGeneralArgs . push ( '--aws-profile' , awsProfile ) ; sopsGeneralEnvVars [ AWS_PROFILE_ENV_VAR_NAME ] = awsProfile ; } if ( gcpCredentialsPath ) { if ( ! path . isAbsolute ( gcpCredentialsPath ) && vscode . workspace . workspaceFolders ) { for ( const workspaceFolder of vscode . workspace . workspaceFolders ) { const gcpCredentialsAbsPath = path . join ( workspaceFolder . uri . path , gcpCredentialsPath ) ; const gcpCredentialsUri = workspaceFolder . uri . with ( { path : gcpCredentialsAbsPath } ) ; if ( await fileExists ( gcpCredentialsUri ) ) { gcpCredentialsPath = gcpCredentialsAbsPath ; break ; } } } sopsGeneralEnvVars [ GCP_CREDENTIALS_ENV_VAR_NAME ] = gcpCredentialsPath ; } debug ( 'sops options' , { sopsGeneralArgs , sopsGeneralEnvVars } ) ; return { sopsGeneralArgs , sopsGeneralEnvVars , } ; } async function getRunControl ( ) { const possibleRCUris = [ ] ; let rcPath = vscode . workspace . getConfiguration ( CONFIG_BASE_SECTION ) . get ( ConfigName . configPath ) ; if ( vscode . workspace . workspaceFolders ) { if ( typeof rcPath === 'string' ) { for ( const rootPath of vscode . workspace . workspaceFolders ) { if ( rcPath . charAt ( 0 ) === '/' ) { possibleRCUris . push ( rootPath . uri . with ( { path : rcPath } ) ) ; } else { possibleRCUris . push ( rootPath . uri . with ( { path : path . join ( rootPath . uri . path , rcPath ) } ) ) ; } } } if ( ! rcPath ) { for ( const rootPath of vscode . workspace . workspaceFolders ) { possibleRCUris . push ( rootPath . uri . with ( { path : path . join ( rootPath . uri . path , DEFAULT_RUN_CONTROL_FILENAME ) } ) ) ; } } } for ( const rcUri of possibleRCUris ) { if ( await fileExists ( rcUri ) ) { const rcContent = await getFileContent ( rcUri ) ; try { const rc = YAML . parse ( rcContent ) ; debug ( 'Parsed Run Control' , rc ) ; return rc ?? { } ; } catch ( error ) { debug ( 'Invalid RC file format' , error ) ; } } } return { } ; } function isDecryptedFile ( uri ) { return path . basename ( uri . path ) . startsWith ( DECRYPTED_PREFIX ) ; } function getDecryptedFileUri ( encryptedUri ) { const decryptedFileName = DECRYPTED_PREFIX + path . basename ( encryptedUri . path ) ; const decryptedFilePath = path . join ( path . dirname ( encryptedUri . path ) , decryptedFileName ) ; const decryptedFileUri = encryptedUri . with ( { path : normalizeCrossPlatformPath ( decryptedFilePath ) } ) ; return decryptedFileUri ; } function getEncryptedFileUri ( decryptedUri ) { return isDecryptedFile ( decryptedUri ) ? decryptedUri . with ( { path : normalizeCrossPlatformPath ( path . join ( path . dirname ( decryptedUri . path ) , path . basename ( decryptedUri . path ) . substring ( DECRYPTED_PREFIX . length ) ) ) , } ) : null ; } async function isSecretPairMember ( uri ) { if ( isDecryptedFile ( uri ) || await fileExists ( getDecryptedFileUri ( uri ) ) ) { return true ; } else { return false ; } } function wait ( timeoutMs ) { return new Promise ( ( resolve ) => setTimeout ( resolve , timeoutMs ) ) ; } function normalizeCrossPlatformPath ( filePath ) { return filePath . replace ( /\\/g , "/" ) ; } export function activate ( context ) { debug ( 'SOPS activated' ) ; let lastActiveEditor ; const decryptedFileUris = [ ] ; async function syncStatusBar ( ) { debug ( 'syncing status bar' ) ; if ( lastActiveEditor ) { if ( isDecryptedFile ( lastActiveEditor . document . uri ) ) { toggleStatusBarItem . text = getToggleBarText ( 'encrypted' ) ; debug ( 'showing status bar for decrypted file' , lastActiveEditor . document . uri . path ) ; toggleStatusBarItem . show ( ) ; return ; } const decryptedFileUri = getDecryptedFileUri ( lastActiveEditor . document . uri ) ; if ( decryptedFileUri && await fileExists ( decryptedFileUri ) ) { toggleStatusBarItem . text = getToggleBarText ( 'decrypted' ) ; debug ( 'showing status bar for encrypted file' , lastActiveEditor . document . uri . path ) ; toggleStatusBarItem . show ( ) ; return ; } } debug ( 'hiding status bar' ) ; toggleStatusBarItem . hide ( ) ; } const toggleOriginalFile = async ( ) => { debug ( `command ${ Command . TOGGLE_ORIGINAL_FILE } executed` ) ; if ( ! isEnabled ( context ) ) { return ; } if ( lastActiveEditor ) { let fileUriToOpen ; const encryptedFileUri = getEncryptedFileUri ( lastActiveEditor . document . uri ) ; if ( encryptedFileUri && await fileExists ( encryptedFileUri ) ) { debug ( 'command encrypted file exists' , encryptedFileUri . path ) ; fileUriToOpen = encryptedFileUri ; } const decryptedFileUri = getDecryptedFileUri ( lastActiveEditor . document . uri ) ; if ( decryptedFileUri && await fileExists ( decryptedFileUri ) ) { debug ( 'command decrypted file exists' , decryptedFileUri . path ) ; fileUriToOpen = decryptedFileUri ; } if ( fileUriToOpen ) { await openFile ( fileUriToOpen ) ; } } } ; const onActiveEditorChanged = async ( editor ) => { debug ( 'change active editor' , editor ?. document . fileName ) ; if ( ! isEnabled ( context ) ) { return ; } if ( editor ) { const document = editor . document ; if ( isDecryptedFile ( document . uri ) ) { decryptedFileUris . push ( document . uri ) ; } if ( ! await isSecretPairMember ( document . uri ) ) { for ( const decryptedFileUri of decryptedFileUris ) { try { await closeAndDeleteFile ( decryptedFileUri ) ; } catch ( error ) { debug ( 'Cannot close file' , document . fileName , error ) ; vscode . window . showErrorMessage ( `Could not delete decrypted SOPS file ${ editor ?. document . fileName }: ${ error . message }` ) ; } } decryptedFileUris . splice ( 0 , decryptedFileUris . length ) ; } try { if ( isIFileFormat ( document . languageId ) && ! await fileExists ( getDecryptedFileUri ( document . uri ) ) ) { await handleFile ( document , document . languageId ) ; } } catch ( error ) { debug ( 'Cannot parse file' , document . fileName , error ) ; if ( ! ( error instanceof ParseError ) ) { vscode . window . showErrorMessage ( `Could not decrypt SOPS file ${ document . fileName }: ${ error . message }` ) ; } } lastActiveEditor = editor ; } await syncStatusBar ( ) ; } ; const onTextDocumentSaved = async ( document ) => { debug ( 'save document' , document . fileName ) ; if ( ! isEnabled ( context ) ) { return ; } try { if ( isIFileFormat ( document . languageId ) ) { await handleSaveFile ( document , document . languageId ) ; } } catch ( error ) { debug ( 'Cannot encrypt file' , document . fileName , error ) ; vscode . window . showErrorMessage ( `Could not encrypt SOPS file ${ document . fileName }: ${ error . message }` ) ; } } ; const printInfo = ( ) => { if ( ! isEnabled ( context ) ) { return ; } vscode . window . showInformationMessage ( 'SOPS!' ) ; } ; const createSetBeta = ( enableBeta ) => ( ) => { vscode . workspace . getConfiguration ( CONFIG_BASE_SECTION ) . update ( ConfigName . beta , enableBeta , vscode . ConfigurationTarget . Global ) ; } ; const activeDisposables = [ ] ; async function updateSubscriptions ( ) { if ( isEnabled ( context ) ) { if ( activeDisposables . length === 0 ) { await wait ( 20 ) ; debug ( 'enabling subscriptions' ) ; activeDisposables . push ( vscode . commands . registerTextEditorCommand ( Command . TOGGLE_ORIGINAL_FILE , toggleOriginalFile ) , vscode . window . onDidChangeActiveTextEditor ( onActiveEditorChanged ) , vscode . workspace . onDidSaveTextDocument ( onTextDocumentSaved ) , vscode . commands . registerCommand ( Command . INFO_COMMAND , printInfo ) , vscode . commands . registerCommand ( Command . ENABLE_BETA , createSetBeta ( true ) ) , vscode . commands . registerCommand ( Command . DISABLE_BETA , createSetBeta ( false ) ) ) ; context . subscriptions . push ( ... activeDisposables ) ; } } else { debug ( 'disabling subscriptions' ) ; let activeDisposable ; while ( activeDisposable = activeDisposables . pop ( ) ) { activeDisposable . dispose ( ) ; context . subscriptions . splice ( context . subscriptions . indexOf ( activeDisposable ) , 1 ) ; } } } const configurationChangesDisposable = vscode . workspace . onDidChangeConfiguration ( ( event ) => { debug ( 'configuration changed' ) ; if ( event . affectsConfiguration ( CONFIG_BASE_SECTION ) ) { debug ( 'updating subscriptions' ) ; updateSubscriptions ( ) ; } } ) ; context . subscriptions . push ( configurationChangesDisposable ) ; updateSubscriptions ( ) ; } export function deactivate ( ) { }