build: use a local npm registry for app packaging

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
This commit is contained in:
Akos Kitta 2022-12-08 16:37:12 +01:00 committed by Akos Kitta
parent 908ec4c544
commit 1d342cdbd0
12 changed files with 4021 additions and 2287 deletions

View File

@ -15,7 +15,6 @@ module.exports = {
'.browser_modules/*', '.browser_modules/*',
'docs/*', 'docs/*',
'scripts/*', 'scripts/*',
'electron/*',
'electron-app/*', 'electron-app/*',
'plugins/*', 'plugins/*',
'arduino-ide-extension/src/node/cli-protocol', 'arduino-ide-extension/src/node/cli-protocol',

View File

@ -2,7 +2,7 @@ import * as React from '@theia/core/shared/react';
import { inject, injectable } from '@theia/core/shared/inversify'; import { inject, injectable } from '@theia/core/shared/inversify';
import { Widget } from '@theia/core/shared/@phosphor/widgets'; import { Widget } from '@theia/core/shared/@phosphor/widgets';
import { Message } from '@theia/core/shared/@phosphor/messaging'; import { Message } from '@theia/core/shared/@phosphor/messaging';
import { clipboard } from 'electron'; import { clipboard } from '@theia/core/electron-shared/@electron/remote';
import { ReactWidget, DialogProps } from '@theia/core/lib/browser'; import { ReactWidget, DialogProps } from '@theia/core/lib/browser';
import { AbstractDialog } from '../theia/dialogs/dialogs'; import { AbstractDialog } from '../theia/dialogs/dialogs';
import { CreateApi } from '../create/create-api'; import { CreateApi } from '../create/create-api';

View File

@ -9,7 +9,7 @@ import { CloudUserCommands } from '../../auth/cloud-user-commands';
import { NodeProps } from '@theia/core/lib/browser/tree/tree-widget'; import { NodeProps } from '@theia/core/lib/browser/tree/tree-widget';
import { TreeNode } from '@theia/core/lib/browser/tree'; import { TreeNode } from '@theia/core/lib/browser/tree';
import { CompositeTreeNode } from '@theia/core/lib/browser'; import { CompositeTreeNode } from '@theia/core/lib/browser';
import { shell } from 'electron'; import { shell } from '@theia/core/electron-shared/@electron/remote';
import { SketchbookTreeWidget } from '../sketchbook/sketchbook-tree-widget'; import { SketchbookTreeWidget } from '../sketchbook/sketchbook-tree-widget';
import { nls } from '@theia/core/lib/common'; import { nls } from '@theia/core/lib/common';

17
electron/.gitignore vendored
View File

@ -1,22 +1,15 @@
# The working-copy folder we use to package the application.
working-copy/
# Ignore all Theia generated things.
*.log
src-gen/
node_modules/
build/yarn.lock
webpack.config.js
lib/
# The electron-builder output. # The electron-builder output.
dist/ dist/
# `dotenv` can provide dynamic input for the elecrton-builder. e.g.: commitish for the build. # `dotenv` can provide dynamic input for the elecrton-builder. e.g.: commitish for the build.
electron-builder.env electron-builder.env
# The generated `package.json` under the `build` folder. # The generated `package.json` and lock file under the `build` folder.
build/package.json build/package.json
build/yarn.lock
# Resources the packager copies from dev to prod # Resources the packager copies from dev to prod
build/resources/preload.html build/resources/preload.html
# For the private npm package registry
packager/npm-registry/storage

View File

@ -21,7 +21,6 @@
"build": "yarn download:plugins && theia build --mode production && yarn patch", "build": "yarn download:plugins && theia build --mode production && yarn patch",
"rebuild": "yarn theia rebuild:electron", "rebuild": "yarn theia rebuild:electron",
"package": "cross-env DEBUG=* && electron-builder --publish=never", "package": "cross-env DEBUG=* && electron-builder --publish=never",
"package:publish": "cross-env DEBUG=* && electron-builder --publish=always",
"download:plugins": "theia download:plugins", "download:plugins": "theia download:plugins",
"patch": "ncp ./patch/backend/main.js ./src-gen/backend/main.js" "patch": "ncp ./patch/backend/main.js ./src-gen/backend/main.js"
}, },

View File

