@tallyrelease/cli 0.1.0-beta.6 → 0.1.1-beta.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,24 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 (2026-10-15)
4
+
5
+
6
+ ### Features
7
+
8
+ * bootstrap tallyrelease with build selection (v0.1) ([#1](https://github.com/abinnovision/tally-release/issues/1)) ([4efde72](https://github.com/abinnovision/tally-release/commit/4efde72e32091df3c84a2b497272a3a0efdac659))
9
+ * **cli:** align human-readable output and add a local build base ([#8](https://github.com/abinnovision/tally-release/issues/8)) ([96cb5f0](https://github.com/abinnovision/tally-release/commit/96cb5f010f3090806da4317a50c23ba17884c5c5))
10
+ * **cli:** publish the tallyrelease.yaml JSON schema ([#9](https://github.com/abinnovision/tally-release/issues/9)) ([c5b5c94](https://github.com/abinnovision/tally-release/commit/c5b5c946315fdcab2a486cfc2eeef5dd47b6a8f8))
11
+ * **cli:** render the unit graph as mermaid ([#7](https://github.com/abinnovision/tally-release/issues/7)) ([297413a](https://github.com/abinnovision/tally-release/commit/297413a06534b803c5e98e3ee84cb8921bdebdd6))
12
+ * make units build-only unless opted into release ([#6](https://github.com/abinnovision/tally-release/issues/6)) ([4b01768](https://github.com/abinnovision/tally-release/commit/4b0176809d38504eec22e35168f57ca17840ef13))
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * **cli:** clearer errors and package docs for the 0.1.0 release ([#11](https://github.com/abinnovision/tally-release/issues/11)) ([85fc7ed](https://github.com/abinnovision/tally-release/commit/85fc7ed70693bae01c9b90319d808f72f5eef6f6))
18
+
19
+
20
+ ### Dependencies
21
+
22
+ * The following workspace dependencies were updated
23
+ * dependencies
24
+ * @tallyrelease/core bumped to 0.1.0
package/README.md CHANGED
@@ -27,13 +27,21 @@ units:
27
27
 
28
28
  Units are build-only unless opted in with `release: true` (per unit) or `defaults.release: true`.
29
29
 
30
+ Tags label units so CI can split the build set by kind:
31
+
32
+ ```yaml
33
+ units:
34
+ apps/web: { tags: [docker] }
35
+ packages/api: { release: true, tags: [npm] }
36
+ ```
37
+
30
38
  ## Commands
31
39
 
32
40
  Global flags: `--cwd <dir>` (repository root), `--config <file>` (default `tallyrelease.yaml`), `--token <token>` (forge token, default `GITHUB_TOKEN`), `--verbose`.
33
41
 
34
42
  - `tallyrelease check` validates config, plugins, units and graph.
35
43
  - `tallyrelease graph [--format text|json|mermaid] [--json]` prints units and edges.
36
- - `tallyrelease affected [--base <ref>] [--format text|json|tsv] [--json] [--out <file>] [--github-output]` computes the build set. `--out` writes the affected document as JSON, `--github-output` appends outputs to `$GITHUB_OUTPUT`.
44
+ - `tallyrelease affected [--base <ref>] [--format text|json|tsv] [--json] [--out <file>] [--github-output] [--tag <tag>]` computes the build set. `--out` writes the affected document as JSON, `--github-output` appends outputs to `$GITHUB_OUTPUT`. `--tag` (repeatable) keeps units with any of the given tags.
37
45
 
38
46
  Exit codes: `0` ok, `1` error, `2` usage. For scripts use `--format tsv` or `--json`; the text layout may change between versions.
39
47
 
@@ -57,6 +65,33 @@ steps:
57
65
 
58
66
  The job needs `actions: read` for the base lookup.
59
67
 
68
+ The `affected` output is a JSON array of `{id, path, plugin, reason, released, tags}` and works as a matrix:
69
+
70
+ ```yaml
71
+ jobs:
72
+ affected:
73
+ runs-on: ubuntu-latest
74
+ outputs:
75
+ docker: ${{ steps.docker.outputs.affected }}
76
+ steps:
77
+ - uses: actions/checkout@v4
78
+ with:
79
+ fetch-depth: 0
80
+ - id: docker
81
+ run: npx @tallyrelease/cli affected --tag docker --github-output
82
+ env:
83
+ GITHUB_TOKEN: ${{ github.token }}
84
+ image:
85
+ needs: affected
86
+ if: needs.affected.outputs.docker != '[]'
87
+ strategy:
88
+ matrix:
89
+ unit: ${{ fromJSON(needs.affected.outputs.docker) }}
90
+ runs-on: ubuntu-latest
91
+ steps:
92
+ - run: echo "build ${{ matrix.unit.id }} from ${{ matrix.unit.path }}"
93
+ ```
94
+
60
95
  ## Editor support
61
96
 
62
97
  `tallyrelease.yaml` has a JSON schema for completion and validation in editors using the YAML language server. Add one of these modelines as the first line.
@@ -31,6 +31,8 @@ export declare const affectedDocSchema: z.ZodObject<{
31
31
  all: "all";
32
32
  }>;
33
33
  via: z.ZodArray<z.ZodString>;
34
+ released: z.ZodBoolean;
35
+ tags: z.ZodArray<z.ZodString>;
34
36
  }, z.core.$strict>>;
35
37
  unattributed: z.ZodArray<z.ZodString>;
36
38
  warnings: z.ZodArray<z.ZodObject<{
@@ -27,6 +27,8 @@ export const affectedDocSchema = z.strictObject({
27
27
  plugin: z.string(),
28
28
  reason,
29
29
  via: z.array(z.string()),
30
+ released: z.boolean(),
31
+ tags: z.array(z.string()),
30
32
  })),
31
33
  unattributed: z.array(z.string()),
32
34
  warnings: z.array(z.strictObject({
@@ -85,6 +85,15 @@
85
85
  "items": {
86
86
  "type": "string"
87
87
  }
88
+ },
89
+ "released": {
90
+ "type": "boolean"
91
+ },
92
+ "tags": {
93
+ "type": "array",
94
+ "items": {
95
+ "type": "string"
96
+ }
88
97
  }
89
98
  },
90
99
  "required": [
@@ -92,7 +101,9 @@
92
101
  "path",
93
102
  "plugin",
94
103
  "reason",
95
- "via"
104
+ "via",
105
+ "released",
106
+ "tags"
96
107
  ],
97
108
  "additionalProperties": false
98
109
  }
@@ -5,7 +5,7 @@ const multiline = (key, value) => {
5
5
  };
6
6
  /** `$GITHUB_OUTPUT` entries of `affected`; units are ordered by id. */
7
7
  export const affectedGithubOutput = (doc) => [
8
- `affected=${JSON.stringify(doc.units.map(({ id, path, reason }) => ({ id, path, reason })))}\n`,
8
+ `affected=${JSON.stringify(doc.units.map(({ id, path, plugin, reason, released, tags }) => ({ id, path, plugin, reason, released, tags })))}\n`,
9
9
  multiline("affected-paths", doc.units.map((u) => u.path).join("\n")),
10
10
  `all=${String(doc.build.all)}\n`,
11
11
  `base=${doc.build.base ?? ""}\n`,
@@ -5,6 +5,8 @@ export interface AffectedFlags {
5
5
  json?: boolean;
6
6
  out?: string;
7
7
  githubOutput?: boolean;
8
+ /** Keeps units with at least one of these tags. */
9
+ tag?: string[];
8
10
  }
9
11
  /** Computes the build set and writes the requested outputs. */
10
12
  export declare const affected: (session: Session, flags: AffectedFlags) => Promise<void>;
@@ -32,8 +32,15 @@ const baseLabel = (base) => {
32
32
  }
33
33
  };
34
34
  const tsv = (doc) => [
35
- "id\tpath\treason\tvia",
36
- ...doc.units.map((u) => [u.id, u.path, u.reason, u.via.join(",")].join("\t")),
35
+ "id\tpath\treason\tvia\treleased\ttags",
36
+ ...doc.units.map((u) => [
37
+ u.id,
38
+ u.path,
39
+ u.reason,
40
+ u.via.join(","),
41
+ String(u.released),
42
+ u.tags.join(","),
43
+ ].join("\t")),
37
44
  ]
38
45
  .map((line) => `${line}\n`)
39
46
  .join("");
@@ -60,9 +67,19 @@ export const affected = async (session, flags) => {
60
67
  base: base.base,
61
68
  baseSource: base.source,
62
69
  });
70
+ const tags = new Set(flags.tag ?? []);
71
+ const known = new Set(workspace.units.flatMap((u) => u.tags));
72
+ const unknownTags = [...tags]
73
+ .filter((t) => !known.has(t))
74
+ .map((t) => ({
75
+ code: "W_TAG_UNKNOWN",
76
+ message: `no unit has tag "${t}"`,
77
+ }));
78
+ const selected = (u) => tags.size === 0 || u.tags.some((t) => tags.has(t));
63
79
  const doc = {
64
80
  ...computed,
65
- warnings: [...computed.warnings, ...base.warnings],
81
+ units: computed.units.filter(selected),
82
+ warnings: [...computed.warnings, ...base.warnings, ...unknownTags],
66
83
  };
67
84
  session.warn(doc.warnings);
68
85
  const json = `${JSON.stringify(doc, null, 2)}\n`;
@@ -86,12 +103,11 @@ export const affected = async (session, flags) => {
86
103
  return;
87
104
  }
88
105
  const paint = painter(session.stdout);
89
- const released = new Set(workspace.units.filter((u) => u.version !== "none").map((u) => u.id));
90
106
  const rows = alignColumns(doc.units.map((u) => [
91
107
  u.id,
92
108
  u.path,
93
109
  u.via.length > 0 ? `via ${u.via.join(", ")}` : "",
94
- released.has(u.id) ? "released" : "",
110
+ u.released ? "released" : "",
95
111
  ]), { indent: 2, styles: [undefined, "dim", "dim", "cyan"] }, paint);
96
112
  const sha = doc.build.base?.slice(0, 7) ?? "none";
97
113
  const lines = [
@@ -115,7 +131,7 @@ export const affected = async (session, flags) => {
115
131
  ? `, ${count(doc.unattributed.length, "file")} outside any unit`
116
132
  : "";
117
133
  lines.push(doc.units.length > 0
118
- ? `${String(doc.units.length)} of ${count(workspace.units.length, "unit")} affected${outside}`
134
+ ? `${String(doc.units.length)} of ${count(workspace.units.filter(selected).length, "unit")} affected${outside}`
119
135
  : `no units affected${outside}`);
120
136
  session.stdout.write(`${lines.join("\n")}\n`);
121
137
  };
@@ -12,6 +12,7 @@ export const graph = async (session, flags) => {
12
12
  path: u.root,
13
13
  plugin: u.plugin,
14
14
  version: u.version,
15
+ tags: [...u.tags],
15
16
  }));
16
17
  const edges = workspace.graph.edges.map((e) => ({
17
18
  from: e.from,
@@ -33,13 +34,15 @@ export const graph = async (session, flags) => {
33
34
  return;
34
35
  }
35
36
  const paint = painter(session.stdout);
37
+ const tagged = units.some((u) => u.tags.length > 0);
36
38
  const table = [
37
- ["UNIT", "PATH", "PLUGIN", "RELEASE"],
39
+ ["UNIT", "PATH", "PLUGIN", "RELEASE", ...(tagged ? ["TAGS"] : [])],
38
40
  ...units.map((u) => [
39
41
  u.id,
40
42
  u.path,
41
43
  u.plugin,
42
44
  u.version === "none" ? "-" : u.version,
45
+ ...(tagged ? [u.tags.join(",")] : []),
43
46
  ]),
44
47
  ];
45
48
  // Header is aligned unstyled so column styles do not apply to it.
@@ -28,6 +28,7 @@ export declare const configSchema: z.ZodObject<{
28
28
  patch: "patch";
29
29
  rebuild: "rebuild";
30
30
  }>>;
31
+ tags: z.ZodExactOptional<z.ZodArray<z.ZodString>>;
31
32
  }, z.core.$strict>>>;
32
33
  deps: z.ZodExactOptional<z.ZodArray<z.ZodObject<{
33
34
  from: z.ZodString;
@@ -70,6 +71,7 @@ export declare const configJsonSchema: () => {
70
71
  patch: "patch";
71
72
  rebuild: "rebuild";
72
73
  }>>;
74
+ tags: z.ZodExactOptional<z.ZodArray<z.ZodString>>;
73
75
  }, z.core.$strict>>>;
74
76
  deps: z.ZodExactOptional<z.ZodArray<z.ZodObject<{
75
77
  from: z.ZodString;
@@ -41,6 +41,12 @@ const unitOverlay = z.strictObject({
41
41
  .exactOptional(),
42
42
  tagFormat: tagFormat.exactOptional(),
43
43
  propagate: propagate.exactOptional(),
44
+ tags: z
45
+ .array(z.string().regex(/^[A-Za-z0-9._-]+$/))
46
+ .meta({
47
+ description: "Labels for filtering with affected --tag (letters, digits, dot, underscore, dash), for example docker or npm.",
48
+ })
49
+ .exactOptional(),
44
50
  });
45
51
  /** Schema of `tallyrelease.yaml`. */
46
52
  export const configSchema = z.strictObject({
package/dist/main.js CHANGED
@@ -55,6 +55,7 @@ export const main = async (argv, deps) => {
55
55
  ]))
56
56
  .option("--out <file>", "write the affected document to a file")
57
57
  .option("--github-output", "append outputs to $GITHUB_OUTPUT")
58
+ .option("--tag <tag>", "keep units with this tag (repeatable)", (tag, prior) => [...(prior ?? []), tag])
58
59
  .action(async (flags, cmd) => {
59
60
  if (flags.json && flags.format !== undefined) {
60
61
  cmd.error("--json and --format cannot be combined", { exitCode: 2 });
@@ -106,6 +106,14 @@
106
106
  "rebuild"
107
107
  ],
108
108
  "description": "Policy for this unit when a dependency is released: patch (at least a patch bump), rebuild (rebuild only) or auto (patch if the edge is pinned, else rebuild)."
109
+ },
110
+ "tags": {
111
+ "type": "array",
112
+ "items": {
113
+ "type": "string",
114
+ "pattern": "^[A-Za-z0-9._-]+$"
115
+ },
116
+ "description": "Labels for filtering with affected --tag (letters, digits, dot, underscore, dash), for example docker or npm."
109
117
  }
110
118
  },
111
119
  "additionalProperties": false
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tallyrelease/cli",
3
- "version": "0.1.0-beta.6",
3
+ "version": "0.1.1-beta.1",
4
4
  "description": "Command line interface of tallyrelease",
5
5
  "repository": {
6
6
  "type": "git",
@@ -48,7 +48,7 @@
48
48
  "prettier": "@abinnovision/prettier-config",
49
49
  "dependencies": {
50
50
  "@octokit/rest": "^22.0.1",
51
- "@tallyrelease/core": "^0.1.0-beta.4",
51
+ "@tallyrelease/core": "^0.1.1-beta.1",
52
52
  "commander": "^15.0.0",
53
53
  "tinyglobby": "^0.2.17",
54
54
  "yaml": "^2.9.1",
@@ -58,14 +58,14 @@
58
58
  "@abinnovision/eslint-config-base": "^3.4.2",
59
59
  "@abinnovision/prettier-config": "^2.2.0",
60
60
  "@internal/tsconfig": "0.0.0",
61
- "@types/node": "^24.0.0",
62
- "@vitest/coverage-v8": "^5.0.3",
61
+ "@types/node": "^24.19.2",
62
+ "@vitest/coverage-v8": "^5.0.4",
63
63
  "ajv": "^8.20.0",
64
64
  "eslint": "^10.12.0",
65
- "prettier": "^3.9.9",
65
+ "prettier": "^3.9.10",
66
66
  "typescript": "^6.0.3",
67
67
  "vite": "^8.3.4",
68
- "vitest": "^5.0.3"
68
+ "vitest": "^5.0.4"
69
69
  },
70
70
  "engines": {
71
71
  "node": ">=24"