@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 +56 -165
- package/dist/cli.mjs +56 -165
- package/package.json +1 -1
- package/dist/cli.d.mts +0 -1
- package/dist/cli.d.ts +0 -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,170 +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 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
|
-
|
|
249
|
-
|
|
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.
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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
|
-
|
|
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,170 +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 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
|
-
|
|
226
|
-
|
|
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.
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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
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
|