Dashboard › craft › Distillation
a3b72662-92d4-4fdb-9188-554ae29b24d2["lore_tm_v1_EMLf9ku8dhc2TLhCMYT88XnPlpYkkxVXdyOraqQY5zg","lore_tm_v1_Ba05tRHnqIGZZWxhy6uLtODwowhHgOrit7FXAHts7jg","lore_tm_v1_6gYh8_xVp5AhwPsRZRnrsWkbEke3Cjwn1tIZ5biYETE","lore_tm_v1_yyaLueKqjcLp0abRSkeGa_YbzDuO9lAtXD2sx5aVf_g"]
Date: Sep 8, 2026
src/__tests__/action.test.ts provides GitHub Action integration-style tests using temporary directories (craft-action-test-), stub executables for craft, git, and gh, and spawnSync('bash', ['-e', '-c', getActionStep(stepName).run!]); afterEach() removes all temporary directories recursively.craft exits with status 1 if CRAFT_WORKSPACE is non-empty, otherwise logs arguments to CRAFT_CALLS and emits ["github"] for craft targets; git logs arguments to GIT_CALLS; gh issue list emits [], while gh issue create records its --title argument in GH_TITLES and returns https://github.com/getsentry/publish/issues/1.runRequestPublish(workspace, environment) executes the Request publish action step with WORKSPACE, GITHUB_REPOSITORY='getsentry/toolkit', PUBLISH_REPO='getsentry/publish', RESOLVED_VERSION='1.2.3', RELEASE_BRANCH='release/1.2.3', RELEASE_PREVIOUS_TAG='1.2.2', RELEASE_SHA='abc123', TARGETS=' - [ ] github', GITHUB_ACTOR='byk', MERGE_TARGET='(default)', and empty CHANGELOG_FILE/SUBDIRECTORY.runActionStep() executes named action steps with WORKSPACE, PATH_INPUT (default '.'), LC_ALL (default 'C'), empty CRAFT_CONFIG_FROM_MERGE_TARGET, MERGE_TARGET, and VERSION, plus temp-file-backed CRAFT_CALLS, GIT_CALLS, and GITHUB_OUTPUT.Validate workspace; its PATH_INPUT environment value is '${{ inputs.path }}'; Craft Prepare and Read Craft Targets each receive WORKSPACE='${{ inputs.workspace }}'.'cli\tnext'), Unicode format character ('cli\u202enext'), Unicode line separator ('cli\u2028next'), Unicode paragraph separator ('cli\u2029next'), and non-ASCII 'cli-é' before any git or craft side effect; the non-ASCII rejection is also tested under LC_ALL='en_US.utf8'.'', 'cli-v2', 'packages/cli', and 'packages/CLI'.PATH_INPUT values '../outside', '/tmp', './packages/cli', 'packages//cli', and 'packages/../cli' before git or craft side effects; it also rejects simultaneous non-root PATH_INPUT='packages/cli' and WORKSPACE='cli'.'cli\nnext', 'packages/*'), unsafe names ('.', '..', '__proto__', '-foo', '--config'), and unsafe paths ('./packages/cli', 'packages//cli', 'packages/./cli', 'packages/../cli', 'packages/__proto__/cli', 'packages/-cli'), all before git or craft side effects.Request publish title behavior is tested: root workspace produces publish: getsentry/toolkit@1.2.3; workspace 'packages/cli' produces publish: getsentry/toolkit/packages/cli@1.2.3, using the full workspace path.CRAFT_WORKSPACE: with parent process.env.CRAFT_WORKSPACE='packages/cli' and action WORKSPACE='', both Craft Prepare and Read Craft Targets succeed and invoke exactly prepare then targets without a workspace environment variable.src/utils/publishState.ts addresses a security vulnerability in legacy repository-local .craft-publish-<version>.json state: a committed file or earlier CI step could pre-populate completed targets and cause craft publish to silently skip them. State now resides outside the checkout under $XDG_STATE_HOME/craft/, falling back to $HOME/.local/state/craft/; getsentry/publish Docker uses HOME=/root, providing a clean workflow-writable location unreachable by committed repository contents.getCraftStateDir() returns join(process.env.XDG_STATE_HOME, 'craft') only when XDG_STATE_HOME is non-empty; otherwise it returns join(homedir(), '.local', 'state', 'craft').sanitiseForFilename() lowercases and replaces/collapses characters outside [a-z0-9._-] with _, trimming leading/trailing underscores. encodeVersionForFilename() preserves a version only when its sanitized result exactly equals the original; otherwise it uses lossless version-${Buffer.from(version).toString('base64url')} encoding.getPublishStateFilename(version, githubConfig, cwd = process.cwd(), workspace?) uses a 12-character SHA-1 hexadecimal hash of cwd for GitHub-configured state: publish-state-<owner>-<repo>-<sha1(cwd)[:12]>-<workspace-prefix?><version>.json. Without GitHub config, it uses publish-state-<sha256(cwd)[:16]>-<workspace-prefix?><version>.json, retaining checkout isolation for offline/non-GitHub contexts.workspace-${Buffer.from(workspace).toString('base64url')}-; no workspace retains the pre-workspace filename form.getPublishStatePath() joins getCraftStateDir() and getPublishStateFilename(); tests verify it never locates state inside the untrusted repository cwd, both with and without GitHub configuration.src/utils/__tests__/publishState.test.ts verifies: default state dir is $HOME/.local/state/craft; XDG_STATE_HOME='/var/lib/ci-state' yields /var/lib/ci-state/craft; empty XDG_STATE_HOME falls back to HOME; configured filenames include owner, repo, 12-hex cwd hash, and version; different monorepo cwd paths produce different filenames; same inputs produce stable filenames.'cli' and 'mcp' end respectively in -workspace-Y2xp-1.2.3.json and -workspace-bWNw-1.2.3.json, and distinct workspace names ['CLI', 'cli', 'cli/workspace', 'cli_workspace'] do not collide.'4.2.6+sentry1' and '4.2.6+Sentry1' do not collide despite sanitization/case differences, producing lossless suffixes -version-NC4yLjYrc2VudHJ5MQ.json and -version-NC4yLjYrU2VudHJ5MQ.json; they also verify owner/repo sanitization for { owner: 'Weird Owner', repo: 'Re po!' } and version '1.2.3+build/hack$', with no /, $, +, or ! in the resulting filename.