johankit 0.0.2

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 (59) hide show
  1. package/README.md +115 -0
  2. package/dist/cli/commands/copy.js +24 -0
  3. package/dist/cli/commands/paste.js +40 -0
  4. package/dist/cli/commands/prompt.js +57 -0
  5. package/dist/cli/commands/sync.js +84 -0
  6. package/dist/core/clipboard.js +54 -0
  7. package/dist/core/config.js +52 -0
  8. package/dist/core/diff.js +29 -0
  9. package/dist/core/git.js +43 -0
  10. package/dist/core/scan.js +67 -0
  11. package/dist/core/schema.js +41 -0
  12. package/dist/core/validation.js +24 -0
  13. package/dist/core/write.js +24 -0
  14. package/dist/index.js +54 -0
  15. package/dist/tests/cli/commands/copy.test.js +47 -0
  16. package/dist/tests/cli/commands/paste.test.js +41 -0
  17. package/dist/tests/cli/commands/prompt.test.js +37 -0
  18. package/dist/tests/cli/commands/sync.test.js +47 -0
  19. package/dist/tests/core/clipboard.test.js +20 -0
  20. package/dist/tests/core/config.test.js +23 -0
  21. package/dist/tests/core/diff.test.js +24 -0
  22. package/dist/tests/core/git.test.js +11 -0
  23. package/dist/tests/core/scan.test.js +16 -0
  24. package/dist/tests/core/schema.test.js +13 -0
  25. package/dist/tests/core/validation.test.js +13 -0
  26. package/dist/tests/core/write.test.js +41 -0
  27. package/dist/types.js +2 -0
  28. package/jest.config.js +6 -0
  29. package/package-lock.json +250 -0
  30. package/package.json +30 -0
  31. package/src/cli/commands/copy.ts +22 -0
  32. package/src/cli/commands/paste.ts +43 -0
  33. package/src/cli/commands/prompt.ts +55 -0
  34. package/src/cli/commands/sync.ts +88 -0
  35. package/src/core/clipboard.ts +56 -0
  36. package/src/core/config.ts +50 -0
  37. package/src/core/diff.ts +31 -0
  38. package/src/core/git.ts +39 -0
  39. package/src/core/scan.ts +70 -0
  40. package/src/core/schema.ts +41 -0
  41. package/src/core/validation.ts +28 -0
  42. package/src/core/write.ts +26 -0
  43. package/src/index.ts +61 -0
  44. package/src/tests/cli/commands/copy.test.ts +26 -0
  45. package/src/tests/cli/commands/paste.test.ts +19 -0
  46. package/src/tests/cli/commands/prompt.test.ts +14 -0
  47. package/src/tests/cli/commands/sync.test.ts +26 -0
  48. package/src/tests/core/clipboard.test.ts +21 -0
  49. package/src/tests/core/config.test.ts +21 -0
  50. package/src/tests/core/diff.test.ts +22 -0
  51. package/src/tests/core/git.test.ts +11 -0
  52. package/src/tests/core/scan.test.ts +13 -0
  53. package/src/tests/core/schema.test.ts +13 -0
  54. package/src/tests/core/validation.test.ts +13 -0
  55. package/src/tests/core/write.test.ts +15 -0
  56. package/src/types.ts +14 -0
  57. package/tsconfig.json +12 -0
  58. package/types.js +2 -0
  59. package/types.ts +11 -0
