@kidd-cli/cli 0.1.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/LICENSE +21 -0
- package/README.md +17 -0
- package/dist/commands/add/command.d.mts +6 -0
- package/dist/commands/add/command.mjs +137 -0
- package/dist/commands/add/index.d.mts +6 -0
- package/dist/commands/add/index.mjs +7 -0
- package/dist/commands/add/middleware.d.mts +6 -0
- package/dist/commands/add/middleware.mjs +101 -0
- package/dist/commands/build.d.mts +6 -0
- package/dist/commands/build.mjs +163 -0
- package/dist/commands/commands.d.mts +13 -0
- package/dist/commands/commands.mjs +135 -0
- package/dist/commands/dev.d.mts +12 -0
- package/dist/commands/dev.mjs +68 -0
- package/dist/commands/doctor.d.mts +6 -0
- package/dist/commands/doctor.mjs +680 -0
- package/dist/commands/init.d.mts +6 -0
- package/dist/commands/init.mjs +167 -0
- package/dist/detect-DDE1hlQ8.mjs +73 -0
- package/dist/index.d.mts +1 -0
- package/dist/index.mjs +41 -0
- package/dist/lib/templates/command/command.ts.liquid +12 -0
- package/dist/lib/templates/middleware/middleware.ts.liquid +9 -0
- package/dist/lib/templates/project/gitignore.liquid +3 -0
- package/dist/lib/templates/project/package.json.liquid +29 -0
- package/dist/lib/templates/project/src/commands/hello.ts.liquid +12 -0
- package/dist/lib/templates/project/src/index.ts.liquid +8 -0
- package/dist/lib/templates/project/tsconfig.json.liquid +19 -0
- package/dist/lib/templates/project/tsdown.config.ts.liquid +9 -0
- package/dist/lib/templates/project/vitest.config.ts.liquid +8 -0
- package/dist/write-DDGnajpV.mjs +166 -0
- package/package.json +43 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Joggr, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# kidd-cli
|
|
2
|
+
|
|
3
|
+
DX companion CLI for the kidd framework.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add -g kidd-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
kidd --help
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
> This package is a placeholder. The CLI implementation is not yet available.
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { n as renderTemplate, t as writeFiles } from "../../write-DDGnajpV.mjs";
|
|
2
|
+
import { t as detectProject } from "../../detect-DDE1hlQ8.mjs";
|
|
3
|
+
import { command } from "@kidd-cli/core";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { loadConfig } from "@kidd-cli/config/loader";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
|
|
8
|
+
//#region src/commands/add/command.ts
|
|
9
|
+
const KEBAB_CASE_CHARS_RE = /^[a-z][\da-z-]*$/;
|
|
10
|
+
const addCommandCommand = command({
|
|
11
|
+
args: z.object({
|
|
12
|
+
args: z.boolean().describe("Include args schema").optional(),
|
|
13
|
+
description: z.string().describe("Command description").optional(),
|
|
14
|
+
name: z.string().describe("Command name (kebab-case)").optional()
|
|
15
|
+
}),
|
|
16
|
+
description: "Add a new command to your project",
|
|
17
|
+
handler: async (ctx) => {
|
|
18
|
+
const [detectError, project] = await detectProject(process.cwd());
|
|
19
|
+
if (detectError) return ctx.fail(detectError.message);
|
|
20
|
+
if (!project) return ctx.fail("Not in a kidd project. Run `kidd init` first.");
|
|
21
|
+
const [configError, configResult] = await loadConfig({ cwd: project.rootDir });
|
|
22
|
+
if (configError) {}
|
|
23
|
+
const commandName = await resolveCommandName(ctx);
|
|
24
|
+
const commandDescription = await resolveDescription(ctx);
|
|
25
|
+
const includeArgs = await resolveIncludeArgs(ctx);
|
|
26
|
+
ctx.spinner.start("Generating command...");
|
|
27
|
+
const [renderError, rendered] = await renderTemplate({
|
|
28
|
+
templateDir: join(import.meta.dirname, "..", "..", "lib", "templates", "command"),
|
|
29
|
+
variables: {
|
|
30
|
+
commandName,
|
|
31
|
+
description: commandDescription,
|
|
32
|
+
includeArgs
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
if (renderError) {
|
|
36
|
+
ctx.spinner.stop("Failed");
|
|
37
|
+
return ctx.fail(renderError.message);
|
|
38
|
+
}
|
|
39
|
+
const outputDir = resolveCommandsDir(configResult, project.rootDir);
|
|
40
|
+
const [writeError, result] = await writeFiles({
|
|
41
|
+
files: rendered.map((file) => ({
|
|
42
|
+
content: file.content,
|
|
43
|
+
relativePath: file.relativePath.replace("command.ts", `${commandName}.ts`)
|
|
44
|
+
})),
|
|
45
|
+
outputDir,
|
|
46
|
+
overwrite: false
|
|
47
|
+
});
|
|
48
|
+
if (writeError) {
|
|
49
|
+
ctx.spinner.stop("Failed");
|
|
50
|
+
return ctx.fail(writeError.message);
|
|
51
|
+
}
|
|
52
|
+
ctx.spinner.stop("Command created!");
|
|
53
|
+
result.written.map((file) => ctx.output.raw(` created ${file}`));
|
|
54
|
+
result.skipped.map((file) => ctx.output.raw(` skipped ${file} (already exists)`));
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
/**
|
|
58
|
+
* Check whether a string is valid kebab-case.
|
|
59
|
+
*
|
|
60
|
+
* @param value - The string to validate.
|
|
61
|
+
* @returns True when the string is kebab-case.
|
|
62
|
+
* @private
|
|
63
|
+
*/
|
|
64
|
+
function isKebabCase(value) {
|
|
65
|
+
if (!KEBAB_CASE_CHARS_RE.test(value)) return false;
|
|
66
|
+
if (value.endsWith("-")) return false;
|
|
67
|
+
if (value.includes("--")) return false;
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Resolve the command name from args or prompt.
|
|
72
|
+
*
|
|
73
|
+
* @param ctx - Command context.
|
|
74
|
+
* @returns The validated command name.
|
|
75
|
+
* @private
|
|
76
|
+
*/
|
|
77
|
+
async function resolveCommandName(ctx) {
|
|
78
|
+
if (ctx.args.name) {
|
|
79
|
+
if (!isKebabCase(ctx.args.name)) return ctx.fail("Command name must be kebab-case (e.g. deploy)");
|
|
80
|
+
return ctx.args.name;
|
|
81
|
+
}
|
|
82
|
+
return ctx.prompts.text({
|
|
83
|
+
message: "Command name",
|
|
84
|
+
placeholder: "deploy",
|
|
85
|
+
validate: (value) => {
|
|
86
|
+
if (value === void 0 || !isKebabCase(value)) return "Must be kebab-case (e.g. deploy)";
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Resolve the command description from args or prompt.
|
|
92
|
+
*
|
|
93
|
+
* @param ctx - Command context.
|
|
94
|
+
* @returns The command description string.
|
|
95
|
+
* @private
|
|
96
|
+
*/
|
|
97
|
+
async function resolveDescription(ctx) {
|
|
98
|
+
if (ctx.args.description) return ctx.args.description;
|
|
99
|
+
return ctx.prompts.text({
|
|
100
|
+
defaultValue: "",
|
|
101
|
+
message: "Description",
|
|
102
|
+
placeholder: "What does this command do?"
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Resolve whether to include args from args or prompt.
|
|
107
|
+
*
|
|
108
|
+
* @param ctx - Command context.
|
|
109
|
+
* @returns True when the command should include a zod args schema.
|
|
110
|
+
* @private
|
|
111
|
+
*/
|
|
112
|
+
async function resolveIncludeArgs(ctx) {
|
|
113
|
+
if (ctx.args.args !== void 0 && ctx.args.args !== null) return ctx.args.args;
|
|
114
|
+
return ctx.prompts.confirm({
|
|
115
|
+
initialValue: true,
|
|
116
|
+
message: "Include args schema?"
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Resolve the commands output directory from the kidd config.
|
|
121
|
+
*
|
|
122
|
+
* Uses the `commands` field from `kidd.config.ts` when available,
|
|
123
|
+
* falling back to the kidd default of `'commands'`.
|
|
124
|
+
*
|
|
125
|
+
* @param configResult - The loaded config result, or null when loading failed.
|
|
126
|
+
* @param rootDir - The project root directory.
|
|
127
|
+
* @returns The absolute path to the commands directory.
|
|
128
|
+
* @private
|
|
129
|
+
*/
|
|
130
|
+
function resolveCommandsDir(configResult, rootDir) {
|
|
131
|
+
const DEFAULT_COMMANDS = "commands";
|
|
132
|
+
if (configResult) return join(rootDir, configResult.config.commands ?? DEFAULT_COMMANDS);
|
|
133
|
+
return join(rootDir, DEFAULT_COMMANDS);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
//#endregion
|
|
137
|
+
export { addCommandCommand as default };
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { n as renderTemplate, t as writeFiles } from "../../write-DDGnajpV.mjs";
|
|
2
|
+
import { t as detectProject } from "../../detect-DDE1hlQ8.mjs";
|
|
3
|
+
import { command } from "@kidd-cli/core";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
|
|
7
|
+
//#region src/commands/add/middleware.ts
|
|
8
|
+
const KEBAB_CASE_CHARS_RE = /^[a-z][\da-z-]*$/;
|
|
9
|
+
const addMiddlewareCommand = command({
|
|
10
|
+
args: z.object({
|
|
11
|
+
description: z.string().describe("Middleware description").optional(),
|
|
12
|
+
name: z.string().describe("Middleware name (kebab-case)").optional()
|
|
13
|
+
}),
|
|
14
|
+
description: "Add a new middleware to your project",
|
|
15
|
+
handler: async (ctx) => {
|
|
16
|
+
const [detectError, project] = await detectProject(process.cwd());
|
|
17
|
+
if (detectError) return ctx.fail(detectError.message);
|
|
18
|
+
if (!project) return ctx.fail("Not in a kidd project. Run `kidd init` first.");
|
|
19
|
+
const middlewareName = await resolveMiddlewareName(ctx);
|
|
20
|
+
const middlewareDescription = await resolveDescription(ctx);
|
|
21
|
+
ctx.spinner.start("Generating middleware...");
|
|
22
|
+
const [renderError, rendered] = await renderTemplate({
|
|
23
|
+
templateDir: join(import.meta.dirname, "..", "..", "lib", "templates", "middleware"),
|
|
24
|
+
variables: {
|
|
25
|
+
description: middlewareDescription,
|
|
26
|
+
middlewareName
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
if (renderError) {
|
|
30
|
+
ctx.spinner.stop("Failed");
|
|
31
|
+
return ctx.fail(renderError.message);
|
|
32
|
+
}
|
|
33
|
+
const outputDir = join(project.rootDir, "src", "middleware");
|
|
34
|
+
const [writeError, result] = await writeFiles({
|
|
35
|
+
files: rendered.map((file) => ({
|
|
36
|
+
content: file.content,
|
|
37
|
+
relativePath: file.relativePath.replace("middleware.ts", `${middlewareName}.ts`)
|
|
38
|
+
})),
|
|
39
|
+
outputDir,
|
|
40
|
+
overwrite: false
|
|
41
|
+
});
|
|
42
|
+
if (writeError) {
|
|
43
|
+
ctx.spinner.stop("Failed");
|
|
44
|
+
return ctx.fail(writeError.message);
|
|
45
|
+
}
|
|
46
|
+
ctx.spinner.stop("Middleware created!");
|
|
47
|
+
result.written.map((file) => ctx.output.raw(` created ${file}`));
|
|
48
|
+
result.skipped.map((file) => ctx.output.raw(` skipped ${file} (already exists)`));
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
/**
|
|
52
|
+
* Check whether a string is valid kebab-case.
|
|
53
|
+
*
|
|
54
|
+
* @param value - The string to validate.
|
|
55
|
+
* @returns True when the string is kebab-case.
|
|
56
|
+
* @private
|
|
57
|
+
*/
|
|
58
|
+
function isKebabCase(value) {
|
|
59
|
+
if (!KEBAB_CASE_CHARS_RE.test(value)) return false;
|
|
60
|
+
if (value.endsWith("-")) return false;
|
|
61
|
+
if (value.includes("--")) return false;
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Resolve the middleware name from args or prompt.
|
|
66
|
+
*
|
|
67
|
+
* @param ctx - Command context.
|
|
68
|
+
* @returns The validated middleware name.
|
|
69
|
+
* @private
|
|
70
|
+
*/
|
|
71
|
+
async function resolveMiddlewareName(ctx) {
|
|
72
|
+
if (ctx.args.name) {
|
|
73
|
+
if (!isKebabCase(ctx.args.name)) return ctx.fail("Middleware name must be kebab-case (e.g. auth)");
|
|
74
|
+
return ctx.args.name;
|
|
75
|
+
}
|
|
76
|
+
return ctx.prompts.text({
|
|
77
|
+
message: "Middleware name",
|
|
78
|
+
placeholder: "auth",
|
|
79
|
+
validate: (value) => {
|
|
80
|
+
if (value === void 0 || !isKebabCase(value)) return "Must be kebab-case (e.g. auth)";
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Resolve the middleware description from args or prompt.
|
|
86
|
+
*
|
|
87
|
+
* @param ctx - Command context.
|
|
88
|
+
* @returns The middleware description string.
|
|
89
|
+
* @private
|
|
90
|
+
*/
|
|
91
|
+
async function resolveDescription(ctx) {
|
|
92
|
+
if (ctx.args.description) return ctx.args.description;
|
|
93
|
+
return ctx.prompts.text({
|
|
94
|
+
defaultValue: "",
|
|
95
|
+
message: "Description",
|
|
96
|
+
placeholder: "What does this middleware do?"
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
//#endregion
|
|
101
|
+
export { addMiddlewareCommand as default };
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { command } from "@kidd-cli/core";
|
|
2
|
+
import { relative } from "node:path";
|
|
3
|
+
import { build, compile, resolveTargetLabel } from "@kidd-cli/bundler";
|
|
4
|
+
import { loadConfig } from "@kidd-cli/config/loader";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
|
|
7
|
+
//#region src/commands/build.ts
|
|
8
|
+
/**
|
|
9
|
+
* Build a kidd CLI project for production using tsdown.
|
|
10
|
+
*
|
|
11
|
+
* Loads the project's `kidd.config.ts`, invokes the bundler, and reports
|
|
12
|
+
* the output entry file and directory on success. When `--compile` or
|
|
13
|
+
* `--targets` is provided (or `compile` is set in config), also compiles
|
|
14
|
+
* to standalone binaries via Bun.
|
|
15
|
+
*/
|
|
16
|
+
const buildCommand = command({
|
|
17
|
+
args: z.object({
|
|
18
|
+
compile: z.boolean().optional().describe("Compile to standalone binaries after bundling"),
|
|
19
|
+
targets: z.array(z.string()).optional().describe("Compile targets (implies --compile)")
|
|
20
|
+
}),
|
|
21
|
+
description: "Build a kidd CLI project for production",
|
|
22
|
+
handler: async (ctx) => {
|
|
23
|
+
const cwd = process.cwd();
|
|
24
|
+
const [configError, configResult] = await loadConfig({ cwd });
|
|
25
|
+
const config = extractConfig(configResult);
|
|
26
|
+
if (configError) {}
|
|
27
|
+
ctx.spinner.start("Bundling with tsdown...");
|
|
28
|
+
const [buildError, buildOutput] = await build({
|
|
29
|
+
config,
|
|
30
|
+
cwd
|
|
31
|
+
});
|
|
32
|
+
if (buildError) {
|
|
33
|
+
ctx.spinner.stop("Bundle failed");
|
|
34
|
+
ctx.fail(buildError.message);
|
|
35
|
+
}
|
|
36
|
+
if (!resolveCompileIntent({
|
|
37
|
+
compileFlag: ctx.args.compile,
|
|
38
|
+
configCompile: config.compile,
|
|
39
|
+
targets: ctx.args.targets
|
|
40
|
+
})) {
|
|
41
|
+
ctx.spinner.stop("Build complete");
|
|
42
|
+
ctx.logger.note(formatBuildNote({
|
|
43
|
+
cwd,
|
|
44
|
+
entryFile: buildOutput.entryFile,
|
|
45
|
+
outDir: buildOutput.outDir
|
|
46
|
+
}), "Bundle");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
ctx.spinner.message("Bundled, compiling binaries...");
|
|
50
|
+
const [compileError, compileOutput] = await compile({
|
|
51
|
+
config: mergeCompileTargets({
|
|
52
|
+
config,
|
|
53
|
+
targets: ctx.args.targets
|
|
54
|
+
}),
|
|
55
|
+
cwd,
|
|
56
|
+
onTargetComplete: (target) => ctx.spinner.message(`Compiled ${resolveTargetLabel(target)}`),
|
|
57
|
+
onTargetStart: (target) => ctx.spinner.message(`Compiling ${resolveTargetLabel(target)}...`)
|
|
58
|
+
});
|
|
59
|
+
if (compileError) {
|
|
60
|
+
ctx.spinner.stop("Compile failed");
|
|
61
|
+
ctx.fail(compileError.message);
|
|
62
|
+
}
|
|
63
|
+
ctx.spinner.stop("Build complete");
|
|
64
|
+
ctx.logger.note(formatBuildNote({
|
|
65
|
+
cwd,
|
|
66
|
+
entryFile: buildOutput.entryFile,
|
|
67
|
+
outDir: buildOutput.outDir
|
|
68
|
+
}), "Bundle");
|
|
69
|
+
ctx.logger.note(formatBinariesNote({
|
|
70
|
+
binaries: compileOutput.binaries,
|
|
71
|
+
cwd
|
|
72
|
+
}), "Binaries");
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
/**
|
|
76
|
+
* Extract a KiddConfig from a load result, falling back to empty defaults.
|
|
77
|
+
*
|
|
78
|
+
* @private
|
|
79
|
+
* @param result - The result from loadConfig, or null when loading failed.
|
|
80
|
+
* @returns The loaded config or an empty object (all KiddConfig fields are optional).
|
|
81
|
+
*/
|
|
82
|
+
function extractConfig(result) {
|
|
83
|
+
if (result) return result.config;
|
|
84
|
+
return {};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Determine whether compilation should run based on CLI flags and config.
|
|
88
|
+
*
|
|
89
|
+
* Resolution order:
|
|
90
|
+
* 1. `--targets` provided → compile (implied)
|
|
91
|
+
* 2. `--compile` flag → use its boolean value
|
|
92
|
+
* 3. Fall back to config `compile` field (truthy = compile)
|
|
93
|
+
*
|
|
94
|
+
* @private
|
|
95
|
+
* @param params - The CLI flags and config compile field.
|
|
96
|
+
* @returns Whether to run the compile step.
|
|
97
|
+
*/
|
|
98
|
+
function resolveCompileIntent(params) {
|
|
99
|
+
if (params.targets && params.targets.length > 0) return true;
|
|
100
|
+
if (params.compileFlag !== void 0) return params.compileFlag;
|
|
101
|
+
if (params.configCompile === true) return true;
|
|
102
|
+
if (typeof params.configCompile === "object") return true;
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Merge CLI `--targets` into the config's compile options.
|
|
107
|
+
*
|
|
108
|
+
* When targets are provided via CLI, they override whatever is in config.
|
|
109
|
+
* Otherwise the config is returned unchanged.
|
|
110
|
+
*
|
|
111
|
+
* @private
|
|
112
|
+
* @param params - The config and optional CLI targets.
|
|
113
|
+
* @returns A config with compile targets merged in.
|
|
114
|
+
*/
|
|
115
|
+
function mergeCompileTargets(params) {
|
|
116
|
+
if (!params.targets || params.targets.length === 0) return params.config;
|
|
117
|
+
const existingCompile = resolveExistingCompile(params.config.compile);
|
|
118
|
+
return {
|
|
119
|
+
...params.config,
|
|
120
|
+
compile: {
|
|
121
|
+
...existingCompile,
|
|
122
|
+
targets: params.targets
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Extract compile options object from the config's compile field.
|
|
128
|
+
*
|
|
129
|
+
* Returns the object as-is when it is an object, or an empty object
|
|
130
|
+
* for boolean / undefined values.
|
|
131
|
+
*
|
|
132
|
+
* @private
|
|
133
|
+
* @param value - The raw compile config value.
|
|
134
|
+
* @returns A compile options object.
|
|
135
|
+
*/
|
|
136
|
+
function resolveExistingCompile(value) {
|
|
137
|
+
if (typeof value === "object") return value;
|
|
138
|
+
return {};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Format bundle output into a multi-line string for display.
|
|
142
|
+
*
|
|
143
|
+
* @private
|
|
144
|
+
* @param params - The build output paths and working directory.
|
|
145
|
+
* @returns A formatted string with entry and output directory.
|
|
146
|
+
*/
|
|
147
|
+
function formatBuildNote(params) {
|
|
148
|
+
return [`entry ${relative(params.cwd, params.entryFile)}`, `output ${relative(params.cwd, params.outDir)}`].join("\n");
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Format compiled binaries into an aligned, multi-line string for display.
|
|
152
|
+
*
|
|
153
|
+
* @private
|
|
154
|
+
* @param params - The binaries and working directory for relative path resolution.
|
|
155
|
+
* @returns A formatted string with one line per binary.
|
|
156
|
+
*/
|
|
157
|
+
function formatBinariesNote(params) {
|
|
158
|
+
const maxLen = Math.max(...params.binaries.map((b) => b.label.length));
|
|
159
|
+
return params.binaries.map((binary) => `${binary.label.padEnd(maxLen)} ${relative(params.cwd, binary.path)}`).join("\n");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
//#endregion
|
|
163
|
+
export { buildCommand as default };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Command } from "@kidd-cli/core";
|
|
2
|
+
|
|
3
|
+
//#region src/commands/commands.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Display the command tree for a kidd CLI project.
|
|
6
|
+
*
|
|
7
|
+
* Loads the project's `kidd.config.ts` to locate the commands directory,
|
|
8
|
+
* scans it with the autoloader, and prints an ASCII tree of all discovered
|
|
9
|
+
* commands and subcommands.
|
|
10
|
+
*/
|
|
11
|
+
declare const commandsCommand: Command;
|
|
12
|
+
//#endregion
|
|
13
|
+
export { commandsCommand as default };
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { autoload, command } from "@kidd-cli/core";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { loadConfig } from "@kidd-cli/config/loader";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
|
|
6
|
+
//#region src/commands/commands.ts
|
|
7
|
+
/**
|
|
8
|
+
* Display the command tree for a kidd CLI project.
|
|
9
|
+
*
|
|
10
|
+
* Loads the project's `kidd.config.ts` to locate the commands directory,
|
|
11
|
+
* scans it with the autoloader, and prints an ASCII tree of all discovered
|
|
12
|
+
* commands and subcommands.
|
|
13
|
+
*/
|
|
14
|
+
const commandsCommand = command({
|
|
15
|
+
description: "Display the command tree for a kidd CLI project",
|
|
16
|
+
handler: async (ctx) => {
|
|
17
|
+
const cwd = process.cwd();
|
|
18
|
+
const [configError, configResult] = await loadConfig({ cwd });
|
|
19
|
+
const config = extractConfig(configResult);
|
|
20
|
+
if (configError) {}
|
|
21
|
+
const commandsDir = join(cwd, config.commands ?? "commands");
|
|
22
|
+
if (!existsSync(commandsDir)) ctx.fail(`Commands directory not found: ${commandsDir}`);
|
|
23
|
+
ctx.spinner.start("Scanning commands...");
|
|
24
|
+
const tree = await buildTree(await autoload({ dir: commandsDir }));
|
|
25
|
+
ctx.spinner.stop("Commands");
|
|
26
|
+
if (tree.length === 0) {
|
|
27
|
+
ctx.output.write("No commands found");
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
ctx.output.raw(`${renderTree(tree)}\n`);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
/**
|
|
34
|
+
* Extract a KiddConfig from a load result, falling back to empty defaults.
|
|
35
|
+
*
|
|
36
|
+
* @private
|
|
37
|
+
* @param result - The result from loadConfig, or null when loading failed.
|
|
38
|
+
* @returns The loaded config or an empty object (all KiddConfig fields are optional).
|
|
39
|
+
*/
|
|
40
|
+
function extractConfig(result) {
|
|
41
|
+
if (result) return result.config;
|
|
42
|
+
return {};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Resolve a command's subcommands field, which may be a Promise, a map, or undefined.
|
|
46
|
+
*
|
|
47
|
+
* @private
|
|
48
|
+
* @param commands - The raw subcommands value from a Command object.
|
|
49
|
+
* @returns The resolved CommandMap, or an empty object when none exist.
|
|
50
|
+
*/
|
|
51
|
+
async function resolveSubcommands(commands) {
|
|
52
|
+
if (!commands) return {};
|
|
53
|
+
if (commands instanceof Promise) return commands;
|
|
54
|
+
return commands;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Recursively build a sorted tree of entries from a CommandMap.
|
|
58
|
+
*
|
|
59
|
+
* @private
|
|
60
|
+
* @param commandMap - The map of command names to Command objects.
|
|
61
|
+
* @returns A sorted array of TreeEntry nodes.
|
|
62
|
+
*/
|
|
63
|
+
async function buildTree(commandMap) {
|
|
64
|
+
const entries = Object.entries(commandMap).toSorted(([a], [b]) => a.localeCompare(b));
|
|
65
|
+
return Promise.all(entries.map(async ([name, cmd]) => {
|
|
66
|
+
return {
|
|
67
|
+
children: await buildTree(await resolveSubcommands(cmd.commands)),
|
|
68
|
+
description: cmd.description ?? "",
|
|
69
|
+
name
|
|
70
|
+
};
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Render a tree of entries into an ASCII tree string.
|
|
75
|
+
*
|
|
76
|
+
* @private
|
|
77
|
+
* @param entries - The top-level tree entries to render.
|
|
78
|
+
* @returns The formatted tree string.
|
|
79
|
+
*/
|
|
80
|
+
function renderTree(entries) {
|
|
81
|
+
return renderEntries(entries, "").join("\n");
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Recursively render tree entries with proper box-drawing connectors.
|
|
85
|
+
*
|
|
86
|
+
* @private
|
|
87
|
+
* @param entries - The entries at this level.
|
|
88
|
+
* @param prefix - The prefix string for indentation.
|
|
89
|
+
* @returns An array of formatted lines.
|
|
90
|
+
*/
|
|
91
|
+
function renderEntries(entries, prefix) {
|
|
92
|
+
return entries.flatMap((entry, index) => {
|
|
93
|
+
const isLast = index === entries.length - 1;
|
|
94
|
+
const connector = resolveConnector(isLast);
|
|
95
|
+
const childPrefix = resolveChildPrefix(isLast);
|
|
96
|
+
return [`${prefix}${connector}${formatLabel(entry.name, entry.description)}`, ...renderEntries(entry.children, `${prefix}${childPrefix}`)];
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Get the box-drawing connector for a tree entry.
|
|
101
|
+
*
|
|
102
|
+
* @private
|
|
103
|
+
* @param isLast - Whether this is the last entry at its level.
|
|
104
|
+
* @returns The connector string.
|
|
105
|
+
*/
|
|
106
|
+
function resolveConnector(isLast) {
|
|
107
|
+
if (isLast) return "└── ";
|
|
108
|
+
return "├── ";
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Get the indentation prefix for children of a tree entry.
|
|
112
|
+
*
|
|
113
|
+
* @private
|
|
114
|
+
* @param isLast - Whether the parent is the last entry at its level.
|
|
115
|
+
* @returns The child prefix string.
|
|
116
|
+
*/
|
|
117
|
+
function resolveChildPrefix(isLast) {
|
|
118
|
+
if (isLast) return " ";
|
|
119
|
+
return "│ ";
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Format a command name and description into a tree label.
|
|
123
|
+
*
|
|
124
|
+
* @private
|
|
125
|
+
* @param name - The command name.
|
|
126
|
+
* @param description - The command description (may be empty).
|
|
127
|
+
* @returns The formatted label string.
|
|
128
|
+
*/
|
|
129
|
+
function formatLabel(name, description) {
|
|
130
|
+
if (description) return `${name} — ${description}`;
|
|
131
|
+
return name;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
//#endregion
|
|
135
|
+
export { commandsCommand as default };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Command } from "@kidd-cli/core";
|
|
2
|
+
|
|
3
|
+
//#region src/commands/dev.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Start a kidd CLI project in development mode with file watching.
|
|
6
|
+
*
|
|
7
|
+
* Loads the project's `kidd.config.ts`, starts tsdown in watch mode, and
|
|
8
|
+
* logs rebuild status on each successful build.
|
|
9
|
+
*/
|
|
10
|
+
declare const devCommand: Command;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { devCommand as default };
|