@supatype/cli 0.1.0-alpha.10
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/.turbo/turbo-build.log +4 -0
- package/.turbo/turbo-test.log +221 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/assets/supatype-logo-wordmark.ascii.txt +6 -0
- package/bin/dev-entry.ts +2 -0
- package/bin/supatype.js +5 -0
- package/dist/app/framework.d.ts +44 -0
- package/dist/app/framework.d.ts.map +1 -0
- package/dist/app/framework.js +200 -0
- package/dist/app/framework.js.map +1 -0
- package/dist/app/proxy-dev-app.d.ts +13 -0
- package/dist/app/proxy-dev-app.d.ts.map +1 -0
- package/dist/app/proxy-dev-app.js +54 -0
- package/dist/app/proxy-dev-app.js.map +1 -0
- package/dist/app-config.d.ts +7 -0
- package/dist/app-config.d.ts.map +1 -0
- package/dist/app-config.js +113 -0
- package/dist/app-config.js.map +1 -0
- package/dist/assets/supatype-logo-wordmark.ascii.txt +6 -0
- package/dist/augmentation-generator.d.ts +2 -0
- package/dist/augmentation-generator.d.ts.map +1 -0
- package/dist/augmentation-generator.js +111 -0
- package/dist/augmentation-generator.js.map +1 -0
- package/dist/binary-cache.d.ts +98 -0
- package/dist/binary-cache.d.ts.map +1 -0
- package/dist/binary-cache.js +687 -0
- package/dist/binary-cache.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +61 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/admin.d.ts +4 -0
- package/dist/commands/admin.d.ts.map +1 -0
- package/dist/commands/admin.js +271 -0
- package/dist/commands/admin.js.map +1 -0
- package/dist/commands/app.d.ts +3 -0
- package/dist/commands/app.d.ts.map +1 -0
- package/dist/commands/app.js +82 -0
- package/dist/commands/app.js.map +1 -0
- package/dist/commands/cache.d.ts +6 -0
- package/dist/commands/cache.d.ts.map +1 -0
- package/dist/commands/cache.js +105 -0
- package/dist/commands/cache.js.map +1 -0
- package/dist/commands/cloud.d.ts +23 -0
- package/dist/commands/cloud.d.ts.map +1 -0
- package/dist/commands/cloud.js +254 -0
- package/dist/commands/cloud.js.map +1 -0
- package/dist/commands/db.d.ts +8 -0
- package/dist/commands/db.d.ts.map +1 -0
- package/dist/commands/db.js +116 -0
- package/dist/commands/db.js.map +1 -0
- package/dist/commands/deploy-types.d.ts +14 -0
- package/dist/commands/deploy-types.d.ts.map +1 -0
- package/dist/commands/deploy-types.js +38 -0
- package/dist/commands/deploy-types.js.map +1 -0
- package/dist/commands/deploy.d.ts +15 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +322 -0
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/dev.d.ts +14 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +806 -0
- package/dist/commands/dev.js.map +1 -0
- package/dist/commands/diff.d.ts +3 -0
- package/dist/commands/diff.d.ts.map +1 -0
- package/dist/commands/diff.js +54 -0
- package/dist/commands/diff.js.map +1 -0
- package/dist/commands/engine.d.ts +7 -0
- package/dist/commands/engine.d.ts.map +1 -0
- package/dist/commands/engine.js +27 -0
- package/dist/commands/engine.js.map +1 -0
- package/dist/commands/functions.d.ts +3 -0
- package/dist/commands/functions.d.ts.map +1 -0
- package/dist/commands/functions.js +749 -0
- package/dist/commands/functions.js.map +1 -0
- package/dist/commands/generate.d.ts +3 -0
- package/dist/commands/generate.d.ts.map +1 -0
- package/dist/commands/generate.js +38 -0
- package/dist/commands/generate.js.map +1 -0
- package/dist/commands/init.d.ts +7 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +228 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/keys.d.ts +4 -0
- package/dist/commands/keys.d.ts.map +1 -0
- package/dist/commands/keys.js +57 -0
- package/dist/commands/keys.js.map +1 -0
- package/dist/commands/logs.d.ts +6 -0
- package/dist/commands/logs.d.ts.map +1 -0
- package/dist/commands/logs.js +52 -0
- package/dist/commands/logs.js.map +1 -0
- package/dist/commands/migrate-from-v1.d.ts +5 -0
- package/dist/commands/migrate-from-v1.d.ts.map +1 -0
- package/dist/commands/migrate-from-v1.js +125 -0
- package/dist/commands/migrate-from-v1.js.map +1 -0
- package/dist/commands/migrate.d.ts +3 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +75 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/pg.d.ts +8 -0
- package/dist/commands/pg.d.ts.map +1 -0
- package/dist/commands/pg.js +102 -0
- package/dist/commands/pg.js.map +1 -0
- package/dist/commands/plugins.d.ts +3 -0
- package/dist/commands/plugins.d.ts.map +1 -0
- package/dist/commands/plugins.js +431 -0
- package/dist/commands/plugins.js.map +1 -0
- package/dist/commands/pull.d.ts +3 -0
- package/dist/commands/pull.d.ts.map +1 -0
- package/dist/commands/pull.js +12 -0
- package/dist/commands/pull.js.map +1 -0
- package/dist/commands/push.d.ts +3 -0
- package/dist/commands/push.d.ts.map +1 -0
- package/dist/commands/push.js +179 -0
- package/dist/commands/push.js.map +1 -0
- package/dist/commands/seed.d.ts +5 -0
- package/dist/commands/seed.d.ts.map +1 -0
- package/dist/commands/seed.js +55 -0
- package/dist/commands/seed.js.map +1 -0
- package/dist/commands/self-host.d.ts +9 -0
- package/dist/commands/self-host.d.ts.map +1 -0
- package/dist/commands/self-host.js +310 -0
- package/dist/commands/self-host.js.map +1 -0
- package/dist/commands/self-update.d.ts +9 -0
- package/dist/commands/self-update.d.ts.map +1 -0
- package/dist/commands/self-update.js +33 -0
- package/dist/commands/self-update.js.map +1 -0
- package/dist/commands/status.d.ts +6 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +70 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/types.d.ts +3 -0
- package/dist/commands/types.d.ts.map +1 -0
- package/dist/commands/types.js +62 -0
- package/dist/commands/types.js.map +1 -0
- package/dist/commands/update.d.ts +7 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +118 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/components.d.ts +5 -0
- package/dist/components.d.ts.map +1 -0
- package/dist/components.js +3 -0
- package/dist/components.js.map +1 -0
- package/dist/config.d.ts +65 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +134 -0
- package/dist/config.js.map +1 -0
- package/dist/dev-compose.d.ts +19 -0
- package/dist/dev-compose.d.ts.map +1 -0
- package/dist/dev-compose.js +468 -0
- package/dist/dev-compose.js.map +1 -0
- package/dist/dev-log-bus.d.ts +30 -0
- package/dist/dev-log-bus.d.ts.map +1 -0
- package/dist/dev-log-bus.js +87 -0
- package/dist/dev-log-bus.js.map +1 -0
- package/dist/dev-log-filter.d.ts +10 -0
- package/dist/dev-log-filter.d.ts.map +1 -0
- package/dist/dev-log-filter.js +36 -0
- package/dist/dev-log-filter.js.map +1 -0
- package/dist/dev-logo.d.ts +12 -0
- package/dist/dev-logo.d.ts.map +1 -0
- package/dist/dev-logo.js +57 -0
- package/dist/dev-logo.js.map +1 -0
- package/dist/dev-session.d.ts +26 -0
- package/dist/dev-session.d.ts.map +1 -0
- package/dist/dev-session.js +106 -0
- package/dist/dev-session.js.map +1 -0
- package/dist/dev-shutdown.d.ts +9 -0
- package/dist/dev-shutdown.d.ts.map +1 -0
- package/dist/dev-shutdown.js +50 -0
- package/dist/dev-shutdown.js.map +1 -0
- package/dist/dev-task-colors.d.ts +14 -0
- package/dist/dev-task-colors.d.ts.map +1 -0
- package/dist/dev-task-colors.js +44 -0
- package/dist/dev-task-colors.js.map +1 -0
- package/dist/dev-tui.d.ts +24 -0
- package/dist/dev-tui.d.ts.map +1 -0
- package/dist/dev-tui.js +188 -0
- package/dist/dev-tui.js.map +1 -0
- package/dist/diff-output.d.ts +4 -0
- package/dist/diff-output.d.ts.map +1 -0
- package/dist/diff-output.js +12 -0
- package/dist/diff-output.js.map +1 -0
- package/dist/docker-postgres.d.ts +57 -0
- package/dist/docker-postgres.d.ts.map +1 -0
- package/dist/docker-postgres.js +208 -0
- package/dist/docker-postgres.js.map +1 -0
- package/dist/engine-client.d.ts +69 -0
- package/dist/engine-client.d.ts.map +1 -0
- package/dist/engine-client.js +157 -0
- package/dist/engine-client.js.map +1 -0
- package/dist/engine-push-output.d.ts +16 -0
- package/dist/engine-push-output.d.ts.map +1 -0
- package/dist/engine-push-output.js +61 -0
- package/dist/engine-push-output.js.map +1 -0
- package/dist/ensure-binary.d.ts +7 -0
- package/dist/ensure-binary.d.ts.map +1 -0
- package/dist/ensure-binary.js +17 -0
- package/dist/ensure-binary.js.map +1 -0
- package/dist/functions-router-gen.d.ts +14 -0
- package/dist/functions-router-gen.d.ts.map +1 -0
- package/dist/functions-router-gen.js +199 -0
- package/dist/functions-router-gen.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/jwt.d.ts +3 -0
- package/dist/jwt.d.ts.map +1 -0
- package/dist/jwt.js +13 -0
- package/dist/jwt.js.map +1 -0
- package/dist/kong-config.d.ts +25 -0
- package/dist/kong-config.d.ts.map +1 -0
- package/dist/kong-config.js +71 -0
- package/dist/kong-config.js.map +1 -0
- package/dist/local-gateway.d.ts +7 -0
- package/dist/local-gateway.d.ts.map +1 -0
- package/dist/local-gateway.js +9 -0
- package/dist/local-gateway.js.map +1 -0
- package/dist/local-storage.d.ts +8 -0
- package/dist/local-storage.d.ts.map +1 -0
- package/dist/local-storage.js +14 -0
- package/dist/local-storage.js.map +1 -0
- package/dist/pgbouncer-userlist.d.ts +5 -0
- package/dist/pgbouncer-userlist.d.ts.map +1 -0
- package/dist/pgbouncer-userlist.js +14 -0
- package/dist/pgbouncer-userlist.js.map +1 -0
- package/dist/postgres-ctl.d.ts +44 -0
- package/dist/postgres-ctl.d.ts.map +1 -0
- package/dist/postgres-ctl.js +137 -0
- package/dist/postgres-ctl.js.map +1 -0
- package/dist/process-manager.d.ts +49 -0
- package/dist/process-manager.d.ts.map +1 -0
- package/dist/process-manager.js +177 -0
- package/dist/process-manager.js.map +1 -0
- package/dist/project-config.d.ts +238 -0
- package/dist/project-config.d.ts.map +1 -0
- package/dist/project-config.js +159 -0
- package/dist/project-config.js.map +1 -0
- package/dist/pull-utils.d.ts +31 -0
- package/dist/pull-utils.d.ts.map +1 -0
- package/dist/pull-utils.js +77 -0
- package/dist/pull-utils.js.map +1 -0
- package/dist/release-pins.d.ts +7 -0
- package/dist/release-pins.d.ts.map +1 -0
- package/dist/release-pins.js +27 -0
- package/dist/release-pins.js.map +1 -0
- package/dist/release-public-key.d.ts +8 -0
- package/dist/release-public-key.d.ts.map +1 -0
- package/dist/release-public-key.js +13 -0
- package/dist/release-public-key.js.map +1 -0
- package/dist/restore-system-relation-targets.d.ts +3 -0
- package/dist/restore-system-relation-targets.d.ts.map +1 -0
- package/dist/restore-system-relation-targets.js +45 -0
- package/dist/restore-system-relation-targets.js.map +1 -0
- package/dist/runtime-routes.d.ts +34 -0
- package/dist/runtime-routes.d.ts.map +1 -0
- package/dist/runtime-routes.js +252 -0
- package/dist/runtime-routes.js.map +1 -0
- package/dist/schema-ast-v2.d.ts +127 -0
- package/dist/schema-ast-v2.d.ts.map +1 -0
- package/dist/schema-ast-v2.js +226 -0
- package/dist/schema-ast-v2.js.map +1 -0
- package/dist/scripts/postinstall.d.ts +11 -0
- package/dist/scripts/postinstall.d.ts.map +1 -0
- package/dist/scripts/postinstall.js +47 -0
- package/dist/scripts/postinstall.js.map +1 -0
- package/dist/seed.d.ts +8 -0
- package/dist/seed.d.ts.map +1 -0
- package/dist/seed.js +32 -0
- package/dist/seed.js.map +1 -0
- package/dist/self-host-compose.d.ts +43 -0
- package/dist/self-host-compose.d.ts.map +1 -0
- package/dist/self-host-compose.js +400 -0
- package/dist/self-host-compose.js.map +1 -0
- package/dist/storage-provision.d.ts +24 -0
- package/dist/storage-provision.d.ts.map +1 -0
- package/dist/storage-provision.js +44 -0
- package/dist/storage-provision.js.map +1 -0
- package/dist/studio-admin-roles.d.ts +7 -0
- package/dist/studio-admin-roles.d.ts.map +1 -0
- package/dist/studio-admin-roles.js +14 -0
- package/dist/studio-admin-roles.js.map +1 -0
- package/dist/studio-dev-server.d.ts +22 -0
- package/dist/studio-dev-server.d.ts.map +1 -0
- package/dist/studio-dev-server.js +28 -0
- package/dist/studio-dev-server.js.map +1 -0
- package/dist/supatype-eval-1781522769253.d.mts +2 -0
- package/dist/supatype-eval-1781522769253.d.mts.map +1 -0
- package/dist/supatype-eval-1781522769253.mjs +3 -0
- package/dist/supatype-eval-1781522769253.mjs.map +1 -0
- package/dist/systemd.d.ts +26 -0
- package/dist/systemd.d.ts.map +1 -0
- package/dist/systemd.js +102 -0
- package/dist/systemd.js.map +1 -0
- package/dist/tsx-runner.d.ts +18 -0
- package/dist/tsx-runner.d.ts.map +1 -0
- package/dist/tsx-runner.js +69 -0
- package/dist/tsx-runner.js.map +1 -0
- package/dist/type-extractor.d.ts +4 -0
- package/dist/type-extractor.d.ts.map +1 -0
- package/dist/type-extractor.js +1213 -0
- package/dist/type-extractor.js.map +1 -0
- package/dist/type-resolver.d.ts +33 -0
- package/dist/type-resolver.d.ts.map +1 -0
- package/dist/type-resolver.js +338 -0
- package/dist/type-resolver.js.map +1 -0
- package/package.json +41 -0
- package/releases/deno/VERSION +1 -0
- package/scripts/mirror-deno-release.sh +76 -0
- package/src/TYPE-RESOLUTION.md +294 -0
- package/src/app/framework.ts +249 -0
- package/src/app/proxy-dev-app.ts +68 -0
- package/src/app-config.ts +128 -0
- package/src/augmentation-generator.ts +126 -0
- package/src/binary-cache.ts +845 -0
- package/src/cli.ts +63 -0
- package/src/commands/admin.ts +372 -0
- package/src/commands/app.ts +97 -0
- package/src/commands/cache.ts +117 -0
- package/src/commands/cloud.ts +325 -0
- package/src/commands/db.ts +136 -0
- package/src/commands/deploy-types.ts +49 -0
- package/src/commands/deploy.ts +400 -0
- package/src/commands/dev.ts +1009 -0
- package/src/commands/diff.ts +63 -0
- package/src/commands/engine.ts +30 -0
- package/src/commands/functions.ts +901 -0
- package/src/commands/generate.ts +44 -0
- package/src/commands/init.ts +253 -0
- package/src/commands/keys.ts +66 -0
- package/src/commands/logs.ts +58 -0
- package/src/commands/migrate-from-v1.ts +131 -0
- package/src/commands/migrate.ts +87 -0
- package/src/commands/pg.ts +133 -0
- package/src/commands/plugins.ts +508 -0
- package/src/commands/pull.ts +17 -0
- package/src/commands/push.ts +226 -0
- package/src/commands/seed.ts +68 -0
- package/src/commands/self-host.ts +364 -0
- package/src/commands/self-update.ts +45 -0
- package/src/commands/status.ts +84 -0
- package/src/commands/types.ts +76 -0
- package/src/commands/update.ts +136 -0
- package/src/components.ts +6 -0
- package/src/config.ts +223 -0
- package/src/dev-compose.ts +583 -0
- package/src/dev-log-bus.ts +101 -0
- package/src/dev-log-filter.ts +32 -0
- package/src/dev-logo.ts +62 -0
- package/src/dev-session.ts +130 -0
- package/src/dev-shutdown.ts +54 -0
- package/src/dev-task-colors.ts +47 -0
- package/src/dev-tui.ts +232 -0
- package/src/diff-output.ts +12 -0
- package/src/docker-postgres.ts +295 -0
- package/src/engine-client.ts +236 -0
- package/src/engine-push-output.ts +71 -0
- package/src/ensure-binary.ts +28 -0
- package/src/functions-router-gen.ts +224 -0
- package/src/index.ts +11 -0
- package/src/jwt.ts +14 -0
- package/src/kong-config.ts +93 -0
- package/src/local-gateway.ts +9 -0
- package/src/local-storage.ts +14 -0
- package/src/pgbouncer-userlist.ts +15 -0
- package/src/postgres-ctl.ts +171 -0
- package/src/process-manager.ts +220 -0
- package/src/project-config.ts +388 -0
- package/src/pull-utils.ts +81 -0
- package/src/release-pins.ts +31 -0
- package/src/release-public-key.ts +12 -0
- package/src/restore-system-relation-targets.ts +45 -0
- package/src/runtime-routes.ts +291 -0
- package/src/schema-ast-v2.ts +324 -0
- package/src/scripts/postinstall.ts +51 -0
- package/src/seed.ts +43 -0
- package/src/self-host-compose.ts +452 -0
- package/src/storage-provision.ts +58 -0
- package/src/studio-admin-roles.ts +16 -0
- package/src/studio-dev-server.ts +53 -0
- package/src/supatype-eval-1781522769253.mts +1 -0
- package/src/systemd.ts +137 -0
- package/src/tsx-runner.ts +89 -0
- package/src/type-extractor.ts +1479 -0
- package/src/type-resolver.ts +457 -0
- package/tests/app-command.test.ts +54 -0
- package/tests/augmentation-generator.test.ts +59 -0
- package/tests/binary-cache-cloud-overrides.test.ts +123 -0
- package/tests/cached-artifact-format.test.ts +84 -0
- package/tests/cli-help.test.ts +133 -0
- package/tests/config.test.ts +252 -0
- package/tests/dev-ui.test.ts +139 -0
- package/tests/docker-postgres.test.ts +39 -0
- package/tests/engine-distribution.test.ts +418 -0
- package/tests/engine-push-output.test.ts +67 -0
- package/tests/ensure-binary.test.ts +59 -0
- package/tests/init.test.ts +127 -0
- package/tests/keys.test.ts +160 -0
- package/tests/migrate-from-v1.test.ts +29 -0
- package/tests/normalize-admin-config.test.ts +48 -0
- package/tests/pg-spawn-env.test.ts +18 -0
- package/tests/postgres-archive-tag.test.ts +9 -0
- package/tests/proxy-dev-app.test.ts +33 -0
- package/tests/pull-utils.test.ts +150 -0
- package/tests/release-pins.test.ts +28 -0
- package/tests/runtime-contract.test.ts +370 -0
- package/tests/seed-discover.test.ts +31 -0
- package/tests/studio-admin-roles.test.ts +27 -0
- package/tests/tsconfig.json +9 -0
- package/tests/tsx-runner.test.ts +66 -0
- package/tests/type-extractor.test.ts +985 -0
- package/tests/type-resolver.test.ts +59 -0
- package/tsconfig.json +10 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/vitest.config.ts +12 -0
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration tests for engine binary distribution (Phase 0.5).
|
|
3
|
+
*
|
|
4
|
+
* Tests cover:
|
|
5
|
+
* - Platform detection
|
|
6
|
+
* - Cache management
|
|
7
|
+
* - Download, checksum, and signature verification
|
|
8
|
+
* - Version compatibility
|
|
9
|
+
* - Offline mode
|
|
10
|
+
* - Error handling
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"
|
|
14
|
+
import { mkdirSync, writeFileSync, readFileSync, existsSync, rmSync, chmodSync } from "node:fs"
|
|
15
|
+
import { join } from "node:path"
|
|
16
|
+
import { tmpdir, homedir } from "node:os"
|
|
17
|
+
import { createHash } from "node:crypto"
|
|
18
|
+
|
|
19
|
+
// ── Platform detection ───────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
describe("Platform detection", () => {
|
|
22
|
+
it("detects supported platforms", async () => {
|
|
23
|
+
const { detectPlatform } = await import("../src/engine/platform.js")
|
|
24
|
+
// This test runs on the current platform — just verify it doesn't throw
|
|
25
|
+
const platform = detectPlatform()
|
|
26
|
+
expect(platform.os).toMatch(/^(linux|darwin|win)$/)
|
|
27
|
+
expect(platform.arch).toMatch(/^(x64|arm64)$/)
|
|
28
|
+
expect(platform.binaryName).toContain("supatype-engine")
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it("builds correct artifact names", async () => {
|
|
32
|
+
const { getArtifactName } = await import("../src/engine/platform.js")
|
|
33
|
+
|
|
34
|
+
expect(
|
|
35
|
+
getArtifactName("0.1.0-alpha.1", { os: "linux", arch: "x64", binaryName: "supatype-engine", ext: "" }),
|
|
36
|
+
).toBe("supatype-engine-0.1.0-alpha.1-linux-x64")
|
|
37
|
+
|
|
38
|
+
expect(
|
|
39
|
+
getArtifactName("0.1.0-alpha.1", { os: "darwin", arch: "arm64", binaryName: "supatype-engine", ext: "" }),
|
|
40
|
+
).toBe("supatype-engine-0.1.0-alpha.1-darwin-arm64")
|
|
41
|
+
|
|
42
|
+
expect(
|
|
43
|
+
getArtifactName("0.1.0-alpha.1", { os: "win", arch: "x64", binaryName: "supatype-engine.exe", ext: ".exe" }),
|
|
44
|
+
).toBe("supatype-engine-0.1.0-alpha.1-win-x64.exe")
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it("rejects unsupported platforms", async () => {
|
|
48
|
+
const platformModule = await import("../src/engine/platform.js")
|
|
49
|
+
// We can't easily test this without mocking process.platform/arch
|
|
50
|
+
// but the logic is covered by the PLATFORM_MAP check
|
|
51
|
+
expect(typeof platformModule.detectPlatform).toBe("function")
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
// ── CDN URL construction ─────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
describe("CDN URL construction", () => {
|
|
58
|
+
it("builds correct CDN URLs", async () => {
|
|
59
|
+
const { getCdnUrl } = await import("../src/engine/platform.js")
|
|
60
|
+
|
|
61
|
+
const url = getCdnUrl(
|
|
62
|
+
"https://releases.supatype.com/engine",
|
|
63
|
+
"0.1.0-alpha.1",
|
|
64
|
+
"supatype-engine-0.1.0-alpha.1-linux-x64",
|
|
65
|
+
)
|
|
66
|
+
expect(url).toBe(
|
|
67
|
+
"https://releases.supatype.com/engine/v0.1.0-alpha.1/supatype-engine-0.1.0-alpha.1-linux-x64",
|
|
68
|
+
)
|
|
69
|
+
})
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
// ── Cache management ─────────────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
describe("Cache management", () => {
|
|
75
|
+
const testCacheDir = join(tmpdir(), `supatype-test-cache-${Date.now()}`)
|
|
76
|
+
|
|
77
|
+
// We test the pure functions, not the ones using homedir()
|
|
78
|
+
afterEach(() => {
|
|
79
|
+
if (existsSync(testCacheDir)) {
|
|
80
|
+
rmSync(testCacheDir, { recursive: true, force: true })
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it("lists cached versions from empty cache", async () => {
|
|
85
|
+
const { listCachedVersions } = await import("../src/engine/cache.js")
|
|
86
|
+
// May or may not have cached versions on this machine
|
|
87
|
+
const versions = listCachedVersions()
|
|
88
|
+
expect(Array.isArray(versions)).toBe(true)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it("pruneCacheExcept keeps specified version", async () => {
|
|
92
|
+
const { pruneCacheExcept, getCacheDir } = await import("../src/engine/cache.js")
|
|
93
|
+
const cacheDir = getCacheDir()
|
|
94
|
+
|
|
95
|
+
// Create test versions
|
|
96
|
+
const v1Dir = join(cacheDir, "test-0.0.1")
|
|
97
|
+
const v2Dir = join(cacheDir, "test-0.0.2")
|
|
98
|
+
mkdirSync(v1Dir, { recursive: true })
|
|
99
|
+
mkdirSync(v2Dir, { recursive: true })
|
|
100
|
+
writeFileSync(join(v1Dir, "supatype-engine"), "binary1")
|
|
101
|
+
writeFileSync(join(v2Dir, "supatype-engine"), "binary2")
|
|
102
|
+
|
|
103
|
+
const result = pruneCacheExcept("test-0.0.2")
|
|
104
|
+
expect(result.removed).toContain("test-0.0.1")
|
|
105
|
+
expect(result.removed).not.toContain("test-0.0.2")
|
|
106
|
+
|
|
107
|
+
// Cleanup
|
|
108
|
+
if (existsSync(v2Dir)) rmSync(v2Dir, { recursive: true, force: true })
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
// ── Checksum verification ────────────────────────────────────────────
|
|
113
|
+
|
|
114
|
+
describe("Checksum verification", () => {
|
|
115
|
+
const testDir = join(tmpdir(), `supatype-test-checksum-${Date.now()}`)
|
|
116
|
+
|
|
117
|
+
beforeEach(() => {
|
|
118
|
+
mkdirSync(testDir, { recursive: true })
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
afterEach(() => {
|
|
122
|
+
if (existsSync(testDir)) {
|
|
123
|
+
rmSync(testDir, { recursive: true, force: true })
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it("verifies correct checksum", async () => {
|
|
128
|
+
const { verifyChecksum } = await import("../src/engine/verify.js")
|
|
129
|
+
|
|
130
|
+
const binaryContent = Buffer.from("test binary content")
|
|
131
|
+
const binaryPath = join(testDir, "supatype-engine")
|
|
132
|
+
writeFileSync(binaryPath, binaryContent)
|
|
133
|
+
|
|
134
|
+
const hash = createHash("sha256").update(binaryContent).digest("hex")
|
|
135
|
+
const checksumPath = join(testDir, "checksums.sha256")
|
|
136
|
+
writeFileSync(checksumPath, `${hash} supatype-engine-0.1.0-linux-x64\n`)
|
|
137
|
+
|
|
138
|
+
const result = await verifyChecksum(
|
|
139
|
+
binaryPath,
|
|
140
|
+
checksumPath,
|
|
141
|
+
"supatype-engine-0.1.0-linux-x64",
|
|
142
|
+
)
|
|
143
|
+
expect(result).toBe(true)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it("rejects mismatched checksum", async () => {
|
|
147
|
+
const { verifyChecksum } = await import("../src/engine/verify.js")
|
|
148
|
+
|
|
149
|
+
const binaryPath = join(testDir, "supatype-engine")
|
|
150
|
+
writeFileSync(binaryPath, "actual content")
|
|
151
|
+
|
|
152
|
+
const checksumPath = join(testDir, "checksums.sha256")
|
|
153
|
+
writeFileSync(checksumPath, `${"a".repeat(64)} supatype-engine-0.1.0-linux-x64\n`)
|
|
154
|
+
|
|
155
|
+
const result = await verifyChecksum(
|
|
156
|
+
binaryPath,
|
|
157
|
+
checksumPath,
|
|
158
|
+
"supatype-engine-0.1.0-linux-x64",
|
|
159
|
+
)
|
|
160
|
+
expect(result).toBe(false)
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it("rejects corrupt cached binary (flipped byte)", async () => {
|
|
164
|
+
const { verifyChecksum } = await import("../src/engine/verify.js")
|
|
165
|
+
|
|
166
|
+
const originalContent = Buffer.from("original binary content here")
|
|
167
|
+
const hash = createHash("sha256").update(originalContent).digest("hex")
|
|
168
|
+
|
|
169
|
+
// Corrupt the binary (flip a byte)
|
|
170
|
+
const corruptContent = Buffer.from(originalContent)
|
|
171
|
+
corruptContent[0] = corruptContent[0]! ^ 0xff
|
|
172
|
+
|
|
173
|
+
const binaryPath = join(testDir, "supatype-engine")
|
|
174
|
+
writeFileSync(binaryPath, corruptContent)
|
|
175
|
+
|
|
176
|
+
const checksumPath = join(testDir, "checksums.sha256")
|
|
177
|
+
writeFileSync(checksumPath, `${hash} supatype-engine-0.1.0-linux-x64\n`)
|
|
178
|
+
|
|
179
|
+
const result = await verifyChecksum(
|
|
180
|
+
binaryPath,
|
|
181
|
+
checksumPath,
|
|
182
|
+
"supatype-engine-0.1.0-linux-x64",
|
|
183
|
+
)
|
|
184
|
+
expect(result).toBe(false)
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
it("throws when filename not found in checksum file", async () => {
|
|
188
|
+
const { verifyChecksum } = await import("../src/engine/verify.js")
|
|
189
|
+
|
|
190
|
+
const binaryPath = join(testDir, "supatype-engine")
|
|
191
|
+
writeFileSync(binaryPath, "content")
|
|
192
|
+
|
|
193
|
+
const checksumPath = join(testDir, "checksums.sha256")
|
|
194
|
+
writeFileSync(checksumPath, `${"a".repeat(64)} other-file\n`)
|
|
195
|
+
|
|
196
|
+
await expect(
|
|
197
|
+
verifyChecksum(binaryPath, checksumPath, "supatype-engine-0.1.0-linux-x64"),
|
|
198
|
+
).rejects.toThrow("No checksum found")
|
|
199
|
+
})
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
// ── Signature verification ───────────────────────────────────────────
|
|
203
|
+
|
|
204
|
+
describe("Signature verification", () => {
|
|
205
|
+
const testDir = join(tmpdir(), `supatype-test-sig-${Date.now()}`)
|
|
206
|
+
|
|
207
|
+
beforeEach(() => {
|
|
208
|
+
mkdirSync(testDir, { recursive: true })
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
afterEach(() => {
|
|
212
|
+
if (existsSync(testDir)) {
|
|
213
|
+
rmSync(testDir, { recursive: true, force: true })
|
|
214
|
+
}
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
it("rejects tampered checksum file (invalid signature)", async () => {
|
|
218
|
+
const { verifySignature } = await import("../src/engine/verify.js")
|
|
219
|
+
|
|
220
|
+
const checksumPath = join(testDir, "checksums.sha256")
|
|
221
|
+
writeFileSync(checksumPath, `${"a".repeat(64)} supatype-engine-0.1.0-linux-x64\n`)
|
|
222
|
+
|
|
223
|
+
// Create a fake .minisig with garbage signature
|
|
224
|
+
const sigPath = join(testDir, "checksums.sha256.minisig")
|
|
225
|
+
writeFileSync(
|
|
226
|
+
sigPath,
|
|
227
|
+
`untrusted comment: fake\n${Buffer.alloc(74).toString("base64")}\n`,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
const result = await verifySignature(checksumPath, sigPath)
|
|
231
|
+
expect(result).toBe(false)
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
it("rejects missing .minisig file", async () => {
|
|
235
|
+
const { verifySignature } = await import("../src/engine/verify.js")
|
|
236
|
+
|
|
237
|
+
const checksumPath = join(testDir, "checksums.sha256")
|
|
238
|
+
writeFileSync(checksumPath, "some content")
|
|
239
|
+
|
|
240
|
+
const result = await verifySignature(checksumPath, join(testDir, "nonexistent.minisig"))
|
|
241
|
+
expect(result).toBe(false)
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
it("rejects forged signature with wrong key", async () => {
|
|
245
|
+
const { verifySignature } = await import("../src/engine/verify.js")
|
|
246
|
+
|
|
247
|
+
const checksumPath = join(testDir, "checksums.sha256")
|
|
248
|
+
writeFileSync(checksumPath, `${"a".repeat(64)} supatype-engine\n`)
|
|
249
|
+
|
|
250
|
+
// Forged signature (valid format but wrong key)
|
|
251
|
+
const sigPath = join(testDir, "checksums.sha256.minisig")
|
|
252
|
+
const fakeSignature = Buffer.alloc(74)
|
|
253
|
+
fakeSignature.writeUInt16LE(0x4564, 0) // Ed algorithm bytes
|
|
254
|
+
writeFileSync(
|
|
255
|
+
sigPath,
|
|
256
|
+
`untrusted comment: forged\n${fakeSignature.toString("base64")}\n`,
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
const result = await verifySignature(checksumPath, sigPath)
|
|
260
|
+
expect(result).toBe(false)
|
|
261
|
+
})
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
// ── Version compatibility ────────────────────────────────────────────
|
|
265
|
+
|
|
266
|
+
describe("Version compatibility", () => {
|
|
267
|
+
it("accepts same major version", async () => {
|
|
268
|
+
const { checkVersionCompatibility } = await import("../src/engine/resolve.js")
|
|
269
|
+
|
|
270
|
+
expect(checkVersionCompatibility("1.2.3", "1.0.0").compatible).toBe(true)
|
|
271
|
+
expect(checkVersionCompatibility("0.1.0", "0.2.0").compatible).toBe(true)
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
it("rejects different major versions", async () => {
|
|
275
|
+
const { checkVersionCompatibility } = await import("../src/engine/resolve.js")
|
|
276
|
+
|
|
277
|
+
const result = checkVersionCompatibility("2.1.0", "1.3.0")
|
|
278
|
+
expect(result.compatible).toBe(false)
|
|
279
|
+
expect(result.message).toContain("not compatible")
|
|
280
|
+
expect(result.message).toContain("npm update @supatype/cli")
|
|
281
|
+
})
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
// ── Update check throttling ─────────────────────────────────────────
|
|
285
|
+
|
|
286
|
+
describe("Update check throttling", () => {
|
|
287
|
+
it("returns true when no check file exists", async () => {
|
|
288
|
+
const { shouldCheckForUpdates } = await import("../src/engine/cache.js")
|
|
289
|
+
// On fresh machine, should want to check
|
|
290
|
+
const result = await shouldCheckForUpdates()
|
|
291
|
+
expect(typeof result).toBe("boolean")
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
it("skips check in CI environments", async () => {
|
|
295
|
+
const origCI = process.env.CI
|
|
296
|
+
process.env.CI = "true"
|
|
297
|
+
|
|
298
|
+
const { shouldCheckForUpdates } = await import("../src/engine/cache.js")
|
|
299
|
+
const result = await shouldCheckForUpdates()
|
|
300
|
+
expect(result).toBe(false)
|
|
301
|
+
|
|
302
|
+
if (origCI !== undefined) {
|
|
303
|
+
process.env.CI = origCI
|
|
304
|
+
} else {
|
|
305
|
+
delete process.env.CI
|
|
306
|
+
}
|
|
307
|
+
})
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
// ── Download retry ──────────────────────────────────────────────────
|
|
311
|
+
|
|
312
|
+
describe("Download retry", () => {
|
|
313
|
+
it("fetchJson returns undefined on network error", async () => {
|
|
314
|
+
const { fetchJson } = await import("../src/engine/download.js")
|
|
315
|
+
const result = await fetchJson("http://localhost:1/nonexistent")
|
|
316
|
+
expect(result).toBeUndefined()
|
|
317
|
+
})
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
// ── Engine version constants ─────────────────────────────────────────
|
|
321
|
+
|
|
322
|
+
describe("Engine version constants", () => {
|
|
323
|
+
it("exports valid version and URLs", async () => {
|
|
324
|
+
const {
|
|
325
|
+
ENGINE_VERSION,
|
|
326
|
+
CDN_BASE_URL,
|
|
327
|
+
ENGINE_RELEASES_REPO,
|
|
328
|
+
GITHUB_RELEASES_FALLBACK_URL,
|
|
329
|
+
} = await import("../src/engine-version.js")
|
|
330
|
+
|
|
331
|
+
expect(ENGINE_VERSION).toMatch(/^\d+\.\d+\.\d+/)
|
|
332
|
+
expect(CDN_BASE_URL).toBe("https://releases.supatype.com/engine")
|
|
333
|
+
expect(ENGINE_RELEASES_REPO).toBe("supatype/engine-releases")
|
|
334
|
+
expect(GITHUB_RELEASES_FALLBACK_URL).toContain("github.com")
|
|
335
|
+
})
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
// ── Full binary verification pipeline ────────────────────────────────
|
|
339
|
+
|
|
340
|
+
describe("Binary verification pipeline", () => {
|
|
341
|
+
const testDir = join(tmpdir(), `supatype-test-pipeline-${Date.now()}`)
|
|
342
|
+
|
|
343
|
+
beforeEach(() => {
|
|
344
|
+
mkdirSync(testDir, { recursive: true })
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
afterEach(() => {
|
|
348
|
+
if (existsSync(testDir)) {
|
|
349
|
+
rmSync(testDir, { recursive: true, force: true })
|
|
350
|
+
}
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
it("verifyChecksumOnly passes with valid checksum", async () => {
|
|
354
|
+
const { verifyChecksumOnly } = await import("../src/engine/verify.js")
|
|
355
|
+
|
|
356
|
+
const content = Buffer.from("valid engine binary")
|
|
357
|
+
const hash = createHash("sha256").update(content).digest("hex")
|
|
358
|
+
|
|
359
|
+
const binaryPath = join(testDir, "engine")
|
|
360
|
+
writeFileSync(binaryPath, content)
|
|
361
|
+
|
|
362
|
+
const checksumPath = join(testDir, "checksums.sha256")
|
|
363
|
+
writeFileSync(checksumPath, `${hash} engine-0.1.0-linux-x64\n`)
|
|
364
|
+
|
|
365
|
+
// Should not throw
|
|
366
|
+
await verifyChecksumOnly(binaryPath, checksumPath, "engine-0.1.0-linux-x64")
|
|
367
|
+
})
|
|
368
|
+
|
|
369
|
+
it("verifyChecksumOnly rejects and deletes corrupt binary", async () => {
|
|
370
|
+
const { verifyChecksumOnly } = await import("../src/engine/verify.js")
|
|
371
|
+
|
|
372
|
+
const binaryPath = join(testDir, "engine")
|
|
373
|
+
writeFileSync(binaryPath, "corrupt content")
|
|
374
|
+
|
|
375
|
+
const checksumPath = join(testDir, "checksums.sha256")
|
|
376
|
+
writeFileSync(checksumPath, `${"f".repeat(64)} engine-0.1.0-linux-x64\n`)
|
|
377
|
+
|
|
378
|
+
await expect(
|
|
379
|
+
verifyChecksumOnly(binaryPath, checksumPath, "engine-0.1.0-linux-x64"),
|
|
380
|
+
).rejects.toThrow("checksum mismatch")
|
|
381
|
+
|
|
382
|
+
// Binary should be deleted
|
|
383
|
+
expect(existsSync(binaryPath)).toBe(false)
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
it("verifyBinary rejects when signature is invalid", async () => {
|
|
387
|
+
const { verifyBinary } = await import("../src/engine/verify.js")
|
|
388
|
+
|
|
389
|
+
const content = Buffer.from("binary content")
|
|
390
|
+
const hash = createHash("sha256").update(content).digest("hex")
|
|
391
|
+
|
|
392
|
+
const binaryPath = join(testDir, "engine")
|
|
393
|
+
writeFileSync(binaryPath, content)
|
|
394
|
+
|
|
395
|
+
const checksumPath = join(testDir, "checksums.sha256")
|
|
396
|
+
writeFileSync(checksumPath, `${hash} engine-artifact\n`)
|
|
397
|
+
|
|
398
|
+
const sigPath = join(testDir, "checksums.sha256.minisig")
|
|
399
|
+
writeFileSync(sigPath, `untrusted comment: bad\n${Buffer.alloc(74).toString("base64")}\n`)
|
|
400
|
+
|
|
401
|
+
await expect(
|
|
402
|
+
verifyBinary(binaryPath, checksumPath, sigPath, "engine-artifact"),
|
|
403
|
+
).rejects.toThrow("signature verification failed")
|
|
404
|
+
|
|
405
|
+
// Binary should be deleted
|
|
406
|
+
expect(existsSync(binaryPath)).toBe(false)
|
|
407
|
+
})
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
// ── Binary size check (placeholder for CI) ───────────────────────────
|
|
411
|
+
|
|
412
|
+
describe("Binary size", () => {
|
|
413
|
+
it("has a 20MB size target documented", () => {
|
|
414
|
+
// This is a CI-level check. Here we just verify the constant is defined.
|
|
415
|
+
const MAX_BINARY_SIZE_MB = 20
|
|
416
|
+
expect(MAX_BINARY_SIZE_MB).toBe(20)
|
|
417
|
+
})
|
|
418
|
+
})
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
|
+
import {
|
|
3
|
+
filterComposeNoise,
|
|
4
|
+
formatEnginePushMessage,
|
|
5
|
+
parseEnginePushOutput,
|
|
6
|
+
} from "../src/engine-push-output.js"
|
|
7
|
+
|
|
8
|
+
describe("parseEnginePushOutput()", () => {
|
|
9
|
+
it("parses JSON on its own line amid docker progress", () => {
|
|
10
|
+
const output = [
|
|
11
|
+
" Container supatype-to-do-db-1 Running",
|
|
12
|
+
" Container supatype-to-do-db-1 Waiting",
|
|
13
|
+
" Container supatype-to-do-db-1 Healthy",
|
|
14
|
+
'{"status":"up_to_date","operations":0,"admin_refreshed":true}',
|
|
15
|
+
" Container supatype-to-do-schema-engine-run-abc Created",
|
|
16
|
+
].join("\n")
|
|
17
|
+
|
|
18
|
+
expect(parseEnginePushOutput(output)).toEqual({
|
|
19
|
+
status: "up_to_date",
|
|
20
|
+
operations: 0,
|
|
21
|
+
admin_refreshed: true,
|
|
22
|
+
})
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it("parses bare JSON output", () => {
|
|
26
|
+
expect(parseEnginePushOutput('{"status":"applied","operations":2}')).toEqual({
|
|
27
|
+
status: "applied",
|
|
28
|
+
operations: 2,
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it("returns null when no JSON present", () => {
|
|
33
|
+
expect(parseEnginePushOutput("engine failed: connection refused")).toBeNull()
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
describe("formatEnginePushMessage()", () => {
|
|
38
|
+
it("formats up_to_date with admin refresh", () => {
|
|
39
|
+
expect(
|
|
40
|
+
formatEnginePushMessage({ status: "up_to_date", operations: 0, admin_refreshed: true }),
|
|
41
|
+
).toBe("Schema up to date — Studio metadata synced.")
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it("formats up_to_date without admin refresh", () => {
|
|
45
|
+
expect(formatEnginePushMessage({ status: "up_to_date" })).toBe("Schema up to date.")
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it("formats applied operations", () => {
|
|
49
|
+
expect(formatEnginePushMessage({ status: "applied", operations: 3 })).toBe(
|
|
50
|
+
"Applied 3 operation(s).",
|
|
51
|
+
)
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
describe("filterComposeNoise()", () => {
|
|
56
|
+
it("removes container progress lines but keeps errors", () => {
|
|
57
|
+
const output = [
|
|
58
|
+
" Container supatype-to-do-db-1 Running",
|
|
59
|
+
"engine error: permission denied",
|
|
60
|
+
'{"status":"up_to_date","operations":0}',
|
|
61
|
+
].join("\n")
|
|
62
|
+
|
|
63
|
+
expect(filterComposeNoise(output)).toBe(
|
|
64
|
+
'engine error: permission denied\n{"status":"up_to_date","operations":0}',
|
|
65
|
+
)
|
|
66
|
+
})
|
|
67
|
+
})
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"
|
|
2
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs"
|
|
3
|
+
import type { SupatypeProjectConfig } from "../src/project-config.js"
|
|
4
|
+
import * as binaryCache from "../src/binary-cache.js"
|
|
5
|
+
import { ensureBinary } from "../src/ensure-binary.js"
|
|
6
|
+
|
|
7
|
+
const TEST_VERSION = "9.9.9"
|
|
8
|
+
|
|
9
|
+
const config = (): SupatypeProjectConfig => ({
|
|
10
|
+
project: { name: "test" },
|
|
11
|
+
database: { provider: "native" },
|
|
12
|
+
server: { mode: "dev" },
|
|
13
|
+
app: { mode: "none" },
|
|
14
|
+
versions: {
|
|
15
|
+
engine: "0.4.2",
|
|
16
|
+
server: "0.1.0",
|
|
17
|
+
postgres: "17.2",
|
|
18
|
+
deno: TEST_VERSION,
|
|
19
|
+
},
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
const dir = binaryCache.cachePath("deno", TEST_VERSION)
|
|
24
|
+
if (existsSync(dir)) {
|
|
25
|
+
rmSync(dir, { recursive: true, force: true })
|
|
26
|
+
}
|
|
27
|
+
mkdirSync(dir, { recursive: true })
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
vi.restoreAllMocks()
|
|
32
|
+
const dir = binaryCache.cachePath("deno", TEST_VERSION)
|
|
33
|
+
if (existsSync(dir)) {
|
|
34
|
+
rmSync(dir, { recursive: true, force: true })
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
describe("ensureBinary", () => {
|
|
39
|
+
it("returns cached path without calling download", async () => {
|
|
40
|
+
const platform = binaryCache.currentPlatform()
|
|
41
|
+
const path = binaryCache.cachedBinaryPath("deno", TEST_VERSION, platform)
|
|
42
|
+
writeFileSync(path, "stub", { mode: 0o755 })
|
|
43
|
+
|
|
44
|
+
const downloadSpy = vi.spyOn(binaryCache, "download")
|
|
45
|
+
const resolved = await ensureBinary("deno", config())
|
|
46
|
+
expect(resolved).toBe(path)
|
|
47
|
+
expect(downloadSpy).not.toHaveBeenCalled()
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it("downloads on cache miss", async () => {
|
|
51
|
+
const platform = binaryCache.currentPlatform()
|
|
52
|
+
const expected = binaryCache.cachedBinaryPath("deno", TEST_VERSION, platform)
|
|
53
|
+
const downloadSpy = vi.spyOn(binaryCache, "download").mockResolvedValue(expected)
|
|
54
|
+
|
|
55
|
+
const path = await ensureBinary("deno", config())
|
|
56
|
+
expect(path).toBe(expected)
|
|
57
|
+
expect(downloadSpy).toHaveBeenCalledWith("deno", TEST_VERSION, platform)
|
|
58
|
+
})
|
|
59
|
+
})
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest"
|
|
2
|
+
import { mkdirSync, rmSync, existsSync, readFileSync, writeFileSync } from "node:fs"
|
|
3
|
+
import { join } from "node:path"
|
|
4
|
+
import { tmpdir } from "node:os"
|
|
5
|
+
import { scaffold } from "../src/commands/init.js"
|
|
6
|
+
|
|
7
|
+
let tmpRoot: string
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
tmpRoot = join(tmpdir(), `dt-init-test-${Date.now()}`)
|
|
11
|
+
mkdirSync(tmpRoot, { recursive: true })
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
rmSync(tmpRoot, { recursive: true, force: true })
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
describe("scaffold()", () => {
|
|
19
|
+
it("creates all expected files", () => {
|
|
20
|
+
scaffold(tmpRoot, "my-app")
|
|
21
|
+
|
|
22
|
+
const expected = [
|
|
23
|
+
"package.json",
|
|
24
|
+
"supatype.config.ts",
|
|
25
|
+
"schema/index.ts",
|
|
26
|
+
".env",
|
|
27
|
+
"seed.ts",
|
|
28
|
+
"seeds/.gitkeep",
|
|
29
|
+
"public/.gitkeep",
|
|
30
|
+
".gitignore",
|
|
31
|
+
]
|
|
32
|
+
for (const rel of expected) {
|
|
33
|
+
expect(existsSync(join(tmpRoot, rel)), `${rel} should exist`).toBe(true)
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it("supatype.config.ts embeds the project name and exports defineConfig", () => {
|
|
38
|
+
scaffold(tmpRoot, "blog-app")
|
|
39
|
+
const content = readFileSync(join(tmpRoot, "supatype.config.ts"), "utf8")
|
|
40
|
+
expect(content).toContain("blog-app")
|
|
41
|
+
expect(content).toContain("defineConfig")
|
|
42
|
+
expect(content).toContain('provider: "docker"')
|
|
43
|
+
expect(content).toContain("schema:")
|
|
44
|
+
expect(content).toContain("Optional: pin component versions")
|
|
45
|
+
expect(content).not.toMatch(/^\s*versions:\s*\{/m)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it("package.json includes @supatype/cli and @supatype/types", () => {
|
|
49
|
+
scaffold(tmpRoot, "pkg-app")
|
|
50
|
+
const content = readFileSync(join(tmpRoot, "package.json"), "utf8")
|
|
51
|
+
expect(content).toContain("@supatype/cli")
|
|
52
|
+
expect(content).toContain("@supatype/types")
|
|
53
|
+
expect(content).toContain("pkg-app")
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it("skips package.json when it already exists", () => {
|
|
57
|
+
const pkgPath = join(tmpRoot, "package.json")
|
|
58
|
+
writeFileSync(pkgPath, '{"name":"existing"}', "utf8")
|
|
59
|
+
scaffold(tmpRoot, "my-app")
|
|
60
|
+
expect(readFileSync(pkgPath, "utf8")).toBe('{"name":"existing"}')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it("supatype.config.ts documents self-host workflow", () => {
|
|
64
|
+
scaffold(tmpRoot, "my-app")
|
|
65
|
+
const content = readFileSync(join(tmpRoot, "supatype.config.ts"), "utf8")
|
|
66
|
+
expect(content).toContain("self-host")
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it(".env contains DATABASE_URL, JWT_SECRET, POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB", () => {
|
|
70
|
+
scaffold(tmpRoot, "my-app")
|
|
71
|
+
const content = readFileSync(join(tmpRoot, ".env"), "utf8")
|
|
72
|
+
expect(content).toContain("DATABASE_URL=")
|
|
73
|
+
expect(content).toContain("JWT_SECRET=")
|
|
74
|
+
expect(content).toContain("POSTGRES_USER=")
|
|
75
|
+
expect(content).toContain("POSTGRES_PASSWORD=")
|
|
76
|
+
expect(content).toContain("POSTGRES_DB=")
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it(".env contains ANON_KEY, SERVICE_ROLE_KEY, and SITE_URL placeholders", () => {
|
|
80
|
+
scaffold(tmpRoot, "my-app")
|
|
81
|
+
const content = readFileSync(join(tmpRoot, ".env"), "utf8")
|
|
82
|
+
expect(content).toContain("ANON_KEY=")
|
|
83
|
+
expect(content).toContain("SERVICE_ROLE_KEY=")
|
|
84
|
+
expect(content).toContain("SITE_URL=")
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it("schema/index.ts exports a User model using RFC v2 Model<>", () => {
|
|
88
|
+
scaffold(tmpRoot, "my-app")
|
|
89
|
+
const content = readFileSync(join(tmpRoot, "schema/index.ts"), "utf8")
|
|
90
|
+
expect(content).toContain("export type User")
|
|
91
|
+
expect(content).toContain("Model<")
|
|
92
|
+
expect(content).toContain("access:")
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it(".gitignore excludes .env, node_modules, and engine binary", () => {
|
|
96
|
+
scaffold(tmpRoot, "my-app")
|
|
97
|
+
const content = readFileSync(join(tmpRoot, ".gitignore"), "utf8")
|
|
98
|
+
expect(content).toContain(".env")
|
|
99
|
+
expect(content).toContain("node_modules/")
|
|
100
|
+
expect(content).toContain(".supatype/engine/")
|
|
101
|
+
expect(content).toContain("supatype.local.config.ts")
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it("seed.ts references the project name", () => {
|
|
105
|
+
scaffold(tmpRoot, "acme")
|
|
106
|
+
const content = readFileSync(join(tmpRoot, "seed.ts"), "utf8")
|
|
107
|
+
expect(content).toContain("acme")
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it("different project names produce different config bodies", () => {
|
|
111
|
+
scaffold(tmpRoot, "alpha")
|
|
112
|
+
const alpha = readFileSync(join(tmpRoot, "supatype.config.ts"), "utf8")
|
|
113
|
+
|
|
114
|
+
const tmp2 = join(tmpdir(), `dt-init-test2-${Date.now()}`)
|
|
115
|
+
mkdirSync(tmp2, { recursive: true })
|
|
116
|
+
try {
|
|
117
|
+
scaffold(tmp2, "beta")
|
|
118
|
+
const beta = readFileSync(join(tmp2, "supatype.config.ts"), "utf8")
|
|
119
|
+
expect(alpha).toContain("alpha")
|
|
120
|
+
expect(beta).toContain("beta")
|
|
121
|
+
expect(alpha).not.toContain("beta")
|
|
122
|
+
expect(beta).not.toContain("alpha")
|
|
123
|
+
} finally {
|
|
124
|
+
rmSync(tmp2, { recursive: true, force: true })
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
})
|