faf-taf-git 2.4.1 → 2.4.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 +11 -1
- package/README.md +1 -1
- package/package.json +14 -1
- package/project.faf +2 -2
- package/.faf-dna +0 -95
- package/.fafignore +0 -41
- package/.github/dependabot.yml +0 -10
- package/.github/workflows/ci.yml +0 -53
- package/.github/workflows/claude-code-review.yml +0 -36
- package/.github/workflows/claude.yml +0 -39
- package/.github/workflows/homebrew.yml +0 -19
- package/.taf +0 -98
- package/CLAUDE.md +0 -30
- package/CODE_OF_CONDUCT.md +0 -21
- package/CONTRIBUTING.md +0 -289
- package/SECURITY.md +0 -19
- package/action.yml +0 -50
- package/agent.fafa +0 -53
- package/badge.svg +0 -21
- package/dist/bundle/index.js +0 -36554
- package/dist/index.js +0 -42827
- package/dist/index.js.map +0 -1
- package/dist/licenses.txt +0 -126
- package/dist/sourcemap-register.js +0 -1
- package/docs/RELEASE-NOTES.md +0 -200
- package/docs/USER-GUIDE.md +0 -301
- package/jest.config.js +0 -13
- package/src/badge.ts +0 -138
- package/src/cli.ts +0 -311
- package/src/index.ts +0 -201
- package/src/parsers/bun.ts +0 -103
- package/src/parsers/index.ts +0 -50
- package/src/parsers/jest.ts +0 -104
- package/src/parsers/junit.ts +0 -110
- package/src/parsers/vitest.ts +0 -65
- package/src/parsers/wjttc.ts +0 -160
- package/src/taf-core.ts +0 -139
- package/test-mcp.ts +0 -83
- package/test-standalone.ts +0 -87
- package/tests/action-bundle.test.ts +0 -34
- package/tests/action-parse-error.test.ts +0 -47
- package/tests/badge.test.ts +0 -260
- package/tests/bun-parser.test.ts +0 -173
- package/tests/cli-version.test.ts +0 -31
- package/tests/jest-parser.test.ts +0 -122
- package/tests/junit-parser.test.ts +0 -109
- package/tests/vitest-parser.test.ts +0 -112
- package/tests/wjttc-parser.test.ts +0 -116
- package/tests/wjttc-taf.test.ts +0 -470
- package/tsconfig.json +0 -18
package/docs/USER-GUIDE.md
DELETED
|
@@ -1,301 +0,0 @@
|
|
|
1
|
-
# FAF TAF Action - User Guide
|
|
2
|
-
|
|
3
|
-
## Introduction
|
|
4
|
-
|
|
5
|
-
The FAF TAF Action automatically maintains a `.taf` (Testing Activity Feed) file that tracks your test runs over time. This creates a permanent, git-tracked record of your project's test quality.
|
|
6
|
-
|
|
7
|
-
### The Golden Triangle
|
|
8
|
-
|
|
9
|
-
```
|
|
10
|
-
.faf
|
|
11
|
-
(WHAT IT IS)
|
|
12
|
-
/ \
|
|
13
|
-
/ \
|
|
14
|
-
/ \
|
|
15
|
-
repo ←→ .taf
|
|
16
|
-
(IMPLEMENTATION) (PROOF IT WORKS)
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
- **.faf** - Project DNA that tells AI what your project is
|
|
20
|
-
- **repo** - Your actual implementation (code doesn't lie)
|
|
21
|
-
- **.taf** - Permanent proof that your code works (formats don't negotiate)
|
|
22
|
-
|
|
23
|
-
## Prerequisites
|
|
24
|
-
|
|
25
|
-
### 1. Install FAF CLI
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
npm install -g faf-cli
|
|
29
|
-
# or
|
|
30
|
-
brew install faf-cli
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
### 2. Initialize .taf File
|
|
34
|
-
|
|
35
|
-
In your project root:
|
|
36
|
-
|
|
37
|
-
```bash
|
|
38
|
-
faf taf init
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
This creates a `.taf` file that tracks your testing timeline.
|
|
42
|
-
|
|
43
|
-
### 3. Commit .taf to Git
|
|
44
|
-
|
|
45
|
-
```bash
|
|
46
|
-
git add .taf
|
|
47
|
-
git commit -m "feat: add testing timeline"
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
## Basic Setup
|
|
51
|
-
|
|
52
|
-
### Minimal Configuration
|
|
53
|
-
|
|
54
|
-
Add to `.github/workflows/test.yml`:
|
|
55
|
-
|
|
56
|
-
```yaml
|
|
57
|
-
name: Tests
|
|
58
|
-
|
|
59
|
-
on: [push, pull_request]
|
|
60
|
-
|
|
61
|
-
jobs:
|
|
62
|
-
test:
|
|
63
|
-
runs-on: ubuntu-latest
|
|
64
|
-
|
|
65
|
-
steps:
|
|
66
|
-
- uses: actions/checkout@v4
|
|
67
|
-
|
|
68
|
-
- name: Setup Node
|
|
69
|
-
uses: actions/setup-node@v4
|
|
70
|
-
with:
|
|
71
|
-
node-version: '20'
|
|
72
|
-
|
|
73
|
-
- name: Install dependencies
|
|
74
|
-
run: npm ci
|
|
75
|
-
|
|
76
|
-
- name: Run tests and update .taf
|
|
77
|
-
uses: Wolfe-Jam/faf-taf-action@v1
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
That's it! The action will:
|
|
81
|
-
1. Run `npm test` (default)
|
|
82
|
-
2. Parse Jest output
|
|
83
|
-
3. Update `.taf` file
|
|
84
|
-
4. Commit changes automatically
|
|
85
|
-
|
|
86
|
-
## Advanced Configuration
|
|
87
|
-
|
|
88
|
-
### Custom Test Command
|
|
89
|
-
|
|
90
|
-
```yaml
|
|
91
|
-
- uses: Wolfe-Jam/faf-taf-action@v1
|
|
92
|
-
with:
|
|
93
|
-
test-command: 'npm run test:coverage'
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
### Custom Commit Message
|
|
97
|
-
|
|
98
|
-
```yaml
|
|
99
|
-
- uses: Wolfe-Jam/faf-taf-action@v1
|
|
100
|
-
with:
|
|
101
|
-
commit-message: 'test: update .taf timeline [skip ci]'
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
### Disable Auto-Commit
|
|
105
|
-
|
|
106
|
-
Handle commits yourself:
|
|
107
|
-
|
|
108
|
-
```yaml
|
|
109
|
-
- uses: Wolfe-Jam/faf-taf-action@v1
|
|
110
|
-
id: taf
|
|
111
|
-
with:
|
|
112
|
-
auto-commit: false
|
|
113
|
-
|
|
114
|
-
- name: Custom commit logic
|
|
115
|
-
if: steps.taf.outputs.taf-updated == 'true'
|
|
116
|
-
run: |
|
|
117
|
-
git add .taf
|
|
118
|
-
git commit -m "test: $(date +'%Y-%m-%d') test results"
|
|
119
|
-
git push
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
## Using Outputs
|
|
123
|
-
|
|
124
|
-
The action provides several outputs you can use:
|
|
125
|
-
|
|
126
|
-
```yaml
|
|
127
|
-
- uses: Wolfe-Jam/faf-taf-action@v1
|
|
128
|
-
id: taf
|
|
129
|
-
|
|
130
|
-
- name: Check test results
|
|
131
|
-
run: |
|
|
132
|
-
echo "Result: ${{ steps.taf.outputs.result }}"
|
|
133
|
-
echo "Tests: ${{ steps.taf.outputs.passed }}/${{ steps.taf.outputs.total }}"
|
|
134
|
-
|
|
135
|
-
- name: Fail if tests failed
|
|
136
|
-
if: steps.taf.outputs.result == 'FAILED'
|
|
137
|
-
run: exit 1
|
|
138
|
-
|
|
139
|
-
- name: Celebrate improvement
|
|
140
|
-
if: steps.taf.outputs.result == 'IMPROVED'
|
|
141
|
-
run: echo "Tests are improving!"
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
### Available Outputs
|
|
145
|
-
|
|
146
|
-
- `result` - PASSED, FAILED, IMPROVED, or DEGRADED
|
|
147
|
-
- `passed` - Number of passing tests
|
|
148
|
-
- `failed` - Number of failing tests
|
|
149
|
-
- `total` - Total test count
|
|
150
|
-
- `taf-updated` - Whether .taf was modified
|
|
151
|
-
|
|
152
|
-
## Workflow Patterns
|
|
153
|
-
|
|
154
|
-
### Skip CI on .taf Updates
|
|
155
|
-
|
|
156
|
-
Prevent infinite loops:
|
|
157
|
-
|
|
158
|
-
```yaml
|
|
159
|
-
- uses: Wolfe-Jam/faf-taf-action@v1
|
|
160
|
-
with:
|
|
161
|
-
commit-message: 'test: update .taf [skip ci]'
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
### Only Update on Main Branch
|
|
165
|
-
|
|
166
|
-
```yaml
|
|
167
|
-
- uses: Wolfe-Jam/faf-taf-action@v1
|
|
168
|
-
if: github.ref == 'refs/heads/main'
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
### Different Commands per Environment
|
|
172
|
-
|
|
173
|
-
```yaml
|
|
174
|
-
- uses: Wolfe-Jam/faf-taf-action@v1
|
|
175
|
-
with:
|
|
176
|
-
test-command: ${{ matrix.os == 'ubuntu-latest' && 'npm test' || 'npm run test:unit' }}
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
### Combine with Coverage
|
|
180
|
-
|
|
181
|
-
```yaml
|
|
182
|
-
- name: Run tests with coverage
|
|
183
|
-
uses: Wolfe-Jam/faf-taf-action@v1
|
|
184
|
-
with:
|
|
185
|
-
test-command: 'npm run test:coverage'
|
|
186
|
-
|
|
187
|
-
- name: Upload coverage
|
|
188
|
-
uses: codecov/codecov-action@v3
|
|
189
|
-
if: always()
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
## Permissions
|
|
193
|
-
|
|
194
|
-
For auto-commit to work, ensure your workflow has write permissions:
|
|
195
|
-
|
|
196
|
-
```yaml
|
|
197
|
-
permissions:
|
|
198
|
-
contents: write
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
If using a fine-grained PAT:
|
|
202
|
-
|
|
203
|
-
```yaml
|
|
204
|
-
- uses: actions/checkout@v4
|
|
205
|
-
with:
|
|
206
|
-
token: ${{ secrets.GITHUB_TOKEN }}
|
|
207
|
-
|
|
208
|
-
- uses: Wolfe-Jam/faf-taf-action@v1
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
## Troubleshooting
|
|
212
|
-
|
|
213
|
-
### "No .taf file found"
|
|
214
|
-
|
|
215
|
-
Run `faf taf init` in your project root and commit the file.
|
|
216
|
-
|
|
217
|
-
### "Could not parse test output"
|
|
218
|
-
|
|
219
|
-
Currently only Jest is supported. Check that your test command outputs Jest format.
|
|
220
|
-
|
|
221
|
-
### Tests pass but action fails
|
|
222
|
-
|
|
223
|
-
Check the action logs for parsing errors. The action needs the standard Jest summary line:
|
|
224
|
-
|
|
225
|
-
```
|
|
226
|
-
Tests: 173 passed, 173 total
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
### Commits not pushing
|
|
230
|
-
|
|
231
|
-
1. Verify `permissions: contents: write` in workflow
|
|
232
|
-
2. Check that `.taf` is not in `.gitignore`
|
|
233
|
-
3. Ensure git is configured properly in the workflow
|
|
234
|
-
|
|
235
|
-
### Action shows "up to date" but .taf wasn't updated
|
|
236
|
-
|
|
237
|
-
The action may have determined there were no changes. Check:
|
|
238
|
-
- Did tests actually run?
|
|
239
|
-
- Was there Jest output to parse?
|
|
240
|
-
- Check action logs for details
|
|
241
|
-
|
|
242
|
-
## Best Practices
|
|
243
|
-
|
|
244
|
-
### 1. Run on Every Push
|
|
245
|
-
|
|
246
|
-
Keep your .taf file current:
|
|
247
|
-
|
|
248
|
-
```yaml
|
|
249
|
-
on: [push, pull_request]
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
### 2. Use [skip ci] for .taf Commits
|
|
253
|
-
|
|
254
|
-
Prevent infinite loops:
|
|
255
|
-
|
|
256
|
-
```yaml
|
|
257
|
-
commit-message: 'test: update .taf [skip ci]'
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
### 3. Track .taf in Git
|
|
261
|
-
|
|
262
|
-
Never add `.taf` to `.gitignore`. It's meant to be tracked!
|
|
263
|
-
|
|
264
|
-
### 4. Review .taf Changes
|
|
265
|
-
|
|
266
|
-
When reviewing PRs, check .taf changes to see test quality trends.
|
|
267
|
-
|
|
268
|
-
### 5. Combine with .faf
|
|
269
|
-
|
|
270
|
-
Use both `.faf` (project context) and `.taf` (proof of quality) together for maximum value.
|
|
271
|
-
|
|
272
|
-
## Philosophy
|
|
273
|
-
|
|
274
|
-
### You Don't Negotiate with a Format
|
|
275
|
-
|
|
276
|
-
.taf files are structured data, not prose. They describe themselves. No interpretation needed.
|
|
277
|
-
|
|
278
|
-
### Software Accountability
|
|
279
|
-
|
|
280
|
-
Every test run becomes permanent, git-tracked proof that your code works (or doesn't). No hiding, no forgetting.
|
|
281
|
-
|
|
282
|
-
### The Format Describes Itself
|
|
283
|
-
|
|
284
|
-
Open your `.taf` file. You'll immediately understand what it tracks and why it matters.
|
|
285
|
-
|
|
286
|
-
## Learn More
|
|
287
|
-
|
|
288
|
-
- [.taf Format Specification](https://faf.one/docs/taf)
|
|
289
|
-
- [FAF CLI Documentation](https://faf.one/docs)
|
|
290
|
-
- [Example .taf Files](https://github.com/Wolfe-Jam/faf-cli/blob/main/.taf)
|
|
291
|
-
- [GitHub Discussions](https://github.com/Wolfe-Jam/faf/discussions)
|
|
292
|
-
|
|
293
|
-
## Support
|
|
294
|
-
|
|
295
|
-
- Report issues: [GitHub Issues](https://github.com/Wolfe-Jam/faf-taf-action/issues)
|
|
296
|
-
- Ask questions: [GitHub Discussions](https://github.com/Wolfe-Jam/faf/discussions)
|
|
297
|
-
- Email: team@faf.one
|
|
298
|
-
|
|
299
|
-
## License
|
|
300
|
-
|
|
301
|
-
MIT - Free forever, no strings attached.
|
package/jest.config.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
module.exports = {
|
|
2
|
-
preset: 'ts-jest',
|
|
3
|
-
testEnvironment: 'node',
|
|
4
|
-
roots: ['<rootDir>/tests'],
|
|
5
|
-
testMatch: ['**/*.test.ts'],
|
|
6
|
-
collectCoverageFrom: [
|
|
7
|
-
'src/**/*.ts',
|
|
8
|
-
'!src/**/*.d.ts',
|
|
9
|
-
],
|
|
10
|
-
coverageDirectory: 'coverage',
|
|
11
|
-
coverageReporters: ['text', 'lcov', 'html'],
|
|
12
|
-
verbose: true,
|
|
13
|
-
};
|
package/src/badge.ts
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Badge Generator for faf-taf-git
|
|
3
|
-
*
|
|
4
|
-
* Generates shields.io-style SVG badges from .taf test history.
|
|
5
|
-
* Self-contained SVG with Verdana font, no external dependencies.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import * as fs from 'fs';
|
|
9
|
-
import * as yaml from 'yaml';
|
|
10
|
-
|
|
11
|
-
export interface BadgeOptions {
|
|
12
|
-
tafPath?: string;
|
|
13
|
-
label?: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface BadgeResult {
|
|
17
|
-
success: boolean;
|
|
18
|
-
svg?: string;
|
|
19
|
-
error?: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
interface LatestRun {
|
|
23
|
-
passed: number;
|
|
24
|
-
total: number;
|
|
25
|
-
failed: number;
|
|
26
|
-
skipped: number;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const COLOR_PASS = '#003366'; // Navy blue (deep — white-text contrast; cyan was too light)
|
|
30
|
-
const COLOR_FAIL = '#E32400'; // Red
|
|
31
|
-
const COLOR_NONE = '#9F9F9F'; // Gray
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Render a shields.io flat-style SVG badge.
|
|
35
|
-
*/
|
|
36
|
-
export function renderBadgeSvg(label: string, value: string, color: string): string {
|
|
37
|
-
// Approximate text widths using Verdana 11px (6.5px per char average)
|
|
38
|
-
const charWidth = 6.5;
|
|
39
|
-
const padding = 10;
|
|
40
|
-
const labelWidth = Math.round(label.length * charWidth + padding * 2);
|
|
41
|
-
const valueWidth = Math.round(value.length * charWidth + padding * 2);
|
|
42
|
-
const totalWidth = labelWidth + valueWidth;
|
|
43
|
-
const height = 20;
|
|
44
|
-
|
|
45
|
-
const labelX = labelWidth / 2;
|
|
46
|
-
const valueX = labelWidth + valueWidth / 2;
|
|
47
|
-
|
|
48
|
-
return `<svg xmlns="http://www.w3.org/2000/svg" width="${totalWidth}" height="${height}" role="img" aria-label="${label}: ${value}">
|
|
49
|
-
<title>${label}: ${value}</title>
|
|
50
|
-
<linearGradient id="s" x2="0" y2="100%">
|
|
51
|
-
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
|
|
52
|
-
<stop offset="1" stop-opacity=".1"/>
|
|
53
|
-
</linearGradient>
|
|
54
|
-
<clipPath id="r">
|
|
55
|
-
<rect width="${totalWidth}" height="${height}" rx="3" fill="#fff"/>
|
|
56
|
-
</clipPath>
|
|
57
|
-
<g clip-path="url(#r)">
|
|
58
|
-
<rect width="${labelWidth}" height="${height}" fill="#555"/>
|
|
59
|
-
<rect x="${labelWidth}" width="${valueWidth}" height="${height}" fill="${color}"/>
|
|
60
|
-
<rect width="${totalWidth}" height="${height}" fill="url(#s)"/>
|
|
61
|
-
</g>
|
|
62
|
-
<g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="11">
|
|
63
|
-
<text x="${labelX}" y="14" fill="#010101" fill-opacity=".3">${label}</text>
|
|
64
|
-
<text x="${labelX}" y="13" fill="#fff">${label}</text>
|
|
65
|
-
<text x="${valueX}" y="14" fill="#010101" fill-opacity=".3">${value}</text>
|
|
66
|
-
<text x="${valueX}" y="13" fill="#fff">${value}</text>
|
|
67
|
-
</g>
|
|
68
|
-
</svg>`;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Read a .taf file and extract the latest test run.
|
|
73
|
-
*/
|
|
74
|
-
function getLatestRun(tafPath: string): LatestRun | null {
|
|
75
|
-
if (!fs.existsSync(tafPath)) {
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const content = fs.readFileSync(tafPath, 'utf-8');
|
|
80
|
-
|
|
81
|
-
// Handle YAML document separators (same logic as taf-core.ts)
|
|
82
|
-
const lines = content.split('\n');
|
|
83
|
-
const yamlLines: string[] = [];
|
|
84
|
-
for (const line of lines) {
|
|
85
|
-
if (line.trim() === '---' && yamlLines.length > 0) break;
|
|
86
|
-
if (line.trim() === '---' && yamlLines.length === 0) continue;
|
|
87
|
-
yamlLines.push(line);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const parsed = yaml.parse(yamlLines.join('\n'));
|
|
91
|
-
|
|
92
|
-
if (!parsed || typeof parsed !== 'object') return null;
|
|
93
|
-
if (!Array.isArray(parsed.test_history) || parsed.test_history.length === 0) return null;
|
|
94
|
-
|
|
95
|
-
const latest = parsed.test_history[parsed.test_history.length - 1];
|
|
96
|
-
if (!latest.tests || typeof latest.tests.total !== 'number') return null;
|
|
97
|
-
|
|
98
|
-
return {
|
|
99
|
-
passed: latest.tests.passed ?? 0,
|
|
100
|
-
total: latest.tests.total,
|
|
101
|
-
failed: latest.tests.failed ?? 0,
|
|
102
|
-
skipped: latest.tests.skipped ?? 0,
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Generate a badge SVG from a .taf file.
|
|
108
|
-
*/
|
|
109
|
-
export function generateBadge(options: BadgeOptions = {}): BadgeResult {
|
|
110
|
-
const label = options.label || 'TAF';
|
|
111
|
-
const tafPath = options.tafPath || '.taf';
|
|
112
|
-
|
|
113
|
-
try {
|
|
114
|
-
const run = getLatestRun(tafPath);
|
|
115
|
-
|
|
116
|
-
if (!run) {
|
|
117
|
-
return {
|
|
118
|
-
success: true,
|
|
119
|
-
svg: renderBadgeSvg(label, 'no data', COLOR_NONE),
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const skippedSuffix = run.skipped > 0 ? ` (${run.skipped} skipped)` : '';
|
|
124
|
-
const displayPassed = run.failed === 0 ? run.total : run.passed;
|
|
125
|
-
const value = `${displayPassed}/${run.total} passing${skippedSuffix}`;
|
|
126
|
-
const color = run.failed === 0 ? COLOR_PASS : COLOR_FAIL;
|
|
127
|
-
|
|
128
|
-
return {
|
|
129
|
-
success: true,
|
|
130
|
-
svg: renderBadgeSvg(label, value, color),
|
|
131
|
-
};
|
|
132
|
-
} catch (error) {
|
|
133
|
-
return {
|
|
134
|
-
success: false,
|
|
135
|
-
error: error instanceof Error ? error.message : 'Failed to generate badge',
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
}
|