@stephenov/feedbackwidget 0.3.1 → 0.3.5
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 +114 -85
- package/dist/cli.mjs +114 -85
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -27,6 +27,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
27
27
|
var import_http = __toESM(require("http"));
|
|
28
28
|
var import_open = __toESM(require("open"));
|
|
29
29
|
var import_crypto = require("crypto");
|
|
30
|
+
var import_child_process = require("child_process");
|
|
30
31
|
var import_fs = __toESM(require("fs"));
|
|
31
32
|
var import_path = __toESM(require("path"));
|
|
32
33
|
var API_BASE = "https://feedbackwidget-api.vercel.app";
|
|
@@ -44,13 +45,6 @@ async function main() {
|
|
|
44
45
|
}
|
|
45
46
|
async function initProject() {
|
|
46
47
|
console.log("\n\u{1F3A4} feedbackwidget - Voice-first feedback for your app\n");
|
|
47
|
-
const existingKey = getStoredApiKey();
|
|
48
|
-
if (existingKey) {
|
|
49
|
-
console.log("\u2705 Already configured!");
|
|
50
|
-
console.log(` API Key: ${existingKey.slice(0, 20)}...`);
|
|
51
|
-
console.log("\n To reconfigure, delete .feedbackwidgetrc and run again.\n");
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
48
|
const state = (0, import_crypto.randomBytes)(16).toString("hex");
|
|
55
49
|
const apiKey = await new Promise((resolve, reject) => {
|
|
56
50
|
const server = import_http.default.createServer((req, res) => {
|
|
@@ -94,49 +88,44 @@ async function initProject() {
|
|
|
94
88
|
reject(new Error("Authentication timed out"));
|
|
95
89
|
}, 5 * 60 * 1e3);
|
|
96
90
|
});
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
91
|
+
const snippet = `<FeedbackWidget apiKey="${apiKey}" />`;
|
|
92
|
+
let copied = false;
|
|
93
|
+
try {
|
|
94
|
+
await copyToClipboard(snippet);
|
|
95
|
+
copied = true;
|
|
96
|
+
} catch {
|
|
97
|
+
}
|
|
98
|
+
console.log("\u2705 Authenticated!\n");
|
|
99
|
+
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");
|
|
100
|
+
console.log(" Your API Key:\n");
|
|
101
|
+
console.log(` ${apiKey}
|
|
100
102
|
`);
|
|
101
|
-
|
|
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
|
+
if (copied) {
|
|
105
|
+
console.log(" \u{1F4CB} Copied to clipboard!\n");
|
|
106
|
+
}
|
|
107
|
+
const framework = detectFramework();
|
|
108
|
+
console.log(" Add to your app:\n");
|
|
109
|
+
console.log(' import { FeedbackWidget } from "@stephenov/feedbackwidget"\n');
|
|
110
|
+
console.log(` ${snippet}
|
|
103
111
|
`);
|
|
104
|
-
|
|
105
|
-
console.log(
|
|
106
|
-
} else {
|
|
107
|
-
console.log(" Add to your .env.local (or .env):\n");
|
|
108
|
-
console.log(` NEXT_PUBLIC_FEEDBACKWIDGET_API_KEY=${apiKey}
|
|
112
|
+
if (framework.name) {
|
|
113
|
+
console.log(` Detected: ${framework.name}
|
|
109
114
|
`);
|
|
110
|
-
console.log(
|
|
111
|
-
console.log(' import "@stephenov/feedbackwidget/auto"\n');
|
|
112
|
-
}
|
|
113
|
-
console.log(" Or use directly:\n");
|
|
114
|
-
console.log(' import { FeedbackWidget } from "@stephenov/feedbackwidget"');
|
|
115
|
-
console.log(` <FeedbackWidget apiKey="${apiKey}" />
|
|
115
|
+
console.log(` Add to: ${framework.file}
|
|
116
116
|
`);
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const apiKey = getStoredApiKey();
|
|
120
|
-
if (!apiKey) {
|
|
121
|
-
console.log("\nNot logged in. Run: npx @stephenov/feedbackwidget init\n");
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
try {
|
|
125
|
-
const res = await fetch(`${API_BASE}/api/v1/validate`, {
|
|
126
|
-
headers: { Authorization: `Bearer ${apiKey}` }
|
|
127
|
-
});
|
|
128
|
-
const data = await res.json();
|
|
129
|
-
if (data.valid) {
|
|
130
|
-
console.log(`
|
|
131
|
-
Logged in as: ${data.project?.name || "Unknown"}`);
|
|
132
|
-
console.log(`Plan: ${data.limits?.submissions || 100} submissions/month
|
|
117
|
+
if (framework.note) {
|
|
118
|
+
console.log(` Note: ${framework.note}
|
|
133
119
|
`);
|
|
134
|
-
} else {
|
|
135
|
-
console.log("\nInvalid API key. Run: npx @stephenov/feedbackwidget init\n");
|
|
136
120
|
}
|
|
137
|
-
}
|
|
138
|
-
console.log("
|
|
121
|
+
} else {
|
|
122
|
+
console.log(" Add to your root component (renders on every page)\n");
|
|
139
123
|
}
|
|
124
|
+
console.log(" Dashboard: https://feedbackwidget-api.vercel.app/dashboard\n");
|
|
125
|
+
}
|
|
126
|
+
async function whoami() {
|
|
127
|
+
console.log("\nRun 'npx feedbackwidget init' to get your API key.\n");
|
|
128
|
+
console.log("Then check your dashboard at: https://feedbackwidget-api.vercel.app/dashboard\n");
|
|
140
129
|
}
|
|
141
130
|
function showHelp() {
|
|
142
131
|
console.log(`
|
|
@@ -151,48 +140,6 @@ Usage:
|
|
|
151
140
|
npx @stephenov/feedbackwidget whoami
|
|
152
141
|
`);
|
|
153
142
|
}
|
|
154
|
-
function getStoredApiKey() {
|
|
155
|
-
const envPath = import_path.default.join(process.cwd(), ".env.local");
|
|
156
|
-
try {
|
|
157
|
-
const content = import_fs.default.readFileSync(envPath, "utf-8");
|
|
158
|
-
const match = content.match(/NEXT_PUBLIC_FEEDBACKWIDGET_API_KEY=(.+)/);
|
|
159
|
-
return match ? match[1].trim() : null;
|
|
160
|
-
} catch {
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
function saveApiKeyToEnv(apiKey) {
|
|
165
|
-
const envVar = `NEXT_PUBLIC_FEEDBACKWIDGET_API_KEY=${apiKey}`;
|
|
166
|
-
const envFiles = [".env.local", ".env"];
|
|
167
|
-
for (const envFile of envFiles) {
|
|
168
|
-
const envPath = import_path.default.join(process.cwd(), envFile);
|
|
169
|
-
try {
|
|
170
|
-
const content = import_fs.default.readFileSync(envPath, "utf-8");
|
|
171
|
-
if (content.includes("NEXT_PUBLIC_FEEDBACKWIDGET_API_KEY=")) {
|
|
172
|
-
const updated = content.replace(
|
|
173
|
-
/NEXT_PUBLIC_FEEDBACKWIDGET_API_KEY=.*/,
|
|
174
|
-
envVar
|
|
175
|
-
);
|
|
176
|
-
import_fs.default.writeFileSync(envPath, updated);
|
|
177
|
-
} else {
|
|
178
|
-
import_fs.default.appendFileSync(envPath, `
|
|
179
|
-
${envVar}
|
|
180
|
-
`);
|
|
181
|
-
}
|
|
182
|
-
return envFile;
|
|
183
|
-
} catch {
|
|
184
|
-
continue;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
try {
|
|
188
|
-
const envPath = import_path.default.join(process.cwd(), ".env.local");
|
|
189
|
-
import_fs.default.writeFileSync(envPath, `${envVar}
|
|
190
|
-
`);
|
|
191
|
-
return ".env.local";
|
|
192
|
-
} catch {
|
|
193
|
-
return null;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
143
|
function successPage() {
|
|
197
144
|
return `
|
|
198
145
|
<!DOCTYPE html>
|
|
@@ -239,4 +186,86 @@ function errorPage(error) {
|
|
|
239
186
|
</body>
|
|
240
187
|
</html>`;
|
|
241
188
|
}
|
|
189
|
+
function detectFramework() {
|
|
190
|
+
const cwd = process.cwd();
|
|
191
|
+
try {
|
|
192
|
+
const pkgPath = import_path.default.join(cwd, "package.json");
|
|
193
|
+
const pkg = JSON.parse(import_fs.default.readFileSync(pkgPath, "utf-8"));
|
|
194
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
195
|
+
if (deps.next && import_fs.default.existsSync(import_path.default.join(cwd, "app"))) {
|
|
196
|
+
if (import_fs.default.existsSync(import_path.default.join(cwd, "app", "layout.tsx"))) {
|
|
197
|
+
return { name: "Next.js (App Router)", file: "app/layout.tsx", note: "Add inside <body> before {children}" };
|
|
198
|
+
}
|
|
199
|
+
if (import_fs.default.existsSync(import_path.default.join(cwd, "app", "layout.js"))) {
|
|
200
|
+
return { name: "Next.js (App Router)", file: "app/layout.js", note: "Add inside <body> before {children}" };
|
|
201
|
+
}
|
|
202
|
+
if (import_fs.default.existsSync(import_path.default.join(cwd, "src", "app", "layout.tsx"))) {
|
|
203
|
+
return { name: "Next.js (App Router)", file: "src/app/layout.tsx", note: "Add inside <body> before {children}" };
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (deps.next && import_fs.default.existsSync(import_path.default.join(cwd, "pages"))) {
|
|
207
|
+
if (import_fs.default.existsSync(import_path.default.join(cwd, "pages", "_app.tsx"))) {
|
|
208
|
+
return { name: "Next.js (Pages Router)", file: "pages/_app.tsx", note: "Add after <Component {...pageProps} />" };
|
|
209
|
+
}
|
|
210
|
+
if (import_fs.default.existsSync(import_path.default.join(cwd, "pages", "_app.js"))) {
|
|
211
|
+
return { name: "Next.js (Pages Router)", file: "pages/_app.js", note: "Add after <Component {...pageProps} />" };
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (deps.vite && (deps.react || deps["@vitejs/plugin-react"])) {
|
|
215
|
+
if (import_fs.default.existsSync(import_path.default.join(cwd, "src", "App.tsx"))) {
|
|
216
|
+
return { name: "Vite + React", file: "src/App.tsx", note: "Add at the end of your App component" };
|
|
217
|
+
}
|
|
218
|
+
if (import_fs.default.existsSync(import_path.default.join(cwd, "src", "App.jsx"))) {
|
|
219
|
+
return { name: "Vite + React", file: "src/App.jsx", note: "Add at the end of your App component" };
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (deps["react-scripts"]) {
|
|
223
|
+
if (import_fs.default.existsSync(import_path.default.join(cwd, "src", "App.tsx"))) {
|
|
224
|
+
return { name: "Create React App", file: "src/App.tsx", note: "Add at the end of your App component" };
|
|
225
|
+
}
|
|
226
|
+
if (import_fs.default.existsSync(import_path.default.join(cwd, "src", "App.js"))) {
|
|
227
|
+
return { name: "Create React App", file: "src/App.js", note: "Add at the end of your App component" };
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (deps.astro) {
|
|
231
|
+
return { name: "Astro", file: "Create a React component", note: "Use client:load directive" };
|
|
232
|
+
}
|
|
233
|
+
if (deps["@remix-run/react"]) {
|
|
234
|
+
if (import_fs.default.existsSync(import_path.default.join(cwd, "app", "root.tsx"))) {
|
|
235
|
+
return { name: "Remix", file: "app/root.tsx", note: "Add inside <body>" };
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (deps.react) {
|
|
239
|
+
return { name: "React", file: "Your main App component", note: "Add anywhere it renders on every page" };
|
|
240
|
+
}
|
|
241
|
+
} catch {
|
|
242
|
+
}
|
|
243
|
+
return { name: null, file: "Your root component" };
|
|
244
|
+
}
|
|
245
|
+
async function copyToClipboard(text) {
|
|
246
|
+
return new Promise((resolve, reject) => {
|
|
247
|
+
const platform = process.platform;
|
|
248
|
+
let cmd;
|
|
249
|
+
let args = [];
|
|
250
|
+
if (platform === "darwin") {
|
|
251
|
+
cmd = "pbcopy";
|
|
252
|
+
} else if (platform === "linux") {
|
|
253
|
+
cmd = "xclip";
|
|
254
|
+
args = ["-selection", "clipboard"];
|
|
255
|
+
} else if (platform === "win32") {
|
|
256
|
+
cmd = "clip";
|
|
257
|
+
} else {
|
|
258
|
+
reject(new Error("Unsupported platform"));
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
const proc = (0, import_child_process.spawn)(cmd, args);
|
|
262
|
+
proc.stdin.write(text);
|
|
263
|
+
proc.stdin.end();
|
|
264
|
+
proc.on("close", (code) => {
|
|
265
|
+
if (code === 0) resolve();
|
|
266
|
+
else reject(new Error(`Clipboard failed with code ${code}`));
|
|
267
|
+
});
|
|
268
|
+
proc.on("error", reject);
|
|
269
|
+
});
|
|
270
|
+
}
|
|
242
271
|
main().catch(console.error);
|
package/dist/cli.mjs
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import http from "http";
|
|
5
5
|
import open from "open";
|
|
6
6
|
import { randomBytes } from "crypto";
|
|
7
|
+
import { spawn } from "child_process";
|
|
7
8
|
import fs from "fs";
|
|
8
9
|
import path from "path";
|
|
9
10
|
var API_BASE = "https://feedbackwidget-api.vercel.app";
|
|
@@ -21,13 +22,6 @@ async function main() {
|
|
|
21
22
|
}
|
|
22
23
|
async function initProject() {
|
|
23
24
|
console.log("\n\u{1F3A4} feedbackwidget - Voice-first feedback for your app\n");
|
|
24
|
-
const existingKey = getStoredApiKey();
|
|
25
|
-
if (existingKey) {
|
|
26
|
-
console.log("\u2705 Already configured!");
|
|
27
|
-
console.log(` API Key: ${existingKey.slice(0, 20)}...`);
|
|
28
|
-
console.log("\n To reconfigure, delete .feedbackwidgetrc and run again.\n");
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
25
|
const state = randomBytes(16).toString("hex");
|
|
32
26
|
const apiKey = await new Promise((resolve, reject) => {
|
|
33
27
|
const server = http.createServer((req, res) => {
|
|
@@ -71,49 +65,44 @@ async function initProject() {
|
|
|
71
65
|
reject(new Error("Authentication timed out"));
|
|
72
66
|
}, 5 * 60 * 1e3);
|
|
73
67
|
});
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
68
|
+
const snippet = `<FeedbackWidget apiKey="${apiKey}" />`;
|
|
69
|
+
let copied = false;
|
|
70
|
+
try {
|
|
71
|
+
await copyToClipboard(snippet);
|
|
72
|
+
copied = true;
|
|
73
|
+
} catch {
|
|
74
|
+
}
|
|
75
|
+
console.log("\u2705 Authenticated!\n");
|
|
76
|
+
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");
|
|
77
|
+
console.log(" Your API Key:\n");
|
|
78
|
+
console.log(` ${apiKey}
|
|
77
79
|
`);
|
|
78
|
-
|
|
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
|
+
if (copied) {
|
|
82
|
+
console.log(" \u{1F4CB} Copied to clipboard!\n");
|
|
83
|
+
}
|
|
84
|
+
const framework = detectFramework();
|
|
85
|
+
console.log(" Add to your app:\n");
|
|
86
|
+
console.log(' import { FeedbackWidget } from "@stephenov/feedbackwidget"\n');
|
|
87
|
+
console.log(` ${snippet}
|
|
80
88
|
`);
|
|
81
|
-
|
|
82
|
-
console.log(
|
|
83
|
-
} else {
|
|
84
|
-
console.log(" Add to your .env.local (or .env):\n");
|
|
85
|
-
console.log(` NEXT_PUBLIC_FEEDBACKWIDGET_API_KEY=${apiKey}
|
|
89
|
+
if (framework.name) {
|
|
90
|
+
console.log(` Detected: ${framework.name}
|
|
86
91
|
`);
|
|
87
|
-
console.log(
|
|
88
|
-
console.log(' import "@stephenov/feedbackwidget/auto"\n');
|
|
89
|
-
}
|
|
90
|
-
console.log(" Or use directly:\n");
|
|
91
|
-
console.log(' import { FeedbackWidget } from "@stephenov/feedbackwidget"');
|
|
92
|
-
console.log(` <FeedbackWidget apiKey="${apiKey}" />
|
|
92
|
+
console.log(` Add to: ${framework.file}
|
|
93
93
|
`);
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const apiKey = getStoredApiKey();
|
|
97
|
-
if (!apiKey) {
|
|
98
|
-
console.log("\nNot logged in. Run: npx @stephenov/feedbackwidget init\n");
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
try {
|
|
102
|
-
const res = await fetch(`${API_BASE}/api/v1/validate`, {
|
|
103
|
-
headers: { Authorization: `Bearer ${apiKey}` }
|
|
104
|
-
});
|
|
105
|
-
const data = await res.json();
|
|
106
|
-
if (data.valid) {
|
|
107
|
-
console.log(`
|
|
108
|
-
Logged in as: ${data.project?.name || "Unknown"}`);
|
|
109
|
-
console.log(`Plan: ${data.limits?.submissions || 100} submissions/month
|
|
94
|
+
if (framework.note) {
|
|
95
|
+
console.log(` Note: ${framework.note}
|
|
110
96
|
`);
|
|
111
|
-
} else {
|
|
112
|
-
console.log("\nInvalid API key. Run: npx @stephenov/feedbackwidget init\n");
|
|
113
97
|
}
|
|
114
|
-
}
|
|
115
|
-
console.log("
|
|
98
|
+
} else {
|
|
99
|
+
console.log(" Add to your root component (renders on every page)\n");
|
|
116
100
|
}
|
|
101
|
+
console.log(" Dashboard: https://feedbackwidget-api.vercel.app/dashboard\n");
|
|
102
|
+
}
|
|
103
|
+
async function whoami() {
|
|
104
|
+
console.log("\nRun 'npx feedbackwidget init' to get your API key.\n");
|
|
105
|
+
console.log("Then check your dashboard at: https://feedbackwidget-api.vercel.app/dashboard\n");
|
|
117
106
|
}
|
|
118
107
|
function showHelp() {
|
|
119
108
|
console.log(`
|
|
@@ -128,48 +117,6 @@ Usage:
|
|
|
128
117
|
npx @stephenov/feedbackwidget whoami
|
|
129
118
|
`);
|
|
130
119
|
}
|
|
131
|
-
function getStoredApiKey() {
|
|
132
|
-
const envPath = path.join(process.cwd(), ".env.local");
|
|
133
|
-
try {
|
|
134
|
-
const content = fs.readFileSync(envPath, "utf-8");
|
|
135
|
-
const match = content.match(/NEXT_PUBLIC_FEEDBACKWIDGET_API_KEY=(.+)/);
|
|
136
|
-
return match ? match[1].trim() : null;
|
|
137
|
-
} catch {
|
|
138
|
-
return null;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
function saveApiKeyToEnv(apiKey) {
|
|
142
|
-
const envVar = `NEXT_PUBLIC_FEEDBACKWIDGET_API_KEY=${apiKey}`;
|
|
143
|
-
const envFiles = [".env.local", ".env"];
|
|
144
|
-
for (const envFile of envFiles) {
|
|
145
|
-
const envPath = path.join(process.cwd(), envFile);
|
|
146
|
-
try {
|
|
147
|
-
const content = fs.readFileSync(envPath, "utf-8");
|
|
148
|
-
if (content.includes("NEXT_PUBLIC_FEEDBACKWIDGET_API_KEY=")) {
|
|
149
|
-
const updated = content.replace(
|
|
150
|
-
/NEXT_PUBLIC_FEEDBACKWIDGET_API_KEY=.*/,
|
|
151
|
-
envVar
|
|
152
|
-
);
|
|
153
|
-
fs.writeFileSync(envPath, updated);
|
|
154
|
-
} else {
|
|
155
|
-
fs.appendFileSync(envPath, `
|
|
156
|
-
${envVar}
|
|
157
|
-
`);
|
|
158
|
-
}
|
|
159
|
-
return envFile;
|
|
160
|
-
} catch {
|
|
161
|
-
continue;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
try {
|
|
165
|
-
const envPath = path.join(process.cwd(), ".env.local");
|
|
166
|
-
fs.writeFileSync(envPath, `${envVar}
|
|
167
|
-
`);
|
|
168
|
-
return ".env.local";
|
|
169
|
-
} catch {
|
|
170
|
-
return null;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
120
|
function successPage() {
|
|
174
121
|
return `
|
|
175
122
|
<!DOCTYPE html>
|
|
@@ -216,4 +163,86 @@ function errorPage(error) {
|
|
|
216
163
|
</body>
|
|
217
164
|
</html>`;
|
|
218
165
|
}
|
|
166
|
+
function detectFramework() {
|
|
167
|
+
const cwd = process.cwd();
|
|
168
|
+
try {
|
|
169
|
+
const pkgPath = path.join(cwd, "package.json");
|
|
170
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
171
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
172
|
+
if (deps.next && fs.existsSync(path.join(cwd, "app"))) {
|
|
173
|
+
if (fs.existsSync(path.join(cwd, "app", "layout.tsx"))) {
|
|
174
|
+
return { name: "Next.js (App Router)", file: "app/layout.tsx", note: "Add inside <body> before {children}" };
|
|
175
|
+
}
|
|
176
|
+
if (fs.existsSync(path.join(cwd, "app", "layout.js"))) {
|
|
177
|
+
return { name: "Next.js (App Router)", file: "app/layout.js", note: "Add inside <body> before {children}" };
|
|
178
|
+
}
|
|
179
|
+
if (fs.existsSync(path.join(cwd, "src", "app", "layout.tsx"))) {
|
|
180
|
+
return { name: "Next.js (App Router)", file: "src/app/layout.tsx", note: "Add inside <body> before {children}" };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (deps.next && fs.existsSync(path.join(cwd, "pages"))) {
|
|
184
|
+
if (fs.existsSync(path.join(cwd, "pages", "_app.tsx"))) {
|
|
185
|
+
return { name: "Next.js (Pages Router)", file: "pages/_app.tsx", note: "Add after <Component {...pageProps} />" };
|
|
186
|
+
}
|
|
187
|
+
if (fs.existsSync(path.join(cwd, "pages", "_app.js"))) {
|
|
188
|
+
return { name: "Next.js (Pages Router)", file: "pages/_app.js", note: "Add after <Component {...pageProps} />" };
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (deps.vite && (deps.react || deps["@vitejs/plugin-react"])) {
|
|
192
|
+
if (fs.existsSync(path.join(cwd, "src", "App.tsx"))) {
|
|
193
|
+
return { name: "Vite + React", file: "src/App.tsx", note: "Add at the end of your App component" };
|
|
194
|
+
}
|
|
195
|
+
if (fs.existsSync(path.join(cwd, "src", "App.jsx"))) {
|
|
196
|
+
return { name: "Vite + React", file: "src/App.jsx", note: "Add at the end of your App component" };
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (deps["react-scripts"]) {
|
|
200
|
+
if (fs.existsSync(path.join(cwd, "src", "App.tsx"))) {
|
|
201
|
+
return { name: "Create React App", file: "src/App.tsx", note: "Add at the end of your App component" };
|
|
202
|
+
}
|
|
203
|
+
if (fs.existsSync(path.join(cwd, "src", "App.js"))) {
|
|
204
|
+
return { name: "Create React App", file: "src/App.js", note: "Add at the end of your App component" };
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (deps.astro) {
|
|
208
|
+
return { name: "Astro", file: "Create a React component", note: "Use client:load directive" };
|
|
209
|
+
}
|
|
210
|
+
if (deps["@remix-run/react"]) {
|
|
211
|
+
if (fs.existsSync(path.join(cwd, "app", "root.tsx"))) {
|
|
212
|
+
return { name: "Remix", file: "app/root.tsx", note: "Add inside <body>" };
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (deps.react) {
|
|
216
|
+
return { name: "React", file: "Your main App component", note: "Add anywhere it renders on every page" };
|
|
217
|
+
}
|
|
218
|
+
} catch {
|
|
219
|
+
}
|
|
220
|
+
return { name: null, file: "Your root component" };
|
|
221
|
+
}
|
|
222
|
+
async function copyToClipboard(text) {
|
|
223
|
+
return new Promise((resolve, reject) => {
|
|
224
|
+
const platform = process.platform;
|
|
225
|
+
let cmd;
|
|
226
|
+
let args = [];
|
|
227
|
+
if (platform === "darwin") {
|
|
228
|
+
cmd = "pbcopy";
|
|
229
|
+
} else if (platform === "linux") {
|
|
230
|
+
cmd = "xclip";
|
|
231
|
+
args = ["-selection", "clipboard"];
|
|
232
|
+
} else if (platform === "win32") {
|
|
233
|
+
cmd = "clip";
|
|
234
|
+
} else {
|
|
235
|
+
reject(new Error("Unsupported platform"));
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const proc = spawn(cmd, args);
|
|
239
|
+
proc.stdin.write(text);
|
|
240
|
+
proc.stdin.end();
|
|
241
|
+
proc.on("close", (code) => {
|
|
242
|
+
if (code === 0) resolve();
|
|
243
|
+
else reject(new Error(`Clipboard failed with code ${code}`));
|
|
244
|
+
});
|
|
245
|
+
proc.on("error", reject);
|
|
246
|
+
});
|
|
247
|
+
}
|
|
219
248
|
main().catch(console.error);
|