diff --git a/.changeset/project-list-deployed-url.md b/.changeset/project-list-deployed-url.md new file mode 100644 index 0000000000..0ff85acf7f --- /dev/null +++ b/.changeset/project-list-deployed-url.md @@ -0,0 +1,5 @@ +--- +"@bigcommerce/catalyst": minor +--- + +Show every deployed URL for each project in `catalyst project list` output (the canonical hostname plus any vanity hostnames) so users can recover the hosted storefront URLs without having to redeploy. diff --git a/packages/catalyst/src/cli/commands/deploy.ts b/packages/catalyst/src/cli/commands/deploy.ts index 78229faabb..6f15c08b37 100644 --- a/packages/catalyst/src/cli/commands/deploy.ts +++ b/packages/catalyst/src/cli/commands/deploy.ts @@ -65,7 +65,11 @@ const DeploymentStatusSchema = z.object({ progress: z.number(), }) .nullable(), - deployment_url: z.string().nullable(), + // Deprecated by ignition; prefer `deployment_hostnames`. Kept here so + // older ignition builds (that haven't shipped the rename yet) still + // parse cleanly during the transition window. + deployment_url: z.string().nullable().optional(), + deployment_hostnames: z.array(z.string()).optional(), error: z .object({ code: z.number(), @@ -285,7 +289,7 @@ export const getDeploymentStatus = async ( const decoder = new TextDecoder(); let done = false; - let deploymentUrl: string | undefined; + let deploymentHostname: string | undefined; while (!done) { // eslint-disable-next-line no-await-in-loop @@ -321,8 +325,12 @@ export const getDeploymentStatus = async ( spinner.text = STEPS[data.event.step]; } - if (data.deployment_url) { - deploymentUrl = data.deployment_url; + // Prefer the new plural field; fall back to the deprecated singular + // for older ignition builds during the transition window. + if (data.deployment_hostnames && data.deployment_hostnames.length > 0) { + deploymentHostname = data.deployment_hostnames[0]; + } else if (data.deployment_url) { + deploymentHostname = data.deployment_url; } }); } @@ -332,10 +340,10 @@ export const getDeploymentStatus = async ( spinner.success('Deployment completed successfully.'); - if (deploymentUrl) { - const url = deploymentUrl.startsWith('https://') ? deploymentUrl : `https://${deploymentUrl}`; - - consola.success(`View your deployment at: ${colorize('blue', url)}`); + if (deploymentHostname) { + consola.success( + `View your deployment at: ${colorize('blue', `https://${deploymentHostname}`)}`, + ); } }; diff --git a/packages/catalyst/src/cli/commands/project.spec.ts b/packages/catalyst/src/cli/commands/project.spec.ts index 79a4933a71..7c74da750a 100644 --- a/packages/catalyst/src/cli/commands/project.spec.ts +++ b/packages/catalyst/src/cli/commands/project.spec.ts @@ -227,7 +227,14 @@ describe('project list', () => { expect(consola.start).toHaveBeenCalledWith('Fetching projects...'); expect(consola.success).toHaveBeenCalledWith('Projects fetched.'); expect(consola.log).toHaveBeenCalledWith('Project One (a23f5785-fd99-4a94-9fb3-945551623923)'); + expect(consola.log).toHaveBeenCalledWith( + expect.stringContaining('https://project-one.catalyst-sandbox.store'), + ); + expect(consola.log).toHaveBeenCalledWith( + expect.stringContaining('https://vanity.project-one.example.com'), + ); expect(consola.log).toHaveBeenCalledWith('Project Two (b23f5785-fd99-4a94-9fb3-945551623924)'); + expect(consola.log).toHaveBeenCalledWith(' (not deployed)'); expect(exitMock).toHaveBeenCalledWith(0); }); diff --git a/packages/catalyst/src/cli/commands/project.ts b/packages/catalyst/src/cli/commands/project.ts index a5ecde3193..43691e765d 100644 --- a/packages/catalyst/src/cli/commands/project.ts +++ b/packages/catalyst/src/cli/commands/project.ts @@ -1,4 +1,5 @@ import { Command, Option } from 'commander'; +import { colorize } from 'consola/utils'; import { consola } from '../lib/logger'; import { createProject, fetchProjects } from '../lib/project'; @@ -53,6 +54,16 @@ Example: projects.forEach((p) => { consola.log(`${p.name} (${p.uuid})`); + + if (p.deployment_hostnames.length === 0) { + consola.log(' (not deployed)'); + } else { + p.deployment_hostnames.forEach((hostname) => { + consola.log(` ${colorize('blue', `https://${hostname}`)}`); + }); + } + + consola.log(''); }); process.exit(0); diff --git a/packages/catalyst/src/cli/lib/project.ts b/packages/catalyst/src/cli/lib/project.ts index 1dc66420ee..dba5bbee16 100644 --- a/packages/catalyst/src/cli/lib/project.ts +++ b/packages/catalyst/src/cli/lib/project.ts @@ -7,6 +7,7 @@ const fetchProjectsSchema = z.object({ z.object({ uuid: z.string(), name: z.string(), + deployment_hostnames: z.array(z.string()), }), ), }); @@ -14,6 +15,7 @@ const fetchProjectsSchema = z.object({ export interface ProjectListItem { uuid: string; name: string; + deployment_hostnames: string[]; } export async function fetchProjects( diff --git a/packages/catalyst/tests/mocks/handlers.ts b/packages/catalyst/tests/mocks/handlers.ts index c16ea054db..099d5bc33d 100644 --- a/packages/catalyst/tests/mocks/handlers.ts +++ b/packages/catalyst/tests/mocks/handlers.ts @@ -29,8 +29,19 @@ export const handlers = [ http.get('https://:apiHost/stores/:storeHash/v3/infrastructure/projects', () => HttpResponse.json({ data: [ - { uuid: 'a23f5785-fd99-4a94-9fb3-945551623923', name: 'Project One' }, - { uuid: 'b23f5785-fd99-4a94-9fb3-945551623924', name: 'Project Two' }, + { + uuid: 'a23f5785-fd99-4a94-9fb3-945551623923', + name: 'Project One', + deployment_hostnames: [ + 'project-one.catalyst-sandbox.store', + 'vanity.project-one.example.com', + ], + }, + { + uuid: 'b23f5785-fd99-4a94-9fb3-945551623924', + name: 'Project Two', + deployment_hostnames: [], + }, ], }), ), @@ -44,14 +55,14 @@ export const handlers = [ controller.enqueue( encoder.encode( // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - `data: {"deployment_status":"in_progress","deployment_uuid":"${params.deploymentUuid}","event":{"step":"processing","progress":75},"deployment_url":null}`, + `data: {"deployment_status":"in_progress","deployment_uuid":"${params.deploymentUuid}","event":{"step":"processing","progress":75},"deployment_url":null,"deployment_hostnames":[]}`, ), ); setTimeout(() => { controller.enqueue( encoder.encode( // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - `data: {"deployment_status":"in_progress","deployment_uuid":"${params.deploymentUuid}","event":{"step":"finalizing","progress":99},"deployment_url":null}`, + `data: {"deployment_status":"in_progress","deployment_uuid":"${params.deploymentUuid}","event":{"step":"finalizing","progress":99},"deployment_url":null,"deployment_hostnames":[]}`, ), ); }, 10); @@ -59,7 +70,7 @@ export const handlers = [ controller.enqueue( encoder.encode( // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - `data: {"deployment_status":"completed","deployment_uuid":"${params.deploymentUuid}","event":null,"deployment_url":"https://example.com"}`, + `data: {"deployment_status":"completed","deployment_uuid":"${params.deploymentUuid}","event":null,"deployment_url":"example.com","deployment_hostnames":["example.com"]}`, ), ); controller.close();