johankit 0.1.3 → 1.0.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 (62) hide show
  1. package/README.md +45 -62
  2. package/dist/cli/commands/sync.js +38 -59
  3. package/dist/core/validation.js +7 -20
  4. package/dist/core/write.js +10 -9
  5. package/dist/src/cli/commands/copy.js +11 -0
  6. package/dist/src/cli/commands/paste.js +128 -0
  7. package/dist/src/cli/commands/prompt.js +54 -0
  8. package/dist/src/cli/commands/sync.js +166 -0
  9. package/dist/src/core/clipboard.js +64 -0
  10. package/dist/src/core/config.js +41 -0
  11. package/dist/{core → src/core}/diff.js +10 -14
  12. package/dist/{core → src/core}/git.js +1 -2
  13. package/dist/src/core/scan.js +75 -0
  14. package/dist/src/core/schema.js +18 -0
  15. package/dist/src/core/validation.js +11 -0
  16. package/dist/src/core/write.js +24 -0
  17. package/dist/src/index.js +39 -0
  18. package/dist/src/tests/cleanCodeBlock.test.js +23 -0
  19. package/dist/src/tests/scan.test.js +35 -0
  20. package/dist/src/tests/schema.test.js +22 -0
  21. package/dist/src/utils/cleanCodeBlock.js +13 -0
  22. package/dist/types.js +1 -0
  23. package/johankit.yml +6 -0
  24. package/package.json +20 -10
  25. package/src/cli/commands/copy.ts +6 -19
  26. package/src/cli/commands/paste.ts +79 -31
  27. package/src/cli/commands/prompt.ts +24 -64
  28. package/src/cli/commands/sync.ts +112 -71
  29. package/src/core/clipboard.ts +46 -80
  30. package/src/core/config.ts +22 -32
  31. package/src/core/diff.ts +10 -21
  32. package/src/core/scan.ts +52 -43
  33. package/src/core/schema.ts +17 -34
  34. package/src/core/validation.ts +8 -27
  35. package/src/core/write.ts +11 -17
  36. package/src/index.ts +43 -77
  37. package/src/tests/cleanCodeBlock.test.ts +21 -0
  38. package/src/tests/scan.test.ts +33 -0
  39. package/src/tests/schema.test.ts +24 -0
  40. package/src/types.ts +4 -50
  41. package/src/utils/cleanCodeBlock.ts +12 -12
  42. package/tsconfig.json +14 -5
  43. package/Readme.md +0 -56
  44. package/dist/cli/commands/copy.js +0 -29
  45. package/dist/cli/commands/paste.js +0 -49
  46. package/dist/cli/commands/prompt.js +0 -89
  47. package/dist/cli/commands/three.js +0 -106
  48. package/dist/cli/commands/tree.js +0 -107
  49. package/dist/core/clipboard.js +0 -89
  50. package/dist/core/config.js +0 -52
  51. package/dist/core/scan.js +0 -67
  52. package/dist/core/schema.js +0 -41
  53. package/dist/index.js +0 -72
  54. package/dist/services/JohankitService.js +0 -59
  55. package/dist/utils/cleanCodeBlock.js +0 -12
  56. package/dist/utils/createAsciiTree.js +0 -46
  57. package/johankit.yaml +0 -2
  58. package/src/cli/commands/tree.ts +0 -119
  59. package/src/services/JohankitService.ts +0 -70
  60. package/src/utils/createAsciiTree.ts +0 -53
  61. package/types.ts +0 -11
  62. /package/{types.js → dist/src/types.js} +0 -0
@@ -1,10 +1,8 @@
1
- // src/core/config.ts
2
1
  import path from "path";
3
- import { readFileSync } from "fs";
2
+ import { readFileSync, existsSync } from "fs";
4
3
  import { load } from "js-yaml";
5
4
  import { Config } from "../types";
6
5
 
