@odeva/cli 0.0.5 → 0.0.6
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/dist/commands/app/init.js +17 -10
- package/dist/commands/autocomplete.js +70 -13
- package/package.json +1 -1
|
@@ -9,6 +9,7 @@ import { withErrorHandling } from "../../lib/run.js";
|
|
|
9
9
|
import { isValidSlug, slugify } from "../../lib/slug.js";
|
|
10
10
|
import { listTemplates, renderTemplate } from "../../lib/templates.js";
|
|
11
11
|
import { ui } from "../../lib/ui.js";
|
|
12
|
+
import { detectShell, isCompletionInstalled } from "../autocomplete.js";
|
|
12
13
|
const DEFAULT_TEMPLATE = "hono-bun";
|
|
13
14
|
class AppInit extends Command {
|
|
14
15
|
static description = "Scaffold a new Odeva app";
|
|
@@ -68,17 +69,23 @@ class AppInit extends Command {
|
|
|
68
69
|
}
|
|
69
70
|
});
|
|
70
71
|
spinner.stop(`Wrote ${filesWritten} file${filesWritten === 1 ? "" : "s"} from template '${template}'.`);
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
const outroLines = [
|
|
73
|
+
ui.ok(`Created ${ui.bold(displayName)}`),
|
|
74
|
+
"",
|
|
75
|
+
" Next steps:",
|
|
76
|
+
` ${ui.code(`cd ${basename(directory)}`)}`,
|
|
77
|
+
` ${ui.code("bun install")}`,
|
|
78
|
+
` ${ui.code("odeva app config link")} ${ui.dim("# register on Odeva")}`,
|
|
79
|
+
` ${ui.code("odeva app dev")} ${ui.dim("# start local dev with a tunnel")}`
|
|
80
|
+
];
|
|
81
|
+
const shell = detectShell();
|
|
82
|
+
if (shell && !isCompletionInstalled(shell, "odeva")) {
|
|
83
|
+
outroLines.push(
|
|
74
84
|
"",
|
|
75
|
-
"
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
` ${ui.code("odeva app dev")} ${ui.dim("# start local dev with a tunnel")}`
|
|
80
|
-
].join("\n")
|
|
81
|
-
);
|
|
85
|
+
` ${ui.dim("Tip:")} ${ui.code("odeva autocomplete --install")} ${ui.dim("to enable tab-completion.")}`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
p.outro(outroLines.join("\n"));
|
|
82
89
|
});
|
|
83
90
|
}
|
|
84
91
|
async resolveDirectory(nameArg, skipPrompts) {
|
|
@@ -1,36 +1,90 @@
|
|
|
1
|
-
import { Args, Command } from "@oclif/core";
|
|
1
|
+
import { Args, Command, Flags } from "@oclif/core";
|
|
2
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { basename, dirname, join } from "node:path";
|
|
2
5
|
import { CliError } from "../lib/errors.js";
|
|
3
6
|
import { withErrorHandling } from "../lib/run.js";
|
|
7
|
+
import { ui } from "../lib/ui.js";
|
|
4
8
|
const SUPPORTED_SHELLS = ["fish", "zsh"];
|
|
5
9
|
class Autocomplete extends Command {
|
|
6
|
-
static description = "Print a shell completion script for the odeva CLI";
|
|
10
|
+
static description = "Print or install a shell completion script for the odeva CLI";
|
|
7
11
|
static examples = [
|
|
8
|
-
"$ odeva autocomplete
|
|
9
|
-
"$ odeva autocomplete
|
|
12
|
+
"$ odeva autocomplete --install # detect shell and install",
|
|
13
|
+
"$ odeva autocomplete fish --install # explicit shell",
|
|
14
|
+
"$ odeva autocomplete fish > ~/.config/fish/completions/odeva.fish"
|
|
10
15
|
];
|
|
11
16
|
static args = {
|
|
12
17
|
shell: Args.string({
|
|
13
|
-
description: "Target shell",
|
|
18
|
+
description: "Target shell (auto-detected from $SHELL when omitted with --install)",
|
|
14
19
|
options: [...SUPPORTED_SHELLS],
|
|
15
|
-
required:
|
|
20
|
+
required: false
|
|
21
|
+
})
|
|
22
|
+
};
|
|
23
|
+
static flags = {
|
|
24
|
+
install: Flags.boolean({
|
|
25
|
+
description: "Write the completion script to the conventional path for the shell",
|
|
26
|
+
default: false
|
|
16
27
|
})
|
|
17
28
|
};
|
|
18
29
|
async run() {
|
|
19
|
-
const { args } = await this.parse(Autocomplete);
|
|
30
|
+
const { args, flags } = await this.parse(Autocomplete);
|
|
20
31
|
await withErrorHandling(this, async () => {
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
32
|
+
const bin = this.config.bin;
|
|
33
|
+
const shell = args.shell ?? (flags.install ? detectShell() : void 0);
|
|
34
|
+
if (!shell) {
|
|
35
|
+
if (flags.install) {
|
|
36
|
+
throw new CliError("Couldn't detect shell from $SHELL.", {
|
|
37
|
+
hint: `Pass it explicitly: \`${bin} autocomplete <${SUPPORTED_SHELLS.join("|")}> --install\`.`
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
throw new CliError("A shell is required.", {
|
|
41
|
+
hint: `Usage: \`${bin} autocomplete <${SUPPORTED_SHELLS.join("|")}>\`. Add --install to write the script to the conventional path.`
|
|
25
42
|
});
|
|
26
43
|
}
|
|
27
|
-
const bin = this.config.bin;
|
|
28
44
|
const tree = buildCommandTree(this.config.commands, bin);
|
|
29
45
|
const script = shell === "fish" ? renderFish(bin, tree) : renderZsh(bin, tree);
|
|
30
|
-
|
|
46
|
+
if (!flags.install) {
|
|
47
|
+
process.stdout.write(script);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const target = completionInstallPath(shell, bin);
|
|
51
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
52
|
+
writeFileSync(target, script, { mode: 420 });
|
|
53
|
+
this.log(ui.ok(`Wrote ${shell} completion to ${ui.code(target)}`));
|
|
54
|
+
if (shell === "fish") {
|
|
55
|
+
this.log(ui.dim(" Open a new fish shell \u2014 completions auto-load from this path."));
|
|
56
|
+
} else {
|
|
57
|
+
const fpathDir = dirname(target);
|
|
58
|
+
this.log("");
|
|
59
|
+
this.log(" Add this to your ~/.zshrc (if you haven't already):");
|
|
60
|
+
this.log("");
|
|
61
|
+
this.log(ui.code(` fpath=(${fpathDir} $fpath)`));
|
|
62
|
+
this.log(ui.code(` autoload -U compinit && compinit`));
|
|
63
|
+
this.log("");
|
|
64
|
+
this.log(ui.dim(" Then open a new zsh shell."));
|
|
65
|
+
}
|
|
31
66
|
});
|
|
32
67
|
}
|
|
33
68
|
}
|
|
69
|
+
function detectShell(env = process.env) {
|
|
70
|
+
const shell = env["SHELL"];
|
|
71
|
+
if (!shell) return null;
|
|
72
|
+
const name = basename(shell);
|
|
73
|
+
if (name === "fish" || name === "zsh") return name;
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
function completionInstallPath(shell, bin, env = process.env) {
|
|
77
|
+
const home = env["HOME"] || homedir();
|
|
78
|
+
if (shell === "fish") {
|
|
79
|
+
const config = env["XDG_CONFIG_HOME"] || join(home, ".config");
|
|
80
|
+
return join(config, "fish", "completions", `${bin}.fish`);
|
|
81
|
+
}
|
|
82
|
+
const data = env["XDG_DATA_HOME"] || join(home, ".local", "share");
|
|
83
|
+
return join(data, bin, "completions", `_${bin}`);
|
|
84
|
+
}
|
|
85
|
+
function isCompletionInstalled(shell, bin, env = process.env) {
|
|
86
|
+
return existsSync(completionInstallPath(shell, bin, env));
|
|
87
|
+
}
|
|
34
88
|
function buildCommandTree(commands, binName) {
|
|
35
89
|
const root = { name: "", description: "", children: /* @__PURE__ */ new Map() };
|
|
36
90
|
for (const cmd of commands) {
|
|
@@ -152,7 +206,10 @@ _${bin} "$@"
|
|
|
152
206
|
}
|
|
153
207
|
export {
|
|
154
208
|
buildCommandTree,
|
|
209
|
+
completionInstallPath,
|
|
155
210
|
Autocomplete as default,
|
|
211
|
+
detectShell,
|
|
212
|
+
isCompletionInstalled,
|
|
156
213
|
renderFish,
|
|
157
214
|
renderZsh
|
|
158
215
|
};
|