blockend-cli 1.3.1 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +228 -154
- package/package.json +5 -2
package/dist/index.js
CHANGED
|
@@ -46,18 +46,35 @@ var format = {
|
|
|
46
46
|
};
|
|
47
47
|
|
|
48
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
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return { baseUrl: ".", paths: {} };
|
|
65
|
+
}
|
|
49
66
|
async function initCommand() {
|
|
50
67
|
const cwd = process.cwd();
|
|
51
68
|
const configPath = join(cwd, "blockend.json");
|
|
52
69
|
console.log(`
|
|
53
|
-
\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557
|
|
54
|
-
\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551
|
|
55
|
-
\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551
|
|
56
|
-
\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551
|
|
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
|
|
57
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
|
|
58
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
|
|
59
76
|
`);
|
|
60
|
-
intro(
|
|
77
|
+
intro(theme.brand.primary(" Blockend \xB7 Intelligent Backend Blocks Setup "));
|
|
61
78
|
if (existsSync(configPath)) {
|
|
62
79
|
const action = await select({
|
|
63
80
|
message: "blockend.json already exists. What do you want to do?",
|
|
@@ -77,53 +94,78 @@ async function initCommand() {
|
|
|
77
94
|
}
|
|
78
95
|
}
|
|
79
96
|
const s = spinner();
|
|
80
|
-
s.start("Scanning project
|
|
97
|
+
s.start("Scanning project layout...");
|
|
81
98
|
const context = await detectProject(cwd);
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
outro(format.muted("Initialization cancelled."));
|
|
106
|
-
return;
|
|
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
|
+
);
|
|
107
|
+
} else {
|
|
108
|
+
const frameworkSelect = await select({
|
|
109
|
+
message: "Framework could not be auto-detected. Select framework environment manually:",
|
|
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;
|
|
107
122
|
}
|
|
108
|
-
const
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
initialValue:
|
|
113
|
-
|
|
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
|
+
}
|
|
114
131
|
});
|
|
115
|
-
if (isCancel(
|
|
132
|
+
if (isCancel(directoryPrompt)) {
|
|
116
133
|
outro(format.muted("Initialization cancelled."));
|
|
117
134
|
return;
|
|
118
135
|
}
|
|
119
|
-
const
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const
|
|
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(/\/$/, "");
|
|
159
|
+
} else {
|
|
160
|
+
const primaryAlias = configuredAliases.find((k) => k.startsWith("@")) || configuredAliases[0];
|
|
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
|
+
}
|
|
127
169
|
let includeRedis = false;
|
|
128
170
|
if (context.hasRedis) {
|
|
129
171
|
const redisConfirm = await confirm({
|
|
@@ -136,11 +178,11 @@ async function initCommand() {
|
|
|
136
178
|
}
|
|
137
179
|
const configPayload = {
|
|
138
180
|
$schema: "https://blockend.dev/schema.json",
|
|
139
|
-
environment: framework,
|
|
140
|
-
language: "typescript",
|
|
181
|
+
environment: framework || "express",
|
|
182
|
+
language: context.language || "typescript",
|
|
141
183
|
includeRedis,
|
|
142
184
|
aliases: {
|
|
143
|
-
blocks:
|
|
185
|
+
blocks: finalBlockAlias
|
|
144
186
|
},
|
|
145
187
|
paths: {
|
|
146
188
|
blocks: finalPath
|
|
@@ -151,7 +193,9 @@ async function initCommand() {
|
|
|
151
193
|
try {
|
|
152
194
|
await fs.writeFile(configPath, JSON.stringify(configPayload, null, 2), "utf-8");
|
|
153
195
|
writeSpinner.stop(format.success("blockend.json ready"));
|
|
154
|
-
outro(
|
|
196
|
+
outro(
|
|
197
|
+
theme.state.success("\u2728 Blockend initialized successfully. Run: npx blockend add <block>")
|
|
198
|
+
);
|
|
155
199
|
} catch {
|
|
156
200
|
writeSpinner.stop(format.error("Failed to write configuration"));
|
|
157
201
|
}
|
|
@@ -202,17 +246,16 @@ async function addCommand(blockName) {
|
|
|
202
246
|
try {
|
|
203
247
|
const configFile = await fs2.readFile(configPath, "utf-8");
|
|
204
248
|
config = JSON.parse(configFile);
|
|
205
|
-
} catch {
|
|
249
|
+
} catch (error) {
|
|
206
250
|
outro2(pc2.red("\u2716 Failed to parse blockend.json layout configuration."));
|
|
251
|
+
console.error(pc2.dim(String(error)));
|
|
207
252
|
return;
|
|
208
253
|
}
|
|
209
254
|
if (config.language !== "typescript") {
|
|
210
255
|
outro2(
|
|
211
256
|
pc2.yellow(
|
|
212
257
|
`
|
|
213
|
-
\u2139 Blockend forces modern architectural standards.
|
|
214
|
-
To ensure strict type safety and absolute code ownership, the registry exclusively supports TypeScript.
|
|
215
|
-
Please migrate your project configuration to TypeScript to ingest blocks.`
|
|
258
|
+
\u2139 Blockend forces modern architectural standards. Registry exclusively supports TypeScript.`
|
|
216
259
|
)
|
|
217
260
|
);
|
|
218
261
|
return;
|
|
@@ -225,16 +268,28 @@ async function addCommand(blockName) {
|
|
|
225
268
|
if (!response.ok) throw new Error(`HTTP Error Status: ${response.status}`);
|
|
226
269
|
registry = await response.json();
|
|
227
270
|
s.stop(pc2.green("\u2714 Remote manifest synchronization complete."));
|
|
228
|
-
} catch {
|
|
271
|
+
} catch (error) {
|
|
229
272
|
s.stop(pc2.red("\u2716 Failed to fetch the component registry from GitHub network paths."));
|
|
273
|
+
console.error(pc2.dim(String(error)));
|
|
230
274
|
return;
|
|
231
275
|
}
|
|
276
|
+
let blockMap = {};
|
|
277
|
+
if (registry.blocks && typeof registry.blocks === "object" && !Array.isArray(registry.blocks)) {
|
|
278
|
+
blockMap = registry.blocks;
|
|
279
|
+
} else {
|
|
280
|
+
blockMap = registry;
|
|
281
|
+
}
|
|
232
282
|
const envKey = String(config.environment);
|
|
233
283
|
let targetBlock = blockName;
|
|
234
284
|
if (!targetBlock) {
|
|
235
|
-
const availableOptions = Object.
|
|
285
|
+
const availableOptions = Object.entries(blockMap).filter(([_, block]) => {
|
|
286
|
+
if (!block) return false;
|
|
287
|
+
const hasAdapter = block.adapters?.[envKey] !== void 0;
|
|
288
|
+
const hasEnvironment = block.environments?.[envKey] !== void 0;
|
|
289
|
+
return hasAdapter || hasEnvironment;
|
|
290
|
+
}).map(([key, block]) => ({
|
|
236
291
|
value: key,
|
|
237
|
-
label: `${key} - ${pc2.dim(
|
|
292
|
+
label: `${key} - ${pc2.dim(block.description)}`
|
|
238
293
|
}));
|
|
239
294
|
if (availableOptions.length === 0) {
|
|
240
295
|
outro2(
|
|
@@ -251,37 +306,45 @@ async function addCommand(blockName) {
|
|
|
251
306
|
handleCancel(selectBlockPrompt);
|
|
252
307
|
targetBlock = selectBlockPrompt;
|
|
253
308
|
}
|
|
254
|
-
const blockMeta =
|
|
309
|
+
const blockMeta = blockMap[targetBlock];
|
|
255
310
|
if (!blockMeta) {
|
|
256
311
|
outro2(pc2.red(`\u2716 Block "${targetBlock}" does not exist in the remote registry.`));
|
|
257
312
|
return;
|
|
258
313
|
}
|
|
259
|
-
const
|
|
260
|
-
if (!
|
|
314
|
+
const adapterContext = blockMeta.adapters?.[envKey] ?? blockMeta.environments?.[envKey];
|
|
315
|
+
if (!adapterContext) {
|
|
261
316
|
outro2(
|
|
262
317
|
pc2.red(`\u2716 The block "${targetBlock}" does not support your environment layout: ${envKey}`)
|
|
263
318
|
);
|
|
264
319
|
return;
|
|
265
320
|
}
|
|
266
321
|
let selectedVariant;
|
|
267
|
-
|
|
268
|
-
if (
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
322
|
+
const variantKeys = Object.keys(adapterContext.variants || {});
|
|
323
|
+
if (variantKeys.length === 0) {
|
|
324
|
+
outro2(pc2.red(`\u2716 No architecture storage layout variants found for block framework: ${envKey}`));
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (variantKeys.includes("redis") && config.redisEnabled) {
|
|
328
|
+
selectedVariant = "redis";
|
|
329
|
+
} else if (variantKeys.length === 1) {
|
|
330
|
+
selectedVariant = variantKeys[0];
|
|
331
|
+
} else {
|
|
332
|
+
const selectVariantPrompt = await select2({
|
|
333
|
+
message: "Which architectural storage variant do you want to back this block?",
|
|
334
|
+
options: variantKeys.map((vKey) => ({
|
|
335
|
+
value: vKey,
|
|
336
|
+
label: vKey.toUpperCase()
|
|
337
|
+
}))
|
|
338
|
+
});
|
|
339
|
+
handleCancel(selectVariantPrompt);
|
|
340
|
+
selectedVariant = selectVariantPrompt;
|
|
341
|
+
}
|
|
342
|
+
const variantMeta = adapterContext.variants[selectedVariant];
|
|
343
|
+
let physicalPath = config.paths.blocks;
|
|
344
|
+
if (physicalPath.startsWith("@")) {
|
|
345
|
+
physicalPath = "./src/blocks";
|
|
284
346
|
}
|
|
347
|
+
const targetFolder = path2.resolve(rootDir, physicalPath, targetBlock);
|
|
285
348
|
const packageJsonPath = await findUp("package.json", rootDir);
|
|
286
349
|
if (!packageJsonPath) {
|
|
287
350
|
outro2(pc2.red("\u2716 Could not locate package.json in your current directory layout hierarchy."));
|
|
@@ -290,21 +353,25 @@ async function addCommand(blockName) {
|
|
|
290
353
|
const packageJsonDir = dirname(packageJsonPath);
|
|
291
354
|
let packageJson;
|
|
292
355
|
try {
|
|
293
|
-
|
|
294
|
-
|
|
356
|
+
const packageJsonContent = await fs2.readFile(packageJsonPath, "utf-8");
|
|
357
|
+
packageJson = JSON.parse(packageJsonContent);
|
|
358
|
+
} catch (error) {
|
|
295
359
|
outro2(pc2.red("\u2716 Failed parsing file data configurations from target package.json location."));
|
|
360
|
+
console.error(pc2.dim(String(error)));
|
|
296
361
|
return;
|
|
297
362
|
}
|
|
298
363
|
const installedDeps = {
|
|
299
364
|
...packageJson.dependencies ?? {},
|
|
300
365
|
...packageJson.devDependencies ?? {}
|
|
301
366
|
};
|
|
302
|
-
const
|
|
303
|
-
const
|
|
304
|
-
|
|
367
|
+
const missingProdDeps = (adapterContext.dependencies ?? []).concat(variantMeta?.dependencies ?? []).filter((dep) => !(dep in installedDeps));
|
|
368
|
+
const missingDevDeps = (adapterContext.devDependencies ?? []).concat(variantMeta?.devDependencies ?? []).filter((dep) => !(dep in installedDeps));
|
|
369
|
+
const hasMissingDeps = missingProdDeps.length > 0 || missingDevDeps.length > 0;
|
|
370
|
+
if (hasMissingDeps) {
|
|
371
|
+
const allMissingNames = [...missingProdDeps, ...missingDevDeps];
|
|
305
372
|
console.log(
|
|
306
373
|
pc2.yellow(`
|
|
307
|
-
\u26A0\uFE0F Missing required infrastructure packages: ${
|
|
374
|
+
\u26A0\uFE0F Missing required infrastructure packages: ${allMissingNames.join(", ")}`)
|
|
308
375
|
);
|
|
309
376
|
const shouldInstallPrompt = await confirm2({
|
|
310
377
|
message: "Would you like the CLI to automatically install these dependencies?",
|
|
@@ -315,95 +382,102 @@ async function addCommand(blockName) {
|
|
|
315
382
|
const packageManager = await fs2.access(join2(packageJsonDir, "pnpm-lock.yaml")).then(() => "pnpm").catch(() => "npm");
|
|
316
383
|
s.start(`Preparing native workspace via ${packageManager}...`);
|
|
317
384
|
try {
|
|
318
|
-
const
|
|
319
|
-
|
|
385
|
+
const installTasks = [];
|
|
386
|
+
if (missingProdDeps.length > 0) {
|
|
387
|
+
installTasks.push(
|
|
388
|
+
packageManager === "pnpm" ? `pnpm add ${missingProdDeps.join(" ")}` : `npm install ${missingProdDeps.join(" ")}`
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
if (missingDevDeps.length > 0) {
|
|
392
|
+
installTasks.push(
|
|
393
|
+
packageManager === "pnpm" ? `pnpm add -D ${missingDevDeps.join(" ")}` : `npm install --save-dev ${missingDevDeps.join(" ")}`
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
for (const installCmd of installTasks) {
|
|
397
|
+
s.stop(pc2.cyan(`Executing: ${installCmd}
|
|
320
398
|
`));
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
process.stdout.write(pc2.dim(data));
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
399
|
+
await new Promise((resolve, reject) => {
|
|
400
|
+
const child = exec(installCmd, { cwd: packageJsonDir });
|
|
401
|
+
child.stdout?.on("data", (data) => process.stdout.write(pc2.dim(data)));
|
|
402
|
+
child.stderr?.on("data", (data) => process.stdout.write(pc2.dim(pc2.red(data))));
|
|
403
|
+
child.on(
|
|
404
|
+
"close",
|
|
405
|
+
(code) => code === 0 ? resolve() : reject(new Error(`Exit code ${code}`))
|
|
406
|
+
);
|
|
328
407
|
});
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
else reject(new Error(`Exit code ${code}`));
|
|
332
|
-
});
|
|
333
|
-
});
|
|
334
|
-
console.log("");
|
|
335
|
-
s.start(pc2.green("\u2714 Dependencies installed cleanly. Continuing components build..."));
|
|
408
|
+
}
|
|
409
|
+
s.start(pc2.green("\u2714 All dependencies synchronized successfully."));
|
|
336
410
|
s.stop();
|
|
337
|
-
} catch {
|
|
411
|
+
} catch (error) {
|
|
338
412
|
s.stop(pc2.red("\u2716 Automated dependency installation failed. Please run setup manually."));
|
|
413
|
+
console.error(pc2.dim(String(error)));
|
|
414
|
+
return;
|
|
339
415
|
}
|
|
340
416
|
}
|
|
341
417
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
const parsedFilename = path2.basename(remoteFile, ".txt");
|
|
362
|
-
const checkFileLocation = join2(targetFolder, parsedFilename);
|
|
363
|
-
try {
|
|
364
|
-
await fs2.access(checkFileLocation);
|
|
365
|
-
fileExistsConflict = true;
|
|
366
|
-
break;
|
|
367
|
-
} catch {
|
|
418
|
+
const filesToDownload = [];
|
|
419
|
+
if (blockMeta.baseFiles) {
|
|
420
|
+
filesToDownload.push(...blockMeta.baseFiles);
|
|
421
|
+
}
|
|
422
|
+
if (adapterContext.core) {
|
|
423
|
+
filesToDownload.push({
|
|
424
|
+
source: adapterContext.core,
|
|
425
|
+
target: path2.basename(adapterContext.core, ".txt")
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
if (variantMeta && Array.isArray(variantMeta.files)) {
|
|
429
|
+
for (const fileEntry of variantMeta.files) {
|
|
430
|
+
if (typeof fileEntry === "string") {
|
|
431
|
+
filesToDownload.push({
|
|
432
|
+
source: fileEntry,
|
|
433
|
+
target: path2.basename(fileEntry, ".txt")
|
|
434
|
+
});
|
|
435
|
+
} else {
|
|
436
|
+
filesToDownload.push(fileEntry);
|
|
368
437
|
}
|
|
369
438
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
return;
|
|
380
|
-
}
|
|
381
|
-
s.start(`Re-downloading template block files [${targetBlock}]...`);
|
|
439
|
+
}
|
|
440
|
+
let fileExistsConflict = false;
|
|
441
|
+
for (const fileMap of filesToDownload) {
|
|
442
|
+
const destinationPath = join2(targetFolder, fileMap.target);
|
|
443
|
+
try {
|
|
444
|
+
await fs2.access(destinationPath);
|
|
445
|
+
fileExistsConflict = true;
|
|
446
|
+
break;
|
|
447
|
+
} catch {
|
|
382
448
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
449
|
+
}
|
|
450
|
+
if (fileExistsConflict) {
|
|
451
|
+
const overwritePrompt = await confirm2({
|
|
452
|
+
message: `\u26A0 Components in [${targetBlock}] already exist. Overwrite custom revisions?`,
|
|
453
|
+
initialValue: false
|
|
454
|
+
});
|
|
455
|
+
handleCancel(overwritePrompt);
|
|
456
|
+
if (!overwritePrompt) {
|
|
457
|
+
outro2(pc2.yellow("\u2139 Operation aborted safely. Local code modifications preserved."));
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
s.start(`Downloading and building template adapter blocks [${targetBlock}]...`);
|
|
462
|
+
try {
|
|
463
|
+
for (const fileMap of filesToDownload) {
|
|
464
|
+
const fileUrl = `${RAW_CDN_BASE}/${fileMap.source}`;
|
|
386
465
|
const response = await fetch(fileUrl);
|
|
387
|
-
if (!response.ok) {
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
const localWriteLocation = join2(targetFolder, targetFilename);
|
|
393
|
-
await fs2.writeFile(localWriteLocation, rawCodeTemplate, "utf-8");
|
|
466
|
+
if (!response.ok) throw new Error(`Download failed for: ${fileMap.source}`);
|
|
467
|
+
const fileContent = await response.text();
|
|
468
|
+
const localWriteLocation = join2(targetFolder, fileMap.target);
|
|
469
|
+
await fs2.mkdir(dirname(localWriteLocation), { recursive: true });
|
|
470
|
+
await fs2.writeFile(localWriteLocation, fileContent, "utf-8");
|
|
394
471
|
}
|
|
395
|
-
const cleanDisplayPath = variantMeta && selectedVariant ? `${physicalPath.replace(/\\/g, "/")}/${targetBlock}` : physicalPath.replace(/\\/g, "/");
|
|
396
472
|
s.stop(pc2.green("\u2714 Component isolation structures written smoothly."));
|
|
397
473
|
outro2(
|
|
398
474
|
pc2.cyan(
|
|
399
|
-
`\u2728 Source blocks written to ${
|
|
475
|
+
`\u2728 Source blocks written to ${physicalPath}/${targetBlock}/. Code ownership transferred!`
|
|
400
476
|
)
|
|
401
477
|
);
|
|
402
|
-
process.exit(0);
|
|
403
478
|
} catch (error) {
|
|
404
|
-
s.stop(pc2.red("\u2716 Fatal
|
|
405
|
-
console.error(error);
|
|
406
|
-
process.exit(1);
|
|
479
|
+
s.stop(pc2.red("\u2716 Fatal error occurred while assembling file mappings."));
|
|
480
|
+
console.error(pc2.dim(String(error)));
|
|
407
481
|
}
|
|
408
482
|
}
|
|
409
483
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blockend-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Intelligent, modular backend blocks right inside your terminal",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
"readme.md"
|
|
13
13
|
],
|
|
14
14
|
"scripts": {
|
|
15
|
+
"test": "vitest run",
|
|
16
|
+
"test:watch": "vitest",
|
|
15
17
|
"build": "tsup src/index.ts --format esm --dts --out-dir dist",
|
|
16
18
|
"typecheck": "tsc --noEmit"
|
|
17
19
|
},
|
|
@@ -33,6 +35,7 @@
|
|
|
33
35
|
},
|
|
34
36
|
"devDependencies": {
|
|
35
37
|
"@types/node": "^25.9.3",
|
|
36
|
-
"tsup": "^8.5.1"
|
|
38
|
+
"tsup": "^8.5.1",
|
|
39
|
+
"vitest": "^1.6.1"
|
|
37
40
|
}
|
|
38
41
|
}
|