code-brick 0.1.0 → 0.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 (3) hide show
  1. package/README.md +85 -0
  2. package/dist/index.js +226 -6
  3. package/package.json +6 -2
package/README.md CHANGED
@@ -312,6 +312,77 @@ import 'package:go_router/go_router.dart';
312
312
 
313
313
  The command auto-detects the project name from `pubspec.yaml`, `package.json`, or `pyproject.toml`.
314
314
 
315
+ ### `brick export <name|index>`
316
+
317
+ Export a template as a shareable `.brick` file. Perfect for sharing templates with teammates or between machines.
318
+
319
+ ```bash
320
+ # Export to current directory
321
+ brick export nestjs-auth
322
+
323
+ # By index
324
+ brick export 0
325
+
326
+ # Specify output path
327
+ brick export 0 --output ~/Desktop/my-auth.brick
328
+ ```
329
+
330
+ **Options:**
331
+
332
+ - `-o, --output <path>` — Output file path (default: `./<name>.brick`)
333
+
334
+ **Example output:**
335
+
336
+ ```
337
+ ┌ 🧱 Exporting template
338
+ │ ● Template: nestjs-auth
339
+ │ ● Files: 5 files
340
+ │ ● Output: /Users/you/nestjs-auth.brick
341
+
342
+ │ ◇ Export complete!
343
+ │ Size: 12.34 KB
344
+
345
+ │ Share this file and import with:
346
+ │ brick import nestjs-auth.brick
347
+
348
+ ```
349
+
350
+ ### `brick import <file>`
351
+
352
+ Import a template from a `.brick` file.
353
+
354
+ ```bash
355
+ # Import from .brick file
356
+ brick import nestjs-auth.brick
357
+
358
+ # Import with custom name
359
+ brick import nestjs-auth.brick --name my-auth-v2
360
+
361
+ # Force overwrite existing template
362
+ brick import nestjs-auth.brick --force
363
+ ```
364
+
365
+ **Options:**
366
+
367
+ - `-n, --name <name>` — Custom name for the imported template
368
+ - `-f, --force` — Overwrite existing template without prompting
369
+
370
+ **Example output:**
371
+
372
+ ```
373
+ ┌ 🧱 Importing template
374
+ │ ● File: /Users/you/nestjs-auth.brick
375
+ │ ● Template: nestjs-auth
376
+ │ ● Description: JWT authentication module
377
+ │ ● Files: 5 files
378
+
379
+ │ ◇ Import complete!
380
+
381
+ │ View structure: brick tree nestjs-auth
382
+ │ Apply template: brick apply nestjs-auth
383
+
384
+ ```
385
+
315
386
  ## Smart Ignore System
316
387
 
317
388
  Brick **automatically ignores** common dependency directories, build outputs, and generated files across all frameworks. This keeps your templates clean and portable.
@@ -449,6 +520,20 @@ brick clean 2 # Remove local imports
449
520
  brick delete 1 --force
