@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gutenye/script.js",
3
- "version": "2.3.0",
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 # create a ake file from template in currenct directory
49
- akectl init remote # create in ~/bin.src/ake/<dir>, doesn't touch original project files
50
- akectl edit # Opens a editor to edit the ake file
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
- 'ake file not found',
25
- 'Use below commands to create one:\nakectl init local\nakectl init remote',
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(async (place: string) => {
23
- const akeFiles = await findAkeFiles()
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
- let target = AKE_FILENAMES[0]
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}/${AKE_FILENAMES[0]}`
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.cmd('edit', 'Edit ake file').add(async () => {
44
- const akeFiles = await findAkeFiles()
45
- const akeFile = akeFiles[0]
46
- if (!akeFile) {
47
- exitWithError('No ake file found')
48
- }
49
- await openEditor(akeFile)
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 nodeFs from 'node:fs'
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 async function findAkeFiles(): Promise<string[]> {
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
- AKE_FILENAMES.map(async (name) => {
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 nodeFs.realpathSync(CWD).replaceAll('/', '_')
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 nodeFs from 'node:fs'
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 { AKE_FILENAMES, getCompletionName } from './ake/shared'
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 basename = path.basename(options.scriptPath)
129
- if (AKE_FILENAMES.includes(basename)) {
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 = nodeFs.readFileSync(filePath, 'utf8')
142
+ existing = fsSync.readFileSync(filePath, 'utf8')
143
143
  } catch {}
144
144
 
145
145
  if (existing === result.text) return
146
146
 
147
- nodeFs.mkdirSync(specsDir, { recursive: true })
148
- nodeFs.writeFileSync(filePath, result.text)
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
  }