@ -1,9 +1,31 @@
//@ts-check //@ts-check
(async () => { (async () => {
const toDispose = [];
const disposeAll = () => {
let disposable = toDispose.pop();
while (disposable) {
try {
disposable();
} catch (err) {
console.error(err);
}
disposable = toDispose.pop();
}
};
process.on('uncaughtException', (error) => {
disposeAll();
throw error;
});
process.on('unhandledRejection', (reason) => {
disposeAll();
throw reason;
});
const fs = require('fs'); const fs = require('fs');
const join = require('path').join; const join = require('path').join;
const shell = require('shelljs'); const shell = require('shelljs');
const { echo, cp, mkdir, mv, rm } = shell;
shell.config.fatal = true;
const glob = require('glob'); const glob = require('glob');
const isCI = require('is-ci'); const isCI = require('is-ci');
// Note, this will crash on PI if the available memory is less than desired heap size. // Note, this will crash on PI if the available memory is less than desired heap size.
@ -21,175 +43,124 @@
echo(`📦 Building ${isRelease ? 'release ' : ''}version '${version}'...`); echo(`📦 Building ${isRelease ? 'release ' : ''}version '${version}'...`);
const workingCopy = 'working-copy'; const repoRoot = join(__dirname, '..', '..');
/** /**
* Relative path from the `__dirname` to the root where the `arduino-ide-extension` and the `electron-app` folders are. * Extensions are expected to be folders directly available from the repository root.
* This could come handy when moving the location of the `electron/packager`.
*/ */
const rootPath = join('..', '..');
// This is a HACK! We rename the root `node_modules` to something else. Otherwise, due to the hoisting,
// multiple Theia extensions will be picked up.
if (fs.existsSync(path(rootPath, 'node_modules'))) {
// We either do this or change the project structure.
echo(
"🔧 >>> [Hack] Renaming the root 'node_modules' folder to '.node_modules'..."
);
mv('-f', path(rootPath, 'node_modules'), path(rootPath, '.node_modules'));
echo(
"👌 <<< [Hack] Renamed the root 'node_modules' folder to '.node_modules'."
);
}
try {
//---------------------------+
// Clean the previous state. |
//---------------------------+
// rm -rf ../working-copy
rm('-rf', path('..', workingCopy));
// Clean up the `./electron/build` folder.
const resourcesToKeep = [
'patch',
'resources',
'scripts',
'template-package.json'
];
fs.readdirSync(path('..', 'build'))
.filter((filename) => resourcesToKeep.indexOf(filename) === -1)
.forEach((filename) => rm('-rf', path('..', 'build', filename)));
// Clean up the `./electron/build/resources` folder with Git.
// To avoid file duplication between bundled app and dev mode, some files are copied from `./electron-app` to `./electron/build` folder.
const foldersToSyncFromDev = ['resources'];
foldersToSyncFromDev.forEach((filename) =>
shell.exec(`git -C ${path('..', 'build', filename)} clean -ffxdq`, {
async: false,
})
);
const extensions = require('./extensions.json'); const extensions = require('./extensions.json');
echo( echo(
`Building the application with the following extensions:\n${extensions `Building the application with the following extensions:\n${extensions
.map((ext) => ` - ${ext}`) .map((ext) => ` - ${ext}`)
.join(',\n')}` .join(',\n')}`
); );
const allDependencies = [...extensions, 'electron-app'];
//----------------------------------------------------------------------------------------------+ try {
// Copy the following items into the `working-copy` folder. Make sure to reuse the `yarn.lock`. | //---------------------------+
//----------------------------------------------------------------------------------------------+ // Clean the previous state. |
mkdir('-p', path('..', workingCopy)); //---------------------------+
for (const filename of [ // Clean up the `./electron/build` folder.
...allDependencies, const resourcesToKeep = [
'yarn.lock', 'patch',
'package.json', 'resources',
'lerna.json', 'scripts',
'i18n', 'template-package.json',
]) { ];
cp('-rf', path(rootPath, filename), path('..', workingCopy)); fs.readdirSync(join(repoRoot, 'electron', 'build'))
.filter((filename) => resourcesToKeep.indexOf(filename) === -1)
.forEach((filename) =>
rm('-rf', join(repoRoot, 'electron', 'build', filename))
);
// Clean up the `./electron/build/resources` folder with Git.
// To avoid file duplication between bundled app and dev mode, some files are copied from `./electron-app` to `./electron/build` folder.
const foldersToSyncFromDev = ['resources'];
foldersToSyncFromDev.forEach((filename) =>
shell.exec(
`git -C ${join(repoRoot, 'electron', 'build', filename)} clean -ffxdq`,
{
async: false,
} }
)
);
//---------------------------------------------------------------------------------------------+ //----------------------------------------------------+
// Copy the patched `index.js` for the frontend, the Theia preload, etc. from `./electron-app` | // Copy the Theia preload, etc. from `./electron-app` |
//---------------------------------------------------------------------------------------------+ //----------------------------------------------------+
for (const filename of foldersToSyncFromDev) { for (const filename of foldersToSyncFromDev) {
cp( cp(
'-rf', '-rf',
path('..', workingCopy, 'electron-app', filename), join(repoRoot, 'electron-app', filename),
path('..', 'build') join(repoRoot, 'electron', 'build')
); );
} }
//----------------------------------------------+ //----------------------------------------------+
// Sanity check: all versions must be the same. | // Sanity check: all versions must be the same. |
//----------------------------------------------+ //----------------------------------------------+
verifyVersions(allDependencies); verifyVersions(extensions);
//----------------------------------------------------------------------+ //-------------------------------+
// Use the nightly patch version if not a release but requires publish. | // Build and test the extensions |
//----------------------------------------------------------------------+ //-------------------------------+
if (!isRelease) {
for (const dependency of allDependencies) {
const pkg = require(`../working-copy/${dependency}/package.json`);
pkg.version = version;
for (const dependency in pkg.dependencies) {
if (allDependencies.indexOf(dependency) !== -1) {
pkg.dependencies[dependency] = version;
}
}
fs.writeFileSync(
path('..', workingCopy, dependency, 'package.json'),
JSON.stringify(pkg, null, 2)
);
}
}
verifyVersions(allDependencies);
//---------------------------------------------------------------------------------------------------+
// Save some time: no need to build the projects that are not needed in final app. Currently unused. |
//---------------------------------------------------------------------------------------------------+
//@ts-ignore
const rootPackageJson = require('../working-copy/package.json');
const workspaces = rootPackageJson.workspaces;
// We cannot remove the `electron-app`. Otherwise, there is not way to collect the unused dependencies.
const dependenciesToRemove = [];
for (const dependencyToRemove of dependenciesToRemove) {
const index = workspaces.indexOf(dependencyToRemove);
if (index !== -1) {
workspaces.splice(index, 1);
}
}
rootPackageJson.workspaces = workspaces;
fs.writeFileSync(
path('..', workingCopy, 'package.json'),
JSON.stringify(rootPackageJson, null, 2)
);
//-------------------------------------------------------------------------------------------------+
// Rebuild the extension with the copied `yarn.lock`. It is a must to use the same Theia versions. |
//-------------------------------------------------------------------------------------------------+
exec(
`yarn --network-timeout 1000000 --cwd ${path('..', workingCopy)}`,
`Building the ${productName} application`
);
//-------------------------------------------------------------------------------------------------------------------------+
// Test the application. With this approach, we cannot publish test results to GH Actions but save 6-10 minutes per builds |
//-------------------------------------------------------------------------------------------------------------------------+
exec(
`yarn --network-timeout 1000000 --cwd ${path('..', workingCopy)} test`,
`Testing the ${productName} application`
);
//-------------------------------------------------------------------------------------------------------------+
// Change the regular NPM dependencies to `local-paths`, so that we can build them without any NPM registries. |
//-------------------------------------------------------------------------------------------------------------+
for (const extension of extensions) { for (const extension of extensions) {
if (extension !== 'arduino-ide-extension') { exec(
// Do not unlink self. `yarn --network-timeout 1000000 --cwd ${join(repoRoot, extension)}`,
// @ts-ignore `Building and testing ${extension}`
rootPackageJson = require(`../working-copy/${extension}/package.json`);
// @ts-ignore
rootPackageJson.dependencies['arduino-ide-extension'] =
'file:../arduino-ide-extension';
fs.writeFileSync(
path('..', workingCopy, extension, 'package.json'),
JSON.stringify(rootPackageJson, null, 2)
); );
} }
//------------------------+
// Publish the extensions |
//------------------------+
const npmrc = join(repoRoot, '.npmrc');
const storage = join(__dirname, 'npm-registry', 'storage');
rm('-rf', npmrc);
rm('-rf', storage);
// To avoid interactive npm login on the CI when publishing to the private registry.
// The actual token is fake and does not matter as the publishing is `$anonymous` anyway.
fs.writeFileSync(npmrc, '//localhost:4873/:_authToken=placeholder\n', {
encoding: 'utf8',
});
toDispose.push(() => rm('-rf', storage));
toDispose.push(() => rm('-rf', npmrc));
const npmProxyProcess = await startNpmRegistry(
join(__dirname, 'npm-registry', 'config.yml')
);
toDispose.push(() => {
if (!npmProxyProcess.killed) {
npmProxyProcess.kill();
}
});
for (const extension of extensions) {
const packageJsonPath = join(repoRoot, extension, 'package.json');
const versionToRestore = readJson(packageJsonPath).version;
exec(
`yarn --network-timeout 1000000 --cwd ${join(
repoRoot,
extension
)} publish --ignore-scripts --new-version ${version} --no-git-tag-version --registry http://localhost:4873`,
`Publishing ${extension}@${version} to the private npm registry`
);
// Publishing will change the version number, this should be reverted up after the build.
// A git checkout or reset could be easier, but this is safer to avoid wiping uncommitted dev state.
toDispose.push(() => {
const json = readJson(packageJsonPath);
json.version = versionToRestore;
writeJson(packageJsonPath, json);
});
} }
//------------------------------------------------------------------------------------+ //-----------------------------------------------------------------------------------------------------------+
// Merge the `working-copy/package.json` with `electron/build/template-package.json`. | // Merge the `./package.json` and `./electron-app/package.json` with `electron/build/template-package.json`. |
//------------------------------------------------------------------------------------+ //-----------------------------------------------------------------------------------------------------------+
// @ts-ignore const rootPackageJson = readJson(join(repoRoot, 'package.json'));
const appPackageJson = require('../working-copy/electron-app/package.json'); const appPackageJson = readJson(
join(repoRoot, 'electron-app', 'package.json')
);
const dependencies = {}; const dependencies = {};
for (const extension of extensions) { for (const extension of extensions) {
dependencies[extension] = `file:../working-copy/${extension}`; dependencies[extension] = version;
} }
// @ts-ignore
appPackageJson.dependencies = { appPackageJson.dependencies = {
...appPackageJson.dependencies, ...appPackageJson.dependencies,
...dependencies, ...dependencies,
@ -199,80 +170,115 @@
...template.devDependencies, ...template.devDependencies,
}; };
// Deep-merging the Theia application configuration. // Deep-merging the Theia application configuration.
// @ts-ignore
const theia = merge(appPackageJson.theia || {}, template.theia || {}); const theia = merge(appPackageJson.theia || {}, template.theia || {});
const content = { const content = {
...appPackageJson, ...appPackageJson,
...template, ...template,
theia, theia,
// @ts-ignore
dependencies: appPackageJson.dependencies, dependencies: appPackageJson.dependencies,
devDependencies: appPackageJson.devDependencies, devDependencies: appPackageJson.devDependencies,
// VS Code extensions and the plugins folder is defined in the top level `package.json`. The template picks them up. // VS Code extensions and the plugins folder is defined in the root `package.json`. The template picks them up.
theiaPluginsDir: rootPackageJson.theiaPluginsDir, theiaPluginsDir: rootPackageJson.theiaPluginsDir,
theiaPlugins: rootPackageJson.theiaPlugins, theiaPlugins: rootPackageJson.theiaPlugins,
}; };
fs.writeFileSync( writeJson(
path('..', 'build', 'package.json'), join(repoRoot, 'electron', 'build', 'package.json'),
JSON.stringify(
merge(content, template, { merge(content, template, {
arrayMerge: (_, sourceArray) => sourceArray, arrayMerge: (_, sourceArray) => sourceArray,
}), })
null,
2
)
); );
echo(`📜 Effective 'package.json' for the ${productName} application is: echo(`📜 Effective 'package.json' for the ${productName} application:
----------------------- -----------------------
${fs.readFileSync(path('..', 'build', 'package.json')).toString()} ${fs
.readFileSync(join(repoRoot, 'electron', 'build', 'package.json'))
.toString()}
----------------------- -----------------------
`); `);
// Make sure the original `yarn.lock` file is used from the electron application. // Make sure the original `yarn.lock` file is used from the electron application.
if (fs.existsSync(path('..', 'build', 'yarn.lock'))) { if (fs.existsSync(join(repoRoot, 'electron', 'build', 'yarn.lock'))) {
echo(`${path('..', 'build', 'yarn.lock')} must not exist.`); echo(
`${join(repoRoot, 'electron', 'build', 'yarn.lock')} must not exist.`
);
shell.exit(1); shell.exit(1);
} }
cp('-rf', path(rootPath, 'yarn.lock'), path('..', 'build')); cp('-rf', join(repoRoot, 'yarn.lock'), join(repoRoot, 'electron', 'build'));
if (!fs.existsSync(path('..', 'build', 'yarn.lock'))) { if (!fs.existsSync(join(repoRoot, 'electron', 'build', 'yarn.lock'))) {
echo(`${path('..', 'build', 'yarn.lock')} does not exist.`); echo(
`${join(repoRoot, 'electron', 'build', 'yarn.lock')} does not exist.`
);
shell.exit(1); shell.exit(1);
} }
// This is a HACK! We rename the root `node_modules` to something else. Otherwise, due to the hoisting,
// multiple Theia extensions will be picked up.
if (fs.existsSync(join(repoRoot, 'node_modules'))) {
// We either do this or change the project structure.
echo(
"🔧 >>> [Hack] Renaming the root 'node_modules' folder to '.node_modules'..."
);
mv('-f', join(repoRoot, 'node_modules'), join(repoRoot, '.node_modules'));
echo(
"👌 <<< [Hack] Renamed the root 'node_modules' folder to '.node_modules'."
);
}
toDispose.push(() => {
if (fs.existsSync(join(repoRoot, '.node_modules'))) {
echo(
"🔧 >>> [Restore] Renaming the root '.node_modules' folder to 'node_modules'..."
);
mv(
'-f',
join(repoRoot, '.node_modules'),
join(repoRoot, 'node_modules')
);
echo(
"👌 >>> [Restore] Renamed the root '.node_modules' folder to 'node_modules'."
);
}
});
//-------------------------------------------------------------------------------------------+ //-------------------------------------------------------------------------------------------+
// Install all private and public dependencies for the electron application and build Theia. | // Install all private and public dependencies for the electron application and build Theia. |
//-------------------------------------------------------------------------------------------+ //-------------------------------------------------------------------------------------------+
exec( exec(
`yarn --network-timeout 1000000 --cwd ${path('..', 'build')}`, `yarn --network-timeout 1000000 --cwd ${join(
repoRoot,
'electron',
'build'
)} --registry http://localhost:4873`,
'Installing dependencies' 'Installing dependencies'
); );
exec( exec(
`yarn --network-timeout 1000000 --cwd ${path('..', 'build')} build`, `yarn --cwd ${join(repoRoot, 'electron', 'build')} build`,
`Building the ${productName} application` `Building the ${productName} application`
); );
exec( exec(
`yarn --network-timeout 1000000 --cwd ${path('..', 'build')} rebuild`, `yarn --cwd ${join(repoRoot, 'electron', 'build')} rebuild`,
'Rebuild native dependencies' 'Rebuilding native dependencies'
); );
//------------------------------------------------------------------------------+ //------------------------------------------------------------------------------+
// Create a throw away dotenv file which we use to feed the builder with input. | // Create a throw away dotenv file which we use to feed the builder with input. |
//------------------------------------------------------------------------------+ //------------------------------------------------------------------------------+
const dotenv = 'electron-builder.env'; const dotenv = 'electron-builder.env';
if (fs.existsSync(path('..', 'build', dotenv))) { if (fs.existsSync(join(repoRoot, 'electron', 'build', dotenv))) {
rm('-rf', path('..', 'build', dotenv)); rm('-rf', join(repoRoot, 'electron', 'build', dotenv));
} }
// For the releases we use the desired tag as is defined by `$(Release.Tag)` from Azure. // For the releases we use the desired tag as is defined by `$(Release.Tag)` from Azure.
// For the preview builds we use the version from the `electron/build/package.json` with the short commit hash. // For the preview builds we use the version from the `electron/build/package.json` with the short commit hash.
fs.writeFileSync(path('..', 'build', dotenv), `ARDUINO_VERSION=${version}`); fs.writeFileSync(
join(repoRoot, 'electron', 'build', dotenv),
`ARDUINO_VERSION=${version}`
);
//-----------------------------------+ //-----------------------------------+
// Package the electron application. | // Package the electron application. |
//-----------------------------------+ //-----------------------------------+
exec( exec(
`yarn --network-timeout 1000000 --cwd ${path('..', 'build')} package`, `yarn --cwd ${join(repoRoot, 'electron', 'build')} package`,
`Packaging your ${productName} application` `Packaging the ${productName} application`
); );
//-----------------------------------------------------------------------------------------------------+ //-----------------------------------------------------------------------------------------------------+
@ -288,9 +294,16 @@ ${fs.readFileSync(path('..', 'build', 'package.json')).toString()}
shell.exit(1); shell.exit(1);
} }
} }
echo(`🎉 Success. Your application is at: ${path('..', 'build', 'dist')}`); echo(
`🎉 Success. The application is at: ${join(
repoRoot,
'electron',
'build',
'dist'
)}`
);
} finally { } finally {
restore(); disposeAll();
} }
//--------+ //--------+
@ -300,67 +313,24 @@ ${fs.readFileSync(path('..', 'build', 'package.json')).toString()}
if (toEcho) { if (toEcho) {
echo(`⏱️ >>> ${toEcho}...`); echo(`⏱️ >>> ${toEcho}...`);
} }
const { code, stderr, stdout } = shell.exec(command); const { stdout } = shell.exec(command);
if (code !== 0) {
echo(`🔥 Error when executing ${command} => ${stderr}`);
shell.exit(1);
}
if (toEcho) { if (toEcho) {
echo(`👌 <<< ${toEcho}.`); echo(`👌 <<< ${toEcho}.`);
} }
return stdout; return stdout;
} }
function cp(options, source, destination) {
shell.cp(options, source, destination);
assertNoError();
}
function rm(options, ...files) {
shell.rm(options, files);
assertNoError();
}
function mv(options, source, destination) {
shell.mv(options, source, destination);
assertNoError();
}
function mkdir(options, ...dir) {
shell.mkdir(options, dir);
assertNoError();
}
function echo(command) {
return shell.echo(command);
}
function assertNoError() {
const error = shell.error();
if (error) {
echo(error);
restore();
shell.exit(1);
}
}
function restore() {
if (fs.existsSync(path(rootPath, '.node_modules'))) {
echo(
"🔧 >>> [Restore] Renaming the root '.node_modules' folder to 'node_modules'..."
);
mv('-f', path(rootPath, '.node_modules'), path(rootPath, 'node_modules'));
echo(
"👌 >>> [Restore] Renamed the root '.node_modules' folder to 'node_modules'."
);
}
}
async function copyFilesToBuildArtifacts() { async function copyFilesToBuildArtifacts() {
echo(`🚢 Detected CI, moving build artifacts...`); echo(`🚢 Detected CI, moving build artifacts...`);
const { platform } = process; const { platform } = process;
const cwd = path('..', 'build', 'dist'); const cwd = join(repoRoot, 'electron', 'build', 'dist');
const targetFolder = path('..', 'build', 'dist', 'build-artifacts'); const targetFolder = join(
repoRoot,
'electron',
'build',
'dist',
'build-artifacts'
);
mkdir('-p', targetFolder); mkdir('-p', targetFolder);
const filesToCopy = []; const filesToCopy = [];
const channelFile = getChannelFile(platform); const channelFile = getChannelFile(platform);
@ -425,7 +395,7 @@ ${fs.readFileSync(path('..', 'build', 'package.json')).toString()}
async function recalculateArtifactsHash() { async function recalculateArtifactsHash() {
echo(`🚢 Detected CI, recalculating artifacts hash...`); echo(`🚢 Detected CI, recalculating artifacts hash...`);
const { platform } = process; const { platform } = process;
const cwd = path('..', 'build', 'dist'); const cwd = join(repoRoot, 'electron', 'build', 'dist');
const channelFilePath = join(cwd, getChannelFile(platform)); const channelFilePath = join(cwd, getChannelFile(platform));
const yaml = require('yaml'); const yaml = require('yaml');
@ -464,14 +434,14 @@ ${fs.readFileSync(path('..', 'build', 'package.json')).toString()}
* @param {BufferEncoding|undefined} [encoding="base64"] * @param {BufferEncoding|undefined} [encoding="base64"]
* @param {object|undefined} [options] * @param {object|undefined} [options]
*/ */
async function hashFile( function hashFile(
file, file,
algorithm = 'sha512', algorithm = 'sha512',
encoding = 'base64', encoding = 'base64',
options options
) { ) {
const crypto = require('crypto'); const crypto = require('crypto');
return await new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const hash = crypto.createHash(algorithm); const hash = crypto.createHash(algorithm);
hash.on('error', reject).setEncoding(encoding); hash.on('error', reject).setEncoding(encoding);
fs.createReadStream( fs.createReadStream(
@ -493,17 +463,14 @@ ${fs.readFileSync(path('..', 'build', 'package.json')).toString()}
} }
/** /**
* Joins tha path from `__dirname`. * @param {string[]} allDependencies
* @param {string} [expectedVersion]
*/ */
function path(...paths) {
return join(__dirname, ...paths);
}
function verifyVersions(allDependencies, expectedVersion) { function verifyVersions(allDependencies, expectedVersion) {
const versions = new Set(); const versions = new Set();
for (const dependency of allDependencies) { for (const dependency of allDependencies) {
versions.add( versions.add(
require(`../working-copy/${dependency}/package.json`).version readJson(join(repoRoot, dependency, 'package.json')).version
); );
} }
if (versions.size !== 1) { if (versions.size !== 1) {
@ -527,4 +494,41 @@ ${fs.readFileSync(path('..', 'build', 'package.json')).toString()}
} }
} }
} }
/**
* @param {string} configPath
* @return {Promise<import('child_process').ChildProcess>}
*/
function startNpmRegistry(configPath) {
return new Promise((resolve, reject) => {
const fork = require('child_process').fork(
require.resolve('verdaccio/bin/verdaccio'),
['-c', configPath]
);
fork.on('message', (msg) => {
if (typeof msg === 'object' && 'verdaccio_started' in msg) {
resolve(fork);
}
});
fork.on('error', reject);
fork.on('disconnect', reject);
});
}
/**
* @param {string} path
* @param {object} jsonObject
*/
function writeJson(path, jsonObject) {
fs.writeFileSync(path, JSON.stringify(jsonObject, null, 2) + '\n');
}
/**
* @param {string} path
* @return {object}
*/
function readJson(path) {
const raw = fs.readFileSync(path, { encoding: 'utf8' });
return JSON.parse(raw);
}
})(); })();

