@aws/nx-plugin 1.1.1 → 1.3.0

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 (150) hide show
  1. package/README.md +31 -29
  2. package/generators.json +16 -0
  3. package/migrations.json +6 -1
  4. package/package.json +1 -1
  5. package/src/agentcore-harness/generator.d.ts +1 -1
  6. package/src/greengrass/component/common.d.ts +60 -0
  7. package/src/greengrass/component/common.js +191 -0
  8. package/src/greengrass/component/common.js.map +1 -0
  9. package/src/greengrass/component/dependencies.d.ts +101 -0
  10. package/src/greengrass/component/dependencies.js +37 -0
  11. package/src/greengrass/component/dependencies.js.map +1 -0
  12. package/src/greengrass/component/generator.d.ts +174 -0
  13. package/src/greengrass/component/generator.js +32 -0
  14. package/src/greengrass/component/generator.js.map +1 -0
  15. package/src/greengrass/component/metadata.d.ts +2 -0
  16. package/src/greengrass/component/metadata.js +14 -0
  17. package/src/greengrass/component/metadata.js.map +1 -0
  18. package/src/greengrass/component/python/__snapshots__/generator.spec.ts.snap +450 -0
  19. package/src/greengrass/component/python/files/component/main.py.template +60 -0
  20. package/src/greengrass/component/python/files/component/recipe.yaml.template +30 -0
  21. package/src/greengrass/component/python/files/gdk/gdk-config.json.template +13 -0
  22. package/src/greengrass/component/python/files/tests/test___componentSnakeCase__.py.template +37 -0
  23. package/src/greengrass/component/python/generator.d.ts +15 -0
  24. package/src/greengrass/component/python/generator.js +192 -0
  25. package/src/greengrass/component/python/generator.js.map +1 -0
  26. package/src/greengrass/component/schema.d.js +6 -0
  27. package/src/greengrass/component/schema.d.js.map +1 -0
  28. package/src/greengrass/component/schema.d.ts +18 -0
  29. package/src/greengrass/component/schema.json +82 -0
  30. package/src/greengrass/component/typescript/__snapshots__/generator.spec.ts.snap +275 -0
  31. package/src/greengrass/component/typescript/files/component/recipe.yaml.template +41 -0
  32. package/src/greengrass/component/typescript/files/entrypoint/main.ts.template +58 -0
  33. package/src/greengrass/component/typescript/files/gdk/gdk-config.json.template +13 -0
  34. package/src/greengrass/component/typescript/files/scripts/vendor-crt-addon.ts.template +115 -0
  35. package/src/greengrass/component/typescript/generator.d.ts +16 -0
  36. package/src/greengrass/component/typescript/generator.js +194 -0
  37. package/src/greengrass/component/typescript/generator.js.map +1 -0
  38. package/src/infra/app/generator.d.ts +1 -1
  39. package/src/init/generator.d.ts +1 -1
  40. package/src/internal/test-matrix/generator.js +48 -1
  41. package/src/internal/test-matrix/generator.js.map +1 -1
  42. package/src/license/dependency-check/pre-approved.js +2 -1
  43. package/src/license/dependency-check/pre-approved.js.map +1 -1
  44. package/src/migrations/latest/ts-agent-guardrails-terraform-version-retention/metadata.json +3 -0
  45. package/src/migrations/latest/ts-agent-guardrails-terraform-version-retention/migration.d.ts +6 -0
  46. package/src/migrations/latest/ts-agent-guardrails-terraform-version-retention/migration.js +118 -0
  47. package/src/migrations/latest/ts-agent-guardrails-terraform-version-retention/migration.js.map +1 -0
  48. package/src/open-api/py-client/generator.d.ts +1 -1
  49. package/src/preset/__snapshots__/generator.spec.ts.snap +4 -0
  50. package/src/preset/generator.d.ts +1 -1
  51. package/src/py/agent/a2a-connection/generator.d.ts +1 -1
  52. package/src/py/agent/gateway-connection/generator.d.ts +1 -1
  53. package/src/py/agent/guardrails/__snapshots__/generator.spec.ts.snap +3813 -0
  54. package/src/py/agent/guardrails/files/langchain/guardrail.py.template +135 -0
  55. package/src/py/agent/guardrails/files/strands/guardrail.py.template +93 -0
  56. package/src/py/agent/guardrails/generator.d.ts +10 -0
  57. package/src/py/agent/guardrails/generator.js +175 -0
  58. package/src/py/agent/guardrails/generator.js.map +1 -0
  59. package/src/py/agent/guardrails/runtime-check/run_checks.py +250 -0
  60. package/src/py/agent/guardrails/schema.d.js +6 -0
  61. package/src/py/agent/guardrails/schema.d.js.map +1 -0
  62. package/src/py/agent/guardrails/schema.d.ts +9 -0
  63. package/src/py/agent/guardrails/schema.json +25 -0
  64. package/src/py/agent/mcp-connection/generator.d.ts +1 -1
  65. package/src/py/agent/react-connection/generator.d.ts +1 -1
  66. package/src/py/fast-api/react/generator.d.ts +1 -1
  67. package/src/py/project/generator.js +2 -0
  68. package/src/py/project/generator.js.map +1 -1
  69. package/src/sdk/greengrass.d.ts +6 -0
  70. package/src/sdk/greengrass.js +6 -0
  71. package/src/sdk/greengrass.js.map +1 -0
  72. package/src/sdk/py.d.ts +2 -0
  73. package/src/sdk/py.js +1 -0
  74. package/src/sdk/py.js.map +1 -1
  75. package/src/smithy/project/generator.d.ts +1 -1
  76. package/src/smithy/react-connection/generator.d.ts +1 -1
  77. package/src/terraform/project/generator.d.ts +1 -1
  78. package/src/trpc/react/generator.d.ts +1 -1
  79. package/src/ts/agent/a2a-connection/generator.d.ts +1 -1
  80. package/src/ts/agent/gateway-connection/generator.d.ts +1 -1
  81. package/src/ts/agent/guardrails/__snapshots__/generator.spec.ts.snap +54 -3
  82. package/src/ts/agent/guardrails/generator.js +2 -1
  83. package/src/ts/agent/guardrails/generator.js.map +1 -1
  84. package/src/ts/agent/mcp-connection/generator.d.ts +1 -1
  85. package/src/ts/agent/react-connection/generator.d.ts +1 -1
  86. package/src/ts/astro-docs/generator.d.ts +1 -1
  87. package/src/ts/dcr-proxy/generator.d.ts +1 -1
  88. package/src/ts/dynamodb/generator.d.ts +1 -1
  89. package/src/ts/lambda-function/generator.d.ts +1 -1
  90. package/src/ts/lib/generator.d.ts +1 -1
  91. package/src/ts/nx-generator/generator.d.ts +1 -1
  92. package/src/ts/nx-migration/generator.d.ts +1 -1
  93. package/src/ts/nx-plugin/generator.d.ts +1 -1
  94. package/src/ts/rdb/generator.d.ts +1 -1
  95. package/src/ts/react-website/agui/generator.d.ts +1 -1
  96. package/src/utils/__snapshots__/shared-greengrass-scripts.spec.ts.snap +1279 -0
  97. package/src/utils/files/common/scripts/src/greengrass/build-artifact.ts.template +236 -0
  98. package/src/utils/files/common/scripts/src/greengrass/component-logs.ts.template +48 -0
  99. package/src/utils/files/common/scripts/src/greengrass/deploy-local.ts.template +497 -0
  100. package/src/utils/files/common/scripts/src/greengrass/recipe-utils.ts.template +356 -0
  101. package/src/utils/files/common/scripts/src/greengrass/zip-writer.ts.template +121 -0
  102. package/src/utils/files/terraform/src/core/runtime-config/read/read.tf.template +3 -3
  103. package/src/utils/files/terraform/src/metrics/metrics.tf.template +1 -1
  104. package/src/utils/greengrass/constants.d.ts +58 -0
  105. package/src/utils/greengrass/constants.js +63 -0
  106. package/src/utils/greengrass/constants.js.map +1 -0
  107. package/src/utils/greengrass/naming.d.ts +46 -0
  108. package/src/utils/greengrass/naming.js +67 -0
  109. package/src/utils/greengrass/naming.js.map +1 -0
  110. package/src/utils/greengrass/recipe.d.ts +143 -0
  111. package/src/utils/greengrass/recipe.js +119 -0
  112. package/src/utils/greengrass/recipe.js.map +1 -0
  113. package/src/utils/greengrass-constructs/files/cdk/app/greengrass-component/__componentDirName__.ts.template +42 -0
  114. package/src/utils/greengrass-constructs/files/cdk/core/greengrass/artifact-bucket.ts.template +82 -0
  115. package/src/utils/greengrass-constructs/files/cdk/core/greengrass/component-version.ts.template +370 -0
  116. package/src/utils/greengrass-constructs/files/cdk/core/greengrass/deployment.ts.template +276 -0
  117. package/src/utils/greengrass-constructs/files/cdk/core/greengrass/index.ts.template +5 -0
  118. package/src/utils/greengrass-constructs/files/cdk/core/greengrass/next-version.ts.template +119 -0
  119. package/src/utils/greengrass-constructs/files/cdk/core/greengrass/recipe.ts.template +90 -0
  120. package/src/utils/greengrass-constructs/files/next-version/__nextVersionFileName__.template +195 -0
  121. package/src/utils/greengrass-constructs/files/terraform/app/greengrass-component/__componentDirName__/__componentDirName__.tf.template +26 -0
  122. package/src/utils/greengrass-constructs/files/terraform/core/greengrass/artifact-bucket/main.tf.template +204 -0
  123. package/src/utils/greengrass-constructs/files/terraform/core/greengrass/component-version/main.tf.template +156 -0
  124. package/src/utils/greengrass-constructs/files/terraform/core/greengrass/deployment/main.tf.template +186 -0
  125. package/src/utils/greengrass-constructs/greengrass-constructs.d.ts +57 -0
  126. package/src/utils/greengrass-constructs/greengrass-constructs.js +154 -0
  127. package/src/utils/greengrass-constructs/greengrass-constructs.js.map +1 -0
  128. package/src/utils/guardrail-constructs/files/terraform/app/guardrails/__nameKebabCase__/__nameKebabCase__-guardrail.tf.template +18 -1
  129. package/src/utils/guardrail-constructs/guardrail-constructs.d.ts +5 -0
  130. package/src/utils/guardrail-constructs/guardrail-constructs.js +8 -0
  131. package/src/utils/guardrail-constructs/guardrail-constructs.js.map +1 -1
  132. package/src/utils/guardrail-constructs/guardrail-wiring.d.ts +7 -0
  133. package/src/utils/guardrail-constructs/guardrail-wiring.js +6 -5
  134. package/src/utils/guardrail-constructs/guardrail-wiring.js.map +1 -1
  135. package/src/utils/nx.d.ts +15 -0
  136. package/src/utils/nx.js +32 -0
  137. package/src/utils/nx.js.map +1 -1
  138. package/src/utils/shared-greengrass-scripts.d.ts +21 -0
  139. package/src/utils/shared-greengrass-scripts.js +43 -0
  140. package/src/utils/shared-greengrass-scripts.js.map +1 -0
  141. package/src/utils/test/fake-aws/fake_aws.py +205 -0
  142. package/src/utils/test/python-dependencies.d.ts +6 -0
  143. package/src/utils/test/python-dependencies.js +19 -0
  144. package/src/utils/test/python-dependencies.js.map +1 -1
  145. package/src/utils/test/warm-python-cache.js +5 -1
  146. package/src/utils/test/warm-python-cache.js.map +1 -1
  147. package/src/utils/versions.d.ts +7 -0
  148. package/src/utils/versions.js +7 -0
  149. package/src/utils/versions.js.map +1 -1
  150. /package/src/{ts/agent/guardrails/files/agent → utils/guardrail-constructs/files/policy}/guardrail.json.template +0 -0
