@alint-js/config 0.0.4

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.
@@ -0,0 +1,26 @@
1
+ import { AlintConfig, AlintConfig as AlintConfig$1, ModelSize, ProviderDefinition, ProviderType, RunnerConfig, SetupConfig, SetupConfig as SetupConfig$1, SetupModelDefinition } from "@alint-js/core";
2
+
3
+ //#region src/load-config.d.ts
4
+ declare function loadAlintConfig(cwd: string, configFile?: string): Promise<AlintConfig$1>;
5
+ //#endregion
6
+ //#region src/paths.d.ts
7
+ interface GlobalSetupConfigPathOptions {
8
+ isMacOS?: boolean;
9
+ xdgConfig?: string;
10
+ }
11
+ declare function getGlobalSetupConfigPath(env?: NodeJS.ProcessEnv, options?: GlobalSetupConfigPathOptions): string;
12
+ declare function getProjectSetupConfigPath(cwd: string): string;
13
+ //#endregion
14
+ //#region src/setup-load.d.ts
15
+ declare const emptySetupConfig: SetupConfig$1;
16
+ declare function loadSetupConfig(filePath: string): Promise<SetupConfig$1>;
17
+ declare function mergeSetupConfigs(...configs: SetupConfig$1[]): SetupConfig$1;
18
+ //#endregion
19
+ //#region src/setup-toml.d.ts
20
+ declare function parseSetupConfigToml(toml: string): SetupConfig$1;
21
+ declare function stringifySetupConfigToml(config: SetupConfig$1): string;
22
+ //#endregion
23
+ //#region src/setup-write.d.ts
24
+ declare function writeSetupConfig(filePath: string, config: SetupConfig$1): Promise<void>;
25
+ //#endregion
26
+ export { type AlintConfig, type GlobalSetupConfigPathOptions, type ModelSize, type ProviderDefinition, type ProviderType, type RunnerConfig, type SetupConfig, type SetupModelDefinition, emptySetupConfig, getGlobalSetupConfigPath, getProjectSetupConfigPath, loadAlintConfig, loadSetupConfig, mergeSetupConfigs, parseSetupConfigToml, stringifySetupConfigToml, writeSetupConfig };
package/dist/index.mjs ADDED
@@ -0,0 +1,282 @@
1
+ import { loadConfig } from "c12";
2
+ import process from "node:process";
3
+ import { homedir } from "node:os";
4
+ import { dirname, join } from "pathe";
5
+ import { xdgConfig } from "xdg-basedir";
6
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
7
+ import { parse, stringify } from "smol-toml";
8
+ //#region src/load-config.ts
9
+ async function loadAlintConfig(cwd, configFile) {
10
+ return (await loadConfig({
11
+ configFile,
12
+ cwd,
13
+ defaults: {
14
+ plugins: [],
15
+ rules: {}
16
+ },
17
+ dotenv: true,
18
+ name: "alint"
19
+ })).config;
20
+ }
21
+ //#endregion
22
+ //#region src/paths.ts
23
+ function getGlobalSetupConfigPath(env = process.env, options = {}) {
24
+ return join(env.XDG_CONFIG_HOME ?? resolveDefaultConfigHome(options), "alint", "config.toml");
25
+ }
26
+ function getProjectSetupConfigPath(cwd) {
27
+ return join(cwd, ".alint", "config.toml");
28
+ }
29
+ function resolveDefaultConfigHome(options) {
30
+ if (options.isMacOS ?? process.platform === "darwin") return join(homedir(), ".config");
31
+ return options.xdgConfig ?? xdgConfig ?? join(homedir(), ".config");
32
+ }
33
+ //#endregion
34
+ //#region src/setup-toml.ts
35
+ const modelSizes = /* @__PURE__ */ new Set([
36
+ "large",
37
+ "medium",
38
+ "small"
39
+ ]);
40
+ function parseSetupConfigToml(toml) {
41
+ const rawConfig = parse(toml);
42
+ if (rawConfig.version !== 1) throw new Error("Invalid setup config: version must be 1.");
43
+ if (!Array.isArray(rawConfig.providers)) throw new TypeError("Invalid setup config: providers must be an array.");
44
+ const parsedConfig = {
45
+ providers: rawConfig.providers.map((provider) => parseProvider(provider)),
46
+ version: 1
47
+ };
48
+ if (rawConfig.runner !== void 0) parsedConfig.runner = parseRunner(rawConfig.runner);
49
+ return parsedConfig;
50
+ }
51
+ function stringifySetupConfigToml(config) {
52
+ const stringifiableConfig = {
53
+ providers: config.providers.map(toTomlProvider),
54
+ version: config.version
55
+ };
56
+ if (config.runner !== void 0) stringifiableConfig.runner = toTomlRunner(config.runner);
57
+ return stringify(stringifiableConfig);
58
+ }
59
+ function isModelSize(value) {
60
+ return typeof value === "string" && modelSizes.has(value);
61
+ }
62
+ function isPlainObject(value) {
63
+ return typeof value === "object" && value !== null && !Array.isArray(value);
64
+ }
65
+ function parseModel(providerId, model) {
66
+ const id = readNonEmptyString(model.id, `provider "${providerId}" model id`);
67
+ const parsedModel = { id };
68
+ if (model.name !== void 0) parsedModel.name = readString(model.name, `model "${id}" name`);
69
+ if (model.aliases !== void 0) parsedModel.aliases = readStringArray(model.aliases, `model "${id}" aliases`);
70
+ if (model.capabilities !== void 0) parsedModel.capabilities = readStringArray(model.capabilities, `model "${id}" capabilities`);
71
+ if (model.size !== void 0) {
72
+ if (!isModelSize(model.size)) throw new Error(`Invalid model "${id}": size must be "small", "medium", or "large".`);
73
+ parsedModel.size = model.size;
74
+ }
75
+ if (model.context_window !== void 0) parsedModel.contextWindow = readFiniteNumber(model.context_window, `model "${id}" context_window`);
76
+ if (model.default_params !== void 0) parsedModel.defaultParams = readRecord(model.default_params, `model "${id}" default_params`);
77
+ return parsedModel;
78
+ }
79
+ function parsePositiveInteger(value, label) {
80
+ if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) throw new TypeError(`Invalid ${label}: must be a positive integer.`);
81
+ return value;
82
+ }
83
+ function parseProvider(provider) {
84
+ const id = readNonEmptyString(provider.id, "provider id");
85
+ if (provider.type !== "openai-compatible") throw new Error(`Invalid provider "${id}": type must be "openai-compatible".`);
86
+ const endpoint = readNonEmptyString(provider.endpoint, `provider "${id}" endpoint`);
87
+ if (!Array.isArray(provider.models)) throw new TypeError(`Invalid provider "${id}": models must be an array.`);
88
+ const parsedProvider = {
89
+ endpoint,
90
+ id,
91
+ models: provider.models.map((model) => parseModel(id, model)),
92
+ type: provider.type
93
+ };
94
+ if (provider.headers !== void 0) parsedProvider.headers = readStringMap(provider.headers, `provider "${id}" headers`);
95
+ return parsedProvider;
96
+ }
97
+ function parseRunner(runner) {
98
+ const parsedRunner = {};
99
+ if (runner.cache !== void 0) parsedRunner.cache = parseRunnerCache(runner.cache);
100
+ if (runner.file_concurrency !== void 0) parsedRunner.fileConcurrency = parsePositiveInteger(runner.file_concurrency, "runner file_concurrency");
101
+ if (runner.rule_concurrency !== void 0) parsedRunner.ruleConcurrency = parsePositiveInteger(runner.rule_concurrency, "runner rule_concurrency");
102
+ if (runner.timeout_ms !== void 0) parsedRunner.timeoutMs = parsePositiveInteger(runner.timeout_ms, "runner timeout_ms");
103
+ return parsedRunner;
104
+ }
105
+ function parseRunnerCache(cache) {
106
+ if (typeof cache === "boolean") return cache;
107
+ if (!isPlainObject(cache)) throw new TypeError("Invalid runner cache: must be a boolean or table.");
108
+ const tomlCache = cache;
109
+ const parsedCache = {};
110
+ if (tomlCache.enabled !== void 0) {
111
+ if (typeof tomlCache.enabled !== "boolean") throw new TypeError("Invalid runner cache enabled: must be a boolean.");
112
+ parsedCache.enabled = tomlCache.enabled;
113
+ }
114
+ if (tomlCache.location !== void 0) parsedCache.location = readNonEmptyString(tomlCache.location, "runner cache location");
115
+ return parsedCache;
116
+ }
117
+ function readFiniteNumber(value, label) {
118
+ if (typeof value !== "number" || !Number.isFinite(value)) throw new TypeError(`Invalid ${label}: must be a finite number.`);
119
+ return value;
120
+ }
121
+ function readNonEmptyString(value, label) {
122
+ const stringValue = readString(value, label);
123
+ if (stringValue.length === 0) throw new Error(`Invalid ${label}: must be a non-empty string.`);
124
+ return stringValue;
125
+ }
126
+ function readRecord(value, label) {
127
+ if (!isPlainObject(value)) throw new Error(`Invalid ${label}: must be a table.`);
128
+ return value;
129
+ }
130
+ function readString(value, label) {
131
+ if (typeof value !== "string") throw new TypeError(`Invalid ${label}: must be a string.`);
132
+ return value;
133
+ }
134
+ function readStringArray(value, label) {
135
+ if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) throw new Error(`Invalid ${label}: must be an array of strings.`);
136
+ return value;
137
+ }
138
+ function readStringMap(value, label) {
139
+ const record = readRecord(value, label);
140
+ if (!Object.values(record).every((item) => typeof item === "string")) throw new Error(`Invalid ${label}: must be a string map.`);
141
+ return record;
142
+ }
143
+ function toTomlModel(model) {
144
+ const tomlModel = { id: model.id };
145
+ if (model.name !== void 0) tomlModel.name = model.name;
146
+ if (model.aliases !== void 0) tomlModel.aliases = model.aliases;
147
+ if (model.capabilities !== void 0) tomlModel.capabilities = model.capabilities;
148
+ if (model.size !== void 0) tomlModel.size = model.size;
149
+ if (model.contextWindow !== void 0) tomlModel.context_window = model.contextWindow;
150
+ if (model.defaultParams !== void 0) tomlModel.default_params = model.defaultParams;
151
+ return tomlModel;
152
+ }
153
+ function toTomlProvider(provider) {
154
+ const tomlProvider = {
155
+ endpoint: provider.endpoint,
156
+ id: provider.id,
157
+ models: provider.models.map(toTomlModel),
158
+ type: provider.type
159
+ };
160
+ if (provider.headers !== void 0) tomlProvider.headers = provider.headers;
161
+ return tomlProvider;
162
+ }
163
+ function toTomlRunner(runner) {
164
+ return {
165
+ cache: toTomlRunnerCache(runner.cache),
166
+ file_concurrency: runner.fileConcurrency,
167
+ rule_concurrency: runner.ruleConcurrency,
168
+ timeout_ms: runner.timeoutMs
169
+ };
170
+ }
171
+ function toTomlRunnerCache(cache) {
172
+ return cache;
173
+ }
174
+ //#endregion
175
+ //#region src/setup-load.ts
176
+ const emptySetupConfig = {
177
+ providers: [],
178
+ version: 1
179
+ };
180
+ async function loadSetupConfig(filePath) {
181
+ try {
182
+ return parseSetupConfigToml(await readFile(filePath, "utf8"));
183
+ } catch (error) {
184
+ if (isNodeError(error) && error.code === "ENOENT") return createEmptySetupConfig();
185
+ throw error;
186
+ }
187
+ }
188
+ function mergeSetupConfigs(...configs) {
189
+ let providers = [];
190
+ let runner;
191
+ const providersById = /* @__PURE__ */ new Map();
192
+ for (const config of configs) {
193
+ const configProviders = [];
194
+ if (config.runner !== void 0) runner = {
195
+ ...runner ?? {},
196
+ ...config.runner
197
+ };
198
+ for (const provider of config.providers) {
199
+ const existingProvider = providersById.get(provider.id);
200
+ if (existingProvider === void 0) {
201
+ const mergedProvider = cloneProvider(provider);
202
+ providers.push(mergedProvider);
203
+ providersById.set(provider.id, mergedProvider);
204
+ configProviders.push(mergedProvider);
205
+ continue;
206
+ }
207
+ existingProvider.endpoint = provider.endpoint;
208
+ existingProvider.type = provider.type;
209
+ configProviders.push(existingProvider);
210
+ if (provider.headers !== void 0) existingProvider.headers = {
211
+ ...existingProvider.headers,
212
+ ...provider.headers
213
+ };
214
+ const nextModels = [];
215
+ for (const incomingModel of provider.models) {
216
+ const existingModelIndex = existingProvider.models.findIndex((model) => model.id === incomingModel.id);
217
+ if (existingModelIndex === -1) {
218
+ nextModels.push(cloneModel(incomingModel));
219
+ continue;
220
+ }
221
+ const existingModel = existingProvider.models[existingModelIndex];
222
+ existingProvider.models.splice(existingModelIndex, 1);
223
+ nextModels.push(mergeModel(existingModel, incomingModel));
224
+ }
225
+ existingProvider.models = [...nextModels, ...existingProvider.models];
226
+ }
227
+ providers = prioritizeProviders(providers, configProviders);
228
+ }
229
+ const mergedConfig = {
230
+ providers,
231
+ version: 1
232
+ };
233
+ if (runner !== void 0) mergedConfig.runner = runner;
234
+ return mergedConfig;
235
+ }
236
+ function cloneModel(model) {
237
+ const clonedModel = { ...model };
238
+ if (model.aliases !== void 0) clonedModel.aliases = [...model.aliases];
239
+ if (model.capabilities !== void 0) clonedModel.capabilities = [...model.capabilities];
240
+ if (model.defaultParams !== void 0) clonedModel.defaultParams = { ...model.defaultParams };
241
+ return clonedModel;
242
+ }
243
+ function cloneProvider(provider) {
244
+ const clonedProvider = {
245
+ ...provider,
246
+ models: provider.models.map(cloneModel)
247
+ };
248
+ if (provider.headers !== void 0) clonedProvider.headers = { ...provider.headers };
249
+ return clonedProvider;
250
+ }
251
+ function createEmptySetupConfig() {
252
+ return {
253
+ providers: [],
254
+ version: 1
255
+ };
256
+ }
257
+ function isNodeError(error) {
258
+ return error instanceof Error && "code" in error;
259
+ }
260
+ function mergeModel(existingModel, incomingModel) {
261
+ const existing = cloneModel(existingModel);
262
+ const incoming = cloneModel(incomingModel);
263
+ return {
264
+ ...existing,
265
+ ...incoming,
266
+ aliases: incoming.aliases ?? existing.aliases,
267
+ capabilities: incoming.capabilities ?? existing.capabilities,
268
+ defaultParams: incoming.defaultParams ?? existing.defaultParams
269
+ };
270
+ }
271
+ function prioritizeProviders(providers, prioritizedProviders) {
272
+ const prioritizedProviderIds = new Set(prioritizedProviders.map((provider) => provider.id));
273
+ return [...prioritizedProviders, ...providers.filter((provider) => !prioritizedProviderIds.has(provider.id))];
274
+ }
275
+ //#endregion
276
+ //#region src/setup-write.ts
277
+ async function writeSetupConfig(filePath, config) {
278
+ await mkdir(dirname(filePath), { recursive: true });
279
+ await writeFile(filePath, stringifySetupConfigToml(config), "utf8");
280
+ }
281
+ //#endregion
282
+ export { emptySetupConfig, getGlobalSetupConfigPath, getProjectSetupConfigPath, loadAlintConfig, loadSetupConfig, mergeSetupConfigs, parseSetupConfigToml, stringifySetupConfigToml, writeSetupConfig };
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@alint-js/config",
3
+ "type": "module",
4
+ "version": "0.0.4",
5
+ "exports": {
6
+ ".": {
7
+ "types": "./dist/index.d.mts",
8
+ "default": "./dist/index.mjs"
9
+ },
10
+ "./package.json": "./package.json"
11
+ },
12
+ "files": [
13
+ "dist"
14
+ ],
15
+ "dependencies": {
16
+ "c12": "^3.3.4",
17
+ "pathe": "^2.0.3",
18
+ "smol-toml": "^1.7.0",
19
+ "xdg-basedir": "^5.1.0",
20
+ "@alint-js/core": "0.0.4"
21
+ },
22
+ "devDependencies": {
23
+ "@types/node": "^26.0.1",
24
+ "tsdown": "^0.22.3",
25
+ "typescript": "^6.0.3",
26
+ "vitest": "^4.1.9"
27
+ },
28
+ "scripts": {
29
+ "build": "tsdown",
30
+ "typecheck": "tsc -p tsconfig.json --noEmit",
31
+ "test": "vitest run --config vitest.config.ts"
32
+ }
33
+ }