@transloadit/node 6.4.0 → 6.6.0

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 (182) hide show
  1. package/README.md +30 -5
  2. package/dist/Transloadit.d.ts.map +1 -1
  3. package/dist/Transloadit.js +61 -5
  4. package/dist/Transloadit.js.map +1 -1
  5. package/dist/alphalib/renderSimpleValue.d.ts +14 -0
  6. package/dist/alphalib/renderSimpleValue.d.ts.map +1 -0
  7. package/dist/alphalib/renderSimpleValue.js +87 -0
  8. package/dist/alphalib/renderSimpleValue.js.map +1 -0
  9. package/dist/alphalib/truncateString.d.ts +5 -0
  10. package/dist/alphalib/truncateString.d.ts.map +1 -0
  11. package/dist/alphalib/truncateString.js +19 -0
  12. package/dist/alphalib/truncateString.js.map +1 -0
  13. package/dist/alphalib/types/builtinTemplates.d.ts +15 -3
  14. package/dist/alphalib/types/builtinTemplates.d.ts.map +1 -1
  15. package/dist/alphalib/types/builtinTemplates.js +23 -3
  16. package/dist/alphalib/types/builtinTemplates.js.map +1 -1
  17. package/dist/alphalib/types/robots/_instructions-primitives.d.ts +6 -0
  18. package/dist/alphalib/types/robots/_instructions-primitives.d.ts.map +1 -1
  19. package/dist/alphalib/types/robots/_instructions-primitives.js +29 -7
  20. package/dist/alphalib/types/robots/_instructions-primitives.js.map +1 -1
  21. package/dist/alphalib/types/robots/_video-generate.d.ts +34 -0
  22. package/dist/alphalib/types/robots/_video-generate.d.ts.map +1 -0
  23. package/dist/alphalib/types/robots/_video-generate.js +157 -0
  24. package/dist/alphalib/types/robots/_video-generate.js.map +1 -0
  25. package/dist/alphalib/types/robots/ai-chat.d.ts +2 -0
  26. package/dist/alphalib/types/robots/ai-chat.d.ts.map +1 -1
  27. package/dist/alphalib/types/robots/ai-chat.js +7 -5
  28. package/dist/alphalib/types/robots/ai-chat.js.map +1 -1
  29. package/dist/alphalib/types/robots/audio-concat.d.ts.map +1 -1
  30. package/dist/alphalib/types/robots/audio-concat.js +5 -5
  31. package/dist/alphalib/types/robots/audio-concat.js.map +1 -1
  32. package/dist/alphalib/types/robots/azure-import.d.ts.map +1 -1
  33. package/dist/alphalib/types/robots/azure-import.js +3 -3
  34. package/dist/alphalib/types/robots/azure-import.js.map +1 -1
  35. package/dist/alphalib/types/robots/document-convert.d.ts.map +1 -1
  36. package/dist/alphalib/types/robots/document-convert.js +7 -5
  37. package/dist/alphalib/types/robots/document-convert.js.map +1 -1
  38. package/dist/alphalib/types/robots/document-extract.d.ts +8 -8
  39. package/dist/alphalib/types/robots/document-extract.d.ts.map +1 -1
  40. package/dist/alphalib/types/robots/document-extract.js +18 -2
  41. package/dist/alphalib/types/robots/document-extract.js.map +1 -1
  42. package/dist/alphalib/types/robots/document-split.d.ts +12 -0
  43. package/dist/alphalib/types/robots/document-split.d.ts.map +1 -1
  44. package/dist/alphalib/types/robots/document-split.js +15 -2
  45. package/dist/alphalib/types/robots/document-split.js.map +1 -1
  46. package/dist/alphalib/types/robots/file-decompress.d.ts.map +1 -1
  47. package/dist/alphalib/types/robots/file-decompress.js +2 -0
  48. package/dist/alphalib/types/robots/file-decompress.js.map +1 -1
  49. package/dist/alphalib/types/robots/file-preview.d.ts +22 -0
  50. package/dist/alphalib/types/robots/file-preview.d.ts.map +1 -1
  51. package/dist/alphalib/types/robots/file-preview.js +43 -26
  52. package/dist/alphalib/types/robots/file-preview.js.map +1 -1
  53. package/dist/alphalib/types/robots/http-request.d.ts.map +1 -1
  54. package/dist/alphalib/types/robots/http-request.js +9 -14
  55. package/dist/alphalib/types/robots/http-request.js.map +1 -1
  56. package/dist/alphalib/types/robots/image-enhance.d.ts +8 -8
  57. package/dist/alphalib/types/robots/image-enhance.js +2 -2
  58. package/dist/alphalib/types/robots/image-resize.d.ts.map +1 -1
  59. package/dist/alphalib/types/robots/image-resize.js +3 -1
  60. package/dist/alphalib/types/robots/image-resize.js.map +1 -1
  61. package/dist/alphalib/types/robots/image-upscale.js +1 -1
  62. package/dist/alphalib/types/robots/image-upscale.js.map +1 -1
  63. package/dist/alphalib/types/robots/s3-import.d.ts.map +1 -1
  64. package/dist/alphalib/types/robots/s3-import.js +14 -15
  65. package/dist/alphalib/types/robots/s3-import.js.map +1 -1
  66. package/dist/alphalib/types/robots/supabase-store.d.ts +16 -6
  67. package/dist/alphalib/types/robots/supabase-store.d.ts.map +1 -1
  68. package/dist/alphalib/types/robots/supabase-store.js +23 -1
  69. package/dist/alphalib/types/robots/supabase-store.js.map +1 -1
  70. package/dist/alphalib/types/robots/tigris-store.d.ts.map +1 -1
  71. package/dist/alphalib/types/robots/tigris-store.js +3 -4
  72. package/dist/alphalib/types/robots/tigris-store.js.map +1 -1
  73. package/dist/alphalib/types/robots/transloadit-import.d.ts +2 -2
  74. package/dist/alphalib/types/robots/transloadit-import.d.ts.map +1 -1
  75. package/dist/alphalib/types/robots/transloadit-import.js +4 -1
  76. package/dist/alphalib/types/robots/transloadit-import.js.map +1 -1
  77. package/dist/alphalib/types/robots/transloadit-store.d.ts +2 -3
  78. package/dist/alphalib/types/robots/transloadit-store.d.ts.map +1 -1
  79. package/dist/alphalib/types/robots/transloadit-store.js +19 -0
  80. package/dist/alphalib/types/robots/transloadit-store.js.map +1 -1
  81. package/dist/alphalib/types/robots/video-adaptive.d.ts.map +1 -1
  82. package/dist/alphalib/types/robots/video-adaptive.js +12 -0
  83. package/dist/alphalib/types/robots/video-adaptive.js.map +1 -1
  84. package/dist/alphalib/types/robots/video-concat.d.ts.map +1 -1
  85. package/dist/alphalib/types/robots/video-concat.js +9 -9
  86. package/dist/alphalib/types/robots/video-concat.js.map +1 -1
  87. package/dist/alphalib/types/robots/video-thumbs.d.ts +8 -8
  88. package/dist/alphalib/types/robots/video-thumbs.js +8 -8
  89. package/dist/alphalib/types/robots/video-thumbs.js.map +1 -1
  90. package/dist/alphalib/types/robots/vimeo-store.js +1 -1
  91. package/dist/alphalib/types/template.d.ts +3 -3
  92. package/dist/alphalib/types/templateValues.d.ts +83 -0
  93. package/dist/alphalib/types/templateValues.d.ts.map +1 -0
  94. package/dist/alphalib/types/templateValues.js +117 -0
  95. package/dist/alphalib/types/templateValues.js.map +1 -0
  96. package/dist/cli/OutputCtl.d.ts +1 -0
  97. package/dist/cli/OutputCtl.d.ts.map +1 -1
  98. package/dist/cli/OutputCtl.js +1 -1
  99. package/dist/cli/OutputCtl.js.map +1 -1
  100. package/dist/cli/commands/BaseCommand.d.ts +3 -2
  101. package/dist/cli/commands/BaseCommand.d.ts.map +1 -1
  102. package/dist/cli/commands/BaseCommand.js +27 -3
  103. package/dist/cli/commands/BaseCommand.js.map +1 -1
  104. package/dist/cli/commands/assemblies.d.ts +21 -1
  105. package/dist/cli/commands/assemblies.d.ts.map +1 -1
  106. package/dist/cli/commands/assemblies.js +584 -91
  107. package/dist/cli/commands/assemblies.js.map +1 -1
  108. package/dist/cli/commands/templates.d.ts +1 -1
  109. package/dist/cli/commands/templates.d.ts.map +1 -1
  110. package/dist/cli/commands/templates.js +57 -67
  111. package/dist/cli/commands/templates.js.map +1 -1
  112. package/dist/cli/errors.d.ts +8 -0
  113. package/dist/cli/errors.d.ts.map +1 -0
  114. package/dist/cli/errors.js +28 -0
  115. package/dist/cli/errors.js.map +1 -0
  116. package/dist/cli/helpers.d.ts.map +1 -1
  117. package/dist/cli/helpers.js +22 -0
  118. package/dist/cli/helpers.js.map +1 -1
  119. package/dist/cli/intentRuntime.d.ts +1 -0
  120. package/dist/cli/intentRuntime.d.ts.map +1 -1
  121. package/dist/cli/intentRuntime.js +9 -3
  122. package/dist/cli/intentRuntime.js.map +1 -1
  123. package/dist/cli/resultFiles.d.ts +1 -0
  124. package/dist/cli/resultFiles.d.ts.map +1 -1
  125. package/dist/cli/resultFiles.js.map +1 -1
  126. package/dist/cli/resultUrls.d.ts +2 -1
  127. package/dist/cli/resultUrls.d.ts.map +1 -1
  128. package/dist/cli/resultUrls.js +6 -3
  129. package/dist/cli/resultUrls.js.map +1 -1
  130. package/dist/cli/stepsInput.d.ts +1 -1
  131. package/dist/cli/stepsInput.d.ts.map +1 -1
  132. package/dist/cli/stepsInput.js +48 -6
  133. package/dist/cli/stepsInput.js.map +1 -1
  134. package/dist/cli/types.d.ts +66 -0
  135. package/dist/cli/types.d.ts.map +1 -1
  136. package/dist/cli/types.js +29 -0
  137. package/dist/cli/types.js.map +1 -1
  138. package/dist/tus.d.ts +7 -2
  139. package/dist/tus.d.ts.map +1 -1
  140. package/dist/tus.js +58 -4
  141. package/dist/tus.js.map +1 -1
  142. package/package.json +1 -1
  143. package/src/Transloadit.ts +65 -4
  144. package/src/alphalib/renderSimpleValue.ts +120 -0
  145. package/src/alphalib/truncateString.ts +23 -0
  146. package/src/alphalib/types/builtinTemplates.ts +33 -3
  147. package/src/alphalib/types/robots/_instructions-primitives.ts +34 -7
  148. package/src/alphalib/types/robots/_video-generate.ts +211 -0
  149. package/src/alphalib/types/robots/ai-chat.ts +8 -5
  150. package/src/alphalib/types/robots/audio-concat.ts +5 -4
  151. package/src/alphalib/types/robots/azure-import.ts +3 -4
  152. package/src/alphalib/types/robots/document-convert.ts +8 -5
  153. package/src/alphalib/types/robots/document-extract.ts +18 -2
  154. package/src/alphalib/types/robots/document-split.ts +17 -2
  155. package/src/alphalib/types/robots/file-decompress.ts +2 -0
  156. package/src/alphalib/types/robots/file-preview.ts +44 -25
  157. package/src/alphalib/types/robots/http-request.ts +9 -13
  158. package/src/alphalib/types/robots/image-enhance.ts +2 -2
  159. package/src/alphalib/types/robots/image-resize.ts +3 -1
  160. package/src/alphalib/types/robots/image-upscale.ts +1 -1
  161. package/src/alphalib/types/robots/s3-import.ts +14 -15
  162. package/src/alphalib/types/robots/supabase-store.ts +23 -1
  163. package/src/alphalib/types/robots/tigris-store.ts +2 -3
  164. package/src/alphalib/types/robots/transloadit-import.ts +4 -1
  165. package/src/alphalib/types/robots/transloadit-store.ts +19 -0
  166. package/src/alphalib/types/robots/video-adaptive.ts +12 -0
  167. package/src/alphalib/types/robots/video-concat.ts +9 -8
  168. package/src/alphalib/types/robots/video-thumbs.ts +8 -8
  169. package/src/alphalib/types/robots/vimeo-store.ts +1 -1
  170. package/src/alphalib/types/templateValues.ts +157 -0
  171. package/src/cli/OutputCtl.ts +2 -1
  172. package/src/cli/commands/BaseCommand.ts +29 -7
  173. package/src/cli/commands/assemblies.ts +775 -118
  174. package/src/cli/commands/templates.ts +93 -98
  175. package/src/cli/errors.ts +34 -0
  176. package/src/cli/helpers.ts +21 -0
  177. package/src/cli/intentRuntime.ts +10 -3
  178. package/src/cli/resultFiles.ts +1 -0
  179. package/src/cli/resultUrls.ts +11 -3
  180. package/src/cli/stepsInput.ts +54 -6
  181. package/src/cli/types.ts +35 -0
  182. package/src/tus.ts +72 -5
