infinite-tag 0.1.1

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.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +142 -0
  3. package/dist/src/apply.d.ts +14 -0
  4. package/dist/src/apply.js +84 -0
  5. package/dist/src/cli.d.ts +2 -0
  6. package/dist/src/cli.js +416 -0
  7. package/dist/src/frameworks/index.d.ts +4 -0
  8. package/dist/src/frameworks/index.js +16 -0
  9. package/dist/src/frameworks/managed-files.d.ts +10 -0
  10. package/dist/src/frameworks/managed-files.js +104 -0
  11. package/dist/src/frameworks/next-app-router.d.ts +2 -0
  12. package/dist/src/frameworks/next-app-router.js +187 -0
  13. package/dist/src/frameworks/next-pages-router.d.ts +2 -0
  14. package/dist/src/frameworks/next-pages-router.js +169 -0
  15. package/dist/src/frameworks/shared.d.ts +17 -0
  16. package/dist/src/frameworks/shared.js +136 -0
  17. package/dist/src/frameworks/static-html.d.ts +2 -0
  18. package/dist/src/frameworks/static-html.js +137 -0
  19. package/dist/src/frameworks/vite-react.d.ts +2 -0
  20. package/dist/src/frameworks/vite-react.js +274 -0
  21. package/dist/src/index.d.ts +12 -0
  22. package/dist/src/index.js +11 -0
  23. package/dist/src/inspect.d.ts +10 -0
  24. package/dist/src/inspect.js +150 -0
  25. package/dist/src/manifest.d.ts +10 -0
  26. package/dist/src/manifest.js +83 -0
  27. package/dist/src/package-manager.d.ts +6 -0
  28. package/dist/src/package-manager.js +110 -0
  29. package/dist/src/plan.d.ts +9 -0
  30. package/dist/src/plan.js +97 -0
  31. package/dist/src/providers/ga4.d.ts +2 -0
  32. package/dist/src/providers/ga4.js +73 -0
  33. package/dist/src/providers/index.d.ts +3 -0
  34. package/dist/src/providers/index.js +11 -0
  35. package/dist/src/providers/posthog.d.ts +2 -0
  36. package/dist/src/providers/posthog.js +77 -0
  37. package/dist/src/providers/validate.d.ts +31 -0
  38. package/dist/src/providers/validate.js +110 -0
  39. package/dist/src/providers/x.d.ts +2 -0
  40. package/dist/src/providers/x.js +76 -0
  41. package/dist/src/render.d.ts +25 -0
  42. package/dist/src/render.js +260 -0
  43. package/dist/src/types.d.ts +151 -0
  44. package/dist/src/types.js +8 -0
  45. package/dist/src/uninstall.d.ts +7 -0
  46. package/dist/src/uninstall.js +66 -0
  47. package/dist/src/verify.d.ts +5 -0
  48. package/dist/src/verify.js +50 -0
  49. package/dist/src/workspace-artifacts.d.ts +41 -0
  50. package/dist/src/workspace-artifacts.js +171 -0
  51. package/package.json +27 -0
