@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/dist/index.d.ts CHANGED
@@ -1,12 +1,4 @@
1
- import type { ImportMapping } from './codegen.js';
2
- export interface LexiconConfig {
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
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElD,MAAM,WAAW,aAAa;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,CAAC,EAAE,aAAa,EAAE,CAAC;IAC3B,OAAO,CAAC,EAAE;QACT,YAAY,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;CACF;AAED,eAAO,MAAM,mBAAmB,GAAI,QAAQ,aAAa,KAAG,aAE3D,CAAC"}
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
@@ -1,4 +1,5 @@
1
+ import { lexiconConfigSchema } from './config.js';
1
2
  export const defineLexiconConfig = (config) => {
2
- return config;
3
+ return lexiconConfigSchema.parse(config);
3
4
  };
4
5
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAYA,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,MAAqB,EAAiB,EAAE;IAC3E,OAAO,MAAM,CAAC;AACf,CAAC,CAAC"}
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
@@ -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.3.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": "^1.2.0"
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.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
- import * as url from 'node:url';
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 type { LexiconConfig } from './index.js';
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 = command(
121
- 'generate',
122
- object({
123
- type: constant('generate'),
124
- config: option('-c', '--config', pathParser({ metavar: 'CONFIG' })),
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 configFilename = path.resolve(result.config);
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, configDirname) : [];
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: configDirname })) {
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(configDirname, filename), 'utf8');
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(configDirname, config.outdir);
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
- LexArray,
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
- if (def.message?.schema.type === 'object') {
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
- if (schema.type === 'object') {
404
- const res = generateObject(imports, defUri, schema, 'none');
400
+ const res = generateType(imports, defUri, schema);
405
401
 
406
- inner += `"message": ${res},`;
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
+ };