apotheke 0.1.0 → 0.1.1

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 CHANGED
@@ -1,9 +1,22 @@
1
- # apotheke
2
-
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)**
1
+ <p align="left">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="https://shieldcn.dev/header/graph.svg?title=apotheke&subtitle=Deterministic%20import%20organization%20for%20JavaScript%20and%20TypeScript&logo=https://raw.githubusercontent.com/mRaffaello/apotheke/main/apps/docs/public/mark-dark.png&align=left&font=geist&mode=dark" />
4
+ <img alt="apothekedeterministic import organization for JavaScript and TypeScript" src="https://shieldcn.dev/header/graph.svg?title=apotheke&subtitle=Deterministic%20import%20organization%20for%20JavaScript%20and%20TypeScript&logo=https://raw.githubusercontent.com/mRaffaello/apotheke/main/apps/docs/public/mark.png&align=left&font=geist&mode=light" />
5
+ </picture>
6
+ </p>
7
+
8
+ <p align="left">
9
+ <a href="https://www.npmjs.com/package/apotheke">
10
+ <picture>
11
+ <source media="(prefers-color-scheme: dark)" srcset="https://shieldcn.dev/group/npm/apotheke+npm/dm/apotheke+bundlephobia/minzip/apotheke+npm/types/apotheke+github/ci/mRaffaello/apotheke+github/license/mRaffaello/apotheke.svg?variant=secondary&mode=dark" />
12
+ <img alt="npm version, monthly downloads, minzipped size, TypeScript types, CI status, MIT license" src="https://shieldcn.dev/group/npm/apotheke+npm/dm/apotheke+bundlephobia/minzip/apotheke+npm/types/apotheke+github/ci/mRaffaello/apotheke+github/license/mRaffaello/apotheke.svg?variant=secondary&mode=light" />
13
+ </picture>
14
+ </a>
15
+ </p>
16
+
17
+ **Prettier for imports.** You describe the groups your codebase actually has —
18
+ apotheke sorts every import into them, the same way, in every file. Run it as a
19
+ Prettier plugin or as a standalone CLI.
7
20
 
8
21
  ```ts
9
22
  // Before
@@ -28,72 +41,20 @@ import { useQuery } from '@tanstack/react-query';
28
41
  import { createRoute } from '@tanstack/react-router';
29
42
  ```
30
43
 
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.
44
+ Note where `../api/tsr` and `@tanstack/react-query` landed: grouping is by
45
+ **role in your app**, not by whether a module is local or comes from
46
+ `node_modules`.
34
47
 
35
- ## Installation
48
+ ## Getting started
36
49
 
37
50
  ```sh
38
51
  pnpm add -D apotheke
39
52
  ```
40
53
 
