Skip to content

Commit bd6db9c

Browse files
committed
Switch smart_sig to Smart URLs
1 parent 9b4b30f commit bd6db9c

File tree

4 files changed

+215
-94
lines changed

4 files changed

+215
-94
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Released: 2025-10-16.
99
[Diff](https://github.com/transloadit/node-sdk/compare/v4.0.2...v4.0.3).
1010

1111
- [x] Fix `smart_sig` CLI to always override embedded credentials with the env-provided auth key while preserving other auth fields.
12+
- [x] Switch `smart_sig` CLI to return Smart CDN URLs via `getSignedSmartCDNUrl`, making it suitable as the cross-SDK reference implementation.
1213
- [x] Ensure the CLI entrypoint executes when invoked through npm/yarn bin shims by resolving symlinks before bootstrapping.
1314
- [x] Harden validation and tests around CLI invocation paths.
1415
- [x] Add `smart_sig` CLI command (usable via `npx transloadit smart_sig`) for signing params fed through stdin.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -387,11 +387,11 @@ This function returns an object with the key `signature` (containing the calcula
387387

388388
#### CLI smart_sig
389389

390-
Generate a signature from the command line without writing any JavaScript. The CLI reads a JSON object from stdin, injects credentials from `TRANSLOADIT_KEY`/`TRANSLOADIT_SECRET`, and prints the payload returned by `calcSignature()`.
390+
Generate a signed Smart CDN URL from the command line. The CLI reads a JSON object from stdin, injects credentials from `TRANSLOADIT_KEY`/`TRANSLOADIT_SECRET`, and prints the URL returned by `getSignedSmartCDNUrl()`.
391391

392392
```sh
393393
TRANSLOADIT_KEY=... TRANSLOADIT_SECRET=... \
394-
printf '{"assembly_id":"12345"}' | npx transloadit smart_sig
394+
printf '{"workspace":"demo","template":"resize","input":"image.jpg","url_params":{"width":320}}' | npx transloadit smart_sig
395395
```
396396

397397
You can also use `TRANSLOADIT_AUTH_KEY`/`TRANSLOADIT_AUTH_SECRET` as aliases for the environment variables.

src/cli.ts

Lines changed: 96 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,23 @@ import { realpathSync } from 'node:fs'
44
import path from 'node:path'
55
import process from 'node:process'
66
import { fileURLToPath } from 'node:url'
7-
import type { ZodIssue } from 'zod'
8-
import {
9-
assemblyAuthInstructionsSchema,
10-
assemblyInstructionsSchema,
11-
} from './alphalib/types/template.ts'
12-
import type { OptionalAuthParams } from './apiTypes.ts'
7+
import { type ZodIssue, z } from 'zod'
138
import { Transloadit } from './Transloadit.ts'
149

10+
type UrlParamPrimitive = string | number | boolean
11+
type UrlParamArray = UrlParamPrimitive[]
12+
type NormalizedUrlParams = Record<string, UrlParamPrimitive | UrlParamArray>
13+
14+
const smartCdnParamsSchema = z
15+
.object({
16+
workspace: z.string().min(1, 'workspace is required'),
17+
template: z.string().min(1, 'template is required'),
18+
input: z.union([z.string(), z.number(), z.boolean()]),
19+
url_params: z.record(z.unknown()).optional(),
20+
expire_at_ms: z.union([z.number(), z.string()]).optional(),
21+
})
22+
.passthrough()
23+
1524
export async function readStdin(): Promise<string> {
1625
if (process.stdin.isTTY) return ''
1726

@@ -30,11 +39,6 @@ function fail(message: string): void {
3039
process.exitCode = 1
3140
}
3241

33-
const cliParamsSchema = assemblyInstructionsSchema
34-
.extend({ auth: assemblyAuthInstructionsSchema.partial().optional() })
35-
.partial()
36-
.passthrough()
37-
3842
function formatIssues(issues: ZodIssue[]): string {
3943
return issues
4044
.map((issue) => {
@@ -44,6 +48,33 @@ function formatIssues(issues: ZodIssue[]): string {
4448
.join('; ')
4549
}
4650

51+
function normalizeUrlParam(value: unknown): UrlParamPrimitive | UrlParamArray | undefined {
52+
if (value == null) return undefined
53+
if (Array.isArray(value)) {
54+
const normalized = value.filter(
55+
(item): item is UrlParamPrimitive =>
56+
typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean',
57+
)
58+
return normalized.length > 0 ? normalized : undefined
59+
}
60+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
61+
return value
62+
}
63+
return undefined
64+
}
65+
66+
function normalizeUrlParams(params?: Record<string, unknown>): NormalizedUrlParams | undefined {
67+
if (params == null) return undefined
68+
let normalized: NormalizedUrlParams | undefined
69+
for (const [key, value] of Object.entries(params)) {
70+
const normalizedValue = normalizeUrlParam(value)
71+
if (normalizedValue === undefined) continue
72+
if (normalized == null) normalized = {}
73+
normalized[key] = normalizedValue
74+
}
75+
return normalized
76+
}
77+
4778
export async function runSmartSig(providedInput?: string): Promise<void> {
4879
const authKey = process.env.TRANSLOADIT_KEY || process.env.TRANSLOADIT_AUTH_KEY
4980
const authSecret = process.env.TRANSLOADIT_SECRET || process.env.TRANSLOADIT_AUTH_SECRET
@@ -57,46 +88,59 @@ export async function runSmartSig(providedInput?: string): Promise<void> {
5788

5889
const rawInput = providedInput ?? (await readStdin())
5990
const input = rawInput.trim()
60-
let params: Record<string, unknown> = {}
61-
62-
if (input !== '') {
63-
try {
64-
const parsed = JSON.parse(input)
65-
if (parsed == null || typeof parsed !== 'object' || Array.isArray(parsed)) {
66-
fail('Invalid params provided via stdin. Expected a JSON object.')
67-
return
68-
}
69-
70-
const parsedResult = cliParamsSchema.safeParse(parsed)
71-
if (!parsedResult.success) {
72-
fail(`Invalid params: ${formatIssues(parsedResult.error.issues)}`)
73-
return
74-
}
75-
76-
const parsedParams = parsedResult.data as Record<string, unknown>
77-
const existingAuth =
78-
typeof parsedParams.auth === 'object' &&
79-
parsedParams.auth != null &&
80-
!Array.isArray(parsedParams.auth)
81-
? (parsedParams.auth as Record<string, unknown>)
82-
: {}
83-
84-
params = {
85-
...parsedParams,
86-
auth: {
87-
...existingAuth,
88-
key: authKey,
89-
},
90-
}
91-
} catch (error: unknown) {
92-
fail(`Failed to parse JSON from stdin: ${(error as Error).message}`)
91+
if (input === '') {
92+
fail(
93+
'Missing params provided via stdin. Expected a JSON object with workspace, template, input, and optional Smart CDN parameters.',
94+
)
95+
return
96+
}
97+
98+
let parsed: unknown
99+
try {
100+
parsed = JSON.parse(input)
101+
} catch (error) {
102+
fail(`Failed to parse JSON from stdin: ${(error as Error).message}`)
103+
return
104+
}
105+
106+
if (parsed == null || typeof parsed !== 'object' || Array.isArray(parsed)) {
107+
fail('Invalid params provided via stdin. Expected a JSON object.')
108+
return
109+
}
110+
111+
const parsedResult = smartCdnParamsSchema.safeParse(parsed)
112+
if (!parsedResult.success) {
113+
fail(`Invalid params: ${formatIssues(parsedResult.error.issues)}`)
114+
return
115+
}
116+
117+
const { workspace, template, input: inputFieldRaw, url_params, expire_at_ms } = parsedResult.data
118+
119+
const urlParams = normalizeUrlParams(url_params as Record<string, unknown> | undefined)
120+
121+
let expiresAt: number | undefined
122+
if (typeof expire_at_ms === 'string') {
123+
const parsedNumber = Number.parseInt(expire_at_ms, 10)
124+
if (Number.isNaN(parsedNumber)) {
125+
fail('Invalid params: expire_at_ms must be a number.')
93126
return
94127
}
128+
expiresAt = parsedNumber
129+
} else {
130+
expiresAt = expire_at_ms
95131
}
96132

133+
const inputField = typeof inputFieldRaw === 'string' ? inputFieldRaw : String(inputFieldRaw)
134+
97135
const client = new Transloadit({ authKey, authSecret })
98-
const signature = client.calcSignature(params as OptionalAuthParams)
99-
process.stdout.write(`${JSON.stringify(signature)}\n`)
136+
const signedUrl = client.getSignedSmartCDNUrl({
137+
workspace,
138+
template,
139+
input: inputField,
140+
urlParams,
141+
expiresAt,
142+
})
143+
process.stdout.write(`${signedUrl}\n`)
100144
}
101145

102146
export async function main(args = process.argv.slice(2)): Promise<void> {
@@ -114,7 +158,12 @@ export async function main(args = process.argv.slice(2)): Promise<void> {
114158
process.stdout.write(
115159
[
116160
'Usage:',
117-
' npx transloadit smart_sig Read params JSON from stdin and output signed payload.',
161+
' npx transloadit smart_sig Read Smart CDN params JSON from stdin and output a signed URL.',
162+
'',
163+
'Required JSON fields:',
164+
' workspace, template, input',
165+
'Optional JSON fields:',
166+
' expire_at_ms, url_params',
118167
'',
119168
'Environment variables:',
120169
' TRANSLOADIT_KEY, TRANSLOADIT_SECRET',

0 commit comments

Comments
 (0)