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.
- package/dist/index.js +187 -213
- 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
|
|
60
|
+
const candidate = join(dir, filename);
|
|
45
61
|
try {
|
|
46
|
-
await fs.access(
|
|
47
|
-
return
|
|
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
|
-
|
|
72
|
-
|
|
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(
|
|
90
|
+
if (!json) log.error(pc.dim(String(err)));
|
|
76
91
|
return;
|
|
77
92
|
}
|
|
78
93
|
if (config.language !== "typescript") {
|
|
79
|
-
|
|
80
|
-
|
|
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
|
|
94
|
-
if (!
|
|
95
|
-
registry = await
|
|
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 (
|
|
110
|
+
} catch (err) {
|
|
98
111
|
if (!json) {
|
|
99
112
|
s.stop("Failed to fetch registry.");
|
|
100
|
-
log.error(pc.dim(String(
|
|
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
|
-
|
|
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
|
|
126
|
+
const available = Object.entries(blockMap).filter(([, block]) => {
|
|
123
127
|
if (!block) return false;
|
|
124
|
-
|
|
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 (
|
|
129
|
-
const msg = `No
|
|
130
|
-
|
|
131
|
-
|
|
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
|
|
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({
|
|
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:
|
|
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(
|
|
153
|
-
targetBlock =
|
|
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, `
|
|
156
|
+
outputError(json, `Block "${targetBlock}" does not support environment: ${envKey}`);
|
|
163
157
|
return;
|
|
164
158
|
}
|
|
165
|
-
|
|
166
|
-
const variantKeys = Object.keys(adapterContext.variants || {});
|
|
159
|
+
const variantKeys = Object.keys(adapterContext.variants ?? {});
|
|
167
160
|
if (variantKeys.length === 0) {
|
|
168
|
-
outputError(json, `No
|
|
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
|
|
172
|
+
const variantPrompt = await select({
|
|
179
173
|
message: "Select a storage variant:",
|
|
180
|
-
options: variantKeys.map((
|
|
181
|
-
value: vKey,
|
|
182
|
-
label: vKey.toUpperCase()
|
|
183
|
-
}))
|
|
174
|
+
options: variantKeys.map((v) => ({ value: v, label: v.toUpperCase() }))
|
|
184
175
|
});
|
|
185
|
-
handleCancel(
|
|
186
|
-
selectedVariant =
|
|
176
|
+
handleCancel(variantPrompt);
|
|
177
|
+
selectedVariant = variantPrompt;
|
|
187
178
|
}
|
|
188
179
|
const variantMeta = adapterContext.variants[selectedVariant];
|
|
189
|
-
|
|
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
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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(
|
|
209
|
+
log.warn(`Missing dependencies: ${pc.cyan([...missingProd, ...missingDev].join(", "))}`);
|
|
220
210
|
}
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
|
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}`);
|
|
228
|
+
for (const cmd of tasks) {
|
|
229
|
+
if (!json) s.message(`Running: ${cmd}`);
|
|
243
230
|
await new Promise((resolve, reject) => {
|
|
244
|
-
const child = exec(
|
|
245
|
-
child.stdout?.on("data", (
|
|
246
|
-
if (!json) process.stdout.write(pc.dim(
|
|
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", (
|
|
249
|
-
if (!json) process.stdout.write(pc.dim(pc.red(
|
|
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
|
|
240
|
+
(code) => code === 0 ? resolve() : reject(new Error(`Exit ${code}`))
|
|
254
241
|
);
|
|
255
242
|
});
|
|
256
243
|
}
|
|
257
|
-
if (!json)
|
|
258
|
-
|
|
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(
|
|
248
|
+
log.error(pc.dim(String(err)));
|
|
264
249
|
} else {
|
|
265
|
-
outputError(json, "
|
|
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
|
|
263
|
+
target: path.basename(adapterContext.core)
|
|
279
264
|
});
|
|
280
265
|
}
|
|
281
266
|
if (variantMeta && Array.isArray(variantMeta.files)) {
|
|
282
|
-
for (const
|
|
283
|
-
if (typeof
|
|
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(
|
|
271
|
+
filesToDownload.push(entry);
|
|
290
272
|
}
|
|
291
273
|
}
|
|
292
274
|
}
|
|
293
|
-
let
|
|
294
|
-
for (const
|
|
295
|
-
const destinationPath = join(targetFolder, fileMap.target);
|
|
275
|
+
let hasConflict = false;
|
|
276
|
+
for (const fm of filesToDownload) {
|
|
296
277
|
try {
|
|
297
|
-
await fs.access(
|
|
298
|
-
|
|
278
|
+
await fs.access(join(targetFolder, fm.target));
|
|
279
|
+
hasConflict = true;
|
|
299
280
|
break;
|
|
300
281
|
} catch {
|
|
301
282
|
}
|
|
302
283
|
}
|
|
303
|
-
if (
|
|
304
|
-
const
|
|
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(
|
|
309
|
-
if (!
|
|
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
|
-
|
|
320
|
-
}
|
|
295
|
+
if (!json) s.start(`Downloading ${targetBlock}...`);
|
|
296
|
+
const filesWritten = [];
|
|
321
297
|
try {
|
|
322
|
-
for (const
|
|
323
|
-
const fileUrl = `${RAW_CDN_BASE}/${
|
|
324
|
-
const
|
|
325
|
-
if (!
|
|
326
|
-
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
await fs.
|
|
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
|
|
309
|
+
if (!json) s.stop("Files written.");
|
|
332
310
|
outputResult(json, {
|
|
333
311
|
success: true,
|
|
334
312
|
block: targetBlock,
|
|
335
|
-
filesWritten
|
|
336
|
-
dependenciesInstalled: [...
|
|
337
|
-
message:
|
|
313
|
+
filesWritten,
|
|
314
|
+
dependenciesInstalled: [...missingProd, ...missingDev],
|
|
315
|
+
message: `${targetBlock} added to ${physicalBlocksPath}/${targetBlock}`
|
|
338
316
|
});
|
|
339
|
-
} catch (
|
|
317
|
+
} catch (err) {
|
|
340
318
|
if (!json) {
|
|
341
|
-
s.stop("Failed
|
|
342
|
-
log.error(pc.dim(String(
|
|
319
|
+
s.stop("Failed.");
|
|
320
|
+
log.error(pc.dim(String(err)));
|
|
343
321
|
} else {
|
|
344
|
-
outputError(json, "Fatal error
|
|
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
|
|
502
|
-
for (const configPath of
|
|
503
|
-
if (existsSync4(configPath))
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
}
|
|
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
|
|
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
|
|
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
|
-
|
|
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}`;
|
|
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.
|
|
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
|
|
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
|
|
667
|
+
outputInitError(true, "Failed to write configuration file.");
|
|
694
668
|
}
|
|
695
669
|
}
|
|
696
670
|
}
|
package/package.json
CHANGED