-
-
Notifications
You must be signed in to change notification settings - Fork 5.1k
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
Desktop: Bundle default plugins with desktop application #6679
Conversation
.github/scripts/run_ci.sh
Outdated
echo "Step: Bundling default plugins in desktop application" | ||
cd "$ROOT_DIR/packages/tools" | ||
tsc bundleDefaultPlugins.ts | ||
node bundleDefaultPlugins.js |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code is only necessary when building the desktop app, but you have it here where it will be called even when building the server image. I think this code only needs to be run for the first block below (Building the desktop application for publishing).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will move it then. I thought Docker Image also needs these plugins.
.github/scripts/run_ci.sh
Outdated
tsc bundleDefaultPlugins.ts | ||
node bundleDefaultPlugins.js |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yarn run build
runs before getting here so this TS file should already been compiled, so please remove the tsc statement
import fs = require('fs-extra'); | ||
const fetch = require('node-fetch'); | ||
|
||
describe('bundlePlugins', function() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be named like the file bundleDefaultPlugins
describe('bundlePlugins', function() { | ||
|
||
beforeEach(() => { | ||
jest.setTimeout(20000); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this setTimeout for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am using this just to increase the timeout for tests on my local system, otherwise they seem to fail randomly. This will be removed before merging the code.
|
||
let manifests = []; | ||
try { | ||
const manifestData = await fetch('https://raw.githubusercontent.com/joplin/plugins/master/manifests.json'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it actually downloading stuff off GitHub? We can't have this, you'll need to mock this. In general all tests should be able to run offline.
try { | ||
const manifestData = await fetch('https://raw.githubusercontent.com/joplin/plugins/master/manifests.json'); | ||
manifests = JSON.parse(await manifestData.text()); | ||
if (!manifests) throw new Error('Invalid or missing JSON'); | ||
} catch (error) { | ||
console.log(error); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something's not right here, it shouldn't log anything in tests. You need to test that it doesn't throw, if it's not supposed to throw. And in fact I think the tests themselves shouldn't throw exception (the code they call might).
const downloadFile = async (url: string, name: string) => { | ||
const response = await fetch(url); | ||
if (!response.ok) throw new Error(`Unexpected response ${response.statusText}`); | ||
await streamPipeline(response.body, fs.createWriteStream(name)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it not possible to use fetch? Too many problems with streams in general
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can't use fetch
alone, we have to write the response to a file. If we don't want to use streams then we can use the following alternative:
function downloadFile(url:string, outputPath:string) {
return fetch(url)
.then(response => response.buffer())
.then(buff => writeFilePromise(outputPath, Buffer.from(buff)));
}
|
||
import path = require('path'); | ||
import { downloadPlugins, localPluginsVersion } from './bundleDefaultPlugins'; | ||
import fs = require('fs-extra'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't import *
only import the functions you need
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Noted 👍
|
||
|
||
|
||
import path = require('path'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
only import the functions you need
describe('bundlePlugins', function() { | ||
|
||
beforeEach(() => { | ||
jest.setTimeout(20000); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this setTimeout for?
await downloadPlugins(tempDir, localPluginsVersions, testDefaultPluginsIds, manifests); | ||
|
||
expect(fs.existsSync(`${tempDir}/io.github.jackgruber.backup`)).toBe(true); | ||
expect(fs.existsSync(`${tempDir}/plugin.calebjohn.rich-markdown`)).toBe(true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only use async methods
const fetch = require('node-fetch'); | ||
const { writeFile } = require('fs'); | ||
const { promisify } = require('util'); | ||
const writeFilePromise = promisify(writeFile); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just import from fs-extra, it's already promisified
localPluginsVersions[pluginId] = '0.0.0'; | ||
continue; | ||
} | ||
const data = fs.readFileSync(`${defaultPluginDir}/${pluginId}/manifest.json`, 'utf8'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
only async calls
}; | ||
|
||
function downloadFile(url: string, outputPath: string) { | ||
return fetch(url).then(response => response.buffer()).then(buff => writeFilePromise(outputPath, Buffer.from(buff))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no then, only await/async
const pluginName = `${manifests[pluginId]._npm_package_name}-${defaultPluginsId[pluginId]}.tgz`; | ||
await downloadFile(pluginUrl, pluginName); | ||
|
||
if (!fs.existsSync(pluginName)) throw new Error(`${pluginName} cannot be downloaded`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
only async code
@@ -170,6 +170,8 @@ cd "$ROOT_DIR/packages/app-desktop" | |||
|
|||
if [[ $GIT_TAG_NAME = v* ]]; then | |||
echo "Step: Building and publishing desktop application..." | |||
cd "$ROOT_DIR/packages/tools" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Line 175 Is meant to be run form the packages/app-desktop directory, but this line places the script in packages/tools. This appears like it will break the ci build (for releases).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. I will fix it.
…/joplin into bundle-default-plugins-1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nearly there I think, just a few more tweaks
} | ||
} | ||
|
||
export const downloadPlugins = async (localPluginsVersions: PluginAndVersion, defaultPluginsInfo: DefaultPluginsInfo, manifests: any): Promise<any> => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't return any
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated this with a type.
const pluginName = `${manifests[pluginId]._npm_package_name}-${defaultPluginsInfo[pluginId].version}.tgz`; | ||
await downloadFile(pluginUrl, pluginName); | ||
|
||
if (!await pathExists(pluginName)) throw new Error(`${pluginName} cannot be downloaded`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need this check, because downloadFile should throw if there's an error
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. Thanks for catching this.
|
||
const downloadedPluginsNames: PluginIdAndName = {}; | ||
for (const pluginId of Object.keys(defaultPluginsInfo)) { | ||
if (localPluginsVersions[pluginId] === defaultPluginsInfo[pluginId].version) continue; | ||
const response = await fetch(`https://registry.npmjs.org/${manifests[pluginId]._npm_package_name}`); | ||
|
||
if (!response.ok) { | ||
const responseText = await response.text(); | ||
throw new Error(`Cannot download plugin file ${responseText.substr(0,500)}`); | ||
throw new Error(`Cannot fetch ${manifests[pluginId]._npm_package_name} release info from NPM`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually your previous code was good, as you were showing the actual error message. Any reason you removed it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, while testing locally I thought that .text()
will show buffer data but on failing to fetch it won't have any buffer data to show 😅 . Restored the previous message.
@@ -32,10 +32,13 @@ export const localPluginsVersion = async (defaultPluginDir: string, defaultPlugi | |||
|
|||
async function downloadFile(url: string, outputPath: string) { | |||
const response = await fetch(url); | |||
if (!response.ok) { | |||
throw new Error(`Cannot download file from ${url}`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here too you might want to provide more info such as response.status or response.text
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done 👍
Looks good now, thanks @mak2002 and @CalebJohn! |
Summary
This PR implements bundling plugins with desktop app. When the app is being built, we will run a JS file that pulls the
jpl
files from the NPM registry and then moves it inbuild
folder of the app. In this script, we are providing default plugins IDs with their versions that we intend to use.