DashboardcraftSession 1VJ3OVRZQ0Cr

Session 1VJ3OVRZQ0Cr

Full ID: 1VJ3OVRZQ0CrLAVdS
Messages: 20
Distillations: 7
Time range: 2026-07-15 10:01:20 — 2026-07-15 10:08:12

Conversation (20 messages)

I'm working in the getsentry/craft repo (Sentry's release automation CLI, TypeScript, Vitest). I'm about to add a new cloudflare release target and prefixed-tag support. I need to understand testing patterns, config schema, and documentation conventions.

Please explore VERY THOROUGHLY and report back with exact file paths and line numbers plus code snippets:

  1. How targets are TESTED. Look in src/__tests__/ and any src/targets/__tests__/. Find an existing target test file (e.g. for npm or gh-pages) and report the testing pattern: how they mock external command execution, how they mock the artifact provider, how they construct the target under test, and how they assert. Include a representative snippet.

  2. The Zod schema for a single target config. Where in src/schemas/ is a target's config defined? How does adding a new target type register its config options in the schema? Show the target config union/discriminated type if it exists.

  3. The docs. Find the documentation for targets (likely docs/src/content/docs/). Which file documents the available targets? Show how one target (e.g. gh-pages or npm) is documented β€” the structure/format expected so I can document cloudflare similarly. Also note the KNOWN gotcha that docs/configuration.md duplicates DEFAULT_RELEASE_CONFIG.

  4. Search for how TargetConfig types flow: from .craft.yml YAML -> parsed config -> target constructor. Report the type of the config object passed to a target constructor.

  5. Any existing target that takes a build directory / static site as input (besides gh-pages). Report how a directory of files (vs individual artifacts) is handled.

  6. The AGENTS.md and any CONTRIBUTING docs mention of how to add a new target. Summarize any documented steps.

Report exact file paths and line numbers, and include representative code snippets. Be comprehensive.

gateway · 2026-07-15 10:01:20

I'll explore the craft repo thoroughly. Let me start by mapping out the structure and finding relevant files in parallel.

anthropic/claude-opus-4.8 · 2026-07-15 10:01:20 · ~41 tokens
result
total 300
drwxrwxr-x 1 byk byk   476 Jun  9 17:16 .
drwxrwxr-x 1 byk byk   246 Apr 21 15:55 ..
-rw-r--r-- 1 byk byk 13207 Feb  9 13:18 awsLambdaLayer.ts
-rw-r--r-- 1 byk byk  2874 Mar 27 13:31 base.ts
-rw-r--r-- 1 byk byk  7878 Feb  9 13:18 brew.ts
-rw-r--r-- 1 byk byk  7012 Jun  9 17:16 cocoapods.ts
-rw-r--r-- 1 byk byk  6268 Apr 22 20:08 commitOnGitRepository.ts
-rw-r--r-- 1 byk byk 12774 Mar  3 10:49 crates.ts
-rw-r--r-- 1 byk byk 21854 Mar 27 13:31 docker.ts
-rw-r--r-- 1 byk byk  7809 Feb  9 13:18 gcs.ts
-rw-r--r-- 1 byk byk  4980 Mar  3 10:49 gem.ts
-rw-r--r-- 1 byk byk  7384 Feb  9 13:18 ghPages.ts
-rw-r--r-- 1 byk byk 27488 Jun  9 17:16 github.ts
-rw-r--r-- 1 byk byk  5050 Mar 27 13:31 hex.ts
-rw-r--r-- 1 byk byk  2299 Mar 27 13:31 index.ts
-rw-r--r-- 1 byk byk 28900 Feb  9 13:18 maven.ts
-rw-r--r-- 1 byk byk 35304 Apr 22 20:08 npm.ts
-rw-r--r-- 1 byk byk  9518 Jun  9 17:16 nuget.ts
-rw-r--r-- 1 byk byk  5727 Feb  9 13:18 powershell.ts
-rw-r--r-- 1 byk byk 10124 Mar  3 10:49 ...
result
docs/node_modules/.pnpm/@babel+helper-string-parser@7.27.1/node_modules/@babel/helper-string-parser/README.md
docs/node_modules/.pnpm/@babel+helper-validator-identifier@7.28.5/node_modules/@babel/helper-validator-identifier/README.md
docs/node_modules/.pnpm/@babel+runtime@7.28.4/node_modules/@babel/runtime/README.md
docs/node_modules/.pnpm/@capsizecss+unpack@4.0.0/node_modules/@capsizecss/unpack/README.md
docs/node_modules/.pnpm/@ctrl+tinycolor@4.2.0/node_modules/@ctrl/tinycolor/README.md
docs/node_modules/.pnpm/@expressive-code+core@0.41.6/node_modules/@expressive-code/core/README.md
docs/node_modules/.pnpm/@expressive-code+plugin-frames@0.41.6/node_modules/@expressive-code/plugin-frames/README.md
docs/node_modules/.pnpm/@expressive-code+plugin-shiki@0.41.6/node_modules/@expressive-code/plugin-shiki/README.md
docs/node_modules/.pnpm/@expressive-code+plugin-text-markers@0.41.6/node_modules/@expressive-code/plugin-text-markers/README.md
docs/node_modules/.pnpm/@img+sharp-libvips-linux-x...
gateway · 2026-07-15 10:02:08

