blockend-cli 1.4.3 → 1.4.4

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.
Files changed (2) hide show
  1. package/dist/index.js +187 -213
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -14,6 +14,22 @@ var REPO_NAME = "blockend";
14
14
  var BRANCH = "master";
15
15
  var RAW_CDN_BASE = `https://raw.githubusercontent.com/${REPO_OWNER}/${REPO_NAME}/${BRANCH}`;
16
16
  var MANIFEST_URL = `${RAW_CDN_BASE}/registry/index.json`;
17
+ function rewriteFileImports(fileContent, writtenFilePath, blocksRoot, aliasBase) {
18
+ if (!aliasBase || aliasBase.startsWith(".")) {
19
+ return fileContent;
20
+ }
21
+ const importRegex = /((?:from|import)\s*)(['"`])(\.\.?\/[^'"` \n\r]+)(['"`])/g;
22
+ const fileDir = dirname(writtenFilePath);
23
+ return fileContent.replace(importRegex, (match, keyword, openQuote, relativePath, closeQuote) => {
24
+ const absoluteTarget = path.resolve(fileDir, relativePath);
25
+ const fromBlocksRoot = path.relative(blocksRoot, absoluteTarget).replace(/\\/g, "/");
26
+ if (fromBlocksRoot.startsWith("..")) {
27
+ return match;
28
+ }
29
+ const aliased = `${aliasBase}/${fromBlocksRoot}`.replace(/\/+/g, "/");
30
+ return `${keyword}${openQuote}${aliased}${closeQuote}`;
31
+ });
32
+ }
17
33
  function outputError(json, message) {
18
34
  if (json) {
19
35
  process.stdout.write(JSON.stringify({ success: false, error: message }) + "\n");
@@ -41,10 +57,10 @@ function handleCancel(value) {
41
57
  async function findUp(filename, startDir) {
42
58
  let dir = startDir;
43
59
  while (true) {
44
- const checkPath = join(dir, filename);
60
+ const candidate = join(dir, filename);
45
61
  try {
46
- await fs.access(checkPath);
47
- return checkPath;
62
+ await fs.access(candidate);
63
+ return candidate;
48
64
  } catch {
49
65
  const parent = dirname(dir);
50
66
  if (parent === dir) break;
@@ -68,89 +84,67 @@ async function addCommand(blockName, options = {}) {
68
84
  const rootDir = dirname(configPath);
69
85
  let config;
70
86
  try {
71
- const configFile = await fs.readFile(configPath, "utf-8");
72
- config = JSON.parse(configFile);
73
- } catch (error) {
87
+ config = JSON.parse(await fs.readFile(configPath, "utf-8"));
88
+ } catch (err) {
74
89
  outputError(json, "Failed to parse blockend.json.");
75
- if (!json) log.error(pc.dim(String(error)));
90
+ if (!json) log.error(pc.dim(String(err)));
76
91
  return;
77
92
  }
78
93
  if (config.language !== "typescript") {
79
- if (json) {
80
- outputError(json, "Registry exclusively supports TypeScript projects.");
81
- } else {
82
- log.warn("Blockend currently only supports TypeScript projects.");
83
- outro(pc.dim("Exiting."));
84
- }
94
+ outputError(json, "Blockend currently only supports TypeScript projects.");
95
+ if (!json) outro(pc.dim("Exiting."));
85
96
  return;
86
97
  }
98
+ const physicalBlocksPath = config.paths.blocks;
99
+ const blocksRootAbsolute = path.resolve(rootDir, physicalBlocksPath);
100
+ const aliasBase = config.aliases.blocks;
101
+ const envKey = config.environment;
87
102
  const s = spinner();
88
- if (!json) {
89
- s.start("Fetching registry...");
90
- }
103
+ if (!json) s.start("Fetching registry...");
91
104
  let registry;
92
105
  try {
93
- const response = await fetch(MANIFEST_URL);
94
- if (!response.ok) throw new Error(`HTTP Error Status: ${response.status}`);
95
- registry = await response.json();
106
+ const res = await fetch(MANIFEST_URL);
107
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
108
+ registry = await res.json();
96
109
  if (!json) s.stop("Registry synced.");
97
- } catch (error) {
110
+ } catch (err) {
98
111
  if (!json) {
99
112
  s.stop("Failed to fetch registry.");
100
- log.error(pc.dim(String(error)));
113
+ log.error(pc.dim(String(err)));
101
114
  } else {
102
115
  outputError(json, "Failed to fetch the component registry from GitHub.");
103
116
  }
104
117
  return;
105
118
  }
106
- let blockMap = {};
107
- if (registry.blocks && typeof registry.blocks === "object" && !Array.isArray(registry.blocks)) {
108
- blockMap = registry.blocks;
109
- } else {
110
- blockMap = registry;
111
- }
112
- const envKey = String(config.environment);
113
- let targetBlock = blockName;
114
- if (yes && !targetBlock) {
115
- outputError(
116
- json,
117
- "Block name required when using --yes flag. Usage: blockend add <block> --yes"
118
- );
119
+ const blockMap = registry.blocks && typeof registry.blocks === "object" && !Array.isArray(registry.blocks) ? registry.blocks : registry;
120
+ if (yes && !blockName) {
121
+ outputError(json, "Block name required with --yes. Usage: blockend add <block> --yes");
119
122
  return;
120
123
  }
124
+ let targetBlock = blockName;
121
125
  if (!targetBlock) {
122
- const filteredBlocks = Object.entries(blockMap).filter(([_, block]) => {
126
+ const available = Object.entries(blockMap).filter(([, block]) => {
123
127
  if (!block) return false;
124
- const hasAdapter = block.adapters?.[envKey] !== void 0;
125
- const hasEnvironment = block.environments?.[envKey] !== void 0;
126
- return hasAdapter || hasEnvironment;
128
+ return block.adapters?.[envKey] !== void 0 || block.environments?.[envKey] !== void 0;
127
129
  });
128
- if (filteredBlocks.length === 0) {
129
- const msg = `No backend blocks are currently available for: ${envKey}.`;
130
- if (json) {
131
- outputError(json, msg);
132
- } else {
133
- log.warn(msg);
134
- outro(pc.dim("Exiting."));
135
- }
130
+ if (available.length === 0) {
131
+ const msg = `No blocks available for environment: ${envKey}`;
132
+ outputError(json, msg);
133
+ if (!json) outro(pc.dim("Exiting."));
136
134
  return;
137
135
  }
138
- const maxKeyLength = Math.max(...filteredBlocks.map(([key]) => key.length), 0);
139
- const availableOptions = filteredBlocks.map(([key, block], index) => {
140
- const paddedKey = key.padEnd(maxKeyLength + 4, " ");
141
- const maxDescWidth = 60;
142
- const optimizedDescription = block.description.length > maxDescWidth ? `${block.description.slice(0, maxDescWidth)}...` : block.description;
143
- return {
144
- value: key,
145
- label: `${pc.dim(`${index + 1}.`)} ${pc.bold(pc.cyan(paddedKey))}${pc.dim(optimizedDescription)}`
146
- };
147
- });
148
- const selectBlockPrompt = await select({
136
+ const maxKeyLen = Math.max(...available.map(([k]) => k.length), 0);
137
+ const selectPrompt = await select({
149
138
  message: "Which block would you like to add?",
150
- options: availableOptions
139
+ options: available.map(([key, block], i) => ({
140
+ value: key,
141
+ label: `${pc.dim(`${i + 1}.`)} ${pc.bold(pc.cyan(key.padEnd(maxKeyLen + 4)))}${pc.dim(
142
+ block.description.length > 60 ? `${block.description.slice(0, 60)}...` : block.description
143
+ )}`
144
+ }))
151
145
  });
152
- handleCancel(selectBlockPrompt);
153
- targetBlock = selectBlockPrompt;
146
+ handleCancel(selectPrompt);
147
+ targetBlock = selectPrompt;
154
148
  }
155
149
  const blockMeta = blockMap[targetBlock];
156
150
  if (!blockMeta) {
@@ -159,189 +153,173 @@ async function addCommand(blockName, options = {}) {
159
153
  }
160
154
  const adapterContext = blockMeta.adapters?.[envKey] ?? blockMeta.environments?.[envKey];
161
155
  if (!adapterContext) {
162
- outputError(json, `The block "${targetBlock}" does not support your environment: ${envKey}`);
156
+ outputError(json, `Block "${targetBlock}" does not support environment: ${envKey}`);
163
157
  return;
164
158
  }
165
- let selectedVariant;
166
- const variantKeys = Object.keys(adapterContext.variants || {});
159
+ const variantKeys = Object.keys(adapterContext.variants ?? {});
167
160
  if (variantKeys.length === 0) {
168
- outputError(json, `No storage variants found for block environment: ${envKey}`);
161
+ outputError(json, `No variants found for block "${targetBlock}" in environment "${envKey}"`);
169
162
  return;
170
163
  }
164
+ let selectedVariant;
171
165
  if (yes) {
172
166
  selectedVariant = variantKeys.includes("memory") ? "memory" : variantKeys[0];
173
- } else if (variantKeys.includes("redis") && config.redisEnabled) {
167
+ } else if (variantKeys.includes("redis") && (config.redisEnabled || config.includeRedis)) {
174
168
  selectedVariant = "redis";
175
169
  } else if (variantKeys.length === 1) {
176
170
  selectedVariant = variantKeys[0];
177
171
  } else {
178
- const selectVariantPrompt = await select({
172
+ const variantPrompt = await select({
179
173
  message: "Select a storage variant:",
180
- options: variantKeys.map((vKey) => ({
181
- value: vKey,
182
- label: vKey.toUpperCase()
183
- }))
174
+ options: variantKeys.map((v) => ({ value: v, label: v.toUpperCase() }))
184
175
  });
185
- handleCancel(selectVariantPrompt);
186
- selectedVariant = selectVariantPrompt;
176
+ handleCancel(variantPrompt);
177
+ selectedVariant = variantPrompt;
187
178
  }
188
179
  const variantMeta = adapterContext.variants[selectedVariant];
189
- let physicalPath = config.paths.blocks;
190
- if (physicalPath.startsWith("@")) {
191
- physicalPath = "./src/blocks";
192
- }
193
- const targetFolder = path.resolve(rootDir, physicalPath, targetBlock);
180
+ const targetFolder = path.resolve(blocksRootAbsolute, targetBlock);
194
181
  const packageJsonPath = await findUp("package.json", rootDir);
195
182
  if (!packageJsonPath) {
196
- outputError(json, "Could not locate package.json in your current directory.");
183
+ outputError(json, "Could not locate package.json.");
197
184
  return;
198
185
  }
199
186
  const packageJsonDir = dirname(packageJsonPath);
200
187
  let packageJson;
201
188
  try {
202
- const packageJsonContent = await fs.readFile(packageJsonPath, "utf-8");
203
- packageJson = JSON.parse(packageJsonContent);
204
- } catch (error) {
205
- outputError(json, "Failed parsing package.json.");
206
- if (!json) log.error(pc.dim(String(error)));
189
+ packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf-8"));
190
+ } catch (err) {
191
+ outputError(json, "Failed to parse package.json.");
192
+ if (!json) log.error(pc.dim(String(err)));
207
193
  return;
208
194
  }
209
195
  const installedDeps = {
210
196
  ...packageJson.dependencies ?? {},
211
197
  ...packageJson.devDependencies ?? {}
212
198
  };
213
- const missingProdDeps = (adapterContext.dependencies ?? []).concat(variantMeta?.dependencies ?? []).filter((dep) => !(dep in installedDeps));
214
- const missingDevDeps = (adapterContext.devDependencies ?? []).concat(variantMeta?.devDependencies ?? []).filter((dep) => !(dep in installedDeps));
215
- const hasMissingDeps = missingProdDeps.length > 0 || missingDevDeps.length > 0;
216
- if (hasMissingDeps) {
217
- const allMissingNames = [...missingProdDeps, ...missingDevDeps];
199
+ const missingProd = [
200
+ ...adapterContext.dependencies ?? [],
201
+ ...variantMeta?.dependencies ?? []
202
+ ].filter((d) => !(d in installedDeps));
203
+ const missingDev = [
204
+ ...adapterContext.devDependencies ?? [],
205
+ ...variantMeta?.devDependencies ?? []
206
+ ].filter((d) => !(d in installedDeps));
207
+ if (missingProd.length > 0 || missingDev.length > 0) {
218
208
  if (!json) {
219
- log.warn(`Missing dependencies: ${pc.cyan(allMissingNames.join(", "))}`);
209
+ log.warn(`Missing dependencies: ${pc.cyan([...missingProd, ...missingDev].join(", "))}`);
220
210
  }
221
- const shouldInstallPrompt = yes ? true : await confirm({
222
- message: "Install missing dependencies?",
223
- initialValue: true
224
- });
225
- if (!yes) handleCancel(shouldInstallPrompt);
226
- if (shouldInstallPrompt) {
227
- const packageManager = await fs.access(join(packageJsonDir, "pnpm-lock.yaml")).then(() => "pnpm").catch(() => "npm");
228
- if (!json) s.start(`Installing via ${packageManager}...`);
211
+ const shouldInstall = yes ? true : await confirm({ message: "Install missing dependencies?", initialValue: true });
212
+ if (!yes) handleCancel(shouldInstall);
213
+ if (shouldInstall) {
214
+ const pm = await fs.access(join(packageJsonDir, "pnpm-lock.yaml")).then(() => "pnpm").catch(() => "npm");
215
+ if (!json) s.start(`Installing via ${pm}...`);
216
+ const tasks = [];
217
+ if (missingProd.length > 0) {
218
+ tasks.push(
219
+ pm === "pnpm" ? `pnpm add ${missingProd.join(" ")}` : `npm install ${missingProd.join(" ")}`
220
+ );
221
+ }
222
+ if (missingDev.length > 0) {
223
+ tasks.push(
224
+ pm === "pnpm" ? `pnpm add -D ${missingDev.join(" ")}` : `npm install --save-dev ${missingDev.join(" ")}`
225
+ );
226
+ }
229
227
  try {
230
- const installTasks = [];
231
- if (missingProdDeps.length > 0) {
232
- installTasks.push(
233
- packageManager === "pnpm" ? `pnpm add ${missingProdDeps.join(" ")}` : `npm install ${missingProdDeps.join(" ")}`
234
- );
235
- }
236
- if (missingDevDeps.length > 0) {
237
- installTasks.push(
238
- packageManager === "pnpm" ? `pnpm add -D ${missingDevDeps.join(" ")}` : `npm install --save-dev ${missingDevDeps.join(" ")}`
239
- );
240
- }
241
- for (const installCmd of installTasks) {
242
- if (!json) s.message(`Executing: ${installCmd}`);
228
+ for (const cmd of tasks) {
229
+ if (!json) s.message(`Running: ${cmd}`);
243
230
  await new Promise((resolve, reject) => {
244
- const child = exec(installCmd, { cwd: packageJsonDir });
245
- child.stdout?.on("data", (data) => {
246
- if (!json) process.stdout.write(pc.dim(data));
231
+ const child = exec(cmd, { cwd: packageJsonDir });
232
+ child.stdout?.on("data", (d) => {
233
+ if (!json) process.stdout.write(pc.dim(d));
247
234
  });
248
- child.stderr?.on("data", (data) => {
249
- if (!json) process.stdout.write(pc.dim(pc.red(data)));
235
+ child.stderr?.on("data", (d) => {
236
+ if (!json) process.stdout.write(pc.dim(pc.red(d)));
250
237
  });
251
238
  child.on(
252
239
  "close",
253
- (code) => code === 0 ? resolve() : reject(new Error(`Exit code ${code}`))
240
+ (code) => code === 0 ? resolve() : reject(new Error(`Exit ${code}`))
254
241
  );
255
242
  });
256
243
  }
257
- if (!json) {
258
- s.stop("Dependencies installed.");
259
- }
260
- } catch (error) {
244
+ if (!json) s.stop("Dependencies installed.");
245
+ } catch (err) {
261
246
  if (!json) {
262
247
  s.stop("Installation failed.");
263
- log.error(pc.dim(String(error)));
248
+ log.error(pc.dim(String(err)));
264
249
  } else {
265
- outputError(json, "Automated dependency installation failed.");
250
+ outputError(json, "Dependency installation failed.");
266
251
  }
267
252
  return;
268
253
  }
269
254
  }
270
255
  }
271
256
  const filesToDownload = [];
272
- if (blockMeta.baseFiles) {
257
+ if (blockMeta.baseFiles && Array.isArray(blockMeta.baseFiles)) {
273
258
  filesToDownload.push(...blockMeta.baseFiles);
274
259
  }
275
260
  if (adapterContext.core) {
276
261
  filesToDownload.push({
277
262
  source: adapterContext.core,
278
- target: path.basename(adapterContext.core, ".txt")
263
+ target: path.basename(adapterContext.core)
279
264
  });
280
265
  }
281
266
  if (variantMeta && Array.isArray(variantMeta.files)) {
282
- for (const fileEntry of variantMeta.files) {
283
- if (typeof fileEntry === "string") {
284
- filesToDownload.push({
285
- source: fileEntry,
286
- target: path.basename(fileEntry, ".txt")
287
- });
267
+ for (const entry of variantMeta.files) {
268
+ if (typeof entry === "string") {
269
+ filesToDownload.push({ source: entry, target: path.basename(entry) });
288
270
  } else {
289
- filesToDownload.push(fileEntry);
271
+ filesToDownload.push(entry);
290
272
  }
291
273
  }
292
274
  }
293
- let fileExistsConflict = false;
294
- for (const fileMap of filesToDownload) {
295
- const destinationPath = join(targetFolder, fileMap.target);
275
+ let hasConflict = false;
276
+ for (const fm of filesToDownload) {
296
277
  try {
297
- await fs.access(destinationPath);
298
- fileExistsConflict = true;
278
+ await fs.access(join(targetFolder, fm.target));
279
+ hasConflict = true;
299
280
  break;
300
281
  } catch {
301
282
  }
302
283
  }
303
- if (fileExistsConflict) {
304
- const overwritePrompt = yes ? true : await confirm({
284
+ if (hasConflict) {
285
+ const overwrite = yes ? true : await confirm({
305
286
  message: `Files for "${targetBlock}" already exist. Overwrite?`,
306
287
  initialValue: false
307
288
  });
308
- if (!yes) handleCancel(overwritePrompt);
309
- if (!overwritePrompt) {
310
- outputResult(json, {
311
- success: false,
312
- reason: "aborted",
313
- message: "Local modifications preserved."
314
- });
289
+ if (!yes) handleCancel(overwrite);
290
+ if (!overwrite) {
291
+ outputResult(json, { success: false, reason: "aborted", message: "Local files preserved." });
315
292
  return;
316
293
  }
317
294
  }
318
- if (!json) {
319
- s.start(`Downloading ${targetBlock}...`);
320
- }
295
+ if (!json) s.start(`Downloading ${targetBlock}...`);
296
+ const filesWritten = [];
321
297
  try {
322
- for (const fileMap of filesToDownload) {
323
- const fileUrl = `${RAW_CDN_BASE}/${fileMap.source}`;
324
- const response = await fetch(fileUrl);
325
- if (!response.ok) throw new Error(`Download failed for: ${fileMap.source}`);
326
- const fileContent = await response.text();
327
- const localWriteLocation = join(targetFolder, fileMap.target);
328
- await fs.mkdir(dirname(localWriteLocation), { recursive: true });
329
- await fs.writeFile(localWriteLocation, fileContent, "utf-8");
298
+ for (const fm of filesToDownload) {
299
+ const fileUrl = `${RAW_CDN_BASE}/${fm.source}`;
300
+ const res = await fetch(fileUrl);
301
+ if (!res.ok) throw new Error(`Failed to download: ${fm.source} (HTTP ${res.status})`);
302
+ let content = await res.text();
303
+ const writtenFilePath = path.join(targetFolder, fm.target);
304
+ content = rewriteFileImports(content, writtenFilePath, blocksRootAbsolute, aliasBase);
305
+ await fs.mkdir(dirname(writtenFilePath), { recursive: true });
306
+ await fs.writeFile(writtenFilePath, content, "utf-8");
307
+ filesWritten.push(writtenFilePath);
330
308
  }
331
- if (!json) s.stop("Files copied.");
309
+ if (!json) s.stop("Files written.");
332
310
  outputResult(json, {
333
311
  success: true,
334
312
  block: targetBlock,
335
- filesWritten: filesToDownload.map((f) => join(targetFolder, f.target)),
336
- dependenciesInstalled: [...missingProdDeps, ...missingDevDeps],
337
- message: `Block added to ${physicalPath}/${targetBlock}`
313
+ filesWritten,
314
+ dependenciesInstalled: [...missingProd, ...missingDev],
315
+ message: `${targetBlock} added to ${physicalBlocksPath}/${targetBlock}`
338
316
  });
339
- } catch (error) {
317
+ } catch (err) {
340
318
  if (!json) {
341
- s.stop("Failed to write files.");
342
- log.error(pc.dim(String(error)));
319
+ s.stop("Failed.");
320
+ log.error(pc.dim(String(err)));
343
321
  } else {
344
- outputError(json, "Fatal error occurred while writing block files.");
322
+ outputError(json, "Fatal error while writing block files.");
345
323
  }
346
324
  }
347
325
  }
@@ -498,22 +476,40 @@ var format = {
498
476
 
499
477
  // src/commands/init.ts
500
478
  async function resolveTsConfigPaths(cwd) {
501
- const possiblePaths = [join7(cwd, "tsconfig.json"), join7(cwd, "jsconfig.json")];
502
- for (const configPath of possiblePaths) {
503
- if (existsSync4(configPath)) {
504
- try {
505
- const rawContent = await fs2.readFile(configPath, "utf-8");
506
- const cleanJsonContent = rawContent.replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, "$1");
507
- const parsed = JSON.parse(cleanJsonContent);
508
- const baseUrl = parsed.compilerOptions?.baseUrl || ".";
509
- const paths = parsed.compilerOptions?.paths || {};
510
- return { baseUrl, paths };
511
- } catch {
512
- }
479
+ const candidates = [join7(cwd, "tsconfig.json"), join7(cwd, "jsconfig.json")];
480
+ for (const configPath of candidates) {
481
+ if (!existsSync4(configPath)) continue;
482
+ try {
483
+ const raw = await fs2.readFile(configPath, "utf-8");
484
+ const clean = raw.replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, "$1");
485
+ const parsed = JSON.parse(clean);
486
+ return {
487
+ baseUrl: parsed.compilerOptions?.baseUrl ?? ".",
488
+ paths: parsed.compilerOptions?.paths ?? {}
489
+ };
490
+ } catch {
513
491
  }
514
492
  }
515
493
  return { baseUrl: ".", paths: {} };
516
494
  }
495
+ function deriveBlockAlias(tsPaths, blocksPhysicalPath) {
496
+ const normalizedBlocksPath = blocksPhysicalPath.replace(/^\.\//, "").replace(/\\/g, "/");
497
+ for (const [aliasKey, targets] of Object.entries(tsPaths)) {
498
+ if (!Array.isArray(targets) || targets.length === 0) continue;
499
+ const rawTarget = targets[0].replace(/\\/g, "/");
500
+ const targetRoot = rawTarget.replace(/\*$/, "").replace(/\/$/, "").replace(/^\.\//, "");
501
+ const aliasRoot = aliasKey.replace(/\/\*$/, "").replace(/\*$/, "");
502
+ if (targetRoot === "") {
503
+ return `${aliasRoot}/${normalizedBlocksPath}`.replace(/\/+/g, "/");
504
+ }
505
+ if (normalizedBlocksPath === targetRoot || normalizedBlocksPath.startsWith(`${targetRoot}/`)) {
506
+ const suffix = normalizedBlocksPath.slice(targetRoot.length).replace(/^\//, "");
507
+ const alias = suffix ? `${aliasRoot}/${suffix}` : aliasRoot;
508
+ return alias.replace(/\/+/g, "/");
509
+ }
510
+ }
511
+ return void 0;
512
+ }
517
513
  function outputInitError(json, message) {
518
514
  if (json) {
519
515
  process.stdout.write(JSON.stringify({ success: false, error: message }) + "\n");
@@ -543,7 +539,7 @@ async function initCommand(options = {}) {
543
539
  action = "overwrite";
544
540
  } else {
545
541
  const actionPrompt = await select2({
546
- message: "A blockend.json configuration already exists. What would you like to do?",
542
+ message: "A blockend.json already exists. What would you like to do?",
547
543
  options: [
548
544
  { value: "keep", label: "Keep existing (cancel setup)" },
549
545
  { value: "overwrite", label: "Overwrite current configuration" },
@@ -609,7 +605,7 @@ async function initCommand(options = {}) {
609
605
  placeholder: defaultDir,
610
606
  initialValue: defaultDir,
611
607
  validate(value) {
612
- if (value?.trim().length === 0) return "Directory path cannot be empty.";
608
+ if (!value || value.trim().length === 0) return "Directory path cannot be empty.";
613
609
  }
614
610
  });
615
611
  if (isCancel2(directoryPrompt)) {
@@ -618,37 +614,14 @@ async function initCommand(options = {}) {
618
614
  }
619
615
  rawPhysicalInput = String(directoryPrompt).trim();
620
616
  }
621
- const relativeBlocksPath = path2.relative(cwd, path2.resolve(cwd, rawPhysicalInput));
622
- let finalPath = relativeBlocksPath.replace(/\\/g, "/");
623
- if (!finalPath.startsWith(".") && !finalPath.startsWith("/")) {
624
- finalPath = `./${finalPath}`;
625
- }
626
- let finalBlockAlias = "@/blocks";
627
- const configuredAliases = Object.keys(tsConfig.paths);
628
- if (configuredAliases.length > 0) {
629
- const matchedAliasKey = configuredAliases.find((alias) => {
630
- const targets = tsConfig.paths[alias];
631
- if (Array.isArray(targets) && targets.length > 0) {
632
- const targetSubPath = targets[0].replace(/\*$/, "").replace(/\\/g, "/");
633
- return finalPath.replace(/^\.\//, "").startsWith(targetSubPath);
634
- }
635
- return false;
636
- });
637
- if (matchedAliasKey) {
638
- const cleanKey = matchedAliasKey.replace(/\*$/, "");
639
- const targets = tsConfig.paths[matchedAliasKey];
640
- const targetSubPath = targets[0].replace(/\*$/, "").replace(/\\/g, "/");
641
- const relativeSuffix = finalPath.replace(/^\.\//, "").replace(targetSubPath, "");
642
- finalBlockAlias = `${cleanKey}${relativeSuffix}`.replace(/\/$/, "");
643
- } else {
644
- const primaryAlias = configuredAliases.find((k) => k.startsWith("@")) || configuredAliases[0];
645
- const inferredBaseAlias = primaryAlias.replace(/\*$/, "");
646
- const folderSegmentName = path2.basename(finalPath);
647
- finalBlockAlias = `${inferredBaseAlias}${folderSegmentName}`;
648
- }
649
- } else {
650
- const folderSegmentName = path2.basename(finalPath);
651
- finalBlockAlias = `@/${folderSegmentName}`;
617
+ const relativeBlocksPath = path2.relative(cwd, path2.resolve(cwd, rawPhysicalInput)).replace(/\\/g, "/");
618
+ const finalPath = relativeBlocksPath.startsWith(".") ? relativeBlocksPath : `./${relativeBlocksPath}`;
619
+ const hasTsPaths = Object.keys(tsConfig.paths).length > 0;
620
+ const finalBlockAlias = hasTsPaths ? deriveBlockAlias(tsConfig.paths, relativeBlocksPath) : void 0;
621
+ if (!json) {
622
+ log2.info(
623
+ `Import alias: ${theme.state.info(finalBlockAlias === void 0 ? " relative imports" : finalBlockAlias)}`
624
+ );
652
625
  }
653
626
  let includeRedis = false;
654
627
  if (context.hasRedis) {
@@ -665,15 +638,16 @@ async function initCommand(options = {}) {
665
638
  }
666
639
  }
667
640
  const configPayload = {
668
- $schema: "https://blockend.dev/schema.json",
641
+ $schema: "https://blockend.noorulhassan.com/schema.json",
669
642
  environment: framework || "express",
670
643
  language: context.language || "typescript",
671
644
  includeRedis,
672
645
  aliases: {
673
- blocks: finalBlockAlias
646
+ ...finalBlockAlias ? { blocks: finalBlockAlias } : {}
674
647
  },
675
648
  paths: {
676
649
  blocks: finalPath
650
+ // e.g. "./src/blocks" — always a real physical path
677
651
  }
678
652
  };
679
653
  if (!json) s.start("Writing configuration...");
@@ -682,7 +656,7 @@ async function initCommand(options = {}) {
682
656
  if (!json) s.stop("Configuration saved.");
683
657
  outputInitResult(json, {
684
658
  success: true,
685
- message: "Blockend initialized successfully! Run: npx blockend add <block>",
659
+ message: "Blockend initialized! Run: npx blockend add <block>",
686
660
  config: configPayload
687
661
  });
688
662
  } catch {
@@ -690,7 +664,7 @@ async function initCommand(options = {}) {
690
664
  s.stop("Failed");
691
665
  outputInitError(false, "Failed to write configuration file.");
692
666
  } else {
693
- outputInitError(true, "Failed to write architectural layout configuration map.");
667
+ outputInitError(true, "Failed to write configuration file.");
694
668
  }
695
669
  }
696
670
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blockend-cli",
3
- "version": "1.4.3",
3
+ "version": "1.4.4",
4
4
  "description": "CLI for installing production-ready backend blocks into Next.js, Express, Hono, NestJS, and other Node.js applications.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",