mirror of
https://github.com/dzeiocom/dotfiles.git
synced 2025-04-22 19:02:15 +00:00
Initial commit
Signed-off-by: Avior <florian.bouillon@delta-wings.net>
This commit is contained in:
commit
d0f8f945f3
8
.editorconfig
Normal file
8
.editorconfig
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = tab
|
||||||
|
indent_size = 4
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
build/
|
||||||
|
.vscode/
|
||||||
|
backups/
|
||||||
|
node_modules/
|
||||||
|
*.js
|
||||||
|
yarn-error.log
|
||||||
|
package-lock.json
|
20
FileInterface.ts
Normal file
20
FileInterface.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import Requirements from "./Prerequises";
|
||||||
|
|
||||||
|
export default interface FileInterface {
|
||||||
|
displayName?: string,
|
||||||
|
filename: string // name of the file in the internal system **MUST** be unique
|
||||||
|
path?: string // if set it will copy the folder/file from path to filename
|
||||||
|
prereqs?: Requirements|Array<Requirements>
|
||||||
|
commands?: { // if set see below
|
||||||
|
save: string // one command to be launch and the result will be saved internally
|
||||||
|
load: string // one command to be launched on restoration
|
||||||
|
/*
|
||||||
|
{filepath} = path of the internal file
|
||||||
|
(if set on save the result content will be ignored)
|
||||||
|
|
||||||
|
load only (only one can be in):
|
||||||
|
{line} = one line in the internal file (if set the command will be launched for each lines)
|
||||||
|
// NOT IMPLEMENTED: {content} = the whole content of the file
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
161
Functions.ts
Normal file
161
Functions.ts
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
import fsAsync, { promises as fs } from 'fs'
|
||||||
|
import os from 'os'
|
||||||
|
import { exec as execSync } from "child_process";
|
||||||
|
import util from 'util'
|
||||||
|
import readline from 'readline'
|
||||||
|
import Options from './Options';
|
||||||
|
import Logger from './Logger';
|
||||||
|
import Statics from './Statics';
|
||||||
|
|
||||||
|
const { Confirm } = require('enquirer')
|
||||||
|
const exec = util.promisify(execSync)
|
||||||
|
|
||||||
|
export function getUserHome() {
|
||||||
|
return os.homedir()
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function processDir(src: string, dest: string) {
|
||||||
|
try {
|
||||||
|
await fs.mkdir(dest)
|
||||||
|
} catch {/*folder already exist*/}
|
||||||
|
const files = await fs.readdir(src)
|
||||||
|
for (const file of files) {
|
||||||
|
const path = `${src}/${file}`
|
||||||
|
const fileDest = `${dest}/${file}`
|
||||||
|
// console.log(`${src}/${file}`)
|
||||||
|
const stats = await fs.stat(path)
|
||||||
|
if (stats.isDirectory()) {
|
||||||
|
await processDir(path, fileDest)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
await processFile(path, fileDest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function processFile(src: string, dest: string) {
|
||||||
|
try {
|
||||||
|
await fs.access(dest)
|
||||||
|
if (!await confirmOverride(dest)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
/* File don't exist, continue */
|
||||||
|
}
|
||||||
|
|
||||||
|
const folder = dest.substring(0, dest.lastIndexOf("/"))
|
||||||
|
await mkdir(folder)
|
||||||
|
await fs.copyFile(src, dest)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function confirmOverride(filename: string): Promise<boolean> {
|
||||||
|
const doOverride = new Options().getConfig().override
|
||||||
|
if (typeof doOverride === "boolean" ) return doOverride
|
||||||
|
Statics.multibar.stop()
|
||||||
|
clear()
|
||||||
|
const resp = await new Confirm({
|
||||||
|
name: "override",
|
||||||
|
message: `the file ${filename} is gonna be overrided, continue?`
|
||||||
|
}).run()
|
||||||
|
if (!resp) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function mkdir(folder: string) {
|
||||||
|
try {
|
||||||
|
await fs.mkdir(folder, {recursive: true})
|
||||||
|
} catch {
|
||||||
|
/* folder exists */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clear() {
|
||||||
|
console.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function copy(src: string, dest: string) {
|
||||||
|
try {
|
||||||
|
const stats = await fs.stat(src)
|
||||||
|
if (stats.isDirectory()) {
|
||||||
|
await processDir(src, dest)
|
||||||
|
} else {
|
||||||
|
await processFile(src, dest)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Logger.getInstance().prepare(`File/Folder don't exist! ${src}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param command The command to be launched
|
||||||
|
* @param location Location of the internal file
|
||||||
|
* @param isRestoring is the system restoring
|
||||||
|
*/
|
||||||
|
export async function processCommand(command: string, location: string, filename: string/*, isRestoring: boolean = false*/) {
|
||||||
|
const regex = new RegExp(/{(\w+)}/g)
|
||||||
|
// const res = regex.exec(command)
|
||||||
|
let filepathSet = false
|
||||||
|
let lineSet = false
|
||||||
|
let res
|
||||||
|
while ((res = regex.exec(command)) !== null) {
|
||||||
|
console.log(res, res[0], res[1])
|
||||||
|
if (res[1] === "filepath") {
|
||||||
|
command = command.replace(res[0], location)
|
||||||
|
filepathSet = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res[1] === "line") {
|
||||||
|
lineSet = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filepathSet && !await confirmOverride(filename)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lineSet) {
|
||||||
|
const stream = fsAsync.createReadStream(location)
|
||||||
|
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: stream,
|
||||||
|
crlfDelay: Infinity
|
||||||
|
})
|
||||||
|
|
||||||
|
for await (const line of rl) {
|
||||||
|
await exec(command.replace("{line}", line))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const {stdout, stderr} = await exec(command)
|
||||||
|
if (!filepathSet) {
|
||||||
|
const tmp = await fs.mkdtemp("dotfiles")
|
||||||
|
const path = `${tmp}/${filename}`
|
||||||
|
await fs.writeFile(path, stdout)
|
||||||
|
await processFile(path, location)
|
||||||
|
await fs.unlink(path)
|
||||||
|
await fs.rmdir(tmp)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Logger.getInstance().prepare(`Error in ${filename}, Command errored`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function getModules(): Promise<Array<string>> {
|
||||||
|
const res = []
|
||||||
|
let els = await fs.readdir("./modules")
|
||||||
|
for (const el of els) {
|
||||||
|
res.push(
|
||||||
|
el.substr(0, el.length-3)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
export function capitalize(str: string) {
|
||||||
|
return str[0].toUpperCase + str.substr(1).toLowerCase()
|
||||||
|
}
|
26
Logger.ts
Normal file
26
Logger.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
export default class Logger {
|
||||||
|
|
||||||
|
private messages: Array<string> = []
|
||||||
|
|
||||||
|
private static instance?: Logger
|
||||||
|
public static getInstance(): Logger {
|
||||||
|
if (!this.instance) {
|
||||||
|
this.instance = new Logger()
|
||||||
|
}
|
||||||
|
return this.instance
|
||||||
|
}
|
||||||
|
|
||||||
|
public prepare(message: string) {
|
||||||
|
this.messages.push(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
public commit() {
|
||||||
|
for (const message of this.messages) {
|
||||||
|
this.log(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public log(message: string) {
|
||||||
|
console.log(message)
|
||||||
|
}
|
||||||
|
}
|
12
ModuleInterface.ts
Normal file
12
ModuleInterface.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { SingleBar } from "cli-progress";
|
||||||
|
import ListI from "./interfaces/Listr";
|
||||||
|
|
||||||
|
export default interface ModuleInterface {
|
||||||
|
moduleName?: string
|
||||||
|
|
||||||
|
save(): Promise<ListI>
|
||||||
|
load(): Promise<ListI>
|
||||||
|
|
||||||
|
isInstalled(): Promise<boolean>
|
||||||
|
custom(): Promise<boolean>
|
||||||
|
}
|
121
Options.ts
Normal file
121
Options.ts
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
import { getUserHome, getModules } from "./Functions"
|
||||||
|
import fs, {promises as fsp} from "fs"
|
||||||
|
import 'colors'
|
||||||
|
|
||||||
|
const { MultiSelect, Select } = require('enquirer')
|
||||||
|
|
||||||
|
interface p {
|
||||||
|
override?: boolean
|
||||||
|
enabled: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Options {
|
||||||
|
|
||||||
|
private configFolder = `${getUserHome()}/.config/dzeio-dotfiles/`
|
||||||
|
private configFile = `config.yml`
|
||||||
|
|
||||||
|
private location: string
|
||||||
|
|
||||||
|
private config: p
|
||||||
|
|
||||||
|
public constructor() {
|
||||||
|
this.location = `${this.configFolder}/${this.configFile}`
|
||||||
|
try {
|
||||||
|
fs.accessSync(this.location)
|
||||||
|
this.config = JSON.parse(fs.readFileSync(this.location).toString())
|
||||||
|
} catch {
|
||||||
|
fs.mkdirSync(this.configFolder)
|
||||||
|
this.config = this.defaultConfig()
|
||||||
|
this.save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private defaultConfig(): p {
|
||||||
|
return {
|
||||||
|
enabled: [
|
||||||
|
"Dotfiles"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public getConfig(): p {
|
||||||
|
return this.config
|
||||||
|
}
|
||||||
|
|
||||||
|
public setConfig(config: p) {
|
||||||
|
this.config = config
|
||||||
|
}
|
||||||
|
|
||||||
|
public async save() {
|
||||||
|
await fsp.writeFile(this.location, JSON.stringify(this.config))
|
||||||
|
}
|
||||||
|
|
||||||
|
public async manager() {
|
||||||
|
const res = await new Select({
|
||||||
|
name: 'select',
|
||||||
|
message: 'Select an option',
|
||||||
|
choices: [
|
||||||
|
'Quick backup elements',
|
||||||
|
'default file override',
|
||||||
|
'Back'
|
||||||
|
]
|
||||||
|
}).run()
|
||||||
|
switch (res) {
|
||||||
|
case 'Quick backup elements':
|
||||||
|
await this.enabled()
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'default file override':
|
||||||
|
await this.override()
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await this.manager()
|
||||||
|
}
|
||||||
|
|
||||||
|
public async override() {
|
||||||
|
let config = this.getConfig()
|
||||||
|
const res = await new Select({
|
||||||
|
name: 'select',
|
||||||
|
message: 'Default Override action ?',
|
||||||
|
footer: `current: ${this.getConfig().override}`,
|
||||||
|
choices: [
|
||||||
|
{message: "Override", value: true},
|
||||||
|
{message: "Ask", value: undefined},
|
||||||
|
{message: "Skip", value: false}
|
||||||
|
]
|
||||||
|
}).run()
|
||||||
|
config.override = res
|
||||||
|
this.setConfig(config)
|
||||||
|
this.save()
|
||||||
|
}
|
||||||
|
|
||||||
|
public async enabled() {
|
||||||
|
let choices: Array<choice> = []
|
||||||
|
const els = await getModules()
|
||||||
|
let config = this.getConfig()
|
||||||
|
for (const value of els) {
|
||||||
|
choices.push({
|
||||||
|
name: value,
|
||||||
|
value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const t = await new MultiSelect({
|
||||||
|
name: 'enabled',
|
||||||
|
message: 'Select wich one to Backup/Restore when selecting quick'.white,
|
||||||
|
initial: this.getConfig().enabled.filter((el) => els.includes(el)),
|
||||||
|
choices,
|
||||||
|
footer: 'space to select, enter to confirm'
|
||||||
|
}).run()
|
||||||
|
config.enabled = t
|
||||||
|
this.setConfig(config)
|
||||||
|
this.save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface choice {
|
||||||
|
name: string,
|
||||||
|
value: string
|
||||||
|
}
|
6
Prerequises.ts
Normal file
6
Prerequises.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
enum Requirements {
|
||||||
|
INSTALLED = "installed",
|
||||||
|
CUSTOM = "custom"
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Requirements
|
171
SimpleModule.ts
Normal file
171
SimpleModule.ts
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
import ModuleInterface from "./ModuleInterface"
|
||||||
|
import { copy, processCommand } from "./Functions"
|
||||||
|
import FileInterface from "./FileInterface"
|
||||||
|
import Requirements from "./Prerequises"
|
||||||
|
import Logger from "./Logger"
|
||||||
|
import ListI, { ListrInterface } from "./interfaces/Listr"
|
||||||
|
const Listr: ListI = require('listr')
|
||||||
|
|
||||||
|
export default abstract class SimpleModule implements ModuleInterface {
|
||||||
|
|
||||||
|
protected files: FileInterface[] = []
|
||||||
|
|
||||||
|
private installed?: boolean
|
||||||
|
|
||||||
|
private logger = Logger.getInstance()
|
||||||
|
|
||||||
|
abstract moduleName: string
|
||||||
|
|
||||||
|
public constructor(files?: FileInterface[]) {
|
||||||
|
|
||||||
|
if (files) this.files = files
|
||||||
|
}
|
||||||
|
|
||||||
|
public async save(): Promise<ListI> {
|
||||||
|
|
||||||
|
const directory = `./backups/${this.moduleName}`
|
||||||
|
const subTasks: ListrInterface[] = []
|
||||||
|
|
||||||
|
for (const file of this.files) {
|
||||||
|
// this.logger.prepare(file.displayName || file.filename)
|
||||||
|
const location = `${directory}/${file.filename}`
|
||||||
|
|
||||||
|
let doSkip = false
|
||||||
|
if (file.prereqs && !await this.processPrereqs(file)) {
|
||||||
|
doSkip = true
|
||||||
|
}
|
||||||
|
|
||||||
|
subTasks.push({
|
||||||
|
title: file.displayName || file.filename,
|
||||||
|
task: async (ctx, task) => {
|
||||||
|
// await wait(1000)
|
||||||
|
// console.log(task.)
|
||||||
|
// this.logger.prepare(task as any)
|
||||||
|
if (file.path !== undefined) {
|
||||||
|
await copy(file.path, location)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (file.commands !== undefined) {
|
||||||
|
// console.log(file.commands.save, location, file.filename)
|
||||||
|
await processCommand(file.commands.save, location, file.filename)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
},
|
||||||
|
skip: () => {
|
||||||
|
if (doSkip) {
|
||||||
|
const resp = `${this.moduleName} is not installed, skipping ${file.displayName || file.filename}`
|
||||||
|
this.logger.prepare(resp)
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return new Listr(subTasks, {concurrent: true})
|
||||||
|
}
|
||||||
|
|
||||||
|
public async load(): Promise<ListI> {
|
||||||
|
const directory = `./backups/${this.moduleName}`
|
||||||
|
const subTasks: ListrInterface[] = []
|
||||||
|
|
||||||
|
for (const file of this.files) {
|
||||||
|
const location = `${directory}/${file.filename}`
|
||||||
|
let doSkip = false
|
||||||
|
if (file.prereqs && !await this.processPrereqs(file)) {
|
||||||
|
doSkip = true
|
||||||
|
}
|
||||||
|
subTasks.push({
|
||||||
|
title: file.displayName || file.filename,
|
||||||
|
skip: () => {
|
||||||
|
if (doSkip) {
|
||||||
|
const resp = `${this.moduleName} is not installed, skipping ${file.displayName || file.filename}`
|
||||||
|
this.logger.prepare(resp)
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
},
|
||||||
|
task: async () => {
|
||||||
|
if (file.path !== undefined) {
|
||||||
|
await copy(file.path, location)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (file.commands !== undefined) {
|
||||||
|
await processCommand(file.commands.load, location, file.filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (file.prereqs && !this.processPrereqs(file)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Listr(subTasks, {concurrent: true})
|
||||||
|
}
|
||||||
|
|
||||||
|
public async isInstalled(): Promise<boolean> {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
public async custom(): Promise<boolean> {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private async processPrereqs(file: FileInterface): Promise<boolean> {
|
||||||
|
let pre = file.prereqs
|
||||||
|
if (typeof pre === "undefined") return true
|
||||||
|
if (typeof pre === "string") {
|
||||||
|
pre = [pre]
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const req of pre) {
|
||||||
|
let res = false
|
||||||
|
switch (req) {
|
||||||
|
|
||||||
|
case Requirements.INSTALLED:
|
||||||
|
if (typeof this.installed === "undefined") {
|
||||||
|
this.installed = await this.isInstalled()
|
||||||
|
}
|
||||||
|
res = this.installed
|
||||||
|
if (!res) {
|
||||||
|
// this.logger.prepare(`${this.moduleName} is not installed, skipping ${file.displayName || file.filename}`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case Requirements.CUSTOM:
|
||||||
|
res = await this.custom()
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!res) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function wait(ms: number): Promise<void> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
// resolve(); return
|
||||||
|
setTimeout(() => {resolve()}, ms)
|
||||||
|
})
|
||||||
|
}
|
20
Statics.ts
Normal file
20
Statics.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { Presets, MultiBar } from "cli-progress";
|
||||||
|
|
||||||
|
export default class Statics {
|
||||||
|
|
||||||
|
private static _multibar: MultiBar
|
||||||
|
|
||||||
|
public static set multibar(bar: MultiBar) {
|
||||||
|
this._multibar = bar
|
||||||
|
}
|
||||||
|
|
||||||
|
public static get multibar() {
|
||||||
|
if (!this._multibar) this._multibar = new MultiBar({
|
||||||
|
format: `{obj}: {percentage}% (${'{bar}'.cyan}) {action}: {el}`,
|
||||||
|
clearOnComplete: false,
|
||||||
|
hideCursor: true,
|
||||||
|
synchronousUpdate: false
|
||||||
|
}, Presets.shades_classic)
|
||||||
|
return this._multibar
|
||||||
|
}
|
||||||
|
}
|
115
cli.ts
Normal file
115
cli.ts
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
console.clear()
|
||||||
|
|
||||||
|
import Logger from './Logger'
|
||||||
|
import ModuleInterface from './ModuleInterface'
|
||||||
|
import Options from './Options'
|
||||||
|
import { getModules } from './Functions'
|
||||||
|
import ListI, { ListrInterface } from './interfaces/Listr'
|
||||||
|
|
||||||
|
const logger = Logger.getInstance()
|
||||||
|
const options = new Options()
|
||||||
|
|
||||||
|
function getSelect(Select: any) {
|
||||||
|
return new Select({
|
||||||
|
name: "dotfiles",
|
||||||
|
message: "Select the action to run",
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
name: 'quick-save',
|
||||||
|
message: 'Quick Save'
|
||||||
|
},{
|
||||||
|
name: 'quick-restore',
|
||||||
|
message: 'Quick Restore'
|
||||||
|
},{
|
||||||
|
name: 'save',
|
||||||
|
message: 'Save'
|
||||||
|
},{
|
||||||
|
name: 'restore',
|
||||||
|
message: 'Restore'
|
||||||
|
},{
|
||||||
|
name: 'options',
|
||||||
|
message: 'Options'
|
||||||
|
},{
|
||||||
|
name: 'exit',
|
||||||
|
message: 'Exit'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveModule(moduleName: string, save = false): Promise<ListI> {
|
||||||
|
const md = `./modules/${moduleName}`
|
||||||
|
const module: ModuleInterface = new (await require(md).default)(moduleName)
|
||||||
|
if (save) {
|
||||||
|
return module.save()
|
||||||
|
} else {
|
||||||
|
return module.load()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function bootstrap(): Promise<never> {
|
||||||
|
const { Select, MultiSelect } = await require('enquirer');
|
||||||
|
const Listr: ListI = await require('listr')
|
||||||
|
|
||||||
|
let response: string = ""
|
||||||
|
try {
|
||||||
|
response = await getSelect(Select).run()
|
||||||
|
} catch {
|
||||||
|
process.exit(process.exitCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = options.getConfig()
|
||||||
|
const modules = await getModules()
|
||||||
|
const quick = config.enabled.filter((el) => modules.includes(el))
|
||||||
|
|
||||||
|
let isSaving = false
|
||||||
|
|
||||||
|
const t: ListrInterface[] = []
|
||||||
|
switch (response) {
|
||||||
|
case "quick-save":
|
||||||
|
isSaving = true
|
||||||
|
case "quick-restore":
|
||||||
|
for (const mod of quick) {
|
||||||
|
t.push({
|
||||||
|
title: mod,
|
||||||
|
task: async () => saveModule(mod, isSaving)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "save":
|
||||||
|
isSaving = true
|
||||||
|
case "restore":
|
||||||
|
const res = await new MultiSelect({
|
||||||
|
name: "mutiSelect",
|
||||||
|
message: `Select what to ${response}`,
|
||||||
|
choices: modules
|
||||||
|
}).run()
|
||||||
|
if (res.length === 0) await bootstrap()
|
||||||
|
for (const mod of res) {
|
||||||
|
t.push({
|
||||||
|
title: mod,
|
||||||
|
task: async () => saveModule(mod, isSaving)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case "options":
|
||||||
|
await options.manager()
|
||||||
|
await bootstrap()
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
await new Listr(t, {concurrent: false}).run()
|
||||||
|
|
||||||
|
|
||||||
|
Logger.getInstance().commit()
|
||||||
|
|
||||||
|
return process.exit(process.exitCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
bootstrap()
|
||||||
|
} catch(e) {
|
||||||
|
logger.log("An error occured 😱")
|
||||||
|
console.log(e)
|
||||||
|
}
|
19
interfaces/Listr.ts
Normal file
19
interfaces/Listr.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
export default interface ListI {
|
||||||
|
new (list: ListrInterface[], options?: ListrOptions): ListI
|
||||||
|
run: () => Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ListrOptions {
|
||||||
|
concurrent?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ListrInterface {
|
||||||
|
title: string,
|
||||||
|
enabled?: (ctx: any) => boolean,
|
||||||
|
skip?: (ctx?: any) => string|undefined|boolean|Promise<string|undefined|boolean>,
|
||||||
|
task: (ctx?: any, task?: Task) => (void|string|ListI|Promise<void|string|ListI>)
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Task {
|
||||||
|
skip: (str: string) => boolean|string|undefined
|
||||||
|
}
|
14
modules/Fish.ts
Normal file
14
modules/Fish.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import SimpleModule from "../SimpleModule";
|
||||||
|
import { getUserHome } from "../Functions";
|
||||||
|
import FileInterface from "../FileInterface";
|
||||||
|
|
||||||
|
export default class Fish extends SimpleModule {
|
||||||
|
files: FileInterface[] = [
|
||||||
|
{
|
||||||
|
displayName: "Functions",
|
||||||
|
filename: "functions",
|
||||||
|
path: `${getUserHome()}/.config/fish/functions`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
moduleName = "Fish"
|
||||||
|
}
|
14
modules/HyperJS.ts
Normal file
14
modules/HyperJS.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import SimpleModule from "../SimpleModule";
|
||||||
|
import { getUserHome } from "../Functions";
|
||||||
|
import FileInterface from "../FileInterface";
|
||||||
|
|
||||||
|
export default class HyperJS extends SimpleModule {
|
||||||
|
files: FileInterface[] = [
|
||||||
|
{
|
||||||
|
displayName: "Config",
|
||||||
|
filename: "hyper.js.bak",
|
||||||
|
path: `${getUserHome()}/.hyper.js`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
moduleName = "HyperJS"
|
||||||
|
}
|
13
modules/Nano.ts
Normal file
13
modules/Nano.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import SimpleModule from "../SimpleModule"
|
||||||
|
import FileInterface from "../FileInterface"
|
||||||
|
import { getUserHome } from "../Functions"
|
||||||
|
|
||||||
|
export default class Nano extends SimpleModule {
|
||||||
|
files: FileInterface[] = [
|
||||||
|
{
|
||||||
|
filename: "Nanorc",
|
||||||
|
path: `${getUserHome()}/.nanorc`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
moduleName = "Nano"
|
||||||
|
}
|
32
modules/OhMyFish.ts
Normal file
32
modules/OhMyFish.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import SimpleModule from "../SimpleModule"
|
||||||
|
import FileInterface from "../FileInterface"
|
||||||
|
import Requirements from "../Prerequises"
|
||||||
|
import { execSync } from "child_process"
|
||||||
|
|
||||||
|
export default class OhMyFish extends SimpleModule {
|
||||||
|
files: FileInterface[] = [
|
||||||
|
{
|
||||||
|
filename: "Extensions",
|
||||||
|
prereqs: Requirements.INSTALLED,
|
||||||
|
commands: {
|
||||||
|
save: `${this.findCommand()} "omf list | tr '\\t' '\\n"' | grep '^[a-z-]' > {filepath}`,
|
||||||
|
load: `${this.findCommand()} "omg install {line}"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
moduleName = "OhMyFish"
|
||||||
|
|
||||||
|
private findCommand(): string {
|
||||||
|
try {
|
||||||
|
execSync('which fish 2> /dev/null')
|
||||||
|
execSync('which omf 2> /dev/null')
|
||||||
|
return 'fish -c'
|
||||||
|
} catch {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async isInstalled(): Promise<boolean> {
|
||||||
|
return this.findCommand() !== ""
|
||||||
|
}
|
||||||
|
}
|
79
modules/VSCode.ts
Normal file
79
modules/VSCode.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import SimpleModule from "../SimpleModule";
|
||||||
|
import fsSync, { promises as fs } from "fs";
|
||||||
|
import { getUserHome } from "../Functions";
|
||||||
|
|
||||||
|
import { execSync } from "child_process";
|
||||||
|
import FileInterface from "../FileInterface";
|
||||||
|
import Requirements from "../Prerequises";
|
||||||
|
import 'colors'
|
||||||
|
|
||||||
|
export default class VSCode extends SimpleModule {
|
||||||
|
files: FileInterface[] = [
|
||||||
|
{
|
||||||
|
displayName: "Settings",
|
||||||
|
filename: "settings.json",
|
||||||
|
path: `${this.findVSCodeFolder()}/User/settings.json`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: "Keybindings",
|
||||||
|
filename: "keybindings.json",
|
||||||
|
path: `${this.findVSCodeFolder()}/User/keybindings.json`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: "Snippets",
|
||||||
|
filename: "snippets",
|
||||||
|
path: `${this.findVSCodeFolder()}/User/snippets`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: "Extensions",
|
||||||
|
filename: "extensions.txt",
|
||||||
|
prereqs: Requirements.INSTALLED,
|
||||||
|
commands: {
|
||||||
|
save: `${this.findCommand()} --list-extensions`,
|
||||||
|
load: `${this.findCommand()} --install-extension {line}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
public moduleName: string
|
||||||
|
|
||||||
|
public constructor() {
|
||||||
|
super()
|
||||||
|
const filename = __filename.split("/")
|
||||||
|
this.moduleName = filename[filename.length - 1].replace(".ts", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
private static commands = ["vscodium", "vscode", "code", "codium"]
|
||||||
|
|
||||||
|
private findVSCodeFolder(): string {
|
||||||
|
const possibilities = [
|
||||||
|
`${getUserHome()}/.config/Code`,
|
||||||
|
`${getUserHome()}/.config/VSCodium`,
|
||||||
|
`${getUserHome()}/.config/Code - OSS`,
|
||||||
|
]
|
||||||
|
for (const pos of possibilities) {
|
||||||
|
try {
|
||||||
|
fsSync.accessSync(pos)
|
||||||
|
return pos
|
||||||
|
} catch {continue}
|
||||||
|
}
|
||||||
|
return possibilities[0] // default to VSCode folder
|
||||||
|
}
|
||||||
|
|
||||||
|
private findCommand(): string {
|
||||||
|
for (const cmd of VSCode.commands) {
|
||||||
|
try {
|
||||||
|
execSync(`which ${cmd}`)
|
||||||
|
return cmd
|
||||||
|
} catch {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
public async isInstalled(): Promise<boolean> {
|
||||||
|
return this.findCommand() !== ""
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
13
modules/Yarn.ts
Normal file
13
modules/Yarn.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import SimpleModule from "../SimpleModule";
|
||||||
|
import { getUserHome } from "../Functions";
|
||||||
|
import FileInterface from "../FileInterface";
|
||||||
|
|
||||||
|
export default class Yarn extends SimpleModule {
|
||||||
|
files: FileInterface[] = [
|
||||||
|
{
|
||||||
|
filename: "yarnrc",
|
||||||
|
path: `${getUserHome()}/.yarnrc`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
moduleName = "Yarn"
|
||||||
|
}
|
30
package.json
Normal file
30
package.json
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "dotfiles",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"main": "build/cli.js",
|
||||||
|
"bin": {
|
||||||
|
"dotfiles": "./build/cli.js"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"private": false,
|
||||||
|
"scripts": {
|
||||||
|
"start": "ts-node index.ts",
|
||||||
|
"build": "tsc"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"clear": "^0.1.0",
|
||||||
|
"cli-color": "^2.0.0",
|
||||||
|
"cli-progress": "^3.3.1",
|
||||||
|
"colors": "^1.4.0",
|
||||||
|
"enquirer": "^2.3.2",
|
||||||
|
"listr": "^0.14.3",
|
||||||
|
"ora": "^4.0.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/cli-progress": "^3.4.0",
|
||||||
|
"@types/node": "^13.1.1",
|
||||||
|
"ts-node": "^8.5.4",
|
||||||
|
"ts-node-dev": "^1.0.0-pre.44",
|
||||||
|
"typescript": "^3.7.4"
|
||||||
|
}
|
||||||
|
}
|
72
tsconfig.json
Normal file
72
tsconfig.json
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
{
|
||||||
|
"include": ["cli.ts", "modules/*"],
|
||||||
|
"exclude": [
|
||||||
|
".vscode/",
|
||||||
|
"node_modules/",
|
||||||
|
"backups/"
|
||||||
|
],
|
||||||
|
"compilerOptions": {
|
||||||
|
/* Basic Options */
|
||||||
|
// "incremental": true, /* Enable incremental compilation */
|
||||||
|
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
|
||||||
|
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
|
||||||
|
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||||
|
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||||
|
// "checkJs": true, /* Report errors in .js files. */
|
||||||
|
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||||
|
// "declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||||
|
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||||
|
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
||||||
|
// "outFile": "./dotfiles.js", /* Concatenate and emit output to single file. */
|
||||||
|
"outDir": "./build/", /* Redirect output structure to the directory. */
|
||||||
|
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||||
|
// "composite": true, /* Enable project compilation */
|
||||||
|
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||||
|
// "removeComments": true, /* Do not emit comments to output. */
|
||||||
|
// "noEmit": true, /* Do not emit outputs. */
|
||||||
|
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||||
|
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||||
|
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||||
|
|
||||||
|
/* Strict Type-Checking Options */
|
||||||
|
"strict": true, /* Enable all strict type-checking options. */
|
||||||
|
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||||
|
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||||
|
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||||
|
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||||
|
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||||
|
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||||
|
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||||
|
|
||||||
|
/* Additional Checks */
|
||||||
|
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||||
|
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||||
|
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||||
|
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||||
|
|
||||||
|
/* Module Resolution Options */
|
||||||
|
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||||
|
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||||
|
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||||
|
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||||
|
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||||
|
// "types": [], /* Type declaration files to be included in compilation. */
|
||||||
|
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||||
|
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||||
|
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||||
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
|
|
||||||
|
/* Source Map Options */
|
||||||
|
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||||
|
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||||
|
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||||
|
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||||
|
|
||||||
|
/* Experimental Options */
|
||||||
|
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||||
|
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||||
|
|
||||||
|
/* Advanced Options */
|
||||||
|
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user