@intellectronica/ruler 0.3.41 → 0.3.42
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/README.md +40 -28
- package/dist/agents/AbstractAgent.d.ts +53 -0
- package/dist/agents/AgentsMdAgent.d.ts +14 -0
- package/dist/agents/AiderAgent.d.ts +14 -0
- package/dist/agents/AiderAgent.js +3 -1
- package/dist/agents/AmazonQCliAgent.d.ts +13 -0
- package/dist/agents/AmpAgent.d.ts +6 -0
- package/dist/agents/AntigravityAgent.d.ts +10 -0
- package/dist/agents/AugmentCodeAgent.d.ts +13 -0
- package/dist/agents/ClaudeAgent.d.ts +13 -0
- package/dist/agents/ClineAgent.d.ts +9 -0
- package/dist/agents/CodexCliAgent.d.ts +31 -0
- package/dist/agents/CopilotAgent.d.ts +20 -0
- package/dist/agents/CrushAgent.d.ts +14 -0
- package/dist/agents/CrushAgent.js +5 -2
- package/dist/agents/CursorAgent.d.ts +17 -0
- package/dist/agents/FactoryDroidAgent.d.ts +13 -0
- package/dist/agents/FirebaseAgent.d.ts +11 -0
- package/dist/agents/FirebenderAgent.d.ts +36 -0
- package/dist/agents/GeminiCliAgent.d.ts +11 -0
- package/dist/agents/GeminiCliAgent.js +2 -2
- package/dist/agents/GooseAgent.d.ts +12 -0
- package/dist/agents/IAgent.d.ts +72 -0
- package/dist/agents/JetBrainsAiAssistantAgent.d.ts +10 -0
- package/dist/agents/JulesAgent.d.ts +5 -0
- package/dist/agents/JunieAgent.d.ts +12 -0
- package/dist/agents/KiloCodeAgent.d.ts +14 -0
- package/dist/agents/KiroAgent.d.ts +8 -0
- package/dist/agents/MistralVibeAgent.d.ts +31 -0
- package/dist/agents/OpenCodeAgent.d.ts +11 -0
- package/dist/agents/OpenCodeAgent.js +14 -9
- package/dist/agents/OpenHandsAgent.d.ts +8 -0
- package/dist/agents/PiAgent.d.ts +9 -0
- package/dist/agents/QwenCodeAgent.d.ts +10 -0
- package/dist/agents/QwenCodeAgent.js +2 -2
- package/dist/agents/RooCodeAgent.d.ts +16 -0
- package/dist/agents/TraeAgent.d.ts +10 -0
- package/dist/agents/WarpAgent.d.ts +12 -0
- package/dist/agents/WindsurfAgent.d.ts +13 -0
- package/dist/agents/ZedAgent.d.ts +21 -0
- package/dist/agents/ZedAgent.js +5 -2
- package/dist/agents/agent-utils.d.ts +5 -0
- package/dist/agents/agent-utils.js +8 -5
- package/dist/agents/index.d.ts +9 -0
- package/dist/cli/commands.d.ts +4 -0
- package/dist/cli/commands.js +1 -2
- package/dist/cli/handlers.d.ts +41 -0
- package/dist/cli/handlers.js +75 -59
- package/dist/cli/index.d.ts +2 -0
- package/dist/constants.d.ts +35 -0
- package/dist/core/ConfigLoader.d.ts +57 -0
- package/dist/core/ConfigLoader.js +106 -39
- package/dist/core/FileSystemUtils.d.ts +51 -0
- package/dist/core/FileSystemUtils.js +37 -17
- package/dist/core/GitignoreUtils.d.ts +15 -0
- package/dist/core/GitignoreUtils.js +32 -1
- package/dist/core/RuleProcessor.d.ts +8 -0
- package/dist/core/SkillsProcessor.d.ts +127 -0
- package/dist/core/SkillsProcessor.js +104 -218
- package/dist/core/SkillsUtils.d.ts +26 -0
- package/dist/core/SubagentsProcessor.d.ts +38 -0
- package/dist/core/SubagentsUtils.d.ts +34 -0
- package/dist/core/UnifiedConfigLoader.d.ts +10 -0
- package/dist/core/UnifiedConfigLoader.js +61 -31
- package/dist/core/UnifiedConfigTypes.d.ts +95 -0
- package/dist/core/agent-selection.d.ts +12 -0
- package/dist/core/agent-selection.js +11 -3
- package/dist/core/apply-engine.d.ts +69 -0
- package/dist/core/apply-engine.js +57 -50
- package/dist/core/config-utils.d.ts +14 -0
- package/dist/core/config-utils.js +9 -3
- package/dist/core/hash.d.ts +2 -0
- package/dist/core/path-utils.d.ts +1 -0
- package/dist/core/path-utils.js +42 -0
- package/dist/core/revert-engine.d.ts +36 -0
- package/dist/core/revert-engine.js +70 -9
- package/dist/lib.d.ts +13 -0
- package/dist/lib.js +16 -3
- package/dist/mcp/capabilities.d.ts +20 -0
- package/dist/mcp/merge.d.ts +10 -0
- package/dist/mcp/merge.js +19 -1
- package/dist/mcp/propagateOpenCodeMcp.d.ts +2 -0
- package/dist/mcp/propagateOpenCodeMcp.js +21 -9
- package/dist/mcp/propagateOpenHandsMcp.d.ts +2 -0
- package/dist/mcp/propagateOpenHandsMcp.js +31 -15
- package/dist/mcp/validate.d.ts +7 -0
- package/dist/mcp/validate.js +6 -1
- package/dist/paths/mcp.d.ts +8 -0
- package/dist/paths/mcp.js +33 -4
- package/dist/revert.d.ts +6 -0
- package/dist/revert.js +39 -27
- package/dist/types.d.ts +87 -0
- package/dist/vscode/settings.d.ts +40 -0
- package/package.json +6 -4
package/dist/revert.js
CHANGED
|
@@ -35,7 +35,6 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.allAgents = void 0;
|
|
37
37
|
exports.revertAllAgentConfigs = revertAllAgentConfigs;
|
|
38
|
-
const path = __importStar(require("path"));
|
|
39
38
|
const fs_1 = require("fs");
|
|
40
39
|
const FileSystemUtils = __importStar(require("./core/FileSystemUtils"));
|
|
41
40
|
const ConfigLoader_1 = require("./core/ConfigLoader");
|
|
@@ -45,7 +44,11 @@ const constants_1 = require("./constants");
|
|
|
45
44
|
const revert_engine_1 = require("./core/revert-engine");
|
|
46
45
|
const agent_selection_1 = require("./core/agent-selection");
|
|
47
46
|
const config_utils_1 = require("./core/config-utils");
|
|
47
|
+
const GitignoreUtils_1 = require("./core/GitignoreUtils");
|
|
48
48
|
const agents = agents_1.allAgents;
|
|
49
|
+
const RULER_IGNORE_START_MARKER = '# START Ruler Generated Files';
|
|
50
|
+
const RULER_IGNORE_END_MARKER = '# END Ruler Generated Files';
|
|
51
|
+
const MANAGED_IGNORE_FILES = ['.gitignore', '.git/info/exclude'];
|
|
49
52
|
/**
|
|
50
53
|
* Reverts ruler configurations for selected AI agents.
|
|
51
54
|
*/
|
|
@@ -55,6 +58,7 @@ async function revertAllAgentConfigs(projectRoot, includedAgents, configPath, ke
|
|
|
55
58
|
projectRoot,
|
|
56
59
|
cliAgents: includedAgents,
|
|
57
60
|
configPath,
|
|
61
|
+
checkGlobal: !localOnly,
|
|
58
62
|
});
|
|
59
63
|
const rulerDir = await FileSystemUtils.findRulerDir(projectRoot, !localOnly);
|
|
60
64
|
if (!rulerDir) {
|
|
@@ -77,18 +81,19 @@ async function revertAllAgentConfigs(projectRoot, includedAgents, configPath, ke
|
|
|
77
81
|
// Fall back to the old logic without validation
|
|
78
82
|
if (config.cliAgents && config.cliAgents.length > 0) {
|
|
79
83
|
const filters = config.cliAgents.map((n) => n.toLowerCase());
|
|
80
|
-
|
|
81
|
-
|
|
84
|
+
const validAgentIdentifiers = new Set(agents.map((agent) => agent.getIdentifier()));
|
|
85
|
+
selected = agents.filter((agent) => filters.some((f) => (0, agent_selection_1.agentMatchesFilter)(agent, f, validAgentIdentifiers)));
|
|
82
86
|
}
|
|
83
87
|
else if (config.defaultAgents && config.defaultAgents.length > 0) {
|
|
84
88
|
const defaults = config.defaultAgents.map((n) => n.toLowerCase());
|
|
89
|
+
const validAgentIdentifiers = new Set(agents.map((agent) => agent.getIdentifier()));
|
|
85
90
|
selected = agents.filter((agent) => {
|
|
86
91
|
const identifier = agent.getIdentifier();
|
|
87
92
|
const override = config.agentConfigs[identifier]?.enabled;
|
|
88
93
|
if (override !== undefined) {
|
|
89
94
|
return override;
|
|
90
95
|
}
|
|
91
|
-
return defaults.some((d) =>
|
|
96
|
+
return defaults.some((d) => (0, agent_selection_1.agentMatchesFilter)(agent, d, validAgentIdentifiers));
|
|
92
97
|
});
|
|
93
98
|
}
|
|
94
99
|
else {
|
|
@@ -118,10 +123,10 @@ async function revertAllAgentConfigs(projectRoot, includedAgents, configPath, ke
|
|
|
118
123
|
// Clean up auxiliary files and directories
|
|
119
124
|
const cleanupResult = await (0, revert_engine_1.cleanUpAuxiliaryFiles)(projectRoot, verbose, dryRun);
|
|
120
125
|
totalFilesRemoved += cleanupResult.additionalFilesRemoved;
|
|
121
|
-
// Clean
|
|
122
|
-
const
|
|
123
|
-
? await
|
|
124
|
-
:
|
|
126
|
+
// Clean managed ignore blocks if reverting all agents.
|
|
127
|
+
const cleanedIgnoreFiles = !config.cliAgents || config.cliAgents.length === 0
|
|
128
|
+
? await cleanManagedIgnoreFiles(projectRoot, verbose, dryRun)
|
|
129
|
+
: [];
|
|
125
130
|
// Display summary
|
|
126
131
|
const prefix = (0, constants_1.actionPrefix)(dryRun);
|
|
127
132
|
if (dryRun) {
|
|
@@ -139,47 +144,54 @@ async function revertAllAgentConfigs(projectRoot, includedAgents, configPath, ke
|
|
|
139
144
|
if (cleanupResult.directoriesRemoved > 0) {
|
|
140
145
|
console.log(` Empty directories removed: ${cleanupResult.directoriesRemoved}`);
|
|
141
146
|
}
|
|
142
|
-
|
|
143
|
-
console.log(`
|
|
147
|
+
for (const ignoreFile of cleanedIgnoreFiles) {
|
|
148
|
+
console.log(` ${ignoreFile} cleaned: yes`);
|
|
144
149
|
}
|
|
145
150
|
}
|
|
146
151
|
/**
|
|
147
|
-
* Removes the ruler-managed block from
|
|
152
|
+
* Removes the ruler-managed block from ignore files Ruler can update.
|
|
148
153
|
*/
|
|
149
|
-
async function
|
|
150
|
-
const
|
|
154
|
+
async function cleanManagedIgnoreFiles(projectRoot, verbose, dryRun) {
|
|
155
|
+
const cleanedFiles = [];
|
|
156
|
+
for (const ignoreFile of MANAGED_IGNORE_FILES) {
|
|
157
|
+
if (await cleanIgnoreFile(projectRoot, ignoreFile, verbose, dryRun)) {
|
|
158
|
+
cleanedFiles.push(ignoreFile);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return cleanedFiles;
|
|
162
|
+
}
|
|
163
|
+
async function cleanIgnoreFile(projectRoot, ignoreFile, verbose, dryRun) {
|
|
164
|
+
const ignorePath = await (0, GitignoreUtils_1.resolveIgnoreFilePath)(projectRoot, ignoreFile);
|
|
151
165
|
try {
|
|
152
|
-
await fs_1.promises.access(
|
|
166
|
+
await fs_1.promises.access(ignorePath);
|
|
153
167
|
}
|
|
154
168
|
catch {
|
|
155
|
-
(0, constants_1.logVerbose)(
|
|
169
|
+
(0, constants_1.logVerbose)(`No ${ignoreFile} file found`, verbose);
|
|
156
170
|
return false;
|
|
157
171
|
}
|
|
158
|
-
const content = await fs_1.promises.readFile(
|
|
159
|
-
const
|
|
160
|
-
const
|
|
161
|
-
const startIndex = content.indexOf(startMarker);
|
|
162
|
-
const endIndex = content.indexOf(endMarker);
|
|
172
|
+
const content = await fs_1.promises.readFile(ignorePath, 'utf8');
|
|
173
|
+
const startIndex = content.indexOf(RULER_IGNORE_START_MARKER);
|
|
174
|
+
const endIndex = content.indexOf(RULER_IGNORE_END_MARKER);
|
|
163
175
|
if (startIndex === -1 || endIndex === -1) {
|
|
164
|
-
(0, constants_1.logVerbose)(
|
|
176
|
+
(0, constants_1.logVerbose)(`No ruler-managed block found in ${ignoreFile}`, verbose);
|
|
165
177
|
return false;
|
|
166
178
|
}
|
|
167
179
|
const prefix = (0, constants_1.actionPrefix)(dryRun);
|
|
168
180
|
if (dryRun) {
|
|
169
|
-
(0, constants_1.logVerbose)(`${prefix} Would remove ruler block from
|
|
181
|
+
(0, constants_1.logVerbose)(`${prefix} Would remove ruler block from ${ignoreFile}`, verbose);
|
|
170
182
|
}
|
|
171
183
|
else {
|
|
172
184
|
const beforeBlock = content.substring(0, startIndex);
|
|
173
|
-
const afterBlock = content.substring(endIndex +
|
|
185
|
+
const afterBlock = content.substring(endIndex + RULER_IGNORE_END_MARKER.length);
|
|
174
186
|
let newContent = beforeBlock + afterBlock;
|
|
175
187
|
newContent = newContent.replace(/\n{3,}/g, '\n\n'); // Replace 3+ newlines with 2
|
|
176
188
|
if (newContent.trim() === '') {
|
|
177
|
-
await fs_1.promises.unlink(
|
|
178
|
-
(0, constants_1.logVerbose)(`${prefix} Removed empty
|
|
189
|
+
await fs_1.promises.unlink(ignorePath);
|
|
190
|
+
(0, constants_1.logVerbose)(`${prefix} Removed empty ${ignoreFile} file`, verbose);
|
|
179
191
|
}
|
|
180
192
|
else {
|
|
181
|
-
await fs_1.promises.writeFile(
|
|
182
|
-
(0, constants_1.logVerbose)(`${prefix} Removed ruler block from
|
|
193
|
+
await fs_1.promises.writeFile(ignorePath, newContent);
|
|
194
|
+
(0, constants_1.logVerbose)(`${prefix} Removed ruler block from ${ignoreFile}`, verbose);
|
|
183
195
|
}
|
|
184
196
|
}
|
|
185
197
|
return true;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for Model Context Protocol (MCP) server configuration.
|
|
3
|
+
*/
|
|
4
|
+
export type McpStrategy = 'merge' | 'overwrite';
|
|
5
|
+
/** MCP configuration for an agent or global. */
|
|
6
|
+
export interface McpConfig {
|
|
7
|
+
/** Enable or disable MCP propagation (merge or overwrite). */
|
|
8
|
+
enabled?: boolean;
|
|
9
|
+
/** Merge strategy: 'merge' to merge servers, 'overwrite' to replace config. */
|
|
10
|
+
strategy?: McpStrategy;
|
|
11
|
+
}
|
|
12
|
+
/** Global MCP configuration section (same as agent-specific config). */
|
|
13
|
+
export type GlobalMcpConfig = McpConfig;
|
|
14
|
+
/** Gitignore configuration for automatic .gitignore file updates. */
|
|
15
|
+
export interface GitignoreConfig {
|
|
16
|
+
/** Enable or disable automatic .gitignore updates. */
|
|
17
|
+
enabled?: boolean;
|
|
18
|
+
/** Write managed ignore entries to .git/info/exclude instead of .gitignore. */
|
|
19
|
+
local?: boolean;
|
|
20
|
+
}
|
|
21
|
+
/** Backup configuration for .bak file generation. */
|
|
22
|
+
export interface BackupConfig {
|
|
23
|
+
/** Enable or disable creation of .bak backup files. */
|
|
24
|
+
enabled?: boolean;
|
|
25
|
+
}
|
|
26
|
+
/** Skills configuration for automatic skills distribution. */
|
|
27
|
+
export interface SkillsConfig {
|
|
28
|
+
/** Enable or disable skills support. */
|
|
29
|
+
enabled?: boolean;
|
|
30
|
+
}
|
|
31
|
+
/** Information about a discovered skill. */
|
|
32
|
+
export interface SkillInfo {
|
|
33
|
+
/** Name of the skill (directory name). */
|
|
34
|
+
name: string;
|
|
35
|
+
/** Absolute path to the skill directory. */
|
|
36
|
+
path: string;
|
|
37
|
+
/** Whether the directory contains a SKILL.md file. */
|
|
38
|
+
hasSkillMd: boolean;
|
|
39
|
+
/** Whether this is a valid skill. */
|
|
40
|
+
valid: boolean;
|
|
41
|
+
/** Error message if invalid. */
|
|
42
|
+
error?: string;
|
|
43
|
+
}
|
|
44
|
+
/** Subagents configuration for automatic subagent distribution. */
|
|
45
|
+
export interface SubagentsConfig {
|
|
46
|
+
/** Enable or disable subagents support. */
|
|
47
|
+
enabled?: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* When true, Ruler may delete previously generated native subagent
|
|
50
|
+
* directories that are stale (disabled, no source definitions, or
|
|
51
|
+
* deselected targets). Defaults to false (non-destructive).
|
|
52
|
+
*/
|
|
53
|
+
cleanup_orphaned?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* When true, `.ruler/agents/*.md` are also concatenated into the
|
|
56
|
+
* generated top-level rule files (CLAUDE.md, AGENTS.md, Copilot
|
|
57
|
+
* instructions, etc.). When false (default), `.ruler/agents/` is
|
|
58
|
+
* skipped during rule concatenation, mirroring `.ruler/skills/`.
|
|
59
|
+
*/
|
|
60
|
+
include_in_rules?: boolean;
|
|
61
|
+
}
|
|
62
|
+
/** Frontmatter fields recognised on a source subagent definition. */
|
|
63
|
+
export interface SubagentFrontmatter {
|
|
64
|
+
name: string;
|
|
65
|
+
description: string;
|
|
66
|
+
tools?: string[];
|
|
67
|
+
model?: string;
|
|
68
|
+
readonly?: boolean;
|
|
69
|
+
is_background?: boolean;
|
|
70
|
+
}
|
|
71
|
+
/** Information about a discovered subagent. */
|
|
72
|
+
export interface SubagentInfo {
|
|
73
|
+
/** Name of the subagent (filename stem and frontmatter `name`). */
|
|
74
|
+
name: string;
|
|
75
|
+
/** Absolute path to the source `.md` file. */
|
|
76
|
+
path: string;
|
|
77
|
+
/** Relative `.md` path under `.ruler/agents/` (preserves nested layout). */
|
|
78
|
+
sourceRelativePath?: string;
|
|
79
|
+
/** Parsed frontmatter (only present when valid). */
|
|
80
|
+
frontmatter?: SubagentFrontmatter;
|
|
81
|
+
/** Body content after the frontmatter delimiter. */
|
|
82
|
+
body?: string;
|
|
83
|
+
/** Whether this subagent passed validation. */
|
|
84
|
+
valid: boolean;
|
|
85
|
+
/** Error message if invalid. */
|
|
86
|
+
error?: string;
|
|
87
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { McpStrategy } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* VSCode settings.json structure for Augment MCP configuration
|
|
4
|
+
*/
|
|
5
|
+
export interface VSCodeSettings {
|
|
6
|
+
'augment.advanced'?: {
|
|
7
|
+
mcpServers?: AugmentMcpServer[];
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
};
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Augment MCP server configuration format
|
|
14
|
+
*/
|
|
15
|
+
export interface AugmentMcpServer {
|
|
16
|
+
name: string;
|
|
17
|
+
command: string;
|
|
18
|
+
args?: string[];
|
|
19
|
+
env?: Record<string, string>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Read VSCode settings.json file
|
|
23
|
+
*/
|
|
24
|
+
export declare function readVSCodeSettings(settingsPath: string): Promise<VSCodeSettings>;
|
|
25
|
+
/**
|
|
26
|
+
* Write VSCode settings.json file
|
|
27
|
+
*/
|
|
28
|
+
export declare function writeVSCodeSettings(settingsPath: string, settings: VSCodeSettings): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Transform ruler MCP config to Augment MCP server array format
|
|
31
|
+
*/
|
|
32
|
+
export declare function transformRulerToAugmentMcp(rulerMcpJson: Record<string, unknown>): AugmentMcpServer[];
|
|
33
|
+
/**
|
|
34
|
+
* Merge MCP servers into VSCode settings using the specified strategy
|
|
35
|
+
*/
|
|
36
|
+
export declare function mergeAugmentMcpServers(existingSettings: VSCodeSettings, newServers: AugmentMcpServer[], strategy: McpStrategy): VSCodeSettings;
|
|
37
|
+
/**
|
|
38
|
+
* Get the VSCode settings.json path for a project (local)
|
|
39
|
+
*/
|
|
40
|
+
export declare function getVSCodeSettingsPath(projectRoot: string): string;
|
package/package.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intellectronica/ruler",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.42",
|
|
4
4
|
"description": "Ruler — apply the same rules to all coding agents",
|
|
5
5
|
"main": "dist/lib.js",
|
|
6
|
+
"types": "dist/lib.d.ts",
|
|
6
7
|
"scripts": {
|
|
7
8
|
"lint": "eslint \"src/**/*.{ts,tsx}\"",
|
|
8
|
-
"format": "prettier --write \"src/**/*.{ts,tsx,json,md}\"",
|
|
9
|
-
"
|
|
9
|
+
"format": "prettier --write package.json package-lock.json tsconfig.json README.md \".github/**/*.yml\" \"src/**/*.{ts,tsx,json,md}\" \"tests/**/*.{ts,tsx,json,md}\"",
|
|
10
|
+
"format:check": "prettier --check package.json package-lock.json tsconfig.json README.md \".github/**/*.yml\" \"src/**/*.{ts,tsx,json,md}\" \"tests/**/*.{ts,tsx,json,md}\"",
|
|
11
|
+
"test": "jest --coverage",
|
|
10
12
|
"test:watch": "jest --watch",
|
|
11
13
|
"test:coverage": "jest --coverage",
|
|
12
14
|
"test:integration": "jest tests/e2e/ruler.integration.test.ts --verbose",
|
|
@@ -66,7 +68,7 @@
|
|
|
66
68
|
},
|
|
67
69
|
"dependencies": {
|
|
68
70
|
"@iarna/toml": "^2.2.5",
|
|
69
|
-
"js-yaml": "^4.1.
|
|
71
|
+
"js-yaml": "^4.1.1",
|
|
70
72
|
"yargs": "^18.0.0",
|
|
71
73
|
"zod": "^4.1.12"
|
|
72
74
|
}
|