Skip to content

Commit 3801aaf

Browse files
author
Ben Demboski
committed
Fix packaging/making with npm/yarn
The code was running 'npm prune', but that doesn't do anything if no dependencies are already installed. What we really want is 'npm install --production' and I also added '--no-bin-links' to match the yarn behavior. Additionally, we weren't using the same logic as ember to decide when to use yarn vs. npm. We would use yarn anytime it was present on the system, without checking to see if the project we are running in has a yarn.lock file. So, now we run the same logic to decide which to use, and set an environment variable to instruct electron-forge to match us. Also, added end-to-end unit tests running with npm in addition to the ones running with yarn. Fixes #308, #309, #312, #313
1 parent b50110c commit 3801aaf

11 files changed

Lines changed: 280 additions & 64 deletions

File tree

.appveyor.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ cache:
88

99
install:
1010
- ps: Install-Product node $env:nodejs_version $env:platform
11+
- npm install yarn -g
1112
- yarn
1213

1314
test_script:

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ cache:
2121
yarn: true
2222

2323
before_install:
24-
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 0.27.5
24+
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.1.0
2525
- export PATH=$HOME/.yarn/bin:$PATH
2626
- yarn config set no-progress
2727

blueprints/ember-electron/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const { all, denodeify } = require('rsvp');
44

55
const Blueprint = require('ember-cli/lib/models/blueprint');
66
const efImport = require('electron-forge/dist/api/import').default;
7+
const { setupForgeEnv } = require('../../lib/utils/yarn-or-npm');
78

89
const Logger = require('../../lib/utils/logger');
910

