-
Notifications
You must be signed in to change notification settings - Fork 3.7k
/
publishing.ts
153 lines (131 loc) · 4.05 KB
/
publishing.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
import { AssetManifest, IManifestEntry } from './asset-manifest';
import { IAws } from './aws';
import { IHandlerHost } from './private/asset-handler';
import { DockerFactory } from './private/docker';
import { makeAssetHandler } from './private/handlers';
import { EventType, IPublishProgress, IPublishProgressListener } from './progress';
export interface AssetPublishingOptions {
/**
* Entry point for AWS client
*/
readonly aws: IAws;
/**
* Listener for progress events
*
* @default No listener
*/
readonly progressListener?: IPublishProgressListener;
/**
* Whether to throw at the end if there were errors
*
* @default true
*/
readonly throwOnError?: boolean;
/**
* Whether to publish in parallel
*
* @default false
*/
readonly publishInParallel?: boolean;
}
/**
* A failure to publish an asset
*/
export interface FailedAsset {
/**
* The asset that failed to publish
*/
readonly asset: IManifestEntry;
/**
* The failure that occurred
*/
readonly error: Error;
}
export class AssetPublishing implements IPublishProgress {
/**
* The message for the IPublishProgress interface
*/
public message: string = 'Starting';
/**
* The current asset for the IPublishProgress interface
*/
public currentAsset?: IManifestEntry;
public readonly failures = new Array<FailedAsset>();
private readonly assets: IManifestEntry[];
private readonly totalOperations: number;
private completedOperations: number = 0;
private aborted = false;
private readonly handlerHost: IHandlerHost;
private readonly publishInParallel: boolean;
constructor(private readonly manifest: AssetManifest, private readonly options: AssetPublishingOptions) {
this.assets = manifest.entries;
this.totalOperations = this.assets.length;
this.publishInParallel = options.publishInParallel ?? false;
const self = this;
this.handlerHost = {
aws: this.options.aws,
get aborted() { return self.aborted; },
emitMessage(t, m) { self.progressEvent(t, m); },
dockerFactory: new DockerFactory(),
};
}
/**
* Publish all assets from the manifest
*/
public async publish(): Promise<void> {
if (this.publishInParallel) {
await Promise.all(this.assets.map(async (asset) => this.publishAsset(asset)));
} else {
for (const asset of this.assets) {
if (!await this.publishAsset(asset)) {
break;
}
}
}
if ((this.options.throwOnError ?? true) && this.failures.length > 0) {
throw new Error(`Error publishing: ${this.failures.map(e => e.error.message)}`);
}
}
/**
* Publish an asset.
* @param asset The asset to publish
* @returns false when publishing should stop
*/
private async publishAsset(asset: IManifestEntry) {
try {
if (this.progressEvent(EventType.START, `Publishing ${asset.id}`)) { return false; }
const handler = makeAssetHandler(this.manifest, asset, this.handlerHost);
await handler.publish();
if (this.aborted) {
throw new Error('Aborted');
}
this.completedOperations++;
if (this.progressEvent(EventType.SUCCESS, `Published ${asset.id}`)) { return false; }
} catch (e) {
this.failures.push({ asset, error: e });
this.completedOperations++;
if (this.progressEvent(EventType.FAIL, e.message)) { return false; }
}
return true;
}
public get percentComplete() {
if (this.totalOperations === 0) { return 100; }
return Math.floor((this.completedOperations / this.totalOperations) * 100);
}
public abort(): void {
this.aborted = true;
}
public get hasFailures() {
return this.failures.length > 0;
}
/**
* Publish a progress event to the listener, if present.
*
* Returns whether an abort is requested. Helper to get rid of repetitive code in publish().
*/
private progressEvent(event: EventType, message: string): boolean {
this.message = message;
if (this.options.progressListener) { this.options.progressListener.onPublishEvent(event, this); }
return this.aborted;
}
}