@glint/core
Version:
A CLI for performing typechecking on Glimmer templates
110 lines • 4.19 kB
JavaScript
import { createRequire } from 'node:module';
import * as path from 'node:path';
import * as fs from 'node:fs';
import SilentError from 'silent-error';
import { GlintConfig } from './config.js';
/**
* @private
*
* Only exported for testing purposes. Do not import.
*/
export const require = createRequire(import.meta.url);
/**
* `ConfigLoader` provides an interface for finding the Glint config that
* applies to a given file or directory, ensuring that only a single instance
* of `GlintConfig` is ever created for a given `tsconfig.json` or
* `jsconfig.json` source file.
*/
export class ConfigLoader {
constructor() {
this.configs = new Map();
}
configForFile(filePath) {
return this.configForDirectory(path.dirname(filePath));
}
configForDirectory(directory) {
let ts = findTypeScript(directory);
if (!ts)
return null;
let configPath = findNearestConfigFile(ts, directory);
if (!configPath)
return null;
let existing = this.configs.get(configPath);
if (existing !== undefined)
return existing;
let configInput = loadConfigInput(ts, configPath);
let config = configInput ? new GlintConfig(ts, configPath, configInput) : null;
this.configs.set(configPath, config);
return config;
}
}
export function findTypeScript(fromDir) {
let requireFrom = path.resolve(fromDir, 'package.json');
return (tryResolve(() => createRequire(requireFrom)('typescript')) ??
tryResolve(() => require('typescript')));
}
function tryResolve(load) {
try {
return load();
}
catch (error) {
if (error?.code === 'MODULE_NOT_FOUND') {
return null;
}
throw error;
}
}
function loadConfigInput(ts, entryPath) {
let fullGlintConfig = {};
let currentPath = entryPath;
while (currentPath) {
let currentContents = ts.readConfigFile(currentPath, ts.sys.readFile).config;
let currentGlintConfig = currentContents.glint ?? {};
assert(currentPath === entryPath || !currentGlintConfig.transform, 'Glint `transform` options may not be specified in extended config.');
fullGlintConfig = { ...currentGlintConfig, ...fullGlintConfig };
if (currentContents.extends) {
currentPath = path.resolve(path.dirname(currentPath), currentContents.extends);
if (!fs.existsSync(currentPath)) {
try {
currentPath = require.resolve(currentContents.extends);
}
catch (error) {
// control the exception, do nothing.
}
}
}
else {
currentPath = undefined;
}
}
return validateConfigInput(fullGlintConfig);
}
function findNearestConfigFile(ts, searchFrom) {
// Assume that the longest path is the most relevant one in the case that
// multiple config files exist at or above our current directory.
let configCandidates = [
ts.findConfigFile(searchFrom, ts.sys.fileExists, 'tsconfig.json'),
ts.findConfigFile(searchFrom, ts.sys.fileExists, 'jsconfig.json'),
]
.filter((path) => typeof path === 'string')
.sort((a, b) => b.length - a.length);
return configCandidates[0];
}
function validateConfigInput(input) {
if (!input['environment'])
return null;
assert(Array.isArray(input['environment'])
? input['environment'].every((env) => typeof env === 'string')
: typeof input['environment'] === 'string' ||
(typeof input['environment'] === 'object' && input['environment']), 'Glint config must specify an `environment` that is a string, array of strings, or an object ' +
'mapping environment names to their config.');
assert(input['checkStandaloneTemplates'] === undefined ||
typeof input['checkStandaloneTemplates'] === 'boolean', 'If defined, `checkStandaloneTemplates` must be a boolean');
return input;
}
function assert(test, message) {
if (!test) {
throw new SilentError(`Glint config: ${message}`);
}
}
//# sourceMappingURL=loader.js.map