Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable all commands when config cannot be loaded #955

Merged
merged 8 commits into from Jul 8, 2022
10 changes: 8 additions & 2 deletions taqueria-plugin-taquito/originate.ts
@@ -1,4 +1,5 @@
import {
getCurrentEnvironment,
getCurrentEnvironmentConfig,
getDefaultAccount,
getInitialStorage,
Expand Down Expand Up @@ -61,7 +62,11 @@ const getValidContracts = async (parsedArgs: Opts) => {
(retval, filename) => {
const storage = getInitialStorage(parsedArgs)(filename);
if (storage === undefined || storage === null) {
sendErr(`No initial storage provided for ${filename}`);
sendErr(
`Michelson artifact ${filename} has no initial storage specified for the target environment.\nStorage is expected to be specified in .taq/config.json at JSON path: environment.${
getCurrentEnvironment(parsedArgs)
}.storage."${filename}"\n`,
);
return retval;
}
return [...retval, { filename, storage }];
Expand Down Expand Up @@ -181,8 +186,9 @@ const originateToSandboxes = (parsedArgs: Opts, currentEnv: Protocol.Environment
const first = getFirstAccountAlias(sandboxName, parsedArgs);
if (first) {
defaultAccount = getSandboxAccountConfig(parsedArgs)(sandboxName)(first);
// TODO: The error should be a warning, not an error. Descriptive string should not begin with 'Warning:'
sendErr(
`No default account has been configured for the sandbox called ${sandboxName}. Using the account called ${first} for origination.`,
`Warning: A default origination account has not been specified for sandbox ${sandboxName}. Taqueria will use the account ${first} for this origination.\nA default account can be specified in .taq/config.json at JSON path: sandbox.${sandboxName}.accounts.default\n`,
);
}
}
Expand Down
60 changes: 6 additions & 54 deletions taqueria-vscode-extension/package.json
Expand Up @@ -86,28 +86,28 @@
"category": "Taqueria",
"title": "Initialize Project",
"shortTitle": "init",
"enablement": "!@taqueria-state/is-taqified"
"enablement": "@taqueria-state/enable-init-scaffold"
},
{
"command": "taqueria.scaffold",
"category": "Taqueria",
"title": "Scaffold Project",
"shortTitle": "scaffold",
"enablement": "!@taqueria-state/is-taqified"
"enablement": "@taqueria-state/enable-init-scaffold"
},
{
"command": "taqueria.install",
"category": "Taqueria",
"title": "Install Plugin",
"shortTitle": "install",
"enablement": "@taqueria-state/is-taqified && @taqueria-state/not-installed-plugin-count != 0"
"enablement": "@taqueria-state/enable-install-uninstall && @taqueria-state/not-installed-plugin-count != 0"
},
{
"command": "taqueria.uninstall",
"category": "Taqueria",
"title": "Uninstall Plugin",
"shortTitle": "uninstall",
"enablement": "@taqueria-state/is-taqified && @taqueria-state/installed-plugin-count != 0"
"enablement": "@taqueria-state/enable-install-uninstall && @taqueria-state/installed-plugin-count != 0"
},
{
"command": "taqueria.compile_smartpy",
Expand Down Expand Up @@ -182,7 +182,7 @@
"category": "Taqueria",
"title": "Typecheck Michelson contracts",
"shortTitle": "typecheck",
"enablement": "@taqueria/plugin-contract-types"
"enablement": "@taqueria/plugin-tezos-client"
},
{
"command": "taqueria.test",
Expand All @@ -191,55 +191,7 @@
"shortTitle": "test",
"enablement": "@taqueria/plugin-jest"
}
],
"menus": {
"commandPalette": [
{
"command": "taqueria.init",
"when": "!@taqueria-state/is-taqified"
},
{
"command": "taqueria.install",
"when": "@taqueria-state/is-taqified"
},
{
"command": "taqueria.compile_ligo",
"when": "@taqueria/plugin-ligo"
},
{
"command": "taqueria.compile_smartpy",
"when": "@taqueria/plugin-smartpy"
},
{
"command": "taqueria.start_sandbox",
"when": "@taqueria/plugin-flextesa"
},
{
"command": "taqueria.stop_sandbox",
"when": "@taqueria/plugin-flextesa"
},
{
"command": "taqueria.list_accounts",
"when": "@taqueria/plugin-flextesa"
},
{
"command": "taqueria.originate",
"when": "@taqueria/plugin-taquito"
},
{
"command": "taqueria.generate_types",
"when": "@taqueria/plugin-contract-types"
},
{
"command": "taqueria.typecheck",
"when": "@taqueria/plugin-contract-types"
},
{
"command": "taqueria.test",
"enablement": "@taqueria/plugin-jest"
}
]
}
]
},
"scripts": {
"vscode:prepublish": "npm run compile",
Expand Down
10 changes: 6 additions & 4 deletions taqueria-vscode-extension/src/extension.ts
Expand Up @@ -7,19 +7,21 @@ import { makeDir } from './lib/pure';

const { clearConfigWatchers, getConfigWatchers, addConfigWatcherIfNotExists } = (() => {
const inMemoryState = {
configWatchers: new Map<string, api.FileSystemWatcher>(),
configWatchers: new Map<string, api.FileSystemWatcher[]>(),
};

const clearConfigWatchers = () => {
for (const watcher of inMemoryState.configWatchers.values()) {
watcher.dispose();
for (const watcherList of inMemoryState.configWatchers.values()) {
for (const watcher of watcherList) {
watcher.dispose();
}
}
inMemoryState.configWatchers.clear();
};

const getConfigWatchers = () => inMemoryState.configWatchers.values();

const addConfigWatcherIfNotExists = (folder: string, factory: () => api.FileSystemWatcher) => {
const addConfigWatcherIfNotExists = (folder: string, factory: () => api.FileSystemWatcher[]) => {
if (inMemoryState.configWatchers.has(folder)) {
return;
}
Expand Down
126 changes: 87 additions & 39 deletions taqueria-vscode-extension/src/lib/helpers.ts
Expand Up @@ -603,39 +603,79 @@ export const inject = (deps: InjectedDependencies) => {
projectDir: Util.PathToDir,
) => {
showOutput(output)(OutputLevels.debug, 'Project config changed, updating command states...');
let taqFolderFound: boolean;
try {
const config = await Util.TaqifiedDir.create(projectDir, i18n);
const availablePlugins = await getAvailablePlugins(context);
const availablePluginsNotInstalled = config.config?.plugins
? availablePlugins.filter(name => config.config.plugins?.findIndex(p => p.name === name) === -1)
: availablePlugins;
showOutput(output)(OutputLevels.debug, `@taqueria-state/is-taqified: ${!!config.config}`);
vscode.commands.executeCommand('setContext', '@taqueria-state/is-taqified', !!config.config);
vscode.commands.executeCommand(
'setContext',
'@taqueria-state/installed-plugin-count',
config.config?.plugins?.length ?? 0,
);
vscode.commands.executeCommand(
'setContext',
'@taqueria-state/not-installed-plugin-count',
availablePluginsNotInstalled.length,
);
const plugins = getWellKnownPlugins();
showOutput(output)(OutputLevels.debug, `Known plugins: ${JSON.stringify(plugins)}`);
for (const plugin of plugins) {
const found = config.config?.plugins?.find(item => item.name === plugin) !== undefined;
showOutput(output)(OutputLevels.debug, `plugins ${plugin}: ${found}`);
vscode.commands.executeCommand('setContext', plugin, found);
}
} catch (e: any) {
if (
e.code === 'E_NOT_TAQIFIED' && e.msg === `The given directory is not taqified as it's missing a .taq directory.`
) {
return;
await Util.makeDir(join(projectDir, '.taq'), i18n);
showOutput(output)(OutputLevels.debug, 'Taq folder is found');
taqFolderFound = true;
} catch {
taqFolderFound = false;
showOutput(output)(OutputLevels.debug, 'Taq folder not found');
}
let enableAllCommands: boolean;
let config: Util.TaqifiedDir | null;
try {
config = await Util.TaqifiedDir.create(projectDir, i18n);
enableAllCommands = false;
} catch (e: unknown) {
config = null;
enableAllCommands = taqFolderFound;
// We don't want to show messages to users when they are working with non-taqified folders (Except when output level is set to info or more verbose)
if (shouldOutput(OutputLevels.info, output.logLevel) || taqFolderFound) {
vscode.commands.executeCommand('setContext', '@taqueria-state/enable-all-commands', true);
showOutput(output)(OutputLevels.error, 'Error: Could not update command states:');
logAllNestedErrors(e, output);
if (taqFolderFound) {
showOutput(output)(
OutputLevels.warn,
'The Taqueria config for this project could not be loaded. All Taqueria commands will be temporarily enabled.\nPlease check for errors in the .taq/config.json file\n',
);
}
}
showOutput(output)(OutputLevels.error, 'Error: Could not update command states:');
logAllNestedErrors(e, output);
}
const availablePlugins = await getAvailablePlugins(context);
const availablePluginsNotInstalled = config?.config?.plugins
? availablePlugins.filter(name => config?.config.plugins?.findIndex(p => p.name === name) === -1)
: availablePlugins;
showOutput(output)(
OutputLevels.debug,
`@taqueria-state/enable-init-scaffold: ${enableAllCommands || !config?.config}`,
);
vscode.commands.executeCommand(
'setContext',
'@taqueria-state/enable-init-scaffold',
enableAllCommands || !config?.config,
);

showOutput(output)(
OutputLevels.debug,
`@taqueria-state/enable-install-uninstall: ${enableAllCommands || !!config?.config}`,
);
vscode.commands.executeCommand(
'setContext',
'@taqueria-state/enable-install-uninstall',
enableAllCommands || !!config?.config,
);

showOutput(output)(OutputLevels.debug, `@taqueria-state/is-taqified: ${!!config?.config}`);
vscode.commands.executeCommand('setContext', '@taqueria-state/is-taqified', !!config?.config);

vscode.commands.executeCommand(
'setContext',
'@taqueria-state/installed-plugin-count',
enableAllCommands ? 1 : config?.config?.plugins?.length ?? 0,
);
vscode.commands.executeCommand(
'setContext',
'@taqueria-state/not-installed-plugin-count',
enableAllCommands ? 1 : availablePluginsNotInstalled.length,
);
const plugins = getWellKnownPlugins();
showOutput(output)(OutputLevels.debug, `Known plugins: ${JSON.stringify(plugins)}`);
for (const plugin of plugins) {
const found = config?.config?.plugins?.find(item => item.name === plugin) !== undefined;
showOutput(output)(OutputLevels.debug, `plugins ${plugin}: ${found}`);
vscode.commands.executeCommand('setContext', plugin, enableAllCommands || found);
}
};

Expand All @@ -644,7 +684,7 @@ export const inject = (deps: InjectedDependencies) => {
output: Output,
i18n: i18n,
projectDir: Util.PathToDir,
addConfigWatcherIfNotExists: (folder: string, factory: () => api.FileSystemWatcher) => void,
addConfigWatcherIfNotExists: (folder: string, factory: () => api.FileSystemWatcher[]) => void,
) => {
showOutput(output)(OutputLevels.debug, `Directory ${projectDir} should be watched.`);
addConfigWatcherIfNotExists(projectDir, () => {
Expand All @@ -655,15 +695,23 @@ export const inject = (deps: InjectedDependencies) => {
logAllNestedErrors(error, output);
}
try {
// TODO: this does not trigger when .taq folder is deleted.
const watcher = vscode.workspace.createFileSystemWatcher(join(projectDir, '.taq/config.json'));
// TODO: We should detect the event that VsCode's current Folder is changed and the watcher should be disposed
const folderWatcher = vscode.workspace.createFileSystemWatcher(join(projectDir, '.taq'));
const configWatcher = vscode.workspace.createFileSystemWatcher(join(projectDir, '.taq/config.json'));
const stateWatcher = vscode.workspace.createFileSystemWatcher(join(projectDir, '.taq/state.json'));

// TODO: Is passing these arguments to the callback of a long lived watcher prevent GC? Are these short lived objects?
watcher.onDidChange((e: api.Uri) => updateCommandStates(context, output, i18n, projectDir));
watcher.onDidCreate((e: api.Uri) => updateCommandStates(context, output, i18n, projectDir));
watcher.onDidDelete((e: api.Uri) => updateCommandStates(context, output, i18n, projectDir));
return watcher;
folderWatcher.onDidChange((e: api.Uri) => updateCommandStates(context, output, i18n, projectDir));
folderWatcher.onDidCreate((e: api.Uri) => updateCommandStates(context, output, i18n, projectDir));
folderWatcher.onDidDelete((e: api.Uri) => updateCommandStates(context, output, i18n, projectDir));

configWatcher.onDidChange((e: api.Uri) => updateCommandStates(context, output, i18n, projectDir));
configWatcher.onDidCreate((e: api.Uri) => updateCommandStates(context, output, i18n, projectDir));
configWatcher.onDidDelete((e: api.Uri) => updateCommandStates(context, output, i18n, projectDir));

stateWatcher.onDidChange((e: api.Uri) => updateCommandStates(context, output, i18n, projectDir));
stateWatcher.onDidCreate((e: api.Uri) => updateCommandStates(context, output, i18n, projectDir));
stateWatcher.onDidDelete((e: api.Uri) => updateCommandStates(context, output, i18n, projectDir));
return [folderWatcher, configWatcher, stateWatcher];
} catch (error: unknown) {
throw {
kind: 'E_UnknownError',
Expand Down
4 changes: 3 additions & 1 deletion tests/e2e/taqueria-plugin-taquito.spec.ts
Expand Up @@ -273,7 +273,9 @@ describe('E2E Testing for taqueria taquito plugin', () => {
const stdoutDeploy = await exec(`taq deploy -e ${environment}`, { cwd: `./${taqueriaProjectPath}` });

// 3. Verify that proper error displays in the console
expect(stdoutDeploy.stderr).toContain('No initial storage provided for hello-tacos.tz');
expect(stdoutDeploy.stderr).toContain(
'Michelson artifact hello-tacos.tz has no initial storage specified for the target environment.\nStorage is expected to be specified in .taq/config.json at JSON path: environment.test.storage."hello-tacos.tz"',
);
});

test('Verify that taqueria taquito plugin will show proper error when configuration is wrong -> initial storage is not a number', async () => {
Expand Down