Skip to content

Commit

Permalink
Update npm task with install/publish/custom commands
Browse files Browse the repository at this point in the history
  • Loading branch information
aldoms committed May 31, 2017
1 parent 3fbf525 commit d7e901b
Show file tree
Hide file tree
Showing 38 changed files with 1,320 additions and 695 deletions.
52 changes: 39 additions & 13 deletions Tasks/Npm/Strings/resources.resjson/en-US/resources.resjson
Original file line number Diff line number Diff line change
@@ -1,17 +1,43 @@
{
"loc.friendlyName": "npm",
"loc.helpMarkDown": "[More Information](https://go.microsoft.com/fwlink/?LinkID=613746)",
"loc.description": "Run an npm command",
"loc.description": "Install and publish npm packages, or run an npm command. Supports npmjs.com and authenticated registries like Package Management.",
"loc.instanceNameFormat": "npm $(command)",
"loc.input.label.cwd": "working folder",
"loc.input.help.cwd": "Working directory where the npm command is run. Defaults to the root of the repo.",
"loc.input.label.command": "npm command",
"loc.input.label.arguments": "arguments",
"loc.input.help.arguments": "Additional arguments passed to npm.",
"loc.messages.InvalidCommand": "Only one command should be used for npm. Use 'Arguments' input for additional arguments.",
"loc.messages.NpmReturnCode": "npm exited with return code: %d",
"loc.messages.NpmFailed": "npm failed with error: %s",
"loc.messages.NpmConfigFailed": "Failed to log the npm config information. Error : %s",
"loc.messages.NpmAuthFailed": "Failed to get the required authentication tokens for npm. Error: %s",
"loc.messages.BuildCredentialsWarn": "Could not determine credentials to use for npm"
}
"loc.group.displayName.customRegistries": "Custom registries and authentication",
"loc.group.displayName.publishRegistries": "Destination registry and authentication",
"loc.input.label.command": "Command",
"loc.input.help.command": "The command and arguments which will be passed to npm for execution.",
"loc.input.label.workingDir": "Working folder with package.json",
"loc.input.help.workingDir": "Path to the folder containing the target package.json and .npmrc files. Select the folder, not the file e.g. \"/packages/mypackage\".",
"loc.input.label.customCommand": "Command and arguments",
"loc.input.help.customCommand": "Custom command to run, e.g. \"dist-tag ls mypackage\".",
"loc.input.label.customRegistry": "Registries to use",
"loc.input.help.customRegistry": "You can either commit a .npmrc file to your source code repository and set its path here or select a registry from VSTS here.",
"loc.input.label.customFeed": "Use packages from this VSTS/TFS registry",
"loc.input.help.customFeed": "Include the selected feed in the generated .npmrc.",
"loc.input.label.customEndpoint": "Credentials for registries outside this account/collection",
"loc.input.help.customEndpoint": "Credentials to use for external registries located in the project's .npmrc. For registries in this account/collection, leave this blank; the build’s credentials are used automatically.",
"loc.input.label.publishRegistry": "Registry location",
"loc.input.help.publishRegistry": "Registry the command will target.",
"loc.input.label.publishFeed": "Target registry",
"loc.input.help.publishFeed": "Select a registry hosted in this account. You must have Package Management installed and licensed to select a registry here.",
"loc.input.label.publishEndpoint": "External Registry",
"loc.input.help.publishEndpoint": "Credentials to use for publishing to an external registry.",
"loc.messages.FoundBuildCredentials": "Found build credentials",
"loc.messages.NoBuildCredentials": "Could not find build credentials",
"loc.messages.UnknownCommand": "Unknown command: %s",
"loc.messages.MultipleProjectConfigs": "More than one project .npmrc found in $s",
"loc.messages.ServiceEndpointNotDefined": "Couldn't find Service Endpoint, make sure the selected endpoint still exists.",
"loc.messages.ServiceEndpointUrlNotDefined": "Couldn't find Url for Service Endpoint, make sure Service Endpoint is correctly configured.",
"loc.messages.SavingFile": "Saving file %s",
"loc.messages.RestoringFile": "Restoring file %s",
"loc.messages.PublishFeed": "Publishing to internal feed",
"loc.messages.PublishExternal": "Publishing to external registry",
"loc.messages.UseFeed": "Using internal feed",
"loc.messages.UseNpmrc": "Using registries in .npmrc",
"loc.messages.PublishRegistry": "Publishing to registry: %s",
"loc.messages.UsingRegistry": "Using registry: %s",
"loc.messages.AddingAuthRegistry": "Adding auth for registry: %s",
"loc.messages.FoundLocalRegistries": "Found %d registries in this account/collection",
"loc.messages.ForcePackagingUrl": "Packaging collection url forced to: %s"
}
264 changes: 191 additions & 73 deletions Tasks/Npm/Tests/L0.ts
Original file line number Diff line number Diff line change
@@ -1,138 +1,256 @@
import * as path from 'path';
import * as assert from 'assert';
import * as fs from 'fs';
import * as path from 'path';
import * as Q from 'q';

