@vicket/create-support 0.1.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/README.md +53 -0
- package/bin/create-vicket-support.js +374 -0
- package/package.json +13 -0
- package/templates/next/src/app/support/page.tsx +398 -0
- package/templates/next/src/app/ticket/page.tsx +250 -0
- package/templates/next/src/app/vicket.css +273 -0
- package/templates/nuxt/assets/css/vicket.css +273 -0
- package/templates/nuxt/pages/support.vue +351 -0
- package/templates/nuxt/pages/ticket.vue +238 -0
- package/templates/react/src/vicket/SupportPage.tsx +399 -0
- package/templates/react/src/vicket/TicketPage.tsx +247 -0
- package/templates/react/src/vicket/vicket.css +273 -0
- package/templates/sveltekit/src/lib/vicket.css +273 -0
- package/templates/sveltekit/src/routes/support/+page.svelte +338 -0
- package/templates/sveltekit/src/routes/ticket/+page.svelte +242 -0
- package/templates/vue/src/vicket/SupportPage.vue +348 -0
- package/templates/vue/src/vicket/TicketPage.vue +235 -0
- package/templates/vue/src/vicket/vicket.css +273 -0
- package/templates-tailwind/next/src/app/vicket.css +162 -0
- package/templates-tailwind/nuxt/assets/css/vicket.css +162 -0
- package/templates-tailwind/react/src/vicket/vicket.css +162 -0
- package/templates-tailwind/sveltekit/src/lib/vicket.css +162 -0
- package/templates-tailwind/vue/src/vicket/vicket.css +162 -0
package/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# @vicket/create-support
|
|
2
|
+
|
|
3
|
+
Scaffold white-label support pages into a client app:
|
|
4
|
+
|
|
5
|
+
- `/support`: create a ticket
|
|
6
|
+
- `/ticket?token=...`: read and reply to a ticket
|
|
7
|
+
|
|
8
|
+
No shadcn requirement and no WYSIWYG dependency.
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npx @vicket/create-support --framework next
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Without `--framework`, the CLI opens a guided selection menu.
|
|
17
|
+
Without `--styling`, the CLI defaults to `css` (or asks in interactive mode).
|
|
18
|
+
|
|
19
|
+
Supported frameworks:
|
|
20
|
+
|
|
21
|
+
- `next`
|
|
22
|
+
- `nuxt`
|
|
23
|
+
- `react` (Vite / client router)
|
|
24
|
+
- `vue` (Vue 3 Vite / client router)
|
|
25
|
+
- `sveltekit` (or `svelte`)
|
|
26
|
+
|
|
27
|
+
Options:
|
|
28
|
+
|
|
29
|
+
- `--framework` / `-f`
|
|
30
|
+
- `--styling` / `-s` (`css` or `tailwind`)
|
|
31
|
+
- `--tailwind` (shortcut for `--styling tailwind`)
|
|
32
|
+
- `--dir` / `-d` output directory (default: current directory)
|
|
33
|
+
- `--yes` / `-y` overwrite files without prompt
|
|
34
|
+
- `--help` / `-h` show usage
|
|
35
|
+
|
|
36
|
+
Tailwind mode:
|
|
37
|
+
|
|
38
|
+
- Generates `vicket.css` using Tailwind directives and `@apply`
|
|
39
|
+
- Requires Tailwind CSS to already be configured in the target app
|
|
40
|
+
|
|
41
|
+
Direct API mode:
|
|
42
|
+
|
|
43
|
+
- All templates call Vicket public endpoints directly from the browser
|
|
44
|
+
|
|
45
|
+
## Required env vars in generated app
|
|
46
|
+
|
|
47
|
+
- `next`: `NEXT_PUBLIC_VICKET_API_URL`, `NEXT_PUBLIC_VICKET_API_KEY`
|
|
48
|
+
- `nuxt`: `NUXT_PUBLIC_VICKET_API_URL`, `NUXT_PUBLIC_VICKET_API_KEY`
|
|
49
|
+
- `sveltekit`: `PUBLIC_VICKET_API_URL`, `PUBLIC_VICKET_API_KEY`
|
|
50
|
+
- `react`: `VITE_VICKET_API_URL`, `VITE_VICKET_API_KEY`
|
|
51
|
+
- `vue`: `VITE_VICKET_API_URL`, `VITE_VICKET_API_KEY`
|
|
52
|
+
|
|
53
|
+
Example URL value: `https://api.vicket.io/api/v1`
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const path = require("node:path");
|
|
5
|
+
const { stdin } = require("node:process");
|
|
6
|
+
|
|
7
|
+
const FRAMEWORK_ALIASES = {
|
|
8
|
+
next: "next",
|
|
9
|
+
nextjs: "next",
|
|
10
|
+
nuxt: "nuxt",
|
|
11
|
+
react: "react",
|
|
12
|
+
reactjs: "react",
|
|
13
|
+
vue: "vue",
|
|
14
|
+
vuejs: "vue",
|
|
15
|
+
svelte: "sveltekit",
|
|
16
|
+
sveltekit: "sveltekit",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const FRAMEWORK_CHOICES = [
|
|
20
|
+
{ value: "next", label: "Next.js (App Router)" },
|
|
21
|
+
{ value: "nuxt", label: "Nuxt 3 (Vue)" },
|
|
22
|
+
{ value: "react", label: "React (Vite / client router)" },
|
|
23
|
+
{ value: "vue", label: "Vue 3 (Vite / client router)" },
|
|
24
|
+
{ value: "sveltekit", label: "SvelteKit" },
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const DEFAULT_FRAMEWORK = "next";
|
|
28
|
+
const STYLING_ALIASES = {
|
|
29
|
+
css: "css",
|
|
30
|
+
tailwind: "tailwind",
|
|
31
|
+
tw: "tailwind",
|
|
32
|
+
};
|
|
33
|
+
const STYLING_CHOICES = [
|
|
34
|
+
{ value: "css", label: "CSS file (default)" },
|
|
35
|
+
{ value: "tailwind", label: "Tailwind CSS (@apply)" },
|
|
36
|
+
];
|
|
37
|
+
const DEFAULT_STYLING = "css";
|
|
38
|
+
let clack = null;
|
|
39
|
+
|
|
40
|
+
async function loadClack() {
|
|
41
|
+
if (clack) return clack;
|
|
42
|
+
const prompts = await import("@clack/prompts");
|
|
43
|
+
clack = prompts;
|
|
44
|
+
return prompts;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function printHelp() {
|
|
48
|
+
console.log(`
|
|
49
|
+
Usage:
|
|
50
|
+
create-vicket-support [options]
|
|
51
|
+
|
|
52
|
+
Options:
|
|
53
|
+
-f, --framework <name> Framework: next | nuxt | react | vue | sveltekit
|
|
54
|
+
-s, --styling <name> Styling: css | tailwind (default: css)
|
|
55
|
+
--tailwind Shortcut for --styling tailwind
|
|
56
|
+
-d, --dir <path> Output directory (default: current directory)
|
|
57
|
+
-y, --yes Overwrite existing files without prompt
|
|
58
|
+
-h, --help Show help
|
|
59
|
+
|
|
60
|
+
Examples:
|
|
61
|
+
npx @vicket/create-support --framework next
|
|
62
|
+
npx @vicket/create-support --framework next --styling tailwind
|
|
63
|
+
npx @vicket/create-support --framework nuxt --dir ./my-app
|
|
64
|
+
npx @vicket/create-support -f sveltekit -d . -y --tailwind
|
|
65
|
+
`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function parseArgs(argv) {
|
|
69
|
+
const options = {
|
|
70
|
+
framework: "",
|
|
71
|
+
styling: "",
|
|
72
|
+
dir: process.cwd(),
|
|
73
|
+
yes: false,
|
|
74
|
+
help: false,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
78
|
+
const arg = argv[i];
|
|
79
|
+
if (arg === "--help" || arg === "-h") {
|
|
80
|
+
options.help = true;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (arg === "--framework" || arg === "-f") {
|
|
84
|
+
options.framework = argv[i + 1] || "";
|
|
85
|
+
i += 1;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (arg.startsWith("--framework=")) {
|
|
89
|
+
options.framework = arg.split("=")[1] || "";
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (arg === "--styling" || arg === "-s") {
|
|
93
|
+
options.styling = argv[i + 1] || options.styling;
|
|
94
|
+
i += 1;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (arg.startsWith("--styling=")) {
|
|
98
|
+
options.styling = arg.split("=")[1] || options.styling;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (arg === "--tailwind") {
|
|
102
|
+
options.styling = "tailwind";
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (arg === "--dir" || arg === "-d") {
|
|
106
|
+
options.dir = argv[i + 1] || options.dir;
|
|
107
|
+
i += 1;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (arg.startsWith("--dir=")) {
|
|
111
|
+
options.dir = arg.split("=")[1] || options.dir;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (arg === "--yes" || arg === "-y") {
|
|
115
|
+
options.yes = true;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return options;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function normalizeFramework(input) {
|
|
124
|
+
return FRAMEWORK_ALIASES[(input || "").trim().toLowerCase()] || "";
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function normalizeStyling(input) {
|
|
128
|
+
return STYLING_ALIASES[(input || "").trim().toLowerCase()] || "";
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function askFrameworkSelection() {
|
|
132
|
+
if (!stdin.isTTY) {
|
|
133
|
+
return "";
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const p = await loadClack();
|
|
137
|
+
p.intro("Create Vicket Support");
|
|
138
|
+
const value = await p.select({
|
|
139
|
+
message: "Select a framework",
|
|
140
|
+
initialValue: DEFAULT_FRAMEWORK,
|
|
141
|
+
options: FRAMEWORK_CHOICES.map((choice) => ({
|
|
142
|
+
value: choice.value,
|
|
143
|
+
label: choice.label,
|
|
144
|
+
})),
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
if (p.isCancel(value)) {
|
|
148
|
+
p.cancel("Operation cancelled.");
|
|
149
|
+
return "";
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return value;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function askStylingSelection() {
|
|
156
|
+
if (!stdin.isTTY) {
|
|
157
|
+
return DEFAULT_STYLING;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const p = await loadClack();
|
|
161
|
+
const value = await p.select({
|
|
162
|
+
message: "Select a styling mode",
|
|
163
|
+
initialValue: DEFAULT_STYLING,
|
|
164
|
+
options: STYLING_CHOICES.map((choice) => ({
|
|
165
|
+
value: choice.value,
|
|
166
|
+
label: choice.label,
|
|
167
|
+
})),
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
if (p.isCancel(value)) {
|
|
171
|
+
p.cancel("Operation cancelled.");
|
|
172
|
+
return "";
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return value;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function collectFiles(baseDir, currentDir = baseDir, acc = []) {
|
|
179
|
+
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
180
|
+
for (const entry of entries) {
|
|
181
|
+
const absolute = path.join(currentDir, entry.name);
|
|
182
|
+
if (entry.isDirectory()) {
|
|
183
|
+
collectFiles(baseDir, absolute, acc);
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const relative = path.relative(baseDir, absolute);
|
|
188
|
+
acc.push(relative);
|
|
189
|
+
}
|
|
190
|
+
return acc;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function ensureDirectory(filePath) {
|
|
194
|
+
const dir = path.dirname(filePath);
|
|
195
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function confirmOverwrite(existingFiles) {
|
|
199
|
+
if (!stdin.isTTY) {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const p = await loadClack();
|
|
204
|
+
p.log.warn("The following files already exist:");
|
|
205
|
+
for (const file of existingFiles) {
|
|
206
|
+
p.log.message(`- ${file}`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const value = await p.confirm({
|
|
210
|
+
message: "Overwrite these files?",
|
|
211
|
+
initialValue: false,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
if (p.isCancel(value)) {
|
|
215
|
+
p.cancel("Operation cancelled.");
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return Boolean(value);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function printPostInstall(framework, styling) {
|
|
223
|
+
const envLinesByFramework = {
|
|
224
|
+
next: [
|
|
225
|
+
"- NEXT_PUBLIC_VICKET_API_URL (example: https://api.vicket.io/api/v1)",
|
|
226
|
+
"- NEXT_PUBLIC_VICKET_API_KEY (website API key)",
|
|
227
|
+
],
|
|
228
|
+
nuxt: [
|
|
229
|
+
"- NUXT_PUBLIC_VICKET_API_URL (example: https://api.vicket.io/api/v1)",
|
|
230
|
+
"- NUXT_PUBLIC_VICKET_API_KEY (website API key)",
|
|
231
|
+
],
|
|
232
|
+
sveltekit: [
|
|
233
|
+
"- PUBLIC_VICKET_API_URL (example: https://api.vicket.io/api/v1)",
|
|
234
|
+
"- PUBLIC_VICKET_API_KEY (website API key)",
|
|
235
|
+
],
|
|
236
|
+
react: [
|
|
237
|
+
"- VITE_VICKET_API_URL (example: https://api.vicket.io/api/v1)",
|
|
238
|
+
"- VITE_VICKET_API_KEY (website API key)",
|
|
239
|
+
],
|
|
240
|
+
vue: [
|
|
241
|
+
"- VITE_VICKET_API_URL (example: https://api.vicket.io/api/v1)",
|
|
242
|
+
"- VITE_VICKET_API_KEY (website API key)",
|
|
243
|
+
],
|
|
244
|
+
};
|
|
245
|
+
const envLines = envLinesByFramework[framework] || envLinesByFramework.next;
|
|
246
|
+
|
|
247
|
+
if (stdin.isTTY) {
|
|
248
|
+
const p = await loadClack();
|
|
249
|
+
p.outro("Scaffold complete.");
|
|
250
|
+
p.log.info("Set these environment variables in the target app:");
|
|
251
|
+
for (const line of envLines) {
|
|
252
|
+
p.log.message(line);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (framework === "nuxt") {
|
|
256
|
+
p.log.info(
|
|
257
|
+
"Nuxt note: if you prefer global styles, add ~/assets/css/vicket.css to nuxt.config.ts > css.",
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
if (styling === "tailwind") {
|
|
261
|
+
p.log.info("Tailwind mode: ensure Tailwind CSS is configured in the target app.");
|
|
262
|
+
}
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
console.log("\nScaffold complete.");
|
|
267
|
+
console.log("Set these environment variables in the target app:");
|
|
268
|
+
for (const line of envLines) {
|
|
269
|
+
console.log(line);
|
|
270
|
+
}
|
|
271
|
+
if (styling === "tailwind") {
|
|
272
|
+
console.log("Tailwind mode: ensure Tailwind CSS is configured in the target app.");
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function mergeTemplateRoots(templateRoots) {
|
|
277
|
+
const mapping = new Map();
|
|
278
|
+
|
|
279
|
+
for (const templateRoot of templateRoots) {
|
|
280
|
+
const files = collectFiles(templateRoot);
|
|
281
|
+
for (const relativePath of files) {
|
|
282
|
+
mapping.set(relativePath, path.join(templateRoot, relativePath));
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return mapping;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async function main() {
|
|
290
|
+
const options = parseArgs(process.argv.slice(2));
|
|
291
|
+
if (options.help) {
|
|
292
|
+
printHelp();
|
|
293
|
+
process.exit(0);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
let framework = normalizeFramework(options.framework);
|
|
297
|
+
const requestedStyling = (options.styling || "").trim();
|
|
298
|
+
if (requestedStyling && !normalizeStyling(requestedStyling)) {
|
|
299
|
+
console.error("Unsupported styling. Use --styling css|tailwind");
|
|
300
|
+
printHelp();
|
|
301
|
+
process.exit(1);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
let styling = normalizeStyling(options.styling) || DEFAULT_STYLING;
|
|
305
|
+
|
|
306
|
+
if (!framework) {
|
|
307
|
+
framework = await askFrameworkSelection();
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (!framework) {
|
|
311
|
+
console.error(
|
|
312
|
+
"Missing or unsupported framework. Use --framework next|nuxt|react|vue|sveltekit",
|
|
313
|
+
);
|
|
314
|
+
printHelp();
|
|
315
|
+
process.exit(1);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (!normalizeStyling(options.styling) && stdin.isTTY && !process.argv.includes("--tailwind")) {
|
|
319
|
+
const selectedStyling = await askStylingSelection();
|
|
320
|
+
if (!selectedStyling) {
|
|
321
|
+
console.log("Aborted. No files were written.");
|
|
322
|
+
process.exit(0);
|
|
323
|
+
}
|
|
324
|
+
styling = selectedStyling;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const packageRoot = path.resolve(__dirname, "..");
|
|
328
|
+
const baseTemplateRoot = path.join(packageRoot, "templates", framework);
|
|
329
|
+
if (!fs.existsSync(baseTemplateRoot)) {
|
|
330
|
+
console.error(`Template not found for framework: ${framework}`);
|
|
331
|
+
process.exit(1);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const templateRoots = [baseTemplateRoot];
|
|
335
|
+
|
|
336
|
+
if (styling === "tailwind") {
|
|
337
|
+
const tailwindOverlayRoot = path.join(packageRoot, "templates-tailwind", framework);
|
|
338
|
+
if (!fs.existsSync(tailwindOverlayRoot)) {
|
|
339
|
+
console.error(`Tailwind template overlay not found for framework: ${framework}`);
|
|
340
|
+
process.exit(1);
|
|
341
|
+
}
|
|
342
|
+
templateRoots.push(tailwindOverlayRoot);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const targetRoot = path.resolve(options.dir);
|
|
346
|
+
const filesMap = mergeTemplateRoots(templateRoots);
|
|
347
|
+
const files = Array.from(filesMap.keys());
|
|
348
|
+
const existing = files.filter((relativePath) =>
|
|
349
|
+
fs.existsSync(path.join(targetRoot, relativePath)),
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
if (existing.length > 0 && !options.yes) {
|
|
353
|
+
const shouldOverwrite = await confirmOverwrite(existing);
|
|
354
|
+
if (!shouldOverwrite) {
|
|
355
|
+
console.log("Aborted. No files were written.");
|
|
356
|
+
process.exit(0);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
for (const relativePath of files) {
|
|
361
|
+
const from = filesMap.get(relativePath);
|
|
362
|
+
if (!from) continue;
|
|
363
|
+
const to = path.join(targetRoot, relativePath);
|
|
364
|
+
ensureDirectory(to);
|
|
365
|
+
fs.copyFileSync(from, to);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
await printPostInstall(framework, styling);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
main().catch((error) => {
|
|
372
|
+
console.error(error);
|
|
373
|
+
process.exit(1);
|
|
374
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vicket/create-support",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Scaffold white-label support pages (/support and /ticket) for Next.js, Nuxt, and SvelteKit.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"create-vicket-support": "./bin/create-vicket-support.js"
|
|
7
|
+
},
|
|
8
|
+
"type": "commonjs",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@clack/prompts": "^0.10.1"
|
|
12
|
+
}
|
|
13
|
+
}
|