@transloadit/node 4.1.9 → 4.3.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 (105) hide show
  1. package/README.md +81 -1
  2. package/dist/Transloadit.d.ts +36 -5
  3. package/dist/Transloadit.d.ts.map +1 -1
  4. package/dist/Transloadit.js +228 -39
  5. package/dist/Transloadit.js.map +1 -1
  6. package/dist/alphalib/assembly-linter.d.ts +123 -0
  7. package/dist/alphalib/assembly-linter.d.ts.map +1 -0
  8. package/dist/alphalib/assembly-linter.js +1142 -0
  9. package/dist/alphalib/assembly-linter.js.map +1 -0
  10. package/dist/alphalib/assembly-linter.lang.en.d.ts +87 -0
  11. package/dist/alphalib/assembly-linter.lang.en.d.ts.map +1 -0
  12. package/dist/alphalib/assembly-linter.lang.en.js +326 -0
  13. package/dist/alphalib/assembly-linter.lang.en.js.map +1 -0
  14. package/dist/alphalib/mcache.d.ts.map +1 -1
  15. package/dist/alphalib/mcache.js +22 -7
  16. package/dist/alphalib/mcache.js.map +1 -1
  17. package/dist/alphalib/object.d.ts +20 -0
  18. package/dist/alphalib/object.d.ts.map +1 -0
  19. package/dist/alphalib/object.js +23 -0
  20. package/dist/alphalib/object.js.map +1 -0
  21. package/dist/alphalib/stepParsing.d.ts +93 -0
  22. package/dist/alphalib/stepParsing.d.ts.map +1 -0
  23. package/dist/alphalib/stepParsing.js +1154 -0
  24. package/dist/alphalib/stepParsing.js.map +1 -0
  25. package/dist/alphalib/templateMerge.d.ts +4 -0
  26. package/dist/alphalib/templateMerge.d.ts.map +1 -0
  27. package/dist/alphalib/templateMerge.js +22 -0
  28. package/dist/alphalib/templateMerge.js.map +1 -0
  29. package/dist/alphalib/types/assemblyReplay.d.ts +56 -0
  30. package/dist/alphalib/types/assemblyReplay.d.ts.map +1 -1
  31. package/dist/alphalib/types/assemblyReplayNotification.d.ts +56 -0
  32. package/dist/alphalib/types/assemblyReplayNotification.d.ts.map +1 -1
  33. package/dist/alphalib/types/assemblyStatus.d.ts +63 -57
  34. package/dist/alphalib/types/assemblyStatus.d.ts.map +1 -1
  35. package/dist/alphalib/types/assemblyStatus.js +9 -1
  36. package/dist/alphalib/types/assemblyStatus.js.map +1 -1
  37. package/dist/alphalib/types/assemblyUrls.d.ts +1 -1
  38. package/dist/alphalib/types/assemblyUrls.d.ts.map +1 -1
  39. package/dist/alphalib/types/assemblyUrls.js.map +1 -1
  40. package/dist/alphalib/types/robots/_index.d.ts +608 -81
  41. package/dist/alphalib/types/robots/_index.d.ts.map +1 -1
  42. package/dist/alphalib/types/robots/_index.js +4 -0
  43. package/dist/alphalib/types/robots/_index.js.map +1 -1
  44. package/dist/alphalib/types/robots/_instructions-primitives.d.ts +4 -4
  45. package/dist/alphalib/types/robots/_instructions-primitives.d.ts.map +1 -1
  46. package/dist/alphalib/types/robots/_instructions-primitives.js +1 -0
  47. package/dist/alphalib/types/robots/_instructions-primitives.js.map +1 -1
  48. package/dist/alphalib/types/robots/document-optimize.d.ts +489 -0
  49. package/dist/alphalib/types/robots/document-optimize.d.ts.map +1 -0
  50. package/dist/alphalib/types/robots/document-optimize.js +151 -0
  51. package/dist/alphalib/types/robots/document-optimize.js.map +1 -0
  52. package/dist/alphalib/types/template.d.ts +1050 -174
  53. package/dist/alphalib/types/template.d.ts.map +1 -1
  54. package/dist/cli/commands/assemblies.d.ts +20 -1
  55. package/dist/cli/commands/assemblies.d.ts.map +1 -1
  56. package/dist/cli/commands/assemblies.js +137 -2
  57. package/dist/cli/commands/assemblies.js.map +1 -1
  58. package/dist/cli/commands/auth.d.ts.map +1 -1
  59. package/dist/cli/commands/auth.js +19 -19
  60. package/dist/cli/commands/auth.js.map +1 -1
  61. package/dist/cli/commands/index.d.ts.map +1 -1
  62. package/dist/cli/commands/index.js +2 -1
  63. package/dist/cli/commands/index.js.map +1 -1
  64. package/dist/cli/docs/assemblyLintingExamples.d.ts +2 -0
  65. package/dist/cli/docs/assemblyLintingExamples.d.ts.map +1 -0
  66. package/dist/cli/docs/assemblyLintingExamples.js +10 -0
  67. package/dist/cli/docs/assemblyLintingExamples.js.map +1 -0
  68. package/dist/cli/helpers.d.ts +11 -0
  69. package/dist/cli/helpers.d.ts.map +1 -1
  70. package/dist/cli/helpers.js +29 -0
  71. package/dist/cli/helpers.js.map +1 -1
  72. package/dist/lintAssemblyInput.d.ts +10 -0
  73. package/dist/lintAssemblyInput.d.ts.map +1 -0
  74. package/dist/lintAssemblyInput.js +73 -0
  75. package/dist/lintAssemblyInput.js.map +1 -0
  76. package/dist/lintAssemblyInstructions.d.ts +29 -0
  77. package/dist/lintAssemblyInstructions.d.ts.map +1 -0
  78. package/dist/lintAssemblyInstructions.js +33 -0
  79. package/dist/lintAssemblyInstructions.js.map +1 -0
  80. package/dist/tus.d.ts +2 -1
  81. package/dist/tus.d.ts.map +1 -1
  82. package/dist/tus.js +2 -1
  83. package/dist/tus.js.map +1 -1
  84. package/package.json +5 -2
  85. package/src/Transloadit.ts +318 -49
  86. package/src/alphalib/assembly-linter.lang.en.ts +393 -0
  87. package/src/alphalib/assembly-linter.ts +1475 -0
  88. package/src/alphalib/mcache.ts +26 -7
  89. package/src/alphalib/object.ts +27 -0
  90. package/src/alphalib/stepParsing.ts +1465 -0
  91. package/src/alphalib/templateMerge.ts +32 -0
  92. package/src/alphalib/types/assemblyStatus.ts +9 -1
  93. package/src/alphalib/types/assemblyUrls.ts +2 -5
  94. package/src/alphalib/types/robots/_index.ts +14 -0
  95. package/src/alphalib/types/robots/_instructions-primitives.ts +1 -0
  96. package/src/alphalib/types/robots/document-optimize.ts +180 -0
  97. package/src/alphalib/typings/json-to-ast.d.ts +34 -0
  98. package/src/cli/commands/assemblies.ts +161 -2
  99. package/src/cli/commands/auth.ts +19 -22
  100. package/src/cli/commands/index.ts +2 -0
  101. package/src/cli/docs/assemblyLintingExamples.ts +9 -0
  102. package/src/cli/helpers.ts +50 -0
  103. package/src/lintAssemblyInput.ts +89 -0
  104. package/src/lintAssemblyInstructions.ts +72 -0
  105. package/src/tus.ts +3 -0
