forked from lerna/lerna
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'feature/pkg-main' into builded/petcommunities
* feature/pkg-main: Bootstrap: copy `main` property from package.json 2.0.0-beta.23 fix execSync use Add `onlyExplicitUpdates` flag so that only packages that have changed have version bumps rather than all packages that depend on the updated packages (lerna#241) Re-introduce node 0.10 support (lerna#248) 2.0.0-beta.22 Consistent naming in README `import` section (lerna#243) [skip ci] Lerna import (lerna#173) Revert "Use sync-exec for node 0.10" (lerna#242) Revert "Fix bootstrap install to use quotes around versions (lerna#235)" 2.0.0-beta.21 Fix bootstrap install to use quotes around versions (lerna#235) typo [skip ci] # Conflicts: # src/commands/BootstrapCommand.js
- Loading branch information
Showing
17 changed files
with
363 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import fs from "fs"; | ||
import path from "path"; | ||
import async from "async"; | ||
import Command from "../Command"; | ||
import progressBar from "../progressBar"; | ||
import PromptUtilities from "../PromptUtilities"; | ||
import ChildProcessUtilities from "../ChildProcessUtilities"; | ||
|
||
export default class ImportCommand extends Command { | ||
initialize(callback) { | ||
const inputPath = this.input[0]; | ||
|
||
if (!inputPath) { | ||
return callback(new Error("Missing argument: Path to external repository")); | ||
} | ||
|
||
const externalRepoPath = path.resolve(inputPath); | ||
const externalRepoBase = path.basename(externalRepoPath); | ||
|
||
try { | ||
const stats = fs.statSync(externalRepoPath); | ||
if (!stats.isDirectory()) { | ||
throw new Error(`Input path "${inputPath}" is not a directory`); | ||
} | ||
const packageJson = path.join(externalRepoPath, "package.json"); | ||
const packageName = require(packageJson).name; | ||
if (!packageName) { | ||
throw new Error(`No package name specified in "${packageJson}"`); | ||
} | ||
} catch (e) { | ||
if (e.code === "ENOENT") { | ||
return callback(new Error(`No repository found at "${inputPath}"`)); | ||
} | ||
return callback(e); | ||
} | ||
|
||
this.targetDir = "packages/" + externalRepoBase; | ||
|
||
try { | ||
if (fs.statSync(this.targetDir)) { | ||
return callback(new Error(`Target directory already exists "${this.targetDir}"`)); | ||
} | ||
} catch (e) { /* Pass */ } | ||
|
||
this.externalExecOpts = { | ||
encoding: "utf8", | ||
cwd: externalRepoPath | ||
}; | ||
|
||
this.commits = this.externalExecSync("git log --format=\"%h\"").split("\n").reverse(); | ||
|
||
if (!this.commits.length) { | ||
callback(new Error(`No git commits to import at "${inputPath}"`)); | ||
} | ||
|
||
this.logger.info(`About to import ${this.commits.length} commits into from ${inputPath} into ${this.targetDir}`); | ||
|
||
if (this.flags.yes) { | ||
callback(null, true); | ||
} else { | ||
PromptUtilities.confirm("Are you sure you want to import these commits onto the current branch?", confirmed => { | ||
if (confirmed) { | ||
callback(null, true); | ||
} else { | ||
this.logger.info("Okay bye!"); | ||
callback(null, false); | ||
} | ||
}); | ||
} | ||
} | ||
|
||
externalExecSync(command) { | ||
return ChildProcessUtilities.execSync(command, this.externalExecOpts).trim(); | ||
} | ||
|
||
execute(callback) { | ||
const replacement = "$1/" + this.targetDir; | ||
|
||
progressBar.init(this.commits.length); | ||
|
||
async.series(this.commits.map(sha => done => { | ||
progressBar.tick(sha); | ||
|
||
// Create a patch file for this commit and prepend the target directory | ||
// to all affected files. This moves the git history for the entire | ||
// external repository into the package subdirectory, commit by commit. | ||
const patch = this.externalExecSync(`git format-patch -1 ${sha} --stdout`) | ||
.replace(/^([-+]{3} [ab])/mg, replacement) | ||
.replace(/^(diff --git a)/mg, replacement) | ||
.replace(/^(diff --git \S+ b)/mg, replacement); | ||
|
||
// Apply the modified patch to the current lerna repository, preserving | ||
// original commit date, author and message. | ||
ChildProcessUtilities.exec("git am", {}, done).stdin.end(patch); | ||
}), err => { | ||
progressBar.terminate(); | ||
this.logger.info("Import complete!"); | ||
callback(err, !err); | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
import pathExists from "path-exists"; | ||
import assert from "assert"; | ||
import path from "path"; | ||
import fs from "fs"; | ||
|
||
import PromptUtilities from "../src/PromptUtilities"; | ||
import ChildProcessUtilities from "../src/ChildProcessUtilities"; | ||
import ImportCommand from "../src/commands/ImportCommand"; | ||
import exitWithCode from "./_exitWithCode"; | ||
import initFixture from "./_initFixture"; | ||
import initExternalFixture from "./_initExternalFixture"; | ||
import assertStubbedCalls from "./_assertStubbedCalls"; | ||
|
||
describe("ImportCommand", () => { | ||
|
||
describe("import", () => { | ||
let testDir, externalDir; | ||
|
||
beforeEach(done => { | ||
testDir = initFixture("ImportCommand/basic", done); | ||
}); | ||
|
||
beforeEach(done => { | ||
externalDir = initExternalFixture("ImportCommand/external", done); | ||
}); | ||
|
||
it("should import into packages with commit history", done => { | ||
const importCommand = new ImportCommand([externalDir], {}); | ||
|
||
importCommand.runValidations(); | ||
importCommand.runPreparations(); | ||
|
||
assertStubbedCalls([ | ||
[PromptUtilities, "confirm", { valueCallback: true }, [ | ||
{ args: ["Are you sure you want to import these commits onto the current branch?"], returns: true } | ||
]], | ||
]); | ||
|
||
importCommand.runCommand(exitWithCode(0, err => { | ||
if (err) return done(err); | ||
|
||
try { | ||
const lastCommit = ChildProcessUtilities.execSync("git log --format=\"%s\"", {encoding:"utf8"}).split("\n")[0]; | ||
const packageJson = path.join(testDir, "packages", path.basename(externalDir), "package.json"); | ||
assert.ok(!pathExists.sync(path.join(testDir, "lerna-debug.log"))); | ||
assert.ok(pathExists.sync(packageJson)); | ||
assert.equal(lastCommit, "Init external commit"); | ||
done(); | ||
} catch (err) { | ||
done(err); | ||
} | ||
})); | ||
}); | ||
|
||
it("should be possible to skip asking for confirmation", done => { | ||
|
||
const importCommand = new ImportCommand([externalDir], { | ||
yes: true | ||
}); | ||
|
||
importCommand.runValidations(); | ||
importCommand.runPreparations(); | ||
|
||
importCommand.initialize(done); | ||
}); | ||
|
||
it("should fail without an argument", done => { | ||
const importCommand = new ImportCommand([], {}); | ||
|
||
importCommand.runValidations(); | ||
importCommand.runPreparations(); | ||
|
||
importCommand.runCommand(exitWithCode(1, err => { | ||
const expect = "Missing argument: Path to external repository"; | ||
assert.equal((err || {}).message, expect); | ||
done(); | ||
})); | ||
}); | ||
|
||
it("should fail with a missing external directory", done => { | ||
const missing = externalDir + "_invalidSuffix"; | ||
const importCommand = new ImportCommand([missing], {}); | ||
|
||
importCommand.runValidations(); | ||
importCommand.runPreparations(); | ||
|
||
importCommand.runCommand(exitWithCode(1, err => { | ||
const expect = `No repository found at "${missing}"`; | ||
assert.equal((err || {}).message, expect); | ||
done(); | ||
})); | ||
}); | ||
|
||
it("should fail with a missing package.json", done => { | ||
const importCommand = new ImportCommand([externalDir], {}); | ||
|
||
const packageJson = path.join(externalDir, "package.json"); | ||
|
||
fs.unlinkSync(packageJson); | ||
|
||
importCommand.runValidations(); | ||
importCommand.runPreparations(); | ||
|
||
importCommand.runCommand(exitWithCode(1, err => { | ||
const expect = `Cannot find module '${packageJson}'`; | ||
assert.equal((err || {}).message, expect); | ||
done(); | ||
})); | ||
}); | ||
|
||
it("should fail with no name in package.json", done => { | ||
const importCommand = new ImportCommand([externalDir], {}); | ||
|
||
const packageJson = path.join(externalDir, "package.json"); | ||
|
||
fs.writeFileSync(packageJson, "{}"); | ||
|
||
importCommand.runValidations(); | ||
importCommand.runPreparations(); | ||
|
||
importCommand.runCommand(exitWithCode(1, err => { | ||
const expect = `No package name specified in "${packageJson}"`; | ||
assert.equal((err || {}).message, expect); | ||
done(); | ||
})); | ||
}); | ||
|
||
it("should fail if target directory exists", done => { | ||
const importCommand = new ImportCommand([externalDir], {}); | ||
|
||
const targetDir = path.relative( | ||
process.cwd(), | ||
path.join(testDir, "packages", path.basename(externalDir)) | ||
); | ||
|
||
fs.mkdirSync(targetDir); | ||
|
||
importCommand.runValidations(); | ||
importCommand.runPreparations(); | ||
|
||
importCommand.runCommand(exitWithCode(1, err => { | ||
const expect = `Target directory already exists "${targetDir}"`; | ||
assert.equal((err || {}).message, expect); | ||
done(); | ||
})); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.