@stephenov/feedbackwidget 0.5.1 → 0.6.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/dist/cli.js CHANGED
@@ -88,50 +88,46 @@ async function initProject() {
88
88
  reject(new Error("Authentication timed out"));
89
89
  }, 5 * 60 * 1e3);
90
90
  });
91
+ const framework = detectFramework();
92
+ const snippet = `<FeedbackWidget apiKey="${apiKey}" />`;
93
+ let copied = false;
94
+ try {
95
+ await copyToClipboard(snippet);
96
+ copied = true;
97
+ } catch {
98
+ }
91
99
  console.log("\u2705 Authenticated!\n");
92
- const injected = injectWidget(apiKey);
93
- if (injected) {
94
- console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n");
95
- console.log(" \u2705 Widget automatically added to your app!\n");
96
- console.log(` Modified: ${injected.file}
100
+ console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n");
101
+ if (framework.name) {
102
+ console.log(` Detected: ${framework.name}
97
103
  `);
98
- console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n");
99
- console.log(" Just run your dev server and the widget will appear.\n");
104
+ }
105
+ console.log(" Step 1: Add the import to " + framework.file + "\n");
106
+ if (framework.name === "Astro") {
107
+ console.log(" ---");
108
+ console.log(' import { FeedbackWidget } from "@stephenov/feedbackwidget";');
109
+ console.log(" ---\n");
100
110
  } else {
101
- const snippet = `<FeedbackWidget apiKey="${apiKey}" />`;
102
- let copied = false;
103
- try {
104
- await copyToClipboard(snippet);
105
- copied = true;
106
- } catch {
107
- }
108
- console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n");
109
- console.log(" Your API Key:\n");
110
- console.log(` ${apiKey}
111
- `);
112
- console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n");
113
- if (copied) {
114
- console.log(" \u{1F4CB} Copied to clipboard!\n");
115
- }
116
- const framework = detectFramework();
117
- console.log(" Add to your app:\n");
118
111
  console.log(' import { FeedbackWidget } from "@stephenov/feedbackwidget"\n');
119
- console.log(` ${snippet}
120
- `);
121
- if (framework.name) {
122
- console.log(` Detected: ${framework.name}
112
+ }
113
+ console.log(" Step 2: Add the component " + (framework.note || "inside <body>") + "\n");
114
+ if (framework.name === "Astro") {
115
+ console.log(` <FeedbackWidget client:load apiKey="${apiKey}" />
123
116
  `);
124
- console.log(` Add to: ${framework.file}
117
+ } else {
118
+ console.log(` ${snippet}
125
119
  `);
126
- if (framework.note) {
127
- console.log(` Note: ${framework.note}
120
+ }
121
+ console.log(" Step 3: Run your dev server\n");
122
+ console.log(` ${framework.devCommand || "npm run dev"}
128
123
  `);
129
- }
130
- } else {
131
- console.log(" Add to your root component (renders on every page)\n");
132
- }
124
+ console.log(" Step 4: Refresh browser - widget appears in bottom-right\n");
125
+ console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n");
126
+ console.log(" Once the widget appears, view feedback at:");
127
+ console.log(" https://feedbackwidget-api.vercel.app/dashboard\n");
128
+ if (copied) {
129
+ console.log(" \u{1F4CB} Snippet copied to clipboard!\n");
133
130
  }
134
- console.log(" Dashboard: https://feedbackwidget-api.vercel.app/dashboard\n");
135
131
  }
