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