blockend-cli 1.4.0 → 1.4.1
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/dist/index.js +880 -278
- package/package.json +60 -18
- package/readme.md +53 -10
package/dist/index.js
CHANGED
|
@@ -1,229 +1,49 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import {
|
|
4
|
+
import { defineCommand, runMain } from "citty";
|
|
5
5
|
|
|
6
|
-
// src/commands/
|
|
7
|
-
import path, { join } from "path";
|
|
6
|
+
// src/commands/add.ts
|
|
7
|
+
import path, { join, dirname } from "path";
|
|
8
8
|
import fs from "fs/promises";
|
|
9
|
-
import {
|
|
10
|
-
import { intro, outro, select,
|
|
11
|
-
import { detectProject } from "@blockend/detector";
|
|
12
|
-
|
|
13
|
-
// src/ui/theme.ts
|
|
9
|
+
import { exec } from "child_process";
|
|
10
|
+
import { intro, outro, select, spinner, confirm, isCancel } from "@clack/prompts";
|
|
14
11
|
import pc from "picocolors";
|
|
15
|
-
var
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
},
|
|
26
|
-
state: {
|
|
27
|
-
success: pc.green,
|
|
28
|
-
warning: pc.yellow,
|
|
29
|
-
error: pc.red,
|
|
30
|
-
info: pc.blue
|
|
31
|
-
},
|
|
32
|
-
emphasis: {
|
|
33
|
-
strong: pc.bold,
|
|
34
|
-
dim: pc.dim
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
// src/ui/format.ts
|
|
39
|
-
var format = {
|
|
40
|
-
title: (text2) => theme.brand.primary(theme.brand.title(text2)),
|
|
41
|
-
success: (text2) => theme.state.success(`\u2714 ${text2}`),
|
|
42
|
-
error: (text2) => theme.state.error(`\u2716 ${text2}`),
|
|
43
|
-
warning: (text2) => theme.state.warning(text2),
|
|
44
|
-
muted: (text2) => theme.text.muted(text2),
|
|
45
|
-
info: (text2) => theme.state.info(text2)
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
// src/commands/init.ts
|
|
49
|
-
async function resolveTsConfigPaths(cwd) {
|
|
50
|
-
const possiblePaths = [join(cwd, "tsconfig.json"), join(cwd, "jsconfig.json")];
|
|
51
|
-
for (const configPath of possiblePaths) {
|
|
52
|
-
if (existsSync(configPath)) {
|
|
53
|
-
try {
|
|
54
|
-
const rawContent = await fs.readFile(configPath, "utf-8");
|
|
55
|
-
const cleanJsonContent = rawContent.replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, "$1");
|
|
56
|
-
const parsed = JSON.parse(cleanJsonContent);
|
|
57
|
-
const baseUrl = parsed.compilerOptions?.baseUrl || ".";
|
|
58
|
-
const paths = parsed.compilerOptions?.paths || {};
|
|
59
|
-
return { baseUrl, paths };
|
|
60
|
-
} catch {
|
|
61
|
-
}
|
|
62
|
-
}
|
|
12
|
+
var REPO_OWNER = "codewithnuh";
|
|
13
|
+
var REPO_NAME = "blockend";
|
|
14
|
+
var BRANCH = "master";
|
|
15
|
+
var RAW_CDN_BASE = `https://raw.githubusercontent.com/${REPO_OWNER}/${REPO_NAME}/${BRANCH}`;
|
|
16
|
+
var MANIFEST_URL = `${RAW_CDN_BASE}/registry/index.json`;
|
|
17
|
+
function outputError(json, message) {
|
|
18
|
+
if (json) {
|
|
19
|
+
process.stdout.write(JSON.stringify({ success: false, error: message }) + "\n");
|
|
20
|
+
} else {
|
|
21
|
+
outro(pc.red(`\u2716 ${message}`));
|
|
63
22
|
}
|
|
64
|
-
return { baseUrl: ".", paths: {} };
|
|
65
23
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
console.log(`
|
|
70
|
-
\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557
|
|
71
|
-
\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2554\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557
|
|
72
|
-
\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551
|
|
73
|
-
\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551
|
|
74
|
-
\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D
|
|
75
|
-
\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D
|
|
76
|
-
`);
|
|
77
|
-
intro(theme.brand.primary(" Blockend \xB7 Intelligent Backend Blocks Setup "));
|
|
78
|
-
if (existsSync(configPath)) {
|
|
79
|
-
const action = await select({
|
|
80
|
-
message: "blockend.json already exists. What do you want to do?",
|
|
81
|
-
options: [
|
|
82
|
-
{ value: "keep", label: "Keep existing config (cancel init)" },
|
|
83
|
-
{ value: "overwrite", label: "Overwrite config" },
|
|
84
|
-
{ value: "regenerate", label: "Delete and regenerate" }
|
|
85
|
-
]
|
|
86
|
-
});
|
|
87
|
-
if (isCancel(action) || action === "keep") {
|
|
88
|
-
outro(format.muted("Initialization cancelled. Existing config preserved."));
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
if (action === "regenerate") {
|
|
92
|
-
await fs.unlink(configPath);
|
|
93
|
-
console.log(format.error("Existing config deleted"));
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
const s = spinner();
|
|
97
|
-
s.start("Scanning project layout...");
|
|
98
|
-
const context = await detectProject(cwd);
|
|
99
|
-
const hasSrcDir = existsSync(join(cwd, "src"));
|
|
100
|
-
const tsConfig = await resolveTsConfigPaths(cwd);
|
|
101
|
-
s.stop(format.success("Project architecture scanned"));
|
|
102
|
-
let framework = context.framework;
|
|
103
|
-
if (framework) {
|
|
104
|
-
console.log(
|
|
105
|
-
`${format.success("\u2714")} Framework environment detected: ${theme.state.info(framework)}`
|
|
106
|
-
);
|
|
24
|
+
function outputResult(json, result) {
|
|
25
|
+
if (json) {
|
|
26
|
+
process.stdout.write(JSON.stringify(result) + "\n");
|
|
107
27
|
} else {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
options: [
|
|
111
|
-
{ value: "express", label: "Express.js" },
|
|
112
|
-
{ value: "fastify", label: "Fastify" },
|
|
113
|
-
{ value: "next", label: "Next.js (App Router)" },
|
|
114
|
-
{ value: "hono", label: "Hono" }
|
|
115
|
-
]
|
|
116
|
-
});
|
|
117
|
-
if (isCancel(frameworkSelect)) {
|
|
118
|
-
outro(format.muted("Initialization cancelled."));
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
framework = frameworkSelect;
|
|
122
|
-
}
|
|
123
|
-
const defaultDir = hasSrcDir ? "src/blocks" : "blocks";
|
|
124
|
-
const directoryPrompt = await text({
|
|
125
|
-
message: "Configure the targeted physical directory destination for blocks:",
|
|
126
|
-
placeholder: defaultDir,
|
|
127
|
-
initialValue: defaultDir,
|
|
128
|
-
validate(value) {
|
|
129
|
-
if (value?.trim().length === 0) return "Physical path location directory cannot be empty.";
|
|
130
|
-
}
|
|
131
|
-
});
|
|
132
|
-
if (isCancel(directoryPrompt)) {
|
|
133
|
-
outro(format.muted("Initialization cancelled."));
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
const rawPhysicalInput = String(directoryPrompt).trim();
|
|
137
|
-
const relativeBlocksPath = path.relative(cwd, path.resolve(cwd, rawPhysicalInput));
|
|
138
|
-
let finalPath = relativeBlocksPath.replace(/\\/g, "/");
|
|
139
|
-
if (!finalPath.startsWith(".") && !finalPath.startsWith("/")) {
|
|
140
|
-
finalPath = `./${finalPath}`;
|
|
141
|
-
}
|
|
142
|
-
let finalBlockAlias = "@/blocks";
|
|
143
|
-
const configuredAliases = Object.keys(tsConfig.paths);
|
|
144
|
-
if (configuredAliases.length > 0) {
|
|
145
|
-
const matchedAliasKey = configuredAliases.find((alias) => {
|
|
146
|
-
const targets = tsConfig.paths[alias];
|
|
147
|
-
if (Array.isArray(targets) && targets.length > 0) {
|
|
148
|
-
const targetSubPath = targets[0].replace(/\*$/, "").replace(/\\/g, "/");
|
|
149
|
-
return finalPath.replace(/^\.\//, "").startsWith(targetSubPath);
|
|
150
|
-
}
|
|
151
|
-
return false;
|
|
152
|
-
});
|
|
153
|
-
if (matchedAliasKey) {
|
|
154
|
-
const cleanKey = matchedAliasKey.replace(/\*$/, "");
|
|
155
|
-
const targets = tsConfig.paths[matchedAliasKey];
|
|
156
|
-
const targetSubPath = targets[0].replace(/\*$/, "").replace(/\\/g, "/");
|
|
157
|
-
const relativeSuffix = finalPath.replace(/^\.\//, "").replace(targetSubPath, "");
|
|
158
|
-
finalBlockAlias = `${cleanKey}${relativeSuffix}`.replace(/\/$/, "");
|
|
28
|
+
if (result.success) {
|
|
29
|
+
outro(pc.cyan(`\u2728 ${result.message}`));
|
|
159
30
|
} else {
|
|
160
|
-
|
|
161
|
-
const inferredBaseAlias = primaryAlias.replace(/\*$/, "");
|
|
162
|
-
const folderSegmentName = path.basename(finalPath);
|
|
163
|
-
finalBlockAlias = `${inferredBaseAlias}${folderSegmentName}`;
|
|
164
|
-
}
|
|
165
|
-
} else {
|
|
166
|
-
const folderSegmentName = path.basename(finalPath);
|
|
167
|
-
finalBlockAlias = `@/${folderSegmentName}`;
|
|
168
|
-
}
|
|
169
|
-
let includeRedis = false;
|
|
170
|
-
if (context.hasRedis) {
|
|
171
|
-
const redisConfirm = await confirm({
|
|
172
|
-
message: "Redis detected. Enable Redis-backed block variants automatically?",
|
|
173
|
-
initialValue: true
|
|
174
|
-
});
|
|
175
|
-
if (!isCancel(redisConfirm)) {
|
|
176
|
-
includeRedis = Boolean(redisConfirm);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
const configPayload = {
|
|
180
|
-
$schema: "https://blockend.dev/schema.json",
|
|
181
|
-
environment: framework || "express",
|
|
182
|
-
language: context.language || "typescript",
|
|
183
|
-
includeRedis,
|
|
184
|
-
aliases: {
|
|
185
|
-
blocks: finalBlockAlias
|
|
186
|
-
},
|
|
187
|
-
paths: {
|
|
188
|
-
blocks: finalPath
|
|
31
|
+
outro(pc.yellow(`\u2139 ${result.message}`));
|
|
189
32
|
}
|
|
190
|
-
};
|
|
191
|
-
const writeSpinner = spinner();
|
|
192
|
-
writeSpinner.start("Finalizing configuration...");
|
|
193
|
-
try {
|
|
194
|
-
await fs.writeFile(configPath, JSON.stringify(configPayload, null, 2), "utf-8");
|
|
195
|
-
writeSpinner.stop(format.success("blockend.json ready"));
|
|
196
|
-
outro(
|
|
197
|
-
theme.state.success("\u2728 Blockend initialized successfully. Run: npx blockend add <block>")
|
|
198
|
-
);
|
|
199
|
-
} catch {
|
|
200
|
-
writeSpinner.stop(format.error("Failed to write configuration"));
|
|
201
33
|
}
|
|
202
34
|
}
|
|
203
|
-
|
|
204
|
-
// src/commands/add.ts
|
|
205
|
-
import path2, { join as join2, dirname } from "path";
|
|
206
|
-
import fs2 from "fs/promises";
|
|
207
|
-
import { exec } from "child_process";
|
|
208
|
-
import { intro as intro2, outro as outro2, select as select2, spinner as spinner2, confirm as confirm2, isCancel as isCancel2 } from "@clack/prompts";
|
|
209
|
-
import pc2 from "picocolors";
|
|
210
|
-
var REPO_OWNER = "codewithnuh";
|
|
211
|
-
var REPO_NAME = "blockend";
|
|
212
|
-
var BRANCH = "master";
|
|
213
|
-
var RAW_CDN_BASE = `https://raw.githubusercontent.com/${REPO_OWNER}/${REPO_NAME}/${BRANCH}`;
|
|
214
|
-
var MANIFEST_URL = `${RAW_CDN_BASE}/registry/index.json`;
|
|
215
35
|
function handleCancel(value) {
|
|
216
|
-
if (
|
|
217
|
-
|
|
36
|
+
if (isCancel(value)) {
|
|
37
|
+
outro(pc.yellow("\u26A0 Operation cancelled. Exiting Blockend CLI cleanly."));
|
|
218
38
|
process.exit(0);
|
|
219
39
|
}
|
|
220
40
|
}
|
|
221
41
|
async function findUp(filename, startDir) {
|
|
222
42
|
let dir = startDir;
|
|
223
43
|
while (true) {
|
|
224
|
-
const checkPath =
|
|
44
|
+
const checkPath = join(dir, filename);
|
|
225
45
|
try {
|
|
226
|
-
await
|
|
46
|
+
await fs.access(checkPath);
|
|
227
47
|
return checkPath;
|
|
228
48
|
} catch {
|
|
229
49
|
const parent = dirname(dir);
|
|
@@ -233,44 +53,60 @@ async function findUp(filename, startDir) {
|
|
|
233
53
|
}
|
|
234
54
|
return null;
|
|
235
55
|
}
|
|
236
|
-
async function addCommand(blockName) {
|
|
237
|
-
|
|
56
|
+
async function addCommand(blockName, options = {}) {
|
|
57
|
+
const { yes = false, json = false } = options;
|
|
58
|
+
if (!json) {
|
|
59
|
+
intro(pc.bgBlack(pc.magenta(" Blockend Component Ingestion ")));
|
|
60
|
+
}
|
|
238
61
|
const cwd = process.cwd();
|
|
239
62
|
const configPath = await findUp("blockend.json", cwd);
|
|
240
63
|
if (!configPath) {
|
|
241
|
-
|
|
64
|
+
outputError(json, "blockend.json not found. Run 'npx blockend init' first.");
|
|
242
65
|
return;
|
|
243
66
|
}
|
|
244
67
|
const rootDir = dirname(configPath);
|
|
245
68
|
let config;
|
|
246
69
|
try {
|
|
247
|
-
const configFile = await
|
|
70
|
+
const configFile = await fs.readFile(configPath, "utf-8");
|
|
248
71
|
config = JSON.parse(configFile);
|
|
249
72
|
} catch (error) {
|
|
250
|
-
|
|
251
|
-
console.error(
|
|
73
|
+
outputError(json, "Failed to parse blockend.json layout configuration.");
|
|
74
|
+
if (!json) console.error(pc.dim(String(error)));
|
|
252
75
|
return;
|
|
253
76
|
}
|
|
254
77
|
if (config.language !== "typescript") {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
78
|
+
if (json) {
|
|
79
|
+
outputError(
|
|
80
|
+
json,
|
|
81
|
+
"Blockend forces modern architectural standards. Registry exclusively supports TypeScript."
|
|
82
|
+
);
|
|
83
|
+
} else {
|
|
84
|
+
outro(
|
|
85
|
+
pc.yellow(
|
|
86
|
+
`
|
|
258
87
|
\u2139 Blockend forces modern architectural standards. Registry exclusively supports TypeScript.`
|
|
259
|
-
|
|
260
|
-
|
|
88
|
+
)
|
|
89
|
+
);
|
|
90
|
+
}
|
|
261
91
|
return;
|
|
262
92
|
}
|
|
263
|
-
const s =
|
|
264
|
-
|
|
93
|
+
const s = spinner();
|
|
94
|
+
if (!json) {
|
|
95
|
+
s.start("Connecting to remote Blockend Registry manifest...");
|
|
96
|
+
}
|
|
265
97
|
let registry;
|
|
266
98
|
try {
|
|
267
99
|
const response = await fetch(MANIFEST_URL);
|
|
268
100
|
if (!response.ok) throw new Error(`HTTP Error Status: ${response.status}`);
|
|
269
101
|
registry = await response.json();
|
|
270
|
-
s.stop(
|
|
102
|
+
if (!json) s.stop(pc.green("\u2714 Remote manifest synchronization complete."));
|
|
271
103
|
} catch (error) {
|
|
272
|
-
|
|
273
|
-
|
|
104
|
+
if (!json) {
|
|
105
|
+
s.stop(pc.red("\u2716 Failed to fetch the component registry from GitHub network paths."));
|
|
106
|
+
console.error(pc.dim(String(error)));
|
|
107
|
+
} else {
|
|
108
|
+
outputError(json, "Failed to fetch the component registry from GitHub network paths.");
|
|
109
|
+
}
|
|
274
110
|
return;
|
|
275
111
|
}
|
|
276
112
|
let blockMap = {};
|
|
@@ -281,6 +117,13 @@ async function addCommand(blockName) {
|
|
|
281
117
|
}
|
|
282
118
|
const envKey = String(config.environment);
|
|
283
119
|
let targetBlock = blockName;
|
|
120
|
+
if (yes && !targetBlock) {
|
|
121
|
+
outputError(
|
|
122
|
+
json,
|
|
123
|
+
"Block name required when using --yes flag. Usage: blockend add <block> --yes"
|
|
124
|
+
);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
284
127
|
if (!targetBlock) {
|
|
285
128
|
const availableOptions = Object.entries(blockMap).filter(([_, block]) => {
|
|
286
129
|
if (!block) return false;
|
|
@@ -289,17 +132,24 @@ async function addCommand(blockName) {
|
|
|
289
132
|
return hasAdapter || hasEnvironment;
|
|
290
133
|
}).map(([key, block]) => ({
|
|
291
134
|
value: key,
|
|
292
|
-
label: `${key} - ${
|
|
135
|
+
label: `${key} - ${pc.dim(block.description)}`
|
|
293
136
|
}));
|
|
294
137
|
if (availableOptions.length === 0) {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
138
|
+
if (json) {
|
|
139
|
+
outputError(
|
|
140
|
+
json,
|
|
141
|
+
`No backend blocks are currently available for your framework layer: [${envKey}].`
|
|
142
|
+
);
|
|
143
|
+
} else {
|
|
144
|
+
outro(
|
|
145
|
+
pc.yellow(
|
|
146
|
+
`\u26A0 No backend blocks are currently available for your framework layer: [${envKey}].`
|
|
147
|
+
)
|
|
148
|
+
);
|
|
149
|
+
}
|
|
300
150
|
return;
|
|
301
151
|
}
|
|
302
|
-
const selectBlockPrompt = await
|
|
152
|
+
const selectBlockPrompt = await select({
|
|
303
153
|
message: "Which backend block would you like to inject?",
|
|
304
154
|
options: availableOptions
|
|
305
155
|
});
|
|
@@ -308,28 +158,34 @@ async function addCommand(blockName) {
|
|
|
308
158
|
}
|
|
309
159
|
const blockMeta = blockMap[targetBlock];
|
|
310
160
|
if (!blockMeta) {
|
|
311
|
-
|
|
161
|
+
outputError(json, `Block "${targetBlock}" does not exist in the remote registry.`);
|
|
312
162
|
return;
|
|
313
163
|
}
|
|
314
164
|
const adapterContext = blockMeta.adapters?.[envKey] ?? blockMeta.environments?.[envKey];
|
|
315
165
|
if (!adapterContext) {
|
|
316
|
-
|
|
317
|
-
|
|
166
|
+
outputError(
|
|
167
|
+
json,
|
|
168
|
+
`The block "${targetBlock}" does not support your environment layout: ${envKey}`
|
|
318
169
|
);
|
|
319
170
|
return;
|
|
320
171
|
}
|
|
321
172
|
let selectedVariant;
|
|
322
173
|
const variantKeys = Object.keys(adapterContext.variants || {});
|
|
323
174
|
if (variantKeys.length === 0) {
|
|
324
|
-
|
|
175
|
+
outputError(
|
|
176
|
+
json,
|
|
177
|
+
`No architecture storage layout variants found for block framework: ${envKey}`
|
|
178
|
+
);
|
|
325
179
|
return;
|
|
326
180
|
}
|
|
327
|
-
if (
|
|
181
|
+
if (yes) {
|
|
182
|
+
selectedVariant = variantKeys.includes("memory") ? "memory" : variantKeys[0];
|
|
183
|
+
} else if (variantKeys.includes("redis") && config.redisEnabled) {
|
|
328
184
|
selectedVariant = "redis";
|
|
329
185
|
} else if (variantKeys.length === 1) {
|
|
330
186
|
selectedVariant = variantKeys[0];
|
|
331
187
|
} else {
|
|
332
|
-
const selectVariantPrompt = await
|
|
188
|
+
const selectVariantPrompt = await select({
|
|
333
189
|
message: "Which architectural storage variant do you want to back this block?",
|
|
334
190
|
options: variantKeys.map((vKey) => ({
|
|
335
191
|
value: vKey,
|
|
@@ -344,20 +200,20 @@ async function addCommand(blockName) {
|
|
|
344
200
|
if (physicalPath.startsWith("@")) {
|
|
345
201
|
physicalPath = "./src/blocks";
|
|
346
202
|
}
|
|
347
|
-
const targetFolder =
|
|
203
|
+
const targetFolder = path.resolve(rootDir, physicalPath, targetBlock);
|
|
348
204
|
const packageJsonPath = await findUp("package.json", rootDir);
|
|
349
205
|
if (!packageJsonPath) {
|
|
350
|
-
|
|
206
|
+
outputError(json, "Could not locate package.json in your current directory layout hierarchy.");
|
|
351
207
|
return;
|
|
352
208
|
}
|
|
353
209
|
const packageJsonDir = dirname(packageJsonPath);
|
|
354
210
|
let packageJson;
|
|
355
211
|
try {
|
|
356
|
-
const packageJsonContent = await
|
|
212
|
+
const packageJsonContent = await fs.readFile(packageJsonPath, "utf-8");
|
|
357
213
|
packageJson = JSON.parse(packageJsonContent);
|
|
358
214
|
} catch (error) {
|
|
359
|
-
|
|
360
|
-
console.error(
|
|
215
|
+
outputError(json, "Failed parsing file data configurations from target package.json location.");
|
|
216
|
+
if (!json) console.error(pc.dim(String(error)));
|
|
361
217
|
return;
|
|
362
218
|
}
|
|
363
219
|
const installedDeps = {
|
|
@@ -369,18 +225,20 @@ async function addCommand(blockName) {
|
|
|
369
225
|
const hasMissingDeps = missingProdDeps.length > 0 || missingDevDeps.length > 0;
|
|
370
226
|
if (hasMissingDeps) {
|
|
371
227
|
const allMissingNames = [...missingProdDeps, ...missingDevDeps];
|
|
372
|
-
|
|
373
|
-
|
|
228
|
+
if (!json) {
|
|
229
|
+
console.log(
|
|
230
|
+
pc.yellow(`
|
|
374
231
|
\u26A0\uFE0F Missing required infrastructure packages: ${allMissingNames.join(", ")}`)
|
|
375
|
-
|
|
376
|
-
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
const shouldInstallPrompt = yes ? true : await confirm({
|
|
377
235
|
message: "Would you like the CLI to automatically install these dependencies?",
|
|
378
236
|
initialValue: true
|
|
379
237
|
});
|
|
380
|
-
handleCancel(shouldInstallPrompt);
|
|
238
|
+
if (!yes) handleCancel(shouldInstallPrompt);
|
|
381
239
|
if (shouldInstallPrompt) {
|
|
382
|
-
const packageManager = await
|
|
383
|
-
s.start(`Preparing native workspace via ${packageManager}...`);
|
|
240
|
+
const packageManager = await fs.access(join(packageJsonDir, "pnpm-lock.yaml")).then(() => "pnpm").catch(() => "npm");
|
|
241
|
+
if (!json) s.start(`Preparing native workspace via ${packageManager}...`);
|
|
384
242
|
try {
|
|
385
243
|
const installTasks = [];
|
|
386
244
|
if (missingProdDeps.length > 0) {
|
|
@@ -394,23 +252,33 @@ async function addCommand(blockName) {
|
|
|
394
252
|
);
|
|
395
253
|
}
|
|
396
254
|
for (const installCmd of installTasks) {
|
|
397
|
-
s.stop(
|
|
255
|
+
if (!json) s.stop(pc.cyan(`Executing: ${installCmd}
|
|
398
256
|
`));
|
|
399
257
|
await new Promise((resolve, reject) => {
|
|
400
258
|
const child = exec(installCmd, { cwd: packageJsonDir });
|
|
401
|
-
child.stdout?.on("data", (data) =>
|
|
402
|
-
|
|
259
|
+
child.stdout?.on("data", (data) => {
|
|
260
|
+
if (!json) process.stdout.write(pc.dim(data));
|
|
261
|
+
});
|
|
262
|
+
child.stderr?.on("data", (data) => {
|
|
263
|
+
if (!json) process.stdout.write(pc.dim(pc.red(data)));
|
|
264
|
+
});
|
|
403
265
|
child.on(
|
|
404
266
|
"close",
|
|
405
267
|
(code) => code === 0 ? resolve() : reject(new Error(`Exit code ${code}`))
|
|
406
268
|
);
|
|
407
269
|
});
|
|
408
270
|
}
|
|
409
|
-
|
|
410
|
-
|
|
271
|
+
if (!json) {
|
|
272
|
+
s.start(pc.green("\u2714 All dependencies synchronized successfully."));
|
|
273
|
+
s.stop();
|
|
274
|
+
}
|
|
411
275
|
} catch (error) {
|
|
412
|
-
|
|
413
|
-
|
|
276
|
+
if (!json) {
|
|
277
|
+
s.stop(pc.red("\u2716 Automated dependency installation failed. Please run setup manually."));
|
|
278
|
+
console.error(pc.dim(String(error)));
|
|
279
|
+
} else {
|
|
280
|
+
outputError(json, "Automated dependency installation failed.");
|
|
281
|
+
}
|
|
414
282
|
return;
|
|
415
283
|
}
|
|
416
284
|
}
|
|
@@ -422,7 +290,7 @@ async function addCommand(blockName) {
|
|
|
422
290
|
if (adapterContext.core) {
|
|
423
291
|
filesToDownload.push({
|
|
424
292
|
source: adapterContext.core,
|
|
425
|
-
target:
|
|
293
|
+
target: path.basename(adapterContext.core, ".txt")
|
|
426
294
|
});
|
|
427
295
|
}
|
|
428
296
|
if (variantMeta && Array.isArray(variantMeta.files)) {
|
|
@@ -430,7 +298,7 @@ async function addCommand(blockName) {
|
|
|
430
298
|
if (typeof fileEntry === "string") {
|
|
431
299
|
filesToDownload.push({
|
|
432
300
|
source: fileEntry,
|
|
433
|
-
target:
|
|
301
|
+
target: path.basename(fileEntry, ".txt")
|
|
434
302
|
});
|
|
435
303
|
} else {
|
|
436
304
|
filesToDownload.push(fileEntry);
|
|
@@ -439,53 +307,787 @@ async function addCommand(blockName) {
|
|
|
439
307
|
}
|
|
440
308
|
let fileExistsConflict = false;
|
|
441
309
|
for (const fileMap of filesToDownload) {
|
|
442
|
-
const destinationPath =
|
|
310
|
+
const destinationPath = join(targetFolder, fileMap.target);
|
|
443
311
|
try {
|
|
444
|
-
await
|
|
312
|
+
await fs.access(destinationPath);
|
|
445
313
|
fileExistsConflict = true;
|
|
446
314
|
break;
|
|
447
315
|
} catch {
|
|
448
316
|
}
|
|
449
317
|
}
|
|
450
318
|
if (fileExistsConflict) {
|
|
451
|
-
const overwritePrompt = await
|
|
319
|
+
const overwritePrompt = yes ? true : await confirm({
|
|
452
320
|
message: `\u26A0 Components in [${targetBlock}] already exist. Overwrite custom revisions?`,
|
|
453
321
|
initialValue: false
|
|
454
322
|
});
|
|
455
|
-
handleCancel(overwritePrompt);
|
|
323
|
+
if (!yes) handleCancel(overwritePrompt);
|
|
456
324
|
if (!overwritePrompt) {
|
|
457
|
-
|
|
325
|
+
outputResult(json, {
|
|
326
|
+
success: false,
|
|
327
|
+
reason: "aborted",
|
|
328
|
+
message: "Local modifications preserved."
|
|
329
|
+
});
|
|
458
330
|
return;
|
|
459
331
|
}
|
|
460
332
|
}
|
|
461
|
-
|
|
333
|
+
if (!json) {
|
|
334
|
+
s.start(`Downloading and building template adapter blocks [${targetBlock}]...`);
|
|
335
|
+
}
|
|
462
336
|
try {
|
|
463
337
|
for (const fileMap of filesToDownload) {
|
|
464
338
|
const fileUrl = `${RAW_CDN_BASE}/${fileMap.source}`;
|
|
465
339
|
const response = await fetch(fileUrl);
|
|
466
340
|
if (!response.ok) throw new Error(`Download failed for: ${fileMap.source}`);
|
|
467
341
|
const fileContent = await response.text();
|
|
468
|
-
const localWriteLocation =
|
|
469
|
-
await
|
|
470
|
-
await
|
|
471
|
-
}
|
|
472
|
-
s.stop(
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
)
|
|
342
|
+
const localWriteLocation = join(targetFolder, fileMap.target);
|
|
343
|
+
await fs.mkdir(dirname(localWriteLocation), { recursive: true });
|
|
344
|
+
await fs.writeFile(localWriteLocation, fileContent, "utf-8");
|
|
345
|
+
}
|
|
346
|
+
if (!json) s.stop(pc.green("\u2714 Component isolation structures written smoothly."));
|
|
347
|
+
outputResult(json, {
|
|
348
|
+
success: true,
|
|
349
|
+
block: targetBlock,
|
|
350
|
+
filesWritten: filesToDownload.map((f) => join(targetFolder, f.target)),
|
|
351
|
+
dependenciesInstalled: [...missingProdDeps, ...missingDevDeps],
|
|
352
|
+
message: `Source blocks written to ${physicalPath}/${targetBlock}/. Code ownership transferred!`
|
|
353
|
+
});
|
|
354
|
+
} catch (error) {
|
|
355
|
+
if (!json) {
|
|
356
|
+
s.stop(pc.red("\u2716 Fatal error occurred while assembling file mappings."));
|
|
357
|
+
console.error(pc.dim(String(error)));
|
|
358
|
+
} else {
|
|
359
|
+
outputError(json, "Fatal error occurred while assembling file mappings.");
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// src/commands/init.ts
|
|
365
|
+
import path2, { join as join2 } from "path";
|
|
366
|
+
import fs2 from "fs/promises";
|
|
367
|
+
import { existsSync } from "fs";
|
|
368
|
+
import { intro as intro2, outro as outro2, select as select2, text, confirm as confirm2, spinner as spinner2, isCancel as isCancel2 } from "@clack/prompts";
|
|
369
|
+
import { detectProject } from "@blockend/detector";
|
|
370
|
+
|
|
371
|
+
// src/ui/theme.ts
|
|
372
|
+
import pc2 from "picocolors";
|
|
373
|
+
var theme = {
|
|
374
|
+
brand: {
|
|
375
|
+
primary: pc2.blue,
|
|
376
|
+
title: pc2.bold,
|
|
377
|
+
logo: pc2.white
|
|
378
|
+
},
|
|
379
|
+
text: {
|
|
380
|
+
normal: pc2.white,
|
|
381
|
+
muted: pc2.dim,
|
|
382
|
+
subtle: pc2.gray
|
|
383
|
+
},
|
|
384
|
+
state: {
|
|
385
|
+
success: pc2.green,
|
|
386
|
+
warning: pc2.yellow,
|
|
387
|
+
error: pc2.red,
|
|
388
|
+
info: pc2.blue
|
|
389
|
+
},
|
|
390
|
+
emphasis: {
|
|
391
|
+
strong: pc2.bold,
|
|
392
|
+
dim: pc2.dim
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
// src/ui/format.ts
|
|
397
|
+
var format = {
|
|
398
|
+
title: (text2) => theme.brand.primary(theme.brand.title(text2)),
|
|
399
|
+
success: (text2) => theme.state.success(`\u2714 ${text2}`),
|
|
400
|
+
error: (text2) => theme.state.error(`\u2716 ${text2}`),
|
|
401
|
+
warning: (text2) => theme.state.warning(text2),
|
|
402
|
+
muted: (text2) => theme.text.muted(text2),
|
|
403
|
+
info: (text2) => theme.state.info(text2)
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
// src/commands/init.ts
|
|
407
|
+
async function resolveTsConfigPaths(cwd) {
|
|
408
|
+
const possiblePaths = [join2(cwd, "tsconfig.json"), join2(cwd, "jsconfig.json")];
|
|
409
|
+
for (const configPath of possiblePaths) {
|
|
410
|
+
if (existsSync(configPath)) {
|
|
411
|
+
try {
|
|
412
|
+
const rawContent = await fs2.readFile(configPath, "utf-8");
|
|
413
|
+
const cleanJsonContent = rawContent.replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, "$1");
|
|
414
|
+
const parsed = JSON.parse(cleanJsonContent);
|
|
415
|
+
const baseUrl = parsed.compilerOptions?.baseUrl || ".";
|
|
416
|
+
const paths = parsed.compilerOptions?.paths || {};
|
|
417
|
+
return { baseUrl, paths };
|
|
418
|
+
} catch {
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return { baseUrl: ".", paths: {} };
|
|
423
|
+
}
|
|
424
|
+
function outputInitError(json, message) {
|
|
425
|
+
if (json) {
|
|
426
|
+
process.stdout.write(JSON.stringify({ success: false, error: message }) + "\n");
|
|
427
|
+
} else {
|
|
428
|
+
outro2(format.error(message));
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
function outputInitResult(json, result) {
|
|
432
|
+
if (json) {
|
|
433
|
+
process.stdout.write(JSON.stringify(result) + "\n");
|
|
434
|
+
} else {
|
|
435
|
+
outro2(theme.state.success(result.message));
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
async function initCommand(options = {}) {
|
|
439
|
+
const { yes = false, json = false } = options;
|
|
440
|
+
const cwd = process.cwd();
|
|
441
|
+
const configPath = join2(cwd, "blockend.json");
|
|
442
|
+
if (!json) {
|
|
443
|
+
console.log(`
|
|
444
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557
|
|
445
|
+
\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2554\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557
|
|
446
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551
|
|
447
|
+
\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551
|
|
448
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D
|
|
449
|
+
\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D
|
|
450
|
+
`);
|
|
451
|
+
intro2(theme.brand.primary(" Blockend \xB7 Intelligent Backend Blocks Setup "));
|
|
452
|
+
}
|
|
453
|
+
if (existsSync(configPath)) {
|
|
454
|
+
let action;
|
|
455
|
+
if (yes) {
|
|
456
|
+
action = "overwrite";
|
|
457
|
+
} else {
|
|
458
|
+
const actionPrompt = await select2({
|
|
459
|
+
message: "blockend.json already exists. What do you want to do?",
|
|
460
|
+
options: [
|
|
461
|
+
{ value: "keep", label: "Keep existing config (cancel init)" },
|
|
462
|
+
{ value: "overwrite", label: "Overwrite config" },
|
|
463
|
+
{ value: "regenerate", label: "Delete and regenerate" }
|
|
464
|
+
]
|
|
465
|
+
});
|
|
466
|
+
if (isCancel2(actionPrompt) || actionPrompt === "keep") {
|
|
467
|
+
if (json) {
|
|
468
|
+
outputInitResult(json, {
|
|
469
|
+
success: false,
|
|
470
|
+
message: "Initialization cancelled. Existing config preserved."
|
|
471
|
+
});
|
|
472
|
+
} else {
|
|
473
|
+
outro2(format.muted("Initialization cancelled. Existing config preserved."));
|
|
474
|
+
}
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
action = actionPrompt;
|
|
478
|
+
}
|
|
479
|
+
if (action === "regenerate") {
|
|
480
|
+
await fs2.unlink(configPath);
|
|
481
|
+
if (!json) console.log(format.error("Existing config deleted"));
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
const s = spinner2();
|
|
485
|
+
if (!json) s.start("Scanning project layout...");
|
|
486
|
+
const context = await detectProject(cwd);
|
|
487
|
+
const hasSrcDir = existsSync(join2(cwd, "src"));
|
|
488
|
+
const tsConfig = await resolveTsConfigPaths(cwd);
|
|
489
|
+
if (!json) s.stop(format.success("Project architecture scanned"));
|
|
490
|
+
let framework = context.framework;
|
|
491
|
+
if (!framework) {
|
|
492
|
+
if (yes) {
|
|
493
|
+
framework = "express";
|
|
494
|
+
} else {
|
|
495
|
+
const frameworkSelect = await select2({
|
|
496
|
+
message: "Framework could not be auto-detected. Select framework environment manually:",
|
|
497
|
+
options: [
|
|
498
|
+
{ value: "express", label: "Express.js" },
|
|
499
|
+
{ value: "fastify", label: "Fastify" },
|
|
500
|
+
{ value: "next", label: "Next.js (App Router)" },
|
|
501
|
+
{ value: "hono", label: "Hono" }
|
|
502
|
+
]
|
|
503
|
+
});
|
|
504
|
+
if (isCancel2(frameworkSelect)) {
|
|
505
|
+
if (json) {
|
|
506
|
+
outputInitResult(json, { success: false, message: "Initialization cancelled." });
|
|
507
|
+
} else {
|
|
508
|
+
outro2(format.muted("Initialization cancelled."));
|
|
509
|
+
}
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
framework = frameworkSelect;
|
|
513
|
+
}
|
|
514
|
+
} else if (!json) {
|
|
515
|
+
console.log(
|
|
516
|
+
`${format.success("\u2714")} Framework environment detected: ${theme.state.info(framework)}`
|
|
477
517
|
);
|
|
518
|
+
}
|
|
519
|
+
const defaultDir = hasSrcDir ? "src/blocks" : "blocks";
|
|
520
|
+
let rawPhysicalInput = defaultDir;
|
|
521
|
+
if (!yes) {
|
|
522
|
+
const directoryPrompt = await text({
|
|
523
|
+
message: "Configure the targeted physical directory destination for blocks:",
|
|
524
|
+
placeholder: defaultDir,
|
|
525
|
+
initialValue: defaultDir,
|
|
526
|
+
validate(value) {
|
|
527
|
+
if (value?.trim().length === 0) return "Physical path location directory cannot be empty.";
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
if (isCancel2(directoryPrompt)) {
|
|
531
|
+
outro2(format.muted("Initialization cancelled."));
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
rawPhysicalInput = String(directoryPrompt).trim();
|
|
535
|
+
}
|
|
536
|
+
const relativeBlocksPath = path2.relative(cwd, path2.resolve(cwd, rawPhysicalInput));
|
|
537
|
+
let finalPath = relativeBlocksPath.replace(/\\/g, "/");
|
|
538
|
+
if (!finalPath.startsWith(".") && !finalPath.startsWith("/")) {
|
|
539
|
+
finalPath = `./${finalPath}`;
|
|
540
|
+
}
|
|
541
|
+
let finalBlockAlias = "@/blocks";
|
|
542
|
+
const configuredAliases = Object.keys(tsConfig.paths);
|
|
543
|
+
if (configuredAliases.length > 0) {
|
|
544
|
+
const matchedAliasKey = configuredAliases.find((alias) => {
|
|
545
|
+
const targets = tsConfig.paths[alias];
|
|
546
|
+
if (Array.isArray(targets) && targets.length > 0) {
|
|
547
|
+
const targetSubPath = targets[0].replace(/\*$/, "").replace(/\\/g, "/");
|
|
548
|
+
return finalPath.replace(/^\.\//, "").startsWith(targetSubPath);
|
|
549
|
+
}
|
|
550
|
+
return false;
|
|
551
|
+
});
|
|
552
|
+
if (matchedAliasKey) {
|
|
553
|
+
const cleanKey = matchedAliasKey.replace(/\*$/, "");
|
|
554
|
+
const targets = tsConfig.paths[matchedAliasKey];
|
|
555
|
+
const targetSubPath = targets[0].replace(/\*$/, "").replace(/\\/g, "/");
|
|
556
|
+
const relativeSuffix = finalPath.replace(/^\.\//, "").replace(targetSubPath, "");
|
|
557
|
+
finalBlockAlias = `${cleanKey}${relativeSuffix}`.replace(/\/$/, "");
|
|
558
|
+
} else {
|
|
559
|
+
const primaryAlias = configuredAliases.find((k) => k.startsWith("@")) || configuredAliases[0];
|
|
560
|
+
const inferredBaseAlias = primaryAlias.replace(/\*$/, "");
|
|
561
|
+
const folderSegmentName = path2.basename(finalPath);
|
|
562
|
+
finalBlockAlias = `${inferredBaseAlias}${folderSegmentName}`;
|
|
563
|
+
}
|
|
564
|
+
} else {
|
|
565
|
+
const folderSegmentName = path2.basename(finalPath);
|
|
566
|
+
finalBlockAlias = `@/${folderSegmentName}`;
|
|
567
|
+
}
|
|
568
|
+
let includeRedis = false;
|
|
569
|
+
if (context.hasRedis) {
|
|
570
|
+
if (yes) {
|
|
571
|
+
includeRedis = true;
|
|
572
|
+
} else {
|
|
573
|
+
const redisConfirm = await confirm2({
|
|
574
|
+
message: "Redis detected. Enable Redis-backed block variants automatically?",
|
|
575
|
+
initialValue: true
|
|
576
|
+
});
|
|
577
|
+
if (!isCancel2(redisConfirm)) {
|
|
578
|
+
includeRedis = Boolean(redisConfirm);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
const configPayload = {
|
|
583
|
+
$schema: "https://blockend.dev/schema.json",
|
|
584
|
+
environment: framework || "express",
|
|
585
|
+
language: context.language || "typescript",
|
|
586
|
+
includeRedis,
|
|
587
|
+
aliases: {
|
|
588
|
+
blocks: finalBlockAlias
|
|
589
|
+
},
|
|
590
|
+
paths: {
|
|
591
|
+
blocks: finalPath
|
|
592
|
+
}
|
|
593
|
+
};
|
|
594
|
+
if (!json) s.start("Finalizing configuration...");
|
|
595
|
+
try {
|
|
596
|
+
await fs2.writeFile(configPath, JSON.stringify(configPayload, null, 2), "utf-8");
|
|
597
|
+
if (!json) s.stop(format.success("blockend.json ready"));
|
|
598
|
+
outputInitResult(json, {
|
|
599
|
+
success: true,
|
|
600
|
+
message: "\u2728 Blockend initialized successfully. Run: npx blockend add <block>",
|
|
601
|
+
config: configPayload
|
|
602
|
+
});
|
|
603
|
+
} catch {
|
|
604
|
+
if (!json) {
|
|
605
|
+
s.stop(format.error("Failed to write configuration"));
|
|
606
|
+
} else {
|
|
607
|
+
outputInitError(json, "Failed to write architectural layout configuration map.");
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// src/commands/detect.ts
|
|
613
|
+
import { detectProject as detectProject2 } from "@blockend/detector";
|
|
614
|
+
import { outro as outro3 } from "@clack/prompts";
|
|
615
|
+
import pc3 from "picocolors";
|
|
616
|
+
async function detectCommand(options = {}) {
|
|
617
|
+
const { json = false } = options;
|
|
618
|
+
try {
|
|
619
|
+
const context = await detectProject2(process.cwd());
|
|
620
|
+
if (json) {
|
|
621
|
+
process.stdout.write(JSON.stringify(context, null, 2) + "\n");
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
console.log();
|
|
625
|
+
console.log(pc3.bold(" Detected project configuration:"));
|
|
626
|
+
console.log();
|
|
627
|
+
console.log(` Framework: ${pc3.cyan(context.framework)}`);
|
|
628
|
+
console.log(` Language: ${pc3.cyan(context.language)}`);
|
|
629
|
+
console.log(` Package manager: ${pc3.cyan(context.packageManager)}`);
|
|
630
|
+
console.log(` Source dir: ${pc3.dim(context.srcDir)}`);
|
|
631
|
+
console.log(
|
|
632
|
+
` Redis: ${context.hasRedis ? pc3.green("detected") : pc3.dim("not found")}`
|
|
633
|
+
);
|
|
634
|
+
console.log(
|
|
635
|
+
` Prisma: ${context.hasPrisma ? pc3.green("detected") : pc3.dim("not found")}`
|
|
636
|
+
);
|
|
637
|
+
console.log(
|
|
638
|
+
` Drizzle: ${context.hasDrizzle ? pc3.green("detected") : pc3.dim("not found")}`
|
|
639
|
+
);
|
|
640
|
+
console.log();
|
|
478
641
|
} catch (error) {
|
|
479
|
-
|
|
480
|
-
|
|
642
|
+
if (json) {
|
|
643
|
+
process.stdout.write(JSON.stringify({ success: false, error: String(error) }) + "\n");
|
|
644
|
+
} else {
|
|
645
|
+
outro3(pc3.red(`\u2716 Detection failed: ${String(error)}`));
|
|
646
|
+
}
|
|
647
|
+
process.exit(1);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// src/commands/list.ts
|
|
652
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
653
|
+
import fs3 from "fs/promises";
|
|
654
|
+
import pc4 from "picocolors";
|
|
655
|
+
import { outro as outro4, spinner as spinner3 } from "@clack/prompts";
|
|
656
|
+
var REPO_OWNER2 = "codewithnuh";
|
|
657
|
+
var REPO_NAME2 = "blockend";
|
|
658
|
+
var BRANCH2 = "master";
|
|
659
|
+
var MANIFEST_URL2 = `https://raw.githubusercontent.com/${REPO_OWNER2}/${REPO_NAME2}/${BRANCH2}/registry/index.json`;
|
|
660
|
+
async function findUp2(filename, startDir) {
|
|
661
|
+
let dir = startDir;
|
|
662
|
+
while (true) {
|
|
663
|
+
const checkPath = join3(dir, filename);
|
|
664
|
+
try {
|
|
665
|
+
await fs3.access(checkPath);
|
|
666
|
+
return checkPath;
|
|
667
|
+
} catch {
|
|
668
|
+
const parent = dirname2(dir);
|
|
669
|
+
if (parent === dir) break;
|
|
670
|
+
dir = parent;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
return null;
|
|
674
|
+
}
|
|
675
|
+
async function listCommand(options = {}) {
|
|
676
|
+
const { json = false } = options;
|
|
677
|
+
const cwd = process.cwd();
|
|
678
|
+
const configPath = await findUp2("blockend.json", cwd);
|
|
679
|
+
if (!configPath) {
|
|
680
|
+
if (json) {
|
|
681
|
+
process.stdout.write(
|
|
682
|
+
JSON.stringify({ success: false, error: "blockend.json not found." }) + "\n"
|
|
683
|
+
);
|
|
684
|
+
} else {
|
|
685
|
+
outro4(pc4.red("\u2716 blockend.json not found. Run 'npx blockend init' first."));
|
|
686
|
+
}
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
let config;
|
|
690
|
+
try {
|
|
691
|
+
const configFile = await fs3.readFile(configPath, "utf-8");
|
|
692
|
+
config = JSON.parse(configFile);
|
|
693
|
+
} catch {
|
|
694
|
+
if (json) {
|
|
695
|
+
process.stdout.write(
|
|
696
|
+
JSON.stringify({ success: false, error: "Failed to parse configuration matrix." }) + "\n"
|
|
697
|
+
);
|
|
698
|
+
} else {
|
|
699
|
+
outro4(pc4.red("\u2716 Failed to parse blockend.json layout configuration."));
|
|
700
|
+
}
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
const envKey = String(config.environment);
|
|
704
|
+
const s = spinner3();
|
|
705
|
+
if (!json) {
|
|
706
|
+
s.start("Syncing registry metadata blocks...");
|
|
707
|
+
}
|
|
708
|
+
let registry;
|
|
709
|
+
try {
|
|
710
|
+
const response = await fetch(MANIFEST_URL2);
|
|
711
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
712
|
+
registry = await response.json();
|
|
713
|
+
if (!json) s.stop(pc4.green("\u2714 Synced available block registries."));
|
|
714
|
+
} catch {
|
|
715
|
+
if (json) {
|
|
716
|
+
process.stdout.write(
|
|
717
|
+
JSON.stringify({ success: false, error: "Network sync failure." }) + "\n"
|
|
718
|
+
);
|
|
719
|
+
} else {
|
|
720
|
+
s.stop(pc4.red("\u2716 Failed to fetch the component registry from GitHub network paths."));
|
|
721
|
+
}
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
let blockMap = {};
|
|
725
|
+
if (registry.blocks && typeof registry.blocks === "object" && !Array.isArray(registry.blocks)) {
|
|
726
|
+
blockMap = registry.blocks;
|
|
727
|
+
} else {
|
|
728
|
+
blockMap = registry;
|
|
729
|
+
}
|
|
730
|
+
const blocksForEnv = Object.entries(blockMap).filter(([_, block]) => {
|
|
731
|
+
if (!block) return false;
|
|
732
|
+
const hasAdapter = block.adapters?.[envKey] !== void 0;
|
|
733
|
+
const hasEnvironment = block.environments?.[envKey] !== void 0;
|
|
734
|
+
return hasAdapter || hasEnvironment;
|
|
735
|
+
}).map(([key, block]) => {
|
|
736
|
+
const context = block.adapters?.[envKey] ?? block.environments?.[envKey];
|
|
737
|
+
return {
|
|
738
|
+
name: key,
|
|
739
|
+
description: block.description,
|
|
740
|
+
variants: Object.keys(context?.variants || {})
|
|
741
|
+
};
|
|
742
|
+
});
|
|
743
|
+
if (json) {
|
|
744
|
+
process.stdout.write(
|
|
745
|
+
JSON.stringify({ success: true, environment: envKey, blocks: blocksForEnv }) + "\n"
|
|
746
|
+
);
|
|
747
|
+
} else {
|
|
748
|
+
console.log(`
|
|
749
|
+
Available backend blocks for ${pc4.magenta(envKey)}:`);
|
|
750
|
+
if (blocksForEnv.length === 0) {
|
|
751
|
+
console.log(pc4.dim(" No blocks found for this framework."));
|
|
752
|
+
} else {
|
|
753
|
+
blocksForEnv.forEach((b) => {
|
|
754
|
+
console.log(`
|
|
755
|
+
${pc4.cyan(b.name)}`);
|
|
756
|
+
console.log(` ${pc4.dim(b.description)}`);
|
|
757
|
+
console.log(` Storage Variants: ${b.variants.map((v) => pc4.yellow(v)).join(", ")}`);
|
|
758
|
+
});
|
|
759
|
+
console.log();
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// src/commands/mcp.ts
|
|
765
|
+
import path3, { join as join4 } from "path";
|
|
766
|
+
import fs4 from "fs/promises";
|
|
767
|
+
import pc5 from "picocolors";
|
|
768
|
+
import { outro as outro5, spinner as spinner4, select as select3, confirm as confirm3 } from "@clack/prompts";
|
|
769
|
+
var CLIENT_MATRIX = {
|
|
770
|
+
claude: {
|
|
771
|
+
label: "Claude Code",
|
|
772
|
+
relativePath: ".mcp.json",
|
|
773
|
+
format: "json"
|
|
774
|
+
},
|
|
775
|
+
codex: {
|
|
776
|
+
label: "Codex CLI",
|
|
777
|
+
relativePath: ".codex/config.toml",
|
|
778
|
+
format: "toml"
|
|
779
|
+
},
|
|
780
|
+
cursor: {
|
|
781
|
+
label: "Cursor",
|
|
782
|
+
relativePath: ".cursor/mcp.json",
|
|
783
|
+
format: "json"
|
|
784
|
+
},
|
|
785
|
+
vscode: {
|
|
786
|
+
label: "VS Code",
|
|
787
|
+
relativePath: ".vscode/settings.json",
|
|
788
|
+
format: "json",
|
|
789
|
+
isCustomSchema: true
|
|
790
|
+
},
|
|
791
|
+
windsurf: {
|
|
792
|
+
label: "Windsurf",
|
|
793
|
+
relativePath: ".windsurf/mcp.json",
|
|
794
|
+
format: "json"
|
|
795
|
+
}
|
|
796
|
+
};
|
|
797
|
+
var BLOCKEND_SERVER = {
|
|
798
|
+
command: "npx",
|
|
799
|
+
args: ["-y", "blockend-cli", "mcp"]
|
|
800
|
+
};
|
|
801
|
+
function generateToml(server) {
|
|
802
|
+
const args = server.args.map((arg) => `"${arg}"`).join(", ");
|
|
803
|
+
return `
|
|
804
|
+
[mcp_servers.blockend]
|
|
805
|
+
command = "${server.command}"
|
|
806
|
+
args = [${args}]
|
|
807
|
+
`.trimStart();
|
|
808
|
+
}
|
|
809
|
+
async function mcpStartCommand() {
|
|
810
|
+
const { McpServer } = await import("@modelcontextprotocol/sdk/server/mcp.js");
|
|
811
|
+
const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
|
|
812
|
+
const { z } = await import("zod");
|
|
813
|
+
const { execa } = await import("execa");
|
|
814
|
+
const server = new McpServer({
|
|
815
|
+
name: "blockend",
|
|
816
|
+
version: "0.1.0",
|
|
817
|
+
description: "Blockend MCP server \u2014 install production-ready backend blocks into any project"
|
|
818
|
+
});
|
|
819
|
+
async function safeExec(command, args, options) {
|
|
820
|
+
try {
|
|
821
|
+
const { stdout } = await execa(command, args, options);
|
|
822
|
+
return { text: String(stdout), isError: false };
|
|
823
|
+
} catch (error) {
|
|
824
|
+
const err = error;
|
|
825
|
+
const rawError = err.stderr || err.stdout || err.message || "Unknown execution error";
|
|
826
|
+
return { text: `Error executing command: ${String(rawError)}`, isError: true };
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
server.tool(
|
|
830
|
+
"list_blocks",
|
|
831
|
+
"Lists all available Blockend blocks with names, descriptions, and tags",
|
|
832
|
+
{},
|
|
833
|
+
async () => {
|
|
834
|
+
const result = await safeExec("npx", ["--no-install", "blockend", "list", "--json"]);
|
|
835
|
+
return { content: [{ type: "text", text: result.text }], isError: result.isError };
|
|
836
|
+
}
|
|
837
|
+
);
|
|
838
|
+
server.tool(
|
|
839
|
+
"add_block",
|
|
840
|
+
"Installs a Blockend block into a project.",
|
|
841
|
+
{
|
|
842
|
+
blockName: z.string().describe("Block name e.g. rate-limit, error-handler, logger"),
|
|
843
|
+
projectPath: z.string().describe("Absolute path to the project root where blockend should install")
|
|
844
|
+
},
|
|
845
|
+
async ({ blockName, projectPath }) => {
|
|
846
|
+
const result = await safeExec(
|
|
847
|
+
"npx",
|
|
848
|
+
["--no-install", "blockend", "add", blockName, "--yes", "--json"],
|
|
849
|
+
{ cwd: projectPath }
|
|
850
|
+
);
|
|
851
|
+
return { content: [{ type: "text", text: result.text }], isError: result.isError };
|
|
852
|
+
}
|
|
853
|
+
);
|
|
854
|
+
server.tool(
|
|
855
|
+
"detect_project",
|
|
856
|
+
"Analyzes a project context.",
|
|
857
|
+
{
|
|
858
|
+
projectPath: z.string().describe("Absolute path to the project root")
|
|
859
|
+
},
|
|
860
|
+
async ({ projectPath }) => {
|
|
861
|
+
const result = await safeExec("npx", ["--no-install", "blockend", "detect", "--json"], {
|
|
862
|
+
cwd: projectPath
|
|
863
|
+
});
|
|
864
|
+
return { content: [{ type: "text", text: result.text }], isError: result.isError };
|
|
865
|
+
}
|
|
866
|
+
);
|
|
867
|
+
const transport = new StdioServerTransport();
|
|
868
|
+
await server.connect(transport);
|
|
869
|
+
}
|
|
870
|
+
async function mcpInitCommand(options) {
|
|
871
|
+
const cwd = process.cwd();
|
|
872
|
+
let chosenClient = options.client;
|
|
873
|
+
if (!chosenClient) {
|
|
874
|
+
if (options.yes) {
|
|
875
|
+
chosenClient = "claude";
|
|
876
|
+
} else {
|
|
877
|
+
const selection = await select3({
|
|
878
|
+
message: "Select target AI client layout profile:",
|
|
879
|
+
options: Object.entries(CLIENT_MATRIX).map(([key, value]) => ({
|
|
880
|
+
value: key,
|
|
881
|
+
label: value.label
|
|
882
|
+
}))
|
|
883
|
+
});
|
|
884
|
+
if (typeof selection === "symbol") return;
|
|
885
|
+
chosenClient = selection;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
const meta = CLIENT_MATRIX[chosenClient];
|
|
889
|
+
if (!meta) {
|
|
890
|
+
outro5(pc5.red(`\u2716 Unsupported client identifier profile: ${options.client}`));
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
const absoluteConfigTarget = join4(cwd, meta.relativePath);
|
|
894
|
+
if (options.dryRun) {
|
|
895
|
+
console.log(
|
|
896
|
+
pc5.cyan(`
|
|
897
|
+
\u2139 Dry-run mode active. Intended path location: ${pc5.dim(meta.relativePath)}`)
|
|
898
|
+
);
|
|
899
|
+
if (meta.format === "json") {
|
|
900
|
+
const jsonConfig = meta.isCustomSchema ? { "mcp.mcpServers": { blockend: BLOCKEND_SERVER } } : { mcpServers: { blockend: BLOCKEND_SERVER } };
|
|
901
|
+
console.log(pc5.gray(JSON.stringify(jsonConfig, null, 2)));
|
|
902
|
+
} else {
|
|
903
|
+
console.log(pc5.gray(generateToml(BLOCKEND_SERVER)));
|
|
904
|
+
}
|
|
905
|
+
outro5(pc5.green("\u2714 Dry run evaluation complete."));
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
let fileConflict = false;
|
|
909
|
+
try {
|
|
910
|
+
await fs4.access(absoluteConfigTarget);
|
|
911
|
+
fileConflict = true;
|
|
912
|
+
} catch {
|
|
913
|
+
}
|
|
914
|
+
if (fileConflict && !options.force && !options.yes) {
|
|
915
|
+
const shouldOverwrite = await confirm3({
|
|
916
|
+
message: `\u26A0 Local configuration at [${meta.relativePath}] already exists. Overwrite?`,
|
|
917
|
+
initialValue: false
|
|
918
|
+
});
|
|
919
|
+
if (!shouldOverwrite || typeof shouldOverwrite === "symbol") {
|
|
920
|
+
outro5(pc5.yellow("\u2139 Operations halted. Project layout revisions preserved."));
|
|
921
|
+
return;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
const s = spinner4();
|
|
925
|
+
s.start(`Writing localized project configurations inside ${pc5.dim(meta.relativePath)}...`);
|
|
926
|
+
try {
|
|
927
|
+
await fs4.mkdir(path3.dirname(absoluteConfigTarget), { recursive: true });
|
|
928
|
+
if (meta.format === "json") {
|
|
929
|
+
let workingConfig = {};
|
|
930
|
+
if (fileConflict) {
|
|
931
|
+
try {
|
|
932
|
+
const rawContent = await fs4.readFile(absoluteConfigTarget, "utf-8");
|
|
933
|
+
workingConfig = JSON.parse(rawContent);
|
|
934
|
+
} catch {
|
|
935
|
+
workingConfig = {};
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
if (meta.isCustomSchema) {
|
|
939
|
+
const existingServers = workingConfig["mcp.mcpServers"] || {};
|
|
940
|
+
workingConfig["mcp.mcpServers"] = { ...existingServers, blockend: BLOCKEND_SERVER };
|
|
941
|
+
} else {
|
|
942
|
+
const existingServers = workingConfig.mcpServers || {};
|
|
943
|
+
workingConfig.mcpServers = { ...existingServers, blockend: BLOCKEND_SERVER };
|
|
944
|
+
}
|
|
945
|
+
await fs4.writeFile(absoluteConfigTarget, JSON.stringify(workingConfig, null, 2), "utf-8");
|
|
946
|
+
} else {
|
|
947
|
+
await fs4.writeFile(absoluteConfigTarget, generateToml(BLOCKEND_SERVER), "utf-8");
|
|
948
|
+
}
|
|
949
|
+
s.stop(
|
|
950
|
+
pc5.green(`\u2714 Local workspace integration fully complete! File target: ${meta.relativePath}`)
|
|
951
|
+
);
|
|
952
|
+
} catch (error) {
|
|
953
|
+
s.stop(pc5.red("\u2716 Fatal crash reading or creating local configuration maps."));
|
|
954
|
+
console.error(pc5.dim(String(error)));
|
|
481
955
|
}
|
|
482
956
|
}
|
|
483
957
|
|
|
484
958
|
// src/index.ts
|
|
485
|
-
var
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
959
|
+
var mcpInit = defineCommand({
|
|
960
|
+
meta: {
|
|
961
|
+
name: "init",
|
|
962
|
+
description: "Generate appropriate project-local MCP infrastructure configurations"
|
|
963
|
+
},
|
|
964
|
+
args: {
|
|
965
|
+
client: {
|
|
966
|
+
type: "string",
|
|
967
|
+
description: "Target specific integration client profiles: claude | codex | cursor | vscode | windsurf"
|
|
968
|
+
},
|
|
969
|
+
force: {
|
|
970
|
+
type: "boolean",
|
|
971
|
+
default: false,
|
|
972
|
+
description: "Force override existing asset layouts"
|
|
973
|
+
},
|
|
974
|
+
"dry-run": {
|
|
975
|
+
type: "boolean",
|
|
976
|
+
default: false,
|
|
977
|
+
description: "Output planned workspace updates without disk mutation logs"
|
|
978
|
+
},
|
|
979
|
+
yes: { type: "boolean", default: false, description: "Bypass verification prompt iterations" }
|
|
980
|
+
},
|
|
981
|
+
async run({ args }) {
|
|
982
|
+
await mcpInitCommand({
|
|
983
|
+
client: args.client,
|
|
984
|
+
force: args.force,
|
|
985
|
+
dryRun: args["dry-run"],
|
|
986
|
+
yes: args.yes
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
});
|
|
990
|
+
var mcp = defineCommand({
|
|
991
|
+
meta: {
|
|
992
|
+
name: "mcp",
|
|
993
|
+
description: "Connect to context protocol channels or configure localized project setups"
|
|
994
|
+
},
|
|
995
|
+
subCommands: {
|
|
996
|
+
init: mcpInit
|
|
997
|
+
},
|
|
998
|
+
async run({ rawArgs }) {
|
|
999
|
+
if (rawArgs.length === 0) {
|
|
1000
|
+
await mcpStartCommand();
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
});
|
|
1004
|
+
var init = defineCommand({
|
|
1005
|
+
meta: {
|
|
1006
|
+
name: "init",
|
|
1007
|
+
description: "Initialize Blockend configuration profile (blockend.json)"
|
|
1008
|
+
},
|
|
1009
|
+
args: {
|
|
1010
|
+
yes: {
|
|
1011
|
+
type: "boolean",
|
|
1012
|
+
alias: "y",
|
|
1013
|
+
default: false,
|
|
1014
|
+
description: "Skip initialization questions and auto-enforce layout defaults"
|
|
1015
|
+
},
|
|
1016
|
+
json: {
|
|
1017
|
+
type: "boolean",
|
|
1018
|
+
default: false,
|
|
1019
|
+
description: "Output machine-readable configuration write states"
|
|
1020
|
+
}
|
|
1021
|
+
},
|
|
1022
|
+
async run({ args }) {
|
|
1023
|
+
await initCommand({ yes: args.yes, json: args.json });
|
|
1024
|
+
}
|
|
1025
|
+
});
|
|
1026
|
+
var list = defineCommand({
|
|
1027
|
+
meta: {
|
|
1028
|
+
name: "list",
|
|
1029
|
+
description: "List available component blocks matching local project runtime environment context"
|
|
1030
|
+
},
|
|
1031
|
+
args: {
|
|
1032
|
+
json: {
|
|
1033
|
+
type: "boolean",
|
|
1034
|
+
default: false,
|
|
1035
|
+
description: "Output machine-readable blocks configuration array stream"
|
|
1036
|
+
}
|
|
1037
|
+
},
|
|
1038
|
+
async run({ args }) {
|
|
1039
|
+
await listCommand({ json: args.json });
|
|
1040
|
+
}
|
|
1041
|
+
});
|
|
1042
|
+
var add = defineCommand({
|
|
1043
|
+
meta: {
|
|
1044
|
+
name: "add",
|
|
1045
|
+
description: "Inject a production-grade component block straight into your codebase"
|
|
1046
|
+
},
|
|
1047
|
+
args: {
|
|
1048
|
+
block: {
|
|
1049
|
+
type: "positional",
|
|
1050
|
+
required: false,
|
|
1051
|
+
description: "Target block key name from the remote repository registry"
|
|
1052
|
+
},
|
|
1053
|
+
yes: {
|
|
1054
|
+
type: "boolean",
|
|
1055
|
+
alias: "y",
|
|
1056
|
+
default: false,
|
|
1057
|
+
description: "Skip structural modification confirmations and force dependency downloads"
|
|
1058
|
+
},
|
|
1059
|
+
json: {
|
|
1060
|
+
type: "boolean",
|
|
1061
|
+
default: false,
|
|
1062
|
+
description: "Output streaming machine-readable JSON payloads for automation systems"
|
|
1063
|
+
}
|
|
1064
|
+
},
|
|
1065
|
+
async run({ args }) {
|
|
1066
|
+
await addCommand(args.block, { yes: args.yes, json: args.json });
|
|
1067
|
+
}
|
|
1068
|
+
});
|
|
1069
|
+
var detect = defineCommand({
|
|
1070
|
+
meta: {
|
|
1071
|
+
name: "detect",
|
|
1072
|
+
description: "Scan runtime directory frameworks, engines, and workspaces architectures"
|
|
1073
|
+
},
|
|
1074
|
+
args: {
|
|
1075
|
+
json: {
|
|
1076
|
+
type: "boolean",
|
|
1077
|
+
default: false,
|
|
1078
|
+
description: "Output full detected project workspace context delta maps directly as JSON"
|
|
1079
|
+
}
|
|
1080
|
+
},
|
|
1081
|
+
async run({ args }) {
|
|
1082
|
+
await detectCommand({ json: args.json });
|
|
1083
|
+
}
|
|
1084
|
+
});
|
|
1085
|
+
var main = defineCommand({
|
|
1086
|
+
meta: {
|
|
1087
|
+
name: "blockend",
|
|
1088
|
+
version: "0.1.0",
|
|
1089
|
+
description: "Blockend CLI - Core architectural blocks straight into your repository layout primitives"
|
|
1090
|
+
},
|
|
1091
|
+
subCommands: { init, add, detect, list, mcp }
|
|
490
1092
|
});
|
|
491
|
-
|
|
1093
|
+
runMain(main);
|