transloadit 4.8.0 → 4.8.2

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.
Files changed (57) hide show
  1. package/README.md +92 -43
  2. package/dist/bearerToken.d.ts +1 -0
  3. package/dist/bearerToken.d.ts.map +1 -1
  4. package/dist/bearerToken.js +5 -3
  5. package/dist/bearerToken.js.map +1 -1
  6. package/dist/cli/commands/BaseCommand.d.ts +0 -1
  7. package/dist/cli/commands/BaseCommand.d.ts.map +1 -1
  8. package/dist/cli/commands/BaseCommand.js +7 -9
  9. package/dist/cli/commands/BaseCommand.js.map +1 -1
  10. package/dist/cli/commands/auth.d.ts.map +1 -1
  11. package/dist/cli/commands/auth.js +49 -29
  12. package/dist/cli/commands/auth.js.map +1 -1
  13. package/dist/cli/generateIntentDocs.d.ts.map +1 -1
  14. package/dist/cli/generateIntentDocs.js +9 -7
  15. package/dist/cli/generateIntentDocs.js.map +1 -1
  16. package/dist/cli/helpers.d.ts +19 -4
  17. package/dist/cli/helpers.d.ts.map +1 -1
  18. package/dist/cli/helpers.js +232 -10
  19. package/dist/cli/helpers.js.map +1 -1
  20. package/dist/cli/intentCommands.d.ts.map +1 -1
  21. package/dist/cli/intentCommands.js +11 -7
  22. package/dist/cli/intentCommands.js.map +1 -1
  23. package/dist/cli/intentRuntime.d.ts +17 -2
  24. package/dist/cli/intentRuntime.d.ts.map +1 -1
  25. package/dist/cli/intentRuntime.js +163 -33
  26. package/dist/cli/intentRuntime.js.map +1 -1
  27. package/dist/cli/semanticIntents/imageDescribe.d.ts +2 -1
  28. package/dist/cli/semanticIntents/imageDescribe.d.ts.map +1 -1
  29. package/dist/cli/semanticIntents/imageDescribe.js +5 -4
  30. package/dist/cli/semanticIntents/imageDescribe.js.map +1 -1
  31. package/dist/cli/semanticIntents/imageGenerate.d.ts +1 -0
  32. package/dist/cli/semanticIntents/imageGenerate.d.ts.map +1 -1
  33. package/dist/cli/semanticIntents/imageGenerate.js +4 -3
  34. package/dist/cli/semanticIntents/imageGenerate.js.map +1 -1
  35. package/dist/cli/semanticIntents/index.d.ts +1 -0
  36. package/dist/cli/semanticIntents/index.d.ts.map +1 -1
  37. package/dist/cli/semanticIntents/index.js.map +1 -1
  38. package/dist/cli/semanticIntents/markdownPdf.d.ts.map +1 -1
  39. package/dist/cli/semanticIntents/markdownPdf.js +2 -1
  40. package/dist/cli/semanticIntents/markdownPdf.js.map +1 -1
  41. package/dist/cli.d.ts +0 -1
  42. package/dist/cli.d.ts.map +1 -1
  43. package/dist/cli.js +3 -2
  44. package/dist/cli.js.map +1 -1
  45. package/package.json +1 -1
  46. package/src/bearerToken.ts +14 -3
  47. package/src/cli/commands/BaseCommand.ts +7 -9
  48. package/src/cli/commands/auth.ts +66 -32
  49. package/src/cli/generateIntentDocs.ts +10 -7
  50. package/src/cli/helpers.ts +278 -13
  51. package/src/cli/intentCommands.ts +11 -7
  52. package/src/cli/intentRuntime.ts +214 -33
  53. package/src/cli/semanticIntents/imageDescribe.ts +5 -4
  54. package/src/cli/semanticIntents/imageGenerate.ts +4 -3
  55. package/src/cli/semanticIntents/index.ts +1 -0
  56. package/src/cli/semanticIntents/markdownPdf.ts +2 -1
  57. package/src/cli.ts +3 -2