@@ -0,0 +1,356 @@
1
+ import { readFileSync, readdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import yaml from 'js-yaml';
4
+
5
+ /**
6
+ * A narrow view of a Greengrass recipe: only the keys this vended toolchain
7
+ * reads. Every other key - including unknown lifecycle keys - is preserved
8
+ * untouched wherever a recipe is re-serialized, because the recipe is
9
+ * user-owned and this toolchain does not model the full recipe schema.
10
+ */
11
+ export type GreengrassRecipe = Record<string, unknown>;
12
+
13
+ export interface ResolvedComponent {
14
+ readonly componentName: string;
15
+ readonly componentVersion: string;
16
+ readonly recipe: GreengrassRecipe;
17
+ }
18
+
19
+ const COMPONENT_NAME_PATTERN = /^[a-zA-Z0-9-_.]+$/;
20
+ const RESERVED_COMPONENT_NAME_PREFIX = 'aws.greengrass.';
21
+ const MAX_COMPONENT_NAME_LENGTH = 128;
22
+ // https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
23
+ const SEMVER_PATTERN =
24
+ /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
25
+ const STRICT_COMPONENT_VERSION_PATTERN =
26
+ /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
27
+ const DECOMPRESSED_PATH_TOKEN = '{artifacts:decompressedPath}';
28
+
29
+ export const NEXT_VERSION_SENTINELS = [
30
+ 'NEXT_PATCH',
31
+ 'NEXT_MINOR',
32
+ 'NEXT_MAJOR',
33
+ ] as const;
34
+
35
+ export type NextVersionSentinel = (typeof NEXT_VERSION_SENTINELS)[number];
36
+
37
+ /** Whether a value requests local deploy-time component version resolution. */
38
+ export function isNextVersionSentinel(
39
+ value: unknown,
40
+ ): value is NextVersionSentinel {
41
+ return (
42
+ typeof value === 'string' &&
43
+ NEXT_VERSION_SENTINELS.includes(value as NextVersionSentinel)
44
+ );
45
+ }
46
+
47
+ /**
48
+ * Resolves a version sentinel on a device: bumps the highest strict M.m.p
49
+ * version among the installed version and the candidate versions the caller
50
+ * collected from the nucleus's package stores, or returns 1.0.0 when there is
51
+ * none. A candidate that is not strict M.m.p is ignored. An installed version
52
+ * that is not strict M.m.p is an error, because the sentinel cannot bump it.
53
+ */
54
+ export function resolveLocalNextVersion(
55
+ installedVersion: string | undefined,
56
+ candidateVersions: readonly string[],
57
+ sentinel: NextVersionSentinel,
58
+ ): string {
59
+ if (
60
+ installedVersion !== undefined &&
61
+ !STRICT_COMPONENT_VERSION_PATTERN.test(installedVersion)
62
+ ) {
63
+ throw new Error(
64
+ `Installed component version "${installedVersion}" is not a strict M.m.p version, so ${sentinel} cannot resolve it.`,
65
+ );
66
+ }
67
+
68
+ const highest = [...candidateVersions, installedVersion]
69
+ .filter(
70
+ (version): version is string =>
71
+ version !== undefined && STRICT_COMPONENT_VERSION_PATTERN.test(version),
72
+ )
73
+ .reduce<string | undefined>((currentHighest, candidate) => {
74
+ if (currentHighest === undefined) {
75
+ return candidate;
76
+ }
77
+ const current = STRICT_COMPONENT_VERSION_PATTERN.exec(currentHighest)!;
78
+ const next = STRICT_COMPONENT_VERSION_PATTERN.exec(candidate)!;
79
+ for (let index = 1; index <= 3; index++) {
80
+ const difference = BigInt(next[index]) - BigInt(current[index]);
81
+ if (difference !== 0n) {
82
+ return difference > 0n ? candidate : currentHighest;
83
+ }
84
+ }
85
+ return currentHighest;
86
+ }, undefined);
87
+ if (highest === undefined) {
88
+ return '1.0.0';
89
+ }
90
+
91
+ const [, major, minor, patch] =
92
+ STRICT_COMPONENT_VERSION_PATTERN.exec(highest)!;
93
+ switch (sentinel) {
94
+ case 'NEXT_PATCH':
95
+ return `${major}.${minor}.${(BigInt(patch) + 1n).toString()}`;
96
+ case 'NEXT_MINOR':
97
+ return `${major}.${(BigInt(minor) + 1n).toString()}.0`;
98
+ case 'NEXT_MAJOR':
99
+ return `${(BigInt(major) + 1n).toString()}.0.0`;
100
+ }
101
+ }
102
+
103
+ /** Reads and parses a recipe.yaml. Throws with a clear message on failure. */
104
+ export function loadRecipe(recipePath: string): GreengrassRecipe {
105
+ let raw: string;
106
+ try {
107
+ raw = readFileSync(recipePath, 'utf-8');
108
+ } catch {
109
+ throw new Error(`Cannot read recipe at "${recipePath}"`);
110
+ }
111
+ const parsed = yaml.load(raw);
112
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
113
+ throw new Error(`"${recipePath}" is not a Greengrass recipe document`);
114
+ }
115
+ return parsed as GreengrassRecipe;
116
+ }
117
+
118
+ /**
119
+ * Validates only the recipe fields this vended toolchain consumes:
120
+ * `ComponentName`, `ComponentVersion`, and, for every entry in
121
+ * `artifactBaseNames`, the `{artifacts:decompressedPath}` reference the
122
+ * local-deploy lifecycle script must carry so that a deployed component can
123
+ * find its own artifact once Greengrass decompresses it.
124
+ *
125
+ * Throws a single `Error` listing every violation when the recipe is invalid.
126
+ */
127
+ export function validateRecipe(
128
+ recipe: GreengrassRecipe,
129
+ artifactBaseNames: readonly string[],
130
+ ): ResolvedComponent {
131
+ const errors: string[] = [];
132
+
133
+ const componentName = recipe.ComponentName;
134
+ if (typeof componentName !== 'string' || componentName.length === 0) {
135
+ errors.push('ComponentName is required and must be a non-empty string');
136
+ } else {
137
+ if (componentName.length > MAX_COMPONENT_NAME_LENGTH) {
138
+ errors.push(
139
+ `ComponentName must be at most ${MAX_COMPONENT_NAME_LENGTH} characters, got ${componentName.length}`,
140
+ );
141
+ }
142
+ if (!COMPONENT_NAME_PATTERN.test(componentName)) {
143
+ errors.push(
144
+ 'ComponentName may only contain letters, numbers, "-", "_" and "."',
145
+ );
146
+ }
147
+ if (componentName.startsWith(RESERVED_COMPONENT_NAME_PREFIX)) {
148
+ errors.push(
149
+ `ComponentName must not start with the reserved prefix "${RESERVED_COMPONENT_NAME_PREFIX}"`,
150
+ );
151
+ }
152
+ }
153
+
154
+ const componentVersion = recipe.ComponentVersion;
155
+ if (
156
+ typeof componentVersion !== 'string' ||
157
+ (!SEMVER_PATTERN.test(componentVersion) &&
158
+ !isNextVersionSentinel(componentVersion))
159
+ ) {
160
+ errors.push(
161
+ 'ComponentVersion is required and must be a valid semver version (e.g. "1.0.0") or one of NEXT_PATCH, NEXT_MINOR, NEXT_MAJOR',
162
+ );
163
+ }
164
+
165
+ for (const baseName of artifactBaseNames) {
166
+ if (!referencesDecompressedComponentDir(recipe, baseName)) {
167
+ errors.push(
168
+ `No lifecycle script references "${DECOMPRESSED_PATH_TOKEN}/${baseName}/" - ` +
169
+ `the Run script path must match the artifact zip base name ("${baseName}.zip")`,
170
+ );
171
+ }
172
+ if (!declaresComponentZipArtifact(recipe, baseName)) {
173
+ errors.push(
174
+ `No manifest declares an artifact whose Uri ends in "/${baseName}.zip" - ` +
175
+ 'Greengrass only unarchives a declared artifact, so ' +
176
+ `"${DECOMPRESSED_PATH_TOKEN}/${baseName}/" would never resolve on the device`,
177
+ );
178
+ }
179
+ }
180
+
181
+ if (errors.length > 0) {
182
+ throw new Error(
183
+ `Invalid recipe:\n${errors.map((e) => ` - ${e}`).join('\n')}`,
184
+ );
185
+ }
186
+
187
+ return {
188
+ componentName: componentName as string,
189
+ componentVersion: componentVersion as string,
190
+ recipe,
191
+ };
192
+ }
193
+
194
+ /**
195
+ * Recursively searches every string value for a decompressedPath reference to
196
+ * `componentDirName`, wherever the recipe places its Lifecycle block (top
197
+ * level or per-manifest).
198
+ */
199
+ function referencesDecompressedComponentDir(
200
+ value: unknown,
201
+ componentDirName: string,
202
+ ): boolean {
203
+ const needle = `${DECOMPRESSED_PATH_TOKEN}/${componentDirName}/`;
204
+ if (typeof value === 'string') {
205
+ return value.includes(needle);
206
+ }
207
+ if (Array.isArray(value)) {
208
+ return value.some((entry) =>
209
+ referencesDecompressedComponentDir(entry, componentDirName),
210
+ );
211
+ }
212
+ if (value !== null && typeof value === 'object') {
213
+ return Object.values(value).some((entry) =>
214
+ referencesDecompressedComponentDir(entry, componentDirName),
215
+ );
216
+ }
217
+ return false;
218
+ }
219
+
220
+ /**
221
+ * Whether any manifest declares an artifact whose Uri basename is
222
+ * `<componentDirName>.zip`, which is the name the artifact this toolchain
223
+ * writes actually carries.
224
+ */
225
+ function declaresComponentZipArtifact(
226
+ recipe: GreengrassRecipe,
227
+ componentDirName: string,
228
+ ): boolean {
229
+ const manifests = Array.isArray(recipe.Manifests) ? recipe.Manifests : [];
230
+ return manifests.some((manifest) =>
231
+ manifestDeclaresZipArtifact(manifest, componentDirName),
232
+ );
233
+ }
234
+
235
+ /**
236
+ * Whether one manifest declares an artifact whose Uri basename is
237
+ * `<componentDirName>.zip`. Case-insensitive on the key so a recipe authored
238
+ * with `URI` (as some AWS samples spell it) still matches.
239
+ */
240
+ function manifestDeclaresZipArtifact(
241
+ manifest: unknown,
242
+ componentDirName: string,
243
+ ): boolean {
244
+ const suffix = `/${componentDirName}.zip`;
245
+ const artifacts = (manifest as { Artifacts?: unknown })?.Artifacts;
246
+ if (!Array.isArray(artifacts)) {
247
+ return false;
248
+ }
249
+ return artifacts.some((artifact) => {
250
+ if (artifact === null || typeof artifact !== 'object') {
251
+ return false;
252
+ }
253
+ return Object.entries(artifact as Record<string, unknown>).some(
254
+ ([key, value]) =>
255
+ key.toLowerCase() === 'uri' &&
256
+ typeof value === 'string' &&
257
+ value.endsWith(suffix),
258
+ );
259
+ });
260
+ }
261
+
262
+ /**
263
+ * Multi-architecture only: checks that every manifest whose
264
+ * `Platform.architecture` names an architecture this build vendored pairs with
265
+ * THAT architecture's own artifact, both in its `Artifacts` and in the
266
+ * lifecycle that unarchives it.
267
+ *
268
+ * {@link validateRecipe}'s checks are global - they prove every built zip is
269
+ * declared and referenced somewhere in the recipe. A recipe that swaps two
270
+ * manifests' artifacts and Run paths satisfies them, and a swap sends one
271
+ * architecture's native wheels to the other, which fails only on the device.
272
+ * A swap is one paste mistake away, because adding a second manifest to an
273
+ * existing component is a hand edit of a user-owned recipe.
274
+ *
275
+ * A manifest with no `Platform.architecture`, or one naming an architecture
276
+ * this build did not vendor, is left alone: a recipe may carry manifests this
277
+ * toolchain does not build.
278
+ */
279
+ export function validateManifestArchitectures(
280
+ recipe: GreengrassRecipe,
281
+ baseNameByArchitecture: Readonly<Record<string, string>>,
282
+ ): void {
283
+ const errors: string[] = [];
284
+ const manifests = Array.isArray(recipe.Manifests) ? recipe.Manifests : [];
285
+ manifests.forEach((manifest, index) => {
286
+ const architecture = (
287
+ manifest as { Platform?: { architecture?: unknown } } | null
288
+ )?.Platform?.architecture;
289
+ if (typeof architecture !== 'string') {
290
+ return;
291
+ }
292
+ const baseName = baseNameByArchitecture[architecture];
293
+ if (!baseName) {
294
+ return;
295
+ }
296
+ if (!manifestDeclaresZipArtifact(manifest, baseName)) {
297
+ errors.push(
298
+ `Manifest ${index + 1} declares "architecture: ${architecture}", so its own artifact Uri must end in "/${baseName}.zip" - ` +
299
+ `that is the zip this build writes for "${architecture}". Manifests must not carry another architecture's artifact`,
300
+ );
301
+ }
302
+ const lifecycle =
303
+ (manifest as { Lifecycle?: unknown }).Lifecycle ?? recipe.Lifecycle;
304
+ if (!referencesDecompressedComponentDir(lifecycle, baseName)) {
305
+ errors.push(
306
+ `Manifest ${index + 1} declares "architecture: ${architecture}", so its lifecycle must reference ` +
307
+ `"${DECOMPRESSED_PATH_TOKEN}/${baseName}/" - the directory that architecture's own artifact unarchives to`,
308
+ );
309
+ }
310
+ });
311
+
312
+ if (errors.length > 0) {
313
+ throw new Error(
314
+ `Invalid recipe:\n${errors.map((e) => ` - ${e}`).join('\n')}`,
315
+ );
316
+ }
317
+ }
318
+
319
+ /**
320
+ * Reads the single resolved recipe a prior `-artifact` run wrote under a
321
+ * `greengrass-build/recipes` directory, for scripts that only need to know
322
+ * which component and version were last built.
323
+ */
324
+ export function readResolvedRecipe(recipesDir: string): ResolvedComponent {
325
+ let files: string[];
326
+ try {
327
+ files = readdirSync(recipesDir).filter(
328
+ (f) => f.endsWith('.yaml') || f.endsWith('.yml'),
329
+ );
330
+ } catch {
331
+ throw new Error(
332
+ `No resolved recipes found at "${recipesDir}" - run the component's "-artifact" target first`,
333
+ );
334
+ }
335
+ if (files.length !== 1) {
336
+ throw new Error(
337
+ `Expected exactly one resolved recipe in "${recipesDir}", found ${files.length}`,
338
+ );
339
+ }
340
+ const recipePath = join(recipesDir, files[0]);
341
+ const recipe = loadRecipe(recipePath);
342
+ const { ComponentName, ComponentVersion } = recipe;
343
+ if (
344
+ typeof ComponentName !== 'string' ||
345
+ typeof ComponentVersion !== 'string'
346
+ ) {
347
+ throw new Error(
348
+ `"${recipePath}" is missing ComponentName/ComponentVersion`,
349
+ );
350
+ }
351
+ return {
352
+ componentName: ComponentName,
353
+ componentVersion: ComponentVersion,
354
+ recipe,
355
+ };
356
+ }
@@ -0,0 +1,121 @@
1
+ import { writeFileSync } from 'node:fs';
2
+ // crc32 from node:zlib requires Node >= 20.15 / 22.2.
3
+ import { crc32, deflateRawSync } from 'node:zlib';
4
+
5
+ /** One file to place in the archive, at a forward-slash relative path. */
6
+ export interface ZipEntry {
7
+ readonly name: string;
8
+ readonly data: Buffer;
9
+ }
10
+
11
+ const LOCAL_FILE_HEADER_SIGNATURE = 0x04034b50;
12
+ const CENTRAL_DIRECTORY_SIGNATURE = 0x02014b50;
13
+ const END_OF_CENTRAL_DIRECTORY_SIGNATURE = 0x06054b50;
14
+ // 20 = version 2.0, the lowest version that documents DEFLATE support.
15
+ const VERSION_NEEDED = 20;
16
+ const DEFLATE_METHOD = 8;
17
+ const STORED_METHOD = 0;
18
+ const MAX_ENTRIES = 65_535;
19
+
20
+ /**
21
+ * Fixed MS-DOS timestamp fields (1980-01-01 00:00:00, the DOS epoch).
22
+ *
23
+ * The archive must be byte-reproducible: its sha256 keys the artifact's S3
24
+ * object and is substituted into the recipe, so a rebuild that produced
25
+ * different bytes for identical content would make CloudFormation try to
26
+ * replace an already-published, immutable component version and fail with
27
+ * AlreadyExists. Stamping the build time would do exactly that on every
28
+ * rebuild.
29
+ */
30
+ const DOS_EPOCH_TIME = 0;
31
+ const DOS_EPOCH_DATE = (1 << 5) | 1;
32
+
33
+ /**
34
+ * Writes `entries` to a plain ZIP archive at `outPath` with no external
35
+ * dependency - only `node:zlib` for DEFLATE and CRC-32. Supports exactly what
36
+ * Greengrass artifact packaging needs: a flat set of files, no encryption, no
37
+ * zip64 (component artifacts are small). Cross-platform: no shell-out to a
38
+ * system `zip` binary.
39
+ */
40
+ export function writeZip(entries: readonly ZipEntry[], outPath: string): void {
41
+ if (entries.length > MAX_ENTRIES) {
42
+ throw new RangeError(
43
+ `ZIP64 is not supported; this ZIP writer can store at most 65,535 entries, received ${entries.length}.`,
44
+ );
45
+ }
46
+
47
+ const fileChunks: Buffer[] = [];
48
+ const centralRecords: Buffer[] = [];
49
+ let offset = 0;
50
+ const time = DOS_EPOCH_TIME;
51
+ const date = DOS_EPOCH_DATE;
52
+
53
+ // Sorted so directory iteration order cannot change the archive's bytes.
54
+ const sortedEntries = [...entries].sort((a, b) =>
55
+ a.name < b.name ? -1 : a.name > b.name ? 1 : 0,
56
+ );
57
+ for (const entry of sortedEntries) {
58
+ const nameBuf = Buffer.from(entry.name, 'utf-8');
59
+ const entryCrc32 = crc32(entry.data) >>> 0;
60
+ const compressed = deflateRawSync(entry.data);
61
+ const useDeflate = compressed.length < entry.data.length;
62
+ const method = useDeflate ? DEFLATE_METHOD : STORED_METHOD;
63
+ const payload = useDeflate ? compressed : entry.data;
64
+
65
+ const localHeader = Buffer.alloc(30);
66
+ localHeader.writeUInt32LE(LOCAL_FILE_HEADER_SIGNATURE, 0);
67
+ localHeader.writeUInt16LE(VERSION_NEEDED, 4);
68
+ localHeader.writeUInt16LE(0, 6); // general purpose flags
69
+ localHeader.writeUInt16LE(method, 8);
70
+ localHeader.writeUInt16LE(time, 10);
71
+ localHeader.writeUInt16LE(date, 12);
72
+ localHeader.writeUInt32LE(entryCrc32, 14);
73
+ localHeader.writeUInt32LE(payload.length, 18);
74
+ localHeader.writeUInt32LE(entry.data.length, 22);
75
+ localHeader.writeUInt16LE(nameBuf.length, 26);
76
+ localHeader.writeUInt16LE(0, 28); // extra field length
77
+
78
+ fileChunks.push(localHeader, nameBuf, payload);
79
+
80
+ const centralHeader = Buffer.alloc(46);
81
+ centralHeader.writeUInt32LE(CENTRAL_DIRECTORY_SIGNATURE, 0);
82
+ centralHeader.writeUInt16LE(VERSION_NEEDED, 4); // version made by
83
+ centralHeader.writeUInt16LE(VERSION_NEEDED, 6); // version needed to extract
84
+ centralHeader.writeUInt16LE(0, 8); // general purpose flags
85
+ centralHeader.writeUInt16LE(method, 10);
86
+ centralHeader.writeUInt16LE(time, 12);
87
+ centralHeader.writeUInt16LE(date, 14);
88
+ centralHeader.writeUInt32LE(entryCrc32, 16);
89
+ centralHeader.writeUInt32LE(payload.length, 20);
90
+ centralHeader.writeUInt32LE(entry.data.length, 24);
91
+ centralHeader.writeUInt16LE(nameBuf.length, 28);
92
+ centralHeader.writeUInt16LE(0, 30); // extra field length
93
+ centralHeader.writeUInt16LE(0, 32); // file comment length
94
+ centralHeader.writeUInt16LE(0, 34); // disk number start
95
+ centralHeader.writeUInt16LE(0, 36); // internal file attributes
96
+ centralHeader.writeUInt32LE(0, 38); // external file attributes
97
+ centralHeader.writeUInt32LE(offset, 42); // relative offset of local header
98
+
99
+ centralRecords.push(centralHeader, nameBuf);
100
+
101
+ offset += localHeader.length + nameBuf.length + payload.length;
102
+ }
103
+
104
+ const centralDirectory = Buffer.concat(centralRecords);
105
+ const centralDirectoryOffset = offset;
106
+
107
+ const endRecord = Buffer.alloc(22);
108
+ endRecord.writeUInt32LE(END_OF_CENTRAL_DIRECTORY_SIGNATURE, 0);
109
+ endRecord.writeUInt16LE(0, 4); // disk number
110
+ endRecord.writeUInt16LE(0, 6); // disk with central directory start
111
+ endRecord.writeUInt16LE(entries.length, 8); // entries on this disk
112
+ endRecord.writeUInt16LE(entries.length, 10); // total entries
113
+ endRecord.writeUInt32LE(centralDirectory.length, 12);
114
+ endRecord.writeUInt32LE(centralDirectoryOffset, 16);
115
+ endRecord.writeUInt16LE(0, 20); // comment length
116
+
117
+ writeFileSync(
118
+ outPath,
119
+ Buffer.concat([...fileChunks, centralDirectory, endRecord]),
120
+ );
121
+ }
@@ -18,9 +18,9 @@ variable "namespace" {
18
18
  }
