Skip to content

Commit 157f586

Browse files
authored
Refactor CLI import module helpers (#772)
This PR refactors some of the import module helpers.
1 parent a11fcdc commit 157f586

File tree

6 files changed

+53
-45
lines changed

6 files changed

+53
-45
lines changed

packages/cli/src/commands/init.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ async function isAnchorIdl(idlPath: string): Promise<boolean> {
203203
const resolvedIdlPath = resolveRelativePath(idlPath);
204204
if (!(await canRead(resolvedIdlPath))) return false;
205205
try {
206-
const idlContent = await importModuleItem('IDL', resolvedIdlPath);
206+
const idlContent = await importModuleItem({ identifier: 'IDL', from: resolvedIdlPath });
207207
return !isRootNode(idlContent);
208208
} catch {
209209
return false;

packages/cli/src/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export async function getConfig(options: Pick<ProgramOptions, 'config'>): Promis
2828
return [{}, configPath];
2929
}
3030

31-
const configFile = await importModuleItem('config file', configPath);
31+
const configFile = await importModuleItem({ identifier: 'config file', from: configPath });
3232
if (!configFile || typeof configFile !== 'object') {
3333
throw new Error(`Invalid config file at "${configPath}"`);
3434
}

packages/cli/src/parsedConfig.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ async function parseConfig(
4444
options: Pick<ProgramOptions, 'idl'>,
4545
): Promise<ParsedConfig> {
4646
const idlPath = parseIdlPath(config, configPath, options);
47-
const idlContent = await importModuleItem('IDL', idlPath);
47+
const idlContent = await importModuleItem({ identifier: 'IDL', from: idlPath });
4848
const rootNode = await getRootNodeFromIdl(idlContent);
4949
const scripts = parseScripts(config.scripts ?? {}, configPath);
5050
const visitors = (config.before ?? []).map((v, i) => parseVisitorConfig(v, configPath, i, null));

packages/cli/src/utils/import.ts

Lines changed: 45 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,80 +2,89 @@ import { createRequire } from 'node:module';
22

33
import { canRead, isLocalModulePath, resolveRelativePath } from './fs';
44

5-
export async function importModuleItem<T = unknown>(
6-
identifier: string,
7-
modulePath: string,
8-
itemName: string = 'default',
9-
): Promise<T> {
10-
const module = await importModule(identifier, modulePath);
11-
const item = pickModuleItem(module, itemName) as T | undefined;
12-
if (item === undefined) {
13-
throw new Error(`Failed to import "${itemName}" from ${identifier} at "${modulePath}".`);
5+
type ImportModuleItemOptions = {
6+
from: string;
7+
identifier?: string;
8+
item?: string;
9+
};
10+
11+
export async function importModuleItem<T = unknown>(options: ImportModuleItemOptions): Promise<T> {
12+
const module = await importModule(options);
13+
const moduleItem = pickModuleItem(module, options.item) as T | undefined;
14+
if (moduleItem === undefined) {
15+
const moduleInfo = getModuleInfo(options);
16+
throw new Error(`Failed to ${moduleInfo}.`);
1417
}
15-
return item;
18+
return moduleItem;
1619
}
1720

1821
type ModuleDefinition = Partial<Record<string, unknown>> & {
1922
__esModule?: boolean;
2023
default?: Partial<Record<string, unknown>> & { default?: Partial<Record<string, unknown>> };
2124
};
2225

23-
function pickModuleItem(module: ModuleDefinition, itemName: string): unknown {
24-
if (itemName === 'default') {
26+
function pickModuleItem(module: ModuleDefinition, item: string = 'default'): unknown {
27+
if (item === 'default') {
2528
return module.default?.default ?? module.default ?? module;
2629
}
27-
return module[itemName] ?? module.default?.[itemName] ?? module.default?.default?.[itemName];
30+
return module[item] ?? module.default?.[item] ?? module.default?.default?.[item];
2831
}
2932

30-
async function importModule<T extends object>(identifier: string, modulePath: string): Promise<T> {
31-
if (isLocalModulePath(modulePath)) {
32-
return await importLocalModule(identifier, modulePath);
33+
async function importModule<T extends object>(options: ImportModuleItemOptions): Promise<T> {
34+
if (isLocalModulePath(options.from)) {
35+
return await importLocalModule(options);
3336
}
3437

3538
try {
36-
return await importExternalUserModule(identifier, modulePath);
39+
return await importExternalUserModule(options);
3740
} catch {
38-
return await importExternalModule(identifier, modulePath);
41+
return await importExternalModule(options);
3942
}
4043
}
4144

42-
async function importLocalModule<T extends object>(identifier: string, modulePath: string): Promise<T> {
43-
if (!(await canRead(modulePath))) {
44-
throw new Error(`Cannot access ${identifier} at "${modulePath}"`);
45+
async function importLocalModule<T extends object>(options: ImportModuleItemOptions): Promise<T> {
46+
const { identifier, from } = options;
47+
if (!(await canRead(from))) {
48+
throw new Error(`Cannot access ${identifier ?? 'module'} at "${from}"`);
4549
}
4650

47-
const dotIndex = modulePath.lastIndexOf('.');
48-
const extension = dotIndex === -1 ? undefined : modulePath.slice(dotIndex);
49-
const modulePromise = extension === '.json' ? import(modulePath, { with: { type: 'json' } }) : import(modulePath);
50-
return await handleImportPromise(modulePromise, identifier, modulePath);
51+
const dotIndex = from.lastIndexOf('.');
52+
const extension = dotIndex === -1 ? undefined : from.slice(dotIndex);
53+
const modulePromise = extension === '.json' ? import(from, { with: { type: 'json' } }) : import(from);
54+
return await handleImportPromise(modulePromise, options);
5155
}
5256

53-
async function importExternalModule<T extends object>(identifier: string, modulePath: string): Promise<T> {
54-
return await handleImportPromise(import(modulePath), identifier, modulePath);
57+
async function importExternalModule<T extends object>(options: ImportModuleItemOptions): Promise<T> {
58+
return await handleImportPromise(import(options.from), options);
5559
}
5660

57-
async function importExternalUserModule<T extends object>(identifier: string, modulePath: string): Promise<T> {
61+
async function importExternalUserModule<T extends object>(options: ImportModuleItemOptions): Promise<T> {
5862
const userPackageJsonPath = resolveRelativePath('package.json');
5963
const userRequire = createRequire(userPackageJsonPath);
60-
const userModulePath = userRequire.resolve(modulePath);
61-
return await importExternalModule<T>(identifier, userModulePath);
64+
const userFrom = userRequire.resolve(options.from);
65+
return await importExternalModule<T>({ ...options, from: userFrom });
6266
}
6367

6468
async function handleImportPromise<T extends object>(
6569
importPromise: Promise<unknown>,
66-
identifier: string,
67-
modulePath: string,
70+
options: ImportModuleItemOptions,
6871
): Promise<T> {
6972
try {
7073
return (await importPromise) as T;
7174
} catch (error) {
75+
const moduleInfo = getModuleInfo(options);
7276
let causeMessage =
7377
!!error && typeof error === 'object' && 'message' in error && typeof error.message === 'string'
7478
? (error as { message: string }).message
7579
: undefined;
76-
causeMessage = causeMessage ? ` (caused by: ${causeMessage})` : '';
77-
throw new Error(`Failed to import ${identifier} at "${modulePath}" as a module${causeMessage}`, {
78-
cause: error,
79-
});
80+
causeMessage = causeMessage ? `\n(caused by: ${causeMessage})` : '';
81+
throw new Error(`Failed to ${moduleInfo}.${causeMessage}`, { cause: error });
8082
}
8183
}
84+
85+
function getModuleInfo(options: ImportModuleItemOptions): string {
86+
const { identifier, from, item } = options;
87+
const importStatement = item ? `import { ${item} } from '${from}'` : `import default from '${from}'`;
88+
if (!identifier) return importStatement;
89+
return `import ${identifier} [${importStatement}]`;
90+
}

packages/cli/src/utils/nodes.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,10 @@ export async function getRootNodeFromIdl(idl: unknown): Promise<RootNode> {
1919
throw new Error('Cannot proceed without Anchor IDL support. Install cancelled by user.');
2020
}
2121

22-
const rootNodeFromAnchor = await importModuleItem<(idl: unknown) => RootNode>(
23-
'Anchor adapter',
24-
'@codama/nodes-from-anchor',
25-
'rootNodeFromAnchor',
26-
);
22+
const rootNodeFromAnchor = await importModuleItem<(idl: unknown) => RootNode>({
23+
from: '@codama/nodes-from-anchor',
24+
item: 'rootNodeFromAnchor',
25+
});
2726
return rootNodeFromAnchor(idl);
2827
}
2928

packages/cli/src/utils/visitors.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export async function getRootNodeVisitors(
1515
async function getRootNodeVisitor(visitorConfig: ParsedVisitorConfig): Promise<Visitor<RootNode, 'rootNode'>> {
1616
const { args, item, path } = visitorConfig;
1717
const identifier = getVisitorIdentifier(visitorConfig);
18-
const moduleItem = await importModuleItem(identifier, path, item);
18+
const moduleItem = await importModuleItem({ identifier, from: path, item });
1919
const visitor = await getVisitorFromModuleItem(identifier, moduleItem, args);
2020
return rootNodeVisitor(root => {
2121
const result = visit(root, visitor);

0 commit comments

Comments
 (0)