@@ -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 {
@@ -278,7 +278,7 @@ function inferExamples(
278
278
  }
279
279
 
280
280
  return [
281
- ['Run the command', `transloadit ${spec.paths.join(' ')} --input input.mp4 --out output/`],
281
+ ['Run the command', `transloadit ${spec.paths.join(' ')} --input input.mp4 --output output/`],
282
282
  ]
283
283
  }
284
284
 
@@ -310,7 +310,7 @@ function inferExamples(
310
310
  }
311
311
 
312
312
  const outputMode = spec.intentDefinition.outputMode ?? 'file'
313
- parts.push('--out', inferOutputPath(spec.paths, outputMode, fieldSpecs))
313
+ parts.push('--output', inferOutputPath(spec.paths, outputMode, fieldSpecs))
314
314
 
315
315
  return [['Run the command', parts.join(' ')]]
316
316
  }
@@ -355,11 +355,13 @@ function resolveRobotIntent(definition: RobotIntentDefinition): BuiltIntentComma
355
355
  input.kind === 'none'
356
356
  ? {
357
357
  execution,
358
+ defaultOutputPath: inferOutputPath(paths, outputMode, fieldSpecs),
358
359
  outputDescription: 'Write the result to this path',
359
360
  outputMode,
360
361
  }
361
362
  : {
362
363
  commandLabel,
364
+ defaultOutputPath: inferOutputPath(paths, outputMode, fieldSpecs),
363
365
  execution,
364
366
  inputPolicy: input.inputPolicy,
365
367
  outputDescription:
@@ -388,18 +390,18 @@ function getIntentDetails({
388
390
  robot: string
389
391
  }): string {
390
392
  if (inputMode === 'none') {
391
- return `Runs \`${robot}\` and writes the result to \`--out\`.`
393
+ return `Runs \`${robot}\` and writes the result to \`--output\`.`
392
394
  }
393
395
 
394
396
  if (defaultSingleAssembly) {
395
- return `Runs \`${robot}\` for the provided inputs and writes the result to \`--out\`.`
397
+ return `Runs \`${robot}\` for the provided inputs and writes the result to \`--output\`.`
396
398
  }
397
399
 
398
400
  if (outputMode === 'directory') {
399
- return `Runs \`${robot}\` on each input file and writes the results to \`--out\`.`
401
+ return `Runs \`${robot}\` on each input file and writes the results to \`--output\`.`
400
402
  }
401
403
 
402
- return `Runs \`${robot}\` on each input file and writes the result to \`--out\`.`
404
+ return `Runs \`${robot}\` on each input file and writes the result to \`--output\`.`
403
405
  }
404
406
 
405
407
  function resolveSemanticIntent(definition: SemanticIntentDefinition): BuiltIntentCommandDefinition {
@@ -415,6 +417,7 @@ function resolveSemanticIntent(definition: SemanticIntentDefinition): BuiltInten
415
417
  runnerKind: descriptor.runnerKind,
416
418
  intentDefinition: {
417
419
  commandLabel: paths.join(' '),
420
+ defaultOutputPath: descriptor.defaultOutputPath,
418
421
  execution: descriptor.execution,
419
422
  inputPolicy: descriptor.inputPolicy,
420
423
  outputDescription: descriptor.outputDescription,
@@ -430,12 +433,13 @@ function resolveTemplateIntent(
430
433
  const spec: BuiltIntentCommandDefinition = {
431
434
  className: `${toPascalCase(paths)}Command`,
432
435
  description: `Run ${stripTrailingPunctuation(definition.templateId)}`,
433
- details: `Runs the \`${definition.templateId}\` template and writes the outputs to \`--out\`.`,
436
+ details: `Runs the \`${definition.templateId}\` template and writes the outputs to \`--output\`.`,
434
437
  examples: [],
435
438
  paths,
436
439
  runnerKind: 'standard',
437
440
  intentDefinition: {
438
441
  commandLabel: paths.join(' '),
442
+ defaultOutputPath: inferOutputPath(paths, outputMode, []),
439
443
  execution: {
440
444
  kind: 'template',
441
445
  templateId: definition.templateId,