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.
- package/LICENSE +21 -0
- package/README.md +142 -0
- package/dist/src/apply.d.ts +14 -0
- package/dist/src/apply.js +84 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +416 -0
- package/dist/src/frameworks/index.d.ts +4 -0
- package/dist/src/frameworks/index.js +16 -0
- package/dist/src/frameworks/managed-files.d.ts +10 -0
- package/dist/src/frameworks/managed-files.js +104 -0
- package/dist/src/frameworks/next-app-router.d.ts +2 -0
- package/dist/src/frameworks/next-app-router.js +187 -0
- package/dist/src/frameworks/next-pages-router.d.ts +2 -0
- package/dist/src/frameworks/next-pages-router.js +169 -0
- package/dist/src/frameworks/shared.d.ts +17 -0
- package/dist/src/frameworks/shared.js +136 -0
- package/dist/src/frameworks/static-html.d.ts +2 -0
- package/dist/src/frameworks/static-html.js +137 -0
- package/dist/src/frameworks/vite-react.d.ts +2 -0
- package/dist/src/frameworks/vite-react.js +274 -0
- package/dist/src/index.d.ts +12 -0
- package/dist/src/index.js +11 -0
- package/dist/src/inspect.d.ts +10 -0
- package/dist/src/inspect.js +150 -0
- package/dist/src/manifest.d.ts +10 -0
- package/dist/src/manifest.js +83 -0
- package/dist/src/package-manager.d.ts +6 -0
- package/dist/src/package-manager.js +110 -0
- package/dist/src/plan.d.ts +9 -0
- package/dist/src/plan.js +97 -0
- package/dist/src/providers/ga4.d.ts +2 -0
- package/dist/src/providers/ga4.js +73 -0
- package/dist/src/providers/index.d.ts +3 -0
- package/dist/src/providers/index.js +11 -0
- package/dist/src/providers/posthog.d.ts +2 -0
- package/dist/src/providers/posthog.js +77 -0
- package/dist/src/providers/validate.d.ts +31 -0
- package/dist/src/providers/validate.js +110 -0
- package/dist/src/providers/x.d.ts +2 -0
- package/dist/src/providers/x.js +76 -0
- package/dist/src/render.d.ts +25 -0
- package/dist/src/render.js +260 -0
- package/dist/src/types.d.ts +151 -0
- package/dist/src/types.js +8 -0
- package/dist/src/uninstall.d.ts +7 -0
- package/dist/src/uninstall.js +66 -0
- package/dist/src/verify.d.ts +5 -0
- package/dist/src/verify.js +50 -0
- package/dist/src/workspace-artifacts.d.ts +41 -0
- package/dist/src/workspace-artifacts.js +171 -0
- 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
|
+
}
|