@blzsky/variant 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +177 -0
  3. package/dist/build-2LO7GD2S.js +63 -0
  4. package/dist/check-ZTKGGF4M.js +44 -0
  5. package/dist/chunk-3E5LBU3O.js +180 -0
  6. package/dist/chunk-4LLK2S4R.js +630 -0
  7. package/dist/chunk-6J4XDG44.js +14 -0
  8. package/dist/chunk-CXNXZLDG.js +13 -0
  9. package/dist/chunk-DRMASVYN.js +25 -0
  10. package/dist/chunk-GDSZFIHM.js +29 -0
  11. package/dist/chunk-ISRF5F3Q.js +68 -0
  12. package/dist/chunk-KSHWC2XN.js +78 -0
  13. package/dist/chunk-LJDDS4RR.js +91 -0
  14. package/dist/chunk-LXKLCLL6.js +91 -0
  15. package/dist/chunk-MZZOE7L4.js +26 -0
  16. package/dist/chunk-NSNZOH5P.js +31 -0
  17. package/dist/chunk-O4LZKXNZ.js +301 -0
  18. package/dist/chunk-RGD422OM.js +108 -0
  19. package/dist/chunk-RVXUCJYA.js +81 -0
  20. package/dist/chunk-SWPVR2DF.js +468 -0
  21. package/dist/chunk-V22IPJ7A.js +64 -0
  22. package/dist/chunk-V5CTGXMM.js +256 -0
  23. package/dist/chunk-VTJ67TT5.js +238 -0
  24. package/dist/chunk-XTYVVUWW.js +279 -0
  25. package/dist/chunk-XUBWCTJZ.js +100 -0
  26. package/dist/chunk-YYGCSKIQ.js +29 -0
  27. package/dist/cli.d.ts +1 -0
  28. package/dist/cli.js +225 -0
  29. package/dist/diff-GFI6RAEA.js +60 -0
  30. package/dist/doctor-GH4RZVEH.js +127 -0
  31. package/dist/env-QORDD6FQ.js +24 -0
  32. package/dist/impact-AF453PLK.js +702 -0
  33. package/dist/index.d.ts +56 -0
  34. package/dist/index.js +7 -0
  35. package/dist/init-KJIUNGAN.js +158 -0
  36. package/dist/insight-DZ5NQY6D.js +29 -0
  37. package/dist/pr-UFZRZX6S.js +152 -0
  38. package/dist/run-6PNH4QRA.js +40 -0
  39. package/dist/symbol-graph-YYUEMZGR.js +22 -0
  40. package/dist/tsconfig-paths-E3UEDGXZ.js +10 -0
  41. package/dist/workspace-X5FUBTY3.js +186 -0
  42. package/package.json +86 -0
