@aws/nx-plugin 1.1.0 → 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 +6 -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/migrations/latest/ts-agent-guardrails-synth-input/metadata.json +3 -0
- package/src/migrations/latest/ts-agent-guardrails-synth-input/migration.d.ts +22 -0
- package/src/migrations/latest/ts-agent-guardrails-synth-input/migration.js +42 -0
- package/src/migrations/latest/ts-agent-guardrails-synth-input/migration.js.map +1 -0
- 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/guardrails/generator.js +10 -1
- package/src/ts/agent/guardrails/generator.js.map +1 -1
- package/src/ts/agent/guardrails/synth-inputs.d.ts +19 -0
- package/src/ts/agent/guardrails/synth-inputs.js +50 -0
- package/src/ts/agent/guardrails/synth-inputs.js.map +1 -0
- 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/guardrail-constructs/guardrail-constructs.d.ts +3 -1
- package/src/utils/guardrail-constructs/guardrail-constructs.js +3 -1
- package/src/utils/guardrail-constructs/guardrail-constructs.js.map +1 -1
- 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,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/ import { generateFiles, joinPathFragments, logger, OverwriteStrategy, updateProjectConfiguration } from "@nx/devkit";
|
|
5
|
+
import { FsCommands } from "../../../utils/fs.js";
|
|
6
|
+
import { GREENGRASS_PLATFORM_MAPPINGS, RECIPE_FORMAT_VERSION } from "../../../utils/greengrass/constants.js";
|
|
7
|
+
import { toSnakeCase } from "../../../utils/names.js";
|
|
8
|
+
import { addArtifactDependencyToTargets, normalizeTargetKeyOrder } from "../../../utils/nx.js";
|
|
9
|
+
import { sortObjectKeys } from "../../../utils/object.js";
|
|
10
|
+
import { toProjectRelativePath } from "../../../utils/paths.js";
|
|
11
|
+
import { sharedGreengrassScriptsGenerator } from "../../../utils/shared-greengrass-scripts.js";
|
|
12
|
+
import { tryReadToml } from "../../../utils/toml.js";
|
|
13
|
+
import { LAMBDA_RUNTIME_VERSIONS } from "../../../utils/versions.js";
|
|
14
|
+
import { assertUnchangedPlatform, finalizeComponent, GREENGRASS_SCRIPTS_DIR, sharedComponentTargets } from "../common.js";
|
|
15
|
+
import { PY_DEPENDENCIES as DEPENDENCIES } from "../dependencies.js";
|
|
16
|
+
/**
|
|
17
|
+
* The lowest Python `major.minor` declared by `[project].requires-python`
|
|
18
|
+
* (eg "3.14" from ">=3.14"), so vendoring targets the device's own floor
|
|
19
|
+
* rather than the Lambda runtime `addPythonBundleTarget` pins. Falls back to
|
|
20
|
+
* the plugin's Lambda Python floor only when the project declares none.
|
|
21
|
+
*/ const pythonVersionFloor = (requiresPython)=>{
|
|
22
|
+
if (typeof requiresPython === 'string') {
|
|
23
|
+
let min;
|
|
24
|
+
for (const match of requiresPython.matchAll(/(\d+)\.(\d+)/g)){
|
|
25
|
+
const major = Number(match[1]);
|
|
26
|
+
const minor = Number(match[2]);
|
|
27
|
+
if (!min || major < min.major || major === min.major && minor < min.minor) {
|
|
28
|
+
min = {
|
|
29
|
+
major,
|
|
30
|
+
minor
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (min) {
|
|
35
|
+
return `${min.major}.${min.minor}`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return LAMBDA_RUNTIME_VERSIONS.python;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Generates an AWS IoT Greengrass v2 component on an existing Python project:
|
|
42
|
+
* packaging and local-deployment targets (`<c>-vendor`, `<c>-artifact`,
|
|
43
|
+
* `<c>-deploy-local`, `<c>-logs`), plus, unless `--infra none`, a
|
|
44
|
+
* `GreengrassComponentVersion` CDK construct (or Terraform module, via the
|
|
45
|
+
* `hashicorp/awscc` provider) that publishes the built artifact.
|
|
46
|
+
*/ export const pythonGreengrassComponentGenerator = async (tree, component)=>{
|
|
47
|
+
const { project: projectConfig, options } = component;
|
|
48
|
+
const componentDirName = component.componentDirName;
|
|
49
|
+
const componentSnakeCase = toSnakeCase(options.name);
|
|
50
|
+
const componentName = component.componentName;
|
|
51
|
+
const componentVersion = component.componentVersion;
|
|
52
|
+
const publisher = component.publisher;
|
|
53
|
+
const ipc = component.ipc;
|
|
54
|
+
const gdkConfig = component.gdkConfig;
|
|
55
|
+
const platforms = component.platforms;
|
|
56
|
+
const isMultiPlatform = platforms.length > 1;
|
|
57
|
+
const distDir = `dist/{projectRoot}/greengrass/${componentDirName}`;
|
|
58
|
+
const distDirVendor = `${distDir}/vendor`;
|
|
59
|
+
// One entry per recipe manifest. Every per-architecture name - the vendor
|
|
60
|
+
// subdirectory, the zip base name and the manifest `architecture` value -
|
|
61
|
+
// is derived from the SAME mapping entry, so they cannot drift apart.
|
|
62
|
+
const manifests = platforms.map((p)=>{
|
|
63
|
+
const { uvPlatform, manifestPlatform } = GREENGRASS_PLATFORM_MAPPINGS[p];
|
|
64
|
+
return {
|
|
65
|
+
uvPlatform,
|
|
66
|
+
os: manifestPlatform.os,
|
|
67
|
+
architecture: manifestPlatform.architecture,
|
|
68
|
+
// Single-platform names stay flat: `<c>.zip` and `vendor/`.
|
|
69
|
+
artifactBaseName: isMultiPlatform ? `${componentDirName}-${manifestPlatform.architecture}` : componentDirName,
|
|
70
|
+
vendorDir: isMultiPlatform ? `${distDirVendor}/${manifestPlatform.architecture}` : distDirVendor
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
const pyProjectPath = joinPathFragments(projectConfig.root, 'pyproject.toml');
|
|
74
|
+
const pyproject = tryReadToml(tree, pyProjectPath);
|
|
75
|
+
const pythonFloor = pythonVersionFloor(pyproject?.project?.['requires-python']);
|
|
76
|
+
const componentDir = joinPathFragments(projectConfig.root, 'greengrass', componentDirName);
|
|
77
|
+
const mainPath = joinPathFragments(componentDir, 'main.py');
|
|
78
|
+
const gdkConfigPath = joinPathFragments(componentDir, 'gdk-config.json');
|
|
79
|
+
// Flat under tests/greengrass/, one file per component: pytest imports a test
|
|
80
|
+
// module by basename, so two components sharing a `test_main.py` basename in
|
|
81
|
+
// sibling directories would collide.
|
|
82
|
+
const testDir = joinPathFragments(projectConfig.root, 'tests', 'greengrass');
|
|
83
|
+
const manifestPreview = manifests.map((m)=>` - Platform:\n os: ${m.os}\n architecture: ${m.architecture}\n Artifacts:\n - Uri: s3://BUCKET_NAME/COMPONENT_NAME/COMPONENT_VERSION/${m.artifactBaseName}.zip\n Unarchive: ZIP\n Lifecycle:\n Run: python${pythonFloor} {artifacts:decompressedPath}/${m.artifactBaseName}/main.py`).join('\n');
|
|
84
|
+
assertUnchangedPlatform(component, manifestPreview);
|
|
85
|
+
const templateOptions = {
|
|
86
|
+
componentName,
|
|
87
|
+
// Named `version` / `recipeFormat`, not `componentVersion` /
|
|
88
|
+
// `recipeFormatVersion`: a `*Version` template variable is how a template
|
|
89
|
+
// names a vended dependency pin (see `VENDED_TEMPLATE_VARS` in
|
|
90
|
+
// `version-upgrade-migration/real-templates.spec.ts`), which neither a
|
|
91
|
+
// user-owned option nor a fixed protocol constant is.
|
|
92
|
+
version: componentVersion,
|
|
93
|
+
recipeFormat: RECIPE_FORMAT_VERSION,
|
|
94
|
+
componentDescription: `${componentName} Greengrass component using Python ${pythonFloor}, generated by greengrass#component.`,
|
|
95
|
+
publisher,
|
|
96
|
+
ipc,
|
|
97
|
+
manifests,
|
|
98
|
+
// The recipe Run command pins the same interpreter the wheels were
|
|
99
|
+
// vendored for; a bare `python3` can resolve to an older device default
|
|
100
|
+
// whose ABI the vendored native wheels do not support.
|
|
101
|
+
pythonFloor,
|
|
102
|
+
componentDirName,
|
|
103
|
+
componentSnakeCase,
|
|
104
|
+
project: projectConfig.name
|
|
105
|
+
};
|
|
106
|
+
// recipe.yaml and main.py are user-owned from first generation onward.
|
|
107
|
+
generateFiles(tree, joinPathFragments(import.meta.dirname, 'files', 'component'), componentDir, templateOptions, {
|
|
108
|
+
overwriteStrategy: OverwriteStrategy.KeepExisting
|
|
109
|
+
});
|
|
110
|
+
if (gdkConfig) {
|
|
111
|
+
generateFiles(tree, joinPathFragments(import.meta.dirname, 'files', 'gdk'), componentDir, templateOptions, {
|
|
112
|
+
overwriteStrategy: OverwriteStrategy.Overwrite
|
|
113
|
+
});
|
|
114
|
+
} else if (tree.exists(gdkConfigPath)) {
|
|
115
|
+
tree.delete(gdkConfigPath);
|
|
116
|
+
logger.info(`Removed ${gdkConfigPath}; pass --gdkConfig to keep it.`);
|
|
117
|
+
}
|
|
118
|
+
generateFiles(tree, joinPathFragments(import.meta.dirname, 'files', 'tests'), testDir, templateOptions, {
|
|
119
|
+
overwriteStrategy: OverwriteStrategy.KeepExisting
|
|
120
|
+
});
|
|
121
|
+
await sharedGreengrassScriptsGenerator(tree, DEPENDENCIES);
|
|
122
|
+
const vendorTarget = `${componentDirName}-vendor`;
|
|
123
|
+
const artifactTarget = `${componentDirName}-artifact`;
|
|
124
|
+
projectConfig.targets ??= {};
|
|
125
|
+
const fs = new FsCommands(tree, DEPENDENCIES);
|
|
126
|
+
projectConfig.targets[vendorTarget] = normalizeTargetKeyOrder({
|
|
127
|
+
cache: true,
|
|
128
|
+
// The vendor dir holds only exported dependencies, never test files.
|
|
129
|
+
inputs: [
|
|
130
|
+
'production',
|
|
131
|
+
'^production'
|
|
132
|
+
],
|
|
133
|
+
outputs: [
|
|
134
|
+
`{workspaceRoot}/${distDirVendor}`
|
|
135
|
+
],
|
|
136
|
+
executor: 'nx:run-commands',
|
|
137
|
+
dependsOn: [
|
|
138
|
+
'compile'
|
|
139
|
+
],
|
|
140
|
+
options: {
|
|
141
|
+
commands: [
|
|
142
|
+
// `uv pip install --target` adds to whatever is already there and
|
|
143
|
+
// never prunes, so without this a dropped dependency - or the other
|
|
144
|
+
// platform's `vendor/<architecture>` directory after a deliberate
|
|
145
|
+
// platform change - would still be packaged into the next artifact.
|
|
146
|
+
// Artifact bytes must depend only on the current source.
|
|
147
|
+
fs.rm(distDirVendor),
|
|
148
|
+
fs.mkdir(distDirVendor),
|
|
149
|
+
`uv export --frozen --no-dev --no-editable --no-emit-project --project {projectRoot} --package ${projectConfig.name} -o ${distDirVendor}/requirements.txt`,
|
|
150
|
+
// `--only-binary :all:` is required: without it an sdist can build a
|
|
151
|
+
// host-architecture binary into a cross-architecture artifact.
|
|
152
|
+
...manifests.map((m)=>`uv pip install -n --no-deps --no-installer-metadata --no-compile-bytecode --only-binary :all: --python-platform ${m.uvPlatform} --python-version ${pythonFloor} --target ${m.vendorDir} -r ${distDirVendor}/requirements.txt`)
|
|
153
|
+
],
|
|
154
|
+
parallel: false
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
projectConfig.targets[artifactTarget] = normalizeTargetKeyOrder({
|
|
158
|
+
cache: true,
|
|
159
|
+
// The vended scripts live outside this project, so the cache key must
|
|
160
|
+
// name them explicitly or a refreshed script serves a stale artifact.
|
|
161
|
+
inputs: [
|
|
162
|
+
'production',
|
|
163
|
+
'^production',
|
|
164
|
+
`{workspaceRoot}/${GREENGRASS_SCRIPTS_DIR}/**/*`
|
|
165
|
+
],
|
|
166
|
+
outputs: [
|
|
167
|
+
`{workspaceRoot}/${distDir}/greengrass-build`
|
|
168
|
+
],
|
|
169
|
+
executor: 'nx:run-commands',
|
|
170
|
+
dependsOn: [
|
|
171
|
+
vendorTarget
|
|
172
|
+
],
|
|
173
|
+
options: {
|
|
174
|
+
command: `tsx ${GREENGRASS_SCRIPTS_DIR}/build-artifact.ts {projectRoot} ${componentDirName} ${distDir}${isMultiPlatform ? ` --platforms=${manifests.map((m)=>m.architecture).join(',')}` : ''}`
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
addArtifactDependencyToTargets(projectConfig, artifactTarget);
|
|
178
|
+
Object.assign(projectConfig.targets, sharedComponentTargets({
|
|
179
|
+
artifactTarget,
|
|
180
|
+
componentDirName,
|
|
181
|
+
componentName,
|
|
182
|
+
distDir
|
|
183
|
+
}));
|
|
184
|
+
// py#project and ts#project sort targets on every run, including re-runs,
|
|
185
|
+
// so append in the same order or a later project re-run reorders the file.
|
|
186
|
+
projectConfig.targets = sortObjectKeys(projectConfig.targets);
|
|
187
|
+
updateProjectConfiguration(tree, projectConfig.name, projectConfig);
|
|
188
|
+
return finalizeComponent(tree, component, joinPathFragments(toProjectRelativePath(projectConfig, mainPath)), DEPENDENCIES);
|
|
189
|
+
};
|
|
190
|
+
export default pythonGreengrassComponentGenerator;
|
|
191
|
+
|
|
192
|
+
//# sourceMappingURL=generator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../packages/nx-plugin/src/greengrass/component/python/generator.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n type GeneratorCallback,\n generateFiles,\n joinPathFragments,\n logger,\n OverwriteStrategy,\n type Tree,\n updateProjectConfiguration,\n} from '@nx/devkit';\nimport { FsCommands } from '../../../utils/fs.js';\nimport {\n GREENGRASS_PLATFORM_MAPPINGS,\n RECIPE_FORMAT_VERSION,\n} from '../../../utils/greengrass/constants.js';\nimport { toSnakeCase } from '../../../utils/names.js';\nimport {\n addArtifactDependencyToTargets,\n normalizeTargetKeyOrder,\n} from '../../../utils/nx.js';\nimport { sortObjectKeys } from '../../../utils/object.js';\nimport { toProjectRelativePath } from '../../../utils/paths.js';\nimport { sharedGreengrassScriptsGenerator } from '../../../utils/shared-greengrass-scripts.js';\nimport { tryReadToml } from '../../../utils/toml.js';\nimport { LAMBDA_RUNTIME_VERSIONS } from '../../../utils/versions.js';\nimport type { NormalizedComponent } from '../common.js';\nimport {\n assertUnchangedPlatform,\n finalizeComponent,\n GREENGRASS_SCRIPTS_DIR,\n sharedComponentTargets,\n} from '../common.js';\nimport { PY_DEPENDENCIES as DEPENDENCIES } from '../dependencies.js';\n\n/**\n * The lowest Python `major.minor` declared by `[project].requires-python`\n * (eg \"3.14\" from \">=3.14\"), so vendoring targets the device's own floor\n * rather than the Lambda runtime `addPythonBundleTarget` pins. Falls back to\n * the plugin's Lambda Python floor only when the project declares none.\n */\nconst pythonVersionFloor = (requiresPython: unknown): string => {\n if (typeof requiresPython === 'string') {\n let min: { major: number; minor: number } | undefined;\n for (const match of requiresPython.matchAll(/(\\d+)\\.(\\d+)/g)) {\n const major = Number(match[1]);\n const minor = Number(match[2]);\n if (\n !min ||\n major < min.major ||\n (major === min.major && minor < min.minor)\n ) {\n min = { major, minor };\n }\n }\n if (min) {\n return `${min.major}.${min.minor}`;\n }\n }\n return LAMBDA_RUNTIME_VERSIONS.python;\n};\n\n/**\n * Generates an AWS IoT Greengrass v2 component on an existing Python project:\n * packaging and local-deployment targets (`<c>-vendor`, `<c>-artifact`,\n * `<c>-deploy-local`, `<c>-logs`), plus, unless `--infra none`, a\n * `GreengrassComponentVersion` CDK construct (or Terraform module, via the\n * `hashicorp/awscc` provider) that publishes the built artifact.\n */\nexport const pythonGreengrassComponentGenerator = async (\n tree: Tree,\n component: NormalizedComponent,\n): Promise<GeneratorCallback> => {\n const { project: projectConfig, options } = component;\n\n const componentDirName = component.componentDirName;\n const componentSnakeCase = toSnakeCase(options.name);\n const componentName = component.componentName;\n const componentVersion = component.componentVersion;\n const publisher = component.publisher;\n const ipc = component.ipc;\n const gdkConfig = component.gdkConfig;\n const platforms = component.platforms;\n const isMultiPlatform = platforms.length > 1;\n const distDir = `dist/{projectRoot}/greengrass/${componentDirName}`;\n const distDirVendor = `${distDir}/vendor`;\n // One entry per recipe manifest. Every per-architecture name - the vendor\n // subdirectory, the zip base name and the manifest `architecture` value -\n // is derived from the SAME mapping entry, so they cannot drift apart.\n const manifests = platforms.map((p) => {\n const { uvPlatform, manifestPlatform } = GREENGRASS_PLATFORM_MAPPINGS[p];\n return {\n uvPlatform,\n os: manifestPlatform.os,\n architecture: manifestPlatform.architecture,\n // Single-platform names stay flat: `<c>.zip` and `vendor/`.\n artifactBaseName: isMultiPlatform\n ? `${componentDirName}-${manifestPlatform.architecture}`\n : componentDirName,\n vendorDir: isMultiPlatform\n ? `${distDirVendor}/${manifestPlatform.architecture}`\n : distDirVendor,\n };\n });\n\n const pyProjectPath = joinPathFragments(projectConfig.root, 'pyproject.toml');\n const pyproject = tryReadToml(tree, pyProjectPath) as unknown as\n | { project?: { 'requires-python'?: unknown } }\n | undefined;\n const pythonFloor = pythonVersionFloor(\n pyproject?.project?.['requires-python'],\n );\n\n const componentDir = joinPathFragments(\n projectConfig.root,\n 'greengrass',\n componentDirName,\n );\n const mainPath = joinPathFragments(componentDir, 'main.py');\n const gdkConfigPath = joinPathFragments(componentDir, 'gdk-config.json');\n // Flat under tests/greengrass/, one file per component: pytest imports a test\n // module by basename, so two components sharing a `test_main.py` basename in\n // sibling directories would collide.\n const testDir = joinPathFragments(projectConfig.root, 'tests', 'greengrass');\n\n const manifestPreview = manifests\n .map(\n (m) =>\n ` - Platform:\\n os: ${m.os}\\n architecture: ${m.architecture}\\n Artifacts:\\n - Uri: s3://BUCKET_NAME/COMPONENT_NAME/COMPONENT_VERSION/${m.artifactBaseName}.zip\\n Unarchive: ZIP\\n Lifecycle:\\n Run: python${pythonFloor} {artifacts:decompressedPath}/${m.artifactBaseName}/main.py`,\n )\n .join('\\n');\n assertUnchangedPlatform(component, manifestPreview);\n\n const templateOptions = {\n componentName,\n // Named `version` / `recipeFormat`, not `componentVersion` /\n // `recipeFormatVersion`: a `*Version` template variable is how a template\n // names a vended dependency pin (see `VENDED_TEMPLATE_VARS` in\n // `version-upgrade-migration/real-templates.spec.ts`), which neither a\n // user-owned option nor a fixed protocol constant is.\n version: componentVersion,\n recipeFormat: RECIPE_FORMAT_VERSION,\n componentDescription: `${componentName} Greengrass component using Python ${pythonFloor}, generated by greengrass#component.`,\n publisher,\n ipc,\n manifests,\n // The recipe Run command pins the same interpreter the wheels were\n // vendored for; a bare `python3` can resolve to an older device default\n // whose ABI the vendored native wheels do not support.\n pythonFloor,\n componentDirName,\n componentSnakeCase,\n project: projectConfig.name,\n };\n\n // recipe.yaml and main.py are user-owned from first generation onward.\n generateFiles(\n tree,\n joinPathFragments(import.meta.dirname, 'files', 'component'),\n componentDir,\n templateOptions,\n { overwriteStrategy: OverwriteStrategy.KeepExisting },\n );\n\n if (gdkConfig) {\n generateFiles(\n tree,\n joinPathFragments(import.meta.dirname, 'files', 'gdk'),\n componentDir,\n templateOptions,\n { overwriteStrategy: OverwriteStrategy.Overwrite },\n );\n } else if (tree.exists(gdkConfigPath)) {\n tree.delete(gdkConfigPath);\n logger.info(`Removed ${gdkConfigPath}; pass --gdkConfig to keep it.`);\n }\n\n generateFiles(\n tree,\n joinPathFragments(import.meta.dirname, 'files', 'tests'),\n testDir,\n templateOptions,\n { overwriteStrategy: OverwriteStrategy.KeepExisting },\n );\n\n await sharedGreengrassScriptsGenerator(tree, DEPENDENCIES);\n\n const vendorTarget = `${componentDirName}-vendor`;\n const artifactTarget = `${componentDirName}-artifact`;\n\n projectConfig.targets ??= {};\n\n const fs = new FsCommands(tree, DEPENDENCIES);\n\n projectConfig.targets[vendorTarget] = normalizeTargetKeyOrder({\n cache: true,\n // The vendor dir holds only exported dependencies, never test files.\n inputs: ['production', '^production'],\n outputs: [`{workspaceRoot}/${distDirVendor}`],\n executor: 'nx:run-commands',\n dependsOn: ['compile'],\n options: {\n commands: [\n // `uv pip install --target` adds to whatever is already there and\n // never prunes, so without this a dropped dependency - or the other\n // platform's `vendor/<architecture>` directory after a deliberate\n // platform change - would still be packaged into the next artifact.\n // Artifact bytes must depend only on the current source.\n fs.rm(distDirVendor),\n fs.mkdir(distDirVendor),\n `uv export --frozen --no-dev --no-editable --no-emit-project --project {projectRoot} --package ${projectConfig.name} -o ${distDirVendor}/requirements.txt`,\n // `--only-binary :all:` is required: without it an sdist can build a\n // host-architecture binary into a cross-architecture artifact.\n ...manifests.map(\n (m) =>\n `uv pip install -n --no-deps --no-installer-metadata --no-compile-bytecode --only-binary :all: --python-platform ${m.uvPlatform} --python-version ${pythonFloor} --target ${m.vendorDir} -r ${distDirVendor}/requirements.txt`,\n ),\n ],\n parallel: false,\n },\n });\n\n projectConfig.targets[artifactTarget] = normalizeTargetKeyOrder({\n cache: true,\n // The vended scripts live outside this project, so the cache key must\n // name them explicitly or a refreshed script serves a stale artifact.\n inputs: [\n 'production',\n '^production',\n `{workspaceRoot}/${GREENGRASS_SCRIPTS_DIR}/**/*`,\n ],\n outputs: [`{workspaceRoot}/${distDir}/greengrass-build`],\n executor: 'nx:run-commands',\n dependsOn: [vendorTarget],\n options: {\n command: `tsx ${GREENGRASS_SCRIPTS_DIR}/build-artifact.ts {projectRoot} ${componentDirName} ${distDir}${\n isMultiPlatform\n ? ` --platforms=${manifests.map((m) => m.architecture).join(',')}`\n : ''\n }`,\n },\n });\n addArtifactDependencyToTargets(projectConfig, artifactTarget);\n\n Object.assign(\n projectConfig.targets,\n sharedComponentTargets({\n artifactTarget,\n componentDirName,\n componentName,\n distDir,\n }),\n );\n\n // py#project and ts#project sort targets on every run, including re-runs,\n // so append in the same order or a later project re-run reorders the file.\n projectConfig.targets = sortObjectKeys(projectConfig.targets);\n updateProjectConfiguration(tree, projectConfig.name, projectConfig);\n\n return finalizeComponent(\n tree,\n component,\n joinPathFragments(toProjectRelativePath(projectConfig, mainPath)),\n DEPENDENCIES,\n );\n};\n\nexport default pythonGreengrassComponentGenerator;\n"],"names":["generateFiles","joinPathFragments","logger","OverwriteStrategy","updateProjectConfiguration","FsCommands","GREENGRASS_PLATFORM_MAPPINGS","RECIPE_FORMAT_VERSION","toSnakeCase","addArtifactDependencyToTargets","normalizeTargetKeyOrder","sortObjectKeys","toProjectRelativePath","sharedGreengrassScriptsGenerator","tryReadToml","LAMBDA_RUNTIME_VERSIONS","assertUnchangedPlatform","finalizeComponent","GREENGRASS_SCRIPTS_DIR","sharedComponentTargets","PY_DEPENDENCIES","DEPENDENCIES","pythonVersionFloor","requiresPython","min","match","matchAll","major","Number","minor","python","pythonGreengrassComponentGenerator","tree","component","project","projectConfig","options","componentDirName","componentSnakeCase","name","componentName","componentVersion","publisher","ipc","gdkConfig","platforms","isMultiPlatform","length","distDir","distDirVendor","manifests","map","p","uvPlatform","manifestPlatform","os","architecture","artifactBaseName","vendorDir","pyProjectPath","root","pyproject","pythonFloor","componentDir","mainPath","gdkConfigPath","testDir","manifestPreview","m","join","templateOptions","version","recipeFormat","componentDescription","dirname","overwriteStrategy","KeepExisting","Overwrite","exists","delete","info","vendorTarget","artifactTarget","targets","fs","cache","inputs","outputs","executor","dependsOn","commands","rm","mkdir","parallel","command","Object","assign"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../packages/nx-plugin/src/greengrass/component/schema.d.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nexport interface GreengrassComponentGeneratorSchema {\n project: string;\n name: string;\n language?: 'typescript' | 'python';\n componentName?: string;\n componentVersion?: string;\n publisher?: string;\n ipc?: boolean;\n gdkConfig?: boolean;\n platform?: 'linux-amd64' | 'linux-arm64' | 'linux-amd64-arm64';\n infra?: 'component-version' | 'none';\n iac?: 'inherit' | 'cdk' | 'terraform';\n preferInstallDependencies?: boolean;\n}\n"],"names":[],"mappings":"AAAA;;;CAGC,GACD,WAaC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
export interface GreengrassComponentGeneratorSchema {
|
|
6
|
+
project: string;
|
|
7
|
+
name: string;
|
|
8
|
+
language?: 'typescript' | 'python';
|
|
9
|
+
componentName?: string;
|
|
10
|
+
componentVersion?: string;
|
|
11
|
+
publisher?: string;
|
|
12
|
+
ipc?: boolean;
|
|
13
|
+
gdkConfig?: boolean;
|
|
14
|
+
platform?: 'linux-amd64' | 'linux-arm64' | 'linux-amd64-arm64';
|
|
15
|
+
infra?: 'component-version' | 'none';
|
|
16
|
+
iac?: 'inherit' | 'cdk' | 'terraform';
|
|
17
|
+
preferInstallDependencies?: boolean;
|
|
18
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/schema",
|
|
3
|
+
"$id": "greengrass#component",
|
|
4
|
+
"title": "greengrass#component",
|
|
5
|
+
"x-label": "Greengrass Component",
|
|
6
|
+
"description": "Add an AWS IoT Greengrass component to an existing Python or TypeScript project. Language is inferred from the selected project's marker when unambiguous.",
|
|
7
|
+
"type": "object",
|
|
8
|
+
"properties": {
|
|
9
|
+
"project": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"description": "The existing Python or TypeScript host project",
|
|
12
|
+
"x-prompt": "Select the project to add the Greengrass component to",
|
|
13
|
+
"x-dropdown": "projects"
|
|
14
|
+
},
|
|
15
|
+
"name": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "The component name",
|
|
18
|
+
"x-priority": "important",
|
|
19
|
+
"$default": { "$source": "argv", "index": 0 },
|
|
20
|
+
"x-prompt": "What name would you like for the Greengrass component?"
|
|
21
|
+
},
|
|
22
|
+
"language": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"enum": ["typescript", "python"],
|
|
25
|
+
"description": "The component language. Omit it only when the host has exactly one supported language marker."
|
|
26
|
+
},
|
|
27
|
+
"componentName": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"description": "The recipe ComponentName. Defaults to a reversed-domain name derived from the workspace npm scope and component name."
|
|
30
|
+
},
|
|
31
|
+
"componentVersion": {
|
|
32
|
+
"type": "string",
|
|
33
|
+
"default": "1.0.0",
|
|
34
|
+
"description": "The recipe ComponentVersion. CDK resolves NEXT_* at deploy time; Terraform requires an explicit semantic version. With infra=none, NEXT_* is written unchanged."
|
|
35
|
+
},
|
|
36
|
+
"publisher": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"description": "The recipe ComponentPublisher. Defaults to the workspace npm scope."
|
|
39
|
+
},
|
|
40
|
+
"ipc": {
|
|
41
|
+
"type": "boolean",
|
|
42
|
+
"default": false,
|
|
43
|
+
"description": "Whether to wire the Greengrass IPC client. Defaults to false for both languages."
|
|
44
|
+
},
|
|
45
|
+
"gdkConfig": {
|
|
46
|
+
"type": "boolean",
|
|
47
|
+
"default": false,
|
|
48
|
+
"description": "Vend gdk-config.json for using the GDK CLI by hand. GDK supports NEXT_PATCH, not NEXT_MINOR or NEXT_MAJOR.",
|
|
49
|
+
"x-priority": "low"
|
|
50
|
+
},
|
|
51
|
+
"platform": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"enum": ["linux-amd64", "linux-arm64", "linux-amd64-arm64"],
|
|
54
|
+
"default": "linux-arm64",
|
|
55
|
+
"description": "The core device platform or platforms.",
|
|
56
|
+
"x-priority": "important",
|
|
57
|
+
"x-prompt": "Which core device platform(s) would you like to target?"
|
|
58
|
+
},
|
|
59
|
+
"infra": {
|
|
60
|
+
"type": "string",
|
|
61
|
+
"enum": ["component-version", "none"],
|
|
62
|
+
"default": "component-version",
|
|
63
|
+
"description": "component-version vends reusable Greengrass component infrastructure; none supports local development only.",
|
|
64
|
+
"x-priority": "important",
|
|
65
|
+
"x-prompt": "Which infrastructure would you like to provision?"
|
|
66
|
+
},
|
|
67
|
+
"iac": {
|
|
68
|
+
"type": "string",
|
|
69
|
+
"enum": ["inherit", "cdk", "terraform"],
|
|
70
|
+
"default": "inherit",
|
|
71
|
+
"description": "The IaC provider, resolved only when infrastructure is requested.",
|
|
72
|
+
"x-priority": "important",
|
|
73
|
+
"x-prompt": "Which provider would you like to manage your infrastructure? (default: Inherit)"
|
|
74
|
+
},
|
|
75
|
+
"preferInstallDependencies": {
|
|
76
|
+
"type": "boolean",
|
|
77
|
+
"default": true,
|
|
78
|
+
"description": "Whether to prefer installing dependencies after the generator runs."
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"required": ["project", "name"]
|
|
82
|
+
}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`greengrass#component generator (typescript) > multi-architecture (platform: linux-amd64-arm64) > should match snapshot for a multi-architecture component > multi-arch-snapshot 1`] = `
|
|
4
|
+
{
|
|
5
|
+
"packages/test-project/greengrass/my-component/recipe.yaml": "RecipeFormatVersion: '2020-01-25'
|
|
6
|
+
ComponentName: com.proj.MyComponent
|
|
7
|
+
ComponentVersion: 1.0.0
|
|
8
|
+
ComponentDescription: com.proj.MyComponent Greengrass component, generated by greengrass#component.
|
|
9
|
+
ComponentPublisher: proj
|
|
10
|
+
Manifests:
|
|
11
|
+
- Platform:
|
|
12
|
+
os: linux
|
|
13
|
+
architecture: amd64
|
|
14
|
+
Artifacts:
|
|
15
|
+
# Placeholders are substituted at publish time. The basename must stay
|
|
16
|
+
# my-component.zip, which is what the Run script below unarchives to.
|
|
17
|
+
- Uri: s3://BUCKET_NAME/COMPONENT_NAME/COMPONENT_VERSION/my-component.zip
|
|
18
|
+
Unarchive: ZIP
|
|
19
|
+
Lifecycle:
|
|
20
|
+
# Requires a node binary on the device's PATH. The generated bundle was
|
|
21
|
+
# tested with Node.js 22; Greengrass uses the installed runtime.
|
|
22
|
+
Run: node {artifacts:decompressedPath}/my-component/index.js
|
|
23
|
+
- Platform:
|
|
24
|
+
os: linux
|
|
25
|
+
architecture: aarch64
|
|
26
|
+
Artifacts:
|
|
27
|
+
# Placeholders are substituted at publish time. The basename must stay
|
|
28
|
+
# my-component.zip, which is what the Run script below unarchives to.
|
|
29
|
+
- Uri: s3://BUCKET_NAME/COMPONENT_NAME/COMPONENT_VERSION/my-component.zip
|
|
30
|
+
Unarchive: ZIP
|
|
31
|
+
Lifecycle:
|
|
32
|
+
# Requires a node binary on the device's PATH. The generated bundle was
|
|
33
|
+
# tested with Node.js 22; Greengrass uses the installed runtime.
|
|
34
|
+
Run: node {artifacts:decompressedPath}/my-component/index.js
|
|
35
|
+
",
|
|
36
|
+
"packages/test-project/project.json": "{
|
|
37
|
+
"name": "test-project",
|
|
38
|
+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
39
|
+
"sourceRoot": "packages/test-project/src",
|
|
40
|
+
"metadata": {
|
|
41
|
+
"components": [
|
|
42
|
+
{
|
|
43
|
+
"generator": "greengrass#component",
|
|
44
|
+
"path": "src/greengrass/my-component/main.ts",
|
|
45
|
+
"name": "my-component",
|
|
46
|
+
"language": "typescript",
|
|
47
|
+
"componentName": "com.proj.MyComponent",
|
|
48
|
+
"componentVersion": "1.0.0",
|
|
49
|
+
"platform": "linux-amd64-arm64",
|
|
50
|
+
"ipc": false,
|
|
51
|
+
"gdkConfig": false
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
"targets": {
|
|
56
|
+
"assemble": {
|
|
57
|
+
"dependsOn": ["bundle", "my-component-artifact"]
|
|
58
|
+
},
|
|
59
|
+
"build": {
|
|
60
|
+
"dependsOn": ["bundle", "my-component-artifact"]
|
|
61
|
+
},
|
|
62
|
+
"bundle": {
|
|
63
|
+
"executor": "nx:run-commands",
|
|
64
|
+
"dependsOn": ["compile"],
|
|
65
|
+
"cache": true,
|
|
66
|
+
"inputs": ["default"],
|
|
67
|
+
"outputs": ["{workspaceRoot}/dist/{projectRoot}/bundle"],
|
|
68
|
+
"options": {
|
|
69
|
+
"command": "rolldown -c rolldown.config.ts",
|
|
70
|
+
"cwd": "{projectRoot}"
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"compile": {
|
|
74
|
+
"executor": "@nx/js:tsc"
|
|
75
|
+
},
|
|
76
|
+
"my-component-artifact": {
|
|
77
|
+
"executor": "nx:run-commands",
|
|
78
|
+
"dependsOn": ["bundle"],
|
|
79
|
+
"cache": true,
|
|
80
|
+
"inputs": [
|
|
81
|
+
"production",
|
|
82
|
+
"^production",
|
|
83
|
+
"{workspaceRoot}/packages/common/scripts/src/greengrass/**/*"
|
|
84
|
+
],
|
|
85
|
+
"outputs": [
|
|
86
|
+
"{workspaceRoot}/dist/{projectRoot}/greengrass/my-component/greengrass-build"
|
|
87
|
+
],
|
|
88
|
+
"options": {
|
|
89
|
+
"command": "tsx packages/common/scripts/src/greengrass/build-artifact.ts {projectRoot} my-component dist/{projectRoot}/greengrass/my-component dist/{projectRoot}/bundle/greengrass/my-component"
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"my-component-deploy-local": {
|
|
93
|
+
"executor": "nx:run-commands",
|
|
94
|
+
"dependsOn": ["my-component-artifact"],
|
|
95
|
+
"options": {
|
|
96
|
+
"command": "tsx packages/common/scripts/src/greengrass/deploy-local.ts dist/{projectRoot}/greengrass/my-component"
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
"my-component-logs": {
|
|
100
|
+
"executor": "nx:run-commands",
|
|
101
|
+
"continuous": true,
|
|
102
|
+
"options": {
|
|
103
|
+
"command": "tsx packages/common/scripts/src/greengrass/component-logs.ts com.proj.MyComponent"
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
",
|
|
109
|
+
"packages/test-project/rolldown.config.ts": "import { defineConfig } from 'rolldown';
|
|
110
|
+
|
|
111
|
+
export default defineConfig([
|
|
112
|
+
{
|
|
113
|
+
tsconfig: 'tsconfig.lib.json',
|
|
114
|
+
input: 'src/greengrass/my-component/main.ts',
|
|
115
|
+
output: {
|
|
116
|
+
file: '../../dist/packages/test-project/bundle/greengrass/my-component/index.js',
|
|
117
|
+
format: 'cjs',
|
|
118
|
+
codeSplitting: false,
|
|
119
|
+
},
|
|
120
|
+
platform: 'node',
|
|
121
|
+
},
|
|
122
|
+
]);
|
|
123
|
+
",
|
|
124
|
+
"packages/test-project/src/greengrass/my-component/main.ts": "const RUN_INTERVAL_MS = 30_000;
|
|
125
|
+
|
|
126
|
+
export function runOnce(sequence: number): void {
|
|
127
|
+
console.log(
|
|
128
|
+
\`[com.proj.MyComponent] heartbeat \${sequence} at \${new Date().toISOString()}\`,
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function main(): Promise<void> {
|
|
133
|
+
let sequence = 0;
|
|
134
|
+
for (;;) {
|
|
135
|
+
runOnce(sequence);
|
|
136
|
+
sequence += 1;
|
|
137
|
+
await new Promise((resolve) => setTimeout(resolve, RUN_INTERVAL_MS));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
main();
|
|
142
|
+
",
|
|
143
|
+
}
|
|
144
|
+
`;
|
|
145
|
+
|
|
146
|
+
exports[`greengrass#component generator (typescript) > should match snapshot > main-snapshot 1`] = `
|
|
147
|
+
{
|
|
148
|
+
"packages/test-project/greengrass/my-component/recipe.yaml": "RecipeFormatVersion: '2020-01-25'
|
|
149
|
+
ComponentName: com.proj.MyComponent
|
|
150
|
+
ComponentVersion: 1.0.0
|
|
151
|
+
ComponentDescription: com.proj.MyComponent Greengrass component, generated by greengrass#component.
|
|
152
|
+
ComponentPublisher: proj
|
|
153
|
+
Manifests:
|
|
154
|
+
- Platform:
|
|
155
|
+
os: linux
|
|
156
|
+
architecture: aarch64
|
|
157
|
+
Artifacts:
|
|
158
|
+
# Placeholders are substituted at publish time. The basename must stay
|
|
159
|
+
# my-component.zip, which is what the Run script below unarchives to.
|
|
160
|
+
- Uri: s3://BUCKET_NAME/COMPONENT_NAME/COMPONENT_VERSION/my-component.zip
|
|
161
|
+
Unarchive: ZIP
|
|
162
|
+
Lifecycle:
|
|
163
|
+
# Requires a node binary on the device's PATH. The generated bundle was
|
|
164
|
+
# tested with Node.js 22; Greengrass uses the installed runtime.
|
|
165
|
+
Run: node {artifacts:decompressedPath}/my-component/index.js
|
|
166
|
+
",
|
|
167
|
+
"packages/test-project/project.json": "{
|
|
168
|
+
"name": "test-project",
|
|
169
|
+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
170
|
+
"sourceRoot": "packages/test-project/src",
|
|
171
|
+
"metadata": {
|
|
172
|
+
"components": [
|
|
173
|
+
{
|
|
174
|
+
"generator": "greengrass#component",
|
|
175
|
+
"path": "src/greengrass/my-component/main.ts",
|
|
176
|
+
"name": "my-component",
|
|
177
|
+
"language": "typescript",
|
|
178
|
+
"componentName": "com.proj.MyComponent",
|
|
179
|
+
"componentVersion": "1.0.0",
|
|
180
|
+
"platform": "linux-arm64",
|
|
181
|
+
"ipc": false,
|
|
182
|
+
"gdkConfig": false
|
|
183
|
+
}
|
|
184
|
+
]
|
|
185
|
+
},
|
|
186
|
+
"targets": {
|
|
187
|
+
"assemble": {
|
|
188
|
+
"dependsOn": ["bundle", "my-component-artifact"]
|
|
189
|
+
},
|
|
190
|
+
"build": {
|
|
191
|
+
"dependsOn": ["bundle", "my-component-artifact"]
|
|
192
|
+
},
|
|
193
|
+
"bundle": {
|
|
194
|
+
"executor": "nx:run-commands",
|
|
195
|
+
"dependsOn": ["compile"],
|
|
196
|
+
"cache": true,
|
|
197
|
+
"inputs": ["default"],
|
|
198
|
+
"outputs": ["{workspaceRoot}/dist/{projectRoot}/bundle"],
|
|
199
|
+
"options": {
|
|
200
|
+
"command": "rolldown -c rolldown.config.ts",
|
|
201
|
+
"cwd": "{projectRoot}"
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
"compile": {
|
|
205
|
+
"executor": "@nx/js:tsc"
|
|
206
|
+
},
|
|
207
|
+
"my-component-artifact": {
|
|
208
|
+
"executor": "nx:run-commands",
|
|
209
|
+
"dependsOn": ["bundle"],
|
|
210
|
+
"cache": true,
|
|
211
|
+
"inputs": [
|
|
212
|
+
"production",
|
|
213
|
+
"^production",
|
|
214
|
+
"{workspaceRoot}/packages/common/scripts/src/greengrass/**/*"
|
|
215
|
+
],
|
|
216
|
+
"outputs": [
|
|
217
|
+
"{workspaceRoot}/dist/{projectRoot}/greengrass/my-component/greengrass-build"
|
|
218
|
+
],
|
|
219
|
+
"options": {
|
|
220
|
+
"command": "tsx packages/common/scripts/src/greengrass/build-artifact.ts {projectRoot} my-component dist/{projectRoot}/greengrass/my-component dist/{projectRoot}/bundle/greengrass/my-component"
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
"my-component-deploy-local": {
|
|
224
|
+
"executor": "nx:run-commands",
|
|
225
|
+
"dependsOn": ["my-component-artifact"],
|
|
226
|
+
"options": {
|
|
227
|
+
"command": "tsx packages/common/scripts/src/greengrass/deploy-local.ts dist/{projectRoot}/greengrass/my-component"
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
"my-component-logs": {
|
|
231
|
+
"executor": "nx:run-commands",
|
|
232
|
+
"continuous": true,
|
|
233
|
+
"options": {
|
|
234
|
+
"command": "tsx packages/common/scripts/src/greengrass/component-logs.ts com.proj.MyComponent"
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
",
|
|
240
|
+
"packages/test-project/rolldown.config.ts": "import { defineConfig } from 'rolldown';
|
|
241
|
+
|
|
242
|
+
export default defineConfig([
|
|
243
|
+
{
|
|
244
|
+
tsconfig: 'tsconfig.lib.json',
|
|
245
|
+
input: 'src/greengrass/my-component/main.ts',
|
|
246
|
+
output: {
|
|
247
|
+
file: '../../dist/packages/test-project/bundle/greengrass/my-component/index.js',
|
|
248
|
+
format: 'cjs',
|
|
249
|
+
codeSplitting: false,
|
|
250
|
+
},
|
|
251
|
+
platform: 'node',
|
|
252
|
+
},
|
|
253
|
+
]);
|
|
254
|
+
",
|
|
255
|
+
"packages/test-project/src/greengrass/my-component/main.ts": "const RUN_INTERVAL_MS = 30_000;
|
|
256
|
+
|
|
257
|
+
export function runOnce(sequence: number): void {
|
|
258
|
+
console.log(
|
|
259
|
+
\`[com.proj.MyComponent] heartbeat \${sequence} at \${new Date().toISOString()}\`,
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async function main(): Promise<void> {
|
|
264
|
+
let sequence = 0;
|
|
265
|
+
for (;;) {
|
|
266
|
+
runOnce(sequence);
|
|
267
|
+
sequence += 1;
|
|
268
|
+
await new Promise((resolve) => setTimeout(resolve, RUN_INTERVAL_MS));
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
main();
|
|
273
|
+
",
|
|
274
|
+
}
|
|
275
|
+
`;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
RecipeFormatVersion: '<%- recipeFormat %>'
|
|
2
|
+
ComponentName: <%- componentName %>
|
|
3
|
+
ComponentVersion: <%- version %>
|
|
4
|
+
ComponentDescription: <%- componentDescription %>
|
|
5
|
+
ComponentPublisher: <%- publisher %>
|
|
6
|
+
<%_ if (ipc) { _%>
|
|
7
|
+
ComponentConfiguration:
|
|
8
|
+
DefaultConfiguration:
|
|
9
|
+
accessControl:
|
|
10
|
+
aws.greengrass.ipc.pubsub:
|
|
11
|
+
<%- componentName %>:pubsub:1:
|
|
12
|
+
policyDescription: Allow publishing heartbeat messages to this component's own topic.
|
|
13
|
+
operations:
|
|
14
|
+
- aws.greengrass#PublishToTopic
|
|
15
|
+
resources:
|
|
16
|
+
- <%- componentName %>/heartbeat
|
|
17
|
+
<%_ } _%>
|
|
18
|
+
Manifests:
|
|
19
|
+
<%_ manifests.forEach((m) => { _%>
|
|
20
|
+
- Platform:
|
|
21
|
+
os: <%- m.os %>
|
|
22
|
+
architecture: <%- m.architecture %>
|
|
23
|
+
Artifacts:
|
|
24
|
+
# Placeholders are substituted at publish time. The basename must stay
|
|
25
|
+
# <%- m.artifactBaseName %>.zip, which is what the Run script below unarchives to.
|
|
26
|
+
- Uri: s3://BUCKET_NAME/COMPONENT_NAME/COMPONENT_VERSION/<%- m.artifactBaseName %>.zip
|
|
27
|
+
Unarchive: ZIP
|
|
28
|
+
Lifecycle:
|
|
29
|
+
<%_ if (ipc) { _%>
|
|
30
|
+
# aws-crt ships its native addon prebuilt for every platform it supports,
|
|
31
|
+
# so this component's build copies this architecture's copy into the
|
|
32
|
+
# artifact and aws-crt's own loader reads it from this path, resolved
|
|
33
|
+
# relative to index.js. There is no Install step: the decompressed
|
|
34
|
+
# artifact folder is read-only by contract, and nothing here writes to it.
|
|
35
|
+
Setenv:
|
|
36
|
+
AWS_CRT_NODEJS_BINARY_RELATIVE_PATH: native/aws-crt/<%- m.nodeCrtAddonDir %>/aws-crt-nodejs.node
|
|
37
|
+
<%_ } _%>
|
|
38
|
+
# Requires a node binary on the device's PATH. The generated bundle was
|
|
39
|
+
# tested with Node.js 22; Greengrass uses the installed runtime.
|
|
40
|
+
Run: node {artifacts:decompressedPath}/<%- m.artifactBaseName %>/index.js
|
|
41
|
+
<%_ }); _%>
|