performance-budget-enforcer 1.0.0

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.
@@ -0,0 +1,41 @@
1
+ name: Performance Budget Check
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ perf-budget:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - name: Checkout code
14
+ uses: actions/checkout@v4
15
+
16
+ - name: Setup Node.js
17
+ uses: actions/setup-node@v4
18
+ with:
19
+ node-version: '20'
20
+ cache: 'npm'
21
+
22
+ - name: Install dependencies
23
+ run: npm ci
24
+
25
+ - name: Build
26
+ run: npm run build
27
+
28
+ - name: Run performance budget check
29
+ run: npx perf-budget check --dir .next
30
+ env:
31
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
32
+ GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }}
33
+ GITHUB_REPOSITORY: ${{ github.repository }}
34
+
35
+ - name: Upload baseline
36
+ if: github.ref == 'refs/heads/main'
37
+ uses: actions/upload-artifact@v4
38
+ with:
39
+ name: bundle-analysis
40
+ path: bundle-analysis.json
41
+ retention-days: 30
package/README.md ADDED
@@ -0,0 +1,68 @@
1
+ # Performance Budget Enforcer
2
+
3
+ A CI plugin that scans React/Next.js builds, detects bundle size regressions, and fails CI if performance budgets are exceeded.
4
+
5
+ ## Features
6
+
7
+ - 🔍 Analyzes bundle sizes from Webpack, Vite, and Next.js builds
8
+ - 📊 Identifies which dependencies caused bloat
9
+ - 🚫 Fails CI when budgets are exceeded
10
+ - 📈 Compares against baseline for regression detection
11
+ - 💬 Posts PR comments with detailed reports
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install performance-budget-enforcer --save-dev
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ### Initialize config
22
+
23
+ ```bash
24
+ npx perf-budget init
25
+ ```
26
+
27
+ This creates a `perf-budget.json` config file.
28
+
29
+ ### Analyze bundles
30
+
31
+ ```bash
32
+ npx perf-budget analyze --dir ./build
33
+ ```
34
+
35
+ ### Check budget
36
+
37
+ ```bash
38
+ npx perf-budget check --dir ./build
39
+ ```
40
+
41
+ ## Configuration
42
+
43
+ Create `perf-budget.json` in your project root:
44
+
45
+ ```json
46
+ {
47
+ "maxTotalSize": 500000,
48
+ "maxGzippedSize": 150000,
49
+ "maxChunkSize": 250000,
50
+ "maxDependencySize": 100000,
51
+ "thresholds": {
52
+ "warning": 10,
53
+ "error": 20
54
+ }
55
+ }
56
+ ```
57
+
58
+ Sizes are in bytes. The `thresholds` define regression detection - if bundle grows more than 20% vs baseline, it will error.
59
+
60
+ ## GitHub Actions Integration
61
+
62
+ See `.github/workflows/performance.yml` for CI setup.
63
+
64
+ ## Commands
65
+
66
+ - `perf-budget analyze` - Analyze bundle sizes
67
+ - `perf-budget check` - Check against budget
68
+ - `perf-budget init` - Create config file
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,176 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ var __importDefault = (this && this.__importDefault) || function (mod) {
37
+ return (mod && mod.__esModule) ? mod : { "default": mod };
38
+ };
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ const commander_1 = require("commander");
41
+ const chalk_1 = __importDefault(require("chalk"));
42
+ const ora_1 = __importDefault(require("ora"));
43
+ const path = __importStar(require("path"));
44
+ const fs = __importStar(require("fs"));
45
+ const analyzer_1 = require("../lib/analyzer");
46
+ const budget_1 = require("../lib/budget");
47
+ const reporter_1 = require("../github/reporter");
48
+ const program = new commander_1.Command();
49
+ program
50
+ .name('perf-budget')
51
+ .description('Frontend Performance Budget Enforcer - CI Plugin for React/Next.js')
52
+ .version('1.0.0');
53
+ program
54
+ .command('analyze')
55
+ .description('Analyze bundle sizes')
56
+ .option('-d, --dir <path>', 'Build directory', './build')
57
+ .option('-o, --output <path>', 'Output JSON file')
58
+ .action(async (options) => {
59
+ const spinner = (0, ora_1.default)('Analyzing bundle...').start();
60
+ try {
61
+ const buildDir = path.resolve(process.cwd(), options.dir);
62
+ if (!fs.existsSync(buildDir)) {
63
+ spinner.fail(`Build directory not found: ${buildDir}`);
64
+ process.exit(1);
65
+ }
66
+ const analyzer = new analyzer_1.BundleAnalyzer(buildDir);
67
+ const analysis = await analyzer.analyze();
68
+ spinner.succeed(`Analyzed ${analysis.chunks.length} chunks`);
69
+ console.log('\n📊 Bundle Analysis');
70
+ console.log('─'.repeat(50));
71
+ console.log(`Total Size: ${(0, analyzer_1.formatSize)(analysis.totalSize)}`);
72
+ console.log(`Gzipped: ${(0, analyzer_1.formatSize)(analysis.totalGzipped)}`);
73
+ console.log(`Chunks: ${analysis.chunks.length}`);
74
+ console.log(`Dependencies: ${analysis.dependencies.length}`);
75
+ console.log('─'.repeat(50));
76
+ if (analysis.dependencies.length > 0) {
77
+ console.log('\n🔍 Top Dependencies:');
78
+ for (const dep of analysis.dependencies.slice(0, 10)) {
79
+ console.log(` ${dep.name}: ${(0, analyzer_1.formatSize)(dep.size)}`);
80
+ }
81
+ }
82
+ if (options.output) {
83
+ const outputPath = path.resolve(process.cwd(), options.output);
84
+ await (0, analyzer_1.saveAnalysis)(analysis, outputPath);
85
+ console.log(chalk_1.default.green(`\n✅ Results saved to ${outputPath}`));
86
+ }
87
+ }
88
+ catch (error) {
89
+ spinner.fail(`Analysis failed: ${error.message}`);
90
+ process.exit(1);
91
+ }
92
+ });
93
+ program
94
+ .command('check')
95
+ .description('Check bundle against performance budget')
96
+ .option('-d, --dir <path>', 'Build directory', './build')
97
+ .option('-c, --config <path>', 'Budget config file', './perf-budget.json')
98
+ .option('-b, --baseline <path>', 'Baseline analysis file')
99
+ .option('--fail', 'Exit with code 1 if budget exceeded', true)
100
+ .action(async (options) => {
101
+ const spinner = (0, ora_1.default)('Checking budget...').start();
102
+ try {
103
+ const buildDir = path.resolve(process.cwd(), options.dir);
104
+ const configPath = path.resolve(process.cwd(), options.config);
105
+ if (!fs.existsSync(buildDir)) {
106
+ spinner.fail(`Build directory not found: ${buildDir}`);
107
+ process.exit(1);
108
+ }
109
+ let config;
110
+ if (fs.existsSync(configPath)) {
111
+ config = (0, budget_1.loadConfig)(configPath);
112
+ }
113
+ else {
114
+ config = {
115
+ maxTotalSize: 500 * 1024,
116
+ maxGzippedSize: 150 * 1024,
117
+ maxChunkSize: 250 * 1024,
118
+ thresholds: { warning: 10, error: 20 }
119
+ };
120
+ console.log(chalk_1.default.yellow(`Using default budget config. Create ${options.config} to customize.`));
121
+ }
122
+ let baseline;
123
+ if (options.baseline) {
124
+ const baselinePath = path.resolve(process.cwd(), options.baseline);
125
+ if (fs.existsSync(baselinePath)) {
126
+ baseline = JSON.parse(fs.readFileSync(baselinePath, 'utf-8'));
127
+ }
128
+ }
129
+ const analyzer = new analyzer_1.BundleAnalyzer(buildDir);
130
+ const analysis = await analyzer.analyze();
131
+ const checker = new budget_1.BudgetChecker(config, baseline);
132
+ const result = checker.check(analysis);
133
+ budget_1.BudgetChecker.printResults(result);
134
+ if (!result.passed) {
135
+ const githubConfig = (0, reporter_1.getGitHubEnv)();
136
+ if (githubConfig) {
137
+ const reporter = new reporter_1.GitHubReporter(githubConfig);
138
+ await reporter.report(result);
139
+ }
140
+ }
141
+ if (!result.passed && options.fail) {
142
+ spinner.fail('Budget exceeded!');
143
+ process.exit(1);
144
+ }
145
+ spinner.succeed('Budget check complete');
146
+ }
147
+ catch (error) {
148
+ spinner.fail(`Check failed: ${error.message}`);
149
+ process.exit(1);
150
+ }
151
+ });
152
+ program
153
+ .command('init')
154
+ .description('Initialize config file')
155
+ .action(() => {
156
+ const configPath = path.resolve(process.cwd(), 'perf-budget.json');
157
+ if (fs.existsSync(configPath)) {
158
+ console.log(chalk_1.default.yellow('Config already exists!'));
159
+ return;
160
+ }
161
+ const defaultConfig = {
162
+ maxTotalSize: 500000,
163
+ maxGzippedSize: 150000,
164
+ maxChunkSize: 250000,
165
+ maxDependencySize: 100000,
166
+ thresholds: {
167
+ warning: 10,
168
+ error: 20
169
+ }
170
+ };
171
+ fs.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2));
172
+ console.log(chalk_1.default.green(`✅ Created ${configPath}`));
173
+ console.log('\nAdjust the values in the config file to match your requirements.');
174
+ });
175
+ program.parse(process.argv);
176
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { BudgetResult } from '../lib/types';
2
+ export interface GitHubConfig {
3
+ token: string;
4
+ owner: string;
5
+ repo: string;
6
+ prNumber?: number;
7
+ }
8
+ export declare class GitHubReporter {
9
+ private config;
10
+ constructor(config: GitHubConfig);
11
+ report(result: BudgetResult): Promise<void>;
12
+ private formatComment;
13
+ private postComment;
14
+ }
15
+ export declare function getGitHubEnv(): GitHubConfig | null;
16
+ //# sourceMappingURL=reporter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../../src/github/reporter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAG5C,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAe;gBAEjB,MAAM,EAAE,YAAY;IAI1B,MAAM,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAUjD,OAAO,CAAC,aAAa;YAuBP,WAAW;CAgC1B;AAED,wBAAgB,YAAY,IAAI,YAAY,GAAG,IAAI,CAiBlD"}
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GitHubReporter = void 0;
4
+ exports.getGitHubEnv = getGitHubEnv;
5
+ const analyzer_1 = require("../lib/analyzer");
6
+ class GitHubReporter {
7
+ constructor(config) {
8
+ this.config = config;
9
+ }
10
+ async report(result) {
11
+ const body = this.formatComment(result);
12
+ if (this.config.prNumber) {
13
+ await this.postComment(body);
14
+ }
15
+ else {
16
+ console.log(body);
17
+ }
18
+ }
19
+ formatComment(result) {
20
+ const status = result.passed ? '✅ Passed' : '❌ Failed';
21
+ let comment = `## 📦 Bundle Size Report\n\n`;
22
+ comment += `**Status:** ${status}\n\n`;
23
+ comment += `| Metric | Size |\n`;
24
+ comment += `|--------|------|\n`;
25
+ comment += `| Total | ${(0, analyzer_1.formatSize)(result.totalSize)} |\n`;
26
+ comment += `| Gzipped | ${(0, analyzer_1.formatSize)(result.totalGzipped)} |\n\n`;
27
+ if (!result.passed && result.violations.length > 0) {
28
+ comment += `### ⚠️ Budget Violations\n\n`;
29
+ comment += `| Type | Name | Current | Limit | Over |\n`;
30
+ comment += `|------|------|---------|-------|------|\n`;
31
+ for (const v of result.violations) {
32
+ comment += `| ${v.type} | ${v.name} | ${(0, analyzer_1.formatSize)(v.current)} | ${(0, analyzer_1.formatSize)(v.limit)} | +${v.percentageOver.toFixed(1)}% |\n`;
33
+ }
34
+ }
35
+ return comment;
36
+ }
37
+ async postComment(body) {
38
+ const { execSync } = require('child_process');
39
+ try {
40
+ const query = `
41
+ mutation($pullRequestId: ID!, $body: String!) {
42
+ addComment(input: { subjectId: $pullRequestId, body: $body }) {
43
+ commentEdge {
44
+ node {
45
+ id
46
+ }
47
+ }
48
+ }
49
+ }
50
+ `;
51
+ const prIdQuery = `
52
+ query($owner: String!, $repo: String!, $prNumber: Int!) {
53
+ repository(owner: $owner, name: $repo) {
54
+ pullRequest(number: $prNumber) {
55
+ id
56
+ }
57
+ }
58
+ }
59
+ `;
60
+ console.log(`Posting comment to PR #${this.config.prNumber}...`);
61
+ console.log(body);
62
+ }
63
+ catch (error) {
64
+ console.error('Failed to post GitHub comment:', error);
65
+ }
66
+ }
67
+ }
68
+ exports.GitHubReporter = GitHubReporter;
69
+ function getGitHubEnv() {
70
+ const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
71
+ const repo = process.env.GITHUB_REPOSITORY;
72
+ const prNumber = process.env.GITHUB_PR_NUMBER ? parseInt(process.env.GITHUB_PR_NUMBER) : undefined;
73
+ if (!token || !repo) {
74
+ return null;
75
+ }
76
+ const [owner, repoName] = repo.split('/');
77
+ return {
78
+ token,
79
+ owner,
80
+ repo: repoName,
81
+ prNumber
82
+ };
83
+ }
84
+ //# sourceMappingURL=reporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reporter.js","sourceRoot":"","sources":["../../src/github/reporter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export { BundleAnalyzer, formatSize, saveAnalysis, loadAnalysis } from './lib/analyzer';
2
+ export { BudgetChecker, loadConfig } from './lib/budget';
3
+ export { GitHubReporter, getGitHubEnv } from './github/reporter';
4
+ export * from './lib/types';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACxF,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjE,cAAc,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.getGitHubEnv = exports.GitHubReporter = exports.loadConfig = exports.BudgetChecker = exports.loadAnalysis = exports.saveAnalysis = exports.formatSize = exports.BundleAnalyzer = void 0;
18
+ var analyzer_1 = require("./lib/analyzer");
19
+ Object.defineProperty(exports, "BundleAnalyzer", { enumerable: true, get: function () { return analyzer_1.BundleAnalyzer; } });
20
+ Object.defineProperty(exports, "formatSize", { enumerable: true, get: function () { return analyzer_1.formatSize; } });
21
+ Object.defineProperty(exports, "saveAnalysis", { enumerable: true, get: function () { return analyzer_1.saveAnalysis; } });
22
+ Object.defineProperty(exports, "loadAnalysis", { enumerable: true, get: function () { return analyzer_1.loadAnalysis; } });
23
+ var budget_1 = require("./lib/budget");
24
+ Object.defineProperty(exports, "BudgetChecker", { enumerable: true, get: function () { return budget_1.BudgetChecker; } });
25
+ Object.defineProperty(exports, "loadConfig", { enumerable: true, get: function () { return budget_1.loadConfig; } });
26
+ var reporter_1 = require("./github/reporter");
27
+ Object.defineProperty(exports, "GitHubReporter", { enumerable: true, get: function () { return reporter_1.GitHubReporter; } });
28
+ Object.defineProperty(exports, "getGitHubEnv", { enumerable: true, get: function () { return reporter_1.getGitHubEnv; } });
29
+ __exportStar(require("./lib/types"), exports);
30
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,2CAAwF;AAA/E,0GAAA,cAAc,OAAA;AAAE,sGAAA,UAAU,OAAA;AAAE,wGAAA,YAAY,OAAA;AAAE,wGAAA,YAAY,OAAA;AAC/D,uCAAyD;AAAhD,uGAAA,aAAa,OAAA;AAAE,oGAAA,UAAU,OAAA;AAClC,8CAAiE;AAAxD,0GAAA,cAAc,OAAA;AAAE,wGAAA,YAAY,OAAA;AACrC,8CAA4B"}
@@ -0,0 +1,21 @@
1
+ import { BundleAnalysis, BuildTool } from './types';
2
+ export declare class BundleAnalyzer {
3
+ private buildDir;
4
+ private buildTool;
5
+ constructor(buildDir: string);
6
+ private detectBuildTool;
7
+ getBuildTool(): BuildTool;
8
+ analyze(): Promise<BundleAnalysis>;
9
+ private analyzeNextJs;
10
+ private analyzeVite;
11
+ private analyzeWebpack;
12
+ private analyzeGeneric;
13
+ private findChunks;
14
+ private getAllFiles;
15
+ private estimateDependencies;
16
+ private parseWebpackStats;
17
+ }
18
+ export declare function formatSize(bytes: number): string;
19
+ export declare function saveAnalysis(analysis: BundleAnalysis, outputPath: string): Promise<void>;
20
+ export declare function loadAnalysis(inputPath: string): Promise<BundleAnalysis>;
21
+ //# sourceMappingURL=analyzer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../../src/lib/analyzer.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAkB,SAAS,EAAE,MAAM,SAAS,CAAC;AAEpE,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,SAAS,CAAwB;gBAE7B,QAAQ,EAAE,MAAM;IAK5B,OAAO,CAAC,eAAe;IAmBvB,YAAY,IAAI,SAAS;IAInB,OAAO,IAAI,OAAO,CAAC,cAAc,CAAC;YAa1B,aAAa;YAmBb,WAAW;YAgBX,cAAc;YAWd,cAAc;YAed,UAAU;IAyBxB,OAAO,CAAC,WAAW;IAqBnB,OAAO,CAAC,oBAAoB;IA8B5B,OAAO,CAAC,iBAAiB;CA2B1B;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQhD;AAED,wBAAsB,YAAY,CAChC,QAAQ,EAAE,cAAc,EACxB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAGf;AAED,wBAAsB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAE7E"}