blockend-cli 1.2.0 → 1.3.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 CHANGED
@@ -142,7 +142,7 @@ async function initCommand() {
142
142
  const cwd = process.cwd();
143
143
  const configPath = join6(cwd, "blockend.json");
144
144
  console.log(`
145
- \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
145
+ \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
146
146
  \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
147
147
  \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
148
148
  \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
@@ -197,18 +197,6 @@ async function initCommand() {
197
197
  outro(format.muted("Initialization cancelled."));
198
198
  return;
199
199
  }
200
- const language = await select({
201
- message: "Confirm primary language",
202
- initialValue: context.language === "typescript" ? "typescript" : "javascript",
203
- options: [
204
- { value: "typescript", label: "TypeScript" },
205
- { value: "javascript", label: "JavaScript" }
206
- ]
207
- });
208
- if (isCancel(language)) {
209
- outro(format.muted("Initialization cancelled."));
210
- return;
211
- }
212
200
  const availableAliases = Object.keys(context.aliasMap || {});
213
201
  const baseAliasToken = availableAliases.length > 0 ? availableAliases[0] : "@/";
214
202
  const blockAliasInput = await text({
@@ -241,7 +229,7 @@ async function initCommand() {
241
229
  const configPayload = {
242
230
  $schema: "https://blockend.dev/schema.json",
243
231
  environment: framework,
244
- language,
232
+ language: "typescript",
245
233
  includeRedis,
246
234
  aliases: {
247
235
  blocks: blockAlias
@@ -262,7 +250,7 @@ async function initCommand() {
262
250
  }
263
251
 
264
252
  // src/commands/add.ts
265
- import path2, { join as join7 } from "path";
253
+ import path2, { join as join7, dirname } from "path";
266
254
  import fs2 from "fs/promises";
267
255
  import { exec } from "child_process";
268
256
  import { intro as intro2, outro as outro2, select as select2, spinner as spinner2, confirm as confirm2, isCancel as isCancel2 } from "@clack/prompts";
@@ -270,7 +258,6 @@ import pc2 from "picocolors";
270
258
  var REPO_OWNER = "codewithnuh";
271
259
  var REPO_NAME = "blockend";
272
260
  var BRANCH = "master";
273
- var LOCAL_DEV_URL = "http://localhost:5000";
274
261
  var RAW_CDN_BASE = `https://raw.githubusercontent.com/${REPO_OWNER}/${REPO_NAME}/${BRANCH}`;
275
262
  var MANIFEST_URL = `${RAW_CDN_BASE}/registry/index.json`;
276
263
  function handleCancel(value) {
@@ -279,16 +266,47 @@ function handleCancel(value) {
279
266
  process.exit(0);
280
267
  }
281
268
  }
269
+ async function findUp(filename, startDir) {
270
+ let dir = startDir;
271
+ while (true) {
272
+ const checkPath = join7(dir, filename);
273
+ try {
274
+ await fs2.access(checkPath);
275
+ return checkPath;
276
+ } catch {
277
+ const parent = dirname(dir);
278
+ if (parent === dir) break;
279
+ dir = parent;
280
+ }
281
+ }
282
+ return null;
283
+ }
282
284
  async function addCommand(blockName) {
283
285
  intro2(pc2.bgBlack(pc2.magenta(" Blockend Component Ingestion ")));
284
286
  const cwd = process.cwd();
285
- const configPath = join7(cwd, "blockend.json");
287
+ const configPath = await findUp("blockend.json", cwd);
288
+ if (!configPath) {
289
+ outro2(pc2.red("\u2716 blockend.json not found. Run 'npx blockend init' first."));
290
+ return;
291
+ }
292
+ const rootDir = dirname(configPath);
286
293
  let config;
287
294
  try {
288
295
  const configFile = await fs2.readFile(configPath, "utf-8");
289
296
  config = JSON.parse(configFile);
290
297
  } catch {
291
- outro2(pc2.red("\u2716 blockend.json not found. Run 'npx blockend init' first."));
298
+ outro2(pc2.red("\u2716 Failed to parse blockend.json layout configuration."));
299
+ return;
300
+ }
301
+ if (config.language !== "typescript") {
302
+ outro2(
303
+ pc2.yellow(
304
+ `
305
+ \u2139 Blockend forces modern architectural standards.
306
+ To ensure strict type safety and absolute code ownership, the registry exclusively supports TypeScript.
307
+ Please migrate your project configuration to TypeScript to ingest blocks.`
308
+ )
309
+ );
292
310
  return;
293
311
  }
294
312
  const s = spinner2();
@@ -303,14 +321,19 @@ async function addCommand(blockName) {
303
321
  s.stop(pc2.red("\u2716 Failed to fetch the component registry from GitHub network paths."));
304
322
  return;
305
323
  }
324
+ const envKey = String(config.environment);
306
325
  let targetBlock = blockName;
307
326
  if (!targetBlock) {
308
- const availableOptions = Object.keys(registry).map((key) => ({
327
+ const availableOptions = Object.keys(registry).filter((key) => registry[key].environments[envKey] !== void 0).map((key) => ({
309
328
  value: key,
310
329
  label: `${key} - ${pc2.dim(registry[key].description)}`
311
330
  }));
312
331
  if (availableOptions.length === 0) {
313
- outro2(pc2.yellow("\u26A0 The remote block registry is currently empty."));
332
+ outro2(
333
+ pc2.yellow(
334
+ `\u26A0 No backend blocks are currently available for your framework layer: [${envKey}].`
335
+ )
336
+ );
314
337
  return;
315
338
  }
316
339
  const selectBlockPrompt = await select2({
@@ -325,7 +348,6 @@ async function addCommand(blockName) {
325
348
  outro2(pc2.red(`\u2716 Block "${targetBlock}" does not exist in the remote registry.`));
326
349
  return;
327
350
  }
328
- const envKey = String(config.environment);
329
351
  const envConfig = blockMeta.environments[envKey];
330
352
  if (!envConfig) {
331
353
  outro2(
@@ -352,13 +374,17 @@ async function addCommand(blockName) {
352
374
  }
353
375
  variantMeta = envConfig.variants[selectedVariant];
354
376
  }
377
+ const packageJsonPath = await findUp("package.json", rootDir);
378
+ if (!packageJsonPath) {
379
+ outro2(pc2.red("\u2716 Could not locate package.json in your current directory layout hierarchy."));
380
+ return;
381
+ }
382
+ const packageJsonDir = dirname(packageJsonPath);
355
383
  let packageJson;
356
384
  try {
357
- packageJson = JSON.parse(
358
- await fs2.readFile(join7(cwd, "package.json"), "utf-8")
359
- );
385
+ packageJson = JSON.parse(await fs2.readFile(packageJsonPath, "utf-8"));
360
386
  } catch {
361
- outro2(pc2.red("\u2716 Could not locate package.json in your current workspace directory."));
387
+ outro2(pc2.red("\u2716 Failed parsing file data configurations from target package.json location."));
362
388
  return;
363
389
  }
364
390
  const installedDeps = {
@@ -378,14 +404,14 @@ async function addCommand(blockName) {
378
404
  });
379
405
  handleCancel(shouldInstallPrompt);
380
406
  if (shouldInstallPrompt) {
381
- const packageManager = await fs2.access(join7(cwd, "pnpm-lock.yaml")).then(() => "pnpm").catch(() => "npm");
407
+ const packageManager = await fs2.access(join7(packageJsonDir, "pnpm-lock.yaml")).then(() => "pnpm").catch(() => "npm");
382
408
  s.start(`Preparing native workspace via ${packageManager}...`);
383
409
  try {
384
410
  const installCmd = packageManager === "pnpm" ? `pnpm add ${missingDeps.join(" ")}` : `npm install ${missingDeps.join(" ")}`;
385
411
  s.stop(pc2.cyan(`Executing: ${installCmd}
386
412
  `));
387
413
  await new Promise((resolve, reject) => {
388
- const child = exec(installCmd, { cwd });
414
+ const child = exec(installCmd, { cwd: packageJsonDir });
389
415
  child.stdout?.on("data", (data) => {
390
416
  process.stdout.write(pc2.dim(data));
391
417
  });
@@ -407,7 +433,7 @@ async function addCommand(blockName) {
407
433
  }
408
434
  s.start(`Downloading clean production template block [${targetBlock}]...`);
409
435
  try {
410
- const BASE_URL = MANIFEST_URL.includes("localhost") ? LOCAL_DEV_URL : RAW_CDN_BASE;
436
+ const BASE_URL = RAW_CDN_BASE;
411
437
  const coreFileUrl = `${BASE_URL}/${envConfig.core}`;
412
438
  const coreFetchResponse = await fetch(coreFileUrl);
413
439
  if (!coreFetchResponse.ok) {
@@ -418,14 +444,29 @@ async function addCommand(blockName) {
418
444
  if (physicalPath.startsWith("@")) {
419
445
  physicalPath = "./src/blocks";
420
446
  }
421
- let targetFolder = path2.resolve(cwd, physicalPath);
447
+ let targetFolder = path2.resolve(rootDir, physicalPath);
422
448
  if (variantMeta && selectedVariant) {
423
449
  targetFolder = join7(targetFolder, targetBlock);
424
450
  }
425
451
  await fs2.mkdir(targetFolder, { recursive: true });
426
- const fileExtension = config.language === "typescript" ? "ts" : "js";
427
- const coreOutputFilename = `${targetBlock}.${fileExtension}`;
428
- await fs2.writeFile(join7(targetFolder, coreOutputFilename), coreCodeTemplate, "utf-8");
452
+ const coreOutputFilename = `${targetBlock}.ts`;
453
+ const coreTargetFileLocation = join7(targetFolder, coreOutputFilename);
454
+ try {
455
+ await fs2.access(coreTargetFileLocation);
456
+ s.stop();
457
+ const overwritePrompt = await confirm2({
458
+ message: `\u26A0 Block component file [${coreOutputFilename}] already exists. Overwrite custom code revisions?`,
459
+ initialValue: false
460
+ });
461
+ handleCancel(overwritePrompt);
462
+ if (!overwritePrompt) {
463
+ outro2(pc2.yellow("\u2139 Operation aborted safely. Local code modifications preserved."));
464
+ return;
465
+ }
466
+ s.start(`Re-downloading template block [${targetBlock}]...`);
467
+ } catch {
468
+ }
469
+ await fs2.writeFile(coreTargetFileLocation, coreCodeTemplate, "utf-8");
429
470
  if (variantMeta && selectedVariant) {
430
471
  const variantFileUrl = `${BASE_URL}/${variantMeta.path}`;
431
472
  const variantFetchResponse = await fetch(variantFileUrl);
@@ -435,7 +476,7 @@ async function addCommand(blockName) {
435
476
  );
436
477
  }
437
478
  const variantCodeTemplate = await variantFetchResponse.text();
438
- const variantOutputFilename = `store-${selectedVariant}.${fileExtension}`;
479
+ const variantOutputFilename = `store-${selectedVariant}.ts`;
439
480
  await fs2.writeFile(join7(targetFolder, variantOutputFilename), variantCodeTemplate, "utf-8");
440
481
  }
441
482
  const cleanDisplayPath = variantMeta && selectedVariant ? `${physicalPath.replace(/\\/g, "/")}/${targetBlock}` : physicalPath.replace(/\\/g, "/");
@@ -445,9 +486,11 @@ async function addCommand(blockName) {
445
486
  `\u2728 Source blocks written to ${cleanDisplayPath}/ layout. Code ownership transferred!`
446
487
  )
447
488
  );
489
+ process.exit(0);
448
490
  } catch (error) {
449
491
  s.stop(pc2.red("\u2716 Fatal crash occurred while downloading or transferring file layouts."));
450
492
  console.error(error);
493
+ process.exit(1);
451
494
  }
452
495
  }
453
496
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blockend-cli",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Intelligent, modular backend blocks right inside your terminal",
5
5
  "main": "./dist/index.js",
6
6
  "type": "module",
@@ -12,7 +12,8 @@
12
12
  "readme.md"
13
13
  ],
14
14
  "scripts": {
15
- "build": "tsup src/index.ts --format esm --dts --out-dir dist"
15
+ "build": "tsup src/index.ts --format esm --dts --out-dir dist",
16
+ "typecheck": "tsc --noEmit"
16
17
  },
17
18
  "keywords": [
18
19
  "cli",
package/dist/index.mjs DELETED
@@ -1,49 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/index.ts
4
- import { Command } from "commander";
5
- import { intro, outro, select, text } from "@clack/prompts";
6
- import pc from "picocolors";
7
- var program = new Command();
8
- program.name("blockend").description("Zero-dependency production backend blocks CLI").version("0.1.0");
9
- program.command("init").description("Initialize blockend configuration in your project").action(async () => {
10
- intro(pc.bgBlack(pc.cyan(" Blockend Initialization ")));
11
- const environment = await select({
12
- message: "Select your backend framework environment:",
13
- options: [
14
- { value: "express", label: "Express.js" },
15
- { value: "fastify", label: "Fastify" },
16
- { value: "nextjs", label: "Next.js (App Router)" }
17
- ]
18
- });
19
- const blockPath = await text({
20
- message: "Where should we save your backend blocks?",
21
- initialValue: "./src/blocks",
22
- placeholder: "./src/blocks"
23
- });
24
- console.log("\nWriting configuration target data...");
25
- console.log(
26
- pc.green(`\u2714 Targets set: Environment -> ${String(environment)}, Path -> ${String(blockPath)}`)
27
- );
28
- outro(pc.cyan("blockend.json configured mock-successfully."));
29
- });
30
- program.command("add [block]").description("Add a backend block to your project").action(async (block) => {
31
- intro(pc.bgBlack(pc.magenta(" Blockend Add Tool ")));
32
- if (!block) {
33
- const selectedBlock = await select({
34
- message: "Which backend block would you like to add?",
35
- options: [
36
- { value: "rate-limiter", label: "rate-limiter (Redis/Memory)" },
37
- { value: "auth-handler", label: "auth-handler (JWT/Session)" },
38
- { value: "error-handler", label: "global-error-handler" }
39
- ]
40
- });
41
- block = selectedBlock;
42
- }
43
- console.log(pc.yellow(`
44
- [Mock Run]: Fetching metadata manifest for "${block}"...`));
45
- console.log(pc.green(`\u2714 [Mock Run]: Injected missing dependencies into your package context.`));
46
- console.log(pc.green(`\u2714 [Mock Run]: Written file cleanly into your target blocks path.`));
47
- outro(pc.magenta(`Successfully added ${block} block!`));
48
- });
49
- program.parse(process.argv);
@@ -1,13 +0,0 @@
1
- export interface SessionUser {
2
- id: string;
3
- email: string;
4
- roles: string[];
5
- }
6
-
7
- export function requireRole(user: SessionUser | null, role: string): SessionUser {
8
- if (!user || !user.roles.includes(role)) {
9
- throw new Error("Unauthorized");
10
- }
11
-
12
- return user;
13
- }
@@ -1,12 +0,0 @@
1
- {
2
- "name": "auth",
3
- "version": "0.1.0",
4
- "dependencies": [],
5
- "prompts": [],
6
- "files": [
7
- {
8
- "template": "index.hbs",
9
- "output": "src/lib/blocks/{{name}}.ts"
10
- }
11
- ]
12
- }
@@ -1,38 +0,0 @@
1
- export interface RateLimitOptions {
2
- limit: number;
3
- windowMs: number;
4
- }
5
-
6
- export interface RateLimitResult {
7
- allowed: boolean;
8
- remaining: number;
9
- resetAt: number;
10
- }
11
-
12
- const buckets = new Map<string, { count: number; resetAt: number }>();
13
-
14
- export function createRateLimiter(options: RateLimitOptions) {
15
- return function checkRateLimit(key: string): RateLimitResult {
16
- const now = Date.now();
17
- const current = buckets.get(key);
18
-
19
- if (!current || current.resetAt <= now) {
20
- const resetAt = now + options.windowMs;
21
- buckets.set(key, { count: 1, resetAt });
22
- return { allowed: true, remaining: options.limit - 1, resetAt };
23
- }
24
-
25
- if (current.count >= options.limit) {
26
- return { allowed: false, remaining: 0, resetAt: current.resetAt };
27
- }
28
-
29
- current.count += 1;
30
- return {
31
- allowed: true,
32
- remaining: Math.max(options.limit - current.count, 0),
33
- resetAt: current.resetAt
34
- };
35
- };
36
- }
37
-
38
- export const rateLimitDriver = "{{driver}}";
@@ -1,22 +0,0 @@
1
- {
2
- "name": "rate-limiter",
3
- "version": "0.1.0",
4
- "dependencies": [],
5
- "prompts": [
6
- {
7
- "id": "driver",
8
- "type": "select",
9
- "message": "Select your storage backend:",
10
- "options": [
11
- { "value": "in-memory", "label": "In-Memory (Zero dependencies)" },
12
- { "value": "redis", "label": "Redis (Scalable cluster)" }
13
- ]
14
- }
15
- ],
16
- "files": [
17
- {
18
- "template": "index.hbs",
19
- "output": "src/lib/blocks/{{name}}.ts"
20
- }
21
- ]
22
- }