@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.
- package/dist/cli.js +56 -153
- package/dist/cli.mjs +56 -153
- 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
|
-
|
|
93
|
-
if (
|
|
94
|
-
console.log(
|
|
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
|
-
|
|
99
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
117
|
+
} else {
|
|
118
|
+
console.log(` ${snippet}
|
|
125
119
|
`);
|
|
126
|
-
|
|
127
|
-
|
|
120
|
+
}
|
|
121
|
+
console.log(" Step 3: Run your dev server\n");
|
|
122
|
+
console.log(` ${framework.devCommand || "npm run dev"}
|
|
128
123
|
`);
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
|
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.
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
237
|
-
|
|
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.
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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
|
-
|
|
70
|
-
if (
|
|
71
|
-
console.log(
|
|
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
|
-
|
|
76
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
94
|
+
} else {
|
|
95
|
+
console.log(` ${snippet}
|
|
102
96
|
`);
|
|
103
|
-
|
|
104
|
-
|
|
97
|
+
}
|
|
98
|
+
console.log(" Step 3: Run your dev server\n");
|
|
99
|
+
console.log(` ${framework.devCommand || "npm run dev"}
|
|
105
100
|
`);
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
|
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.
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
|
|
214
|
-
|
|
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.
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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) => {
|