@kenkaiiii/gg-pixel 4.3.72 → 4.3.74
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +307 -11
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +299 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +307 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -408,7 +408,14 @@ var LocalSqliteSink = class {
|
|
|
408
408
|
};
|
|
409
409
|
|
|
410
410
|
// src/install.ts
|
|
411
|
-
import {
|
|
411
|
+
import {
|
|
412
|
+
existsSync as existsSync2,
|
|
413
|
+
readFileSync as readFileSync2,
|
|
414
|
+
writeFileSync,
|
|
415
|
+
appendFileSync,
|
|
416
|
+
mkdirSync as mkdirSync2,
|
|
417
|
+
readdirSync
|
|
418
|
+
} from "fs";
|
|
412
419
|
import { homedir as homedir2 } from "os";
|
|
413
420
|
import { dirname as dirname2, join as join2, relative, resolve, sep } from "path";
|
|
414
421
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
@@ -420,17 +427,25 @@ async function install(opts = {}) {
|
|
|
420
427
|
const home = opts.homeDir ?? homedir2();
|
|
421
428
|
const nodeRoot = findProjectRoot(cwd);
|
|
422
429
|
const pythonRoot = findPythonProjectRoot(cwd);
|
|
423
|
-
|
|
430
|
+
const goRoot = findGoProjectRoot(cwd);
|
|
431
|
+
const rubyRoot = findRubyProjectRoot(cwd);
|
|
432
|
+
if (!nodeRoot && !pythonRoot && !goRoot && !rubyRoot) {
|
|
424
433
|
throw new Error(
|
|
425
|
-
`No project found at ${cwd}: looked for package.json
|
|
434
|
+
`No project found at ${cwd}: looked for package.json, pyproject.toml/setup.py/requirements.txt/Pipfile, go.mod, Gemfile/*.gemspec.`
|
|
426
435
|
);
|
|
427
436
|
}
|
|
428
|
-
const
|
|
429
|
-
if (
|
|
437
|
+
const closestRoot = pickClosestRoot([nodeRoot, pythonRoot, goRoot, rubyRoot]);
|
|
438
|
+
if (closestRoot === goRoot && goRoot) {
|
|
439
|
+
return installGo({ projectRoot: goRoot, opts, ingestUrl, fetchFn, home });
|
|
440
|
+
}
|
|
441
|
+
if (closestRoot === rubyRoot && rubyRoot) {
|
|
442
|
+
return installRuby({ projectRoot: rubyRoot, opts, ingestUrl, fetchFn, home });
|
|
443
|
+
}
|
|
444
|
+
if (closestRoot === pythonRoot && pythonRoot) {
|
|
430
445
|
return installPython({ projectRoot: pythonRoot, opts, ingestUrl, fetchFn, home });
|
|
431
446
|
}
|
|
432
447
|
if (!nodeRoot) {
|
|
433
|
-
throw new Error("Internal:
|
|
448
|
+
throw new Error("Internal: closest root is Node but nodeRoot is null");
|
|
434
449
|
}
|
|
435
450
|
const pkgPath = join2(nodeRoot, "package.json");
|
|
436
451
|
const pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
|
|
@@ -666,6 +681,9 @@ function isCommonJsEntry(entryPath, pkg) {
|
|
|
666
681
|
}
|
|
667
682
|
function detectJsProjectKind(pkg, projectRoot) {
|
|
668
683
|
const all = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
684
|
+
if (existsSync2(join2(projectRoot, "wrangler.toml")) || existsSync2(join2(projectRoot, "wrangler.jsonc")) || existsSync2(join2(projectRoot, "wrangler.json"))) {
|
|
685
|
+
return "cloudflare-workers";
|
|
686
|
+
}
|
|
669
687
|
if ("electron" in all) return "electron";
|
|
670
688
|
if (existsSync2(join2(projectRoot, "src-tauri")) || "@tauri-apps/api" in all) return "tauri";
|
|
671
689
|
if ("react-native" in all) return "react-native";
|
|
@@ -696,8 +714,12 @@ function wireFramework(w) {
|
|
|
696
714
|
return wireTauri(w);
|
|
697
715
|
case "react-native":
|
|
698
716
|
return wireReactNative(w);
|
|
717
|
+
case "cloudflare-workers":
|
|
718
|
+
return wireWorkers(w);
|
|
699
719
|
case "python":
|
|
700
|
-
|
|
720
|
+
case "go":
|
|
721
|
+
case "ruby":
|
|
722
|
+
throw new Error(`Internal: ${w.kind} should have been handled earlier`);
|
|
701
723
|
}
|
|
702
724
|
}
|
|
703
725
|
function wireNode({ projectRoot, pkg, ingestUrl }) {
|
|
@@ -723,6 +745,7 @@ function wireNextjs({ projectRoot, projectKey, ingestUrl }) {
|
|
|
723
745
|
const serverInitPath = pickPath(projectRoot, ["instrumentation.ts", "instrumentation.js"]);
|
|
724
746
|
const finalServerPath = serverInitPath ?? join2(projectRoot, "instrumentation.ts");
|
|
725
747
|
writeNextInstrumentation(finalServerPath, ingestUrl);
|
|
748
|
+
patchNextConfig(projectRoot);
|
|
726
749
|
const clientInitPath = join2(projectRoot, "gg-pixel.client.mjs");
|
|
727
750
|
writeFileSync(clientInitPath, renderBrowserInitFile(ingestUrl, projectKey), "utf8");
|
|
728
751
|
const layoutPath = findNextLayout(projectRoot);
|
|
@@ -795,6 +818,56 @@ function findNextLayout(projectRoot) {
|
|
|
795
818
|
}
|
|
796
819
|
return null;
|
|
797
820
|
}
|
|
821
|
+
function patchNextConfig(projectRoot) {
|
|
822
|
+
const candidates = ["next.config.ts", "next.config.mjs", "next.config.js", "next.config.cjs"];
|
|
823
|
+
let configPath = null;
|
|
824
|
+
for (const c of candidates) {
|
|
825
|
+
const p = join2(projectRoot, c);
|
|
826
|
+
if (existsSync2(p)) {
|
|
827
|
+
configPath = p;
|
|
828
|
+
break;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
if (!configPath) {
|
|
832
|
+
configPath = join2(projectRoot, "next.config.ts");
|
|
833
|
+
writeFileSync(
|
|
834
|
+
configPath,
|
|
835
|
+
`import type { NextConfig } from "next";
|
|
836
|
+
|
|
837
|
+
const nextConfig: NextConfig = {
|
|
838
|
+
// Keeps Next's bundler from trying to compile better-sqlite3 (native dep).
|
|
839
|
+
serverExternalPackages: ["@kenkaiiii/gg-pixel"],
|
|
840
|
+
};
|
|
841
|
+
|
|
842
|
+
export default nextConfig;
|
|
843
|
+
`,
|
|
844
|
+
"utf8"
|
|
845
|
+
);
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
const content = readFileSync2(configPath, "utf8");
|
|
849
|
+
if (content.includes("@kenkaiiii/gg-pixel")) return;
|
|
850
|
+
if (content.includes("serverExternalPackages")) {
|
|
851
|
+
const updated = content.replace(
|
|
852
|
+
/serverExternalPackages\s*:\s*\[([^\]]*)\]/,
|
|
853
|
+
(_match, inside) => {
|
|
854
|
+
const trimmed = inside.trim();
|
|
855
|
+
const sep2 = trimmed.length > 0 ? ", " : "";
|
|
856
|
+
return `serverExternalPackages: [${trimmed}${sep2}"@kenkaiiii/gg-pixel"]`;
|
|
857
|
+
}
|
|
858
|
+
);
|
|
859
|
+
if (updated !== content) writeFileSync(configPath, updated, "utf8");
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
const objStart = /(const\s+\w+\s*:\s*NextConfig\s*=\s*\{|module\.exports\s*=\s*\{|export\s+default\s*\{)/;
|
|
863
|
+
const m = objStart.exec(content);
|
|
864
|
+
if (m) {
|
|
865
|
+
const insertAt = m.index + m[0].length;
|
|
866
|
+
const updated = content.slice(0, insertAt) + `
|
|
867
|
+
serverExternalPackages: ["@kenkaiiii/gg-pixel"],` + content.slice(insertAt);
|
|
868
|
+
writeFileSync(configPath, updated, "utf8");
|
|
869
|
+
}
|
|
870
|
+
}
|
|
798
871
|
function wireSveltekit({ projectRoot, projectKey, ingestUrl }) {
|
|
799
872
|
const serverPath = join2(projectRoot, "src/hooks.server.ts");
|
|
800
873
|
const clientPath = join2(projectRoot, "src/hooks.client.ts");
|
|
@@ -956,6 +1029,48 @@ function wireTauri({ projectRoot, pkg, projectKey, ingestUrl }) {
|
|
|
956
1029
|
]
|
|
957
1030
|
};
|
|
958
1031
|
}
|
|
1032
|
+
function wireWorkers({ projectRoot, projectKey, ingestUrl }) {
|
|
1033
|
+
const initPath = join2(projectRoot, "gg-pixel.workers.snippet.ts");
|
|
1034
|
+
writeFileSync(
|
|
1035
|
+
initPath,
|
|
1036
|
+
`// gg-pixel \u2014 Cloudflare Workers wiring snippet.
|
|
1037
|
+
// Auto-generated by ggcoder pixel install. Wrap your default export with
|
|
1038
|
+
// withPixel(...) so any throw in your handler is auto-reported. Example:
|
|
1039
|
+
//
|
|
1040
|
+
// import { withPixel } from "@kenkaiiii/gg-pixel/workers";
|
|
1041
|
+
//
|
|
1042
|
+
// export default withPixel(
|
|
1043
|
+
// { projectKey: ${JSON.stringify(projectKey)} },
|
|
1044
|
+
// {
|
|
1045
|
+
// async fetch(req, env, ctx) { /* your code */ },
|
|
1046
|
+
// async scheduled(evt, env, ctx) { /* your code */ },
|
|
1047
|
+
// },
|
|
1048
|
+
// );
|
|
1049
|
+
//
|
|
1050
|
+
// For manual reports inside a handler:
|
|
1051
|
+
//
|
|
1052
|
+
// import { reportPixel } from "@kenkaiiii/gg-pixel/workers";
|
|
1053
|
+
// reportPixel(ctx, { projectKey: ${JSON.stringify(projectKey)} }, {
|
|
1054
|
+
// message: "user clicked the broken button",
|
|
1055
|
+
// });
|
|
1056
|
+
//
|
|
1057
|
+
// Your project_key is publishable \u2014 safe to commit.
|
|
1058
|
+
|
|
1059
|
+
import { withPixel, reportPixel } from "@kenkaiiii/gg-pixel/workers";
|
|
1060
|
+
export const PIXEL_KEY = ${JSON.stringify(projectKey)};
|
|
1061
|
+
export const PIXEL_INGEST = ${JSON.stringify(ingestUrl)};
|
|
1062
|
+
export { withPixel, reportPixel };
|
|
1063
|
+
`,
|
|
1064
|
+
"utf8"
|
|
1065
|
+
);
|
|
1066
|
+
return {
|
|
1067
|
+
primaryInitPath: initPath,
|
|
1068
|
+
entryWiring: { kind: "no_entry_found" },
|
|
1069
|
+
warnings: [
|
|
1070
|
+
`Cloudflare Workers default exports can't be auto-wrapped safely. Open ${initPath} for a 3-line snippet you can paste into your worker.`
|
|
1071
|
+
]
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
959
1074
|
function wireReactNative({ projectRoot }) {
|
|
960
1075
|
return {
|
|
961
1076
|
primaryInitPath: join2(projectRoot, "(not-installed)"),
|
|
@@ -1061,10 +1176,191 @@ function detectPythonPackageManager(projectRoot) {
|
|
|
1061
1176
|
if (existsSync2(join2(projectRoot, "Pipfile.lock"))) return "pipenv";
|
|
1062
1177
|
return "pip";
|
|
1063
1178
|
}
|
|
1064
|
-
function
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1179
|
+
function pickClosestRoot(roots) {
|
|
1180
|
+
let best = null;
|
|
1181
|
+
for (const r of roots) {
|
|
1182
|
+
if (!r) continue;
|
|
1183
|
+
if (!best || r.length > best.length) best = r;
|
|
1184
|
+
}
|
|
1185
|
+
return best;
|
|
1186
|
+
}
|
|
1187
|
+
var GO_MARKER = "go.mod";
|
|
1188
|
+
var RUBY_MARKERS = ["Gemfile"];
|
|
1189
|
+
function findGoProjectRoot(start) {
|
|
1190
|
+
let dir = start;
|
|
1191
|
+
for (let i = 0; i < 20; i++) {
|
|
1192
|
+
if (existsSync2(join2(dir, GO_MARKER))) return dir;
|
|
1193
|
+
const parent = dirname2(dir);
|
|
1194
|
+
if (parent === dir) return null;
|
|
1195
|
+
dir = parent;
|
|
1196
|
+
}
|
|
1197
|
+
return null;
|
|
1198
|
+
}
|
|
1199
|
+
function findRubyProjectRoot(start) {
|
|
1200
|
+
let dir = start;
|
|
1201
|
+
for (let i = 0; i < 20; i++) {
|
|
1202
|
+
for (const m of RUBY_MARKERS) {
|
|
1203
|
+
if (existsSync2(join2(dir, m))) return dir;
|
|
1204
|
+
}
|
|
1205
|
+
try {
|
|
1206
|
+
const entries = readdirSync(dir);
|
|
1207
|
+
if (entries.some((e) => e.endsWith(".gemspec"))) return dir;
|
|
1208
|
+
} catch {
|
|
1209
|
+
}
|
|
1210
|
+
const parent = dirname2(dir);
|
|
1211
|
+
if (parent === dir) return null;
|
|
1212
|
+
dir = parent;
|
|
1213
|
+
}
|
|
1214
|
+
return null;
|
|
1215
|
+
}
|
|
1216
|
+
async function installGo(ctx) {
|
|
1217
|
+
const { projectRoot, opts, ingestUrl, fetchFn, home } = ctx;
|
|
1218
|
+
const projectName = opts.projectName ?? readGoModuleName(projectRoot) ?? projectRoot.split("/").pop() ?? "unnamed";
|
|
1219
|
+
const projectsJsonPath = join2(home, ".gg", "projects.json");
|
|
1220
|
+
const envFilePath = join2(projectRoot, ".env");
|
|
1221
|
+
const existing = findMappingByPath(projectsJsonPath, projectRoot);
|
|
1222
|
+
const existingKey = readEnvKey(envFilePath, "GG_PIXEL_KEY");
|
|
1223
|
+
let created;
|
|
1224
|
+
let reused = false;
|
|
1225
|
+
if (existing && existingKey) {
|
|
1226
|
+
created = { id: existing.id, key: existingKey };
|
|
1227
|
+
reused = true;
|
|
1228
|
+
} else {
|
|
1229
|
+
created = await createProject(fetchFn, ingestUrl, projectName);
|
|
1230
|
+
}
|
|
1231
|
+
const packageInstalled = opts.skipPackageInstall ? false : runGoGet(projectRoot);
|
|
1232
|
+
const initFilePath = join2(projectRoot, "gg_pixel_init.go");
|
|
1233
|
+
writeFileSync(
|
|
1234
|
+
initFilePath,
|
|
1235
|
+
`// gg-pixel init \u2014 auto-generated by ggcoder pixel install.
|
|
1236
|
+
package main
|
|
1237
|
+
|
|
1238
|
+
import (
|
|
1239
|
+
"os"
|
|
1240
|
+
gg "github.com/kenkaiiii/gg-pixel-go"
|
|
1241
|
+
)
|
|
1242
|
+
|
|
1243
|
+
func init() {
|
|
1244
|
+
key := os.Getenv("GG_PIXEL_KEY")
|
|
1245
|
+
if key == "" {
|
|
1246
|
+
key = ${JSON.stringify(created.key)}
|
|
1247
|
+
}
|
|
1248
|
+
_ = gg.Init(gg.Options{ProjectKey: key, IngestURL: ${JSON.stringify(`${ingestUrl}/ingest`)}})
|
|
1249
|
+
}
|
|
1250
|
+
`,
|
|
1251
|
+
"utf8"
|
|
1252
|
+
);
|
|
1253
|
+
writeEnvKey(envFilePath, "GG_PIXEL_KEY", created.key);
|
|
1254
|
+
writeProjectsMapping(projectsJsonPath, created.id, projectName, projectRoot);
|
|
1255
|
+
return {
|
|
1256
|
+
projectId: created.id,
|
|
1257
|
+
projectKey: created.key,
|
|
1258
|
+
projectName,
|
|
1259
|
+
projectKind: "go",
|
|
1260
|
+
initFilePath,
|
|
1261
|
+
envFilePath,
|
|
1262
|
+
projectsJsonPath,
|
|
1263
|
+
packageManager: "pip",
|
|
1264
|
+
packageInstalled,
|
|
1265
|
+
entryWiring: { kind: "no_entry_found" },
|
|
1266
|
+
reused,
|
|
1267
|
+
warnings: [
|
|
1268
|
+
"Add `defer ggpixel.Recover()` near the top of your main() so panics are captured before the process exits."
|
|
1269
|
+
]
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
function readGoModuleName(projectRoot) {
|
|
1273
|
+
try {
|
|
1274
|
+
const content = readFileSync2(join2(projectRoot, "go.mod"), "utf8");
|
|
1275
|
+
const match = /^\s*module\s+(\S+)\s*$/m.exec(content);
|
|
1276
|
+
if (!match) return null;
|
|
1277
|
+
return match[1].split("/").pop() ?? null;
|
|
1278
|
+
} catch {
|
|
1279
|
+
return null;
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
function runGoGet(projectRoot) {
|
|
1283
|
+
const result = spawnSync2("go", ["get", "github.com/kenkaiiii/gg-pixel-go@latest"], {
|
|
1284
|
+
cwd: projectRoot,
|
|
1285
|
+
stdio: "inherit"
|
|
1286
|
+
});
|
|
1287
|
+
return result.status === 0;
|
|
1288
|
+
}
|
|
1289
|
+
async function installRuby(ctx) {
|
|
1290
|
+
const { projectRoot, opts, ingestUrl, fetchFn, home } = ctx;
|
|
1291
|
+
const projectName = opts.projectName ?? readRubyAppName(projectRoot) ?? projectRoot.split("/").pop() ?? "unnamed";
|
|
1292
|
+
const projectsJsonPath = join2(home, ".gg", "projects.json");
|
|
1293
|
+
const envFilePath = join2(projectRoot, ".env");
|
|
1294
|
+
const existing = findMappingByPath(projectsJsonPath, projectRoot);
|
|
1295
|
+
const existingKey = readEnvKey(envFilePath, "GG_PIXEL_KEY");
|
|
1296
|
+
let created;
|
|
1297
|
+
let reused = false;
|
|
1298
|
+
if (existing && existingKey) {
|
|
1299
|
+
created = { id: existing.id, key: existingKey };
|
|
1300
|
+
reused = true;
|
|
1301
|
+
} else {
|
|
1302
|
+
created = await createProject(fetchFn, ingestUrl, projectName);
|
|
1303
|
+
}
|
|
1304
|
+
const packageInstalled = opts.skipPackageInstall ? false : runRubyInstall(projectRoot);
|
|
1305
|
+
const initFilePath = join2(projectRoot, "gg_pixel_init.rb");
|
|
1306
|
+
writeFileSync(
|
|
1307
|
+
initFilePath,
|
|
1308
|
+
`# gg-pixel init \u2014 auto-generated by ggcoder pixel install.
|
|
1309
|
+
require "gg_pixel"
|
|
1310
|
+
GGPixel.init(
|
|
1311
|
+
project_key: ENV["GG_PIXEL_KEY"] || ${JSON.stringify(created.key)},
|
|
1312
|
+
ingest_url: ${JSON.stringify(`${ingestUrl}/ingest`)},
|
|
1313
|
+
)
|
|
1314
|
+
`,
|
|
1315
|
+
"utf8"
|
|
1316
|
+
);
|
|
1317
|
+
writeEnvKey(envFilePath, "GG_PIXEL_KEY", created.key);
|
|
1318
|
+
writeProjectsMapping(projectsJsonPath, created.id, projectName, projectRoot);
|
|
1319
|
+
return {
|
|
1320
|
+
projectId: created.id,
|
|
1321
|
+
projectKey: created.key,
|
|
1322
|
+
projectName,
|
|
1323
|
+
projectKind: "ruby",
|
|
1324
|
+
initFilePath,
|
|
1325
|
+
envFilePath,
|
|
1326
|
+
projectsJsonPath,
|
|
1327
|
+
packageManager: "pip",
|
|
1328
|
+
packageInstalled,
|
|
1329
|
+
entryWiring: { kind: "no_entry_found" },
|
|
1330
|
+
reused,
|
|
1331
|
+
warnings: [
|
|
1332
|
+
`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).`
|
|
1333
|
+
]
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
function readRubyAppName(projectRoot) {
|
|
1337
|
+
try {
|
|
1338
|
+
const entries = readdirSync(projectRoot);
|
|
1339
|
+
const gemspec = entries.find((e) => e.endsWith(".gemspec"));
|
|
1340
|
+
if (!gemspec) return null;
|
|
1341
|
+
return gemspec.replace(/\.gemspec$/, "");
|
|
1342
|
+
} catch {
|
|
1343
|
+
return null;
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
function runRubyInstall(projectRoot) {
|
|
1347
|
+
if (existsSync2(join2(projectRoot, "Gemfile"))) {
|
|
1348
|
+
try {
|
|
1349
|
+
const content = readFileSync2(join2(projectRoot, "Gemfile"), "utf8");
|
|
1350
|
+
if (!content.includes("gg_pixel")) {
|
|
1351
|
+
writeFileSync(
|
|
1352
|
+
join2(projectRoot, "Gemfile"),
|
|
1353
|
+
content + (content.endsWith("\n") ? "" : "\n") + 'gem "gg_pixel"\n',
|
|
1354
|
+
"utf8"
|
|
1355
|
+
);
|
|
1356
|
+
}
|
|
1357
|
+
} catch {
|
|
1358
|
+
}
|
|
1359
|
+
const r = spawnSync2("bundle", ["install"], { cwd: projectRoot, stdio: "inherit" });
|
|
1360
|
+
if (r.status === 0) return true;
|
|
1361
|
+
}
|
|
1362
|
+
const r2 = spawnSync2("gem", ["install", "gg_pixel"], { cwd: projectRoot, stdio: "inherit" });
|
|
1363
|
+
return r2.status === 0;
|
|
1068
1364
|
}
|
|
1069
1365
|
async function installPython(ctx) {
|
|
1070
1366
|
const { projectRoot, opts, ingestUrl, fetchFn, home } = ctx;
|