Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ This project is structured as follows:
- **Type Safety**: Never use non-null assertions (`!`). Always write type-safe code. Non-null assertions are only allowed in test files.
- **Zod Coercion for CLI Options**: Always use `z.coerce.number()` (not `z.number()`) for numeric CLI options, since CLI arguments are parsed as strings.
- **`--json` Output**: Commands that return data take a `json` option (`z.boolean().optional().describe('Output in JSON format.')`) and print `console.log(JSON.stringify(data, null, 2))` with the resource's own field names (`{ id }`). Commands with progress output (spinners, step logs) keep that output and just append the JSON, since progress is useful in CI/CD. Commands without progress output use `if (json) { ...JSON... } else { ...consola... }` so `--json` stays clean.
- **URL Field Naming**: JSON output fields that point to a page in a web UI (e.g. the Capawesome Cloud Console) are named `webUrl` — never `url` or `consoleUrl`. Nested resources use the full entity name as the key (e.g. `appBuildShare`, not `share`).

## Philosophy

Expand Down
6 changes: 3 additions & 3 deletions src/commands/apps/builds/create.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,14 @@ describe('apps-builds-create', () => {
expect(findScope.isDone()).toBe(true);
expect(shareScope.isDone()).toBe(true);

const url = `${DEFAULT_CONSOLE_BASE_URL}/app-build-shares/${shareId}`;
const qrCodeUrl = `${DEFAULT_API_BASE_URL}/v1/qrcodes?content=${encodeURIComponent(url)}&format=png`;
const webUrl = `${DEFAULT_CONSOLE_BASE_URL}/app-build-shares/${shareId}`;
const qrCodeUrl = `${DEFAULT_API_BASE_URL}/v1/qrcodes?content=${encodeURIComponent(webUrl)}&format=png`;
expect(console.log).toHaveBeenCalledWith(
JSON.stringify(
{
id: buildId,
numberAsString: '42',
share: { id: shareId, url, qrCodeUrl, expiresAt: null },
appBuildShare: { id: shareId, qrCodeUrl, webUrl, expiresAt: null },
},
null,
2,
Expand Down
10 changes: 5 additions & 5 deletions src/commands/apps/builds/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,7 @@ export default defineCommand({
});
}
// Create a public share link if requested
let share: { id: string; url: string; qrCodeUrl: string; expiresAt: string | null } | undefined;
let appBuildShare: { id: string; qrCodeUrl: string; webUrl: string; expiresAt: string | null } | undefined;
if (options.share) {
const expiresAt =
options.shareExpiresInDays !== undefined
Expand All @@ -468,15 +468,15 @@ export default defineCommand({
expiresAt,
});
const shareUrls = await getAppBuildShareUrls(createdShare.id);
share = {
appBuildShare = {
id: createdShare.id,
url: shareUrls.url,
qrCodeUrl: shareUrls.qrCodeUrl,
webUrl: shareUrls.webUrl,
expiresAt: createdShare.expiresAt,
};
if (!json) {
consola.success('Build shared successfully.');
consola.info(`Share URL: ${shareUrls.url}`);
consola.info(`Share URL: ${shareUrls.webUrl}`);
}
}

Expand All @@ -497,7 +497,7 @@ export default defineCommand({
{
id: response.id,
numberAsString: response.numberAsString,
...(share ? { share } : {}),
...(appBuildShare ? { appBuildShare } : {}),
},
null,
2,
Expand Down
8 changes: 5 additions & 3 deletions src/commands/apps/builds/share.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,11 @@ describe('apps-builds-share', () => {
expect(buildScope.isDone()).toBe(true);
expect(shareScope.isDone()).toBe(true);

const url = `${DEFAULT_CONSOLE_BASE_URL}/app-build-shares/${shareId}`;
const qrCodeUrl = `${DEFAULT_API_BASE_URL}/v1/qrcodes?content=${encodeURIComponent(url)}&format=png`;
expect(console.log).toHaveBeenCalledWith(JSON.stringify({ id: shareId, url, qrCodeUrl, expiresAt: null }, null, 2));
const webUrl = `${DEFAULT_CONSOLE_BASE_URL}/app-build-shares/${shareId}`;
const qrCodeUrl = `${DEFAULT_API_BASE_URL}/v1/qrcodes?content=${encodeURIComponent(webUrl)}&format=png`;
expect(console.log).toHaveBeenCalledWith(
JSON.stringify({ id: shareId, qrCodeUrl, webUrl, expiresAt: null }, null, 2),
);
expect(mockConsola.success).not.toHaveBeenCalled();
});

Expand Down
6 changes: 3 additions & 3 deletions src/commands/apps/builds/share.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,13 @@ export default defineCommand({
: undefined;

const share = await appBuildsService.createShare({ appId, appBuildId: buildId, description, expiresAt });
const { url, qrCodeUrl } = await getAppBuildShareUrls(share.id);
const { qrCodeUrl, webUrl } = await getAppBuildShareUrls(share.id);

if (json) {
console.log(JSON.stringify({ id: share.id, url, qrCodeUrl, expiresAt: share.expiresAt }, null, 2));
console.log(JSON.stringify({ id: share.id, qrCodeUrl, webUrl, expiresAt: share.expiresAt }, null, 2));
} else {
consola.success('Build shared successfully.');
consola.info(`Share URL: ${url}`);
consola.info(`Share URL: ${webUrl}`);
consola.info(`QR Code URL: ${qrCodeUrl}`);
if (share.expiresAt) {
consola.info(`Expires At: ${share.expiresAt}`);
Expand Down
8 changes: 4 additions & 4 deletions src/utils/app-build-shares.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import configService from '@/services/config.js';

export interface AppBuildShareUrls {
url: string;
qrCodeUrl: string;
webUrl: string;
}

/**
Expand All @@ -11,7 +11,7 @@ export interface AppBuildShareUrls {
export const getAppBuildShareUrls = async (shareId: string): Promise<AppBuildShareUrls> => {
const consoleBaseUrl = await configService.getValueForKey('CONSOLE_BASE_URL');
const apiBaseUrl = await configService.getValueForKey('API_BASE_URL');
const url = `${consoleBaseUrl}/app-build-shares/${shareId}`;
const qrCodeUrl = `${apiBaseUrl}/v1/qrcodes?content=${encodeURIComponent(url)}&format=png`;
return { url, qrCodeUrl };
const webUrl = `${consoleBaseUrl}/app-build-shares/${shareId}`;
const qrCodeUrl = `${apiBaseUrl}/v1/qrcodes?content=${encodeURIComponent(webUrl)}&format=png`;
return { qrCodeUrl, webUrl };
};
Loading