johankit 0.0.2 → 0.0.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.
Files changed (50) hide show
  1. package/README.md +0 -5
  2. package/Readme.md +56 -0
  3. package/dist/cli/commands/copy.js +3 -4
  4. package/dist/cli/commands/paste.js +19 -10
  5. package/dist/cli/commands/prompt.js +40 -8
  6. package/dist/cli/commands/sync.js +10 -6
  7. package/dist/cli/commands/three.js +106 -0
  8. package/dist/index.js +52 -34
  9. package/dist/services/JohankitService.js +59 -0
  10. package/dist/utils/cleanCodeBlock.js +12 -0
  11. package/dist/utils/createAsciiTree.js +46 -0
  12. package/johankit.yaml +2 -0
  13. package/package.json +5 -2
  14. package/src/cli/commands/copy.ts +3 -4
  15. package/src/cli/commands/paste.ts +16 -16
  16. package/src/cli/commands/prompt.ts +47 -9
  17. package/src/cli/commands/sync.ts +8 -6
  18. package/src/cli/commands/three.ts +117 -0
  19. package/src/core/scan.ts +1 -1
  20. package/src/index.ts +54 -37
  21. package/src/services/JohankitService.ts +70 -0
  22. package/src/types.ts +45 -0
  23. package/src/utils/cleanCodeBlock.ts +13 -0
  24. package/src/utils/createAsciiTree.ts +53 -0
  25. package/tsconfig.json +4 -0
  26. package/dist/tests/cli/commands/copy.test.js +0 -47
  27. package/dist/tests/cli/commands/paste.test.js +0 -41
  28. package/dist/tests/cli/commands/prompt.test.js +0 -37
  29. package/dist/tests/cli/commands/sync.test.js +0 -47
  30. package/dist/tests/core/clipboard.test.js +0 -20
  31. package/dist/tests/core/config.test.js +0 -23
  32. package/dist/tests/core/diff.test.js +0 -24
  33. package/dist/tests/core/git.test.js +0 -11
  34. package/dist/tests/core/scan.test.js +0 -16
  35. package/dist/tests/core/schema.test.js +0 -13
  36. package/dist/tests/core/validation.test.js +0 -13
  37. package/dist/tests/core/write.test.js +0 -41
  38. package/package-lock.json +0 -250
  39. package/src/tests/cli/commands/copy.test.ts +0 -26
  40. package/src/tests/cli/commands/paste.test.ts +0 -19
  41. package/src/tests/cli/commands/prompt.test.ts +0 -14
  42. package/src/tests/cli/commands/sync.test.ts +0 -26
  43. package/src/tests/core/clipboard.test.ts +0 -21
  44. package/src/tests/core/config.test.ts +0 -21
  45. package/src/tests/core/diff.test.ts +0 -22
  46. package/src/tests/core/git.test.ts +0 -11
  47. package/src/tests/core/scan.test.ts +0 -13
  48. package/src/tests/core/schema.test.ts +0 -13
  49. package/src/tests/core/validation.test.ts +0 -13
  50. package/src/tests/core/write.test.ts +0 -15
@@ -1,6 +1,9 @@
1
1
  // src/cli/commands/paste.ts
2
2
  import { writeFiles } from "../../core/write";
3
3
  import { readClipboard } from "../../core/clipboard";
4
+ import cleanCodeBlock from "../../utils/cleanCodeBlock";
5
+ import { applyDiff } from "../../core/diff";
6
+ import { validatePatches } from "../../core/validation";
4
7
 
