apostil 0.1.6 → 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.
- package/README.md +8 -9
- package/dist/cli/index.cjs +252 -72
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +252 -72
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +38 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +38 -33
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -24,10 +24,10 @@ function parseMode(flags) {
|
|
|
24
24
|
}
|
|
25
25
|
function printHelp() {
|
|
26
26
|
console.log(`
|
|
27
|
-
apostil \u2014
|
|
27
|
+
apostil \u2014 Lightweight, Figma-like commenting for React
|
|
28
28
|
|
|
29
29
|
Usage:
|
|
30
|
-
npx apostil init [mode] Set up apostil in your
|
|
30
|
+
npx apostil init [mode] Set up apostil in your project
|
|
31
31
|
npx apostil remove Remove apostil from your project
|
|
32
32
|
npx apostil help Show this help
|
|
33
33
|
|
|
@@ -35,20 +35,54 @@ function printHelp() {
|
|
|
35
35
|
(default) Personal \u2014 local dev only, comments gitignored
|
|
36
36
|
--dev Dev + staging \u2014 comments gitignored, env-controlled
|
|
37
37
|
--public All environments \u2014 comments committed to git
|
|
38
|
+
|
|
39
|
+
Supported frameworks: Next.js (App Router), Vite + React
|
|
40
|
+
Framework is auto-detected from your project.
|
|
38
41
|
`);
|
|
39
42
|
}
|
|
43
|
+
async function detectFramework(cwd) {
|
|
44
|
+
for (const name of ["next.config.js", "next.config.ts", "next.config.mjs"]) {
|
|
45
|
+
if (await fileExists(path.join(cwd, name))) return "nextjs";
|
|
46
|
+
}
|
|
47
|
+
for (const name of ["vite.config.js", "vite.config.ts", "vite.config.mjs"]) {
|
|
48
|
+
if (await fileExists(path.join(cwd, name))) return "vite";
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
const pkg = JSON.parse(await fs.readFile(path.join(cwd, "package.json"), "utf-8"));
|
|
52
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
53
|
+
if (deps["next"]) return "nextjs";
|
|
54
|
+
if (deps["vite"]) return "vite";
|
|
55
|
+
} catch {
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
40
59
|
async function init(mode) {
|
|
41
60
|
const cwd = process.cwd();
|
|
61
|
+
const framework = await detectFramework(cwd);
|
|
62
|
+
if (!framework) {
|
|
63
|
+
console.log(" Could not detect your framework.");
|
|
64
|
+
console.log(" Apostil supports Next.js (App Router) and Vite + React.");
|
|
65
|
+
console.log(" Make sure you're running this from your project root.\n");
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
console.log(`
|
|
69
|
+
Detected: ${framework === "nextjs" ? "Next.js" : "Vite + React"}`);
|
|
70
|
+
if (framework === "nextjs") {
|
|
71
|
+
await initNextjs(cwd, mode);
|
|
72
|
+
} else {
|
|
73
|
+
await initVite(cwd, mode);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async function initNextjs(cwd, mode) {
|
|
42
77
|
const appDir = await findAppDir(cwd);
|
|
43
78
|
if (!appDir) {
|
|
44
79
|
console.log(" Could not find a Next.js app/ directory.");
|
|
45
|
-
console.log(" Make sure you're
|
|
80
|
+
console.log(" Make sure you're using the App Router.\n");
|
|
46
81
|
process.exit(1);
|
|
47
82
|
}
|
|
48
83
|
const useSrc = appDir.includes("src/app");
|
|
49
84
|
const modeLabel = mode === "personal" ? "personal" : mode === "dev" ? "dev" : "public";
|
|
50
|
-
console.log(`
|
|
51
|
-
Setting up apostil (${modeLabel} mode)...
|
|
85
|
+
console.log(` Setting up apostil (${modeLabel} mode)...
|
|
52
86
|
`);
|
|
53
87
|
const apiDir = path.join(appDir, "api", "apostil");
|
|
54
88
|
const apiFile = path.join(apiDir, "route.ts");
|
|
@@ -67,80 +101,37 @@ async function init(mode) {
|
|
|
67
101
|
const componentsDir = path.join(cwd, useSrc ? "src/components" : "components");
|
|
68
102
|
const wrapperFile = path.join(componentsDir, "apostil-wrapper.tsx");
|
|
69
103
|
await fs.mkdir(componentsDir, { recursive: true });
|
|
70
|
-
await fs.writeFile(wrapperFile,
|
|
104
|
+
await fs.writeFile(wrapperFile, getNextjsWrapper(mode), "utf-8");
|
|
71
105
|
console.log(` \u2713 Created ${rel(cwd, wrapperFile)} (${modeLabel} mode)`);
|
|
72
106
|
const commentsDir = path.join(cwd, ".apostil");
|
|
73
107
|
await fs.mkdir(commentsDir, { recursive: true });
|
|
74
108
|
console.log(" \u2713 Created .apostil/ directory");
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
try {
|
|
78
|
-
gitignore = await fs.readFile(gitignorePath, "utf-8");
|
|
79
|
-
} catch {
|
|
80
|
-
}
|
|
81
|
-
if (mode === "public") {
|
|
82
|
-
if (gitignore.includes(".apostil")) {
|
|
83
|
-
console.log(" \u2713 .gitignore unchanged (public mode \u2014 comments will be committed)");
|
|
84
|
-
} else {
|
|
85
|
-
console.log(" \u2713 Comments will be committed to git (public mode)");
|
|
86
|
-
}
|
|
87
|
-
} else {
|
|
88
|
-
if (!gitignore.includes(".apostil")) {
|
|
89
|
-
const entry = "\n# Apostil comments\n.apostil/\n";
|
|
90
|
-
await fs.appendFile(gitignorePath, entry, "utf-8");
|
|
91
|
-
console.log(" \u2713 Added .apostil/ to .gitignore");
|
|
92
|
-
} else {
|
|
93
|
-
console.log(" \u2713 .gitignore already configured");
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
const layoutInjected = await injectIntoLayout(appDir, useSrc);
|
|
109
|
+
await handleGitignore(cwd, mode);
|
|
110
|
+
const layoutInjected = await injectIntoNextjsLayout(appDir, useSrc);
|
|
97
111
|
if (layoutInjected) {
|
|
98
112
|
console.log(" \u2713 Added <ApostilWrapper> to root layout");
|
|
99
113
|
}
|
|
100
114
|
console.log("\n Done! Run your dev server and press C to start commenting.\n");
|
|
101
115
|
}
|
|
102
|
-
async function
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if (appDir) {
|
|
108
|
-
const apiDir = path.join(appDir, "api", "apostil");
|
|
109
|
-
if (await fileExists(path.join(apiDir, "route.ts"))) {
|
|
110
|
-
await fs.rm(apiDir, { recursive: true });
|
|
111
|
-
console.log(" \u2713 Removed API route");
|
|
112
|
-
}
|
|
113
|
-
}
|
|
116
|
+
async function initVite(cwd, mode) {
|
|
117
|
+
const useSrc = await fileExists(path.join(cwd, "src"));
|
|
118
|
+
const modeLabel = mode === "personal" ? "personal" : mode === "dev" ? "dev" : "public";
|
|
119
|
+
console.log(` Setting up apostil (${modeLabel} mode)...
|
|
120
|
+
`);
|
|
114
121
|
const componentsDir = path.join(cwd, useSrc ? "src/components" : "components");
|
|
115
122
|
const wrapperFile = path.join(componentsDir, "apostil-wrapper.tsx");
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
const commentsDir = path.join(cwd, ".apostil");
|
|
127
|
-
if (await fileExists(commentsDir)) {
|
|
128
|
-
await fs.rm(commentsDir, { recursive: true });
|
|
129
|
-
console.log(" \u2713 Removed .apostil/ directory");
|
|
130
|
-
}
|
|
131
|
-
const gitignorePath = path.join(cwd, ".gitignore");
|
|
132
|
-
try {
|
|
133
|
-
let gitignore = await fs.readFile(gitignorePath, "utf-8");
|
|
134
|
-
gitignore = gitignore.replace(/\n?# Apostil comments\n\.apostil\/\n?/g, "");
|
|
135
|
-
await fs.writeFile(gitignorePath, gitignore, "utf-8");
|
|
136
|
-
console.log(" \u2713 Cleaned .gitignore");
|
|
137
|
-
} catch {
|
|
123
|
+
await fs.mkdir(componentsDir, { recursive: true });
|
|
124
|
+
const hasRouter = await hasReactRouter(cwd);
|
|
125
|
+
await fs.writeFile(wrapperFile, getViteWrapper(mode, hasRouter), "utf-8");
|
|
126
|
+
console.log(` \u2713 Created ${rel(cwd, wrapperFile)} (${modeLabel} mode)`);
|
|
127
|
+
console.log(" \u2713 Using localStorage adapter (comments stored in browser)");
|
|
128
|
+
const entryInjected = await injectIntoViteEntry(cwd, useSrc);
|
|
129
|
+
if (entryInjected) {
|
|
130
|
+
console.log(" \u2713 Added <ApostilWrapper> to app entry");
|
|
138
131
|
}
|
|
139
|
-
console.log(
|
|
140
|
-
Done! Now run: npm uninstall apostil
|
|
141
|
-
`);
|
|
132
|
+
console.log("\n Done! Run your dev server and press C to start commenting.\n");
|
|
142
133
|
}
|
|
143
|
-
function
|
|
134
|
+
function getNextjsWrapper(mode) {
|
|
144
135
|
const envGuard = mode === "personal" ? `
|
|
145
136
|
// Personal mode \u2014 only active in local development
|
|
146
137
|
if (process.env.NODE_ENV !== "development") {
|
|
@@ -186,7 +177,86 @@ export function ApostilWrapper({ children }: { children: React.ReactNode }) {${e
|
|
|
186
177
|
}
|
|
187
178
|
`;
|
|
188
179
|
}
|
|
189
|
-
async function
|
|
180
|
+
async function hasReactRouter(cwd) {
|
|
181
|
+
try {
|
|
182
|
+
const pkg = JSON.parse(await fs.readFile(path.join(cwd, "package.json"), "utf-8"));
|
|
183
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
184
|
+
return !!deps["react-router-dom"] || !!deps["react-router"];
|
|
185
|
+
} catch {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function getViteWrapper(mode, hasRouter) {
|
|
190
|
+
const envGuard = mode === "personal" ? `
|
|
191
|
+
// Personal mode \u2014 only active in local development
|
|
192
|
+
if (import.meta.env.PROD) {
|
|
193
|
+
return <>{children}</>;
|
|
194
|
+
}
|
|
195
|
+
` : mode === "dev" ? `
|
|
196
|
+
// Dev mode \u2014 active in dev + staging, disabled in production
|
|
197
|
+
// Set VITE_APOSTIL=true to force on in any environment
|
|
198
|
+
const forceOn = import.meta.env.VITE_APOSTIL === "true";
|
|
199
|
+
if (import.meta.env.PROD && !forceOn) {
|
|
200
|
+
return <>{children}</>;
|
|
201
|
+
}
|
|
202
|
+
` : `
|
|
203
|
+
// Public mode \u2014 active in all environments
|
|
204
|
+
// Set VITE_APOSTIL=false to disable
|
|
205
|
+
if (import.meta.env.VITE_APOSTIL === "false") {
|
|
206
|
+
return <>{children}</>;
|
|
207
|
+
}
|
|
208
|
+
`;
|
|
209
|
+
const routerImport = hasRouter ? `import { useLocation } from "react-router-dom";
|
|
210
|
+
` : "";
|
|
211
|
+
const pageIdLogic = hasRouter ? ` const location = useLocation();
|
|
212
|
+
const pageId = location.pathname.replace(/\\//g, "--").replace(/^--/, "") || "home";` : ` const pageId = window.location.pathname.replace(/\\//g, "--").replace(/^--/, "") || "home";`;
|
|
213
|
+
return `${routerImport}import {
|
|
214
|
+
ApostilProvider,
|
|
215
|
+
CommentOverlay,
|
|
216
|
+
CommentToggle,
|
|
217
|
+
CommentSidebar,
|
|
218
|
+
} from "apostil";
|
|
219
|
+
import { localStorageAdapter } from "apostil/adapters/localStorage";
|
|
220
|
+
import "apostil/styles.css";
|
|
221
|
+
|
|
222
|
+
export function ApostilWrapper({ children }: { children: React.ReactNode }) {${envGuard}
|
|
223
|
+
${pageIdLogic}
|
|
224
|
+
|
|
225
|
+
return (
|
|
226
|
+
<ApostilProvider pageId={pageId} storage={localStorageAdapter}>
|
|
227
|
+
{children}
|
|
228
|
+
<CommentOverlay />
|
|
229
|
+
<CommentSidebar />
|
|
230
|
+
<CommentToggle />
|
|
231
|
+
</ApostilProvider>
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
`;
|
|
235
|
+
}
|
|
236
|
+
async function handleGitignore(cwd, mode) {
|
|
237
|
+
const gitignorePath = path.join(cwd, ".gitignore");
|
|
238
|
+
let gitignore = "";
|
|
239
|
+
try {
|
|
240
|
+
gitignore = await fs.readFile(gitignorePath, "utf-8");
|
|
241
|
+
} catch {
|
|
242
|
+
}
|
|
243
|
+
if (mode === "public") {
|
|
244
|
+
if (gitignore.includes(".apostil")) {
|
|
245
|
+
console.log(" \u2713 .gitignore unchanged (public mode \u2014 comments will be committed)");
|
|
246
|
+
} else {
|
|
247
|
+
console.log(" \u2713 Comments will be committed to git (public mode)");
|
|
248
|
+
}
|
|
249
|
+
} else {
|
|
250
|
+
if (!gitignore.includes(".apostil")) {
|
|
251
|
+
const entry = "\n# Apostil comments\n.apostil/\n";
|
|
252
|
+
await fs.appendFile(gitignorePath, entry, "utf-8");
|
|
253
|
+
console.log(" \u2713 Added .apostil/ to .gitignore");
|
|
254
|
+
} else {
|
|
255
|
+
console.log(" \u2713 .gitignore already configured");
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
async function injectIntoNextjsLayout(appDir, useSrc) {
|
|
190
260
|
const layoutPath = await findLayout(appDir);
|
|
191
261
|
if (!layoutPath) return false;
|
|
192
262
|
let content = await fs.readFile(layoutPath, "utf-8");
|
|
@@ -229,14 +299,124 @@ async function injectIntoLayout(appDir, useSrc) {
|
|
|
229
299
|
await fs.writeFile(layoutPath, content, "utf-8");
|
|
230
300
|
return true;
|
|
231
301
|
}
|
|
232
|
-
async function
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
302
|
+
async function injectIntoViteEntry(cwd, useSrc) {
|
|
303
|
+
const baseDir = useSrc ? path.join(cwd, "src") : cwd;
|
|
304
|
+
for (const ext of ["tsx", "jsx"]) {
|
|
305
|
+
const mainFile = path.join(baseDir, `main.${ext}`);
|
|
306
|
+
if (await fileExists(mainFile)) {
|
|
307
|
+
return await injectIntoViteMain(mainFile);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
console.log(" \u26A0 Could not find main.tsx or main.jsx \u2014 add <ApostilWrapper> manually");
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
async function injectIntoViteMain(mainFile, wrapperFile) {
|
|
314
|
+
let content = await fs.readFile(mainFile, "utf-8");
|
|
315
|
+
if (content.includes("ApostilWrapper")) {
|
|
316
|
+
console.log(" \u2713 Entry already has <ApostilWrapper>");
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
const mainDir = path.dirname(mainFile);
|
|
320
|
+
const wrapperPath = wrapperFile ?? path.join(mainDir, "components", "apostil-wrapper.tsx");
|
|
321
|
+
let relativePath = path.relative(mainDir, wrapperPath).replace(/\.tsx$/, "");
|
|
322
|
+
if (!relativePath.startsWith(".")) relativePath = "./" + relativePath;
|
|
323
|
+
const importLine = `import { ApostilWrapper } from "${relativePath}";
|
|
324
|
+
`;
|
|
325
|
+
const importRegex = /^import\s.+$/gm;
|
|
326
|
+
let lastImportIndex = 0;
|
|
327
|
+
let match;
|
|
328
|
+
while ((match = importRegex.exec(content)) !== null) {
|
|
329
|
+
lastImportIndex = match.index + match[0].length;
|
|
330
|
+
}
|
|
331
|
+
if (lastImportIndex > 0) {
|
|
332
|
+
content = content.slice(0, lastImportIndex) + "\n" + importLine + content.slice(lastImportIndex);
|
|
333
|
+
} else {
|
|
334
|
+
content = importLine + content;
|
|
335
|
+
}
|
|
336
|
+
content = content.replace(/<App\s*\/>/, `<ApostilWrapper><App /></ApostilWrapper>`);
|
|
337
|
+
await fs.writeFile(mainFile, content, "utf-8");
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
async function remove() {
|
|
341
|
+
const cwd = process.cwd();
|
|
342
|
+
const framework = await detectFramework(cwd);
|
|
343
|
+
console.log("\n Removing apostil...\n");
|
|
344
|
+
if (framework === "nextjs") {
|
|
345
|
+
await removeNextjs(cwd);
|
|
346
|
+
} else if (framework === "vite") {
|
|
347
|
+
await removeVite(cwd);
|
|
348
|
+
} else {
|
|
349
|
+
await removeNextjs(cwd);
|
|
350
|
+
await removeVite(cwd);
|
|
351
|
+
}
|
|
352
|
+
const commentsDir = path.join(cwd, ".apostil");
|
|
353
|
+
if (await fileExists(commentsDir)) {
|
|
354
|
+
await fs.rm(commentsDir, { recursive: true });
|
|
355
|
+
console.log(" \u2713 Removed .apostil/ directory");
|
|
356
|
+
}
|
|
357
|
+
const gitignorePath = path.join(cwd, ".gitignore");
|
|
358
|
+
try {
|
|
359
|
+
let gitignore = await fs.readFile(gitignorePath, "utf-8");
|
|
360
|
+
gitignore = gitignore.replace(/\n?# Apostil comments\n\.apostil\/\n?/g, "");
|
|
361
|
+
await fs.writeFile(gitignorePath, gitignore, "utf-8");
|
|
362
|
+
console.log(" \u2713 Cleaned .gitignore");
|
|
363
|
+
} catch {
|
|
364
|
+
}
|
|
365
|
+
console.log(`
|
|
366
|
+
Done! Now run: npm uninstall apostil
|
|
367
|
+
`);
|
|
368
|
+
}
|
|
369
|
+
async function removeNextjs(cwd) {
|
|
370
|
+
const appDir = await findAppDir(cwd);
|
|
371
|
+
const useSrc = appDir?.includes("src/app") ?? false;
|
|
372
|
+
if (appDir) {
|
|
373
|
+
const apiDir = path.join(appDir, "api", "apostil");
|
|
374
|
+
if (await fileExists(path.join(apiDir, "route.ts"))) {
|
|
375
|
+
await fs.rm(apiDir, { recursive: true });
|
|
376
|
+
console.log(" \u2713 Removed API route");
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
const componentsDir = path.join(cwd, useSrc ? "src/components" : "components");
|
|
380
|
+
const wrapperFile = path.join(componentsDir, "apostil-wrapper.tsx");
|
|
381
|
+
if (await fileExists(wrapperFile)) {
|
|
382
|
+
await fs.rm(wrapperFile);
|
|
383
|
+
console.log(" \u2713 Removed wrapper component");
|
|
384
|
+
}
|
|
385
|
+
if (appDir) {
|
|
386
|
+
const layoutPath = await findLayout(appDir);
|
|
387
|
+
if (layoutPath) {
|
|
388
|
+
const unwrapped = await removeWrapperFromFile(layoutPath);
|
|
389
|
+
if (unwrapped) {
|
|
390
|
+
console.log(" \u2713 Removed <ApostilWrapper> from root layout");
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
async function removeVite(cwd) {
|
|
396
|
+
const useSrc = await fileExists(path.join(cwd, "src"));
|
|
397
|
+
const baseDir = useSrc ? path.join(cwd, "src") : cwd;
|
|
398
|
+
const componentsDir = path.join(cwd, useSrc ? "src/components" : "components");
|
|
399
|
+
const wrapperFile = path.join(componentsDir, "apostil-wrapper.tsx");
|
|
400
|
+
if (await fileExists(wrapperFile)) {
|
|
401
|
+
await fs.rm(wrapperFile);
|
|
402
|
+
console.log(" \u2713 Removed wrapper component");
|
|
403
|
+
}
|
|
404
|
+
for (const name of ["App.tsx", "App.jsx", "main.tsx", "main.jsx"]) {
|
|
405
|
+
const file = path.join(baseDir, name);
|
|
406
|
+
if (await fileExists(file)) {
|
|
407
|
+
const unwrapped = await removeWrapperFromFile(file);
|
|
408
|
+
if (unwrapped) {
|
|
409
|
+
console.log(` \u2713 Removed <ApostilWrapper> from ${name}`);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
async function removeWrapperFromFile(filePath) {
|
|
415
|
+
let content = await fs.readFile(filePath, "utf-8");
|
|
236
416
|
if (!content.includes("ApostilWrapper")) return false;
|
|
237
417
|
content = content.replace(/import\s*\{[^}]*ApostilWrapper[^}]*\}\s*from\s*["'][^"']+["'];?\n?/g, "");
|
|
238
418
|
content = content.replace(/<ApostilWrapper>([\s\S]*?)<\/ApostilWrapper>/g, "$1");
|
|
239
|
-
await fs.writeFile(
|
|
419
|
+
await fs.writeFile(filePath, content, "utf-8");
|
|
240
420
|
return true;
|
|
241
421
|
}
|
|
242
422
|
async function findAppDir(cwd) {
|
package/dist/cli/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport fs from \"fs/promises\";\nimport path from \"path\";\n\ntype Mode = \"personal\" | \"dev\" | \"public\";\n\nconst args = process.argv.slice(2);\nconst command = args[0];\n\nif (command === \"init\") {\n const mode = parseMode(args.slice(1));\n init(mode);\n} else if (command === \"remove\") {\n remove();\n} else if (command === \"help\" || command === \"--help\" || command === \"-h\" || !command) {\n printHelp();\n} else {\n console.log(` Unknown command: ${command}\\n`);\n printHelp();\n}\n\nfunction parseMode(flags: string[]): Mode {\n if (flags.includes(\"--dev\")) return \"dev\";\n if (flags.includes(\"--public\")) return \"public\";\n return \"personal\";\n}\n\nfunction printHelp() {\n console.log(`\n apostil — Pin-and-comment feedback for React & Next.js\n\n Usage:\n npx apostil init [mode] Set up apostil in your Next.js project\n npx apostil remove Remove apostil from your project\n npx apostil help Show this help\n\n Modes:\n (default) Personal — local dev only, comments gitignored\n --dev Dev + staging — comments gitignored, env-controlled\n --public All environments — comments committed to git\n`);\n}\n\nasync function init(mode: Mode) {\n const cwd = process.cwd();\n\n // Detect Next.js app directory\n const appDir = await findAppDir(cwd);\n if (!appDir) {\n console.log(\" Could not find a Next.js app/ directory.\");\n console.log(\" Make sure you're running this from your project root.\\n\");\n process.exit(1);\n }\n\n // Detect src/ prefix\n const useSrc = appDir.includes(\"src/app\");\n\n const modeLabel = mode === \"personal\" ? \"personal\" : mode === \"dev\" ? \"dev\" : \"public\";\n console.log(`\\n Setting up apostil (${modeLabel} mode)...\\n`);\n\n // 1. Create API route\n const apiDir = path.join(appDir, \"api\", \"apostil\");\n const apiFile = path.join(apiDir, \"route.ts\");\n if (await fileExists(apiFile)) {\n console.log(\" ✓ API route already exists\");\n } else {\n await fs.mkdir(apiDir, { recursive: true });\n await fs.writeFile(\n apiFile,\n `export { GET, POST } from \"apostil/adapters/nextjs\";\\n`,\n \"utf-8\"\n );\n console.log(` ✓ Created ${rel(cwd, apiFile)}`);\n }\n\n // 2. Create wrapper component\n const componentsDir = path.join(cwd, useSrc ? \"src/components\" : \"components\");\n const wrapperFile = path.join(componentsDir, \"apostil-wrapper.tsx\");\n await fs.mkdir(componentsDir, { recursive: true });\n await fs.writeFile(wrapperFile, getWrapperComponent(mode), \"utf-8\");\n console.log(` ✓ Created ${rel(cwd, wrapperFile)} (${modeLabel} mode)`);\n\n // 3. Create .apostil/ directory for comment storage\n const commentsDir = path.join(cwd, \".apostil\");\n await fs.mkdir(commentsDir, { recursive: true });\n console.log(\" ✓ Created .apostil/ directory\");\n\n // 4. Handle .gitignore based on mode\n const gitignorePath = path.join(cwd, \".gitignore\");\n let gitignore = \"\";\n try {\n gitignore = await fs.readFile(gitignorePath, \"utf-8\");\n } catch {}\n\n if (mode === \"public\") {\n // Public mode: don't gitignore comments — they get committed\n if (gitignore.includes(\".apostil\")) {\n console.log(\" ✓ .gitignore unchanged (public mode — comments will be committed)\");\n } else {\n console.log(\" ✓ Comments will be committed to git (public mode)\");\n }\n } else {\n // Personal & dev: gitignore comments\n if (!gitignore.includes(\".apostil\")) {\n const entry = \"\\n# Apostil comments\\n.apostil/\\n\";\n await fs.appendFile(gitignorePath, entry, \"utf-8\");\n console.log(\" ✓ Added .apostil/ to .gitignore\");\n } else {\n console.log(\" ✓ .gitignore already configured\");\n }\n }\n\n // 5. Inject wrapper into root layout\n const layoutInjected = await injectIntoLayout(appDir, useSrc);\n if (layoutInjected) {\n console.log(\" ✓ Added <ApostilWrapper> to root layout\");\n }\n\n console.log(\"\\n Done! Run your dev server and press C to start commenting.\\n\");\n}\n\nasync function remove() {\n const cwd = process.cwd();\n const appDir = await findAppDir(cwd);\n const useSrc = appDir?.includes(\"src/app\") ?? false;\n\n console.log(\"\\n Removing apostil...\\n\");\n\n // 1. Remove API route\n if (appDir) {\n const apiDir = path.join(appDir, \"api\", \"apostil\");\n if (await fileExists(path.join(apiDir, \"route.ts\"))) {\n await fs.rm(apiDir, { recursive: true });\n console.log(\" ✓ Removed API route\");\n }\n }\n\n // 2. Remove wrapper component\n const componentsDir = path.join(cwd, useSrc ? \"src/components\" : \"components\");\n const wrapperFile = path.join(componentsDir, \"apostil-wrapper.tsx\");\n if (await fileExists(wrapperFile)) {\n await fs.rm(wrapperFile);\n console.log(\" ✓ Removed wrapper component\");\n }\n\n // 3. Remove wrapper from layout\n if (appDir) {\n const unwrapped = await removeFromLayout(appDir);\n if (unwrapped) {\n console.log(\" ✓ Removed <ApostilWrapper> from root layout\");\n }\n }\n\n // 4. Remove .apostil/ directory\n const commentsDir = path.join(cwd, \".apostil\");\n if (await fileExists(commentsDir)) {\n await fs.rm(commentsDir, { recursive: true });\n console.log(\" ✓ Removed .apostil/ directory\");\n }\n\n // 5. Remove from .gitignore\n const gitignorePath = path.join(cwd, \".gitignore\");\n try {\n let gitignore = await fs.readFile(gitignorePath, \"utf-8\");\n gitignore = gitignore.replace(/\\n?# Apostil comments\\n\\.apostil\\/\\n?/g, \"\");\n await fs.writeFile(gitignorePath, gitignore, \"utf-8\");\n console.log(\" ✓ Cleaned .gitignore\");\n } catch {}\n\n console.log(`\n Done! Now run: npm uninstall apostil\n`);\n}\n\n// --- Wrapper component template ---\n\nfunction getWrapperComponent(mode: Mode): string {\n const envGuard = mode === \"personal\"\n ? `\n // Personal mode — only active in local development\n if (process.env.NODE_ENV !== \"development\") {\n return <>{children}</>;\n }\n`\n : mode === \"dev\"\n ? `\n // Dev mode — active in dev + staging, disabled in production\n // Set NEXT_PUBLIC_APOSTIL=true to force on in any environment\n const forceOn = process.env.NEXT_PUBLIC_APOSTIL === \"true\";\n if (process.env.NODE_ENV === \"production\" && !forceOn) {\n return <>{children}</>;\n }\n`\n : `\n // Public mode — active in all environments\n // Set NEXT_PUBLIC_APOSTIL=false to disable\n if (process.env.NEXT_PUBLIC_APOSTIL === \"false\") {\n return <>{children}</>;\n }\n`;\n\n return `\"use client\";\n\nimport { usePathname } from \"next/navigation\";\nimport {\n ApostilProvider,\n CommentOverlay,\n CommentToggle,\n CommentSidebar,\n} from \"apostil\";\nimport \"apostil/styles.css\";\n\nexport function ApostilWrapper({ children }: { children: React.ReactNode }) {${envGuard}\n const pathname = usePathname();\n const pageId = pathname.replace(/\\\\//g, \"--\").replace(/^--/, \"\") || \"home\";\n\n return (\n <ApostilProvider pageId={pageId}>\n {children}\n <CommentOverlay />\n <CommentSidebar />\n <CommentToggle />\n </ApostilProvider>\n );\n}\n`;\n}\n\n// --- Layout injection ---\n\nasync function injectIntoLayout(appDir: string, useSrc: boolean): Promise<boolean> {\n const layoutPath = await findLayout(appDir);\n if (!layoutPath) return false;\n\n let content = await fs.readFile(layoutPath, \"utf-8\");\n\n // Skip if already injected\n if (content.includes(\"ApostilWrapper\")) {\n console.log(\" ✓ Layout already has <ApostilWrapper>\");\n return false;\n }\n\n // Add import at the top (after existing imports or \"use\" directives)\n const importPath = useSrc ? \"@/components/apostil-wrapper\" : \"../components/apostil-wrapper\";\n const importLine = `import { ApostilWrapper } from \"${importPath}\";\\n`;\n\n // Find the last import statement and insert after it\n const importRegex = /^import\\s.+$/gm;\n let lastImportIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = importRegex.exec(content)) !== null) {\n lastImportIndex = match.index + match[0].length;\n }\n\n if (lastImportIndex > 0) {\n content = content.slice(0, lastImportIndex) + \"\\n\" + importLine + content.slice(lastImportIndex);\n } else {\n // No imports found, add at the top (after \"use client\" or \"use server\" if present)\n const useDirective = content.match(/^[\"']use (client|server)[\"'];?\\n/);\n const insertAt = useDirective ? useDirective[0].length : 0;\n content = content.slice(0, insertAt) + importLine + content.slice(insertAt);\n }\n\n // Wrap {children} with <ApostilWrapper>\n // Handle both {children} and { children } patterns inside <body>\n const bodyChildrenRegex = /(<body[^>]*>)([\\s\\S]*?)(\\{[\\s]*children[\\s]*\\})([\\s\\S]*?)(<\\/body>)/;\n const bodyMatch = content.match(bodyChildrenRegex);\n\n if (bodyMatch) {\n content = content.replace(\n bodyChildrenRegex,\n `$1$2<ApostilWrapper>$3</ApostilWrapper>$4$5`\n );\n } else {\n // Fallback: try to find {children} anywhere and wrap it\n const childrenRegex = /(\\{[\\s]*children[\\s]*\\})/;\n if (childrenRegex.test(content)) {\n content = content.replace(childrenRegex, `<ApostilWrapper>$1</ApostilWrapper>`);\n } else {\n console.log(\" ⚠ Could not find {children} in layout — add <ApostilWrapper> manually\");\n return false;\n }\n }\n\n await fs.writeFile(layoutPath, content, \"utf-8\");\n return true;\n}\n\nasync function removeFromLayout(appDir: string): Promise<boolean> {\n const layoutPath = await findLayout(appDir);\n if (!layoutPath) return false;\n\n let content = await fs.readFile(layoutPath, \"utf-8\");\n\n if (!content.includes(\"ApostilWrapper\")) return false;\n\n // Remove import line\n content = content.replace(/import\\s*\\{[^}]*ApostilWrapper[^}]*\\}\\s*from\\s*[\"'][^\"']+[\"'];?\\n?/g, \"\");\n\n // Unwrap <ApostilWrapper>{children}</ApostilWrapper>\n content = content.replace(/<ApostilWrapper>([\\s\\S]*?)<\\/ApostilWrapper>/g, \"$1\");\n\n await fs.writeFile(layoutPath, content, \"utf-8\");\n return true;\n}\n\n// --- Helpers ---\n\nasync function findAppDir(cwd: string): Promise<string | null> {\n for (const candidate of [\"src/app\", \"app\"]) {\n const dir = path.join(cwd, candidate);\n try {\n const stat = await fs.stat(dir);\n if (stat.isDirectory()) return dir;\n } catch {}\n }\n return null;\n}\n\nasync function findLayout(appDir: string): Promise<string | null> {\n for (const ext of [\"tsx\", \"jsx\", \"ts\", \"js\"]) {\n const file = path.join(appDir, `layout.${ext}`);\n if (await fileExists(file)) return file;\n }\n return null;\n}\n\nasync function fileExists(p: string): Promise<boolean> {\n try {\n await fs.stat(p);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction rel(cwd: string, filePath: string): string {\n return path.relative(cwd, filePath);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport fs from \"fs/promises\";\nimport path from \"path\";\n\ntype Mode = \"personal\" | \"dev\" | \"public\";\ntype Framework = \"nextjs\" | \"vite\";\n\nconst args = process.argv.slice(2);\nconst command = args[0];\n\nif (command === \"init\") {\n const mode = parseMode(args.slice(1));\n init(mode);\n} else if (command === \"remove\") {\n remove();\n} else if (command === \"help\" || command === \"--help\" || command === \"-h\" || !command) {\n printHelp();\n} else {\n console.log(` Unknown command: ${command}\\n`);\n printHelp();\n}\n\nfunction parseMode(flags: string[]): Mode {\n if (flags.includes(\"--dev\")) return \"dev\";\n if (flags.includes(\"--public\")) return \"public\";\n return \"personal\";\n}\n\nfunction printHelp() {\n console.log(`\n apostil — Lightweight, Figma-like commenting for React\n\n Usage:\n npx apostil init [mode] Set up apostil in your project\n npx apostil remove Remove apostil from your project\n npx apostil help Show this help\n\n Modes:\n (default) Personal — local dev only, comments gitignored\n --dev Dev + staging — comments gitignored, env-controlled\n --public All environments — comments committed to git\n\n Supported frameworks: Next.js (App Router), Vite + React\n Framework is auto-detected from your project.\n`);\n}\n\n// --- Framework detection ---\n\nasync function detectFramework(cwd: string): Promise<Framework | null> {\n // Check config files first\n for (const name of [\"next.config.js\", \"next.config.ts\", \"next.config.mjs\"]) {\n if (await fileExists(path.join(cwd, name))) return \"nextjs\";\n }\n for (const name of [\"vite.config.js\", \"vite.config.ts\", \"vite.config.mjs\"]) {\n if (await fileExists(path.join(cwd, name))) return \"vite\";\n }\n\n // Fallback: check package.json dependencies\n try {\n const pkg = JSON.parse(await fs.readFile(path.join(cwd, \"package.json\"), \"utf-8\"));\n const deps = { ...pkg.dependencies, ...pkg.devDependencies };\n if (deps[\"next\"]) return \"nextjs\";\n if (deps[\"vite\"]) return \"vite\";\n } catch {}\n\n return null;\n}\n\n// --- Init ---\n\nasync function init(mode: Mode) {\n const cwd = process.cwd();\n const framework = await detectFramework(cwd);\n\n if (!framework) {\n console.log(\" Could not detect your framework.\");\n console.log(\" Apostil supports Next.js (App Router) and Vite + React.\");\n console.log(\" Make sure you're running this from your project root.\\n\");\n process.exit(1);\n }\n\n console.log(`\\n Detected: ${framework === \"nextjs\" ? \"Next.js\" : \"Vite + React\"}`);\n\n if (framework === \"nextjs\") {\n await initNextjs(cwd, mode);\n } else {\n await initVite(cwd, mode);\n }\n}\n\n// --- Next.js init ---\n\nasync function initNextjs(cwd: string, mode: Mode) {\n const appDir = await findAppDir(cwd);\n if (!appDir) {\n console.log(\" Could not find a Next.js app/ directory.\");\n console.log(\" Make sure you're using the App Router.\\n\");\n process.exit(1);\n }\n\n const useSrc = appDir.includes(\"src/app\");\n const modeLabel = mode === \"personal\" ? \"personal\" : mode === \"dev\" ? \"dev\" : \"public\";\n console.log(` Setting up apostil (${modeLabel} mode)...\\n`);\n\n // 1. Create API route\n const apiDir = path.join(appDir, \"api\", \"apostil\");\n const apiFile = path.join(apiDir, \"route.ts\");\n if (await fileExists(apiFile)) {\n console.log(\" ✓ API route already exists\");\n } else {\n await fs.mkdir(apiDir, { recursive: true });\n await fs.writeFile(\n apiFile,\n `export { GET, POST } from \"apostil/adapters/nextjs\";\\n`,\n \"utf-8\"\n );\n console.log(` ✓ Created ${rel(cwd, apiFile)}`);\n }\n\n // 2. Create wrapper component\n const componentsDir = path.join(cwd, useSrc ? \"src/components\" : \"components\");\n const wrapperFile = path.join(componentsDir, \"apostil-wrapper.tsx\");\n await fs.mkdir(componentsDir, { recursive: true });\n await fs.writeFile(wrapperFile, getNextjsWrapper(mode), \"utf-8\");\n console.log(` ✓ Created ${rel(cwd, wrapperFile)} (${modeLabel} mode)`);\n\n // 3. Create .apostil/ directory\n const commentsDir = path.join(cwd, \".apostil\");\n await fs.mkdir(commentsDir, { recursive: true });\n console.log(\" ✓ Created .apostil/ directory\");\n\n // 4. Handle .gitignore\n await handleGitignore(cwd, mode);\n\n // 5. Inject wrapper into root layout\n const layoutInjected = await injectIntoNextjsLayout(appDir, useSrc);\n if (layoutInjected) {\n console.log(\" ✓ Added <ApostilWrapper> to root layout\");\n }\n\n console.log(\"\\n Done! Run your dev server and press C to start commenting.\\n\");\n}\n\n// --- Vite init ---\n\nasync function initVite(cwd: string, mode: Mode) {\n const useSrc = await fileExists(path.join(cwd, \"src\"));\n const modeLabel = mode === \"personal\" ? \"personal\" : mode === \"dev\" ? \"dev\" : \"public\";\n console.log(` Setting up apostil (${modeLabel} mode)...\\n`);\n\n // 1. Create wrapper component\n const componentsDir = path.join(cwd, useSrc ? \"src/components\" : \"components\");\n const wrapperFile = path.join(componentsDir, \"apostil-wrapper.tsx\");\n await fs.mkdir(componentsDir, { recursive: true });\n const hasRouter = await hasReactRouter(cwd);\n await fs.writeFile(wrapperFile, getViteWrapper(mode, hasRouter), \"utf-8\");\n console.log(` ✓ Created ${rel(cwd, wrapperFile)} (${modeLabel} mode)`);\n\n console.log(\" ✓ Using localStorage adapter (comments stored in browser)\");\n\n // 3. Inject wrapper into entry point\n const entryInjected = await injectIntoViteEntry(cwd, useSrc);\n if (entryInjected) {\n console.log(\" ✓ Added <ApostilWrapper> to app entry\");\n }\n\n console.log(\"\\n Done! Run your dev server and press C to start commenting.\\n\");\n}\n\n// --- Wrapper templates ---\n\nfunction getNextjsWrapper(mode: Mode): string {\n const envGuard = mode === \"personal\"\n ? `\n // Personal mode — only active in local development\n if (process.env.NODE_ENV !== \"development\") {\n return <>{children}</>;\n }\n`\n : mode === \"dev\"\n ? `\n // Dev mode — active in dev + staging, disabled in production\n // Set NEXT_PUBLIC_APOSTIL=true to force on in any environment\n const forceOn = process.env.NEXT_PUBLIC_APOSTIL === \"true\";\n if (process.env.NODE_ENV === \"production\" && !forceOn) {\n return <>{children}</>;\n }\n`\n : `\n // Public mode — active in all environments\n // Set NEXT_PUBLIC_APOSTIL=false to disable\n if (process.env.NEXT_PUBLIC_APOSTIL === \"false\") {\n return <>{children}</>;\n }\n`;\n\n return `\"use client\";\n\nimport { usePathname } from \"next/navigation\";\nimport {\n ApostilProvider,\n CommentOverlay,\n CommentToggle,\n CommentSidebar,\n} from \"apostil\";\nimport \"apostil/styles.css\";\n\nexport function ApostilWrapper({ children }: { children: React.ReactNode }) {${envGuard}\n const pathname = usePathname();\n const pageId = pathname.replace(/\\\\//g, \"--\").replace(/^--/, \"\") || \"home\";\n\n return (\n <ApostilProvider pageId={pageId}>\n {children}\n <CommentOverlay />\n <CommentSidebar />\n <CommentToggle />\n </ApostilProvider>\n );\n}\n`;\n}\n\nasync function hasReactRouter(cwd: string): Promise<boolean> {\n try {\n const pkg = JSON.parse(await fs.readFile(path.join(cwd, \"package.json\"), \"utf-8\"));\n const deps = { ...pkg.dependencies, ...pkg.devDependencies };\n return !!deps[\"react-router-dom\"] || !!deps[\"react-router\"];\n } catch {\n return false;\n }\n}\n\n// No \"use client\" directive — Vite doesn't use React Server Components\nfunction getViteWrapper(mode: Mode, hasRouter: boolean): string {\n const envGuard = mode === \"personal\"\n ? `\n // Personal mode — only active in local development\n if (import.meta.env.PROD) {\n return <>{children}</>;\n }\n`\n : mode === \"dev\"\n ? `\n // Dev mode — active in dev + staging, disabled in production\n // Set VITE_APOSTIL=true to force on in any environment\n const forceOn = import.meta.env.VITE_APOSTIL === \"true\";\n if (import.meta.env.PROD && !forceOn) {\n return <>{children}</>;\n }\n`\n : `\n // Public mode — active in all environments\n // Set VITE_APOSTIL=false to disable\n if (import.meta.env.VITE_APOSTIL === \"false\") {\n return <>{children}</>;\n }\n`;\n\n const routerImport = hasRouter\n ? `import { useLocation } from \"react-router-dom\";\\n`\n : \"\";\n\n const pageIdLogic = hasRouter\n ? ` const location = useLocation();\n const pageId = location.pathname.replace(/\\\\//g, \"--\").replace(/^--/, \"\") || \"home\";`\n : ` const pageId = window.location.pathname.replace(/\\\\//g, \"--\").replace(/^--/, \"\") || \"home\";`;\n\n return `${routerImport}import {\n ApostilProvider,\n CommentOverlay,\n CommentToggle,\n CommentSidebar,\n} from \"apostil\";\nimport { localStorageAdapter } from \"apostil/adapters/localStorage\";\nimport \"apostil/styles.css\";\n\nexport function ApostilWrapper({ children }: { children: React.ReactNode }) {${envGuard}\n${pageIdLogic}\n\n return (\n <ApostilProvider pageId={pageId} storage={localStorageAdapter}>\n {children}\n <CommentOverlay />\n <CommentSidebar />\n <CommentToggle />\n </ApostilProvider>\n );\n}\n`;\n}\n\n// --- Gitignore ---\n\nasync function handleGitignore(cwd: string, mode: Mode) {\n const gitignorePath = path.join(cwd, \".gitignore\");\n let gitignore = \"\";\n try {\n gitignore = await fs.readFile(gitignorePath, \"utf-8\");\n } catch {}\n\n if (mode === \"public\") {\n if (gitignore.includes(\".apostil\")) {\n console.log(\" ✓ .gitignore unchanged (public mode — comments will be committed)\");\n } else {\n console.log(\" ✓ Comments will be committed to git (public mode)\");\n }\n } else {\n if (!gitignore.includes(\".apostil\")) {\n const entry = \"\\n# Apostil comments\\n.apostil/\\n\";\n await fs.appendFile(gitignorePath, entry, \"utf-8\");\n console.log(\" ✓ Added .apostil/ to .gitignore\");\n } else {\n console.log(\" ✓ .gitignore already configured\");\n }\n }\n}\n\n// --- Next.js layout injection ---\n\nasync function injectIntoNextjsLayout(appDir: string, useSrc: boolean): Promise<boolean> {\n const layoutPath = await findLayout(appDir);\n if (!layoutPath) return false;\n\n let content = await fs.readFile(layoutPath, \"utf-8\");\n\n if (content.includes(\"ApostilWrapper\")) {\n console.log(\" ✓ Layout already has <ApostilWrapper>\");\n return false;\n }\n\n const importPath = useSrc ? \"@/components/apostil-wrapper\" : \"../components/apostil-wrapper\";\n const importLine = `import { ApostilWrapper } from \"${importPath}\";\\n`;\n\n const importRegex = /^import\\s.+$/gm;\n let lastImportIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = importRegex.exec(content)) !== null) {\n lastImportIndex = match.index + match[0].length;\n }\n\n if (lastImportIndex > 0) {\n content = content.slice(0, lastImportIndex) + \"\\n\" + importLine + content.slice(lastImportIndex);\n } else {\n const useDirective = content.match(/^[\"']use (client|server)[\"'];?\\n/);\n const insertAt = useDirective ? useDirective[0].length : 0;\n content = content.slice(0, insertAt) + importLine + content.slice(insertAt);\n }\n\n const bodyChildrenRegex = /(<body[^>]*>)([\\s\\S]*?)(\\{[\\s]*children[\\s]*\\})([\\s\\S]*?)(<\\/body>)/;\n const bodyMatch = content.match(bodyChildrenRegex);\n\n if (bodyMatch) {\n content = content.replace(\n bodyChildrenRegex,\n `$1$2<ApostilWrapper>$3</ApostilWrapper>$4$5`\n );\n } else {\n const childrenRegex = /(\\{[\\s]*children[\\s]*\\})/;\n if (childrenRegex.test(content)) {\n content = content.replace(childrenRegex, `<ApostilWrapper>$1</ApostilWrapper>`);\n } else {\n console.log(\" ⚠ Could not find {children} in layout — add <ApostilWrapper> manually\");\n return false;\n }\n }\n\n await fs.writeFile(layoutPath, content, \"utf-8\");\n return true;\n}\n\n// --- Vite entry injection ---\n\nasync function injectIntoViteEntry(cwd: string, useSrc: boolean): Promise<boolean> {\n const baseDir = useSrc ? path.join(cwd, \"src\") : cwd;\n\n // Inject into main.tsx/jsx — wrap <App /> which is a reliable, unique target\n for (const ext of [\"tsx\", \"jsx\"]) {\n const mainFile = path.join(baseDir, `main.${ext}`);\n if (await fileExists(mainFile)) {\n return await injectIntoViteMain(mainFile);\n }\n }\n\n console.log(\" ⚠ Could not find main.tsx or main.jsx — add <ApostilWrapper> manually\");\n return false;\n}\n\nasync function injectIntoViteMain(mainFile: string, wrapperFile?: string): Promise<boolean> {\n let content = await fs.readFile(mainFile, \"utf-8\");\n\n if (content.includes(\"ApostilWrapper\")) {\n console.log(\" ✓ Entry already has <ApostilWrapper>\");\n return false;\n }\n\n // Compute relative import path from main file to wrapper\n const mainDir = path.dirname(mainFile);\n const wrapperPath = wrapperFile ?? path.join(mainDir, \"components\", \"apostil-wrapper.tsx\");\n let relativePath = path.relative(mainDir, wrapperPath).replace(/\\.tsx$/, \"\");\n if (!relativePath.startsWith(\".\")) relativePath = \"./\" + relativePath;\n const importLine = `import { ApostilWrapper } from \"${relativePath}\";\\n`;\n const importRegex = /^import\\s.+$/gm;\n let lastImportIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = importRegex.exec(content)) !== null) {\n lastImportIndex = match.index + match[0].length;\n }\n\n if (lastImportIndex > 0) {\n content = content.slice(0, lastImportIndex) + \"\\n\" + importLine + content.slice(lastImportIndex);\n } else {\n content = importLine + content;\n }\n\n // Wrap <App /> with <ApostilWrapper>\n content = content.replace(/<App\\s*\\/>/, `<ApostilWrapper><App /></ApostilWrapper>`);\n\n await fs.writeFile(mainFile, content, \"utf-8\");\n return true;\n}\n\n// --- Remove ---\n\nasync function remove() {\n const cwd = process.cwd();\n const framework = await detectFramework(cwd);\n\n console.log(\"\\n Removing apostil...\\n\");\n\n if (framework === \"nextjs\") {\n await removeNextjs(cwd);\n } else if (framework === \"vite\") {\n await removeVite(cwd);\n } else {\n // Try both\n await removeNextjs(cwd);\n await removeVite(cwd);\n }\n\n // Remove .apostil/ directory\n const commentsDir = path.join(cwd, \".apostil\");\n if (await fileExists(commentsDir)) {\n await fs.rm(commentsDir, { recursive: true });\n console.log(\" ✓ Removed .apostil/ directory\");\n }\n\n // Remove from .gitignore\n const gitignorePath = path.join(cwd, \".gitignore\");\n try {\n let gitignore = await fs.readFile(gitignorePath, \"utf-8\");\n gitignore = gitignore.replace(/\\n?# Apostil comments\\n\\.apostil\\/\\n?/g, \"\");\n await fs.writeFile(gitignorePath, gitignore, \"utf-8\");\n console.log(\" ✓ Cleaned .gitignore\");\n } catch {}\n\n console.log(`\n Done! Now run: npm uninstall apostil\n`);\n}\n\nasync function removeNextjs(cwd: string) {\n const appDir = await findAppDir(cwd);\n const useSrc = appDir?.includes(\"src/app\") ?? false;\n\n // Remove API route\n if (appDir) {\n const apiDir = path.join(appDir, \"api\", \"apostil\");\n if (await fileExists(path.join(apiDir, \"route.ts\"))) {\n await fs.rm(apiDir, { recursive: true });\n console.log(\" ✓ Removed API route\");\n }\n }\n\n // Remove wrapper component\n const componentsDir = path.join(cwd, useSrc ? \"src/components\" : \"components\");\n const wrapperFile = path.join(componentsDir, \"apostil-wrapper.tsx\");\n if (await fileExists(wrapperFile)) {\n await fs.rm(wrapperFile);\n console.log(\" ✓ Removed wrapper component\");\n }\n\n // Remove wrapper from layout\n if (appDir) {\n const layoutPath = await findLayout(appDir);\n if (layoutPath) {\n const unwrapped = await removeWrapperFromFile(layoutPath);\n if (unwrapped) {\n console.log(\" ✓ Removed <ApostilWrapper> from root layout\");\n }\n }\n }\n}\n\nasync function removeVite(cwd: string) {\n const useSrc = await fileExists(path.join(cwd, \"src\"));\n const baseDir = useSrc ? path.join(cwd, \"src\") : cwd;\n\n // Remove wrapper component\n const componentsDir = path.join(cwd, useSrc ? \"src/components\" : \"components\");\n const wrapperFile = path.join(componentsDir, \"apostil-wrapper.tsx\");\n if (await fileExists(wrapperFile)) {\n await fs.rm(wrapperFile);\n console.log(\" ✓ Removed wrapper component\");\n }\n\n // Remove wrapper from App.tsx or main.tsx\n for (const name of [\"App.tsx\", \"App.jsx\", \"main.tsx\", \"main.jsx\"]) {\n const file = path.join(baseDir, name);\n if (await fileExists(file)) {\n const unwrapped = await removeWrapperFromFile(file);\n if (unwrapped) {\n console.log(` ✓ Removed <ApostilWrapper> from ${name}`);\n }\n }\n }\n}\n\nasync function removeWrapperFromFile(filePath: string): Promise<boolean> {\n let content = await fs.readFile(filePath, \"utf-8\");\n if (!content.includes(\"ApostilWrapper\")) return false;\n\n // Remove import line\n content = content.replace(/import\\s*\\{[^}]*ApostilWrapper[^}]*\\}\\s*from\\s*[\"'][^\"']+[\"'];?\\n?/g, \"\");\n\n // Unwrap <ApostilWrapper>...</ApostilWrapper>\n content = content.replace(/<ApostilWrapper>([\\s\\S]*?)<\\/ApostilWrapper>/g, \"$1\");\n\n await fs.writeFile(filePath, content, \"utf-8\");\n return true;\n}\n\n// --- Helpers ---\n\nasync function findAppDir(cwd: string): Promise<string | null> {\n for (const candidate of [\"src/app\", \"app\"]) {\n const dir = path.join(cwd, candidate);\n try {\n const stat = await fs.stat(dir);\n if (stat.isDirectory()) return dir;\n } catch {}\n }\n return null;\n}\n\nasync function findLayout(appDir: string): Promise<string | null> {\n for (const ext of [\"tsx\", \"jsx\", \"ts\", \"js\"]) {\n const file = path.join(appDir, `layout.${ext}`);\n if (await fileExists(file)) return file;\n }\n return null;\n}\n\nasync function fileExists(p: string): Promise<boolean> {\n try {\n await fs.stat(p);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction rel(cwd: string, filePath: string): string {\n return path.relative(cwd, filePath);\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.cjs
CHANGED
|
@@ -484,7 +484,11 @@ function OverlayPin({
|
|
|
484
484
|
(0, import_react2.useEffect)(() => {
|
|
485
485
|
updatePos();
|
|
486
486
|
window.addEventListener("resize", updatePos);
|
|
487
|
-
|
|
487
|
+
document.addEventListener("scroll", updatePos, true);
|
|
488
|
+
return () => {
|
|
489
|
+
window.removeEventListener("resize", updatePos);
|
|
490
|
+
document.removeEventListener("scroll", updatePos, true);
|
|
491
|
+
};
|
|
488
492
|
}, [updatePos]);
|
|
489
493
|
if (!pos) return null;
|
|
490
494
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
@@ -517,6 +521,7 @@ function CommentPin({
|
|
|
517
521
|
index,
|
|
518
522
|
overlayRef
|
|
519
523
|
}) {
|
|
524
|
+
if (thread.resolved) return null;
|
|
520
525
|
if (thread.targetId) {
|
|
521
526
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(TargetedPin, { thread, index }, `targeted-${thread.id}`);
|
|
522
527
|
}
|
|
@@ -601,9 +606,10 @@ function CommentComposer({
|
|
|
601
606
|
const inputRef = (0, import_react3.useRef)(null);
|
|
602
607
|
(0, import_react3.useEffect)(() => {
|
|
603
608
|
if (autoFocus) {
|
|
604
|
-
|
|
609
|
+
const timer = setTimeout(() => {
|
|
605
610
|
inputRef.current?.focus();
|
|
606
|
-
});
|
|
611
|
+
}, 50);
|
|
612
|
+
return () => clearTimeout(timer);
|
|
607
613
|
}
|
|
608
614
|
}, [autoFocus]);
|
|
609
615
|
const handleSubmit = () => {
|
|
@@ -627,7 +633,7 @@ function CommentComposer({
|
|
|
627
633
|
},
|
|
628
634
|
placeholder,
|
|
629
635
|
rows: 1,
|
|
630
|
-
className: "flex-1 resize-none rounded-lg border border-neutral-200 bg-white px-3 py-
|
|
636
|
+
className: "flex-1 resize-none rounded-lg border border-neutral-200 bg-white px-3 py-1.5 text-sm\n placeholder:text-neutral-400 focus:outline-none focus:ring-2 focus:ring-neutral-300\n min-h-[36px] max-h-[120px]"
|
|
631
637
|
}
|
|
632
638
|
),
|
|
633
639
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
@@ -635,7 +641,7 @@ function CommentComposer({
|
|
|
635
641
|
{
|
|
636
642
|
onClick: handleSubmit,
|
|
637
643
|
disabled: !value.trim(),
|
|
638
|
-
className: "flex items-center justify-center w-
|
|
644
|
+
className: "flex items-center justify-center w-9 h-9 rounded-lg\n text-white disabled:opacity-30\n transition-colors shrink-0",
|
|
639
645
|
style: { backgroundColor: brandColor },
|
|
640
646
|
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Send, { className: "w-3.5 h-3.5" })
|
|
641
647
|
}
|
|
@@ -963,7 +969,7 @@ function findCommentTarget(el, boundary) {
|
|
|
963
969
|
};
|
|
964
970
|
}
|
|
965
971
|
function CommentOverlay() {
|
|
966
|
-
const { threads, commentMode, setCommentMode, user, addThread, setActiveThreadId, brandColor } = useApostil();
|
|
972
|
+
const { threads, commentMode, setCommentMode, user, addThread, activeThreadId, setActiveThreadId, brandColor } = useApostil();
|
|
967
973
|
const overlayRef = (0, import_react6.useRef)(null);
|
|
968
974
|
const [pendingPin, setPendingPin] = (0, import_react6.useState)(null);
|
|
969
975
|
const [pendingPixel, setPendingPixel] = (0, import_react6.useState)(null);
|
|
@@ -974,9 +980,15 @@ function CommentOverlay() {
|
|
|
974
980
|
if (!commentMode || !overlayRef.current) return;
|
|
975
981
|
const overlayRect = overlayRef.current.getBoundingClientRect();
|
|
976
982
|
const overlay = overlayRef.current;
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
983
|
+
const elements = document.elementsFromPoint(e.clientX, e.clientY);
|
|
984
|
+
let elementBelow = null;
|
|
985
|
+
for (const el of elements) {
|
|
986
|
+
if (el === overlay || overlay.contains(el)) continue;
|
|
987
|
+
if (el instanceof HTMLElement) {
|
|
988
|
+
elementBelow = el;
|
|
989
|
+
break;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
980
992
|
debug.log(" click at", { clientX: e.clientX, clientY: e.clientY });
|
|
981
993
|
debug.log(" element below overlay:", elementBelow);
|
|
982
994
|
if (elementBelow) {
|
|
@@ -1040,34 +1052,28 @@ function CommentOverlay() {
|
|
|
1040
1052
|
},
|
|
1041
1053
|
[pendingPin, addThread]
|
|
1042
1054
|
);
|
|
1043
|
-
const cancelPending = (0, import_react6.useCallback)(() => {
|
|
1044
|
-
setPendingPin(null);
|
|
1045
|
-
setPendingPixel(null);
|
|
1046
|
-
setCommentMode(false);
|
|
1047
|
-
}, [setCommentMode]);
|
|
1048
1055
|
(0, import_react6.useEffect)(() => {
|
|
1049
|
-
if (!
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
});
|
|
1056
|
+
if (!pendingPixel || !overlayRef.current) return;
|
|
1057
|
+
const overlayRect = overlayRef.current.getBoundingClientRect();
|
|
1058
|
+
const clickX = overlayRect.left + pendingPixel.left;
|
|
1059
|
+
const clickY = overlayRect.top + pendingPixel.top;
|
|
1060
|
+
setPendingFlip({
|
|
1061
|
+
x: clickX + 308 > window.innerWidth,
|
|
1062
|
+
y: clickY + 200 > window.innerHeight
|
|
1057
1063
|
});
|
|
1058
|
-
}, [
|
|
1064
|
+
}, [pendingPixel, overlayRef]);
|
|
1059
1065
|
(0, import_react6.useEffect)(() => {
|
|
1060
1066
|
const hash = window.location.hash;
|
|
1061
|
-
|
|
1067
|
+
debug.log("hash check:", hash, "threads:", threads.length);
|
|
1062
1068
|
if (!hash.startsWith("#apostil-")) return;
|
|
1063
1069
|
const threadId = hash.replace("#apostil-", "");
|
|
1064
|
-
|
|
1070
|
+
debug.log("looking for thread:", threadId);
|
|
1065
1071
|
if (threads.length === 0) {
|
|
1066
|
-
|
|
1072
|
+
debug.log("no threads loaded yet, waiting...");
|
|
1067
1073
|
return;
|
|
1068
1074
|
}
|
|
1069
1075
|
const found = threads.find((t) => t.id === threadId);
|
|
1070
|
-
|
|
1076
|
+
debug.log("found thread:", found ? "yes" : "no");
|
|
1071
1077
|
if (found) {
|
|
1072
1078
|
setActiveThreadId(threadId);
|
|
1073
1079
|
window.history.replaceState(null, "", window.location.pathname);
|
|
@@ -1081,6 +1087,8 @@ function CommentOverlay() {
|
|
|
1081
1087
|
setPendingPin(null);
|
|
1082
1088
|
setPendingPixel(null);
|
|
1083
1089
|
setCommentMode(false);
|
|
1090
|
+
} else if (activeThreadId) {
|
|
1091
|
+
setActiveThreadId(null);
|
|
1084
1092
|
} else if (commentMode) {
|
|
1085
1093
|
setCommentMode(false);
|
|
1086
1094
|
}
|
|
@@ -1102,11 +1110,8 @@ function CommentOverlay() {
|
|
|
1102
1110
|
};
|
|
1103
1111
|
document.addEventListener("keydown", handler);
|
|
1104
1112
|
return () => document.removeEventListener("keydown", handler);
|
|
1105
|
-
}, [commentMode, pendingPin, setCommentMode, setActiveThreadId]);
|
|
1106
|
-
const
|
|
1107
|
-
if (a.resolved !== b.resolved) return a.resolved ? 1 : -1;
|
|
1108
|
-
return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
|
|
1109
|
-
});
|
|
1113
|
+
}, [commentMode, pendingPin, activeThreadId, setCommentMode, setActiveThreadId]);
|
|
1114
|
+
const visibleThreads = threads.filter((t) => !t.resolved).sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
|
|
1110
1115
|
const showingUserPrompt = commentMode && !user;
|
|
1111
1116
|
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
1112
1117
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
@@ -1117,7 +1122,7 @@ function CommentOverlay() {
|
|
|
1117
1122
|
style: { zIndex: commentMode ? getHighestZIndex() + 10 : 55 },
|
|
1118
1123
|
onMouseDown: handleClick,
|
|
1119
1124
|
children: [
|
|
1120
|
-
|
|
1125
|
+
visibleThreads.map((thread, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "pointer-events-auto", children: [
|
|
1121
1126
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CommentPin, { thread, index: i, overlayRef }),
|
|
1122
1127
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ApostilThreadPopover, { thread, overlayRef })
|
|
1123
1128
|
] }, thread.id)),
|