View File

@ -0,0 +1,19 @@
storage: ./storage
uplinks:
npmjs:
url: 'https://registry.npmjs.org/'
packages:
'**':
access: $all
publish: $anonymous
unpublish: $anonymous
proxy: npmjs
server:
keepAliveTimeout: 60
listen:
- 'localhost:4873'
max_body_size: 100mb
log:
type: stdout
format: pretty
level: warn

View File

@ -13,9 +13,9 @@
"author": "Arduino SA", "author": "Arduino SA",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"7zip-min": "^1.1.1",
"@types/file-type": "^10.9.1", "@types/file-type": "^10.9.1",
"@types/temp": "^0.8.32", "@types/temp": "^0.8.32",
"7zip-min": "^1.1.1",
"chai": "^4.2.0", "chai": "^4.2.0",
"crypto": "^1.0.1", "crypto": "^1.0.1",
"dateformat": "^3.0.3", "dateformat": "^3.0.3",
@ -28,6 +28,7 @@
"shelljs": "^0.8.3", "shelljs": "^0.8.3",
"sinon": "^9.0.1", "sinon": "^9.0.1",
"temp": "^0.9.1", "temp": "^0.9.1",
"verdaccio": "6-next",
"yaml": "^1.10.2", "yaml": "^1.10.2",
"yargs": "^12.0.5" "yargs": "^12.0.5"
}, },

