sqlsift-cli 0.1.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/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ /node_modules
2
+
package/CHANGELOG.md ADDED
@@ -0,0 +1,157 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0-alpha.8] - 2026-02-13
11
+
12
+ ### Changed
13
+ - **Project renamed from sqlsurge to sqlsift** to avoid name conflict with existing [senkenn/sqlsurge](https://github.com/senkenn/sqlsurge) VS Code extension
14
+ - crate names: `sqlsift-core`, `sqlsift-cli`, `sqlsift-lsp`
15
+ - npm package: `sqlsift-cli`
16
+ - binary: `sqlsift`
17
+ - config file: `sqlsift.toml`
18
+
19
+ ### Fixed
20
+ - Fix `auto-tag.yml` release automation: add `actions: write` permission to enable `gh workflow run`
21
+
22
+ ## [0.1.0-alpha.7] - 2026-02-11
23
+
24
+ ### Added
25
+ - **LSP server** (`sqlsift-lsp`): Language Server Protocol support for real-time SQL diagnostics in editors
26
+ - textDocument/didOpen, didChange, didSave, didClose
27
+ - Automatic schema loading from `sqlsift.toml`
28
+ - Schema file change detection with catalog rebuild
29
+ - Diagnostic severity, error codes, and help messages
30
+ - 12 unit tests (diagnostic conversion, state management)
31
+ - **VS Code extension** (`editors/vscode/`): Thin LSP client for VS Code
32
+ - Configurable server path via `sqlsift.serverPath` setting
33
+ - SQL language configuration (comments, brackets)
34
+ - **Neovim support**: Works out of the box with built-in LSP client
35
+
36
+ ### Fixed
37
+ - `SELECT *` diagnostic now points to the `SELECT` keyword instead of file start
38
+
39
+ ## [0.1.0-alpha.6] - 2026-02-08
40
+
41
+ ### Added
42
+ - **Type inference engine**: SQL expression type checking for WHERE clauses and JOIN conditions
43
+ - E0003 (type-mismatch): Detect incompatible type comparisons (e.g., `WHERE id = 'text'`)
44
+ - E0007 (join-type-mismatch): Detect JOIN condition type incompatibilities (e.g., `ON users.id = orders.name`)
45
+ - Binary operator type validation: comparisons (=, !=, <, >, <=, >=) and arithmetic (+, -, *, /)
46
+ - Nested expression type inference: `(a + b) * 2 = c`
47
+ - Numeric type compatibility: implicit casts between TINYINT, SMALLINT, INTEGER, BIGINT
48
+
49
+ ### Changed
50
+ - Reorganized test suite: moved integration tests to `tests/analyzer_tests.rs` (74 tests)
51
+ - Improved API documentation with doc-test examples
52
+ - Replaced `unwrap()` with `expect()` in catalog code for better error messages
53
+
54
+ ## [0.1.0-alpha.5] - 2026-02-08
55
+
56
+ ### Added
57
+ - **MySQL dialect support**: Full schema parsing and query validation for MySQL
58
+ - MySQL-specific types: `TINYINT`, `MEDIUMINT`, `UNSIGNED` integer variants, `DATETIME`, inline `ENUM`
59
+ - `AUTO_INCREMENT` handling with implicit NOT NULL inference
60
+ - 10 MySQL unit tests covering schema parsing, SELECT, JOIN, INSERT, UPDATE, DELETE, subquery, CTE, and error detection
61
+ - Real-world MySQL test fixtures:
62
+ - **Sakila** (BSD): 16 tables, 40 valid queries, 12 error detection tests
63
+ - **Chinook MySQL** (MIT): 11 tables, 40 valid queries, 12 error detection tests
64
+
65
+ ## [0.1.0-alpha.4] - 2026-02-08
66
+
67
+ ### Added
68
+ - **Derived table (subquery in FROM) support**: Resolve aliases and validate column references for `FROM (SELECT ...) AS sub`
69
+ - **LATERAL vs non-LATERAL scope isolation**: Non-LATERAL subqueries correctly cannot see outer FROM tables
70
+ - **UPDATE ... FROM / DELETE ... USING**: PostgreSQL-specific multi-table update/delete syntax
71
+ - **Recursive CTE support**: CTEs can reference themselves in recursive queries
72
+ - **Table-valued functions in FROM**: `generate_series()`, `unnest()` etc. recognized as table sources
73
+ - **UNION/INTERSECT/EXCEPT column inference**: Infer output columns from set operations for CTE/derived table validation
74
+ - **Comprehensive expression resolution**: AtTimeZone, Collate, Ceil/Floor, Overlay, IsDistinctFrom, IsUnknown, SimilarTo, Tuple, Array, Subscript, Method, GroupingSets/Cube/Rollup
75
+ - **Function FILTER/OVER clause resolution**: Validate column references in `COUNT(*) FILTER (WHERE ...)` and `OVER (PARTITION BY ... ORDER BY ...)`
76
+ - **ORDER BY column resolution**: Validate ORDER BY references including SELECT alias support
77
+ - **Named function argument resolution**: Handle `func(name => value)` syntax
78
+ - 72 PostgreSQL pattern test fixtures (basic, advanced, and expression coverage)
79
+
80
+ ### Fixed
81
+ - WHERE subquery scope leak: subqueries in IN/EXISTS no longer pollute outer table scope
82
+ - VALUES derived table column aliases now correctly applied
83
+ - Empty derived_columns (table-valued functions) no longer cause false column-not-found errors
84
+
85
+ ## [0.1.0-alpha.3] - 2026-02-08
86
+
87
+ ### Added
88
+ - **`--dialect` flag wired up**: CLI `--dialect` option now correctly configures the SQL parser dialect (previously ignored)
89
+ - **Real-world schema test fixtures**: Chinook, Pagila, Northwind schemas with comprehensive valid/invalid query tests covering SELECT, JOIN, INSERT, UPDATE, DELETE, subqueries, and CTEs
90
+ - Third-party license file for test fixtures
91
+
92
+ ### Fixed
93
+ - `--dialect` CLI flag was completely ignored; PostgreSQL dialect was hardcoded throughout
94
+ - ALTER TABLE warnings for non-schema-affecting operations (e.g., `OWNER TO`) are now suppressed
95
+
96
+ ## [0.1.0-alpha.2] - 2026-02-08
97
+
98
+ ### Added
99
+ - **CHECK constraints**: Column-level and table-level CHECK constraint parsing and storage
100
+ - **CREATE TYPE AS ENUM**: Enum type definitions with value storage in catalog
101
+ - **GENERATED AS IDENTITY**: ALWAYS and BY DEFAULT identity columns with implicit NOT NULL
102
+ - **CREATE VIEW**: View definitions with column inference from SELECT projection, wildcard expansion, and query-time resolution
103
+ - **ALTER TABLE**: ADD COLUMN, DROP COLUMN, RENAME COLUMN, RENAME TABLE, ADD CONSTRAINT support
104
+ - **Resilient SQL parsing**: Gracefully skip unsupported DDL statements (CREATE FUNCTION, CREATE TRIGGER, CREATE DOMAIN, etc.) instead of failing the entire schema file
105
+ - Real-world test fixtures from Sakila and webknossos schemas
106
+
107
+ ### Fixed
108
+ - Schema files with mixed supported/unsupported SQL statements now parse correctly
109
+ - Comments preceding DDL statements no longer cause statement-by-statement parsing to skip valid statements
110
+
111
+ ### Changed
112
+ - Known Limitations updated: VIEWs are now supported; ALTER TABLE is now supported
113
+
114
+ ## [0.1.0-alpha.1] - 2026-02-07
115
+
116
+ ### Added
117
+ - Initial release of sqlsift
118
+ - SQL static analysis against schema definitions
119
+ - Support for PostgreSQL dialect
120
+ - Schema parsing from CREATE TABLE statements
121
+ - Query validation for SELECT, INSERT, UPDATE, DELETE statements
122
+ - Error detection:
123
+ - E0001: Table not found
124
+ - E0002: Column not found
125
+ - E0003: Type mismatch (reserved)
126
+ - E0004: Potential NULL violation (reserved)
127
+ - E0005: Column count mismatch in INSERT
128
+ - E0006: Ambiguous column reference
129
+ - E0007: JOIN type mismatch (reserved)
130
+ - E1000: Parse error
131
+ - JOIN condition validation
132
+ - Subquery support (including correlated subqueries)
133
+ - CTE (Common Table Expressions) support
134
+ - Error position reporting (line and column numbers)
135
+ - Multiple output formats: human-readable, JSON, SARIF
136
+ - Configuration file support (sqlsift.toml)
137
+ - Rule disabling via CLI (--disable) or config file
138
+ - CLI with check, schema, and parse commands
139
+ - Typo suggestions using Levenshtein distance
140
+ - CI/CD integration support via exit codes and SARIF output
141
+ - Framework integration examples (Rails, Prisma)
142
+
143
+ ### Known Limitations
144
+ - Only PostgreSQL dialect fully supported
145
+ - Type checking is basic (existence only, not full type inference)
146
+ - No support for VIEWs, functions, or stored procedures
147
+ - Derived table (subquery in FROM) column resolution is incomplete
148
+
149
+ [Unreleased]: https://github.com/yukikotani231/sqlsift/compare/v0.1.0-alpha.8...HEAD
150
+ [0.1.0-alpha.8]: https://github.com/yukikotani231/sqlsift/compare/v0.1.0-alpha.7...v0.1.0-alpha.8
151
+ [0.1.0-alpha.7]: https://github.com/yukikotani231/sqlsift/compare/v0.1.0-alpha.6...v0.1.0-alpha.7
152
+ [0.1.0-alpha.6]: https://github.com/yukikotani231/sqlsift/compare/v0.1.0-alpha.5...v0.1.0-alpha.6
153
+ [0.1.0-alpha.5]: https://github.com/yukikotani231/sqlsift/compare/v0.1.0-alpha.4...v0.1.0-alpha.5
154
+ [0.1.0-alpha.4]: https://github.com/yukikotani231/sqlsift/compare/v0.1.0-alpha.3...v0.1.0-alpha.4
155
+ [0.1.0-alpha.3]: https://github.com/yukikotani231/sqlsift/compare/v0.1.0-alpha.2...v0.1.0-alpha.3
156
+ [0.1.0-alpha.2]: https://github.com/yukikotani231/sqlsift/compare/v0.1.0-alpha.1...v0.1.0-alpha.2
157
+ [0.1.0-alpha.1]: https://github.com/yukikotani231/sqlsift/releases/tag/v0.1.0-alpha.1
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 yukikotani
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,263 @@
1
+ # sqlsift
2
+
3
+ [![CI](https://github.com/yukikotani231/sqlsift/actions/workflows/ci.yml/badge.svg)](https://github.com/yukikotani231/sqlsift/actions/workflows/ci.yml)
4
+ [![Crates.io](https://img.shields.io/crates/v/sqlsift-cli.svg)](https://crates.io/crates/sqlsift-cli)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
6
+
7
+ **SQL static analyzer that validates queries against schema definitions — no database connection required.**
8
+
9
+ sqlsift parses your DDL files (CREATE TABLE, CREATE VIEW, CREATE TYPE, ALTER TABLE, etc.) and validates SQL queries at build time, catching errors like missing tables, unknown columns, and typos before they reach production.
10
+
11
+ > **Note:** sqlsift is in early development (alpha). APIs and diagnostics may change between versions. Feedback and contributions are welcome!
12
+
13
+ ## Features
14
+
15
+ - **Zero database dependency** — Works entirely offline using schema SQL files
16
+ - **Framework agnostic** — Works with Rails, Prisma, raw SQL migrations, and more
17
+ - **Helpful diagnostics** — Clear error messages with suggestions for typos
18
+ - **CI-ready** — JSON and SARIF output formats for integration with CI/CD pipelines
19
+ - **Fast** — Built in Rust for speed
20
+
21
+ ## Installation
22
+
23
+ ### via npm (Recommended)
24
+
25
+ ```bash
26
+ npm install -g sqlsift-cli
27
+ ```
28
+
29
+ Or use directly with `npx`:
30
+
31
+ ```bash
32
+ npx sqlsift-cli check --schema schema.sql query.sql
33
+ ```
34
+
35
+ ### via Cargo
36
+
37
+ ```bash
38
+ cargo install sqlsift-cli
39
+ ```
40
+
41
+ ### From GitHub Releases
42
+
43
+ Download the latest binary from [Releases](https://github.com/yukikotani231/sqlsift/releases).
44
+
45
+ ## Quick Start
46
+
47
+ ```bash
48
+ # Validate queries against a schema file
49
+ sqlsift check --schema schema.sql queries/*.sql
50
+
51
+ # Use multiple schema files
52
+ sqlsift check -s users.sql -s orders.sql queries/*.sql
53
+
54
+ # Use a migrations directory
55
+ sqlsift check --schema-dir ./migrations queries/*.sql
56
+ ```
57
+
58
+ ## Example
59
+
60
+ Given a schema:
61
+
62
+ ```sql
63
+ -- schema.sql
64
+ CREATE TABLE users (
65
+ id SERIAL PRIMARY KEY,
66
+ name VARCHAR(100) NOT NULL,
67
+ email TEXT UNIQUE
68
+ );
69
+ ```
70
+
71
+ And a query with errors:
72
+
73
+ ```sql
74
+ -- query.sql
75
+ SELECT naem, user_id FROM users;
76
+ ```
77
+
78
+ sqlsift will report:
79
+
80
+ ```
81
+ error[E0002]: Column 'naem' not found
82
+ = help: Did you mean 'name'?
83
+
84
+ error[E0002]: Column 'user_id' not found
85
+ ```
86
+
87
+ ## Framework Integration
88
+
89
+ ### Prisma
90
+
91
+ Prisma generates SQL migration files automatically:
92
+
93
+ ```bash
94
+ sqlsift check --schema-dir prisma/migrations queries/*.sql
95
+ ```
96
+
97
+ ### Rails
98
+
99
+ With `config.active_record.schema_format = :sql`:
100
+
101
+ ```bash
102
+ sqlsift check --schema db/structure.sql queries/*.sql
103
+ ```
104
+
105
+ Or with SQL migrations:
106
+
107
+ ```bash
108
+ sqlsift check --schema-dir db/migrate queries/*.sql
109
+ ```
110
+
111
+ ### Raw SQL
112
+
113
+ Just point to your schema files:
114
+
115
+ ```bash
116
+ sqlsift check --schema schema/*.sql queries/**/*.sql
117
+ ```
118
+
119
+ ## Diagnostic Rules
120
+
121
+ | Code | Name | Description | Status |
122
+ |------|------|-------------|--------|
123
+ | E0001 | table-not-found | Referenced table does not exist in schema | ✅ Implemented |
124
+ | E0002 | column-not-found | Referenced column does not exist in table | ✅ Implemented |
125
+ | E0003 | type-mismatch | Type incompatibility in expressions (comparisons, arithmetic) | ✅ Implemented |
126
+ | E0004 | potential-null-violation | Possible NOT NULL constraint violation | 🚧 Reserved |
127
+ | E0005 | column-count-mismatch | INSERT column count doesn't match values | ✅ Implemented |
128
+ | E0006 | ambiguous-column | Column reference is ambiguous across tables | ✅ Implemented |
129
+ | E0007 | join-type-mismatch | JOIN condition compares incompatible types | ✅ Implemented |
130
+
131
+ ### Type Inference Coverage (E0003, E0007)
132
+
133
+ **Currently Detected:**
134
+ - ✅ WHERE clause comparisons (`WHERE id = 'text'`)
135
+ - ✅ Arithmetic operations (`SELECT name + 10`)
136
+ - ✅ JOIN conditions (`ON users.id = orders.user_name`)
137
+ - ✅ INSERT value type mismatches (`INSERT INTO users (id) VALUES ('text')`)
138
+ - ✅ UPDATE assignment type mismatches (`UPDATE users SET id = 'text'`)
139
+ - ✅ Nested expressions (`WHERE (a + b) * 2 = 'text'`)
140
+ - ✅ All comparison operators (=, !=, <, >, <=, >=)
141
+ - ✅ Numeric type compatibility (INTEGER, BIGINT, DECIMAL, etc.)
142
+
143
+ **Not Yet Detected:**
144
+ - ⏳ CAST expression type inference
145
+ - ⏳ Function return types (COUNT, SUM, AVG, etc.)
146
+ - ⏳ CASE expression type consistency
147
+ - ⏳ Subquery/CTE column type inference
148
+
149
+ ## CLI Reference
150
+
151
+ ```
152
+ sqlsift check [OPTIONS] <FILES>...
153
+
154
+ Arguments:
155
+ <FILES>... SQL files to validate (supports glob patterns)
156
+
157
+ Options:
158
+ -s, --schema <FILE> Schema definition file (can be specified multiple times)
159
+ --schema-dir <DIR> Directory containing schema files
160
+ -c, --config <FILE> Path to configuration file [default: sqlsift.toml]
161
+ --disable <RULE> Disable specific rules (e.g., E0001, E0002)
162
+ -d, --dialect <NAME> SQL dialect [default: postgresql]
163
+ -f, --format <FORMAT> Output format: human, json, sarif [default: human]
164
+ --max-errors <N> Maximum number of errors before stopping [default: 100]
165
+ -v, --verbose Enable verbose output
166
+ -q, --quiet Suppress non-error output
167
+ -h, --help Print help
168
+ ```
169
+
170
+ ## Output Formats
171
+
172
+ ### Human (default)
173
+
174
+ ```
175
+ error[E0002]: Column 'user_id' not found in table 'users'
176
+ --> queries/fetch.sql:3:12
177
+ |
178
+ 3 | WHERE users.user_id = $1
179
+ | ^^^^^^^^^
180
+ |
181
+ = help: Did you mean 'id'?
182
+ ```
183
+
184
+ ### JSON
185
+
186
+ ```bash
187
+ sqlsift check -s schema.sql -f json queries/*.sql
188
+ ```
189
+
190
+ ### SARIF (for GitHub Code Scanning)
191
+
192
+ ```bash
193
+ sqlsift check -s schema.sql -f sarif queries/*.sql > results.sarif
194
+ ```
195
+
196
+ ## Supported SQL Queries
197
+
198
+ - SELECT, INSERT, UPDATE, DELETE with full column/table validation
199
+ - JOINs (INNER, LEFT, RIGHT, FULL, CROSS, NATURAL) with ON/USING clause validation
200
+ - CTEs (WITH clause) including recursive CTEs
201
+ - Subqueries (WHERE IN/EXISTS, FROM derived tables, scalar subqueries)
202
+ - LATERAL vs non-LATERAL scope isolation
203
+ - UPDATE ... FROM / DELETE ... USING (PostgreSQL extensions)
204
+ - Window functions (OVER, PARTITION BY, FILTER)
205
+ - GROUPING SETS, CUBE, ROLLUP
206
+ - DISTINCT ON, UNION / INTERSECT / EXCEPT
207
+ - ORDER BY with SELECT alias support
208
+ - Comprehensive expression coverage (CASE, CAST, JSON operators, AT TIME ZONE, ARRAY, etc.)
209
+
210
+ ## Supported DDL
211
+
212
+ - `CREATE TABLE` (columns, constraints, primary keys, foreign keys, UNIQUE)
213
+ - `CREATE VIEW` (column inference from SELECT projection)
214
+ - `CREATE TYPE AS ENUM`
215
+ - `ALTER TABLE` (ADD/DROP/RENAME COLUMN, ADD CONSTRAINT, RENAME TABLE)
216
+ - `CHECK` constraints (column-level and table-level)
217
+ - `GENERATED AS IDENTITY` columns (ALWAYS / BY DEFAULT)
218
+ - Resilient parsing — unsupported DDL (functions, triggers, domains, etc.) is gracefully skipped
219
+
220
+ ## Supported SQL Dialects
221
+
222
+ - **PostgreSQL** (default) — fully supported
223
+ - **MySQL** — supported (`--dialect mysql`)
224
+ - **SQLite** — supported (`--dialect sqlite`)
225
+
226
+ Use the `--dialect` flag to specify the dialect.
227
+
228
+ ## Roadmap
229
+
230
+ ### Completed
231
+ - [x] Configuration file (`sqlsift.toml`)
232
+ - [x] MySQL dialect support
233
+ - [x] SQLite dialect support
234
+ - [x] Type inference for expressions (WHERE, JOIN, arithmetic, INSERT/UPDATE)
235
+ - [x] LSP server for editor integration (VS Code extension)
236
+
237
+ ### Planned
238
+ - [ ] CAST expression type inference
239
+ - [ ] Function return type inference (COUNT, SUM, AVG, etc.)
240
+ - [ ] CASE expression type consistency checking
241
+ - [ ] Subquery/CTE column type inference
242
+ - [ ] Custom rule plugins
243
+
244
+ ## Contributing
245
+
246
+ Contributions are welcome! Please feel free to submit a Pull Request.
247
+
248
+ 1. Fork the repository
249
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
250
+ 3. Run tests (`cargo test`)
251
+ 4. Commit your changes (`git commit -m 'Add amazing feature'`)
252
+ 5. Push to the branch (`git push origin feature/amazing-feature`)
253
+ 6. Open a Pull Request
254
+
255
+ ## License
256
+
257
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
258
+
259
+ ## Acknowledgments
260
+
261
+ - [sqlparser-rs](https://github.com/apache/datafusion-sqlparser-rs) — SQL parsing
262
+ - [miette](https://github.com/zkat/miette) — Diagnostic rendering
263
+ - [clap](https://github.com/clap-rs/clap) — CLI framework
@@ -0,0 +1,212 @@
1
+ const { createWriteStream, existsSync, mkdirSync, mkdtemp } = require("fs");
2
+ const { join, sep } = require("path");
3
+ const { spawnSync } = require("child_process");
4
+ const { tmpdir } = require("os");
5
+
6
+ const axios = require("axios");
7
+ const rimraf = require("rimraf");
8
+ const tmpDir = tmpdir();
9
+
10
+ const error = (msg) => {
11
+ console.error(msg);
12
+ process.exit(1);
13
+ };
14
+
15
+ class Package {
16
+ constructor(platform, name, url, filename, zipExt, binaries) {
17
+ let errors = [];
18
+ if (typeof url !== "string") {
19
+ errors.push("url must be a string");
20
+ } else {
21
+ try {
22
+ new URL(url);
23
+ } catch (e) {
24
+ errors.push(e);
25
+ }
26
+ }
27
+ if (name && typeof name !== "string") {
28
+ errors.push("package name must be a string");
29
+ }
30
+ if (!name) {
31
+ errors.push("You must specify the name of your package");
32
+ }
33
+ if (binaries && typeof binaries !== "object") {
34
+ errors.push("binaries must be a string => string map");
35
+ }
36
+ if (!binaries) {
37
+ errors.push("You must specify the binaries in the package");
38
+ }
39
+
40
+ if (errors.length > 0) {
41
+ let errorMsg =
42
+ "One or more of the parameters you passed to the Binary constructor are invalid:\n";
43
+ errors.forEach((error) => {
44
+ errorMsg += error;
45
+ });
46
+ errorMsg +=
47
+ '\n\nCorrect usage: new Package("my-binary", "https://example.com/binary/download.tar.gz", {"my-binary": "my-binary"})';
48
+ error(errorMsg);
49
+ }
50
+
51
+ this.platform = platform;
52
+ this.url = url;
53
+ this.name = name;
54
+ this.filename = filename;
55
+ this.zipExt = zipExt;
56
+ this.installDirectory = join(__dirname, "node_modules", ".bin_real");
57
+ this.binaries = binaries;
58
+
59
+ if (!existsSync(this.installDirectory)) {
60
+ mkdirSync(this.installDirectory, { recursive: true });
61
+ }
62
+ }
63
+
64
+ exists() {
65
+ for (const binaryName in this.binaries) {
66
+ const binRelPath = this.binaries[binaryName];
67
+ const binPath = join(this.installDirectory, binRelPath);
68
+ if (!existsSync(binPath)) {
69
+ return false;
70
+ }
71
+ }
72
+ return true;
73
+ }
74
+
75
+ install(fetchOptions, suppressLogs = false) {
76
+ if (this.exists()) {
77
+ if (!suppressLogs) {
78
+ console.error(
79
+ `${this.name} is already installed, skipping installation.`,
80
+ );
81
+ }
82
+ return Promise.resolve();
83
+ }
84
+
85
+ if (existsSync(this.installDirectory)) {
86
+ rimraf.sync(this.installDirectory);
87
+ }
88
+
89
+ mkdirSync(this.installDirectory, { recursive: true });
90
+
91
+ if (!suppressLogs) {
92
+ console.error(`Downloading release from ${this.url}`);
93
+ }
94
+
95
+ return axios({ ...fetchOptions, url: this.url, responseType: "stream" })
96
+ .then((res) => {
97
+ return new Promise((resolve, reject) => {
98
+ mkdtemp(`${tmpDir}${sep}`, (err, directory) => {
99
+ let tempFile = join(directory, this.filename);
100
+ const sink = res.data.pipe(createWriteStream(tempFile));
101
+ sink.on("error", (err) => reject(err));
102
+ sink.on("close", () => {
103
+ if (/\.tar\.*/.test(this.zipExt)) {
104
+ const result = spawnSync("tar", [
105
+ "xf",
106
+ tempFile,
107
+ // The tarballs are stored with a leading directory
108
+ // component; we strip one component in the
109
+ // shell installers too.
110
+ "--strip-components",
111
+ "1",
112
+ "-C",
113
+ this.installDirectory,
114
+ ]);
115
+ if (result.status == 0) {
116
+ resolve();
117
+ } else if (result.error) {
118
+ reject(result.error);
119
+ } else {
120
+ reject(
121
+ new Error(
122
+ `An error occurred untarring the artifact: stdout: ${result.stdout}; stderr: ${result.stderr}`,
123
+ ),
124
+ );
125
+ }
126
+ } else if (this.zipExt == ".zip") {
127
+ let result;
128
+ if (this.platform.artifactName.includes("windows")) {
129
+ // Windows does not have "unzip" by default on many installations, instead
130
+ // we use Expand-Archive from powershell
131
+ result = spawnSync("powershell.exe", [
132
+ "-NoProfile",
133
+ "-NonInteractive",
134
+ "-Command",
135
+ `& {
136
+ param([string]$LiteralPath, [string]$DestinationPath)
137
+ Expand-Archive -LiteralPath $LiteralPath -DestinationPath $DestinationPath -Force
138
+ }`,
139
+ tempFile,
140
+ this.installDirectory,
141
+ ]);
142
+ } else {
143
+ result = spawnSync("unzip", [
144
+ "-q",
145
+ tempFile,
146
+ "-d",
147
+ this.installDirectory,
148
+ ]);
149
+ }
150
+
151
+ if (result.status == 0) {
152
+ resolve();
153
+ } else if (result.error) {
154
+ reject(result.error);
155
+ } else {
156
+ reject(
157
+ new Error(
158
+ `An error occurred unzipping the artifact: stdout: ${result.stdout}; stderr: ${result.stderr}`,
159
+ ),
160
+ );
161
+ }
162
+ } else {
163
+ reject(
164
+ new Error(`Unrecognized file extension: ${this.zipExt}`),
165
+ );
166
+ }
167
+ });
168
+ });
169
+ });
170
+ })
171
+ .then(() => {
172
+ if (!suppressLogs) {
173
+ console.error(`${this.name} has been installed!`);
174
+ }
175
+ })
176
+ .catch((e) => {
177
+ error(`Error fetching release: ${e.message}`);
178
+ });
179
+ }
180
+
181
+ run(binaryName, fetchOptions) {
182
+ const promise = !this.exists()
183
+ ? this.install(fetchOptions, true)
184
+ : Promise.resolve();
185
+
186
+ promise
187
+ .then(() => {
188
+ const [, , ...args] = process.argv;
189
+
190
+ const options = { cwd: process.cwd(), stdio: "inherit" };
191
+
192
+ const binRelPath = this.binaries[binaryName];
193
+ if (!binRelPath) {
194
+ error(`${binaryName} is not a known binary in ${this.name}`);
195
+ }
196
+ const binPath = join(this.installDirectory, binRelPath);
197
+ const result = spawnSync(binPath, args, options);
198
+
199
+ if (result.error) {
200
+ error(result.error);
201
+ }
202
+
203
+ process.exit(result.status);
204
+ })
205
+ .catch((e) => {
206
+ error(e.message);
207
+ process.exit(1);
208
+ });
209
+ }
210
+ }
211
+
212
+ module.exports.Package = Package;