@gutenye/script.js 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -0
- package/package.json +65 -0
- package/src/app.ts +25 -0
- package/src/command.ts +51 -0
- package/src/csv.ts +12 -0
- package/src/exit.ts +10 -0
- package/src/fileSystem.ts +17 -0
- package/src/mixins.ts +19 -0
- package/src/script.ts +53 -0
- package/src/spawn.ts +23 -0
- package/src/types/global.d.ts +8 -0
- package/src/ui/index.ts +1 -0
- package/src/ui/table.ts +35 -0
- package/src/utils/fs.ts +226 -0
- package/src/utils/index.ts +1 -0
- package/tsconfig.json +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# 🌟 Script.js 🌟
|
|
2
|
+
|
|
3
|
+
[](https://github.com/gutenye/script.js) [](https://www.npmjs.com/package/@gutenye/script.js) [](https://github.com/gutenye/script.js/blob/main/LICENSE) [](https://github.com/gutenye/script.js#-contribute)
|
|
4
|
+
|
|
5
|
+
Write shell scripts in JavaScript
|
|
6
|
+
|
|
7
|
+
**Show your ❤️ and support by starring this project and following the author, [Guten Ye](https://github.com/gutenye)!**
|
|
8
|
+
|
|
9
|
+
## 🌟 Features
|
|
10
|
+
|
|
11
|
+
- **Javascript**: ..
|
|
12
|
+
- **Autocomplete**: ..
|
|
13
|
+
- **Fast**: ..
|
|
14
|
+
|
|
15
|
+
## 📖 Documentation
|
|
16
|
+
|
|
17
|
+
- [Getting Started](./docs/Getting%20Started.md)
|
|
18
|
+
- [Completion](./docs/Completion.md)
|
|
19
|
+
|
|
20
|
+
## 🤝 Contribute
|
|
21
|
+
|
|
22
|
+
We welcome contributions from the community! Whether it’s reporting bugs, suggesting features, or submitting pull requests, your help is appreciated.
|
|
23
|
+
|
|
24
|
+
1. Fork the Repository
|
|
25
|
+
2. Open a Pull Request on Github
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
Thank you for using Script.js! 🔐 ✨ If you found it helpful, please ⭐️ star the project ️️⭐ on GitHub. If you have any questions or encounter issues, please refer to the documentation or report an issue on GitHub.
|
|
30
|
+
|
|
31
|
+
**Thanks to all the people who contribute:**
|
|
32
|
+
|
|
33
|
+
[](https://github.com/gutenye/script.js/graphs/contributors)
|
|
34
|
+
|
|
35
|
+
[⬆ Back to top ⬆](#readme)
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gutenye/script.js",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Write shell scripts in JavaScript",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"shell",
|
|
7
|
+
"script",
|
|
8
|
+
"bash",
|
|
9
|
+
"bin",
|
|
10
|
+
"binary",
|
|
11
|
+
"child",
|
|
12
|
+
"process",
|
|
13
|
+
"exec",
|
|
14
|
+
"execute",
|
|
15
|
+
"invoke",
|
|
16
|
+
"call",
|
|
17
|
+
"spawn",
|
|
18
|
+
"zx",
|
|
19
|
+
"bunshell"
|
|
20
|
+
],
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"repository": "github:gutenye/script.js",
|
|
23
|
+
"type": "module",
|
|
24
|
+
"files": [
|
|
25
|
+
"src",
|
|
26
|
+
"tsconfig.json",
|
|
27
|
+
"build",
|
|
28
|
+
"!**/__tests__"
|
|
29
|
+
],
|
|
30
|
+
"bin": {
|
|
31
|
+
"gutenye-script.js": "src/script.ts"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"test": "bun test",
|
|
35
|
+
"lint": "biome check --fix",
|
|
36
|
+
"lint:ci": "biome ci --reporter=github",
|
|
37
|
+
"build": "rm -rf build; tsc --project tsconfig.build.json; tsc-alias --project tsconfig.build.json"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@gutenye/commander-completion-carapace": "^1.0.3",
|
|
44
|
+
"chalk": "^5.3.0",
|
|
45
|
+
"commander": "^12.1.0",
|
|
46
|
+
"csv-parse": "^5.5.6",
|
|
47
|
+
"lodash-es": "^4.17.21",
|
|
48
|
+
"table": "^6.8.2",
|
|
49
|
+
"tiny-invariant": "^1.3.3",
|
|
50
|
+
"yaml": "^2.6.0",
|
|
51
|
+
"zx": "^8.2.1"
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"typescript": "^5.7.2"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@biomejs/biome": "1.9.4",
|
|
58
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
59
|
+
"@semantic-release/git": "^10.0.1",
|
|
60
|
+
"@types/bun": "latest",
|
|
61
|
+
"@types/lodash-es": "^4.17.12",
|
|
62
|
+
"conventional-changelog-conventionalcommits": "^8.0.0",
|
|
63
|
+
"tsc-alias": "^1.8.10"
|
|
64
|
+
}
|
|
65
|
+
}
|
package/src/app.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Argument, Option, program } from './command'
|
|
2
|
+
|
|
3
|
+
const [bunPath, scriptJsPath, scriptPath, ...args] = process.argv
|
|
4
|
+
|
|
5
|
+
export const app = program
|
|
6
|
+
|
|
7
|
+
app.Argument = Argument
|
|
8
|
+
app.Option = Option
|
|
9
|
+
|
|
10
|
+
export async function start() {
|
|
11
|
+
if (!scriptPath) {
|
|
12
|
+
echo('Error: missing script path, usage: gutenye-script.js <script>')
|
|
13
|
+
process.exit(1)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
await import(scriptPath)
|
|
17
|
+
|
|
18
|
+
const promise = program.installCompletion()
|
|
19
|
+
if (args.length === 0) {
|
|
20
|
+
// wait for file write operation completed before app print hellp and exit in parse()
|
|
21
|
+
await promise
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
program.parse(args, { from: 'user' })
|
|
25
|
+
}
|
package/src/command.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Command as BaseCommand } from '@gutenye/commander-completion-carapace'
|
|
2
|
+
|
|
3
|
+
export * from '@gutenye/commander-completion-carapace'
|
|
4
|
+
|
|
5
|
+
export class Command extends BaseCommand {
|
|
6
|
+
createCommand(name) {
|
|
7
|
+
return new Command(name)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async invoke(text, ...args) {
|
|
11
|
+
if (args.length === 0) {
|
|
12
|
+
const args = text.split(/ +/)
|
|
13
|
+
return program.parseAsync(args, { from: 'user' })
|
|
14
|
+
} else {
|
|
15
|
+
return program._findCommand(text)._actionHandler(...args)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
actionLinux(fn) {
|
|
20
|
+
this._actionLinux = fn
|
|
21
|
+
this._actionOS()
|
|
22
|
+
return this
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
actionMac(fn) {
|
|
26
|
+
this._actionMac = fn
|
|
27
|
+
this._actionOS()
|
|
28
|
+
return this
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
actionWin(fn) {
|
|
32
|
+
this._actionWin = fn
|
|
33
|
+
this._actionOS()
|
|
34
|
+
return this
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
_actionOS(fn) {
|
|
38
|
+
this.action((...args) => {
|
|
39
|
+
switch (process.platform) {
|
|
40
|
+
case 'linux':
|
|
41
|
+
return this._actionLinux?.(...args)
|
|
42
|
+
case 'darwin':
|
|
43
|
+
return this._actionMac?.(...args)
|
|
44
|
+
case 'win32':
|
|
45
|
+
return this._actionWin?.(...args)
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const program = new Command()
|
package/src/csv.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as csv from 'csv-parse/sync'
|
|
2
|
+
|
|
3
|
+
// csv.parse(text, { delimiter: ','*, columns: true* })
|
|
4
|
+
// columns: true -> [{column: value}]
|
|
5
|
+
// columns: false -> [[column], [value]]
|
|
6
|
+
export function parse(text, options = {}) {
|
|
7
|
+
const newOptions = {
|
|
8
|
+
columns: true,
|
|
9
|
+
...options,
|
|
10
|
+
}
|
|
11
|
+
return csv.parse(text, newOptions)
|
|
12
|
+
}
|
package/src/exit.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// exitWithError(message, [more])
|
|
2
|
+
export function exitWithError(message, more) {
|
|
3
|
+
// for comletion <Tab> display error message: 1) no prefix: '\n, it uses first line 2) use console.error
|
|
4
|
+
let text = colors.red.bold(`Error: ${message}`)
|
|
5
|
+
if (more) {
|
|
6
|
+
text += `\n${more}`
|
|
7
|
+
}
|
|
8
|
+
console.error(text)
|
|
9
|
+
process.exit(1)
|
|
10
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { fs } from '#/utils'
|
|
2
|
+
|
|
3
|
+
export { fs }
|
|
4
|
+
|
|
5
|
+
export const cp = fs.copy
|
|
6
|
+
|
|
7
|
+
export const mv = fs.move
|
|
8
|
+
|
|
9
|
+
export const rm = fs.remove
|
|
10
|
+
|
|
11
|
+
export const mkdir = fs.mkdirp
|
|
12
|
+
|
|
13
|
+
export const ls = glob
|
|
14
|
+
|
|
15
|
+
export const ln = fs.ln
|
|
16
|
+
|
|
17
|
+
export const lns = fs.symlink
|
package/src/mixins.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export async function mixins(...names) {
|
|
2
|
+
const errorNames = []
|
|
3
|
+
for (const name of names) {
|
|
4
|
+
try {
|
|
5
|
+
// TODO: use env to config
|
|
6
|
+
await import(`${process.env.HOME}/bin.src/mixins/${name}`)
|
|
7
|
+
} catch (error) {
|
|
8
|
+
if (error.message.match(/Cannot find module/)) {
|
|
9
|
+
errorNames.push(name)
|
|
10
|
+
} else {
|
|
11
|
+
throw error
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
if (errorNames.length > 0) {
|
|
16
|
+
console.error(`Error: [mixins] not found: ${errorNames.join(', ')}`)
|
|
17
|
+
process.exit(1)
|
|
18
|
+
}
|
|
19
|
+
}
|
package/src/script.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import 'zx/globals'
|
|
4
|
+
|
|
5
|
+
// Variables
|
|
6
|
+
globalThis.HOME = os.homedir()
|
|
7
|
+
globalThis.PWD = process.cwd()
|
|
8
|
+
globalThis.ENV = process.env
|
|
9
|
+
|
|
10
|
+
// App
|
|
11
|
+
import { app, start } from './app'
|
|
12
|
+
globalThis.app = app
|
|
13
|
+
|
|
14
|
+
// Mixins
|
|
15
|
+
import { mixins } from './mixins'
|
|
16
|
+
globalThis.mixins = mixins
|
|
17
|
+
|
|
18
|
+
// Spawn
|
|
19
|
+
import { $, $l, $t } from './spawn'
|
|
20
|
+
globalThis.$ = $
|
|
21
|
+
globalThis.$t = $t
|
|
22
|
+
globalThis.$l = $l
|
|
23
|
+
|
|
24
|
+
// Error
|
|
25
|
+
import { exitWithError } from './exit'
|
|
26
|
+
globalThis.exitWithError = exitWithError
|
|
27
|
+
|
|
28
|
+
globalThis.mixins = mixins
|
|
29
|
+
|
|
30
|
+
// Filesystem
|
|
31
|
+
import { fs, cp, ls, mkdir, mv, rm } from './fileSystem'
|
|
32
|
+
globalThis.fs = fs
|
|
33
|
+
globalThis.cp = cp
|
|
34
|
+
globalThis.mv = mv
|
|
35
|
+
globalThis.rm = rm
|
|
36
|
+
globalThis.mkdir = mkdir
|
|
37
|
+
globalThis.ls = ls
|
|
38
|
+
|
|
39
|
+
// Lodash
|
|
40
|
+
import _ from 'lodash-es'
|
|
41
|
+
globalThis._ = _
|
|
42
|
+
|
|
43
|
+
// UI
|
|
44
|
+
import * as ui from './ui'
|
|
45
|
+
globalThis.ui = ui
|
|
46
|
+
import colors from 'chalk'
|
|
47
|
+
globalThis.colors = colors
|
|
48
|
+
|
|
49
|
+
// Csv
|
|
50
|
+
import * as csv from './csv'
|
|
51
|
+
globalThis.csv = csv
|
|
52
|
+
|
|
53
|
+
start()
|
package/src/spawn.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as zx from 'zx'
|
|
2
|
+
|
|
3
|
+
// wait for release fix: handle nullable stdout/stderr https://github.com/google/zx/commits/main/
|
|
4
|
+
// export const $ = zx.$.sync({ stdio: 'inherit' })
|
|
5
|
+
export const $ = zx.$({ stdio: 'inherit' })
|
|
6
|
+
|
|
7
|
+
// Returns text
|
|
8
|
+
export function $t(...args) {
|
|
9
|
+
const result = zx.$.sync(...args).text()
|
|
10
|
+
return result.trim()
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Returns lines
|
|
14
|
+
export function $l(...args) {
|
|
15
|
+
const lines = zx.$.sync(...args).lines()
|
|
16
|
+
// fix [''] issue
|
|
17
|
+
if (lines.length === 1 && lines[0] === '') {
|
|
18
|
+
return []
|
|
19
|
+
} else {
|
|
20
|
+
// fix ' a\n b\n' with space issue
|
|
21
|
+
return lines.map((v) => v.trim())
|
|
22
|
+
}
|
|
23
|
+
}
|
package/src/ui/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './table'
|
package/src/ui/table.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { getBorderCharacters, table as rawTable } from 'table'
|
|
2
|
+
import invariant from 'tiny-invariant'
|
|
3
|
+
|
|
4
|
+
// Display table
|
|
5
|
+
// data: [{a: '1'}] | [['a'], ['1'], ..]
|
|
6
|
+
export function table(data) {
|
|
7
|
+
invariant(_.isArray(data), 'data should be array')
|
|
8
|
+
let columns = []
|
|
9
|
+
let rows = []
|
|
10
|
+
if (_.isPlainObject(data[0])) {
|
|
11
|
+
columns = Object.keys(data[0])
|
|
12
|
+
rows = data.map((v) => Object.values(v))
|
|
13
|
+
} else if (_.isArray(data[0])) {
|
|
14
|
+
columns = data[0]
|
|
15
|
+
rows = data.slice(1)
|
|
16
|
+
} else {
|
|
17
|
+
throw new Error('data should be array of object or array')
|
|
18
|
+
}
|
|
19
|
+
const newData = [data[0], ...data.slice(1)]
|
|
20
|
+
const tableText = rawTable(
|
|
21
|
+
[columns.map((v) => colors.green.bold(v)), ...rows],
|
|
22
|
+
{
|
|
23
|
+
drawHorizontalLine: (lineIndex, rowCount) =>
|
|
24
|
+
[0, 1, rowCount].includes(lineIndex),
|
|
25
|
+
border: {
|
|
26
|
+
...getBorderCharacters('norc'),
|
|
27
|
+
topLeft: '╭',
|
|
28
|
+
topRight: '╮',
|
|
29
|
+
bottomLeft: '╰',
|
|
30
|
+
bottomRight: '╯',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
)
|
|
34
|
+
echo(tableText)
|
|
35
|
+
}
|
package/src/utils/fs.ts
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import fs from 'node:fs/promises'
|
|
2
|
+
import os from 'node:os'
|
|
3
|
+
import nodePath from 'node:path'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Check path exists
|
|
7
|
+
*/
|
|
8
|
+
async function pathExists(path: string) {
|
|
9
|
+
try {
|
|
10
|
+
await fs.access(cleanPath(path))
|
|
11
|
+
return true
|
|
12
|
+
} catch (error) {
|
|
13
|
+
if (isNodeError(error) && error.code === 'ENOENT') {
|
|
14
|
+
return false
|
|
15
|
+
}
|
|
16
|
+
throw error
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* - file not exists: returns undefined
|
|
22
|
+
*/
|
|
23
|
+
export async function inputFile(
|
|
24
|
+
path: ReadFileArgs[0],
|
|
25
|
+
options?: ReadFileArgs[1],
|
|
26
|
+
) {
|
|
27
|
+
try {
|
|
28
|
+
return await fs.readFile(cleanPath(path), options)
|
|
29
|
+
} catch (error) {
|
|
30
|
+
if (isNodeError(error) && error.code === 'ENOENT') {
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
throw error
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* - Auto create missing dirs
|
|
39
|
+
*/
|
|
40
|
+
async function outputFile(
|
|
41
|
+
rawPath: WriteFileArgs[0],
|
|
42
|
+
data: WriteFileArgs[1],
|
|
43
|
+
options?: WriteFileArgs[2],
|
|
44
|
+
) {
|
|
45
|
+
const path = cleanPath(rawPath)
|
|
46
|
+
if (typeof path === 'string') {
|
|
47
|
+
const dir = nodePath.dirname(path)
|
|
48
|
+
await fs.mkdir(dir, { recursive: true })
|
|
49
|
+
}
|
|
50
|
+
return fs.writeFile(path, data, options)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// TODO
|
|
54
|
+
// emptyDir: readdirSync(dir).forEach(v => fs.rmSync(`${dir}/${v}`, { recursive: true })
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Walk dir
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
async function* walk(rawDir: string): AsyncGenerator<string> {
|
|
61
|
+
const dir = cleanPath(rawDir)
|
|
62
|
+
for await (const d of await fs.opendir(dir)) {
|
|
63
|
+
const entry = nodePath.join(dir, d.name)
|
|
64
|
+
if (d.isDirectory()) yield* walk(entry)
|
|
65
|
+
else if (d.isFile()) yield entry
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* - uses inputFile
|
|
71
|
+
*/
|
|
72
|
+
async function inputJson(input: ReadFileArgs[0], options?: ReadFileArgs[1]) {
|
|
73
|
+
const text = await inputFile(cleanPath(input), options)
|
|
74
|
+
if (!text) {
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
return JSON.parse(text)
|
|
79
|
+
} catch (error) {
|
|
80
|
+
if (error instanceof Error) {
|
|
81
|
+
throw new Error(`[inputJson] ${error.message} from '${input}'`)
|
|
82
|
+
}
|
|
83
|
+
throw error
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/*
|
|
88
|
+
* - uses readFile
|
|
89
|
+
*/
|
|
90
|
+
async function readJson(input: ReadFileArgs[0], options?: ReadFileArgs[1]) {
|
|
91
|
+
const text = await fs.readFile(cleanPath(input), options)
|
|
92
|
+
try {
|
|
93
|
+
return JSON.parse(text)
|
|
94
|
+
} catch (error) {
|
|
95
|
+
if (error instanceof Error) {
|
|
96
|
+
throw new Error(`[readJson] ${error.message} from '${input}'`)
|
|
97
|
+
}
|
|
98
|
+
throw error
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* isSymlink
|
|
104
|
+
*/
|
|
105
|
+
async function isSymlink(input: PathLike) {
|
|
106
|
+
const stat = await lstatSafe(input)
|
|
107
|
+
return stat ? stat.isSymbolicLink() : false
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* isFile
|
|
112
|
+
*/
|
|
113
|
+
async function isFile(input: PathLike) {
|
|
114
|
+
const stat = await lstatSafe(input)
|
|
115
|
+
return stat ? stat.isFile() : false
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* isDir
|
|
120
|
+
*/
|
|
121
|
+
async function isDir(input: PathLike) {
|
|
122
|
+
const stat = await lstatSafe(input)
|
|
123
|
+
return stat ? stat.isDirectory() : false
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* TS check if error is a Node Error
|
|
128
|
+
*/
|
|
129
|
+
function isNodeError(error: unknown): error is NodeJS.ErrnoException {
|
|
130
|
+
return error instanceof Error && 'code' in error
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ignore ENOENT
|
|
134
|
+
async function lstatSafe(input: PathLike) {
|
|
135
|
+
try {
|
|
136
|
+
return await fs.lstat(cleanPath(input))
|
|
137
|
+
} catch (error) {
|
|
138
|
+
if (isNodeError(error) && error.code === 'ENOENT') {
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
throw error
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Expands a file path that starts with '~' to the absolute path of the home directory.
|
|
147
|
+
* @param {string} path - The file path to expand.
|
|
148
|
+
* @returns {string} - The expanded absolute file path.
|
|
149
|
+
*/
|
|
150
|
+
export function expand(path: any) {
|
|
151
|
+
if (!path || typeof path !== 'string') {
|
|
152
|
+
return path
|
|
153
|
+
}
|
|
154
|
+
const home = os.homedir()
|
|
155
|
+
if (path === '~') {
|
|
156
|
+
return home
|
|
157
|
+
}
|
|
158
|
+
if (path.startsWith('~/') || path.startsWith('~\\')) {
|
|
159
|
+
return nodePath.join(os.homedir(), path.slice(2))
|
|
160
|
+
}
|
|
161
|
+
return path
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function removeTrailingSlash(path: any) {
|
|
165
|
+
if (!path || typeof path !== 'string') {
|
|
166
|
+
return path
|
|
167
|
+
}
|
|
168
|
+
return path.replace(/[\\/]+$/, '')
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function cleanPath(path: any) {
|
|
172
|
+
return removeTrailingSlash(expand(path))
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async function remove(path: PathLike) {
|
|
176
|
+
return fs.rm(cleanPath(path), { recursive: true, force: true })
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function copy(source: CpArgs[0], destination: CpArgs[1]) {
|
|
180
|
+
return fs.cp(source, destination, { recursive: true })
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function move(rawSrc: PathLike, rawDest: PathLike) {
|
|
184
|
+
const src = cleanPath(rawSrc)
|
|
185
|
+
const dest = cleanPath(rawDest)
|
|
186
|
+
await makeMissingDirs(dest)
|
|
187
|
+
return fs.rename(src, dest)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function makeMissingDirs(rawPath: PathLike) {
|
|
191
|
+
if (typeof rawPath !== 'string') {
|
|
192
|
+
return
|
|
193
|
+
}
|
|
194
|
+
const path = cleanPath(rawPath)
|
|
195
|
+
const parent = nodePath.dirname(path)
|
|
196
|
+
return mkdirp(parent)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async function mkdirp(path: PathLike) {
|
|
200
|
+
return fs.mkdir(path, { recursive: true })
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
type WriteFileArgs = Parameters<typeof fs.writeFile>
|
|
204
|
+
type ReadFileArgs = Parameters<typeof fs.readFile>
|
|
205
|
+
type PathLike = Parameters<typeof fs.lstat>[0]
|
|
206
|
+
type CpArgs = Parameters<typeof fs.cp>
|
|
207
|
+
|
|
208
|
+
export default {
|
|
209
|
+
...fs,
|
|
210
|
+
pathExists,
|
|
211
|
+
expand,
|
|
212
|
+
cleanPath,
|
|
213
|
+
inputFile,
|
|
214
|
+
outputFile,
|
|
215
|
+
isNodeError,
|
|
216
|
+
readJson,
|
|
217
|
+
inputJson,
|
|
218
|
+
walk,
|
|
219
|
+
isFile,
|
|
220
|
+
isDir,
|
|
221
|
+
isSymlink,
|
|
222
|
+
remove,
|
|
223
|
+
copy,
|
|
224
|
+
move,
|
|
225
|
+
mkdirp,
|
|
226
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as fs } from './fs'
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
// Import path alias
|
|
4
|
+
"baseUrl": ".",
|
|
5
|
+
"paths": {
|
|
6
|
+
"#/*": ["src/*"],
|
|
7
|
+
"#root/*": ["./*"]
|
|
8
|
+
},
|
|
9
|
+
|
|
10
|
+
// Enable latest features
|
|
11
|
+
"lib": ["ESNext", "DOM"],
|
|
12
|
+
"target": "ESNext",
|
|
13
|
+
"module": "ESNext",
|
|
14
|
+
"moduleDetection": "force",
|
|
15
|
+
"jsx": "react-jsx",
|
|
16
|
+
"allowJs": true,
|
|
17
|
+
|
|
18
|
+
// Bundler mode
|
|
19
|
+
"moduleResolution": "bundler",
|
|
20
|
+
"allowImportingTsExtensions": true,
|
|
21
|
+
"verbatimModuleSyntax": true,
|
|
22
|
+
"noEmit": true,
|
|
23
|
+
|
|
24
|
+
// Best practices
|
|
25
|
+
"strict": true,
|
|
26
|
+
"skipLibCheck": true,
|
|
27
|
+
"noFallthroughCasesInSwitch": true,
|
|
28
|
+
|
|
29
|
+
// Some stricter flags (disabled by default)
|
|
30
|
+
"noUnusedLocals": false,
|
|
31
|
+
"noUnusedParameters": false,
|
|
32
|
+
"noPropertyAccessFromIndexSignature": false
|
|
33
|
+
}
|
|
34
|
+
}
|