2019-03-03 08:12:46 +00:00
|
|
|
import path from 'path'
|
|
|
|
import consola from 'consola'
|
|
|
|
import hash from 'hash-sum'
|
|
|
|
import fs from 'fs-extra'
|
|
|
|
import properlock from 'proper-lockfile'
|
2023-04-17 10:10:45 +00:00
|
|
|
// eslint-disable-next-line import/namespace
|
|
|
|
import { onExit } from 'signal-exit'
|
2019-03-03 08:12:46 +00:00
|
|
|
|
|
|
|
export const lockPaths = new Set()
|
|
|
|
|
|
|
|
export const defaultLockOptions = {
|
2019-03-19 13:54:23 +00:00
|
|
|
stale: 30000,
|
|
|
|
onCompromised: err => consola.warn(err)
|
2019-03-03 08:12:46 +00:00
|
|
|
}
|
|
|
|
|
2019-07-10 10:45:49 +00:00
|
|
|
export function getLockOptions (options) {
|
2019-03-03 08:12:46 +00:00
|
|
|
return Object.assign({}, defaultLockOptions, options)
|
|
|
|
}
|
|
|
|
|
2019-07-10 10:45:49 +00:00
|
|
|
export function createLockPath ({ id = 'nuxt', dir, root }) {
|
2019-03-03 08:12:46 +00:00
|
|
|
const sum = hash(`${root}-${dir}`)
|
|
|
|
|
|
|
|
return path.resolve(root, 'node_modules/.cache/nuxt', `${id}-lock-${sum}`)
|
|
|
|
}
|
|
|
|
|
2019-07-10 10:45:49 +00:00
|
|
|
export async function getLockPath (config) {
|
2019-03-03 08:12:46 +00:00
|
|
|
const lockPath = createLockPath(config)
|
|
|
|
|
|
|
|
// the lock is created for the lockPath as ${lockPath}.lock
|
|
|
|
// so the (temporary) lockPath needs to exist
|
|
|
|
await fs.ensureDir(lockPath)
|
|
|
|
|
|
|
|
return lockPath
|
|
|
|
}
|
|
|
|
|
2019-07-10 10:45:49 +00:00
|
|
|
export async function lock ({ id, dir, root, options }) {
|
2019-03-03 08:12:46 +00:00
|
|
|
const lockPath = await getLockPath({ id, dir, root })
|
|
|
|
|
2019-03-23 06:46:00 +00:00
|
|
|
try {
|
|
|
|
const locked = await properlock.check(lockPath)
|
|
|
|
if (locked) {
|
|
|
|
consola.fatal(`A lock with id '${id}' already exists on ${dir}`)
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
consola.debug(`Check for an existing lock with id '${id}' on ${dir} failed`, e)
|
2019-03-03 08:12:46 +00:00
|
|
|
}
|
|
|
|
|
2019-03-23 06:46:00 +00:00
|
|
|
let lockWasCompromised = false
|
|
|
|
let release
|
|
|
|
|
|
|
|
try {
|
|
|
|
options = getLockOptions(options)
|
|
|
|
|
|
|
|
const onCompromised = options.onCompromised
|
|
|
|
options.onCompromised = (err) => {
|
|
|
|
onCompromised(err)
|
|
|
|
lockWasCompromised = true
|
|
|
|
}
|
|
|
|
|
|
|
|
release = await properlock.lock(lockPath, options)
|
|
|
|
} catch (e) {}
|
2019-03-03 08:12:46 +00:00
|
|
|
|
|
|
|
if (!release) {
|
|
|
|
consola.warn(`Unable to get a lock with id '${id}' on ${dir} (but will continue)`)
|
2019-03-23 06:46:00 +00:00
|
|
|
return false
|
2019-03-03 08:12:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!lockPaths.size) {
|
2020-04-14 09:26:40 +00:00
|
|
|
// make sure to always cleanup our temporary lockPaths
|
2019-03-03 08:12:46 +00:00
|
|
|
onExit(() => {
|
|
|
|
for (const lockPath of lockPaths) {
|
|
|
|
fs.removeSync(lockPath)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
lockPaths.add(lockPath)
|
|
|
|
|
2019-07-10 10:45:49 +00:00
|
|
|
return async function lockRelease () {
|
2019-03-23 06:46:00 +00:00
|
|
|
try {
|
|
|
|
await fs.remove(lockPath)
|
|
|
|
lockPaths.delete(lockPath)
|
|
|
|
|
|
|
|
// release as last so the lockPath is still removed
|
|
|
|
// when it fails on a compromised lock
|
|
|
|
await release()
|
|
|
|
} catch (e) {
|
|
|
|
if (!lockWasCompromised || !e.message.includes('already released')) {
|
|
|
|
consola.debug(e)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// proper-lockfile doesnt remove lockDir when lock is compromised
|
|
|
|
// removing it here could cause the 'other' process to throw an error
|
|
|
|
// as well, but in our case its much more likely the lock was
|
|
|
|
// compromised due to mtime update timeouts
|
|
|
|
const lockDir = `${lockPath}.lock`
|
|
|
|
if (await fs.exists(lockDir)) {
|
|
|
|
await fs.remove(lockDir)
|
|
|
|
}
|
|
|
|
}
|
2019-03-03 08:12:46 +00:00
|
|
|
}
|
|
|
|
}
|