diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 64299d9..6f7f0e0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [10.x, 12.x, 14.x, 16.x] + node-version: [12.x, 14.x, 16.x] steps: - uses: actions/checkout@v2 diff --git a/ci/snapcraft.yaml b/ci/snapcraft.yaml index 2bd27f0..af83217 100644 --- a/ci/snapcraft.yaml +++ b/ci/snapcraft.yaml @@ -1,17 +1,37 @@ -name: electron-app +name: electronApp version: '1.0.0' summary: App summary description: | App description -base: core18 + +base: core20 +confinement: strict grade: devel -confinement: devmode + +apps: + electronApp: + extensions: [gnome-3-34] + plugs: + - alsa + - browser-support + - desktop + - desktop-legacy + - gsettings + - home + - network + - opengl + - pulseaudio + - unity7 + - wayland + - x11 + environment: + DISABLE_WAYLAND: "1" + TMPDIR: $XDG_RUNTIME_DIR parts: - electron-deps: - plugin: nil - after: - - desktop-gtk3 + electronApp: + source: . + plugin: dump stage-packages: - libnotify4 - libnss3 @@ -19,3 +39,10 @@ parts: - libuuid1 - libxss1 - libxtst6 + + electron-launch: + plugin: dump + source: . + override-build: | + snapcraftctl build + chmod +x bin/electron-launch \ No newline at end of file diff --git a/package.json b/package.json index de4e8fa..0818257 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ }, "dependencies": { "@malept/cross-spawn-promise": "^1.0.0", - "debug": "^4.1.1", + "debug": "^4.3.4", "electron-installer-common": "^0.10.2", "fs-extra": "^9.0.0", "js-yaml": "^3.10.0", diff --git a/resources/classic-launcher.sh b/resources/classic/classic-launcher.sh similarity index 100% rename from resources/classic-launcher.sh rename to resources/classic/classic-launcher.sh diff --git a/resources/snapcraft.yaml b/resources/classic/snapcraft.yaml similarity index 87% rename from resources/snapcraft.yaml rename to resources/classic/snapcraft.yaml index 5dceacd..de17e2c 100644 --- a/resources/snapcraft.yaml +++ b/resources/classic/snapcraft.yaml @@ -5,7 +5,7 @@ description: | App description grade: devel -confinement: devmode +confinement: classic apps: electronApp: @@ -26,12 +26,13 @@ apps: parts: electronApp: source: . - plugin: dump + plugin: nil stage-packages: - libnotify4 - libnss3 - libpcre3 + - libuuid1 - libxss1 - libxtst6 after: - - desktop-gtk2 + - desktop-gtk3 diff --git a/resources/desktop-launcher.sh b/resources/desktop-launcher.sh new file mode 100644 index 0000000..65c91d6 --- /dev/null +++ b/resources/desktop-launcher.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +exec "$@" --executed-from="$(pwd)" --pid=$$ diff --git a/resources/strict/snapcraft.yaml b/resources/strict/snapcraft.yaml new file mode 100644 index 0000000..7eb9653 --- /dev/null +++ b/resources/strict/snapcraft.yaml @@ -0,0 +1,44 @@ +name: electronApp +version: '1.0.0' +summary: App summary +description: | + App description + +base: core18 +confinement: strict +grade: devel + +apps: + electronApp: + extensions: [gnome-3-34] + plugs: + - browser-support + - desktop + - desktop-legacy + - gsettings + - home + - network + - unity7 + - wayland + - x11 + environment: + DISABLE_WAYLAND: "1" + TMPDIR: $XDG_RUNTIME_DIR + +parts: + electronApp: + source: . + plugin: dump + stage-packages: + - libnotify4 + - libnss3 + - libpcre3 + - libxss1 + - libxtst6 + + electron-launch: + plugin: dump + source: . + override-build: | + snapcraftctl build + chmod +x bin/electron-launch \ No newline at end of file diff --git a/src/launcher.js b/src/launcher.js index 961bb36..da117d0 100644 --- a/src/launcher.js +++ b/src/launcher.js @@ -18,24 +18,33 @@ limitations under the License. const fs = require('fs-extra') const path = require('path') +function getBrowserSandboxFlag (data) { + if (data.apps) { + const plugs = data.apps[`${data.name}`].plugs + return plugs.includes('browser-sandbox') ? '' : '--no-sandbox' + } + return '' +} + async function copyLauncher (snapDir, config) { + const binDir = path.join(snapDir, 'bin') + let launcherPath = path.resolve(__dirname, '..', 'resources', 'desktop-launcher.sh') if (config.confinement === 'classic') { - const binDir = path.join(snapDir, 'bin') - const launcherPath = path.resolve(__dirname, '..', 'resources', 'classic-launcher.sh') - await fs.mkdirs(binDir) - await fs.copy(launcherPath, path.join(binDir, 'electron-launch')) + launcherPath = path.resolve(__dirname, '..', 'resources', 'classic', 'classic-launcher.sh') } + await fs.mkdirs(binDir) + await fs.copy(launcherPath, path.join(binDir, 'electron-launch')) } function createDesktopLaunchCommand (data) { - const executableName = data.executableName || data.productName + const executableName = data.executableName || data.productName || data.name delete data.executableName delete data.productName - const launcher = data.confinement === 'classic' ? 'bin/electron-launch' : 'desktop-launch' + const sandboxFlag = getBrowserSandboxFlag(data) - return `${launcher} '$SNAP/${data.name}/${executableName}'` + return `bin/electron-launch $SNAP/${data.name}/${executableName} ${sandboxFlag}` } module.exports = { diff --git a/src/snapcraft.js b/src/snapcraft.js index 118dc50..be64a19 100644 --- a/src/snapcraft.js +++ b/src/snapcraft.js @@ -55,12 +55,19 @@ class Snapcraft { const args = [command] for (const flag in options) { const value = options[flag] - if (value) { - args.push(`--${flag}=${value}`) - } else { - args.push(`--${flag}`) + if (flag !== 'target-arch') { + if (value) { + args.push(`--${flag}=${value}`) + } else { + args.push(`--${flag}`) + } } } + /* istanbul ignore if */ + if (debug.enabled) { + args.push('--destructive-mode') + args.push('--debug') + } if (extraArgs) { Array.prototype.push.apply(args, extraArgs) diff --git a/src/yaml.js b/src/yaml.js index 52c7824..a71ea23 100644 --- a/src/yaml.js +++ b/src/yaml.js @@ -201,7 +201,6 @@ class SnapcraftYAML { } updateDependencies () { - this.parts.after[0] = common.getGTKDepends(this.electronVersion, DEPENDENCY_MAP) this.parts['stage-packages'] = this.parts['stage-packages'] .concat(common.getATSPIDepends(this.electronVersion, DEPENDENCY_MAP)) .concat(common.getDRMDepends(this.electronVersion, DEPENDENCY_MAP)) @@ -210,6 +209,10 @@ class SnapcraftYAML { .concat(common.getUUIDDepends(this.electronVersion, DEPENDENCY_MAP)) .concat(common.getXcbDri3Depends(this.electronVersion, DEPENDENCY_MAP)) + if (this.data.confinement === 'classic') { + this.parts.after[0] = common.getGTKDepends(this.electronVersion, DEPENDENCY_MAP) + } + return this.data } @@ -255,7 +258,9 @@ class SnapcraftYAML { } module.exports = async function createYamlFromTemplate (snapDir, packageDir, userSupplied) { - const templateFilename = path.resolve(__dirname, '..', 'resources', 'snapcraft.yaml') + const templateFilename = (userSupplied.confinement && userSupplied.confinement === 'classic') + ? path.resolve(__dirname, '..', 'resources', 'classic', 'snapcraft.yaml') + : path.resolve(__dirname, '..', 'resources', 'strict', 'snapcraft.yaml') delete userSupplied.snapcraft const yamlData = new SnapcraftYAML() diff --git a/test/index.js b/test/index.js index 225af9e..4d9ea02 100644 --- a/test/index.js +++ b/test/index.js @@ -52,9 +52,7 @@ test('snap name has no letters', t => { t.throws(() => creator.sanitizeName('0-9'), { message: /needs to have at least one letter/ }) }) -// TODO: These are currently failing in CI, due to GH Actions not working with multipass. -// Configure this with a custom Docker image or LXD to get these working in CI again. -if (!process.env.FAST_TESTS_ONLY && !process.env.CI) { +if (!process.env.FAST_TESTS_ONLY) { test.serial('creates a snap', async t => { const snapPath = await snap({ src: path.join(__dirname, 'fixtures', 'app-with-asar') }) t.truthy(snapPath, 'snap returns a truthy value') diff --git a/test/launcher.js b/test/launcher.js index d67142f..38dfb52 100644 --- a/test/launcher.js +++ b/test/launcher.js @@ -24,14 +24,14 @@ require('./_util') test('desktop-launch command uses productName by default', t => { const command = launcher.createDesktopLaunchCommand({ name: 'app-name', productName: 'App Name' }) - t.true(command.startsWith('desktop-launch'), 'Command uses desktop-launch') - t.true(command.endsWith("/App Name'"), 'Command uses exe-name') + t.true(command.startsWith('bin/electron-launch'), 'Command uses electron-launch') + t.true(command.includes('App Name'), 'Command uses productName') }) test('desktop-launch command uses executableName if specified', t => { const command = launcher.createDesktopLaunchCommand({ name: 'app-name', productName: 'App Name', executableName: 'exe-name' }) - t.true(command.startsWith('desktop-launch'), 'Command uses desktop-launch') - t.true(command.endsWith("/exe-name'"), 'Command uses exe-name') + t.true(command.startsWith('bin/electron-launch'), 'Command uses electron-launch') + t.true(command.includes('exe-name'), 'Command uses exe-name') }) test('launcher is classic launcher in classic confinement', t => { @@ -39,9 +39,9 @@ test('launcher is classic launcher in classic confinement', t => { t.true(command.startsWith('bin/electron-launch'), 'Command uses electron-launch') }) -test('no custom launcher is copied to bin folder in non-classic confinement', async t => { +test('custom launcher is copied to bin folder in non-classic confinement', async t => { await launcher.copyLauncher(t.context.tempDir.name, { confinement: 'strict' }) - t.false(await fs.pathExists(path.join(t.context.tempDir.name, 'bin', 'electron-launch')), 'launcher does not exist') + t.true(await fs.pathExists(path.join(t.context.tempDir.name, 'bin', 'electron-launch')), 'launcher exists') }) test('custom launcher is copied to bin folder in classic confinement', async t => { diff --git a/test/snapcraft.js b/test/snapcraft.js index f80b6f9..f2ec5ff 100644 --- a/test/snapcraft.js +++ b/test/snapcraft.js @@ -15,6 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +const debug = require('debug')('electron-installer-snap:snapcraft') const Snapcraft = require('../src/snapcraft') const test = require('ava') @@ -33,5 +34,10 @@ test('generateArgs flags and options', t => { const snapcraft = new Snapcraft() const args = snapcraft.generateArgs('nonexistent', { a: 1, b: null }, ['foo', 'bar']) - t.deepEqual(args, ['nonexistent', '--a=1', '--b', 'foo', 'bar'], 'generated args') + // Note: --destructive-mode and --debug are enabled by default in CI + if (debug.enabled) { + t.deepEqual(args, ['nonexistent', '--a=1', '--b', '--destructive-mode', '--debug', 'foo', 'bar'], 'generated args') + } else { + t.deepEqual(args, ['nonexistent', '--a=1', '--b', 'foo', 'bar'], 'generated args') + } }) diff --git a/test/yaml.js b/test/yaml.js index 126acf3..2f0ea3a 100644 --- a/test/yaml.js +++ b/test/yaml.js @@ -115,13 +115,38 @@ test('custom app config', async t => { t.true(apps.electronAppName.daemon, 'daemon is set in app') }) -test('Electron < 2 apps use desktop-gtk2', async t => { - const { parts } = await createYaml(t, { name: 'electronAppName' }, '1.8.2') +test('strict confinement should apply by default', async t => { + const snapcraftYaml = await createYaml(t, { name: 'electronAppName' }) + t.is(snapcraftYaml.confinement, 'strict') +}) + +test('custom confinement config (devmode should apply correctly)', async t => { + const snapcraftYaml = await createYaml(t, { name: 'electronAppName', confinement: 'devmode' }) + t.is(snapcraftYaml.confinement, 'devmode') +}) + +test('custom confinement config (strict should apply correctly)', async t => { + const snapcraftYaml = await createYaml(t, { name: 'electronAppName', confinement: 'strict' }) + t.is(snapcraftYaml.confinement, 'strict') +}) + +test('custom confinement config (classic should apply correctly)', async t => { + const snapcraftYaml = await createYaml(t, { name: 'electronAppName', confinement: 'classic' }) + t.is(snapcraftYaml.confinement, 'classic') +}) + +test('use gnome extensions with strict confinement', async t => { + const { apps } = await createYaml(t, { name: 'electronAppName' }) + t.deepEqual(apps.electronAppName.extensions, ['gnome-3-34']) +}) + +test('Electron < 2 classic confinement apps use desktop-gtk2', async t => { + const { parts } = await createYaml(t, { name: 'electronAppName', confinement: 'classic' }, '1.8.2') t.deepEqual(parts.electronAppName.after, ['desktop-gtk2']) }) -test('Electron 2 apps use desktop-gtk3', async t => { - const { parts } = await createYaml(t, { name: 'electronAppName' }, '2.0.0-beta.1') +test('Electron 2 classic confinement apps use desktop-gtk3', async t => { + const { parts } = await createYaml(t, { name: 'electronAppName', confinement: 'classic' }, '2.0.0-beta.1') t.deepEqual(parts.electronAppName.after, ['desktop-gtk3']) })