@gutenye/script.js 2.3.0 → 2.5.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/package.json +2 -2
- package/src/ake/README.md +76 -3
- package/src/ake/ake.ts +7 -4
- package/src/ake/akectl.ts +39 -15
- package/src/ake/completions/ake.fish +13 -4
- package/src/ake/shared.ts +23 -7
- package/src/completion.ts +8 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gutenye/script.js",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"description": "Write shell scripts in JavaScript",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"shell",
|
|
@@ -26,7 +26,6 @@
|
|
|
26
26
|
],
|
|
27
27
|
"bin": {
|
|
28
28
|
"script.js": "src/script.ts",
|
|
29
|
-
"a": "src/ake.ts",
|
|
30
29
|
"ake": "src/ake.ts",
|
|
31
30
|
"akectl": "src/akectl.ts"
|
|
32
31
|
},
|
|
@@ -39,6 +38,7 @@
|
|
|
39
38
|
"access": "public"
|
|
40
39
|
},
|
|
41
40
|
"dependencies": {
|
|
41
|
+
"@gutenye/script.js": "^2.3.0",
|
|
42
42
|
"yaml": "^2.6.0"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
package/src/ake/README.md
CHANGED
|
@@ -45,7 +45,80 @@ ake <Tab> # uses ake file's completion
|
|
|
45
45
|
Create `~/bin.src/ake/template`
|
|
46
46
|
|
|
47
47
|
```sh
|
|
48
|
-
akectl init local
|
|
49
|
-
akectl init
|
|
50
|
-
akectl
|
|
48
|
+
akectl init local # create an ake file from template in current directory
|
|
49
|
+
akectl init local foo # create an akefoo file
|
|
50
|
+
akectl init remote # create in ~/bin.src/ake/<dir>, doesn't touch original project files
|
|
51
|
+
akectl init remote foo # create akefoo in remote location
|
|
52
|
+
akectl edit # opens an editor to edit the ake file
|
|
53
|
+
akectl edit foo # opens an editor to edit the akefoo file
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Organize Multiple Files
|
|
57
|
+
|
|
58
|
+
For larger projects, split commands into separate files and import them.
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
project/
|
|
62
|
+
├── ake # entry point (executable)
|
|
63
|
+
├── src/
|
|
64
|
+
│ ├── index.ts # imports all command files
|
|
65
|
+
│ ├── cmd1.ts
|
|
66
|
+
│ └── cmd2.ts
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`src/cmd1.ts`
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import { app } from "@gutenye/script.js";
|
|
73
|
+
|
|
74
|
+
app.cmd("greetings").add(() => {
|
|
75
|
+
$`echo greetings`;
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`src/index.ts`
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import "./cmd1";
|
|
83
|
+
import "./cmd2";
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`ake`
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
#!/usr/bin/env bun
|
|
90
|
+
|
|
91
|
+
import { app } from "@gutenye/script.js";
|
|
92
|
+
import "./src";
|
|
93
|
+
|
|
94
|
+
await app.run();
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Multiple Ake Files
|
|
98
|
+
|
|
99
|
+
Any file named `ake<suffix>` or `ake<suffix>.ts` is supported.
|
|
100
|
+
|
|
101
|
+
1. Create a variant ake file
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
akectl init local foo # creates ./akefoo
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
2. Create a wrapper script so you can invoke it by name
|
|
108
|
+
|
|
109
|
+
```sh
|
|
110
|
+
akectl install-bin ~/bin/ake foo
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
3. Enable shell completion in `ake.fish`
|
|
114
|
+
|
|
115
|
+
```fish
|
|
116
|
+
_setup_ake_complete ake foo
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
4. Run it
|
|
120
|
+
|
|
121
|
+
```sh
|
|
122
|
+
akefoo greetings
|
|
123
|
+
akefoo <Tab> # with autocompletion
|
|
51
124
|
```
|
package/src/ake/ake.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
|
-
import { exitWithError, findAkeFiles } from './shared'
|
|
3
|
+
import { exitWithError, findAkeFiles, getSuffix } from './shared'
|
|
4
|
+
|
|
5
|
+
const suffix = process.env.SUFFIX ?? getSuffix()
|
|
4
6
|
|
|
5
7
|
async function main() {
|
|
6
8
|
const akeFile = await findAkeFile()
|
|
@@ -8,7 +10,7 @@ async function main() {
|
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
async function findAkeFile() {
|
|
11
|
-
const akeFiles = await findAkeFiles()
|
|
13
|
+
const akeFiles = await findAkeFiles(suffix)
|
|
12
14
|
|
|
13
15
|
if (akeFiles.length >= 2) {
|
|
14
16
|
exitWithError(
|
|
@@ -20,9 +22,10 @@ async function findAkeFile() {
|
|
|
20
22
|
const akeFile = akeFiles[0]
|
|
21
23
|
|
|
22
24
|
if (!akeFile) {
|
|
25
|
+
const name = `ake${suffix}`
|
|
23
26
|
exitWithError(
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
`${name} file not found`,
|
|
28
|
+
`Use below commands to create one:\nakectl init local${suffix ? ` ${suffix}` : ''}\nakectl init remote${suffix ? ` ${suffix}` : ''}`,
|
|
26
29
|
)
|
|
27
30
|
}
|
|
28
31
|
|
package/src/ake/akectl.ts
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
import { castArray } from 'lodash-es'
|
|
4
4
|
import fs from '../utils/fs'
|
|
5
5
|
import {
|
|
6
|
-
AKE_FILENAMES,
|
|
7
|
-
STORAGE_DIR,
|
|
8
|
-
TEMPLATE_NAME,
|
|
9
6
|
exitWithError,
|
|
10
7
|
findAkeFiles,
|
|
8
|
+
getAkeFilenames,
|
|
11
9
|
getRemoteDir,
|
|
10
|
+
STORAGE_DIR,
|
|
11
|
+
TEMPLATE_NAME,
|
|
12
12
|
} from './shared'
|
|
13
13
|
|
|
14
14
|
const NAME = 'akectl'
|
|
@@ -19,16 +19,19 @@ app.meta(NAME)
|
|
|
19
19
|
app
|
|
20
20
|
.cmd('init', 'Create ake file')
|
|
21
21
|
.add('<place>', 'Place', ['local', 'remote'])
|
|
22
|
-
.add(
|
|
23
|
-
|
|
22
|
+
.add('[suffix]', 'Ake file suffix (e.g. "foo" for akefoo)')
|
|
23
|
+
.add(async (place: string, suffix: string) => {
|
|
24
|
+
suffix = suffix ?? ''
|
|
25
|
+
const akeFiles = await findAkeFiles(suffix)
|
|
24
26
|
if (akeFiles.length > 0) {
|
|
25
27
|
exitWithError('Already have an ake file, cannot create a new one')
|
|
26
28
|
}
|
|
27
|
-
|
|
29
|
+
const filenames = getAkeFilenames(suffix)
|
|
30
|
+
let target = filenames[0]
|
|
28
31
|
if (place === 'remote') {
|
|
29
32
|
const remoteDir = getRemoteDir()
|
|
30
33
|
await fs.mkdirp(remoteDir)
|
|
31
|
-
target = `${remoteDir}/${
|
|
34
|
+
target = `${remoteDir}/${filenames[0]}`
|
|
32
35
|
}
|
|
33
36
|
const templateFile = `${STORAGE_DIR}/${TEMPLATE_NAME}`
|
|
34
37
|
if (await fs.pathExists(templateFile)) {
|
|
@@ -40,14 +43,35 @@ app
|
|
|
40
43
|
await openEditor(target)
|
|
41
44
|
})
|
|
42
45
|
|
|
43
|
-
app
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
app
|
|
47
|
+
.cmd('install-bin', 'Create a wrapper script for ake with a suffix')
|
|
48
|
+
.add('<prefix>', 'Path prefix (e.g. ~/bin/ake, ~/bin/a)')
|
|
49
|
+
.add('<suffix>', 'Suffix to append (e.g. "foo" → ~/bin/akefoo)')
|
|
50
|
+
.add(async (prefix: string, suffix: string) => {
|
|
51
|
+
const target = `${prefix}${suffix}`
|
|
52
|
+
const content = `
|
|
53
|
+
#!/usr/bin/env bash
|
|
54
|
+
|
|
55
|
+
SUFFIX=${suffix} exec ${prefix} "$@"
|
|
56
|
+
`.trim()
|
|
57
|
+
await fs.writeFile(target, content)
|
|
58
|
+
await fs.chmod(target, 0o755)
|
|
59
|
+
console.log(`created ${target}`)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
app
|
|
63
|
+
.cmd('edit', 'Edit ake file')
|
|
64
|
+
.add('[suffix]', 'Ake file suffix (e.g. "foo" for akefoo)')
|
|
65
|
+
.add(async (suffix: string) => {
|
|
66
|
+
suffix = suffix ?? ''
|
|
67
|
+
const akeFiles = await findAkeFiles(suffix)
|
|
68
|
+
const akeFile = akeFiles[0]
|
|
69
|
+
if (!akeFile) {
|
|
70
|
+
const name = `ake${suffix}`
|
|
71
|
+
exitWithError(`${name} file not found`)
|
|
72
|
+
}
|
|
73
|
+
await openEditor(akeFile)
|
|
74
|
+
})
|
|
51
75
|
|
|
52
76
|
async function openEditor(inputPaths: string | string[]) {
|
|
53
77
|
const paths = castArray(inputPaths)
|
|
@@ -1,8 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
function _setup_ake_complete
|
|
2
|
+
set cmd $argv[1]
|
|
3
|
+
set suffix $argv[2]
|
|
4
|
+
complete --erase $cmd$suffix
|
|
5
|
+
complete --command $cmd$suffix --no-files --arguments "(_ake_complete $suffix)"
|
|
6
|
+
end
|
|
3
7
|
|
|
4
8
|
function _ake_complete
|
|
5
|
-
set
|
|
6
|
-
set
|
|
9
|
+
set suffix $argv[1]
|
|
10
|
+
set unique_name (string replace --all '/' '_' $PWD)
|
|
11
|
+
set name "ake$suffix.$unique_name"
|
|
7
12
|
_carapace_completer $name
|
|
8
13
|
end
|
|
14
|
+
|
|
15
|
+
_setup_ake_complete ake
|
|
16
|
+
# _setup_ake_complete ake <suffix>
|
|
17
|
+
|
package/src/ake/shared.ts
CHANGED
|
@@ -1,22 +1,38 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fsSync from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
2
3
|
import os from 'node:os'
|
|
3
4
|
import fs from '../utils/fs'
|
|
4
5
|
|
|
5
6
|
const HOME = os.homedir()
|
|
6
7
|
const CWD = process.cwd()
|
|
7
8
|
|
|
8
|
-
export const AKE_FILENAMES = ['ake', 'ake.ts']
|
|
9
9
|
export const STORAGE_DIR = `${HOME}/bin.src/ake`
|
|
10
10
|
export const TEMPLATE_NAME = 'template'
|
|
11
11
|
|
|
12
|
-
export
|
|
12
|
+
export function getAkeFilenames(suffix = ''): string[] {
|
|
13
|
+
const name = `ake${suffix}`
|
|
14
|
+
return [name, `${name}.ts`]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getAkeSuffix(name: string): string | null {
|
|
18
|
+
const base = name.replace(/\.ts$/, '')
|
|
19
|
+
if (!base.startsWith('ake') || base === 'akectl') return null
|
|
20
|
+
return base.slice(3)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function getSuffix(): string {
|
|
24
|
+
return getAkeSuffix(path.basename(process.argv[1])) ?? ''
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function findAkeFiles(suffix = ''): Promise<string[]> {
|
|
28
|
+
const filenames = getAkeFilenames(suffix)
|
|
13
29
|
const localDir = CWD
|
|
14
30
|
const remoteDir = getRemoteDir()
|
|
15
31
|
const dirsToCheck = [localDir, remoteDir]
|
|
16
32
|
|
|
17
33
|
const akeFiles = await Promise.all(
|
|
18
34
|
dirsToCheck.flatMap((dir) =>
|
|
19
|
-
|
|
35
|
+
filenames.map(async (name) => {
|
|
20
36
|
const akeFile = `${dir}/${name}`
|
|
21
37
|
return (await fs.pathExists(akeFile)) ? akeFile : null
|
|
22
38
|
}),
|
|
@@ -30,13 +46,13 @@ export function getRemoteDir() {
|
|
|
30
46
|
return `${STORAGE_DIR}/${getUniqueName()}`
|
|
31
47
|
}
|
|
32
48
|
|
|
33
|
-
export function getCompletionName() {
|
|
34
|
-
return `ake.${getUniqueName()}`
|
|
49
|
+
export function getCompletionName(suffix = '') {
|
|
50
|
+
return `ake${suffix}.${getUniqueName()}`
|
|
35
51
|
}
|
|
36
52
|
|
|
37
53
|
export function getUniqueName() {
|
|
38
54
|
// use sync method to avoid using await in app.enableAkeCompletion()
|
|
39
|
-
return
|
|
55
|
+
return fsSync.realpathSync(CWD).replaceAll('/', '_')
|
|
40
56
|
}
|
|
41
57
|
|
|
42
58
|
export function exitWithError(message: string, help?: string): never {
|
package/src/completion.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fsSync from 'node:fs'
|
|
2
2
|
import os from 'node:os'
|
|
3
3
|
import path from 'node:path'
|
|
4
4
|
import * as yaml from 'yaml'
|
|
5
5
|
import type { Command } from './Command'
|
|
6
|
-
import {
|
|
6
|
+
import { getAkeSuffix, getCompletionName } from './ake/shared'
|
|
7
7
|
|
|
8
8
|
export type CompletionValue = string[] | (() => string[])
|
|
9
9
|
|
|
@@ -125,9 +125,9 @@ export async function installCompletion(
|
|
|
125
125
|
) {
|
|
126
126
|
try {
|
|
127
127
|
if (!command.name && options.scriptPath) {
|
|
128
|
-
const
|
|
129
|
-
if (
|
|
130
|
-
command.name = getCompletionName()
|
|
128
|
+
const suffix = getAkeSuffix(path.basename(options.scriptPath))
|
|
129
|
+
if (suffix !== null) {
|
|
130
|
+
command.name = getCompletionName(suffix)
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
|
|
@@ -139,13 +139,13 @@ export async function installCompletion(
|
|
|
139
139
|
|
|
140
140
|
let existing: string | undefined
|
|
141
141
|
try {
|
|
142
|
-
existing =
|
|
142
|
+
existing = fsSync.readFileSync(filePath, 'utf8')
|
|
143
143
|
} catch {}
|
|
144
144
|
|
|
145
145
|
if (existing === result.text) return
|
|
146
146
|
|
|
147
|
-
|
|
148
|
-
|
|
147
|
+
fsSync.mkdirSync(specsDir, { recursive: true })
|
|
148
|
+
fsSync.writeFileSync(filePath, result.text)
|
|
149
149
|
} catch {
|
|
150
150
|
// completion is supplementary — silently ignore errors
|
|
151
151
|
}
|