@aws/nx-plugin 1.0.0-rc.95 → 1.0.0-rc.97
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 +234 -38
- package/migrations.json +65 -2
- package/package.json +6 -6
- package/src/agentcore-gateway/__snapshots__/generator.spec.ts.snap +1 -1
- package/src/agentcore-gateway/generator.js +6 -0
- package/src/agentcore-gateway/generator.js.map +1 -1
- package/src/agentcore-gateway/react-connection/__snapshots__/generator.spec.ts.snap +4 -1
- package/src/agentcore-harness/__snapshots__/generator.spec.ts.snap +68 -4
- package/src/agentcore-harness/generator.js +6 -0
- package/src/agentcore-harness/generator.js.map +1 -1
- package/src/connection/gateway-runtime-config.js +1 -1
- package/src/connection/gateway-runtime-config.js.map +1 -1
- package/src/infra/app/__snapshots__/generator.spec.ts.snap +10 -16
- package/src/infra/app/generator.js +34 -7
- package/src/infra/app/generator.js.map +1 -1
- package/src/init/generator.d.ts +1 -1
- package/src/init/generator.js +8 -6
- package/src/init/generator.js.map +1 -1
- package/src/init/schema.d.js.map +1 -1
- package/src/init/schema.d.ts +1 -0
- package/src/init/schema.json +5 -0
- package/src/mcp-server/server.js +2 -0
- package/src/mcp-server/server.js.map +1 -1
- package/src/mcp-server/tools/best-practices.d.ts +28 -0
- package/src/mcp-server/tools/best-practices.js +43 -0
- package/src/mcp-server/tools/best-practices.js.map +1 -0
- package/src/mcp-server/tools/general-guidance.d.ts +1 -1
- package/src/mcp-server/tools/general-guidance.js +13 -4
- package/src/mcp-server/tools/general-guidance.js.map +1 -1
- package/src/migrations/latest/checkov-scan-cache-inputs/metadata.json +3 -0
- package/src/migrations/latest/checkov-scan-cache-inputs/migration.d.ts +6 -0
- package/src/migrations/latest/checkov-scan-cache-inputs/migration.js +58 -0
- package/src/migrations/latest/checkov-scan-cache-inputs/migration.js.map +1 -0
- package/src/migrations/latest/gateway-url-accessor/__snapshots__/migration.spec.ts.snap +84 -0
- package/src/migrations/latest/gateway-url-accessor/metadata.json +3 -0
- package/src/migrations/latest/gateway-url-accessor/migration.d.ts +6 -0
- package/src/migrations/latest/gateway-url-accessor/migration.js +94 -0
- package/src/migrations/latest/gateway-url-accessor/migration.js.map +1 -0
- package/src/migrations/latest/infra-deploy-express-mode/metadata.json +1 -1
- package/src/migrations/latest/infra-deploy-express-mode/migration.js +26 -28
- package/src/migrations/latest/infra-deploy-express-mode/migration.js.map +1 -1
- package/src/migrations/latest/py-lambda-powertools-context/metadata.json +3 -0
- package/src/migrations/latest/py-lambda-powertools-context/migration.d.ts +6 -0
- package/src/migrations/latest/py-lambda-powertools-context/migration.js +58 -0
- package/src/migrations/latest/py-lambda-powertools-context/migration.js.map +1 -0
- package/src/migrations/latest/script-project-quality-gates/metadata.json +3 -0
- package/src/migrations/latest/script-project-quality-gates/migration.d.ts +6 -0
- package/src/migrations/latest/script-project-quality-gates/migration.js +53 -0
- package/src/migrations/latest/script-project-quality-gates/migration.js.map +1 -0
- package/src/migrations/latest/smithy-extension-trait-aliases/__snapshots__/migration.spec.ts.snap +25 -0
- package/src/migrations/latest/smithy-extension-trait-aliases/metadata.json +3 -0
- package/src/migrations/latest/smithy-extension-trait-aliases/migration.d.ts +28 -0
- package/src/migrations/latest/smithy-extension-trait-aliases/migration.js +75 -0
- package/src/migrations/latest/smithy-extension-trait-aliases/migration.js.map +1 -0
- package/src/migrations/latest/terraform-validate-without-backend/metadata.json +3 -0
- package/src/migrations/latest/terraform-validate-without-backend/migration.d.ts +6 -0
- package/src/migrations/latest/terraform-validate-without-backend/migration.js +84 -0
- package/src/migrations/latest/terraform-validate-without-backend/migration.js.map +1 -0
- package/src/preset/__snapshots__/generator.spec.ts.snap +3 -5
- package/src/preset/generator.js +4 -25
- package/src/preset/generator.js.map +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/__snapshots__/generator.spec.ts.snap +4 -1
- package/src/py/agent/react-connection/generator.d.ts +29 -0
- package/src/py/dynamodb/__snapshots__/generator.spec.ts.snap +8 -10
- package/src/py/dynamodb/generator.js +34 -7
- package/src/py/dynamodb/generator.js.map +1 -1
- package/src/py/lambda-function/__snapshots__/generator.spec.ts.snap +2 -1
- package/src/py/lambda-function/files/handler/__lambdaFunctionSnakeCase__.py.template +2 -1
- package/src/py/project/schema.json +3 -1
- package/src/py/rdb/__snapshots__/generator.spec.ts.snap +28 -12
- package/src/py/rdb/files/Dockerfile.create-db-user.template +7 -3
- package/src/py/rdb/files/Dockerfile.migration.template +7 -3
- package/src/py/rdb/generator.js +29 -28
- package/src/py/rdb/generator.js.map +1 -1
- package/src/smithy/react-connection/files/model/extensions.smithy.template +3 -3
- package/src/smithy/ts/api/generator.js +7 -2
- package/src/smithy/ts/api/generator.js.map +1 -1
- package/src/terraform/project/generator.js +34 -10
- package/src/terraform/project/generator.js.map +1 -1
- package/src/trpc/backend/generator.js +7 -1
- package/src/trpc/backend/generator.js.map +1 -1
- package/src/trpc/react/generator.js +6 -0
- package/src/trpc/react/generator.js.map +1 -1
- package/src/ts/agent/generator.js +3 -3
- package/src/ts/agent/generator.js.map +1 -1
- package/src/ts/agent/react-connection/generator.d.ts +37 -0
- package/src/ts/dynamodb/__snapshots__/generator.spec.ts.snap +2 -0
- package/src/ts/dynamodb/generator.js +30 -6
- package/src/ts/dynamodb/generator.js.map +1 -1
- package/src/ts/lib/biome.d.ts +15 -1
- package/src/ts/lib/biome.js +79 -63
- package/src/ts/lib/biome.js.map +1 -1
- package/src/ts/lib/generator.d.ts +13 -0
- package/src/ts/lib/generator.js +14 -9
- package/src/ts/lib/generator.js.map +1 -1
- package/src/ts/lib/ts-project-utils.d.ts +31 -1
- package/src/ts/lib/ts-project-utils.js +53 -0
- package/src/ts/lib/ts-project-utils.js.map +1 -1
- package/src/ts/lib/vitest.d.ts +6 -0
- package/src/ts/lib/vitest.js +16 -1
- package/src/ts/lib/vitest.js.map +1 -1
- package/src/ts/mcp-server/generator.js +3 -3
- package/src/ts/mcp-server/generator.js.map +1 -1
- package/src/ts/rdb/__snapshots__/generator.spec.ts.snap +5 -0
- package/src/ts/rdb/files/Dockerfile.template +5 -0
- package/src/ts/rdb/generator.d.ts +8 -0
- package/src/ts/rdb/generator.js +28 -24
- package/src/ts/rdb/generator.js.map +1 -1
- package/src/ts/rdb/trpc-connection/__snapshots__/generator.spec.ts.snap +39 -0
- package/src/ts/rdb/trpc-connection/generator.js +13 -0
- package/src/ts/rdb/trpc-connection/generator.js.map +1 -1
- package/src/ts/react-website/agui/files/common/src/components/__agentNameKebabCase__-chat.tsx.template +9 -0
- package/src/ts/react-website/agui/files/common/src/hooks/useAgui__agentNameClassName__.tsx.template +4 -1
- package/src/ts/react-website/agui/generator.d.ts +21 -0
- package/src/ts/react-website/agui/generator.js +6 -2
- package/src/ts/react-website/agui/generator.js.map +1 -1
- package/src/ts/react-website/app/__snapshots__/generator.spec.ts.snap +26 -22
- package/src/ts/react-website/app/generator.d.ts +42 -0
- package/src/ts/react-website/app/generator.js +4 -8
- package/src/ts/react-website/app/generator.js.map +1 -1
- package/src/utils/agent-connection/agent-connection.d.ts +2 -1
- package/src/utils/agent-connection/agent-connection.js +6 -1
- package/src/utils/agent-connection/agent-connection.js.map +1 -1
- package/src/utils/agent-core-constructs/files/cdk/app/agentcore-gateway/__nameKebabCase__/__nameKebabCase__.ts.template +11 -7
- package/src/utils/agent-core-constructs/files/cdk/core/agentcore-gateway/agentcore-gateway.ts.template +14 -6
- package/src/utils/agent-core-packaging.js +5 -5
- package/src/utils/agent-core-packaging.js.map +1 -1
- package/src/utils/api-constructs/api-constructs.d.ts +5 -0
- package/src/utils/api-constructs/api-constructs.js +8 -0
- package/src/utils/api-constructs/api-constructs.js.map +1 -1
- package/src/utils/api-constructs/files/cdk/app/apis/http/__apiNameKebabCase__.ts.template +4 -2
- package/src/utils/api-constructs/files/cdk/app/apis/rest/__apiNameKebabCase__.ts.template +4 -2
- package/src/utils/ast.d.ts +10 -0
- package/src/utils/ast.js +33 -0
- package/src/utils/ast.js.map +1 -1
- package/src/utils/dependencies.js +1 -10
- package/src/utils/dependencies.js.map +1 -1
- package/src/utils/docker.d.ts +11 -0
- package/src/utils/docker.js +16 -3
- package/src/utils/docker.js.map +1 -1
- package/src/utils/dynamodb-config.d.ts +48 -0
- package/src/utils/dynamodb-config.js +44 -0
- package/src/utils/dynamodb-config.js.map +1 -0
- package/src/utils/dynamodb-constructs/files/cdk/app/dynamodb/__nameKebabCase__.ts.template +8 -4
- package/src/utils/git-secrets.d.ts +28 -0
- package/src/utils/git-secrets.js +66 -0
- package/src/utils/git-secrets.js.map +1 -0
- package/src/utils/init.js +7 -6
- package/src/utils/init.js.map +1 -1
- package/src/utils/local-project-dependency.d.ts +28 -0
- package/src/utils/local-project-dependency.js +40 -0
- package/src/utils/local-project-dependency.js.map +1 -0
- package/src/utils/nx.d.ts +14 -0
- package/src/utils/nx.js +30 -0
- package/src/utils/nx.js.map +1 -1
- package/src/utils/rdb-constructs/files/cdk/app/dbs/__nameKebabCase__.ts.template +11 -6
- package/src/utils/script-project-targets.d.ts +26 -0
- package/src/utils/script-project-targets.js +75 -0
- package/src/utils/script-project-targets.js.map +1 -0
- package/src/utils/shared-shadcn.d.ts +13 -0
- package/src/utils/shared-shadcn.js +3 -2
- package/src/utils/shared-shadcn.js.map +1 -1
- package/src/utils/test.js +15 -0
- package/src/utils/test.js.map +1 -1
- package/src/utils/version-upgrade-migration/sync-vended-versions.js +4 -4
- package/src/utils/version-upgrade-migration/sync-vended-versions.js.map +1 -1
- package/src/utils/versions.d.ts +13 -9
- package/src/utils/versions.js +17 -8
- package/src/utils/versions.js.map +1 -1
- package/src/utils/website-constructs/files/cdk/app/static-websites/__websiteNameKebabCase__.ts.template +8 -4
- package/src/py/dynamodb/files/__name__/__init__.py.template +0 -6
- package/src/py/dynamodb/files/__name__/entities/__init__.py.template +0 -4
- package/src/py/dynamodb/files/config.json.template +0 -24
- package/src/ts/dynamodb/files/config.json.template +0 -24
- package/src/ts/dynamodb/files/src/entities/index.ts.template +0 -1
- package/src/ts/dynamodb/files/src/index.ts.template +0 -2
package/src/utils/ast.d.ts
CHANGED
|
@@ -28,6 +28,16 @@ export declare const addSingleImport: (tree: Tree, filePath: string, variableNam
|
|
|
28
28
|
* Note that this will create the file if it does not exist in the tree.
|
|
29
29
|
*/
|
|
30
30
|
export declare const addStarExport: (tree: Tree, filePath: string, from: string) => Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Adds an `export { <names> } from '<from>';` statement to the given TypeScript
|
|
33
|
+
* file, merging into an existing re-export of the same module when there is one.
|
|
34
|
+
* Note that this will create the file if it does not exist in the tree.
|
|
35
|
+
*
|
|
36
|
+
* Adding to whatever is already there is what lets a barrel the user curates —
|
|
37
|
+
* their own entities, models or components — survive a generator re-run while
|
|
38
|
+
* the generated entry is still registered.
|
|
39
|
+
*/
|
|
40
|
+
export declare const addNamedExport: (tree: Tree, filePath: string, names: string[], from: string) => Promise<void>;
|
|
31
41
|
/**
|
|
32
42
|
* Return whether or not the given identifier is exported in the source file.
|
|
33
43
|
* Checks for both `export { Identifier }` and `export type Identifier = ...`.
|
package/src/utils/ast.js
CHANGED
|
@@ -138,6 +138,39 @@ export const addDestructuredImport = async (tree, filePath, variableNames, from)
|
|
|
138
138
|
// Prepend new export to file
|
|
139
139
|
tree.write(filePath, `export * from '${from}';\n${contents}`);
|
|
140
140
|
};
|
|
141
|
+
/**
|
|
142
|
+
* Adds an `export { <names> } from '<from>';` statement to the given TypeScript
|
|
143
|
+
* file, merging into an existing re-export of the same module when there is one.
|
|
144
|
+
* Note that this will create the file if it does not exist in the tree.
|
|
145
|
+
*
|
|
146
|
+
* Adding to whatever is already there is what lets a barrel the user curates —
|
|
147
|
+
* their own entities, models or components — survive a generator re-run while
|
|
148
|
+
* the generated entry is still registered.
|
|
149
|
+
*/ export const addNamedExport = async (tree, filePath, names, from)=>{
|
|
150
|
+
from = normalizeModuleSpecifier(tree, from);
|
|
151
|
+
const contents = tree.read(filePath)?.toString() ?? '';
|
|
152
|
+
if (!contents.trim()) {
|
|
153
|
+
tree.write(filePath, `export { ${names.join(', ')} } from '${from}';\n`);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const hasExistingExport = await matchGritQL(tree, filePath, `\`export { $_ } from '${from}'\``);
|
|
157
|
+
if (hasExistingExport) {
|
|
158
|
+
for (const name of names){
|
|
159
|
+
await applyGritQL(tree, filePath, `\`export { $names } from '${from}'\` => \`export { $names, ${name} } from '${from}'\` where { $names <: not contains \`${name}\` }`);
|
|
160
|
+
}
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const missing = [];
|
|
164
|
+
for (const name of names){
|
|
165
|
+
if (!await matchGritQL(tree, filePath, `\`${name}\``)) {
|
|
166
|
+
missing.push(name);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (missing.length === 0) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
tree.write(filePath, `export { ${missing.join(', ')} } from '${from}';\n${contents}`);
|
|
173
|
+
};
|
|
141
174
|
/**
|
|
142
175
|
* Return whether or not the given identifier is exported in the source file.
|
|
143
176
|
* Checks for both `export { Identifier }` and `export type Identifier = ...`.
|
package/src/utils/ast.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../packages/nx-plugin/src/utils/ast.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { QueryBuilder } from '@getgrit/gritql';\nimport type { Tree } from '@nx/devkit';\nimport * as path from 'path';\nimport { updateGitIgnore } from './git.js';\nimport { isEsmWorkspace } from './module-format.js';\n\nconst GRIT_DIR = '.grit';\n\n/**\n * Normalize a relative module specifier for the workspace's module format.\n * ESM (`nodenext`) requires an explicit `.js` extension on relative imports;\n * CommonJS (`node16`) resolves them extensionless. Callers always pass the ESM\n * form (with `.js`); this strips the extension for CommonJS workspaces.\n */\nconst normalizeModuleSpecifier = (tree: Tree, from: string): string => {\n if (isEsmWorkspace(tree)) {\n return from;\n }\n const isRelative = from.startsWith('./') || from.startsWith('../');\n return isRelative && from.endsWith('.js')\n ? from.slice(0, -'.js'.length)\n : from;\n};\n\n// Pin the gritql native library's \"global\" stdlib directory to <workspace>/.grit so it doesn't try to write to /usr/local/.grit (or wherever node's grandparent resolves to).\nconst ensureGritDir = (tree: Tree) => {\n process.env.GRIT_GLOBAL_DIR ??= path.join(tree.root, GRIT_DIR);\n updateGitIgnore(tree, '.', (patterns) => [...patterns, GRIT_DIR]);\n};\n\nconst assertFilePath = (tree: Tree, filePath: string) => {\n if (!tree.exists(filePath)) {\n throw new Error(`No file located at ${filePath}`);\n }\n};\n\nexport const addDestructuredImport = async (\n tree: Tree,\n filePath: string,\n variableNames: string[],\n from: string,\n) => {\n assertFilePath(tree, filePath);\n from = normalizeModuleSpecifier(tree, from);\n\n // Check if there's an existing import from this module\n const hasExistingImport = await matchGritQL(\n tree,\n filePath,\n `\\`import { $_ } from '${from}'\\``,\n );\n\n if (hasExistingImport) {\n // For each new variable, use GritQL rewrite to add it if not already present\n for (const variableName of variableNames) {\n const localName = variableName.includes(' as ')\n ? variableName.split(' as ')[1]\n : variableName;\n // Use rewrite (=>) which correctly handles comma-separated import specifier lists\n await applyGritQL(\n tree,\n filePath,\n `\\`import { $imports } from '${from}'\\` => \\`import { $imports, ${variableName} } from '${from}'\\` where { $imports <: not contains \\`${localName}\\` }`,\n );\n }\n } else {\n // No existing import — prepend a new one (after shebang if present)\n const specifiers = variableNames.join(', ');\n const contents = tree.read(filePath)!.toString();\n const newImport = `import { ${specifiers} } from '${from}';\\n`;\n if (contents.startsWith('#!')) {\n const newlineIndex = contents.indexOf('\\n');\n tree.write(\n filePath,\n `${contents.substring(0, newlineIndex + 1)}${newImport}${contents.substring(newlineIndex + 1)}`,\n );\n } else {\n tree.write(filePath, `${newImport}${contents}`);\n }\n }\n};\n\n/**\n * Ensure a Python `from <module> import <name1>, <name2>, ...` statement\n * exists in the given file. If the module already has a `from <module> import`\n * line, any missing names are appended; otherwise a fresh import statement is\n * prepended to the file (ruff's import sorter will reorder it into place on\n * the next format pass).\n *\n * We try to append first (which only succeeds if an import-from for the\n * module already exists AND the name isn't yet in the list). If nothing\n * was rewritten after trying every requested name, we fall back to checking\n * whether the module is imported at all; if not, we prepend a fresh\n * `from <module> import ...` line.\n */\nexport const addPythonDestructuredImport = async (\n tree: Tree,\n filePath: string,\n variableNames: string[],\n from: string,\n) => {\n assertFilePath(tree, filePath);\n\n // Determine whether there's any `from <module> import ...` line in the file.\n // We detect this by attempting a no-op transformation — the file is unchanged,\n // but a successful match tells us the line exists.\n const beforeContents = tree.read(filePath)!.toString();\n let moduleAlreadyImported = false;\n for (const variableName of variableNames) {\n // Appends the name if the import-from exists and the name isn't already there.\n const appended = await applyGritQL(\n tree,\n filePath,\n `language python\\n\\`from ${from} import $names\\` where { $names <: not contains \\`${variableName}\\`, $names += \\`, ${variableName}\\` }`,\n );\n if (appended) {\n moduleAlreadyImported = true;\n }\n }\n\n // If the append succeeded for at least one name, we know the module line\n // exists; we're done. If nothing was appended we need to distinguish\n // between \"module not imported\" (need to add a new line) and \"module\n // imported but all names already present\" (no-op).\n if (moduleAlreadyImported) {\n return;\n }\n\n const allAlreadyPresent = await matchGritQL(\n tree,\n filePath,\n `language python\\n\\`from ${from} import $names\\` where { ${variableNames\n .map((n) => `$names <: contains \\`${n}\\``)\n .join(', ')} }`,\n );\n if (allAlreadyPresent) {\n return;\n }\n\n // No existing `from <module> import` line — prepend one. Ruff's import\n // sorter will place it in the right group on the next formatter pass.\n const specifiers = variableNames.join(', ');\n tree.write(filePath, `from ${from} import ${specifiers}\\n${beforeContents}`);\n};\n\n/**\n * Adds an `import <variableName> from '<from>'; statement to the beginning of the file,\n * if it doesn't already exist\n */\nexport const addSingleImport = async (\n tree: Tree,\n filePath: string,\n variableName: string,\n from: string,\n) => {\n assertFilePath(tree, filePath);\n from = normalizeModuleSpecifier(tree, from);\n\n // Check if default import already exists using GritQL\n const alreadyImported = await matchGritQL(\n tree,\n filePath,\n `\\`import ${variableName} from '${from}'\\``,\n );\n if (alreadyImported) {\n return;\n }\n\n // The same identifier bound to a different module would be a duplicate\n // declaration, so refuse rather than emit a file that cannot compile.\n const identifierTaken = await matchGritQL(\n tree,\n filePath,\n `\\`import ${variableName} from $module\\``,\n );\n if (identifierTaken) {\n throw new Error(\n `Cannot import ${variableName} from '${from}' in ${filePath}: ${variableName} is already imported from a different module. Rename the project so its generated identifier does not collide.`,\n );\n }\n\n // Prepend new import to file\n const contents = tree.read(filePath)!.toString();\n tree.write(filePath, `import ${variableName} from '${from}';\\n${contents}`);\n};\n\n/**\n * Adds an `export * from '<from>'; statement to the given TypeScript file.\n * Note that this will create the file if it does not exist in the tree.\n */\nexport const addStarExport = async (\n tree: Tree,\n filePath: string,\n from: string,\n) => {\n from = normalizeModuleSpecifier(tree, from);\n const contents = tree.read(filePath)?.toString() ?? '';\n\n // For empty/non-existent files, just write the export\n if (!contents.trim()) {\n tree.write(filePath, `export * from '${from}';\\n`);\n return;\n }\n\n // Check if already exported using GritQL\n const alreadyExported = await matchGritQL(\n tree,\n filePath,\n `\\`export * from '${from}'\\``,\n );\n if (alreadyExported) {\n return;\n }\n\n // Prepend new export to file\n tree.write(filePath, `export * from '${from}';\\n${contents}`);\n};\n\n/**\n * Return whether or not the given identifier is exported in the source file.\n * Checks for both `export { Identifier }` and `export type Identifier = ...`.\n */\nexport const hasExportDeclaration = async (\n tree: Tree,\n source: string,\n identifierName: string,\n): Promise<boolean> => {\n ensureGritDir(tree);\n const patterns = [\n `\\`export type ${identifierName} = $_\\``,\n `\\`export { ${identifierName} }\\``,\n `\\`export type { ${identifierName} } from $_\\``,\n ];\n\n for (const pattern of patterns) {\n try {\n const q = new QueryBuilder(`$p => $p where { $p <: ${pattern} }`);\n const result = await q.applyToFile({\n path: 'check.ts',\n content: source,\n });\n if (result !== null) return true;\n } catch {\n // Pattern didn't match, try next\n }\n }\n\n return false;\n};\n\n/**\n * Apply a GritQL pattern to a file in the Nx tree.\n */\nexport const applyGritQL = async (\n tree: Tree,\n filePath: string,\n pattern: string,\n): Promise<boolean> => {\n if (!tree.exists(filePath)) throw new Error(`No file at ${filePath}`);\n ensureGritDir(tree);\n const source = tree.read(filePath)!.toString();\n const query = new QueryBuilder(pattern);\n const result = await query.applyToFile({ path: filePath, content: source });\n if (result && result.content !== source) {\n tree.write(filePath, result.content);\n return true;\n }\n return false;\n};\n\n/**\n * Check whether a GritQL pattern matches anywhere in a file.\n * Returns true if the pattern matches at least once.\n *\n * Accepts raw GritQL, including patterns that start with a `language <name>`\n * header (e.g. `language python\\n\\`print($_)\\``) — the pattern is passed\n * straight through to QueryBuilder without any wrapping rewrite.\n */\nexport const matchGritQL = async (\n tree: Tree,\n filePath: string,\n pattern: string,\n): Promise<boolean> => {\n if (!tree.exists(filePath)) return false;\n ensureGritDir(tree);\n const source = tree.read(filePath)!.toString();\n let matched = false;\n try {\n const query = new QueryBuilder(pattern);\n query.filter(() => {\n matched = true;\n return true;\n });\n await query.applyToFile({ path: filePath, content: source });\n } catch {\n return false;\n }\n return matched;\n};\n\n/**\n * Capture the source text of the first match of a GritQL pattern.\n *\n * For example `` `dependencyCheck: $_` `` returns the whole\n * `dependencyCheck: { ... }` property text. Returns undefined if nothing\n * matches. The file is not modified.\n */\nexport const captureGritQL = async (\n tree: Tree,\n filePath: string,\n pattern: string,\n): Promise<string | undefined> => {\n if (!tree.exists(filePath)) return undefined;\n ensureGritDir(tree);\n const source = tree.read(filePath)!.toString();\n let captured: string | undefined;\n try {\n const query = new QueryBuilder(pattern);\n query.filter((node) => {\n captured ??= node.text();\n return true;\n });\n await query.applyToFile({ path: filePath, content: source });\n } catch {\n return undefined;\n }\n return captured;\n};\n\n/**\n * Like `captureGritQL`, but returns one metavariable's binding (e.g. `mod`\n * for `$mod`) instead of the whole matched node.\n */\nexport const captureGritQLVariable = async (\n tree: Tree,\n filePath: string,\n pattern: string,\n variableName: string,\n): Promise<string | undefined> => {\n if (!tree.exists(filePath)) return undefined;\n ensureGritDir(tree);\n const source = tree.read(filePath)!.toString();\n // The underlying binding lookup requires the `$` sigil.\n const name = variableName.startsWith('$') ? variableName : `$${variableName}`;\n let captured: string | undefined;\n try {\n const query = new QueryBuilder(pattern);\n query.filter((node) => {\n captured ??= node.var(name) ?? undefined;\n return true;\n });\n await query.applyToFile({ path: filePath, content: source });\n } catch {\n return undefined;\n }\n return captured;\n};\n\n/**\n * Like {@link captureAllGritQL}, but returns one metavariable's binding for every\n * match rather than the whole matched node.\n *\n * This is what lets a caller work on the value a pattern isolated — matching\n * `` `runtime: $value` `` and reading `$value` gives `Runtime.NODEJS_24_X` alone,\n * so nothing downstream has to re-derive the assignment around it.\n *\n * A match whose metavariable is unbound contributes nothing, so the result holds\n * only real bindings. Returns undefined if the pattern could not be applied,\n * which an empty array cannot express.\n */\nexport const captureAllGritQLVariable = async (\n tree: Tree,\n filePath: string,\n pattern: string,\n variableName: string,\n): Promise<string[] | undefined> => {\n if (!tree.exists(filePath)) return undefined;\n ensureGritDir(tree);\n const source = tree.read(filePath)!.toString();\n // The underlying binding lookup requires the `$` sigil.\n const name = variableName.startsWith('$') ? variableName : `$${variableName}`;\n const captured: string[] = [];\n try {\n const query = new QueryBuilder(pattern);\n query.filter((node) => {\n const bound = node.var(name);\n if (bound !== null && bound !== undefined) {\n captured.push(bound);\n }\n return true;\n });\n await query.applyToFile({ path: filePath, content: source });\n } catch {\n return undefined;\n }\n return captured;\n};\n\n/**\n * Return the text of every node matching the GritQL pattern, in document order.\n */\nexport const captureAllGritQL = async (\n tree: Tree,\n filePath: string,\n pattern: string,\n): Promise<string[]> => {\n if (!tree.exists(filePath)) return [];\n ensureGritDir(tree);\n const source = tree.read(filePath)!.toString();\n const captured: string[] = [];\n try {\n const query = new QueryBuilder(pattern);\n query.filter((node) => {\n captured.push(node.text());\n return true;\n });\n await query.applyToFile({ path: filePath, content: source });\n } catch {\n return [];\n }\n return captured;\n};\n\n/**\n * A unique token that is a valid identifier, so it can stand in for an array\n * element or object property inside a GritQL rewrite.\n */\nexport const GRIT_INSERT_PLACEHOLDER = '__GRIT_INSERT_PLACEHOLDER__';\n\n/**\n * Apply a GritQL rewrite that inserts {@link GRIT_INSERT_PLACEHOLDER}, then\n * replace the placeholder with `text`. Routing user-provided text through a\n * placeholder keeps it out of the GritQL pattern, where quotes, backticks or\n * `${...}` would otherwise break parsing.\n *\n * Any separator (e.g. a leading `, ` when appending to an array) must be part\n * of the GritQL pattern, not `text`. The one adjustment made here: if the\n * placeholder lands after a trailing line comment (`// ...`) — which would\n * comment out the text — the text is moved to its own line.\n *\n * The caller formats afterwards. Returns true if the pattern matched and the\n * file changed.\n */\nexport const insertViaGritQL = async (\n tree: Tree,\n filePath: string,\n pattern: string,\n text: string,\n): Promise<boolean> => {\n if (!(await applyGritQL(tree, filePath, pattern))) return false;\n const content = tree.read(filePath)!.toString();\n const at = content.indexOf(GRIT_INSERT_PLACEHOLDER);\n const lineSoFar = content.slice(content.lastIndexOf('\\n', at) + 1, at);\n const replacement = lineSoFar.includes('//') ? `\\n${text}` : text;\n\n // Function replacer so `$` in `text` isn't treated as a replacement pattern.\n tree.write(\n filePath,\n content.replace(GRIT_INSERT_PLACEHOLDER, () => replacement),\n );\n return true;\n};\n\n/**\n * Append `entry` to an array, skipping arrays that already contain it, e.g.\n * `` appendToArrayViaGritQL(tree, file, 'tools:', 'myTool', { scope: 'new Agent($_)' }) ``\n * turns `tools: [a, b]` into `tools: [a, b, myTool]`.\n *\n * `arrayPrefix` is whatever precedes the array literal, including its separator —\n * `'tools:'` for an object property, `'tools='` for a keyword argument or\n * assignment, `'\"tools\":'` for a quoted key. Which separator applies depends on\n * the construct rather than the language, so the caller states it.\n *\n * Appends after the last *element* rather than rewriting the element list with\n * `=>`. GritQL binds a `[$items]` list to its trailing comma too, so\n * `` `[$items]` => `[$items, entry]` `` emits the sparse array `[a, b, , entry]`\n * once the formatter has wrapped the array — a hole that is `undefined` at\n * runtime. (Accumulating with `$items += \", entry\"` is safe by comparison, since\n * it appends within the list rather than re-emitting it; both forms appear in\n * this repo.)\n *\n * `scope` limits the match to an enclosing pattern, so same-named arrays\n * elsewhere in the file are left alone. `skipIfContains` overrides what the\n * append dedupes on, for when the rendered entry is not the identifier to match.\n *\n * The caller formats afterwards. Returns true if the entry was appended.\n */\nexport const appendToArrayViaGritQL = async (\n tree: Tree,\n filePath: string,\n arrayPrefix: string,\n entry: string,\n options: { scope?: string; skipIfContains?: string; language?: 'py' } = {},\n): Promise<boolean> => {\n const { scope, skipIfContains, language } = options;\n const within = (variable: string) =>\n scope ? `${variable} <: within \\`${scope}\\`, ` : '';\n const pattern =\n `or {` +\n // An empty array has no last element to append after.\n ` \\`${arrayPrefix}$arr\\` where { $arr <: \\`[]\\`, ${within('$arr')}` +\n ` $arr => \\`[${GRIT_INSERT_PLACEHOLDER}]\\` },` +\n // Dedupe on the list, append after its last element.\n ` \\`${arrayPrefix}[$items]\\` where {` +\n ` $items <: not contains \\`${skipIfContains ?? entry}\\`, ${within('$items')}` +\n ` $items <: [$..., $last], $last += \\`, ${GRIT_INSERT_PLACEHOLDER}\\` }` +\n ` }`;\n\n return insertViaGritQL(\n tree,\n filePath,\n language === 'py' ? `language python\\n${pattern}` : pattern,\n entry,\n );\n};\n"],"names":["QueryBuilder","path","updateGitIgnore","isEsmWorkspace","GRIT_DIR","normalizeModuleSpecifier","tree","from","isRelative","startsWith","endsWith","slice","length","ensureGritDir","process","env","GRIT_GLOBAL_DIR","join","root","patterns","assertFilePath","filePath","exists","Error","addDestructuredImport","variableNames","hasExistingImport","matchGritQL","variableName","localName","includes","split","applyGritQL","specifiers","contents","read","toString","newImport","newlineIndex","indexOf","write","substring","addPythonDestructuredImport","beforeContents","moduleAlreadyImported","appended","allAlreadyPresent","map","n","addSingleImport","alreadyImported","identifierTaken","addStarExport","trim","alreadyExported","hasExportDeclaration","source","identifierName","pattern","q","result","applyToFile","content","query","matched","filter","captureGritQL","undefined","captured","node","text","captureGritQLVariable","name","var","captureAllGritQLVariable","bound","push","captureAllGritQL","GRIT_INSERT_PLACEHOLDER","insertViaGritQL","at","lineSoFar","lastIndexOf","replacement","replace","appendToArrayViaGritQL","arrayPrefix","entry","options","scope","skipIfContains","language","within","variable"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../packages/nx-plugin/src/utils/ast.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { QueryBuilder } from '@getgrit/gritql';\nimport type { Tree } from '@nx/devkit';\nimport * as path from 'path';\nimport { updateGitIgnore } from './git.js';\nimport { isEsmWorkspace } from './module-format.js';\n\nconst GRIT_DIR = '.grit';\n\n/**\n * Normalize a relative module specifier for the workspace's module format.\n * ESM (`nodenext`) requires an explicit `.js` extension on relative imports;\n * CommonJS (`node16`) resolves them extensionless. Callers always pass the ESM\n * form (with `.js`); this strips the extension for CommonJS workspaces.\n */\nconst normalizeModuleSpecifier = (tree: Tree, from: string): string => {\n if (isEsmWorkspace(tree)) {\n return from;\n }\n const isRelative = from.startsWith('./') || from.startsWith('../');\n return isRelative && from.endsWith('.js')\n ? from.slice(0, -'.js'.length)\n : from;\n};\n\n// Pin the gritql native library's \"global\" stdlib directory to <workspace>/.grit so it doesn't try to write to /usr/local/.grit (or wherever node's grandparent resolves to).\nconst ensureGritDir = (tree: Tree) => {\n process.env.GRIT_GLOBAL_DIR ??= path.join(tree.root, GRIT_DIR);\n updateGitIgnore(tree, '.', (patterns) => [...patterns, GRIT_DIR]);\n};\n\nconst assertFilePath = (tree: Tree, filePath: string) => {\n if (!tree.exists(filePath)) {\n throw new Error(`No file located at ${filePath}`);\n }\n};\n\nexport const addDestructuredImport = async (\n tree: Tree,\n filePath: string,\n variableNames: string[],\n from: string,\n) => {\n assertFilePath(tree, filePath);\n from = normalizeModuleSpecifier(tree, from);\n\n // Check if there's an existing import from this module\n const hasExistingImport = await matchGritQL(\n tree,\n filePath,\n `\\`import { $_ } from '${from}'\\``,\n );\n\n if (hasExistingImport) {\n // For each new variable, use GritQL rewrite to add it if not already present\n for (const variableName of variableNames) {\n const localName = variableName.includes(' as ')\n ? variableName.split(' as ')[1]\n : variableName;\n // Use rewrite (=>) which correctly handles comma-separated import specifier lists\n await applyGritQL(\n tree,\n filePath,\n `\\`import { $imports } from '${from}'\\` => \\`import { $imports, ${variableName} } from '${from}'\\` where { $imports <: not contains \\`${localName}\\` }`,\n );\n }\n } else {\n // No existing import — prepend a new one (after shebang if present)\n const specifiers = variableNames.join(', ');\n const contents = tree.read(filePath)!.toString();\n const newImport = `import { ${specifiers} } from '${from}';\\n`;\n if (contents.startsWith('#!')) {\n const newlineIndex = contents.indexOf('\\n');\n tree.write(\n filePath,\n `${contents.substring(0, newlineIndex + 1)}${newImport}${contents.substring(newlineIndex + 1)}`,\n );\n } else {\n tree.write(filePath, `${newImport}${contents}`);\n }\n }\n};\n\n/**\n * Ensure a Python `from <module> import <name1>, <name2>, ...` statement\n * exists in the given file. If the module already has a `from <module> import`\n * line, any missing names are appended; otherwise a fresh import statement is\n * prepended to the file (ruff's import sorter will reorder it into place on\n * the next format pass).\n *\n * We try to append first (which only succeeds if an import-from for the\n * module already exists AND the name isn't yet in the list). If nothing\n * was rewritten after trying every requested name, we fall back to checking\n * whether the module is imported at all; if not, we prepend a fresh\n * `from <module> import ...` line.\n */\nexport const addPythonDestructuredImport = async (\n tree: Tree,\n filePath: string,\n variableNames: string[],\n from: string,\n) => {\n assertFilePath(tree, filePath);\n\n // Determine whether there's any `from <module> import ...` line in the file.\n // We detect this by attempting a no-op transformation — the file is unchanged,\n // but a successful match tells us the line exists.\n const beforeContents = tree.read(filePath)!.toString();\n let moduleAlreadyImported = false;\n for (const variableName of variableNames) {\n // Appends the name if the import-from exists and the name isn't already there.\n const appended = await applyGritQL(\n tree,\n filePath,\n `language python\\n\\`from ${from} import $names\\` where { $names <: not contains \\`${variableName}\\`, $names += \\`, ${variableName}\\` }`,\n );\n if (appended) {\n moduleAlreadyImported = true;\n }\n }\n\n // If the append succeeded for at least one name, we know the module line\n // exists; we're done. If nothing was appended we need to distinguish\n // between \"module not imported\" (need to add a new line) and \"module\n // imported but all names already present\" (no-op).\n if (moduleAlreadyImported) {\n return;\n }\n\n const allAlreadyPresent = await matchGritQL(\n tree,\n filePath,\n `language python\\n\\`from ${from} import $names\\` where { ${variableNames\n .map((n) => `$names <: contains \\`${n}\\``)\n .join(', ')} }`,\n );\n if (allAlreadyPresent) {\n return;\n }\n\n // No existing `from <module> import` line — prepend one. Ruff's import\n // sorter will place it in the right group on the next formatter pass.\n const specifiers = variableNames.join(', ');\n tree.write(filePath, `from ${from} import ${specifiers}\\n${beforeContents}`);\n};\n\n/**\n * Adds an `import <variableName> from '<from>'; statement to the beginning of the file,\n * if it doesn't already exist\n */\nexport const addSingleImport = async (\n tree: Tree,\n filePath: string,\n variableName: string,\n from: string,\n) => {\n assertFilePath(tree, filePath);\n from = normalizeModuleSpecifier(tree, from);\n\n // Check if default import already exists using GritQL\n const alreadyImported = await matchGritQL(\n tree,\n filePath,\n `\\`import ${variableName} from '${from}'\\``,\n );\n if (alreadyImported) {\n return;\n }\n\n // The same identifier bound to a different module would be a duplicate\n // declaration, so refuse rather than emit a file that cannot compile.\n const identifierTaken = await matchGritQL(\n tree,\n filePath,\n `\\`import ${variableName} from $module\\``,\n );\n if (identifierTaken) {\n throw new Error(\n `Cannot import ${variableName} from '${from}' in ${filePath}: ${variableName} is already imported from a different module. Rename the project so its generated identifier does not collide.`,\n );\n }\n\n // Prepend new import to file\n const contents = tree.read(filePath)!.toString();\n tree.write(filePath, `import ${variableName} from '${from}';\\n${contents}`);\n};\n\n/**\n * Adds an `export * from '<from>'; statement to the given TypeScript file.\n * Note that this will create the file if it does not exist in the tree.\n */\nexport const addStarExport = async (\n tree: Tree,\n filePath: string,\n from: string,\n) => {\n from = normalizeModuleSpecifier(tree, from);\n const contents = tree.read(filePath)?.toString() ?? '';\n\n // For empty/non-existent files, just write the export\n if (!contents.trim()) {\n tree.write(filePath, `export * from '${from}';\\n`);\n return;\n }\n\n // Check if already exported using GritQL\n const alreadyExported = await matchGritQL(\n tree,\n filePath,\n `\\`export * from '${from}'\\``,\n );\n if (alreadyExported) {\n return;\n }\n\n // Prepend new export to file\n tree.write(filePath, `export * from '${from}';\\n${contents}`);\n};\n\n/**\n * Adds an `export { <names> } from '<from>';` statement to the given TypeScript\n * file, merging into an existing re-export of the same module when there is one.\n * Note that this will create the file if it does not exist in the tree.\n *\n * Adding to whatever is already there is what lets a barrel the user curates —\n * their own entities, models or components — survive a generator re-run while\n * the generated entry is still registered.\n */\nexport const addNamedExport = async (\n tree: Tree,\n filePath: string,\n names: string[],\n from: string,\n) => {\n from = normalizeModuleSpecifier(tree, from);\n const contents = tree.read(filePath)?.toString() ?? '';\n\n if (!contents.trim()) {\n tree.write(filePath, `export { ${names.join(', ')} } from '${from}';\\n`);\n return;\n }\n\n const hasExistingExport = await matchGritQL(\n tree,\n filePath,\n `\\`export { $_ } from '${from}'\\``,\n );\n\n if (hasExistingExport) {\n for (const name of names) {\n await applyGritQL(\n tree,\n filePath,\n `\\`export { $names } from '${from}'\\` => \\`export { $names, ${name} } from '${from}'\\` where { $names <: not contains \\`${name}\\` }`,\n );\n }\n return;\n }\n\n const missing: string[] = [];\n for (const name of names) {\n if (!(await matchGritQL(tree, filePath, `\\`${name}\\``))) {\n missing.push(name);\n }\n }\n if (missing.length === 0) {\n return;\n }\n\n tree.write(\n filePath,\n `export { ${missing.join(', ')} } from '${from}';\\n${contents}`,\n );\n};\n\n/**\n * Return whether or not the given identifier is exported in the source file.\n * Checks for both `export { Identifier }` and `export type Identifier = ...`.\n */\nexport const hasExportDeclaration = async (\n tree: Tree,\n source: string,\n identifierName: string,\n): Promise<boolean> => {\n ensureGritDir(tree);\n const patterns = [\n `\\`export type ${identifierName} = $_\\``,\n `\\`export { ${identifierName} }\\``,\n `\\`export type { ${identifierName} } from $_\\``,\n ];\n\n for (const pattern of patterns) {\n try {\n const q = new QueryBuilder(`$p => $p where { $p <: ${pattern} }`);\n const result = await q.applyToFile({\n path: 'check.ts',\n content: source,\n });\n if (result !== null) return true;\n } catch {\n // Pattern didn't match, try next\n }\n }\n\n return false;\n};\n\n/**\n * Apply a GritQL pattern to a file in the Nx tree.\n */\nexport const applyGritQL = async (\n tree: Tree,\n filePath: string,\n pattern: string,\n): Promise<boolean> => {\n if (!tree.exists(filePath)) throw new Error(`No file at ${filePath}`);\n ensureGritDir(tree);\n const source = tree.read(filePath)!.toString();\n const query = new QueryBuilder(pattern);\n const result = await query.applyToFile({ path: filePath, content: source });\n if (result && result.content !== source) {\n tree.write(filePath, result.content);\n return true;\n }\n return false;\n};\n\n/**\n * Check whether a GritQL pattern matches anywhere in a file.\n * Returns true if the pattern matches at least once.\n *\n * Accepts raw GritQL, including patterns that start with a `language <name>`\n * header (e.g. `language python\\n\\`print($_)\\``) — the pattern is passed\n * straight through to QueryBuilder without any wrapping rewrite.\n */\nexport const matchGritQL = async (\n tree: Tree,\n filePath: string,\n pattern: string,\n): Promise<boolean> => {\n if (!tree.exists(filePath)) return false;\n ensureGritDir(tree);\n const source = tree.read(filePath)!.toString();\n let matched = false;\n try {\n const query = new QueryBuilder(pattern);\n query.filter(() => {\n matched = true;\n return true;\n });\n await query.applyToFile({ path: filePath, content: source });\n } catch {\n return false;\n }\n return matched;\n};\n\n/**\n * Capture the source text of the first match of a GritQL pattern.\n *\n * For example `` `dependencyCheck: $_` `` returns the whole\n * `dependencyCheck: { ... }` property text. Returns undefined if nothing\n * matches. The file is not modified.\n */\nexport const captureGritQL = async (\n tree: Tree,\n filePath: string,\n pattern: string,\n): Promise<string | undefined> => {\n if (!tree.exists(filePath)) return undefined;\n ensureGritDir(tree);\n const source = tree.read(filePath)!.toString();\n let captured: string | undefined;\n try {\n const query = new QueryBuilder(pattern);\n query.filter((node) => {\n captured ??= node.text();\n return true;\n });\n await query.applyToFile({ path: filePath, content: source });\n } catch {\n return undefined;\n }\n return captured;\n};\n\n/**\n * Like `captureGritQL`, but returns one metavariable's binding (e.g. `mod`\n * for `$mod`) instead of the whole matched node.\n */\nexport const captureGritQLVariable = async (\n tree: Tree,\n filePath: string,\n pattern: string,\n variableName: string,\n): Promise<string | undefined> => {\n if (!tree.exists(filePath)) return undefined;\n ensureGritDir(tree);\n const source = tree.read(filePath)!.toString();\n // The underlying binding lookup requires the `$` sigil.\n const name = variableName.startsWith('$') ? variableName : `$${variableName}`;\n let captured: string | undefined;\n try {\n const query = new QueryBuilder(pattern);\n query.filter((node) => {\n captured ??= node.var(name) ?? undefined;\n return true;\n });\n await query.applyToFile({ path: filePath, content: source });\n } catch {\n return undefined;\n }\n return captured;\n};\n\n/**\n * Like {@link captureAllGritQL}, but returns one metavariable's binding for every\n * match rather than the whole matched node.\n *\n * This is what lets a caller work on the value a pattern isolated — matching\n * `` `runtime: $value` `` and reading `$value` gives `Runtime.NODEJS_24_X` alone,\n * so nothing downstream has to re-derive the assignment around it.\n *\n * A match whose metavariable is unbound contributes nothing, so the result holds\n * only real bindings. Returns undefined if the pattern could not be applied,\n * which an empty array cannot express.\n */\nexport const captureAllGritQLVariable = async (\n tree: Tree,\n filePath: string,\n pattern: string,\n variableName: string,\n): Promise<string[] | undefined> => {\n if (!tree.exists(filePath)) return undefined;\n ensureGritDir(tree);\n const source = tree.read(filePath)!.toString();\n // The underlying binding lookup requires the `$` sigil.\n const name = variableName.startsWith('$') ? variableName : `$${variableName}`;\n const captured: string[] = [];\n try {\n const query = new QueryBuilder(pattern);\n query.filter((node) => {\n const bound = node.var(name);\n if (bound !== null && bound !== undefined) {\n captured.push(bound);\n }\n return true;\n });\n await query.applyToFile({ path: filePath, content: source });\n } catch {\n return undefined;\n }\n return captured;\n};\n\n/**\n * Return the text of every node matching the GritQL pattern, in document order.\n */\nexport const captureAllGritQL = async (\n tree: Tree,\n filePath: string,\n pattern: string,\n): Promise<string[]> => {\n if (!tree.exists(filePath)) return [];\n ensureGritDir(tree);\n const source = tree.read(filePath)!.toString();\n const captured: string[] = [];\n try {\n const query = new QueryBuilder(pattern);\n query.filter((node) => {\n captured.push(node.text());\n return true;\n });\n await query.applyToFile({ path: filePath, content: source });\n } catch {\n return [];\n }\n return captured;\n};\n\n/**\n * A unique token that is a valid identifier, so it can stand in for an array\n * element or object property inside a GritQL rewrite.\n */\nexport const GRIT_INSERT_PLACEHOLDER = '__GRIT_INSERT_PLACEHOLDER__';\n\n/**\n * Apply a GritQL rewrite that inserts {@link GRIT_INSERT_PLACEHOLDER}, then\n * replace the placeholder with `text`. Routing user-provided text through a\n * placeholder keeps it out of the GritQL pattern, where quotes, backticks or\n * `${...}` would otherwise break parsing.\n *\n * Any separator (e.g. a leading `, ` when appending to an array) must be part\n * of the GritQL pattern, not `text`. The one adjustment made here: if the\n * placeholder lands after a trailing line comment (`// ...`) — which would\n * comment out the text — the text is moved to its own line.\n *\n * The caller formats afterwards. Returns true if the pattern matched and the\n * file changed.\n */\nexport const insertViaGritQL = async (\n tree: Tree,\n filePath: string,\n pattern: string,\n text: string,\n): Promise<boolean> => {\n if (!(await applyGritQL(tree, filePath, pattern))) return false;\n const content = tree.read(filePath)!.toString();\n const at = content.indexOf(GRIT_INSERT_PLACEHOLDER);\n const lineSoFar = content.slice(content.lastIndexOf('\\n', at) + 1, at);\n const replacement = lineSoFar.includes('//') ? `\\n${text}` : text;\n\n // Function replacer so `$` in `text` isn't treated as a replacement pattern.\n tree.write(\n filePath,\n content.replace(GRIT_INSERT_PLACEHOLDER, () => replacement),\n );\n return true;\n};\n\n/**\n * Append `entry` to an array, skipping arrays that already contain it, e.g.\n * `` appendToArrayViaGritQL(tree, file, 'tools:', 'myTool', { scope: 'new Agent($_)' }) ``\n * turns `tools: [a, b]` into `tools: [a, b, myTool]`.\n *\n * `arrayPrefix` is whatever precedes the array literal, including its separator —\n * `'tools:'` for an object property, `'tools='` for a keyword argument or\n * assignment, `'\"tools\":'` for a quoted key. Which separator applies depends on\n * the construct rather than the language, so the caller states it.\n *\n * Appends after the last *element* rather than rewriting the element list with\n * `=>`. GritQL binds a `[$items]` list to its trailing comma too, so\n * `` `[$items]` => `[$items, entry]` `` emits the sparse array `[a, b, , entry]`\n * once the formatter has wrapped the array — a hole that is `undefined` at\n * runtime. (Accumulating with `$items += \", entry\"` is safe by comparison, since\n * it appends within the list rather than re-emitting it; both forms appear in\n * this repo.)\n *\n * `scope` limits the match to an enclosing pattern, so same-named arrays\n * elsewhere in the file are left alone. `skipIfContains` overrides what the\n * append dedupes on, for when the rendered entry is not the identifier to match.\n *\n * The caller formats afterwards. Returns true if the entry was appended.\n */\nexport const appendToArrayViaGritQL = async (\n tree: Tree,\n filePath: string,\n arrayPrefix: string,\n entry: string,\n options: { scope?: string; skipIfContains?: string; language?: 'py' } = {},\n): Promise<boolean> => {\n const { scope, skipIfContains, language } = options;\n const within = (variable: string) =>\n scope ? `${variable} <: within \\`${scope}\\`, ` : '';\n const pattern =\n `or {` +\n // An empty array has no last element to append after.\n ` \\`${arrayPrefix}$arr\\` where { $arr <: \\`[]\\`, ${within('$arr')}` +\n ` $arr => \\`[${GRIT_INSERT_PLACEHOLDER}]\\` },` +\n // Dedupe on the list, append after its last element.\n ` \\`${arrayPrefix}[$items]\\` where {` +\n ` $items <: not contains \\`${skipIfContains ?? entry}\\`, ${within('$items')}` +\n ` $items <: [$..., $last], $last += \\`, ${GRIT_INSERT_PLACEHOLDER}\\` }` +\n ` }`;\n\n return insertViaGritQL(\n tree,\n filePath,\n language === 'py' ? `language python\\n${pattern}` : pattern,\n entry,\n );\n};\n"],"names":["QueryBuilder","path","updateGitIgnore","isEsmWorkspace","GRIT_DIR","normalizeModuleSpecifier","tree","from","isRelative","startsWith","endsWith","slice","length","ensureGritDir","process","env","GRIT_GLOBAL_DIR","join","root","patterns","assertFilePath","filePath","exists","Error","addDestructuredImport","variableNames","hasExistingImport","matchGritQL","variableName","localName","includes","split","applyGritQL","specifiers","contents","read","toString","newImport","newlineIndex","indexOf","write","substring","addPythonDestructuredImport","beforeContents","moduleAlreadyImported","appended","allAlreadyPresent","map","n","addSingleImport","alreadyImported","identifierTaken","addStarExport","trim","alreadyExported","addNamedExport","names","hasExistingExport","name","missing","push","hasExportDeclaration","source","identifierName","pattern","q","result","applyToFile","content","query","matched","filter","captureGritQL","undefined","captured","node","text","captureGritQLVariable","var","captureAllGritQLVariable","bound","captureAllGritQL","GRIT_INSERT_PLACEHOLDER","insertViaGritQL","at","lineSoFar","lastIndexOf","replacement","replace","appendToArrayViaGritQL","arrayPrefix","entry","options","scope","skipIfContains","language","within","variable"],"mappings":"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"}
|
|
@@ -113,15 +113,6 @@ export const catalogsEnabled = (tree)=>{
|
|
|
113
113
|
}
|
|
114
114
|
return callback;
|
|
115
115
|
};
|
|
116
|
-
// Nx generators read these versions from the root manifest and coerce without
|
|
117
|
-
// a null guard; Nx ships no bun catalog manager, so an unresolved `catalog:`
|
|
118
|
-
// crashes them. Keep direct ranges on bun (react-dom pinned with react).
|
|
119
|
-
const BUN_INTROSPECTED_PACKAGES = new Set([
|
|
120
|
-
'vite',
|
|
121
|
-
'react',
|
|
122
|
-
'react-dom'
|
|
123
|
-
]);
|
|
124
|
-
const isCatalogExcluded = (tree, packageName)=>detectWorkspacePackageManager(tree) === 'bun' && BUN_INTROSPECTED_PACKAGES.has(packageName);
|
|
125
116
|
// Convert direct version ranges to `catalog:` references in a single manifest
|
|
126
117
|
// and record the range in the workspace catalog. Protocol specifiers
|
|
127
118
|
// (catalog:/workspace:/...) are left alone.
|
|
@@ -134,7 +125,7 @@ const convertDependenciesToCatalog = (tree, packageJsonPath, packageNames)=>{
|
|
|
134
125
|
]){
|
|
135
126
|
for (const packageName of packageNames){
|
|
136
127
|
const version = json[field]?.[packageName];
|
|
137
|
-
if (version && !version.includes(':')
|
|
128
|
+
if (version && !version.includes(':')) {
|
|
138
129
|
catalogUpdates[packageName] = version;
|
|
139
130
|
json[field][packageName] = 'catalog:';
|
|
140
131
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../packages/nx-plugin/src/utils/dependencies.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n detectPackageManager,\n addDependenciesToPackageJson as devkitAddDependenciesToPackageJson,\n type GeneratorCallback,\n getPackageManagerVersion,\n type PackageManager,\n readJson,\n type Tree,\n updateJson,\n} from '@nx/devkit';\nimport yaml from 'js-yaml';\nimport { coerce, gt, gte } from 'semver';\nimport { readAwsNxPluginConfig } from './config/utils.js';\n\n// Minimum version that introduced catalog support per package manager (npm has none).\nconst CATALOG_SUPPORT: Partial<Record<PackageManager, string>> = {\n pnpm: '9.5.0',\n yarn: '4.10.0',\n bun: '1.2.14',\n};\n\n// Prefer tree markers over devkit's `detectPackageManager`, which reads the\n// real filesystem and is nondeterministic for virtual trees in tests.\nexport const detectWorkspacePackageManager = (tree: Tree): PackageManager =>\n tree.exists('pnpm-workspace.yaml') ? 'pnpm' : detectPackageManager(tree.root);\n\n// Coerce a single simple range to a version. Rejects compound ranges\n// (whitespace / `||`), which coerce would silently reduce, plus tags/protocols.\nconst parseSimpleRange = (version: string) => {\n const trimmed = version.trim();\n if (/\\s|\\|\\|/.test(trimmed)) {\n return undefined;\n }\n return coerce(trimmed, { includePrerelease: true }) ?? undefined;\n};\n\nconst versionAtLeast = (version: string, minimum: string): boolean =>\n gte(\n parseSimpleRange(version) ?? '0.0.0',\n parseSimpleRange(minimum) ?? '0.0.0',\n );\n\n// Only a strict upgrade replaces an existing catalog entry, so generating a\n// project never downgrades a version the user raised. Uncomparable entries\n// (tags, complex ranges) are kept.\nconst isVersionUpgrade = (incoming: string, existing: string): boolean => {\n const existingParsed = parseSimpleRange(existing);\n const incomingParsed = parseSimpleRange(incoming);\n if (!existingParsed || !incomingParsed) {\n return false;\n }\n return gt(incomingParsed, existingParsed);\n};\n\n// Keyed by root + package manager; detecting support shells out for a version.\nconst catalogSupportCache = new Map<string, boolean>();\n\n/** Clears the catalog support cache. Only needed by tests. */\nexport const resetCatalogSupportCache = (): void => {\n catalogSupportCache.clear();\n};\n\n// Local-project specifier: the `workspace:` protocol where supported (pnpm,\n// bun, yarn berry), `*` on npm and yarn classic which reject it.\nexport const getLocalDependencySpecifier = (tree: Tree): string => {\n const packageManager = detectWorkspacePackageManager(tree);\n if (packageManager === 'npm') {\n return '*';\n }\n if (packageManager === 'yarn') {\n let major = 2;\n try {\n major = Number.parseInt(\n getPackageManagerVersion('yarn', tree.root).split('.')[0],\n 10,\n );\n } catch {\n // Not installed (e.g. tests) — assume berry.\n }\n if (major < 2) {\n return '*';\n }\n }\n return 'workspace:*';\n};\n\nexport const supportsCatalogs = (tree: Tree): boolean => {\n const packageManager = detectWorkspacePackageManager(tree);\n const cacheKey = `${tree.root}|${packageManager}`;\n const cached = catalogSupportCache.get(cacheKey);\n if (cached !== undefined) {\n return cached;\n }\n const minimumVersion = CATALOG_SUPPORT[packageManager];\n let supported = false;\n if (minimumVersion) {\n try {\n supported = versionAtLeast(\n getPackageManagerVersion(packageManager, tree.root),\n minimumVersion,\n );\n } catch {\n // Not installed (e.g. tests) — assume a modern version.\n supported = true;\n }\n }\n catalogSupportCache.set(cacheKey, supported);\n return supported;\n};\n\n// True when the package manager supports catalogs and the workspace hasn't\n// opted out via `packageManager.catalogs: false` in aws-nx-plugin.config.mts.\nexport const catalogsEnabled = (tree: Tree): boolean => {\n if (!supportsCatalogs(tree)) {\n return false;\n }\n const config = readAwsNxPluginConfig(tree);\n return config?.packageManager?.catalogs !== false;\n};\n\n/**\n * Drop-in replacement for devkit's `addDependenciesToPackageJson` that records\n * versions in the package manager's catalog when enabled. Callers pass a\n * project manifest path for runtime deps (so `noUndeclaredDependencies` passes)\n * and the root for shared tooling.\n */\nexport const addDependenciesToPackageJson = (\n tree: Tree,\n dependencies: Record<string, string>,\n devDependencies: Record<string, string>,\n packageJsonPath = 'package.json',\n): GeneratorCallback => {\n const callback = devkitAddDependenciesToPackageJson(\n tree,\n dependencies,\n devDependencies,\n packageJsonPath,\n );\n\n if (catalogsEnabled(tree)) {\n const packageNames = [\n ...Object.keys(dependencies),\n ...Object.keys(devDependencies),\n ];\n convertDependenciesToCatalog(tree, packageJsonPath, packageNames);\n // Convert any matching root ranges Nx wrote behind our back (e.g.\n // `@types/node`, `react`) so they don't resolve to a second copy alongside\n // the catalog version. Skipped when the caller already targeted the root.\n if (packageJsonPath !== 'package.json') {\n convertDependenciesToCatalog(tree, 'package.json', packageNames);\n }\n }\n\n return callback;\n};\n\n// Nx generators read these versions from the root manifest and coerce without\n// a null guard; Nx ships no bun catalog manager, so an unresolved `catalog:`\n// crashes them. Keep direct ranges on bun (react-dom pinned with react).\nconst BUN_INTROSPECTED_PACKAGES = new Set<string>([\n 'vite',\n 'react',\n 'react-dom',\n]);\n\nconst isCatalogExcluded = (tree: Tree, packageName: string): boolean =>\n detectWorkspacePackageManager(tree) === 'bun' &&\n BUN_INTROSPECTED_PACKAGES.has(packageName);\n\n// Convert direct version ranges to `catalog:` references in a single manifest\n// and record the range in the workspace catalog. Protocol specifiers\n// (catalog:/workspace:/...) are left alone.\nconst convertDependenciesToCatalog = (\n tree: Tree,\n packageJsonPath: string,\n packageNames: string[],\n): void => {\n const catalogUpdates: Record<string, string> = {};\n\n updateJson(tree, packageJsonPath, (json) => {\n for (const field of ['dependencies', 'devDependencies'] as const) {\n for (const packageName of packageNames) {\n const version = json[field]?.[packageName];\n if (\n version &&\n !version.includes(':') &&\n !isCatalogExcluded(tree, packageName)\n ) {\n catalogUpdates[packageName] = version;\n json[field][packageName] = 'catalog:';\n }\n }\n }\n return json;\n });\n\n if (Object.keys(catalogUpdates).length > 0) {\n writeCatalogVersions(tree, catalogUpdates);\n }\n};\n\n// Record ranges in the default catalog, creating it if absent. Only strict\n// upgrades replace existing entries (see `isVersionUpgrade`).\nconst writeCatalogVersions = (\n tree: Tree,\n updates: Record<string, string>,\n): void => {\n const applyUpdates = (\n catalog: Record<string, string> | undefined,\n ): Record<string, string> | undefined => {\n const existing = catalog ?? {};\n const applicable = Object.fromEntries(\n Object.entries(updates).filter(\n ([name, version]) =>\n existing[name] === undefined ||\n isVersionUpgrade(version, existing[name]),\n ),\n );\n if (Object.keys(applicable).length === 0) {\n return undefined;\n }\n return { ...existing, ...applicable };\n };\n\n switch (detectWorkspacePackageManager(tree)) {\n case 'pnpm': {\n const workspaceYaml =\n (yaml.load(tree.read('pnpm-workspace.yaml', 'utf-8') ?? '') as Record<\n string,\n unknown\n >) ?? {};\n const catalog = applyUpdates(\n workspaceYaml.catalog as Record<string, string> | undefined,\n );\n if (catalog) {\n tree.write(\n 'pnpm-workspace.yaml',\n yaml.dump({ ...workspaceYaml, catalog }, { quotingType: \"'\" }),\n );\n }\n break;\n }\n case 'yarn': {\n const yarnRc =\n (yaml.load(tree.read('.yarnrc.yml', 'utf-8') ?? '') as Record<\n string,\n unknown\n >) ?? {};\n const catalog = applyUpdates(\n yarnRc.catalog as Record<string, string> | undefined,\n );\n if (catalog) {\n tree.write(\n '.yarnrc.yml',\n yaml.dump({ ...yarnRc, catalog }, { quotingType: \"'\" }),\n );\n }\n break;\n }\n case 'bun': {\n const catalog = applyUpdates(\n readJson<{ catalog?: Record<string, string> }>(tree, 'package.json')\n .catalog,\n );\n if (catalog) {\n updateJson(tree, 'package.json', (json) => ({ ...json, catalog }));\n }\n break;\n }\n }\n};\n"],"names":["detectPackageManager","addDependenciesToPackageJson","devkitAddDependenciesToPackageJson","getPackageManagerVersion","readJson","updateJson","yaml","coerce","gt","gte","readAwsNxPluginConfig","CATALOG_SUPPORT","pnpm","yarn","bun","detectWorkspacePackageManager","tree","exists","root","parseSimpleRange","version","trimmed","trim","test","undefined","includePrerelease","versionAtLeast","minimum","isVersionUpgrade","incoming","existing","existingParsed","incomingParsed","catalogSupportCache","Map","resetCatalogSupportCache","clear","getLocalDependencySpecifier","packageManager","major","Number","parseInt","split","supportsCatalogs","cacheKey","cached","get","minimumVersion","supported","set","catalogsEnabled","config","catalogs","dependencies","devDependencies","packageJsonPath","callback","packageNames","Object","keys","convertDependenciesToCatalog","BUN_INTROSPECTED_PACKAGES","Set","isCatalogExcluded","packageName","has","catalogUpdates","json","field","includes","length","writeCatalogVersions","updates","applyUpdates","catalog","applicable","fromEntries","entries","filter","name","workspaceYaml","load","read","write","dump","quotingType","yarnRc"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../packages/nx-plugin/src/utils/dependencies.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n detectPackageManager,\n addDependenciesToPackageJson as devkitAddDependenciesToPackageJson,\n type GeneratorCallback,\n getPackageManagerVersion,\n type PackageManager,\n readJson,\n type Tree,\n updateJson,\n} from '@nx/devkit';\nimport yaml from 'js-yaml';\nimport { coerce, gt, gte } from 'semver';\nimport { readAwsNxPluginConfig } from './config/utils.js';\n\n// Minimum version that introduced catalog support per package manager (npm has none).\nconst CATALOG_SUPPORT: Partial<Record<PackageManager, string>> = {\n pnpm: '9.5.0',\n yarn: '4.10.0',\n bun: '1.2.14',\n};\n\n// Prefer tree markers over devkit's `detectPackageManager`, which reads the\n// real filesystem and is nondeterministic for virtual trees in tests.\nexport const detectWorkspacePackageManager = (tree: Tree): PackageManager =>\n tree.exists('pnpm-workspace.yaml') ? 'pnpm' : detectPackageManager(tree.root);\n\n// Coerce a single simple range to a version. Rejects compound ranges\n// (whitespace / `||`), which coerce would silently reduce, plus tags/protocols.\nconst parseSimpleRange = (version: string) => {\n const trimmed = version.trim();\n if (/\\s|\\|\\|/.test(trimmed)) {\n return undefined;\n }\n return coerce(trimmed, { includePrerelease: true }) ?? undefined;\n};\n\nconst versionAtLeast = (version: string, minimum: string): boolean =>\n gte(\n parseSimpleRange(version) ?? '0.0.0',\n parseSimpleRange(minimum) ?? '0.0.0',\n );\n\n// Only a strict upgrade replaces an existing catalog entry, so generating a\n// project never downgrades a version the user raised. Uncomparable entries\n// (tags, complex ranges) are kept.\nconst isVersionUpgrade = (incoming: string, existing: string): boolean => {\n const existingParsed = parseSimpleRange(existing);\n const incomingParsed = parseSimpleRange(incoming);\n if (!existingParsed || !incomingParsed) {\n return false;\n }\n return gt(incomingParsed, existingParsed);\n};\n\n// Keyed by root + package manager; detecting support shells out for a version.\nconst catalogSupportCache = new Map<string, boolean>();\n\n/** Clears the catalog support cache. Only needed by tests. */\nexport const resetCatalogSupportCache = (): void => {\n catalogSupportCache.clear();\n};\n\n// Local-project specifier: the `workspace:` protocol where supported (pnpm,\n// bun, yarn berry), `*` on npm and yarn classic which reject it.\nexport const getLocalDependencySpecifier = (tree: Tree): string => {\n const packageManager = detectWorkspacePackageManager(tree);\n if (packageManager === 'npm') {\n return '*';\n }\n if (packageManager === 'yarn') {\n let major = 2;\n try {\n major = Number.parseInt(\n getPackageManagerVersion('yarn', tree.root).split('.')[0],\n 10,\n );\n } catch {\n // Not installed (e.g. tests) — assume berry.\n }\n if (major < 2) {\n return '*';\n }\n }\n return 'workspace:*';\n};\n\nexport const supportsCatalogs = (tree: Tree): boolean => {\n const packageManager = detectWorkspacePackageManager(tree);\n const cacheKey = `${tree.root}|${packageManager}`;\n const cached = catalogSupportCache.get(cacheKey);\n if (cached !== undefined) {\n return cached;\n }\n const minimumVersion = CATALOG_SUPPORT[packageManager];\n let supported = false;\n if (minimumVersion) {\n try {\n supported = versionAtLeast(\n getPackageManagerVersion(packageManager, tree.root),\n minimumVersion,\n );\n } catch {\n // Not installed (e.g. tests) — assume a modern version.\n supported = true;\n }\n }\n catalogSupportCache.set(cacheKey, supported);\n return supported;\n};\n\n// True when the package manager supports catalogs and the workspace hasn't\n// opted out via `packageManager.catalogs: false` in aws-nx-plugin.config.mts.\nexport const catalogsEnabled = (tree: Tree): boolean => {\n if (!supportsCatalogs(tree)) {\n return false;\n }\n const config = readAwsNxPluginConfig(tree);\n return config?.packageManager?.catalogs !== false;\n};\n\n/**\n * Drop-in replacement for devkit's `addDependenciesToPackageJson` that records\n * versions in the package manager's catalog when enabled. Callers pass a\n * project manifest path for runtime deps (so `noUndeclaredDependencies` passes)\n * and the root for shared tooling.\n */\nexport const addDependenciesToPackageJson = (\n tree: Tree,\n dependencies: Record<string, string>,\n devDependencies: Record<string, string>,\n packageJsonPath = 'package.json',\n): GeneratorCallback => {\n const callback = devkitAddDependenciesToPackageJson(\n tree,\n dependencies,\n devDependencies,\n packageJsonPath,\n );\n\n if (catalogsEnabled(tree)) {\n const packageNames = [\n ...Object.keys(dependencies),\n ...Object.keys(devDependencies),\n ];\n convertDependenciesToCatalog(tree, packageJsonPath, packageNames);\n // Convert any matching root ranges Nx wrote behind our back (e.g.\n // `@types/node`, `react`) so they don't resolve to a second copy alongside\n // the catalog version. Skipped when the caller already targeted the root.\n if (packageJsonPath !== 'package.json') {\n convertDependenciesToCatalog(tree, 'package.json', packageNames);\n }\n }\n\n return callback;\n};\n\n// Convert direct version ranges to `catalog:` references in a single manifest\n// and record the range in the workspace catalog. Protocol specifiers\n// (catalog:/workspace:/...) are left alone.\nconst convertDependenciesToCatalog = (\n tree: Tree,\n packageJsonPath: string,\n packageNames: string[],\n): void => {\n const catalogUpdates: Record<string, string> = {};\n\n updateJson(tree, packageJsonPath, (json) => {\n for (const field of ['dependencies', 'devDependencies'] as const) {\n for (const packageName of packageNames) {\n const version = json[field]?.[packageName];\n if (version && !version.includes(':')) {\n catalogUpdates[packageName] = version;\n json[field][packageName] = 'catalog:';\n }\n }\n }\n return json;\n });\n\n if (Object.keys(catalogUpdates).length > 0) {\n writeCatalogVersions(tree, catalogUpdates);\n }\n};\n\n// Record ranges in the default catalog, creating it if absent. Only strict\n// upgrades replace existing entries (see `isVersionUpgrade`).\nconst writeCatalogVersions = (\n tree: Tree,\n updates: Record<string, string>,\n): void => {\n const applyUpdates = (\n catalog: Record<string, string> | undefined,\n ): Record<string, string> | undefined => {\n const existing = catalog ?? {};\n const applicable = Object.fromEntries(\n Object.entries(updates).filter(\n ([name, version]) =>\n existing[name] === undefined ||\n isVersionUpgrade(version, existing[name]),\n ),\n );\n if (Object.keys(applicable).length === 0) {\n return undefined;\n }\n return { ...existing, ...applicable };\n };\n\n switch (detectWorkspacePackageManager(tree)) {\n case 'pnpm': {\n const workspaceYaml =\n (yaml.load(tree.read('pnpm-workspace.yaml', 'utf-8') ?? '') as Record<\n string,\n unknown\n >) ?? {};\n const catalog = applyUpdates(\n workspaceYaml.catalog as Record<string, string> | undefined,\n );\n if (catalog) {\n tree.write(\n 'pnpm-workspace.yaml',\n yaml.dump({ ...workspaceYaml, catalog }, { quotingType: \"'\" }),\n );\n }\n break;\n }\n case 'yarn': {\n const yarnRc =\n (yaml.load(tree.read('.yarnrc.yml', 'utf-8') ?? '') as Record<\n string,\n unknown\n >) ?? {};\n const catalog = applyUpdates(\n yarnRc.catalog as Record<string, string> | undefined,\n );\n if (catalog) {\n tree.write(\n '.yarnrc.yml',\n yaml.dump({ ...yarnRc, catalog }, { quotingType: \"'\" }),\n );\n }\n break;\n }\n case 'bun': {\n const catalog = applyUpdates(\n readJson<{ catalog?: Record<string, string> }>(tree, 'package.json')\n .catalog,\n );\n if (catalog) {\n updateJson(tree, 'package.json', (json) => ({ ...json, catalog }));\n }\n break;\n }\n }\n};\n"],"names":["detectPackageManager","addDependenciesToPackageJson","devkitAddDependenciesToPackageJson","getPackageManagerVersion","readJson","updateJson","yaml","coerce","gt","gte","readAwsNxPluginConfig","CATALOG_SUPPORT","pnpm","yarn","bun","detectWorkspacePackageManager","tree","exists","root","parseSimpleRange","version","trimmed","trim","test","undefined","includePrerelease","versionAtLeast","minimum","isVersionUpgrade","incoming","existing","existingParsed","incomingParsed","catalogSupportCache","Map","resetCatalogSupportCache","clear","getLocalDependencySpecifier","packageManager","major","Number","parseInt","split","supportsCatalogs","cacheKey","cached","get","minimumVersion","supported","set","catalogsEnabled","config","catalogs","dependencies","devDependencies","packageJsonPath","callback","packageNames","Object","keys","convertDependenciesToCatalog","catalogUpdates","json","field","packageName","includes","length","writeCatalogVersions","updates","applyUpdates","catalog","applicable","fromEntries","entries","filter","name","workspaceYaml","load","read","write","dump","quotingType","yarnRc"],"mappings":"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"}
|
package/src/utils/docker.d.ts
CHANGED
|
@@ -43,6 +43,17 @@ export declare const NODE_IMAGE_DEPENDENCIES: readonly [{
|
|
|
43
43
|
}, {
|
|
44
44
|
readonly name: "minimatch";
|
|
45
45
|
}];
|
|
46
|
+
/**
|
|
47
|
+
* Packages the prisma CLI install in a vended RDB migration `Dockerfile` pins.
|
|
48
|
+
* The CLI's own ranges resolve both below a known HIGH vulnerability's fix, so
|
|
49
|
+
* the image overrides them. Spread through `ownedElsewhere` for the same reason
|
|
50
|
+
* as {@link NODE_IMAGE_DEPENDENCIES}.
|
|
51
|
+
*/
|
|
52
|
+
export declare const PRISMA_IMAGE_DEPENDENCIES: readonly [{
|
|
53
|
+
readonly name: "deepmerge-ts";
|
|
54
|
+
}, {
|
|
55
|
+
readonly name: "mysql2";
|
|
56
|
+
}];
|
|
46
57
|
/**
|
|
47
58
|
* Packages the AWS Distro for OpenTelemetry install in a vended `Dockerfile`
|
|
48
59
|
* pins, for the images that auto-instrument with it. Spread through
|
package/src/utils/docker.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/ import { joinPathFragments } from "@nx/devkit";
|
|
5
5
|
import { forDependencies } from "./declared-dependencies.js";
|
|
6
6
|
import { FS_DEPENDENCIES, FsCommands } from "./fs.js";
|
|
7
|
-
import { addDependencyToTargetIfNotPresent } from "./nx.js";
|
|
7
|
+
import { addDependencyToTargetIfNotPresent, normalizeTargetKeyOrder } from "./nx.js";
|
|
8
8
|
import { containerImage, TS_VERSIONS } from "./versions.js";
|
|
9
9
|
/** Dependencies a caller must declare to add a Docker scan target. */ export const DOCKER_DEPENDENCIES = [
|
|
10
10
|
...FS_DEPENDENCIES
|
|
@@ -49,6 +49,19 @@ const TRIVY_IGNORE_CONTENTS = `# Trivy ignore file. Add one vulnerability ID (e.
|
|
|
49
49
|
name: 'minimatch'
|
|
50
50
|
}
|
|
51
51
|
];
|
|
52
|
+
/**
|
|
53
|
+
* Packages the prisma CLI install in a vended RDB migration `Dockerfile` pins.
|
|
54
|
+
* The CLI's own ranges resolve both below a known HIGH vulnerability's fix, so
|
|
55
|
+
* the image overrides them. Spread through `ownedElsewhere` for the same reason
|
|
56
|
+
* as {@link NODE_IMAGE_DEPENDENCIES}.
|
|
57
|
+
*/ export const PRISMA_IMAGE_DEPENDENCIES = [
|
|
58
|
+
{
|
|
59
|
+
name: 'deepmerge-ts'
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: 'mysql2'
|
|
63
|
+
}
|
|
64
|
+
];
|
|
52
65
|
/**
|
|
53
66
|
* Packages the AWS Distro for OpenTelemetry install in a vended `Dockerfile`
|
|
54
67
|
* pins, for the images that auto-instrument with it. Spread through
|
|
@@ -102,7 +115,7 @@ const TRIVY_IGNORE_CONTENTS = `# Trivy ignore file. Add one vulnerability ID (e.
|
|
|
102
115
|
commands.push(`${containerEngine} save -o ${joinPathFragments(scanDir, tarName)} ${imageTag}`, `${containerEngine} run --rm -v "./${scanDir}":/scan ${trivyImage} image --input /scan/${tarName} --ignorefile /scan/${TRIVY_IGNORE_FILE} --scanners vuln --severity HIGH,CRITICAL --ignore-unfixed --exit-code 1 --no-progress -q`);
|
|
103
116
|
});
|
|
104
117
|
project.targets ??= {};
|
|
105
|
-
project.targets[trivyTargetName] = {
|
|
118
|
+
project.targets[trivyTargetName] = normalizeTargetKeyOrder({
|
|
106
119
|
// The image being scanned is only reachable through the container engine,
|
|
107
120
|
// so a restored cache entry would report a pass for an image that may no
|
|
108
121
|
// longer exist. The scan re-runs and either scans the real image or fails.
|
|
@@ -120,7 +133,7 @@ const TRIVY_IGNORE_CONTENTS = `# Trivy ignore file. Add one vulnerability ID (e.
|
|
|
120
133
|
dependsOn: [
|
|
121
134
|
dockerTargetName
|
|
122
135
|
]
|
|
123
|
-
};
|
|
136
|
+
});
|
|
124
137
|
// Aggregate per-component scan targets under a single `trivy` target. The
|
|
125
138
|
// scan is intentionally NOT wired into `build`: image scanning is slow and
|
|
126
139
|
// its result depends on the ever-changing vulnerability database (a scan
|
package/src/utils/docker.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../packages/nx-plugin/src/utils/docker.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n joinPathFragments,\n type ProjectConfiguration,\n type Tree,\n} from '@nx/devkit';\nimport {\n type DependencyDeclaration,\n forDependencies,\n type MustDeclare,\n} from './declared-dependencies.js';\nimport { FS_DEPENDENCIES, FsCommands } from './fs.js';\nimport { addDependencyToTargetIfNotPresent } from './nx.js';\nimport { containerImage, type ITsDepVersion, TS_VERSIONS } from './versions.js';\n\n/** Dependencies a caller must declare to add a Docker scan target. */\nexport const DOCKER_DEPENDENCIES = [...FS_DEPENDENCIES] as const;\n\n/**\n * The `cache` setting every target that builds a container image must use.\n *\n * A built image lives in the container engine's own store, not under any\n * `outputs` path, so Nx has nothing to restore a cache hit from: it would\n * report the build complete while the image is missing, and the `trivy` scan\n * that depends on it would report a cached pass against an image that isn't\n * there. The container engine's layer cache is the sound cache here — it is\n * keyed on the build context and lives alongside the image, so an unchanged\n * context rebuilds in well under a second and always leaves the image present.\n */\nexport const IMAGE_BUILD_CACHE = false;\n\nconst TRIVY_IGNORE_FILE = '.trivyignore';\n\nconst TRIVY_IGNORE_CONTENTS = `# Trivy ignore file. Add one vulnerability ID (e.g. CVE-2021-12345) per line to\n# suppress it during the image scan (\\`nx run-many --target trivy\\`).\n# https://trivy.dev/latest/docs/configuration/filtering/#by-finding-ids\n`;\n\n/**\n * Substitution variables for the vended Node `Dockerfile` templates: the npm\n * version to install globally, and the versions its dependency overrides pin.\n */\nexport const nodeImageVersions = () => ({\n npmVersion: TS_VERSIONS.npm,\n minimatchVersion: TS_VERSIONS.minimatch,\n});\n\n/**\n * Packages a vended Node `Dockerfile` pins, which every generator writing one\n * must declare so the version sync keeps those pins current.\n *\n * Spread through `ownedElsewhere`: the pin lives in the image build rather than\n * in any manifest, so nothing is installed into the workspace. Left undeclared\n * these would be the only vended versions never upgraded — and they are\n * precisely the ones held at a version clear of a known HIGH/CRITICAL\n * vulnerability.\n */\nexport const NODE_IMAGE_DEPENDENCIES = [\n { name: 'npm' },\n { name: 'minimatch' },\n] as const satisfies readonly { name: ITsDepVersion }[];\n\n/**\n * Packages the AWS Distro for OpenTelemetry install in a vended `Dockerfile`\n * pins, for the images that auto-instrument with it. Spread through\n * `ownedElsewhere` for the same reason as {@link NODE_IMAGE_DEPENDENCIES}.\n */\nexport const ADOT_IMAGE_DEPENDENCIES = [\n { name: '@aws/aws-distro-opentelemetry-node-autoinstrumentation' },\n { name: '@opentelemetry/propagator-jaeger' },\n] as const satisfies readonly { name: ITsDepVersion }[];\n\nexport interface DockerScanTargetOptions {\n /**\n * Project configuration to add the scan target to (mutated in place).\n */\n readonly project: ProjectConfiguration;\n /**\n * Container engine command to invoke (\\`docker\\` or \\`finch\\`).\n */\n readonly containerEngine: string;\n /**\n * Name of the scan target to add, e.g. \\`my-agent-trivy\\`.\n */\n readonly trivyTargetName: string;\n /**\n * Name of the docker target that builds the image(s) to scan. The scan\n * target depends on it.\n */\n readonly dockerTargetName: string;\n /**\n * Tags of the images built by the docker target which should be scanned.\n */\n readonly imageTags: string[];\n}\n\n/**\n * Add a Trivy scan target for the images built by a docker target and wire it\n * under the aggregate \\`trivy\\` target. The scan is not part of \\`build\\` — run\n * \\`nx run-many --target trivy\\` (the root \\`trivy\\` script) in CI.\n *\n * The scan target depends on the docker target and is not cacheable, because\n * what it scans lives in the container engine rather than under its \\`outputs\\`.\n * Each image is saved to a tarball under \\`dist/<projectRoot>/trivy/<scan-key>\\`\n * (kept out of any Docker build context) and scanned with the pinned ECR-hosted\n * Trivy image via a workspace-relative bind mount, so the same commands work\n * under both docker and finch. The scan exits non-zero (exit code 1) on\n * HIGH/CRITICAL vulnerabilities.\n *\n * A \\`.trivyignore\\` is vended at the project root (kept if it already exists)\n * for suppressing findings.\n */\nexport const addDockerScanTarget = <const D extends DependencyDeclaration>(\n tree: Tree,\n options: DockerScanTargetOptions,\n declaration: D & MustDeclare<typeof DOCKER_DEPENDENCIES, D>,\n): void => {\n const { project, containerEngine, trivyTargetName, dockerTargetName } =\n options;\n const { imageTags } = options;\n const projectRoot = project.root;\n\n const ignoreFilePath = joinPathFragments(projectRoot, TRIVY_IGNORE_FILE);\n if (!tree.exists(ignoreFilePath)) {\n tree.write(ignoreFilePath, TRIVY_IGNORE_CONTENTS);\n }\n\n // Stage scan artifacts in a directory unique to this scan target so that\n // sibling targets in the same project (e.g. multiple agents) don't clobber\n // each other's tarballs when run in parallel. The first image tag is unique\n // per target, so it makes a stable, collision-free key.\n const scanKey = imageTags[0].replace(/[^a-zA-Z0-9-]/g, '-');\n const scanDir = joinPathFragments('dist', projectRoot, 'trivy', scanKey);\n const trivyImage = containerImage('trivy');\n\n const fs = new FsCommands(\n tree,\n forDependencies<typeof DOCKER_DEPENDENCIES>(declaration),\n );\n const commands = [\n fs.rm(scanDir),\n fs.mkdir(scanDir),\n fs.cpFile(ignoreFilePath, joinPathFragments(scanDir, TRIVY_IGNORE_FILE)),\n ];\n\n imageTags.forEach((imageTag, index) => {\n const tarName = `image-${index}.tar`;\n commands.push(\n `${containerEngine} save -o ${joinPathFragments(scanDir, tarName)} ${imageTag}`,\n `${containerEngine} run --rm -v \"./${scanDir}\":/scan ${trivyImage} image --input /scan/${tarName} --ignorefile /scan/${TRIVY_IGNORE_FILE} --scanners vuln --severity HIGH,CRITICAL --ignore-unfixed --exit-code 1 --no-progress -q`,\n );\n });\n\n project.targets ??= {};\n project.targets[trivyTargetName] = {\n // The image being scanned is only reachable through the container engine,\n // so a restored cache entry would report a pass for an image that may no\n // longer exist. The scan re-runs and either scans the real image or fails.\n // Nx only hashes a cacheable task, so declaring `inputs` here would have no\n // effect.\n cache: false,\n outputs: [`{workspaceRoot}/${scanDir}`],\n executor: 'nx:run-commands',\n options: {\n commands,\n parallel: false,\n },\n dependsOn: [dockerTargetName],\n };\n\n // Aggregate per-component scan targets under a single `trivy` target. The\n // scan is intentionally NOT wired into `build`: image scanning is slow and\n // its result depends on the ever-changing vulnerability database (a scan\n // that passes today can fail tomorrow when a new CVE is published), so\n // coupling it to `build` would make local and CI builds non-deterministic.\n // Run `nx run-many --target trivy` (the root `trivy` script) in CI instead.\n if (trivyTargetName !== 'trivy') {\n addDependencyToTargetIfNotPresent(project, 'trivy', trivyTargetName);\n }\n};\n"],"names":["joinPathFragments","forDependencies","FS_DEPENDENCIES","FsCommands","addDependencyToTargetIfNotPresent","containerImage","TS_VERSIONS","DOCKER_DEPENDENCIES","IMAGE_BUILD_CACHE","TRIVY_IGNORE_FILE","TRIVY_IGNORE_CONTENTS","nodeImageVersions","npmVersion","npm","minimatchVersion","minimatch","NODE_IMAGE_DEPENDENCIES","name","ADOT_IMAGE_DEPENDENCIES","addDockerScanTarget","tree","options","declaration","project","containerEngine","trivyTargetName","dockerTargetName","imageTags","projectRoot","root","ignoreFilePath","exists","write","scanKey","replace","scanDir","trivyImage","fs","commands","rm","mkdir","cpFile","forEach","imageTag","index","tarName","push","targets","cache","outputs","executor","parallel","dependsOn"],"mappings":"AAAA;;;CAGC,GACD,SACEA,iBAAiB,QAGZ,aAAa;AACpB,SAEEC,eAAe,QAEV,6BAA6B;AACpC,SAASC,eAAe,EAAEC,UAAU,QAAQ,UAAU;AACtD,SAASC,iCAAiC,QAAQ,UAAU;AAC5D,SAASC,cAAc,EAAsBC,WAAW,QAAQ,gBAAgB;AAEhF,oEAAoE,GACpE,OAAO,MAAMC,sBAAsB;OAAIL;CAAgB,CAAU;AAEjE;;;;;;;;;;CAUC,GACD,OAAO,MAAMM,oBAAoB,MAAM;AAEvC,MAAMC,oBAAoB;AAE1B,MAAMC,wBAAwB,CAAC;;;AAG/B,CAAC;AAED;;;CAGC,GACD,OAAO,MAAMC,oBAAoB,IAAO,CAAA;QACtCC,YAAYN,YAAYO,GAAG;QAC3BC,kBAAkBR,YAAYS,SAAS;IACzC,CAAA,EAAG;AAEH;;;;;;;;;CASC,GACD,OAAO,MAAMC,0BAA0B;IACrC;QAAEC,MAAM;IAAM;IACd;QAAEA,MAAM;IAAY;CACrB,CAAuD;AAExD;;;;CAIC,GACD,OAAO,MAAMC,0BAA0B;IACrC;QAAED,MAAM;IAAyD;IACjE;QAAEA,MAAM;IAAmC;CAC5C,CAAuD;AA0BxD;;;;;;;;;;;;;;;CAeC,GACD,OAAO,MAAME,sBAAsB,CACjCC,MACAC,SACAC;IAEA,MAAM,EAAEC,OAAO,EAAEC,eAAe,EAAEC,eAAe,EAAEC,gBAAgB,EAAE,GACnEL;IACF,MAAM,EAAEM,SAAS,EAAE,GAAGN;IACtB,MAAMO,cAAcL,QAAQM,IAAI;IAEhC,MAAMC,iBAAiB9B,kBAAkB4B,aAAanB;IACtD,IAAI,CAACW,KAAKW,MAAM,CAACD,iBAAiB;QAChCV,KAAKY,KAAK,CAACF,gBAAgBpB;IAC7B;IAEA,yEAAyE;IACzE,2EAA2E;IAC3E,4EAA4E;IAC5E,wDAAwD;IACxD,MAAMuB,UAAUN,SAAS,CAAC,EAAE,CAACO,OAAO,CAAC,kBAAkB;IACvD,MAAMC,UAAUnC,kBAAkB,QAAQ4B,aAAa,SAASK;IAChE,MAAMG,aAAa/B,eAAe;IAElC,MAAMgC,KAAK,IAAIlC,WACbiB,MACAnB,gBAA4CqB;IAE9C,MAAMgB,WAAW;QACfD,GAAGE,EAAE,CAACJ;QACNE,GAAGG,KAAK,CAACL;QACTE,GAAGI,MAAM,CAACX,gBAAgB9B,kBAAkBmC,SAAS1B;KACtD;IAEDkB,UAAUe,OAAO,CAAC,CAACC,UAAUC;QAC3B,MAAMC,UAAU,CAAC,MAAM,EAAED,MAAM,IAAI,CAAC;QACpCN,SAASQ,IAAI,CACX,GAAGtB,gBAAgB,SAAS,EAAExB,kBAAkBmC,SAASU,SAAS,CAAC,EAAEF,UAAU,EAC/E,GAAGnB,gBAAgB,gBAAgB,EAAEW,QAAQ,QAAQ,EAAEC,WAAW,qBAAqB,EAAES,QAAQ,oBAAoB,EAAEpC,kBAAkB,yFAAyF,CAAC;IAEvO;IAEAc,QAAQwB,OAAO,KAAK,CAAC;IACrBxB,QAAQwB,OAAO,CAACtB,gBAAgB,GAAG;QACjC,0EAA0E;QAC1E,yEAAyE;QACzE,2EAA2E;QAC3E,4EAA4E;QAC5E,UAAU;QACVuB,OAAO;QACPC,SAAS;YAAC,CAAC,gBAAgB,EAAEd,SAAS;SAAC;QACvCe,UAAU;QACV7B,SAAS;YACPiB;YACAa,UAAU;QACZ;QACAC,WAAW;YAAC1B;SAAiB;IAC/B;IAEA,0EAA0E;IAC1E,2EAA2E;IAC3E,yEAAyE;IACzE,uEAAuE;IACvE,2EAA2E;IAC3E,4EAA4E;IAC5E,IAAID,oBAAoB,SAAS;QAC/BrB,kCAAkCmB,SAAS,SAASE;IACtD;AACF,EAAE"}
|
|
1
|
+
{"version":3,"sources":["../../../../../packages/nx-plugin/src/utils/docker.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n joinPathFragments,\n type ProjectConfiguration,\n type Tree,\n} from '@nx/devkit';\nimport {\n type DependencyDeclaration,\n forDependencies,\n type MustDeclare,\n} from './declared-dependencies.js';\nimport { FS_DEPENDENCIES, FsCommands } from './fs.js';\nimport {\n addDependencyToTargetIfNotPresent,\n normalizeTargetKeyOrder,\n} from './nx.js';\nimport { containerImage, type ITsDepVersion, TS_VERSIONS } from './versions.js';\n\n/** Dependencies a caller must declare to add a Docker scan target. */\nexport const DOCKER_DEPENDENCIES = [...FS_DEPENDENCIES] as const;\n\n/**\n * The `cache` setting every target that builds a container image must use.\n *\n * A built image lives in the container engine's own store, not under any\n * `outputs` path, so Nx has nothing to restore a cache hit from: it would\n * report the build complete while the image is missing, and the `trivy` scan\n * that depends on it would report a cached pass against an image that isn't\n * there. The container engine's layer cache is the sound cache here — it is\n * keyed on the build context and lives alongside the image, so an unchanged\n * context rebuilds in well under a second and always leaves the image present.\n */\nexport const IMAGE_BUILD_CACHE = false;\n\nconst TRIVY_IGNORE_FILE = '.trivyignore';\n\nconst TRIVY_IGNORE_CONTENTS = `# Trivy ignore file. Add one vulnerability ID (e.g. CVE-2021-12345) per line to\n# suppress it during the image scan (\\`nx run-many --target trivy\\`).\n# https://trivy.dev/latest/docs/configuration/filtering/#by-finding-ids\n`;\n\n/**\n * Substitution variables for the vended Node `Dockerfile` templates: the npm\n * version to install globally, and the versions its dependency overrides pin.\n */\nexport const nodeImageVersions = () => ({\n npmVersion: TS_VERSIONS.npm,\n minimatchVersion: TS_VERSIONS.minimatch,\n});\n\n/**\n * Packages a vended Node `Dockerfile` pins, which every generator writing one\n * must declare so the version sync keeps those pins current.\n *\n * Spread through `ownedElsewhere`: the pin lives in the image build rather than\n * in any manifest, so nothing is installed into the workspace. Left undeclared\n * these would be the only vended versions never upgraded — and they are\n * precisely the ones held at a version clear of a known HIGH/CRITICAL\n * vulnerability.\n */\nexport const NODE_IMAGE_DEPENDENCIES = [\n { name: 'npm' },\n { name: 'minimatch' },\n] as const satisfies readonly { name: ITsDepVersion }[];\n\n/**\n * Packages the prisma CLI install in a vended RDB migration `Dockerfile` pins.\n * The CLI's own ranges resolve both below a known HIGH vulnerability's fix, so\n * the image overrides them. Spread through `ownedElsewhere` for the same reason\n * as {@link NODE_IMAGE_DEPENDENCIES}.\n */\nexport const PRISMA_IMAGE_DEPENDENCIES = [\n { name: 'deepmerge-ts' },\n { name: 'mysql2' },\n] as const satisfies readonly { name: ITsDepVersion }[];\n\n/**\n * Packages the AWS Distro for OpenTelemetry install in a vended `Dockerfile`\n * pins, for the images that auto-instrument with it. Spread through\n * `ownedElsewhere` for the same reason as {@link NODE_IMAGE_DEPENDENCIES}.\n */\nexport const ADOT_IMAGE_DEPENDENCIES = [\n { name: '@aws/aws-distro-opentelemetry-node-autoinstrumentation' },\n { name: '@opentelemetry/propagator-jaeger' },\n] as const satisfies readonly { name: ITsDepVersion }[];\n\nexport interface DockerScanTargetOptions {\n /**\n * Project configuration to add the scan target to (mutated in place).\n */\n readonly project: ProjectConfiguration;\n /**\n * Container engine command to invoke (\\`docker\\` or \\`finch\\`).\n */\n readonly containerEngine: string;\n /**\n * Name of the scan target to add, e.g. \\`my-agent-trivy\\`.\n */\n readonly trivyTargetName: string;\n /**\n * Name of the docker target that builds the image(s) to scan. The scan\n * target depends on it.\n */\n readonly dockerTargetName: string;\n /**\n * Tags of the images built by the docker target which should be scanned.\n */\n readonly imageTags: string[];\n}\n\n/**\n * Add a Trivy scan target for the images built by a docker target and wire it\n * under the aggregate \\`trivy\\` target. The scan is not part of \\`build\\` — run\n * \\`nx run-many --target trivy\\` (the root \\`trivy\\` script) in CI.\n *\n * The scan target depends on the docker target and is not cacheable, because\n * what it scans lives in the container engine rather than under its \\`outputs\\`.\n * Each image is saved to a tarball under \\`dist/<projectRoot>/trivy/<scan-key>\\`\n * (kept out of any Docker build context) and scanned with the pinned ECR-hosted\n * Trivy image via a workspace-relative bind mount, so the same commands work\n * under both docker and finch. The scan exits non-zero (exit code 1) on\n * HIGH/CRITICAL vulnerabilities.\n *\n * A \\`.trivyignore\\` is vended at the project root (kept if it already exists)\n * for suppressing findings.\n */\nexport const addDockerScanTarget = <const D extends DependencyDeclaration>(\n tree: Tree,\n options: DockerScanTargetOptions,\n declaration: D & MustDeclare<typeof DOCKER_DEPENDENCIES, D>,\n): void => {\n const { project, containerEngine, trivyTargetName, dockerTargetName } =\n options;\n const { imageTags } = options;\n const projectRoot = project.root;\n\n const ignoreFilePath = joinPathFragments(projectRoot, TRIVY_IGNORE_FILE);\n if (!tree.exists(ignoreFilePath)) {\n tree.write(ignoreFilePath, TRIVY_IGNORE_CONTENTS);\n }\n\n // Stage scan artifacts in a directory unique to this scan target so that\n // sibling targets in the same project (e.g. multiple agents) don't clobber\n // each other's tarballs when run in parallel. The first image tag is unique\n // per target, so it makes a stable, collision-free key.\n const scanKey = imageTags[0].replace(/[^a-zA-Z0-9-]/g, '-');\n const scanDir = joinPathFragments('dist', projectRoot, 'trivy', scanKey);\n const trivyImage = containerImage('trivy');\n\n const fs = new FsCommands(\n tree,\n forDependencies<typeof DOCKER_DEPENDENCIES>(declaration),\n );\n const commands = [\n fs.rm(scanDir),\n fs.mkdir(scanDir),\n fs.cpFile(ignoreFilePath, joinPathFragments(scanDir, TRIVY_IGNORE_FILE)),\n ];\n\n imageTags.forEach((imageTag, index) => {\n const tarName = `image-${index}.tar`;\n commands.push(\n `${containerEngine} save -o ${joinPathFragments(scanDir, tarName)} ${imageTag}`,\n `${containerEngine} run --rm -v \"./${scanDir}\":/scan ${trivyImage} image --input /scan/${tarName} --ignorefile /scan/${TRIVY_IGNORE_FILE} --scanners vuln --severity HIGH,CRITICAL --ignore-unfixed --exit-code 1 --no-progress -q`,\n );\n });\n\n project.targets ??= {};\n project.targets[trivyTargetName] = normalizeTargetKeyOrder({\n // The image being scanned is only reachable through the container engine,\n // so a restored cache entry would report a pass for an image that may no\n // longer exist. The scan re-runs and either scans the real image or fails.\n // Nx only hashes a cacheable task, so declaring `inputs` here would have no\n // effect.\n cache: false,\n outputs: [`{workspaceRoot}/${scanDir}`],\n executor: 'nx:run-commands',\n options: {\n commands,\n parallel: false,\n },\n dependsOn: [dockerTargetName],\n });\n\n // Aggregate per-component scan targets under a single `trivy` target. The\n // scan is intentionally NOT wired into `build`: image scanning is slow and\n // its result depends on the ever-changing vulnerability database (a scan\n // that passes today can fail tomorrow when a new CVE is published), so\n // coupling it to `build` would make local and CI builds non-deterministic.\n // Run `nx run-many --target trivy` (the root `trivy` script) in CI instead.\n if (trivyTargetName !== 'trivy') {\n addDependencyToTargetIfNotPresent(project, 'trivy', trivyTargetName);\n }\n};\n"],"names":["joinPathFragments","forDependencies","FS_DEPENDENCIES","FsCommands","addDependencyToTargetIfNotPresent","normalizeTargetKeyOrder","containerImage","TS_VERSIONS","DOCKER_DEPENDENCIES","IMAGE_BUILD_CACHE","TRIVY_IGNORE_FILE","TRIVY_IGNORE_CONTENTS","nodeImageVersions","npmVersion","npm","minimatchVersion","minimatch","NODE_IMAGE_DEPENDENCIES","name","PRISMA_IMAGE_DEPENDENCIES","ADOT_IMAGE_DEPENDENCIES","addDockerScanTarget","tree","options","declaration","project","containerEngine","trivyTargetName","dockerTargetName","imageTags","projectRoot","root","ignoreFilePath","exists","write","scanKey","replace","scanDir","trivyImage","fs","commands","rm","mkdir","cpFile","forEach","imageTag","index","tarName","push","targets","cache","outputs","executor","parallel","dependsOn"],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
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
|
+
/** A Global Secondary Index as `config.json` declares it. */
|
|
7
|
+
export interface DynamoDBGlobalSecondaryIndex {
|
|
8
|
+
readonly indexName: string;
|
|
9
|
+
readonly partitionKey: string;
|
|
10
|
+
readonly sortKey?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface DynamoDBLocalDevConfig {
|
|
13
|
+
readonly port: number;
|
|
14
|
+
readonly tableName: string;
|
|
15
|
+
readonly image: string;
|
|
16
|
+
readonly containerName: string;
|
|
17
|
+
readonly containerEngine: string;
|
|
18
|
+
}
|
|
19
|
+
export interface DynamoDBConfig {
|
|
20
|
+
runtimeConfigKey: string;
|
|
21
|
+
localDev: DynamoDBLocalDevConfig;
|
|
22
|
+
tableConfig: {
|
|
23
|
+
globalSecondaryIndexes: DynamoDBGlobalSecondaryIndex[];
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/** The image the local DynamoDB container runs. */
|
|
27
|
+
export declare const DYNAMODB_LOCAL_IMAGE = "public.ecr.aws/aws-dynamodb-local/aws-dynamodb-local:latest";
|
|
28
|
+
/**
|
|
29
|
+
* The two overloadable GSIs a new table starts with, following the single-table
|
|
30
|
+
* design naming convention the guides describe.
|
|
31
|
+
*/
|
|
32
|
+
export declare const DEFAULT_GLOBAL_SECONDARY_INDEXES: DynamoDBGlobalSecondaryIndex[];
|
|
33
|
+
/**
|
|
34
|
+
* Write a DynamoDB project's `config.json`.
|
|
35
|
+
*
|
|
36
|
+
* The file has two halves with different owners, so it is merged rather than
|
|
37
|
+
* re-emitted whole:
|
|
38
|
+
*
|
|
39
|
+
* - `runtimeConfigKey` and `localDev` are derived from the generator's options,
|
|
40
|
+
* so they converge on a re-run — changing `--tableName` still takes effect.
|
|
41
|
+
* - `tableConfig.globalSecondaryIndexes` is the user's, since the guides tell
|
|
42
|
+
* them to add an entry per GSI. The defaults seed a new project; an existing
|
|
43
|
+
* list is carried through untouched.
|
|
44
|
+
*/
|
|
45
|
+
export declare const writeDynamoDBConfig: (tree: Tree, projectRoot: string, config: {
|
|
46
|
+
runtimeConfigKey: string;
|
|
47
|
+
localDev: DynamoDBLocalDevConfig;
|
|
48
|
+
}) => void;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/ import { joinPathFragments, readJson, writeJson } from "@nx/devkit";
|
|
5
|
+
/** The image the local DynamoDB container runs. */ export const DYNAMODB_LOCAL_IMAGE = 'public.ecr.aws/aws-dynamodb-local/aws-dynamodb-local:latest';
|
|
6
|
+
/**
|
|
7
|
+
* The two overloadable GSIs a new table starts with, following the single-table
|
|
8
|
+
* design naming convention the guides describe.
|
|
9
|
+
*/ export const DEFAULT_GLOBAL_SECONDARY_INDEXES = [
|
|
10
|
+
{
|
|
11
|
+
indexName: 'gsi1pk-gsi1sk-index',
|
|
12
|
+
partitionKey: 'gsi1pk',
|
|
13
|
+
sortKey: 'gsi1sk'
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
indexName: 'gsi2pk-gsi2sk-index',
|
|
17
|
+
partitionKey: 'gsi2pk',
|
|
18
|
+
sortKey: 'gsi2sk'
|
|
19
|
+
}
|
|
20
|
+
];
|
|
21
|
+
/**
|
|
22
|
+
* Write a DynamoDB project's `config.json`.
|
|
23
|
+
*
|
|
24
|
+
* The file has two halves with different owners, so it is merged rather than
|
|
25
|
+
* re-emitted whole:
|
|
26
|
+
*
|
|
27
|
+
* - `runtimeConfigKey` and `localDev` are derived from the generator's options,
|
|
28
|
+
* so they converge on a re-run — changing `--tableName` still takes effect.
|
|
29
|
+
* - `tableConfig.globalSecondaryIndexes` is the user's, since the guides tell
|
|
30
|
+
* them to add an entry per GSI. The defaults seed a new project; an existing
|
|
31
|
+
* list is carried through untouched.
|
|
32
|
+
*/ export const writeDynamoDBConfig = (tree, projectRoot, config)=>{
|
|
33
|
+
const configPath = joinPathFragments(projectRoot, 'config.json');
|
|
34
|
+
const existing = tree.exists(configPath) ? readJson(tree, configPath) : {};
|
|
35
|
+
writeJson(tree, configPath, {
|
|
36
|
+
runtimeConfigKey: config.runtimeConfigKey,
|
|
37
|
+
localDev: config.localDev,
|
|
38
|
+
tableConfig: {
|
|
39
|
+
globalSecondaryIndexes: existing.tableConfig?.globalSecondaryIndexes ?? DEFAULT_GLOBAL_SECONDARY_INDEXES
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
//# sourceMappingURL=dynamodb-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../packages/nx-plugin/src/utils/dynamodb-config.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport { joinPathFragments, readJson, type Tree, writeJson } from '@nx/devkit';\n\n/** A Global Secondary Index as `config.json` declares it. */\nexport interface DynamoDBGlobalSecondaryIndex {\n readonly indexName: string;\n readonly partitionKey: string;\n readonly sortKey?: string;\n}\n\nexport interface DynamoDBLocalDevConfig {\n readonly port: number;\n readonly tableName: string;\n readonly image: string;\n readonly containerName: string;\n readonly containerEngine: string;\n}\n\nexport interface DynamoDBConfig {\n runtimeConfigKey: string;\n localDev: DynamoDBLocalDevConfig;\n tableConfig: { globalSecondaryIndexes: DynamoDBGlobalSecondaryIndex[] };\n}\n\n/** The image the local DynamoDB container runs. */\nexport const DYNAMODB_LOCAL_IMAGE =\n 'public.ecr.aws/aws-dynamodb-local/aws-dynamodb-local:latest';\n\n/**\n * The two overloadable GSIs a new table starts with, following the single-table\n * design naming convention the guides describe.\n */\nexport const DEFAULT_GLOBAL_SECONDARY_INDEXES: DynamoDBGlobalSecondaryIndex[] =\n [\n {\n indexName: 'gsi1pk-gsi1sk-index',\n partitionKey: 'gsi1pk',\n sortKey: 'gsi1sk',\n },\n {\n indexName: 'gsi2pk-gsi2sk-index',\n partitionKey: 'gsi2pk',\n sortKey: 'gsi2sk',\n },\n ];\n\n/**\n * Write a DynamoDB project's `config.json`.\n *\n * The file has two halves with different owners, so it is merged rather than\n * re-emitted whole:\n *\n * - `runtimeConfigKey` and `localDev` are derived from the generator's options,\n * so they converge on a re-run — changing `--tableName` still takes effect.\n * - `tableConfig.globalSecondaryIndexes` is the user's, since the guides tell\n * them to add an entry per GSI. The defaults seed a new project; an existing\n * list is carried through untouched.\n */\nexport const writeDynamoDBConfig = (\n tree: Tree,\n projectRoot: string,\n config: {\n runtimeConfigKey: string;\n localDev: DynamoDBLocalDevConfig;\n },\n): void => {\n const configPath = joinPathFragments(projectRoot, 'config.json');\n const existing: Partial<DynamoDBConfig> = tree.exists(configPath)\n ? readJson(tree, configPath)\n : {};\n\n writeJson(tree, configPath, {\n runtimeConfigKey: config.runtimeConfigKey,\n localDev: config.localDev,\n tableConfig: {\n globalSecondaryIndexes:\n existing.tableConfig?.globalSecondaryIndexes ??\n DEFAULT_GLOBAL_SECONDARY_INDEXES,\n },\n } satisfies DynamoDBConfig);\n};\n"],"names":["joinPathFragments","readJson","writeJson","DYNAMODB_LOCAL_IMAGE","DEFAULT_GLOBAL_SECONDARY_INDEXES","indexName","partitionKey","sortKey","writeDynamoDBConfig","tree","projectRoot","config","configPath","existing","exists","runtimeConfigKey","localDev","tableConfig","globalSecondaryIndexes"],"mappings":"AAAA;;;CAGC,GACD,SAASA,iBAAiB,EAAEC,QAAQ,EAAaC,SAAS,QAAQ,aAAa;AAuB/E,iDAAiD,GACjD,OAAO,MAAMC,uBACX,8DAA8D;AAEhE;;;CAGC,GACD,OAAO,MAAMC,mCACX;IACE;QACEC,WAAW;QACXC,cAAc;QACdC,SAAS;IACX;IACA;QACEF,WAAW;QACXC,cAAc;QACdC,SAAS;IACX;CACD,CAAC;AAEJ;;;;;;;;;;;CAWC,GACD,OAAO,MAAMC,sBAAsB,CACjCC,MACAC,aACAC;IAKA,MAAMC,aAAaZ,kBAAkBU,aAAa;IAClD,MAAMG,WAAoCJ,KAAKK,MAAM,CAACF,cAClDX,SAASQ,MAAMG,cACf,CAAC;IAELV,UAAUO,MAAMG,YAAY;QAC1BG,kBAAkBJ,OAAOI,gBAAgB;QACzCC,UAAUL,OAAOK,QAAQ;QACzBC,aAAa;YACXC,wBACEL,SAASI,WAAW,EAAEC,0BACtBd;QACJ;IACF;AACF,EAAE"}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { Construct } from 'constructs';
|
|
2
2
|
import { AttributeType, ProjectionType } from 'aws-cdk-lib/aws-dynamodb';
|
|
3
|
+
<%_ /* Alias the core imports only when this table's name would shadow them. */ _%>
|
|
4
|
+
<%_ const collides = nameClassName === 'DynamoDBTable'; _%>
|
|
5
|
+
<%_ const coreTable = collides ? 'CoreDynamoDBTable' : 'DynamoDBTable'; _%>
|
|
6
|
+
<%_ const coreTableProps = collides ? 'CoreDynamoDBTableProps' : 'DynamoDBTableProps'; _%>
|
|
3
7
|
import {
|
|
4
|
-
DynamoDBTable
|
|
5
|
-
DynamoDBTableProps
|
|
8
|
+
DynamoDBTable<% if (collides) { %> as <%= coreTable %><% } %>,
|
|
9
|
+
DynamoDBTableProps<% if (collides) { %> as <%= coreTableProps %><% } %>,
|
|
6
10
|
} from '../../core/dynamodb<% if (esm) { %>.js<% } %>';
|
|
7
11
|
import { findWorkspaceRoot } from '../../core/workspace<% if (esm) { %>.js<% } %>';
|
|
8
12
|
import { readFileSync } from 'fs';
|
|
@@ -20,11 +24,11 @@ const { runtimeConfigKey, tableConfig } = JSON.parse(
|
|
|
20
24
|
) as { runtimeConfigKey: string; tableConfig: { globalSecondaryIndexes: { indexName: string; partitionKey: string; sortKey?: string }[] } };
|
|
21
25
|
|
|
22
26
|
export type <%= nameClassName %>Props = Omit<
|
|
23
|
-
|
|
27
|
+
<%= coreTableProps %>,
|
|
24
28
|
'runtimeConfigKey'
|
|
25
29
|
>;
|
|
26
30
|
|
|
27
|
-
export class <%= nameClassName %> extends
|
|
31
|
+
export class <%= nameClassName %> extends <%= coreTable %> {
|
|
28
32
|
constructor(scope: Construct, id: string, props?: <%= nameClassName %>Props) {
|
|
29
33
|
super(scope, id, {
|
|
30
34
|
...props,
|
|
@@ -0,0 +1,28 @@
|
|
|
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
|
+
import { type DependencyDeclaration, type MustDeclare } from './declared-dependencies.js';
|
|
7
|
+
/** Dependencies a caller must declare to set up git-secrets. */
|
|
8
|
+
export declare const GIT_SECRETS_DEPENDENCIES: readonly [{
|
|
9
|
+
readonly name: "husky";
|
|
10
|
+
}];
|
|
11
|
+
/** The vended script, which the husky pre-commit hook runs. */
|
|
12
|
+
export declare const GIT_SECRETS_SCRIPT = ".git-secrets/git-secrets";
|
|
13
|
+
/**
|
|
14
|
+
* Patterns allowed so a generated workspace commits cleanly, matched against
|
|
15
|
+
* `<path>:<line>:<content>`. Only the vended script, whose body contains the
|
|
16
|
+
* patterns it registers.
|
|
17
|
+
*/
|
|
18
|
+
export declare const GIT_SECRETS_ALLOWED: string[];
|
|
19
|
+
/**
|
|
20
|
+
* Vend the git-secrets pre-commit hook: the script itself, the husky hook that
|
|
21
|
+
* runs it, and the allowlist.
|
|
22
|
+
*
|
|
23
|
+
* Re-runnable. The vendored script is always rewritten so an upgrade picks up a
|
|
24
|
+
* newer copy, while everything a user owns is preserved: `.gitallowed` only
|
|
25
|
+
* gains the entries a generated workspace needs, and an existing `pre-commit`
|
|
26
|
+
* hook or `prepare` script is left alone and reported rather than replaced.
|
|
27
|
+
*/
|
|
28
|
+
export declare const setUpGitSecrets: <const D extends DependencyDeclaration>(tree: Tree, declaration: D & MustDeclare<typeof GIT_SECRETS_DEPENDENCIES, D>) => void;
|