@bakers-build/addon-deployment 0.0.0-stage → 0.1.0-next.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 keytec GmbH
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,5 @@
1
- # Temporary Holding Version
1
+ # @bakers-build/addon-deployment
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ bakers.build deployment command plugin: `bakers deployment tickets` — the release batch computed from a git-log range.
4
+
5
+ Part of the bakers.build CLI. Install the meta package `@bakers-build/bakers` to get the `bakers` CLI with all addons. Source: https://git.key-tec.de/bakers-build/bakers-build (bakers-cli/).
@@ -0,0 +1,51 @@
1
+ import type { BbRemote } from '@bakers-build/conductor/contract';
2
+ import type { Command } from 'commander';
3
+ export interface GatherResult {
4
+ range: string;
5
+ resolved: Array<{
6
+ identifier: string;
7
+ uuid: string;
8
+ title: string;
9
+ }>;
10
+ unresolved: string[];
11
+ }
12
+ /**
13
+ * Deduped ticket identifiers from a git log, in first-seen order — for any
14
+ * project key, not just `bakers.build` (BB-392 AC-2).
15
+ *
16
+ * The parse admits *keys*, not tokens, in two passes: a key is admitted where
17
+ * it stands in one of the anchored positions above, and every identifier whose
18
+ * key was admitted is then collected — anchored or bare.
19
+ *
20
+ * The second pass is not optional: of the last 300 commits on `develop` only
21
+ * two carry a `ref:` trailer, the rest name the ticket in the subject, and a
22
+ * bare `BB-135` in a body is a real reference. What the rule gives up is one
23
+ * residual case — a delta in which a key appears *only* in bare mid-prose form,
24
+ * anchored nowhere, yields nothing for that key. That is the deliberate trade:
25
+ * such a token is indistinguishable from `AC-1` without hardcoding a project
26
+ * key, which is the defect this replaces. The cheap direction is the other one,
27
+ * a wrongly admitted key, because it surfaces in `unresolved` rather than as a
28
+ * wrong ticket.
29
+ */
30
+ export declare function parseIdentifiers(gitLog: string): string[];
31
+ /**
32
+ * Compute the release batch from a git-log delta: parse ticket identifiers and
33
+ * resolve each to a ticket. Unresolved identifiers are reported, never fatal
34
+ * (the resolver returning null is not an error) — BB-153 AC-2.
35
+ */
36
+ export declare function gatherBatch(opts: {
37
+ sinceRef: string;
38
+ toRef: string;
39
+ gitLog: string;
40
+ resolve: (id: string) => Promise<{
41
+ uuid: string;
42
+ title: string;
43
+ } | null>;
44
+ }): Promise<GatherResult>;
45
+ /**
46
+ * Register the `bakers deployment` command group. `tickets` computes the release's
47
+ * tickets (git-log identifier parse + resolve over the given range) and prints
48
+ * them as JSON — pure compute + report, it does not write `referenced_tickets`
49
+ * (the prepare-deployment skill does, via the documented dropsh path).
50
+ */
51
+ export declare function registerDeployment(program: Command, resolveRemote: () => Promise<BbRemote>, resolveProject: () => Promise<string>, cwd: () => string): void;
@@ -0,0 +1,124 @@
1
+ import { exec } from '@bakers-build/core';
2
+ /**
3
+ * An identifier's shape: a project key plus its number. A key is validated as
4
+ * `/^[A-Z][A-Z0-9]{1,9}$/` and never contains a hyphen (`BbProject`), so this
5
+ * is the whole grammar — and it is also the shape of prose like `AC-1`,
6
+ * `UTF-8` or `SHA-256`, which is why matching it alone is not enough.
7
+ */
8
+ const IDENTIFIER = '[A-Z][A-Z0-9]{1,9}-\\d+';
9
+ /**
10
+ * What may precede a subject: the `%x1e` record separator and the abbreviated
11
+ * or full commit hash that `bakers deployment tickets` asks `git log` to print in
12
+ * front of `%s`. Both optional, so a bare subject line anchors too.
13
+ */
14
+ const SUBJECT_HEAD = '^\\x1e?(?:[0-9a-f]{2,40} )?';
15
+ /**
16
+ * The three positions in which an identifier can only be a ticket reference,
17
+ * and which therefore admit its project key:
18
+ *
19
+ * 1. the `ref:` trailer the commit convention prescribes — `ref:SHOP-42`;
20
+ * 2. a conventional-commit scope — `feat(BB-134): …`;
21
+ * 3. the head of a subject line — `BB-375: …`, the legacy form 298 of the
22
+ * last 300 commits on `develop` use.
23
+ *
24
+ * Positions 2 and 3 are deliberately anchored to the head of a line rather than
25
+ * to "any token before a colon" or "any parenthesised token": a body bullet
26
+ * `- AC-2: …` and a body line `(AC-5): parent = …` are exactly those two
27
+ * shapes, and both occur in this repository's real history. Admitting `AC` from
28
+ * them puts six phantom identifiers into a 300-commit release batch.
29
+ */
30
+ const ANCHORED = new RegExp([
31
+ `ref:[ \\t]*(${IDENTIFIER})\\b`,
32
+ `${SUBJECT_HEAD}[a-z]+!?\\((${IDENTIFIER})\\):`,
33
+ `${SUBJECT_HEAD}(${IDENTIFIER}):`,
34
+ ].join('|'), 'gm');
35
+ /**
36
+ * Deduped ticket identifiers from a git log, in first-seen order — for any
37
+ * project key, not just `bakers.build` (BB-392 AC-2).
38
+ *
39
+ * The parse admits *keys*, not tokens, in two passes: a key is admitted where
40
+ * it stands in one of the anchored positions above, and every identifier whose
41
+ * key was admitted is then collected — anchored or bare.
42
+ *
43
+ * The second pass is not optional: of the last 300 commits on `develop` only
44
+ * two carry a `ref:` trailer, the rest name the ticket in the subject, and a
45
+ * bare `BB-135` in a body is a real reference. What the rule gives up is one
46
+ * residual case — a delta in which a key appears *only* in bare mid-prose form,
47
+ * anchored nowhere, yields nothing for that key. That is the deliberate trade:
48
+ * such a token is indistinguishable from `AC-1` without hardcoding a project
49
+ * key, which is the defect this replaces. The cheap direction is the other one,
50
+ * a wrongly admitted key, because it surfaces in `unresolved` rather than as a
51
+ * wrong ticket.
52
+ */
53
+ export function parseIdentifiers(gitLog) {
54
+ const admitted = new Set();
55
+ for (const [, trailer, scope, subject] of gitLog.matchAll(ANCHORED)) {
56
+ const identifier = trailer ?? scope ?? subject;
57
+ if (identifier !== undefined) {
58
+ admitted.add(keyOf(identifier));
59
+ }
60
+ }
61
+ const seen = new Set();
62
+ const out = [];
63
+ for (const [identifier] of gitLog.matchAll(new RegExp(`\\b${IDENTIFIER}\\b`, 'g'))) {
64
+ if (admitted.has(keyOf(identifier)) && !seen.has(identifier)) {
65
+ seen.add(identifier);
66
+ out.push(identifier);
67
+ }
68
+ }
69
+ return out;
70
+ }
71
+ /** An identifier's project key — everything left of its `-<number>`. */
72
+ function keyOf(identifier) {
73
+ return identifier.slice(0, identifier.indexOf('-'));
74
+ }
75
+ /**
76
+ * Compute the release batch from a git-log delta: parse ticket identifiers and
77
+ * resolve each to a ticket. Unresolved identifiers are reported, never fatal
78
+ * (the resolver returning null is not an error) — BB-153 AC-2.
79
+ */
80
+ export async function gatherBatch(opts) {
81
+ const resolved = [];
82
+ const unresolved = [];
83
+ for (const identifier of parseIdentifiers(opts.gitLog)) {
84
+ const hit = await opts.resolve(identifier);
85
+ if (hit) {
86
+ resolved.push({ identifier, ...hit });
87
+ }
88
+ else {
89
+ unresolved.push(identifier);
90
+ }
91
+ }
92
+ return { range: `${opts.sinceRef}..${opts.toRef}`, resolved, unresolved };
93
+ }
94
+ /**
95
+ * Register the `bakers deployment` command group. `tickets` computes the release's
96
+ * tickets (git-log identifier parse + resolve over the given range) and prints
97
+ * them as JSON — pure compute + report, it does not write `referenced_tickets`
98
+ * (the prepare-deployment skill does, via the documented dropsh path).
99
+ */
100
+ export function registerDeployment(program, resolveRemote, resolveProject, cwd) {
101
+ const dep = program
102
+ .command('deployment')
103
+ .description('release / deployment helpers');
104
+ dep
105
+ .command('tickets')
106
+ .description("the release's tickets (git-log identifier parse + resolve over a range) as JSON")
107
+ .requiredOption('--since <ref>', "the env's currently-deployed ref (git range start)")
108
+ .requiredOption('--to <ref>', 'the post-merge env branch tip (git range end)')
109
+ .action(async (opts) => {
110
+ // Prefix each commit with a record separator (%x1e) so one commit's body
111
+ // cannot run into the next commit's header line; the identifier parse is
112
+ // separator-agnostic, but the boundary keeps the log unambiguous.
113
+ const gitLog = await exec('git', ['log', '--format=%x1e%H %s%n%b', `${opts.since}..${opts.to}`], { cwd: cwd() });
114
+ const remote = await resolveRemote();
115
+ const project = await resolveProject();
116
+ const result = await gatherBatch({
117
+ sinceRef: opts.since,
118
+ toRef: opts.to,
119
+ gitLog,
120
+ resolve: (id) => remote.resolveTicketByIdentifier(project, id),
121
+ });
122
+ console.log(JSON.stringify(result));
123
+ });
124
+ }
@@ -0,0 +1,5 @@
1
+ import { type BbCommandPlugin } from '@bakers-build/core';
2
+ /** The `deployment` command plugin the host mounts (`bakers deployment …`). */
3
+ declare const deploymentCommandPlugin: BbCommandPlugin;
4
+ export default deploymentCommandPlugin;
5
+ export { type GatherResult, gatherBatch, parseIdentifiers, registerDeployment, } from './deployment.js';
@@ -0,0 +1,34 @@
1
+ import { composeConductorConfig, loadConductorConfig, selectRemote, } from '@bakers-build/conductor';
2
+ import { loadBbConfig, resolveConfigPath, } from '@bakers-build/core';
3
+ import { registerDeployment } from './deployment.js';
4
+ // BB-224 (Finding 7): `deployment` is its own COMMAND-surface addon, no longer
5
+ // a side-registration of the `conductor` command plugin. The release-batch
6
+ // gathering (`src/deployment.ts`) was already fully dependency-inverted — it
7
+ // takes the remote, the project and the cwd as thunks — so this module only has
8
+ // to reproduce the thunks the conductor command plugin used to pass, over the
9
+ // same split-config resolution:
10
+ // engine half `conductor.config.js` via `loadConductorConfig`
11
+ // connection half `bakers.config.js` via `loadBbConfig`
12
+ // composed by `composeConductorConfig`, with the remote picked by `selectRemote`.
13
+ //
14
+ // The `@bakers-build/conductor` import is the package MAIN (engine + config loader),
15
+ // not the runtime-light `./contract` subpath — that cost is paid only when a
16
+ // `bakers deployment …` subcommand actually dispatches, because the host mounts
17
+ // this module lazily (the `./preset` accumulator is what `bakers --help` reads).
18
+ /** Resolve + compose the full conductor config (engine + connection halves). */
19
+ async function resolveConfig(host) {
20
+ const engine = await loadConductorConfig(resolveConfigPath());
21
+ const connection = await loadBbConfig(host, {});
22
+ return composeConductorConfig(engine, connection);
23
+ }
24
+ /** The `deployment` command plugin the host mounts (`bakers deployment …`). */
25
+ const deploymentCommandPlugin = {
26
+ kind: 'command',
27
+ name: 'deployment',
28
+ describe: 'release / deployment helpers',
29
+ register(program, host) {
30
+ registerDeployment(program, async () => selectRemote(await resolveConfig(host)), async () => (await resolveConfig(host)).project, () => process.cwd());
31
+ },
32
+ };
33
+ export default deploymentCommandPlugin;
34
+ export { gatherBatch, parseIdentifiers, registerDeployment, } from './deployment.js';
@@ -0,0 +1,2 @@
1
+ import type { BbPreset } from '@bakers-build/core';
2
+ export declare const commands: BbPreset['commands'];
@@ -0,0 +1,8 @@
1
+ export const commands = (acc) => [
2
+ ...acc,
3
+ {
4
+ name: 'deployment',
5
+ plugin: '@bakers-build/addon-deployment',
6
+ describe: 'release / deployment helpers',
7
+ },
8
+ ];
package/package.json CHANGED
@@ -1,6 +1,29 @@
1
1
  {
2
2
  "name": "@bakers-build/addon-deployment",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0-next.38",
4
+ "description": "bakers.build deployment command plugin: `bakers deployment tickets` — the release batch computed from a git-log range.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "exports": {
8
+ ".": "./dist/src/index.js",
9
+ "./preset": "./dist/src/preset.js"
10
+ },
11
+ "files": [
12
+ "dist/src"
13
+ ],
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://git.key-tec.de/bakers-build/bakers-build.git",
20
+ "directory": "bakers-cli/addons/deployment"
21
+ },
22
+ "dependencies": {
23
+ "commander": "^12.1.0"
24
+ },
25
+ "peerDependencies": {
26
+ "@bakers-build/conductor": "^0.1.0-next.38",
27
+ "@bakers-build/core": "^0.1.0-next.38"
28
+ }
29
+ }