-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
lib/test: add initial prototype for wiby --test #3
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,26 @@ | ||
#!/usr/bin/env node | ||
|
||
require('dotenv').config() | ||
const test = require('../lib/test') | ||
const result = require('../lib/result') | ||
|
||
if (process.argv[2] === 'test') { | ||
test() | ||
const args = require('yargs') | ||
.option('test', { | ||
alias: 't', | ||
describe: 'Test your dependents' | ||
}) | ||
.option('result', { | ||
alias: 'r', | ||
describe: 'Get the result of your tests' | ||
}) | ||
.argv | ||
|
||
if (args.test) { | ||
test(args.test) | ||
} | ||
|
||
if (process.argv[2] === 'result') { | ||
if (args.result) { | ||
result() | ||
} | ||
|
||
// Usage: wiby --test URL |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
const { graphql } = require('@octokit/graphql') | ||
const { Octokit } = require('@octokit/rest') | ||
const octokit = new Octokit({ | ||
auth: process.env.GITHUB_TOKEN | ||
}) | ||
|
||
const graphqlWithAuth = graphql.defaults({ | ||
headers: { | ||
authorization: `token ${process.env.GITHUB_TOKEN}` | ||
} | ||
}) | ||
|
||
module.exports.getPackageJson = | ||
async function getPackageJson (owner, repo) { | ||
try { | ||
const resp = await graphqlWithAuth({ | ||
query: `query($owner: String!, $repo: String!) { | ||
repository(name: $repo, owner: $owner) { | ||
object(expression: "master:package.json") { | ||
... on Blob { | ||
text | ||
} | ||
} | ||
} | ||
}`, | ||
owner: owner, | ||
repo: repo | ||
}) | ||
return (JSON.parse(resp.repository.object.text)) | ||
} catch (err) { | ||
if (err.errors && err.errors[0].type === 'NOT_FOUND') { | ||
throw Error(`Could not find GitHub repository at https://www.github.com/${owner}/${repo}`) | ||
} else { | ||
throw err | ||
} | ||
} | ||
} | ||
|
||
module.exports.getPermissions = | ||
async function getPermissions (owner, repo) { | ||
try { | ||
const resp = await graphqlWithAuth({ | ||
query: `query($owner: String!, $repo: String!) { | ||
repository(name: $repo, owner: $owner) { | ||
viewerPermission | ||
} | ||
}`, | ||
owner: owner, | ||
repo: repo | ||
}) | ||
return resp.repository.viewerPermission | ||
} catch (err) { | ||
if (err.errors && err.errors[0].type === 'NOT_FOUND') { | ||
throw Error(`Could not find GitHub repository at https://www.github.com/${owner}/${repo}`) | ||
} else { | ||
throw err | ||
} | ||
} | ||
} | ||
|
||
module.exports.getShas = | ||
async function getShas (owner, repo) { | ||
const resp = await octokit.repos.listCommits({ | ||
owner, | ||
repo, | ||
per_page: 1 | ||
}) | ||
const headSha = resp.data[0].sha | ||
const treeSha = resp.data[0].commit.tree.sha | ||
// console.log(`lastSha: ${headSha} treeSha: ${treeSha}`) | ||
return [headSha, treeSha] | ||
} | ||
|
||
module.exports.createBlob = | ||
async function createBlob (owner, repo, file) { | ||
const { | ||
data: { sha: blobSha } | ||
} = await octokit.git.createBlob({ | ||
owner, | ||
repo, | ||
content: file, | ||
encoding: 'base64' | ||
}) | ||
return blobSha | ||
} | ||
|
||
module.exports.createTree = async function createTree (owner, repo, treeSha, blobSha) { | ||
const resp = await octokit.git.createTree({ | ||
owner, | ||
repo, | ||
base_tree: treeSha, | ||
tree: [ | ||
{ path: 'package.json', mode: '100644', sha: blobSha } | ||
], | ||
headers: { | ||
Accept: 'application/json' | ||
} | ||
}) | ||
const newTreeSha = resp.data.sha | ||
return newTreeSha | ||
} | ||
|
||
module.exports.createCommit = | ||
async function createCommit (owner, repo, message, newTreeSha, headSha) { | ||
const resp = await octokit.git.createCommit({ | ||
owner, | ||
repo, | ||
message: message, | ||
tree: newTreeSha, | ||
parents: [headSha] | ||
}) | ||
const commitSha = resp.data.sha | ||
return commitSha | ||
} | ||
|
||
module.exports.createBranch = | ||
async function createBranch (owner, repo, commitSha, branch) { | ||
await octokit.git.createRef({ | ||
owner, | ||
repo, | ||
sha: commitSha, | ||
ref: `refs/heads/${branch}` | ||
}) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,105 @@ | ||
module.exports = () => { | ||
console.log('wiby test') | ||
const fsPromises = require('fs').promises | ||
const fetch = require('node-fetch') | ||
const github = require('./github') | ||
|
||
module.exports = async function (url) { | ||
const dep = await getLocalPackageName() | ||
console.log('Got local package name: ', dep) | ||
const [org, repo] = getOrgRepo(url) | ||
console.log(`Got org: ${org} and repo: ${repo}`) | ||
const packageJSON = await github.getPackageJson(org, repo) | ||
if (!checkPackageInPackageJSON(dep, packageJSON)) { | ||
throw new Error('Dependency not found in package.json') | ||
} | ||
const patch = await getPatch(dep) | ||
console.log('Created patch: ', patch) | ||
const patchedPackageJSON = applyPatch(patch, dep, packageJSON) | ||
await pushPatch(patchedPackageJSON, org, repo, dep) | ||
} | ||
|
||
const getCommitHash = module.exports.getCommitHash = | ||
async function getCommitHash (owner, repo) { | ||
const headSha = (await github.getShas(owner, repo))[0] | ||
return headSha | ||
} | ||
|
||
const checkPackageInPackageJSON = module.exports.checkPackageInPackageJSON = | ||
function checkPackageInPackageJSON (dep, packageJSON) { | ||
return Object.prototype.hasOwnProperty.call(packageJSON.dependencies, dep) | ||
} | ||
|
||
const getOrgRepo = module.exports.getOrgRepo = | ||
function getOrgRepo (url) { | ||
const repoOrgArr = (url.split('github.com'))[1].split('/') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I find https://www.npmjs.com/package/git-url-parse handy for things like this, esp. if we're going to support Github Enterprise. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Covered by #10 |
||
const org = repoOrgArr[1] | ||
const repo = repoOrgArr[2] | ||
return [org, repo] | ||
} | ||
|
||
const getPatch = module.exports.getPatch = | ||
async function getPatch (dep, hash) { | ||
dep = dep || await getLocalPackageName() | ||
const [org, repo] = await getGitHubOrgRepo(dep) | ||
hash = hash || await getCommitHash(org, repo) | ||
const patch = `${org}/${repo}#${hash}` | ||
return patch | ||
} | ||
|
||
const getLocalPackageName = module.exports.getLocalPackageName = | ||
async function getLocalPackageName (pkgPath) { | ||
pkgPath = pkgPath || 'package.json' | ||
let pkg = await fsPromises.readFile(pkgPath).catch((err) => { | ||
throw (err) | ||
}) | ||
pkg = JSON.parse(pkg) | ||
return pkg.name | ||
} | ||
|
||
const getGitHubOrgRepo = module.exports.getGitHubOrgRepo = | ||
async function getGitHubOrgRepo (dep) { | ||
const urlRegex = /github.com\/([^/])+\/[^/]+/g | ||
const resp = await fetchRegistryInfo(dep) | ||
let org, repo | ||
if (resp.repository && resp.repository.url) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Covered by #10 |
||
let gitUrl = (resp.repository.url).match(urlRegex) | ||
if (gitUrl) { | ||
gitUrl = gitUrl[0].replace(/(\.git)/g, '') | ||
org = getOrgRepo(gitUrl)[0] | ||
repo = getOrgRepo(gitUrl)[1] | ||
} | ||
} | ||
if (!org && !repo) { | ||
org = 'undefined' | ||
repo = 'undefined' | ||
} | ||
return [org, repo] | ||
} | ||
|
||
const fetchRegistryInfo = module.exports.fetchRegistryInfo = | ||
async function fetchRegistryInfo (dep) { | ||
const resp = await fetch(`https://registry.npmjs.org/${dep}`) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should use |
||
return resp.json() | ||
} | ||
|
||
const applyPatch = module.exports.applyPatch = | ||
function applyPatch (patch, dep, packageJSON) { | ||
if (!Object.prototype.hasOwnProperty.call(packageJSON.dependencies, dep)) { | ||
throw new Error('Dependency not found in package.json') | ||
} | ||
packageJSON.dependencies[dep] = patch | ||
return packageJSON | ||
} | ||
|
||
async function pushPatch (packageJSON, owner, repo, dep) { | ||
const file = JSON.stringify(packageJSON, null, 2) + '\n' // assumes package.json is using two spaces | ||
const encodedFile = Buffer.from(file).toString('base64') | ||
const message = `wiby: update ${dep}` | ||
const branch = `wiby-${dep}` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does Should the |
||
|
||
const [headSha, treeSha] = await github.getShas(owner, repo) | ||
const blobSha = await github.createBlob(owner, repo, encodedFile) | ||
const newTreeSha = await github.createTree(owner, repo, treeSha, blobSha) | ||
const commitSha = await github.createCommit(owner, repo, message, newTreeSha, headSha) | ||
await github.createBranch(owner, repo, commitSha, branch) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I haven't looked in detail, but I think it might be possible to do all of the above in a single API request: https://octokit.github.io/rest.js/v18#repos-create-or-update-file-contents. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Covered by #9 |
||
console.log(`Changes pushed to https://github.com/${owner}/${repo}/blob/${branch}/package.json`) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we consider
test
/result
to be commands instead? e.g.This does have the downside that you can't
--test --result
like you do now, but maybe--wait-for-result
or smth, which keeps polling for results, is good enough.The upside is that this allows structuring the project with
Yargs.commandDir()
. I find that commands are also a nice way to expose and mirror the behavior between a package being a lib and a cli, i.e.require('wiby').test({ dependent: '@pkgjs/nv' })
can be equivalent tonpx wiby test --dependent=@pkgjs/nv
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Covered by #7