create-rakta-app 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 +101 -0
- package/dist/index.js +4094 -0
- package/dist/index.js.map +23 -0
- package/package.json +65 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,4094 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
function __accessProp(key) {
|
|
9
|
+
return this[key];
|
|
10
|
+
}
|
|
11
|
+
var __toESMCache_node;
|
|
12
|
+
var __toESMCache_esm;
|
|
13
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
14
|
+
var canCache = mod != null && typeof mod === "object";
|
|
15
|
+
if (canCache) {
|
|
16
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
17
|
+
var cached = cache.get(mod);
|
|
18
|
+
if (cached)
|
|
19
|
+
return cached;
|
|
20
|
+
}
|
|
21
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
22
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
23
|
+
for (let key of __getOwnPropNames(mod))
|
|
24
|
+
if (!__hasOwnProp.call(to, key))
|
|
25
|
+
__defProp(to, key, {
|
|
26
|
+
get: __accessProp.bind(mod, key),
|
|
27
|
+
enumerable: true
|
|
28
|
+
});
|
|
29
|
+
if (canCache)
|
|
30
|
+
cache.set(mod, to);
|
|
31
|
+
return to;
|
|
32
|
+
};
|
|
33
|
+
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
34
|
+
|
|
35
|
+
// ../../node_modules/.bun/sisteransi@1.0.5/node_modules/sisteransi/src/index.js
|
|
36
|
+
var require_src = __commonJS((exports, module) => {
|
|
37
|
+
var ESC2 = "\x1B";
|
|
38
|
+
var CSI2 = `${ESC2}[`;
|
|
39
|
+
var beep = "\x07";
|
|
40
|
+
var cursor = {
|
|
41
|
+
to(x, y) {
|
|
42
|
+
if (!y)
|
|
43
|
+
return `${CSI2}${x + 1}G`;
|
|
44
|
+
return `${CSI2}${y + 1};${x + 1}H`;
|
|
45
|
+
},
|
|
46
|
+
move(x, y) {
|
|
47
|
+
let ret = "";
|
|
48
|
+
if (x < 0)
|
|
49
|
+
ret += `${CSI2}${-x}D`;
|
|
50
|
+
else if (x > 0)
|
|
51
|
+
ret += `${CSI2}${x}C`;
|
|
52
|
+
if (y < 0)
|
|
53
|
+
ret += `${CSI2}${-y}A`;
|
|
54
|
+
else if (y > 0)
|
|
55
|
+
ret += `${CSI2}${y}B`;
|
|
56
|
+
return ret;
|
|
57
|
+
},
|
|
58
|
+
up: (count = 1) => `${CSI2}${count}A`,
|
|
59
|
+
down: (count = 1) => `${CSI2}${count}B`,
|
|
60
|
+
forward: (count = 1) => `${CSI2}${count}C`,
|
|
61
|
+
backward: (count = 1) => `${CSI2}${count}D`,
|
|
62
|
+
nextLine: (count = 1) => `${CSI2}E`.repeat(count),
|
|
63
|
+
prevLine: (count = 1) => `${CSI2}F`.repeat(count),
|
|
64
|
+
left: `${CSI2}G`,
|
|
65
|
+
hide: `${CSI2}?25l`,
|
|
66
|
+
show: `${CSI2}?25h`,
|
|
67
|
+
save: `${ESC2}7`,
|
|
68
|
+
restore: `${ESC2}8`
|
|
69
|
+
};
|
|
70
|
+
var scroll = {
|
|
71
|
+
up: (count = 1) => `${CSI2}S`.repeat(count),
|
|
72
|
+
down: (count = 1) => `${CSI2}T`.repeat(count)
|
|
73
|
+
};
|
|
74
|
+
var erase = {
|
|
75
|
+
screen: `${CSI2}2J`,
|
|
76
|
+
up: (count = 1) => `${CSI2}1J`.repeat(count),
|
|
77
|
+
down: (count = 1) => `${CSI2}J`.repeat(count),
|
|
78
|
+
line: `${CSI2}2K`,
|
|
79
|
+
lineEnd: `${CSI2}K`,
|
|
80
|
+
lineStart: `${CSI2}1K`,
|
|
81
|
+
lines(count) {
|
|
82
|
+
let clear = "";
|
|
83
|
+
for (let i = 0;i < count; i++)
|
|
84
|
+
clear += this.line + (i < count - 1 ? cursor.up() : "");
|
|
85
|
+
if (count)
|
|
86
|
+
clear += cursor.left;
|
|
87
|
+
return clear;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
module.exports = { cursor, scroll, erase, beep };
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// ../../node_modules/.bun/picocolors@1.1.1/node_modules/picocolors/picocolors.js
|
|
94
|
+
var require_picocolors = __commonJS((exports, module) => {
|
|
95
|
+
var p2 = process || {};
|
|
96
|
+
var argv = p2.argv || [];
|
|
97
|
+
var env = p2.env || {};
|
|
98
|
+
var isColorSupported = !(!!env.NO_COLOR || argv.includes("--no-color")) && (!!env.FORCE_COLOR || argv.includes("--color") || p2.platform === "win32" || (p2.stdout || {}).isTTY && env.TERM !== "dumb" || !!env.CI);
|
|
99
|
+
var formatter = (open, close, replace = open) => (input) => {
|
|
100
|
+
let string = "" + input, index = string.indexOf(close, open.length);
|
|
101
|
+
return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close;
|
|
102
|
+
};
|
|
103
|
+
var replaceClose = (string, close, replace, index) => {
|
|
104
|
+
let result = "", cursor3 = 0;
|
|
105
|
+
do {
|
|
106
|
+
result += string.substring(cursor3, index) + replace;
|
|
107
|
+
cursor3 = index + close.length;
|
|
108
|
+
index = string.indexOf(close, cursor3);
|
|
109
|
+
} while (~index);
|
|
110
|
+
return result + string.substring(cursor3);
|
|
111
|
+
};
|
|
112
|
+
var createColors = (enabled = isColorSupported) => {
|
|
113
|
+
let f2 = enabled ? formatter : () => String;
|
|
114
|
+
return {
|
|
115
|
+
isColorSupported: enabled,
|
|
116
|
+
reset: f2("\x1B[0m", "\x1B[0m"),
|
|
117
|
+
bold: f2("\x1B[1m", "\x1B[22m", "\x1B[22m\x1B[1m"),
|
|
118
|
+
dim: f2("\x1B[2m", "\x1B[22m", "\x1B[22m\x1B[2m"),
|
|
119
|
+
italic: f2("\x1B[3m", "\x1B[23m"),
|
|
120
|
+
underline: f2("\x1B[4m", "\x1B[24m"),
|
|
121
|
+
inverse: f2("\x1B[7m", "\x1B[27m"),
|
|
122
|
+
hidden: f2("\x1B[8m", "\x1B[28m"),
|
|
123
|
+
strikethrough: f2("\x1B[9m", "\x1B[29m"),
|
|
124
|
+
black: f2("\x1B[30m", "\x1B[39m"),
|
|
125
|
+
red: f2("\x1B[31m", "\x1B[39m"),
|
|
126
|
+
green: f2("\x1B[32m", "\x1B[39m"),
|
|
127
|
+
yellow: f2("\x1B[33m", "\x1B[39m"),
|
|
128
|
+
blue: f2("\x1B[34m", "\x1B[39m"),
|
|
129
|
+
magenta: f2("\x1B[35m", "\x1B[39m"),
|
|
130
|
+
cyan: f2("\x1B[36m", "\x1B[39m"),
|
|
131
|
+
white: f2("\x1B[37m", "\x1B[39m"),
|
|
132
|
+
gray: f2("\x1B[90m", "\x1B[39m"),
|
|
133
|
+
bgBlack: f2("\x1B[40m", "\x1B[49m"),
|
|
134
|
+
bgRed: f2("\x1B[41m", "\x1B[49m"),
|
|
135
|
+
bgGreen: f2("\x1B[42m", "\x1B[49m"),
|
|
136
|
+
bgYellow: f2("\x1B[43m", "\x1B[49m"),
|
|
137
|
+
bgBlue: f2("\x1B[44m", "\x1B[49m"),
|
|
138
|
+
bgMagenta: f2("\x1B[45m", "\x1B[49m"),
|
|
139
|
+
bgCyan: f2("\x1B[46m", "\x1B[49m"),
|
|
140
|
+
bgWhite: f2("\x1B[47m", "\x1B[49m"),
|
|
141
|
+
blackBright: f2("\x1B[90m", "\x1B[39m"),
|
|
142
|
+
redBright: f2("\x1B[91m", "\x1B[39m"),
|
|
143
|
+
greenBright: f2("\x1B[92m", "\x1B[39m"),
|
|
144
|
+
yellowBright: f2("\x1B[93m", "\x1B[39m"),
|
|
145
|
+
blueBright: f2("\x1B[94m", "\x1B[39m"),
|
|
146
|
+
magentaBright: f2("\x1B[95m", "\x1B[39m"),
|
|
147
|
+
cyanBright: f2("\x1B[96m", "\x1B[39m"),
|
|
148
|
+
whiteBright: f2("\x1B[97m", "\x1B[39m"),
|
|
149
|
+
bgBlackBright: f2("\x1B[100m", "\x1B[49m"),
|
|
150
|
+
bgRedBright: f2("\x1B[101m", "\x1B[49m"),
|
|
151
|
+
bgGreenBright: f2("\x1B[102m", "\x1B[49m"),
|
|
152
|
+
bgYellowBright: f2("\x1B[103m", "\x1B[49m"),
|
|
153
|
+
bgBlueBright: f2("\x1B[104m", "\x1B[49m"),
|
|
154
|
+
bgMagentaBright: f2("\x1B[105m", "\x1B[49m"),
|
|
155
|
+
bgCyanBright: f2("\x1B[106m", "\x1B[49m"),
|
|
156
|
+
bgWhiteBright: f2("\x1B[107m", "\x1B[49m")
|
|
157
|
+
};
|
|
158
|
+
};
|
|
159
|
+
module.exports = createColors();
|
|
160
|
+
module.exports.createColors = createColors;
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// src/index.ts
|
|
164
|
+
import { existsSync as existsSync2 } from "fs";
|
|
165
|
+
import { resolve as resolve2 } from "path";
|
|
166
|
+
|
|
167
|
+
// ../../node_modules/.bun/@clack+core@1.4.2/node_modules/@clack/core/dist/index.mjs
|
|
168
|
+
import { styleText } from "util";
|
|
169
|
+
import { stdout, stdin } from "process";
|
|
170
|
+
import * as l from "readline";
|
|
171
|
+
import l__default from "readline";
|
|
172
|
+
|
|
173
|
+
// ../../node_modules/.bun/fast-string-truncated-width@3.0.3/node_modules/fast-string-truncated-width/dist/utils.js
|
|
174
|
+
var getCodePointsLength = (() => {
|
|
175
|
+
const SURROGATE_PAIR_RE = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g;
|
|
176
|
+
return (input) => {
|
|
177
|
+
let surrogatePairsNr = 0;
|
|
178
|
+
SURROGATE_PAIR_RE.lastIndex = 0;
|
|
179
|
+
while (SURROGATE_PAIR_RE.test(input)) {
|
|
180
|
+
surrogatePairsNr += 1;
|
|
181
|
+
}
|
|
182
|
+
return input.length - surrogatePairsNr;
|
|
183
|
+
};
|
|
184
|
+
})();
|
|
185
|
+
var isFullWidth = (x) => {
|
|
186
|
+
return x === 12288 || x >= 65281 && x <= 65376 || x >= 65504 && x <= 65510;
|
|
187
|
+
};
|
|
188
|
+
var isWideNotCJKTNotEmoji = (x) => {
|
|
189
|
+
return x === 8987 || x === 9001 || x >= 12272 && x <= 12287 || x >= 12289 && x <= 12350 || x >= 12441 && x <= 12543 || x >= 12549 && x <= 12591 || x >= 12593 && x <= 12686 || x >= 12688 && x <= 12771 || x >= 12783 && x <= 12830 || x >= 12832 && x <= 12871 || x >= 12880 && x <= 19903 || x >= 65040 && x <= 65049 || x >= 65072 && x <= 65106 || x >= 65108 && x <= 65126 || x >= 65128 && x <= 65131 || x >= 127488 && x <= 127490 || x >= 127504 && x <= 127547 || x >= 127552 && x <= 127560 || x >= 131072 && x <= 196605 || x >= 196608 && x <= 262141;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// ../../node_modules/.bun/fast-string-truncated-width@3.0.3/node_modules/fast-string-truncated-width/dist/index.js
|
|
193
|
+
var ANSI_RE = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]|\u001b\]8;[^;]*;.*?(?:\u0007|\u001b\u005c)/y;
|
|
194
|
+
var CONTROL_RE = /[\x00-\x08\x0A-\x1F\x7F-\x9F]{1,1000}/y;
|
|
195
|
+
var CJKT_WIDE_RE = /(?:(?![\uFF61-\uFF9F\uFF00-\uFFEF])[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}\p{Script=Tangut}]){1,1000}/yu;
|
|
196
|
+
var TAB_RE = /\t{1,1000}/y;
|
|
197
|
+
var EMOJI_RE = /[\u{1F1E6}-\u{1F1FF}]{2}|\u{1F3F4}[\u{E0061}-\u{E007A}]{2}[\u{E0030}-\u{E0039}\u{E0061}-\u{E007A}]{1,3}\u{E007F}|(?:\p{Emoji}\uFE0F\u20E3?|\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation})(?:\u200D(?:\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F\u20E3?))*/yu;
|
|
198
|
+
var LATIN_RE = /(?:[\x20-\x7E\xA0-\xFF](?!\uFE0F)){1,1000}/y;
|
|
199
|
+
var MODIFIER_RE = /\p{M}+/gu;
|
|
200
|
+
var NO_TRUNCATION = { limit: Infinity, ellipsis: "" };
|
|
201
|
+
var getStringTruncatedWidth = (input, truncationOptions = {}, widthOptions = {}) => {
|
|
202
|
+
const LIMIT = truncationOptions.limit ?? Infinity;
|
|
203
|
+
const ELLIPSIS = truncationOptions.ellipsis ?? "";
|
|
204
|
+
const ELLIPSIS_WIDTH = truncationOptions?.ellipsisWidth ?? (ELLIPSIS ? getStringTruncatedWidth(ELLIPSIS, NO_TRUNCATION, widthOptions).width : 0);
|
|
205
|
+
const ANSI_WIDTH = 0;
|
|
206
|
+
const CONTROL_WIDTH = widthOptions.controlWidth ?? 0;
|
|
207
|
+
const TAB_WIDTH = widthOptions.tabWidth ?? 8;
|
|
208
|
+
const EMOJI_WIDTH = widthOptions.emojiWidth ?? 2;
|
|
209
|
+
const FULL_WIDTH_WIDTH = 2;
|
|
210
|
+
const REGULAR_WIDTH = widthOptions.regularWidth ?? 1;
|
|
211
|
+
const WIDE_WIDTH = widthOptions.wideWidth ?? FULL_WIDTH_WIDTH;
|
|
212
|
+
const PARSE_BLOCKS = [
|
|
213
|
+
[LATIN_RE, REGULAR_WIDTH],
|
|
214
|
+
[ANSI_RE, ANSI_WIDTH],
|
|
215
|
+
[CONTROL_RE, CONTROL_WIDTH],
|
|
216
|
+
[TAB_RE, TAB_WIDTH],
|
|
217
|
+
[EMOJI_RE, EMOJI_WIDTH],
|
|
218
|
+
[CJKT_WIDE_RE, WIDE_WIDTH]
|
|
219
|
+
];
|
|
220
|
+
let indexPrev = 0;
|
|
221
|
+
let index = 0;
|
|
222
|
+
let length = input.length;
|
|
223
|
+
let lengthExtra = 0;
|
|
224
|
+
let truncationEnabled = false;
|
|
225
|
+
let truncationIndex = length;
|
|
226
|
+
let truncationLimit = Math.max(0, LIMIT - ELLIPSIS_WIDTH);
|
|
227
|
+
let unmatchedStart = 0;
|
|
228
|
+
let unmatchedEnd = 0;
|
|
229
|
+
let width = 0;
|
|
230
|
+
let widthExtra = 0;
|
|
231
|
+
outer:
|
|
232
|
+
while (true) {
|
|
233
|
+
if (unmatchedEnd > unmatchedStart || index >= length && index > indexPrev) {
|
|
234
|
+
const unmatched = input.slice(unmatchedStart, unmatchedEnd) || input.slice(indexPrev, index);
|
|
235
|
+
lengthExtra = 0;
|
|
236
|
+
for (const char of unmatched.replaceAll(MODIFIER_RE, "")) {
|
|
237
|
+
const codePoint = char.codePointAt(0) || 0;
|
|
238
|
+
if (isFullWidth(codePoint)) {
|
|
239
|
+
widthExtra = FULL_WIDTH_WIDTH;
|
|
240
|
+
} else if (isWideNotCJKTNotEmoji(codePoint)) {
|
|
241
|
+
widthExtra = WIDE_WIDTH;
|
|
242
|
+
} else {
|
|
243
|
+
widthExtra = REGULAR_WIDTH;
|
|
244
|
+
}
|
|
245
|
+
if (width + widthExtra > truncationLimit) {
|
|
246
|
+
truncationIndex = Math.min(truncationIndex, Math.max(unmatchedStart, indexPrev) + lengthExtra);
|
|
247
|
+
}
|
|
248
|
+
if (width + widthExtra > LIMIT) {
|
|
249
|
+
truncationEnabled = true;
|
|
250
|
+
break outer;
|
|
251
|
+
}
|
|
252
|
+
lengthExtra += char.length;
|
|
253
|
+
width += widthExtra;
|
|
254
|
+
}
|
|
255
|
+
unmatchedStart = unmatchedEnd = 0;
|
|
256
|
+
}
|
|
257
|
+
if (index >= length) {
|
|
258
|
+
break outer;
|
|
259
|
+
}
|
|
260
|
+
for (let i = 0, l = PARSE_BLOCKS.length;i < l; i++) {
|
|
261
|
+
const [BLOCK_RE, BLOCK_WIDTH] = PARSE_BLOCKS[i];
|
|
262
|
+
BLOCK_RE.lastIndex = index;
|
|
263
|
+
if (BLOCK_RE.test(input)) {
|
|
264
|
+
lengthExtra = BLOCK_RE === CJKT_WIDE_RE ? getCodePointsLength(input.slice(index, BLOCK_RE.lastIndex)) : BLOCK_RE === EMOJI_RE ? 1 : BLOCK_RE.lastIndex - index;
|
|
265
|
+
widthExtra = lengthExtra * BLOCK_WIDTH;
|
|
266
|
+
if (width + widthExtra > truncationLimit) {
|
|
267
|
+
truncationIndex = Math.min(truncationIndex, index + Math.floor((truncationLimit - width) / BLOCK_WIDTH));
|
|
268
|
+
}
|
|
269
|
+
if (width + widthExtra > LIMIT) {
|
|
270
|
+
truncationEnabled = true;
|
|
271
|
+
break outer;
|
|
272
|
+
}
|
|
273
|
+
width += widthExtra;
|
|
274
|
+
unmatchedStart = indexPrev;
|
|
275
|
+
unmatchedEnd = index;
|
|
276
|
+
index = indexPrev = BLOCK_RE.lastIndex;
|
|
277
|
+
continue outer;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
index += 1;
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
width: truncationEnabled ? truncationLimit : width,
|
|
284
|
+
index: truncationEnabled ? truncationIndex : length,
|
|
285
|
+
truncated: truncationEnabled,
|
|
286
|
+
ellipsed: truncationEnabled && LIMIT >= ELLIPSIS_WIDTH
|
|
287
|
+
};
|
|
288
|
+
};
|
|
289
|
+
var dist_default = getStringTruncatedWidth;
|
|
290
|
+
|
|
291
|
+
// ../../node_modules/.bun/fast-string-width@3.0.2/node_modules/fast-string-width/dist/index.js
|
|
292
|
+
var NO_TRUNCATION2 = {
|
|
293
|
+
limit: Infinity,
|
|
294
|
+
ellipsis: "",
|
|
295
|
+
ellipsisWidth: 0
|
|
296
|
+
};
|
|
297
|
+
var fastStringWidth = (input, options = {}) => {
|
|
298
|
+
return dist_default(input, NO_TRUNCATION2, options).width;
|
|
299
|
+
};
|
|
300
|
+
var dist_default2 = fastStringWidth;
|
|
301
|
+
|
|
302
|
+
// ../../node_modules/.bun/fast-wrap-ansi@0.2.2/node_modules/fast-wrap-ansi/lib/main.js
|
|
303
|
+
var ESC = "\x1B";
|
|
304
|
+
var CSI = "\x9B";
|
|
305
|
+
var END_CODE = 39;
|
|
306
|
+
var ANSI_ESCAPE_BELL = "\x07";
|
|
307
|
+
var ANSI_CSI = "[";
|
|
308
|
+
var ANSI_OSC = "]";
|
|
309
|
+
var ANSI_SGR_TERMINATOR = "m";
|
|
310
|
+
var ANSI_ESCAPE_LINK = `${ANSI_OSC}8;;`;
|
|
311
|
+
var GROUP_REGEX = new RegExp(`(?:\\${ANSI_CSI}(?<code>\\d+)m|\\${ANSI_ESCAPE_LINK}(?<uri>.*)${ANSI_ESCAPE_BELL})`, "y");
|
|
312
|
+
var getClosingCode = (openingCode) => {
|
|
313
|
+
if (openingCode >= 30 && openingCode <= 37)
|
|
314
|
+
return 39;
|
|
315
|
+
if (openingCode >= 90 && openingCode <= 97)
|
|
316
|
+
return 39;
|
|
317
|
+
if (openingCode >= 40 && openingCode <= 47)
|
|
318
|
+
return 49;
|
|
319
|
+
if (openingCode >= 100 && openingCode <= 107)
|
|
320
|
+
return 49;
|
|
321
|
+
if (openingCode === 1 || openingCode === 2)
|
|
322
|
+
return 22;
|
|
323
|
+
if (openingCode === 3)
|
|
324
|
+
return 23;
|
|
325
|
+
if (openingCode === 4)
|
|
326
|
+
return 24;
|
|
327
|
+
if (openingCode === 7)
|
|
328
|
+
return 27;
|
|
329
|
+
if (openingCode === 8)
|
|
330
|
+
return 28;
|
|
331
|
+
if (openingCode === 9)
|
|
332
|
+
return 29;
|
|
333
|
+
if (openingCode === 0)
|
|
334
|
+
return 0;
|
|
335
|
+
return;
|
|
336
|
+
};
|
|
337
|
+
var wrapAnsiCode = (code) => `${ESC}${ANSI_CSI}${code}${ANSI_SGR_TERMINATOR}`;
|
|
338
|
+
var wrapAnsiHyperlink = (url) => `${ESC}${ANSI_ESCAPE_LINK}${url}${ANSI_ESCAPE_BELL}`;
|
|
339
|
+
var wrapWord = (rows, word, columns) => {
|
|
340
|
+
const characters = word[Symbol.iterator]();
|
|
341
|
+
let isInsideEscape = false;
|
|
342
|
+
let isInsideLinkEscape = false;
|
|
343
|
+
let lastRow = rows.at(-1);
|
|
344
|
+
let visible = lastRow === undefined ? 0 : dist_default2(lastRow);
|
|
345
|
+
let currentCharacter = characters.next();
|
|
346
|
+
let nextCharacter = characters.next();
|
|
347
|
+
let rawCharacterIndex = 0;
|
|
348
|
+
while (!currentCharacter.done) {
|
|
349
|
+
const character = currentCharacter.value;
|
|
350
|
+
const characterLength = dist_default2(character);
|
|
351
|
+
if (visible + characterLength <= columns) {
|
|
352
|
+
rows[rows.length - 1] += character;
|
|
353
|
+
} else {
|
|
354
|
+
rows.push(character);
|
|
355
|
+
visible = 0;
|
|
356
|
+
}
|
|
357
|
+
if (character === ESC || character === CSI) {
|
|
358
|
+
isInsideEscape = true;
|
|
359
|
+
isInsideLinkEscape = word.startsWith(ANSI_ESCAPE_LINK, rawCharacterIndex + 1);
|
|
360
|
+
}
|
|
361
|
+
if (isInsideEscape) {
|
|
362
|
+
if (isInsideLinkEscape) {
|
|
363
|
+
if (character === ANSI_ESCAPE_BELL) {
|
|
364
|
+
isInsideEscape = false;
|
|
365
|
+
isInsideLinkEscape = false;
|
|
366
|
+
}
|
|
367
|
+
} else if (character === ANSI_SGR_TERMINATOR) {
|
|
368
|
+
isInsideEscape = false;
|
|
369
|
+
}
|
|
370
|
+
} else {
|
|
371
|
+
visible += characterLength;
|
|
372
|
+
if (visible === columns && !nextCharacter.done) {
|
|
373
|
+
rows.push("");
|
|
374
|
+
visible = 0;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
currentCharacter = nextCharacter;
|
|
378
|
+
nextCharacter = characters.next();
|
|
379
|
+
rawCharacterIndex += character.length;
|
|
380
|
+
}
|
|
381
|
+
lastRow = rows.at(-1);
|
|
382
|
+
if (!visible && lastRow !== undefined && lastRow.length && rows.length > 1) {
|
|
383
|
+
rows[rows.length - 2] += rows.pop();
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
var stringVisibleTrimSpacesRight = (string) => {
|
|
387
|
+
const words = string.split(" ");
|
|
388
|
+
let last = words.length;
|
|
389
|
+
while (last) {
|
|
390
|
+
if (dist_default2(words[last - 1])) {
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
last--;
|
|
394
|
+
}
|
|
395
|
+
if (last === words.length) {
|
|
396
|
+
return string;
|
|
397
|
+
}
|
|
398
|
+
return words.slice(0, last).join(" ") + words.slice(last).join("");
|
|
399
|
+
};
|
|
400
|
+
var exec = (string, columns, options = {}) => {
|
|
401
|
+
if (options.trim !== false && string.trim() === "") {
|
|
402
|
+
return "";
|
|
403
|
+
}
|
|
404
|
+
let returnValue = "";
|
|
405
|
+
let escapeCode;
|
|
406
|
+
let escapeUrl;
|
|
407
|
+
const words = string.split(" ");
|
|
408
|
+
let rows = [""];
|
|
409
|
+
let rowLength = 0;
|
|
410
|
+
for (let index = 0;index < words.length; index++) {
|
|
411
|
+
const word = words[index];
|
|
412
|
+
if (options.trim !== false) {
|
|
413
|
+
const row = rows.at(-1) ?? "";
|
|
414
|
+
const trimmed = row.trimStart();
|
|
415
|
+
if (row.length !== trimmed.length) {
|
|
416
|
+
rows[rows.length - 1] = trimmed;
|
|
417
|
+
rowLength = dist_default2(trimmed);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
if (index !== 0) {
|
|
421
|
+
if (rowLength >= columns && (options.wordWrap === false || options.trim === false)) {
|
|
422
|
+
rows.push("");
|
|
423
|
+
rowLength = 0;
|
|
424
|
+
}
|
|
425
|
+
if (rowLength || options.trim === false) {
|
|
426
|
+
rows[rows.length - 1] += " ";
|
|
427
|
+
rowLength++;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
const wordLength = dist_default2(word);
|
|
431
|
+
if (options.hard && wordLength > columns) {
|
|
432
|
+
const remainingColumns = columns - rowLength;
|
|
433
|
+
const breaksStartingThisLine = 1 + Math.floor((wordLength - remainingColumns - 1) / columns);
|
|
434
|
+
const breaksStartingNextLine = Math.floor((wordLength - 1) / columns);
|
|
435
|
+
if (breaksStartingNextLine < breaksStartingThisLine) {
|
|
436
|
+
rows.push("");
|
|
437
|
+
}
|
|
438
|
+
wrapWord(rows, word, columns);
|
|
439
|
+
rowLength = dist_default2(rows.at(-1) ?? "");
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
if (rowLength + wordLength > columns && rowLength && wordLength) {
|
|
443
|
+
if (options.wordWrap === false && rowLength < columns) {
|
|
444
|
+
wrapWord(rows, word, columns);
|
|
445
|
+
rowLength = dist_default2(rows.at(-1) ?? "");
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
rows.push("");
|
|
449
|
+
rowLength = 0;
|
|
450
|
+
}
|
|
451
|
+
if (rowLength + wordLength > columns && options.wordWrap === false) {
|
|
452
|
+
wrapWord(rows, word, columns);
|
|
453
|
+
rowLength = dist_default2(rows.at(-1) ?? "");
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
rows[rows.length - 1] += word;
|
|
457
|
+
rowLength += wordLength;
|
|
458
|
+
}
|
|
459
|
+
if (options.trim !== false) {
|
|
460
|
+
rows = rows.map((row) => stringVisibleTrimSpacesRight(row));
|
|
461
|
+
}
|
|
462
|
+
const preString = rows.join(`
|
|
463
|
+
`);
|
|
464
|
+
let inSurrogate = false;
|
|
465
|
+
for (let i = 0;i < preString.length; i++) {
|
|
466
|
+
const character = preString[i];
|
|
467
|
+
returnValue += character;
|
|
468
|
+
if (!inSurrogate) {
|
|
469
|
+
inSurrogate = character >= "\uD800" && character <= "\uDBFF";
|
|
470
|
+
if (inSurrogate) {
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
} else {
|
|
474
|
+
inSurrogate = false;
|
|
475
|
+
}
|
|
476
|
+
if (character === ESC || character === CSI) {
|
|
477
|
+
GROUP_REGEX.lastIndex = i + 1;
|
|
478
|
+
const groupsResult = GROUP_REGEX.exec(preString);
|
|
479
|
+
const groups = groupsResult?.groups;
|
|
480
|
+
if (groups?.code !== undefined) {
|
|
481
|
+
const code = Number.parseFloat(groups.code);
|
|
482
|
+
escapeCode = code === END_CODE ? undefined : code;
|
|
483
|
+
} else if (groups?.uri !== undefined) {
|
|
484
|
+
escapeUrl = groups.uri.length === 0 ? undefined : groups.uri;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
if (preString[i + 1] === `
|
|
488
|
+
`) {
|
|
489
|
+
if (escapeUrl) {
|
|
490
|
+
returnValue += wrapAnsiHyperlink("");
|
|
491
|
+
}
|
|
492
|
+
const closingCode = escapeCode ? getClosingCode(escapeCode) : undefined;
|
|
493
|
+
if (escapeCode && closingCode) {
|
|
494
|
+
returnValue += wrapAnsiCode(closingCode);
|
|
495
|
+
}
|
|
496
|
+
} else if (character === `
|
|
497
|
+
`) {
|
|
498
|
+
if (escapeCode && getClosingCode(escapeCode)) {
|
|
499
|
+
returnValue += wrapAnsiCode(escapeCode);
|
|
500
|
+
}
|
|
501
|
+
if (escapeUrl) {
|
|
502
|
+
returnValue += wrapAnsiHyperlink(escapeUrl);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return returnValue;
|
|
507
|
+
};
|
|
508
|
+
var CRLF_OR_LF = /\r?\n/;
|
|
509
|
+
function wrapAnsi(string, columns, options) {
|
|
510
|
+
return String(string).normalize().split(CRLF_OR_LF).map((line) => exec(line, columns, options)).join(`
|
|
511
|
+
`);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// ../../node_modules/.bun/@clack+core@1.4.2/node_modules/@clack/core/dist/index.mjs
|
|
515
|
+
var import_sisteransi = __toESM(require_src(), 1);
|
|
516
|
+
import { ReadStream } from "tty";
|
|
517
|
+
function findCursor(s, o, l2) {
|
|
518
|
+
if (!l2.some((r) => !r.disabled))
|
|
519
|
+
return s;
|
|
520
|
+
const t = s + o, n = Math.max(l2.length - 1, 0), e = t < 0 ? n : t > n ? 0 : t;
|
|
521
|
+
return l2[e].disabled ? findCursor(e, o < 0 ? -1 : 1, l2) : e;
|
|
522
|
+
}
|
|
523
|
+
function findTextCursor(s, o, l2, i) {
|
|
524
|
+
const t = i.split(`
|
|
525
|
+
`);
|
|
526
|
+
let n = 0, e = s;
|
|
527
|
+
for (const r of t) {
|
|
528
|
+
if (e <= r.length)
|
|
529
|
+
break;
|
|
530
|
+
e -= r.length + 1, n++;
|
|
531
|
+
}
|
|
532
|
+
for (n = Math.max(0, Math.min(t.length - 1, n + l2)), e = Math.min(e, t[n].length) + o;e < 0 && n > 0; )
|
|
533
|
+
n--, e += t[n].length + 1;
|
|
534
|
+
for (;e > t[n].length && n < t.length - 1; )
|
|
535
|
+
e -= t[n].length + 1, n++;
|
|
536
|
+
e = Math.max(0, Math.min(t[n].length, e));
|
|
537
|
+
let h = 0;
|
|
538
|
+
for (let r = 0;r < n; r++)
|
|
539
|
+
h += t[r].length + 1;
|
|
540
|
+
return h + e;
|
|
541
|
+
}
|
|
542
|
+
var a$2 = ["up", "down", "left", "right", "space", "enter", "cancel"];
|
|
543
|
+
var t = [
|
|
544
|
+
"January",
|
|
545
|
+
"February",
|
|
546
|
+
"March",
|
|
547
|
+
"April",
|
|
548
|
+
"May",
|
|
549
|
+
"June",
|
|
550
|
+
"July",
|
|
551
|
+
"August",
|
|
552
|
+
"September",
|
|
553
|
+
"October",
|
|
554
|
+
"November",
|
|
555
|
+
"December"
|
|
556
|
+
];
|
|
557
|
+
var settings = {
|
|
558
|
+
actions: new Set(a$2),
|
|
559
|
+
aliases: /* @__PURE__ */ new Map([
|
|
560
|
+
["k", "up"],
|
|
561
|
+
["j", "down"],
|
|
562
|
+
["h", "left"],
|
|
563
|
+
["l", "right"],
|
|
564
|
+
["\x03", "cancel"],
|
|
565
|
+
["escape", "cancel"]
|
|
566
|
+
]),
|
|
567
|
+
messages: {
|
|
568
|
+
cancel: "Canceled",
|
|
569
|
+
error: "Something went wrong"
|
|
570
|
+
},
|
|
571
|
+
withGuide: true,
|
|
572
|
+
date: {
|
|
573
|
+
monthNames: [...t],
|
|
574
|
+
messages: {
|
|
575
|
+
required: "Please enter a valid date",
|
|
576
|
+
invalidMonth: "There are only 12 months in a year",
|
|
577
|
+
invalidDay: (n, e) => `There are only ${n} days in ${e}`,
|
|
578
|
+
afterMin: (n) => `Date must be on or after ${n.toISOString().slice(0, 10)}`,
|
|
579
|
+
beforeMax: (n) => `Date must be on or before ${n.toISOString().slice(0, 10)}`
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
function isActionKey(n, e) {
|
|
584
|
+
if (typeof n == "string")
|
|
585
|
+
return settings.aliases.get(n) === e;
|
|
586
|
+
for (const s of n)
|
|
587
|
+
if (s !== undefined && isActionKey(s, e))
|
|
588
|
+
return true;
|
|
589
|
+
return false;
|
|
590
|
+
}
|
|
591
|
+
function diffLines(i, s) {
|
|
592
|
+
if (i === s)
|
|
593
|
+
return;
|
|
594
|
+
const e = i.split(`
|
|
595
|
+
`), t2 = s.split(`
|
|
596
|
+
`), r = Math.max(e.length, t2.length), f = [];
|
|
597
|
+
for (let n = 0;n < r; n++)
|
|
598
|
+
e[n] !== t2[n] && f.push(n);
|
|
599
|
+
return {
|
|
600
|
+
lines: f,
|
|
601
|
+
numLinesBefore: e.length,
|
|
602
|
+
numLinesAfter: t2.length,
|
|
603
|
+
numLines: r
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
var R = globalThis.process.platform.startsWith("win");
|
|
607
|
+
var CANCEL_SYMBOL = Symbol("clack:cancel");
|
|
608
|
+
function isCancel(e) {
|
|
609
|
+
return e === CANCEL_SYMBOL;
|
|
610
|
+
}
|
|
611
|
+
function setRawMode(e, r) {
|
|
612
|
+
const o = e;
|
|
613
|
+
o.isTTY && o.setRawMode(r);
|
|
614
|
+
}
|
|
615
|
+
function block({
|
|
616
|
+
input: e = stdin,
|
|
617
|
+
output: r = stdout,
|
|
618
|
+
overwrite: o = true,
|
|
619
|
+
hideCursor: t2 = true
|
|
620
|
+
} = {}) {
|
|
621
|
+
const s = l.createInterface({
|
|
622
|
+
input: e,
|
|
623
|
+
output: r,
|
|
624
|
+
prompt: "",
|
|
625
|
+
tabSize: 1
|
|
626
|
+
});
|
|
627
|
+
l.emitKeypressEvents(e, s), e instanceof ReadStream && e.isTTY && e.setRawMode(true);
|
|
628
|
+
const n = (f, { name: a, sequence: p }) => {
|
|
629
|
+
const c = String(f);
|
|
630
|
+
if (isActionKey([c, a, p], "cancel")) {
|
|
631
|
+
t2 && r.write(import_sisteransi.cursor.show), process.exit(0);
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
if (!o)
|
|
635
|
+
return;
|
|
636
|
+
const i = a === "return" ? 0 : -1, m = a === "return" ? -1 : 0;
|
|
637
|
+
l.moveCursor(r, i, m, () => {
|
|
638
|
+
l.clearLine(r, 1, () => {
|
|
639
|
+
e.once("keypress", n);
|
|
640
|
+
});
|
|
641
|
+
});
|
|
642
|
+
};
|
|
643
|
+
return t2 && r.write(import_sisteransi.cursor.hide), e.once("keypress", n), () => {
|
|
644
|
+
e.off("keypress", n), t2 && r.write(import_sisteransi.cursor.show), e instanceof ReadStream && e.isTTY && !R && e.setRawMode(false), s.terminal = false, s.close();
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
var getColumns = (e) => ("columns" in e) && typeof e.columns == "number" ? e.columns : 80;
|
|
648
|
+
var getRows = (e) => ("rows" in e) && typeof e.rows == "number" ? e.rows : 20;
|
|
649
|
+
function wrapTextWithPrefix(e, r, o, t2 = o, s = o, n) {
|
|
650
|
+
const f = getColumns(e ?? stdout);
|
|
651
|
+
return wrapAnsi(r, f - o.length, {
|
|
652
|
+
hard: true,
|
|
653
|
+
trim: false
|
|
654
|
+
}).split(`
|
|
655
|
+
`).map((c, i, m) => {
|
|
656
|
+
const d = n ? n(c, i) : c;
|
|
657
|
+
return i === 0 ? `${t2}${d}` : i === m.length - 1 ? `${s}${d}` : `${o}${d}`;
|
|
658
|
+
}).join(`
|
|
659
|
+
`);
|
|
660
|
+
}
|
|
661
|
+
function runValidation(e, n) {
|
|
662
|
+
if ("~standard" in e) {
|
|
663
|
+
const a = e["~standard"].validate(n);
|
|
664
|
+
if (a instanceof Promise)
|
|
665
|
+
throw new TypeError("Schema validation must be synchronous. Update `validate()` and remove any asynchronous logic.");
|
|
666
|
+
return a.issues?.at(0)?.message;
|
|
667
|
+
}
|
|
668
|
+
return e(n);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
class V {
|
|
672
|
+
input;
|
|
673
|
+
output;
|
|
674
|
+
_abortSignal;
|
|
675
|
+
rl;
|
|
676
|
+
opts;
|
|
677
|
+
_render;
|
|
678
|
+
_track = false;
|
|
679
|
+
_prevFrame = "";
|
|
680
|
+
_subscribers = /* @__PURE__ */ new Map;
|
|
681
|
+
_cursor = 0;
|
|
682
|
+
state = "initial";
|
|
683
|
+
error = "";
|
|
684
|
+
value;
|
|
685
|
+
userInput = "";
|
|
686
|
+
constructor(t2, e = true) {
|
|
687
|
+
const { input: i = stdin, output: n = stdout, render: s, signal: r, ...o } = t2;
|
|
688
|
+
this.opts = o, this.onKeypress = this.onKeypress.bind(this), this.close = this.close.bind(this), this.render = this.render.bind(this), this._render = s.bind(this), this._track = e, this._abortSignal = r, this.input = i, this.output = n;
|
|
689
|
+
}
|
|
690
|
+
unsubscribe() {
|
|
691
|
+
this._subscribers.clear();
|
|
692
|
+
}
|
|
693
|
+
setSubscriber(t2, e) {
|
|
694
|
+
const i = this._subscribers.get(t2) ?? [];
|
|
695
|
+
i.push(e), this._subscribers.set(t2, i);
|
|
696
|
+
}
|
|
697
|
+
on(t2, e) {
|
|
698
|
+
this.setSubscriber(t2, { cb: e });
|
|
699
|
+
}
|
|
700
|
+
once(t2, e) {
|
|
701
|
+
this.setSubscriber(t2, { cb: e, once: true });
|
|
702
|
+
}
|
|
703
|
+
emit(t2, ...e) {
|
|
704
|
+
const i = this._subscribers.get(t2) ?? [], n = [];
|
|
705
|
+
for (const s of i)
|
|
706
|
+
s.cb(...e), s.once && n.push(() => i.splice(i.indexOf(s), 1));
|
|
707
|
+
for (const s of n)
|
|
708
|
+
s();
|
|
709
|
+
}
|
|
710
|
+
prompt() {
|
|
711
|
+
return new Promise((t2) => {
|
|
712
|
+
if (this._abortSignal) {
|
|
713
|
+
if (this._abortSignal.aborted)
|
|
714
|
+
return this.state = "cancel", this.close(), t2(CANCEL_SYMBOL);
|
|
715
|
+
this._abortSignal.addEventListener("abort", () => {
|
|
716
|
+
this.state = "cancel", this.close();
|
|
717
|
+
}, { once: true });
|
|
718
|
+
}
|
|
719
|
+
this.rl = l__default.createInterface({
|
|
720
|
+
input: this.input,
|
|
721
|
+
tabSize: 2,
|
|
722
|
+
prompt: "",
|
|
723
|
+
escapeCodeTimeout: 50,
|
|
724
|
+
terminal: true
|
|
725
|
+
}), this.rl.prompt(), this.opts.initialUserInput !== undefined && this._setUserInput(this.opts.initialUserInput, true), this.input.on("keypress", this.onKeypress), setRawMode(this.input, true), this.output.on("resize", this.render), this.render(), this.once("submit", () => {
|
|
726
|
+
this.output.write(import_sisteransi.cursor.show), this.output.off("resize", this.render), setRawMode(this.input, false), t2(this.value);
|
|
727
|
+
}), this.once("cancel", () => {
|
|
728
|
+
this.output.write(import_sisteransi.cursor.show), this.output.off("resize", this.render), setRawMode(this.input, false), t2(CANCEL_SYMBOL);
|
|
729
|
+
});
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
_isActionKey(t2, e) {
|
|
733
|
+
return t2 === "\t";
|
|
734
|
+
}
|
|
735
|
+
_shouldSubmit(t2, e) {
|
|
736
|
+
return true;
|
|
737
|
+
}
|
|
738
|
+
_setValue(t2) {
|
|
739
|
+
this.value = t2, this.emit("value", this.value);
|
|
740
|
+
}
|
|
741
|
+
_setUserInput(t2, e) {
|
|
742
|
+
this.userInput = t2 ?? "", this.emit("userInput", this.userInput), e && this._track && this.rl && (this.rl.write(this.userInput), this._cursor = this.rl.cursor);
|
|
743
|
+
}
|
|
744
|
+
_clearUserInput() {
|
|
745
|
+
this.rl?.write(null, { ctrl: true, name: "u" }), this._setUserInput("");
|
|
746
|
+
}
|
|
747
|
+
onKeypress(t2, e) {
|
|
748
|
+
if (this._track && e.name !== "return" && (e.name && this._isActionKey(t2, e) && this.rl?.write(null, { ctrl: true, name: "h" }), this._cursor = this.rl?.cursor ?? 0, this._setUserInput(this.rl?.line)), this.state === "error" && (this.state = "active"), e?.name && (!this._track && settings.aliases.has(e.name) && this.emit("cursor", settings.aliases.get(e.name)), settings.actions.has(e.name) && this.emit("cursor", e.name)), t2 && (t2.toLowerCase() === "y" || t2.toLowerCase() === "n") && this.emit("confirm", t2.toLowerCase() === "y"), this.emit("key", t2, e), e?.name === "return" && this._shouldSubmit(t2, e)) {
|
|
749
|
+
if (this.opts.validate) {
|
|
750
|
+
const i = runValidation(this.opts.validate, this.value);
|
|
751
|
+
i && (this.error = i instanceof Error ? i.message : i, this.state = "error", this.rl?.write(this.userInput));
|
|
752
|
+
}
|
|
753
|
+
this.state !== "error" && (this.state = "submit");
|
|
754
|
+
}
|
|
755
|
+
isActionKey([t2, e?.name, e?.sequence], "cancel") && (this.state = "cancel"), (this.state === "submit" || this.state === "cancel") && this.emit("finalize"), this.render(), (this.state === "submit" || this.state === "cancel") && this.close();
|
|
756
|
+
}
|
|
757
|
+
close() {
|
|
758
|
+
this.input.unpipe(), this.input.removeListener("keypress", this.onKeypress), this.output.write(`
|
|
759
|
+
`), setRawMode(this.input, false), this.rl?.close(), this.rl = undefined, this.emit(`${this.state}`, this.value), this.unsubscribe();
|
|
760
|
+
}
|
|
761
|
+
restoreCursor() {
|
|
762
|
+
const t2 = wrapAnsi(this._prevFrame, process.stdout.columns, { hard: true, trim: false }).split(`
|
|
763
|
+
`).length - 1;
|
|
764
|
+
this.output.write(import_sisteransi.cursor.move(-999, t2 * -1));
|
|
765
|
+
}
|
|
766
|
+
render() {
|
|
767
|
+
const t2 = wrapAnsi(this._render(this) ?? "", process.stdout.columns, {
|
|
768
|
+
hard: true,
|
|
769
|
+
trim: false
|
|
770
|
+
});
|
|
771
|
+
if (t2 !== this._prevFrame) {
|
|
772
|
+
if (this.state === "initial")
|
|
773
|
+
this.output.write(import_sisteransi.cursor.hide);
|
|
774
|
+
else {
|
|
775
|
+
const e = diffLines(this._prevFrame, t2), i = getRows(this.output);
|
|
776
|
+
if (this.restoreCursor(), e) {
|
|
777
|
+
const n = Math.max(0, e.numLinesAfter - i), s = Math.max(0, e.numLinesBefore - i);
|
|
778
|
+
let r = e.lines.find((o) => o >= n);
|
|
779
|
+
if (r === undefined) {
|
|
780
|
+
this._prevFrame = t2;
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
if (e.lines.length === 1) {
|
|
784
|
+
this.output.write(import_sisteransi.cursor.move(0, r - s)), this.output.write(import_sisteransi.erase.lines(1));
|
|
785
|
+
const o = t2.split(`
|
|
786
|
+
`);
|
|
787
|
+
this.output.write(o[r]), this._prevFrame = t2, this.output.write(import_sisteransi.cursor.move(0, o.length - r - 1));
|
|
788
|
+
return;
|
|
789
|
+
} else if (e.lines.length > 1) {
|
|
790
|
+
if (n < s)
|
|
791
|
+
r = n;
|
|
792
|
+
else {
|
|
793
|
+
const h = r - s;
|
|
794
|
+
h > 0 && this.output.write(import_sisteransi.cursor.move(0, h));
|
|
795
|
+
}
|
|
796
|
+
this.output.write(import_sisteransi.erase.down());
|
|
797
|
+
const f = t2.split(`
|
|
798
|
+
`).slice(r);
|
|
799
|
+
this.output.write(f.join(`
|
|
800
|
+
`)), this._prevFrame = t2;
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
this.output.write(import_sisteransi.erase.down());
|
|
805
|
+
}
|
|
806
|
+
this.output.write(t2), this.state === "initial" && (this.state = "active"), this._prevFrame = t2;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
function p$1(l2, e) {
|
|
811
|
+
if (l2 === undefined || e.length === 0)
|
|
812
|
+
return 0;
|
|
813
|
+
const i = e.findIndex((s) => s.value === l2);
|
|
814
|
+
return i !== -1 ? i : 0;
|
|
815
|
+
}
|
|
816
|
+
function g(l2, e) {
|
|
817
|
+
return (e.label ?? String(e.value)).toLowerCase().includes(l2.toLowerCase());
|
|
818
|
+
}
|
|
819
|
+
function m(l2, e) {
|
|
820
|
+
if (e)
|
|
821
|
+
return l2 ? e : e[0];
|
|
822
|
+
}
|
|
823
|
+
var T$1 = class T extends V {
|
|
824
|
+
filteredOptions;
|
|
825
|
+
multiple;
|
|
826
|
+
isNavigating = false;
|
|
827
|
+
selectedValues = [];
|
|
828
|
+
focusedValue;
|
|
829
|
+
#e = 0;
|
|
830
|
+
#s = "";
|
|
831
|
+
#t;
|
|
832
|
+
#i;
|
|
833
|
+
#n;
|
|
834
|
+
get cursor() {
|
|
835
|
+
return this.#e;
|
|
836
|
+
}
|
|
837
|
+
get userInputWithCursor() {
|
|
838
|
+
if (!this.userInput)
|
|
839
|
+
return styleText(["inverse", "hidden"], "_");
|
|
840
|
+
if (this._cursor >= this.userInput.length)
|
|
841
|
+
return `${this.userInput}\u2588`;
|
|
842
|
+
const e = this.userInput.slice(0, this._cursor), [t2, ...i] = this.userInput.slice(this._cursor);
|
|
843
|
+
return `${e}${styleText("inverse", t2)}${i.join("")}`;
|
|
844
|
+
}
|
|
845
|
+
get options() {
|
|
846
|
+
return typeof this.#i == "function" ? this.#i() : this.#i;
|
|
847
|
+
}
|
|
848
|
+
constructor(e) {
|
|
849
|
+
super(e), this.#i = e.options, this.#n = e.placeholder;
|
|
850
|
+
const t2 = this.options;
|
|
851
|
+
this.filteredOptions = [...t2], this.multiple = e.multiple === true, this.#t = typeof e.options == "function" ? e.filter : e.filter ?? g;
|
|
852
|
+
let i;
|
|
853
|
+
if (e.initialValue && Array.isArray(e.initialValue) ? this.multiple ? i = e.initialValue : i = e.initialValue.slice(0, 1) : !this.multiple && this.options.length > 0 && (i = [this.options[0].value]), i)
|
|
854
|
+
for (const s of i) {
|
|
855
|
+
const n = t2.findIndex((o) => o.value === s);
|
|
856
|
+
n !== -1 && (this.toggleSelected(s), this.#e = n);
|
|
857
|
+
}
|
|
858
|
+
this.focusedValue = this.options[this.#e]?.value, this.on("key", (s, n) => this.#l(s, n)), this.on("userInput", (s) => this.#u(s));
|
|
859
|
+
}
|
|
860
|
+
_isActionKey(e, t2) {
|
|
861
|
+
return e === "\t" || this.multiple && this.isNavigating && t2.name === "space" && e !== undefined && e !== "";
|
|
862
|
+
}
|
|
863
|
+
#l(e, t2) {
|
|
864
|
+
const i = t2.name === "up", s = t2.name === "down", n = t2.name === "return", o = this.userInput === "" || this.userInput === "\t", u = this.#n, h = this.options, f = u !== undefined && u !== "" && h.some((r) => !r.disabled && (this.#t ? this.#t(u, r) : true));
|
|
865
|
+
if (t2.name === "tab" && o && f) {
|
|
866
|
+
this.userInput === "\t" && this._clearUserInput(), this._setUserInput(u, true), this.isNavigating = false;
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
i || s ? (this.#e = findCursor(this.#e, i ? -1 : 1, this.filteredOptions), this.focusedValue = this.filteredOptions[this.#e]?.value, this.multiple || (this.selectedValues = [this.focusedValue]), this.isNavigating = true) : n ? this.value = m(this.multiple, this.selectedValues) : this.multiple ? this.focusedValue !== undefined && (t2.name === "tab" || this.isNavigating && t2.name === "space") ? this.toggleSelected(this.focusedValue) : this.isNavigating = false : (this.focusedValue && (this.selectedValues = [this.focusedValue]), this.isNavigating = false);
|
|
870
|
+
}
|
|
871
|
+
deselectAll() {
|
|
872
|
+
this.selectedValues = [];
|
|
873
|
+
}
|
|
874
|
+
toggleSelected(e) {
|
|
875
|
+
this.filteredOptions.length !== 0 && (this.multiple ? this.selectedValues.includes(e) ? this.selectedValues = this.selectedValues.filter((t2) => t2 !== e) : this.selectedValues = [...this.selectedValues, e] : this.selectedValues = [e]);
|
|
876
|
+
}
|
|
877
|
+
#u(e) {
|
|
878
|
+
if (e !== this.#s) {
|
|
879
|
+
this.#s = e;
|
|
880
|
+
const t2 = this.options;
|
|
881
|
+
e && this.#t ? this.filteredOptions = t2.filter((n) => this.#t?.(e, n)) : this.filteredOptions = [...t2];
|
|
882
|
+
const i = p$1(this.focusedValue, this.filteredOptions);
|
|
883
|
+
this.#e = findCursor(i, 0, this.filteredOptions);
|
|
884
|
+
const s = this.filteredOptions[this.#e];
|
|
885
|
+
s && !s.disabled ? this.focusedValue = s.value : this.focusedValue = undefined, this.multiple || (this.focusedValue !== undefined ? this.toggleSelected(this.focusedValue) : this.deselectAll());
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
};
|
|
889
|
+
var _ = {
|
|
890
|
+
Y: { type: "year", len: 4 },
|
|
891
|
+
M: { type: "month", len: 2 },
|
|
892
|
+
D: { type: "day", len: 2 }
|
|
893
|
+
};
|
|
894
|
+
function M(r) {
|
|
895
|
+
return [...r].map((t2) => _[t2]);
|
|
896
|
+
}
|
|
897
|
+
function P(r) {
|
|
898
|
+
const i = new Intl.DateTimeFormat(r, {
|
|
899
|
+
year: "numeric",
|
|
900
|
+
month: "2-digit",
|
|
901
|
+
day: "2-digit"
|
|
902
|
+
}).formatToParts(new Date(2000, 0, 15)), s = [];
|
|
903
|
+
let n = "/";
|
|
904
|
+
for (const e of i)
|
|
905
|
+
e.type === "literal" ? n = e.value.trim() || e.value : (e.type === "year" || e.type === "month" || e.type === "day") && s.push({ type: e.type, len: e.type === "year" ? 4 : 2 });
|
|
906
|
+
return { segments: s, separator: n };
|
|
907
|
+
}
|
|
908
|
+
function p(r) {
|
|
909
|
+
return Number.parseInt((r || "0").replace(/_/g, "0"), 10) || 0;
|
|
910
|
+
}
|
|
911
|
+
function f(r) {
|
|
912
|
+
return {
|
|
913
|
+
year: p(r.year),
|
|
914
|
+
month: p(r.month),
|
|
915
|
+
day: p(r.day)
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
function c(r, t2) {
|
|
919
|
+
return new Date(r || 2001, t2 || 1, 0).getDate();
|
|
920
|
+
}
|
|
921
|
+
function b(r) {
|
|
922
|
+
const { year: t2, month: i, day: s } = f(r);
|
|
923
|
+
if (!t2 || t2 < 0 || t2 > 9999 || !i || i < 1 || i > 12 || !s || s < 1)
|
|
924
|
+
return;
|
|
925
|
+
const n = new Date(Date.UTC(t2, i - 1, s));
|
|
926
|
+
if (!(n.getUTCFullYear() !== t2 || n.getUTCMonth() !== i - 1 || n.getUTCDate() !== s))
|
|
927
|
+
return { year: t2, month: i, day: s };
|
|
928
|
+
}
|
|
929
|
+
function C(r) {
|
|
930
|
+
const t2 = b(r);
|
|
931
|
+
return t2 ? new Date(Date.UTC(t2.year, t2.month - 1, t2.day)) : undefined;
|
|
932
|
+
}
|
|
933
|
+
function T2(r, t2, i, s) {
|
|
934
|
+
const n = i ? {
|
|
935
|
+
year: i.getUTCFullYear(),
|
|
936
|
+
month: i.getUTCMonth() + 1,
|
|
937
|
+
day: i.getUTCDate()
|
|
938
|
+
} : null, e = s ? {
|
|
939
|
+
year: s.getUTCFullYear(),
|
|
940
|
+
month: s.getUTCMonth() + 1,
|
|
941
|
+
day: s.getUTCDate()
|
|
942
|
+
} : null;
|
|
943
|
+
return r === "year" ? { min: n?.year ?? 1, max: e?.year ?? 9999 } : r === "month" ? {
|
|
944
|
+
min: n && t2.year === n.year ? n.month : 1,
|
|
945
|
+
max: e && t2.year === e.year ? e.month : 12
|
|
946
|
+
} : {
|
|
947
|
+
min: n && t2.year === n.year && t2.month === n.month ? n.day : 1,
|
|
948
|
+
max: e && t2.year === e.year && t2.month === e.month ? e.day : c(t2.year, t2.month)
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
class U extends V {
|
|
953
|
+
#i;
|
|
954
|
+
#o;
|
|
955
|
+
#t;
|
|
956
|
+
#h;
|
|
957
|
+
#u;
|
|
958
|
+
#e = { segmentIndex: 0, positionInSegment: 0 };
|
|
959
|
+
#n = true;
|
|
960
|
+
#s = null;
|
|
961
|
+
inlineError = "";
|
|
962
|
+
get segmentCursor() {
|
|
963
|
+
return { ...this.#e };
|
|
964
|
+
}
|
|
965
|
+
get segmentValues() {
|
|
966
|
+
return { ...this.#t };
|
|
967
|
+
}
|
|
968
|
+
get segments() {
|
|
969
|
+
return this.#i;
|
|
970
|
+
}
|
|
971
|
+
get separator() {
|
|
972
|
+
return this.#o;
|
|
973
|
+
}
|
|
974
|
+
get formattedValue() {
|
|
975
|
+
return this.#l(this.#t);
|
|
976
|
+
}
|
|
977
|
+
#l(t2) {
|
|
978
|
+
return this.#i.map((i) => t2[i.type]).join(this.#o);
|
|
979
|
+
}
|
|
980
|
+
#r() {
|
|
981
|
+
this._setUserInput(this.#l(this.#t)), this._setValue(C(this.#t) ?? undefined);
|
|
982
|
+
}
|
|
983
|
+
constructor(t2) {
|
|
984
|
+
const i = t2.format ? { segments: M(t2.format), separator: t2.separator ?? "/" } : P(t2.locale), s = t2.separator ?? i.separator, n = t2.format ? M(t2.format) : i.segments, e = t2.initialValue ?? t2.defaultValue, m2 = e ? {
|
|
985
|
+
year: String(e.getUTCFullYear()).padStart(4, "0"),
|
|
986
|
+
month: String(e.getUTCMonth() + 1).padStart(2, "0"),
|
|
987
|
+
day: String(e.getUTCDate()).padStart(2, "0")
|
|
988
|
+
} : { year: "____", month: "__", day: "__" }, o = n.map((a) => m2[a.type]).join(s);
|
|
989
|
+
super({ ...t2, initialUserInput: o }, false), this.#i = n, this.#o = s, this.#t = m2, this.#h = t2.minDate, this.#u = t2.maxDate, this.#r(), this.on("cursor", (a) => this.#f(a)), this.on("key", (a, u) => this.#y(a, u)), this.on("finalize", () => this.#p(t2));
|
|
990
|
+
}
|
|
991
|
+
#a() {
|
|
992
|
+
const t2 = Math.max(0, Math.min(this.#e.segmentIndex, this.#i.length - 1)), i = this.#i[t2];
|
|
993
|
+
if (i)
|
|
994
|
+
return this.#e.positionInSegment = Math.max(0, Math.min(this.#e.positionInSegment, i.len - 1)), { segment: i, index: t2 };
|
|
995
|
+
}
|
|
996
|
+
#m(t2) {
|
|
997
|
+
this.inlineError = "", this.#s = null;
|
|
998
|
+
const i = this.#a();
|
|
999
|
+
i && (this.#e.segmentIndex = Math.max(0, Math.min(this.#i.length - 1, i.index + t2)), this.#e.positionInSegment = 0, this.#n = true);
|
|
1000
|
+
}
|
|
1001
|
+
#d(t2) {
|
|
1002
|
+
const i = this.#a();
|
|
1003
|
+
if (!i)
|
|
1004
|
+
return;
|
|
1005
|
+
const { segment: s } = i, n = this.#t[s.type], e = !n || n.replace(/_/g, "") === "", m2 = Number.parseInt((n || "0").replace(/_/g, "0"), 10) || 0, o = T2(s.type, f(this.#t), this.#h, this.#u);
|
|
1006
|
+
let a;
|
|
1007
|
+
e ? a = t2 === 1 ? o.min : o.max : a = Math.max(Math.min(o.max, m2 + t2), o.min), this.#t = {
|
|
1008
|
+
...this.#t,
|
|
1009
|
+
[s.type]: a.toString().padStart(s.len, "0")
|
|
1010
|
+
}, this.#n = true, this.#s = null, this.#r();
|
|
1011
|
+
}
|
|
1012
|
+
#f(t2) {
|
|
1013
|
+
if (t2)
|
|
1014
|
+
switch (t2) {
|
|
1015
|
+
case "right":
|
|
1016
|
+
return this.#m(1);
|
|
1017
|
+
case "left":
|
|
1018
|
+
return this.#m(-1);
|
|
1019
|
+
case "up":
|
|
1020
|
+
return this.#d(1);
|
|
1021
|
+
case "down":
|
|
1022
|
+
return this.#d(-1);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
#y(t2, i) {
|
|
1026
|
+
if (i?.name === "backspace" || i?.sequence === "\x7F" || i?.sequence === "\b" || t2 === "\x7F" || t2 === "\b") {
|
|
1027
|
+
this.inlineError = "";
|
|
1028
|
+
const n = this.#a();
|
|
1029
|
+
if (!n)
|
|
1030
|
+
return;
|
|
1031
|
+
if (!this.#t[n.segment.type].replace(/_/g, "")) {
|
|
1032
|
+
this.#m(-1);
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1035
|
+
this.#t[n.segment.type] = "_".repeat(n.segment.len), this.#n = true, this.#e.positionInSegment = 0, this.#r();
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
if (i?.name === "tab") {
|
|
1039
|
+
this.inlineError = "";
|
|
1040
|
+
const n = this.#a();
|
|
1041
|
+
if (!n)
|
|
1042
|
+
return;
|
|
1043
|
+
const e = i.shift ? -1 : 1, m2 = n.index + e;
|
|
1044
|
+
m2 >= 0 && m2 < this.#i.length && (this.#e.segmentIndex = m2, this.#e.positionInSegment = 0, this.#n = true);
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
if (t2 && /^[0-9]$/.test(t2)) {
|
|
1048
|
+
const n = this.#a();
|
|
1049
|
+
if (!n)
|
|
1050
|
+
return;
|
|
1051
|
+
const { segment: e } = n, m2 = !this.#t[e.type].replace(/_/g, "");
|
|
1052
|
+
if (this.#n && this.#s !== null && !m2) {
|
|
1053
|
+
const h = this.#s + t2, d = { ...this.#t, [e.type]: h }, g2 = this.#g(d, e);
|
|
1054
|
+
if (g2) {
|
|
1055
|
+
this.inlineError = g2, this.#s = null, this.#n = false;
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
this.inlineError = "", this.#t[e.type] = h, this.#s = null, this.#n = false, this.#r(), n.index < this.#i.length - 1 && (this.#e.segmentIndex = n.index + 1, this.#e.positionInSegment = 0, this.#n = true);
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
this.#n && !m2 && (this.#t[e.type] = "_".repeat(e.len), this.#e.positionInSegment = 0), this.#n = false, this.#s = null;
|
|
1062
|
+
const o = this.#t[e.type], a = o.indexOf("_"), u = a >= 0 ? a : Math.min(this.#e.positionInSegment, e.len - 1);
|
|
1063
|
+
if (u < 0 || u >= e.len)
|
|
1064
|
+
return;
|
|
1065
|
+
let l2 = o.slice(0, u) + t2 + o.slice(u + 1), D = false;
|
|
1066
|
+
if (u === 0 && o === "__" && (e.type === "month" || e.type === "day")) {
|
|
1067
|
+
const h = Number.parseInt(t2, 10);
|
|
1068
|
+
l2 = `0${t2}`, D = h <= (e.type === "month" ? 1 : 2);
|
|
1069
|
+
}
|
|
1070
|
+
if (e.type === "year" && (l2 = (o.replace(/_/g, "") + t2).padStart(e.len, "_")), !l2.includes("_")) {
|
|
1071
|
+
const h = { ...this.#t, [e.type]: l2 }, d = this.#g(h, e);
|
|
1072
|
+
if (d) {
|
|
1073
|
+
this.inlineError = d;
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
this.inlineError = "", this.#t[e.type] = l2;
|
|
1078
|
+
const y = l2.includes("_") ? undefined : b(this.#t);
|
|
1079
|
+
if (y) {
|
|
1080
|
+
const { year: h, month: d } = y, g2 = c(h, d);
|
|
1081
|
+
this.#t = {
|
|
1082
|
+
year: String(Math.max(0, Math.min(9999, h))).padStart(4, "0"),
|
|
1083
|
+
month: String(Math.max(1, Math.min(12, d))).padStart(2, "0"),
|
|
1084
|
+
day: String(Math.max(1, Math.min(g2, y.day))).padStart(2, "0")
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
this.#r();
|
|
1088
|
+
const S = l2.indexOf("_");
|
|
1089
|
+
D ? (this.#n = true, this.#s = t2) : S >= 0 ? this.#e.positionInSegment = S : a >= 0 && n.index < this.#i.length - 1 ? (this.#e.segmentIndex = n.index + 1, this.#e.positionInSegment = 0, this.#n = true) : this.#e.positionInSegment = Math.min(u + 1, e.len - 1);
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
#g(t2, i) {
|
|
1093
|
+
const { month: s, day: n } = f(t2);
|
|
1094
|
+
if (i.type === "month" && (s < 0 || s > 12))
|
|
1095
|
+
return settings.date.messages.invalidMonth;
|
|
1096
|
+
if (i.type === "day" && (n < 0 || n > 31))
|
|
1097
|
+
return settings.date.messages.invalidDay(31, "any month");
|
|
1098
|
+
}
|
|
1099
|
+
#p(t2) {
|
|
1100
|
+
const { year: i, month: s, day: n } = f(this.#t);
|
|
1101
|
+
if (i && s && n) {
|
|
1102
|
+
const e = c(i, s);
|
|
1103
|
+
this.#t = {
|
|
1104
|
+
...this.#t,
|
|
1105
|
+
day: String(Math.min(n, e)).padStart(2, "0")
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
this.value = C(this.#t) ?? t2.defaultValue ?? undefined;
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
var u$1 = class u extends V {
|
|
1112
|
+
options;
|
|
1113
|
+
cursor = 0;
|
|
1114
|
+
#t;
|
|
1115
|
+
getGroupItems(t2) {
|
|
1116
|
+
return this.options.filter((r) => r.group === t2);
|
|
1117
|
+
}
|
|
1118
|
+
isGroupSelected(t2) {
|
|
1119
|
+
const r = this.getGroupItems(t2), e = this.value;
|
|
1120
|
+
return e === undefined ? false : r.every((s) => e.includes(s.value));
|
|
1121
|
+
}
|
|
1122
|
+
toggleValue() {
|
|
1123
|
+
const t2 = this.options[this.cursor];
|
|
1124
|
+
if (this.value === undefined && (this.value = []), t2.group === true) {
|
|
1125
|
+
const r = t2.value, e = this.getGroupItems(r);
|
|
1126
|
+
this.isGroupSelected(r) ? this.value = this.value.filter((s) => e.findIndex((i) => i.value === s) === -1) : this.value = [...this.value, ...e.map((s) => s.value)], this.value = Array.from(new Set(this.value));
|
|
1127
|
+
} else {
|
|
1128
|
+
const r = this.value.includes(t2.value);
|
|
1129
|
+
this.value = r ? this.value.filter((e) => e !== t2.value) : [...this.value, t2.value];
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
constructor(t2) {
|
|
1133
|
+
super(t2, false);
|
|
1134
|
+
const { options: r } = t2;
|
|
1135
|
+
this.#t = t2.selectableGroups !== false, this.options = Object.entries(r).flatMap(([e, s]) => [
|
|
1136
|
+
{ value: e, group: true, label: e },
|
|
1137
|
+
...s.map((i) => ({ ...i, group: e }))
|
|
1138
|
+
]), this.value = [...t2.initialValues ?? []], this.cursor = Math.max(this.options.findIndex(({ value: e }) => e === t2.cursorAt), this.#t ? 0 : 1), this.on("cursor", (e) => {
|
|
1139
|
+
switch (e) {
|
|
1140
|
+
case "left":
|
|
1141
|
+
case "up": {
|
|
1142
|
+
this.cursor = this.cursor === 0 ? this.options.length - 1 : this.cursor - 1;
|
|
1143
|
+
const s = this.options[this.cursor]?.group === true;
|
|
1144
|
+
!this.#t && s && (this.cursor = this.cursor === 0 ? this.options.length - 1 : this.cursor - 1);
|
|
1145
|
+
break;
|
|
1146
|
+
}
|
|
1147
|
+
case "down":
|
|
1148
|
+
case "right": {
|
|
1149
|
+
this.cursor = this.cursor === this.options.length - 1 ? 0 : this.cursor + 1;
|
|
1150
|
+
const s = this.options[this.cursor]?.group === true;
|
|
1151
|
+
!this.#t && s && (this.cursor = this.cursor === this.options.length - 1 ? 0 : this.cursor + 1);
|
|
1152
|
+
break;
|
|
1153
|
+
}
|
|
1154
|
+
case "space":
|
|
1155
|
+
this.toggleValue();
|
|
1156
|
+
break;
|
|
1157
|
+
}
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1160
|
+
};
|
|
1161
|
+
var o$1 = /* @__PURE__ */ new Set(["up", "down", "left", "right"]);
|
|
1162
|
+
|
|
1163
|
+
class h extends V {
|
|
1164
|
+
#t = false;
|
|
1165
|
+
#s;
|
|
1166
|
+
focused = "editor";
|
|
1167
|
+
get userInputWithCursor() {
|
|
1168
|
+
if (this.state === "submit")
|
|
1169
|
+
return this.userInput;
|
|
1170
|
+
const t2 = this.userInput;
|
|
1171
|
+
if (this.cursor >= t2.length)
|
|
1172
|
+
return `${t2}\u2588`;
|
|
1173
|
+
const s = t2.slice(0, this.cursor), r = t2[this.cursor], i = t2.slice(this.cursor + 1);
|
|
1174
|
+
return r === `
|
|
1175
|
+
` ? `${s}\u2588
|
|
1176
|
+
${i}` : `${s}${styleText("inverse", r)}${i}`;
|
|
1177
|
+
}
|
|
1178
|
+
get cursor() {
|
|
1179
|
+
return this._cursor;
|
|
1180
|
+
}
|
|
1181
|
+
#r(t2) {
|
|
1182
|
+
if (this.userInput.length === 0) {
|
|
1183
|
+
this._setUserInput(t2);
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
this._setUserInput(this.userInput.slice(0, this.cursor) + t2 + this.userInput.slice(this.cursor));
|
|
1187
|
+
}
|
|
1188
|
+
#i(t2) {
|
|
1189
|
+
const s = this.value ?? "";
|
|
1190
|
+
switch (t2) {
|
|
1191
|
+
case "up":
|
|
1192
|
+
this._cursor = findTextCursor(this._cursor, 0, -1, s);
|
|
1193
|
+
return;
|
|
1194
|
+
case "down":
|
|
1195
|
+
this._cursor = findTextCursor(this._cursor, 0, 1, s);
|
|
1196
|
+
return;
|
|
1197
|
+
case "left":
|
|
1198
|
+
this._cursor = findTextCursor(this._cursor, -1, 0, s);
|
|
1199
|
+
return;
|
|
1200
|
+
case "right":
|
|
1201
|
+
this._cursor = findTextCursor(this._cursor, 1, 0, s);
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
_shouldSubmit(t2, s) {
|
|
1206
|
+
if (this.#s)
|
|
1207
|
+
return this.focused === "submit" ? true : (this.#r(`
|
|
1208
|
+
`), this._cursor++, false);
|
|
1209
|
+
const r = this.#t;
|
|
1210
|
+
return this.#t = true, r && this.cursor === this.userInput.length ? (this.userInput[this.cursor - 1] === `
|
|
1211
|
+
` && (this._setUserInput(this.userInput.slice(0, this.cursor - 1) + this.userInput.slice(this.cursor)), this._cursor--), true) : (this.#r(`
|
|
1212
|
+
`), this._cursor++, false);
|
|
1213
|
+
}
|
|
1214
|
+
constructor(t2) {
|
|
1215
|
+
const s = t2.initialUserInput ?? t2.initialValue;
|
|
1216
|
+
super({
|
|
1217
|
+
...t2,
|
|
1218
|
+
initialUserInput: s
|
|
1219
|
+
}, false), s !== undefined && (this._cursor = s.length), this.#s = t2.showSubmit ?? false, this.on("key", (r, i) => {
|
|
1220
|
+
if (i?.name && o$1.has(i.name)) {
|
|
1221
|
+
this.#t = false, this.#i(i.name);
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
if (r === "\t" && this.#s) {
|
|
1225
|
+
this.focused = this.focused === "editor" ? "submit" : "editor";
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
if (i?.name !== "return") {
|
|
1229
|
+
if (this.#t = false, i?.name === "backspace" && this.cursor > 0) {
|
|
1230
|
+
this._setUserInput(this.userInput.slice(0, this.cursor - 1) + this.userInput.slice(this.cursor)), this._cursor--;
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
if (i?.name === "delete" && this.cursor < this.userInput.length) {
|
|
1234
|
+
this._setUserInput(this.userInput.slice(0, this.cursor) + this.userInput.slice(this.cursor + 1));
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
r && (this.#s && this.focused === "submit" && (this.focused = "editor"), this.#r(r ?? ""), this._cursor++);
|
|
1238
|
+
}
|
|
1239
|
+
}), this.on("userInput", (r) => {
|
|
1240
|
+
this._setValue(r);
|
|
1241
|
+
}), this.on("finalize", () => {
|
|
1242
|
+
this.value || (this.value = t2.defaultValue), this.value === undefined && (this.value = "");
|
|
1243
|
+
});
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
class a extends V {
|
|
1247
|
+
options;
|
|
1248
|
+
cursor = 0;
|
|
1249
|
+
get _selectedValue() {
|
|
1250
|
+
return this.options[this.cursor];
|
|
1251
|
+
}
|
|
1252
|
+
changeValue() {
|
|
1253
|
+
this.value = this._selectedValue.value;
|
|
1254
|
+
}
|
|
1255
|
+
constructor(t2) {
|
|
1256
|
+
super(t2, false), this.options = t2.options;
|
|
1257
|
+
const i = this.options.findIndex(({ value: s }) => s === t2.initialValue), e = i === -1 ? 0 : i;
|
|
1258
|
+
this.cursor = this.options[e].disabled ? findCursor(e, 1, this.options) : e, this.changeValue(), this.on("cursor", (s) => {
|
|
1259
|
+
switch (s) {
|
|
1260
|
+
case "left":
|
|
1261
|
+
case "up":
|
|
1262
|
+
this.cursor = findCursor(this.cursor, -1, this.options);
|
|
1263
|
+
break;
|
|
1264
|
+
case "down":
|
|
1265
|
+
case "right":
|
|
1266
|
+
this.cursor = findCursor(this.cursor, 1, this.options);
|
|
1267
|
+
break;
|
|
1268
|
+
}
|
|
1269
|
+
this.changeValue();
|
|
1270
|
+
});
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
class n extends V {
|
|
1274
|
+
get userInputWithCursor() {
|
|
1275
|
+
if (this.state === "submit")
|
|
1276
|
+
return this.userInput;
|
|
1277
|
+
const t2 = this.userInput;
|
|
1278
|
+
if (this.cursor >= t2.length)
|
|
1279
|
+
return `${this.userInput}\u2588`;
|
|
1280
|
+
const e = t2.slice(0, this.cursor), [s, ...r] = t2.slice(this.cursor);
|
|
1281
|
+
return `${e}${styleText("inverse", s)}${r.join("")}`;
|
|
1282
|
+
}
|
|
1283
|
+
get cursor() {
|
|
1284
|
+
return this._cursor;
|
|
1285
|
+
}
|
|
1286
|
+
constructor(t2) {
|
|
1287
|
+
super({
|
|
1288
|
+
...t2,
|
|
1289
|
+
initialUserInput: t2.initialUserInput ?? t2.initialValue
|
|
1290
|
+
}), this.on("userInput", (e) => {
|
|
1291
|
+
this._setValue(e);
|
|
1292
|
+
}), this.on("finalize", () => {
|
|
1293
|
+
this.value || (this.value = t2.defaultValue), this.value === undefined && (this.value = "");
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
// ../../node_modules/.bun/@clack+prompts@1.6.0/node_modules/@clack/prompts/dist/index.mjs
|
|
1299
|
+
import { styleText as styleText2, stripVTControlCharacters } from "util";
|
|
1300
|
+
import process$1 from "process";
|
|
1301
|
+
var import_sisteransi2 = __toESM(require_src(), 1);
|
|
1302
|
+
function isUnicodeSupported() {
|
|
1303
|
+
if (process$1.platform !== "win32") {
|
|
1304
|
+
return process$1.env.TERM !== "linux";
|
|
1305
|
+
}
|
|
1306
|
+
return Boolean(process$1.env.CI) || Boolean(process$1.env.WT_SESSION) || Boolean(process$1.env.TERMINUS_SUBLIME) || process$1.env.ConEmuTask === "{cmd::Cmder}" || process$1.env.TERM_PROGRAM === "Terminus-Sublime" || process$1.env.TERM_PROGRAM === "vscode" || process$1.env.TERM === "xterm-256color" || process$1.env.TERM === "alacritty" || process$1.env.TERMINAL_EMULATOR === "JetBrains-JediTerm";
|
|
1307
|
+
}
|
|
1308
|
+
var unicode = isUnicodeSupported();
|
|
1309
|
+
var isCI = () => process.env.CI === "true";
|
|
1310
|
+
var unicodeOr = (o2, e) => unicode ? o2 : e;
|
|
1311
|
+
var S_STEP_ACTIVE = unicodeOr("\u25C6", "*");
|
|
1312
|
+
var S_STEP_CANCEL = unicodeOr("\u25A0", "x");
|
|
1313
|
+
var S_STEP_ERROR = unicodeOr("\u25B2", "x");
|
|
1314
|
+
var S_STEP_SUBMIT = unicodeOr("\u25C7", "o");
|
|
1315
|
+
var S_BAR_START = unicodeOr("\u250C", "T");
|
|
1316
|
+
var S_BAR = unicodeOr("\u2502", "|");
|
|
1317
|
+
var S_BAR_END = unicodeOr("\u2514", "\u2014");
|
|
1318
|
+
var S_BAR_START_RIGHT = unicodeOr("\u2510", "T");
|
|
1319
|
+
var S_BAR_END_RIGHT = unicodeOr("\u2518", "\u2014");
|
|
1320
|
+
var S_RADIO_ACTIVE = unicodeOr("\u25CF", ">");
|
|
1321
|
+
var S_RADIO_INACTIVE = unicodeOr("\u25CB", " ");
|
|
1322
|
+
var S_CHECKBOX_ACTIVE = unicodeOr("\u25FB", "[\u2022]");
|
|
1323
|
+
var S_CHECKBOX_SELECTED = unicodeOr("\u25FC", "[+]");
|
|
1324
|
+
var S_CHECKBOX_INACTIVE = unicodeOr("\u25FB", "[ ]");
|
|
1325
|
+
var S_PASSWORD_MASK = unicodeOr("\u25AA", "\u2022");
|
|
1326
|
+
var S_BAR_H = unicodeOr("\u2500", "-");
|
|
1327
|
+
var S_CORNER_TOP_RIGHT = unicodeOr("\u256E", "+");
|
|
1328
|
+
var S_CONNECT_LEFT = unicodeOr("\u251C", "+");
|
|
1329
|
+
var S_CORNER_BOTTOM_RIGHT = unicodeOr("\u256F", "+");
|
|
1330
|
+
var S_CORNER_BOTTOM_LEFT = unicodeOr("\u2570", "+");
|
|
1331
|
+
var S_CORNER_TOP_LEFT = unicodeOr("\u256D", "+");
|
|
1332
|
+
var S_INFO = unicodeOr("\u25CF", "\u2022");
|
|
1333
|
+
var S_SUCCESS = unicodeOr("\u25C6", "*");
|
|
1334
|
+
var S_WARN = unicodeOr("\u25B2", "!");
|
|
1335
|
+
var S_ERROR = unicodeOr("\u25A0", "x");
|
|
1336
|
+
var symbol = (o2) => {
|
|
1337
|
+
switch (o2) {
|
|
1338
|
+
case "initial":
|
|
1339
|
+
case "active":
|
|
1340
|
+
return styleText2("cyan", S_STEP_ACTIVE);
|
|
1341
|
+
case "cancel":
|
|
1342
|
+
return styleText2("red", S_STEP_CANCEL);
|
|
1343
|
+
case "error":
|
|
1344
|
+
return styleText2("yellow", S_STEP_ERROR);
|
|
1345
|
+
case "submit":
|
|
1346
|
+
return styleText2("green", S_STEP_SUBMIT);
|
|
1347
|
+
}
|
|
1348
|
+
};
|
|
1349
|
+
var symbolBar = (o2) => {
|
|
1350
|
+
switch (o2) {
|
|
1351
|
+
case "initial":
|
|
1352
|
+
case "active":
|
|
1353
|
+
return styleText2("cyan", S_BAR);
|
|
1354
|
+
case "cancel":
|
|
1355
|
+
return styleText2("red", S_BAR);
|
|
1356
|
+
case "error":
|
|
1357
|
+
return styleText2("yellow", S_BAR);
|
|
1358
|
+
case "submit":
|
|
1359
|
+
return styleText2("green", S_BAR);
|
|
1360
|
+
}
|
|
1361
|
+
};
|
|
1362
|
+
function formatInstructionFooter(o2, e) {
|
|
1363
|
+
const r2 = [`${e ? `${styleText2("cyan", S_BAR)} ` : ""}${o2.join(" \u2022 ")}`];
|
|
1364
|
+
return e && r2.push(styleText2("cyan", S_BAR_END)), r2;
|
|
1365
|
+
}
|
|
1366
|
+
var E$1 = (l2, o2, g2, c2, h2, O = false) => {
|
|
1367
|
+
let r2 = o2, w = 0;
|
|
1368
|
+
if (O)
|
|
1369
|
+
for (let i = c2 - 1;i >= g2 && (r2 -= l2[i].length, w++, !(r2 <= h2)); i--)
|
|
1370
|
+
;
|
|
1371
|
+
else
|
|
1372
|
+
for (let i = g2;i < c2 && (r2 -= l2[i].length, w++, !(r2 <= h2)); i++)
|
|
1373
|
+
;
|
|
1374
|
+
return { lineCount: r2, removals: w };
|
|
1375
|
+
};
|
|
1376
|
+
var limitOptions = ({
|
|
1377
|
+
cursor: l2,
|
|
1378
|
+
options: o2,
|
|
1379
|
+
style: g2,
|
|
1380
|
+
output: c2 = process.stdout,
|
|
1381
|
+
maxItems: h2 = Number.POSITIVE_INFINITY,
|
|
1382
|
+
columnPadding: O = 0,
|
|
1383
|
+
rowPadding: r2 = 4
|
|
1384
|
+
}) => {
|
|
1385
|
+
const i = getColumns(c2) - O, I = getRows(c2), C2 = styleText2("dim", "..."), x = Math.max(I - r2, 0), m2 = Math.max(Math.min(h2, x), 5);
|
|
1386
|
+
let p2 = 0;
|
|
1387
|
+
l2 >= m2 - 3 && (p2 = Math.max(Math.min(l2 - m2 + 3, o2.length - m2), 0));
|
|
1388
|
+
let f2 = m2 < o2.length && p2 > 0, u3 = m2 < o2.length && p2 + m2 < o2.length;
|
|
1389
|
+
const W = Math.min(p2 + m2, o2.length), e = [];
|
|
1390
|
+
let d = 0;
|
|
1391
|
+
f2 && d++, u3 && d++;
|
|
1392
|
+
const v = p2 + (f2 ? 1 : 0), P2 = W - (u3 ? 1 : 0);
|
|
1393
|
+
for (let t2 = v;t2 < P2; t2++) {
|
|
1394
|
+
const n2 = wrapAnsi(g2(o2[t2], t2 === l2), i, {
|
|
1395
|
+
hard: true,
|
|
1396
|
+
trim: false
|
|
1397
|
+
}).split(`
|
|
1398
|
+
`);
|
|
1399
|
+
e.push(n2), d += n2.length;
|
|
1400
|
+
}
|
|
1401
|
+
if (d > x) {
|
|
1402
|
+
let t2 = 0, n2 = 0, s = d;
|
|
1403
|
+
const M2 = l2 - v;
|
|
1404
|
+
let a2 = x;
|
|
1405
|
+
const T3 = () => E$1(e, s, 0, M2, a2), L = () => E$1(e, s, M2 + 1, e.length, a2, true);
|
|
1406
|
+
f2 ? ({ lineCount: s, removals: t2 } = T3(), s > a2 && (u3 || (a2 -= 1), { lineCount: s, removals: n2 } = L())) : (u3 || (a2 -= 1), { lineCount: s, removals: n2 } = L(), s > a2 && (a2 -= 1, { lineCount: s, removals: t2 } = T3())), t2 > 0 && (f2 = true, e.splice(0, t2)), n2 > 0 && (u3 = true, e.splice(e.length - n2, n2));
|
|
1407
|
+
}
|
|
1408
|
+
const b2 = [];
|
|
1409
|
+
f2 && b2.push(C2);
|
|
1410
|
+
for (const t2 of e)
|
|
1411
|
+
for (const n2 of t2)
|
|
1412
|
+
b2.push(n2);
|
|
1413
|
+
return u3 && b2.push(C2), b2;
|
|
1414
|
+
};
|
|
1415
|
+
var MULTISELECT_INSTRUCTIONS = [
|
|
1416
|
+
`${styleText2("dim", "\u2191/\u2193")} to navigate`,
|
|
1417
|
+
`${styleText2("dim", "Space:")} select`,
|
|
1418
|
+
`${styleText2("dim", "Enter:")} confirm`
|
|
1419
|
+
];
|
|
1420
|
+
var cancel = (o2 = "", t2) => {
|
|
1421
|
+
const i = t2?.output ?? process.stdout, e = t2?.withGuide ?? settings.withGuide ? `${styleText2("gray", S_BAR_END)} ` : "";
|
|
1422
|
+
i.write(`${e}${styleText2("red", o2)}
|
|
1423
|
+
|
|
1424
|
+
`);
|
|
1425
|
+
};
|
|
1426
|
+
var W = (l2) => styleText2("magenta", l2);
|
|
1427
|
+
var spinner = ({
|
|
1428
|
+
indicator: l2 = "dots",
|
|
1429
|
+
onCancel: h2,
|
|
1430
|
+
output: n2 = process.stdout,
|
|
1431
|
+
cancelMessage: G,
|
|
1432
|
+
errorMessage: O,
|
|
1433
|
+
frames: E = unicode ? ["\u25D2", "\u25D0", "\u25D3", "\u25D1"] : ["\u2022", "o", "O", "0"],
|
|
1434
|
+
delay: F = unicode ? 80 : 120,
|
|
1435
|
+
signal: m2,
|
|
1436
|
+
...I
|
|
1437
|
+
} = {}) => {
|
|
1438
|
+
const u3 = isCI();
|
|
1439
|
+
let M2, T3, d = false, S = false, s = "", p2, w = performance.now();
|
|
1440
|
+
const x = getColumns(n2), k = I?.styleFrame ?? W, g2 = (e) => {
|
|
1441
|
+
const r2 = e > 1 ? O ?? settings.messages.error : G ?? settings.messages.cancel;
|
|
1442
|
+
S = e === 1, d && (a2(r2, e), S && typeof h2 == "function" && h2());
|
|
1443
|
+
}, f2 = () => g2(2), i = () => g2(1), A = () => {
|
|
1444
|
+
process.on("uncaughtExceptionMonitor", f2), process.on("unhandledRejection", f2), process.on("SIGINT", i), process.on("SIGTERM", i), process.on("exit", g2), m2 && m2.addEventListener("abort", i);
|
|
1445
|
+
}, H = () => {
|
|
1446
|
+
process.removeListener("uncaughtExceptionMonitor", f2), process.removeListener("unhandledRejection", f2), process.removeListener("SIGINT", i), process.removeListener("SIGTERM", i), process.removeListener("exit", g2), m2 && m2.removeEventListener("abort", i);
|
|
1447
|
+
}, y = () => {
|
|
1448
|
+
if (p2 === undefined)
|
|
1449
|
+
return;
|
|
1450
|
+
u3 && n2.write(`
|
|
1451
|
+
`);
|
|
1452
|
+
const r2 = wrapAnsi(p2, x, {
|
|
1453
|
+
hard: true,
|
|
1454
|
+
trim: false
|
|
1455
|
+
}).split(`
|
|
1456
|
+
`);
|
|
1457
|
+
r2.length > 1 && n2.write(import_sisteransi2.cursor.up(r2.length - 1)), n2.write(import_sisteransi2.cursor.to(0)), n2.write(import_sisteransi2.erase.down());
|
|
1458
|
+
}, C2 = (e) => e.replace(/\.+$/, ""), _2 = (e) => {
|
|
1459
|
+
const r2 = (performance.now() - e) / 1000, t2 = Math.floor(r2 / 60), o2 = Math.floor(r2 % 60);
|
|
1460
|
+
return t2 > 0 ? `[${t2}m ${o2}s]` : `[${o2}s]`;
|
|
1461
|
+
}, N = I.withGuide ?? settings.withGuide, P2 = (e = "") => {
|
|
1462
|
+
d = true, M2 = block({ output: n2 }), s = C2(e), w = performance.now(), N && n2.write(`${styleText2("gray", S_BAR)}
|
|
1463
|
+
`);
|
|
1464
|
+
let r2 = 0, t2 = 0;
|
|
1465
|
+
A(), T3 = setInterval(() => {
|
|
1466
|
+
if (u3 && s === p2)
|
|
1467
|
+
return;
|
|
1468
|
+
y(), p2 = s;
|
|
1469
|
+
const o2 = k(E[r2]);
|
|
1470
|
+
let v;
|
|
1471
|
+
if (u3)
|
|
1472
|
+
v = `${o2} ${s}...`;
|
|
1473
|
+
else if (l2 === "timer")
|
|
1474
|
+
v = `${o2} ${s} ${_2(w)}`;
|
|
1475
|
+
else {
|
|
1476
|
+
const B = ".".repeat(Math.floor(t2)).slice(0, 3);
|
|
1477
|
+
v = `${o2} ${s}${B}`;
|
|
1478
|
+
}
|
|
1479
|
+
const j = wrapAnsi(v, x, {
|
|
1480
|
+
hard: true,
|
|
1481
|
+
trim: false
|
|
1482
|
+
});
|
|
1483
|
+
n2.write(j), r2 = r2 + 1 < E.length ? r2 + 1 : 0, t2 = t2 < 4 ? t2 + 0.125 : 0;
|
|
1484
|
+
}, F);
|
|
1485
|
+
}, a2 = (e = "", r2 = 0, t2 = false) => {
|
|
1486
|
+
if (!d)
|
|
1487
|
+
return;
|
|
1488
|
+
d = false, clearInterval(T3), y();
|
|
1489
|
+
const o2 = r2 === 0 ? styleText2("green", S_STEP_SUBMIT) : r2 === 1 ? styleText2("red", S_STEP_CANCEL) : styleText2("red", S_STEP_ERROR);
|
|
1490
|
+
s = e ?? s, t2 || (l2 === "timer" ? n2.write(`${o2} ${s} ${_2(w)}
|
|
1491
|
+
`) : n2.write(`${o2} ${s}
|
|
1492
|
+
`)), H(), M2();
|
|
1493
|
+
};
|
|
1494
|
+
return {
|
|
1495
|
+
start: P2,
|
|
1496
|
+
stop: (e = "") => a2(e, 0),
|
|
1497
|
+
message: (e = "") => {
|
|
1498
|
+
s = C2(e ?? s);
|
|
1499
|
+
},
|
|
1500
|
+
cancel: (e = "") => a2(e, 1),
|
|
1501
|
+
error: (e = "") => a2(e, 2),
|
|
1502
|
+
clear: () => a2("", 0, true),
|
|
1503
|
+
get isCancelled() {
|
|
1504
|
+
return S;
|
|
1505
|
+
}
|
|
1506
|
+
};
|
|
1507
|
+
};
|
|
1508
|
+
var u3 = {
|
|
1509
|
+
light: unicodeOr("\u2500", "-"),
|
|
1510
|
+
heavy: unicodeOr("\u2501", "="),
|
|
1511
|
+
block: unicodeOr("\u2588", "#")
|
|
1512
|
+
};
|
|
1513
|
+
var SELECT_INSTRUCTIONS = [
|
|
1514
|
+
`${styleText2("dim", "\u2191/\u2193")} to navigate`,
|
|
1515
|
+
`${styleText2("dim", "Enter:")} confirm`
|
|
1516
|
+
];
|
|
1517
|
+
var c2 = (t2, a2) => t2.includes(`
|
|
1518
|
+
`) ? t2.split(`
|
|
1519
|
+
`).map((i) => a2(i)).join(`
|
|
1520
|
+
`) : a2(t2);
|
|
1521
|
+
var select = (t2) => {
|
|
1522
|
+
const a2 = (i, m2) => {
|
|
1523
|
+
const s = i.label ?? String(i.value);
|
|
1524
|
+
switch (m2) {
|
|
1525
|
+
case "disabled":
|
|
1526
|
+
return `${styleText2("gray", S_RADIO_INACTIVE)} ${c2(s, (n2) => styleText2("gray", n2))}${i.hint ? ` ${styleText2("dim", `(${i.hint ?? "disabled"})`)}` : ""}`;
|
|
1527
|
+
case "selected":
|
|
1528
|
+
return `${c2(s, (n2) => styleText2("dim", n2))}`;
|
|
1529
|
+
case "active":
|
|
1530
|
+
return `${styleText2("green", S_RADIO_ACTIVE)} ${s}${i.hint ? ` ${styleText2("dim", `(${i.hint})`)}` : ""}`;
|
|
1531
|
+
case "cancelled":
|
|
1532
|
+
return `${c2(s, (n2) => styleText2(["strikethrough", "dim"], n2))}`;
|
|
1533
|
+
default:
|
|
1534
|
+
return `${styleText2("dim", S_RADIO_INACTIVE)} ${c2(s, (n2) => styleText2("dim", n2))}`;
|
|
1535
|
+
}
|
|
1536
|
+
};
|
|
1537
|
+
return new a({
|
|
1538
|
+
options: t2.options,
|
|
1539
|
+
signal: t2.signal,
|
|
1540
|
+
input: t2.input,
|
|
1541
|
+
output: t2.output,
|
|
1542
|
+
initialValue: t2.initialValue,
|
|
1543
|
+
render() {
|
|
1544
|
+
const i = t2.withGuide ?? settings.withGuide, m2 = `${symbol(this.state)} `, s = `${symbolBar(this.state)} `, n2 = wrapTextWithPrefix(t2.output, t2.message, s, m2), u4 = `${i ? `${styleText2("gray", S_BAR)}
|
|
1545
|
+
` : ""}${n2}
|
|
1546
|
+
`;
|
|
1547
|
+
switch (this.state) {
|
|
1548
|
+
case "submit": {
|
|
1549
|
+
const r2 = i ? `${styleText2("gray", S_BAR)} ` : "", o2 = wrapTextWithPrefix(t2.output, a2(this.options[this.cursor], "selected"), r2);
|
|
1550
|
+
return `${u4}${o2}`;
|
|
1551
|
+
}
|
|
1552
|
+
case "cancel": {
|
|
1553
|
+
const r2 = i ? `${styleText2("gray", S_BAR)} ` : "", o2 = wrapTextWithPrefix(t2.output, a2(this.options[this.cursor], "cancelled"), r2);
|
|
1554
|
+
return `${u4}${o2}${i ? `
|
|
1555
|
+
${styleText2("gray", S_BAR)}` : ""}`;
|
|
1556
|
+
}
|
|
1557
|
+
default: {
|
|
1558
|
+
const r2 = i ? `${styleText2("cyan", S_BAR)} ` : "", o2 = u4.split(`
|
|
1559
|
+
`).length, $ = formatInstructionFooter(SELECT_INSTRUCTIONS, i), h2 = $.join(`
|
|
1560
|
+
`), b2 = $.length + 1;
|
|
1561
|
+
return `${u4}${r2}${limitOptions({
|
|
1562
|
+
output: t2.output,
|
|
1563
|
+
cursor: this.cursor,
|
|
1564
|
+
options: this.options,
|
|
1565
|
+
maxItems: t2.maxItems,
|
|
1566
|
+
columnPadding: r2.length,
|
|
1567
|
+
rowPadding: o2 + b2,
|
|
1568
|
+
style: (p2, x) => a2(p2, p2.disabled ? "disabled" : x ? "active" : "inactive")
|
|
1569
|
+
}).join(`
|
|
1570
|
+
${r2}`)}
|
|
1571
|
+
${h2}
|
|
1572
|
+
`;
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
}).prompt();
|
|
1577
|
+
};
|
|
1578
|
+
var i = `${styleText2("gray", S_BAR)} `;
|
|
1579
|
+
var text = (t2) => new n({
|
|
1580
|
+
validate: t2.validate,
|
|
1581
|
+
placeholder: t2.placeholder,
|
|
1582
|
+
defaultValue: t2.defaultValue,
|
|
1583
|
+
initialValue: t2.initialValue,
|
|
1584
|
+
output: t2.output,
|
|
1585
|
+
signal: t2.signal,
|
|
1586
|
+
input: t2.input,
|
|
1587
|
+
render() {
|
|
1588
|
+
const i2 = t2?.withGuide ?? settings.withGuide, s = `${`${i2 ? `${styleText2("gray", S_BAR)}
|
|
1589
|
+
` : ""}${symbol(this.state)} `}${t2.message}
|
|
1590
|
+
`, c3 = t2.placeholder ? styleText2("inverse", t2.placeholder[0]) + styleText2("dim", t2.placeholder.slice(1)) : styleText2(["inverse", "hidden"], "_"), o2 = this.userInput ? this.userInputWithCursor : c3, a2 = this.value ?? "";
|
|
1591
|
+
switch (this.state) {
|
|
1592
|
+
case "error": {
|
|
1593
|
+
const n2 = this.error ? ` ${styleText2("yellow", this.error)}` : "", r2 = i2 ? `${styleText2("yellow", S_BAR)} ` : "", d = i2 ? styleText2("yellow", S_BAR_END) : "";
|
|
1594
|
+
return `${s.trim()}
|
|
1595
|
+
${r2}${o2}
|
|
1596
|
+
${d}${n2}
|
|
1597
|
+
`;
|
|
1598
|
+
}
|
|
1599
|
+
case "submit": {
|
|
1600
|
+
const n2 = a2 ? ` ${styleText2("dim", a2)}` : "", r2 = i2 ? styleText2("gray", S_BAR) : "";
|
|
1601
|
+
return `${s}${r2}${n2}`;
|
|
1602
|
+
}
|
|
1603
|
+
case "cancel": {
|
|
1604
|
+
const n2 = a2 ? ` ${styleText2(["strikethrough", "dim"], a2)}` : "", r2 = i2 ? styleText2("gray", S_BAR) : "";
|
|
1605
|
+
return `${s}${r2}${n2}${a2.trim() ? `
|
|
1606
|
+
${r2}` : ""}`;
|
|
1607
|
+
}
|
|
1608
|
+
default: {
|
|
1609
|
+
const n2 = i2 ? `${styleText2("cyan", S_BAR)} ` : "", r2 = i2 ? styleText2("cyan", S_BAR_END) : "";
|
|
1610
|
+
return `${s}${n2}${o2}
|
|
1611
|
+
${r2}
|
|
1612
|
+
`;
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
}).prompt();
|
|
1617
|
+
|
|
1618
|
+
// src/index.ts
|
|
1619
|
+
var import_picocolors = __toESM(require_picocolors(), 1);
|
|
1620
|
+
|
|
1621
|
+
// src/ascii.ts
|
|
1622
|
+
var RED = "\x1B[31m";
|
|
1623
|
+
var BOLD = "\x1B[1m";
|
|
1624
|
+
var RESET = "\x1B[0m";
|
|
1625
|
+
var BANNER = [
|
|
1626
|
+
"",
|
|
1627
|
+
`${BOLD}${RED} \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557${RESET}`,
|
|
1628
|
+
`${BOLD}${RED} \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2554\u255D \u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D${RESET}`,
|
|
1629
|
+
`${BOLD}${RED} \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557${RESET}`,
|
|
1630
|
+
`${BOLD}${RED} \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u2588\u2588 \u2588\u2588\u2551 \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551${RESET}`,
|
|
1631
|
+
`${BOLD}${RED} \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551${RESET}`,
|
|
1632
|
+
`${BOLD}${RED} \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D${RESET}`,
|
|
1633
|
+
"",
|
|
1634
|
+
`${BOLD}${RED} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500Rhein Sullivan\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${RESET}`,
|
|
1635
|
+
`${BOLD}${RED} Rakta.js \u2014 React.js, Bun, TypeScript, Easy Router, and Fullstack Generator${RESET}`,
|
|
1636
|
+
`${BOLD}${RED} Small in size. Fierce in speed. Alive in every route.${RESET}`,
|
|
1637
|
+
`${BOLD}${RED} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500Vyagra Nexus\u2122\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${RESET}`,
|
|
1638
|
+
""
|
|
1639
|
+
].join(`
|
|
1640
|
+
`);
|
|
1641
|
+
function printBanner() {
|
|
1642
|
+
console.log(BANNER);
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
// src/fileSystem.ts
|
|
1646
|
+
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
1647
|
+
import { dirname, join, resolve } from "path";
|
|
1648
|
+
function ensureDirectory(dirPath) {
|
|
1649
|
+
if (!existsSync(dirPath)) {
|
|
1650
|
+
mkdirSync(dirPath, {
|
|
1651
|
+
recursive: true
|
|
1652
|
+
});
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
function writeFile(absolutePath, fileContent) {
|
|
1656
|
+
ensureDirectory(dirname(absolutePath));
|
|
1657
|
+
writeFileSync(absolutePath, fileContent, "utf-8");
|
|
1658
|
+
}
|
|
1659
|
+
function writeProjectFiles(projectRoot, generatedFiles) {
|
|
1660
|
+
for (const projectFile of generatedFiles) {
|
|
1661
|
+
const absolutePath = join(resolve(projectRoot), projectFile.path);
|
|
1662
|
+
writeFile(absolutePath, projectFile.content);
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
// src/types.ts
|
|
1667
|
+
var CSS_DISPLAY = {
|
|
1668
|
+
tailwind: "Tailwind CSS",
|
|
1669
|
+
bootstrap: "Bootstrap",
|
|
1670
|
+
sass: "SASS",
|
|
1671
|
+
none: "None"
|
|
1672
|
+
};
|
|
1673
|
+
var BACKEND_DISPLAY = {
|
|
1674
|
+
gaman: "Gaman.js",
|
|
1675
|
+
express: "Express.js",
|
|
1676
|
+
nest: "Nest.js",
|
|
1677
|
+
adonis: "Adonis.js"
|
|
1678
|
+
};
|
|
1679
|
+
var DATABASE_DISPLAY = {
|
|
1680
|
+
postgresql: "PostgreSQL",
|
|
1681
|
+
mysql: "MySQL",
|
|
1682
|
+
mongodb: "MongoDB",
|
|
1683
|
+
firebase: "Firebase",
|
|
1684
|
+
sqlite: "SQLite",
|
|
1685
|
+
mariadb: "MariaDB",
|
|
1686
|
+
redis: "Redis",
|
|
1687
|
+
planetscale: "PlanetScale",
|
|
1688
|
+
neon: "Neon",
|
|
1689
|
+
turso: "Turso"
|
|
1690
|
+
};
|
|
1691
|
+
var RENDER_MODE_DISPLAY = {
|
|
1692
|
+
csr: "CSR \u2014 Client-Side Rendering",
|
|
1693
|
+
ssr: "SSR \u2014 Server-Side Rendering",
|
|
1694
|
+
ssg: "SSG \u2014 Static Site Generation",
|
|
1695
|
+
csg: "CSG \u2014 Configurable Static Generation",
|
|
1696
|
+
spa: "SPA \u2014 Single Page Application",
|
|
1697
|
+
hybrid: "Hybrid \u2014 Mixed per-route"
|
|
1698
|
+
};
|
|
1699
|
+
var PROJECT_MODE_DISPLAY = {
|
|
1700
|
+
fullstack: "Fullstack app (frontend + backend + database)",
|
|
1701
|
+
"frontend-only": "Frontend only (no backend, no database)"
|
|
1702
|
+
};
|
|
1703
|
+
|
|
1704
|
+
// src/generator.ts
|
|
1705
|
+
function getRootFiles(projectConfig) {
|
|
1706
|
+
const { projectName, projectMode } = projectConfig;
|
|
1707
|
+
const workspaces = projectMode === "fullstack" ? ["frontend", "backend", "shared"] : [];
|
|
1708
|
+
return [
|
|
1709
|
+
{
|
|
1710
|
+
path: "package.json",
|
|
1711
|
+
content: JSON.stringify({
|
|
1712
|
+
name: projectName,
|
|
1713
|
+
version: "0.1.0",
|
|
1714
|
+
private: true,
|
|
1715
|
+
...projectMode === "fullstack" ? { workspaces } : {},
|
|
1716
|
+
scripts: projectMode === "fullstack" ? {
|
|
1717
|
+
"dev:frontend": "cd frontend && bun run dev",
|
|
1718
|
+
"dev:backend": "cd backend && bun run dev",
|
|
1719
|
+
"build:frontend": "cd frontend && bun run build",
|
|
1720
|
+
"build:backend": "cd backend && bun run build",
|
|
1721
|
+
build: "bun run build:frontend && bun run build:backend",
|
|
1722
|
+
start: "cd backend && bun run start",
|
|
1723
|
+
typecheck: "cd frontend && bun run typecheck && cd ../backend && bun run typecheck"
|
|
1724
|
+
} : {
|
|
1725
|
+
dev: "rakta dev",
|
|
1726
|
+
build: "rakta build",
|
|
1727
|
+
start: "rakta start",
|
|
1728
|
+
typecheck: "tsc --noEmit"
|
|
1729
|
+
},
|
|
1730
|
+
description: `${projectName} \u2014 built with Rakta.js`
|
|
1731
|
+
}, null, 2)
|
|
1732
|
+
},
|
|
1733
|
+
{
|
|
1734
|
+
path: "tsconfig.base.json",
|
|
1735
|
+
content: JSON.stringify({
|
|
1736
|
+
compilerOptions: {
|
|
1737
|
+
target: "ESNext",
|
|
1738
|
+
module: "ESNext",
|
|
1739
|
+
moduleResolution: "Bundler",
|
|
1740
|
+
jsx: "react-jsx",
|
|
1741
|
+
lib: ["ESNext", "DOM", "DOM.Iterable"],
|
|
1742
|
+
strict: true,
|
|
1743
|
+
noUncheckedIndexedAccess: true,
|
|
1744
|
+
exactOptionalPropertyTypes: true,
|
|
1745
|
+
skipLibCheck: true,
|
|
1746
|
+
esModuleInterop: true,
|
|
1747
|
+
allowSyntheticDefaultImports: true,
|
|
1748
|
+
resolveJsonModule: true,
|
|
1749
|
+
verbatimModuleSyntax: true,
|
|
1750
|
+
isolatedModules: true
|
|
1751
|
+
},
|
|
1752
|
+
exclude: ["node_modules", "dist", "**/dist/**"]
|
|
1753
|
+
}, null, 2)
|
|
1754
|
+
},
|
|
1755
|
+
{
|
|
1756
|
+
path: "bunfig.toml",
|
|
1757
|
+
content: `[install]
|
|
1758
|
+
auto = "fallback"
|
|
1759
|
+
exact = false
|
|
1760
|
+
registry = "https://registry.npmjs.org/"
|
|
1761
|
+
|
|
1762
|
+
[run]
|
|
1763
|
+
bun = true
|
|
1764
|
+
`
|
|
1765
|
+
},
|
|
1766
|
+
{
|
|
1767
|
+
path: ".npmrc",
|
|
1768
|
+
content: `registry=https://registry.npmjs.org/
|
|
1769
|
+
strict-ssl=true
|
|
1770
|
+
fetch-retries=5
|
|
1771
|
+
fetch-retry-mintimeout=20000
|
|
1772
|
+
fetch-retry-maxtimeout=120000
|
|
1773
|
+
`
|
|
1774
|
+
},
|
|
1775
|
+
{
|
|
1776
|
+
path: ".env.example",
|
|
1777
|
+
content: `NODE_ENV=development
|
|
1778
|
+
`
|
|
1779
|
+
},
|
|
1780
|
+
{
|
|
1781
|
+
path: ".gitignore",
|
|
1782
|
+
content: `node_modules/
|
|
1783
|
+
dist/
|
|
1784
|
+
.env
|
|
1785
|
+
.env.*
|
|
1786
|
+
!.env.example
|
|
1787
|
+
.DS_Store
|
|
1788
|
+
*.log
|
|
1789
|
+
.rakta/
|
|
1790
|
+
`
|
|
1791
|
+
},
|
|
1792
|
+
{
|
|
1793
|
+
path: "README.md",
|
|
1794
|
+
content: generateProjectReadme(projectConfig)
|
|
1795
|
+
}
|
|
1796
|
+
];
|
|
1797
|
+
}
|
|
1798
|
+
function getFrontendOnlyFiles(projectConfig) {
|
|
1799
|
+
const { projectName, cssFramework } = projectConfig;
|
|
1800
|
+
const styleFileName = cssFramework === "sass" ? "globals.scss" : "globals.css";
|
|
1801
|
+
return [
|
|
1802
|
+
{
|
|
1803
|
+
path: "package.json",
|
|
1804
|
+
content: JSON.stringify({
|
|
1805
|
+
name: projectName,
|
|
1806
|
+
version: "0.1.0",
|
|
1807
|
+
private: true,
|
|
1808
|
+
type: "module",
|
|
1809
|
+
scripts: {
|
|
1810
|
+
dev: "rakta dev",
|
|
1811
|
+
build: "rakta build",
|
|
1812
|
+
start: "rakta start",
|
|
1813
|
+
routes: "rakta routes",
|
|
1814
|
+
typecheck: "tsc --noEmit"
|
|
1815
|
+
},
|
|
1816
|
+
dependencies: {
|
|
1817
|
+
rakta: "^0.1.0",
|
|
1818
|
+
react: "^19.2.7",
|
|
1819
|
+
"react-dom": "^19.2.7",
|
|
1820
|
+
...getCssDependencies(cssFramework)
|
|
1821
|
+
},
|
|
1822
|
+
devDependencies: {
|
|
1823
|
+
"@types/react": "^19.2.17",
|
|
1824
|
+
"@types/react-dom": "^19.2.3",
|
|
1825
|
+
typescript: "^6.0.3",
|
|
1826
|
+
...getCssDevDependencies(cssFramework)
|
|
1827
|
+
}
|
|
1828
|
+
}, null, 2)
|
|
1829
|
+
},
|
|
1830
|
+
{
|
|
1831
|
+
path: "tsconfig.json",
|
|
1832
|
+
content: JSON.stringify({
|
|
1833
|
+
extends: "./tsconfig.base.json",
|
|
1834
|
+
compilerOptions: {
|
|
1835
|
+
outDir: "./dist",
|
|
1836
|
+
rootDir: "./",
|
|
1837
|
+
types: ["react", "react-dom"]
|
|
1838
|
+
},
|
|
1839
|
+
include: [
|
|
1840
|
+
"rakta-env.d.ts",
|
|
1841
|
+
"app/**/*",
|
|
1842
|
+
"components/**/*",
|
|
1843
|
+
"styles/**/*",
|
|
1844
|
+
"rakta.config.ts"
|
|
1845
|
+
],
|
|
1846
|
+
exclude: ["node_modules", "dist"]
|
|
1847
|
+
}, null, 2)
|
|
1848
|
+
},
|
|
1849
|
+
{
|
|
1850
|
+
path: "rakta-env.d.ts",
|
|
1851
|
+
content: generateFrontendOnlyRaktaEnv()
|
|
1852
|
+
},
|
|
1853
|
+
{
|
|
1854
|
+
path: "rakta.config.ts",
|
|
1855
|
+
content: `import { defineRaktaConfig } from "rakta";
|
|
1856
|
+
|
|
1857
|
+
export default defineRaktaConfig({
|
|
1858
|
+
appName: "${projectName}",
|
|
1859
|
+
render: {
|
|
1860
|
+
defaultMode: "csr",
|
|
1861
|
+
routes: {},
|
|
1862
|
+
},
|
|
1863
|
+
});
|
|
1864
|
+
`
|
|
1865
|
+
},
|
|
1866
|
+
{
|
|
1867
|
+
path: "app/layout.tsx",
|
|
1868
|
+
content: generateFrontendOnlyLayout()
|
|
1869
|
+
},
|
|
1870
|
+
{
|
|
1871
|
+
path: "app/page.tsx",
|
|
1872
|
+
content: generateFrontendOnlyPage(projectName)
|
|
1873
|
+
},
|
|
1874
|
+
{
|
|
1875
|
+
path: "app/loading.tsx",
|
|
1876
|
+
content: generateFrontendOnlyLoading()
|
|
1877
|
+
},
|
|
1878
|
+
{
|
|
1879
|
+
path: "app/error.tsx",
|
|
1880
|
+
content: generateFrontendOnlyError()
|
|
1881
|
+
},
|
|
1882
|
+
{
|
|
1883
|
+
path: "app/notFound.tsx",
|
|
1884
|
+
content: generateFrontendOnlyNotFound()
|
|
1885
|
+
},
|
|
1886
|
+
{
|
|
1887
|
+
path: "app/components/raktaShrimpMascot.tsx",
|
|
1888
|
+
content: generateShrimpMascotComponent()
|
|
1889
|
+
},
|
|
1890
|
+
{
|
|
1891
|
+
path: "app/components/shrimpRunGame.tsx",
|
|
1892
|
+
content: generateShrimpRunGameComponent()
|
|
1893
|
+
},
|
|
1894
|
+
{
|
|
1895
|
+
path: `styles/${styleFileName}`,
|
|
1896
|
+
content: getFrontendOnlyCssGlobals(cssFramework)
|
|
1897
|
+
},
|
|
1898
|
+
{
|
|
1899
|
+
path: "public/.gitkeep",
|
|
1900
|
+
content: ""
|
|
1901
|
+
}
|
|
1902
|
+
];
|
|
1903
|
+
}
|
|
1904
|
+
function getFullstackFrontendFiles(projectConfig) {
|
|
1905
|
+
const { projectName, cssFramework } = projectConfig;
|
|
1906
|
+
const styleFileName = cssFramework === "sass" ? "globals.scss" : "globals.css";
|
|
1907
|
+
return [
|
|
1908
|
+
{
|
|
1909
|
+
path: "frontend/package.json",
|
|
1910
|
+
content: JSON.stringify({
|
|
1911
|
+
name: `${projectName}-frontend`,
|
|
1912
|
+
version: "0.1.0",
|
|
1913
|
+
private: true,
|
|
1914
|
+
scripts: {
|
|
1915
|
+
dev: "rakta dev",
|
|
1916
|
+
build: "rakta build",
|
|
1917
|
+
start: "rakta start",
|
|
1918
|
+
routes: "rakta routes",
|
|
1919
|
+
"imports:generate": "rakta imports:generate",
|
|
1920
|
+
"rpc:types": "rakta rpc:types",
|
|
1921
|
+
typecheck: "tsc --noEmit"
|
|
1922
|
+
},
|
|
1923
|
+
dependencies: {
|
|
1924
|
+
rakta: "^0.1.0",
|
|
1925
|
+
react: "^19.2.7",
|
|
1926
|
+
"react-dom": "^19.2.7",
|
|
1927
|
+
...getCssDependencies(cssFramework)
|
|
1928
|
+
},
|
|
1929
|
+
devDependencies: {
|
|
1930
|
+
"@types/react": "^19.2.17",
|
|
1931
|
+
"@types/react-dom": "^19.2.3",
|
|
1932
|
+
typescript: "^6.0.3",
|
|
1933
|
+
...getCssDevDependencies(cssFramework)
|
|
1934
|
+
}
|
|
1935
|
+
}, null, 2)
|
|
1936
|
+
},
|
|
1937
|
+
{
|
|
1938
|
+
path: "frontend/tsconfig.json",
|
|
1939
|
+
content: JSON.stringify({
|
|
1940
|
+
extends: "../tsconfig.base.json",
|
|
1941
|
+
compilerOptions: {
|
|
1942
|
+
outDir: "./dist",
|
|
1943
|
+
rootDir: "./"
|
|
1944
|
+
},
|
|
1945
|
+
include: [
|
|
1946
|
+
"app/**/*",
|
|
1947
|
+
"components/**/*",
|
|
1948
|
+
"lib/**/*",
|
|
1949
|
+
"stores/**/*",
|
|
1950
|
+
"schemas/**/*",
|
|
1951
|
+
"rakta.config.ts"
|
|
1952
|
+
],
|
|
1953
|
+
exclude: ["node_modules", "dist"]
|
|
1954
|
+
}, null, 2)
|
|
1955
|
+
},
|
|
1956
|
+
{
|
|
1957
|
+
path: "frontend/rakta.config.ts",
|
|
1958
|
+
content: `import { defineRaktaConfig } from "rakta";
|
|
1959
|
+
|
|
1960
|
+
export default defineRaktaConfig({
|
|
1961
|
+
appName: "${projectName}",
|
|
1962
|
+
render: {
|
|
1963
|
+
defaultMode: "csr",
|
|
1964
|
+
routes: {
|
|
1965
|
+
"/": "ssg",
|
|
1966
|
+
"/about": "ssg",
|
|
1967
|
+
"/blog": "csg",
|
|
1968
|
+
"/blog/:slug": "csg",
|
|
1969
|
+
"/dashboard": "csr"
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
});
|
|
1973
|
+
`
|
|
1974
|
+
},
|
|
1975
|
+
{
|
|
1976
|
+
path: "frontend/app/layout.tsx",
|
|
1977
|
+
content: `import React from "react";
|
|
1978
|
+
import "../styles/${styleFileName}";
|
|
1979
|
+
|
|
1980
|
+
interface RootLayoutProps {
|
|
1981
|
+
readonly children: React.ReactNode;
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
export default function RootLayout({ children }: RootLayoutProps) {
|
|
1985
|
+
return (
|
|
1986
|
+
<html lang="en">
|
|
1987
|
+
<body>{children}</body>
|
|
1988
|
+
</html>
|
|
1989
|
+
);
|
|
1990
|
+
}
|
|
1991
|
+
`
|
|
1992
|
+
},
|
|
1993
|
+
{
|
|
1994
|
+
path: "frontend/app/page.tsx",
|
|
1995
|
+
content: generateFullstackHomePage(projectName)
|
|
1996
|
+
},
|
|
1997
|
+
{
|
|
1998
|
+
path: "frontend/app/about/page.tsx",
|
|
1999
|
+
content: `import React from "react";
|
|
2000
|
+
|
|
2001
|
+
export default function AboutPage() {
|
|
2002
|
+
return (
|
|
2003
|
+
<main className="page-shell">
|
|
2004
|
+
<section className="hero-card">
|
|
2005
|
+
<p className="eyebrow">ABOUT</p>
|
|
2006
|
+
<h1>About ${projectName}</h1>
|
|
2007
|
+
<p>This project is built with Rakta.js, React, Bun, and TypeScript.</p>
|
|
2008
|
+
<a href="/">Back to home</a>
|
|
2009
|
+
</section>
|
|
2010
|
+
</main>
|
|
2011
|
+
);
|
|
2012
|
+
}
|
|
2013
|
+
`
|
|
2014
|
+
},
|
|
2015
|
+
{
|
|
2016
|
+
path: "frontend/app/blog/page.tsx",
|
|
2017
|
+
content: `import React from "react";
|
|
2018
|
+
|
|
2019
|
+
const BLOG_POSTS = [
|
|
2020
|
+
{ slug: "getting-started", title: "Getting started with Rakta.js" },
|
|
2021
|
+
{ slug: "file-based-routing", title: "File-based routing explained" },
|
|
2022
|
+
{ slug: "type-safe-rpc", title: "Type-safe API with CarubanWire" },
|
|
2023
|
+
];
|
|
2024
|
+
|
|
2025
|
+
export default function BlogPage() {
|
|
2026
|
+
return (
|
|
2027
|
+
<main className="page-shell">
|
|
2028
|
+
<section className="hero-card">
|
|
2029
|
+
<p className="eyebrow">BLOG</p>
|
|
2030
|
+
<h1>Articles</h1>
|
|
2031
|
+
<ul>
|
|
2032
|
+
{BLOG_POSTS.map((post) => (
|
|
2033
|
+
<li key={post.slug}>
|
|
2034
|
+
<a href={\`/blog/\${post.slug}\`}>{post.title}</a>
|
|
2035
|
+
</li>
|
|
2036
|
+
))}
|
|
2037
|
+
</ul>
|
|
2038
|
+
</section>
|
|
2039
|
+
</main>
|
|
2040
|
+
);
|
|
2041
|
+
}
|
|
2042
|
+
`
|
|
2043
|
+
},
|
|
2044
|
+
{
|
|
2045
|
+
path: "frontend/app/blog/[slug]/page.tsx",
|
|
2046
|
+
content: `import React from "react";
|
|
2047
|
+
|
|
2048
|
+
interface BlogPostPageProps {
|
|
2049
|
+
readonly params: {
|
|
2050
|
+
readonly slug?: string;
|
|
2051
|
+
};
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
export default function BlogPostPage({ params }: BlogPostPageProps) {
|
|
2055
|
+
const postTitle = params.slug?.replaceAll("-", " ") ?? "Article";
|
|
2056
|
+
|
|
2057
|
+
return (
|
|
2058
|
+
<main className="page-shell">
|
|
2059
|
+
<section className="hero-card">
|
|
2060
|
+
<p className="eyebrow">BLOG POST</p>
|
|
2061
|
+
<h1>{postTitle}</h1>
|
|
2062
|
+
<p>Slug: <code>{params.slug}</code></p>
|
|
2063
|
+
<a href="/blog">Back to blog</a>
|
|
2064
|
+
</section>
|
|
2065
|
+
</main>
|
|
2066
|
+
);
|
|
2067
|
+
}
|
|
2068
|
+
`
|
|
2069
|
+
},
|
|
2070
|
+
{
|
|
2071
|
+
path: "frontend/app/loading.tsx",
|
|
2072
|
+
content: `import React from "react";
|
|
2073
|
+
|
|
2074
|
+
export default function Loading() {
|
|
2075
|
+
return (
|
|
2076
|
+
<main className="page-shell">
|
|
2077
|
+
<p>Loading...</p>
|
|
2078
|
+
</main>
|
|
2079
|
+
);
|
|
2080
|
+
}
|
|
2081
|
+
`
|
|
2082
|
+
},
|
|
2083
|
+
{
|
|
2084
|
+
path: "frontend/app/error.tsx",
|
|
2085
|
+
content: `import React from "react";
|
|
2086
|
+
|
|
2087
|
+
interface ErrorPageProps {
|
|
2088
|
+
readonly error: Error;
|
|
2089
|
+
readonly reset: () => void;
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
export default function ErrorPage({ error, reset }: ErrorPageProps) {
|
|
2093
|
+
return (
|
|
2094
|
+
<main className="page-shell">
|
|
2095
|
+
<section className="hero-card">
|
|
2096
|
+
<h1>Something went wrong</h1>
|
|
2097
|
+
<p>{error.message}</p>
|
|
2098
|
+
<button type="button" onClick={reset}>Try again</button>
|
|
2099
|
+
</section>
|
|
2100
|
+
</main>
|
|
2101
|
+
);
|
|
2102
|
+
}
|
|
2103
|
+
`
|
|
2104
|
+
},
|
|
2105
|
+
{
|
|
2106
|
+
path: "frontend/app/not-found.tsx",
|
|
2107
|
+
content: `import React from "react";
|
|
2108
|
+
|
|
2109
|
+
export default function NotFound() {
|
|
2110
|
+
return (
|
|
2111
|
+
<main className="page-shell">
|
|
2112
|
+
<section className="hero-card">
|
|
2113
|
+
<h1>404</h1>
|
|
2114
|
+
<p>The page you are looking for does not exist.</p>
|
|
2115
|
+
<a href="/">Return home</a>
|
|
2116
|
+
</section>
|
|
2117
|
+
</main>
|
|
2118
|
+
);
|
|
2119
|
+
}
|
|
2120
|
+
`
|
|
2121
|
+
},
|
|
2122
|
+
{
|
|
2123
|
+
path: "frontend/app/api/hello/route.ts",
|
|
2124
|
+
content: `export async function GET(request: Request): Promise<Response> {
|
|
2125
|
+
const requestUrl = new URL(request.url);
|
|
2126
|
+
|
|
2127
|
+
return Response.json({
|
|
2128
|
+
message: "Hello from Rakta.js API",
|
|
2129
|
+
pathname: requestUrl.pathname,
|
|
2130
|
+
timestamp: new Date().toISOString(),
|
|
2131
|
+
});
|
|
2132
|
+
}
|
|
2133
|
+
|
|
2134
|
+
export async function POST(request: Request): Promise<Response> {
|
|
2135
|
+
const requestBody = await request.json() as Record<string, unknown>;
|
|
2136
|
+
|
|
2137
|
+
return Response.json({
|
|
2138
|
+
received: requestBody,
|
|
2139
|
+
timestamp: new Date().toISOString(),
|
|
2140
|
+
});
|
|
2141
|
+
}
|
|
2142
|
+
`
|
|
2143
|
+
},
|
|
2144
|
+
{
|
|
2145
|
+
path: "frontend/lib/http.ts",
|
|
2146
|
+
content: `export const API_URL = process.env["API_URL"] ?? "http://localhost:4000";
|
|
2147
|
+
|
|
2148
|
+
export async function apiGet<TData>(path: string): Promise<TData> {
|
|
2149
|
+
const response = await fetch(\`\${API_URL}\${path}\`);
|
|
2150
|
+
|
|
2151
|
+
if (!response.ok) {
|
|
2152
|
+
throw new Error(\`Request failed with status \${response.status}\`);
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
return response.json() as Promise<TData>;
|
|
2156
|
+
}
|
|
2157
|
+
`
|
|
2158
|
+
},
|
|
2159
|
+
{
|
|
2160
|
+
path: "frontend/lib/routes.ts",
|
|
2161
|
+
content: `export const ROUTES = {
|
|
2162
|
+
home: "/",
|
|
2163
|
+
about: "/about",
|
|
2164
|
+
blog: "/blog",
|
|
2165
|
+
blogPost: (slug: string) => \`/blog/\${slug}\`,
|
|
2166
|
+
apiHello: "/api/hello",
|
|
2167
|
+
} as const;
|
|
2168
|
+
`
|
|
2169
|
+
},
|
|
2170
|
+
{
|
|
2171
|
+
path: "frontend/lib/utils.ts",
|
|
2172
|
+
content: `export function cn(...classNames: Array<string | undefined | null | false>): string {
|
|
2173
|
+
return classNames.filter(Boolean).join(" ");
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
export function slugify(text: string): string {
|
|
2177
|
+
return text
|
|
2178
|
+
.toLowerCase()
|
|
2179
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
2180
|
+
.replace(/(^-|-$)/g, "");
|
|
2181
|
+
}
|
|
2182
|
+
`
|
|
2183
|
+
},
|
|
2184
|
+
{
|
|
2185
|
+
path: "frontend/stores/counter.store.ts",
|
|
2186
|
+
content: `import { createRaktaStore } from "rakta";
|
|
2187
|
+
|
|
2188
|
+
interface CounterState {
|
|
2189
|
+
readonly count: number;
|
|
2190
|
+
readonly increment: () => void;
|
|
2191
|
+
readonly decrement: () => void;
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2194
|
+
export const useCounterStore = createRaktaStore<CounterState>((setState, getState) => ({
|
|
2195
|
+
count: 0,
|
|
2196
|
+
increment: () => setState({ count: getState().count + 1 }),
|
|
2197
|
+
decrement: () => setState({ count: getState().count - 1 }),
|
|
2198
|
+
}));
|
|
2199
|
+
`
|
|
2200
|
+
},
|
|
2201
|
+
{
|
|
2202
|
+
path: "frontend/schemas/user.schema.ts",
|
|
2203
|
+
content: `import { object, string, number } from "rakta";
|
|
2204
|
+
|
|
2205
|
+
export const userSchema = object({
|
|
2206
|
+
name: string().min(1),
|
|
2207
|
+
email: string().min(5),
|
|
2208
|
+
age: number().min(0).max(120),
|
|
2209
|
+
});
|
|
2210
|
+
|
|
2211
|
+
export type UserSchema = typeof userSchema;
|
|
2212
|
+
`
|
|
2213
|
+
},
|
|
2214
|
+
{
|
|
2215
|
+
path: `frontend/styles/${styleFileName}`,
|
|
2216
|
+
content: getCssGlobals(cssFramework)
|
|
2217
|
+
},
|
|
2218
|
+
{
|
|
2219
|
+
path: "frontend/public/.gitkeep",
|
|
2220
|
+
content: ""
|
|
2221
|
+
},
|
|
2222
|
+
{
|
|
2223
|
+
path: "frontend/components/ui/.gitkeep",
|
|
2224
|
+
content: ""
|
|
2225
|
+
},
|
|
2226
|
+
{
|
|
2227
|
+
path: "frontend/components/layout/.gitkeep",
|
|
2228
|
+
content: ""
|
|
2229
|
+
}
|
|
2230
|
+
];
|
|
2231
|
+
}
|
|
2232
|
+
function getBackendFiles(projectConfig) {
|
|
2233
|
+
return [
|
|
2234
|
+
...getBackendCommonFiles(projectConfig),
|
|
2235
|
+
...getBackendFrameworkFiles(projectConfig.backendFramework, projectConfig)
|
|
2236
|
+
];
|
|
2237
|
+
}
|
|
2238
|
+
function getBackendCommonFiles(projectConfig) {
|
|
2239
|
+
return [
|
|
2240
|
+
{
|
|
2241
|
+
path: "backend/src/env.ts",
|
|
2242
|
+
content: `function optionalEnv(envKey: string, fallbackValue: string): string {
|
|
2243
|
+
return process.env[envKey] ?? fallbackValue;
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
export const env = {
|
|
2247
|
+
port: Number(optionalEnv("PORT", "4000")),
|
|
2248
|
+
nodeEnv: optionalEnv("NODE_ENV", "development"),
|
|
2249
|
+
corsOrigin: optionalEnv("CORS_ORIGIN", "http://localhost:3000"),
|
|
2250
|
+
databaseUrl: optionalEnv("DATABASE_URL", ""),
|
|
2251
|
+
} as const;
|
|
2252
|
+
`
|
|
2253
|
+
},
|
|
2254
|
+
{
|
|
2255
|
+
path: "backend/src/config/app.config.ts",
|
|
2256
|
+
content: `import { env } from "../env";
|
|
2257
|
+
|
|
2258
|
+
export const appConfig = {
|
|
2259
|
+
name: "${projectConfig.projectName} API",
|
|
2260
|
+
version: "0.1.0",
|
|
2261
|
+
port: env.port,
|
|
2262
|
+
nodeEnv: env.nodeEnv,
|
|
2263
|
+
corsOrigin: env.corsOrigin,
|
|
2264
|
+
isDev: env.nodeEnv === "development",
|
|
2265
|
+
} as const;
|
|
2266
|
+
`
|
|
2267
|
+
},
|
|
2268
|
+
{
|
|
2269
|
+
path: "backend/src/config/database.config.ts",
|
|
2270
|
+
content: getDatabaseConfig(projectConfig.database)
|
|
2271
|
+
},
|
|
2272
|
+
{
|
|
2273
|
+
path: "backend/src/database/client.ts",
|
|
2274
|
+
content: getDatabaseClient(projectConfig.database)
|
|
2275
|
+
},
|
|
2276
|
+
{
|
|
2277
|
+
path: "backend/src/controllers/hello.controller.ts",
|
|
2278
|
+
content: `export interface HelloResponse {
|
|
2279
|
+
readonly success: boolean;
|
|
2280
|
+
readonly message: string;
|
|
2281
|
+
readonly framework: string;
|
|
2282
|
+
readonly version: string;
|
|
2283
|
+
readonly timestamp: string;
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
export function helloController(): HelloResponse {
|
|
2287
|
+
return {
|
|
2288
|
+
success: true,
|
|
2289
|
+
message: "Hello from ${projectConfig.projectName} API",
|
|
2290
|
+
framework: "${BACKEND_DISPLAY[projectConfig.backendFramework]}",
|
|
2291
|
+
version: "0.1.0",
|
|
2292
|
+
timestamp: new Date().toISOString(),
|
|
2293
|
+
};
|
|
2294
|
+
}
|
|
2295
|
+
`
|
|
2296
|
+
},
|
|
2297
|
+
{
|
|
2298
|
+
path: "backend/.env.example",
|
|
2299
|
+
content: getDatabaseEnvExample(projectConfig.database)
|
|
2300
|
+
},
|
|
2301
|
+
{
|
|
2302
|
+
path: "backend/tsconfig.json",
|
|
2303
|
+
content: JSON.stringify({
|
|
2304
|
+
extends: "../tsconfig.base.json",
|
|
2305
|
+
compilerOptions: {
|
|
2306
|
+
outDir: "./dist",
|
|
2307
|
+
rootDir: "./src",
|
|
2308
|
+
types: ["node", "bun"]
|
|
2309
|
+
},
|
|
2310
|
+
include: ["src/**/*"],
|
|
2311
|
+
exclude: ["node_modules", "dist"]
|
|
2312
|
+
}, null, 2)
|
|
2313
|
+
},
|
|
2314
|
+
{
|
|
2315
|
+
path: "backend/src/database/schema/.gitkeep",
|
|
2316
|
+
content: ""
|
|
2317
|
+
}
|
|
2318
|
+
];
|
|
2319
|
+
}
|
|
2320
|
+
function getBackendFrameworkFiles(selectedFramework, projectConfig) {
|
|
2321
|
+
switch (selectedFramework) {
|
|
2322
|
+
case "gaman":
|
|
2323
|
+
return getGamanFiles(projectConfig);
|
|
2324
|
+
case "express":
|
|
2325
|
+
return getExpressFiles(projectConfig);
|
|
2326
|
+
case "nest":
|
|
2327
|
+
return getNestFiles(projectConfig);
|
|
2328
|
+
case "adonis":
|
|
2329
|
+
return getAdonisFiles(projectConfig);
|
|
2330
|
+
default:
|
|
2331
|
+
return getGamanFiles(projectConfig);
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
function getGamanFiles(projectConfig) {
|
|
2335
|
+
return [
|
|
2336
|
+
{
|
|
2337
|
+
path: "backend/package.json",
|
|
2338
|
+
content: getBackendPackageJson(projectConfig, {
|
|
2339
|
+
dependencies: { ...getDatabaseDependencies(projectConfig.database) },
|
|
2340
|
+
devDependencies: { "@types/bun": "^1.3.14" }
|
|
2341
|
+
})
|
|
2342
|
+
},
|
|
2343
|
+
{
|
|
2344
|
+
path: "backend/src/app.ts",
|
|
2345
|
+
content: `import { appConfig } from "./config/app.config";
|
|
2346
|
+
import { helloController } from "./controllers/hello.controller";
|
|
2347
|
+
|
|
2348
|
+
const corsHeaders = {
|
|
2349
|
+
"Access-Control-Allow-Origin": appConfig.corsOrigin,
|
|
2350
|
+
"Access-Control-Allow-Methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS",
|
|
2351
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
2352
|
+
};
|
|
2353
|
+
|
|
2354
|
+
const server = Bun.serve({
|
|
2355
|
+
port: appConfig.port,
|
|
2356
|
+
fetch(request: Request): Response {
|
|
2357
|
+
const requestUrl = new URL(request.url);
|
|
2358
|
+
|
|
2359
|
+
if (request.method === "OPTIONS") {
|
|
2360
|
+
return new Response(null, { status: 204, headers: corsHeaders });
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2363
|
+
if (requestUrl.pathname === "/api/hello" && request.method === "GET") {
|
|
2364
|
+
return Response.json(helloController(), { headers: corsHeaders });
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
return Response.json(
|
|
2368
|
+
{ success: false, error: "Not found" },
|
|
2369
|
+
{ status: 404, headers: corsHeaders }
|
|
2370
|
+
);
|
|
2371
|
+
},
|
|
2372
|
+
});
|
|
2373
|
+
|
|
2374
|
+
console.log(\`[${projectConfig.projectName}] Backend running at http://localhost:\${server.port}\`);
|
|
2375
|
+
`
|
|
2376
|
+
}
|
|
2377
|
+
];
|
|
2378
|
+
}
|
|
2379
|
+
function getExpressFiles(projectConfig) {
|
|
2380
|
+
return [
|
|
2381
|
+
{
|
|
2382
|
+
path: "backend/package.json",
|
|
2383
|
+
content: getBackendPackageJson(projectConfig, {
|
|
2384
|
+
dependencies: {
|
|
2385
|
+
express: "^4.19.2",
|
|
2386
|
+
cors: "^2.8.5",
|
|
2387
|
+
...getDatabaseDependencies(projectConfig.database)
|
|
2388
|
+
},
|
|
2389
|
+
devDependencies: {
|
|
2390
|
+
"@types/express": "^4.17.21",
|
|
2391
|
+
"@types/cors": "^2.8.17",
|
|
2392
|
+
"@types/node": "^26.0.1"
|
|
2393
|
+
}
|
|
2394
|
+
})
|
|
2395
|
+
},
|
|
2396
|
+
{
|
|
2397
|
+
path: "backend/src/app.ts",
|
|
2398
|
+
content: `import cors from "cors";
|
|
2399
|
+
import express from "express";
|
|
2400
|
+
import { appConfig } from "./config/app.config";
|
|
2401
|
+
import { helloController } from "./controllers/hello.controller";
|
|
2402
|
+
|
|
2403
|
+
const app = express();
|
|
2404
|
+
|
|
2405
|
+
app.use(cors({ origin: appConfig.corsOrigin }));
|
|
2406
|
+
app.use(express.json());
|
|
2407
|
+
|
|
2408
|
+
app.get("/api/hello", (_request, response) => {
|
|
2409
|
+
response.json(helloController());
|
|
2410
|
+
});
|
|
2411
|
+
|
|
2412
|
+
app.use((_request, response) => {
|
|
2413
|
+
response.status(404).json({ success: false, error: "Not found" });
|
|
2414
|
+
});
|
|
2415
|
+
|
|
2416
|
+
app.listen(appConfig.port, () => {
|
|
2417
|
+
console.log(\`[${projectConfig.projectName}] Backend running at http://localhost:\${appConfig.port}\`);
|
|
2418
|
+
});
|
|
2419
|
+
`
|
|
2420
|
+
},
|
|
2421
|
+
{
|
|
2422
|
+
path: "backend/src/middlewares/.gitkeep",
|
|
2423
|
+
content: ""
|
|
2424
|
+
},
|
|
2425
|
+
{
|
|
2426
|
+
path: "backend/src/routes/.gitkeep",
|
|
2427
|
+
content: ""
|
|
2428
|
+
},
|
|
2429
|
+
{
|
|
2430
|
+
path: "backend/src/services/.gitkeep",
|
|
2431
|
+
content: ""
|
|
2432
|
+
}
|
|
2433
|
+
];
|
|
2434
|
+
}
|
|
2435
|
+
function getNestFiles(projectConfig) {
|
|
2436
|
+
return [
|
|
2437
|
+
{
|
|
2438
|
+
path: "backend/package.json",
|
|
2439
|
+
content: getBackendPackageJson(projectConfig, {
|
|
2440
|
+
dependencies: {
|
|
2441
|
+
"@nestjs/common": "^10.3.0",
|
|
2442
|
+
"@nestjs/core": "^10.3.0",
|
|
2443
|
+
"@nestjs/platform-express": "^10.3.0",
|
|
2444
|
+
"reflect-metadata": "^0.2.2",
|
|
2445
|
+
...getDatabaseDependencies(projectConfig.database)
|
|
2446
|
+
},
|
|
2447
|
+
devDependencies: {
|
|
2448
|
+
"@types/node": "^26.0.1"
|
|
2449
|
+
}
|
|
2450
|
+
})
|
|
2451
|
+
},
|
|
2452
|
+
{
|
|
2453
|
+
path: "backend/nest-cli.json",
|
|
2454
|
+
content: JSON.stringify({
|
|
2455
|
+
$schema: "https://json.schemastore.org/nest-cli",
|
|
2456
|
+
collection: "@nestjs/schematics",
|
|
2457
|
+
sourceRoot: "src"
|
|
2458
|
+
}, null, 2)
|
|
2459
|
+
},
|
|
2460
|
+
{
|
|
2461
|
+
path: "backend/src/app.module.ts",
|
|
2462
|
+
content: `import { Module } from "@nestjs/common";
|
|
2463
|
+
import { AppController } from "./app.controller";
|
|
2464
|
+
|
|
2465
|
+
@Module({
|
|
2466
|
+
controllers: [AppController],
|
|
2467
|
+
})
|
|
2468
|
+
export class AppModule {}
|
|
2469
|
+
`
|
|
2470
|
+
},
|
|
2471
|
+
{
|
|
2472
|
+
path: "backend/src/app.controller.ts",
|
|
2473
|
+
content: `import { Controller, Get } from "@nestjs/common";
|
|
2474
|
+
import { helloController } from "./controllers/hello.controller";
|
|
2475
|
+
|
|
2476
|
+
@Controller("api")
|
|
2477
|
+
export class AppController {
|
|
2478
|
+
@Get("hello")
|
|
2479
|
+
hello() {
|
|
2480
|
+
return helloController();
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
`
|
|
2484
|
+
},
|
|
2485
|
+
{
|
|
2486
|
+
path: "backend/src/main.ts",
|
|
2487
|
+
content: `import "reflect-metadata";
|
|
2488
|
+
import { NestFactory } from "@nestjs/core";
|
|
2489
|
+
import { AppModule } from "./app.module";
|
|
2490
|
+
import { appConfig } from "./config/app.config";
|
|
2491
|
+
|
|
2492
|
+
async function bootstrap(): Promise<void> {
|
|
2493
|
+
const app = await NestFactory.create(AppModule);
|
|
2494
|
+
app.enableCors({ origin: appConfig.corsOrigin });
|
|
2495
|
+
await app.listen(appConfig.port);
|
|
2496
|
+
console.log(\`[${projectConfig.projectName}] Backend running at http://localhost:\${appConfig.port}\`);
|
|
2497
|
+
}
|
|
2498
|
+
|
|
2499
|
+
bootstrap().catch(console.error);
|
|
2500
|
+
`
|
|
2501
|
+
},
|
|
2502
|
+
{
|
|
2503
|
+
path: "backend/src/modules/.gitkeep",
|
|
2504
|
+
content: ""
|
|
2505
|
+
},
|
|
2506
|
+
{
|
|
2507
|
+
path: "backend/src/common/.gitkeep",
|
|
2508
|
+
content: ""
|
|
2509
|
+
}
|
|
2510
|
+
];
|
|
2511
|
+
}
|
|
2512
|
+
function getAdonisFiles(projectConfig) {
|
|
2513
|
+
return [
|
|
2514
|
+
{
|
|
2515
|
+
path: "backend/package.json",
|
|
2516
|
+
content: getBackendPackageJson(projectConfig, {
|
|
2517
|
+
dependencies: {
|
|
2518
|
+
"@adonisjs/core": "^6.9.0",
|
|
2519
|
+
...getDatabaseDependencies(projectConfig.database)
|
|
2520
|
+
},
|
|
2521
|
+
devDependencies: {
|
|
2522
|
+
"@adonisjs/assembler": "^7.7.0",
|
|
2523
|
+
"@types/node": "^26.0.1"
|
|
2524
|
+
}
|
|
2525
|
+
})
|
|
2526
|
+
},
|
|
2527
|
+
{
|
|
2528
|
+
path: "backend/app/controllers/.gitkeep",
|
|
2529
|
+
content: ""
|
|
2530
|
+
},
|
|
2531
|
+
{
|
|
2532
|
+
path: "backend/app/middleware/.gitkeep",
|
|
2533
|
+
content: ""
|
|
2534
|
+
},
|
|
2535
|
+
{
|
|
2536
|
+
path: "backend/app/services/.gitkeep",
|
|
2537
|
+
content: ""
|
|
2538
|
+
},
|
|
2539
|
+
{
|
|
2540
|
+
path: "backend/start/.gitkeep",
|
|
2541
|
+
content: ""
|
|
2542
|
+
},
|
|
2543
|
+
{
|
|
2544
|
+
path: "backend/config/.gitkeep",
|
|
2545
|
+
content: ""
|
|
2546
|
+
},
|
|
2547
|
+
{
|
|
2548
|
+
path: "backend/src/app.ts",
|
|
2549
|
+
content: `import { appConfig } from "./config/app.config";
|
|
2550
|
+
import { helloController } from "./controllers/hello.controller";
|
|
2551
|
+
|
|
2552
|
+
const corsHeaders = {
|
|
2553
|
+
"Access-Control-Allow-Origin": appConfig.corsOrigin,
|
|
2554
|
+
"Access-Control-Allow-Methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS",
|
|
2555
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
2556
|
+
};
|
|
2557
|
+
|
|
2558
|
+
const server = Bun.serve({
|
|
2559
|
+
port: appConfig.port,
|
|
2560
|
+
fetch(request: Request): Response {
|
|
2561
|
+
const requestUrl = new URL(request.url);
|
|
2562
|
+
|
|
2563
|
+
if (request.method === "OPTIONS") {
|
|
2564
|
+
return new Response(null, { status: 204, headers: corsHeaders });
|
|
2565
|
+
}
|
|
2566
|
+
|
|
2567
|
+
if (requestUrl.pathname === "/api/hello" && request.method === "GET") {
|
|
2568
|
+
return Response.json(helloController(), { headers: corsHeaders });
|
|
2569
|
+
}
|
|
2570
|
+
|
|
2571
|
+
return Response.json(
|
|
2572
|
+
{ success: false, error: "Not found" },
|
|
2573
|
+
{ status: 404, headers: corsHeaders }
|
|
2574
|
+
);
|
|
2575
|
+
},
|
|
2576
|
+
});
|
|
2577
|
+
|
|
2578
|
+
console.log(\`[${projectConfig.projectName}] Backend running at http://localhost:\${server.port}\`);
|
|
2579
|
+
`
|
|
2580
|
+
}
|
|
2581
|
+
];
|
|
2582
|
+
}
|
|
2583
|
+
function getBackendPackageJson(projectConfig, packageDeps) {
|
|
2584
|
+
return JSON.stringify({
|
|
2585
|
+
name: `${projectConfig.projectName}-backend`,
|
|
2586
|
+
version: "0.1.0",
|
|
2587
|
+
private: true,
|
|
2588
|
+
type: "module",
|
|
2589
|
+
scripts: {
|
|
2590
|
+
dev: "bun run --watch src/app.ts",
|
|
2591
|
+
build: "bun build src/app.ts --outfile dist/app.js --target bun",
|
|
2592
|
+
start: "bun run dist/app.js",
|
|
2593
|
+
typecheck: "tsc --noEmit"
|
|
2594
|
+
},
|
|
2595
|
+
dependencies: {
|
|
2596
|
+
...packageDeps.dependencies
|
|
2597
|
+
},
|
|
2598
|
+
devDependencies: {
|
|
2599
|
+
"@types/bun": "^1.3.14",
|
|
2600
|
+
typescript: "^6.0.3",
|
|
2601
|
+
...packageDeps.devDependencies
|
|
2602
|
+
}
|
|
2603
|
+
}, null, 2);
|
|
2604
|
+
}
|
|
2605
|
+
function getSharedFiles(projectConfig) {
|
|
2606
|
+
return [
|
|
2607
|
+
{
|
|
2608
|
+
path: "shared/types/index.ts",
|
|
2609
|
+
content: `export interface ApiResponse<TData = unknown> {
|
|
2610
|
+
readonly success: boolean;
|
|
2611
|
+
readonly data?: TData;
|
|
2612
|
+
readonly error?: string;
|
|
2613
|
+
readonly message?: string;
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
export interface User {
|
|
2617
|
+
readonly id: string;
|
|
2618
|
+
readonly name: string;
|
|
2619
|
+
readonly email: string;
|
|
2620
|
+
readonly createdAt: string;
|
|
2621
|
+
readonly updatedAt: string;
|
|
2622
|
+
}
|
|
2623
|
+
`
|
|
2624
|
+
},
|
|
2625
|
+
{
|
|
2626
|
+
path: "shared/constants/index.ts",
|
|
2627
|
+
content: `export const APP_NAME = "${projectConfig.projectName}";
|
|
2628
|
+
export const API_VERSION = "v1";
|
|
2629
|
+
export const DEFAULT_PAGE_SIZE = 20;
|
|
2630
|
+
`
|
|
2631
|
+
}
|
|
2632
|
+
];
|
|
2633
|
+
}
|
|
2634
|
+
function getCssDependencies(cssFramework) {
|
|
2635
|
+
switch (cssFramework) {
|
|
2636
|
+
case "tailwind":
|
|
2637
|
+
return { tailwindcss: "^4.3.1" };
|
|
2638
|
+
case "bootstrap":
|
|
2639
|
+
return { bootstrap: "^5.3.3" };
|
|
2640
|
+
case "sass":
|
|
2641
|
+
case "none":
|
|
2642
|
+
return {};
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
function getCssDevDependencies(cssFramework) {
|
|
2646
|
+
switch (cssFramework) {
|
|
2647
|
+
case "sass":
|
|
2648
|
+
return { sass: "^1.77.0" };
|
|
2649
|
+
default:
|
|
2650
|
+
return {};
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
function getFrontendOnlyCssGlobals(cssFramework) {
|
|
2654
|
+
const cssImport = cssFramework === "tailwind" ? `@import "tailwindcss";
|
|
2655
|
+
|
|
2656
|
+
` : cssFramework === "bootstrap" ? `@import url("https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css");
|
|
2657
|
+
|
|
2658
|
+
` : cssFramework === "sass" ? `$color-primary: #dc2626;
|
|
2659
|
+
$color-background: #050505;
|
|
2660
|
+
$color-foreground: #f8fafc;
|
|
2661
|
+
|
|
2662
|
+
` : "";
|
|
2663
|
+
return `${cssImport}:root {
|
|
2664
|
+
--color-primary: #dc2626;
|
|
2665
|
+
--color-background: #050505;
|
|
2666
|
+
--color-foreground: #f8fafc;
|
|
2667
|
+
--color-surface: #0e111a;
|
|
2668
|
+
--color-border: rgba(255, 255, 255, 0.1);
|
|
2669
|
+
--color-muted: #94a3b8;
|
|
2670
|
+
color-scheme: dark;
|
|
2671
|
+
}
|
|
2672
|
+
|
|
2673
|
+
html {
|
|
2674
|
+
scroll-behavior: smooth;
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
body {
|
|
2678
|
+
margin: 0;
|
|
2679
|
+
min-width: 320px;
|
|
2680
|
+
min-height: 100vh;
|
|
2681
|
+
color: var(--color-foreground);
|
|
2682
|
+
background: var(--color-background);
|
|
2683
|
+
font-family:
|
|
2684
|
+
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
|
2685
|
+
Roboto, sans-serif;
|
|
2686
|
+
-webkit-font-smoothing: antialiased;
|
|
2687
|
+
}
|
|
2688
|
+
|
|
2689
|
+
*,
|
|
2690
|
+
*::before,
|
|
2691
|
+
*::after {
|
|
2692
|
+
box-sizing: border-box;
|
|
2693
|
+
}
|
|
2694
|
+
|
|
2695
|
+
@keyframes shrimpLegs {
|
|
2696
|
+
0% {
|
|
2697
|
+
transform: translateY(0);
|
|
2698
|
+
}
|
|
2699
|
+
|
|
2700
|
+
50% {
|
|
2701
|
+
transform: translateY(2px);
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
`;
|
|
2705
|
+
}
|
|
2706
|
+
function getCssGlobals(cssFramework) {
|
|
2707
|
+
const sharedBase = `
|
|
2708
|
+
:root {
|
|
2709
|
+
--color-primary: #dc2626;
|
|
2710
|
+
--color-background: #050505;
|
|
2711
|
+
--color-foreground: #f8fafc;
|
|
2712
|
+
}
|
|
2713
|
+
|
|
2714
|
+
* {
|
|
2715
|
+
box-sizing: border-box;
|
|
2716
|
+
}
|
|
2717
|
+
|
|
2718
|
+
body {
|
|
2719
|
+
margin: 0;
|
|
2720
|
+
color: var(--color-foreground);
|
|
2721
|
+
background: var(--color-background);
|
|
2722
|
+
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
2723
|
+
}
|
|
2724
|
+
|
|
2725
|
+
.page-shell {
|
|
2726
|
+
width: min(100% - 2rem, 960px);
|
|
2727
|
+
margin: 0 auto;
|
|
2728
|
+
padding: 4rem 0;
|
|
2729
|
+
}
|
|
2730
|
+
|
|
2731
|
+
.hero-card,
|
|
2732
|
+
.feature-card {
|
|
2733
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
2734
|
+
background: #0e111a;
|
|
2735
|
+
border-radius: 24px;
|
|
2736
|
+
padding: 2rem;
|
|
2737
|
+
margin-bottom: 1.5rem;
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2740
|
+
.eyebrow {
|
|
2741
|
+
color: #dc2626;
|
|
2742
|
+
font-weight: 700;
|
|
2743
|
+
letter-spacing: 0.12em;
|
|
2744
|
+
font-size: 0.75rem;
|
|
2745
|
+
text-transform: uppercase;
|
|
2746
|
+
}
|
|
2747
|
+
|
|
2748
|
+
.button-row {
|
|
2749
|
+
display: flex;
|
|
2750
|
+
gap: 0.75rem;
|
|
2751
|
+
flex-wrap: wrap;
|
|
2752
|
+
margin-top: 1rem;
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2755
|
+
a {
|
|
2756
|
+
color: #dc2626;
|
|
2757
|
+
}
|
|
2758
|
+
|
|
2759
|
+
button {
|
|
2760
|
+
cursor: pointer;
|
|
2761
|
+
}
|
|
2762
|
+
`;
|
|
2763
|
+
switch (cssFramework) {
|
|
2764
|
+
case "tailwind":
|
|
2765
|
+
return `@import "tailwindcss";
|
|
2766
|
+
${sharedBase}`;
|
|
2767
|
+
case "bootstrap":
|
|
2768
|
+
return `@import url("https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css");
|
|
2769
|
+
${sharedBase}`;
|
|
2770
|
+
case "sass":
|
|
2771
|
+
return `$color-primary: #dc2626;
|
|
2772
|
+
$color-background: #050505;
|
|
2773
|
+
$color-foreground: #f8fafc;
|
|
2774
|
+
${sharedBase}`;
|
|
2775
|
+
case "none":
|
|
2776
|
+
return sharedBase;
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
function generateFrontendOnlyRaktaEnv() {
|
|
2780
|
+
return `declare module "*.css";
|
|
2781
|
+
declare module "*.scss";
|
|
2782
|
+
declare module "*.sass";
|
|
2783
|
+
|
|
2784
|
+
type RaktaClickAttributes = Omit<
|
|
2785
|
+
import("react").AnchorHTMLAttributes<HTMLAnchorElement>,
|
|
2786
|
+
"href"
|
|
2787
|
+
> & {
|
|
2788
|
+
readonly to: string;
|
|
2789
|
+
};
|
|
2790
|
+
|
|
2791
|
+
declare global {
|
|
2792
|
+
namespace JSX {
|
|
2793
|
+
interface IntrinsicElements {
|
|
2794
|
+
click: RaktaClickAttributes;
|
|
2795
|
+
}
|
|
2796
|
+
}
|
|
2797
|
+
|
|
2798
|
+
const useCallback: typeof import("react").useCallback;
|
|
2799
|
+
const useEffect: typeof import("react").useEffect;
|
|
2800
|
+
const useRef: typeof import("react").useRef;
|
|
2801
|
+
const useState: typeof import("react").useState;
|
|
2802
|
+
|
|
2803
|
+
const ShrimpRunGame: typeof import("./app/components/shrimpRunGame").default;
|
|
2804
|
+
const RaktaShrimpMascot: typeof import("./app/components/raktaShrimpMascot").default;
|
|
2805
|
+
}
|
|
2806
|
+
|
|
2807
|
+
declare module "react/jsx-runtime" {
|
|
2808
|
+
namespace JSX {
|
|
2809
|
+
interface IntrinsicElements {
|
|
2810
|
+
click: RaktaClickAttributes;
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2814
|
+
|
|
2815
|
+
declare module "react/jsx-dev-runtime" {
|
|
2816
|
+
namespace JSX {
|
|
2817
|
+
interface IntrinsicElements {
|
|
2818
|
+
click: RaktaClickAttributes;
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
2822
|
+
|
|
2823
|
+
export {};
|
|
2824
|
+
`;
|
|
2825
|
+
}
|
|
2826
|
+
function generateFrontendOnlyLayout() {
|
|
2827
|
+
return `interface RootLayoutProps {
|
|
2828
|
+
readonly children: import("react").ReactNode;
|
|
2829
|
+
}
|
|
2830
|
+
|
|
2831
|
+
export default function RootLayout({ children }: RootLayoutProps) {
|
|
2832
|
+
return (
|
|
2833
|
+
<html lang="en">
|
|
2834
|
+
<head>
|
|
2835
|
+
<meta charSet="UTF-8" />
|
|
2836
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
2837
|
+
<title>Rakta.js App</title>
|
|
2838
|
+
</head>
|
|
2839
|
+
<body className="min-h-screen bg-[#050505] text-slate-50 antialiased">
|
|
2840
|
+
{children}
|
|
2841
|
+
</body>
|
|
2842
|
+
</html>
|
|
2843
|
+
);
|
|
2844
|
+
}
|
|
2845
|
+
`;
|
|
2846
|
+
}
|
|
2847
|
+
function generateFrontendOnlyPage(projectName) {
|
|
2848
|
+
return `export default function HomePage() {
|
|
2849
|
+
return (
|
|
2850
|
+
<main className="mx-auto flex min-h-screen w-full max-w-4xl flex-col gap-6 px-4 py-16 sm:px-6 lg:px-8">
|
|
2851
|
+
<section className="rounded-3xl border border-white/10 bg-[#0e111a] p-8 shadow-2xl shadow-red-950/20">
|
|
2852
|
+
<p className="mb-2 text-xs font-bold uppercase tracking-[0.3em] text-red-600">
|
|
2853
|
+
THE RED ROUTER FRAMEWORK
|
|
2854
|
+
</p>
|
|
2855
|
+
<h1 className="mb-3 text-4xl font-extrabold leading-tight text-white md:text-6xl">
|
|
2856
|
+
Welcome to ${projectName}
|
|
2857
|
+
</h1>
|
|
2858
|
+
<p className="max-w-2xl text-base leading-7 text-slate-400">
|
|
2859
|
+
Built with Rakta.js \u2014 Small in size. Fierce in speed. Alive in every route.
|
|
2860
|
+
</p>
|
|
2861
|
+
|
|
2862
|
+
<div className="mt-6 flex flex-wrap gap-3">
|
|
2863
|
+
<click
|
|
2864
|
+
to="/offline"
|
|
2865
|
+
className="rounded-xl border border-red-500/40 px-4 py-2 text-sm font-semibold text-red-400 transition hover:border-red-400 hover:text-red-300"
|
|
2866
|
+
>
|
|
2867
|
+
Open offline page
|
|
2868
|
+
</click>
|
|
2869
|
+
<click
|
|
2870
|
+
to="/dashboard"
|
|
2871
|
+
className="rounded-xl bg-red-600 px-4 py-2 text-sm font-semibold text-white transition hover:bg-red-700"
|
|
2872
|
+
>
|
|
2873
|
+
View dashboard
|
|
2874
|
+
</click>
|
|
2875
|
+
</div>
|
|
2876
|
+
</section>
|
|
2877
|
+
|
|
2878
|
+
<section className="rounded-3xl border border-white/10 bg-[#0e111a] p-8 shadow-2xl shadow-red-950/20">
|
|
2879
|
+
<p className="mb-2 text-xs font-bold uppercase tracking-[0.3em] text-red-600">
|
|
2880
|
+
SHRIMPRUN
|
|
2881
|
+
</p>
|
|
2882
|
+
<h2 className="mb-1 text-2xl font-bold text-white">Play ShrimpRun</h2>
|
|
2883
|
+
<p className="mb-6 text-sm leading-6 text-slate-400">
|
|
2884
|
+
Press Space or click the game area to jump. Avoid the red obstacles!
|
|
2885
|
+
</p>
|
|
2886
|
+
|
|
2887
|
+
<ShrimpRunGame />
|
|
2888
|
+
</section>
|
|
2889
|
+
</main>
|
|
2890
|
+
);
|
|
2891
|
+
}
|
|
2892
|
+
`;
|
|
2893
|
+
}
|
|
2894
|
+
function generateFrontendOnlyLoading() {
|
|
2895
|
+
return `export default function Loading() {
|
|
2896
|
+
return (
|
|
2897
|
+
<main className="flex min-h-screen items-center justify-center bg-[#050505] px-4">
|
|
2898
|
+
<p className="text-sm font-medium text-slate-400">Loading...</p>
|
|
2899
|
+
</main>
|
|
2900
|
+
);
|
|
2901
|
+
}
|
|
2902
|
+
`;
|
|
2903
|
+
}
|
|
2904
|
+
function generateFrontendOnlyError() {
|
|
2905
|
+
return `interface ErrorPageProps {
|
|
2906
|
+
readonly error: Error;
|
|
2907
|
+
readonly reset: () => void;
|
|
2908
|
+
}
|
|
2909
|
+
|
|
2910
|
+
export default function ErrorPage({ error, reset }: ErrorPageProps) {
|
|
2911
|
+
return (
|
|
2912
|
+
<main className="mx-auto flex min-h-screen w-full max-w-3xl items-center px-4 py-16">
|
|
2913
|
+
<section className="w-full rounded-3xl border border-white/10 bg-[#0e111a] p-8 shadow-2xl shadow-red-950/20">
|
|
2914
|
+
<p className="mb-2 text-xs font-bold uppercase tracking-[0.3em] text-red-600">
|
|
2915
|
+
ERROR
|
|
2916
|
+
</p>
|
|
2917
|
+
<h1 className="mb-3 text-3xl font-extrabold text-white">
|
|
2918
|
+
Something went wrong
|
|
2919
|
+
</h1>
|
|
2920
|
+
<p className="mb-6 wrap-break-word text-sm leading-6 text-slate-400">
|
|
2921
|
+
{error.message}
|
|
2922
|
+
</p>
|
|
2923
|
+
<button
|
|
2924
|
+
type="button"
|
|
2925
|
+
onClick={reset}
|
|
2926
|
+
className="rounded-lg bg-red-600 px-6 py-2 font-semibold text-white transition hover:bg-red-700 active:bg-red-800 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-500"
|
|
2927
|
+
>
|
|
2928
|
+
Try again
|
|
2929
|
+
</button>
|
|
2930
|
+
</section>
|
|
2931
|
+
</main>
|
|
2932
|
+
);
|
|
2933
|
+
}
|
|
2934
|
+
`;
|
|
2935
|
+
}
|
|
2936
|
+
function generateFrontendOnlyNotFound() {
|
|
2937
|
+
return `export default function NotFound() {
|
|
2938
|
+
return (
|
|
2939
|
+
<main className="mx-auto flex min-h-screen w-full max-w-3xl items-center px-4 py-16">
|
|
2940
|
+
<section className="w-full rounded-3xl border border-white/10 bg-[#0e111a] p-8 shadow-2xl shadow-red-950/20">
|
|
2941
|
+
<p className="mb-2 text-xs font-bold uppercase tracking-[0.3em] text-red-600">
|
|
2942
|
+
404
|
|
2943
|
+
</p>
|
|
2944
|
+
<h1 className="mb-3 text-3xl font-extrabold text-white">
|
|
2945
|
+
Page not found
|
|
2946
|
+
</h1>
|
|
2947
|
+
<p className="mb-6 text-sm leading-6 text-slate-400">
|
|
2948
|
+
The page you are looking for does not exist.
|
|
2949
|
+
</p>
|
|
2950
|
+
<click
|
|
2951
|
+
to="/"
|
|
2952
|
+
className="font-semibold text-red-500 underline-offset-4 transition hover:text-red-400 hover:underline"
|
|
2953
|
+
>
|
|
2954
|
+
Return home
|
|
2955
|
+
</click>
|
|
2956
|
+
</section>
|
|
2957
|
+
</main>
|
|
2958
|
+
);
|
|
2959
|
+
}
|
|
2960
|
+
`;
|
|
2961
|
+
}
|
|
2962
|
+
function generateShrimpMascotComponent() {
|
|
2963
|
+
return `interface RaktaShrimpMascotProps {
|
|
2964
|
+
readonly isJumping: boolean;
|
|
2965
|
+
readonly isDead: boolean;
|
|
2966
|
+
readonly style?: import("react").CSSProperties;
|
|
2967
|
+
}
|
|
2968
|
+
|
|
2969
|
+
/**
|
|
2970
|
+
* RaktaShrimpMascot \u2014 The animated shrimp hero of ShrimpRun.
|
|
2971
|
+
* Drawn entirely with inline SVG. No external assets required.
|
|
2972
|
+
*/
|
|
2973
|
+
export default function RaktaShrimpMascot({
|
|
2974
|
+
isJumping,
|
|
2975
|
+
isDead,
|
|
2976
|
+
style,
|
|
2977
|
+
}: RaktaShrimpMascotProps) {
|
|
2978
|
+
const bodyColor = isDead ? "#6b7280" : "#dc2626";
|
|
2979
|
+
const eyeColor = isDead ? "#374151" : "#fff";
|
|
2980
|
+
const legAnimation =
|
|
2981
|
+
isJumping || isDead ? "none" : "shrimpLegs 0.3s steps(2) infinite";
|
|
2982
|
+
|
|
2983
|
+
return (
|
|
2984
|
+
<svg
|
|
2985
|
+
viewBox="0 0 48 48"
|
|
2986
|
+
width="48"
|
|
2987
|
+
height="48"
|
|
2988
|
+
style={{ display: "block", ...style }}
|
|
2989
|
+
aria-label={
|
|
2990
|
+
isDead ? "dead shrimp" : isJumping ? "shrimp jumping" : "running shrimp"
|
|
2991
|
+
}
|
|
2992
|
+
role="img"
|
|
2993
|
+
>
|
|
2994
|
+
<style>{\`
|
|
2995
|
+
@keyframes shrimpLegs {
|
|
2996
|
+
0% { transform: translateY(0); }
|
|
2997
|
+
50% { transform: translateY(2px); }
|
|
2998
|
+
}
|
|
2999
|
+
\`}</style>
|
|
3000
|
+
|
|
3001
|
+
{/* Body */}
|
|
3002
|
+
<path
|
|
3003
|
+
d="M8 30 Q10 14 24 12 Q38 10 40 22 Q42 32 32 36 Q20 40 8 30Z"
|
|
3004
|
+
fill={bodyColor}
|
|
3005
|
+
/>
|
|
3006
|
+
|
|
3007
|
+
{/* Shell segments */}
|
|
3008
|
+
<path
|
|
3009
|
+
d="M12 28 Q16 20 24 18 Q30 17 34 22"
|
|
3010
|
+
stroke="#b91c1c"
|
|
3011
|
+
strokeWidth="1.5"
|
|
3012
|
+
fill="none"
|
|
3013
|
+
opacity={isDead ? 0.3 : 0.6}
|
|
3014
|
+
/>
|
|
3015
|
+
<path
|
|
3016
|
+
d="M14 32 Q19 24 28 22 Q33 21 36 26"
|
|
3017
|
+
stroke="#b91c1c"
|
|
3018
|
+
strokeWidth="1.5"
|
|
3019
|
+
fill="none"
|
|
3020
|
+
opacity={isDead ? 0.3 : 0.6}
|
|
3021
|
+
/>
|
|
3022
|
+
|
|
3023
|
+
{/* Eye */}
|
|
3024
|
+
<circle cx="34" cy="18" r="4" fill="#1e293b" />
|
|
3025
|
+
<circle cx="35" cy="17" r="2" fill={eyeColor} />
|
|
3026
|
+
|
|
3027
|
+
{isDead && (
|
|
3028
|
+
<>
|
|
3029
|
+
<line
|
|
3030
|
+
x1="32"
|
|
3031
|
+
y1="16"
|
|
3032
|
+
x2="36"
|
|
3033
|
+
y2="20"
|
|
3034
|
+
stroke="#374151"
|
|
3035
|
+
strokeWidth="1.5"
|
|
3036
|
+
/>
|
|
3037
|
+
<line
|
|
3038
|
+
x1="36"
|
|
3039
|
+
y1="16"
|
|
3040
|
+
x2="32"
|
|
3041
|
+
y2="20"
|
|
3042
|
+
stroke="#374151"
|
|
3043
|
+
strokeWidth="1.5"
|
|
3044
|
+
/>
|
|
3045
|
+
</>
|
|
3046
|
+
)}
|
|
3047
|
+
|
|
3048
|
+
{/* Antennae */}
|
|
3049
|
+
<line
|
|
3050
|
+
x1="34"
|
|
3051
|
+
y1="14"
|
|
3052
|
+
x2="40"
|
|
3053
|
+
y2="6"
|
|
3054
|
+
stroke={bodyColor}
|
|
3055
|
+
strokeWidth="1.5"
|
|
3056
|
+
strokeLinecap="round"
|
|
3057
|
+
/>
|
|
3058
|
+
<line
|
|
3059
|
+
x1="32"
|
|
3060
|
+
y1="13"
|
|
3061
|
+
x2="36"
|
|
3062
|
+
y2="4"
|
|
3063
|
+
stroke={bodyColor}
|
|
3064
|
+
strokeWidth="1.5"
|
|
3065
|
+
strokeLinecap="round"
|
|
3066
|
+
/>
|
|
3067
|
+
|
|
3068
|
+
{/* Tail */}
|
|
3069
|
+
<path
|
|
3070
|
+
d="M10 30 Q4 26 6 20"
|
|
3071
|
+
stroke={bodyColor}
|
|
3072
|
+
strokeWidth="2"
|
|
3073
|
+
fill="none"
|
|
3074
|
+
strokeLinecap="round"
|
|
3075
|
+
/>
|
|
3076
|
+
<path
|
|
3077
|
+
d="M10 30 Q2 30 4 36"
|
|
3078
|
+
stroke={bodyColor}
|
|
3079
|
+
strokeWidth="2"
|
|
3080
|
+
fill="none"
|
|
3081
|
+
strokeLinecap="round"
|
|
3082
|
+
/>
|
|
3083
|
+
<path
|
|
3084
|
+
d="M10 30 Q6 34 8 40"
|
|
3085
|
+
stroke={bodyColor}
|
|
3086
|
+
strokeWidth="2"
|
|
3087
|
+
fill="none"
|
|
3088
|
+
strokeLinecap="round"
|
|
3089
|
+
/>
|
|
3090
|
+
|
|
3091
|
+
{/* Legs */}
|
|
3092
|
+
<g
|
|
3093
|
+
style={{
|
|
3094
|
+
animation: legAnimation,
|
|
3095
|
+
transformOrigin: "24px 36px",
|
|
3096
|
+
}}
|
|
3097
|
+
>
|
|
3098
|
+
<line
|
|
3099
|
+
x1="18"
|
|
3100
|
+
y1="36"
|
|
3101
|
+
x2="14"
|
|
3102
|
+
y2="44"
|
|
3103
|
+
stroke={bodyColor}
|
|
3104
|
+
strokeWidth="1.5"
|
|
3105
|
+
strokeLinecap="round"
|
|
3106
|
+
/>
|
|
3107
|
+
<line
|
|
3108
|
+
x1="22"
|
|
3109
|
+
y1="38"
|
|
3110
|
+
x2="18"
|
|
3111
|
+
y2="46"
|
|
3112
|
+
stroke={bodyColor}
|
|
3113
|
+
strokeWidth="1.5"
|
|
3114
|
+
strokeLinecap="round"
|
|
3115
|
+
/>
|
|
3116
|
+
<line
|
|
3117
|
+
x1="26"
|
|
3118
|
+
y1="38"
|
|
3119
|
+
x2="24"
|
|
3120
|
+
y2="46"
|
|
3121
|
+
stroke={bodyColor}
|
|
3122
|
+
strokeWidth="1.5"
|
|
3123
|
+
strokeLinecap="round"
|
|
3124
|
+
/>
|
|
3125
|
+
<line
|
|
3126
|
+
x1="30"
|
|
3127
|
+
y1="37"
|
|
3128
|
+
x2="28"
|
|
3129
|
+
y2="45"
|
|
3130
|
+
stroke={bodyColor}
|
|
3131
|
+
strokeWidth="1.5"
|
|
3132
|
+
strokeLinecap="round"
|
|
3133
|
+
/>
|
|
3134
|
+
</g>
|
|
3135
|
+
</svg>
|
|
3136
|
+
);
|
|
3137
|
+
}
|
|
3138
|
+
`;
|
|
3139
|
+
}
|
|
3140
|
+
function generateShrimpRunGameComponent() {
|
|
3141
|
+
return `// \u2500\u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3142
|
+
|
|
3143
|
+
type GameStatus = "idle" | "running" | "dead";
|
|
3144
|
+
|
|
3145
|
+
interface ObstacleState {
|
|
3146
|
+
readonly id: number;
|
|
3147
|
+
readonly xPosition: number;
|
|
3148
|
+
readonly width: number;
|
|
3149
|
+
readonly height: number;
|
|
3150
|
+
}
|
|
3151
|
+
|
|
3152
|
+
interface ShrimpState {
|
|
3153
|
+
readonly yPosition: number;
|
|
3154
|
+
readonly velocityY: number;
|
|
3155
|
+
readonly isJumping: boolean;
|
|
3156
|
+
}
|
|
3157
|
+
|
|
3158
|
+
// \u2500\u2500\u2500 Constants \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3159
|
+
|
|
3160
|
+
const CANVAS_WIDTH = 640;
|
|
3161
|
+
const CANVAS_HEIGHT = 160;
|
|
3162
|
+
const GROUND_STRIP_HEIGHT = 4;
|
|
3163
|
+
const SHRIMP_START_X = 60;
|
|
3164
|
+
const SHRIMP_WIDTH = 48;
|
|
3165
|
+
const SHRIMP_HEIGHT = 48;
|
|
3166
|
+
const GRAVITY = 1.4;
|
|
3167
|
+
const JUMP_VELOCITY = -18;
|
|
3168
|
+
const INITIAL_OBSTACLE_SPEED = 5;
|
|
3169
|
+
const SPEED_INCREMENT_PER_SCORE = 0.003;
|
|
3170
|
+
const OBSTACLE_SPAWN_INTERVAL_MS = 1600;
|
|
3171
|
+
const SCORE_TICK_MS = 80;
|
|
3172
|
+
const COLLISION_MARGIN = 8;
|
|
3173
|
+
const MAX_CONCURRENT_OBSTACLES = 3;
|
|
3174
|
+
const FRAME_SKIP_THRESHOLD_MS = 100;
|
|
3175
|
+
|
|
3176
|
+
// \u2500\u2500\u2500 Physics helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3177
|
+
|
|
3178
|
+
function getObstacleSpeed(currentScore: number): number {
|
|
3179
|
+
return INITIAL_OBSTACLE_SPEED + currentScore * SPEED_INCREMENT_PER_SCORE;
|
|
3180
|
+
}
|
|
3181
|
+
|
|
3182
|
+
function checkCollision(
|
|
3183
|
+
shrimpYPosition: number,
|
|
3184
|
+
obstacle: ObstacleState,
|
|
3185
|
+
): boolean {
|
|
3186
|
+
const shrimpLeft = SHRIMP_START_X + COLLISION_MARGIN;
|
|
3187
|
+
const shrimpRight = SHRIMP_START_X + SHRIMP_WIDTH - COLLISION_MARGIN;
|
|
3188
|
+
const shrimpTop =
|
|
3189
|
+
CANVAS_HEIGHT -
|
|
3190
|
+
GROUND_STRIP_HEIGHT -
|
|
3191
|
+
shrimpYPosition -
|
|
3192
|
+
SHRIMP_HEIGHT +
|
|
3193
|
+
COLLISION_MARGIN;
|
|
3194
|
+
const shrimpBottom = CANVAS_HEIGHT - GROUND_STRIP_HEIGHT - shrimpYPosition;
|
|
3195
|
+
|
|
3196
|
+
const obstacleLeft = obstacle.xPosition + COLLISION_MARGIN;
|
|
3197
|
+
const obstacleRight = obstacle.xPosition + obstacle.width - COLLISION_MARGIN;
|
|
3198
|
+
const obstacleTop = CANVAS_HEIGHT - GROUND_STRIP_HEIGHT - obstacle.height;
|
|
3199
|
+
const obstacleBottom = CANVAS_HEIGHT - GROUND_STRIP_HEIGHT;
|
|
3200
|
+
|
|
3201
|
+
return (
|
|
3202
|
+
shrimpLeft < obstacleRight &&
|
|
3203
|
+
shrimpRight > obstacleLeft &&
|
|
3204
|
+
shrimpTop < obstacleBottom &&
|
|
3205
|
+
shrimpBottom > obstacleTop
|
|
3206
|
+
);
|
|
3207
|
+
}
|
|
3208
|
+
|
|
3209
|
+
// \u2500\u2500\u2500 Component \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3210
|
+
|
|
3211
|
+
/**
|
|
3212
|
+
* ShrimpRun \u2014 Default Rakta.js interactive starter game.
|
|
3213
|
+
*
|
|
3214
|
+
* Like the Chrome offline Dino game, but the dinosaur is an animated shrimp.
|
|
3215
|
+
* Press Space or click the game canvas to jump. Avoid the red obstacles!
|
|
3216
|
+
*
|
|
3217
|
+
* Features:
|
|
3218
|
+
* - React state only \u2014 no external game library
|
|
3219
|
+
* - requestAnimationFrame game loop
|
|
3220
|
+
* - Physics: gravity + jump velocity
|
|
3221
|
+
* - Score that increases over time
|
|
3222
|
+
* - Speed ramps up as score grows
|
|
3223
|
+
* - Collision detection with margin
|
|
3224
|
+
* - High score tracked in component state
|
|
3225
|
+
* - Keyboard (Space) and click/tap support
|
|
3226
|
+
* - Accessible button game canvas
|
|
3227
|
+
* - SVG shrimp mascot \u2014 no external assets
|
|
3228
|
+
*/
|
|
3229
|
+
export default function ShrimpRunGame() {
|
|
3230
|
+
const [gameStatus, setGameStatus] = useState<GameStatus>("idle");
|
|
3231
|
+
const [score, setScore] = useState(0);
|
|
3232
|
+
const [highScore, setHighScore] = useState(0);
|
|
3233
|
+
const [obstacles, setObstacles] = useState<ObstacleState[]>([]);
|
|
3234
|
+
const [shrimp, setShrimp] = useState<ShrimpState>({
|
|
3235
|
+
yPosition: 0,
|
|
3236
|
+
velocityY: 0,
|
|
3237
|
+
isJumping: false,
|
|
3238
|
+
});
|
|
3239
|
+
|
|
3240
|
+
const gameStatusRef = useRef<GameStatus>("idle");
|
|
3241
|
+
const scoreRef = useRef(0);
|
|
3242
|
+
const shrimpRef = useRef<ShrimpState>({
|
|
3243
|
+
yPosition: 0,
|
|
3244
|
+
velocityY: 0,
|
|
3245
|
+
isJumping: false,
|
|
3246
|
+
});
|
|
3247
|
+
const obstaclesRef = useRef<ObstacleState[]>([]);
|
|
3248
|
+
const obstacleIdRef = useRef(0);
|
|
3249
|
+
const animationFrameRef = useRef<number>(0);
|
|
3250
|
+
const lastObstacleTimeRef = useRef(0);
|
|
3251
|
+
const lastScoreTickRef = useRef(0);
|
|
3252
|
+
|
|
3253
|
+
const jump = useCallback((): void => {
|
|
3254
|
+
if (gameStatusRef.current === "dead") {
|
|
3255
|
+
return;
|
|
3256
|
+
}
|
|
3257
|
+
|
|
3258
|
+
if (gameStatusRef.current === "idle") {
|
|
3259
|
+
gameStatusRef.current = "running";
|
|
3260
|
+
setGameStatus("running");
|
|
3261
|
+
}
|
|
3262
|
+
|
|
3263
|
+
if (!shrimpRef.current.isJumping) {
|
|
3264
|
+
const nextShrimp: ShrimpState = {
|
|
3265
|
+
...shrimpRef.current,
|
|
3266
|
+
velocityY: JUMP_VELOCITY,
|
|
3267
|
+
isJumping: true,
|
|
3268
|
+
};
|
|
3269
|
+
|
|
3270
|
+
shrimpRef.current = nextShrimp;
|
|
3271
|
+
setShrimp(nextShrimp);
|
|
3272
|
+
}
|
|
3273
|
+
}, []);
|
|
3274
|
+
|
|
3275
|
+
const resetGame = useCallback((): void => {
|
|
3276
|
+
const freshShrimp: ShrimpState = {
|
|
3277
|
+
yPosition: 0,
|
|
3278
|
+
velocityY: 0,
|
|
3279
|
+
isJumping: false,
|
|
3280
|
+
};
|
|
3281
|
+
|
|
3282
|
+
shrimpRef.current = freshShrimp;
|
|
3283
|
+
obstaclesRef.current = [];
|
|
3284
|
+
obstacleIdRef.current = 0;
|
|
3285
|
+
scoreRef.current = 0;
|
|
3286
|
+
gameStatusRef.current = "idle";
|
|
3287
|
+
lastObstacleTimeRef.current = 0;
|
|
3288
|
+
lastScoreTickRef.current = 0;
|
|
3289
|
+
|
|
3290
|
+
setShrimp(freshShrimp);
|
|
3291
|
+
setObstacles([]);
|
|
3292
|
+
setScore(0);
|
|
3293
|
+
setGameStatus("idle");
|
|
3294
|
+
}, []);
|
|
3295
|
+
|
|
3296
|
+
useEffect(() => {
|
|
3297
|
+
let previousTimestamp = 0;
|
|
3298
|
+
|
|
3299
|
+
function gameTick(timestamp: number): void {
|
|
3300
|
+
if (gameStatusRef.current !== "running") {
|
|
3301
|
+
animationFrameRef.current = requestAnimationFrame(gameTick);
|
|
3302
|
+
return;
|
|
3303
|
+
}
|
|
3304
|
+
|
|
3305
|
+
const deltaTime = timestamp - previousTimestamp;
|
|
3306
|
+
previousTimestamp = timestamp;
|
|
3307
|
+
|
|
3308
|
+
if (deltaTime > FRAME_SKIP_THRESHOLD_MS) {
|
|
3309
|
+
animationFrameRef.current = requestAnimationFrame(gameTick);
|
|
3310
|
+
return;
|
|
3311
|
+
}
|
|
3312
|
+
|
|
3313
|
+
const currentShrimp = shrimpRef.current;
|
|
3314
|
+
let nextVelocityY = currentShrimp.velocityY + GRAVITY;
|
|
3315
|
+
let nextYPosition = currentShrimp.yPosition - nextVelocityY;
|
|
3316
|
+
|
|
3317
|
+
if (nextYPosition <= 0) {
|
|
3318
|
+
nextYPosition = 0;
|
|
3319
|
+
nextVelocityY = 0;
|
|
3320
|
+
}
|
|
3321
|
+
|
|
3322
|
+
const nextShrimp: ShrimpState = {
|
|
3323
|
+
yPosition: nextYPosition,
|
|
3324
|
+
velocityY: nextVelocityY,
|
|
3325
|
+
isJumping: nextYPosition > 0,
|
|
3326
|
+
};
|
|
3327
|
+
|
|
3328
|
+
shrimpRef.current = nextShrimp;
|
|
3329
|
+
setShrimp(nextShrimp);
|
|
3330
|
+
|
|
3331
|
+
const obstacleSpeed = getObstacleSpeed(scoreRef.current);
|
|
3332
|
+
|
|
3333
|
+
const movedObstacles = obstaclesRef.current
|
|
3334
|
+
.map(
|
|
3335
|
+
(obstacle): ObstacleState => ({
|
|
3336
|
+
...obstacle,
|
|
3337
|
+
xPosition: obstacle.xPosition - obstacleSpeed,
|
|
3338
|
+
}),
|
|
3339
|
+
)
|
|
3340
|
+
.filter((obstacle) => obstacle.xPosition + obstacle.width > -10);
|
|
3341
|
+
|
|
3342
|
+
if (
|
|
3343
|
+
timestamp - lastObstacleTimeRef.current > OBSTACLE_SPAWN_INTERVAL_MS &&
|
|
3344
|
+
movedObstacles.length < MAX_CONCURRENT_OBSTACLES
|
|
3345
|
+
) {
|
|
3346
|
+
const obstacleHeight = 30 + Math.floor(Math.random() * 30);
|
|
3347
|
+
const obstacleWidth = 20 + Math.floor(Math.random() * 20);
|
|
3348
|
+
|
|
3349
|
+
movedObstacles.push({
|
|
3350
|
+
id: obstacleIdRef.current,
|
|
3351
|
+
xPosition: CANVAS_WIDTH + 20,
|
|
3352
|
+
width: obstacleWidth,
|
|
3353
|
+
height: obstacleHeight,
|
|
3354
|
+
});
|
|
3355
|
+
|
|
3356
|
+
obstacleIdRef.current += 1;
|
|
3357
|
+
lastObstacleTimeRef.current = timestamp;
|
|
3358
|
+
}
|
|
3359
|
+
|
|
3360
|
+
obstaclesRef.current = movedObstacles;
|
|
3361
|
+
setObstacles([...movedObstacles]);
|
|
3362
|
+
|
|
3363
|
+
for (const obstacle of movedObstacles) {
|
|
3364
|
+
if (checkCollision(nextShrimp.yPosition, obstacle)) {
|
|
3365
|
+
gameStatusRef.current = "dead";
|
|
3366
|
+
setGameStatus("dead");
|
|
3367
|
+
setHighScore((previousHighScore: number) =>
|
|
3368
|
+
Math.max(previousHighScore, scoreRef.current),
|
|
3369
|
+
);
|
|
3370
|
+
animationFrameRef.current = requestAnimationFrame(gameTick);
|
|
3371
|
+
return;
|
|
3372
|
+
}
|
|
3373
|
+
}
|
|
3374
|
+
|
|
3375
|
+
if (timestamp - lastScoreTickRef.current > SCORE_TICK_MS) {
|
|
3376
|
+
scoreRef.current += 1;
|
|
3377
|
+
setScore(scoreRef.current);
|
|
3378
|
+
lastScoreTickRef.current = timestamp;
|
|
3379
|
+
}
|
|
3380
|
+
|
|
3381
|
+
animationFrameRef.current = requestAnimationFrame(gameTick);
|
|
3382
|
+
}
|
|
3383
|
+
|
|
3384
|
+
animationFrameRef.current = requestAnimationFrame(gameTick);
|
|
3385
|
+
|
|
3386
|
+
return () => {
|
|
3387
|
+
cancelAnimationFrame(animationFrameRef.current);
|
|
3388
|
+
};
|
|
3389
|
+
}, []);
|
|
3390
|
+
|
|
3391
|
+
useEffect(() => {
|
|
3392
|
+
function handleKeyDown(keyboardEvent: KeyboardEvent): void {
|
|
3393
|
+
if (keyboardEvent.code === "Space") {
|
|
3394
|
+
keyboardEvent.preventDefault();
|
|
3395
|
+
jump();
|
|
3396
|
+
}
|
|
3397
|
+
}
|
|
3398
|
+
|
|
3399
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
3400
|
+
|
|
3401
|
+
return () => {
|
|
3402
|
+
window.removeEventListener("keydown", handleKeyDown);
|
|
3403
|
+
};
|
|
3404
|
+
}, [jump]);
|
|
3405
|
+
|
|
3406
|
+
const shrimpBottomOffset = GROUND_STRIP_HEIGHT + shrimp.yPosition;
|
|
3407
|
+
const isDead = gameStatus === "dead";
|
|
3408
|
+
const isIdle = gameStatus === "idle";
|
|
3409
|
+
const isRunning = gameStatus === "running";
|
|
3410
|
+
|
|
3411
|
+
return (
|
|
3412
|
+
<div className="flex flex-col items-start gap-4 py-4">
|
|
3413
|
+
<div className="flex flex-wrap items-center gap-8">
|
|
3414
|
+
<span className="font-mono text-xl font-bold tabular-nums text-red-600">
|
|
3415
|
+
Score: {score}
|
|
3416
|
+
</span>
|
|
3417
|
+
{highScore > 0 && (
|
|
3418
|
+
<span className="text-sm text-slate-400">Best: {highScore}</span>
|
|
3419
|
+
)}
|
|
3420
|
+
</div>
|
|
3421
|
+
|
|
3422
|
+
<button
|
|
3423
|
+
type="button"
|
|
3424
|
+
className="relative block max-w-full cursor-pointer select-none overflow-hidden rounded-2xl border-2 border-red-600/30 bg-[#0e111a] p-0 text-left focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-500"
|
|
3425
|
+
aria-label="ShrimpRun game area. Click or press Space to jump."
|
|
3426
|
+
onClick={jump}
|
|
3427
|
+
onKeyDown={(keyboardEvent: import("react").KeyboardEvent) => {
|
|
3428
|
+
if (keyboardEvent.code === "Space") {
|
|
3429
|
+
keyboardEvent.preventDefault();
|
|
3430
|
+
jump();
|
|
3431
|
+
}
|
|
3432
|
+
}}
|
|
3433
|
+
style={{
|
|
3434
|
+
width: CANVAS_WIDTH,
|
|
3435
|
+
height: CANVAS_HEIGHT,
|
|
3436
|
+
}}
|
|
3437
|
+
>
|
|
3438
|
+
<span
|
|
3439
|
+
className="absolute bottom-0 left-0 w-full rounded-sm bg-red-600"
|
|
3440
|
+
style={{
|
|
3441
|
+
height: GROUND_STRIP_HEIGHT,
|
|
3442
|
+
}}
|
|
3443
|
+
/>
|
|
3444
|
+
|
|
3445
|
+
<span
|
|
3446
|
+
className="absolute"
|
|
3447
|
+
style={{
|
|
3448
|
+
left: SHRIMP_START_X,
|
|
3449
|
+
bottom: shrimpBottomOffset,
|
|
3450
|
+
width: SHRIMP_WIDTH,
|
|
3451
|
+
height: SHRIMP_HEIGHT,
|
|
3452
|
+
}}
|
|
3453
|
+
>
|
|
3454
|
+
<RaktaShrimpMascot isJumping={shrimp.isJumping} isDead={isDead} />
|
|
3455
|
+
</span>
|
|
3456
|
+
|
|
3457
|
+
{obstacles.map((obstacle) => (
|
|
3458
|
+
<span
|
|
3459
|
+
key={obstacle.id}
|
|
3460
|
+
className="absolute rounded-t bg-red-600"
|
|
3461
|
+
style={{
|
|
3462
|
+
left: obstacle.xPosition,
|
|
3463
|
+
bottom: GROUND_STRIP_HEIGHT,
|
|
3464
|
+
width: obstacle.width,
|
|
3465
|
+
height: obstacle.height,
|
|
3466
|
+
}}
|
|
3467
|
+
/>
|
|
3468
|
+
))}
|
|
3469
|
+
|
|
3470
|
+
{isIdle && (
|
|
3471
|
+
<span className="pointer-events-none absolute inset-0 flex items-center justify-center text-sm text-slate-400">
|
|
3472
|
+
Press Space or click to start
|
|
3473
|
+
</span>
|
|
3474
|
+
)}
|
|
3475
|
+
|
|
3476
|
+
{isDead && (
|
|
3477
|
+
<span className="absolute inset-0 flex flex-col items-center justify-center gap-2 bg-black/75">
|
|
3478
|
+
<span className="text-lg font-bold tracking-widest text-red-600">
|
|
3479
|
+
GAME OVER
|
|
3480
|
+
</span>
|
|
3481
|
+
<span className="text-sm text-slate-400">Score: {score}</span>
|
|
3482
|
+
</span>
|
|
3483
|
+
)}
|
|
3484
|
+
|
|
3485
|
+
{isRunning && score > 0 && score % 50 === 0 && (
|
|
3486
|
+
<span className="absolute right-3 top-2 text-xs font-bold tracking-widest text-red-600 opacity-80">
|
|
3487
|
+
{score}!
|
|
3488
|
+
</span>
|
|
3489
|
+
)}
|
|
3490
|
+
</button>
|
|
3491
|
+
|
|
3492
|
+
<p className="min-h-5 text-sm text-slate-400">
|
|
3493
|
+
{isIdle && "\uD83E\uDD90 Click or press Space to make the shrimp jump!"}
|
|
3494
|
+
{isRunning && "\uD83E\uDD90 Don't hit the obstacles!"}
|
|
3495
|
+
{isDead && "The shrimp got cooked. Try again!"}
|
|
3496
|
+
</p>
|
|
3497
|
+
|
|
3498
|
+
{isDead && (
|
|
3499
|
+
<button
|
|
3500
|
+
type="button"
|
|
3501
|
+
className="w-fit rounded-lg bg-red-600 px-6 py-2 font-semibold text-white transition hover:bg-red-700 active:bg-red-800 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-500"
|
|
3502
|
+
onClick={resetGame}
|
|
3503
|
+
>
|
|
3504
|
+
Restart
|
|
3505
|
+
</button>
|
|
3506
|
+
)}
|
|
3507
|
+
</div>
|
|
3508
|
+
);
|
|
3509
|
+
}
|
|
3510
|
+
`;
|
|
3511
|
+
}
|
|
3512
|
+
function generateFullstackHomePage(projectName) {
|
|
3513
|
+
return `import React from "react";
|
|
3514
|
+
|
|
3515
|
+
export default function HomePage() {
|
|
3516
|
+
return (
|
|
3517
|
+
<main className="page-shell">
|
|
3518
|
+
<section className="hero-card">
|
|
3519
|
+
<p className="eyebrow">THE RED ROUTER FRAMEWORK</p>
|
|
3520
|
+
<h1>Welcome to ${projectName}</h1>
|
|
3521
|
+
<p>
|
|
3522
|
+
Built with Rakta.js \u2014 Small in size. Fierce in speed. Alive in every route.
|
|
3523
|
+
</p>
|
|
3524
|
+
<div className="button-row">
|
|
3525
|
+
<a href="/about">About</a>
|
|
3526
|
+
<a href="/blog">Blog</a>
|
|
3527
|
+
</div>
|
|
3528
|
+
</section>
|
|
3529
|
+
</main>
|
|
3530
|
+
);
|
|
3531
|
+
}
|
|
3532
|
+
`;
|
|
3533
|
+
}
|
|
3534
|
+
function getDatabaseDependencies(selectedDatabase) {
|
|
3535
|
+
switch (selectedDatabase) {
|
|
3536
|
+
case "postgresql":
|
|
3537
|
+
return { postgres: "^3.4.4" };
|
|
3538
|
+
case "mysql":
|
|
3539
|
+
case "mariadb":
|
|
3540
|
+
return { mysql2: "^3.9.8" };
|
|
3541
|
+
case "mongodb":
|
|
3542
|
+
return { mongodb: "^6.8.0" };
|
|
3543
|
+
case "firebase":
|
|
3544
|
+
return { "firebase-admin": "^12.7.0" };
|
|
3545
|
+
case "sqlite":
|
|
3546
|
+
return {};
|
|
3547
|
+
case "redis":
|
|
3548
|
+
return { ioredis: "^5.4.1" };
|
|
3549
|
+
case "planetscale":
|
|
3550
|
+
return { "@planetscale/database": "^1.18.0" };
|
|
3551
|
+
case "neon":
|
|
3552
|
+
return { "@neondatabase/serverless": "^0.9.4" };
|
|
3553
|
+
case "turso":
|
|
3554
|
+
return { "@libsql/client": "^0.6.2" };
|
|
3555
|
+
default:
|
|
3556
|
+
return {};
|
|
3557
|
+
}
|
|
3558
|
+
}
|
|
3559
|
+
function getDatabaseConfig(selectedDatabase) {
|
|
3560
|
+
switch (selectedDatabase) {
|
|
3561
|
+
case "sqlite":
|
|
3562
|
+
return `export const databaseConfig = {
|
|
3563
|
+
path: process.env["DATABASE_PATH"] ?? "./database.sqlite",
|
|
3564
|
+
provider: "sqlite" as const,
|
|
3565
|
+
} as const;
|
|
3566
|
+
`;
|
|
3567
|
+
case "firebase":
|
|
3568
|
+
return `export const databaseConfig = {
|
|
3569
|
+
projectId: process.env["FIREBASE_PROJECT_ID"] ?? "",
|
|
3570
|
+
clientEmail: process.env["FIREBASE_CLIENT_EMAIL"] ?? "",
|
|
3571
|
+
privateKey: process.env["FIREBASE_PRIVATE_KEY"]?.replace(/\\\\\\\\n/g, "\\\\n") ?? "",
|
|
3572
|
+
databaseUrl: process.env["FIREBASE_DATABASE_URL"] ?? "",
|
|
3573
|
+
provider: "firebase" as const,
|
|
3574
|
+
} as const;
|
|
3575
|
+
`;
|
|
3576
|
+
case "turso":
|
|
3577
|
+
return `export const databaseConfig = {
|
|
3578
|
+
url: process.env["TURSO_DATABASE_URL"] ?? "",
|
|
3579
|
+
authToken: process.env["TURSO_AUTH_TOKEN"] ?? "",
|
|
3580
|
+
provider: "turso" as const,
|
|
3581
|
+
} as const;
|
|
3582
|
+
`;
|
|
3583
|
+
default:
|
|
3584
|
+
return `import { env } from "../env";
|
|
3585
|
+
|
|
3586
|
+
export const databaseConfig = {
|
|
3587
|
+
url: env.databaseUrl,
|
|
3588
|
+
provider: "${selectedDatabase}" as const,
|
|
3589
|
+
} as const;
|
|
3590
|
+
`;
|
|
3591
|
+
}
|
|
3592
|
+
}
|
|
3593
|
+
function getDatabaseClient(selectedDatabase) {
|
|
3594
|
+
switch (selectedDatabase) {
|
|
3595
|
+
case "postgresql":
|
|
3596
|
+
return `import postgres from "postgres";
|
|
3597
|
+
import { databaseConfig } from "../config/database.config";
|
|
3598
|
+
|
|
3599
|
+
export const sql = databaseConfig.url ? postgres(databaseConfig.url) : null;
|
|
3600
|
+
`;
|
|
3601
|
+
case "mysql":
|
|
3602
|
+
case "mariadb":
|
|
3603
|
+
return `import mysql from "mysql2/promise";
|
|
3604
|
+
import { databaseConfig } from "../config/database.config";
|
|
3605
|
+
|
|
3606
|
+
export const pool = databaseConfig.url
|
|
3607
|
+
? mysql.createPool({ uri: databaseConfig.url, connectionLimit: 10 })
|
|
3608
|
+
: null;
|
|
3609
|
+
`;
|
|
3610
|
+
case "mongodb":
|
|
3611
|
+
return `import { MongoClient } from "mongodb";
|
|
3612
|
+
import { databaseConfig } from "../config/database.config";
|
|
3613
|
+
|
|
3614
|
+
export const mongoClient = databaseConfig.url
|
|
3615
|
+
? new MongoClient(databaseConfig.url)
|
|
3616
|
+
: null;
|
|
3617
|
+
`;
|
|
3618
|
+
case "firebase":
|
|
3619
|
+
return `import { cert, getApps, initializeApp } from "firebase-admin/app";
|
|
3620
|
+
import { getFirestore } from "firebase-admin/firestore";
|
|
3621
|
+
import { databaseConfig } from "../config/database.config";
|
|
3622
|
+
|
|
3623
|
+
const credentialReady =
|
|
3624
|
+
databaseConfig.projectId.length > 0 &&
|
|
3625
|
+
databaseConfig.clientEmail.length > 0 &&
|
|
3626
|
+
databaseConfig.privateKey.length > 0;
|
|
3627
|
+
|
|
3628
|
+
export const firebaseApp = getApps()[0] ?? initializeApp(
|
|
3629
|
+
credentialReady
|
|
3630
|
+
? {
|
|
3631
|
+
credential: cert({
|
|
3632
|
+
projectId: databaseConfig.projectId,
|
|
3633
|
+
clientEmail: databaseConfig.clientEmail,
|
|
3634
|
+
privateKey: databaseConfig.privateKey,
|
|
3635
|
+
}),
|
|
3636
|
+
databaseURL: databaseConfig.databaseUrl || undefined,
|
|
3637
|
+
}
|
|
3638
|
+
: undefined
|
|
3639
|
+
);
|
|
3640
|
+
|
|
3641
|
+
export const firestore = getFirestore(firebaseApp);
|
|
3642
|
+
`;
|
|
3643
|
+
case "sqlite":
|
|
3644
|
+
return `import { Database } from "bun:sqlite";
|
|
3645
|
+
import { databaseConfig } from "../config/database.config";
|
|
3646
|
+
|
|
3647
|
+
export const db = new Database(databaseConfig.path, { create: true });
|
|
3648
|
+
`;
|
|
3649
|
+
case "redis":
|
|
3650
|
+
return `import Redis from "ioredis";
|
|
3651
|
+
import { databaseConfig } from "../config/database.config";
|
|
3652
|
+
|
|
3653
|
+
export const redis = databaseConfig.url ? new Redis(databaseConfig.url) : null;
|
|
3654
|
+
`;
|
|
3655
|
+
case "planetscale":
|
|
3656
|
+
return `import { connect } from "@planetscale/database";
|
|
3657
|
+
import { databaseConfig } from "../config/database.config";
|
|
3658
|
+
|
|
3659
|
+
export const connection = databaseConfig.url
|
|
3660
|
+
? connect({ url: databaseConfig.url })
|
|
3661
|
+
: null;
|
|
3662
|
+
`;
|
|
3663
|
+
case "neon":
|
|
3664
|
+
return `import { neon } from "@neondatabase/serverless";
|
|
3665
|
+
import { databaseConfig } from "../config/database.config";
|
|
3666
|
+
|
|
3667
|
+
export const sql = databaseConfig.url ? neon(databaseConfig.url) : null;
|
|
3668
|
+
`;
|
|
3669
|
+
case "turso":
|
|
3670
|
+
return `import { createClient } from "@libsql/client";
|
|
3671
|
+
import { databaseConfig } from "../config/database.config";
|
|
3672
|
+
|
|
3673
|
+
export const db =
|
|
3674
|
+
databaseConfig.url && databaseConfig.authToken
|
|
3675
|
+
? createClient({ url: databaseConfig.url, authToken: databaseConfig.authToken })
|
|
3676
|
+
: null;
|
|
3677
|
+
`;
|
|
3678
|
+
default:
|
|
3679
|
+
return `export const databaseClient = null;
|
|
3680
|
+
`;
|
|
3681
|
+
}
|
|
3682
|
+
}
|
|
3683
|
+
function getDatabaseEnvExample(selectedDatabase) {
|
|
3684
|
+
const baseEnv = `NODE_ENV=development
|
|
3685
|
+
PORT=4000
|
|
3686
|
+
CORS_ORIGIN=http://localhost:3000
|
|
3687
|
+
`;
|
|
3688
|
+
switch (selectedDatabase) {
|
|
3689
|
+
case "postgresql":
|
|
3690
|
+
return `${baseEnv}DATABASE_URL=postgresql://user:password@localhost:5432/dbname
|
|
3691
|
+
`;
|
|
3692
|
+
case "mysql":
|
|
3693
|
+
return `${baseEnv}DATABASE_URL=mysql://user:password@localhost:3306/dbname
|
|
3694
|
+
`;
|
|
3695
|
+
case "mariadb":
|
|
3696
|
+
return `${baseEnv}DATABASE_URL=mysql://user:password@localhost:3306/dbname
|
|
3697
|
+
`;
|
|
3698
|
+
case "mongodb":
|
|
3699
|
+
return `${baseEnv}DATABASE_URL=mongodb://localhost:27017/dbname
|
|
3700
|
+
`;
|
|
3701
|
+
case "firebase":
|
|
3702
|
+
return `${baseEnv}FIREBASE_PROJECT_ID=your-project-id
|
|
3703
|
+
FIREBASE_CLIENT_EMAIL=firebase-adminsdk@your-project.iam.gserviceaccount.com
|
|
3704
|
+
FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\\nyour-key\\n-----END PRIVATE KEY-----\\n"
|
|
3705
|
+
FIREBASE_DATABASE_URL=https://your-project.firebaseio.com
|
|
3706
|
+
`;
|
|
3707
|
+
case "sqlite":
|
|
3708
|
+
return `${baseEnv}DATABASE_PATH=./database.sqlite
|
|
3709
|
+
`;
|
|
3710
|
+
case "redis":
|
|
3711
|
+
return `${baseEnv}DATABASE_URL=redis://localhost:6379
|
|
3712
|
+
`;
|
|
3713
|
+
case "planetscale":
|
|
3714
|
+
return `${baseEnv}DATABASE_URL=mysql://user:password@aws.connect.psdb.cloud/dbname
|
|
3715
|
+
`;
|
|
3716
|
+
case "neon":
|
|
3717
|
+
return `${baseEnv}DATABASE_URL=postgresql://user:password@ep-xxx.neon.tech/neondb?sslmode=require
|
|
3718
|
+
`;
|
|
3719
|
+
case "turso":
|
|
3720
|
+
return `${baseEnv}TURSO_DATABASE_URL=libsql://your-db.turso.io
|
|
3721
|
+
TURSO_AUTH_TOKEN=your-auth-token
|
|
3722
|
+
`;
|
|
3723
|
+
default:
|
|
3724
|
+
return `${baseEnv}DATABASE_URL=
|
|
3725
|
+
`;
|
|
3726
|
+
}
|
|
3727
|
+
}
|
|
3728
|
+
function generateProjectReadme(projectConfig) {
|
|
3729
|
+
const { projectName, projectMode } = projectConfig;
|
|
3730
|
+
if (projectMode === "frontend-only") {
|
|
3731
|
+
return `# ${projectName}
|
|
3732
|
+
|
|
3733
|
+
Built with Rakta.js \u2014 Small in size. Fierce in speed. Alive in every route.
|
|
3734
|
+
|
|
3735
|
+
## Stack
|
|
3736
|
+
|
|
3737
|
+
| Layer | Technology |
|
|
3738
|
+
| --- | --- |
|
|
3739
|
+
| Frontend | Rakta.js + React + TypeScript |
|
|
3740
|
+
| CSS | ${CSS_DISPLAY[projectConfig.cssFramework]} |
|
|
3741
|
+
| Runtime | Bun |
|
|
3742
|
+
|
|
3743
|
+
## Run
|
|
3744
|
+
|
|
3745
|
+
\`\`\`bash
|
|
3746
|
+
bun install
|
|
3747
|
+
bun run dev
|
|
3748
|
+
\`\`\`
|
|
3749
|
+
|
|
3750
|
+
## ShrimpRun
|
|
3751
|
+
|
|
3752
|
+
Your starter includes ShrimpRun \u2014 an interactive game where a shrimp dodges obstacles. Press Space or click to jump!
|
|
3753
|
+
`;
|
|
3754
|
+
}
|
|
3755
|
+
return `# ${projectName}
|
|
3756
|
+
|
|
3757
|
+
Built with Rakta.js \u2014 Small in size. Fierce in speed. Alive in every route.
|
|
3758
|
+
|
|
3759
|
+
## Stack
|
|
3760
|
+
|
|
3761
|
+
| Layer | Technology |
|
|
3762
|
+
| --- | --- |
|
|
3763
|
+
| Frontend | Rakta.js + React + TypeScript |
|
|
3764
|
+
| CSS | ${CSS_DISPLAY[projectConfig.cssFramework]} |
|
|
3765
|
+
| Backend | ${BACKEND_DISPLAY[projectConfig.backendFramework]} |
|
|
3766
|
+
| Database | ${DATABASE_DISPLAY[projectConfig.database]} |
|
|
3767
|
+
| Runtime | Bun |
|
|
3768
|
+
|
|
3769
|
+
## Run
|
|
3770
|
+
|
|
3771
|
+
\`\`\`bash
|
|
3772
|
+
bun install
|
|
3773
|
+
|
|
3774
|
+
# Terminal 1
|
|
3775
|
+
bun run dev:frontend
|
|
3776
|
+
|
|
3777
|
+
# Terminal 2
|
|
3778
|
+
bun run dev:backend
|
|
3779
|
+
\`\`\`
|
|
3780
|
+
|
|
3781
|
+
## Endpoints
|
|
3782
|
+
|
|
3783
|
+
- Frontend: http://localhost:3000
|
|
3784
|
+
- Backend: http://localhost:4000
|
|
3785
|
+
`;
|
|
3786
|
+
}
|
|
3787
|
+
function generateProjectFiles(projectConfig) {
|
|
3788
|
+
if (projectConfig.projectMode === "frontend-only") {
|
|
3789
|
+
return [
|
|
3790
|
+
...getRootFiles(projectConfig),
|
|
3791
|
+
...getFrontendOnlyFiles(projectConfig)
|
|
3792
|
+
];
|
|
3793
|
+
}
|
|
3794
|
+
return [
|
|
3795
|
+
...getRootFiles(projectConfig),
|
|
3796
|
+
...getFullstackFrontendFiles(projectConfig),
|
|
3797
|
+
...getBackendFiles(projectConfig),
|
|
3798
|
+
...getSharedFiles(projectConfig)
|
|
3799
|
+
];
|
|
3800
|
+
}
|
|
3801
|
+
|
|
3802
|
+
// src/prompts.ts
|
|
3803
|
+
function getPromptValue(promptResult) {
|
|
3804
|
+
if (isCancel(promptResult)) {
|
|
3805
|
+
cancel("Setup cancelled.");
|
|
3806
|
+
process.exit(0);
|
|
3807
|
+
}
|
|
3808
|
+
return promptResult;
|
|
3809
|
+
}
|
|
3810
|
+
async function promptProjectName(fallbackName) {
|
|
3811
|
+
const promptResult = await text({
|
|
3812
|
+
message: "Project name:",
|
|
3813
|
+
placeholder: fallbackName,
|
|
3814
|
+
defaultValue: fallbackName,
|
|
3815
|
+
validate(inputValue) {
|
|
3816
|
+
const trimmedInput = (inputValue ?? "").trim();
|
|
3817
|
+
if (trimmedInput.length === 0) {
|
|
3818
|
+
return "Project name cannot be empty.";
|
|
3819
|
+
}
|
|
3820
|
+
if (!/^[a-z0-9-_]+$/i.test(trimmedInput)) {
|
|
3821
|
+
return "Use only letters, numbers, hyphens, and underscores.";
|
|
3822
|
+
}
|
|
3823
|
+
return;
|
|
3824
|
+
}
|
|
3825
|
+
});
|
|
3826
|
+
return getPromptValue(promptResult).trim();
|
|
3827
|
+
}
|
|
3828
|
+
async function promptProjectMode() {
|
|
3829
|
+
const promptResult = await select({
|
|
3830
|
+
message: "What do you want to create?",
|
|
3831
|
+
options: [
|
|
3832
|
+
{
|
|
3833
|
+
value: "frontend-only",
|
|
3834
|
+
label: PROJECT_MODE_DISPLAY["frontend-only"],
|
|
3835
|
+
hint: "recommended for starters"
|
|
3836
|
+
},
|
|
3837
|
+
{
|
|
3838
|
+
value: "fullstack",
|
|
3839
|
+
label: PROJECT_MODE_DISPLAY.fullstack
|
|
3840
|
+
}
|
|
3841
|
+
],
|
|
3842
|
+
initialValue: "frontend-only"
|
|
3843
|
+
});
|
|
3844
|
+
return getPromptValue(promptResult);
|
|
3845
|
+
}
|
|
3846
|
+
async function promptCssFramework() {
|
|
3847
|
+
const promptResult = await select({
|
|
3848
|
+
message: "Choose a CSS framework:",
|
|
3849
|
+
options: [
|
|
3850
|
+
{
|
|
3851
|
+
value: "tailwind",
|
|
3852
|
+
label: CSS_DISPLAY.tailwind,
|
|
3853
|
+
hint: "recommended"
|
|
3854
|
+
},
|
|
3855
|
+
{
|
|
3856
|
+
value: "bootstrap",
|
|
3857
|
+
label: CSS_DISPLAY.bootstrap
|
|
3858
|
+
},
|
|
3859
|
+
{
|
|
3860
|
+
value: "sass",
|
|
3861
|
+
label: CSS_DISPLAY.sass
|
|
3862
|
+
},
|
|
3863
|
+
{
|
|
3864
|
+
value: "none",
|
|
3865
|
+
label: CSS_DISPLAY.none
|
|
3866
|
+
}
|
|
3867
|
+
],
|
|
3868
|
+
initialValue: "tailwind"
|
|
3869
|
+
});
|
|
3870
|
+
return getPromptValue(promptResult);
|
|
3871
|
+
}
|
|
3872
|
+
async function promptRenderMode() {
|
|
3873
|
+
const promptResult = await select({
|
|
3874
|
+
message: "Choose a render mode:",
|
|
3875
|
+
options: [
|
|
3876
|
+
{
|
|
3877
|
+
value: "csr",
|
|
3878
|
+
label: RENDER_MODE_DISPLAY.csr,
|
|
3879
|
+
hint: "recommended"
|
|
3880
|
+
},
|
|
3881
|
+
{
|
|
3882
|
+
value: "spa",
|
|
3883
|
+
label: RENDER_MODE_DISPLAY.spa
|
|
3884
|
+
},
|
|
3885
|
+
{
|
|
3886
|
+
value: "hybrid",
|
|
3887
|
+
label: RENDER_MODE_DISPLAY.hybrid
|
|
3888
|
+
},
|
|
3889
|
+
{
|
|
3890
|
+
value: "ssr",
|
|
3891
|
+
label: RENDER_MODE_DISPLAY.ssr
|
|
3892
|
+
},
|
|
3893
|
+
{
|
|
3894
|
+
value: "ssg",
|
|
3895
|
+
label: RENDER_MODE_DISPLAY.ssg
|
|
3896
|
+
},
|
|
3897
|
+
{
|
|
3898
|
+
value: "csg",
|
|
3899
|
+
label: RENDER_MODE_DISPLAY.csg
|
|
3900
|
+
}
|
|
3901
|
+
],
|
|
3902
|
+
initialValue: "csr"
|
|
3903
|
+
});
|
|
3904
|
+
return getPromptValue(promptResult);
|
|
3905
|
+
}
|
|
3906
|
+
async function promptBackendFramework() {
|
|
3907
|
+
const promptResult = await select({
|
|
3908
|
+
message: "Choose a backend framework:",
|
|
3909
|
+
options: [
|
|
3910
|
+
{
|
|
3911
|
+
value: "gaman",
|
|
3912
|
+
label: BACKEND_DISPLAY.gaman,
|
|
3913
|
+
hint: "recommended"
|
|
3914
|
+
},
|
|
3915
|
+
{
|
|
3916
|
+
value: "express",
|
|
3917
|
+
label: BACKEND_DISPLAY.express
|
|
3918
|
+
},
|
|
3919
|
+
{
|
|
3920
|
+
value: "nest",
|
|
3921
|
+
label: BACKEND_DISPLAY.nest
|
|
3922
|
+
},
|
|
3923
|
+
{
|
|
3924
|
+
value: "adonis",
|
|
3925
|
+
label: BACKEND_DISPLAY.adonis
|
|
3926
|
+
}
|
|
3927
|
+
],
|
|
3928
|
+
initialValue: "gaman"
|
|
3929
|
+
});
|
|
3930
|
+
return getPromptValue(promptResult);
|
|
3931
|
+
}
|
|
3932
|
+
async function promptDatabase() {
|
|
3933
|
+
const promptResult = await select({
|
|
3934
|
+
message: "Choose a database:",
|
|
3935
|
+
options: [
|
|
3936
|
+
{
|
|
3937
|
+
value: "postgresql",
|
|
3938
|
+
label: DATABASE_DISPLAY.postgresql,
|
|
3939
|
+
hint: "recommended"
|
|
3940
|
+
},
|
|
3941
|
+
{
|
|
3942
|
+
value: "mysql",
|
|
3943
|
+
label: DATABASE_DISPLAY.mysql
|
|
3944
|
+
},
|
|
3945
|
+
{
|
|
3946
|
+
value: "mongodb",
|
|
3947
|
+
label: DATABASE_DISPLAY.mongodb
|
|
3948
|
+
},
|
|
3949
|
+
{
|
|
3950
|
+
value: "firebase",
|
|
3951
|
+
label: DATABASE_DISPLAY.firebase
|
|
3952
|
+
},
|
|
3953
|
+
{
|
|
3954
|
+
value: "sqlite",
|
|
3955
|
+
label: DATABASE_DISPLAY.sqlite
|
|
3956
|
+
},
|
|
3957
|
+
{
|
|
3958
|
+
value: "mariadb",
|
|
3959
|
+
label: DATABASE_DISPLAY.mariadb
|
|
3960
|
+
},
|
|
3961
|
+
{
|
|
3962
|
+
value: "redis",
|
|
3963
|
+
label: DATABASE_DISPLAY.redis
|
|
3964
|
+
},
|
|
3965
|
+
{
|
|
3966
|
+
value: "planetscale",
|
|
3967
|
+
label: DATABASE_DISPLAY.planetscale
|
|
3968
|
+
},
|
|
3969
|
+
{
|
|
3970
|
+
value: "neon",
|
|
3971
|
+
label: DATABASE_DISPLAY.neon
|
|
3972
|
+
},
|
|
3973
|
+
{
|
|
3974
|
+
value: "turso",
|
|
3975
|
+
label: DATABASE_DISPLAY.turso
|
|
3976
|
+
}
|
|
3977
|
+
],
|
|
3978
|
+
initialValue: "postgresql"
|
|
3979
|
+
});
|
|
3980
|
+
return getPromptValue(promptResult);
|
|
3981
|
+
}
|
|
3982
|
+
async function runPrompts(suggestedName) {
|
|
3983
|
+
const projectName = await promptProjectName(suggestedName);
|
|
3984
|
+
const projectMode = await promptProjectMode();
|
|
3985
|
+
const cssFramework = await promptCssFramework();
|
|
3986
|
+
const renderMode = await promptRenderMode();
|
|
3987
|
+
if (projectMode === "frontend-only") {
|
|
3988
|
+
return {
|
|
3989
|
+
projectName,
|
|
3990
|
+
projectMode,
|
|
3991
|
+
cssFramework,
|
|
3992
|
+
renderMode,
|
|
3993
|
+
backendFramework: "gaman",
|
|
3994
|
+
database: "postgresql"
|
|
3995
|
+
};
|
|
3996
|
+
}
|
|
3997
|
+
const backendFramework = await promptBackendFramework();
|
|
3998
|
+
const selectedDatabase = await promptDatabase();
|
|
3999
|
+
return {
|
|
4000
|
+
projectName,
|
|
4001
|
+
projectMode,
|
|
4002
|
+
cssFramework,
|
|
4003
|
+
renderMode,
|
|
4004
|
+
backendFramework,
|
|
4005
|
+
database: selectedDatabase
|
|
4006
|
+
};
|
|
4007
|
+
}
|
|
4008
|
+
|
|
4009
|
+
// src/index.ts
|
|
4010
|
+
function getProjectNameFromArgs(cliArgs) {
|
|
4011
|
+
const nameArg = cliArgs.find((arg) => !arg.startsWith("--"));
|
|
4012
|
+
if (nameArg !== undefined && nameArg.trim().length > 0) {
|
|
4013
|
+
return nameArg.trim();
|
|
4014
|
+
}
|
|
4015
|
+
return;
|
|
4016
|
+
}
|
|
4017
|
+
function formatFullstackCommands() {
|
|
4018
|
+
return [
|
|
4019
|
+
`${import_picocolors.default.dim("# Terminal 1")}`,
|
|
4020
|
+
"cd frontend",
|
|
4021
|
+
"bun install",
|
|
4022
|
+
"bun run dev",
|
|
4023
|
+
"",
|
|
4024
|
+
`${import_picocolors.default.dim("# Terminal 2")}`,
|
|
4025
|
+
"cd backend",
|
|
4026
|
+
"bun install",
|
|
4027
|
+
"bun run dev"
|
|
4028
|
+
].join(`
|
|
4029
|
+
`);
|
|
4030
|
+
}
|
|
4031
|
+
function formatFrontendOnlyCommands(projectName) {
|
|
4032
|
+
return [`cd ${projectName}`, "bun install", "bun run dev"].join(`
|
|
4033
|
+
`);
|
|
4034
|
+
}
|
|
4035
|
+
function printSuccessMessage(projectConfig) {
|
|
4036
|
+
const modeLabel = PROJECT_MODE_DISPLAY[projectConfig.projectMode];
|
|
4037
|
+
const cssLabel = CSS_DISPLAY[projectConfig.cssFramework];
|
|
4038
|
+
const renderLabel = RENDER_MODE_DISPLAY[projectConfig.renderMode];
|
|
4039
|
+
const isFullstack = projectConfig.projectMode === "fullstack";
|
|
4040
|
+
const backendLine = isFullstack ? `${import_picocolors.default.dim("Backend:")} ${BACKEND_DISPLAY[projectConfig.backendFramework]}` : "";
|
|
4041
|
+
const databaseLine = isFullstack ? `${import_picocolors.default.dim("DB:")} ${DATABASE_DISPLAY[projectConfig.database]}` : "";
|
|
4042
|
+
const nextSteps = isFullstack ? formatFullstackCommands() : formatFrontendOnlyCommands(projectConfig.projectName);
|
|
4043
|
+
console.log(`
|
|
4044
|
+
${import_picocolors.default.bold(import_picocolors.default.green("Project created!"))}
|
|
4045
|
+
|
|
4046
|
+
${import_picocolors.default.dim("Mode:")} ${modeLabel}
|
|
4047
|
+
${import_picocolors.default.dim("CSS:")} ${cssLabel}
|
|
4048
|
+
${import_picocolors.default.dim("Render:")} ${renderLabel}
|
|
4049
|
+
${backendLine}
|
|
4050
|
+
${databaseLine}
|
|
4051
|
+
|
|
4052
|
+
${import_picocolors.default.bold("Next steps:")}
|
|
4053
|
+
|
|
4054
|
+
${nextSteps}
|
|
4055
|
+
|
|
4056
|
+
${import_picocolors.default.bold("Frontend:")} ${import_picocolors.default.cyan("http://localhost:3000")}
|
|
4057
|
+
${isFullstack ? `${import_picocolors.default.bold("Backend:")} ${import_picocolors.default.cyan("http://localhost:4000")}` : ""}
|
|
4058
|
+
`);
|
|
4059
|
+
}
|
|
4060
|
+
async function main() {
|
|
4061
|
+
const rawArgs = process.argv.slice(2);
|
|
4062
|
+
printBanner();
|
|
4063
|
+
const projectNameFromArgs = getProjectNameFromArgs(rawArgs);
|
|
4064
|
+
const projectName = projectNameFromArgs ?? await promptProjectName("my-rakta-app");
|
|
4065
|
+
const targetPath = resolve2(process.cwd(), projectName);
|
|
4066
|
+
if (existsSync2(targetPath)) {
|
|
4067
|
+
cancel(`Directory ${import_picocolors.default.bold(import_picocolors.default.yellow(projectName))} already exists. Choose a different name.`);
|
|
4068
|
+
process.exit(1);
|
|
4069
|
+
}
|
|
4070
|
+
const projectConfig = await runPrompts(projectName);
|
|
4071
|
+
const loadingSpinner = spinner();
|
|
4072
|
+
loadingSpinner.start("Generating project files...");
|
|
4073
|
+
const generatedFiles = generateProjectFiles(projectConfig);
|
|
4074
|
+
try {
|
|
4075
|
+
writeProjectFiles(targetPath, generatedFiles);
|
|
4076
|
+
loadingSpinner.stop(import_picocolors.default.green("Project files generated."));
|
|
4077
|
+
} catch (caughtError) {
|
|
4078
|
+
loadingSpinner.stop(import_picocolors.default.red("File generation failed."));
|
|
4079
|
+
if (caughtError instanceof Error) {
|
|
4080
|
+
console.error(import_picocolors.default.red(caughtError.message));
|
|
4081
|
+
}
|
|
4082
|
+
process.exit(1);
|
|
4083
|
+
}
|
|
4084
|
+
printSuccessMessage(projectConfig);
|
|
4085
|
+
}
|
|
4086
|
+
main().catch((caughtError) => {
|
|
4087
|
+
const errorMessage = caughtError instanceof Error ? caughtError.message : String(caughtError);
|
|
4088
|
+
console.error(import_picocolors.default.red(`
|
|
4089
|
+
Error: ${errorMessage}
|
|
4090
|
+
`));
|
|
4091
|
+
process.exit(1);
|
|
4092
|
+
});
|
|
4093
|
+
|
|
4094
|
+
//# debugId=B9AF63AAAD27EC1C64756E2164756E21
|