@bockit/sequel 0.0.0-stage → 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/LICENSE +202 -0
- package/README.md +191 -2
- package/THIRD_PARTY_NOTICES.md +268 -0
- package/binaries.json +106 -0
- package/dist/binaries/cache.d.ts +6 -0
- package/dist/binaries/cache.js +36 -0
- package/dist/binaries/ensure-binary.d.ts +10 -0
- package/dist/binaries/ensure-binary.js +85 -0
- package/dist/binaries/manifest.d.ts +30 -0
- package/dist/binaries/manifest.js +112 -0
- package/dist/binaries/platform.d.ts +3 -0
- package/dist/binaries/platform.js +10 -0
- package/dist/binaries/unpack.d.ts +2 -0
- package/dist/binaries/unpack.js +35 -0
- package/dist/binaries/verify.d.ts +2 -0
- package/dist/binaries/verify.js +16 -0
- package/dist/check.d.ts +9 -0
- package/dist/check.js +166 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +324 -0
- package/dist/config/define-config.d.ts +23 -0
- package/dist/config/define-config.js +4 -0
- package/dist/config/load-config.d.ts +6 -0
- package/dist/config/load-config.js +34 -0
- package/dist/config/resolve-config.d.ts +34 -0
- package/dist/config/resolve-config.js +144 -0
- package/dist/db/connection.d.ts +6 -0
- package/dist/db/connection.js +38 -0
- package/dist/db/shadow.d.ts +11 -0
- package/dist/db/shadow.js +68 -0
- package/dist/engine/client.d.ts +24 -0
- package/dist/engine/client.js +123 -0
- package/dist/engine/diff.d.ts +12 -0
- package/dist/engine/diff.js +51 -0
- package/dist/errors.d.ts +3 -0
- package/dist/errors.js +5 -0
- package/dist/extras/snapshot.d.ts +3 -0
- package/dist/extras/snapshot.js +99 -0
- package/dist/files.d.ts +1 -0
- package/dist/files.js +15 -0
- package/dist/generate/generate-modules.d.ts +17 -0
- package/dist/generate/generate-modules.js +97 -0
- package/dist/generate/modules.d.ts +16 -0
- package/dist/generate/modules.js +118 -0
- package/dist/generate/pg-dump.d.ts +8 -0
- package/dist/generate/pg-dump.js +66 -0
- package/dist/generate/plugin-wrapper.d.ts +3 -0
- package/dist/generate/plugin-wrapper.js +20 -0
- package/dist/generate/sqlc.d.ts +27 -0
- package/dist/generate/sqlc.js +113 -0
- package/dist/generate/watch.d.ts +16 -0
- package/dist/generate/watch.js +73 -0
- package/dist/generator/generate.d.ts +21 -0
- package/dist/generator/generate.js +440 -0
- package/dist/generator/keys.d.ts +4 -0
- package/dist/generator/keys.js +24 -0
- package/dist/generator/lexer.d.ts +12 -0
- package/dist/generator/lexer.js +210 -0
- package/dist/generator/main.d.ts +1 -0
- package/dist/generator/main.js +12 -0
- package/dist/generator/pg-array-helper.d.ts +2 -0
- package/dist/generator/pg-array-helper.js +53 -0
- package/dist/generator/plugin.d.ts +6 -0
- package/dist/generator/plugin.js +26 -0
- package/dist/generator/request.d.ts +57 -0
- package/dist/generator/request.js +96 -0
- package/dist/generator/select-list.d.ts +27 -0
- package/dist/generator/select-list.js +580 -0
- package/dist/generator/strings.d.ts +2 -0
- package/dist/generator/strings.js +34 -0
- package/dist/generator/types.d.ts +23 -0
- package/dist/generator/types.js +216 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/migrate/adopt-prisma.d.ts +23 -0
- package/dist/migrate/adopt-prisma.js +119 -0
- package/dist/migrate/deploy.d.ts +7 -0
- package/dist/migrate/deploy.js +23 -0
- package/dist/migrate/drift.d.ts +10 -0
- package/dist/migrate/drift.js +36 -0
- package/dist/migrate/files.d.ts +19 -0
- package/dist/migrate/files.js +97 -0
- package/dist/migrate/history.d.ts +20 -0
- package/dist/migrate/history.js +54 -0
- package/dist/migrate/lint.d.ts +21 -0
- package/dist/migrate/lint.js +153 -0
- package/dist/migrate/new.d.ts +13 -0
- package/dist/migrate/new.js +41 -0
- package/dist/migrate/runner.d.ts +28 -0
- package/dist/migrate/runner.js +124 -0
- package/dist/migrate/sql-text.d.ts +33 -0
- package/dist/migrate/sql-text.js +233 -0
- package/dist/schema/schema.d.ts +16 -0
- package/dist/schema/schema.js +111 -0
- package/dist/schema/unsupported.d.ts +3 -0
- package/dist/schema/unsupported.js +36 -0
- package/dist/schema/wasm.d.ts +8 -0
- package/dist/schema/wasm.js +32 -0
- package/package.json +50 -4
- package/vendor/prisma-schema-wasm/LICENSE +201 -0
- package/vendor/prisma-schema-wasm/README.md +32 -0
- package/vendor/prisma-schema-wasm/package.json +3 -0
- package/vendor/prisma-schema-wasm/prisma_schema_build.js +539 -0
- package/vendor/prisma-schema-wasm/prisma_schema_build_bg.wasm +0 -0
package/binaries.json
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tools": {
|
|
3
|
+
"sqlc": {
|
|
4
|
+
"version": "1.31.1",
|
|
5
|
+
"executable": "sqlc",
|
|
6
|
+
"platforms": {
|
|
7
|
+
"darwin-arm64": {
|
|
8
|
+
"platform": "darwin_arm64",
|
|
9
|
+
"asset": "sqlc_1.31.1_darwin_arm64.tar.gz",
|
|
10
|
+
"archive": "tar.gz",
|
|
11
|
+
"member": "sqlc",
|
|
12
|
+
"upstream": "https://github.com/sqlc-dev/sqlc/releases/download/v1.31.1/sqlc_1.31.1_darwin_arm64.tar.gz",
|
|
13
|
+
"mirror": "https://github.com/Bockit/sequel/releases/download/bin-sqlc-1.31.1/sqlc_1.31.1_darwin_arm64.tar.gz",
|
|
14
|
+
"assetSha256": "21602158c99eb1f2bae197a66abfb1941d1e9e50b23125bb193349c6b1acc71e",
|
|
15
|
+
"executableSha256": "7e63a8b2166f1103a2ad1d2e5d05d8fe3f13ca65ecf2774a2570b830d32055fd"
|
|
16
|
+
},
|
|
17
|
+
"linux-x64": {
|
|
18
|
+
"platform": "linux_amd64",
|
|
19
|
+
"asset": "sqlc_1.31.1_linux_amd64.tar.gz",
|
|
20
|
+
"archive": "tar.gz",
|
|
21
|
+
"member": "sqlc",
|
|
22
|
+
"upstream": "https://github.com/sqlc-dev/sqlc/releases/download/v1.31.1/sqlc_1.31.1_linux_amd64.tar.gz",
|
|
23
|
+
"mirror": "https://github.com/Bockit/sequel/releases/download/bin-sqlc-1.31.1/sqlc_1.31.1_linux_amd64.tar.gz",
|
|
24
|
+
"assetSha256": "497ae4fcdfa64c5b0c311ffe4c2bd991e43991e82e5367792ed78bc2dca27354",
|
|
25
|
+
"executableSha256": "0496fbc18f603e2fbd10c752942d1389a1fa9bc3d18de7243ec00cccd611575f"
|
|
26
|
+
},
|
|
27
|
+
"linux-arm64": {
|
|
28
|
+
"platform": "linux_arm64",
|
|
29
|
+
"asset": "sqlc_1.31.1_linux_arm64.tar.gz",
|
|
30
|
+
"archive": "tar.gz",
|
|
31
|
+
"member": "sqlc",
|
|
32
|
+
"upstream": "https://github.com/sqlc-dev/sqlc/releases/download/v1.31.1/sqlc_1.31.1_linux_arm64.tar.gz",
|
|
33
|
+
"mirror": "https://github.com/Bockit/sequel/releases/download/bin-sqlc-1.31.1/sqlc_1.31.1_linux_arm64.tar.gz",
|
|
34
|
+
"assetSha256": "b7cae247740d0c51a1e657479e5b2d21e6fef428f596682a01bc55bf4ab8a23d",
|
|
35
|
+
"executableSha256": "3b42bdbc5be1d534b95fc6b9f0e1572e6aa555fda3c96f208cb26abb1bb77d7e"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"squawk": {
|
|
40
|
+
"version": "2.71.0",
|
|
41
|
+
"executable": "squawk",
|
|
42
|
+
"platforms": {
|
|
43
|
+
"darwin-arm64": {
|
|
44
|
+
"platform": "darwin-arm64",
|
|
45
|
+
"asset": "squawk-darwin-arm64",
|
|
46
|
+
"archive": "none",
|
|
47
|
+
"upstream": "https://github.com/sbdchd/squawk/releases/download/v2.71.0/squawk-darwin-arm64",
|
|
48
|
+
"mirror": "https://github.com/Bockit/sequel/releases/download/bin-squawk-2.71.0/squawk-darwin-arm64",
|
|
49
|
+
"assetSha256": "6132cb9779c688303e43746e10268f280b35a3fbcc70b851472021cc882ef5a2",
|
|
50
|
+
"executableSha256": "6132cb9779c688303e43746e10268f280b35a3fbcc70b851472021cc882ef5a2"
|
|
51
|
+
},
|
|
52
|
+
"linux-x64": {
|
|
53
|
+
"platform": "linux-musl-x64",
|
|
54
|
+
"asset": "squawk-linux-musl-x64",
|
|
55
|
+
"archive": "none",
|
|
56
|
+
"upstream": "https://github.com/sbdchd/squawk/releases/download/v2.71.0/squawk-linux-musl-x64",
|
|
57
|
+
"mirror": "https://github.com/Bockit/sequel/releases/download/bin-squawk-2.71.0/squawk-linux-musl-x64",
|
|
58
|
+
"assetSha256": "cf542e159fb3a94e5fd31d8b5282a31334df8205c6d7993afe286e9133fdff3e",
|
|
59
|
+
"executableSha256": "cf542e159fb3a94e5fd31d8b5282a31334df8205c6d7993afe286e9133fdff3e"
|
|
60
|
+
},
|
|
61
|
+
"linux-arm64": {
|
|
62
|
+
"platform": "linux-musl-arm64",
|
|
63
|
+
"asset": "squawk-linux-musl-arm64",
|
|
64
|
+
"archive": "none",
|
|
65
|
+
"upstream": "https://github.com/sbdchd/squawk/releases/download/v2.71.0/squawk-linux-musl-arm64",
|
|
66
|
+
"mirror": "https://github.com/Bockit/sequel/releases/download/bin-squawk-2.71.0/squawk-linux-musl-arm64",
|
|
67
|
+
"assetSha256": "3b350a7ad426cd4f000b9b43211b19d8a724392ad715ad454156653aadf77347",
|
|
68
|
+
"executableSha256": "3b350a7ad426cd4f000b9b43211b19d8a724392ad715ad454156653aadf77347"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"schema-engine": {
|
|
73
|
+
"version": "8.1.0-1.d1b3f377ca835d73fd50954a5e22128b9ebdd6b8",
|
|
74
|
+
"executable": "schema-engine",
|
|
75
|
+
"platforms": {
|
|
76
|
+
"darwin-arm64": {
|
|
77
|
+
"platform": "darwin-arm64",
|
|
78
|
+
"asset": "schema-engine-darwin-arm64.gz",
|
|
79
|
+
"archive": "gz",
|
|
80
|
+
"upstream": "https://binaries.prisma.sh/all_commits/d1b3f377ca835d73fd50954a5e22128b9ebdd6b8/darwin-arm64/schema-engine.gz",
|
|
81
|
+
"mirror": "https://github.com/Bockit/sequel/releases/download/bin-schema-engine-8.1.0-1.d1b3f377ca835d73fd50954a5e22128b9ebdd6b8/schema-engine-darwin-arm64.gz",
|
|
82
|
+
"assetSha256": "d7e50db1fa19f659dc95050e0e5b23bd3699c5db2f33bd0c43cb3e9bf7df3dc6",
|
|
83
|
+
"executableSha256": "6032ea1bdbd6dc3d453c36a22838d1d3b3554053083bf7695c104e9993637ba1"
|
|
84
|
+
},
|
|
85
|
+
"linux-x64": {
|
|
86
|
+
"platform": "linux-static-x64",
|
|
87
|
+
"asset": "schema-engine-linux-static-x64.gz",
|
|
88
|
+
"archive": "gz",
|
|
89
|
+
"upstream": "https://binaries.prisma.sh/all_commits/d1b3f377ca835d73fd50954a5e22128b9ebdd6b8/linux-static-x64/schema-engine.gz",
|
|
90
|
+
"mirror": "https://github.com/Bockit/sequel/releases/download/bin-schema-engine-8.1.0-1.d1b3f377ca835d73fd50954a5e22128b9ebdd6b8/schema-engine-linux-static-x64.gz",
|
|
91
|
+
"assetSha256": "1f609ddc58e6f5e21934976311162f1e5daa954fd6359c55ef65fee2033c45b4",
|
|
92
|
+
"executableSha256": "ec4adf16b03f1a37bae5965e4a8f265dccb48096fe03ec253385609babbddfaf"
|
|
93
|
+
},
|
|
94
|
+
"linux-arm64": {
|
|
95
|
+
"platform": "linux-static-arm64",
|
|
96
|
+
"asset": "schema-engine-linux-static-arm64.gz",
|
|
97
|
+
"archive": "gz",
|
|
98
|
+
"upstream": "https://binaries.prisma.sh/all_commits/d1b3f377ca835d73fd50954a5e22128b9ebdd6b8/linux-static-arm64/schema-engine.gz",
|
|
99
|
+
"mirror": "https://github.com/Bockit/sequel/releases/download/bin-schema-engine-8.1.0-1.d1b3f377ca835d73fd50954a5e22128b9ebdd6b8/schema-engine-linux-static-arm64.gz",
|
|
100
|
+
"assetSha256": "a80add0047a24ef33cc8e87070caa65a107a406addf3341bc4ccad72c147bbd4",
|
|
101
|
+
"executableSha256": "edd9415943a93f06ca11564f1fb151e6966410bb35d52ab1fe6d5d613fce91ab"
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Platform } from './platform.ts';
|
|
2
|
+
export declare function cacheRoot(env?: NodeJS.ProcessEnv, home?: string): string;
|
|
3
|
+
export declare function cachedPath(root: string, tool: string, version: string, platform: Platform, executable: string): string;
|
|
4
|
+
export declare function makeWorkDir(root: string): Promise<string>;
|
|
5
|
+
export declare function installExecutable(source: string, destination: string): Promise<void>;
|
|
6
|
+
export declare function removeWorkDir(dir: string): Promise<void>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { chmod, mkdir, rename, rm } from 'node:fs/promises';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
// `$SEQUEL_CACHE_DIR`, else `$XDG_CACHE_HOME/sequel`, else `~/.cache/sequel`.
|
|
6
|
+
export function cacheRoot(env = process.env, home = homedir()) {
|
|
7
|
+
if (env.SEQUEL_CACHE_DIR)
|
|
8
|
+
return path.resolve(env.SEQUEL_CACHE_DIR);
|
|
9
|
+
// The XDG spec says a relative XDG_CACHE_HOME is invalid and is to be ignored.
|
|
10
|
+
if (env.XDG_CACHE_HOME && path.isAbsolute(env.XDG_CACHE_HOME)) {
|
|
11
|
+
return path.join(env.XDG_CACHE_HOME, 'sequel');
|
|
12
|
+
}
|
|
13
|
+
return path.join(home, '.cache', 'sequel');
|
|
14
|
+
}
|
|
15
|
+
// Where a verified executable lives: keyed by tool, version and platform.
|
|
16
|
+
export function cachedPath(root, tool, version, platform, executable) {
|
|
17
|
+
return path.join(root, tool, version, platform, executable);
|
|
18
|
+
}
|
|
19
|
+
// A fresh working folder inside the cache, on the same filesystem as the executables, so a
|
|
20
|
+
// finished file can be renamed into place. The caller removes it.
|
|
21
|
+
export async function makeWorkDir(root) {
|
|
22
|
+
const dir = path.join(root, 'tmp', `${process.pid}-${randomBytes(6).toString('hex')}`);
|
|
23
|
+
await mkdir(dir, { recursive: true });
|
|
24
|
+
return dir;
|
|
25
|
+
}
|
|
26
|
+
// Makes `source` executable and renames it to `destination`. The rename is atomic, so a
|
|
27
|
+
// concurrent run or an interrupted one never sees a partial file at `destination`, and two
|
|
28
|
+
// runs installing the same verified file at once both succeed.
|
|
29
|
+
export async function installExecutable(source, destination) {
|
|
30
|
+
await chmod(source, 0o755);
|
|
31
|
+
await mkdir(path.dirname(destination), { recursive: true });
|
|
32
|
+
await rename(source, destination);
|
|
33
|
+
}
|
|
34
|
+
export async function removeWorkDir(dir) {
|
|
35
|
+
await rm(dir, { recursive: true, force: true });
|
|
36
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type Manifest } from './manifest.ts';
|
|
2
|
+
import { type Platform } from './platform.ts';
|
|
3
|
+
export type EnsureBinaryOptions = {
|
|
4
|
+
manifest?: Manifest;
|
|
5
|
+
platform?: Platform;
|
|
6
|
+
cacheDir?: string;
|
|
7
|
+
fetch?: typeof globalThis.fetch;
|
|
8
|
+
log?: (message: string) => void;
|
|
9
|
+
};
|
|
10
|
+
export declare function ensureBinary(tool: string, options?: EnsureBinaryOptions): Promise<string>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { createWriteStream, existsSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { Readable } from 'node:stream';
|
|
4
|
+
import { pipeline } from 'node:stream/promises';
|
|
5
|
+
import { SequelError } from '../errors.js';
|
|
6
|
+
import { cachedPath, cacheRoot, installExecutable, makeWorkDir, removeWorkDir } from './cache.js';
|
|
7
|
+
import { loadManifest, manifestEntry } from './manifest.js';
|
|
8
|
+
import { currentPlatform } from './platform.js';
|
|
9
|
+
import { unpack } from './unpack.js';
|
|
10
|
+
import { sha256File, verifySha256 } from './verify.js';
|
|
11
|
+
const DOWNLOAD_TIMEOUT_MS = 10 * 60_000;
|
|
12
|
+
// Executables verified by this process, keyed by path and expected hash. Each process checks
|
|
13
|
+
// a cached binary's sha256 once, before its first use, and shares the check between callers.
|
|
14
|
+
const verified = new Map();
|
|
15
|
+
// Returns the path of a pinned tool's verified executable, fetching it into the cache on
|
|
16
|
+
// first use: from the mirror, then upstream.
|
|
17
|
+
export function ensureBinary(tool, options = {}) {
|
|
18
|
+
const manifest = options.manifest ?? loadManifest();
|
|
19
|
+
const platform = options.platform ?? currentPlatform();
|
|
20
|
+
const root = options.cacheDir ?? cacheRoot();
|
|
21
|
+
const { version, executable, entry } = manifestEntry(manifest, tool, platform);
|
|
22
|
+
const destination = cachedPath(root, tool, version, platform, executable);
|
|
23
|
+
const key = `${destination}\0${entry.executableSha256}`;
|
|
24
|
+
let pending = verified.get(key);
|
|
25
|
+
if (pending === undefined) {
|
|
26
|
+
pending = prepare({
|
|
27
|
+
label: `${tool} ${version} (${platform})`,
|
|
28
|
+
entry,
|
|
29
|
+
root,
|
|
30
|
+
destination,
|
|
31
|
+
fetch: options.fetch ?? globalThis.fetch,
|
|
32
|
+
log: options.log ?? ((message) => process.stderr.write(`${message}\n`)),
|
|
33
|
+
});
|
|
34
|
+
verified.set(key, pending);
|
|
35
|
+
// A failure isn't remembered, so a later call in the same process tries again.
|
|
36
|
+
pending.catch(() => verified.delete(key));
|
|
37
|
+
}
|
|
38
|
+
return pending;
|
|
39
|
+
}
|
|
40
|
+
async function prepare(job) {
|
|
41
|
+
const { label, entry, destination } = job;
|
|
42
|
+
if (existsSync(destination)) {
|
|
43
|
+
const actual = await sha256File(destination);
|
|
44
|
+
if (actual === entry.executableSha256)
|
|
45
|
+
return destination;
|
|
46
|
+
throw new SequelError(`The cached ${label} at ${destination} failed its checksum: expected sha256 ${entry.executableSha256}, got ${actual}. It changed after it was verified, so it was not run. Delete ${path.dirname(destination)} and rerun to fetch it again.`);
|
|
47
|
+
}
|
|
48
|
+
job.log(`Fetching ${label}…`);
|
|
49
|
+
const workDir = await makeWorkDir(job.root);
|
|
50
|
+
try {
|
|
51
|
+
const asset = await download(job, workDir);
|
|
52
|
+
const unpacked = await unpack(asset, entry, workDir);
|
|
53
|
+
await verifySha256(unpacked, entry.executableSha256, `The executable in ${entry.asset}`);
|
|
54
|
+
await installExecutable(unpacked, destination);
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
await removeWorkDir(workDir);
|
|
58
|
+
}
|
|
59
|
+
return destination;
|
|
60
|
+
}
|
|
61
|
+
// Downloads the asset and verifies it, trying the mirror first and then upstream. A source
|
|
62
|
+
// that fails, or serves bytes with the wrong checksum, is passed over for the next.
|
|
63
|
+
async function download(job, workDir) {
|
|
64
|
+
const { entry } = job;
|
|
65
|
+
const file = path.join(workDir, 'asset');
|
|
66
|
+
const failures = [];
|
|
67
|
+
for (const [source, url] of [
|
|
68
|
+
['mirror', entry.mirror],
|
|
69
|
+
['upstream', entry.upstream],
|
|
70
|
+
]) {
|
|
71
|
+
try {
|
|
72
|
+
const response = await job.fetch(url, { signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS) });
|
|
73
|
+
if (!response.ok || response.body === null) {
|
|
74
|
+
throw new Error(`HTTP ${response.status}`);
|
|
75
|
+
}
|
|
76
|
+
await pipeline(Readable.fromWeb(response.body), createWriteStream(file));
|
|
77
|
+
await verifySha256(file, entry.assetSha256, entry.asset);
|
|
78
|
+
return file;
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
failures.push(` ${source} ${url}: ${error.message}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
throw new SequelError(`Could not fetch ${job.label}:\n${failures.join('\n')}\nCheck the network connection and rerun.`);
|
|
85
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type Platform } from './platform.ts';
|
|
2
|
+
export type Archive = 'none' | 'tar.gz' | 'gz';
|
|
3
|
+
export type PlatformEntry = {
|
|
4
|
+
platform: string;
|
|
5
|
+
asset: string;
|
|
6
|
+
archive: Archive;
|
|
7
|
+
member?: string;
|
|
8
|
+
upstream: string;
|
|
9
|
+
mirror: string;
|
|
10
|
+
assetSha256: string;
|
|
11
|
+
executableSha256: string;
|
|
12
|
+
};
|
|
13
|
+
export type ToolEntry = {
|
|
14
|
+
version: string;
|
|
15
|
+
executable: string;
|
|
16
|
+
platforms: Record<Platform, PlatformEntry>;
|
|
17
|
+
};
|
|
18
|
+
export type Manifest = {
|
|
19
|
+
tools: Record<string, ToolEntry>;
|
|
20
|
+
};
|
|
21
|
+
export declare const MANIFEST_FILE: string;
|
|
22
|
+
export declare const MIRROR_PREFIX = "https://github.com/Bockit/sequel/releases/download/";
|
|
23
|
+
export declare function loadManifest(file?: string): Manifest;
|
|
24
|
+
export declare function parseManifest(json: unknown, file?: string): Manifest;
|
|
25
|
+
export declare function manifestEntry(manifest: Manifest, tool: string, platform: Platform): {
|
|
26
|
+
version: string;
|
|
27
|
+
executable: string;
|
|
28
|
+
entry: PlatformEntry;
|
|
29
|
+
};
|
|
30
|
+
export declare function mirrorTag(entry: PlatformEntry): string | undefined;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { SequelError } from '../errors.js';
|
|
5
|
+
import { PLATFORMS } from './platform.js';
|
|
6
|
+
export const MANIFEST_FILE = fileURLToPath(new URL('../../binaries.json', import.meta.url));
|
|
7
|
+
export const MIRROR_PREFIX = 'https://github.com/Bockit/sequel/releases/download/';
|
|
8
|
+
const SHA256 = /^[0-9a-f]{64}$/;
|
|
9
|
+
const ARCHIVES = ['none', 'tar.gz', 'gz'];
|
|
10
|
+
export function loadManifest(file = MANIFEST_FILE) {
|
|
11
|
+
let json;
|
|
12
|
+
try {
|
|
13
|
+
json = JSON.parse(readFileSync(file, 'utf8'));
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
throw new SequelError(`${file} could not be read: ${error.message}`);
|
|
17
|
+
}
|
|
18
|
+
return parseManifest(json, file);
|
|
19
|
+
}
|
|
20
|
+
// Checks the manifest's shape, so a bad edit fails on load rather than mid-download.
|
|
21
|
+
export function parseManifest(json, file = 'binaries.json') {
|
|
22
|
+
const fail = (where, problem) => {
|
|
23
|
+
throw new SequelError(where ? `${file}: \`${where}\` ${problem}` : `${file} ${problem}`);
|
|
24
|
+
};
|
|
25
|
+
const tools = object(object(json, '', fail).tools, 'tools', fail);
|
|
26
|
+
for (const [name, toolValue] of Object.entries(tools)) {
|
|
27
|
+
const tool = object(toolValue, `tools.${name}`, fail);
|
|
28
|
+
for (const key of ['version', 'executable']) {
|
|
29
|
+
if (typeof tool[key] !== 'string' || tool[key] === '') {
|
|
30
|
+
fail(`tools.${name}.${key}`, 'must be a non-empty string.');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (tool.executable.includes('/')) {
|
|
34
|
+
fail(`tools.${name}.executable`, 'must be a file name, not a path.');
|
|
35
|
+
}
|
|
36
|
+
const platforms = object(tool.platforms, `tools.${name}.platforms`, fail);
|
|
37
|
+
const keys = Object.keys(platforms).sort();
|
|
38
|
+
if (keys.join() !== [...PLATFORMS].sort().join()) {
|
|
39
|
+
fail(`tools.${name}.platforms`, `must have exactly ${PLATFORMS.join(', ')}; it has ${keys.join(', ') || 'none'}.`);
|
|
40
|
+
}
|
|
41
|
+
for (const platform of PLATFORMS) {
|
|
42
|
+
checkEntry(platforms[platform], `tools.${name}.platforms.${platform}`, fail);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return json;
|
|
46
|
+
}
|
|
47
|
+
// The manifest entry for one tool on one platform.
|
|
48
|
+
export function manifestEntry(manifest, tool, platform) {
|
|
49
|
+
const toolEntry = manifest.tools[tool];
|
|
50
|
+
if (toolEntry === undefined) {
|
|
51
|
+
throw new SequelError(`binaries.json has no entry for ${tool}.`);
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
version: toolEntry.version,
|
|
55
|
+
executable: toolEntry.executable,
|
|
56
|
+
entry: toolEntry.platforms[platform],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
// The `Bockit/sequel` release tag a mirror URL points into, or undefined if it isn't one.
|
|
60
|
+
export function mirrorTag(entry) {
|
|
61
|
+
const suffix = `/${entry.asset}`;
|
|
62
|
+
if (!entry.mirror.startsWith(MIRROR_PREFIX) || !entry.mirror.endsWith(suffix))
|
|
63
|
+
return undefined;
|
|
64
|
+
const tag = entry.mirror.slice(MIRROR_PREFIX.length, -suffix.length);
|
|
65
|
+
return /^[A-Za-z0-9._-]+$/.test(tag) ? tag : undefined;
|
|
66
|
+
}
|
|
67
|
+
function object(value, where, fail) {
|
|
68
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
69
|
+
fail(where, 'must be an object.');
|
|
70
|
+
}
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
function checkEntry(value, where, fail) {
|
|
74
|
+
const entry = object(value, where, fail);
|
|
75
|
+
for (const key of ['platform', 'asset', 'upstream', 'mirror']) {
|
|
76
|
+
if (typeof entry[key] !== 'string' || entry[key] === '') {
|
|
77
|
+
fail(`${where}.${key}`, 'must be a non-empty string.');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
for (const key of ['assetSha256', 'executableSha256']) {
|
|
81
|
+
if (typeof entry[key] !== 'string' || !SHA256.test(entry[key])) {
|
|
82
|
+
fail(`${where}.${key}`, 'must be a sha256: 64 lower-case hex digits.');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (!ARCHIVES.includes(entry.archive)) {
|
|
86
|
+
fail(`${where}.archive`, `must be one of ${ARCHIVES.join(', ')}.`);
|
|
87
|
+
}
|
|
88
|
+
if (entry.asset.includes('/')) {
|
|
89
|
+
fail(`${where}.asset`, 'must be a file name, not a path.');
|
|
90
|
+
}
|
|
91
|
+
if (entry.archive === 'tar.gz') {
|
|
92
|
+
const member = entry.member;
|
|
93
|
+
if (typeof member !== 'string' || member === '') {
|
|
94
|
+
fail(`${where}.member`, 'must name the executable inside the tar.gz.');
|
|
95
|
+
}
|
|
96
|
+
if (path.isAbsolute(member) || member.split('/').includes('..')) {
|
|
97
|
+
fail(`${where}.member`, 'must be a relative path inside the archive.');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else if (entry.member !== undefined) {
|
|
101
|
+
fail(`${where}.member`, 'is only for tar.gz assets.');
|
|
102
|
+
}
|
|
103
|
+
if (entry.archive === 'none' && entry.assetSha256 !== entry.executableSha256) {
|
|
104
|
+
fail(where, 'is a bare executable, so its two sha256s must be the same.');
|
|
105
|
+
}
|
|
106
|
+
if (!entry.upstream.startsWith('https://')) {
|
|
107
|
+
fail(`${where}.upstream`, 'must be an https URL.');
|
|
108
|
+
}
|
|
109
|
+
if (mirrorTag(entry) === undefined) {
|
|
110
|
+
fail(`${where}.mirror`, `must be ${MIRROR_PREFIX}<tag>/${entry.asset}.`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { SequelError } from '../errors.js';
|
|
2
|
+
export const PLATFORMS = ['darwin-arm64', 'linux-x64', 'linux-arm64'];
|
|
3
|
+
// sequel's name for the platform it runs on. Each manifest entry maps it to the tool's own name.
|
|
4
|
+
export function currentPlatform(os = process.platform, arch = process.arch) {
|
|
5
|
+
const platform = `${os}-${arch}`;
|
|
6
|
+
if (!PLATFORMS.includes(platform)) {
|
|
7
|
+
throw new SequelError(`sequel does not support ${platform}. Its pinned tools are built for ${PLATFORMS.join(', ')}.`);
|
|
8
|
+
}
|
|
9
|
+
return platform;
|
|
10
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { createReadStream, createWriteStream } from 'node:fs';
|
|
3
|
+
import { mkdir } from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { pipeline } from 'node:stream/promises';
|
|
6
|
+
import { promisify } from 'node:util';
|
|
7
|
+
import { createGunzip } from 'node:zlib';
|
|
8
|
+
import { SequelError } from '../errors.js';
|
|
9
|
+
const run = promisify(execFile);
|
|
10
|
+
// Extracts the executable from a verified asset into `workDir`, and returns its path.
|
|
11
|
+
// Only call this after the asset's checksum has passed.
|
|
12
|
+
export async function unpack(asset, entry, workDir) {
|
|
13
|
+
const output = path.join(workDir, 'executable');
|
|
14
|
+
switch (entry.archive) {
|
|
15
|
+
case 'none':
|
|
16
|
+
return asset;
|
|
17
|
+
case 'gz':
|
|
18
|
+
await pipeline(createReadStream(asset), createGunzip(), createWriteStream(output));
|
|
19
|
+
return output;
|
|
20
|
+
case 'tar.gz': {
|
|
21
|
+
const member = entry.member;
|
|
22
|
+
const dir = path.join(workDir, 'unpacked');
|
|
23
|
+
await mkdir(dir);
|
|
24
|
+
try {
|
|
25
|
+
// The system tar, present on every supported platform. Only the named member is
|
|
26
|
+
// extracted, and both GNU tar and bsdtar refuse members that escape `dir`.
|
|
27
|
+
await run('tar', ['-xzf', asset, '-C', dir, member]);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
throw new SequelError(`Could not extract ${member} from ${entry.asset} with tar: ${error.message.trim()}`);
|
|
31
|
+
}
|
|
32
|
+
return path.join(dir, member);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { createReadStream } from 'node:fs';
|
|
3
|
+
import { SequelError } from '../errors.js';
|
|
4
|
+
export async function sha256File(file) {
|
|
5
|
+
const hash = createHash('sha256');
|
|
6
|
+
for await (const chunk of createReadStream(file))
|
|
7
|
+
hash.update(chunk);
|
|
8
|
+
return hash.digest('hex');
|
|
9
|
+
}
|
|
10
|
+
// Refuses a file whose sha256 isn't the one recorded in the manifest. `what` names it.
|
|
11
|
+
export async function verifySha256(file, expected, what) {
|
|
12
|
+
const actual = await sha256File(file);
|
|
13
|
+
if (actual !== expected) {
|
|
14
|
+
throw new SequelError(`${what} failed its checksum: expected sha256 ${expected}, got ${actual}. It was not used.`);
|
|
15
|
+
}
|
|
16
|
+
}
|
package/dist/check.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ResolvedConfig } from './config/resolve-config.ts';
|
|
2
|
+
export type CheckFailure = {
|
|
3
|
+
problem: string;
|
|
4
|
+
details: string[];
|
|
5
|
+
fix: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function runChecks(config: ResolvedConfig, log: (message: string) => void): Promise<CheckFailure[]>;
|
|
8
|
+
export declare function lineChanges(current: string, next: string): string[];
|
|
9
|
+
export declare function checkFailureMessage(failures: CheckFailure[]): string;
|
package/dist/check.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { ensureBinary } from './binaries/ensure-binary.js';
|
|
4
|
+
import { requireDatabase } from './config/resolve-config.js';
|
|
5
|
+
import { SequelError } from './errors.js';
|
|
6
|
+
import { readExtrasSnapshot } from './extras/snapshot.js';
|
|
7
|
+
import { checkModules } from './generate/generate-modules.js';
|
|
8
|
+
import { findQueryFolders } from './generate/modules.js';
|
|
9
|
+
import { deployTo } from './migrate/deploy.js';
|
|
10
|
+
import { diffMigrationsToSchema } from './migrate/drift.js';
|
|
11
|
+
import { findMigrations } from './migrate/files.js';
|
|
12
|
+
import { formatFinding, lintMigrations, migrationsToLint } from './migrate/lint.js';
|
|
13
|
+
import { readSchemaFiles, validateSchema } from './schema/schema.js';
|
|
14
|
+
import { unsupportedColumns } from './schema/unsupported.js';
|
|
15
|
+
// SPEC §3's `check`: brings the test database up to the latest migration, then runs every check
|
|
16
|
+
// and returns every failure, in SPEC's order. It writes no files. A check that can't run because
|
|
17
|
+
// an earlier one failed (drift needs a valid schema, say) is skipped, and the earlier failure
|
|
18
|
+
// says why.
|
|
19
|
+
export async function runChecks(config, log) {
|
|
20
|
+
const testUrl = requireDatabase(config, 'test');
|
|
21
|
+
const shown = (file) => path.relative(config.root, file);
|
|
22
|
+
const failures = [];
|
|
23
|
+
const fail = (order, failure) => failures.push({ order, failure });
|
|
24
|
+
const expected = async (order, problem, fix, body) => {
|
|
25
|
+
try {
|
|
26
|
+
return await body();
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
if (!(error instanceof SequelError))
|
|
30
|
+
throw error;
|
|
31
|
+
fail(order, { problem, details: [error.message], fix });
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
// The schema folder.
|
|
36
|
+
const files = await expected(5, "The schema folder can't be read", "Fix the config's `schema`, then run `sequel check` again.", () => readSchemaFiles(config.schema, config.root));
|
|
37
|
+
let schemaValid = false;
|
|
38
|
+
if (files !== undefined) {
|
|
39
|
+
const message = validateSchema(files, config.root);
|
|
40
|
+
if (message === undefined)
|
|
41
|
+
schemaValid = true;
|
|
42
|
+
else {
|
|
43
|
+
fail(5, {
|
|
44
|
+
problem: "The schema folder doesn't validate",
|
|
45
|
+
details: [message],
|
|
46
|
+
fix: 'Fix the schema. `sequel validate` shows the same errors.',
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// The migrations, and the test database.
|
|
51
|
+
const migrations = await expected(2, "The migrations can't be read", 'Fix the files named, then run `sequel check` again.', () => findMigrations(config.migrations, { root: config.root }));
|
|
52
|
+
const deployed = migrations !== undefined &&
|
|
53
|
+
(await expected(1, "The test database can't be brought up to the latest migration", 'Fix the migration named, then run `sequel check` again.', async () => {
|
|
54
|
+
await deployTo(config, testUrl, { log, quietWhenCurrent: true });
|
|
55
|
+
return true;
|
|
56
|
+
})) === true;
|
|
57
|
+
// 1. Generated modules.
|
|
58
|
+
if (deployed) {
|
|
59
|
+
const modules = await expected(1, "The query modules can't be generated", 'Fix the queries named. `sequel generate` reports the same errors.', async () => checkModules({
|
|
60
|
+
sqlc: await ensureBinary('sqlc'),
|
|
61
|
+
databaseUrl: testUrl,
|
|
62
|
+
folders: await findQueryFolders(config.queries),
|
|
63
|
+
bigint: config.types.bigint,
|
|
64
|
+
root: config.root,
|
|
65
|
+
}));
|
|
66
|
+
if (modules !== undefined) {
|
|
67
|
+
const details = [
|
|
68
|
+
...modules.stale.map((file) => `stale: ${shown(file)}`),
|
|
69
|
+
...modules.missing.map((file) => `missing: ${shown(file)}`),
|
|
70
|
+
...modules.orphaned.map((file) => `orphaned: ${shown(file)} (its query file is gone)`),
|
|
71
|
+
];
|
|
72
|
+
if (details.length > 0) {
|
|
73
|
+
fail(1, {
|
|
74
|
+
problem: 'Generated modules are out of date',
|
|
75
|
+
details,
|
|
76
|
+
fix: 'Run `sequel generate`, and commit the result.',
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// 2. Drift.
|
|
82
|
+
if (migrations !== undefined && files !== undefined && schemaValid) {
|
|
83
|
+
const diff = await expected(2, "Drift between the migrations and the schema folder can't be checked", 'Fix the problem named, then run `sequel check` again.', () => diffMigrationsToSchema({ config, migrations, files }));
|
|
84
|
+
if (diff?.changed) {
|
|
85
|
+
fail(2, {
|
|
86
|
+
problem: 'The migrations and the schema folder differ. Replayed, the migrations need this to match the schema',
|
|
87
|
+
details: [diff.sql.trimEnd()],
|
|
88
|
+
fix: 'Run `sequel migrate new <name>` to write a migration for the difference, or change the schema to match the migrations.',
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// 3. Lint after the cut-over.
|
|
93
|
+
if (migrations !== undefined) {
|
|
94
|
+
const findings = await expected(3, "The migrations can't be linted", 'Fix the problem named, then run `sequel check` again.', async () => lintMigrations({
|
|
95
|
+
squawk: await ensureBinary('squawk'),
|
|
96
|
+
migrations: migrationsToLint(migrations, config.lint.cutover),
|
|
97
|
+
root: config.root,
|
|
98
|
+
...(files === undefined ? {} : { unsupported: unsupportedColumns(files) }),
|
|
99
|
+
}));
|
|
100
|
+
if (findings !== undefined && findings.length > 0) {
|
|
101
|
+
fail(3, {
|
|
102
|
+
problem: `Migrations after the lint cut-over have ${findings.length} lint finding${findings.length === 1 ? '' : 's'}`,
|
|
103
|
+
details: findings.map(formatFinding),
|
|
104
|
+
fix: 'Fix each, or suppress a Squawk finding with `-- squawk-ignore <rule> -- <reason>`. `sequel migrate lint` lists them.',
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// 4. The extras snapshot.
|
|
109
|
+
if (deployed) {
|
|
110
|
+
const next = await readExtrasSnapshot(testUrl);
|
|
111
|
+
const current = await readFile(config.snapshot, 'utf8').catch(() => undefined);
|
|
112
|
+
if (current !== next) {
|
|
113
|
+
fail(4, {
|
|
114
|
+
problem: current === undefined
|
|
115
|
+
? `The extras snapshot ${shown(config.snapshot)} is missing`
|
|
116
|
+
: `The extras snapshot ${shown(config.snapshot)} is stale`,
|
|
117
|
+
details: current === undefined ? [] : lineChanges(current, next),
|
|
118
|
+
fix: 'Run `sequel extras`, and commit the snapshot.',
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return failures.sort((a, b) => a.order - b.order).map((entry) => entry.failure);
|
|
123
|
+
}
|
|
124
|
+
// The lines that differ, from a longest-common-subsequence diff: `-` for the snapshot as
|
|
125
|
+
// committed, `+` for the database. Snapshots are small, so the quadratic table is fine.
|
|
126
|
+
export function lineChanges(current, next) {
|
|
127
|
+
const a = current.split('\n');
|
|
128
|
+
const b = next.split('\n');
|
|
129
|
+
const lcs = Array.from({ length: a.length + 1 }, () => new Array(b.length + 1).fill(0));
|
|
130
|
+
for (let i = a.length - 1; i >= 0; i--) {
|
|
131
|
+
for (let j = b.length - 1; j >= 0; j--) {
|
|
132
|
+
const row = lcs[i];
|
|
133
|
+
row[j] =
|
|
134
|
+
a[i] === b[j]
|
|
135
|
+
? lcs[i + 1][j + 1] + 1
|
|
136
|
+
: Math.max(lcs[i + 1][j], row[j + 1]);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const changes = [];
|
|
140
|
+
let i = 0;
|
|
141
|
+
let j = 0;
|
|
142
|
+
while (i < a.length || j < b.length) {
|
|
143
|
+
if (i < a.length && j < b.length && a[i] === b[j]) {
|
|
144
|
+
i++;
|
|
145
|
+
j++;
|
|
146
|
+
}
|
|
147
|
+
else if (i < a.length &&
|
|
148
|
+
(j === b.length ||
|
|
149
|
+
lcs[i + 1][j] >= lcs[i][j + 1])) {
|
|
150
|
+
changes.push(`- ${a[i++]}`);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
changes.push(`+ ${b[j++]}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return changes;
|
|
157
|
+
}
|
|
158
|
+
export function checkFailureMessage(failures) {
|
|
159
|
+
const blocks = failures.map((failure, i) => {
|
|
160
|
+
const details = failure.details
|
|
161
|
+
.flatMap((detail) => detail.split('\n'))
|
|
162
|
+
.map((line) => ` ${line}`);
|
|
163
|
+
return [`${i + 1}. ${failure.problem}:`, ...details, ` Fix: ${failure.fix}`].join('\n');
|
|
164
|
+
});
|
|
165
|
+
return `sequel check found ${failures.length} problem${failures.length === 1 ? '' : 's'}:\n\n${blocks.join('\n\n')}`;
|
|
166
|
+
}
|
package/dist/cli.d.ts
ADDED