mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-04-27 16:57:19 +00:00

- Support for multiple electron targe per platform. - Removed packager CLI. Changed the logic we calculate the app name. - Fixed various OS-specific tests: stubbed `os`. - Restructured the final ZIP formats for Windows and Linux. - Added packager tests. - Switched from `@grpc/grpc-js` to native `grpc`. - Updated the version from 0.0.5 to 0.0.6. Signed-off-by: Akos Kitta <kittaakos@typefox.io>
208 lines
7.1 KiB
JavaScript
208 lines
7.1 KiB
JavaScript
//@ts-check
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const temp = require('temp');
|
|
const zip = require('7zip-min');
|
|
const shell = require('shelljs');
|
|
const depcheck = require('depcheck');
|
|
const fromFile = require('file-type').fromFile;
|
|
|
|
/**
|
|
* Returns with the version info for the artifact.
|
|
* If the `RELEASE_TAG` environment variable is set, we us that.
|
|
* Falls back to the commit SHA if the `RELEASE_TAG` is the `$(Release.Tag)` string.
|
|
* Otherwise, we concatenate the version of the extracted from `theia-app/electron-app/package.json`
|
|
* and append the short commit SHA.
|
|
*/
|
|
function versionInfo() {
|
|
if (typeof process.env.RELEASE_TAG === 'undefined' || !process.env.RELEASE_TAG || /* Azure -> */ process.env.RELEASE_TAG === '$(Release.Tag)') {
|
|
return {
|
|
version: `${targetVersion()}-${currentCommitish()}`,
|
|
release: false
|
|
}
|
|
} else {
|
|
return {
|
|
version: process.env.RELEASE_TAG,
|
|
release: true
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns with the absolute path of the `theia-app/electron-app/`.
|
|
*/
|
|
function arduinoExtensionPath() {
|
|
// TODO: be smarter and locate the extension with Git: `git rev-parse --show-toplevel`.
|
|
return path.join(__dirname, '..', '..', 'arduino-ide-extension');
|
|
}
|
|
|
|
/**
|
|
* The version extracted from the `package.json` of the `arduino-ide-extension`. Falls back to `x.x.x`.
|
|
*/
|
|
function targetVersion() {
|
|
return JSON.parse(fs.readFileSync(path.join(arduinoExtensionPath(), 'package.json'), { encoding: 'utf8' })).version || 'x.x.x';
|
|
}
|
|
|
|
/**
|
|
* Returns with the trimmed result of the `git rev-parse --short HEAD` as the current commitish if `git` is on the `PATH`.
|
|
* Otherwise, it returns with `DEV_BUILD`.
|
|
*/
|
|
function currentCommitish() {
|
|
try {
|
|
const gitPath = shell.which('git');
|
|
const error = shell.error();
|
|
if (error) {
|
|
throw new Error(error);
|
|
}
|
|
const { stderr, stdout } = shell.exec(`"${gitPath}" rev-parse --short HEAD`, { silent: true });
|
|
if (stderr) {
|
|
throw new Error(stderr.toString().trim());
|
|
}
|
|
return stdout.toString().trim();
|
|
} catch (e) {
|
|
return 'DEV_BUILD';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
});
|
|
})
|
|
}
|
|
|
|
/**
|
|
* `pathToZip` is a `path/to/your/app-name.zip`.
|
|
* If the `pathToZip` archive does not have a root directory with name `app-name`, it creates one, and move the content from the
|
|
* archive's root to the new root folder. If the archive already has the desired root folder, calling this function is a NOOP.
|
|
* 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}...`);
|
|
|
|
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();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
function unpack(what, where) {
|
|
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();
|
|
})
|
|
});
|
|
}
|
|
|
|
function list(what) {
|
|
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';
|
|
}
|
|
|
|
module.exports = { versionInfo, collectUnusedDependencies, adjustArchiveStructure, isZip, unpack };
|