@pipeline-builder/pipeline-manager 0.0.1
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/LICENSE +202 -0
- package/README.md +145 -0
- package/cdk.json +91 -0
- package/config.yml +94 -0
- package/dist/agent/ai.d.ts +48 -0
- package/dist/agent/ai.d.ts.map +1 -0
- package/dist/agent/ai.js +149 -0
- package/dist/agent/bootstrap.d.ts +39 -0
- package/dist/agent/bootstrap.d.ts.map +1 -0
- package/dist/agent/bootstrap.js +66 -0
- package/dist/agent/env-file.d.ts +17 -0
- package/dist/agent/env-file.d.ts.map +1 -0
- package/dist/agent/env-file.js +47 -0
- package/dist/agent/executor.d.ts +30 -0
- package/dist/agent/executor.d.ts.map +1 -0
- package/dist/agent/executor.js +60 -0
- package/dist/agent/health.d.ts +43 -0
- package/dist/agent/health.d.ts.map +1 -0
- package/dist/agent/health.js +109 -0
- package/dist/agent/ports.d.ts +26 -0
- package/dist/agent/ports.d.ts.map +1 -0
- package/dist/agent/ports.js +132 -0
- package/dist/agent/post-steps.d.ts +61 -0
- package/dist/agent/post-steps.d.ts.map +1 -0
- package/dist/agent/post-steps.js +131 -0
- package/dist/agent/prereqs.d.ts +26 -0
- package/dist/agent/prereqs.d.ts.map +1 -0
- package/dist/agent/prereqs.js +134 -0
- package/dist/agent/targets.d.ts +128 -0
- package/dist/agent/targets.d.ts.map +1 -0
- package/dist/agent/targets.js +265 -0
- package/dist/agent/tools.d.ts +13 -0
- package/dist/agent/tools.d.ts.map +1 -0
- package/dist/agent/tools.js +75 -0
- package/dist/agent/troubleshoot.d.ts +30 -0
- package/dist/agent/troubleshoot.d.ts.map +1 -0
- package/dist/agent/troubleshoot.js +87 -0
- package/dist/boilerplate.d.ts +3 -0
- package/dist/boilerplate.d.ts.map +1 -0
- package/dist/boilerplate.js +54 -0
- package/dist/cdk.json +91 -0
- package/dist/cli.d.ts +62 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +463 -0
- package/dist/commands/audit-stacks.d.ts +24 -0
- package/dist/commands/audit-stacks.d.ts.map +1 -0
- package/dist/commands/audit-stacks.js +182 -0
- package/dist/commands/audit-tokens.d.ts +21 -0
- package/dist/commands/audit-tokens.d.ts.map +1 -0
- package/dist/commands/audit-tokens.js +153 -0
- package/dist/commands/bootstrap.d.ts +11 -0
- package/dist/commands/bootstrap.d.ts.map +1 -0
- package/dist/commands/bootstrap.js +159 -0
- package/dist/commands/create-pat.d.ts +27 -0
- package/dist/commands/create-pat.d.ts.map +1 -0
- package/dist/commands/create-pat.js +194 -0
- package/dist/commands/create-pipeline.d.ts +12 -0
- package/dist/commands/create-pipeline.d.ts.map +1 -0
- package/dist/commands/create-pipeline.js +315 -0
- package/dist/commands/deploy.d.ts +15 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +157 -0
- package/dist/commands/entity-get.d.ts +51 -0
- package/dist/commands/entity-get.d.ts.map +1 -0
- package/dist/commands/entity-get.js +69 -0
- package/dist/commands/entity-list.d.ts +39 -0
- package/dist/commands/entity-list.d.ts.map +1 -0
- package/dist/commands/entity-list.js +88 -0
- package/dist/commands/get-pipeline.d.ts +13 -0
- package/dist/commands/get-pipeline.d.ts.map +1 -0
- package/dist/commands/get-pipeline.js +49 -0
- package/dist/commands/get-plugin.d.ts +13 -0
- package/dist/commands/get-plugin.d.ts.map +1 -0
- package/dist/commands/get-plugin.js +51 -0
- package/dist/commands/instantiate-template.d.ts +20 -0
- package/dist/commands/instantiate-template.d.ts.map +1 -0
- package/dist/commands/instantiate-template.js +274 -0
- package/dist/commands/list-pipelines.d.ts +20 -0
- package/dist/commands/list-pipelines.d.ts.map +1 -0
- package/dist/commands/list-pipelines.js +99 -0
- package/dist/commands/list-plugins.d.ts +20 -0
- package/dist/commands/list-plugins.d.ts.map +1 -0
- package/dist/commands/list-plugins.js +92 -0
- package/dist/commands/login.d.ts +21 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +174 -0
- package/dist/commands/new-plugin.d.ts +12 -0
- package/dist/commands/new-plugin.d.ts.map +1 -0
- package/dist/commands/new-plugin.js +129 -0
- package/dist/commands/org-export.d.ts +25 -0
- package/dist/commands/org-export.d.ts.map +1 -0
- package/dist/commands/org-export.js +100 -0
- package/dist/commands/provision.d.ts +122 -0
- package/dist/commands/provision.d.ts.map +1 -0
- package/dist/commands/provision.js +867 -0
- package/dist/commands/redrive-events.d.ts +24 -0
- package/dist/commands/redrive-events.d.ts.map +1 -0
- package/dist/commands/redrive-events.js +127 -0
- package/dist/commands/register.d.ts +38 -0
- package/dist/commands/register.d.ts.map +1 -0
- package/dist/commands/register.js +160 -0
- package/dist/commands/setup-events.d.ts +15 -0
- package/dist/commands/setup-events.d.ts.map +1 -0
- package/dist/commands/setup-events.js +168 -0
- package/dist/commands/status.d.ts +11 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +81 -0
- package/dist/commands/store-token.d.ts +26 -0
- package/dist/commands/store-token.d.ts.map +1 -0
- package/dist/commands/store-token.js +305 -0
- package/dist/commands/synth.d.ts +21 -0
- package/dist/commands/synth.d.ts.map +1 -0
- package/dist/commands/synth.js +158 -0
- package/dist/commands/upload-plugin.d.ts +21 -0
- package/dist/commands/upload-plugin.d.ts.map +1 -0
- package/dist/commands/upload-plugin.js +275 -0
- package/dist/commands/validate-plugin.d.ts +12 -0
- package/dist/commands/validate-plugin.d.ts.map +1 -0
- package/dist/commands/validate-plugin.js +175 -0
- package/dist/commands/validate-templates.d.ts +11 -0
- package/dist/commands/validate-templates.d.ts.map +1 -0
- package/dist/commands/validate-templates.js +116 -0
- package/dist/commands/version.d.ts +12 -0
- package/dist/commands/version.d.ts.map +1 -0
- package/dist/commands/version.js +216 -0
- package/dist/config/cli.constants.d.ts +116 -0
- package/dist/config/cli.constants.d.ts.map +1 -0
- package/dist/config/cli.constants.js +194 -0
- package/dist/config.yml +94 -0
- package/dist/lambda/token-renew-handler.d.ts +2 -0
- package/dist/lambda/token-renew-handler.d.ts.map +1 -0
- package/dist/lambda/token-renew-handler.js +125 -0
- package/dist/templates/events-stack.json +174 -0
- package/dist/templates/token-renew-stack.json +122 -0
- package/dist/types/config.d.ts +53 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +4 -0
- package/dist/types/error.d.ts +61 -0
- package/dist/types/error.d.ts.map +1 -0
- package/dist/types/error.js +35 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +11 -0
- package/dist/types/pipeline-template.d.ts +48 -0
- package/dist/types/pipeline-template.d.ts.map +1 -0
- package/dist/types/pipeline-template.js +4 -0
- package/dist/types/pipeline.d.ts +121 -0
- package/dist/types/pipeline.d.ts.map +1 -0
- package/dist/types/pipeline.js +4 -0
- package/dist/types/plugin.d.ts +135 -0
- package/dist/types/plugin.d.ts.map +1 -0
- package/dist/types/plugin.js +4 -0
- package/dist/utils/api-client.d.ts +26 -0
- package/dist/utils/api-client.d.ts.map +1 -0
- package/dist/utils/api-client.js +151 -0
- package/dist/utils/audit-log.d.ts +8 -0
- package/dist/utils/audit-log.d.ts.map +1 -0
- package/dist/utils/audit-log.js +57 -0
- package/dist/utils/auth-guard.d.ts +20 -0
- package/dist/utils/auth-guard.d.ts.map +1 -0
- package/dist/utils/auth-guard.js +22 -0
- package/dist/utils/auth-utils.d.ts +68 -0
- package/dist/utils/auth-utils.d.ts.map +1 -0
- package/dist/utils/auth-utils.js +77 -0
- package/dist/utils/aws-env.d.ts +21 -0
- package/dist/utils/aws-env.d.ts.map +1 -0
- package/dist/utils/aws-env.js +34 -0
- package/dist/utils/aws-secrets.d.ts +48 -0
- package/dist/utils/aws-secrets.d.ts.map +1 -0
- package/dist/utils/aws-secrets.js +115 -0
- package/dist/utils/banner.d.ts +19 -0
- package/dist/utils/banner.d.ts.map +1 -0
- package/dist/utils/banner.js +52 -0
- package/dist/utils/cdk-utils.d.ts +74 -0
- package/dist/utils/cdk-utils.d.ts.map +1 -0
- package/dist/utils/cdk-utils.js +152 -0
- package/dist/utils/command-utils.d.ts +75 -0
- package/dist/utils/command-utils.d.ts.map +1 -0
- package/dist/utils/command-utils.js +155 -0
- package/dist/utils/config-loader.d.ts +33 -0
- package/dist/utils/config-loader.d.ts.map +1 -0
- package/dist/utils/config-loader.js +154 -0
- package/dist/utils/cron.d.ts +28 -0
- package/dist/utils/cron.d.ts.map +1 -0
- package/dist/utils/cron.js +119 -0
- package/dist/utils/deploy-runner.d.ts +38 -0
- package/dist/utils/deploy-runner.d.ts.map +1 -0
- package/dist/utils/deploy-runner.js +112 -0
- package/dist/utils/error-handler.d.ts +29 -0
- package/dist/utils/error-handler.d.ts.map +1 -0
- package/dist/utils/error-handler.js +302 -0
- package/dist/utils/list-command-utils.d.ts +23 -0
- package/dist/utils/list-command-utils.d.ts.map +1 -0
- package/dist/utils/list-command-utils.js +57 -0
- package/dist/utils/output-utils.d.ts +40 -0
- package/dist/utils/output-utils.d.ts.map +1 -0
- package/dist/utils/output-utils.js +250 -0
- package/dist/utils/pipeline-config.d.ts +33 -0
- package/dist/utils/pipeline-config.d.ts.map +1 -0
- package/dist/utils/pipeline-config.js +55 -0
- package/dist/utils/platform-secret.d.ts +27 -0
- package/dist/utils/platform-secret.d.ts.map +1 -0
- package/dist/utils/platform-secret.js +92 -0
- package/dist/utils/plugin-resolver.d.ts +17 -0
- package/dist/utils/plugin-resolver.d.ts.map +1 -0
- package/dist/utils/plugin-resolver.js +101 -0
- package/dist/utils/preflight.d.ts +26 -0
- package/dist/utils/preflight.d.ts.map +1 -0
- package/dist/utils/preflight.js +59 -0
- package/dist/utils/rate-limiter.d.ts +15 -0
- package/dist/utils/rate-limiter.d.ts.map +1 -0
- package/dist/utils/rate-limiter.js +164 -0
- package/dist/utils/registry.d.ts +86 -0
- package/dist/utils/registry.d.ts.map +1 -0
- package/dist/utils/registry.js +140 -0
- package/dist/utils/response-utils.d.ts +21 -0
- package/dist/utils/response-utils.d.ts.map +1 -0
- package/dist/utils/response-utils.js +70 -0
- package/dist/utils/tls.d.ts +52 -0
- package/dist/utils/tls.d.ts.map +1 -0
- package/dist/utils/tls.js +88 -0
- package/package.json +181 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { ApiClient } from './api-client.js';
|
|
4
|
+
import { printWarning } from './output-utils.js';
|
|
5
|
+
/**
|
|
6
|
+
* Cache key matching `PluginLookup.normalize()`: explicit alias when set,
|
|
7
|
+
* otherwise `${name}-alias`. Must stay in sync with the consumers in
|
|
8
|
+
* pipeline-core/plugin-lookup.ts and pipeline-builder.ts that read this map.
|
|
9
|
+
*/
|
|
10
|
+
function cacheKey(ref) {
|
|
11
|
+
return ref.alias || `${ref.name}-alias`;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Walk the pipeline props tree to collect every plugin reference. Plugins
|
|
15
|
+
* appear at `synth.plugin` and at `stages[].steps[].plugin`. Entries are
|
|
16
|
+
* de-duplicated by `cacheKey()` — the same key `PluginLookup.plugin()` uses,
|
|
17
|
+
* so the resolved-plugins map lookups match downstream.
|
|
18
|
+
*/
|
|
19
|
+
function collectPluginRefs(props) {
|
|
20
|
+
const refs = [];
|
|
21
|
+
const seen = new Set();
|
|
22
|
+
const push = (raw) => {
|
|
23
|
+
if (!raw || typeof raw !== 'object')
|
|
24
|
+
return;
|
|
25
|
+
const r = raw;
|
|
26
|
+
if (!r.name)
|
|
27
|
+
return;
|
|
28
|
+
const key = cacheKey(r);
|
|
29
|
+
if (seen.has(key))
|
|
30
|
+
return;
|
|
31
|
+
seen.add(key);
|
|
32
|
+
refs.push(r);
|
|
33
|
+
};
|
|
34
|
+
const synth = props.synth;
|
|
35
|
+
push(synth?.plugin);
|
|
36
|
+
const stages = props.stages;
|
|
37
|
+
for (const stage of stages ?? []) {
|
|
38
|
+
for (const step of stage.steps ?? []) {
|
|
39
|
+
push(step.plugin);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return refs;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Pre-resolve plugins by calling the same `POST /api/plugins/lookup` endpoint
|
|
46
|
+
* the deploy-time custom resource Lambda uses. Returning the full Plugin
|
|
47
|
+
* record at synth time is what allows the resulting CFN template to ship
|
|
48
|
+
* with real CodeBuild image URIs (`<host>/<ns>/<name>:<version>`) rather than
|
|
49
|
+
* the `aws/codebuild/standard:7.0` fallback that the synth-time token path
|
|
50
|
+
* forces.
|
|
51
|
+
*
|
|
52
|
+
* Failures are non-fatal: a missing plugin or unreachable API logs a warning
|
|
53
|
+
* and falls through to the deploy-time custom resource path so partial
|
|
54
|
+
* platform outages don't block synth/deploy.
|
|
55
|
+
*
|
|
56
|
+
* Keyed by `alias || name` to match `PluginLookup.plugin()`.
|
|
57
|
+
*/
|
|
58
|
+
export async function resolvePluginsForProps(client, props) {
|
|
59
|
+
const refs = collectPluginRefs(props);
|
|
60
|
+
if (refs.length === 0)
|
|
61
|
+
return {};
|
|
62
|
+
const resolved = {};
|
|
63
|
+
await Promise.all(refs.map(async (ref) => {
|
|
64
|
+
const key = cacheKey(ref);
|
|
65
|
+
// The plugin NAME lives on the ref (a sibling of `filter`), NOT inside the
|
|
66
|
+
// filter object — but the lookup matches on the filter. A filter without
|
|
67
|
+
// `name` (e.g. `{version, visibility, isActive, isDefault}`) matches ANY
|
|
68
|
+
// plugin with those attributes, and the endpoint returns an arbitrary one
|
|
69
|
+
// (seen: `dockerfile-multi-provider`). That made the synth and every step
|
|
70
|
+
// resolve to the WRONG plugin. So when the filter omits `name`, fall back to
|
|
71
|
+
// the ref's plugin name; an explicit filter `name` still takes precedence.
|
|
72
|
+
const filter = { name: ref.name, ...(ref.filter ?? { isActive: true, isDefault: true }) };
|
|
73
|
+
try {
|
|
74
|
+
const res = await client.post('/api/plugins/lookup', { filter });
|
|
75
|
+
// Unwrap the plugin record from whatever envelope the response middleware
|
|
76
|
+
// applied. The platform's standard success envelope is
|
|
77
|
+
// `{ success, statusCode, data: { plugin: Plugin } }` (note the DOUBLE
|
|
78
|
+
// nesting: data.plugin), but tolerate `{ data: Plugin }`, `{ plugin }`,
|
|
79
|
+
// and a bare Plugin too. Only stopping at `res.data` (which is
|
|
80
|
+
// `{ plugin: ... }`) made `.name` undefined → every lookup fell back to
|
|
81
|
+
// deploy-time resolution even though the catalog had the plugin.
|
|
82
|
+
const data = res.data;
|
|
83
|
+
const plugin = data?.plugin // { data: { plugin } }
|
|
84
|
+
?? res.plugin // { plugin }
|
|
85
|
+
?? data // { data: Plugin }
|
|
86
|
+
?? res; // bare Plugin
|
|
87
|
+
if (plugin && typeof plugin === 'object' && plugin.name) {
|
|
88
|
+
resolved[key] = plugin;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
printWarning(`Plugin "${ref.name}" lookup returned no record — falling back to deploy-time resolution`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
96
|
+
printWarning(`Plugin "${ref.name}" pre-resolution failed (${msg}) — falling back to deploy-time resolution`);
|
|
97
|
+
}
|
|
98
|
+
}));
|
|
99
|
+
return resolved;
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2luLXJlc29sdmVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3V0aWxzL3BsdWdpbi1yZXNvbHZlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBRXRDLE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUM1QyxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFTakQ7Ozs7R0FJRztBQUNILFNBQVMsUUFBUSxDQUFDLEdBQWM7SUFDOUIsT0FBTyxHQUFHLENBQUMsS0FBSyxJQUFJLEdBQUcsR0FBRyxDQUFDLElBQUksUUFBUSxDQUFDO0FBQzFDLENBQUM7QUFFRDs7Ozs7R0FLRztBQUNILFNBQVMsaUJBQWlCLENBQUMsS0FBOEI7SUFDdkQsTUFBTSxJQUFJLEdBQWdCLEVBQUUsQ0FBQztJQUM3QixNQUFNLElBQUksR0FBRyxJQUFJLEdBQUcsRUFBVSxDQUFDO0lBRS9CLE1BQU0sSUFBSSxHQUFHLENBQUMsR0FBWSxFQUFRLEVBQUU7UUFDbEMsSUFBSSxDQUFDLEdBQUcsSUFBSSxPQUFPLEdBQUcsS0FBSyxRQUFRO1lBQUUsT0FBTztRQUM1QyxNQUFNLENBQUMsR0FBRyxHQUFnQixDQUFDO1FBQzNCLElBQUksQ0FBQyxDQUFDLENBQUMsSUFBSTtZQUFFLE9BQU87UUFDcEIsTUFBTSxHQUFHLEdBQUcsUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ3hCLElBQUksSUFBSSxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUM7WUFBRSxPQUFPO1FBQzFCLElBQUksQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDZCxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ2YsQ0FBQyxDQUFDO0lBRUYsTUFBTSxLQUFLLEdBQUcsS0FBSyxDQUFDLEtBQXlDLENBQUM7SUFDOUQsSUFBSSxDQUFDLEtBQUssRUFBRSxNQUFNLENBQUMsQ0FBQztJQUVwQixNQUFNLE1BQU0sR0FBRyxLQUFLLENBQUMsTUFBb0UsQ0FBQztJQUMxRixLQUFLLE1BQU0sS0FBSyxJQUFJLE1BQU0sSUFBSSxFQUFFLEVBQUUsQ0FBQztRQUNqQyxLQUFLLE1BQU0sSUFBSSxJQUFJLEtBQUssQ0FBQyxLQUFLLElBQUksRUFBRSxFQUFFLENBQUM7WUFDckMsSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUNwQixDQUFDO0lBQ0gsQ0FBQztJQUVELE9BQU8sSUFBSSxDQUFDO0FBQ2QsQ0FBQztBQUVEOzs7Ozs7Ozs7Ozs7O0dBYUc7QUFDSCxNQUFNLENBQUMsS0FBSyxVQUFVLHNCQUFzQixDQUMxQyxNQUFpQixFQUNqQixLQUE4QjtJQUU5QixNQUFNLElBQUksR0FBRyxpQkFBaUIsQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUN0QyxJQUFJLElBQUksQ0FBQyxNQUFNLEtBQUssQ0FBQztRQUFFLE9BQU8sRUFBRSxDQUFDO0lBRWpDLE1BQU0sUUFBUSxHQUE0QixFQUFFLENBQUM7SUFFN0MsTUFBTSxPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsS0FBSyxFQUFFLEdBQUcsRUFBRSxFQUFFO1FBQ3ZDLE1BQU0sR0FBRyxHQUFHLFFBQVEsQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUMxQiwyRUFBMkU7UUFDM0UseUVBQXlFO1FBQ3pFLHlFQUF5RTtRQUN6RSwwRUFBMEU7UUFDMUUsMEVBQTBFO1FBQzFFLDZFQUE2RTtRQUM3RSwyRUFBMkU7UUFDM0UsTUFBTSxNQUFNLEdBQUcsRUFBRSxJQUFJLEVBQUUsR0FBRyxDQUFDLElBQUksRUFBRSxHQUFHLENBQUMsR0FBRyxDQUFDLE1BQU0sSUFBSSxFQUFFLFFBQVEsRUFBRSxJQUFJLEVBQUUsU0FBUyxFQUFFLElBQUksRUFBRSxDQUFDLEVBQUUsQ0FBQztRQUMxRixJQUFJLENBQUM7WUFDSCxNQUFNLEdBQUcsR0FBRyxNQUFNLE1BQU0sQ0FBQyxJQUFJLENBQVUscUJBQXFCLEVBQUUsRUFBRSxNQUFNLEVBQUUsQ0FBQyxDQUFDO1lBQzFFLDBFQUEwRTtZQUMxRSx1REFBdUQ7WUFDdkQsdUVBQXVFO1lBQ3ZFLHdFQUF3RTtZQUN4RSwrREFBK0Q7WUFDL0Qsd0VBQXdFO1lBQ3hFLGlFQUFpRTtZQUNqRSxNQUFNLElBQUksR0FBSSxHQUEwQixDQUFDLElBQUksQ0FBQztZQUM5QyxNQUFNLE1BQU0sR0FDVCxJQUF5QyxFQUFFLE1BQU0sQ0FBQyx1QkFBdUI7bUJBQ3RFLEdBQTRCLENBQUMsTUFBTSxDQUFDLGFBQWE7bUJBQ2xELElBQUksQ0FBQyxtQkFBbUI7bUJBQ3hCLEdBQUcsQ0FBQyxDQUFDLGNBQWM7WUFDeEIsSUFBSSxNQUFNLElBQUksT0FBTyxNQUFNLEtBQUssUUFBUSxJQUFLLE1BQTRCLENBQUMsSUFBSSxFQUFFLENBQUM7Z0JBQy9FLFFBQVEsQ0FBQyxHQUFHLENBQUMsR0FBRyxNQUFNLENBQUM7WUFDekIsQ0FBQztpQkFBTSxDQUFDO2dCQUNOLFlBQVksQ0FBQyxXQUFXLEdBQUcsQ0FBQyxJQUFJLHNFQUFzRSxDQUFDLENBQUM7WUFDMUcsQ0FBQztRQUNILENBQUM7UUFBQyxPQUFPLEdBQUcsRUFBRSxDQUFDO1lBQ2IsTUFBTSxHQUFHLEdBQUcsR0FBRyxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDO1lBQzdELFlBQVksQ0FBQyxXQUFXLEdBQUcsQ0FBQyxJQUFJLDRCQUE0QixHQUFHLDRDQUE0QyxDQUFDLENBQUM7UUFDL0csQ0FBQztJQUNILENBQUMsQ0FBQyxDQUFDLENBQUM7SUFFSixPQUFPLFFBQVEsQ0FBQztBQUNsQixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQ29weXJpZ2h0IDIwMjYgUGlwZWxpbmUgQnVpbGRlciBDb250cmlidXRvcnNcbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBcGFjaGUtMi4wXG5cbmltcG9ydCB7IEFwaUNsaWVudCB9IGZyb20gJy4vYXBpLWNsaWVudC5qcyc7XG5pbXBvcnQgeyBwcmludFdhcm5pbmcgfSBmcm9tICcuL291dHB1dC11dGlscy5qcyc7XG5cbi8qKiBNaW5pbWFsIHBsdWdpbiByZWZlcmVuY2Ugc2hhcGUg4oCUIG1hdGNoZXMgUGx1Z2luT3B0aW9ucyBmcm9tIHBpcGVsaW5lLWNvcmUuICovXG5pbnRlcmZhY2UgUGx1Z2luUmVmIHtcbiAgbmFtZTogc3RyaW5nO1xuICBhbGlhcz86IHN0cmluZztcbiAgZmlsdGVyPzogUmVjb3JkPHN0cmluZywgdW5rbm93bj47XG59XG5cbi8qKlxuICogQ2FjaGUga2V5IG1hdGNoaW5nIGBQbHVnaW5Mb29rdXAubm9ybWFsaXplKClgOiBleHBsaWNpdCBhbGlhcyB3aGVuIHNldCxcbiAqIG90aGVyd2lzZSBgJHtuYW1lfS1hbGlhc2AuIE11c3Qgc3RheSBpbiBzeW5jIHdpdGggdGhlIGNvbnN1bWVycyBpblxuICogcGlwZWxpbmUtY29yZS9wbHVnaW4tbG9va3VwLnRzIGFuZCBwaXBlbGluZS1idWlsZGVyLnRzIHRoYXQgcmVhZCB0aGlzIG1hcC5cbiAqL1xuZnVuY3Rpb24gY2FjaGVLZXkocmVmOiBQbHVnaW5SZWYpOiBzdHJpbmcge1xuICByZXR1cm4gcmVmLmFsaWFzIHx8IGAke3JlZi5uYW1lfS1hbGlhc2A7XG59XG5cbi8qKlxuICogV2FsayB0aGUgcGlwZWxpbmUgcHJvcHMgdHJlZSB0byBjb2xsZWN0IGV2ZXJ5IHBsdWdpbiByZWZlcmVuY2UuIFBsdWdpbnNcbiAqIGFwcGVhciBhdCBgc3ludGgucGx1Z2luYCBhbmQgYXQgYHN0YWdlc1tdLnN0ZXBzW10ucGx1Z2luYC4gRW50cmllcyBhcmVcbiAqIGRlLWR1cGxpY2F0ZWQgYnkgYGNhY2hlS2V5KClgIOKAlCB0aGUgc2FtZSBrZXkgYFBsdWdpbkxvb2t1cC5wbHVnaW4oKWAgdXNlcyxcbiAqIHNvIHRoZSByZXNvbHZlZC1wbHVnaW5zIG1hcCBsb29rdXBzIG1hdGNoIGRvd25zdHJlYW0uXG4gKi9cbmZ1bmN0aW9uIGNvbGxlY3RQbHVnaW5SZWZzKHByb3BzOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPik6IFBsdWdpblJlZltdIHtcbiAgY29uc3QgcmVmczogUGx1Z2luUmVmW10gPSBbXTtcbiAgY29uc3Qgc2VlbiA9IG5ldyBTZXQ8c3RyaW5nPigpO1xuXG4gIGNvbnN0IHB1c2ggPSAocmF3OiB1bmtub3duKTogdm9pZCA9PiB7XG4gICAgaWYgKCFyYXcgfHwgdHlwZW9mIHJhdyAhPT0gJ29iamVjdCcpIHJldHVybjtcbiAgICBjb25zdCByID0gcmF3IGFzIFBsdWdpblJlZjtcbiAgICBpZiAoIXIubmFtZSkgcmV0dXJuO1xuICAgIGNvbnN0IGtleSA9IGNhY2hlS2V5KHIpO1xuICAgIGlmIChzZWVuLmhhcyhrZXkpKSByZXR1cm47XG4gICAgc2Vlbi5hZGQoa2V5KTtcbiAgICByZWZzLnB1c2gocik7XG4gIH07XG5cbiAgY29uc3Qgc3ludGggPSBwcm9wcy5zeW50aCBhcyB7IHBsdWdpbj86IHVua25vd24gfSB8IHVuZGVmaW5lZDtcbiAgcHVzaChzeW50aD8ucGx1Z2luKTtcblxuICBjb25zdCBzdGFnZXMgPSBwcm9wcy5zdGFnZXMgYXMgQXJyYXk8eyBzdGVwcz86IEFycmF5PHsgcGx1Z2luPzogdW5rbm93biB9PiB9PiB8IHVuZGVmaW5lZDtcbiAgZm9yIChjb25zdCBzdGFnZSBvZiBzdGFnZXMgPz8gW10pIHtcbiAgICBmb3IgKGNvbnN0IHN0ZXAgb2Ygc3RhZ2Uuc3RlcHMgPz8gW10pIHtcbiAgICAgIHB1c2goc3RlcC5wbHVnaW4pO1xuICAgIH1cbiAgfVxuXG4gIHJldHVybiByZWZzO1xufVxuXG4vKipcbiAqIFByZS1yZXNvbHZlIHBsdWdpbnMgYnkgY2FsbGluZyB0aGUgc2FtZSBgUE9TVCAvYXBpL3BsdWdpbnMvbG9va3VwYCBlbmRwb2ludFxuICogdGhlIGRlcGxveS10aW1lIGN1c3RvbSByZXNvdXJjZSBMYW1iZGEgdXNlcy4gUmV0dXJuaW5nIHRoZSBmdWxsIFBsdWdpblxuICogcmVjb3JkIGF0IHN5bnRoIHRpbWUgaXMgd2hhdCBhbGxvd3MgdGhlIHJlc3VsdGluZyBDRk4gdGVtcGxhdGUgdG8gc2hpcFxuICogd2l0aCByZWFsIENvZGVCdWlsZCBpbWFnZSBVUklzIChgPGhvc3Q+Lzxucz4vPG5hbWU+Ojx2ZXJzaW9uPmApIHJhdGhlciB0aGFuXG4gKiB0aGUgYGF3cy9jb2RlYnVpbGQvc3RhbmRhcmQ6Ny4wYCBmYWxsYmFjayB0aGF0IHRoZSBzeW50aC10aW1lIHRva2VuIHBhdGhcbiAqIGZvcmNlcy5cbiAqXG4gKiBGYWlsdXJlcyBhcmUgbm9uLWZhdGFsOiBhIG1pc3NpbmcgcGx1Z2luIG9yIHVucmVhY2hhYmxlIEFQSSBsb2dzIGEgd2FybmluZ1xuICogYW5kIGZhbGxzIHRocm91Z2ggdG8gdGhlIGRlcGxveS10aW1lIGN1c3RvbSByZXNvdXJjZSBwYXRoIHNvIHBhcnRpYWxcbiAqIHBsYXRmb3JtIG91dGFnZXMgZG9uJ3QgYmxvY2sgc3ludGgvZGVwbG95LlxuICpcbiAqIEtleWVkIGJ5IGBhbGlhcyB8fCBuYW1lYCB0byBtYXRjaCBgUGx1Z2luTG9va3VwLnBsdWdpbigpYC5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHJlc29sdmVQbHVnaW5zRm9yUHJvcHMoXG4gIGNsaWVudDogQXBpQ2xpZW50LFxuICBwcm9wczogUmVjb3JkPHN0cmluZywgdW5rbm93bj4sXG4pOiBQcm9taXNlPFJlY29yZDxzdHJpbmcsIHVua25vd24+PiB7XG4gIGNvbnN0IHJlZnMgPSBjb2xsZWN0UGx1Z2luUmVmcyhwcm9wcyk7XG4gIGlmIChyZWZzLmxlbmd0aCA9PT0gMCkgcmV0dXJuIHt9O1xuXG4gIGNvbnN0IHJlc29sdmVkOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPiA9IHt9O1xuXG4gIGF3YWl0IFByb21pc2UuYWxsKHJlZnMubWFwKGFzeW5jIChyZWYpID0+IHtcbiAgICBjb25zdCBrZXkgPSBjYWNoZUtleShyZWYpO1xuICAgIC8vIFRoZSBwbHVnaW4gTkFNRSBsaXZlcyBvbiB0aGUgcmVmIChhIHNpYmxpbmcgb2YgYGZpbHRlcmApLCBOT1QgaW5zaWRlIHRoZVxuICAgIC8vIGZpbHRlciBvYmplY3Qg4oCUIGJ1dCB0aGUgbG9va3VwIG1hdGNoZXMgb24gdGhlIGZpbHRlci4gQSBmaWx0ZXIgd2l0aG91dFxuICAgIC8vIGBuYW1lYCAoZS5nLiBge3ZlcnNpb24sIHZpc2liaWxpdHksIGlzQWN0aXZlLCBpc0RlZmF1bHR9YCkgbWF0Y2hlcyBBTllcbiAgICAvLyBwbHVnaW4gd2l0aCB0aG9zZSBhdHRyaWJ1dGVzLCBhbmQgdGhlIGVuZHBvaW50IHJldHVybnMgYW4gYXJiaXRyYXJ5IG9uZVxuICAgIC8vIChzZWVuOiBgZG9ja2VyZmlsZS1tdWx0aS1wcm92aWRlcmApLiBUaGF0IG1hZGUgdGhlIHN5bnRoIGFuZCBldmVyeSBzdGVwXG4gICAgLy8gcmVzb2x2ZSB0byB0aGUgV1JPTkcgcGx1Z2luLiBTbyB3aGVuIHRoZSBmaWx0ZXIgb21pdHMgYG5hbWVgLCBmYWxsIGJhY2sgdG9cbiAgICAvLyB0aGUgcmVmJ3MgcGx1Z2luIG5hbWU7IGFuIGV4cGxpY2l0IGZpbHRlciBgbmFtZWAgc3RpbGwgdGFrZXMgcHJlY2VkZW5jZS5cbiAgICBjb25zdCBmaWx0ZXIgPSB7IG5hbWU6IHJlZi5uYW1lLCAuLi4ocmVmLmZpbHRlciA/PyB7IGlzQWN0aXZlOiB0cnVlLCBpc0RlZmF1bHQ6IHRydWUgfSkgfTtcbiAgICB0cnkge1xuICAgICAgY29uc3QgcmVzID0gYXdhaXQgY2xpZW50LnBvc3Q8dW5rbm93bj4oJy9hcGkvcGx1Z2lucy9sb29rdXAnLCB7IGZpbHRlciB9KTtcbiAgICAgIC8vIFVud3JhcCB0aGUgcGx1Z2luIHJlY29yZCBmcm9tIHdoYXRldmVyIGVudmVsb3BlIHRoZSByZXNwb25zZSBtaWRkbGV3YXJlXG4gICAgICAvLyBhcHBsaWVkLiBUaGUgcGxhdGZvcm0ncyBzdGFuZGFyZCBzdWNjZXNzIGVudmVsb3BlIGlzXG4gICAgICAvLyBgeyBzdWNjZXNzLCBzdGF0dXNDb2RlLCBkYXRhOiB7IHBsdWdpbjogUGx1Z2luIH0gfWAgKG5vdGUgdGhlIERPVUJMRVxuICAgICAgLy8gbmVzdGluZzogZGF0YS5wbHVnaW4pLCBidXQgdG9sZXJhdGUgYHsgZGF0YTogUGx1Z2luIH1gLCBgeyBwbHVnaW4gfWAsXG4gICAgICAvLyBhbmQgYSBiYXJlIFBsdWdpbiB0b28uIE9ubHkgc3RvcHBpbmcgYXQgYHJlcy5kYXRhYCAod2hpY2ggaXNcbiAgICAgIC8vIGB7IHBsdWdpbjogLi4uIH1gKSBtYWRlIGAubmFtZWAgdW5kZWZpbmVkIOKGkiBldmVyeSBsb29rdXAgZmVsbCBiYWNrIHRvXG4gICAgICAvLyBkZXBsb3ktdGltZSByZXNvbHV0aW9uIGV2ZW4gdGhvdWdoIHRoZSBjYXRhbG9nIGhhZCB0aGUgcGx1Z2luLlxuICAgICAgY29uc3QgZGF0YSA9IChyZXMgYXMgeyBkYXRhPzogdW5rbm93biB9KS5kYXRhO1xuICAgICAgY29uc3QgcGx1Z2luID1cbiAgICAgICAgKGRhdGEgYXMgeyBwbHVnaW4/OiB1bmtub3duIH0gfCB1bmRlZmluZWQpPy5wbHVnaW4gLy8geyBkYXRhOiB7IHBsdWdpbiB9IH1cbiAgICAgICAgPz8gKHJlcyBhcyB7IHBsdWdpbj86IHVua25vd24gfSkucGx1Z2luIC8vIHsgcGx1Z2luIH1cbiAgICAgICAgPz8gZGF0YSAvLyB7IGRhdGE6IFBsdWdpbiB9XG4gICAgICAgID8/IHJlczsgLy8gYmFyZSBQbHVnaW5cbiAgICAgIGlmIChwbHVnaW4gJiYgdHlwZW9mIHBsdWdpbiA9PT0gJ29iamVjdCcgJiYgKHBsdWdpbiBhcyB7IG5hbWU/OiBzdHJpbmcgfSkubmFtZSkge1xuICAgICAgICByZXNvbHZlZFtrZXldID0gcGx1Z2luO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgcHJpbnRXYXJuaW5nKGBQbHVnaW4gXCIke3JlZi5uYW1lfVwiIGxvb2t1cCByZXR1cm5lZCBubyByZWNvcmQg4oCUIGZhbGxpbmcgYmFjayB0byBkZXBsb3ktdGltZSByZXNvbHV0aW9uYCk7XG4gICAgICB9XG4gICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICBjb25zdCBtc2cgPSBlcnIgaW5zdGFuY2VvZiBFcnJvciA/IGVyci5tZXNzYWdlIDogU3RyaW5nKGVycik7XG4gICAgICBwcmludFdhcm5pbmcoYFBsdWdpbiBcIiR7cmVmLm5hbWV9XCIgcHJlLXJlc29sdXRpb24gZmFpbGVkICgke21zZ30pIOKAlCBmYWxsaW5nIGJhY2sgdG8gZGVwbG95LXRpbWUgcmVzb2x1dGlvbmApO1xuICAgIH1cbiAgfSkpO1xuXG4gIHJldHVybiByZXNvbHZlZDtcbn1cbiJdfQ==
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type Command } from 'commander';
|
|
2
|
+
export type LocalTool = 'cdk' | 'bundler';
|
|
3
|
+
/**
|
|
4
|
+
* Per-command local-tool requirements, checked by the CLI's `preAction` hook
|
|
5
|
+
* BEFORE a command's action runs — so a missing tool fails fast with an
|
|
6
|
+
* actionable message instead of deep inside a CDK bundle. Keyed by full command
|
|
7
|
+
* path (e.g. "pipeline deploy").
|
|
8
|
+
*
|
|
9
|
+
* Only `pipeline synth`/`deploy` shell out to cdk (+ esbuild/pnpm for the
|
|
10
|
+
* PluginLookup Lambda bundle). Every other command is API-only and requires
|
|
11
|
+
* nothing local, so it's absent here and never gated — as are version / help /
|
|
12
|
+
* completions. (`infra provision` runs its own target-specific prereq checks.)
|
|
13
|
+
*/
|
|
14
|
+
export declare const COMMAND_TOOL_REQUIREMENTS: Record<string, LocalTool[]>;
|
|
15
|
+
/** Full space-joined command path for a Commander command, e.g. "pipeline deploy". */
|
|
16
|
+
export declare function commandPath(cmd: Command): string;
|
|
17
|
+
/** The local tools a command declares it needs (empty when it needs nothing). */
|
|
18
|
+
export declare function requiredToolsFor(cmd: Command): LocalTool[];
|
|
19
|
+
/**
|
|
20
|
+
* Preflight the local tools an about-to-run command declares. Reuses the tools'
|
|
21
|
+
* `ensureX` helpers (which print the actionable message + install hint on
|
|
22
|
+
* failure), then exits non-zero so the command body never runs. No-op for any
|
|
23
|
+
* command without requirements (API-only commands, version/help/completions).
|
|
24
|
+
*/
|
|
25
|
+
export declare function preflightCommandTools(actionCommand: Command): void;
|
|
26
|
+
//# sourceMappingURL=preflight.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preflight.d.ts","sourceRoot":"","sources":["../../src/utils/preflight.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,WAAW,CAAC;AAGzC,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,SAAS,CAAC;AAE1C;;;;;;;;;;GAUG;AACH,eAAO,MAAM,yBAAyB,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,CAMjE,CAAC;AAEF,sFAAsF;AACtF,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAQhD;AAED,iFAAiF;AACjF,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,SAAS,EAAE,CAE1D;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,aAAa,EAAE,OAAO,GAAG,IAAI,CAWlE"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import {} from 'commander';
|
|
4
|
+
import { ensureBundlerAvailable, ensureCdkAvailable } from './cdk-utils.js';
|
|
5
|
+
/**
|
|
6
|
+
* Per-command local-tool requirements, checked by the CLI's `preAction` hook
|
|
7
|
+
* BEFORE a command's action runs — so a missing tool fails fast with an
|
|
8
|
+
* actionable message instead of deep inside a CDK bundle. Keyed by full command
|
|
9
|
+
* path (e.g. "pipeline deploy").
|
|
10
|
+
*
|
|
11
|
+
* Only `pipeline synth`/`deploy` shell out to cdk (+ esbuild/pnpm for the
|
|
12
|
+
* PluginLookup Lambda bundle). Every other command is API-only and requires
|
|
13
|
+
* nothing local, so it's absent here and never gated — as are version / help /
|
|
14
|
+
* completions. (`infra provision` runs its own target-specific prereq checks.)
|
|
15
|
+
*/
|
|
16
|
+
export const COMMAND_TOOL_REQUIREMENTS = {
|
|
17
|
+
'pipeline synth': ['cdk', 'bundler'],
|
|
18
|
+
'pipeline deploy': ['cdk', 'bundler'],
|
|
19
|
+
// `infra bootstrap` runs `cdk bootstrap` (AWS env bootstrap) — cdk only; it
|
|
20
|
+
// synthesizes no Lambda, so no esbuild/pnpm bundler is needed.
|
|
21
|
+
'infra bootstrap': ['cdk'],
|
|
22
|
+
};
|
|
23
|
+
/** Full space-joined command path for a Commander command, e.g. "pipeline deploy". */
|
|
24
|
+
export function commandPath(cmd) {
|
|
25
|
+
const parts = [];
|
|
26
|
+
let c = cmd;
|
|
27
|
+
while (c && c.parent) {
|
|
28
|
+
parts.unshift(c.name());
|
|
29
|
+
c = c.parent ?? undefined;
|
|
30
|
+
}
|
|
31
|
+
return parts.join(' ');
|
|
32
|
+
}
|
|
33
|
+
/** The local tools a command declares it needs (empty when it needs nothing). */
|
|
34
|
+
export function requiredToolsFor(cmd) {
|
|
35
|
+
return COMMAND_TOOL_REQUIREMENTS[commandPath(cmd)] ?? [];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Preflight the local tools an about-to-run command declares. Reuses the tools'
|
|
39
|
+
* `ensureX` helpers (which print the actionable message + install hint on
|
|
40
|
+
* failure), then exits non-zero so the command body never runs. No-op for any
|
|
41
|
+
* command without requirements (API-only commands, version/help/completions).
|
|
42
|
+
*/
|
|
43
|
+
export function preflightCommandTools(actionCommand) {
|
|
44
|
+
const reqs = requiredToolsFor(actionCommand);
|
|
45
|
+
if (reqs.length === 0)
|
|
46
|
+
return;
|
|
47
|
+
try {
|
|
48
|
+
if (reqs.includes('cdk'))
|
|
49
|
+
ensureCdkAvailable();
|
|
50
|
+
if (reqs.includes('bundler'))
|
|
51
|
+
ensureBundlerAvailable();
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Message already printed by ensureX. Halt before the action runs so the
|
|
55
|
+
// command doesn't proceed with a broken toolchain.
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJlZmxpZ2h0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3V0aWxzL3ByZWZsaWdodC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBRXRDLE9BQU8sRUFBZ0IsTUFBTSxXQUFXLENBQUM7QUFDekMsT0FBTyxFQUFFLHNCQUFzQixFQUFFLGtCQUFrQixFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFJNUU7Ozs7Ozs7Ozs7R0FVRztBQUNILE1BQU0sQ0FBQyxNQUFNLHlCQUF5QixHQUFnQztJQUNwRSxnQkFBZ0IsRUFBRSxDQUFDLEtBQUssRUFBRSxTQUFTLENBQUM7SUFDcEMsaUJBQWlCLEVBQUUsQ0FBQyxLQUFLLEVBQUUsU0FBUyxDQUFDO0lBQ3JDLDRFQUE0RTtJQUM1RSwrREFBK0Q7SUFDL0QsaUJBQWlCLEVBQUUsQ0FBQyxLQUFLLENBQUM7Q0FDM0IsQ0FBQztBQUVGLHNGQUFzRjtBQUN0RixNQUFNLFVBQVUsV0FBVyxDQUFDLEdBQVk7SUFDdEMsTUFBTSxLQUFLLEdBQWEsRUFBRSxDQUFDO0lBQzNCLElBQUksQ0FBQyxHQUF3QixHQUFHLENBQUM7SUFDakMsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLE1BQU0sRUFBRSxDQUFDO1FBQ3JCLEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFDLENBQUM7UUFDeEIsQ0FBQyxHQUFHLENBQUMsQ0FBQyxNQUFNLElBQUksU0FBUyxDQUFDO0lBQzVCLENBQUM7SUFDRCxPQUFPLEtBQUssQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7QUFDekIsQ0FBQztBQUVELGlGQUFpRjtBQUNqRixNQUFNLFVBQVUsZ0JBQWdCLENBQUMsR0FBWTtJQUMzQyxPQUFPLHlCQUF5QixDQUFDLFdBQVcsQ0FBQyxHQUFHLENBQUMsQ0FBQyxJQUFJLEVBQUUsQ0FBQztBQUMzRCxDQUFDO0FBRUQ7Ozs7O0dBS0c7QUFDSCxNQUFNLFVBQVUscUJBQXFCLENBQUMsYUFBc0I7SUFDMUQsTUFBTSxJQUFJLEdBQUcsZ0JBQWdCLENBQUMsYUFBYSxDQUFDLENBQUM7SUFDN0MsSUFBSSxJQUFJLENBQUMsTUFBTSxLQUFLLENBQUM7UUFBRSxPQUFPO0lBQzlCLElBQUksQ0FBQztRQUNILElBQUksSUFBSSxDQUFDLFFBQVEsQ0FBQyxLQUFLLENBQUM7WUFBRSxrQkFBa0IsRUFBRSxDQUFDO1FBQy9DLElBQUksSUFBSSxDQUFDLFFBQVEsQ0FBQyxTQUFTLENBQUM7WUFBRSxzQkFBc0IsRUFBRSxDQUFDO0lBQ3pELENBQUM7SUFBQyxNQUFNLENBQUM7UUFDUCx5RUFBeUU7UUFDekUsbURBQW1EO1FBQ25ELE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDbEIsQ0FBQztBQUNILENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuaW1wb3J0IHsgdHlwZSBDb21tYW5kIH0gZnJvbSAnY29tbWFuZGVyJztcbmltcG9ydCB7IGVuc3VyZUJ1bmRsZXJBdmFpbGFibGUsIGVuc3VyZUNka0F2YWlsYWJsZSB9IGZyb20gJy4vY2RrLXV0aWxzLmpzJztcblxuZXhwb3J0IHR5cGUgTG9jYWxUb29sID0gJ2NkaycgfCAnYnVuZGxlcic7XG5cbi8qKlxuICogUGVyLWNvbW1hbmQgbG9jYWwtdG9vbCByZXF1aXJlbWVudHMsIGNoZWNrZWQgYnkgdGhlIENMSSdzIGBwcmVBY3Rpb25gIGhvb2tcbiAqIEJFRk9SRSBhIGNvbW1hbmQncyBhY3Rpb24gcnVucyDigJQgc28gYSBtaXNzaW5nIHRvb2wgZmFpbHMgZmFzdCB3aXRoIGFuXG4gKiBhY3Rpb25hYmxlIG1lc3NhZ2UgaW5zdGVhZCBvZiBkZWVwIGluc2lkZSBhIENESyBidW5kbGUuIEtleWVkIGJ5IGZ1bGwgY29tbWFuZFxuICogcGF0aCAoZS5nLiBcInBpcGVsaW5lIGRlcGxveVwiKS5cbiAqXG4gKiBPbmx5IGBwaXBlbGluZSBzeW50aGAvYGRlcGxveWAgc2hlbGwgb3V0IHRvIGNkayAoKyBlc2J1aWxkL3BucG0gZm9yIHRoZVxuICogUGx1Z2luTG9va3VwIExhbWJkYSBidW5kbGUpLiBFdmVyeSBvdGhlciBjb21tYW5kIGlzIEFQSS1vbmx5IGFuZCByZXF1aXJlc1xuICogbm90aGluZyBsb2NhbCwgc28gaXQncyBhYnNlbnQgaGVyZSBhbmQgbmV2ZXIgZ2F0ZWQg4oCUIGFzIGFyZSB2ZXJzaW9uIC8gaGVscCAvXG4gKiBjb21wbGV0aW9ucy4gKGBpbmZyYSBwcm92aXNpb25gIHJ1bnMgaXRzIG93biB0YXJnZXQtc3BlY2lmaWMgcHJlcmVxIGNoZWNrcy4pXG4gKi9cbmV4cG9ydCBjb25zdCBDT01NQU5EX1RPT0xfUkVRVUlSRU1FTlRTOiBSZWNvcmQ8c3RyaW5nLCBMb2NhbFRvb2xbXT4gPSB7XG4gICdwaXBlbGluZSBzeW50aCc6IFsnY2RrJywgJ2J1bmRsZXInXSxcbiAgJ3BpcGVsaW5lIGRlcGxveSc6IFsnY2RrJywgJ2J1bmRsZXInXSxcbiAgLy8gYGluZnJhIGJvb3RzdHJhcGAgcnVucyBgY2RrIGJvb3RzdHJhcGAgKEFXUyBlbnYgYm9vdHN0cmFwKSDigJQgY2RrIG9ubHk7IGl0XG4gIC8vIHN5bnRoZXNpemVzIG5vIExhbWJkYSwgc28gbm8gZXNidWlsZC9wbnBtIGJ1bmRsZXIgaXMgbmVlZGVkLlxuICAnaW5mcmEgYm9vdHN0cmFwJzogWydjZGsnXSxcbn07XG5cbi8qKiBGdWxsIHNwYWNlLWpvaW5lZCBjb21tYW5kIHBhdGggZm9yIGEgQ29tbWFuZGVyIGNvbW1hbmQsIGUuZy4gXCJwaXBlbGluZSBkZXBsb3lcIi4gKi9cbmV4cG9ydCBmdW5jdGlvbiBjb21tYW5kUGF0aChjbWQ6IENvbW1hbmQpOiBzdHJpbmcge1xuICBjb25zdCBwYXJ0czogc3RyaW5nW10gPSBbXTtcbiAgbGV0IGM6IENvbW1hbmQgfCB1bmRlZmluZWQgPSBjbWQ7XG4gIHdoaWxlIChjICYmIGMucGFyZW50KSB7XG4gICAgcGFydHMudW5zaGlmdChjLm5hbWUoKSk7XG4gICAgYyA9IGMucGFyZW50ID8/IHVuZGVmaW5lZDtcbiAgfVxuICByZXR1cm4gcGFydHMuam9pbignICcpO1xufVxuXG4vKiogVGhlIGxvY2FsIHRvb2xzIGEgY29tbWFuZCBkZWNsYXJlcyBpdCBuZWVkcyAoZW1wdHkgd2hlbiBpdCBuZWVkcyBub3RoaW5nKS4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZXF1aXJlZFRvb2xzRm9yKGNtZDogQ29tbWFuZCk6IExvY2FsVG9vbFtdIHtcbiAgcmV0dXJuIENPTU1BTkRfVE9PTF9SRVFVSVJFTUVOVFNbY29tbWFuZFBhdGgoY21kKV0gPz8gW107XG59XG5cbi8qKlxuICogUHJlZmxpZ2h0IHRoZSBsb2NhbCB0b29scyBhbiBhYm91dC10by1ydW4gY29tbWFuZCBkZWNsYXJlcy4gUmV1c2VzIHRoZSB0b29scydcbiAqIGBlbnN1cmVYYCBoZWxwZXJzICh3aGljaCBwcmludCB0aGUgYWN0aW9uYWJsZSBtZXNzYWdlICsgaW5zdGFsbCBoaW50IG9uXG4gKiBmYWlsdXJlKSwgdGhlbiBleGl0cyBub24temVybyBzbyB0aGUgY29tbWFuZCBib2R5IG5ldmVyIHJ1bnMuIE5vLW9wIGZvciBhbnlcbiAqIGNvbW1hbmQgd2l0aG91dCByZXF1aXJlbWVudHMgKEFQSS1vbmx5IGNvbW1hbmRzLCB2ZXJzaW9uL2hlbHAvY29tcGxldGlvbnMpLlxuICovXG5leHBvcnQgZnVuY3Rpb24gcHJlZmxpZ2h0Q29tbWFuZFRvb2xzKGFjdGlvbkNvbW1hbmQ6IENvbW1hbmQpOiB2b2lkIHtcbiAgY29uc3QgcmVxcyA9IHJlcXVpcmVkVG9vbHNGb3IoYWN0aW9uQ29tbWFuZCk7XG4gIGlmIChyZXFzLmxlbmd0aCA9PT0gMCkgcmV0dXJuO1xuICB0cnkge1xuICAgIGlmIChyZXFzLmluY2x1ZGVzKCdjZGsnKSkgZW5zdXJlQ2RrQXZhaWxhYmxlKCk7XG4gICAgaWYgKHJlcXMuaW5jbHVkZXMoJ2J1bmRsZXInKSkgZW5zdXJlQnVuZGxlckF2YWlsYWJsZSgpO1xuICB9IGNhdGNoIHtcbiAgICAvLyBNZXNzYWdlIGFscmVhZHkgcHJpbnRlZCBieSBlbnN1cmVYLiBIYWx0IGJlZm9yZSB0aGUgYWN0aW9uIHJ1bnMgc28gdGhlXG4gICAgLy8gY29tbWFuZCBkb2Vzbid0IHByb2NlZWQgd2l0aCBhIGJyb2tlbiB0b29sY2hhaW4uXG4gICAgcHJvY2Vzcy5leGl0KDEpO1xuICB9XG59XG4iXX0=
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if auth attempts for this identifier are currently rate-limited.
|
|
3
|
+
* @returns null if allowed, or a message string if blocked.
|
|
4
|
+
*/
|
|
5
|
+
export declare function checkAuthRateLimit(identifier?: string, baseUrl?: string): string | null;
|
|
6
|
+
/**
|
|
7
|
+
* Record a successful auth for this identifier — clears its failure counter.
|
|
8
|
+
*/
|
|
9
|
+
export declare function recordAuthSuccess(identifier?: string, baseUrl?: string): void;
|
|
10
|
+
/**
|
|
11
|
+
* Record a failed auth attempt for this identifier. After MAX_FAILURES, locks out
|
|
12
|
+
* that identifier for COOLDOWN_MS.
|
|
13
|
+
*/
|
|
14
|
+
export declare function recordAuthFailure(identifier?: string, baseUrl?: string): void;
|
|
15
|
+
//# sourceMappingURL=rate-limiter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../../src/utils/rate-limiter.ts"],"names":[],"mappings":"AAgHA;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAyBvF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAM7E;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAgB7E"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import crypto from 'crypto';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import os from 'os';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
/**
|
|
8
|
+
* Simple file-based rate limiter for auth operations.
|
|
9
|
+
* Tracks failed attempts and enforces a cooldown period after too many failures.
|
|
10
|
+
* State is persisted in a temp file so it survives across CLI invocations.
|
|
11
|
+
*
|
|
12
|
+
* State is keyed per (baseUrl, identifier) so a failed login for one account does
|
|
13
|
+
* NOT lock out another account on a shared workstation / CI host. Calls without an
|
|
14
|
+
* identifier fall back to a shared `_default` bucket (coarse anti-bruteforce).
|
|
15
|
+
*/
|
|
16
|
+
const STATE_FILE = path.join(os.tmpdir(), '.pipeline-manager-auth-state.json');
|
|
17
|
+
// Directory-as-mutex guarding the read-modify-write. `mkdir` is atomic on every
|
|
18
|
+
// platform, so concurrent CLI invocations serialize on it instead of racing (a
|
|
19
|
+
// lost increment would under-count toward lockout — see withLock).
|
|
20
|
+
const LOCK_DIR = `${STATE_FILE}.lock`;
|
|
21
|
+
const MAX_FAILURES = 5;
|
|
22
|
+
const COOLDOWN_MS = 60_000; // 1 minute
|
|
23
|
+
const PRUNE_MS = 60 * 60_000; // drop buckets idle for >1h to bound file growth
|
|
24
|
+
const LOCK_STALE_MS = 5_000; // reclaim a lock left by a crashed process
|
|
25
|
+
const LOCK_WAIT_MS = 1_000; // give up waiting after this (best-effort: proceed unlocked)
|
|
26
|
+
const LOCK_RETRY_MS = 15; // spin interval while the lock is held
|
|
27
|
+
const EMPTY = { failures: 0, lastFailure: 0, lockedUntil: 0 };
|
|
28
|
+
/** Stable, non-reversible bucket key for a (baseUrl, identifier) pair. */
|
|
29
|
+
function keyFor(identifier, baseUrl) {
|
|
30
|
+
if (!identifier)
|
|
31
|
+
return '_default';
|
|
32
|
+
return crypto.createHash('sha256').update(`${baseUrl ?? ''}\n${identifier}`).digest('hex').slice(0, 16);
|
|
33
|
+
}
|
|
34
|
+
function readMap() {
|
|
35
|
+
try {
|
|
36
|
+
if (fs.existsSync(STATE_FILE)) {
|
|
37
|
+
const parsed = JSON.parse(fs.readFileSync(STATE_FILE, 'utf-8'));
|
|
38
|
+
if (parsed && typeof parsed === 'object')
|
|
39
|
+
return parsed;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch { /* ignore corrupt state */ }
|
|
43
|
+
return {};
|
|
44
|
+
}
|
|
45
|
+
function writeMap(map) {
|
|
46
|
+
const now = Date.now();
|
|
47
|
+
// Prune idle buckets so the file doesn't grow unbounded across many identifiers.
|
|
48
|
+
for (const [k, s] of Object.entries(map)) {
|
|
49
|
+
if (s.lockedUntil <= now && now - s.lastFailure > PRUNE_MS)
|
|
50
|
+
delete map[k];
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
// Write to a per-process temp file then rename: rename is atomic on the same
|
|
54
|
+
// filesystem, so a concurrent reader never observes a torn/half-written file.
|
|
55
|
+
const tmp = `${STATE_FILE}.${process.pid}.tmp`;
|
|
56
|
+
fs.writeFileSync(tmp, JSON.stringify(map), { mode: 0o600 });
|
|
57
|
+
fs.renameSync(tmp, STATE_FILE);
|
|
58
|
+
}
|
|
59
|
+
catch { /* best-effort */ }
|
|
60
|
+
}
|
|
61
|
+
/** Block the current thread for `ms` without a busy-wait (CLI-only, short waits). */
|
|
62
|
+
function sleep(ms) {
|
|
63
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Run `fn` while holding a cross-process advisory lock so the read-modify-write in
|
|
67
|
+
* recordAuthFailure/Success can't lose an update to a concurrent CLI invocation
|
|
68
|
+
* (the TOCTOU that let parallel runs under-count toward lockout). Best-effort: if
|
|
69
|
+
* the lock can't be acquired within LOCK_WAIT_MS we proceed anyway rather than
|
|
70
|
+
* block the user — server-side rate limiting remains the real enforcement.
|
|
71
|
+
*/
|
|
72
|
+
function withLock(fn) {
|
|
73
|
+
const deadline = Date.now() + LOCK_WAIT_MS;
|
|
74
|
+
let held = false;
|
|
75
|
+
while (Date.now() < deadline) {
|
|
76
|
+
try {
|
|
77
|
+
fs.mkdirSync(LOCK_DIR);
|
|
78
|
+
held = true;
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// Held elsewhere — reclaim it if it's stale (owner crashed without cleanup),
|
|
83
|
+
// otherwise wait a beat and retry.
|
|
84
|
+
try {
|
|
85
|
+
if (Date.now() - fs.statSync(LOCK_DIR).mtimeMs > LOCK_STALE_MS) {
|
|
86
|
+
fs.rmdirSync(LOCK_DIR);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch { /* lock vanished between calls — just retry */ }
|
|
91
|
+
sleep(LOCK_RETRY_MS);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
return fn();
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
if (held) {
|
|
99
|
+
try {
|
|
100
|
+
fs.rmdirSync(LOCK_DIR);
|
|
101
|
+
}
|
|
102
|
+
catch { /* already gone */ }
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Check if auth attempts for this identifier are currently rate-limited.
|
|
108
|
+
* @returns null if allowed, or a message string if blocked.
|
|
109
|
+
*/
|
|
110
|
+
export function checkAuthRateLimit(identifier, baseUrl) {
|
|
111
|
+
const key = keyFor(identifier, baseUrl);
|
|
112
|
+
const map = readMap();
|
|
113
|
+
const state = map[key] ?? EMPTY;
|
|
114
|
+
const now = Date.now();
|
|
115
|
+
if (state.lockedUntil > now) {
|
|
116
|
+
const waitSec = Math.ceil((state.lockedUntil - now) / 1000);
|
|
117
|
+
return `Too many failed login attempts. Try again in ${waitSec}s.`;
|
|
118
|
+
}
|
|
119
|
+
// Reset this bucket once the cooldown window has passed. Re-read under the lock
|
|
120
|
+
// so we don't clobber a concurrent recordAuthFailure for another bucket.
|
|
121
|
+
if (state.lastFailure !== 0 && now - state.lastFailure > COOLDOWN_MS) {
|
|
122
|
+
withLock(() => {
|
|
123
|
+
const m = readMap();
|
|
124
|
+
const s = m[key];
|
|
125
|
+
if (s && s.lastFailure !== 0 && Date.now() - s.lastFailure > COOLDOWN_MS) {
|
|
126
|
+
delete m[key];
|
|
127
|
+
writeMap(m);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Record a successful auth for this identifier — clears its failure counter.
|
|
135
|
+
*/
|
|
136
|
+
export function recordAuthSuccess(identifier, baseUrl) {
|
|
137
|
+
withLock(() => {
|
|
138
|
+
const map = readMap();
|
|
139
|
+
delete map[keyFor(identifier, baseUrl)];
|
|
140
|
+
writeMap(map);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Record a failed auth attempt for this identifier. After MAX_FAILURES, locks out
|
|
145
|
+
* that identifier for COOLDOWN_MS.
|
|
146
|
+
*/
|
|
147
|
+
export function recordAuthFailure(identifier, baseUrl) {
|
|
148
|
+
const key = keyFor(identifier, baseUrl);
|
|
149
|
+
// Read-modify-write under the lock: a concurrent CLI invocation must not read the
|
|
150
|
+
// same pre-increment count and overwrite this one (which would under-count).
|
|
151
|
+
withLock(() => {
|
|
152
|
+
const map = readMap();
|
|
153
|
+
const now = Date.now();
|
|
154
|
+
const state = { ...(map[key] ?? EMPTY) };
|
|
155
|
+
state.failures += 1;
|
|
156
|
+
state.lastFailure = now;
|
|
157
|
+
if (state.failures >= MAX_FAILURES) {
|
|
158
|
+
state.lockedUntil = now + COOLDOWN_MS;
|
|
159
|
+
}
|
|
160
|
+
map[key] = state;
|
|
161
|
+
writeMap(map);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmF0ZS1saW1pdGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3V0aWxzL3JhdGUtbGltaXRlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBRXRDLE9BQU8sTUFBTSxNQUFNLFFBQVEsQ0FBQztBQUM1QixPQUFPLEVBQUUsTUFBTSxJQUFJLENBQUM7QUFDcEIsT0FBTyxFQUFFLE1BQU0sSUFBSSxDQUFDO0FBQ3BCLE9BQU8sSUFBSSxNQUFNLE1BQU0sQ0FBQztBQUV4Qjs7Ozs7Ozs7R0FRRztBQUVILE1BQU0sVUFBVSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLE1BQU0sRUFBRSxFQUFFLG1DQUFtQyxDQUFDLENBQUM7QUFDL0UsZ0ZBQWdGO0FBQ2hGLCtFQUErRTtBQUMvRSxtRUFBbUU7QUFDbkUsTUFBTSxRQUFRLEdBQUcsR0FBRyxVQUFVLE9BQU8sQ0FBQztBQUN0QyxNQUFNLFlBQVksR0FBRyxDQUFDLENBQUM7QUFDdkIsTUFBTSxXQUFXLEdBQUcsTUFBTSxDQUFDLENBQUMsV0FBVztBQUN2QyxNQUFNLFFBQVEsR0FBRyxFQUFFLEdBQUcsTUFBTSxDQUFDLENBQUMsaURBQWlEO0FBQy9FLE1BQU0sYUFBYSxHQUFHLEtBQUssQ0FBQyxDQUFDLDJDQUEyQztBQUN4RSxNQUFNLFlBQVksR0FBRyxLQUFLLENBQUMsQ0FBQyw2REFBNkQ7QUFDekYsTUFBTSxhQUFhLEdBQUcsRUFBRSxDQUFDLENBQUMsdUNBQXVDO0FBVWpFLE1BQU0sS0FBSyxHQUFjLEVBQUUsUUFBUSxFQUFFLENBQUMsRUFBRSxXQUFXLEVBQUUsQ0FBQyxFQUFFLFdBQVcsRUFBRSxDQUFDLEVBQUUsQ0FBQztBQUV6RSwwRUFBMEU7QUFDMUUsU0FBUyxNQUFNLENBQUMsVUFBbUIsRUFBRSxPQUFnQjtJQUNuRCxJQUFJLENBQUMsVUFBVTtRQUFFLE9BQU8sVUFBVSxDQUFDO0lBQ25DLE9BQU8s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R0FBRyxDQUFDO1FBQ3hCLElBQUksS0FBSyxDQUFDLFFBQVEsSUFBSSxZQUFZLEVBQUUsQ0FBQztZQUNuQyxLQUFLLENBQUMsV0FBVyxHQUFHLEdBQUcsR0FBRyxXQUFXLENBQUM7UUFDeEMsQ0FBQztRQUNELEdBQUcsQ0FBQyxHQUFHLENBQUMsR0FBRyxLQUFLLENBQUM7UUFDakIsUUFBUSxDQUFDLEdBQUcsQ0FBQyxDQUFDO0lBQ2hCLENBQUMsQ0FBQyxDQUFDO0FBQ0wsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG5pbXBvcnQgY3J5cHRvIGZyb20gJ2NyeXB0byc7XG5pbXBvcnQgZnMgZnJvbSAnZnMnO1xuaW1wb3J0IG9zIGZyb20gJ29zJztcbmltcG9ydCBwYXRoIGZyb20gJ3BhdGgnO1xuXG4vKipcbiAqIFNpbXBsZSBmaWxlLWJhc2VkIHJhdGUgbGltaXRlciBmb3IgYXV0aCBvcGVyYXRpb25zLlxuICogVHJhY2tzIGZhaWxlZCBhdHRlbXB0cyBhbmQgZW5mb3JjZXMgYSBjb29sZG93biBwZXJpb2QgYWZ0ZXIgdG9vIG1hbnkgZmFpbHVyZXMuXG4gKiBTdGF0ZSBpcyBwZXJzaXN0ZWQgaW4gYSB0ZW1wIGZpbGUgc28gaXQgc3Vydml2ZXMgYWNyb3NzIENMSSBpbnZvY2F0aW9ucy5cbiAqXG4gKiBTdGF0ZSBpcyBrZXllZCBwZXIgKGJhc2VVcmwsIGlkZW50aWZpZXIpIHNvIGEgZmFpbGVkIGxvZ2luIGZvciBvbmUgYWNjb3VudCBkb2VzXG4gKiBOT1QgbG9jayBvdXQgYW5vdGhlciBhY2NvdW50IG9uIGEgc2hhcmVkIHdvcmtzdGF0aW9uIC8gQ0kgaG9zdC4gQ2FsbHMgd2l0aG91dCBhblxuICogaWRlbnRpZmllciBmYWxsIGJhY2sgdG8gYSBzaGFyZWQgYF9kZWZhdWx0YCBidWNrZXQgKGNvYXJzZSBhbnRpLWJydXRlZm9yY2UpLlxuICovXG5cbmNvbnN0IFNUQVRFX0ZJTEUgPSBwYXRoLmpvaW4ob3MudG1wZGlyKCksICcucGlwZWxpbmUtbWFuYWdlci1hdXRoLXN0YXRlLmpzb24nKTtcbi8vIERpcmVjdG9yeS1hcy1tdXRleCBndWFyZGluZyB0aGUgcmVhZC1tb2RpZnktd3JpdGUuIGBta2RpcmAgaXMgYXRvbWljIG9uIGV2ZXJ5XG4vLyBwbGF0Zm9ybSwgc28gY29uY3VycmVudCBDTEkgaW52b2NhdGlvbnMgc2VyaWFsaXplIG9uIGl0IGluc3RlYWQgb2YgcmFjaW5nIChhXG4vLyBsb3N0IGluY3JlbWVudCB3b3VsZCB1bmRlci1jb3VudCB0b3dhcmQgbG9ja291dCDigJQgc2VlIHdpdGhMb2NrKS5cbmNvbnN0IExPQ0tfRElSID0gYCR7U1RBVEVfRklMRX0ubG9ja2A7XG5jb25zdCBNQVhfRkFJTFVSRVMgPSA1O1xuY29uc3QgQ09PTERPV05fTVMgPSA2MF8wMDA7IC8vIDEgbWludXRlXG5jb25zdCBQUlVORV9NUyA9IDYwICogNjBfMDAwOyAvLyBkcm9wIGJ1Y2tldHMgaWRsZSBmb3IgPjFoIHRvIGJvdW5kIGZpbGUgZ3Jvd3RoXG5jb25zdCBMT0NLX1NUQUxFX01TID0gNV8wMDA7IC8vIHJlY2xhaW0gYSBsb2NrIGxlZnQgYnkgYSBjcmFzaGVkIHByb2Nlc3NcbmNvbnN0IExPQ0tfV0FJVF9NUyA9IDFfMDAwOyAvLyBnaXZlIHVwIHdhaXRpbmcgYWZ0ZXIgdGhpcyAoYmVzdC1lZmZvcnQ6IHByb2NlZWQgdW5sb2NrZWQpXG5jb25zdCBMT0NLX1JFVFJZX01TID0gMTU7IC8vIHNwaW4gaW50ZXJ2YWwgd2hpbGUgdGhlIGxvY2sgaXMgaGVsZFxuXG5pbnRlcmZhY2UgQXV0aFN0YXRlIHtcbiAgZmFpbHVyZXM6IG51bWJlcjtcbiAgbGFzdEZhaWx1cmU6IG51bWJlcjtcbiAgbG9ja2VkVW50aWw6IG51bWJlcjtcbn1cblxudHlwZSBTdGF0ZU1hcCA9IFJlY29yZDxzdHJpbmcsIEF1dGhTdGF0ZT47XG5cbmNvbnN0IEVNUFRZOiBBdXRoU3RhdGUgPSB7IGZhaWx1cmVzOiAwLCBsYXN0RmFpbHVyZTogMCwgbG9ja2VkVW50aWw6IDAgfTtcblxuLyoqIFN0YWJsZSwgbm9uLXJldmVyc2libGUgYnVja2V0IGtleSBmb3IgYSAoYmFzZVVybCwgaWRlbnRpZmllcikgcGFpci4gKi9cbmZ1bmN0aW9uIGtleUZvcihpZGVudGlmaWVyPzogc3RyaW5nLCBiYXNlVXJsPzogc3RyaW5nKTogc3RyaW5nIHtcbiAgaWYgKCFpZGVudGlmaWVyKSByZXR1cm4gJ19kZWZhdWx0JztcbiAgcmV0dXJuIGNyeXB0by5jcmVhdGVIYXNoKCdzaGEyNTYnKS51cGRhdGUoYCR7YmFzZVVybCA/PyAnJ31cXG4ke2lkZW50aWZpZXJ9YCkuZGlnZXN0KCdoZXgnKS5zbGljZSgwLCAxNik7XG59XG5cbmZ1bmN0aW9uIHJlYWRNYXAoKTogU3RhdGVNYXAge1xuICB0cnkge1xuICAgIGlmIChmcy5leGlzdHNTeW5jKFNUQVRFX0ZJTEUpKSB7XG4gICAgICBjb25zdCBwYXJzZWQgPSBKU09OLnBhcnNlKGZzLnJlYWRGaWxlU3luYyhTVEFURV9GSUxFLCAndXRmLTgnKSkgYXMgdW5rbm93bjtcbiAgICAgIGlmIChwYXJzZWQgJiYgdHlwZW9mIHBhcnNlZCA9PT0gJ29iamVjdCcpIHJldHVybiBwYXJzZWQgYXMgU3RhdGVNYXA7XG4gICAgfVxuICB9IGNhdGNoIHsgLyogaWdub3JlIGNvcnJ1cHQgc3RhdGUgKi8gfVxuICByZXR1cm4ge307XG59XG5cbmZ1bmN0aW9uIHdyaXRlTWFwKG1hcDogU3RhdGVNYXApOiB2b2lkIHtcbiAgY29uc3Qgbm93ID0gRGF0ZS5ub3coKTtcbiAgLy8gUHJ1bmUgaWRsZSBidWNrZXRzIHNvIHRoZSBmaWxlIGRvZXNuJ3QgZ3JvdyB1bmJvdW5kZWQgYWNyb3NzIG1hbnkgaWRlbnRpZmllcnMuXG4gIGZvciAoY29uc3QgW2ssIHNdIG9mIE9iamVjdC5lbnRyaWVzKG1hcCkpIHtcbiAgICBpZiAocy5sb2NrZWRVbnRpbCA8PSBub3cgJiYgbm93IC0gcy5sYXN0RmFpbHVyZSA+IFBSVU5FX01TKSBkZWxldGUgbWFwW2tdO1xuICB9XG4gIHRyeSB7XG4gICAgLy8gV3JpdGUgdG8gYSBwZXItcHJvY2VzcyB0ZW1wIGZpbGUgdGhlbiByZW5hbWU6IHJlbmFtZSBpcyBhdG9taWMgb24gdGhlIHNhbWVcbiAgICAvLyBmaWxlc3lzdGVtLCBzbyBhIGNvbmN1cnJlbnQgcmVhZGVyIG5ldmVyIG9ic2VydmVzIGEgdG9ybi9oYWxmLXdyaXR0ZW4gZmlsZS5cbiAgICBjb25zdCB0bXAgPSBgJHtTVEFURV9GSUxFfS4ke3Byb2Nlc3MucGlkfS50bXBgO1xuICAgIGZzLndyaXRlRmlsZVN5bmModG1wLCBKU09OLnN0cmluZ2lmeShtYXApLCB7IG1vZGU6IDBvNjAwIH0pO1xuICAgIGZzLnJlbmFtZVN5bmModG1wLCBTVEFURV9GSUxFKTtcbiAgfSBjYXRjaCB7IC8qIGJlc3QtZWZmb3J0ICovIH1cbn1cblxuLyoqIEJsb2NrIHRoZSBjdXJyZW50IHRocmVhZCBmb3IgYG1zYCB3aXRob3V0IGEgYnVzeS13YWl0IChDTEktb25seSwgc2hvcnQgd2FpdHMpLiAqL1xuZnVuY3Rpb24gc2xlZXAobXM6IG51bWJlcik6IHZvaWQge1xuICBBdG9taWNzLndhaXQobmV3IEludDMyQXJyYXkobmV3IFNoYXJlZEFycmF5QnVmZmVyKDQpKSwgMCwgMCwgbXMpO1xufVxuXG4vKipcbiAqIFJ1biBgZm5gIHdoaWxlIGhvbGRpbmcgYSBjcm9zcy1wcm9jZXNzIGFkdmlzb3J5IGxvY2sgc28gdGhlIHJlYWQtbW9kaWZ5LXdyaXRlIGluXG4gKiByZWNvcmRBdXRoRmFpbHVyZS9TdWNjZXNzIGNhbid0IGxvc2UgYW4gdXBkYXRlIHRvIGEgY29uY3VycmVudCBDTEkgaW52b2NhdGlvblxuICogKHRoZSBUT0NUT1UgdGhhdCBsZXQgcGFyYWxsZWwgcnVucyB1bmRlci1jb3VudCB0b3dhcmQgbG9ja291dCkuIEJlc3QtZWZmb3J0OiBpZlxuICogdGhlIGxvY2sgY2FuJ3QgYmUgYWNxdWlyZWQgd2l0aGluIExPQ0tfV0FJVF9NUyB3ZSBwcm9jZWVkIGFueXdheSByYXRoZXIgdGhhblxuICogYmxvY2sgdGhlIHVzZXIg4oCUIHNlcnZlci1zaWRlIHJhdGUgbGltaXRpbmcgcmVtYWlucyB0aGUgcmVhbCBlbmZvcmNlbWVudC5cbiAqL1xuZnVuY3Rpb24gd2l0aExvY2s8VD4oZm46ICgpID0+IFQpOiBUIHtcbiAgY29uc3QgZGVhZGxpbmUgPSBEYXRlLm5vdygpICsgTE9DS19XQUlUX01TO1xuICBsZXQgaGVsZCA9IGZhbHNlO1xuICB3aGlsZSAoRGF0ZS5ub3coKSA8IGRlYWRsaW5lKSB7XG4gICAgdHJ5IHtcbiAgICAgIGZzLm1rZGlyU3luYyhMT0NLX0RJUik7XG4gICAgICBoZWxkID0gdHJ1ZTtcbiAgICAgIGJyZWFrO1xuICAgIH0gY2F0Y2gge1xuICAgICAgLy8gSGVsZCBlbHNld2hlcmUg4oCUIHJlY2xhaW0gaXQgaWYgaXQncyBzdGFsZSAob3duZXIgY3Jhc2hlZCB3aXRob3V0IGNsZWFudXApLFxuICAgICAgLy8gb3RoZXJ3aXNlIHdhaXQgYSBiZWF0IGFuZCByZXRyeS5cbiAgICAgIHRyeSB7XG4gICAgICAgIGlmIChEYXRlLm5vdygpIC0gZnMuc3RhdFN5bmMoTE9DS19ESVIpLm10aW1lTXMgPiBMT0NLX1NUQUxFX01TKSB7XG4gICAgICAgICAgZnMucm1kaXJTeW5jKExPQ0tfRElSKTtcbiAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgfVxuICAgICAgfSBjYXRjaCB7IC8qIGxvY2sgdmFuaXNoZWQgYmV0d2VlbiBjYWxscyDigJQganVzdCByZXRyeSAqLyB9XG4gICAgICBzbGVlcChMT0NLX1JFVFJZX01TKTtcbiAgICB9XG4gIH1cbiAgdHJ5IHtcbiAgICByZXR1cm4gZm4oKTtcbiAgfSBmaW5hbGx5IHtcbiAgICBpZiAoaGVsZCkge1xuICAgICAgdHJ5IHsgZnMucm1kaXJTeW5jKExPQ0tfRElSKTsgfSBjYXRjaCB7IC8qIGFscmVhZHkgZ29uZSAqLyB9XG4gICAgfVxuICB9XG59XG5cbi8qKlxuICogQ2hlY2sgaWYgYXV0aCBhdHRlbXB0cyBmb3IgdGhpcyBpZGVudGlmaWVyIGFyZSBjdXJyZW50bHkgcmF0ZS1saW1pdGVkLlxuICogQHJldHVybnMgbnVsbCBpZiBhbGxvd2VkLCBvciBhIG1lc3NhZ2Ugc3RyaW5nIGlmIGJsb2NrZWQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBjaGVja0F1dGhSYXRlTGltaXQoaWRlbnRpZmllcj86IHN0cmluZywgYmFzZVVybD86IHN0cmluZyk6IHN0cmluZyB8IG51bGwge1xuICBjb25zdCBrZXkgPSBrZXlGb3IoaWRlbnRpZmllciwgYmFzZVVybCk7XG4gIGNvbnN0IG1hcCA9IHJlYWRNYXAoKTtcbiAgY29uc3Qgc3RhdGUgPSBtYXBba2V5XSA/PyBFTVBUWTtcbiAgY29uc3Qgbm93ID0gRGF0ZS5ub3coKTtcblxuICBpZiAoc3RhdGUubG9ja2VkVW50aWwgPiBub3cpIHtcbiAgICBjb25zdCB3YWl0U2VjID0gTWF0aC5jZWlsKChzdGF0ZS5sb2NrZWRVbnRpbCAtIG5vdykgLyAxMDAwKTtcbiAgICByZXR1cm4gYFRvbyBtYW55IGZhaWxlZCBsb2dpbiBhdHRlbXB0cy4gVHJ5IGFnYWluIGluICR7d2FpdFNlY31zLmA7XG4gIH1cblxuICAvLyBSZXNldCB0aGlzIGJ1Y2tldCBvbmNlIHRoZSBjb29sZG93biB3aW5kb3cgaGFzIHBhc3NlZC4gUmUtcmVhZCB1bmRlciB0aGUgbG9ja1xuICAvLyBzbyB3ZSBkb24ndCBjbG9iYmVyIGEgY29uY3VycmVudCByZWNvcmRBdXRoRmFpbHVyZSBmb3IgYW5vdGhlciBidWNrZXQuXG4gIGlmIChzdGF0ZS5sYXN0RmFpbHVyZSAhPT0gMCAmJiBub3cgLSBzdGF0ZS5sYXN0RmFpbHVyZSA+IENPT0xET1dOX01TKSB7XG4gICAgd2l0aExvY2soKCkgPT4ge1xuICAgICAgY29uc3QgbSA9IHJlYWRNYXAoKTtcbiAgICAgIGNvbnN0IHMgPSBtW2tleV07XG4gICAgICBpZiAocyAmJiBzLmxhc3RGYWlsdXJlICE9PSAwICYmIERhdGUubm93KCkgLSBzLmxhc3RGYWlsdXJlID4gQ09PTERPV05fTVMpIHtcbiAgICAgICAgZGVsZXRlIG1ba2V5XTtcbiAgICAgICAgd3JpdGVNYXAobSk7XG4gICAgICB9XG4gICAgfSk7XG4gIH1cblxuICByZXR1cm4gbnVsbDtcbn1cblxuLyoqXG4gKiBSZWNvcmQgYSBzdWNjZXNzZnVsIGF1dGggZm9yIHRoaXMgaWRlbnRpZmllciDigJQgY2xlYXJzIGl0cyBmYWlsdXJlIGNvdW50ZXIuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZWNvcmRBdXRoU3VjY2VzcyhpZGVudGlmaWVyPzogc3RyaW5nLCBiYXNlVXJsPzogc3RyaW5nKTogdm9pZCB7XG4gIHdpdGhMb2NrKCgpID0+IHtcbiAgICBjb25zdCBtYXAgPSByZWFkTWFwKCk7XG4gICAgZGVsZXRlIG1hcFtrZXlGb3IoaWRlbnRpZmllciwgYmFzZVVybCldO1xuICAgIHdyaXRlTWFwKG1hcCk7XG4gIH0pO1xufVxuXG4vKipcbiAqIFJlY29yZCBhIGZhaWxlZCBhdXRoIGF0dGVtcHQgZm9yIHRoaXMgaWRlbnRpZmllci4gQWZ0ZXIgTUFYX0ZBSUxVUkVTLCBsb2NrcyBvdXRcbiAqIHRoYXQgaWRlbnRpZmllciBmb3IgQ09PTERPV05fTVMuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZWNvcmRBdXRoRmFpbHVyZShpZGVudGlmaWVyPzogc3RyaW5nLCBiYXNlVXJsPzogc3RyaW5nKTogdm9pZCB7XG4gIGNvbnN0IGtleSA9IGtleUZvcihpZGVudGlmaWVyLCBiYXNlVXJsKTtcbiAgLy8gUmVhZC1tb2RpZnktd3JpdGUgdW5kZXIgdGhlIGxvY2s6IGEgY29uY3VycmVudCBDTEkgaW52b2NhdGlvbiBtdXN0IG5vdCByZWFkIHRoZVxuICAvLyBzYW1lIHByZS1pbmNyZW1lbnQgY291bnQgYW5kIG92ZXJ3cml0ZSB0aGlzIG9uZSAod2hpY2ggd291bGQgdW5kZXItY291bnQpLlxuICB3aXRoTG9jaygoKSA9PiB7XG4gICAgY29uc3QgbWFwID0gcmVhZE1hcCgpO1xuICAgIGNvbnN0IG5vdyA9IERhdGUubm93KCk7XG4gICAgY29uc3Qgc3RhdGUgPSB7IC4uLihtYXBba2V5XSA/PyBFTVBUWSkgfTtcbiAgICBzdGF0ZS5mYWlsdXJlcyArPSAxO1xuICAgIHN0YXRlLmxhc3RGYWlsdXJlID0gbm93O1xuICAgIGlmIChzdGF0ZS5mYWlsdXJlcyA+PSBNQVhfRkFJTFVSRVMpIHtcbiAgICAgIHN0YXRlLmxvY2tlZFVudGlsID0gbm93ICsgQ09PTERPV05fTVM7XG4gICAgfVxuICAgIG1hcFtrZXldID0gc3RhdGU7XG4gICAgd3JpdGVNYXAobWFwKTtcbiAgfSk7XG59XG4iXX0=
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for registering a deployed pipeline ARN with the platform.
|
|
3
|
+
*
|
|
4
|
+
* The registry table maps deployed CodePipeline ARNs back to pipeline records
|
|
5
|
+
* and orgs. It's how the dashboard's "Deployed pipelines" panel and the event
|
|
6
|
+
* reporting Lambda resolve incoming events to a pipeline definition.
|
|
7
|
+
*
|
|
8
|
+
* Two failure modes are handled here:
|
|
9
|
+
*
|
|
10
|
+
* 1. Platform unreachable at deploy time. The CDK stack lands in AWS but the
|
|
11
|
+
* registry POST fails. Without retry the deploy command would have to
|
|
12
|
+
* re-run cdk-deploy (slow, sometimes blocked by stack state) just to
|
|
13
|
+
* record the ARN. Instead, we write a pending intent to a local file;
|
|
14
|
+
* `pipeline-manager pipeline register` drains them.
|
|
15
|
+
*
|
|
16
|
+
* 2. User explicitly invokes `pipeline-manager pipeline register --id <pipelineId>`
|
|
17
|
+
* after a successful deploy that didn't register. Same path: rebuilds
|
|
18
|
+
* the ARN from STS, POSTs to the platform.
|
|
19
|
+
*
|
|
20
|
+
* Pending intents store ONLY the registration payload — never tokens or URLs.
|
|
21
|
+
* Auth is supplied by whichever command drains the intent (deploy or register),
|
|
22
|
+
* so a stale intent file is never an authentication risk.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* Platform-sourced registry pull target for the CDK synth, derived from the
|
|
26
|
+
* platform base URL the CLI deploys against (the same host that serves the
|
|
27
|
+
* registry `/v2/` data path via nginx). Reuses pipeline-core's
|
|
28
|
+
* `parsePlatformBaseUrl` so host/port parsing stays identical on both sides.
|
|
29
|
+
* Returns `undefined` for an unparseable/absent URL. (Only the pull host/port
|
|
30
|
+
* are returned — `registry.http` describes the in-cluster push hop, which the
|
|
31
|
+
* synth never reads, so deriving it from the public URL would be misleading.)
|
|
32
|
+
*/
|
|
33
|
+
export declare function registryOverrideFromBaseUrl(baseUrl: string | undefined): {
|
|
34
|
+
pullHost: string;
|
|
35
|
+
pullPort: number;
|
|
36
|
+
} | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* Bake the platform-derived registry pull target into the synth props
|
|
39
|
+
* (`BuilderProps.registry`) so the CodeBuild image URIs use a host AWS CodeBuild
|
|
40
|
+
* can resolve — not the in-cluster `registry:5000` default. Shared by the
|
|
41
|
+
* `deploy` and `synth` commands.
|
|
42
|
+
*
|
|
43
|
+
* An explicit `IMAGE_REGISTRY_PULL_HOST` in the synth env takes precedence, so
|
|
44
|
+
* this is a no-op when it is set (the synth's `loadRegistryConfig` already
|
|
45
|
+
* honors it). Also a no-op for an unparseable/absent base URL.
|
|
46
|
+
*/
|
|
47
|
+
export declare function bakePlatformRegistry(props: Record<string, unknown>, baseUrl: string | undefined): void;
|
|
48
|
+
/** Shape of the body POSTed to /api/pipelines/registry. */
|
|
49
|
+
export interface RegistryPayload {
|
|
50
|
+
pipelineId: string;
|
|
51
|
+
orgId: string;
|
|
52
|
+
pipelineName: string;
|
|
53
|
+
region: string;
|
|
54
|
+
project: string;
|
|
55
|
+
organization: string;
|
|
56
|
+
stackName: string;
|
|
57
|
+
}
|
|
58
|
+
/** Pipeline fields needed to construct a RegistryPayload. */
|
|
59
|
+
export interface PipelineForRegistry {
|
|
60
|
+
id: string;
|
|
61
|
+
orgId: string;
|
|
62
|
+
pipelineName?: string;
|
|
63
|
+
project: string;
|
|
64
|
+
organization: string;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Build a RegistryPayload from the platform's pipeline metadata. No AWS calls
|
|
68
|
+
* and no ARN/account: the events Lambda attributes events via the pipeline's
|
|
69
|
+
* `pb.pipeline-id` tag (= pipelineId), so the registry only needs the stable
|
|
70
|
+
* pipelineId + display metadata.
|
|
71
|
+
*/
|
|
72
|
+
export declare function buildRegistryPayload(pipeline: PipelineForRegistry, regionOverride?: string): Promise<RegistryPayload>;
|
|
73
|
+
/**
|
|
74
|
+
* Persist a registration intent for later retry.
|
|
75
|
+
*
|
|
76
|
+
* Intent files are keyed by pipelineId so re-running deploy on the same
|
|
77
|
+
* pipeline overwrites rather than accumulates. We deliberately store the
|
|
78
|
+
* payload plain (not the full pipeline doc, not auth) so a stale file is
|
|
79
|
+
* safe to leave around indefinitely.
|
|
80
|
+
*/
|
|
81
|
+
export declare function writePendingIntent(payload: RegistryPayload): Promise<string>;
|
|
82
|
+
/** Remove a pending intent after a successful drain. */
|
|
83
|
+
export declare function clearPendingIntent(pipelineId: string): Promise<void>;
|
|
84
|
+
/** Read all pending intents currently on disk. Empty array if dir doesn't exist. */
|
|
85
|
+
export declare function readPendingIntents(): Promise<RegistryPayload[]>;
|
|
86
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/utils/registry.ts"],"names":[],"mappings":"AAWA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,MAAM,GAAG,SAAS,GAC1B;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAGpD;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAMtG;AAED,2DAA2D;AAC3D,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,6DAA6D;AAC7D,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;CACtB;AAKD;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,mBAAmB,EAC7B,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,eAAe,CAAC,CAgB1B;AAED;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAKlF;AAED,wDAAwD;AACxD,wBAAsB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQ1E;AAED,oFAAoF;AACpF,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC,CAqBrE"}
|