Skip to content
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

Integrations setup #4354

Merged
merged 17 commits into from
May 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions integrations/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules/
99 changes: 99 additions & 0 deletions integrations/execute.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
let path = require('path')
let { spawn } = require('child_process')
let resolveToolRoot = require('./resolve-tool-root')

let runningProcessess = []

afterEach(() => {
runningProcessess.splice(0).forEach((runningProcess) => runningProcess.stop())
})

module.exports = function $(command, options = {}) {
let abortController = new AbortController()
let cwd = resolveToolRoot()

let args = command.split(' ')
command = args.shift()
command = path.resolve(cwd, 'node_modules', '.bin', command)

let stdoutMessages = []
let stderrMessages = []

let stdoutActors = []
let stderrActors = []

function notifyNext(actors, messages) {
if (actors.length <= 0) return
let [next] = actors

for (let [idx, message] of messages.entries()) {
if (next.predicate(message)) {
messages.splice(0, idx + 1)
let actorIdx = actors.indexOf(next)
actors.splice(actorIdx, 1)
next.resolve()
break
}
}
}

function notifyNextStdoutActor() {
return notifyNext(stdoutActors, stdoutMessages)
}

function notifyNextStderrActor() {
return notifyNext(stderrActors, stderrMessages)
}

let runningProcess = new Promise((resolve, reject) => {
let child = spawn(command, args, {
...options,
env: {
...process.env,
...options.env,
},
signal: abortController.signal,
cwd,
})

let stdout = ''
let stderr = ''

child.stdout.on('data', (data) => {
stdoutMessages.push(data.toString())
notifyNextStdoutActor()
stdout += data
})

child.stderr.on('data', (data) => {
stderrMessages.push(data.toString())
notifyNextStderrActor()
stderr += data
})

child.on('close', (code, signal) => {
;(signal === 'SIGTERM' ? resolve : code === 0 ? resolve : reject)({ code, stdout, stderr })
})
})

runningProcessess.push(runningProcess)

return Object.assign(runningProcess, {
stop() {
abortController.abort()
return runningProcess
},
onStdout(predicate) {
return new Promise((resolve) => {
stdoutActors.push({ predicate, resolve })
notifyNextStdoutActor()
})
},
onStderr(predicate) {
return new Promise((resolve) => {
stderrActors.push({ predicate, resolve })
notifyNextStderrActor()
})
},
})
}
4 changes: 4 additions & 0 deletions integrations/html.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Small helper to allow for html highlighting / formatting in most editors.
module.exports = function html(templates) {
return templates.join('')
}
139 changes: 139 additions & 0 deletions integrations/io.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
let { rm, existsSync } = require('fs')
let path = require('path')
let fs = require('fs/promises')

let chokidar = require('chokidar')

let resolveToolRoot = require('./resolve-tool-root')

module.exports = function ({
/** Output directory, relative to the tool. */
output = 'dist',

/** Input directory, relative to the tool. */
input = 'src',

/** Whether or not you want to cleanup the output directory. */
cleanup = true,
} = {}) {
let toolRoot = resolveToolRoot()
let fileCache = {}

let absoluteOutputFolder = path.resolve(toolRoot, output)
let absoluteInputFolder = path.resolve(toolRoot, input)

if (cleanup) {
beforeAll((done) => rm(absoluteOutputFolder, { recursive: true, force: true }, done))
afterEach((done) => rm(absoluteOutputFolder, { recursive: true, force: true }, done))
}

// Restore all written files
afterEach(async () => {
await Promise.all(
Object.entries(fileCache).map(([file, content]) => fs.writeFile(file, content, 'utf8'))
)
})

async function readdir(start, parent = []) {
let files = await fs.readdir(start, { withFileTypes: true })
let resolvedFiles = await Promise.all(
files.map((file) => {
if (file.isDirectory()) {
return readdir(path.resolve(start, file.name), [...parent, file.name])
}
return parent.concat(file.name).join(path.sep)
})
)
return resolvedFiles.flat(Infinity)
}

async function resolveFile(fileOrRegex, directory) {
if (fileOrRegex instanceof RegExp) {
let files = await readdir(directory)
if (files.length === 0) {
throw new Error(`No files exists in "${directory}"`)
}

let filtered = files.filter((file) => fileOrRegex.test(file))
if (filtered.length === 0) {
throw new Error(`Not a single file matched: ${fileOrRegex}`)
} else if (filtered.length > 1) {
throw new Error(`Multiple files matched: ${fileOrRegex}`)
}

return filtered[0]
}

return fileOrRegex
}

return {
async readOutputFile(file) {
file = await resolveFile(file, absoluteOutputFolder)
return fs.readFile(path.resolve(absoluteOutputFolder, file), 'utf8')
},
async appendToInputFile(file, contents) {
let filePath = path.resolve(absoluteInputFolder, file)
if (!fileCache[filePath]) {
fileCache[filePath] = await fs.readFile(filePath, 'utf8')
}

return fs.appendFile(filePath, contents, 'utf8')
},
async writeInputFile(file, contents) {
let filePath = path.resolve(absoluteInputFolder, file)
if (!fileCache[filePath]) {
fileCache[filePath] = await fs.readFile(filePath, 'utf8')
}

return fs.writeFile(path.resolve(absoluteInputFolder, file), contents, 'utf8')
},
async waitForOutputFileCreation(file) {
if (file instanceof RegExp) {
let r = file
let watcher = chokidar.watch(absoluteOutputFolder)

return new Promise((resolve) => {
watcher.on('add', (file) => {
if (r.test(file)) {
watcher.close()
resolve()
}
})
})
} else {
let filePath = path.resolve(absoluteOutputFolder, file)
let watcher = chokidar.watch(filePath)

let watcherPromise = new Promise((resolve) => {
watcher.once('add', () => {
watcher.close()
resolve()
})
})

if (existsSync(filePath)) {
watcher.close()
return Promise.resolve()
}

return watcherPromise
}
},
async waitForOutputFileChange(file, cb = () => {}) {
file = await resolveFile(file, absoluteOutputFolder)

let filePath = path.resolve(absoluteOutputFolder, file)
let watcher = chokidar.watch(filePath)

return new Promise((resolve) => {
let chain = Promise.resolve()
watcher.once('change', () => {
watcher.close()
chain.then(() => resolve())
})
chain.then(() => cb())
})
},
}
}
Loading