@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.
Files changed (222) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +145 -0
  3. package/cdk.json +91 -0
  4. package/config.yml +94 -0
  5. package/dist/agent/ai.d.ts +48 -0
  6. package/dist/agent/ai.d.ts.map +1 -0
  7. package/dist/agent/ai.js +149 -0
  8. package/dist/agent/bootstrap.d.ts +39 -0
  9. package/dist/agent/bootstrap.d.ts.map +1 -0
  10. package/dist/agent/bootstrap.js +66 -0
  11. package/dist/agent/env-file.d.ts +17 -0
  12. package/dist/agent/env-file.d.ts.map +1 -0
  13. package/dist/agent/env-file.js +47 -0
  14. package/dist/agent/executor.d.ts +30 -0
  15. package/dist/agent/executor.d.ts.map +1 -0
  16. package/dist/agent/executor.js +60 -0
  17. package/dist/agent/health.d.ts +43 -0
  18. package/dist/agent/health.d.ts.map +1 -0
  19. package/dist/agent/health.js +109 -0
  20. package/dist/agent/ports.d.ts +26 -0
  21. package/dist/agent/ports.d.ts.map +1 -0
  22. package/dist/agent/ports.js +132 -0
  23. package/dist/agent/post-steps.d.ts +61 -0
  24. package/dist/agent/post-steps.d.ts.map +1 -0
  25. package/dist/agent/post-steps.js +131 -0
  26. package/dist/agent/prereqs.d.ts +26 -0
  27. package/dist/agent/prereqs.d.ts.map +1 -0
  28. package/dist/agent/prereqs.js +134 -0
  29. package/dist/agent/targets.d.ts +128 -0
  30. package/dist/agent/targets.d.ts.map +1 -0
  31. package/dist/agent/targets.js +265 -0
  32. package/dist/agent/tools.d.ts +13 -0
  33. package/dist/agent/tools.d.ts.map +1 -0
  34. package/dist/agent/tools.js +75 -0
  35. package/dist/agent/troubleshoot.d.ts +30 -0
  36. package/dist/agent/troubleshoot.d.ts.map +1 -0
  37. package/dist/agent/troubleshoot.js +87 -0
  38. package/dist/boilerplate.d.ts +3 -0
  39. package/dist/boilerplate.d.ts.map +1 -0
  40. package/dist/boilerplate.js +54 -0
  41. package/dist/cdk.json +91 -0
  42. package/dist/cli.d.ts +62 -0
  43. package/dist/cli.d.ts.map +1 -0
  44. package/dist/cli.js +463 -0
  45. package/dist/commands/audit-stacks.d.ts +24 -0
  46. package/dist/commands/audit-stacks.d.ts.map +1 -0
  47. package/dist/commands/audit-stacks.js +182 -0
  48. package/dist/commands/audit-tokens.d.ts +21 -0
  49. package/dist/commands/audit-tokens.d.ts.map +1 -0
  50. package/dist/commands/audit-tokens.js +153 -0
  51. package/dist/commands/bootstrap.d.ts +11 -0
  52. package/dist/commands/bootstrap.d.ts.map +1 -0
  53. package/dist/commands/bootstrap.js +159 -0
  54. package/dist/commands/create-pat.d.ts +27 -0
  55. package/dist/commands/create-pat.d.ts.map +1 -0
  56. package/dist/commands/create-pat.js +194 -0
  57. package/dist/commands/create-pipeline.d.ts +12 -0
  58. package/dist/commands/create-pipeline.d.ts.map +1 -0
  59. package/dist/commands/create-pipeline.js +315 -0
  60. package/dist/commands/deploy.d.ts +15 -0
  61. package/dist/commands/deploy.d.ts.map +1 -0
  62. package/dist/commands/deploy.js +157 -0
  63. package/dist/commands/entity-get.d.ts +51 -0
  64. package/dist/commands/entity-get.d.ts.map +1 -0
  65. package/dist/commands/entity-get.js +69 -0
  66. package/dist/commands/entity-list.d.ts +39 -0
  67. package/dist/commands/entity-list.d.ts.map +1 -0
  68. package/dist/commands/entity-list.js +88 -0
  69. package/dist/commands/get-pipeline.d.ts +13 -0
  70. package/dist/commands/get-pipeline.d.ts.map +1 -0
  71. package/dist/commands/get-pipeline.js +49 -0
  72. package/dist/commands/get-plugin.d.ts +13 -0
  73. package/dist/commands/get-plugin.d.ts.map +1 -0
  74. package/dist/commands/get-plugin.js +51 -0
  75. package/dist/commands/instantiate-template.d.ts +20 -0
  76. package/dist/commands/instantiate-template.d.ts.map +1 -0
  77. package/dist/commands/instantiate-template.js +274 -0
  78. package/dist/commands/list-pipelines.d.ts +20 -0
  79. package/dist/commands/list-pipelines.d.ts.map +1 -0
  80. package/dist/commands/list-pipelines.js +99 -0
  81. package/dist/commands/list-plugins.d.ts +20 -0
  82. package/dist/commands/list-plugins.d.ts.map +1 -0
  83. package/dist/commands/list-plugins.js +92 -0
  84. package/dist/commands/login.d.ts +21 -0
  85. package/dist/commands/login.d.ts.map +1 -0
  86. package/dist/commands/login.js +174 -0
  87. package/dist/commands/new-plugin.d.ts +12 -0
  88. package/dist/commands/new-plugin.d.ts.map +1 -0
  89. package/dist/commands/new-plugin.js +129 -0
  90. package/dist/commands/org-export.d.ts +25 -0
  91. package/dist/commands/org-export.d.ts.map +1 -0
  92. package/dist/commands/org-export.js +100 -0
  93. package/dist/commands/provision.d.ts +122 -0
  94. package/dist/commands/provision.d.ts.map +1 -0
  95. package/dist/commands/provision.js +867 -0
  96. package/dist/commands/redrive-events.d.ts +24 -0
  97. package/dist/commands/redrive-events.d.ts.map +1 -0
  98. package/dist/commands/redrive-events.js +127 -0
  99. package/dist/commands/register.d.ts +38 -0
  100. package/dist/commands/register.d.ts.map +1 -0
  101. package/dist/commands/register.js +160 -0
  102. package/dist/commands/setup-events.d.ts +15 -0
  103. package/dist/commands/setup-events.d.ts.map +1 -0
  104. package/dist/commands/setup-events.js +168 -0
  105. package/dist/commands/status.d.ts +11 -0
  106. package/dist/commands/status.d.ts.map +1 -0
  107. package/dist/commands/status.js +81 -0
  108. package/dist/commands/store-token.d.ts +26 -0
  109. package/dist/commands/store-token.d.ts.map +1 -0
  110. package/dist/commands/store-token.js +305 -0
  111. package/dist/commands/synth.d.ts +21 -0
  112. package/dist/commands/synth.d.ts.map +1 -0
  113. package/dist/commands/synth.js +158 -0
  114. package/dist/commands/upload-plugin.d.ts +21 -0
  115. package/dist/commands/upload-plugin.d.ts.map +1 -0
  116. package/dist/commands/upload-plugin.js +275 -0
  117. package/dist/commands/validate-plugin.d.ts +12 -0
  118. package/dist/commands/validate-plugin.d.ts.map +1 -0
  119. package/dist/commands/validate-plugin.js +175 -0
  120. package/dist/commands/validate-templates.d.ts +11 -0
  121. package/dist/commands/validate-templates.d.ts.map +1 -0
  122. package/dist/commands/validate-templates.js +116 -0
  123. package/dist/commands/version.d.ts +12 -0
  124. package/dist/commands/version.d.ts.map +1 -0
  125. package/dist/commands/version.js +216 -0
  126. package/dist/config/cli.constants.d.ts +116 -0
  127. package/dist/config/cli.constants.d.ts.map +1 -0
  128. package/dist/config/cli.constants.js +194 -0
  129. package/dist/config.yml +94 -0
  130. package/dist/lambda/token-renew-handler.d.ts +2 -0
  131. package/dist/lambda/token-renew-handler.d.ts.map +1 -0
  132. package/dist/lambda/token-renew-handler.js +125 -0
  133. package/dist/templates/events-stack.json +174 -0
  134. package/dist/templates/token-renew-stack.json +122 -0
  135. package/dist/types/config.d.ts +53 -0
  136. package/dist/types/config.d.ts.map +1 -0
  137. package/dist/types/config.js +4 -0
  138. package/dist/types/error.d.ts +61 -0
  139. package/dist/types/error.d.ts.map +1 -0
  140. package/dist/types/error.js +35 -0
  141. package/dist/types/index.d.ts +9 -0
  142. package/dist/types/index.d.ts.map +1 -0
  143. package/dist/types/index.js +11 -0
  144. package/dist/types/pipeline-template.d.ts +48 -0
  145. package/dist/types/pipeline-template.d.ts.map +1 -0
  146. package/dist/types/pipeline-template.js +4 -0
  147. package/dist/types/pipeline.d.ts +121 -0
  148. package/dist/types/pipeline.d.ts.map +1 -0
  149. package/dist/types/pipeline.js +4 -0
  150. package/dist/types/plugin.d.ts +135 -0
  151. package/dist/types/plugin.d.ts.map +1 -0
  152. package/dist/types/plugin.js +4 -0
  153. package/dist/utils/api-client.d.ts +26 -0
  154. package/dist/utils/api-client.d.ts.map +1 -0
  155. package/dist/utils/api-client.js +151 -0
  156. package/dist/utils/audit-log.d.ts +8 -0
  157. package/dist/utils/audit-log.d.ts.map +1 -0
  158. package/dist/utils/audit-log.js +57 -0
  159. package/dist/utils/auth-guard.d.ts +20 -0
  160. package/dist/utils/auth-guard.d.ts.map +1 -0
  161. package/dist/utils/auth-guard.js +22 -0
  162. package/dist/utils/auth-utils.d.ts +68 -0
  163. package/dist/utils/auth-utils.d.ts.map +1 -0
  164. package/dist/utils/auth-utils.js +77 -0
  165. package/dist/utils/aws-env.d.ts +21 -0
  166. package/dist/utils/aws-env.d.ts.map +1 -0
  167. package/dist/utils/aws-env.js +34 -0
  168. package/dist/utils/aws-secrets.d.ts +48 -0
  169. package/dist/utils/aws-secrets.d.ts.map +1 -0
  170. package/dist/utils/aws-secrets.js +115 -0
  171. package/dist/utils/banner.d.ts +19 -0
  172. package/dist/utils/banner.d.ts.map +1 -0
  173. package/dist/utils/banner.js +52 -0
  174. package/dist/utils/cdk-utils.d.ts +74 -0
  175. package/dist/utils/cdk-utils.d.ts.map +1 -0
  176. package/dist/utils/cdk-utils.js +152 -0
  177. package/dist/utils/command-utils.d.ts +75 -0
  178. package/dist/utils/command-utils.d.ts.map +1 -0
  179. package/dist/utils/command-utils.js +155 -0
  180. package/dist/utils/config-loader.d.ts +33 -0
  181. package/dist/utils/config-loader.d.ts.map +1 -0
  182. package/dist/utils/config-loader.js +154 -0
  183. package/dist/utils/cron.d.ts +28 -0
  184. package/dist/utils/cron.d.ts.map +1 -0
  185. package/dist/utils/cron.js +119 -0
  186. package/dist/utils/deploy-runner.d.ts +38 -0
  187. package/dist/utils/deploy-runner.d.ts.map +1 -0
  188. package/dist/utils/deploy-runner.js +112 -0
  189. package/dist/utils/error-handler.d.ts +29 -0
  190. package/dist/utils/error-handler.d.ts.map +1 -0
  191. package/dist/utils/error-handler.js +302 -0
  192. package/dist/utils/list-command-utils.d.ts +23 -0
  193. package/dist/utils/list-command-utils.d.ts.map +1 -0
  194. package/dist/utils/list-command-utils.js +57 -0
  195. package/dist/utils/output-utils.d.ts +40 -0
  196. package/dist/utils/output-utils.d.ts.map +1 -0
  197. package/dist/utils/output-utils.js +250 -0
  198. package/dist/utils/pipeline-config.d.ts +33 -0
  199. package/dist/utils/pipeline-config.d.ts.map +1 -0
  200. package/dist/utils/pipeline-config.js +55 -0
  201. package/dist/utils/platform-secret.d.ts +27 -0
  202. package/dist/utils/platform-secret.d.ts.map +1 -0
  203. package/dist/utils/platform-secret.js +92 -0
  204. package/dist/utils/plugin-resolver.d.ts +17 -0
  205. package/dist/utils/plugin-resolver.d.ts.map +1 -0
  206. package/dist/utils/plugin-resolver.js +101 -0
  207. package/dist/utils/preflight.d.ts +26 -0
  208. package/dist/utils/preflight.d.ts.map +1 -0
  209. package/dist/utils/preflight.js +59 -0
  210. package/dist/utils/rate-limiter.d.ts +15 -0
  211. package/dist/utils/rate-limiter.d.ts.map +1 -0
  212. package/dist/utils/rate-limiter.js +164 -0
  213. package/dist/utils/registry.d.ts +86 -0
  214. package/dist/utils/registry.d.ts.map +1 -0
  215. package/dist/utils/registry.js +140 -0
  216. package/dist/utils/response-utils.d.ts +21 -0
  217. package/dist/utils/response-utils.d.ts.map +1 -0
  218. package/dist/utils/response-utils.js +70 -0
  219. package/dist/utils/tls.d.ts +52 -0
  220. package/dist/utils/tls.d.ts.map +1 -0
  221. package/dist/utils/tls.js +88 -0
  222. package/package.json +181 -0