136
132
  async function whoami() {
137
133
  console.log("\nRun 'npx feedbackwidget init' to get your API key.\n");
@@ -196,170 +192,65 @@ function errorPage(error) {
196
192
  </body>
197
193
  </html>`;
198
194
  }
199
- function injectWidget(apiKey) {
195
+ function detectFramework() {
200
196
  const cwd = process.cwd();
201
- const importLine = 'import { FeedbackWidget } from "@stephenov/feedbackwidget";';
202
- const component = `<FeedbackWidget apiKey="${apiKey}" />`;
203
197
  try {
204
198
  const pkgPath = import_path.default.join(cwd, "package.json");
205
199
  const pkg = JSON.parse(import_fs.default.readFileSync(pkgPath, "utf-8"));
206
200
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
207
- if (deps.next) {
208
- const layoutPaths = [
209
- import_path.default.join(cwd, "app", "layout.tsx"),
210
- import_path.default.join(cwd, "app", "layout.js"),
211
- import_path.default.join(cwd, "src", "app", "layout.tsx"),
212
- import_path.default.join(cwd, "src", "app", "layout.js")
213
- ];
214
- for (const layoutPath of layoutPaths) {
215
- if (import_fs.default.existsSync(layoutPath)) {
216
- let content = import_fs.default.readFileSync(layoutPath, "utf-8");
217
- if (content.includes("FeedbackWidget")) {
218
- return null;
219
- }
220
- const lines = content.split("\n");
221
- let lastImportIndex = -1;
222
- for (let i = 0; i < lines.length; i++) {
223
- if (lines[i].startsWith("import ")) {
224
- lastImportIndex = i;
225
- }
226
- }
227
- if (lastImportIndex >= 0) {
228
- lines.splice(lastImportIndex + 1, 0, importLine);
229
- } else {
230
- lines.unshift(importLine);
231
- }
232
- content = lines.join("\n");
233
- const bodyRegex = /(<body[^>]*>)/;
234
- const bodyMatch = content.match(bodyRegex);
235
- if (bodyMatch && bodyMatch.index !== void 0) {
236
- const insertPos = bodyMatch.index + bodyMatch[0].length;
237
- content = content.slice(0, insertPos) + "\n " + component + content.slice(insertPos);
238
- }
239
- import_fs.default.writeFileSync(layoutPath, content, { encoding: "utf-8", flag: "w" });
240
- const verify = import_fs.default.readFileSync(layoutPath, "utf-8");
241
- if (!verify.includes("FeedbackWidget")) {
242
- console.error(" Warning: File write may not have persisted. Check " + layoutPath);
243
- return null;
244
- }
245
- return { file: layoutPath.replace(cwd + "/", "") };
246
- }
201
+ if (deps.astro) {
202
+ if (import_fs.default.existsSync(import_path.default.join(cwd, "src", "layouts", "Layout.astro"))) {
203
+ return { name: "Astro", file: "src/layouts/Layout.astro", note: "inside <body>", devCommand: "npm run dev" };
247
204
  }
248
- const appPaths = [
249
- import_path.default.join(cwd, "pages", "_app.tsx"),
250
- import_path.default.join(cwd, "pages", "_app.js"),
251
- import_path.default.join(cwd, "src", "pages", "_app.tsx"),
252
- import_path.default.join(cwd, "src", "pages", "_app.js")
253
- ];
254
- for (const appPath of appPaths) {
255
- if (import_fs.default.existsSync(appPath)) {
256
- let content = import_fs.default.readFileSync(appPath, "utf-8");
257
- if (content.includes("FeedbackWidget")) {
258
- return null;
259
- }
260
- const importMatch = content.match(/^(import .+\n)+/m);
261
- if (importMatch) {
262
- const lastImportEnd = importMatch.index + importMatch[0].length;
263
- content = content.slice(0, lastImportEnd) + importLine + "\n" + content.slice(lastImportEnd);
264
- }
265
- const componentMatch = content.match(/<Component\s+\{\.\.\.pageProps\}\s*\/>/);
266
- if (componentMatch) {
267
- const insertPos = componentMatch.index + componentMatch[0].length;
268
- content = content.slice(0, insertPos) + "\n " + component + content.slice(insertPos);
269
- }
270
- import_fs.default.writeFileSync(appPath, content);
271
- return { file: appPath.replace(cwd + "/", "") };
272
- }
205
+ if (import_fs.default.existsSync(import_path.default.join(cwd, "src", "layouts", "BaseLayout.astro"))) {
206
+ return { name: "Astro", file: "src/layouts/BaseLayout.astro", note: "inside <body>", devCommand: "npm run dev" };
273
207
  }
208
+ return { name: "Astro", file: "your main layout .astro file", note: "inside <body>", devCommand: "npm run dev" };
274
209
  }
275
- if (deps.vite || deps["react-scripts"] || deps.react) {
276
- const appPaths = [
277
- import_path.default.join(cwd, "src", "App.tsx"),
278
- import_path.default.join(cwd, "src", "App.jsx")
279
- ];
280
- for (const appPath of appPaths) {
281
- if (import_fs.default.existsSync(appPath)) {
282
- let content = import_fs.default.readFileSync(appPath, "utf-8");
283
- if (content.includes("FeedbackWidget")) {
284
- return null;
285
- }
286
- const importMatch = content.match(/^(import .+\n)+/m);
287
- if (importMatch) {
288
- const lastImportEnd = importMatch.index + importMatch[0].length;
289
- content = content.slice(0, lastImportEnd) + importLine + "\n" + content.slice(lastImportEnd);
290
- }
291
- const returnMatch = content.match(/return\s*\(\s*\n?\s*(<[A-Za-z>])/);
292
- if (returnMatch) {
293
- const match = content.match(/return\s*\(\s*\n?\s*<[A-Za-z]+[^>]*>/);
294
- if (match) {
295
- const insertPos = match.index + match[0].length;
296
- content = content.slice(0, insertPos) + "\n " + component + content.slice(insertPos);
297
- }
298
- }
299
- import_fs.default.writeFileSync(appPath, content);
300
- return { file: appPath.replace(cwd + "/", "") };
301
- }
302
- }
303
- }
304
- } catch {
305
- }
306
- return null;
307
- }
308
- function detectFramework() {
309
- const cwd = process.cwd();
310
- try {
311
- const pkgPath = import_path.default.join(cwd, "package.json");
312
- const pkg = JSON.parse(import_fs.default.readFileSync(pkgPath, "utf-8"));
313
- const deps = { ...pkg.dependencies, ...pkg.devDependencies };
314
- if (deps.next && import_fs.default.existsSync(import_path.default.join(cwd, "app"))) {
210
+ if (deps.next) {
315
211
  if (import_fs.default.existsSync(import_path.default.join(cwd, "app", "layout.tsx"))) {
316
- return { name: "Next.js (App Router)", file: "app/layout.tsx", note: "Inside <body>, before {children}" };
212
+ return { name: "Next.js (App Router)", file: "app/layout.tsx", note: "inside <body>, before {children}", devCommand: "npm run dev" };
317
213
  }
318
214
  if (import_fs.default.existsSync(import_path.default.join(cwd, "app", "layout.js"))) {
319
- return { name: "Next.js (App Router)", file: "app/layout.js", note: "Inside <body>, before {children}" };
215
+ return { name: "Next.js (App Router)", file: "app/layout.js", note: "inside <body>, before {children}", devCommand: "npm run dev" };
320
216
  }
321
217
  if (import_fs.default.existsSync(import_path.default.join(cwd, "src", "app", "layout.tsx"))) {
322
- return { name: "Next.js (App Router)", file: "src/app/layout.tsx", note: "Inside <body>, before {children}" };
218
+ return { name: "Next.js (App Router)", file: "src/app/layout.tsx", note: "inside <body>, before {children}", devCommand: "npm run dev" };
323
219
  }
324
- }
325
- if (deps.next && import_fs.default.existsSync(import_path.default.join(cwd, "pages"))) {
326
220
  if (import_fs.default.existsSync(import_path.default.join(cwd, "pages", "_app.tsx"))) {
327
- return { name: "Next.js (Pages Router)", file: "pages/_app.tsx", note: "After <Component {...pageProps} />" };
221
+ return { name: "Next.js (Pages Router)", file: "pages/_app.tsx", note: "after <Component {...pageProps} />", devCommand: "npm run dev" };
328
222
  }
329
223
  if (import_fs.default.existsSync(import_path.default.join(cwd, "pages", "_app.js"))) {
330
- return { name: "Next.js (Pages Router)", file: "pages/_app.js", note: "After <Component {...pageProps} />" };
224
+ return { name: "Next.js (Pages Router)", file: "pages/_app.js", note: "after <Component {...pageProps} />", devCommand: "npm run dev" };
225
+ }
226
+ }
227
+ if (deps["@remix-run/react"]) {
228
+ if (import_fs.default.existsSync(import_path.default.join(cwd, "app", "root.tsx"))) {
229
+ return { name: "Remix", file: "app/root.tsx", note: "inside <body>", devCommand: "npm run dev" };
331
230
  }
332
231
  }
333
232
  if (deps.vite && (deps.react || deps["@vitejs/plugin-react"])) {
334
233
  if (import_fs.default.existsSync(import_path.default.join(cwd, "src", "App.tsx"))) {
335
- return { name: "Vite + React", file: "src/App.tsx", note: "At end of App component" };
234
+ return { name: "Vite + React", file: "src/App.tsx", note: "at the end of your App component", devCommand: "npm run dev" };
336
235
  }
337
236
  if (import_fs.default.existsSync(import_path.default.join(cwd, "src", "App.jsx"))) {
338
- return { name: "Vite + React", file: "src/App.jsx", note: "At end of App component" };
237
+ return { name: "Vite + React", file: "src/App.jsx", note: "at the end of your App component", devCommand: "npm run dev" };
339
238
  }
340
239
  }
341
240
  if (deps["react-scripts"]) {
342
241
  if (import_fs.default.existsSync(import_path.default.join(cwd, "src", "App.tsx"))) {
343
- return { name: "Create React App", file: "src/App.tsx", note: "At end of App component" };
242
+ return { name: "Create React App", file: "src/App.tsx", note: "at the end of your App component", devCommand: "npm start" };
344
243
  }
345
244
  if (import_fs.default.existsSync(import_path.default.join(cwd, "src", "App.js"))) {
346
- return { name: "Create React App", file: "src/App.js", note: "At end of App component" };
347
- }
348
- }
349
- if (deps.astro) {
350
- return { name: "Astro", file: "A React component", note: "Use client:load directive" };
351
- }
352
- if (deps["@remix-run/react"]) {
353
- if (import_fs.default.existsSync(import_path.default.join(cwd, "app", "root.tsx"))) {
354
- return { name: "Remix", file: "app/root.tsx", note: "Inside <body>" };
245
+ return { name: "Create React App", file: "src/App.js", note: "at the end of your App component", devCommand: "npm start" };
355
246
  }
356
247
  }
357
248
  if (deps.react) {
358
- return { name: "React", file: "Your App component", note: "Where it renders on every page" };
249
+ return { name: "React", file: "your main App component", note: "where it renders on every page", devCommand: "npm run dev" };
359
250
  }
360
251
  } catch {
361
252
  }
362
- return { name: null, file: "Your root component" };
253
+ return { name: null, file: "your root component", note: "inside the main layout", devCommand: "npm run dev" };
363
254
  }
364
255
  async function copyToClipboard(text) {
365
256
  return new Promise((resolve, reject) => {
package/dist/cli.mjs CHANGED
@@ -65,50 +65,46 @@ async function initProject() {
65
65
  reject(new Error("Authentication timed out"));
66
66
  }, 5 * 60 * 1e3);
67
67
  });
68
+ const framework = detectFramework();
69
+ const snippet = `<FeedbackWidget apiKey="${apiKey}" />`;
70
+ let copied = false;
71
+ try {
72
+ await copyToClipboard(snippet);
73
+ copied = true;
74
+ } catch {
75
+ }
68
76
  console.log("\u2705 Authenticated!\n");
69
- const injected = injectWidget(apiKey);
70
- if (injected) {
71
- console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n");
72
- console.log(" \u2705 Widget automatically added to your app!\n");
73
- console.log(` Modified: ${injected.file}
77
+ console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n");
78
+ if (framework.name) {
79
+ console.log(` Detected: ${framework.name}
74
80
  `);
75
- console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n");
76
- console.log(" Just run your dev server and the widget will appear.\n");
81
+ }
82
+ console.log(" Step 1: Add the import to " + framework.file + "\n");
83
+ if (framework.name === "Astro") {
84
+ console.log(" ---");
85
+ console.log(' import { FeedbackWidget } from "@stephenov/feedbackwidget";');
86
+ console.log(" ---\n");
77
87
  } else {
78
- const snippet = `<FeedbackWidget apiKey="${apiKey}" />`;
79
- let copied = false;
80
- try {
81
- await copyToClipboard(snippet);
82
- copied = true;
83
- } catch {
84
- }
85
- console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n");
86
- console.log(" Your API Key:\n");
87
- console.log(` ${apiKey}
88
- `);
89
- console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n");
90
- if (copied) {
91
- console.log(" \u{1F4CB} Copied to clipboard!\n");
92
- }
93
- const framework = detectFramework();
94
- console.log(" Add to your app:\n");
95
88
  console.log(' import { FeedbackWidget } from "@stephenov/feedbackwidget"\n');
96
- console.log(` ${snippet}
97
- `);
98
- if (framework.name) {
99
- console.log(` Detected: ${framework.name}
89
+ }
90
+ console.log(" Step 2: Add the component " + (framework.note || "inside <body>") + "\n");
91
+ if (framework.name === "Astro") {
92
+ console.log(` <FeedbackWidget client:load apiKey="${apiKey}" />
100
93
  `);
101
- console.log(` Add to: ${framework.file}
94
+ } else {
95
+ console.log(` ${snippet}
102
96
  `);
103
- if (framework.note) {
104
- console.log(` Note: ${framework.note}
97
+ }
98
+ console.log(" Step 3: Run your dev server\n");
99
+ console.log(` ${framework.devCommand || "npm run dev"}
105
100
  `);
106
- }
107
- } else {
108
- console.log(" Add to your root component (renders on every page)\n");
109
- }
101
+ console.log(" Step 4: Refresh browser - widget appears in bottom-right\n");
102
+ console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n");
103
+ console.log(" Once the widget appears, view feedback at:");
104
+ console.log(" https://feedbackwidget-api.vercel.app/dashboard\n");
105
+ if (copied) {
106
+ console.log(" \u{1F4CB} Snippet copied to clipboard!\n");
110
107
  }
111
- console.log(" Dashboard: https://feedbackwidget-api.vercel.app/dashboard\n");
112
108
  }
113
109
  async function whoami() {
114
110
  console.log("\nRun 'npx feedbackwidget init' to get your API key.\n");
@@ -173,170 +169,65 @@ function errorPage(error) {
173
169
  </body>
174
170
  </html>`;
175
171
  }
176
- function injectWidget(apiKey) {
172
+ function detectFramework() {
177
173
  const cwd = process.cwd();
178
- const importLine = 'import { FeedbackWidget } from "@stephenov/feedbackwidget";';
179
- const component = `<FeedbackWidget apiKey="${apiKey}" />`;
180
174
  try {
181
175
  const pkgPath = path.join(cwd, "package.json");
182
176
  const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
183
177
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
184
- if (deps.next) {
185
- const layoutPaths = [
186
- path.join(cwd, "app", "layout.tsx"),
187
- path.join(cwd, "app", "layout.js"),
188
- path.join(cwd, "src", "app", "layout.tsx"),
189
- path.join(cwd, "src", "app", "layout.js")
190
- ];
191
- for (const layoutPath of layoutPaths) {
192
- if (fs.existsSync(layoutPath)) {
193
- let content = fs.readFileSync(layoutPath, "utf-8");
194
- if (content.includes("FeedbackWidget")) {
195
- return null;
196
- }
197
- const lines = content.split("\n");
198
- let lastImportIndex = -1;
199
- for (let i = 0; i < lines.length; i++) {
200
- if (lines[i].startsWith("import ")) {
201
- lastImportIndex = i;
202
- }
203
- }
204
- if (lastImportIndex >= 0) {
205
- lines.splice(lastImportIndex + 1, 0, importLine);
206
- } else {
207
- lines.unshift(importLine);
208
- }
209
- content = lines.join("\n");
210
- const bodyRegex = /(<body[^>]*>)/;
211
- const bodyMatch = content.match(bodyRegex);
212
- if (bodyMatch && bodyMatch.index !== void 0) {
213
- const insertPos = bodyMatch.index + bodyMatch[0].length;
214
- content = content.slice(0, insertPos) + "\n " + component + content.slice(insertPos);
215
- }
216
- fs.writeFileSync(layoutPath, content, { encoding: "utf-8", flag: "w" });
217
- const verify = fs.readFileSync(layoutPath, "utf-8");
218
- if (!verify.includes("FeedbackWidget")) {
219
- console.error(" Warning: File write may not have persisted. Check " + layoutPath);
220
- return null;
221
- }
222
- return { file: layoutPath.replace(cwd + "/", "") };
223
- }
178
+ if (deps.astro) {
179
+ if (fs.existsSync(path.join(cwd, "src", "layouts", "Layout.astro"))) {
180
+ return { name: "Astro", file: "src/layouts/Layout.astro", note: "inside <body>", devCommand: "npm run dev" };
224
181
  }
225
- const appPaths = [
226
- path.join(cwd, "pages", "_app.tsx"),
227
- path.join(cwd, "pages", "_app.js"),
228
- path.join(cwd, "src", "pages", "_app.tsx"),
229
- path.join(cwd, "src", "pages", "_app.js")
230
- ];
231
- for (const appPath of appPaths) {
232
- if (fs.existsSync(appPath)) {
233
- let content = fs.readFileSync(appPath, "utf-8");
234
- if (content.includes("FeedbackWidget")) {
235
- return null;
236
- }
237
- const importMatch = content.match(/^(import .+\n)+/m);
238
- if (importMatch) {
239
- const lastImportEnd = importMatch.index + importMatch[0].length;
240
- content = content.slice(0, lastImportEnd) + importLine + "\n" + content.slice(lastImportEnd);
241
- }
242
- const componentMatch = content.match(/<Component\s+\{\.\.\.pageProps\}\s*\/>/);
243
- if (componentMatch) {
244
- const insertPos = componentMatch.index + componentMatch[0].length;
245
- content = content.slice(0, insertPos) + "\n " + component + content.slice(insertPos);
246
- }
247
- fs.writeFileSync(appPath, content);
248
- return { file: appPath.replace(cwd + "/", "") };
249
- }
182
+ if (fs.existsSync(path.join(cwd, "src", "layouts", "BaseLayout.astro"))) {
183
+ return { name: "Astro", file: "src/layouts/BaseLayout.astro", note: "inside <body>", devCommand: "npm run dev" };
250
184
  }
185
+ return { name: "Astro", file: "your main layout .astro file", note: "inside <body>", devCommand: "npm run dev" };
251
186
  }
252
- if (deps.vite || deps["react-scripts"] || deps.react) {
253
- const appPaths = [
254
- path.join(cwd, "src", "App.tsx"),
255
- path.join(cwd, "src", "App.jsx")
256
- ];
257
- for (const appPath of appPaths) {
258
- if (fs.existsSync(appPath)) {
259
- let content = fs.readFileSync(appPath, "utf-8");
260
- if (content.includes("FeedbackWidget")) {
261
- return null;
262
- }
263
- const importMatch = content.match(/^(import .+\n)+/m);
264
- if (importMatch) {
265
- const lastImportEnd = importMatch.index + importMatch[0].length;
266
- content = content.slice(0, lastImportEnd) + importLine + "\n" + content.slice(lastImportEnd);
267
- }
268
- const returnMatch = content.match(/return\s*\(\s*\n?\s*(<[A-Za-z>])/);
269
- if (returnMatch) {
270
- const match = content.match(/return\s*\(\s*\n?\s*<[A-Za-z]+[^>]*>/);
271
- if (match) {
272
- const insertPos = match.index + match[0].length;
273
- content = content.slice(0, insertPos) + "\n " + component + content.slice(insertPos);
274
- }
275
- }
276
- fs.writeFileSync(appPath, content);
277
- return { file: appPath.replace(cwd + "/", "") };
278
- }
279
- }
280
- }
281
- } catch {
282
- }
283
- return null;
284
- }
285
- function detectFramework() {
286
- const cwd = process.cwd();
287
- try {
288
- const pkgPath = path.join(cwd, "package.json");
289
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
290
- const deps = { ...pkg.dependencies, ...pkg.devDependencies };
291
- if (deps.next && fs.existsSync(path.join(cwd, "app"))) {
187
+ if (deps.next) {
292
188
  if (fs.existsSync(path.join(cwd, "app", "layout.tsx"))) {
293
- return { name: "Next.js (App Router)", file: "app/layout.tsx", note: "Inside <body>, before {children}" };
189
+ return { name: "Next.js (App Router)", file: "app/layout.tsx", note: "inside <body>, before {children}", devCommand: "npm run dev" };
294
190
  }
295
191
  if (fs.existsSync(path.join(cwd, "app", "layout.js"))) {
296
- return { name: "Next.js (App Router)", file: "app/layout.js", note: "Inside <body>, before {children}" };
192
+ return { name: "Next.js (App Router)", file: "app/layout.js", note: "inside <body>, before {children}", devCommand: "npm run dev" };
297
193
  }
298
194
  if (fs.existsSync(path.join(cwd, "src", "app", "layout.tsx"))) {
299
- return { name: "Next.js (App Router)", file: "src/app/layout.tsx", note: "Inside <body>, before {children}" };
195
+ return { name: "Next.js (App Router)", file: "src/app/layout.tsx", note: "inside <body>, before {children}", devCommand: "npm run dev" };
300
196
  }
301
- }
302
- if (deps.next && fs.existsSync(path.join(cwd, "pages"))) {
303
197
  if (fs.existsSync(path.join(cwd, "pages", "_app.tsx"))) {
304
- return { name: "Next.js (Pages Router)", file: "pages/_app.tsx", note: "After <Component {...pageProps} />" };
198
+ return { name: "Next.js (Pages Router)", file: "pages/_app.tsx", note: "after <Component {...pageProps} />", devCommand: "npm run dev" };
305
199
  }
306
200
  if (fs.existsSync(path.join(cwd, "pages", "_app.js"))) {
307
- return { name: "Next.js (Pages Router)", file: "pages/_app.js", note: "After <Component {...pageProps} />" };
201
+ return { name: "Next.js (Pages Router)", file: "pages/_app.js", note: "after <Component {...pageProps} />", devCommand: "npm run dev" };
202
+ }
203
+ }
204
+ if (deps["@remix-run/react"]) {
205
+ if (fs.existsSync(path.join(cwd, "app", "root.tsx"))) {
206
+ return { name: "Remix", file: "app/root.tsx", note: "inside <body>", devCommand: "npm run dev" };
308
207
  }
309
208
  }
310
209
  if (deps.vite && (deps.react || deps["@vitejs/plugin-react"])) {
311
210
  if (fs.existsSync(path.join(cwd, "src", "App.tsx"))) {
312
- return { name: "Vite + React", file: "src/App.tsx", note: "At end of App component" };
211
+ return { name: "Vite + React", file: "src/App.tsx", note: "at the end of your App component", devCommand: "npm run dev" };
313
212
  }
314
213
  if (fs.existsSync(path.join(cwd, "src", "App.jsx"))) {
315
- return { name: "Vite + React", file: "src/App.jsx", note: "At end of App component" };
214
+ return { name: "Vite + React", file: "src/App.jsx", note: "at the end of your App component", devCommand: "npm run dev" };
316
215
  }
317
216
  }
318
217
  if (deps["react-scripts"]) {
319
218
  if (fs.existsSync(path.join(cwd, "src", "App.tsx"))) {
320
- return { name: "Create React App", file: "src/App.tsx", note: "At end of App component" };
219
+ return { name: "Create React App", file: "src/App.tsx", note: "at the end of your App component", devCommand: "npm start" };
321
220
  }
322
221
  if (fs.existsSync(path.join(cwd, "src", "App.js"))) {
323
- return { name: "Create React App", file: "src/App.js", note: "At end of App component" };
324
- }
325
- }
326
- if (deps.astro) {
327
- return { name: "Astro", file: "A React component", note: "Use client:load directive" };
328
- }
329
- if (deps["@remix-run/react"]) {
330
- if (fs.existsSync(path.join(cwd, "app", "root.tsx"))) {
331
- return { name: "Remix", file: "app/root.tsx", note: "Inside <body>" };
222
+ return { name: "Create React App", file: "src/App.js", note: "at the end of your App component", devCommand: "npm start" };
332
223
  }
333
224
  }
334
225
  if (deps.react) {
335
- return { name: "React", file: "Your App component", note: "Where it renders on every page" };
226
+ return { name: "React", file: "your main App component", note: "where it renders on every page", devCommand: "npm run dev" };
336
227
  }
337
228
  } catch {
338
229
  }
339
- return { name: null, file: "Your root component" };
230
+ return { name: null, file: "your root component", note: "inside the main layout", devCommand: "npm run dev" };
340
231
  }
341
232
  async function copyToClipboard(text) {
342
233
  return new Promise((resolve, reject) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stephenov/feedbackwidget",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "Voice-first feedback widget with AI analysis, GitHub, and Slack integration",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
package/dist/cli.d.mts DELETED
@@ -1 +0,0 @@
1
- #!/usr/bin/env node
package/dist/cli.d.ts DELETED
@@ -1 +0,0 @@
1
- #!/usr/bin/env node