@piklv/ftaql-cli 1.0.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.
- package/@types/ftaql-cli.d.ts +140 -0
- package/README.md +260 -0
- package/binaries/README.md +1 -0
- package/binaries/ftaql-aarch64-apple-darwin/ftaql +0 -0
- package/binaries/ftaql-aarch64-pc-windows-msvc/ftaql.exe +0 -0
- package/binaries/ftaql-aarch64-unknown-linux-musl/ftaql +0 -0
- package/binaries/ftaql-arm-unknown-linux-musleabi/ftaql +0 -0
- package/binaries/ftaql-x86_64-apple-darwin/ftaql +0 -0
- package/binaries/ftaql-x86_64-pc-windows-msvc/ftaql.exe +0 -0
- package/binaries/ftaql-x86_64-unknown-linux-musl/ftaql +0 -0
- package/check.js +37 -0
- package/index.js +107 -0
- package/package.json +34 -0
- package/targets.js +18 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
declare module "@piklv/ftaql-cli" {
|
|
2
|
+
/**
|
|
3
|
+
* Полная структура метрик и анализа для одного исходного файла (1:1 с Rust FileData).
|
|
4
|
+
*/
|
|
5
|
+
export type AnalyzedFile = {
|
|
6
|
+
/**
|
|
7
|
+
* Полный путь или имя исходного файла.
|
|
8
|
+
*/
|
|
9
|
+
file_name: string;
|
|
10
|
+
/**
|
|
11
|
+
* Метрики размера файла.
|
|
12
|
+
*/
|
|
13
|
+
size_metrics: {
|
|
14
|
+
/** Количество строк кода в файле (без пустых строк и, опционально, комментариев). */
|
|
15
|
+
line_count: number;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Метрики сложности файла.
|
|
19
|
+
*/
|
|
20
|
+
complexity_metrics: {
|
|
21
|
+
/** Цикломатическая сложность (количество независимых путей исполнения). */
|
|
22
|
+
cyclomatic: number;
|
|
23
|
+
/** Метрики Холстеда — количественная оценка сложности кода. */
|
|
24
|
+
halstead: {
|
|
25
|
+
uniq_operators: number;
|
|
26
|
+
uniq_operands: number;
|
|
27
|
+
total_operators: number;
|
|
28
|
+
total_operands: number;
|
|
29
|
+
program_length: number;
|
|
30
|
+
vocabulary_size: number;
|
|
31
|
+
volume: number;
|
|
32
|
+
difficulty: number;
|
|
33
|
+
effort: number;
|
|
34
|
+
time: number;
|
|
35
|
+
bugs: number;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Метрики связанности (coupling) для файла. Может отсутствовать или быть null, если не применимо.
|
|
40
|
+
*/
|
|
41
|
+
coupling_metrics?: {
|
|
42
|
+
/** Входящая связанность (afferent coupling, Ca): сколько других файлов зависят от этого файла. */
|
|
43
|
+
afferent_coupling: number;
|
|
44
|
+
/** Исходящая связанность (efferent coupling, Ce): от скольких файлов зависит этот файл. */
|
|
45
|
+
efferent_coupling: number;
|
|
46
|
+
/** Instability: Ce / (Ca + Ce). Чем ближе к 1, тем менее устойчив модуль. */
|
|
47
|
+
instability: number;
|
|
48
|
+
/** Сила зависимости: карта "путь к файлу" → "количество импортируемых идентификаторов". */
|
|
49
|
+
dependency_strength: Record<string, number>;
|
|
50
|
+
/** Информация о циклах в графе зависимостей. */
|
|
51
|
+
cycles?: {
|
|
52
|
+
/** ID цикла, в котором участвует файл. Ссылается на `project_analysis.cycles`. */
|
|
53
|
+
cycle_id?: number;
|
|
54
|
+
/** ID runtime-цикла, если цикл существует во время выполнения. Ссылается на `project_analysis.runtime_cycles`. */
|
|
55
|
+
runtime_cycle_id?: number;
|
|
56
|
+
};
|
|
57
|
+
} | null;
|
|
58
|
+
/**
|
|
59
|
+
* Итоговые оценки файла.
|
|
60
|
+
*/
|
|
61
|
+
scores: {
|
|
62
|
+
/** Итоговый File Score (чем ниже, тем лучше). */
|
|
63
|
+
file_score: number;
|
|
64
|
+
/** Итоговый Coupling Score (чем ниже, тем лучше). */
|
|
65
|
+
coupling_score: number;
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Информация о циклических зависимостях (сильно связанных компонентах) в проекте.
|
|
71
|
+
*/
|
|
72
|
+
export type CycleInfo = {
|
|
73
|
+
/** Уникальный идентификатор цикла. */
|
|
74
|
+
id: number;
|
|
75
|
+
/** Количество файлов в цикле. */
|
|
76
|
+
size: number;
|
|
77
|
+
/**
|
|
78
|
+
* Граф цикла, где ключи верхнего и вложенного уровней - это индексы из
|
|
79
|
+
* `project_analysis.cycle_members`, а значения - вес связи между файлами.
|
|
80
|
+
*/
|
|
81
|
+
graph: Record<string, Record<string, number>>;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Глобальные метрики и анализ для всего проекта.
|
|
86
|
+
*/
|
|
87
|
+
export type ProjectAnalysis = {
|
|
88
|
+
/** Общий отсортированный список файлов, участвующих хотя бы в одном цикле. */
|
|
89
|
+
cycle_members: string[];
|
|
90
|
+
/** Список всех обнаруженных в проекте циклических зависимостей. */
|
|
91
|
+
cycles: CycleInfo[];
|
|
92
|
+
/** Список циклов, которые сохраняются во время выполнения. */
|
|
93
|
+
runtime_cycles: CycleInfo[];
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Корневой объект, возвращаемый FtaQl при анализе в формате JSON.
|
|
98
|
+
*/
|
|
99
|
+
export type FtaQlJsonOutput = {
|
|
100
|
+
/** Глобальный анализ проекта. */
|
|
101
|
+
project_analysis: ProjectAnalysis;
|
|
102
|
+
/** Список проанализированных файлов. */
|
|
103
|
+
findings: AnalyzedFile[];
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Options for persisting a project analysis snapshot into SQLite.
|
|
108
|
+
*
|
|
109
|
+
* `dbPath` is required. The remaining fields are stored as metadata on the analysis run.
|
|
110
|
+
*/
|
|
111
|
+
export type FtaQlRunOptions = {
|
|
112
|
+
/**
|
|
113
|
+
* Path to the SQLite database file that should receive the snapshot.
|
|
114
|
+
*/
|
|
115
|
+
dbPath: string;
|
|
116
|
+
/**
|
|
117
|
+
* Optional custom path to `ftaql.json`.
|
|
118
|
+
*/
|
|
119
|
+
configPath?: string;
|
|
120
|
+
/**
|
|
121
|
+
* Optional revision identifier, for example a git commit SHA.
|
|
122
|
+
*/
|
|
123
|
+
revision?: string;
|
|
124
|
+
/**
|
|
125
|
+
* Optional human-readable label such as branch or tag.
|
|
126
|
+
*/
|
|
127
|
+
ref?: string;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Runs the project analysis for the given project and persists the result into SQLite.
|
|
132
|
+
*
|
|
133
|
+
* @param projectPath - The path to the root of the project to analyze
|
|
134
|
+
* @param options - SQLite persistence options. `dbPath` is required.
|
|
135
|
+
*/
|
|
136
|
+
export function runFtaQl(
|
|
137
|
+
projectPath: string,
|
|
138
|
+
options: FtaQlRunOptions
|
|
139
|
+
): string;
|
|
140
|
+
}
|
package/README.md
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# FtaQl
|
|
2
|
+
|
|
3
|
+
[English](https://github.com/pikulev/ftaql/blob/main/README.md) | [Русский](https://github.com/pikulev/ftaql/blob/main/README.ru.md)
|
|
4
|
+
|
|
5
|
+
FtaQl helps you see where risk is actually accumulating in a TS/JS project. It walks the repository, stores a snapshot in SQLite, and turns code analysis into plain SQL instead of guesswork.
|
|
6
|
+
|
|
7
|
+
After one run you can ask which files stay expensive across revisions, where coupling keeps growing, and when runtime cycles first appeared. That makes it useful for refactors, CI, and history analysis when you need an accumulated dataset instead of a one-off report.
|
|
8
|
+
|
|
9
|
+
The core is written in Rust, so repeated runs across large codebases and revision history stay practical. On Apple M1 hardware FtaQl can analyze up to **3000 files per second**.
|
|
10
|
+
|
|
11
|
+
## What FtaQl Collects
|
|
12
|
+
|
|
13
|
+
For project-level analysis through the native CLI and Node wrapper, FtaQl stores:
|
|
14
|
+
|
|
15
|
+
| Metric / artifact | What it means |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| `file_score` | A composite score for a file based on its own metrics. |
|
|
18
|
+
| `coupling_score` | A composite score for relationship risk in the context of the whole project. |
|
|
19
|
+
| Cyclomatic complexity | How many independent execution paths the code contains. |
|
|
20
|
+
| Halstead metrics | Operator/operand metrics that help estimate code volume and complexity. |
|
|
21
|
+
| Afferent and efferent coupling | Who depends on the file, and how many files the file depends on. |
|
|
22
|
+
| Dependency strength | How tight module-to-module relationships are. |
|
|
23
|
+
| Full-graph cycles | Cycles in the whole project dependency graph, including type-only edges. |
|
|
24
|
+
| `runtime` cycles | Cycles over dependencies that actually participate in execution. |
|
|
25
|
+
| SQLite snapshots | Normalized runs for SQL queries and historical comparisons. |
|
|
26
|
+
|
|
27
|
+
Detailed formulas, caveats, and interpretation notes are documented in [`docs/scoring/en.md`](https://github.com/pikulev/ftaql/blob/main/docs/scoring/en.md).
|
|
28
|
+
|
|
29
|
+
A `runtime` cycle is a cyclic dependency through runtime entities. A full-graph cycle can include type-only dependencies, which may hurt tooling and architecture even when runtime is unaffected.
|
|
30
|
+
|
|
31
|
+
## Quickstart: run -> persist -> query
|
|
32
|
+
|
|
33
|
+
Project-level analysis is available through the native CLI and the Node.js wrapper. The WASM build analyzes a single file and returns JSON for that file only.
|
|
34
|
+
In WASM, `coupling_score` is always `0.0`, and there is no project-level dependency graph or cycle analysis.
|
|
35
|
+
|
|
36
|
+
The basic loop is simple: persist a snapshot to SQLite, attach `revision` and `ref` when needed, then query the accumulated runs with SQL.
|
|
37
|
+
|
|
38
|
+
Persist the current checkout:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npx @piklv/ftaql-cli path/to/project --db ./ftaql.sqlite
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Append revision metadata as you collect more snapshots:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npx @piklv/ftaql-cli path/to/project \
|
|
48
|
+
--db ./ftaql.sqlite \
|
|
49
|
+
--revision "$(git rev-parse HEAD)" \
|
|
50
|
+
--ref main
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Use a custom config file when needed:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npx @piklv/ftaql-cli path/to/project \
|
|
57
|
+
--db ./ftaql.sqlite \
|
|
58
|
+
--config-path ./path/to/ftaql.json
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Main options:
|
|
62
|
+
|
|
63
|
+
- `--db`
|
|
64
|
+
- `--config-path`
|
|
65
|
+
- `--revision`
|
|
66
|
+
- `--ref`
|
|
67
|
+
|
|
68
|
+
When you append multiple runs into the same SQLite database:
|
|
69
|
+
|
|
70
|
+
- `--revision` stores the exact snapshot identifier, usually a commit SHA
|
|
71
|
+
- `--ref` stores a human-readable branch, tag, or channel label such as `main`, `release/1.2`, or `nightly`
|
|
72
|
+
- using both lets you compare exact snapshots while still grouping runs by branch or release line
|
|
73
|
+
|
|
74
|
+
## Querying the Snapshot History
|
|
75
|
+
|
|
76
|
+
Once snapshots are in SQLite, the interesting part starts.
|
|
77
|
+
|
|
78
|
+
Worst files in the latest snapshot:
|
|
79
|
+
|
|
80
|
+
```sql
|
|
81
|
+
SELECT file_path, file_score, coupling_score
|
|
82
|
+
FROM files
|
|
83
|
+
WHERE run_id = (SELECT MAX(id) FROM analysis_runs)
|
|
84
|
+
ORDER BY file_score DESC, coupling_score DESC
|
|
85
|
+
LIMIT 20;
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
If your team prefers buckets right away, you can layer empirical ranks on top of the same columns. This is not an official FtaQl scale, just one example of a project-level interpretation, so the thresholds should be tuned to your own codebase:
|
|
89
|
+
|
|
90
|
+
```sql
|
|
91
|
+
SELECT
|
|
92
|
+
file_path,
|
|
93
|
+
file_score,
|
|
94
|
+
CASE
|
|
95
|
+
WHEN file_score <= 50 THEN 'OK'
|
|
96
|
+
WHEN file_score <= 60 THEN 'Could be better'
|
|
97
|
+
ELSE 'Needs improvement'
|
|
98
|
+
END AS file_rank,
|
|
99
|
+
coupling_score,
|
|
100
|
+
CASE
|
|
101
|
+
WHEN coupling_score <= 100 THEN 'OK'
|
|
102
|
+
WHEN coupling_score <= 200 THEN 'Could be better'
|
|
103
|
+
ELSE 'Needs improvement'
|
|
104
|
+
END AS coupling_rank
|
|
105
|
+
FROM files
|
|
106
|
+
WHERE run_id = (SELECT MAX(id) FROM analysis_runs)
|
|
107
|
+
ORDER BY file_score DESC, coupling_score DESC
|
|
108
|
+
LIMIT 20;
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Compare average `file_score` across revisions on one branch:
|
|
112
|
+
|
|
113
|
+
```sql
|
|
114
|
+
SELECT ar.revision, AVG(f.file_score) AS avg_file_score
|
|
115
|
+
FROM analysis_runs ar
|
|
116
|
+
JOIN files f ON f.run_id = ar.id
|
|
117
|
+
WHERE ar.ref_label = 'main'
|
|
118
|
+
GROUP BY ar.revision
|
|
119
|
+
ORDER BY ar.created_at;
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Inspect runtime cycles for a specific revision:
|
|
123
|
+
|
|
124
|
+
```sql
|
|
125
|
+
SELECT cf.cycle_id, cf.file_path
|
|
126
|
+
FROM cycle_files cf
|
|
127
|
+
JOIN analysis_runs ar ON ar.id = cf.run_id
|
|
128
|
+
WHERE ar.revision = 'abc123'
|
|
129
|
+
AND cf.cycle_kind = 'runtime'
|
|
130
|
+
ORDER BY cf.cycle_id, cf.file_path;
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Shape coupling hotspots into JSON for downstream tooling:
|
|
134
|
+
|
|
135
|
+
```sql
|
|
136
|
+
SELECT json_group_array(
|
|
137
|
+
json_object(
|
|
138
|
+
'file_path', hotspot.file_path,
|
|
139
|
+
'file_score', hotspot.file_score,
|
|
140
|
+
'coupling_score', hotspot.coupling_score
|
|
141
|
+
)
|
|
142
|
+
) AS hotspots
|
|
143
|
+
FROM (
|
|
144
|
+
SELECT file_path, file_score, coupling_score
|
|
145
|
+
FROM files
|
|
146
|
+
WHERE run_id = (SELECT MAX(id) FROM analysis_runs)
|
|
147
|
+
ORDER BY coupling_score DESC, file_score DESC
|
|
148
|
+
LIMIT 10
|
|
149
|
+
) AS hotspot;
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Typical Use Cases
|
|
153
|
+
|
|
154
|
+
- Snapshot a large TS/JS monorepo before and after a refactor, then query the worst files instead of guessing where the risk lives.
|
|
155
|
+
- Append runs for many commits or CI builds into the same SQLite database and compare trends by `revision` or `ref`.
|
|
156
|
+
- Use SQL as the analysis layer itself: aggregate hotspots, inspect cycles, or shape rows into JSON payloads for scripts and dashboards.
|
|
157
|
+
|
|
158
|
+
## What FtaQl Measures
|
|
159
|
+
|
|
160
|
+
The table above is the fastest way to understand the main metrics, while formulas and caveats live in [`docs/scoring/en.md`](https://github.com/pikulev/ftaql/blob/main/docs/scoring/en.md). In practice, most teams start from three views:
|
|
161
|
+
|
|
162
|
+
- `file_score`, when they want the quickest list of the heaviest files
|
|
163
|
+
- `coupling_score`, when they want to see where project relationships create the most risk
|
|
164
|
+
- full-graph cycles and `runtime` cycles, when they need to separate architectural smells from runtime trouble
|
|
165
|
+
|
|
166
|
+
## What Gets Persisted
|
|
167
|
+
|
|
168
|
+
FtaQl stores normalized project snapshots in SQLite. The core tables are:
|
|
169
|
+
|
|
170
|
+
- `analysis_runs` for run metadata and resolved config
|
|
171
|
+
- `files` for per-file metrics
|
|
172
|
+
- `file_dependencies` for dependency edges between analyzed files
|
|
173
|
+
- `cycles`, `cycle_files`, and `cycle_edges` for normalized cycle data
|
|
174
|
+
|
|
175
|
+
That layout is enough to accumulate many revisions in one database and query them with plain SQL. For the full contract, see [`docs/sqlite-output/en.md`](https://github.com/pikulev/ftaql/blob/main/docs/sqlite-output/en.md).
|
|
176
|
+
|
|
177
|
+
## Using FtaQl In Package Scripts
|
|
178
|
+
|
|
179
|
+
Install `@piklv/ftaql-cli`:
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
yarn add -D @piklv/ftaql-cli
|
|
183
|
+
# or
|
|
184
|
+
npm install --save-dev @piklv/ftaql-cli
|
|
185
|
+
# or
|
|
186
|
+
pnpm add -D @piklv/ftaql-cli
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Then add a script:
|
|
190
|
+
|
|
191
|
+
```json
|
|
192
|
+
{
|
|
193
|
+
"scripts": {
|
|
194
|
+
"ftaql": "ftaql . --db ./ftaql.sqlite"
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Using FtaQl From Code
|
|
200
|
+
|
|
201
|
+
`@piklv/ftaql-cli` also exports `runFtaQl(projectPath, options)`.
|
|
202
|
+
|
|
203
|
+
```javascript
|
|
204
|
+
import { runFtaQl } from "@piklv/ftaql-cli";
|
|
205
|
+
// CommonJS alternative:
|
|
206
|
+
// const { runFtaQl } = require("@piklv/ftaql-cli");
|
|
207
|
+
|
|
208
|
+
const output = runFtaQl("path/to/project", {
|
|
209
|
+
dbPath: "./ftaql.sqlite",
|
|
210
|
+
revision: process.env.GIT_SHA,
|
|
211
|
+
ref: process.env.GIT_BRANCH,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
console.log(output);
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Important notes about the Node.js wrapper:
|
|
218
|
+
|
|
219
|
+
- `options.dbPath` is required
|
|
220
|
+
- the wrapper persists a snapshot and returns the CLI summary from stdout
|
|
221
|
+
- `configPath`, `revision`, and `ref` are forwarded to the native binary
|
|
222
|
+
|
|
223
|
+
## Configuration
|
|
224
|
+
|
|
225
|
+
By default, the native CLI looks for `ftaql.json` in the analyzed project root. You can override that path with `--config-path`.
|
|
226
|
+
|
|
227
|
+
The `ftaql.json` file controls analysis behavior such as:
|
|
228
|
+
|
|
229
|
+
- `extensions`
|
|
230
|
+
- `exclude_filenames`
|
|
231
|
+
- `exclude_directories`
|
|
232
|
+
- `score_cap`
|
|
233
|
+
- `include_comments`
|
|
234
|
+
- `exclude_under`
|
|
235
|
+
|
|
236
|
+
FtaQl also auto-detects `tsconfig.json` and `jsconfig.json` files when resolving imports. It supports:
|
|
237
|
+
|
|
238
|
+
- `compilerOptions.paths`
|
|
239
|
+
- `compilerOptions.baseUrl`
|
|
240
|
+
- inherited configs via `extends`
|
|
241
|
+
- project references discovered through the resolver
|
|
242
|
+
|
|
243
|
+
For monorepo and nested-config examples, see [`docs/usage-patterns/en.md`](https://github.com/pikulev/ftaql/blob/main/docs/usage-patterns/en.md). For the exact config contract, see [`docs/configuration/en.md`](https://github.com/pikulev/ftaql/blob/main/docs/configuration/en.md).
|
|
244
|
+
|
|
245
|
+
## WebAssembly
|
|
246
|
+
|
|
247
|
+
The `@piklv/ftaql-wasm` package is intended for browser usage and analyzes one source file at a time:
|
|
248
|
+
|
|
249
|
+
- input: source code string
|
|
250
|
+
- output: JSON string for a single file
|
|
251
|
+
- no filesystem access
|
|
252
|
+
- no project-level coupling analysis
|
|
253
|
+
|
|
254
|
+
## Docs
|
|
255
|
+
|
|
256
|
+
Read the full documentation in [`docs/`](https://github.com/pikulev/ftaql/tree/main/docs), especially [`docs/overview/en.md`](https://github.com/pikulev/ftaql/blob/main/docs/overview/en.md).
|
|
257
|
+
|
|
258
|
+
## License
|
|
259
|
+
|
|
260
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
This directory must contain the ftaql crate binaries at publish time
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/check.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const path = require("node:path");
|
|
5
|
+
|
|
6
|
+
const { exeTargets, plainTargets } = require("./targets");
|
|
7
|
+
|
|
8
|
+
let missingBinaries = [];
|
|
9
|
+
|
|
10
|
+
function checkBinaryFile(target, path) {
|
|
11
|
+
try {
|
|
12
|
+
fs.readFileSync(path);
|
|
13
|
+
} catch (e) {
|
|
14
|
+
missingBinaries.push(target);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
for (let i = 0; i < exeTargets.length; i += 1) {
|
|
19
|
+
const bin = path.join(__dirname, "binaries", exeTargets[i], "ftaql.exe");
|
|
20
|
+
checkBinaryFile(exeTargets[i], bin);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
for (let i = 0; i < plainTargets.length; i += 1) {
|
|
24
|
+
const bin = path.join(__dirname, "binaries", plainTargets[i], "ftaql");
|
|
25
|
+
checkBinaryFile(plainTargets[i], bin);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (missingBinaries.length > 0) {
|
|
29
|
+
console.log("The following binaries are missing: \n");
|
|
30
|
+
missingBinaries.forEach((target) => {
|
|
31
|
+
console.log("- " + target);
|
|
32
|
+
});
|
|
33
|
+
console.log("\n");
|
|
34
|
+
throw new Error("Check failed");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
console.log("All binaries were located");
|
package/index.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { execFileSync } = require("node:child_process");
|
|
4
|
+
const path = require("node:path");
|
|
5
|
+
const fs = require("node:fs");
|
|
6
|
+
|
|
7
|
+
const platform = process.platform;
|
|
8
|
+
const architecture = process.arch;
|
|
9
|
+
|
|
10
|
+
function getBinaryPath() {
|
|
11
|
+
const targetDirectory = path.join(__dirname, "binaries");
|
|
12
|
+
|
|
13
|
+
switch (platform) {
|
|
14
|
+
case "win32":
|
|
15
|
+
if (architecture === "x64") {
|
|
16
|
+
return path.join(
|
|
17
|
+
targetDirectory,
|
|
18
|
+
"ftaql-x86_64-pc-windows-msvc",
|
|
19
|
+
"ftaql.exe"
|
|
20
|
+
);
|
|
21
|
+
} else if (architecture === "arm64") {
|
|
22
|
+
return path.join(
|
|
23
|
+
targetDirectory,
|
|
24
|
+
"ftaql-aarch64-pc-windows-msvc",
|
|
25
|
+
"ftaql.exe"
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
case "darwin":
|
|
29
|
+
if (architecture === "x64") {
|
|
30
|
+
return path.join(targetDirectory, "ftaql-x86_64-apple-darwin", "ftaql");
|
|
31
|
+
} else if (architecture === "arm64") {
|
|
32
|
+
return path.join(targetDirectory, "ftaql-aarch64-apple-darwin", "ftaql");
|
|
33
|
+
}
|
|
34
|
+
case "linux":
|
|
35
|
+
if (architecture === "x64") {
|
|
36
|
+
return path.join(
|
|
37
|
+
targetDirectory,
|
|
38
|
+
"ftaql-x86_64-unknown-linux-musl",
|
|
39
|
+
"ftaql"
|
|
40
|
+
);
|
|
41
|
+
} else if (architecture === "arm64") {
|
|
42
|
+
return path.join(
|
|
43
|
+
targetDirectory,
|
|
44
|
+
"ftaql-aarch64-unknown-linux-musl",
|
|
45
|
+
"ftaql"
|
|
46
|
+
);
|
|
47
|
+
} else if (architecture === "arm") {
|
|
48
|
+
return path.join(
|
|
49
|
+
targetDirectory,
|
|
50
|
+
"ftaql-arm-unknown-linux-musleabi",
|
|
51
|
+
"ftaql"
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
break;
|
|
55
|
+
default:
|
|
56
|
+
throw new Error("Unsupported platform: " + platform);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
throw new Error("Binary not found for the current platform");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function setUnixPerms(binaryPath) {
|
|
63
|
+
if (platform === "darwin" || platform === "linux") {
|
|
64
|
+
try {
|
|
65
|
+
fs.chmodSync(binaryPath, "755");
|
|
66
|
+
} catch (e) {
|
|
67
|
+
console.warn("Could not chmod ftaql binary: ", e);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Run the binary from code
|
|
73
|
+
// We build arguments that get sent to the binary
|
|
74
|
+
function runFtaQl(project, options) {
|
|
75
|
+
if (!options || !options.dbPath) {
|
|
76
|
+
throw new Error("runFtaQl(project, options) requires options.dbPath");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const binaryPath = getBinaryPath();
|
|
80
|
+
setUnixPerms(binaryPath);
|
|
81
|
+
const binaryArgs = [project, "--db", options.dbPath];
|
|
82
|
+
|
|
83
|
+
if (options.configPath) {
|
|
84
|
+
binaryArgs.push("--config-path", options.configPath);
|
|
85
|
+
}
|
|
86
|
+
if (options.revision) {
|
|
87
|
+
binaryArgs.push("--revision", options.revision);
|
|
88
|
+
}
|
|
89
|
+
if (options.ref) {
|
|
90
|
+
binaryArgs.push("--ref", options.ref);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const result = execFileSync(binaryPath, binaryArgs);
|
|
94
|
+
return result.toString();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Run the binary directly if executed as a standalone script
|
|
98
|
+
// Arguments are directly forwarded to the binary
|
|
99
|
+
if (require.main === module) {
|
|
100
|
+
const args = process.argv.slice(2); // Exclude the first two arguments (node binary and project path)
|
|
101
|
+
const binaryPath = getBinaryPath();
|
|
102
|
+
setUnixPerms(binaryPath);
|
|
103
|
+
|
|
104
|
+
execFileSync(binaryPath, args, { stdio: "inherit" });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
module.exports.runFtaQl = runFtaQl;
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@piklv/ftaql-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "FtaQl is a super-fast TypeScript static analysis tool written in Rust",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/pikulev/ftaql.git"
|
|
8
|
+
},
|
|
9
|
+
"author": "vasya pikulev",
|
|
10
|
+
"homepage": "https://github.com/pikulev/ftaql#readme",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/pikulev/ftaql/issues"
|
|
13
|
+
},
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"main": "index.js",
|
|
16
|
+
"bin": {
|
|
17
|
+
"ftaql": "index.js"
|
|
18
|
+
},
|
|
19
|
+
"types": "@types/ftaql-cli.d.ts",
|
|
20
|
+
"files": [
|
|
21
|
+
"index.js",
|
|
22
|
+
"check.js",
|
|
23
|
+
"targets.js",
|
|
24
|
+
"README.md",
|
|
25
|
+
"@types",
|
|
26
|
+
"binaries"
|
|
27
|
+
],
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"prepublishOnly": "node check.js"
|
|
33
|
+
}
|
|
34
|
+
}
|
package/targets.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Windows
|
|
2
|
+
const exeTargets = [
|
|
3
|
+
"ftaql-aarch64-pc-windows-msvc",
|
|
4
|
+
"ftaql-x86_64-pc-windows-msvc",
|
|
5
|
+
];
|
|
6
|
+
|
|
7
|
+
const plainTargets = [
|
|
8
|
+
// macOS
|
|
9
|
+
"ftaql-x86_64-apple-darwin",
|
|
10
|
+
"ftaql-aarch64-apple-darwin",
|
|
11
|
+
// Linux
|
|
12
|
+
"ftaql-x86_64-unknown-linux-musl",
|
|
13
|
+
"ftaql-aarch64-unknown-linux-musl",
|
|
14
|
+
"ftaql-arm-unknown-linux-musleabi",
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
module.exports.exeTargets = exeTargets;
|
|
18
|
+
module.exports.plainTargets = plainTargets;
|