@aws/nx-plugin 1.0.0-rc.40 → 1.0.0-rc.41

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.
@@ -6,7 +6,6 @@ import { relative } from "path";
6
6
  import PackageJson from "../../../package.json" with {
7
7
  type: 'json'
8
8
  };
9
- import { formatFilesInSubtree } from "../../utils/format.js";
10
9
  import { getGeneratorInfo } from "../../utils/nx.js";
11
10
  export const TS_SYNC_GENERATOR_INFO = getGeneratorInfo(import.meta.filename);
12
11
  export const SYNC_GENERATOR_NAME = `${PackageJson.name}:${TS_SYNC_GENERATOR_INFO.id}`;
@@ -35,7 +34,6 @@ export const tsSyncGeneratorGenerator = async (tree)=>{
35
34
  if (Object.keys(changesByConfigFile).length === 0) {
36
35
  return {};
37
36
  }
38
- await formatFilesInSubtree(tree);
39
37
  return {
40
38
  outOfSyncMessage: buildOutOfSyncMessage(changesByConfigFile)
41
39
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../packages/nx-plugin/src/ts/sync/generator.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport {\n getProjects,\n joinPathFragments,\n readJson,\n type Tree,\n updateJson,\n} from '@nx/devkit';\nimport type { SyncGeneratorResult } from 'nx/src/utils/sync-generators';\nimport { relative } from 'path';\nimport PackageJson from '../../../package.json' with { type: 'json' };\nimport { formatFilesInSubtree } from '../../utils/format';\nimport { getGeneratorInfo, type NxGeneratorInfo } from '../../utils/nx';\n\nexport const TS_SYNC_GENERATOR_INFO: NxGeneratorInfo = getGeneratorInfo(\n import.meta.filename,\n);\n\nexport const SYNC_GENERATOR_NAME = `${PackageJson.name}:${TS_SYNC_GENERATOR_INFO.id}`;\n\nexport const tsSyncGeneratorGenerator = async (\n tree: Tree,\n): Promise<SyncGeneratorResult> => {\n const basePaths = readBaseTsConfigPaths(tree);\n\n if (!basePaths) {\n return {};\n }\n\n const changesByConfigFile: Record<string, PathChange[]> = {};\n\n for (const project of getProjects(tree).values()) {\n for (const tsConfigFileName of [\n 'tsconfig.json',\n 'tsconfig.lib.json',\n 'tsconfig.app.json',\n ]) {\n const tsConfigPath = joinPathFragments(project.root, tsConfigFileName);\n if (!tree.exists(tsConfigPath)) {\n continue;\n }\n\n const { changes, updated } = syncPathsWithBase(\n tree,\n tsConfigPath,\n basePaths,\n project.root,\n );\n\n if (updated) {\n changesByConfigFile[tsConfigPath] = changes;\n }\n }\n }\n\n if (Object.keys(changesByConfigFile).length === 0) {\n return {};\n }\n\n await formatFilesInSubtree(tree);\n\n return {\n outOfSyncMessage: buildOutOfSyncMessage(changesByConfigFile),\n };\n};\n\nexport default tsSyncGeneratorGenerator;\n\nconst readBaseTsConfigPaths = (\n tree: Tree,\n): Record<string, string[]> | undefined => {\n const baseConfigPath = ['tsconfig.base.json', 'tsconfig.json'].find((path) =>\n tree.exists(path),\n );\n\n if (!baseConfigPath) {\n return undefined;\n }\n\n const baseConfigJson = readJson<Record<string, any>>(tree, baseConfigPath);\n return baseConfigJson?.compilerOptions?.paths ?? {};\n};\n\ntype PathChangeType = 'added' | 'updated';\n\ntype PathChange = {\n alias: string;\n type: PathChangeType;\n};\n\nconst arePathArraysEqual = (first: string[], second: string[]): boolean => {\n if (first.length !== second.length) {\n return false;\n }\n\n return first.every((value, index) => value === second[index]);\n};\n\n/**\n * Rebase a path from the workspace root to a project root.\n * e.g. \"./packages/foo/src/index.ts\" from project \"packages/bar\"\n * becomes \"../../packages/foo/src/index.ts\"\n */\nconst rebasePath = (pathValue: string, projectRoot: string): string => {\n // Strip leading ./ if present\n const normalized = pathValue.startsWith('./')\n ? pathValue.slice(2)\n : pathValue;\n const relPrefix = relative(projectRoot, '.') || '.';\n return joinPathFragments(relPrefix, normalized);\n};\n\nconst syncPathsWithBase = (\n tree: Tree,\n tsConfigPath: string,\n basePaths: Record<string, string[]>,\n projectRoot: string,\n): { updated: boolean; changes: PathChange[] } => {\n const changes: PathChange[] = [];\n\n const tsConfigJson = readJson(tree, tsConfigPath);\n const paths = tsConfigJson?.compilerOptions?.paths;\n\n if (!paths) {\n return { updated: false, changes };\n }\n\n const updatedAliases: Record<string, string[]> = {\n ...(tsConfigJson.compilerOptions?.paths ?? {}),\n };\n let updated = false;\n\n // Add or update aliases that exist in the base config, rebasing paths\n // relative to the project root since baseUrl is no longer set\n for (const [alias, baseValue] of Object.entries(basePaths)) {\n const rebasedValue = (baseValue as string[]).map((p) =>\n rebasePath(p, projectRoot),\n );\n const existingValue = updatedAliases[alias];\n if (!existingValue) {\n changes.push({ alias, type: 'added' });\n updated = true;\n } else if (!arePathArraysEqual(existingValue, rebasedValue)) {\n changes.push({ alias, type: 'updated' });\n updated = true;\n }\n\n updatedAliases[alias] = rebasedValue;\n }\n\n if (updated) {\n updateJson(tree, tsConfigPath, (json) => ({\n ...json,\n compilerOptions: {\n ...json.compilerOptions,\n paths: updatedAliases,\n },\n }));\n }\n\n return { updated, changes };\n};\n\n/**\n * Build the message to display when the sync generator would make changes to the tree\n */\nconst buildOutOfSyncMessage = (\n changesByConfig: Record<string, PathChange[]>,\n): string =>\n `TypeScript path aliases are out of sync with the base tsconfig. The following configs will be updated:\\n${Object.entries(\n changesByConfig,\n )\n .map(\n ([config, changes]) =>\n `${config}:\\n${changes\n .slice()\n .sort((left, right) => left.alias.localeCompare(right.alias))\n .map(({ alias, type }) => `- ${alias} (${type})`)\n .join('\\n')}`,\n )\n .join('\\n\\n')}`;\n"],"names":["getProjects","joinPathFragments","readJson","updateJson","relative","PackageJson","type","formatFilesInSubtree","getGeneratorInfo","TS_SYNC_GENERATOR_INFO","filename","SYNC_GENERATOR_NAME","name","id","tsSyncGeneratorGenerator","tree","basePaths","readBaseTsConfigPaths","changesByConfigFile","project","values","tsConfigFileName","tsConfigPath","root","exists","changes","updated","syncPathsWithBase","Object","keys","length","outOfSyncMessage","buildOutOfSyncMessage","baseConfigPath","find","path","undefined","baseConfigJson","compilerOptions","paths","arePathArraysEqual","first","second","every","value","index","rebasePath","pathValue","projectRoot","normalized","startsWith","slice","relPrefix","tsConfigJson","updatedAliases","alias","baseValue","entries","rebasedValue","map","p","existingValue","push","json","changesByConfig","config","sort","left","right","localeCompare","join"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../packages/nx-plugin/src/ts/sync/generator.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport {\n getProjects,\n joinPathFragments,\n readJson,\n type Tree,\n updateJson,\n} from '@nx/devkit';\nimport type { SyncGeneratorResult } from 'nx/src/utils/sync-generators';\nimport { relative } from 'path';\nimport PackageJson from '../../../package.json' with { type: 'json' };\nimport { getGeneratorInfo, type NxGeneratorInfo } from '../../utils/nx';\n\nexport const TS_SYNC_GENERATOR_INFO: NxGeneratorInfo = getGeneratorInfo(\n import.meta.filename,\n);\n\nexport const SYNC_GENERATOR_NAME = `${PackageJson.name}:${TS_SYNC_GENERATOR_INFO.id}`;\n\nexport const tsSyncGeneratorGenerator = async (\n tree: Tree,\n): Promise<SyncGeneratorResult> => {\n const basePaths = readBaseTsConfigPaths(tree);\n\n if (!basePaths) {\n return {};\n }\n\n const changesByConfigFile: Record<string, PathChange[]> = {};\n\n for (const project of getProjects(tree).values()) {\n for (const tsConfigFileName of [\n 'tsconfig.json',\n 'tsconfig.lib.json',\n 'tsconfig.app.json',\n ]) {\n const tsConfigPath = joinPathFragments(project.root, tsConfigFileName);\n if (!tree.exists(tsConfigPath)) {\n continue;\n }\n\n const { changes, updated } = syncPathsWithBase(\n tree,\n tsConfigPath,\n basePaths,\n project.root,\n );\n\n if (updated) {\n changesByConfigFile[tsConfigPath] = changes;\n }\n }\n }\n\n if (Object.keys(changesByConfigFile).length === 0) {\n return {};\n }\n\n return {\n outOfSyncMessage: buildOutOfSyncMessage(changesByConfigFile),\n };\n};\n\nexport default tsSyncGeneratorGenerator;\n\nconst readBaseTsConfigPaths = (\n tree: Tree,\n): Record<string, string[]> | undefined => {\n const baseConfigPath = ['tsconfig.base.json', 'tsconfig.json'].find((path) =>\n tree.exists(path),\n );\n\n if (!baseConfigPath) {\n return undefined;\n }\n\n const baseConfigJson = readJson<Record<string, any>>(tree, baseConfigPath);\n return baseConfigJson?.compilerOptions?.paths ?? {};\n};\n\ntype PathChangeType = 'added' | 'updated';\n\ntype PathChange = {\n alias: string;\n type: PathChangeType;\n};\n\nconst arePathArraysEqual = (first: string[], second: string[]): boolean => {\n if (first.length !== second.length) {\n return false;\n }\n\n return first.every((value, index) => value === second[index]);\n};\n\n/**\n * Rebase a path from the workspace root to a project root.\n * e.g. \"./packages/foo/src/index.ts\" from project \"packages/bar\"\n * becomes \"../../packages/foo/src/index.ts\"\n */\nconst rebasePath = (pathValue: string, projectRoot: string): string => {\n // Strip leading ./ if present\n const normalized = pathValue.startsWith('./')\n ? pathValue.slice(2)\n : pathValue;\n const relPrefix = relative(projectRoot, '.') || '.';\n return joinPathFragments(relPrefix, normalized);\n};\n\nconst syncPathsWithBase = (\n tree: Tree,\n tsConfigPath: string,\n basePaths: Record<string, string[]>,\n projectRoot: string,\n): { updated: boolean; changes: PathChange[] } => {\n const changes: PathChange[] = [];\n\n const tsConfigJson = readJson(tree, tsConfigPath);\n const paths = tsConfigJson?.compilerOptions?.paths;\n\n if (!paths) {\n return { updated: false, changes };\n }\n\n const updatedAliases: Record<string, string[]> = {\n ...(tsConfigJson.compilerOptions?.paths ?? {}),\n };\n let updated = false;\n\n // Add or update aliases that exist in the base config, rebasing paths\n // relative to the project root since baseUrl is no longer set\n for (const [alias, baseValue] of Object.entries(basePaths)) {\n const rebasedValue = (baseValue as string[]).map((p) =>\n rebasePath(p, projectRoot),\n );\n const existingValue = updatedAliases[alias];\n if (!existingValue) {\n changes.push({ alias, type: 'added' });\n updated = true;\n } else if (!arePathArraysEqual(existingValue, rebasedValue)) {\n changes.push({ alias, type: 'updated' });\n updated = true;\n }\n\n updatedAliases[alias] = rebasedValue;\n }\n\n if (updated) {\n updateJson(tree, tsConfigPath, (json) => ({\n ...json,\n compilerOptions: {\n ...json.compilerOptions,\n paths: updatedAliases,\n },\n }));\n }\n\n return { updated, changes };\n};\n\n/**\n * Build the message to display when the sync generator would make changes to the tree\n */\nconst buildOutOfSyncMessage = (\n changesByConfig: Record<string, PathChange[]>,\n): string =>\n `TypeScript path aliases are out of sync with the base tsconfig. The following configs will be updated:\\n${Object.entries(\n changesByConfig,\n )\n .map(\n ([config, changes]) =>\n `${config}:\\n${changes\n .slice()\n .sort((left, right) => left.alias.localeCompare(right.alias))\n .map(({ alias, type }) => `- ${alias} (${type})`)\n .join('\\n')}`,\n )\n .join('\\n\\n')}`;\n"],"names":["getProjects","joinPathFragments","readJson","updateJson","relative","PackageJson","type","getGeneratorInfo","TS_SYNC_GENERATOR_INFO","filename","SYNC_GENERATOR_NAME","name","id","tsSyncGeneratorGenerator","tree","basePaths","readBaseTsConfigPaths","changesByConfigFile","project","values","tsConfigFileName","tsConfigPath","root","exists","changes","updated","syncPathsWithBase","Object","keys","length","outOfSyncMessage","buildOutOfSyncMessage","baseConfigPath","find","path","undefined","baseConfigJson","compilerOptions","paths","arePathArraysEqual","first","second","every","value","index","rebasePath","pathValue","projectRoot","normalized","startsWith","slice","relPrefix","tsConfigJson","updatedAliases","alias","baseValue","entries","rebasedValue","map","p","existingValue","push","json","changesByConfig","config","sort","left","right","localeCompare","join"],"mappings":"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"}
@@ -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';\nimport { isEsmWorkspace } from './module-format';\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') ? from.slice(0, -'.js'.length) : 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 // 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 * 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"],"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","addStarExport","trim","alreadyExported","hasExportDeclaration","source","identifierName","pattern","q","result","applyToFile","content","query","matched","filter","captureGritQL","undefined","captured","node","text","captureAllGritQL","push","GRIT_INSERT_PLACEHOLDER","insertViaGritQL","at","lineSoFar","lastIndexOf","replacement","replace"],"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';\nimport { isEsmWorkspace } from './module-format';\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 // 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 * 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"],"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","addStarExport","trim","alreadyExported","hasExportDeclaration","source","identifierName","pattern","q","result","applyToFile","content","query","matched","filter","captureGritQL","undefined","captured","node","text","captureAllGritQL","push","GRIT_INSERT_PLACEHOLDER","insertViaGritQL","at","lineSoFar","lastIndexOf","replacement","replace"],"mappings":"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"}
@@ -34,82 +34,59 @@ resource "random_string" "suffix" {
34
34
  upper = false
35
35
  }
36
36
 
37
- # Access logs bucket for the asset bucket.
38
- resource "aws_s3_bucket" "access_logs" {
39
- #checkov:skip=CKV2_AWS_61:Lifecycle configuration not required for access log bucket
40
- #checkov:skip=CKV_AWS_144:Cross-region replication not required for access log bucket
41
- #checkov:skip=CKV2_AWS_62:Event notifications not required for access log bucket
42
- #checkov:skip=CKV_AWS_18:Access logging the access log bucket would create a cycle
43
- #checkov:skip=CKV_AWS_145:AES256 (S3-managed) encryption is sufficient for access logs
44
- bucket = "${var.bucket_name_prefix}-logs-${data.aws_caller_identity.current.account_id}-${data.aws_region.current.region}-${random_string.suffix.result}"
45
- force_destroy = true
46
-
47
- tags = var.tags
37
+ locals {
38
+ # Delivery source/destination names have a 60 character maximum. Truncate the
39
+ # bucket name prefix so the delivery names stay within the limit.
40
+ access_logs_name_prefix = substr(var.bucket_name_prefix, 0, 16)
48
41
  }
49
42
 
50
- resource "aws_s3_bucket_versioning" "access_logs" {
51
- bucket = aws_s3_bucket.access_logs.id
52
- versioning_configuration {
53
- status = "Enabled"
54
- }
55
- }
56
-
57
- resource "aws_s3_bucket_server_side_encryption_configuration" "access_logs" {
58
- bucket = aws_s3_bucket.access_logs.id
59
-
60
- rule {
61
- apply_server_side_encryption_by_default {
62
- sse_algorithm = "AES256"
63
- }
64
- }
65
- }
66
-
67
- resource "aws_s3_bucket_public_access_block" "access_logs" {
68
- bucket = aws_s3_bucket.access_logs.id
69
-
70
- block_public_acls = true
71
- block_public_policy = true
72
- ignore_public_acls = true
73
- restrict_public_buckets = true
74
- }
75
-
76
- resource "aws_s3_bucket_policy" "access_logs" {
77
- bucket = aws_s3_bucket.access_logs.id
43
+ # KMS key encrypting the server access logs delivered to CloudWatch Logs.
44
+ resource "aws_kms_key" "access_logs" {
45
+ description = "KMS key for ${var.bucket_name_prefix} asset bucket access logs"
46
+ enable_key_rotation = true
47
+ deletion_window_in_days = 7
78
48
 
79
49
  policy = jsonencode({
80
50
  Version = "2012-10-17"
81
51
  Statement = [
82
52
  {
83
- Sid = "DenyInsecureConnections"
84
- Effect = "Deny"
85
- Principal = "*"
86
- Action = "s3:*"
87
- Resource = [
88
- aws_s3_bucket.access_logs.arn,
89
- "${aws_s3_bucket.access_logs.arn}/*"
90
- ]
91
- Condition = {
92
- Bool = {
93
- "aws:SecureTransport" = "false"
94
- }
95
- }
53
+ Sid = "EnableIAMUserPermissions"
54
+ Effect = "Allow"
55
+ Principal = { AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root" }
56
+ Action = "kms:*"
57
+ Resource = "*"
96
58
  },
97
59
  {
98
- Sid = "AllowS3LogDelivery"
60
+ Sid = "AllowCloudWatchLogs"
99
61
  Effect = "Allow"
100
- Principal = {
101
- Service = "logging.s3.amazonaws.com"
102
- }
103
- Action = "s3:PutObject"
104
- Resource = "${aws_s3_bucket.access_logs.arn}/*"
62
+ Principal = { Service = "logs.${data.aws_region.current.region}.amazonaws.com" }
63
+ Action = [
64
+ "kms:Encrypt",
65
+ "kms:Decrypt",
66
+ "kms:ReEncrypt*",
67
+ "kms:GenerateDataKey*",
68
+ "kms:DescribeKey"
69
+ ]
70
+ Resource = "*"
105
71
  Condition = {
106
- StringEquals = {
107
- "aws:SourceAccount" = data.aws_caller_identity.current.account_id
72
+ ArnLike = {
73
+ "kms:EncryptionContext:aws:logs:arn" = "arn:aws:logs:${data.aws_region.current.region}:${data.aws_caller_identity.current.account_id}:log-group:*"
108
74
  }
109
75
  }
110
76
  }
111
77
  ]
112
78
  })
79
+
80
+ tags = var.tags
81
+ }
82
+
83
+ # CloudWatch log group receiving the asset bucket server access logs.
84
+ resource "aws_cloudwatch_log_group" "access_logs" {
85
+ name = "/aws/s3/${var.bucket_name_prefix}-access-logs-${random_string.suffix.result}"
86
+ retention_in_days = 365
87
+ kms_key_id = aws_kms_key.access_logs.arn
88
+
89
+ tags = var.tags
113
90
  }
114
91
 
115
92
  # Shared asset bucket
@@ -118,6 +95,7 @@ resource "aws_s3_bucket" "assets" {
118
95
  #checkov:skip=CKV_AWS_144:Cross-region replication not required for asset bucket
119
96
  #checkov:skip=CKV2_AWS_62:Event notifications not required for asset bucket
120
97
  #checkov:skip=CKV_AWS_145:AES256 (S3-managed) encryption is sufficient for build artefacts
98
+ #checkov:skip=CKV_AWS_18:Server access logs are delivered to CloudWatch Logs (see aws_cloudwatch_log_delivery.assets)
121
99
  bucket = "${var.bucket_name_prefix}-${data.aws_caller_identity.current.account_id}-${data.aws_region.current.region}-${random_string.suffix.result}"
122
100
  force_destroy = true
123
101
 
@@ -150,11 +128,24 @@ resource "aws_s3_bucket_public_access_block" "assets" {
150
128
  restrict_public_buckets = true
151
129
  }
152
130
 
153
- resource "aws_s3_bucket_logging" "assets" {
154
- bucket = aws_s3_bucket.assets.id
131
+ # Deliver the asset bucket's server access logs to CloudWatch Logs.
132
+ resource "aws_cloudwatch_log_delivery_source" "assets" {
133
+ name = "${local.access_logs_name_prefix}-access-logs-source-${random_string.suffix.result}"
134
+ log_type = "S3_SERVER_ACCESS_LOGS"
135
+ resource_arn = aws_s3_bucket.assets.arn
136
+ }
137
+
138
+ resource "aws_cloudwatch_log_delivery_destination" "assets" {
139
+ name = "${local.access_logs_name_prefix}-access-logs-dest-${random_string.suffix.result}"
140
+
141
+ delivery_destination_configuration {
142
+ destination_resource_arn = aws_cloudwatch_log_group.access_logs.arn
143
+ }
144
+ }
155
145
 
156
- target_bucket = aws_s3_bucket.access_logs.id
157
- target_prefix = "s3-access-logs/"
146
+ resource "aws_cloudwatch_log_delivery" "assets" {
147
+ delivery_source_name = aws_cloudwatch_log_delivery_source.assets.name
148
+ delivery_destination_arn = aws_cloudwatch_log_delivery_destination.assets.arn
158
149
  }
159
150
 
160
151
  resource "aws_s3_bucket_policy" "assets" {
@@ -192,12 +183,12 @@ output "bucket_arn" {
192
183
  value = aws_s3_bucket.assets.arn
193
184
  }
194
185
 
195
- output "access_logs_bucket_name" {
196
- description = "Name of the access-logs bucket that receives S3 access logs for the asset bucket."
197
- value = aws_s3_bucket.access_logs.id
186
+ output "access_logs_log_group_name" {
187
+ description = "Name of the CloudWatch log group that receives S3 server access logs for the asset bucket."
188
+ value = aws_cloudwatch_log_group.access_logs.name
198
189
  }
199
190
 
200
- output "access_logs_bucket_arn" {
201
- description = "ARN of the access-logs bucket"
202
- value = aws_s3_bucket.access_logs.arn
191
+ output "access_logs_log_group_arn" {
192
+ description = "ARN of the CloudWatch log group that receives S3 server access logs for the asset bucket."
193
+ value = aws_cloudwatch_log_group.access_logs.arn
203
194
  }
@@ -56,7 +56,10 @@ export const DEFAULT_BIOME_CONFIG = {
56
56
  '!**/node_modules',
57
57
  '!**/.nx',
58
58
  '!**/.venv',
59
- '!**/*.css'
59
+ '!**/*.css',
60
+ '!**/*.gen.*',
61
+ '!**/generated/**',
62
+ '!**/tsconfig*.json'
60
63
  ]
61
64
  }
62
65
  };
@@ -73,6 +76,7 @@ const BIOME_FORMATTABLE_EXTENSIONS = new Set([
73
76
  '.jsonc',
74
77
  '.css'
75
78
  ]);
79
+ /** Matches `tsconfig.json` and variants like `tsconfig.lib.json`. */ const isTsConfig = (filePath)=>/(^|\/)tsconfig[^/]*\.json$/.test(filePath);
76
80
  /**
77
81
  * Format files in the given directory within the tree.
78
82
  * Handles both TypeScript/JavaScript/JSON (via biome) and Python (via ruff) files.
@@ -80,7 +84,12 @@ const BIOME_FORMATTABLE_EXTENSIONS = new Set([
80
84
  */ export async function formatFilesInSubtree(tree, dir) {
81
85
  const changedFiles = tree.listChanges().filter((file)=>file.type !== 'DELETE').filter((file)=>dir ? file.path.startsWith(dir) : true);
82
86
  const pyFiles = changedFiles.filter((file)=>file.path.endsWith('.py'));
83
- const otherFiles = changedFiles.filter((file)=>BIOME_FORMATTABLE_EXTENSIONS.has(path.extname(file.path)));
87
+ const otherFiles = changedFiles.filter((file)=>BIOME_FORMATTABLE_EXTENSIONS.has(path.extname(file.path)) && // tsconfigs are not biome-managed: they're excluded from the vended
88
+ // format target (Nx's typescript-sync rewrites them without formatting),
89
+ // so formatting them at generation would only diverge from the form
90
+ // written on later runs. Leave them as updateJson/writeJson emit them so
91
+ // repeated generation stays idempotent.
92
+ !isTsConfig(file.path));
84
93
  // Resolve each project's ruff settings (module names, line-length) so files
85
94
  // are formatted to match the on-disk build (see getPythonProjectRuffConfigs).
86
95
  const pythonProjectConfigs = pyFiles.length ? getPythonProjectRuffConfigs(tree) : [];
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../packages/nx-plugin/src/utils/format.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { Biome } from '@biomejs/js-api/nodejs';\nimport { getProjects, type Tree } from '@nx/devkit';\nimport { execFileSync, execSync } from 'child_process';\nimport { existsSync, readFileSync } from 'fs';\nimport { createRequire } from 'module';\nimport path from 'path';\nimport { readToml } from './toml';\n\nconst require = createRequire(import.meta.url);\n\nexport const DEFAULT_BIOME_CONFIG = {\n $schema: 'https://biomejs.dev/schemas/2.4.16/schema.json',\n root: true,\n formatter: {\n enabled: true,\n indentStyle: 'space',\n indentWidth: 2,\n lineWidth: 80,\n },\n javascript: {\n formatter: {\n quoteStyle: 'single',\n trailingCommas: 'all',\n },\n },\n css: {\n formatter: {\n quoteStyle: 'single',\n },\n linter: {\n enabled: false,\n },\n },\n linter: {\n enabled: true,\n rules: {\n recommended: false,\n correctness: {\n noUndeclaredDependencies: 'warn',\n },\n },\n },\n assist: {\n actions: {\n source: {\n organizeImports: 'on',\n },\n },\n },\n files: {\n includes: [\n '**',\n '!**/dist',\n '!**/out-tsc',\n '!**/node_modules',\n '!**/.nx',\n '!**/.venv',\n '!**/*.css',\n ],\n },\n};\n\nconst BIOME_FORMATTABLE_EXTENSIONS = new Set([\n '.ts',\n '.tsx',\n '.js',\n '.jsx',\n '.mjs',\n '.cjs',\n '.mts',\n '.cts',\n '.json',\n '.jsonc',\n '.css',\n]);\n\n/**\n * Format files in the given directory within the tree.\n * Handles both TypeScript/JavaScript/JSON (via biome) and Python (via ruff) files.\n * See https://github.com/nrwl/nx/blob/4cd640a9187954505d12de5b6d76a90d8ce4c2eb/packages/devkit/src/generators/format-files.ts#L11\n */\nexport async function formatFilesInSubtree(\n tree: Tree,\n dir?: string,\n): Promise<void> {\n const changedFiles = tree\n .listChanges()\n .filter((file) => file.type !== 'DELETE')\n .filter((file) => (dir ? file.path.startsWith(dir) : true));\n\n const pyFiles = changedFiles.filter((file) => file.path.endsWith('.py'));\n const otherFiles = changedFiles.filter((file) =>\n BIOME_FORMATTABLE_EXTENSIONS.has(path.extname(file.path)),\n );\n\n // Resolve each project's ruff settings (module names, line-length) so files\n // are formatted to match the on-disk build (see getPythonProjectRuffConfigs).\n const pythonProjectConfigs = pyFiles.length\n ? getPythonProjectRuffConfigs(tree)\n : [];\n\n // Format Python files with ruff (lint fixes + formatting)\n for (const file of pyFiles) {\n try {\n const content = ruffFixAndFormat(\n file.content.toString('utf-8'),\n file.path,\n hasRuffConfigOnDisk(tree, file.path),\n getOwningProjectRuffConfig(file.path, pythonProjectConfigs),\n );\n tree.write(file.path, content);\n } catch {\n // Silently skip ruff formatting failures\n }\n }\n\n if (otherFiles.length === 0) return;\n\n // Use the workspace's own Biome CLI (its version and config) when biome.json\n // exists on disk; otherwise format via the bundled library API with the\n // in-memory tree config. The CLI path does not see in-tree config changes.\n if (existsSync(path.join(tree.root, 'biome.json'))) {\n formatWithBiomeCli(tree, otherFiles);\n } else {\n formatWithBiomeApi(tree, otherFiles);\n }\n}\n\n/**\n * Format files via the workspace's Biome CLI, run from the workspace root so it\n * discovers the on-disk biome.json.\n */\nfunction formatWithBiomeCli(\n tree: Tree,\n files: { path: string; content: Buffer | null }[],\n): void {\n const biome = getBiomeCommand(tree.root);\n if (!biome) {\n // Fall back to the library API if the CLI cannot be resolved\n formatWithBiomeApi(tree, files);\n return;\n }\n\n for (const file of files) {\n try {\n const content = execFileSync(\n biome.command,\n [...biome.args, 'format', `--stdin-file-path=${file.path}`],\n {\n input: file.content?.toString('utf-8') ?? '',\n encoding: 'utf-8',\n cwd: tree.root,\n stdio: ['pipe', 'pipe', 'pipe'],\n },\n );\n tree.write(file.path, content);\n } catch {\n // Leave individual files that fail to format untouched\n }\n }\n}\n\n/**\n * Format files via the bundled Biome library API, applying the in-memory tree\n * config.\n */\nfunction formatWithBiomeApi(\n tree: Tree,\n files: { path: string; content: Buffer | null }[],\n): void {\n try {\n const biome = new Biome();\n const { projectKey } = biome.openProject();\n // Apply the workspace biome.json if it exists in the tree, otherwise the defaults.\n const treeConfig = tree.read('biome.json', 'utf-8');\n biome.applyConfiguration(\n projectKey,\n treeConfig ? JSON.parse(treeConfig) : DEFAULT_BIOME_CONFIG,\n );\n\n for (const file of files) {\n try {\n const { content } = biome.formatContent(\n projectKey,\n file.content?.toString('utf-8') ?? '',\n { filePath: file.path },\n );\n tree.write(file.path, content);\n } catch {\n // Leave individual files that fail to format untouched\n }\n }\n } catch {\n // Silently skip formatting failures\n }\n}\n\ninterface BiomeCommand {\n command: string;\n args: string[];\n}\n\n/**\n * Resolve the `@biomejs/biome` CLI from the user's workspace, falling back to a\n * `biome` binary on the PATH.\n */\nconst _biomeCommands = new Map<string, BiomeCommand | null>();\nfunction getBiomeCommand(root: string): BiomeCommand | undefined {\n if (_biomeCommands.has(root)) {\n return _biomeCommands.get(root) ?? undefined;\n }\n\n // Run via node for cross-platform execution of the bin shim.\n try {\n const pkgJsonPath = require.resolve('@biomejs/biome/package.json', {\n paths: [root, import.meta.dirname],\n });\n const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));\n const binRelative =\n typeof pkgJson.bin === 'string' ? pkgJson.bin : pkgJson.bin?.biome;\n if (binRelative) {\n const binPath = path.join(path.dirname(pkgJsonPath), binRelative);\n const command = { command: process.execPath, args: [binPath] };\n _biomeCommands.set(root, command);\n return command;\n }\n } catch {\n // Fall back to a biome binary on the PATH\n }\n\n try {\n execSync('biome --version', {\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n const command = { command: 'biome', args: [] };\n _biomeCommands.set(root, command);\n return command;\n } catch {\n _biomeCommands.set(root, null);\n return undefined;\n }\n}\n\n/**\n * Find the ruff command. Tries 'uv run ruff', then 'uvx ruff'.\n * Matches how @nxlv/python runs ruff via the UV provider.\n */\nlet _ruffCommand: string | undefined;\nfunction getRuffCommand(): string | undefined {\n if (_ruffCommand !== undefined) {\n return _ruffCommand || undefined;\n }\n for (const cmd of ['uv run ruff', 'uvx ruff']) {\n try {\n execSync(`${cmd} --version`, {\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n _ruffCommand = cmd;\n return cmd;\n } catch {\n // Try next command\n }\n }\n _ruffCommand = '';\n return undefined;\n}\n\n/**\n * Whether ruff would discover a config on disk for a file, by walking from its\n * directory up to the workspace root looking for `.ruff.toml`, `ruff.toml`, or a\n * `pyproject.toml` with a `[tool.ruff]` section — the same files ruff itself\n * resolves. The walk stops at `tree.root` so a stray config in a parent of the\n * workspace (or the home directory) is never treated as the project's. Used to\n * decide whether to nudge ruff towards import sorting (see\n * {@link ruffFixAndFormat}).\n */\nfunction hasRuffConfigOnDisk(tree: Tree, filePath: string): boolean {\n const root = path.resolve(tree.root);\n let dir = path.resolve(root, path.dirname(filePath));\n while (true) {\n if (\n existsSync(path.join(dir, '.ruff.toml')) ||\n existsSync(path.join(dir, 'ruff.toml'))\n ) {\n return true;\n }\n const pyproject = path.join(dir, 'pyproject.toml');\n if (\n existsSync(pyproject) &&\n readFileSync(pyproject, 'utf-8').includes('[tool.ruff')\n ) {\n return true;\n }\n const parent = path.dirname(dir);\n // Stop once the workspace root has been checked (or we hit the FS root).\n if (dir === root || parent === dir) {\n return false;\n }\n dir = parent;\n }\n}\n\ninterface PythonProjectRuffConfig {\n /** Project root, normalised to use forward slashes. */\n readonly root: string;\n /** Top-level importable module names declared by the project. */\n readonly modules: string[];\n /** The project's `[tool.ruff].line-length`, if set. */\n readonly lineLength?: number;\n}\n\n/**\n * Map each Nx project with a `pyproject.toml` to the ruff settings the on-disk\n * build enforces for it: its top-level module names (from\n * `[tool.hatch.build.targets.wheel].packages`) and its `[tool.ruff].line-length`.\n */\nfunction getPythonProjectRuffConfigs(tree: Tree): PythonProjectRuffConfig[] {\n const configs: PythonProjectRuffConfig[] = [];\n\n for (const project of getProjects(tree).values()) {\n const pyprojectPath = path.join(project.root, 'pyproject.toml');\n if (tree.exists(pyprojectPath)) {\n try {\n const pyproject = readToml(tree, pyprojectPath) as any;\n const wheelPackages: unknown =\n pyproject?.tool?.hatch?.build?.targets?.wheel?.packages;\n // Record the top-level module segment (`pkg/sub` -> `pkg`), which is\n // all `known-first-party` keys off.\n const modules = Array.isArray(wheelPackages)\n ? wheelPackages\n .filter((pkg): pkg is string => typeof pkg === 'string' && !!pkg)\n .map((pkg) => pkg.split('/')[0])\n : [];\n const lineLength: unknown = pyproject?.tool?.ruff?.['line-length'];\n if (modules.length || typeof lineLength === 'number') {\n configs.push({\n root: project.root.split(path.sep).join('/'),\n modules,\n lineLength: typeof lineLength === 'number' ? lineLength : undefined,\n });\n }\n } catch {\n // Skip projects whose pyproject.toml cannot be parsed\n }\n }\n }\n\n return configs;\n}\n\n/**\n * Resolve the ruff config for the project that owns a file (the project with\n * the longest root that is a prefix of the file path). Ruff runs per-project on\n * disk, so a file's settings come from its own project — only its own module is\n * first-party (sibling workspace packages are third-party) and its own\n * line-length applies — and scoping this way keeps in-tree formatting\n * consistent with the on-disk build.\n */\nfunction getOwningProjectRuffConfig(\n filePath: string,\n configs: PythonProjectRuffConfig[],\n): PythonProjectRuffConfig | undefined {\n let owner: PythonProjectRuffConfig | undefined;\n for (const config of configs) {\n if (\n (filePath === config.root || filePath.startsWith(`${config.root}/`)) &&\n (!owner || config.root.length > owner.root.length)\n ) {\n owner = config;\n }\n }\n return owner;\n}\n\n/**\n * Run ruff check --fix and ruff format on Python file content via stdin.\n * Applies all configured lint fixes (including import sorting) and formatting.\n *\n * When no ruff config exists on disk (`hasConfig` false) ruff falls back to its\n * defaults, which omit isort — but generated projects enable rule `I` and their\n * build fails on unsorted imports (I001). In that case we add `--extend-select\n * I` so import sorting matches what the build enforces. When a config does\n * exist we defer to it entirely, honouring the user's rule selection.\n *\n * `projectConfig` carries the owning project's ruff settings, which ruff cannot\n * detect from the filesystem during generation because the project lives only\n * in the tree. We pass them via `--config` so in-tree formatting matches the\n * on-disk build: `known-first-party` (the project's own modules) keeps its\n * imports in their own group, and `line-length` keeps wrapping consistent (the\n * generated config raises it above ruff's default of 88). These are additive to\n * any on-disk config, so they are safe to pass regardless of `hasConfig`.\n */\nfunction ruffFixAndFormat(\n content: string,\n filePath: string,\n hasConfig: boolean,\n projectConfig?: PythonProjectRuffConfig,\n): string {\n const ruff = getRuffCommand();\n if (!ruff) return content;\n\n const extendSelect = hasConfig ? '' : ' --extend-select I';\n const configArgs: string[] = [];\n if (projectConfig?.modules.length) {\n configArgs.push(\n `lint.isort.known-first-party = ${JSON.stringify(projectConfig.modules)}`,\n );\n }\n if (typeof projectConfig?.lineLength === 'number') {\n configArgs.push(`line-length = ${projectConfig.lineLength}`);\n }\n const config = configArgs\n .map((arg) => ` --config ${JSON.stringify(arg)}`)\n .join('');\n\n // First apply lint fixes (import sorting, unused imports, etc.)\n try {\n const result = execSync(\n `${ruff} check --fix${extendSelect}${config} --stdin-filename ${filePath} -`,\n { input: content, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] },\n );\n content = result;\n } catch (e: any) {\n // ruff check exits non-zero when it finds unfixable issues,\n // but stdout still contains the fixed content\n if (e.stdout) {\n content = e.stdout;\n }\n }\n\n // Then apply formatting\n try {\n content = execSync(\n `${ruff} format${config} --stdin-filename ${filePath} -`,\n {\n input: content,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n },\n );\n } catch {\n // Fall through with whatever content we have\n }\n\n return content;\n}\n"],"names":["Biome","getProjects","execFileSync","execSync","existsSync","readFileSync","createRequire","path","readToml","require","url","DEFAULT_BIOME_CONFIG","$schema","root","formatter","enabled","indentStyle","indentWidth","lineWidth","javascript","quoteStyle","trailingCommas","css","linter","rules","recommended","correctness","noUndeclaredDependencies","assist","actions","source","organizeImports","files","includes","BIOME_FORMATTABLE_EXTENSIONS","Set","formatFilesInSubtree","tree","dir","changedFiles","listChanges","filter","file","type","startsWith","pyFiles","endsWith","otherFiles","has","extname","pythonProjectConfigs","length","getPythonProjectRuffConfigs","content","ruffFixAndFormat","toString","hasRuffConfigOnDisk","getOwningProjectRuffConfig","write","join","formatWithBiomeCli","formatWithBiomeApi","biome","getBiomeCommand","command","args","input","encoding","cwd","stdio","projectKey","openProject","treeConfig","read","applyConfiguration","JSON","parse","formatContent","filePath","_biomeCommands","Map","get","undefined","pkgJsonPath","resolve","paths","dirname","pkgJson","binRelative","bin","binPath","process","execPath","set","_ruffCommand","getRuffCommand","cmd","pyproject","parent","configs","project","values","pyprojectPath","exists","wheelPackages","tool","hatch","build","targets","wheel","packages","modules","Array","isArray","pkg","map","split","lineLength","ruff","push","sep","owner","config","hasConfig","projectConfig","extendSelect","configArgs","stringify","arg","result","e","stdout"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../packages/nx-plugin/src/utils/format.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { Biome } from '@biomejs/js-api/nodejs';\nimport { getProjects, type Tree } from '@nx/devkit';\nimport { execFileSync, execSync } from 'child_process';\nimport { existsSync, readFileSync } from 'fs';\nimport { createRequire } from 'module';\nimport path from 'path';\nimport { readToml } from './toml';\n\nconst require = createRequire(import.meta.url);\n\nexport const DEFAULT_BIOME_CONFIG = {\n $schema: 'https://biomejs.dev/schemas/2.4.16/schema.json',\n root: true,\n formatter: {\n enabled: true,\n indentStyle: 'space',\n indentWidth: 2,\n lineWidth: 80,\n },\n javascript: {\n formatter: {\n quoteStyle: 'single',\n trailingCommas: 'all',\n },\n },\n css: {\n formatter: {\n quoteStyle: 'single',\n },\n linter: {\n enabled: false,\n },\n },\n linter: {\n enabled: true,\n rules: {\n recommended: false,\n correctness: {\n noUndeclaredDependencies: 'warn',\n },\n },\n },\n assist: {\n actions: {\n source: {\n organizeImports: 'on',\n },\n },\n },\n files: {\n includes: [\n '**',\n '!**/dist',\n '!**/out-tsc',\n '!**/node_modules',\n '!**/.nx',\n '!**/.venv',\n '!**/*.css',\n '!**/*.gen.*',\n '!**/generated/**',\n '!**/tsconfig*.json',\n ],\n },\n};\n\nconst BIOME_FORMATTABLE_EXTENSIONS = new Set([\n '.ts',\n '.tsx',\n '.js',\n '.jsx',\n '.mjs',\n '.cjs',\n '.mts',\n '.cts',\n '.json',\n '.jsonc',\n '.css',\n]);\n\n/** Matches `tsconfig.json` and variants like `tsconfig.lib.json`. */\nconst isTsConfig = (filePath: string): boolean =>\n /(^|\\/)tsconfig[^/]*\\.json$/.test(filePath);\n\n/**\n * Format files in the given directory within the tree.\n * Handles both TypeScript/JavaScript/JSON (via biome) and Python (via ruff) files.\n * See https://github.com/nrwl/nx/blob/4cd640a9187954505d12de5b6d76a90d8ce4c2eb/packages/devkit/src/generators/format-files.ts#L11\n */\nexport async function formatFilesInSubtree(\n tree: Tree,\n dir?: string,\n): Promise<void> {\n const changedFiles = tree\n .listChanges()\n .filter((file) => file.type !== 'DELETE')\n .filter((file) => (dir ? file.path.startsWith(dir) : true));\n\n const pyFiles = changedFiles.filter((file) => file.path.endsWith('.py'));\n const otherFiles = changedFiles.filter(\n (file) =>\n BIOME_FORMATTABLE_EXTENSIONS.has(path.extname(file.path)) &&\n // tsconfigs are not biome-managed: they're excluded from the vended\n // format target (Nx's typescript-sync rewrites them without formatting),\n // so formatting them at generation would only diverge from the form\n // written on later runs. Leave them as updateJson/writeJson emit them so\n // repeated generation stays idempotent.\n !isTsConfig(file.path),\n );\n\n // Resolve each project's ruff settings (module names, line-length) so files\n // are formatted to match the on-disk build (see getPythonProjectRuffConfigs).\n const pythonProjectConfigs = pyFiles.length\n ? getPythonProjectRuffConfigs(tree)\n : [];\n\n // Format Python files with ruff (lint fixes + formatting)\n for (const file of pyFiles) {\n try {\n const content = ruffFixAndFormat(\n file.content.toString('utf-8'),\n file.path,\n hasRuffConfigOnDisk(tree, file.path),\n getOwningProjectRuffConfig(file.path, pythonProjectConfigs),\n );\n tree.write(file.path, content);\n } catch {\n // Silently skip ruff formatting failures\n }\n }\n\n if (otherFiles.length === 0) return;\n\n // Use the workspace's own Biome CLI (its version and config) when biome.json\n // exists on disk; otherwise format via the bundled library API with the\n // in-memory tree config. The CLI path does not see in-tree config changes.\n if (existsSync(path.join(tree.root, 'biome.json'))) {\n formatWithBiomeCli(tree, otherFiles);\n } else {\n formatWithBiomeApi(tree, otherFiles);\n }\n}\n\n/**\n * Format files via the workspace's Biome CLI, run from the workspace root so it\n * discovers the on-disk biome.json.\n */\nfunction formatWithBiomeCli(\n tree: Tree,\n files: { path: string; content: Buffer | null }[],\n): void {\n const biome = getBiomeCommand(tree.root);\n if (!biome) {\n // Fall back to the library API if the CLI cannot be resolved\n formatWithBiomeApi(tree, files);\n return;\n }\n\n for (const file of files) {\n try {\n const content = execFileSync(\n biome.command,\n [...biome.args, 'format', `--stdin-file-path=${file.path}`],\n {\n input: file.content?.toString('utf-8') ?? '',\n encoding: 'utf-8',\n cwd: tree.root,\n stdio: ['pipe', 'pipe', 'pipe'],\n },\n );\n tree.write(file.path, content);\n } catch {\n // Leave individual files that fail to format untouched\n }\n }\n}\n\n/**\n * Format files via the bundled Biome library API, applying the in-memory tree\n * config.\n */\nfunction formatWithBiomeApi(\n tree: Tree,\n files: { path: string; content: Buffer | null }[],\n): void {\n try {\n const biome = new Biome();\n const { projectKey } = biome.openProject();\n // Apply the workspace biome.json if it exists in the tree, otherwise the defaults.\n const treeConfig = tree.read('biome.json', 'utf-8');\n biome.applyConfiguration(\n projectKey,\n treeConfig ? JSON.parse(treeConfig) : DEFAULT_BIOME_CONFIG,\n );\n\n for (const file of files) {\n try {\n const { content } = biome.formatContent(\n projectKey,\n file.content?.toString('utf-8') ?? '',\n { filePath: file.path },\n );\n tree.write(file.path, content);\n } catch {\n // Leave individual files that fail to format untouched\n }\n }\n } catch {\n // Silently skip formatting failures\n }\n}\n\ninterface BiomeCommand {\n command: string;\n args: string[];\n}\n\n/**\n * Resolve the `@biomejs/biome` CLI from the user's workspace, falling back to a\n * `biome` binary on the PATH.\n */\nconst _biomeCommands = new Map<string, BiomeCommand | null>();\nfunction getBiomeCommand(root: string): BiomeCommand | undefined {\n if (_biomeCommands.has(root)) {\n return _biomeCommands.get(root) ?? undefined;\n }\n\n // Run via node for cross-platform execution of the bin shim.\n try {\n const pkgJsonPath = require.resolve('@biomejs/biome/package.json', {\n paths: [root, import.meta.dirname],\n });\n const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));\n const binRelative =\n typeof pkgJson.bin === 'string' ? pkgJson.bin : pkgJson.bin?.biome;\n if (binRelative) {\n const binPath = path.join(path.dirname(pkgJsonPath), binRelative);\n const command = { command: process.execPath, args: [binPath] };\n _biomeCommands.set(root, command);\n return command;\n }\n } catch {\n // Fall back to a biome binary on the PATH\n }\n\n try {\n execSync('biome --version', {\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n const command = { command: 'biome', args: [] };\n _biomeCommands.set(root, command);\n return command;\n } catch {\n _biomeCommands.set(root, null);\n return undefined;\n }\n}\n\n/**\n * Find the ruff command. Tries 'uv run ruff', then 'uvx ruff'.\n * Matches how @nxlv/python runs ruff via the UV provider.\n */\nlet _ruffCommand: string | undefined;\nfunction getRuffCommand(): string | undefined {\n if (_ruffCommand !== undefined) {\n return _ruffCommand || undefined;\n }\n for (const cmd of ['uv run ruff', 'uvx ruff']) {\n try {\n execSync(`${cmd} --version`, {\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n _ruffCommand = cmd;\n return cmd;\n } catch {\n // Try next command\n }\n }\n _ruffCommand = '';\n return undefined;\n}\n\n/**\n * Whether ruff would discover a config on disk for a file, by walking from its\n * directory up to the workspace root looking for `.ruff.toml`, `ruff.toml`, or a\n * `pyproject.toml` with a `[tool.ruff]` section — the same files ruff itself\n * resolves. The walk stops at `tree.root` so a stray config in a parent of the\n * workspace (or the home directory) is never treated as the project's. Used to\n * decide whether to nudge ruff towards import sorting (see\n * {@link ruffFixAndFormat}).\n */\nfunction hasRuffConfigOnDisk(tree: Tree, filePath: string): boolean {\n const root = path.resolve(tree.root);\n let dir = path.resolve(root, path.dirname(filePath));\n while (true) {\n if (\n existsSync(path.join(dir, '.ruff.toml')) ||\n existsSync(path.join(dir, 'ruff.toml'))\n ) {\n return true;\n }\n const pyproject = path.join(dir, 'pyproject.toml');\n if (\n existsSync(pyproject) &&\n readFileSync(pyproject, 'utf-8').includes('[tool.ruff')\n ) {\n return true;\n }\n const parent = path.dirname(dir);\n // Stop once the workspace root has been checked (or we hit the FS root).\n if (dir === root || parent === dir) {\n return false;\n }\n dir = parent;\n }\n}\n\ninterface PythonProjectRuffConfig {\n /** Project root, normalised to use forward slashes. */\n readonly root: string;\n /** Top-level importable module names declared by the project. */\n readonly modules: string[];\n /** The project's `[tool.ruff].line-length`, if set. */\n readonly lineLength?: number;\n}\n\n/**\n * Map each Nx project with a `pyproject.toml` to the ruff settings the on-disk\n * build enforces for it: its top-level module names (from\n * `[tool.hatch.build.targets.wheel].packages`) and its `[tool.ruff].line-length`.\n */\nfunction getPythonProjectRuffConfigs(tree: Tree): PythonProjectRuffConfig[] {\n const configs: PythonProjectRuffConfig[] = [];\n\n for (const project of getProjects(tree).values()) {\n const pyprojectPath = path.join(project.root, 'pyproject.toml');\n if (tree.exists(pyprojectPath)) {\n try {\n const pyproject = readToml(tree, pyprojectPath) as any;\n const wheelPackages: unknown =\n pyproject?.tool?.hatch?.build?.targets?.wheel?.packages;\n // Record the top-level module segment (`pkg/sub` -> `pkg`), which is\n // all `known-first-party` keys off.\n const modules = Array.isArray(wheelPackages)\n ? wheelPackages\n .filter((pkg): pkg is string => typeof pkg === 'string' && !!pkg)\n .map((pkg) => pkg.split('/')[0])\n : [];\n const lineLength: unknown = pyproject?.tool?.ruff?.['line-length'];\n if (modules.length || typeof lineLength === 'number') {\n configs.push({\n root: project.root.split(path.sep).join('/'),\n modules,\n lineLength: typeof lineLength === 'number' ? lineLength : undefined,\n });\n }\n } catch {\n // Skip projects whose pyproject.toml cannot be parsed\n }\n }\n }\n\n return configs;\n}\n\n/**\n * Resolve the ruff config for the project that owns a file (the project with\n * the longest root that is a prefix of the file path). Ruff runs per-project on\n * disk, so a file's settings come from its own project — only its own module is\n * first-party (sibling workspace packages are third-party) and its own\n * line-length applies — and scoping this way keeps in-tree formatting\n * consistent with the on-disk build.\n */\nfunction getOwningProjectRuffConfig(\n filePath: string,\n configs: PythonProjectRuffConfig[],\n): PythonProjectRuffConfig | undefined {\n let owner: PythonProjectRuffConfig | undefined;\n for (const config of configs) {\n if (\n (filePath === config.root || filePath.startsWith(`${config.root}/`)) &&\n (!owner || config.root.length > owner.root.length)\n ) {\n owner = config;\n }\n }\n return owner;\n}\n\n/**\n * Run ruff check --fix and ruff format on Python file content via stdin.\n * Applies all configured lint fixes (including import sorting) and formatting.\n *\n * When no ruff config exists on disk (`hasConfig` false) ruff falls back to its\n * defaults, which omit isort — but generated projects enable rule `I` and their\n * build fails on unsorted imports (I001). In that case we add `--extend-select\n * I` so import sorting matches what the build enforces. When a config does\n * exist we defer to it entirely, honouring the user's rule selection.\n *\n * `projectConfig` carries the owning project's ruff settings, which ruff cannot\n * detect from the filesystem during generation because the project lives only\n * in the tree. We pass them via `--config` so in-tree formatting matches the\n * on-disk build: `known-first-party` (the project's own modules) keeps its\n * imports in their own group, and `line-length` keeps wrapping consistent (the\n * generated config raises it above ruff's default of 88). These are additive to\n * any on-disk config, so they are safe to pass regardless of `hasConfig`.\n */\nfunction ruffFixAndFormat(\n content: string,\n filePath: string,\n hasConfig: boolean,\n projectConfig?: PythonProjectRuffConfig,\n): string {\n const ruff = getRuffCommand();\n if (!ruff) return content;\n\n const extendSelect = hasConfig ? '' : ' --extend-select I';\n const configArgs: string[] = [];\n if (projectConfig?.modules.length) {\n configArgs.push(\n `lint.isort.known-first-party = ${JSON.stringify(projectConfig.modules)}`,\n );\n }\n if (typeof projectConfig?.lineLength === 'number') {\n configArgs.push(`line-length = ${projectConfig.lineLength}`);\n }\n const config = configArgs\n .map((arg) => ` --config ${JSON.stringify(arg)}`)\n .join('');\n\n // First apply lint fixes (import sorting, unused imports, etc.)\n try {\n const result = execSync(\n `${ruff} check --fix${extendSelect}${config} --stdin-filename ${filePath} -`,\n { input: content, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] },\n );\n content = result;\n } catch (e: any) {\n // ruff check exits non-zero when it finds unfixable issues,\n // but stdout still contains the fixed content\n if (e.stdout) {\n content = e.stdout;\n }\n }\n\n // Then apply formatting\n try {\n content = execSync(\n `${ruff} format${config} --stdin-filename ${filePath} -`,\n {\n input: content,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n },\n );\n } catch {\n // Fall through with whatever content we have\n }\n\n return content;\n}\n"],"names":["Biome","getProjects","execFileSync","execSync","existsSync","readFileSync","createRequire","path","readToml","require","url","DEFAULT_BIOME_CONFIG","$schema","root","formatter","enabled","indentStyle","indentWidth","lineWidth","javascript","quoteStyle","trailingCommas","css","linter","rules","recommended","correctness","noUndeclaredDependencies","assist","actions","source","organizeImports","files","includes","BIOME_FORMATTABLE_EXTENSIONS","Set","isTsConfig","filePath","test","formatFilesInSubtree","tree","dir","changedFiles","listChanges","filter","file","type","startsWith","pyFiles","endsWith","otherFiles","has","extname","pythonProjectConfigs","length","getPythonProjectRuffConfigs","content","ruffFixAndFormat","toString","hasRuffConfigOnDisk","getOwningProjectRuffConfig","write","join","formatWithBiomeCli","formatWithBiomeApi","biome","getBiomeCommand","command","args","input","encoding","cwd","stdio","projectKey","openProject","treeConfig","read","applyConfiguration","JSON","parse","formatContent","_biomeCommands","Map","get","undefined","pkgJsonPath","resolve","paths","dirname","pkgJson","binRelative","bin","binPath","process","execPath","set","_ruffCommand","getRuffCommand","cmd","pyproject","parent","configs","project","values","pyprojectPath","exists","wheelPackages","tool","hatch","build","targets","wheel","packages","modules","Array","isArray","pkg","map","split","lineLength","ruff","push","sep","owner","config","hasConfig","projectConfig","extendSelect","configArgs","stringify","arg","result","e","stdout"],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Vitest globalSetup that warms uv's tool cache once before any worker spawns.
3
+ *
4
+ * The formatter runs `uvx ruff` per Python file. On a cold cache the first uv
5
+ * invocation takes an exclusive write lock on uv's global cache while it
6
+ * installs ruff; with many parallel workers each racing that lock, generation
7
+ * stalls. Installing ruff once up front means every worker hits a warm cache,
8
+ * where uv only takes shared locks and calls run concurrently without
9
+ * contention. Both command forms the formatter may use are warmed; if uv is
10
+ * unavailable the formatter skips ruff anyway, so failures here are ignored.
11
+ */
12
+ export default function setup(): void;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */ import { execFileSync } from "child_process";
5
+ /**
6
+ * Vitest globalSetup that warms uv's tool cache once before any worker spawns.
7
+ *
8
+ * The formatter runs `uvx ruff` per Python file. On a cold cache the first uv
9
+ * invocation takes an exclusive write lock on uv's global cache while it
10
+ * installs ruff; with many parallel workers each racing that lock, generation
11
+ * stalls. Installing ruff once up front means every worker hits a warm cache,
12
+ * where uv only takes shared locks and calls run concurrently without
13
+ * contention. Both command forms the formatter may use are warmed; if uv is
14
+ * unavailable the formatter skips ruff anyway, so failures here are ignored.
15
+ */ export default function setup() {
16
+ for (const [command, ...args] of [
17
+ [
18
+ 'uv',
19
+ 'run',
20
+ 'ruff',
21
+ '--version'
22
+ ],
23
+ [
24
+ 'uvx',
25
+ 'ruff',
26
+ '--version'
27
+ ]
28
+ ]){
29
+ try {
30
+ execFileSync(command, args, {
31
+ stdio: 'ignore'
32
+ });
33
+ } catch {
34
+ // Ignore — the formatter falls back or skips ruff when it is unavailable
35
+ }
36
+ }
37
+ }
38
+
39
+ //# sourceMappingURL=warm-ruff-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../packages/nx-plugin/src/utils/warm-ruff-cache.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport { execFileSync } from 'child_process';\n\n/**\n * Vitest globalSetup that warms uv's tool cache once before any worker spawns.\n *\n * The formatter runs `uvx ruff` per Python file. On a cold cache the first uv\n * invocation takes an exclusive write lock on uv's global cache while it\n * installs ruff; with many parallel workers each racing that lock, generation\n * stalls. Installing ruff once up front means every worker hits a warm cache,\n * where uv only takes shared locks and calls run concurrently without\n * contention. Both command forms the formatter may use are warmed; if uv is\n * unavailable the formatter skips ruff anyway, so failures here are ignored.\n */\nexport default function setup() {\n for (const [command, ...args] of [\n ['uv', 'run', 'ruff', '--version'],\n ['uvx', 'ruff', '--version'],\n ]) {\n try {\n execFileSync(command, args, { stdio: 'ignore' });\n } catch {\n // Ignore — the formatter falls back or skips ruff when it is unavailable\n }\n }\n}\n"],"names":["execFileSync","setup","command","args","stdio"],"mappings":"AAAA;;;CAGC,GACD,SAASA,YAAY,QAAQ,gBAAgB;AAE7C;;;;;;;;;;CAUC,GACD,eAAe,SAASC;IACtB,KAAK,MAAM,CAACC,SAAS,GAAGC,KAAK,IAAI;QAC/B;YAAC;YAAM;YAAO;YAAQ;SAAY;QAClC;YAAC;YAAO;YAAQ;SAAY;KAC7B,CAAE;QACD,IAAI;YACFH,aAAaE,SAASC,MAAM;gBAAEC,OAAO;YAAS;QAChD,EAAE,OAAM;QACN,yEAAyE;QAC3E;IACF;AACF"}