package/README.md ADDED
@@ -0,0 +1,115 @@
1
+ # JohanKit
2
+
3
+ **JohanKit** is a developer-friendly CLI designed to supercharge your **vibe-coding** flow. It helps you capture, restore, and manipulate snapshots of your codebase, making it effortless to experiment, refactor, and collaborate—without locking you into a specific framework or workflow.
4
+
5
+ Think of it as your personal **code snapshot toolkit**: lightweight, fast, and agnostic.
6
+
7
+ ---
8
+
9
+ ## Why JohanKit?
10
+
11
+ The name combines:
12
+
13
+ - **Johan** – a nod to the “creator” or developer archetype.
14
+ - **Kit** – a set of practical tools.
15
+
16
+ JohanKit is a “kit for developers,” crafted to streamline coding sessions, prototype features quickly, and integrate seamlessly with AI-assisted refactoring or review tools.
17
+
18
+ ---
19
+
20
+ ## Features
21
+
22
+ ### copy
23
+
24
+ Take a snapshot of files in a directory and copy it to your clipboard as JSON.
25
+
26
+ ```bash
27
+ johankit copy <dir> [exts]
28
+ ````
29
+
30
+ * `dir`: Directory to scan (default: current)
31
+ * `exts`: Comma-separated list of extensions (e.g., `ts,js`)
32
+
33
+ ---
34
+
35
+ ### paste
36
+
37
+ Restore files from a JSON snapshot stored in your clipboard.
38
+
39
+ ```bash
40
+ johankit paste <dir>
41
+ ```
42
+
43
+ * `dir`: Directory where files will be created (default: current)
44
+
45
+ ---
46
+
47
+ ### prompt
48
+
49
+ Generate a ready-to-use AI prompt including a snapshot of your codebase.
50
+
51
+ ```bash
52
+ johankit prompt <dir> "<user request>"
53
+ ```
54
+
55
+ * `dir`: Directory to scan (default: current)
56
+ * `<user request>`: Instruction for AI to apply on the snapshot
57
+
58
+ ---
59
+
60
+ ### sync
61
+
62
+ Apply JSON patches to your codebase and update the clipboard with the new snapshot.
63
+
64
+ ```bash
65
+ johankit sync <dir>
66
+ ```
67
+
68
+ * `dir`: Target directory (default: current)
69
+
70
+ ---
71
+
72
+ ## Example Workflow
73
+
74
+ ```bash
75
+ # Copy snapshot of your current src
76
+ johankit copy src
77
+
78
+ # Ask AI to refactor your codebase
79
+ johankit prompt src "Convert all callbacks to async/await"
80
+
81
+ # Apply AI-suggested changes and update snapshot
82
+ johankit sync src
83
+ ```
84
+
85
+ With just a few commands, JohanKit lets you **capture ideas, experiment safely, and sync changes instantly**.
86
+
87
+ ---
88
+
89
+ ## Configuration
90
+
91
+ You can configure defaults with a `johankit.yaml` file in the project root:
92
+
93
+ ```yaml
94
+ ignore:
95
+ - dist
96
+ - node_modules
97
+ - .git
98
+ ```
99
+
100
+ This ensures you never accidentally snapshot unnecessary files.
101
+
102
+ ---
103
+
104
+ ## Requirements
105
+
106
+ * Node.js >= 14
107
+ * Clipboard-compatible OS (`xclip`, `pbcopy`, or `clip` on Windows)
108
+
109
+ ---
110
+
111
+ ## Installation
112
+
113
+ ```bash
114
+ npm install -g johankit
115
+ ```
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.copy = void 0;
4
+ const scan_1 = require("../../core/scan");
5
+ const clipboard_1 = require("../../core/clipboard");
6
+ function copy(input) {
7
+ let snapshot;
8
+ if (Array.isArray(input)) {
9
+ snapshot = input.map(path => {
10
+ const fileSnapshot = (0, scan_1.scanDir)(path);
11
+ if (fileSnapshot.length !== 1) {
12
+ throw new Error(`Expected single file for path: ${path}`);
13
+ }
14
+ return fileSnapshot[0];
15
+ });
16
+ }
17
+ else {
18
+ const stat = (0, scan_1.scanDir)(input);
19
+ snapshot = stat.length === 1 ? stat : (0, scan_1.scanDir)(input);
20
+ }
21
+ const clipboardJSON = JSON.stringify(snapshot, null, 2); // <- garante JSON válido
22
+ (0, clipboard_1.copyToClipboard)(clipboardJSON);
23
+ }
24
+ exports.copy = copy;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.paste = void 0;
4
+ // src/cli/commands/paste.ts
5
+ const write_1 = require("../../core/write");
6
+ const clipboard_1 = require("../../core/clipboard");
7
+ async function paste(dir) {
8
+ try {
9
+ const content = await (0, clipboard_1.readClipboard)();
10
+ if (!content) {
11
+ throw new Error("Clipboard empty or inaccessible");
12
+ }
13
+ let files;
14
+ try {
15
+ const cleanContent = content.replace(/^\uFEFF/, "").trim();
16
+ files = JSON.parse(cleanContent);
17
+ }
18
+ catch (e) {
19
+ throw new Error("Clipboard content is not valid JSON");
20
+ }
21
+ if (!Array.isArray(files)) {
22
+ throw new Error("Clipboard content is not a JSON array");
23
+ }
24
+ // Validação simples do snapshot
25
+ const isValidSnapshot = files.every(f => typeof f.path === 'string' && typeof f.content === 'string');
26
+ if (!isValidSnapshot) {
27
+ throw new Error("JSON does not match FileSnapshot structure {path, content}");
28
+ }
29
+ (0, write_1.writeFiles)(dir, files, true);
30
+ process.stdout.write("✔ Pasted from clipboard\n");
31
+ }
32
+ catch (error) {
33
+ process.stderr.write("✖ Paste failed\n");
34
+ if (error instanceof Error) {
35
+ process.stderr.write(`${error.message}\n`);
36
+ }
37
+ process.exit(1);
38
+ }
39
+ }
40
+ exports.paste = paste;
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.prompt = void 0;
4
+ // src/cli/commands/prompt.ts
5
+ const scan_1 = require("../../core/scan");
6
+ const clipboard_1 = require("../../core/clipboard");
7
+ async function prompt(dir, userPrompt) {
8
+ const snapshot = (0, scan_1.scanDir)(dir);
9
+ const template = `
10
+ You are an AI software engineer.
11
+
12
+ You will receive a JSON array representing a snapshot of a codebase.
13
+ Each item has the following structure:
14
+
15
+ {
16
+ "path": "relative/path/to/file.ext",
17
+ "content": "full file content"
18
+ }
19
+
20
+ ---
21
+
22
+ SNAPSHOT
23
+ ${JSON.stringify(snapshot, null, 2)}
24
+
25
+ ---
26
+
27
+ YOUR TASK
28
+ Propose changes according to the user request.
29
+
30
+ Return ONLY a JSON array of patches.
31
+
32
+ PATCH FORMAT (STRICT)
33
+ {
34
+ "path": "relative/path/to/file.ext",
35
+ "content": "FULL updated file content (omit for delete)"
36
+ }
37
+
38
+ IMPORTANT RULES
39
+ - Do NOT return explanations
40
+ - Do NOT return markdown
41
+ - Return ONLY valid JSON inside the "\`\`\`"
42
+ - Always return within a Markdown Code Block (with "\`\`\`json" syntax highlighting)")
43
+
44
+ USER REQUEST
45
+ ${userPrompt}
46
+ `;
47
+ try {
48
+ await (0, clipboard_1.copyToClipboard)(template.trim());
49
+ process.stdout.write(template.trim());
50
+ process.stdout.write("\n\n✔ Prompt + Snapshot copied to clipboard\n");
51
+ }
52
+ catch (e) {
53
+ process.stdout.write(template.trim());
54
+ process.stderr.write("\n✖ Failed to copy to clipboard (output only)\n");
55
+ }
56
+ }
57
+ exports.prompt = prompt;
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sync = void 0;
4
+ // src/cli/commands/sync.ts
5
+ const scan_1 = require("../../core/scan");
6
+ const diff_1 = require("../../core/diff");
7
+ const clipboard_1 = require("../../core/clipboard");
8
+ const validation_1 = require("../../core/validation");
9
+ async function sync(dir) {
10
+ try {
11
+ const snapshotBefore = (0, scan_1.scanDir)(dir);
12
+ // Gera prompt genérico para AI com snapshot atual
13
+ const template = `
14
+ You are an AI software engineer.
15
+
16
+ You will receive a JSON array representing a snapshot of a codebase.
17
+ Each item has the following structure:
18
+
19
+ \`\`\`json
20
+ {
21
+ "path": "relative/path/to/file.ext",
22
+ "content": "full file content"
23
+ }
24
+ \`\`\`
25
+
26
+ ---
27
+
28
+ SNAPSHOT
29
+ ${JSON.stringify(snapshotBefore, null, 2)}
30
+
31
+ ---
32
+
33
+ YOUR TASK
34
+ Propose changes according to the user request.
35
+
36
+ Return ONLY a JSON array of patches.
37
+
38
+ PATCH FORMAT (STRICT)
39
+ {
40
+ \"path\": \"relative/path/to/file.ext\",
41
+ \"content\": \"FULL updated file content (omit for delete)\"
42
+ }
43
+
44
+ IMPORTANT RULES
45
+ - Do NOT return explanations
46
+ - Do NOT return markdown
47
+ - Return ONLY valid JSON
48
+
49
+ USER REQUEST
50
+ <Replace this with the user request>
51
+ `;
52
+ await (0, clipboard_1.copyToClipboard)(template.trim());
53
+ process.stdout.write("✔ Prompt with snapshot copied to clipboard\n");
54
+ // Aguarda entrada do usuário (resposta da AI) via stdin
55
+ const input = await readStdin();
56
+ let patches;
57
+ try {
58
+ patches = JSON.parse(input);
59
+ }
60
+ catch {
61
+ throw new Error("Invalid JSON input");
62
+ }
63
+ const validated = (0, validation_1.validatePatches)(patches);
64
+ (0, diff_1.applyDiff)(dir, validated);
65
+ const snapshotAfter = (0, scan_1.scanDir)(dir);
66
+ await (0, clipboard_1.copyToClipboard)(JSON.stringify(snapshotAfter, null, 2));
67
+ process.stdout.write("✔ Sync applied and new snapshot copied to clipboard\n");
68
+ }
69
+ catch (error) {
70
+ process.stderr.write("✖ Sync failed\n");
71
+ if (error instanceof Error) {
72
+ process.stderr.write(`${error.message}\n`);
73
+ }
74
+ process.exit(1);
75
+ }
76
+ }
77
+ exports.sync = sync;
78
+ function readStdin() {
79
+ return new Promise(resolve => {
80
+ let data = "";
81
+ process.stdin.on("data", c => (data += c));
82
+ process.stdin.on("end", () => resolve(data));
83
+ });
84
+ }
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readClipboard = exports.copyToClipboard = void 0;
4
+ // src/core/clipboard.ts
5
+ const child_process_1 = require("child_process");
6
+ function copyToClipboard(text) {
7
+ return new Promise((resolve, reject) => {
8
+ let command = "xclip";
9
+ let args = ["-selection", "clipboard"];
10
+ if (process.platform === "darwin") {
11
+ command = "pbcopy";
12
+ args = [];
13
+ }
14
+ else if (process.platform === "win32") {
15
+ command = "clip";
16
+ args = [];
17
+ }
18
+ const child = (0, child_process_1.spawn)(command, args, { stdio: ["pipe", "ignore", "ignore"] });
19
+ child.on("error", (err) => reject(err));
20
+ child.on("close", () => resolve());
21
+ child.stdin.write(text);
22
+ child.stdin.end();
23
+ });
24
+ }
25
+ exports.copyToClipboard = copyToClipboard;
26
+ function readClipboard() {
27
+ return new Promise((resolve, reject) => {
28
+ let command = "xclip";
29
+ let args = ["-selection", "clipboard", "-o"];
30
+ if (process.platform === "darwin") {
31
+ command = "pbpaste";
32
+ args = [];
33
+ }
34
+ else if (process.platform === "win32") {
35
+ command = "powershell";
36
+ args = ["-command", "Get-Clipboard"];
37
+ }
38
+ const child = (0, child_process_1.spawn)(command, args, { stdio: ["ignore", "pipe", "pipe"] });
39
+ let output = "";
40
+ let error = "";
41
+ child.stdout.on("data", (d) => (output += d.toString()));
42
+ child.stderr.on("data", (d) => (error += d.toString()));
43
+ child.on("error", (err) => reject(err));
44
+ child.on("close", (code) => {
45
+ if (code !== 0 && error && !output) {
46
+ reject(new Error(error || "Clipboard read failed"));
47
+ }
48
+ else {
49
+ resolve(output.trim().replace(/^\uFEFF/, ""));
50
+ }
51
+ });
52
+ });
53
+ }
54
+ exports.readClipboard = readClipboard;
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.loadConfig = void 0;
7
+ // src/core/config.ts
8
+ const path_1 = __importDefault(require("path"));
9
+ const fs_1 = require("fs");
10
+ const js_yaml_1 = require("js-yaml");
11
+ const CONFIG_FILENAME = "johankit.yaml";
12
+ const DEFAULT_IGNORE = [
13
+ ".git",
14
+ "node_modules",
15
+ "dist",
16
+ "build",
17
+ "coverage",
18
+ "tmp",
19
+ "temp",
20
+ ];
21
+ /**
22
+ * Tenta carregar as configurações do arquivo johankit.yaml na basePath.
23
+ * Retorna um objeto Config com defaults se o arquivo não for encontrado.
24
+ * @param basePath O diretório base para procurar o arquivo de configuração.
25
+ * @returns O objeto de configuração.
26
+ */
27
+ function loadConfig(basePath) {
28
+ const configPath = path_1.default.join(basePath, CONFIG_FILENAME);
29
+ try {
30
+ const content = (0, fs_1.readFileSync)(configPath, "utf8");
31
+ const loadedConfig = (0, js_yaml_1.load)(content);
32
+ return {
33
+ ignore: [
34
+ ...DEFAULT_IGNORE,
35
+ ...(loadedConfig.ignore || []),
36
+ ],
37
+ };
38
+ }
39
+ catch (error) {
40
+ if (error instanceof Error && error.code === "ENOENT") {
41
+ // Arquivo não encontrado, retorna configuração padrão
42
+ return {
43
+ ignore: DEFAULT_IGNORE,
44
+ };
45
+ }
46
+ console.warn(`[johankit] Aviso: Falha ao carregar ${CONFIG_FILENAME}. Usando defaults.`, error);
47
+ return {
48
+ ignore: DEFAULT_IGNORE,
49
+ };
50
+ }
51
+ }
52
+ exports.loadConfig = loadConfig;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.applyDiff = void 0;
7
+ // src/core/diff.ts
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ function applyDiff(basePath, patches) {
11
+ for (const patch of patches) {
12
+ const fullPath = path_1.default.join(basePath, patch.path);
13
+ switch (patch.type) {
14
+ case "delete": {
15
+ if (fs_1.default.existsSync(fullPath)) {
16
+ fs_1.default.unlinkSync(fullPath);
17
+ }
18
+ break;
19
+ }
20
+ case "create":
21
+ case "modify": {
22
+ fs_1.default.mkdirSync(path_1.default.dirname(fullPath), { recursive: true });
23
+ fs_1.default.writeFileSync(fullPath, patch.content ?? "", "utf8");
24
+ break;
25
+ }
26
+ }
27
+ }
28
+ }
29
+ exports.applyDiff = applyDiff;
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ensureGitCommit = void 0;
7
+ // src/core/git.ts
8
+ const child_process_1 = require("child_process");
9
+ const crypto_1 = __importDefault(require("crypto"));
10
+ function getDiffHash() {
11
+ try {
12
+ const diff = (0, child_process_1.execSync)("git diff --staged", { encoding: "utf8" });
13
+ if (!diff.trim())
14
+ return null;
15
+ return crypto_1.default
16
+ .createHash("sha1")
17
+ .update(diff)
18
+ .digest("hex")
19
+ .slice(0, 7);
20
+ }
21
+ catch {
22
+ return null;
23
+ }
24
+ }
25
+ function defaultCommitMessage(prefix = "Auto Commit") {
26
+ const timestamp = new Date().toISOString().slice(0, 16);
27
+ const diffHash = getDiffHash();
28
+ return diffHash
29
+ ? `${prefix}: ${timestamp} · ${diffHash}`
30
+ : `${prefix}: ${timestamp}`;
31
+ }
32
+ function ensureGitCommit(message) {
33
+ try {
34
+ (0, child_process_1.execSync)("git rev-parse --is-inside-work-tree", { stdio: "ignore" });
35
+ (0, child_process_1.execSync)("git add .");
36
+ const commitMessage = message ?? defaultCommitMessage();
37
+ (0, child_process_1.execSync)(`git commit -m "${commitMessage}"`, { stdio: "ignore" });
38
+ }
39
+ catch {
40
+ // noop: no git or nothing to commit
41
+ }
42
+ }
43
+ exports.ensureGitCommit = ensureGitCommit;
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.scanDir = void 0;
7
+ // src/core/scan.ts
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ function scanDir(basePath, options = {}) {
11
+ const result = [];
12
+ const base = path_1.default.resolve(basePath);
13
+ const exts = options.extensions?.map(e => e.startsWith(".") ? e : `.${e}`);
14
+ // Default ignores
15
+ const ignoreSet = new Set([
16
+ "node_modules", ".git", "dist", "build", ".DS_Store", "coverage", ".env", "yarn.lock",
17
+ ]);
18
+ // Read .gitignore if exists
19
+ const gitignorePath = path_1.default.join(base, ".gitignore");
20
+ if (fs_1.default.existsSync(gitignorePath)) {
21
+ try {
22
+ const lines = fs_1.default.readFileSync(gitignorePath, "utf8").split("\n");
23
+ for (const line of lines) {
24
+ const trimmed = line.trim();
25
+ if (trimmed && !trimmed.startsWith("#")) {
26
+ ignoreSet.add(trimmed.replace(/^\//, "").replace(/\/$/, ""));
27
+ }
28
+ }
29
+ }
30
+ catch (e) {
31
+ // ignore read errors
32
+ }
33
+ }
34
+ function shouldIgnore(name) {
35
+ if (ignoreSet.has(name))
36
+ return true;
37
+ for (const pattern of ignoreSet) {
38
+ if (pattern.startsWith("*") && name.endsWith(pattern.slice(1)))
39
+ return true;
40
+ if (name.startsWith(pattern + "/"))
41
+ return true;
42
+ }
43
+ return false;
44
+ }
45
+ function loop(currentPath) {
46
+ const entries = fs_1.default.readdirSync(currentPath, { withFileTypes: true });
47
+ for (const entry of entries) {
48
+ if (shouldIgnore(entry.name))
49
+ continue;
50
+ const fullPath = path_1.default.join(currentPath, entry.name);
51
+ if (entry.isDirectory()) {
52
+ loop(fullPath);
53
+ }
54
+ else {
55
+ if (exts && !exts.includes(path_1.default.extname(entry.name)))
56
+ continue;
57
+ result.push({
58
+ path: path_1.default.relative(base, fullPath).replace(/\\/g, "/"),
59
+ content: fs_1.default.readFileSync(fullPath, "utf8"),
60
+ });
61
+ }
62
+ }
63
+ }
64
+ loop(base);
65
+ return result;
66
+ }
67
+ exports.scanDir = scanDir;
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validatePatches = void 0;
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) {
9
+ if (typeof patch !== "object" || patch === null)
10
+ return false;
11
+ if (typeof patch.path !== "string" || patch.path.length === 0)
12
+ return false;
13
+ const validTypes = ["modify", "create", "delete"];
14
+ if (!validTypes.includes(patch.type))
15
+ return false;
16
+ if (patch.type === "delete") {
17
+ return patch.content === undefined;
18
+ }
19
+ else {
20
+ return typeof patch.content === "string";
21
+ }
22
+ }
23
+ /**
24
+ * Valida um array de patches de diff (DiffPatch[]).
25
+ * @param patches O array a ser validado.
26
+ * @returns O array de patches se for válido.
27
+ * @throws Um erro se a validação falhar.
28
+ */
29
+ function validatePatches(patches) {
30
+ if (!Array.isArray(patches)) {
31
+ throw new Error("O patch deve ser um array JSON válido");
32
+ }
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 }`);
36
+ }
37
+ }
38
+ // Assume que o array validado está no formato correto de DiffPatch[]
39
+ return patches;
40
+ }
41
+ exports.validatePatches = validatePatches;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validatePatches = void 0;
4
+ function validatePatches(json) {
5
+ if (!Array.isArray(json)) {
6
+ throw new Error("Validation Error: Input is not a JSON array.");
7
+ }
8
+ return json.map((item, index) => {
9
+ if (typeof item !== "object" || item === null) {
10
+ throw new Error(`Validation Error: Item at index ${index} is not an object.`);
11
+ }
12
+ if (!["modify", "create", "delete"].includes(item.type)) {
13
+ throw new Error(`Validation Error: Invalid type '${item.type}' at index ${index}.`);
14
+ }
15
+ if (typeof item.path !== "string" || !item.path.trim()) {
16
+ throw new Error(`Validation Error: Invalid or missing path at index ${index}.`);
17
+ }
18
+ if (item.type !== "delete" && typeof item.content !== "string") {
19
+ throw new Error(`Validation Error: Missing content for '${item.type}' at index ${index}.`);
20
+ }
21
+ return item;
22
+ });
23
+ }
24
+ exports.validatePatches = validatePatches;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.writeFiles = void 0;
7
+ // src/core/write.ts
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const git_1 = require("./git");
11
+ function writeFiles(basePath, files, commit = true) {
12
+ if (commit) {
13
+ (0, git_1.ensureGitCommit)("johankit: before paste");
14
+ }
15
+ for (const file of files) {
16
+ const fullPath = path_1.default.join(basePath, file.path);
17
+ const dir = path_1.default.dirname(fullPath);
18
+ if (!fs_1.default.existsSync(dir)) {
19
+ fs_1.default.mkdirSync(dir, { recursive: true });
20
+ }
21
+ fs_1.default.writeFileSync(fullPath, file.content, "utf8");
22
+ }
23
+ }
24
+ exports.writeFiles = writeFiles;