@williamthorsen/release-kit 5.3.0 → 5.3.2
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 +27 -0
- package/README.md +2 -38
- package/dist/esm/.cache +1 -1
- package/dist/esm/discoverWorkspaces.js +2 -2
- package/dist/esm/generateChangelogs.d.ts +2 -0
- package/dist/esm/generateChangelogs.js +5 -1
- package/dist/esm/releasePrepareMono.js +1 -4
- package/dist/esm/sync-labels/generateCommand.js +2 -2
- package/dist/esm/sync-labels/presets.js +2 -2
- package/dist/esm/validateOverridesCommand.d.ts +2 -1
- package/dist/esm/validateOverridesCommand.js +33 -16
- package/package.json +4 -5
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## 5.3.2 — 2026-06-16
|
|
6
|
+
|
|
7
|
+
### ♻️ Refactoring
|
|
8
|
+
|
|
9
|
+
- Migrate from js-yaml to yaml (#410)
|
|
10
|
+
|
|
11
|
+
Replaces the `js-yaml` dependency with the `yaml` package for all YAML reading and writing, with no change to behavior or generated output. The swap trims the dependency footprint by dropping a direct dependency and its companion type-definitions package, since `yaml` ships its own types.
|
|
12
|
+
|
|
13
|
+
## 5.3.1 — 2026-05-19
|
|
14
|
+
|
|
15
|
+
### 🐛 Bug fixes
|
|
16
|
+
|
|
17
|
+
- Validate overrides against the full release history (#401)
|
|
18
|
+
|
|
19
|
+
Fixes an issue where `release-kit overrides validate` reported overrides as stale when they targeted commits in past releases, even though `release-kit prepare` correctly matched them. The two commands now agree on which overrides are stale.
|
|
20
|
+
|
|
21
|
+
### ♻️ Refactoring
|
|
22
|
+
|
|
23
|
+
- Restructure tests and align core package directory with package name (#405)
|
|
24
|
+
|
|
25
|
+
Tests in every package are now typechecked alongside the code they cover, so type breakage in tests fails the build instead of slipping through. The `core` package's workspace directory is renamed to match its package name, so `nmr -F nmr-core ...` and `pnpm --filter nmr-core ...` now resolve where they previously failed.
|
|
26
|
+
|
|
5
27
|
## 5.3.0 — 2026-05-10
|
|
6
28
|
|
|
7
29
|
### 🎉 Features
|
|
@@ -435,6 +457,7 @@ All notable changes to this project will be documented in this file.
|
|
|
435
457
|
- Cover multi-changelogPaths and error paths (#44)
|
|
436
458
|
|
|
437
459
|
Add three tests for previously untested code paths:
|
|
460
|
+
|
|
438
461
|
- `releasePrepareMono`: component with two `changelogPaths` entries, asserting `git-cliff` is invoked once per path with the correct `--output` target.
|
|
439
462
|
- `getCommitsSinceTarget`: `git describe` failure with a non-128 exit status propagates as a wrapped error instead of being swallowed.
|
|
440
463
|
- `getCommitsSinceTarget`: `git log` failure is wrapped and re-thrown with the commit range in the message.
|
|
@@ -451,6 +474,10 @@ All notable changes to this project will be documented in this file.
|
|
|
451
474
|
|
|
452
475
|
### 🎉 Features
|
|
453
476
|
|
|
477
|
+
- Migrate release-kit from toolbelt (#18)
|
|
478
|
+
|
|
479
|
+
Migrates the complete `@williamthorsen/release-kit` package (v1.0.1) from `williamthorsen/toolbelt` into `packages/release-kit/`, adds shebang preservation to the shared esbuild plugin for CLI binaries, and sets up dogfooding infrastructure so this monorepo uses release-kit for its own releases.
|
|
480
|
+
|
|
454
481
|
- 🚨 **Breaking:** Slim down release workflow by removing unnecessary pnpm install (#21)
|
|
455
482
|
|
|
456
483
|
Make release-kit self-contained by invoking git-cliff via `npx --yes` instead of requiring it on PATH, and by appending modified file paths to the format command so lightweight formatters like `npx prettier --write` work without a full `pnpm install`. Update init templates, README, and consuming repo config/workflow to reference workflow v3.
|
package/README.md
CHANGED
|
@@ -4,43 +4,7 @@ Version-bumping and changelog-generation toolkit for release workflows.
|
|
|
4
4
|
|
|
5
5
|
Provides a self-contained CLI that auto-discovers workspaces from `pnpm-workspace.yaml`, parses conventional commits, determines version bumps, updates `package.json` files, and generates changelogs from `git-cliff --context` output rendered in-process (with optional [editorial overrides](#editorial-overrides)).
|
|
6
6
|
|
|
7
|
-
<!-- section:release-notes -->
|
|
8
|
-
## Release notes — v5.3.0 (2026-05-10)
|
|
9
|
-
|
|
10
|
-
### 🎉 Features
|
|
11
|
-
|
|
12
|
-
- Enable editorial overrides for changelog entries (#387)
|
|
13
|
-
|
|
14
|
-
Allows `release-kit` consumers to skip or correct historical changelog entries by means of an overrides file.
|
|
15
|
-
|
|
16
|
-
- Decentralize changelog overrides to per-scope .meta/ files (#391)
|
|
17
|
-
|
|
18
|
-
Adds support for workspace-scoped editorial-override files for `release-kit`-generated changelogs. A repo-root file applies overrides to every workspace's changelog; a workspace-tier file applies only to that workspace.
|
|
19
|
-
|
|
20
|
-
- Add section markers and authenticated upstream fetch (#393)
|
|
21
|
-
|
|
22
|
-
A new `markers` block in `work-types.json` describes the breaking-changes emoji and label, making them available for use by consumers.
|
|
23
|
-
|
|
24
|
-
`work-types check` and `work-types sync` now authenticate when `GITHUB_TOKEN` is set, so they can reach private upstream repositories.
|
|
25
|
-
|
|
26
|
-
- Validate changelog overrides from the command line (#395)
|
|
27
|
-
|
|
28
|
-
Adds a `release-kit overrides validate` subcommand that audits every `.meta/changelog-overrides.json` file across the project root and per-workspace scopes in one pass. The command reports schema errors, ambiguous-prefix collisions, and stale-key warnings with tiered exit codes so CI can choose its own failure threshold. The same validation is also available via a library function exported by the package.
|
|
29
|
-
|
|
30
|
-
### 🐛 Bug fixes
|
|
31
|
-
|
|
32
|
-
- Suppress git-cliff stale-version warnings on prepare (#373)
|
|
33
|
-
|
|
34
|
-
Fixes an issue where `release-kit prepare` repeatedly printed git-cliff's "A new version of git-cliff is available" notice — twice per release unit, so 2 × N times for an N-package monorepo run — while never updating the locally cached git-cliff binary. Each `prepare` run now revalidates the npm cache once before any cliff work, so the binary stays current with upstream releases and the notice no longer surfaces on every per-workspace invocation.
|
|
35
|
-
|
|
36
|
-
- Use synthetic changelogs for forced empty-range releases (#376)
|
|
37
|
-
|
|
38
|
-
Fixes an issue where `release-kit prepare` with `--force`, `--bump=X`, or `--set-version` would invoke git-cliff against units that had no commits since their last tag, surfacing confusing `WARN git_cliff > There is already a tag (...)` lines (twice per affected unit) and silently leaving `CHANGELOG.md` and `.meta/changelog.json` stale. Empty-range bumps now write a synthetic `Notes / Forced version bump.` entry to both files instead of invoking git-cliff. Applies to all three release stages: single-package, per-workspace, and project. Prior changelog history is preserved on every path.
|
|
39
|
-
|
|
40
|
-
- Accept `breakingPolicies` field in config files (#394)
|
|
41
|
-
|
|
42
|
-
Fixes an issue where setting `breakingPolicies` in `release-kit.config.ts` was rejected as an unknown field, leaving per-work-type breaking-policy configuration unreachable from the config file. Each entry accepts `'forbidden'`, `'optional'`, or `'required'`; an empty object opts out of enforcement.
|
|
43
|
-
<!-- /section:release-notes -->
|
|
7
|
+
<!-- section:release-notes --><!-- /section:release-notes -->
|
|
44
8
|
|
|
45
9
|
## Installation
|
|
46
10
|
|
|
@@ -495,7 +459,7 @@ The override file is validated when `release-kit prepare` loads it. Each error n
|
|
|
495
459
|
|
|
496
460
|
#### Standalone validation: `release-kit overrides validate`
|
|
497
461
|
|
|
498
|
-
For a
|
|
462
|
+
For a focused overrides-only health check (locally or as a CI gate), run:
|
|
499
463
|
|
|
500
464
|
```sh
|
|
501
465
|
pnpm exec release-kit overrides validate
|
package/dist/esm/.cache
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
a675735fcc8ee6abbe7201c05a9cec6517a6495d796f61322b141ed79e4e7bcf
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { glob } from "glob";
|
|
4
|
-
import {
|
|
4
|
+
import { parse } from "yaml";
|
|
5
5
|
import { isRecord } from "./typeGuards.js";
|
|
6
6
|
async function discoverWorkspaces() {
|
|
7
7
|
const workspaceFile = "pnpm-workspace.yaml";
|
|
@@ -15,7 +15,7 @@ async function discoverWorkspaces() {
|
|
|
15
15
|
console.warn(`Warning: Failed to read ${workspaceFile}: ${error instanceof Error ? error.message : String(error)}`);
|
|
16
16
|
return void 0;
|
|
17
17
|
}
|
|
18
|
-
const parsed =
|
|
18
|
+
const parsed = parse(content);
|
|
19
19
|
if (!isRecord(parsed)) {
|
|
20
20
|
return void 0;
|
|
21
21
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import type { WorkspaceConfig } from './types.ts';
|
|
1
2
|
export declare function buildTagPattern(tagPrefixes: readonly string[]): string;
|
|
3
|
+
export declare function getAllTagPrefixes(workspace: WorkspaceConfig): string[];
|
|
2
4
|
export interface GenerateChangelogOptions {
|
|
3
5
|
tagPattern?: string;
|
|
4
6
|
includePaths?: string[];
|
|
@@ -9,9 +9,13 @@ function buildTagPattern(tagPrefixes) {
|
|
|
9
9
|
const escaped = tagPrefixes.map(escapeRegex);
|
|
10
10
|
return `(${escaped.join("|")})[0-9].*`;
|
|
11
11
|
}
|
|
12
|
+
function getAllTagPrefixes(workspace) {
|
|
13
|
+
return [workspace.tagPrefix, ...workspace.legacyIdentities?.map((identity) => identity.tagPrefix) ?? []];
|
|
14
|
+
}
|
|
12
15
|
function escapeRegex(value) {
|
|
13
16
|
return value.replace(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
|
|
14
17
|
}
|
|
15
18
|
export {
|
|
16
|
-
buildTagPattern
|
|
19
|
+
buildTagPattern,
|
|
20
|
+
getAllTagPrefixes
|
|
17
21
|
};
|
|
@@ -19,7 +19,7 @@ import { isForwardVersion } from "./compareVersions.js";
|
|
|
19
19
|
import { decideRelease } from "./decideRelease.js";
|
|
20
20
|
import { DEFAULT_BREAKING_POLICIES, DEFAULT_VERSION_PATTERNS, DEFAULT_WORK_TYPES } from "./defaults.js";
|
|
21
21
|
import { detectUndeclaredTagPrefixes } from "./detectUndeclaredTagPrefixes.js";
|
|
22
|
-
import { buildTagPattern } from "./generateChangelogs.js";
|
|
22
|
+
import { buildTagPattern, getAllTagPrefixes } from "./generateChangelogs.js";
|
|
23
23
|
import { getCommitsSinceTarget } from "./getCommitsSinceTarget.js";
|
|
24
24
|
import { hasPrettierConfig } from "./hasPrettierConfig.js";
|
|
25
25
|
import { resolveWorkTypes } from "./loadConfig.js";
|
|
@@ -550,9 +550,6 @@ function topologicalSort(releaseSet, graph) {
|
|
|
550
550
|
}
|
|
551
551
|
return { sorted, cyclicDirs };
|
|
552
552
|
}
|
|
553
|
-
function getAllTagPrefixes(workspace) {
|
|
554
|
-
return [workspace.tagPrefix, ...workspace.legacyIdentities?.map((identity) => identity.tagPrefix) ?? []];
|
|
555
|
-
}
|
|
556
553
|
export {
|
|
557
554
|
releasePrepareMono
|
|
558
555
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { stringify } from "yaml";
|
|
4
4
|
import { loadSyncLabelsConfig, SYNC_LABELS_CONFIG_PATH } from "./loadSyncLabelsConfig.js";
|
|
5
5
|
import { hashPresetFile } from "./presets.js";
|
|
6
6
|
import { resolveLabels } from "./resolveLabels.js";
|
|
@@ -10,7 +10,7 @@ function formatLabelsYaml(labels, presetHashes) {
|
|
|
10
10
|
for (const [name, hash] of [...presetHashes.entries()].sort(([a], [b]) => a.localeCompare(b))) {
|
|
11
11
|
headerLines.push(`# ${name} preset hash: ${hash}`);
|
|
12
12
|
}
|
|
13
|
-
const yamlBody =
|
|
13
|
+
const yamlBody = stringify(labels, { singleQuote: true, lineWidth: 0 });
|
|
14
14
|
return headerLines.join("\n") + "\n" + yamlBody;
|
|
15
15
|
}
|
|
16
16
|
async function generateCommand() {
|
|
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
4
|
import { findPackageRoot } from "@williamthorsen/nmr-core";
|
|
5
|
-
import {
|
|
5
|
+
import { parse } from "yaml";
|
|
6
6
|
import { isRecord } from "../typeGuards.js";
|
|
7
7
|
function resolvePresetPath(presetName) {
|
|
8
8
|
const root = findPackageRoot(import.meta.url);
|
|
@@ -28,7 +28,7 @@ function loadPreset(presetName) {
|
|
|
28
28
|
const message = error instanceof Error ? error.message : String(error);
|
|
29
29
|
throw new Error(`Failed to read preset "${presetName}": ${message}`);
|
|
30
30
|
}
|
|
31
|
-
const parsed =
|
|
31
|
+
const parsed = parse(content);
|
|
32
32
|
if (!Array.isArray(parsed)) {
|
|
33
33
|
throw new TypeError(`Preset "${presetName}" must be a YAML array of label definitions`);
|
|
34
34
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ValidateAllChangelogOverridesInputs, type ValidateAllChangelogOverridesResult } from './changelogOverrides.ts';
|
|
2
|
+
import type { ChangelogEntry, ReleaseConfig } from './types.ts';
|
|
2
3
|
export interface ValidateOverridesCommandResult {
|
|
3
4
|
exitCode: 0 | 1 | 2;
|
|
4
5
|
message: string;
|
|
@@ -6,7 +7,7 @@ export interface ValidateOverridesCommandResult {
|
|
|
6
7
|
export interface ValidateOverridesCommandDependencies {
|
|
7
8
|
discoverWorkspaces?: () => Promise<string[] | undefined>;
|
|
8
9
|
loadConfig?: () => Promise<unknown>;
|
|
9
|
-
|
|
10
|
+
buildEntries?: (config: Pick<ReleaseConfig, 'cliffConfigPath' | 'changelogJson'>, tagPattern?: string, includePaths?: string[]) => ChangelogEntry[];
|
|
10
11
|
validate?: (inputs: ValidateAllChangelogOverridesInputs) => ValidateAllChangelogOverridesResult;
|
|
11
12
|
}
|
|
12
13
|
export declare function validateOverridesCommand(dependencies?: ValidateOverridesCommandDependencies): Promise<ValidateOverridesCommandResult>;
|
|
@@ -1,15 +1,17 @@
|
|
|
1
|
+
import { buildChangelogEntries } from "./buildChangelogEntries.js";
|
|
1
2
|
import {
|
|
2
3
|
resolveOverridePath,
|
|
3
4
|
validateAllChangelogOverrides
|
|
4
5
|
} from "./changelogOverrides.js";
|
|
5
6
|
import { discoverWorkspaces } from "./discoverWorkspaces.js";
|
|
6
|
-
import {
|
|
7
|
+
import { buildTagPattern, getAllTagPrefixes } from "./generateChangelogs.js";
|
|
7
8
|
import { loadConfig, mergeMonorepoConfig, mergeSinglePackageConfig, readRootPackageVersion } from "./loadConfig.js";
|
|
8
9
|
import { validateConfig } from "./validateConfig.js";
|
|
10
|
+
const SYNTHETIC_VALIDATE_TAG = "validate-only";
|
|
9
11
|
async function validateOverridesCommand(dependencies = {}) {
|
|
10
12
|
const discover = dependencies.discoverWorkspaces ?? discoverWorkspaces;
|
|
11
13
|
const load = dependencies.loadConfig ?? loadConfig;
|
|
12
|
-
const
|
|
14
|
+
const buildEntries = dependencies.buildEntries ?? defaultBuildEntries;
|
|
13
15
|
const validate = dependencies.validate ?? validateAllChangelogOverrides;
|
|
14
16
|
let userConfig;
|
|
15
17
|
try {
|
|
@@ -25,7 +27,7 @@ async function validateOverridesCommand(dependencies = {}) {
|
|
|
25
27
|
}
|
|
26
28
|
let inputs;
|
|
27
29
|
try {
|
|
28
|
-
inputs = discoveredPaths === void 0 ? buildSinglePackageInputs(userConfig,
|
|
30
|
+
inputs = discoveredPaths === void 0 ? buildSinglePackageInputs(userConfig, buildEntries) : buildMonorepoInputs(discoveredPaths, userConfig, buildEntries);
|
|
29
31
|
} catch (error) {
|
|
30
32
|
return { exitCode: 2, message: `Error resolving overrides scope: ${errorMessage(error)}` };
|
|
31
33
|
}
|
|
@@ -60,11 +62,28 @@ function pluralize(count, noun) {
|
|
|
60
62
|
function errorMessage(error) {
|
|
61
63
|
return error instanceof Error ? error.message : String(error);
|
|
62
64
|
}
|
|
63
|
-
function
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
function defaultBuildEntries(config, tagPattern, includePaths) {
|
|
66
|
+
const options = {};
|
|
67
|
+
if (tagPattern !== void 0) {
|
|
68
|
+
options.tagPattern = tagPattern;
|
|
66
69
|
}
|
|
67
|
-
|
|
70
|
+
if (includePaths !== void 0) {
|
|
71
|
+
options.includePaths = includePaths;
|
|
72
|
+
}
|
|
73
|
+
return buildChangelogEntries(config, SYNTHETIC_VALIDATE_TAG, options);
|
|
74
|
+
}
|
|
75
|
+
function flattenEntriesToHashes(entries) {
|
|
76
|
+
const hashes = [];
|
|
77
|
+
for (const entry of entries) {
|
|
78
|
+
for (const section of entry.sections) {
|
|
79
|
+
for (const item of section.items) {
|
|
80
|
+
if (item.hash !== void 0) {
|
|
81
|
+
hashes.push(item.hash);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return hashes;
|
|
68
87
|
}
|
|
69
88
|
async function loadAndValidateConfig(load) {
|
|
70
89
|
let rawConfig;
|
|
@@ -83,24 +102,21 @@ async function loadAndValidateConfig(load) {
|
|
|
83
102
|
}
|
|
84
103
|
return config;
|
|
85
104
|
}
|
|
86
|
-
function buildSinglePackageInputs(userConfig,
|
|
105
|
+
function buildSinglePackageInputs(userConfig, buildEntries) {
|
|
87
106
|
const config = mergeSinglePackageConfig(userConfig);
|
|
88
|
-
const hashes =
|
|
107
|
+
const hashes = flattenEntriesToHashes(buildEntries(config));
|
|
89
108
|
return {
|
|
90
109
|
project: { filePath: resolveOverridePath("."), hashes }
|
|
91
110
|
};
|
|
92
111
|
}
|
|
93
|
-
function buildMonorepoInputs(discoveredPaths, userConfig,
|
|
112
|
+
function buildMonorepoInputs(discoveredPaths, userConfig, buildEntries) {
|
|
94
113
|
const rootPackage = readRootPackageVersion();
|
|
95
114
|
const config = mergeMonorepoConfig(discoveredPaths, userConfig, rootPackage);
|
|
96
115
|
const workspaces = config.workspaces.map((workspace) => {
|
|
97
|
-
const
|
|
98
|
-
workspace.tagPrefix,
|
|
99
|
-
...workspace.legacyIdentities?.map((identity) => identity.tagPrefix) ?? []
|
|
100
|
-
];
|
|
116
|
+
const tagPattern = buildTagPattern(getAllTagPrefixes(workspace));
|
|
101
117
|
return {
|
|
102
118
|
filePath: resolveOverridePath(workspace.workspacePath),
|
|
103
|
-
hashes:
|
|
119
|
+
hashes: flattenEntriesToHashes(buildEntries(config, tagPattern, workspace.paths))
|
|
104
120
|
};
|
|
105
121
|
});
|
|
106
122
|
const project = config.project;
|
|
@@ -109,7 +125,8 @@ function buildMonorepoInputs(discoveredPaths, userConfig, collect) {
|
|
|
109
125
|
};
|
|
110
126
|
if (project !== void 0) {
|
|
111
127
|
const contributingPaths = config.workspaces.flatMap((workspace) => workspace.paths);
|
|
112
|
-
|
|
128
|
+
const projectTagPattern = buildTagPattern([project.tagPrefix]);
|
|
129
|
+
projectScope.hashes = flattenEntriesToHashes(buildEntries(config, projectTagPattern, contributingPaths));
|
|
113
130
|
}
|
|
114
131
|
return { project: projectScope, workspaces };
|
|
115
132
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@williamthorsen/release-kit",
|
|
3
|
-
"version": "5.3.
|
|
3
|
+
"version": "5.3.2",
|
|
4
4
|
"description": "Version-bumping and changelog-generation toolkit for release workflows",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"homepage": "https://github.com/williamthorsen/node-monorepo-tools/tree/main/packages/release-kit#readme",
|
|
@@ -34,14 +34,13 @@
|
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"glob": "13.0.6",
|
|
36
36
|
"jiti": "2.7.0",
|
|
37
|
-
"js-yaml": "4.1.1",
|
|
38
37
|
"json-stringify-pretty-compact": "4.0.0",
|
|
39
|
-
"semver": "7.8.
|
|
38
|
+
"semver": "7.8.4",
|
|
39
|
+
"yaml": "2.9.0",
|
|
40
40
|
"zod": "4.4.3",
|
|
41
|
-
"@williamthorsen/nmr-core": "0.3.
|
|
41
|
+
"@williamthorsen/nmr-core": "0.3.2"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@types/js-yaml": "4.0.9",
|
|
45
44
|
"@types/semver": "7.7.1",
|
|
46
45
|
"smol-toml": "1.6.1"
|
|
47
46
|
},
|