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

Multiple package.json files #100

Merged
merged 2 commits into from
Apr 25, 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 94 additions & 54 deletions __tests__/api/BundlePush/BundlePusher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ const DEFAULT_PARAMTERS: IHandlerParameters = {
definition: PushBundleDefinition.PushBundleDefinition,
fullDefinition: PushBundleDefinition.PushBundleDefinition,
};
const IS_DIRECTORY: any = {
isDirectory: jest.fn((directory) => (true))
};
const IS_NOT_DIRECTORY: any = {
isDirectory: jest.fn((directory) => (false))
};
let consoleText = "";

// Initialise xml2json before mocking anything
Expand All @@ -68,6 +74,8 @@ let shellSpy = jest.spyOn(Shell, "executeSshCwd").mockImplementation(() => ({}))
let existsSpy = jest.spyOn(fs, "existsSync").mockImplementation(() => ({}));
let readSpy = jest.spyOn(fs, "readFileSync").mockImplementation(() => ({}));
let uploadSpy = jest.spyOn(Upload, "dirToUSSDirRecursive").mockImplementation(() => ({}));
let readdirSpy = jest.spyOn(fs, "readdirSync").mockImplementation(() => ({}));
let lstatSpy = jest.spyOn(fs, "lstatSync").mockImplementation(() => ({}));