Let me look at the key files. gh-pages is the best analog for cloudflare (static site directory). Let me read the relevant source, tests, schema, index, base, and docs.

anthropic/claude-opus-4.8 · 2026-07-15 10:02:08 · ~56 tokens
result
<path>/home/byk/Code/getsentry/craft/src/targets/ghPages.ts</path>
<type>file</type>
<content>
1: import * as fs from 'fs';
2: import * as path from 'path';
3: 
4: import { Octokit } from '@octokit/rest';
5: 
6: import {
7:   GitHubGlobalConfig,
8:   TargetConfig,
9:   TypedTargetConfig,
10: } from '../schemas/project_config';
11: import { ConfigurationError, reportError } from '../utils/errors';
12: import { withTempDir } from '../utils/files';
13: import {
14:   getGitHubApiToken,
15:   getGitHubClient,
16:   GitHubRemote,
17: } from '../utils/githubApi';
18: import { cloneRepo } from '../utils/git';
19: import { extractZipArchive } from '../utils/system';
20: import { BaseTarget } from './base';
21: import { BaseArtifactProvider } from '../artifact_providers/base';
22: 
23: /** GH Pages target configuration fields */
24: interface GhPagesConfigFields extends Record<string, unknown> {
25:   branch?: string;
26:   githubOwner?: string;
27:   githubRepo?: string;
28: }
29: 
30: ...
result
<path>/home/byk/Code/getsentry/craft/src/targets/base.ts</path>
<type>file</type>
<content>
1: import { logger as loggerRaw } from '../logger';
2: import { GitHubGlobalConfig, TargetConfig } from '../schemas/project_config';
3: import {
4:   parseFilterOptions,
5:   RawFilterOptions,
6:   ParsedFilterOptions,
7: } from '../artifact_providers/base';
8: import { stringToRegexp } from '../utils/filters';
9: import {
10:   BaseArtifactProvider,
11:   RemoteArtifact,
12: } from '../artifact_providers/base';
13: 
14: /**
15:  * Base class for all remote targets
16:  */
17: export class BaseTarget {
18:   public readonly id: string;
19:   protected readonly logger: typeof loggerRaw;
20:   /** Artifact provider */
21:   public readonly artifactProvider: BaseArtifactProvider;
22:   /** Unparsed target configuration */
23:   public readonly config: TargetConfig;
24:   /** Artifact filtering options for the target */
25:   public readonly filterOptions: ParsedFilterOptions;
26:   /** GitHub repo ...
result
<path>/home/byk/Code/getsentry/craft/src/targets/index.ts</path>
<type>file</type>
<content>
1: import { BaseTarget } from './base';
2: import { BrewTarget } from './brew';
3: import { CocoapodsTarget } from './cocoapods';
4: import { CratesTarget } from './crates';
5: import { DockerTarget } from './docker';
6: import { GcsTarget } from './gcs';
7: import { GemTarget } from './gem';
8: import { GhPagesTarget } from './ghPages';
9: import { GitHubTarget } from './github';
10: import { NpmTarget } from './npm';
11: import { NugetTarget } from './nuget';
12: import { SentryPypiTarget } from './sentryPypi';
13: import { PypiTarget } from './pypi';
14: import { RegistryTarget } from './registry';
15: import { AwsLambdaLayerTarget } from './awsLambdaLayer';
16: import { UpmTarget } from './upm';
17: import { MavenTarget } from './maven';
18: import { SymbolCollector } from './symbolCollector';
19: import { PubDevTarget } from './pubDev';
20: import { HexTarget } from './hex';
21: import { ...
result
<path>/home/byk/Code/getsentry/craft/src/schemas/project_config.ts</path>
<type>file</type>
<content>
1: import { z } from 'zod';
2: 
3: /**
4:  * DEPRECATED: Use changelog.policy instead. Different policies for changelog management
5:  */
6: export const enum ChangelogPolicy {
7:   Auto = 'auto',
8:   Simple = 'simple',
9:   None = 'none',
10: }
11: 
12: /**
13:  * Name of the status provider
14:  */
15: export const enum StatusProviderName {
16:   GitHub = 'github',
17: }
18: 
19: /**
20:  * Name of the artifact provider
21:  */
22: export const enum ArtifactProviderName {
23:   GCS = 'gcs',
24:   GitHub = 'github',
25:   None = 'none',
26: }
27: 
28: /**
29:  * Default versioning policy when no version argument is provided. ...
gateway · 2026-07-15 10:02:50

Now let me read a representative test file (npm and gcs β€” gcs is a good analog for file/directory handling), the docs, AGENTS.md, CONTRIBUTING, and how targets are constructed in publish.ts.

anthropic/claude-opus-4.8 · 2026-07-15 10:02:50 · ~64 tokens
result
<path>/home/byk/Code/getsentry/craft/src/targets/__tests__/npm.test.ts</path>
<type>file</type>
<content>
1: import { vi, type MockInstance } from 'vitest';
2: import {
3:   getPublishTag,
4:   getLatestVersion,
5:   NpmTarget,
6:   NpmPackageAccess,
7:   NPM_BIN,
8:   YARN_BIN,
9: } from '../npm';
10: import type { BaseArtifactProvider } from '../../artifact_providers/base';
11: import type { SemVer } from '../../utils/version';
12: import * as system from '../../utils/system';
13: import * as workspaces from '../../utils/workspaces';
14: 
15: const defaultNpmConfig = {
16:   useYarn: false,
17:   token: 'xxx',
18:   useOidc: false,
19: };
20: 
21: const oidcNpmConfigNoToken = {
22:   useYarn: false,
23:   token: undefined,
24:   useOidc: true,
25: };
26: 
27: describe('getLatestVersion', () => {
28:   let spawnProcessMock: MockInstance;
29: 
30:   beforeEach(() => {
31:     spawnProcessMock = vi
32:       .spyOn(system, 'spawnProcess')
33:       .mockImplementation(() => ...
result
<path>/home/byk/Code/getsentry/craft/src/targets/gcs.ts</path>
<type>file</type>
<content>
1: import { TargetConfig, TypedTargetConfig } from '../schemas/project_config';
2: import { forEachChained } from '../utils/async';
3: import { ConfigurationError, reportError } from '../utils/errors';
4: import {
5:   BucketPath,
6:   CraftGCSClient,
7:   GCSBucketConfig,
8:   getGCSCredsFromEnv,
9:   DEFAULT_UPLOAD_METADATA,
10: } from '../utils/gcsApi';
11: import { renderTemplateSafe } from '../utils/strings';
12: import { BaseTarget } from './base';
13: import {
14:   BaseArtifactProvider,
15:   RemoteArtifact,
16: } from '../artifact_providers/base';
17: 
18: /**
19:  * Adds templating to the BucketPath interface.
20:  *
21:  * Omits required property `path` since that will be computed dynamically later. ...
result
<path>/home/byk/Code/getsentry/craft/CONTRIBUTING.md</path>
<type>file</type>
<content>
1: # Contributing
2: 
3: ## Setup
4: 
5: Craft uses pnpm for managing its dependencies. We also rely on
6: [Volta](https://volta.sh/) to manage our Node and pnpm versions. We highly
7: recommend installing Volta if you don't already have it.
8: 
9: Then, to get started, install the dependencies and get an initial build:
10: 
11: ```shell
12: pnpm install
13: pnpm build
14: ```
15: 
16: ## Logging Level
17: 
18: Logging level for `craft` can be configured via setting the `CRAFT_LOG_LEVEL`
19: environment variable or using the `--log-level` CLI flag.
20: 
21: Accepted values are: `Fatal`, `Error`, `Warn`, `Log`, `Info`, `Success`,
22: `Debug`, `Trace`, `Silent`, `Verbose`
23: 
24: ## Dry-run Mode
25: 
26: Dry-run mode can be enabled via setting the `CRAFT_DRY_RUN` environment variable
27: to any truthy value (any value other than `undefined`, `null`, `""`, `0`,
28: `false`, and `no`). ...
result
<path>/home/byk/Code/getsentry/craft/AGENTS.md</path>
<type>file</type>
<content>
1: # AGENTS.md
2: 
3: This file provides guidance for AI coding assistants working with the Craft codebase.
4: 
5: ## Package Management
6: 
7: - **Always use `pnpm`** for package management. Never use `npm` or `yarn`.
8: - Node.js version is managed by [Volta](https://volta.sh/) (currently v22.12.0).
9: - Install dependencies with `pnpm install --frozen-lockfile`.
10: 
11: ## Development Commands
12: 
13: | Command      | Description                                 |
14: | ------------ | ------------------------------------------- |
15: | `pnpm build` | Build the project (outputs to `dist/craft`) |
16: | `pnpm test`  | Run tests                                   |
17: | `pnpm lint`  | Run ESLint                                  |
18: | `pnpm fix`   | Auto-fix lint issues                        |
19: 
20: To manually test changes:
21: 
22: ```bash
23: pnpm build && ./dist/craft
24: ```
25: 
26: ## Code ...
gateway · 2026-07-15 10:03:27

Now let me look at a test that constructs a target with a mocked artifact provider (gcs/docker test), how targets get constructed in the publish command, the docs targets file, and how prefixed tags might work. Let me also check the docs directory structure.

anthropic/claude-opus-4.8 · 2026-07-15 10:03:27 · ~86 tokens
result
<path>/home/byk/Code/getsentry/craft/src/targets/__tests__/docker.test.ts</path>
<type>file</type>
<content>
1: import { vi, type Mocked } from 'vitest';
2: import * as fs from 'node:fs';
3: import * as os from 'node:os';
4: 
5: import {
6:   DockerTarget,
7:   extractRegistry,
8:   registryToEnvPrefix,
9:   normalizeImageRef,
10:   isGoogleCloudRegistry,
11:   hasGcloudCredentials,
12: } from '../docker';
13: import { NoneArtifactProvider } from '../../artifact_providers/none';
14: import * as system from '../../utils/system';
15: 
16: vi.mock('../../utils/system', async importOriginal => {
17:   const actual = await importOriginal<typeof system>();
18:   return {
19:     ...actual,
20:     checkExecutableIsPresent: vi.fn(),
21:     spawnProcess: vi.fn().mockResolvedValue(Buffer.from('')),
22:   };
23: });
24: 
25: vi.mock('node:fs');
26: vi.mock('node:os');
27: 
28: describe('normalizeImageRef', () => {
29:   it('normalizes string source to object with image property', () => {
30:   ...
result
total 56
drwxrwxr-x 1 byk byk   164 Apr 22 20:08 .
drwxrwxr-x 1 byk byk     8 May  5 11:45 ..
-rw-r--r-- 1 byk byk 18005 Apr 22 20:08 configuration.md
-rw-rw-r-- 1 byk byk  3081 Jan 13  2026 contributing.md
-rw-r--r-- 1 byk byk  9787 Apr 21 15:55 getting-started.md
-rw-r--r-- 1 byk byk 13575 Mar 16 09:32 github-actions.md
-rw-r--r-- 1 byk byk  2037 Mar  3 10:49 index.mdx
drwxrwxr-x 1 byk byk   434 Mar 25 17:30 targets
---GREP getTargetByName usage---
21:import { getAllTargetNames, getTargetByName, SpecialTarget } from '../targets';
212: * @param artifactProvider Artifact provider instance
216:  artifactProvider: BaseArtifactProvider,
219:  const artifacts = await artifactProvider.listArtifactsForRevision(revision);
244:    const artifactsConfig = config?.artifactProvider?.config?.artifacts;
262:  artifactProvider: BaseArtifactProvider,
268:    const targetClass = getTargetByName(targetConfig.name);
277:      const target = new targetClass(
279:        artifactProvider,
301: * @param ...
result
Found 14 matches
/home/byk/Code/getsentry/craft/src/commands/publish.ts:
  Line 16:   expandWorkspaceTargets,

  Line 265:   const githubRepo = await getGlobalGitHubConfig();

  Line 277:       const target = new targetClass(

  Line 280:         githubRepo,

  Line 672:   let targetConfigList = await expandWorkspaceTargets(config.targets || []);


/home/byk/Code/getsentry/craft/src/commands/targets.ts:
  Line 1: import { getConfiguration, expandWorkspaceTargets } from '../config';

  Line 13:   definedTargets = await expandWorkspaceTargets(definedTargets);


/home/byk/Code/getsentry/craft/src/commands/__tests__/targets.test.ts:
  Line 15:   expandWorkspaceTargets: vi.fn(),

  Line 22: import { getConfiguration, expandWorkspaceTargets } from '../../config';

  Line 27:   const mockedExpandWorkspaceTargets = expandWorkspaceTargets as Mock;

  Line 55:   test('lists expanded workspace targets', async () => {

  Line 61:     const expandedTargets = [

  Line 68:     ...
gateway · 2026-07-15 10:04:26

Now let me read the target construction in publish.ts, the docs/targets directory, and a docs target file (gh-pages), plus check the docker.test.ts for the target-construction/publish pattern with mocked provider.

anthropic/claude-opus-4.8 · 2026-07-15 10:04:26 · ~71 tokens
result
<path>/home/byk/Code/getsentry/craft/src/commands/publish.ts</path>
<type>file</type>
<content>
255:       logger.warn('No artifacts found for the revision.');
256:     }
257:   }
258: }
259: 
260: async function getTargetList(
261:   targetConfigList: TargetConfig[],
262:   artifactProvider: BaseArtifactProvider,
263: ): Promise<BaseTarget[]> {
264:   logger.trace('Initializing targets');
265:   const githubRepo = await getGlobalGitHubConfig();
266:   const targetList: BaseTarget[] = [];
267:   for (const targetConfig of targetConfigList) {
268:     const targetClass = getTargetByName(targetConfig.name);
269:     const targetDescriptor = BaseTarget.getId(targetConfig);
270:     if (!targetClass) {
271:       logger.warn(`Target implementation for "${targetDescriptor}" not found.`);
272:       continue;
273:     }
274:     try {
275:       logger.debug(`Creating target ${targetDescriptor}`);
276:       logger.trace(targetConfig);
277:       const target = new targetClass(
278:         ...
result
total 96
drwxrwxr-x 1 byk byk  434 Mar 25 17:30 .
drwxrwxr-x 1 byk byk  164 Apr 22 20:08 ..
-rw-r--r-- 1 byk byk 2635 Jan 30 23:59 aws-lambda-layer.md
-rw-r--r-- 1 byk byk 1727 Jan 30 23:59 brew.md
-rw-rw-r-- 1 byk byk  715 Jan 13  2026 cocoapods.md
-rw-rw-r-- 1 byk byk 1099 Jan 13  2026 commit-on-git-repository.md
-rw-rw-r-- 1 byk byk  848 Jan 13  2026 crates.md
-rw-r--r-- 1 byk byk 4114 Jan 30 23:59 docker.md
-rw-rw-r-- 1 byk byk 1063 Jan 13  2026 gcs.md
-rw-rw-r-- 1 byk byk  440 Jan 13  2026 gem.md
-rw-rw-r-- 1 byk byk  954 Jan 13  2026 gh-pages.md
-rw-rw-r-- 1 byk byk 1465 Jan 13  2026 github.md
-rw-rw-r-- 1 byk byk  512 Jan 13  2026 hex.md
-rw-rw-r-- 1 byk byk 2351 Jan 13  2026 index.md
-rw-rw-r-- 1 byk byk 2085 Jan 13  2026 maven.md
-rw-r--r-- 1 byk byk 5297 Mar 16 09:32 npm.md
-rw-rw-r-- 1 byk byk  575 Jan 13  2026 nuget.md
-rw-rw-r-- 1 byk byk  909 Jan 13  2026 powershell.md
-rw-rw-r-- 1 byk byk 1315 Jan 13  2026 pub-dev.md
-rw-rw-r-- 1 byk byk  989 Jan 13  2026 ...
result
Found 100 matches (more matches available)
/home/byk/Code/getsentry/craft/src/targets/__tests__/powershell.test.ts:
  Line 3: import { NoneArtifactProvider } from '../../artifact_providers/none';

  Line 18:     new NoneArtifactProvider(),

  Line 71:       await target.publish('', '');

  Line 101:       const noPackageFound = await target.publish('version', 'revision');

  Line 120:       await target.publish('1.0', 'sha');

  Line 130:       await target.publish('1.0', 'sha');


/home/byk/Code/getsentry/craft/src/targets/__tests__/mavenDiskIo.test.ts:
  Line 2: import { NoneArtifactProvider } from '../../artifact_providers/none';

  Line 40:   return new MavenTarget(mergedConfig, new NoneArtifactProvider());


/home/byk/Code/getsentry/craft/src/targets/__tests__/pubDev.test.ts:
  Line 8: import { NoneArtifactProvider } from '../../artifact_providers/none';

  Line 74:     new NoneArtifactProvider(),

  Line 157:     await target.publish('1.0.0', revision);

  Line 193:     await ...
gateway · 2026-07-15 10:05:25

Now let me look at the gcs test (best analog β€” mocks the API client and asserts on publish with a mocked provider), the docs gh-pages.md and index.md, and check for prefixed-tag support in the codebase.

anthropic/claude-opus-4.8 · 2026-07-15 10:05:25 · ~68 tokens
result
<path>/home/byk/Code/getsentry/craft/src/targets/__tests__/registry.test.ts</path>
<type>file</type>
<content>
1: import { vi, type Mock, type MockedFunction } from 'vitest';
2: vi.mock('../../utils/githubApi.ts');
3: import { getGitHubClient } from '../../utils/githubApi';
4: import { RegistryConfig, RegistryTarget } from '../registry';
5: import { NoneArtifactProvider } from '../../artifact_providers/none';
6: import { RegistryPackageType } from '../../utils/registry';
7: 
8: describe('getUpdatedManifest', () => {
9:   let mockClient: Mock;
10: 
11:   beforeEach(() => {
12:     vi.resetAllMocks();
13:     mockClient = vi.fn();
14:     (getGitHubClient as MockedFunction<typeof getGitHubClient>)
15:       // @ts-ignore -- we only need to mock a subset
16:       .mockReturnValue({ graphql: mockClient });
17:   });
18: 
19:   const target = new RegistryTarget(
20:     { name: 'pypi' },
21:     new NoneArtifactProvider(),
22:     { owner: 'testSourceOwner', repo: 'testSourceRepo' },
23:  ...
result
<path>/home/byk/Code/getsentry/craft/docs/src/content/docs/targets/gh-pages.md</path>
<type>file</type>
<content>
1: ---
2: title: GitHub Pages
3: description: Deploy static sites to GitHub Pages
4: ---
5: 
6: Extracts an archive with static assets and pushes them to a git branch for GitHub Pages deployment.
7: 
8: :::caution
9: The destination branch will be completely overwritten by the archive contents.
10: :::
11: 
12: ## Configuration
13: 
14: | Option | Description |
15: |--------|-------------|
16: | `branch` | Branch to push to. Default: `gh-pages` |
17: | `githubOwner` | GitHub project owner. Default: from global config |
18: | `githubRepo` | GitHub project name. Default: from global config |
19: 
20: ## Default Behavior
21: 
22: By default, this target:
23: 1. Looks for an artifact named `gh-pages.zip`
24: 2. Extracts its contents
25: 3. ...
result
<path>/home/byk/Code/getsentry/craft/docs/src/content/docs/targets/index.md</path>
<type>file</type>
<content>
1: ---
2: title: Targets Overview
3: description: Overview of all available release targets
4: ---
5: 
6: Targets define where Craft publishes your release artifacts. Configure them in `.craft.yml` under the `targets` key.
7: 
8: ## Available Targets
9: 
10: | Target | Description |
11: |--------|-------------|
12: | [GitHub](./github/) | Create GitHub releases and tags |
13: | [NPM](./npm/) | Publish to NPM registry |
14: | [PyPI](./pypi/) | Publish to Python Package Index |
15: | [Crates](./crates/) | Publish Rust crates |
16: | [NuGet](./nuget/) | Publish .NET packages |
17: | [Docker](./docker/) | Tag and push Docker images |
18: | [Homebrew](./brew/) | Update Homebrew formulas |
19: | [GCS](./gcs/) | Upload to Google Cloud Storage |
20: | [GitHub Pages](./gh-pages/) | Deploy static sites |
21: | [CocoaPods](./cocoapods/) | Publish iOS/macOS pods |
22: | [Ruby ...
result
<path>/home/byk/Code/getsentry/craft/docs/src/content/docs/targets/gcs.md</path>
<type>file</type>
<content>
1: ---
2: title: Google Cloud Storage
3: description: Upload artifacts to GCS buckets
4: ---
5: 
6: Uploads artifacts to a bucket in Google Cloud Storage.
7: 
8: ## Configuration
9: 
10: | Option | Description |
11: |--------|-------------|
12: | `bucket` | GCS bucket name |
13: | `paths` | List of path objects |
14: | `paths.path` | Bucket path with `{{ version }}` and/or `{{ revision }}` templates |
15: | `paths.metadata` | Optional metadata for uploaded files |
16: 
17: ## Environment Variables
18: 
19: | Name | Description |
20: |------|-------------|
21: | `CRAFT_GCS_TARGET_CREDS_PATH` | Path to Google Cloud credentials file |
22: | `CRAFT_GCS_TARGET_CREDS_JSON` | Service account file contents as JSON string |
23: 
24: If both are set, `CRAFT_GCS_TARGET_CREDS_JSON` takes precedence. ...
gateway · 2026-07-15 10:05:54

Now let me check for prefixed-tag support (existing patterns), the DEFAULT_RELEASE_CONFIG duplication in configuration.md, and how the artifact provider's download/extract handles directories (extractZipArchive, withTempDir). Let me also confirm the gcs test file existence and check config.test.ts.

anthropic/claude-opus-4.8 · 2026-07-15 10:05:54 · ~100 tokens
result
src/__tests__/config.test.ts:20:      targets: [{ name: 'npm' }, { name: 'github', tagPrefix: 'v' }],
src/commands/__tests__/prepare.test.ts:225:        e: 'Invalid version or version part specified: "v2.3.3". Removing the "v" prefix will likely fix the issue',
src/commands/prepare.ts:226:  const branchName = `${branchPrefix}/${newVersion}`;
src/commands/publish.ts:601:    branchName = `${branchPrefix}/${newVersion}`;
src/targets/__tests__/symbolCollector.test.ts:129:        "bundleIdPrefix-version",
src/targets/__tests__/github.test.ts:458:        tagPrefix: '',
src/targets/symbolCollector.ts:76:    const bundleId = this.symbolCollectorConfig.bundleIdPrefix + version;
src/targets/upm.ts:57:      tagPrefix: config.tagPrefix,
src/targets/npm.ts:610:        (_m, prefix: string) => `${prefix}"version": "${newVersion}"`,
src/targets/github.ts:46:  tagPrefix?: string;
src/targets/github.ts:58:  /** Prefix that will be used to generate tag name */
src/targets/github.ts:59:  tagPrefix: ...
result
Found 91 matches
/home/byk/Code/getsentry/craft/docs/src/content/docs/index.mdx:
  Line 21: ## Features

  Line 25:     Prepare and publish releases with a single command. Craft handles version bumping, changelog management, and artifact publishing.

  Line 34:     Automatic changelog generation using conventional commits or manual changelog policies.

  Line 59: Craft enforces a specific workflow for managing release branches, changelogs, and artifact publishing. It:

  Line 62: - Validates changelog entries


/home/byk/Code/getsentry/craft/docs/src/content/docs/github-actions.md:
  Line 3: description: Automate releases and changelog previews with Craft GitHub Actions

  Line 6: Craft provides GitHub Actions for automating releases and previewing changelog entries in pull requests.

  Line 62: | `changelog`    | The changelog for this release               |

  Line 121: The changelog preview workflow posts a comment on pull requests showing how they will appear in the changelog. ...
gateway · 2026-07-15 10:06:23

I have found the tagPrefix support in the github target. Let me read the github target's config parsing and versionToTag, plus the configuration.md DEFAULT_RELEASE_CONFIG section, and the config.test.ts to complete the picture on prefixed-tag support and config flow.

anthropic/claude-opus-4.8 · 2026-07-15 10:06:23 · ~91 tokens
result
<path>/home/byk/Code/getsentry/craft/src/targets/github.ts</path>
<type>file</type>
<content>
40: export const GITHUB_RELEASE_BODY_MAX = 125_000;
41: 
42: /** GitHub target configuration fields */
43: interface GitHubConfigFields extends Record<string, unknown> {
44:   owner?: string;
45:   repo?: string;
46:   tagPrefix?: string;
47:   tagOnly?: boolean;
48:   previewReleases?: boolean;
49:   floatingTags?: string[];
50: }
51: 
52: /**
53:  * Configuration options for the GitHub target.
54:  */
55: export interface GitHubTargetConfig extends GitHubGlobalConfig {
56:   /** Path to changelog inside the repository */
57:   changelog: string;
58:   /** Prefix that will be used to generate tag name */
59:   tagPrefix: string;
60:   /** Mark release as pre-release, if the version looks like a non-public release */
61:   previewReleases: boolean;
62:   /** Do not create a full GitHub release, only push a git tag */
63:   tagOnly: boolean;
64:   /**
65:    * Floating tags to create/update ...
result
<path>/home/byk/Code/getsentry/craft/src/__tests__/config.test.ts</path>
<type>file</type>
<content>
1: import { describe, test, expect } from 'vitest';
2: /**
3:  * Tests of our ability to read craft config files. (This is NOT general test
4:  * configuration).
5:  */
6: 
7: import { validateConfiguration } from '../config';
8: import { CraftProjectConfigSchema } from '../schemas/project_config';
9: 
10: describe('validateConfiguration', () => {
11:   test('parses minimal configuration', () => {
12:     const data = { github: { owner: 'getsentry', repo: 'craft' } };
13: 
14:     expect(validateConfiguration(data)).toEqual(data);
15:   });
16: 
17:   test('parses configuration with targets', () => {
18:     const data = {
19:       github: { owner: 'getsentry', repo: 'craft' },
20:       targets: [{ name: 'npm' }, { name: 'github', tagPrefix: 'v' }],
21:     };
22: 
23:     expect(validateConfiguration(data)).toEqual(data);
24:   });
25: 
26:   test('parses configuration with ...
result
<path>/home/byk/Code/getsentry/craft/docs/src/content/docs/configuration.md</path>
<type>file</type>
<content>
225: 
226: #### Default Configuration
227: 
228: If `.github/release.yml` doesn't exist, Craft uses these defaults based on [Conventional Commits](https://www.conventionalcommits.org/):
229: 
230: ```yaml
231: changelog:
232:   exclude:
233:     labels:
234:       - skip-changelog
235:   categories:
236:     - title: Breaking Changes πŸ› 
237:       commit_patterns:
238:         - "^(?<type>\\w+(?:\\((?<scope>[^)]+)\\))?!:\\s*)"
239:       semver: major
240:     - title: Security πŸ”’
241:       commit_patterns:
242:         - "^(?<type>security(?:\\((?<scope>[^)]+)\\))?!?:\\s*)"
243:       semver: patch
244:     - title: New Features ✨
245:       commit_patterns:
246:         - "^(?<type>feat(?:\\((?<scope>[^)]+)\\))?!?:\\s*)"
247:       semver: minor
248:     - title: Bug Fixes πŸ›
249:       commit_patterns:
250:         - "^(?<type>fix(?:\\((?<scope>[^)]+)\\))?!?:\\s*)"
251:   ...
gateway · 2026-07-15 10:06:37

I have gathered everything needed. Let me do one final check on versionToTag (the prefix-application util) and confirm the docker test's target-construction + publish assertion pattern for a representative snippet.

anthropic/claude-opus-4.8 · 2026-07-15 10:06:37 · ~72 tokens
result
Found 1 matches
/home/byk/Code/getsentry/craft/src/utils/version.ts:
  Line 135: export function versionToTag(version: string, tagPrefix?: string): string {
result
<path>/home/byk/Code/getsentry/craft/src/targets/__tests__/docker.test.ts</path>
<type>file</type>
<content>
355: 
356:   describe('target credential resolution', () => {
357:     describe('Mode A: explicit usernameVar/passwordVar', () => {
358:       it('uses explicit env vars when both are specified', () => {
359:         process.env.MY_USER = 'custom-user';
360:         process.env.MY_PASS = 'custom-pass';
361: 
362:         const target = new DockerTarget(
363:           {
364:             name: 'docker',
365:             source: 'ghcr.io/org/image',
366:             target: 'ghcr.io/org/image',
367:             usernameVar: 'MY_USER',
368:             passwordVar: 'MY_PASS',
369:           },
370:           new NoneArtifactProvider(),
371:         );
372: 
373:         expect(target.dockerConfig.target.credentials!.username).toBe(
374:           'custom-user',
375:         );
376:         expect(target.dockerConfig.target.credentials!.password).toBe(
377:           ...
gateway · 2026-07-15 10:08:12

I now have a comprehensive picture. Here is my full report.


Craft codebase exploration report

1. How targets are TESTED

Location: src/targets/__tests__/*.test.ts (one file per target). There is no src/targets/__tests__/ with a nested dir β€” tests sit directly in src/targets/__tests__/. Broader tests live in src/__tests__/.

The core testing pattern

Targets are unit-tested by constructing the target directly with a fake artifact provider β€” almost always NoneArtifactProvider from src/artifact_providers/none.ts. External command execution (spawnProcess) and external clients (getGitHubClient, GCS client) are mocked with Vitest.

Constructing the target under test (src/targets/__tests__/docker.test.ts:362-371):

const target = new DockerTarget(
  {
    name: 'docker',
    source: 'ghcr.io/org/image',
    target: 'ghcr.io/org/image',
    usernameVar: 'MY_USER',
    passwordVar: 'MY_PASS',
  },
  new NoneArtifactProvider(),   // <-- fake artifact provider
);
expect(target.dockerConfig.target.credentials!.username).toBe('custom-user');

The constructor signature everywhere is (config, artifactProvider, githubRepo?). When a target needs the third githubRepo arg, tests pass a literal (src/targets/__tests__/registry.test.ts:19-23):

const target = new RegistryTarget(
  { name: 'pypi' },
  new NoneArtifactProvider(),
  { owner: 'testSourceOwner', repo: 'testSourceRepo' },
);

Mocking external command execution β€” spy on spawnProcess in src/utils/system.ts (src/targets/__tests__/npm.test.ts:30-52):

import * as system from '../../utils/system';

spawnProcessMock = vi
  .spyOn(system, 'spawnProcess')
  .mockImplementation(() => Promise.reject('does not exist'));
// ...
expect(spawnProcessMock).toBeCalledWith(
  'npm',
  ['info', 'sentry-xx-this-does-not-exist', 'version'],
  expect.objectContaining({}),
);

Alternatively, whole-module mock with importOriginal ...

anthropic/claude-opus-4.8 · 2026-07-15 10:08:12 · ~5.2K tokens

Distillations (7)

Gen 0 2026-07-15 10:08:33 · 207 tokens

* 🟑 (10:08) User provided file content for /home/byk/Code/getsentry/craft/src/utils/version.ts (lines 135-174). * πŸ”΄ [requested-review] (10:09) User requested review of version.ts. * πŸ”΄ [requested-tests] (10:09) User implicitly requested tests by analyzing existing test files, specifically version.ts. * 🟑 (10:10) User provided grep results for versionToTag usage in multiple files. * πŸ”΄ (10:11) …

Gen 0 2026-07-15 10:06:56 · 523 tokens

* πŸ”΄ (10:05) User stated always sets repo_url from githubRepo config. * 🟑 (10:05) User provided file content for /home/byk/Code/getsentry/craft/src/targets/__tests__/docker.test.ts (1394 lines). * πŸ”΄ [requested-review] (10:04) User requested review of docker.test.ts. * πŸ”΄ [requested-tests] (10:04) User implicitly requested tests by analyzing existing test files, specifically docker.test.ts. * πŸŸ‘β€¦

Gen 0 2026-07-15 10:06:43 · 172 tokens

* 🟑 (10:04) User provided file content for /home/byk/Code/getsentry/craft/src/targets/__tests__/docker.test.ts (1394 lines). * πŸ”΄ [requested-review] (10:04) User requested review of docker.test.ts. * πŸ”΄ [requested-tests] (10:04) User implicitly requested tests by analyzing existing test files, specifically docker.test.ts. * 🟑 (10:04) User provided directory contents for /home/byk/Code/getsentry…

Gen 0 2026-07-15 10:04:03 · 228 tokens

πŸ”΄ (10:03) User stated preference for package management: always use `pnpm`, never use `npm` or `yarn`. * πŸ”΄ [requested-tests] (10:03) User implicitly requested tests by analyzing existing test files. * πŸ”΄ [requested-review] (10:03) User requested review of relevant source files (index, base, schema, and docs). * 🟑 (10:03) User provided file content for /home/byk/Code/getsentry/craft/src/targets…

Gen 0 2026-07-15 10:03:59 · 339 tokens

* 🟑 (10:02) User requested analysis of getsentry/craft repo focusing on testing patterns, config schema, and documentation conventions. * πŸ”΄ (10:02) User listed directory contents of getsentry/craft repo. * πŸ”΄ (10:02) User listed test files in getsentry/craft repo. * πŸ”΄ (10:02) User mentioned schema files in getsentry/craft repo, specifically project_config.ts. * πŸ”΄ [requested-tests] (10:02) Use…

Gen 0 2026-07-15 10:03:56 · 474 tokens

Date: July 15, 2026 * 🟑 (10:02) User requested analysis of the getsentry/craft repo, focusing on understanding testing patterns, config schema, and documentation conventions. * πŸ”΄ (10:02) User listed directory contents of the getsentry/craft repo, including:   - Files: awsLambdaLayer.ts, base.ts, brew.ts, cocoapods.ts, commitOnGitRepository.ts, crates.ts, docker.ts, gcs.ts, gem.ts, ghPages.ts, g…

Gen 0 2026-07-15 10:03:34 · 113 tokens

Date: July 15, 2026 * πŸ”΄ (10:01) User stated needs to understand testing patterns, config schema, and documentation conventions for the getsentry/craft repo. * πŸ”΄ (10:01) User is working on adding a new `cloudflare` release target and prefixed-tag support in the getsentry/craft repo (Sentry's release automation CLI, TypeScript, Vitest).