@@ -0,0 +1,41 @@
1
+ import type { PackageManager, WorkspaceInstallArtifacts } from "./types.js";
2
+ export interface WorkspaceArtifactOptions {
3
+ artifactFile?: string;
4
+ ga4MeasurementId?: string;
5
+ posthogProjectKey?: string;
6
+ posthogApiHost?: string;
7
+ xPixelId?: string;
8
+ xEventTagIds?: string[];
9
+ packageManager?: PackageManager;
10
+ }
11
+ export declare function readWorkspaceArtifactsFile(root: string, artifactFile?: string): WorkspaceInstallArtifacts;
12
+ /**
13
+ * Coerce arbitrary parsed JSON into the known artifact shape: only the ga4/posthog/x keys
14
+ * with string fields survive. This stops a hostile or malformed `--artifact-file` from
15
+ * smuggling unexpected structures in; the providers still strictly validate the value
16
+ * FORMATS (G-…, phc_…, https origin) at plan time.
17
+ */
18
+ export declare function coerceWorkspaceArtifacts(value: unknown): WorkspaceInstallArtifacts;
19
+ /** Where `infinite setup` saves the public handoff files; INFINITE_ARTIFACTS_DIR overrides (tests). */
20
+ export declare function defaultArtifactsDir(): string;
21
+ export interface DiscoveredWorkspaceArtifacts {
22
+ filePath: string;
23
+ /** Workspace id recorded in the file (or its file name); callers adopt it only when no --workspace was given. */
24
+ workspaceId?: string;
25
+ providers: Array<"ga4" | "posthog" | "x">;
26
+ artifacts: WorkspaceInstallArtifacts;
27
+ }
28
+ /**
29
+ * Same-machine flag-free install: `infinite setup` saves the captured PUBLIC artifacts to
30
+ * `~/.infinite/artifacts/<workspaceId>.json`; when the founder passes no artifact flags and
31
+ * no --artifact-file, the CLI discovers that file here. With a --workspace, only that
32
+ * workspace's file is considered; without one, a single saved file is used (adopting its
33
+ * workspace id) while multiple files are listed and never guessed between. Unreadable or
34
+ * malformed files warn and behave as if absent. Callers must not invoke discovery when any
35
+ * explicit artifact input was given — explicit flags and --artifact-file always win.
36
+ */
37
+ export declare function discoverWorkspaceArtifacts(options: {
38
+ workspaceId?: string;
39
+ warn?: (message: string) => void;
40
+ }): DiscoveredWorkspaceArtifacts | null;
41
+ export declare function resolveWorkspaceArtifacts(root: string, options: WorkspaceArtifactOptions): WorkspaceInstallArtifacts;
@@ -0,0 +1,171 @@
1
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { basename, join } from "node:path";
4
+ export function readWorkspaceArtifactsFile(root, artifactFile) {
5
+ if (!artifactFile) {
6
+ return {};
7
+ }
8
+ const artifactPath = artifactFile.startsWith("/")
9
+ ? artifactFile
10
+ : join(root, artifactFile);
11
+ if (!existsSync(artifactPath)) {
12
+ throw new Error(`Artifact file not found: ${artifactPath}`);
13
+ }
14
+ let parsed;
15
+ try {
16
+ parsed = JSON.parse(readFileSync(artifactPath, "utf8"));
17
+ }
18
+ catch (error) {
19
+ throw new Error(`Artifact file ${artifactPath} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
20
+ }
21
+ return coerceWorkspaceArtifacts(parsed);
22
+ }
23
+ /**
24
+ * Coerce arbitrary parsed JSON into the known artifact shape: only the ga4/posthog/x keys
25
+ * with string fields survive. This stops a hostile or malformed `--artifact-file` from
26
+ * smuggling unexpected structures in; the providers still strictly validate the value
27
+ * FORMATS (G-…, phc_…, https origin) at plan time.
28
+ */
29
+ export function coerceWorkspaceArtifacts(value) {
30
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
31
+ throw new Error("Artifact file must contain a JSON object.");
32
+ }
33
+ const record = value;
34
+ const artifacts = {};
35
+ const ga4 = asRecord(record.ga4);
36
+ if (ga4 && typeof ga4.measurementId === "string") {
37
+ artifacts.ga4 = { measurementId: ga4.measurementId };
38
+ }
39
+ const posthog = asRecord(record.posthog);
40
+ if (posthog && (typeof posthog.projectKey === "string" || typeof posthog.apiHost === "string")) {
41
+ artifacts.posthog = {
42
+ projectKey: typeof posthog.projectKey === "string" ? posthog.projectKey : "",
43
+ apiHost: typeof posthog.apiHost === "string" ? posthog.apiHost : ""
44
+ };
45
+ }
46
+ const x = asRecord(record.x);
47
+ if (x) {
48
+ const eventTagIds = Array.isArray(x.eventTagIds)
49
+ ? x.eventTagIds.filter((id) => typeof id === "string")
50
+ : [];
51
+ if (typeof x.pixelId === "string" || eventTagIds.length > 0) {
52
+ artifacts.x = {
53
+ pixelId: typeof x.pixelId === "string" ? x.pixelId : "",
54
+ eventTagIds
55
+ };
56
+ }
57
+ }
58
+ return artifacts;
59
+ }
60
+ function asRecord(value) {
61
+ return value !== null && typeof value === "object" && !Array.isArray(value)
62
+ ? value
63
+ : null;
64
+ }
65
+ /** Where `infinite setup` saves the public handoff files; INFINITE_ARTIFACTS_DIR overrides (tests). */
66
+ export function defaultArtifactsDir() {
67
+ const override = process.env.INFINITE_ARTIFACTS_DIR?.trim();
68
+ return override ? override : join(homedir(), ".infinite", "artifacts");
69
+ }
70
+ /**
71
+ * Same-machine flag-free install: `infinite setup` saves the captured PUBLIC artifacts to
72
+ * `~/.infinite/artifacts/<workspaceId>.json`; when the founder passes no artifact flags and
73
+ * no --artifact-file, the CLI discovers that file here. With a --workspace, only that
74
+ * workspace's file is considered; without one, a single saved file is used (adopting its
75
+ * workspace id) while multiple files are listed and never guessed between. Unreadable or
76
+ * malformed files warn and behave as if absent. Callers must not invoke discovery when any
77
+ * explicit artifact input was given — explicit flags and --artifact-file always win.
78
+ */
79
+ export function discoverWorkspaceArtifacts(options) {
80
+ const warn = options.warn ?? (() => undefined);
81
+ const dir = defaultArtifactsDir();
82
+ if (!existsSync(dir)) {
83
+ return null;
84
+ }
85
+ if (options.workspaceId !== undefined) {
86
+ if (!isSafeArtifactFileStem(options.workspaceId)) {
87
+ return null;
88
+ }
89
+ const filePath = join(dir, `${options.workspaceId}.json`);
90
+ return existsSync(filePath) ? readDiscoveredArtifactsFile(filePath, warn) : null;
91
+ }
92
+ let names;
93
+ try {
94
+ names = readdirSync(dir).filter((name) => name.endsWith(".json")).sort();
95
+ }
96
+ catch (error) {
97
+ warn(`Could not read the saved artifacts directory ${dir}: ${errorMessage(error)}`);
98
+ return null;
99
+ }
100
+ if (names.length === 0) {
101
+ return null;
102
+ }
103
+ if (names.length > 1) {
104
+ warn([
105
+ `Found ${names.length} saved artifact files in ${dir}:`,
106
+ ...names.map((name) => ` - ${name}`),
107
+ "Pass --workspace <id> to pick one; infinite-tag will not guess."
108
+ ].join("\n"));
109
+ return null;
110
+ }
111
+ return readDiscoveredArtifactsFile(join(dir, names[0]), warn);
112
+ }
113
+ function readDiscoveredArtifactsFile(filePath, warn) {
114
+ let parsed;
115
+ try {
116
+ parsed = JSON.parse(readFileSync(filePath, "utf8"));
117
+ }
118
+ catch (error) {
119
+ warn(`Ignoring saved artifact file ${filePath}: ${errorMessage(error)}`);
120
+ return null;
121
+ }
122
+ let artifacts;
123
+ try {
124
+ artifacts = coerceWorkspaceArtifacts(parsed);
125
+ }
126
+ catch (error) {
127
+ warn(`Ignoring saved artifact file ${filePath}: ${errorMessage(error)}`);
128
+ return null;
129
+ }
130
+ const providers = ["ga4", "posthog", "x"].filter((provider) => artifacts[provider] !== undefined);
131
+ if (providers.length === 0) {
132
+ warn(`Ignoring saved artifact file ${filePath}: it contains no usable public artifacts.`);
133
+ return null;
134
+ }
135
+ const record = asRecord(parsed);
136
+ const recordedWorkspaceId = typeof record?.workspaceId === "string" && record.workspaceId.trim()
137
+ ? record.workspaceId.trim()
138
+ : undefined;
139
+ return {
140
+ filePath,
141
+ workspaceId: recordedWorkspaceId ?? basename(filePath, ".json"),
142
+ providers: [...providers],
143
+ artifacts
144
+ };
145
+ }
146
+ function isSafeArtifactFileStem(workspaceId) {
147
+ return /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(workspaceId);
148
+ }
149
+ function errorMessage(error) {
150
+ return error instanceof Error ? error.message : String(error);
151
+ }
152
+ export function resolveWorkspaceArtifacts(root, options) {
153
+ const fromFile = readWorkspaceArtifactsFile(root, options.artifactFile);
154
+ const artifacts = { ...fromFile };
155
+ if (options.ga4MeasurementId) {
156
+ artifacts.ga4 = { measurementId: options.ga4MeasurementId };
157
+ }
158
+ if (options.posthogProjectKey || options.posthogApiHost) {
159
+ artifacts.posthog = {
160
+ projectKey: options.posthogProjectKey ?? artifacts.posthog?.projectKey ?? "",
161
+ apiHost: options.posthogApiHost ?? artifacts.posthog?.apiHost ?? ""
162
+ };
163
+ }
164
+ if (options.xPixelId || options.xEventTagIds?.length) {
165
+ artifacts.x = {
166
+ pixelId: options.xPixelId ?? artifacts.x?.pixelId ?? "",
167
+ eventTagIds: options.xEventTagIds ?? artifacts.x?.eventTagIds ?? []
168
+ };
169
+ }
170
+ return artifacts;
171
+ }
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "infinite-tag",
3
+ "version": "0.1.1",
4
+ "description": "Founder-run installer that adds analytics (Google Analytics 4, PostHog, X/Twitter Pixel) to your web app — public keys only, idempotent, and reversible.",
5
+ "keywords": ["analytics","ga4","google-analytics","posthog","twitter-pixel","installer","instrumentation","nextjs","vite","infinite"],
6
+ "license": "MIT",
7
+ "author": "Ultima AI, Inc",
8
+ "type": "module",
9
+ "engines": {
10
+ "node": ">=18"
11
+ },
12
+ "bin": {
13
+ "infinite-tag": "./dist/src/cli.js"
14
+ },
15
+ "exports": {
16
+ ".": "./dist/src/index.js"
17
+ },
18
+ "files": [
19
+ "dist/src"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsc -b tsconfig.json",
23
+ "prepack": "rm -rf dist && tsc -p tsconfig.build.json",
24
+ "test": "vitest run",
25
+ "typecheck": "tsc --noEmit --pretty false"
26
+ }
27
+ }