Dashboard › craft › Distillation
f16b5612-c0d2-4958-b768-bc75f6f5f180["lore_tm_v1_NUnVZM0vc9CpouHR39K-dVh-6lB-wiCPyPJUqJ7seQA","lore_tm_v1_i0evPnOFbCTAL55U_c2sxqJy9fMoDWxZ_z1qibJV_yc","lore_tm_v1_sNMcj0gmn_CLk_yMv0s9cLhYFZct1BfZf02rvF2soVg","lore_tm_v1_7v2LDXm6liWiOPtM_aZTLGtlW3xynFWPHqsMd-UWBlM"]
Date: Sep 8, 2026
posix: true option always resolves returned paths to POSIX style, using / separators.absolute: true option always receives absolute paths for matched files.absolute: false option always receives relative paths for matched files..github/workflows/release.yml defines both workflow_dispatch and reusable workflow_call release workflows. Both expose optional workspace inputs described as βNamed Craft release workspace to prepare and publishβ; the external reusable workflow passes workspace: ${{ inputs.workspace }} to getsentry/craft@v2..github/workflows/release.yml external workflow_call inputs include version, force (default 'false'), merge_target, blocker_label (default 'release-blocker'), publish_repo, git_user_name, git_user_email, path (default '.'), workspace, and craft_config_from_merge_target (default 'false'). The local dogfooding action (uses: ./) receives github.event.inputs.workspace.bin/craft, bin/git, and bin/gh stubs plus craft-calls, git-calls, gh-titles, and github-output files. The craft stub exits 1 if inherited CRAFT_WORKSPACE is nonempty, logs arguments to $CRAFT_CALLS, and prints ["github"] for targets; gh records issue create --title values and returns https://github.com/getsentry/publish/issues/1.Validate workspace receives PATH_INPUT: '${{ inputs.path }}', while Craft Prepare and Read Craft Targets receive WORKSPACE: '${{ inputs.workspace }}'.cli\tnext), bidi format (cli\u202enext), line separator (cli\u2028next), paragraph separator (cli\u2029next), non-ASCII (cli-Γ©, including en_US.utf8 locale), newline (cli\nnext), glob (packages/*), unsafe names (., .., __proto__, -foo, --config), and unsafe paths (./packages/cli, packages//cli, packages/./cli, packages/../cli, packages/__proto__/cli, packages/-cli); each rejection occurs before any git or craft side effect.'', cli-v2, packages/cli, and packages/CLI; reject unsafe checkout path inputs ../outside, /tmp, ./packages/cli, packages//cli, and packages/../cli; and reject a nonempty workspace together with path: 'packages/cli' before side effects.publish: getsentry/toolkit@1.2.3 and workspace packages/cli title publish: getsentry/toolkit/packages/cli@1.2.3; this preserves the complete concrete workspace path in titles.CRAFT_WORKSPACE='packages/cli': Craft Prepare and Read Craft Targets succeed and invoke exactly prepare then targets.src/__tests__/config.test.ts changes workspace configuration tests to remove workspace github.projectPath; workspace GitHub configuration may override owner and/or repo, shallow-merging with base GitHub configuration only when complete.src/__tests__/config.test.ts adds validation coverage: allows multi-segment workspace key cli/v2; rejects . and .. with Workspace names cannot be "." or "..".; rejects YAML key __proto__ with Workspace name "__proto__" is not supported.; rejects workspace github.projectPath with Workspace github.projectPath is not supported.; rejects top-level github.projectPath when nonempty workspaces are configured with Workspace configurations cannot use github.projectPath.; permits top-level github.projectPath with workspaces: {}.src/__tests__/config.test.ts adds getWorkspaceNames() tests: with configured cli and mcp, returns ['cli', 'mcp'] without selecting a workspace; throws when workspace config has minVersion: 2.14.0, requiring minVersion >= ${WORKSPACES_MIN_VERSION}.src/__tests__/config.test.ts adds workspace-glob behavior tests: packages/* expands to concrete directories packages/cli and packages/mcp; remote config loaded with repository root expands relative to that root even when current directory is nested; supported globs include negated character classes packages/[!a]* and packages/[^a]*, brace alternatives packages/{cli,mcp} and packages/{cli,{mcp,api}}, packages/?li, packages/[cm]*, and recursive packages/**/cli.packages/{cli,{mcp,api}} resolve ['packages/api', 'packages/cli', 'packages/mcp']; recursive packages/**/cli resolves ['packages/cli', 'packages/nested/cli'].packages/**/release does not traverse a symlinked packages/external directory outside the repository, returning only ['packages/internal/release'].{../outside/*,packages/*} and {/tmp/*,packages/*}, malformed braces packages/{cli, packages/{cli}, packages/{cli,{mcp}}, packages/{cli,{{},mcp}}, and unsafe literal paths packages/./cli, packages/../cli, packages/__proto__/cli, packages/foo], packages/foo!, and packages/foo^, with safe-ASCII-segment validation errors.packages/* and packages/cli*; concrete packages/cli must throw matches multiple workspace patterns: packages/*, packages/cli* both from getWorkspaceNames() and getConfiguration(true).src/commands/prepare.ts fix: loading .craft.yml from argv.configFrom previously called loadConfigurationFromString(configContent) without a root, so remote workspace glob expansion could use the wrong current directory. Fix obtains repositoryRoot = await git.revparse(['--show-toplevel']) and calls loadConfigurationFromString(configContent, repositoryRoot.trim()).src/commands/publish.ts adds getPublishStateGitHubConfig(githubConfig, stateRepository = process.env.CRAFT_PUBLISH_STATE_GITHUB_REPO): when CRAFT_PUBLISH_STATE_GITHUB_REPO is a valid owner/repo matching /^(?<owner>[A-Za-z0-9_.-]+)\/(?<repo>[A-Za-z0-9_.-]+)$/, it overrides only publish-state identity; malformed values throw ConfigurationError('CRAFT_PUBLISH_STATE_GITHUB_REPO must be a GitHub owner/repository pair.'); absent state identity retains resolved GitHub config.src/commands/__tests__/publish.test.ts tests getPublishStateGitHubConfig() with resolved workspace GitHub { owner: 'release-owner', repo: 'release-repo', projectPath: 'packages/cli' } and controller repository getsentry/toolkit: returned state identity is { owner: 'getsentry', repo: 'toolkit' }, yielding the same getPublishStateFilename() for version 1.2.3, cwd /github/workspace/__repo__/packages/cli, workspace cli; without controller identity it preserves the original config object.src/commands/publish.ts fix: name-rev --name-only --no-undefined <revision> failures previously prevented publishing a detached CI-approved SHA. New exported getRevisionBranchName(git, revision) returns the trimmed named ref when available, but catches failures and returns ''; publishMain() uses it and checks out branchName || rev.src/commands/__tests__/publish.test.ts tests getRevisionBranchName() returns release/1.2.3 for git.raw('name-rev', '--name-only', '--no-undefined', 'abc123') output, and returns '' when git.raw rejects with Could not get ref name.src/commands/workspace.ts registers workspace <command> with description Manage release workspaces, requires a subcommand, and registers ./workspace_cmds/list; new src/commands/workspace_cmds/list.ts defines list, description List defined release workspaces as a JSON array, and prints formatJson(getWorkspaceNames()).src/commands/workspace_cmds/__tests__/list.test.ts verifies craft workspace list prints exact JSON ["cli","mcp.v2"] for workspace names ['cli', 'mcp.v2'], and [] when no workspaces exist.src/config.ts adds globSync/hasMagic from glob and realpathSync from fs; workspace resolution now accepts a workspaceDirectory, supports literal and glob workspace keys, expands globs relative to that directory, returns sorted concrete workspace names, and throws if a concrete name matches multiple workspace patterns.src/config.ts glob expansion uses globSync(workspaceGlob, { absolute: false, cwd: root, dot: true, ignore: ['**/node_modules/**'], posix: true }), filters results to safe workspace paths, repository-root-contained resolved paths, realpath-contained paths, and directories via lstatSync(resolvedMatch).isDirectory(). This prevents path traversal, symlink escape, non-directory matches, and node_modules matches.src/config.ts safe literal workspace segments must be nonempty ASCII [A-Za-z0-9_.-]+, cannot be ., .., __proto__, begin with -, be absolute, or contain backslashes. Safe glob validation additionally supports only glob metacharacters allowed by /^[A-Za-z0-9_.?*[\]!^-]+$/, validates every brace-expanded alternative, and rejects malformed brace syntax.src/config.ts adds checkWorkspacesMinVersion(config) and uses it for selected workspaces; applyWorkspaceSelection(config, workspaceDirectory) now lists expanded concrete names in its missing-selection error and resolves selected glob workspaces against workspaceDirectory.src/config.ts modifies findConfigFile() cache behavior: it reuses _configPathCache only if existsSync(_configPathCache); otherwise clears the cache before scanning. setActiveWorkspace() now also clears _configPathCache.absolute: true returns absolute matched paths, absolute: false returns paths relative to cwd, and unset absolute returns absolute results only for absolute patterns. It states posix: true uses /; on Windows absolute paths become fully resolved UNC-style paths such as //?/C:/foo/bar; absolute cannot be combined with withFileTypes.