@stackaid/stackaid-json-generator
Advanced tools
| export declare const GITHUB_DOMAIN = "github.com"; | ||
| export declare type Ecosystem = 'go' | 'java' | 'javascript' | 'php' | 'python' | 'ruby' | 'rust'; | ||
| export declare const FileTypes: Record<Ecosystem, string[]>; | ||
| export declare const SUMMARY_FILE_TYPES: string[]; | ||
| export declare const DEPENDENCY_FILE_TYPES: string[]; |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.DEPENDENCY_FILE_TYPES = exports.SUMMARY_FILE_TYPES = exports.FileTypes = exports.GITHUB_DOMAIN = void 0; | ||
| exports.GITHUB_DOMAIN = 'github.com'; | ||
| exports.FileTypes = { | ||
| go: ['go.mod'], | ||
| java: ['pom.xml'], | ||
| javascript: ['package.json'], | ||
| php: ['composer.json'], | ||
| python: ['pipfile', 'pyproject.toml', 'setup.py'], | ||
| ruby: ['gemfile'], | ||
| rust: ['cargo.toml'], | ||
| }; | ||
| exports.SUMMARY_FILE_TYPES = Object.values(exports.FileTypes).flat(); | ||
| exports.DEPENDENCY_FILE_TYPES = [ | ||
| exports.FileTypes.java, | ||
| exports.FileTypes.php, | ||
| exports.FileTypes.python, | ||
| exports.FileTypes.ruby, | ||
| exports.FileTypes.rust, | ||
| ].flat(); |
| import { DependencyConfig, GraphConfig, PackageJson, StackAidDependency } from './types/index.js'; | ||
| export declare const generators: { | ||
| go: ({ owner, repo, filename, sourceDir, }: DependencyConfig) => Promise<StackAidDependency[]>; | ||
| javascript: ({ octokit, owner, repo, filename, }: DependencyConfig) => Promise<PackageJson>; | ||
| graph: ({ octokit, owner, repo, after, }: GraphConfig) => Promise<StackAidDependency[]>; | ||
| }; |
| "use strict"; | ||
| var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| var desc = Object.getOwnPropertyDescriptor(m, k); | ||
| if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
| desc = { enumerable: true, get: function() { return m[k]; } }; | ||
| } | ||
| Object.defineProperty(o, k2, desc); | ||
| }) : (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| o[k2] = m[k]; | ||
| })); | ||
| var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
| Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
| }) : function(o, v) { | ||
| o["default"] = v; | ||
| }); | ||
| var __importStar = (this && this.__importStar) || function (mod) { | ||
| if (mod && mod.__esModule) return mod; | ||
| var result = {}; | ||
| if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
| __setModuleDefault(result, mod); | ||
| return result; | ||
| }; | ||
| var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
| function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
| return new (P || (P = Promise))(function (resolve, reject) { | ||
| function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
| function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
| function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
| step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
| }); | ||
| }; | ||
| var __importDefault = (this && this.__importDefault) || function (mod) { | ||
| return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.generators = void 0; | ||
| const go = __importStar(require("./go.js")); | ||
| const lodash_1 = __importDefault(require("lodash")); | ||
| const path_1 = __importDefault(require("path")); | ||
| const constants_js_1 = require("./constants.js"); | ||
| const queries_js_1 = require("./queries.js"); | ||
| const { uniqBy } = lodash_1.default; | ||
| const getJavaScriptDependencies = ({ octokit, owner, repo, filename, }) => __awaiter(void 0, void 0, void 0, function* () { | ||
| const content = yield (0, queries_js_1.getClient)(octokit).getFileContents(owner, repo, filename); | ||
| const { dependencies, devDependencies } = JSON.parse(content); | ||
| return { filename, dependencies, devDependencies }; | ||
| }); | ||
| const getGoDependencies = ({ owner, repo, filename, sourceDir, }) => __awaiter(void 0, void 0, void 0, function* () { | ||
| const parent = `https://${constants_js_1.GITHUB_DOMAIN}/${owner}/${repo}`; | ||
| const deps = go | ||
| .getDependencies(path_1.default.dirname(filename), sourceDir) | ||
| .filter(({ source }) => source !== parent); | ||
| return deps; | ||
| }); | ||
| const getDependencyGraph = ({ octokit, owner, repo, after, }) => __awaiter(void 0, void 0, void 0, function* () { | ||
| const client = (0, queries_js_1.getClient)(octokit); | ||
| const dependencies = []; | ||
| const results = yield client.getRepositoryDependencies(owner, repo, 1, after); | ||
| const direct = uniqBy(results, (d) => d.repository.url); | ||
| for (const dep of direct) { | ||
| const { url: source, name, owner: { login: owner }, } = dep.repository; | ||
| const summary = yield client.getRepositorySummary(owner, name); | ||
| console.log(`${owner}/${name}: ${summary.map((s) => s.node.filename)}`); | ||
| let indirect = []; | ||
| for (const { after } of summary) { | ||
| const deps = yield client.getRepositoryDependencies(owner, name, 1, after); | ||
| indirect.push(...deps.map((d) => ({ source: d.repository.url }))); | ||
| } | ||
| // Dependencies shouldn't be funding themselves. | ||
| indirect = indirect.filter((d) => d.source !== source); | ||
| dependencies.push({ source, dependencies: uniqBy(indirect, 'source') }); | ||
| } | ||
| return dependencies; | ||
| }); | ||
| exports.generators = { | ||
| go: getGoDependencies, | ||
| javascript: getJavaScriptDependencies, | ||
| graph: getDependencyGraph, | ||
| }; |
| import { StackAidDependency } from './types/index.js'; | ||
| export declare const listDirectDeps: (dir: string, sourceDir: string) => { | ||
| module: string; | ||
| version: string; | ||
| }[]; | ||
| export declare const getModuleGraph: (dir: string, sourceDir: string) => Record<string, { | ||
| module: string; | ||
| version: string; | ||
| }[]>; | ||
| export declare const getDependencies: (dir?: string, sourceDir?: string) => StackAidDependency[]; |
| "use strict"; | ||
| var __importDefault = (this && this.__importDefault) || function (mod) { | ||
| return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.getDependencies = exports.getModuleGraph = exports.listDirectDeps = void 0; | ||
| const lodash_1 = __importDefault(require("lodash")); | ||
| const path_1 = __importDefault(require("path")); | ||
| const constants_js_1 = require("./constants.js"); | ||
| const child_process_1 = require("child_process"); | ||
| const { uniqBy } = lodash_1.default; | ||
| const filterDependency = (line) => line.startsWith(constants_js_1.GITHUB_DOMAIN); | ||
| const parseDependency = (line) => { | ||
| switch (true) { | ||
| case line.startsWith(constants_js_1.GITHUB_DOMAIN): | ||
| const [domain, owner, repo] = line.split('/'); | ||
| return `https://${domain}/${owner}/${repo}`; | ||
| default: | ||
| return; | ||
| } | ||
| }; | ||
| const parseModuleUrl = (m) => { | ||
| const [url, version = ''] = m.split('@'); | ||
| const [domain, owner, repo] = url.split('/'); | ||
| return { module: [domain, owner, repo].join('/'), version }; | ||
| }; | ||
| const listDirectDeps = (dir, sourceDir) => { | ||
| let output = (0, child_process_1.execSync)(`go list -f '{{if not .Indirect}}{{.}}{{end}}' -m all`, { cwd: path_1.default.resolve(sourceDir, 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, sourceDir) => { | ||
| const output = (0, child_process_1.execSync)(`go mod graph`, { | ||
| cwd: path_1.default.resolve(sourceDir, dir), | ||
| }).toString(); | ||
| 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] = uniqBy(deps, 'module'); | ||
| }); | ||
| return graph; | ||
| }; | ||
| exports.getModuleGraph = getModuleGraph; | ||
| const getDependencies = (dir = '', sourceDir = process.cwd()) => { | ||
| const graph = (0, exports.getModuleGraph)(dir, sourceDir); | ||
| const direct = (0, exports.listDirectDeps)(dir, sourceDir); | ||
| 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), | ||
| dependencies: deps | ||
| .filter((d) => filterDependency(d.module)) | ||
| .map((d) => ({ | ||
| source: parseDependency(d.module), | ||
| })), | ||
| }; | ||
| }); | ||
| return dependencies; | ||
| }; | ||
| exports.getDependencies = getDependencies; |
| import { DependencyConfig, PackageJson, StackAidJson } from './types/index.js'; | ||
| import { generators } from './generate.js'; | ||
| export { generators } from './generate.js'; | ||
| export declare const getDependencies: (config: DependencyConfig, generatorTypes?: Partial<typeof generators>) => Promise<{ | ||
| stackAidJson: StackAidJson; | ||
| packageJson: PackageJson[]; | ||
| } | { | ||
| packageJson: PackageJson[]; | ||
| stackAidJson?: undefined; | ||
| }>; |
| "use strict"; | ||
| var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| var desc = Object.getOwnPropertyDescriptor(m, k); | ||
| if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
| desc = { enumerable: true, get: function() { return m[k]; } }; | ||
| } | ||
| Object.defineProperty(o, k2, desc); | ||
| }) : (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| o[k2] = m[k]; | ||
| })); | ||
| var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
| Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
| }) : function(o, v) { | ||
| o["default"] = v; | ||
| }); | ||
| var __importStar = (this && this.__importStar) || function (mod) { | ||
| if (mod && mod.__esModule) return mod; | ||
| var result = {}; | ||
| if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
| __setModuleDefault(result, mod); | ||
| return result; | ||
| }; | ||
| var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
| function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
| return new (P || (P = Promise))(function (resolve, reject) { | ||
| function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
| function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
| function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
| step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
| }); | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.getDependencies = exports.generators = void 0; | ||
| const core = __importStar(require("@actions/core")); | ||
| const constants_js_1 = require("./constants.js"); | ||
| const generate_js_1 = require("./generate.js"); | ||
| const queries_js_1 = require("./queries.js"); | ||
| const utils_js_1 = require("./utils.js"); | ||
| var generate_js_2 = require("./generate.js"); | ||
| Object.defineProperty(exports, "generators", { enumerable: true, get: function () { return generate_js_2.generators; } }); | ||
| const getDependencies = (config, generatorTypes) => __awaiter(void 0, void 0, void 0, function* () { | ||
| const { owner, repo, octokit } = config; | ||
| const packageJson = []; | ||
| const stackAidJson = { version: 1, dependencies: [] }; | ||
| const client = (0, queries_js_1.getClient)(octokit); | ||
| const summary = yield client.getRepositorySummary(owner, repo, '**/'); | ||
| const generate = Object.assign(Object.assign({}, generate_js_1.generators), generatorTypes); | ||
| for (const { after, node: { filename }, } of summary) { | ||
| switch (true) { | ||
| case (0, utils_js_1.isFileType)(filename, constants_js_1.FileTypes.go): { | ||
| core.info(`Found ${filename}, getting Go dependencies`); | ||
| const deps = yield generate.go(Object.assign(Object.assign({}, config), { filename })); | ||
| stackAidJson.dependencies.push(...deps); | ||
| break; | ||
| } | ||
| case (0, utils_js_1.isFileType)(filename, constants_js_1.FileTypes.javascript): { | ||
| core.info(`Found ${filename}, copying dependencies`); | ||
| const deps = yield generate.javascript(Object.assign(Object.assign({}, config), { filename })); | ||
| packageJson.push(deps); | ||
| break; | ||
| } | ||
| default: | ||
| const deps = yield generate.graph(Object.assign(Object.assign({}, config), { after })); | ||
| stackAidJson.dependencies.push(...deps); | ||
| break; | ||
| } | ||
| } | ||
| return stackAidJson.dependencies.length | ||
| ? { stackAidJson, packageJson } | ||
| : { packageJson }; | ||
| }); | ||
| exports.getDependencies = getDependencies; |
| import { CreateCommitOnBranchInput } from './types/graphql.js'; | ||
| import { Octokit } from 'octokit'; | ||
| export declare const summaryFragment: import("graphql").DocumentNode; | ||
| export declare const repositoryFragment: import("graphql").DocumentNode; | ||
| export declare const getClient: (octokit: Octokit) => { | ||
| graphql(query: string, variables?: Record<string, any>): Promise<any>; | ||
| getFileContents(owner: string, repo: string, path: string): Promise<string | null>; | ||
| getRepositorySummaryPage(owner: string, repo: string, cursor?: string): Promise<{ | ||
| cursor: string; | ||
| node: { | ||
| id: string; | ||
| filename: string; | ||
| }; | ||
| }[]>; | ||
| getRepositorySummary(owner: string, repo: string, glob?: string): Promise<{ | ||
| after: string | undefined; | ||
| cursor: string; | ||
| node: { | ||
| id: string; | ||
| filename: string; | ||
| }; | ||
| }[]>; | ||
| getRepositoryDependencies(owner: string, repo: string, first?: number, after?: string): Promise<{ | ||
| packageManager: string; | ||
| requirements: string; | ||
| packageName: string; | ||
| hasDependencies: boolean; | ||
| repository: { | ||
| name: string; | ||
| url: any; | ||
| owner: { | ||
| login: string; | ||
| } | { | ||
| login: string; | ||
| }; | ||
| }; | ||
| }[]>; | ||
| getHeadOid(owner: string, repo: string): Promise<{ | ||
| name: string; | ||
| oid: any; | ||
| }>; | ||
| createCommit(owner: string, repo: string, input: Partial<CreateCommitOnBranchInput>): Promise<{ | ||
| commit: { | ||
| url: any; | ||
| }; | ||
| }>; | ||
| }; |
| "use strict"; | ||
| var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
| function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
| return new (P || (P = Promise))(function (resolve, reject) { | ||
| function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
| function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
| function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
| step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
| }); | ||
| }; | ||
| var __importDefault = (this && this.__importDefault) || function (mod) { | ||
| return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.getClient = exports.repositoryFragment = exports.summaryFragment = void 0; | ||
| const lodash_1 = __importDefault(require("lodash")); | ||
| const constants_js_1 = require("./constants.js"); | ||
| const graphql_tag_1 = require("graphql-tag"); | ||
| const utils_js_1 = require("./utils.js"); | ||
| const graphql_1 = require("graphql"); | ||
| const { uniqBy } = lodash_1.default; | ||
| exports.summaryFragment = (0, graphql_tag_1.gql)(` | ||
| fragment summaryFragment on DependencyGraphManifestConnection { | ||
| edges { | ||
| cursor | ||
| node { | ||
| id | ||
| filename | ||
| } | ||
| } | ||
| } | ||
| `); | ||
| exports.repositoryFragment = (0, graphql_tag_1.gql)(` | ||
| fragment repositoryFragment on DependencyGraphManifestConnection { | ||
| nodes { | ||
| filename | ||
| dependencies { | ||
| nodes { | ||
| repository { | ||
| name | ||
| owner { | ||
| login | ||
| } | ||
| url | ||
| } | ||
| packageManager | ||
| requirements | ||
| packageName | ||
| hasDependencies | ||
| } | ||
| } | ||
| } | ||
| } | ||
| `); | ||
| const getClient = (octokit) => { | ||
| return { | ||
| graphql(query, variables) { | ||
| return __awaiter(this, void 0, void 0, function* () { | ||
| const result = yield octokit.graphql(Object.assign(Object.assign({}, variables), { query, headers: { | ||
| // Required for dependency graph queries, see: | ||
| // https://docs.github.com/en/graphql/overview/schema-previews#access-to-a-repositories-dependency-graph-preview | ||
| Accept: 'application/vnd.github.hawkgirl-preview+json', | ||
| }, request: { timeout: 60 * 1000 } })); | ||
| return result; | ||
| }); | ||
| }, | ||
| getFileContents(owner, repo, path) { | ||
| return __awaiter(this, void 0, void 0, function* () { | ||
| const res = yield octokit.rest.repos.getContent({ | ||
| owner, | ||
| repo, | ||
| path, | ||
| }); | ||
| if ((res === null || res === void 0 ? void 0 : res.status) !== 200) { | ||
| return null; | ||
| } | ||
| const encodedContent = res.data.content; | ||
| return Buffer.from(encodedContent, 'base64').toString(); | ||
| }); | ||
| }, | ||
| getRepositorySummaryPage(owner, repo, cursor = '') { | ||
| return __awaiter(this, void 0, void 0, function* () { | ||
| const result = (yield this.graphql(` | ||
| query getRepositorySummary( | ||
| $owner: String! | ||
| $repo: String! | ||
| $cursor: String | ||
| ) { | ||
| repository(owner: $owner, name: $repo) { | ||
| dependencyGraphManifests( | ||
| dependenciesFirst: 1 | ||
| withDependencies: true | ||
| first: 100 | ||
| after: $cursor | ||
| ) { | ||
| ...summaryFragment | ||
| } | ||
| } | ||
| } | ||
| ${(0, graphql_1.print)(exports.summaryFragment)} | ||
| `, { repo, owner, cursor })); | ||
| const { dependencyGraphManifests: { edges }, } = result.repository; | ||
| return edges; | ||
| }); | ||
| }, | ||
| getRepositorySummary(owner, repo, glob = '') { | ||
| return __awaiter(this, void 0, void 0, function* () { | ||
| let edges = yield this.getRepositorySummaryPage(owner, repo); | ||
| if (!edges.length) { | ||
| return []; | ||
| } | ||
| let { cursor } = edges[edges.length - 1]; | ||
| while (cursor) { | ||
| edges = [ | ||
| ...edges, | ||
| ...(yield this.getRepositorySummaryPage(owner, repo, cursor)), | ||
| ]; | ||
| const next = edges[edges.length - 1].cursor; | ||
| cursor = next !== cursor ? next : ''; | ||
| } | ||
| const relevant = edges | ||
| .map((edge, i) => (Object.assign(Object.assign({}, edge), { after: i > 0 ? edges[i - 1].cursor : undefined }))) | ||
| .filter((edge) => (0, utils_js_1.matches)(edge.node.filename, constants_js_1.SUMMARY_FILE_TYPES, glob)); | ||
| return relevant; | ||
| }); | ||
| }, | ||
| getRepositoryDependencies(owner, repo, first, after) { | ||
| return __awaiter(this, void 0, void 0, function* () { | ||
| const result = (yield this.graphql(` | ||
| query getRepositoryDependencies( | ||
| $owner: String! | ||
| $repo: String! | ||
| $first: Int | ||
| $after: String | ||
| ) { | ||
| repository(owner: $owner, name: $repo) { | ||
| dependencyGraphManifests( | ||
| dependenciesFirst: 1 | ||
| withDependencies: true | ||
| first: $first | ||
| after: $after | ||
| ) { | ||
| ...repositoryFragment | ||
| } | ||
| } | ||
| } | ||
| ${(0, graphql_1.print)(exports.repositoryFragment)} | ||
| `, { repo, owner, first, after })); | ||
| const { dependencyGraphManifests: { nodes }, } = result.repository; | ||
| const dependencies = uniqBy(nodes | ||
| .filter((n) => (0, utils_js_1.matches)(n.filename, constants_js_1.DEPENDENCY_FILE_TYPES)) | ||
| .flatMap((n) => n.dependencies.nodes) | ||
| .filter((d) => { var _a; return (_a = d.repository) === null || _a === void 0 ? void 0 : _a.url; }), (d) => d.repository.url); | ||
| return dependencies; | ||
| }); | ||
| }, | ||
| getHeadOid(owner, repo) { | ||
| return __awaiter(this, void 0, void 0, function* () { | ||
| const result = (yield this.graphql(` | ||
| query getHeadOid($owner: String!, $repo: String!) { | ||
| repository(owner: $owner, name: $repo) { | ||
| defaultBranchRef { | ||
| name | ||
| target { | ||
| ... on Commit { | ||
| history(first: 1) { | ||
| nodes { | ||
| oid | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| `, { owner, repo })); | ||
| const { name, target } = result.repository.defaultBranchRef; | ||
| return { name, oid: target.history.nodes[0].oid }; | ||
| }); | ||
| }, | ||
| createCommit(owner, repo, input) { | ||
| return __awaiter(this, void 0, void 0, function* () { | ||
| const { name: branchName, oid } = yield this.getHeadOid(owner, repo); | ||
| const result = (yield this.graphql(` | ||
| mutation createCommit($input: CreateCommitOnBranchInput!) { | ||
| createCommitOnBranch(input: $input) { | ||
| commit { | ||
| url | ||
| } | ||
| } | ||
| } | ||
| `, { | ||
| input: Object.assign({ branch: { | ||
| repositoryNameWithOwner: `${owner}/${repo}`, | ||
| branchName, | ||
| }, expectedHeadOid: oid }, input), | ||
| })); | ||
| return result.createCommitOnBranch; | ||
| }); | ||
| }, | ||
| }; | ||
| }; | ||
| exports.getClient = getClient; |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
| import { Octokit } from 'octokit'; | ||
| export interface DependencyConfig { | ||
| octokit: Octokit; | ||
| owner: string; | ||
| repo: string; | ||
| filename?: string; | ||
| sourceDir?: string; | ||
| } | ||
| export interface GraphConfig { | ||
| octokit: Octokit; | ||
| owner: string; | ||
| repo: string; | ||
| after?: string; | ||
| } | ||
| export interface PackageJson { | ||
| filename: string; | ||
| dependencies: Record<string, string>; | ||
| devDependencies: Record<string, string>; | ||
| } | ||
| export interface StackAidDependency { | ||
| source: string; | ||
| dependencies?: StackAidDependency[]; | ||
| } | ||
| export interface StackAidJson { | ||
| version: 1; | ||
| dependencies: StackAidDependency[]; | ||
| } | ||
| export interface GoModule { | ||
| Path: string; | ||
| Dir: string; | ||
| Version: string; | ||
| } |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); |
| import { Ecosystem, FileTypes } from './constants.js'; | ||
| export declare const matches: (file: string, fileTypes: string[], glob?: string) => boolean; | ||
| export declare const isFileType: (filename: string, fileType: (typeof FileTypes)[Ecosystem]) => boolean; |
| "use strict"; | ||
| var __importDefault = (this && this.__importDefault) || function (mod) { | ||
| return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.isFileType = exports.matches = void 0; | ||
| const minimatch_1 = __importDefault(require("minimatch")); | ||
| const matches = (file, fileTypes, glob = '') => (0, minimatch_1.default)(file.toLowerCase(), `${glob}*(${fileTypes.join('|')})`); | ||
| exports.matches = matches; | ||
| const isFileType = (filename, fileType) => (0, exports.matches)(filename, fileType, '**/'); | ||
| exports.isFileType = isFileType; |
| export declare const GITHUB_DOMAIN = "github.com"; | ||
| export declare type Ecosystem = 'go' | 'java' | 'javascript' | 'php' | 'python' | 'ruby' | 'rust'; | ||
| export declare const FileTypes: Record<Ecosystem, string[]>; | ||
| export declare const SUMMARY_FILE_TYPES: string[]; | ||
| export declare const DEPENDENCY_FILE_TYPES: string[]; |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.DEPENDENCY_FILE_TYPES = exports.SUMMARY_FILE_TYPES = exports.FileTypes = exports.GITHUB_DOMAIN = void 0; | ||
| exports.GITHUB_DOMAIN = 'github.com'; | ||
| exports.FileTypes = { | ||
| go: ['go.mod'], | ||
| java: ['pom.xml'], | ||
| javascript: ['package.json'], | ||
| php: ['composer.json'], | ||
| python: ['pipfile', 'pyproject.toml', 'setup.py'], | ||
| ruby: ['gemfile'], | ||
| rust: ['cargo.toml'], | ||
| }; | ||
| exports.SUMMARY_FILE_TYPES = Object.values(exports.FileTypes).flat(); | ||
| exports.DEPENDENCY_FILE_TYPES = [ | ||
| exports.FileTypes.java, | ||
| exports.FileTypes.php, | ||
| exports.FileTypes.python, | ||
| exports.FileTypes.ruby, | ||
| exports.FileTypes.rust, | ||
| ].flat(); |
| import { DependencyConfig, GraphConfig, PackageJson, StackAidDependency } from './types/index.js'; | ||
| export declare const generators: { | ||
| go: ({ owner, repo, filename, sourceDir, }: DependencyConfig) => Promise<StackAidDependency[]>; | ||
| javascript: ({ octokit, owner, repo, filename, }: DependencyConfig) => Promise<PackageJson>; | ||
| graph: ({ octokit, owner, repo, after, }: GraphConfig) => Promise<StackAidDependency[]>; | ||
| }; |
| "use strict"; | ||
| var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| var desc = Object.getOwnPropertyDescriptor(m, k); | ||
| if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
| desc = { enumerable: true, get: function() { return m[k]; } }; | ||
| } | ||
| Object.defineProperty(o, k2, desc); | ||
| }) : (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| o[k2] = m[k]; | ||
| })); | ||
| var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
| Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
| }) : function(o, v) { | ||
| o["default"] = v; | ||
| }); | ||
| var __importStar = (this && this.__importStar) || function (mod) { | ||
| if (mod && mod.__esModule) return mod; | ||
| var result = {}; | ||
| if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
| __setModuleDefault(result, mod); | ||
| return result; | ||
| }; | ||
| var __importDefault = (this && this.__importDefault) || function (mod) { | ||
| return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.generators = void 0; | ||
| const go = __importStar(require("./go.js")); | ||
| const lodash_1 = __importDefault(require("lodash")); | ||
| const path_1 = __importDefault(require("path")); | ||
| const constants_js_1 = require("./constants.js"); | ||
| const queries_js_1 = require("./queries.js"); | ||
| const { uniqBy } = lodash_1.default; | ||
| const getJavaScriptDependencies = async ({ octokit, owner, repo, filename, }) => { | ||
| const content = await (0, queries_js_1.getClient)(octokit).getFileContents(owner, repo, filename); | ||
| const { dependencies, devDependencies } = JSON.parse(content); | ||
| return { filename, dependencies, devDependencies }; | ||
| }; | ||
| const getGoDependencies = async ({ owner, repo, filename, sourceDir, }) => { | ||
| const parent = `https://${constants_js_1.GITHUB_DOMAIN}/${owner}/${repo}`; | ||
| const deps = go | ||
| .getDependencies(path_1.default.dirname(filename), sourceDir) | ||
| .filter(({ source }) => source !== parent); | ||
| return deps; | ||
| }; | ||
| const getDependencyGraph = async ({ octokit, owner, repo, after, }) => { | ||
| const client = (0, queries_js_1.getClient)(octokit); | ||
| const dependencies = []; | ||
| const results = await client.getRepositoryDependencies(owner, repo, 1, after); | ||
| const direct = uniqBy(results, (d) => d.repository.url); | ||
| for (const dep of direct) { | ||
| const { url: source, name, owner: { login: owner }, } = dep.repository; | ||
| const summary = await client.getRepositorySummary(owner, name); | ||
| console.log(`${owner}/${name}: ${summary.map((s) => s.node.filename)}`); | ||
| let indirect = []; | ||
| for (const { after } of summary) { | ||
| const deps = await client.getRepositoryDependencies(owner, name, 1, after); | ||
| indirect.push(...deps.map((d) => ({ source: d.repository.url }))); | ||
| } | ||
| // Dependencies shouldn't be funding themselves. | ||
| indirect = indirect.filter((d) => d.source !== source); | ||
| dependencies.push({ source, dependencies: uniqBy(indirect, 'source') }); | ||
| } | ||
| return dependencies; | ||
| }; | ||
| exports.generators = { | ||
| go: getGoDependencies, | ||
| javascript: getJavaScriptDependencies, | ||
| graph: getDependencyGraph, | ||
| }; |
| import { StackAidDependency } from './types/index.js'; | ||
| export declare const listDirectDeps: (dir: string, sourceDir: string) => { | ||
| module: string; | ||
| version: string; | ||
| }[]; | ||
| export declare const getModuleGraph: (dir: string, sourceDir: string) => Record<string, { | ||
| module: string; | ||
| version: string; | ||
| }[]>; | ||
| export declare const getDependencies: (dir?: string, sourceDir?: string) => StackAidDependency[]; |
| "use strict"; | ||
| var __importDefault = (this && this.__importDefault) || function (mod) { | ||
| return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.getDependencies = exports.getModuleGraph = exports.listDirectDeps = void 0; | ||
| const lodash_1 = __importDefault(require("lodash")); | ||
| const path_1 = __importDefault(require("path")); | ||
| const constants_js_1 = require("./constants.js"); | ||
| const child_process_1 = require("child_process"); | ||
| const { uniqBy } = lodash_1.default; | ||
| const filterDependency = (line) => line.startsWith(constants_js_1.GITHUB_DOMAIN); | ||
| const parseDependency = (line) => { | ||
| switch (true) { | ||
| case line.startsWith(constants_js_1.GITHUB_DOMAIN): | ||
| const [domain, owner, repo] = line.split('/'); | ||
| return `https://${domain}/${owner}/${repo}`; | ||
| default: | ||
| return; | ||
| } | ||
| }; | ||
| const parseModuleUrl = (m) => { | ||
| const [url, version = ''] = m.split('@'); | ||
| const [domain, owner, repo] = url.split('/'); | ||
| return { module: [domain, owner, repo].join('/'), version }; | ||
| }; | ||
| const listDirectDeps = (dir, sourceDir) => { | ||
| let output = (0, child_process_1.execSync)(`go list -f '{{if not .Indirect}}{{.}}{{end}}' -m all`, { cwd: path_1.default.resolve(sourceDir, 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, sourceDir) => { | ||
| const output = (0, child_process_1.execSync)(`go mod graph`, { | ||
| cwd: path_1.default.resolve(sourceDir, dir), | ||
| }).toString(); | ||
| 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] = uniqBy(deps, 'module'); | ||
| }); | ||
| return graph; | ||
| }; | ||
| exports.getModuleGraph = getModuleGraph; | ||
| const getDependencies = (dir = '', sourceDir = process.cwd()) => { | ||
| const graph = (0, exports.getModuleGraph)(dir, sourceDir); | ||
| const direct = (0, exports.listDirectDeps)(dir, sourceDir); | ||
| 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), | ||
| dependencies: deps | ||
| .filter((d) => filterDependency(d.module)) | ||
| .map((d) => ({ | ||
| source: parseDependency(d.module), | ||
| })), | ||
| }; | ||
| }); | ||
| return dependencies; | ||
| }; | ||
| exports.getDependencies = getDependencies; |
| import { DependencyConfig, PackageJson, StackAidJson } from './types/index.js'; | ||
| import { generators } from './generate.js'; | ||
| export { generators } from './generate.js'; | ||
| export declare const getDependencies: (config: DependencyConfig, generatorTypes?: Partial<typeof generators>) => Promise<{ | ||
| stackAidJson: StackAidJson; | ||
| packageJson: PackageJson[]; | ||
| } | { | ||
| packageJson: PackageJson[]; | ||
| stackAidJson?: undefined; | ||
| }>; |
| "use strict"; | ||
| var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| var desc = Object.getOwnPropertyDescriptor(m, k); | ||
| if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
| desc = { enumerable: true, get: function() { return m[k]; } }; | ||
| } | ||
| Object.defineProperty(o, k2, desc); | ||
| }) : (function(o, m, k, k2) { | ||
| if (k2 === undefined) k2 = k; | ||
| o[k2] = m[k]; | ||
| })); | ||
| var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
| Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
| }) : function(o, v) { | ||
| o["default"] = v; | ||
| }); | ||
| var __importStar = (this && this.__importStar) || function (mod) { | ||
| if (mod && mod.__esModule) return mod; | ||
| var result = {}; | ||
| if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
| __setModuleDefault(result, mod); | ||
| return result; | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.getDependencies = exports.generators = void 0; | ||
| const core = __importStar(require("@actions/core")); | ||
| const constants_js_1 = require("./constants.js"); | ||
| const generate_js_1 = require("./generate.js"); | ||
| const queries_js_1 = require("./queries.js"); | ||
| const utils_js_1 = require("./utils.js"); | ||
| var generate_js_2 = require("./generate.js"); | ||
| Object.defineProperty(exports, "generators", { enumerable: true, get: function () { return generate_js_2.generators; } }); | ||
| const getDependencies = async (config, generatorTypes) => { | ||
| const { owner, repo, octokit } = config; | ||
| const packageJson = []; | ||
| const stackAidJson = { version: 1, dependencies: [] }; | ||
| const client = (0, queries_js_1.getClient)(octokit); | ||
| const summary = await client.getRepositorySummary(owner, repo, '**/'); | ||
| const generate = { ...generate_js_1.generators, ...generatorTypes }; | ||
| for (const { after, node: { filename }, } of summary) { | ||
| switch (true) { | ||
| case (0, utils_js_1.isFileType)(filename, constants_js_1.FileTypes.go): { | ||
| core.info(`Found ${filename}, getting Go dependencies`); | ||
| const deps = await generate.go({ ...config, filename }); | ||
| stackAidJson.dependencies.push(...deps); | ||
| break; | ||
| } | ||
| case (0, utils_js_1.isFileType)(filename, constants_js_1.FileTypes.javascript): { | ||
| core.info(`Found ${filename}, copying dependencies`); | ||
| const deps = await generate.javascript({ ...config, filename }); | ||
| packageJson.push(deps); | ||
| break; | ||
| } | ||
| default: | ||
| const deps = await generate.graph({ ...config, after }); | ||
| stackAidJson.dependencies.push(...deps); | ||
| break; | ||
| } | ||
| } | ||
| return stackAidJson.dependencies.length | ||
| ? { stackAidJson, packageJson } | ||
| : { packageJson }; | ||
| }; | ||
| exports.getDependencies = getDependencies; |
| import { CreateCommitOnBranchInput } from './types/graphql.js'; | ||
| import { Octokit } from 'octokit'; | ||
| export declare const summaryFragment: import("graphql").DocumentNode; | ||
| export declare const repositoryFragment: import("graphql").DocumentNode; | ||
| export declare const getClient: (octokit: Octokit) => { | ||
| graphql(query: string, variables?: Record<string, any>): Promise<any>; | ||
| getFileContents(owner: string, repo: string, path: string): Promise<string | null>; | ||
| getRepositorySummaryPage(owner: string, repo: string, cursor?: string): Promise<{ | ||
| cursor: string; | ||
| node: { | ||
| id: string; | ||
| filename: string; | ||
| }; | ||
| }[]>; | ||
| getRepositorySummary(owner: string, repo: string, glob?: string): Promise<{ | ||
| after: string | undefined; | ||
| cursor: string; | ||
| node: { | ||
| id: string; | ||
| filename: string; | ||
| }; | ||
| }[]>; | ||
| getRepositoryDependencies(owner: string, repo: string, first?: number, after?: string): Promise<{ | ||
| packageManager: string; | ||
| requirements: string; | ||
| packageName: string; | ||
| hasDependencies: boolean; | ||
| repository: { | ||
| name: string; | ||
| url: any; | ||
| owner: { | ||
| login: string; | ||
| } | { | ||
| login: string; | ||
| }; | ||
| }; | ||
| }[]>; | ||
| getHeadOid(owner: string, repo: string): Promise<{ | ||
| name: string; | ||
| oid: any; | ||
| }>; | ||
| createCommit(owner: string, repo: string, input: Partial<CreateCommitOnBranchInput>): Promise<{ | ||
| commit: { | ||
| url: any; | ||
| }; | ||
| }>; | ||
| }; |
| "use strict"; | ||
| var __importDefault = (this && this.__importDefault) || function (mod) { | ||
| return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.getClient = exports.repositoryFragment = exports.summaryFragment = void 0; | ||
| const lodash_1 = __importDefault(require("lodash")); | ||
| const constants_js_1 = require("./constants.js"); | ||
| const graphql_tag_1 = require("graphql-tag"); | ||
| const utils_js_1 = require("./utils.js"); | ||
| const graphql_1 = require("graphql"); | ||
| const { uniqBy } = lodash_1.default; | ||
| exports.summaryFragment = (0, graphql_tag_1.gql)(` | ||
| fragment summaryFragment on DependencyGraphManifestConnection { | ||
| edges { | ||
| cursor | ||
| node { | ||
| id | ||
| filename | ||
| } | ||
| } | ||
| } | ||
| `); | ||
| exports.repositoryFragment = (0, graphql_tag_1.gql)(` | ||
| fragment repositoryFragment on DependencyGraphManifestConnection { | ||
| nodes { | ||
| filename | ||
| dependencies { | ||
| nodes { | ||
| repository { | ||
| name | ||
| owner { | ||
| login | ||
| } | ||
| url | ||
| } | ||
| packageManager | ||
| requirements | ||
| packageName | ||
| hasDependencies | ||
| } | ||
| } | ||
| } | ||
| } | ||
| `); | ||
| const getClient = (octokit) => { | ||
| return { | ||
| async graphql(query, variables) { | ||
| const result = await octokit.graphql({ | ||
| ...variables, | ||
| query, | ||
| headers: { | ||
| // Required for dependency graph queries, see: | ||
| // https://docs.github.com/en/graphql/overview/schema-previews#access-to-a-repositories-dependency-graph-preview | ||
| Accept: 'application/vnd.github.hawkgirl-preview+json', | ||
| }, | ||
| request: { timeout: 60 * 1000 }, | ||
| }); | ||
| return result; | ||
| }, | ||
| async getFileContents(owner, repo, path) { | ||
| const res = await octokit.rest.repos.getContent({ | ||
| owner, | ||
| repo, | ||
| path, | ||
| }); | ||
| if (res?.status !== 200) { | ||
| return null; | ||
| } | ||
| const encodedContent = res.data.content; | ||
| return Buffer.from(encodedContent, 'base64').toString(); | ||
| }, | ||
| async getRepositorySummaryPage(owner, repo, cursor = '') { | ||
| const result = (await this.graphql(` | ||
| query getRepositorySummary( | ||
| $owner: String! | ||
| $repo: String! | ||
| $cursor: String | ||
| ) { | ||
| repository(owner: $owner, name: $repo) { | ||
| dependencyGraphManifests( | ||
| dependenciesFirst: 1 | ||
| withDependencies: true | ||
| first: 100 | ||
| after: $cursor | ||
| ) { | ||
| ...summaryFragment | ||
| } | ||
| } | ||
| } | ||
| ${(0, graphql_1.print)(exports.summaryFragment)} | ||
| `, { repo, owner, cursor })); | ||
| const { dependencyGraphManifests: { edges }, } = result.repository; | ||
| return edges; | ||
| }, | ||
| async getRepositorySummary(owner, repo, glob = '') { | ||
| let edges = await this.getRepositorySummaryPage(owner, repo); | ||
| if (!edges.length) { | ||
| return []; | ||
| } | ||
| let { cursor } = edges[edges.length - 1]; | ||
| while (cursor) { | ||
| edges = [ | ||
| ...edges, | ||
| ...(await this.getRepositorySummaryPage(owner, repo, cursor)), | ||
| ]; | ||
| const next = edges[edges.length - 1].cursor; | ||
| cursor = next !== cursor ? next : ''; | ||
| } | ||
| const relevant = edges | ||
| .map((edge, i) => ({ | ||
| ...edge, | ||
| after: i > 0 ? edges[i - 1].cursor : undefined, | ||
| })) | ||
| .filter((edge) => (0, utils_js_1.matches)(edge.node.filename, constants_js_1.SUMMARY_FILE_TYPES, glob)); | ||
| return relevant; | ||
| }, | ||
| async getRepositoryDependencies(owner, repo, first, after) { | ||
| const result = (await this.graphql(` | ||
| query getRepositoryDependencies( | ||
| $owner: String! | ||
| $repo: String! | ||
| $first: Int | ||
| $after: String | ||
| ) { | ||
| repository(owner: $owner, name: $repo) { | ||
| dependencyGraphManifests( | ||
| dependenciesFirst: 1 | ||
| withDependencies: true | ||
| first: $first | ||
| after: $after | ||
| ) { | ||
| ...repositoryFragment | ||
| } | ||
| } | ||
| } | ||
| ${(0, graphql_1.print)(exports.repositoryFragment)} | ||
| `, { repo, owner, first, after })); | ||
| const { dependencyGraphManifests: { nodes }, } = result.repository; | ||
| const dependencies = uniqBy(nodes | ||
| .filter((n) => (0, utils_js_1.matches)(n.filename, constants_js_1.DEPENDENCY_FILE_TYPES)) | ||
| .flatMap((n) => n.dependencies.nodes) | ||
| .filter((d) => d.repository?.url), (d) => d.repository.url); | ||
| return dependencies; | ||
| }, | ||
| async getHeadOid(owner, repo) { | ||
| const result = (await this.graphql(` | ||
| query getHeadOid($owner: String!, $repo: String!) { | ||
| repository(owner: $owner, name: $repo) { | ||
| defaultBranchRef { | ||
| name | ||
| target { | ||
| ... on Commit { | ||
| history(first: 1) { | ||
| nodes { | ||
| oid | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| `, { owner, repo })); | ||
| const { name, target } = result.repository.defaultBranchRef; | ||
| return { name, oid: target.history.nodes[0].oid }; | ||
| }, | ||
| async createCommit(owner, repo, input) { | ||
| const { name: branchName, oid } = await this.getHeadOid(owner, repo); | ||
| const result = (await this.graphql(` | ||
| mutation createCommit($input: CreateCommitOnBranchInput!) { | ||
| createCommitOnBranch(input: $input) { | ||
| commit { | ||
| url | ||
| } | ||
| } | ||
| } | ||
| `, { | ||
| input: { | ||
| branch: { | ||
| repositoryNameWithOwner: `${owner}/${repo}`, | ||
| branchName, | ||
| }, | ||
| expectedHeadOid: oid, | ||
| ...input, | ||
| }, | ||
| })); | ||
| return result.createCommitOnBranch; | ||
| }, | ||
| }; | ||
| }; | ||
| exports.getClient = getClient; |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
| import { Octokit } from 'octokit'; | ||
| export interface DependencyConfig { | ||
| octokit: Octokit; | ||
| owner: string; | ||
| repo: string; | ||
| filename?: string; | ||
| sourceDir?: string; | ||
| } | ||
| export interface GraphConfig { | ||
| octokit: Octokit; | ||
| owner: string; | ||
| repo: string; | ||
| after?: string; | ||
| } | ||
| export interface PackageJson { | ||
| filename: string; | ||
| dependencies: Record<string, string>; | ||
| devDependencies: Record<string, string>; | ||
| } | ||
| export interface StackAidDependency { | ||
| source: string; | ||
| dependencies?: StackAidDependency[]; | ||
| } | ||
| export interface StackAidJson { | ||
| version: 1; | ||
| dependencies: StackAidDependency[]; | ||
| } | ||
| export interface GoModule { | ||
| Path: string; | ||
| Dir: string; | ||
| Version: string; | ||
| } |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); |
| import { Ecosystem, FileTypes } from './constants.js'; | ||
| export declare const matches: (file: string, fileTypes: string[], glob?: string) => boolean; | ||
| export declare const isFileType: (filename: string, fileType: (typeof FileTypes)[Ecosystem]) => boolean; |
| "use strict"; | ||
| var __importDefault = (this && this.__importDefault) || function (mod) { | ||
| return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.isFileType = exports.matches = void 0; | ||
| const minimatch_1 = __importDefault(require("minimatch")); | ||
| const matches = (file, fileTypes, glob = '') => (0, minimatch_1.default)(file.toLowerCase(), `${glob}*(${fileTypes.join('|')})`); | ||
| exports.matches = matches; | ||
| const isFileType = (filename, fileType) => (0, exports.matches)(filename, fileType, '**/'); | ||
| exports.isFileType = isFileType; |
+17
-12
| { | ||
| "name": "@stackaid/stackaid-json-generator", | ||
| "version": "1.9.0-9", | ||
| "version": "1.9.0-10", | ||
| "private": false, | ||
| "description": "Generate a stackaid.json file based on your repository's dependency graph", | ||
| "exports": "./dist/index.js", | ||
| "type": "module", | ||
| "engines": { | ||
| "node": ">=14.16" | ||
| }, | ||
| "scripts": { | ||
| "build": "tsc", | ||
| "build": "npm run build:esm && npm run build:cjs", | ||
| "build:esm": "tsc", | ||
| "build:cjs": "tsc -p tsconfig-cjs.json", | ||
| "build:action": "ncc build ./src/action.ts -o dist/action --target es2020", | ||
@@ -28,7 +25,14 @@ "release": "np", | ||
| "license": "ISC", | ||
| "types": "./dist/types/index.d.ts", | ||
| "main": "./dist/cjs/index.js", | ||
| "module": "./dist/esm/index.js", | ||
| "exports": { | ||
| "node": { | ||
| "import": "./dist/ems/index.js", | ||
| "require": "./dist/cjs/index.js" | ||
| }, | ||
| "default": "./dist/cjs/index.js" | ||
| }, | ||
| "files": [ | ||
| "dist/*.js", | ||
| "dist/*.d.ts", | ||
| "dist/types/index.*" | ||
| "dist/esm/*", | ||
| "dist/cjs/*" | ||
| ], | ||
@@ -41,3 +45,4 @@ "dependencies": { | ||
| "lodash": "^4.17.21", | ||
| "minimatch": "^3.1.2" | ||
| "minimatch": "^3.1.2", | ||
| "octokit": "^2.0.10" | ||
| }, | ||
@@ -44,0 +49,0 @@ "devDependencies": { |
| export declare const GITHUB_DOMAIN = "github.com"; | ||
| export declare type Ecosystem = 'go' | 'java' | 'javascript' | 'php' | 'python' | 'ruby' | 'rust'; | ||
| export declare const FileTypes: Record<Ecosystem, string[]>; | ||
| export declare const SUMMARY_FILE_TYPES: string[]; | ||
| export declare const DEPENDENCY_FILE_TYPES: string[]; |
| export const GITHUB_DOMAIN = 'github.com'; | ||
| export const FileTypes = { | ||
| go: ['go.mod'], | ||
| java: ['pom.xml'], | ||
| javascript: ['package.json'], | ||
| php: ['composer.json'], | ||
| python: ['pipfile', 'pyproject.toml', 'setup.py'], | ||
| ruby: ['gemfile'], | ||
| rust: ['cargo.toml'], | ||
| }; | ||
| export const SUMMARY_FILE_TYPES = Object.values(FileTypes).flat(); | ||
| export const DEPENDENCY_FILE_TYPES = [ | ||
| FileTypes.java, | ||
| FileTypes.php, | ||
| FileTypes.python, | ||
| FileTypes.ruby, | ||
| FileTypes.rust, | ||
| ].flat(); |
| import { DependencyConfig, GraphConfig, PackageJson, StackAidDependency } from './types/index.js'; | ||
| export declare const generators: { | ||
| go: ({ owner, repo, filename, sourceDir, }: DependencyConfig) => Promise<StackAidDependency[]>; | ||
| javascript: ({ octokit, owner, repo, filename, }: DependencyConfig) => Promise<PackageJson>; | ||
| graph: ({ octokit, owner, repo, after, }: GraphConfig) => Promise<StackAidDependency[]>; | ||
| }; |
| import * as go from './go.js'; | ||
| import lodash from 'lodash'; | ||
| import path from 'path'; | ||
| import { GITHUB_DOMAIN } from './constants.js'; | ||
| import { getClient } from './queries.js'; | ||
| const { uniqBy } = lodash; | ||
| const getJavaScriptDependencies = async ({ octokit, owner, repo, filename, }) => { | ||
| const content = await getClient(octokit).getFileContents(owner, repo, filename); | ||
| const { dependencies, devDependencies } = JSON.parse(content); | ||
| return { filename, dependencies, devDependencies }; | ||
| }; | ||
| const getGoDependencies = async ({ owner, repo, filename, sourceDir, }) => { | ||
| const parent = `https://${GITHUB_DOMAIN}/${owner}/${repo}`; | ||
| const deps = go | ||
| .getDependencies(path.dirname(filename), sourceDir) | ||
| .filter(({ source }) => source !== parent); | ||
| return deps; | ||
| }; | ||
| const getDependencyGraph = async ({ octokit, owner, repo, after, }) => { | ||
| const client = getClient(octokit); | ||
| const dependencies = []; | ||
| const results = await client.getRepositoryDependencies(owner, repo, 1, after); | ||
| const direct = uniqBy(results, (d) => d.repository.url); | ||
| for (const dep of direct) { | ||
| const { url: source, name, owner: { login: owner }, } = dep.repository; | ||
| const summary = await client.getRepositorySummary(owner, name); | ||
| console.log(`${owner}/${name}: ${summary.map((s) => s.node.filename)}`); | ||
| let indirect = []; | ||
| for (const { after } of summary) { | ||
| const deps = await client.getRepositoryDependencies(owner, name, 1, after); | ||
| indirect.push(...deps.map((d) => ({ source: d.repository.url }))); | ||
| } | ||
| // Dependencies shouldn't be funding themselves. | ||
| indirect = indirect.filter((d) => d.source !== source); | ||
| dependencies.push({ source, dependencies: uniqBy(indirect, 'source') }); | ||
| } | ||
| return dependencies; | ||
| }; | ||
| export const generators = { | ||
| go: getGoDependencies, | ||
| javascript: getJavaScriptDependencies, | ||
| graph: getDependencyGraph, | ||
| }; |
-10
| import { StackAidDependency } from './types/index.js'; | ||
| export declare const listDirectDeps: (dir: string, sourceDir: string) => { | ||
| module: string; | ||
| version: string; | ||
| }[]; | ||
| export declare const getModuleGraph: (dir: string, sourceDir: string) => Record<string, { | ||
| module: string; | ||
| version: string; | ||
| }[]>; | ||
| export declare const getDependencies: (dir?: string, sourceDir?: string) => StackAidDependency[]; |
-72
| import lodash from 'lodash'; | ||
| import path from 'path'; | ||
| import { GITHUB_DOMAIN } from './constants.js'; | ||
| import { execSync } from 'child_process'; | ||
| const { uniqBy } = lodash; | ||
| const filterDependency = (line) => line.startsWith(GITHUB_DOMAIN); | ||
| const parseDependency = (line) => { | ||
| switch (true) { | ||
| case line.startsWith(GITHUB_DOMAIN): | ||
| const [domain, owner, repo] = line.split('/'); | ||
| return `https://${domain}/${owner}/${repo}`; | ||
| default: | ||
| return; | ||
| } | ||
| }; | ||
| const parseModuleUrl = (m) => { | ||
| const [url, version = ''] = m.split('@'); | ||
| const [domain, owner, repo] = url.split('/'); | ||
| return { module: [domain, owner, repo].join('/'), version }; | ||
| }; | ||
| export const listDirectDeps = (dir, sourceDir) => { | ||
| let output = execSync(`go list -f '{{if not .Indirect}}{{.}}{{end}}' -m all`, { cwd: path.resolve(sourceDir, 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, sourceDir) => { | ||
| const output = execSync(`go mod graph`, { | ||
| cwd: path.resolve(sourceDir, dir), | ||
| }).toString(); | ||
| 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] = uniqBy(deps, 'module'); | ||
| }); | ||
| return graph; | ||
| }; | ||
| export const getDependencies = (dir = '', sourceDir = process.cwd()) => { | ||
| const graph = getModuleGraph(dir, sourceDir); | ||
| const direct = listDirectDeps(dir, sourceDir); | ||
| 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), | ||
| dependencies: deps | ||
| .filter((d) => filterDependency(d.module)) | ||
| .map((d) => ({ | ||
| source: parseDependency(d.module), | ||
| })), | ||
| }; | ||
| }); | ||
| return dependencies; | ||
| }; |
| import { DependencyConfig, PackageJson, StackAidJson } from './types/index.js'; | ||
| import { generators } from './generate.js'; | ||
| export { generators } from './generate.js'; | ||
| export declare const getDependencies: (config: DependencyConfig, generatorTypes?: Partial<typeof generators>) => Promise<{ | ||
| stackAidJson: StackAidJson; | ||
| packageJson: PackageJson[]; | ||
| } | { | ||
| packageJson: PackageJson[]; | ||
| stackAidJson?: undefined; | ||
| }>; |
| import * as core from '@actions/core'; | ||
| import { FileTypes } from './constants.js'; | ||
| import { generators } from './generate.js'; | ||
| import { getClient } from './queries.js'; | ||
| import { isFileType } from './utils.js'; | ||
| export { generators } from './generate.js'; | ||
| export const getDependencies = async (config, generatorTypes) => { | ||
| const { owner, repo, octokit } = config; | ||
| const packageJson = []; | ||
| const stackAidJson = { version: 1, dependencies: [] }; | ||
| const client = getClient(octokit); | ||
| const summary = await client.getRepositorySummary(owner, repo, '**/'); | ||
| const generate = { ...generators, ...generatorTypes }; | ||
| for (const { after, node: { filename }, } of summary) { | ||
| switch (true) { | ||
| case isFileType(filename, FileTypes.go): { | ||
| core.info(`Found ${filename}, getting Go dependencies`); | ||
| const deps = await generate.go({ ...config, filename }); | ||
| stackAidJson.dependencies.push(...deps); | ||
| break; | ||
| } | ||
| case isFileType(filename, FileTypes.javascript): { | ||
| core.info(`Found ${filename}, copying dependencies`); | ||
| const deps = await generate.javascript({ ...config, filename }); | ||
| packageJson.push(deps); | ||
| break; | ||
| } | ||
| default: | ||
| const deps = await generate.graph({ ...config, after }); | ||
| stackAidJson.dependencies.push(...deps); | ||
| break; | ||
| } | ||
| } | ||
| return stackAidJson.dependencies.length | ||
| ? { stackAidJson, packageJson } | ||
| : { packageJson }; | ||
| }; |
| import { CreateCommitOnBranchInput } from './types/graphql.js'; | ||
| import { Octokit } from './types/index.js'; | ||
| export declare const summaryFragment: import("graphql").DocumentNode; | ||
| export declare const repositoryFragment: import("graphql").DocumentNode; | ||
| export declare const getClient: (octokit: Octokit) => { | ||
| octokit: import("@octokit/core").Octokit & import("@octokit/plugin-rest-endpoint-methods/dist-types/types.js").Api & { | ||
| paginate: import("@octokit/plugin-paginate-rest").PaginateInterface; | ||
| }; | ||
| graphql(query: string, variables?: Record<string, any>): Promise<any>; | ||
| getFileContents(owner: string, repo: string, path: string): Promise<string | null>; | ||
| getRepositorySummaryPage(owner: string, repo: string, cursor?: string): Promise<{ | ||
| cursor: string; | ||
| node: { | ||
| id: string; | ||
| filename: string; | ||
| }; | ||
| }[]>; | ||
| getRepositorySummary(owner: string, repo: string, glob?: string): Promise<{ | ||
| after: string | undefined; | ||
| cursor: string; | ||
| node: { | ||
| id: string; | ||
| filename: string; | ||
| }; | ||
| }[]>; | ||
| getRepositoryDependencies(owner: string, repo: string, first?: number, after?: string): Promise<{ | ||
| packageManager: string; | ||
| requirements: string; | ||
| packageName: string; | ||
| hasDependencies: boolean; | ||
| repository: { | ||
| name: string; | ||
| url: any; | ||
| owner: { | ||
| login: string; | ||
| } | { | ||
| login: string; | ||
| }; | ||
| }; | ||
| }[]>; | ||
| getHeadOid(owner: string, repo: string): Promise<{ | ||
| name: string; | ||
| oid: any; | ||
| }>; | ||
| createCommit(owner: string, repo: string, input: Partial<CreateCommitOnBranchInput>): Promise<{ | ||
| commit: { | ||
| url: any; | ||
| }; | ||
| }>; | ||
| }; |
-186
| import lodash from 'lodash'; | ||
| import { DEPENDENCY_FILE_TYPES, SUMMARY_FILE_TYPES } from './constants.js'; | ||
| import { gql } from 'graphql-tag'; | ||
| import { matches } from './utils.js'; | ||
| import { print } from 'graphql'; | ||
| const { uniqBy } = lodash; | ||
| export const summaryFragment = gql(` | ||
| fragment summaryFragment on DependencyGraphManifestConnection { | ||
| edges { | ||
| cursor | ||
| node { | ||
| id | ||
| filename | ||
| } | ||
| } | ||
| } | ||
| `); | ||
| export const repositoryFragment = gql(` | ||
| fragment repositoryFragment on DependencyGraphManifestConnection { | ||
| nodes { | ||
| filename | ||
| dependencies { | ||
| nodes { | ||
| repository { | ||
| name | ||
| owner { | ||
| login | ||
| } | ||
| url | ||
| } | ||
| packageManager | ||
| requirements | ||
| packageName | ||
| hasDependencies | ||
| } | ||
| } | ||
| } | ||
| } | ||
| `); | ||
| export const getClient = (octokit) => { | ||
| return { | ||
| octokit, | ||
| async graphql(query, variables) { | ||
| const result = await this.octokit.graphql({ | ||
| ...variables, | ||
| query, | ||
| headers: { | ||
| // Required for dependency graph queries, see: | ||
| // https://docs.github.com/en/graphql/overview/schema-previews#access-to-a-repositories-dependency-graph-preview | ||
| Accept: 'application/vnd.github.hawkgirl-preview+json', | ||
| }, | ||
| request: { timeout: 60 * 1000 }, | ||
| }); | ||
| return result; | ||
| }, | ||
| async getFileContents(owner, repo, path) { | ||
| const res = await this.octokit.rest.repos.getContent({ | ||
| owner, | ||
| repo, | ||
| path, | ||
| }); | ||
| if (res?.status !== 200) { | ||
| return null; | ||
| } | ||
| const encodedContent = res.data.content; | ||
| return Buffer.from(encodedContent, 'base64').toString(); | ||
| }, | ||
| async getRepositorySummaryPage(owner, repo, cursor = '') { | ||
| const result = (await this.graphql(` | ||
| query getRepositorySummary( | ||
| $owner: String! | ||
| $repo: String! | ||
| $cursor: String | ||
| ) { | ||
| repository(owner: $owner, name: $repo) { | ||
| dependencyGraphManifests( | ||
| dependenciesFirst: 1 | ||
| withDependencies: true | ||
| first: 100 | ||
| after: $cursor | ||
| ) { | ||
| ...summaryFragment | ||
| } | ||
| } | ||
| } | ||
| ${print(summaryFragment)} | ||
| `, { repo, owner, cursor })); | ||
| const { dependencyGraphManifests: { edges }, } = result.repository; | ||
| return edges; | ||
| }, | ||
| async getRepositorySummary(owner, repo, glob = '') { | ||
| let edges = await this.getRepositorySummaryPage(owner, repo); | ||
| if (!edges.length) { | ||
| return []; | ||
| } | ||
| let { cursor } = edges[edges.length - 1]; | ||
| while (cursor) { | ||
| edges = [ | ||
| ...edges, | ||
| ...(await this.getRepositorySummaryPage(owner, repo, cursor)), | ||
| ]; | ||
| const next = edges[edges.length - 1].cursor; | ||
| cursor = next !== cursor ? next : ''; | ||
| } | ||
| const relevant = edges | ||
| .map((edge, i) => ({ | ||
| ...edge, | ||
| after: i > 0 ? edges[i - 1].cursor : undefined, | ||
| })) | ||
| .filter((edge) => matches(edge.node.filename, SUMMARY_FILE_TYPES, glob)); | ||
| return relevant; | ||
| }, | ||
| async getRepositoryDependencies(owner, repo, first, after) { | ||
| const result = (await this.graphql(` | ||
| query getRepositoryDependencies( | ||
| $owner: String! | ||
| $repo: String! | ||
| $first: Int | ||
| $after: String | ||
| ) { | ||
| repository(owner: $owner, name: $repo) { | ||
| dependencyGraphManifests( | ||
| dependenciesFirst: 1 | ||
| withDependencies: true | ||
| first: $first | ||
| after: $after | ||
| ) { | ||
| ...repositoryFragment | ||
| } | ||
| } | ||
| } | ||
| ${print(repositoryFragment)} | ||
| `, { repo, owner, first, after })); | ||
| const { dependencyGraphManifests: { nodes }, } = result.repository; | ||
| const dependencies = uniqBy(nodes | ||
| .filter((n) => matches(n.filename, DEPENDENCY_FILE_TYPES)) | ||
| .flatMap((n) => n.dependencies.nodes) | ||
| .filter((d) => d.repository?.url), (d) => d.repository.url); | ||
| return dependencies; | ||
| }, | ||
| async getHeadOid(owner, repo) { | ||
| const result = (await this.graphql(` | ||
| query getHeadOid($owner: String!, $repo: String!) { | ||
| repository(owner: $owner, name: $repo) { | ||
| defaultBranchRef { | ||
| name | ||
| target { | ||
| ... on Commit { | ||
| history(first: 1) { | ||
| nodes { | ||
| oid | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| `, { owner, repo })); | ||
| const { name, target } = result.repository.defaultBranchRef; | ||
| return { name, oid: target.history.nodes[0].oid }; | ||
| }, | ||
| async createCommit(owner, repo, input) { | ||
| const { name: branchName, oid } = await this.getHeadOid(owner, repo); | ||
| const result = (await this.graphql(` | ||
| mutation createCommit($input: CreateCommitOnBranchInput!) { | ||
| createCommitOnBranch(input: $input) { | ||
| commit { | ||
| url | ||
| } | ||
| } | ||
| } | ||
| `, { | ||
| input: { | ||
| branch: { | ||
| repositoryNameWithOwner: `${owner}/${repo}`, | ||
| branchName, | ||
| }, | ||
| expectedHeadOid: oid, | ||
| ...input, | ||
| }, | ||
| })); | ||
| return result.createCommitOnBranch; | ||
| }, | ||
| }; | ||
| }; |
| import * as github from '@actions/github'; | ||
| export declare type Octokit = ReturnType<typeof github.getOctokit>; | ||
| export interface DependencyConfig { | ||
| octokit: Octokit; | ||
| owner: string; | ||
| repo: string; | ||
| filename?: string; | ||
| sourceDir?: string; | ||
| } | ||
| export interface GraphConfig { | ||
| octokit: Octokit; | ||
| owner: string; | ||
| repo: string; | ||
| after?: string; | ||
| } | ||
| export interface PackageJson { | ||
| filename: string; | ||
| dependencies: Record<string, string>; | ||
| devDependencies: Record<string, string>; | ||
| } | ||
| export interface StackAidDependency { | ||
| source: string; | ||
| dependencies?: StackAidDependency[]; | ||
| } | ||
| export interface StackAidJson { | ||
| version: 1; | ||
| dependencies: StackAidDependency[]; | ||
| } | ||
| export interface GoModule { | ||
| Path: string; | ||
| Dir: string; | ||
| Version: string; | ||
| } |
| export {}; |
| import { Ecosystem, FileTypes } from './constants.js'; | ||
| export declare const matches: (file: string, fileTypes: string[], glob?: string) => boolean; | ||
| export declare const isFileType: (filename: string, fileType: (typeof FileTypes)[Ecosystem]) => boolean; |
| import minimatch from 'minimatch'; | ||
| export const matches = (file, fileTypes, glob = '') => minimatch(file.toLowerCase(), `${glob}*(${fileTypes.join('|')})`); | ||
| export const isFileType = (filename, fileType) => matches(filename, fileType, '**/'); |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
2447277
10122.54%35
105.88%52325
10869.6%7
16.67%12
500%3
200%No
NaN+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added