mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-07-09 04:16:38 +00:00
build: use a local npm registry for app packaging
Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
This commit is contained in:
parent
908ec4c544
commit
1d342cdbd0
@ -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',
|
||||||
|
@ -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';
|
||||||
|
@ -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
17
electron/.gitignore
vendored
@ -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
|
||||||
|
@ -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"
|
||||||
},
|
},
|
||||||
|
@ -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('..', '..');
|
const extensions = require('./extensions.json');
|
||||||
|
echo(
|
||||||
// This is a HACK! We rename the root `node_modules` to something else. Otherwise, due to the hoisting,
|
`Building the application with the following extensions:\n${extensions
|
||||||
// multiple Theia extensions will be picked up.
|
.map((ext) => ` - ${ext}`)
|
||||||
if (fs.existsSync(path(rootPath, 'node_modules'))) {
|
.join(',\n')}`
|
||||||
// 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 {
|
try {
|
||||||
//---------------------------+
|
//---------------------------+
|
||||||
// Clean the previous state. |
|
// Clean the previous state. |
|
||||||
//---------------------------+
|
//---------------------------+
|
||||||
// rm -rf ../working-copy
|
|
||||||
rm('-rf', path('..', workingCopy));
|
|
||||||
// Clean up the `./electron/build` folder.
|
// Clean up the `./electron/build` folder.
|
||||||
const resourcesToKeep = [
|
const resourcesToKeep = [
|
||||||
'patch',
|
'patch',
|
||||||
'resources',
|
'resources',
|
||||||
'scripts',
|
'scripts',
|
||||||
'template-package.json'
|
'template-package.json',
|
||||||
];
|
];
|
||||||
fs.readdirSync(path('..', 'build'))
|
fs.readdirSync(join(repoRoot, 'electron', 'build'))
|
||||||
.filter((filename) => resourcesToKeep.indexOf(filename) === -1)
|
.filter((filename) => resourcesToKeep.indexOf(filename) === -1)
|
||||||
.forEach((filename) => rm('-rf', path('..', 'build', filename)));
|
.forEach((filename) =>
|
||||||
|
rm('-rf', join(repoRoot, 'electron', 'build', filename))
|
||||||
|
);
|
||||||
|
|
||||||
// Clean up the `./electron/build/resources` folder with Git.
|
// 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.
|
// To avoid file duplication between bundled app and dev mode, some files are copied from `./electron-app` to `./electron/build` folder.
|
||||||
const foldersToSyncFromDev = ['resources'];
|
const foldersToSyncFromDev = ['resources'];
|
||||||
foldersToSyncFromDev.forEach((filename) =>
|
foldersToSyncFromDev.forEach((filename) =>
|
||||||
shell.exec(`git -C ${path('..', 'build', filename)} clean -ffxdq`, {
|
shell.exec(
|
||||||
async: false,
|
`git -C ${join(repoRoot, 'electron', 'build', filename)} clean -ffxdq`,
|
||||||
})
|
{
|
||||||
|
async: false,
|
||||||
|
}
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const extensions = require('./extensions.json');
|
//----------------------------------------------------+
|
||||||
echo(
|
// Copy the Theia preload, etc. from `./electron-app` |
|
||||||
`Building the application with the following extensions:\n${extensions
|
//----------------------------------------------------+
|
||||||
.map((ext) => ` - ${ext}`)
|
|
||||||
.join(',\n')}`
|
|
||||||
);
|
|
||||||
const allDependencies = [...extensions, 'electron-app'];
|
|
||||||
|
|
||||||
//----------------------------------------------------------------------------------------------+
|
|
||||||
// Copy the following items into the `working-copy` folder. Make sure to reuse the `yarn.lock`. |
|
|
||||||
//----------------------------------------------------------------------------------------------+
|
|
||||||
mkdir('-p', path('..', workingCopy));
|
|
||||||
for (const filename of [
|
|
||||||
...allDependencies,
|
|
||||||
'yarn.lock',
|
|
||||||
'package.json',
|
|
||||||
'lerna.json',
|
|
||||||
'i18n',
|
|
||||||
]) {
|
|
||||||
cp('-rf', path(rootPath, filename), path('..', workingCopy));
|
|
||||||
}
|
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------+
|
|
||||||
// Copy the patched `index.js` for the frontend, 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)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------------+
|
//------------------------+
|
||||||
// Merge the `working-copy/package.json` with `electron/build/template-package.json`. |
|
// Publish the extensions |
|
||||||
//------------------------------------------------------------------------------------+
|
//------------------------+
|
||||||
// @ts-ignore
|
const npmrc = join(repoRoot, '.npmrc');
|
||||||
const appPackageJson = require('../working-copy/electron-app/package.json');
|
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 `./package.json` and `./electron-app/package.json` with `electron/build/template-package.json`. |
|
||||||
|
//-----------------------------------------------------------------------------------------------------------+
|
||||||
|
const rootPackageJson = readJson(join(repoRoot, '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);
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
|
19
electron/packager/npm-registry/config.yml
Normal file
19
electron/packager/npm-registry/config.yml
Normal 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
|
@ -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"
|
||||||
},
|
},
|
||||||
@ -40,4 +41,4 @@
|
|||||||
"watch-extensions": "js",
|
"watch-extensions": "js",
|
||||||
"timeout": 10000
|
"timeout": 10000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
@ -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",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user