File: D:/HostingSpaces/SBogers10/shop.komma.nl/node_modules/@vue/cli-ui/apollo-server/connectors/tasks.js
const { chalk, execa } = require('@vue/cli-shared-utils')
// Subs
const channels = require('../channels')
// Connectors
const cwd = require('./cwd')
const folders = require('./folders')
const logs = require('./logs')
const plugins = require('./plugins')
const prompts = require('./prompts')
const views = require('./views')
const projects = require('./projects')
// Utils
const { log } = require('../util/logger')
const { notify } = require('../util/notification')
const { terminate } = require('../util/terminate')
const { parseArgs } = require('../util/parse-args')
const MAX_LOGS = 2000
const VIEW_ID = 'vue-project-tasks'
const WIN_ENOENT_THRESHOLD = 500 // ms
const tasks = new Map()
function getTasks (file = null) {
if (!file) file = cwd.get()
let list = tasks.get(file)
if (!list) {
list = []
tasks.set(file, list)
}
return list
}
async function list ({ file = null, api = true } = {}, context) {
if (!file) file = cwd.get()
let list = getTasks(file)
const pkg = folders.readPackage(file, context)
if (pkg.scripts) {
const existing = new Map()
if (projects.getType(file, context) === 'vue') {
await plugins.list(file, context, { resetApi: false, lightApi: true })
}
const pluginApi = api && plugins.getApi(file)
// Get current valid tasks in project `package.json`
const scriptKeys = Object.keys(pkg.scripts)
let currentTasks = scriptKeys.map(
name => {
const id = `${file}:${name}`
existing.set(id, true)
const command = pkg.scripts[name]
const moreData = pluginApi ? pluginApi.getDescribedTask(command) : null
return {
id,
name,
command,
index: list.findIndex(t => t.id === id),
prompts: [],
views: [],
path: file,
...moreData
}
}
)
if (api && pluginApi) {
currentTasks = currentTasks.concat(plugins.getApi(file).addedTasks.map(
task => {
const id = `${file}:${task.name}`
existing.set(id, true)
return {
id,
index: list.findIndex(t => t.id === id),
prompts: [],
views: [],
path: file,
uiOnly: true,
...task
}
}
))
}
// Process existing tasks
const existingTasks = currentTasks.filter(
task => task.index !== -1
)
// Update tasks data
existingTasks.forEach(task => {
Object.assign(list[task.index], task)
})
// Process removed tasks
const removedTasks = list.filter(
t => currentTasks.findIndex(c => c.id === t.id) === -1
)
// Remove badges
removedTasks.forEach(task => {
updateViewBadges({ task }, context)
})
// Process new tasks
const newTasks = currentTasks.filter(
task => task.index === -1
).map(
task => ({
...task,
status: 'idle',
child: null,
logs: []
})
)
// Keep existing running tasks
list = list.filter(
task => existing.get(task.id) ||
task.status === 'running'
)
// Add the new tasks
list = list.concat(newTasks)
// Sort
const getSortScore = task => {
const index = scriptKeys.indexOf(task.name)
if (index !== -1) return index
return Infinity
}
list.sort((a, b) => getSortScore(a) - getSortScore(b))
tasks.set(file, list)
}
return list
}
function findOne (id, context) {
for (const [, list] of tasks) {
const result = list.find(t => t.id === id)
if (result) return result
}
}
function getSavedData (id, context) {
let data = context.db.get('tasks').find({
id
}).value()
// Clone
if (data != null) data = JSON.parse(JSON.stringify(data))
return data
}
function updateSavedData (data, context) {
if (getSavedData(data.id, context)) {
context.db.get('tasks').find({ id: data.id }).assign(data).write()
} else {
context.db.get('tasks').push(data).write()
}
}
function getPrompts (id, context) {
return restoreParameters({ id }, context)
}
function updateOne (data, context) {
const task = findOne(data.id)
if (task) {
if (task.status !== data.status) {
updateViewBadges({
task,
data
}, context)
}
Object.assign(task, data)
context.pubsub.publish(channels.TASK_CHANGED, {
taskChanged: task
})
}
return task
}
function updateViewBadges ({ task, data }, context) {
const viewId = VIEW_ID
// New badges
if (data) {
if (data.status === 'error') {
views.addBadge({
viewId,
badge: {
id: 'vue-task-error',
type: 'error',
label: 'org.vue.components.view-badge.labels.tasks.error',
priority: 3
}
}, context)
} else if (data.status === 'running') {
views.addBadge({
viewId,
badge: {
id: 'vue-task-running',
type: 'info',
label: 'org.vue.components.view-badge.labels.tasks.running',
priority: 2
}
}, context)
} else if (data.status === 'done') {
views.addBadge({
viewId,
badge: {
id: 'vue-task-done',
type: 'success',
label: 'org.vue.components.view-badge.labels.tasks.done',
priority: 1,
hidden: true
}
}, context)
}
}
// Remove previous badges
if (task.status === 'error') {
views.removeBadge({ viewId, badgeId: 'vue-task-error' }, context)
} else if (task.status === 'running') {
views.removeBadge({ viewId, badgeId: 'vue-task-running' }, context)
} else if (task.status === 'done') {
views.removeBadge({ viewId, badgeId: 'vue-task-done' }, context)
}
}
async function run (id, context) {
const task = findOne(id, context)
if (task && task.status !== 'running') {
task._terminating = false
// Answers
const answers = prompts.getAnswers()
let [command, ...args] = parseArgs(task.command)
// Output colors
// See: https://www.npmjs.com/package/supports-color
process.env.FORCE_COLOR = 1
// Plugin API
if (task.onBeforeRun) {
if (!answers.$_overrideArgs) {
const origPush = args.push.bind(args)
args.push = (...items) => {
if (items.length && args.indexOf(items[0]) !== -1) return items.length
return origPush(...items)
}
}
await task.onBeforeRun({
answers,
args
})
}
// Deduplicate arguments
const dedupedArgs = []
for (let i = args.length - 1; i >= 0; i--) {
const arg = args[i]
if (typeof arg === 'string' && arg.indexOf('--') === 0) {
if (dedupedArgs.indexOf(arg) === -1) {
dedupedArgs.push(arg)
} else {
const value = args[i + 1]
if (value && value.indexOf('--') !== 0) {
dedupedArgs.pop()
}
}
} else {
dedupedArgs.push(arg)
}
}
args = dedupedArgs.reverse()
if (command === 'npm') {
args.splice(0, 0, '--')
}
log('Task run', command, args)
updateOne({
id: task.id,
status: 'running'
}, context)
logs.add({
message: `Task ${task.id} started`,
type: 'info'
}, context)
addLog({
taskId: task.id,
type: 'stdout',
text: chalk.grey(`$ ${command} ${args.join(' ')}`)
}, context)
task.time = Date.now()
// Task env
process.env.VUE_CLI_CONTEXT = cwd.get()
process.env.VUE_CLI_PROJECT_ID = projects.getCurrent(context).id
const nodeEnv = process.env.NODE_ENV
delete process.env.NODE_ENV
const child = execa(command, args, {
cwd: cwd.get(),
stdio: ['inherit', 'pipe', 'pipe'],
shell: true
})
if (typeof nodeEnv !== 'undefined') {
process.env.NODE_ENV = nodeEnv
}
task.child = child
const outPipe = logPipe(queue => {
addLog({
taskId: task.id,
type: 'stdout',
text: queue
}, context)
})
child.stdout.on('data', buffer => {
outPipe.add(buffer.toString())
})
const errPipe = logPipe(queue => {
addLog({
taskId: task.id,
type: 'stderr',
text: queue
}, context)
})
child.stderr.on('data', buffer => {
errPipe.add(buffer.toString())
})
const onExit = async (code, signal) => {
outPipe.flush()
errPipe.flush()
log('Task exit', command, args, 'code:', code, 'signal:', signal)
const duration = Date.now() - task.time
const seconds = Math.round(duration / 10) / 100
addLog({
taskId: task.id,
type: 'stdout',
text: chalk.grey(`Total task duration: ${seconds}s`)
}, context)
// Plugin API
if (task.onExit) {
await task.onExit({
args,
child,
cwd: cwd.get(),
code,
signal
})
}
if (code === null || task._terminating) {
updateOne({
id: task.id,
status: 'terminated'
}, context)
logs.add({
message: `Task ${task.id} was terminated`,
type: 'info'
}, context)
} else if (code !== 0) {
updateOne({
id: task.id,
status: 'error'
}, context)
logs.add({
message: `Task ${task.id} ended with error code ${code}`,
type: 'error'
}, context)
notify({
title: 'Task error',
message: `Task ${task.id} ended with error code ${code}`,
icon: 'error'
})
} else {
updateOne({
id: task.id,
status: 'done'
}, context)
logs.add({
message: `Task ${task.id} completed`,
type: 'done'
}, context)
notify({
title: 'Task completed',
message: `Task ${task.id} completed in ${seconds}s.`,
icon: 'done'
})
}
plugins.callHook({
id: 'taskExit',
args: [{
task,
args,
child,
cwd: cwd.get(),
signal,
code
}],
file: cwd.get()
}, context)
}
child.on('exit', onExit)
child.on('error', error => {
const duration = Date.now() - task.time
// hackish workaround for https://github.com/vuejs/vue-cli/issues/2096
if (process.platform === 'win32' && error.code === 'ENOENT' && duration > WIN_ENOENT_THRESHOLD) {
return onExit(null)
}
updateOne({
id: task.id,
status: 'error'
}, context)
logs.add({
message: `Error while running task ${task.id} with message'${error.message}'`,
type: 'error'
}, context)
notify({
title: 'Task error',
message: `Error while running task ${task.id} with message'${error.message}'`,
icon: 'error'
})
addLog({
taskId: task.id,
type: 'stdout',
text: chalk.red(`Error while running task ${task.id} with message '${error.message}'`)
}, context)
console.error(error)
})
// Plugin API
if (task.onRun) {
await task.onRun({
args,
child,
cwd: cwd.get()
})
}
plugins.callHook({
id: 'taskRun',
args: [{
task,
args,
child,
cwd: cwd.get()
}],
file: cwd.get()
}, context)
}
return task
}
async function stop (id, context) {
const task = findOne(id, context)
if (task && task.status === 'running' && task.child) {
task._terminating = true
try {
const { success, error } = await terminate(task.child, cwd.get())
if (success) {
updateOne({
id: task.id,
status: 'terminated'
}, context)
} else if (error) {
throw error
} else {
throw new Error('Unknown error')
}
} catch (e) {
console.log(chalk.red(`Can't terminate process ${task.child.pid}`))
console.error(e)
}
}
return task
}
function addLog (log, context) {
const task = findOne(log.taskId, context)
if (task) {
if (task.logs.length === MAX_LOGS) {
task.logs.shift()
}
task.logs.push(log)
context.pubsub.publish(channels.TASK_LOG_ADDED, {
taskLogAdded: log
})
}
}
function clearLogs (id, context) {
const task = findOne(id, context)
if (task) {
task.logs = []
}
return task
}
function open (id, context) {
const task = findOne(id, context)
plugins.callHook({
id: 'taskOpen',
args: [{
task,
cwd: cwd.get()
}],
file: cwd.get()
}, context)
return true
}
function logPipe (action) {
const maxTime = 300
let queue = ''
let size = 0
let time = Date.now()
let timeout
const add = (string) => {
queue += string
size++
if (size === 50 || Date.now() > time + maxTime) {
flush()
} else {
clearTimeout(timeout)
timeout = setTimeout(flush, maxTime)
}
}
const flush = () => {
clearTimeout(timeout)
if (!size) return
action(queue)
queue = ''
size = 0
time = Date.now()
}
return {
add,
flush
}
}
function saveParameters ({ id }, context) {
// Answers
const answers = prompts.getAnswers()
// Save parameters
updateSavedData({
id,
answers
}, context)
return prompts.list()
}
async function restoreParameters ({ id }, context) {
const task = findOne(id, context)
if (task) {
await prompts.reset()
if (task.prompts.length) {
prompts.add({
name: '$_overrideArgs',
type: 'confirm',
default: false,
message: 'org.vue.views.project-task-details.override-args.message',
description: 'org.vue.views.project-task-details.override-args.description'
})
}
task.prompts.forEach(prompts.add)
const data = getSavedData(id, context)
if (data) {
await prompts.setAnswers(data.answers)
}
await prompts.start()
}
return prompts.list()
}
module.exports = {
list,
findOne,
getPrompts,
run,
stop,
updateOne,
clearLogs,
open,
saveParameters,
restoreParameters
}