@aws/nx-plugin 1.0.0-rc.89 → 1.0.0-rc.90
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -22
- package/executors.json +5 -0
- package/generators.json +2 -1
- package/migrations.json +47 -2
- package/package.json +1 -1
- package/src/infra/app/__snapshots__/generator.spec.ts.snap +24 -12
- package/src/infra/app/generator.js +9 -7
- package/src/infra/app/generator.js.map +1 -1
- package/src/mcp-server/generator-info.d.ts +5 -0
- package/src/mcp-server/generator-info.js +6 -2
- package/src/mcp-server/generator-info.js.map +1 -1
- package/src/migrations/latest/add-assemble-target/metadata.json +3 -0
- package/src/migrations/latest/add-assemble-target/migration.d.ts +6 -0
- package/src/migrations/latest/add-assemble-target/migration.js +241 -0
- package/src/migrations/latest/add-assemble-target/migration.js.map +1 -0
- package/src/migrations/latest/declare-cacheable-target-inputs/metadata.json +3 -0
- package/src/migrations/latest/declare-cacheable-target-inputs/migration.d.ts +6 -0
- package/src/migrations/latest/declare-cacheable-target-inputs/migration.js +94 -0
- package/src/migrations/latest/declare-cacheable-target-inputs/migration.js.map +1 -0
- package/src/migrations/latest/dev-target-depends-on-pull-image/metadata.json +3 -0
- package/src/migrations/latest/dev-target-depends-on-pull-image/migration.d.ts +6 -0
- package/src/migrations/latest/dev-target-depends-on-pull-image/migration.js +67 -0
- package/src/migrations/latest/dev-target-depends-on-pull-image/migration.js.map +1 -0
- package/src/migrations/latest/docker-target-cache-soundness/metadata.json +3 -0
- package/src/migrations/latest/docker-target-cache-soundness/migration.d.ts +6 -0
- package/src/migrations/latest/docker-target-cache-soundness/migration.js +95 -0
- package/src/migrations/latest/docker-target-cache-soundness/migration.js.map +1 -0
- package/src/migrations/latest/open-api-codegen-executor/metadata.json +3 -0
- package/src/migrations/latest/open-api-codegen-executor/migration.d.ts +6 -0
- package/src/migrations/latest/open-api-codegen-executor/migration.js +100 -0
- package/src/migrations/latest/open-api-codegen-executor/migration.js.map +1 -0
- package/src/migrations/latest/python-test-input-scoping/metadata.json +3 -0
- package/src/migrations/latest/python-test-input-scoping/migration.d.ts +6 -0
- package/src/migrations/latest/python-test-input-scoping/migration.js +142 -0
- package/src/migrations/latest/python-test-input-scoping/migration.js.map +1 -0
- package/src/migrations/latest/terraform-fmt-check/metadata.json +3 -0
- package/src/migrations/latest/terraform-fmt-check/migration.d.ts +6 -0
- package/src/migrations/latest/terraform-fmt-check/migration.js +165 -0
- package/src/migrations/latest/terraform-fmt-check/migration.js.map +1 -0
- package/src/migrations/latest/terraform-provider-plugin-cache/metadata.json +3 -0
- package/src/migrations/latest/terraform-provider-plugin-cache/migration.d.ts +6 -0
- package/src/migrations/latest/terraform-provider-plugin-cache/migration.js +164 -0
- package/src/migrations/latest/terraform-provider-plugin-cache/migration.js.map +1 -0
- package/src/migrations/latest/vitest-coverage-output-dir/metadata.json +3 -0
- package/src/migrations/latest/vitest-coverage-output-dir/migration.d.ts +6 -0
- package/src/migrations/latest/vitest-coverage-output-dir/migration.js +177 -0
- package/src/migrations/latest/vitest-coverage-output-dir/migration.js.map +1 -0
- package/src/open-api/codegen/executor-schema.d.js +6 -0
- package/src/open-api/codegen/executor-schema.d.js.map +1 -0
- package/src/open-api/codegen/executor-schema.d.ts +18 -0
- package/src/open-api/codegen/executor-schema.json +33 -0
- package/src/open-api/codegen/executor.d.ts +18 -0
- package/src/open-api/codegen/executor.js +56 -0
- package/src/open-api/codegen/executor.js.map +1 -0
- package/src/preset/__snapshots__/generator.spec.ts.snap +2 -1
- package/src/py/agent/generator.js +21 -22
- package/src/py/agent/generator.js.map +1 -1
- package/src/py/agent/react-connection/generator.js +9 -0
- package/src/py/agent/react-connection/generator.js.map +1 -1
- package/src/py/dynamodb/__snapshots__/generator.spec.ts.snap +0 -30
- package/src/py/fast-api/react/open-api.js +19 -7
- package/src/py/fast-api/react/open-api.js.map +1 -1
- package/src/py/mcp-server/generator.js +4 -4
- package/src/py/mcp-server/generator.js.map +1 -1
- package/src/py/project/generator.d.ts +9 -0
- package/src/py/project/generator.js +51 -1
- package/src/py/project/generator.js.map +1 -1
- package/src/py/rdb/generator.js +10 -4
- package/src/py/rdb/generator.js.map +1 -1
- package/src/smithy/project/__snapshots__/generator.spec.ts.snap +15 -0
- package/src/smithy/project/generator.js +10 -0
- package/src/smithy/project/generator.js.map +1 -1
- package/src/smithy/ts/api/__snapshots__/generator.spec.ts.snap +7 -0
- package/src/terraform/project/files/application/scripts/env.ts.template +11 -0
- package/src/terraform/project/files/application/scripts/init.ts.template +2 -1
- package/src/terraform/project/files/application/src/providers.tf.template +2 -2
- package/src/terraform/project/generator.d.ts +11 -1
- package/src/terraform/project/generator.js +104 -20
- package/src/terraform/project/generator.js.map +1 -1
- package/src/trpc/backend/generator.js +2 -2
- package/src/trpc/backend/generator.js.map +1 -1
- package/src/ts/agent/generator.js +4 -7
- package/src/ts/agent/generator.js.map +1 -1
- package/src/ts/astro-docs/generator.js +5 -0
- package/src/ts/astro-docs/generator.js.map +1 -1
- package/src/ts/dynamodb/__snapshots__/generator.spec.ts.snap +0 -30
- package/src/ts/dynamodb/generator.js +3 -0
- package/src/ts/dynamodb/generator.js.map +1 -1
- package/src/ts/lib/__snapshots__/generator.spec.ts.snap +10 -1
- package/src/ts/lib/__snapshots__/vitest.spec.ts.snap +1 -1
- package/src/ts/lib/generator.d.ts +16 -0
- package/src/ts/lib/generator.js +31 -5
- package/src/ts/lib/generator.js.map +1 -1
- package/src/ts/lib/vitest.d.ts +11 -0
- package/src/ts/lib/vitest.js +31 -2
- package/src/ts/lib/vitest.js.map +1 -1
- package/src/ts/mcp-server/generator.js +4 -7
- package/src/ts/mcp-server/generator.js.map +1 -1
- package/src/ts/rdb/generator.js +8 -5
- package/src/ts/rdb/generator.js.map +1 -1
- package/src/ts/react-website/app/__snapshots__/generator.spec.ts.snap +27 -11
- package/src/ts/react-website/app/generator.js +9 -1
- package/src/ts/react-website/app/generator.js.map +1 -1
- package/src/ts/sync/generator.js +19 -2
- package/src/ts/sync/generator.js.map +1 -1
- package/src/utils/agent-core-constructs/agent-core-constructs.js +3 -3
- package/src/utils/agent-core-constructs/agent-core-constructs.js.map +1 -1
- package/src/utils/api-constructs/api-constructs.js +2 -2
- package/src/utils/api-constructs/api-constructs.js.map +1 -1
- package/src/utils/api-constructs/open-api-metadata.js +15 -36
- package/src/utils/api-constructs/open-api-metadata.js.map +1 -1
- package/src/utils/api-constructs/trpc-operations-metadata.js +9 -3
- package/src/utils/api-constructs/trpc-operations-metadata.js.map +1 -1
- package/src/utils/bundle/bundle.js +13 -4
- package/src/utils/bundle/bundle.js.map +1 -1
- package/src/utils/connection/open-api/react.js +7 -17
- package/src/utils/connection/open-api/react.js.map +1 -1
- package/src/utils/dcr-proxy-constructs/dcr-proxy-constructs.js +2 -2
- package/src/utils/dcr-proxy-constructs/dcr-proxy-constructs.js.map +1 -1
- package/src/utils/docker.d.ts +22 -10
- package/src/utils/docker.js +27 -17
- package/src/utils/docker.js.map +1 -1
- package/src/utils/dynamodb-constructs/dynamodb-constructs.js +2 -2
- package/src/utils/dynamodb-constructs/dynamodb-constructs.js.map +1 -1
- package/src/utils/files/common/scripts/src/dynamodb/start-container.ts.template +0 -15
- package/src/utils/files/common/scripts/src/rdb/shared/start-container.ts.template +0 -15
- package/src/utils/format.d.ts +5 -0
- package/src/utils/format.js +5 -0
- package/src/utils/format.js.map +1 -1
- package/src/utils/function-constructs/function-constructs.js +2 -2
- package/src/utils/function-constructs/function-constructs.js.map +1 -1
- package/src/utils/generators.d.ts +33 -0
- package/src/utils/generators.js +18 -3
- package/src/utils/generators.js.map +1 -1
- package/src/utils/init.js +4 -6
- package/src/utils/init.js.map +1 -1
- package/src/utils/mock-project-graph.js +5 -2
- package/src/utils/mock-project-graph.js.map +1 -1
- package/src/utils/nx.d.ts +19 -0
- package/src/utils/nx.js +23 -0
- package/src/utils/nx.js.map +1 -1
- package/src/utils/open-api-codegen-target.d.ts +28 -0
- package/src/utils/open-api-codegen-target.js +31 -0
- package/src/utils/open-api-codegen-target.js.map +1 -0
- package/src/utils/rdb-constructs/rdb-constructs.js +2 -2
- package/src/utils/rdb-constructs/rdb-constructs.js.map +1 -1
- package/src/utils/website-constructs/website-constructs.js +2 -2
- package/src/utils/website-constructs/website-constructs.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../packages/nx-plugin/src/mcp-server/generator-info.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport fs from 'fs';\nimport path from 'path';\nimport type { NxGeneratorInfo } from '../utils/generators.js';\nimport { kebabCase } from '../utils/names.js';\nimport { parseMdx, postProcessGuideWithRemark } from './guide-pipeline.js';\nimport {\n buildNxCommand,\n renderGeneratorCommand,\n renderSchema,\n} from './guide-render.js';\nimport { collectFilterableKeysFromJsx, extractFrontmatter } from './mdx-ast.js';\nimport { evaluatePredicate } from './option-filter.js';\nimport {\n filterableOptionsFromSchema,\n type GeneratorSchema,\n} from './schema-registry.js';\n\n// Re-export so existing callers of `generator-info` continue working.\nexport { buildNxCommand } from './guide-render.js';\n\n/**\n * Render summary information about a generator\n */\nexport const renderGeneratorInfo = (\n packageManager: string,\n info: NxGeneratorInfo,\n): string => {\n const schema = JSON.parse(fs.readFileSync(info.resolvedSchemaPath, 'utf-8'));\n\n return `${info.id}\n\nDescription: ${info.description}\n\nAvailable Parameters:\n${renderSchema(schema)}\n\nCommand:\n${renderGeneratorCommand(info.id, schema, packageManager)}\n`;\n};\n\nexport type FilterableOption = import('./schema-registry.js').FilterableOption;\n\n/**\n * Enum properties from the generator's JSON schema — the options a user\n * could pass on the CLI. Agents use this list to pick `options` values\n * when calling `generator-guide`.\n *\n * Multi-variant generators (e.g. `connection`) layer additional keys on\n * top via each guide page's `when:` frontmatter — merged in by\n * `collectFrontmatterFilterableOptions` / `renderFilterableOptionsAsync`.\n */\nexport const loadFilterableOptionsFromSchema = (\n info: NxGeneratorInfo,\n): FilterableOption[] => {\n try {\n const schema = JSON.parse(\n fs.readFileSync(info.resolvedSchemaPath, 'utf-8'),\n ) as GeneratorSchema;\n return filterableOptionsFromSchema(schema);\n } catch {\n return [];\n }\n};\n\n/**\n * Look at the `when:` frontmatter of every guide page belonging to a\n * generator and add any keys not already surfaced via the JSON schema.\n * This lets the `connection` generator advertise `sourceType` /\n * `targetType` / `protocol` without touching `generators.json`.\n */\nconst collectFrontmatterFilterableOptions = (\n fetched: FetchedGuide[],\n fromSchema: FilterableOption[],\n): FilterableOption[] => {\n const extras = new Map<string, Set<string>>();\n for (const guide of fetched) {\n const when = guide.frontmatter.when;\n if (!when) continue;\n for (const [key, rawValues] of Object.entries(when)) {\n const bucket = extras.get(key) ?? new Set<string>();\n for (const v of rawValues) bucket.add(v);\n extras.set(key, bucket);\n }\n }\n const out: FilterableOption[] = [];\n const seen = new Set(fromSchema.map((o) => o.key));\n for (const [key, values] of extras) {\n if (seen.has(key)) continue;\n out.push({ key, enum: [...values] });\n }\n return out;\n};\n\n/**\n * Decide whether the agent-supplied `options` describe a combination the\n * generator actually supports. Returns undefined when the combination is\n * fine (including when the agent hasn't narrowed enough to decide) and a\n * human-readable explanation otherwise.\n *\n * Supported combinations are inferred from the union of `when:` predicates\n * across a generator's guide pages. A generator with no `when:` predicates\n * on any of its pages (the common case — `ts#trpc-api`, `py#fast-api` …)\n * opts out of the check automatically.\n */\nconst describeUnsupportedCombinationFromFetched = (\n info: NxGeneratorInfo,\n fetched: FetchedGuide[],\n options: Record<string, string> | undefined,\n): string | undefined => {\n if (!options) return undefined;\n\n const predicates = fetched\n .map((g) => g.frontmatter.when)\n .filter((w): w is Record<string, string[]> => w !== undefined);\n if (predicates.length === 0) return undefined;\n\n // Consider only the option keys that actually participate in at least\n // one predicate — other options (e.g. schema enums) are orthogonal.\n const predicateKeys = new Set<string>();\n for (const p of predicates)\n for (const k of Object.keys(p)) predicateKeys.add(k);\n const suppliedPredicateKeys = [...predicateKeys].filter(\n (k) => options[k] !== undefined,\n );\n if (suppliedPredicateKeys.length === 0) return undefined;\n\n // If any predicate is still reachable given the keys the agent has\n // committed to so far, treat this as a partial (valid) selection and\n // skip the warning — the agent can keep narrowing.\n const stillReachable = predicates.some((pred) =>\n suppliedPredicateKeys.every((k) => {\n const values = pred[k];\n if (values === undefined) return true; // predicate doesn't care about this key\n return values.includes(options[k]);\n }),\n );\n if (stillReachable) return undefined;\n\n return renderUnsupportedMessage(info, options, predicates);\n};\n\nconst renderUnsupportedMessage = (\n info: NxGeneratorInfo,\n requested: Record<string, string>,\n predicates: Record<string, string[]>[],\n): string => {\n const requestedDesc = Object.entries(requested)\n .map(([k, v]) => `${k} = ${v}`)\n .join(', ');\n const supportedList = predicates\n .map((pred) => {\n const parts = Object.entries(pred).map(\n ([k, vs]) => `${k} = ${vs.join(' | ')}`,\n );\n return `- ${parts.join(', ')}`;\n })\n .join('\\n');\n return `## ${info.id}\n\n> [!WARNING] Unsupported combination: ${requestedDesc}. The \\`${info.id}\\` generator has no guide variant matching this combination — running it will likely fail.\n\nSupported combinations:\n${supportedList}\n\nPick a combination from the list above, or choose a different generator.`;\n};\n\n/**\n * Render the \"Filterable options\" block that list-generators emits for each\n * generator. Only keys the guide pages actually branch on are included —\n * enum properties the schema exposes but no guide filters on are omitted\n * so agents don't waste tokens setting options that won't affect the\n * returned content.\n *\n * Keys come from two sources:\n * - Schema enums, intersected with keys referenced by\n * `<OptionFilter when>` / `<TabItem _filter>` across the generator's\n * guide pages.\n * - Per-page `when:` frontmatter blocks (multi-variant generators like\n * `connection`).\n */\nexport const renderFilterableOptionsAsync = async (\n info: NxGeneratorInfo,\n): Promise<string> => {\n const fetched = await fetchGuideFrontmatters(info);\n\n const referenced = new Set<string>();\n for (const g of fetched) {\n for (const k of g.referencedKeys) referenced.add(k);\n }\n\n const schemaOptions = loadFilterableOptionsFromSchema(info).filter((o) =>\n referenced.has(o.key),\n );\n const frontmatterOptions = collectFrontmatterFilterableOptions(\n fetched,\n schemaOptions,\n );\n return formatFilterableOptions(info, [\n ...schemaOptions,\n ...frontmatterOptions,\n ]);\n};\n\nconst formatFilterableOptions = (\n _info: NxGeneratorInfo,\n filterable: FilterableOption[],\n): string => {\n if (filterable.length === 0) return '';\n const entries = filterable\n .map((opt) => {\n const def = opt.default ? ` (default: ${opt.default})` : '';\n return `- ${opt.key}: ${opt.enum.join(' | ')}${def}`;\n })\n .join('\\n');\n return `\\`generator-guide\\` options:\\n${entries}`;\n};\n\n/**\n * A guide page after its raw MDX has been fetched and the YAML frontmatter\n * parsed. The body excludes the frontmatter delimiter; the parsed\n * `frontmatter.when` predicate drives guide-level filtering.\n */\ninterface FetchedGuide {\n page: string;\n raw: string;\n body: string;\n frontmatter: {\n when?: Record<string, string[]>;\n title?: string;\n };\n /** Option keys referenced by `<OptionFilter when>` / `<TabItem _filter>` in the body. */\n referencedKeys: Set<string>;\n}\n\n/**\n * Result of `fetchGuidePagesForGenerator`. When the agent's options pick\n * a combination no variant page matches, `kind` is `'unsupported'` and\n * `content` holds an explanatory warning the tool should surface on its\n * own (without wrapping it in the usual `## <id>` summary). Otherwise\n * `content` is the rendered guide text.\n */\nexport type GuideFetchResult =\n | { kind: 'ok'; content: string }\n | { kind: 'unsupported'; content: string };\n\n/**\n * Retrieve the markdown guide pages for a generator.\n *\n * Every page listed in `guidePages` (or the kebab-cased generator id when\n * that field is absent) is fetched in parallel — local files first, GitHub\n * fallback — and its YAML frontmatter is parsed to pick up any `when:`\n * predicate. Pages without a `when:` are always included; pages with a\n * `when:` are included when either:\n * - `options` is absent (agent hasn't narrowed yet — keep every variant),\n * or\n * - the predicate matches `options` using the same semantics as\n * `<OptionFilter when={{…}}>` (AND across keys, OR within a key).\n *\n * If the agent has supplied enough options to identify a combination and\n * no variant page matches, the result is `{ kind: 'unsupported' }` and the\n * caller surfaces the warning directly.\n */\nexport const fetchGuidePagesForGenerator = async (\n info: NxGeneratorInfo,\n generators: NxGeneratorInfo[],\n packageManager?: string,\n snippetContentProvider?: SnippetContentProvider,\n options?: Record<string, string>,\n): Promise<GuideFetchResult> => {\n const fetched = await fetchGuideFrontmatters(info);\n\n const unsupported = describeUnsupportedCombinationFromFetched(\n info,\n fetched,\n options,\n );\n if (unsupported !== undefined) {\n return { kind: 'unsupported', content: unsupported };\n }\n\n const kept = fetched.filter((g) => {\n if (!g.frontmatter.when) return true;\n if (!options) return true;\n return evaluatePredicate(g.frontmatter.when, false, options);\n });\n\n const processed = await Promise.all(\n kept.map((g) =>\n postProcessGuide(\n g.body,\n generators,\n packageManager,\n snippetContentProvider,\n options,\n ),\n ),\n );\n return { kind: 'ok', content: processed.join('\\n\\n') };\n};\n\n/**\n * Fetch every guide page referenced by a generator, in parallel, and\n * return { page, raw, body, frontmatter } for each one. Used by both\n * `fetchGuidePagesForGenerator` (to filter + render) and\n * `renderFilterableOptionsAsync` (to build the filterable-options list\n * from the union of frontmatter `when:` keys).\n */\nexport const fetchGuideFrontmatters = async (\n info: NxGeneratorInfo,\n): Promise<FetchedGuide[]> => {\n const pages: string[] = [...(info.guidePages ?? [kebabCase(info.id)])];\n const results = await Promise.allSettled(\n pages.map(async (page) => ({ page, raw: await fetchGuideRaw(page) })),\n );\n const fulfilled: { page: string; raw: string }[] = [];\n for (const r of results) {\n if (r.status === 'fulfilled' && r.value.raw) fulfilled.push(r.value);\n }\n return Promise.all(\n fulfilled.map(async ({ page, raw }) => {\n const parsed = await parseGuide(raw);\n return { page, raw, ...parsed };\n }),\n );\n};\n\n/**\n * Read a guide's raw MDX from the local filesystem (bundled or monorepo).\n */\nconst fetchGuideRaw = async (page: string): Promise<string> => {\n const local = fetchLocalGuide(page);\n if (local !== undefined) return local;\n return '';\n};\n\n/**\n * Parse a guide's MDX once and return the body, the frontmatter, and the\n * set of option keys its `<OptionFilter>` / `<TabItem _filter>` blocks\n * actually reference. The yaml node's `position.end.offset` gives us the\n * body slice without re-stringifying the tree.\n */\nconst parseGuide = async (\n raw: string,\n): Promise<Omit<FetchedGuide, 'page' | 'raw'>> => {\n const tree = await parseMdx(raw);\n const { data, bodyOffset } = extractFrontmatter(tree);\n const body = raw.slice(bodyOffset).replace(/^\\r?\\n/, '');\n const frontmatter: { when?: Record<string, string[]>; title?: string } = {};\n if (typeof data.title === 'string') frontmatter.title = data.title;\n if (data.when && typeof data.when === 'object' && !Array.isArray(data.when)) {\n const when: Record<string, string[]> = {};\n for (const [key, value] of Object.entries(\n data.when as Record<string, unknown>,\n )) {\n when[key] = Array.isArray(value) ? value.map(String) : [String(value)];\n }\n frontmatter.when = when;\n }\n return {\n body,\n frontmatter,\n referencedKeys: collectFilterableKeysFromJsx(tree),\n };\n};\n\n/**\n * Base directories to probe for guide MDX files, relative to this module.\n * Each is checked in order:\n * 1. Bundled docs in the published @aws/nx-plugin-mcp package\n * 2. Source checkout when running from the monorepo (dev/test)\n * 3. Rolldown-bundled binary in dist/ (monorepo layout)\n */\nconst GUIDE_BASE_PROBES = [\n // Bundled docs inside published @aws/nx-plugin-mcp package (bin/ → ../docs)\n '../docs',\n // nx-plugin package compiled from source (src/mcp-server/…)\n '../../../docs/src/content/docs/en',\n // rolldown-bundled `@aws/nx-plugin-mcp` binary in dist (monorepo layout)\n '../../../../docs/src/content/docs/en',\n];\n\n/**\n * Content sub-directories that hold pages a generator may reference via\n * `guidePages`. Both the per-generator reference guides (`guides/`) and the\n * getting-started pages (`get_started/`) are fetchable, so a page can live in\n * whichever section reads best for humans while still being served by the MCP\n * tools. Bare `guidePages` names may also include a sub-path (e.g.\n * `connection/react-trpc`).\n */\nconst GUIDE_SECTIONS = ['guides', 'get_started'];\n\nconst fetchLocalGuide = (guide: string): string | undefined => {\n for (const base of GUIDE_BASE_PROBES) {\n for (const section of GUIDE_SECTIONS) {\n const candidate = path.resolve(\n import.meta.dirname,\n base,\n section,\n `${guide}.mdx`,\n );\n try {\n if (fs.existsSync(candidate)) {\n return fs.readFileSync(candidate, 'utf-8');\n }\n } catch {\n // Probe failure → keep looking.\n }\n }\n }\n return undefined;\n};\n\n/**\n * Fetch markdown guide pages from the local filesystem (bundled or monorepo).\n */\nexport const fetchGuidePages = async (\n guidePages: string[],\n generators: NxGeneratorInfo[],\n packageManager?: string,\n snippetContentProvider?: SnippetContentProvider,\n options?: Record<string, string>,\n): Promise<string> => {\n const guides = guidePages\n .map((guide) => fetchLocalGuide(guide))\n .filter((content): content is string => content !== undefined);\n const processed = await Promise.all(\n guides.map((content) =>\n postProcessGuide(\n content,\n generators,\n packageManager,\n snippetContentProvider,\n options,\n ),\n ),\n );\n return processed.join('\\n\\n');\n};\n\n/**\n * A function which retrieves snippet content given a snippet name.\n */\nexport type SnippetContentProvider = (\n snippetName: string,\n) => Promise<string> | string;\n\nconst SNIPPETS_RELATIVE_PROBES = [\n // Bundled docs inside published @aws/nx-plugin-mcp package (bin/ → ../docs/snippets)\n '../docs/snippets',\n '../../../docs/src/content/docs/en/snippets',\n '../../../../docs/src/content/docs/en/snippets',\n];\n\n/**\n * Fetch a snippet's content from the local filesystem (bundled or monorepo).\n */\nexport const fetchSnippet: SnippetContentProvider = async (\n snippetName: string,\n): Promise<string> => {\n for (const rel of SNIPPETS_RELATIVE_PROBES) {\n const candidate = path.resolve(\n import.meta.dirname,\n rel,\n `${snippetName}.mdx`,\n );\n try {\n if (fs.existsSync(candidate)) {\n return fs.readFileSync(candidate, 'utf-8');\n }\n } catch {\n // Probe failure — try next path.\n }\n }\n return '';\n};\n\n/**\n * Post-process a guide page. Thin wrapper around the unified/remark-mdx\n * pipeline in `guide-pipeline.ts` — the real work (snippet inlining,\n * OptionFilter/Infrastructure/TabItem filtering, command/schema rendering)\n * happens there. Kept under the same name so other packages\n * (e.g. the docs site's `markdown-content.astro`) can continue importing\n * `postProcessGuide` with the same signature.\n */\nexport const postProcessGuide = async (\n guide: string,\n generators: NxGeneratorInfo[],\n packageManager?: string,\n snippetContentProvider?: SnippetContentProvider,\n options?: Record<string, string>,\n): Promise<string> => {\n return postProcessGuideWithRemark(guide, {\n generators,\n packageManager,\n snippetContentProvider: snippetContentProvider ?? fetchSnippet,\n options,\n });\n};\n"],"names":["fs","path","kebabCase","parseMdx","postProcessGuideWithRemark","renderGeneratorCommand","renderSchema","collectFilterableKeysFromJsx","extractFrontmatter","evaluatePredicate","filterableOptionsFromSchema","buildNxCommand","renderGeneratorInfo","packageManager","info","schema","JSON","parse","readFileSync","resolvedSchemaPath","id","description","loadFilterableOptionsFromSchema","collectFrontmatterFilterableOptions","fetched","fromSchema","extras","Map","guide","when","frontmatter","key","rawValues","Object","entries","bucket","get","Set","v","add","set","out","seen","map","o","values","has","push","enum","describeUnsupportedCombinationFromFetched","options","undefined","predicates","g","filter","w","length","predicateKeys","p","k","keys","suppliedPredicateKeys","stillReachable","some","pred","every","includes","renderUnsupportedMessage","requested","requestedDesc","join","supportedList","parts","vs","renderFilterableOptionsAsync","fetchGuideFrontmatters","referenced","referencedKeys","schemaOptions","frontmatterOptions","formatFilterableOptions","_info","filterable","opt","def","default","fetchGuidePagesForGenerator","generators","snippetContentProvider","unsupported","kind","content","kept","processed","Promise","all","postProcessGuide","body","pages","guidePages","results","allSettled","page","raw","fetchGuideRaw","fulfilled","r","status","value","parsed","parseGuide","local","fetchLocalGuide","tree","data","bodyOffset","slice","replace","title","Array","isArray","String","GUIDE_BASE_PROBES","GUIDE_SECTIONS","base","section","candidate","resolve","dirname","existsSync","fetchGuidePages","guides","SNIPPETS_RELATIVE_PROBES","fetchSnippet","snippetName","rel"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../packages/nx-plugin/src/mcp-server/generator-info.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport fs from 'fs';\nimport path from 'path';\nimport type { NxGeneratorInfo } from '../utils/generators.js';\nimport { kebabCase } from '../utils/names.js';\nimport { parseMdx, postProcessGuideWithRemark } from './guide-pipeline.js';\nimport {\n buildNxCommand,\n renderGeneratorCommand,\n renderSchema,\n} from './guide-render.js';\nimport { collectFilterableKeysFromJsx, extractFrontmatter } from './mdx-ast.js';\nimport { evaluatePredicate } from './option-filter.js';\nimport {\n filterableOptionsFromSchema,\n type GeneratorSchema,\n} from './schema-registry.js';\n\n// Re-export so existing callers of `generator-info` continue working.\nexport { buildNxCommand } from './guide-render.js';\n\n/**\n * Warn agents that an experimental generator's output may change in a future\n * release without a migration to carry an existing workspace across.\n */\nexport const renderExperimentalWarning = (generatorId: string): string =>\n `> [!WARNING] Experimental: the \\`${generatorId}\\` generator's behaviour and generated output may change in a future release without a migration being published to apply the change to an existing workspace.`;\n\n/**\n * Render summary information about a generator\n */\nexport const renderGeneratorInfo = (\n packageManager: string,\n info: NxGeneratorInfo,\n): string => {\n const schema = JSON.parse(fs.readFileSync(info.resolvedSchemaPath, 'utf-8'));\n\n return `${info.id}\n\nDescription: ${info.description}\n${info.experimental ? `\\n${renderExperimentalWarning(info.id)}\\n` : ''}\nAvailable Parameters:\n${renderSchema(schema)}\n\nCommand:\n${renderGeneratorCommand(info.id, schema, packageManager)}\n`;\n};\n\nexport type FilterableOption = import('./schema-registry.js').FilterableOption;\n\n/**\n * Enum properties from the generator's JSON schema — the options a user\n * could pass on the CLI. Agents use this list to pick `options` values\n * when calling `generator-guide`.\n *\n * Multi-variant generators (e.g. `connection`) layer additional keys on\n * top via each guide page's `when:` frontmatter — merged in by\n * `collectFrontmatterFilterableOptions` / `renderFilterableOptionsAsync`.\n */\nexport const loadFilterableOptionsFromSchema = (\n info: NxGeneratorInfo,\n): FilterableOption[] => {\n try {\n const schema = JSON.parse(\n fs.readFileSync(info.resolvedSchemaPath, 'utf-8'),\n ) as GeneratorSchema;\n return filterableOptionsFromSchema(schema);\n } catch {\n return [];\n }\n};\n\n/**\n * Look at the `when:` frontmatter of every guide page belonging to a\n * generator and add any keys not already surfaced via the JSON schema.\n * This lets the `connection` generator advertise `sourceType` /\n * `targetType` / `protocol` without touching `generators.json`.\n */\nconst collectFrontmatterFilterableOptions = (\n fetched: FetchedGuide[],\n fromSchema: FilterableOption[],\n): FilterableOption[] => {\n const extras = new Map<string, Set<string>>();\n for (const guide of fetched) {\n const when = guide.frontmatter.when;\n if (!when) continue;\n for (const [key, rawValues] of Object.entries(when)) {\n const bucket = extras.get(key) ?? new Set<string>();\n for (const v of rawValues) bucket.add(v);\n extras.set(key, bucket);\n }\n }\n const out: FilterableOption[] = [];\n const seen = new Set(fromSchema.map((o) => o.key));\n for (const [key, values] of extras) {\n if (seen.has(key)) continue;\n out.push({ key, enum: [...values] });\n }\n return out;\n};\n\n/**\n * Decide whether the agent-supplied `options` describe a combination the\n * generator actually supports. Returns undefined when the combination is\n * fine (including when the agent hasn't narrowed enough to decide) and a\n * human-readable explanation otherwise.\n *\n * Supported combinations are inferred from the union of `when:` predicates\n * across a generator's guide pages. A generator with no `when:` predicates\n * on any of its pages (the common case — `ts#trpc-api`, `py#fast-api` …)\n * opts out of the check automatically.\n */\nconst describeUnsupportedCombinationFromFetched = (\n info: NxGeneratorInfo,\n fetched: FetchedGuide[],\n options: Record<string, string> | undefined,\n): string | undefined => {\n if (!options) return undefined;\n\n const predicates = fetched\n .map((g) => g.frontmatter.when)\n .filter((w): w is Record<string, string[]> => w !== undefined);\n if (predicates.length === 0) return undefined;\n\n // Consider only the option keys that actually participate in at least\n // one predicate — other options (e.g. schema enums) are orthogonal.\n const predicateKeys = new Set<string>();\n for (const p of predicates)\n for (const k of Object.keys(p)) predicateKeys.add(k);\n const suppliedPredicateKeys = [...predicateKeys].filter(\n (k) => options[k] !== undefined,\n );\n if (suppliedPredicateKeys.length === 0) return undefined;\n\n // If any predicate is still reachable given the keys the agent has\n // committed to so far, treat this as a partial (valid) selection and\n // skip the warning — the agent can keep narrowing.\n const stillReachable = predicates.some((pred) =>\n suppliedPredicateKeys.every((k) => {\n const values = pred[k];\n if (values === undefined) return true; // predicate doesn't care about this key\n return values.includes(options[k]);\n }),\n );\n if (stillReachable) return undefined;\n\n return renderUnsupportedMessage(info, options, predicates);\n};\n\nconst renderUnsupportedMessage = (\n info: NxGeneratorInfo,\n requested: Record<string, string>,\n predicates: Record<string, string[]>[],\n): string => {\n const requestedDesc = Object.entries(requested)\n .map(([k, v]) => `${k} = ${v}`)\n .join(', ');\n const supportedList = predicates\n .map((pred) => {\n const parts = Object.entries(pred).map(\n ([k, vs]) => `${k} = ${vs.join(' | ')}`,\n );\n return `- ${parts.join(', ')}`;\n })\n .join('\\n');\n return `## ${info.id}\n\n> [!WARNING] Unsupported combination: ${requestedDesc}. The \\`${info.id}\\` generator has no guide variant matching this combination — running it will likely fail.\n${info.experimental ? `\\n${renderExperimentalWarning(info.id)}\\n` : ''}\nSupported combinations:\n${supportedList}\n\nPick a combination from the list above, or choose a different generator.`;\n};\n\n/**\n * Render the \"Filterable options\" block that list-generators emits for each\n * generator. Only keys the guide pages actually branch on are included —\n * enum properties the schema exposes but no guide filters on are omitted\n * so agents don't waste tokens setting options that won't affect the\n * returned content.\n *\n * Keys come from two sources:\n * - Schema enums, intersected with keys referenced by\n * `<OptionFilter when>` / `<TabItem _filter>` across the generator's\n * guide pages.\n * - Per-page `when:` frontmatter blocks (multi-variant generators like\n * `connection`).\n */\nexport const renderFilterableOptionsAsync = async (\n info: NxGeneratorInfo,\n): Promise<string> => {\n const fetched = await fetchGuideFrontmatters(info);\n\n const referenced = new Set<string>();\n for (const g of fetched) {\n for (const k of g.referencedKeys) referenced.add(k);\n }\n\n const schemaOptions = loadFilterableOptionsFromSchema(info).filter((o) =>\n referenced.has(o.key),\n );\n const frontmatterOptions = collectFrontmatterFilterableOptions(\n fetched,\n schemaOptions,\n );\n return formatFilterableOptions(info, [\n ...schemaOptions,\n ...frontmatterOptions,\n ]);\n};\n\nconst formatFilterableOptions = (\n _info: NxGeneratorInfo,\n filterable: FilterableOption[],\n): string => {\n if (filterable.length === 0) return '';\n const entries = filterable\n .map((opt) => {\n const def = opt.default ? ` (default: ${opt.default})` : '';\n return `- ${opt.key}: ${opt.enum.join(' | ')}${def}`;\n })\n .join('\\n');\n return `\\`generator-guide\\` options:\\n${entries}`;\n};\n\n/**\n * A guide page after its raw MDX has been fetched and the YAML frontmatter\n * parsed. The body excludes the frontmatter delimiter; the parsed\n * `frontmatter.when` predicate drives guide-level filtering.\n */\ninterface FetchedGuide {\n page: string;\n raw: string;\n body: string;\n frontmatter: {\n when?: Record<string, string[]>;\n title?: string;\n };\n /** Option keys referenced by `<OptionFilter when>` / `<TabItem _filter>` in the body. */\n referencedKeys: Set<string>;\n}\n\n/**\n * Result of `fetchGuidePagesForGenerator`. When the agent's options pick\n * a combination no variant page matches, `kind` is `'unsupported'` and\n * `content` holds an explanatory warning the tool should surface on its\n * own (without wrapping it in the usual `## <id>` summary). Otherwise\n * `content` is the rendered guide text.\n */\nexport type GuideFetchResult =\n | { kind: 'ok'; content: string }\n | { kind: 'unsupported'; content: string };\n\n/**\n * Retrieve the markdown guide pages for a generator.\n *\n * Every page listed in `guidePages` (or the kebab-cased generator id when\n * that field is absent) is fetched in parallel — local files first, GitHub\n * fallback — and its YAML frontmatter is parsed to pick up any `when:`\n * predicate. Pages without a `when:` are always included; pages with a\n * `when:` are included when either:\n * - `options` is absent (agent hasn't narrowed yet — keep every variant),\n * or\n * - the predicate matches `options` using the same semantics as\n * `<OptionFilter when={{…}}>` (AND across keys, OR within a key).\n *\n * If the agent has supplied enough options to identify a combination and\n * no variant page matches, the result is `{ kind: 'unsupported' }` and the\n * caller surfaces the warning directly.\n */\nexport const fetchGuidePagesForGenerator = async (\n info: NxGeneratorInfo,\n generators: NxGeneratorInfo[],\n packageManager?: string,\n snippetContentProvider?: SnippetContentProvider,\n options?: Record<string, string>,\n): Promise<GuideFetchResult> => {\n const fetched = await fetchGuideFrontmatters(info);\n\n const unsupported = describeUnsupportedCombinationFromFetched(\n info,\n fetched,\n options,\n );\n if (unsupported !== undefined) {\n return { kind: 'unsupported', content: unsupported };\n }\n\n const kept = fetched.filter((g) => {\n if (!g.frontmatter.when) return true;\n if (!options) return true;\n return evaluatePredicate(g.frontmatter.when, false, options);\n });\n\n const processed = await Promise.all(\n kept.map((g) =>\n postProcessGuide(\n g.body,\n generators,\n packageManager,\n snippetContentProvider,\n options,\n ),\n ),\n );\n return { kind: 'ok', content: processed.join('\\n\\n') };\n};\n\n/**\n * Fetch every guide page referenced by a generator, in parallel, and\n * return { page, raw, body, frontmatter } for each one. Used by both\n * `fetchGuidePagesForGenerator` (to filter + render) and\n * `renderFilterableOptionsAsync` (to build the filterable-options list\n * from the union of frontmatter `when:` keys).\n */\nexport const fetchGuideFrontmatters = async (\n info: NxGeneratorInfo,\n): Promise<FetchedGuide[]> => {\n const pages: string[] = [...(info.guidePages ?? [kebabCase(info.id)])];\n const results = await Promise.allSettled(\n pages.map(async (page) => ({ page, raw: await fetchGuideRaw(page) })),\n );\n const fulfilled: { page: string; raw: string }[] = [];\n for (const r of results) {\n if (r.status === 'fulfilled' && r.value.raw) fulfilled.push(r.value);\n }\n return Promise.all(\n fulfilled.map(async ({ page, raw }) => {\n const parsed = await parseGuide(raw);\n return { page, raw, ...parsed };\n }),\n );\n};\n\n/**\n * Read a guide's raw MDX from the local filesystem (bundled or monorepo).\n */\nconst fetchGuideRaw = async (page: string): Promise<string> => {\n const local = fetchLocalGuide(page);\n if (local !== undefined) return local;\n return '';\n};\n\n/**\n * Parse a guide's MDX once and return the body, the frontmatter, and the\n * set of option keys its `<OptionFilter>` / `<TabItem _filter>` blocks\n * actually reference. The yaml node's `position.end.offset` gives us the\n * body slice without re-stringifying the tree.\n */\nconst parseGuide = async (\n raw: string,\n): Promise<Omit<FetchedGuide, 'page' | 'raw'>> => {\n const tree = await parseMdx(raw);\n const { data, bodyOffset } = extractFrontmatter(tree);\n const body = raw.slice(bodyOffset).replace(/^\\r?\\n/, '');\n const frontmatter: { when?: Record<string, string[]>; title?: string } = {};\n if (typeof data.title === 'string') frontmatter.title = data.title;\n if (data.when && typeof data.when === 'object' && !Array.isArray(data.when)) {\n const when: Record<string, string[]> = {};\n for (const [key, value] of Object.entries(\n data.when as Record<string, unknown>,\n )) {\n when[key] = Array.isArray(value) ? value.map(String) : [String(value)];\n }\n frontmatter.when = when;\n }\n return {\n body,\n frontmatter,\n referencedKeys: collectFilterableKeysFromJsx(tree),\n };\n};\n\n/**\n * Base directories to probe for guide MDX files, relative to this module.\n * Each is checked in order:\n * 1. Bundled docs in the published @aws/nx-plugin-mcp package\n * 2. Source checkout when running from the monorepo (dev/test)\n * 3. Rolldown-bundled binary in dist/ (monorepo layout)\n */\nconst GUIDE_BASE_PROBES = [\n // Bundled docs inside published @aws/nx-plugin-mcp package (bin/ → ../docs)\n '../docs',\n // nx-plugin package compiled from source (src/mcp-server/…)\n '../../../docs/src/content/docs/en',\n // rolldown-bundled `@aws/nx-plugin-mcp` binary in dist (monorepo layout)\n '../../../../docs/src/content/docs/en',\n];\n\n/**\n * Content sub-directories that hold pages a generator may reference via\n * `guidePages`. Both the per-generator reference guides (`guides/`) and the\n * getting-started pages (`get_started/`) are fetchable, so a page can live in\n * whichever section reads best for humans while still being served by the MCP\n * tools. Bare `guidePages` names may also include a sub-path (e.g.\n * `connection/react-trpc`).\n */\nconst GUIDE_SECTIONS = ['guides', 'get_started'];\n\nconst fetchLocalGuide = (guide: string): string | undefined => {\n for (const base of GUIDE_BASE_PROBES) {\n for (const section of GUIDE_SECTIONS) {\n const candidate = path.resolve(\n import.meta.dirname,\n base,\n section,\n `${guide}.mdx`,\n );\n try {\n if (fs.existsSync(candidate)) {\n return fs.readFileSync(candidate, 'utf-8');\n }\n } catch {\n // Probe failure → keep looking.\n }\n }\n }\n return undefined;\n};\n\n/**\n * Fetch markdown guide pages from the local filesystem (bundled or monorepo).\n */\nexport const fetchGuidePages = async (\n guidePages: string[],\n generators: NxGeneratorInfo[],\n packageManager?: string,\n snippetContentProvider?: SnippetContentProvider,\n options?: Record<string, string>,\n): Promise<string> => {\n const guides = guidePages\n .map((guide) => fetchLocalGuide(guide))\n .filter((content): content is string => content !== undefined);\n const processed = await Promise.all(\n guides.map((content) =>\n postProcessGuide(\n content,\n generators,\n packageManager,\n snippetContentProvider,\n options,\n ),\n ),\n );\n return processed.join('\\n\\n');\n};\n\n/**\n * A function which retrieves snippet content given a snippet name.\n */\nexport type SnippetContentProvider = (\n snippetName: string,\n) => Promise<string> | string;\n\nconst SNIPPETS_RELATIVE_PROBES = [\n // Bundled docs inside published @aws/nx-plugin-mcp package (bin/ → ../docs/snippets)\n '../docs/snippets',\n '../../../docs/src/content/docs/en/snippets',\n '../../../../docs/src/content/docs/en/snippets',\n];\n\n/**\n * Fetch a snippet's content from the local filesystem (bundled or monorepo).\n */\nexport const fetchSnippet: SnippetContentProvider = async (\n snippetName: string,\n): Promise<string> => {\n for (const rel of SNIPPETS_RELATIVE_PROBES) {\n const candidate = path.resolve(\n import.meta.dirname,\n rel,\n `${snippetName}.mdx`,\n );\n try {\n if (fs.existsSync(candidate)) {\n return fs.readFileSync(candidate, 'utf-8');\n }\n } catch {\n // Probe failure — try next path.\n }\n }\n return '';\n};\n\n/**\n * Post-process a guide page. Thin wrapper around the unified/remark-mdx\n * pipeline in `guide-pipeline.ts` — the real work (snippet inlining,\n * OptionFilter/Infrastructure/TabItem filtering, command/schema rendering)\n * happens there. Kept under the same name so other packages\n * (e.g. the docs site's `markdown-content.astro`) can continue importing\n * `postProcessGuide` with the same signature.\n */\nexport const postProcessGuide = async (\n guide: string,\n generators: NxGeneratorInfo[],\n packageManager?: string,\n snippetContentProvider?: SnippetContentProvider,\n options?: Record<string, string>,\n): Promise<string> => {\n return postProcessGuideWithRemark(guide, {\n generators,\n packageManager,\n snippetContentProvider: snippetContentProvider ?? fetchSnippet,\n options,\n });\n};\n"],"names":["fs","path","kebabCase","parseMdx","postProcessGuideWithRemark","renderGeneratorCommand","renderSchema","collectFilterableKeysFromJsx","extractFrontmatter","evaluatePredicate","filterableOptionsFromSchema","buildNxCommand","renderExperimentalWarning","generatorId","renderGeneratorInfo","packageManager","info","schema","JSON","parse","readFileSync","resolvedSchemaPath","id","description","experimental","loadFilterableOptionsFromSchema","collectFrontmatterFilterableOptions","fetched","fromSchema","extras","Map","guide","when","frontmatter","key","rawValues","Object","entries","bucket","get","Set","v","add","set","out","seen","map","o","values","has","push","enum","describeUnsupportedCombinationFromFetched","options","undefined","predicates","g","filter","w","length","predicateKeys","p","k","keys","suppliedPredicateKeys","stillReachable","some","pred","every","includes","renderUnsupportedMessage","requested","requestedDesc","join","supportedList","parts","vs","renderFilterableOptionsAsync","fetchGuideFrontmatters","referenced","referencedKeys","schemaOptions","frontmatterOptions","formatFilterableOptions","_info","filterable","opt","def","default","fetchGuidePagesForGenerator","generators","snippetContentProvider","unsupported","kind","content","kept","processed","Promise","all","postProcessGuide","body","pages","guidePages","results","allSettled","page","raw","fetchGuideRaw","fulfilled","r","status","value","parsed","parseGuide","local","fetchLocalGuide","tree","data","bodyOffset","slice","replace","title","Array","isArray","String","GUIDE_BASE_PROBES","GUIDE_SECTIONS","base","section","candidate","resolve","dirname","existsSync","fetchGuidePages","guides","SNIPPETS_RELATIVE_PROBES","fetchSnippet","snippetName","rel"],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
import { type MigrationReturnObject, type Tree } from '@nx/devkit';
|
|
6
|
+
export default function migration(tree: Tree): Promise<MigrationReturnObject>;
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/ import { getProjects, updateProjectConfiguration } from "@nx/devkit";
|
|
5
|
+
import { INFRA_APP_GENERATOR_INFO } from "../../../infra/app/generator.js";
|
|
6
|
+
import { TERRAFORM_PROJECT_GENERATOR_INFO } from "../../../terraform/project/generator.js";
|
|
7
|
+
import { REACT_WEBSITE_APP_GENERATOR_INFO } from "../../../ts/react-website/app/generator.js";
|
|
8
|
+
import { formatFilesInSubtree } from "../../../utils/format.js";
|
|
9
|
+
import { normalizeTargetKeyOrder } from "../../../utils/nx.js";
|
|
10
|
+
import { sortObjectKeys } from "../../../utils/object.js";
|
|
11
|
+
/**
|
|
12
|
+
* `build` runs everything: it produces a project's deployable artifacts and runs
|
|
13
|
+
* its quality gates (lint, test, typecheck). The deploy targets depended on
|
|
14
|
+
* `^build`, so deploying pulled in every upstream project's gates too — work a
|
|
15
|
+
* deploy does not need.
|
|
16
|
+
*
|
|
17
|
+
* `assemble` is the artifact-only sibling of `build`, which the deploy targets
|
|
18
|
+
* depend on instead. `build` is untouched, so it remains the target that runs
|
|
19
|
+
* everything.
|
|
20
|
+
*
|
|
21
|
+
* Each project's `assemble` is derived from what its own `build` declares rather
|
|
22
|
+
* than from the generator that created it, so a project whose build has been
|
|
23
|
+
* extended with an extra artifact target keeps producing it. Anything this
|
|
24
|
+
* migration cannot classify is reported via `nextSteps` and left alone.
|
|
25
|
+
*
|
|
26
|
+
* A `compile` target depending on `^build` is also narrowed to `^compile`, which
|
|
27
|
+
* is what keeps `^assemble` from pulling the upstream quality gates back in.
|
|
28
|
+
*
|
|
29
|
+
* How to write a migration:
|
|
30
|
+
* - https://nx.dev/docs/kb/migration-generators
|
|
31
|
+
* - What `nextSteps` means: https://nx.dev/docs/reference/devkit/MigrationReturnObject
|
|
32
|
+
*/ /**
|
|
33
|
+
* Targets that produce a deployable artifact, so they belong on `assemble`.
|
|
34
|
+
*
|
|
35
|
+
* `bundle-migration` and `bundle-create-db-user` are the RDB bundles, which the
|
|
36
|
+
* generator registers on `build` directly as well as on `bundle`.
|
|
37
|
+
*/ const ARTIFACT_TARGETS = [
|
|
38
|
+
'compile',
|
|
39
|
+
'bundle',
|
|
40
|
+
'bundle-migration',
|
|
41
|
+
'bundle-create-db-user',
|
|
42
|
+
'docker',
|
|
43
|
+
'openapi',
|
|
44
|
+
'operations',
|
|
45
|
+
'synth',
|
|
46
|
+
'generate-ssdk'
|
|
47
|
+
];
|
|
48
|
+
/**
|
|
49
|
+
* Targets that check the code rather than produce an artifact. Listed so an
|
|
50
|
+
* unrecognised entry is reported rather than silently classified either way.
|
|
51
|
+
*
|
|
52
|
+
* `fmt` is the Terraform formatter, and `checkov` scans what the build has
|
|
53
|
+
* already produced, so neither produces an artifact of its own.
|
|
54
|
+
*/ const QUALITY_GATES = [
|
|
55
|
+
'lint',
|
|
56
|
+
'format',
|
|
57
|
+
'fmt',
|
|
58
|
+
'test',
|
|
59
|
+
'typecheck',
|
|
60
|
+
'checkov'
|
|
61
|
+
];
|
|
62
|
+
/** The CDK targets whose `^build` becomes `^assemble`. */ const CDK_DEPLOY_TARGETS = [
|
|
63
|
+
'synth',
|
|
64
|
+
'deploy',
|
|
65
|
+
'deploy-sandbox',
|
|
66
|
+
'destroy',
|
|
67
|
+
'destroy-sandbox'
|
|
68
|
+
];
|
|
69
|
+
/** True when the project was created by the given generator. */ const generatedBy = (project, id)=>project.metadata?.generator === id;
|
|
70
|
+
/**
|
|
71
|
+
* `generate:<name>` targets write generated source (an API client, operations
|
|
72
|
+
* metadata) that the infrastructure reads, so they produce artifacts.
|
|
73
|
+
*/ const isGenerateTarget = (target)=>target.startsWith('generate:') || target === 'generate';
|
|
74
|
+
/**
|
|
75
|
+
* The `assemble` dependencies derived from a project's `build`.
|
|
76
|
+
*
|
|
77
|
+
* Returns `'unclassifiable'` when `build` declares something this migration
|
|
78
|
+
* cannot classify, so the caller reports it rather than guessing. A `build` that
|
|
79
|
+
* declares nothing at all is not unclassifiable — it yields an empty list, and
|
|
80
|
+
* the project gets a no-op `assemble`.
|
|
81
|
+
*/ const assembleDependenciesFor = (build)=>{
|
|
82
|
+
const dependsOn = build?.dependsOn;
|
|
83
|
+
if (dependsOn === undefined) return [];
|
|
84
|
+
if (!Array.isArray(dependsOn)) return 'unclassifiable';
|
|
85
|
+
const dependencies = [];
|
|
86
|
+
for (const dependency of dependsOn){
|
|
87
|
+
// The object form is valid Nx, and this plugin emits it. It names another
|
|
88
|
+
// project's target explicitly, so it is classified on that target.
|
|
89
|
+
if (typeof dependency !== 'string') {
|
|
90
|
+
const objectTarget = dependency?.target;
|
|
91
|
+
if (typeof objectTarget !== 'string') return 'unclassifiable';
|
|
92
|
+
if (objectTarget === 'build') {
|
|
93
|
+
dependencies.push({
|
|
94
|
+
...dependency,
|
|
95
|
+
target: 'assemble'
|
|
96
|
+
});
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (ARTIFACT_TARGETS.includes(objectTarget) || isGenerateTarget(objectTarget)) {
|
|
100
|
+
dependencies.push(dependency);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (QUALITY_GATES.includes(objectTarget)) continue;
|
|
104
|
+
return 'unclassifiable';
|
|
105
|
+
}
|
|
106
|
+
const [, crossProjectTarget] = dependency.match(/^.+:([^:]+)$/) ?? [];
|
|
107
|
+
if (crossProjectTarget === 'build') {
|
|
108
|
+
// Mirror the cross-project edge against the consumed project's own
|
|
109
|
+
// `assemble`, which keeps the artifact-only chain closed.
|
|
110
|
+
dependencies.push(dependency.replace(/:build$/, ':assemble'));
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (ARTIFACT_TARGETS.includes(crossProjectTarget)) {
|
|
114
|
+
// Already names an artifact target on the other project, so it carries
|
|
115
|
+
// over unchanged.
|
|
116
|
+
dependencies.push(dependency);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (crossProjectTarget && QUALITY_GATES.includes(crossProjectTarget)) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (ARTIFACT_TARGETS.includes(dependency) || isGenerateTarget(dependency)) {
|
|
123
|
+
dependencies.push(dependency);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (QUALITY_GATES.includes(dependency)) continue;
|
|
127
|
+
return 'unclassifiable';
|
|
128
|
+
}
|
|
129
|
+
return dependencies;
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* Adds the project's `assemble`, derived from its `build`. Returns whether the
|
|
133
|
+
* project was changed.
|
|
134
|
+
*/ const addAssembleTarget = (projectName, project, nextSteps)=>{
|
|
135
|
+
// Already migrated. A project's own `package` target is deliberately left
|
|
136
|
+
// alone: that name is for publishing to a package manager, a different job.
|
|
137
|
+
if (project.targets.assemble) return false;
|
|
138
|
+
// Nothing to derive an `assemble` from, so there is nothing to do. Consumers
|
|
139
|
+
// reference `<project>:build`, which is untouched.
|
|
140
|
+
if (!project.targets.build) return false;
|
|
141
|
+
const dependencies = assembleDependenciesFor(project.targets.build);
|
|
142
|
+
if (dependencies === 'unclassifiable') {
|
|
143
|
+
// Consumers have already been repointed at this project's `assemble`, and Nx
|
|
144
|
+
// silently skips a dependency whose target does not exist, so say so: an
|
|
145
|
+
// unmigrated project here means its artifacts stop being built for a deploy.
|
|
146
|
+
nextSteps.push(`Add an 'assemble' target to ${projectName} by hand — its 'build' target declares dependencies this migration does not recognise. It should depend on whichever of build's dependencies produce deployable artifacts, and on none of its lint/test/typecheck gates. Projects that consume ${projectName} now depend on '${projectName}:assemble', and Nx silently skips a dependency on a target that does not exist, so until you add it a deploy will not rebuild this project's artifacts.`);
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
project.targets.assemble = normalizeTargetKeyOrder(dependencies.length > 0 ? {
|
|
150
|
+
dependsOn: dependencies
|
|
151
|
+
} : {
|
|
152
|
+
executor: 'nx:noop'
|
|
153
|
+
});
|
|
154
|
+
return true;
|
|
155
|
+
};
|
|
156
|
+
/** Repoints one entry of a target's `dependsOn`. */ const repoint = (target, from, to)=>{
|
|
157
|
+
const dependsOn = target?.dependsOn;
|
|
158
|
+
if (!Array.isArray(dependsOn)) return 'diverged';
|
|
159
|
+
if (dependsOn.includes(to)) return 'already';
|
|
160
|
+
const index = dependsOn.indexOf(from);
|
|
161
|
+
if (index === -1) return 'diverged';
|
|
162
|
+
dependsOn[index] = to;
|
|
163
|
+
return 'repointed';
|
|
164
|
+
};
|
|
165
|
+
/** Points a CDK infrastructure project's deploy targets at `^assemble`. */ const migrateCdkDeployTargets = (projectName, project, nextSteps)=>{
|
|
166
|
+
let changed = false;
|
|
167
|
+
const diverged = [];
|
|
168
|
+
for (const name of CDK_DEPLOY_TARGETS){
|
|
169
|
+
if (!project.targets[name]) continue;
|
|
170
|
+
const result = repoint(project.targets[name], '^build', '^assemble');
|
|
171
|
+
if (result === 'repointed') changed = true;
|
|
172
|
+
if (result === 'diverged') diverged.push(name);
|
|
173
|
+
}
|
|
174
|
+
if (diverged.length > 0) {
|
|
175
|
+
const targets = diverged.map((t)=>`'${t}'`).join(', ');
|
|
176
|
+
const singular = diverged.length === 1;
|
|
177
|
+
nextSteps.push(`Point ${targets} on ${projectName} at '^assemble' by hand — ${singular ? 'it does' : 'they do'} not depend on '^build' as the generator left ${singular ? 'it' : 'them'}.`);
|
|
178
|
+
}
|
|
179
|
+
return changed;
|
|
180
|
+
};
|
|
181
|
+
/**
|
|
182
|
+
* Points a Terraform application's `plan` at `assemble`, so planning no longer
|
|
183
|
+
* runs the module tests.
|
|
184
|
+
*/ const migrateTerraformPlanTarget = (projectName, project, nextSteps)=>{
|
|
185
|
+
const result = repoint(project.targets.plan, 'build', 'assemble');
|
|
186
|
+
if (result === 'diverged') {
|
|
187
|
+
nextSteps.push(`Point 'plan' on ${projectName} at 'assemble' by hand — it does not depend on 'build' as the generator left it.`);
|
|
188
|
+
}
|
|
189
|
+
return result === 'repointed';
|
|
190
|
+
};
|
|
191
|
+
/**
|
|
192
|
+
* A website's `compile` needs its dependencies' declarations, which `compile`
|
|
193
|
+
* emits — not their lint or test results. Narrowing it is what keeps `^assemble`
|
|
194
|
+
* from pulling the upstream quality gates back in.
|
|
195
|
+
*
|
|
196
|
+
* Keyed off the target's shape rather than the generator, so a project that
|
|
197
|
+
* adopted the same `^build` compile is narrowed too. A project the plugin did
|
|
198
|
+
* not generate is reported, since its `compile` may depend on `^build` for a
|
|
199
|
+
* reason this migration cannot see.
|
|
200
|
+
*/ const narrowCompileTarget = (projectName, project, nextSteps)=>{
|
|
201
|
+
if (repoint(project.targets.compile, '^build', '^compile') !== 'repointed') {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
if (!generatedBy(project, REACT_WEBSITE_APP_GENERATOR_INFO.id)) {
|
|
205
|
+
nextSteps.push(`Narrowed 'compile' on ${projectName} from '^build' to '^compile', so it no longer waits for its dependencies' lint and test targets. Revert it if that project's compile relied on an upstream step that only 'build' runs.`);
|
|
206
|
+
}
|
|
207
|
+
return true;
|
|
208
|
+
};
|
|
209
|
+
export default async function migration(tree) {
|
|
210
|
+
const nextSteps = [];
|
|
211
|
+
for (const [projectName, project] of getProjects(tree)){
|
|
212
|
+
project.targets ??= {};
|
|
213
|
+
let changed = addAssembleTarget(projectName, project, nextSteps);
|
|
214
|
+
if (generatedBy(project, INFRA_APP_GENERATOR_INFO.id)) {
|
|
215
|
+
// `synth` is the cloud assembly, so it belongs on `assemble`; `checkov`
|
|
216
|
+
// scans that assembly and stays a `build`-only gate.
|
|
217
|
+
const assemble = project.targets.assemble;
|
|
218
|
+
if (assemble?.dependsOn && !assemble.dependsOn.includes('synth')) {
|
|
219
|
+
assemble.dependsOn.push('synth');
|
|
220
|
+
changed = true;
|
|
221
|
+
}
|
|
222
|
+
changed = migrateCdkDeployTargets(projectName, project, nextSteps) || changed;
|
|
223
|
+
}
|
|
224
|
+
if (generatedBy(project, TERRAFORM_PROJECT_GENERATOR_INFO.id) && project.projectType === 'application') {
|
|
225
|
+
changed = migrateTerraformPlanTarget(projectName, project, nextSteps) || changed;
|
|
226
|
+
}
|
|
227
|
+
changed = narrowCompileTarget(projectName, project, nextSteps) || changed;
|
|
228
|
+
if (changed) {
|
|
229
|
+
updateProjectConfiguration(tree, projectName, {
|
|
230
|
+
...project,
|
|
231
|
+
targets: sortObjectKeys(project.targets)
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
await formatFilesInSubtree(tree);
|
|
236
|
+
return {
|
|
237
|
+
nextSteps
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
//# sourceMappingURL=migration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/latest/add-assemble-target/migration.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n getProjects,\n type MigrationReturnObject,\n type ProjectConfiguration,\n type Tree,\n updateProjectConfiguration,\n} from '@nx/devkit';\nimport { INFRA_APP_GENERATOR_INFO } from '../../../infra/app/generator.js';\nimport { TERRAFORM_PROJECT_GENERATOR_INFO } from '../../../terraform/project/generator.js';\nimport { REACT_WEBSITE_APP_GENERATOR_INFO } from '../../../ts/react-website/app/generator.js';\nimport { formatFilesInSubtree } from '../../../utils/format.js';\nimport {\n normalizeTargetKeyOrder,\n type TargetDependency,\n} from '../../../utils/nx.js';\nimport { sortObjectKeys } from '../../../utils/object.js';\n\n/**\n * `build` runs everything: it produces a project's deployable artifacts and runs\n * its quality gates (lint, test, typecheck). The deploy targets depended on\n * `^build`, so deploying pulled in every upstream project's gates too — work a\n * deploy does not need.\n *\n * `assemble` is the artifact-only sibling of `build`, which the deploy targets\n * depend on instead. `build` is untouched, so it remains the target that runs\n * everything.\n *\n * Each project's `assemble` is derived from what its own `build` declares rather\n * than from the generator that created it, so a project whose build has been\n * extended with an extra artifact target keeps producing it. Anything this\n * migration cannot classify is reported via `nextSteps` and left alone.\n *\n * A `compile` target depending on `^build` is also narrowed to `^compile`, which\n * is what keeps `^assemble` from pulling the upstream quality gates back in.\n *\n * How to write a migration:\n * - https://nx.dev/docs/kb/migration-generators\n * - What `nextSteps` means: https://nx.dev/docs/reference/devkit/MigrationReturnObject\n */\n\n/**\n * Targets that produce a deployable artifact, so they belong on `assemble`.\n *\n * `bundle-migration` and `bundle-create-db-user` are the RDB bundles, which the\n * generator registers on `build` directly as well as on `bundle`.\n */\nconst ARTIFACT_TARGETS = [\n 'compile',\n 'bundle',\n 'bundle-migration',\n 'bundle-create-db-user',\n 'docker',\n 'openapi',\n 'operations',\n 'synth',\n 'generate-ssdk',\n];\n\n/**\n * Targets that check the code rather than produce an artifact. Listed so an\n * unrecognised entry is reported rather than silently classified either way.\n *\n * `fmt` is the Terraform formatter, and `checkov` scans what the build has\n * already produced, so neither produces an artifact of its own.\n */\nconst QUALITY_GATES = ['lint', 'format', 'fmt', 'test', 'typecheck', 'checkov'];\n\n/** The CDK targets whose `^build` becomes `^assemble`. */\nconst CDK_DEPLOY_TARGETS = [\n 'synth',\n 'deploy',\n 'deploy-sandbox',\n 'destroy',\n 'destroy-sandbox',\n];\n\n/** True when the project was created by the given generator. */\nconst generatedBy = (project: ProjectConfiguration, id: string): boolean =>\n (project.metadata as { generator?: string } | undefined)?.generator === id;\n\n/**\n * `generate:<name>` targets write generated source (an API client, operations\n * metadata) that the infrastructure reads, so they produce artifacts.\n */\nconst isGenerateTarget = (target: string): boolean =>\n target.startsWith('generate:') || target === 'generate';\n\n/**\n * The `assemble` dependencies derived from a project's `build`.\n *\n * Returns `'unclassifiable'` when `build` declares something this migration\n * cannot classify, so the caller reports it rather than guessing. A `build` that\n * declares nothing at all is not unclassifiable — it yields an empty list, and\n * the project gets a no-op `assemble`.\n */\nconst assembleDependenciesFor = (\n build: ProjectConfiguration['targets'][string] | undefined,\n): TargetDependency[] | 'unclassifiable' => {\n const dependsOn = build?.dependsOn;\n if (dependsOn === undefined) return [];\n if (!Array.isArray(dependsOn)) return 'unclassifiable';\n\n const dependencies: TargetDependency[] = [];\n for (const dependency of dependsOn) {\n // The object form is valid Nx, and this plugin emits it. It names another\n // project's target explicitly, so it is classified on that target.\n if (typeof dependency !== 'string') {\n const objectTarget = (dependency as { target?: unknown })?.target;\n if (typeof objectTarget !== 'string') return 'unclassifiable';\n if (objectTarget === 'build') {\n dependencies.push({ ...dependency, target: 'assemble' });\n continue;\n }\n if (\n ARTIFACT_TARGETS.includes(objectTarget) ||\n isGenerateTarget(objectTarget)\n ) {\n dependencies.push(dependency);\n continue;\n }\n if (QUALITY_GATES.includes(objectTarget)) continue;\n return 'unclassifiable';\n }\n const [, crossProjectTarget] = dependency.match(/^.+:([^:]+)$/) ?? [];\n if (crossProjectTarget === 'build') {\n // Mirror the cross-project edge against the consumed project's own\n // `assemble`, which keeps the artifact-only chain closed.\n dependencies.push(dependency.replace(/:build$/, ':assemble'));\n continue;\n }\n if (ARTIFACT_TARGETS.includes(crossProjectTarget)) {\n // Already names an artifact target on the other project, so it carries\n // over unchanged.\n dependencies.push(dependency);\n continue;\n }\n if (crossProjectTarget && QUALITY_GATES.includes(crossProjectTarget)) {\n continue;\n }\n if (ARTIFACT_TARGETS.includes(dependency) || isGenerateTarget(dependency)) {\n dependencies.push(dependency);\n continue;\n }\n if (QUALITY_GATES.includes(dependency)) continue;\n return 'unclassifiable';\n }\n return dependencies;\n};\n\n/**\n * Adds the project's `assemble`, derived from its `build`. Returns whether the\n * project was changed.\n */\nconst addAssembleTarget = (\n projectName: string,\n project: ProjectConfiguration,\n nextSteps: string[],\n): boolean => {\n // Already migrated. A project's own `package` target is deliberately left\n // alone: that name is for publishing to a package manager, a different job.\n if (project.targets.assemble) return false;\n\n // Nothing to derive an `assemble` from, so there is nothing to do. Consumers\n // reference `<project>:build`, which is untouched.\n if (!project.targets.build) return false;\n\n const dependencies = assembleDependenciesFor(project.targets.build);\n\n if (dependencies === 'unclassifiable') {\n // Consumers have already been repointed at this project's `assemble`, and Nx\n // silently skips a dependency whose target does not exist, so say so: an\n // unmigrated project here means its artifacts stop being built for a deploy.\n nextSteps.push(\n `Add an 'assemble' target to ${projectName} by hand — its 'build' target declares dependencies this migration does not recognise. It should depend on whichever of build's dependencies produce deployable artifacts, and on none of its lint/test/typecheck gates. Projects that consume ${projectName} now depend on '${projectName}:assemble', and Nx silently skips a dependency on a target that does not exist, so until you add it a deploy will not rebuild this project's artifacts.`,\n );\n return false;\n }\n\n project.targets.assemble = normalizeTargetKeyOrder(\n dependencies.length > 0\n ? { dependsOn: dependencies }\n : { executor: 'nx:noop' },\n );\n return true;\n};\n\n/** Repoints one entry of a target's `dependsOn`. */\nconst repoint = (\n target: ProjectConfiguration['targets'][string] | undefined,\n from: string,\n to: string,\n): 'repointed' | 'already' | 'diverged' => {\n const dependsOn = target?.dependsOn;\n if (!Array.isArray(dependsOn)) return 'diverged';\n if (dependsOn.includes(to)) return 'already';\n const index = dependsOn.indexOf(from);\n if (index === -1) return 'diverged';\n dependsOn[index] = to;\n return 'repointed';\n};\n\n/** Points a CDK infrastructure project's deploy targets at `^assemble`. */\nconst migrateCdkDeployTargets = (\n projectName: string,\n project: ProjectConfiguration,\n nextSteps: string[],\n): boolean => {\n let changed = false;\n const diverged: string[] = [];\n\n for (const name of CDK_DEPLOY_TARGETS) {\n if (!project.targets[name]) continue;\n const result = repoint(project.targets[name], '^build', '^assemble');\n if (result === 'repointed') changed = true;\n if (result === 'diverged') diverged.push(name);\n }\n\n if (diverged.length > 0) {\n const targets = diverged.map((t) => `'${t}'`).join(', ');\n const singular = diverged.length === 1;\n nextSteps.push(\n `Point ${targets} on ${projectName} at '^assemble' by hand — ${singular ? 'it does' : 'they do'} not depend on '^build' as the generator left ${singular ? 'it' : 'them'}.`,\n );\n }\n return changed;\n};\n\n/**\n * Points a Terraform application's `plan` at `assemble`, so planning no longer\n * runs the module tests.\n */\nconst migrateTerraformPlanTarget = (\n projectName: string,\n project: ProjectConfiguration,\n nextSteps: string[],\n): boolean => {\n const result = repoint(project.targets.plan, 'build', 'assemble');\n if (result === 'diverged') {\n nextSteps.push(\n `Point 'plan' on ${projectName} at 'assemble' by hand — it does not depend on 'build' as the generator left it.`,\n );\n }\n return result === 'repointed';\n};\n\n/**\n * A website's `compile` needs its dependencies' declarations, which `compile`\n * emits — not their lint or test results. Narrowing it is what keeps `^assemble`\n * from pulling the upstream quality gates back in.\n *\n * Keyed off the target's shape rather than the generator, so a project that\n * adopted the same `^build` compile is narrowed too. A project the plugin did\n * not generate is reported, since its `compile` may depend on `^build` for a\n * reason this migration cannot see.\n */\nconst narrowCompileTarget = (\n projectName: string,\n project: ProjectConfiguration,\n nextSteps: string[],\n): boolean => {\n if (repoint(project.targets.compile, '^build', '^compile') !== 'repointed') {\n return false;\n }\n if (!generatedBy(project, REACT_WEBSITE_APP_GENERATOR_INFO.id)) {\n nextSteps.push(\n `Narrowed 'compile' on ${projectName} from '^build' to '^compile', so it no longer waits for its dependencies' lint and test targets. Revert it if that project's compile relied on an upstream step that only 'build' runs.`,\n );\n }\n return true;\n};\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const nextSteps: string[] = [];\n\n for (const [projectName, project] of getProjects(tree)) {\n project.targets ??= {};\n\n let changed = addAssembleTarget(projectName, project, nextSteps);\n\n if (generatedBy(project, INFRA_APP_GENERATOR_INFO.id)) {\n // `synth` is the cloud assembly, so it belongs on `assemble`; `checkov`\n // scans that assembly and stays a `build`-only gate.\n const assemble = project.targets.assemble;\n if (assemble?.dependsOn && !assemble.dependsOn.includes('synth')) {\n assemble.dependsOn.push('synth');\n changed = true;\n }\n changed =\n migrateCdkDeployTargets(projectName, project, nextSteps) || changed;\n }\n\n if (\n generatedBy(project, TERRAFORM_PROJECT_GENERATOR_INFO.id) &&\n project.projectType === 'application'\n ) {\n changed =\n migrateTerraformPlanTarget(projectName, project, nextSteps) || changed;\n }\n\n changed = narrowCompileTarget(projectName, project, nextSteps) || changed;\n\n if (changed) {\n updateProjectConfiguration(tree, projectName, {\n ...project,\n targets: sortObjectKeys(project.targets),\n });\n }\n }\n\n await formatFilesInSubtree(tree);\n\n return { nextSteps };\n}\n"],"names":["getProjects","updateProjectConfiguration","INFRA_APP_GENERATOR_INFO","TERRAFORM_PROJECT_GENERATOR_INFO","REACT_WEBSITE_APP_GENERATOR_INFO","formatFilesInSubtree","normalizeTargetKeyOrder","sortObjectKeys","ARTIFACT_TARGETS","QUALITY_GATES","CDK_DEPLOY_TARGETS","generatedBy","project","id","metadata","generator","isGenerateTarget","target","startsWith","assembleDependenciesFor","build","dependsOn","undefined","Array","isArray","dependencies","dependency","objectTarget","push","includes","crossProjectTarget","match","replace","addAssembleTarget","projectName","nextSteps","targets","assemble","length","executor","repoint","from","to","index","indexOf","migrateCdkDeployTargets","changed","diverged","name","result","map","t","join","singular","migrateTerraformPlanTarget","plan","narrowCompileTarget","compile","migration","tree","projectType"],"mappings":"AAAA;;;CAGC,GACD,SACEA,WAAW,EAIXC,0BAA0B,QACrB,aAAa;AACpB,SAASC,wBAAwB,QAAQ,kCAAkC;AAC3E,SAASC,gCAAgC,QAAQ,0CAA0C;AAC3F,SAASC,gCAAgC,QAAQ,6CAA6C;AAC9F,SAASC,oBAAoB,QAAQ,2BAA2B;AAChE,SACEC,uBAAuB,QAElB,uBAAuB;AAC9B,SAASC,cAAc,QAAQ,2BAA2B;AAE1D;;;;;;;;;;;;;;;;;;;;;CAqBC,GAED;;;;;CAKC,GACD,MAAMC,mBAAmB;IACvB;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD;AAED;;;;;;CAMC,GACD,MAAMC,gBAAgB;IAAC;IAAQ;IAAU;IAAO;IAAQ;IAAa;CAAU;AAE/E,wDAAwD,GACxD,MAAMC,qBAAqB;IACzB;IACA;IACA;IACA;IACA;CACD;AAED,8DAA8D,GAC9D,MAAMC,cAAc,CAACC,SAA+BC,KAClD,AAACD,QAAQE,QAAQ,EAAyCC,cAAcF;AAE1E;;;CAGC,GACD,MAAMG,mBAAmB,CAACC,SACxBA,OAAOC,UAAU,CAAC,gBAAgBD,WAAW;AAE/C;;;;;;;CAOC,GACD,MAAME,0BAA0B,CAC9BC;IAEA,MAAMC,YAAYD,OAAOC;IACzB,IAAIA,cAAcC,WAAW,OAAO,EAAE;IACtC,IAAI,CAACC,MAAMC,OAAO,CAACH,YAAY,OAAO;IAEtC,MAAMI,eAAmC,EAAE;IAC3C,KAAK,MAAMC,cAAcL,UAAW;QAClC,0EAA0E;QAC1E,mEAAmE;QACnE,IAAI,OAAOK,eAAe,UAAU;YAClC,MAAMC,eAAgBD,YAAqCT;YAC3D,IAAI,OAAOU,iBAAiB,UAAU,OAAO;YAC7C,IAAIA,iBAAiB,SAAS;gBAC5BF,aAAaG,IAAI,CAAC;oBAAE,GAAGF,UAAU;oBAAET,QAAQ;gBAAW;gBACtD;YACF;YACA,IACET,iBAAiBqB,QAAQ,CAACF,iBAC1BX,iBAAiBW,eACjB;gBACAF,aAAaG,IAAI,CAACF;gBAClB;YACF;YACA,IAAIjB,cAAcoB,QAAQ,CAACF,eAAe;YAC1C,OAAO;QACT;QACA,MAAM,GAAGG,mBAAmB,GAAGJ,WAAWK,KAAK,CAAC,mBAAmB,EAAE;QACrE,IAAID,uBAAuB,SAAS;YAClC,mEAAmE;YACnE,0DAA0D;YAC1DL,aAAaG,IAAI,CAACF,WAAWM,OAAO,CAAC,WAAW;YAChD;QACF;QACA,IAAIxB,iBAAiBqB,QAAQ,CAACC,qBAAqB;YACjD,uEAAuE;YACvE,kBAAkB;YAClBL,aAAaG,IAAI,CAACF;YAClB;QACF;QACA,IAAII,sBAAsBrB,cAAcoB,QAAQ,CAACC,qBAAqB;YACpE;QACF;QACA,IAAItB,iBAAiBqB,QAAQ,CAACH,eAAeV,iBAAiBU,aAAa;YACzED,aAAaG,IAAI,CAACF;YAClB;QACF;QACA,IAAIjB,cAAcoB,QAAQ,CAACH,aAAa;QACxC,OAAO;IACT;IACA,OAAOD;AACT;AAEA;;;CAGC,GACD,MAAMQ,oBAAoB,CACxBC,aACAtB,SACAuB;IAEA,0EAA0E;IAC1E,4EAA4E;IAC5E,IAAIvB,QAAQwB,OAAO,CAACC,QAAQ,EAAE,OAAO;IAErC,6EAA6E;IAC7E,mDAAmD;IACnD,IAAI,CAACzB,QAAQwB,OAAO,CAAChB,KAAK,EAAE,OAAO;IAEnC,MAAMK,eAAeN,wBAAwBP,QAAQwB,OAAO,CAAChB,KAAK;IAElE,IAAIK,iBAAiB,kBAAkB;QACrC,6EAA6E;QAC7E,yEAAyE;QACzE,6EAA6E;QAC7EU,UAAUP,IAAI,CACZ,CAAC,4BAA4B,EAAEM,YAAY,+OAA+O,EAAEA,YAAY,gBAAgB,EAAEA,YAAY,uJAAuJ,CAAC;QAEhe,OAAO;IACT;IAEAtB,QAAQwB,OAAO,CAACC,QAAQ,GAAG/B,wBACzBmB,aAAaa,MAAM,GAAG,IAClB;QAAEjB,WAAWI;IAAa,IAC1B;QAAEc,UAAU;IAAU;IAE5B,OAAO;AACT;AAEA,kDAAkD,GAClD,MAAMC,UAAU,CACdvB,QACAwB,MACAC;IAEA,MAAMrB,YAAYJ,QAAQI;IAC1B,IAAI,CAACE,MAAMC,OAAO,CAACH,YAAY,OAAO;IACtC,IAAIA,UAAUQ,QAAQ,CAACa,KAAK,OAAO;IACnC,MAAMC,QAAQtB,UAAUuB,OAAO,CAACH;IAChC,IAAIE,UAAU,CAAC,GAAG,OAAO;IACzBtB,SAAS,CAACsB,MAAM,GAAGD;IACnB,OAAO;AACT;AAEA,yEAAyE,GACzE,MAAMG,0BAA0B,CAC9BX,aACAtB,SACAuB;IAEA,IAAIW,UAAU;IACd,MAAMC,WAAqB,EAAE;IAE7B,KAAK,MAAMC,QAAQtC,mBAAoB;QACrC,IAAI,CAACE,QAAQwB,OAAO,CAACY,KAAK,EAAE;QAC5B,MAAMC,SAAST,QAAQ5B,QAAQwB,OAAO,CAACY,KAAK,EAAE,UAAU;QACxD,IAAIC,WAAW,aAAaH,UAAU;QACtC,IAAIG,WAAW,YAAYF,SAASnB,IAAI,CAACoB;IAC3C;IAEA,IAAID,SAAST,MAAM,GAAG,GAAG;QACvB,MAAMF,UAAUW,SAASG,GAAG,CAAC,CAACC,IAAM,CAAC,CAAC,EAAEA,EAAE,CAAC,CAAC,EAAEC,IAAI,CAAC;QACnD,MAAMC,WAAWN,SAAST,MAAM,KAAK;QACrCH,UAAUP,IAAI,CACZ,CAAC,MAAM,EAAEQ,QAAQ,IAAI,EAAEF,YAAY,0BAA0B,EAAEmB,WAAW,YAAY,UAAU,8CAA8C,EAAEA,WAAW,OAAO,OAAO,CAAC,CAAC;IAE/K;IACA,OAAOP;AACT;AAEA;;;CAGC,GACD,MAAMQ,6BAA6B,CACjCpB,aACAtB,SACAuB;IAEA,MAAMc,SAAST,QAAQ5B,QAAQwB,OAAO,CAACmB,IAAI,EAAE,SAAS;IACtD,IAAIN,WAAW,YAAY;QACzBd,UAAUP,IAAI,CACZ,CAAC,gBAAgB,EAAEM,YAAY,gFAAgF,CAAC;IAEpH;IACA,OAAOe,WAAW;AACpB;AAEA;;;;;;;;;CASC,GACD,MAAMO,sBAAsB,CAC1BtB,aACAtB,SACAuB;IAEA,IAAIK,QAAQ5B,QAAQwB,OAAO,CAACqB,OAAO,EAAE,UAAU,gBAAgB,aAAa;QAC1E,OAAO;IACT;IACA,IAAI,CAAC9C,YAAYC,SAASR,iCAAiCS,EAAE,GAAG;QAC9DsB,UAAUP,IAAI,CACZ,CAAC,sBAAsB,EAAEM,YAAY,uLAAuL,CAAC;IAEjO;IACA,OAAO;AACT;AAEA,eAAe,eAAewB,UAC5BC,IAAU;IAEV,MAAMxB,YAAsB,EAAE;IAE9B,KAAK,MAAM,CAACD,aAAatB,QAAQ,IAAIZ,YAAY2D,MAAO;QACtD/C,QAAQwB,OAAO,KAAK,CAAC;QAErB,IAAIU,UAAUb,kBAAkBC,aAAatB,SAASuB;QAEtD,IAAIxB,YAAYC,SAASV,yBAAyBW,EAAE,GAAG;YACrD,wEAAwE;YACxE,qDAAqD;YACrD,MAAMwB,WAAWzB,QAAQwB,OAAO,CAACC,QAAQ;YACzC,IAAIA,UAAUhB,aAAa,CAACgB,SAAShB,SAAS,CAACQ,QAAQ,CAAC,UAAU;gBAChEQ,SAAShB,SAAS,CAACO,IAAI,CAAC;gBACxBkB,UAAU;YACZ;YACAA,UACED,wBAAwBX,aAAatB,SAASuB,cAAcW;QAChE;QAEA,IACEnC,YAAYC,SAAST,iCAAiCU,EAAE,KACxDD,QAAQgD,WAAW,KAAK,eACxB;YACAd,UACEQ,2BAA2BpB,aAAatB,SAASuB,cAAcW;QACnE;QAEAA,UAAUU,oBAAoBtB,aAAatB,SAASuB,cAAcW;QAElE,IAAIA,SAAS;YACX7C,2BAA2B0D,MAAMzB,aAAa;gBAC5C,GAAGtB,OAAO;gBACVwB,SAAS7B,eAAeK,QAAQwB,OAAO;YACzC;QACF;IACF;IAEA,MAAM/B,qBAAqBsD;IAE3B,OAAO;QAAExB;IAAU;AACrB"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
import { type MigrationReturnObject, type Tree } from '@nx/devkit';
|
|
6
|
+
export default function migration(tree: Tree): Promise<MigrationReturnObject>;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/ import { getProjects, updateProjectConfiguration } from "@nx/devkit";
|
|
5
|
+
import { formatFilesInSubtree } from "../../../utils/format.js";
|
|
6
|
+
/**
|
|
7
|
+
* A cacheable target declaring no `inputs` gets Nx's implicit
|
|
8
|
+
* `["default", "^default"]`, which reads a dependency's whole project directory
|
|
9
|
+
* — its README, its dev scripts, everything. That is wider than the explicit
|
|
10
|
+
* inputs its siblings carry, so editing a dependency's README re-ran targets
|
|
11
|
+
* whose output could not possibly change.
|
|
12
|
+
*
|
|
13
|
+
* Which explicit inputs are right depends on whether the target has a
|
|
14
|
+
* `dependsOn`. `default` carries a transitive `dependentTasksOutputFiles` entry,
|
|
15
|
+
* which Nx resolves against the task's upstream tasks — so a target with an
|
|
16
|
+
* upstream task still hashes the dependency output it consumes through it, and
|
|
17
|
+
* `["default"]` is enough. A target with no `dependsOn` has nothing for that
|
|
18
|
+
* entry to resolve against, so `default` collapses to the project's own files
|
|
19
|
+
* and dropping `^default` would remove the last edge to the dependency. Those
|
|
20
|
+
* keep a dependency input: `["default", "^production"]`.
|
|
21
|
+
*/ /** Targets whose `dependsOn` keeps the dependency edge inside `default`. */ const DEFAULT_ONLY_NAMES = [
|
|
22
|
+
'bundle',
|
|
23
|
+
'bundle-migration',
|
|
24
|
+
'bundle-create-db-user',
|
|
25
|
+
'operations'
|
|
26
|
+
];
|
|
27
|
+
/**
|
|
28
|
+
* Suffix of the OpenAPI spec targets, which carry a per-agent prefix
|
|
29
|
+
* (`my-agent-openapi`) on a project hosting more than one.
|
|
30
|
+
*
|
|
31
|
+
* These have no `dependsOn`, and their spec serialises models a dependency may
|
|
32
|
+
* own, so they keep a dependency input — without it a dependency's model change
|
|
33
|
+
* serves a stale spec, and that spec feeds client generation.
|
|
34
|
+
*/ const OPENAPI_SUFFIX = 'openapi';
|
|
35
|
+
const CHECKOV_TARGET = 'checkov';
|
|
36
|
+
/**
|
|
37
|
+
* Inputs for a target with no upstream task to resolve `default`'s transitive
|
|
38
|
+
* `dependentTasksOutputFiles` against. `^production` rather than `^default` so a
|
|
39
|
+
* dependency's test edits still do not invalidate it.
|
|
40
|
+
*/ const WITH_DEPENDENCY_INPUTS = [
|
|
41
|
+
'default',
|
|
42
|
+
'^production'
|
|
43
|
+
];
|
|
44
|
+
/** Whether a target is one of the ones narrowed to `["default"]`. */ const isDefaultOnlyTarget = (name)=>DEFAULT_ONLY_NAMES.includes(name);
|
|
45
|
+
/** Whether a target is an OpenAPI spec target, which has no `dependsOn`. */ const isOpenApiTarget = (name)=>name === OPENAPI_SUFFIX || name.endsWith(`-${OPENAPI_SUFFIX}`);
|
|
46
|
+
/**
|
|
47
|
+
* Whether a target still looks like the one the generators produced: a
|
|
48
|
+
* run-commands target declaring where it writes. A target with no declared
|
|
49
|
+
* outputs is not one of ours, and is the user's to scope since only they know
|
|
50
|
+
* what it reads.
|
|
51
|
+
*
|
|
52
|
+
* The location is not checked. `operations` writes generated Terraform sources
|
|
53
|
+
* under `packages/common/terraform/src/generated/`, not `dist`, so requiring
|
|
54
|
+
* `dist` would mean this migration could never match a real one.
|
|
55
|
+
*/ const matchesVendedShape = (target)=>target.executor === 'nx:run-commands' && Array.isArray(target.outputs) && target.outputs.length > 0 && target.outputs.every((output)=>typeof output === 'string');
|
|
56
|
+
/** The inputs a target should declare, or undefined to leave it alone. */ const inputsFor = (name, target)=>{
|
|
57
|
+
// Only a cacheable target with no inputs at all falls back to Nx's implicit
|
|
58
|
+
// ones, so one already declaring inputs is left as it is — which also makes
|
|
59
|
+
// re-running this a no-op.
|
|
60
|
+
if (!target.cache || target.inputs) return undefined;
|
|
61
|
+
if (!matchesVendedShape(target)) return undefined;
|
|
62
|
+
// `checkov` resolves the relative Terraform modules a project consumes, so it
|
|
63
|
+
// keeps a dependency edge rather than dropping one.
|
|
64
|
+
if (name === CHECKOV_TARGET || isOpenApiTarget(name)) {
|
|
65
|
+
return WITH_DEPENDENCY_INPUTS;
|
|
66
|
+
}
|
|
67
|
+
return isDefaultOnlyTarget(name) ? [
|
|
68
|
+
'default'
|
|
69
|
+
] : undefined;
|
|
70
|
+
};
|
|
71
|
+
export default async function migration(tree) {
|
|
72
|
+
const nextSteps = [];
|
|
73
|
+
for (const [projectName, project] of getProjects(tree)){
|
|
74
|
+
let changed = false;
|
|
75
|
+
for (const [name, target] of Object.entries(project.targets ?? {})){
|
|
76
|
+
const inputs = inputsFor(name, target);
|
|
77
|
+
if (!inputs) continue;
|
|
78
|
+
project.targets[name] = {
|
|
79
|
+
...target,
|
|
80
|
+
inputs
|
|
81
|
+
};
|
|
82
|
+
changed = true;
|
|
83
|
+
}
|
|
84
|
+
if (changed) {
|
|
85
|
+
updateProjectConfiguration(tree, projectName, project);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
await formatFilesInSubtree(tree);
|
|
89
|
+
return {
|
|
90
|
+
nextSteps
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
//# sourceMappingURL=migration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/latest/declare-cacheable-target-inputs/migration.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n getProjects,\n type MigrationReturnObject,\n type TargetConfiguration,\n type Tree,\n updateProjectConfiguration,\n} from '@nx/devkit';\nimport { formatFilesInSubtree } from '../../../utils/format.js';\n\n/**\n * A cacheable target declaring no `inputs` gets Nx's implicit\n * `[\"default\", \"^default\"]`, which reads a dependency's whole project directory\n * — its README, its dev scripts, everything. That is wider than the explicit\n * inputs its siblings carry, so editing a dependency's README re-ran targets\n * whose output could not possibly change.\n *\n * Which explicit inputs are right depends on whether the target has a\n * `dependsOn`. `default` carries a transitive `dependentTasksOutputFiles` entry,\n * which Nx resolves against the task's upstream tasks — so a target with an\n * upstream task still hashes the dependency output it consumes through it, and\n * `[\"default\"]` is enough. A target with no `dependsOn` has nothing for that\n * entry to resolve against, so `default` collapses to the project's own files\n * and dropping `^default` would remove the last edge to the dependency. Those\n * keep a dependency input: `[\"default\", \"^production\"]`.\n */\n\n/** Targets whose `dependsOn` keeps the dependency edge inside `default`. */\nconst DEFAULT_ONLY_NAMES = [\n 'bundle',\n 'bundle-migration',\n 'bundle-create-db-user',\n 'operations',\n];\n\n/**\n * Suffix of the OpenAPI spec targets, which carry a per-agent prefix\n * (`my-agent-openapi`) on a project hosting more than one.\n *\n * These have no `dependsOn`, and their spec serialises models a dependency may\n * own, so they keep a dependency input — without it a dependency's model change\n * serves a stale spec, and that spec feeds client generation.\n */\nconst OPENAPI_SUFFIX = 'openapi';\n\nconst CHECKOV_TARGET = 'checkov';\n\n/**\n * Inputs for a target with no upstream task to resolve `default`'s transitive\n * `dependentTasksOutputFiles` against. `^production` rather than `^default` so a\n * dependency's test edits still do not invalidate it.\n */\nconst WITH_DEPENDENCY_INPUTS = ['default', '^production'];\n\n/** Whether a target is one of the ones narrowed to `[\"default\"]`. */\nconst isDefaultOnlyTarget = (name: string): boolean =>\n DEFAULT_ONLY_NAMES.includes(name);\n\n/** Whether a target is an OpenAPI spec target, which has no `dependsOn`. */\nconst isOpenApiTarget = (name: string): boolean =>\n name === OPENAPI_SUFFIX || name.endsWith(`-${OPENAPI_SUFFIX}`);\n\n/**\n * Whether a target still looks like the one the generators produced: a\n * run-commands target declaring where it writes. A target with no declared\n * outputs is not one of ours, and is the user's to scope since only they know\n * what it reads.\n *\n * The location is not checked. `operations` writes generated Terraform sources\n * under `packages/common/terraform/src/generated/`, not `dist`, so requiring\n * `dist` would mean this migration could never match a real one.\n */\nconst matchesVendedShape = (target: TargetConfiguration): boolean =>\n target.executor === 'nx:run-commands' &&\n Array.isArray(target.outputs) &&\n target.outputs.length > 0 &&\n target.outputs.every((output) => typeof output === 'string');\n\n/** The inputs a target should declare, or undefined to leave it alone. */\nconst inputsFor = (\n name: string,\n target: TargetConfiguration,\n): string[] | undefined => {\n // Only a cacheable target with no inputs at all falls back to Nx's implicit\n // ones, so one already declaring inputs is left as it is — which also makes\n // re-running this a no-op.\n if (!target.cache || target.inputs) return undefined;\n if (!matchesVendedShape(target)) return undefined;\n // `checkov` resolves the relative Terraform modules a project consumes, so it\n // keeps a dependency edge rather than dropping one.\n if (name === CHECKOV_TARGET || isOpenApiTarget(name)) {\n return WITH_DEPENDENCY_INPUTS;\n }\n return isDefaultOnlyTarget(name) ? ['default'] : undefined;\n};\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const nextSteps: string[] = [];\n\n for (const [projectName, project] of getProjects(tree)) {\n let changed = false;\n\n for (const [name, target] of Object.entries(project.targets ?? {})) {\n const inputs = inputsFor(name, target);\n if (!inputs) continue;\n\n project.targets[name] = { ...target, inputs };\n changed = true;\n }\n\n if (changed) {\n updateProjectConfiguration(tree, projectName, project);\n }\n }\n\n await formatFilesInSubtree(tree);\n\n return { nextSteps };\n}\n"],"names":["getProjects","updateProjectConfiguration","formatFilesInSubtree","DEFAULT_ONLY_NAMES","OPENAPI_SUFFIX","CHECKOV_TARGET","WITH_DEPENDENCY_INPUTS","isDefaultOnlyTarget","name","includes","isOpenApiTarget","endsWith","matchesVendedShape","target","executor","Array","isArray","outputs","length","every","output","inputsFor","cache","inputs","undefined","migration","tree","nextSteps","projectName","project","changed","Object","entries","targets"],"mappings":"AAAA;;;CAGC,GACD,SACEA,WAAW,EAIXC,0BAA0B,QACrB,aAAa;AACpB,SAASC,oBAAoB,QAAQ,2BAA2B;AAEhE;;;;;;;;;;;;;;;CAeC,GAED,0EAA0E,GAC1E,MAAMC,qBAAqB;IACzB;IACA;IACA;IACA;CACD;AAED;;;;;;;CAOC,GACD,MAAMC,iBAAiB;AAEvB,MAAMC,iBAAiB;AAEvB;;;;CAIC,GACD,MAAMC,yBAAyB;IAAC;IAAW;CAAc;AAEzD,mEAAmE,GACnE,MAAMC,sBAAsB,CAACC,OAC3BL,mBAAmBM,QAAQ,CAACD;AAE9B,0EAA0E,GAC1E,MAAME,kBAAkB,CAACF,OACvBA,SAASJ,kBAAkBI,KAAKG,QAAQ,CAAC,CAAC,CAAC,EAAEP,gBAAgB;AAE/D;;;;;;;;;CASC,GACD,MAAMQ,qBAAqB,CAACC,SAC1BA,OAAOC,QAAQ,KAAK,qBACpBC,MAAMC,OAAO,CAACH,OAAOI,OAAO,KAC5BJ,OAAOI,OAAO,CAACC,MAAM,GAAG,KACxBL,OAAOI,OAAO,CAACE,KAAK,CAAC,CAACC,SAAW,OAAOA,WAAW;AAErD,wEAAwE,GACxE,MAAMC,YAAY,CAChBb,MACAK;IAEA,4EAA4E;IAC5E,4EAA4E;IAC5E,2BAA2B;IAC3B,IAAI,CAACA,OAAOS,KAAK,IAAIT,OAAOU,MAAM,EAAE,OAAOC;IAC3C,IAAI,CAACZ,mBAAmBC,SAAS,OAAOW;IACxC,8EAA8E;IAC9E,oDAAoD;IACpD,IAAIhB,SAASH,kBAAkBK,gBAAgBF,OAAO;QACpD,OAAOF;IACT;IACA,OAAOC,oBAAoBC,QAAQ;QAAC;KAAU,GAAGgB;AACnD;AAEA,eAAe,eAAeC,UAC5BC,IAAU;IAEV,MAAMC,YAAsB,EAAE;IAE9B,KAAK,MAAM,CAACC,aAAaC,QAAQ,IAAI7B,YAAY0B,MAAO;QACtD,IAAII,UAAU;QAEd,KAAK,MAAM,CAACtB,MAAMK,OAAO,IAAIkB,OAAOC,OAAO,CAACH,QAAQI,OAAO,IAAI,CAAC,GAAI;YAClE,MAAMV,SAASF,UAAUb,MAAMK;YAC/B,IAAI,CAACU,QAAQ;YAEbM,QAAQI,OAAO,CAACzB,KAAK,GAAG;gBAAE,GAAGK,MAAM;gBAAEU;YAAO;YAC5CO,UAAU;QACZ;QAEA,IAAIA,SAAS;YACX7B,2BAA2ByB,MAAME,aAAaC;QAChD;IACF;IAEA,MAAM3B,qBAAqBwB;IAE3B,OAAO;QAAEC;IAAU;AACrB"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
import { type MigrationReturnObject, type Tree } from '@nx/devkit';
|
|
6
|
+
export default function migration(tree: Tree): Promise<MigrationReturnObject>;
|