blockend-cli 1.0.2 → 1.2.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.
Files changed (2) hide show
  1. package/dist/index.js +90 -28
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -264,14 +264,21 @@ async function initCommand() {
264
264
  // src/commands/add.ts
265
265
  import path2, { join as join7 } from "path";
266
266
  import fs2 from "fs/promises";
267
- import { execSync } from "child_process";
268
- import { intro as intro2, outro as outro2, select as select2, spinner as spinner2, confirm as confirm2 } from "@clack/prompts";
267
+ import { exec } from "child_process";
268
+ import { intro as intro2, outro as outro2, select as select2, spinner as spinner2, confirm as confirm2, isCancel as isCancel2 } from "@clack/prompts";
269
269
  import pc2 from "picocolors";
270
- var REPO_OWNER = "codewitnuh";
270
+ var REPO_OWNER = "codewithnuh";
271
271
  var REPO_NAME = "blockend";
272
272
  var BRANCH = "master";
273
+ var LOCAL_DEV_URL = "http://localhost:5000";
273
274
  var RAW_CDN_BASE = `https://raw.githubusercontent.com/${REPO_OWNER}/${REPO_NAME}/${BRANCH}`;
274
275
  var MANIFEST_URL = `${RAW_CDN_BASE}/registry/index.json`;
276
+ function handleCancel(value) {
277
+ if (isCancel2(value)) {
278
+ outro2(pc2.yellow("\u26A0 Operation cancelled. Exiting Blockend CLI cleanly."));
279
+ process.exit(0);
280
+ }
281
+ }
275
282
  async function addCommand(blockName) {
276
283
  intro2(pc2.bgBlack(pc2.magenta(" Blockend Component Ingestion ")));
277
284
  const cwd = process.cwd();
@@ -300,16 +307,18 @@ async function addCommand(blockName) {
300
307
  if (!targetBlock) {
301
308
  const availableOptions = Object.keys(registry).map((key) => ({
302
309
  value: key,
303
- label: `${key} - pc.dim(${registry[key].description})`
310
+ label: `${key} - ${pc2.dim(registry[key].description)}`
304
311
  }));
305
312
  if (availableOptions.length === 0) {
306
313
  outro2(pc2.yellow("\u26A0 The remote block registry is currently empty."));
307
314
  return;
308
315
  }
309
- targetBlock = await select2({
316
+ const selectBlockPrompt = await select2({
310
317
  message: "Which backend block would you like to inject?",
311
318
  options: availableOptions
312
319
  });
320
+ handleCancel(selectBlockPrompt);
321
+ targetBlock = selectBlockPrompt;
313
322
  }
314
323
  const blockMeta = registry[targetBlock];
315
324
  if (!blockMeta) {
@@ -320,15 +329,34 @@ async function addCommand(blockName) {
320
329
  const envConfig = blockMeta.environments[envKey];
321
330
  if (!envConfig) {
322
331
  outro2(
323
- pc2.red(
324
- `\u2716 The block "${targetBlock}" does not support your environment layout: ${String(config.environment)}`
325
- )
332
+ pc2.red(`\u2716 The block "${targetBlock}" does not support your environment layout: ${envKey}`)
326
333
  );
327
334
  return;
328
335
  }
336
+ let selectedVariant;
337
+ let variantMeta = void 0;
338
+ if (envConfig.variants && Object.keys(envConfig.variants).length > 0) {
339
+ const variantKeys = Object.keys(envConfig.variants);
340
+ if (variantKeys.includes("redis") && config.redisEnabled) {
341
+ selectedVariant = "redis";
342
+ } else {
343
+ const selectVariantPrompt = await select2({
344
+ message: "Which architectural storage variant do you want to back this block?",
345
+ options: variantKeys.map((vKey) => ({
346
+ value: vKey,
347
+ label: vKey.toUpperCase()
348
+ }))
349
+ });
350
+ handleCancel(selectVariantPrompt);
351
+ selectedVariant = selectVariantPrompt;
352
+ }
353
+ variantMeta = envConfig.variants[selectedVariant];
354
+ }
329
355
  let packageJson;
330
356
  try {
331
- packageJson = JSON.parse(await fs2.readFile(join7(cwd, "package.json"), "utf-8"));
357
+ packageJson = JSON.parse(
358
+ await fs2.readFile(join7(cwd, "package.json"), "utf-8")
359
+ );
332
360
  } catch {
333
361
  outro2(pc2.red("\u2716 Could not locate package.json in your current workspace directory."));
334
362
  return;
@@ -337,23 +365,41 @@ async function addCommand(blockName) {
337
365
  ...packageJson.dependencies ?? {},
338
366
  ...packageJson.devDependencies ?? {}
339
367
  };
340
- const missingDeps = envConfig.dependencies.filter((dep) => !(dep in installedDeps));
368
+ const allRequiredDeps = [...envConfig.dependencies ?? [], ...variantMeta?.dependencies ?? []];
369
+ const missingDeps = allRequiredDeps.filter((dep) => !(dep in installedDeps));
341
370
  if (missingDeps.length > 0) {
342
371
  console.log(
343
372
  pc2.yellow(`
344
373
  \u26A0\uFE0F Missing required infrastructure packages: ${missingDeps.join(", ")}`)
345
374
  );
346
- const shouldInstall = await confirm2({
375
+ const shouldInstallPrompt = await confirm2({
347
376
  message: "Would you like the CLI to automatically install these dependencies?",
348
377
  initialValue: true
349
378
  });
350
- if (shouldInstall) {
351
- s.start(`Installing dependencies via your native package manager...`);
379
+ handleCancel(shouldInstallPrompt);
380
+ if (shouldInstallPrompt) {
381
+ const packageManager = await fs2.access(join7(cwd, "pnpm-lock.yaml")).then(() => "pnpm").catch(() => "npm");
382
+ s.start(`Preparing native workspace via ${packageManager}...`);
352
383
  try {
353
- const lockfileCheck = await fs2.access(join7(cwd, "pnpm-lock.yaml")).then(() => "pnpm").catch(() => "npm");
354
- const installCmd = lockfileCheck === "pnpm" ? `pnpm add ${missingDeps.join(" ")}` : `npm install ${missingDeps.join(" ")}`;
355
- execSync(installCmd, { stdio: "ignore", cwd });
356
- s.stop(pc2.green("\u2714 Dependencies installed cleanly."));
384
+ const installCmd = packageManager === "pnpm" ? `pnpm add ${missingDeps.join(" ")}` : `npm install ${missingDeps.join(" ")}`;
385
+ s.stop(pc2.cyan(`Executing: ${installCmd}
386
+ `));
387
+ await new Promise((resolve, reject) => {
388
+ const child = exec(installCmd, { cwd });
389
+ child.stdout?.on("data", (data) => {
390
+ process.stdout.write(pc2.dim(data));
391
+ });
392
+ child.stderr?.on("data", (data) => {
393
+ process.stdout.write(pc2.dim(pc2.red(data)));
394
+ });
395
+ child.on("close", (code) => {
396
+ if (code === 0) resolve();
397
+ else reject(new Error(`Exit code ${code}`));
398
+ });
399
+ });
400
+ console.log("");
401
+ s.start(pc2.green("\u2714 Dependencies installed cleanly. Continuing components build..."));
402
+ s.stop();
357
403
  } catch {
358
404
  s.stop(pc2.red("\u2716 Automated dependency installation failed. Please run setup manually."));
359
405
  }
@@ -361,26 +407,42 @@ async function addCommand(blockName) {
361
407
  }
362
408
  s.start(`Downloading clean production template block [${targetBlock}]...`);
363
409
  try {
364
- const targetFileUrl = `${RAW_CDN_BASE}/${envConfig.path}`;
365
- const codeFetchResponse = await fetch(targetFileUrl);
366
- if (!codeFetchResponse.ok) {
367
- throw new Error(`Failed downloading component source file: ${codeFetchResponse.statusText}`);
410
+ const BASE_URL = MANIFEST_URL.includes("localhost") ? LOCAL_DEV_URL : RAW_CDN_BASE;
411
+ const coreFileUrl = `${BASE_URL}/${envConfig.core}`;
412
+ const coreFetchResponse = await fetch(coreFileUrl);
413
+ if (!coreFetchResponse.ok) {
414
+ throw new Error(`Failed downloading core component file: ${coreFetchResponse.statusText}`);
368
415
  }
369
- const targetCodeTemplate = await codeFetchResponse.text();
416
+ const coreCodeTemplate = await coreFetchResponse.text();
370
417
  let physicalPath = config.paths.blocks;
371
418
  if (physicalPath.startsWith("@")) {
372
419
  physicalPath = "./src/blocks";
373
420
  }
374
- const targetFolder = path2.resolve(cwd, physicalPath);
421
+ let targetFolder = path2.resolve(cwd, physicalPath);
422
+ if (variantMeta && selectedVariant) {
423
+ targetFolder = join7(targetFolder, targetBlock);
424
+ }
375
425
  await fs2.mkdir(targetFolder, { recursive: true });
376
426
  const fileExtension = config.language === "typescript" ? "ts" : "js";
377
- const outputFilename = `${targetBlock}.${fileExtension}`;
378
- await fs2.writeFile(join7(targetFolder, outputFilename), targetCodeTemplate, "utf-8");
379
- const cleanDisplayPath = physicalPath.replace(/\\/g, "/");
380
- s.stop(pc2.green(`\u2714 ${outputFilename} successfully injected into codebase.`));
427
+ const coreOutputFilename = `${targetBlock}.${fileExtension}`;
428
+ await fs2.writeFile(join7(targetFolder, coreOutputFilename), coreCodeTemplate, "utf-8");
429
+ if (variantMeta && selectedVariant) {
430
+ const variantFileUrl = `${BASE_URL}/${variantMeta.path}`;
431
+ const variantFetchResponse = await fetch(variantFileUrl);
432
+ if (!variantFetchResponse.ok) {
433
+ throw new Error(
434
+ `Failed downloading storage variant file: ${variantFetchResponse.statusText}`
435
+ );
436
+ }
437
+ const variantCodeTemplate = await variantFetchResponse.text();
438
+ const variantOutputFilename = `store-${selectedVariant}.${fileExtension}`;
439
+ await fs2.writeFile(join7(targetFolder, variantOutputFilename), variantCodeTemplate, "utf-8");
440
+ }
441
+ const cleanDisplayPath = variantMeta && selectedVariant ? `${physicalPath.replace(/\\/g, "/")}/${targetBlock}` : physicalPath.replace(/\\/g, "/");
442
+ s.stop(pc2.green("\u2714 Component isolation structures written smoothly."));
381
443
  outro2(
382
444
  pc2.cyan(
383
- `\u2728 Source blocks written to ${cleanDisplayPath}/${outputFilename}. Code ownership transferred!`
445
+ `\u2728 Source blocks written to ${cleanDisplayPath}/ layout. Code ownership transferred!`
384
446
  )
385
447
  );
386
448
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blockend-cli",
3
- "version": "1.0.2",
3
+ "version": "1.2.0",
4
4
  "description": "Intelligent, modular backend blocks right inside your terminal",
5
5
  "main": "./dist/index.js",
6
6
  "type": "module",