@iroaxel/arcena 4.3.157
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/LICENSE +21 -0
- package/README.md +157 -0
- package/dist/chunk-KZJPLEZU.js +358 -0
- package/dist/chunk-KZJPLEZU.js.map +1 -0
- package/dist/chunk-OWJSBNAJ.js +94597 -0
- package/dist/chunk-OWJSBNAJ.js.map +1 -0
- package/dist/chunk-YKKUCYNE.js +2305 -0
- package/dist/chunk-YKKUCYNE.js.map +1 -0
- package/dist/chunk-YNWFCUMR.js +57 -0
- package/dist/chunk-YNWFCUMR.js.map +1 -0
- package/dist/cli.js +3504 -0
- package/dist/cli.js.map +1 -0
- package/dist/devtools-4TI4D7F2.js +3739 -0
- package/dist/devtools-4TI4D7F2.js.map +1 -0
- package/dist/dist-VXOVSHZ5.js +44276 -0
- package/dist/dist-VXOVSHZ5.js.map +1 -0
- package/dist/done.mp3 +0 -0
- package/dist/ignore-76P4EAAU.js +465 -0
- package/dist/ignore-76P4EAAU.js.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/out-XEXARMKS.js +5644 -0
- package/dist/out-XEXARMKS.js.map +1 -0
- package/dist/pixel-I5TTXQ4I.js +14 -0
- package/dist/pixel-I5TTXQ4I.js.map +1 -0
- package/dist/pixel-fix-UIPF4UIQ.js +941 -0
- package/dist/pixel-fix-UIPF4UIQ.js.map +1 -0
- package/dist/ready.mp3 +0 -0
- package/dist/splash.mp3 +0 -0
- package/package.json +50 -0
|
@@ -0,0 +1,2305 @@
|
|
|
1
|
+
import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
init_esm_shims
|
|
4
|
+
} from "./chunk-YNWFCUMR.js";
|
|
5
|
+
|
|
6
|
+
// ../../node_modules/.pnpm/chalk@5.6.2/node_modules/chalk/source/index.js
|
|
7
|
+
init_esm_shims();
|
|
8
|
+
|
|
9
|
+
// ../../node_modules/.pnpm/chalk@5.6.2/node_modules/chalk/source/vendor/ansi-styles/index.js
|
|
10
|
+
init_esm_shims();
|
|
11
|
+
var ANSI_BACKGROUND_OFFSET = 10;
|
|
12
|
+
var wrapAnsi16 = (offset = 0) => (code) => `\x1B[${code + offset}m`;
|
|
13
|
+
var wrapAnsi256 = (offset = 0) => (code) => `\x1B[${38 + offset};5;${code}m`;
|
|
14
|
+
var wrapAnsi16m = (offset = 0) => (red, green, blue) => `\x1B[${38 + offset};2;${red};${green};${blue}m`;
|
|
15
|
+
var styles = {
|
|
16
|
+
modifier: {
|
|
17
|
+
reset: [0, 0],
|
|
18
|
+
// 21 isn't widely supported and 22 does the same thing
|
|
19
|
+
bold: [1, 22],
|
|
20
|
+
dim: [2, 22],
|
|
21
|
+
italic: [3, 23],
|
|
22
|
+
underline: [4, 24],
|
|
23
|
+
overline: [53, 55],
|
|
24
|
+
inverse: [7, 27],
|
|
25
|
+
hidden: [8, 28],
|
|
26
|
+
strikethrough: [9, 29]
|
|
27
|
+
},
|
|
28
|
+
color: {
|
|
29
|
+
black: [30, 39],
|
|
30
|
+
red: [31, 39],
|
|
31
|
+
green: [32, 39],
|
|
32
|
+
yellow: [33, 39],
|
|
33
|
+
blue: [34, 39],
|
|
34
|
+
magenta: [35, 39],
|
|
35
|
+
cyan: [36, 39],
|
|
36
|
+
white: [37, 39],
|
|
37
|
+
// Bright color
|
|
38
|
+
blackBright: [90, 39],
|
|
39
|
+
gray: [90, 39],
|
|
40
|
+
// Alias of `blackBright`
|
|
41
|
+
grey: [90, 39],
|
|
42
|
+
// Alias of `blackBright`
|
|
43
|
+
redBright: [91, 39],
|
|
44
|
+
greenBright: [92, 39],
|
|
45
|
+
yellowBright: [93, 39],
|
|
46
|
+
blueBright: [94, 39],
|
|
47
|
+
magentaBright: [95, 39],
|
|
48
|
+
cyanBright: [96, 39],
|
|
49
|
+
whiteBright: [97, 39]
|
|
50
|
+
},
|
|
51
|
+
bgColor: {
|
|
52
|
+
bgBlack: [40, 49],
|
|
53
|
+
bgRed: [41, 49],
|
|
54
|
+
bgGreen: [42, 49],
|
|
55
|
+
bgYellow: [43, 49],
|
|
56
|
+
bgBlue: [44, 49],
|
|
57
|
+
bgMagenta: [45, 49],
|
|
58
|
+
bgCyan: [46, 49],
|
|
59
|
+
bgWhite: [47, 49],
|
|
60
|
+
// Bright color
|
|
61
|
+
bgBlackBright: [100, 49],
|
|
62
|
+
bgGray: [100, 49],
|
|
63
|
+
// Alias of `bgBlackBright`
|
|
64
|
+
bgGrey: [100, 49],
|
|
65
|
+
// Alias of `bgBlackBright`
|
|
66
|
+
bgRedBright: [101, 49],
|
|
67
|
+
bgGreenBright: [102, 49],
|
|
68
|
+
bgYellowBright: [103, 49],
|
|
69
|
+
bgBlueBright: [104, 49],
|
|
70
|
+
bgMagentaBright: [105, 49],
|
|
71
|
+
bgCyanBright: [106, 49],
|
|
72
|
+
bgWhiteBright: [107, 49]
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
var modifierNames = Object.keys(styles.modifier);
|
|
76
|
+
var foregroundColorNames = Object.keys(styles.color);
|
|
77
|
+
var backgroundColorNames = Object.keys(styles.bgColor);
|
|
78
|
+
var colorNames = [...foregroundColorNames, ...backgroundColorNames];
|
|
79
|
+
function assembleStyles() {
|
|
80
|
+
const codes = /* @__PURE__ */ new Map();
|
|
81
|
+
for (const [groupName, group] of Object.entries(styles)) {
|
|
82
|
+
for (const [styleName, style] of Object.entries(group)) {
|
|
83
|
+
styles[styleName] = {
|
|
84
|
+
open: `\x1B[${style[0]}m`,
|
|
85
|
+
close: `\x1B[${style[1]}m`
|
|
86
|
+
};
|
|
87
|
+
group[styleName] = styles[styleName];
|
|
88
|
+
codes.set(style[0], style[1]);
|
|
89
|
+
}
|
|
90
|
+
Object.defineProperty(styles, groupName, {
|
|
91
|
+
value: group,
|
|
92
|
+
enumerable: false
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
Object.defineProperty(styles, "codes", {
|
|
96
|
+
value: codes,
|
|
97
|
+
enumerable: false
|
|
98
|
+
});
|
|
99
|
+
styles.color.close = "\x1B[39m";
|
|
100
|
+
styles.bgColor.close = "\x1B[49m";
|
|
101
|
+
styles.color.ansi = wrapAnsi16();
|
|
102
|
+
styles.color.ansi256 = wrapAnsi256();
|
|
103
|
+
styles.color.ansi16m = wrapAnsi16m();
|
|
104
|
+
styles.bgColor.ansi = wrapAnsi16(ANSI_BACKGROUND_OFFSET);
|
|
105
|
+
styles.bgColor.ansi256 = wrapAnsi256(ANSI_BACKGROUND_OFFSET);
|
|
106
|
+
styles.bgColor.ansi16m = wrapAnsi16m(ANSI_BACKGROUND_OFFSET);
|
|
107
|
+
Object.defineProperties(styles, {
|
|
108
|
+
rgbToAnsi256: {
|
|
109
|
+
value(red, green, blue) {
|
|
110
|
+
if (red === green && green === blue) {
|
|
111
|
+
if (red < 8) {
|
|
112
|
+
return 16;
|
|
113
|
+
}
|
|
114
|
+
if (red > 248) {
|
|
115
|
+
return 231;
|
|
116
|
+
}
|
|
117
|
+
return Math.round((red - 8) / 247 * 24) + 232;
|
|
118
|
+
}
|
|
119
|
+
return 16 + 36 * Math.round(red / 255 * 5) + 6 * Math.round(green / 255 * 5) + Math.round(blue / 255 * 5);
|
|
120
|
+
},
|
|
121
|
+
enumerable: false
|
|
122
|
+
},
|
|
123
|
+
hexToRgb: {
|
|
124
|
+
value(hex) {
|
|
125
|
+
const matches = /[a-f\d]{6}|[a-f\d]{3}/i.exec(hex.toString(16));
|
|
126
|
+
if (!matches) {
|
|
127
|
+
return [0, 0, 0];
|
|
128
|
+
}
|
|
129
|
+
let [colorString] = matches;
|
|
130
|
+
if (colorString.length === 3) {
|
|
131
|
+
colorString = [...colorString].map((character) => character + character).join("");
|
|
132
|
+
}
|
|
133
|
+
const integer = Number.parseInt(colorString, 16);
|
|
134
|
+
return [
|
|
135
|
+
/* eslint-disable no-bitwise */
|
|
136
|
+
integer >> 16 & 255,
|
|
137
|
+
integer >> 8 & 255,
|
|
138
|
+
integer & 255
|
|
139
|
+
/* eslint-enable no-bitwise */
|
|
140
|
+
];
|
|
141
|
+
},
|
|
142
|
+
enumerable: false
|
|
143
|
+
},
|
|
144
|
+
hexToAnsi256: {
|
|
145
|
+
value: (hex) => styles.rgbToAnsi256(...styles.hexToRgb(hex)),
|
|
146
|
+
enumerable: false
|
|
147
|
+
},
|
|
148
|
+
ansi256ToAnsi: {
|
|
149
|
+
value(code) {
|
|
150
|
+
if (code < 8) {
|
|
151
|
+
return 30 + code;
|
|
152
|
+
}
|
|
153
|
+
if (code < 16) {
|
|
154
|
+
return 90 + (code - 8);
|
|
155
|
+
}
|
|
156
|
+
let red;
|
|
157
|
+
let green;
|
|
158
|
+
let blue;
|
|
159
|
+
if (code >= 232) {
|
|
160
|
+
red = ((code - 232) * 10 + 8) / 255;
|
|
161
|
+
green = red;
|
|
162
|
+
blue = red;
|
|
163
|
+
} else {
|
|
164
|
+
code -= 16;
|
|
165
|
+
const remainder = code % 36;
|
|
166
|
+
red = Math.floor(code / 36) / 5;
|
|
167
|
+
green = Math.floor(remainder / 6) / 5;
|
|
168
|
+
blue = remainder % 6 / 5;
|
|
169
|
+
}
|
|
170
|
+
const value = Math.max(red, green, blue) * 2;
|
|
171
|
+
if (value === 0) {
|
|
172
|
+
return 30;
|
|
173
|
+
}
|
|
174
|
+
let result = 30 + (Math.round(blue) << 2 | Math.round(green) << 1 | Math.round(red));
|
|
175
|
+
if (value === 2) {
|
|
176
|
+
result += 60;
|
|
177
|
+
}
|
|
178
|
+
return result;
|
|
179
|
+
},
|
|
180
|
+
enumerable: false
|
|
181
|
+
},
|
|
182
|
+
rgbToAnsi: {
|
|
183
|
+
value: (red, green, blue) => styles.ansi256ToAnsi(styles.rgbToAnsi256(red, green, blue)),
|
|
184
|
+
enumerable: false
|
|
185
|
+
},
|
|
186
|
+
hexToAnsi: {
|
|
187
|
+
value: (hex) => styles.ansi256ToAnsi(styles.hexToAnsi256(hex)),
|
|
188
|
+
enumerable: false
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
return styles;
|
|
192
|
+
}
|
|
193
|
+
var ansiStyles = assembleStyles();
|
|
194
|
+
var ansi_styles_default = ansiStyles;
|
|
195
|
+
|
|
196
|
+
// ../../node_modules/.pnpm/chalk@5.6.2/node_modules/chalk/source/vendor/supports-color/index.js
|
|
197
|
+
init_esm_shims();
|
|
198
|
+
import process2 from "process";
|
|
199
|
+
import os from "os";
|
|
200
|
+
import tty from "tty";
|
|
201
|
+
function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process2.argv) {
|
|
202
|
+
const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
|
|
203
|
+
const position = argv.indexOf(prefix + flag);
|
|
204
|
+
const terminatorPosition = argv.indexOf("--");
|
|
205
|
+
return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
|
|
206
|
+
}
|
|
207
|
+
var { env } = process2;
|
|
208
|
+
var flagForceColor;
|
|
209
|
+
if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) {
|
|
210
|
+
flagForceColor = 0;
|
|
211
|
+
} else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) {
|
|
212
|
+
flagForceColor = 1;
|
|
213
|
+
}
|
|
214
|
+
function envForceColor() {
|
|
215
|
+
if ("FORCE_COLOR" in env) {
|
|
216
|
+
if (env.FORCE_COLOR === "true") {
|
|
217
|
+
return 1;
|
|
218
|
+
}
|
|
219
|
+
if (env.FORCE_COLOR === "false") {
|
|
220
|
+
return 0;
|
|
221
|
+
}
|
|
222
|
+
return env.FORCE_COLOR.length === 0 ? 1 : Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function translateLevel(level) {
|
|
226
|
+
if (level === 0) {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
level,
|
|
231
|
+
hasBasic: true,
|
|
232
|
+
has256: level >= 2,
|
|
233
|
+
has16m: level >= 3
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
|
|
237
|
+
const noFlagForceColor = envForceColor();
|
|
238
|
+
if (noFlagForceColor !== void 0) {
|
|
239
|
+
flagForceColor = noFlagForceColor;
|
|
240
|
+
}
|
|
241
|
+
const forceColor = sniffFlags ? flagForceColor : noFlagForceColor;
|
|
242
|
+
if (forceColor === 0) {
|
|
243
|
+
return 0;
|
|
244
|
+
}
|
|
245
|
+
if (sniffFlags) {
|
|
246
|
+
if (hasFlag("color=16m") || hasFlag("color=full") || hasFlag("color=truecolor")) {
|
|
247
|
+
return 3;
|
|
248
|
+
}
|
|
249
|
+
if (hasFlag("color=256")) {
|
|
250
|
+
return 2;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if ("TF_BUILD" in env && "AGENT_NAME" in env) {
|
|
254
|
+
return 1;
|
|
255
|
+
}
|
|
256
|
+
if (haveStream && !streamIsTTY && forceColor === void 0) {
|
|
257
|
+
return 0;
|
|
258
|
+
}
|
|
259
|
+
const min = forceColor || 0;
|
|
260
|
+
if (env.TERM === "dumb") {
|
|
261
|
+
return min;
|
|
262
|
+
}
|
|
263
|
+
if (process2.platform === "win32") {
|
|
264
|
+
const osRelease = os.release().split(".");
|
|
265
|
+
if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
|
|
266
|
+
return Number(osRelease[2]) >= 14931 ? 3 : 2;
|
|
267
|
+
}
|
|
268
|
+
return 1;
|
|
269
|
+
}
|
|
270
|
+
if ("CI" in env) {
|
|
271
|
+
if (["GITHUB_ACTIONS", "GITEA_ACTIONS", "CIRCLECI"].some((key) => key in env)) {
|
|
272
|
+
return 3;
|
|
273
|
+
}
|
|
274
|
+
if (["TRAVIS", "APPVEYOR", "GITLAB_CI", "BUILDKITE", "DRONE"].some((sign) => sign in env) || env.CI_NAME === "codeship") {
|
|
275
|
+
return 1;
|
|
276
|
+
}
|
|
277
|
+
return min;
|
|
278
|
+
}
|
|
279
|
+
if ("TEAMCITY_VERSION" in env) {
|
|
280
|
+
return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0;
|
|
281
|
+
}
|
|
282
|
+
if (env.COLORTERM === "truecolor") {
|
|
283
|
+
return 3;
|
|
284
|
+
}
|
|
285
|
+
if (env.TERM === "xterm-kitty") {
|
|
286
|
+
return 3;
|
|
287
|
+
}
|
|
288
|
+
if (env.TERM === "xterm-ghostty") {
|
|
289
|
+
return 3;
|
|
290
|
+
}
|
|
291
|
+
if (env.TERM === "wezterm") {
|
|
292
|
+
return 3;
|
|
293
|
+
}
|
|
294
|
+
if ("TERM_PROGRAM" in env) {
|
|
295
|
+
const version = Number.parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
|
|
296
|
+
switch (env.TERM_PROGRAM) {
|
|
297
|
+
case "iTerm.app": {
|
|
298
|
+
return version >= 3 ? 3 : 2;
|
|
299
|
+
}
|
|
300
|
+
case "Apple_Terminal": {
|
|
301
|
+
return 2;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if (/-256(color)?$/i.test(env.TERM)) {
|
|
306
|
+
return 2;
|
|
307
|
+
}
|
|
308
|
+
if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
|
|
309
|
+
return 1;
|
|
310
|
+
}
|
|
311
|
+
if ("COLORTERM" in env) {
|
|
312
|
+
return 1;
|
|
313
|
+
}
|
|
314
|
+
return min;
|
|
315
|
+
}
|
|
316
|
+
function createSupportsColor(stream, options = {}) {
|
|
317
|
+
const level = _supportsColor(stream, {
|
|
318
|
+
streamIsTTY: stream && stream.isTTY,
|
|
319
|
+
...options
|
|
320
|
+
});
|
|
321
|
+
return translateLevel(level);
|
|
322
|
+
}
|
|
323
|
+
var supportsColor = {
|
|
324
|
+
stdout: createSupportsColor({ isTTY: tty.isatty(1) }),
|
|
325
|
+
stderr: createSupportsColor({ isTTY: tty.isatty(2) })
|
|
326
|
+
};
|
|
327
|
+
var supports_color_default = supportsColor;
|
|
328
|
+
|
|
329
|
+
// ../../node_modules/.pnpm/chalk@5.6.2/node_modules/chalk/source/utilities.js
|
|
330
|
+
init_esm_shims();
|
|
331
|
+
function stringReplaceAll(string, substring, replacer) {
|
|
332
|
+
let index = string.indexOf(substring);
|
|
333
|
+
if (index === -1) {
|
|
334
|
+
return string;
|
|
335
|
+
}
|
|
336
|
+
const substringLength = substring.length;
|
|
337
|
+
let endIndex = 0;
|
|
338
|
+
let returnValue = "";
|
|
339
|
+
do {
|
|
340
|
+
returnValue += string.slice(endIndex, index) + substring + replacer;
|
|
341
|
+
endIndex = index + substringLength;
|
|
342
|
+
index = string.indexOf(substring, endIndex);
|
|
343
|
+
} while (index !== -1);
|
|
344
|
+
returnValue += string.slice(endIndex);
|
|
345
|
+
return returnValue;
|
|
346
|
+
}
|
|
347
|
+
function stringEncaseCRLFWithFirstIndex(string, prefix, postfix, index) {
|
|
348
|
+
let endIndex = 0;
|
|
349
|
+
let returnValue = "";
|
|
350
|
+
do {
|
|
351
|
+
const gotCR = string[index - 1] === "\r";
|
|
352
|
+
returnValue += string.slice(endIndex, gotCR ? index - 1 : index) + prefix + (gotCR ? "\r\n" : "\n") + postfix;
|
|
353
|
+
endIndex = index + 1;
|
|
354
|
+
index = string.indexOf("\n", endIndex);
|
|
355
|
+
} while (index !== -1);
|
|
356
|
+
returnValue += string.slice(endIndex);
|
|
357
|
+
return returnValue;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// ../../node_modules/.pnpm/chalk@5.6.2/node_modules/chalk/source/index.js
|
|
361
|
+
var { stdout: stdoutColor, stderr: stderrColor } = supports_color_default;
|
|
362
|
+
var GENERATOR = /* @__PURE__ */ Symbol("GENERATOR");
|
|
363
|
+
var STYLER = /* @__PURE__ */ Symbol("STYLER");
|
|
364
|
+
var IS_EMPTY = /* @__PURE__ */ Symbol("IS_EMPTY");
|
|
365
|
+
var levelMapping = [
|
|
366
|
+
"ansi",
|
|
367
|
+
"ansi",
|
|
368
|
+
"ansi256",
|
|
369
|
+
"ansi16m"
|
|
370
|
+
];
|
|
371
|
+
var styles2 = /* @__PURE__ */ Object.create(null);
|
|
372
|
+
var applyOptions = (object, options = {}) => {
|
|
373
|
+
if (options.level && !(Number.isInteger(options.level) && options.level >= 0 && options.level <= 3)) {
|
|
374
|
+
throw new Error("The `level` option should be an integer from 0 to 3");
|
|
375
|
+
}
|
|
376
|
+
const colorLevel = stdoutColor ? stdoutColor.level : 0;
|
|
377
|
+
object.level = options.level === void 0 ? colorLevel : options.level;
|
|
378
|
+
};
|
|
379
|
+
var chalkFactory = (options) => {
|
|
380
|
+
const chalk2 = (...strings) => strings.join(" ");
|
|
381
|
+
applyOptions(chalk2, options);
|
|
382
|
+
Object.setPrototypeOf(chalk2, createChalk.prototype);
|
|
383
|
+
return chalk2;
|
|
384
|
+
};
|
|
385
|
+
function createChalk(options) {
|
|
386
|
+
return chalkFactory(options);
|
|
387
|
+
}
|
|
388
|
+
Object.setPrototypeOf(createChalk.prototype, Function.prototype);
|
|
389
|
+
for (const [styleName, style] of Object.entries(ansi_styles_default)) {
|
|
390
|
+
styles2[styleName] = {
|
|
391
|
+
get() {
|
|
392
|
+
const builder = createBuilder(this, createStyler(style.open, style.close, this[STYLER]), this[IS_EMPTY]);
|
|
393
|
+
Object.defineProperty(this, styleName, { value: builder });
|
|
394
|
+
return builder;
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
styles2.visible = {
|
|
399
|
+
get() {
|
|
400
|
+
const builder = createBuilder(this, this[STYLER], true);
|
|
401
|
+
Object.defineProperty(this, "visible", { value: builder });
|
|
402
|
+
return builder;
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
var getModelAnsi = (model, level, type, ...arguments_) => {
|
|
406
|
+
if (model === "rgb") {
|
|
407
|
+
if (level === "ansi16m") {
|
|
408
|
+
return ansi_styles_default[type].ansi16m(...arguments_);
|
|
409
|
+
}
|
|
410
|
+
if (level === "ansi256") {
|
|
411
|
+
return ansi_styles_default[type].ansi256(ansi_styles_default.rgbToAnsi256(...arguments_));
|
|
412
|
+
}
|
|
413
|
+
return ansi_styles_default[type].ansi(ansi_styles_default.rgbToAnsi(...arguments_));
|
|
414
|
+
}
|
|
415
|
+
if (model === "hex") {
|
|
416
|
+
return getModelAnsi("rgb", level, type, ...ansi_styles_default.hexToRgb(...arguments_));
|
|
417
|
+
}
|
|
418
|
+
return ansi_styles_default[type][model](...arguments_);
|
|
419
|
+
};
|
|
420
|
+
var usedModels = ["rgb", "hex", "ansi256"];
|
|
421
|
+
for (const model of usedModels) {
|
|
422
|
+
styles2[model] = {
|
|
423
|
+
get() {
|
|
424
|
+
const { level } = this;
|
|
425
|
+
return function(...arguments_) {
|
|
426
|
+
const styler = createStyler(getModelAnsi(model, levelMapping[level], "color", ...arguments_), ansi_styles_default.color.close, this[STYLER]);
|
|
427
|
+
return createBuilder(this, styler, this[IS_EMPTY]);
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
const bgModel = "bg" + model[0].toUpperCase() + model.slice(1);
|
|
432
|
+
styles2[bgModel] = {
|
|
433
|
+
get() {
|
|
434
|
+
const { level } = this;
|
|
435
|
+
return function(...arguments_) {
|
|
436
|
+
const styler = createStyler(getModelAnsi(model, levelMapping[level], "bgColor", ...arguments_), ansi_styles_default.bgColor.close, this[STYLER]);
|
|
437
|
+
return createBuilder(this, styler, this[IS_EMPTY]);
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
var proto = Object.defineProperties(() => {
|
|
443
|
+
}, {
|
|
444
|
+
...styles2,
|
|
445
|
+
level: {
|
|
446
|
+
enumerable: true,
|
|
447
|
+
get() {
|
|
448
|
+
return this[GENERATOR].level;
|
|
449
|
+
},
|
|
450
|
+
set(level) {
|
|
451
|
+
this[GENERATOR].level = level;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
var createStyler = (open, close, parent) => {
|
|
456
|
+
let openAll;
|
|
457
|
+
let closeAll;
|
|
458
|
+
if (parent === void 0) {
|
|
459
|
+
openAll = open;
|
|
460
|
+
closeAll = close;
|
|
461
|
+
} else {
|
|
462
|
+
openAll = parent.openAll + open;
|
|
463
|
+
closeAll = close + parent.closeAll;
|
|
464
|
+
}
|
|
465
|
+
return {
|
|
466
|
+
open,
|
|
467
|
+
close,
|
|
468
|
+
openAll,
|
|
469
|
+
closeAll,
|
|
470
|
+
parent
|
|
471
|
+
};
|
|
472
|
+
};
|
|
473
|
+
var createBuilder = (self, _styler, _isEmpty) => {
|
|
474
|
+
const builder = (...arguments_) => applyStyle(builder, arguments_.length === 1 ? "" + arguments_[0] : arguments_.join(" "));
|
|
475
|
+
Object.setPrototypeOf(builder, proto);
|
|
476
|
+
builder[GENERATOR] = self;
|
|
477
|
+
builder[STYLER] = _styler;
|
|
478
|
+
builder[IS_EMPTY] = _isEmpty;
|
|
479
|
+
return builder;
|
|
480
|
+
};
|
|
481
|
+
var applyStyle = (self, string) => {
|
|
482
|
+
if (self.level <= 0 || !string) {
|
|
483
|
+
return self[IS_EMPTY] ? "" : string;
|
|
484
|
+
}
|
|
485
|
+
let styler = self[STYLER];
|
|
486
|
+
if (styler === void 0) {
|
|
487
|
+
return string;
|
|
488
|
+
}
|
|
489
|
+
const { openAll, closeAll } = styler;
|
|
490
|
+
if (string.includes("\x1B")) {
|
|
491
|
+
while (styler !== void 0) {
|
|
492
|
+
string = stringReplaceAll(string, styler.close, styler.open);
|
|
493
|
+
styler = styler.parent;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
const lfIndex = string.indexOf("\n");
|
|
497
|
+
if (lfIndex !== -1) {
|
|
498
|
+
string = stringEncaseCRLFWithFirstIndex(string, closeAll, openAll, lfIndex);
|
|
499
|
+
}
|
|
500
|
+
return openAll + string + closeAll;
|
|
501
|
+
};
|
|
502
|
+
Object.defineProperties(createChalk.prototype, styles2);
|
|
503
|
+
var chalk = createChalk();
|
|
504
|
+
var chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
|
|
505
|
+
var source_default = chalk;
|
|
506
|
+
|
|
507
|
+
// ../gg-pixel/dist/index.js
|
|
508
|
+
init_esm_shims();
|
|
509
|
+
import { createRequire } from "module";
|
|
510
|
+
import {
|
|
511
|
+
existsSync as existsSync2,
|
|
512
|
+
readFileSync as readFileSync2,
|
|
513
|
+
writeFileSync,
|
|
514
|
+
appendFileSync,
|
|
515
|
+
mkdirSync as mkdirSync2,
|
|
516
|
+
readdirSync
|
|
517
|
+
} from "fs";
|
|
518
|
+
import { createRequire as createRequire2 } from "module";
|
|
519
|
+
import { homedir as homedir2 } from "os";
|
|
520
|
+
import { dirname as dirname2, join as join2, relative, resolve, sep } from "path";
|
|
521
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
522
|
+
import { spawn as nodeSpawn } from "child_process";
|
|
523
|
+
import { writeFileSync as writeFileSync2, unlinkSync, mkdirSync as mkdirSync3, existsSync as existsSync3 } from "fs";
|
|
524
|
+
import { join as join3 } from "path";
|
|
525
|
+
import { randomBytes } from "crypto";
|
|
526
|
+
var requireBSQ = createRequire(import.meta.url);
|
|
527
|
+
var nodeRequire = createRequire2(import.meta.url);
|
|
528
|
+
var DEFAULT_INGEST_URL = "https://gg-pixel-server.buzzbeamaustralia.workers.dev";
|
|
529
|
+
async function install(opts = {}) {
|
|
530
|
+
const cwd = resolve(opts.cwd ?? process.cwd());
|
|
531
|
+
const ingestUrl = (opts.ingestUrl ?? DEFAULT_INGEST_URL).replace(/\/+$/, "");
|
|
532
|
+
const fetchFn = opts.fetchFn ?? fetch;
|
|
533
|
+
const home = opts.homeDir ?? homedir2();
|
|
534
|
+
const nodeRoot = findProjectRoot(cwd);
|
|
535
|
+
const pythonRoot = findPythonProjectRoot(cwd);
|
|
536
|
+
const goRoot = findGoProjectRoot(cwd);
|
|
537
|
+
const rubyRoot = findRubyProjectRoot(cwd);
|
|
538
|
+
if (!nodeRoot && !pythonRoot && !goRoot && !rubyRoot) {
|
|
539
|
+
throw new Error(
|
|
540
|
+
`No project found at ${cwd}: looked for package.json, pyproject.toml/setup.py/requirements.txt/Pipfile, go.mod, Gemfile/*.gemspec.`
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
const closestRoot = pickClosestRoot([nodeRoot, pythonRoot, goRoot, rubyRoot]);
|
|
544
|
+
if (closestRoot === goRoot && goRoot) {
|
|
545
|
+
return installGo({ projectRoot: goRoot, opts, ingestUrl, fetchFn, home });
|
|
546
|
+
}
|
|
547
|
+
if (closestRoot === rubyRoot && rubyRoot) {
|
|
548
|
+
return installRuby({ projectRoot: rubyRoot, opts, ingestUrl, fetchFn, home });
|
|
549
|
+
}
|
|
550
|
+
if (closestRoot === pythonRoot && pythonRoot) {
|
|
551
|
+
return installPython({ projectRoot: pythonRoot, opts, ingestUrl, fetchFn, home });
|
|
552
|
+
}
|
|
553
|
+
if (!nodeRoot) {
|
|
554
|
+
throw new Error("Internal: closest root is Node but nodeRoot is null");
|
|
555
|
+
}
|
|
556
|
+
const pkgPath = join2(nodeRoot, "package.json");
|
|
557
|
+
const pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
|
|
558
|
+
const projectName = opts.projectName ?? pkg.name ?? nodeRoot.split("/").pop() ?? "unnamed";
|
|
559
|
+
const kind = detectJsProjectKind(pkg, nodeRoot);
|
|
560
|
+
const projectsJsonPath = join2(home, ".gg", "projects.json");
|
|
561
|
+
const envFilePath = join2(nodeRoot, ".env");
|
|
562
|
+
const existing = findMappingByPath(projectsJsonPath, nodeRoot);
|
|
563
|
+
const existingKey = readEnvKey(envFilePath, "GG_PIXEL_KEY");
|
|
564
|
+
let created;
|
|
565
|
+
let reused = false;
|
|
566
|
+
if (existing && existing.secret && existingKey) {
|
|
567
|
+
created = { id: existing.id, key: existingKey, secret: existing.secret };
|
|
568
|
+
reused = true;
|
|
569
|
+
} else {
|
|
570
|
+
created = await createProject(fetchFn, ingestUrl, projectName);
|
|
571
|
+
}
|
|
572
|
+
const pm = detectPackageManager(nodeRoot);
|
|
573
|
+
const packageInstalled = opts.skipPackageInstall ? false : runInstall(nodeRoot, pm, "@iroaxel/gg-pixel");
|
|
574
|
+
const wired = wireFramework({
|
|
575
|
+
kind,
|
|
576
|
+
projectRoot: nodeRoot,
|
|
577
|
+
pkg,
|
|
578
|
+
projectKey: created.key,
|
|
579
|
+
ingestUrl
|
|
580
|
+
});
|
|
581
|
+
if (kind !== "browser" && kind !== "tauri") {
|
|
582
|
+
writeEnvKey(envFilePath, "GG_PIXEL_KEY", created.key);
|
|
583
|
+
}
|
|
584
|
+
writeProjectsMapping(projectsJsonPath, created.id, projectName, nodeRoot, created.secret);
|
|
585
|
+
return {
|
|
586
|
+
projectId: created.id,
|
|
587
|
+
projectKey: created.key,
|
|
588
|
+
projectSecret: created.secret,
|
|
589
|
+
projectName,
|
|
590
|
+
projectKind: kind,
|
|
591
|
+
projectRoot: nodeRoot,
|
|
592
|
+
initFilePath: wired.primaryInitPath,
|
|
593
|
+
envFilePath,
|
|
594
|
+
projectsJsonPath,
|
|
595
|
+
packageManager: pm,
|
|
596
|
+
packageInstalled,
|
|
597
|
+
entryWiring: wired.entryWiring,
|
|
598
|
+
reused,
|
|
599
|
+
secondaryInit: wired.secondaryInit,
|
|
600
|
+
warnings: wired.warnings
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
function findMappingByPath(projectsJsonPath, projectRoot) {
|
|
604
|
+
if (!existsSync2(projectsJsonPath)) return null;
|
|
605
|
+
let map;
|
|
606
|
+
try {
|
|
607
|
+
map = JSON.parse(readFileSync2(projectsJsonPath, "utf8"));
|
|
608
|
+
} catch {
|
|
609
|
+
return null;
|
|
610
|
+
}
|
|
611
|
+
let fallback = null;
|
|
612
|
+
for (const [id, entry] of Object.entries(map)) {
|
|
613
|
+
if (entry.path !== projectRoot) continue;
|
|
614
|
+
if (entry.secret) return { id, ...entry };
|
|
615
|
+
if (!fallback) fallback = { id, ...entry };
|
|
616
|
+
}
|
|
617
|
+
return fallback;
|
|
618
|
+
}
|
|
619
|
+
function readEnvKey(envPath, key) {
|
|
620
|
+
if (!existsSync2(envPath)) return null;
|
|
621
|
+
try {
|
|
622
|
+
const content = readFileSync2(envPath, "utf8");
|
|
623
|
+
const match = new RegExp(`^${key}=(.+)$`, "m").exec(content);
|
|
624
|
+
return match?.[1]?.trim() ?? null;
|
|
625
|
+
} catch {
|
|
626
|
+
return null;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
function findProjectRoot(start) {
|
|
630
|
+
let dir = start;
|
|
631
|
+
for (let i = 0; i < 20; i++) {
|
|
632
|
+
if (existsSync2(join2(dir, "package.json"))) return dir;
|
|
633
|
+
const parent = dirname2(dir);
|
|
634
|
+
if (parent === dir) return null;
|
|
635
|
+
dir = parent;
|
|
636
|
+
}
|
|
637
|
+
return null;
|
|
638
|
+
}
|
|
639
|
+
async function createProject(fetchFn, ingestUrl, name) {
|
|
640
|
+
const res = await fetchFn(`${ingestUrl}/api/projects`, {
|
|
641
|
+
method: "POST",
|
|
642
|
+
headers: { "content-type": "application/json" },
|
|
643
|
+
body: JSON.stringify({ name })
|
|
644
|
+
});
|
|
645
|
+
if (!res.ok) {
|
|
646
|
+
throw new Error(`POST /api/projects failed: ${res.status} ${await safeText(res)}`);
|
|
647
|
+
}
|
|
648
|
+
const body = await res.json();
|
|
649
|
+
if (!body.id || !body.key || !body.secret) {
|
|
650
|
+
throw new Error("response missing id/key/secret");
|
|
651
|
+
}
|
|
652
|
+
return { id: body.id, key: body.key, secret: body.secret };
|
|
653
|
+
}
|
|
654
|
+
async function safeText(r) {
|
|
655
|
+
try {
|
|
656
|
+
return await r.text();
|
|
657
|
+
} catch {
|
|
658
|
+
return "";
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
function detectPackageManager(projectRoot) {
|
|
662
|
+
if (existsSync2(join2(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
|
|
663
|
+
if (existsSync2(join2(projectRoot, "bun.lock")) || existsSync2(join2(projectRoot, "bun.lockb"))) {
|
|
664
|
+
return "bun";
|
|
665
|
+
}
|
|
666
|
+
if (existsSync2(join2(projectRoot, "yarn.lock"))) return "yarn";
|
|
667
|
+
return "npm";
|
|
668
|
+
}
|
|
669
|
+
function runInstall(projectRoot, pm, pkg) {
|
|
670
|
+
const cmd = pm;
|
|
671
|
+
const args = pm === "npm" ? ["install", pkg, "--no-audit", "--no-fund"] : ["add", pkg];
|
|
672
|
+
const result = spawnSync2(cmd, args, { cwd: projectRoot, stdio: "inherit" });
|
|
673
|
+
return result.status === 0;
|
|
674
|
+
}
|
|
675
|
+
function renderInitFile(ingestUrl, projectKey) {
|
|
676
|
+
const fallback = projectKey ? ` || ${JSON.stringify(projectKey)}` : "";
|
|
677
|
+
return `import { initPixel } from "@iroaxel/gg-pixel";
|
|
678
|
+
|
|
679
|
+
const key = process.env.GG_PIXEL_KEY${fallback};
|
|
680
|
+
if (key) {
|
|
681
|
+
initPixel({
|
|
682
|
+
projectKey: key,
|
|
683
|
+
sink: { kind: "http", ingestUrl: ${JSON.stringify(`${ingestUrl}/ingest`)} },
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
`;
|
|
687
|
+
}
|
|
688
|
+
function renderInitFileCjs(ingestUrl, projectKey) {
|
|
689
|
+
const fallback = projectKey ? ` || ${JSON.stringify(projectKey)}` : "";
|
|
690
|
+
return `const { initPixel } = require("@iroaxel/gg-pixel");
|
|
691
|
+
|
|
692
|
+
const key = process.env.GG_PIXEL_KEY${fallback};
|
|
693
|
+
if (key) {
|
|
694
|
+
initPixel({
|
|
695
|
+
projectKey: key,
|
|
696
|
+
sink: { kind: "http", ingestUrl: ${JSON.stringify(`${ingestUrl}/ingest`)} },
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
`;
|
|
700
|
+
}
|
|
701
|
+
function writeEnvKey(envPath, key, value) {
|
|
702
|
+
if (existsSync2(envPath)) {
|
|
703
|
+
const current = readFileSync2(envPath, "utf8");
|
|
704
|
+
const lineRegex = new RegExp(`^${key}=.*$`, "m");
|
|
705
|
+
if (lineRegex.test(current)) {
|
|
706
|
+
writeFileSync(envPath, current.replace(lineRegex, `${key}=${value}`), "utf8");
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
const sep2 = current.endsWith("\n") || current.length === 0 ? "" : "\n";
|
|
710
|
+
appendFileSync(envPath, `${sep2}${key}=${value}
|
|
711
|
+
`, "utf8");
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
writeFileSync(envPath, `${key}=${value}
|
|
715
|
+
`, "utf8");
|
|
716
|
+
}
|
|
717
|
+
function wireEntryFile(projectRoot, initFilePath, pkg) {
|
|
718
|
+
const entryPath = findEntryFile(projectRoot, pkg);
|
|
719
|
+
if (!entryPath) return { kind: "no_entry_found" };
|
|
720
|
+
let content;
|
|
721
|
+
try {
|
|
722
|
+
content = readFileSync2(entryPath, "utf8");
|
|
723
|
+
} catch (err) {
|
|
724
|
+
return { kind: "skipped", reason: `unreadable: ${err.message}` };
|
|
725
|
+
}
|
|
726
|
+
if (content.includes("gg-pixel.init")) {
|
|
727
|
+
return { kind: "already_present", entryPath };
|
|
728
|
+
}
|
|
729
|
+
const fromDir = dirname2(entryPath);
|
|
730
|
+
let spec = relative(fromDir, initFilePath).split(sep).join("/");
|
|
731
|
+
if (!spec.startsWith(".")) spec = "./" + spec;
|
|
732
|
+
const isCjs = isCommonJsEntry(entryPath, pkg);
|
|
733
|
+
const importLine = isCjs ? `require(${JSON.stringify(spec)});` : `import ${JSON.stringify(spec)};`;
|
|
734
|
+
const lines = content.split("\n");
|
|
735
|
+
let insertAt = 0;
|
|
736
|
+
if (lines[0]?.startsWith("#!")) insertAt = 1;
|
|
737
|
+
while (insertAt < lines.length && /^\s*(?:["']use strict["']|\/\/|\/\*)/.test(lines[insertAt] ?? "")) {
|
|
738
|
+
insertAt++;
|
|
739
|
+
}
|
|
740
|
+
const updated = [...lines.slice(0, insertAt), importLine, ...lines.slice(insertAt)].join("\n");
|
|
741
|
+
writeFileSync(entryPath, updated, "utf8");
|
|
742
|
+
return { kind: "injected", entryPath };
|
|
743
|
+
}
|
|
744
|
+
function findEntryFile(projectRoot, pkg) {
|
|
745
|
+
const tryPath = (rel) => {
|
|
746
|
+
const p = join2(projectRoot, rel);
|
|
747
|
+
if (existsSync2(p)) return p;
|
|
748
|
+
if (rel.endsWith(".js")) {
|
|
749
|
+
const ts = join2(projectRoot, rel.replace(/\.js$/, ".ts"));
|
|
750
|
+
if (existsSync2(ts)) return ts;
|
|
751
|
+
}
|
|
752
|
+
return null;
|
|
753
|
+
};
|
|
754
|
+
if (typeof pkg.bin === "string") {
|
|
755
|
+
const found = tryPath(pkg.bin);
|
|
756
|
+
if (found) return found;
|
|
757
|
+
}
|
|
758
|
+
if (pkg.bin && typeof pkg.bin === "object") {
|
|
759
|
+
for (const value of Object.values(pkg.bin)) {
|
|
760
|
+
if (typeof value === "string") {
|
|
761
|
+
const found = tryPath(value);
|
|
762
|
+
if (found) return found;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
if (pkg.main) {
|
|
767
|
+
const found = tryPath(pkg.main);
|
|
768
|
+
if (found) return found;
|
|
769
|
+
}
|
|
770
|
+
if (pkg.module) {
|
|
771
|
+
const found = tryPath(pkg.module);
|
|
772
|
+
if (found) return found;
|
|
773
|
+
}
|
|
774
|
+
const candidates = [
|
|
775
|
+
"src/index.ts",
|
|
776
|
+
"src/index.tsx",
|
|
777
|
+
"src/index.js",
|
|
778
|
+
"src/index.mjs",
|
|
779
|
+
"src/main.ts",
|
|
780
|
+
"src/main.tsx",
|
|
781
|
+
"src/main.js",
|
|
782
|
+
"src/server.ts",
|
|
783
|
+
"src/server.js",
|
|
784
|
+
"src/app.ts",
|
|
785
|
+
"src/app.js",
|
|
786
|
+
"src/cli.ts",
|
|
787
|
+
"src/cli.js",
|
|
788
|
+
"index.ts",
|
|
789
|
+
"index.tsx",
|
|
790
|
+
"index.js",
|
|
791
|
+
"index.mjs",
|
|
792
|
+
"main.ts",
|
|
793
|
+
"main.js",
|
|
794
|
+
"server.ts",
|
|
795
|
+
"server.js",
|
|
796
|
+
"app.ts",
|
|
797
|
+
"app.js"
|
|
798
|
+
];
|
|
799
|
+
for (const c of candidates) {
|
|
800
|
+
const found = tryPath(c);
|
|
801
|
+
if (found) return found;
|
|
802
|
+
}
|
|
803
|
+
return null;
|
|
804
|
+
}
|
|
805
|
+
function isCommonJsEntry(entryPath, pkg) {
|
|
806
|
+
if (entryPath.endsWith(".cjs")) return true;
|
|
807
|
+
if (entryPath.endsWith(".mjs")) return false;
|
|
808
|
+
if (entryPath.endsWith(".ts") || entryPath.endsWith(".tsx")) return false;
|
|
809
|
+
return pkg.type !== "module";
|
|
810
|
+
}
|
|
811
|
+
function detectJsProjectKind(pkg, projectRoot) {
|
|
812
|
+
const all = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
813
|
+
if (existsSync2(join2(projectRoot, "wrangler.toml")) || existsSync2(join2(projectRoot, "wrangler.jsonc")) || existsSync2(join2(projectRoot, "wrangler.json"))) {
|
|
814
|
+
return "cloudflare-workers";
|
|
815
|
+
}
|
|
816
|
+
if ("electron" in all) return "electron";
|
|
817
|
+
if (existsSync2(join2(projectRoot, "src-tauri")) || "@tauri-apps/api" in all) return "tauri";
|
|
818
|
+
if ("react-native" in all) return "react-native";
|
|
819
|
+
if ("next" in all) return "nextjs";
|
|
820
|
+
if ("@sveltejs/kit" in all) return "sveltekit";
|
|
821
|
+
if ("nuxt" in all || "nuxt3" in all) return "nuxt";
|
|
822
|
+
if ("@remix-run/react" in all || "@remix-run/node" in all) return "remix";
|
|
823
|
+
if (isBrowserProject(pkg, projectRoot)) return "browser";
|
|
824
|
+
return "node";
|
|
825
|
+
}
|
|
826
|
+
function wireFramework(w) {
|
|
827
|
+
switch (w.kind) {
|
|
828
|
+
case "node":
|
|
829
|
+
return wireNode(w);
|
|
830
|
+
case "browser":
|
|
831
|
+
return wireBrowser(w);
|
|
832
|
+
case "nextjs":
|
|
833
|
+
return wireNextjs(w);
|
|
834
|
+
case "sveltekit":
|
|
835
|
+
return wireSveltekit(w);
|
|
836
|
+
case "nuxt":
|
|
837
|
+
return wireNuxt(w);
|
|
838
|
+
case "remix":
|
|
839
|
+
return wireRemix(w);
|
|
840
|
+
case "electron":
|
|
841
|
+
return wireElectron(w);
|
|
842
|
+
case "tauri":
|
|
843
|
+
return wireTauri(w);
|
|
844
|
+
case "react-native":
|
|
845
|
+
return wireReactNative(w);
|
|
846
|
+
case "cloudflare-workers":
|
|
847
|
+
return wireWorkers(w);
|
|
848
|
+
case "python":
|
|
849
|
+
case "go":
|
|
850
|
+
case "ruby":
|
|
851
|
+
throw new Error(`Internal: ${w.kind} should have been handled earlier`);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
function wireNode({ projectRoot, pkg, projectKey, ingestUrl }) {
|
|
855
|
+
const initPath = join2(projectRoot, "gg-pixel.init.mjs");
|
|
856
|
+
writeFileSync(initPath, renderInitFile(ingestUrl, projectKey), "utf8");
|
|
857
|
+
return {
|
|
858
|
+
primaryInitPath: initPath,
|
|
859
|
+
entryWiring: wireEntryFile(projectRoot, initPath, pkg),
|
|
860
|
+
warnings: []
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
function wireBrowser({ projectRoot, pkg, projectKey, ingestUrl }) {
|
|
864
|
+
const initPath = join2(projectRoot, "gg-pixel.init.mjs");
|
|
865
|
+
writeFileSync(initPath, renderBrowserInitFile(ingestUrl, projectKey), "utf8");
|
|
866
|
+
return {
|
|
867
|
+
primaryInitPath: initPath,
|
|
868
|
+
entryWiring: wireEntryFile(projectRoot, initPath, pkg),
|
|
869
|
+
warnings: []
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
function wireNextjs({ projectRoot, projectKey, ingestUrl }) {
|
|
873
|
+
const warnings = [];
|
|
874
|
+
const serverInitPath = pickPath(projectRoot, ["instrumentation.ts", "instrumentation.js"]);
|
|
875
|
+
const finalServerPath = serverInitPath ?? join2(projectRoot, "instrumentation.ts");
|
|
876
|
+
writeNextInstrumentation(finalServerPath, ingestUrl, projectKey);
|
|
877
|
+
patchNextConfig(projectRoot);
|
|
878
|
+
const clientInitPath = join2(projectRoot, "gg-pixel.client.tsx");
|
|
879
|
+
writeFileSync(clientInitPath, renderNextClientComponent(ingestUrl, projectKey), "utf8");
|
|
880
|
+
const layoutPath = findNextLayout(projectRoot);
|
|
881
|
+
let entryWiring;
|
|
882
|
+
if (!layoutPath) {
|
|
883
|
+
warnings.push(
|
|
884
|
+
'Could not auto-wire the Next.js client init \u2014 no app/layout.{tsx,jsx} or pages/_app.{tsx,jsx} found. Add `<GGPixelClient />` from "./gg-pixel.client" to your root layout/_app.'
|
|
885
|
+
);
|
|
886
|
+
entryWiring = { kind: "no_entry_found" };
|
|
887
|
+
} else {
|
|
888
|
+
entryWiring = injectNextClientComponent(layoutPath, clientInitPath);
|
|
889
|
+
}
|
|
890
|
+
return {
|
|
891
|
+
primaryInitPath: clientInitPath,
|
|
892
|
+
entryWiring,
|
|
893
|
+
secondaryInit: {
|
|
894
|
+
path: finalServerPath,
|
|
895
|
+
description: "Next.js server instrumentation (auto-loaded by Next runtime)"
|
|
896
|
+
},
|
|
897
|
+
warnings
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
function writeNextInstrumentation(path, ingestUrl, projectKey) {
|
|
901
|
+
const existing = existsSync2(path) ? readFileSync2(path, "utf8") : "";
|
|
902
|
+
const cleaned = stripLegacyPixelContent(existing);
|
|
903
|
+
const block = nextInstrumentationBlock(ingestUrl, projectKey);
|
|
904
|
+
const next = upsertPixelBlock(cleaned, block);
|
|
905
|
+
if (next !== existing) writeFileSync(path, next, "utf8");
|
|
906
|
+
}
|
|
907
|
+
function nextInstrumentationBlock(ingestUrl, projectKey) {
|
|
908
|
+
const fallback = projectKey ? ` ?? ${JSON.stringify(projectKey)}` : "";
|
|
909
|
+
return `// Next.js auto-loads this file on server start. Pixel hooks the
|
|
910
|
+
// uncaughtExceptionMonitor + unhandledRejection events for API routes,
|
|
911
|
+
// Server Components, and route handlers.
|
|
912
|
+
export async function register() {
|
|
913
|
+
if (process.env.NEXT_RUNTIME === "nodejs") {
|
|
914
|
+
const { initPixel } = await import("@iroaxel/gg-pixel");
|
|
915
|
+
initPixel({
|
|
916
|
+
projectKey: process.env.GG_PIXEL_KEY${fallback},
|
|
917
|
+
sink: { kind: "http", ingestUrl: ${JSON.stringify(`${ingestUrl}/ingest`)} },
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
}`;
|
|
921
|
+
}
|
|
922
|
+
function findNextLayout(projectRoot) {
|
|
923
|
+
const candidates = [
|
|
924
|
+
"app/layout.tsx",
|
|
925
|
+
"app/layout.jsx",
|
|
926
|
+
"app/layout.ts",
|
|
927
|
+
"src/app/layout.tsx",
|
|
928
|
+
"src/app/layout.jsx",
|
|
929
|
+
"pages/_app.tsx",
|
|
930
|
+
"pages/_app.jsx",
|
|
931
|
+
"src/pages/_app.tsx",
|
|
932
|
+
"src/pages/_app.jsx"
|
|
933
|
+
];
|
|
934
|
+
for (const c of candidates) {
|
|
935
|
+
const p = join2(projectRoot, c);
|
|
936
|
+
if (existsSync2(p)) return p;
|
|
937
|
+
}
|
|
938
|
+
return null;
|
|
939
|
+
}
|
|
940
|
+
function renderNextClientComponent(ingestUrl, projectKey) {
|
|
941
|
+
return `"use client";
|
|
942
|
+
// Client-only pixel init. Rendered from the root layout. The "use client"
|
|
943
|
+
// directive guarantees this module never executes during server-side
|
|
944
|
+
// rendering \u2014 \`window.onerror\` references would otherwise crash builds.
|
|
945
|
+
import { useEffect } from "react";
|
|
946
|
+
import { initPixel } from "@iroaxel/gg-pixel/browser";
|
|
947
|
+
|
|
948
|
+
let inited = false;
|
|
949
|
+
|
|
950
|
+
export default function GGPixelClient() {
|
|
951
|
+
useEffect(() => {
|
|
952
|
+
if (inited) return;
|
|
953
|
+
inited = true;
|
|
954
|
+
initPixel({
|
|
955
|
+
projectKey: ${JSON.stringify(projectKey)},
|
|
956
|
+
ingestUrl: ${JSON.stringify(ingestUrl)},
|
|
957
|
+
});
|
|
958
|
+
}, []);
|
|
959
|
+
return null;
|
|
960
|
+
}
|
|
961
|
+
`;
|
|
962
|
+
}
|
|
963
|
+
function injectNextClientComponent(layoutPath, clientInitPath) {
|
|
964
|
+
let content;
|
|
965
|
+
try {
|
|
966
|
+
content = readFileSync2(layoutPath, "utf8");
|
|
967
|
+
} catch (err) {
|
|
968
|
+
return { kind: "skipped", reason: `unreadable: ${err.message}` };
|
|
969
|
+
}
|
|
970
|
+
if (content.includes("GGPixelClient") || content.includes("@iroaxel/gg-pixel")) {
|
|
971
|
+
return { kind: "already_present", entryPath: layoutPath };
|
|
972
|
+
}
|
|
973
|
+
const fromDir = dirname2(layoutPath);
|
|
974
|
+
let spec = relative(fromDir, clientInitPath).split(sep).join("/");
|
|
975
|
+
if (!spec.startsWith(".")) spec = "./" + spec;
|
|
976
|
+
spec = spec.replace(/\.tsx$/, "");
|
|
977
|
+
const astResult = injectClientComponentViaAst(layoutPath, content, spec);
|
|
978
|
+
if (astResult) return astResult;
|
|
979
|
+
return injectClientComponentViaRegex(layoutPath, content, spec);
|
|
980
|
+
}
|
|
981
|
+
function injectClientComponentViaAst(layoutPath, content, importSpec) {
|
|
982
|
+
let recast;
|
|
983
|
+
let bp;
|
|
984
|
+
let bt;
|
|
985
|
+
try {
|
|
986
|
+
recast = nodeRequire("recast");
|
|
987
|
+
bp = nodeRequire("@babel/parser");
|
|
988
|
+
bt = nodeRequire("@babel/types");
|
|
989
|
+
} catch {
|
|
990
|
+
return null;
|
|
991
|
+
}
|
|
992
|
+
let ast;
|
|
993
|
+
try {
|
|
994
|
+
ast = recast.parse(content, {
|
|
995
|
+
parser: {
|
|
996
|
+
parse: (src) => bp.parse(src, {
|
|
997
|
+
sourceType: "module",
|
|
998
|
+
plugins: ["jsx", "typescript"],
|
|
999
|
+
allowImportExportEverywhere: true,
|
|
1000
|
+
tokens: true
|
|
1001
|
+
})
|
|
1002
|
+
}
|
|
1003
|
+
});
|
|
1004
|
+
} catch {
|
|
1005
|
+
return null;
|
|
1006
|
+
}
|
|
1007
|
+
const program = ast.program;
|
|
1008
|
+
if (!program || !Array.isArray(program.body)) return null;
|
|
1009
|
+
const bodyEl = findFirstJsxElementByName(ast, "body");
|
|
1010
|
+
if (!bodyEl) return null;
|
|
1011
|
+
const newComponent = bt.jsxElement(
|
|
1012
|
+
bt.jsxOpeningElement(bt.jsxIdentifier("GGPixelClient"), [], true),
|
|
1013
|
+
null,
|
|
1014
|
+
[],
|
|
1015
|
+
true
|
|
1016
|
+
);
|
|
1017
|
+
const leadingText = bt.jsxText("\n ");
|
|
1018
|
+
const trailingText = bt.jsxText("\n ");
|
|
1019
|
+
bodyEl.children = [leadingText, newComponent, trailingText, ...bodyEl.children];
|
|
1020
|
+
const importDecl = bt.importDeclaration(
|
|
1021
|
+
[bt.importDefaultSpecifier(bt.identifier("GGPixelClient"))],
|
|
1022
|
+
bt.stringLiteral(importSpec)
|
|
1023
|
+
);
|
|
1024
|
+
const body = program.body;
|
|
1025
|
+
let insertAt = 0;
|
|
1026
|
+
for (let i = 0; i < body.length; i++) {
|
|
1027
|
+
if (body[i]?.type === "ImportDeclaration") insertAt = i + 1;
|
|
1028
|
+
}
|
|
1029
|
+
body.splice(insertAt, 0, importDecl);
|
|
1030
|
+
let out;
|
|
1031
|
+
try {
|
|
1032
|
+
out = recast.print(ast).code;
|
|
1033
|
+
} catch {
|
|
1034
|
+
return null;
|
|
1035
|
+
}
|
|
1036
|
+
writeFileSync(layoutPath, out, "utf8");
|
|
1037
|
+
return { kind: "injected", entryPath: layoutPath };
|
|
1038
|
+
}
|
|
1039
|
+
function findFirstJsxElementByName(ast, name) {
|
|
1040
|
+
let found = null;
|
|
1041
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
1042
|
+
const MAX_DEPTH = 500;
|
|
1043
|
+
const SKIP_KEYS = /* @__PURE__ */ new Set([
|
|
1044
|
+
"loc",
|
|
1045
|
+
"tokens",
|
|
1046
|
+
"original",
|
|
1047
|
+
"comments",
|
|
1048
|
+
"leadingComments",
|
|
1049
|
+
"trailingComments",
|
|
1050
|
+
"innerComments",
|
|
1051
|
+
"range",
|
|
1052
|
+
"start",
|
|
1053
|
+
"end"
|
|
1054
|
+
]);
|
|
1055
|
+
function walk(node, depth) {
|
|
1056
|
+
if (found || !node || typeof node !== "object") return;
|
|
1057
|
+
if (depth > MAX_DEPTH) return;
|
|
1058
|
+
if (seen.has(node)) return;
|
|
1059
|
+
seen.add(node);
|
|
1060
|
+
if (Array.isArray(node)) {
|
|
1061
|
+
for (const c of node) walk(c, depth + 1);
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
const n = node;
|
|
1065
|
+
if (n.type === "JSXElement") {
|
|
1066
|
+
const opening = n.openingElement;
|
|
1067
|
+
if (opening?.type === "JSXOpeningElement" && opening.name) {
|
|
1068
|
+
const namedNode = opening.name;
|
|
1069
|
+
if (namedNode.type === "JSXIdentifier" && namedNode.name === name) {
|
|
1070
|
+
found = n;
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
for (const key of Object.keys(n)) {
|
|
1076
|
+
if (SKIP_KEYS.has(key)) continue;
|
|
1077
|
+
walk(n[key], depth + 1);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
walk(ast, 0);
|
|
1081
|
+
return found;
|
|
1082
|
+
}
|
|
1083
|
+
function injectClientComponentViaRegex(layoutPath, content, spec) {
|
|
1084
|
+
const importLine = `import GGPixelClient from ${JSON.stringify(spec)};`;
|
|
1085
|
+
const lines = content.split("\n");
|
|
1086
|
+
let insertImportAt = 0;
|
|
1087
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1088
|
+
if (/^\s*import\s/.test(lines[i] ?? "")) insertImportAt = i + 1;
|
|
1089
|
+
}
|
|
1090
|
+
lines.splice(insertImportAt, 0, importLine);
|
|
1091
|
+
const updated = lines.join("\n");
|
|
1092
|
+
const childrenIdx = updated.lastIndexOf("{children}");
|
|
1093
|
+
if (childrenIdx === -1) {
|
|
1094
|
+
writeFileSync(layoutPath, updated, "utf8");
|
|
1095
|
+
return {
|
|
1096
|
+
kind: "skipped",
|
|
1097
|
+
reason: "added import but couldn't find {children} to render <GGPixelClient />"
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
const before = updated.slice(0, childrenIdx);
|
|
1101
|
+
const after = updated.slice(childrenIdx);
|
|
1102
|
+
const finalContent = before + "<GGPixelClient />\n " + after;
|
|
1103
|
+
writeFileSync(layoutPath, finalContent, "utf8");
|
|
1104
|
+
return { kind: "injected", entryPath: layoutPath };
|
|
1105
|
+
}
|
|
1106
|
+
function patchNextConfig(projectRoot) {
|
|
1107
|
+
const candidates = ["next.config.ts", "next.config.mjs", "next.config.js", "next.config.cjs"];
|
|
1108
|
+
let configPath = null;
|
|
1109
|
+
for (const c of candidates) {
|
|
1110
|
+
const p = join2(projectRoot, c);
|
|
1111
|
+
if (existsSync2(p)) {
|
|
1112
|
+
configPath = p;
|
|
1113
|
+
break;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
if (!configPath) {
|
|
1117
|
+
configPath = join2(projectRoot, "next.config.ts");
|
|
1118
|
+
writeFileSync(
|
|
1119
|
+
configPath,
|
|
1120
|
+
`import type { NextConfig } from "next";
|
|
1121
|
+
|
|
1122
|
+
const nextConfig: NextConfig = {
|
|
1123
|
+
// Keeps Next's bundler from trying to compile better-sqlite3 (native dep).
|
|
1124
|
+
serverExternalPackages: ["@iroaxel/gg-pixel"],
|
|
1125
|
+
};
|
|
1126
|
+
|
|
1127
|
+
export default nextConfig;
|
|
1128
|
+
`,
|
|
1129
|
+
"utf8"
|
|
1130
|
+
);
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
patchNextConfigViaAst(configPath);
|
|
1134
|
+
}
|
|
1135
|
+
var PIXEL_PKG = "@iroaxel/gg-pixel";
|
|
1136
|
+
function patchNextConfigViaAst(configPath) {
|
|
1137
|
+
const original = readFileSync2(configPath, "utf8");
|
|
1138
|
+
if (original.includes(PIXEL_PKG)) return;
|
|
1139
|
+
let parseModule;
|
|
1140
|
+
let generateCode;
|
|
1141
|
+
try {
|
|
1142
|
+
const mod = nodeRequire("magicast");
|
|
1143
|
+
parseModule = mod.parseModule;
|
|
1144
|
+
generateCode = mod.generateCode;
|
|
1145
|
+
} catch {
|
|
1146
|
+
return patchNextConfigViaRegex(configPath, original);
|
|
1147
|
+
}
|
|
1148
|
+
let module_;
|
|
1149
|
+
try {
|
|
1150
|
+
module_ = parseModule(original);
|
|
1151
|
+
} catch {
|
|
1152
|
+
return patchNextConfigViaRegex(configPath, original);
|
|
1153
|
+
}
|
|
1154
|
+
const cfg = resolveNextConfigObject(module_);
|
|
1155
|
+
if (!cfg) {
|
|
1156
|
+
return patchNextConfigViaRegex(configPath, original);
|
|
1157
|
+
}
|
|
1158
|
+
const existing = cfg.serverExternalPackages;
|
|
1159
|
+
const isArrayLike = Array.isArray(existing) || typeof existing === "object" && existing !== null && existing.$type === "array";
|
|
1160
|
+
if (isArrayLike) {
|
|
1161
|
+
const arr = existing;
|
|
1162
|
+
if (arr.includes(PIXEL_PKG)) return;
|
|
1163
|
+
arr.push(PIXEL_PKG);
|
|
1164
|
+
} else {
|
|
1165
|
+
cfg.serverExternalPackages = [PIXEL_PKG];
|
|
1166
|
+
}
|
|
1167
|
+
const out = generateCode(module_).code;
|
|
1168
|
+
if (out !== original) writeFileSync(configPath, out, "utf8");
|
|
1169
|
+
}
|
|
1170
|
+
function resolveNextConfigObject(mod) {
|
|
1171
|
+
const root = mod.exports.default ?? mod.exports;
|
|
1172
|
+
if (!root) return null;
|
|
1173
|
+
return unwrapWrappers(root);
|
|
1174
|
+
}
|
|
1175
|
+
function unwrapWrappers(node) {
|
|
1176
|
+
let cur = node;
|
|
1177
|
+
for (let i = 0; i < 6; i++) {
|
|
1178
|
+
if (cur.$type === "function-call") {
|
|
1179
|
+
const args = cur.$args ?? [];
|
|
1180
|
+
const objArg = args.find(
|
|
1181
|
+
(a) => typeof a === "object" && a !== null && a.$type !== "function-call"
|
|
1182
|
+
);
|
|
1183
|
+
if (!objArg) return null;
|
|
1184
|
+
cur = objArg;
|
|
1185
|
+
continue;
|
|
1186
|
+
}
|
|
1187
|
+
if (cur.$type === "object" || cur.$type === void 0) return cur;
|
|
1188
|
+
return null;
|
|
1189
|
+
}
|
|
1190
|
+
return null;
|
|
1191
|
+
}
|
|
1192
|
+
function patchNextConfigViaRegex(configPath, content) {
|
|
1193
|
+
if (content.includes(PIXEL_PKG)) return;
|
|
1194
|
+
if (content.includes("serverExternalPackages")) {
|
|
1195
|
+
const updated = content.replace(
|
|
1196
|
+
/serverExternalPackages\s*:\s*\[([^\]]*)\]/,
|
|
1197
|
+
(_match, inside) => {
|
|
1198
|
+
const trimmed = inside.trim();
|
|
1199
|
+
const sep2 = trimmed.length > 0 ? ", " : "";
|
|
1200
|
+
return `serverExternalPackages: [${trimmed}${sep2}${JSON.stringify(PIXEL_PKG)}]`;
|
|
1201
|
+
}
|
|
1202
|
+
);
|
|
1203
|
+
if (updated !== content) writeFileSync(configPath, updated, "utf8");
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
const objStart = /(const\s+\w+\s*(?::\s*\w+)?\s*=\s*\{|module\.exports\s*=\s*\{|export\s+default\s*\{)/;
|
|
1207
|
+
const m = objStart.exec(content);
|
|
1208
|
+
if (m) {
|
|
1209
|
+
const insertAt = m.index + m[0].length;
|
|
1210
|
+
const updated = content.slice(0, insertAt) + `
|
|
1211
|
+
serverExternalPackages: [${JSON.stringify(PIXEL_PKG)}],` + content.slice(insertAt);
|
|
1212
|
+
writeFileSync(configPath, updated, "utf8");
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
function wireSveltekit({ projectRoot, projectKey, ingestUrl }) {
|
|
1216
|
+
const serverPath = join2(projectRoot, "src/hooks.server.ts");
|
|
1217
|
+
const clientPath = join2(projectRoot, "src/hooks.client.ts");
|
|
1218
|
+
if (!existsSync2(dirname2(serverPath))) mkdirSync2(dirname2(serverPath), { recursive: true });
|
|
1219
|
+
upsertPixelBlockInFile(
|
|
1220
|
+
serverPath,
|
|
1221
|
+
`import { initPixel } from "@iroaxel/gg-pixel";
|
|
1222
|
+
initPixel({
|
|
1223
|
+
projectKey: process.env.GG_PIXEL_KEY ?? ${JSON.stringify(projectKey)},
|
|
1224
|
+
sink: { kind: "http", ingestUrl: ${JSON.stringify(`${ingestUrl}/ingest`)} },
|
|
1225
|
+
});`
|
|
1226
|
+
);
|
|
1227
|
+
upsertPixelBlockInFile(
|
|
1228
|
+
clientPath,
|
|
1229
|
+
`import { initPixel } from "@iroaxel/gg-pixel/browser";
|
|
1230
|
+
initPixel({
|
|
1231
|
+
projectKey: ${JSON.stringify(projectKey)},
|
|
1232
|
+
ingestUrl: ${JSON.stringify(ingestUrl)},
|
|
1233
|
+
});`
|
|
1234
|
+
);
|
|
1235
|
+
return {
|
|
1236
|
+
primaryInitPath: clientPath,
|
|
1237
|
+
entryWiring: { kind: "injected", entryPath: clientPath },
|
|
1238
|
+
secondaryInit: {
|
|
1239
|
+
path: serverPath,
|
|
1240
|
+
description: "SvelteKit server hooks (auto-loaded)"
|
|
1241
|
+
},
|
|
1242
|
+
warnings: []
|
|
1243
|
+
};
|
|
1244
|
+
}
|
|
1245
|
+
function wireNuxt({ projectRoot, projectKey, ingestUrl }) {
|
|
1246
|
+
const pluginsDir = join2(projectRoot, "plugins");
|
|
1247
|
+
mkdirSync2(pluginsDir, { recursive: true });
|
|
1248
|
+
const serverPath = join2(pluginsDir, "gg-pixel.server.ts");
|
|
1249
|
+
const clientPath = join2(pluginsDir, "gg-pixel.client.ts");
|
|
1250
|
+
writeFileSync(
|
|
1251
|
+
serverPath,
|
|
1252
|
+
`import { initPixel } from "@iroaxel/gg-pixel";
|
|
1253
|
+
export default defineNuxtPlugin(() => {
|
|
1254
|
+
initPixel({
|
|
1255
|
+
projectKey: process.env.GG_PIXEL_KEY ?? ${JSON.stringify(projectKey)},
|
|
1256
|
+
sink: { kind: "http", ingestUrl: ${JSON.stringify(`${ingestUrl}/ingest`)} },
|
|
1257
|
+
});
|
|
1258
|
+
});
|
|
1259
|
+
`,
|
|
1260
|
+
"utf8"
|
|
1261
|
+
);
|
|
1262
|
+
writeFileSync(
|
|
1263
|
+
clientPath,
|
|
1264
|
+
`import { initPixel } from "@iroaxel/gg-pixel/browser";
|
|
1265
|
+
export default defineNuxtPlugin(() => {
|
|
1266
|
+
initPixel({
|
|
1267
|
+
projectKey: ${JSON.stringify(projectKey)},
|
|
1268
|
+
ingestUrl: ${JSON.stringify(ingestUrl)},
|
|
1269
|
+
});
|
|
1270
|
+
});
|
|
1271
|
+
`,
|
|
1272
|
+
"utf8"
|
|
1273
|
+
);
|
|
1274
|
+
return {
|
|
1275
|
+
primaryInitPath: clientPath,
|
|
1276
|
+
entryWiring: { kind: "injected", entryPath: clientPath },
|
|
1277
|
+
secondaryInit: { path: serverPath, description: "Nuxt server plugin (auto-loaded)" },
|
|
1278
|
+
warnings: []
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
function wireRemix({ projectRoot, projectKey, ingestUrl }) {
|
|
1282
|
+
const serverPath = pickPath(projectRoot, ["app/entry.server.tsx", "app/entry.server.jsx"]);
|
|
1283
|
+
const clientPath = pickPath(projectRoot, ["app/entry.client.tsx", "app/entry.client.jsx"]);
|
|
1284
|
+
const warnings = [];
|
|
1285
|
+
const clientInitPath = join2(projectRoot, "gg-pixel.client.mjs");
|
|
1286
|
+
writeFileSync(clientInitPath, renderBrowserInitFile(ingestUrl, projectKey), "utf8");
|
|
1287
|
+
if (clientPath) {
|
|
1288
|
+
injectImport(clientPath, clientInitPath);
|
|
1289
|
+
} else {
|
|
1290
|
+
warnings.push(
|
|
1291
|
+
"No app/entry.client.tsx found. Run `npx remix reveal` then re-run pixel install."
|
|
1292
|
+
);
|
|
1293
|
+
}
|
|
1294
|
+
const serverInitPath = join2(projectRoot, "gg-pixel.server.mjs");
|
|
1295
|
+
writeFileSync(serverInitPath, renderInitFile(ingestUrl), "utf8");
|
|
1296
|
+
let serverEntry = { kind: "no_entry_found" };
|
|
1297
|
+
if (serverPath) {
|
|
1298
|
+
serverEntry = injectImport(serverPath, serverInitPath);
|
|
1299
|
+
} else {
|
|
1300
|
+
warnings.push(
|
|
1301
|
+
"No app/entry.server.tsx found. Run `npx remix reveal` then re-run pixel install."
|
|
1302
|
+
);
|
|
1303
|
+
}
|
|
1304
|
+
void serverEntry;
|
|
1305
|
+
return {
|
|
1306
|
+
primaryInitPath: clientInitPath,
|
|
1307
|
+
entryWiring: clientPath ? { kind: "injected", entryPath: clientPath } : { kind: "no_entry_found" },
|
|
1308
|
+
secondaryInit: { path: serverInitPath, description: "Remix server init" },
|
|
1309
|
+
warnings
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
function wireElectron({ projectRoot, pkg, projectKey, ingestUrl }) {
|
|
1313
|
+
const warnings = [];
|
|
1314
|
+
const isMainEsm = pkg.type === "module";
|
|
1315
|
+
const mainInitPath = isMainEsm ? join2(projectRoot, "gg-pixel.main.mjs") : join2(projectRoot, "gg-pixel.main.cjs");
|
|
1316
|
+
writeFileSync(
|
|
1317
|
+
mainInitPath,
|
|
1318
|
+
isMainEsm ? renderInitFile(ingestUrl, projectKey) : renderInitFileCjs(ingestUrl, projectKey),
|
|
1319
|
+
"utf8"
|
|
1320
|
+
);
|
|
1321
|
+
const mainEntry = resolveMainEntryFromPkg(projectRoot, pkg);
|
|
1322
|
+
let mainWiring = { kind: "no_entry_found" };
|
|
1323
|
+
if (mainEntry && existsSync2(mainEntry)) {
|
|
1324
|
+
mainWiring = injectImport(mainEntry, mainInitPath);
|
|
1325
|
+
}
|
|
1326
|
+
const htmlFiles = findRendererHtmlFiles(projectRoot);
|
|
1327
|
+
let rendererInitPath;
|
|
1328
|
+
if (htmlFiles.length > 0) {
|
|
1329
|
+
const rendererDir = dirname2(htmlFiles[0]);
|
|
1330
|
+
rendererInitPath = join2(rendererDir, "gg-pixel.browser.iife.js");
|
|
1331
|
+
if (!copyIifeBundle(projectRoot, rendererInitPath)) {
|
|
1332
|
+
warnings.push(
|
|
1333
|
+
"Could not copy gg-pixel browser IIFE bundle \u2014 install @iroaxel/gg-pixel and re-run."
|
|
1334
|
+
);
|
|
1335
|
+
}
|
|
1336
|
+
let wiredAny = false;
|
|
1337
|
+
for (const html of htmlFiles) {
|
|
1338
|
+
const r = patchRendererHtml(html, rendererInitPath, projectKey, ingestUrl);
|
|
1339
|
+
if (r === "patched" || r === "already") wiredAny = true;
|
|
1340
|
+
}
|
|
1341
|
+
if (!wiredAny) {
|
|
1342
|
+
warnings.push(
|
|
1343
|
+
`Found HTML files in ${rendererDir} but couldn't patch any \u2014 they may have unusual CSP or no <head>.`
|
|
1344
|
+
);
|
|
1345
|
+
}
|
|
1346
|
+
} else {
|
|
1347
|
+
rendererInitPath = join2(projectRoot, "gg-pixel.renderer.mjs");
|
|
1348
|
+
writeFileSync(rendererInitPath, renderBrowserInitFile(ingestUrl, projectKey), "utf8");
|
|
1349
|
+
const rendererEntry = pickPath(projectRoot, [
|
|
1350
|
+
"src/renderer/index.ts",
|
|
1351
|
+
"src/renderer/index.tsx",
|
|
1352
|
+
"src/renderer/index.js",
|
|
1353
|
+
"src/renderer/main.ts",
|
|
1354
|
+
"src/renderer/main.tsx",
|
|
1355
|
+
"src/renderer/main.js",
|
|
1356
|
+
"renderer/index.ts",
|
|
1357
|
+
"renderer/index.tsx",
|
|
1358
|
+
"renderer/index.js",
|
|
1359
|
+
"renderer.ts",
|
|
1360
|
+
"renderer.tsx",
|
|
1361
|
+
"renderer.js",
|
|
1362
|
+
// `src/renderer.{ts,tsx,js}` is the convention used by multi-window
|
|
1363
|
+
// Electron apps that keep all renderer entries in src/.
|
|
1364
|
+
"src/renderer.ts",
|
|
1365
|
+
"src/renderer.tsx",
|
|
1366
|
+
"src/renderer.js",
|
|
1367
|
+
"src/index.tsx",
|
|
1368
|
+
"src/index.jsx",
|
|
1369
|
+
"src/main.tsx",
|
|
1370
|
+
"src/main.jsx"
|
|
1371
|
+
]);
|
|
1372
|
+
if (rendererEntry) injectImport(rendererEntry, rendererInitPath);
|
|
1373
|
+
else
|
|
1374
|
+
warnings.push(
|
|
1375
|
+
'Could not auto-detect the Electron renderer entry. Add `import "./gg-pixel.renderer.mjs";` to the top of your renderer entry file.'
|
|
1376
|
+
);
|
|
1377
|
+
}
|
|
1378
|
+
return {
|
|
1379
|
+
primaryInitPath: rendererInitPath,
|
|
1380
|
+
entryWiring: { kind: "injected", entryPath: rendererInitPath },
|
|
1381
|
+
secondaryInit: {
|
|
1382
|
+
path: mainInitPath,
|
|
1383
|
+
description: "Electron main-process init" + (mainWiring.kind === "injected" ? ` (wired into ${mainEntry})` : "")
|
|
1384
|
+
},
|
|
1385
|
+
warnings
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
function resolveMainEntryFromPkg(projectRoot, pkg) {
|
|
1389
|
+
if (pkg.main) {
|
|
1390
|
+
const sourceCandidates = [];
|
|
1391
|
+
const main = pkg.main;
|
|
1392
|
+
if (/^(dist|build|\.next|out)\//.test(main)) {
|
|
1393
|
+
const swap = main.replace(/^(dist|build|\.next|out)\//, "src/");
|
|
1394
|
+
if (swap.endsWith(".js")) {
|
|
1395
|
+
sourceCandidates.push(swap.replace(/\.js$/, ".ts"));
|
|
1396
|
+
sourceCandidates.push(swap.replace(/\.js$/, ".tsx"));
|
|
1397
|
+
}
|
|
1398
|
+
sourceCandidates.push(swap);
|
|
1399
|
+
}
|
|
1400
|
+
if (main.endsWith(".js")) {
|
|
1401
|
+
sourceCandidates.push(main.replace(/\.js$/, ".ts"));
|
|
1402
|
+
sourceCandidates.push(main.replace(/\.js$/, ".tsx"));
|
|
1403
|
+
}
|
|
1404
|
+
for (const c of sourceCandidates) {
|
|
1405
|
+
const p = join2(projectRoot, c);
|
|
1406
|
+
if (existsSync2(p)) return p;
|
|
1407
|
+
}
|
|
1408
|
+
const literal = join2(projectRoot, main);
|
|
1409
|
+
if (existsSync2(literal)) return literal;
|
|
1410
|
+
}
|
|
1411
|
+
return pickPath(projectRoot, [
|
|
1412
|
+
"main.js",
|
|
1413
|
+
"main.ts",
|
|
1414
|
+
"src/main.js",
|
|
1415
|
+
"src/main.ts",
|
|
1416
|
+
"src/main/index.ts",
|
|
1417
|
+
"src/main/index.js",
|
|
1418
|
+
"electron/main.js",
|
|
1419
|
+
"electron/main.ts"
|
|
1420
|
+
]);
|
|
1421
|
+
}
|
|
1422
|
+
var RENDERER_HTML_DIRS = ["ui", "renderer", "src/renderer", "src", "public", "static"];
|
|
1423
|
+
function findRendererHtmlFiles(projectRoot) {
|
|
1424
|
+
for (const dir of RENDERER_HTML_DIRS) {
|
|
1425
|
+
const root = join2(projectRoot, dir);
|
|
1426
|
+
if (!existsSync2(root)) continue;
|
|
1427
|
+
const html = [];
|
|
1428
|
+
let entries;
|
|
1429
|
+
try {
|
|
1430
|
+
entries = readdirSync(root);
|
|
1431
|
+
} catch {
|
|
1432
|
+
continue;
|
|
1433
|
+
}
|
|
1434
|
+
for (const e of entries) {
|
|
1435
|
+
if (!e.endsWith(".html")) continue;
|
|
1436
|
+
const p = join2(root, e);
|
|
1437
|
+
try {
|
|
1438
|
+
const c = readFileSync2(p, "utf8");
|
|
1439
|
+
if (/<meta[^>]+content-security-policy/i.test(c) || /<script[\s>]/i.test(c)) {
|
|
1440
|
+
html.push(p);
|
|
1441
|
+
}
|
|
1442
|
+
} catch {
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
if (html.length > 0) return html;
|
|
1446
|
+
}
|
|
1447
|
+
return [];
|
|
1448
|
+
}
|
|
1449
|
+
function copyIifeBundle(projectRoot, dest) {
|
|
1450
|
+
const candidates = [
|
|
1451
|
+
join2(projectRoot, "node_modules/@iroaxel/gg-pixel/dist/browser.iife.global.js"),
|
|
1452
|
+
join2(projectRoot, "node_modules/@iroaxel/gg-pixel/dist/browser.iife.js")
|
|
1453
|
+
];
|
|
1454
|
+
for (const c of candidates) {
|
|
1455
|
+
if (existsSync2(c)) {
|
|
1456
|
+
try {
|
|
1457
|
+
writeFileSync(dest, readFileSync2(c, "utf8"), "utf8");
|
|
1458
|
+
return true;
|
|
1459
|
+
} catch {
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
return false;
|
|
1464
|
+
}
|
|
1465
|
+
var PIXEL_HTML_MARKER = "<!-- gg-pixel: auto-wired by arcicoder pixel install -->";
|
|
1466
|
+
function patchRendererHtml(htmlPath, iifePath, projectKey, ingestUrl) {
|
|
1467
|
+
let content;
|
|
1468
|
+
try {
|
|
1469
|
+
content = readFileSync2(htmlPath, "utf8");
|
|
1470
|
+
} catch {
|
|
1471
|
+
return "not-applicable";
|
|
1472
|
+
}
|
|
1473
|
+
const original = content;
|
|
1474
|
+
const markerIdx = content.indexOf(PIXEL_HTML_MARKER);
|
|
1475
|
+
if (markerIdx !== -1) {
|
|
1476
|
+
const firstScriptEnd = content.indexOf("</script>", markerIdx);
|
|
1477
|
+
const secondScriptEnd = firstScriptEnd !== -1 ? content.indexOf("</script>", firstScriptEnd + "</script>".length) : -1;
|
|
1478
|
+
if (secondScriptEnd !== -1) {
|
|
1479
|
+
let stripEnd = secondScriptEnd + "</script>".length;
|
|
1480
|
+
while (stripEnd < content.length && /\s/.test(content[stripEnd])) stripEnd++;
|
|
1481
|
+
let stripStart = markerIdx;
|
|
1482
|
+
while (stripStart > 0 && /[ \t]/.test(content[stripStart - 1])) stripStart--;
|
|
1483
|
+
if (stripStart > 0 && content[stripStart - 1] === "\n") stripStart--;
|
|
1484
|
+
content = content.slice(0, stripStart) + content.slice(stripEnd);
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
const ingestOrigin = new URL(ingestUrl).origin;
|
|
1488
|
+
content = content.replace(
|
|
1489
|
+
/(<meta[^>]+http-equiv=["']?content-security-policy["']?[^>]*content=)("([^"]+)"|'([^']+)')/i,
|
|
1490
|
+
(_match, before, _all, dq, sq) => {
|
|
1491
|
+
const quote = dq !== void 0 ? '"' : "'";
|
|
1492
|
+
const csp = dq !== void 0 ? dq : sq;
|
|
1493
|
+
let updated = csp;
|
|
1494
|
+
if (/connect-src\s/i.test(csp)) {
|
|
1495
|
+
if (!csp.includes(ingestOrigin)) {
|
|
1496
|
+
updated = csp.replace(/(connect-src[^;]*)/i, `$1 ${ingestOrigin}`);
|
|
1497
|
+
}
|
|
1498
|
+
} else {
|
|
1499
|
+
updated = csp.trim().replace(/;?$/, `; connect-src 'self' ${ingestOrigin};`);
|
|
1500
|
+
}
|
|
1501
|
+
return before + quote + updated + quote;
|
|
1502
|
+
}
|
|
1503
|
+
);
|
|
1504
|
+
const relScript = relative(dirname2(htmlPath), iifePath).split(sep).join("/");
|
|
1505
|
+
const inject = `
|
|
1506
|
+
${PIXEL_HTML_MARKER}
|
|
1507
|
+
<script src="${relScript}"></script>
|
|
1508
|
+
<script>
|
|
1509
|
+
if (window.GGPixel) GGPixel.initPixel({ projectKey: ${JSON.stringify(projectKey)}, ingestUrl: ${JSON.stringify(ingestUrl)} });
|
|
1510
|
+
</script>
|
|
1511
|
+
`;
|
|
1512
|
+
if (/<head[^>]*>/i.test(content)) {
|
|
1513
|
+
content = content.replace(/(<head[^>]*>)/i, `$1${inject}`);
|
|
1514
|
+
} else if (/<html[^>]*>/i.test(content)) {
|
|
1515
|
+
content = content.replace(/(<html[^>]*>)/i, `$1<head>${inject}</head>`);
|
|
1516
|
+
} else {
|
|
1517
|
+
return "not-applicable";
|
|
1518
|
+
}
|
|
1519
|
+
if (content === original) return "already";
|
|
1520
|
+
writeFileSync(htmlPath, content, "utf8");
|
|
1521
|
+
return "patched";
|
|
1522
|
+
}
|
|
1523
|
+
function wireTauri({ projectRoot, pkg, projectKey, ingestUrl }) {
|
|
1524
|
+
const initPath = join2(projectRoot, "gg-pixel.init.mjs");
|
|
1525
|
+
writeFileSync(initPath, renderBrowserInitFile(ingestUrl, projectKey), "utf8");
|
|
1526
|
+
const entryWiring = wireEntryFile(projectRoot, initPath, pkg);
|
|
1527
|
+
return {
|
|
1528
|
+
primaryInitPath: initPath,
|
|
1529
|
+
entryWiring,
|
|
1530
|
+
warnings: [
|
|
1531
|
+
"Tauri Rust backend is not instrumented \u2014 no Rust SDK exists yet. Frontend errors are captured."
|
|
1532
|
+
]
|
|
1533
|
+
};
|
|
1534
|
+
}
|
|
1535
|
+
function wireWorkers({ projectRoot, projectKey, ingestUrl }) {
|
|
1536
|
+
const initPath = join2(projectRoot, "gg-pixel.workers.snippet.ts");
|
|
1537
|
+
writeFileSync(
|
|
1538
|
+
initPath,
|
|
1539
|
+
`// gg-pixel \u2014 Cloudflare Workers wiring snippet.
|
|
1540
|
+
// Auto-generated by arcicoder pixel install. Wrap your default export with
|
|
1541
|
+
// withPixel(...) so any throw in your handler is auto-reported. Example:
|
|
1542
|
+
//
|
|
1543
|
+
// import { withPixel } from "@iroaxel/gg-pixel/workers";
|
|
1544
|
+
//
|
|
1545
|
+
// export default withPixel(
|
|
1546
|
+
// { projectKey: ${JSON.stringify(projectKey)} },
|
|
1547
|
+
// {
|
|
1548
|
+
// async fetch(req, env, ctx) { /* your code */ },
|
|
1549
|
+
// async scheduled(evt, env, ctx) { /* your code */ },
|
|
1550
|
+
// },
|
|
1551
|
+
// );
|
|
1552
|
+
//
|
|
1553
|
+
// For manual reports inside a handler:
|
|
1554
|
+
//
|
|
1555
|
+
// import { reportPixel } from "@iroaxel/gg-pixel/workers";
|
|
1556
|
+
// reportPixel(ctx, { projectKey: ${JSON.stringify(projectKey)} }, {
|
|
1557
|
+
// message: "user clicked the broken button",
|
|
1558
|
+
// });
|
|
1559
|
+
//
|
|
1560
|
+
// Your project_key is publishable \u2014 safe to commit.
|
|
1561
|
+
|
|
1562
|
+
import { withPixel, reportPixel } from "@iroaxel/gg-pixel/workers";
|
|
1563
|
+
export const PIXEL_KEY = ${JSON.stringify(projectKey)};
|
|
1564
|
+
export const PIXEL_INGEST = ${JSON.stringify(ingestUrl)};
|
|
1565
|
+
export { withPixel, reportPixel };
|
|
1566
|
+
`,
|
|
1567
|
+
"utf8"
|
|
1568
|
+
);
|
|
1569
|
+
return {
|
|
1570
|
+
primaryInitPath: initPath,
|
|
1571
|
+
entryWiring: { kind: "no_entry_found" },
|
|
1572
|
+
warnings: [
|
|
1573
|
+
`Cloudflare Workers default exports can't be auto-wrapped safely. Open ${initPath} for a 3-line snippet you can paste into your worker.`
|
|
1574
|
+
]
|
|
1575
|
+
};
|
|
1576
|
+
}
|
|
1577
|
+
function wireReactNative({ projectRoot }) {
|
|
1578
|
+
return {
|
|
1579
|
+
primaryInitPath: join2(projectRoot, "(not-installed)"),
|
|
1580
|
+
entryWiring: { kind: "skipped", reason: "react-native SDK not built yet" },
|
|
1581
|
+
warnings: [
|
|
1582
|
+
"React Native is not yet supported \u2014 its JS runtime is neither browser nor Node.",
|
|
1583
|
+
"A dedicated React Native SDK will be a future slice."
|
|
1584
|
+
]
|
|
1585
|
+
};
|
|
1586
|
+
}
|
|
1587
|
+
function pickPath(root, candidates) {
|
|
1588
|
+
for (const c of candidates) {
|
|
1589
|
+
const p = join2(root, c);
|
|
1590
|
+
if (existsSync2(p)) return p;
|
|
1591
|
+
}
|
|
1592
|
+
return null;
|
|
1593
|
+
}
|
|
1594
|
+
var PIXEL_MARK_BEGIN = "// >>> gg-pixel auto-generated \u2014 do not edit between these markers <<<";
|
|
1595
|
+
var PIXEL_MARK_END = "// >>> /gg-pixel <<<";
|
|
1596
|
+
function wrapPixelBlock(content) {
|
|
1597
|
+
return `${PIXEL_MARK_BEGIN}
|
|
1598
|
+
${content.replace(/\s+$/, "")}
|
|
1599
|
+
${PIXEL_MARK_END}
|
|
1600
|
+
`;
|
|
1601
|
+
}
|
|
1602
|
+
function upsertPixelBlock(existing, block) {
|
|
1603
|
+
const wrapped = wrapPixelBlock(block);
|
|
1604
|
+
const beginIdx = existing.indexOf(PIXEL_MARK_BEGIN);
|
|
1605
|
+
if (beginIdx !== -1) {
|
|
1606
|
+
const endIdx = existing.indexOf(PIXEL_MARK_END, beginIdx);
|
|
1607
|
+
if (endIdx !== -1) {
|
|
1608
|
+
const after = endIdx + PIXEL_MARK_END.length;
|
|
1609
|
+
const trailNL = existing[after] === "\n" ? 1 : 0;
|
|
1610
|
+
return existing.slice(0, beginIdx) + wrapped + existing.slice(after + trailNL);
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
if (existing.length === 0) return wrapped;
|
|
1614
|
+
const sep2 = existing.endsWith("\n") ? "" : "\n";
|
|
1615
|
+
return existing + sep2 + "\n" + wrapped;
|
|
1616
|
+
}
|
|
1617
|
+
function upsertPixelBlockInFile(filePath, block) {
|
|
1618
|
+
const existing = existsSync2(filePath) ? readFileSync2(filePath, "utf8") : "";
|
|
1619
|
+
const cleaned = stripLegacyPixelContent(existing);
|
|
1620
|
+
const next = upsertPixelBlock(cleaned, block);
|
|
1621
|
+
if (next !== existing) writeFileSync(filePath, next, "utf8");
|
|
1622
|
+
}
|
|
1623
|
+
function stripLegacyPixelContent(content) {
|
|
1624
|
+
if (!content.includes("@iroaxel/gg-pixel")) return content;
|
|
1625
|
+
const beginIdx = content.indexOf(PIXEL_MARK_BEGIN);
|
|
1626
|
+
const endIdx = beginIdx === -1 ? -1 : content.indexOf(PIXEL_MARK_END, beginIdx);
|
|
1627
|
+
const insideMarkers = (start, end) => beginIdx !== -1 && endIdx !== -1 && start >= beginIdx && end <= endIdx + PIXEL_MARK_END.length;
|
|
1628
|
+
const ranges = [];
|
|
1629
|
+
const registerRe = /(?:^|\n)((?:[ \t]*\/\/[^\n]*\n)*)[ \t]*export\s+async\s+function\s+register\s*\(\s*\)\s*\{/g;
|
|
1630
|
+
let m;
|
|
1631
|
+
while ((m = registerRe.exec(content)) !== null) {
|
|
1632
|
+
const blockStart = m.index + (content[m.index] === "\n" ? 1 : 0);
|
|
1633
|
+
const openBraceIdx = m.index + m[0].length - 1;
|
|
1634
|
+
let depth = 1;
|
|
1635
|
+
let i = openBraceIdx + 1;
|
|
1636
|
+
while (i < content.length && depth > 0) {
|
|
1637
|
+
const ch = content[i];
|
|
1638
|
+
if (ch === "{") depth++;
|
|
1639
|
+
else if (ch === "}") depth--;
|
|
1640
|
+
i++;
|
|
1641
|
+
}
|
|
1642
|
+
if (depth !== 0) continue;
|
|
1643
|
+
const blockEnd = i;
|
|
1644
|
+
const blockText = content.slice(blockStart, blockEnd);
|
|
1645
|
+
if (!blockText.includes("@iroaxel/gg-pixel")) continue;
|
|
1646
|
+
if (insideMarkers(blockStart, blockEnd)) continue;
|
|
1647
|
+
const trailingNL = content[blockEnd] === "\n" ? 1 : 0;
|
|
1648
|
+
ranges.push({ start: blockStart, end: blockEnd + trailingNL });
|
|
1649
|
+
}
|
|
1650
|
+
const importRe = /(?:^|\n)((?:[ \t]*\/\/[^\n]*\n)*)[ \t]*import\s*\{\s*initPixel[^}]*\}\s*from\s*"@iroaxel\/gg-pixel(?:\/[\w-]+)?"\s*;?\s*\n/g;
|
|
1651
|
+
while ((m = importRe.exec(content)) !== null) {
|
|
1652
|
+
const blockStart = m.index + (content[m.index] === "\n" ? 1 : 0);
|
|
1653
|
+
const callIdx = content.indexOf("initPixel(", importRe.lastIndex);
|
|
1654
|
+
if (callIdx === -1 || callIdx - importRe.lastIndex > 2048) continue;
|
|
1655
|
+
const openParen = content.indexOf("(", callIdx);
|
|
1656
|
+
let depth = 1;
|
|
1657
|
+
let i = openParen + 1;
|
|
1658
|
+
while (i < content.length && depth > 0) {
|
|
1659
|
+
const ch = content[i];
|
|
1660
|
+
if (ch === "(" || ch === "{") depth++;
|
|
1661
|
+
else if (ch === ")" || ch === "}") depth--;
|
|
1662
|
+
i++;
|
|
1663
|
+
}
|
|
1664
|
+
if (depth !== 0) continue;
|
|
1665
|
+
while (i < content.length && (content[i] === ";" || content[i] === " ")) i++;
|
|
1666
|
+
const trailingNL = content[i] === "\n" ? 1 : 0;
|
|
1667
|
+
const blockEnd = i + trailingNL;
|
|
1668
|
+
if (insideMarkers(blockStart, blockEnd)) continue;
|
|
1669
|
+
ranges.push({ start: blockStart, end: blockEnd });
|
|
1670
|
+
}
|
|
1671
|
+
if (ranges.length === 0) return content;
|
|
1672
|
+
ranges.sort((a, b) => b.start - a.start);
|
|
1673
|
+
let out = content;
|
|
1674
|
+
for (const r of ranges) out = out.slice(0, r.start) + out.slice(r.end);
|
|
1675
|
+
return out.replace(/\n{3,}/g, "\n\n");
|
|
1676
|
+
}
|
|
1677
|
+
function injectImport(entryPath, initFilePath) {
|
|
1678
|
+
let content;
|
|
1679
|
+
try {
|
|
1680
|
+
content = readFileSync2(entryPath, "utf8");
|
|
1681
|
+
} catch (err) {
|
|
1682
|
+
return { kind: "skipped", reason: `unreadable: ${err.message}` };
|
|
1683
|
+
}
|
|
1684
|
+
const initBasename = initFilePath.split(sep).pop() ?? "gg-pixel.init.mjs";
|
|
1685
|
+
if (content.includes(initBasename) || content.includes("@iroaxel/gg-pixel")) {
|
|
1686
|
+
return { kind: "already_present", entryPath };
|
|
1687
|
+
}
|
|
1688
|
+
const fromDir = dirname2(entryPath);
|
|
1689
|
+
let spec = relative(fromDir, initFilePath).split(sep).join("/");
|
|
1690
|
+
if (!spec.startsWith(".")) spec = "./" + spec;
|
|
1691
|
+
const isCjs = entryPath.endsWith(".cjs") || entryPath.endsWith(".js") && /\brequire\s*\(/.test(content) && !/\bimport\s+/.test(content) && !/\bexport\s+/.test(content);
|
|
1692
|
+
const importLine = isCjs ? `require(${JSON.stringify(spec)});` : `import ${JSON.stringify(spec)};`;
|
|
1693
|
+
const lines = content.split("\n");
|
|
1694
|
+
let insertAt = 0;
|
|
1695
|
+
if (lines[0]?.startsWith("#!")) insertAt = 1;
|
|
1696
|
+
while (insertAt < lines.length && /^\s*(?:["']use strict["']|\/\/|\/\*)/.test(lines[insertAt] ?? "")) {
|
|
1697
|
+
insertAt++;
|
|
1698
|
+
}
|
|
1699
|
+
const updated = [...lines.slice(0, insertAt), importLine, ...lines.slice(insertAt)].join("\n");
|
|
1700
|
+
writeFileSync(entryPath, updated, "utf8");
|
|
1701
|
+
return { kind: "injected", entryPath };
|
|
1702
|
+
}
|
|
1703
|
+
function isBrowserProject(pkg, projectRoot) {
|
|
1704
|
+
const all = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
1705
|
+
const browserishDeps = [
|
|
1706
|
+
"react",
|
|
1707
|
+
"react-dom",
|
|
1708
|
+
"vue",
|
|
1709
|
+
"@vue/runtime-core",
|
|
1710
|
+
"svelte",
|
|
1711
|
+
"next",
|
|
1712
|
+
"vite",
|
|
1713
|
+
"@vitejs/plugin-react",
|
|
1714
|
+
"@angular/core",
|
|
1715
|
+
"solid-js",
|
|
1716
|
+
"preact",
|
|
1717
|
+
"@remix-run/react",
|
|
1718
|
+
"astro",
|
|
1719
|
+
"qwik",
|
|
1720
|
+
"@sveltejs/kit",
|
|
1721
|
+
"expo"
|
|
1722
|
+
];
|
|
1723
|
+
if (browserishDeps.some((d) => d in all)) return true;
|
|
1724
|
+
if (pkg.browser !== void 0) return true;
|
|
1725
|
+
if (existsSync2(join2(projectRoot, "index.html"))) return true;
|
|
1726
|
+
if (existsSync2(join2(projectRoot, "public", "index.html"))) return true;
|
|
1727
|
+
return false;
|
|
1728
|
+
}
|
|
1729
|
+
function renderBrowserInitFile(ingestUrl, projectKey) {
|
|
1730
|
+
return `// gg-pixel init \u2014 auto-generated by arcicoder pixel install.
|
|
1731
|
+
// The project_key is publishable (designed to live in browser bundles).
|
|
1732
|
+
import { initPixel } from "@iroaxel/gg-pixel/browser";
|
|
1733
|
+
|
|
1734
|
+
initPixel({
|
|
1735
|
+
projectKey: ${JSON.stringify(projectKey)},
|
|
1736
|
+
ingestUrl: ${JSON.stringify(ingestUrl)},
|
|
1737
|
+
});
|
|
1738
|
+
`;
|
|
1739
|
+
}
|
|
1740
|
+
var PYTHON_MARKERS = ["pyproject.toml", "setup.py", "requirements.txt", "Pipfile"];
|
|
1741
|
+
function findPythonProjectRoot(start) {
|
|
1742
|
+
let dir = start;
|
|
1743
|
+
for (let i = 0; i < 20; i++) {
|
|
1744
|
+
if (PYTHON_MARKERS.some((m) => existsSync2(join2(dir, m)))) return dir;
|
|
1745
|
+
const parent = dirname2(dir);
|
|
1746
|
+
if (parent === dir) return null;
|
|
1747
|
+
dir = parent;
|
|
1748
|
+
}
|
|
1749
|
+
return null;
|
|
1750
|
+
}
|
|
1751
|
+
function detectPythonPackageManager(projectRoot) {
|
|
1752
|
+
if (existsSync2(join2(projectRoot, "uv.lock"))) return "uv";
|
|
1753
|
+
if (existsSync2(join2(projectRoot, "poetry.lock"))) return "poetry";
|
|
1754
|
+
if (existsSync2(join2(projectRoot, "Pipfile.lock"))) return "pipenv";
|
|
1755
|
+
return "pip";
|
|
1756
|
+
}
|
|
1757
|
+
function pickClosestRoot(roots) {
|
|
1758
|
+
let best = null;
|
|
1759
|
+
for (const r of roots) {
|
|
1760
|
+
if (!r) continue;
|
|
1761
|
+
if (!best || r.length > best.length) best = r;
|
|
1762
|
+
}
|
|
1763
|
+
return best;
|
|
1764
|
+
}
|
|
1765
|
+
var GO_MARKER = "go.mod";
|
|
1766
|
+
var RUBY_MARKERS = ["Gemfile"];
|
|
1767
|
+
function findGoProjectRoot(start) {
|
|
1768
|
+
let dir = start;
|
|
1769
|
+
for (let i = 0; i < 20; i++) {
|
|
1770
|
+
if (existsSync2(join2(dir, GO_MARKER))) return dir;
|
|
1771
|
+
const parent = dirname2(dir);
|
|
1772
|
+
if (parent === dir) return null;
|
|
1773
|
+
dir = parent;
|
|
1774
|
+
}
|
|
1775
|
+
return null;
|
|
1776
|
+
}
|
|
1777
|
+
function findRubyProjectRoot(start) {
|
|
1778
|
+
let dir = start;
|
|
1779
|
+
for (let i = 0; i < 20; i++) {
|
|
1780
|
+
for (const m of RUBY_MARKERS) {
|
|
1781
|
+
if (existsSync2(join2(dir, m))) return dir;
|
|
1782
|
+
}
|
|
1783
|
+
try {
|
|
1784
|
+
const entries = readdirSync(dir);
|
|
1785
|
+
if (entries.some((e) => e.endsWith(".gemspec"))) return dir;
|
|
1786
|
+
} catch {
|
|
1787
|
+
}
|
|
1788
|
+
const parent = dirname2(dir);
|
|
1789
|
+
if (parent === dir) return null;
|
|
1790
|
+
dir = parent;
|
|
1791
|
+
}
|
|
1792
|
+
return null;
|
|
1793
|
+
}
|
|
1794
|
+
async function installGo(ctx) {
|
|
1795
|
+
const { projectRoot, opts, ingestUrl, fetchFn, home } = ctx;
|
|
1796
|
+
const projectName = opts.projectName ?? readGoModuleName(projectRoot) ?? projectRoot.split("/").pop() ?? "unnamed";
|
|
1797
|
+
const projectsJsonPath = join2(home, ".gg", "projects.json");
|
|
1798
|
+
const envFilePath = join2(projectRoot, ".env");
|
|
1799
|
+
const existing = findMappingByPath(projectsJsonPath, projectRoot);
|
|
1800
|
+
const existingKey = readEnvKey(envFilePath, "GG_PIXEL_KEY");
|
|
1801
|
+
let created;
|
|
1802
|
+
let reused = false;
|
|
1803
|
+
if (existing && existing.secret && existingKey) {
|
|
1804
|
+
created = { id: existing.id, key: existingKey, secret: existing.secret };
|
|
1805
|
+
reused = true;
|
|
1806
|
+
} else {
|
|
1807
|
+
created = await createProject(fetchFn, ingestUrl, projectName);
|
|
1808
|
+
}
|
|
1809
|
+
const packageInstalled = opts.skipPackageInstall ? false : runGoGet(projectRoot);
|
|
1810
|
+
const initFilePath = join2(projectRoot, "gg_pixel_init.go");
|
|
1811
|
+
writeFileSync(
|
|
1812
|
+
initFilePath,
|
|
1813
|
+
`// gg-pixel init \u2014 auto-generated by arcicoder pixel install.
|
|
1814
|
+
package main
|
|
1815
|
+
|
|
1816
|
+
import (
|
|
1817
|
+
"os"
|
|
1818
|
+
gg "github.com/kenkaiiii/gg-pixel-go"
|
|
1819
|
+
)
|
|
1820
|
+
|
|
1821
|
+
func init() {
|
|
1822
|
+
key := os.Getenv("GG_PIXEL_KEY")
|
|
1823
|
+
if key == "" {
|
|
1824
|
+
key = ${JSON.stringify(created.key)}
|
|
1825
|
+
}
|
|
1826
|
+
_ = gg.Init(gg.Options{ProjectKey: key, IngestURL: ${JSON.stringify(`${ingestUrl}/ingest`)}})
|
|
1827
|
+
}
|
|
1828
|
+
`,
|
|
1829
|
+
"utf8"
|
|
1830
|
+
);
|
|
1831
|
+
writeEnvKey(envFilePath, "GG_PIXEL_KEY", created.key);
|
|
1832
|
+
writeProjectsMapping(projectsJsonPath, created.id, projectName, projectRoot, created.secret);
|
|
1833
|
+
return {
|
|
1834
|
+
projectId: created.id,
|
|
1835
|
+
projectKey: created.key,
|
|
1836
|
+
projectSecret: created.secret,
|
|
1837
|
+
projectName,
|
|
1838
|
+
projectKind: "go",
|
|
1839
|
+
projectRoot,
|
|
1840
|
+
initFilePath,
|
|
1841
|
+
envFilePath,
|
|
1842
|
+
projectsJsonPath,
|
|
1843
|
+
packageManager: "pip",
|
|
1844
|
+
packageInstalled,
|
|
1845
|
+
entryWiring: { kind: "no_entry_found" },
|
|
1846
|
+
reused,
|
|
1847
|
+
warnings: [
|
|
1848
|
+
"Add `defer ggpixel.Recover()` near the top of your main() so panics are captured before the process exits."
|
|
1849
|
+
]
|
|
1850
|
+
};
|
|
1851
|
+
}
|
|
1852
|
+
function readGoModuleName(projectRoot) {
|
|
1853
|
+
try {
|
|
1854
|
+
const content = readFileSync2(join2(projectRoot, "go.mod"), "utf8");
|
|
1855
|
+
const match = /^\s*module\s+(\S+)\s*$/m.exec(content);
|
|
1856
|
+
if (!match) return null;
|
|
1857
|
+
return match[1].split("/").pop() ?? null;
|
|
1858
|
+
} catch {
|
|
1859
|
+
return null;
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
function runGoGet(projectRoot) {
|
|
1863
|
+
const result = spawnSync2("go", ["get", "github.com/kenkaiiii/gg-pixel-go@latest"], {
|
|
1864
|
+
cwd: projectRoot,
|
|
1865
|
+
stdio: "inherit"
|
|
1866
|
+
});
|
|
1867
|
+
return result.status === 0;
|
|
1868
|
+
}
|
|
1869
|
+
async function installRuby(ctx) {
|
|
1870
|
+
const { projectRoot, opts, ingestUrl, fetchFn, home } = ctx;
|
|
1871
|
+
const projectName = opts.projectName ?? readRubyAppName(projectRoot) ?? projectRoot.split("/").pop() ?? "unnamed";
|
|
1872
|
+
const projectsJsonPath = join2(home, ".gg", "projects.json");
|
|
1873
|
+
const envFilePath = join2(projectRoot, ".env");
|
|
1874
|
+
const existing = findMappingByPath(projectsJsonPath, projectRoot);
|
|
1875
|
+
const existingKey = readEnvKey(envFilePath, "GG_PIXEL_KEY");
|
|
1876
|
+
let created;
|
|
1877
|
+
let reused = false;
|
|
1878
|
+
if (existing && existing.secret && existingKey) {
|
|
1879
|
+
created = { id: existing.id, key: existingKey, secret: existing.secret };
|
|
1880
|
+
reused = true;
|
|
1881
|
+
} else {
|
|
1882
|
+
created = await createProject(fetchFn, ingestUrl, projectName);
|
|
1883
|
+
}
|
|
1884
|
+
const packageInstalled = opts.skipPackageInstall ? false : runRubyInstall(projectRoot);
|
|
1885
|
+
const initFilePath = join2(projectRoot, "gg_pixel_init.rb");
|
|
1886
|
+
writeFileSync(
|
|
1887
|
+
initFilePath,
|
|
1888
|
+
`# gg-pixel init \u2014 auto-generated by arcicoder pixel install.
|
|
1889
|
+
require "gg_pixel"
|
|
1890
|
+
GGPixel.init(
|
|
1891
|
+
project_key: ENV["GG_PIXEL_KEY"] || ${JSON.stringify(created.key)},
|
|
1892
|
+
ingest_url: ${JSON.stringify(`${ingestUrl}/ingest`)},
|
|
1893
|
+
)
|
|
1894
|
+
`,
|
|
1895
|
+
"utf8"
|
|
1896
|
+
);
|
|
1897
|
+
writeEnvKey(envFilePath, "GG_PIXEL_KEY", created.key);
|
|
1898
|
+
writeProjectsMapping(projectsJsonPath, created.id, projectName, projectRoot, created.secret);
|
|
1899
|
+
return {
|
|
1900
|
+
projectId: created.id,
|
|
1901
|
+
projectKey: created.key,
|
|
1902
|
+
projectSecret: created.secret,
|
|
1903
|
+
projectName,
|
|
1904
|
+
projectKind: "ruby",
|
|
1905
|
+
projectRoot,
|
|
1906
|
+
initFilePath,
|
|
1907
|
+
envFilePath,
|
|
1908
|
+
projectsJsonPath,
|
|
1909
|
+
packageManager: "pip",
|
|
1910
|
+
packageInstalled,
|
|
1911
|
+
entryWiring: { kind: "no_entry_found" },
|
|
1912
|
+
reused,
|
|
1913
|
+
warnings: [
|
|
1914
|
+
`Add \`require "./gg_pixel_init"\` at the top of your entry script (often \`config/application.rb\` for Rails, \`app.rb\` for Sinatra, or your main file).`
|
|
1915
|
+
]
|
|
1916
|
+
};
|
|
1917
|
+
}
|
|
1918
|
+
function readRubyAppName(projectRoot) {
|
|
1919
|
+
try {
|
|
1920
|
+
const entries = readdirSync(projectRoot);
|
|
1921
|
+
const gemspec = entries.find((e) => e.endsWith(".gemspec"));
|
|
1922
|
+
if (!gemspec) return null;
|
|
1923
|
+
return gemspec.replace(/\.gemspec$/, "");
|
|
1924
|
+
} catch {
|
|
1925
|
+
return null;
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
function runRubyInstall(projectRoot) {
|
|
1929
|
+
if (existsSync2(join2(projectRoot, "Gemfile"))) {
|
|
1930
|
+
try {
|
|
1931
|
+
const content = readFileSync2(join2(projectRoot, "Gemfile"), "utf8");
|
|
1932
|
+
if (!content.includes("gg_pixel")) {
|
|
1933
|
+
writeFileSync(
|
|
1934
|
+
join2(projectRoot, "Gemfile"),
|
|
1935
|
+
content + (content.endsWith("\n") ? "" : "\n") + 'gem "gg_pixel"\n',
|
|
1936
|
+
"utf8"
|
|
1937
|
+
);
|
|
1938
|
+
}
|
|
1939
|
+
} catch {
|
|
1940
|
+
}
|
|
1941
|
+
const r = spawnSync2("bundle", ["install"], { cwd: projectRoot, stdio: "inherit" });
|
|
1942
|
+
if (r.status === 0) return true;
|
|
1943
|
+
}
|
|
1944
|
+
const r2 = spawnSync2("gem", ["install", "gg_pixel"], { cwd: projectRoot, stdio: "inherit" });
|
|
1945
|
+
return r2.status === 0;
|
|
1946
|
+
}
|
|
1947
|
+
async function installPython(ctx) {
|
|
1948
|
+
const { projectRoot, opts, ingestUrl, fetchFn, home } = ctx;
|
|
1949
|
+
const projectName = opts.projectName ?? readPyprojectName(projectRoot) ?? projectRoot.split("/").pop() ?? "unnamed";
|
|
1950
|
+
const projectsJsonPath = join2(home, ".gg", "projects.json");
|
|
1951
|
+
const envFilePath = join2(projectRoot, ".env");
|
|
1952
|
+
const existing = findMappingByPath(projectsJsonPath, projectRoot);
|
|
1953
|
+
const existingKey = readEnvKey(envFilePath, "GG_PIXEL_KEY");
|
|
1954
|
+
let created;
|
|
1955
|
+
let reused = false;
|
|
1956
|
+
if (existing && existing.secret && existingKey) {
|
|
1957
|
+
created = { id: existing.id, key: existingKey, secret: existing.secret };
|
|
1958
|
+
reused = true;
|
|
1959
|
+
} else {
|
|
1960
|
+
created = await createProject(fetchFn, ingestUrl, projectName);
|
|
1961
|
+
}
|
|
1962
|
+
const pm = detectPythonPackageManager(projectRoot);
|
|
1963
|
+
const packageInstalled = opts.skipPackageInstall ? false : runPythonInstall(projectRoot, pm);
|
|
1964
|
+
const initFilePath = join2(projectRoot, "gg_pixel_init.py");
|
|
1965
|
+
writeFileSync(initFilePath, renderPythonInitFile(ingestUrl, created.key), "utf8");
|
|
1966
|
+
writeEnvKey(envFilePath, "GG_PIXEL_KEY", created.key);
|
|
1967
|
+
writeProjectsMapping(projectsJsonPath, created.id, projectName, projectRoot, created.secret);
|
|
1968
|
+
const entryWiring = wirePythonEntry(projectRoot, initFilePath);
|
|
1969
|
+
return {
|
|
1970
|
+
projectId: created.id,
|
|
1971
|
+
projectKey: created.key,
|
|
1972
|
+
projectSecret: created.secret,
|
|
1973
|
+
projectName,
|
|
1974
|
+
projectKind: "python",
|
|
1975
|
+
projectRoot,
|
|
1976
|
+
initFilePath,
|
|
1977
|
+
envFilePath,
|
|
1978
|
+
projectsJsonPath,
|
|
1979
|
+
packageManager: pm,
|
|
1980
|
+
packageInstalled,
|
|
1981
|
+
entryWiring,
|
|
1982
|
+
reused,
|
|
1983
|
+
warnings: []
|
|
1984
|
+
};
|
|
1985
|
+
}
|
|
1986
|
+
function readPyprojectName(projectRoot) {
|
|
1987
|
+
const path = join2(projectRoot, "pyproject.toml");
|
|
1988
|
+
if (!existsSync2(path)) return null;
|
|
1989
|
+
try {
|
|
1990
|
+
const content = readFileSync2(path, "utf8");
|
|
1991
|
+
const match = /^\s*name\s*=\s*["']([^"']+)["']/m.exec(content);
|
|
1992
|
+
return match?.[1] ?? null;
|
|
1993
|
+
} catch {
|
|
1994
|
+
return null;
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
function runPythonInstall(projectRoot, pm) {
|
|
1998
|
+
const cmd = pm === "uv" ? ["uv", ["add", "gg-pixel"]] : pm === "poetry" ? ["poetry", ["add", "gg-pixel"]] : pm === "pipenv" ? ["pipenv", ["install", "gg-pixel"]] : ["pip", ["install", "gg-pixel"]];
|
|
1999
|
+
const result = spawnSync2(cmd[0], cmd[1], {
|
|
2000
|
+
cwd: projectRoot,
|
|
2001
|
+
stdio: "inherit"
|
|
2002
|
+
});
|
|
2003
|
+
if (result.status === 0) return true;
|
|
2004
|
+
if (pm === "pip") {
|
|
2005
|
+
const r2 = spawnSync2("pip3", ["install", "gg-pixel"], {
|
|
2006
|
+
cwd: projectRoot,
|
|
2007
|
+
stdio: "inherit"
|
|
2008
|
+
});
|
|
2009
|
+
if (r2.status === 0) return true;
|
|
2010
|
+
const r3 = spawnSync2("python3", ["-m", "pip", "install", "gg-pixel"], {
|
|
2011
|
+
cwd: projectRoot,
|
|
2012
|
+
stdio: "inherit"
|
|
2013
|
+
});
|
|
2014
|
+
return r3.status === 0;
|
|
2015
|
+
}
|
|
2016
|
+
return false;
|
|
2017
|
+
}
|
|
2018
|
+
function renderPythonInitFile(ingestUrl, projectKey) {
|
|
2019
|
+
return `"""gg-pixel init \u2014 auto-generated by arcicoder pixel install.
|
|
2020
|
+
|
|
2021
|
+
This file initializes error tracking. Importing it (which the install step
|
|
2022
|
+
wires into your entry file) registers the global Python error handlers.
|
|
2023
|
+
"""
|
|
2024
|
+
import os
|
|
2025
|
+
|
|
2026
|
+
import gg_pixel
|
|
2027
|
+
|
|
2028
|
+
gg_pixel.init_pixel(
|
|
2029
|
+
project_key=os.environ.get("GG_PIXEL_KEY") or ${JSON.stringify(projectKey)},
|
|
2030
|
+
ingest_url=${JSON.stringify(`${ingestUrl}/ingest`)},
|
|
2031
|
+
)
|
|
2032
|
+
`;
|
|
2033
|
+
}
|
|
2034
|
+
function wirePythonEntry(projectRoot, initFilePath) {
|
|
2035
|
+
const entryPath = findPythonEntryFile(projectRoot);
|
|
2036
|
+
if (!entryPath) return { kind: "no_entry_found" };
|
|
2037
|
+
let content;
|
|
2038
|
+
try {
|
|
2039
|
+
content = readFileSync2(entryPath, "utf8");
|
|
2040
|
+
} catch (err) {
|
|
2041
|
+
return { kind: "skipped", reason: `unreadable: ${err.message}` };
|
|
2042
|
+
}
|
|
2043
|
+
if (content.includes("gg_pixel_init")) {
|
|
2044
|
+
return { kind: "already_present", entryPath };
|
|
2045
|
+
}
|
|
2046
|
+
const fromDir = dirname2(entryPath);
|
|
2047
|
+
const rel = relative(fromDir, initFilePath).split(sep).join("/");
|
|
2048
|
+
let moduleSpec;
|
|
2049
|
+
if (rel === "gg_pixel_init.py") {
|
|
2050
|
+
moduleSpec = "gg_pixel_init";
|
|
2051
|
+
} else if (rel.startsWith("../")) {
|
|
2052
|
+
moduleSpec = "gg_pixel_init";
|
|
2053
|
+
} else {
|
|
2054
|
+
moduleSpec = rel.replace(/\.py$/, "").replace(/\//g, ".");
|
|
2055
|
+
}
|
|
2056
|
+
const importLine = `import ${moduleSpec} # noqa: F401, E402 -- gg-pixel`;
|
|
2057
|
+
const lines = content.split("\n");
|
|
2058
|
+
let insertAt = 0;
|
|
2059
|
+
if (lines[0]?.startsWith("#!")) insertAt = 1;
|
|
2060
|
+
while (insertAt < lines.length && /^\s*(?:#.*coding[:=]|["']{3}|#)/.test(lines[insertAt] ?? "")) {
|
|
2061
|
+
insertAt++;
|
|
2062
|
+
}
|
|
2063
|
+
const updated = [...lines.slice(0, insertAt), importLine, ...lines.slice(insertAt)].join("\n");
|
|
2064
|
+
writeFileSync(entryPath, updated, "utf8");
|
|
2065
|
+
return { kind: "injected", entryPath };
|
|
2066
|
+
}
|
|
2067
|
+
function findPythonEntryFile(projectRoot) {
|
|
2068
|
+
const tryPath = (rel) => {
|
|
2069
|
+
const p = join2(projectRoot, rel);
|
|
2070
|
+
return existsSync2(p) ? p : null;
|
|
2071
|
+
};
|
|
2072
|
+
const candidates = [
|
|
2073
|
+
"main.py",
|
|
2074
|
+
"app.py",
|
|
2075
|
+
"server.py",
|
|
2076
|
+
"manage.py",
|
|
2077
|
+
"wsgi.py",
|
|
2078
|
+
"asgi.py",
|
|
2079
|
+
"__main__.py",
|
|
2080
|
+
"src/main.py",
|
|
2081
|
+
"src/app.py",
|
|
2082
|
+
"src/server.py",
|
|
2083
|
+
"src/__main__.py"
|
|
2084
|
+
];
|
|
2085
|
+
for (const c of candidates) {
|
|
2086
|
+
const found = tryPath(c);
|
|
2087
|
+
if (found) return found;
|
|
2088
|
+
}
|
|
2089
|
+
return null;
|
|
2090
|
+
}
|
|
2091
|
+
function writeProjectsMapping(projectsJsonPath, projectId, name, path, secret) {
|
|
2092
|
+
mkdirSync2(dirname2(projectsJsonPath), { recursive: true });
|
|
2093
|
+
let map = {};
|
|
2094
|
+
if (existsSync2(projectsJsonPath)) {
|
|
2095
|
+
try {
|
|
2096
|
+
map = JSON.parse(readFileSync2(projectsJsonPath, "utf8"));
|
|
2097
|
+
} catch {
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
const entry = { name, path };
|
|
2101
|
+
if (secret) entry.secret = secret;
|
|
2102
|
+
map[projectId] = entry;
|
|
2103
|
+
writeFileSync(projectsJsonPath, `${JSON.stringify(map, null, 2)}
|
|
2104
|
+
`, "utf8");
|
|
2105
|
+
}
|
|
2106
|
+
var PROBE_FINGERPRINT_PREFIX = "__pixel_install_probe__";
|
|
2107
|
+
function isInstallProbeFingerprint(fingerprint2) {
|
|
2108
|
+
return typeof fingerprint2 === "string" && fingerprint2.startsWith(PROBE_FINGERPRINT_PREFIX);
|
|
2109
|
+
}
|
|
2110
|
+
async function verifyInstall(opts) {
|
|
2111
|
+
const fetchFn = opts.fetchFn ?? fetch;
|
|
2112
|
+
const spawnFn = opts.spawnFn ?? nodeSpawn;
|
|
2113
|
+
const timeoutMs = opts.timeoutMs ?? 5e3;
|
|
2114
|
+
const ingest = opts.ingestUrl.replace(/\/+$/, "");
|
|
2115
|
+
const fingerprint2 = `${PROBE_FINGERPRINT_PREFIX}${randomBytes(6).toString("hex")}`;
|
|
2116
|
+
const start = Date.now();
|
|
2117
|
+
let method;
|
|
2118
|
+
let probeError = null;
|
|
2119
|
+
if (opts.skipChildProbe) {
|
|
2120
|
+
try {
|
|
2121
|
+
await postDirectIngest({
|
|
2122
|
+
ingestUrl: ingest,
|
|
2123
|
+
projectKey: opts.projectKey,
|
|
2124
|
+
fingerprint: fingerprint2,
|
|
2125
|
+
fetchFn
|
|
2126
|
+
});
|
|
2127
|
+
method = "direct_ingest";
|
|
2128
|
+
} catch (err) {
|
|
2129
|
+
return {
|
|
2130
|
+
kind: "failed",
|
|
2131
|
+
reason: "Direct ingest failed",
|
|
2132
|
+
hint: err.message
|
|
2133
|
+
};
|
|
2134
|
+
}
|
|
2135
|
+
} else {
|
|
2136
|
+
try {
|
|
2137
|
+
await runChildProbe({
|
|
2138
|
+
projectRoot: opts.projectRoot,
|
|
2139
|
+
ingestUrl: ingest,
|
|
2140
|
+
projectKey: opts.projectKey,
|
|
2141
|
+
fingerprint: fingerprint2,
|
|
2142
|
+
spawnFn
|
|
2143
|
+
});
|
|
2144
|
+
method = "child_process";
|
|
2145
|
+
} catch (err) {
|
|
2146
|
+
probeError = err.message;
|
|
2147
|
+
try {
|
|
2148
|
+
await postDirectIngest({
|
|
2149
|
+
ingestUrl: ingest,
|
|
2150
|
+
projectKey: opts.projectKey,
|
|
2151
|
+
fingerprint: fingerprint2,
|
|
2152
|
+
fetchFn
|
|
2153
|
+
});
|
|
2154
|
+
method = "direct_ingest";
|
|
2155
|
+
} catch (err2) {
|
|
2156
|
+
return {
|
|
2157
|
+
kind: "failed",
|
|
2158
|
+
reason: "Could not deliver probe event",
|
|
2159
|
+
hint: `child-process: ${probeError}; direct ingest: ${err2.message}`
|
|
2160
|
+
};
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
const probeRow = await pollForFingerprint({
|
|
2165
|
+
ingestUrl: ingest,
|
|
2166
|
+
projectId: opts.projectId,
|
|
2167
|
+
projectSecret: opts.projectSecret,
|
|
2168
|
+
fingerprint: fingerprint2,
|
|
2169
|
+
timeoutMs,
|
|
2170
|
+
fetchFn
|
|
2171
|
+
});
|
|
2172
|
+
if (!probeRow) {
|
|
2173
|
+
return {
|
|
2174
|
+
kind: "failed",
|
|
2175
|
+
reason: `Probe sent (${method}) but didn't appear in /api/projects/${opts.projectId}/errors within ${timeoutMs}ms`,
|
|
2176
|
+
hint: probeError ?? void 0
|
|
2177
|
+
};
|
|
2178
|
+
}
|
|
2179
|
+
try {
|
|
2180
|
+
await fetchFn(`${ingest}/api/errors/${probeRow.id}`, {
|
|
2181
|
+
method: "DELETE",
|
|
2182
|
+
headers: { authorization: `Bearer ${opts.projectSecret}` }
|
|
2183
|
+
});
|
|
2184
|
+
} catch {
|
|
2185
|
+
}
|
|
2186
|
+
return {
|
|
2187
|
+
kind: "ok",
|
|
2188
|
+
method,
|
|
2189
|
+
latencyMs: Date.now() - start
|
|
2190
|
+
};
|
|
2191
|
+
}
|
|
2192
|
+
async function runChildProbe(args) {
|
|
2193
|
+
const ggDir = join3(args.projectRoot, ".gg");
|
|
2194
|
+
if (!existsSync3(ggDir)) mkdirSync3(ggDir, { recursive: true });
|
|
2195
|
+
const probePath = join3(ggDir, `pixel-probe-${randomBytes(4).toString("hex")}.mjs`);
|
|
2196
|
+
const script = `import "@iroaxel/gg-pixel";
|
|
2197
|
+
const body = ${JSON.stringify({
|
|
2198
|
+
project_key: args.projectKey,
|
|
2199
|
+
fingerprint: args.fingerprint,
|
|
2200
|
+
type: "InstallProbe",
|
|
2201
|
+
message: "Install verification probe \u2014 auto-generated, safe to delete",
|
|
2202
|
+
stack: [],
|
|
2203
|
+
code_context: null,
|
|
2204
|
+
runtime: "",
|
|
2205
|
+
manual_report: true,
|
|
2206
|
+
level: "error"
|
|
2207
|
+
})};
|
|
2208
|
+
body.event_id = "evt_probe_" + crypto.randomUUID();
|
|
2209
|
+
body.runtime = "installer-node-" + process.versions.node;
|
|
2210
|
+
body.occurred_at = new Date().toISOString();
|
|
2211
|
+
const res = await fetch(${JSON.stringify(args.ingestUrl + "/ingest")}, {
|
|
2212
|
+
method: "POST",
|
|
2213
|
+
headers: { "content-type": "application/json" },
|
|
2214
|
+
body: JSON.stringify(body),
|
|
2215
|
+
});
|
|
2216
|
+
if (!res.ok) {
|
|
2217
|
+
const txt = await res.text().catch(() => "");
|
|
2218
|
+
console.error("probe ingest failed: status=" + res.status + " body=" + txt.slice(0, 200));
|
|
2219
|
+
process.exit(1);
|
|
2220
|
+
}
|
|
2221
|
+
`;
|
|
2222
|
+
writeFileSync2(probePath, script, "utf8");
|
|
2223
|
+
try {
|
|
2224
|
+
await new Promise((resolve2, reject) => {
|
|
2225
|
+
const opts = { cwd: args.projectRoot, stdio: "pipe" };
|
|
2226
|
+
const child = args.spawnFn("node", [probePath], opts);
|
|
2227
|
+
let stderr = "";
|
|
2228
|
+
child.stderr?.on("data", (b) => {
|
|
2229
|
+
stderr += b.toString();
|
|
2230
|
+
});
|
|
2231
|
+
const timer = setTimeout(() => {
|
|
2232
|
+
child.kill("SIGKILL");
|
|
2233
|
+
reject(new Error("Probe child timed out after 10s"));
|
|
2234
|
+
}, 1e4);
|
|
2235
|
+
child.on("error", (err) => {
|
|
2236
|
+
clearTimeout(timer);
|
|
2237
|
+
reject(err);
|
|
2238
|
+
});
|
|
2239
|
+
child.on("exit", (code) => {
|
|
2240
|
+
clearTimeout(timer);
|
|
2241
|
+
if (code === 0) resolve2();
|
|
2242
|
+
else reject(new Error(`Probe exited code=${code}; stderr: ${stderr.trim().slice(0, 400)}`));
|
|
2243
|
+
});
|
|
2244
|
+
});
|
|
2245
|
+
} finally {
|
|
2246
|
+
try {
|
|
2247
|
+
unlinkSync(probePath);
|
|
2248
|
+
} catch {
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
async function postDirectIngest(args) {
|
|
2253
|
+
const res = await args.fetchFn(`${args.ingestUrl}/ingest`, {
|
|
2254
|
+
method: "POST",
|
|
2255
|
+
headers: { "content-type": "application/json" },
|
|
2256
|
+
body: JSON.stringify({
|
|
2257
|
+
event_id: `evt_probe_${randomBytes(8).toString("hex")}`,
|
|
2258
|
+
project_key: args.projectKey,
|
|
2259
|
+
fingerprint: args.fingerprint,
|
|
2260
|
+
type: "InstallProbe",
|
|
2261
|
+
message: "Install verification probe",
|
|
2262
|
+
stack: [],
|
|
2263
|
+
code_context: null,
|
|
2264
|
+
runtime: `installer-node-${process.versions.node}`,
|
|
2265
|
+
manual_report: true,
|
|
2266
|
+
level: "error",
|
|
2267
|
+
occurred_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2268
|
+
})
|
|
2269
|
+
});
|
|
2270
|
+
if (!res.ok) {
|
|
2271
|
+
let body = "";
|
|
2272
|
+
try {
|
|
2273
|
+
body = await res.text();
|
|
2274
|
+
} catch {
|
|
2275
|
+
}
|
|
2276
|
+
throw new Error(`POST /ingest \u2192 ${res.status} ${body.slice(0, 200)}`);
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
async function pollForFingerprint(args) {
|
|
2280
|
+
const deadline = Date.now() + args.timeoutMs;
|
|
2281
|
+
while (Date.now() < deadline) {
|
|
2282
|
+
try {
|
|
2283
|
+
const res = await args.fetchFn(`${args.ingestUrl}/api/projects/${args.projectId}/errors`, {
|
|
2284
|
+
headers: { authorization: `Bearer ${args.projectSecret}` }
|
|
2285
|
+
});
|
|
2286
|
+
if (res.ok) {
|
|
2287
|
+
const body = await res.json();
|
|
2288
|
+
const match = body.errors.find((e) => e.fingerprint === args.fingerprint);
|
|
2289
|
+
if (match) return { id: match.id };
|
|
2290
|
+
}
|
|
2291
|
+
} catch {
|
|
2292
|
+
}
|
|
2293
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
2294
|
+
}
|
|
2295
|
+
return null;
|
|
2296
|
+
}
|
|
2297
|
+
|
|
2298
|
+
export {
|
|
2299
|
+
source_default,
|
|
2300
|
+
DEFAULT_INGEST_URL,
|
|
2301
|
+
install,
|
|
2302
|
+
isInstallProbeFingerprint,
|
|
2303
|
+
verifyInstall
|
|
2304
|
+
};
|
|
2305
|
+
//# sourceMappingURL=chunk-YKKUCYNE.js.map
|