7
- const CONFIG_FILENAME = "johankit.yaml";
8
6
  const DEFAULT_IGNORE = [
9
7
  ".git",
10
8
  "node_modules",
@@ -15,36 +13,28 @@ const DEFAULT_IGNORE = [
15
13
  "temp",
16
14
  ];
17
15
 
18
- /**
19
- * Tenta carregar as configurações do arquivo johankit.yaml na basePath.
20
- * Retorna um objeto Config com defaults se o arquivo não for encontrado.
21
- * @param basePath O diretório base para procurar o arquivo de configuração.
22
- * @returns O objeto de configuração.
23
- */
24
16
  export function loadConfig(basePath: string): Config {
25
- const configPath = path.join(basePath, CONFIG_FILENAME);
17
+ // Lista de possíveis nomes para o arquivo de configuração
18
+ const configFilenames = ["johankit.yaml", "johankit.yml"];
19
+ let loadedConfig: Partial<Config> = {};
26
20
 
27
- try {
28
- const content = readFileSync(configPath, "utf8");
29
- const loadedConfig = load(content) as Partial<Config>;
30
-
31
- return {
32
- ignore: [
33
- ...DEFAULT_IGNORE,
34
- ...(loadedConfig.ignore || []),
35
- ],
36
- };
37
- } catch (error) {
38
- if (error instanceof Error && (error as any).code === "ENOENT") {
39
- // Arquivo não encontrado, retorna configuração padrão
40
- return {
41
- ignore: DEFAULT_IGNORE,
42
- };
21
+ for (const filename of configFilenames) {
22
+ const configPath = path.join(basePath, filename);
23
+ if (existsSync(configPath)) {
24
+ try {
25
+ const content = readFileSync(configPath, "utf8");
26
+ loadedConfig = (load(content) as Partial<Config>) || {};
27
+ break; // Para no primeiro que encontrar
28
+ } catch (error) {
29
+ console.warn(`[johankit] Erro ao ler ${filename}, tentando próximo...`);
30
+ }
43
31
  }
44
-
45
- console.warn(`[johankit] Aviso: Falha ao carregar ${CONFIG_FILENAME}. Usando defaults.`, error);
46
- return {
47
- ignore: DEFAULT_IGNORE,
48
- };
49
32
  }
50
- }
33
+
34
+ // Set para garantir que não existam duplicatas nos padrões de ignore
35
+ const ignoreSet = new Set([...DEFAULT_IGNORE, ...(loadedConfig.ignore || [])]);
36
+
37
+ return {
38
+ ignore: Array.from(ignoreSet),
39
+ };
40
+ }
package/src/core/diff.ts CHANGED
@@ -1,31 +1,20 @@
1
1
  // src/core/diff.ts
2
2
  import fs from "fs";
3
3
  import path from "path";
4
+ import { PatchItem } from "./schema";
4
5
 
5
- export interface DiffPatch {
6
- type: "modify" | "create" | "delete";
7
- path: string;
8
- content?: string;
9
- }
10
-
11
- export function applyDiff(basePath: string, patches: DiffPatch[]) {
6
+ export function applyDiff(basePath: string, patches: PatchItem[]) {
12
7
  for (const patch of patches) {
8
+ if (!patch.path) continue;
13
9
  const fullPath = path.join(basePath, patch.path);
14
10
 
15
- switch (patch.type) {
16
- case "delete": {
17
- if (fs.existsSync(fullPath)) {
18
- fs.unlinkSync(fullPath);
19
- }
20
- break;
21
- }
22
-
23
- case "create":
24
- case "modify": {
25
- fs.mkdirSync(path.dirname(fullPath), { recursive: true });
26
- fs.writeFileSync(fullPath, patch.content ?? "", "utf8");
27
- break;
11
+ if (patch.content === null || patch.content === undefined) {
12
+ if (fs.existsSync(fullPath)) {
13
+ fs.unlinkSync(fullPath);
28
14
  }
15
+ } else {
16
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
17
+ fs.writeFileSync(fullPath, patch.content, "utf8");
29
18
  }
30
19
  }
31
- }
20
+ }
package/src/core/scan.ts CHANGED
@@ -1,70 +1,79 @@
1
- // src/core/scan.ts
2
1
  import fs from "fs";
3
2
  import path from "path";
4
- import { FileSnapshot, ScanOptions } from "../types";
3
+ import { FileSnapshot, ScanOptions, Config } from "../types";
4
+ import { loadConfig } from "./config";
5
5
 
6
- export function scanDir(
7
- basePath: string,
8
- options: ScanOptions = {}
9
- ): FileSnapshot[] {
6
+ // Cache para evitar re-leitura de configurações durante o mesmo processo
7
+ let cachedConfig: Config | null = null;
8
+ let cachedIgnoreList: string[] | null = null;
9
+
10
+ export function scanDir(basePath: string, options: ScanOptions = {}): FileSnapshot[] {
10
11
  const result: FileSnapshot[] = [];
11
12
  const base = path.resolve(basePath);
13
+ const exts = options.extensions?.map(e => e.startsWith('.') ? e : `.${e}`);
12
14
 
13
- const exts = options.extensions?.map(e =>
14
- e.startsWith(".") ? e : `.${e}`
15
- );
16
-
17
- // Default ignores
18
- const ignoreSet = new Set([
19
- "node_modules", ".git", "dist", "build", ".DS_Store", "coverage", ".env", "yarn.lock",
20
- ]);
15
+ if (!cachedConfig) {
16
+ cachedConfig = loadConfig(base);
17
+ const ignorePatterns = new Set(cachedConfig.ignore);
21
18
 
22
- // Read .gitignore if exists
23
- const gitignorePath = path.join(base, ".gitignore");
24
- if (fs.existsSync(gitignorePath)) {
25
- try {
26
- const lines = fs.readFileSync(gitignorePath, "utf8").split("\n");
27
- for (const line of lines) {
28
- const trimmed = line.trim();
29
- if (trimmed && !trimmed.startsWith("#")) {
30
- ignoreSet.add(trimmed.replace(/^\//, "").replace(/\/$/, ""));
31
- }
32
- }
33
- } catch (e) {
34
- // ignore read errors
19
+ const gitignorePath = path.join(base, '.gitignore');
20
+ if (fs.existsSync(gitignorePath)) {
21
+ try {
22
+ fs.readFileSync(gitignorePath, 'utf8')
23
+ .split('\n')
24
+ .forEach(line => {
25
+ const trimmed = line.trim();
26
+ if (trimmed && !trimmed.startsWith('#')) {
27
+ ignorePatterns.add(trimmed.replace(/^\//, '').replace(/\/$/, ''));
28
+ }
29
+ });
30
+ } catch { }
35
31
  }
32
+ cachedIgnoreList = Array.from(ignorePatterns);
36
33
  }
37
34
 
38
- function shouldIgnore(name: string): boolean {
39
- if (ignoreSet.has(name)) return true;
40
- for (const pattern of ignoreSet) {
41
- if (pattern.startsWith("*") && name.endsWith(pattern.slice(1))) return true;
42
- if (name.startsWith(pattern + "/")) return true;
43
- }
44
- return false;
45
- }
35
+ const finalIgnoreList = cachedIgnoreList!;
46
36
 
47
37
  function loop(currentPath: string) {
48
38
  const entries = fs.readdirSync(currentPath, { withFileTypes: true });
49
39
 
50
40
  for (const entry of entries) {
51
- if (shouldIgnore(entry.name)) continue;
41
+ const name = entry.name;
42
+
43
+ // Otimização: Verificação rápida de binários e pastas ocultas comuns
44
+ if (name.startsWith('.') && name !== '.gitignore') continue;
45
+ if (name.match(/\.(png|jpg|jpeg|gif|pdf|zip|exe|dll|so|db|map|lock)$/i)) continue;
46
+
47
+ const fullPath = path.join(currentPath, name);
48
+ const relPath = path.relative(base, fullPath).replace(/\\/g, '/');
49
+
50
+ const shouldIgnore = finalIgnoreList.some(p =>
51
+ relPath === p || relPath.startsWith(p + '/')
52
+ );
52
53
 
53
- const fullPath = path.join(currentPath, entry.name);
54
+ if (shouldIgnore) continue;
54
55
 
55
56
  if (entry.isDirectory()) {
56
57
  loop(fullPath);
57
58
  } else {
58
- if (exts && !exts.includes(path.extname(entry.name))) continue;
59
+ if (exts && !exts.includes(path.extname(name))) continue;
59
60
 
60
- result.push({
61
- path: path.relative(base, fullPath).replace(/\\/g, "/"),
62
- content: fs.readFileSync(fullPath, "utf8"),
63
- });
61
+ try {
62
+ const stats = fs.statSync(fullPath);
63
+ // Ignora arquivos maiores que 200KB para manter performance do clipboard
64
+ if (stats.size > 200 * 1024) continue;
65
+
66
+ result.push({
67
+ path: relPath,
68
+ content: fs.readFileSync(fullPath, 'utf8')
69
+ });
70
+ } catch (e) {
71
+ continue;
72
+ }
64
73
  }
65
74
  }
66
75
  }
67
76
 
68
77
  loop(base);
69
78
  return result;
70
- }
79
+ }
@@ -1,41 +1,24 @@
1
1
  // src/core/schema.ts
2
- import { DiffPatch } from "./diff";
3
2
 
4
- /**
5
- * Valida se um objeto se parece com um Patch de DiffPatch válido.
6
- * Não faz validação completa de esquema (JSON Schema), mas verifica a estrutura básica.
7
- */
8
- function isValidPatch(patch: any): boolean {
9
- if (typeof patch !== "object" || patch === null) return false;
10
- if (typeof patch.path !== "string" || patch.path.length === 0) return false;
11
-
12
- const validTypes = ["modify", "create", "delete"];
13
- if (!validTypes.includes(patch.type)) return false;
14
-
15
- if (patch.type === "delete") {
16
- return patch.content === undefined;
17
- } else {
18
- return typeof patch.content === "string";
19
- }
3
+ export interface PatchItem {
4
+ path?: string;
5
+ content?: string | null;
6
+ type?: 'console';
7
+ command?: string;
20
8
  }
21
9
 
22
- /**
23
- * Valida um array de patches de diff (DiffPatch[]).
24
- * @param patches O array a ser validado.
25
- * @returns O array de patches se for válido.
26
- * @throws Um erro se a validação falhar.
27
- */
28
- export function validatePatches(patches: any): DiffPatch[] {
29
- if (!Array.isArray(patches)) {
30
- throw new Error("O patch deve ser um array JSON válido");
10
+ export function validatePatches(json: any): PatchItem[] {
11
+ if (!Array.isArray(json)) {
12
+ throw new Error("Input must be a valid JSON array");
31
13
  }
14
+ // Validação permissiva: ou tem path (arquivo) ou tem type console + command
15
+ return json.map((item, index) => {
16
+ const isFile = typeof item.path === 'string';
17
+ const isCommand = item.type === 'console' && typeof item.command === 'string';
32
18
 
33
- for (const [index, patch] of patches.entries()) {
34
- if (!isValidPatch(patch)) {
35
- throw new Error(`Patch inválido no índice ${index}: ${JSON.stringify(patch, null, 2)}.\nEsperado: { type: 'modify'|'create'|'delete', path: string, content?: string }`);
19
+ if (!isFile && !isCommand) {
20
+ throw new Error(`Item at index ${index} is invalid. Must have 'path' or 'type: console'`);
36
21
  }
37
- }
38
-
39
- // Assume que o array validado está no formato correto de DiffPatch[]
40
- return patches as DiffPatch[];
41
- }
22
+ return item as PatchItem;
23
+ });
24
+ }
@@ -1,28 +1,9 @@
1
1
  // src/core/validation.ts
2
- import { DiffPatch } from "./diff";
3
-
4
- export function validatePatches(json: any): DiffPatch[] {
5
- if (!Array.isArray(json)) {
6
- throw new Error("Validation Error: Input is not a JSON array.");
7
- }
8
-
9
- return json.map((item, index) => {
10
- if (typeof item !== "object" || item === null) {
11
- throw new Error(`Validation Error: Item at index ${index} is not an object.`);
12
- }
13
-
14
- if (!["modify", "create", "delete"].includes(item.type)) {
15
- throw new Error(`Validation Error: Invalid type '${item.type}' at index ${index}.`);
16
- }
17
-
18
- if (typeof item.path !== "string" || !item.path.trim()) {
19
- throw new Error(`Validation Error: Invalid or missing path at index ${index}.`);
20
- }
21
-
22
- if (item.type !== "delete" && typeof item.content !== "string") {
23
- throw new Error(`Validation Error: Missing content for '${item.type}' at index ${index}.`);
24
- }
25
-
26
- return item as DiffPatch;
27
- });
28
- }
2
+ import { validatePatches as sharedValidate } from "./schema";
3
+
4
+ /**
5
+ * @deprecated Use validatePatches from core/schema instead.
6
+ */
7
+ export function validatePatches(json: any): any[] {
8
+ return sharedValidate(json);
9
+ }
package/src/core/write.ts CHANGED
@@ -1,26 +1,20 @@
1
1
  // src/core/write.ts
2
2
  import fs from "fs";
3
3
  import path from "path";
4
- import { FileSnapshot } from "../types";
5
4
  import { ensureGitCommit } from "./git";
6
5
 
7
- export function writeFiles(
8
- basePath: string,
9
- files: FileSnapshot[],
10
- commit = true
11
- ) {
12
- if (commit) {
13
- ensureGitCommit("johankit: before paste");
14
- }
6
+ /**
7
+ * @deprecated Use applyDiff from core/diff for more flexibility (supports deletes and console commands).
8
+ */
9
+ export function writeFiles(basePath: string, files: any, commit = true) {
10
+ // if (commit && files.length > 0) {
11
+ // ensureGitCommit("johankit: before write");
12
+ // }
15
13
 
16
14
  for (const file of files) {
15
+ if (!file.path) continue;
17
16
  const fullPath = path.join(basePath, file.path);
18
- const dir = path.dirname(fullPath);
19
-
20
- if (!fs.existsSync(dir)) {
21
- fs.mkdirSync(dir, { recursive: true });
22
- }
23
-
24
- fs.writeFileSync(fullPath, file.content, "utf8");
17
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
18
+ fs.writeFileSync(fullPath, file.content || "", "utf8");
25
19
  }
26
- }
20
+ }
package/src/index.ts CHANGED
@@ -1,78 +1,44 @@
1
1
  #!/usr/bin/env node
2
-
3
- import { JohankitService } from "./services/JohankitService";
4
-
5
- const [, , command, ...args] = process.argv;
6
- const service = new JohankitService();
7
-
8
- async function main() {
9
- try {
10
- switch (command) {
11
- case "copy": {
12
- const dir = args[0] ?? ".";
13
- await service.copy(dir);
14
- break;
15
- }
16
-
17
- case "paste": {
18
- const dir = args[0] ?? ".";
19
- await service.paste(dir);
20
- break;
21
- }
22
-
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;
29
- }
30
-
31
- case "sync": {
32
- const dir = args[0] ?? ".";
33
- await service.sync(dir);
34
- break;
35
- }
36
-
37
- case "tree": {
38
- const dir = args[0] ?? ".";
39
- const output = await service.tree(dir);
40
- console.log(output);
41
- break;
42
- }
43
-
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);
55
- }
56
- }
57
-
58
- function showHelp() {
59
- console.log(`
60
- Usage:
61
- johankit copy <dir>
62
- johankit paste <dir>
63
- johankit prompt <dir> "<user request>" [--diff]
64
- johankit sync <dir>
65
- johankit tree <dir>
66
-
67
- Examples:
68
- johankit copy src
69
- johankit paste src
70
- johankit prompt src "refactor to async/await"
71
- johankit sync src
72
- johankit tree src
73
- `);
74
- }
75
-
76
- main();
77
-
78
- export { JohankitService };
2
+ import { Command } from "commander";
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";
7
+
8
+ const program = new Command();
9
+
10
+ program
11
+ .name('johankit')
12
+ .description('Developer-friendly CLI for codebase snapshots and AI vibe-coding')
13
+ .version('0.0.3');
14
+
15
+ program
16
+ .command('copy [dir] [exts]')
17
+ .action((dir = '.', exts) => copy(dir, exts?.split(',')));
18
+
19
+ program
20
+ .command('paste [dir]')
21
+ .option('--run', 'execute console commands')
22
+ .option('-y', 'auto accept commands without confirmation')
23
+ .option('--dry-run', 'list changes without applying them')
24
+ .option('--diff', 'show diff and ask for confirmation for each file')
25
+ .action((dir = '.', opts) => paste(dir, !!opts.run, !!opts.dryRun, !!opts.diff));
26
+
27
+ program
28
+ .command('prompt [dir] <request...>')
29
+ .action((dir = '.', request) => prompt(dir, request.join(' ')));
30
+
31
+ program
32
+ .command('sync [dir]')
33
+ .option('--run', 'execute console commands')
34
+ .option('-y', 'auto accept commands without confirmation')
35
+ .option('--dry-run', 'list changes without applying them')
36
+ .option('--diff', 'show diff and ask for confirmation for each file')
37
+ .option('--watch', 'continuously watch clipboard for new patches')
38
+ .option('--auto', 'alias for --watch with auto-accept')
39
+ .action((dir = '.', opts) => {
40
+ const isAuto = !!opts.auto;
41
+ sync(dir, isAuto || !!opts.run, !!opts.dryRun, !!opts.diff, isAuto || !!opts.watch);
42
+ });
43
+
44
+ program.parse();
@@ -0,0 +1,21 @@
1
+ import cleanCodeBlock from '../utils/cleanCodeBlock';
2
+
3
+ describe('cleanCodeBlock', () => {
4
+ it('should extract JSON from markdown blocks', () => {
5
+ const input = 'Check this: ```json [{"path": "test"}] ``` and some text';
6
+ const { cleaned } = cleanCodeBlock(input);
7
+ expect(cleaned).toBe('[{"path": "test"}]');
8
+ });
9
+
10
+ it('should handle raw JSON arrays', () => {
11
+ const input = '[{"path": "test"}]';
12
+ const { cleaned } = cleanCodeBlock(input);
13
+ expect(cleaned).toBe('[{"path": "test"}]');
14
+ });
15
+
16
+ it('should remove invisible characters', () => {
17
+ const input = '\uFEFF[{"path": "test"}]';
18
+ const { cleaned } = cleanCodeBlock(input);
19
+ expect(cleaned).toBe('[{"path": "test"}]');
20
+ });
21
+ });
@@ -0,0 +1,33 @@
1
+ import { scanDir } from '../core/scan';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+
5
+ describe('scanDir', () => {
6
+ const testDir = path.join(__dirname, 'test-tmp');
7
+
8
+ beforeEach(() => {
9
+ if (fs.existsSync(testDir)) fs.rmSync(testDir, { recursive: true });
10
+ fs.mkdirSync(testDir);
11
+ });
12
+
13
+ afterEach(() => {
14
+ if (fs.existsSync(testDir)) fs.rmSync(testDir, { recursive: true });
15
+ });
16
+
17
+ it('should scan files in a directory', () => {
18
+ fs.writeFileSync(path.join(testDir, 'test.ts'), 'console.log("hello")');
19
+ const results = scanDir(testDir);
20
+ expect(results.length).toBe(1);
21
+ expect(results[0].path).toBe('test.ts');
22
+ expect(results[0].content).toBe('console.log("hello")');
23
+ });
24
+
25
+ it('should respect ignore patterns from config', () => {
26
+ fs.mkdirSync(path.join(testDir, 'node_modules'));
27
+ fs.writeFileSync(path.join(testDir, 'node_modules/ignore.ts'), 'ignore');
28
+ fs.writeFileSync(path.join(testDir, 'keep.ts'), 'keep');
29
+ const results = scanDir(testDir);
30
+ expect(results.find(r => r.path.includes('node_modules'))).toBeUndefined();
31
+ expect(results.find(r => r.path === 'keep.ts')).toBeDefined();
32
+ });
33
+ });
@@ -0,0 +1,24 @@
1
+ import { validatePatches } from '../core/schema';
2
+
3
+ describe('validatePatches', () => {
4
+ it('should validate correct file patches', () => {
5
+ const input = [{ path: 'src/index.ts', content: 'test' }];
6
+ const output = validatePatches(input);
7
+ expect(output).toEqual(input);
8
+ });
9
+
10
+ it('should validate correct console patches', () => {
11
+ const input = [{ type: 'console', command: 'npm install' }];
12
+ const output = validatePatches(input);
13
+ expect(output).toEqual(input);
14
+ });
15
+
16
+ it('should throw error for invalid items', () => {
17
+ const input = [{ invalid: 'item' }];
18
+ expect(() => validatePatches(input)).toThrow();
19
+ });
20
+
21
+ it('should throw error if input is not an array', () => {
22
+ expect(() => validatePatches({})).toThrow();
23
+ });
24
+ });
package/src/types.ts CHANGED
@@ -1,59 +1,13 @@
1
1
  // src/types.ts
2
2
  export interface FileSnapshot {
3
3
  path: string;
4
- content: string;
4
+ content: string | null;
5
5
  }
6
6
 
7
7
  export interface ScanOptions {
8
- extensions?: string[]; // ['.js', '.ts', '.css']
9
- // A opção 'ignore' foi movida para o arquivo de configuração e 'loadConfig'
8
+ extensions?: string[];
10
9
  }
11
10
 
12
11
  export interface Config {
13
- ignore: string[]; // Patterns de arquivos/diretórios a ignorar
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
- }
12
+ ignore: string[];
13
+ }
@@ -1,13 +1,13 @@
1
+ // src/utils/cleanCodeBlock.ts
1
2
  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
- }
3
+ // Regex robusta para capturar o primeiro array JSON ou bloco de código em string suja
4
+ const jsonRegex = /```json\s*([\s\S]*?)\s*```|(\[\s*{[\s\S]*}\s*\])/;
5
+ const match = content.match(jsonRegex);
6
+
7
+ let cleaned = match ? (match[1] || match[2]) : content;
8
+
9
+ cleaned = cleaned.replace(/^\uFEFF/, '');
10
+ cleaned = cleaned.replace(/[\u0000-\u0008\u000B-\u000C\u000E-\u001F\u007F-\u009F]/g, '');
11
+
12
+ return { cleaned: cleaned.trim() };
13
+ }