@kenkaiiii/gg-pixel 4.3.72 → 4.3.73
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 +256 -11
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +248 -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 +256 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/install.ts
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
existsSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
writeFileSync,
|
|
8
|
+
appendFileSync,
|
|
9
|
+
mkdirSync,
|
|
10
|
+
readdirSync
|
|
11
|
+
} from "fs";
|
|
5
12
|
import { homedir } from "os";
|
|
6
13
|
import { dirname, join, relative, resolve, sep } from "path";
|
|
7
14
|
import { spawnSync } from "child_process";
|
|
@@ -13,17 +20,25 @@ async function install(opts = {}) {
|
|
|
13
20
|
const home = opts.homeDir ?? homedir();
|
|
14
21
|
const nodeRoot = findProjectRoot(cwd);
|
|
15
22
|
const pythonRoot = findPythonProjectRoot(cwd);
|
|
16
|
-
|
|
23
|
+
const goRoot = findGoProjectRoot(cwd);
|
|
24
|
+
const rubyRoot = findRubyProjectRoot(cwd);
|
|
25
|
+
if (!nodeRoot && !pythonRoot && !goRoot && !rubyRoot) {
|
|
17
26
|
throw new Error(
|
|
18
|
-
`No project found at ${cwd}: looked for package.json
|
|
27
|
+
`No project found at ${cwd}: looked for package.json, pyproject.toml/setup.py/requirements.txt/Pipfile, go.mod, Gemfile/*.gemspec.`
|
|
19
28
|
);
|
|
20
29
|
}
|
|
21
|
-
const
|
|
22
|
-
if (
|
|
30
|
+
const closestRoot = pickClosestRoot([nodeRoot, pythonRoot, goRoot, rubyRoot]);
|
|
31
|
+
if (closestRoot === goRoot && goRoot) {
|
|
32
|
+
return installGo({ projectRoot: goRoot, opts, ingestUrl, fetchFn, home });
|
|
33
|
+
}
|
|
34
|
+
if (closestRoot === rubyRoot && rubyRoot) {
|
|
35
|
+
return installRuby({ projectRoot: rubyRoot, opts, ingestUrl, fetchFn, home });
|
|
36
|
+
}
|
|
37
|
+
if (closestRoot === pythonRoot && pythonRoot) {
|
|
23
38
|
return installPython({ projectRoot: pythonRoot, opts, ingestUrl, fetchFn, home });
|
|
24
39
|
}
|
|
25
40
|
if (!nodeRoot) {
|
|
26
|
-
throw new Error("Internal:
|
|
41
|
+
throw new Error("Internal: closest root is Node but nodeRoot is null");
|
|
27
42
|
}
|
|
28
43
|
const pkgPath = join(nodeRoot, "package.json");
|
|
29
44
|
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
@@ -259,6 +274,9 @@ function isCommonJsEntry(entryPath, pkg) {
|
|
|
259
274
|
}
|
|
260
275
|
function detectJsProjectKind(pkg, projectRoot) {
|
|
261
276
|
const all = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
277
|
+
if (existsSync(join(projectRoot, "wrangler.toml")) || existsSync(join(projectRoot, "wrangler.jsonc")) || existsSync(join(projectRoot, "wrangler.json"))) {
|
|
278
|
+
return "cloudflare-workers";
|
|
279
|
+
}
|
|
262
280
|
if ("electron" in all) return "electron";
|
|
263
281
|
if (existsSync(join(projectRoot, "src-tauri")) || "@tauri-apps/api" in all) return "tauri";
|
|
264
282
|
if ("react-native" in all) return "react-native";
|
|
@@ -289,8 +307,12 @@ function wireFramework(w) {
|
|
|
289
307
|
return wireTauri(w);
|
|
290
308
|
case "react-native":
|
|
291
309
|
return wireReactNative(w);
|
|
310
|
+
case "cloudflare-workers":
|
|
311
|
+
return wireWorkers(w);
|
|
292
312
|
case "python":
|
|
293
|
-
|
|
313
|
+
case "go":
|
|
314
|
+
case "ruby":
|
|
315
|
+
throw new Error(`Internal: ${w.kind} should have been handled earlier`);
|
|
294
316
|
}
|
|
295
317
|
}
|
|
296
318
|
function wireNode({ projectRoot, pkg, ingestUrl }) {
|
|
@@ -549,6 +571,48 @@ function wireTauri({ projectRoot, pkg, projectKey, ingestUrl }) {
|
|
|
549
571
|
]
|
|
550
572
|
};
|
|
551
573
|
}
|
|
574
|
+
function wireWorkers({ projectRoot, projectKey, ingestUrl }) {
|
|
575
|
+
const initPath = join(projectRoot, "gg-pixel.workers.snippet.ts");
|
|
576
|
+
writeFileSync(
|
|
577
|
+
initPath,
|
|
578
|
+
`// gg-pixel \u2014 Cloudflare Workers wiring snippet.
|
|
579
|
+
// Auto-generated by ggcoder pixel install. Wrap your default export with
|
|
580
|
+
// withPixel(...) so any throw in your handler is auto-reported. Example:
|
|
581
|
+
//
|
|
582
|
+
// import { withPixel } from "@kenkaiiii/gg-pixel/workers";
|
|
583
|
+
//
|
|
584
|
+
// export default withPixel(
|
|
585
|
+
// { projectKey: ${JSON.stringify(projectKey)} },
|
|
586
|
+
// {
|
|
587
|
+
// async fetch(req, env, ctx) { /* your code */ },
|
|
588
|
+
// async scheduled(evt, env, ctx) { /* your code */ },
|
|
589
|
+
// },
|
|
590
|
+
// );
|
|
591
|
+
//
|
|
592
|
+
// For manual reports inside a handler:
|
|
593
|
+
//
|
|
594
|
+
// import { reportPixel } from "@kenkaiiii/gg-pixel/workers";
|
|
595
|
+
// reportPixel(ctx, { projectKey: ${JSON.stringify(projectKey)} }, {
|
|
596
|
+
// message: "user clicked the broken button",
|
|
597
|
+
// });
|
|
598
|
+
//
|
|
599
|
+
// Your project_key is publishable \u2014 safe to commit.
|
|
600
|
+
|
|
601
|
+
import { withPixel, reportPixel } from "@kenkaiiii/gg-pixel/workers";
|
|
602
|
+
export const PIXEL_KEY = ${JSON.stringify(projectKey)};
|
|
603
|
+
export const PIXEL_INGEST = ${JSON.stringify(ingestUrl)};
|
|
604
|
+
export { withPixel, reportPixel };
|
|
605
|
+
`,
|
|
606
|
+
"utf8"
|
|
607
|
+
);
|
|
608
|
+
return {
|
|
609
|
+
primaryInitPath: initPath,
|
|
610
|
+
entryWiring: { kind: "no_entry_found" },
|
|
611
|
+
warnings: [
|
|
612
|
+
`Cloudflare Workers default exports can't be auto-wrapped safely. Open ${initPath} for a 3-line snippet you can paste into your worker.`
|
|
613
|
+
]
|
|
614
|
+
};
|
|
615
|
+
}
|
|
552
616
|
function wireReactNative({ projectRoot }) {
|
|
553
617
|
return {
|
|
554
618
|
primaryInitPath: join(projectRoot, "(not-installed)"),
|
|
@@ -654,10 +718,191 @@ function detectPythonPackageManager(projectRoot) {
|
|
|
654
718
|
if (existsSync(join(projectRoot, "Pipfile.lock"))) return "pipenv";
|
|
655
719
|
return "pip";
|
|
656
720
|
}
|
|
657
|
-
function
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
721
|
+
function pickClosestRoot(roots) {
|
|
722
|
+
let best = null;
|
|
723
|
+
for (const r of roots) {
|
|
724
|
+
if (!r) continue;
|
|
725
|
+
if (!best || r.length > best.length) best = r;
|
|
726
|
+
}
|
|
727
|
+
return best;
|
|
728
|
+
}
|
|
729
|
+
var GO_MARKER = "go.mod";
|
|
730
|
+
var RUBY_MARKERS = ["Gemfile"];
|
|
731
|
+
function findGoProjectRoot(start) {
|
|
732
|
+
let dir = start;
|
|
733
|
+
for (let i = 0; i < 20; i++) {
|
|
734
|
+
if (existsSync(join(dir, GO_MARKER))) return dir;
|
|
735
|
+
const parent = dirname(dir);
|
|
736
|
+
if (parent === dir) return null;
|
|
737
|
+
dir = parent;
|
|
738
|
+
}
|
|
739
|
+
return null;
|
|
740
|
+
}
|
|
741
|
+
function findRubyProjectRoot(start) {
|
|
742
|
+
let dir = start;
|
|
743
|
+
for (let i = 0; i < 20; i++) {
|
|
744
|
+
for (const m of RUBY_MARKERS) {
|
|
745
|
+
if (existsSync(join(dir, m))) return dir;
|
|
746
|
+
}
|
|
747
|
+
try {
|
|
748
|
+
const entries = readdirSync(dir);
|
|
749
|
+
if (entries.some((e) => e.endsWith(".gemspec"))) return dir;
|
|
750
|
+
} catch {
|
|
751
|
+
}
|
|
752
|
+
const parent = dirname(dir);
|
|
753
|
+
if (parent === dir) return null;
|
|
754
|
+
dir = parent;
|
|
755
|
+
}
|
|
756
|
+
return null;
|
|
757
|
+
}
|
|
758
|
+
async function installGo(ctx) {
|
|
759
|
+
const { projectRoot, opts, ingestUrl, fetchFn, home } = ctx;
|
|
760
|
+
const projectName = opts.projectName ?? readGoModuleName(projectRoot) ?? projectRoot.split("/").pop() ?? "unnamed";
|
|
761
|
+
const projectsJsonPath = join(home, ".gg", "projects.json");
|
|
762
|
+
const envFilePath = join(projectRoot, ".env");
|
|
763
|
+
const existing = findMappingByPath(projectsJsonPath, projectRoot);
|
|
764
|
+
const existingKey = readEnvKey(envFilePath, "GG_PIXEL_KEY");
|
|
765
|
+
let created;
|
|
766
|
+
let reused = false;
|
|
767
|
+
if (existing && existingKey) {
|
|
768
|
+
created = { id: existing.id, key: existingKey };
|
|
769
|
+
reused = true;
|
|
770
|
+
} else {
|
|
771
|
+
created = await createProject(fetchFn, ingestUrl, projectName);
|
|
772
|
+
}
|
|
773
|
+
const packageInstalled = opts.skipPackageInstall ? false : runGoGet(projectRoot);
|
|
774
|
+
const initFilePath = join(projectRoot, "gg_pixel_init.go");
|
|
775
|
+
writeFileSync(
|
|
776
|
+
initFilePath,
|
|
777
|
+
`// gg-pixel init \u2014 auto-generated by ggcoder pixel install.
|
|
778
|
+
package main
|
|
779
|
+
|
|
780
|
+
import (
|
|
781
|
+
"os"
|
|
782
|
+
gg "github.com/kenkaiiii/gg-pixel-go"
|
|
783
|
+
)
|
|
784
|
+
|
|
785
|
+
func init() {
|
|
786
|
+
key := os.Getenv("GG_PIXEL_KEY")
|
|
787
|
+
if key == "" {
|
|
788
|
+
key = ${JSON.stringify(created.key)}
|
|
789
|
+
}
|
|
790
|
+
_ = gg.Init(gg.Options{ProjectKey: key, IngestURL: ${JSON.stringify(`${ingestUrl}/ingest`)}})
|
|
791
|
+
}
|
|
792
|
+
`,
|
|
793
|
+
"utf8"
|
|
794
|
+
);
|
|
795
|
+
writeEnvKey(envFilePath, "GG_PIXEL_KEY", created.key);
|
|
796
|
+
writeProjectsMapping(projectsJsonPath, created.id, projectName, projectRoot);
|
|
797
|
+
return {
|
|
798
|
+
projectId: created.id,
|
|
799
|
+
projectKey: created.key,
|
|
800
|
+
projectName,
|
|
801
|
+
projectKind: "go",
|
|
802
|
+
initFilePath,
|
|
803
|
+
envFilePath,
|
|
804
|
+
projectsJsonPath,
|
|
805
|
+
packageManager: "pip",
|
|
806
|
+
packageInstalled,
|
|
807
|
+
entryWiring: { kind: "no_entry_found" },
|
|
808
|
+
reused,
|
|
809
|
+
warnings: [
|
|
810
|
+
"Add `defer ggpixel.Recover()` near the top of your main() so panics are captured before the process exits."
|
|
811
|
+
]
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
function readGoModuleName(projectRoot) {
|
|
815
|
+
try {
|
|
816
|
+
const content = readFileSync(join(projectRoot, "go.mod"), "utf8");
|
|
817
|
+
const match = /^\s*module\s+(\S+)\s*$/m.exec(content);
|
|
818
|
+
if (!match) return null;
|
|
819
|
+
return match[1].split("/").pop() ?? null;
|
|
820
|
+
} catch {
|
|
821
|
+
return null;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
function runGoGet(projectRoot) {
|
|
825
|
+
const result = spawnSync("go", ["get", "github.com/kenkaiiii/gg-pixel-go@latest"], {
|
|
826
|
+
cwd: projectRoot,
|
|
827
|
+
stdio: "inherit"
|
|
828
|
+
});
|
|
829
|
+
return result.status === 0;
|
|
830
|
+
}
|
|
831
|
+
async function installRuby(ctx) {
|
|
832
|
+
const { projectRoot, opts, ingestUrl, fetchFn, home } = ctx;
|
|
833
|
+
const projectName = opts.projectName ?? readRubyAppName(projectRoot) ?? projectRoot.split("/").pop() ?? "unnamed";
|
|
834
|
+
const projectsJsonPath = join(home, ".gg", "projects.json");
|
|
835
|
+
const envFilePath = join(projectRoot, ".env");
|
|
836
|
+
const existing = findMappingByPath(projectsJsonPath, projectRoot);
|
|
837
|
+
const existingKey = readEnvKey(envFilePath, "GG_PIXEL_KEY");
|
|
838
|
+
let created;
|
|
839
|
+
let reused = false;
|
|
840
|
+
if (existing && existingKey) {
|
|
841
|
+
created = { id: existing.id, key: existingKey };
|
|
842
|
+
reused = true;
|
|
843
|
+
} else {
|
|
844
|
+
created = await createProject(fetchFn, ingestUrl, projectName);
|
|
845
|
+
}
|
|
846
|
+
const packageInstalled = opts.skipPackageInstall ? false : runRubyInstall(projectRoot);
|
|
847
|
+
const initFilePath = join(projectRoot, "gg_pixel_init.rb");
|
|
848
|
+
writeFileSync(
|
|
849
|
+
initFilePath,
|
|
850
|
+
`# gg-pixel init \u2014 auto-generated by ggcoder pixel install.
|
|
851
|
+
require "gg_pixel"
|
|
852
|
+
GGPixel.init(
|
|
853
|
+
project_key: ENV["GG_PIXEL_KEY"] || ${JSON.stringify(created.key)},
|
|
854
|
+
ingest_url: ${JSON.stringify(`${ingestUrl}/ingest`)},
|
|
855
|
+
)
|
|
856
|
+
`,
|
|
857
|
+
"utf8"
|
|
858
|
+
);
|
|
859
|
+
writeEnvKey(envFilePath, "GG_PIXEL_KEY", created.key);
|
|
860
|
+
writeProjectsMapping(projectsJsonPath, created.id, projectName, projectRoot);
|
|
861
|
+
return {
|
|
862
|
+
projectId: created.id,
|
|
863
|
+
projectKey: created.key,
|
|
864
|
+
projectName,
|
|
865
|
+
projectKind: "ruby",
|
|
866
|
+
initFilePath,
|
|
867
|
+
envFilePath,
|
|
868
|
+
projectsJsonPath,
|
|
869
|
+
packageManager: "pip",
|
|
870
|
+
packageInstalled,
|
|
871
|
+
entryWiring: { kind: "no_entry_found" },
|
|
872
|
+
reused,
|
|
873
|
+
warnings: [
|
|
874
|
+
`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).`
|
|
875
|
+
]
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
function readRubyAppName(projectRoot) {
|
|
879
|
+
try {
|
|
880
|
+
const entries = readdirSync(projectRoot);
|
|
881
|
+
const gemspec = entries.find((e) => e.endsWith(".gemspec"));
|
|
882
|
+
if (!gemspec) return null;
|
|
883
|
+
return gemspec.replace(/\.gemspec$/, "");
|
|
884
|
+
} catch {
|
|
885
|
+
return null;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
function runRubyInstall(projectRoot) {
|
|
889
|
+
if (existsSync(join(projectRoot, "Gemfile"))) {
|
|
890
|
+
try {
|
|
891
|
+
const content = readFileSync(join(projectRoot, "Gemfile"), "utf8");
|
|
892
|
+
if (!content.includes("gg_pixel")) {
|
|
893
|
+
writeFileSync(
|
|
894
|
+
join(projectRoot, "Gemfile"),
|
|
895
|
+
content + (content.endsWith("\n") ? "" : "\n") + 'gem "gg_pixel"\n',
|
|
896
|
+
"utf8"
|
|
897
|
+
);
|
|
898
|
+
}
|
|
899
|
+
} catch {
|
|
900
|
+
}
|
|
901
|
+
const r = spawnSync("bundle", ["install"], { cwd: projectRoot, stdio: "inherit" });
|
|
902
|
+
if (r.status === 0) return true;
|
|
903
|
+
}
|
|
904
|
+
const r2 = spawnSync("gem", ["install", "gg_pixel"], { cwd: projectRoot, stdio: "inherit" });
|
|
905
|
+
return r2.status === 0;
|
|
661
906
|
}
|
|
662
907
|
async function installPython(ctx) {
|
|
663
908
|
const { projectRoot, opts, ingestUrl, fetchFn, home } = ctx;
|