Skip to content

Commit

Permalink
Fix discovery of Go direct dependencies (#6)
Browse files Browse the repository at this point in the history
* Fix discovery of direct dependencies

- Our current approach relied on a main.go file residing at the root of the repository which may not always be the case.
- Additionally calling go list {{.Deps}} module would not always give indented results.

* Bump version
  • Loading branch information
wescarr committed Sep 6, 2022
1 parent b383172 commit 4e72843
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 44 deletions.
70 changes: 52 additions & 18 deletions dist/index.js
Expand Up @@ -61,7 +61,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.getDependencies = exports.listDeps = void 0;
exports.getDependencies = exports.getModuleGraph = exports.listDirectDeps = void 0;
const core = __importStar(__nccwpck_require__(2186));
const path_1 = __importDefault(__nccwpck_require__(1017));
const constants_1 = __nccwpck_require__(9677);
Expand All @@ -79,30 +79,64 @@ const parseDependency = (line) => {
return;
}
};
const listDeps = (dir, module = '') => {
let output = (0, child_process_1.execSync)(`go list -f {{.Deps}} ${module}`, {
const parseModuleUrl = (m) => {
const [url, version = ''] = m.split('@');
const [domain, owner, repo] = url.split('/');
return { module: [domain, owner, repo].join('/'), version };
};
const listDirectDeps = (dir) => {
let output = (0, child_process_1.execSync)(`go list -f '{{if not .Indirect}}{{.}}{{end}}' -m all`, { cwd: resolveDir(dir) }).toString();
return output
.split('\n')
.map((d) => {
const [module, version = ''] = d.split(' ');
return { module, version };
})
.filter((entry) => filterDependency(entry.module));
};
exports.listDirectDeps = listDirectDeps;
const getModuleGraph = (dir) => {
const output = (0, child_process_1.execSync)(`go mod graph`, {
cwd: resolveDir(dir),
}).toString();
// trim `[]` at start and end of string
output = output.slice(1, -1);
return output.split(/\s+/).filter(filterDependency);
const graph = {};
output.split('\n').forEach((line) => {
if (!line) {
return;
}
const [parent, child] = line.split(' ');
const mod = parseModuleUrl(parent);
const childMod = parseModuleUrl(child);
const key = `${mod.module}@${mod.version}`;
graph[key] = graph[key] || [];
if (childMod.module !== key) {
graph[key].push(childMod);
}
});
Object.entries(graph).forEach(([key, deps]) => {
graph[key] = (0, lodash_1.uniqBy)(deps, 'module');
});
return graph;
};
exports.listDeps = listDeps;
exports.getModuleGraph = getModuleGraph;
const getDependencies = (dir = '') => {
const direct = (0, exports.listDeps)(dir);
let dependencies = direct.map((d) => ({
source: parseDependency(d),
dependencies: (0, exports.listDeps)(dir, d).map((d) => ({ source: parseDependency(d) })),
}));
// Merge direct dependencies with the same source
// and remove self dependencies
const groups = (0, lodash_1.groupBy)(dependencies, 'source');
return Object.entries(groups).map(([source, group]) => {
const graph = (0, exports.getModuleGraph)(dir);
const direct = (0, exports.listDirectDeps)(dir);
let dependencies = direct
.filter((d) => filterDependency(d.module))
.map((d) => {
const url = parseModuleUrl(d.module).module;
const deps = graph[`${url}@${d.version}`] || [];
return {
source,
dependencies: (0, lodash_1.uniqBy)(group.flatMap((g) => g.dependencies), 'source').filter((d) => d.source !== source),
source: parseDependency(d.module),
dependencies: deps
.filter((d) => filterDependency(d.module))
.map((d) => ({
source: parseDependency(d.module),
})),
};
});
return dependencies;
};
exports.getDependencies = getDependencies;

Expand Down
2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "stackaid-json-generator",
"version": "1.3.0",
"version": "1.4.0",
"private": false,
"description": "A GitHub action to generate a stackaid.json file based on your repository's dependency graph",
"main": "lib/src/main.js",
Expand Down
92 changes: 68 additions & 24 deletions src/go.ts
Expand Up @@ -2,7 +2,7 @@ import * as core from '@actions/core'
import path from 'path'
import { GITHUB_DOMAIN } from './constants'
import { execSync } from 'child_process'
import { groupBy, uniqBy } from 'lodash'
import { uniqBy } from 'lodash'

const sourceDir = core.getInput('src_dir') || process.cwd()

Expand All @@ -20,33 +20,77 @@ const parseDependency = (line: string) => {
}
}

export const listDeps = (dir: string, module: string = '') => {
let output = execSync(`go list -f {{.Deps}} ${module}`, {
const parseModuleUrl = (m: string) => {
const [url, version = ''] = m.split('@')
const [domain, owner, repo] = url.split('/')

return { module: [domain, owner, repo].join('/'), version }
}

export const listDirectDeps = (dir: string) => {
let output = execSync(
`go list -f '{{if not .Indirect}}{{.}}{{end}}' -m all`,
{ cwd: resolveDir(dir) }
).toString()

return output
.split('\n')
.map((d) => {
const [module, version = ''] = d.split(' ')
return { module, version }
})
.filter((entry) => filterDependency(entry.module))
}

export const getModuleGraph = (dir: string) => {
const output = execSync(`go mod graph`, {
cwd: resolveDir(dir),
}).toString()
// trim `[]` at start and end of string
output = output.slice(1, -1)

return output.split(/\s+/).filter(filterDependency)
const graph: Record<string, { module: string; version: string }[]> = {}

output.split('\n').forEach((line) => {
if (!line) {
return
}

const [parent, child] = line.split(' ')
const mod = parseModuleUrl(parent)
const childMod = parseModuleUrl(child)

const key = `${mod.module}@${mod.version}`
graph[key] = graph[key] || []

if (childMod.module !== key) {
graph[key].push(childMod)
}
})

Object.entries(graph).forEach(([key, deps]) => {
graph[key] = uniqBy(deps, 'module')
})

return graph
}

export const getDependencies = (dir: string = '') => {
const direct = listDeps(dir)
let dependencies = direct.map((d) => ({
source: parseDependency(d),
dependencies: listDeps(dir, d).map((d) => ({ source: parseDependency(d) })),
}))

// Merge direct dependencies with the same source
// and remove self dependencies
const groups = groupBy(dependencies, 'source')
return Object.entries(groups).map(([source, group]) => {
return {
source,
dependencies: uniqBy(
group.flatMap((g) => g.dependencies),
'source'
).filter((d) => d.source !== source),
}
}) as StackAidDependency[]
const graph = getModuleGraph(dir)
const direct = listDirectDeps(dir)

let dependencies = direct
.filter((d) => filterDependency(d.module))
.map((d) => {
const url = parseModuleUrl(d.module).module
const deps = graph[`${url}@${d.version}`] || []
return {
source: parseDependency(d.module) as string,
dependencies: deps
.filter((d) => filterDependency(d.module))
.map((d) => ({
source: parseDependency(d.module) as string,
})),
}
})

return dependencies as StackAidDependency[]
}

0 comments on commit 4e72843

Please sign in to comment.