-
Notifications
You must be signed in to change notification settings - Fork 305
Expand file tree
/
Copy pathutils.ts
More file actions
146 lines (123 loc) · 5.3 KB
/
Copy pathutils.ts
File metadata and controls
146 lines (123 loc) · 5.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import { readFile } from 'fs/promises';
import stripJsonComments from 'strip-json-comments';
import { z } from "zod";
import { DEFAULT_CONFIG_SETTINGS } from "./constants.js";
import { ConfigSettings } from "./types.js";
import { Org, Repo } from "@sourcebot/db";
import path from "path";
import { env, isRemotePath, loadConfig } from "./env.server.js";
import { isAnonymousAccessAvailable } from './entitlements.js';
// From https://developer.mozilla.org/en-US/docs/Glossary/Base64#the_unicode_problem
export const base64Decode = (base64: string): string => {
const binString = atob(base64);
return Buffer.from(Uint8Array.from(binString, (m) => m.codePointAt(0)!).buffer).toString();
}
// TODO: Merge this with config loading logic which uses AJV
export const loadJsonFile = async <T>(
filePath: string,
schema: any
): Promise<T> => {
const fileContent = await (async () => {
if (isRemotePath(filePath)) {
const response = await fetch(filePath);
if (!response.ok) {
throw new Error(`Failed to fetch file ${filePath}: ${response.statusText}`);
}
return response.text();
} else {
// Retry logic for handling race conditions with mounted volumes
const maxAttempts = 5;
const retryDelayMs = 2000;
let lastError: Error | null = null;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await readFile(filePath, {
encoding: 'utf-8',
});
} catch (error) {
lastError = error as Error;
// Only retry on ENOENT errors (file not found)
if ((error as NodeJS.ErrnoException)?.code !== 'ENOENT') {
throw error; // Throw immediately for non-ENOENT errors
}
// Log warning before retry (except on the last attempt)
if (attempt < maxAttempts) {
console.warn(`File not found, retrying in 2s... (Attempt ${attempt}/${maxAttempts})`);
await new Promise(resolve => setTimeout(resolve, retryDelayMs));
}
}
}
// If we've exhausted all retries, throw the last ENOENT error
if (lastError) {
throw lastError;
}
throw new Error('Failed to load file after all retry attempts');
}
})();
const parsedData = JSON.parse(stripJsonComments(fileContent));
try {
return schema.parse(parsedData);
} catch (error) {
if (error instanceof z.ZodError) {
throw new Error(`File '${filePath}' is invalid: ${error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ')}`);
}
throw error;
}
}
export const getConfigSettings = async (configPath?: string): Promise<ConfigSettings> => {
if (!configPath) {
return DEFAULT_CONFIG_SETTINGS;
}
const config = await loadConfig(configPath);
return {
...DEFAULT_CONFIG_SETTINGS,
...config.settings,
// Fall back to deprecated experiment_ variants if new keys are not set.
repoDrivenPermissionSyncIntervalMs:
config.settings?.repoDrivenPermissionSyncIntervalMs
?? config.settings?.experiment_repoDrivenPermissionSyncIntervalMs
?? DEFAULT_CONFIG_SETTINGS.repoDrivenPermissionSyncIntervalMs,
userDrivenPermissionSyncIntervalMs:
config.settings?.userDrivenPermissionSyncIntervalMs
?? config.settings?.experiment_userDrivenPermissionSyncIntervalMs
?? DEFAULT_CONFIG_SETTINGS.userDrivenPermissionSyncIntervalMs,
}
}
export const getRepoIdFromPath = (repoPath: string): number | undefined => {
const id = parseInt(path.basename(repoPath), 10);
return isNaN(id) ? undefined : id;
}
export const getRepoPath = (repo: Repo): { path: string, isReadOnly: boolean } => {
// If we are dealing with a local repository, then use that as the path.
// Mark as read-only since we aren't guaranteed to have write access to the local filesystem.
const cloneUrl = new URL(repo.cloneUrl);
if (repo.external_codeHostType === 'genericGitHost' && cloneUrl.protocol === 'file:') {
return {
path: cloneUrl.pathname,
isReadOnly: true,
}
}
const reposPath = path.join(env.DATA_CACHE_DIR, 'repos');
return {
path: path.join(reposPath, repo.id.toString()),
isReadOnly: false,
}
}
export const isCredentialsLoginEnabled = (org: Org): boolean => {
if (env.AUTH_CREDENTIALS_LOGIN_ENABLED !== undefined) {
return env.AUTH_CREDENTIALS_LOGIN_ENABLED === 'true';
}
return org.isCredentialsLoginEnabled;
}
export const isEmailCodeLoginEnabled = (org: Org): boolean => {
if (env.AUTH_EMAIL_CODE_LOGIN_ENABLED !== undefined) {
return env.AUTH_EMAIL_CODE_LOGIN_ENABLED === 'true';
}
return org.isEmailCodeLoginEnabled;
}
export const isMemberApprovalRequired = (org: Org): boolean => {
if (env.REQUIRE_APPROVAL_NEW_MEMBERS !== undefined) {
return env.REQUIRE_APPROVAL_NEW_MEMBERS === 'true';
}
return org.memberApprovalRequired;
}