@@ -12,7 +12,9 @@ import {
1212import {
1313 execCmd ,
1414 extractDeploymentUrl ,
15+ isSensitiveEnvVar ,
1516 parseEnvExample ,
17+ tryRecoverDeployUrl ,
1618} from "../cli/commands/deploy.js"
1719
1820const fixturesDir = join ( import . meta. dirname , "__fixtures_deploy__" )
@@ -124,6 +126,45 @@ describe("extractDeploymentUrl", () => {
124126 } )
125127} )
126128
129+ describe ( "isSensitiveEnvVar" , ( ) => {
130+ it . each ( [
131+ "OPENSEA_API_KEY" ,
132+ "ANTHROPIC_API_KEY" ,
133+ "MY_SECRET" ,
134+ "AUTH_TOKEN" ,
135+ "DB_PASSWORD" ,
136+ "WALLET_PRIVATE" ,
137+ ] ) ( "should return true for sensitive var %s" , name => {
138+ expect ( isSensitiveEnvVar ( name ) ) . toBe ( true )
139+ } )
140+
141+ it . each ( [
142+ "CREATOR_ADDRESS" ,
143+ "TOOL_ENDPOINT" ,
144+ "DATABASE_URL" ,
145+ "NODE_ENV" ,
146+ "PORT" ,
147+ "KEYBOARD_LAYOUT" ,
148+ ] ) ( "should return false for non-sensitive var %s" , name => {
149+ expect ( isSensitiveEnvVar ( name ) ) . toBe ( false )
150+ } )
151+
152+ it ( "should be case-insensitive" , ( ) => {
153+ expect ( isSensitiveEnvVar ( "my_api_key" ) ) . toBe ( true )
154+ expect ( isSensitiveEnvVar ( "My_Secret" ) ) . toBe ( true )
155+ expect ( isSensitiveEnvVar ( "auth_token" ) ) . toBe ( true )
156+ } )
157+
158+ it ( "should only match at the end of the name" , ( ) => {
159+ expect ( isSensitiveEnvVar ( "SECRET_NAME" ) ) . toBe ( false )
160+ expect ( isSensitiveEnvVar ( "TOKEN_EXPIRY" ) ) . toBe ( false )
161+ expect ( isSensitiveEnvVar ( "PASSWORD_RESET_URL" ) ) . toBe ( false )
162+ expect ( isSensitiveEnvVar ( "RESET_TOKEN_COUNT" ) ) . toBe ( false )
163+ expect ( isSensitiveEnvVar ( "MY_SECRET_NAME" ) ) . toBe ( false )
164+ expect ( isSensitiveEnvVar ( "MY_KEY_STORE" ) ) . toBe ( false )
165+ } )
166+ } )
167+
127168describe ( "execCmd" , ( ) => {
128169 it ( "should execute a simple command and return output" , ( ) => {
129170 const result = execCmd ( "echo hello" , { silent : true } )
@@ -139,3 +180,85 @@ describe("execCmd", () => {
139180 expect ( result ) . toBe ( "test-input" )
140181 } )
141182} )
183+
184+ describe ( "tryRecoverDeployUrl" , ( ) => {
185+ afterEach ( ( ) => {
186+ vi . restoreAllMocks ( )
187+ } )
188+
189+ it ( "should return undefined when no URL in error message" , async ( ) => {
190+ const result = await tryRecoverDeployUrl (
191+ "Something went wrong" ,
192+ fixturesDir ,
193+ )
194+ expect ( result ) . toBeUndefined ( )
195+ } )
196+
197+ it ( "should return URL when manifest responds 200" , async ( ) => {
198+ const manifestDir = join ( fixturesDir , "recover-200" )
199+ mkdirSync ( join ( manifestDir , "src" ) , { recursive : true } )
200+ writeFileSync ( join ( manifestDir , "src" , "manifest.ts" ) , "" )
201+ writeFileSync (
202+ join ( manifestDir , "package.json" ) ,
203+ JSON . stringify ( { name : "my-tool" } ) ,
204+ )
205+
206+ vi . stubGlobal ( "fetch" , vi . fn ( ) . mockResolvedValue ( { status : 200 } ) )
207+
208+ const result = await tryRecoverDeployUrl (
209+ "Error: command exited with code 1\nhttps://my-tool-abc123.vercel.app\nsome other output" ,
210+ manifestDir ,
211+ )
212+ expect ( result ) . toBe ( "https://my-tool-abc123.vercel.app" )
213+ } )
214+
215+ it ( "should return undefined when manifest responds non-200" , async ( ) => {
216+ const manifestDir = join ( fixturesDir , "recover-404" )
217+ mkdirSync ( join ( manifestDir , "src" ) , { recursive : true } )
218+ writeFileSync ( join ( manifestDir , "src" , "manifest.ts" ) , "" )
219+ writeFileSync (
220+ join ( manifestDir , "package.json" ) ,
221+ JSON . stringify ( { name : "my-tool" } ) ,
222+ )
223+
224+ vi . stubGlobal ( "fetch" , vi . fn ( ) . mockResolvedValue ( { status : 404 } ) )
225+
226+ const result = await tryRecoverDeployUrl (
227+ "Error: failed\nhttps://my-tool-abc123.vercel.app" ,
228+ manifestDir ,
229+ )
230+ expect ( result ) . toBeUndefined ( )
231+ } )
232+
233+ it ( "should return URL when manifest URL cannot be derived" , async ( ) => {
234+ const emptyDir = join ( fixturesDir , "recover-no-manifest" )
235+ mkdirSync ( emptyDir , { recursive : true } )
236+
237+ const result = await tryRecoverDeployUrl (
238+ "Error: exit 1\nhttps://my-tool-abc123.vercel.app" ,
239+ emptyDir ,
240+ )
241+ expect ( result ) . toBe ( "https://my-tool-abc123.vercel.app" )
242+ } )
243+
244+ it ( "should return undefined when fetch throws" , async ( ) => {
245+ const manifestDir = join ( fixturesDir , "recover-fetch-err" )
246+ mkdirSync ( join ( manifestDir , "src" ) , { recursive : true } )
247+ writeFileSync ( join ( manifestDir , "src" , "manifest.ts" ) , "" )
248+ writeFileSync (
249+ join ( manifestDir , "package.json" ) ,
250+ JSON . stringify ( { name : "my-tool" } ) ,
251+ )
252+
253+ vi . stubGlobal (
254+ "fetch" ,
255+ vi . fn ( ) . mockRejectedValue ( new Error ( "network error" ) ) ,
256+ )
257+
258+ const result = await tryRecoverDeployUrl (
259+ "Error: failed\nhttps://my-tool-abc123.vercel.app" ,
260+ manifestDir ,
261+ )
262+ expect ( result ) . toBeUndefined ( )
263+ } )
264+ } )
0 commit comments