@mindees/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 +31 -0
- package/README.md +70 -0
- package/dist/ai.d.ts +15 -0
- package/dist/ai.d.ts.map +1 -0
- package/dist/ai.js +57 -0
- package/dist/ai.js.map +1 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +94 -0
- package/dist/bin.js.map +1 -0
- package/dist/build.d.ts +37 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.js +97 -0
- package/dist/build.js.map +1 -0
- package/dist/cli.d.ts +32 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +194 -0
- package/dist/cli.js.map +1 -0
- package/dist/create-target.d.ts +36 -0
- package/dist/create-target.d.ts.map +1 -0
- package/dist/create-target.js +90 -0
- package/dist/create-target.js.map +1 -0
- package/dist/dev.d.ts +32 -0
- package/dist/dev.d.ts.map +1 -0
- package/dist/dev.js +64 -0
- package/dist/dev.js.map +1 -0
- package/dist/doctor.d.ts +15 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +92 -0
- package/dist/doctor.js.map +1 -0
- package/dist/fs.d.ts +28 -0
- package/dist/fs.d.ts.map +1 -0
- package/dist/fs.js +39 -0
- package/dist/fs.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/nl.d.ts +26 -0
- package/dist/nl.d.ts.map +1 -0
- package/dist/nl.js +49 -0
- package/dist/nl.js.map +1 -0
- package/dist/scaffold.d.ts +32 -0
- package/dist/scaffold.d.ts.map +1 -0
- package/dist/scaffold.js +52 -0
- package/dist/scaffold.js.map +1 -0
- package/dist/templates.d.ts +32 -0
- package/dist/templates.d.ts.map +1 -0
- package/dist/templates.js +131 -0
- package/dist/templates.js.map +1 -0
- package/dist/types.d.ts +54 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/version.d.ts +17 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +18 -0
- package/dist/version.js.map +1 -0
- package/package.json +37 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { runAiCommand } from "./ai.js";
|
|
2
|
+
import { buildProject } from "./build.js";
|
|
3
|
+
import { quoteShellPath, resolveCreateTarget } from "./create-target.js";
|
|
4
|
+
import { doctorSummary, renderDoctor, runDoctor } from "./doctor.js";
|
|
5
|
+
import { templateNames } from "./templates.js";
|
|
6
|
+
import { naturalLanguageToTemplate } from "./nl.js";
|
|
7
|
+
import { scaffold } from "./scaffold.js";
|
|
8
|
+
import { parseArgs } from "node:util";
|
|
9
|
+
//#region src/cli.ts
|
|
10
|
+
/**
|
|
11
|
+
* Forge CLI dispatch — `mindees <command> [args]`.
|
|
12
|
+
*
|
|
13
|
+
* `runCli` is a pure function of (argv, context) → exit code, writing structured
|
|
14
|
+
* output through an injected {@link Writer}. All side-effecting capabilities
|
|
15
|
+
* (filesystem, env probe) are injected via {@link CliContext}, so the entire CLI
|
|
16
|
+
* is deterministically testable; the thin `bin` entrypoint wires real adapters.
|
|
17
|
+
*
|
|
18
|
+
* @module
|
|
19
|
+
*/
|
|
20
|
+
const HELP = `mindees — the MindeesNative CLI (Forge)
|
|
21
|
+
|
|
22
|
+
Usage: mindees <command> [options]
|
|
23
|
+
|
|
24
|
+
Commands:
|
|
25
|
+
create <name> Scaffold a new app (--template <name>, --force)
|
|
26
|
+
build Type-check + compile the project (--out-dir <dir>)
|
|
27
|
+
dev Build and rebuild on change (developer preview)
|
|
28
|
+
doctor Diagnose your environment
|
|
29
|
+
info Show CLI + environment info
|
|
30
|
+
ai explain <err> Explain an error with AI (needs MINDEES_AI_* env)
|
|
31
|
+
help Show this help
|
|
32
|
+
|
|
33
|
+
Run \`mindees create --help\` style flags inline. Templates: ${templateNames().join(", ")}.`;
|
|
34
|
+
const CREATE_HELP = `Usage: mindees create <name-or-path> [options]
|
|
35
|
+
|
|
36
|
+
Options:
|
|
37
|
+
-t, --template <name> Template to scaffold (${templateNames().join(", ")})
|
|
38
|
+
-p, --prompt <text> Pick a template from a short prompt
|
|
39
|
+
--force Overwrite a non-empty target directory
|
|
40
|
+
-h, --help Show this help`;
|
|
41
|
+
function out(write, text) {
|
|
42
|
+
write({
|
|
43
|
+
stream: "out",
|
|
44
|
+
text
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
function err(write, text) {
|
|
48
|
+
write({
|
|
49
|
+
stream: "err",
|
|
50
|
+
text
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Run the CLI. Returns a {@link CommandResult} with the process exit code.
|
|
55
|
+
* Never throws for expected failures — it reports them and returns non-zero.
|
|
56
|
+
*/
|
|
57
|
+
function runCli(argv, ctx) {
|
|
58
|
+
const [command, ...rest] = argv;
|
|
59
|
+
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
60
|
+
out(ctx.write, HELP);
|
|
61
|
+
return { exitCode: 0 };
|
|
62
|
+
}
|
|
63
|
+
if (command === "--version" || command === "-v" || command === "version") {
|
|
64
|
+
out(ctx.write, ctx.version);
|
|
65
|
+
return { exitCode: 0 };
|
|
66
|
+
}
|
|
67
|
+
switch (command) {
|
|
68
|
+
case "create": return cmdCreate(rest, ctx);
|
|
69
|
+
case "build": return cmdBuild(rest, ctx);
|
|
70
|
+
case "dev": return cmdDev(ctx);
|
|
71
|
+
case "doctor": return cmdDoctor(ctx);
|
|
72
|
+
case "info": return cmdInfo(ctx);
|
|
73
|
+
default:
|
|
74
|
+
err(ctx.write, `Unknown command "${command}". Run \`mindees help\`.`);
|
|
75
|
+
return { exitCode: 1 };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The async CLI entry. Handles the model-calling `ai` command (which is asynchronous) and
|
|
80
|
+
* delegates every synchronous command to {@link runCli}. The `bin` calls this; tests can call
|
|
81
|
+
* either (sync commands stay testable through `runCli`).
|
|
82
|
+
*/
|
|
83
|
+
function runCliAsync(argv, ctx) {
|
|
84
|
+
const [command, ...rest] = argv;
|
|
85
|
+
if (command === "ai") return runAiCommand(rest, {
|
|
86
|
+
write: ctx.write,
|
|
87
|
+
...ctx.aiBackend ? { backend: ctx.aiBackend } : {}
|
|
88
|
+
});
|
|
89
|
+
return Promise.resolve(runCli(argv, ctx));
|
|
90
|
+
}
|
|
91
|
+
function cmdCreate(args, ctx) {
|
|
92
|
+
const { values, positionals } = parseArgs({
|
|
93
|
+
args: [...args],
|
|
94
|
+
allowPositionals: true,
|
|
95
|
+
strict: false,
|
|
96
|
+
options: {
|
|
97
|
+
help: {
|
|
98
|
+
type: "boolean",
|
|
99
|
+
short: "h"
|
|
100
|
+
},
|
|
101
|
+
template: {
|
|
102
|
+
type: "string",
|
|
103
|
+
short: "t"
|
|
104
|
+
},
|
|
105
|
+
force: { type: "boolean" },
|
|
106
|
+
prompt: {
|
|
107
|
+
type: "string",
|
|
108
|
+
short: "p"
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
if (values.help === true || positionals[0] === "help") {
|
|
113
|
+
out(ctx.write, CREATE_HELP);
|
|
114
|
+
return { exitCode: 0 };
|
|
115
|
+
}
|
|
116
|
+
const name = positionals[0];
|
|
117
|
+
if (!name) {
|
|
118
|
+
err(ctx.write, "create: missing app name or target path. Usage: mindees create <name-or-path>");
|
|
119
|
+
return { exitCode: 1 };
|
|
120
|
+
}
|
|
121
|
+
const explicitTemplate = typeof values.template === "string" && values.template.length > 0 ? values.template : void 0;
|
|
122
|
+
let template = explicitTemplate ?? "blank";
|
|
123
|
+
if (explicitTemplate === void 0 && typeof values.prompt === "string" && values.prompt.length > 0) {
|
|
124
|
+
const picked = naturalLanguageToTemplate(values.prompt);
|
|
125
|
+
template = picked.template;
|
|
126
|
+
out(ctx.write, `Interpreted prompt → "${picked.template}" template (${picked.reason}).`);
|
|
127
|
+
}
|
|
128
|
+
const target = resolveCreateTarget(name, ctx.cwd);
|
|
129
|
+
if (!target.ok) {
|
|
130
|
+
err(ctx.write, target.error);
|
|
131
|
+
return { exitCode: 1 };
|
|
132
|
+
}
|
|
133
|
+
const result = scaffold(ctx.fs, {
|
|
134
|
+
appName: target.packageName,
|
|
135
|
+
targetDir: target.targetDir,
|
|
136
|
+
template,
|
|
137
|
+
force: values.force === true
|
|
138
|
+
});
|
|
139
|
+
if (!result.ok) {
|
|
140
|
+
err(ctx.write, result.error ?? "create failed");
|
|
141
|
+
return { exitCode: 1 };
|
|
142
|
+
}
|
|
143
|
+
out(ctx.write, `Created "${target.packageName}" from the ${result.template} template (${result.written.length} files).`);
|
|
144
|
+
out(ctx.write, `Next: cd ${quoteShellPath(target.displayDir)} && pnpm install && mindees dev`);
|
|
145
|
+
return { exitCode: 0 };
|
|
146
|
+
}
|
|
147
|
+
function cmdBuild(args, ctx) {
|
|
148
|
+
const { values } = parseArgs({
|
|
149
|
+
args: [...args],
|
|
150
|
+
allowPositionals: true,
|
|
151
|
+
strict: false,
|
|
152
|
+
options: {
|
|
153
|
+
"out-dir": { type: "string" },
|
|
154
|
+
"no-source-map": { type: "boolean" }
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
const result = buildProject(ctx.fs, {
|
|
158
|
+
root: ctx.cwd,
|
|
159
|
+
outDir: typeof values["out-dir"] === "string" ? values["out-dir"] : `${ctx.cwd}/dist`,
|
|
160
|
+
sourceMap: values["no-source-map"] !== true
|
|
161
|
+
});
|
|
162
|
+
for (const d of result.diagnostics) {
|
|
163
|
+
const where = d.file ? `${d.file}${d.position ? `:${d.position.line}` : ""}: ` : "";
|
|
164
|
+
err(ctx.write, `${d.severity} ${d.code} ${where}${d.message}`);
|
|
165
|
+
}
|
|
166
|
+
if (!result.ok) {
|
|
167
|
+
err(ctx.write, "Build failed: fix the type errors above.");
|
|
168
|
+
return { exitCode: 1 };
|
|
169
|
+
}
|
|
170
|
+
out(ctx.write, `Built ${result.compiled.length} module(s); flattened ${result.stats.flattenedNodes}/${result.stats.totalElements} elements.`);
|
|
171
|
+
return { exitCode: 0 };
|
|
172
|
+
}
|
|
173
|
+
function cmdDev(ctx) {
|
|
174
|
+
out(ctx.write, "mindees dev — developer preview.");
|
|
175
|
+
out(ctx.write, "The rebuild-on-change orchestrator is available via startDev(); the");
|
|
176
|
+
out(ctx.write, "HTTP/HMR transport is being finalized. Use `mindees build` for now.");
|
|
177
|
+
return { exitCode: 0 };
|
|
178
|
+
}
|
|
179
|
+
function cmdDoctor(ctx) {
|
|
180
|
+
const checks = runDoctor(ctx.env);
|
|
181
|
+
for (const line of renderDoctor(checks)) out(ctx.write, line);
|
|
182
|
+
return { exitCode: doctorSummary(checks) === "fail" ? 1 : 0 };
|
|
183
|
+
}
|
|
184
|
+
function cmdInfo(ctx) {
|
|
185
|
+
out(ctx.write, `mindees CLI ${ctx.version}`);
|
|
186
|
+
out(ctx.write, `Node ${ctx.env.nodeVersion}`);
|
|
187
|
+
out(ctx.write, `Package manager: ${ctx.env.packageManager ? `${ctx.env.packageManager.name} ${ctx.env.packageManager.version}` : "none"}`);
|
|
188
|
+
out(ctx.write, `Templates: ${templateNames().join(", ")}`);
|
|
189
|
+
return { exitCode: 0 };
|
|
190
|
+
}
|
|
191
|
+
//#endregion
|
|
192
|
+
export { runCli, runCliAsync };
|
|
193
|
+
|
|
194
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["/**\n * Forge CLI dispatch — `mindees <command> [args]`.\n *\n * `runCli` is a pure function of (argv, context) → exit code, writing structured\n * output through an injected {@link Writer}. All side-effecting capabilities\n * (filesystem, env probe) are injected via {@link CliContext}, so the entire CLI\n * is deterministically testable; the thin `bin` entrypoint wires real adapters.\n *\n * @module\n */\n\nimport { parseArgs } from 'node:util'\nimport type { AiBackend } from '@mindees/ai'\nimport { runAiCommand } from './ai'\nimport { buildProject } from './build'\nimport { quoteShellPath, resolveCreateTarget } from './create-target'\nimport { doctorSummary, renderDoctor, runDoctor } from './doctor'\nimport type { FileSystem } from './fs'\nimport { naturalLanguageToTemplate } from './nl'\nimport { scaffold } from './scaffold'\nimport { DEFAULT_TEMPLATE, templateNames } from './templates'\nimport type { CommandResult, EnvProbe, Writer } from './types'\n\n/** Everything the CLI needs from the outside world (injected for testability). */\nexport interface CliContext {\n fs: FileSystem\n env: EnvProbe\n /** Working directory (where `create` writes, what `build` reads). */\n cwd: string\n /** CLI version string (from package metadata). */\n version: string\n /** Output sink. */\n write: Writer\n /** AI backend for `ai` commands (wired from `MINDEES_AI_*` env in `bin`). */\n aiBackend?: AiBackend\n}\n\nconst HELP = `mindees — the MindeesNative CLI (Forge)\n\nUsage: mindees <command> [options]\n\nCommands:\n create <name> Scaffold a new app (--template <name>, --force)\n build Type-check + compile the project (--out-dir <dir>)\n dev Build and rebuild on change (developer preview)\n doctor Diagnose your environment\n info Show CLI + environment info\n ai explain <err> Explain an error with AI (needs MINDEES_AI_* env)\n help Show this help\n\nRun \\`mindees create --help\\` style flags inline. Templates: ${templateNames().join(', ')}.`\n\nconst CREATE_HELP = `Usage: mindees create <name-or-path> [options]\n\nOptions:\n -t, --template <name> Template to scaffold (${templateNames().join(', ')})\n -p, --prompt <text> Pick a template from a short prompt\n --force Overwrite a non-empty target directory\n -h, --help Show this help`\n\nfunction out(write: Writer, text: string): void {\n write({ stream: 'out', text })\n}\nfunction err(write: Writer, text: string): void {\n write({ stream: 'err', text })\n}\n\n/**\n * Run the CLI. Returns a {@link CommandResult} with the process exit code.\n * Never throws for expected failures — it reports them and returns non-zero.\n */\nexport function runCli(argv: readonly string[], ctx: CliContext): CommandResult {\n const [command, ...rest] = argv\n\n if (!command || command === 'help' || command === '--help' || command === '-h') {\n out(ctx.write, HELP)\n return { exitCode: 0 }\n }\n\n if (command === '--version' || command === '-v' || command === 'version') {\n out(ctx.write, ctx.version)\n return { exitCode: 0 }\n }\n\n switch (command) {\n case 'create':\n return cmdCreate(rest, ctx)\n case 'build':\n return cmdBuild(rest, ctx)\n case 'dev':\n return cmdDev(ctx)\n case 'doctor':\n return cmdDoctor(ctx)\n case 'info':\n return cmdInfo(ctx)\n default:\n err(ctx.write, `Unknown command \"${command}\". Run \\`mindees help\\`.`)\n return { exitCode: 1 }\n }\n}\n\n/**\n * The async CLI entry. Handles the model-calling `ai` command (which is asynchronous) and\n * delegates every synchronous command to {@link runCli}. The `bin` calls this; tests can call\n * either (sync commands stay testable through `runCli`).\n */\nexport function runCliAsync(argv: readonly string[], ctx: CliContext): Promise<CommandResult> {\n const [command, ...rest] = argv\n if (command === 'ai') {\n return runAiCommand(rest, {\n write: ctx.write,\n ...(ctx.aiBackend ? { backend: ctx.aiBackend } : {}),\n })\n }\n return Promise.resolve(runCli(argv, ctx))\n}\n\nfunction cmdCreate(args: readonly string[], ctx: CliContext): CommandResult {\n const { values, positionals } = parseArgs({\n args: [...args],\n allowPositionals: true,\n strict: false,\n options: {\n help: { type: 'boolean', short: 'h' },\n template: { type: 'string', short: 't' },\n force: { type: 'boolean' },\n prompt: { type: 'string', short: 'p' },\n },\n })\n\n if (values.help === true || positionals[0] === 'help') {\n out(ctx.write, CREATE_HELP)\n return { exitCode: 0 }\n }\n\n const name = positionals[0]\n if (!name) {\n err(ctx.write, 'create: missing app name or target path. Usage: mindees create <name-or-path>')\n return { exitCode: 1 }\n }\n\n // NL → template: `--prompt \"a counter app\"` picks a template deterministically\n // (offline). Real AI generation arrives with Synapse in Phase 10; until then\n // this is an honest keyword-based mapping that never blocks `create`. An\n // explicit `--template` always wins; the prompt only resolves a template when\n // the caller didn't choose one (mirrors `create-mindees`'s runCreate so both\n // entrypoints agree on precedence).\n // Treat a present-but-empty `--template \"\"` as \"not chosen\" (defer to prompt/default),\n // matching create-mindees's runCreate so both entrypoints agree on precedence.\n const explicitTemplate =\n typeof values.template === 'string' && values.template.length > 0 ? values.template : undefined\n let template = explicitTemplate ?? DEFAULT_TEMPLATE\n if (\n explicitTemplate === undefined &&\n typeof values.prompt === 'string' &&\n values.prompt.length > 0\n ) {\n const picked = naturalLanguageToTemplate(values.prompt)\n template = picked.template\n out(ctx.write, `Interpreted prompt → \"${picked.template}\" template (${picked.reason}).`)\n }\n\n const target = resolveCreateTarget(name, ctx.cwd)\n if (!target.ok) {\n err(ctx.write, target.error)\n return { exitCode: 1 }\n }\n\n const result = scaffold(ctx.fs, {\n appName: target.packageName,\n targetDir: target.targetDir,\n template,\n force: values.force === true,\n })\n\n if (!result.ok) {\n err(ctx.write, result.error ?? 'create failed')\n return { exitCode: 1 }\n }\n\n out(\n ctx.write,\n `Created \"${target.packageName}\" from the ${result.template} template (${result.written.length} files).`,\n )\n out(ctx.write, `Next: cd ${quoteShellPath(target.displayDir)} && pnpm install && mindees dev`)\n return { exitCode: 0 }\n}\n\nfunction cmdBuild(args: readonly string[], ctx: CliContext): CommandResult {\n const { values } = parseArgs({\n args: [...args],\n allowPositionals: true,\n strict: false,\n options: { 'out-dir': { type: 'string' }, 'no-source-map': { type: 'boolean' } },\n })\n\n const result = buildProject(ctx.fs, {\n root: ctx.cwd,\n outDir: typeof values['out-dir'] === 'string' ? values['out-dir'] : `${ctx.cwd}/dist`,\n sourceMap: values['no-source-map'] !== true,\n })\n\n for (const d of result.diagnostics) {\n const where = d.file ? `${d.file}${d.position ? `:${d.position.line}` : ''}: ` : ''\n err(ctx.write, `${d.severity} ${d.code} ${where}${d.message}`)\n }\n\n if (!result.ok) {\n err(ctx.write, 'Build failed: fix the type errors above.')\n return { exitCode: 1 }\n }\n out(\n ctx.write,\n `Built ${result.compiled.length} module(s); flattened ${result.stats.flattenedNodes}/${result.stats.totalElements} elements.`,\n )\n return { exitCode: 0 }\n}\n\nfunction cmdDev(ctx: CliContext): CommandResult {\n // The dev server transport (HTTP + HMR socket) is a developer-preview layer in\n // `bin`. The CLI command here reports how to use it; the tested rebuild\n // orchestrator lives in `dev.ts` (`startDev`).\n out(ctx.write, 'mindees dev — developer preview.')\n out(ctx.write, 'The rebuild-on-change orchestrator is available via startDev(); the')\n out(ctx.write, 'HTTP/HMR transport is being finalized. Use `mindees build` for now.')\n return { exitCode: 0 }\n}\n\nfunction cmdDoctor(ctx: CliContext): CommandResult {\n const checks = runDoctor(ctx.env)\n for (const line of renderDoctor(checks)) out(ctx.write, line)\n const summary = doctorSummary(checks)\n return { exitCode: summary === 'fail' ? 1 : 0 }\n}\n\nfunction cmdInfo(ctx: CliContext): CommandResult {\n out(ctx.write, `mindees CLI ${ctx.version}`)\n out(ctx.write, `Node ${ctx.env.nodeVersion}`)\n out(\n ctx.write,\n `Package manager: ${ctx.env.packageManager ? `${ctx.env.packageManager.name} ${ctx.env.packageManager.version}` : 'none'}`,\n )\n out(ctx.write, `Templates: ${templateNames().join(', ')}`)\n return { exitCode: 0 }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
//#region src/create-target.d.ts
|
|
2
|
+
/** Resolved create target metadata shared by `mindees create` and `create-mindees`. */
|
|
3
|
+
interface CreateTarget {
|
|
4
|
+
/** Raw user input after trimming. */
|
|
5
|
+
input: string;
|
|
6
|
+
/** Target directory with POSIX separators and normalized `.` / `..` segments. */
|
|
7
|
+
targetDir: string;
|
|
8
|
+
/** npm-safe package name derived from the final path segment. */
|
|
9
|
+
packageName: string;
|
|
10
|
+
/** Directory string to show in follow-up `cd` guidance. */
|
|
11
|
+
displayDir: string;
|
|
12
|
+
}
|
|
13
|
+
/** Outcome of create target resolution. */
|
|
14
|
+
type CreateTargetResult = ({
|
|
15
|
+
ok: true;
|
|
16
|
+
} & CreateTarget) | {
|
|
17
|
+
ok: false;
|
|
18
|
+
error: string;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Quote a path for the `cd <path> && ...` command hints printed by create commands.
|
|
22
|
+
* Uses POSIX **single quotes**: everything inside is literal, so a directory name
|
|
23
|
+
* containing `$(...)`, backticks, `$VAR`, or `\` cannot be expanded/executed when the
|
|
24
|
+
* hint is pasted into a shell (double quotes would still expand those). An embedded
|
|
25
|
+
* single quote is closed, escaped, and reopened (`'\''`).
|
|
26
|
+
*/
|
|
27
|
+
declare function quoteShellPath(path: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Resolve a `create` positional into a real target directory plus an npm-safe
|
|
30
|
+
* package name. Accepts simple names, relative paths, and absolute Windows/POSIX
|
|
31
|
+
* paths without letting the package name inherit path separators.
|
|
32
|
+
*/
|
|
33
|
+
declare function resolveCreateTarget(input: string, cwd?: string): CreateTargetResult;
|
|
34
|
+
//#endregion
|
|
35
|
+
export { CreateTarget, CreateTargetResult, quoteShellPath, resolveCreateTarget };
|
|
36
|
+
//# sourceMappingURL=create-target.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-target.d.ts","names":[],"sources":["../src/create-target.ts"],"mappings":";;UACiB,YAAA;EAAY;EAE3B,KAAA;EAF2B;EAI3B,SAAA;EAAA;EAEA,WAAA;EAEA;EAAA,UAAA;AAAA;AAIF;AAAA,KAAY,kBAAA;EACL,EAAA;AAAA,IAAa,YAAY;EAE1B,EAAA;EACA,KAAA;AAAA;;;AAAK;AA2EX;;;;iBAAgB,cAAA,CAAe,IAAY;AAS3C;;;;;AAAA,iBAAgB,mBAAA,CAAoB,KAAA,UAAe,GAAA,YAAY,kBAAkB"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
//#region src/create-target.ts
|
|
2
|
+
const WINDOWS_DRIVE_ROOT = /^([a-zA-Z]:)\//;
|
|
3
|
+
function toPosixPath(path) {
|
|
4
|
+
return path.replace(/\\/g, "/");
|
|
5
|
+
}
|
|
6
|
+
function hasAbsoluteRoot(path) {
|
|
7
|
+
return path.startsWith("/") || WINDOWS_DRIVE_ROOT.test(path);
|
|
8
|
+
}
|
|
9
|
+
function normalizeLogicalPath(path) {
|
|
10
|
+
const normalized = toPosixPath(path);
|
|
11
|
+
const drive = normalized.match(WINDOWS_DRIVE_ROOT)?.[1];
|
|
12
|
+
const root = drive ? `${drive}/` : normalized.startsWith("/") ? "/" : "";
|
|
13
|
+
const body = root ? normalized.slice(root.length) : normalized;
|
|
14
|
+
const parts = [];
|
|
15
|
+
for (const segment of body.split("/")) {
|
|
16
|
+
if (!segment || segment === ".") continue;
|
|
17
|
+
if (segment === "..") {
|
|
18
|
+
const previous = parts.at(-1);
|
|
19
|
+
if (previous && previous !== "..") parts.pop();
|
|
20
|
+
else if (!root) parts.push(segment);
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
parts.push(segment);
|
|
24
|
+
}
|
|
25
|
+
const suffix = parts.join("/");
|
|
26
|
+
if (root) return suffix ? `${root}${suffix}` : root;
|
|
27
|
+
return suffix || ".";
|
|
28
|
+
}
|
|
29
|
+
function pathBasename(path) {
|
|
30
|
+
const normalized = toPosixPath(path).replace(/\/+$/, "");
|
|
31
|
+
if (/^[a-zA-Z]:$/.test(normalized)) return "";
|
|
32
|
+
return normalized.split("/").filter(Boolean).at(-1) ?? "";
|
|
33
|
+
}
|
|
34
|
+
function isPathLike(input) {
|
|
35
|
+
const normalized = toPosixPath(input);
|
|
36
|
+
return normalized.includes("/") || normalized === "." || normalized === ".." || normalized.startsWith("./") || normalized.startsWith("../") || hasAbsoluteRoot(normalized);
|
|
37
|
+
}
|
|
38
|
+
function sanitizePackageName(rawName) {
|
|
39
|
+
return rawName.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 214).replace(/-+$/g, "");
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Quote a path for the `cd <path> && ...` command hints printed by create commands.
|
|
43
|
+
* Uses POSIX **single quotes**: everything inside is literal, so a directory name
|
|
44
|
+
* containing `$(...)`, backticks, `$VAR`, or `\` cannot be expanded/executed when the
|
|
45
|
+
* hint is pasted into a shell (double quotes would still expand those). An embedded
|
|
46
|
+
* single quote is closed, escaped, and reopened (`'\''`).
|
|
47
|
+
*/
|
|
48
|
+
function quoteShellPath(path) {
|
|
49
|
+
return `'${path.replace(/'/g, "'\\''")}'`;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Resolve a `create` positional into a real target directory plus an npm-safe
|
|
53
|
+
* package name. Accepts simple names, relative paths, and absolute Windows/POSIX
|
|
54
|
+
* paths without letting the package name inherit path separators.
|
|
55
|
+
*/
|
|
56
|
+
function resolveCreateTarget(input, cwd = ".") {
|
|
57
|
+
const trimmed = input.trim();
|
|
58
|
+
if (!trimmed) return {
|
|
59
|
+
ok: false,
|
|
60
|
+
error: "create: missing app name or target path."
|
|
61
|
+
};
|
|
62
|
+
if (toPosixPath(trimmed).startsWith("//")) return {
|
|
63
|
+
ok: false,
|
|
64
|
+
error: `UNC paths are not supported: "${input}". Use a drive-letter or relative path.`
|
|
65
|
+
};
|
|
66
|
+
const basename = pathBasename(trimmed);
|
|
67
|
+
if (!basename || basename === "." || basename === "..") return {
|
|
68
|
+
ok: false,
|
|
69
|
+
error: `Could not derive a package name from "${input}".`
|
|
70
|
+
};
|
|
71
|
+
const packageName = sanitizePackageName(basename);
|
|
72
|
+
if (!packageName) return {
|
|
73
|
+
ok: false,
|
|
74
|
+
error: `Could not derive a valid npm package name from "${basename}". Use a name with letters or numbers.`
|
|
75
|
+
};
|
|
76
|
+
const targetInput = toPosixPath(trimmed);
|
|
77
|
+
const cwdPath = normalizeLogicalPath(cwd || ".");
|
|
78
|
+
const targetDir = hasAbsoluteRoot(targetInput) ? normalizeLogicalPath(targetInput) : normalizeLogicalPath(cwdPath === "." ? targetInput : `${cwdPath}/${targetInput}`);
|
|
79
|
+
return {
|
|
80
|
+
ok: true,
|
|
81
|
+
input: trimmed,
|
|
82
|
+
targetDir,
|
|
83
|
+
packageName,
|
|
84
|
+
displayDir: isPathLike(trimmed) ? targetDir : trimmed
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
//#endregion
|
|
88
|
+
export { quoteShellPath, resolveCreateTarget };
|
|
89
|
+
|
|
90
|
+
//# sourceMappingURL=create-target.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-target.js","names":[],"sources":["../src/create-target.ts"],"sourcesContent":["/** Resolved create target metadata shared by `mindees create` and `create-mindees`. */\nexport interface CreateTarget {\n /** Raw user input after trimming. */\n input: string\n /** Target directory with POSIX separators and normalized `.` / `..` segments. */\n targetDir: string\n /** npm-safe package name derived from the final path segment. */\n packageName: string\n /** Directory string to show in follow-up `cd` guidance. */\n displayDir: string\n}\n\n/** Outcome of create target resolution. */\nexport type CreateTargetResult =\n | ({ ok: true } & CreateTarget)\n | {\n ok: false\n error: string\n }\n\nconst WINDOWS_DRIVE_ROOT = /^([a-zA-Z]:)\\//\n\nfunction toPosixPath(path: string): string {\n return path.replace(/\\\\/g, '/')\n}\n\nfunction hasAbsoluteRoot(path: string): boolean {\n return path.startsWith('/') || WINDOWS_DRIVE_ROOT.test(path)\n}\n\nfunction normalizeLogicalPath(path: string): string {\n const normalized = toPosixPath(path)\n const drive = normalized.match(WINDOWS_DRIVE_ROOT)?.[1]\n const root = drive ? `${drive}/` : normalized.startsWith('/') ? '/' : ''\n const body = root ? normalized.slice(root.length) : normalized\n const parts: string[] = []\n\n for (const segment of body.split('/')) {\n if (!segment || segment === '.') continue\n if (segment === '..') {\n const previous = parts.at(-1)\n if (previous && previous !== '..') {\n parts.pop()\n } else if (!root) {\n parts.push(segment)\n }\n continue\n }\n parts.push(segment)\n }\n\n const suffix = parts.join('/')\n if (root) return suffix ? `${root}${suffix}` : root\n return suffix || '.'\n}\n\nfunction pathBasename(path: string): string {\n const normalized = toPosixPath(path).replace(/\\/+$/, '')\n if (/^[a-zA-Z]:$/.test(normalized)) return ''\n const parts = normalized.split('/').filter(Boolean)\n return parts.at(-1) ?? ''\n}\n\nfunction isPathLike(input: string): boolean {\n const normalized = toPosixPath(input)\n return (\n normalized.includes('/') ||\n normalized === '.' ||\n normalized === '..' ||\n normalized.startsWith('./') ||\n normalized.startsWith('../') ||\n hasAbsoluteRoot(normalized)\n )\n}\n\nfunction sanitizePackageName(rawName: string): string {\n return rawName\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 214)\n .replace(/-+$/g, '')\n}\n\n/**\n * Quote a path for the `cd <path> && ...` command hints printed by create commands.\n * Uses POSIX **single quotes**: everything inside is literal, so a directory name\n * containing `$(...)`, backticks, `$VAR`, or `\\` cannot be expanded/executed when the\n * hint is pasted into a shell (double quotes would still expand those). An embedded\n * single quote is closed, escaped, and reopened (`'\\''`).\n */\nexport function quoteShellPath(path: string): string {\n return `'${path.replace(/'/g, \"'\\\\''\")}'`\n}\n\n/**\n * Resolve a `create` positional into a real target directory plus an npm-safe\n * package name. Accepts simple names, relative paths, and absolute Windows/POSIX\n * paths without letting the package name inherit path separators.\n */\nexport function resolveCreateTarget(input: string, cwd = '.'): CreateTargetResult {\n const trimmed = input.trim()\n if (!trimmed) {\n return { ok: false, error: 'create: missing app name or target path.' }\n }\n\n // Reject UNC paths (\\\\server\\share) up front: normalizeLogicalPath would collapse\n // the `//` root to a single `/`, silently retargeting to the wrong location.\n if (toPosixPath(trimmed).startsWith('//')) {\n return {\n ok: false,\n error: `UNC paths are not supported: \"${input}\". Use a drive-letter or relative path.`,\n }\n }\n\n const basename = pathBasename(trimmed)\n if (!basename || basename === '.' || basename === '..') {\n return { ok: false, error: `Could not derive a package name from \"${input}\".` }\n }\n\n const packageName = sanitizePackageName(basename)\n if (!packageName) {\n return {\n ok: false,\n error: `Could not derive a valid npm package name from \"${basename}\". Use a name with letters or numbers.`,\n }\n }\n\n const targetInput = toPosixPath(trimmed)\n const cwdPath = normalizeLogicalPath(cwd || '.')\n const targetDir = hasAbsoluteRoot(targetInput)\n ? normalizeLogicalPath(targetInput)\n : normalizeLogicalPath(cwdPath === '.' ? targetInput : `${cwdPath}/${targetInput}`)\n\n return {\n ok: true,\n input: trimmed,\n targetDir,\n packageName,\n displayDir: isPathLike(trimmed) ? targetDir : trimmed,\n }\n}\n"],"mappings":";AAoBA,MAAM,qBAAqB;AAE3B,SAAS,YAAY,MAAsB;CACzC,OAAO,KAAK,QAAQ,OAAO,GAAG;AAChC;AAEA,SAAS,gBAAgB,MAAuB;CAC9C,OAAO,KAAK,WAAW,GAAG,KAAK,mBAAmB,KAAK,IAAI;AAC7D;AAEA,SAAS,qBAAqB,MAAsB;CAClD,MAAM,aAAa,YAAY,IAAI;CACnC,MAAM,QAAQ,WAAW,MAAM,kBAAkB,IAAI;CACrD,MAAM,OAAO,QAAQ,GAAG,MAAM,KAAK,WAAW,WAAW,GAAG,IAAI,MAAM;CACtE,MAAM,OAAO,OAAO,WAAW,MAAM,KAAK,MAAM,IAAI;CACpD,MAAM,QAAkB,CAAC;CAEzB,KAAK,MAAM,WAAW,KAAK,MAAM,GAAG,GAAG;EACrC,IAAI,CAAC,WAAW,YAAY,KAAK;EACjC,IAAI,YAAY,MAAM;GACpB,MAAM,WAAW,MAAM,GAAG,EAAE;GAC5B,IAAI,YAAY,aAAa,MAC3B,MAAM,IAAI;QACL,IAAI,CAAC,MACV,MAAM,KAAK,OAAO;GAEpB;EACF;EACA,MAAM,KAAK,OAAO;CACpB;CAEA,MAAM,SAAS,MAAM,KAAK,GAAG;CAC7B,IAAI,MAAM,OAAO,SAAS,GAAG,OAAO,WAAW;CAC/C,OAAO,UAAU;AACnB;AAEA,SAAS,aAAa,MAAsB;CAC1C,MAAM,aAAa,YAAY,IAAI,EAAE,QAAQ,QAAQ,EAAE;CACvD,IAAI,cAAc,KAAK,UAAU,GAAG,OAAO;CAE3C,OADc,WAAW,MAAM,GAAG,EAAE,OAAO,OAChC,EAAE,GAAG,EAAE,KAAK;AACzB;AAEA,SAAS,WAAW,OAAwB;CAC1C,MAAM,aAAa,YAAY,KAAK;CACpC,OACE,WAAW,SAAS,GAAG,KACvB,eAAe,OACf,eAAe,QACf,WAAW,WAAW,IAAI,KAC1B,WAAW,WAAW,KAAK,KAC3B,gBAAgB,UAAU;AAE9B;AAEA,SAAS,oBAAoB,SAAyB;CACpD,OAAO,QACJ,KAAK,EACL,YAAY,EACZ,QAAQ,eAAe,GAAG,EAC1B,QAAQ,YAAY,EAAE,EACtB,MAAM,GAAG,GAAG,EACZ,QAAQ,QAAQ,EAAE;AACvB;;;;;;;;AASA,SAAgB,eAAe,MAAsB;CACnD,OAAO,IAAI,KAAK,QAAQ,MAAM,OAAO,EAAE;AACzC;;;;;;AAOA,SAAgB,oBAAoB,OAAe,MAAM,KAAyB;CAChF,MAAM,UAAU,MAAM,KAAK;CAC3B,IAAI,CAAC,SACH,OAAO;EAAE,IAAI;EAAO,OAAO;CAA2C;CAKxE,IAAI,YAAY,OAAO,EAAE,WAAW,IAAI,GACtC,OAAO;EACL,IAAI;EACJ,OAAO,iCAAiC,MAAM;CAChD;CAGF,MAAM,WAAW,aAAa,OAAO;CACrC,IAAI,CAAC,YAAY,aAAa,OAAO,aAAa,MAChD,OAAO;EAAE,IAAI;EAAO,OAAO,yCAAyC,MAAM;CAAI;CAGhF,MAAM,cAAc,oBAAoB,QAAQ;CAChD,IAAI,CAAC,aACH,OAAO;EACL,IAAI;EACJ,OAAO,mDAAmD,SAAS;CACrE;CAGF,MAAM,cAAc,YAAY,OAAO;CACvC,MAAM,UAAU,qBAAqB,OAAO,GAAG;CAC/C,MAAM,YAAY,gBAAgB,WAAW,IACzC,qBAAqB,WAAW,IAChC,qBAAqB,YAAY,MAAM,cAAc,GAAG,QAAQ,GAAG,aAAa;CAEpF,OAAO;EACL,IAAI;EACJ,OAAO;EACP;EACA;EACA,YAAY,WAAW,OAAO,IAAI,YAAY;CAChD;AACF"}
|
package/dist/dev.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { FileSystem } from "./fs.js";
|
|
2
|
+
import { BuildOptions, BuildResult } from "./build.js";
|
|
3
|
+
|
|
4
|
+
//#region src/dev.d.ts
|
|
5
|
+
/** A file watcher: registers a listener, returns an unsubscribe function. */
|
|
6
|
+
interface Watcher {
|
|
7
|
+
/** Subscribe to change events; the returned function stops watching. */
|
|
8
|
+
onChange(listener: (changedPath: string) => void): () => void;
|
|
9
|
+
}
|
|
10
|
+
/** A running dev session. */
|
|
11
|
+
interface DevSession {
|
|
12
|
+
/** Number of builds performed (1 initial + one per change). */
|
|
13
|
+
readonly buildCount: number;
|
|
14
|
+
/** The most recent build result. */
|
|
15
|
+
readonly lastResult: BuildResult;
|
|
16
|
+
/** Stop watching and end the session. */
|
|
17
|
+
stop(): void;
|
|
18
|
+
}
|
|
19
|
+
/** Options for {@link startDev}. */
|
|
20
|
+
interface DevOptions extends BuildOptions {
|
|
21
|
+
/** Called after every (re)build, e.g. to push HMR updates. */
|
|
22
|
+
onRebuild?: (result: BuildResult, changedPath: string | null) => void;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Start a dev session: build once immediately, then rebuild on each watcher
|
|
26
|
+
* change. Returns a {@link DevSession} exposing the build count + last result
|
|
27
|
+
* and a `stop()` that unsubscribes.
|
|
28
|
+
*/
|
|
29
|
+
declare function startDev(fs: FileSystem, watcher: Watcher, options?: DevOptions): DevSession;
|
|
30
|
+
//#endregion
|
|
31
|
+
export { DevOptions, DevSession, Watcher, startDev };
|
|
32
|
+
//# sourceMappingURL=dev.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev.d.ts","names":[],"sources":["../src/dev.ts"],"mappings":";;;;;UAmBiB,OAAA;EAUN;EART,QAAA,CAAS,QAAA,GAAW,WAAA;AAAA;;UAIL,UAAA;EAUA;EAAA,SARN,UAAA;;WAEA,UAAA,EAAY,WAAW;EAME;EAJlC,IAAA;AAAA;;UAIe,UAAA,SAAmB,YAAY;EAEc;EAA5D,SAAA,IAAa,MAAA,EAAQ,WAAA,EAAa,WAAA;AAAA;;;;;;iBAQpB,QAAA,CAAS,EAAA,EAAI,UAAA,EAAY,OAAA,EAAS,OAAA,EAAS,OAAA,GAAS,UAAA,GAAkB,UAAA"}
|
package/dist/dev.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { buildProject } from "./build.js";
|
|
2
|
+
//#region src/dev.ts
|
|
3
|
+
/**
|
|
4
|
+
* `mindees dev` — the dev orchestrator.
|
|
5
|
+
*
|
|
6
|
+
* The orchestrator is the deterministic, tested core: it builds once, then
|
|
7
|
+
* rebuilds whenever a {@link Watcher} reports a change, tracking rebuild count
|
|
8
|
+
* and the last result. It is decoupled from any real file watcher or HTTP/HMR
|
|
9
|
+
* transport (those are injected), so it can be unit-tested with a fake watcher.
|
|
10
|
+
*
|
|
11
|
+
* The live HTTP server + browser HMR socket is a thin developer-preview
|
|
12
|
+
* transport layer (in `bin`); the rebuild-on-change behavior proven here is the
|
|
13
|
+
* substance.
|
|
14
|
+
*
|
|
15
|
+
* @module
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Start a dev session: build once immediately, then rebuild on each watcher
|
|
19
|
+
* change. Returns a {@link DevSession} exposing the build count + last result
|
|
20
|
+
* and a `stop()` that unsubscribes.
|
|
21
|
+
*/
|
|
22
|
+
function startDev(fs, watcher, options = {}) {
|
|
23
|
+
const { onRebuild, ...buildOptions } = options;
|
|
24
|
+
let buildCount = 0;
|
|
25
|
+
let lastResult;
|
|
26
|
+
const rebuild = (changedPath) => {
|
|
27
|
+
try {
|
|
28
|
+
lastResult = buildProject(fs, buildOptions);
|
|
29
|
+
} catch (e) {
|
|
30
|
+
lastResult = {
|
|
31
|
+
ok: false,
|
|
32
|
+
compiled: [],
|
|
33
|
+
diagnostics: [{
|
|
34
|
+
severity: "error",
|
|
35
|
+
code: "MDC_DEV",
|
|
36
|
+
message: e instanceof Error ? e.message : String(e)
|
|
37
|
+
}],
|
|
38
|
+
stats: {
|
|
39
|
+
flattenedNodes: 0,
|
|
40
|
+
totalElements: 0
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
buildCount++;
|
|
45
|
+
onRebuild?.(lastResult, changedPath);
|
|
46
|
+
};
|
|
47
|
+
rebuild(null);
|
|
48
|
+
const unsubscribe = watcher.onChange((path) => rebuild(path));
|
|
49
|
+
return {
|
|
50
|
+
get buildCount() {
|
|
51
|
+
return buildCount;
|
|
52
|
+
},
|
|
53
|
+
get lastResult() {
|
|
54
|
+
return lastResult;
|
|
55
|
+
},
|
|
56
|
+
stop() {
|
|
57
|
+
unsubscribe();
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
//#endregion
|
|
62
|
+
export { startDev };
|
|
63
|
+
|
|
64
|
+
//# sourceMappingURL=dev.js.map
|
package/dist/dev.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev.js","names":[],"sources":["../src/dev.ts"],"sourcesContent":["/**\n * `mindees dev` — the dev orchestrator.\n *\n * The orchestrator is the deterministic, tested core: it builds once, then\n * rebuilds whenever a {@link Watcher} reports a change, tracking rebuild count\n * and the last result. It is decoupled from any real file watcher or HTTP/HMR\n * transport (those are injected), so it can be unit-tested with a fake watcher.\n *\n * The live HTTP server + browser HMR socket is a thin developer-preview\n * transport layer (in `bin`); the rebuild-on-change behavior proven here is the\n * substance.\n *\n * @module\n */\n\nimport { type BuildOptions, type BuildResult, buildProject } from './build'\nimport type { FileSystem } from './fs'\n\n/** A file watcher: registers a listener, returns an unsubscribe function. */\nexport interface Watcher {\n /** Subscribe to change events; the returned function stops watching. */\n onChange(listener: (changedPath: string) => void): () => void\n}\n\n/** A running dev session. */\nexport interface DevSession {\n /** Number of builds performed (1 initial + one per change). */\n readonly buildCount: number\n /** The most recent build result. */\n readonly lastResult: BuildResult\n /** Stop watching and end the session. */\n stop(): void\n}\n\n/** Options for {@link startDev}. */\nexport interface DevOptions extends BuildOptions {\n /** Called after every (re)build, e.g. to push HMR updates. */\n onRebuild?: (result: BuildResult, changedPath: string | null) => void\n}\n\n/**\n * Start a dev session: build once immediately, then rebuild on each watcher\n * change. Returns a {@link DevSession} exposing the build count + last result\n * and a `stop()` that unsubscribes.\n */\nexport function startDev(fs: FileSystem, watcher: Watcher, options: DevOptions = {}): DevSession {\n const { onRebuild, ...buildOptions } = options\n\n let buildCount = 0\n let lastResult: BuildResult\n\n const rebuild = (changedPath: string | null): void => {\n try {\n lastResult = buildProject(fs, buildOptions)\n } catch (e) {\n // A rebuild must never kill the session: in a long-running watch a file can\n // be removed between readDir() and readFile() (a real FS race), making the\n // build throw. Turn an unexpected throw into a failed result and keep\n // watching — mirroring how `mindees build` reports expected failures as\n // diagnostics rather than throwing.\n lastResult = {\n ok: false,\n compiled: [],\n diagnostics: [\n {\n severity: 'error',\n code: 'MDC_DEV',\n message: e instanceof Error ? e.message : String(e),\n },\n ],\n stats: { flattenedNodes: 0, totalElements: 0 },\n }\n }\n buildCount++\n onRebuild?.(lastResult, changedPath)\n }\n\n rebuild(null) // initial build\n const unsubscribe = watcher.onChange((path) => rebuild(path))\n\n return {\n get buildCount() {\n return buildCount\n },\n get lastResult() {\n return lastResult\n },\n stop() {\n unsubscribe()\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA6CA,SAAgB,SAAS,IAAgB,SAAkB,UAAsB,CAAC,GAAe;CAC/F,MAAM,EAAE,WAAW,GAAG,iBAAiB;CAEvC,IAAI,aAAa;CACjB,IAAI;CAEJ,MAAM,WAAW,gBAAqC;EACpD,IAAI;GACF,aAAa,aAAa,IAAI,YAAY;EAC5C,SAAS,GAAG;GAMV,aAAa;IACX,IAAI;IACJ,UAAU,CAAC;IACX,aAAa,CACX;KACE,UAAU;KACV,MAAM;KACN,SAAS,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;IACpD,CACF;IACA,OAAO;KAAE,gBAAgB;KAAG,eAAe;IAAE;GAC/C;EACF;EACA;EACA,YAAY,YAAY,WAAW;CACrC;CAEA,QAAQ,IAAI;CACZ,MAAM,cAAc,QAAQ,UAAU,SAAS,QAAQ,IAAI,CAAC;CAE5D,OAAO;EACL,IAAI,aAAa;GACf,OAAO;EACT;EACA,IAAI,aAAa;GACf,OAAO;EACT;EACA,OAAO;GACL,YAAY;EACd;CACF;AACF"}
|
package/dist/doctor.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { DoctorCheck, EnvProbe } from "./types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/doctor.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Run the environment checks against `env`. Pure + deterministic: returns the
|
|
6
|
+
* structured checks; rendering and `process` access live elsewhere.
|
|
7
|
+
*/
|
|
8
|
+
declare function runDoctor(env: EnvProbe): DoctorCheck[];
|
|
9
|
+
/** Overall status: `fail` if any check failed, else `warn` if any warned, else `ok`. */
|
|
10
|
+
declare function doctorSummary(checks: readonly DoctorCheck[]): 'ok' | 'warn' | 'fail';
|
|
11
|
+
/** Render checks as human-readable lines (for the `doctor` command output). */
|
|
12
|
+
declare function renderDoctor(checks: readonly DoctorCheck[]): string[];
|
|
13
|
+
//#endregion
|
|
14
|
+
export { doctorSummary, renderDoctor, runDoctor };
|
|
15
|
+
//# sourceMappingURL=doctor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","names":[],"sources":["../src/doctor.ts"],"mappings":";;;AA0BqD;AAqErD;;;AArEqD,iBAArC,SAAA,CAAU,GAAA,EAAK,QAAA,GAAW,WAAW;AAqEO;AAAA,iBAA5C,aAAA,CAAc,MAA8B,WAAb,WAAW;;iBAO1C,YAAA,CAAa,MAA8B,WAAb,WAAW"}
|
package/dist/doctor.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
//#region src/doctor.ts
|
|
2
|
+
/** Minimum supported Node.js major (matches the repo's engines floor). */
|
|
3
|
+
const MIN_NODE_MAJOR = 22;
|
|
4
|
+
const PNPM_EXEC_FALLBACK = "npm exec --yes --package=pnpm@11.5.0 -- pnpm";
|
|
5
|
+
function parseMajor(version) {
|
|
6
|
+
const m = version.match(/^v?(\d+)\./);
|
|
7
|
+
return m?.[1] ? Number.parseInt(m[1], 10) : null;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Run the environment checks against `env`. Pure + deterministic: returns the
|
|
11
|
+
* structured checks; rendering and `process` access live elsewhere.
|
|
12
|
+
*/
|
|
13
|
+
function runDoctor(env) {
|
|
14
|
+
const checks = [];
|
|
15
|
+
const major = parseMajor(env.nodeVersion);
|
|
16
|
+
if (major === null) checks.push({
|
|
17
|
+
name: "Node.js",
|
|
18
|
+
status: "warn",
|
|
19
|
+
detail: `unrecognized version "${env.nodeVersion}"`,
|
|
20
|
+
fix: `Install Node.js ${MIN_NODE_MAJOR} LTS or newer.`
|
|
21
|
+
});
|
|
22
|
+
else if (major < MIN_NODE_MAJOR) checks.push({
|
|
23
|
+
name: "Node.js",
|
|
24
|
+
status: "fail",
|
|
25
|
+
detail: `${env.nodeVersion} (need >= ${MIN_NODE_MAJOR})`,
|
|
26
|
+
fix: `Upgrade to Node.js ${MIN_NODE_MAJOR} LTS or newer (see .nvmrc).`
|
|
27
|
+
});
|
|
28
|
+
else checks.push({
|
|
29
|
+
name: "Node.js",
|
|
30
|
+
status: "ok",
|
|
31
|
+
detail: env.nodeVersion
|
|
32
|
+
});
|
|
33
|
+
if (!env.packageManager) checks.push({
|
|
34
|
+
name: "Package manager",
|
|
35
|
+
status: "warn",
|
|
36
|
+
detail: "none detected",
|
|
37
|
+
fix: `Enable pnpm via Corepack: \`corepack enable\`. If Windows blocks Corepack shims, use \`${PNPM_EXEC_FALLBACK}\`.`
|
|
38
|
+
});
|
|
39
|
+
else if (env.packageManager.name !== "pnpm") checks.push({
|
|
40
|
+
name: "Package manager",
|
|
41
|
+
status: "warn",
|
|
42
|
+
detail: `${env.packageManager.name} ${env.packageManager.version}`,
|
|
43
|
+
fix: `MindeesNative uses pnpm. Enable it via Corepack: \`corepack enable\`. If Windows blocks Corepack shims, use \`${PNPM_EXEC_FALLBACK}\`.`
|
|
44
|
+
});
|
|
45
|
+
else checks.push({
|
|
46
|
+
name: "Package manager",
|
|
47
|
+
status: "ok",
|
|
48
|
+
detail: `pnpm ${env.packageManager.version}`
|
|
49
|
+
});
|
|
50
|
+
if (!env.hasPackageJson) checks.push({
|
|
51
|
+
name: "Project",
|
|
52
|
+
status: "warn",
|
|
53
|
+
detail: "no package.json in this directory",
|
|
54
|
+
fix: "Run `mindees create <name>` to scaffold a new app, or cd into a project."
|
|
55
|
+
});
|
|
56
|
+
else if (!env.hasNodeModules) checks.push({
|
|
57
|
+
name: "Dependencies",
|
|
58
|
+
status: "warn",
|
|
59
|
+
detail: "node_modules missing",
|
|
60
|
+
fix: `Install dependencies: \`pnpm install\` (or \`${PNPM_EXEC_FALLBACK} install\` if the pnpm shim is unavailable).`
|
|
61
|
+
});
|
|
62
|
+
else checks.push({
|
|
63
|
+
name: "Project",
|
|
64
|
+
status: "ok",
|
|
65
|
+
detail: "package.json + node_modules present"
|
|
66
|
+
});
|
|
67
|
+
return checks;
|
|
68
|
+
}
|
|
69
|
+
/** Overall status: `fail` if any check failed, else `warn` if any warned, else `ok`. */
|
|
70
|
+
function doctorSummary(checks) {
|
|
71
|
+
if (checks.some((c) => c.status === "fail")) return "fail";
|
|
72
|
+
if (checks.some((c) => c.status === "warn")) return "warn";
|
|
73
|
+
return "ok";
|
|
74
|
+
}
|
|
75
|
+
/** Render checks as human-readable lines (for the `doctor` command output). */
|
|
76
|
+
function renderDoctor(checks) {
|
|
77
|
+
const icon = {
|
|
78
|
+
ok: "✓",
|
|
79
|
+
warn: "!",
|
|
80
|
+
fail: "✗"
|
|
81
|
+
};
|
|
82
|
+
const lines = [];
|
|
83
|
+
for (const c of checks) {
|
|
84
|
+
lines.push(`${icon[c.status]} ${c.name}: ${c.detail}`);
|
|
85
|
+
if (c.fix && c.status !== "ok") lines.push(` → ${c.fix}`);
|
|
86
|
+
}
|
|
87
|
+
return lines;
|
|
88
|
+
}
|
|
89
|
+
//#endregion
|
|
90
|
+
export { doctorSummary, renderDoctor, runDoctor };
|
|
91
|
+
|
|
92
|
+
//# sourceMappingURL=doctor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.js","names":[],"sources":["../src/doctor.ts"],"sourcesContent":["/**\n * `mindees doctor` — environment diagnostics with actionable fixes.\n *\n * Directly attacks the \"cryptic error\" pain point: instead of a confusing\n * failure later, `doctor` checks the environment up front and tells you exactly\n * what to do. The check logic is pure over an injected {@link EnvProbe}, so it's\n * fully deterministic in tests; the `bin` layer supplies the real probe.\n *\n * @module\n */\n\nimport type { DoctorCheck, EnvProbe } from './types'\n\n/** Minimum supported Node.js major (matches the repo's engines floor). */\nconst MIN_NODE_MAJOR = 22\nconst PNPM_EXEC_FALLBACK = 'npm exec --yes --package=pnpm@11.5.0 -- pnpm'\n\nfunction parseMajor(version: string): number | null {\n const m = version.match(/^v?(\\d+)\\./)\n return m?.[1] ? Number.parseInt(m[1], 10) : null\n}\n\n/**\n * Run the environment checks against `env`. Pure + deterministic: returns the\n * structured checks; rendering and `process` access live elsewhere.\n */\nexport function runDoctor(env: EnvProbe): DoctorCheck[] {\n const checks: DoctorCheck[] = []\n\n // Node version.\n const major = parseMajor(env.nodeVersion)\n if (major === null) {\n checks.push({\n name: 'Node.js',\n status: 'warn',\n detail: `unrecognized version \"${env.nodeVersion}\"`,\n fix: `Install Node.js ${MIN_NODE_MAJOR} LTS or newer.`,\n })\n } else if (major < MIN_NODE_MAJOR) {\n checks.push({\n name: 'Node.js',\n status: 'fail',\n detail: `${env.nodeVersion} (need >= ${MIN_NODE_MAJOR})`,\n fix: `Upgrade to Node.js ${MIN_NODE_MAJOR} LTS or newer (see .nvmrc).`,\n })\n } else {\n checks.push({ name: 'Node.js', status: 'ok', detail: env.nodeVersion })\n }\n\n // Package manager.\n if (!env.packageManager) {\n checks.push({\n name: 'Package manager',\n status: 'warn',\n detail: 'none detected',\n fix: `Enable pnpm via Corepack: \\`corepack enable\\`. If Windows blocks Corepack shims, use \\`${PNPM_EXEC_FALLBACK}\\`.`,\n })\n } else if (env.packageManager.name !== 'pnpm') {\n checks.push({\n name: 'Package manager',\n status: 'warn',\n detail: `${env.packageManager.name} ${env.packageManager.version}`,\n fix: `MindeesNative uses pnpm. Enable it via Corepack: \\`corepack enable\\`. If Windows blocks Corepack shims, use \\`${PNPM_EXEC_FALLBACK}\\`.`,\n })\n } else {\n checks.push({\n name: 'Package manager',\n status: 'ok',\n detail: `pnpm ${env.packageManager.version}`,\n })\n }\n\n // Project presence.\n if (!env.hasPackageJson) {\n checks.push({\n name: 'Project',\n status: 'warn',\n detail: 'no package.json in this directory',\n fix: 'Run `mindees create <name>` to scaffold a new app, or cd into a project.',\n })\n } else if (!env.hasNodeModules) {\n checks.push({\n name: 'Dependencies',\n status: 'warn',\n detail: 'node_modules missing',\n fix: `Install dependencies: \\`pnpm install\\` (or \\`${PNPM_EXEC_FALLBACK} install\\` if the pnpm shim is unavailable).`,\n })\n } else {\n checks.push({ name: 'Project', status: 'ok', detail: 'package.json + node_modules present' })\n }\n\n return checks\n}\n\n/** Overall status: `fail` if any check failed, else `warn` if any warned, else `ok`. */\nexport function doctorSummary(checks: readonly DoctorCheck[]): 'ok' | 'warn' | 'fail' {\n if (checks.some((c) => c.status === 'fail')) return 'fail'\n if (checks.some((c) => c.status === 'warn')) return 'warn'\n return 'ok'\n}\n\n/** Render checks as human-readable lines (for the `doctor` command output). */\nexport function renderDoctor(checks: readonly DoctorCheck[]): string[] {\n const icon = { ok: '✓', warn: '!', fail: '✗' } as const\n const lines: string[] = []\n for (const c of checks) {\n lines.push(`${icon[c.status]} ${c.name}: ${c.detail}`)\n if (c.fix && c.status !== 'ok') lines.push(` → ${c.fix}`)\n }\n return lines\n}\n"],"mappings":";;AAcA,MAAM,iBAAiB;AACvB,MAAM,qBAAqB;AAE3B,SAAS,WAAW,SAAgC;CAClD,MAAM,IAAI,QAAQ,MAAM,YAAY;CACpC,OAAO,IAAI,KAAK,OAAO,SAAS,EAAE,IAAI,EAAE,IAAI;AAC9C;;;;;AAMA,SAAgB,UAAU,KAA8B;CACtD,MAAM,SAAwB,CAAC;CAG/B,MAAM,QAAQ,WAAW,IAAI,WAAW;CACxC,IAAI,UAAU,MACZ,OAAO,KAAK;EACV,MAAM;EACN,QAAQ;EACR,QAAQ,yBAAyB,IAAI,YAAY;EACjD,KAAK,mBAAmB,eAAe;CACzC,CAAC;MACI,IAAI,QAAQ,gBACjB,OAAO,KAAK;EACV,MAAM;EACN,QAAQ;EACR,QAAQ,GAAG,IAAI,YAAY,YAAY,eAAe;EACtD,KAAK,sBAAsB,eAAe;CAC5C,CAAC;MAED,OAAO,KAAK;EAAE,MAAM;EAAW,QAAQ;EAAM,QAAQ,IAAI;CAAY,CAAC;CAIxE,IAAI,CAAC,IAAI,gBACP,OAAO,KAAK;EACV,MAAM;EACN,QAAQ;EACR,QAAQ;EACR,KAAK,0FAA0F,mBAAmB;CACpH,CAAC;MACI,IAAI,IAAI,eAAe,SAAS,QACrC,OAAO,KAAK;EACV,MAAM;EACN,QAAQ;EACR,QAAQ,GAAG,IAAI,eAAe,KAAK,GAAG,IAAI,eAAe;EACzD,KAAK,iHAAiH,mBAAmB;CAC3I,CAAC;MAED,OAAO,KAAK;EACV,MAAM;EACN,QAAQ;EACR,QAAQ,QAAQ,IAAI,eAAe;CACrC,CAAC;CAIH,IAAI,CAAC,IAAI,gBACP,OAAO,KAAK;EACV,MAAM;EACN,QAAQ;EACR,QAAQ;EACR,KAAK;CACP,CAAC;MACI,IAAI,CAAC,IAAI,gBACd,OAAO,KAAK;EACV,MAAM;EACN,QAAQ;EACR,QAAQ;EACR,KAAK,gDAAgD,mBAAmB;CAC1E,CAAC;MAED,OAAO,KAAK;EAAE,MAAM;EAAW,QAAQ;EAAM,QAAQ;CAAsC,CAAC;CAG9F,OAAO;AACT;;AAGA,SAAgB,cAAc,QAAwD;CACpF,IAAI,OAAO,MAAM,MAAM,EAAE,WAAW,MAAM,GAAG,OAAO;CACpD,IAAI,OAAO,MAAM,MAAM,EAAE,WAAW,MAAM,GAAG,OAAO;CACpD,OAAO;AACT;;AAGA,SAAgB,aAAa,QAA0C;CACrE,MAAM,OAAO;EAAE,IAAI;EAAK,MAAM;EAAK,MAAM;CAAI;CAC7C,MAAM,QAAkB,CAAC;CACzB,KAAK,MAAM,KAAK,QAAQ;EACtB,MAAM,KAAK,GAAG,KAAK,EAAE,QAAQ,GAAG,EAAE,KAAK,IAAI,EAAE,QAAQ;EACrD,IAAI,EAAE,OAAO,EAAE,WAAW,MAAM,MAAM,KAAK,SAAS,EAAE,KAAK;CAC7D;CACA,OAAO;AACT"}
|
package/dist/fs.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
//#region src/fs.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* A tiny filesystem abstraction so CLI commands are testable without touching
|
|
4
|
+
* the real disk. The `bin` layer supplies a `node:fs`-backed implementation; the
|
|
5
|
+
* core (`scaffold`, `buildProject`) takes a {@link FileSystem}.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
/** The minimal filesystem surface the CLI core needs. */
|
|
10
|
+
interface FileSystem {
|
|
11
|
+
/** True if a file or directory exists at `path`. */
|
|
12
|
+
exists(path: string): boolean;
|
|
13
|
+
/** Read a UTF-8 file. Throws if missing. */
|
|
14
|
+
readFile(path: string): string;
|
|
15
|
+
/** Write a UTF-8 file, creating parent directories as needed. */
|
|
16
|
+
writeFile(path: string, contents: string): void;
|
|
17
|
+
/** Create a directory (and parents). No-op if it exists. */
|
|
18
|
+
mkdir(path: string): void;
|
|
19
|
+
/** List files (recursively) under `dir`, returned as POSIX-relative paths. */
|
|
20
|
+
readDir(dir: string): string[];
|
|
21
|
+
}
|
|
22
|
+
/** An in-memory {@link FileSystem} for tests and dry runs. */
|
|
23
|
+
declare function createMemoryFileSystem(initial?: Record<string, string>): FileSystem & {
|
|
24
|
+
/** Snapshot of all written files (path → contents). */snapshot(): Record<string, string>;
|
|
25
|
+
};
|
|
26
|
+
//#endregion
|
|
27
|
+
export { FileSystem, createMemoryFileSystem };
|
|
28
|
+
//# sourceMappingURL=fs.d.ts.map
|
package/dist/fs.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs.d.ts","names":[],"sources":["../src/fs.ts"],"mappings":";;AASA;;;;;;;UAAiB,UAAA;EAMf;EAJA,MAAA,CAAO,IAAA;EAIiB;EAFxB,QAAA,CAAS,IAAA;EAIH;EAFN,SAAA,CAAU,IAAA,UAAc,QAAA;EAIhB;EAFR,KAAA,CAAM,IAAA;EAEa;EAAnB,OAAA,CAAQ,GAAA;AAAA;;iBAIM,sBAAA,CAAuB,OAAA,GAAS,MAAA,mBAA8B,UAAA;EAAA,uDAE5E,QAAA,IAAY,MAAA;AAAA"}
|