@rhseung/claude-mv 0.1.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.
@@ -0,0 +1,195 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ PlainReporter,
4
+ describeStep,
5
+ duration,
6
+ pad
7
+ } from "./chunk-JJSX2UL6.js";
8
+
9
+ // src/render/ink/reporter.tsx
10
+ import { render } from "ink";
11
+
12
+ // src/render/ink/app.tsx
13
+ import { ConfirmInput, Spinner } from "@inkjs/ui";
14
+ import { Box, Text } from "ink";
15
+ import { useEffect, useState } from "react";
16
+ import stringWidth from "string-width";
17
+ import { jsx, jsxs } from "react/jsx-runtime";
18
+ function useStore(store) {
19
+ const [, force] = useState(0);
20
+ useEffect(() => store.subscribe(() => force((n) => n + 1)), [store]);
21
+ return store.state;
22
+ }
23
+ var MARK = { pending: "-", running: " ", done: "v", failed: "x" };
24
+ var COLOR = { pending: "gray", running: "cyan", done: "green", failed: "red" };
25
+ function App({ store }) {
26
+ const state = useStore(store);
27
+ if (!state.plan) return null;
28
+ const rows = state.steps.map((entry) => {
29
+ const described = describeStep(entry.step);
30
+ return { ...entry, described };
31
+ });
32
+ const labelWidth = Math.max(...rows.map((r) => stringWidth(r.described.label)), 12);
33
+ const actionWidth = Math.max(...rows.map((r) => stringWidth(r.described.action)), 6);
34
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginY: 1, children: [
35
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
36
+ /* @__PURE__ */ jsxs(Text, { children: [
37
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "from " }),
38
+ state.plan.request.src
39
+ ] }),
40
+ /* @__PURE__ */ jsxs(Text, { children: [
41
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " to " }),
42
+ state.plan.request.dst
43
+ ] })
44
+ ] }),
45
+ rows.map((row, index) => /* @__PURE__ */ jsxs(Box, { children: [
46
+ /* @__PURE__ */ jsx(Text, { color: COLOR[row.status], children: row.status === "running" ? /* @__PURE__ */ jsx(Spinner, {}) : ` ${MARK[row.status]} ` }),
47
+ /* @__PURE__ */ jsxs(Text, { children: [
48
+ " ",
49
+ pad(row.described.label, labelWidth + 2)
50
+ ] }),
51
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: pad(row.described.action, actionWidth + 2) }),
52
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: row.detail ?? row.described.detail })
53
+ ] }, `${row.step.id}-${index}`)),
54
+ state.plan.proseLeftBehind > 0 && /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \uB300\uD654 \uBCF8\uBB38\uC758 \uACBD\uB85C ${state.plan.proseLeftBehind}\uACF3\uC740 \uADF8\uB300\uB85C \uB461\uB2C8\uB2E4 (--rewrite-prose \uB85C \uD3EC\uD568)` }) }),
55
+ state.warnings.length > 0 && /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginTop: 1, children: state.warnings.map((warning, index) => /* @__PURE__ */ jsx(Text, { color: "yellow", children: ` ! ${warning}` }, index)) }),
56
+ state.backupId && !state.outcome && /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \uBC31\uC5C5 ${state.backupId}` }) }),
57
+ state.question && /* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
58
+ /* @__PURE__ */ jsx(Text, { children: ` ${state.question} ` }),
59
+ /* @__PURE__ */ jsx(
60
+ ConfirmInput,
61
+ {
62
+ onConfirm: () => store.answer?.(true),
63
+ onCancel: () => store.answer?.(false)
64
+ }
65
+ )
66
+ ] }),
67
+ state.notes.map((note, index) => /* @__PURE__ */ jsx(Text, { children: ` ${note}` }, index)),
68
+ state.outcome?.kind === "ok" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
69
+ /* @__PURE__ */ jsx(Text, { color: "green", children: ` \uC131\uACF5 ${state.steps.length}\uAC1C \uD56D\uBAA9, ${duration(state.outcome.durationMs)}` }),
70
+ state.backupId && /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \uB418\uB3CC\uB9AC\uAE30 claude-mv rollback ${state.backupId}` })
71
+ ] }),
72
+ state.outcome?.kind === "failed" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
73
+ /* @__PURE__ */ jsx(Text, { color: "red", children: ` \uC2E4\uD328 ${state.outcome.message}` }),
74
+ /* @__PURE__ */ jsx(Text, { color: state.outcome.rolledBack ? "green" : "red", children: state.outcome.rolledBack ? " \uC804\uBD80 \uC6D0\uB798\uB300\uB85C \uB3CC\uB838\uC2B5\uB2C8\uB2E4. \uBC14\uB010 \uAC83\uC740 \uC5C6\uC2B5\uB2C8\uB2E4." : " \uB418\uB3CC\uB9AC\uAE30\uB3C4 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4. \uC190\uC73C\uB85C \uBCF5\uAD6C\uD574\uC57C \uD569\uB2C8\uB2E4." })
75
+ ] })
76
+ ] });
77
+ }
78
+
79
+ // src/render/ink/store.ts
80
+ function createStore() {
81
+ const listeners = /* @__PURE__ */ new Set();
82
+ const store = {
83
+ state: {
84
+ plan: null,
85
+ backupId: null,
86
+ steps: [],
87
+ notes: [],
88
+ warnings: [],
89
+ question: null,
90
+ outcome: null
91
+ },
92
+ subscribe(listener) {
93
+ listeners.add(listener);
94
+ return () => listeners.delete(listener);
95
+ },
96
+ set(patch) {
97
+ const next = typeof patch === "function" ? patch(store.state) : patch;
98
+ store.state = { ...store.state, ...next };
99
+ for (const listener of listeners) listener();
100
+ },
101
+ answer: null
102
+ };
103
+ return store;
104
+ }
105
+
106
+ // src/render/ink/reporter.tsx
107
+ import { jsx as jsx2 } from "react/jsx-runtime";
108
+ function createInkReporter() {
109
+ const store = createStore();
110
+ const plain = new PlainReporter();
111
+ const instance = render(/* @__PURE__ */ jsx2(App, { store }), { exitOnCtrlC: false });
112
+ return {
113
+ plan(plan, opts) {
114
+ store.set((prev) => ({
115
+ plan,
116
+ backupId: opts.backupId,
117
+ steps: plan.steps.map((step) => ({ step, status: "pending" })),
118
+ warnings: [...prev.warnings, ...plan.warnings.map(describeWarning)]
119
+ }));
120
+ },
121
+ confirm(question) {
122
+ return new Promise((resolve) => {
123
+ store.answer = (value) => {
124
+ store.answer = null;
125
+ store.set({ question: null });
126
+ resolve(value);
127
+ };
128
+ store.set({ question });
129
+ });
130
+ },
131
+ progress(event) {
132
+ if (!("step" in event)) return;
133
+ const status = event.kind === "step-start" ? "running" : event.kind === "step-done" ? "done" : "failed";
134
+ store.set((prev) => ({
135
+ steps: prev.steps.map(
136
+ (entry) => entry.step.id === event.step.id ? { ...entry, status, detail: "detail" in event ? event.detail : entry.detail } : entry
137
+ )
138
+ }));
139
+ },
140
+ applied(_plan, opts) {
141
+ store.set({ outcome: { kind: "ok", durationMs: opts.durationMs } });
142
+ },
143
+ failed(_plan, opts) {
144
+ store.set({
145
+ outcome: { kind: "failed", message: opts.error.message, rolledBack: opts.rolledBack }
146
+ });
147
+ },
148
+ locks(report) {
149
+ store.set((prev) => ({
150
+ warnings: [
151
+ ...prev.warnings,
152
+ ...report.blocking.map(
153
+ (f) => `claude \uC138\uC158\uC774 \uC774 \uACBD\uB85C\uB97C \uC4F0\uACE0 \uC788\uC2B5\uB2C8\uB2E4 (pid ${f.session.pid}, ${f.session.cwd}). \uC138\uC158\uC744 \uB2EB\uACE0 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.`
154
+ )
155
+ ]
156
+ }));
157
+ },
158
+ doctor: (report) => plain.doctor(report),
159
+ list: (projects) => plain.list(projects),
160
+ info: (project) => plain.info(project),
161
+ backups: (items) => plain.backups(items),
162
+ note(message) {
163
+ if (store.state.plan) store.set((prev) => ({ notes: [...prev.notes, message] }));
164
+ else plain.note(message);
165
+ },
166
+ warn(message) {
167
+ if (store.state.plan) store.set((prev) => ({ warnings: [...prev.warnings, message] }));
168
+ else plain.warn(message);
169
+ },
170
+ async close() {
171
+ instance.unmount();
172
+ await instance.waitUntilExit();
173
+ }
174
+ };
175
+ }
176
+ function describeWarning(warning) {
177
+ switch (warning.kind) {
178
+ case "mixed-cwd":
179
+ return `\uD55C \uC138\uC158\uC774 \uC5EC\uB7EC \uACBD\uB85C\uB97C \uC624\uAC11\uB2C8\uB2E4. \uD30C\uC77C\uC740 \uC81C\uC790\uB9AC\uC5D0 \uB450\uACE0 \uD574\uB2F9 \uB808\uCF54\uB4DC\uB9CC \uACE0\uCE69\uB2C8\uB2E4: ${basename(warning.file)}`;
180
+ case "rewritten-in-parent-dir":
181
+ return `\uC0C1\uC704 \uD504\uB85C\uC81D\uD2B8\uC5D0 \uC18D\uD55C ${warning.files}\uAC1C \uD30C\uC77C\uC740 \uC62E\uAE30\uC9C0 \uC54A\uACE0 \uC81C\uC790\uB9AC\uC5D0\uC11C \uACE0\uCE69\uB2C8\uB2E4.`;
182
+ case "mangle-collision":
183
+ return `\uB300\uC0C1 \uACBD\uB85C\uAC00 \uB2E4\uB978 \uACBD\uB85C\uC640 \uAC19\uC740 \uB514\uB809\uD130\uB9AC \uC774\uB984\uC744 \uC501\uB2C8\uB2E4: ${warning.other}`;
184
+ case "skipped-live":
185
+ return `\uC138\uC158 ${warning.sessionId.slice(0, 8)} \uC740 \uC2E4\uD589 \uC911\uC774\uB77C \uBCF8\uCCB4 \uD2B8\uB79C\uC2A4\uD06C\uB9BD\uD2B8\uB97C \uAC74\uB4DC\uB9AC\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.`;
186
+ case "nothing-to-do":
187
+ return "\uC62E\uAE38 \uC0C1\uD0DC\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.";
188
+ }
189
+ }
190
+ function basename(path) {
191
+ return path.split(/[/\\]/).pop() ?? path;
192
+ }
193
+ export {
194
+ createInkReporter
195
+ };
package/package.json ADDED
@@ -0,0 +1,91 @@
1
+ {
2
+ "name": "@rhseung/claude-mv",
3
+ "version": "0.1.0",
4
+ "description": "프로젝트 디렉터리를 옮기면서 Claude Code 프로젝트 상태까지 같이 옮깁니다",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "rhseung <ryu@rhseung.me>",
8
+ "homepage": "https://github.com/rhseung/claude-mv#readme",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/rhseung/claude-mv.git"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/rhseung/claude-mv/issues"
15
+ },
16
+ "keywords": [
17
+ "claude",
18
+ "claude-code",
19
+ "cli",
20
+ "mv",
21
+ "rename",
22
+ "migration"
23
+ ],
24
+ "bin": {
25
+ "claude-mv": "dist/index.js"
26
+ },
27
+ "files": [
28
+ "dist",
29
+ "LICENSE"
30
+ ],
31
+ "engines": {
32
+ "node": ">=20"
33
+ },
34
+ "scripts": {
35
+ "build": "tsup",
36
+ "dev": "tsup --watch",
37
+ "format": "prettier --check .",
38
+ "check": "prettier --write . && eslint . --fix",
39
+ "lint": "eslint .",
40
+ "typecheck": "tsc --noEmit",
41
+ "test": "vitest run",
42
+ "test:watch": "vitest",
43
+ "sync:version": "bun scripts/sync-version.ts",
44
+ "verify": "bun run format && bun run lint && bun run typecheck && bun run test",
45
+ "version": "bun run sync:version && bun run build && git add plugin",
46
+ "prepublishOnly": "bun run build",
47
+ "prepare": "husky"
48
+ },
49
+ "dependencies": {
50
+ "@inkjs/ui": "^2.0.0",
51
+ "env-paths": "^3.0.0",
52
+ "execa": "^9.6.0",
53
+ "fastest-levenshtein": "^1.0.16",
54
+ "fs-extra": "^11.3.2",
55
+ "fzf": "^0.5.2",
56
+ "ink": "^5.2.1",
57
+ "jsonc-parser": "^3.3.1",
58
+ "p-limit": "^7.3.3",
59
+ "pretty-bytes": "^7.1.3",
60
+ "pretty-ms": "^9.3.1",
61
+ "proper-lockfile": "^4.1.2",
62
+ "ps-list": "^9.0.0",
63
+ "react": "^18.3.1",
64
+ "string-width": "^8.2.2",
65
+ "tar": "^7.5.7",
66
+ "ulid": "^3.0.2",
67
+ "write-file-atomic": "^8.0.0"
68
+ },
69
+ "devDependencies": {
70
+ "@eslint/js": "^10.0.1",
71
+ "@types/fs-extra": "^11.0.4",
72
+ "@types/node": "^26.2.0",
73
+ "@types/proper-lockfile": "^4.1.4",
74
+ "@types/react": "^18.3.12",
75
+ "@types/write-file-atomic": "^4.0.3",
76
+ "eslint": "^10.8.1",
77
+ "eslint-config-prettier": "^10.1.8",
78
+ "eslint-plugin-check-file": "^3.3.2",
79
+ "eslint-plugin-import-x": "^4.17.1",
80
+ "eslint-plugin-unused-imports": "^4.4.1",
81
+ "globals": "^17.9.0",
82
+ "husky": "^9.1.7",
83
+ "ink-testing-library": "^4.0.0",
84
+ "prettier": "^3.9.6",
85
+ "tempy": "^3.1.0",
86
+ "tsup": "^8.5.0",
87
+ "typescript": "^6.0.3",
88
+ "typescript-eslint": "^8.66.0",
89
+ "vitest": "^4.1.10"
90
+ }
91
+ }