History

Use platform-standard home config paths

Rhys Sullivan committed 8 months ago

Commit 2b571621 parent 9ad2bf82 files · +123 −3Browse files

Unified · Split

2 files
  1. packages/control-plane/src/runtime/local-config.test.ts +46 −0
  2. packages/control-plane/src/runtime/local-config.ts +77 −3
packages/control-plane/src/runtime/local-config.test.ts+46−0
@@ -7,6 +7,8 @@ import * as Effect from "effect/Effect"; · 6 unmodified lines
77
88 import {
99 loadLocalExecutorConfig,
10+ resolveDefaultHomeConfigCandidates,
11+ resolveHomeConfigPath,
1012 resolveLocalWorkspaceContext,
1113 } from "./local-config";
1214
@@ -93,4 +95,48 @@ describe("local-config", () => { · 80 unmodified lines
9395 expect(failure.message).toContain("line 5, column 7");
9496 }),
9597 );
98+
99+ it("uses platform-standard home config candidates", () => {
100+ const linuxCandidates = resolveDefaultHomeConfigCandidates({
101+ platform: "linux",
102+ homeDirectory: "/home/alice",
103+ env: {},
104+ });
105+ const macCandidates = resolveDefaultHomeConfigCandidates({
106+ platform: "darwin",
107+ homeDirectory: "/Users/alice",
108+ env: {},
109+ });
110+
111+ expect(linuxCandidates[0]).toBe("/home/alice/.config/executor/executor.jsonc");
112+ expect(macCandidates[0]).toBe(
113+ "/Users/alice/Library/Application Support/Executor/executor.jsonc",
114+ );
115+ expect(macCandidates[2]).toBe("/Users/alice/.config/executor/executor.jsonc");
116+ });
117+
118+ it.effect("prefers an existing legacy home config path before the canonical path", () =>
119+ Effect.gen(function* () {
120+ const homeDirectory = makeWorkspaceRoot();
121+ const legacyConfigDirectory = join(homeDirectory, ".config", "executor");
122+ yield* Effect.promise(() => mkdir(legacyConfigDirectory, { recursive: true }));
123+ yield* Effect.promise(() =>
124+ writeFile(
125+ join(legacyConfigDirectory, "executor.jsonc"),
126+ "{\n \"sources\": {}\n}\n",
127+ "utf8",
128+ ),
129+ );
130+
131+ const resolvedPath = yield* Effect.promise(() =>
132+ resolveHomeConfigPath({
133+ platform: "darwin",
134+ homeDirectory,
135+ env: {},
136+ }),
137+ );
138+
139+ expect(resolvedPath).toBe(join(legacyConfigDirectory, "executor.jsonc"));
140+ }),
141+ );
96142 });
packages/control-plane/src/runtime/local-config.ts+77−3
@@ -22,8 +22,7 @@ const decodeLocalExecutorConfig = Schema.decodeUnknownSync(LocalExecutorConfigSc · 21 unmodified lines
2222 const PROJECT_CONFIG_BASENAME = "executor.jsonc";
2323 const PROJECT_CONFIG_FALLBACK_BASENAME = "executor.json";
2424 const PROJECT_CONFIG_DIRECTORY = ".executor";
25−const HOME_CONFIG_DIRECTORY = join(homedir(), ".config", "executor");
26−const HOME_CONFIG_PATH = join(HOME_CONFIG_DIRECTORY, PROJECT_CONFIG_BASENAME);
25+const EXECUTOR_CONFIG_DIR_ENV = "EXECUTOR_CONFIG_DIR";
2726
2827 const normalizeSlashPath = (value: string): string =>
2928 value.replaceAll("\\", "/");
@@ -54,6 +53,80 @@ const trimOrUndefined = (value: string | undefined | null): string | undefined = · 24 unmodified lines
5453 return trimmed && trimmed.length > 0 ? trimmed : undefined;
5554 };
5655
56+const defaultExecutorConfigDirectory = (input: {
57+ env?: NodeJS.ProcessEnv;
58+ platform?: NodeJS.Platform;
59+ homeDirectory?: string;
60+} = {}): string => {
61+ const env = input.env ?? process.env;
62+ const platform = input.platform ?? process.platform;
63+ const homeDirectory = input.homeDirectory ?? homedir();
64+ const explicitConfigDirectory = trimOrUndefined(env[EXECUTOR_CONFIG_DIR_ENV]);
65+
66+ if (explicitConfigDirectory) {
67+ return explicitConfigDirectory;
68+ }
69+
70+ if (platform === "win32") {
71+ return join(
72+ trimOrUndefined(env.LOCALAPPDATA) ?? join(homeDirectory, "AppData", "Local"),
73+ "Executor",
74+ );
75+ }
76+
77+ if (platform === "darwin") {
78+ return join(homeDirectory, "Library", "Application Support", "Executor");
79+ }
80+
81+ return join(
82+ trimOrUndefined(env.XDG_CONFIG_HOME) ?? join(homeDirectory, ".config"),
83+ "executor",
84+ );
85+};
86+
87+const legacyExecutorConfigDirectory = (input: {
88+ homeDirectory?: string;
89+} = {}): string =>
90+ join(input.homeDirectory ?? homedir(), ".config", "executor");
91+
92+export const resolveDefaultHomeConfigCandidates = (input: {
93+ env?: NodeJS.ProcessEnv;
94+ platform?: NodeJS.Platform;
95+ homeDirectory?: string;
96+} = {}): string[] => {
97+ const homeDirectory = input.homeDirectory ?? homedir();
98+ const canonicalDirectory = defaultExecutorConfigDirectory({
99+ env: input.env,
100+ platform: input.platform,
101+ homeDirectory,
102+ });
103+ const legacyDirectory = legacyExecutorConfigDirectory({ homeDirectory });
104+ const directories = canonicalDirectory === legacyDirectory
105+ ? [canonicalDirectory]
106+ : [canonicalDirectory, legacyDirectory];
107+
108+ return directories.flatMap((directory) => [
109+ join(directory, PROJECT_CONFIG_BASENAME),
110+ join(directory, PROJECT_CONFIG_FALLBACK_BASENAME),
111+ ]);
112+};
113+
114+export const resolveHomeConfigPath = async (input: {
115+ env?: NodeJS.ProcessEnv;
116+ platform?: NodeJS.Platform;
117+ homeDirectory?: string;
118+} = {}): Promise<string> => {
119+ const candidates = resolveDefaultHomeConfigCandidates(input);
120+
121+ for (const candidate of candidates) {
122+ if (await fileExists(candidate)) {
123+ return candidate;
124+ }
125+ }
126+
127+ return candidates[0]!;
128+};
129+
57130 const formatJsoncParseErrors = (content: string, errors: readonly JsoncParseError[]): string => {
58131 const lines = content.split("\n");
59132
@@ -232,6 +305,7 @@ export const resolveLocalWorkspaceContext = async (input: { · 172 unmodified lines
232305 );
233306 const workspaceName = basename(workspaceRoot) || "workspace";
234307 const projectConfigPath = await resolveProjectConfigPath(workspaceRoot);
308+ const homeConfigPath = await resolveHomeConfigPath();
235309
236310 return {
237311 cwd,
@@ -239,7 +313,7 @@ export const resolveLocalWorkspaceContext = async (input: { · 1 unmodified line
239313 workspaceName,
240314 configDirectory: join(workspaceRoot, PROJECT_CONFIG_DIRECTORY),
241315 projectConfigPath,
242− homeConfigPath: HOME_CONFIG_PATH,
316+ homeConfigPath,
243317 artifactsDirectory: join(workspaceRoot, PROJECT_CONFIG_DIRECTORY, "artifacts"),
244318 stateDirectory: join(workspaceRoot, PROJECT_CONFIG_DIRECTORY, "state"),
245319 installationId: `local_${stableHash(normalizeSlashPath(workspaceRoot))}`,