diff --git a/packages/terraform/package.json b/packages/terraform/package.json index b1ae027c..3b2e796f 100644 --- a/packages/terraform/package.json +++ b/packages/terraform/package.json @@ -16,5 +16,8 @@ "license": "MIT", "main": "src/index.js", "builders": "./executors.json", - "generators": "./generators.json" + "generators": "./generators.json", + "dependencies": { + "@evops/hcl-terraform-parser": "^1.0.0" + } } diff --git a/packages/terraform/src/graph.ts b/packages/terraform/src/graph.ts new file mode 100644 index 00000000..77a43120 --- /dev/null +++ b/packages/terraform/src/graph.ts @@ -0,0 +1,66 @@ +import * as hclParser from '@evops/hcl-terraform-parser' +import { + CreateDependencies, + RawProjectGraphDependency, + workspaceRoot +} from '@nx/devkit' +import * as fs from 'node:fs/promises' +import * as path from 'node:path' +import { DependencyType } from 'nx/src/config/project-graph' + +const isLocalPath = (path: string) => { + return path.startsWith('./') || path.startsWith('../') +} + +export const createDependencies: CreateDependencies = async (_, ctx) => { + const results: RawProjectGraphDependency[] = [] + + const projectRootsToProject: [projectRoot: string, name: string][] = + Object.entries(ctx.projects).map(([name, project]) => [project.root, name]) + + for (const project of Object.keys(ctx.projects)) { + // find tf files to process in the project + const tfFilesToProcess = + ctx.filesToProcess.projectFileMap[project]?.filter((file) => + file.file.endsWith('.tf') + ) ?? [] + + for (const file of tfFilesToProcess) { + const data = await fs.readFile(file.file) + const hclFile = hclParser.parse(data) + const moduleCalls = hclFile['module_calls'] + + for (const moduleCall of Object.values(moduleCalls)) { + const depSourcePathRel = moduleCall.source + + if (!isLocalPath(depSourcePathRel)) { + continue + } + + const depSourceAbs = path.resolve(file.file, depSourcePathRel) + + const depSourceRelativeToWorkspace = path.relative( + workspaceRoot, + depSourceAbs + ) + + const targetProject = projectRootsToProject.find(([root]) => + depSourceRelativeToWorkspace.startsWith(root) + )?.[1] + + if (!targetProject || targetProject === project) { + continue + } + + results.push({ + type: DependencyType.static, + source: project, + target: targetProject, + sourceFile: file.file + }) + } + } + } + + return results +} diff --git a/packages/terraform/src/hcl-terraform-parser.d.ts b/packages/terraform/src/hcl-terraform-parser.d.ts new file mode 100644 index 00000000..cfe978a4 --- /dev/null +++ b/packages/terraform/src/hcl-terraform-parser.d.ts @@ -0,0 +1,11 @@ +declare module '@evops/hcl-terraform-parser' { + export type HclDef = { + module_calls: { + [key: string]: { + source: string + } + } + } + + export function parse(data: Buffer): HclDef +} diff --git a/packages/terraform/src/index.ts b/packages/terraform/src/index.ts index e69de29b..b8ade72c 100644 --- a/packages/terraform/src/index.ts +++ b/packages/terraform/src/index.ts @@ -0,0 +1 @@ +export * from './graph' diff --git a/yarn.lock b/yarn.lock index e652360e..d858666c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5692,6 +5692,13 @@ __metadata: languageName: node linkType: hard +"@evops/hcl-terraform-parser@npm:^1.0.0": + version: 1.0.0 + resolution: "@evops/hcl-terraform-parser@npm:1.0.0" + checksum: 10/d48b92b594a04a10a513e9e2984ac48437348ff23ff0e898e6fdb612715bb0a20c66d176b57f2b828c7c8da1cd282763aed152bcf17c00f67ae2c061c78212fd + languageName: node + linkType: hard + "@fastify/busboy@npm:^2.0.0": version: 2.1.1 resolution: "@fastify/busboy@npm:2.1.1" @@ -7345,6 +7352,8 @@ __metadata: "@nx-extend/terraform@workspace:packages/terraform": version: 0.0.0-use.local resolution: "@nx-extend/terraform@workspace:packages/terraform" + dependencies: + "@evops/hcl-terraform-parser": "npm:^1.0.0" languageName: unknown linkType: soft