@stephenov/feedbackwidget 0.5.0 → 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.
Files changed (3) hide show
  1. package/dist/cli.js +56 -153
  2. package/dist/cli.mjs +56 -153
  3. package/package.json +1 -1
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,158 +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 importMatch = content.match(/^(import .+\n)+/m);
221
- if (importMatch) {
222
- const lastImportEnd = importMatch.index + importMatch[0].length;
223
- content = content.slice(0, lastImportEnd) + importLine + "\n" + content.slice(lastImportEnd);
224
- } else {
225
- content = importLine + "\n" + content;
226
- }
227
- const bodyMatch = content.match(/(<body[^>]*>)(\s*)/);
228
- if (bodyMatch) {
229
- const insertPos = bodyMatch.index + bodyMatch[0].length;
230
- content = content.slice(0, insertPos) + "\n " + component + content.slice(insertPos);
231
- }
232
- import_fs.default.writeFileSync(layoutPath, content);
233
- return { file: layoutPath.replace(cwd + "/", "") };
234
- }
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" };
235
204
  }
236
- const appPaths = [
237
- import_path.default.join(cwd, "pages", "_app.tsx"),
238
- import_path.default.join(cwd, "pages", "_app.js"),
239
- import_path.default.join(cwd, "src", "pages", "_app.tsx"),
240
- import_path.default.join(cwd, "src", "pages", "_app.js")
241
- ];
242
- for (const appPath of appPaths) {
243
- if (import_fs.default.existsSync(appPath)) {
244
- let content = import_fs.default.readFileSync(appPath, "utf-8");
245
- if (content.includes("FeedbackWidget")) {
246
- return null;
247
- }
248
- const importMatch = content.match(/^(import .+\n)+/m);
249
- if (importMatch) {
250
- const lastImportEnd = importMatch.index + importMatch[0].length;
251
- content = content.slice(0, lastImportEnd) + importLine + "\n" + content.slice(lastImportEnd);
252
- }
253
- const componentMatch = content.match(/<Component\s+\{\.\.\.pageProps\}\s*\/>/);
254
- if (componentMatch) {
255
- const insertPos = componentMatch.index + componentMatch[0].length;
256
- content = content.slice(0, insertPos) + "\n " + component + content.slice(insertPos);
257
- }
258
- import_fs.default.writeFileSync(appPath, content);
259
- return { file: appPath.replace(cwd + "/", "") };
260
- }
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" };
261
207
  }
208
+ return { name: "Astro", file: "your main layout .astro file", note: "inside <body>", devCommand: "npm run dev" };
262
209
  }
