[ci]: Made various changes for the electron app:

- 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>
This commit is contained in:
Akos Kitta 2020-03-23 11:49:48 +01:00
parent d54a69935e
commit 6ce4143d49
30 changed files with 2549 additions and 976 deletions

View File

@ -1,14 +1,14 @@
{
"name": "arduino-debugger-extension",
"version": "0.0.5",
"version": "0.0.6",
"description": "An extension for debugging Arduino programs",
"license": "MIT",
"engines": {
"node": ">=10.10.0"
"node": ">=10.11.0 <12"
},
"dependencies": {
"@theia/debug": "next",
"arduino-ide-extension": "0.0.5",
"arduino-ide-extension": "0.0.6",
"cdt-gdb-adapter": "^0.0.14",
"vscode-debugadapter": "^1.26.0",
"vscode-debugprotocol": "^1.26.0"

View File

@ -1,10 +1,10 @@
{
"name": "arduino-ide-extension",
"version": "0.0.5",
"version": "0.0.6",
"description": "An extension for Theia building the Arduino IDE",
"license": "MIT",
"engines": {
"node": ">=10.10.0"
"node": ">=10.11.0 <12"
},
"scripts": {
"prepare": "yarn download-cli && yarn generate-protocol && yarn download-ls && yarn run clean && yarn run build",
@ -19,7 +19,6 @@
"test:watch": "mocha --watch --watch-files lib \"./lib/test/**/*.test.js\""
},
"dependencies": {
"@grpc/grpc-js": "^0.6.18",
"@theia/application-package": "next",
"@theia/core": "next",
"@theia/cpp": "next",
@ -37,11 +36,12 @@
"@types/dateformat": "^3.0.1",
"@types/deepmerge": "^2.2.0",
"@types/glob": "^5.0.35",
"@types/google-protobuf": "^3.7.1",
"@types/google-protobuf": "^3.7.2",
"@types/js-yaml": "^3.12.2",
"@types/lodash.debounce": "^4.0.6",
"@types/ps-tree": "^1.1.0",
"@types/react-select": "^3.0.0",
"@types/sinon": "^7.5.2",
"@types/which": "^1.3.1",
"ajv": "^6.5.3",
"css-element-queries": "^1.2.0",
@ -49,7 +49,8 @@
"deepmerge": "^4.2.2",
"fuzzy": "^0.1.3",
"glob": "^7.1.6",
"google-protobuf": "^3.11.0",
"google-protobuf": "^3.11.4",
"grpc": "^1.24.2",
"lodash.debounce": "^4.0.8",
"js-yaml": "^3.13.1",
"p-queue": "^5.0.0",
@ -79,6 +80,7 @@
"ncp": "^2.0.0",
"protoc": "1.0.4",
"shelljs": "^0.8.3",
"sinon": "^9.0.1",
"temp": "^0.9.1",
"uuid": "^3.2.1",
"yargs": "^11.1.0"

View File

@ -86,8 +86,6 @@ ${protos.join(' ')}`).code !== 0) {
shell.exit(1);
}
const { patch } = require('./patch-grpc-js');
patch([out])
shell.echo('Done.');
})();

View File

@ -1,38 +0,0 @@
// Use `@grpc/grpc-js` instead of `grpc` at runtime.
// https://github.com/grpc/grpc-node/issues/624
// https://github.com/grpc/grpc-node/issues/931
const fs = require('fs');
const path = require('path');
module.exports.patch = function (roots = [path.join(__dirname, '..', 'src', 'node')]) {
console.info('🔧 <<< Patching code...');
patch(roots);
console.info('👌 <<< Done. The code has been patched.');
};
function patch(paths) {
for (const p of paths) {
const exist = fs.existsSync(p);
if (exist) {
const stat = fs.statSync(p);
if (stat.isDirectory()) {
console.info(`🔧 >>> Scanning code in ${p}...`);
patch(fs.readdirSync(p).map(name => path.join(p, name)));
} else {
let content = fs.readFileSync(p, { encoding: 'utf8' });
if (content.indexOf("require('grpc')") !== -1) {
console.info(`Updated require('grpc') to require('@grpc/grpc-js') in ${p}.`);
fs.writeFileSync(p, content.replace("require('grpc')", "require('@grpc/grpc-js')"));
}
content = fs.readFileSync(p, { encoding: 'utf8' });
if (content.indexOf('import * as grpc from "grpc"') !== -1) {
console.info(`Updated import * as grpc from "grpc" to import * as grpc from "@grpc/grpc-js" in ${p}.`);
fs.writeFileSync(p, content.replace('import * as grpc from "grpc"', 'import * as grpc from "@grpc/grpc-js"'));
}
}
} else {
console.warn(`${p} does not exist. Skipping.`);
}
}
}

View File

@ -1,6 +1,6 @@
import * as path from 'path';
import * as yaml from 'js-yaml';
import * as grpc from '@grpc/grpc-js';
import * as grpc from 'grpc';
import * as deepmerge from 'deepmerge';
import { injectable, inject, named } from 'inversify';
import URI from '@theia/core/lib/common/uri';

View File

@ -1,4 +1,4 @@
import * as grpc from '@grpc/grpc-js';
import * as grpc from 'grpc';
import { inject, injectable } from 'inversify';
import { ToolOutputServiceServer } from '../common/protocol';
import { GrpcClientProvider } from './grpc-client-provider';
@ -35,7 +35,7 @@ export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Cl
}
protected async createClient(port: string | number): Promise<CoreClientProvider.Client> {
const client = new ArduinoCoreClient(`localhost:${port}`, grpc.credentials.createInsecure());
const client = new ArduinoCoreClient(`localhost:${port}`, grpc.credentials.createInsecure(), this.channelOptions);
const initReq = new InitReq();
initReq.setLibraryManagerOnly(false);
const initResp = await new Promise<InitResp>(resolve => {

View File

@ -1,3 +1,4 @@
import * as grpc from 'grpc';
import { inject, injectable, postConstruct } from 'inversify';
import { ILogger } from '@theia/core/lib/common/logger';
import { MaybePromise } from '@theia/core/lib/common/types';
@ -68,4 +69,11 @@ export abstract class GrpcClientProvider<C> {
protected abstract close(client: C): void;
protected get channelOptions(): grpc.CallOptions {
return {
'grpc.max_send_message_length': 512 * 1024 * 1024,
'grpc.max_receive_message_length': 512 * 1024 * 1024
};
}
}

View File

@ -1,4 +1,4 @@
import * as grpc from '@grpc/grpc-js';
import * as grpc from 'grpc';
import { injectable } from 'inversify';
import { MonitorClient } from '../cli-protocol/monitor/monitor_grpc_pb';
import { GrpcClientProvider } from '../grpc-client-provider';
@ -7,7 +7,7 @@ import { GrpcClientProvider } from '../grpc-client-provider';
export class MonitorClientProvider extends GrpcClientProvider<MonitorClient> {
createClient(port: string | number): MonitorClient {
return new MonitorClient(`localhost:${port}`, grpc.credentials.createInsecure());
return new MonitorClient(`localhost:${port}`, grpc.credentials.createInsecure(), this.channelOptions);
}
close(client: MonitorClient): void {

View File

@ -1,4 +1,4 @@
import { ClientDuplexStream } from '@grpc/grpc-js';
import { ClientDuplexStream } from 'grpc';
import { TextDecoder, TextEncoder } from 'util';
import { injectable, inject, named } from 'inversify';
import { Struct } from 'google-protobuf/google/protobuf/struct_pb';

View File

@ -1,4 +1,6 @@
import { expect } from 'chai';
import * as sinon from 'sinon';
import * as os from '@theia/core/lib/common/os';
import { Container, injectable } from 'inversify';
import { Event } from '@theia/core/lib/common/event';
import { ILogger } from '@theia/core/lib/common/logger';
@ -26,18 +28,23 @@ describe('boards-service-client-impl', () => {
const guessed = AvailableBoard.State.guessed;
const incomplete = AvailableBoard.State.incomplete;
let stub: sinon.SinonStub;
let server: MockBoardsService;
let client: BoardsServiceClientImpl;
// let storage: MockStorageService;
beforeEach(() => {
stub = sinon.stub(os, 'isOSX').value(true);
const container = init();
server = container.get(MockBoardsService);
client = container.get(BoardsServiceClientImpl);
// storage = container.get(MockStorageService);
server.setClient(client);
});
afterEach(() => {
stub.reset();
});
it('should have no available boards by default', () => {
expect(client.availableBoards).to.have.length(0);
});

View File

@ -45,16 +45,9 @@ jobs:
RELEASE_TAG: $(Release.Tag)
condition: or(in(variables['Agent.OS'], 'Windows_NT'), in(variables['Build.Reason'], 'Manual', 'Schedule'))
displayName: Package
- bash: |
export ARDUINO_POC_NAME=$(./electron/packager/cli name)
echo "##vso[task.setvariable variable=ArduinoPoC.AppName]$ARDUINO_POC_NAME"
env:
RELEASE_TAG: $(Release.Tag)
condition: or(in(variables['Agent.OS'], 'Windows_NT'), in(variables['Build.Reason'], 'Manual', 'Schedule'))
displayName: '[Config] Use - ARDUINO_POC_NAME env'
- task: PublishBuildArtifacts@1
inputs:
pathtoPublish: electron/build/dist/$(ArduinoPoC.AppName)
pathtoPublish: electron/build/dist/build-artifacts
artifactName: 'Arduino Pro IDE - Applications'
condition: or(in(variables['Agent.OS'], 'Windows_NT'), in(variables['Build.Reason'], 'Manual', 'Schedule'))
displayName: Publish
@ -77,7 +70,7 @@ jobs:
assets: |
gh-release/Arduino Pro IDE - Applications/*.zip
gh-release/Arduino Pro IDE - Applications/*.dmg
gh-release/Arduino Pro IDE - Applications/*.tar.xz
gh-release/Arduino Pro IDE - Applications/*.AppImage
target: $(Build.SourceVersion)
action: Edit
tagSource: auto

View File

@ -1,7 +1,7 @@
{
"private": true,
"name": "browser-app",
"version": "0.0.5",
"version": "0.0.6",
"license": "MIT",
"dependencies": {
"@theia/core": "next",
@ -20,8 +20,8 @@
"@theia/process": "next",
"@theia/terminal": "next",
"@theia/workspace": "next",
"arduino-ide-extension": "0.0.5",
"arduino-debugger-extension": "0.0.5"
"arduino-ide-extension": "0.0.6",
"arduino-debugger-extension": "0.0.6"
},
"devDependencies": {
"@theia/cli": "next"

View File

@ -1,8 +1,9 @@
{
"private": true,
"name": "electron-app",
"version": "0.0.5",
"version": "0.0.6",
"license": "MIT",
"main": "src-gen/frontend/electron-main.js",
"dependencies": {
"@theia/core": "next",
"@theia/cpp": "next",
@ -21,8 +22,8 @@
"@theia/process": "next",
"@theia/terminal": "next",
"@theia/workspace": "next",
"arduino-ide-extension": "0.0.5",
"arduino-debugger-extension": "0.0.5"
"arduino-ide-extension": "0.0.6",
"arduino-debugger-extension": "0.0.6"
},
"devDependencies": {
"@theia/cli": "next"

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

View File

@ -1,6 +1,9 @@
const os = require('os');
const path = require('path');
// To be able to propagate the `process.versions.electron` to the backend main, so that we can load natives correctly.
process.env.THEIA_ELECTRON_VERSION = process.versions.electron;
process.env.THEIA_DEFAULT_PLUGINS = `local-dir:${path.resolve(__dirname, '..', 'plugins')}`;
process.env.THEIA_PLUGINS = [
process.env.THEIA_PLUGINS,

View File

@ -0,0 +1,57 @@
//@ts-check
// Patches the `src-gen/backend/main.js` so that the forked backend process has the `process.versions.electron` in the bundled electron app.
// https://github.com/eclipse-theia/theia/issues/7358#issue-583306096
const args = process.argv.slice(2);
if (!args.length) {
console.error(`Expected an argument pointing to the app folder. An app folder is where you have the package.json and src-gen folder.`);
process.exit(1);
}
if (args.length > 1) {
console.error(`Expected exactly one argument pointing to the app folder. Got multiple instead: ${JSON.stringify(args)}`);
process.exit(1);
}
const arg = args.shift();
if (!arg) {
console.error('App path was not specified.');
process.exit(1);
}
const fs = require('fs');
const path = require('path');
const appPath = path.resolve((path.isAbsolute(arg) ? path.join(process.cwd(), arg) : arg));
if (!fs.existsSync(appPath)) {
console.error(`${appPath} does not exist.`);
process.exit(1);
}
if (!fs.lstatSync(appPath).isDirectory()) {
console.error(`${appPath} is not a directory.`);
process.exit(1);
}
const patched = path.join(appPath, 'src-gen', 'backend', 'original-main.js');
if (fs.existsSync(patched)) {
console.error(`Already patched. ${patched} already exists.`);
process.exit(1);
}
const toPatch = path.join(appPath, 'src-gen', 'backend', 'main.js');
if (fs.existsSync(patched)) {
console.error(`Cannot patch. ${toPatch} does not exist.`);
process.exit(1);
}
console.log(`⏱️ >>> Patching ${toPatch}...`);
const originalContent = fs.readFileSync(toPatch, { encoding: 'utf8' });
const patchedContent = `if (typeof process.versions.electron === 'undefined' && typeof process.env.THEIA_ELECTRON_VERSION === 'string') {
process.versions.electron = process.env.THEIA_ELECTRON_VERSION;
}
require('./original-main');
`
fs.writeFileSync(patched, originalContent);
fs.writeFileSync(toPatch, patchedContent);
console.log(`👌 <<< Patched ${toPatch}. Original 'main.js' is now at ${patched}.`);

View File

@ -12,17 +12,17 @@
},
"devDependencies": {
"@theia/cli": "next",
"electron-builder": "^21.2.0"
"electron-builder": "^22.4.1"
},
"scripts": {
"build": "yarn download:plugins && theia build --mode development",
"build:release": "yarn download:plugins && theia build --mode development",
"build": "yarn download:plugins && theia build --mode development && yarn patch:main",
"build:release": "yarn download:plugins && theia build --mode production && yarn patch:main",
"package": "electron-builder --publish=never",
"package:preview": "electron-builder --dir",
"download:plugins": "theia download:plugins"
"download:plugins": "theia download:plugins",
"patch:main": "node ./scripts/patch-backend-main ."
},
"engines": {
"node": ">=10.10.0"
"node": ">=10.11.0 <12"
},
"repository": {
"type": "git",
@ -80,9 +80,17 @@
},
"linux": {
"target": [
"zip"
{
"target": "zip"
},
{
"target": "AppImage",
"arch": "armv7l"
}
],
"artifactName": "${productName}-${env.ARDUINO_VERSION}-${os}.${ext}"
"category": "Development",
"icon": "resources/icons",
"artifactName": "${productName}-${env.ARDUINO_VERSION}-${os}-${arch}.${ext}"
},
"dmg": {
"icon": "resources/icon.icns",

View File

@ -1,34 +0,0 @@
#!/usr/bin/env node
// @ts-check
const { versionInfo } = require('./utils');
const yargs = require('yargs');
(() => {
yargs
.command({
command: 'name',
describe: 'Returns with the application name we build. The name includes the full application name with the version, the platform and the file extension.',
handler: () => {
const { platform } = process;
let ext = undefined;
let os = undefined;
if (platform === 'darwin') {
ext = 'dmg';
os = 'mac';
} else if (platform === 'win32') {
ext = 'zip';
os = 'win';
} else if (platform === 'linux') {
ext = 'zip';
os = 'linux';
} else {
process.stderr.write(`Unexpected platform: ${platform}.`);
process.exit(1);
}
process.stdout.write(`Arduino Pro IDE-${versionInfo().version}-${os}.${ext}`);
process.exit(0);
}
})
.demandCommand(1)
.argv;
})();

View File

@ -5,6 +5,8 @@
const fs = require('fs');
const join = require('path').join;
const shell = require('shelljs');
const glob = require('glob');
const isCI = require('is-ci');
shell.env.THEIA_ELECTRON_SKIP_REPLACE_FFMPEG = '1'; // Do not run the ffmpeg validation for the packager.
shell.env.NODE_OPTIONS = '--max_old_space_size=4096'; // Increase heap size for the CI
const utils = require('./utils');
@ -72,13 +74,13 @@
// We have to do it before changing the dependencies to `local-path`.
const unusedDependencies = await utils.collectUnusedDependencies('../working-copy/electron-app/');
//-------------------------------------------------------------------------------------------------------------+
// Change the regular NPM dependencies to `local-paths`, so that we can build them without any NPM registries. |
//-------------------------------------------------------------------------------------------------------------+
// @ts-ignore
pkg = require('../working-copy/arduino-debugger-extension/package.json');
pkg.dependencies['arduino-ide-extension'] = 'file:../arduino-ide-extension';
fs.writeFileSync(path('..', workingCopy, 'arduino-debugger-extension', 'package.json'), JSON.stringify(pkg, null, 2));
//-------------------------------------------------------------------------------------------------------------+
// Change the regular NPM dependencies to `local-paths`, so that we can build them without any NPM registries. |
//-------------------------------------------------------------------------------------------------------------+
// @ts-ignore
pkg = require('../working-copy/arduino-debugger-extension/package.json');
pkg.dependencies['arduino-ide-extension'] = 'file:../arduino-ide-extension';
fs.writeFileSync(path('..', workingCopy, 'arduino-debugger-extension', 'package.json'), JSON.stringify(pkg, null, 2));
//------------------------------------------------------------------------------------+
// Merge the `working-copy/package.json` with `electron/build/template-package.json`. |
@ -138,6 +140,18 @@ ${fs.readFileSync(path('..', 'build', 'package.json')).toString()}
// Package the electron application. |
//-----------------------------------+
exec(`yarn --network-timeout 1000000 --cwd ${path('..', 'build')} package`, `Packaging your Arduino Pro IDE application`);
//-----------------------------------------------------------------------------------------------------+
// Copy to another folder. Azure does not support wildcard for `PublishBuildArtifacts@1.pathToPublish` |
//-----------------------------------------------------------------------------------------------------+
if (isCI) {
try {
await copyFilesToBuildArtifacts();
} catch (e) {
echo(JSON.stringify(e));
shell.exit(1);
}
}
echo(`🎉 Success. Your application is at: ${path('..', 'build', 'dist')}`);
restore();
@ -201,6 +215,47 @@ ${fs.readFileSync(path('..', 'build', 'package.json')).toString()}
}
}
async function copyFilesToBuildArtifacts() {
echo(`🚢 Detected CI, moving build artifacts...`);
const { platform } = process;
const cwd = path('..', 'build', 'dist');
const targetFolder = path('..', 'build', 'dist', 'build-artifacts');
mkdir('-p', targetFolder);
const filesToCopy = [];
switch (platform) {
case 'linux': {
filesToCopy.push(...glob.sync('**/Arduino Pro IDE*.{zip,AppImage}', { cwd }).map(p => join(cwd, p)));
break;
}
case 'win32': {
filesToCopy.push(...glob.sync('**/Arduino Pro IDE*.zip', { cwd }).map(p => join(cwd, p)));
break;
}
case 'darwin': {
filesToCopy.push(...glob.sync('**/Arduino Pro IDE*.dmg', { cwd }).map(p => join(cwd, p)));
break;
}
default: {
echo(`Unsupported platform: ${platform}.`);
shell.exit(1);
}
}
if (!filesToCopy.length) {
echo(`Could not collect any build artifacts from ${cwd}.`);
shell.exit(1);
}
for (const fileToCopy of filesToCopy) {
echo(`🚢 >>> Copying ${fileToCopy} to ${targetFolder}.`);
const isZip = await utils.isZip(fileToCopy);
if (isZip) {
await utils.adjustArchiveStructure(fileToCopy, targetFolder);
} else {
cp('-rf', fileToCopy, targetFolder);
}
echo(`👌 >>> Copied ${fileToCopy} to ${targetFolder}.`);
}
}
/**
* Joins tha path from `__dirname`.
*/

View File

@ -5,19 +5,36 @@
"description": "Packager for the Arduino Pro IDE electron application",
"main": "index.js",
"scripts": {
"prepare": "yarn test",
"package": "node index.js",
"cli": "./cli"
"test": "mocha \"./test/**/*.test.js\""
},
"keywords": [],
"author": "Arduino SA",
"license": "MIT",
"dependencies": {
"deepmerge": "4.2.2",
"depcheck": "^0.7.1",
"@types/file-type": "^10.9.1",
"@types/temp": "^0.8.32",
"7zip-min": "^1.1.1",
"chai": "^4.2.0",
"deepmerge": "^4.2.2",
"depcheck": "^0.9.2",
"file-type": "^14.1.4",
"glob": "^7.1.6",
"is-ci": "^2.0.0",
"mocha": "^7.1.1",
"sinon": "^9.0.1",
"shelljs": "^0.8.3",
"temp": "^0.9.1",
"yargs": "^12.0.5"
},
"engines": {
"node": ">=8.12.0"
"node": ">=10.11.0 <12"
},
"mocha": {
"reporter": "spec",
"colors": true,
"watch-extensions": "js",
"timeout": 10000
}
}

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,119 @@
const fs = require('fs');
const path = require('path');
const expect = require('chai').expect;
const track = require('temp').track();
const unpack = require('../utils').unpack;
const testMe = require('../utils');
const sinon = require('sinon');
describe('utils', () => {
describe('adjustArchiveStructure', () => {
let consoleStub;
beforeEach(() => {
consoleStub = sinon.stub(console, 'log').value(() => { });
});
afterEach(() => {
consoleStub.reset();
track.cleanupSync();
});
it('should reject when not a zip file', async () => {
try {
const invalid = path.join(__dirname, 'resources', 'not-a-zip.dmg');
await testMe.adjustArchiveStructure(invalid, track.mkdirSync());
throw new Error('Expected a rejection');
} catch (e) {
expect(e).to.be.an.instanceOf(Error);
expect(e.message).to.be.equal('Expected a ZIP file.');
}
});
it('should reject when target directory does not exist', async () => {
try {
const zip = path.join(__dirname, 'resources', 'zip-with-base-folder.zip');
await testMe.adjustArchiveStructure(zip, path.join(__dirname, 'some', 'missing', 'path'));
throw new Error('Expected a rejection');
} catch (e) {
expect(e).to.be.an.instanceOf(Error);
expect(e.message.endsWith('does not exist.')).to.be.true;
}
});
it('should reject when target is a file', async () => {
try {
const zip = path.join(__dirname, 'resources', 'zip-with-base-folder.zip');
await testMe.adjustArchiveStructure(zip, path.join(__filename));
throw new Error('Expected a rejection');
} catch (e) {
expect(e).to.be.an.instanceOf(Error);
expect(e.message.endsWith('is not a directory.')).to.be.true;
}
});
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 actual = await testMe.adjustArchiveStructure(zip, track.mkdirSync());
expect(actual).to.be.equal(zip);
});
it('should handle whitespace in file path gracefully', async () => {
const zip = path.join(__dirname, 'resources', 'zip with whitespace.zip');
const out = track.mkdirSync();
const actual = await testMe.adjustArchiveStructure(zip, out, true);
expect(actual).to.be.equal(path.join(out, 'zip with whitespace.zip'));
console.log(actual);
expect(fs.existsSync(actual)).to.be.true;
const verifyOut = track.mkdirSync();
await unpack(actual, verifyOut);
const root = path.join(verifyOut, 'zip with whitespace');
expect(fs.existsSync(root)).to.be.true;
expect(fs.lstatSync(root).isDirectory()).to.be.true;
const subs = fs.readdirSync(root);
expect(subs).to.have.lengthOf(3);
expect(subs.sort()).to.be.deep.equal(['a.txt', 'b.txt', 'foo']);
});
it('should keep the symlinks after ZIP adjustments', async function () {
if (process.platform === 'win32') {
this.skip();
}
const zip = path.join(__dirname, 'resources', 'zip-with-symlink.zip');
const out = track.mkdirSync();
const actual = await testMe.adjustArchiveStructure(zip, out, true);
expect(actual).to.be.equal(path.join(out, 'zip-with-symlink.zip'));
console.log(actual);
expect(fs.existsSync(actual)).to.be.true;
const verifyOut = track.mkdirSync();
await unpack(actual, verifyOut);
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 () => {
const zip = path.join(__dirname, 'resources', 'zip-without-symlink.zip');
const out = track.mkdirSync();
const actual = await testMe.adjustArchiveStructure(zip, out, true);
expect(actual).to.be.equal(path.join(out, 'zip-without-symlink.zip'));
console.log(actual);
expect(fs.existsSync(actual)).to.be.true;
const verifyOut = track.mkdirSync();
await unpack(actual, verifyOut);
const root = path.join(verifyOut, 'zip-without-symlink');
expect(fs.existsSync(root)).to.be.true;
expect(fs.lstatSync(root).isDirectory()).to.be.true;
const subs = fs.readdirSync(root);
expect(subs).to.have.lengthOf(3);
expect(subs.sort()).to.be.deep.equal(['a.txt', 'b.txt', 'foo']);
});
});
});

View File

@ -2,8 +2,11 @@
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.
@ -67,7 +70,7 @@ function currentCommitish() {
*/
function collectUnusedDependencies(pathToProject = process.cwd()) {
const p = path.isAbsolute(pathToProject) ? pathToProject : path.resolve(process.cwd(), pathToProject);
console.log(`⏱️ >>> Collecting unused backend dependencies for ${p}.`);
console.log(`⏱️ >>> Collecting unused backend dependencies for ${p}...`);
return new Promise(resolve => {
depcheck(p, {
ignoreDirs: [
@ -97,4 +100,108 @@ function collectUnusedDependencies(pathToProject = process.cwd()) {
})
}
module.exports = { versionInfo, collectUnusedDependencies };
/**
* `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 };

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "arduino-editor",
"version": "0.0.5",
"version": "0.0.6",
"description": "Arduino Pro IDE",
"repository": "https://github.com/bcmi-labs/arduino-editor.git",
"author": "Arduino SA",
@ -16,7 +16,7 @@
"scripts": {
"prepare": "lerna run prepare && yarn test && yarn download:plugins",
"rebuild:browser": "theia rebuild:browser",
"rebuild:electron": "theia rebuild:electron",
"rebuild:electron": "theia rebuild:electron --modules \"@theia/node-pty\" nsfw native-keymap find-git-repositories grpc",
"start": "yarn --cwd ./browser-app start",
"watch": "lerna run watch --parallel",
"test": "lerna run test",

1923
yarn.lock

File diff suppressed because it is too large Load Diff