450
521
  ```
451
522
 
523
+ ### Share Templates with Your Team
524
+
525
+ ```bash
526
+ # Export a template to share
527
+ brick export nestjs-auth
528
+ # Creates: nestjs-auth.brick (shareable file)
529
+
530
+ # Send the .brick file to your teammate, then they run:
531
+ brick import nestjs-auth.brick
532
+
533
+ # Or import with a custom name
534
+ brick import nestjs-auth.brick --name company-auth
535
+ ```
536
+
452
537
  ## Contributing
453
538
 
454
539
  Contributions are welcome! Please open an issue or submit a pull request.
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
- import pc13 from "picocolors";
5
+ import pc15 from "picocolors";
6
6
 
7
7
  // src/commands/init.ts
8
8
  import * as p from "@clack/prompts";
@@ -518,16 +518,16 @@ async function saveCommand(name, sourcePath, options) {
518
518
  }
519
519
  let files;
520
520
  if (options.include) {
521
- const patterns = options.include.split(",").map((p12) => p12.trim());
521
+ const patterns = options.include.split(",").map((p13) => p13.trim());
522
522
  files = await fg(patterns, {
523
523
  cwd: resolvedPath,
524
524
  dot: false,
525
- ignore: options.exclude?.split(",").map((p12) => p12.trim()) || []
525
+ ignore: options.exclude?.split(",").map((p13) => p13.trim()) || []
526
526
  });
527
527
  } else {
528
528
  files = await getFilesRecursive(resolvedPath);
529
529
  if (options.exclude) {
530
- const excludePatterns = options.exclude.split(",").map((p12) => p12.trim());
530
+ const excludePatterns = options.exclude.split(",").map((p13) => p13.trim());
531
531
  const excluded = await fg(excludePatterns, {
532
532
  cwd: resolvedPath,
533
533
  dot: false
@@ -1811,11 +1811,229 @@ async function cleanCommand(nameOrIndex, options) {
1811
1811
  p11.outro("Template cleaned successfully! \u2713");
1812
1812
  }
1813
1813
 
1814
+ // src/commands/export.ts
1815
+ import fs8 from "fs-extra";
1816
+ import path8 from "path";
1817
+ import archiver from "archiver";
1818
+ import pc13 from "picocolors";
1819
+ async function exportCommand(templateNameOrIndex, options) {
1820
+ if (!await isInitialized()) {
1821
+ console.log(pc13.red("CodeBrick is not initialized. Run: brick init"));
1822
+ process.exit(1);
1823
+ }
1824
+ const templateName = await resolveTemplateName(templateNameOrIndex);
1825
+ if (!templateName) {
1826
+ console.log(pc13.red(`Template '${templateNameOrIndex}' not found`));
1827
+ process.exit(1);
1828
+ }
1829
+ const template = await getTemplate(templateName);
1830
+ if (!template) {
1831
+ console.log(pc13.red(`Template '${templateName}' not found`));
1832
+ process.exit(1);
1833
+ }
1834
+ if (template.type !== "local") {
1835
+ console.log(
1836
+ pc13.red("Only local templates can be exported. Remote templates must be pulled first.")
1837
+ );
1838
+ process.exit(1);
1839
+ }
1840
+ const templatePath = getTemplatePath(templateName);
1841
+ if (!await fs8.pathExists(templatePath)) {
1842
+ console.log(pc13.red(`Template files not found at: ${templatePath}`));
1843
+ process.exit(1);
1844
+ }
1845
+ const metadata = await loadTemplateMetadata(templateName);
1846
+ if (!metadata) {
1847
+ console.log(pc13.red("Template metadata not found"));
1848
+ process.exit(1);
1849
+ }
1850
+ const outputFileName = `${templateName}.brick`;
1851
+ const outputPath = options.output ? path8.resolve(options.output) : path8.join(process.cwd(), outputFileName);
1852
+ await fs8.ensureDir(path8.dirname(outputPath));
1853
+ console.log(pc13.cyan("\u250C \u{1F9F1} Exporting template"));
1854
+ console.log(pc13.gray(`\u2502 \u25CF Template: ${pc13.white(templateName)}`));
1855
+ console.log(pc13.gray(`\u2502 \u25CF Files: ${pc13.white(metadata.files.length.toString())} files`));
1856
+ console.log(pc13.gray(`\u2502 \u25CF Output: ${pc13.white(outputPath)}`));
1857
+ try {
1858
+ await createBrickArchive(templatePath, outputPath, metadata);
1859
+ const stats = await fs8.stat(outputPath);
1860
+ const sizeKB = (stats.size / 1024).toFixed(2);
1861
+ console.log(pc13.gray("\u2502"));
1862
+ console.log(pc13.green(`\u2502 \u25C7 Export complete!`));
1863
+ console.log(pc13.gray(`\u2502 Size: ${pc13.white(sizeKB + " KB")}`));
1864
+ console.log(pc13.gray("\u2502"));
1865
+ console.log(pc13.gray(`\u2502 Share this file and import with:`));
1866
+ console.log(pc13.cyan(`\u2502 brick import ${outputFileName}`));
1867
+ console.log(pc13.cyan("\u2514"));
1868
+ console.log();
1869
+ console.log(pc13.green(`\u2713 Template '${templateName}' exported to ${outputPath}`));
1870
+ } catch (error) {
1871
+ console.log(pc13.red(`\u2502 \u2717 Export failed: ${error.message}`));
1872
+ console.log(pc13.cyan("\u2514"));
1873
+ process.exit(1);
1874
+ }
1875
+ }
1876
+ async function createBrickArchive(templatePath, outputPath, metadata) {
1877
+ return new Promise((resolve, reject) => {
1878
+ const output = fs8.createWriteStream(outputPath);
1879
+ const archive = archiver("tar", {
1880
+ gzip: true,
1881
+ gzipOptions: { level: 9 }
1882
+ });
1883
+ output.on("close", () => resolve());
1884
+ output.on("error", (err) => reject(err));
1885
+ archive.on("error", (err) => reject(err));
1886
+ archive.on("warning", (err) => {
1887
+ if (err.code !== "ENOENT") {
1888
+ reject(err);
1889
+ }
1890
+ });
1891
+ archive.pipe(output);
1892
+ archive.directory(templatePath, "template", (entry) => {
1893
+ if (entry.name === "brick.json") {
1894
+ return false;
1895
+ }
1896
+ return entry;
1897
+ });
1898
+ const metadataBuffer = Buffer.from(JSON.stringify(metadata, null, 2));
1899
+ archive.append(metadataBuffer, { name: "brick.json" });
1900
+ archive.finalize();
1901
+ });
1902
+ }
1903
+
1904
+ // src/commands/import.ts
1905
+ import fs9 from "fs-extra";
1906
+ import path9 from "path";
1907
+ import * as tar from "tar";
1908
+ import pc14 from "picocolors";
1909
+ import * as p12 from "@clack/prompts";
1910
+ async function importCommand(filePath, options) {
1911
+ if (!await isInitialized()) {
1912
+ console.log(pc14.red("CodeBrick is not initialized. Run: brick init"));
1913
+ process.exit(1);
1914
+ }
1915
+ const absolutePath = path9.resolve(filePath);
1916
+ if (!await fs9.pathExists(absolutePath)) {
1917
+ console.log(pc14.red(`File not found: ${absolutePath}`));
1918
+ process.exit(1);
1919
+ }
1920
+ if (!absolutePath.endsWith(".brick")) {
1921
+ console.log(pc14.yellow("\u26A0 Warning: File does not have .brick extension"));
1922
+ }
1923
+ console.log(pc14.cyan("\u250C \u{1F9F1} Importing template"));
1924
+ console.log(pc14.gray(`\u2502 \u25CF File: ${pc14.white(absolutePath)}`));
1925
+ try {
1926
+ const tempDir = path9.join(
1927
+ process.cwd(),
1928
+ `.brick-import-${Date.now()}`
1929
+ );
1930
+ await fs9.ensureDir(tempDir);
1931
+ try {
1932
+ await tar.extract({
1933
+ file: absolutePath,
1934
+ cwd: tempDir
1935
+ });
1936
+ const metadataPath = path9.join(tempDir, "brick.json");
1937
+ if (!await fs9.pathExists(metadataPath)) {
1938
+ throw new Error("Invalid .brick file: missing brick.json metadata");
1939
+ }
1940
+ const metadata = await fs9.readJson(metadataPath);
1941
+ let templateName = options.name || metadata.name;
1942
+ console.log(pc14.gray(`\u2502 \u25CF Template: ${pc14.white(templateName)}`));
1943
+ console.log(pc14.gray(`\u2502 \u25CF Description: ${pc14.white(metadata.description || "No description")}`));
1944
+ console.log(pc14.gray(`\u2502 \u25CF Files: ${pc14.white(metadata.files.length.toString())} files`));
1945
+ if (await templateExists(templateName)) {
1946
+ if (options.force) {
1947
+ console.log(pc14.yellow(`\u2502 \u26A0 Overwriting existing template '${templateName}'`));
1948
+ } else {
1949
+ console.log(pc14.gray("\u2502"));
1950
+ const shouldOverwrite = await p12.confirm({
1951
+ message: `Template '${templateName}' already exists. Overwrite?`
1952
+ });
1953
+ if (p12.isCancel(shouldOverwrite) || !shouldOverwrite) {
1954
+ const newName = await p12.text({
1955
+ message: "Enter a new name for the template:",
1956
+ placeholder: `${templateName}-imported`,
1957
+ validate: (value) => {
1958
+ if (!value) return "Name is required";
1959
+ if (!/^[a-z0-9-_]+$/i.test(value)) {
1960
+ return "Name can only contain letters, numbers, hyphens, and underscores";
1961
+ }
1962
+ }
1963
+ });
1964
+ if (p12.isCancel(newName)) {
1965
+ console.log(pc14.yellow("\u2502 Import cancelled"));
1966
+ console.log(pc14.cyan("\u2514"));
1967
+ await fs9.remove(tempDir);
1968
+ return;
1969
+ }
1970
+ templateName = newName;
1971
+ console.log(pc14.gray(`\u2502 \u25CF New name: ${pc14.white(templateName)}`));
1972
+ }
1973
+ }
1974
+ }
1975
+ const templateDir = getTemplatePath(templateName);
1976
+ const sourceTemplateDir = path9.join(tempDir, "template");
1977
+ if (await fs9.pathExists(templateDir)) {
1978
+ await fs9.remove(templateDir);
1979
+ }
1980
+ if (await fs9.pathExists(sourceTemplateDir)) {
1981
+ await fs9.copy(sourceTemplateDir, templateDir);
1982
+ } else {
1983
+ await fs9.ensureDir(templateDir);
1984
+ const entries = await fs9.readdir(tempDir);
1985
+ for (const entry of entries) {
1986
+ if (entry !== "brick.json") {
1987
+ await fs9.copy(
1988
+ path9.join(tempDir, entry),
1989
+ path9.join(templateDir, entry)
1990
+ );
1991
+ }
1992
+ }
1993
+ }
1994
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1995
+ const updatedMetadata = {
1996
+ ...metadata,
1997
+ name: templateName,
1998
+ updatedAt: now,
1999
+ source: {
2000
+ origin: "local",
2001
+ path: `imported from ${path9.basename(absolutePath)}`
2002
+ }
2003
+ };
2004
+ await saveTemplateMetadata(templateName, updatedMetadata);
2005
+ const storeEntry = {
2006
+ type: "local",
2007
+ path: templateDir,
2008
+ description: metadata.description || "",
2009
+ tags: metadata.tags || [],
2010
+ createdAt: metadata.createdAt || now,
2011
+ updatedAt: now
2012
+ };
2013
+ await addTemplateToStore(templateName, storeEntry);
2014
+ console.log(pc14.gray("\u2502"));
2015
+ console.log(pc14.green(`\u2502 \u25C7 Import complete!`));
2016
+ console.log(pc14.gray("\u2502"));
2017
+ console.log(pc14.gray(`\u2502 View structure: ${pc14.cyan(`brick tree ${templateName}`)}`));
2018
+ console.log(pc14.gray(`\u2502 Apply template: ${pc14.cyan(`brick apply ${templateName}`)}`));
2019
+ console.log(pc14.cyan("\u2514"));
2020
+ console.log();
2021
+ console.log(pc14.green(`\u2713 Template '${templateName}' imported successfully!`));
2022
+ } finally {
2023
+ await fs9.remove(tempDir);
2024
+ }
2025
+ } catch (error) {
2026
+ console.log(pc14.red(`\u2502 \u2717 Import failed: ${error.message}`));
2027
+ console.log(pc14.cyan("\u2514"));
2028
+ process.exit(1);
2029
+ }
2030
+ }
2031
+
1814
2032
  // src/index.ts
1815
2033
  var program = new Command();
1816
2034
  program.name("brick").description(
1817
- pc13.cyan("\u{1F9F1} CodeBrick") + " - A framework-agnostic CLI for managing reusable code templates"
1818
- ).version("0.1.0");
2035
+ pc15.cyan("\u{1F9F1} CodeBrick") + " - A framework-agnostic CLI for managing reusable code templates"
2036
+ ).version("0.2.0");
1819
2037
  program.command("init").description("Initialize CodeBrick in the system").action(initCommand);
1820
2038
  program.command("save <name> [path]").description("Create a new LOCAL template from files").option("-d, --description <desc>", "Template description").option("-t, --tags <tags>", "Comma-separated tags").option("--include <patterns>", "Glob patterns to include").option("--exclude <patterns>", "Glob patterns to exclude").option("--detect-deps", "Auto-detect dependencies from imports").action(saveCommand);
1821
2039
  program.command("list").alias("ls").description("List all saved templates").option("--local", "Show only local templates").option("--remote", "Show only remote templates").option("--tag <tag>", "Filter by tag").option("--detailed", "Show detailed view").action(listCommand);
@@ -1827,4 +2045,6 @@ program.command("remove-file <name> <files...>").description("Remove files from
1827
2045
  program.command("delete <name>").alias("rm").description("Delete a template entirely").option("-f, --force", "Skip confirmation").action(deleteCommand);
1828
2046
  program.command("size [name]").description("Show template size(s) - all templates if no name provided").action(sizeCommand);
1829
2047
  program.command("clean <name>").description("Remove local/project-specific imports from template files").option("-p, --pattern <regex>", "Custom regex pattern to match imports to remove").option("--dry-run", "Preview changes without modifying files").option("--no-keep-external", "Also remove external package imports").action(cleanCommand);
2048
+ program.command("export <name>").description("Export a template as a shareable .brick file").option("-o, --output <path>", "Output file path (default: ./<name>.brick)").action(exportCommand);
2049
+ program.command("import <file>").description("Import a template from a .brick file").option("-n, --name <name>", "Custom name for the imported template").option("-f, --force", "Overwrite existing template without prompting").action(importCommand);
1830
2050
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-brick",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "A framework-agnostic CLI tool for managing reusable code templates. Save, manage, and apply code snippets across projects.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -39,14 +39,18 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "@clack/prompts": "^0.7.0",
42
+ "archiver": "^7.0.1",
42
43
  "commander": "^12.0.0",
44
+ "fast-glob": "^3.3.2",
43
45
  "fs-extra": "^11.2.0",
44
46
  "picocolors": "^1.0.0",
45
- "fast-glob": "^3.3.2"
47
+ "tar": "^7.5.2"
46
48
  },
47
49
  "devDependencies": {
50
+ "@types/archiver": "^7.0.0",
48
51
  "@types/fs-extra": "^11.0.4",
49
52
  "@types/node": "^20.11.0",
53
+ "@types/tar": "^6.1.13",
50
54
  "tsup": "^8.0.1",
51
55
  "typescript": "^5.3.3"
52
56
  },