@@ -53,6 +54,7 @@ module.exports = class EmberElectronBlueprint extends Blueprint {
5354
_installElectronTooling(logger) {
5455
// n.b. addPackageToProject does not let us save prod deps, so we task
5556
let npmInstall = this.taskFor('npm-install');
57+
setupForgeEnv(this.project.root);
5658

5759
logger.startProgress('Installing electron build tools');
5860

lib/tasks/assemble.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
const chalk = require('chalk');
44
const execa = require('execa');
5-
const { hasYarn } = require('yarn-or-npm');
65
const Task = require('ember-cli/lib/models/task');
76
const BuildTask = require('./build');
87
const Assembler = require('../models/assembler');
8+
const { shouldUseYarn } = require('../utils/yarn-or-npm');
99

1010
//
1111
// A task for assembling an electron forge-compatible project by combining the
@@ -64,10 +64,10 @@ class AssembleTask extends Task {
6464
}
6565

6666
pruneCommand() {
67-
if (hasYarn()) {
67+
if (shouldUseYarn(this.project.root)) {
6868
return 'yarn install --production --no-bin-links';
6969
} else {
70-
return 'npm prune --production';
70+
return 'npm install --production --no-bin-links';
7171
}
7272
}
7373

lib/tasks/package.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const { resolve } = require('rsvp');
66
const quickTemp = require('quick-temp');
77
const Task = require('ember-cli/lib/models/task');
88
const AssembleTask = require('./assemble');
9+
const { setupForgeEnv } = require('../utils/yarn-or-npm');
910
const forgePackage = require('electron-forge/dist/api/package').default;
1011

1112
//
@@ -37,6 +38,7 @@ class PackageTask extends Task {
3738
}
3839

3940
return assemblePromise.then(() => {
41+
setupForgeEnv(this.project.root);
4042
ui.writeLine(chalk.green('Packaging Electron project.'));
4143

4244
let options = {

lib/utils/yarn-or-npm.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
const path = require('path');
2+
const { existsSync } = require('fs');
3+
const { hasYarn } = require('yarn-or-npm');
4+
5+
//
6+
// This function mimics ember-cli's logic for deciding when to use yarn vs. npm
7+
//
8+
function shouldUseYarn(projectRoot) {
9+
return existsSync(path.join(projectRoot, 'yarn.lock')) && hasYarn();
10+
}
11+
12+
//
13+
// This function sets an environment variable that forces electron-forge to use
14+
// either yarn or npm, according to what shouldUseYarn() says.
15+
//
16+
function setupForgeEnv(projectRoot) {
17+
process.env.NODE_INSTALLER = shouldUseYarn(projectRoot) ? 'yarn' : 'npm';
18+
}
19+
20+
module.exports = {
21+
shouldUseYarn,
22+
setupForgeEnv,
23+
};

node-tests/acceptance/end-to-end-test.js

Lines changed: 97 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -22,105 +22,143 @@ function run(cmd, args, opts = {}) {
2222
}
2323

2424
describe('end-to-end', function() {
25-
this.timeout(5 * 60 * 1000);
25+
this.timeout(10 * 60 * 1000);
2626

27+
let oldEnv;
2728
let rootDir = process.cwd();
2829
let emberPath = path.join(rootDir, 'node_modules', '.bin', 'ember');
30+
let { name: packageTmpDir } = tmp.dirSync();
2931

3032
function ember(...args) {
3133
return listenForPrompts(run(emberPath, args, {
3234
stdio: ['pipe', 'pipe', process.stderr],
3335
}));
3436
}
3537

36-
before(function() {
37-
this.timeout(10 * 60 * 1000);
38-
39-
let { name: tmpDir } = tmp.dirSync();
38+
before(() => {
39+
// If we're running via a yarn script like `yarn test`, then we'll have
40+
// a whole bunch of npm_* environment variables set by yarn that can mess
41+
// things up, so let's scrub them.
42+
oldEnv = Object.assign({}, process.env);
43+
Object.keys(process.env).forEach((key) => {
44+
if (key.startsWith('npm_')) {
45+
delete process.env[key];
46+
}
47+
});
4048

41-
return run('yarn', ['pack', '--filename', path.join(tmpDir, 'ember-electron.tgz')]).then(() => {
42-
process.chdir(tmpDir);
49+
//
50+
// Pack up current ember-electron directory so it can be installed in new
51+
// ember projects.
52+
//
53+
return run('yarn', ['pack', '--filename', path.join(packageTmpDir, 'ember-electron.tgz')]).then(() => {
54+
process.chdir(packageTmpDir);
4355

44-
// yarn won't install from a gzipped tarball, and try as I might I can't
45-
// get node-tar or tar.gz to untar the tarballs created by yarn or npm
4656
return run('tar', ['-xzf', 'ember-electron.tgz']);
4757
}).then(() => {
4858
// Prevent yarn caching from screwing us
4959
let packageJson = readJsonSync(path.join('package', 'package.json'));
50-
packageJson.version = `packageJson.version-${new Date().getTime()}`;
60+
packageJson.version = `${packageJson.version}-${new Date().getTime()}`;
5161
writeJsonSync(path.join('package', 'package.json'), packageJson);
52-
53-
return ember('new', 'ee-test-app', '--yarn');
54-
}).then(() => {
55-
process.chdir('ee-test-app');
56-
57-
return ember('install', `ember-electron@file:${tmpDir}/package`);
58-
}).then(() => {
59-
// yarn has some kind of bug that causes it to hoist the wrong version of
60-
// npmlog during one of the operations performed by the ember-electron
61-
// blueprint, which causes things to explode. Running yarn upgrade
62-
// corrects the situation.
63-
return run('yarn', ['upgrade']);
6462
});
6563
});
6664

6765
after(() => {
68-
process.chdir(rootDir);
66+
process.env = oldEnv;
6967
});
7068

7169
afterEach(() => {
7270
removeSync('electron-out');
7371
});
7472

75-
it('tests', () => {
76-
return expect(ember('electron:test')).to.eventually.be.fulfilled;
77-
});
73+
describe('with yarn', function() {
74+
before(function() {
75+
let { name: tmpDir } = tmp.dirSync();
76+
process.chdir(tmpDir);
77+
78+
return ember('new', 'ee-test-app', '--yarn').then(() => {
79+
process.chdir('ee-test-app');
7880

79-
it('builds', () => {
80-
return ember('electron:build').then(() => {
81-
expect(existsSync(path.join('electron-out', 'ember'))).to.be.ok;
81+
return ember('install', `ember-electron@${path.join(packageTmpDir, 'package')}`);
82+
});
8283
});
83-
});
8484

85-
it('assembles', () => {
86-
return ember('electron:assemble').then(() => {
87-
expect(existsSync(path.join('electron-out', 'project'))).to.be.ok;
85+
after(() => {
86+
process.chdir(rootDir);
8887
});
88+
89+
runTests();
8990
});
9091

91-
it('packages', () => {
92-
return ember('electron:package').then(() => {
93-
expect(existsSync(path.join('electron-out', `ee-test-app-${process.platform}-${process.arch}`))).to.be.ok;
92+
describe('with npm', function() {
93+
before(function() {
94+
let { name: tmpDir } = tmp.dirSync();
95+
process.chdir(tmpDir);
96+
97+
return ember('new', 'ee-test-app').then(() => {
98+
process.chdir('ee-test-app');
99+
100+
return ember('install', `ember-electron@${path.join(packageTmpDir, 'package')}`);
101+
});
94102
});
95-
});
96103

97-
it('makes', () => {
98-
// Only build zip target so we don't fail from missing platform dependencies
99-
// (e.g. rpmbuild)
100-
return ember('electron:make', '--targets', 'zip').then(() => {
101-
expect(existsSync(path.join('electron-out', 'make'))).to.be.ok;
104+
after(() => {
105+
process.chdir(rootDir);
102106
});
107+
108+
runTests();
103109
});
104110

105-
it('extra checks pass', () => {
106-
let fixturePath = path.resolve(__dirname, '..', 'fixtures', 'ember-test');
107-
108-
// Append our extra test content to the end of test-main.js
109-
let testMainPath = path.join('ember-electron', 'test-main.js');
110-
let extraContentPath = path.join(fixturePath, 'test-main-extra.js');
111-
let content = [
112-
readFileSync(testMainPath),
113-
readFileSync(extraContentPath),
114-
].join('\n');
115-
writeFileSync(path.join('ember-electron', 'test-main.js'), content);
116-
117-
// Copy the lib and resources directories over
118-
['lib', 'resources'].forEach((dir) => {
119-
copySync(path.join(fixturePath, dir), path.join('ember-electron', dir));
111+
function runTests() {
112+
it('tests', () => {
113+
return expect(ember('electron:test')).to.eventually.be.fulfilled;
120114
});
121115

122-
return expect(ember('electron:test')).to.eventually.be.fulfilled;
123-
});
116+
it('builds', () => {
117+
return ember('electron:build').then(() => {
118+
expect(existsSync(path.join('electron-out', 'ember'))).to.be.ok;
119+
});
120+
});
121+
122+
it('assembles', () => {
123+
return ember('electron:assemble').then(() => {
124+
expect(existsSync(path.join('electron-out', 'project'))).to.be.ok;
125+
});
126+
});
127+
128+
it('packages', () => {
129+
return ember('electron:package').then(() => {
130+
expect(existsSync(path.join('electron-out', `ee-test-app-${process.platform}-${process.arch}`))).to.be.ok;
131+
});
132+
});
133+
134+
it('makes', () => {
135+
// Only build zip target so we don't fail from missing platform dependencies
136+
// (e.g. rpmbuild)
137+
return ember('electron:make', '--targets', 'zip').then(() => {
138+
expect(existsSync(path.join('electron-out', 'make'))).to.be.ok;
139+
});
140+
});
141+
142+
it('extra checks pass', () => {
143+
let fixturePath = path.resolve(__dirname, '..', 'fixtures', 'ember-test');
144+
145+
// Append our extra test content to the end of test-main.js
146+
let testMainPath = path.join('ember-electron', 'test-main.js');
147+
let extraContentPath = path.join(fixturePath, 'test-main-extra.js');
148+
let content = [
149+
readFileSync(testMainPath),
150+
readFileSync(extraContentPath),
151+
].join('\n');
152+
writeFileSync(path.join('ember-electron', 'test-main.js'), content);
153+
154+
// Copy the lib and resources directories over
155+
['lib', 'resources'].forEach((dir) => {
156+
copySync(path.join(fixturePath, dir), path.join('ember-electron', dir));
157+
});
158+
159+
return expect(ember('electron:test')).to.eventually.be.fulfilled;
160+
});
161+
}
124162
});
125163

126164
function listenForPrompts(child) {

node-tests/unit/tasks/assemble-test.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ describe('AssembleTask', () => {
1919
let assemblerOptions;
2020
let assemblerFail;
2121

22+
let useYarn;
23+
24+
let installCommand;
2225
let installOptions;
2326

2427
let task;
@@ -56,6 +59,7 @@ describe('AssembleTask', () => {
5659

5760
function mockInstall(command, args, opts) {
5861
operations.push('install-dependencies');
62+
installCommand = command;
5963
installOptions = opts;
6064

6165
return resolve();
@@ -83,6 +87,7 @@ describe('AssembleTask', () => {
8387

8488
mockery.registerMock('./build', MockBuildTask);
8589
mockery.registerMock('../models/assembler', MockAssembler);
90+
mockery.registerMock('../utils/yarn-or-npm', { shouldUseYarn: () => useYarn });
8691
mockery.registerMock('execa', mockInstall);
8792

8893
const AssembleTask = require('../../../lib/tasks/assemble');
@@ -182,6 +187,28 @@ describe('AssembleTask', () => {
182187
});
183188
});
184189

190+
it('should use yarn when appropriate', () => {
191+
let options = {
192+
outputPath: 'output',
193+
};
194+
useYarn = true;
195+
196+
expect(task.run(options)).to.be.rejected.then(() => {
197+
expect(installCommand).to.equal('yarn');
198+
});
199+
});
200+
201+
it('should use npm when appropriate', () => {
202+
let options = {
203+
outputPath: 'output',
204+
};
205+
useYarn = false;
206+
207+
expect(task.run(options)).to.be.rejected.then(() => {
208+
expect(installCommand).to.equal('npm');
209+
});
210+
});
211+
185212
it('should fail on assemble failures', () => {
186213
let options = {
187214
buildPath: 'ember-build',

0 commit comments

Comments
 (0)