@aws/nx-plugin 1.1.1 → 1.2.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.
- package/README.md +30 -29
- package/generators.json +8 -0
- package/migrations.json +1 -1
- package/package.json +1 -1
- package/src/agentcore-harness/generator.d.ts +1 -1
- package/src/greengrass/component/common.d.ts +60 -0
- package/src/greengrass/component/common.js +191 -0
- package/src/greengrass/component/common.js.map +1 -0
- package/src/greengrass/component/dependencies.d.ts +101 -0
- package/src/greengrass/component/dependencies.js +37 -0
- package/src/greengrass/component/dependencies.js.map +1 -0
- package/src/greengrass/component/generator.d.ts +174 -0
- package/src/greengrass/component/generator.js +32 -0
- package/src/greengrass/component/generator.js.map +1 -0
- package/src/greengrass/component/metadata.d.ts +2 -0
- package/src/greengrass/component/metadata.js +14 -0
- package/src/greengrass/component/metadata.js.map +1 -0
- package/src/greengrass/component/python/__snapshots__/generator.spec.ts.snap +450 -0
- package/src/greengrass/component/python/files/component/main.py.template +60 -0
- package/src/greengrass/component/python/files/component/recipe.yaml.template +30 -0
- package/src/greengrass/component/python/files/gdk/gdk-config.json.template +13 -0
- package/src/greengrass/component/python/files/tests/test___componentSnakeCase__.py.template +37 -0
- package/src/greengrass/component/python/generator.d.ts +15 -0
- package/src/greengrass/component/python/generator.js +192 -0
- package/src/greengrass/component/python/generator.js.map +1 -0
- package/src/greengrass/component/schema.d.js +6 -0
- package/src/greengrass/component/schema.d.js.map +1 -0
- package/src/greengrass/component/schema.d.ts +18 -0
- package/src/greengrass/component/schema.json +82 -0
- package/src/greengrass/component/typescript/__snapshots__/generator.spec.ts.snap +275 -0
- package/src/greengrass/component/typescript/files/component/recipe.yaml.template +41 -0
- package/src/greengrass/component/typescript/files/entrypoint/main.ts.template +58 -0
- package/src/greengrass/component/typescript/files/gdk/gdk-config.json.template +13 -0
- package/src/greengrass/component/typescript/files/scripts/vendor-crt-addon.ts.template +115 -0
- package/src/greengrass/component/typescript/generator.d.ts +16 -0
- package/src/greengrass/component/typescript/generator.js +194 -0
- package/src/greengrass/component/typescript/generator.js.map +1 -0
- package/src/infra/app/generator.d.ts +1 -1
- package/src/init/generator.d.ts +1 -1
- package/src/internal/test-matrix/generator.js +42 -0
- package/src/internal/test-matrix/generator.js.map +1 -1
- package/src/license/dependency-check/pre-approved.js +2 -1
- package/src/license/dependency-check/pre-approved.js.map +1 -1
- package/src/open-api/py-client/generator.d.ts +1 -1
- package/src/preset/__snapshots__/generator.spec.ts.snap +2 -0
- package/src/preset/generator.d.ts +1 -1
- package/src/py/agent/a2a-connection/generator.d.ts +1 -1
- package/src/py/agent/gateway-connection/generator.d.ts +1 -1
- package/src/py/agent/mcp-connection/generator.d.ts +1 -1
- package/src/py/agent/react-connection/generator.d.ts +1 -1
- package/src/py/fast-api/react/generator.d.ts +1 -1
- package/src/py/project/generator.js +2 -0
- package/src/py/project/generator.js.map +1 -1
- package/src/sdk/greengrass.d.ts +6 -0
- package/src/sdk/greengrass.js +6 -0
- package/src/sdk/greengrass.js.map +1 -0
- package/src/smithy/project/generator.d.ts +1 -1
- package/src/smithy/react-connection/generator.d.ts +1 -1
- package/src/terraform/project/generator.d.ts +1 -1
- package/src/trpc/react/generator.d.ts +1 -1
- package/src/ts/agent/a2a-connection/generator.d.ts +1 -1
- package/src/ts/agent/gateway-connection/generator.d.ts +1 -1
- package/src/ts/agent/mcp-connection/generator.d.ts +1 -1
- package/src/ts/agent/react-connection/generator.d.ts +1 -1
- package/src/ts/astro-docs/generator.d.ts +1 -1
- package/src/ts/dcr-proxy/generator.d.ts +1 -1
- package/src/ts/dynamodb/generator.d.ts +1 -1
- package/src/ts/lambda-function/generator.d.ts +1 -1
- package/src/ts/lib/generator.d.ts +1 -1
- package/src/ts/nx-generator/generator.d.ts +1 -1
- package/src/ts/nx-migration/generator.d.ts +1 -1
- package/src/ts/nx-plugin/generator.d.ts +1 -1
- package/src/ts/rdb/generator.d.ts +1 -1
- package/src/ts/react-website/agui/generator.d.ts +1 -1
- package/src/utils/__snapshots__/shared-greengrass-scripts.spec.ts.snap +1279 -0
- package/src/utils/files/common/scripts/src/greengrass/build-artifact.ts.template +236 -0
- package/src/utils/files/common/scripts/src/greengrass/component-logs.ts.template +48 -0
- package/src/utils/files/common/scripts/src/greengrass/deploy-local.ts.template +497 -0
- package/src/utils/files/common/scripts/src/greengrass/recipe-utils.ts.template +356 -0
- package/src/utils/files/common/scripts/src/greengrass/zip-writer.ts.template +121 -0
- package/src/utils/files/terraform/src/core/runtime-config/read/read.tf.template +3 -3
- package/src/utils/files/terraform/src/metrics/metrics.tf.template +1 -1
- package/src/utils/greengrass/constants.d.ts +58 -0
- package/src/utils/greengrass/constants.js +63 -0
- package/src/utils/greengrass/constants.js.map +1 -0
- package/src/utils/greengrass/naming.d.ts +46 -0
- package/src/utils/greengrass/naming.js +67 -0
- package/src/utils/greengrass/naming.js.map +1 -0
- package/src/utils/greengrass/recipe.d.ts +143 -0
- package/src/utils/greengrass/recipe.js +119 -0
- package/src/utils/greengrass/recipe.js.map +1 -0
- package/src/utils/greengrass-constructs/files/cdk/app/greengrass-component/__componentDirName__.ts.template +42 -0
- package/src/utils/greengrass-constructs/files/cdk/core/greengrass/artifact-bucket.ts.template +82 -0
- package/src/utils/greengrass-constructs/files/cdk/core/greengrass/component-version.ts.template +370 -0
- package/src/utils/greengrass-constructs/files/cdk/core/greengrass/deployment.ts.template +276 -0
- package/src/utils/greengrass-constructs/files/cdk/core/greengrass/index.ts.template +5 -0
- package/src/utils/greengrass-constructs/files/cdk/core/greengrass/next-version.ts.template +119 -0
- package/src/utils/greengrass-constructs/files/cdk/core/greengrass/recipe.ts.template +90 -0
- package/src/utils/greengrass-constructs/files/next-version/__nextVersionFileName__.template +195 -0
- package/src/utils/greengrass-constructs/files/terraform/app/greengrass-component/__componentDirName__/__componentDirName__.tf.template +26 -0
- package/src/utils/greengrass-constructs/files/terraform/core/greengrass/artifact-bucket/main.tf.template +204 -0
- package/src/utils/greengrass-constructs/files/terraform/core/greengrass/component-version/main.tf.template +156 -0
- package/src/utils/greengrass-constructs/files/terraform/core/greengrass/deployment/main.tf.template +186 -0
- package/src/utils/greengrass-constructs/greengrass-constructs.d.ts +57 -0
- package/src/utils/greengrass-constructs/greengrass-constructs.js +154 -0
- package/src/utils/greengrass-constructs/greengrass-constructs.js.map +1 -0
- package/src/utils/nx.d.ts +15 -0
- package/src/utils/nx.js +32 -0
- package/src/utils/nx.js.map +1 -1
- package/src/utils/shared-greengrass-scripts.d.ts +21 -0
- package/src/utils/shared-greengrass-scripts.js +43 -0
- package/src/utils/shared-greengrass-scripts.js.map +1 -0
- package/src/utils/versions.d.ts +7 -0
- package/src/utils/versions.js +7 -0
- package/src/utils/versions.js.map +1 -1
|
@@ -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
|
|
22
|
-
entries_dir
|
|
23
|
-
namespace_path
|
|
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
|
|
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;
|