@link-assistant/agent 0.0.9 → 0.0.12
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/EXAMPLES.md +36 -0
- package/MODELS.md +72 -24
- package/README.md +59 -2
- package/TOOLS.md +20 -0
- package/package.json +35 -2
- package/src/agent/agent.ts +68 -54
- package/src/auth/claude-oauth.ts +426 -0
- package/src/auth/index.ts +28 -26
- package/src/auth/plugins.ts +876 -0
- package/src/bun/index.ts +53 -43
- package/src/bus/global.ts +5 -5
- package/src/bus/index.ts +59 -53
- package/src/cli/bootstrap.js +12 -12
- package/src/cli/bootstrap.ts +6 -6
- package/src/cli/cmd/agent.ts +97 -92
- package/src/cli/cmd/auth.ts +469 -0
- package/src/cli/cmd/cmd.ts +2 -2
- package/src/cli/cmd/export.ts +41 -41
- package/src/cli/cmd/mcp.ts +144 -119
- package/src/cli/cmd/models.ts +30 -29
- package/src/cli/cmd/run.ts +269 -213
- package/src/cli/cmd/stats.ts +185 -146
- package/src/cli/error.ts +17 -13
- package/src/cli/ui.ts +39 -24
- package/src/command/index.ts +26 -26
- package/src/config/config.ts +528 -288
- package/src/config/markdown.ts +15 -15
- package/src/file/ripgrep.ts +201 -169
- package/src/file/time.ts +21 -18
- package/src/file/watcher.ts +51 -42
- package/src/file.ts +1 -1
- package/src/flag/flag.ts +26 -11
- package/src/format/formatter.ts +206 -162
- package/src/format/index.ts +61 -61
- package/src/global/index.ts +21 -21
- package/src/id/id.ts +47 -33
- package/src/index.js +346 -199
- package/src/json-standard/index.ts +67 -51
- package/src/mcp/index.ts +135 -128
- package/src/patch/index.ts +336 -267
- package/src/project/bootstrap.ts +15 -15
- package/src/project/instance.ts +43 -36
- package/src/project/project.ts +47 -47
- package/src/project/state.ts +37 -33
- package/src/provider/models-macro.ts +5 -5
- package/src/provider/models.ts +32 -32
- package/src/provider/opencode.js +19 -19
- package/src/provider/provider.ts +518 -277
- package/src/provider/transform.ts +143 -102
- package/src/server/project.ts +21 -21
- package/src/server/server.ts +111 -105
- package/src/session/agent.js +66 -60
- package/src/session/compaction.ts +136 -111
- package/src/session/index.ts +189 -156
- package/src/session/message-v2.ts +312 -268
- package/src/session/message.ts +73 -57
- package/src/session/processor.ts +180 -166
- package/src/session/prompt.ts +678 -533
- package/src/session/retry.ts +26 -23
- package/src/session/revert.ts +76 -62
- package/src/session/status.ts +26 -26
- package/src/session/summary.ts +97 -76
- package/src/session/system.ts +77 -63
- package/src/session/todo.ts +22 -16
- package/src/snapshot/index.ts +92 -76
- package/src/storage/storage.ts +157 -120
- package/src/tool/bash.ts +116 -106
- package/src/tool/batch.ts +73 -59
- package/src/tool/codesearch.ts +60 -53
- package/src/tool/edit.ts +319 -263
- package/src/tool/glob.ts +32 -28
- package/src/tool/grep.ts +72 -53
- package/src/tool/invalid.ts +7 -7
- package/src/tool/ls.ts +77 -64
- package/src/tool/multiedit.ts +30 -21
- package/src/tool/patch.ts +121 -94
- package/src/tool/read.ts +140 -122
- package/src/tool/registry.ts +38 -38
- package/src/tool/task.ts +93 -60
- package/src/tool/todo.ts +16 -16
- package/src/tool/tool.ts +45 -36
- package/src/tool/webfetch.ts +97 -74
- package/src/tool/websearch.ts +78 -64
- package/src/tool/write.ts +21 -15
- package/src/util/binary.ts +27 -19
- package/src/util/context.ts +8 -8
- package/src/util/defer.ts +7 -5
- package/src/util/error.ts +24 -19
- package/src/util/eventloop.ts +16 -10
- package/src/util/filesystem.ts +37 -33
- package/src/util/fn.ts +11 -8
- package/src/util/iife.ts +1 -1
- package/src/util/keybind.ts +44 -44
- package/src/util/lazy.ts +7 -7
- package/src/util/locale.ts +20 -16
- package/src/util/lock.ts +43 -38
- package/src/util/log.ts +95 -85
- package/src/util/queue.ts +8 -8
- package/src/util/rpc.ts +35 -23
- package/src/util/scrap.ts +4 -4
- package/src/util/signal.ts +5 -5
- package/src/util/timeout.ts +6 -6
- package/src/util/token.ts +2 -2
- package/src/util/wildcard.ts +38 -27
package/src/format/formatter.ts
CHANGED
|
@@ -1,248 +1,292 @@
|
|
|
1
|
-
import { readableStreamToText } from
|
|
2
|
-
import { BunProc } from
|
|
3
|
-
import { Instance } from
|
|
4
|
-
import { Filesystem } from
|
|
1
|
+
import { readableStreamToText } from 'bun';
|
|
2
|
+
import { BunProc } from '../bun';
|
|
3
|
+
import { Instance } from '../project/instance';
|
|
4
|
+
import { Filesystem } from '../util/filesystem';
|
|
5
5
|
|
|
6
6
|
export interface Info {
|
|
7
|
-
name: string
|
|
8
|
-
command: string[]
|
|
9
|
-
environment?: Record<string, string
|
|
10
|
-
extensions: string[]
|
|
11
|
-
enabled(): Promise<boolean
|
|
7
|
+
name: string;
|
|
8
|
+
command: string[];
|
|
9
|
+
environment?: Record<string, string>;
|
|
10
|
+
extensions: string[];
|
|
11
|
+
enabled(): Promise<boolean>;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export const gofmt: Info = {
|
|
15
|
-
name:
|
|
16
|
-
command: [
|
|
17
|
-
extensions: [
|
|
15
|
+
name: 'gofmt',
|
|
16
|
+
command: ['gofmt', '-w', '$FILE'],
|
|
17
|
+
extensions: ['.go'],
|
|
18
18
|
async enabled() {
|
|
19
|
-
return Bun.which(
|
|
19
|
+
return Bun.which('gofmt') !== null;
|
|
20
20
|
},
|
|
21
|
-
}
|
|
21
|
+
};
|
|
22
22
|
|
|
23
23
|
export const mix: Info = {
|
|
24
|
-
name:
|
|
25
|
-
command: [
|
|
26
|
-
extensions: [
|
|
24
|
+
name: 'mix',
|
|
25
|
+
command: ['mix', 'format', '$FILE'],
|
|
26
|
+
extensions: ['.ex', '.exs', '.eex', '.heex', '.leex', '.neex', '.sface'],
|
|
27
27
|
async enabled() {
|
|
28
|
-
return Bun.which(
|
|
28
|
+
return Bun.which('mix') !== null;
|
|
29
29
|
},
|
|
30
|
-
}
|
|
30
|
+
};
|
|
31
31
|
|
|
32
32
|
export const prettier: Info = {
|
|
33
|
-
name:
|
|
34
|
-
command: [BunProc.which(),
|
|
33
|
+
name: 'prettier',
|
|
34
|
+
command: [BunProc.which(), 'x', 'prettier', '--write', '$FILE'],
|
|
35
35
|
environment: {
|
|
36
|
-
BUN_BE_BUN:
|
|
36
|
+
BUN_BE_BUN: '1',
|
|
37
37
|
},
|
|
38
38
|
extensions: [
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
39
|
+
'.js',
|
|
40
|
+
'.jsx',
|
|
41
|
+
'.mjs',
|
|
42
|
+
'.cjs',
|
|
43
|
+
'.ts',
|
|
44
|
+
'.tsx',
|
|
45
|
+
'.mts',
|
|
46
|
+
'.cts',
|
|
47
|
+
'.html',
|
|
48
|
+
'.htm',
|
|
49
|
+
'.css',
|
|
50
|
+
'.scss',
|
|
51
|
+
'.sass',
|
|
52
|
+
'.less',
|
|
53
|
+
'.vue',
|
|
54
|
+
'.svelte',
|
|
55
|
+
'.json',
|
|
56
|
+
'.jsonc',
|
|
57
|
+
'.yaml',
|
|
58
|
+
'.yml',
|
|
59
|
+
'.toml',
|
|
60
|
+
'.xml',
|
|
61
|
+
'.md',
|
|
62
|
+
'.mdx',
|
|
63
|
+
'.graphql',
|
|
64
|
+
'.gql',
|
|
65
65
|
],
|
|
66
66
|
async enabled() {
|
|
67
|
-
const items = await Filesystem.findUp(
|
|
67
|
+
const items = await Filesystem.findUp(
|
|
68
|
+
'package.json',
|
|
69
|
+
Instance.directory,
|
|
70
|
+
Instance.worktree
|
|
71
|
+
);
|
|
68
72
|
for (const item of items) {
|
|
69
|
-
const json = await Bun.file(item).json()
|
|
70
|
-
if (json.dependencies?.prettier) return true
|
|
71
|
-
if (json.devDependencies?.prettier) return true
|
|
73
|
+
const json = await Bun.file(item).json();
|
|
74
|
+
if (json.dependencies?.prettier) return true;
|
|
75
|
+
if (json.devDependencies?.prettier) return true;
|
|
72
76
|
}
|
|
73
|
-
return false
|
|
77
|
+
return false;
|
|
74
78
|
},
|
|
75
|
-
}
|
|
79
|
+
};
|
|
76
80
|
|
|
77
81
|
export const biome: Info = {
|
|
78
|
-
name:
|
|
79
|
-
command: [
|
|
82
|
+
name: 'biome',
|
|
83
|
+
command: [
|
|
84
|
+
BunProc.which(),
|
|
85
|
+
'x',
|
|
86
|
+
'@biomejs/biome',
|
|
87
|
+
'format',
|
|
88
|
+
'--write',
|
|
89
|
+
'$FILE',
|
|
90
|
+
],
|
|
80
91
|
environment: {
|
|
81
|
-
BUN_BE_BUN:
|
|
92
|
+
BUN_BE_BUN: '1',
|
|
82
93
|
},
|
|
83
94
|
extensions: [
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
95
|
+
'.js',
|
|
96
|
+
'.jsx',
|
|
97
|
+
'.mjs',
|
|
98
|
+
'.cjs',
|
|
99
|
+
'.ts',
|
|
100
|
+
'.tsx',
|
|
101
|
+
'.mts',
|
|
102
|
+
'.cts',
|
|
103
|
+
'.html',
|
|
104
|
+
'.htm',
|
|
105
|
+
'.css',
|
|
106
|
+
'.scss',
|
|
107
|
+
'.sass',
|
|
108
|
+
'.less',
|
|
109
|
+
'.vue',
|
|
110
|
+
'.svelte',
|
|
111
|
+
'.json',
|
|
112
|
+
'.jsonc',
|
|
113
|
+
'.yaml',
|
|
114
|
+
'.yml',
|
|
115
|
+
'.toml',
|
|
116
|
+
'.xml',
|
|
117
|
+
'.md',
|
|
118
|
+
'.mdx',
|
|
119
|
+
'.graphql',
|
|
120
|
+
'.gql',
|
|
110
121
|
],
|
|
111
122
|
async enabled() {
|
|
112
|
-
const configs = [
|
|
123
|
+
const configs = ['biome.json', 'biome.jsonc'];
|
|
113
124
|
for (const config of configs) {
|
|
114
|
-
const found = await Filesystem.findUp(
|
|
125
|
+
const found = await Filesystem.findUp(
|
|
126
|
+
config,
|
|
127
|
+
Instance.directory,
|
|
128
|
+
Instance.worktree
|
|
129
|
+
);
|
|
115
130
|
if (found.length > 0) {
|
|
116
|
-
return true
|
|
131
|
+
return true;
|
|
117
132
|
}
|
|
118
133
|
}
|
|
119
|
-
return false
|
|
134
|
+
return false;
|
|
120
135
|
},
|
|
121
|
-
}
|
|
136
|
+
};
|
|
122
137
|
|
|
123
138
|
export const zig: Info = {
|
|
124
|
-
name:
|
|
125
|
-
command: [
|
|
126
|
-
extensions: [
|
|
139
|
+
name: 'zig',
|
|
140
|
+
command: ['zig', 'fmt', '$FILE'],
|
|
141
|
+
extensions: ['.zig', '.zon'],
|
|
127
142
|
async enabled() {
|
|
128
|
-
return Bun.which(
|
|
143
|
+
return Bun.which('zig') !== null;
|
|
129
144
|
},
|
|
130
|
-
}
|
|
145
|
+
};
|
|
131
146
|
|
|
132
147
|
export const clang: Info = {
|
|
133
|
-
name:
|
|
134
|
-
command: [
|
|
135
|
-
extensions: [
|
|
148
|
+
name: 'clang-format',
|
|
149
|
+
command: ['clang-format', '-i', '$FILE'],
|
|
150
|
+
extensions: [
|
|
151
|
+
'.c',
|
|
152
|
+
'.cc',
|
|
153
|
+
'.cpp',
|
|
154
|
+
'.cxx',
|
|
155
|
+
'.c++',
|
|
156
|
+
'.h',
|
|
157
|
+
'.hh',
|
|
158
|
+
'.hpp',
|
|
159
|
+
'.hxx',
|
|
160
|
+
'.h++',
|
|
161
|
+
'.ino',
|
|
162
|
+
'.C',
|
|
163
|
+
'.H',
|
|
164
|
+
],
|
|
136
165
|
async enabled() {
|
|
137
|
-
const items = await Filesystem.findUp(
|
|
138
|
-
|
|
166
|
+
const items = await Filesystem.findUp(
|
|
167
|
+
'.clang-format',
|
|
168
|
+
Instance.directory,
|
|
169
|
+
Instance.worktree
|
|
170
|
+
);
|
|
171
|
+
return items.length > 0;
|
|
139
172
|
},
|
|
140
|
-
}
|
|
173
|
+
};
|
|
141
174
|
|
|
142
175
|
export const ktlint: Info = {
|
|
143
|
-
name:
|
|
144
|
-
command: [
|
|
145
|
-
extensions: [
|
|
176
|
+
name: 'ktlint',
|
|
177
|
+
command: ['ktlint', '-F', '$FILE'],
|
|
178
|
+
extensions: ['.kt', '.kts'],
|
|
146
179
|
async enabled() {
|
|
147
|
-
return Bun.which(
|
|
180
|
+
return Bun.which('ktlint') !== null;
|
|
148
181
|
},
|
|
149
|
-
}
|
|
182
|
+
};
|
|
150
183
|
|
|
151
184
|
export const ruff: Info = {
|
|
152
|
-
name:
|
|
153
|
-
command: [
|
|
154
|
-
extensions: [
|
|
185
|
+
name: 'ruff',
|
|
186
|
+
command: ['ruff', 'format', '$FILE'],
|
|
187
|
+
extensions: ['.py', '.pyi'],
|
|
155
188
|
async enabled() {
|
|
156
|
-
if (!Bun.which(
|
|
157
|
-
const configs = [
|
|
189
|
+
if (!Bun.which('ruff')) return false;
|
|
190
|
+
const configs = ['pyproject.toml', 'ruff.toml', '.ruff.toml'];
|
|
158
191
|
for (const config of configs) {
|
|
159
|
-
const found = await Filesystem.findUp(
|
|
192
|
+
const found = await Filesystem.findUp(
|
|
193
|
+
config,
|
|
194
|
+
Instance.directory,
|
|
195
|
+
Instance.worktree
|
|
196
|
+
);
|
|
160
197
|
if (found.length > 0) {
|
|
161
|
-
if (config ===
|
|
162
|
-
const content = await Bun.file(found[0]).text()
|
|
163
|
-
if (content.includes(
|
|
198
|
+
if (config === 'pyproject.toml') {
|
|
199
|
+
const content = await Bun.file(found[0]).text();
|
|
200
|
+
if (content.includes('[tool.ruff]')) return true;
|
|
164
201
|
} else {
|
|
165
|
-
return true
|
|
202
|
+
return true;
|
|
166
203
|
}
|
|
167
204
|
}
|
|
168
205
|
}
|
|
169
|
-
const deps = [
|
|
206
|
+
const deps = ['requirements.txt', 'pyproject.toml', 'Pipfile'];
|
|
170
207
|
for (const dep of deps) {
|
|
171
|
-
const found = await Filesystem.findUp(
|
|
208
|
+
const found = await Filesystem.findUp(
|
|
209
|
+
dep,
|
|
210
|
+
Instance.directory,
|
|
211
|
+
Instance.worktree
|
|
212
|
+
);
|
|
172
213
|
if (found.length > 0) {
|
|
173
|
-
const content = await Bun.file(found[0]).text()
|
|
174
|
-
if (content.includes(
|
|
214
|
+
const content = await Bun.file(found[0]).text();
|
|
215
|
+
if (content.includes('ruff')) return true;
|
|
175
216
|
}
|
|
176
217
|
}
|
|
177
|
-
return false
|
|
218
|
+
return false;
|
|
178
219
|
},
|
|
179
|
-
}
|
|
220
|
+
};
|
|
180
221
|
|
|
181
222
|
export const rlang: Info = {
|
|
182
|
-
name:
|
|
183
|
-
command: [
|
|
184
|
-
extensions: [
|
|
223
|
+
name: 'air',
|
|
224
|
+
command: ['air', 'format', '$FILE'],
|
|
225
|
+
extensions: ['.R'],
|
|
185
226
|
async enabled() {
|
|
186
|
-
const airPath = Bun.which(
|
|
187
|
-
if (airPath == null) return false
|
|
227
|
+
const airPath = Bun.which('air');
|
|
228
|
+
if (airPath == null) return false;
|
|
188
229
|
|
|
189
230
|
try {
|
|
190
|
-
const proc = Bun.spawn([
|
|
191
|
-
stdout:
|
|
192
|
-
stderr:
|
|
193
|
-
})
|
|
194
|
-
await proc.exited
|
|
195
|
-
const output = await readableStreamToText(proc.stdout)
|
|
231
|
+
const proc = Bun.spawn(['air', '--help'], {
|
|
232
|
+
stdout: 'pipe',
|
|
233
|
+
stderr: 'pipe',
|
|
234
|
+
});
|
|
235
|
+
await proc.exited;
|
|
236
|
+
const output = await readableStreamToText(proc.stdout);
|
|
196
237
|
|
|
197
238
|
// Check for "Air: An R language server and formatter"
|
|
198
|
-
const firstLine = output.split(
|
|
199
|
-
const hasR = firstLine.includes(
|
|
200
|
-
const hasFormatter = firstLine.includes(
|
|
201
|
-
return hasR && hasFormatter
|
|
239
|
+
const firstLine = output.split('\n')[0];
|
|
240
|
+
const hasR = firstLine.includes('R language');
|
|
241
|
+
const hasFormatter = firstLine.includes('formatter');
|
|
242
|
+
return hasR && hasFormatter;
|
|
202
243
|
} catch (error) {
|
|
203
|
-
return false
|
|
244
|
+
return false;
|
|
204
245
|
}
|
|
205
246
|
},
|
|
206
|
-
}
|
|
247
|
+
};
|
|
207
248
|
|
|
208
249
|
export const uvformat: Info = {
|
|
209
|
-
name:
|
|
210
|
-
command: [
|
|
211
|
-
extensions: [
|
|
250
|
+
name: 'uv format',
|
|
251
|
+
command: ['uv', 'format', '--', '$FILE'],
|
|
252
|
+
extensions: ['.py', '.pyi'],
|
|
212
253
|
async enabled() {
|
|
213
|
-
if (await ruff.enabled()) return false
|
|
214
|
-
if (Bun.which(
|
|
215
|
-
const proc = Bun.spawn([
|
|
216
|
-
|
|
217
|
-
|
|
254
|
+
if (await ruff.enabled()) return false;
|
|
255
|
+
if (Bun.which('uv') !== null) {
|
|
256
|
+
const proc = Bun.spawn(['uv', 'format', '--help'], {
|
|
257
|
+
stderr: 'pipe',
|
|
258
|
+
stdout: 'pipe',
|
|
259
|
+
});
|
|
260
|
+
const code = await proc.exited;
|
|
261
|
+
return code === 0;
|
|
218
262
|
}
|
|
219
|
-
return false
|
|
263
|
+
return false;
|
|
220
264
|
},
|
|
221
|
-
}
|
|
265
|
+
};
|
|
222
266
|
|
|
223
267
|
export const rubocop: Info = {
|
|
224
|
-
name:
|
|
225
|
-
command: [
|
|
226
|
-
extensions: [
|
|
268
|
+
name: 'rubocop',
|
|
269
|
+
command: ['rubocop', '--autocorrect', '$FILE'],
|
|
270
|
+
extensions: ['.rb', '.rake', '.gemspec', '.ru'],
|
|
227
271
|
async enabled() {
|
|
228
|
-
return Bun.which(
|
|
272
|
+
return Bun.which('rubocop') !== null;
|
|
229
273
|
},
|
|
230
|
-
}
|
|
274
|
+
};
|
|
231
275
|
|
|
232
276
|
export const standardrb: Info = {
|
|
233
|
-
name:
|
|
234
|
-
command: [
|
|
235
|
-
extensions: [
|
|
277
|
+
name: 'standardrb',
|
|
278
|
+
command: ['standardrb', '--fix', '$FILE'],
|
|
279
|
+
extensions: ['.rb', '.rake', '.gemspec', '.ru'],
|
|
236
280
|
async enabled() {
|
|
237
|
-
return Bun.which(
|
|
281
|
+
return Bun.which('standardrb') !== null;
|
|
238
282
|
},
|
|
239
|
-
}
|
|
283
|
+
};
|
|
240
284
|
|
|
241
285
|
export const htmlbeautifier: Info = {
|
|
242
|
-
name:
|
|
243
|
-
command: [
|
|
244
|
-
extensions: [
|
|
286
|
+
name: 'htmlbeautifier',
|
|
287
|
+
command: ['htmlbeautifier', '$FILE'],
|
|
288
|
+
extensions: ['.erb', '.html.erb'],
|
|
245
289
|
async enabled() {
|
|
246
|
-
return Bun.which(
|
|
290
|
+
return Bun.which('htmlbeautifier') !== null;
|
|
247
291
|
},
|
|
248
|
-
}
|
|
292
|
+
};
|
package/src/format/index.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { Bus } from
|
|
2
|
-
import { File } from
|
|
3
|
-
import { Log } from
|
|
4
|
-
import path from
|
|
5
|
-
import z from
|
|
1
|
+
import { Bus } from '../bus';
|
|
2
|
+
import { File } from '../file';
|
|
3
|
+
import { Log } from '../util/log';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import z from 'zod';
|
|
6
6
|
|
|
7
|
-
import * as Formatter from
|
|
8
|
-
import { Config } from
|
|
9
|
-
import { mergeDeep } from
|
|
10
|
-
import { Instance } from
|
|
7
|
+
import * as Formatter from './formatter';
|
|
8
|
+
import { Config } from '../config/config';
|
|
9
|
+
import { mergeDeep } from 'remeda';
|
|
10
|
+
import { Instance } from '../project/instance';
|
|
11
11
|
|
|
12
12
|
export namespace Format {
|
|
13
|
-
const log = Log.create({ service:
|
|
13
|
+
const log = Log.create({ service: 'format' });
|
|
14
14
|
|
|
15
15
|
export const Status = z
|
|
16
16
|
.object({
|
|
@@ -19,119 +19,119 @@ export namespace Format {
|
|
|
19
19
|
enabled: z.boolean(),
|
|
20
20
|
})
|
|
21
21
|
.meta({
|
|
22
|
-
ref:
|
|
23
|
-
})
|
|
24
|
-
export type Status = z.infer<typeof Status
|
|
22
|
+
ref: 'FormatterStatus',
|
|
23
|
+
});
|
|
24
|
+
export type Status = z.infer<typeof Status>;
|
|
25
25
|
|
|
26
26
|
const state = Instance.state(async () => {
|
|
27
|
-
const enabled: Record<string, boolean> = {}
|
|
28
|
-
const cfg = await Config.get()
|
|
27
|
+
const enabled: Record<string, boolean> = {};
|
|
28
|
+
const cfg = await Config.get();
|
|
29
29
|
|
|
30
|
-
const formatters: Record<string, Formatter.Info> = {}
|
|
30
|
+
const formatters: Record<string, Formatter.Info> = {};
|
|
31
31
|
if (cfg.formatter === false) {
|
|
32
|
-
log.info(
|
|
32
|
+
log.info('all formatters are disabled');
|
|
33
33
|
return {
|
|
34
34
|
enabled,
|
|
35
35
|
formatters,
|
|
36
|
-
}
|
|
36
|
+
};
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
for (const item of Object.values(Formatter)) {
|
|
40
|
-
formatters[item.name] = item
|
|
40
|
+
formatters[item.name] = item;
|
|
41
41
|
}
|
|
42
42
|
for (const [name, item] of Object.entries(cfg.formatter ?? {})) {
|
|
43
43
|
if (item.disabled) {
|
|
44
|
-
delete formatters[name]
|
|
45
|
-
continue
|
|
44
|
+
delete formatters[name];
|
|
45
|
+
continue;
|
|
46
46
|
}
|
|
47
47
|
const result: Formatter.Info = mergeDeep(formatters[name] ?? {}, {
|
|
48
48
|
command: [],
|
|
49
49
|
extensions: [],
|
|
50
50
|
...item,
|
|
51
|
-
})
|
|
51
|
+
});
|
|
52
52
|
|
|
53
|
-
if (result.command.length === 0) continue
|
|
53
|
+
if (result.command.length === 0) continue;
|
|
54
54
|
|
|
55
|
-
result.enabled = async () => true
|
|
56
|
-
result.name = name
|
|
57
|
-
formatters[name] = result
|
|
55
|
+
result.enabled = async () => true;
|
|
56
|
+
result.name = name;
|
|
57
|
+
formatters[name] = result;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
return {
|
|
61
61
|
enabled,
|
|
62
62
|
formatters,
|
|
63
|
-
}
|
|
64
|
-
})
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
65
|
|
|
66
66
|
async function isEnabled(item: Formatter.Info) {
|
|
67
|
-
const s = await state()
|
|
68
|
-
let status = s.enabled[item.name]
|
|
67
|
+
const s = await state();
|
|
68
|
+
let status = s.enabled[item.name];
|
|
69
69
|
if (status === undefined) {
|
|
70
|
-
status = await item.enabled()
|
|
71
|
-
s.enabled[item.name] = status
|
|
70
|
+
status = await item.enabled();
|
|
71
|
+
s.enabled[item.name] = status;
|
|
72
72
|
}
|
|
73
|
-
return status
|
|
73
|
+
return status;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
async function getFormatter(ext: string) {
|
|
77
|
-
const formatters = await state().then((x) => x.formatters)
|
|
78
|
-
const result = []
|
|
77
|
+
const formatters = await state().then((x) => x.formatters);
|
|
78
|
+
const result = [];
|
|
79
79
|
for (const item of Object.values(formatters)) {
|
|
80
|
-
log.info(
|
|
81
|
-
if (!item.extensions.includes(ext)) continue
|
|
82
|
-
if (!(await isEnabled(item))) continue
|
|
83
|
-
log.info(
|
|
84
|
-
result.push(item)
|
|
80
|
+
log.info('checking', { name: item.name, ext });
|
|
81
|
+
if (!item.extensions.includes(ext)) continue;
|
|
82
|
+
if (!(await isEnabled(item))) continue;
|
|
83
|
+
log.info('enabled', { name: item.name, ext });
|
|
84
|
+
result.push(item);
|
|
85
85
|
}
|
|
86
|
-
return result
|
|
86
|
+
return result;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
export async function status() {
|
|
90
|
-
const s = await state()
|
|
91
|
-
const result: Status[] = []
|
|
90
|
+
const s = await state();
|
|
91
|
+
const result: Status[] = [];
|
|
92
92
|
for (const formatter of Object.values(s.formatters)) {
|
|
93
|
-
const enabled = await isEnabled(formatter)
|
|
93
|
+
const enabled = await isEnabled(formatter);
|
|
94
94
|
result.push({
|
|
95
95
|
name: formatter.name,
|
|
96
96
|
extensions: formatter.extensions,
|
|
97
97
|
enabled,
|
|
98
|
-
})
|
|
98
|
+
});
|
|
99
99
|
}
|
|
100
|
-
return result
|
|
100
|
+
return result;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
export function init() {
|
|
104
|
-
log.info(
|
|
104
|
+
log.info('init');
|
|
105
105
|
Bus.subscribe(File.Event.Edited, async (payload) => {
|
|
106
|
-
const file = payload.properties.file
|
|
107
|
-
log.info(
|
|
108
|
-
const ext = path.extname(file)
|
|
106
|
+
const file = payload.properties.file;
|
|
107
|
+
log.info('formatting', { file });
|
|
108
|
+
const ext = path.extname(file);
|
|
109
109
|
|
|
110
110
|
for (const item of await getFormatter(ext)) {
|
|
111
|
-
log.info(
|
|
111
|
+
log.info('running', { command: item.command });
|
|
112
112
|
try {
|
|
113
113
|
const proc = Bun.spawn({
|
|
114
|
-
cmd: item.command.map((x) => x.replace(
|
|
114
|
+
cmd: item.command.map((x) => x.replace('$FILE', file)),
|
|
115
115
|
cwd: Instance.directory,
|
|
116
116
|
env: { ...process.env, ...item.environment },
|
|
117
|
-
stdout:
|
|
118
|
-
stderr:
|
|
119
|
-
})
|
|
120
|
-
const exit = await proc.exited
|
|
117
|
+
stdout: 'ignore',
|
|
118
|
+
stderr: 'ignore',
|
|
119
|
+
});
|
|
120
|
+
const exit = await proc.exited;
|
|
121
121
|
if (exit !== 0)
|
|
122
|
-
log.error(
|
|
122
|
+
log.error('failed', {
|
|
123
123
|
command: item.command,
|
|
124
124
|
...item.environment,
|
|
125
|
-
})
|
|
125
|
+
});
|
|
126
126
|
} catch (error) {
|
|
127
|
-
log.error(
|
|
127
|
+
log.error('failed to format file', {
|
|
128
128
|
error,
|
|
129
129
|
command: item.command,
|
|
130
130
|
...item.environment,
|
|
131
131
|
file,
|
|
132
|
-
})
|
|
132
|
+
});
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
|
-
})
|
|
135
|
+
});
|
|
136
136
|
}
|
|
137
137
|
}
|