@@ -0,0 +1,194 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { randomBytes } from 'crypto';
4
+ import { readFileSync } from 'node:fs';
5
+ // ESM has no synchronous named JSON import (Node only exposes a default export,
6
+ // and importing `../../package.json` would also fall outside `rootDir: src`), so
7
+ // read the version from package.json at load time relative to this module.
8
+ const { version } = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf-8'));
9
+ /**
10
+ * Application metadata
11
+ */
12
+ export const APP_NAME = 'pipeline-manager';
13
+ export const APP_VERSION = version;
14
+ export const APP_DESCRIPTION = 'A CLI tool to manage pipelines and plugins';
15
+ /**
16
+ * Banner configuration for CLI startup
17
+ */
18
+ export const BANNER_OPTIONS = {
19
+ font: 'Doom',
20
+ horizontalLayout: 'fitted',
21
+ getWidth: () => process.stdout.columns ?? 80,
22
+ };
23
+ /**
24
+ * Default timeouts (in milliseconds)
25
+ */
26
+ export const TIMEOUTS = {
27
+ HTTP_REQUEST: 30000,
28
+ HEALTH_CHECK: 5000,
29
+ };
30
+ /**
31
+ * File size limits (in bytes)
32
+ */
33
+ export const FILE_SIZE_LIMITS = {
34
+ PLUGIN: 4 * 1024 * 1024 * 1024, // 4GB
35
+ PIPELINE_PROPS: 10 * 1024 * 1024, // 10MB
36
+ };
37
+ /**
38
+ * Environment variable names
39
+ */
40
+ export const ENV_VARS = {
41
+ PLATFORM_TOKEN: 'PLATFORM_TOKEN',
42
+ PLATFORM_BASE_URL: 'PLATFORM_BASE_URL',
43
+ CLI_CONFIG_PATH: 'CLI_CONFIG_PATH',
44
+ TLS_REJECT_UNAUTHORIZED: 'TLS_REJECT_UNAUTHORIZED',
45
+ AWS_REGION: 'AWS_REGION',
46
+ DEBUG: 'DEBUG',
47
+ };
48
+ /**
49
+ * Check if debug mode is enabled
50
+ */
51
+ export function isDebugMode(options) {
52
+ return options?.debug ?? process.env.DEBUG === 'true';
53
+ }
54
+ /**
55
+ * Format file size for display
56
+ */
57
+ export function formatFileSize(bytes) {
58
+ if (bytes === 0)
59
+ return '0 B';
60
+ const units = ['B', 'KB', 'MB', 'GB', 'TB'];
61
+ const k = 1024;
62
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
63
+ return `${(bytes / Math.pow(k, i)).toFixed(2)} ${units[i]}`;
64
+ }
65
+ /**
66
+ * Format duration for display
67
+ */
68
+ export function formatDuration(ms) {
69
+ if (ms < 1000)
70
+ return `${ms}ms`;
71
+ if (ms < 60000)
72
+ return `${(ms / 1000).toFixed(2)}s`;
73
+ const minutes = Math.floor(ms / 60000);
74
+ const remainingSeconds = ((ms % 60000) / 1000).toFixed(0);
75
+ return `${minutes}m ${remainingSeconds}s`;
76
+ }
77
+ /**
78
+ * Generate cryptographically random execution ID for request tracing.
79
+ */
80
+ export function generateExecutionId() {
81
+ return randomBytes(4).toString('hex').toUpperCase();
82
+ }
83
+ /**
84
+ * Table display options (box-drawing characters)
85
+ */
86
+ export const TABLE_OPTIONS = {
87
+ border: {
88
+ topBody: '─',
89
+ topJoin: '┬',
90
+ topLeft: '┌',
91
+ topRight: '┐',
92
+ bottomBody: '─',
93
+ bottomJoin: '┴',
94
+ bottomLeft: '└',
95
+ bottomRight: '┘',
96
+ bodyLeft: '│',
97
+ bodyRight: '│',
98
+ bodyJoin: '│',
99
+ joinBody: '─',
100
+ joinLeft: '├',
101
+ joinRight: '┤',
102
+ joinJoin: '┼',
103
+ },
104
+ };
105
+ /**
106
+ * Validate boolean string from CLI input
107
+ */
108
+ export function validateBoolean(value, fieldName) {
109
+ const normalized = value.toLowerCase().trim();
110
+ if (['true', '1', 'yes', 'y'].includes(normalized))
111
+ return true;
112
+ if (['false', '0', 'no', 'n'].includes(normalized))
113
+ return false;
114
+ throw new Error(`Invalid boolean value for ${fieldName}: "${value}". Use true/false, yes/no, or 1/0.`);
115
+ }
116
+ /**
117
+ * Validate and parse a numeric CLI parameter within optional bounds.
118
+ */
119
+ export function validateNumber(value, fieldName, min, max) {
120
+ // A missing value (e.g. a list command that forgot its commander default)
121
+ // would otherwise throw a cryptic `undefined.trim()` TypeError — fail with a
122
+ // clear validation message instead.
123
+ if (value === undefined || value === null || value === '') {
124
+ throw new Error(`Invalid ${fieldName}: a value is required`);
125
+ }
126
+ // Reject partial parses ("12abc" -> 12) and non-integers ("5.9" -> 5) that
127
+ // `parseInt` would otherwise accept silently.
128
+ if (typeof value === 'string' && !/^-?\d+$/.test(value.trim())) {
129
+ throw new Error(`Invalid ${fieldName}: must be a whole number`);
130
+ }
131
+ const num = typeof value === 'number' ? value : parseInt(value.trim(), 10);
132
+ if (!Number.isInteger(num)) {
133
+ throw new Error(`Invalid ${fieldName}: must be a whole number`);
134
+ }
135
+ if (min !== undefined && num < min) {
136
+ throw new Error(`Invalid ${fieldName}: must be >= ${min}`);
137
+ }
138
+ if (max !== undefined && num > max) {
139
+ throw new Error(`Invalid ${fieldName}: must be <= ${max}`);
140
+ }
141
+ return num;
142
+ }
143
+ /**
144
+ * Validate sort parameter format (e.g., "createdAt:desc").
145
+ * Returns the validated sort string, or the default if invalid.
146
+ */
147
+ const SORT_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*:(asc|desc)$/;
148
+ export function validateSort(value) {
149
+ if (!value)
150
+ return undefined;
151
+ if (!SORT_PATTERN.test(value)) {
152
+ throw new Error(`Invalid sort format: "${value}". Expected "field:asc" or "field:desc".`);
153
+ }
154
+ return value;
155
+ }
156
+ /**
157
+ * Reject strings containing shell metacharacters.
158
+ * Used to sanitize CLI inputs before passing to shell commands.
159
+ */
160
+ const SHELL_UNSAFE = /[;&|`$(){}[\]<>!#~*?\\\n\r]/;
161
+ export function assertShellSafe(value, fieldName, opts = {}) {
162
+ if (SHELL_UNSAFE.test(value)) {
163
+ // `redactValue` is for secrets (tokens, passwords) — never echo the offending
164
+ // value into an error/log line when the field is sensitive.
165
+ const detail = opts.redactValue ? '' : `: "${value}"`;
166
+ throw new Error(`${fieldName} contains unsafe characters${detail}`);
167
+ }
168
+ }
169
+ /**
170
+ * POSIX single-quote a value (escaping embedded single quotes) for safe shell
171
+ * interpolation. Single source of truth — drift in a shell-quoting helper is
172
+ * security-relevant, so both agent modules import this rather than re-declaring it.
173
+ */
174
+ export function shellQuote(value) {
175
+ return `'${value.replace(/'/g, "'\\''")}'`;
176
+ }
177
+ /**
178
+ * Canonical default pipeline name when none is given — MUST mirror pipeline-core's
179
+ * pipeline-configuration.ts (`${sanitize(org)}-${sanitize(project)}-pipeline`,
180
+ * non-alphanumeric → '_', lowercased), so the CLI's create and registry paths
181
+ * derive the SAME name the CDK builder actually deploys (a mismatch breaks
182
+ * StartPipelineExecution against the registered name).
183
+ */
184
+ export function defaultPipelineName(organization, project) {
185
+ const sanitize = (s) => s.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase();
186
+ return `${sanitize(organization)}-${sanitize(project)}-pipeline`;
187
+ }
188
+ /**
189
+ * CloudFormation stack name for the event-ingestion infra. Shared so `setup-events`
190
+ * (which deploys it) and `redrive-events` (which resolves the DLQ/queue from it)
191
+ * can never drift apart.
192
+ */
193
+ export const EVENTS_STACK_NAME = 'pipeline-builder-events';
194
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xpLmNvbnN0YW50cy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9jb25maWcvY2xpLmNvbnN0YW50cy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBRXRDLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxRQUFRLENBQUM7QUFDckMsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLFNBQVMsQ0FBQztBQUV2QyxnRkFBZ0Y7QUFDaEYsaUZBQWlGO0FBQ2pGLDJFQUEyRTtBQUMzRSxNQUFNLEVBQUUsT0FBTyxFQUFFLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FDNUIsWUFBWSxDQUFDLElBQUksR0FBRyxDQUFDLG9CQUFvQixFQUFFLE9BQU8sSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLE9BQU8sQ0FBQyxDQUMvQyxDQUFDO0FBRXpCOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sUUFBUSxHQUFHLGtCQUFrQixDQUFDO0FBQzNDLE1BQU0sQ0FBQyxNQUFNLFdBQVcsR0FBRyxPQUFPLENBQUM7QUFDbkMsTUFBTSxDQUFDLE1BQU0sZUFBZSxHQUFHLDRDQUE0QyxDQUFDO0FBRTVFOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sY0FBYyxHQUFHO0lBQzVCLElBQUksRUFBRSxNQUFlO0lBQ3JCLGdCQUFnQixFQUFFLFFBQWlCO0lBQ25DLFFBQVEsRUFBRSxHQUFHLEVBQUUsQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDLE9BQU8sSUFBSSxFQUFFO0NBQ3BDLENBQUM7QUFPWDs7R0FFRztBQUNILE1BQU0sQ0FBQyxNQUFNLFFBQVEsR0FBRztJQUN0QixZQUFZLEVBQUUsS0FBSztJQUNuQixZQUFZLEVBQUUsSUFBSTtDQUNWLENBQUM7QUFFWDs7R0FFRztBQUNILE1BQU0sQ0FBQyxNQUFNLGdCQUFnQixHQUFHO0lBQzlCLE1BQU0sRUFBRSxDQUFDLEdBQUcsSUFBSSxHQUFHLElBQUksR0FBRyxJQUFJ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QUMsV0FBVyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLE1BQU0sS0FBSyxHQUFHLENBQUM7UUFDdEQsTUFBTSxJQUFJLEtBQUssQ0FBQyxHQUFHLFNBQVMsOEJBQThCLE1BQU0sRUFBRSxDQUFDLENBQUM7SUFDdEUsQ0FBQztBQUNILENBQUM7QUFFRDs7OztHQUlHO0FBQ0gsTUFBTSxVQUFVLFVBQVUsQ0FBQyxLQUFhO0lBQ3RDLE9BQU8sSUFBSSxLQUFLLENBQUMsT0FBTyxDQUFDLElBQUksRUFBRSxPQUFPLENBQUMsR0FBRyxDQUFDO0FBQzdDLENBQUM7QUFFRDs7Ozs7O0dBTUc7QUFDSCxNQUFNLFVBQVUsbUJBQW1CLENBQUMsWUFBb0IsRUFBRSxPQUFlO0lBQ3ZFLE1BQU0sUUFBUSxHQUFHLENBQUMsQ0FBUyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLGVBQWUsRUFBRSxHQUFHLENBQUMsQ0FBQyxXQUFXLEVBQUUsQ0FBQztJQUM5RSxPQUFPLEdBQUcsUUFBUSxDQUFDLFlBQVksQ0FBQyxJQUFJLFFBQVEsQ0FBQyxPQUFPLENBQUMsV0FBVyxDQUFDO0FBQ25FLENBQUM7QUFFRDs7OztHQUlHO0FBQ0gsTUFBTSxDQUFDLE1BQU0saUJBQWlCLEdBQUcseUJBQXlCLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuaW1wb3J0IHsgcmFuZG9tQnl0ZXMgfSBmcm9tICdjcnlwdG8nO1xuaW1wb3J0IHsgcmVhZEZpbGVTeW5jIH0gZnJvbSAnbm9kZTpmcyc7XG5cbi8vIEVTTSBoYXMgbm8gc3luY2hyb25vdXMgbmFtZWQgSlNPTiBpbXBvcnQgKE5vZGUgb25seSBleHBvc2VzIGEgZGVmYXVsdCBleHBvcnQsXG4vLyBhbmQgaW1wb3J0aW5nIGAuLi8uLi9wYWNrYWdlLmpzb25gIHdvdWxkIGFsc28gZmFsbCBvdXRzaWRlIGByb290RGlyOiBzcmNgKSwgc29cbi8vIHJlYWQgdGhlIHZlcnNpb24gZnJvbSBwYWNrYWdlLmpzb24gYXQgbG9hZCB0aW1lIHJlbGF0aXZlIHRvIHRoaXMgbW9kdWxlLlxuY29uc3QgeyB2ZXJzaW9uIH0gPSBKU09OLnBhcnNlKFxuICByZWFkRmlsZVN5bmMobmV3IFVSTCgnLi4vLi4vcGFja2FnZS5qc29uJywgaW1wb3J0Lm1ldGEudXJsKSwgJ3V0Zi04JyksXG4pIGFzIHsgdmVyc2lvbjogc3RyaW5nIH07XG5cbi8qKlxuICogQXBwbGljYXRpb24gbWV0YWRhdGFcbiAqL1xuZXhwb3J0IGNvbnN0IEFQUF9OQU1FID0gJ3BpcGVsaW5lLW1hbmFnZXInO1xuZXhwb3J0IGNvbnN0IEFQUF9WRVJTSU9OID0gdmVyc2lvbjtcbmV4cG9ydCBjb25zdCBBUFBfREVTQ1JJUFRJT04gPSAnQSBDTEkgdG9vbCB0byBtYW5hZ2UgcGlwZWxpbmVzIGFuZCBwbHVnaW5zJztcblxuLyoqXG4gKiBCYW5uZXIgY29uZmlndXJhdGlvbiBmb3IgQ0xJIHN0YXJ0dXBcbiAqL1xuZXhwb3J0IGNvbnN0IEJBTk5FUl9PUFRJT05TID0ge1xuICBmb250OiAnRG9vbScgYXMgY29uc3QsXG4gIGhvcml6b250YWxMYXlvdXQ6ICdmaXR0ZWQnIGFzIGNvbnN0LFxuICBnZXRXaWR0aDogKCkgPT4gcHJvY2Vzcy5zdGRvdXQuY29sdW1ucyA/PyA4MCxcbn0gYXMgY29uc3Q7XG5cbi8qKlxuICogT3V0cHV0IGZvcm1hdCB0eXBlc1xuICovXG5leHBvcnQgdHlwZSBPdXRwdXRGb3JtYXQgPSAndGFibGUnIHwgJ2pzb24nIHwgJ3lhbWwnIHwgJ2Nzdic7XG5cbi8qKlxuICogRGVmYXVsdCB0aW1lb3V0cyAoaW4gbWlsbGlzZWNvbmRzKVxuICovXG5leHBvcnQgY29uc3QgVElNRU9VVFMgPSB7XG4gIEhUVFBfUkVRVUVTVDogMzAwMDAsXG4gIEhFQUxUSF9DSEVDSzogNTAwMCxcbn0gYXMgY29uc3Q7XG5cbi8qKlxuICogRmlsZSBzaXplIGxpbWl0cyAoaW4gYnl0ZXMpXG4gKi9cbmV4cG9ydCBjb25zdCBGSUxFX1NJWkVfTElNSVRTID0ge1xuICBQTFVHSU46IDQgKiAxMDI0ICogMTAyNCAqIDEwMjQsIC8vIDRHQlxuICBQSVBFTElORV9QUk9QUzogMTAgKiAxMDI0ICogMTAyNCwgLy8gMTBNQlxufSBhcyBjb25zdDtcblxuLyoqXG4gKiBFbnZpcm9ubWVudCB2YXJpYWJsZSBuYW1lc1xuICovXG5leHBvcnQgY29uc3QgRU5WX1ZBUlMgPSB7XG4gIFBMQVRGT1JNX1RPS0VOOiAnUExBVEZPUk1fVE9LRU4nLFxuICBQTEFURk9STV9CQVNFX1VSTDogJ1BMQVRGT1JNX0JBU0VfVVJMJyxcbiAgQ0xJX0NPTkZJR19QQVRIOiAnQ0xJX0NPTkZJR19QQVRIJyxcbiAgVExTX1JFSkVDVF9VTkFVVEhPUklaRUQ6ICdUTFNfUkVKRUNUX1VOQVVUSE9SSVpFRCcsXG4gIEFXU19SRUdJT046ICdBV1NfUkVHSU9OJyxcbiAgREVCVUc6ICdERUJVRycsXG59IGFzIGNvbnN0O1xuXG4vKipcbiAqIENoZWNrIGlmIGRlYnVnIG1vZGUgaXMgZW5hYmxlZFxuICovXG5leHBvcnQgZnVuY3Rpb24gaXNEZWJ1Z01vZGUob3B0aW9ucz86IHsgZGVidWc/OiBib29sZWFuIH0pOiBib29sZWFuIHtcbiAgcmV0dXJuIG9wdGlvbnM/LmRlYnVnID8/IHByb2Nlc3MuZW52LkRFQlVHID09PSAndHJ1ZSc7XG59XG5cbi8qKlxuICogRm9ybWF0IGZpbGUgc2l6ZSBmb3IgZGlzcGxheVxuICovXG5leHBvcnQgZnVuY3Rpb24gZm9ybWF0RmlsZVNpemUoYnl0ZXM6IG51bWJlcik6IHN0cmluZyB7XG4gIGlmIChieXRlcyA9PT0gMCkgcmV0dXJuICcwIEInO1xuICBjb25zdCB1bml0cyA9IFsnQicsICdLQicsICdNQicsICdHQicsICdUQiddO1xuICBjb25zdCBrID0gMTAyNDtcbiAgY29uc3QgaSA9IE1hdGguZmxvb3IoTWF0aC5sb2coYnl0ZXMpIC8gTWF0aC5sb2coaykpO1xuICByZXR1cm4gYCR7KGJ5dGVzIC8gTWF0aC5wb3coaywgaSkpLnRvRml4ZWQoMil9ICR7dW5pdHNbaV19YDtcbn1cblxuLyoqXG4gKiBGb3JtYXQgZHVyYXRpb24gZm9yIGRpc3BsYXlcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGZvcm1hdER1cmF0aW9uKG1zOiBudW1iZXIpOiBzdHJpbmcge1xuICBpZiAobXMgPCAxMDAwKSByZXR1cm4gYCR7bXN9bXNgO1xuICBpZiAobXMgPCA2MDAwMCkgcmV0dXJuIGAkeyhtcyAvIDEwMDApLnRvRml4ZWQoMil9c2A7XG4gIGNvbnN0IG1pbnV0ZXMgPSBNYXRoLmZsb29yKG1zIC8gNjAwMDApO1xuICBjb25zdCByZW1haW5pbmdTZWNvbmRzID0gKChtcyAlIDYwMDAwKSAvIDEwMDApLnRvRml4ZWQoMCk7XG4gIHJldHVybiBgJHttaW51dGVzfW0gJHtyZW1haW5pbmdTZWNvbmRzfXNgO1xufVxuXG4vKipcbiAqIEdlbmVyYXRlIGNyeXB0b2dyYXBoaWNhbGx5IHJhbmRvbSBleGVjdXRpb24gSUQgZm9yIHJlcXVlc3QgdHJhY2luZy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGdlbmVyYXRlRXhlY3V0aW9uSWQoKTogc3RyaW5nIHtcbiAgcmV0dXJuIHJhbmRvbUJ5dGVzKDQpLnRvU3RyaW5nKCdoZXgnKS50b1VwcGVyQ2FzZSgpO1xufVxuXG4vKipcbiAqIFRhYmxlIGRpc3BsYXkgb3B0aW9ucyAoYm94LWRyYXdpbmcgY2hhcmFjdGVycylcbiAqL1xuZXhwb3J0IGNvbnN0IFRBQkxFX09QVElPTlMgPSB7XG4gIGJvcmRlcjoge1xuICAgIHRvcEJvZHk6ICfilIAnLFxuICAgIHRvcEpvaW46ICfilKwnLFxuICAgIHRvcExlZnQ6ICfilIwnLFxuICAgIHRvcFJpZ2h0OiAn4pSQJyxcbiAgICBib3R0b21Cb2R5OiAn4pSAJyxcbiAgICBib3R0b21Kb2luOiAn4pS0JyxcbiAgICBib3R0b21MZWZ0OiAn4pSUJyxcbiAgICBib3R0b21SaWdodDogJ+KUmCcsXG4gICAgYm9keUxlZnQ6ICfilIInLFxuICAgIGJvZHlSaWdodDogJ+KUgicsXG4gICAgYm9keUpvaW46ICfilIInLFxuICAgIGpvaW5Cb2R5OiAn4pSAJyxcbiAgICBqb2luTGVmdDogJ+KUnCcsXG4gICAgam9pblJpZ2h0OiAn4pSkJyxcbiAgICBqb2luSm9pbjogJ+KUvCcsXG4gIH0sXG59IGFzIGNvbnN0O1xuXG4vKipcbiAqIFZhbGlkYXRlIGJvb2xlYW4gc3RyaW5nIGZyb20gQ0xJIGlucHV0XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiB2YWxpZGF0ZUJvb2xlYW4odmFsdWU6IHN0cmluZywgZmllbGROYW1lOiBzdHJpbmcpOiBib29sZWFuIHtcbiAgY29uc3Qgbm9ybWFsaXplZCA9IHZhbHVlLnRvTG93ZXJDYXNlKCkudHJpbSgpO1xuICBpZiAoWyd0cnVlJywgJzEnLCAneWVzJywgJ3knXS5pbmNsdWRlcyhub3JtYWxpemVkKSkgcmV0dXJuIHRydWU7XG4gIGlmIChbJ2ZhbHNlJywgJzAnLCAnbm8nLCAnbiddLmluY2x1ZGVzKG5vcm1hbGl6ZWQpKSByZXR1cm4gZmFsc2U7XG4gIHRocm93IG5ldyBFcnJvcihgSW52YWxpZCBib29sZWFuIHZhbHVlIGZvciAke2ZpZWxkTmFtZX06IFwiJHt2YWx1ZX1cIi4gVXNlIHRydWUvZmFsc2UsIHllcy9ubywgb3IgMS8wLmApO1xufVxuXG4vKipcbiAqIFZhbGlkYXRlIGFuZCBwYXJzZSBhIG51bWVyaWMgQ0xJIHBhcmFtZXRlciB3aXRoaW4gb3B0aW9uYWwgYm91bmRzLlxuICovXG5leHBvcnQgZnVuY3Rpb24gdmFsaWRhdGVOdW1iZXIodmFsdWU6IHN0cmluZyB8IG51bWJlciB8IHVuZGVmaW5lZCB8IG51bGwsIGZpZWxkTmFtZTogc3RyaW5nLCBtaW4/OiBudW1iZXIsIG1heD86IG51bWJlcik6IG51bWJlciB7XG4gIC8vIEEgbWlzc2luZyB2YWx1ZSAoZS5nLiBhIGxpc3QgY29tbWFuZCB0aGF0IGZvcmdvdCBpdHMgY29tbWFuZGVyIGRlZmF1bHQpXG4gIC8vIHdvdWxkIG90aGVyd2lzZSB0aHJvdyBhIGNyeXB0aWMgYHVuZGVmaW5lZC50cmltKClgIFR5cGVFcnJvciDigJQgZmFpbCB3aXRoIGFcbiAgLy8gY2xlYXIgdmFsaWRhdGlvbiBtZXNzYWdlIGluc3RlYWQuXG4gIGlmICh2YWx1ZSA9PT0gdW5kZWZpbmVkIHx8IHZhbHVlID09PSBudWxsIHx8IHZhbHVlID09PSAnJykge1xuICAgIHRocm93IG5ldyBFcnJvcihgSW52YWxpZCAke2ZpZWxkTmFtZX06IGEgdmFsdWUgaXMgcmVxdWlyZWRgKTtcbiAgfVxuICAvLyBSZWplY3QgcGFydGlhbCBwYXJzZXMgKFwiMTJhYmNcIiAtPiAxMikgYW5kIG5vbi1pbnRlZ2VycyAoXCI1LjlcIiAtPiA1KSB0aGF0XG4gIC8vIGBwYXJzZUludGAgd291bGQgb3RoZXJ3aXNlIGFjY2VwdCBzaWxlbnRseS5cbiAgaWYgKHR5cGVvZiB2YWx1ZSA9PT0gJ3N0cmluZycgJiYgIS9eLT9cXGQrJC8udGVzdCh2YWx1ZS50cmltKCkpKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKGBJbnZhbGlkICR7ZmllbGROYW1lfTogbXVzdCBiZSBhIHdob2xlIG51bWJlcmApO1xuICB9XG4gIGNvbnN0IG51bSA9IHR5cGVvZiB2YWx1ZSA9PT0gJ251bWJlcicgPyB2YWx1ZSA6IHBhcnNlSW50KHZhbHVlLnRyaW0oKSwgMTApO1xuICBpZiAoIU51bWJlci5pc0ludGVnZXIobnVtKSkge1xuICAgIHRocm93IG5ldyBFcnJvcihgSW52YWxpZCAke2ZpZWxkTmFtZX06IG11c3QgYmUgYSB3aG9sZSBudW1iZXJgKTtcbiAgfVxuICBpZiAobWluICE9PSB1bmRlZmluZWQgJiYgbnVtIDwgbWluKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKGBJbnZhbGlkICR7ZmllbGROYW1lfTogbXVzdCBiZSA+PSAke21pbn1gKTtcbiAgfVxuICBpZiAobWF4ICE9PSB1bmRlZmluZWQgJiYgbnVtID4gbWF4KSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKGBJbnZhbGlkICR7ZmllbGROYW1lfTogbXVzdCBiZSA8PSAke21heH1gKTtcbiAgfVxuICByZXR1cm4gbnVtO1xufVxuXG4vKipcbiAqIFZhbGlkYXRlIHNvcnQgcGFyYW1ldGVyIGZvcm1hdCAoZS5nLiwgXCJjcmVhdGVkQXQ6ZGVzY1wiKS5cbiAqIFJldHVybnMgdGhlIHZhbGlkYXRlZCBzb3J0IHN0cmluZywgb3IgdGhlIGRlZmF1bHQgaWYgaW52YWxpZC5cbiAqL1xuY29uc3QgU09SVF9QQVRURVJOID0gL15bYS16QS1aX11bYS16QS1aMC05X10qOihhc2N8ZGVzYykkLztcblxuZXhwb3J0IGZ1bmN0aW9uIHZhbGlkYXRlU29ydCh2YWx1ZTogc3RyaW5nIHwgdW5kZWZpbmVkKTogc3RyaW5nIHwgdW5kZWZpbmVkIHtcbiAgaWYgKCF2YWx1ZSkgcmV0dXJuIHVuZGVmaW5lZDtcbiAgaWYgKCFTT1JUX1BBVFRFUk4udGVzdCh2YWx1ZSkpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoYEludmFsaWQgc29ydCBmb3JtYXQ6IFwiJHt2YWx1ZX1cIi4gRXhwZWN0ZWQgXCJmaWVsZDphc2NcIiBvciBcImZpZWxkOmRlc2NcIi5gKTtcbiAgfVxuICByZXR1cm4gdmFsdWU7XG59XG5cbi8qKlxuICogUmVqZWN0IHN0cmluZ3MgY29udGFpbmluZyBzaGVsbCBtZXRhY2hhcmFjdGVycy5cbiAqIFVzZWQgdG8gc2FuaXRpemUgQ0xJIGlucHV0cyBiZWZvcmUgcGFzc2luZyB0byBzaGVsbCBjb21tYW5kcy5cbiAqL1xuY29uc3QgU0hFTExfVU5TQUZFID0gL1s7JnxgJCgpe31bXFxdPD4hI34qP1xcXFxcXG5cXHJdLztcblxuZXhwb3J0IGZ1bmN0aW9uIGFzc2VydFNoZWxsU2FmZShcbiAgdmFsdWU6IHN0cmluZyxcbiAgZmllbGROYW1lOiBzdHJpbmcsXG4gIG9wdHM6IHsgcmVkYWN0VmFsdWU/OiBib29sZWFuIH0gPSB7fSxcbik6IHZvaWQge1xuICBpZiAoU0hFTExfVU5TQUZFLnRlc3QodmFsdWUpKSB7XG4gICAgLy8gYHJlZGFjdFZhbHVlYCBpcyBmb3Igc2VjcmV0cyAodG9rZW5zLCBwYXNzd29yZHMpIOKAlCBuZXZlciBlY2hvIHRoZSBvZmZlbmRpbmdcbiAgICAvLyB2YWx1ZSBpbnRvIGFuIGVycm9yL2xvZyBsaW5lIHdoZW4gdGhlIGZpZWxkIGlzIHNlbnNpdGl2ZS5cbiAgICBjb25zdCBkZXRhaWwgPSBvcHRzLnJlZGFjdFZhbHVlID8gJycgOiBgOiBcIiR7dmFsdWV9XCJgO1xuICAgIHRocm93IG5ldyBFcnJvcihgJHtmaWVsZE5hbWV9IGNvbnRhaW5zIHVuc2FmZSBjaGFyYWN0ZXJzJHtkZXRhaWx9YCk7XG4gIH1cbn1cblxuLyoqXG4gKiBQT1NJWCBzaW5nbGUtcXVvdGUgYSB2YWx1ZSAoZXNjYXBpbmcgZW1iZWRkZWQgc2luZ2xlIHF1b3RlcykgZm9yIHNhZmUgc2hlbGxcbiAqIGludGVycG9sYXRpb24uIFNpbmdsZSBzb3VyY2Ugb2YgdHJ1dGgg4oCUIGRyaWZ0IGluIGEgc2hlbGwtcXVvdGluZyBoZWxwZXIgaXNcbiAqIHNlY3VyaXR5LXJlbGV2YW50LCBzbyBib3RoIGFnZW50IG1vZHVsZXMgaW1wb3J0IHRoaXMgcmF0aGVyIHRoYW4gcmUtZGVjbGFyaW5nIGl0LlxuICovXG5leHBvcnQgZnVuY3Rpb24gc2hlbGxRdW90ZSh2YWx1ZTogc3RyaW5nKTogc3RyaW5nIHtcbiAgcmV0dXJuIGAnJHt2YWx1ZS5yZXBsYWNlKC8nL2csIFwiJ1xcXFwnJ1wiKX0nYDtcbn1cblxuLyoqXG4gKiBDYW5vbmljYWwgZGVmYXVsdCBwaXBlbGluZSBuYW1lIHdoZW4gbm9uZSBpcyBnaXZlbiDigJQgTVVTVCBtaXJyb3IgcGlwZWxpbmUtY29yZSdzXG4gKiBwaXBlbGluZS1jb25maWd1cmF0aW9uLnRzIChgJHtzYW5pdGl6ZShvcmcpfS0ke3Nhbml0aXplKHByb2plY3QpfS1waXBlbGluZWAsXG4gKiBub24tYWxwaGFudW1lcmljIOKGkiAnXycsIGxvd2VyY2FzZWQpLCBzbyB0aGUgQ0xJJ3MgY3JlYXRlIGFuZCByZWdpc3RyeSBwYXRoc1xuICogZGVyaXZlIHRoZSBTQU1FIG5hbWUgdGhlIENESyBidWlsZGVyIGFjdHVhbGx5IGRlcGxveXMgKGEgbWlzbWF0Y2ggYnJlYWtzXG4gKiBTdGFydFBpcGVsaW5lRXhlY3V0aW9uIGFnYWluc3QgdGhlIHJlZ2lzdGVyZWQgbmFtZSkuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBkZWZhdWx0UGlwZWxpbmVOYW1lKG9yZ2FuaXphdGlvbjogc3RyaW5nLCBwcm9qZWN0OiBzdHJpbmcpOiBzdHJpbmcge1xuICBjb25zdCBzYW5pdGl6ZSA9IChzOiBzdHJpbmcpID0+IHMucmVwbGFjZSgvW15hLXpBLVowLTldL2csICdfJykudG9Mb3dlckNhc2UoKTtcbiAgcmV0dXJuIGAke3Nhbml0aXplKG9yZ2FuaXphdGlvbil9LSR7c2FuaXRpemUocHJvamVjdCl9LXBpcGVsaW5lYDtcbn1cblxuLyoqXG4gKiBDbG91ZEZvcm1hdGlvbiBzdGFjayBuYW1lIGZvciB0aGUgZXZlbnQtaW5nZXN0aW9uIGluZnJhLiBTaGFyZWQgc28gYHNldHVwLWV2ZW50c2BcbiAqICh3aGljaCBkZXBsb3lzIGl0KSBhbmQgYHJlZHJpdmUtZXZlbnRzYCAod2hpY2ggcmVzb2x2ZXMgdGhlIERMUS9xdWV1ZSBmcm9tIGl0KVxuICogY2FuIG5ldmVyIGRyaWZ0IGFwYXJ0LlxuICovXG5leHBvcnQgY29uc3QgRVZFTlRTX1NUQUNLX05BTUUgPSAncGlwZWxpbmUtYnVpbGRlci1ldmVudHMnO1xuIl19
@@ -0,0 +1,94 @@
1
+ # CLI Configuration File
2
+ # Save this as config.yml in your project root or specify with CLI_CONFIG_PATH
3
+
4
+ # ============================================================================
5
+ # API Configuration
6
+ # ============================================================================
7
+ api:
8
+ # Base URL for the API
9
+ # Can be overridden with PLATFORM_URL environment variable
10
+ baseUrl: https://localhost:8443
11
+
12
+ # Request timeout in milliseconds
13
+ timeout: 30000
14
+
15
+ # SSL/TLS Certificate Validation
16
+ # Set to false to disable certificate validation (DEVELOPMENT ONLY)
17
+ # Default: true (secure)
18
+ #
19
+ # WARNING: Setting this to false disables certificate validation and makes
20
+ # your application vulnerable to man-in-the-middle attacks. Only use this
21
+ # in development environments with self-signed certificates.
22
+ rejectUnauthorized: true
23
+
24
+ # API Endpoints
25
+ #
26
+ # Pipeline Endpoints:
27
+ # pipelineUrl - Single pipeline operations (GET by ID, POST)
28
+ # Used by: get-pipeline, create-pipeline
29
+ # Format: /api/pipeline/{id} or /api/pipeline
30
+ #
31
+ # pipelineListUrl - List/query multiple pipelines with filters
32
+ # Used by: list-pipelines
33
+ # Format: /api/pipelines?filter1=value1&filter2=value2
34
+ # Supports filters: id, accessModifier, isDefault, isActive,
35
+ # project, projectPattern, organizationPattern,
36
+ # limit, offset, sort
37
+ #
38
+ # Plugin Endpoints:
39
+ # pluginUrl - Single plugin operations (GET by ID)
40
+ # Used by: get-plugin
41
+ # Format: /api/plugin/{id}
42
+ #
43
+ # pluginListUrl - List/query multiple plugins with filters
44
+ # Used by: list-plugins
45
+ # Format: /api/plugins?filter1=value1&filter2=value2
46
+ # Supports filters: id, accessModifier, isDefault, isActive,
47
+ # name, namePattern, version, versionRange,
48
+ # imageTag, limit, offset, sort
49
+ #
50
+ # pluginUploadUrl - Plugin upload endpoint
51
+ # Used by: upload-plugin
52
+ # Format: /api/plugin/upload
53
+ #
54
+ # Note: Singular vs Plural URLs
55
+ # - Singular (/api/plugin, /api/pipeline) - Single resource operations
56
+ # - Plural (/api/plugins, /api/pipelines) - List/query operations
57
+ pipelineUrl: /api/pipeline
58
+ pipelineListUrl: /api/pipelines
59
+ pluginUrl: /api/plugin
60
+ pluginListUrl: /api/plugins
61
+ pluginUploadUrl: /api/plugin/upload
62
+
63
+ # ============================================================================
64
+ # AUTHENTICATION
65
+ # ============================================================================
66
+ # Authentication is handled via environment variable ONLY.
67
+ # DO NOT include auth configuration in this file.
68
+ #
69
+ # Set your token with:
70
+ # export PLATFORM_TOKEN='your-token-here'
71
+ #
72
+ # The token should NEVER be committed to version control.
73
+ # ============================================================================
74
+
75
+ # ============================================================================
76
+ # ENVIRONMENT VARIABLES
77
+ # ============================================================================
78
+ # The following environment variables are used by the CLI:
79
+ #
80
+ # REQUIRED:
81
+ # PLATFORM_TOKEN - Authentication token for API access
82
+ # Example: export PLATFORM_TOKEN='eyJhbG...'
83
+ #
84
+ # OPTIONAL:
85
+ # PLATFORM_URL - Override api.baseUrl
86
+ # Example: export PLATFORM_URL='https://api.example.com'
87
+ #
88
+ # CLI_CONFIG_PATH - Path to this config file
89
+ # Example: export CLI_CONFIG_PATH='/path/to/config.yml'
90
+ #
91
+ # TLS_REJECT_UNAUTHORIZED - Set to '0' to disable cert validation
92
+ # Example: export TLS_REJECT_UNAUTHORIZED='0'
93
+ #
94
+ # ============================================================================
@@ -0,0 +1,2 @@
1
+ export declare const handler: () => Promise<void>;
2
+ //# sourceMappingURL=token-renew-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-renew-handler.d.ts","sourceRoot":"","sources":["../../src/lambda/token-renew-handler.ts"],"names":[],"mappings":"AA4DA,eAAO,MAAM,OAAO,QAAa,OAAO,CAAC,IAAI,CAyE5C,CAAC"}
@@ -0,0 +1,125 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Lambda handler that re-mints the platform JWT in Secrets Manager so it never
5
+ * lapses. Deployed + scheduled (once a day) by `pipeline-manager infra store-token
6
+ * --schedule`. Rather than re-implementing the token flow, it is a thin
7
+ * orchestrator that reuses the tested CLI — each run:
8
+ *
9
+ * 1. Point the `@pipeline-builder` npm scope at public npm.
10
+ * 2. Download `@pipeline-builder/pipeline-manager` from npm into /tmp.
11
+ * 3. Read the current platform JWT from the secret.
12
+ * 4. Run `pipeline-manager infra store-token`, which mints a fresh long-lived token
13
+ * via /api/user/generate-token and writes it back to the same secret.
14
+ *
15
+ * `/tmp` is the only writable path in Lambda, so npm's HOME/cache/prefix all live
16
+ * there. store-token does NOT deploy the renewal stack by default (no --schedule),
17
+ * so the renewal run never redeploys/recurses into its own stack.
18
+ *
19
+ * Env: PLATFORM_SECRET_NAME, PLATFORM_BASE_URL, RENEW_DAYS (default 30),
20
+ * PIPELINE_MANAGER_VERSION (default "latest"), PLATFORM_VERIFY_SSL ("false"
21
+ * to disable TLS verification).
22
+ */
23
+ import { execFileSync } from 'node:child_process';
24
+ import { mkdirSync, writeFileSync } from 'node:fs';
25
+ import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';
26
+ const PM_PACKAGE = '@pipeline-builder/pipeline-manager';
27
+ const TMP = '/tmp';
28
+ const PM_PREFIX = `${TMP}/pm`;
29
+ const NPM_CACHE = `${TMP}/.npm`;
30
+ const NPMRC = `${TMP}/.npmrc`;
31
+ const CLI = `${PM_PREFIX}/node_modules/${PM_PACKAGE}/dist/cli.js`;
32
+ function required(name) {
33
+ const v = process.env[name];
34
+ if (!v)
35
+ throw new Error(`${name} environment variable is required`);
36
+ return v;
37
+ }
38
+ /**
39
+ * Read the `scope` claim from a JWT payload without verifying the signature
40
+ * (the platform re-verifies on use; this only decides whether to re-mint WITH
41
+ * a scope). A scoped credential (e.g. `reporting:ingest`) must renew with the
42
+ * same scope or the fresh token would be a full-privilege platform token and
43
+ * the scoped consumer's calls would break. Returns undefined for an unscoped
44
+ * (platform) token or any decode failure.
45
+ */
46
+ function decodeJwtScope(jwt) {
47
+ try {
48
+ const payload = jwt.split('.')[1];
49
+ if (!payload)
50
+ return undefined;
51
+ const json = Buffer.from(payload.replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString('utf8');
52
+ const claims = JSON.parse(json);
53
+ return typeof claims.scope === 'string' && claims.scope ? claims.scope : undefined;
54
+ }
55
+ catch {
56
+ return undefined;
57
+ }
58
+ }
59
+ export const handler = async () => {
60
+ const secretName = required('PLATFORM_SECRET_NAME');
61
+ const platformUrl = required('PLATFORM_BASE_URL').replace(/\/$/, '');
62
+ const days = process.env.RENEW_DAYS || '30';
63
+ const version = process.env.PIPELINE_MANAGER_VERSION || 'latest';
64
+ const region = process.env.AWS_REGION || 'us-east-1';
65
+ // npm in Lambda can only write under /tmp; HOME/cache/userconfig point there.
66
+ const npmEnv = { ...process.env, HOME: TMP, npm_config_cache: NPM_CACHE };
67
+ // 1. Scope config: resolve @pipeline-builder from public npm regardless of any
68
+ // inherited registry. Written as a userconfig file (env mapping of scoped
69
+ // keys is unreliable).
70
+ writeFileSync(NPMRC, '@pipeline-builder:registry=https://registry.npmjs.org/\n');
71
+ // 2. Download pipeline-manager into /tmp.
72
+ mkdirSync(PM_PREFIX, { recursive: true });
73
+ // eslint-disable-next-line no-console
74
+ console.log(JSON.stringify({ level: 'INFO', msg: 'installing pipeline-manager', package: `${PM_PACKAGE}@${version}` }));
75
+ execFileSync('npm', ['install', '--prefix', PM_PREFIX, '--no-audit', '--no-fund', '--userconfig', NPMRC, `${PM_PACKAGE}@${version}`], { env: npmEnv, stdio: 'inherit' });
76
+ // 3. Read the current platform JWT — it authenticates the renewal request.
77
+ const sm = new SecretsManagerClient({ region });
78
+ const current = await sm.send(new GetSecretValueCommand({ SecretId: secretName }));
79
+ if (!current.SecretString)
80
+ throw new Error(`Secret "${secretName}" is empty`);
81
+ const jwt = JSON.parse(current.SecretString).password;
82
+ if (!jwt)
83
+ throw new Error(`Secret "${secretName}" missing password (JWT)`);
84
+ // 4. Run store-token (without --schedule, the default) so it ONLY re-mints and
85
+ // writes the secret — it must not redeploy this stack (would recurse). The
86
+ // write uses the Lambda role's creds.
87
+ // store-token no longer takes --secret-name; it reads PLATFORM_SECRET_NAME from
88
+ // the environment (else derives it from the token's org).
89
+ const args = ['infra', 'store-token', '--region', region, '--days', days];
90
+ // Preserve the token's capability scope on renewal (else a scoped ingest
91
+ // credential re-mints as a scopeless platform token and ingest starts 403ing).
92
+ // The re-mint authenticates with THIS token, and a scoped token still carries
93
+ // a userId, so /user/generate-token accepts it and re-issues the same scope.
94
+ const tokenScope = decodeJwtScope(jwt);
95
+ if (tokenScope) {
96
+ args.push('--scope', tokenScope);
97
+ // eslint-disable-next-line no-console
98
+ console.log(JSON.stringify({ level: 'INFO', msg: 'renewing scoped token', scope: tokenScope, secretName }));
99
+ }
100
+ // SECURITY: the spawned store-token POSTs the JWT to the platform. Refuse to
101
+ // disable TLS verification in production so a MITM can't harvest it. Production
102
+ // is this Lambda's normal mode (the renew stack sets NODE_ENV=production), so
103
+ // PLATFORM_VERIFY_SSL=false is honored ONLY outside production. This inlines the
104
+ // same `NODE_ENV==='production'` policy as utils/tls.ts#assertSslDisableAllowed
105
+ // (the handler ships as a single self-contained index.mjs and can't import it).
106
+ const isProduction = process.env.NODE_ENV === 'production';
107
+ if (process.env.PLATFORM_VERIFY_SSL === 'false') {
108
+ if (isProduction) {
109
+ // eslint-disable-next-line no-console
110
+ console.log(JSON.stringify({ level: 'WARN', msg: 'Refusing PLATFORM_VERIFY_SSL=false in production (NODE_ENV=production) — TLS verification stays enabled' }));
111
+ }
112
+ else {
113
+ args.push('--no-verify-ssl');
114
+ }
115
+ }
116
+ execFileSync('node', [CLI, ...args], {
117
+ // NODE_ENV is inherited via npmEnv (spread of process.env), so the spawned CLI
118
+ // enforces the same production TLS-disable refusal as this handler.
119
+ env: { ...npmEnv, PLATFORM_TOKEN: jwt, PLATFORM_BASE_URL: platformUrl, PLATFORM_SECRET_NAME: secretName },
120
+ stdio: 'inherit',
121
+ });
122
+ // eslint-disable-next-line no-console
123
+ console.log(JSON.stringify({ level: 'INFO', msg: 'platform token renewed', secretName }));
124
+ };
125
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidG9rZW4tcmVuZXctaGFuZGxlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9sYW1iZGEvdG9rZW4tcmVuZXctaGFuZGxlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBRXRDOzs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBbUJHO0FBQ0gsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBQ2xELE9BQU8sRUFBRSxTQUFTLEVBQUUsYUFBYSxFQUFFLE1BQU0sU0FBUyxDQUFDO0FBQ25ELE9BQU8sRUFBRSxvQkFBb0IsRUFBRSxxQkFBcUIsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBRTlGLE1BQU0sVUFBVSxHQUFHLG9DQUFvQyxDQUFDO0FBQ3hELE1BQU0sR0FBRyxHQUFHLE1BQU0sQ0FBQztBQUNuQixNQUFNLFNBQVMsR0FBRyxHQUFHLEdBQUcsS0FBSyxDQUFDO0FBQzlCLE1BQU0sU0FBUyxHQUFHLEdBQUcsR0FBRyxPQUFPLENBQUM7QUFDaEMsTUFBTSxLQUFLLEdBQUcsR0FBRyxHQUFHLFNBQVMsQ0FBQztBQUM5QixNQUFNLEdBQUcsR0FBRyxHQUFHLFNBQVMsaUJBQWlCLFVBQVUsY0FBYyxDQUFDO0FBRWxFLFNBQVMsUUFBUSxDQUFDLElBQVk7SUFDNUIsTUFBTSxDQUFDLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUM1QixJQUFJLENBQUMsQ0FBQztRQUFFLE1BQU0sSUFBSSxLQUFLLENBQUMsR0FBRyxJQUFJLG1DQUFtQyxDQUFDLENBQUM7SUFDcEUsT0FBTyxDQUFDLENBQUM7QUFDWCxDQUFDO0FBRUQ7Ozs7Ozs7R0FPRztBQUNILFNBQVMsY0FBYyxDQUFDLEdBQVc7SUFDakMsSUFBSSxDQUFDO1FBQ0gsTUFBTSxPQUFPLEdBQUcsR0FBRyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUNs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LElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxDQUFDO1FBQy9CLENBQUM7SUFDSCxDQUFDO0lBRUQsWUFBWSxDQUFDLE1BQU0sRUFBRSxDQUFDLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQyxFQUFFO1FBQ25DLCtFQUErRTtRQUMvRSxvRUFBb0U7UUFDcEUsR0FBRyxFQUFFLEVBQUUsR0FBRyxNQUFNLEVBQUUsY0FBYyxFQUFFLEdBQUcsRUFBRSxpQkFBaUIsRUFBRSxXQUFXLEVBQUUsb0JBQW9CLEVBQUUsVUFBVSxFQUFFO1FBQ3pHLEtBQUssRUFBRSxTQUFTO0tBQ2pCLENBQUMsQ0FBQztJQUVILHNDQUFzQztJQUN0QyxPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsRUFBRSxLQUFLLEVBQUUsTUFBTSxFQUFFLEdBQUcsRUFBRSx3QkFBd0IsRUFBRSxVQUFVLEVBQUUsQ0FBQyxDQUFDLENBQUM7QUFDNUYsQ0FBQyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQ29weXJpZ2h0IDIwMjYgUGlwZWxpbmUgQnVpbGRlciBDb250cmlidXRvcnNcbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBcGFjaGUtMi4wXG5cbi8qKlxuICogTGFtYmRhIGhhbmRsZXIgdGhhdCByZS1taW50cyB0aGUgcGxhdGZvcm0gSldUIGluIFNlY3JldHMgTWFuYWdlciBzbyBpdCBuZXZlclxuICogbGFwc2VzLiBEZXBsb3llZCArIHNjaGVkdWxlZCAob25jZSBhIGRheSkgYnkgYHBpcGVsaW5lLW1hbmFnZXIgaW5mcmEgc3RvcmUtdG9rZW5cbiAqIC0tc2NoZWR1bGVgLiBSYXRoZXIgdGhhbiByZS1pbXBsZW1lbnRpbmcgdGhlIHRva2VuIGZsb3csIGl0IGlzIGEgdGhpblxuICogb3JjaGVzdHJhdG9yIHRoYXQgcmV1c2VzIHRoZSB0ZXN0ZWQgQ0xJIOKAlCBlYWNoIHJ1bjpcbiAqXG4gKiAgIDEuIFBvaW50IHRoZSBgQHBpcGVsaW5lLWJ1aWxkZXJgIG5wbSBzY29wZSBhdCBwdWJsaWMgbnBtLlxuICogICAyLiBEb3dubG9hZCBgQHBpcGVsaW5lLWJ1aWxkZXIvcGlwZWxpbmUtbWFuYWdlcmAgZnJvbSBucG0gaW50byAvdG1wLlxuICogICAzLiBSZWFkIHRoZSBjdXJyZW50IHBsYXRmb3JtIEpXVCBmcm9tIHRoZSBzZWNyZXQuXG4gKiAgIDQuIFJ1biBgcGlwZWxpbmUtbWFuYWdlciBpbmZyYSBzdG9yZS10b2tlbmAsIHdoaWNoIG1pbnRzIGEgZnJlc2ggbG9uZy1saXZlZCB0b2tlblxuICogICAgICB2aWEgL2FwaS91c2VyL2dlbmVyYXRlLXRva2VuIGFuZCB3cml0ZXMgaXQgYmFjayB0byB0aGUgc2FtZSBzZWNyZXQuXG4gKlxuICogYC90bXBgIGlzIHRoZSBvbmx5IHdyaXRhYmxlIHBhdGggaW4gTGFtYmRhLCBzbyBucG0ncyBIT01FL2NhY2hlL3ByZWZpeCBhbGwgbGl2ZVxuICogdGhlcmUuIHN0b3JlLXRva2VuIGRvZXMgTk9UIGRlcGxveSB0aGUgcmVuZXdhbCBzdGFjayBieSBkZWZhdWx0IChubyAtLXNjaGVkdWxlKSxcbiAqIHNvIHRoZSByZW5ld2FsIHJ1biBuZXZlciByZWRlcGxveXMvcmVjdXJzZXMgaW50byBpdHMgb3duIHN0YWNrLlxuICpcbiAqIEVudjogUExBVEZPUk1fU0VDUkVUX05BTUUsIFBMQVRGT1JNX0JBU0VfVVJMLCBSRU5FV19EQVlTIChkZWZhdWx0IDMwKSxcbiAqICAgICAgUElQRUxJTkVfTUFOQUdFUl9WRVJTSU9OIChkZWZhdWx0IFwibGF0ZXN0XCIpLCBQTEFURk9STV9WRVJJRllfU1NMIChcImZhbHNlXCJcbiAqICAgICAgdG8gZGlzYWJsZSBUTFMgdmVyaWZpY2F0aW9uKS5cbiAqL1xuaW1wb3J0IHsgZXhlY0ZpbGVTeW5jIH0gZnJvbSAnbm9kZTpjaGlsZF9wcm9jZXNzJztcbmltcG9ydCB7IG1rZGlyU3luYywgd3JpdGVGaWxlU3luYyB9IGZyb20gJ25vZGU6ZnMnO1xuaW1wb3J0IHsgU2VjcmV0c01hbmFnZXJDbGllbnQsIEdldFNlY3JldFZhbHVlQ29tbWFuZCB9IGZyb20gJ0Bhd3Mtc2RrL2NsaWVudC1zZWNyZXRzLW1hbmFnZXInO1xuXG5jb25zdCBQTV9QQUNLQUdFID0gJ0BwaXBlbGluZS1idWlsZGVyL3BpcGVsaW5lLW1hbmFnZXInO1xuY29uc3QgVE1QID0gJy90bXAnO1xuY29uc3QgUE1fUFJFRklYID0gYCR7VE1QfS9wbWA7XG5jb25zdCBOUE1fQ0FDSEUgPSBgJHtUTVB9Ly5ucG1gO1xuY29uc3QgTlBNUkMgPSBgJHtUTVB9Ly5ucG1yY2A7XG5jb25zdCBDTEkgPSBgJHtQTV9QUkVGSVh9L25vZGVfbW9kdWxlcy8ke1BNX1BBQ0tBR0V9L2Rpc3QvY2xpLmpzYDtcblxuZnVuY3Rpb24gcmVxdWlyZWQobmFtZTogc3RyaW5nKTogc3RyaW5nIHtcbiAgY29uc3QgdiA9IHByb2Nlc3MuZW52W25hbWVdO1xuICBpZiAoIXYpIHRocm93IG5ldyBFcnJvcihgJHtuYW1lfSBlbnZpcm9ubWVudCB2YXJpYWJsZSBpcyByZXF1aXJlZGApO1xuICByZXR1cm4gdjtcbn1cblxuLyoqXG4gKiBSZWFkIHRoZSBgc2NvcGVgIGNsYWltIGZyb20gYSBKV1QgcGF5bG9hZCB3aXRob3V0IHZlcmlmeWluZyB0aGUgc2lnbmF0dXJlXG4gKiAodGhlIHBsYXRmb3JtIHJlLXZlcmlmaWVzIG9uIHVzZTsgdGhpcyBvbmx5IGRlY2lkZXMgd2hldGhlciB0byByZS1taW50IFdJVEhcbiAqIGEgc2NvcGUpLiBBIHNjb3BlZCBjcmVkZW50aWFsIChlLmcuIGByZXBvcnRpbmc6aW5nZXN0YCkgbXVzdCByZW5ldyB3aXRoIHRoZVxuICogc2FtZSBzY29wZSBvciB0aGUgZnJlc2ggdG9rZW4gd291bGQgYmUgYSBmdWxsLXByaXZpbGVnZSBwbGF0Zm9ybSB0b2tlbiBhbmRcbiAqIHRoZSBzY29wZWQgY29uc3VtZXIncyBjYWxscyB3b3VsZCBicmVhay4gUmV0dXJucyB1bmRlZmluZWQgZm9yIGFuIHVuc2NvcGVkXG4gKiAocGxhdGZvcm0pIHRva2VuIG9yIGFueSBkZWNvZGUgZmFpbHVyZS5cbiAqL1xuZnVuY3Rpb24gZGVjb2RlSnd0U2NvcGUoand0OiBzdHJpbmcpOiBzdHJpbmcgfCB1bmRlZmluZWQge1xuICB0cnkge1xuICAgIGNvbnN0IHBheWxvYWQgPSBqd3Quc3BsaXQoJy4nKVsxXTtcbiAgICBpZiAoIXBheWxvYWQpIHJldHVybiB1bmRlZmluZWQ7XG4gICAgY29uc3QganNvbiA9IEJ1ZmZlci5mcm9tKHBheWxvYWQucmVwbGFjZSgvLS9nLCAnKycpLnJlcGxhY2UoL18vZywgJy8nKSwgJ2Jhc2U2NCcpLnRvU3RyaW5nKCd1dGY4Jyk7XG4gICAgY29uc3QgY2xhaW1zID0gSlNPTi5wYXJzZShqc29uKSBhcyB7IHNjb3BlPzogdW5rbm93biB9O1xuICAgIHJldHVybiB0eXBlb2YgY2xhaW1zLnNjb3BlID09PSAnc3RyaW5nJyAmJiBjbGFpbXMuc2NvcGUgPyBjbGFpbXMuc2NvcGUgOiB1bmRlZmluZWQ7XG4gIH0gY2F0Y2gge1xuICAgIHJldHVybiB1bmRlZmluZWQ7XG4gIH1cbn1cblxuZXhwb3J0IGNvbnN0IGhhbmRsZXIgPSBhc3luYyAoKTogUHJvbWlzZTx2b2lkPiA9PiB7XG4gIGNvbnN0IHNlY3JldE5hbWUgPSByZXF1aXJlZCgnUExBVEZPUk1fU0VDUkVUX05BTUUnKTtcbiAgY29uc3QgcGxhdGZvcm1VcmwgPSByZXF1aXJlZCgnUExBVEZPUk1fQkFTRV9VUkwnKS5yZXBsYWNlKC9cXC8kLywgJycpO1xuICBjb25zdCBkYXlzID0gcHJvY2Vzcy5lbnYuUkVORVdfREFZUyB8fCAnMzAnO1xuICBjb25zdCB2ZXJzaW9uID0gcHJvY2Vzcy5lbnYuUElQRUxJTkVfTUFOQUdFUl9WRVJTSU9OIHx8ICdsYXRlc3QnO1xuICBjb25zdCByZWdpb24gPSBwcm9jZXNzLmVudi5BV1NfUkVHSU9OIHx8ICd1cy1lYXN0LTEnO1xuXG4gIC8vIG5wbSBpbiBMYW1iZGEgY2FuIG9ubHkgd3JpdGUgdW5kZXIgL3RtcDsgSE9NRS9jYWNoZS91c2VyY29uZmlnIHBvaW50IHRoZXJlLlxuICBjb25zdCBucG1FbnYgPSB7IC4uLnByb2Nlc3MuZW52LCBIT01FOiBUTVAsIG5wbV9jb25maWdfY2FjaGU6IE5QTV9DQUNIRSB9O1xuXG4gIC8vIDEuIFNjb3BlIGNvbmZpZzogcmVzb2x2ZSBAcGlwZWxpbmUtYnVpbGRlciBmcm9tIHB1YmxpYyBucG0gcmVnYXJkbGVzcyBvZiBhbnlcbiAgLy8gICAgaW5oZXJpdGVkIHJlZ2lzdHJ5LiBXcml0dGVuIGFzIGEgdXNlcmNvbmZpZyBmaWxlIChlbnYgbWFwcGluZyBvZiBzY29wZWRcbiAgLy8gICAga2V5cyBpcyB1bnJlbGlhYmxlKS5cbiAgd3JpdGVGaWxlU3luYyhOUE1SQywgJ0BwaXBlbGluZS1idWlsZGVyOnJlZ2lzdHJ5PWh0dHBzOi8vcmVnaXN0cnkubnBtanMub3JnL1xcbicpO1xuXG4gIC8vIDIuIERvd25sb2FkIHBpcGVsaW5lLW1hbmFnZXIgaW50byAvdG1wLlxuICBta2RpclN5bmMoUE1fUFJFRklYLCB7IHJlY3Vyc2l2ZTogdHJ1ZSB9KTtcbiAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIG5vLWNvbnNvbGVcbiAgY29uc29sZS5sb2coSlNPTi5zdHJpbmdpZnkoeyBsZXZlbDogJ0lORk8nLCBtc2c6ICdpbnN0YWxsaW5nIHBpcGVsaW5lLW1hbmFnZXInLCBwYWNrYWdlOiBgJHtQTV9QQUNLQUdFfUAke3ZlcnNpb259YCB9KSk7XG4gIGV4ZWNGaWxlU3luYyhcbiAgICAnbnBtJyxcbiAgICBbJ2luc3RhbGwnLCAnLS1wcmVmaXgnLCBQTV9QUkVGSVgsICctLW5vLWF1ZGl0JywgJy0tbm8tZnVuZCcsICctLXVzZXJjb25maWcnLCBOUE1SQywgYCR7UE1fUEFDS0FHRX1AJHt2ZXJzaW9ufWBdLFxuICAgIHsgZW52OiBucG1FbnYsIHN0ZGlvOiAnaW5oZXJpdCcgfSxcbiAgKTtcblxuICAvLyAzLiBSZWFkIHRoZSBjdXJyZW50IHBsYXRmb3JtIEpXVCDigJQgaXQgYXV0aGVudGljYXRlcyB0aGUgcmVuZXdhbCByZXF1ZXN0LlxuICBjb25zdCBzbSA9IG5ldyBTZWNyZXRzTWFuYWdlckNsaWVudCh7IHJlZ2lvbiB9KTtcbiAgY29uc3QgY3VycmVudCA9IGF3YWl0IHNtLnNlbmQobmV3IEdldFNlY3JldFZhbHVlQ29tbWFuZCh7IFNlY3JldElkOiBzZWNyZXROYW1lIH0pKTtcbiAgaWYgKCFjdXJyZW50LlNlY3JldFN0cmluZykgdGhyb3cgbmV3IEVycm9yKGBTZWNyZXQgXCIke3NlY3JldE5hbWV9XCIgaXMgZW1wdHlgKTtcbiAgY29uc3Qgand0ID0gKEpTT04ucGFyc2UoY3VycmVudC5TZWNyZXRTdHJpbmcpIGFzIHsgcGFzc3dvcmQ/OiBzdHJpbmcgfSkucGFzc3dvcmQ7XG4gIGlmICghand0KSB0aHJvdyBuZXcgRXJyb3IoYFNlY3JldCBcIiR7c2VjcmV0TmFtZX1cIiBtaXNzaW5nIHBhc3N3b3JkIChKV1QpYCk7XG5cbiAgLy8gNC4gUnVuIHN0b3JlLXRva2VuICh3aXRob3V0IC0tc2NoZWR1bGUsIHRoZSBkZWZhdWx0KSBzbyBpdCBPTkxZIHJlLW1pbnRzIGFuZFxuICAvLyAgICB3cml0ZXMgdGhlIHNlY3JldCDigJQgaXQgbXVzdCBub3QgcmVkZXBsb3kgdGhpcyBzdGFjayAod291bGQgcmVjdXJzZSkuIFRoZVxuICAvLyAgICB3cml0ZSB1c2VzIHRoZSBMYW1iZGEgcm9sZSdzIGNyZWRzLlxuICAvLyBzdG9yZS10b2tlbiBubyBsb25nZXIgdGFrZXMgLS1zZWNyZXQtbmFtZTsgaXQgcmVhZHMgUExBVEZPUk1fU0VDUkVUX05BTUUgZnJvbVxuICAvLyB0aGUgZW52aXJvbm1lbnQgKGVsc2UgZGVyaXZlcyBpdCBmcm9tIHRoZSB0b2tlbidzIG9yZykuXG4gIGNvbnN0IGFyZ3MgPSBbJ2luZnJhJywgJ3N0b3JlLXRva2VuJywgJy0tcmVnaW9uJywgcmVnaW9uLCAnLS1kYXlzJywgZGF5c107XG4gIC8vIFByZXNlcnZlIHRoZSB0b2tlbidzIGNhcGFiaWxpdHkgc2NvcGUgb24gcmVuZXdhbCAoZWxzZSBhIHNjb3BlZCBpbmdlc3RcbiAgLy8gY3JlZGVudGlhbCByZS1taW50cyBhcyBhIHNjb3BlbGVzcyBwbGF0Zm9ybSB0b2tlbiBhbmQgaW5nZXN0IHN0YXJ0cyA0MDNpbmcpLlxuICAvLyBUaGUgcmUtbWludCBhdXRoZW50aWNhdGVzIHdpdGggVEhJUyB0b2tlbiwgYW5kIGEgc2NvcGVkIHRva2VuIHN0aWxsIGNhcnJpZXNcbiAgLy8gYSB1c2VySWQsIHNvIC91c2VyL2dlbmVyYXRlLXRva2VuIGFjY2VwdHMgaXQgYW5kIHJlLWlzc3VlcyB0aGUgc2FtZSBzY29wZS5cbiAgY29uc3QgdG9rZW5TY29wZSA9IGRlY29kZUp3dFNjb3BlKGp3dCk7XG4gIGlmICh0b2tlblNjb3BlKSB7XG4gICAgYXJncy5wdXNoKCctLXNjb3BlJywgdG9rZW5TY29wZSk7XG4gICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIG5vLWNvbnNvbGVcbiAgICBjb25zb2xlLmxvZyhKU09OLnN0cmluZ2lmeSh7IGxldmVsOiAnSU5GTycsIG1zZzogJ3JlbmV3aW5nIHNjb3BlZCB0b2tlbicsIHNjb3BlOiB0b2tlblNjb3BlLCBzZWNyZXROYW1lIH0pKTtcbiAgfVxuICAvLyBTRUNVUklUWTogdGhlIHNwYXduZWQgc3RvcmUtdG9rZW4gUE9TVHMgdGhlIEpXVCB0byB0aGUgcGxhdGZvcm0uIFJlZnVzZSB0b1xuICAvLyBkaXNhYmxlIFRMUyB2ZXJpZmljYXRpb24gaW4gcHJvZHVjdGlvbiBzbyBhIE1JVE0gY2FuJ3QgaGFydmVzdCBpdC4gUHJvZHVjdGlvblxuICAvLyBpcyB0aGlzIExhbWJkYSdzIG5vcm1hbCBtb2RlICh0aGUgcmVuZXcgc3RhY2sgc2V0cyBOT0RFX0VOVj1wcm9kdWN0aW9uKSwgc29cbiAgLy8gUExBVEZPUk1fVkVSSUZZX1NTTD1mYWxzZSBpcyBob25vcmVkIE9OTFkgb3V0c2lkZSBwcm9kdWN0aW9uLiBUaGlzIGlubGluZXMgdGhlXG4gIC8vIHNhbWUgYE5PREVfRU5WPT09J3Byb2R1Y3Rpb24nYCBwb2xpY3kgYXMgdXRpbHMvdGxzLnRzI2Fzc2VydFNzbERpc2FibGVBbGxvd2VkXG4gIC8vICh0aGUgaGFuZGxlciBzaGlwcyBhcyBhIHNpbmdsZSBzZWxmLWNvbnRhaW5lZCBpbmRleC5tanMgYW5kIGNhbid0IGltcG9ydCBpdCkuXG4gIGNvbnN0IGlzUHJvZHVjdGlvbiA9IHByb2Nlc3MuZW52Lk5PREVfRU5WID09PSAncHJvZHVjdGlvbic7XG4gIGlmIChwcm9jZXNzLmVudi5QTEFURk9STV9WRVJJRllfU1NMID09PSAnZmFsc2UnKSB7XG4gICAgaWYgKGlzUHJvZHVjdGlvbikge1xuICAgICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIG5vLWNvbnNvbGVcbiAgICAgIGNvbnNvbGUubG9nKEpTT04uc3RyaW5naWZ5KHsgbGV2ZWw6ICdXQVJOJywgbXNnOiAnUmVmdXNpbmcgUExBVEZPUk1fVkVSSUZZX1NTTD1mYWxzZSBpbiBwcm9kdWN0aW9uIChOT0RFX0VOVj1wcm9kdWN0aW9uKSDigJQgVExTIHZlcmlmaWNhdGlvbiBzdGF5cyBlbmFibGVkJyB9KSk7XG4gICAgfSBlbHNlIHtcbiAgICAgIGFyZ3MucHVzaCgnLS1uby12ZXJpZnktc3NsJyk7XG4gICAgfVxuICB9XG5cbiAgZXhlY0ZpbGVTeW5jKCdub2RlJywgW0NMSSwgLi4uYXJnc10sIHtcbiAgICAvLyBOT0RFX0VOViBpcyBpbmhlcml0ZWQgdmlhIG5wbUVudiAoc3ByZWFkIG9mIHByb2Nlc3MuZW52KSwgc28gdGhlIHNwYXduZWQgQ0xJXG4gICAgLy8gZW5mb3JjZXMgdGhlIHNhbWUgcHJvZHVjdGlvbiBUTFMtZGlzYWJsZSByZWZ1c2FsIGFzIHRoaXMgaGFuZGxlci5cbiAgICBlbnY6IHsgLi4ubnBtRW52LCBQTEFURk9STV9UT0tFTjogand0LCBQTEFURk9STV9CQVNFX1VSTDogcGxhdGZvcm1VcmwsIFBMQVRGT1JNX1NFQ1JFVF9OQU1FOiBzZWNyZXROYW1lIH0sXG4gICAgc3RkaW86ICdpbmhlcml0JyxcbiAgfSk7XG5cbiAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIG5vLWNvbnNvbGVcbiAgY29uc29sZS5sb2coSlNPTi5zdHJpbmdpZnkoeyBsZXZlbDogJ0lORk8nLCBtc2c6ICdwbGF0Zm9ybSB0b2tlbiByZW5ld2VkJywgc2VjcmV0TmFtZSB9KSk7XG59O1xuIl19
@@ -0,0 +1,174 @@
1
+ {
2
+ "AWSTemplateFormatVersion": "2010-09-09",
3
+ "Description": "Pipeline Builder event ingestion — EventBridge rule, SQS queue, and Lambda that resolves each pipeline's pb.pipeline-id tag and forwards CodePipeline events to the reporting service API. Lambda code is deployed separately via pipeline-manager infra setup-events.",
4
+ "Parameters": {
5
+ "PlatformBaseUrl": {
6
+ "Type": "String",
7
+ "Description": "Base URL of the platform (e.g. https://app.example.com)"
8
+ },
9
+ "PlatformSecretName": {
10
+ "Type": "String",
11
+ "Description": "Secrets Manager secret name containing the platform JWT token (e.g. pipeline-builder/{orgId}/platform)"
12
+ },
13
+ "DoraEnabled": {
14
+ "Type": "String",
15
+ "Default": "false",
16
+ "AllowedValues": ["false", "true"],
17
+ "Description": "When true, the ingestion Lambda resolves commit timestamps in-account for DORA lead time (extra SCM/secret cost). Enable only for orgs with the advanced_reporting add-on."
18
+ }
19
+ },
20
+ "Conditions": {
21
+ "DoraOn": { "Fn::Equals": [{ "Ref": "DoraEnabled" }, "true"] }
22
+ },
23
+ "Resources": {
24
+ "EventQueue": {
25
+ "Type": "AWS::SQS::Queue",
26
+ "Properties": {
27
+ "QueueName": "pipeline-builder-events",
28
+ "VisibilityTimeout": 300,
29
+ "MessageRetentionPeriod": 1209600,
30
+ "RedrivePolicy": {
31
+ "deadLetterTargetArn": { "Fn::GetAtt": ["DeadLetterQueue", "Arn"] },
32
+ "maxReceiveCount": 3
33
+ }
34
+ }
35
+ },
36
+ "DeadLetterQueue": {
37
+ "Type": "AWS::SQS::Queue",
38
+ "Properties": {
39
+ "QueueName": "pipeline-builder-events-dlq",
40
+ "MessageRetentionPeriod": 1209600
41
+ }
42
+ },
43
+ "EventQueuePolicy": {
44
+ "Type": "AWS::SQS::QueuePolicy",
45
+ "Properties": {
46
+ "Queues": [{ "Ref": "EventQueue" }],
47
+ "PolicyDocument": {
48
+ "Statement": [{
49
+ "Effect": "Allow",
50
+ "Principal": { "Service": "events.amazonaws.com" },
51
+ "Action": "sqs:SendMessage",
52
+ "Resource": { "Fn::GetAtt": ["EventQueue", "Arn"] }
53
+ }]
54
+ }
55
+ }
56
+ },
57
+ "EventRule": {
58
+ "Type": "AWS::Events::Rule",
59
+ "Properties": {
60
+ "Name": "pipeline-builder-codepipeline-events",
61
+ "Description": "Capture CodePipeline state changes for reporting",
62
+ "State": "ENABLED",
63
+ "EventPattern": {
64
+ "source": ["aws.codepipeline"],
65
+ "detail-type": [
66
+ "CodePipeline Pipeline Execution State Change",
67
+ "CodePipeline Stage Execution State Change",
68
+ "CodePipeline Action Execution State Change"
69
+ ]
70
+ },
71
+ "Targets": [{
72
+ "Id": "pipeline-events-queue",
73
+ "Arn": { "Fn::GetAtt": ["EventQueue", "Arn"] }
74
+ }]
75
+ }
76
+ },
77
+ "LambdaRole": {
78
+ "Type": "AWS::IAM::Role",
79
+ "Properties": {
80
+ "RoleName": "pipeline-builder-event-ingestion",
81
+ "AssumeRolePolicyDocument": {
82
+ "Version": "2012-10-17",
83
+ "Statement": [{
84
+ "Effect": "Allow",
85
+ "Principal": { "Service": "lambda.amazonaws.com" },
86
+ "Action": "sts:AssumeRole"
87
+ }]
88
+ },
89
+ "ManagedPolicyArns": [
90
+ "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
91
+ ],
92
+ "Policies": [{
93
+ "PolicyName": "event-ingestion",
94
+ "PolicyDocument": {
95
+ "Statement": [
96
+ {
97
+ "Effect": "Allow",
98
+ "Action": ["sqs:ReceiveMessage", "sqs:DeleteMessage", "sqs:GetQueueAttributes"],
99
+ "Resource": { "Fn::GetAtt": ["EventQueue", "Arn"] }
100
+ },
101
+ {
102
+ "Effect": "Allow",
103
+ "Action": ["sqs:StartMessageMoveTask", "sqs:ListMessageMoveTasks", "sqs:GetQueueAttributes"],
104
+ "Resource": [
105
+ { "Fn::GetAtt": ["EventQueue", "Arn"] },
106
+ { "Fn::GetAtt": ["DeadLetterQueue", "Arn"] }
107
+ ]
108
+ },
109
+ {
110
+ "Effect": "Allow",
111
+ "Action": "secretsmanager:GetSecretValue",
112
+ "Resource": [
113
+ { "Fn::Sub": "arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:pipeline-builder/*/platform-*" },
114
+ { "Fn::Sub": "arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:pipeline-builder/*/reporting-ingest-*" },
115
+ { "Fn::If": ["DoraOn", { "Fn::Sub": "arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:pipeline-builder/*/github-token-*" }, { "Ref": "AWS::NoValue" }] }
116
+ ]
117
+ },
118
+ {
119
+ "Effect": "Allow",
120
+ "Action": "codepipeline:ListTagsForResource",
121
+ "Resource": { "Fn::Sub": "arn:aws:codepipeline:*:${AWS::AccountId}:*" }
122
+ },
123
+ {
124
+ "Fn::If": ["DoraOn", {
125
+ "Effect": "Allow",
126
+ "Action": "codecommit:GetCommit",
127
+ "Resource": { "Fn::Sub": "arn:aws:codecommit:*:${AWS::AccountId}:*" }
128
+ }, { "Ref": "AWS::NoValue" }]
129
+ }
130
+ ]
131
+ }
132
+ }]
133
+ }
134
+ },
135
+ "EventHandler": {
136
+ "Type": "AWS::Lambda::Function",
137
+ "Properties": {
138
+ "FunctionName": "pipeline-builder-event-ingestion",
139
+ "Runtime": "nodejs24.x",
140
+ "Handler": "index.handler",
141
+ "Code": {
142
+ "ZipFile": "exports.handler=async(event)=>{console.log('Placeholder handler — run pipeline-manager infra setup-events to deploy real code');return{statusCode:200,body:JSON.stringify({message:'hello',records:event.Records?.length||0})}}"
143
+ },
144
+ "Role": { "Fn::GetAtt": ["LambdaRole", "Arn"] },
145
+ "Timeout": 60,
146
+ "MemorySize": 256,
147
+ "Environment": {
148
+ "Variables": {
149
+ "PLATFORM_BASE_URL": { "Ref": "PlatformBaseUrl" },
150
+ "PLATFORM_SECRET_NAME": { "Ref": "PlatformSecretName" },
151
+ "DORA_ENABLED": { "Ref": "DoraEnabled" }
152
+ }
153
+ }
154
+ }
155
+ },
156
+ "EventSourceMapping": {
157
+ "Type": "AWS::Lambda::EventSourceMapping",
158
+ "Properties": {
159
+ "EventSourceArn": { "Fn::GetAtt": ["EventQueue", "Arn"] },
160
+ "FunctionName": { "Ref": "EventHandler" },
161
+ "BatchSize": 10,
162
+ "MaximumBatchingWindowInSeconds": 5,
163
+ "Enabled": true
164
+ }
165
+ }
166
+ },
167
+ "Outputs": {
168
+ "EventQueueArn": { "Value": { "Fn::GetAtt": ["EventQueue", "Arn"] } },
169
+ "EventQueueUrl": { "Value": { "Ref": "EventQueue" } },
170
+ "DeadLetterQueueUrl": { "Value": { "Ref": "DeadLetterQueue" } },
171
+ "LambdaFunctionArn": { "Value": { "Fn::GetAtt": ["EventHandler", "Arn"] } },
172
+ "EventRuleName": { "Value": { "Ref": "EventRule" } }
173
+ }
174
+ }