@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
JavaScript
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
};