@@ -0,0 +1,256 @@
1
+ // src/adapters/frameworks/generic.ts
2
+ var genericAdapter = {
3
+ name: "generic",
4
+ detect(_cwd) {
5
+ return true;
6
+ },
7
+ devCommand() {
8
+ return "dev";
9
+ },
10
+ buildCommand() {
11
+ return "build";
12
+ }
13
+ };
14
+
15
+ // src/adapters/frameworks/next.ts
16
+ import { readdir, readFile, stat } from "fs/promises";
17
+ import path from "path";
18
+ var nextAdapter = {
19
+ name: "next",
20
+ async detect(cwd) {
21
+ const pkgPath = path.join(cwd, "package.json");
22
+ try {
23
+ const json = JSON.parse(await readFile(pkgPath, "utf8"));
24
+ const deps = json["dependencies"];
25
+ const devDeps = json["devDependencies"];
26
+ const peerDeps = json["peerDependencies"];
27
+ return Boolean(deps?.["next"] || devDeps?.["next"] || peerDeps?.["next"]);
28
+ } catch {
29
+ return false;
30
+ }
31
+ },
32
+ devCommand: () => "next dev",
33
+ buildCommand: () => "next build"
34
+ };
35
+ var nextPlugin = {
36
+ name: "next",
37
+ hooks: {
38
+ async onDetect(ctx) {
39
+ const apps = await findNextApps(ctx.cwd);
40
+ for (const app of apps) {
41
+ const taskName = `${app}:build`;
42
+ if (!ctx.tasks[taskName]) {
43
+ ctx.tasks[taskName] = {
44
+ command: `${ctx.pm} --filter ${app} run build`,
45
+ inputs: [`apps/${app}/**/*.{ts,tsx,js,jsx,json,css}`]
46
+ };
47
+ }
48
+ }
49
+ }
50
+ }
51
+ };
52
+ async function findNextApps(cwd) {
53
+ const appsDir = path.join(cwd, "apps");
54
+ try {
55
+ const dirs = await readdir(appsDir, { withFileTypes: true });
56
+ const out = [];
57
+ for (const d of dirs) {
58
+ if (!d.isDirectory()) continue;
59
+ const cfg = path.join(appsDir, d.name, "next.config.js");
60
+ const cfgMjs = path.join(appsDir, d.name, "next.config.mjs");
61
+ if (await fileExists(cfg) || await fileExists(cfgMjs)) {
62
+ out.push(d.name);
63
+ }
64
+ }
65
+ return out;
66
+ } catch {
67
+ return [];
68
+ }
69
+ }
70
+ async function fileExists(p) {
71
+ try {
72
+ await stat(p);
73
+ return true;
74
+ } catch {
75
+ return false;
76
+ }
77
+ }
78
+
79
+ // src/adapters/frameworks/vite.ts
80
+ import { existsSync, readFileSync } from "fs";
81
+ import path2 from "path";
82
+ var viteAdapter = {
83
+ name: "vite",
84
+ detect(cwd) {
85
+ const pkgPath = path2.join(cwd, "package.json");
86
+ if (!existsSync(pkgPath)) return false;
87
+ try {
88
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
89
+ const deps = {
90
+ ...pkg["dependencies"],
91
+ ...pkg["devDependencies"]
92
+ };
93
+ return "vite" in deps;
94
+ } catch {
95
+ return false;
96
+ }
97
+ },
98
+ devCommand() {
99
+ return "vite";
100
+ },
101
+ buildCommand() {
102
+ return "vite build";
103
+ }
104
+ };
105
+
106
+ // src/core/detection/framework.ts
107
+ var FRAMEWORK_ADAPTERS = [nextAdapter, viteAdapter];
108
+ async function detectFramework(cwd) {
109
+ for (const adapter of FRAMEWORK_ADAPTERS) {
110
+ if (await Promise.resolve(adapter.detect(cwd))) return adapter;
111
+ }
112
+ return null;
113
+ }
114
+
115
+ // src/core/detection/packageManager.ts
116
+ import { existsSync as existsSync2 } from "fs";
117
+ import path3 from "path";
118
+
119
+ // src/adapters/pm/npm.ts
120
+ var npmAdapter = {
121
+ name: "npm",
122
+ async runScript(scriptName, cwd) {
123
+ const { execa } = await import("execa");
124
+ await execa("npm", ["run", scriptName], { cwd, stdio: "inherit" });
125
+ },
126
+ async install(cwd) {
127
+ const { execa } = await import("execa");
128
+ await execa("npm", ["install"], { cwd, stdio: "inherit" });
129
+ }
130
+ };
131
+
132
+ // src/adapters/pm/pnpm.ts
133
+ var pnpmAdapter = {
134
+ name: "pnpm",
135
+ async runScript(scriptName, cwd) {
136
+ const { execa } = await import("execa");
137
+ await execa("pnpm", ["run", scriptName], { cwd, stdio: "inherit" });
138
+ },
139
+ async install(cwd) {
140
+ const { execa } = await import("execa");
141
+ await execa("pnpm", ["install"], { cwd, stdio: "inherit" });
142
+ }
143
+ };
144
+
145
+ // src/adapters/pm/yarn.ts
146
+ var yarnAdapter = {
147
+ name: "yarn",
148
+ async runScript(scriptName, cwd) {
149
+ const { execa } = await import("execa");
150
+ await execa("yarn", ["run", scriptName], { cwd, stdio: "inherit" });
151
+ },
152
+ async install(cwd) {
153
+ const { execa } = await import("execa");
154
+ await execa("yarn", ["install"], { cwd, stdio: "inherit" });
155
+ }
156
+ };
157
+
158
+ // src/core/detection/packageManager.ts
159
+ function detectPackageManager(cwd) {
160
+ if (existsSync2(path3.join(cwd, "pnpm-lock.yaml"))) return pnpmAdapter;
161
+ if (existsSync2(path3.join(cwd, "pnpm-workspace.yaml"))) return pnpmAdapter;
162
+ if (existsSync2(path3.join(cwd, "yarn.lock"))) return yarnAdapter;
163
+ if (existsSync2(path3.join(cwd, "package-lock.json"))) return npmAdapter;
164
+ return npmAdapter;
165
+ }
166
+
167
+ // src/adapters/runtimes/probe.ts
168
+ import { execSync } from "child_process";
169
+ function createVersionProbe(binary, parse = (output) => output.trim()) {
170
+ let cached;
171
+ return () => {
172
+ if (cached === void 0) {
173
+ try {
174
+ cached = {
175
+ version: parse(
176
+ execSync(`${binary} --version`, { stdio: "pipe" }).toString()
177
+ )
178
+ };
179
+ } catch {
180
+ cached = { version: null };
181
+ }
182
+ }
183
+ return cached.version;
184
+ };
185
+ }
186
+
187
+ // src/adapters/runtimes/bun.ts
188
+ var probe = createVersionProbe("bun");
189
+ var bunAdapter = {
190
+ name: "bun",
191
+ available() {
192
+ return probe() !== null;
193
+ },
194
+ version() {
195
+ return probe();
196
+ }
197
+ };
198
+
199
+ // src/adapters/runtimes/deno.ts
200
+ var probe2 = createVersionProbe(
201
+ "deno",
202
+ (output) => output.trim().split("\n")[0]?.trim() ?? null
203
+ );
204
+ var denoAdapter = {
205
+ name: "deno",
206
+ available() {
207
+ return probe2() !== null;
208
+ },
209
+ version() {
210
+ return probe2();
211
+ }
212
+ };
213
+
214
+ // src/adapters/runtimes/node.ts
215
+ var nodeAdapter = {
216
+ name: "node",
217
+ // Both answers are already in this process. Spawning `node --version` to
218
+ // ask the running Node its own version cost ~8 ms for a value held in
219
+ // memory, and a PATH lookup could even have found a different Node than
220
+ // the one executing this code.
221
+ available() {
222
+ return typeof process.versions.node === "string";
223
+ },
224
+ version() {
225
+ return process.version;
226
+ }
227
+ };
228
+
229
+ // src/core/detection/runtime.ts
230
+ function detectRuntime() {
231
+ const g = globalThis;
232
+ if (typeof g["Bun"] !== "undefined") {
233
+ return bunAdapter;
234
+ }
235
+ if (typeof g["Deno"] !== "undefined") {
236
+ return denoAdapter;
237
+ }
238
+ return nodeAdapter;
239
+ }
240
+
241
+ // src/core/detection/project.ts
242
+ async function detectProject(cwd) {
243
+ return {
244
+ pm: detectPackageManager(cwd),
245
+ runtime: detectRuntime(),
246
+ framework: await detectFramework(cwd)
247
+ };
248
+ }
249
+
250
+ export {
251
+ genericAdapter,
252
+ nextAdapter,
253
+ nextPlugin,
254
+ viteAdapter,
255
+ detectProject
256
+ };
@@ -0,0 +1,238 @@
1
+ import {
2
+ GraphError
3
+ } from "./chunk-RVXUCJYA.js";
4
+ import {
5
+ collectExportedSurface
6
+ } from "./chunk-O4LZKXNZ.js";
7
+
8
+ // src/core/semantic/symbol-graph.ts
9
+ import { createHash } from "crypto";
10
+ import { mkdir, readFile, writeFile } from "fs/promises";
11
+ import path from "path";
12
+ var SYMBOL_GRAPH_VERSION = 1;
13
+ var GRAPH_FILENAME = "symbols.json";
14
+ var DEFAULT_INCLUDE = ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"];
15
+ var DEFAULT_IGNORE = [
16
+ "node_modules/**",
17
+ ".git/**",
18
+ ".variant/**",
19
+ "dist/**",
20
+ "**/*.d.ts"
21
+ ];
22
+ function defaultGraphDir(cwd) {
23
+ return path.join(cwd, ".variant", "graph");
24
+ }
25
+ async function buildSymbolGraph(cwd, options = {}) {
26
+ const fg = (await import("fast-glob")).default;
27
+ const include = options.include ?? DEFAULT_INCLUDE;
28
+ for (const p of include) assertSafePattern(p);
29
+ const files = (await fg(include, {
30
+ cwd,
31
+ onlyFiles: true,
32
+ ignore: [...DEFAULT_IGNORE, ...options.ignore ?? []]
33
+ })).map(toPosix).sort();
34
+ const previous = options.previous?.version === SYMBOL_GRAPH_VERSION ? options.previous : null;
35
+ const stats = {
36
+ scanned: files.length,
37
+ parsed: 0,
38
+ reused: 0,
39
+ removed: 0
40
+ };
41
+ const contents = new Array(files.length);
42
+ const limit = Math.max(1, options.parallel ?? 32);
43
+ let next = 0;
44
+ const worker = async () => {
45
+ while (true) {
46
+ const i = next++;
47
+ if (i >= files.length) return;
48
+ const relPath = files[i];
49
+ if (relPath === void 0) return;
50
+ contents[i] = await readFile(path.join(cwd, relPath), "utf8");
51
+ }
52
+ };
53
+ await Promise.all(
54
+ Array.from({ length: Math.min(limit, files.length || 1) }, worker)
55
+ );
56
+ let tsm;
57
+ let project;
58
+ const out = {};
59
+ for (let i = 0; i < files.length; i++) {
60
+ const relPath = files[i];
61
+ const content = contents[i];
62
+ if (relPath === void 0 || content === void 0) continue;
63
+ const contentHash = sha256(content);
64
+ const prev = previous?.files[relPath];
65
+ if (prev !== void 0 && prev.contentHash === contentHash) {
66
+ out[relPath] = prev;
67
+ stats.reused++;
68
+ continue;
69
+ }
70
+ if (tsm === void 0 || project === void 0) {
71
+ tsm = await import("ts-morph");
72
+ project = new tsm.Project({ useInMemoryFileSystem: true });
73
+ }
74
+ const sourceFile = project.createSourceFile(relPath, content, {
75
+ overwrite: true
76
+ });
77
+ out[relPath] = indexSourceFile(sourceFile, contentHash, tsm);
78
+ stats.parsed++;
79
+ }
80
+ if (previous !== null) {
81
+ for (const key of Object.keys(previous.files)) {
82
+ if (!(key in out)) stats.removed++;
83
+ }
84
+ }
85
+ return {
86
+ graph: {
87
+ version: SYMBOL_GRAPH_VERSION,
88
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
89
+ files: out
90
+ },
91
+ stats
92
+ };
93
+ }
94
+ async function updateSymbolGraph(cwd, options = {}) {
95
+ const graphDir = options.graphDir ?? defaultGraphDir(cwd);
96
+ const previous = options.previous ?? await loadSymbolGraph(graphDir);
97
+ const result = await buildSymbolGraph(cwd, { ...options, previous });
98
+ await saveSymbolGraph(graphDir, result.graph);
99
+ return result;
100
+ }
101
+ async function loadSymbolGraph(graphDir) {
102
+ const filePath = path.join(graphDir, GRAPH_FILENAME);
103
+ let raw;
104
+ try {
105
+ raw = await readFile(filePath, "utf8");
106
+ } catch (err) {
107
+ if (err.code === "ENOENT") return null;
108
+ throw new GraphError(
109
+ `Failed to read symbol graph at "${filePath}": ${String(err)}`,
110
+ { cause: err }
111
+ );
112
+ }
113
+ try {
114
+ const parsed = JSON.parse(raw);
115
+ if (parsed.version !== SYMBOL_GRAPH_VERSION) return null;
116
+ if (typeof parsed.files !== "object" || parsed.files === null) return null;
117
+ return parsed;
118
+ } catch {
119
+ return null;
120
+ }
121
+ }
122
+ async function saveSymbolGraph(graphDir, graph) {
123
+ try {
124
+ await mkdir(graphDir, { recursive: true });
125
+ await writeFile(
126
+ path.join(graphDir, GRAPH_FILENAME),
127
+ JSON.stringify(graph, null, 2)
128
+ );
129
+ } catch (err) {
130
+ throw new GraphError(
131
+ `Failed to write symbol graph to "${graphDir}": ${String(err)}`,
132
+ { cause: err }
133
+ );
134
+ }
135
+ }
136
+ function findSymbol(graph, name) {
137
+ const hits = [];
138
+ for (const [file, index] of Object.entries(graph.files)) {
139
+ for (const symbol of index.symbols) {
140
+ if (symbol.name === name) hits.push({ file, symbol });
141
+ }
142
+ }
143
+ return hits;
144
+ }
145
+ function getFileIndex(graph, file) {
146
+ return graph.files[toPosix(file)];
147
+ }
148
+ function indexSourceFile(file, contentHash, tsm) {
149
+ const surface = collectExportedSurface(file, tsm);
150
+ const { imports, notes: importNotes } = collectImports(file, tsm);
151
+ const symbols = [...surface.symbols.entries()].map(([name, s]) => ({
152
+ name,
153
+ kind: s.kind,
154
+ signatureHash: sha256(s.signature),
155
+ bodyHash: s.body === "" ? "" : sha256(s.body),
156
+ typeSpace: s.typeSpace
157
+ })).sort((a, b) => a.name.localeCompare(b.name));
158
+ return {
159
+ contentHash,
160
+ symbols,
161
+ imports,
162
+ notes: [.../* @__PURE__ */ new Set([...surface.notes, ...importNotes])]
163
+ };
164
+ }
165
+ function collectImports(file, tsm) {
166
+ const imports = [];
167
+ const notes = [];
168
+ for (const imp of file.getImportDeclarations()) {
169
+ const names = [];
170
+ if (imp.getDefaultImport() !== void 0) names.push("default");
171
+ if (imp.getNamespaceImport() !== void 0) names.push("*");
172
+ for (const named of imp.getNamedImports()) names.push(named.getName());
173
+ imports.push({
174
+ module: imp.getModuleSpecifierValue(),
175
+ kind: "static",
176
+ typeOnly: imp.isTypeOnly(),
177
+ names
178
+ });
179
+ }
180
+ for (const exp of file.getExportDeclarations()) {
181
+ const module = exp.getModuleSpecifierValue();
182
+ if (module === void 0) continue;
183
+ const named = exp.getNamedExports().map((s) => s.getName());
184
+ imports.push({
185
+ module,
186
+ kind: "reexport",
187
+ typeOnly: exp.isTypeOnly(),
188
+ names: named.length > 0 ? named : ["*"]
189
+ });
190
+ }
191
+ for (const call of file.getDescendantsOfKind(tsm.SyntaxKind.CallExpression)) {
192
+ if (call.getExpression().getKind() !== tsm.SyntaxKind.ImportKeyword) {
193
+ continue;
194
+ }
195
+ const arg = call.getArguments()[0];
196
+ if (arg !== void 0 && tsm.Node.isStringLiteral(arg)) {
197
+ imports.push({
198
+ module: arg.getLiteralValue(),
199
+ kind: "dynamic",
200
+ typeOnly: false,
201
+ names: []
202
+ });
203
+ } else {
204
+ notes.push("dynamic import() with a non-literal specifier");
205
+ }
206
+ }
207
+ return { imports, notes };
208
+ }
209
+ function sha256(text) {
210
+ return createHash("sha256").update(text).digest("hex");
211
+ }
212
+ function toPosix(p) {
213
+ return p.replace(/\\/g, "/");
214
+ }
215
+ function assertSafePattern(pattern) {
216
+ if (path.isAbsolute(pattern)) {
217
+ throw new GraphError(
218
+ `Unsafe glob pattern "${pattern}": patterns must be relative and must not traverse outside the project root.`
219
+ );
220
+ }
221
+ const segments = pattern.replace(/\\/g, "/").split("/");
222
+ if (segments.some((seg) => seg === "..")) {
223
+ throw new GraphError(
224
+ `Unsafe glob pattern "${pattern}": patterns must be relative and must not traverse outside the project root.`
225
+ );
226
+ }
227
+ }
228
+
229
+ export {
230
+ SYMBOL_GRAPH_VERSION,
231
+ defaultGraphDir,
232
+ buildSymbolGraph,
233
+ updateSymbolGraph,
234
+ loadSymbolGraph,
235
+ saveSymbolGraph,
236
+ findSymbol,
237
+ getFileIndex
238
+ };