@gutenye/script.js 2.3.0 → 2.4.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 +26 -3
- package/src/ake/ake.ts +7 -4
- package/src/ake/akectl.ts +21 -13
- 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.4.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
|
@@ -40,12 +40,35 @@ ake greetings # find the ake file and runs it
|
|
|
40
40
|
ake <Tab> # uses ake file's completion
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
+
## Multiple Ake Files
|
|
44
|
+
|
|
45
|
+
You can have multiple ake files in the same directory, each for different tasks. Any file named `ake<suffix>` or `ake<suffix>.ts` is supported.
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
# Create variant ake files
|
|
49
|
+
akectl init local foo # creates ./akefoo
|
|
50
|
+
akectl init local bar # creates ./akebar
|
|
51
|
+
|
|
52
|
+
# Create symlinks so you can invoke them by name
|
|
53
|
+
ln -sf $(which ake) ~/bin/akefoo
|
|
54
|
+
ln -sf $(which ake) ~/bin/akebar
|
|
55
|
+
|
|
56
|
+
# Run them
|
|
57
|
+
akefoo greetings # finds ./akefoo and runs it
|
|
58
|
+
akebar deploy # finds ./akebar and runs it
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Each variant gets its own shell completion spec automatically.
|
|
62
|
+
|
|
43
63
|
## Use a template / another location
|
|
44
64
|
|
|
45
65
|
Create `~/bin.src/ake/template`
|
|
46
66
|
|
|
47
67
|
```sh
|
|
48
|
-
akectl init local
|
|
49
|
-
akectl init
|
|
50
|
-
akectl
|
|
68
|
+
akectl init local # create an ake file from template in current directory
|
|
69
|
+
akectl init local foo # create an akefoo file
|
|
70
|
+
akectl init remote # create in ~/bin.src/ake/<dir>, doesn't touch original project files
|
|
71
|
+
akectl init remote foo # create akefoo in remote location
|
|
72
|
+
akectl edit # opens an editor to edit the ake file
|
|
73
|
+
akectl edit foo # opens an editor to edit the akefoo file
|
|
51
74
|
```
|
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 = 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,11 +3,11 @@
|
|
|
3
3
|
import { castArray } from 'lodash-es'
|
|
4
4
|
import fs from '../utils/fs'
|
|
5
5
|
import {
|
|
6
|
-
AKE_FILENAMES,
|
|
7
6
|
STORAGE_DIR,
|
|
8
7
|
TEMPLATE_NAME,
|
|
9
8
|
exitWithError,
|
|
10
9
|
findAkeFiles,
|
|
10
|
+
getAkeFilenames,
|
|
11
11
|
getRemoteDir,
|
|
12
12
|
} from './shared'
|
|
13
13
|
|
|
@@ -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,19 @@ app
|
|
|
40
43
|
await openEditor(target)
|
|
41
44
|
})
|
|
42
45
|
|
|
43
|
-
app
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
app
|
|
47
|
+
.cmd('edit', 'Edit ake file')
|
|
48
|
+
.add('[suffix]', 'Ake file suffix (e.g. "foo" for akefoo)')
|
|
49
|
+
.add(async (suffix: string) => {
|
|
50
|
+
suffix = suffix ?? ''
|
|
51
|
+
const akeFiles = await findAkeFiles(suffix)
|
|
52
|
+
const akeFile = akeFiles[0]
|
|
53
|
+
if (!akeFile) {
|
|
54
|
+
const name = `ake${suffix}`
|
|
55
|
+
exitWithError(`${name} file not found`)
|
|
56
|
+
}
|
|
57
|
+
await openEditor(akeFile)
|
|
58
|
+
})
|
|
51
59
|
|
|
52
60
|
async function openEditor(inputPaths: string | string[]) {
|
|
53
61
|
const paths = castArray(inputPaths)
|
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
|
}
|