describe("BundlePusher01", () => {

Expand All @@ -88,6 +96,8 @@ describe("BundlePusher01", () => {
}
});
uploadSpy = jest.spyOn(Upload, "dirToUSSDirRecursive").mockImplementation(() => ({}));
readdirSpy = jest.spyOn(fs, "readdirSync").mockImplementation(() => ([]));
lstatSpy = jest.spyOn(fs, "lstatSync").mockImplementation(() => ( IS_NOT_DIRECTORY ));
consoleText = "";
});
afterEach(() => {
Expand Down Expand Up @@ -407,7 +417,7 @@ describe("BundlePusher01", () => {
expect(shellSpy).toHaveBeenCalledTimes(1);
expect(membersSpy).toHaveBeenCalledTimes(2);
expect(submitSpy).toHaveBeenCalledTimes(2);
expect(existsSpy).toHaveBeenCalledTimes(2);
expect(existsSpy).toHaveBeenCalledTimes(1);
expect(readSpy).toHaveBeenCalledTimes(2);
});
it("should use a default zosattribs file", async () => {
Expand All @@ -422,7 +432,7 @@ describe("BundlePusher01", () => {
expect(shellSpy).toHaveBeenCalledTimes(1);
expect(membersSpy).toHaveBeenCalledTimes(2);
expect(submitSpy).toHaveBeenCalledTimes(2);
expect(existsSpy).toHaveBeenCalledTimes(2);
expect(existsSpy).toHaveBeenCalledTimes(1);
expect(readSpy).toHaveBeenCalledTimes(1);
});
it("should handle error with bundle upload", async () => {
Expand Down Expand Up @@ -499,13 +509,8 @@ describe("BundlePusher01", () => {
return true;
}
});
existsSpy.mockImplementation((data: string) => {
if (data.indexOf(".zosattributes") > -1) {
return false;
}
if (data.indexOf("package.json") > -1) {
return true;
}
readdirSpy.mockImplementation((data: string) => {
return [ "package.json" ];
});

await runPushTestWithError("__tests__/__resources__/ExampleBundle01", false,
Expand All @@ -518,9 +523,11 @@ describe("BundlePusher01", () => {
expect(shellSpy).toHaveBeenCalledTimes(1);
expect(membersSpy).toHaveBeenCalledTimes(0);
expect(submitSpy).toHaveBeenCalledTimes(0);
expect(existsSpy).toHaveBeenCalledTimes(2);
expect(existsSpy).toHaveBeenCalledTimes(1);
expect(readSpy).toHaveBeenCalledTimes(1);
expect(uploadSpy).toHaveBeenCalledTimes(1);
expect(readdirSpy).toHaveBeenCalledTimes(1);
expect(lstatSpy).toHaveBeenCalledTimes(1);
});
it("should handle failure of remote npm install", async () => {
shellSpy.mockImplementation((session: any, cmd: string, dir: string, stdoutHandler: (data: string) => void) => {
Expand All @@ -531,13 +538,8 @@ describe("BundlePusher01", () => {
return true;
}
});
existsSpy.mockImplementation((data: string) => {
if (data.indexOf(".zosattributes") > -1) {
return false;
}
if (data.indexOf("package.json") > -1) {
return true;
}
readdirSpy.mockImplementation((data: string) => {
return [ "package.json" ];
});

await runPushTestWithError("__tests__/__resources__/ExampleBundle01", false,
Expand All @@ -553,9 +555,11 @@ describe("BundlePusher01", () => {
expect(shellSpy).toHaveBeenCalledTimes(1);
expect(membersSpy).toHaveBeenCalledTimes(0);
expect(submitSpy).toHaveBeenCalledTimes(0);
expect(existsSpy).toHaveBeenCalledTimes(2);
expect(existsSpy).toHaveBeenCalledTimes(1);
expect(readSpy).toHaveBeenCalledTimes(1);
expect(uploadSpy).toHaveBeenCalledTimes(1);
expect(readdirSpy).toHaveBeenCalledTimes(1);
expect(lstatSpy).toHaveBeenCalledTimes(1);
});
it("should handle failure of remote npm install with FSUM message", async () => {
shellSpy.mockImplementation((session: any, cmd: string, dir: string, stdoutHandler: (data: string) => void) => {
Expand All @@ -566,13 +570,8 @@ describe("BundlePusher01", () => {
return true;
}
});
existsSpy.mockImplementation((data: string) => {
if (data.indexOf(".zosattributes") > -1) {
return false;
}
if (data.indexOf("package.json") > -1) {
return true;
}
readdirSpy.mockImplementation((data: string) => {
return [ "package.json" ];
});

await runPushTestWithError("__tests__/__resources__/ExampleBundle01", false,
Expand All @@ -588,9 +587,11 @@ describe("BundlePusher01", () => {
expect(shellSpy).toHaveBeenCalledTimes(1);
expect(membersSpy).toHaveBeenCalledTimes(0);
expect(submitSpy).toHaveBeenCalledTimes(0);
expect(existsSpy).toHaveBeenCalledTimes(2);
expect(existsSpy).toHaveBeenCalledTimes(1);
expect(readSpy).toHaveBeenCalledTimes(1);
expect(uploadSpy).toHaveBeenCalledTimes(1);
expect(readdirSpy).toHaveBeenCalledTimes(1);
expect(lstatSpy).toHaveBeenCalledTimes(1);
});
it("should handle failure of remote npm install with node error", async () => {
shellSpy.mockImplementation((session: any, cmd: string, dir: string, stdoutHandler: (data: string) => void) => {
Expand All @@ -601,13 +602,8 @@ describe("BundlePusher01", () => {
return true;
}
});
existsSpy.mockImplementation((data: string) => {
if (data.indexOf(".zosattributes") > -1) {
return false;
}
if (data.indexOf("package.json") > -1) {
return true;
}
readdirSpy.mockImplementation((data: string) => {
return [ "package.json" ];
});

await runPushTestWithError("__tests__/__resources__/ExampleBundle01", false,
Expand All @@ -623,9 +619,11 @@ describe("BundlePusher01", () => {
expect(shellSpy).toHaveBeenCalledTimes(1);
expect(membersSpy).toHaveBeenCalledTimes(0);
expect(submitSpy).toHaveBeenCalledTimes(0);
expect(existsSpy).toHaveBeenCalledTimes(2);
expect(existsSpy).toHaveBeenCalledTimes(1);
expect(readSpy).toHaveBeenCalledTimes(1);
expect(uploadSpy).toHaveBeenCalledTimes(1);
expect(readdirSpy).toHaveBeenCalledTimes(1);
expect(lstatSpy).toHaveBeenCalledTimes(1);
});
it("should handle error with remote bundle deploy", async () => {
submitSpy.mockImplementationOnce(() => { throw new Error("Injected deploy error"); });
Expand All @@ -641,7 +639,7 @@ describe("BundlePusher01", () => {
expect(shellSpy).toHaveBeenCalledTimes(0);
expect(membersSpy).toHaveBeenCalledTimes(2);
expect(submitSpy).toHaveBeenCalledTimes(1);
expect(existsSpy).toHaveBeenCalledTimes(2);
expect(existsSpy).toHaveBeenCalledTimes(1);
expect(readSpy).toHaveBeenCalledTimes(1);
expect(uploadSpy).toHaveBeenCalledTimes(1);
});
Expand All @@ -656,24 +654,19 @@ describe("BundlePusher01", () => {
expect(shellSpy).toHaveBeenCalledTimes(0);
expect(membersSpy).toHaveBeenCalledTimes(2);
expect(submitSpy).toHaveBeenCalledTimes(1);
expect(existsSpy).toHaveBeenCalledTimes(2);
expect(existsSpy).toHaveBeenCalledTimes(1);
expect(readSpy).toHaveBeenCalledTimes(1);
expect(uploadSpy).toHaveBeenCalledTimes(1);
});
it("should processed an escaped targetdir", async () => {
it("should process an escaped targetdir", async () => {
const parms = getCommonParmsForPushTests();
parms.arguments.verbose = true;
parms.arguments.targetdir = "//u//escapedDirName";
shellSpy.mockImplementation((session: any, cmd: string, dir: string, stdoutHandler: (data: string) => void) => {
stdoutHandler("Injected stdout shell message");
});
existsSpy.mockImplementation((data: string) => {
if (data.indexOf(".zosattributes") > -1) {
return false;
}
if (data.indexOf("package.json") > -1) {
return true;
}
readdirSpy.mockImplementation((data: string) => {
return [ "package.json" ];
});

await runPushTest("__tests__/__resources__/ExampleBundle01", false, "PUSH operation completed.", parms);
Expand All @@ -686,31 +679,76 @@ describe("BundlePusher01", () => {
expect(shellSpy).toHaveBeenCalledTimes(1);
expect(membersSpy).toHaveBeenCalledTimes(2);
expect(submitSpy).toHaveBeenCalledTimes(1);
expect(existsSpy).toHaveBeenCalledTimes(2);
expect(existsSpy).toHaveBeenCalledTimes(1);
expect(readSpy).toHaveBeenCalledTimes(1);
expect(uploadSpy).toHaveBeenCalledTimes(1);
expect(readdirSpy).toHaveBeenCalledTimes(1);
expect(lstatSpy).toHaveBeenCalledTimes(1);
});
it("should run to completion with verbose output", async () => {
it("should run npm install for each package.json", async () => {
const parms = getCommonParmsForPushTests();
parms.arguments.verbose = true;
parms.arguments.targetdir = "//u//escapedDirName";
shellSpy.mockImplementation((session: any, cmd: string, dir: string, stdoutHandler: (data: string) => void) => {
stdoutHandler("Injected stdout shell message");
stdoutHandler("Injected stdout shell message for " + dir);
});
existsSpy.mockImplementation((data: string) => {
if (data.indexOf(".zosattributes") > -1) {
return false;
readdirSpy.mockImplementation((data: string) => {
if (data.endsWith("XXXDIRXXX")) {
return ["file.XXX.1", "package.json", "ZZZDIRZZZ"];
}
if (data.indexOf("package.json") > -1) {
return true;
if (data.endsWith("YYYDIRYYY")) {
return [ "file.YYY.1"];
}
if (data.endsWith("ZZZDIRZZZ")) {
return ["package.json"];
}
if (data.endsWith("node_modules")) {
return ["package.json"];
}
return [ "file.1", "file.2", "XXXDIRXXX", "YYYDIRYYY", "node_modules" ];
});
lstatSpy.mockImplementation((data: string) => {
if (data.endsWith("XXXDIRXXX") || data.endsWith("YYYDIRYYY") || data.endsWith("ZZZDIRZZZ") ||
data.endsWith("node_modules")) {
return IS_DIRECTORY;
}
return IS_NOT_DIRECTORY;
});

await runPushTest("__tests__/__resources__/ExampleBundle01", false, "PUSH operation completed.", parms);

expect(consoleText).toContain("Running 'npm install' in '/u/escapedDirName/12345678/XXXDIRXXX'");
expect(consoleText).toContain("Running 'npm install' in '/u/escapedDirName/12345678/XXXDIRXXX/ZZZDIRZZZ'");
expect(consoleText).not.toContain("Running 'npm install' in '/u/escapedDirName/12345678/node_modules'");
expect(zosMFSpy).toHaveBeenCalledTimes(1);
expect(sshSpy).toHaveBeenCalledTimes(1);
expect(listSpy).toHaveBeenCalledTimes(1);
expect(createSpy).toHaveBeenCalledTimes(1);
expect(shellSpy).toHaveBeenCalledTimes(2);
expect(membersSpy).toHaveBeenCalledTimes(2);
expect(submitSpy).toHaveBeenCalledTimes(1);
expect(existsSpy).toHaveBeenCalledTimes(1);
expect(readSpy).toHaveBeenCalledTimes(1);
expect(uploadSpy).toHaveBeenCalledTimes(1);
expect(readdirSpy).toHaveBeenCalledTimes(4);
expect(lstatSpy).toHaveBeenCalledTimes(10);
});
it("should run to completion with verbose output", async () => {
const parms = getCommonParmsForPushTests();
parms.arguments.verbose = true;
shellSpy.mockImplementation((session: any, cmd: string, dir: string, stdoutHandler: (data: string) => void) => {
stdoutHandler("Injected stdout shell message");
});
readdirSpy.mockImplementation((data: string) => {
return [ "package.json" ];
});

await runPushTest("__tests__/__resources__/ExampleBundle01", false, "PUSH operation completed.", parms);

expect(consoleText).toContain("Making remote bundle directory '/u/ThisDoesNotExist/12345678'");
expect(consoleText).toContain("Accessing contents of the remote bundle directory");
expect(consoleText).toContain("Uploading the bundle contents to the remote bundle directory");
expect(consoleText).toContain("Running npm install for the remote bundle");
expect(consoleText).toContain("Running 'npm install' in '/u/ThisDoesNotExist/12345678'");
expect(consoleText).toContain("Injected stdout shell message");
expect(consoleText).toContain("Deploying bundle '12345678' to CICS");
expect(consoleText).toContain("Deployed bundle '12345678' to CICS");
Expand All @@ -721,9 +759,11 @@ describe("BundlePusher01", () => {
expect(shellSpy).toHaveBeenCalledTimes(1);
expect(membersSpy).toHaveBeenCalledTimes(2);
expect(submitSpy).toHaveBeenCalledTimes(1);
expect(existsSpy).toHaveBeenCalledTimes(2);
expect(existsSpy).toHaveBeenCalledTimes(1);
expect(readSpy).toHaveBeenCalledTimes(1);
expect(uploadSpy).toHaveBeenCalledTimes(1);
expect(readdirSpy).toHaveBeenCalledTimes(1);
expect(lstatSpy).toHaveBeenCalledTimes(1);
});
});

Expand Down
55 changes: 44 additions & 11 deletions src/api/BundlePush/BundlePusher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,8 @@ export class BundlePusher {
// Upload the bundle
await this.uploadBundle(zosMFSession);

// If there's a package.json file in the root of the bundle then run npm install
if (bundle.contains("package.json")) {
await this.runNpmInstall(sshSession);
}
// Run 'npm install' for each package.json file that exists in the bundle
await this.runAllNpmInstalls(sshSession);

// Run DFHDPLOY to install the bundle
await this.deployBundle(zosMFSession, bd);
Expand Down Expand Up @@ -306,14 +304,12 @@ export class BundlePusher {
await this.runSshCommandInRemoteDirectory(sshSession, this.params.arguments.bundledir, "rm -r *");
}

private async runNpmInstall(sshSession: SshSession) {
this.updateStatus("Running npm install for the remote bundle");
private async runSingleNpmInstall(sshSession: SshSession, remoteDirectory: string) {
this.updateStatus("Running 'npm install' in '" + remoteDirectory + "'");

// Attempt to set the PATH for the default location of Node on z/OS. Note,
// we might be able to improve this by looking for the location via the
// architected .profile within the USSCONFIG structure.
// Attempt to set the PATH for the default location of Node on z/OS.
const setNodehomeCmd = "export PATH=\"$PATH:/usr/lpp/IBM/cnj/IBM/node-latest-os390-s390x/bin\"";
await this.runSshCommandInRemoteDirectory(sshSession, this.params.arguments.bundledir, setNodehomeCmd + " && npm install");
await this.runSshCommandInRemoteDirectory(sshSession, remoteDirectory, setNodehomeCmd + " && npm install");
}

private async runSshCommandInRemoteDirectory(sshSession: SshSession, directory: string, sshCommand: string) {
Expand Down Expand Up @@ -400,8 +396,16 @@ export class BundlePusher {

private updateStatus(status: string) {
const PERCENT3 = 3;
const MAX_PROGRESS_BAR_MESSAGE = 70;
this.progressBar.percentComplete += PERCENT3;
this.progressBar.statusMessage = status;

if (status.length > MAX_PROGRESS_BAR_MESSAGE)
{
this.progressBar.statusMessage = status.substring(0, MAX_PROGRESS_BAR_MESSAGE) + "...";
}
else {
this.progressBar.statusMessage = status;
}

if (this.params.arguments.verbose) {
this.params.response.console.log(Buffer.from(status + "\n"));
Expand All @@ -424,4 +428,33 @@ export class BundlePusher {
this.params.response.progress.endBar();
}
}

private findAllPackageJSONDirs(directoryNameLocal: string, directoryNameRemote: string, found: string[]) {
// accumulate an array of all directories / sub-directories that contain a package.json file
const files = this.fs.readdirSync(directoryNameLocal);
for (const currentFile of files) {
const localFileName = this.path.join(directoryNameLocal, currentFile);
const remoteFileName = this.path.posix.join(directoryNameRemote, currentFile);
const stat = this.fs.lstatSync(localFileName);

if (stat.isDirectory() && currentFile !== "node_modules") {
// If we've found a sub-directory, and it's not the special node_modules directory, scan it too.
this.findAllPackageJSONDirs(localFileName, remoteFileName, found);
}
else if (currentFile === "package.json") {
// The current directory has a package.json
found.push(directoryNameRemote);
}
}
}

private async runAllNpmInstalls(sshSession: SshSession) {
// Find each directory/sub-directory that has a package.json file
const packageJsonFiles: string[] = [];
this.findAllPackageJSONDirs(this.localDirectory, this.params.arguments.bundledir, packageJsonFiles);

for (const remoteDirectory of packageJsonFiles) {
await this.runSingleNpmInstall(sshSession, remoteDirectory);
}
}
}