Skip to content

Commit 66f1561

Browse files
kylecarbshugodutka
andauthored
Allow blink deploy to be ran from CI (#28)
Co-authored-by: Hugo Dutka <[email protected]>
1 parent 43ae051 commit 66f1561

File tree

2 files changed

+112
-58
lines changed

2 files changed

+112
-58
lines changed

packages/blink/src/cli/deploy.ts

Lines changed: 103 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,10 @@ 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. To use CI mode, please deploy in interactive mode first to select an organization and generate .blink/config.json"
96+
);
8297
} else {
8398
const selectedId = await select({
8499
message: "Which organization should contain this agent?",
@@ -252,59 +267,78 @@ export default async function deploy(
252267
const missingEnvVars = Object.keys(localEnv).filter((key) => !prodEnv[key]);
253268

254269
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
270+
if (isCI) {
271+
console.log(
272+
chalk.yellow("Warning:") +
273+
` Missing ${missingEnvVars.length} var${missingEnvVars.length === 1 ? "" : "s"} in .env.production: ${missingEnvVars.join(", ")}`
274+
);
275+
console.log(
276+
chalk.dim(
277+
" Skipping in CI mode. Set these in .env.production if needed."
278+
)
279+
);
280+
} else {
281+
console.log("\n" + chalk.cyan("Environment Variables"));
282+
console.log(
283+
chalk.dim(
284+
` Missing ${missingEnvVars.length} var${missingEnvVars.length === 1 ? "" : "s"} in .env.production: ${missingEnvVars.join(", ")}`
279285
)
280-
.map(([key, value]) => `${key}=${value}`)
281-
.join("\n")}`,
282-
"utf-8"
283286
);
287+
288+
const confirmed = await confirm({
289+
message: "Copy missing vars from .env.local to .env.production?",
290+
initialValue: true,
291+
});
292+
if (isCancel(confirmed)) {
293+
return;
294+
}
295+
// Add a newline for visual separation.
296+
console.log();
297+
if (confirmed) {
298+
for (const key of missingEnvVars) {
299+
prodEnv[key] = localEnv[key]!;
300+
}
301+
await writeFile(
302+
prodEnvFile,
303+
`# Environment variables for production deployment\n${Object.entries(
304+
prodEnv
305+
)
306+
.map(([key, value]) => `${key}=${value}`)
307+
.join("\n")}`,
308+
"utf-8"
309+
);
310+
}
284311
}
285312
}
286313

287314
// Prompt to migrate devhook to production
288315
const devhookID = getDevhookID(directory);
289316
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-
);
317+
if (isCI) {
318+
// Skip devhook migration in CI mode
319+
console.log(
320+
chalk.dim(" Skipping webhook tunnel migration in CI mode")
321+
);
322+
} else {
323+
const productionUrl = `https://${devhookID}.blink.host`;
324+
console.log("\n" + chalk.cyan("Webhook Tunnel"));
325+
console.log(chalk.dim(` Current: ${productionUrl} → local dev`));
326+
console.log(chalk.dim(` After: ${productionUrl} → production`));
327+
console.log(
328+
chalk.dim(" Migrating will keep your webhooks working in production")
329+
);
297330

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;
331+
const confirmed = await confirm({
332+
message: "Migrate tunnel to production?",
333+
});
334+
if (isCancel(confirmed)) {
335+
return;
336+
}
337+
// Add a newline for visual separation.
338+
console.log();
339+
if (confirmed) {
340+
migratedDevhook = true;
341+
}
308342
}
309343
}
310344
}
@@ -362,17 +396,28 @@ export default async function deploy(
362396
(key) => !Object.keys(prodEnv).includes(key)
363397
);
364398
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;
399+
if (isCI) {
400+
console.log(
401+
chalk.yellow("Warning:") +
402+
" The following environment variables are set in .env.local but not in .env.production:"
403+
);
404+
for (const v of missingEnvVars) {
405+
console.log(`- ${v}`);
406+
}
407+
console.log(chalk.dim(" Continuing deployment in CI mode"));
408+
} else {
409+
console.log(
410+
"Warning: The following environment variables are set in .env.local but not in .env.production:"
411+
);
412+
for (const v of missingEnvVars) {
413+
console.log(`- ${v}`);
414+
}
415+
const confirmed = await confirm({
416+
message: "Do you want to deploy anyway?",
417+
});
418+
if (confirmed === false || isCancel(confirmed)) {
419+
return;
420+
}
376421
}
377422
}
378423

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

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

403450
// Poll for deployment completion
404451
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)