Dashboard › craft › Distillation
3f72d341-a10e-4c44-9295-94527311a7fa["lore_tm_v1_J25Wm5oYKhMdkBWmEAmHAIRrXmqalvvIfGHf8zBaFlc","lore_tm_v1_y-C7wTCn2rXbM1DhE0vzcVlELA5NCMwkt6CV6aRkqVY","lore_tm_v1_QX91Acb9SIkdxf4TmIYujqSgeFdC4Z3HPSCk1_rpK6M","lore_tm_v1_bWMblN4cZ6GdW0eT9iGl8y83lm-jyrY59m2rx7YUXE0","lore_tm_v1_fhZE0zPemkGX8yZ4q34zuskBXgEcek4Duzq9xmqnVow","lore_tm_v1_d_FHLR7PTsJ5bg8TfI79jWKGtPU4394Ga0bYLbiwoSI"]
Date: Sep 3, 2026
scripts/generate-publish-issue-title-parser.js; grammar/parser src/modules/publish-issue-title.peggy and src/modules/publish-issue-title.js; parsing/validation src/modules/details-from-context.js; location resolution src/modules/publish-location.js and src/publish/resolve-location.js; workflow .github/workflows/publish.yml; and related tests including src/modules/__tests__/details-from-context.js, src/modules/__tests__/publish-location.js, and src/modules/__tests__/publish-workflow.js.package.json scripts "generate": "node scripts/generate-publish-issue-title-parser.js" and "check:generated": "node scripts/generate-publish-issue-title-parser.js --check"; generator uses grammar source "publish-issue-title.peggy" and produces "publish-issue-title.js"..github/workflows/publish.yml resolves publish location in step id: location: initializes workspace_names='[]', invokes needsWorkspaceDiscovery from ./.__publish__/src/modules/publish-location, and, only when discovery is required and __repo__/.craft.yml exists, runs getsentry/craft:latest workspace list in Docker with __repo__ mounted at /github/workspace/__repo__; passes resulting JSON through CRAFT_WORKSPACE_NAMES to node .__publish__/src/publish/resolve-location.js..github/workflows/publish.yml passes resolved path and optional workspace into Craft as CRAFT_PUBLISH_PATH: ${{ fromJSON(steps.location.outputs.result).path }} and CRAFT_PUBLISH_WORKSPACE: ${{ fromJSON(steps.location.outputs.result).workspace || '' }} / CRAFT_WORKSPACE: ${{ fromJSON(steps.location.outputs.result).workspace || '' }}. It validates container_cwd remains /github/workspace/__repo__ or below it, otherwise emits ::error::Publish path must remain inside the target checkout.; state-file names include a URL-safe base64 workspace prefix workspace-${workspace_encoded}- when applicable.src/modules/publish-issue-title.peggy defines canonical publish-title grammar: publish: , optional getsentry/, repository [A-Za-z0-9_.-]+, optional slash-delimited path segments [A-Za-z0-9_.-]+, optional legacy suffix [workspace: <JsonString>], @, and version [A-Za-z0-9_.+-]+, followed by end-of-input !.. Parsed result is { repo, path: path || "", version, ...(workspace ? { workspace } : {}) }.src/modules/publish-issue-title.peggy documents that a path suffix is syntactic only: the controller resolves a single path segment as a workspace after checkout of the CI-approved revision. LegacyWorkspace retains the JSON string verbatim; JsonCharacter accepts non-quote/non-backslash/non-control characters or a backslash followed by any non-control character, delegating authoritative escape validation to JSON.parse so malformed legacy values produce the existing clear diagnostic.src/modules/details-from-context.js detailsFromContext({ context }) validates context.payload.issue, otherwise throws "Issue context is not defined"; parses context.payload.issue.title with parsePublishIssueTitle, translating parser failures to Invalid publish issue title: '<title>'.detailsFromContext: destructures legacy parsed workspace as workspaceJson, parses it with JSON.parse, and on failure throws Invalid publish workspace JSON in title: '<title>'; rejects empty workspace names and any Unicode control (\p{Cc}), format (\p{Cf}), line-separator (\p{Zl}), or paragraph-separator (\p{Zp}) characters with "Workspace names must be nonempty and cannot contain Unicode control, format, or separator characters".detailsFromContext derives dry_run as "1" when an issue label is named "dry-run" and "" otherwise; prefixes parsed title path with "."; rejects a path containing a .. segment with Invalid publish issue path: '<path>'; and rejects any explicit workspace paired with non-root path via "A publish workspace must use the repository root path.".detailsFromContext extracts optional merge_target from an issue-body line matching /^Merge target: (?<merge_target>[\w.\-/]+)$/m; extracts checked target IDs from the matched Targets block using CHECKED_TARGETS_PARSER_REGEX = /^\s*- \[x\] (\S+)/gim; returns parsed title data, dry_run, merge_target, normalized path, targets, and optional validated workspace.src/modules/details-from-context.js target regexes are TARGETS_SECTION_PARSER_REGEX = /^(?!### Targets$\s)(?: *- \[[ xX]\] \S+\s*$(?:\r?\n)?)+/m, TARGETS_PARSER_REGEX = /^\s*- \[[ x]\] (\S+)/gim, and CHECKED_TARGETS_PARSER_REGEX = /^\s*- \[x\] (\S+)/gim.src/modules/publish-location.js defines needsWorkspaceDiscovery({ path, workspace }) as true only for no explicit workspace and a single path segment matching /^\.\/[A-Za-z0-9_.-]+$/.resolvePublishLocation({ path, workspace, workspaceNames }) returns { path, workspace } for an explicit workspace only when path === ".", otherwise throws "A publish workspace must use the repository root path."; for paths not requiring discovery returns { path }; validates every discovered name using isWorkspaceName, throwing "Craft workspace discovery returned an invalid workspace list." on invalid input; and converts a discovered single-segment path ./<name> into { path: ".", workspace: <name> } only if workspaceNames.includes(<name>).isWorkspaceName(name) accepts only strings other than "__proto__", ".", and ".." that match /^[A-Za-z0-9_.-]+$/.src/publish/resolve-location.js parses process.env.PUBLISH_ARGS as JSON; parses process.env.CRAFT_WORKSPACE_NAMES only if needsWorkspaceDiscovery(input) is true, otherwise uses []; rejects a non-array workspace result with "Craft workspace discovery returned an invalid workspace list."; then sets GitHub Actions output "result" to resolvePublishLocation({ path: input.path, workspace: input.workspace, workspaceNames }).