@aws/nx-plugin 1.0.6 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE-THIRD-PARTY +3 -3
- package/migrations.json +26 -1
- package/package.json +1 -1
- package/src/infra/app/__snapshots__/generator.spec.ts.snap +10 -0
- package/src/infra/app/files/app/README.md.template +12 -0
- package/src/infra/app/generator.js +8 -0
- package/src/infra/app/generator.js.map +1 -1
- package/src/migrations/latest/cache-inputs-outputs/metadata.json +3 -0
- package/src/migrations/latest/cache-inputs-outputs/migration.d.ts +6 -0
- package/src/migrations/latest/cache-inputs-outputs/migration.js +176 -0
- package/src/migrations/latest/cache-inputs-outputs/migration.js.map +1 -0
- package/src/migrations/latest/infra-test-loop/metadata.json +3 -0
- package/src/migrations/latest/infra-test-loop/migration.d.ts +6 -0
- package/src/migrations/latest/infra-test-loop/migration.js +110 -0
- package/src/migrations/latest/infra-test-loop/migration.js.map +1 -0
- package/src/migrations/latest/pin-strands-express-peer/metadata.json +3 -0
- package/src/migrations/latest/pin-strands-express-peer/migration.d.ts +29 -0
- package/src/migrations/latest/pin-strands-express-peer/migration.js +58 -0
- package/src/migrations/latest/pin-strands-express-peer/migration.js.map +1 -0
- package/src/migrations/latest/py-dynamodb-type-base-model-meta/metadata.json +3 -0
- package/src/migrations/latest/py-dynamodb-type-base-model-meta/migration.d.ts +6 -0
- package/src/migrations/latest/py-dynamodb-type-base-model-meta/migration.js +87 -0
- package/src/migrations/latest/py-dynamodb-type-base-model-meta/migration.js.map +1 -0
- package/src/migrations/latest/rdb-drop-lambda-rie/metadata.json +3 -0
- package/src/migrations/latest/rdb-drop-lambda-rie/migration.d.ts +6 -0
- package/src/migrations/latest/rdb-drop-lambda-rie/migration.js +105 -0
- package/src/migrations/latest/rdb-drop-lambda-rie/migration.js.map +1 -0
- package/src/migrations/v1.0.0-rc.90/0004-python-test-input-scoping/migration.js +8 -2
- package/src/migrations/v1.0.0-rc.90/0004-python-test-input-scoping/migration.js.map +1 -1
- package/src/open-api/py-client/files/client/__clientModuleName__.py.template +17 -6
- package/src/py/dynamodb/__snapshots__/generator.spec.ts.snap +4 -4
- package/src/py/dynamodb/files/__name__/entities/base.py.template +2 -2
- package/src/py/rdb/__snapshots__/generator.spec.ts.snap +20 -8
- package/src/py/rdb/files/Dockerfile.create-db-user.template +5 -2
- package/src/py/rdb/files/Dockerfile.migration.template +5 -2
- package/src/smithy/project/__snapshots__/generator.spec.ts.snap +9 -0
- package/src/smithy/project/generator.d.ts +9 -0
- package/src/smithy/project/generator.js +15 -0
- package/src/smithy/project/generator.js.map +1 -1
- package/src/smithy/ts/api/__snapshots__/generator.spec.ts.snap +2 -0
- package/src/terraform/project/generator.js +2 -4
- package/src/terraform/project/generator.js.map +1 -1
- package/src/ts/agent/a2a-connection/generator.d.ts +26 -30
- package/src/ts/agent/a2a-connection/generator.js +9 -5
- package/src/ts/agent/a2a-connection/generator.js.map +1 -1
- package/src/ts/agent/gateway-connection/generator.d.ts +5 -7
- package/src/ts/agent/gateway-connection/generator.js +12 -0
- package/src/ts/agent/gateway-connection/generator.js.map +1 -1
- package/src/ts/agent/generator.d.ts +3 -8
- package/src/ts/agent/generator.js +16 -2
- package/src/ts/agent/generator.js.map +1 -1
- package/src/ts/agent/mcp-connection/generator.d.ts +5 -7
- package/src/ts/agent/mcp-connection/generator.js +12 -0
- package/src/ts/agent/mcp-connection/generator.js.map +1 -1
- package/src/ts/astro-docs/generator.d.ts +4 -4
- package/src/ts/astro-docs/generator.js +10 -4
- package/src/ts/astro-docs/generator.js.map +1 -1
- package/src/ts/lib/__snapshots__/generator.spec.ts.snap +1 -1
- package/src/ts/lib/generator.d.ts +14 -3
- package/src/ts/lib/generator.js +18 -7
- package/src/ts/lib/generator.js.map +1 -1
- package/src/ts/rdb/__snapshots__/generator.spec.ts.snap +4 -2
- package/src/ts/rdb/files/Dockerfile.template +4 -2
- package/src/ts/rdb/generator.js +4 -0
- package/src/ts/rdb/generator.js.map +1 -1
- package/src/ts/react-website/app/__snapshots__/generator.spec.ts.snap +102 -2
- package/src/utils/__snapshots__/cdk-test-setup.spec.ts.snap +25 -0
- package/src/utils/agent-connection/agent-connection.d.ts +28 -5
- package/src/utils/agent-connection/agent-connection.js +39 -14
- package/src/utils/agent-connection/agent-connection.js.map +1 -1
- package/src/utils/cdk-test-setup.d.ts +22 -0
- package/src/utils/cdk-test-setup.js +38 -0
- package/src/utils/cdk-test-setup.js.map +1 -0
- package/src/utils/files/cdk-test-setup/tests/global-setup.ts.template +45 -0
- package/src/utils/shared-constructs.js +2 -0
- package/src/utils/shared-constructs.js.map +1 -1
- package/src/utils/test/python-dependencies.d.ts +6 -0
- package/src/utils/test/python-dependencies.js +9 -0
- package/src/utils/test/python-dependencies.js.map +1 -1
- package/src/utils/test/warm-python-cache.js +40 -21
- package/src/utils/test/warm-python-cache.js.map +1 -1
- package/src/utils/versions.d.ts +2 -2
- package/src/utils/versions.js +2 -2
- package/src/utils/versions.js.map +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/latest/infra-test-loop/migration.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n getProjects,\n joinPathFragments,\n type MigrationReturnObject,\n type ProjectConfiguration,\n type TargetConfiguration,\n type Tree,\n updateProjectConfiguration,\n} from '@nx/devkit';\nimport { INFRA_APP_GENERATOR_INFO } from '../../../infra/app/generator.js';\nimport { matchGritQL } from '../../../utils/ast.js';\nimport {\n addCdkTestGlobalSetup,\n CDK_TEST_GLOBAL_SETUP,\n CDK_TEST_GLOBAL_SETUP_ENTRY,\n findVitestConfig,\n renderCdkTestGlobalSetup,\n} from '../../../utils/cdk-test-setup.js';\nimport { formatFilesInSubtree } from '../../../utils/format.js';\nimport { sortObjectKeys } from '../../../utils/object.js';\nimport {\n PACKAGES_DIR,\n SHARED_CONSTRUCTS_DIR,\n} from '../../../utils/shared-constructs-constants.js';\n\n/**\n * Run CDK infrastructure tests against the artifacts their stacks deploy, in a\n * per-run temp directory with CDK asset bundling skipped.\n *\n * The infra `test` target inherited the `^build` target default, so every\n * infra test run first ran every deployed project's lint, test, type-check and\n * bundle. The stacks only need the bundles: `Code.fromAsset` and its siblings\n * throw on a missing asset path, so the edge is narrowed to `^assemble` (the\n * artifact-only chain `synth` and `deploy` already use) rather than removed.\n *\n * Each `new App()` in a test synthesizes into a fresh `cdk.out*` directory\n * under the OS temp dir, which nothing removed, so a workspace's tests leaked\n * into the shared `/tmp`. The vended `tests/global-setup.ts` points `TMPDIR`,\n * `TEMP` and `TMP` at a directory of its own for the run and removes it in\n * teardown. aws-cdk-lib deletes the `cdk.out*` directories an `App` created\n * from a `process.on('exit')` handler, and vitest runs the global teardown\n * while its forked workers are still exiting, so a single `rmSync` can race\n * that delete and return with the directory still there. The teardown\n * therefore retries every 50 ms for up to 2 s, and warns (naming the\n * directory) rather than failing the run if it is still there. It also merges `aws:cdk:bundling-stacks: []` into\n * `CDK_CONTEXT_JSON`, which only affects user-written bundling constructs\n * (`NodejsFunction`, `PythonFunction`); the vended constructs read prebuilt\n * bundles. The setup is registered in the infra project's and the shared\n * constructs' vitest configs.\n *\n * Only the exact shape the generator wrote before is changed. A `test` target,\n * a `globalSetup`, a vitest config or a `tests/global-setup.ts` that differs is\n * left untouched and reported, and a setup file the plugin did not write is\n * never registered. The migration writes the generator's own template, so a migrated project is\n * byte-identical to a freshly generated one. Infra READMEs are left alone: the\n * generator writes them `KeepExisting`, and users edit them.\n */\n\nconst TEST_TARGET = 'test';\nconst TEST_DEPENDS_ON = ['^assemble'];\n\nconst isFixedTestTarget = (target: TargetConfiguration): boolean =>\n Object.keys(target).length === 1 &&\n Array.isArray(target.dependsOn) &&\n target.dependsOn.length === TEST_DEPENDS_ON.length &&\n target.dependsOn.every((dep, index) => dep === TEST_DEPENDS_ON[index]);\n\n/** Any `globalSetup` property in the config. */\nconst ANY_GLOBAL_SETUP = '`globalSetup: $_`';\n\n/** Exactly the entry the generator registers. */\nconst VENDED_GLOBAL_SETUP = `\\`globalSetup: ['${CDK_TEST_GLOBAL_SETUP_ENTRY}']\\``;\n\n/** The `defineConfig(() => ({ test: { ... } }))` shape the generator inserts into. */\nconst RECOGNISED_CONFIG = `\\`test: { $props }\\` where { $props <: within \\`defineConfig($_)\\` }`;\n\n/**\n * Vend and register the global setup for one project, unless any of its files\n * has diverged from the generated shape. Returns the reason it was skipped.\n */\nconst migrateGlobalSetup = async (\n tree: Tree,\n projectName: string,\n root: string,\n): Promise<string | undefined> => {\n const setupPath = joinPathFragments(root, CDK_TEST_GLOBAL_SETUP);\n const configPath = findVitestConfig(tree, root);\n\n if (!configPath) {\n return `${projectName}: no vitest config found in ${root} - add \\`globalSetup: ['${CDK_TEST_GLOBAL_SETUP_ENTRY}']\\` to its \\`test\\` options to give each test run its own temp directory.`;\n }\n\n if (\n tree.exists(setupPath) &&\n (tree.read(setupPath, 'utf-8') ?? '') !== renderCdkTestGlobalSetup()\n ) {\n return `${projectName}: ${setupPath} differs from the vended global setup - left untouched and not registered. Compare it with a freshly generated one, which gives each test run its own temp directory and skips CDK asset bundling, then add \\`globalSetup: ['${CDK_TEST_GLOBAL_SETUP_ENTRY}']\\` to ${configPath}.`;\n }\n\n if (await matchGritQL(tree, configPath, ANY_GLOBAL_SETUP)) {\n if (await matchGritQL(tree, configPath, VENDED_GLOBAL_SETUP)) {\n await addCdkTestGlobalSetup(tree, root);\n return undefined;\n }\n return `${projectName}: ${configPath} already declares a \\`globalSetup\\` - left untouched. Add '${CDK_TEST_GLOBAL_SETUP_ENTRY}' to it to give each test run its own temp directory.`;\n }\n\n if (!(await matchGritQL(tree, configPath, RECOGNISED_CONFIG))) {\n return `${projectName}: ${configPath} has diverged from the generated shape - left untouched. Add \\`globalSetup: ['${CDK_TEST_GLOBAL_SETUP_ENTRY}']\\` to its \\`test\\` options to give each test run its own temp directory.`;\n }\n\n await addCdkTestGlobalSetup(tree, root);\n return undefined;\n};\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const nextSteps: string[] = [];\n\n for (const [projectName, project] of getProjects(tree)) {\n const generator = (project.metadata as { generator?: string } | undefined)\n ?.generator;\n if (generator !== INFRA_APP_GENERATOR_INFO.id) continue;\n\n const testTarget = project.targets?.[TEST_TARGET];\n if (!testTarget) {\n updateProjectConfiguration(tree, projectName, {\n ...project,\n targets: sortObjectKeys({\n ...project.targets,\n [TEST_TARGET]: { dependsOn: [...TEST_DEPENDS_ON] },\n }),\n } as ProjectConfiguration);\n } else if (!isFixedTestTarget(testTarget)) {\n nextSteps.push(\n `${projectName}: the \\`test\\` target in ${joinPathFragments(project.root, 'project.json')} has diverged from the generated shape - left untouched. Set its \\`dependsOn\\` to [\"^assemble\"] so tests build the deployed projects' artifacts rather than every upstream build.`,\n );\n }\n\n const skipped = await migrateGlobalSetup(tree, projectName, project.root);\n if (skipped) nextSteps.push(skipped);\n }\n\n const constructsRoot = joinPathFragments(PACKAGES_DIR, SHARED_CONSTRUCTS_DIR);\n if (tree.exists(joinPathFragments(constructsRoot, 'project.json'))) {\n const skipped = await migrateGlobalSetup(\n tree,\n constructsRoot,\n constructsRoot,\n );\n if (skipped) nextSteps.push(skipped);\n }\n\n await formatFilesInSubtree(tree);\n\n return { nextSteps };\n}\n"],"names":["getProjects","joinPathFragments","updateProjectConfiguration","INFRA_APP_GENERATOR_INFO","matchGritQL","addCdkTestGlobalSetup","CDK_TEST_GLOBAL_SETUP","CDK_TEST_GLOBAL_SETUP_ENTRY","findVitestConfig","renderCdkTestGlobalSetup","formatFilesInSubtree","sortObjectKeys","PACKAGES_DIR","SHARED_CONSTRUCTS_DIR","TEST_TARGET","TEST_DEPENDS_ON","isFixedTestTarget","target","Object","keys","length","Array","isArray","dependsOn","every","dep","index","ANY_GLOBAL_SETUP","VENDED_GLOBAL_SETUP","RECOGNISED_CONFIG","migrateGlobalSetup","tree","projectName","root","setupPath","configPath","exists","read","undefined","migration","nextSteps","project","generator","metadata","id","testTarget","targets","push","skipped","constructsRoot"],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
* `express` is an optional peer of `@strands-agents/sdk` that any importer
|
|
8
|
+
* declaring strands without also declaring express can leave undeclared. pnpm
|
|
9
|
+
* suffixes each installed copy of a package by the versions of its peers, so
|
|
10
|
+
* such an importer can resolve express to a different version than the pin
|
|
11
|
+
* every other strands importer uses — splitting `@strands-agents/sdk` into two
|
|
12
|
+
* peer-suffixed instances of the same version.
|
|
13
|
+
*
|
|
14
|
+
* Anything importing both instances then sees two nominal identities of every
|
|
15
|
+
* SDK type — `Agent`, `McpClient`, `Plugin` — and fails to compile with "Types
|
|
16
|
+
* have separate declarations of a private property". Declaring express
|
|
17
|
+
* wherever strands is declared keeps the whole strands subgraph on the
|
|
18
|
+
* catalog's single pin. Mirrors the earlier `@aws-sdk/client-s3` peer fix, one
|
|
19
|
+
* optional peer further on.
|
|
20
|
+
*
|
|
21
|
+
* Detection is generic — every project's `package.json` plus the workspace
|
|
22
|
+
* root, checked for "declares @strands-agents/sdk but not express" — rather
|
|
23
|
+
* than listing the generators that currently produce that shape, so a future
|
|
24
|
+
* strands importer this migration predates is still covered. The shared
|
|
25
|
+
* agent-connection project is checked explicitly too, in case its project.json
|
|
26
|
+
* is missing or not yet registered when this runs — `getProjects` would miss
|
|
27
|
+
* it then, same as rc.95's `@aws-sdk/client-s3` migration guarded against.
|
|
28
|
+
*/
|
|
29
|
+
export default function migration(tree: Tree): Promise<void>;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/ import { getProjects, joinPathFragments } from "@nx/devkit";
|
|
5
|
+
import { AGENT_CONNECTION_PROJECT_DIR } from "../../../utils/agent-connection/agent-connection.js";
|
|
6
|
+
import { addDependenciesToPackageJson } from "../../../utils/dependencies.js";
|
|
7
|
+
import { formatFilesInSubtree } from "../../../utils/format.js";
|
|
8
|
+
import { TS_VERSIONS } from "../../../utils/versions.js";
|
|
9
|
+
const EXPRESS = 'express';
|
|
10
|
+
const STRANDS_SDK = '@strands-agents/sdk';
|
|
11
|
+
const ROOT_PACKAGE_JSON = 'package.json';
|
|
12
|
+
/**
|
|
13
|
+
* `express` is an optional peer of `@strands-agents/sdk` that any importer
|
|
14
|
+
* declaring strands without also declaring express can leave undeclared. pnpm
|
|
15
|
+
* suffixes each installed copy of a package by the versions of its peers, so
|
|
16
|
+
* such an importer can resolve express to a different version than the pin
|
|
17
|
+
* every other strands importer uses — splitting `@strands-agents/sdk` into two
|
|
18
|
+
* peer-suffixed instances of the same version.
|
|
19
|
+
*
|
|
20
|
+
* Anything importing both instances then sees two nominal identities of every
|
|
21
|
+
* SDK type — `Agent`, `McpClient`, `Plugin` — and fails to compile with "Types
|
|
22
|
+
* have separate declarations of a private property". Declaring express
|
|
23
|
+
* wherever strands is declared keeps the whole strands subgraph on the
|
|
24
|
+
* catalog's single pin. Mirrors the earlier `@aws-sdk/client-s3` peer fix, one
|
|
25
|
+
* optional peer further on.
|
|
26
|
+
*
|
|
27
|
+
* Detection is generic — every project's `package.json` plus the workspace
|
|
28
|
+
* root, checked for "declares @strands-agents/sdk but not express" — rather
|
|
29
|
+
* than listing the generators that currently produce that shape, so a future
|
|
30
|
+
* strands importer this migration predates is still covered. The shared
|
|
31
|
+
* agent-connection project is checked explicitly too, in case its project.json
|
|
32
|
+
* is missing or not yet registered when this runs — `getProjects` would miss
|
|
33
|
+
* it then, same as rc.95's `@aws-sdk/client-s3` migration guarded against.
|
|
34
|
+
*/ export default async function migration(tree) {
|
|
35
|
+
pinIfNeeded(tree, ROOT_PACKAGE_JSON);
|
|
36
|
+
pinIfNeeded(tree, joinPathFragments(AGENT_CONNECTION_PROJECT_DIR, 'package.json'));
|
|
37
|
+
for (const project of getProjects(tree).values()){
|
|
38
|
+
pinIfNeeded(tree, joinPathFragments(project.root, 'package.json'));
|
|
39
|
+
}
|
|
40
|
+
await formatFilesInSubtree(tree);
|
|
41
|
+
}
|
|
42
|
+
/** Declare express on the given manifest, if it declares strands but not express. */ function pinIfNeeded(tree, packageJsonPath) {
|
|
43
|
+
if (!tree.exists(packageJsonPath)) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const packageJson = JSON.parse(tree.read(packageJsonPath, 'utf-8') ?? '{}');
|
|
47
|
+
// Only where a strands client is actually declared — that is what pulls the
|
|
48
|
+
// optional peer. An already-declared express (even a stale range) is left to
|
|
49
|
+
// addDependenciesToPackageJson, which resolves it onto the catalog.
|
|
50
|
+
if (!packageJson.dependencies?.[STRANDS_SDK]) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
addDependenciesToPackageJson(tree, {
|
|
54
|
+
[EXPRESS]: TS_VERSIONS[EXPRESS]
|
|
55
|
+
}, {}, packageJsonPath);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
//# sourceMappingURL=migration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/latest/pin-strands-express-peer/migration.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport { getProjects, joinPathFragments, type Tree } from '@nx/devkit';\nimport { AGENT_CONNECTION_PROJECT_DIR } from '../../../utils/agent-connection/agent-connection.js';\nimport { addDependenciesToPackageJson } from '../../../utils/dependencies.js';\nimport { formatFilesInSubtree } from '../../../utils/format.js';\nimport { TS_VERSIONS } from '../../../utils/versions.js';\n\nconst EXPRESS = 'express';\nconst STRANDS_SDK = '@strands-agents/sdk';\nconst ROOT_PACKAGE_JSON = 'package.json';\n\n/**\n * `express` is an optional peer of `@strands-agents/sdk` that any importer\n * declaring strands without also declaring express can leave undeclared. pnpm\n * suffixes each installed copy of a package by the versions of its peers, so\n * such an importer can resolve express to a different version than the pin\n * every other strands importer uses — splitting `@strands-agents/sdk` into two\n * peer-suffixed instances of the same version.\n *\n * Anything importing both instances then sees two nominal identities of every\n * SDK type — `Agent`, `McpClient`, `Plugin` — and fails to compile with \"Types\n * have separate declarations of a private property\". Declaring express\n * wherever strands is declared keeps the whole strands subgraph on the\n * catalog's single pin. Mirrors the earlier `@aws-sdk/client-s3` peer fix, one\n * optional peer further on.\n *\n * Detection is generic — every project's `package.json` plus the workspace\n * root, checked for \"declares @strands-agents/sdk but not express\" — rather\n * than listing the generators that currently produce that shape, so a future\n * strands importer this migration predates is still covered. The shared\n * agent-connection project is checked explicitly too, in case its project.json\n * is missing or not yet registered when this runs — `getProjects` would miss\n * it then, same as rc.95's `@aws-sdk/client-s3` migration guarded against.\n */\nexport default async function migration(tree: Tree): Promise<void> {\n pinIfNeeded(tree, ROOT_PACKAGE_JSON);\n pinIfNeeded(\n tree,\n joinPathFragments(AGENT_CONNECTION_PROJECT_DIR, 'package.json'),\n );\n\n for (const project of getProjects(tree).values()) {\n pinIfNeeded(tree, joinPathFragments(project.root, 'package.json'));\n }\n\n await formatFilesInSubtree(tree);\n}\n\n/** Declare express on the given manifest, if it declares strands but not express. */\nfunction pinIfNeeded(tree: Tree, packageJsonPath: string): void {\n if (!tree.exists(packageJsonPath)) {\n return;\n }\n\n const packageJson = JSON.parse(tree.read(packageJsonPath, 'utf-8') ?? '{}');\n // Only where a strands client is actually declared — that is what pulls the\n // optional peer. An already-declared express (even a stale range) is left to\n // addDependenciesToPackageJson, which resolves it onto the catalog.\n if (!packageJson.dependencies?.[STRANDS_SDK]) {\n return;\n }\n\n addDependenciesToPackageJson(\n tree,\n { [EXPRESS]: TS_VERSIONS[EXPRESS] },\n {},\n packageJsonPath,\n );\n}\n"],"names":["getProjects","joinPathFragments","AGENT_CONNECTION_PROJECT_DIR","addDependenciesToPackageJson","formatFilesInSubtree","TS_VERSIONS","EXPRESS","STRANDS_SDK","ROOT_PACKAGE_JSON","migration","tree","pinIfNeeded","project","values","root","packageJsonPath","exists","packageJson","JSON","parse","read","dependencies"],"mappings":"AAAA;;;CAGC,GACD,SAASA,WAAW,EAAEC,iBAAiB,QAAmB,aAAa;AACvE,SAASC,4BAA4B,QAAQ,sDAAsD;AACnG,SAASC,4BAA4B,QAAQ,iCAAiC;AAC9E,SAASC,oBAAoB,QAAQ,2BAA2B;AAChE,SAASC,WAAW,QAAQ,6BAA6B;AAEzD,MAAMC,UAAU;AAChB,MAAMC,cAAc;AACpB,MAAMC,oBAAoB;AAE1B;;;;;;;;;;;;;;;;;;;;;;CAsBC,GACD,eAAe,eAAeC,UAAUC,IAAU;IAChDC,YAAYD,MAAMF;IAClBG,YACED,MACAT,kBAAkBC,8BAA8B;IAGlD,KAAK,MAAMU,WAAWZ,YAAYU,MAAMG,MAAM,GAAI;QAChDF,YAAYD,MAAMT,kBAAkBW,QAAQE,IAAI,EAAE;IACpD;IAEA,MAAMV,qBAAqBM;AAC7B;AAEA,mFAAmF,GACnF,SAASC,YAAYD,IAAU,EAAEK,eAAuB;IACtD,IAAI,CAACL,KAAKM,MAAM,CAACD,kBAAkB;QACjC;IACF;IAEA,MAAME,cAAcC,KAAKC,KAAK,CAACT,KAAKU,IAAI,CAACL,iBAAiB,YAAY;IACtE,4EAA4E;IAC5E,6EAA6E;IAC7E,oEAAoE;IACpE,IAAI,CAACE,YAAYI,YAAY,EAAE,CAACd,YAAY,EAAE;QAC5C;IACF;IAEAJ,6BACEO,MACA;QAAE,CAACJ,QAAQ,EAAED,WAAW,CAACC,QAAQ;IAAC,GAClC,CAAC,GACDS;AAEJ"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
import { type MigrationReturnObject, type Tree } from '@nx/devkit';
|
|
6
|
+
export default function migration(tree: Tree): Promise<MigrationReturnObject>;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/ import { getProjects, joinPathFragments } from "@nx/devkit";
|
|
5
|
+
import { PY_DYNAMODB_GENERATOR_INFO } from "../../../py/dynamodb/generator.js";
|
|
6
|
+
import { formatFilesInSubtree } from "../../../utils/format.js";
|
|
7
|
+
/**
|
|
8
|
+
* Type the `Meta` class of the `BaseModel` that py#dynamodb vends in
|
|
9
|
+
* `<module>/entities/base.py` as a subclass of pynamodb's `MetaProtocol`.
|
|
10
|
+
*
|
|
11
|
+
* pynamodb declares `Model.Meta: MetaProtocol`. The vended nested `class Meta:`
|
|
12
|
+
* defines only some of the protocol's members, and infers `region` as `str`
|
|
13
|
+
* where the protocol declares a mutable `Optional[str]`. From ty 0.0.86 that
|
|
14
|
+
* makes the project's `typecheck` target fail with `invalid-attribute-override`.
|
|
15
|
+
* Subclassing the protocol declares every member with the protocol's type.
|
|
16
|
+
* The members it only annotates are not set on the class, so pynamodb's
|
|
17
|
+
* runtime defaults apply as before.
|
|
18
|
+
*
|
|
19
|
+
* The generator writes base.py once and keeps the user's copy, so this brings
|
|
20
|
+
* existing workspaces up to date. A base.py is edited only where its import
|
|
21
|
+
* line and `Meta` block still have the exact shape the generator wrote. One
|
|
22
|
+
* that has diverged is left untouched and reported via `nextSteps`.
|
|
23
|
+
*/ const OLD_IMPORT = '\nfrom pynamodb.models import Model\n';
|
|
24
|
+
const NEW_IMPORT = '\nfrom pynamodb.models import MetaProtocol, Model\n';
|
|
25
|
+
const OLD_META_HEADER = ' class Meta:\n';
|
|
26
|
+
const NEW_META_HEADER = ' class Meta(MetaProtocol):\n';
|
|
27
|
+
/**
|
|
28
|
+
* The helper the vended `host` line reads the local port through. Workspaces
|
|
29
|
+
* generated before the serve-local -> local-dev rename use `_serve_local`.
|
|
30
|
+
*/ const LOCAL_DEV_HELPERS = [
|
|
31
|
+
'_local_dev',
|
|
32
|
+
'_serve_local'
|
|
33
|
+
];
|
|
34
|
+
/**
|
|
35
|
+
* The quote the block's string literals use: the template's single quotes, or
|
|
36
|
+
* the double quotes `ruff format` rewrites them to.
|
|
37
|
+
*/ const QUOTES = [
|
|
38
|
+
'"',
|
|
39
|
+
"'"
|
|
40
|
+
];
|
|
41
|
+
/** The vended `BaseModel.Meta` block, up to the blank line that ends it. */ const metaBlock = (header, localDevHelper, quote)=>`${header} table_name: str
|
|
42
|
+
region = os.environ.get("AWS_REGION", ${quote}us-east-1${quote})
|
|
43
|
+
host = f"http://localhost:{${localDevHelper}()['port']}" if is_local() else None
|
|
44
|
+
aws_access_key_id = ${quote}UNUSED${quote} if is_local() else None
|
|
45
|
+
aws_secret_access_key = ${quote}UNUSED${quote} if is_local() else None
|
|
46
|
+
|
|
47
|
+
`;
|
|
48
|
+
/** Every shape of the block the generator has written. */ const OLD_META_SHAPES = LOCAL_DEV_HELPERS.flatMap((helper)=>QUOTES.map((quote)=>({
|
|
49
|
+
helper,
|
|
50
|
+
quote
|
|
51
|
+
})));
|
|
52
|
+
const divergedNextStep = (filePath)=>`${filePath}: its BaseModel Meta class or its pynamodb.models import has been customised, so it was left unchanged. If \`ty check\` reports invalid-attribute-override for it, declare it as \`class Meta(MetaProtocol):\`, importing MetaProtocol from pynamodb.models.`;
|
|
53
|
+
/**
|
|
54
|
+
* Rewrite a base.py still in the generated shape. Already migrated and missing
|
|
55
|
+
* files are left alone; a diverged one is reported via nextSteps.
|
|
56
|
+
*/ const migrateBasePy = (tree, filePath, nextSteps)=>{
|
|
57
|
+
if (!tree.exists(filePath)) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const content = tree.read(filePath, 'utf-8') ?? '';
|
|
61
|
+
if (content.includes(NEW_META_HEADER)) {
|
|
62
|
+
return; // Already migrated (or user-added).
|
|
63
|
+
}
|
|
64
|
+
const shape = OLD_META_SHAPES.find(({ helper, quote })=>content.includes(metaBlock(OLD_META_HEADER, helper, quote)));
|
|
65
|
+
if (!shape || !content.includes(OLD_IMPORT)) {
|
|
66
|
+
nextSteps.push(divergedNextStep(filePath));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
tree.write(filePath, content.replace(OLD_IMPORT, NEW_IMPORT).replace(metaBlock(OLD_META_HEADER, shape.helper, shape.quote), metaBlock(NEW_META_HEADER, shape.helper, shape.quote)));
|
|
70
|
+
};
|
|
71
|
+
export default async function migration(tree) {
|
|
72
|
+
const nextSteps = [];
|
|
73
|
+
for (const project of getProjects(tree).values()){
|
|
74
|
+
const generator = project.metadata?.generator;
|
|
75
|
+
// py#dynamodb writes its package under the project's sourceRoot.
|
|
76
|
+
if (generator !== PY_DYNAMODB_GENERATOR_INFO.id || !project.sourceRoot) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
migrateBasePy(tree, joinPathFragments(project.sourceRoot, 'entities', 'base.py'), nextSteps);
|
|
80
|
+
}
|
|
81
|
+
await formatFilesInSubtree(tree);
|
|
82
|
+
return {
|
|
83
|
+
nextSteps
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
//# sourceMappingURL=migration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/latest/py-dynamodb-type-base-model-meta/migration.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n getProjects,\n joinPathFragments,\n type MigrationReturnObject,\n type Tree,\n} from '@nx/devkit';\nimport { PY_DYNAMODB_GENERATOR_INFO } from '../../../py/dynamodb/generator.js';\nimport { formatFilesInSubtree } from '../../../utils/format.js';\n\n/**\n * Type the `Meta` class of the `BaseModel` that py#dynamodb vends in\n * `<module>/entities/base.py` as a subclass of pynamodb's `MetaProtocol`.\n *\n * pynamodb declares `Model.Meta: MetaProtocol`. The vended nested `class Meta:`\n * defines only some of the protocol's members, and infers `region` as `str`\n * where the protocol declares a mutable `Optional[str]`. From ty 0.0.86 that\n * makes the project's `typecheck` target fail with `invalid-attribute-override`.\n * Subclassing the protocol declares every member with the protocol's type.\n * The members it only annotates are not set on the class, so pynamodb's\n * runtime defaults apply as before.\n *\n * The generator writes base.py once and keeps the user's copy, so this brings\n * existing workspaces up to date. A base.py is edited only where its import\n * line and `Meta` block still have the exact shape the generator wrote. One\n * that has diverged is left untouched and reported via `nextSteps`.\n */\n\nconst OLD_IMPORT = '\\nfrom pynamodb.models import Model\\n';\nconst NEW_IMPORT = '\\nfrom pynamodb.models import MetaProtocol, Model\\n';\n\nconst OLD_META_HEADER = ' class Meta:\\n';\nconst NEW_META_HEADER = ' class Meta(MetaProtocol):\\n';\n\n/**\n * The helper the vended `host` line reads the local port through. Workspaces\n * generated before the serve-local -> local-dev rename use `_serve_local`.\n */\nconst LOCAL_DEV_HELPERS = ['_local_dev', '_serve_local'];\n\n/**\n * The quote the block's string literals use: the template's single quotes, or\n * the double quotes `ruff format` rewrites them to.\n */\nconst QUOTES = ['\"', \"'\"];\n\n/** The vended `BaseModel.Meta` block, up to the blank line that ends it. */\nconst metaBlock = (\n header: string,\n localDevHelper: string,\n quote: string,\n): string =>\n `${header} table_name: str\n region = os.environ.get(\"AWS_REGION\", ${quote}us-east-1${quote})\n host = f\"http://localhost:{${localDevHelper}()['port']}\" if is_local() else None\n aws_access_key_id = ${quote}UNUSED${quote} if is_local() else None\n aws_secret_access_key = ${quote}UNUSED${quote} if is_local() else None\n\n`;\n\n/** Every shape of the block the generator has written. */\nconst OLD_META_SHAPES = LOCAL_DEV_HELPERS.flatMap((helper) =>\n QUOTES.map((quote) => ({ helper, quote })),\n);\n\nconst divergedNextStep = (filePath: string): string =>\n `${filePath}: its BaseModel Meta class or its pynamodb.models import has been customised, so it was left unchanged. If \\`ty check\\` reports invalid-attribute-override for it, declare it as \\`class Meta(MetaProtocol):\\`, importing MetaProtocol from pynamodb.models.`;\n\n/**\n * Rewrite a base.py still in the generated shape. Already migrated and missing\n * files are left alone; a diverged one is reported via nextSteps.\n */\nconst migrateBasePy = (\n tree: Tree,\n filePath: string,\n nextSteps: string[],\n): void => {\n if (!tree.exists(filePath)) {\n return;\n }\n\n const content = tree.read(filePath, 'utf-8') ?? '';\n if (content.includes(NEW_META_HEADER)) {\n return; // Already migrated (or user-added).\n }\n\n const shape = OLD_META_SHAPES.find(({ helper, quote }) =>\n content.includes(metaBlock(OLD_META_HEADER, helper, quote)),\n );\n if (!shape || !content.includes(OLD_IMPORT)) {\n nextSteps.push(divergedNextStep(filePath));\n return;\n }\n\n tree.write(\n filePath,\n content\n .replace(OLD_IMPORT, NEW_IMPORT)\n .replace(\n metaBlock(OLD_META_HEADER, shape.helper, shape.quote),\n metaBlock(NEW_META_HEADER, shape.helper, shape.quote),\n ),\n );\n};\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const nextSteps: string[] = [];\n\n for (const project of getProjects(tree).values()) {\n const generator = (project.metadata as { generator?: string } | undefined)\n ?.generator;\n // py#dynamodb writes its package under the project's sourceRoot.\n if (generator !== PY_DYNAMODB_GENERATOR_INFO.id || !project.sourceRoot) {\n continue;\n }\n migrateBasePy(\n tree,\n joinPathFragments(project.sourceRoot, 'entities', 'base.py'),\n nextSteps,\n );\n }\n\n await formatFilesInSubtree(tree);\n\n return { nextSteps };\n}\n"],"names":["getProjects","joinPathFragments","PY_DYNAMODB_GENERATOR_INFO","formatFilesInSubtree","OLD_IMPORT","NEW_IMPORT","OLD_META_HEADER","NEW_META_HEADER","LOCAL_DEV_HELPERS","QUOTES","metaBlock","header","localDevHelper","quote","OLD_META_SHAPES","flatMap","helper","map","divergedNextStep","filePath","migrateBasePy","tree","nextSteps","exists","content","read","includes","shape","find","push","write","replace","migration","project","values","generator","metadata","id","sourceRoot"],"mappings":"AAAA;;;CAGC,GACD,SACEA,WAAW,EACXC,iBAAiB,QAGZ,aAAa;AACpB,SAASC,0BAA0B,QAAQ,oCAAoC;AAC/E,SAASC,oBAAoB,QAAQ,2BAA2B;AAEhE;;;;;;;;;;;;;;;;CAgBC,GAED,MAAMC,aAAa;AACnB,MAAMC,aAAa;AAEnB,MAAMC,kBAAkB;AACxB,MAAMC,kBAAkB;AAExB;;;CAGC,GACD,MAAMC,oBAAoB;IAAC;IAAc;CAAe;AAExD;;;CAGC,GACD,MAAMC,SAAS;IAAC;IAAK;CAAI;AAEzB,0EAA0E,GAC1E,MAAMC,YAAY,CAChBC,QACAC,gBACAC,QAEA,GAAGF,OAAO;8CACkC,EAAEE,MAAM,SAAS,EAAEA,MAAM;mCACpC,EAAED,eAAe;4BACxB,EAAEC,MAAM,MAAM,EAAEA,MAAM;gCAClB,EAAEA,MAAM,MAAM,EAAEA,MAAM;;AAEtD,CAAC;AAED,wDAAwD,GACxD,MAAMC,kBAAkBN,kBAAkBO,OAAO,CAAC,CAACC,SACjDP,OAAOQ,GAAG,CAAC,CAACJ,QAAW,CAAA;YAAEG;YAAQH;QAAM,CAAA;AAGzC,MAAMK,mBAAmB,CAACC,WACxB,GAAGA,SAAS,4PAA4P,CAAC;AAE3Q;;;CAGC,GACD,MAAMC,gBAAgB,CACpBC,MACAF,UACAG;IAEA,IAAI,CAACD,KAAKE,MAAM,CAACJ,WAAW;QAC1B;IACF;IAEA,MAAMK,UAAUH,KAAKI,IAAI,CAACN,UAAU,YAAY;IAChD,IAAIK,QAAQE,QAAQ,CAACnB,kBAAkB;QACrC,QAAQ,oCAAoC;IAC9C;IAEA,MAAMoB,QAAQb,gBAAgBc,IAAI,CAAC,CAAC,EAAEZ,MAAM,EAAEH,KAAK,EAAE,GACnDW,QAAQE,QAAQ,CAAChB,UAAUJ,iBAAiBU,QAAQH;IAEtD,IAAI,CAACc,SAAS,CAACH,QAAQE,QAAQ,CAACtB,aAAa;QAC3CkB,UAAUO,IAAI,CAACX,iBAAiBC;QAChC;IACF;IAEAE,KAAKS,KAAK,CACRX,UACAK,QACGO,OAAO,CAAC3B,YAAYC,YACpB0B,OAAO,CACNrB,UAAUJ,iBAAiBqB,MAAMX,MAAM,EAAEW,MAAMd,KAAK,GACpDH,UAAUH,iBAAiBoB,MAAMX,MAAM,EAAEW,MAAMd,KAAK;AAG5D;AAEA,eAAe,eAAemB,UAC5BX,IAAU;IAEV,MAAMC,YAAsB,EAAE;IAE9B,KAAK,MAAMW,WAAWjC,YAAYqB,MAAMa,MAAM,GAAI;QAChD,MAAMC,YAAaF,QAAQG,QAAQ,EAC/BD;QACJ,iEAAiE;QACjE,IAAIA,cAAcjC,2BAA2BmC,EAAE,IAAI,CAACJ,QAAQK,UAAU,EAAE;YACtE;QACF;QACAlB,cACEC,MACApB,kBAAkBgC,QAAQK,UAAU,EAAE,YAAY,YAClDhB;IAEJ;IAEA,MAAMnB,qBAAqBkB;IAE3B,OAAO;QAAEC;IAAU;AACrB"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
import { type MigrationReturnObject, type Tree } from '@nx/devkit';
|
|
6
|
+
export default function migration(tree: Tree): Promise<MigrationReturnObject>;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/ import { getProjects, joinPathFragments } from "@nx/devkit";
|
|
5
|
+
import { PY_RDB_GENERATOR_INFO } from "../../../py/rdb/generator.js";
|
|
6
|
+
import { TS_RDB_GENERATOR_INFO } from "../../../ts/rdb/generator.js";
|
|
7
|
+
import { formatFilesInSubtree } from "../../../utils/format.js";
|
|
8
|
+
/**
|
|
9
|
+
* Remove the Lambda Runtime Interface Emulator (RIE) from the vended ts#rdb
|
|
10
|
+
* and py#rdb Dockerfiles.
|
|
11
|
+
*
|
|
12
|
+
* `/lambda-entrypoint.sh` only invokes RIE when AWS_LAMBDA_RUNTIME_API is
|
|
13
|
+
* unset, i.e. for local `docker run` emulation outside Lambda — the deployed
|
|
14
|
+
* function never runs it. Its upstream build lags the Go toolchain, so it
|
|
15
|
+
* carries known HIGH vulnerabilities (net/http and crypto/tls DoS CVEs) the
|
|
16
|
+
* image scan flags with no fixed upstream image coming soon. Stripping it
|
|
17
|
+
* follows the same pattern the templates already use for other unused,
|
|
18
|
+
* vulnerable parts of the base image (py#rdb removing pip, ts#rdb removing
|
|
19
|
+
* the runtime's AWS SDK copy). New workspaces get this from the generator
|
|
20
|
+
* templates; this brings existing ones up to date.
|
|
21
|
+
*
|
|
22
|
+
* Only the three Dockerfiles the ts#rdb / py#rdb generators vend are touched,
|
|
23
|
+
* located from each rdb project's root (recorded in its metadata). Each is
|
|
24
|
+
* edited in place only where its comment + `RUN microdnf upgrade ...` block
|
|
25
|
+
* still has the exact shape the generator wrote before this change; one that
|
|
26
|
+
* has diverged (a customised comment or upgrade command) is left untouched
|
|
27
|
+
* and reported via `nextSteps`.
|
|
28
|
+
*/ const AWS_LAMBDA_RIE = '/usr/local/bin/aws-lambda-rie';
|
|
29
|
+
/**
|
|
30
|
+
* The exact comment + `RUN microdnf upgrade ...` block ts#rdb's Dockerfile had
|
|
31
|
+
* before this migration, and what it becomes.
|
|
32
|
+
*/ const TS_RDB_OLD_UPGRADE = `# Patch OS packages beyond the base image's pinned release, and drop the
|
|
33
|
+
# runtime's convenience copy of the AWS SDK (the handler bundle inlines its
|
|
34
|
+
# own dependencies), keeping the image free of known HIGH/CRITICAL
|
|
35
|
+
# vulnerabilities
|
|
36
|
+
RUN microdnf upgrade -y --releasever latest && microdnf clean all \\
|
|
37
|
+
&& rm -rf /var/runtime/node_modules/@aws-sdk`;
|
|
38
|
+
const TS_RDB_NEW_UPGRADE = `# Patch OS packages beyond the base image's pinned release, and drop the
|
|
39
|
+
# runtime's convenience copy of the AWS SDK (the handler bundle inlines its
|
|
40
|
+
# own dependencies), keeping the image free of known HIGH/CRITICAL
|
|
41
|
+
# vulnerabilities. Also drop the Lambda Runtime Interface Emulator: it is only
|
|
42
|
+
# used for local \`docker run\` emulation outside Lambda, never by the deployed
|
|
43
|
+
# function, and carries known HIGH vulnerabilities.
|
|
44
|
+
RUN microdnf upgrade -y --releasever latest && microdnf clean all \\
|
|
45
|
+
&& rm -rf /var/runtime/node_modules/@aws-sdk ${AWS_LAMBDA_RIE}`;
|
|
46
|
+
/**
|
|
47
|
+
* The exact comment + `RUN microdnf upgrade ...` block py#rdb's two Dockerfiles
|
|
48
|
+
* had before this migration, and what it becomes. Both Dockerfile.migration and
|
|
49
|
+
* Dockerfile.create-db-user share this block verbatim.
|
|
50
|
+
*/ const PY_RDB_OLD_UPGRADE = `# Patch OS packages beyond the base image's pinned release, and drop pip, whose
|
|
51
|
+
# vendored copies of msgpack and setuptools carry known HIGH vulnerabilities.
|
|
52
|
+
# The handler's dependencies are already installed in the image, so nothing
|
|
53
|
+
# installs packages at runtime.
|
|
54
|
+
RUN microdnf upgrade -y --releasever latest && microdnf clean all \\
|
|
55
|
+
&& rm -rf /var/lang/lib/python*/site-packages/pip \\
|
|
56
|
+
/var/lang/lib/python*/site-packages/pip-*.dist-info`;
|
|
57
|
+
const PY_RDB_NEW_UPGRADE = `# Patch OS packages beyond the base image's pinned release, and drop pip, whose
|
|
58
|
+
# vendored copies of msgpack and setuptools carry known HIGH vulnerabilities.
|
|
59
|
+
# The handler's dependencies are already installed in the image, so nothing
|
|
60
|
+
# installs packages at runtime. Also drop the Lambda Runtime Interface
|
|
61
|
+
# Emulator: it is only used for local \`docker run\` emulation outside Lambda,
|
|
62
|
+
# never by the deployed function, and carries known HIGH vulnerabilities.
|
|
63
|
+
RUN microdnf upgrade -y --releasever latest && microdnf clean all \\
|
|
64
|
+
&& rm -rf /var/lang/lib/python*/site-packages/pip \\
|
|
65
|
+
/var/lang/lib/python*/site-packages/pip-*.dist-info \\
|
|
66
|
+
${AWS_LAMBDA_RIE}`;
|
|
67
|
+
/** The marker checked to detect an already-migrated (or user-added) removal. */ const RIE_REMOVED_MARKER = AWS_LAMBDA_RIE;
|
|
68
|
+
const divergedNextStep = (filePath)=>`${filePath}: its microdnf upgrade command has been customised, so it was left unchanged. Remove the Lambda Runtime Interface Emulator by adding "${AWS_LAMBDA_RIE}" to its "rm -rf" — it is only used for local \`docker run\` emulation outside Lambda and carries known HIGH vulnerabilities.`;
|
|
69
|
+
/**
|
|
70
|
+
* Replace a Dockerfile's old comment + upgrade block with the new one, only
|
|
71
|
+
* when the file still has the exact shape the generator wrote. Already
|
|
72
|
+
* migrated and diverged files are both left alone; the latter is reported via
|
|
73
|
+
* nextSteps.
|
|
74
|
+
*/ const migrateDockerfile = (tree, filePath, oldBlock, newBlock, nextSteps)=>{
|
|
75
|
+
if (!tree.exists(filePath)) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const content = tree.read(filePath, 'utf-8') ?? '';
|
|
79
|
+
if (content.includes(RIE_REMOVED_MARKER)) {
|
|
80
|
+
return; // Already migrated (or user-added).
|
|
81
|
+
}
|
|
82
|
+
if (!content.includes(oldBlock)) {
|
|
83
|
+
nextSteps.push(divergedNextStep(filePath));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
tree.write(filePath, content.replace(oldBlock, newBlock));
|
|
87
|
+
};
|
|
88
|
+
export default async function migration(tree) {
|
|
89
|
+
const nextSteps = [];
|
|
90
|
+
for (const project of getProjects(tree).values()){
|
|
91
|
+
const generator = project.metadata?.generator;
|
|
92
|
+
if (generator === TS_RDB_GENERATOR_INFO.id) {
|
|
93
|
+
migrateDockerfile(tree, joinPathFragments(project.root, 'Dockerfile'), TS_RDB_OLD_UPGRADE, TS_RDB_NEW_UPGRADE, nextSteps);
|
|
94
|
+
} else if (generator === PY_RDB_GENERATOR_INFO.id) {
|
|
95
|
+
migrateDockerfile(tree, joinPathFragments(project.root, 'Dockerfile.migration'), PY_RDB_OLD_UPGRADE, PY_RDB_NEW_UPGRADE, nextSteps);
|
|
96
|
+
migrateDockerfile(tree, joinPathFragments(project.root, 'Dockerfile.create-db-user'), PY_RDB_OLD_UPGRADE, PY_RDB_NEW_UPGRADE, nextSteps);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
await formatFilesInSubtree(tree);
|
|
100
|
+
return {
|
|
101
|
+
nextSteps
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
//# sourceMappingURL=migration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/latest/rdb-drop-lambda-rie/migration.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n getProjects,\n joinPathFragments,\n type MigrationReturnObject,\n type Tree,\n} from '@nx/devkit';\nimport { PY_RDB_GENERATOR_INFO } from '../../../py/rdb/generator.js';\nimport { TS_RDB_GENERATOR_INFO } from '../../../ts/rdb/generator.js';\nimport { formatFilesInSubtree } from '../../../utils/format.js';\n\n/**\n * Remove the Lambda Runtime Interface Emulator (RIE) from the vended ts#rdb\n * and py#rdb Dockerfiles.\n *\n * `/lambda-entrypoint.sh` only invokes RIE when AWS_LAMBDA_RUNTIME_API is\n * unset, i.e. for local `docker run` emulation outside Lambda — the deployed\n * function never runs it. Its upstream build lags the Go toolchain, so it\n * carries known HIGH vulnerabilities (net/http and crypto/tls DoS CVEs) the\n * image scan flags with no fixed upstream image coming soon. Stripping it\n * follows the same pattern the templates already use for other unused,\n * vulnerable parts of the base image (py#rdb removing pip, ts#rdb removing\n * the runtime's AWS SDK copy). New workspaces get this from the generator\n * templates; this brings existing ones up to date.\n *\n * Only the three Dockerfiles the ts#rdb / py#rdb generators vend are touched,\n * located from each rdb project's root (recorded in its metadata). Each is\n * edited in place only where its comment + `RUN microdnf upgrade ...` block\n * still has the exact shape the generator wrote before this change; one that\n * has diverged (a customised comment or upgrade command) is left untouched\n * and reported via `nextSteps`.\n */\n\nconst AWS_LAMBDA_RIE = '/usr/local/bin/aws-lambda-rie';\n\n/**\n * The exact comment + `RUN microdnf upgrade ...` block ts#rdb's Dockerfile had\n * before this migration, and what it becomes.\n */\nconst TS_RDB_OLD_UPGRADE = `# Patch OS packages beyond the base image's pinned release, and drop the\n# runtime's convenience copy of the AWS SDK (the handler bundle inlines its\n# own dependencies), keeping the image free of known HIGH/CRITICAL\n# vulnerabilities\nRUN microdnf upgrade -y --releasever latest && microdnf clean all \\\\\n && rm -rf /var/runtime/node_modules/@aws-sdk`;\nconst TS_RDB_NEW_UPGRADE = `# Patch OS packages beyond the base image's pinned release, and drop the\n# runtime's convenience copy of the AWS SDK (the handler bundle inlines its\n# own dependencies), keeping the image free of known HIGH/CRITICAL\n# vulnerabilities. Also drop the Lambda Runtime Interface Emulator: it is only\n# used for local \\`docker run\\` emulation outside Lambda, never by the deployed\n# function, and carries known HIGH vulnerabilities.\nRUN microdnf upgrade -y --releasever latest && microdnf clean all \\\\\n && rm -rf /var/runtime/node_modules/@aws-sdk ${AWS_LAMBDA_RIE}`;\n\n/**\n * The exact comment + `RUN microdnf upgrade ...` block py#rdb's two Dockerfiles\n * had before this migration, and what it becomes. Both Dockerfile.migration and\n * Dockerfile.create-db-user share this block verbatim.\n */\nconst PY_RDB_OLD_UPGRADE = `# Patch OS packages beyond the base image's pinned release, and drop pip, whose\n# vendored copies of msgpack and setuptools carry known HIGH vulnerabilities.\n# The handler's dependencies are already installed in the image, so nothing\n# installs packages at runtime.\nRUN microdnf upgrade -y --releasever latest && microdnf clean all \\\\\n && rm -rf /var/lang/lib/python*/site-packages/pip \\\\\n /var/lang/lib/python*/site-packages/pip-*.dist-info`;\nconst PY_RDB_NEW_UPGRADE = `# Patch OS packages beyond the base image's pinned release, and drop pip, whose\n# vendored copies of msgpack and setuptools carry known HIGH vulnerabilities.\n# The handler's dependencies are already installed in the image, so nothing\n# installs packages at runtime. Also drop the Lambda Runtime Interface\n# Emulator: it is only used for local \\`docker run\\` emulation outside Lambda,\n# never by the deployed function, and carries known HIGH vulnerabilities.\nRUN microdnf upgrade -y --releasever latest && microdnf clean all \\\\\n && rm -rf /var/lang/lib/python*/site-packages/pip \\\\\n /var/lang/lib/python*/site-packages/pip-*.dist-info \\\\\n ${AWS_LAMBDA_RIE}`;\n\n/** The marker checked to detect an already-migrated (or user-added) removal. */\nconst RIE_REMOVED_MARKER = AWS_LAMBDA_RIE;\n\nconst divergedNextStep = (filePath: string): string =>\n `${filePath}: its microdnf upgrade command has been customised, so it was left unchanged. Remove the Lambda Runtime Interface Emulator by adding \"${AWS_LAMBDA_RIE}\" to its \"rm -rf\" — it is only used for local \\`docker run\\` emulation outside Lambda and carries known HIGH vulnerabilities.`;\n\n/**\n * Replace a Dockerfile's old comment + upgrade block with the new one, only\n * when the file still has the exact shape the generator wrote. Already\n * migrated and diverged files are both left alone; the latter is reported via\n * nextSteps.\n */\nconst migrateDockerfile = (\n tree: Tree,\n filePath: string,\n oldBlock: string,\n newBlock: string,\n nextSteps: string[],\n): void => {\n if (!tree.exists(filePath)) {\n return;\n }\n\n const content = tree.read(filePath, 'utf-8') ?? '';\n if (content.includes(RIE_REMOVED_MARKER)) {\n return; // Already migrated (or user-added).\n }\n\n if (!content.includes(oldBlock)) {\n nextSteps.push(divergedNextStep(filePath));\n return;\n }\n\n tree.write(filePath, content.replace(oldBlock, newBlock));\n};\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const nextSteps: string[] = [];\n\n for (const project of getProjects(tree).values()) {\n const generator = (project.metadata as { generator?: string } | undefined)\n ?.generator;\n\n if (generator === TS_RDB_GENERATOR_INFO.id) {\n migrateDockerfile(\n tree,\n joinPathFragments(project.root, 'Dockerfile'),\n TS_RDB_OLD_UPGRADE,\n TS_RDB_NEW_UPGRADE,\n nextSteps,\n );\n } else if (generator === PY_RDB_GENERATOR_INFO.id) {\n migrateDockerfile(\n tree,\n joinPathFragments(project.root, 'Dockerfile.migration'),\n PY_RDB_OLD_UPGRADE,\n PY_RDB_NEW_UPGRADE,\n nextSteps,\n );\n migrateDockerfile(\n tree,\n joinPathFragments(project.root, 'Dockerfile.create-db-user'),\n PY_RDB_OLD_UPGRADE,\n PY_RDB_NEW_UPGRADE,\n nextSteps,\n );\n }\n }\n\n await formatFilesInSubtree(tree);\n\n return { nextSteps };\n}\n"],"names":["getProjects","joinPathFragments","PY_RDB_GENERATOR_INFO","TS_RDB_GENERATOR_INFO","formatFilesInSubtree","AWS_LAMBDA_RIE","TS_RDB_OLD_UPGRADE","TS_RDB_NEW_UPGRADE","PY_RDB_OLD_UPGRADE","PY_RDB_NEW_UPGRADE","RIE_REMOVED_MARKER","divergedNextStep","filePath","migrateDockerfile","tree","oldBlock","newBlock","nextSteps","exists","content","read","includes","push","write","replace","migration","project","values","generator","metadata","id","root"],"mappings":"AAAA;;;CAGC,GACD,SACEA,WAAW,EACXC,iBAAiB,QAGZ,aAAa;AACpB,SAASC,qBAAqB,QAAQ,+BAA+B;AACrE,SAASC,qBAAqB,QAAQ,+BAA+B;AACrE,SAASC,oBAAoB,QAAQ,2BAA2B;AAEhE;;;;;;;;;;;;;;;;;;;;CAoBC,GAED,MAAMC,iBAAiB;AAEvB;;;CAGC,GACD,MAAMC,qBAAqB,CAAC;;;;;gDAKoB,CAAC;AACjD,MAAMC,qBAAqB,CAAC;;;;;;;iDAOqB,EAAEF,gBAAgB;AAEnE;;;;CAIC,GACD,MAAMG,qBAAqB,CAAC;;;;;;uDAM2B,CAAC;AACxD,MAAMC,qBAAqB,CAAC;;;;;;;;;IASxB,EAAEJ,gBAAgB;AAEtB,8EAA8E,GAC9E,MAAMK,qBAAqBL;AAE3B,MAAMM,mBAAmB,CAACC,WACxB,GAAGA,SAAS,sIAAsI,EAAEP,eAAe,6HAA6H,CAAC;AAEnS;;;;;CAKC,GACD,MAAMQ,oBAAoB,CACxBC,MACAF,UACAG,UACAC,UACAC;IAEA,IAAI,CAACH,KAAKI,MAAM,CAACN,WAAW;QAC1B;IACF;IAEA,MAAMO,UAAUL,KAAKM,IAAI,CAACR,UAAU,YAAY;IAChD,IAAIO,QAAQE,QAAQ,CAACX,qBAAqB;QACxC,QAAQ,oCAAoC;IAC9C;IAEA,IAAI,CAACS,QAAQE,QAAQ,CAACN,WAAW;QAC/BE,UAAUK,IAAI,CAACX,iBAAiBC;QAChC;IACF;IAEAE,KAAKS,KAAK,CAACX,UAAUO,QAAQK,OAAO,CAACT,UAAUC;AACjD;AAEA,eAAe,eAAeS,UAC5BX,IAAU;IAEV,MAAMG,YAAsB,EAAE;IAE9B,KAAK,MAAMS,WAAW1B,YAAYc,MAAMa,MAAM,GAAI;QAChD,MAAMC,YAAaF,QAAQG,QAAQ,EAC/BD;QAEJ,IAAIA,cAAczB,sBAAsB2B,EAAE,EAAE;YAC1CjB,kBACEC,MACAb,kBAAkByB,QAAQK,IAAI,EAAE,eAChCzB,oBACAC,oBACAU;QAEJ,OAAO,IAAIW,cAAc1B,sBAAsB4B,EAAE,EAAE;YACjDjB,kBACEC,MACAb,kBAAkByB,QAAQK,IAAI,EAAE,yBAChCvB,oBACAC,oBACAQ;YAEFJ,kBACEC,MACAb,kBAAkByB,QAAQK,IAAI,EAAE,8BAChCvB,oBACAC,oBACAQ;QAEJ;IACF;IAEA,MAAMb,qBAAqBU;IAE3B,OAAO;QAAEG;IAAU;AACrB"}
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/ import { getProjects, readNxJson, updateNxJson, updateProjectConfiguration } from "@nx/devkit";
|
|
5
5
|
import { PYTHON_TEST_FILE_EXCLUSIONS } from "../../../py/project/generator.js";
|
|
6
|
-
import { DEPENDENT_TASKS_OUTPUT_FILES_INPUT } from "../../../ts/lib/generator.js";
|
|
7
6
|
import { formatFilesInSubtree } from "../../../utils/format.js";
|
|
8
7
|
/**
|
|
9
8
|
* Editing a Python test file used to invalidate the whole build chain for its
|
|
@@ -39,6 +38,13 @@ import { formatFilesInSubtree } from "../../../utils/format.js";
|
|
|
39
38
|
'**/*',
|
|
40
39
|
'dist/**'
|
|
41
40
|
];
|
|
41
|
+
/**
|
|
42
|
+
* The dependent-task-output glob this migration shipped with at release. A
|
|
43
|
+
* released migration's output must not change, so this is the literal value
|
|
44
|
+
* rather than a reference to ts/lib/generator.ts's current constant, which a
|
|
45
|
+
* later migration (cache-inputs-outputs) widens further and reports through
|
|
46
|
+
* its own stale-pass advisory.
|
|
47
|
+
*/ const RELEASED_DEPENDENT_TASKS_OUTPUT_GLOB = '!{reports,coverage}/**';
|
|
42
48
|
const isPythonProject = (project)=>Object.values(project.targets ?? {}).some((target)=>String(target?.executor ?? '').startsWith('@nxlv/python'));
|
|
43
49
|
/**
|
|
44
50
|
* An OpenAPI spec generation target as the generators vend it: named `openapi`
|
|
@@ -71,7 +77,7 @@ const arraysEqual = (a, b)=>a.length === b.length && a.every((entry, index)=>ent
|
|
|
71
77
|
const defaultInput = nxJson.namedInputs?.default;
|
|
72
78
|
let nextDefault = defaultInput;
|
|
73
79
|
if (defaultInput) {
|
|
74
|
-
const glob =
|
|
80
|
+
const glob = RELEASED_DEPENDENT_TASKS_OUTPUT_GLOB;
|
|
75
81
|
// Only an entry this plugin vended is rewritten; a different glob is the
|
|
76
82
|
// user's own scoping decision.
|
|
77
83
|
nextDefault = defaultInput.map((input)=>typeof input === 'object' && input !== null && 'dependentTasksOutputFiles' in input && PREVIOUSLY_VENDED_GLOBS.includes(input.dependentTasksOutputFiles) ? {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/v1.0.0-rc.90/0004-python-test-input-scoping/migration.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n getProjects,\n type MigrationReturnObject,\n type ProjectConfiguration,\n readNxJson,\n type TargetConfiguration,\n type Tree,\n updateNxJson,\n updateProjectConfiguration,\n} from '@nx/devkit';\nimport { PYTHON_TEST_FILE_EXCLUSIONS } from '../../../py/project/generator.js';\nimport { DEPENDENT_TASKS_OUTPUT_FILES_INPUT } from '../../../ts/lib/generator.js';\nimport { formatFilesInSubtree } from '../../../utils/format.js';\n\n/**\n * Editing a Python test file used to invalidate the whole build chain for its\n * project, and cascade into every downstream project. Two independent causes:\n *\n * 1. The `production` named input excluded TypeScript specs but nothing Python,\n * and the Python build targets read `default` (every file in the project), so\n * a test file was a build input by construction.\n * 2. `default` consumed *every* output of a dependency task. The Python `test`\n * target writes JUnit and coverage reports that pytest stamps wall-clock time\n * into, so each test run minted a new hash that propagated downstream.\n *\n * Targets whose output genuinely cannot contain a test file move to\n * `production`; `typecheck` deliberately stays on `default`, because `ty` checks\n * the tests directory and must re-run when a test changes.\n */\n\n/** The Python targets whose declared output cannot contain a test file. */\nconst PRODUCTION_SCOPED_TARGETS = ['compile', 'bundle-x86', 'bundle-arm'];\n\n/** The inputs those targets read once scoped to production sources. */\nconst PRODUCTION_INPUTS = ['production', '^production'];\n\n/** The inputs those targets previously read. */\nconst PREVIOUS_INPUTS = ['default', '^production'];\n\n/**\n * Dependent-task-output globs this plugin has vended. Any of them is rewritten\n * to the current one; anything else is the user's own scoping decision.\n */\nconst PREVIOUSLY_VENDED_GLOBS = ['**/*', 'dist/**'];\n\nconst isPythonProject = (project: ProjectConfiguration): boolean =>\n Object.values(project.targets ?? {}).some((target) =>\n String(target?.executor ?? '').startsWith('@nxlv/python'),\n );\n\n/**\n * An OpenAPI spec generation target as the generators vend it: named `openapi`\n * or `<agent>-openapi`, running a command, and writing its spec under\n * `dist/.../openapi`. The extra checks keep a user-authored target that merely\n * shares the suffix from being rescoped, since these targets are identified by\n * declaring no `inputs` at all.\n */\nconst isOpenApiTarget = (\n name: string,\n target?: TargetConfiguration,\n): boolean => {\n if (name !== 'openapi' && !name.endsWith('-openapi')) return false;\n if (!target) return true;\n return (\n target.executor === 'nx:run-commands' &&\n (target.outputs ?? []).some((output) => output.includes('/openapi'))\n );\n};\n\nconst arraysEqual = (a: readonly unknown[], b: readonly unknown[]): boolean =>\n a.length === b.length && a.every((entry, index) => entry === b[index]);\n\n/**\n * Excludes Python test files from the `production` named input, and narrows the\n * dependent-task-output input so test reports no longer propagate downstream.\n */\nconst migrateNamedInputs = (tree: Tree, nextSteps: string[]): void => {\n const nxJson = readNxJson(tree);\n if (!nxJson) return;\n\n // Seeded as `['default']` when absent, exactly as the generator does. The\n // targets below are rewritten to read `production`, and Nx fails hard on a\n // named input that is not defined, so the two must not diverge.\n const production = nxJson.namedInputs?.production ?? ['default'];\n const nextProduction = [\n ...production,\n ...PYTHON_TEST_FILE_EXCLUSIONS.filter(\n (exclusion) => !production.includes(exclusion),\n ),\n ];\n\n const defaultInput = nxJson.namedInputs?.default;\n let nextDefault = defaultInput;\n if (defaultInput) {\n const glob = DEPENDENT_TASKS_OUTPUT_FILES_INPUT.dependentTasksOutputFiles;\n // Only an entry this plugin vended is rewritten; a different glob is the\n // user's own scoping decision.\n nextDefault = defaultInput.map((input) =>\n typeof input === 'object' &&\n input !== null &&\n 'dependentTasksOutputFiles' in input &&\n PREVIOUSLY_VENDED_GLOBS.includes(\n input.dependentTasksOutputFiles as string,\n )\n ? { ...input, dependentTasksOutputFiles: glob }\n : input,\n );\n const hadVendedEntry = nextDefault.some(\n (input) =>\n typeof input === 'object' &&\n input !== null &&\n 'dependentTasksOutputFiles' in input &&\n input.dependentTasksOutputFiles === glob,\n );\n const hasCustomEntry = defaultInput.some(\n (input) =>\n typeof input === 'object' &&\n input !== null &&\n 'dependentTasksOutputFiles' in input &&\n !PREVIOUSLY_VENDED_GLOBS.includes(\n input.dependentTasksOutputFiles as string,\n ) &&\n input.dependentTasksOutputFiles !== glob,\n );\n if (!hadVendedEntry && hasCustomEntry) {\n nextSteps.push(\n `nx.json's 'default' named input scopes 'dependentTasksOutputFiles' to a custom glob — left untouched. Narrow it to '${glob}' so timestamped test reports do not invalidate downstream tasks.`,\n );\n }\n }\n\n updateNxJson(tree, {\n ...nxJson,\n namedInputs: {\n ...nxJson.namedInputs,\n production: nextProduction,\n ...(nextDefault ? { default: nextDefault } : {}),\n },\n });\n};\n\n/**\n * Scopes a Python project's build-chain targets to production sources. Only\n * targets still carrying the inputs the generator vended are rewritten, so a\n * project whose inputs the user has tuned is reported rather than clobbered.\n */\nconst migrateProjectTargets = (\n tree: Tree,\n projectName: string,\n project: ProjectConfiguration,\n nextSteps: string[],\n): void => {\n let changed = false;\n\n for (const [targetName, target] of Object.entries(project.targets ?? {})) {\n if (\n !PRODUCTION_SCOPED_TARGETS.includes(targetName) &&\n !isOpenApiTarget(targetName, target)\n ) {\n continue;\n }\n\n const inputs = target.inputs;\n\n // Already migrated, so a re-run is a no-op.\n if (inputs && arraysEqual(inputs as unknown[], PRODUCTION_INPUTS)) {\n continue;\n }\n\n // The OpenAPI targets declared no inputs at all, so they fell back to\n // `default`. Everything else carried the vended pair.\n const isVendedShape = inputs\n ? arraysEqual(inputs as unknown[], PREVIOUS_INPUTS)\n : isOpenApiTarget(targetName, target);\n\n if (!isVendedShape) {\n nextSteps.push(\n `${projectName}:${targetName}: 'inputs' has diverged from the generated shape — left untouched. Scope it to ['production', '^production'] so Python test edits do not invalidate it.`,\n );\n continue;\n }\n\n target.inputs = [...PRODUCTION_INPUTS];\n changed = true;\n }\n\n if (changed) {\n updateProjectConfiguration(tree, projectName, project);\n }\n};\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const nextSteps: string[] = [];\n\n migrateNamedInputs(tree, nextSteps);\n\n for (const [projectName, project] of getProjects(tree)) {\n if (!isPythonProject(project)) continue;\n migrateProjectTargets(tree, projectName, project, nextSteps);\n }\n\n await formatFilesInSubtree(tree);\n\n return { nextSteps };\n}\n"],"names":["getProjects","readNxJson","updateNxJson","updateProjectConfiguration","PYTHON_TEST_FILE_EXCLUSIONS","DEPENDENT_TASKS_OUTPUT_FILES_INPUT","formatFilesInSubtree","PRODUCTION_SCOPED_TARGETS","PRODUCTION_INPUTS","PREVIOUS_INPUTS","PREVIOUSLY_VENDED_GLOBS","isPythonProject","project","Object","values","targets","some","target","String","executor","startsWith","isOpenApiTarget","name","endsWith","outputs","output","includes","arraysEqual","a","b","length","every","entry","index","migrateNamedInputs","tree","nextSteps","nxJson","production","namedInputs","nextProduction","filter","exclusion","defaultInput","default","nextDefault","glob","dependentTasksOutputFiles","map","input","hadVendedEntry","hasCustomEntry","push","migrateProjectTargets","projectName","changed","targetName","entries","inputs","isVendedShape","migration"],"mappings":"AAAA;;;CAGC,GACD,SACEA,WAAW,EAGXC,UAAU,EAGVC,YAAY,EACZC,0BAA0B,QACrB,aAAa;AACpB,SAASC,2BAA2B,QAAQ,mCAAmC;AAC/E,SAASC,kCAAkC,QAAQ,+BAA+B;AAClF,SAASC,oBAAoB,QAAQ,2BAA2B;AAEhE;;;;;;;;;;;;;;CAcC,GAED,yEAAyE,GACzE,MAAMC,4BAA4B;IAAC;IAAW;IAAc;CAAa;AAEzE,qEAAqE,GACrE,MAAMC,oBAAoB;IAAC;IAAc;CAAc;AAEvD,8CAA8C,GAC9C,MAAMC,kBAAkB;IAAC;IAAW;CAAc;AAElD;;;CAGC,GACD,MAAMC,0BAA0B;IAAC;IAAQ;CAAU;AAEnD,MAAMC,kBAAkB,CAACC,UACvBC,OAAOC,MAAM,CAACF,QAAQG,OAAO,IAAI,CAAC,GAAGC,IAAI,CAAC,CAACC,SACzCC,OAAOD,QAAQE,YAAY,IAAIC,UAAU,CAAC;AAG9C;;;;;;CAMC,GACD,MAAMC,kBAAkB,CACtBC,MACAL;IAEA,IAAIK,SAAS,aAAa,CAACA,KAAKC,QAAQ,CAAC,aAAa,OAAO;IAC7D,IAAI,CAACN,QAAQ,OAAO;IACpB,OACEA,OAAOE,QAAQ,KAAK,qBACpB,AAACF,CAAAA,OAAOO,OAAO,IAAI,EAAE,AAAD,EAAGR,IAAI,CAAC,CAACS,SAAWA,OAAOC,QAAQ,CAAC;AAE5D;AAEA,MAAMC,cAAc,CAACC,GAAuBC,IAC1CD,EAAEE,MAAM,KAAKD,EAAEC,MAAM,IAAIF,EAAEG,KAAK,CAAC,CAACC,OAAOC,QAAUD,UAAUH,CAAC,CAACI,MAAM;AAEvE;;;CAGC,GACD,MAAMC,qBAAqB,CAACC,MAAYC;IACtC,MAAMC,SAASpC,WAAWkC;IAC1B,IAAI,CAACE,QAAQ;IAEb,0EAA0E;IAC1E,2EAA2E;IAC3E,gEAAgE;IAChE,MAAMC,aAAaD,OAAOE,WAAW,EAAED,cAAc;QAAC;KAAU;IAChE,MAAME,iBAAiB;WAClBF;WACAlC,4BAA4BqC,MAAM,CACnC,CAACC,YAAc,CAACJ,WAAWZ,QAAQ,CAACgB;KAEvC;IAED,MAAMC,eAAeN,OAAOE,WAAW,EAAEK;IACzC,IAAIC,cAAcF;IAClB,IAAIA,cAAc;QAChB,MAAMG,OAAOzC,mCAAmC0C,yBAAyB;QACzE,yEAAyE;QACzE,+BAA+B;QAC/BF,cAAcF,aAAaK,GAAG,CAAC,CAACC,QAC9B,OAAOA,UAAU,YACjBA,UAAU,QACV,+BAA+BA,SAC/BvC,wBAAwBgB,QAAQ,CAC9BuB,MAAMF,yBAAyB,IAE7B;gBAAE,GAAGE,KAAK;gBAAEF,2BAA2BD;YAAK,IAC5CG;QAEN,MAAMC,iBAAiBL,YAAY7B,IAAI,CACrC,CAACiC,QACC,OAAOA,UAAU,YACjBA,UAAU,QACV,+BAA+BA,SAC/BA,MAAMF,yBAAyB,KAAKD;QAExC,MAAMK,iBAAiBR,aAAa3B,IAAI,CACtC,CAACiC,QACC,OAAOA,UAAU,YACjBA,UAAU,QACV,+BAA+BA,SAC/B,CAACvC,wBAAwBgB,QAAQ,CAC/BuB,MAAMF,yBAAyB,KAEjCE,MAAMF,yBAAyB,KAAKD;QAExC,IAAI,CAACI,kBAAkBC,gBAAgB;YACrCf,UAAUgB,IAAI,CACZ,CAAC,oHAAoH,EAAEN,KAAK,iEAAiE,CAAC;QAElM;IACF;IAEA5C,aAAaiC,MAAM;QACjB,GAAGE,MAAM;QACTE,aAAa;YACX,GAAGF,OAAOE,WAAW;YACrBD,YAAYE;YACZ,GAAIK,cAAc;gBAAED,SAASC;YAAY,IAAI,CAAC,CAAC;QACjD;IACF;AACF;AAEA;;;;CAIC,GACD,MAAMQ,wBAAwB,CAC5BlB,MACAmB,aACA1C,SACAwB;IAEA,IAAImB,UAAU;IAEd,KAAK,MAAM,CAACC,YAAYvC,OAAO,IAAIJ,OAAO4C,OAAO,CAAC7C,QAAQG,OAAO,IAAI,CAAC,GAAI;QACxE,IACE,CAACR,0BAA0BmB,QAAQ,CAAC8B,eACpC,CAACnC,gBAAgBmC,YAAYvC,SAC7B;YACA;QACF;QAEA,MAAMyC,SAASzC,OAAOyC,MAAM;QAE5B,4CAA4C;QAC5C,IAAIA,UAAU/B,YAAY+B,QAAqBlD,oBAAoB;YACjE;QACF;QAEA,sEAAsE;QACtE,sDAAsD;QACtD,MAAMmD,gBAAgBD,SAClB/B,YAAY+B,QAAqBjD,mBACjCY,gBAAgBmC,YAAYvC;QAEhC,IAAI,CAAC0C,eAAe;YAClBvB,UAAUgB,IAAI,CACZ,GAAGE,YAAY,CAAC,EAAEE,WAAW,uJAAuJ,CAAC;YAEvL;QACF;QAEAvC,OAAOyC,MAAM,GAAG;eAAIlD;SAAkB;QACtC+C,UAAU;IACZ;IAEA,IAAIA,SAAS;QACXpD,2BAA2BgC,MAAMmB,aAAa1C;IAChD;AACF;AAEA,eAAe,eAAegD,UAC5BzB,IAAU;IAEV,MAAMC,YAAsB,EAAE;IAE9BF,mBAAmBC,MAAMC;IAEzB,KAAK,MAAM,CAACkB,aAAa1C,QAAQ,IAAIZ,YAAYmC,MAAO;QACtD,IAAI,CAACxB,gBAAgBC,UAAU;QAC/ByC,sBAAsBlB,MAAMmB,aAAa1C,SAASwB;IACpD;IAEA,MAAM9B,qBAAqB6B;IAE3B,OAAO;QAAEC;IAAU;AACrB"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/v1.0.0-rc.90/0004-python-test-input-scoping/migration.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n getProjects,\n type MigrationReturnObject,\n type ProjectConfiguration,\n readNxJson,\n type TargetConfiguration,\n type Tree,\n updateNxJson,\n updateProjectConfiguration,\n} from '@nx/devkit';\nimport { PYTHON_TEST_FILE_EXCLUSIONS } from '../../../py/project/generator.js';\nimport { formatFilesInSubtree } from '../../../utils/format.js';\n\n/**\n * Editing a Python test file used to invalidate the whole build chain for its\n * project, and cascade into every downstream project. Two independent causes:\n *\n * 1. The `production` named input excluded TypeScript specs but nothing Python,\n * and the Python build targets read `default` (every file in the project), so\n * a test file was a build input by construction.\n * 2. `default` consumed *every* output of a dependency task. The Python `test`\n * target writes JUnit and coverage reports that pytest stamps wall-clock time\n * into, so each test run minted a new hash that propagated downstream.\n *\n * Targets whose output genuinely cannot contain a test file move to\n * `production`; `typecheck` deliberately stays on `default`, because `ty` checks\n * the tests directory and must re-run when a test changes.\n */\n\n/** The Python targets whose declared output cannot contain a test file. */\nconst PRODUCTION_SCOPED_TARGETS = ['compile', 'bundle-x86', 'bundle-arm'];\n\n/** The inputs those targets read once scoped to production sources. */\nconst PRODUCTION_INPUTS = ['production', '^production'];\n\n/** The inputs those targets previously read. */\nconst PREVIOUS_INPUTS = ['default', '^production'];\n\n/**\n * Dependent-task-output globs this plugin has vended. Any of them is rewritten\n * to the current one; anything else is the user's own scoping decision.\n */\nconst PREVIOUSLY_VENDED_GLOBS = ['**/*', 'dist/**'];\n\n/**\n * The dependent-task-output glob this migration shipped with at release. A\n * released migration's output must not change, so this is the literal value\n * rather than a reference to ts/lib/generator.ts's current constant, which a\n * later migration (cache-inputs-outputs) widens further and reports through\n * its own stale-pass advisory.\n */\nconst RELEASED_DEPENDENT_TASKS_OUTPUT_GLOB = '!{reports,coverage}/**';\n\nconst isPythonProject = (project: ProjectConfiguration): boolean =>\n Object.values(project.targets ?? {}).some((target) =>\n String(target?.executor ?? '').startsWith('@nxlv/python'),\n );\n\n/**\n * An OpenAPI spec generation target as the generators vend it: named `openapi`\n * or `<agent>-openapi`, running a command, and writing its spec under\n * `dist/.../openapi`. The extra checks keep a user-authored target that merely\n * shares the suffix from being rescoped, since these targets are identified by\n * declaring no `inputs` at all.\n */\nconst isOpenApiTarget = (\n name: string,\n target?: TargetConfiguration,\n): boolean => {\n if (name !== 'openapi' && !name.endsWith('-openapi')) return false;\n if (!target) return true;\n return (\n target.executor === 'nx:run-commands' &&\n (target.outputs ?? []).some((output) => output.includes('/openapi'))\n );\n};\n\nconst arraysEqual = (a: readonly unknown[], b: readonly unknown[]): boolean =>\n a.length === b.length && a.every((entry, index) => entry === b[index]);\n\n/**\n * Excludes Python test files from the `production` named input, and narrows the\n * dependent-task-output input so test reports no longer propagate downstream.\n */\nconst migrateNamedInputs = (tree: Tree, nextSteps: string[]): void => {\n const nxJson = readNxJson(tree);\n if (!nxJson) return;\n\n // Seeded as `['default']` when absent, exactly as the generator does. The\n // targets below are rewritten to read `production`, and Nx fails hard on a\n // named input that is not defined, so the two must not diverge.\n const production = nxJson.namedInputs?.production ?? ['default'];\n const nextProduction = [\n ...production,\n ...PYTHON_TEST_FILE_EXCLUSIONS.filter(\n (exclusion) => !production.includes(exclusion),\n ),\n ];\n\n const defaultInput = nxJson.namedInputs?.default;\n let nextDefault = defaultInput;\n if (defaultInput) {\n const glob = RELEASED_DEPENDENT_TASKS_OUTPUT_GLOB;\n // Only an entry this plugin vended is rewritten; a different glob is the\n // user's own scoping decision.\n nextDefault = defaultInput.map((input) =>\n typeof input === 'object' &&\n input !== null &&\n 'dependentTasksOutputFiles' in input &&\n PREVIOUSLY_VENDED_GLOBS.includes(\n input.dependentTasksOutputFiles as string,\n )\n ? { ...input, dependentTasksOutputFiles: glob }\n : input,\n );\n const hadVendedEntry = nextDefault.some(\n (input) =>\n typeof input === 'object' &&\n input !== null &&\n 'dependentTasksOutputFiles' in input &&\n input.dependentTasksOutputFiles === glob,\n );\n const hasCustomEntry = defaultInput.some(\n (input) =>\n typeof input === 'object' &&\n input !== null &&\n 'dependentTasksOutputFiles' in input &&\n !PREVIOUSLY_VENDED_GLOBS.includes(\n input.dependentTasksOutputFiles as string,\n ) &&\n input.dependentTasksOutputFiles !== glob,\n );\n if (!hadVendedEntry && hasCustomEntry) {\n nextSteps.push(\n `nx.json's 'default' named input scopes 'dependentTasksOutputFiles' to a custom glob — left untouched. Narrow it to '${glob}' so timestamped test reports do not invalidate downstream tasks.`,\n );\n }\n }\n\n updateNxJson(tree, {\n ...nxJson,\n namedInputs: {\n ...nxJson.namedInputs,\n production: nextProduction,\n ...(nextDefault ? { default: nextDefault } : {}),\n },\n });\n};\n\n/**\n * Scopes a Python project's build-chain targets to production sources. Only\n * targets still carrying the inputs the generator vended are rewritten, so a\n * project whose inputs the user has tuned is reported rather than clobbered.\n */\nconst migrateProjectTargets = (\n tree: Tree,\n projectName: string,\n project: ProjectConfiguration,\n nextSteps: string[],\n): void => {\n let changed = false;\n\n for (const [targetName, target] of Object.entries(project.targets ?? {})) {\n if (\n !PRODUCTION_SCOPED_TARGETS.includes(targetName) &&\n !isOpenApiTarget(targetName, target)\n ) {\n continue;\n }\n\n const inputs = target.inputs;\n\n // Already migrated, so a re-run is a no-op.\n if (inputs && arraysEqual(inputs as unknown[], PRODUCTION_INPUTS)) {\n continue;\n }\n\n // The OpenAPI targets declared no inputs at all, so they fell back to\n // `default`. Everything else carried the vended pair.\n const isVendedShape = inputs\n ? arraysEqual(inputs as unknown[], PREVIOUS_INPUTS)\n : isOpenApiTarget(targetName, target);\n\n if (!isVendedShape) {\n nextSteps.push(\n `${projectName}:${targetName}: 'inputs' has diverged from the generated shape — left untouched. Scope it to ['production', '^production'] so Python test edits do not invalidate it.`,\n );\n continue;\n }\n\n target.inputs = [...PRODUCTION_INPUTS];\n changed = true;\n }\n\n if (changed) {\n updateProjectConfiguration(tree, projectName, project);\n }\n};\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const nextSteps: string[] = [];\n\n migrateNamedInputs(tree, nextSteps);\n\n for (const [projectName, project] of getProjects(tree)) {\n if (!isPythonProject(project)) continue;\n migrateProjectTargets(tree, projectName, project, nextSteps);\n }\n\n await formatFilesInSubtree(tree);\n\n return { nextSteps };\n}\n"],"names":["getProjects","readNxJson","updateNxJson","updateProjectConfiguration","PYTHON_TEST_FILE_EXCLUSIONS","formatFilesInSubtree","PRODUCTION_SCOPED_TARGETS","PRODUCTION_INPUTS","PREVIOUS_INPUTS","PREVIOUSLY_VENDED_GLOBS","RELEASED_DEPENDENT_TASKS_OUTPUT_GLOB","isPythonProject","project","Object","values","targets","some","target","String","executor","startsWith","isOpenApiTarget","name","endsWith","outputs","output","includes","arraysEqual","a","b","length","every","entry","index","migrateNamedInputs","tree","nextSteps","nxJson","production","namedInputs","nextProduction","filter","exclusion","defaultInput","default","nextDefault","glob","map","input","dependentTasksOutputFiles","hadVendedEntry","hasCustomEntry","push","migrateProjectTargets","projectName","changed","targetName","entries","inputs","isVendedShape","migration"],"mappings":"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"}
|
|
@@ -46,12 +46,23 @@ const clientClassName = `${asyncPrefix}${className}`;
|
|
|
46
46
|
const streamType = (item) => `${isAsync ? 'AsyncGenerator' : 'Iterator'}[${item}]`;
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
|
-
* The `collections.abc` names the module imports.
|
|
50
|
-
*
|
|
51
|
-
*
|
|
49
|
+
* The `collections.abc` names the module imports. `_stream_with_url` is
|
|
50
|
+
* decorated `@contextmanager`/`@asynccontextmanager`, which must be annotated
|
|
51
|
+
* `Generator`/`AsyncGenerator` rather than `Iterator`/`AsyncIterator` — a type
|
|
52
|
+
* checker (e.g. `ty`) rejects the iterator form on a context-manager function.
|
|
53
|
+
* Async spells both names `AsyncGenerator`; sync needs both `Iterator` (an
|
|
54
|
+
* operation's stream) and `Generator` (the context manager).
|
|
52
55
|
*/
|
|
53
56
|
const streamAbc = isAsync ? 'AsyncGenerator' : 'Iterator';
|
|
54
|
-
const
|
|
57
|
+
const contextAbcName = isAsync ? 'AsyncGenerator' : 'Generator';
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The declared return type of a `@contextmanager`/`@asynccontextmanager`
|
|
61
|
+
* function. `AsyncGenerator` takes a send type, `Generator` also takes a
|
|
62
|
+
* return type — both left as `None` since nothing here uses either.
|
|
63
|
+
*/
|
|
64
|
+
const contextReturnType = (item) =>
|
|
65
|
+
isAsync ? `AsyncGenerator[${item}, None]` : `Generator[${item}, None, None]`;
|
|
55
66
|
|
|
56
67
|
const resultTypeFor = (op) => {
|
|
57
68
|
if (op.isStreaming) {
|
|
@@ -329,7 +340,7 @@ const tagGroups = operationsByTag;
|
|
|
329
340
|
const hasContentJsonParams = allOperations.some(op => (op.parameters || []).some(p => p.in !== 'body' && p.mediaType === 'application/json'));
|
|
330
341
|
const hasPartContentTypes = allOperations.some(op => op.parametersBody && op.parametersBody.partContentTypes);
|
|
331
342
|
const hasAllowReservedParams = allOperations.some(op => (op.parameters || []).some(p => p.in === 'query' && p.allowReserved));
|
|
332
|
-
const abcImports = [...new Set([streamAbc, ...(hasAllowReservedParams ? [
|
|
343
|
+
const abcImports = [...new Set([streamAbc, ...(hasAllowReservedParams ? [contextAbcName] : [])])].join(', ');
|
|
333
344
|
// A response declaring several media types is read through `_json_body`.
|
|
334
345
|
const hasMultiMediaResponse = allOperations.some(op => (op.responses || []).some(r => (r.mediaTypes || []).length > 1));
|
|
335
346
|
// An untyped streamed item is decoded with `json.loads`, which needs the import.
|
|
@@ -681,7 +692,7 @@ class <%- clientClassName %>:
|
|
|
681
692
|
@<%- contextManager %>
|
|
682
693
|
<%- kw.def %> _stream_with_url(
|
|
683
694
|
self, final_url: str, **kwargs: Any
|
|
684
|
-
) -> <%-
|
|
695
|
+
) -> <%- contextReturnType('httpx.Response') %>:
|
|
685
696
|
# The streaming counterpart of `_send_with_url`: `stream()` would rebuild
|
|
686
697
|
# the query from the client's params and percent-encode a hand-built one.
|
|
687
698
|
request = self._client.build_request(**kwargs)
|
|
@@ -739,7 +739,7 @@ exports[`py#dynamodb generator > should generate the dynamodb project > packages
|
|
|
739
739
|
|
|
740
740
|
from pynamodb.attributes import DiscriminatorAttribute, UnicodeAttribute
|
|
741
741
|
from pynamodb.indexes import AllProjection, GlobalSecondaryIndex
|
|
742
|
-
from pynamodb.models import Model
|
|
742
|
+
from pynamodb.models import MetaProtocol, Model
|
|
743
743
|
|
|
744
744
|
from ..client import _local_dev, get_table_name, is_local
|
|
745
745
|
|
|
@@ -775,7 +775,7 @@ class BaseModel(Model):
|
|
|
775
775
|
See https://pynamodb.readthedocs.io/en/stable/polymorphism.html
|
|
776
776
|
"""
|
|
777
777
|
|
|
778
|
-
class Meta:
|
|
778
|
+
class Meta(MetaProtocol):
|
|
779
779
|
table_name: str
|
|
780
780
|
region = os.environ.get("AWS_REGION", "us-east-1")
|
|
781
781
|
host = f"http://localhost:{_local_dev()['port']}" if is_local() else None
|
|
@@ -1459,7 +1459,7 @@ exports[`py#dynamodb generator > should generate with infra=none then upgrade to
|
|
|
1459
1459
|
|
|
1460
1460
|
from pynamodb.attributes import DiscriminatorAttribute, UnicodeAttribute
|
|
1461
1461
|
from pynamodb.indexes import AllProjection, GlobalSecondaryIndex
|
|
1462
|
-
from pynamodb.models import Model
|
|
1462
|
+
from pynamodb.models import MetaProtocol, Model
|
|
1463
1463
|
|
|
1464
1464
|
from ..client import _local_dev, get_table_name, is_local
|
|
1465
1465
|
|
|
@@ -1495,7 +1495,7 @@ class BaseModel(Model):
|
|
|
1495
1495
|
See https://pynamodb.readthedocs.io/en/stable/polymorphism.html
|
|
1496
1496
|
"""
|
|
1497
1497
|
|
|
1498
|
-
class Meta:
|
|
1498
|
+
class Meta(MetaProtocol):
|
|
1499
1499
|
table_name: str
|
|
1500
1500
|
region = os.environ.get("AWS_REGION", "us-east-1")
|
|
1501
1501
|
host = f"http://localhost:{_local_dev()['port']}" if is_local() else None
|
|
@@ -2,7 +2,7 @@ import os
|
|
|
2
2
|
|
|
3
3
|
from pynamodb.attributes import DiscriminatorAttribute, UnicodeAttribute
|
|
4
4
|
from pynamodb.indexes import AllProjection, GlobalSecondaryIndex
|
|
5
|
-
from pynamodb.models import Model
|
|
5
|
+
from pynamodb.models import MetaProtocol, Model
|
|
6
6
|
|
|
7
7
|
from ..client import _local_dev, get_table_name, is_local
|
|
8
8
|
|
|
@@ -38,7 +38,7 @@ class BaseModel(Model):
|
|
|
38
38
|
See https://pynamodb.readthedocs.io/en/stable/polymorphism.html
|
|
39
39
|
"""
|
|
40
40
|
|
|
41
|
-
class Meta:
|
|
41
|
+
class Meta(MetaProtocol):
|
|
42
42
|
table_name: str
|
|
43
43
|
region = os.environ.get("AWS_REGION", 'us-east-1')
|
|
44
44
|
host = f"http://localhost:{_local_dev()['port']}" if is_local() else None
|