Skip to content

Commit 46c5075

Browse files
committed
Run install and pack when fetching git dependencies with a prepare script.
1 parent 76489f9 commit 46c5075

File tree

6 files changed

+168
-17
lines changed

6 files changed

+168
-17
lines changed

__tests__/fetchers.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,26 @@ 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: '90f974f63a1ab7f9f9030808896ff39b4d597591',
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+
expect(await fs.exists(path.join(dir, 'prepare.js'))).toBe(false);
97+
});
98+
7999
test('TarballFetcher.fetch', async () => {
80100
const dir = await mkdir('tarball-fetcher');
81101
const fetcher = new TarballFetcher(

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"strip-bom": "^3.0.0",
3838
"tar-fs": "^1.15.1",
3939
"tar-stream": "^1.5.2",
40+
"test-js-git-repo": "https://github.com/Volune/test-js-git-repo",
4041
"uuid": "^3.0.1",
4142
"v8-compile-cache": "^1.1.0",
4243
"validate-npm-package-license": "^3.0.1"

src/cli/commands/install.js

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

797+
export async function install(config: Config, reporter: Reporter, flags: Object, lockfile: Lockfile): Promise<void> {
798+
await wrapLifecycle(config, flags, async () => {
799+
const install = new Install(flags, config, reporter, lockfile);
800+
await install.init();
801+
});
802+
}
803+
797804
export async function run(config: Config, reporter: Reporter, flags: Object, args: Array<string>): Promise<void> {
798805
let lockfile;
799806
if (flags.lockfile === false) {
@@ -826,10 +833,7 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg
826833
throw new MessageError(reporter.lang('installCommandRenamed', `yarn ${command} ${exampleArgs.join(' ')}`));
827834
}
828835

829-
await wrapLifecycle(config, flags, async () => {
830-
const install = new Install(flags, config, reporter, lockfile);
831-
await install.init();
832-
});
836+
await install(config, reporter, flags, lockfile);
833837
}
834838

835839
export async function wrapLifecycle(config: Config, flags: Object, factory: () => Promise<void>): Promise<void> {
@@ -842,7 +846,9 @@ export async function wrapLifecycle(config: Config, flags: Object, factory: () =
842846
await config.executeLifecycleScript('postinstall');
843847

844848
if (!config.production) {
845-
await config.executeLifecycleScript('prepublish');
849+
if (!config.gitDependency) {
850+
await config.executeLifecycleScript('prepublish');
851+
}
846852
await config.executeLifecycleScript('prepare');
847853
}
848854
}

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

@@ -127,10 +130,15 @@ export async function pack(config: Config, dir: string): Promise<stream$Duplex>
127130
header.name = `package${suffix}`;
128131
delete header.uid;
129132
delete header.gid;
130-
return header;
133+
return mapHeader ? mapHeader(header) : header;
131134
},
132135
});
133136

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

136144
return compressor;

src/config.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export type ConfigOptions = {
3838
ignoreEngines?: boolean,
3939
cafile?: ?string,
4040
production?: boolean,
41+
gitDependency?: boolean,
4142
binLinks?: boolean,
4243
networkConcurrency?: number,
4344
childConcurrency?: number,
@@ -142,6 +143,8 @@ export default class Config {
142143

143144
production: boolean;
144145

146+
gitDependency: boolean;
147+
145148
nonInteractive: boolean;
146149

147150
workspacesExperimental: boolean;
@@ -320,6 +323,8 @@ export default class Config {
320323
this.ignorePlatform = !!opts.ignorePlatform;
321324
this.ignoreScripts = !!opts.ignoreScripts;
322325

326+
this.gitDependency = !!opts.gitDependency;
327+
323328
this.nonInteractive = !!opts.nonInteractive;
324329

325330
this.requestManager.setOptions({

src/fetchers/git-fetcher.js

Lines changed: 121 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 getLocalAvailabilityStatus(): Promise<boolean> {
2026
// Some mirrors might still have files named "./reponame" instead of "./reponame-commit"
@@ -78,11 +84,7 @@ export default class GitFetcher extends BaseFetcher {
7884
}
7985

8086
return new Promise((resolve, reject) => {
81-
const untarStream = tarFs.extract(this.dest, {
82-
dmode: 0o555, // all dirs should be readable
83-
fmode: 0o444, // all files should be readable
84-
chown: false, // don't chown. just leave as it is
85-
});
87+
const untarStream = this._createUntarStream(this.dest);
8688

8789
const hashStream = new crypto.HashStream();
8890

@@ -117,22 +119,131 @@ export default class GitFetcher extends BaseFetcher {
117119
const gitUrl = Git.npmUrlToGitUrl(this.reference);
118120
const git = new Git(this.config, gitUrl, hash);
119121
await git.init();
120-
await git.clone(this.dest);
122+
123+
const manifestFile = await git.getFile('package.json');
124+
if (!manifestFile) {
125+
throw new MessageError(this.reporter.lang('couldntFindPackagejson', gitUrl));
126+
}
127+
const scripts = JSON.parse(manifestFile).scripts;
128+
const hasPrepareScript = Boolean(scripts && scripts.prepare);
129+
130+
if (hasPrepareScript) {
131+
await this.fetchFromInstallAndPack(git);
132+
} else {
133+
await this.fetchFromGitArchive(git);
134+
}
135+
136+
return {
137+
hash,
138+
};
139+
}
140+
141+
async fetchFromInstallAndPack(git: Git): Promise<void> {
142+
const prepareDirectory = this.config.getTemp(
143+
`${crypto.hash(git.gitUrl.repository)}.${git.hash}.prepare`);
144+
await fsUtil.unlink(prepareDirectory);
145+
146+
await git.clone(prepareDirectory);
147+
148+
const [
149+
prepareConfig,
150+
prepareLockFile,
151+
] = await Promise.all([
152+
Config.create({
153+
cwd: prepareDirectory,
154+
gitDependency: true,
155+
}, this.reporter),
156+
Lockfile.fromDirectory(prepareDirectory, this.reporter),
157+
]);
158+
await install(prepareConfig, this.reporter, {}, prepareLockFile);
121159

122160
const tarballMirrorPath = this.getTarballMirrorPath();
123161
const tarballCachePath = this.getTarballCachePath();
124162

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

129244
if (tarballCachePath) {
130245
await git.archive(tarballCachePath);
131246
}
132-
133-
return {
134-
hash,
135-
};
136247
}
137248

138249
async _fetch(): Promise<FetchedOverride> {

0 commit comments

Comments
 (0)