41
- Requires Node.js 18, and Prettier ≥ 3 if you use the plugin.
42
-
43
- ## Usage
44
-
45
- Describe the groups you want in `apotheke.config.mjs`:
46
-
47
- ```js
48
- export default {
49
- groups: [
50
- { name: 'React', match: ['react', 'react-dom'] },
51
- { name: 'Hooks', match: ['**/hooks/**'] },
52
- { name: 'Api', match: ['**/api/**', '@tanstack/react-query'] },
53
- { name: 'Navigation', match: ['@tanstack/react-router'] }
54
- ]
55
- };
56
- ```
57
-
58
- Then either add the Prettier plugin — apotheke must be **last** in the array:
59
-
60
- ```json
61
- { "plugins": ["apotheke"] }
62
- ```
63
-
64
- or use the CLI:
65
-
66
- ```sh
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
70
- ```
71
-
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.
76
-
77
- ## Documentation
78
-
79
- Full guides and reference at
80
- **[marcoraffaello.com/apotheke](https://marcoraffaello.com/apotheke)**:
81
-
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)
54
+ 📖 **[Read the documentation →](https://marcoraffaello.com/apotheke)**
87
55
 
88
- ## Agent skill
89
-
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:
92
-
93
- ```sh
94
- mkdir -p .claude/skills/setup-apotheke
95
- cp node_modules/apotheke/SKILL.md .claude/skills/setup-apotheke/SKILL.md
96
- ```
56
+ The [quick start](https://marcoraffaello.com/apotheke/docs/quick-start) goes
57
+ from an empty config to organised imports in about two minutes.
97
58
 
98
59
  ## Development
99
60
 
@@ -107,6 +68,9 @@ pnpm test # unit + e2e
107
68
  pnpm docs:dev # run the documentation site locally
108
69
  ```
109
70
 
71
+ Issues and pull requests are welcome at
72
+ [github.com/mRaffaello/apotheke](https://github.com/mRaffaello/apotheke).
73
+
110
74
  ## License
111
75
 
112
76
  MIT © Marco Raffaello
package/dist/cli.js CHANGED
@@ -102,6 +102,8 @@ function deduplicateImports(imports) {
102
102
 
103
103
  // src/grouper.ts
104
104
  import path2 from "path";
105
+ var SIDE_EFFECTS_GROUP = "SideEffects";
106
+ var OTHERS_GROUP = "Others";
105
107
  function groupImports(imports, config, options = {}) {
106
108
  const buckets = /* @__PURE__ */ new Map();
107
109
  for (const node of imports) {
@@ -110,21 +112,21 @@ function groupImports(imports, config, options = {}) {
110
112
  buckets.get(groupName).push(node);
111
113
  }
112
114
  const result = [];
113
- if (buckets.has("SideEffects")) {
114
- result.push({ name: "SideEffects", imports: buckets.get("SideEffects") });
115
+ if (buckets.has(SIDE_EFFECTS_GROUP)) {
116
+ result.push({ name: SIDE_EFFECTS_GROUP, imports: buckets.get(SIDE_EFFECTS_GROUP) });
115
117
  }
116
118
  for (const group of config.groups) {
117
119
  if (buckets.has(group.name)) {
118
120
  result.push({ name: group.name, imports: buckets.get(group.name) });
119
121
  }
120
122
  }
121
- if (buckets.has("Others")) {
122
- result.push({ name: "Others", imports: buckets.get("Others") });
123
+ if (buckets.has(OTHERS_GROUP)) {
124
+ result.push({ name: OTHERS_GROUP, imports: buckets.get(OTHERS_GROUP) });
123
125
  }
124
126
  return result;
125
127
  }
126
128
  function assignGroup(node, config, options) {
127
- if (node.isSideEffect) return "SideEffects";
129
+ if (node.isSideEffect) return SIDE_EFFECTS_GROUP;
128
130
  const canonicalPath = resolveCanonicalPath(node.specifier, config, options);
129
131
  for (const group of config.groups) {
130
132
  for (const pattern of group.match) {
@@ -132,7 +134,7 @@ function assignGroup(node, config, options) {
132
134
  if (canonicalPath && matches(canonicalPath, pattern)) return group.name;
133
135
  }
134
136
  }
135
- return "Others";
137
+ return OTHERS_GROUP;
136
138
  }
137
139
  function resolveCanonicalPath(specifier, config, options) {
138
140
  if (specifier.startsWith(".") || specifier.startsWith("/")) {
@@ -335,7 +337,9 @@ function formatImports(source, config, options = {}) {
335
337
  const deduped = deduplicateImports(clean);
336
338
  const sorted = deduped.map(sortNamedImports);
337
339
  const grouped = groupImports(sorted, config, options);
338
- const sortedGroups = grouped.map((g) => ({ ...g, imports: sortGroup(g.imports) }));
340
+ const sortedGroups = grouped.map(
341
+ (g) => g.name === SIDE_EFFECTS_GROUP ? g : { ...g, imports: sortGroup(g.imports) }
342
+ );
339
343
  const q = detectQuoteChar(source);
340
344
  const newImportBlock = printGroups(sortedGroups, config, q);
341
345
  const firstImport = imports[0];
package/dist/index.js CHANGED
@@ -99,6 +99,8 @@ function deduplicateImports(imports) {
99
99
 
100
100
  // src/grouper.ts
101
101
  import path2 from "path";
102
+ var SIDE_EFFECTS_GROUP = "SideEffects";
103
+ var OTHERS_GROUP = "Others";
102
104
  function groupImports(imports, config, options = {}) {
103
105
  const buckets = /* @__PURE__ */ new Map();
104
106
  for (const node of imports) {
@@ -107,21 +109,21 @@ function groupImports(imports, config, options = {}) {
107
109
  buckets.get(groupName).push(node);
108
110
  }
109
111
  const result = [];
110
- if (buckets.has("SideEffects")) {
111
- result.push({ name: "SideEffects", imports: buckets.get("SideEffects") });
112
+ if (buckets.has(SIDE_EFFECTS_GROUP)) {
113
+ result.push({ name: SIDE_EFFECTS_GROUP, imports: buckets.get(SIDE_EFFECTS_GROUP) });
112
114
  }
113
115
  for (const group of config.groups) {
114
116
  if (buckets.has(group.name)) {
115
117
  result.push({ name: group.name, imports: buckets.get(group.name) });
116
118
  }
117
119
  }
118
- if (buckets.has("Others")) {
119
- result.push({ name: "Others", imports: buckets.get("Others") });
120
+ if (buckets.has(OTHERS_GROUP)) {
121
+ result.push({ name: OTHERS_GROUP, imports: buckets.get(OTHERS_GROUP) });
120
122
  }
121
123
  return result;
122
124
  }
123
125
  function assignGroup(node, config, options) {
124
- if (node.isSideEffect) return "SideEffects";
126
+ if (node.isSideEffect) return SIDE_EFFECTS_GROUP;
125
127
  const canonicalPath = resolveCanonicalPath(node.specifier, config, options);
126
128
  for (const group of config.groups) {
127
129
  for (const pattern of group.match) {
@@ -129,7 +131,7 @@ function assignGroup(node, config, options) {
129
131
  if (canonicalPath && matches(canonicalPath, pattern)) return group.name;
130
132
  }
131
133
  }
132
- return "Others";
134
+ return OTHERS_GROUP;
133
135
  }
134
136
  function resolveCanonicalPath(specifier, config, options) {
135
137
  if (specifier.startsWith(".") || specifier.startsWith("/")) {
@@ -332,7 +334,9 @@ function formatImports(source, config, options = {}) {
332
334
  const deduped = deduplicateImports(clean);
333
335
  const sorted = deduped.map(sortNamedImports);
334
336
  const grouped = groupImports(sorted, config, options);
335
- const sortedGroups = grouped.map((g) => ({ ...g, imports: sortGroup(g.imports) }));
337
+ const sortedGroups = grouped.map(
338
+ (g) => g.name === SIDE_EFFECTS_GROUP ? g : { ...g, imports: sortGroup(g.imports) }
339
+ );
336
340
  const q = detectQuoteChar(source);
337
341
  const newImportBlock = printGroups(sortedGroups, config, q);
338
342
  const firstImport = imports[0];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apotheke",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Prettier for imports — deterministic import organization as a Prettier plugin or CLI",
5
5
  "type": "module",
6
6
  "license": "MIT",