Skip to content
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
24 changes: 16 additions & 8 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
const { overrides } = require('@netlify/eslint-config-node')

module.exports = {
parser: 'babel-eslint',
plugins: ['prettier'],
env: {
node: true,
es6: true,
},
extends: ['eslint:recommended', 'prettier'],
};
extends: '@netlify/eslint-config-node',
overrides: [
...overrides,
{
files: ['*.mjs'],
parserOptions: {
sourceType: 'module',
},
rules: {
'import/extensions': [2, 'always'],
},
},
Copy link
Contributor Author

@ehmicky ehmicky Sep 27, 2021

Choose a reason for hiding this comment

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

This was also added to @netlify/eslint-config-node directly in netlify/eslint-config-node#309

],
}
6 changes: 2 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Test
name: Build
on:
# Ensure GitHub actions are not run twice for same commits
push:
Expand All @@ -22,8 +22,6 @@ jobs:
- name: Install dependencies
run: npm ci
- name: Linting
run: npm run lint
- name: Formatting
run: npm run format:ci
- name: Tests
run: npm run test
run: npm run test:ci
6 changes: 0 additions & 6 deletions .prettierrc

This file was deleted.

1 change: 1 addition & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"@netlify/eslint-config-node/.prettierrc.json"
2 changes: 1 addition & 1 deletion commitlint.config.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
module.exports = { extends: ['@commitlint/config-conventional'] };
module.exports = { extends: ['@commitlint/config-conventional'] }
201 changes: 102 additions & 99 deletions functions/npm-diff.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
const pacote = require('pacote');
const fetch = require('node-fetch');
const Diff = require('diff');
const { validate } = require('./utils/validate');
/* eslint-disable max-lines, unicorn/filename-case */
const { env } = require('process')

const Diff = require('diff')
const fetch = require('node-fetch')
const pacote = require('pacote')

const { validate } = require('./utils/validate')

const fetchJson = async (url, options, errorPrefix) => {
const response = await fetch(url, options);
const json = await response.json();
const response = await fetch(url, options)
const json = await response.json()
if (!response.ok) {
throw new Error(`${errorPrefix}: ${json.message}`);
throw new Error(`${errorPrefix}: ${json.message}`)
}
return json;
};
return json
}

const fetchText = async (url, options, errorPrefix) => {
const response = await fetch(url, options);
const text = await response.text();
const response = await fetch(url, options)
const text = await response.text()
if (!response.ok) {
throw new Error(`${errorPrefix}: ${text}`);
throw new Error(`${errorPrefix}: ${text}`)
}
return text;
};
return text
}

const getBaseFile = async ({ baseRepoUrl, baseSha }) => {
const baseFile = await fetchText(
Expand All @@ -28,34 +32,31 @@ const getBaseFile = async ({ baseRepoUrl, baseSha }) => {
headers: { Accept: 'application/vnd.github.VERSION.raw' },
},
'getBaseFile',
);
return baseFile;
};
)
return baseFile
}

const getDiffText = async ({ diffUrl }) => {
const diffText = await fetchText(diffUrl, {}, 'getDiffText');
return diffText;
};
const diffText = await fetchText(diffUrl, {}, 'getDiffText')
return diffText
}

const getDiffedFile = ({ baseFile, diffText }) => {
const diffed = Diff.applyPatch(baseFile, diffText);
const diffed = Diff.applyPatch(baseFile, diffText)
// applyPatch() sometimes returns `false` instead of errors
// https://github.com/kpdecker/jsdiff/issues/247
if (diffed === false) {
throw new Error('Failed applying diff');
throw new Error('Failed applying diff')
}
return diffed;
};
return diffed
}

const toDictionary = (plugins) => {
return plugins.reduce((dic, plugin) => {
return { ...dic, [`${plugin.author}-${plugin.package}`]: plugin };
}, {});
};
const toDictionary = (plugins) =>
plugins.reduce((dic, plugin) => ({ ...dic, [`${plugin.author}-${plugin.package}`]: plugin }), {})

