Skip to content

Commit c5b72df

Browse files
kylecarbshugodutka
authored andcommitted
Allow blink deploy to be ran from CI
1 parent 43ae051 commit c5b72df

File tree

2 files changed

+113
-58
lines changed

2 files changed

+113
-58
lines changed

packages/blink/src/cli/deploy.ts

Lines changed: 104 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ export default async function deploy(
2626
directory = process.cwd();
2727
}
2828

29+
// Detect CI environment
30+
const isCI = process.env.CI === "true" || !process.stdout.isTTY;
31+
2932
// Auto-migrate data to .blink if it exists
3033
await migrateDataToBlink(directory);
3134

@@ -63,6 +66,14 @@ export default async function deploy(
6366
deployConfig = JSON.parse(deployConfigContent);
6467
}
6568

69+
// Environment variables take precedence over config file
70+
if (process.env.BLINK_ORGANIZATION_ID) {
71+
deployConfig.organizationId = process.env.BLINK_ORGANIZATION_ID;
72+
}
73+
if (process.env.BLINK_AGENT_ID) {
74+
deployConfig.agentId = process.env.BLINK_AGENT_ID;
75+
}
76+
6677
// Select organization
6778
let organizationName!: string;
6879
if (deployConfig?.organizationId) {
@@ -79,6 +90,11 @@ export default async function deploy(
7990
if (organizations.length === 1) {
8091
deployConfig.organizationId = organizations[0]!.id;
8192
organizationName = organizations[0]!.name;
93+
} else if (isCI) {
94+
throw new Error(
95+
"Multiple organizations found. In CI mode, you must first deploy locally to select an organization, " +
96+
"or set the organization in .blink/config.json"
97+
);
8298
} else {
8399
const selectedId = await select({
84100
message: "Which organization should contain this agent?",
@@ -252,59 +268,78 @@ export default async function deploy(
252268
const missingEnvVars = Object.keys(localEnv).filter((key) => !prodEnv[key]);
253269

254270
if (missingEnvVars.length > 0) {
255-
console.log("\n" + chalk.cyan("Environment Variables"));
256-
console.log(
257-
chalk.dim(
258-
` Missing ${missingEnvVars.length} var${missingEnvVars.length === 1 ? "" : "s"} in .env.production: ${missingEnvVars.join(", ")}`
259-
)
260-
);
261-
262-
const confirmed = await confirm({
263-
message: "Copy missing vars from .env.local to .env.production?",
264-
initialValue: true,
265-
});
266-
if (isCancel(confirmed)) {
267-
return;
268-
}
269-
// Add a newline for visual separation.
270-
console.log();
271-
if (confirmed) {
272-
for (const key of missingEnvVars) {
273-
prodEnv[key] = localEnv[key]!;
274-
}
275-
await writeFile(
276-
prodEnvFile,
277-
`# Environment variables for production deployment\n${Object.entries(
278-
prodEnv
271+
if (isCI) {
272+
console.log(
273+
chalk.yellow("Warning:") +
274+
` Missing ${missingEnvVars.length} var${missingEnvVars.length === 1 ? "" : "s"} in .env.production: ${missingEnvVars.join(", ")}`
275+
);
276+
console.log(
277+
chalk.dim(
278+
" Skipping in CI mode. Set these in .env.production if needed."
279+
)
280+
);
281+
} else {
282+
console.log("\n" + chalk.cyan("Environment Variables"));
283+
console.log(
284+
chalk.dim(
285+
` Missing ${missingEnvVars.length} var${missingEnvVars.length === 1 ? "" : "s"} in .env.production: ${missingEnvVars.join(", ")}`
279286
)
280-
.map(([key, value]) => `${key}=${value}`)
281-
.join("\n")}`,
282-
"utf-8"
283287
);
288+
289+
const confirmed = await confirm({
290+
message: "Copy missing vars from .env.local to .env.production?",
291+
initialValue: true,
292+
});
293+
if (isCancel(confirmed)) {
294+
return;
295+
}
296+
// Add a newline for visual separation.
297+
console.log();
298+
if (confirmed) {
299+
for (const key of missingEnvVars) {
300+
prodEnv[key] = localEnv[key]!;
301+
}
302+
await writeFile(
303+
prodEnvFile,
304+
`# Environment variables for production deployment\n${Object.entries(
305+
prodEnv
306+
)
307+
.map(([key, value]) => `${key}=${value}`)
308+
.join("\n")}`,
309+
"utf-8"
310+
);
311+
}
284312
}
285313
}
286314

287315
// Prompt to migrate devhook to production
288316
const devhookID = getDevhookID(directory);
289317
if (devhookID) {
290-
const productionUrl = `https://${devhookID}.blink.host`;
291-
console.log("\n" + chalk.cyan("Webhook Tunnel"));
292-
console.log(chalk.dim(` Current: ${productionUrl} → local dev`));
293-
console.log(chalk.dim(` After: ${productionUrl} → production`));
294-
console.log(
295-
chalk.dim(" Migrating will keep your webhooks working in production")
296-
);
318+
if (isCI) {
319+
// Skip devhook migration in CI mode
320+
console.log(
321+
chalk.dim(" Skipping webhook tunnel migration in CI mode")
322+
);
323+
} else {
324+
const productionUrl = `https://${devhookID}.blink.host`;
325+
console.log("\n" + chalk.cyan("Webhook Tunnel"));
326+
console.log(chalk.dim(` Current: ${productionUrl} → local dev`));
327+
console.log(chalk.dim(` After: ${productionUrl} → production`));
328+
console.log(
329+
chalk.dim(" Migrating will keep your webhooks working in production")
330+
);
297331

298-
const confirmed = await confirm({
299-
message: "Migrate tunnel to production?",
300-
});
301-
if (isCancel(confirmed)) {
302-
return;
303-
}
304-
// Add a newline for visual separation.
305-
console.log();
306-
if (confirmed) {
307-
migratedDevhook = true;
332+
const confirmed = await confirm({
333+
message: "Migrate tunnel to production?",
334+
});
335+
if (isCancel(confirmed)) {
336+
return;
337+
}
338+
// Add a newline for visual separation.
339+
console.log();
340+
if (confirmed) {
341+
migratedDevhook = true;
342+
}
308343
}
309344
}
310345
}
@@ -362,17 +397,28 @@ export default async function deploy(
362397
(key) => !Object.keys(prodEnv).includes(key)
363398
);
364399
if (missingEnvVars.length > 0) {
365-
console.log(
366-
"Warning: The following environment variables are set in .env.local but not in .env.production:"
367-
);
368-
for (const v of missingEnvVars) {
369-
console.log(`- ${v}`);
370-
}
371-
const confirmed = await confirm({
372-
message: "Do you want to deploy anyway?",
373-
});
374-
if (confirmed === false || isCancel(confirmed)) {
375-
return;
400+
if (isCI) {
401+
console.log(
402+
chalk.yellow("Warning:") +
403+
" The following environment variables are set in .env.local but not in .env.production:"
404+
);
405+
for (const v of missingEnvVars) {
406+
console.log(`- ${v}`);
407+
}
408+
console.log(chalk.dim(" Continuing deployment in CI mode"));
409+
} else {
410+
console.log(
411+
"Warning: The following environment variables are set in .env.local but not in .env.production:"
412+
);
413+
for (const v of missingEnvVars) {
414+
console.log(`- ${v}`);
415+
}
416+
const confirmed = await confirm({
417+
message: "Do you want to deploy anyway?",
418+
});
419+
if (confirmed === false || isCancel(confirmed)) {
420+
return;
421+
}
376422
}
377423
}
378424

@@ -398,7 +444,9 @@ export default async function deploy(
398444
console.log(chalk.gray(`View Deployment ${chalk.dim(inspectUrl)}`));
399445

400446
// Write deploy config on success
401-
await writeDeployConfig(deployConfigPath, deployConfig);
447+
if (!isCI) {
448+
await writeDeployConfig(deployConfigPath, deployConfig);
449+
}
402450

403451
// Poll for deployment completion
404452
const s = spinner();

packages/blink/src/cli/lib/auth.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,15 +68,22 @@ function getAuthTokenConfigPath() {
6868

6969
export async function loginIfNeeded(): Promise<string> {
7070
const client = new Client();
71-
let token = getAuthToken();
71+
72+
// Check for BLINK_TOKEN environment variable first (for CI)
73+
let token = process.env.BLINK_TOKEN || getAuthToken();
74+
7275
if (token) {
7376
client.authToken = token;
7477

7578
try {
7679
// Ensure that the token is valid.
7780
await client.users.me();
7881
} catch (_err) {
79-
// The token is invalid, so we need to login again.
82+
// The token is invalid
83+
if (process.env.BLINK_TOKEN) {
84+
throw new Error("BLINK_TOKEN environment variable is invalid");
85+
}
86+
// Try to login again
8087
token = await login();
8188
}
8289
} else {

0 commit comments

Comments
 (0)