@dcoder-x/plugin-shared 0.1.3 → 0.1.5
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/dist/buildId.d.ts +5 -0
- package/dist/buildId.js +17 -0
- package/dist/extractors/ComponentContextResolver.d.ts +37 -0
- package/dist/extractors/ComponentContextResolver.js +167 -0
- package/dist/extractors/ComponentExtractor.d.ts +6 -1
- package/dist/extractors/ComponentExtractor.js +124 -11
- package/dist/extractors/FlowInferrer.d.ts +5 -2
- package/dist/extractors/FlowInferrer.js +117 -14
- package/dist/extractors/InteractionGraphExtractor.d.ts +28 -0
- package/dist/extractors/InteractionGraphExtractor.js +333 -0
- package/dist/extractors/SelectorGenerator.d.ts +6 -1
- package/dist/extractors/SelectorGenerator.js +28 -3
- package/dist/index.d.ts +7 -1
- package/dist/index.js +21 -0
- package/dist/injection/ClippyIdInjector.d.ts +17 -0
- package/dist/injection/ClippyIdInjector.js +164 -0
- package/dist/injection/HtmlTagGuards.d.ts +3 -0
- package/dist/injection/HtmlTagGuards.js +41 -0
- package/dist/injection/IdStrategy.d.ts +36 -0
- package/dist/injection/IdStrategy.js +148 -0
- package/dist/types.d.ts +108 -1
- package/dist/upload/Adapter.d.ts +3 -0
- package/dist/upload/Adapter.js +13 -0
- package/dist/upload/BackendAdapter.d.ts +30 -0
- package/dist/upload/BackendAdapter.js +42 -0
- package/dist/upload/BackendUploadAdapter.d.ts +13 -0
- package/dist/upload/BackendUploadAdapter.js +51 -0
- package/dist/upload/PackageBuilder.d.ts +34 -1
- package/dist/upload/PackageBuilder.js +220 -0
- package/dist/upload/PackageWriter.d.ts +9 -1
- package/dist/upload/PackageWriter.js +26 -0
- package/dist/upload/UploadStrategy.d.ts +11 -0
- package/dist/upload/UploadStrategy.js +40 -0
- package/dist/upload/Uploader.d.ts +6 -1
- package/dist/upload/Uploader.js +48 -3
- package/package.json +1 -1
|
@@ -5,16 +5,236 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.PackageBuilder = void 0;
|
|
7
7
|
const zlib_1 = __importDefault(require("zlib"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const IdStrategy_1 = require("../injection/IdStrategy");
|
|
8
10
|
class PackageBuilder {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.version = '1.0.0';
|
|
13
|
+
}
|
|
9
14
|
buildPackage(data) {
|
|
10
15
|
return {
|
|
11
16
|
...data,
|
|
12
17
|
generatedAt: new Date().toISOString(),
|
|
13
18
|
};
|
|
14
19
|
}
|
|
20
|
+
buildArtifacts(data) {
|
|
21
|
+
const generatedAt = new Date().toISOString();
|
|
22
|
+
const policy = this.buildPolicyDocument({
|
|
23
|
+
...data,
|
|
24
|
+
generatedAt,
|
|
25
|
+
});
|
|
26
|
+
const selectorManifest = this.buildSelectorManifest({
|
|
27
|
+
buildId: data.buildId,
|
|
28
|
+
generatedAt,
|
|
29
|
+
selectors: policy.selectors,
|
|
30
|
+
});
|
|
31
|
+
return {
|
|
32
|
+
metadata: {
|
|
33
|
+
version: this.version,
|
|
34
|
+
buildId: data.buildId,
|
|
35
|
+
generatedAt,
|
|
36
|
+
bundler: data.bundler,
|
|
37
|
+
},
|
|
38
|
+
policy,
|
|
39
|
+
selectorManifest,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
buildPolicyDocument(data) {
|
|
43
|
+
const projectRoot = this.inferProjectRoot(data.routes);
|
|
44
|
+
const normalizedRoutes = data.routes.map((r) => ({
|
|
45
|
+
...r,
|
|
46
|
+
filePath: this.normalizePath(r.filePath, projectRoot),
|
|
47
|
+
layout: r.layout ? this.normalizePath(r.layout, projectRoot) : null,
|
|
48
|
+
}));
|
|
49
|
+
const policySelectors = this.toPolicySelectors(data.selectors, projectRoot);
|
|
50
|
+
return {
|
|
51
|
+
version: this.version,
|
|
52
|
+
buildId: data.buildId,
|
|
53
|
+
generatedAt: data.generatedAt,
|
|
54
|
+
bundler: data.bundler,
|
|
55
|
+
routes: normalizedRoutes,
|
|
56
|
+
selectors: policySelectors,
|
|
57
|
+
components: data.components ?? this.toPolicyComponents(data.selectors),
|
|
58
|
+
flows: this.toPolicyFlows(data.flows, normalizedRoutes, policySelectors),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
buildSelectorManifest(data) {
|
|
62
|
+
// Deduplicate by clippyId — shared components used on multiple routes appear once
|
|
63
|
+
// with a routes[] array listing every route where they appear.
|
|
64
|
+
const byId = new Map();
|
|
65
|
+
for (const entry of data.selectors) {
|
|
66
|
+
if (byId.has(entry.clippyId)) {
|
|
67
|
+
const existing = byId.get(entry.clippyId);
|
|
68
|
+
if (!existing.routes.includes(entry.route)) {
|
|
69
|
+
existing.routes.push(entry.route);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
byId.set(entry.clippyId, {
|
|
74
|
+
id: entry.clippyId,
|
|
75
|
+
selector: entry.selector,
|
|
76
|
+
component: entry.component,
|
|
77
|
+
tag: entry.tag,
|
|
78
|
+
label: entry.label,
|
|
79
|
+
routes: [entry.route],
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
version: this.version,
|
|
85
|
+
buildId: data.buildId,
|
|
86
|
+
generatedAt: data.generatedAt,
|
|
87
|
+
selectors: Array.from(byId.values()),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
15
90
|
build(data) {
|
|
16
91
|
const pkg = this.buildPackage(data);
|
|
17
92
|
return zlib_1.default.gzipSync(JSON.stringify(pkg));
|
|
18
93
|
}
|
|
94
|
+
buildArtifactsBuffer(artifacts) {
|
|
95
|
+
return zlib_1.default.gzipSync(JSON.stringify(artifacts));
|
|
96
|
+
}
|
|
97
|
+
inferProjectRoot(routes) {
|
|
98
|
+
const filePaths = routes.map((r) => r.filePath).filter(Boolean);
|
|
99
|
+
if (filePaths.length === 0)
|
|
100
|
+
return process.cwd();
|
|
101
|
+
// Find the longest common directory prefix across all route file paths
|
|
102
|
+
const parts = filePaths[0].replace(/\\/g, '/').split('/');
|
|
103
|
+
let commonParts = parts;
|
|
104
|
+
for (const fp of filePaths.slice(1)) {
|
|
105
|
+
const fpParts = fp.replace(/\\/g, '/').split('/');
|
|
106
|
+
const len = Math.min(commonParts.length, fpParts.length);
|
|
107
|
+
let i = 0;
|
|
108
|
+
while (i < len && commonParts[i] === fpParts[i])
|
|
109
|
+
i++;
|
|
110
|
+
commonParts = commonParts.slice(0, i);
|
|
111
|
+
}
|
|
112
|
+
return commonParts.join('/') || process.cwd();
|
|
113
|
+
}
|
|
114
|
+
normalizePath(filePath, projectRoot) {
|
|
115
|
+
try {
|
|
116
|
+
const rel = path_1.default.relative(projectRoot, filePath).replace(/\\/g, '/');
|
|
117
|
+
return rel.startsWith('..') ? filePath.replace(/\\/g, '/') : rel;
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return filePath.replace(/\\/g, '/');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
toPolicySelectors(selectors, projectRoot) {
|
|
124
|
+
return selectors
|
|
125
|
+
.filter((element) => !element.isNavigationLink)
|
|
126
|
+
.map((element, index) => {
|
|
127
|
+
const component = element.component || (0, IdStrategy_1.deriveComponentName)(element.filePath);
|
|
128
|
+
const preferred = element.selectors.find((candidate) => candidate.type === 'clippy_id') ||
|
|
129
|
+
element.selectors[0];
|
|
130
|
+
const stableLine = element.loc?.line ?? index;
|
|
131
|
+
const clippyId = preferred?.type === 'clippy_id'
|
|
132
|
+
? this.extractClippyIdFromSelector(preferred.value) ||
|
|
133
|
+
this.deriveStableId(component, element.tag, stableLine)
|
|
134
|
+
: this.deriveStableId(component, element.tag, stableLine);
|
|
135
|
+
const selector = preferred?.value ||
|
|
136
|
+
`[data-clippy-id='${clippyId}']`;
|
|
137
|
+
return {
|
|
138
|
+
clippyId,
|
|
139
|
+
selector,
|
|
140
|
+
tag: element.tag,
|
|
141
|
+
component,
|
|
142
|
+
label: element.label,
|
|
143
|
+
route: element.route,
|
|
144
|
+
filePath: projectRoot ? this.normalizePath(element.filePath, projectRoot) : element.filePath.replace(/\\/g, '/'),
|
|
145
|
+
attributes: Object.entries(element.staticProps).map(([name, value]) => ({ name, value })),
|
|
146
|
+
candidates: element.selectors,
|
|
147
|
+
};
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
toPolicyComponents(selectors) {
|
|
151
|
+
const byFile = new Map();
|
|
152
|
+
for (const element of selectors) {
|
|
153
|
+
if (element.isNavigationLink)
|
|
154
|
+
continue;
|
|
155
|
+
if (!byFile.has(element.filePath))
|
|
156
|
+
byFile.set(element.filePath, []);
|
|
157
|
+
byFile.get(element.filePath).push(element);
|
|
158
|
+
}
|
|
159
|
+
return Array.from(byFile.entries()).map(([filePath, items]) => {
|
|
160
|
+
// Prefer the component name captured during extraction over the filename
|
|
161
|
+
const componentName = items.find((i) => i.component)?.component ||
|
|
162
|
+
this.deriveComponentName(filePath);
|
|
163
|
+
const route = items[0]?.route || '/';
|
|
164
|
+
return {
|
|
165
|
+
name: componentName,
|
|
166
|
+
filePath,
|
|
167
|
+
route,
|
|
168
|
+
stateVariables: [],
|
|
169
|
+
interactions: [],
|
|
170
|
+
};
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
toPolicyFlows(flows, routes, selectors) {
|
|
174
|
+
return flows.map((flow, flowIndex) => {
|
|
175
|
+
const firstRoute = flow.steps[0] || '/';
|
|
176
|
+
const page = flow.page || routes.find((r) => r.path === firstRoute)?.path || firstRoute;
|
|
177
|
+
const steps = flow.steps.map((routePath, stepIndex) => {
|
|
178
|
+
// For transition steps (not the first), find the edge that leads to this route
|
|
179
|
+
// and try to match its trigger text to a specific selector
|
|
180
|
+
const incomingEdge = stepIndex > 0
|
|
181
|
+
? flow.edges.find((e) => e.to === routePath)
|
|
182
|
+
: null;
|
|
183
|
+
let target;
|
|
184
|
+
if (incomingEdge?.trigger && incomingEdge.trigger !== 'link') {
|
|
185
|
+
// Try to find the specific triggering element by label match
|
|
186
|
+
const fromRoute = incomingEdge.from;
|
|
187
|
+
const byLabel = selectors.find((s) => s.route === fromRoute &&
|
|
188
|
+
s.label?.toLowerCase() === incomingEdge.trigger.toLowerCase());
|
|
189
|
+
const byAttr = !byLabel
|
|
190
|
+
? selectors.find((s) => s.route === fromRoute &&
|
|
191
|
+
s.attributes.some((a) => a.value.toLowerCase() === incomingEdge.trigger.toLowerCase()))
|
|
192
|
+
: null;
|
|
193
|
+
target = (byLabel || byAttr)?.selector;
|
|
194
|
+
}
|
|
195
|
+
// Fallback: first selector on the destination route
|
|
196
|
+
if (!target) {
|
|
197
|
+
target =
|
|
198
|
+
selectors.find((s) => s.route === routePath)?.selector ||
|
|
199
|
+
`[data-clippy-route='${routePath}']`;
|
|
200
|
+
}
|
|
201
|
+
const isFirst = stepIndex === 0;
|
|
202
|
+
const isInteractionStep = flow.edges.some((e) => e.from === e.to && e.trigger?.startsWith('on'));
|
|
203
|
+
return {
|
|
204
|
+
step: stepIndex + 1,
|
|
205
|
+
action: isFirst
|
|
206
|
+
? isInteractionStep ? 'interact' : 'navigate'
|
|
207
|
+
: 'transition',
|
|
208
|
+
target,
|
|
209
|
+
};
|
|
210
|
+
});
|
|
211
|
+
const intentPatterns = flow.intentPatterns && flow.intentPatterns.length > 0
|
|
212
|
+
? flow.intentPatterns
|
|
213
|
+
: [flow.name.toLowerCase()];
|
|
214
|
+
return {
|
|
215
|
+
flowId: flow.id || `flow_${flowIndex + 1}`,
|
|
216
|
+
page,
|
|
217
|
+
intentPatterns,
|
|
218
|
+
steps,
|
|
219
|
+
};
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
deriveComponentName(filePath) {
|
|
223
|
+
return (0, IdStrategy_1.deriveComponentName)(filePath);
|
|
224
|
+
}
|
|
225
|
+
deriveStableId(component, tag, line) {
|
|
226
|
+
return (0, IdStrategy_1.deriveClippyId)(component, tag, line);
|
|
227
|
+
}
|
|
228
|
+
extractClippyIdFromSelector(selector) {
|
|
229
|
+
const match = selector.match(/\[data-clippy-id=['\"]([^'\"]+)['\"]\]/);
|
|
230
|
+
return match?.[1] || null;
|
|
231
|
+
}
|
|
232
|
+
simpleHash(input) {
|
|
233
|
+
let hash = 0;
|
|
234
|
+
for (let i = 0; i < input.length; i++) {
|
|
235
|
+
hash = (hash * 31 + input.charCodeAt(i)) >>> 0;
|
|
236
|
+
}
|
|
237
|
+
return hash.toString(36);
|
|
238
|
+
}
|
|
19
239
|
}
|
|
20
240
|
exports.PackageBuilder = PackageBuilder;
|
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
import type { KnowledgePackage } from '../types';
|
|
1
|
+
import type { KnowledgePackage, PolicyArtifacts } from '../types';
|
|
2
2
|
export declare class PackageWriter {
|
|
3
3
|
write(outputDir: string, knowledgePackage: KnowledgePackage): {
|
|
4
4
|
jsonPath: string;
|
|
5
5
|
gzipPath: string;
|
|
6
6
|
};
|
|
7
|
+
writeArtifacts(outputDir: string, artifacts: PolicyArtifacts, options?: {
|
|
8
|
+
gzip?: boolean;
|
|
9
|
+
}): {
|
|
10
|
+
policyPath: string;
|
|
11
|
+
selectorsPath: string;
|
|
12
|
+
policyGzipPath?: string;
|
|
13
|
+
selectorsGzipPath?: string;
|
|
14
|
+
};
|
|
7
15
|
}
|
|
@@ -18,5 +18,31 @@ class PackageWriter {
|
|
|
18
18
|
fs_1.default.writeFileSync(gzipPath, zlib_1.default.gzipSync(content));
|
|
19
19
|
return { jsonPath, gzipPath };
|
|
20
20
|
}
|
|
21
|
+
writeArtifacts(outputDir, artifacts, options) {
|
|
22
|
+
fs_1.default.mkdirSync(outputDir, { recursive: true });
|
|
23
|
+
const policyPath = path_1.default.join(outputDir, 'clippy-policy.json');
|
|
24
|
+
const selectorsPath = path_1.default.join(outputDir, 'clippy-selectors.json');
|
|
25
|
+
const policyContent = JSON.stringify(artifacts.policy, null, 2);
|
|
26
|
+
const selectorsContent = JSON.stringify(artifacts.selectorManifest, null, 2);
|
|
27
|
+
fs_1.default.writeFileSync(policyPath, policyContent, 'utf-8');
|
|
28
|
+
fs_1.default.writeFileSync(selectorsPath, selectorsContent, 'utf-8');
|
|
29
|
+
const shouldGzip = options?.gzip ?? true;
|
|
30
|
+
if (!shouldGzip) {
|
|
31
|
+
return {
|
|
32
|
+
policyPath,
|
|
33
|
+
selectorsPath,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const policyGzipPath = `${policyPath}.gz`;
|
|
37
|
+
const selectorsGzipPath = `${selectorsPath}.gz`;
|
|
38
|
+
fs_1.default.writeFileSync(policyGzipPath, zlib_1.default.gzipSync(policyContent));
|
|
39
|
+
fs_1.default.writeFileSync(selectorsGzipPath, zlib_1.default.gzipSync(selectorsContent));
|
|
40
|
+
return {
|
|
41
|
+
policyPath,
|
|
42
|
+
selectorsPath,
|
|
43
|
+
policyGzipPath,
|
|
44
|
+
selectorsGzipPath,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
21
47
|
}
|
|
22
48
|
exports.PackageWriter = PackageWriter;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { PolicyArtifacts } from '../types';
|
|
2
|
+
export interface ArtifactRequest {
|
|
3
|
+
endpoint: string;
|
|
4
|
+
payload: Buffer;
|
|
5
|
+
extraHeaders?: Record<string, string>;
|
|
6
|
+
}
|
|
7
|
+
declare const POLICY_ENDPOINT = "https://api.clippy.dev/v1/policy";
|
|
8
|
+
declare const SELECTORS_ENDPOINT = "https://api.clippy.dev/v1/selectors";
|
|
9
|
+
declare const BUNDLE_ENDPOINT = "https://api.clippy.dev/v1/policy-artifacts";
|
|
10
|
+
export declare function buildArtifactRequests(artifacts: PolicyArtifacts, mode: 'single' | 'split'): ArtifactRequest[];
|
|
11
|
+
export { POLICY_ENDPOINT, SELECTORS_ENDPOINT, BUNDLE_ENDPOINT };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.BUNDLE_ENDPOINT = exports.SELECTORS_ENDPOINT = exports.POLICY_ENDPOINT = void 0;
|
|
7
|
+
exports.buildArtifactRequests = buildArtifactRequests;
|
|
8
|
+
const zlib_1 = __importDefault(require("zlib"));
|
|
9
|
+
const POLICY_ENDPOINT = 'https://api.clippy.dev/v1/policy';
|
|
10
|
+
exports.POLICY_ENDPOINT = POLICY_ENDPOINT;
|
|
11
|
+
const SELECTORS_ENDPOINT = 'https://api.clippy.dev/v1/selectors';
|
|
12
|
+
exports.SELECTORS_ENDPOINT = SELECTORS_ENDPOINT;
|
|
13
|
+
const BUNDLE_ENDPOINT = 'https://api.clippy.dev/v1/policy-artifacts';
|
|
14
|
+
exports.BUNDLE_ENDPOINT = BUNDLE_ENDPOINT;
|
|
15
|
+
function buildArtifactRequests(artifacts, mode) {
|
|
16
|
+
if (mode === 'split') {
|
|
17
|
+
const policyPayload = zlib_1.default.gzipSync(JSON.stringify(artifacts.policy));
|
|
18
|
+
const selectorsPayload = zlib_1.default.gzipSync(JSON.stringify(artifacts.selectorManifest));
|
|
19
|
+
return [
|
|
20
|
+
{
|
|
21
|
+
endpoint: POLICY_ENDPOINT,
|
|
22
|
+
payload: policyPayload,
|
|
23
|
+
extraHeaders: { 'X-Clippy-Artifact': 'policy' },
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
endpoint: SELECTORS_ENDPOINT,
|
|
27
|
+
payload: selectorsPayload,
|
|
28
|
+
extraHeaders: { 'X-Clippy-Artifact': 'selectors' },
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
}
|
|
32
|
+
const bundle = zlib_1.default.gzipSync(JSON.stringify(artifacts));
|
|
33
|
+
return [
|
|
34
|
+
{
|
|
35
|
+
endpoint: BUNDLE_ENDPOINT,
|
|
36
|
+
payload: bundle,
|
|
37
|
+
extraHeaders: { 'X-Clippy-Artifact': 'bundle' },
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import type { ClippyPluginOptions } from '../types';
|
|
1
|
+
import type { ClippyPluginOptions, PolicyArtifacts, UploadResult } from '../types';
|
|
2
2
|
export declare class Uploader {
|
|
3
3
|
private options;
|
|
4
4
|
private endpoint;
|
|
5
|
+
private policyArtifactsEndpoint;
|
|
6
|
+
private policyEndpoint;
|
|
7
|
+
private selectorsEndpoint;
|
|
5
8
|
private requestTimeoutMs;
|
|
6
9
|
private maxAttempts;
|
|
7
10
|
constructor(options: ClippyPluginOptions);
|
|
8
11
|
upload(compressedPackage: Buffer): Promise<boolean>;
|
|
12
|
+
uploadArtifacts(artifacts: PolicyArtifacts): Promise<UploadResult>;
|
|
13
|
+
private uploadWithRetry;
|
|
9
14
|
private uploadOnce;
|
|
10
15
|
}
|
package/dist/upload/Uploader.js
CHANGED
|
@@ -5,10 +5,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.Uploader = void 0;
|
|
7
7
|
const https_1 = __importDefault(require("https"));
|
|
8
|
+
const UploadStrategy_1 = require("./UploadStrategy");
|
|
8
9
|
class Uploader {
|
|
9
10
|
constructor(options) {
|
|
10
11
|
this.options = options;
|
|
11
12
|
this.endpoint = 'https://api.clippy.dev/v1/knowledge';
|
|
13
|
+
this.policyArtifactsEndpoint = 'https://api.clippy.dev/v1/policy-artifacts';
|
|
14
|
+
this.policyEndpoint = 'https://api.clippy.dev/v1/policy';
|
|
15
|
+
this.selectorsEndpoint = 'https://api.clippy.dev/v1/selectors';
|
|
12
16
|
this.requestTimeoutMs = 10000;
|
|
13
17
|
this.maxAttempts = 3;
|
|
14
18
|
}
|
|
@@ -18,7 +22,7 @@ class Uploader {
|
|
|
18
22
|
let lastError = null;
|
|
19
23
|
for (let attempt = 1; attempt <= this.maxAttempts; attempt++) {
|
|
20
24
|
try {
|
|
21
|
-
await this.uploadOnce(compressedPackage);
|
|
25
|
+
await this.uploadOnce(this.endpoint, compressedPackage);
|
|
22
26
|
return true;
|
|
23
27
|
}
|
|
24
28
|
catch (err) {
|
|
@@ -30,7 +34,47 @@ class Uploader {
|
|
|
30
34
|
}
|
|
31
35
|
throw lastError || new Error('Upload failed after retries');
|
|
32
36
|
}
|
|
33
|
-
async
|
|
37
|
+
async uploadArtifacts(artifacts) {
|
|
38
|
+
if (this.options.skipUpload) {
|
|
39
|
+
return {
|
|
40
|
+
skipped: true,
|
|
41
|
+
policyUploaded: false,
|
|
42
|
+
selectorsUploaded: false,
|
|
43
|
+
mode: this.options.artifactUploadMode || 'single',
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const mode = this.options.artifactUploadMode || 'single';
|
|
47
|
+
const requests = (0, UploadStrategy_1.buildArtifactRequests)(artifacts, mode);
|
|
48
|
+
for (const req of requests) {
|
|
49
|
+
await this.uploadWithRetry(req.endpoint, req.payload, {
|
|
50
|
+
...(req.extraHeaders || {}),
|
|
51
|
+
'X-Build-Id': artifacts.metadata.buildId,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
skipped: false,
|
|
56
|
+
policyUploaded: true,
|
|
57
|
+
selectorsUploaded: true,
|
|
58
|
+
mode,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
async uploadWithRetry(endpoint, payload, extraHeaders) {
|
|
62
|
+
let lastError = null;
|
|
63
|
+
for (let attempt = 1; attempt <= this.maxAttempts; attempt++) {
|
|
64
|
+
try {
|
|
65
|
+
await this.uploadOnce(endpoint, payload, extraHeaders);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
lastError = err;
|
|
70
|
+
if (attempt < this.maxAttempts) {
|
|
71
|
+
await delay(250 * Math.pow(2, attempt - 1));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
throw lastError || new Error('Upload failed after retries');
|
|
76
|
+
}
|
|
77
|
+
async uploadOnce(endpoint, compressedPackage, extraHeaders) {
|
|
34
78
|
return new Promise((resolve, reject) => {
|
|
35
79
|
let settled = false;
|
|
36
80
|
const finalize = (fn) => {
|
|
@@ -39,7 +83,7 @@ class Uploader {
|
|
|
39
83
|
settled = true;
|
|
40
84
|
fn();
|
|
41
85
|
};
|
|
42
|
-
const req = https_1.default.request(
|
|
86
|
+
const req = https_1.default.request(endpoint, {
|
|
43
87
|
method: 'POST',
|
|
44
88
|
headers: {
|
|
45
89
|
Authorization: `Bearer ${this.options.apiKey}`,
|
|
@@ -47,6 +91,7 @@ class Uploader {
|
|
|
47
91
|
'Content-Type': 'application/octet-stream',
|
|
48
92
|
'Content-Encoding': 'gzip',
|
|
49
93
|
'Content-Length': compressedPackage.length,
|
|
94
|
+
...extraHeaders,
|
|
50
95
|
},
|
|
51
96
|
}, (res) => {
|
|
52
97
|
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|