@rudderhq/agent-runtime-utils 0.2.0-canary.2 → 0.2.0-canary.21

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.
@@ -0,0 +1,31 @@
1
+ export type GitIdentitySource = "environment" | "repository" | "rudder_confirmed" | "global";
2
+ export type GitIdentity = {
3
+ name: string;
4
+ email: string;
5
+ source: GitIdentitySource;
6
+ };
7
+ export type GitIdentityPreparationResult = {
8
+ identity: GitIdentity | null;
9
+ configTarget: string;
10
+ configuredUseConfigOnly: boolean;
11
+ warnings: string[];
12
+ };
13
+ type LogFn = (stream: "stdout" | "stderr", chunk: string) => Promise<void>;
14
+ export declare function isUnsafeGitIdentityEmail(email: string | null | undefined): boolean;
15
+ export declare function applyGitIdentityPreparationEnv(env: Record<string, string>, preparation: GitIdentityPreparationResult): void;
16
+ export declare function normalizeConfirmedRudderGitIdentity(input: unknown): GitIdentity | null;
17
+ export declare function ensureGitIdentityFileConfig(input: {
18
+ cwd: string;
19
+ home: string;
20
+ sourceEnv?: NodeJS.ProcessEnv;
21
+ onLog?: LogFn | null;
22
+ confirmedIdentity?: GitIdentity | null;
23
+ }): Promise<GitIdentityPreparationResult>;
24
+ export declare function ensureGitRepositoryIdentityConfig(input: {
25
+ cwd: string;
26
+ sourceEnv?: NodeJS.ProcessEnv;
27
+ onLog?: LogFn | null;
28
+ confirmedIdentity?: GitIdentity | null;
29
+ }): Promise<GitIdentityPreparationResult>;
30
+ export {};
31
+ //# sourceMappingURL=git-identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-identity.d.ts","sourceRoot":"","sources":["../src/git-identity.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,iBAAiB,GAAG,aAAa,GAAG,YAAY,GAAG,kBAAkB,GAAG,QAAQ,CAAC;AAE7F,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,iBAAiB,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG;IACzC,QAAQ,EAAE,WAAW,GAAG,IAAI,CAAC;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,uBAAuB,EAAE,OAAO,CAAC;IACjC,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB,CAAC;AAgBF,KAAK,KAAK,GAAG,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAkB3E,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAKlF;AAED,wBAAgB,8BAA8B,CAC5C,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC3B,WAAW,EAAE,4BAA4B,GACxC,IAAI,CAWN;AAyBD,wBAAgB,mCAAmC,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,GAAG,IAAI,CAStF;AAsID,wBAAsB,2BAA2B,CAAC,KAAK,EAAE;IACvD,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IAC9B,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;IACrB,iBAAiB,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;CACxC,GAAG,OAAO,CAAC,4BAA4B,CAAC,CAgDxC;AAED,wBAAsB,iCAAiC,CAAC,KAAK,EAAE;IAC7D,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IAC9B,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;IACrB,iBAAiB,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;CACxC,GAAG,OAAO,CAAC,4BAA4B,CAAC,CAoCxC"}
@@ -0,0 +1,250 @@
1
+ import { spawn } from "node:child_process";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ const GIT_IDENTITY_ENV_KEYS = [
5
+ "GIT_AUTHOR_NAME",
6
+ "GIT_AUTHOR_EMAIL",
7
+ "GIT_COMMITTER_NAME",
8
+ "GIT_COMMITTER_EMAIL",
9
+ ];
10
+ const LOCAL_EMAIL_RE = /@[^@\s<>]+\.local$/i;
11
+ const EMAIL_RE = /^[^@\s<>]+@[^@\s<>]+\.[^@\s<>]+$/;
12
+ function normalizeEnv(env) {
13
+ return Object.fromEntries(Object.entries(env ?? process.env).filter((entry) => typeof entry[1] === "string"));
14
+ }
15
+ function nonEmpty(value) {
16
+ const normalized = value?.trim() ?? "";
17
+ return normalized.length > 0 ? normalized : null;
18
+ }
19
+ export function isUnsafeGitIdentityEmail(email) {
20
+ const normalized = nonEmpty(email);
21
+ if (!normalized)
22
+ return true;
23
+ if (!EMAIL_RE.test(normalized))
24
+ return true;
25
+ return LOCAL_EMAIL_RE.test(normalized);
26
+ }
27
+ export function applyGitIdentityPreparationEnv(env, preparation) {
28
+ env.GIT_CONFIG_GLOBAL = preparation.configTarget;
29
+ if (!preparation.identity) {
30
+ for (const key of GIT_IDENTITY_ENV_KEYS)
31
+ env[key] = "";
32
+ return;
33
+ }
34
+ env.GIT_AUTHOR_NAME = preparation.identity.name;
35
+ env.GIT_AUTHOR_EMAIL = preparation.identity.email;
36
+ env.GIT_COMMITTER_NAME = preparation.identity.name;
37
+ env.GIT_COMMITTER_EMAIL = preparation.identity.email;
38
+ }
39
+ function buildIdentity(name, email, source) {
40
+ const normalizedName = nonEmpty(name);
41
+ const normalizedEmail = nonEmpty(email);
42
+ if (!normalizedName || !normalizedEmail)
43
+ return null;
44
+ if (isUnsafeGitIdentityEmail(normalizedEmail))
45
+ return null;
46
+ return {
47
+ name: normalizedName,
48
+ email: normalizedEmail,
49
+ source,
50
+ };
51
+ }
52
+ function identityFromEnv(env) {
53
+ return (buildIdentity(env.GIT_AUTHOR_NAME, env.GIT_AUTHOR_EMAIL, "environment") ??
54
+ buildIdentity(env.GIT_COMMITTER_NAME, env.GIT_COMMITTER_EMAIL, "environment"));
55
+ }
56
+ export function normalizeConfirmedRudderGitIdentity(input) {
57
+ if (!input || typeof input !== "object")
58
+ return null;
59
+ const record = input;
60
+ if (record.confirmed === false)
61
+ return null;
62
+ return buildIdentity(typeof record.name === "string" ? record.name : null, typeof record.email === "string" ? record.email : null, "rudder_confirmed");
63
+ }
64
+ async function runGit(args, opts = {}) {
65
+ const env = normalizeEnv(opts.env);
66
+ return await new Promise((resolve) => {
67
+ const child = spawn("git", args, {
68
+ cwd: opts.cwd ?? process.cwd(),
69
+ env,
70
+ stdio: ["ignore", "pipe", "pipe"],
71
+ });
72
+ let stdout = "";
73
+ let stderr = "";
74
+ child.stdout?.on("data", (chunk) => {
75
+ stdout += String(chunk);
76
+ });
77
+ child.stderr?.on("data", (chunk) => {
78
+ stderr += String(chunk);
79
+ });
80
+ child.on("error", (error) => {
81
+ resolve({ code: null, stdout, stderr, error });
82
+ });
83
+ child.on("close", (code) => {
84
+ resolve({ code, stdout, stderr, error: null });
85
+ });
86
+ });
87
+ }
88
+ async function readGitConfigValue(args, key, opts = {}) {
89
+ const result = await runGit([...args, "--get", key], opts);
90
+ if (result.code !== 0)
91
+ return null;
92
+ return nonEmpty(result.stdout);
93
+ }
94
+ async function readIdentityFromGitConfig(args, source, opts = {}) {
95
+ const [name, email] = await Promise.all([
96
+ readGitConfigValue(args, "user.name", opts),
97
+ readGitConfigValue(args, "user.email", opts),
98
+ ]);
99
+ return buildIdentity(name, email, source);
100
+ }
101
+ async function setGitConfigValue(args, key, value, opts) {
102
+ const result = await runGit([...args, key, value], opts);
103
+ if (result.code === 0)
104
+ return;
105
+ const details = [result.stderr.trim(), result.stdout.trim(), result.error?.message]
106
+ .filter(Boolean)
107
+ .join("\n");
108
+ throw new Error(details || `git config ${key} failed`);
109
+ }
110
+ async function unsetGitConfigValue(args, key, opts) {
111
+ await runGit([...args, "--unset-all", key], opts);
112
+ }
113
+ async function existingGitConfigPaths(candidates) {
114
+ const seen = new Set();
115
+ const existing = [];
116
+ for (const candidate of candidates) {
117
+ const normalized = nonEmpty(candidate);
118
+ if (!normalized)
119
+ continue;
120
+ const resolved = path.resolve(normalized);
121
+ if (seen.has(resolved))
122
+ continue;
123
+ const stat = await fs.stat(resolved).catch(() => null);
124
+ if (!stat?.isFile())
125
+ continue;
126
+ seen.add(resolved);
127
+ existing.push(resolved);
128
+ }
129
+ return existing;
130
+ }
131
+ async function resolveHostGlobalGitConfigIncludes(sourceEnv, targetConfigPath) {
132
+ const home = nonEmpty(sourceEnv.HOME);
133
+ const xdgConfigHome = nonEmpty(sourceEnv.XDG_CONFIG_HOME);
134
+ const candidates = [
135
+ nonEmpty(sourceEnv.GIT_CONFIG_GLOBAL) ?? "",
136
+ home ? path.join(home, ".gitconfig") : "",
137
+ xdgConfigHome ? path.join(xdgConfigHome, "git", "config") : "",
138
+ home ? path.join(home, ".config", "git", "config") : "",
139
+ ].filter(Boolean);
140
+ const target = path.resolve(targetConfigPath);
141
+ return (await existingGitConfigPaths(candidates)).filter((candidate) => candidate !== target);
142
+ }
143
+ function renderGitConfigIncludes(includePaths) {
144
+ if (includePaths.length === 0)
145
+ return [];
146
+ return ["[include]", ...includePaths.map((includePath) => `\tpath = ${includePath}`), ""];
147
+ }
148
+ async function writeGitConfigSeed(configPath, includePaths) {
149
+ await fs.mkdir(path.dirname(configPath), { recursive: true });
150
+ const lines = renderGitConfigIncludes(includePaths);
151
+ await fs.writeFile(configPath, lines.length > 0 ? `${lines.join("\n")}\n` : "", "utf8");
152
+ }
153
+ async function writeFallbackGitConfigFile(configPath, identity, includePaths = []) {
154
+ const lines = [...renderGitConfigIncludes(includePaths), "[user]", "\tuseConfigOnly = true"];
155
+ if (identity) {
156
+ lines.push(`\tname = ${identity.name}`);
157
+ lines.push(`\temail = ${identity.email}`);
158
+ }
159
+ await fs.mkdir(path.dirname(configPath), { recursive: true });
160
+ await fs.writeFile(configPath, `${lines.join("\n")}\n`, "utf8");
161
+ }
162
+ async function resolveBestGitIdentity(input) {
163
+ return (identityFromEnv(input.sourceEnv) ??
164
+ (await readIdentityFromGitConfig(["config", "--local"], "repository", {
165
+ cwd: input.cwd,
166
+ env: input.sourceEnv,
167
+ })) ??
168
+ input.confirmedIdentity ??
169
+ (await readIdentityFromGitConfig(["config", "--global"], "global", {
170
+ cwd: input.cwd,
171
+ env: input.sourceEnv,
172
+ })));
173
+ }
174
+ export async function ensureGitIdentityFileConfig(input) {
175
+ const sourceEnv = normalizeEnv(input.sourceEnv);
176
+ const home = path.resolve(input.home);
177
+ const gitConfigPath = path.join(home, ".gitconfig");
178
+ await fs.mkdir(home, { recursive: true });
179
+ const configArgs = ["config", "--file", gitConfigPath];
180
+ const identity = await resolveBestGitIdentity({
181
+ cwd: input.cwd,
182
+ sourceEnv,
183
+ confirmedIdentity: input.confirmedIdentity,
184
+ });
185
+ const includePaths = identity
186
+ ? await resolveHostGlobalGitConfigIncludes(sourceEnv, gitConfigPath)
187
+ : [];
188
+ const warnings = [];
189
+ try {
190
+ await writeGitConfigSeed(gitConfigPath, includePaths);
191
+ await setGitConfigValue(configArgs, "user.useConfigOnly", "true", { cwd: input.cwd, env: sourceEnv });
192
+ if (identity) {
193
+ await setGitConfigValue(configArgs, "user.name", identity.name, { cwd: input.cwd, env: sourceEnv });
194
+ await setGitConfigValue(configArgs, "user.email", identity.email, { cwd: input.cwd, env: sourceEnv });
195
+ }
196
+ else {
197
+ await unsetGitConfigValue(configArgs, "user.name", { cwd: input.cwd, env: sourceEnv });
198
+ await unsetGitConfigValue(configArgs, "user.email", { cwd: input.cwd, env: sourceEnv });
199
+ }
200
+ }
201
+ catch (error) {
202
+ warnings.push(error instanceof Error ? error.message : String(error));
203
+ await writeFallbackGitConfigFile(gitConfigPath, identity, includePaths);
204
+ }
205
+ if (input.onLog) {
206
+ const detail = identity
207
+ ? `using ${identity.source} Git identity ${identity.name} <${identity.email}>`
208
+ : "without a usable Git identity; commits will fail until user.name and user.email are configured";
209
+ await input.onLog("stdout", `[rudder] Prepared isolated Git config at ${gitConfigPath} with user.useConfigOnly=true (${detail}).\n`);
210
+ }
211
+ return {
212
+ identity,
213
+ configTarget: gitConfigPath,
214
+ configuredUseConfigOnly: true,
215
+ warnings,
216
+ };
217
+ }
218
+ export async function ensureGitRepositoryIdentityConfig(input) {
219
+ const sourceEnv = normalizeEnv(input.sourceEnv);
220
+ const cwd = path.resolve(input.cwd);
221
+ const configArgs = ["config", "--local"];
222
+ const identity = await resolveBestGitIdentity({
223
+ cwd,
224
+ sourceEnv,
225
+ confirmedIdentity: input.confirmedIdentity,
226
+ });
227
+ const warnings = [];
228
+ await setGitConfigValue(configArgs, "user.useConfigOnly", "true", { cwd, env: sourceEnv });
229
+ if (identity) {
230
+ await setGitConfigValue(configArgs, "user.name", identity.name, { cwd, env: sourceEnv });
231
+ await setGitConfigValue(configArgs, "user.email", identity.email, { cwd, env: sourceEnv });
232
+ }
233
+ else {
234
+ await unsetGitConfigValue(configArgs, "user.name", { cwd, env: sourceEnv });
235
+ await unsetGitConfigValue(configArgs, "user.email", { cwd, env: sourceEnv });
236
+ }
237
+ if (input.onLog) {
238
+ const detail = identity
239
+ ? `using ${identity.source} Git identity ${identity.name} <${identity.email}>`
240
+ : "without a usable Git identity; commits will fail until user.name and user.email are configured";
241
+ await input.onLog("stdout", `[rudder] Prepared repository Git config in ${cwd} with user.useConfigOnly=true (${detail}).\n`);
242
+ }
243
+ return {
244
+ identity,
245
+ configTarget: cwd,
246
+ configuredUseConfigOnly: true,
247
+ warnings,
248
+ };
249
+ }
250
+ //# sourceMappingURL=git-identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-identity.js","sourceRoot":"","sources":["../src/git-identity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=git-identity.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-identity.test.d.ts","sourceRoot":"","sources":["../src/git-identity.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,347 @@
1
+ import { execFile } from "node:child_process";
2
+ import fs from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { promisify } from "node:util";
6
+ import { describe, expect, it } from "vitest";
7
+ import { applyGitIdentityPreparationEnv, ensureGitIdentityFileConfig, ensureGitRepositoryIdentityConfig, isUnsafeGitIdentityEmail, normalizeConfirmedRudderGitIdentity, } from "./git-identity.js";
8
+ const execFileAsync = promisify(execFile);
9
+ function baseGitTestEnv(env) {
10
+ const base = { ...process.env };
11
+ for (const key of [
12
+ "GIT_AUTHOR_NAME",
13
+ "GIT_AUTHOR_EMAIL",
14
+ "GIT_COMMITTER_NAME",
15
+ "GIT_COMMITTER_EMAIL",
16
+ "GIT_CONFIG_GLOBAL",
17
+ "XDG_CONFIG_HOME",
18
+ ]) {
19
+ delete base[key];
20
+ }
21
+ return {
22
+ ...base,
23
+ ...env,
24
+ };
25
+ }
26
+ async function runGit(cwd, args, env) {
27
+ return execFileAsync("git", args, {
28
+ cwd,
29
+ env: baseGitTestEnv(env),
30
+ });
31
+ }
32
+ async function createRepoWithoutStoredIdentity(root) {
33
+ const repo = path.join(root, "repo");
34
+ await fs.mkdir(repo, { recursive: true });
35
+ await runGit(repo, ["init"]);
36
+ await fs.writeFile(path.join(repo, "README.md"), "hello\n", "utf8");
37
+ await runGit(repo, ["add", "README.md"]);
38
+ await runGit(repo, [
39
+ "-c",
40
+ "user.name=Setup User",
41
+ "-c",
42
+ "user.email=setup@example.com",
43
+ "commit",
44
+ "-m",
45
+ "Initial commit",
46
+ ]);
47
+ await runGit(repo, ["checkout", "-B", "main"]);
48
+ return repo;
49
+ }
50
+ async function gitAuthorIdent(cwd, env) {
51
+ return execFileAsync("git", ["var", "GIT_AUTHOR_IDENT"], {
52
+ cwd,
53
+ env: baseGitTestEnv(env),
54
+ });
55
+ }
56
+ describe("git identity guard", () => {
57
+ it("rejects local-host fallback emails as unsafe identities", () => {
58
+ expect(isUnsafeGitIdentityEmail("zeeland@ZeelanddeMacBook-Pro.local")).toBe(true);
59
+ expect(isUnsafeGitIdentityEmail("72488598+Undertone0809@users.noreply.github.com")).toBe(false);
60
+ });
61
+ it("seeds isolated HOME Git config from repository identity", async () => {
62
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), "rudder-git-identity-repo-"));
63
+ try {
64
+ const repo = await createRepoWithoutStoredIdentity(root);
65
+ const isolatedHome = path.join(root, "agent-home");
66
+ await runGit(repo, ["config", "user.name", "Rudder Agent"]);
67
+ await runGit(repo, ["config", "user.email", "rudder-agent@example.com"]);
68
+ const result = await ensureGitIdentityFileConfig({
69
+ cwd: repo,
70
+ home: isolatedHome,
71
+ sourceEnv: baseGitTestEnv({
72
+ HOME: path.join(root, "empty-home"),
73
+ GIT_CONFIG_NOSYSTEM: "1",
74
+ }),
75
+ });
76
+ expect(result.identity).toMatchObject({
77
+ name: "Rudder Agent",
78
+ email: "rudder-agent@example.com",
79
+ source: "repository",
80
+ });
81
+ await expect(fs.readFile(path.join(isolatedHome, ".gitconfig"), "utf8")).resolves.toContain("useConfigOnly = true");
82
+ const ident = await gitAuthorIdent(repo, {
83
+ HOME: isolatedHome,
84
+ GIT_CONFIG_NOSYSTEM: "1",
85
+ });
86
+ expect(ident.stdout).toContain("Rudder Agent <rudder-agent@example.com>");
87
+ expect(ident.stdout).not.toContain(".local");
88
+ }
89
+ finally {
90
+ await fs.rm(root, { recursive: true, force: true });
91
+ }
92
+ });
93
+ it("includes host global Git config when a safe identity is resolved", async () => {
94
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), "rudder-git-identity-include-"));
95
+ try {
96
+ const repo = await createRepoWithoutStoredIdentity(root);
97
+ const hostHome = path.join(root, "host-home");
98
+ const isolatedHome = path.join(root, "agent-home");
99
+ await fs.mkdir(hostHome, { recursive: true });
100
+ await fs.writeFile(path.join(hostHome, ".gitconfig"), [
101
+ "[user]",
102
+ "\tname = Host Operator",
103
+ "\temail = operator@example.com",
104
+ "[credential]",
105
+ "\thelper = store",
106
+ ].join("\n") + "\n", "utf8");
107
+ const result = await ensureGitIdentityFileConfig({
108
+ cwd: repo,
109
+ home: isolatedHome,
110
+ sourceEnv: baseGitTestEnv({
111
+ HOME: hostHome,
112
+ GIT_CONFIG_NOSYSTEM: "1",
113
+ }),
114
+ });
115
+ expect(result.identity).toMatchObject({
116
+ name: "Host Operator",
117
+ email: "operator@example.com",
118
+ source: "global",
119
+ });
120
+ await expect(runGit(repo, [
121
+ "config",
122
+ "--get",
123
+ "credential.helper",
124
+ ], {
125
+ HOME: isolatedHome,
126
+ GIT_CONFIG_GLOBAL: path.join(isolatedHome, ".gitconfig"),
127
+ GIT_CONFIG_NOSYSTEM: "1",
128
+ })).resolves.toMatchObject({ stdout: "store\n" });
129
+ await expect(runGit(repo, [
130
+ "config",
131
+ "--get",
132
+ "user.email",
133
+ ], {
134
+ HOME: isolatedHome,
135
+ GIT_CONFIG_GLOBAL: path.join(isolatedHome, ".gitconfig"),
136
+ GIT_CONFIG_NOSYSTEM: "1",
137
+ })).resolves.toMatchObject({ stdout: "operator@example.com\n" });
138
+ }
139
+ finally {
140
+ await fs.rm(root, { recursive: true, force: true });
141
+ }
142
+ });
143
+ it("prevents Git fallback commits when isolated HOME has no usable identity", async () => {
144
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), "rudder-git-identity-missing-"));
145
+ try {
146
+ const repo = await createRepoWithoutStoredIdentity(root);
147
+ const isolatedHome = path.join(root, "agent-home");
148
+ const result = await ensureGitIdentityFileConfig({
149
+ cwd: repo,
150
+ home: isolatedHome,
151
+ sourceEnv: baseGitTestEnv({
152
+ HOME: path.join(root, "empty-home"),
153
+ GIT_CONFIG_NOSYSTEM: "1",
154
+ }),
155
+ });
156
+ expect(result.identity).toBeNull();
157
+ await expect(gitAuthorIdent(repo, {
158
+ HOME: isolatedHome,
159
+ GIT_CONFIG_NOSYSTEM: "1",
160
+ })).rejects.toMatchObject({
161
+ stderr: expect.not.stringContaining(".local"),
162
+ });
163
+ await expect(execFileAsync("git", ["commit", "--allow-empty", "-m", "agent commit"], {
164
+ cwd: repo,
165
+ env: baseGitTestEnv({
166
+ HOME: isolatedHome,
167
+ GIT_CONFIG_NOSYSTEM: "1",
168
+ }),
169
+ })).rejects.toMatchObject({
170
+ stderr: expect.stringContaining("auto-detection is disabled"),
171
+ });
172
+ const count = await runGit(repo, ["rev-list", "--count", "HEAD"]);
173
+ expect(count.stdout.trim()).toBe("1");
174
+ }
175
+ finally {
176
+ await fs.rm(root, { recursive: true, force: true });
177
+ }
178
+ });
179
+ it("does not reuse stale managed HOME Git identity when current sources are missing", async () => {
180
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), "rudder-git-identity-stale-managed-"));
181
+ try {
182
+ const repo = await createRepoWithoutStoredIdentity(root);
183
+ const isolatedHome = path.join(root, "agent-home");
184
+ await fs.mkdir(isolatedHome, { recursive: true });
185
+ await fs.writeFile(path.join(isolatedHome, ".gitconfig"), [
186
+ "[user]",
187
+ "\tname = Old Operator",
188
+ "\temail = old@example.com",
189
+ "\tuseConfigOnly = true",
190
+ ].join("\n") + "\n", "utf8");
191
+ const result = await ensureGitIdentityFileConfig({
192
+ cwd: repo,
193
+ home: isolatedHome,
194
+ sourceEnv: baseGitTestEnv({
195
+ HOME: path.join(root, "empty-home"),
196
+ GIT_CONFIG_NOSYSTEM: "1",
197
+ }),
198
+ });
199
+ expect(result.identity).toBeNull();
200
+ const managedConfig = await fs.readFile(path.join(isolatedHome, ".gitconfig"), "utf8");
201
+ expect(managedConfig).toContain("useConfigOnly = true");
202
+ expect(managedConfig).not.toContain("old@example.com");
203
+ await expect(gitAuthorIdent(repo, {
204
+ HOME: isolatedHome,
205
+ GIT_CONFIG_NOSYSTEM: "1",
206
+ })).rejects.toMatchObject({
207
+ stderr: expect.not.stringContaining("old@example.com"),
208
+ });
209
+ }
210
+ finally {
211
+ await fs.rm(root, { recursive: true, force: true });
212
+ }
213
+ });
214
+ it("applies an isolated Git config and blanks unsafe inherited identity env when identity is missing", async () => {
215
+ const env = {
216
+ GIT_AUTHOR_NAME: "Host User",
217
+ GIT_AUTHOR_EMAIL: "host@machine.local",
218
+ GIT_COMMITTER_NAME: "Host User",
219
+ GIT_COMMITTER_EMAIL: "host@machine.local",
220
+ };
221
+ applyGitIdentityPreparationEnv(env, {
222
+ identity: null,
223
+ configTarget: "/tmp/rudder-agent-home/.gitconfig",
224
+ configuredUseConfigOnly: true,
225
+ warnings: [],
226
+ });
227
+ expect(env).toMatchObject({
228
+ GIT_CONFIG_GLOBAL: "/tmp/rudder-agent-home/.gitconfig",
229
+ GIT_AUTHOR_NAME: "",
230
+ GIT_AUTHOR_EMAIL: "",
231
+ GIT_COMMITTER_NAME: "",
232
+ GIT_COMMITTER_EMAIL: "",
233
+ });
234
+ });
235
+ it("applies isolated Git config and confirmed identity env when identity is available", async () => {
236
+ const env = {};
237
+ applyGitIdentityPreparationEnv(env, {
238
+ identity: {
239
+ name: "Rudder Operator",
240
+ email: "operator@example.com",
241
+ source: "global",
242
+ },
243
+ configTarget: "/tmp/rudder-agent-home/.gitconfig",
244
+ configuredUseConfigOnly: true,
245
+ warnings: [],
246
+ });
247
+ expect(env).toMatchObject({
248
+ GIT_CONFIG_GLOBAL: "/tmp/rudder-agent-home/.gitconfig",
249
+ GIT_AUTHOR_NAME: "Rudder Operator",
250
+ GIT_AUTHOR_EMAIL: "operator@example.com",
251
+ GIT_COMMITTER_NAME: "Rudder Operator",
252
+ GIT_COMMITTER_EMAIL: "operator@example.com",
253
+ });
254
+ });
255
+ it("configures repository useConfigOnly without inventing a fallback identity", async () => {
256
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), "rudder-git-identity-worktree-"));
257
+ try {
258
+ const repo = await createRepoWithoutStoredIdentity(root);
259
+ const result = await ensureGitRepositoryIdentityConfig({
260
+ cwd: repo,
261
+ sourceEnv: baseGitTestEnv({
262
+ HOME: path.join(root, "empty-home"),
263
+ GIT_CONFIG_NOSYSTEM: "1",
264
+ }),
265
+ });
266
+ expect(result.identity).toBeNull();
267
+ await expect(runGit(repo, ["config", "--local", "--get", "user.useConfigOnly"])).resolves.toMatchObject({
268
+ stdout: "true\n",
269
+ });
270
+ await expect(gitAuthorIdent(repo, {
271
+ HOME: path.join(root, "empty-home"),
272
+ GIT_CONFIG_NOSYSTEM: "1",
273
+ })).rejects.toMatchObject({
274
+ stderr: expect.not.stringContaining(".local"),
275
+ });
276
+ }
277
+ finally {
278
+ await fs.rm(root, { recursive: true, force: true });
279
+ }
280
+ });
281
+ it("normalizes only safe confirmed Rudder identities", () => {
282
+ expect(normalizeConfirmedRudderGitIdentity({
283
+ name: " Rudder Operator ",
284
+ email: " operator@example.com ",
285
+ confirmed: true,
286
+ })).toEqual({
287
+ name: "Rudder Operator",
288
+ email: "operator@example.com",
289
+ source: "rudder_confirmed",
290
+ });
291
+ expect(normalizeConfirmedRudderGitIdentity({
292
+ name: "Host User",
293
+ email: "host@machine.local",
294
+ confirmed: true,
295
+ })).toBeNull();
296
+ expect(normalizeConfirmedRudderGitIdentity({
297
+ name: "Host User",
298
+ email: "host@example.com",
299
+ confirmed: false,
300
+ })).toBeNull();
301
+ });
302
+ it("prefers confirmed Rudder identity over host global identity for isolated config", async () => {
303
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), "rudder-git-identity-confirmed-"));
304
+ try {
305
+ const repo = await createRepoWithoutStoredIdentity(root);
306
+ const hostHome = path.join(root, "host-home");
307
+ const isolatedHome = path.join(root, "agent-home");
308
+ await fs.mkdir(hostHome, { recursive: true });
309
+ await fs.writeFile(path.join(hostHome, ".gitconfig"), [
310
+ "[user]",
311
+ "\tname = Host Operator",
312
+ "\temail = host@example.com",
313
+ ].join("\n") + "\n", "utf8");
314
+ const result = await ensureGitIdentityFileConfig({
315
+ cwd: repo,
316
+ home: isolatedHome,
317
+ sourceEnv: baseGitTestEnv({
318
+ HOME: hostHome,
319
+ GIT_CONFIG_NOSYSTEM: "1",
320
+ }),
321
+ confirmedIdentity: normalizeConfirmedRudderGitIdentity({
322
+ name: "Confirmed Operator",
323
+ email: "confirmed@example.com",
324
+ confirmed: true,
325
+ }),
326
+ });
327
+ expect(result.identity).toMatchObject({
328
+ name: "Confirmed Operator",
329
+ email: "confirmed@example.com",
330
+ source: "rudder_confirmed",
331
+ });
332
+ await expect(runGit(repo, [
333
+ "config",
334
+ "--get",
335
+ "user.email",
336
+ ], {
337
+ HOME: isolatedHome,
338
+ GIT_CONFIG_GLOBAL: path.join(isolatedHome, ".gitconfig"),
339
+ GIT_CONFIG_NOSYSTEM: "1",
340
+ })).resolves.toMatchObject({ stdout: "confirmed@example.com\n" });
341
+ }
342
+ finally {
343
+ await fs.rm(root, { recursive: true, force: true });
344
+ }
345
+ });
346
+ });
347
+ //# sourceMappingURL=git-identity.test.js.map