@@ -0,0 +1,32 @@
1
+ import merge from 'lodash-es/merge.js'
2
+ import type { ResponseTemplateContent, TemplateContent } from '../apiTypes.ts'
3
+ import type { AssemblyInstructionsInput } from './types/template.ts'
4
+
5
+ export function mergeTemplateContent(
6
+ template: TemplateContent | ResponseTemplateContent,
7
+ params?: AssemblyInstructionsInput,
8
+ ): AssemblyInstructionsInput {
9
+ const templateContent = structuredClone(template) as TemplateContent | ResponseTemplateContent
10
+ const templateRecord = templateContent as Record<string, unknown>
11
+
12
+ if (templateContent.allow_steps_override == null) {
13
+ templateContent.allow_steps_override = true
14
+ }
15
+
16
+ if (params?.steps != null && templateContent.allow_steps_override === false) {
17
+ throw new Error('TEMPLATE_DENIES_STEPS_OVERRIDE')
18
+ }
19
+
20
+ if (params == null) {
21
+ return templateContent as AssemblyInstructionsInput
22
+ }
23
+
24
+ const paramsRecord = { ...params } as Record<string, unknown>
25
+ for (const key of Object.keys(templateRecord)) {
26
+ if (paramsRecord[key] === null && templateRecord[key] !== null) {
27
+ paramsRecord[key] = templateRecord[key]
28
+ }
29
+ }
30
+
31
+ return merge({}, templateRecord, paramsRecord) as AssemblyInstructionsInput
32
+ }
@@ -78,6 +78,8 @@ export const assemblyStatusErrCodeSchema = z.enum([
78
78
  'DIGITALOCEAN_STORE_ACCESS_DENIED',
79
79
  'DO_NOT_REUSE_ASSEMBLY_IDS',
80
80
  'DOCUMENT_CONVERT_UNSUPPORTED_CONVERSION',
81
+ 'DOCUMENT_OPTIMIZE_UNSUPPORTED_INPUT',
82
+ 'DOCUMENT_OPTIMIZE_VALIDATION',
81
83
  'DOCUMENT_SPLIT_VALIDATION',
82
84
  'FILE_DOWNLOAD_ERROR',
83
85
  'FILE_FILTER_DECLINED_FILE',
@@ -112,6 +114,7 @@ export const assemblyStatusErrCodeSchema = z.enum([
112
114
  'INVALID_AUTH_REFERER_PARAMETER',
113
115
  'INVALID_FILE_META_DATA',
114
116
  'INVALID_FORM_DATA',
117
+ 'INVALID_URL_ENCODING',
115
118
  'INVALID_INPUT_ERROR',
116
119
  'INVALID_PARAMS_FIELD',
117
120
  'INVALID_SIGNATURE',
@@ -156,6 +159,9 @@ export const assemblyStatusErrCodeSchema = z.enum([
156
159
  'TMP_FILE_DOWNLOAD_ERROR',
157
160
  'USER_COMMAND_ERROR',
158
161
  'VERIFIED_EMAIL_REQUIRED',
162
+ 'VIDEO_CONCAT_INVALID_INPUT',
163
+ 'VIDEO_CONCAT_NO_OUTPUT',
164
+ 'VIDEO_CONCAT_VALIDATION',
159
165
  'VIDEO_ENCODE_VALIDATION',
160
166
  'VIMEO_IMPORT_FAILURE',
161
167
  'WORKER_JOB_ERROR',
@@ -379,7 +385,7 @@ export const assemblyStatusUploadSchema = z
379
385
  basename: z.string(),
380
386
  ext: z.string(),
381
387
  size: z.number(),
382
- mime: z.string(),
388
+ mime: z.string().nullable(),
383
389
  type: z.string().nullable(),
384
390
  field: z.string().nullable(),
385
391
  md5hash: z.string().nullable(),
@@ -614,6 +620,8 @@ export const assemblyStatusErrSchema = assemblyStatusBaseSchema
614
620
  reason: z.string().optional(),
615
621
  step: z.string().optional(),
616
622
  previousStep: z.string().optional(),
623
+ file: z.string().optional(),
624
+ name: z.string().optional(),
617
625
  path: z.string().optional(),
618
626
  exitCode: z.number().nullable().optional(),
619
627
  exitSignal: z.string().nullable().optional(),
@@ -1,5 +1,5 @@
1
+ import type { AssemblyStatus } from './assemblyStatus.ts'
1
2
  import {
2
- type AssemblyStatus,
3
3
  isAssemblyBusyStatus,
4
4
  isAssemblyTerminalError,
5
5
  isAssemblyTerminalOk,
@@ -56,10 +56,7 @@ const assemblyUrlKeys = ['assembly_ssl_url', 'assembly_url', 'assemblyUrl'] as c
56
56
  const isRecord = (value: unknown): value is Record<string, unknown> =>
57
57
  value !== null && typeof value === 'object' && !Array.isArray(value)
58
58
 
59
- const pickString = (
60
- record: Record<string, unknown>,
61
- keys: readonly string[],
62
- ): string | null => {
59
+ const pickString = (record: Record<string, unknown>, keys: readonly string[]): string | null => {
63
60
  for (const key of keys) {
64
61
  const value = record[key]
65
62
  if (typeof value === 'string' && value.length > 0) return value
@@ -104,6 +104,11 @@ import {
104
104
  interpolatableRobotDocumentOcrInstructionsSchema,
105
105
  interpolatableRobotDocumentOcrInstructionsWithHiddenFieldsSchema,
106
106
  } from './document-ocr.ts'
107
+ import {
108
+ meta as documentOptimizeMeta,
109
+ interpolatableRobotDocumentOptimizeInstructionsSchema,
110
+ interpolatableRobotDocumentOptimizeInstructionsWithHiddenFieldsSchema,
111
+ } from './document-optimize.ts'
107
112
  import {
108
113
  meta as documentSplitMeta,
109
114
  interpolatableRobotDocumentSplitInstructionsSchema,
@@ -435,6 +440,7 @@ const robotStepsInstructions = [
435
440
  interpolatableRobotDocumentConvertInstructionsSchema,
436
441
  interpolatableRobotDocumentMergeInstructionsSchema,
437
442
  interpolatableRobotDocumentOcrInstructionsSchema,
443
+ interpolatableRobotDocumentOptimizeInstructionsSchema,
438
444
  interpolatableRobotFileReadInstructionsSchema,
439
445
  interpolatableRobotDocumentSplitInstructionsSchema,
440
446
  interpolatableRobotDocumentThumbsInstructionsSchema,
@@ -519,6 +525,7 @@ const robotStepsInstructionsWithHiddenFields = [
519
525
  interpolatableRobotDocumentConvertInstructionsWithHiddenFieldsSchema,
520
526
  interpolatableRobotDocumentMergeInstructionsWithHiddenFieldsSchema,
521
527
  interpolatableRobotDocumentOcrInstructionsWithHiddenFieldsSchema,
528
+ interpolatableRobotDocumentOptimizeInstructionsWithHiddenFieldsSchema,
522
529
  interpolatableRobotFileReadInstructionsWithHiddenFieldsSchema,
523
530
  interpolatableRobotDocumentSplitInstructionsWithHiddenFieldsSchema,
524
531
  interpolatableRobotDocumentThumbsInstructionsWithHiddenFieldsSchema,
@@ -632,6 +639,7 @@ export const robotsMeta = {
632
639
  documentConvertMeta,
633
640
  documentMergeMeta,
634
641
  documentOcrMeta,
642
+ documentOptimizeMeta,
635
643
  documentSplitMeta,
636
644
  documentThumbsMeta,
637
645
  dropboxImportMeta,
@@ -824,6 +832,12 @@ export type {
824
832
  InterpolatableRobotDocumentOcrInstructionsWithHiddenFields,
825
833
  InterpolatableRobotDocumentOcrInstructionsWithHiddenFieldsInput,
826
834
  } from './document-ocr.ts'
835
+ export type {
836
+ InterpolatableRobotDocumentOptimizeInstructions,
837
+ InterpolatableRobotDocumentOptimizeInstructionsInput,
838
+ InterpolatableRobotDocumentOptimizeInstructionsWithHiddenFields,
839
+ InterpolatableRobotDocumentOptimizeInstructionsWithHiddenFieldsInput,
840
+ } from './document-optimize.ts'
827
841
  export type {
828
842
  InterpolatableRobotDocumentSplitInstructions,
829
843
  InterpolatableRobotDocumentSplitInstructionsInput,
@@ -40,6 +40,7 @@ export const robotNames = z.enum([
40
40
  'DocumentConvertRobot',
41
41
  'DocumentMergeRobot',
42
42
  'DocumentSplitRobot',
43
+ 'DocumentOptimizeRobot',
43
44
  'DocumentAutorotateRobot',
44
45
  'HtmlConvertRobot',
45
46
  'ImageResizeRobot',
@@ -0,0 +1,180 @@
1
+ import { z } from 'zod'
2
+
3
+ import type { RobotMetaInput } from './_instructions-primitives.ts'
4
+ import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts'
5
+
6
+ export const meta: RobotMetaInput = {
7
+ allowed_for_url_transform: true,
8
+ bytescount: 1,
9
+ discount_factor: 1,
10
+ discount_pct: 0,
11
+ example_code: {
12
+ steps: {
13
+ optimized: {
14
+ robot: '/document/optimize',
15
+ use: ':original',
16
+ preset: 'ebook',
17
+ },
18
+ },
19
+ },
20
+ example_code_description: 'Optimize PDF file size using the ebook preset:',
21
+ extended_description: `
22
+ This <dfn>Robot</dfn> reduces PDF file sizes. It recompresses images, subsets fonts, and applies various optimizations to reduce file size while maintaining acceptable quality.
23
+
24
+ ## Quality Presets
25
+
26
+ The Robot supports four quality presets that control the trade-off between file size and quality:
27
+
28
+ | Preset | DPI | Use Case | Typical Savings |
29
+ |--------|-----|----------|-----------------|
30
+ | \`screen\` | 72 | Screen viewing, smallest files | ~86% |
31
+ | \`ebook\` | 150 | Good balance of quality/size | ~71% |
32
+ | \`printer\` | 300 | Print quality | Moderate |
33
+ | \`prepress\` | Highest | Press-ready, largest files | Minimal |
34
+
35
+ ## Use Cases
36
+
37
+ - Reducing storage costs for archived documents
38
+ - Faster document delivery and download
39
+ - Meeting email attachment size limits
40
+ - Mobile-optimized document viewing
41
+ `,
42
+ minimum_charge: 2097152,
43
+ output_factor: 0.5,
44
+ override_lvl1: 'Document Processing',
45
+ purpose_sentence: 'reduces the file size of PDF documents',
46
+ purpose_verb: 'optimize',
47
+ purpose_word: 'optimize PDF',
48
+ purpose_words: 'Optimize PDF file size',
49
+ service_slug: 'document-processing',
50
+ slot_count: 10,
51
+ title: 'Reduce PDF file size',
52
+ typical_file_size_mb: 2.0,
53
+ typical_file_type: 'document',
54
+ name: 'DocumentOptimizeRobot',
55
+ priceFactor: 1,
56
+ queueSlotCount: 10,
57
+ minimumCharge: 2097152,
58
+ isAllowedForUrlTransform: true,
59
+ trackOutputFileSize: true,
60
+ isInternal: false,
61
+ removeJobResultFilesFromDiskRightAfterStoringOnS3: false,
62
+ stage: 'beta',
63
+ }
64
+
65
+ export const robotDocumentOptimizeInstructionsSchema = robotBase
66
+ .merge(robotUse)
67
+ .extend({
68
+ robot: z.literal('/document/optimize').describe(`
69
+ This Robot reduces PDF file sizes. It recompresses images, subsets fonts, and applies various optimizations to reduce file size while maintaining acceptable quality.
70
+
71
+ ## Quality Presets
72
+
73
+ The Robot supports four quality presets that control the trade-off between file size and quality:
74
+
75
+ | Preset | DPI | Use Case | Typical Savings |
76
+ |--------|-----|----------|-----------------|
77
+ | \`screen\` | 72 | Screen viewing, smallest files | ~86% |
78
+ | \`ebook\` | 150 | Good balance of quality/size | ~71% |
79
+ | \`printer\` | 300 | Print quality | Moderate |
80
+ | \`prepress\` | Highest | Press-ready, largest files | Minimal |
81
+ `),
82
+ preset: z
83
+ .enum(['screen', 'ebook', 'printer', 'prepress'])
84
+ .default('ebook')
85
+ .describe(`
86
+ The quality preset to use for optimization. Each preset provides a different balance between file size and quality:
87
+
88
+ - \`screen\` - Lowest quality, smallest file size. Best for screen viewing only. Images are downsampled to 72 DPI.
89
+ - \`ebook\` - Good balance of quality and size. Suitable for most purposes. Images are downsampled to 150 DPI.
90
+ - \`printer\` - High quality suitable for printing. Images are kept at 300 DPI.
91
+ - \`prepress\` - Highest quality for professional printing. Minimal compression applied.
92
+ `),
93
+ image_dpi: z
94
+ .number()
95
+ .int()
96
+ .min(36)
97
+ .max(600)
98
+ .optional()
99
+ .describe(`
100
+ Target DPI (dots per inch) for embedded images. When specified, this overrides the DPI setting from the preset.
101
+
102
+ Higher DPI values result in better image quality but larger file sizes. Lower values produce smaller files but may result in pixelated images when printed.
103
+
104
+ Common values:
105
+ - 72 - Screen viewing
106
+ - 150 - eBooks and general documents
107
+ - 300 - Print quality
108
+ - 600 - High-quality print
109
+ `),
110
+ compress_fonts: z
111
+ .boolean()
112
+ .default(true)
113
+ .describe(`
114
+ Whether to compress embedded fonts. When enabled, fonts are compressed to reduce file size.
115
+ `),
116
+ subset_fonts: z
117
+ .boolean()
118
+ .default(true)
119
+ .describe(`
120
+ Whether to subset embedded fonts, keeping only the glyphs that are actually used in the document. This can significantly reduce file size for documents that only use a small portion of a font's character set.
121
+ `),
122
+ remove_metadata: z
123
+ .boolean()
124
+ .default(false)
125
+ .describe(`
126
+ Whether to strip document metadata (title, author, keywords, etc.) from the PDF. This can provide a small reduction in file size and may be useful for privacy.
127
+ `),
128
+ linearize: z
129
+ .boolean()
130
+ .default(true)
131
+ .describe(`
132
+ Whether to linearize (optimize for Fast Web View) the output PDF. Linearized PDFs can begin displaying in a browser before they are fully downloaded, improving the user experience for web delivery.
133
+ `),
134
+ compatibility: z
135
+ .enum(['1.4', '1.5', '1.6', '1.7', '2.0'])
136
+ .default('1.7')
137
+ .describe(`
138
+ The PDF version compatibility level. Lower versions have broader compatibility but fewer features. Higher versions support more advanced features but may not open in older PDF readers.
139
+
140
+ - \`1.4\` - Acrobat 5 compatibility, most widely supported
141
+ - \`1.5\` - Acrobat 6 compatibility
142
+ - \`1.6\` - Acrobat 7 compatibility
143
+ - \`1.7\` - Acrobat 8+ compatibility (default)
144
+ - \`2.0\` - PDF 2.0 standard
145
+ `),
146
+ })
147
+ .strict()
148
+
149
+ export const robotDocumentOptimizeInstructionsWithHiddenFieldsSchema =
150
+ robotDocumentOptimizeInstructionsSchema.extend({
151
+ result: z
152
+ .union([z.literal('debug'), robotDocumentOptimizeInstructionsSchema.shape.result])
153
+ .optional(),
154
+ })
155
+
156
+ export type RobotDocumentOptimizeInstructions = z.infer<
157
+ typeof robotDocumentOptimizeInstructionsSchema
158
+ >
159
+ export type RobotDocumentOptimizeInstructionsWithHiddenFields = z.infer<
160
+ typeof robotDocumentOptimizeInstructionsWithHiddenFieldsSchema
161
+ >
162
+
163
+ export const interpolatableRobotDocumentOptimizeInstructionsSchema = interpolateRobot(
164
+ robotDocumentOptimizeInstructionsSchema,
165
+ )
166
+ export type InterpolatableRobotDocumentOptimizeInstructions =
167
+ InterpolatableRobotDocumentOptimizeInstructionsInput
168
+
169
+ export type InterpolatableRobotDocumentOptimizeInstructionsInput = z.input<
170
+ typeof interpolatableRobotDocumentOptimizeInstructionsSchema
171
+ >
172
+
173
+ export const interpolatableRobotDocumentOptimizeInstructionsWithHiddenFieldsSchema =
174
+ interpolateRobot(robotDocumentOptimizeInstructionsWithHiddenFieldsSchema)
175
+ export type InterpolatableRobotDocumentOptimizeInstructionsWithHiddenFields = z.infer<
176
+ typeof interpolatableRobotDocumentOptimizeInstructionsWithHiddenFieldsSchema
177
+ >
178
+ export type InterpolatableRobotDocumentOptimizeInstructionsWithHiddenFieldsInput = z.input<
179
+ typeof interpolatableRobotDocumentOptimizeInstructionsWithHiddenFieldsSchema
180
+ >
@@ -0,0 +1,34 @@
1
+ declare module 'json-to-ast' {
2
+ export interface JsonToAstLocation {
3
+ start: { line: number; column: number }
4
+ }
5
+
6
+ export interface ObjectPropertyNode {
7
+ key: { value: string; loc?: JsonToAstLocation }
8
+ value: ValueNode
9
+ loc?: JsonToAstLocation
10
+ }
11
+
12
+ export interface LiteralNode {
13
+ type: 'Literal'
14
+ value: unknown
15
+ loc?: JsonToAstLocation
16
+ }
17
+
18
+ export interface ArrayNode {
19
+ type: 'Array'
20
+ children: ValueNode[]
21
+ loc?: JsonToAstLocation
22
+ }
23
+
24
+ export interface ObjectNode {
25
+ type: 'Object'
26
+ children: ObjectPropertyNode[]
27
+ loc?: JsonToAstLocation
28
+ }
29
+
30
+ export type ValueNode = LiteralNode | ArrayNode | ObjectNode
31
+
32
+ const parse: (source: string, options?: { loc?: boolean }) => ValueNode
33
+ export default parse
34
+ }
@@ -13,15 +13,19 @@ import got from 'got'
13
13
  import PQueue from 'p-queue'
14
14
  import * as t from 'typanion'
15
15
  import { z } from 'zod'
16
+ import { formatLintIssue } from '../../alphalib/assembly-linter.lang.en.ts'
16
17
  import { tryCatch } from '../../alphalib/tryCatch.ts'
17
18
  import type { Steps, StepsInput } from '../../alphalib/types/template.ts'
18
19
  import { stepsSchema } from '../../alphalib/types/template.ts'
19
20
  import type { CreateAssemblyParams, ReplayAssemblyParams } from '../../apiTypes.ts'
21
+ import type { LintFatalLevel } from '../../lintAssemblyInstructions.ts'
22
+ import { lintAssemblyInstructions } from '../../lintAssemblyInstructions.ts'
20
23
  import type { CreateAssemblyOptions, Transloadit } from '../../Transloadit.ts'
21
- import { createReadStream, formatAPIError, streamToBuffer } from '../helpers.ts'
24
+ import { lintingExamples } from '../docs/assemblyLintingExamples.ts'
25
+ import { createReadStream, formatAPIError, readCliInput, streamToBuffer } from '../helpers.ts'
22
26
  import type { IOutputCtl } from '../OutputCtl.ts'
23
27
  import { ensureError, isErrnoException } from '../types.ts'
24
- import { AuthenticatedCommand } from './BaseCommand.ts'
28
+ import { AuthenticatedCommand, UnauthenticatedCommand } from './BaseCommand.ts'
25
29
 
26
30
  // --- From assemblies.ts: Schemas and interfaces ---
27
31
  export interface AssemblyListOptions {
@@ -48,6 +52,15 @@ export interface AssemblyReplayOptions {
48
52
  assemblies: string[]
49
53
  }
50
54
 
55
+ export interface AssemblyLintOptions {
56
+ steps?: string
57
+ template?: string
58
+ fatal?: LintFatalLevel
59
+ fix?: boolean
60
+ providedInput?: string
61
+ json?: boolean
62
+ }
63
+
51
64
  const AssemblySchema = z.object({
52
65
  id: z.string(),
53
66
  })
@@ -163,6 +176,97 @@ export async function replay(
163
176
  }
164
177
  }
165
178
 
179
+ export async function lint(
180
+ output: IOutputCtl,
181
+ client: Transloadit | null,
182
+ { steps, template, fatal, fix, providedInput, json }: AssemblyLintOptions,
183
+ ): Promise<number> {
184
+ let content: string | null
185
+ let isStdin: boolean
186
+ let inputPath: string | undefined
187
+ try {
188
+ ;({
189
+ content,
190
+ isStdin,
191
+ path: inputPath,
192
+ } = await readCliInput({
193
+ inputPath: steps,
194
+ providedInput,
195
+ allowStdinWhenNoPath: true,
196
+ }))
197
+ } catch (error) {
198
+ output.error(ensureError(error).message)
199
+ return 1
200
+ }
201
+
202
+ if (content == null && template == null) {
203
+ output.error('assemblies lint requires --steps or stdin input unless --template is provided')
204
+ return 1
205
+ }
206
+
207
+ if (fix && content == null && template != null) {
208
+ output.error('assemblies lint --fix requires local instructions (stdin or --steps)')
209
+ return 1
210
+ }
211
+
212
+ let result: Awaited<ReturnType<typeof lintAssemblyInstructions>>
213
+ try {
214
+ if (template != null) {
215
+ if (!client) {
216
+ output.error('Missing client for template lookup')
217
+ return 1
218
+ }
219
+ result = await client.lintAssemblyInstructions({
220
+ assemblyInstructions: content ?? undefined,
221
+ templateId: template,
222
+ fatal,
223
+ fix,
224
+ })
225
+ } else {
226
+ result = await lintAssemblyInstructions({
227
+ assemblyInstructions: content ?? undefined,
228
+ fatal,
229
+ fix,
230
+ })
231
+ }
232
+ } catch (error) {
233
+ output.error(ensureError(error).message)
234
+ return 1
235
+ }
236
+
237
+ const issues = result.issues
238
+
239
+ if (fix && isStdin) {
240
+ if (result.fixedInstructions == null) {
241
+ output.error('No fixed output available.')
242
+ return 1
243
+ }
244
+ process.stdout.write(`${result.fixedInstructions}\n`)
245
+ for (const issue of issues) {
246
+ const line = formatLintIssue(issue)
247
+ if (issue.type === 'warning') output.warn(line)
248
+ else output.error(line)
249
+ }
250
+ return result.success ? 0 : 1
251
+ }
252
+
253
+ if (fix && inputPath && result.fixedInstructions != null) {
254
+ await fsp.writeFile(inputPath, result.fixedInstructions)
255
+ }
256
+
257
+ if (json) {
258
+ output.print({ ...result, issues }, result)
259
+ } else if (issues.length === 0) {
260
+ output.print('No issues found', result)
261
+ } else {
262
+ for (const issue of issues) {
263
+ output.print(formatLintIssue(issue), issue)
264
+ }
265
+ }
266
+
267
+ return result.success ? 0 : 1
268
+ }
269
+
166
270
  // --- From assemblies-create.ts: Helper classes and functions ---
167
271
  interface NodeWatcher {
168
272
  on(event: 'error', listener: (err: Error) => void): void
@@ -1373,3 +1477,58 @@ export class AssembliesReplayCommand extends AuthenticatedCommand {
1373
1477
  return undefined
1374
1478
  }
1375
1479
  }
1480
+
1481
+ export class AssembliesLintCommand extends UnauthenticatedCommand {
1482
+ static override paths = [
1483
+ ['assemblies', 'lint'],
1484
+ ['assembly', 'lint'],
1485
+ ['a', 'lint'],
1486
+ ]
1487
+
1488
+ static override usage = Command.Usage({
1489
+ category: 'Assemblies',
1490
+ description: 'Lint Assembly Instructions',
1491
+ details: `
1492
+ Lint Assembly Instructions locally using Transloadit's linter.
1493
+ Provide instructions via --steps or stdin (steps-only JSON is accepted).
1494
+ Optionally pass --template to
1495
+ merge template content with steps before linting (same merge behavior as the API).
1496
+ `,
1497
+ examples: lintingExamples,
1498
+ })
1499
+
1500
+ steps = Option.String('--steps,-s', {
1501
+ description: 'JSON file with Assembly Instructions (use "-" for stdin)',
1502
+ })
1503
+
1504
+ template = Option.String('--template,-t', {
1505
+ description:
1506
+ 'Template ID to merge before linting. If the template forbids step overrides, linting will fail when steps are provided.',
1507
+ })
1508
+
1509
+ fatal = Option.String('--fatal', {
1510
+ description: 'Treat issues at this level as fatal (error or warning)',
1511
+ validator: t.isEnum(['error', 'warning']),
1512
+ })
1513
+
1514
+ fix = Option.Boolean('--fix', false, {
1515
+ description:
1516
+ 'Apply auto-fixes. For files, overwrites in place. For stdin, writes fixed JSON to stdout.',
1517
+ })
1518
+
1519
+ protected async run(): Promise<number | undefined> {
1520
+ let client: Transloadit | null = null
1521
+ if (this.template) {
1522
+ if (!this.setupClient()) return 1
1523
+ client = this.client
1524
+ }
1525
+
1526
+ return await lint(this.output, client, {
1527
+ steps: this.steps,
1528
+ template: this.template,
1529
+ fatal: this.fatal as LintFatalLevel | undefined,
1530
+ fix: this.fix,
1531
+ json: this.json,
1532
+ })
1533
+ }
1534
+ }
@@ -8,7 +8,7 @@ import {
8
8
  } from '../../alphalib/types/template.ts'
9
9
  import type { OptionalAuthParams } from '../../apiTypes.ts'
10
10
  import { Transloadit } from '../../Transloadit.ts'
11
- import { getEnvCredentials } from '../helpers.ts'
11
+ import { getEnvCredentials, readCliInput } from '../helpers.ts'
12
12
  import { UnauthenticatedCommand } from './BaseCommand.ts'
13
13
 
14
14
  type UrlParamPrimitive = string | number | boolean
@@ -68,19 +68,6 @@ function normalizeUrlParams(params?: Record<string, unknown>): NormalizedUrlPara
68
68
  return normalized
69
69
  }
70
70
 
71
- async function readStdin(): Promise<string> {
72
- if (process.stdin.isTTY) return ''
73
-
74
- process.stdin.setEncoding('utf8')
75
- let data = ''
76
-
77
- for await (const chunk of process.stdin) {
78
- data += chunk
79
- }
80
-
81
- return data
82
- }
83
-
84
71
  const getCredentials = getEnvCredentials
85
72
 
86
73
  // Result type for signature operations
@@ -217,8 +204,12 @@ export async function runSig(options: RunSigOptions = {}): Promise<void> {
217
204
  return
218
205
  }
219
206
 
220
- const rawInput = options.providedInput ?? (await readStdin())
221
- const result = generateSignature(rawInput.trim(), credentials, options.algorithm)
207
+ const { content } = await readCliInput({
208
+ providedInput: options.providedInput,
209
+ allowStdinWhenNoPath: true,
210
+ })
211
+ const rawInput = (content ?? '').trim()
212
+ const result = generateSignature(rawInput, credentials, options.algorithm)
222
213
 
223
214
  if (result.ok) {
224
215
  process.stdout.write(`${result.output}\n`)
@@ -238,8 +229,12 @@ export async function runSmartSig(options: RunSmartSigOptions = {}): Promise<voi
238
229
  return
239
230
  }
240
231
 
241
- const rawInput = options.providedInput ?? (await readStdin())
242
- const result = generateSmartCdnUrl(rawInput.trim(), credentials)
232
+ const { content } = await readCliInput({
233
+ providedInput: options.providedInput,
234
+ allowStdinWhenNoPath: true,
235
+ })
236
+ const rawInput = (content ?? '').trim()
237
+ const result = generateSmartCdnUrl(rawInput, credentials)
243
238
 
244
239
  if (result.ok) {
245
240
  process.stdout.write(`${result.output}\n`)
@@ -287,8 +282,9 @@ export class SignatureCommand extends UnauthenticatedCommand {
287
282
  return 1
288
283
  }
289
284
 
290
- const rawInput = await readStdin()
291
- const result = generateSignature(rawInput.trim(), credentials, this.algorithm)
285
+ const { content } = await readCliInput({ allowStdinWhenNoPath: true })
286
+ const rawInput = (content ?? '').trim()
287
+ const result = generateSignature(rawInput, credentials, this.algorithm)
292
288
 
293
289
  if (result.ok) {
294
290
  process.stdout.write(`${result.output}\n`)
@@ -340,8 +336,9 @@ export class SmartCdnSignatureCommand extends UnauthenticatedCommand {
340
336
  return 1
341
337
  }
342
338
 
343
- const rawInput = await readStdin()
344
- const result = generateSmartCdnUrl(rawInput.trim(), credentials)
339
+ const { content } = await readCliInput({ allowStdinWhenNoPath: true })
340
+ const rawInput = (content ?? '').trim()
341
+ const result = generateSmartCdnUrl(rawInput, credentials)
345
342
 
346
343
  if (result.ok) {
347
344
  process.stdout.write(`${result.output}\n`)
@@ -6,6 +6,7 @@ import {
6
6
  AssembliesCreateCommand,
7
7
  AssembliesDeleteCommand,
8
8
  AssembliesGetCommand,
9
+ AssembliesLintCommand,
9
10
  AssembliesListCommand,
10
11
  AssembliesReplayCommand,
11
12
  } from './assemblies.ts'
@@ -46,6 +47,7 @@ export function createCli(): Cli {
46
47
  cli.register(AssembliesGetCommand)
47
48
  cli.register(AssembliesDeleteCommand)
48
49
  cli.register(AssembliesReplayCommand)
50
+ cli.register(AssembliesLintCommand)
49
51
 
50
52
  // Templates commands
51
53
  cli.register(TemplatesCreateCommand)
@@ -0,0 +1,9 @@
1
+ export const lintingExamples: Array<[string, string]> = [
2
+ ['Lint a steps file', 'transloadit assemblies lint --steps steps.json'],
3
+ ['Lint from stdin', 'cat steps.json | transloadit assemblies lint --steps -'],
4
+ [
5
+ 'Lint with template merge',
6
+ 'transloadit assemblies lint --template TEMPLATE_ID --steps steps.json',
7
+ ],
8
+ ['Auto-fix in place', 'transloadit assemblies lint --steps steps.json --fix'],
9
+ ]