@atcute/lex-cli 2.5.3 → 2.6.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/dist/codegen.d.ts +1 -7
- package/dist/codegen.d.ts.map +1 -1
- package/dist/codegen.js +7 -16
- package/dist/codegen.js.map +1 -1
- package/dist/commands/export.d.ts +2 -6
- package/dist/commands/export.d.ts.map +1 -1
- package/dist/commands/export.js +5 -17
- package/dist/commands/export.js.map +1 -1
- package/dist/commands/generate.d.ts +2 -6
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +12 -10
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/pull.d.ts +2 -6
- package/dist/commands/pull.d.ts.map +1 -1
- package/dist/commands/pull.js +12 -17
- package/dist/commands/pull.js.map +1 -1
- package/dist/config.d.ts +17 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +19 -1
- package/dist/config.js.map +1 -1
- package/dist/formatter.d.ts +19 -0
- package/dist/formatter.d.ts.map +1 -0
- package/dist/formatter.js +111 -0
- package/dist/formatter.js.map +1 -0
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js.map +1 -1
- package/dist/index.d.ts +2 -66
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lexicon-loader.d.ts.map +1 -1
- package/dist/lexicon-loader.js +9 -1
- package/dist/lexicon-loader.js.map +1 -1
- package/dist/lexicon-metadata.js.map +1 -1
- package/dist/pull-sources/atproto.d.ts +3 -11
- package/dist/pull-sources/atproto.d.ts.map +1 -1
- package/dist/pull-sources/atproto.js.map +1 -1
- package/dist/pull-sources/git.d.ts +3 -7
- package/dist/pull-sources/git.d.ts.map +1 -1
- package/dist/pull-sources/git.js.map +1 -1
- package/dist/shared-options.d.ts +1 -1
- package/package.json +9 -9
- package/src/cli.ts +3 -3
- package/src/codegen.ts +7 -27
- package/src/commands/export.ts +8 -20
- package/src/commands/generate.ts +21 -17
- package/src/commands/pull.ts +17 -23
- package/src/config.ts +16 -1
- package/src/formatter.ts +145 -0
- package/src/index.ts +1 -1
- package/src/pull-sources/atproto.ts +2 -2
- package/src/pull-sources/git.ts +3 -3
package/src/codegen.ts
CHANGED
|
@@ -16,8 +16,6 @@ import type {
|
|
|
16
16
|
} from '@atcute/lexicon-doc';
|
|
17
17
|
import { formatLexiconRef, parseLexiconRef, type ParsedLexiconRef } from '@atcute/lexicon-doc';
|
|
18
18
|
|
|
19
|
-
import * as prettier from 'prettier';
|
|
20
|
-
|
|
21
19
|
export interface SourceFile {
|
|
22
20
|
filename: string;
|
|
23
21
|
code: string;
|
|
@@ -34,13 +32,6 @@ export interface LexiconApiOptions {
|
|
|
34
32
|
modules: {
|
|
35
33
|
importSuffix: string;
|
|
36
34
|
};
|
|
37
|
-
prettier: {
|
|
38
|
-
cwd: string;
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export interface LexiconApiResult {
|
|
43
|
-
files: SourceFile[];
|
|
44
35
|
}
|
|
45
36
|
|
|
46
37
|
type DocumentMap = Map<string, LexiconDoc>;
|
|
@@ -68,7 +59,7 @@ const resolveExternalImport = (nsid: string, mappings: ImportMapping[]): ImportM
|
|
|
68
59
|
|
|
69
60
|
const PURE = `/*#__PURE__*/`;
|
|
70
61
|
|
|
71
|
-
export
|
|
62
|
+
export function* generateLexiconApi(opts: LexiconApiOptions): Generator<SourceFile> {
|
|
72
63
|
const importExt = opts.modules?.importSuffix;
|
|
73
64
|
|
|
74
65
|
const documents = opts.documents.toSorted((a, b) => {
|
|
@@ -83,7 +74,6 @@ export const generateLexiconApi = async (opts: LexiconApiOptions): Promise<Lexic
|
|
|
83
74
|
});
|
|
84
75
|
|
|
85
76
|
const map: DocumentMap = new Map(documents.map((doc) => [doc.id, doc]));
|
|
86
|
-
const files: SourceFile[] = [];
|
|
87
77
|
const generatedIds = new Set<string>();
|
|
88
78
|
|
|
89
79
|
for (const doc of documents) {
|
|
@@ -336,7 +326,7 @@ export const generateLexiconApi = async (opts: LexiconApiOptions): Promise<Lexic
|
|
|
336
326
|
if (file.exports) {
|
|
337
327
|
generatedIds.add(doc.id);
|
|
338
328
|
|
|
339
|
-
|
|
329
|
+
yield {
|
|
340
330
|
filename: filename,
|
|
341
331
|
code:
|
|
342
332
|
file.imports +
|
|
@@ -354,7 +344,7 @@ export const generateLexiconApi = async (opts: LexiconApiOptions): Promise<Lexic
|
|
|
354
344
|
file.sinterfaces +
|
|
355
345
|
`\n\n` +
|
|
356
346
|
file.ambients,
|
|
357
|
-
}
|
|
347
|
+
};
|
|
358
348
|
}
|
|
359
349
|
}
|
|
360
350
|
|
|
@@ -369,23 +359,12 @@ export const generateLexiconApi = async (opts: LexiconApiOptions): Promise<Lexic
|
|
|
369
359
|
code += `export * as ${toTitleCase(doc.id)} from ${lit(`./types/${doc.id.replaceAll('.', '/')}${importExt}`)};\n`;
|
|
370
360
|
}
|
|
371
361
|
|
|
372
|
-
|
|
362
|
+
yield {
|
|
373
363
|
filename: 'index.ts',
|
|
374
364
|
code: code,
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
if (opts.prettier) {
|
|
379
|
-
const config = await prettier.resolveConfig(opts.prettier.cwd, { editorconfig: true });
|
|
380
|
-
|
|
381
|
-
for (const file of files) {
|
|
382
|
-
const formatted = await prettier.format(file.code, { ...config, parser: 'typescript' });
|
|
383
|
-
file.code = formatted;
|
|
384
|
-
}
|
|
365
|
+
};
|
|
385
366
|
}
|
|
386
|
-
|
|
387
|
-
return { files };
|
|
388
|
-
};
|
|
367
|
+
}
|
|
389
368
|
|
|
390
369
|
const generateXrpcQuery = (imports: ImportSet, path: ParsedLexiconRef, spec: LexXrpcQuery): string => {
|
|
391
370
|
const params = generateXrpcParameters(imports, path, spec.parameters);
|
|
@@ -729,6 +708,7 @@ const generateType = (
|
|
|
729
708
|
const refPath = resolvePath(path, ref);
|
|
730
709
|
return { path: refPath, uri: formatLexiconRef(refPath) };
|
|
731
710
|
})
|
|
711
|
+
// oxlint-disable-next-line unicorn/no-array-sort -- map already clones
|
|
732
712
|
.sort((a, b) => {
|
|
733
713
|
if (a.uri < b.uri) {
|
|
734
714
|
return -1;
|
package/src/commands/export.ts
CHANGED
|
@@ -8,11 +8,11 @@ import { message } from '@optique/core/message';
|
|
|
8
8
|
import { type InferValue } from '@optique/core/parser';
|
|
9
9
|
import { command, constant } from '@optique/core/primitives';
|
|
10
10
|
import pc from 'picocolors';
|
|
11
|
-
import prettier from 'prettier';
|
|
12
11
|
|
|
13
|
-
import { loadConfig, type ExportConfig, type NormalizedConfig } from '../config.
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
12
|
+
import { loadConfig, type ExportConfig, type NormalizedConfig } from '../config.ts';
|
|
13
|
+
import { createFormatter, type Formatter } from '../formatter.ts';
|
|
14
|
+
import { loadLexicons } from '../lexicon-loader.ts';
|
|
15
|
+
import { sharedOptions } from '../shared-options.ts';
|
|
16
16
|
|
|
17
17
|
export const exportCommandSchema = command(
|
|
18
18
|
'export',
|
|
@@ -44,27 +44,17 @@ const ensureExportConfig = (config: NormalizedConfig): ExportConfig => {
|
|
|
44
44
|
return config.export;
|
|
45
45
|
};
|
|
46
46
|
|
|
47
|
-
/**
|
|
48
|
-
* writes a lexicon document to disk as formatted JSON
|
|
49
|
-
* @param outdir output directory
|
|
50
|
-
* @param nsid the NSID of the lexicon
|
|
51
|
-
* @param doc the lexicon document
|
|
52
|
-
* @param prettierConfig prettier configuration
|
|
53
|
-
*/
|
|
54
47
|
const writeLexicon = async (
|
|
55
48
|
outdir: string,
|
|
56
49
|
nsid: string,
|
|
57
50
|
doc: LexiconDoc,
|
|
58
|
-
|
|
51
|
+
formatter: Formatter,
|
|
59
52
|
): Promise<void> => {
|
|
60
53
|
const nsidPath = nsid.replaceAll('.', '/');
|
|
61
54
|
const target = path.join(outdir, `${nsidPath}.json`);
|
|
62
55
|
const dirname = path.dirname(target);
|
|
63
56
|
|
|
64
|
-
const code = await
|
|
65
|
-
...prettierConfig,
|
|
66
|
-
parser: 'json',
|
|
67
|
-
});
|
|
57
|
+
const code = await formatter.format(JSON.stringify(doc, null, 2), target);
|
|
68
58
|
|
|
69
59
|
await fs.mkdir(dirname, { recursive: true });
|
|
70
60
|
await fs.writeFile(target, code);
|
|
@@ -81,7 +71,7 @@ export const runExport = async (args: ExportCommand): Promise<void> => {
|
|
|
81
71
|
// use export.files if specified, otherwise fall back to root files config
|
|
82
72
|
const files = exportConfig.files ?? config.files;
|
|
83
73
|
const outdir = path.resolve(config.root, exportConfig.outdir);
|
|
84
|
-
const
|
|
74
|
+
const formatter = await createFormatter(config.formatter, config.root);
|
|
85
75
|
|
|
86
76
|
// load lexicons from files
|
|
87
77
|
const loaded = await loadLexicons(files, config.root);
|
|
@@ -99,9 +89,7 @@ export const runExport = async (args: ExportCommand): Promise<void> => {
|
|
|
99
89
|
await fs.mkdir(outdir, { recursive: true });
|
|
100
90
|
|
|
101
91
|
// write each lexicon as JSON
|
|
102
|
-
|
|
103
|
-
await writeLexicon(outdir, nsid, doc, prettierConfig);
|
|
104
|
-
}
|
|
92
|
+
await Promise.all(loaded.map(({ nsid, doc }) => writeLexicon(outdir, nsid, doc, formatter)));
|
|
105
93
|
|
|
106
94
|
console.log(pc.green(`exported ${loaded.length} lexicon(s) to ${outdir}`));
|
|
107
95
|
};
|
package/src/commands/generate.ts
CHANGED
|
@@ -7,11 +7,12 @@ import { type InferValue } from '@optique/core/parser';
|
|
|
7
7
|
import { command, constant } from '@optique/core/primitives';
|
|
8
8
|
import pc from 'picocolors';
|
|
9
9
|
|
|
10
|
-
import { generateLexiconApi, type ImportMapping } from '../codegen.
|
|
11
|
-
import { loadConfig } from '../config.
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
10
|
+
import { generateLexiconApi, type ImportMapping } from '../codegen.ts';
|
|
11
|
+
import { loadConfig } from '../config.ts';
|
|
12
|
+
import { createFormatter } from '../formatter.ts';
|
|
13
|
+
import { loadLexicons } from '../lexicon-loader.ts';
|
|
14
|
+
import { packageJsonSchema } from '../lexicon-metadata.ts';
|
|
15
|
+
import { sharedOptions } from '../shared-options.ts';
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* resolves package imports to ImportMapping[]
|
|
@@ -147,24 +148,27 @@ export const runGenerate = async (args: GenerateCommand): Promise<void> => {
|
|
|
147
148
|
const loaded = await loadLexicons(config.files, config.root);
|
|
148
149
|
const documents = loaded.map((l) => l.doc);
|
|
149
150
|
|
|
150
|
-
const
|
|
151
|
+
const outdir = path.join(config.root, config.outdir);
|
|
152
|
+
const formatter = await createFormatter(config.formatter, config.root);
|
|
153
|
+
const pending: Promise<void>[] = [];
|
|
154
|
+
|
|
155
|
+
for (const file of generateLexiconApi({
|
|
151
156
|
documents: documents,
|
|
152
157
|
mappings: allMappings,
|
|
153
158
|
modules: {
|
|
154
159
|
importSuffix: config.modules?.importSuffix ?? '.js',
|
|
155
160
|
},
|
|
156
|
-
|
|
157
|
-
cwd: process.cwd(),
|
|
158
|
-
},
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
const outdir = path.join(config.root, config.outdir);
|
|
162
|
-
|
|
163
|
-
for (const file of generationResult.files) {
|
|
161
|
+
})) {
|
|
164
162
|
const filename = path.join(outdir, file.filename);
|
|
165
|
-
const dirname = path.dirname(filename);
|
|
166
163
|
|
|
167
|
-
|
|
168
|
-
|
|
164
|
+
pending.push(
|
|
165
|
+
(async () => {
|
|
166
|
+
const formatted = await formatter.format(file.code, filename);
|
|
167
|
+
await fs.mkdir(path.dirname(filename), { recursive: true });
|
|
168
|
+
await fs.writeFile(filename, formatted);
|
|
169
|
+
})(),
|
|
170
|
+
);
|
|
169
171
|
}
|
|
172
|
+
|
|
173
|
+
await Promise.all(pending);
|
|
170
174
|
};
|
package/src/commands/pull.ts
CHANGED
|
@@ -8,13 +8,13 @@ import { message } from '@optique/core/message';
|
|
|
8
8
|
import { type InferValue } from '@optique/core/parser';
|
|
9
9
|
import { command, constant } from '@optique/core/primitives';
|
|
10
10
|
import pc from 'picocolors';
|
|
11
|
-
import prettier from 'prettier';
|
|
12
11
|
|
|
13
|
-
import { loadConfig, type NormalizedConfig, type PullConfig, type SourceConfig } from '../config.
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import
|
|
17
|
-
import {
|
|
12
|
+
import { loadConfig, type NormalizedConfig, type PullConfig, type SourceConfig } from '../config.ts';
|
|
13
|
+
import { createFormatter, type Formatter } from '../formatter.ts';
|
|
14
|
+
import { pullAtprotoSource } from '../pull-sources/atproto.ts';
|
|
15
|
+
import { pullGitSource } from '../pull-sources/git.ts';
|
|
16
|
+
import type { PullResult, PulledLexicon, SourceLocation } from '../pull-sources/types.ts';
|
|
17
|
+
import { sharedOptions } from '../shared-options.ts';
|
|
18
18
|
|
|
19
19
|
export const pullCommandSchema = command(
|
|
20
20
|
'pull',
|
|
@@ -110,16 +110,13 @@ const writeLexicon = async (
|
|
|
110
110
|
outdir: string,
|
|
111
111
|
nsid: string,
|
|
112
112
|
doc: LexiconDoc,
|
|
113
|
-
|
|
113
|
+
formatter: Formatter,
|
|
114
114
|
): Promise<void> => {
|
|
115
115
|
const nsidPath = nsid.replaceAll('.', '/');
|
|
116
116
|
const target = path.join(outdir, `${nsidPath}.json`);
|
|
117
117
|
const dirname = path.dirname(target);
|
|
118
118
|
|
|
119
|
-
const code = await
|
|
120
|
-
...prettierConfig,
|
|
121
|
-
parser: 'json',
|
|
122
|
-
});
|
|
119
|
+
const code = await formatter.format(JSON.stringify(doc, null, 2), target);
|
|
123
120
|
|
|
124
121
|
await fs.mkdir(dirname, { recursive: true });
|
|
125
122
|
await fs.writeFile(target, code);
|
|
@@ -139,7 +136,7 @@ const pullSource = async (source: SourceConfig): Promise<PullResult> => {
|
|
|
139
136
|
const writeSourceReadme = async (
|
|
140
137
|
outdir: string,
|
|
141
138
|
revisions: SourceRevision[],
|
|
142
|
-
|
|
139
|
+
formatter: Formatter,
|
|
143
140
|
): Promise<void> => {
|
|
144
141
|
const lines = [
|
|
145
142
|
'# lexicon sources',
|
|
@@ -173,12 +170,10 @@ const writeSourceReadme = async (
|
|
|
173
170
|
lines.push('');
|
|
174
171
|
|
|
175
172
|
const content = lines.join('\n');
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
parser: 'markdown',
|
|
179
|
-
});
|
|
173
|
+
const target = path.join(outdir, 'README.md');
|
|
174
|
+
const formatted = await formatter.format(content, target);
|
|
180
175
|
|
|
181
|
-
await fs.writeFile(
|
|
176
|
+
await fs.writeFile(target, formatted);
|
|
182
177
|
};
|
|
183
178
|
|
|
184
179
|
/**
|
|
@@ -190,7 +185,7 @@ export const runPull = async (args: PullCommand): Promise<void> => {
|
|
|
190
185
|
const pullConfig = ensurePullConfig(config);
|
|
191
186
|
|
|
192
187
|
const outdir = path.resolve(config.root, pullConfig.outdir);
|
|
193
|
-
const
|
|
188
|
+
const formatter = await createFormatter(config.formatter, config.root);
|
|
194
189
|
|
|
195
190
|
const seen = new Map<string, SourceLocation>();
|
|
196
191
|
const collected: PulledLexicon[] = [];
|
|
@@ -224,9 +219,8 @@ export const runPull = async (args: PullCommand): Promise<void> => {
|
|
|
224
219
|
|
|
225
220
|
await fs.mkdir(outdir, { recursive: true });
|
|
226
221
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
await writeSourceReadme(outdir, sourceRevisions, prettierConfig);
|
|
222
|
+
await Promise.all([
|
|
223
|
+
...collected.map((entry) => writeLexicon(outdir, entry.nsid, entry.doc, formatter)),
|
|
224
|
+
writeSourceReadme(outdir, sourceRevisions, formatter),
|
|
225
|
+
]);
|
|
232
226
|
};
|
package/src/config.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as fs from 'node:fs/promises';
|
|
2
|
+
import { availableParallelism } from 'node:os';
|
|
2
3
|
import * as path from 'node:path';
|
|
3
4
|
import * as url from 'node:url';
|
|
4
5
|
|
|
@@ -8,7 +9,7 @@ import { isHandle, isNsid } from '@atcute/lexicons/syntax';
|
|
|
8
9
|
import * as v from '@badrap/valita';
|
|
9
10
|
import pc from 'picocolors';
|
|
10
11
|
|
|
11
|
-
import type { ImportMapping } from './codegen.
|
|
12
|
+
import type { ImportMapping } from './codegen.ts';
|
|
12
13
|
|
|
13
14
|
const gitSourceConfigSchema = v.object({
|
|
14
15
|
type: v.literal('git'),
|
|
@@ -63,6 +64,18 @@ const exportConfigSchema = v.object({
|
|
|
63
64
|
clean: v.boolean().optional(),
|
|
64
65
|
});
|
|
65
66
|
|
|
67
|
+
const formatterConfigSchema = v.union(
|
|
68
|
+
v.object({ type: v.literal('prettier') }),
|
|
69
|
+
v.object({
|
|
70
|
+
type: v.literal('command'),
|
|
71
|
+
command: v.string().assert((value) => value.length > 0, `must not be empty`),
|
|
72
|
+
concurrency: v
|
|
73
|
+
.number()
|
|
74
|
+
.assert((value) => Number.isInteger(value) && value > 0, `must be a positive integer`)
|
|
75
|
+
.optional(() => availableParallelism()),
|
|
76
|
+
}),
|
|
77
|
+
);
|
|
78
|
+
|
|
66
79
|
export type GitSourceConfig = v.Infer<typeof gitSourceConfigSchema>;
|
|
67
80
|
export type AtprotoNsidsSourceConfig = v.Infer<typeof atprotoNsidsSourceConfigSchema>;
|
|
68
81
|
export type AtprotoAuthoritySourceConfig = v.Infer<typeof atprotoAuthoritySourceConfigSchema>;
|
|
@@ -70,6 +83,7 @@ export type AtprotoSourceConfig = v.Infer<typeof atprotoSourceConfigSchema>;
|
|
|
70
83
|
export type SourceConfig = v.Infer<typeof sourceConfigSchema>;
|
|
71
84
|
export type PullConfig = v.Infer<typeof pullConfigSchema>;
|
|
72
85
|
export type ExportConfig = v.Infer<typeof exportConfigSchema>;
|
|
86
|
+
export type FormatterConfig = v.Infer<typeof formatterConfigSchema>;
|
|
73
87
|
|
|
74
88
|
const isValidLexiconPattern = (pattern: string): boolean => {
|
|
75
89
|
if (pattern.endsWith('.*')) {
|
|
@@ -126,6 +140,7 @@ export const lexiconConfigSchema = v.object({
|
|
|
126
140
|
})
|
|
127
141
|
.partial()
|
|
128
142
|
.optional(),
|
|
143
|
+
formatter: formatterConfigSchema.optional((): FormatterConfig => ({ type: 'prettier' })),
|
|
129
144
|
pull: pullConfigSchema.optional(),
|
|
130
145
|
export: exportConfigSchema.optional(),
|
|
131
146
|
});
|
package/src/formatter.ts
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { availableParallelism } from 'node:os';
|
|
3
|
+
|
|
4
|
+
import type { FormatterConfig } from './config.ts';
|
|
5
|
+
|
|
6
|
+
/** formats source code */
|
|
7
|
+
export interface Formatter {
|
|
8
|
+
/**
|
|
9
|
+
* formats the given code
|
|
10
|
+
* @param code source code to format
|
|
11
|
+
* @param filepath filepath hint for language detection and config resolution
|
|
12
|
+
* @returns formatted code
|
|
13
|
+
*/
|
|
14
|
+
format(code: string, filepath: string): Promise<string>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const inferPrettierParser = (filepath: string): string => {
|
|
18
|
+
if (filepath.endsWith('.ts') || filepath.endsWith('.tsx')) {
|
|
19
|
+
return 'typescript';
|
|
20
|
+
}
|
|
21
|
+
if (filepath.endsWith('.json')) {
|
|
22
|
+
return 'json';
|
|
23
|
+
}
|
|
24
|
+
if (filepath.endsWith('.md') || filepath.endsWith('.markdown')) {
|
|
25
|
+
return 'markdown';
|
|
26
|
+
}
|
|
27
|
+
return 'typescript';
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// #region semaphore
|
|
31
|
+
|
|
32
|
+
interface Lock {
|
|
33
|
+
release(): void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
class Semaphore {
|
|
37
|
+
private waiting: (() => void)[] = [];
|
|
38
|
+
private active = 0;
|
|
39
|
+
private max: number;
|
|
40
|
+
|
|
41
|
+
constructor(max: number) {
|
|
42
|
+
this.max = max;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
acquire(): Promise<Lock> {
|
|
46
|
+
const lock: Lock = {
|
|
47
|
+
release: () => {
|
|
48
|
+
this.active--;
|
|
49
|
+
const next = this.waiting.shift();
|
|
50
|
+
if (next) {
|
|
51
|
+
this.active++;
|
|
52
|
+
next();
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
if (this.active < this.max) {
|
|
58
|
+
this.active++;
|
|
59
|
+
return Promise.resolve(lock);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const { promise, resolve } = Promise.withResolvers<Lock>();
|
|
63
|
+
this.waiting.push(() => resolve(lock));
|
|
64
|
+
return promise;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// #endregion
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* creates a formatter from the given configuration
|
|
72
|
+
* @param config formatter configuration
|
|
73
|
+
* @param root project root for config resolution
|
|
74
|
+
* @returns a formatter instance
|
|
75
|
+
*/
|
|
76
|
+
export const createFormatter = async (config: FormatterConfig, root: string): Promise<Formatter> => {
|
|
77
|
+
let inner: Formatter;
|
|
78
|
+
let concurrency: number;
|
|
79
|
+
|
|
80
|
+
if (config.type === 'prettier') {
|
|
81
|
+
const prettier = await import('prettier');
|
|
82
|
+
const prettierConfig = await prettier.resolveConfig(root, { editorconfig: true });
|
|
83
|
+
|
|
84
|
+
// prettier is in-process and CPU-bound, so concurrency only helps
|
|
85
|
+
// avoid buffering all files in memory at once
|
|
86
|
+
concurrency = availableParallelism();
|
|
87
|
+
inner = {
|
|
88
|
+
async format(code, filepath) {
|
|
89
|
+
return prettier.format(code, { ...prettierConfig, parser: inferPrettierParser(filepath) });
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
} else {
|
|
93
|
+
// the template uses {filepath} as a placeholder, which is passed as a
|
|
94
|
+
// positional argument to sh to avoid shell injection via filenames
|
|
95
|
+
const shellCmd = config.command.replaceAll('{filepath}', '"$1"');
|
|
96
|
+
|
|
97
|
+
concurrency = config.concurrency;
|
|
98
|
+
inner = {
|
|
99
|
+
format(code, filepath) {
|
|
100
|
+
return new Promise<string>((resolve, reject) => {
|
|
101
|
+
const child = spawn('sh', ['-c', shellCmd, 'sh', filepath], {
|
|
102
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const stdoutChunks: Buffer[] = [];
|
|
106
|
+
const stderrChunks: Buffer[] = [];
|
|
107
|
+
|
|
108
|
+
child.stdout.on('data', (chunk: Buffer) => {
|
|
109
|
+
stdoutChunks.push(chunk);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
child.stderr.on('data', (chunk: Buffer) => {
|
|
113
|
+
stderrChunks.push(chunk);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
child.on('error', reject);
|
|
117
|
+
|
|
118
|
+
child.on('close', (exitCode: number | null) => {
|
|
119
|
+
if (exitCode !== 0) {
|
|
120
|
+
const stderr = Buffer.concat(stderrChunks).toString();
|
|
121
|
+
reject(new Error(`formatter exited with code ${exitCode}:\n${stderr}`));
|
|
122
|
+
} else {
|
|
123
|
+
resolve(Buffer.concat(stdoutChunks).toString());
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
child.stdin.end(code);
|
|
128
|
+
});
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const semaphore = new Semaphore(concurrency);
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
async format(code, filepath) {
|
|
137
|
+
const lock = await semaphore.acquire();
|
|
138
|
+
try {
|
|
139
|
+
return await inner.format(code, filepath);
|
|
140
|
+
} finally {
|
|
141
|
+
lock.release();
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -20,9 +20,9 @@ import {
|
|
|
20
20
|
|
|
21
21
|
import pc from 'picocolors';
|
|
22
22
|
|
|
23
|
-
import type { AtprotoSourceConfig } from '../config.
|
|
23
|
+
import type { AtprotoSourceConfig } from '../config.ts';
|
|
24
24
|
|
|
25
|
-
import type { PullResult, SourceLocation } from './types.
|
|
25
|
+
import type { PullResult, SourceLocation } from './types.ts';
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* discovers all published lexicons for an authority by listing records in the
|
package/src/pull-sources/git.ts
CHANGED
|
@@ -6,10 +6,10 @@ import type { LexiconDoc } from '@atcute/lexicon-doc';
|
|
|
6
6
|
|
|
7
7
|
import pc from 'picocolors';
|
|
8
8
|
|
|
9
|
-
import type { GitSourceConfig } from '../config.
|
|
10
|
-
import { runGit, GitError } from '../git.
|
|
9
|
+
import type { GitSourceConfig } from '../config.ts';
|
|
10
|
+
import { runGit, GitError } from '../git.ts';
|
|
11
11
|
|
|
12
|
-
import type { PullResult, PulledLexicon, SourceLocation } from './types.
|
|
12
|
+
import type { PullResult, PulledLexicon, SourceLocation } from './types.ts';
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* pulls lexicon documents from a git repository source
|