apotheke 0.0.1 → 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Marco Raffaello
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,6 +1,9 @@
1
1
  # apotheke
2
2
 
3
- Import organizer for JavaScript and TypeScript projects. Groups, sorts, and deduplicates imports based on a simple config file.
3
+ **Prettier for imports.** Deterministic, configurable import organization for
4
+ JavaScript and TypeScript — as a Prettier plugin or a standalone CLI.
5
+
6
+ 📖 **[Documentation](https://marcoraffaello.com/apotheke)**
4
7
 
5
8
  ```ts
6
9
  // Before
@@ -25,224 +28,85 @@ import { useQuery } from '@tanstack/react-query';
25
28
  import { createRoute } from '@tanstack/react-router';
26
29
  ```
27
30
 
28
- ## Requirements
29
-
30
- - Node.js 18
31
+ apotheke is not a replacement for Prettier — it runs _inside_ Prettier as a
32
+ `preprocess` plugin, so one `prettier --write` organises imports and formats
33
+ code in a single pass.
31
34
 
32
35
  ## Installation
33
36
 
34
37
  ```sh
35
- npm install -D apotheke
36
- # or
37
38
  pnpm add -D apotheke
38
39
  ```
39
40
 
40
- ## Usage
41
+ Requires Node.js ≥ 18, and Prettier ≥ 3 if you use the plugin.
41
42
 
42
- ```sh
43
- apotheke --write src/**/*.{ts,tsx} # format in place
44
- apotheke --check src/**/*.{ts,tsx} # CI — exit 1 if anything would change
45
- apotheke --diff src/**/*.{ts,tsx} # print diff without writing
46
- apotheke --stdin-filepath src/app.tsx # read from stdin, write to stdout
47
- ```
48
-
49
- ## Config
43
+ ## Usage
50
44
 
51
- Create `apotheke.config.mjs` at your project root:
45
+ Describe the groups you want in `apotheke.config.mjs`:
52
46
 
53
47
  ```js
54
- // apotheke.config.mjs
55
48
  export default {
56
49
  groups: [
57
- { name: 'React', match: ['react', 'react-dom', 'react-*'] },
50
+ { name: 'React', match: ['react', 'react-dom'] },
58
51
  { name: 'Hooks', match: ['**/hooks/**'] },
59
52
  { name: 'Api', match: ['**/api/**', '@tanstack/react-query'] },
60
- { name: 'Navigation', match: ['@tanstack/react-router'] },
61
- { name: 'Assets', match: ['lucide-react'] }
62
- ],
63
- aliases: {
64
- '@': './src' // mirrors tsconfig paths — auto-read if omitted
65
- },
66
- groupSeparator: true, // blank line between groups
67
- groupComments: true // // GroupName header above each group
68
- };
69
- ```
70
-
71
- Apotheke also reads `tsconfig.json` (or `jsconfig.json`) automatically to pick up `paths` aliases and `baseUrl`, so you often don't need to set `aliases` manually.
72
-
73
- Unmatched imports collect in an **Others** group at the end. Side-effect imports (`import './styles.css'`) always go first.
74
-
75
- ### Monorepo
76
-
77
- Place a root config and extend it per-package:
78
-
79
- ```js
80
- // apps/web/apotheke.config.mjs
81
- export default {
82
- extends: '../../apotheke.config.mjs',
83
- groups: [{ name: 'Shared', match: ['@acme/*'] }]
84
- };
85
- ```
86
-
87
- ---
88
-
89
- ## Prettier plugin
90
-
91
- Apotheke ships as a prettier plugin. When loaded, it runs as a `preprocess` step — apotheke organises imports first, then prettier formats the result. One pass, correct order, no conflicts.
92
-
93
- **Requirements:** Prettier v3 or later (v3 supports async `preprocess`; v2 does not).
94
-
95
- ### Permanent setup
96
-
97
- **1. Install apotheke**
98
-
99
- ```sh
100
- pnpm add -D apotheke
101
- ```
102
-
103
- **2. Add the plugin to your prettier config**
104
-
105
- ```js
106
- // .prettierrc.js
107
- module.exports = {
108
- plugins: ['apotheke'],
53
+ { name: 'Navigation', match: ['@tanstack/react-router'] }
54
+ ]
109
55
  };
110
56
  ```
111
57
 
112
- That's it`prettier --write` will now organise imports automatically.
113
-
114
- ### Quick test (without installing)
115
-
116
- ```sh
117
- cd /path/to/your-project
118
- npx prettier@3 \
119
- --plugin /path/to/apotheke/dist/index.js \
120
- --write 'src/App.tsx'
121
- ```
122
-
123
- ### VS Code on-save
124
-
125
- Install the [Prettier - Code formatter](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) extension. Because `prettier.format()` calls our plugin's `preprocess` hook directly, no extra config is needed:
58
+ Then either add the Prettier plugin apotheke must be **last** in the array:
126
59
 
127
60
  ```json
128
- {
129
- "editor.defaultFormatter": "esbenp.prettier-vscode",
130
- "editor.formatOnSave": true
131
- }
132
- ```
133
-
134
- ### Pre-commit with Husky + lint-staged
135
-
136
- ```json
137
- {
138
- "lint-staged": {
139
- "*.{ts,tsx,js,jsx}": ["prettier --write"]
140
- }
141
- }
142
- ```
143
-
144
- ### Prettier v2 fallback
145
-
146
- If you can't upgrade to prettier v3, use the CLI sequentially via lint-staged:
147
-
148
- ```json
149
- {
150
- "lint-staged": {
151
- "*.{ts,tsx,js,jsx}": [
152
- "apotheke --write",
153
- "prettier --write"
154
- ]
155
- }
156
- }
157
- ```
158
-
159
- ---
160
-
161
- ## Testing locally without publishing
162
-
163
- ### Option 1 — Direct invocation (no setup)
164
-
165
- ```sh
166
- node /path/to/apotheke/dist/cli.js --write 'src/**/*.{ts,tsx}'
61
+ { "plugins": ["apotheke"] }
167
62
  ```
168
63
 
169
- Build first if needed: `cd /path/to/apotheke && pnpm build`
170
-
171
- ### Option 2 — `pnpm link` (recommended)
172
-
173
- **In the apotheke repo:**
64
+ or use the CLI:
174
65
 
175
66
  ```sh
176
- pnpm build
177
- pnpm link --global
67
+ apotheke --write 'src/**/*.{ts,tsx}' # rewrite in place
68
+ apotheke --check 'src/**/*.{ts,tsx}' # CI — exit 1 if anything would change
69
+ apotheke --diff 'src/**/*.{ts,tsx}' # preview without writing
178
70
  ```
179
71
 
180
- **In your target repo:**
72
+ Groups are matched in order and the first match wins. Unmatched imports collect
73
+ in a trailing `Others` group; side-effect imports always come first. Imports are
74
+ sorted and deduplicated within each group, and `tsconfig.json` path aliases are
75
+ read automatically.
181
76
 
182
- ```sh
183
- pnpm link --global apotheke
184
- ```
77
+ ## Documentation
185
78
 
186
- Now `apotheke --write src/**/*.tsx` works as if it were installed normally. You'll need to re-run `pnpm build` in the apotheke repo after making source changes.
79
+ Full guides and reference at
80
+ **[marcoraffaello.com/apotheke](https://marcoraffaello.com/apotheke)**:
187
81
 
188
- **To unlink when done:**
82
+ - [Configuring groups](https://marcoraffaello.com/apotheke/docs/guides/groups)
83
+ - [Aliases and tsconfig paths](https://marcoraffaello.com/apotheke/docs/guides/aliases)
84
+ - [Monorepos](https://marcoraffaello.com/apotheke/docs/guides/monorepos)
85
+ - [Prettier plugin](https://marcoraffaello.com/apotheke/docs/guides/prettier-plugin)
86
+ - [Config reference](https://marcoraffaello.com/apotheke/docs/reference/config)
189
87
 
190
- ```sh
191
- # In your target repo
192
- pnpm unlink --global apotheke
193
-
194
- # In the apotheke repo
195
- pnpm unlink --global
196
- ```
88
+ ## Agent skill
197
89
 
198
- ### Option 3 Path dependency in `package.json`
90
+ This package ships a `SKILL.md` that teaches a coding agent to scan your
91
+ codebase, propose a group config and wire everything up:
199
92
 
200
93
  ```sh
201
- pnpm add /path/to/apotheke
94
+ mkdir -p .claude/skills/setup-apotheke
95
+ cp node_modules/apotheke/SKILL.md .claude/skills/setup-apotheke/SKILL.md
202
96
  ```
203
97
 
204
- ---
205
-
206
98
  ## Development
207
99
 
100
+ This repository is a pnpm workspace. The published package lives in
101
+ `packages/apotheke`, the documentation site in `apps/docs`.
102
+
208
103
  ```sh
209
104
  pnpm install
210
-
211
- # Build (required before running locally or testing the plugin)
212
- pnpm build
213
-
214
- # Run all tests (unit + e2e)
215
- pnpm test
216
-
217
- # Unit tests only
218
- pnpm test:unit
219
-
220
- # E2E tests against real repos (cloned automatically on first run)
221
- pnpm test:e2e
222
-
223
- # Type check
224
- pnpm typecheck
105
+ pnpm build # build the package (required before format and dogfooding)
106
+ pnpm test # unit + e2e
107
+ pnpm docs:dev # run the documentation site locally
225
108
  ```
226
109
 
227
- ### Project structure
110
+ ## License
228
111
 
229
- ```
230
- src/
231
- parser.ts OXC-based import extractor
232
- grouper.ts Glob-based group assignment
233
- sorter.ts Alphabetical sort, type imports float to top
234
- deduplicator.ts Merge named/default imports from same specifier
235
- printer.ts Reconstruct import block with group headers
236
- config.ts Load apotheke.config.mjs, merge tsconfig aliases
237
- format.ts Top-level formatImports(source, config)
238
- types.ts Shared types
239
- cli.ts CLI source (Node.js)
240
- index.ts Prettier plugin + programmatic API
241
- dist/
242
- cli.js Compiled CLI — run with node or via the apotheke bin
243
- index.js Compiled prettier plugin
244
- index.d.ts Types for programmatic use
245
- tests/
246
- unit/ 76 unit tests
247
- e2e/ 20 e2e tests against sonner and tremor
248
- ```
112
+ MIT © Marco Raffaello
package/SKILL.md ADDED
@@ -0,0 +1,249 @@
1
+ ---
2
+ name: setup-apotheke
3
+ description: Set up apotheke import organizer in any JS/TS project. Scans existing imports, proposes group config, writes apotheke.config.mjs, and wires up prettier plugin. Use when user wants to organize imports, set up apotheke, or add import formatting to a project.
4
+ ---
5
+
6
+ # Setup Apotheke
7
+
8
+ Apotheke is **Prettier for imports** — deterministic, configurable import organization for JavaScript and TypeScript. It is not a replacement for prettier: it runs *inside* prettier as a `preprocess` plugin, so imports get organized and code gets formatted in a single `prettier --write` pass. It also ships a standalone CLI for projects that do not use prettier.
9
+
10
+ This skill installs and configures it by analyzing the project's actual imports, then wiring it up as a prettier plugin.
11
+
12
+ Full documentation: <https://marcoraffaello.com/apotheke>
13
+
14
+ ## Step 1 — Detect project structure (monorepo vs single package)
15
+
16
+ Before doing anything else, determine whether this is a monorepo. Check for these signals at the working directory root:
17
+
18
+ | File / field | Tool |
19
+ | ---------------------------------------- | ----------------------- |
20
+ | `pnpm-workspace.yaml` | pnpm workspaces |
21
+ | `turbo.json` | Turborepo |
22
+ | `nx.json` | Nx |
23
+ | `lerna.json` | Lerna |
24
+ | `rush.json` | Rush |
25
+ | `package.json` with `"workspaces"` field | npm/yarn/bun workspaces |
26
+
27
+ If any of these are present, it's a **monorepo**. Follow the monorepo path below. Otherwise follow the single-package path.
28
+
29
+ ### Single-package path
30
+
31
+ Proceed to Step 2 with the repo root as the single package root.
32
+
33
+ ### Monorepo path
34
+
35
+ 1. Identify all package roots by reading the workspaces glob (e.g. `packages/*`, `apps/*`) from whichever workspace config is present. List them to the user.
36
+ 2. Ask: "Should I set up a shared root config that all packages inherit from, or configure each package independently?"
37
+ - **Shared root** (recommended): write a root `apotheke.config.mjs` with groups common across all packages; each package gets its own config with `extends: "../../apotheke.config.mjs"` that overrides or adds package-specific groups.
38
+ - **Independent**: each package gets its own self-contained config with no inheritance.
39
+ 3. Scan each package separately (Step 2 runs once per package root). Combine the findings to build the shared group set before writing configs.
40
+ 4. In Step 6, add scripts to each package's `package.json` scoped to that package's source folder. Optionally add a root-level script that runs prettier across all packages.
41
+
42
+ ## Step 2 — Scan the project
43
+
44
+ For each package root (or just the single root), search for all `.ts`, `.tsx`, `.js`, `.jsx` files (excluding `node_modules`, `dist`, `.next`, `build`, `out`). Read a sample of files (up to 20 per package, spread across the directory tree) and extract the unique import specifiers.
45
+
46
+ Pay attention to:
47
+
48
+ - **Package imports**: `react`, `@tanstack/react-query`, `lucide-react`, etc.
49
+ - **Path alias imports**: `@/components/...`, `~/lib/...`, `#utils/...`
50
+ - **Relative path patterns**: which folders appear most — `hooks/`, `api/`, `components/`, `views/`, `pages/`, `utils/`, `lib/`, `stores/`, etc.
51
+ - **tsconfig.json / jsconfig.json**: read `compilerOptions.paths` and `compilerOptions.baseUrl` to understand existing aliases. In a monorepo, check both the root tsconfig and each package's tsconfig.
52
+
53
+ ## Step 3 — Check for existing config
54
+
55
+ Look for `apotheke.config.mjs`, `apotheke.config.js`, or `apotheke.config.ts` at each relevant root. If one already exists, read it, tell the user, and ask whether to update it or abort.
56
+
57
+ ## Step 4 — Propose groups
58
+
59
+ Based on what you found, propose a `groups` array. Use your judgment to create meaningful groups. Common patterns:
60
+
61
+ - If the project uses React → group `react`, `react-dom`, `react-*`
62
+ > **Warning — `react-*` is greedy**: it will also match `react-hook-form`, `react-i18next`, `react-day-picker`, etc. Groups are matched in order and the first match wins. If you want those libraries in their own groups (Forms, i18n, Date), either list them in those groups _before_ the React group in the array, or narrow the React match to just `['react', 'react-dom']` without the wildcard.
63
+ - If the project has `hooks/` folder → group `**/hooks/**`
64
+ - If the project has `api/`, `services/`, `queries/` → group those paths + data-fetching libraries (react-query, swr, etc.)
65
+ - If the project has `components/`, `ui/` → group those
66
+ - If the project uses a router (react-router, tanstack-router, next/navigation) → group it
67
+ - If the project uses icon libraries (lucide-react, @heroicons, react-icons) → group as Assets
68
+ - If the project has a `store/`, `context/` folder → group it
69
+ - If the project uses a UI kit (@mui, @radix-ui, shadcn) → group it
70
+ - In a monorepo, internal workspace packages (e.g. `@acme/*`) deserve their own group
71
+
72
+ Always propose a sensible **alias map** based on tsconfig `paths`.
73
+
74
+ In a monorepo with a shared root config: put universal groups (React, external libraries) in the root config, and package-specific groups (internal paths, local components) in each package config.
75
+
76
+ Present the proposed config clearly and ask the user:
77
+
78
+ 1. Are these groups right? Any to add, remove, or rename?
79
+ 2. Is the order right? Groups are matched top-to-bottom and the first match wins.
80
+ 3. Should there be blank lines between groups? (`groupSeparator`, default: yes)
81
+ 4. Should group comments (`// React`) be added? (`groupComments`, default: yes)
82
+
83
+ Do **not** offer to toggle sorting or deduplication. Imports are always sorted
84
+ alphabetically within a group, named imports are always sorted within the
85
+ braces, and duplicate specifiers are always merged. There are no options for
86
+ these.
87
+
88
+ Wait for confirmation before proceeding.
89
+
90
+ ## Step 5 — Write apotheke.config.mjs
91
+
92
+ **Always use `.mjs`** — it works with both the CLI and the Node.js prettier plugin. Never write `.ts` configs: Node.js (where prettier runs) cannot `import()` TypeScript files. Only `apotheke.config.mjs` and `apotheke.config.js` are discovered.
93
+
94
+ ```js
95
+ // apotheke.config.mjs
96
+ export default {
97
+ groups: [
98
+ // ... confirmed groups
99
+ ],
100
+ aliases: {
101
+ // ... from tsconfig paths
102
+ },
103
+ groupSeparator: true,
104
+ groupComments: true
105
+ };
106
+ ```
107
+
108
+ For a monorepo with a shared root:
109
+
110
+ ```js
111
+ // apps/web/apotheke.config.mjs
112
+ export default {
113
+ extends: '../../apotheke.config.mjs',
114
+ groups: [
115
+ // package-specific groups only — root groups are inherited
116
+ { name: 'Local Components', match: ['**/components/**'] }
117
+ ]
118
+ };
119
+ ```
120
+
121
+ ## Step 6 — Install apotheke and wire up prettier
122
+
123
+ ### 6a. Install apotheke
124
+
125
+ Check if apotheke is already in `package.json` dependencies. If not, install it:
126
+
127
+ - Bun project (`bun.lock` present) → `bun add -D apotheke`
128
+ - pnpm (`pnpm-lock.yaml`) → `pnpm add -D apotheke`
129
+ - yarn (`yarn.lock`) → `yarn add -D apotheke`
130
+ - npm (`package-lock.json`) → `npm install -D apotheke`
131
+
132
+ In a monorepo, install at the root unless the workspace requires per-package installs.
133
+
134
+ ### 6b. Add apotheke to the prettier config
135
+
136
+ Check for `.prettierrc`, `.prettierrc.js`, `.prettierrc.json`, or `prettier.config.js`. Add `"apotheke"` to the `plugins` array (create the file if none exists):
137
+
138
+ ```js
139
+ // prettier.config.js (or .prettierrc.js)
140
+ module.exports = {
141
+ // ...existing options unchanged...
142
+ plugins: ['apotheke']
143
+ };
144
+ ```
145
+
146
+ For JSON prettier configs (`.prettierrc` or `.prettierrc.json`):
147
+
148
+ ```json
149
+ {
150
+ "plugins": ["apotheke"]
151
+ }
152
+ ```
153
+
154
+ > **Ordering is critical**: apotheke must be the **last** entry in `plugins`. It chains to the previous plugin's `preprocess` hook via `getBase()?.preprocess`, so any plugin listed after it will completely override apotheke's preprocess and imports will not be organized. If another prettier plugin (e.g. `prettier-plugin-tailwindcss`) is already present, place `"apotheke"` after it:
155
+ >
156
+ > ```json
157
+ > { "plugins": ["prettier-plugin-tailwindcss", "apotheke"] }
158
+ > ```
159
+
160
+ ### 6c. Verify prettier version
161
+
162
+ Check that prettier v3 is installed (`"prettier": "^3"` in devDependencies). If v2 is found, tell the user:
163
+
164
+ > "Apotheke's prettier plugin requires prettier v3 (async `preprocess`). I can upgrade it, or set up the v2 fallback (CLI + prettier in sequence). Which do you prefer?"
165
+
166
+ **v2 fallback** (lint-staged only):
167
+
168
+ ```json
169
+ {
170
+ "lint-staged": {
171
+ "*.{ts,tsx,js,jsx}": ["apotheke --write", "prettier --write"]
172
+ }
173
+ }
174
+ ```
175
+
176
+ ## Step 7 — Add scripts to package.json
177
+
178
+ With the prettier plugin wired up, a single `prettier --write` does everything. Add:
179
+
180
+ ```json
181
+ {
182
+ "scripts": {
183
+ "format": "prettier --write 'src/**/*.{ts,tsx,js,jsx}'",
184
+ "format:check": "prettier --check 'src/**/*.{ts,tsx,js,jsx}'"
185
+ }
186
+ }
187
+ ```
188
+
189
+ Adjust the glob to match the project's source folder. In a monorepo, also add a root script:
190
+
191
+ ```json
192
+ {
193
+ "scripts": {
194
+ "format": "prettier --write 'packages/*/src/**/*.{ts,tsx}' 'apps/*/src/**/*.{ts,tsx}'",
195
+ "format:check": "prettier --check 'packages/*/src/**/*.{ts,tsx}' 'apps/*/src/**/*.{ts,tsx}'"
196
+ }
197
+ }
198
+ ```
199
+
200
+ ## Step 8 — VS Code on-save (optional)
201
+
202
+ Ask the user: "Do you want imports organized automatically on save in VS Code?"
203
+
204
+ If yes, the prettier VS Code extension handles this automatically once the plugin is in the prettier config — no extra config needed. Confirm that the extension is installed and `editor.formatOnSave` is enabled:
205
+
206
+ ```json
207
+ // .vscode/settings.json
208
+ {
209
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
210
+ "editor.formatOnSave": true,
211
+ "editor.codeActionsOnSave": {
212
+ "source.organizeImports": false
213
+ }
214
+ }
215
+ ```
216
+
217
+ Disable `source.organizeImports` to prevent VS Code's built-in import sorter from conflicting with apotheke.
218
+
219
+ ## Step 9 — Run a dry-run
220
+
221
+ Run a true dry-run (no writes) on a small sample (3–5 files, one per package in a monorepo) and show the user what would change:
222
+
223
+ ```sh
224
+ prettier --list-different 'src/App.tsx' 'src/main.tsx'
225
+ ```
226
+
227
+ > `--write --list-different` together would actually write the files; use `--list-different` alone for a real dry-run.
228
+
229
+ Show the before/after for imports to confirm the groups look right.
230
+
231
+ Ask: "Does this look right? Shall I run `format` on the whole project?"
232
+
233
+ If yes, run the `format` script using the package manager detected in Step 6a
234
+ (`pnpm run format`, `bun run format`, `yarn format` or `npm run format`).
235
+
236
+ ## Final summary
237
+
238
+ Report:
239
+
240
+ - Project type: single-package or monorepo (list package roots if monorepo)
241
+ - Config(s) written to: paths
242
+ - Groups configured: list them
243
+ - Prettier plugin: wired up in `prettier.config.js` (or equivalent)
244
+ - Script added: `format` / `format:check`
245
+ - Files that would be changed: N files
246
+ - Whether `--write` was run
247
+
248
+ Point the user at the documentation for anything beyond this setup:
249
+ <https://marcoraffaello.com/apotheke>
package/dist/cli.js CHANGED
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // cli.ts
4
- import path3 from "path";
5
- import fs from "fs";
6
4
  import { execFileSync } from "child_process";
5
+ import fs from "fs";
6
+ import path3 from "path";
7
7
  import fg from "fast-glob";
8
8
 
9
9
  // src/config.ts
10
- import path from "path";
11
10
  import { existsSync, readFileSync } from "fs";
11
+ import path from "path";
12
12
  function mergeConfigs(parent, child) {
13
13
  const mergedGroups = [...parent.groups];
14
14
  for (const childGroup of child.groups ?? []) {
@@ -74,65 +74,6 @@ function loadTsConfig(dir) {
74
74
  }
75
75
  }
76
76
 
77
- // src/parser.ts
78
- import { parseSync } from "oxc-parser";
79
- function parseImports(source) {
80
- const result = parseSync("file.tsx", source);
81
- const comments = result.comments;
82
- const nodes = [];
83
- for (const node of result.program.body) {
84
- if (node.type !== "ImportDeclaration") continue;
85
- const specifier = node.source.value;
86
- let defaultImport;
87
- const namedImports = [];
88
- let namespaceImport;
89
- for (const s of node.specifiers) {
90
- if (s.type === "ImportDefaultSpecifier") {
91
- defaultImport = s.local.name;
92
- } else if (s.type === "ImportNamespaceSpecifier") {
93
- namespaceImport = s.local.name;
94
- } else if (s.type === "ImportSpecifier") {
95
- const imported = s.imported;
96
- const name = imported.type === "Identifier" ? imported.name : imported.value;
97
- const localName = s.local.name;
98
- const kind = s.importKind === "type" ? "type" : "value";
99
- const named = { name, kind };
100
- if (localName !== name) named.alias = localName;
101
- namedImports.push(named);
102
- }
103
- }
104
- const importKind = node.importKind === "type" ? "type" : "value";
105
- const isSideEffect = node.specifiers.length === 0 && importKind === "value";
106
- const importNode = {
107
- specifier,
108
- namedImports,
109
- isSideEffect,
110
- importKind,
111
- start: node.start,
112
- end: node.end
113
- };
114
- if (defaultImport) importNode.defaultImport = defaultImport;
115
- if (namespaceImport) importNode.namespaceImport = namespaceImport;
116
- const attached = findAttachedComment(source, node.start, comments);
117
- if (attached) importNode.attachedComment = attached;
118
- nodes.push(importNode);
119
- }
120
- return nodes;
121
- }
122
- function findAttachedComment(source, importStart, comments) {
123
- const linesBefore = source.slice(0, importStart).split("\n");
124
- const importLine = linesBefore.length - 1;
125
- for (const comment of comments) {
126
- if (comment.type !== "Line") continue;
127
- const commentLines = source.slice(0, comment.end).split("\n");
128
- const commentLine = commentLines.length - 1;
129
- if (commentLine === importLine - 1) {
130
- return `//${comment.value}`;
131
- }
132
- }
133
- return void 0;
134
- }
135
-
136
77
  // src/deduplicator.ts
137
78
  function deduplicateImports(imports) {
138
79
  const map = /* @__PURE__ */ new Map();
@@ -241,21 +182,63 @@ function globToRegex(pattern) {
241
182
  return new RegExp(re + "$");
242
183
  }
243
184
 
244
- // src/sorter.ts
245
- function sortGroup(imports) {
246
- return [...imports].sort((a, b) => {
247
- if (a.importKind === "type" && b.importKind !== "type") return -1;
248
- if (a.importKind !== "type" && b.importKind === "type") return 1;
249
- return a.specifier.localeCompare(b.specifier);
250
- });
185
+ // src/parser.ts
186
+ import { parseSync } from "oxc-parser";
187
+ function parseImports(source) {
188
+ const result = parseSync("file.tsx", source);
189
+ const comments = result.comments;
190
+ const nodes = [];
191
+ for (const node of result.program.body) {
192
+ if (node.type !== "ImportDeclaration") continue;
193
+ const specifier = node.source.value;
194
+ let defaultImport;
195
+ const namedImports = [];
196
+ let namespaceImport;
197
+ for (const s of node.specifiers) {
198
+ if (s.type === "ImportDefaultSpecifier") {
199
+ defaultImport = s.local.name;
200
+ } else if (s.type === "ImportNamespaceSpecifier") {
201
+ namespaceImport = s.local.name;
202
+ } else if (s.type === "ImportSpecifier") {
203
+ const imported = s.imported;
204
+ const name = imported.type === "Identifier" ? imported.name : imported.value;
205
+ const localName = s.local.name;
206
+ const kind = s.importKind === "type" ? "type" : "value";
207
+ const named = { name, kind };
208
+ if (localName !== name) named.alias = localName;
209
+ namedImports.push(named);
210
+ }
211
+ }
212
+ const importKind = node.importKind === "type" ? "type" : "value";
213
+ const isSideEffect = node.specifiers.length === 0 && importKind === "value";
214
+ const importNode = {
215
+ specifier,
216
+ namedImports,
217
+ isSideEffect,
218
+ importKind,
219
+ start: node.start,
220
+ end: node.end
221
+ };
222
+ if (defaultImport) importNode.defaultImport = defaultImport;
223
+ if (namespaceImport) importNode.namespaceImport = namespaceImport;
224
+ const attached = findAttachedComment(source, node.start, comments);
225
+ if (attached) importNode.attachedComment = attached;
226
+ nodes.push(importNode);
227
+ }
228
+ return nodes;
251
229
  }
252
- function sortNamedImports(node) {
253
- const sorted = [...node.namedImports].sort((a, b) => {
254
- if (a.kind === "type" && b.kind !== "type") return -1;
255
- if (a.kind !== "type" && b.kind === "type") return 1;
256
- return a.name.localeCompare(b.name);
257
- });
258
- return { ...node, namedImports: sorted };
230
+ function findAttachedComment(source, importStart, comments) {
231
+ const linesBefore = source.slice(0, importStart).split("\n");
232
+ const importLine = linesBefore.length - 1;
233
+ for (const comment of comments) {
234
+ if (comment.type !== "Line") continue;
235
+ const commentLines = source.slice(0, comment.end).split("\n");
236
+ const commentLine = commentLines.length - 1;
237
+ if (commentLine === importLine - 1) {
238
+ return `//${comment.value}`;
239
+ }
240
+ }
241
+ return void 0;
259
242
  }
260
243
 
261
244
  // src/printer.ts
@@ -307,6 +290,23 @@ function buildImportStatement(node, q) {
307
290
  return `import ${typePrefix}${parts.join(", ")} from ${q}${node.specifier}${q};`;
308
291
  }
309
292
 
293
+ // src/sorter.ts
294
+ function sortGroup(imports) {
295
+ return [...imports].sort((a, b) => {
296
+ if (a.importKind === "type" && b.importKind !== "type") return -1;
297
+ if (a.importKind !== "type" && b.importKind === "type") return 1;
298
+ return a.specifier.localeCompare(b.specifier);
299
+ });
300
+ }
301
+ function sortNamedImports(node) {
302
+ const sorted = [...node.namedImports].sort((a, b) => {
303
+ if (a.kind === "type" && b.kind !== "type") return -1;
304
+ if (a.kind !== "type" && b.kind === "type") return 1;
305
+ return a.name.localeCompare(b.name);
306
+ });
307
+ return { ...node, namedImports: sorted };
308
+ }
309
+
310
310
  // src/format.ts
311
311
  function collectOrphanSegments(source, imports) {
312
312
  const orphans = [];
@@ -497,19 +497,21 @@ async function expandGlobs(patterns) {
497
497
  }
498
498
  function getSubmoduleDirs(cwd) {
499
499
  try {
500
- const output = execFileSync("git", ["-C", cwd, "submodule", "foreach", "--recursive", "--quiet", "pwd"], {
501
- encoding: "utf-8",
502
- stdio: ["ignore", "pipe", "ignore"]
503
- });
500
+ const output = execFileSync(
501
+ "git",
502
+ ["-C", cwd, "submodule", "foreach", "--recursive", "--quiet", "pwd"],
503
+ {
504
+ encoding: "utf-8",
505
+ stdio: ["ignore", "pipe", "ignore"]
506
+ }
507
+ );
504
508
  return output.trim().split("\n").filter(Boolean);
505
509
  } catch {
506
510
  return [];
507
511
  }
508
512
  }
509
513
  function isUnderSubmodule(filePath, submoduleDirs) {
510
- return submoduleDirs.some(
511
- (dir) => filePath === dir || filePath.startsWith(dir + path3.sep)
512
- );
514
+ return submoduleDirs.some((dir) => filePath === dir || filePath.startsWith(dir + path3.sep));
513
515
  }
514
516
  function printDiff(file, original, updated) {
515
517
  const origLines = original.split("\n");
package/dist/index.d.ts CHANGED
@@ -7,7 +7,6 @@ interface ApothekeConfig {
7
7
  groups: GroupConfig[];
8
8
  aliases?: Record<string, string>;
9
9
  baseUrl?: string;
10
- normalizeImports?: 'alias' | 'relative' | 'absolute' | false;
11
10
  groupSeparator?: boolean;
12
11
  groupComments?: boolean;
13
12
  }
@@ -24,7 +23,7 @@ declare function formatImports(source: string, config: ApothekeConfig, options?:
24
23
  * Prettier plugin usage (.prettierrc):
25
24
  * { "plugins": ["apotheke"] }
26
25
  *
27
- * Requirements: prettier v3+, Bun runtime
26
+ * Requirements: prettier v3+, Node.js >= 18
28
27
  *
29
28
  * The plugin runs as a `preprocess` step, so apotheke organises imports
30
29
  * first and prettier formats the result — correct order, one pass.
@@ -32,9 +31,11 @@ declare function formatImports(source: string, config: ApothekeConfig, options?:
32
31
  * Pre-commit (lint-staged, prettier v2):
33
32
  * { "*.{ts,tsx}": ["apotheke --write", "prettier --write"] }
34
33
  */
34
+ declare function isDocumentSnippet(parentParser: unknown): boolean;
35
35
  type PrettierParser = {
36
36
  preprocess?: (text: string, opts: {
37
37
  filepath?: string;
38
+ parentParser?: string;
38
39
  }) => string | Promise<string>;
39
40
  parse?: (text: string, options: unknown) => unknown;
40
41
  astFormat?: string;
@@ -45,8 +46,8 @@ type PrettierParser = {
45
46
  declare const parsers: {
46
47
  typescript: PrettierParser;
47
48
  babel: PrettierParser;
48
- "babel-ts": PrettierParser;
49
- "babel-flow": PrettierParser;
49
+ 'babel-ts': PrettierParser;
50
+ 'babel-flow': PrettierParser;
50
51
  };
51
52
 
52
- export { type ApothekeConfig, formatImports, parsers };
53
+ export { type ApothekeConfig, formatImports, isDocumentSnippet, parsers };
package/dist/index.js CHANGED
@@ -1,64 +1,74 @@
1
1
  // index.ts
2
- import path3 from "path";
3
2
  import { existsSync as existsSync2 } from "fs";
3
+ import { createRequire } from "module";
4
+ import path3 from "path";
4
5
 
5
- // src/parser.ts
6
- import { parseSync } from "oxc-parser";
7
- function parseImports(source) {
8
- const result = parseSync("file.tsx", source);
9
- const comments = result.comments;
10
- const nodes = [];
11
- for (const node of result.program.body) {
12
- if (node.type !== "ImportDeclaration") continue;
13
- const specifier = node.source.value;
14
- let defaultImport;
15
- const namedImports = [];
16
- let namespaceImport;
17
- for (const s of node.specifiers) {
18
- if (s.type === "ImportDefaultSpecifier") {
19
- defaultImport = s.local.name;
20
- } else if (s.type === "ImportNamespaceSpecifier") {
21
- namespaceImport = s.local.name;
22
- } else if (s.type === "ImportSpecifier") {
23
- const imported = s.imported;
24
- const name = imported.type === "Identifier" ? imported.name : imported.value;
25
- const localName = s.local.name;
26
- const kind = s.importKind === "type" ? "type" : "value";
27
- const named = { name, kind };
28
- if (localName !== name) named.alias = localName;
29
- namedImports.push(named);
30
- }
6
+ // src/config.ts
7
+ import { existsSync, readFileSync } from "fs";
8
+ import path from "path";
9
+ function mergeConfigs(parent, child) {
10
+ const mergedGroups = [...parent.groups];
11
+ for (const childGroup of child.groups ?? []) {
12
+ const existingIdx = mergedGroups.findIndex((g) => g.name === childGroup.name);
13
+ if (existingIdx >= 0) {
14
+ mergedGroups[existingIdx] = childGroup;
15
+ } else {
16
+ mergedGroups.push(childGroup);
31
17
  }
32
- const importKind = node.importKind === "type" ? "type" : "value";
33
- const isSideEffect = node.specifiers.length === 0 && importKind === "value";
34
- const importNode = {
35
- specifier,
36
- namedImports,
37
- isSideEffect,
38
- importKind,
39
- start: node.start,
40
- end: node.end
41
- };
42
- if (defaultImport) importNode.defaultImport = defaultImport;
43
- if (namespaceImport) importNode.namespaceImport = namespaceImport;
44
- const attached = findAttachedComment(source, node.start, comments);
45
- if (attached) importNode.attachedComment = attached;
46
- nodes.push(importNode);
47
18
  }
48
- return nodes;
19
+ return {
20
+ ...parent,
21
+ ...child,
22
+ groups: mergedGroups,
23
+ aliases: { ...parent.aliases ?? {}, ...child.aliases ?? {} }
24
+ };
49
25
  }
50
- function findAttachedComment(source, importStart, comments) {
51
- const linesBefore = source.slice(0, importStart).split("\n");
52
- const importLine = linesBefore.length - 1;
53
- for (const comment of comments) {
54
- if (comment.type !== "Line") continue;
55
- const commentLines = source.slice(0, comment.end).split("\n");
56
- const commentLine = commentLines.length - 1;
57
- if (commentLine === importLine - 1) {
58
- return `//${comment.value}`;
26
+ async function loadConfig(configPath) {
27
+ const mod = await import(configPath);
28
+ const userConfig = mod.default ?? mod;
29
+ const configDir = path.dirname(configPath);
30
+ const config = { ...userConfig };
31
+ const tsconfig = loadTsConfig(configDir);
32
+ if (tsconfig) {
33
+ const opts = tsconfig.compilerOptions ?? {};
34
+ if (opts.baseUrl && !config.baseUrl) {
35
+ config.baseUrl = opts.baseUrl;
36
+ }
37
+ if (opts.paths) {
38
+ const aliases = { ...config.aliases ?? {} };
39
+ for (const [aliasPattern, targets] of Object.entries(
40
+ opts.paths
41
+ )) {
42
+ const alias = aliasPattern.replace(/\/\*$/, "");
43
+ const target = (targets[0] ?? "").replace(/\/\*$/, "");
44
+ if (!aliases[alias]) aliases[alias] = target;
45
+ }
46
+ config.aliases = aliases;
59
47
  }
60
48
  }
61
- return void 0;
49
+ if (userConfig.extends) {
50
+ const parentPath = path.resolve(configDir, userConfig.extends);
51
+ if (!existsSync(parentPath)) {
52
+ throw new Error(
53
+ `apotheke: extended config not found: ${parentPath}
54
+ (referenced from ${configPath} via "extends": "${userConfig.extends}")`
55
+ );
56
+ }
57
+ const parent = await loadConfig(parentPath);
58
+ return mergeConfigs(parent, config);
59
+ }
60
+ return config;
61
+ }
62
+ function loadTsConfig(dir) {
63
+ const tsconfigPath = path.join(dir, "tsconfig.json");
64
+ if (!existsSync(tsconfigPath)) return null;
65
+ try {
66
+ const text = readFileSync(tsconfigPath, "utf8");
67
+ const cleaned = text.replace(/\/\/[^\n]*/g, "").replace(/\/\*[\s\S]*?\*\//g, "");
68
+ return JSON.parse(cleaned);
69
+ } catch {
70
+ return null;
71
+ }
62
72
  }
63
73
 
64
74
  // src/deduplicator.ts
@@ -88,7 +98,7 @@ function deduplicateImports(imports) {
88
98
  }
89
99
 
90
100
  // src/grouper.ts
91
- import path from "path";
101
+ import path2 from "path";
92
102
  function groupImports(imports, config, options = {}) {
93
103
  const buckets = /* @__PURE__ */ new Map();
94
104
  for (const node of imports) {
@@ -124,7 +134,7 @@ function assignGroup(node, config, options) {
124
134
  function resolveCanonicalPath(specifier, config, options) {
125
135
  if (specifier.startsWith(".") || specifier.startsWith("/")) {
126
136
  const base = options.fileDir ?? process.cwd();
127
- return path.resolve(base, specifier);
137
+ return path2.resolve(base, specifier);
128
138
  }
129
139
  const aliases = config.aliases ?? {};
130
140
  for (const [alias, target] of Object.entries(aliases)) {
@@ -132,7 +142,7 @@ function resolveCanonicalPath(specifier, config, options) {
132
142
  if (specifier === alias || specifier.startsWith(prefix)) {
133
143
  const rest = specifier.slice(prefix.length);
134
144
  const root = options.rootDir ?? process.cwd();
135
- const resolved = path.resolve(root, target, rest);
145
+ const resolved = path2.resolve(root, target, rest);
136
146
  return resolved;
137
147
  }
138
148
  }
@@ -169,21 +179,63 @@ function globToRegex(pattern) {
169
179
  return new RegExp(re + "$");
170
180
  }
171
181
 
172
- // src/sorter.ts
173
- function sortGroup(imports) {
174
- return [...imports].sort((a, b) => {
175
- if (a.importKind === "type" && b.importKind !== "type") return -1;
176
- if (a.importKind !== "type" && b.importKind === "type") return 1;
177
- return a.specifier.localeCompare(b.specifier);
178
- });
182
+ // src/parser.ts
183
+ import { parseSync } from "oxc-parser";
184
+ function parseImports(source) {
185
+ const result = parseSync("file.tsx", source);
186
+ const comments = result.comments;
187
+ const nodes = [];
188
+ for (const node of result.program.body) {
189
+ if (node.type !== "ImportDeclaration") continue;
190
+ const specifier = node.source.value;
191
+ let defaultImport;
192
+ const namedImports = [];
193
+ let namespaceImport;
194
+ for (const s of node.specifiers) {
195
+ if (s.type === "ImportDefaultSpecifier") {
196
+ defaultImport = s.local.name;
197
+ } else if (s.type === "ImportNamespaceSpecifier") {
198
+ namespaceImport = s.local.name;
199
+ } else if (s.type === "ImportSpecifier") {
200
+ const imported = s.imported;
201
+ const name = imported.type === "Identifier" ? imported.name : imported.value;
202
+ const localName = s.local.name;
203
+ const kind = s.importKind === "type" ? "type" : "value";
204
+ const named = { name, kind };
205
+ if (localName !== name) named.alias = localName;
206
+ namedImports.push(named);
207
+ }
208
+ }
209
+ const importKind = node.importKind === "type" ? "type" : "value";
210
+ const isSideEffect = node.specifiers.length === 0 && importKind === "value";
211
+ const importNode = {
212
+ specifier,
213
+ namedImports,
214
+ isSideEffect,
215
+ importKind,
216
+ start: node.start,
217
+ end: node.end
218
+ };
219
+ if (defaultImport) importNode.defaultImport = defaultImport;
220
+ if (namespaceImport) importNode.namespaceImport = namespaceImport;
221
+ const attached = findAttachedComment(source, node.start, comments);
222
+ if (attached) importNode.attachedComment = attached;
223
+ nodes.push(importNode);
224
+ }
225
+ return nodes;
179
226
  }
180
- function sortNamedImports(node) {
181
- const sorted = [...node.namedImports].sort((a, b) => {
182
- if (a.kind === "type" && b.kind !== "type") return -1;
183
- if (a.kind !== "type" && b.kind === "type") return 1;
184
- return a.name.localeCompare(b.name);
185
- });
186
- return { ...node, namedImports: sorted };
227
+ function findAttachedComment(source, importStart, comments) {
228
+ const linesBefore = source.slice(0, importStart).split("\n");
229
+ const importLine = linesBefore.length - 1;
230
+ for (const comment of comments) {
231
+ if (comment.type !== "Line") continue;
232
+ const commentLines = source.slice(0, comment.end).split("\n");
233
+ const commentLine = commentLines.length - 1;
234
+ if (commentLine === importLine - 1) {
235
+ return `//${comment.value}`;
236
+ }
237
+ }
238
+ return void 0;
187
239
  }
188
240
 
189
241
  // src/printer.ts
@@ -235,6 +287,23 @@ function buildImportStatement(node, q) {
235
287
  return `import ${typePrefix}${parts.join(", ")} from ${q}${node.specifier}${q};`;
236
288
  }
237
289
 
290
+ // src/sorter.ts
291
+ function sortGroup(imports) {
292
+ return [...imports].sort((a, b) => {
293
+ if (a.importKind === "type" && b.importKind !== "type") return -1;
294
+ if (a.importKind !== "type" && b.importKind === "type") return 1;
295
+ return a.specifier.localeCompare(b.specifier);
296
+ });
297
+ }
298
+ function sortNamedImports(node) {
299
+ const sorted = [...node.namedImports].sort((a, b) => {
300
+ if (a.kind === "type" && b.kind !== "type") return -1;
301
+ if (a.kind !== "type" && b.kind === "type") return 1;
302
+ return a.name.localeCompare(b.name);
303
+ });
304
+ return { ...node, namedImports: sorted };
305
+ }
306
+
238
307
  // src/format.ts
239
308
  function collectOrphanSegments(source, imports) {
240
309
  const orphans = [];
@@ -282,76 +351,7 @@ function formatImports(source, config, options = {}) {
282
351
  return before + newImportBlock + "\n" + orphanSuffix + after;
283
352
  }
284
353
 
285
- // src/config.ts
286
- import path2 from "path";
287
- import { existsSync, readFileSync } from "fs";
288
- function mergeConfigs(parent, child) {
289
- const mergedGroups = [...parent.groups];
290
- for (const childGroup of child.groups ?? []) {
291
- const existingIdx = mergedGroups.findIndex((g) => g.name === childGroup.name);
292
- if (existingIdx >= 0) {
293
- mergedGroups[existingIdx] = childGroup;
294
- } else {
295
- mergedGroups.push(childGroup);
296
- }
297
- }
298
- return {
299
- ...parent,
300
- ...child,
301
- groups: mergedGroups,
302
- aliases: { ...parent.aliases ?? {}, ...child.aliases ?? {} }
303
- };
304
- }
305
- async function loadConfig(configPath) {
306
- const mod = await import(configPath);
307
- const userConfig = mod.default ?? mod;
308
- const configDir = path2.dirname(configPath);
309
- const config = { ...userConfig };
310
- const tsconfig = loadTsConfig(configDir);
311
- if (tsconfig) {
312
- const opts = tsconfig.compilerOptions ?? {};
313
- if (opts.baseUrl && !config.baseUrl) {
314
- config.baseUrl = opts.baseUrl;
315
- }
316
- if (opts.paths) {
317
- const aliases = { ...config.aliases ?? {} };
318
- for (const [aliasPattern, targets] of Object.entries(
319
- opts.paths
320
- )) {
321
- const alias = aliasPattern.replace(/\/\*$/, "");
322
- const target = (targets[0] ?? "").replace(/\/\*$/, "");
323
- if (!aliases[alias]) aliases[alias] = target;
324
- }
325
- config.aliases = aliases;
326
- }
327
- }
328
- if (userConfig.extends) {
329
- const parentPath = path2.resolve(configDir, userConfig.extends);
330
- if (!existsSync(parentPath)) {
331
- throw new Error(
332
- `apotheke: extended config not found: ${parentPath}
333
- (referenced from ${configPath} via "extends": "${userConfig.extends}")`
334
- );
335
- }
336
- const parent = await loadConfig(parentPath);
337
- return mergeConfigs(parent, config);
338
- }
339
- return config;
340
- }
341
- function loadTsConfig(dir) {
342
- const tsconfigPath = path2.join(dir, "tsconfig.json");
343
- if (!existsSync(tsconfigPath)) return null;
344
- try {
345
- const text = readFileSync(tsconfigPath, "utf8");
346
- const cleaned = text.replace(/\/\/[^\n]*/g, "").replace(/\/\*[\s\S]*?\*\//g, "");
347
- return JSON.parse(cleaned);
348
- } catch {
349
- return null;
350
- }
351
- }
352
-
353
354
  // index.ts
354
- import { createRequire } from "module";
355
355
  async function resolveConfig(filePath) {
356
356
  const candidates = ["apotheke.config.mjs", "apotheke.config.js"];
357
357
  let dir = path3.dirname(path3.resolve(filePath));
@@ -368,7 +368,12 @@ async function resolveConfig(filePath) {
368
368
  `apotheke: no config found for ${filePath}. Create an apotheke.config.mjs in your project root.`
369
369
  );
370
370
  }
371
+ var DOCUMENT_PARSERS = /* @__PURE__ */ new Set(["markdown", "mdx"]);
372
+ function isDocumentSnippet(parentParser) {
373
+ return typeof parentParser === "string" && DOCUMENT_PARSERS.has(parentParser);
374
+ }
371
375
  async function preprocess(text, options) {
376
+ if (isDocumentSnippet(options.parentParser)) return text;
372
377
  try {
373
378
  const config = await resolveConfig(options.filepath ?? process.cwd());
374
379
  const fileDir = options.filepath ? path3.dirname(path3.resolve(options.filepath)) : void 0;
@@ -436,7 +441,10 @@ function makeParser(pluginPath, parserName) {
436
441
  async preprocess(text, opts) {
437
442
  const base = getBase(opts?.filepath);
438
443
  const basePreprocess = base?.preprocess;
439
- const after = basePreprocess ? await basePreprocess(text, opts) : text;
444
+ const after = basePreprocess ? await basePreprocess(
445
+ text,
446
+ opts
447
+ ) : text;
440
448
  return preprocess(after, opts);
441
449
  }
442
450
  };
@@ -449,5 +457,6 @@ var parsers = {
449
457
  };
450
458
  export {
451
459
  formatImports,
460
+ isDocumentSnippet,
452
461
  parsers
453
462
  };
package/package.json CHANGED
@@ -1,48 +1,67 @@
1
1
  {
2
- "name": "apotheke",
3
- "version": "0.0.1",
4
- "description": "JavaScript/TypeScript import organizerprettier plugin + CLI",
5
- "type": "module",
6
- "repository": {
7
- "url": "https://github.com/mRaffaello/apotheke"
8
- },
9
- "exports": {
10
- ".": "./dist/index.js"
11
- },
12
- "main": "./dist/index.js",
13
- "types": "./dist/index.d.ts",
14
- "bin": {
15
- "apotheke": "./dist/cli.js"
16
- },
17
- "files": [
18
- "dist"
19
- ],
20
- "devDependencies": {
21
- "@commitlint/cli": "^20.5.3",
22
- "@commitlint/config-conventional": "^20.5.3",
23
- "@types/node": "^25.6.0",
24
- "husky": "^9.1.7",
25
- "prettier": "^3.8.3",
26
- "tsup": "^8.5.1",
27
- "vitest": "^4.1.5"
28
- },
29
- "peerDependencies": {
30
- "prettier": ">=3"
31
- },
32
- "peerDependenciesMeta": {
33
- "prettier": {
34
- "optional": true
2
+ "name": "apotheke",
3
+ "version": "0.1.0",
4
+ "description": "Prettier for importsdeterministic import organization as a Prettier plugin or CLI",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "Marco Raffaello",
8
+ "homepage": "https://marcoraffaello.com/apotheke",
9
+ "keywords": [
10
+ "imports",
11
+ "import-sorter",
12
+ "organize-imports",
13
+ "prettier",
14
+ "prettier-plugin",
15
+ "typescript",
16
+ "javascript",
17
+ "codemod",
18
+ "monorepo"
19
+ ],
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/mRaffaello/apotheke",
23
+ "directory": "packages/apotheke"
24
+ },
25
+ "bugs": {
26
+ "url": "https://github.com/mRaffaello/apotheke/issues"
27
+ },
28
+ "exports": {
29
+ ".": "./dist/index.js"
30
+ },
31
+ "main": "./dist/index.js",
32
+ "types": "./dist/index.d.ts",
33
+ "bin": {
34
+ "apotheke": "./dist/cli.js"
35
+ },
36
+ "files": [
37
+ "dist",
38
+ "SKILL.md"
39
+ ],
40
+ "scripts": {
41
+ "build": "tsup",
42
+ "lint": "eslint .",
43
+ "lint:fix": "eslint . --fix",
44
+ "typecheck": "tsc --noEmit",
45
+ "test": "vitest run",
46
+ "test:unit": "vitest run tests/unit",
47
+ "test:e2e": "vitest run tests/e2e/e2e.test.ts"
48
+ },
49
+ "devDependencies": {
50
+ "@types/node": "^25.6.0",
51
+ "tsup": "^8.5.1",
52
+ "typescript": "~5.9.3",
53
+ "vitest": "^4.1.5"
54
+ },
55
+ "peerDependencies": {
56
+ "prettier": ">=3"
57
+ },
58
+ "peerDependenciesMeta": {
59
+ "prettier": {
60
+ "optional": true
61
+ }
62
+ },
63
+ "dependencies": {
64
+ "fast-glob": "^3.3.3",
65
+ "oxc-parser": "^0.129.0"
35
66
  }
36
- },
37
- "dependencies": {
38
- "fast-glob": "^3.3.3",
39
- "oxc-parser": "^0.129.0"
40
- },
41
- "scripts": {
42
- "build": "tsup",
43
- "typecheck": "tsc --noEmit",
44
- "test": "vitest run",
45
- "test:unit": "vitest run tests/unit",
46
- "test:e2e": "vitest run tests/e2e/e2e.test.ts"
47
- }
48
- }
67
+ }