transloadit 6.1.1 → 6.2.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.
- package/README.md +7 -0
- package/dist/alphalib/stepParsing.d.ts +1 -0
- package/dist/alphalib/stepParsing.d.ts.map +1 -1
- package/dist/alphalib/stepParsing.js +15 -4
- package/dist/alphalib/stepParsing.js.map +1 -1
- package/dist/alphalib/types/builtinTemplates.d.ts +40 -0
- package/dist/alphalib/types/builtinTemplates.d.ts.map +1 -1
- package/dist/alphalib/types/builtinTemplates.js +29 -0
- package/dist/alphalib/types/builtinTemplates.js.map +1 -1
- package/dist/alphalib/types/responseErrors/DocumentConvertRobot.d.ts +17 -0
- package/dist/alphalib/types/responseErrors/DocumentConvertRobot.d.ts.map +1 -0
- package/dist/alphalib/types/responseErrors/DocumentConvertRobot.js +17 -0
- package/dist/alphalib/types/responseErrors/DocumentConvertRobot.js.map +1 -0
- package/dist/alphalib/types/robots/document-convert.d.ts.map +1 -1
- package/dist/alphalib/types/robots/document-convert.js +19 -0
- package/dist/alphalib/types/robots/document-convert.js.map +1 -1
- package/dist/alphalib/types/robots/document-extract.d.ts +32 -20
- package/dist/alphalib/types/robots/document-extract.d.ts.map +1 -1
- package/dist/alphalib/types/robots/document-extract.js +51 -1
- package/dist/alphalib/types/robots/document-extract.js.map +1 -1
- package/dist/alphalib/types/robots/html-convert.d.ts.map +1 -1
- package/dist/alphalib/types/robots/html-convert.js +1 -0
- package/dist/alphalib/types/robots/html-convert.js.map +1 -1
- package/dist/alphalib/types/robots/speech-transcribe.d.ts +14 -0
- package/dist/alphalib/types/robots/speech-transcribe.d.ts.map +1 -1
- package/dist/alphalib/types/robots/speech-transcribe.js +42 -2
- package/dist/alphalib/types/robots/speech-transcribe.js.map +1 -1
- package/dist/alphalib/types/robots/video-split.d.ts +11 -0
- package/dist/alphalib/types/robots/video-split.d.ts.map +1 -1
- package/dist/alphalib/types/robots/video-split.js +2 -1
- package/dist/alphalib/types/robots/video-split.js.map +1 -1
- package/dist/alphalib/types/robots/video-subtitle.d.ts +24 -24
- package/dist/alphalib/types/robots/video-subtitle.d.ts.map +1 -1
- package/dist/alphalib/types/robots/video-subtitle.js +12 -11
- package/dist/alphalib/types/robots/video-subtitle.js.map +1 -1
- package/dist/alphalib/types/robots/video-thumbs.d.ts +36 -36
- package/dist/alphalib/types/robots/video-thumbs.d.ts.map +1 -1
- package/dist/alphalib/types/robots/video-thumbs.js +8 -3
- package/dist/alphalib/types/robots/video-thumbs.js.map +1 -1
- package/dist/alphalib/types/template.d.ts.map +1 -1
- package/dist/alphalib/types/template.js +5 -2
- package/dist/alphalib/types/template.js.map +1 -1
- package/dist/cli/commands/assemblies.d.ts.map +1 -1
- package/dist/cli/commands/assemblies.js +8 -7
- package/dist/cli/commands/assemblies.js.map +1 -1
- package/dist/cli/commands/auth.d.ts.map +1 -1
- package/dist/cli/commands/auth.js +4 -3
- package/dist/cli/commands/auth.js.map +1 -1
- package/dist/cli/commands/templates.d.ts.map +1 -1
- package/dist/cli/commands/templates.js +11 -5
- package/dist/cli/commands/templates.js.map +1 -1
- package/dist/cli/stepsInput.d.ts +6 -2
- package/dist/cli/stepsInput.d.ts.map +1 -1
- package/dist/cli/stepsInput.js +24 -5
- package/dist/cli/stepsInput.js.map +1 -1
- package/dist/cli/types.d.ts +0 -1
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/cli/types.js +0 -7
- package/dist/cli/types.js.map +1 -1
- package/package.json +1 -1
- package/src/alphalib/stepParsing.ts +16 -4
- package/src/alphalib/types/builtinTemplates.ts +37 -0
- package/src/alphalib/types/responseErrors/DocumentConvertRobot.ts +21 -0
- package/src/alphalib/types/robots/document-convert.ts +25 -0
- package/src/alphalib/types/robots/document-extract.ts +76 -0
- package/src/alphalib/types/robots/html-convert.ts +1 -0
- package/src/alphalib/types/robots/speech-transcribe.ts +47 -2
- package/src/alphalib/types/robots/video-split.ts +2 -1
- package/src/alphalib/types/robots/video-subtitle.ts +12 -11
- package/src/alphalib/types/robots/video-thumbs.ts +8 -3
- package/src/alphalib/types/template.ts +3 -0
- package/src/cli/commands/assemblies.ts +13 -7
- package/src/cli/commands/auth.ts +7 -2
- package/src/cli/commands/templates.ts +13 -6
- package/src/cli/stepsInput.ts +34 -5
- package/src/cli/types.ts +0 -9
|
@@ -59,7 +59,7 @@ export const meta: RobotMetaInput = {
|
|
|
59
59
|
},
|
|
60
60
|
},
|
|
61
61
|
example_code_description:
|
|
62
|
-
'Transcribe an uploaded video into timestamped words, then burn in uppercase social captions with the spoken word highlighted. Captions normally contain up to three words, breaking at sentence endings and pauses longer than 0.4 seconds.
|
|
62
|
+
'Transcribe an uploaded video into timestamped words, then burn in uppercase social captions with the spoken word highlighted. Captions normally contain up to three words, breaking at sentence endings and pauses longer than 0.4 seconds. Social phrasing can use one extra word to keep a short phrase together or avoid a sentence-final orphan:',
|
|
63
63
|
purpose_sentence: 'adds subtitles and closed captions to videos',
|
|
64
64
|
purpose_verb: 'subtitle',
|
|
65
65
|
purpose_word: 'subtitle',
|
|
@@ -89,7 +89,7 @@ Determines if subtitles are added as a separate stream to the video (value \`"ex
|
|
|
89
89
|
.enum(['social'])
|
|
90
90
|
.optional()
|
|
91
91
|
.describe(`
|
|
92
|
-
Applies a ready-made appearance for burned subtitles: heavy Poppins text
|
|
92
|
+
Applies a ready-made appearance for burned subtitles: heavy Poppins text, a thick outline, and a soft black shadow. JSON captions render in uppercase in the lower third. By default, JSON font sizing uses the shorter side of the video and fits each caption within the safe width, preferring one line and using at most two. Words are never split. Each caption grows from 85% to full size over 120 milliseconds, then its words stay in place while the highlight changes color. The transcript file is preserved. Existing styling parameters override the preset; an explicit \`font_size\` disables automatic fitting. ASS files retain their own styles. With timestamped JSON from \`/speech/transcribe\`, the active word is highlighted until the next word or caption starts; other words use \`font_color\`. Trailing commas and periods are omitted from social JSON captions; question marks and exclamation marks are preserved. Word highlighting and grouping require JSON input.
|
|
93
93
|
`),
|
|
94
94
|
highlight_color: subtitleColorSchema.default('FFD60A').describe(`
|
|
95
95
|
The accent color of the active word in JSON subtitles. Only applies to burned JSON subtitles. With \`"style": "social"\`, the previous word stays highlighted through short gaps until the next word or caption starts. Without the social style, gaps use \`font_color\` for the entire caption.
|
|
@@ -102,7 +102,7 @@ ${subtitleColorDescription}
|
|
|
102
102
|
.min(1)
|
|
103
103
|
.default(3)
|
|
104
104
|
.describe(`
|
|
105
|
-
The target maximum number of timestamped JSON words shown in a caption. Captions break earlier at sentence-ending punctuation or pauses longer than 0.4 seconds. With \`"style": "social"\`, a trailing English function word such as “by,” “to,” or “the” moves to the next caption when possible,
|
|
105
|
+
The target maximum number of timestamped JSON words shown in a caption. Captions break earlier at sentence-ending punctuation or pauses longer than 0.4 seconds. With \`"style": "social"\`, a trailing English function word such as “by,” “to,” or “the” moves to the next caption when possible. Short phrases such as “line of code” can stay together, and a sentence-final single word can join the previous caption. These adjustments allow at most one extra word per caption, without crossing sentence endings or long pauses. Single-word captions are preserved when the limit is 1. A caption stays visible until the next caption starts when the pause is at most 0.4 seconds, and disappears at its last word’s end during longer pauses. Does not change SRT, VTT, or ASS cues.
|
|
106
106
|
`),
|
|
107
107
|
border_style: z
|
|
108
108
|
.enum(['box', 'outline', 'shadow'])
|
|
@@ -115,12 +115,13 @@ The color for the subtitle border. The default is opaque black. Only applies to
|
|
|
115
115
|
|
|
116
116
|
${subtitleColorDescription}
|
|
117
117
|
`),
|
|
118
|
+
// Preserve omitted appearance fields so parsing cannot override the selected style's defaults.
|
|
118
119
|
// TODO: Make font an enum
|
|
119
120
|
font: z
|
|
120
121
|
.string()
|
|
121
|
-
.
|
|
122
|
+
.optional()
|
|
122
123
|
.describe(`
|
|
123
|
-
The font family to use. Also includes boldness and style of the font. With \`"style": "social"\`, the default is
|
|
124
|
+
The font family to use. Also includes boldness and style of the font. The default is Arial. With \`"style": "social"\`, the default is ${videoSubtitleSocialStyle.font}, using its ExtraBold weight for JSON captions. An explicit font overrides the style.
|
|
124
125
|
|
|
125
126
|
[Here](/docs/supported-formats/fonts/) is a list of all supported fonts.
|
|
126
127
|
`),
|
|
@@ -133,15 +134,15 @@ ${subtitleColorDescription}
|
|
|
133
134
|
.number()
|
|
134
135
|
.int()
|
|
135
136
|
.min(1)
|
|
136
|
-
.
|
|
137
|
+
.optional()
|
|
137
138
|
.describe(`
|
|
138
|
-
Specifies the size of the text in the 288-pixel subtitle coordinate space, scaled with the video height. With \`"style": "social"\`, the default is
|
|
139
|
+
Specifies the size of the text in the 288-pixel subtitle coordinate space, scaled with the video height. The default is 16. With \`"style": "social"\`, the default is ${videoSubtitleSocialStyle.font_size}. For social JSON captions, an omitted value scales this default with the shorter side of the video, increases it by 18% for portrait video, and shrinks captions as needed to fit at most two lines. Poppins capital letters are about 7.5% of the shorter side before fitting, or about 9% for portrait video. An explicit value keeps height-relative sizing and disables automatic fitting.
|
|
139
140
|
`),
|
|
140
141
|
bold: z
|
|
141
142
|
.boolean()
|
|
142
|
-
.
|
|
143
|
+
.optional()
|
|
143
144
|
.describe(`
|
|
144
|
-
Specifies whether the subtitle text should be bold. Only applies to burned subtitles. With \`"style": "social"\`, the default is
|
|
145
|
+
Specifies whether the subtitle text should be bold. Only applies to burned subtitles. The default is \`false\`. With \`"style": "social"\`, the default is \`${videoSubtitleSocialStyle.bold}\`; an explicit \`false\` overrides the style.
|
|
145
146
|
`),
|
|
146
147
|
italic: z
|
|
147
148
|
.boolean()
|
|
@@ -157,10 +158,10 @@ Specifies whether the subtitle text should be italic. Only applies to burned sub
|
|
|
157
158
|
.optional()
|
|
158
159
|
.nullable()
|
|
159
160
|
.describe(`
|
|
160
|
-
Specifies the width of the text outline in the 288-pixel subtitle coordinate space, scaled with the video height. Numeric strings such as \`"4"\` are also accepted. Only applies to burned subtitles with \`"outline"\` or \`"box"\` border styles. If omitted or \`null\`, the default width is used (2 with \`"style": "social"\`,
|
|
161
|
+
Specifies the width of the text outline in the 288-pixel subtitle coordinate space, scaled with the video height. Numeric strings such as \`"4"\` are also accepted. Only applies to burned subtitles with \`"outline"\` or \`"box"\` border styles. If omitted or \`null\`, the default width is used (2 with \`"style": "social"\`, proportional to the final fitted font size for social JSON captions).
|
|
161
162
|
`),
|
|
162
163
|
position: positionSchema.default('bottom').describe(`
|
|
163
|
-
Specifies the position of the subtitles. With \`"style": "social"\`, the default bottom position places JSON captions in the lower third. An explicit value overrides the style.
|
|
164
|
+
Specifies the position of the subtitles. With \`"style": "social"\`, the default bottom position places JSON captions in the lower third, with extra bottom clearance on portrait video. An explicit value overrides the style.
|
|
164
165
|
`),
|
|
165
166
|
language: z
|
|
166
167
|
.string()
|
|
@@ -94,7 +94,8 @@ The thumbnails are taken at regular intervals, determined by dividing the video
|
|
|
94
94
|
To extract thumbnails for specific timestamps, use the \`offsets\` parameter.
|
|
95
95
|
`),
|
|
96
96
|
offsets: z
|
|
97
|
-
.union([z.array(z.number()), z.array(percentageSchema)])
|
|
97
|
+
.union([z.array(z.number().finite()), z.array(percentageSchema)])
|
|
98
|
+
.nullable()
|
|
98
99
|
.default([])
|
|
99
100
|
.describe(`
|
|
100
101
|
An array of offsets representing seconds of the file duration, such as \`[ 2, 45, 120 ]\`. Millisecond durations of a file can also be used by using decimal place values. For example, an offset from 1250 milliseconds would be represented with \`1.25\`. Offsets can also be percentage values such as \`[ "2%", "50%", "75%" ]\`.
|
|
@@ -102,6 +103,8 @@ An array of offsets representing seconds of the file duration, such as \`[ 2, 45
|
|
|
102
103
|
This option cannot be used with the \`count\` parameter, and takes precedence if both are specified. Out-of-range offsets are silently ignored.
|
|
103
104
|
|
|
104
105
|
When \`smart\` is \`true\`, smart selection ignores \`offsets\` and uses \`count\` to select from its own candidate timestamps. If no smart candidates can be extracted, the Robot falls back to standard extraction, where \`offsets\` takes precedence. Use \`smart: false\` to consistently extract the specified timestamps.
|
|
106
|
+
|
|
107
|
+
Use \`null\` to leave timestamp selection to \`count\` and \`smart\`.
|
|
105
108
|
`),
|
|
106
109
|
format: z
|
|
107
110
|
.enum(['jpeg', 'jpg', 'png'])
|
|
@@ -114,18 +117,20 @@ The format of the extracted thumbnail. Supported values are \`"jpg"\`, \`"jpeg"\
|
|
|
114
117
|
.int()
|
|
115
118
|
.min(1)
|
|
116
119
|
.max(1920)
|
|
120
|
+
.nullable()
|
|
117
121
|
.optional()
|
|
118
122
|
.describe(`
|
|
119
|
-
The width of the thumbnail, in pixels.
|
|
123
|
+
The width of the thumbnail, in pixels. Omit or use \`null\` for the original width of the video.
|
|
120
124
|
`),
|
|
121
125
|
height: z
|
|
122
126
|
.number()
|
|
123
127
|
.int()
|
|
124
128
|
.min(1)
|
|
125
129
|
.max(1080)
|
|
130
|
+
.nullable()
|
|
126
131
|
.optional()
|
|
127
132
|
.describe(`
|
|
128
|
-
The height of the thumbnail, in pixels.
|
|
133
|
+
The height of the thumbnail, in pixels. Omit or use \`null\` for the original height of the video.
|
|
129
134
|
`),
|
|
130
135
|
resize_strategy: resize_strategy.describe(`
|
|
131
136
|
One of the [available resize strategies](/docs/topics/resize-strategies/).
|
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
RobotsWithHiddenBotsAndFields,
|
|
10
10
|
} from './robots/_index.ts'
|
|
11
11
|
import type { RobotUse } from './robots/_instructions-primitives.ts'
|
|
12
|
+
import { refineDocumentExtractOcr } from './robots/document-extract.ts'
|
|
12
13
|
import { refineTransloaditImportSelector } from './robots/transloadit-import.ts'
|
|
13
14
|
|
|
14
15
|
export type Step = RobotsSchema
|
|
@@ -26,6 +27,7 @@ export const stepSchema: z.ZodType<Step> = z
|
|
|
26
27
|
})
|
|
27
28
|
.and(robotsSchema)
|
|
28
29
|
.superRefine(refineTransloaditImportSelector)
|
|
30
|
+
.superRefine(refineDocumentExtractOcr)
|
|
29
31
|
export const stepsSchema: z.ZodType<Steps> = z
|
|
30
32
|
.record(stepSchema)
|
|
31
33
|
.describe('Contains Assembly Instructions.')
|
|
@@ -45,6 +47,7 @@ export const stepSchemaWithHiddenFields: z.ZodType<StepWithHiddenFields> = z
|
|
|
45
47
|
})
|
|
46
48
|
.and(robotsWithHiddenBotsAndFieldsSchema)
|
|
47
49
|
.superRefine(refineTransloaditImportSelector)
|
|
50
|
+
.superRefine(refineDocumentExtractOcr)
|
|
48
51
|
export const stepsSchemaWithHiddenFields: z.ZodType<StepsWithHiddenFields> = z
|
|
49
52
|
.record(stepSchemaWithHiddenFields)
|
|
50
53
|
.describe('Contains Assembly Instructions.')
|
|
@@ -227,7 +227,7 @@ export async function replay(
|
|
|
227
227
|
): Promise<void> {
|
|
228
228
|
if (steps) {
|
|
229
229
|
try {
|
|
230
|
-
await apiCall(await readStepsInputFile(steps))
|
|
230
|
+
await apiCall(await readStepsInputFile(steps, output))
|
|
231
231
|
} catch (err) {
|
|
232
232
|
const error = ensureError(err)
|
|
233
233
|
output.error(error.message)
|
|
@@ -735,7 +735,9 @@ async function resolveResultDownloadTargets({
|
|
|
735
735
|
throw new Error('stdout can only receive a single result file')
|
|
736
736
|
}
|
|
737
737
|
|
|
738
|
-
throw new Error(
|
|
738
|
+
throw new Error(
|
|
739
|
+
'file outputs can only receive a single result file; use -o <dir>/ to write multiple results to a directory',
|
|
740
|
+
)
|
|
739
741
|
}
|
|
740
742
|
|
|
741
743
|
return getSingleResultDownloadTarget(allFiles, outputPath)
|
|
@@ -1351,18 +1353,20 @@ export async function create(
|
|
|
1351
1353
|
// to avoid zod adding default values that the API may reject
|
|
1352
1354
|
let effectiveStepsData = stepsData
|
|
1353
1355
|
if (steps) {
|
|
1354
|
-
effectiveStepsData = await readStepsInputFile(steps)
|
|
1356
|
+
effectiveStepsData = await readStepsInputFile(steps, outputctl)
|
|
1355
1357
|
}
|
|
1356
1358
|
|
|
1357
1359
|
// Determine output stat async before entering the Promise constructor
|
|
1358
1360
|
let outstat: StatLike | undefined
|
|
1359
1361
|
if (resolvedOutput != null) {
|
|
1362
|
+
const isDirectoryOutput =
|
|
1363
|
+
outputMode === 'directory' || resolvedOutput.endsWith('/') || resolvedOutput.endsWith('\\')
|
|
1360
1364
|
const [err, stat] = await tryCatch(myStat(process.stdout, resolvedOutput))
|
|
1361
1365
|
if (err && (!isErrnoException(err) || err.code !== 'ENOENT')) throw err
|
|
1362
1366
|
outstat =
|
|
1363
1367
|
stat ??
|
|
1364
1368
|
({
|
|
1365
|
-
isDirectory: () =>
|
|
1369
|
+
isDirectory: () => isDirectoryOutput,
|
|
1366
1370
|
} satisfies StatLike)
|
|
1367
1371
|
|
|
1368
1372
|
if (outputMode === 'directory' && stat != null && !stat.isDirectory()) {
|
|
@@ -1826,7 +1830,8 @@ export class AssembliesCreateCommand extends AuthenticatedCommand {
|
|
|
1826
1830
|
inputs = inputPathsOption()
|
|
1827
1831
|
|
|
1828
1832
|
outputPath = Option.String('--output,-o', {
|
|
1829
|
-
description:
|
|
1833
|
+
description:
|
|
1834
|
+
'Specify an output file or directory; a trailing / or \\ creates a directory recursively',
|
|
1830
1835
|
})
|
|
1831
1836
|
|
|
1832
1837
|
fields = Option.Array('--field,-f', {
|
|
@@ -1945,7 +1950,8 @@ export class RunCommand extends AuthenticatedCommand {
|
|
|
1945
1950
|
inputs = inputPathsOption()
|
|
1946
1951
|
|
|
1947
1952
|
outputPath = Option.String('--output,-o', {
|
|
1948
|
-
description:
|
|
1953
|
+
description:
|
|
1954
|
+
'Specify an output file or directory; a trailing / or \\ creates a directory recursively',
|
|
1949
1955
|
})
|
|
1950
1956
|
|
|
1951
1957
|
fields = Option.Array('--field,-f', {
|
|
@@ -2012,7 +2018,7 @@ export class RunCommand extends AuthenticatedCommand {
|
|
|
2012
2018
|
}
|
|
2013
2019
|
|
|
2014
2020
|
const { hasFailures, resultUrls } = await create(this.output, this.client, {
|
|
2015
|
-
stepsData: parseStepsInputJson(JSON.stringify(compiled.instructions.steps)),
|
|
2021
|
+
stepsData: parseStepsInputJson(JSON.stringify(compiled.instructions.steps), this.output),
|
|
2016
2022
|
fields: fieldsMap ?? {},
|
|
2017
2023
|
watch: this.watch,
|
|
2018
2024
|
recursive: this.recursive,
|
package/src/cli/commands/auth.ts
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
requireCliCredentials,
|
|
23
23
|
resolveCliConfig,
|
|
24
24
|
} from '../helpers.ts'
|
|
25
|
+
import { createCliStepsSchema } from '../stepsInput.ts'
|
|
25
26
|
import { UnauthenticatedCommand } from './BaseCommand.ts'
|
|
26
27
|
|
|
27
28
|
type UrlParamPrimitive = string | number | boolean
|
|
@@ -113,6 +114,7 @@ function generateSignature(
|
|
|
113
114
|
input: string,
|
|
114
115
|
credentials: CliKeySecretCredentials,
|
|
115
116
|
algorithm?: string,
|
|
117
|
+
output: Pick<IOutputCtl, 'warn'> = console,
|
|
116
118
|
): OutputResult {
|
|
117
119
|
const { authKey } = credentials
|
|
118
120
|
let params: CliSignatureParams
|
|
@@ -120,7 +122,10 @@ function generateSignature(
|
|
|
120
122
|
if (input === '') {
|
|
121
123
|
params = { auth: { key: authKey } }
|
|
122
124
|
} else {
|
|
123
|
-
const parsedResult = parseJsonObject(
|
|
125
|
+
const parsedResult = parseJsonObject(
|
|
126
|
+
input,
|
|
127
|
+
cliSignatureParamsSchema.extend({ steps: createCliStepsSchema(output).optional() }),
|
|
128
|
+
)
|
|
124
129
|
if (!parsedResult.ok) {
|
|
125
130
|
return { ok: false, error: parsedResult.error }
|
|
126
131
|
}
|
|
@@ -347,7 +352,7 @@ export class SignatureCommand extends UnauthenticatedCommand {
|
|
|
347
352
|
|
|
348
353
|
const { content } = await readCliInput({ allowStdinWhenNoPath: true })
|
|
349
354
|
const rawInput = (content ?? '').trim()
|
|
350
|
-
const result = generateSignature(rawInput, credentials, this.algorithm)
|
|
355
|
+
const result = generateSignature(rawInput, credentials, this.algorithm, this.output)
|
|
351
356
|
|
|
352
357
|
if (result.ok) {
|
|
353
358
|
process.stdout.write(`${result.output}\n`)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { TemplateContent } from '../../apiTypes.ts'
|
|
2
2
|
import type { Transloadit } from '../../Transloadit.ts'
|
|
3
3
|
import type { IOutputCtl } from '../OutputCtl.ts'
|
|
4
|
-
import type { TemplateFile } from '../types.ts'
|
|
4
|
+
import type { TemplateFile, TemplateFileData } from '../types.ts'
|
|
5
5
|
|
|
6
6
|
import fsp from 'node:fs/promises'
|
|
7
7
|
import path from 'node:path'
|
|
@@ -13,9 +13,9 @@ import { z } from 'zod'
|
|
|
13
13
|
|
|
14
14
|
import { tryCatch } from '../../alphalib/tryCatch.ts'
|
|
15
15
|
import { createReadStream, formatAPIError, streamToBuffer } from '../helpers.ts'
|
|
16
|
-
import { parseStepsInputJson } from '../stepsInput.ts'
|
|
16
|
+
import { createCliStepsSchema, parseStepsInputJson } from '../stepsInput.ts'
|
|
17
17
|
import ModifiedLookup from '../template-last-modified.ts'
|
|
18
|
-
import { ensureError, isTransloaditAPIError
|
|
18
|
+
import { ensureError, isTransloaditAPIError } from '../types.ts'
|
|
19
19
|
import { AuthenticatedCommand } from './BaseCommand.ts'
|
|
20
20
|
|
|
21
21
|
const rreaddirAsync = promisify(rreaddir)
|
|
@@ -62,7 +62,7 @@ export async function create(
|
|
|
62
62
|
try {
|
|
63
63
|
const buf = await streamToBuffer(createReadStream(file))
|
|
64
64
|
|
|
65
|
-
const steps = parseStepsInputJson(buf.toString())
|
|
65
|
+
const steps = parseStepsInputJson(buf.toString(), output)
|
|
66
66
|
|
|
67
67
|
const result = await client.createTemplate({
|
|
68
68
|
name,
|
|
@@ -107,7 +107,7 @@ export async function modify(
|
|
|
107
107
|
let newName = name
|
|
108
108
|
|
|
109
109
|
if (buf.length > 0) {
|
|
110
|
-
steps = parseStepsInputJson(buf.toString()) as TemplateContent['steps']
|
|
110
|
+
steps = parseStepsInputJson(buf.toString(), output) as TemplateContent['steps']
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
if (!name || buf.length === 0) {
|
|
@@ -225,6 +225,13 @@ export async function sync(
|
|
|
225
225
|
client: Transloadit,
|
|
226
226
|
{ files, recursive }: TemplateSyncOptions,
|
|
227
227
|
): Promise<void> {
|
|
228
|
+
const templateFileDataSchema: z.ZodType<TemplateFileData> = z
|
|
229
|
+
.object({
|
|
230
|
+
transloadit_template_id: z.string().optional(),
|
|
231
|
+
steps: createCliStepsSchema(output).optional(),
|
|
232
|
+
})
|
|
233
|
+
.passthrough()
|
|
234
|
+
|
|
228
235
|
// Promise [String] -- all files in the directory tree
|
|
229
236
|
const relevantFilesNested = await Promise.all(
|
|
230
237
|
files.map(async (file) => {
|
|
@@ -263,7 +270,7 @@ export async function sync(
|
|
|
263
270
|
try {
|
|
264
271
|
const data = await fsp.readFile(file, 'utf8')
|
|
265
272
|
const parsed: unknown = JSON.parse(data)
|
|
266
|
-
const validated =
|
|
273
|
+
const validated = templateFileDataSchema.safeParse(parsed)
|
|
267
274
|
if (!validated.success) return null
|
|
268
275
|
return 'transloadit_template_id' in validated.data ? { file, data: validated.data } : null
|
|
269
276
|
} catch (e) {
|
package/src/cli/stepsInput.ts
CHANGED
|
@@ -1,14 +1,40 @@
|
|
|
1
1
|
import type { StepsInput } from '../alphalib/types/template.ts'
|
|
2
|
+
import type { IOutputCtl } from './OutputCtl.ts'
|
|
2
3
|
|
|
3
4
|
import fsp from 'node:fs/promises'
|
|
4
5
|
|
|
5
6
|
import { z } from 'zod'
|
|
6
7
|
|
|
7
|
-
import {
|
|
8
|
+
import { robotsSchema } from '../alphalib/types/robots/_index.ts'
|
|
9
|
+
import { refineDocumentExtractOcr } from '../alphalib/types/robots/document-extract.ts'
|
|
10
|
+
import { refineTransloaditImportSelector } from '../alphalib/types/robots/transloadit-import.ts'
|
|
8
11
|
|
|
9
12
|
const stepsWrapperSchema = z.object({ steps: z.record(z.unknown()) })
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
/** CLI inputs can be partial overrides; API2 validates OCR after merging their runtime context. */
|
|
15
|
+
export function createCliStepsSchema(
|
|
16
|
+
output: Pick<IOutputCtl, 'warn'> = console,
|
|
17
|
+
): z.ZodType<StepsInput> {
|
|
18
|
+
const cliStepSchema = robotsSchema
|
|
19
|
+
.superRefine(refineTransloaditImportSelector)
|
|
20
|
+
.superRefine((step, context) => {
|
|
21
|
+
// API2 merges Template defaults before Robot validation. New OCR checks must not reject
|
|
22
|
+
// partial --steps overrides in a minor release; explicit Zod schemas remain strict.
|
|
23
|
+
refineDocumentExtractOcr(step, {
|
|
24
|
+
...context,
|
|
25
|
+
addIssue(issue) {
|
|
26
|
+
const field = [...context.path, ...(issue.path ?? [])].join('.')
|
|
27
|
+
output.warn(`${field}: ${issue.message ?? 'Invalid OCR dependency.'}`)
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
return z.record(cliStepSchema)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function parseStepsInputJson(
|
|
35
|
+
content: string,
|
|
36
|
+
output: Pick<IOutputCtl, 'warn'> = console,
|
|
37
|
+
): StepsInput {
|
|
12
38
|
const parsed: unknown = JSON.parse(content)
|
|
13
39
|
const wrapped = stepsWrapperSchema.safeParse(parsed)
|
|
14
40
|
let stepsInput = parsed
|
|
@@ -21,7 +47,7 @@ export function parseStepsInputJson(content: string): StepsInput {
|
|
|
21
47
|
}
|
|
22
48
|
stepsInput = validatedWrapper.data.steps
|
|
23
49
|
}
|
|
24
|
-
const validated =
|
|
50
|
+
const validated = createCliStepsSchema(output).safeParse(stepsInput)
|
|
25
51
|
if (!validated.success) {
|
|
26
52
|
throw new Error(`Invalid steps format: ${validated.error.message}`)
|
|
27
53
|
}
|
|
@@ -42,7 +68,10 @@ export function parseStepsInputJson(content: string): StepsInput {
|
|
|
42
68
|
) as StepsInput
|
|
43
69
|
}
|
|
44
70
|
|
|
45
|
-
export async function readStepsInputFile(
|
|
71
|
+
export async function readStepsInputFile(
|
|
72
|
+
filePath: string,
|
|
73
|
+
output: Pick<IOutputCtl, 'warn'> = console,
|
|
74
|
+
): Promise<StepsInput> {
|
|
46
75
|
const content = await fsp.readFile(filePath, 'utf8')
|
|
47
|
-
return parseStepsInputJson(content)
|
|
76
|
+
return parseStepsInputJson(content, output)
|
|
48
77
|
}
|
package/src/cli/types.ts
CHANGED
|
@@ -2,8 +2,6 @@ import type { Steps } from '../alphalib/types/template.ts'
|
|
|
2
2
|
|
|
3
3
|
import { z } from 'zod'
|
|
4
4
|
|
|
5
|
-
import { optionalStepsSchema } from '../alphalib/types/template.ts'
|
|
6
|
-
|
|
7
5
|
// Zod schemas for runtime validation
|
|
8
6
|
const APIErrorSchema = z.object({
|
|
9
7
|
error: z.string(),
|
|
@@ -36,13 +34,6 @@ export interface TemplateFileData {
|
|
|
36
34
|
[key: string]: unknown // passthrough
|
|
37
35
|
}
|
|
38
36
|
|
|
39
|
-
export const TemplateFileDataSchema: z.ZodType<TemplateFileData> = z
|
|
40
|
-
.object({
|
|
41
|
-
transloadit_template_id: z.string().optional(),
|
|
42
|
-
steps: optionalStepsSchema,
|
|
43
|
-
})
|
|
44
|
-
.passthrough() as z.ZodType<TemplateFileData>
|
|
45
|
-
|
|
46
37
|
export interface TemplateFile {
|
|
47
38
|
file: string
|
|
48
39
|
data: TemplateFileData
|