View File

@ -7,13 +7,13 @@ const testMe = require('../utils');
const sinon = require('sinon'); const sinon = require('sinon');
describe('utils', () => { describe('utils', () => {
describe('adjustArchiveStructure', () => { describe('adjustArchiveStructure', () => {
let consoleStub; let consoleStub;
beforeEach(() => { beforeEach(() => {
consoleStub = sinon.stub(console, 'log').value(() => { }); consoleStub = sinon.stub(console, 'log').value(() => {
/* NOOP */
});
}); });
afterEach(() => { afterEach(() => {
@ -34,8 +34,15 @@ describe('utils', () => {
it('should reject when target directory does not exist', async () => { it('should reject when target directory does not exist', async () => {
try { try {
const zip = path.join(__dirname, 'resources', 'zip-with-base-folder.zip'); const zip = path.join(
await testMe.adjustArchiveStructure(zip, path.join(__dirname, 'some', 'missing', 'path')); __dirname,
'resources',
'zip-with-base-folder.zip'
);
await testMe.adjustArchiveStructure(
zip,
path.join(__dirname, 'some', 'missing', 'path')
);
throw new Error('Expected a rejection'); throw new Error('Expected a rejection');
} catch (e) { } catch (e) {
expect(e).to.be.an.instanceOf(Error); expect(e).to.be.an.instanceOf(Error);
@ -45,7 +52,11 @@ describe('utils', () => {
it('should reject when target is a file', async () => { it('should reject when target is a file', async () => {
try { try {
const zip = path.join(__dirname, 'resources', 'zip-with-base-folder.zip'); const zip = path.join(
__dirname,
'resources',
'zip-with-base-folder.zip'
);
await testMe.adjustArchiveStructure(zip, path.join(__filename)); await testMe.adjustArchiveStructure(zip, path.join(__filename));
throw new Error('Expected a rejection'); throw new Error('Expected a rejection');
} catch (e) { } catch (e) {
@ -56,7 +67,10 @@ describe('utils', () => {
it('should be a NOOP when the zip already has the desired base folder', async () => { it('should be a NOOP when the zip already has the desired base folder', async () => {
const zip = path.join(__dirname, 'resources', 'zip-with-base-folder.zip'); const zip = path.join(__dirname, 'resources', 'zip-with-base-folder.zip');
const actual = await testMe.adjustArchiveStructure(zip, track.mkdirSync()); const actual = await testMe.adjustArchiveStructure(
zip,
track.mkdirSync()
);
expect(actual).to.be.equal(zip); expect(actual).to.be.equal(zip);
}); });
@ -92,7 +106,13 @@ describe('utils', () => {
const verifyOut = track.mkdirSync(); const verifyOut = track.mkdirSync();
await unpack(actual, verifyOut); await unpack(actual, verifyOut);
expect(fs.lstatSync(path.join(verifyOut, 'zip-with-symlink', 'folder', 'symlinked-sub')).isSymbolicLink()).to.be.true; expect(
fs
.lstatSync(
path.join(verifyOut, 'zip-with-symlink', 'folder', 'symlinked-sub')
)
.isSymbolicLink()
).to.be.true;
}); });
it('should adjust the archive structure if base folder is not present', async () => { it('should adjust the archive structure if base folder is not present', async () => {
@ -113,7 +133,5 @@ describe('utils', () => {
expect(subs).to.have.lengthOf(3); expect(subs).to.have.lengthOf(3);
expect(subs.sort()).to.be.deep.equal(['a.txt', 'b.txt', 'foo']); expect(subs.sort()).to.be.deep.equal(['a.txt', 'b.txt', 'foo']);
}); });
}); });
}); });

File diff suppressed because it is too large Load Diff

View File

@ -25,7 +25,7 @@
"husky": "^6.0.0", "husky": "^6.0.0",
"ignore-styles": "^5.0.1", "ignore-styles": "^5.0.1",
"jsdom": "^11.5.1", "jsdom": "^11.5.1",
"lerna": "^3.20.2", "lerna": "^6.1.0",
"lint-staged": "^11.0.0", "lint-staged": "^11.0.0",
"node-gyp": "^9.3.0", "node-gyp": "^9.3.0",
"prettier": "^2.3.1", "prettier": "^2.3.1",
@ -38,8 +38,7 @@
}, },
"resolutions": { "resolutions": {
"@types/react": "18.0.0", "@types/react": "18.0.0",
"@types/react-dom": "18.0.0", "@types/react-dom": "18.0.0"
"electron-rebuild": "3.2.9"
}, },
"scripts": { "scripts": {
"prepare": "lerna run prepare && yarn download:plugins", "prepare": "lerna run prepare && yarn download:plugins",

3883
yarn.lock

File diff suppressed because it is too large Load Diff