@@ -4,310 +4,8 @@ import { realpathSync } from 'node:fs'
44import path from 'node:path'
55import process from 'node:process'
66import { fileURLToPath } from 'node:url'
7- import { type ZodIssue , z } from 'zod'
8- import {
9- assemblyAuthInstructionsSchema ,
10- assemblyInstructionsSchema ,
11- } from './alphalib/types/template.ts'
12- import type { OptionalAuthParams } from './apiTypes.ts'
13- import { Transloadit } from './Transloadit.ts'
14-
15- type UrlParamPrimitive = string | number | boolean
16- type UrlParamArray = UrlParamPrimitive [ ]
17- type NormalizedUrlParams = Record < string , UrlParamPrimitive | UrlParamArray >
18-
19- interface RunSigOptions {
20- providedInput ?: string
21- algorithm ?: string
22- }
23-
24- interface RunSmartSigOptions {
25- providedInput ?: string
26- }
27-
28- const smartCdnParamsSchema = z
29- . object ( {
30- workspace : z . string ( ) . min ( 1 , 'workspace is required' ) ,
31- template : z . string ( ) . min ( 1 , 'template is required' ) ,
32- input : z . union ( [ z . string ( ) , z . number ( ) , z . boolean ( ) ] ) ,
33- url_params : z . record ( z . unknown ( ) ) . optional ( ) ,
34- expire_at_ms : z . union ( [ z . number ( ) , z . string ( ) ] ) . optional ( ) ,
35- } )
36- . passthrough ( )
37-
38- const cliSignatureParamsSchema = assemblyInstructionsSchema
39- . extend ( { auth : assemblyAuthInstructionsSchema . partial ( ) . optional ( ) } )
40- . partial ( )
41- . passthrough ( )
42-
43- export async function readStdin ( ) : Promise < string > {
44- if ( process . stdin . isTTY ) return ''
45-
46- process . stdin . setEncoding ( 'utf8' )
47- let data = ''
48-
49- for await ( const chunk of process . stdin ) {
50- data += chunk
51- }
52-
53- return data
54- }
55-
56- function fail ( message : string ) : void {
57- console . error ( message )
58- process . exitCode = 1
59- }
60-
61- function formatIssues ( issues : ZodIssue [ ] ) : string {
62- return issues
63- . map ( ( issue ) => {
64- const path = issue . path . join ( '.' ) || '(root)'
65- return `${ path } : ${ issue . message } `
66- } )
67- . join ( '; ' )
68- }
69-
70- function normalizeUrlParam ( value : unknown ) : UrlParamPrimitive | UrlParamArray | undefined {
71- if ( value == null ) return undefined
72- if ( Array . isArray ( value ) ) {
73- const normalized = value . filter (
74- ( item ) : item is UrlParamPrimitive =>
75- typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean' ,
76- )
77- return normalized . length > 0 ? normalized : undefined
78- }
79- if ( typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' ) {
80- return value
81- }
82- return undefined
83- }
84-
85- function normalizeUrlParams ( params ?: Record < string , unknown > ) : NormalizedUrlParams | undefined {
86- if ( params == null ) return undefined
87- let normalized : NormalizedUrlParams | undefined
88- for ( const [ key , value ] of Object . entries ( params ) ) {
89- const normalizedValue = normalizeUrlParam ( value )
90- if ( normalizedValue === undefined ) continue
91- if ( normalized == null ) normalized = { }
92- normalized [ key ] = normalizedValue
93- }
94- return normalized
95- }
96-
97- function ensureCredentials ( ) : { authKey: string ; authSecret: string } | null {
98- const authKey = process . env . TRANSLOADIT_KEY || process . env . TRANSLOADIT_AUTH_KEY
99- const authSecret = process . env . TRANSLOADIT_SECRET || process . env . TRANSLOADIT_AUTH_SECRET
100-
101- if ( ! authKey || ! authSecret ) {
102- fail (
103- 'Missing credentials. Please set TRANSLOADIT_KEY and TRANSLOADIT_SECRET environment variables.' ,
104- )
105- return null
106- }
107-
108- return { authKey, authSecret }
109- }
110-
111- export async function runSig ( options : RunSigOptions = { } ) : Promise < void > {
112- const credentials = ensureCredentials ( )
113- if ( credentials == null ) return
114- const { authKey, authSecret } = credentials
115- const { providedInput, algorithm } = options
116-
117- const rawInput = providedInput ?? ( await readStdin ( ) )
118- const input = rawInput . trim ( )
119- let params : Record < string , unknown >
120-
121- if ( input === '' ) {
122- params = { auth : { key : authKey } }
123- } else {
124- let parsed : unknown
125- try {
126- parsed = JSON . parse ( input )
127- } catch ( error ) {
128- fail ( `Failed to parse JSON from stdin: ${ ( error as Error ) . message } ` )
129- return
130- }
131-
132- if ( parsed == null || typeof parsed !== 'object' || Array . isArray ( parsed ) ) {
133- fail ( 'Invalid params provided via stdin. Expected a JSON object.' )
134- return
135- }
136-
137- const parsedResult = cliSignatureParamsSchema . safeParse ( parsed )
138- if ( ! parsedResult . success ) {
139- fail ( `Invalid params: ${ formatIssues ( parsedResult . error . issues ) } ` )
140- return
141- }
142-
143- const parsedParams = parsedResult . data as Record < string , unknown >
144- const existingAuth =
145- typeof parsedParams . auth === 'object' &&
146- parsedParams . auth != null &&
147- ! Array . isArray ( parsedParams . auth )
148- ? ( parsedParams . auth as Record < string , unknown > )
149- : { }
150-
151- params = {
152- ...parsedParams ,
153- auth : {
154- ...existingAuth ,
155- key : authKey ,
156- } ,
157- }
158- }
159-
160- const client = new Transloadit ( { authKey, authSecret } )
161- try {
162- const signature = client . calcSignature ( params as OptionalAuthParams , algorithm )
163- process . stdout . write ( `${ JSON . stringify ( signature ) } \n` )
164- } catch ( error ) {
165- fail ( `Failed to generate signature: ${ ( error as Error ) . message } ` )
166- }
167- }
168-
169- export async function runSmartSig ( options : RunSmartSigOptions = { } ) : Promise < void > {
170- const credentials = ensureCredentials ( )
171- if ( credentials == null ) return
172- const { authKey , authSecret } = credentials
173-
174- const rawInput = options . providedInput ?? ( await readStdin ( ) )
175- const input = rawInput . trim ( )
176- if ( input === '' ) {
177- fail (
178- 'Missing params provided via stdin. Expected a JSON object with workspace, template, input, and optional Smart CDN parameters.' ,
179- )
180- return
181- }
182-
183- let parsed : unknown
184- try {
185- parsed = JSON . parse ( input )
186- } catch ( error ) {
187- fail ( `Failed to parse JSON from stdin: ${ ( error as Error ) . message } ` )
188- return
189- }
190-
191- if ( parsed == null || typeof parsed !== 'object' || Array . isArray ( parsed ) ) {
192- fail ( 'Invalid params provided via stdin. Expected a JSON object.' )
193- return
194- }
195-
196- const parsedResult = smartCdnParamsSchema . safeParse ( parsed )
197- if ( ! parsedResult . success ) {
198- fail ( `Invalid params: ${ formatIssues ( parsedResult . error . issues ) } ` )
199- return
200- }
201-
202- const { workspace , template , input : inputFieldRaw , url_params , expire_at_ms } = parsedResult . data
203- const urlParams = normalizeUrlParams ( url_params as Record < string , unknown > | undefined )
204-
205- let expiresAt : number | undefined
206- if ( typeof expire_at_ms === 'string' ) {
207- const parsedNumber = Number . parseInt ( expire_at_ms , 10 )
208- if ( Number . isNaN ( parsedNumber ) ) {
209- fail ( 'Invalid params: expire_at_ms must be a number.' )
210- return
211- }
212- expiresAt = parsedNumber
213- } else {
214- expiresAt = expire_at_ms
215- }
216-
217- const inputField = typeof inputFieldRaw === 'string' ? inputFieldRaw : String ( inputFieldRaw )
218-
219- const client = new Transloadit ( { authKey, authSecret } )
220- try {
221- const signedUrl = client . getSignedSmartCDNUrl ( {
222- workspace,
223- template,
224- input : inputField ,
225- urlParams,
226- expiresAt,
227- } )
228- process . stdout . write ( `${ signedUrl } \n` )
229- } catch ( error ) {
230- fail ( `Failed to generate Smart CDN URL: ${ ( error as Error ) . message } ` )
231- }
232- }
233-
234- function parseSigArguments ( args : string [ ] ) : { algorithm ?: string } {
235- let algorithm : string | undefined
236- let index = 0
237- while ( index < args . length ) {
238- const arg = args [ index ]
239- if ( arg === '--algorithm' || arg === '-a' ) {
240- const next = args [ index + 1 ]
241- if ( next == null || next . startsWith ( '-' ) ) {
242- throw new Error ( 'Missing value for --algorithm option' )
243- }
244- algorithm = next
245- index += 2
246- continue
247- }
248- if ( arg . startsWith ( '--algorithm=' ) ) {
249- const [ , value ] = arg . split ( '=' , 2 )
250- if ( value === undefined || value === '' ) {
251- throw new Error ( 'Missing value for --algorithm option' )
252- }
253- algorithm = value
254- index += 1
255- continue
256- }
257- throw new Error ( `Unknown option: ${ arg } ` )
258- }
259-
260- return { algorithm }
261- }
262-
263- export async function main ( args = process . argv . slice ( 2 ) ) : Promise < void > {
264- const [ command , ...commandArgs ] = args
265-
266- switch ( command ) {
267- case 'smart_sig' : {
268- await runSmartSig ( )
269- break
270- }
271-
272- case 'sig ': {
273- try {
274- const { algorithm } = parseSigArguments ( commandArgs )
275- await runSig ( { algorithm } )
276- } catch ( error ) {
277- fail ( ( error as Error ) . message )
278- }
279- break
280- }
281-
282- case '-h' :
283- case '--help' :
284- case undefined : {
285- process . stdout . write (
286- [
287- 'Usage:' ,
288- ' npx transloadit smart_sig Read Smart CDN params JSON from stdin and output a signed URL.' ,
289- ' npx transloadit sig [--algorithm <name>] Read params JSON from stdin and output signed payload JSON.' ,
290- '' ,
291- 'Required JSON fields:' ,
292- ' smart_sig: workspace, template, input' ,
293- ' sig: none (object is optional)' ,
294- 'Optional JSON fields:' ,
295- ' smart_sig: expire_at_ms, url_params' ,
296- ' sig: auth.expires and any supported assembly params' ,
297- '' ,
298- 'Environment variables:' ,
299- ' TRANSLOADIT_KEY, TRANSLOADIT_SECRET' ,
300- ] . join ( '\n' ) ,
301- )
302- if ( command === undefined ) process . exitCode = 1
303- break
304- }
305-
306- default : {
307- fail ( `Unknown command: ${ command } ` )
308- }
309- }
310- }
7+ import 'dotenv/config'
8+ import { createCli } from './cli/commands/index.ts'
3119
31210const currentFile = realpathSync ( fileURLToPath ( import . meta. url ) )
31311
@@ -326,11 +24,20 @@ export function shouldRunCli(invoked?: string): boolean {
32624 return resolved === currentFile
32725}
32826
27+ export async function main ( args = process . argv . slice ( 2 ) ) : Promise < void > {
28+ const cli = createCli ( )
29+ const exitCode = await cli . run ( args )
30+ if ( exitCode !== 0 ) {
31+ process . exitCode = exitCode
32+ }
33+ }
34+
32935export function runCliWhenExecuted ( ) : void {
33036 if ( ! shouldRunCli ( process . argv [ 1 ] ) ) return
33137
33238 void main ( ) . catch ( ( error ) => {
333- fail ( ( error as Error ) . message )
39+ console . error ( ( error as Error ) . message )
40+ process . exitCode = 1
33441 } )
33542}
33643
0 commit comments