UNPKG

@query-key-gen/used-generator

Version:

Vite plugin that scans your project and tracks where `queryKey` values from `globalQueryKeys` are used — useful for dead query analysis, usage stats, or documentation.

347 lines (336 loc) 11.2 kB
var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; var __getOwnPropDescs = Object.getOwnPropertyDescriptors; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); var __async = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { step(generator.next(value)); } catch (e) { reject(e); } }; var rejected = (value) => { try { step(generator.throw(value)); } catch (e) { reject(e); } }; var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); step((generator = generator.apply(__this, __arguments)).next()); }); }; // src/index.ts import path4 from "path"; // ../common/src/core/Programmer.ts import fg from "fast-glob"; import path from "path"; import ts from "typescript"; var Programmer = class { constructor(config) { // getCompilerOptions = () => { // const { options: compilerOptions } = ts.parseJsonConfigFileContent( // ts.readConfigFile('tsconfig.node.json', ts.sys.readFile).config, // { // fileExists: ts.sys.fileExists, // readFile: ts.sys.readFile, // readDirectory: ts.sys.readDirectory, // useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames // }, // path.dirname('.') // ); // return compilerOptions; // }; this.findFiles = (program) => __async(this, null, function* () { const config = this.config; const sourceFiles = program.getSourceFiles().filter((item) => { return !item.isDeclarationFile; }); const outputFile = ts.createSourceFile(config.output, "", this.compilerOptions.target); const projectFiles = sourceFiles.filter((item) => { const relativePath = path.relative(process.cwd(), item.fileName); const outputPath = path.relative(process.cwd(), config.output); return !relativePath.includes(outputPath); }); return { outputFile, projectFiles }; }); this.config = config; this.compilerOptions = { target: ts.ScriptTarget.ES2015 }; } // Getter로 createProgram 정의 get create() { return () => __async(this, null, function* () { const files = yield fg(this.config.path, { onlyFiles: true }); const program = ts.createProgram({ rootNames: files, options: this.compilerOptions }); return { program, checker: program.getTypeChecker() }; }); } }; // ../common/src/core/GenericRunner.ts var GenericRunner = class { constructor(programmer, config) { this.programmer = programmer; this.config = config; } process() { return __async(this, null, function* () { console.log("\u{1F527} Base process logic"); }); } execute() { this.process(); } }; // src/core/QueryKeyUsedInfoGenerator.ts import fs2 from "fs"; import ts3 from "typescript"; // src/queryFinder/QueryKeyUsedFinder.ts import ts2 from "typescript"; var QueryKeyUsedFinder; ((QueryKeyUsedFinder2) => { QueryKeyUsedFinder2.find = (sourceFiles, globalQueryKeyName) => { const globalQueryKeys = sourceFiles.map((sourceFile) => { return findGlobalQueryKeys(sourceFile, globalQueryKeyName); }); const queryKeyByDeclaration = globalQueryKeys.flatMap((keys) => { const usedDeclarations = keys.findNode.map((key) => { const declaration = findUsedDeclaration(key.node); return __spreadProps(__spreadValues({}, key), { declaration, sourceFile: keys.sourceFile }); }); return usedDeclarations; }); return queryKeyByDeclaration; }; const findGlobalQueryKeys = (node, globalQueryKeyName) => { const findNode = []; const findText = [globalQueryKeyName]; const visitor = (node2) => { if (ts2.isImportDeclaration(node2)) { return; } if (ts2.isCallExpression(node2) || ts2.isIdentifier(node2)) { if (ts2.isIdentifier(node2) && findText.includes(node2.getText())) { const grandParent = node2.parent.parent; if (ts2.isPropertyAccessExpression(node2.parent)) { const isGlobalQueryKey = findText.some( (text) => grandParent.getText().includes(text) ); findNode.push({ node: node2, parent: isGlobalQueryKey ? grandParent : null }); } } } ts2.forEachChild(node2, visitor); }; visitor(node); return { findNode, sourceFile: node }; }; const findUsedDeclaration = (node) => { let parentNode = node.parent; let declaration = null; while (parentNode) { if (ts2.isFunctionDeclaration(parentNode)) { declaration = parentNode; break; } if (ts2.isVariableDeclaration(parentNode) && parentNode.initializer && ts2.isArrowFunction(parentNode.initializer)) { declaration = parentNode; break; } parentNode = parentNode.parent; } return declaration; }; })(QueryKeyUsedFinder || (QueryKeyUsedFinder = {})); // src/utils/index.ts import { execSync } from "child_process"; import fs from "fs"; import path2 from "path"; var prettify = (output) => { const prettier = path2.resolve("./node_modules/.bin/prettier"); if (fs.existsSync(prettier)) execSync(`${prettier} --write --cache ${output}`); }; var logger = (() => { const prefix = "[QUERY-KEY-USED-GENERATOR]: "; const log = () => { return { warn: (msg) => { console.warn(`[WARN]${prefix}${msg}`); }, error: (msg) => { console.error(`[ERROR]${prefix}${msg}`); }, info: (msg) => { console.log(`[INFO]${prefix}${msg}`); } }; }; return log(); })(); // src/core/QueryKeyUsedInfoGenerator.ts import path3 from "path"; var QueryKeyUsedInfoGenerator = class extends GenericRunner { constructor() { super(...arguments); this.process = () => __async(this, null, function* () { const { program } = yield this.programmer.create(); const { projectFiles } = yield this.programmer.findFiles(program); const queryKeyUsedInfo = QueryKeyUsedFinder.find( projectFiles, this.config.globalQueryKeyName ); const result = this.makeQueryKeyUsedInfo(queryKeyUsedInfo); this.writeInfo(result); prettify(this.config.output); }); this.makeQueryKeyUsedInfo = (queryKeyUsedInfo) => { const result = queryKeyUsedInfo.map((item) => { var _a, _b; const { declaration, sourceFile, parent } = item; const declarationName = function() { var _a2; if (!declaration) { return; } if (ts3.isVariableDeclaration(declaration)) { return declaration.name.getText(); } if (ts3.isFunctionDeclaration(declaration)) { return (_a2 = declaration.name) == null ? void 0 : _a2.getText(); } return; }(); const fileName = path3.relative(process.cwd(), sourceFile.fileName).replace("src/", ""); return { sourceFile: { name: fileName }, ["query-key"]: { name: parent == null ? void 0 : parent.getText(), pos: parent == null ? void 0 : parent.pos, end: parent == null ? void 0 : parent.end }, func: { name: declarationName, pos: (_a = declaration == null ? void 0 : declaration.pos) != null ? _a : 0, end: (_b = declaration == null ? void 0 : declaration.end) != null ? _b : 0 } }; }); return result; }; this.writeType = () => { return ` interface QueryKeyUsedInfo { sourceFile: { name: string; }; ["query-key"]: { name: string; pos: number; end: number; }; func: { name: string; pos: number; end: number; }; } `; }; this.writeInfo = (result) => { const content = ` ${this.writeType()} export const queryKeyUsedInfo : QueryKeyUsedInfo[] = ${JSON.stringify(result)}; `; fs2.writeFileSync(this.config.output, content); }; } }; // src/types/config.ts import { z } from "zod"; var configSchema = z.object({ output: z.string().optional().catch("src/query-key-used-info.ts"), path: z.string().optional().catch("./src/**/*.{jsx,tsx,ts}"), globalQueryKeyName: z.string().optional().catch("globalQueryKeys"), ignoreFiles: z.array(z.string()).optional().catch([]) }); var defaultConfig = { output: "./src/query-key-used-info.ts", path: "./src/**/*.{jsx,tsx,ts}", ignoreFiles: [".d.ts", "queryKeys.ts"], globalQueryKeyName: "globalQueryKeys" }; // src/index.ts function QueryKeyUsedPlugin(_config) { var _a; const config = configSchema.parse(__spreadProps(__spreadValues(__spreadValues({}, defaultConfig), _config != null ? _config : {}), { ignoreFiles: [...defaultConfig.ignoreFiles, ...(_a = _config == null ? void 0 : _config.ignoreFiles) != null ? _a : []] })); const rootDir = process.cwd(); const ignoreFiles = config.ignoreFiles; const program = new Programmer(config); const generator = new QueryKeyUsedInfoGenerator(program, config); return { name: "query-key-used-generator", enforce: "pre", configureServer(server) { const listener = (absolutePath = "") => { const filePath = path4.relative(rootDir, absolutePath); const outputPath = path4.relative(rootDir, config.output); if (!filePath.startsWith("src") || filePath.startsWith(outputPath)) return; if (ignoreFiles.some((item) => { const ignorePath = path4.relative(process.cwd(), item); return filePath.includes(ignorePath); })) { return; } generator.execute(); }; server.watcher.on("add", listener); server.watcher.on("change", listener); server.watcher.on("unlink", listener); }, buildStart() { generator.execute(); } }; } export { QueryKeyUsedPlugin as default };