import * as mockery from 'mockery';
import * as ttm from 'vsts-task-lib/mock-test';
import {NpmMockHelper} from './NpmMockHelper';

import { NpmMockHelper } from './NpmMockHelper';

describe('Npm Task', function () {
before(() => {
mockery.enable({
useCleanCache: true,
warnOnUnregistered: false
} as mockery.MockeryEnableArgs);
});

after(() => {
mockery.disable();
});

beforeEach(() => {
mockery.resetCache();
});

afterEach(() => {
mockery.deregisterAll();
});

/* Current behavior */
it("should execute 'npm config list' successfully", (done: MochaDone) => {
// custom
it('custom command should return npm version', (done: MochaDone) => {
this.timeout(1000);
let tp = path.join(__dirname, 'test-configlist.js')
let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp);
let tp = path.join(__dirname, 'custom-version.js');
let tr = new ttm.MockTestRunner(tp);

tr.run();

assert.equal(tr.invokedToolCount, 3, 'should have run vsts-npm-auth, npm config list and npm command');
assert(tr.ran(`${NpmMockHelper.NpmCmdPath} config list`), 'it should have run npm');
assert(tr.stdOutContained('; cli configs'), "should have npm config output");
assert.equal(tr.errorIssues.length, 0, "should have no errors");
// This assert is skipped due to a test mocking issue on non windows platforms.
// assert.equal(tr.warningIssues.length, 0, "should have no warnings: " + tr.warningIssues.join(','));
assert(tr.succeeded, 'should have succeeded');
assert.equal(tr.invokedToolCount, 2, 'task should have run npm');
assert(tr.succeeded, 'task should have succeeded');
assert(tr.stdOutContained('; debug cli configs'), 'should have debug npm config output');
assert(tr.stdOutContained('; cli configs') === false, 'should not have regular npm config output');

done();
});

it('should pass when no arguments are supplied', (done: MochaDone) => {

// show config
it('should execute \'npm config list\' without debug switch', (done: MochaDone) => {
this.timeout(1000);
let tp = path.join(__dirname, 'test-commandWithoutArguments.js')
let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp);
let tp = path.join(__dirname, 'config-noDebug.js');
let tr = new ttm.MockTestRunner(tp);

tr.run();

assert.equal(tr.invokedToolCount, 3, 'should have run npm');
assert(tr.ran(`${NpmMockHelper.NpmCmdPath} root`), 'it should have run npm');
assert(tr.stdOutContained(`${NpmMockHelper.FakeWorkingDirectory}`), "should have npm root output - working directory");
assert(tr.stdOutContained("node_modules"), "should have npm root output - 'node_modules' directory");
assert.equal(tr.errorIssues.length, 0, "should have no errors");
// This assert is skipped due to a test mocking issue on non windows platforms.
// assert.equal(tr.warningIssues.length, 0, "should have no warnings: " + tr.warningIssues.join(','));
assert(tr.succeeded, 'should have succeeded');
assert.equal(tr.invokedToolCount, 2, 'task should have run npm');
assert(tr.succeeded, 'task should have succeeded');
assert(tr.stdOutContained('; cli configs'), 'should have regular npm config output');
assert(tr.stdOutContained('; debug cli configs') === false, 'should not have debug npm config output');

done();
});

it('should fail when command contains spaces', (done: MochaDone) => {

// install command
it('should fail when npm fails', (done: MochaDone) => {
this.timeout(1000);
let tp = path.join(__dirname, 'test-commandContainsSpaces.js')
let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp);
let tp = path.join(__dirname, 'install-npmFailure.js');
let tr = new ttm.MockTestRunner(tp);

tr.run();

assert.equal(tr.invokedToolCount, 0, 'should not have run npm');
assert(tr.failed, 'should have failed');
assert(tr.failed, 'task should have failed');

done();
});
it('should fail when task fails', (done: MochaDone) => {

it ('install using local feed', (done: MochaDone) => {
this.timeout(1000);
let tp = path.join(__dirname, 'test-npmFailure.js')
let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp);
let tp = path.join(__dirname, 'install-feed.js');
let tr = new ttm.MockTestRunner(tp);

tr.run();

assert.equal(tr.invokedToolCount, 3, 'should have run npm');
assert(tr.failed, 'should have failed');
assert.equal(tr.invokedToolCount, 2, 'task should have run npm');
assert(tr.stdOutContained('npm install successful'), 'npm should have installed the package');
assert(tr.succeeded, 'task should have succeeded');

done();
});

/* Deprecated behavior */
it("should execute 'npm config list' successfully (deprecated task)", (done: MochaDone) => {
it ('install using npmrc', (done: MochaDone) => {
this.timeout(1000);
let tp = path.join(__dirname, 'test-configlist-deprecated.js')
let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp);
let tp = path.join(__dirname, 'install-npmrc.js');
let tr = new ttm.MockTestRunner(tp);

tr.run();

assert.equal(tr.invokedToolCount, 1, 'should have run npm');
assert(tr.ran(`${NpmMockHelper.NpmCmdPath} config list`), 'it should have run npm');
assert(tr.stdOutContained('; cli configs'), "should have npm config output");
assert.equal(tr.errorIssues.length, 0, "should have no errors");
assert.equal(tr.warningIssues.length, 0, "should have no warnings: " + tr.warningIssues.join(','));
assert(tr.succeeded, 'should have succeeded');
assert.equal(tr.invokedToolCount, 2, 'task should have run npm');
assert(tr.stdOutContained('npm install successful'), 'npm should have installed the package');
assert(tr.succeeded, 'task should have succeeded');

done();
});

it('should pass when no arguments are supplied (deprecated task)', (done: MochaDone) => {

// publish
it ('publish using feed', (done: MochaDone) => {
this.timeout(1000);
let tp = path.join(__dirname, 'test-commandWithoutArguments-deprecated.js')
let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp);
let tp = path.join(__dirname, 'publish-feed.js');
let tr = new ttm.MockTestRunner(tp);

tr.run();

assert.equal(tr.invokedToolCount, 1, 'should have run npm');
assert(tr.ran(`${NpmMockHelper.NpmCmdPath} root`), 'it should have run npm');
assert(tr.stdOutContained(`${NpmMockHelper.FakeWorkingDirectory}`), "should have npm root output - working directory");
assert(tr.stdOutContained("node_modules"), "should have npm root output - 'node_modules' directory");
assert.equal(tr.errorIssues.length, 0, "should have no errors");
assert.equal(tr.warningIssues.length, 0, "should have no warnings: " + tr.warningIssues.join(','));
assert(tr.succeeded, 'should have succeeded');
assert.equal(tr.invokedToolCount, 2, 'task should have run npm');
assert(tr.stdOutContained('npm publish successful'), 'npm should have installed the package');
assert(tr.succeeded, 'task should have succeeded');

done();
});
it('should fail when command contains spaces (deprecated task)', (done: MochaDone) => {

it ('publish using external registry', (done: MochaDone) => {
this.timeout(1000);
let tp = path.join(__dirname, 'test-commandContainsSpaces-deprecated.js')
let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp);
let tp = path.join(__dirname, 'publish-external.js');
let tr = new ttm.MockTestRunner(tp);

tr.run();

assert.equal(tr.invokedToolCount, 0, 'should not have run npm');
assert(tr.failed, 'should have failed');
assert.equal(tr.invokedToolCount, 2, 'task should have run npm');
assert(tr.stdOutContained('npm publish successful'), 'npm should have installed the package');
assert(tr.succeeded, 'task should have succeeded');

done();
});

it('should fail when task fails (deprecated task)', (done: MochaDone) => {
this.timeout(1000);
let tp = path.join(__dirname, 'test-npmFailure-deprecated.js')
let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp);

tr.run();
// util
it('gets npm registries', (done: MochaDone) => {
let mockTask = {
writeFile: (file: string, data: string | Buffer) => {
// no-op
}
};
mockery.registerMock('vsts-task-lib/task', mockTask);
let npmrc = `registry=http://example.com
always-auth=true
@scoped:registry=http://scoped.com
//scoped.com/:_authToken=thisIsASecretToken
@scopedTwo:registry=http://scopedTwo.com
; some comments
@scoped:always-auth=true
# more comments`;

let mockFs = {
readFileSync: (path: string) => npmrc
};
mockery.registerMock('fs', mockFs);

assert.equal(tr.invokedToolCount, 1, 'should have run npm');
assert(tr.failed, 'should have failed');
let npmrcParser = require('../npmrcparser');
let registries = npmrcParser.GetRegistries('');

assert.equal(registries.length, 3);
assert.equal(registries[0], 'http://example.com/');
assert.equal(registries[1], 'http://scoped.com/');
assert.equal(registries[2], 'http://scopedTwo.com/');

done();
});

it('gets feed id from VSTS registry', (done: MochaDone) => {
mockery.registerMock('vsts-task-lib/task', {});
let util = require('../util');

assert.equal(util.getFeedIdFromRegistry(
'http://account.visualstudio.com/_packaging/feedId/npm/registry'),
'feedId');
assert.equal(util.getFeedIdFromRegistry(
'http://account.visualstudio.com/_packaging/feedId/npm/registry/'),
'feedId');
assert.equal(util.getFeedIdFromRegistry(
'http://TFSSERVER/_packaging/feedId/npm/registry'),
'feedId');
assert.equal(util.getFeedIdFromRegistry(
'http://TFSSERVER:1234/_packaging/feedId/npm/registry'),
'feedId');

done();
});
});

it('gets correct packaging Url', () => {
let mockTask = {
getVariable: (v: string) => {
if (v === 'System.TeamFoundationCollectionUri') {
return 'http://example.visualstudio.com';
}
},
debug: (message: string) => {
// no-op
},
loc: (key: string) => {
// no-op
}
};
mockery.registerMock('vsts-task-lib/task', mockTask);
let util = require('../util');

return util.getPackagingCollectionUrl().then(u => {
assert.equal(u, 'http://example.pkgs.visualstudio.com/'.toLowerCase());

mockTask.getVariable = (v: string) => 'http://TFSSERVER.com/';
return util.getPackagingCollectionUrl().then(u => {
assert.equal(u, 'http://TFSSERVER.com/'.toLowerCase());

mockTask.getVariable = (v: string) => 'http://serverWithPort:1234';
return util.getPackagingCollectionUrl().then(u => {
assert.equal(u, 'http://serverWithPort:1234/'.toLowerCase());

return;
});
});
});
});

it('gets correct local registries', () => {
let mockParser = {
GetRegistries: (npmrc: string) => [
'http://registry.com/npmRegistry/',
'http://example.pkgs.visualstudio.com/npmRegistry/',
'http://localTFSServer/npmRegistry/'
]
};
mockery.registerMock('./npmrcparser', mockParser);
let mockTask = {
getVariable: (v: string) => {
if (v === 'System.TeamFoundationCollectionUri') {
return 'http://example.visualstudio.com';
}
},
debug: (message: string) => {
// no-op
},
loc: (key: string) => {
// no-op
}
};
mockery.registerMock('vsts-task-lib/task', mockTask);
let util = require('../util');

return util.getLocalRegistries('').then((registries: string[]) => {
assert.equal(registries.length, 1);
assert.equal(registries[0], 'http://example.pkgs.visualstudio.com/npmRegistry/');

mockTask.getVariable = () => 'http://localTFSServer/';
return util.getLocalRegistries('').then((registries: string[]) => {
assert.equal(registries.length, 1);
assert.equal(registries[0], 'http://localTFSServer/npmRegistry/');
});
});
});
});
Loading

0 comments on commit d7e901b

Please sign in to comment.