263
- if (deps.vite || deps["react-scripts"] || deps.react) {
264
- const appPaths = [
265
- import_path.default.join(cwd, "src", "App.tsx"),
266
- import_path.default.join(cwd, "src", "App.jsx")
267
- ];
268
- for (const appPath of appPaths) {
269
- if (import_fs.default.existsSync(appPath)) {
270
- let content = import_fs.default.readFileSync(appPath, "utf-8");
271
- if (content.includes("FeedbackWidget")) {
272
- return null;
273
- }
274
- const importMatch = content.match(/^(import .+\n)+/m);
275
- if (importMatch) {
276
- const lastImportEnd = importMatch.index + importMatch[0].length;
277
- content = content.slice(0, lastImportEnd) + importLine + "\n" + content.slice(lastImportEnd);
278
- }
279
- const returnMatch = content.match(/return\s*\(\s*\n?\s*(<[A-Za-z>])/);
280
- if (returnMatch) {
281
- const match = content.match(/return\s*\(\s*\n?\s*<[A-Za-z]+[^>]*>/);
282
- if (match) {
283
- const insertPos = match.index + match[0].length;
284
- content = content.slice(0, insertPos) + "\n " + component + content.slice(insertPos);
285
- }
286
- }
287
- import_fs.default.writeFileSync(appPath, content);
288
- return { file: appPath.replace(cwd + "/", "") };
289
- }
290
- }
291
- }
292
- } catch {
293
- }
294
- return null;
295
- }
296
- function detectFramework() {
297
- const cwd = process.cwd();
298
- try {
299
- const pkgPath = import_path.default.join(cwd, "package.json");
300
- const pkg = JSON.parse(import_fs.default.readFileSync(pkgPath, "utf-8"));
301
- const deps = { ...pkg.dependencies, ...pkg.devDependencies };
302
- if (deps.next && import_fs.default.existsSync(import_path.default.join(cwd, "app"))) {
210
+ if (deps.next) {
303
211
  if (import_fs.default.existsSync(import_path.default.join(cwd, "app", "layout.tsx"))) {
304
- 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" };
305
213
  }
306
214
  if (import_fs.default.existsSync(import_path.default.join(cwd, "app", "layout.js"))) {
307
- 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" };
308
216
  }
309
217
  if (import_fs.default.existsSync(import_path.default.join(cwd, "src", "app", "layout.tsx"))) {
310
- 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" };
311
219
  }
312
- }
313
- if (deps.next && import_fs.default.existsSync(import_path.default.join(cwd, "pages"))) {
314
220
  if (import_fs.default.existsSync(import_path.default.join(cwd, "pages", "_app.tsx"))) {
315
- 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" };
316
222
  }
317
223
  if (import_fs.default.existsSync(import_path.default.join(cwd, "pages", "_app.js"))) {
318
- 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" };
319
230
  }
320
231
  }
321
232
  if (deps.vite && (deps.react || deps["@vitejs/plugin-react"])) {
322
233
  if (import_fs.default.existsSync(import_path.default.join(cwd, "src", "App.tsx"))) {
323
- 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" };
324
235
  }
325
236
  if (import_fs.default.existsSync(import_path.default.join(cwd, "src", "App.jsx"))) {
326
- 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" };
327
238
  }
328
239
  }
329
240
  if (deps["react-scripts"]) {
330
241
  if (import_fs.default.existsSync(import_path.default.join(cwd, "src", "App.tsx"))) {
331
- 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" };
332
243
  }
333
244
  if (import_fs.default.existsSync(import_path.default.join(cwd, "src", "App.js"))) {
334
- return { name: "Create React App", file: "src/App.js", note: "At end of App component" };
335
- }
336
- }
337
- if (deps.astro) {
338
- return { name: "Astro", file: "A React component", note: "Use client:load directive" };
339
- }
340
- if (deps["@remix-run/react"]) {
341
- if (import_fs.default.existsSync(import_path.default.join(cwd, "app", "root.tsx"))) {
342
- 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" };
343
246
  }
344
247
  }
345
248
  if (deps.react) {
346
- 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" };
347
250
  }
348
251
  } catch {
349
252
  }
350
- return { name: null, file: "Your root component" };
253
+ return { name: null, file: "your root component", note: "inside the main layout", devCommand: "npm run dev" };
351
254
  }
352
255
  async function copyToClipboard(text) {
353
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,158 +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 importMatch = content.match(/^(import .+\n)+/m);
198
- if (importMatch) {
199
- const lastImportEnd = importMatch.index + importMatch[0].length;
200
- content = content.slice(0, lastImportEnd) + importLine + "\n" + content.slice(lastImportEnd);
201
- } else {
202
- content = importLine + "\n" + content;
203
- }
204
- const bodyMatch = content.match(/(<body[^>]*>)(\s*)/);
205
- if (bodyMatch) {
206
- const insertPos = bodyMatch.index + bodyMatch[0].length;
207
- content = content.slice(0, insertPos) + "\n " + component + content.slice(insertPos);
208
- }
209
- fs.writeFileSync(layoutPath, content);
210
- return { file: layoutPath.replace(cwd + "/", "") };
211
- }
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" };
212
181
  }
213
- const appPaths = [
214
- path.join(cwd, "pages", "_app.tsx"),
215
- path.join(cwd, "pages", "_app.js"),
216
- path.join(cwd, "src", "pages", "_app.tsx"),
217
- path.join(cwd, "src", "pages", "_app.js")
218
- ];
219
- for (const appPath of appPaths) {
220
- if (fs.existsSync(appPath)) {
221
- let content = fs.readFileSync(appPath, "utf-8");
222
- if (content.includes("FeedbackWidget")) {
223
- return null;
224
- }
225
- const importMatch = content.match(/^(import .+\n)+/m);
226
- if (importMatch) {
227
- const lastImportEnd = importMatch.index + importMatch[0].length;
228
- content = content.slice(0, lastImportEnd) + importLine + "\n" + content.slice(lastImportEnd);
229
- }
230
- const componentMatch = content.match(/<Component\s+\{\.\.\.pageProps\}\s*\/>/);
231
- if (componentMatch) {
232
- const insertPos = componentMatch.index + componentMatch[0].length;
233
- content = content.slice(0, insertPos) + "\n " + component + content.slice(insertPos);
234
- }
235
- fs.writeFileSync(appPath, content);
236
- return { file: appPath.replace(cwd + "/", "") };
237
- }
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" };
238
184
  }
