@stephenov/feedbackwidget 0.5.1 → 0.7.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 +82 -166
- package/dist/cli.mjs +82 -166
- 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,71 @@ async function initProject() {
|
|
|
88
88
|
reject(new Error("Authentication timed out"));
|
|
89
89
|
}, 5 * 60 * 1e3);
|
|
90
90
|
});
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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}
|
|
91
|
+
const framework = detectFramework();
|
|
92
|
+
const cwd = process.cwd();
|
|
93
|
+
const fullPath = framework.fullPath ? import_path.default.join(cwd, framework.fullPath) : null;
|
|
94
|
+
const targetFile = fullPath || framework.file;
|
|
95
|
+
const isAstro = framework.name === "Astro";
|
|
96
|
+
const importLine = 'import { FeedbackWidget } from "@stephenov/feedbackwidget";';
|
|
97
|
+
const componentSnippet = isAstro ? `<FeedbackWidget client:load apiKey="${apiKey}" />` : `<FeedbackWidget apiKey="${apiKey}" />`;
|
|
98
|
+
console.log("\n\u2705 Authenticated!\n");
|
|
99
|
+
if (framework.name) {
|
|
100
|
+
console.log(`Detected: ${framework.name}
|
|
111
101
|
`);
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
console.log(" Add to your app:\n");
|
|
118
|
-
console.log(' import { FeedbackWidget } from "@stephenov/feedbackwidget"\n');
|
|
119
|
-
console.log(` ${snippet}
|
|
102
|
+
}
|
|
103
|
+
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");
|
|
104
|
+
console.log("STEP 1: Add the import\n");
|
|
105
|
+
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");
|
|
106
|
+
console.log(`Open: ${targetFile}
|
|
120
107
|
`);
|
|
121
|
-
|
|
122
|
-
|
|
108
|
+
if (isAstro) {
|
|
109
|
+
console.log("Add this inside the --- frontmatter block:\n");
|
|
110
|
+
} else {
|
|
111
|
+
console.log("Add this with your other imports:\n");
|
|
112
|
+
}
|
|
113
|
+
console.log(` ${importLine}
|
|
123
114
|
`);
|
|
124
|
-
|
|
115
|
+
await waitForEnter("Press ENTER when you've added the import...");
|
|
116
|
+
console.log("\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\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");
|
|
117
|
+
console.log("STEP 2: Add the component\n");
|
|
118
|
+
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");
|
|
119
|
+
console.log(`In the same file, add this ${framework.note || "inside <body>"}:
|
|
125
120
|
`);
|
|
126
|
-
|
|
127
|
-
console.log(` Note: ${framework.note}
|
|
121
|
+
console.log(` ${componentSnippet}
|
|
128
122
|
`);
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
123
|
+
try {
|
|
124
|
+
await copyToClipboard(componentSnippet);
|
|
125
|
+
console.log(" \u{1F4CB} Copied to clipboard!\n");
|
|
126
|
+
} catch {
|
|
127
|
+
}
|
|
128
|
+
await waitForEnter("Press ENTER when you've added the component...");
|
|
129
|
+
let verified = false;
|
|
130
|
+
if (fullPath && import_fs.default.existsSync(fullPath)) {
|
|
131
|
+
const content = import_fs.default.readFileSync(fullPath, "utf-8");
|
|
132
|
+
if (content.includes("FeedbackWidget")) {
|
|
133
|
+
verified = true;
|
|
132
134
|
}
|
|
133
135
|
}
|
|
134
|
-
|
|
136
|
+
if (verified) {
|
|
137
|
+
console.log("\n\u2705 Found FeedbackWidget in your file!\n");
|
|
138
|
+
}
|
|
139
|
+
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");
|
|
140
|
+
console.log("\u{1F389} SETUP COMPLETE!\n");
|
|
141
|
+
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");
|
|
142
|
+
console.log(`Run: ${framework.devCommand || "npm run dev"}
|
|
143
|
+
`);
|
|
144
|
+
console.log("Refresh your browser - the widget will appear in the bottom-right.\n");
|
|
145
|
+
console.log("View feedback at: https://feedbackwidget-api.vercel.app/dashboard\n");
|
|
146
|
+
}
|
|
147
|
+
function waitForEnter(prompt) {
|
|
148
|
+
return new Promise((resolve) => {
|
|
149
|
+
process.stdout.write(prompt);
|
|
150
|
+
process.stdin.setRawMode?.(false);
|
|
151
|
+
process.stdin.resume();
|
|
152
|
+
process.stdin.once("data", () => {
|
|
153
|
+
resolve();
|
|
154
|
+
});
|
|
155
|
+
});
|
|
135
156
|
}
|
|
136
157
|
async function whoami() {
|
|
137
158
|
console.log("\nRun 'npx feedbackwidget init' to get your API key.\n");
|
|
@@ -196,170 +217,65 @@ function errorPage(error) {
|
|
|
196
217
|
</body>
|
|
197
218
|
</html>`;
|
|
198
219
|
}
|
|
199
|
-
function
|
|
220
|
+
function detectFramework() {
|
|
200
221
|
const cwd = process.cwd();
|
|
201
|
-
const importLine = 'import { FeedbackWidget } from "@stephenov/feedbackwidget";';
|
|
202
|
-
const component = `<FeedbackWidget apiKey="${apiKey}" />`;
|
|
203
222
|
try {
|
|
204
223
|
const pkgPath = import_path.default.join(cwd, "package.json");
|
|
205
224
|
const pkg = JSON.parse(import_fs.default.readFileSync(pkgPath, "utf-8"));
|
|
206
225
|
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
|
-
}
|
|
247
|
-
}
|
|
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
|
-
}
|
|
226
|
+
if (deps.astro) {
|
|
227
|
+
if (import_fs.default.existsSync(import_path.default.join(cwd, "src", "layouts", "Layout.astro"))) {
|
|
228
|
+
return { name: "Astro", file: "src/layouts/Layout.astro", fullPath: "src/layouts/Layout.astro", note: "inside <body>", devCommand: "npm run dev" };
|
|
273
229
|
}
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
}
|
|
230
|
+
if (import_fs.default.existsSync(import_path.default.join(cwd, "src", "layouts", "BaseLayout.astro"))) {
|
|
231
|
+
return { name: "Astro", file: "src/layouts/BaseLayout.astro", fullPath: "src/layouts/BaseLayout.astro", note: "inside <body>", devCommand: "npm run dev" };
|
|
302
232
|
}
|
|
233
|
+
return { name: "Astro", file: "your main layout .astro file", note: "inside <body>", devCommand: "npm run dev" };
|
|
303
234
|
}
|
|
304
|
-
|
|
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"))) {
|
|
235
|
+
if (deps.next) {
|
|
315
236
|
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: "
|
|
237
|
+
return { name: "Next.js (App Router)", file: "app/layout.tsx", fullPath: "app/layout.tsx", note: "inside <body>, before {children}", devCommand: "npm run dev" };
|
|
317
238
|
}
|
|
318
239
|
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: "
|
|
240
|
+
return { name: "Next.js (App Router)", file: "app/layout.js", fullPath: "app/layout.js", note: "inside <body>, before {children}", devCommand: "npm run dev" };
|
|
320
241
|
}
|
|
321
242
|
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: "
|
|
243
|
+
return { name: "Next.js (App Router)", file: "src/app/layout.tsx", fullPath: "src/app/layout.tsx", note: "inside <body>, before {children}", devCommand: "npm run dev" };
|
|
323
244
|
}
|
|
324
|
-
}
|
|
325
|
-
if (deps.next && import_fs.default.existsSync(import_path.default.join(cwd, "pages"))) {
|
|
326
245
|
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: "
|
|
246
|
+
return { name: "Next.js (Pages Router)", file: "pages/_app.tsx", fullPath: "pages/_app.tsx", note: "after <Component {...pageProps} />", devCommand: "npm run dev" };
|
|
328
247
|
}
|
|
329
248
|
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: "
|
|
249
|
+
return { name: "Next.js (Pages Router)", file: "pages/_app.js", fullPath: "pages/_app.js", note: "after <Component {...pageProps} />", devCommand: "npm run dev" };
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (deps["@remix-run/react"]) {
|
|
253
|
+
if (import_fs.default.existsSync(import_path.default.join(cwd, "app", "root.tsx"))) {
|
|
254
|
+
return { name: "Remix", file: "app/root.tsx", fullPath: "app/root.tsx", note: "inside <body>", devCommand: "npm run dev" };
|
|
331
255
|
}
|
|
332
256
|
}
|
|
333
257
|
if (deps.vite && (deps.react || deps["@vitejs/plugin-react"])) {
|
|
334
258
|
if (import_fs.default.existsSync(import_path.default.join(cwd, "src", "App.tsx"))) {
|
|
335
|
-
return { name: "Vite + React", file: "src/App.tsx", note: "
|
|
259
|
+
return { name: "Vite + React", file: "src/App.tsx", fullPath: "src/App.tsx", note: "at the end of your App component", devCommand: "npm run dev" };
|
|
336
260
|
}
|
|
337
261
|
if (import_fs.default.existsSync(import_path.default.join(cwd, "src", "App.jsx"))) {
|
|
338
|
-
return { name: "Vite + React", file: "src/App.jsx", note: "
|
|
262
|
+
return { name: "Vite + React", file: "src/App.jsx", fullPath: "src/App.jsx", note: "at the end of your App component", devCommand: "npm run dev" };
|
|
339
263
|
}
|
|
340
264
|
}
|
|
341
265
|
if (deps["react-scripts"]) {
|
|
342
266
|
if (import_fs.default.existsSync(import_path.default.join(cwd, "src", "App.tsx"))) {
|
|
343
|
-
return { name: "Create React App", file: "src/App.tsx", note: "
|
|
267
|
+
return { name: "Create React App", file: "src/App.tsx", fullPath: "src/App.tsx", note: "at the end of your App component", devCommand: "npm start" };
|
|
344
268
|
}
|
|
345
269
|
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>" };
|
|
270
|
+
return { name: "Create React App", file: "src/App.js", fullPath: "src/App.js", note: "at the end of your App component", devCommand: "npm start" };
|
|
355
271
|
}
|
|
356
272
|
}
|
|
357
273
|
if (deps.react) {
|
|
358
|
-
return { name: "React", file: "
|
|
274
|
+
return { name: "React", file: "your main App component", note: "where it renders on every page", devCommand: "npm run dev" };
|
|
359
275
|
}
|
|
360
276
|
} catch {
|
|
361
277
|
}
|
|
362
|
-
return { name: null, file: "
|
|
278
|
+
return { name: null, file: "your root component", note: "inside the main layout", devCommand: "npm run dev" };
|
|
363
279
|
}
|
|
364
280
|
async function copyToClipboard(text) {
|
|
365
281
|
return new Promise((resolve, reject) => {
|
package/dist/cli.mjs
CHANGED
|
@@ -65,50 +65,71 @@ async function initProject() {
|
|
|
65
65
|
reject(new Error("Authentication timed out"));
|
|
66
66
|
}, 5 * 60 * 1e3);
|
|
67
67
|
});
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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}
|
|
68
|
+
const framework = detectFramework();
|
|
69
|
+
const cwd = process.cwd();
|
|
70
|
+
const fullPath = framework.fullPath ? path.join(cwd, framework.fullPath) : null;
|
|
71
|
+
const targetFile = fullPath || framework.file;
|
|
72
|
+
const isAstro = framework.name === "Astro";
|
|
73
|
+
const importLine = 'import { FeedbackWidget } from "@stephenov/feedbackwidget";';
|
|
74
|
+
const componentSnippet = isAstro ? `<FeedbackWidget client:load apiKey="${apiKey}" />` : `<FeedbackWidget apiKey="${apiKey}" />`;
|
|
75
|
+
console.log("\n\u2705 Authenticated!\n");
|
|
76
|
+
if (framework.name) {
|
|
77
|
+
console.log(`Detected: ${framework.name}
|
|
88
78
|
`);
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
console.log(" Add to your app:\n");
|
|
95
|
-
console.log(' import { FeedbackWidget } from "@stephenov/feedbackwidget"\n');
|
|
96
|
-
console.log(` ${snippet}
|
|
79
|
+
}
|
|
80
|
+
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");
|
|
81
|
+
console.log("STEP 1: Add the import\n");
|
|
82
|
+
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");
|
|
83
|
+
console.log(`Open: ${targetFile}
|
|
97
84
|
`);
|
|
98
|
-
|
|
99
|
-
|
|
85
|
+
if (isAstro) {
|
|
86
|
+
console.log("Add this inside the --- frontmatter block:\n");
|
|
87
|
+
} else {
|
|
88
|
+
console.log("Add this with your other imports:\n");
|
|
89
|
+
}
|
|
90
|
+
console.log(` ${importLine}
|
|
100
91
|
`);
|
|
101
|
-
|
|
92
|
+
await waitForEnter("Press ENTER when you've added the import...");
|
|
93
|
+
console.log("\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\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");
|
|
94
|
+
console.log("STEP 2: Add the component\n");
|
|
95
|
+
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");
|
|
96
|
+
console.log(`In the same file, add this ${framework.note || "inside <body>"}:
|
|
102
97
|
`);
|
|
103
|
-
|
|
104
|
-
console.log(` Note: ${framework.note}
|
|
98
|
+
console.log(` ${componentSnippet}
|
|
105
99
|
`);
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
100
|
+
try {
|
|
101
|
+
await copyToClipboard(componentSnippet);
|
|
102
|
+
console.log(" \u{1F4CB} Copied to clipboard!\n");
|
|
103
|
+
} catch {
|
|
104
|
+
}
|
|
105
|
+
await waitForEnter("Press ENTER when you've added the component...");
|
|
106
|
+
let verified = false;
|
|
107
|
+
if (fullPath && fs.existsSync(fullPath)) {
|
|
108
|
+
const content = fs.readFileSync(fullPath, "utf-8");
|
|
109
|
+
if (content.includes("FeedbackWidget")) {
|
|
110
|
+
verified = true;
|
|
109
111
|
}
|
|
110
112
|
}
|
|
111
|
-
|
|
113
|
+
if (verified) {
|
|
114
|
+
console.log("\n\u2705 Found FeedbackWidget in your file!\n");
|
|
115
|
+
}
|
|
116
|
+
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");
|
|
117
|
+
console.log("\u{1F389} SETUP COMPLETE!\n");
|
|
118
|
+
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");
|
|
119
|
+
console.log(`Run: ${framework.devCommand || "npm run dev"}
|
|
120
|
+
`);
|
|
121
|
+
console.log("Refresh your browser - the widget will appear in the bottom-right.\n");
|
|
122
|
+
console.log("View feedback at: https://feedbackwidget-api.vercel.app/dashboard\n");
|
|
123
|
+
}
|
|
124
|
+
function waitForEnter(prompt) {
|
|
125
|
+
return new Promise((resolve) => {
|
|
126
|
+
process.stdout.write(prompt);
|
|
127
|
+
process.stdin.setRawMode?.(false);
|
|
128
|
+
process.stdin.resume();
|
|
129
|
+
process.stdin.once("data", () => {
|
|
130
|
+
resolve();
|
|
131
|
+
});
|
|
132
|
+
});
|
|
112
133
|
}
|
|
113
134
|
async function whoami() {
|
|
114
135
|
console.log("\nRun 'npx feedbackwidget init' to get your API key.\n");
|
|
@@ -173,170 +194,65 @@ function errorPage(error) {
|
|
|
173
194
|
</body>
|
|
174
195
|
</html>`;
|
|
175
196
|
}
|
|
176
|
-
function
|
|
197
|
+
function detectFramework() {
|
|
177
198
|
const cwd = process.cwd();
|
|
178
|
-
const importLine = 'import { FeedbackWidget } from "@stephenov/feedbackwidget";';
|
|
179
|
-
const component = `<FeedbackWidget apiKey="${apiKey}" />`;
|
|
180
199
|
try {
|
|
181
200
|
const pkgPath = path.join(cwd, "package.json");
|
|
182
201
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
183
202
|
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
|
-
}
|
|
224
|
-
}
|
|
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
|
-
}
|
|
203
|
+
if (deps.astro) {
|
|
204
|
+
if (fs.existsSync(path.join(cwd, "src", "layouts", "Layout.astro"))) {
|
|
205
|
+
return { name: "Astro", file: "src/layouts/Layout.astro", fullPath: "src/layouts/Layout.astro", note: "inside <body>", devCommand: "npm run dev" };
|
|
250
206
|
}
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
}
|
|
207
|
+
if (fs.existsSync(path.join(cwd, "src", "layouts", "BaseLayout.astro"))) {
|
|
208
|
+
return { name: "Astro", file: "src/layouts/BaseLayout.astro", fullPath: "src/layouts/BaseLayout.astro", note: "inside <body>", devCommand: "npm run dev" };
|
|
279
209
|
}
|
|
210
|
+
return { name: "Astro", file: "your main layout .astro file", note: "inside <body>", devCommand: "npm run dev" };
|
|
280
211
|
}
|
|
281
|
-
|
|
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"))) {
|
|
212
|
+
if (deps.next) {
|
|
292
213
|
if (fs.existsSync(path.join(cwd, "app", "layout.tsx"))) {
|
|
293
|
-
return { name: "Next.js (App Router)", file: "app/layout.tsx", note: "
|
|
214
|
+
return { name: "Next.js (App Router)", file: "app/layout.tsx", fullPath: "app/layout.tsx", note: "inside <body>, before {children}", devCommand: "npm run dev" };
|
|
294
215
|
}
|
|
295
216
|
if (fs.existsSync(path.join(cwd, "app", "layout.js"))) {
|
|
296
|
-
return { name: "Next.js (App Router)", file: "app/layout.js", note: "
|
|
217
|
+
return { name: "Next.js (App Router)", file: "app/layout.js", fullPath: "app/layout.js", note: "inside <body>, before {children}", devCommand: "npm run dev" };
|
|
297
218
|
}
|
|
298
219
|
if (fs.existsSync(path.join(cwd, "src", "app", "layout.tsx"))) {
|
|
299
|
-
return { name: "Next.js (App Router)", file: "src/app/layout.tsx", note: "
|
|
220
|
+
return { name: "Next.js (App Router)", file: "src/app/layout.tsx", fullPath: "src/app/layout.tsx", note: "inside <body>, before {children}", devCommand: "npm run dev" };
|
|
300
221
|
}
|
|
301
|
-
}
|
|
302
|
-
if (deps.next && fs.existsSync(path.join(cwd, "pages"))) {
|
|
303
222
|
if (fs.existsSync(path.join(cwd, "pages", "_app.tsx"))) {
|
|
304
|
-
return { name: "Next.js (Pages Router)", file: "pages/_app.tsx", note: "
|
|
223
|
+
return { name: "Next.js (Pages Router)", file: "pages/_app.tsx", fullPath: "pages/_app.tsx", note: "after <Component {...pageProps} />", devCommand: "npm run dev" };
|
|
305
224
|
}
|
|
306
225
|
if (fs.existsSync(path.join(cwd, "pages", "_app.js"))) {
|
|
307
|
-
return { name: "Next.js (Pages Router)", file: "pages/_app.js", note: "
|
|
226
|
+
return { name: "Next.js (Pages Router)", file: "pages/_app.js", fullPath: "pages/_app.js", note: "after <Component {...pageProps} />", devCommand: "npm run dev" };
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (deps["@remix-run/react"]) {
|
|
230
|
+
if (fs.existsSync(path.join(cwd, "app", "root.tsx"))) {
|
|
231
|
+
return { name: "Remix", file: "app/root.tsx", fullPath: "app/root.tsx", note: "inside <body>", devCommand: "npm run dev" };
|
|
308
232
|
}
|
|
309
233
|
}
|
|
310
234
|
if (deps.vite && (deps.react || deps["@vitejs/plugin-react"])) {
|
|
311
235
|
if (fs.existsSync(path.join(cwd, "src", "App.tsx"))) {
|
|
312
|
-
return { name: "Vite + React", file: "src/App.tsx", note: "
|
|
236
|
+
return { name: "Vite + React", file: "src/App.tsx", fullPath: "src/App.tsx", note: "at the end of your App component", devCommand: "npm run dev" };
|
|
313
237
|
}
|
|
314
238
|
if (fs.existsSync(path.join(cwd, "src", "App.jsx"))) {
|
|
315
|
-
return { name: "Vite + React", file: "src/App.jsx", note: "
|
|
239
|
+
return { name: "Vite + React", file: "src/App.jsx", fullPath: "src/App.jsx", note: "at the end of your App component", devCommand: "npm run dev" };
|
|
316
240
|
}
|
|
317
241
|
}
|
|
318
242
|
if (deps["react-scripts"]) {
|
|
319
243
|
if (fs.existsSync(path.join(cwd, "src", "App.tsx"))) {
|
|
320
|
-
return { name: "Create React App", file: "src/App.tsx", note: "
|
|
244
|
+
return { name: "Create React App", file: "src/App.tsx", fullPath: "src/App.tsx", note: "at the end of your App component", devCommand: "npm start" };
|
|
321
245
|
}
|
|
322
246
|
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>" };
|
|
247
|
+
return { name: "Create React App", file: "src/App.js", fullPath: "src/App.js", note: "at the end of your App component", devCommand: "npm start" };
|
|
332
248
|
}
|
|
333
249
|
}
|
|
334
250
|
if (deps.react) {
|
|
335
|
-
return { name: "React", file: "
|
|
251
|
+
return { name: "React", file: "your main App component", note: "where it renders on every page", devCommand: "npm run dev" };
|
|
336
252
|
}
|
|
337
253
|
} catch {
|
|
338
254
|
}
|
|
339
|
-
return { name: null, file: "
|
|
255
|
+
return { name: null, file: "your root component", note: "inside the main layout", devCommand: "npm run dev" };
|
|
340
256
|
}
|
|
341
257
|
async function copyToClipboard(text) {
|
|
342
258
|
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
|