Skip to content

Commit c71dfe9

Browse files
committed
Run install and pack when fetching git dependencies with a prepare script.
1 parent 9199a1b commit c71dfe9

File tree

5 files changed

+172
-17
lines changed

5 files changed

+172
-17
lines changed

__tests__/fetchers.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,32 @@ test('GitFetcher.fetch', async () => {
7676
expect(name).toBe('beeper');
7777
});
7878

79+
test('GitFetcher.fetch with prepare script', async () => {
80+
const dir = await mkdir('git-fetcher-with-prepare');
81+
const fetcher = new GitFetcher(
82+
dir,
83+
{
84+
type: 'git',
85+
reference: 'https://github.com/Volune/test-js-git-repo',
86+
hash: '96e838bcc908ed424666b4b04efe802fd4c8bccd',
87+
registry: 'npm',
88+
},
89+
(await Config.create()),
90+
);
91+
await fetcher.fetch();
92+
const name = (await fs.readJson(path.join(dir, 'package.json'))).name;
93+
expect(name).toBe('test-js-git-repo');
94+
const dependencyName = (await fs.readJson(path.join(dir, 'dependency-package.json'))).name;
95+
expect(dependencyName).toBe('beeper');
96+
// The file "prepare.js" is not in "files" list
97+
expect(await fs.exists(path.join(dir, 'prepare.js'))).toBe(false);
98+
// Check executed lifecycle scripts
99+
expect(await fs.exists(path.join(dir, 'generated', 'preinstall'))).toBe(true);
100+
expect(await fs.exists(path.join(dir, 'generated', 'install'))).toBe(true);
101+
expect(await fs.exists(path.join(dir, 'generated', 'postinstall'))).toBe(true);
102+
expect(await fs.exists(path.join(dir, 'generated', 'prepublish'))).toBe(false);
103+
});
104+
79105
test('TarballFetcher.fetch', async () => {
80106
const dir = await mkdir('tarball-fetcher');
81107
const fetcher = new TarballFetcher(

src/cli/commands/install.js

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -823,6 +823,13 @@ export function setFlags(commander: Object) {
823823
commander.option('-T, --save-tilde', 'DEPRECATED');
824824
}
825825

826+
export async function install(config: Config, reporter: Reporter, flags: Object, lockfile: Lockfile): Promise<void> {
827+
await wrapLifecycle(config, flags, async () => {
828+
const install = new Install(flags, config, reporter, lockfile);
829+
await install.init();
830+
});
831+
}
832+
826833
export async function run(config: Config, reporter: Reporter, flags: Object, args: Array<string>): Promise<void> {
827834
let lockfile;
828835
if (flags.lockfile === false) {
@@ -855,10 +862,7 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg
855862
throw new MessageError(reporter.lang('installCommandRenamed', `yarn ${command} ${exampleArgs.join(' ')}`));
856863
}
857864

858-
await wrapLifecycle(config, flags, async () => {
859-
const install = new Install(flags, config, reporter, lockfile);
860-
await install.init();
861-
});
865+
await install(config, reporter, flags, lockfile);
862866
}
863867

864868
export async function wrapLifecycle(config: Config, flags: Object, factory: () => Promise<void>): Promise<void> {
@@ -871,7 +875,9 @@ export async function wrapLifecycle(config: Config, flags: Object, factory: () =
871875
await config.executeLifecycleScript('postinstall');
872876

873877
if (!config.production) {
874-
await config.executeLifecycleScript('prepublish');
878+
if (!config.disablePrepublish) {
879+
await config.executeLifecycleScript('prepublish');
880+
}
875881
await config.executeLifecycleScript('prepare');
876882
}
877883
}

src/cli/commands/pack.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,10 @@ const NEVER_IGNORE = ignoreLinesToRegex([
5050
'!/+(changes|changelog|history)*',
5151
]);
5252

53-
export async function pack(config: Config, dir: string): Promise<stream$Duplex> {
53+
export async function packTarball(
54+
config: Config,
55+
{mapHeader}: {mapHeader?: Object => Object} = {},
56+
): Promise<stream$Duplex> {
5457
const pkg = await config.readRootManifest();
5558
const {bundledDependencies, main, files: onlyFiles} = pkg;
5659

@@ -125,10 +128,15 @@ export async function pack(config: Config, dir: string): Promise<stream$Duplex>
125128
header.name = `package${suffix}`;
126129
delete header.uid;
127130
delete header.gid;
128-
return header;
131+
return mapHeader ? mapHeader(header) : header;
129132
},
130133
});
131134

135+
return packer;
136+
}
137+
138+
export async function pack(config: Config, dir: string): Promise<stream$Duplex> {
139+
const packer = await packTarball(config);
132140
const compressor = packer.pipe(new zlib.Gzip());
133141

134142
return compressor;

src/config.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export type ConfigOptions = {
3939
ignoreEngines?: boolean,
4040
cafile?: ?string,
4141
production?: boolean,
42+
disablePrepublish?: boolean,
4243
binLinks?: boolean,
4344
networkConcurrency?: number,
4445
childConcurrency?: number,
@@ -144,6 +145,8 @@ export default class Config {
144145

145146
production: boolean;
146147

148+
disablePrepublish: boolean;
149+
147150
nonInteractive: boolean;
148151

149152
workspacesEnabled: boolean;
@@ -328,6 +331,8 @@ export default class Config {
328331
this.ignorePlatform = !!opts.ignorePlatform;
329332
this.ignoreScripts = !!opts.ignoreScripts;
330333

334+
this.disablePrepublish = !!opts.disablePrepublish;
335+
331336
this.nonInteractive = !!opts.nonInteractive;
332337

333338
this.requestManager.setOptions({

src/fetchers/git-fetcher.js

Lines changed: 120 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ import Git from '../util/git.js';
77
import * as fsUtil from '../util/fs.js';
88
import * as constants from '../constants.js';
99
import * as crypto from '../util/crypto.js';
10+
import {install} from '../cli/commands/install.js';
11+
import Lockfile from '../lockfile/wrapper.js';
12+
import Config from '../config.js';
13+
import {packTarball} from '../cli/commands/pack.js';
1014

1115
const tarFs = require('tar-fs');
1216
const url = require('url');
@@ -15,6 +19,8 @@ const fs = require('fs');
1519

1620
const invariant = require('invariant');
1721

22+
const PACKED_FLAG = '1';
23+
1824
export default class GitFetcher extends BaseFetcher {
1925
async setupMirrorFromCache(): Promise<?string> {
2026
const tarballMirrorPath = this.getTarballMirrorPath();
@@ -90,11 +96,7 @@ export default class GitFetcher extends BaseFetcher {
9096
}
9197

9298
return new Promise((resolve, reject) => {
93-
const untarStream = tarFs.extract(this.dest, {
94-
dmode: 0o555, // all dirs should be readable
95-
fmode: 0o444, // all files should be readable
96-
chown: false, // don't chown. just leave as it is
97-
});
99+
const untarStream = this._createUntarStream(this.dest);
98100

99101
const hashStream = new crypto.HashStream();
100102

@@ -131,22 +133,130 @@ export default class GitFetcher extends BaseFetcher {
131133
const gitUrl = Git.npmUrlToGitUrl(this.reference);
132134
const git = new Git(this.config, gitUrl, hash);
133135
await git.init();
134-
await git.clone(this.dest);
136+
137+
const manifestFile = await git.getFile('package.json');
138+
if (!manifestFile) {
139+
throw new MessageError(this.reporter.lang('couldntFindPackagejson', gitUrl));
140+
}
141+
const scripts = JSON.parse(manifestFile).scripts;
142+
const hasPrepareScript = Boolean(scripts && scripts.prepare);
143+
144+
if (hasPrepareScript) {
145+
await this.fetchFromInstallAndPack(git);
146+
} else {
147+
await this.fetchFromGitArchive(git);
148+
}
149+
150+
return {
151+
hash,
152+
};
153+
}
154+
155+
async fetchFromInstallAndPack(git: Git): Promise<void> {
156+
const prepareDirectory = this.config.getTemp(`${crypto.hash(git.gitUrl.repository)}.${git.hash}.prepare`);
157+
await fsUtil.unlink(prepareDirectory);
158+
159+
await git.clone(prepareDirectory);
160+
161+
const [prepareConfig, prepareLockFile] = await Promise.all([
162+
Config.create(
163+
{
164+
cwd: prepareDirectory,
165+
disablePrepublish: true,
166+
},
167+
this.reporter,
168+
),
169+
Lockfile.fromDirectory(prepareDirectory, this.reporter),
170+
]);
171+
await install(prepareConfig, this.reporter, {}, prepareLockFile);
135172

136173
const tarballMirrorPath = this.getTarballMirrorPath();
137174
const tarballCachePath = this.getTarballCachePath();
138175

176+
if (tarballMirrorPath) {
177+
await this._packToTarball(prepareConfig, tarballMirrorPath);
178+
}
179+
if (tarballCachePath) {
180+
await this._packToTarball(prepareConfig, tarballCachePath);
181+
}
182+
183+
await this._packToDirectory(prepareConfig, this.dest);
184+
185+
await fsUtil.unlink(prepareDirectory);
186+
}
187+
188+
async _packToTarball(config: Config, path: string): Promise<void> {
189+
const tarballStream = await this._createTarballStream(config);
190+
await new Promise((resolve, reject) => {
191+
const writeStream = fs.createWriteStream(path);
192+
tarballStream.on('error', reject);
193+
writeStream.on('error', reject);
194+
writeStream.on('end', resolve);
195+
writeStream.on('open', () => {
196+
tarballStream.pipe(writeStream);
197+
});
198+
writeStream.once('finish', resolve);
199+
});
200+
}
201+
202+
async _packToDirectory(config: Config, dest: string): Promise<void> {
203+
const tarballStream = await this._createTarballStream(config);
204+
await new Promise((resolve, reject) => {
205+
const untarStream = this._createUntarStream(dest);
206+
tarballStream.on('error', reject);
207+
untarStream.on('error', reject);
208+
untarStream.on('end', resolve);
209+
untarStream.once('finish', resolve);
210+
tarballStream.pipe(untarStream);
211+
});
212+
}
213+
214+
_createTarballStream(config: Config): Promise<stream$Duplex> {
215+
let savedPackedHeader = false;
216+
return packTarball(config, {
217+
mapHeader(header: Object): Object {
218+
if (!savedPackedHeader) {
219+
savedPackedHeader = true;
220+
header.pax = header.pax || {};
221+
// add a custom data on the first header
222+
// in order to distinguish a tar from "git archive" and a tar from "pack" command
223+
header.pax.packed = PACKED_FLAG;
224+
}
225+
return header;
226+
},
227+
});
228+
}
229+
230+
_createUntarStream(dest: string): stream$Writable {
231+
const PREFIX = 'package/';
232+
let isPackedTarball = undefined;
233+
return tarFs.extract(dest, {
234+
dmode: 0o555, // all dirs should be readable
235+
fmode: 0o444, // all files should be readable
236+
chown: false, // don't chown. just leave as it is
237+
map: header => {
238+
if (isPackedTarball === undefined) {
239+
isPackedTarball = header.pax && header.pax.packed === PACKED_FLAG;
240+
}
241+
if (isPackedTarball) {
242+
header.name = header.name.substr(PREFIX.length);
243+
}
244+
},
245+
});
246+
}
247+
248+
async fetchFromGitArchive(git: Git): Promise<void> {
249+
await git.clone(this.dest);
250+
const tarballMirrorPath = this.getTarballMirrorPath();
251+
const tarballCachePath = this.getTarballCachePath();
252+
139253
if (tarballMirrorPath) {
140254
await git.archive(tarballMirrorPath);
141255
}
142256

143257
if (tarballCachePath) {
144258
await git.archive(tarballCachePath);
145259
}
146-
147-
return {
148-
hash,
149-
};
150260
}
151261

152262
async _fetch(): Promise<FetchedOverride> {

0 commit comments

Comments
 (0)