transloadit 4.8.0 → 4.8.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.
@@ -1,30 +1,295 @@
1
1
  import fs from 'node:fs'
2
2
  import fsp from 'node:fs/promises'
3
+ import { homedir } from 'node:os'
4
+ import path from 'node:path'
3
5
  import type { Readable } from 'node:stream'
6
+ import { parse as parseDotenv } from 'dotenv'
4
7
  import { isAPIError } from './types.ts'
5
8
 
6
- const MISSING_CREDENTIALS_MESSAGE =
7
- 'Missing credentials. Please set TRANSLOADIT_KEY and TRANSLOADIT_SECRET environment variables.'
9
+ export type CliKeySecretCredentials = { authKey: string; authSecret: string }
10
+ export type CliAuthToken = { authToken: string }
11
+ export type CliAuth = CliKeySecretCredentials | CliAuthToken
12
+ type CliEnvSource = {
13
+ name: 'env' | 'credentialsFile'
14
+ values: Record<string, string | undefined>
15
+ }
16
+
17
+ let loadedProjectDotenvPath: string | undefined
18
+ let projectDotenvInjectedValues: Record<string, string> | undefined
19
+ let projectDotenvPreviousValues: Record<string, string | undefined> | undefined
20
+ let shellEnvBeforeProjectDotenv: Record<string, string | undefined> | undefined
21
+
22
+ type LoadCliEnvSourcesResult = {
23
+ loadError?: string
24
+ shellEnvSource: CliEnvSource
25
+ sources: CliEnvSource[]
26
+ }
27
+
28
+ export type ResolvedCliConfig = {
29
+ auth?: CliAuth
30
+ credentials?: CliKeySecretCredentials
31
+ credentialsEndpoint?: string
32
+ endpoint?: string
33
+ loadError?: string
34
+ }
35
+
36
+ function normalizeEnvValue(value: string | undefined): string | undefined {
37
+ const trimmed = value?.trim()
38
+ return trimmed ? trimmed : undefined
39
+ }
40
+
41
+ function getConfiguredCredentialsFilePath(): string {
42
+ const configuredPath = normalizeEnvValue(process.env.TRANSLOADIT_CREDENTIALS_FILE)
43
+ if (configuredPath != null) {
44
+ return path.resolve(configuredPath)
45
+ }
46
+
47
+ return path.join(homedir(), '.transloadit', 'credentials')
48
+ }
49
+
50
+ function getProjectDotenvPath(): string {
51
+ return path.resolve(process.cwd(), '.env')
52
+ }
53
+
54
+ function getDisplayPath(filePath: string): string {
55
+ const normalizedHome = path.resolve(homedir())
56
+ const normalizedFilePath = path.resolve(filePath)
57
+ if (normalizedFilePath === normalizedHome) return '~'
58
+ if (normalizedFilePath.startsWith(`${normalizedHome}${path.sep}`)) {
59
+ return `~${normalizedFilePath.slice(normalizedHome.length)}`
60
+ }
61
+
62
+ return normalizedFilePath
63
+ }
64
+
65
+ export function buildMissingCredentialsMessage(): string {
66
+ return [
67
+ 'Missing credentials.',
68
+ '',
69
+ 'Looked for TRANSLOADIT_KEY + TRANSLOADIT_SECRET in this order:',
70
+ '1. Shell env: TRANSLOADIT_KEY / TRANSLOADIT_SECRET',
71
+ `2. Current directory .env: ${getProjectDotenvPath()}`,
72
+ `3. Credentials file: ${getDisplayPath(getConfiguredCredentialsFilePath())}`,
73
+ ].join('\n')
74
+ }
75
+
76
+ export function buildMissingAuthMessage(): string {
77
+ return [
78
+ 'Missing authentication.',
79
+ '',
80
+ 'Looked for TRANSLOADIT_AUTH_TOKEN or TRANSLOADIT_KEY + TRANSLOADIT_SECRET in this order:',
81
+ '1. Shell env: TRANSLOADIT_AUTH_TOKEN, or TRANSLOADIT_KEY / TRANSLOADIT_SECRET',
82
+ `2. Current directory .env: ${getProjectDotenvPath()}`,
83
+ `3. Credentials file: ${getDisplayPath(getConfiguredCredentialsFilePath())}`,
84
+ ].join('\n')
85
+ }
86
+
87
+ function readEnvFile(
88
+ filePath: string,
89
+ ): { ok: true; source: CliEnvSource } | { ok: false; error: string } | null {
90
+ if (!fs.existsSync(filePath)) return null
91
+
92
+ try {
93
+ return {
94
+ ok: true,
95
+ source: {
96
+ name: 'credentialsFile',
97
+ values: parseDotenv(fs.readFileSync(filePath)),
98
+ },
99
+ }
100
+ } catch (err) {
101
+ if (!(err instanceof Error)) {
102
+ throw new Error(`Was thrown a non-error: ${err}`)
103
+ }
104
+ return { ok: false, error: `Failed to read ${filePath}: ${err.message}` }
105
+ }
106
+ }
107
+
108
+ export function loadProjectDotenvIntoProcessEnv(): string | undefined {
109
+ const projectDotenvPath = getProjectDotenvPath()
110
+ if (loadedProjectDotenvPath !== projectDotenvPath) {
111
+ restoreProjectDotenvFromProcessEnv()
112
+ shellEnvBeforeProjectDotenv = { ...process.env }
113
+ loadedProjectDotenvPath = projectDotenvPath
114
+ }
115
+
116
+ const projectDotenvResult = readEnvFile(projectDotenvPath)
117
+ if (projectDotenvResult == null) {
118
+ restoreProjectDotenvFromProcessEnv()
119
+ projectDotenvInjectedValues = undefined
120
+ projectDotenvPreviousValues = undefined
121
+ return undefined
122
+ }
123
+
124
+ if (!projectDotenvResult.ok) return projectDotenvResult.error
125
+ if (projectDotenvInjectedValues != null) return undefined
126
+
127
+ const previousValues: Record<string, string | undefined> = {}
128
+ const injectedValues: Record<string, string> = {}
129
+ for (const [key, value] of Object.entries(projectDotenvResult.source.values)) {
130
+ if (value == null) continue
131
+ if (normalizeEnvValue(process.env[key]) != null) continue
132
+ previousValues[key] = process.env[key]
133
+ process.env[key] = value
134
+ injectedValues[key] = value
135
+ }
136
+
137
+ projectDotenvPreviousValues = previousValues
138
+ projectDotenvInjectedValues = injectedValues
139
+ return undefined
140
+ }
141
+
142
+ function getShellEnvValues(): Record<string, string | undefined> {
143
+ if (loadedProjectDotenvPath === getProjectDotenvPath() && shellEnvBeforeProjectDotenv != null) {
144
+ return shellEnvBeforeProjectDotenv
145
+ }
146
+
147
+ return { ...process.env }
148
+ }
149
+
150
+ function restoreProjectDotenvFromProcessEnv(): void {
151
+ if (projectDotenvInjectedValues == null || projectDotenvPreviousValues == null) return
152
+
153
+ for (const [key, injectedValue] of Object.entries(projectDotenvInjectedValues)) {
154
+ if (process.env[key] !== injectedValue) continue
155
+
156
+ const previousValue = projectDotenvPreviousValues[key]
157
+ if (previousValue == null) {
158
+ delete process.env[key]
159
+ continue
160
+ }
161
+
162
+ process.env[key] = previousValue
163
+ }
164
+
165
+ projectDotenvInjectedValues = undefined
166
+ projectDotenvPreviousValues = undefined
167
+ }
168
+
169
+ function loadCliEnvSources(): LoadCliEnvSourcesResult {
170
+ const shellEnvSource: CliEnvSource = {
171
+ name: 'env',
172
+ values: getShellEnvValues(),
173
+ }
174
+ const loadErrors: string[] = []
175
+
176
+ const projectDotenvLoadError = loadProjectDotenvIntoProcessEnv()
177
+ if (projectDotenvLoadError != null) {
178
+ loadErrors.push(projectDotenvLoadError)
179
+ }
180
+
181
+ const sources: CliEnvSource[] = [
182
+ {
183
+ name: 'env',
184
+ values: { ...process.env },
185
+ },
186
+ ]
187
+
188
+ const credentialsFilePath = getConfiguredCredentialsFilePath()
189
+ const credentialsFileResult = readEnvFile(credentialsFilePath)
190
+ if (credentialsFileResult?.ok === true) {
191
+ sources.push(credentialsFileResult.source)
192
+ } else if (credentialsFileResult?.ok === false) {
193
+ loadErrors.push(credentialsFileResult.error)
194
+ } else if (normalizeEnvValue(process.env.TRANSLOADIT_CREDENTIALS_FILE) != null) {
195
+ loadErrors.push(`Configured credentials file does not exist: ${credentialsFilePath}`)
196
+ }
197
+
198
+ return {
199
+ shellEnvSource,
200
+ sources,
201
+ ...(loadErrors[0] ? { loadError: loadErrors[0] } : {}),
202
+ }
203
+ }
8
204
 
