import { createRequire } from "node:module"; import { formatSourceFromFile } from "format-imports"; import { parsers as babelParsers } from "prettier/plugins/babel"; /** * @file Prettier import plugin. * * @import { Plugin, ParserOptions } from "prettier"; */ import { parsers as typescriptParsers } from "prettier/plugins/typescript"; const require = createRequire(process.cwd() + "/"); /** * @param {string} name * @returns {string | null} */ function resolveModule(name) { try { return require.resolve(name); } catch (error) { return null; } } const webSubmodules = [ // --- "common", "elements", "components", "user", "admin", "flow", ]; /** * Ensure that every import without an extension adds one. * @param {string} input * @returns {string} */ function normalizeExtensions(input) { return input.replace(/(?:import|from)\s*["']((?:\.\.?\/).*?)(? { return line.replace(path, `${path}.js`); }); } /** * @param {string} filepath * @param {string} input * @returns {string} */ function normalizeImports(filepath, input) { let output = input; // Replace all TypeScript imports with the paths resolved by Node/Browser import maps. for (const submodule of webSubmodules) { const legacyPattern = new RegExp( [ // --- `(?:import|from)`, `\\\(?\\n?\\s*`, `"(?@goauthentik\/${submodule}\/)`, `(?[^"'.]+)`, `(?:\.[^"']+)?["']`, `\\n?\\s*\\\)?;`, ].join(""), "gm", ); output = output.replace( legacyPattern, /** * @param {string} line * @param {string} suffix * @param {string} path */ (line, suffix, path) => { const exported = `@goauthentik/web/${submodule}/${path}`; let imported = `#${submodule}/${path}`; let module = resolveModule(`${exported}.ts`); if (!module) { module = resolveModule(`${exported}/index.ts`); imported += "/index"; } if (imported.endsWith(".css")) { imported += ".js"; } if (!module) { console.warn(`\nCannot resolve module ${exported} from ${filepath}`, { line, path, exported, imported, module, }); process.exit(1); } return ( line // --- .replace(suffix + path, imported) .replace(`${imported}.js`, imported) ); }, ); } return output; } /** * @returns {Plugin} */ export function importsPlugin({ useLegacyCleanup = process.env.AK_FIX_LEGACY_IMPORTS === "true", } = {}) { /** * @param {string} input * @param {ParserOptions} options */ const preprocess = (input, { filepath, printWidth }) => { let output = input; if (useLegacyCleanup) { output = normalizeExtensions(input); output = normalizeImports(filepath, output); } const value = formatSourceFromFile.sync(output, filepath, { nodeProtocol: "always", maxLineLength: printWidth, wrappingStyle: "prettier", groupRules: [ "^node:", ...webSubmodules.map((submodule) => `^(@goauthentik/|#)${submodule}.+`), "^#.+", "^@goauthentik.+", {}, // Other imports. "^(@?)lit(.*)$", "\\.css$", "^@goauthentik/api$", "^[./]", ], }); return value || input; }; return { parsers: { typescript: { ...typescriptParsers.typescript, preprocess, }, babel: { ...babelParsers.babel, preprocess, }, }, }; }