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 +29 -65
- package/dist/cli.js +11 -7
- package/dist/index.js +11 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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="apotheke — deterministic 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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
##
|
|
48
|
+
## Getting started
|
|
36
49
|
|
|
37
50
|
```sh
|
|
38
51
|
pnpm add -D apotheke
|
|
39
52
|
```
|
|
40
53
|
|
|
41
|
-
|
|
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
|
-
|
|
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(
|
|
114
|
-
result.push({ name:
|
|
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(
|
|
122
|
-
result.push({ name:
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
111
|
-
result.push({ name:
|
|
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(
|
|
119
|
-
result.push({ name:
|
|
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
|
|
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
|
|
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(
|
|
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];
|