@@ -11,11 +11,13 @@ import { Command, Option } from 'clipanion'
11
11
  import rreaddir from 'recursive-readdir'
12
12
  import { z } from 'zod'
13
13
 
14
+ import { ApiError } from '../../ApiError.ts'
14
15
  import { tryCatch } from '../../alphalib/tryCatch.ts'
16
+ import { CliInputError } from '../errors.ts'
15
17
  import { createReadStream, formatAPIError, streamToBuffer } from '../helpers.ts'
16
18
  import { createCliStepsSchema, parseStepsInputJson } from '../stepsInput.ts'
17
19
  import ModifiedLookup from '../template-last-modified.ts'
18
- import { ensureError, isTransloaditAPIError } from '../types.ts'
20
+ import { isTransloaditAPIError } from '../types.ts'
19
21
  import { AuthenticatedCommand } from './BaseCommand.ts'
20
22
 
21
23
  const rreaddirAsync = promisify(rreaddir)
@@ -54,27 +56,29 @@ export interface TemplateSyncOptions {
54
56
  recursive?: boolean
55
57
  }
56
58
 
59
+ async function readTemplateInput<T>(input: Promise<T>): Promise<T> {
60
+ const [error, content] = await tryCatch(input)
61
+ if (error) {
62
+ throw new CliInputError(`Cannot read Template input: ${error.message}`, { cause: error })
63
+ }
64
+ return content
65
+ }
66
+
57
67
  export async function create(
58
68
  output: IOutputCtl,
59
69
  client: Transloadit,
60
70
  { name, file }: TemplateCreateOptions,
61
71
  ): Promise<unknown> {
62
- try {
63
- const buf = await streamToBuffer(createReadStream(file))
72
+ const buf = await readTemplateInput(streamToBuffer(createReadStream(file)))
64
73
 
65
- const steps = parseStepsInputJson(buf.toString(), output)
74
+ const steps = parseStepsInputJson(buf.toString(), output)
66
75
 
67
- const result = await client.createTemplate({
68
- name,
69
- template: { steps } as TemplateContent,
70
- })
71
- output.print(result.id, result)
72
- return result
73
- } catch (err) {
74
- const error = ensureError(err)
75
- output.error(error.message)
76
- throw err
77
- }
76
+ const result = await client.createTemplate({
77
+ name,
78
+ template: { steps } as TemplateContent,
79
+ })
80
+ output.print(result.id, result)
81
+ return result
78
82
  }
