Check for IDE update at startup (#797)

* Remove check for updates on startup setting

* Remove useless exported function

* Update template-package.json used to package IDE

* Add function to get channel file during packaging step

* Add updates check

* move ide updater on backend

* configure updater options

* add auto update preferences

* TMP check updates on start and download

* index on check-update-startup: fcb8f6e TMP check updates on start and download

* set version to skip on local storage

* add IDE setting to toggle update check on start-up

* comment out check for updates on startup and auto update settings

* Update Theia to 1.22.1

* updated CI

* download changelog and show it in IDE updater dialog

* remove useless file

* remove useless code

* add i18n to updater dialog

* fix i18n

* refactor UpdateInfo typing

* add macos zip to artifacts

* Simply use `--ignore-engines`

* Use correct --ignore-engines

* Fix semver#valid call

* Use C++17

* updated documentation

* add update channel preference

* update updater url

* updated documentation

* Fix the C++ version

* Build flag for cpp

* add disclaimer with correct node version

* Update `electron-builder`

* Fix `Electron.Menu` issue

* Skip electron rebuild

* Rebuild native dependencies beforehand

* Use resolutions section

* Update template-package.json as well

* move ide-updater to electron application

* refactor ide-updater service

* update yarn.lock

* update i18n

* Revert "Add gRPC user agent (#834)"

This reverts commit 5ab3a747a6.

* fix ide download url

* update latest file in CI

* fix i18n check

Co-authored-by: Silvano Cerza <silvanocerza@gmail.com>
Co-authored-by: Francesco Stasi <f.stasi@me.com>
Co-authored-by: Mark Sujew <msujew@yahoo.de>
This commit is contained in:
Alberto Iannaccone
2022-02-15 18:01:19 +01:00
committed by GitHub
parent 9ecff86bbe
commit f660058c75
30 changed files with 1915 additions and 346 deletions

View File

@@ -12,35 +12,42 @@ const fromFile = require('file-type').fromFile;
* Resolves to an array of `npm` package names that are declared in the `package.json` but **not** used by the project.
*/
function collectUnusedDependencies(pathToProject = process.cwd()) {
const p = path.isAbsolute(pathToProject) ? pathToProject : path.resolve(process.cwd(), pathToProject);
console.log(`⏱️ >>> Collecting unused backend dependencies for ${p}...`);
return new Promise(resolve => {
depcheck(p, {
ignoreDirs: [
'frontend'
],
parsers: {
'*.js': depcheck.parser.es6,
'*.jsx': depcheck.parser.jsx
},
detectors: [
depcheck.detector.requireCallExpression,
depcheck.detector.importDeclaration
],
specials: [
depcheck.special.eslint,
depcheck.special.webpack
]
}, unused => {
const { dependencies } = unused
if (dependencies && dependencies.length > 0) {
console.log(`👌 <<< The following unused dependencies have been found: ${JSON.stringify(dependencies, null, 2)}`);
} else {
console.log('👌 <<< No unused dependencies have been found.');
}
resolve(dependencies);
});
})
const p = path.isAbsolute(pathToProject)
? pathToProject
: path.resolve(process.cwd(), pathToProject);
console.log(`⏱️ >>> Collecting unused backend dependencies for ${p}...`);
return new Promise((resolve) => {
depcheck(
p,
{
ignoreDirs: ['frontend'],
parsers: {
'*.js': depcheck.parser.es6,
'*.jsx': depcheck.parser.jsx,
},
detectors: [
depcheck.detector.requireCallExpression,
depcheck.detector.importDeclaration,
],
specials: [depcheck.special.eslint, depcheck.special.webpack],
},
(unused) => {
const { dependencies } = unused;
if (dependencies && dependencies.length > 0) {
console.log(
`👌 <<< The following unused dependencies have been found: ${JSON.stringify(
dependencies,
null,
2
)}`
);
} else {
console.log('👌 <<< No unused dependencies have been found.');
}
resolve(dependencies);
}
);
});
}
/**
@@ -50,101 +57,111 @@ function collectUnusedDependencies(pathToProject = process.cwd()) {
* If `pathToZip` is not a ZIP, rejects. `targetFolderName` is the destination folder not the new archive location.
*/
function adjustArchiveStructure(pathToZip, targetFolderName, noCleanup) {
return new Promise(async (resolve, reject) => {
if (!await isZip(pathToZip)) {
reject(new Error(`Expected a ZIP file.`));
return;
}
if (!fs.existsSync(targetFolderName)) {
reject(new Error(`${targetFolderName} does not exist.`));
return;
}
if (!fs.lstatSync(targetFolderName).isDirectory()) {
reject(new Error(`${targetFolderName} is not a directory.`));
return;
}
console.log(`⏱️ >>> Adjusting ZIP structure ${pathToZip}...`);
return new Promise(async (resolve, reject) => {
if (!(await isZip(pathToZip))) {
reject(new Error(`Expected a ZIP file.`));
return;
}
if (!fs.existsSync(targetFolderName)) {
reject(new Error(`${targetFolderName} does not exist.`));
return;
}
if (!fs.lstatSync(targetFolderName).isDirectory()) {
reject(new Error(`${targetFolderName} is not a directory.`));
return;
}
console.log(`⏱️ >>> Adjusting ZIP structure ${pathToZip}...`);
const root = basename(pathToZip);
const resources = await list(pathToZip);
const hasBaseFolder = resources.find(name => name === root);
if (hasBaseFolder) {
if (resources.filter(name => name.indexOf(path.sep) === -1).length > 1) {
console.warn(`${pathToZip} ZIP has the desired root folder ${root}, however the ZIP contains other entries too: ${JSON.stringify(resources)}`);
}
console.log(`👌 <<< The ZIP already has the desired ${root} folder.`);
resolve(pathToZip);
return;
}
const root = basename(pathToZip);
const resources = await list(pathToZip);
const hasBaseFolder = resources.find((name) => name === root);
if (hasBaseFolder) {
if (
resources.filter((name) => name.indexOf(path.sep) === -1).length > 1
) {
console.warn(
`${pathToZip} ZIP has the desired root folder ${root}, however the ZIP contains other entries too: ${JSON.stringify(
resources
)}`
);
}
console.log(`👌 <<< The ZIP already has the desired ${root} folder.`);
resolve(pathToZip);
return;
}
const track = temp.track();
try {
const unzipOut = path.join(track.mkdirSync(), root);
fs.mkdirSync(unzipOut);
await unpack(pathToZip, unzipOut);
const adjustedZip = path.join(targetFolderName, path.basename(pathToZip));
await pack(unzipOut, adjustedZip);
console.log(`👌 <<< Adjusted the ZIP structure. Moved the modified ${basename(pathToZip)} to the ${targetFolderName} folder.`);
resolve(adjustedZip);
} finally {
if (!noCleanup) {
track.cleanupSync();
}
}
});
const track = temp.track();
try {
const unzipOut = path.join(track.mkdirSync(), root);
fs.mkdirSync(unzipOut);
await unpack(pathToZip, unzipOut);
const adjustedZip = path.join(targetFolderName, path.basename(pathToZip));
await pack(unzipOut, adjustedZip);
console.log(
`👌 <<< Adjusted the ZIP structure. Moved the modified ${basename(
pathToZip
)} to the ${targetFolderName} folder.`
);
resolve(adjustedZip);
} finally {
if (!noCleanup) {
track.cleanupSync();
}
}
});
}
/**
* Returns the `basename` of `pathToFile` without the file extension.
*/
function basename(pathToFile) {
const name = path.basename(pathToFile);
const ext = path.extname(pathToFile);
return name.substr(0, name.length - ext.length);
const name = path.basename(pathToFile);
const ext = path.extname(pathToFile);
return name.substr(0, name.length - ext.length);
}
function unpack(what, where) {
return new Promise((resolve, reject) => {
zip.unpack(what, where, error => {
if (error) {
reject(error);
return;
}
resolve();
})
return new Promise((resolve, reject) => {
zip.unpack(what, where, (error) => {
if (error) {
reject(error);
return;
}
resolve();
});
});
}
function pack(what, where) {
return new Promise((resolve, reject) => {
zip.pack(what, where, error => {
if (error) {
reject(error);
return;
}
resolve();
})
return new Promise((resolve, reject) => {
zip.pack(what, where, (error) => {
if (error) {
reject(error);
return;
}
resolve();
});
});
}
function list(what) {
return new Promise((resolve, reject) => {
zip.list(what, (error, result) => {
if (error) {
reject(error);
return;
}
resolve(result.map(({ name }) => name));
})
return new Promise((resolve, reject) => {
zip.list(what, (error, result) => {
if (error) {
reject(error);
return;
}
resolve(result.map(({ name }) => name));
});
});
}
async function isZip(pathToFile) {
if (!fs.existsSync(pathToFile)) {
throw new Error(`${pathToFile} does not exist`);
}
const type = await fromFile(pathToFile);
return type && type.ext === 'zip';
if (!fs.existsSync(pathToFile)) {
throw new Error(`${pathToFile} does not exist`);
}
const type = await fromFile(pathToFile);
return type && type.ext === 'zip';
}
const isElectronPublish = false; // TODO: support auto-updates
@@ -152,20 +169,62 @@ const isNightly = process.env.IS_NIGHTLY === 'true';
const isRelease = process.env.IS_RELEASE === 'true';
function git(command) {
try {
const gitPath = shell.which('git');
const error = shell.error();
if (error) {
throw new Error(error);
}
const { stderr, stdout } = shell.exec(`"${gitPath}" ${command}`, { silent: true });
if (stderr) {
throw new Error(stderr.toString().trim());
}
return stdout.toString().trim();
} catch (e) {
throw e;
try {
const gitPath = shell.which('git');
const error = shell.error();
if (error) {
throw new Error(error);
}
const { stderr, stdout } = shell.exec(`"${gitPath}" ${command}`, {
silent: true,
});
if (stderr) {
throw new Error(stderr.toString().trim());
}
return stdout.toString().trim();
} catch (e) {
throw e;
}
}
module.exports = { collectUnusedDependencies, adjustArchiveStructure, isZip, unpack, isNightly, isRelease, isElectronPublish, git };
// getChannelFile returns the name of the channel file to be released
// together with the IDE file.
// The channel file depends on the platform and whether we're creating
// a nightly build or a full release.
// In all other cases, like when building a tester build for a PR,
// an empty string is returned since we don't need a channel file.
// The channel files are necessary for updates check with electron-updater
// to work correctly.
// For more information: https://www.electron.build/auto-update
function getChannelFile(platform) {
let currentChannel = '';
if (isNightly) {
currentChannel = 'nightly';
} else if (isRelease) {
currentChannel = 'stable';
} else {
// We're not creating a nightly build nor releasing
// a new version, no need for a channel file.
return '';
}
return (
currentChannel +
{
linux: '-linux.yml',
win32: '.yml',
darwin: '-mac.yml',
}[platform]
);
}
module.exports = {
collectUnusedDependencies,
adjustArchiveStructure,
isZip,
unpack,
isNightly,
isRelease,
isElectronPublish,
git,
getChannelFile,
};