aicm 0.17.0 → 0.17.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/README.md +128 -138
- package/dist/cli.js +5 -0
- package/dist/commands/clean.d.ts +19 -0
- package/dist/commands/clean.js +175 -0
- package/dist/commands/install.js +18 -63
- package/dist/utils/rules-file-writer.d.ts +4 -0
- package/dist/utils/rules-file-writer.js +15 -0
- package/dist/utils/workspace-discovery.d.ts +13 -0
- package/dist/utils/workspace-discovery.js +53 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,56 +2,76 @@
|
|
|
2
2
|
|
|
3
3
|
> AI Configuration Manager
|
|
4
4
|
|
|
5
|
-
A CLI tool for managing Agentic configurations across projects
|
|
5
|
+
A CLI tool for managing Agentic configurations across projects.
|
|
6
6
|
|
|
7
7
|

|
|
8
8
|
|
|
9
|
+
## Table of Contents
|
|
10
|
+
|
|
11
|
+
- [Why](#why)
|
|
12
|
+
- [Supported Environments](#supported-environments)
|
|
13
|
+
- [Getting Started](#getting-started)
|
|
14
|
+
- [Creating a Preset](#creating-a-preset)
|
|
15
|
+
- [Using a Preset](#using-a-preset)
|
|
16
|
+
- [Features](#features)
|
|
17
|
+
- [Rules](#using-rules)
|
|
18
|
+
- [Commands](#using-commands)
|
|
19
|
+
- [MCP Servers](#mcp-servers)
|
|
20
|
+
- [Auxiliary Files](#referencing-auxiliary-files)
|
|
21
|
+
- [Overrides](#overrides)
|
|
22
|
+
- [Workspaces Support](#workspaces-support)
|
|
23
|
+
- [Configuration](#configuration)
|
|
24
|
+
- [CLI Commands](#cli-commands)
|
|
25
|
+
- [Node.js API](#nodejs-api)
|
|
26
|
+
|
|
9
27
|
## Why
|
|
10
28
|
|
|
11
|
-
Modern AI-powered IDEs like Cursor and Agents like Codex
|
|
29
|
+
Modern AI-powered IDEs like Cursor and Agents like Codex allow developers to add custom instructions, commands, and MCP servers. However, keeping these configurations consistent across a team or multiple projects is a challenge.
|
|
12
30
|
|
|
13
|
-
**aicm**
|
|
31
|
+
**aicm** enables **"Write Once, Use Everywhere"** for your AI configurations.
|
|
14
32
|
|
|
15
|
-
|
|
33
|
+
- **Team Consistency:** Ensure every developer on your team uses the same rules and best practices.
|
|
34
|
+
- **Reusable Presets:** Bundle your rules, commands & MCP configurations into npm packages (e.g., `@company/ai-preset`) to share them across your organization.
|
|
35
|
+
- **Multi-Target Support:** Write rules once in the comprehensive `.mdc` format, and automatically deploy them to Cursor, Windsurf, Codex, and Claude.
|
|
16
36
|
|
|
17
|
-
|
|
37
|
+
## Supported Environments
|
|
18
38
|
|
|
19
|
-
|
|
20
|
-
- **Windsurf**: Generates `.windsurfrules` file
|
|
21
|
-
- **Codex**: Generates `AGENTS.md` file
|
|
22
|
-
- **Claude**: Generates `CLAUDE.md` file
|
|
39
|
+
aicm acts as a bridge between your configuration and your AI tools. It accepts Cursor's `.mdc` format and can transform it for other environments:
|
|
23
40
|
|
|
24
|
-
|
|
41
|
+
| Target | Installation |
|
|
42
|
+
| ------------ | ------------------------------------------------------------------------------ |
|
|
43
|
+
| **Cursor** | Copies `.mdc` files to `.cursor/rules/aicm/` and configures `.cursor/mcp.json` |
|
|
44
|
+
| **Windsurf** | Generates a `.windsurfrules` file that links to rules in `.aicm/` |
|
|
45
|
+
| **Codex** | Generates an `AGENTS.md` file that references rules in `.aicm/` |
|
|
46
|
+
| **Claude** | Generates a `CLAUDE.md` file that references rules in `.aicm/` |
|
|
25
47
|
|
|
26
48
|
## Getting Started
|
|
27
49
|
|
|
28
50
|
The easiest way to get started with aicm is by using **presets** - npm packages containing rules and MCP configurations that you can install in any project.
|
|
29
51
|
|
|
30
|
-
###
|
|
52
|
+
### Demo
|
|
31
53
|
|
|
32
|
-
|
|
54
|
+
We'll install [an npm package](https://github.com/ranyitz/pirate-coding) containing a simple "Pirate Coding" preset to demonstrate how aicm works.
|
|
55
|
+
|
|
56
|
+
1. **Install the demo preset package**:
|
|
33
57
|
|
|
34
58
|
```bash
|
|
35
|
-
npm install --save-dev
|
|
59
|
+
npm install --save-dev pirate-coding
|
|
36
60
|
```
|
|
37
61
|
|
|
38
|
-
2. **Create an `aicm.json` file** in your project
|
|
62
|
+
2. **Create an `aicm.json` file** in your project:
|
|
39
63
|
|
|
40
|
-
```
|
|
41
|
-
{ "presets": ["
|
|
64
|
+
```bash
|
|
65
|
+
echo '{ "presets": ["pirate-coding"] }' > aicm.json
|
|
42
66
|
```
|
|
43
67
|
|
|
44
|
-
3. **
|
|
68
|
+
3. **Install all rules & MCPs from your configuration**:
|
|
45
69
|
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
"scripts": {
|
|
49
|
-
"prepare": "npx aicm -y install"
|
|
50
|
-
}
|
|
51
|
-
}
|
|
70
|
+
```bash
|
|
71
|
+
npx aicm install
|
|
52
72
|
```
|
|
53
73
|
|
|
54
|
-
|
|
74
|
+
After installation, open Cursor and ask it to do something. Your AI assistant will respond with pirate-themed coding advice.
|
|
55
75
|
|
|
56
76
|
### Creating a Preset
|
|
57
77
|
|
|
@@ -85,48 +105,59 @@ The rules are now installed in `.cursor/rules/aicm/` and any MCP servers are con
|
|
|
85
105
|
|
|
86
106
|
> **Note:** This is syntactic sugar for `@team/ai-preset/aicm.json`.
|
|
87
107
|
|
|
88
|
-
### Using
|
|
108
|
+
### Using a Preset
|
|
89
109
|
|
|
90
|
-
|
|
110
|
+
To use a real preset in your production project:
|
|
111
|
+
|
|
112
|
+
1. **Install a preset npm package**:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
npm install --save-dev @team/ai-preset
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
2. **Create an `aicm.json` file** in your project root:
|
|
91
119
|
|
|
92
120
|
```json
|
|
93
|
-
{
|
|
94
|
-
"rulesDir": "path/to/rules/dir"
|
|
95
|
-
}
|
|
121
|
+
{ "presets": ["@team/ai-preset"] }
|
|
96
122
|
```
|
|
97
123
|
|
|
98
|
-
**
|
|
124
|
+
3. **Add a prepare script** to your `package.json` to ensure rules are always up to date:
|
|
99
125
|
|
|
100
|
-
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"scripts": {
|
|
129
|
+
"prepare": "npx aicm -y install"
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
```
|
|
101
133
|
|
|
102
|
-
|
|
134
|
+
The rules are now installed in `.cursor/rules/aicm/` and any MCP servers are configured in `.cursor/mcp.json`.
|
|
103
135
|
|
|
104
|
-
|
|
105
|
-
# My Rule
|
|
136
|
+
### Notes
|
|
106
137
|
|
|
107
|
-
|
|
108
|
-
|
|
138
|
+
- Generated rules are always placed in a subdirectory for deterministic cleanup and easy gitignore.
|
|
139
|
+
- Users should add `.cursor/rules/aicm/` and `.aicm/` (for Windsurf/Codex) to `.gitignore` to avoid tracking generated rules.
|
|
109
140
|
|
|
110
|
-
|
|
141
|
+
## Features
|
|
111
142
|
|
|
112
|
-
|
|
143
|
+
### Using Rules
|
|
113
144
|
|
|
114
|
-
|
|
145
|
+
aicm uses Cursor's `.mdc` files for rules. Read more about the format [here](https://cursor.com/docs/context/rules).
|
|
115
146
|
|
|
116
|
-
|
|
117
|
-
# Generate Schema
|
|
147
|
+
Add a rules directory to your project configuration:
|
|
118
148
|
|
|
119
|
-
|
|
149
|
+
```json
|
|
150
|
+
{
|
|
151
|
+
"rulesDir": "./rules",
|
|
152
|
+
"targets": ["cursor"]
|
|
153
|
+
}
|
|
120
154
|
```
|
|
121
155
|
|
|
122
|
-
|
|
156
|
+
Rules are installed in `.cursor/rules/aicm/` and are loaded automatically by Cursor.
|
|
123
157
|
|
|
124
158
|
### Using Commands
|
|
125
159
|
|
|
126
|
-
Cursor supports custom commands that can be invoked directly in the chat interface. aicm can manage these command files
|
|
127
|
-
alongside your rules and MCP configurations so they install automatically into Cursor.
|
|
128
|
-
|
|
129
|
-
#### Local Commands
|
|
160
|
+
Cursor supports custom commands that can be invoked directly in the chat interface. aicm can manage these command files alongside your rules and MCP configurations.
|
|
130
161
|
|
|
131
162
|
Add a commands directory to your project configuration:
|
|
132
163
|
|
|
@@ -139,84 +170,66 @@ Add a commands directory to your project configuration:
|
|
|
139
170
|
|
|
140
171
|
Command files ending in `.md` are installed to `.cursor/commands/aicm/` and appear in Cursor under the `/` command menu.
|
|
141
172
|
|
|
142
|
-
|
|
173
|
+
### MCP Servers
|
|
143
174
|
|
|
144
|
-
|
|
175
|
+
You can configure MCP servers directly in your `aicm.json`, which is useful for sharing mcp configurations across your team or bundling them into presets.
|
|
145
176
|
|
|
146
177
|
```json
|
|
147
178
|
{
|
|
148
|
-
"
|
|
149
|
-
|
|
179
|
+
"mcpServers": {
|
|
180
|
+
"Playwright": {
|
|
181
|
+
"command": "npx",
|
|
182
|
+
"args": ["@playwright/mcp"]
|
|
183
|
+
}
|
|
184
|
+
}
|
|
150
185
|
}
|
|
151
186
|
```
|
|
152
187
|
|
|
153
|
-
|
|
154
|
-
If multiple presets provide a command at the same relative path, aicm will warn during installation and use the version from the
|
|
155
|
-
last preset listed in your configuration. Use `overrides` to explicitly choose a different definition when needed.
|
|
188
|
+
When installed, these servers are automatically added to your `.cursor/mcp.json`.
|
|
156
189
|
|
|
157
|
-
|
|
190
|
+
### Referencing Auxiliary Files
|
|
158
191
|
|
|
159
|
-
|
|
192
|
+
You can place any file (e.g., `example.ts`, `schema.json`, `guide.md`) in your `rulesDir` alongside your `.mdc` files. These assets are automatically copied to the target location. You can reference them in your rules using relative paths, and aicm will automatically rewrite the links to point to the correct location for each target IDE.
|
|
160
193
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
}
|
|
194
|
+
Example `rules/my-rule.mdc`:
|
|
195
|
+
|
|
196
|
+
```markdown
|
|
197
|
+
# My Rule
|
|
198
|
+
|
|
199
|
+
See [Example](./example.ts) for details.
|
|
169
200
|
```
|
|
170
201
|
|
|
171
|
-
|
|
202
|
+
#### Commands Referencing Files
|
|
172
203
|
|
|
173
|
-
|
|
174
|
-
|
|
204
|
+
You can also use this feature to create commands that reference auxiliary files in your `rulesDir`. Since assets in `rulesDir` are copied to the target directory, your commands can link to them.
|
|
205
|
+
|
|
206
|
+
For example, if you have a schema file at `rules/schema.json` and a command at `commands/generate-schema.md`:
|
|
207
|
+
|
|
208
|
+
```markdown
|
|
209
|
+
# Generate Schema
|
|
210
|
+
|
|
211
|
+
Use the schema defined in [Schema Template](../rules/schema.json) to generate the response.
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
When installed, `aicm` will automatically rewrite the link to point to the correct location of `schema.json` in the target environment (e.g., `../../rules/aicm/schema.json` for Cursor).
|
|
215
|
+
|
|
216
|
+
> **Note:** Path rewriting works for any relative path format in your commands - markdown links, inline code references, or bare paths - as long as they point to actual files in your `rulesDir`.
|
|
175
217
|
|
|
176
218
|
### Overrides
|
|
177
219
|
|
|
178
|
-
You can disable or replace specific rules provided by presets using the `overrides` field:
|
|
220
|
+
You can disable or replace specific rules or commands provided by presets using the `overrides` field:
|
|
179
221
|
|
|
180
222
|
```json
|
|
181
223
|
{
|
|
182
224
|
"presets": ["@company/ai-rules"],
|
|
183
225
|
"overrides": {
|
|
184
226
|
"rule-from-preset-a": "./rules/override-rule.mdc",
|
|
185
|
-
"rule-from-preset-b": false
|
|
227
|
+
"rule-from-preset-b": false,
|
|
228
|
+
"legacy-command": false
|
|
186
229
|
}
|
|
187
230
|
}
|
|
188
231
|
```
|
|
189
232
|
|
|
190
|
-
### Demo
|
|
191
|
-
|
|
192
|
-
We'll install [an npm package](https://github.com/ranyitz/pirate-coding) containing a simple preset to demonstrate how aicm works.
|
|
193
|
-
|
|
194
|
-
1. **Install the demo preset package**:
|
|
195
|
-
|
|
196
|
-
```bash
|
|
197
|
-
npm install --save-dev pirate-coding
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
2. **Create an `aicm.json` file** in your project:
|
|
201
|
-
|
|
202
|
-
```bash
|
|
203
|
-
echo '{ "presets": ["pirate-coding"] }' > aicm.json
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
3. **Install all rules & MCPs from your configuration**:
|
|
207
|
-
|
|
208
|
-
```bash
|
|
209
|
-
npx aicm install
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
This command installs all configured rules and MCPs to their IDE-specific locations.
|
|
213
|
-
|
|
214
|
-
After installation, open Cursor and ask it to do something. Your AI assistant will respond with pirate-themed coding advice. You can also ask it about the aicm library which uses https://gitmcp.io/ to give you advice based on the latest documentation.
|
|
215
|
-
|
|
216
|
-
## Security Note
|
|
217
|
-
|
|
218
|
-
To prevent [prompt-injection](https://en.wikipedia.org/wiki/Prompt_injection), use only packages from trusted sources.
|
|
219
|
-
|
|
220
233
|
## Workspaces Support
|
|
221
234
|
|
|
222
235
|
aicm supports workspaces by automatically discovering and installing configurations across multiple packages in your repository.
|
|
@@ -229,17 +242,14 @@ You can enable workspaces mode by setting the `workspaces` property to `true` in
|
|
|
229
242
|
}
|
|
230
243
|
```
|
|
231
244
|
|
|
232
|
-
aicm automatically detects workspaces if your `package.json` contains a `workspaces` configuration
|
|
233
|
-
|
|
234
|
-
### How It Works
|
|
235
|
-
|
|
236
|
-
1. **Discover packages**: Automatically find all directories containing `aicm.json` files in your repository
|
|
237
|
-
2. **Install per package**: Install rules and MCPs for each package individually in their respective directories
|
|
238
|
-
3. **Merge MCP servers**: Write a merged `.cursor/mcp.json` at the repository root containing all MCP servers from every package
|
|
245
|
+
aicm automatically detects workspaces if your `package.json` contains a `workspaces` configuration.
|
|
239
246
|
|
|
240
247
|
### How It Works
|
|
241
248
|
|
|
242
|
-
|
|
249
|
+
1. **Discover packages**: Automatically find all directories containing `aicm.json` files in your repository.
|
|
250
|
+
2. **Install per package**: Install rules and MCPs for each package individually in their respective directories.
|
|
251
|
+
3. **Merge MCP servers**: Write a merged `.cursor/mcp.json` at the repository root containing all MCP servers from every package.
|
|
252
|
+
4. **Merge commands**: Write a merged `.cursor/commands/aicm/` at the repository root containing all commands from every package.
|
|
243
253
|
|
|
244
254
|
For example, in a workspace structure like:
|
|
245
255
|
|
|
@@ -300,17 +310,7 @@ Create an `aicm.json` file in your project root, or an `aicm` key in your projec
|
|
|
300
310
|
- **workspaces**: Set to `true` to enable workspace mode. If not specified, aicm will automatically detect workspaces from your `package.json`.
|
|
301
311
|
- **skipInstall**: Set to `true` to skip rule installation for this package. Useful for preset packages that provide rules but shouldn't have rules installed into them.
|
|
302
312
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
- **Cursor**: MCP server configs are written to `.cursor/mcp.json`.
|
|
306
|
-
|
|
307
|
-
## Supported Targets
|
|
308
|
-
|
|
309
|
-
- **Cursor**: Rules are installed as individual `.mdc` files in the Cursor rules directory (`.cursor/rules/aicm/`), mcp servers are installed to `.cursor/mcp.json`
|
|
310
|
-
- **Windsurf**: Rules are installed in the `.aicm` directory which should be added to your `.gitignore` file. Our approach for Windsurf is to create links from the `.windsurfrules` file to the respective rules in the `.aicm` directory. There is no support for local mcp servers at the moment.
|
|
311
|
-
- **Codex**: Rules are installed in the `.aicm` directory and referenced from `AGENTS.md`.
|
|
312
|
-
|
|
313
|
-
## Commands
|
|
313
|
+
## CLI Commands
|
|
314
314
|
|
|
315
315
|
### Global Options
|
|
316
316
|
|
|
@@ -343,6 +343,14 @@ Options:
|
|
|
343
343
|
- `--verbose`: show detailed output and stack traces for debugging
|
|
344
344
|
- `--dry-run`: simulate installation without writing files, useful for validating presets in CI
|
|
345
345
|
|
|
346
|
+
### `clean`
|
|
347
|
+
|
|
348
|
+
Removes all files, directories & changes made by aicm.
|
|
349
|
+
|
|
350
|
+
```bash
|
|
351
|
+
npx aicm clean
|
|
352
|
+
```
|
|
353
|
+
|
|
346
354
|
## Node.js API
|
|
347
355
|
|
|
348
356
|
In addition to the CLI, aicm can be used programmatically in Node.js applications:
|
|
@@ -373,27 +381,9 @@ install({
|
|
|
373
381
|
});
|
|
374
382
|
```
|
|
375
383
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
#### `install(options?: InstallOptions): Promise<InstallResult>`
|
|
379
|
-
|
|
380
|
-
Installs rules and MCP servers based on configuration.
|
|
381
|
-
|
|
382
|
-
**Options:**
|
|
383
|
-
|
|
384
|
-
- `cwd`: Base directory to use instead of `process.cwd()`
|
|
385
|
-
- `config`: Custom config object to use instead of loading from file
|
|
386
|
-
- `installOnCI`: Run installation on CI environments (default: `false`)
|
|
387
|
-
- `verbose`: Show verbose output and stack traces for debugging (default: `false`)
|
|
388
|
-
- `dryRun`: Simulate installation without writing files, useful for preset validation in CI (default: `false`)
|
|
389
|
-
|
|
390
|
-
**Returns:**
|
|
391
|
-
|
|
392
|
-
A Promise that resolves to an object with:
|
|
384
|
+
## Security Note
|
|
393
385
|
|
|
394
|
-
-
|
|
395
|
-
- `error`: Error object if the operation failed
|
|
396
|
-
- `installedRuleCount`: Number of rules installed
|
|
386
|
+
To prevent [prompt-injection](https://en.wikipedia.org/wiki/Prompt_injection), use only packages from trusted sources.
|
|
397
387
|
|
|
398
388
|
## Contributing
|
|
399
389
|
|
package/dist/cli.js
CHANGED
|
@@ -10,6 +10,7 @@ const chalk_1 = __importDefault(require("chalk"));
|
|
|
10
10
|
const init_1 = require("./commands/init");
|
|
11
11
|
const install_1 = require("./commands/install");
|
|
12
12
|
const list_1 = require("./commands/list");
|
|
13
|
+
const clean_1 = require("./commands/clean");
|
|
13
14
|
// Define version from package.json
|
|
14
15
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
15
16
|
const pkg = require("../package.json");
|
|
@@ -48,6 +49,9 @@ async function runCli() {
|
|
|
48
49
|
case "list":
|
|
49
50
|
await (0, list_1.listCommand)();
|
|
50
51
|
break;
|
|
52
|
+
case "clean":
|
|
53
|
+
await (0, clean_1.cleanCommand)(args["--verbose"]);
|
|
54
|
+
break;
|
|
51
55
|
default:
|
|
52
56
|
showHelp();
|
|
53
57
|
break;
|
|
@@ -69,6 +73,7 @@ ${chalk_1.default.bold("COMMANDS")}
|
|
|
69
73
|
init Initialize a new aicm configuration file
|
|
70
74
|
install Install rules from configured sources
|
|
71
75
|
list List all configured rules and their status
|
|
76
|
+
clean Remove all files and directories created by aicm
|
|
72
77
|
|
|
73
78
|
${chalk_1.default.bold("OPTIONS")}
|
|
74
79
|
-h, --help Show this help message
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface CleanOptions {
|
|
2
|
+
/**
|
|
3
|
+
* Base directory to use instead of process.cwd()
|
|
4
|
+
*/
|
|
5
|
+
cwd?: string;
|
|
6
|
+
/**
|
|
7
|
+
* Show verbose output
|
|
8
|
+
*/
|
|
9
|
+
verbose?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface CleanResult {
|
|
12
|
+
success: boolean;
|
|
13
|
+
error?: Error;
|
|
14
|
+
cleanedCount: number;
|
|
15
|
+
}
|
|
16
|
+
export declare function cleanPackage(options?: CleanOptions): Promise<CleanResult>;
|
|
17
|
+
export declare function cleanWorkspaces(cwd: string, verbose?: boolean): Promise<CleanResult>;
|
|
18
|
+
export declare function clean(options?: CleanOptions): Promise<CleanResult>;
|
|
19
|
+
export declare function cleanCommand(verbose?: boolean): Promise<void>;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.cleanPackage = cleanPackage;
|
|
7
|
+
exports.cleanWorkspaces = cleanWorkspaces;
|
|
8
|
+
exports.clean = clean;
|
|
9
|
+
exports.cleanCommand = cleanCommand;
|
|
10
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
11
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
12
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
13
|
+
const config_1 = require("../utils/config");
|
|
14
|
+
const working_directory_1 = require("../utils/working-directory");
|
|
15
|
+
const rules_file_writer_1 = require("../utils/rules-file-writer");
|
|
16
|
+
const workspace_discovery_1 = require("../utils/workspace-discovery");
|
|
17
|
+
function cleanFile(filePath, verbose) {
|
|
18
|
+
if (!fs_extra_1.default.existsSync(filePath))
|
|
19
|
+
return false;
|
|
20
|
+
try {
|
|
21
|
+
fs_extra_1.default.removeSync(filePath);
|
|
22
|
+
if (verbose)
|
|
23
|
+
console.log(chalk_1.default.gray(` Removed ${filePath}`));
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
catch (_a) {
|
|
27
|
+
console.warn(chalk_1.default.yellow(`Warning: Failed to remove ${filePath}`));
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function cleanRulesBlock(filePath, verbose) {
|
|
32
|
+
if (!fs_extra_1.default.existsSync(filePath))
|
|
33
|
+
return false;
|
|
34
|
+
try {
|
|
35
|
+
const content = fs_extra_1.default.readFileSync(filePath, "utf8");
|
|
36
|
+
const cleanedContent = (0, rules_file_writer_1.removeRulesBlock)(content);
|
|
37
|
+
if (content === cleanedContent)
|
|
38
|
+
return false;
|
|
39
|
+
if (cleanedContent.trim() === "") {
|
|
40
|
+
fs_extra_1.default.removeSync(filePath);
|
|
41
|
+
if (verbose)
|
|
42
|
+
console.log(chalk_1.default.gray(` Removed empty file ${filePath}`));
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
fs_extra_1.default.writeFileSync(filePath, cleanedContent);
|
|
46
|
+
if (verbose)
|
|
47
|
+
console.log(chalk_1.default.gray(` Cleaned rules block from ${filePath}`));
|
|
48
|
+
}
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
catch (_a) {
|
|
52
|
+
console.warn(chalk_1.default.yellow(`Warning: Failed to clean ${filePath}`));
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function cleanMcpServers(cwd, verbose) {
|
|
57
|
+
const mcpPath = node_path_1.default.join(cwd, ".cursor", "mcp.json");
|
|
58
|
+
if (!fs_extra_1.default.existsSync(mcpPath))
|
|
59
|
+
return false;
|
|
60
|
+
try {
|
|
61
|
+
const content = fs_extra_1.default.readJsonSync(mcpPath);
|
|
62
|
+
const mcpServers = content.mcpServers;
|
|
63
|
+
if (!mcpServers)
|
|
64
|
+
return false;
|
|
65
|
+
let hasChanges = false;
|
|
66
|
+
const newMcpServers = {};
|
|
67
|
+
for (const [key, value] of Object.entries(mcpServers)) {
|
|
68
|
+
if (typeof value === "object" &&
|
|
69
|
+
value !== null &&
|
|
70
|
+
"aicm" in value &&
|
|
71
|
+
value.aicm === true) {
|
|
72
|
+
hasChanges = true;
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
newMcpServers[key] = value;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (!hasChanges)
|
|
79
|
+
return false;
|
|
80
|
+
content.mcpServers = newMcpServers;
|
|
81
|
+
fs_extra_1.default.writeJsonSync(mcpPath, content, { spaces: 2 });
|
|
82
|
+
if (verbose)
|
|
83
|
+
console.log(chalk_1.default.gray(` Cleaned aicm MCP servers from ${mcpPath}`));
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
catch (_a) {
|
|
87
|
+
console.warn(chalk_1.default.yellow(`Warning: Failed to clean MCP servers`));
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async function cleanPackage(options = {}) {
|
|
92
|
+
const cwd = options.cwd || process.cwd();
|
|
93
|
+
const verbose = options.verbose || false;
|
|
94
|
+
return (0, working_directory_1.withWorkingDirectory)(cwd, async () => {
|
|
95
|
+
let cleanedCount = 0;
|
|
96
|
+
const filesToClean = [
|
|
97
|
+
node_path_1.default.join(cwd, ".cursor", "rules", "aicm"),
|
|
98
|
+
node_path_1.default.join(cwd, ".cursor", "commands", "aicm"),
|
|
99
|
+
node_path_1.default.join(cwd, ".aicm"),
|
|
100
|
+
];
|
|
101
|
+
const rulesFilesToClean = [
|
|
102
|
+
node_path_1.default.join(cwd, ".windsurfrules"),
|
|
103
|
+
node_path_1.default.join(cwd, "AGENTS.md"),
|
|
104
|
+
node_path_1.default.join(cwd, "CLAUDE.md"),
|
|
105
|
+
];
|
|
106
|
+
// Clean directories and files
|
|
107
|
+
for (const file of filesToClean) {
|
|
108
|
+
if (cleanFile(file, verbose))
|
|
109
|
+
cleanedCount++;
|
|
110
|
+
}
|
|
111
|
+
// Clean rules blocks from files
|
|
112
|
+
for (const file of rulesFilesToClean) {
|
|
113
|
+
if (cleanRulesBlock(file, verbose))
|
|
114
|
+
cleanedCount++;
|
|
115
|
+
}
|
|
116
|
+
// Clean MCP servers
|
|
117
|
+
if (cleanMcpServers(cwd, verbose))
|
|
118
|
+
cleanedCount++;
|
|
119
|
+
return {
|
|
120
|
+
success: true,
|
|
121
|
+
cleanedCount,
|
|
122
|
+
};
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
async function cleanWorkspaces(cwd, verbose = false) {
|
|
126
|
+
if (verbose)
|
|
127
|
+
console.log(chalk_1.default.blue("🔍 Discovering packages..."));
|
|
128
|
+
const packages = await (0, workspace_discovery_1.discoverPackagesWithAicm)(cwd);
|
|
129
|
+
if (verbose && packages.length > 0) {
|
|
130
|
+
console.log(chalk_1.default.blue(`Found ${packages.length} packages with aicm configurations.`));
|
|
131
|
+
}
|
|
132
|
+
let totalCleaned = 0;
|
|
133
|
+
// Clean all discovered packages
|
|
134
|
+
for (const pkg of packages) {
|
|
135
|
+
if (verbose)
|
|
136
|
+
console.log(chalk_1.default.blue(`Cleaning package: ${pkg.relativePath}`));
|
|
137
|
+
const result = await cleanPackage({
|
|
138
|
+
cwd: pkg.absolutePath,
|
|
139
|
+
verbose,
|
|
140
|
+
});
|
|
141
|
+
totalCleaned += result.cleanedCount;
|
|
142
|
+
}
|
|
143
|
+
// Always clean root directory (for merged artifacts like mcp.json and commands)
|
|
144
|
+
const rootPackage = packages.find((p) => p.absolutePath === cwd);
|
|
145
|
+
if (!rootPackage) {
|
|
146
|
+
if (verbose)
|
|
147
|
+
console.log(chalk_1.default.blue(`Cleaning root workspace artifacts...`));
|
|
148
|
+
const rootResult = await cleanPackage({ cwd, verbose });
|
|
149
|
+
totalCleaned += rootResult.cleanedCount;
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
success: true,
|
|
153
|
+
cleanedCount: totalCleaned,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
async function clean(options = {}) {
|
|
157
|
+
var _a;
|
|
158
|
+
const cwd = options.cwd || process.cwd();
|
|
159
|
+
const verbose = options.verbose || false;
|
|
160
|
+
const shouldUseWorkspaces = ((_a = (await (0, config_1.loadConfig)(cwd))) === null || _a === void 0 ? void 0 : _a.config.workspaces) ||
|
|
161
|
+
(0, config_1.detectWorkspacesFromPackageJson)(cwd);
|
|
162
|
+
if (shouldUseWorkspaces) {
|
|
163
|
+
return cleanWorkspaces(cwd, verbose);
|
|
164
|
+
}
|
|
165
|
+
return cleanPackage(options);
|
|
166
|
+
}
|
|
167
|
+
async function cleanCommand(verbose) {
|
|
168
|
+
const result = await clean({ verbose });
|
|
169
|
+
if (result.cleanedCount === 0) {
|
|
170
|
+
console.log("Nothing to clean.");
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
console.log(chalk_1.default.green(`Successfully cleaned ${result.cleanedCount} file(s)/director(y/ies).`));
|
|
174
|
+
}
|
|
175
|
+
}
|
package/dist/commands/install.js
CHANGED
|
@@ -9,11 +9,11 @@ exports.installCommand = installCommand;
|
|
|
9
9
|
const chalk_1 = __importDefault(require("chalk"));
|
|
10
10
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
11
11
|
const node_path_1 = __importDefault(require("node:path"));
|
|
12
|
-
const child_process_1 = require("child_process");
|
|
13
12
|
const config_1 = require("../utils/config");
|
|
14
13
|
const working_directory_1 = require("../utils/working-directory");
|
|
15
14
|
const is_ci_1 = require("../utils/is-ci");
|
|
16
15
|
const rules_file_writer_1 = require("../utils/rules-file-writer");
|
|
16
|
+
const workspace_discovery_1 = require("../utils/workspace-discovery");
|
|
17
17
|
function getTargetPaths() {
|
|
18
18
|
const projectDir = process.cwd();
|
|
19
19
|
return {
|
|
@@ -41,7 +41,7 @@ function writeCursorRules(rules, cursorRulesDir) {
|
|
|
41
41
|
fs_extra_1.default.writeFileSync(ruleFile, rule.content);
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
-
function writeCursorCommands(commands, cursorCommandsDir) {
|
|
44
|
+
function writeCursorCommands(commands, cursorCommandsDir, assets) {
|
|
45
45
|
fs_extra_1.default.removeSync(cursorCommandsDir);
|
|
46
46
|
for (const command of commands) {
|
|
47
47
|
const commandNameParts = command.name
|
|
@@ -56,22 +56,18 @@ function writeCursorCommands(commands, cursorCommandsDir) {
|
|
|
56
56
|
// Rules/assets are installed in .cursor/rules/aicm/
|
|
57
57
|
// So a link like "../rules/asset.json" in source (from commands/ to rules/)
|
|
58
58
|
// needs to become "../../rules/aicm/asset.json" in target (from .cursor/commands/aicm/ to .cursor/rules/aicm/)
|
|
59
|
-
const content = rewriteCommandRelativeLinks(command.content);
|
|
59
|
+
const content = rewriteCommandRelativeLinks(command.content, command.sourcePath, assets);
|
|
60
60
|
fs_extra_1.default.writeFileSync(commandFile, content);
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
|
-
function rewriteCommandRelativeLinks(content) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const filename = parts[parts.length - 1];
|
|
72
|
-
return `[${text}](../../rules/aicm/${filename})`;
|
|
73
|
-
}
|
|
74
|
-
return match;
|
|
63
|
+
function rewriteCommandRelativeLinks(content, commandSourcePath, assets) {
|
|
64
|
+
const commandDir = node_path_1.default.dirname(commandSourcePath);
|
|
65
|
+
const assetMap = new Map(assets.map((a) => [node_path_1.default.normalize(a.sourcePath), a.name]));
|
|
66
|
+
return content.replace(/\.\.[/\\][\w\-/\\.]+/g, (match) => {
|
|
67
|
+
const resolved = node_path_1.default.normalize(node_path_1.default.resolve(commandDir, match));
|
|
68
|
+
return assetMap.has(resolved)
|
|
69
|
+
? `../../rules/aicm/${assetMap.get(resolved)}`
|
|
70
|
+
: match;
|
|
75
71
|
});
|
|
76
72
|
}
|
|
77
73
|
function extractNamespaceFromPresetPath(presetPath) {
|
|
@@ -189,13 +185,13 @@ function writeRulesToTargets(rules, assets, targets) {
|
|
|
189
185
|
// Write assets after rules so they don't get wiped by emptyDirSync
|
|
190
186
|
writeAssetsToTargets(assets, targets);
|
|
191
187
|
}
|
|
192
|
-
function writeCommandsToTargets(commands, targets) {
|
|
188
|
+
function writeCommandsToTargets(commands, assets, targets) {
|
|
193
189
|
const projectDir = process.cwd();
|
|
194
190
|
const cursorRoot = node_path_1.default.join(projectDir, ".cursor");
|
|
195
191
|
for (const target of targets) {
|
|
196
192
|
if (target === "cursor") {
|
|
197
193
|
const commandsDir = node_path_1.default.join(cursorRoot, "commands", "aicm");
|
|
198
|
-
writeCursorCommands(commands, commandsDir);
|
|
194
|
+
writeCursorCommands(commands, commandsDir, assets);
|
|
199
195
|
}
|
|
200
196
|
// Other targets do not support commands yet
|
|
201
197
|
}
|
|
@@ -346,49 +342,6 @@ function mergeWorkspaceMcpServers(packages) {
|
|
|
346
342
|
}
|
|
347
343
|
return { merged, conflicts };
|
|
348
344
|
}
|
|
349
|
-
/**
|
|
350
|
-
* Discover all packages with aicm configurations using git ls-files
|
|
351
|
-
*/
|
|
352
|
-
function findAicmFiles(rootDir) {
|
|
353
|
-
try {
|
|
354
|
-
const output = (0, child_process_1.execSync)("git ls-files --cached --others --exclude-standard aicm.json **/aicm.json", {
|
|
355
|
-
cwd: rootDir,
|
|
356
|
-
encoding: "utf8",
|
|
357
|
-
});
|
|
358
|
-
return output
|
|
359
|
-
.trim()
|
|
360
|
-
.split("\n")
|
|
361
|
-
.filter(Boolean)
|
|
362
|
-
.map((file) => node_path_1.default.resolve(rootDir, file));
|
|
363
|
-
}
|
|
364
|
-
catch (_a) {
|
|
365
|
-
// Fallback to manual search if git is not available
|
|
366
|
-
return [];
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
/**
|
|
370
|
-
* Discover all packages with aicm configurations
|
|
371
|
-
*/
|
|
372
|
-
async function discoverPackagesWithAicm(rootDir) {
|
|
373
|
-
const aicmFiles = findAicmFiles(rootDir);
|
|
374
|
-
const packages = [];
|
|
375
|
-
for (const aicmFile of aicmFiles) {
|
|
376
|
-
const packageDir = node_path_1.default.dirname(aicmFile);
|
|
377
|
-
const relativePath = node_path_1.default.relative(rootDir, packageDir);
|
|
378
|
-
// Normalize to forward slashes for cross-platform compatibility
|
|
379
|
-
const normalizedRelativePath = relativePath.replace(/\\/g, "/");
|
|
380
|
-
const config = await (0, config_1.loadConfig)(packageDir);
|
|
381
|
-
if (config) {
|
|
382
|
-
packages.push({
|
|
383
|
-
relativePath: normalizedRelativePath || ".",
|
|
384
|
-
absolutePath: packageDir,
|
|
385
|
-
config,
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
// Sort packages by relativePath for deterministic order
|
|
390
|
-
return packages.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
391
|
-
}
|
|
392
345
|
/**
|
|
393
346
|
* Install rules for a single package (used within workspaces and standalone installs)
|
|
394
347
|
*/
|
|
@@ -427,7 +380,7 @@ async function installPackage(options = {}) {
|
|
|
427
380
|
try {
|
|
428
381
|
if (!options.dryRun) {
|
|
429
382
|
writeRulesToTargets(rules, assets, config.targets);
|
|
430
|
-
writeCommandsToTargets(commandsToInstall, config.targets);
|
|
383
|
+
writeCommandsToTargets(commandsToInstall, assets, config.targets);
|
|
431
384
|
if (mcpServers && Object.keys(mcpServers).length > 0) {
|
|
432
385
|
writeMcpServersToTargets(mcpServers, config.targets, cwd);
|
|
433
386
|
}
|
|
@@ -511,7 +464,7 @@ async function installWorkspaces(cwd, installOnCI, verbose = false, dryRun = fal
|
|
|
511
464
|
if (verbose) {
|
|
512
465
|
console.log(chalk_1.default.blue("🔍 Discovering packages..."));
|
|
513
466
|
}
|
|
514
|
-
const allPackages = await discoverPackagesWithAicm(cwd);
|
|
467
|
+
const allPackages = await (0, workspace_discovery_1.discoverPackagesWithAicm)(cwd);
|
|
515
468
|
const packages = allPackages.filter((pkg) => {
|
|
516
469
|
if (pkg.config.config.skipInstall === true) {
|
|
517
470
|
return false;
|
|
@@ -556,7 +509,9 @@ async function installWorkspaces(cwd, installOnCI, verbose = false, dryRun = fal
|
|
|
556
509
|
workspaceCommands.length > 0 &&
|
|
557
510
|
workspaceCommandTargets.length > 0) {
|
|
558
511
|
const dedupedWorkspaceCommands = dedupeCommandsForInstall(workspaceCommands);
|
|
559
|
-
|
|
512
|
+
// Collect all assets from packages for command path rewriting
|
|
513
|
+
const allAssets = packages.flatMap((pkg) => { var _a; return (_a = pkg.config.assets) !== null && _a !== void 0 ? _a : []; });
|
|
514
|
+
writeCommandsToTargets(dedupedWorkspaceCommands, allAssets, workspaceCommandTargets);
|
|
560
515
|
}
|
|
561
516
|
const { merged: rootMcp, conflicts } = mergeWorkspaceMcpServers(packages);
|
|
562
517
|
const hasCursorTarget = packages.some((p) => p.config.config.targets.includes("cursor"));
|
|
@@ -3,6 +3,10 @@ export type RuleMetadata = Record<string, string | boolean | string[]>;
|
|
|
3
3
|
* Parse YAML frontmatter blocks from a rule file and return a flat metadata object
|
|
4
4
|
*/
|
|
5
5
|
export declare function parseRuleFrontmatter(content: string): RuleMetadata;
|
|
6
|
+
/**
|
|
7
|
+
* Remove the rules block from the content
|
|
8
|
+
*/
|
|
9
|
+
export declare function removeRulesBlock(content: string): string;
|
|
6
10
|
/**
|
|
7
11
|
* Write rules to the .windsurfrules file
|
|
8
12
|
* This will update the content between the RULES_BEGIN and RULES_END markers
|
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.parseRuleFrontmatter = parseRuleFrontmatter;
|
|
7
|
+
exports.removeRulesBlock = removeRulesBlock;
|
|
7
8
|
exports.writeRulesFile = writeRulesFile;
|
|
8
9
|
exports.generateRulesFileContent = generateRulesFileContent;
|
|
9
10
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
@@ -55,6 +56,20 @@ function parseRuleFrontmatter(content) {
|
|
|
55
56
|
const RULES_BEGIN = "<!-- AICM:BEGIN -->";
|
|
56
57
|
const RULES_END = "<!-- AICM:END -->";
|
|
57
58
|
const WARNING = "<!-- WARNING: Everything between these markers will be overwritten during installation -->";
|
|
59
|
+
/**
|
|
60
|
+
* Remove the rules block from the content
|
|
61
|
+
*/
|
|
62
|
+
function removeRulesBlock(content) {
|
|
63
|
+
// Check if our markers exist
|
|
64
|
+
if (content.includes(RULES_BEGIN) && content.includes(RULES_END)) {
|
|
65
|
+
const parts = content.split(RULES_BEGIN);
|
|
66
|
+
const beforeMarker = parts[0];
|
|
67
|
+
const afterParts = parts[1].split(RULES_END);
|
|
68
|
+
const afterMarker = afterParts.slice(1).join(RULES_END); // In case RULES_END appears multiple times (unlikely but safe)
|
|
69
|
+
return (beforeMarker + afterMarker).trim();
|
|
70
|
+
}
|
|
71
|
+
return content;
|
|
72
|
+
}
|
|
58
73
|
/**
|
|
59
74
|
* Create a formatted block of content with rules markers
|
|
60
75
|
*/
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ResolvedConfig } from "./config";
|
|
2
|
+
/**
|
|
3
|
+
* Discover all packages with aicm configurations using git ls-files
|
|
4
|
+
*/
|
|
5
|
+
export declare function findAicmFiles(rootDir: string): string[];
|
|
6
|
+
/**
|
|
7
|
+
* Discover all packages with aicm configurations
|
|
8
|
+
*/
|
|
9
|
+
export declare function discoverPackagesWithAicm(rootDir: string): Promise<Array<{
|
|
10
|
+
relativePath: string;
|
|
11
|
+
absolutePath: string;
|
|
12
|
+
config: ResolvedConfig;
|
|
13
|
+
}>>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.findAicmFiles = findAicmFiles;
|
|
7
|
+
exports.discoverPackagesWithAicm = discoverPackagesWithAicm;
|
|
8
|
+
const child_process_1 = require("child_process");
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const config_1 = require("./config");
|
|
11
|
+
/**
|
|
12
|
+
* Discover all packages with aicm configurations using git ls-files
|
|
13
|
+
*/
|
|
14
|
+
function findAicmFiles(rootDir) {
|
|
15
|
+
try {
|
|
16
|
+
const output = (0, child_process_1.execSync)("git ls-files --cached --others --exclude-standard aicm.json **/aicm.json", {
|
|
17
|
+
cwd: rootDir,
|
|
18
|
+
encoding: "utf8",
|
|
19
|
+
});
|
|
20
|
+
return output
|
|
21
|
+
.trim()
|
|
22
|
+
.split("\n")
|
|
23
|
+
.filter(Boolean)
|
|
24
|
+
.map((file) => path_1.default.resolve(rootDir, file));
|
|
25
|
+
}
|
|
26
|
+
catch (_a) {
|
|
27
|
+
// Fallback to manual search if git is not available
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Discover all packages with aicm configurations
|
|
33
|
+
*/
|
|
34
|
+
async function discoverPackagesWithAicm(rootDir) {
|
|
35
|
+
const aicmFiles = findAicmFiles(rootDir);
|
|
36
|
+
const packages = [];
|
|
37
|
+
for (const aicmFile of aicmFiles) {
|
|
38
|
+
const packageDir = path_1.default.dirname(aicmFile);
|
|
39
|
+
const relativePath = path_1.default.relative(rootDir, packageDir);
|
|
40
|
+
// Normalize to forward slashes for cross-platform compatibility
|
|
41
|
+
const normalizedRelativePath = relativePath.replace(/\\/g, "/");
|
|
42
|
+
const config = await (0, config_1.loadConfig)(packageDir);
|
|
43
|
+
if (config) {
|
|
44
|
+
packages.push({
|
|
45
|
+
relativePath: normalizedRelativePath || ".",
|
|
46
|
+
absolutePath: packageDir,
|
|
47
|
+
config,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Sort packages by relativePath for deterministic order
|
|
52
|
+
return packages.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
53
|
+
}
|