Skip to content

Commit 7cc71cb

Browse files
feat: hosting app deploy supports preview deployment flag (#1205)
* feat: hosting app deploy supports preview deployment flag * write preview url to github actions output * bump * promote instead of preview * working nicely * fmt
1 parent 1cc07e9 commit 7cc71cb

File tree

5 files changed

+72
-32
lines changed

5 files changed

+72
-32
lines changed

packages/cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "deco-cli",
3-
"version": "0.19.3",
3+
"version": "0.20.0",
44
"description": "CLI for managing decocms.com apps & projects",
55
"license": "MIT",
66
"author": "Deco team",

packages/cli/src/cli.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ const hostingDeploy = new Command("deploy")
183183
"--dry-run",
184184
"Write deploy manifest to local filesystem instead of deploying",
185185
)
186+
.option("--no-promote", "Do not promote the deployment to production routes")
186187
.argument("[cwd]", "Working directory")
187188
.action(async (cwd, options) => {
188189
try {
@@ -206,6 +207,7 @@ const hostingDeploy = new Command("deploy")
206207
assetsDirectory,
207208
force: options.force,
208209
dryRun: options.dryRun,
210+
promote: options.promote ?? true,
209211
});
210212
} catch (error) {
211213
console.error(

packages/cli/src/commands/hosting/deploy.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ interface Options {
3535
unlisted?: boolean;
3636
assetsDirectory?: string;
3737
force?: boolean;
38+
promote?: boolean;
3839
dryRun?: boolean;
3940
}
4041

@@ -48,6 +49,7 @@ export const deploy = async ({
4849
assetsDirectory,
4950
skipConfirmation,
5051
force,
52+
promote = true,
5153
unlisted = true,
5254
dryRun = false,
5355
}: Options) => {
@@ -163,13 +165,17 @@ export const deploy = async ({
163165
bundle: hasTsFile,
164166
unlisted,
165167
force,
168+
promote,
166169
};
167170

168171
console.log("🚚 Deployment summary:");
169172
console.log(` App: ${appSlug}`);
170173
console.log(` Files: ${files.length}`);
171174
console.log(` ${envVarsStatus}`);
172175
console.log(` ${wranglerConfigStatus}`);
176+
if (promote) {
177+
console.log(` Promote mode: true (deployment will replace production)`);
178+
}
173179

174180
if (dryRun) {
175181
const manifestPath = join(cwd, "deploy-manifest.json");
@@ -240,4 +246,12 @@ export const deploy = async ({
240246
console.log(`\n🎉 Deployed! Available at:`);
241247
hosts.forEach((host) => console.log(` ${host}`));
242248
console.log();
249+
250+
const previewUrl = promote ? null : hosts[0];
251+
if (process.env.GITHUB_OUTPUT && previewUrl) {
252+
await fs.appendFile(
253+
process.env.GITHUB_OUTPUT,
254+
`preview_url=${previewUrl}\n`,
255+
);
256+
}
243257
};

packages/create-deco/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "create-deco",
3-
"version": "1.0.7",
3+
"version": "1.0.8",
44
"description": "Create a new deco app",
55
"main": "index.js",
66
"bin": {
@@ -20,7 +20,7 @@
2020
"author": "Deco team",
2121
"license": "MIT",
2222
"dependencies": {
23-
"deco-cli": "^0.17.6"
23+
"deco-cli": "^0.20.0"
2424
},
2525
"engines": {
2626
"node": ">=22.0.0"

packages/sdk/src/mcp/hosting/api.ts

Lines changed: 53 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ interface UpdateDatabaseArgs {
147147
result: DeployResult;
148148
wranglerConfig: WranglerConfig;
149149
force?: boolean;
150+
promote?: boolean;
150151
files?: Record<string, string>;
151152
}
152153

@@ -158,6 +159,7 @@ async function updateDatabase({
158159
result,
159160
wranglerConfig,
160161
force,
162+
promote = true,
161163
}: UpdateDatabaseArgs) {
162164
// First, ensure the app exists (without deployment-specific data)
163165
let { data: app, error: updateError } = await c.db
@@ -275,32 +277,34 @@ async function updateDatabase({
275277
const regularRoutes = toInsert.filter((r) => !r.custom_domain);
276278

277279
// For custom domains, use the promote functionality (update existing routes only)
278-
279-
await Promise.all(
280-
customDomainRoutes.map(async (route) => {
281-
try {
282-
await promoteDeployment(c, {
283-
deploymentId: deployment.id,
284-
routePattern: route.route_pattern,
285-
force,
286-
});
287-
} catch (error) {
288-
// If route doesn't exist, create it (only during initial deployment)
289-
if (error instanceof NotFoundError) {
290-
const { error: insertError } = await c.db
291-
.from(DECO_CHAT_HOSTING_ROUTES_TABLE)
292-
.insert({
293-
deployment_id: deployment.id,
294-
route_pattern: route.route_pattern,
295-
custom_domain: true,
296-
});
297-
if (insertError) throw insertError;
298-
} else {
299-
throw error;
280+
// Skip promotion if this is a preview deployment
281+
if (promote) {
282+
await Promise.all(
283+
customDomainRoutes.map(async (route) => {
284+
try {
285+
await promoteDeployment(c, {
286+
deploymentId: deployment.id,
287+
routePattern: route.route_pattern,
288+
force,
289+
});
290+
} catch (error) {
291+
// If route doesn't exist, create it (only during initial deployment)
292+
if (error instanceof NotFoundError) {
293+
const { error: insertError } = await c.db
294+
.from(DECO_CHAT_HOSTING_ROUTES_TABLE)
295+
.insert({
296+
deployment_id: deployment.id,
297+
route_pattern: route.route_pattern,
298+
custom_domain: true,
299+
});
300+
if (insertError) throw insertError;
301+
} else {
302+
throw error;
303+
}
300304
}
301-
}
302-
}),
303-
);
305+
}),
306+
);
307+
}
304308
// For regular routes (non-custom domains), just insert them in batch
305309
if (regularRoutes.length > 0) {
306310
const { error: insertError } = await c.db
@@ -446,6 +450,8 @@ export const deployFiles = createTool({
446450
name: "HOSTING_APP_DEPLOY",
447451
description: `Deploy multiple TypeScript files that use Wrangler for bundling and deployment to Cloudflare Workers. You must provide a package.json file with the necessary dependencies and a wrangler.toml file matching the Workers for Platforms format. Use 'main_module' instead of 'main', and define bindings using the [[bindings]] array, where each binding is a table specifying its type and properties. To add custom Deco bindings, set type = "MCP" in a binding entry (these will be filtered and handled automatically).
448452
453+
Set 'preview: true' to create a preview deployment that won't replace the production version. Preview deployments get their own unique URL but are not promoted to production routes.
454+
449455
Common patterns:
450456
1. Use a package.json file to manage dependencies:
451457
// package.json
@@ -649,6 +655,13 @@ Important Notes:
649655
"If true, force the deployment even if there are breaking changes",
650656
)
651657
.optional(),
658+
promote: z
659+
.boolean()
660+
.describe(
661+
"If true, promotes the deployment to production routes. The deployment will be available at a unique URL but won't replace the production version.",
662+
)
663+
.optional()
664+
.default(true),
652665
appSlug: z
653666
.string()
654667
.optional()
@@ -694,6 +707,7 @@ Important Notes:
694707
handler: async (
695708
{
696709
force,
710+
promote = true,
697711
appSlug: _appSlug,
698712
files,
699713
envVars,
@@ -726,6 +740,7 @@ Important Notes:
726740
: ({ name: _appSlug } as WranglerConfig);
727741

728742
addDefaultCustomDomain(wranglerConfig);
743+
729744
// check if the entrypoint is in the files
730745
const entrypoints = [
731746
...ENTRYPOINTS,
@@ -851,6 +866,7 @@ Important Notes:
851866
const data = await updateDatabase({
852867
c,
853868
force,
869+
promote,
854870
workspace,
855871
scriptSlug,
856872
result,
@@ -877,12 +893,20 @@ Important Notes:
877893
throw err;
878894
}
879895
});
896+
897+
const hosts = [];
898+
899+
if (promote) {
900+
hosts.push(data.entrypoint);
901+
}
902+
903+
if (deploymentId) {
904+
hosts.push(Entrypoint.build(data.slug!, deploymentId));
905+
}
906+
880907
return {
881908
entrypoint: data.entrypoint,
882-
hosts: [
883-
data.entrypoint,
884-
...(deploymentId ? [Entrypoint.build(data.slug!, deploymentId)] : []),
885-
],
909+
hosts,
886910
id: data.id,
887911
workspace: data.workspace,
888912
deploymentId,

0 commit comments

Comments
 (0)