-
Notifications
You must be signed in to change notification settings - Fork 2.6k
/
publishbuildartifacts.ts
165 lines (141 loc) · 6.77 KB
/
publishbuildartifacts.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
import os = require('os');
import path = require('path');
var process = require('process');
import * as tl from 'azure-pipelines-task-lib/task';
import * as tr from 'azure-pipelines-task-lib/toolrunner';
// used for escaping the path to the Invoke-Robocopy.ps1 script that is passed to the powershell command
let pathToScriptPSString = (filePath: string) => {
// remove double quotes
let result: string = filePath.replace(/"/g, '');
// double-up single quotes and enclose in single quotes. this is to create a single-quoted string in powershell.
result = result.replace(/'/g, "''");
return `'${result}'`;
}
// used for escaping file paths that are ultimately passed to robocopy (via the powershell command)
let pathToRobocopyPSString = (filePath: string) => {
// the path needs to be fixed-up due to a robocopy quirk handling trailing backslashes.
//
// according to http://ss64.com/nt/robocopy.html:
// If either the source or desination are a "quoted long foldername" do not include a
// trailing backslash as this will be treated as an escape character, i.e. "C:\some path\"
// will fail but "C:\some path\\" or "C:\some path\." or "C:\some path" will work.
//
// furthermore, PowerShell implicitly double-quotes arguments to external commands when the
// argument contains unquoted spaces.
//
// note, details on PowerShell quoting rules for external commands can be found in the
// source code here:
// https://github.com/PowerShell/PowerShell/blob/v0.6.0/src/System.Management.Automation/engine/NativeCommandParameterBinder.cs
// remove double quotes
let result: string = filePath.replace(/"/g, '');
// append a "." if the path ends with a backslash. e.g. "C:\some path\" -> "C:\some path\."
if (result.endsWith('\\')) {
result += '.';
}
// double-up single quotes and enclose in single quotes. this is to create a single-quoted string in powershell.
result = result.replace(/'/g, "''");
return `'${result}'`;
}
async function run() {
try {
tl.setResourcePath(path.join(__dirname, 'task.json'));
// PathtoPublish is a folder that contains the files
let pathtoPublish: string = tl.getPathInput('PathtoPublish', true, true);
let artifactName: string = tl.getInput('ArtifactName', true);
let artifactType: string = tl.getInput('ArtifactType', true);
const fileCopyOptions = tl.getInput('FileCopyOptions', false);
let hostType = tl.getVariable('system.hostType');
if ((hostType && hostType.toUpperCase() != 'BUILD') && (artifactType.toUpperCase() !== "FILEPATH")) {
tl.setResult(tl.TaskResult.Failed, tl.loc('ErrorHostTypeNotSupported'));
return;
}
artifactType = artifactType.toLowerCase();
let data = {
artifacttype: artifactType,
artifactname: artifactName
};
// upload or copy
if (artifactType === "container") {
data["containerfolder"] = artifactName;
// add localpath to ##vso command's properties for back compat of old Xplat agent
data["localpath"] = pathtoPublish;
tl.command("artifact.upload", data, pathtoPublish);
}
else if (artifactType === "filepath") {
let targetPath: string = tl.getInput('TargetPath', true);
let artifactPath: string = path.join(targetPath, artifactName);
data['artifactlocation'] = targetPath; // artifactlocation for back compat with old xplat agent
if (os.platform() == 'win32') {
tl.mkdirP(artifactPath);
// create the artifact. at this point, mkdirP already succeeded so the path is good.
// the artifact should get cleaned up during retention even if the copy fails in the
// middle
tl.command("artifact.associate", data, targetPath);
let parallel: boolean = tl.getBoolInput('Parallel', false);
let parallelCount = 1;
if (parallel) {
parallelCount = getParallelCount();
}
// copy the files
let script: string = path.join(__dirname, 'Invoke-Robocopy.ps1');
let command: string = `& ${pathToScriptPSString(script)} -Source ${pathToRobocopyPSString(pathtoPublish)} -Target ${pathToRobocopyPSString(artifactPath)} -ParallelCount ${parallelCount}`
if (tl.stats(pathtoPublish).isFile()) {
let parentFolder = path.dirname(pathtoPublish);
let file = path.basename(pathtoPublish);
command = `& ${pathToScriptPSString(script)} -Source ${pathToRobocopyPSString(parentFolder)} -Target ${pathToRobocopyPSString(artifactPath)} -ParallelCount ${parallelCount} -File '${file}'`
}
if (fileCopyOptions) {
command += ` -FileCopyOptions '${fileCopyOptions}'`;
}
let powershell = new tr.ToolRunner('powershell.exe');
powershell.arg('-NoLogo');
powershell.arg('-Sta');
powershell.arg('-NoProfile');
powershell.arg('-NonInteractive');
powershell.arg('-ExecutionPolicy');
powershell.arg('Unrestricted');
powershell.arg('-Command');
powershell.arg(command);
powershell.on('stdout', (buffer: Buffer) => {
process.stdout.write(buffer);
});
powershell.on('stderr', (buffer: Buffer) => {
process.stderr.write(buffer);
});
let execOptions = { silent: true } as tr.IExecOptions;
await powershell.exec(execOptions);
}
else {
// file share artifacts are not currently supported on OSX/Linux.
tl.setResult(tl.TaskResult.Failed, tl.loc('ErrorFileShareLinux'));
return;
}
}
}
catch (err) {
tl.setResult(tl.TaskResult.Failed, tl.loc('PublishBuildArtifactsFailed', err.message));
}
}
function getParallelCount(): number {
let result = 8;
let inputValue: string = tl.getInput('ParallelCount', false);
if (Number.isNaN(Number(inputValue))) {
tl.warning(tl.loc('UnexpectedParallelCount', inputValue));
}
else {
let parsedInput = parseInt(inputValue);
if (parsedInput < 1) {
tl.warning(tl.loc('UnexpectedParallelCount', parsedInput));
result = 1;
}
else if (parsedInput > 128) {
tl.warning(tl.loc('UnexpectedParallelCount', parsedInput));
result = 128;
}
else {
result = parsedInput;
}
}
return result;
}
run();