9
- type EnvCredentials = { authKey: string; authSecret: string }
205
+ function getSourceValue(source: CliEnvSource, keys: string[]): string | undefined {
206
+ for (const key of keys) {
207
+ const value = normalizeEnvValue(source.values[key])
208
+ if (value != null) return value
209
+ }
10
210
 
11
- function getEnvCredentials(): { authKey: string; authSecret: string } | null {
12
- const authKey = process.env.TRANSLOADIT_KEY ?? process.env.TRANSLOADIT_AUTH_KEY
13
- const authSecret = process.env.TRANSLOADIT_SECRET ?? process.env.TRANSLOADIT_AUTH_SECRET
211
+ return undefined
212
+ }
14
213
 
15
- if (!authKey || !authSecret) return null
214
+ function getSourceCredentials(source: CliEnvSource): CliKeySecretCredentials | undefined {
215
+ const authKey = getSourceValue(source, ['TRANSLOADIT_KEY', 'TRANSLOADIT_AUTH_KEY'])
216
+ const authSecret = getSourceValue(source, ['TRANSLOADIT_SECRET', 'TRANSLOADIT_AUTH_SECRET'])
217
+ if (authKey == null || authSecret == null) return undefined
16
218
 
17
219
  return { authKey, authSecret }
18
220
  }
19
221
 
20
- type RequireEnvCredentialsResult =
21
- | { ok: true; credentials: EnvCredentials }
222
+ function getSourceAuthToken(source: CliEnvSource): CliAuthToken | undefined {
223
+ const authToken = getSourceValue(source, ['TRANSLOADIT_AUTH_TOKEN'])
224
+ if (authToken == null) return undefined
225
+
226
+ return { authToken }
227
+ }
228
+
229
+ function resolveEndpointForSource(
230
+ source: CliEnvSource | undefined,
231
+ shellEnvSource: CliEnvSource,
232
+ ): string | undefined {
233
+ const shellEndpoint = getSourceValue(shellEnvSource, ['TRANSLOADIT_ENDPOINT'])
234
+ if (shellEndpoint != null) return shellEndpoint
235
+ if (source == null) return undefined
236
+
237
+ return getSourceValue(source, ['TRANSLOADIT_ENDPOINT'])
238
+ }
239
+
240
+ export function resolveCliConfig(): ResolvedCliConfig {
241
+ const { loadError, shellEnvSource, sources } = loadCliEnvSources()
242
+ let auth: CliAuth | undefined
243
+ let authSource: CliEnvSource | undefined
244
+ let credentials: CliKeySecretCredentials | undefined
245
+ let credentialsSource: CliEnvSource | undefined
246
+
247
+ for (const source of sources) {
248
+ if (auth == null) {
249
+ const authToken = getSourceAuthToken(source)
250
+ if (authToken != null) {
251
+ auth = authToken
252
+ authSource = source
253
+ } else {
254
+ const sourceCredentials = getSourceCredentials(source)
255
+ if (sourceCredentials != null) {
256
+ auth = sourceCredentials
257
+ authSource = source
258
+ }
259
+ }
260
+ }
261
+
262
+ if (credentials != null) continue
263
+
264
+ const sourceCredentials = getSourceCredentials(source)
265
+ if (sourceCredentials != null) {
266
+ credentials = sourceCredentials
267
+ credentialsSource = source
268
+ }
269
+ }
270
+
271
+ return {
272
+ ...(auth != null ? { auth } : {}),
273
+ ...(credentials != null ? { credentials } : {}),
274
+ ...(authSource != null
275
+ ? { endpoint: resolveEndpointForSource(authSource, shellEnvSource) }
276
+ : {}),
277
+ ...(credentialsSource != null
278
+ ? { credentialsEndpoint: resolveEndpointForSource(credentialsSource, shellEnvSource) }
279
+ : {}),
280
+ ...(loadError != null ? { loadError } : {}),
281
+ }
282
+ }
283
+
284
+ type RequireCliCredentialsResult =
285
+ | { ok: true; credentials: CliKeySecretCredentials }
22
286
  | { ok: false; error: string }
23
287
 
24
- export function requireEnvCredentials(): RequireEnvCredentialsResult {
25
- const credentials = getEnvCredentials()
26
- if (credentials == null) return { ok: false, error: MISSING_CREDENTIALS_MESSAGE }
27
- return { ok: true, credentials }
288
+ export function requireCliCredentials(): RequireCliCredentialsResult {
289
+ const { credentials, loadError } = resolveCliConfig()
290
+ if (credentials != null) return { ok: true, credentials }
291
+ if (loadError != null) return { ok: false, error: loadError }
292
+ return { ok: false, error: buildMissingCredentialsMessage() }
28
293
  }
29
294
 
30
295
  export function createReadStream(file: string): Readable {
package/src/cli.ts CHANGED
@@ -4,8 +4,7 @@ import { realpathSync } from 'node:fs'
4
4
  import path from 'node:path'
5
5
  import process from 'node:process'
6
6
  import { fileURLToPath } from 'node:url'
7
- import 'dotenv/config'
8
- import { createCli } from './cli/commands/index.ts'
7
+ import { loadProjectDotenvIntoProcessEnv } from './cli/helpers.ts'
9
8
  import { ensureError } from './cli/types.ts'
10
9
 
11
10
  const currentFile = realpathSync(fileURLToPath(import.meta.url))
@@ -26,6 +25,8 @@ export function shouldRunCli(invoked?: string): boolean {
26
25
  }
27
26
 
28
27
  export async function main(args = process.argv.slice(2)): Promise<void> {
28
+ loadProjectDotenvIntoProcessEnv()
29
+ const { createCli } = await import('./cli/commands/index.ts')
29
30
  const cli = createCli()
30
31
  const exitCode = await cli.run(args)
31
32
  if (exitCode !== 0) {