79
83
 
80
84
  export async function get(
@@ -84,11 +88,7 @@ export async function get(
84
88
  ): Promise<void> {
85
89
  const requests = templates.map((template) => client.getTemplate(template))
86
90
 
87
- const [err, results] = await tryCatch(Promise.all(requests))
88
- if (err) {
89
- output.error(formatAPIError(err))
90
- throw err
91
- }
91
+ const results = await Promise.all(requests)
92
92
 
93
93
  for (const result of results) {
94
94
  output.print(result, result)
@@ -100,53 +100,49 @@ export async function modify(
100
100
  client: Transloadit,
101
101
  { template, name, file }: TemplateModifyOptions,
102
102
  ): Promise<void> {
103
- try {
104
- const buf = await streamToBuffer(createReadStream(file))
103
+ const buf = await readTemplateInput(streamToBuffer(createReadStream(file)))
105
104
 
106
- let steps: TemplateContent['steps'] | null = null
107
- let newName = name
105
+ let steps: TemplateContent['steps'] | null = null
106
+ let newName = name
108
107
 
109
- if (buf.length > 0) {
110
- steps = parseStepsInputJson(buf.toString(), output) as TemplateContent['steps']
111
- }
112
-
113
- if (!name || buf.length === 0) {
114
- const tpl = await client.getTemplate(template)
115
- if (!name) newName = tpl.name
116
- if (buf.length === 0 && tpl.content.steps) {
117
- steps = tpl.content.steps as TemplateContent['steps']
118
- }
119
- }
108
+ if (buf.length > 0) {
109
+ steps = parseStepsInputJson(buf.toString(), output) as TemplateContent['steps']
110
+ }
120
111
 
121
- if (steps === null) {
122
- throw new Error('No steps to update template with')
112
+ if (!name || buf.length === 0) {
113
+ const tpl = await client.getTemplate(template)
114
+ if (!name) newName = tpl.name
115
+ if (buf.length === 0 && tpl.content.steps) {
116
+ steps = tpl.content.steps as TemplateContent['steps']
123
117
  }
118
+ }
124
119
 
125
- await client.editTemplate(template, {
126
- name: newName,
127
- // Steps (validated) is assignable to StepsInput at runtime; cast for TS
128
- template: { steps } as TemplateContent,
129
- })
130
- } catch (err) {
131
- output.error(formatAPIError(err))
132
- throw err
120
+ if (steps === null) {
121
+ throw new CliInputError('No steps to update template with')
133
122
  }
123
+
124
+ await client.editTemplate(template, {
125
+ name: newName,
126
+ // Steps (validated) is assignable to StepsInput at runtime; cast for TS
127
+ template: { steps } as TemplateContent,
128
+ })
134
129
  }
135
130
 
136
131
  async function deleteTemplates(
137
132
  output: IOutputCtl,
138
133
  client: Transloadit,
139
134
  { templates }: TemplateDeleteOptions,
140
- ): Promise<void> {
141
- await Promise.all(
142
- templates.map(async (template) => {
143
- const [err] = await tryCatch(client.deleteTemplate(template))
144
- if (err) {
145
- output.error(formatAPIError(err))
146
- throw err
147
- }
148
- }),
135
+ ): Promise<number | undefined> {
136
+ const results = await Promise.allSettled(
137
+ templates.map((template) => client.deleteTemplate(template)),
149
138
  )
139
+ let exitCode: number | undefined
140
+ for (const [index, result] of results.entries()) {
141
+ if (result.status === 'fulfilled') continue
142
+ output.error(`Template ${templates[index]}: ${formatAPIError(result.reason)}`)
143
+ exitCode = 1
144
+ }
145
+ return exitCode
150
146
  }
151
147
 
152
148
  // Export with `delete` alias for external consumers
@@ -281,54 +277,54 @@ export async function sync(
281
277
 
282
278
  const modified = new ModifiedLookup(client)
283
279
 
284
- const [err] = await tryCatch(
285
- Promise.all(
286
- templates.map(async (template) => {
287
- if (!('steps' in template.data)) {
288
- if (!template.data.transloadit_template_id) {
289
- throw new Error(`Template file has no id and no steps: ${template.file}`)
290
- }
291
- return download(template)
280
+ await Promise.all(
281
+ templates.map(async (template) => {
282
+ if (!('steps' in template.data)) {
283
+ if (!template.data.transloadit_template_id) {
284
+ throw new CliInputError(`Template file has no id and no steps: ${template.file}`)
292
285
  }
286
+ return download(template)
287
+ }
293
288
 
294
- if (!template.data.transloadit_template_id) return upload(template)
295
-
296
- const stats = await fsp.stat(template.file)
297
- const fileModified = stats.mtime
298
-
299
- let templateModified: Date
300
- const templateId = template.data.transloadit_template_id
301
- try {
302
- await client.getTemplate(templateId)
303
- templateModified = await new Promise<Date>((resolve, reject) =>
304
- modified.byId(templateId, (err, res) => {
305
- if (err) {
306
- reject(err)
307
- } else if (res) {
308
- resolve(res)
309
- } else {
310
- reject(new Error('No date returned'))
311
- }
312
- }),
313
- )
314
- } catch (err) {
315
- if (isTransloaditAPIError(err)) {
316
- if (err.code === 'SERVER_404' || (err.response && err.response.statusCode === 404)) {
317
- throw new Error(`Template file references nonexistent template: ${template.file}`)
289
+ if (!template.data.transloadit_template_id) return upload(template)
290
+
291
+ const stats = await readTemplateInput(fsp.stat(template.file))
292
+ const fileModified = stats.mtime
293
+
294
+ let templateModified: Date
295
+ const templateId = template.data.transloadit_template_id
296
+ try {
297
+ await client.getTemplate(templateId)
298
+ templateModified = await new Promise<Date>((resolve, reject) =>
299
+ modified.byId(templateId, (err, res) => {
300
+ if (err) {
301
+ reject(err)
302
+ } else if (res) {
303
+ resolve(res)
304
+ } else {
305
+ reject(new Error('No date returned'))
318
306
  }
319
- }
320
- throw err
307
+ }),
308
+ )
309
+ } catch (err) {
310
+ if (
311
+ (err instanceof ApiError &&
312
+ (err.code === 'TEMPLATE_NOT_FOUND' || err.cause?.response?.statusCode === 404)) ||
313
+ (isTransloaditAPIError(err) &&
314
+ (err.code === 'SERVER_404' || err.response?.statusCode === 404))
315
+ ) {
316
+ throw new CliInputError(
317
+ `Template file references nonexistent template: ${template.file}`,
318
+ { cause: err },
319
+ )
321
320
  }
321
+ throw err
322
+ }
322
323
 
323
- if (fileModified > templateModified) return upload(template)
324
- return download(template)
325
- }),
326
- ),
324
+ if (fileModified > templateModified) return upload(template)
325
+ return download(template)
326
+ }),
327
327
  )
328
- if (err) {
329
- output.error(err)
330
- throw err
331
- }
332
328
 
333
329
  async function upload(template: TemplateFile): Promise<void> {
334
330
  const params = {
@@ -479,10 +475,9 @@ export class TemplatesDeleteCommand extends AuthenticatedCommand {
479
475
  templateIds = Option.Rest({ required: 1 })
480
476
 
481
477
  protected async run(): Promise<number | undefined> {
482
- await deleteTemplates(this.output, this.client, {
478
+ return await deleteTemplates(this.output, this.client, {
483
479
  templates: this.templateIds,
484
480
  })
485
- return undefined
486
481
  }
487
482
  }
488
483
 
@@ -0,0 +1,34 @@
1
+ import type { ZodIssue } from 'zod'
2
+
3
+ /** An input error the CLI can explain without an internal-error stack trace. */
4
+ export class CliInputError extends Error {
5
+ override name = 'CliInputError'
6
+ }
7
+
8
+ function describeIssues(issues: ZodIssue[]): string[] {
9
+ return issues.flatMap((issue) => {
10
+ if (issue.code === 'invalid_union') {
11
+ const descriptions = issue.unionErrors.flatMap((error) => describeIssues(error.issues))
12
+ // Interpolation is an alternative to literal Robot parameters, not a required input format.
13
+ const literals = descriptions.filter(
14
+ (description) => !description.endsWith(': Must be a full interpolation string'),
15
+ )
16
+ return literals.length > 0 ? literals : descriptions
17
+ }
18
+ const message =
19
+ issue.code === 'invalid_enum_value'
20
+ ? `Expected one of: ${issue.options.join(', ')}`
21
+ : issue.message
22
+ return [`${issue.path.join('.') || '(root)'}: ${message}`]
23
+ })
24
+ }
25
+
26
+ /** Summarize validation paths and remedies without duplicating Zod's union diagnostics. */
27
+ export function formatSchemaIssues(issues: ZodIssue[]): string {
28
+ const descriptions = [...new Set(describeIssues(issues))]
29
+ const remaining = descriptions.length - 10
30
+ return [
31
+ ...descriptions.slice(0, 10),
32
+ ...(remaining > 0 ? [`${remaining} more validation error${remaining === 1 ? '' : 's'}`] : []),
33
+ ].join('; ')
34
+ }
@@ -8,8 +8,10 @@ import { homedir, userInfo } from 'node:os'
8
8
  import path from 'node:path'
9
9
 
10
10
  import { parse as parseDotenv } from 'dotenv'
11
+ import { ParseError, RequestError } from 'got'
11
12
  import { z } from 'zod'
12
13
 
14
+ import { ApiError } from '../ApiError.ts'
13
15
  import { isAPIError } from './types.ts'
14
16
 
15
17
  /** API signing algorithms supported by CLI credentials and device authorization. */
@@ -535,6 +537,25 @@ export async function readCliInput(options: ReadCliInputOptions): Promise<CliInp
535
537
  }
536
538
 
537
539
  export function formatAPIError(err: unknown): string {
540
+ if (err instanceof RequestError) {
541
+ // ParseError messages contain response fragments and signed request URLs.
542
+ if (err instanceof ParseError) return `${err.code}: The API returned invalid JSON.`
543
+ const requestUrl = err.options.url
544
+ const message =
545
+ requestUrl instanceof URL
546
+ ? err.message.replaceAll(requestUrl.href, `${requestUrl.origin}${requestUrl.pathname}`)
547
+ : err.message
548
+ return `${err.code}: ${message}`
549
+ }
550
+ if (err instanceof ApiError && !err.assemblySslUrl) {
551
+ const requestUrl = err.cause?.options.url
552
+ const url = typeof requestUrl === 'string' ? new URL(requestUrl) : requestUrl
553
+ if (url && /\/assemblies\/[^/]+$/.test(url.pathname)) {
554
+ // Request URLs can carry signed query parameters; only expose the Assembly location.
555
+ return `${err.message} (Assembly URL: ${url.origin}${url.pathname})`
556
+ }
557
+ if (err.assemblyId) return `${err.message} (Assembly ID: ${err.assemblyId})`
558
+ }
538
559
  if (isAPIError(err)) {
539
560
  return `${err.error}: ${err.message}`
540
561
  }
@@ -33,6 +33,7 @@ export interface PreparedIntentInputs {
33
33
  cleanup: Array<() => Promise<void>>
34
34
  hasTransientInputs: boolean
35
35
  inputs: string[]
36
+ transientInputPaths?: string[]
36
37
  }
37
38
 
38
39
  export interface IntentSingleStepExecutionDefinition {
@@ -252,6 +253,7 @@ export async function prepareIntentInputs({
252
253
  cleanup: prepared.cleanup,
253
254
  hasTransientInputs: true,
254
255
  inputs,
256
+ transientInputPaths: Object.values(prepared.files),
255
257
  }
256
258
  }
257
259
 
@@ -383,14 +385,15 @@ async function executeIntentCommand({
383
385
  } as AssembliesCreateOptions['stepsData'],
384
386
  }
385
387
 
386
- const { hasFailures, resultUrls } = await assembliesCommands.create(output, client, {
388
+ const { assemblies, hasFailures, resultUrls } = await assembliesCommands.create(output, client, {
387
389
  ...createOptions,
388
390
  output: outputPath ?? null,
389
391
  outputMode,
390
392
  ...executionOptions,
393
+ printUrls,
391
394
  })
392
395
  if (printUrls) {
393
- printResultUrls(output, resultUrls)
396
+ printResultUrls(output, resultUrls, assemblies)
394
397
  }
395
398
  return hasFailures ? 1 : undefined
396
399
  }
@@ -766,9 +769,13 @@ abstract class GeneratedFileIntentCommandBase extends GeneratedIntentCommandBase
766
769
  }
767
770
 
768
771
  const effectiveOutputPath = this.getEffectiveOutputPath(rawValues)
772
+ const createOptions = this.getCreateOptions(effectivePreparedInputs.inputs)
773
+ if (effectivePreparedInputs.transientInputPaths != null) {
774
+ createOptions.transientInputPaths = effectivePreparedInputs.transientInputPaths
775
+ }
769
776
  return await executeIntentCommand({
770
777
  client: this.client,
771
- createOptions: this.getCreateOptions(effectivePreparedInputs.inputs),
778
+ createOptions,
772
779
  definition: this.getIntentDefinition(),
773
780
  output: this.output,
774
781
  outputMode: this.getEffectiveOutputMode(rawValues, effectiveOutputPath),
@@ -1,6 +1,7 @@
1
1
  export interface AssemblyResultEntryLike {
2
2
  basename?: unknown
3
3
  ext?: unknown
4
+ meta?: unknown
4
5
  name?: unknown
5
6
  ssl_url?: unknown
6
7
  url?: unknown
@@ -1,5 +1,6 @@
1
1
  import type { IOutputCtl } from './OutputCtl.ts'
2
2
  import type { NormalizedAssemblyResults } from './resultFiles.ts'
3
+ import type { AssemblySummary } from './types.ts'
3
4
 
4
5
  import { normalizeAssemblyResults } from './resultFiles.ts'
5
6
 
@@ -64,10 +65,17 @@ export function formatResultUrlRows(rows: readonly ResultUrlRow[]): string {
64
65
  .join('\n')
65
66
  }
66
67
 
67
- export function printResultUrls(output: IOutputCtl, rows: readonly ResultUrlRow[]): void {
68
- if (rows.length === 0) {
68
+ export function printResultUrls(
69
+ output: IOutputCtl,
70
+ rows: readonly ResultUrlRow[],
71
+ assemblies?: readonly AssemblySummary[],
72
+ ): void {
73
+ if (rows.length === 0 && !assemblies?.length) {
69
74
  return
70
75
  }
71
76
 
72
- output.print(formatResultUrlRows(rows), { urls: rows })
77
+ output.print(rows.length > 0 ? formatResultUrlRows(rows) : undefined, {
78
+ urls: rows,
79
+ ...(assemblies?.length ? { assemblies } : {}),
80
+ })
73
81
  }
@@ -5,17 +5,52 @@ import fsp from 'node:fs/promises'
5
5
 
6
6
  import { z } from 'zod'
7
7
 
8
+ import { tryCatch } from '../alphalib/tryCatch.ts'
8
9
  import { robotsSchema } from '../alphalib/types/robots/_index.ts'
9
10
  import { refineDocumentExtractOcr } from '../alphalib/types/robots/document-extract.ts'
10
11
  import { refineTransloaditImportSelector } from '../alphalib/types/robots/transloadit-import.ts'
12
+ import { CliInputError, formatSchemaIssues } from './errors.ts'
11
13
 
12
14
  const stepsWrapperSchema = z.object({ steps: z.record(z.unknown()) })
13
15
 
14
- /** CLI inputs can be partial overrides; API2 validates OCR after merging their runtime context. */
16
+ /** CLI overrides warn on new parameters and defer contextual OCR checks to API2. */
15
17
  export function createCliStepsSchema(
16
18
  output: Pick<IOutputCtl, 'warn'> = console,
17
19
  ): z.ZodType<StepsInput> {
18
- const cliStepSchema = robotsSchema
20
+ const cliStepSchema = z
21
+ .object({ robot: z.string() })
22
+ .catchall(z.unknown())
23
+ .transform((step, context) => {
24
+ let validated = robotsSchema.safeParse(step, { path: context.path })
25
+ const unknownKeys = new Set<string>()
26
+ if (!validated.success) {
27
+ for (const issue of validated.error.issues) {
28
+ // Only new top-level Robot parameters are advisory; nested objects stay strict.
29
+ if (issue.code !== 'unrecognized_keys' || issue.path.length !== context.path.length)
30
+ continue
31
+ for (const key of issue.keys) unknownKeys.add(key)
32
+ }
33
+ if (unknownKeys.size > 0) {
34
+ validated = robotsSchema.safeParse(
35
+ Object.fromEntries(Object.entries(step).filter(([key]) => !unknownKeys.has(key))),
36
+ { path: context.path },
37
+ )
38
+ }
39
+ }
40
+ if (!validated.success) {
41
+ for (const issue of validated.error.issues) {
42
+ // Keep nested union diagnostics rooted at the Step while addIssue prefixes its own path.
43
+ context.addIssue({ ...issue, path: issue.path.slice(context.path.length) })
44
+ }
45
+ return z.NEVER
46
+ }
47
+ if (unknownKeys.size > 0) {
48
+ output.warn(
49
+ `${context.path.join('.')}: Unrecognized step parameters: ${[...unknownKeys].join(', ')}. Preserving them for API validation when submitted.`,
50
+ )
51
+ }
52
+ return { ...step, ...validated.data }
53
+ })
19
54
  .superRefine(refineTransloaditImportSelector)
20
55
  .superRefine((step, context) => {
21
56
  // API2 merges Template defaults before Robot validation. New OCR checks must not reject
@@ -35,7 +70,12 @@ export function parseStepsInputJson(
35
70
  content: string,
36
71
  output: Pick<IOutputCtl, 'warn'> = console,
37
72
  ): StepsInput {
38
- const parsed: unknown = JSON.parse(content)
73
+ let parsed: unknown
74
+ try {
75
+ parsed = JSON.parse(content)
76
+ } catch (error) {
77
+ throw new CliInputError('Invalid steps format: expected valid JSON', { cause: error })
78
+ }
39
79
  const wrapped = stepsWrapperSchema.safeParse(parsed)
40
80
  let stepsInput = parsed
41
81
  // A step named “steps” has a robot, unlike the top-level instructions wrapper.
@@ -43,13 +83,18 @@ export function parseStepsInputJson(
43
83
  // --steps only supplies steps; other instructions must not be silently discarded.
44
84
  const validatedWrapper = stepsWrapperSchema.strict().safeParse(parsed)
45
85
  if (!validatedWrapper.success) {
46
- throw new Error(`Invalid steps format: ${validatedWrapper.error.message}`)
86
+ throw new CliInputError(
87
+ `Invalid steps format: ${formatSchemaIssues(validatedWrapper.error.issues)}`,
88
+ { cause: validatedWrapper.error },
89
+ )
47
90
  }
48
91
  stepsInput = validatedWrapper.data.steps
49
92
  }
50
93
  const validated = createCliStepsSchema(output).safeParse(stepsInput)
51
94
  if (!validated.success) {
52
- throw new Error(`Invalid steps format: ${validated.error.message}`)
95
+ throw new CliInputError(`Invalid steps format: ${formatSchemaIssues(validated.error.issues)}`, {
96
+ cause: validated.error,
97
+ })
53
98
  }
54
99
 
55
100
  const parsedSteps = stepsInput as Record<string, Record<string, unknown>>
@@ -72,6 +117,9 @@ export async function readStepsInputFile(
72
117
  filePath: string,
73
118
  output: Pick<IOutputCtl, 'warn'> = console,
74
119
  ): Promise<StepsInput> {
75
- const content = await fsp.readFile(filePath, 'utf8')
120
+ const [error, content] = await tryCatch(fsp.readFile(filePath, 'utf8'))
121
+ if (error) {
122
+ throw new CliInputError(`Cannot read steps input: ${error.message}`, { cause: error })
123
+ }
76
124
  return parseStepsInputJson(content, output)
77
125
  }
package/src/cli/types.ts CHANGED
@@ -2,6 +2,41 @@ import type { Steps } from '../alphalib/types/template.ts'
2
2
 
3
3
  import { z } from 'zod'
4
4
 
5
+ const ignoredErrorDetailsSchema = z
6
+ .union([
7
+ z.string(),
8
+ z.object({
9
+ code: z.string().optional().catch(undefined),
10
+ message: z.string().optional().catch(undefined),
11
+ }),
12
+ ])
13
+ .optional()
14
+ .catch(undefined)
15
+
16
+ const ignoredErrorSummarySchema = z
17
+ .object({
18
+ step: z.string().nullable().optional().catch(undefined),
19
+ phase: z.string().optional().catch(undefined),
20
+ error: ignoredErrorDetailsSchema,
21
+ message: z.string().optional().catch(undefined),
22
+ })
23
+ .catch({})
24
+
25
+ /** Project unchecked API responses to CLI diagnostics without nested request metadata. */
26
+ export const assemblySummarySchema = z.object({
27
+ assembly_id: z.string().optional().catch(undefined),
28
+ assembly_url: z.string().nullable().optional().catch(undefined),
29
+ assembly_ssl_url: z.string().nullable().optional().catch(undefined),
30
+ ok: z.string().nullable().optional().catch(undefined),
31
+ error: z.string().optional().catch(undefined),
32
+ message: z.string().optional().catch(undefined),
33
+ ignored_error_count: z.number().finite().int().nonnegative().default(0).catch(0),
34
+ ignored_errors: z.array(ignoredErrorSummarySchema).default([]).catch([]),
35
+ })
36
+
37
+ /** Public diagnostics from one Assembly, excluding instructions and request internals. */
38
+ export type AssemblySummary = z.infer<typeof assemblySummarySchema>
39
+
5
40
  // Zod schemas for runtime validation
6
41
  const APIErrorSchema = z.object({
7
42
  error: z.string(),