const getDiffs = (basePluginsDictionary, headPluginsDictionary) => {
const diffs = Object.entries(headPluginsDictionary).reduce((acc, [key, headPlugin]) => {
const basePlugin = basePluginsDictionary[key];
const basePlugin = basePluginsDictionary[key]
if (basePlugin && basePlugin.version !== headPlugin.version) {
// existing plugin
return [
Expand All @@ -66,46 +67,45 @@ const getDiffs = (basePluginsDictionary, headPluginsDictionary) => {
url: `https://diff.intrinsic.com/${headPlugin.package}/${basePlugin.version}/${headPlugin.version}`,
status: 'updated',
},
];
} else if (!basePlugin) {
]
}
if (!basePlugin) {
// new plugin
return [...acc, { package: headPlugin.package, version: headPlugin.version, status: 'added' }];
} else {
// unchanged version
return acc;
return [...acc, { package: headPlugin.package, version: headPlugin.version, status: 'added' }]
}
}, []);
// unchanged version
return acc
}, [])

return diffs;
};
return diffs
}

const getNewPluginsUrls = async (diffs) => {
const diffUrls = await Promise.all(
diffs.map(async (diff) => {
if (diff.status === 'added') {
const manifest = await pacote.manifest(`${diff.package}@${diff.version}`);
return { ...diff, url: manifest.dist.tarball };
} else {
return diff;
const manifest = await pacote.manifest(`${diff.package}@${diff.version}`)
return { ...diff, url: manifest.dist.tarball }
}
return diff
}),
);
)

return diffUrls;
};
return diffUrls
}

const ADDED_HEADER = '#### Added Packages';
const UPDATED_HEADER = '#### Updated Packages';
const ADDED_HEADER = '#### Added Packages'
const UPDATED_HEADER = '#### Updated Packages'

const addOrUpdatePrComment = async ({ comment, commentsUrl, token }) => {
try {
const headers = {
'Content-Type': 'application/json',
};
const comments = await fetchJson(commentsUrl, { headers }, 'failed getting comments');
const existingComment = comments.find((c) => c.body.includes(ADDED_HEADER) || c.body.includes(UPDATED_HEADER));
}
const comments = await fetchJson(commentsUrl, { headers }, 'failed getting comments')
const existingComment = comments.find(({ body }) => body.includes(ADDED_HEADER) || body.includes(UPDATED_HEADER))
if (existingComment) {
console.log(`Updating comment to:\n${comment}`);
console.log(`Updating comment to:\n${comment}`)
await fetchJson(
existingComment.url,
{
Expand All @@ -117,9 +117,9 @@ const addOrUpdatePrComment = async ({ comment, commentsUrl, token }) => {
body: JSON.stringify({ body: comment }),
},
'failed updating comment',
);
)
} else {
console.log(`Creating comment:\n${comment}`);
console.log(`Creating comment:\n${comment}`)
await fetchJson(
commentsUrl,
{
Expand All @@ -131,84 +131,87 @@ const addOrUpdatePrComment = async ({ comment, commentsUrl, token }) => {
body: JSON.stringify({ body: comment }),
},
'failed creating comment',
);
)
}
} catch (e) {
console.log(`addOrUpdatePrComment`, e.message);
} catch (error) {
console.log(`addOrUpdatePrComment`, error.message)
}
};
}

