@atcute/lex-cli 2.3.2 → 2.4.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/README.md +34 -0
- package/dist/cli.js +26 -21
- package/dist/cli.js.map +1 -1
- package/dist/codegen.d.ts.map +1 -1
- package/dist/codegen.js +7 -14
- package/dist/codegen.js.map +1 -1
- package/dist/config.d.ts +53 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +97 -0
- package/dist/config.js.map +1 -0
- package/dist/git.d.ts +27 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +73 -0
- package/dist/git.js.map +1 -0
- package/dist/index.d.ts +2 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/pull.d.ts +7 -0
- package/dist/pull.d.ts.map +1 -0
- package/dist/pull.js +209 -0
- package/dist/pull.js.map +1 -0
- package/package.json +3 -3
- package/src/cli.ts +39 -28
- package/src/codegen.ts +10 -26
- package/src/config.ts +130 -0
- package/src/git.ts +104 -0
- package/src/index.ts +3 -11
- package/src/pull.ts +298 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
import type
|
|
2
|
-
export
|
|
3
|
-
outdir: string;
|
|
4
|
-
files: string[];
|
|
5
|
-
imports?: string[];
|
|
6
|
-
mappings?: ImportMapping[];
|
|
7
|
-
modules?: {
|
|
8
|
-
importSuffix?: string;
|
|
9
|
-
};
|
|
10
|
-
}
|
|
1
|
+
import { type LexiconConfig } from './config.js';
|
|
2
|
+
export type { LexiconConfig };
|
|
11
3
|
export declare const defineLexiconConfig: (config: LexiconConfig) => LexiconConfig;
|
|
12
4
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAEtE,YAAY,EAAE,aAAa,EAAE,CAAC;AAE9B,eAAO,MAAM,mBAAmB,GAAI,QAAQ,aAAa,KAAG,aAE3D,CAAC"}
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAsB,MAAM,aAAa,CAAC;AAItE,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,MAAqB,EAAiB,EAAE;IAC3E,OAAO,mBAAmB,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;AAC1C,CAAC,CAAC"}
|
package/dist/pull.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { NormalizedConfig } from './config.js';
|
|
2
|
+
/**
|
|
3
|
+
* pulls lexicon documents from configured sources and writes them to disk using nsid-based paths.
|
|
4
|
+
* @param config normalized lex-cli configuration
|
|
5
|
+
*/
|
|
6
|
+
export declare const runPull: (config: NormalizedConfig) => Promise<void>;
|
|
7
|
+
//# sourceMappingURL=pull.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pull.d.ts","sourceRoot":"","sources":["../src/pull.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,gBAAgB,EAA4B,MAAM,aAAa,CAAC;AAkP9E;;;GAGG;AACH,eAAO,MAAM,OAAO,GAAU,QAAQ,gBAAgB,KAAG,OAAO,CAAC,IAAI,CA0CpE,CAAC"}
|
package/dist/pull.js
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { lexiconDoc, refineLexiconDoc } from '@atcute/lexicon-doc';
|
|
5
|
+
import prettier from 'prettier';
|
|
6
|
+
import pc from 'picocolors';
|
|
7
|
+
import { runGit, GitError } from './git.js';
|
|
8
|
+
const ensurePullConfig = (config) => {
|
|
9
|
+
if (!config.pull) {
|
|
10
|
+
console.error(pc.bold(pc.red(`pull configuration missing`)));
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
return config.pull;
|
|
14
|
+
};
|
|
15
|
+
const parseLexiconFile = async (loc) => {
|
|
16
|
+
let source;
|
|
17
|
+
try {
|
|
18
|
+
source = await fs.readFile(loc.absolutePath, 'utf8');
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
console.error(pc.bold(pc.red(`file read error for ${loc.relativePath} when pulling ${loc.sourceDescription}`)));
|
|
22
|
+
console.error(`found in ${loc.absolutePath}`);
|
|
23
|
+
console.error(err);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
let json;
|
|
27
|
+
try {
|
|
28
|
+
json = JSON.parse(source);
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
console.error(pc.bold(pc.red(`json parse error in ${loc.relativePath} when pulling ${loc.sourceDescription}`)));
|
|
32
|
+
console.error(`found in ${loc.absolutePath}`);
|
|
33
|
+
console.error(err);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
const result = lexiconDoc.try(json, { mode: 'passthrough' });
|
|
37
|
+
if (!result.ok) {
|
|
38
|
+
console.error(pc.bold(pc.red(`schema validation failed for ${loc.relativePath} when pulling ${loc.sourceDescription}`)));
|
|
39
|
+
console.error(`found in ${loc.absolutePath}`);
|
|
40
|
+
console.error(result.message);
|
|
41
|
+
for (const issue of result.issues) {
|
|
42
|
+
console.log(`- ${issue.code} at .${issue.path.join('.')}`);
|
|
43
|
+
}
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
const issues = refineLexiconDoc(result.value, true);
|
|
47
|
+
if (issues.length > 0) {
|
|
48
|
+
console.error(pc.bold(pc.red(`lint validation failed for ${loc.relativePath} when pulling ${loc.sourceDescription}`)));
|
|
49
|
+
console.error(`found in ${loc.absolutePath}`);
|
|
50
|
+
for (const issue of issues) {
|
|
51
|
+
console.log(`- ${issue.message} at .${issue.path.join('.')}`);
|
|
52
|
+
}
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
return result.value;
|
|
56
|
+
};
|
|
57
|
+
const writeLexicon = async (outdir, nsid, doc, prettierConfig) => {
|
|
58
|
+
const nsidPath = nsid.replaceAll('.', '/');
|
|
59
|
+
const target = path.join(outdir, `${nsidPath}.json`);
|
|
60
|
+
const dirname = path.dirname(target);
|
|
61
|
+
const code = await prettier.format(JSON.stringify(doc, null, 2), {
|
|
62
|
+
...(prettierConfig ?? {}),
|
|
63
|
+
parser: 'json',
|
|
64
|
+
});
|
|
65
|
+
await fs.mkdir(dirname, { recursive: true });
|
|
66
|
+
await fs.writeFile(target, code);
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* pulls lexicon documents from a git repository source
|
|
70
|
+
* @param source git source configuration
|
|
71
|
+
* @returns pulled lexicons and commit hash
|
|
72
|
+
*/
|
|
73
|
+
const pullGitSource = async (source) => {
|
|
74
|
+
const tempParent = await fs.mkdtemp(path.join(os.tmpdir(), 'lex-cli-pull-'));
|
|
75
|
+
const cloneDir = path.join(tempParent, 'repo');
|
|
76
|
+
try {
|
|
77
|
+
await runGit([
|
|
78
|
+
'clone',
|
|
79
|
+
'--filter=blob:none',
|
|
80
|
+
'--depth',
|
|
81
|
+
'1',
|
|
82
|
+
'--sparse',
|
|
83
|
+
...(source.ref ? ['--branch', source.ref, '--single-branch'] : []),
|
|
84
|
+
source.remote,
|
|
85
|
+
cloneDir,
|
|
86
|
+
], { timeoutMs: 60_000 });
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
if (err instanceof GitError) {
|
|
90
|
+
console.error(pc.bold(pc.red(`git clone failed for ${source.remote}:`)));
|
|
91
|
+
console.error(err.stderr || err.message);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
throw err;
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
await runGit(['-C', cloneDir, 'sparse-checkout', 'set', '--no-cone', ...source.pattern], {
|
|
98
|
+
timeoutMs: 30_000,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
if (err instanceof GitError) {
|
|
103
|
+
console.error(pc.bold(pc.red(`git sparse-checkout failed for ${source.remote}:`)));
|
|
104
|
+
console.error(err.stderr || err.message);
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
throw err;
|
|
108
|
+
}
|
|
109
|
+
const pulled = new Map();
|
|
110
|
+
for await (const filename of fs.glob(source.pattern, { cwd: cloneDir })) {
|
|
111
|
+
const absolute = path.join(cloneDir, filename);
|
|
112
|
+
const stat = await fs.stat(absolute);
|
|
113
|
+
if (!stat.isFile()) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const location = {
|
|
117
|
+
absolutePath: absolute,
|
|
118
|
+
relativePath: filename,
|
|
119
|
+
sourceDescription: source.remote,
|
|
120
|
+
};
|
|
121
|
+
const doc = await parseLexiconFile(location);
|
|
122
|
+
pulled.set(doc.id, { nsid: doc.id, doc, location });
|
|
123
|
+
}
|
|
124
|
+
// get the commit hash
|
|
125
|
+
let rev;
|
|
126
|
+
try {
|
|
127
|
+
const result = await runGit(['-C', cloneDir, 'rev-parse', 'HEAD'], { timeoutMs: 10_000 });
|
|
128
|
+
rev = result.stdout.trim();
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
if (err instanceof GitError) {
|
|
132
|
+
console.error(pc.bold(pc.red(`git rev-parse failed for ${source.remote}:`)));
|
|
133
|
+
console.error(err.stderr || err.message);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
throw err;
|
|
137
|
+
}
|
|
138
|
+
await fs.rm(tempParent, { recursive: true, force: true });
|
|
139
|
+
return { pulled, rev };
|
|
140
|
+
};
|
|
141
|
+
const pullSource = async (source) => {
|
|
142
|
+
switch (source.type) {
|
|
143
|
+
case 'git': {
|
|
144
|
+
return pullGitSource(source);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
const writeSourceReadme = async (outdir, revisions, prettierConfig) => {
|
|
149
|
+
const lines = [
|
|
150
|
+
'# lexicon sources',
|
|
151
|
+
'',
|
|
152
|
+
'this directory contains lexicon documents pulled from the following sources:',
|
|
153
|
+
'',
|
|
154
|
+
];
|
|
155
|
+
for (const { source, rev } of revisions) {
|
|
156
|
+
switch (source.type) {
|
|
157
|
+
case 'git': {
|
|
158
|
+
lines.push(`- ${source.remote}${source.ref ? ` (ref: ${source.ref})` : ``}`);
|
|
159
|
+
lines.push(` - commit: ${rev}`);
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
lines.push('');
|
|
165
|
+
const content = lines.join('\n');
|
|
166
|
+
const formatted = await prettier.format(content, {
|
|
167
|
+
...(prettierConfig ?? {}),
|
|
168
|
+
parser: 'markdown',
|
|
169
|
+
});
|
|
170
|
+
await fs.writeFile(path.join(outdir, 'README.md'), formatted);
|
|
171
|
+
};
|
|
172
|
+
/**
|
|
173
|
+
* pulls lexicon documents from configured sources and writes them to disk using nsid-based paths.
|
|
174
|
+
* @param config normalized lex-cli configuration
|
|
175
|
+
*/
|
|
176
|
+
export const runPull = async (config) => {
|
|
177
|
+
const pullConfig = ensurePullConfig(config);
|
|
178
|
+
const outdir = path.resolve(config.root, pullConfig.outdir);
|
|
179
|
+
const prettierConfig = await prettier.resolveConfig(config.root, { editorconfig: true });
|
|
180
|
+
const seen = new Map();
|
|
181
|
+
const collected = [];
|
|
182
|
+
const sourceRevisions = [];
|
|
183
|
+
for (const source of pullConfig.sources) {
|
|
184
|
+
const result = await pullSource(source);
|
|
185
|
+
sourceRevisions.push({ source, rev: result.rev });
|
|
186
|
+
for (const [nsid, entry] of result.pulled) {
|
|
187
|
+
const existing = seen.get(nsid);
|
|
188
|
+
if (existing) {
|
|
189
|
+
console.error(pc.bold(pc.red(`duplicate lexicon "${nsid}"`)));
|
|
190
|
+
console.error(`- found ${entry.location.relativePath} from ${entry.location.sourceDescription}`);
|
|
191
|
+
console.error(` at ${entry.location.absolutePath}`);
|
|
192
|
+
console.error(`- already found ${existing.relativePath} from ${existing.sourceDescription}`);
|
|
193
|
+
console.error(` at ${existing.absolutePath}`);
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
seen.set(nsid, entry.location);
|
|
197
|
+
collected.push(entry);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (pullConfig.clean) {
|
|
201
|
+
await fs.rm(outdir, { recursive: true, force: true });
|
|
202
|
+
}
|
|
203
|
+
await fs.mkdir(outdir, { recursive: true });
|
|
204
|
+
for (const entry of collected) {
|
|
205
|
+
await writeLexicon(outdir, entry.nsid, entry.doc, prettierConfig);
|
|
206
|
+
}
|
|
207
|
+
await writeSourceReadme(outdir, sourceRevisions, prettierConfig);
|
|
208
|
+
};
|
|
209
|
+
//# sourceMappingURL=pull.js.map
|
package/dist/pull.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pull.js","sourceRoot":"","sources":["../src/pull.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@atcute/lex-cli",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.4.0",
|
|
5
5
|
"description": "cli tool to generate type definitions for atcute",
|
|
6
6
|
"license": "0BSD",
|
|
7
7
|
"repository": {
|
|
@@ -26,12 +26,12 @@
|
|
|
26
26
|
"@optique/run": "^0.6.2",
|
|
27
27
|
"picocolors": "^1.1.1",
|
|
28
28
|
"prettier": "^3.6.2",
|
|
29
|
-
"@atcute/lexicon-doc": "^
|
|
29
|
+
"@atcute/lexicon-doc": "^2.0.2"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/node": "^22.19.0",
|
|
33
33
|
"tschema": "^3.2.0",
|
|
34
|
-
"@atcute/lexicons": "^1.2.
|
|
34
|
+
"@atcute/lexicons": "^1.2.5"
|
|
35
35
|
},
|
|
36
36
|
"scripts": {
|
|
37
37
|
"build": "pnpm run generate:schema && tsc",
|
package/src/cli.ts
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import * as fs from 'node:fs/promises';
|
|
2
2
|
import * as path from 'node:path';
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
import { lexiconDoc, refineLexiconDoc, type LexiconDoc } from '@atcute/lexicon-doc';
|
|
4
5
|
|
|
5
6
|
import { object } from '@optique/core/constructs';
|
|
6
7
|
import { command, constant, option } from '@optique/core/primitives';
|
|
8
|
+
import { or } from '@optique/core/constructs';
|
|
7
9
|
import { run } from '@optique/run';
|
|
8
10
|
import { path as pathParser } from '@optique/run/valueparser';
|
|
9
11
|
import pc from 'picocolors';
|
|
10
12
|
|
|
11
|
-
import { lexiconDoc, type LexiconDoc } from '@atcute/lexicon-doc';
|
|
12
|
-
|
|
13
13
|
import { generateLexiconApi, type ImportMapping } from './codegen.js';
|
|
14
|
-
import
|
|
14
|
+
import { loadConfig } from './config.js';
|
|
15
15
|
import { packageJsonSchema } from './lexicon-metadata.js';
|
|
16
|
+
import { runPull } from './pull.js';
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Resolves package imports to ImportMapping[]
|
|
@@ -117,42 +118,38 @@ const resolveImportsToMappings = async (
|
|
|
117
118
|
return mappings;
|
|
118
119
|
};
|
|
119
120
|
|
|
120
|
-
const parser =
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
121
|
+
const parser = or(
|
|
122
|
+
command(
|
|
123
|
+
'generate',
|
|
124
|
+
object({
|
|
125
|
+
type: constant('generate'),
|
|
126
|
+
config: option('-c', '--config', pathParser({ metavar: 'CONFIG' })),
|
|
127
|
+
}),
|
|
128
|
+
),
|
|
129
|
+
command(
|
|
130
|
+
'pull',
|
|
131
|
+
object({
|
|
132
|
+
type: constant('pull'),
|
|
133
|
+
config: option('-c', '--config', pathParser({ metavar: 'CONFIG' })),
|
|
134
|
+
}),
|
|
135
|
+
),
|
|
126
136
|
);
|
|
127
137
|
|
|
128
138
|
const result = run(parser, { programName: 'lex-cli' });
|
|
129
139
|
|
|
130
140
|
if (result.type === 'generate') {
|
|
131
|
-
const
|
|
132
|
-
const configDirname = path.dirname(configFilename);
|
|
133
|
-
|
|
134
|
-
let config: LexiconConfig;
|
|
135
|
-
try {
|
|
136
|
-
const configURL = url.pathToFileURL(configFilename);
|
|
137
|
-
const configMod = (await import(configURL.href)) as { default: LexiconConfig };
|
|
138
|
-
config = configMod.default;
|
|
139
|
-
} catch (err) {
|
|
140
|
-
console.error(pc.bold(pc.red(`failed to import config:`)));
|
|
141
|
-
console.error(err);
|
|
142
|
-
|
|
143
|
-
process.exit(1);
|
|
144
|
-
}
|
|
141
|
+
const config = await loadConfig(result.config);
|
|
145
142
|
|
|
146
143
|
// Resolve imports to mappings
|
|
147
|
-
const importMappings = config.imports ? await resolveImportsToMappings(config.imports,
|
|
144
|
+
const importMappings = config.imports ? await resolveImportsToMappings(config.imports, config.root) : [];
|
|
148
145
|
const allMappings = [...importMappings, ...(config.mappings ?? [])];
|
|
149
146
|
|
|
150
147
|
const documents: LexiconDoc[] = [];
|
|
151
148
|
|
|
152
|
-
for await (const filename of fs.glob(config.files, { cwd:
|
|
149
|
+
for await (const filename of fs.glob(config.files, { cwd: config.root })) {
|
|
153
150
|
let source: string;
|
|
154
151
|
try {
|
|
155
|
-
source = await fs.readFile(path.join(
|
|
152
|
+
source = await fs.readFile(path.join(config.root, filename), 'utf8');
|
|
156
153
|
} catch (err) {
|
|
157
154
|
console.error(pc.bold(pc.red(`file read error with "${filename}"`)));
|
|
158
155
|
console.error(err);
|
|
@@ -182,6 +179,17 @@ if (result.type === 'generate') {
|
|
|
182
179
|
process.exit(1);
|
|
183
180
|
}
|
|
184
181
|
|
|
182
|
+
const issues = refineLexiconDoc(result.value, true);
|
|
183
|
+
if (issues.length > 0) {
|
|
184
|
+
console.error(pc.bold(pc.red(`lint validation failed for "${filename}"`)));
|
|
185
|
+
|
|
186
|
+
for (const issue of issues) {
|
|
187
|
+
console.log(`- ${issue.message} at .${issue.path.join('.')}`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
|
|
185
193
|
documents.push(result.value);
|
|
186
194
|
}
|
|
187
195
|
|
|
@@ -196,7 +204,7 @@ if (result.type === 'generate') {
|
|
|
196
204
|
},
|
|
197
205
|
});
|
|
198
206
|
|
|
199
|
-
const outdir = path.join(
|
|
207
|
+
const outdir = path.join(config.root, config.outdir);
|
|
200
208
|
|
|
201
209
|
for (const file of generationResult.files) {
|
|
202
210
|
const filename = path.join(outdir, file.filename);
|
|
@@ -205,4 +213,7 @@ if (result.type === 'generate') {
|
|
|
205
213
|
await fs.mkdir(dirname, { recursive: true });
|
|
206
214
|
await fs.writeFile(filename, file.code);
|
|
207
215
|
}
|
|
216
|
+
} else if (result.type === 'pull') {
|
|
217
|
+
const config = await loadConfig(result.config);
|
|
218
|
+
await runPull(config);
|
|
208
219
|
}
|
package/src/codegen.ts
CHANGED
|
@@ -3,12 +3,9 @@ import { dirname as getDirname, relative as getRelativePath } from 'node:path/po
|
|
|
3
3
|
import * as prettier from 'prettier';
|
|
4
4
|
|
|
5
5
|
import type {
|
|
6
|
-
|
|
7
|
-
LexBlob,
|
|
6
|
+
LexDefinableField,
|
|
8
7
|
LexiconDoc,
|
|
9
|
-
LexIpldType,
|
|
10
8
|
LexObject,
|
|
11
|
-
LexPrimitive,
|
|
12
9
|
LexRecord,
|
|
13
10
|
LexRefVariant,
|
|
14
11
|
LexUnknown,
|
|
@@ -177,6 +174,10 @@ export const generateLexiconApi = async (opts: LexiconApiOptions): Promise<Lexic
|
|
|
177
174
|
result = `${PURE} v.literal(${lit(stripMainHash(defUri))})`;
|
|
178
175
|
break;
|
|
179
176
|
}
|
|
177
|
+
case 'permission-set': {
|
|
178
|
+
// skip permission sets
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
180
181
|
default: {
|
|
181
182
|
result = generateType(imports, defUri, def);
|
|
182
183
|
break;
|
|
@@ -248,11 +249,7 @@ export const generateLexiconApi = async (opts: LexiconApiOptions): Promise<Lexic
|
|
|
248
249
|
}
|
|
249
250
|
|
|
250
251
|
if (def.message?.schema) {
|
|
251
|
-
|
|
252
|
-
file.sinterfaces += `export interface $message extends v.InferInput<${camelcased}Schema['message']> {}\n`;
|
|
253
|
-
} else {
|
|
254
|
-
file.sinterfaces += `export type $message = v.InferInput<${camelcased}Schema['message']>;\n`;
|
|
255
|
-
}
|
|
252
|
+
file.sinterfaces += `export type $message = v.InferInput<${camelcased}Schema['message']>;\n`;
|
|
256
253
|
}
|
|
257
254
|
|
|
258
255
|
break;
|
|
@@ -400,15 +397,9 @@ const generateXrpcSubscription = (imports: ImportSet, defUri: string, spec: LexX
|
|
|
400
397
|
inner += `"params": ${params},`;
|
|
401
398
|
|
|
402
399
|
if (schema) {
|
|
403
|
-
|
|
404
|
-
const res = generateObject(imports, defUri, schema, 'none');
|
|
400
|
+
const res = generateType(imports, defUri, schema);
|
|
405
401
|
|
|
406
|
-
|
|
407
|
-
} else {
|
|
408
|
-
const res = generateType(imports, defUri, schema);
|
|
409
|
-
|
|
410
|
-
inner += `get "message" () { return ${res} },`;
|
|
411
|
-
}
|
|
402
|
+
inner += `get "message" () { return ${res} },`;
|
|
412
403
|
} else {
|
|
413
404
|
inner += `"message": null,`;
|
|
414
405
|
}
|
|
@@ -687,12 +678,7 @@ const generateJsdocField = (spec: LexUserType | LexRefVariant | LexUnknown) => {
|
|
|
687
678
|
return res;
|
|
688
679
|
};
|
|
689
680
|
|
|
690
|
-
const generateType = (
|
|
691
|
-
imports: ImportSet,
|
|
692
|
-
defUri: string,
|
|
693
|
-
spec: LexArray | LexPrimitive | LexIpldType | LexRefVariant | LexBlob,
|
|
694
|
-
lazy = false,
|
|
695
|
-
): string => {
|
|
681
|
+
const generateType = (imports: ImportSet, defUri: string, spec: LexDefinableField, lazy = false): string => {
|
|
696
682
|
switch (spec.type) {
|
|
697
683
|
// LexRefVariant
|
|
698
684
|
case 'ref': {
|
|
@@ -933,9 +919,7 @@ const generateType = (
|
|
|
933
919
|
}
|
|
934
920
|
};
|
|
935
921
|
|
|
936
|
-
const isRefVariant = (
|
|
937
|
-
spec: LexArray | LexPrimitive | LexIpldType | LexRefVariant | LexBlob,
|
|
938
|
-
): spec is LexRefVariant => {
|
|
922
|
+
const isRefVariant = (spec: LexDefinableField): spec is LexRefVariant => {
|
|
939
923
|
const type = spec.type;
|
|
940
924
|
return type === 'ref' || type === 'union';
|
|
941
925
|
};
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
import * as url from 'node:url';
|
|
3
|
+
|
|
4
|
+
import * as v from '@badrap/valita';
|
|
5
|
+
import pc from 'picocolors';
|
|
6
|
+
|
|
7
|
+
import { isNsid } from '@atcute/lexicons/syntax';
|
|
8
|
+
|
|
9
|
+
import type { ImportMapping } from './codegen.js';
|
|
10
|
+
|
|
11
|
+
const gitSourceConfigSchema = v.object({
|
|
12
|
+
type: v.literal('git'),
|
|
13
|
+
remote: v.string().assert((value) => value.length > 0, `must not be empty`),
|
|
14
|
+
ref: v
|
|
15
|
+
.string()
|
|
16
|
+
.assert((value) => value.length > 0, `must not be empty`)
|
|
17
|
+
.optional(),
|
|
18
|
+
pattern: v
|
|
19
|
+
.array(v.string().assert((value) => value.length > 0, `must not be empty`))
|
|
20
|
+
.assert((value) => value.length > 0, `must include at least one glob pattern`),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const sourceConfigSchema = v.union(gitSourceConfigSchema);
|
|
24
|
+
|
|
25
|
+
const pullConfigSchema = v.object({
|
|
26
|
+
outdir: v.string().assert((value) => value.length > 0, `must not be empty`),
|
|
27
|
+
clean: v.boolean().optional(),
|
|
28
|
+
sources: v
|
|
29
|
+
.array(sourceConfigSchema)
|
|
30
|
+
.assert((value) => value.length > 0, `must include at least one source`),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export type GitSourceConfig = v.Infer<typeof gitSourceConfigSchema>;
|
|
34
|
+
export type SourceConfig = v.Infer<typeof sourceConfigSchema>;
|
|
35
|
+
export type PullConfig = v.Infer<typeof pullConfigSchema>;
|
|
36
|
+
|
|
37
|
+
const isValidLexiconPattern = (pattern: string): boolean => {
|
|
38
|
+
if (pattern.endsWith('.*')) {
|
|
39
|
+
return isNsid(`${pattern.slice(0, -2)}.x`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return isNsid(pattern);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const mappingImports: v.Type<ImportMapping['imports']> = v.unknown().chain((value) => {
|
|
46
|
+
if (typeof value === 'string') {
|
|
47
|
+
if (value.length === 0) {
|
|
48
|
+
return v.err('imports must not be empty');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return v.ok(value);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (typeof value === 'function') {
|
|
55
|
+
return v.ok(value as ImportMapping['imports']);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return v.err('imports must be a string or function');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const importMappingSchema: v.Type<ImportMapping> = v.object({
|
|
62
|
+
nsid: v
|
|
63
|
+
.array(
|
|
64
|
+
v.string().chain((value) => {
|
|
65
|
+
if (!isValidLexiconPattern(value)) {
|
|
66
|
+
return v.err(`invalid NSID pattern (must be valid NSID or end with .*)`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return v.ok(value);
|
|
70
|
+
}),
|
|
71
|
+
)
|
|
72
|
+
.assert((patterns) => patterns.length > 0, `nsid requires at least one pattern`),
|
|
73
|
+
imports: mappingImports,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
export const lexiconConfigSchema = v.object({
|
|
77
|
+
outdir: v.string().assert((value) => value.length > 0, `must not be empty`),
|
|
78
|
+
files: v
|
|
79
|
+
.array(v.string().assert((value) => value.length > 0, `must not be empty`))
|
|
80
|
+
.assert((value) => value.length > 0, `must include at least one glob pattern`),
|
|
81
|
+
imports: v.array(v.string().assert((value) => value.length > 0, `must not be empty`)).optional(),
|
|
82
|
+
mappings: v.array(importMappingSchema).optional(),
|
|
83
|
+
modules: v
|
|
84
|
+
.object({
|
|
85
|
+
importSuffix: v
|
|
86
|
+
.string()
|
|
87
|
+
.assert((value) => value.length > 0, `must not be empty`)
|
|
88
|
+
.optional(),
|
|
89
|
+
})
|
|
90
|
+
.partial()
|
|
91
|
+
.optional(),
|
|
92
|
+
pull: pullConfigSchema.optional(),
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
export type LexiconConfig = v.Infer<typeof lexiconConfigSchema>;
|
|
96
|
+
|
|
97
|
+
export interface NormalizedConfig extends LexiconConfig {
|
|
98
|
+
root: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export const loadConfig = async (configPath: string): Promise<NormalizedConfig> => {
|
|
102
|
+
const configFilename = path.resolve(configPath);
|
|
103
|
+
const configDirname = path.dirname(configFilename);
|
|
104
|
+
|
|
105
|
+
let rawConfig: unknown;
|
|
106
|
+
try {
|
|
107
|
+
const configURL = url.pathToFileURL(configFilename);
|
|
108
|
+
const configMod = (await import(configURL.href)) as { default: unknown };
|
|
109
|
+
rawConfig = configMod.default;
|
|
110
|
+
} catch (err) {
|
|
111
|
+
console.error(pc.bold(pc.red(`failed to import config:`)));
|
|
112
|
+
console.error(err);
|
|
113
|
+
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const configResult = lexiconConfigSchema.try(rawConfig, { mode: 'passthrough' });
|
|
118
|
+
if (!configResult.ok) {
|
|
119
|
+
console.error(pc.bold(pc.red(`invalid config:`)));
|
|
120
|
+
console.error(configResult.message);
|
|
121
|
+
|
|
122
|
+
for (const issue of configResult.issues) {
|
|
123
|
+
console.log(`- ${issue.code} at .${issue.path.join('.')}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return { ...configResult.value, root: configDirname };
|
|
130
|
+
};
|