5
8
  export async function paste(dir: string) {
6
9
  try {
@@ -10,28 +13,25 @@ export async function paste(dir: string) {
10
13
  throw new Error("Clipboard empty or inaccessible");
11
14
  }
12
15
 
13
- let files;
16
+ let parsed;
14
17
  try {
15
- const cleanContent = content.replace(/^\uFEFF/, "").trim();
16
- files = JSON.parse(cleanContent);
18
+ const { cleaned } = cleanCodeBlock(content);
19
+ parsed = JSON.parse(cleaned);
17
20
  } catch (e) {
18
21
  throw new Error("Clipboard content is not valid JSON");
19
22
  }
20
23
 
21
- if (!Array.isArray(files)) {
22
- throw new Error("Clipboard content is not a JSON array");
24
+ if (Array.isArray(parsed) && parsed.every(f => f.path && f.content)) {
25
+ // Caso seja snapshot
26
+ writeFiles(dir, parsed, true);
27
+ } else if (Array.isArray(parsed) && parsed.every(f => f.type && f.path)) {
28
+ // Caso seja DiffPatch
29
+ const validated = validatePatches(parsed);
30
+ applyDiff(dir, validated);
31
+ } else {
32
+ throw new Error("JSON is neither FileSnapshot array nor DiffPatch array");
23
33
  }
24
34
 
25
- // Validação simples do snapshot
26
- const isValidSnapshot = files.every(f =>
27
- typeof f.path === 'string' && typeof f.content === 'string'
28
- );
29
-
30
- if (!isValidSnapshot) {
31
- throw new Error("JSON does not match FileSnapshot structure {path, content}");
32
- }
33
-
34
- writeFiles(dir, files, true);
35
35
  process.stdout.write("✔ Pasted from clipboard\n");
36
36
  } catch (error) {
37
37
  process.stderr.write("✖ Paste failed\n");
@@ -40,4 +40,4 @@ export async function paste(dir: string) {
40
40
  }
41
41
  process.exit(1);
42
42
  }
43
- }
43
+ }
@@ -2,15 +2,50 @@
2
2
  import { scanDir } from "../../core/scan";
3
3
  import { copyToClipboard } from "../../core/clipboard";
4
4
 
