@pipeline-builder/api-server 4.1.22 → 4.1.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/api/index.d.ts +1 -1
- package/lib/api/index.js +2 -2
- package/lib/api/metrics.d.ts +23 -0
- package/lib/api/metrics.js +73 -54
- package/lib/api/server.js +9 -9
- package/lib/http/sse-connection-manager.d.ts +14 -84
- package/lib/http/sse-connection-manager.js +31 -144
- package/lib/http/sse-ticket-broker.d.ts +127 -0
- package/lib/http/sse-ticket-broker.js +170 -0
- package/lib/testing/exports.json +1 -0
- package/package.json +4 -4
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minting and redeeming the short-lived SSE tickets that authorize a log
|
|
3
|
+
* stream, plus the stream-ownership binding those mints are checked against.
|
|
4
|
+
*
|
|
5
|
+
* Separate from {@link SSEManager} because it shares nothing with the
|
|
6
|
+
* connection registry: its state lives entirely in the injected
|
|
7
|
+
* {@link SseTicketStore} (Redis in a multi-pod deploy), and every rule in here
|
|
8
|
+
* is an AUTHORIZATION rule — who may open which subject's stream — which is
|
|
9
|
+
* easier to audit as one unit than interleaved with connection bookkeeping.
|
|
10
|
+
*/
|
|
11
|
+
import { type SseTicketStore } from '@pipeline-builder/api-core';
|
|
12
|
+
/** Result of {@link SseTicketBroker.createTicket}. */
|
|
13
|
+
export type CreateTicketResult = {
|
|
14
|
+
ok: true;
|
|
15
|
+
ticket: string;
|
|
16
|
+
} | {
|
|
17
|
+
ok: false;
|
|
18
|
+
reason: 'org-limit' | 'capacity' | 'forbidden';
|
|
19
|
+
};
|
|
20
|
+
export interface SseTicketBrokerOptions {
|
|
21
|
+
/** Ticket + stream-ownership backend. Defaults to an in-memory store (single
|
|
22
|
+
* replica only), built on first use. Inject an api-core
|
|
23
|
+
* `createEnvSseTicketStore(...)` so mint/redeem and stream ownership work
|
|
24
|
+
* across pods. */
|
|
25
|
+
store?: SseTicketStore;
|
|
26
|
+
/** Cap on live tickets across the process, for the DEFAULT in-memory store.
|
|
27
|
+
* Ignored when `store` is injected (the store carries its own caps). */
|
|
28
|
+
maxTotal?: number;
|
|
29
|
+
/** Per-org cap on live tickets, for the DEFAULT in-memory store. Ignored when
|
|
30
|
+
* `store` is injected. */
|
|
31
|
+
maxPerOrg?: number;
|
|
32
|
+
/** Ticket TTL in ms for the default store. */
|
|
33
|
+
ttlMs?: number;
|
|
34
|
+
/** TTL for a stream-ownership binding (default 1h). Long enough to outlive a
|
|
35
|
+
* build; the producer re-binds as needed. */
|
|
36
|
+
ownerTtlMs?: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Canonical form of a stream subject id for map/store keys: the id is allowed
|
|
40
|
+
* in two renderings (a dashed api-server uuid and nginx's undashed
|
|
41
|
+
* `$request_id`), and normalizing makes them compare equal. Callers are
|
|
42
|
+
* expected to have already format-validated against `SSE_REQUEST_ID_RE`.
|
|
43
|
+
*/
|
|
44
|
+
export declare function normalizeRequestId(requestId: string): string;
|
|
45
|
+
/**
|
|
46
|
+
* Canonical form of an orgId for ownership equality checks: trimmed and
|
|
47
|
+
* lowercased. The stream PRODUCER (bindStreamOwner) and the ticket-minting
|
|
48
|
+
* CONSUMER (createTicket) run in different services, so a casing / whitespace
|
|
49
|
+
* drift between how each renders the same org would otherwise make the owner
|
|
50
|
+
* comparison in {@link SseTicketBroker.createTicket} false-`forbidden` the real
|
|
51
|
+
* owner. Normalizing both sides — mirroring {@link normalizeRequestId} — closes that.
|
|
52
|
+
*/
|
|
53
|
+
export declare function normalizeOrgId(orgId: string): string;
|
|
54
|
+
export declare class SseTicketBroker {
|
|
55
|
+
private storeInstance?;
|
|
56
|
+
/** True when this broker built its store (so shutdown stops it). */
|
|
57
|
+
private ownsStore;
|
|
58
|
+
private readonly maxTotal;
|
|
59
|
+
private readonly maxPerOrg;
|
|
60
|
+
private readonly ttlMs;
|
|
61
|
+
private readonly ownerTtlMs;
|
|
62
|
+
constructor(options?: SseTicketBrokerOptions);
|
|
63
|
+
/** The ticket store, building the in-memory default on first use. */
|
|
64
|
+
private get store();
|
|
65
|
+
/**
|
|
66
|
+
* Record the org that OWNS a stream subject. The stream PRODUCER calls this
|
|
67
|
+
* when it creates a build-log stream so that ticket minting can assert the
|
|
68
|
+
* caller's org owns the subject (see {@link createTicket}). Backed by the
|
|
69
|
+
* injected ticket store, so when a Redis store is wired the platform producer
|
|
70
|
+
* (a different service sharing the same Redis) can bind ownership that this
|
|
71
|
+
* service reads — closing the cross-tenant attach gap where any org could mint
|
|
72
|
+
* a ticket for a guessed requestId.
|
|
73
|
+
*
|
|
74
|
+
* @param requestId - The stream subject (format-validated by the caller).
|
|
75
|
+
* @param orgId - The owning org (normalized internally via normalizeOrgId).
|
|
76
|
+
*/
|
|
77
|
+
bindStreamOwner(requestId: string, orgId: string): Promise<void>;
|
|
78
|
+
/**
|
|
79
|
+
* Mint a short-lived, single-use SSE ticket bound to `orgId` AND a specific
|
|
80
|
+
* stream subject (`requestId`). Clients POST to obtain one (JWT-authenticated)
|
|
81
|
+
* for the exact stream they intend to open, then open the EventSource with
|
|
82
|
+
* `?ticket=<t>` so the JWT never lands in a query string / access log. The
|
|
83
|
+
* ticket can then only be consumed to open that one subject's stream — see
|
|
84
|
+
* {@link consumeTicket} — which is what enforces per-subject authorization.
|
|
85
|
+
*
|
|
86
|
+
* ORG-OWNERSHIP: if a stream owner has been bound for this subject (via
|
|
87
|
+
* {@link bindStreamOwner}) and it is a DIFFERENT org, minting is refused
|
|
88
|
+
* (`reason: 'forbidden'`) — an org cannot mint a ticket for another org's
|
|
89
|
+
* stream even if it guesses the requestId. When no owner is bound (producer
|
|
90
|
+
* wiring not yet present), minting falls back to binding the ticket to the
|
|
91
|
+
* caller's own org, preserving current behavior.
|
|
92
|
+
*
|
|
93
|
+
* Bounded by the ticket store's live-ticket caps (total and per org). An
|
|
94
|
+
* ownership lookup the store can't answer is refused as `capacity`.
|
|
95
|
+
*
|
|
96
|
+
* @param orgId - Owning org (normalized internally via normalizeOrgId).
|
|
97
|
+
* @param requestId - The build-log stream subject this ticket authorizes.
|
|
98
|
+
* The caller must have format-validated it (see `SSE_REQUEST_ID_RE`).
|
|
99
|
+
* @returns `{ ok: true, ticket }` on success, or `{ ok: false, reason }`
|
|
100
|
+
* where reason is `'org-limit'`, `'capacity'`, or `'forbidden'`.
|
|
101
|
+
*/
|
|
102
|
+
createTicket(orgId: string, requestId: string): Promise<CreateTicketResult>;
|
|
103
|
+
/**
|
|
104
|
+
* Validate and CONSUME a ticket for a specific stream subject. Single-use:
|
|
105
|
+
* the ticket is deleted whether or not it turns out to be valid, so a replay
|
|
106
|
+
* of the same value always fails. Returns the bound org on success, or null
|
|
107
|
+
* when the ticket is unknown / already-used / expired / OR was minted for a
|
|
108
|
+
* DIFFERENT `requestId` than the one being opened.
|
|
109
|
+
*
|
|
110
|
+
* The subject check is the authorization fix: without it, a ticket minted for
|
|
111
|
+
* one stream could be presented to open ANY stream the holder's org could
|
|
112
|
+
* name, letting an authenticated org attach to another org's log stream by
|
|
113
|
+
* guessing its requestId. The generic 401 the caller returns does not
|
|
114
|
+
* distinguish "unknown ticket" from "wrong subject", so it leaks nothing
|
|
115
|
+
* about which requestIds exist.
|
|
116
|
+
*
|
|
117
|
+
* @param ticketId - The opaque ticket value from `?ticket=`.
|
|
118
|
+
* @param requestId - The stream subject from the URL path (`:requestId`),
|
|
119
|
+
* already format-validated by the caller.
|
|
120
|
+
*/
|
|
121
|
+
consumeTicket(ticketId: string, requestId: string): Promise<{
|
|
122
|
+
orgId: string;
|
|
123
|
+
} | null>;
|
|
124
|
+
/** Stop the store, but only the one this broker built — an injected store is
|
|
125
|
+
* the caller's to close. */
|
|
126
|
+
shutdown(): void;
|
|
127
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Minting and redeeming the short-lived SSE tickets that authorize a log
|
|
5
|
+
* stream, plus the stream-ownership binding those mints are checked against.
|
|
6
|
+
*
|
|
7
|
+
* Separate from {@link SSEManager} because it shares nothing with the
|
|
8
|
+
* connection registry: its state lives entirely in the injected
|
|
9
|
+
* {@link SseTicketStore} (Redis in a multi-pod deploy), and every rule in here
|
|
10
|
+
* is an AUTHORIZATION rule — who may open which subject's stream — which is
|
|
11
|
+
* easier to audit as one unit than interleaved with connection bookkeeping.
|
|
12
|
+
*/
|
|
13
|
+
import { createLogger, createMemorySseTicketStore, envSseTicketCaps, SSE_TICKET_TTL_MS, errorMessage } from '@pipeline-builder/api-core';
|
|
14
|
+
import { incCounter } from '../api/metrics.js';
|
|
15
|
+
const logger = createLogger('sse-tickets');
|
|
16
|
+
/**
|
|
17
|
+
* Canonical form of a stream subject id for map/store keys: the id is allowed
|
|
18
|
+
* in two renderings (a dashed api-server uuid and nginx's undashed
|
|
19
|
+
* `$request_id`), and normalizing makes them compare equal. Callers are
|
|
20
|
+
* expected to have already format-validated against `SSE_REQUEST_ID_RE`.
|
|
21
|
+
*/
|
|
22
|
+
export function normalizeRequestId(requestId) {
|
|
23
|
+
return requestId.replace(/-/g, '').toLowerCase();
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Canonical form of an orgId for ownership equality checks: trimmed and
|
|
27
|
+
* lowercased. The stream PRODUCER (bindStreamOwner) and the ticket-minting
|
|
28
|
+
* CONSUMER (createTicket) run in different services, so a casing / whitespace
|
|
29
|
+
* drift between how each renders the same org would otherwise make the owner
|
|
30
|
+
* comparison in {@link SseTicketBroker.createTicket} false-`forbidden` the real
|
|
31
|
+
* owner. Normalizing both sides — mirroring {@link normalizeRequestId} — closes that.
|
|
32
|
+
*/
|
|
33
|
+
export function normalizeOrgId(orgId) {
|
|
34
|
+
return orgId.trim().toLowerCase();
|
|
35
|
+
}
|
|
36
|
+
export class SseTicketBroker {
|
|
37
|
+
storeInstance;
|
|
38
|
+
/** True when this broker built its store (so shutdown stops it). */
|
|
39
|
+
ownsStore = false;
|
|
40
|
+
maxTotal;
|
|
41
|
+
maxPerOrg;
|
|
42
|
+
ttlMs;
|
|
43
|
+
ownerTtlMs;
|
|
44
|
+
constructor(options = {}) {
|
|
45
|
+
const caps = envSseTicketCaps();
|
|
46
|
+
this.maxTotal = options.maxTotal ?? caps.maxTotal;
|
|
47
|
+
this.maxPerOrg = options.maxPerOrg ?? caps.maxPerOrg;
|
|
48
|
+
this.ttlMs = options.ttlMs ?? SSE_TICKET_TTL_MS;
|
|
49
|
+
this.ownerTtlMs = options.ownerTtlMs ?? 3_600_000; // 1 hour
|
|
50
|
+
this.storeInstance = options.store;
|
|
51
|
+
}
|
|
52
|
+
/** The ticket store, building the in-memory default on first use. */
|
|
53
|
+
get store() {
|
|
54
|
+
if (!this.storeInstance) {
|
|
55
|
+
this.storeInstance = createMemorySseTicketStore({
|
|
56
|
+
ttlMs: this.ttlMs,
|
|
57
|
+
maxTotal: this.maxTotal,
|
|
58
|
+
maxPerOrg: this.maxPerOrg,
|
|
59
|
+
});
|
|
60
|
+
this.ownsStore = true;
|
|
61
|
+
}
|
|
62
|
+
return this.storeInstance;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Record the org that OWNS a stream subject. The stream PRODUCER calls this
|
|
66
|
+
* when it creates a build-log stream so that ticket minting can assert the
|
|
67
|
+
* caller's org owns the subject (see {@link createTicket}). Backed by the
|
|
68
|
+
* injected ticket store, so when a Redis store is wired the platform producer
|
|
69
|
+
* (a different service sharing the same Redis) can bind ownership that this
|
|
70
|
+
* service reads — closing the cross-tenant attach gap where any org could mint
|
|
71
|
+
* a ticket for a guessed requestId.
|
|
72
|
+
*
|
|
73
|
+
* @param requestId - The stream subject (format-validated by the caller).
|
|
74
|
+
* @param orgId - The owning org (normalized internally via normalizeOrgId).
|
|
75
|
+
*/
|
|
76
|
+
async bindStreamOwner(requestId, orgId) {
|
|
77
|
+
await this.store.bindOwner(normalizeRequestId(requestId), normalizeOrgId(orgId), this.ownerTtlMs);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Mint a short-lived, single-use SSE ticket bound to `orgId` AND a specific
|
|
81
|
+
* stream subject (`requestId`). Clients POST to obtain one (JWT-authenticated)
|
|
82
|
+
* for the exact stream they intend to open, then open the EventSource with
|
|
83
|
+
* `?ticket=<t>` so the JWT never lands in a query string / access log. The
|
|
84
|
+
* ticket can then only be consumed to open that one subject's stream — see
|
|
85
|
+
* {@link consumeTicket} — which is what enforces per-subject authorization.
|
|
86
|
+
*
|
|
87
|
+
* ORG-OWNERSHIP: if a stream owner has been bound for this subject (via
|
|
88
|
+
* {@link bindStreamOwner}) and it is a DIFFERENT org, minting is refused
|
|
89
|
+
* (`reason: 'forbidden'`) — an org cannot mint a ticket for another org's
|
|
90
|
+
* stream even if it guesses the requestId. When no owner is bound (producer
|
|
91
|
+
* wiring not yet present), minting falls back to binding the ticket to the
|
|
92
|
+
* caller's own org, preserving current behavior.
|
|
93
|
+
*
|
|
94
|
+
* Bounded by the ticket store's live-ticket caps (total and per org). An
|
|
95
|
+
* ownership lookup the store can't answer is refused as `capacity`.
|
|
96
|
+
*
|
|
97
|
+
* @param orgId - Owning org (normalized internally via normalizeOrgId).
|
|
98
|
+
* @param requestId - The build-log stream subject this ticket authorizes.
|
|
99
|
+
* The caller must have format-validated it (see `SSE_REQUEST_ID_RE`).
|
|
100
|
+
* @returns `{ ok: true, ticket }` on success, or `{ ok: false, reason }`
|
|
101
|
+
* where reason is `'org-limit'`, `'capacity'`, or `'forbidden'`.
|
|
102
|
+
*/
|
|
103
|
+
async createTicket(orgId, requestId) {
|
|
104
|
+
// Ownership gate first — a cross-tenant mint attempt should never even
|
|
105
|
+
// consume cap budget. Only enforced when an owner is actually bound. Both
|
|
106
|
+
// sides are org-normalized so a producer/consumer casing drift can't
|
|
107
|
+
// false-`forbidden` the real owner (mirrors requestId normalization).
|
|
108
|
+
const normalized = normalizeRequestId(requestId);
|
|
109
|
+
const normalizedOrg = normalizeOrgId(orgId);
|
|
110
|
+
let owner;
|
|
111
|
+
try {
|
|
112
|
+
owner = await this.store.getOwner(normalized);
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
// Can't tell who owns the subject — refuse rather than mint unchecked.
|
|
116
|
+
logger.warn('SSE ticket refused: stream ownership lookup failed', { error: errorMessage(err) });
|
|
117
|
+
incCounter('sse_ticket_rejected_total', { reason: 'capacity' });
|
|
118
|
+
return { ok: false, reason: 'capacity' };
|
|
119
|
+
}
|
|
120
|
+
if (owner && owner !== normalizedOrg) {
|
|
121
|
+
logger.warn(`SSE ticket refused: org ${normalizedOrg} does not own stream subject`);
|
|
122
|
+
incCounter('sse_ticket_rejected_total', { reason: 'forbidden' });
|
|
123
|
+
return { ok: false, reason: 'forbidden' };
|
|
124
|
+
}
|
|
125
|
+
const issued = await this.store.issue(normalizedOrg, normalized);
|
|
126
|
+
if (!issued.ok) {
|
|
127
|
+
const reason = issued.reason === 'org' ? 'org-limit' : 'capacity';
|
|
128
|
+
logger.warn(`SSE ticket refused (${reason}) for ${normalizedOrg}`);
|
|
129
|
+
incCounter('sse_ticket_rejected_total', { reason });
|
|
130
|
+
return { ok: false, reason };
|
|
131
|
+
}
|
|
132
|
+
return { ok: true, ticket: issued.ticket };
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Validate and CONSUME a ticket for a specific stream subject. Single-use:
|
|
136
|
+
* the ticket is deleted whether or not it turns out to be valid, so a replay
|
|
137
|
+
* of the same value always fails. Returns the bound org on success, or null
|
|
138
|
+
* when the ticket is unknown / already-used / expired / OR was minted for a
|
|
139
|
+
* DIFFERENT `requestId` than the one being opened.
|
|
140
|
+
*
|
|
141
|
+
* The subject check is the authorization fix: without it, a ticket minted for
|
|
142
|
+
* one stream could be presented to open ANY stream the holder's org could
|
|
143
|
+
* name, letting an authenticated org attach to another org's log stream by
|
|
144
|
+
* guessing its requestId. The generic 401 the caller returns does not
|
|
145
|
+
* distinguish "unknown ticket" from "wrong subject", so it leaks nothing
|
|
146
|
+
* about which requestIds exist.
|
|
147
|
+
*
|
|
148
|
+
* @param ticketId - The opaque ticket value from `?ticket=`.
|
|
149
|
+
* @param requestId - The stream subject from the URL path (`:requestId`),
|
|
150
|
+
* already format-validated by the caller.
|
|
151
|
+
*/
|
|
152
|
+
async consumeTicket(ticketId, requestId) {
|
|
153
|
+
const ticket = await this.store.consume(ticketId); // single-use
|
|
154
|
+
if (!ticket)
|
|
155
|
+
return null;
|
|
156
|
+
// Subject binding — reject a ticket presented for a subject it was not
|
|
157
|
+
// minted for (including an unbound, org-channel ticket). Both sides are
|
|
158
|
+
// normalized so dashed/undashed forms match.
|
|
159
|
+
if (ticket.subject !== normalizeRequestId(requestId))
|
|
160
|
+
return null;
|
|
161
|
+
return { orgId: ticket.orgId };
|
|
162
|
+
}
|
|
163
|
+
/** Stop the store, but only the one this broker built — an injected store is
|
|
164
|
+
* the caller's to close. */
|
|
165
|
+
shutdown() {
|
|
166
|
+
if (this.ownsStore)
|
|
167
|
+
this.storeInstance?.stop();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3NlLXRpY2tldC1icm9rZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvaHR0cC9zc2UtdGlja2V0LWJyb2tlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBRXRDOzs7Ozs7Ozs7R0FTRztBQUVILE9BQU8sRUFBRSxZQUFZLEVBQUUsMEJBQTBCLEVBQUUsZ0JBQWdCLEVBQUUsaUJBQWlCLEVBQXVCLFlBQVksRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBQzlKLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQztBQUUvQyxNQUFNLE1BQU0sR0FBRyxZQUFZLENBQUMsYUFBYSxDQUFDLENBQUM7QUEwQjNDOzs7OztHQUtHO0FBQ0gsTUFBTSxVQUFVLGtCQUFrQixDQUFDLFNBQWlCO0lBQ2xELE9BQU8sU0FBUyxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDLENBQUMsV0FBVyxFQUFFLENBQUM7QUFDbkQsQ0FBQztBQUVEOzs7Ozs7O0dBT0c7QUFDSCxNQUFNLFVBQVUsY0FBYyxDQUFDLEtBQWE7SUFDMUMsT0FBTyxLQUFLLENBQUMsSUFBSSxFQUFFLENBQUMsV0FBVyxFQUFFLENBQUM7QUFDcEMsQ0FBQztBQUVELE1BQU0sT0FBTyxlQUFlO0lBQ2xCLGFBQWEsQ0FBa0I7SUFDdkMsb0VBQW9FO0lBQzVELFNBQVMsR0FBRyxLQUFLLENBQUM7SUFDVCxRQUFRLENBQVM7SUFDakIsU0FBUyxDQUFTO0lBQ2xCLEtBQUssQ0FBUztJQUNkLFVBQVUsQ0FBUztJQUVwQyxZQUFZLE9BQU8sR0FBMkIsRUFBRTtRQUM5QyxNQUFNLElBQUksR0FBRyxnQkFBZ0IsRUFBRSxDQUFDO1FBQ2hDLElBQUksQ0FBQyxRQUFRLEdBQUcsT0FBTyxDQUFDLFFBQVEsSUFBSSxJQUFJLENBQUMsUUFBUSxDQUFDO1FBQ2xELElBQUksQ0FBQyxTQUFTLEdBQUcsT0FBTyxDQUFDLFNB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QyxDQUFDO1lBQ25FLFVBQVUsQ0FBQywyQkFBMkIsRUFBRSxFQUFFLE1BQU0sRUFBRSxDQUFDLENBQUM7WUFDcEQsT0FBTyxFQUFFLEVBQUUsRUFBRSxLQUFLLEVBQUUsTUFBTSxFQUFFLENBQUM7UUFDL0IsQ0FBQztRQUNELE9BQU8sRUFBRSxFQUFFLEVBQUUsSUFBSSxFQUFFLE1BQU0sRUFBRSxNQUFNLENBQUMsTUFBTSxFQUFFLENBQUM7SUFDN0MsQ0FBQztJQUVEOzs7Ozs7Ozs7Ozs7Ozs7OztPQWlCRztJQUNILEtBQUssQ0FBQyxhQUFhLENBQUMsUUFBZ0IsRUFBRSxTQUFpQjtRQUNyRCxNQUFNLE1BQU0sR0FBRyxNQUFNLElBQUksQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsYUFBYTtRQUNoRSxJQUFJLENBQUMsTUFBTTtZQUFFLE9BQU8sSUFBSSxDQUFDO1FBQ3pCLHVFQUF1RTtRQUN2RSx3RUFBd0U7UUFDeEUsNkNBQTZDO1FBQzdDLElBQUksTUFBTSxDQUFDLE9BQU8sS0FBSyxrQkFBa0IsQ0FBQyxTQUFTLENBQUM7WUFBRSxPQUFPLElBQUksQ0FBQztRQUNsRSxPQUFPLEVBQUUsS0FBSyxFQUFFLE1BQU0sQ0FBQyxLQUFLLEVBQUUsQ0FBQztJQUNqQyxDQUFDO0lBRUQ7aUNBQzZCO0lBQzdCLFFBQVE7UUFDTixJQUFJLElBQUksQ0FBQyxTQUFTO1lBQUUsSUFBSSxDQUFDLGFBQWEsRUFBRSxJQUFJLEVBQUUsQ0FBQztJQUNqRCxDQUFDO0NBQ0YiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuLyoqXG4gKiBNaW50aW5nIGFuZCByZWRlZW1pbmcgdGhlIHNob3J0LWxpdmVkIFNTRSB0aWNrZXRzIHRoYXQgYXV0aG9yaXplIGEgbG9nXG4gKiBzdHJlYW0sIHBsdXMgdGhlIHN0cmVhbS1vd25lcnNoaXAgYmluZGluZyB0aG9zZSBtaW50cyBhcmUgY2hlY2tlZCBhZ2FpbnN0LlxuICpcbiAqIFNlcGFyYXRlIGZyb20ge0BsaW5rIFNTRU1hbmFnZXJ9IGJlY2F1c2UgaXQgc2hhcmVzIG5vdGhpbmcgd2l0aCB0aGVcbiAqIGNvbm5lY3Rpb24gcmVnaXN0cnk6IGl0cyBzdGF0ZSBsaXZlcyBlbnRpcmVseSBpbiB0aGUgaW5qZWN0ZWRcbiAqIHtAbGluayBTc2VUaWNrZXRTdG9yZX0gKFJlZGlzIGluIGEgbXVsdGktcG9kIGRlcGxveSksIGFuZCBldmVyeSBydWxlIGluIGhlcmVcbiAqIGlzIGFuIEFVVEhPUklaQVRJT04gcnVsZSDigJQgd2hvIG1heSBvcGVuIHdoaWNoIHN1YmplY3QncyBzdHJlYW0g4oCUIHdoaWNoIGlzXG4gKiBlYXNpZXIgdG8gYXVkaXQgYXMgb25lIHVuaXQgdGhhbiBpbnRlcmxlYXZlZCB3aXRoIGNvbm5lY3Rpb24gYm9va2tlZXBpbmcuXG4gKi9cblxuaW1wb3J0IHsgY3JlYXRlTG9nZ2VyLCBjcmVhdGVNZW1vcnlTc2VUaWNrZXRTdG9yZSwgZW52U3NlVGlja2V0Q2FwcywgU1NFX1RJQ0tFVF9UVExfTVMsIHR5cGUgU3NlVGlja2V0U3RvcmUsIGVycm9yTWVzc2FnZSB9IGZyb20gJ0BwaXBlbGluZS1idWlsZGVyL2FwaS1jb3JlJztcbmltcG9ydCB7IGluY0NvdW50ZXIgfSBmcm9tICcuLi9hcGkvbWV0cmljcy5qcyc7XG5cbmNvbnN0IGxvZ2dlciA9IGNyZWF0ZUxvZ2dlcignc3NlLXRpY2tldHMnKTtcblxuLyoqIFJlc3VsdCBvZiB7QGxpbmsgU3NlVGlja2V0QnJva2VyLmNyZWF0ZVRpY2tldH0uICovXG5leHBvcnQgdHlwZSBDcmVhdGVUaWNrZXRSZXN1bHQgPVxuICB8IHsgb2s6IHRydWU7IHRpY2tldDogc3RyaW5nIH1cbiAgfCB7IG9rOiBmYWxzZTsgcmVhc29uOiAnb3JnLWxpbWl0JyB8ICdjYXBhY2l0eScgfCAnZm9yYmlkZGVuJyB9O1xuXG5leHBvcnQgaW50ZXJmYWNlIFNzZVRpY2tldEJyb2tlck9wdGlvbnMge1xuICAvKiogVGlja2V0ICsgc3RyZWFtLW93bmVyc2hpcCBiYWNrZW5kLiBEZWZhdWx0cyB0byBhbiBpbi1tZW1vcnkgc3RvcmUgKHNpbmdsZVxuICAgKiAgcmVwbGljYSBvbmx5KSwgYnVpbHQgb24gZmlyc3QgdXNlLiBJbmplY3QgYW4gYXBpLWNvcmVcbiAgICogIGBjcmVhdGVFbnZTc2VUaWNrZXRTdG9yZSguLi4pYCBzbyBtaW50L3JlZGVlbSBhbmQgc3RyZWFtIG93bmVyc2hpcCB3b3JrXG4gICAqICBhY3Jvc3MgcG9kcy4gKi9cbiAgc3RvcmU/OiBTc2VUaWNrZXRTdG9yZTtcbiAgLyoqIENhcCBvbiBsaXZlIHRpY2tldHMgYWNyb3NzIHRoZSBwcm9jZXNzLCBmb3IgdGhlIERFRkFVTFQgaW4tbWVtb3J5IHN0b3JlLlxuICAgKiAgSWdub3JlZCB3aGVuIGBzdG9yZWAgaXMgaW5qZWN0ZWQgKHRoZSBzdG9yZSBjYXJyaWVzIGl0cyBvd24gY2FwcykuICovXG4gIG1heFRvdGFsPzogbnVtYmVyO1xuICAvKiogUGVyLW9yZyBjYXAgb24gbGl2ZSB0aWNrZXRzLCBmb3IgdGhlIERFRkFVTFQgaW4tbWVtb3J5IHN0b3JlLiBJZ25vcmVkIHdoZW5cbiAgICogIGBzdG9yZWAgaXMgaW5qZWN0ZWQuICovXG4gIG1heFBlck9yZz86IG51bWJlcjtcbiAgLyoqIFRpY2tldCBUVEwgaW4gbXMgZm9yIHRoZSBkZWZhdWx0IHN0b3JlLiAqL1xuICB0dGxNcz86IG51bWJlcjtcbiAgLyoqIFRUTCBmb3IgYSBzdHJlYW0tb3duZXJzaGlwIGJpbmRpbmcgKGRlZmF1bHQgMWgpLiBMb25nIGVub3VnaCB0byBvdXRsaXZlIGFcbiAgICogIGJ1aWxkOyB0aGUgcHJvZHVjZXIgcmUtYmluZHMgYXMgbmVlZGVkLiAqL1xuICBvd25lclR0bE1zPzogbnVtYmVyO1xufVxuXG4vKipcbiAqIENhbm9uaWNhbCBmb3JtIG9mIGEgc3RyZWFtIHN1YmplY3QgaWQgZm9yIG1hcC9zdG9yZSBrZXlzOiB0aGUgaWQgaXMgYWxsb3dlZFxuICogaW4gdHdvIHJlbmRlcmluZ3MgKGEgZGFzaGVkIGFwaS1zZXJ2ZXIgdXVpZCBhbmQgbmdpbngncyB1bmRhc2hlZFxuICogYCRyZXF1ZXN0X2lkYCksIGFuZCBub3JtYWxpemluZyBtYWtlcyB0aGVtIGNvbXBhcmUgZXF1YWwuIENhbGxlcnMgYXJlXG4gKiBleHBlY3RlZCB0byBoYXZlIGFscmVhZHkgZm9ybWF0LXZhbGlkYXRlZCBhZ2FpbnN0IGBTU0VfUkVRVUVTVF9JRF9SRWAuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBub3JtYWxpemVSZXF1ZXN0SWQocmVxdWVzdElkOiBzdHJpbmcpOiBzdHJpbmcge1xuICByZXR1cm4gcmVxdWVzdElkLnJlcGxhY2UoLy0vZywgJycpLnRvTG93ZXJDYXNlKCk7XG59XG5cbi8qKlxuICogQ2Fub25pY2FsIGZvcm0gb2YgYW4gb3JnSWQgZm9yIG93bmVyc2hpcCBlcXVhbGl0eSBjaGVja3M6IHRyaW1tZWQgYW5kXG4gKiBsb3dlcmNhc2VkLiBUaGUgc3RyZWFtIFBST0RVQ0VSIChiaW5kU3RyZWFtT3duZXIpIGFuZCB0aGUgdGlja2V0LW1pbnRpbmdcbiAqIENPTlNVTUVSIChjcmVhdGVUaWNrZXQpIHJ1biBpbiBkaWZmZXJlbnQgc2VydmljZXMsIHNvIGEgY2FzaW5nIC8gd2hpdGVzcGFjZVxuICogZHJpZnQgYmV0d2VlbiBob3cgZWFjaCByZW5kZXJzIHRoZSBzYW1lIG9yZyB3b3VsZCBvdGhlcndpc2UgbWFrZSB0aGUgb3duZXJcbiAqIGNvbXBhcmlzb24gaW4ge0BsaW5rIFNzZVRpY2tldEJyb2tlci5jcmVhdGVUaWNrZXR9IGZhbHNlLWBmb3JiaWRkZW5gIHRoZSByZWFsXG4gKiBvd25lci4gTm9ybWFsaXppbmcgYm90aCBzaWRlcyDigJQgbWlycm9yaW5nIHtAbGluayBub3JtYWxpemVSZXF1ZXN0SWR9IOKAlCBjbG9zZXMgdGhhdC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIG5vcm1hbGl6ZU9yZ0lkKG9yZ0lkOiBzdHJpbmcpOiBzdHJpbmcge1xuICByZXR1cm4gb3JnSWQudHJpbSgpLnRvTG93ZXJDYXNlKCk7XG59XG5cbmV4cG9ydCBjbGFzcyBTc2VUaWNrZXRCcm9rZXIge1xuICBwcml2YXRlIHN0b3JlSW5zdGFuY2U/OiBTc2VUaWNrZXRTdG9yZTtcbiAgLyoqIFRydWUgd2hlbiB0aGlzIGJyb2tlciBidWlsdCBpdHMgc3RvcmUgKHNvIHNodXRkb3duIHN0b3BzIGl0KS4gKi9cbiAgcHJpdmF0ZSBvd25zU3RvcmUgPSBmYWxzZTtcbiAgcHJpdmF0ZSByZWFkb25seSBtYXhUb3RhbDogbnVtYmVyO1xuICBwcml2YXRlIHJlYWRvbmx5IG1heFBlck9yZzogbnVtYmVyO1xuICBwcml2YXRlIHJlYWRvbmx5IHR0bE1zOiBudW1iZXI7XG4gIHByaXZhdGUgcmVhZG9ubHkgb3duZXJUdGxNczogbnVtYmVyO1xuXG4gIGNvbnN0cnVjdG9yKG9wdGlvbnM6IFNzZVRpY2tldEJyb2tlck9wdGlvbnMgPSB7fSkge1xuICAgIGNvbnN0IGNhcHMgPSBlbnZTc2VUaWNrZXRDYXBzKCk7XG4gICAgdGhpcy5tYXhUb3RhbCA9IG9wdGlvbnMubWF4VG90YWwgPz8gY2Fwcy5tYXhUb3RhbDtcbiAgICB0aGlzLm1heFBlck9yZyA9IG9wdGlvbnMubWF4UGVyT3JnID8/IGNhcHMubWF4UGVyT3JnO1xuICAgIHRoaXMudHRsTXMgPSBvcHRpb25zLnR0bE1zID8/IFNTRV9USUNLRVRfVFRMX01TO1xuICAgIHRoaXMub3duZXJUdGxNcyA9IG9wdGlvbnMub3duZXJUdGxNcyA/PyAzXzYwMF8wMDA7IC8vIDEgaG91clxuICAgIHRoaXMuc3RvcmVJbnN0YW5jZSA9IG9wdGlvbnMuc3RvcmU7XG4gIH1cblxuICAvKiogVGhlIHRpY2tldCBzdG9yZSwgYnVpbGRpbmcgdGhlIGluLW1lbW9yeSBkZWZhdWx0IG9uIGZpcnN0IHVzZS4gKi9cbiAgcHJpdmF0ZSBnZXQgc3RvcmUoKTogU3NlVGlja2V0U3RvcmUge1xuICAgIGlmICghdGhpcy5zdG9yZUluc3RhbmNlKSB7XG4gICAgICB0aGlzLnN0b3JlSW5zdGFuY2UgPSBjcmVhdGVNZW1vcnlTc2VUaWNrZXRTdG9yZSh7XG4gICAgICAgIHR0bE1zOiB0aGlzLnR0bE1zLFxuICAgICAgICBtYXhUb3RhbDogdGhpcy5tYXhUb3RhbCxcbiAgICAgICAgbWF4UGVyT3JnOiB0aGlzLm1heFBlck9yZyxcbiAgICAgIH0pO1xuICAgICAgdGhpcy5vd25zU3RvcmUgPSB0cnVlO1xuICAgIH1cbiAgICByZXR1cm4gdGhpcy5zdG9yZUluc3RhbmNlO1xuICB9XG5cbiAgLyoqXG4gICAqIFJlY29yZCB0aGUgb3JnIHRoYXQgT1dOUyBhIHN0cmVhbSBzdWJqZWN0LiBUaGUgc3RyZWFtIFBST0RVQ0VSIGNhbGxzIHRoaXNcbiAgICogd2hlbiBpdCBjcmVhdGVzIGEgYnVpbGQtbG9nIHN0cmVhbSBzbyB0aGF0IHRpY2tldCBtaW50aW5nIGNhbiBhc3NlcnQgdGhlXG4gICAqIGNhbGxlcidzIG9yZyBvd25zIHRoZSBzdWJqZWN0IChzZWUge0BsaW5rIGNyZWF0ZVRpY2tldH0pLiBCYWNrZWQgYnkgdGhlXG4gICAqIGluamVjdGVkIHRpY2tldCBzdG9yZSwgc28gd2hlbiBhIFJlZGlzIHN0b3JlIGlzIHdpcmVkIHRoZSBwbGF0Zm9ybSBwcm9kdWNlclxuICAgKiAoYSBkaWZmZXJlbnQgc2VydmljZSBzaGFyaW5nIHRoZSBzYW1lIFJlZGlzKSBjYW4gYmluZCBvd25lcnNoaXAgdGhhdCB0aGlzXG4gICAqIHNlcnZpY2UgcmVhZHMg4oCUIGNsb3NpbmcgdGhlIGNyb3NzLXRlbmFudCBhdHRhY2ggZ2FwIHdoZXJlIGFueSBvcmcgY291bGQgbWludFxuICAgKiBhIHRpY2tldCBmb3IgYSBndWVzc2VkIHJlcXVlc3RJZC5cbiAgICpcbiAgICogQHBhcmFtIHJlcXVlc3RJZCAtIFRoZSBzdHJlYW0gc3ViamVjdCAoZm9ybWF0LXZhbGlkYXRlZCBieSB0aGUgY2FsbGVyKS5cbiAgICogQHBhcmFtIG9yZ0lkIC0gVGhlIG93bmluZyBvcmcgKG5vcm1hbGl6ZWQgaW50ZXJuYWxseSB2aWEgbm9ybWFsaXplT3JnSWQpLlxuICAgKi9cbiAgYXN5bmMgYmluZFN0cmVhbU93bmVyKHJlcXVlc3RJZDogc3RyaW5nLCBvcmdJZDogc3RyaW5nKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgYXdhaXQgdGhpcy5zdG9yZS5iaW5kT3duZXIobm9ybWFsaXplUmVxdWVzdElkKHJlcXVlc3RJZCksIG5vcm1hbGl6ZU9yZ0lkKG9yZ0lkKSwgdGhpcy5vd25lclR0bE1zKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBNaW50IGEgc2hvcnQtbGl2ZWQsIHNpbmdsZS11c2UgU1NFIHRpY2tldCBib3VuZCB0byBgb3JnSWRgIEFORCBhIHNwZWNpZmljXG4gICAqIHN0cmVhbSBzdWJqZWN0IChgcmVxdWVzdElkYCkuIENsaWVudHMgUE9TVCB0byBvYnRhaW4gb25lIChKV1QtYXV0aGVudGljYXRlZClcbiAgICogZm9yIHRoZSBleGFjdCBzdHJlYW0gdGhleSBpbnRlbmQgdG8gb3BlbiwgdGhlbiBvcGVuIHRoZSBFdmVudFNvdXJjZSB3aXRoXG4gICAqIGA/dGlja2V0PTx0PmAgc28gdGhlIEpXVCBuZXZlciBsYW5kcyBpbiBhIHF1ZXJ5IHN0cmluZyAvIGFjY2VzcyBsb2cuIFRoZVxuICAgKiB0aWNrZXQgY2FuIHRoZW4gb25seSBiZSBjb25zdW1lZCB0byBvcGVuIHRoYXQgb25lIHN1YmplY3QncyBzdHJlYW0g4oCUIHNlZVxuICAgKiB7QGxpbmsgY29uc3VtZVRpY2tldH0g4oCUIHdoaWNoIGlzIHdoYXQgZW5mb3JjZXMgcGVyLXN1YmplY3QgYXV0aG9yaXphdGlvbi5cbiAgICpcbiAgICogT1JHLU9XTkVSU0hJUDogaWYgYSBzdHJlYW0gb3duZXIgaGFzIGJlZW4gYm91bmQgZm9yIHRoaXMgc3ViamVjdCAodmlhXG4gICAqIHtAbGluayBiaW5kU3RyZWFtT3duZXJ9KSBhbmQgaXQgaXMgYSBESUZGRVJFTlQgb3JnLCBtaW50aW5nIGlzIHJlZnVzZWRcbiAgICogKGByZWFzb246ICdmb3JiaWRkZW4nYCkg4oCUIGFuIG9yZyBjYW5ub3QgbWludCBhIHRpY2tldCBmb3IgYW5vdGhlciBvcmcnc1xuICAgKiBzdHJlYW0gZXZlbiBpZiBpdCBndWVzc2VzIHRoZSByZXF1ZXN0SWQuIFdoZW4gbm8gb3duZXIgaXMgYm91bmQgKHByb2R1Y2VyXG4gICAqIHdpcmluZyBub3QgeWV0IHByZXNlbnQpLCBtaW50aW5nIGZhbGxzIGJhY2sgdG8gYmluZGluZyB0aGUgdGlja2V0IHRvIHRoZVxuICAgKiBjYWxsZXIncyBvd24gb3JnLCBwcmVzZXJ2aW5nIGN1cnJlbnQgYmVoYXZpb3IuXG4gICAqXG4gICAqIEJvdW5kZWQgYnkgdGhlIHRpY2tldCBzdG9yZSdzIGxpdmUtdGlja2V0IGNhcHMgKHRvdGFsIGFuZCBwZXIgb3JnKS4gQW5cbiAgICogb3duZXJzaGlwIGxvb2t1cCB0aGUgc3RvcmUgY2FuJ3QgYW5zd2VyIGlzIHJlZnVzZWQgYXMgYGNhcGFjaXR5YC5cbiAgICpcbiAgICogQHBhcmFtIG9yZ0lkIC0gT3duaW5nIG9yZyAobm9ybWFsaXplZCBpbnRlcm5hbGx5IHZpYSBub3JtYWxpemVPcmdJZCkuXG4gICAqIEBwYXJhbSByZXF1ZXN0SWQgLSBUaGUgYnVpbGQtbG9nIHN0cmVhbSBzdWJqZWN0IHRoaXMgdGlja2V0IGF1dGhvcml6ZXMuXG4gICAqICAgVGhlIGNhbGxlciBtdXN0IGhhdmUgZm9ybWF0LXZhbGlkYXRlZCBpdCAoc2VlIGBTU0VfUkVRVUVTVF9JRF9SRWApLlxuICAgKiBAcmV0dXJucyBgeyBvazogdHJ1ZSwgdGlja2V0IH1gIG9uIHN1Y2Nlc3MsIG9yIGB7IG9rOiBmYWxzZSwgcmVhc29uIH1gXG4gICAqICAgd2hlcmUgcmVhc29uIGlzIGAnb3JnLWxpbWl0J2AsIGAnY2FwYWNpdHknYCwgb3IgYCdmb3JiaWRkZW4nYC5cbiAgICovXG4gIGFzeW5jIGNyZWF0ZVRpY2tldChvcmdJZDogc3RyaW5nLCByZXF1ZXN0SWQ6IHN0cmluZyk6IFByb21pc2U8Q3JlYXRlVGlja2V0UmVzdWx0PiB7XG4gICAgLy8gT3duZXJzaGlwIGdhdGUgZmlyc3Qg4oCUIGEgY3Jvc3MtdGVuYW50IG1pbnQgYXR0ZW1wdCBzaG91bGQgbmV2ZXIgZXZlblxuICAgIC8vIGNvbnN1bWUgY2FwIGJ1ZGdldC4gT25seSBlbmZvcmNlZCB3aGVuIGFuIG93bmVyIGlzIGFjdHVhbGx5IGJvdW5kLiBCb3RoXG4gICAgLy8gc2lkZXMgYXJlIG9yZy1ub3JtYWxpemVkIHNvIGEgcHJvZHVjZXIvY29uc3VtZXIgY2FzaW5nIGRyaWZ0IGNhbid0XG4gICAgLy8gZmFsc2UtYGZvcmJpZGRlbmAgdGhlIHJlYWwgb3duZXIgKG1pcnJvcnMgcmVxdWVzdElkIG5vcm1hbGl6YXRpb24pLlxuICAgIGNvbnN0IG5vcm1hbGl6ZWQgPSBub3JtYWxpemVSZXF1ZXN0SWQocmVxdWVzdElkKTtcbiAgICBjb25zdCBub3JtYWxpemVkT3JnID0gbm9ybWFsaXplT3JnSWQob3JnSWQpO1xuICAgIGxldCBvd25lcjogc3RyaW5nIHwgbnVsbDtcbiAgICB0cnkge1xuICAgICAgb3duZXIgPSBhd2FpdCB0aGlzLnN0b3JlLmdldE93bmVyKG5vcm1hbGl6ZWQpO1xuICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgLy8gQ2FuJ3QgdGVsbCB3aG8gb3ducyB0aGUgc3ViamVjdCDigJQgcmVmdXNlIHJhdGhlciB0aGFuIG1pbnQgdW5jaGVja2VkLlxuICAgICAgbG9nZ2VyLndhcm4oJ1NTRSB0aWNrZXQgcmVmdXNlZDogc3RyZWFtIG93bmVyc2hpcCBsb29rdXAgZmFpbGVkJywgeyBlcnJvcjogZXJyb3JNZXNzYWdlKGVycikgfSk7XG4gICAgICBpbmNDb3VudGVyKCdzc2VfdGlja2V0X3JlamVjdGVkX3RvdGFsJywgeyByZWFzb246ICdjYXBhY2l0eScgfSk7XG4gICAgICByZXR1cm4geyBvazogZmFsc2UsIHJlYXNvbjogJ2NhcGFjaXR5JyB9O1xuICAgIH1cbiAgICBpZiAob3duZXIgJiYgb3duZXIgIT09IG5vcm1hbGl6ZWRPcmcpIHtcbiAgICAgIGxvZ2dlci53YXJuKGBTU0UgdGlja2V0IHJlZnVzZWQ6IG9yZyAke25vcm1hbGl6ZWRPcmd9IGRvZXMgbm90IG93biBzdHJlYW0gc3ViamVjdGApO1xuICAgICAgaW5jQ291bnRlcignc3NlX3RpY2tldF9yZWplY3RlZF90b3RhbCcsIHsgcmVhc29uOiAnZm9yYmlkZGVuJyB9KTtcbiAgICAgIHJldHVybiB7IG9rOiBmYWxzZSwgcmVhc29uOiAnZm9yYmlkZGVuJyB9O1xuICAgIH1cblxuICAgIGNvbnN0IGlzc3VlZCA9IGF3YWl0IHRoaXMuc3RvcmUuaXNzdWUobm9ybWFsaXplZE9yZywgbm9ybWFsaXplZCk7XG4gICAgaWYgKCFpc3N1ZWQub2spIHtcbiAgICAgIGNvbnN0IHJlYXNvbiA9IGlzc3VlZC5yZWFzb24gPT09ICdvcmcnID8gJ29yZy1saW1pdCcgOiAnY2FwYWNpdHknO1xuICAgICAgbG9nZ2VyLndhcm4oYFNTRSB0aWNrZXQgcmVmdXNlZCAoJHtyZWFzb259KSBmb3IgJHtub3JtYWxpemVkT3JnfWApO1xuICAgICAgaW5jQ291bnRlcignc3NlX3RpY2tldF9yZWplY3RlZF90b3RhbCcsIHsgcmVhc29uIH0pO1xuICAgICAgcmV0dXJuIHsgb2s6IGZhbHNlLCByZWFzb24gfTtcbiAgICB9XG4gICAgcmV0dXJuIHsgb2s6IHRydWUsIHRpY2tldDogaXNzdWVkLnRpY2tldCB9O1xuICB9XG5cbiAgLyoqXG4gICAqIFZhbGlkYXRlIGFuZCBDT05TVU1FIGEgdGlja2V0IGZvciBhIHNwZWNpZmljIHN0cmVhbSBzdWJqZWN0LiBTaW5nbGUtdXNlOlxuICAgKiB0aGUgdGlja2V0IGlzIGRlbGV0ZWQgd2hldGhlciBvciBub3QgaXQgdHVybnMgb3V0IHRvIGJlIHZhbGlkLCBzbyBhIHJlcGxheVxuICAgKiBvZiB0aGUgc2FtZSB2YWx1ZSBhbHdheXMgZmFpbHMuIFJldHVybnMgdGhlIGJvdW5kIG9yZyBvbiBzdWNjZXNzLCBvciBudWxsXG4gICAqIHdoZW4gdGhlIHRpY2tldCBpcyB1bmtub3duIC8gYWxyZWFkeS11c2VkIC8gZXhwaXJlZCAvIE9SIHdhcyBtaW50ZWQgZm9yIGFcbiAgICogRElGRkVSRU5UIGByZXF1ZXN0SWRgIHRoYW4gdGhlIG9uZSBiZWluZyBvcGVuZWQuXG4gICAqXG4gICAqIFRoZSBzdWJqZWN0IGNoZWNrIGlzIHRoZSBhdXRob3JpemF0aW9uIGZpeDogd2l0aG91dCBpdCwgYSB0aWNrZXQgbWludGVkIGZvclxuICAgKiBvbmUgc3RyZWFtIGNvdWxkIGJlIHByZXNlbnRlZCB0byBvcGVuIEFOWSBzdHJlYW0gdGhlIGhvbGRlcidzIG9yZyBjb3VsZFxuICAgKiBuYW1lLCBsZXR0aW5nIGFuIGF1dGhlbnRpY2F0ZWQgb3JnIGF0dGFjaCB0byBhbm90aGVyIG9yZydzIGxvZyBzdHJlYW0gYnlcbiAgICogZ3Vlc3NpbmcgaXRzIHJlcXVlc3RJZC4gVGhlIGdlbmVyaWMgNDAxIHRoZSBjYWxsZXIgcmV0dXJucyBkb2VzIG5vdFxuICAgKiBkaXN0aW5ndWlzaCBcInVua25vd24gdGlja2V0XCIgZnJvbSBcIndyb25nIHN1YmplY3RcIiwgc28gaXQgbGVha3Mgbm90aGluZ1xuICAgKiBhYm91dCB3aGljaCByZXF1ZXN0SWRzIGV4aXN0LlxuICAgKlxuICAgKiBAcGFyYW0gdGlja2V0SWQgLSBUaGUgb3BhcXVlIHRpY2tldCB2YWx1ZSBmcm9tIGA/dGlja2V0PWAuXG4gICAqIEBwYXJhbSByZXF1ZXN0SWQgLSBUaGUgc3RyZWFtIHN1YmplY3QgZnJvbSB0aGUgVVJMIHBhdGggKGA6cmVxdWVzdElkYCksXG4gICAqICAgYWxyZWFkeSBmb3JtYXQtdmFsaWRhdGVkIGJ5IHRoZSBjYWxsZXIuXG4gICAqL1xuICBhc3luYyBjb25zdW1lVGlja2V0KHRpY2tldElkOiBzdHJpbmcsIHJlcXVlc3RJZDogc3RyaW5nKTogUHJvbWlzZTx7IG9yZ0lkOiBzdHJpbmcgfSB8IG51bGw+IHtcbiAgICBjb25zdCB0aWNrZXQgPSBhd2FpdCB0aGlzLnN0b3JlLmNvbnN1bWUodGlja2V0SWQpOyAvLyBzaW5nbGUtdXNlXG4gICAgaWYgKCF0aWNrZXQpIHJldHVybiBudWxsO1xuICAgIC8vIFN1YmplY3QgYmluZGluZyDigJQgcmVqZWN0IGEgdGlja2V0IHByZXNlbnRlZCBmb3IgYSBzdWJqZWN0IGl0IHdhcyBub3RcbiAgICAvLyBtaW50ZWQgZm9yIChpbmNsdWRpbmcgYW4gdW5ib3VuZCwgb3JnLWNoYW5uZWwgdGlja2V0KS4gQm90aCBzaWRlcyBhcmVcbiAgICAvLyBub3JtYWxpemVkIHNvIGRhc2hlZC91bmRhc2hlZCBmb3JtcyBtYXRjaC5cbiAgICBpZiAodGlja2V0LnN1YmplY3QgIT09IG5vcm1hbGl6ZVJlcXVlc3RJZChyZXF1ZXN0SWQpKSByZXR1cm4gbnVsbDtcbiAgICByZXR1cm4geyBvcmdJZDogdGlja2V0Lm9yZ0lkIH07XG4gIH1cblxuICAvKiogU3RvcCB0aGUgc3RvcmUsIGJ1dCBvbmx5IHRoZSBvbmUgdGhpcyBicm9rZXIgYnVpbHQg4oCUIGFuIGluamVjdGVkIHN0b3JlIGlzXG4gICAqICB0aGUgY2FsbGVyJ3MgdG8gY2xvc2UuICovXG4gIHNodXRkb3duKCk6IHZvaWQge1xuICAgIGlmICh0aGlzLm93bnNTdG9yZSkgdGhpcy5zdG9yZUluc3RhbmNlPy5zdG9wKCk7XG4gIH1cbn1cbiJdfQ==
|
package/lib/testing/exports.json
CHANGED
package/package.json
CHANGED
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
"swagger-ui-express": "5.0.1",
|
|
51
51
|
"unified": "11.0.5",
|
|
52
52
|
"uuid": "14.0.2",
|
|
53
|
-
"@pipeline-builder/pipeline-core": "4.1.
|
|
54
|
-
"@pipeline-builder/api-core": "4.1.
|
|
55
|
-
"@pipeline-builder/pipeline-data": "4.1.
|
|
53
|
+
"@pipeline-builder/pipeline-core": "4.1.24",
|
|
54
|
+
"@pipeline-builder/api-core": "4.1.22",
|
|
55
|
+
"@pipeline-builder/pipeline-data": "4.1.24"
|
|
56
56
|
},
|
|
57
57
|
"keywords": [
|
|
58
58
|
"ci-cd",
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
"access": "public",
|
|
109
109
|
"registry": "https://registry.npmjs.org/"
|
|
110
110
|
},
|
|
111
|
-
"version": "4.1.
|
|
111
|
+
"version": "4.1.24",
|
|
112
112
|
"bugs": {
|
|
113
113
|
"url": "https://github.com/mwashburn160/pipeline-builder/issues"
|
|
114
114
|
},
|