// eslint-disable-next-line complexity, max-statements
const diff = async ({ baseSha, baseRepoUrl, diffUrl, commentsUrl, token }) => {
const [baseFile, diffText] = await Promise.all([getBaseFile({ baseSha, baseRepoUrl }), getDiffText({ diffUrl })]);
const [baseFile, diffText] = await Promise.all([getBaseFile({ baseSha, baseRepoUrl }), getDiffText({ diffUrl })])

const basePlugins = JSON.parse(baseFile);
const headPlugins = JSON.parse(getDiffedFile({ baseFile, diffText }));
const basePlugins = JSON.parse(baseFile)
const headPlugins = JSON.parse(getDiffedFile({ baseFile, diffText }))

const basePluginsDictionary = toDictionary(basePlugins);
const headPluginsDictionary = toDictionary(headPlugins);
const basePluginsDictionary = toDictionary(basePlugins)
const headPluginsDictionary = toDictionary(headPlugins)

const diffs = getDiffs(basePluginsDictionary, headPluginsDictionary);
const diffs = getDiffs(basePluginsDictionary, headPluginsDictionary)
if (diffs.length <= 0) {
console.log('No changed plugins');
return;
console.log('No changed plugins')
return
}
const diffUrls = await getNewPluginsUrls(diffs);
const diffUrls = await getNewPluginsUrls(diffs)

const sorted = [...diffUrls];
sorted.sort((a, b) => {
return a.package.localeCompare(b.package);
});
const sorted = [...diffUrls]
sorted.sort(({ package: packageA }, { package: packageB }) => packageA.localeCompare(packageB))

const added = sorted.filter((d) => d.status === 'added');
const updated = sorted.filter((d) => d.status === 'updated');
const added = sorted.filter(({ status }) => status === 'added')
const updated = sorted.filter(({ status }) => status === 'updated')

let comment = '';
if (added.length > 0) {
comment = comment + `${ADDED_HEADER}\n\n${added.map((d) => `- ${d.url}`).join('\n')}`;
let comment = ''
if (added.length !== 0) {
comment += `${ADDED_HEADER}\n\n${added.map(({ url }) => `- ${url}`).join('\n')}`
}
if (updated.length > 0) {
if (updated.length !== 0) {
if (comment) {
comment = comment + '\n\n';
comment += '\n\n'
}
comment = comment + `${UPDATED_HEADER}\n\n${updated.map((d) => `- ${d.url}`).join('\n')}`;
comment += `${UPDATED_HEADER}\n\n${updated.map(({ url }) => `- ${url}`).join('\n')}`
}

if (comment) {
await addOrUpdatePrComment({ comment, commentsUrl, token });
await addOrUpdatePrComment({ comment, commentsUrl, token })
}
};
}

module.exports.handler = async function (e) {
// eslint-disable-next-line max-statements
const handler = async function (rawEvent) {
try {
const { error } = validate(e);
const { error } = validate(rawEvent)
if (error) {
console.warn('Validation error:', error.message);
console.warn('Validation error:', error.message)
return {
statusCode: 404,
body: 'Not Found',
};
}
}
const event = JSON.parse(e.body);
console.log(JSON.stringify(event, null, 2));
const event = JSON.parse(rawEvent.body)
console.log(JSON.stringify(event, null, 2))
if (['opened', 'synchronize', 'reopened'].includes(event.action)) {
const baseSha = event.pull_request.base.sha;
const baseRepoUrl = event.pull_request.base.repo.url;
const diffUrl = event.pull_request.diff_url;
const commentsUrl = event.pull_request.comments_url;
const token = process.env.GITHUB_TOKEN;
await diff({ baseSha, baseRepoUrl, diffUrl, commentsUrl, token });
const baseSha = event.pull_request.base.sha
const baseRepoUrl = event.pull_request.base.repo.url
const diffUrl = event.pull_request.diff_url
const commentsUrl = event.pull_request.comments_url
const token = env.GITHUB_TOKEN
await diff({ baseSha, baseRepoUrl, diffUrl, commentsUrl, token })
} else {
console.log(`Ignoring action ${event.action}`);
console.log(`Ignoring action ${event.action}`)
}
return {
statusCode: 200,
body: JSON.stringify({ message: 'success' }),
};
} catch (e) {
console.error(e);
}
} catch (error) {
console.error(error)
return {
statusCode: 500,
body: JSON.stringify({ message: 'Unknown error' }),
};
}
}
};
}

module.exports = { handler }
/* eslint-enable max-lines, unicorn/filename-case */
Loading