185
+ return { name: "Astro", file: "your main layout .astro file", note: "inside <body>", devCommand: "npm run dev" };
239
186
  }
240
- if (deps.vite || deps["react-scripts"] || deps.react) {
241
- const appPaths = [
242
- path.join(cwd, "src", "App.tsx"),
243
- path.join(cwd, "src", "App.jsx")
244
- ];
245
- for (const appPath of appPaths) {
246
- if (fs.existsSync(appPath)) {
247
- let content = fs.readFileSync(appPath, "utf-8");
248
- if (content.includes("FeedbackWidget")) {
249
- return null;
250
- }
251
- const importMatch = content.match(/^(import .+\n)+/m);
252
- if (importMatch) {
253
- const lastImportEnd = importMatch.index + importMatch[0].length;
254
- content = content.slice(0, lastImportEnd) + importLine + "\n" + content.slice(lastImportEnd);
255
- }
256
- const returnMatch = content.match(/return\s*\(\s*\n?\s*(<[A-Za-z>])/);
257
- if (returnMatch) {
258
- const match = content.match(/return\s*\(\s*\n?\s*<[A-Za-z]+[^>]*>/);
259
- if (match) {
260
- const insertPos = match.index + match[0].length;
261
- content = content.slice(0, insertPos) + "\n " + component + content.slice(insertPos);
262
- }
263
- }
264
- fs.writeFileSync(appPath, content);
265
- return { file: appPath.replace(cwd + "/", "") };
266
- }
267
- }
268
- }
269
- } catch {
270
- }
271
- return null;
272
- }
273
- function detectFramework() {
274
- const cwd = process.cwd();
275
- try {
276
- const pkgPath = path.join(cwd, "package.json");
277
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
278
- const deps = { ...pkg.dependencies, ...pkg.devDependencies };
279
- if (deps.next && fs.existsSync(path.join(cwd, "app"))) {
187
+ if (deps.next) {
280
188
  if (fs.existsSync(path.join(cwd, "app", "layout.tsx"))) {
281
- 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" };
282
190
  }
283
191
  if (fs.existsSync(path.join(cwd, "app", "layout.js"))) {
284
- 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" };
285
193
  }
286
194
  if (fs.existsSync(path.join(cwd, "src", "app", "layout.tsx"))) {
287
- 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" };
288
196
  }
289
- }
290
- if (deps.next && fs.existsSync(path.join(cwd, "pages"))) {
291
197
  if (fs.existsSync(path.join(cwd, "pages", "_app.tsx"))) {
292
- 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" };
293
199
  }
294
200
  if (fs.existsSync(path.join(cwd, "pages", "_app.js"))) {
295
- 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" };
296
207
  }
297
208
  }
298
209
  if (deps.vite && (deps.react || deps["@vitejs/plugin-react"])) {
299
210
  if (fs.existsSync(path.join(cwd, "src", "App.tsx"))) {
300
- 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" };
301
212
  }
302
213
  if (fs.existsSync(path.join(cwd, "src", "App.jsx"))) {
303
- 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" };
304
215
  }
305
216
  }
306
217
  if (deps["react-scripts"]) {
307
218
  if (fs.existsSync(path.join(cwd, "src", "App.tsx"))) {
308
- 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" };
309
220
  }
310
221
  if (fs.existsSync(path.join(cwd, "src", "App.js"))) {
311
- return { name: "Create React App", file: "src/App.js", note: "At end of App component" };
312
- }
313
- }
314
- if (deps.astro) {
315
- return { name: "Astro", file: "A React component", note: "Use client:load directive" };
316
- }
317
- if (deps["@remix-run/react"]) {
318
- if (fs.existsSync(path.join(cwd, "app", "root.tsx"))) {
319
- 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" };
320
223
  }
321
224
  }
322
225
  if (deps.react) {
323
- 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" };
324
227
  }
325
228
  } catch {
326
229
  }
327
- return { name: null, file: "Your root component" };
230
+ return { name: null, file: "your root component", note: "inside the main layout", devCommand: "npm run dev" };
328
231
  }
329
232
  async function copyToClipboard(text) {
330
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.0",
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",