Dashboard › craft › Distillation
ba83757b-c7c8-4343-b428-ea158e4db8f0["lore_tm_v1_TABABoYK4fMvSbWZdHMkwt5SDg4POI1ceV5XcvI7sAs","lore_tm_v1_Wu7ZjV3QFsLAaXrGqHGQtcJHi-zN89U2xUqgo67WtP8","lore_tm_v1_6Xes-y3JmDbdu4aMMYC7kx1_SefpoRIaCEdR6N_kQT8","lore_tm_v1_ybm1SNDrMld_Fi_z4-JvDgCxCnf3aSepCfBNPYWU1Yo","lore_tm_v1_FMhMw3BYvNPRsSy9f_-_iWT7uVtoh1nBptve4wg5Qpc","lore_tm_v1_vKfVkDrHXo-rklrbPUJUv-awkLpiT5k3YdhHkUVn5Fc","lore_tm_v1_vliEIYLmxYH6G3s6QNxvnkg9LFDRx5aYsb3mZg3qxX0","lore_tm_v1_8VC7AM_mR3oWSFhA6tgESb4-u9HFb-kzcKQAaWeebxA","lore_tm_v1_KYWuG_fEBU1prNnj_RrHdORZZ6O7CJBBAWqEBvr9-W8","lore_tm_v1_bAhoPLl4BcG2xvDRUC26maYkJSnHwBhddQi7NGlDCs0","lore_tm_v1_3ih3CaPwlKNFa2_PQVRS0GYkEFN7gZASDwI3ghvEtHc","lore_tm_v1_VuCy9ImkrZoDmgeDC-C5LdjEYOO4gf_sLFSwyRAvOEw","lore_tm_v1_8-iEkxG0tlFKAzVEC5OtyijjQEkir6jagICeRLELPLA","lore_tm_v1_pH15jC_cmVO79OISM6wGwHx2SHc7DNvZAv1WE0BMxSs","lore_tm_v1_OpoVVO3rb30rXSRbG3KFYOVW2bsGUCOFKbWkbVP3zJE","lore_tm_v1_rM5aaObTYdBrkCpDMiqB0GjMw2oFcO04Q8v6CAZTlt4"]
Date: Aug 27, 2026
src/utils/publishState.ts moves resumable craft publish completed-target JSON state out of the repository checkout—previously .craft-publish-<version>.json in cwd could be pre-populated by committed content or earlier CI steps to silently skip targets—to $XDG_STATE_HOME/craft/, falling back to $HOME/.local/state/craft/. In getsentry/publish Docker, HOME=/root, making this a clean workflow-writable location inaccessible to committed repository contents.src/utils/publishState.ts:getCraftStateDir() returns join(process.env.XDG_STATE_HOME, 'craft') when nonempty, otherwise join(homedir(), '.local', 'state', 'craft'); STATE_DIR_NAME = 'craft'.src/utils/publishState.ts:sanitiseForFilename() lowercases strings, replaces runs outside [a-z0-9._-] with _, and strips leading/trailing underscores. shortCwdHash() uses the first 12 hex characters of SHA-1 of absolute cwd to isolate monorepo subpaths.getPublishStateFilename(version, githubConfig, cwd = process.cwd(), workspace?) creates publish-state-<owner>-<repo>-<sha1(cwd)[:12]>-<workspace-prefix><safe-version>.json when GitHub config is resolvable; offline/non-GitHub fallback is publish-state-<sha256(cwd)[:16]>-<workspace-prefix><safe-version>.json. Workspace prefix is lossless Base64URL encoding: workspace-${Buffer.from(workspace).toString('base64url')}-, preventing same-repository/same-version release workspaces from sharing state. getPublishStatePath() joins this filename to getCraftStateDir().src/commands/__tests__/publish.test.ts:runPostReleaseCommand tests default execution of /bin/bash scripts/post-release.sh '' 2.3.4 only when executable; custom command python ./increase_version.py "argument 1" executes as python ['./increase_version.py', 'argument 1', '', '2.3.4']. Subprocess environment includes PATH, HOME, USER, GIT_COMMITTER_NAME, GIT_AUTHOR_NAME, EMAIL, all GITHUB_*/RUNNER_*, and CRAFT_RELEASED_VERSION.LD_PRELOAD, AWS_SECRET_ACCESS_KEY, and SECRET_TOKEN—must not reach the subprocess; GITHUB_RUN_ID, GITHUB_REPOSITORY, and RUNNER_OS are forwarded by prefix, while NPM_TOKEN and DOCKER_PASSWORD are excluded.getPublishStateGitHubConfig() uses controller checkout identity getsentry/toolkit for publish-state identity even if resolved workspace GitHub config is {owner: 'release-owner', repo: 'release-repo', projectPath: 'packages/cli'}; without controller identity it retains resolved config; malformed controller repo value getsentry/toolkit/extra throws referencing CRAFT_PUBLISH_STATE_GITHUB_REPO.--workspace --dry-run must never be mistaken for a workspace name.src/utils/helpers.ts:extractWorkspaceSelection(argv, env = process.env) pre-parses --workspace foo/--workspace=foo and CRAFT_WORKSPACE with Node parseArgs(... tokens: true) because yargs command builders may load configuration before middleware. It selects the last valid workspace occurrence; a bare/empty last option clears an earlier CLI value and falls back to CRAFT_WORKSPACE; inline option-like values such as --workspace=-foo are accepted, but non-inline values beginning - are rejected.src/index.ts calls sanitizeDynamicLinkerEnv() before all other CLI work to prevent LD_PRELOAD, DYLD_*, and related dynamic-linker variables leaking into spawned subprocesses. It extracts workspace selection and calls setActiveWorkspace(extractWorkspaceSelection(argv)) before yargs parsing, since publish builders derive target choices from config and cannot wait for middleware.--workspace is a string option described as selecting a named release-unit workspace, required when configuration defines workspaces, with CRAFT_WORKSPACE environment support. --dry-run means no file writes, commits, pushes, or API mutations; --no-input defaults to isCI; yargs uses boolean-negation: false, strict commands, and setGlobals middleware.minVersion side.src/config.ts:applyWorkspaceSelection() returns config unchanged only when no workspaces and no active selection; errors if a workspace is selected but config lacks workspaces; errors if workspaces exist with no explicit selection, listing available names and requiring --workspace <name> or CRAFT_WORKSPACE; and gates selected workspaces on minVersion >= WORKSPACES_MIN_VERSION before resolveWorkspaceConfig(config, _activeWorkspaceName).getConfiguration() and loadConfigurationFromString() validate YAML with CraftProjectConfigSchema, call checkMinimalConfigVersion(parsed), then cache applyWorkspaceSelection(parsed). loadConfigurationFromString() is used by --config-from.checkMinimalConfigVersion() treats a local prerelease Craft build such as 2.29.0-dev.0 as satisfying minVersion up to release 2.29.0 for dogfooding. It strips prerelease and build metadata only from currentVersion when prerelease, otherwise strips build metadata; it always strips only build metadata from configured minVersion. Build metadata such as 2.29.0+linux is ignored for precedence because the comparison helper rejects build metadata.workflow_dispatch for manual recovery.publish-workspace-acceptance/.github/workflows/ contains auto-approve.yml, ci-poller.yml, cocoapods-keepalive.yml, publish.yml, and test.yml.ci-poller.yml synchronizes repository variable CI_POLLER_HAS_PENDING after CI checks: it sets "false" when no ci-pending issues remain and "true" otherwise, including when a manual workflow_dispatch bypassed a false gate. It obtains an app token with actions/create-github-app-token@v3, vars.CI_POLLER_APP_CLIENT_ID, and secrets.CI_POLLER_APP_PRIVATE_KEY.ci-poller.yml self-dispatches ci-poller.yml while pending issues remain, using GH_TOKEN: ${{ steps.token.outputs.token }} and input attempt; it increments the attempt count, caps at 60 attempts (approximately 30 minutes), relies on cron fallback after the cap, and cites GitHub */5 cron drift to 30–40 minutes under load versus roughly 30–60 seconds between self-dispatch checks. Its concurrency group limits accumulation to 1 running plus 1 queued run.publish-workspace-acceptance/src/publish/update-issue.js gets GitHub Actions context, creates Octokit with github.getOctokit(getGitHubToken()), parses process.env.PUBLISH_ARGS as JSON, and calls updateIssue({ context, octokit, inputs }).