19
19
 
20
20
  locals {
21
- config_dir = "${path.module}/../../../../../../../dist/packages/common/terraform/runtime-config"
22
- entries_dir = "${local.config_dir}/entries/${var.namespace}"
23
- namespace_path = "${local.config_dir}/${var.namespace}.json"
21
+ config_dir = "${path.module}/../../../../../../../dist/packages/common/terraform/runtime-config"
22
+ entries_dir = "${local.config_dir}/entries/${var.namespace}"
23
+ namespace_path = "${local.config_dir}/${var.namespace}.json"
24
24
  }
25
25
 
26
26
  # Aggregate leaf entry files into the namespace JSON on every apply. The
@@ -17,7 +17,7 @@ resource "aws_cloudformation_stack" "metrics" {
17
17
 
18
18
  template_body = jsonencode({
19
19
  AWSTemplateFormatVersion = "2010-09-09"
20
- Description = "(${local.metric_id}) (version:${local.metric_version}) (tag:${join(",", local.metric_tags)})"
20
+ Description = "(${local.metric_id}) (version:${local.metric_version}) (tag:${join(",", local.metric_tags)})"
21
21
  Resources = {
22
22
  # Empty stack - used only for metrics tracking via description
23
23
  MetricsPlaceholder = {
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ /**
6
+ * The `RecipeFormatVersion` a Greengrass v2 recipe declares. Fixed by the
7
+ * service since the format's introduction; there is no later version to
8
+ * migrate to.
9
+ */
10
+ export declare const RECIPE_FORMAT_VERSION: "2020-01-25";
11
+ /**
12
+ * Component platforms the generator supports. Windows and `linux-any` are
13
+ * deferred. A recipe may carry one manifest per platform - see
14
+ * `GREENGRASS_PLATFORM_SELECTIONS`.
15
+ */
16
+ export declare const GREENGRASS_PLATFORMS: readonly ["linux-amd64", "linux-arm64"];
17
+ export type GreengrassPlatform = (typeof GREENGRASS_PLATFORMS)[number];
18
+ /**
19
+ * `Platform.os` / `Platform.architecture` values written into a recipe manifest.
20
+ * The nucleus matches `aarch64` (not the Docker-style `arm64`) on 64-bit ARM
21
+ * devices - verified against a real core device reporting
22
+ * `{os=linux, architecture=aarch64}`.
23
+ */
24
+ export interface GreengrassManifestPlatform {
25
+ readonly os: 'linux';
26
+ readonly architecture: 'amd64' | 'aarch64';
27
+ }
28
+ /** How a {@link GreengrassPlatform} maps onto a `uv` target platform and a manifest `Platform` block. */
29
+ export interface GreengrassPlatformMapping {
30
+ /** Value for `uv`'s `--python-platform` flag when vendoring dependencies. */
31
+ readonly uvPlatform: 'x86_64-manylinux_2_28' | 'aarch64-manylinux_2_28';
32
+ /**
33
+ * The `dist/bin/<platform>-<arch>-<cRuntime>` directory name aws-crt uses for
34
+ * its prebuilt Node addon on this platform. Node's arch token (`x64`,
35
+ * `arm64`) is not the manifest token (`amd64`, `aarch64`), so both live in one
36
+ * entry instead of being derived from each other. glibc only - a musl device
37
+ * needs the escape hatch the guide documents.
38
+ */
39
+ readonly nodeCrtAddonDir: 'linux-x64-glibc' | 'linux-arm64-glibc';
40
+ readonly manifestPlatform: GreengrassManifestPlatform;
41
+ }
42
+ /**
43
+ * Per-platform `uv` target and recipe manifest `Platform` block. Mirrors the
44
+ * platform strings `addPythonBundleTarget` uses for Lambda, but this map is
45
+ * not shared with it: a device's Python floor is not a Lambda runtime.
46
+ */
47
+ export declare const GREENGRASS_PLATFORM_MAPPINGS: Readonly<Record<GreengrassPlatform, GreengrassPlatformMapping>>;
48
+ /**
49
+ * What a component generator's `platform` option accepts: one architecture, or
50
+ * the explicit pair. Deliberately not a `multi` value that means "every
51
+ * supported architecture" - that would silently change what an existing
52
+ * invocation produces the day a third architecture lands.
53
+ */
54
+ export declare const GREENGRASS_PLATFORM_SELECTIONS: readonly ["linux-amd64", "linux-arm64", "linux-amd64-arm64"];
55
+ export type GreengrassPlatformSelection = (typeof GREENGRASS_PLATFORM_SELECTIONS)[number];
56
+ /** The platforms each selection expands to, in manifest order. */
57
+ export declare const GREENGRASS_SELECTION_PLATFORMS: Readonly<Record<GreengrassPlatformSelection, readonly GreengrassPlatform[]>>;
58
+ export declare const resolvePlatforms: (selection: GreengrassPlatformSelection) => readonly GreengrassPlatform[];
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */ /**
5
+ * The `RecipeFormatVersion` a Greengrass v2 recipe declares. Fixed by the
6
+ * service since the format's introduction; there is no later version to
7
+ * migrate to.
8
+ */ export const RECIPE_FORMAT_VERSION = '2020-01-25';
9
+ /**
10
+ * Component platforms the generator supports. Windows and `linux-any` are
11
+ * deferred. A recipe may carry one manifest per platform - see
12
+ * `GREENGRASS_PLATFORM_SELECTIONS`.
13
+ */ export const GREENGRASS_PLATFORMS = [
14
+ 'linux-amd64',
15
+ 'linux-arm64'
16
+ ];
17
+ /**
18
+ * Per-platform `uv` target and recipe manifest `Platform` block. Mirrors the
19
+ * platform strings `addPythonBundleTarget` uses for Lambda, but this map is
20
+ * not shared with it: a device's Python floor is not a Lambda runtime.
21
+ */ export const GREENGRASS_PLATFORM_MAPPINGS = {
22
+ 'linux-amd64': {
23
+ uvPlatform: 'x86_64-manylinux_2_28',
24
+ nodeCrtAddonDir: 'linux-x64-glibc',
25
+ manifestPlatform: {
26
+ os: 'linux',
27
+ architecture: 'amd64'
28
+ }
29
+ },
30
+ 'linux-arm64': {
31
+ uvPlatform: 'aarch64-manylinux_2_28',
32
+ nodeCrtAddonDir: 'linux-arm64-glibc',
33
+ manifestPlatform: {
34
+ os: 'linux',
35
+ architecture: 'aarch64'
36
+ }
37
+ }
38
+ };
39
+ /**
40
+ * What a component generator's `platform` option accepts: one architecture, or
41
+ * the explicit pair. Deliberately not a `multi` value that means "every
42
+ * supported architecture" - that would silently change what an existing
43
+ * invocation produces the day a third architecture lands.
44
+ */ export const GREENGRASS_PLATFORM_SELECTIONS = [
45
+ 'linux-amd64',
46
+ 'linux-arm64',
47
+ 'linux-amd64-arm64'
48
+ ];
49
+ /** The platforms each selection expands to, in manifest order. */ export const GREENGRASS_SELECTION_PLATFORMS = {
50
+ 'linux-amd64': [
51
+ 'linux-amd64'
52
+ ],
53
+ 'linux-arm64': [
54
+ 'linux-arm64'
55
+ ],
56
+ 'linux-amd64-arm64': [
57
+ 'linux-amd64',
58
+ 'linux-arm64'
59
+ ]
60
+ };
61
+ export const resolvePlatforms = (selection)=>GREENGRASS_SELECTION_PLATFORMS[selection];
62
+
63
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../packages/nx-plugin/src/utils/greengrass/constants.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\n\n/**\n * The `RecipeFormatVersion` a Greengrass v2 recipe declares. Fixed by the\n * service since the format's introduction; there is no later version to\n * migrate to.\n */\nexport const RECIPE_FORMAT_VERSION = '2020-01-25' as const;\n\n/**\n * Component platforms the generator supports. Windows and `linux-any` are\n * deferred. A recipe may carry one manifest per platform - see\n * `GREENGRASS_PLATFORM_SELECTIONS`.\n */\nexport const GREENGRASS_PLATFORMS = ['linux-amd64', 'linux-arm64'] as const;\n\nexport type GreengrassPlatform = (typeof GREENGRASS_PLATFORMS)[number];\n\n/**\n * `Platform.os` / `Platform.architecture` values written into a recipe manifest.\n * The nucleus matches `aarch64` (not the Docker-style `arm64`) on 64-bit ARM\n * devices - verified against a real core device reporting\n * `{os=linux, architecture=aarch64}`.\n */\nexport interface GreengrassManifestPlatform {\n readonly os: 'linux';\n readonly architecture: 'amd64' | 'aarch64';\n}\n\n/** How a {@link GreengrassPlatform} maps onto a `uv` target platform and a manifest `Platform` block. */\nexport interface GreengrassPlatformMapping {\n /** Value for `uv`'s `--python-platform` flag when vendoring dependencies. */\n readonly uvPlatform: 'x86_64-manylinux_2_28' | 'aarch64-manylinux_2_28';\n /**\n * The `dist/bin/<platform>-<arch>-<cRuntime>` directory name aws-crt uses for\n * its prebuilt Node addon on this platform. Node's arch token (`x64`,\n * `arm64`) is not the manifest token (`amd64`, `aarch64`), so both live in one\n * entry instead of being derived from each other. glibc only - a musl device\n * needs the escape hatch the guide documents.\n */\n readonly nodeCrtAddonDir: 'linux-x64-glibc' | 'linux-arm64-glibc';\n readonly manifestPlatform: GreengrassManifestPlatform;\n}\n\n/**\n * Per-platform `uv` target and recipe manifest `Platform` block. Mirrors the\n * platform strings `addPythonBundleTarget` uses for Lambda, but this map is\n * not shared with it: a device's Python floor is not a Lambda runtime.\n */\nexport const GREENGRASS_PLATFORM_MAPPINGS: Readonly<\n Record<GreengrassPlatform, GreengrassPlatformMapping>\n> = {\n 'linux-amd64': {\n uvPlatform: 'x86_64-manylinux_2_28',\n nodeCrtAddonDir: 'linux-x64-glibc',\n manifestPlatform: { os: 'linux', architecture: 'amd64' },\n },\n 'linux-arm64': {\n uvPlatform: 'aarch64-manylinux_2_28',\n nodeCrtAddonDir: 'linux-arm64-glibc',\n manifestPlatform: { os: 'linux', architecture: 'aarch64' },\n },\n} as const;\n\n/**\n * What a component generator's `platform` option accepts: one architecture, or\n * the explicit pair. Deliberately not a `multi` value that means \"every\n * supported architecture\" - that would silently change what an existing\n * invocation produces the day a third architecture lands.\n */\nexport const GREENGRASS_PLATFORM_SELECTIONS = [\n 'linux-amd64',\n 'linux-arm64',\n 'linux-amd64-arm64',\n] as const;\n\nexport type GreengrassPlatformSelection =\n (typeof GREENGRASS_PLATFORM_SELECTIONS)[number];\n\n/** The platforms each selection expands to, in manifest order. */\nexport const GREENGRASS_SELECTION_PLATFORMS: Readonly<\n Record<GreengrassPlatformSelection, readonly GreengrassPlatform[]>\n> = {\n 'linux-amd64': ['linux-amd64'],\n 'linux-arm64': ['linux-arm64'],\n 'linux-amd64-arm64': ['linux-amd64', 'linux-arm64'],\n} as const;\n\nexport const resolvePlatforms = (\n selection: GreengrassPlatformSelection,\n): readonly GreengrassPlatform[] => GREENGRASS_SELECTION_PLATFORMS[selection];\n"],"names":["RECIPE_FORMAT_VERSION","GREENGRASS_PLATFORMS","GREENGRASS_PLATFORM_MAPPINGS","uvPlatform","nodeCrtAddonDir","manifestPlatform","os","architecture","GREENGRASS_PLATFORM_SELECTIONS","GREENGRASS_SELECTION_PLATFORMS","resolvePlatforms","selection"],"mappings":"AAAA;;;CAGC,GAED;;;;CAIC,GACD,OAAO,MAAMA,wBAAwB,aAAsB;AAE3D;;;;CAIC,GACD,OAAO,MAAMC,uBAAuB;IAAC;IAAe;CAAc,CAAU;AA8B5E;;;;CAIC,GACD,OAAO,MAAMC,+BAET;IACF,eAAe;QACbC,YAAY;QACZC,iBAAiB;QACjBC,kBAAkB;YAAEC,IAAI;YAASC,cAAc;QAAQ;IACzD;IACA,eAAe;QACbJ,YAAY;QACZC,iBAAiB;QACjBC,kBAAkB;YAAEC,IAAI;YAASC,cAAc;QAAU;IAC3D;AACF,EAAW;AAEX;;;;;CAKC,GACD,OAAO,MAAMC,iCAAiC;IAC5C;IACA;IACA;CACD,CAAU;AAKX,gEAAgE,GAChE,OAAO,MAAMC,iCAET;IACF,eAAe;QAAC;KAAc;IAC9B,eAAe;QAAC;KAAc;IAC9B,qBAAqB;QAAC;QAAe;KAAc;AACrD,EAAW;AAEX,OAAO,MAAMC,mBAAmB,CAC9BC,YACkCF,8BAA8B,CAACE,UAAU,CAAC"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ import type { Tree } from '@nx/devkit';
6
+ /**
7
+ * Characters a Greengrass v2 `ComponentName` may contain. Matches the
8
+ * `CreateComponentVersion` API's own constraint.
9
+ */
10
+ export declare const COMPONENT_NAME_PATTERN: RegExp;
11
+ /** Longest `ComponentName` the `CreateComponentVersion` API accepts. */
12
+ export declare const MAX_COMPONENT_NAME_LENGTH = 128;
13
+ /** Longest name that fits the CDK artifact prefix with its 32-character hash. */
14
+ export declare const MAX_CDK_COMPONENT_NAME_LENGTH = 68;
15
+ /**
16
+ * Prefix AWS reserves for its own public components. A user-authored
17
+ * `ComponentName` starting with this is rejected by the service, so the
18
+ * plugin rejects it up front with an actionable message instead.
19
+ */
20
+ export declare const RESERVED_COMPONENT_NAME_PREFIX = "aws.greengrass.";
21
+ /** Why a `ComponentName` failed validation. */
22
+ export type ComponentNameIssueReason = 'invalid-characters' | 'too-long' | 'reserved-prefix';
23
+ export interface ComponentNameIssue {
24
+ readonly reason: ComponentNameIssueReason;
25
+ readonly message: string;
26
+ }
27
+ /**
28
+ * Validate a `ComponentName` against the constraints the service enforces:
29
+ * the allowed character set, the length limit, and the reserved
30
+ * `aws.greengrass.` prefix. Returns one issue per violated constraint, empty
31
+ * when the name is valid.
32
+ */
33
+ export declare const validateComponentName: (componentName: string) => ComponentNameIssue[];
34
+ /**
35
+ * Validate a `ComponentName`, throwing an actionable error naming every
36
+ * violated constraint when it is invalid.
37
+ */
38
+ export declare const assertValidComponentName: (componentName: string) => void;
39
+ /**
40
+ * Build the default `ComponentName` for a new component: `com.<sanitized
41
+ * workspace npm scope>.<PascalCaseName>`, the reversed-domain style the
42
+ * service's own samples use. Reflects the workspace's npm scope at
43
+ * generation time; re-scoping the workspace afterwards does not rename
44
+ * already-generated components.
45
+ */
46
+ export declare const buildDefaultComponentName: (tree: Tree, name: string) => string;