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

PDE-4495 fix(cli): zapier convert crashes due to syntax error in user's code and should not replace source in sample #730

Merged
merged 1 commit into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 78 additions & 1 deletion packages/cli/src/tests/utils/convert.js
Original file line number Diff line number Diff line change
Expand Up @@ -258,16 +258,25 @@ const setupTempWorkingDir = () => {
};

describe('convert', () => {
let tempAppDir, readTempFile;
let tempAppDir, readTempFile, origConsoleWarn;
const warnings = [];

beforeEach(() => {
tempAppDir = setupTempWorkingDir();
readTempFile = (fpath) =>
fs.readFileSync(path.join(tempAppDir, fpath), 'utf-8');

origConsoleWarn = console.warn;
warnings.length = 0;
console.warn = (msg) => {
warnings.push(msg);
};
});

afterEach(() => {
fs.removeSync(tempAppDir);

console.warn = origConsoleWarn;
});

describe('visual builder apps', () => {
Expand Down Expand Up @@ -386,5 +395,73 @@ describe('convert', () => {
'\n// a comment';
await convertApp(visualApp, appDefinition, tempAppDir);
});

it('should not break over syntax error in authentication', async () => {
// PDE-4495
const appDefinition = cloneDeep(visualAppDefinition);
appDefinition.authentication.test.source += '{ bad code }';
await convertApp(visualApp, appDefinition, tempAppDir);

warnings.should.have.length(1);
warnings[0].should.containEql('Your code has syntax error');
warnings[0].should.containEql('authentication.js');

const authenticationFile = readTempFile('authentication.js');
should(
authenticationFile.includes('const test = async (z, bundle)')
).be.true();
should(authenticationFile.includes('{ bad code }')).be.true();
});

it('should not break over syntax error in step', async () => {
// PDE-4495
const appDefinition = cloneDeep(visualAppDefinition);
appDefinition.triggers.codemode.operation.perform.source +=
'{ bad code }';
await convertApp(visualApp, appDefinition, tempAppDir);

warnings.should.have.length(1);
warnings[0].should.containEql('Your code has syntax error');
warnings[0].should.containEql('triggers/codemode.js');

const triggerFile = readTempFile('triggers/codemode.js');
should(triggerFile.includes('const perform = async (z)')).be.true();
should(triggerFile.includes('{ bad code }')).be.true();
});

it('should not break over syntax error in hydrator', async () => {
// PDE-4495
const appDefinition = cloneDeep(visualAppDefinition);
appDefinition.hydrators.getMovieDetails.source += '{ bad code }';
await convertApp(visualApp, appDefinition, tempAppDir);

warnings.should.have.length(1);
warnings[0].should.containEql('Your code has syntax error');
warnings[0].should.containEql('hydrators.js');

const hydratorsFile = readTempFile('hydrators.js');
should(
hydratorsFile.includes('getMovieDetails = async (z, bundle)')
).be.true();
should(hydratorsFile.includes('{ bad code }')).be.true();
});

it("should not replace 'source' in sample", async () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great tests! 💯

// PDE-4495
const appDefinition = cloneDeep(visualAppDefinition);
appDefinition.creates.create_project.operation.sample = {
id: 1234,
data: {
objects: [{ source: 'not a function' }],
},
};
await convertApp(visualApp, appDefinition, tempAppDir);

const createFile = readTempFile('creates/create_project.js');
should(
createFile.includes('const inputFields = async (z, bundle)')
).be.true();
should(createFile.includes("[{ source: 'not a function' }]")).be.true();
});
});
});
30 changes: 25 additions & 5 deletions packages/cli/src/utils/convert.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const path = require('path');

const _ = require('lodash');
const chalk = require('chalk');
const prettier = require('prettier');
const semver = require('semver');
const traverse = require('traverse');
Expand Down Expand Up @@ -157,6 +158,15 @@ const renderPackageJson = async (appInfo, appDefinition) => {
const renderSource = (definition, functions = {}) => {
traverse(definition).forEach(function (source) {
if (this.key === 'source') {
if (
this.path.length >= 2 &&
this.path[0] === 'operation' &&
this.path[1] === 'sample'
) {
// Don't replace 'source' if it's in sample
return;
}

const args = this.parent.node.args || ['z', 'bundle'];
// Find first parent that is not an array (applies to inputFields)
const funcNameBase = this.path
Expand All @@ -178,7 +188,7 @@ const renderSource = (definition, functions = {}) => {
});
};

const renderDefinitionSlice = (definitionSlice) => {
const renderDefinitionSlice = (definitionSlice, filename) => {
let exportBlock = _.cloneDeep(definitionSlice);
let functionBlock = {};

Expand All @@ -190,7 +200,17 @@ const renderDefinitionSlice = (definitionSlice) => {

functionBlock = Object.values(functionBlock).join('\n\n');

return prettifyJs(functionBlock + '\n\n' + exportBlock);
const uglyCode = functionBlock + '\n\n' + exportBlock;
try {
return prettifyJs(uglyCode);
} catch (err) {
console.warn(
`Warning: Your code has syntax error in ${chalk.underline.bold(
filename
)}. ` + `It will be left as is and won't be prettified.\n\n${err.message}`
);
return uglyCode;
}
};

const renderStepTest = async (stepType, definition) => {
Expand All @@ -205,10 +225,10 @@ const renderStepTest = async (stepType, definition) => {
};

const renderAuth = async (appDefinition) =>
renderDefinitionSlice(appDefinition.authentication);
renderDefinitionSlice(appDefinition.authentication, 'authentication.js');

const renderHydrators = async (appDefinition) =>
renderDefinitionSlice(appDefinition.hydrators);
renderDefinitionSlice(appDefinition.hydrators, 'hydrators.js');

const renderIndex = async (appDefinition) => {
let exportBlock = _.cloneDeep(appDefinition);
Expand Down Expand Up @@ -284,7 +304,7 @@ const renderEnvironment = (appDefinition) => {

const writeStep = async (stepType, definition, key, newAppDir) => {
const filename = `${stepType}/${snakeCase(key)}.js`;
const content = await renderDefinitionSlice(definition);
const content = await renderDefinitionSlice(definition, filename);
await createFile(content, filename, newAppDir);
};

Expand Down