#!/usr/bin/env zx import osInfo from 'linux-os-info' let installOrder, osArch, osId, osType, pkgs, sysType async function getOsInfo() { return osInfo({ mode: 'sync' }) } function getPkgData(pref, pkg, installer) { if (installer) { if (pkg[`${pref}:${installer}:${osId}:${osArch}`]) { return `${pref}:${installer}:${osId}:${osArch}` // Handles case like `_bin:pipx:debian:x64:` } else if (pkg[`${pref}:${osId}:${installer}:${osArch}`]) { return `${pref}:${osId}:${installer}:${osArch}` // Handles case like `_bin:debian:pipx:x64:` } else if (pkg[`${pref}:${installer}:${osType}:${osArch}`]) { return `${pref}:${installer}:${osType}:${osArch}` // Handles case like `_bin:pipx:windows:x64:` } else if (pkg[`${pref}:${osType}:${installer}:${osArch}`]) { return `${pref}:${osType}:${installer}:${osArch}` // Handles case like `_bin:windows:pipx:x64:` } else if (pkg[`${pref}:${installer}:${osId}`]) { return `${pref}:${installer}:${osType}` // Handles case like `_bin:pipx:fedora:` } else if (pkg[`${pref}:${osId}:${installer}`]) { return `${pref}:${osType}:${installer}` // Handles case like `_bin:fedora:pipx:` } else if (pkg[`${pref}:${installer}:${osType}`]) { return `${pref}:${installer}:${osType}` // Handles case like `_bin:pipx:darwin:` } else if (pkg[`${pref}:${osType}:${installer}`]) { return `${pref}:${osType}:${installer}` // Handles case like `_bin:darwin:pipx:` } else if (pkg[`${pref}:${installer}`]) { return `${pref}` // Handles case like `_bin:pipx:` } else if (pkg[`${pref}`]) { return `${pref}` // Handles case like `_bin:` } else { return false } } else { if (pkg[`${pref}:${osId}:${osArch}`]) { return `${pref}:${osId}:${osArch}` // Handles case like `pipx:debian:x64:` } else if (pkg[`${pref}:${osType}:${osArch}`]) { return `${pref}:${osType}:${osArch}` // Handles case like `pipx:windows:x64:` } else if (pkg[`${pref}:${osId}`]) { return `${pref}:${osType}` // Handles case like `pipx:fedora:` } else if (pkg[`${pref}:${osType}`]) { return `${pref}:${osType}` // Handles case like `pipx:darwin:` } else if (pkg[`${pref}`]) { return `${pref}` // Handles case like `pipx:` } else { return false } } } async function getSoftwareDefinitions() { try { return YAML.parse(fs.readFileSync(`${os.homedir()}/.local/share/chezmoi/software.yml`, 'utf8')) } catch (e) { throw Error('Failed to load software definitions', e) } } async function getSystemType() { if (process.platform === "win32") { return "windows" } else if (process.platform === "linux") { if (which.sync('apk')) { return "apk" } else if (which.sync('apt-get')) { return "apt" } else if (which.sync('dnf')) { return "dnf" } else if (which.sync('pacman')) { return "pacman" } else if (which.sync('zypper')) { return "zypper" } else { return "linux" } } else { return process.platform } } async function getBrewFormulas() { await $`brew list -1 > brew-list` return fs.readFileSync('./brew-list').toString().split('\n') } async function getGems() { await $`gem query --local > gem-list` return fs.readFileSync('./gem-list').toString().split('\n').map(x => x.split(' ')[0]) } async function getVoltaInstalls() { await $`volta list --format plain > volta-list` return fs.readFileSync('./volta-list').toString().split('\n').filter(x => x).map(x => x.split(' ')[1].split('@')[0]) } async function getCrates() { await $`cargo install --list | awk '/^[[:alnum:]]/ {print $1}' > cargo-list` return fs.readFileSync('./cargo-list').toString().split('\n') } async function getPipxPackages() { await $`pipx list --short > pipx-list` return fs.readFileSync('./pipx-list').toString().split('\n').map(x => x.split(' ')[0]) } async function getPips() { await $`pip3 list > pip-list` return fs.readFileSync('./pip-list').toString().split('\n').map(x => x.split(' ')[0]) } function expandDeps(keys) { for (const i of keys) { for (const pref of installOrder[sysType]) { const installKey = getPkgData(pref, pkgs[i], false) if (installKey) { const installType = installKey.split(':')[0] const depsKey = getPkgData('_deps', pkgs[i], installType) if (depsKey) { const deps = typeof pkgs[i][depsKey] === 'string' ? [pkgs[i][depsKey]] : pkgs[i][depsKey] return [...keys, ...expandDeps(deps)] } } } return [...keys] } return [...keys] } async function bundleInstall(brews, casks) { const lines = [] for (const cask of casks) { lines.push(`cask "${cask}"`) } for (const brew of brews) { lines.push(`brew "${brew}"`) } fs.writeFileSync('Brewfile', lines.join('\n')) await $`brew bundle --file Brewfile` } async function installPackages(pkgInstructions) { const combined = {} const promises = [] for (const option of installOrder[sysType]) { combined[option] = pkgInstructions.filter(x => x.installType === option) } if (combined.brew || combined.cask) { promises.push(bundleInstall(combined.brew ? combined.brew : [], combined.cask ? combined.cask : [])) } if (combined.cargo) { for (const pkg of combined.cargo) { promises.push($`cargo install ${pkg}`) } } if (combined.go) { for (const pkg of combined.go) { promises.push($`go install ${pkg}`) } } if (combined.npm) { for (const pkg of combined.npm) { promises.push($`volta install ${pkg}`) } } if (combined.pacman) { const pacmanPkgs = combined.pacman.map(x => ) promises.push($`sudo pacman -Sy --noconfirm --needed ${combined.pacman.map}`) } if (combined.pipx) { for (const pkg of combined.pipx) { promises.push($`pipx install ${pkg}`) } } console.log(combinedInstructions) } async function main() { cd(await $`mktemp -d`) const initData = await Promise.all([ getOsInfo(), getSoftwareDefinitions(), getSystemType(), getBrewFormulas(), getGems(), getVoltaInstalls(), getCrates(), getPipxPackages(), getPips() ]) osArch = initData[0].arch osId = process.platform === 'win32' ? 'win32' : (process.platform === 'linux' ? initData[0].id : process.platform) osType = process.platform === 'win32' ? 'windows' : process.platform pkgs = initData[1].softwarePackages sysType = initData[2] installOrder = initData[1].installerPreference const managerLists = { brew: initData[3], cargo: initData[6], gem: initData[4], npm: initData[5], pip: initData[8], pipx: initData[7] } const installKeys = argv._ const installInstructions = Object.keys(pkgs) .filter(i => expandDeps(installKeys).includes(i)) .map(i => { for (const pref of installOrder[sysType]) { const installKey = getPkgData(pref, pkgs[i], false) if (installKey) { return { ...pkgs[i], listKey: i, installKey, installType: installKey.split(':')[0], installList: typeof pkgs[i][installKey] === 'string' ? [pkgs[i][installKey]] : pkgs[i][installKey] } } } return { ...pkgs[i], listKey: i, installKey: false, installType: false, installList: [] } }) .filter(x => x.installKey) .filter(x => { // Filter out packages already installed by by package managers return Object.keys(managerLists).includes(x.installType) }) .filter(x => { // Filter out macOS apps that already have a _app installed if (x.installType === 'cask') { const appField = getPkgData('_app', x, x.installType) if (fs.existsSync(`/Applications/${x[appField]}`) || fs.existsSync(`${os.homedir()}/Applications/${x[appField]}`)) { return false } } return true }) .filter(async x => { // Filter out packages that already have a bin in the PATH const binField = getPkgData('_bin', x, x.installType) const binCheck = x[binField] && await which(x[binField], { nothrow: true }) return binField ? binCheck : true }) .filter(async x => { // Filter out packages that do not pass _when check const whenField = getPkgData('_when', x, x.installType) const whenCheck = x[whenField] && await $`${x[whenField]}`.exitCode == 0 return whenField ? whenCheck : true }) await installPackages(installInstructions) //.filter(x => x.installKey) console.log('install instructions', installInstructions) } main()