Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(link): generate shims for missing 'bin' scripts #2059

Merged
merged 2 commits into from May 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -0,0 +1 @@
a-build-tool.js
@@ -0,0 +1,3 @@
{
"version": "1.0.0"
}
@@ -0,0 +1,7 @@
{
"name": "basic",
"version": "monorepo",
"private": true,
"dependencies": {
}
}
@@ -0,0 +1,10 @@
{
"name": "@test/build-tool",
"version": "1.0.0",
"bin": {
"a-build-tool": "a-build-tool.js"
},
"scripts": {
"build": "echo \"#!/usr/bin/env node\nconsole.log('build tool executed')\" > a-build-tool.js"
}
}
@@ -0,0 +1,10 @@
{
"name": "@test/buildable",
"version": "2.0.0",
"devDependencies": {
"@test/build-tool": "*"
},
"scripts": {
"build": "a-build-tool"
}
}
16 changes: 16 additions & 0 deletions integration/lerna-link-sibling-bins.test.js
@@ -0,0 +1,16 @@
"use strict";

const cliRunner = require("@lerna-test/cli-runner");
const initFixture = require("@lerna-test/init-fixture")(__dirname);

test("lerna link symlinks generated binaries of sibling packages", async () => {
const cwd = await initFixture("lerna-generated-build-tool");
const lerna = cliRunner(cwd);

// First bootstrap, I expect this to succeed but don't are about the output
await lerna("bootstrap");

const { stdout } = await lerna("run", "build");

expect(stdout).toMatch("build tool executed");
});
1 change: 1 addition & 0 deletions utils/create-symlink/__tests__/create-symlink.test.js
Expand Up @@ -15,6 +15,7 @@ describe("create-symlink", () => {
fs.lstat.mockImplementation(() => Promise.reject(new Error("MOCK")));
fs.unlink.mockResolvedValue();
fs.symlink.mockResolvedValue();
fs.pathExists.mockResolvedValue(true);
// cmdShim is a traditional errback
cmdShim.mockImplementation(callsBack());

Expand Down
25 changes: 23 additions & 2 deletions utils/create-symlink/create-symlink.js
Expand Up @@ -31,9 +31,20 @@ function createSymbolicLink(src, dest, type) {

function createPosixSymlink(origin, dest, _type) {
const type = _type === "exec" ? "file" : _type;
const src = path.relative(path.dirname(dest), origin);
const relativeSymlink = path.relative(path.dirname(dest), origin);

return createSymbolicLink(src, dest, type);
if (_type === "exec") {
// If the target exists, create real symlink. If the target doesn't exist yet,
// create a shim shell script.
return fs.pathExists(origin).then(exists => {
if (exists) {
return createSymbolicLink(relativeSymlink, dest, type).then(() => fs.chmod(origin, "755"));
}
return shShim(origin, dest, type).then(() => fs.chmod(dest, "755"));
});
}

return createSymbolicLink(relativeSymlink, dest, type);
}

function createWindowsSymlink(src, dest, type) {
Expand All @@ -51,3 +62,13 @@ function createWindowsSymlink(src, dest, type) {

return createSymbolicLink(src, dest, type);
}

function shShim(target, script, type) {
log.silly("shShim", [target, script, type]);

const absTarget = path.resolve(path.dirname(script), target);

const scriptLines = ["#!/bin/sh", `chmod +x ${absTarget} && exec ${absTarget} "$@"`];

return fs.writeFile(script, scriptLines.join("\n"));
}
6 changes: 3 additions & 3 deletions utils/symlink-binary/__tests__/symlink-binary.test.js
Expand Up @@ -42,15 +42,15 @@ describe("symlink-binary", () => {
expect(dstPath).not.toHaveBinaryLinks();
});

it("should skip missing bin files", async () => {
it("should create shims for all declared binaries", async () => {
const testDir = await initFixture("links");
const srcPath = path.join(testDir, "packages/package-3");
const dstPath = path.join(testDir, "packages/package-4");

await symlinkBinary(srcPath, dstPath);

expect(srcPath).toHaveExecutables("cli1.js", "cli2.js");
expect(dstPath).toHaveBinaryLinks("links3cli1", "links3cli2");
expect(dstPath).toHaveBinaryLinks("links3cli1", "links3cli2", "links3cli3");
});

it("should preserve previous bin entries", async () => {
Expand All @@ -62,6 +62,6 @@ describe("symlink-binary", () => {
await symlinkBinary(pkg2Path, destPath);
await symlinkBinary(pkg3Path, destPath);

expect(destPath).toHaveBinaryLinks("links-2", "links3cli1", "links3cli2");
expect(destPath).toHaveBinaryLinks("links-2", "links3cli1", "links3cli2", "links3cli3");
});
});
12 changes: 6 additions & 6 deletions utils/symlink-binary/symlink-binary.js
Expand Up @@ -22,11 +22,11 @@ function symlinkBinary(srcPackageRef, destPackageRef) {
const src = path.join(srcPackage.location, srcPackage.bin[name]);
const dst = path.join(destPackage.binLocation, name);

return fs.pathExists(src).then(exists => {
if (exists) {
return { src, dst };
}
});
// Symlink all declared binaries, even if they don't exist (yet). We will
// assume the package author knows what they're doing and that the binaries
// will be generated during a later build phase (potentially source compiled from
// another language).
return { src, dst };
});

if (actions.length === 0) {
Expand All @@ -36,7 +36,7 @@ function symlinkBinary(srcPackageRef, destPackageRef) {
return fs.mkdirp(destPackage.binLocation).then(() =>
pMap(actions, meta => {
if (meta) {
return createSymlink(meta.src, meta.dst, "exec").then(() => fs.chmod(meta.src, "755"));
return createSymlink(meta.src, meta.dst, "exec");
}
})
);
Expand Down