5
- export async function prompt(dir: string, userPrompt: string) {
5
+ interface LLMResponseExample {
6
+ type: 'FileSnapshot' | 'DiffPatch';
7
+ example: any;
8
+ }
9
+
10
+ export async function prompt(dir: string, userPrompt: string, diff = false) {
6
11
  const snapshot = scanDir(dir);
7
12
 
13
+ const llmExamples: LLMResponseExample[] = [
14
+ {
15
+ type: 'FileSnapshot',
16
+ example: [
17
+ {
18
+ path: 'src/example.ts',
19
+ content: 'export const x = 42;'
20
+ }
21
+ ]
22
+ },
23
+ {
24
+ type: 'DiffPatch',
25
+ example: [
26
+ {
27
+ type: 'modify',
28
+ path: 'src/example.ts',
29
+ content: 'export const x = 43;'
30
+ },
31
+ {
32
+ type: 'create',
33
+ path: 'src/newFile.ts',
34
+ content: 'export const newFile = true;'
35
+ },
36
+ {
37
+ type: 'delete',
38
+ path: 'src/oldFile.ts'
39
+ }
40
+ ]
41
+ }
42
+ ];
43
+
8
44
  const template = `
9
45
  You are an AI software engineer.
10
46
 
11
47
  You will receive a JSON array representing a snapshot of a codebase.
12
48
  Each item has the following structure:
13
-
14
49
  {
15
50
  "path": "relative/path/to/file.ext",
16
51
  "content": "full file content"
@@ -26,19 +61,22 @@ ${JSON.stringify(snapshot, null, 2)}
26
61
  YOUR TASK
27
62
  Propose changes according to the user request.
28
63
 
29
- Return ONLY a JSON array of patches.
64
+ Return ONLY a JSON array of ${diff ? 'DiffPatch' : 'FileSnapshot'}.
30
65
 
31
66
  PATCH FORMAT (STRICT)
32
67
  {
33
- "path": "relative/path/to/file.ext",
34
- "content": "FULL updated file content (omit for delete)"
68
+ \"path\": \"relative/path/to/file.ext\",
69
+ \"content\": \"FULL updated file content (omit for delete)\"
35
70
  }
36
71
 
72
+ EXAMPLE RESPONSE FROM LLM:
73
+ ${JSON.stringify(diff ? llmExamples.find(e => e.type === 'DiffPatch')?.example : llmExamples.find(e => e.type === 'FileSnapshot')?.example, null, 2)}
74
+
37
75
  IMPORTANT RULES
38
76
  - Do NOT return explanations
39
77
  - Do NOT return markdown
40
- - Return ONLY valid JSON inside the "\`\`\`"
41
- - Always return within a Markdown Code Block (with "\`\`\`json" syntax highlighting)")
78
+ - Return ONLY valid JSON inside the \"\`\`\`\"
79
+ - Always return within a Markdown Code Block.
42
80
 
43
81
  USER REQUEST
44
82
  ${userPrompt}
@@ -47,9 +85,9 @@ ${userPrompt}
47
85
  try {
48
86
  await copyToClipboard(template.trim());
49
87
  process.stdout.write(template.trim());
50
- process.stdout.write("\n\n✔ Prompt + Snapshot copied to clipboard\n");
88
+ process.stdout.write("\n\n✔ Prompt + Snapshot + Example copied to clipboard\n");
51
89
  } catch (e) {
52
90
  process.stdout.write(template.trim());
53
91
  process.stderr.write("\n✖ Failed to copy to clipboard (output only)\n");
54
92
  }
55
- }
93
+ }
@@ -5,11 +5,10 @@ import { copyToClipboard } from "../../core/clipboard";
5
5
  import { validatePatches } from "../../core/validation";
6
6
  import { writeFiles } from "../../core/write";
7
7
 
8
- export async function sync(dir: string) {
8
+ export async function sync(dir: string, diff = false) {
9
9
  try {
10
10
  const snapshotBefore = scanDir(dir);
11
11
 
12
- // Gera prompt genérico para AI com snapshot atual
13
12
  const template = `
14
13
  You are an AI software engineer.
15
14
 
@@ -33,7 +32,7 @@ ${JSON.stringify(snapshotBefore, null, 2)}
33
32
  YOUR TASK
34
33
  Propose changes according to the user request.
35
34
 
36
- Return ONLY a JSON array of patches.
35
+ Return ONLY a JSON array of ${diff ? 'DiffPatch' : 'FileSnapshot'}.
37
36
 
38
37
  PATCH FORMAT (STRICT)
39
38
  {
@@ -53,7 +52,6 @@ USER REQUEST
53
52
  await copyToClipboard(template.trim());
54
53
  process.stdout.write("✔ Prompt with snapshot copied to clipboard\n");
55
54
 
56
- // Aguarda entrada do usuário (resposta da AI) via stdin
57
55
  const input = await readStdin();
58
56
 
59
57
  let patches;
@@ -63,8 +61,12 @@ USER REQUEST
63
61
  throw new Error("Invalid JSON input");
64
62
  }
65
63
 
66
- const validated = validatePatches(patches);
67
- applyDiff(dir, validated);
64
+ if (diff) {
65
+ const validated = validatePatches(patches);
66
+ applyDiff(dir, validated);
67
+ } else {
68
+ writeFiles(dir, patches as any, true);
69
+ }
68
70
 
69
71
  const snapshotAfter = scanDir(dir);
70
72
  await copyToClipboard(JSON.stringify(snapshotAfter, null, 2));
@@ -0,0 +1,117 @@
1
+ import path from "path";
2
+ import { Project, SourceFile, SyntaxKind } from "ts-morph";
3
+ import {
4
+ FileTree,
5
+ ClassInfo,
6
+ FunctionInfo,
7
+ VariableInfo,
8
+ ExportInfo,
9
+ FileKind
10
+ } from "types";
11
+
12
+ /**
13
+ * Heurística simples para classificar arquivos
14
+ */
15
+ function detectFileKind(filePath: string, exportsCount: number): FileKind {
16
+ if (filePath.includes("/cli/")) return "cli";
17
+ if (filePath.endsWith("index.ts") || filePath.endsWith("main.ts")) return "entry";
18
+ if (exportsCount === 0) return "util";
19
+ if (exportsCount > 3) return "domain";
20
+ return "unknown";
21
+ }
22
+
23
+ export async function three(dir: string): Promise<FileTree[]> {
24
+ const project = new Project({});
25
+
26
+ project.addSourceFilesAtPaths([
27
+ `${dir}/**/*.{ts,tsx,js,jsx}`,
28
+ `!${dir}/**/node_modules/**/*`,
29
+ `!${dir}/**/dist/**/*`,
30
+ ]);
31
+
32
+ const tree: FileTree[] = [];
33
+
34
+ for (const file of project.getSourceFiles()) {
35
+ const absolutePath = file.getFilePath();
36
+ const filePath = path.relative(dir, absolutePath);
37
+
38
+ if (filePath.includes("node_modules") || filePath.includes("/dist/")) {
39
+ continue;
40
+ }
41
+
42
+ /* ---------------- IMPORTS ---------------- */
43
+ const imports = file
44
+ .getImportDeclarations()
45
+ .map(i => i.getModuleSpecifierValue());
46
+
47
+ /* ---------------- CLASSES ---------------- */
48
+ const classes: ClassInfo[] = file.getClasses().map(cls => ({
49
+ name: cls.getName() || "<anonymous>",
50
+ methods: cls.getMethods().map(m => ({
51
+ name: m.getName(),
52
+ params: m.getParameters().map(p => p.getName()),
53
+ returnType: safeType(() => m.getReturnType().getText()),
54
+ scope: "class",
55
+ }))
56
+ }));
57
+
58
+ /* ---------------- FUNCTIONS ---------------- */
59
+ const functions: FunctionInfo[] = file.getFunctions().map(fn => ({
60
+ name: fn.getName() || "<anonymous>",
61
+ params: fn.getParameters().map(p => p.getName()),
62
+ returnType: safeType(() => fn.getReturnType().getText()),
63
+ scope: "global",
64
+ }));
65
+
66
+ /* ---------------- VARIABLES ---------------- */
67
+ const variables: VariableInfo[] = file.getVariableDeclarations().map(v => ({
68
+ name: v.getName(),
69
+ type: safeType(() => v.getType().getText()),
70
+ scope: v.getParent() instanceof SourceFile ? "global" : "local",
71
+ }));
72
+
73
+ /* ---------------- EXPORTS ---------------- */
74
+ const exports: ExportInfo[] = [];
75
+ let mainExport: string | undefined;
76
+
77
+ file.getExportedDeclarations().forEach((decls, name) => {
78
+ decls.forEach(d => {
79
+ let kind: ExportInfo["kind"] = "unknown";
80
+
81
+ if (d.getKind() === SyntaxKind.ClassDeclaration) kind = "class";
82
+ if (d.getKind() === SyntaxKind.FunctionDeclaration) kind = "function";
83
+ if (d.getKind() === SyntaxKind.VariableDeclaration) kind = "variable";
84
+ if (d.getKind() === SyntaxKind.TypeAliasDeclaration) kind = "type";
85
+
86
+ exports.push({ name, kind });
87
+ });
88
+ });
89
+
90
+ if (exports.length === 1) {
91
+ mainExport = exports[0].name;
92
+ }
93
+
94
+ const kind = detectFileKind(filePath, exports.length);
95
+
96
+ tree.push({
97
+ path: filePath,
98
+ kind,
99
+ imports,
100
+ classes,
101
+ functions,
102
+ variables,
103
+ exports,
104
+ mainExport,
105
+ });
106
+ }
107
+
108
+ return tree;
109
+ }
110
+
111
+ function safeType(fn: () => string): string {
112
+ try {
113
+ return fn();
114
+ } catch {
115
+ return "unknown";
116
+ }
117
+ }
package/src/core/scan.ts CHANGED
@@ -34,7 +34,7 @@ export function scanDir(
34
34
  // ignore read errors
35
35
  }
36
36
  }
37
-
37
+
38
38
  function shouldIgnore(name: string): boolean {
39
39
  if (ignoreSet.has(name)) return true;
40
40
  for (const pattern of ignoreSet) {
package/src/index.ts CHANGED
@@ -1,61 +1,78 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { copy } from "./cli/commands/copy";
4
- import { paste } from "./cli/commands/paste";
5
- import { prompt } from "./cli/commands/prompt";
6
- import { sync } from "./cli/commands/sync";
3
+ import { JohankitService } from "./services/JohankitService";
7
4
 
8
5
  const [, , command, ...args] = process.argv;
6
+ const service = new JohankitService();
9
7
 
10
8
  async function main() {
11
- switch (command) {
12
- case "copy": {
13
- const dir = args[0] ?? ".";
14
- const exts = args[1]?.split(",");
15
- await copy(dir);
16
- break;
17
- }
9
+ try {
10
+ switch (command) {
11
+ case "copy": {
12
+ const dir = args[0] ?? ".";
13
+ await service.copy(dir);
14
+ break;
15
+ }
18
16
 
19
- case "paste": {
20
- const dir = args[0] ?? ".";
21
- await paste(dir);
22
- break;
23
- }
17
+ case "paste": {
18
+ const dir = args[0] ?? ".";
19
+ await service.paste(dir);
20
+ break;
21
+ }
24
22
 
25
- case "prompt": {
26
- const dir = args[0] ?? ".";
27
- const userPrompt = args.slice(1).join(" ");
28
- if (!userPrompt) {
29
- console.error("Missing user prompt");
30
- process.exit(1);
23
+ case "prompt": {
24
+ const dir = args[0] ?? ".";
25
+ const diff = args.includes("--diff");
26
+ const userPrompt = args.filter(a => a !== "--diff").slice(1).join(" ");
27
+ await service.prompt(dir, userPrompt, diff);
28
+ break;
31
29
  }
32
- await prompt(dir, userPrompt);
33
- break;
34
- }
35
30
 
36
- case "sync": {
37
- const dir = args[0] ?? ".";
38
- await sync(dir);
39
- break;
40
- }
31
+ case "sync": {
32
+ const dir = args[0] ?? ".";
33
+ await service.sync(dir);
34
+ break;
35
+ }
36
+
37
+ case "three": {
38
+ const dir = args[0] ?? ".";
39
+ const output = await service.three(dir);
40
+ console.log(output);
41
+ break;
42
+ }
41
43
 
42
- default:
43
- help();
44
+ default:
45
+ showHelp();
46
+ break;
47
+ }
48
+ } catch (error) {
49
+ if (error instanceof Error) {
50
+ console.error("Error:", error.message);
51
+ } else {
52
+ console.error("Unexpected error:", error);
53
+ }
54
+ process.exit(1);
44
55
  }
45
56
  }
46
57
 
47
- function help() {
58
+ function showHelp() {
48
59
  console.log(`
49
60
  Usage:
50
- johankit copy <dir> [exts]
61
+ johankit copy <dir>
51
62
  johankit paste <dir>
52
- johankit prompt <dir> "<user request>"
63
+ johankit prompt <dir> "<user request>" [--diff]
53
64
  johankit sync <dir>
65
+ johankit three <dir>
54
66
 
55
67
  Examples:
56
- johankit prompt src "refatorar para async/await"
68
+ johankit copy src
69
+ johankit paste src
70
+ johankit prompt src "refactor to async/await"
57
71
  johankit sync src
72
+ johankit three src
58
73
  `);
59
74
  }
60
75
 
61
- main();
76
+ main();
77
+
78
+ export { JohankitService };
@@ -0,0 +1,70 @@
1
+ import createAsciiTree from "../utils/createAsciiTree";
2
+ import { copy } from "../cli/commands/copy";
3
+ import { paste } from "../cli/commands/paste";
4
+ import { prompt } from "../cli/commands/prompt";
5
+ import { three } from "../cli/commands/three";
6
+ import { scanDir } from "../core/scan";
7
+ import { applyDiff, DiffPatch } from "../core/diff";
8
+ import { validatePatches } from "../core/validation";
9
+ import { writeFiles } from "../core/write";
10
+
11
+ interface JohankitServiceOptions {
12
+ isolated?: boolean;
13
+ }
14
+
15
+ export class JohankitService {
16
+ private isolated: boolean;
17
+ private internalInput?: any;
18
+
19
+ constructor(options: JohankitServiceOptions = {}) {
20
+ this.isolated = options.isolated ?? false;
21
+ }
22
+
23
+ setInput(input: any) {
24
+ this.internalInput = input;
25
+ }
26
+
27
+ async copy(dir: string = ".") {
28
+ return copy(dir);
29
+ }
30
+
31
+ async paste(dir: string = ".") {
32
+ return paste(dir);
33
+ }
34
+
35
+ async prompt(dir: string, userPrompt: string, diff = false) {
36
+ if (!userPrompt) {
37
+ throw new Error("Missing user prompt");
38
+ }
39
+ return prompt(dir, userPrompt, diff);
40
+ }
41
+
42
+ async sync(dir: string = ".", diff = false) {
43
+ const snapshotBefore = scanDir(dir);
44
+
45
+ const input = this.isolated ? this.internalInput : undefined;
46
+
47
+ if (!this.isolated && input === undefined) {
48
+ throw new Error("sync() without isolation must be used via CLI (stdin)");
49
+ }
50
+
51
+ if (!input) {
52
+ throw new Error("No input provided for isolated sync");
53
+ }
54
+
55
+ if (diff) {
56
+ const validated = validatePatches(input) as DiffPatch[];
57
+ applyDiff(dir, validated);
58
+ } else {
59
+ writeFiles(dir, input, true);
60
+ }
61
+
62
+ const snapshotAfter = scanDir(dir);
63
+ return { before: snapshotBefore, after: snapshotAfter };
64
+ }
65
+
66
+ async three(dir: string = ".") {
67
+ const tree = await three(dir);
68
+ return createAsciiTree(tree);
69
+ }
70
+ }
package/src/types.ts CHANGED
@@ -12,3 +12,48 @@ export interface ScanOptions {
12
12
  export interface Config {
13
13
  ignore: string[]; // Patterns de arquivos/diretórios a ignorar
14
14
  }
15
+
16
+ export type FileKind =
17
+ | "entry"
18
+ | "cli"
19
+ | "domain"
20
+ | "infra"
21
+ | "util"
22
+ | "unknown";
23
+
24
+ export interface VariableInfo {
25
+ name: string;
26
+ type?: string;
27
+ scope: "global" | "local";
28
+ }
29
+
30
+ export interface FunctionInfo {
31
+ name: string;
32
+ params: string[];
33
+ returnType?: string;
34
+ scope: "global" | "class";
35
+ }
36
+
37
+ export interface ClassInfo {
38
+ name: string;
39
+ methods: FunctionInfo[];
40
+ }
41
+
42
+ export interface ExportInfo {
43
+ name: string;
44
+ kind: "function" | "class" | "variable" | "type" | "unknown";
45
+ }
46
+
47
+ export interface FileTree {
48
+ path: string;
49
+ kind: FileKind;
50
+
51
+ imports: string[];
52
+
53
+ classes: ClassInfo[];
54
+ functions: FunctionInfo[];
55
+ variables: VariableInfo[];
56
+
57
+ exports: ExportInfo[];
58
+ mainExport?: string;
59
+ }
@@ -0,0 +1,13 @@
1
+ export default function cleanCodeBlock(content: string) {
2
+ const langMatch = content.match(/^```(\w+)?/);
3
+ const lang = langMatch ? langMatch[1] : null;
4
+
5
+ let cleaned = content.replace(/^\uFEFF/, '');
6
+
7
+ cleaned = cleaned.replace(/^```(\w+)?\s*/, '').replace(/```$/, '');
8
+
9
+ cleaned = cleaned.replace(/[\u0000-\u0008\u000B-\u000C\u000E-\u001F\u007F-\u009F]/g, '');
10
+ cleaned = cleaned.trim();
11
+
12
+ return { lang, cleaned };
13
+ }
@@ -0,0 +1,53 @@
1
+ import { FileTree } from "types";
2
+
3
+ export default function createAsciiTree(files: FileTree[]): string {
4
+ let out = "";
5
+
6
+ files.forEach((file, i) => {
7
+ const lastFile = i === files.length - 1;
8
+ const fPrefix = lastFile ? "└── " : "├── ";
9
+ const fIndent = lastFile ? " " : "│ ";
10
+
11
+ out += `${fPrefix}📄 ${file.path} (${file.kind})\n`;
12
+
13
+ /* -------- Imports -------- */
14
+ if (file.imports.length) {
15
+ out += `${fIndent}├── 📦 Imports [${file.imports.length}]\n`;
16
+ file.imports.forEach((imp, idx) => {
17
+ const last = idx === file.imports.length - 1;
18
+ out += `${fIndent}${last ? "└── " : "├── "}${imp}\n`;
19
+ });
20
+ }
21
+
22
+ /* -------- Exports -------- */
23
+ if (file.exports.length) {
24
+ out += `${fIndent}├── 🔑 Exports\n`;
25
+ file.exports.forEach((e, idx) => {
26
+ const last = idx === file.exports.length - 1;
27
+ const main = e.name === file.mainExport ? " ⭐" : "";
28
+ out += `${fIndent}${last ? "└── " : "├── "}${e.name} (${e.kind})${main}\n`;
29
+ });
30
+ }
31
+
32
+ /* -------- Variables -------- */
33
+ file.variables.forEach(v => {
34
+ out += `${fIndent}├── 💡 ${v.name}: ${v.type} (${v.scope})\n`;
35
+ });
36
+
37
+ /* -------- Functions -------- */
38
+ file.functions.forEach(fn => {
39
+ out += `${fIndent}├── ⚡ ${fn.name}(${fn.params.join(", ")}): ${fn.returnType}\n`;
40
+ });
41
+
42
+ /* -------- Classes -------- */
43
+ file.classes.forEach(cls => {
44
+ out += `${fIndent}├── ⚙️ Class ${cls.name}\n`;
45
+ cls.methods.forEach((m, mi) => {
46
+ const last = mi === cls.methods.length - 1;
47
+ out += `${fIndent}│ ${last ? "└── " : "├── "}➡️ ${m.name}(${m.params.join(", ")}): ${m.returnType}\n`;
48
+ });
49
+ });
50
+ });
51
+
52
+ return out;
53
+ }
package/tsconfig.json CHANGED
@@ -3,6 +3,10 @@
3
3
  "target": "ES2020",
4
4
  "module": "NodeNext",
5
5
  "moduleResolution": "NodeNext",
6
+ "baseUrl": "./src",
7
+ "paths": {
8
+ "*": ["*", "core/*"]
9
+ },
6
10
  "outDir": "dist",
7
11
  "rootDir": "src",
8
12
  "esModuleInterop": true,
@@ -1,47 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
25
- Object.defineProperty(exports, "__esModule", { value: true });
26
- const copy_1 = require("../../../cli/commands/copy");
27
- const scan = __importStar(require("../../../core/scan"));
28
- const clipboard = __importStar(require("../../../core/clipboard"));
29
- jest.mock('../../../core/scan');
30
- jest.mock('../../../core/clipboard');
31
- describe('copy', () => {
32
- it('should copy single file snapshot to clipboard', () => {
33
- scan.scanDir.mockReturnValue([{ path: 'file.txt', content: 'hello' }]);
34
- (0, copy_1.copy)('file.txt');
35
- expect(clipboard.copyToClipboard).toHaveBeenCalledWith(JSON.stringify([{ path: 'file.txt', content: 'hello' }], null, 2));
36
- });
37
- it('should copy multiple file snapshots to clipboard', () => {
38
- scan.scanDir
39
- .mockReturnValueOnce([{ path: 'file1.txt', content: 'a' }])
40
- .mockReturnValueOnce([{ path: 'file2.txt', content: 'b' }]);
41
- (0, copy_1.copy)(['file1.txt', 'file2.txt']);
42
- expect(clipboard.copyToClipboard).toHaveBeenCalledWith(JSON.stringify([
43
- { path: 'file1.txt', content: 'a' },
44
- { path: 'file2.txt', content: 'b' }
45
- ], null, 2));
46
- });
47
- });