Dashboard › craft › Session 1VJ3OVRZQ0Cr
1VJ3OVRZQ0CrLAVdSI'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:
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.
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.
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.
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.
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.
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.
I'll explore the craft repo thoroughly. Let me start by mapping out the structure and finding relevant files in parallel.
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 ...
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...
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.
<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: ...
<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 ...
<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 { ...
<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. ...
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.
<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(() => ...
<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. ...
<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`). ...
<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 ...
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.
<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: ...
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 ...
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: ...
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.
<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: ...
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 ...
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 ...
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.
<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: ...
<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. ...
<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 ...
<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. ...
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.
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: ...
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. ...
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.
<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 ...
<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 ...
<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: ...
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.
Found 1 matches
/home/byk/Code/getsentry/craft/src/utils/version.ts:
Line 135: export function versionToTag(version: string, tagPrefix?: string): string {
<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: ...
I now have a comprehensive picture. Here is my full report.
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__/.
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 ...
* π‘ (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) β¦
* π΄ (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. * π‘β¦
* π‘ (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β¦
π΄ (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β¦
* π‘ (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β¦
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β¦
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).