@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,457 @@
|
|
|
1
|
+
import { resolve } from "node:path"
|
|
2
|
+
import ts from "typescript"
|
|
3
|
+
|
|
4
|
+
export type AliasEntry = {
|
|
5
|
+
typeParams: string[]
|
|
6
|
+
body: ts.TypeNode
|
|
7
|
+
sourceFile: ts.SourceFile
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** fileName → (localName → canonicalName) for explicit `import { X as Y }` renames. */
|
|
11
|
+
export type ImportRenameMap = Map<string, Map<string, string>>
|
|
12
|
+
|
|
13
|
+
export type CheckerContext = {
|
|
14
|
+
program: ts.Program
|
|
15
|
+
checker: ts.TypeChecker
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type ResolveContext = {
|
|
19
|
+
aliasRegistry: Map<string, AliasEntry>
|
|
20
|
+
renameMap: ImportRenameMap
|
|
21
|
+
sourceFiles: ts.SourceFile[]
|
|
22
|
+
getChecker: () => CheckerContext
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const MODEL_ALIAS_NAMES = new Set(["Model", "LocalizedModel"])
|
|
26
|
+
|
|
27
|
+
export function createResolveContext(sourceFiles: ts.SourceFile[]): ResolveContext {
|
|
28
|
+
let checkerCtx: CheckerContext | undefined
|
|
29
|
+
return {
|
|
30
|
+
aliasRegistry: buildAliasRegistry(sourceFiles),
|
|
31
|
+
renameMap: buildImportRenameMap(sourceFiles),
|
|
32
|
+
sourceFiles,
|
|
33
|
+
getChecker: () => {
|
|
34
|
+
if (!checkerCtx) {
|
|
35
|
+
checkerCtx = createCheckerContext(sourceFiles)
|
|
36
|
+
}
|
|
37
|
+
return checkerCtx
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function buildAliasRegistry(sourceFiles: ts.SourceFile[]): Map<string, AliasEntry> {
|
|
43
|
+
const registry = new Map<string, AliasEntry>()
|
|
44
|
+
for (const sourceFile of sourceFiles) {
|
|
45
|
+
for (const stmt of sourceFile.statements) {
|
|
46
|
+
if (!ts.isTypeAliasDeclaration(stmt)) continue
|
|
47
|
+
if (ts.isTypeReferenceNode(stmt.type) && ts.isIdentifier(stmt.type.typeName)) {
|
|
48
|
+
if (MODEL_ALIAS_NAMES.has(stmt.type.typeName.text)) continue
|
|
49
|
+
}
|
|
50
|
+
const typeParams = stmt.typeParameters?.map((p) => p.name.text) ?? []
|
|
51
|
+
registry.set(stmt.name.text, {
|
|
52
|
+
typeParams,
|
|
53
|
+
body: stmt.type,
|
|
54
|
+
sourceFile,
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return registry
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function buildImportRenameMap(sourceFiles: ts.SourceFile[]): ImportRenameMap {
|
|
62
|
+
const renameMap: ImportRenameMap = new Map()
|
|
63
|
+
for (const sourceFile of sourceFiles) {
|
|
64
|
+
const fileRenames = new Map<string, string>()
|
|
65
|
+
for (const stmt of sourceFile.statements) {
|
|
66
|
+
if (!ts.isImportDeclaration(stmt) || !stmt.importClause) continue
|
|
67
|
+
const bindings = stmt.importClause.namedBindings
|
|
68
|
+
if (!bindings || !ts.isNamedImports(bindings)) continue
|
|
69
|
+
for (const el of bindings.elements) {
|
|
70
|
+
const localName = el.name.text
|
|
71
|
+
const canonicalName = el.propertyName?.text ?? localName
|
|
72
|
+
if (localName !== canonicalName) {
|
|
73
|
+
fileRenames.set(localName, canonicalName)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (fileRenames.size > 0) {
|
|
78
|
+
renameMap.set(sourceFile.fileName, fileRenames)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return renameMap
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function applyImportRename(
|
|
85
|
+
name: string,
|
|
86
|
+
sourceFile: ts.SourceFile,
|
|
87
|
+
renameMap: ImportRenameMap,
|
|
88
|
+
): string {
|
|
89
|
+
return renameMap.get(sourceFile.fileName)?.get(name) ?? name
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function needsChecker(node: ts.TypeNode): boolean {
|
|
93
|
+
return containsConditionalOrMapped(node)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function resolveTypeNode(
|
|
97
|
+
typeNode: ts.TypeNode,
|
|
98
|
+
sourceFile: ts.SourceFile,
|
|
99
|
+
ctx: ResolveContext,
|
|
100
|
+
options: { fieldName?: string; resolving?: Set<string> } = {},
|
|
101
|
+
): ts.TypeNode {
|
|
102
|
+
if (needsChecker(typeNode)) {
|
|
103
|
+
return resolveViaChecker(typeNode, sourceFile, ctx, options.fieldName)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) {
|
|
107
|
+
const renamed = applyImportRename(typeNode.typeName.text, sourceFile, ctx.renameMap)
|
|
108
|
+
if (renamed !== typeNode.typeName.text) {
|
|
109
|
+
return resolveTypeNode(
|
|
110
|
+
ts.factory.createTypeReferenceNode(renamed, typeNode.typeArguments),
|
|
111
|
+
sourceFile,
|
|
112
|
+
ctx,
|
|
113
|
+
options,
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
const expanded = tryExpandAlias(typeNode, sourceFile, ctx, options)
|
|
117
|
+
if (expanded) {
|
|
118
|
+
return resolveTypeNode(expanded, sourceFile, ctx, options)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return typeNode
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function tryResolveTypeReference(
|
|
126
|
+
typeNode: ts.TypeReferenceNode,
|
|
127
|
+
sourceFile: ts.SourceFile,
|
|
128
|
+
ctx: ResolveContext,
|
|
129
|
+
options: { fieldName?: string; resolving?: Set<string> } = {},
|
|
130
|
+
): ts.TypeNode | null {
|
|
131
|
+
if (!ts.isIdentifier(typeNode.typeName)) return null
|
|
132
|
+
|
|
133
|
+
const renamed = applyImportRename(typeNode.typeName.text, sourceFile, ctx.renameMap)
|
|
134
|
+
if (renamed !== typeNode.typeName.text) {
|
|
135
|
+
return ts.factory.createTypeReferenceNode(renamed, typeNode.typeArguments)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return tryExpandAlias(typeNode, sourceFile, ctx, options)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function tryExpandAlias(
|
|
142
|
+
typeNode: ts.TypeReferenceNode,
|
|
143
|
+
sourceFile: ts.SourceFile,
|
|
144
|
+
ctx: ResolveContext,
|
|
145
|
+
options: { fieldName?: string; resolving?: Set<string> },
|
|
146
|
+
): ts.TypeNode | null {
|
|
147
|
+
if (!ts.isIdentifier(typeNode.typeName)) return null
|
|
148
|
+
const aliasName = applyImportRename(typeNode.typeName.text, sourceFile, ctx.renameMap)
|
|
149
|
+
const entry = ctx.aliasRegistry.get(aliasName)
|
|
150
|
+
if (!entry) return null
|
|
151
|
+
|
|
152
|
+
const resolving = options.resolving ?? new Set<string>()
|
|
153
|
+
if (resolving.has(aliasName)) {
|
|
154
|
+
throw new Error(
|
|
155
|
+
`Field "${options.fieldName ?? "?"}": circular alias chain detected resolving "${aliasName}".`,
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
resolving.add(aliasName)
|
|
160
|
+
|
|
161
|
+
const typeArgs = typeNode.typeArguments ?? []
|
|
162
|
+
const synthetic =
|
|
163
|
+
tryEvaluateConditionalAlias(entry, typeArgs) ?? tryEvaluateMappedOptionalFields(entry, typeArgs)
|
|
164
|
+
if (synthetic) {
|
|
165
|
+
return expandAliasChain(synthetic, sourceFile, ctx, options, resolving)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let expanded: ts.TypeNode
|
|
169
|
+
if (needsChecker(entry.body)) {
|
|
170
|
+
expanded = resolveViaChecker(typeNode, sourceFile, ctx, options.fieldName)
|
|
171
|
+
} else {
|
|
172
|
+
expanded = substituteAndParse(entry, typeArgs)
|
|
173
|
+
if (needsChecker(expanded)) {
|
|
174
|
+
expanded = resolveViaChecker(typeNode, sourceFile, ctx, options.fieldName)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return expandAliasChain(expanded, sourceFile, ctx, options, resolving)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function expandAliasChain(
|
|
182
|
+
typeNode: ts.TypeNode,
|
|
183
|
+
sourceFile: ts.SourceFile,
|
|
184
|
+
ctx: ResolveContext,
|
|
185
|
+
options: { fieldName?: string; resolving?: Set<string> },
|
|
186
|
+
resolving: Set<string>,
|
|
187
|
+
): ts.TypeNode {
|
|
188
|
+
if (!ts.isTypeReferenceNode(typeNode) || !ts.isIdentifier(typeNode.typeName)) {
|
|
189
|
+
return typeNode
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const chainName = applyImportRename(typeNode.typeName.text, sourceFile, ctx.renameMap)
|
|
193
|
+
if (!ctx.aliasRegistry.has(chainName) || resolving.has(chainName)) {
|
|
194
|
+
return typeNode
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const entry = ctx.aliasRegistry.get(chainName)!
|
|
198
|
+
resolving.add(chainName)
|
|
199
|
+
|
|
200
|
+
const typeArgs = typeNode.typeArguments ?? []
|
|
201
|
+
const synthetic =
|
|
202
|
+
tryEvaluateConditionalAlias(entry, typeArgs) ?? tryEvaluateMappedOptionalFields(entry, typeArgs)
|
|
203
|
+
if (synthetic) {
|
|
204
|
+
return expandAliasChain(synthetic, sourceFile, ctx, options, resolving)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
let expanded: ts.TypeNode
|
|
208
|
+
if (needsChecker(entry.body)) {
|
|
209
|
+
expanded = resolveViaChecker(typeNode, sourceFile, ctx, options.fieldName)
|
|
210
|
+
} else {
|
|
211
|
+
expanded = substituteAndParse(entry, typeArgs)
|
|
212
|
+
if (needsChecker(expanded)) {
|
|
213
|
+
expanded = resolveViaChecker(typeNode, sourceFile, ctx, options.fieldName)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return expandAliasChain(expanded, sourceFile, ctx, options, resolving)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function substituteAndParse(
|
|
221
|
+
entry: AliasEntry,
|
|
222
|
+
typeArgs: readonly ts.TypeNode[],
|
|
223
|
+
): ts.TypeNode {
|
|
224
|
+
let text = entry.body.getText(entry.sourceFile)
|
|
225
|
+
for (let i = 0; i < entry.typeParams.length; i++) {
|
|
226
|
+
const param = entry.typeParams[i]
|
|
227
|
+
const arg = typeArgs[i]
|
|
228
|
+
if (!param || !arg) continue
|
|
229
|
+
const argSource = arg.getSourceFile()
|
|
230
|
+
const argText = arg.getText(argSource)
|
|
231
|
+
text = text.replace(new RegExp(`\\b${escapeRegExp(param)}\\b`, "g"), argText)
|
|
232
|
+
}
|
|
233
|
+
const parsed = ts.createSourceFile(
|
|
234
|
+
"__alias__.ts",
|
|
235
|
+
`type __A__ = ${text}`,
|
|
236
|
+
ts.ScriptTarget.Latest,
|
|
237
|
+
true,
|
|
238
|
+
ts.ScriptKind.TS,
|
|
239
|
+
)
|
|
240
|
+
const decl = parsed.statements[0]
|
|
241
|
+
if (!decl || !ts.isTypeAliasDeclaration(decl)) {
|
|
242
|
+
throw new Error("Internal error: failed to parse substituted type alias body.")
|
|
243
|
+
}
|
|
244
|
+
return decl.type
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function resolveViaChecker(
|
|
248
|
+
node: ts.TypeNode,
|
|
249
|
+
sourceFile: ts.SourceFile,
|
|
250
|
+
ctx: ResolveContext,
|
|
251
|
+
fieldName?: string,
|
|
252
|
+
): ts.TypeNode {
|
|
253
|
+
const { program, checker } = ctx.getChecker()
|
|
254
|
+
const nodeFileName = resolve(node.getSourceFile().fileName)
|
|
255
|
+
const programSf = program.getSourceFile(nodeFileName) ?? program.getSourceFile(resolve(sourceFile.fileName))
|
|
256
|
+
if (!programSf) {
|
|
257
|
+
throw checkerResolutionError(fieldName)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const programNode = findNodeAtPosition(programSf, node.pos, node.end) ?? node
|
|
261
|
+
|
|
262
|
+
const type = checker.getTypeAtLocation(programNode)
|
|
263
|
+
let typeText = checker.typeToString(
|
|
264
|
+
type,
|
|
265
|
+
programNode,
|
|
266
|
+
ts.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope,
|
|
267
|
+
)
|
|
268
|
+
if (!typeText.trim()) {
|
|
269
|
+
throw checkerResolutionError(fieldName)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
let parsed = parseResolvedTypeText(typeText)
|
|
273
|
+
if (needsChecker(parsed)) {
|
|
274
|
+
typeText = checker.typeToString(type, programNode)
|
|
275
|
+
parsed = parseResolvedTypeText(typeText)
|
|
276
|
+
}
|
|
277
|
+
if (needsChecker(parsed)) {
|
|
278
|
+
throw checkerResolutionError(fieldName)
|
|
279
|
+
}
|
|
280
|
+
return parsed
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function parseResolvedTypeText(typeText: string): ts.TypeNode {
|
|
284
|
+
const parsed = ts.createSourceFile(
|
|
285
|
+
"__resolved__.ts",
|
|
286
|
+
`type __R__ = ${typeText}`,
|
|
287
|
+
ts.ScriptTarget.Latest,
|
|
288
|
+
true,
|
|
289
|
+
ts.ScriptKind.TS,
|
|
290
|
+
)
|
|
291
|
+
const decl = parsed.statements[0]
|
|
292
|
+
if (!decl || !ts.isTypeAliasDeclaration(decl)) {
|
|
293
|
+
throw new Error("Internal error: failed to parse checker output.")
|
|
294
|
+
}
|
|
295
|
+
return decl.type
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/** `T extends string ? Optional<T> : T` and similar — pick the Optional branch for scalar args. */
|
|
299
|
+
function tryEvaluateConditionalAlias(entry: AliasEntry, typeArgs: readonly ts.TypeNode[]): ts.TypeNode | null {
|
|
300
|
+
const substituted = substituteAndParse(entry, typeArgs)
|
|
301
|
+
if (!ts.isConditionalTypeNode(substituted)) return null
|
|
302
|
+
if (substituted.extendsType.getText() !== "string") return null
|
|
303
|
+
const trueType = substituted.trueType
|
|
304
|
+
if (
|
|
305
|
+
ts.isTypeReferenceNode(trueType) &&
|
|
306
|
+
ts.isIdentifier(trueType.typeName) &&
|
|
307
|
+
trueType.typeName.text === "Optional"
|
|
308
|
+
) {
|
|
309
|
+
return trueType
|
|
310
|
+
}
|
|
311
|
+
return null
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** `{ [K in keyof T]: Optional<T[K]> }` with a concrete `T` type literal. */
|
|
315
|
+
function tryEvaluateMappedOptionalFields(entry: AliasEntry, typeArgs: readonly ts.TypeNode[]): ts.TypeNode | null {
|
|
316
|
+
if (!ts.isMappedTypeNode(entry.body)) return null
|
|
317
|
+
const template = entry.body.type
|
|
318
|
+
if (
|
|
319
|
+
template === undefined ||
|
|
320
|
+
!ts.isTypeReferenceNode(template) ||
|
|
321
|
+
template.typeName.getText() !== "Optional"
|
|
322
|
+
) {
|
|
323
|
+
return null
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const arg = typeArgs[0]
|
|
327
|
+
if (!arg || !ts.isTypeLiteralNode(arg)) return null
|
|
328
|
+
|
|
329
|
+
const argSource = arg.getSourceFile()
|
|
330
|
+
const parts: string[] = []
|
|
331
|
+
for (const member of arg.members) {
|
|
332
|
+
if (!ts.isPropertySignature(member) || !member.type) continue
|
|
333
|
+
const name = member.name.getText(argSource)
|
|
334
|
+
const typeText = member.type.getText(argSource)
|
|
335
|
+
parts.push(`${name}: Optional<${typeText}>`)
|
|
336
|
+
}
|
|
337
|
+
if (parts.length === 0) return null
|
|
338
|
+
|
|
339
|
+
const parsed = ts.createSourceFile(
|
|
340
|
+
"__mapped__.ts",
|
|
341
|
+
`type __M__ = { ${parts.join("; ")} }`,
|
|
342
|
+
ts.ScriptTarget.Latest,
|
|
343
|
+
true,
|
|
344
|
+
ts.ScriptKind.TS,
|
|
345
|
+
)
|
|
346
|
+
const decl = parsed.statements[0]
|
|
347
|
+
if (!decl || !ts.isTypeAliasDeclaration(decl) || !ts.isTypeLiteralNode(decl.type)) return null
|
|
348
|
+
return decl.type
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function checkerResolutionError(fieldName?: string): Error {
|
|
352
|
+
const prefix = fieldName ? `Field "${fieldName}": ` : ""
|
|
353
|
+
return new Error(`${prefix}could not resolve conditional/mapped type via type checker.`)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const SUPATYPE_CHECKER_STUB_PATH = resolve(process.cwd(), "__supatype_checker_stubs__.ts")
|
|
357
|
+
const SUPATYPE_CHECKER_STUB_SOURCE = `
|
|
358
|
+
export type Optional<T> = T;
|
|
359
|
+
export type Unique<T> = T;
|
|
360
|
+
export type PrimaryKey<T> = T;
|
|
361
|
+
export type Localized<T> = T;
|
|
362
|
+
export type NotLocalized<T> = T;
|
|
363
|
+
export type Default<T, V> = T;
|
|
364
|
+
export type Email = string & { readonly __supatypeEmailBrand: unique symbol };
|
|
365
|
+
export type UUID = string & { readonly __supatypeUuidBrand: unique symbol };
|
|
366
|
+
export type RichText = unknown;
|
|
367
|
+
export type Int = number;
|
|
368
|
+
export type SmallInt = number;
|
|
369
|
+
export type BigInt = number;
|
|
370
|
+
export type Timestamp = string;
|
|
371
|
+
export type Date = string;
|
|
372
|
+
export type DateTime = string;
|
|
373
|
+
export type DateOnly = string;
|
|
374
|
+
export type Slug<S extends string = string> = string;
|
|
375
|
+
export type RelatedTo<T> = string;
|
|
376
|
+
export type Text = string;
|
|
377
|
+
`
|
|
378
|
+
|
|
379
|
+
function createCheckerStubFile(): ts.SourceFile {
|
|
380
|
+
return ts.createSourceFile(
|
|
381
|
+
SUPATYPE_CHECKER_STUB_PATH,
|
|
382
|
+
SUPATYPE_CHECKER_STUB_SOURCE,
|
|
383
|
+
ts.ScriptTarget.Latest,
|
|
384
|
+
true,
|
|
385
|
+
ts.ScriptKind.TS,
|
|
386
|
+
)
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function createCheckerContext(sourceFiles: ts.SourceFile[]): CheckerContext {
|
|
390
|
+
const stubFile = createCheckerStubFile()
|
|
391
|
+
const fileMap = new Map<string, ts.SourceFile>([
|
|
392
|
+
[resolve(stubFile.fileName), stubFile],
|
|
393
|
+
...sourceFiles.map((sf) => [resolve(sf.fileName), sf] as const),
|
|
394
|
+
])
|
|
395
|
+
const options: ts.CompilerOptions = {
|
|
396
|
+
target: ts.ScriptTarget.Latest,
|
|
397
|
+
skipLibCheck: true,
|
|
398
|
+
baseUrl: process.cwd(),
|
|
399
|
+
paths: {
|
|
400
|
+
"@supatype/types": ["__supatype_checker_stubs__.ts"],
|
|
401
|
+
},
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const host: ts.CompilerHost = {
|
|
405
|
+
getSourceFile: (fileName, languageVersion) => {
|
|
406
|
+
const existing = fileMap.get(resolve(fileName))
|
|
407
|
+
if (existing) return existing
|
|
408
|
+
const libContent = ts.sys.readFile(fileName)
|
|
409
|
+
if (libContent) {
|
|
410
|
+
return ts.createSourceFile(fileName, libContent, languageVersion, true)
|
|
411
|
+
}
|
|
412
|
+
return undefined
|
|
413
|
+
},
|
|
414
|
+
getDefaultLibFileName: (opts) => ts.getDefaultLibFileName(opts ?? options),
|
|
415
|
+
writeFile: () => {},
|
|
416
|
+
getCurrentDirectory: () => process.cwd(),
|
|
417
|
+
getCanonicalFileName: (f) => f,
|
|
418
|
+
useCaseSensitiveFileNames: () => true,
|
|
419
|
+
getNewLine: () => ts.sys.newLine,
|
|
420
|
+
fileExists: (fileName) => fileMap.has(resolve(fileName)) || ts.sys.fileExists(fileName),
|
|
421
|
+
readFile: (fileName) => fileMap.get(resolve(fileName))?.getFullText() ?? ts.sys.readFile(fileName),
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const program = ts.createProgram([...fileMap.keys()], options, host)
|
|
425
|
+
return { program, checker: program.getTypeChecker() }
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function findNodeAtPosition(root: ts.Node, pos: number, end: number): ts.Node | undefined {
|
|
429
|
+
const queue: ts.Node[] = [root]
|
|
430
|
+
while (queue.length > 0) {
|
|
431
|
+
const current = queue.shift()
|
|
432
|
+
if (!current) continue
|
|
433
|
+
if (current.pos === pos && current.end === end) return current
|
|
434
|
+
ts.forEachChild(current, (child) => queue.push(child))
|
|
435
|
+
}
|
|
436
|
+
return undefined
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function containsConditionalOrMapped(node: ts.Node): boolean {
|
|
440
|
+
if (ts.isConditionalTypeNode(node) || ts.isMappedTypeNode(node)) return true
|
|
441
|
+
let found = false
|
|
442
|
+
ts.forEachChild(node, (child) => {
|
|
443
|
+
if (!found && containsConditionalOrMapped(child)) found = true
|
|
444
|
+
})
|
|
445
|
+
return found
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function escapeRegExp(value: string): string {
|
|
449
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
export function unknownTypeError(typeName: string, fieldName: string): Error {
|
|
453
|
+
return new Error(
|
|
454
|
+
`Unknown Supatype type "${typeName}" in field "${fieldName}". ` +
|
|
455
|
+
"If this is a type alias, confirm the file defining it is reachable from your schema entry point.",
|
|
456
|
+
)
|
|
457
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest"
|
|
2
|
+
import { mkdirSync, rmSync, readFileSync, existsSync } from "node:fs"
|
|
3
|
+
import { join } from "node:path"
|
|
4
|
+
import { tmpdir } from "node:os"
|
|
5
|
+
import { spawnSync } from "node:child_process"
|
|
6
|
+
import { resolve, dirname } from "node:path"
|
|
7
|
+
import { fileURLToPath } from "node:url"
|
|
8
|
+
import { scaffold } from "../src/commands/init.js"
|
|
9
|
+
|
|
10
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
11
|
+
const CLI_BIN = resolve(__dirname, "../bin/supatype.js")
|
|
12
|
+
|
|
13
|
+
function runCli(cwd: string, args: string[]): { stdout: string; stderr: string; exitCode: number } {
|
|
14
|
+
const result = spawnSync(process.execPath, [CLI_BIN, ...args], {
|
|
15
|
+
encoding: "utf8",
|
|
16
|
+
cwd,
|
|
17
|
+
timeout: 10_000,
|
|
18
|
+
})
|
|
19
|
+
return {
|
|
20
|
+
stdout: String(result.stdout ?? ""),
|
|
21
|
+
stderr: String(result.stderr ?? ""),
|
|
22
|
+
exitCode: result.status ?? 1,
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let tmpRoot: string
|
|
27
|
+
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
tmpRoot = join(tmpdir(), `dt-app-test-${Date.now()}`)
|
|
30
|
+
mkdirSync(tmpRoot, { recursive: true })
|
|
31
|
+
scaffold(tmpRoot, "app-test")
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
rmSync(tmpRoot, { recursive: true, force: true })
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
describe("supatype app add --static", () => {
|
|
39
|
+
it("sets app.mode=static and static_dir in supatype.config.ts", () => {
|
|
40
|
+
const { exitCode } = runCli(tmpRoot, ["app", "add", "--static", "./public"])
|
|
41
|
+
expect(exitCode).toBe(0)
|
|
42
|
+
const config = readFileSync(join(tmpRoot, "supatype.config.ts"), "utf8")
|
|
43
|
+
expect(config).toContain('mode: "static"')
|
|
44
|
+
expect(config).toContain('static_dir: "./public"')
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it("creates the static directory when missing", () => {
|
|
48
|
+
const siteDir = join(tmpRoot, "site")
|
|
49
|
+
expect(existsSync(siteDir)).toBe(false)
|
|
50
|
+
const { exitCode } = runCli(tmpRoot, ["app", "add", "--static", "./site"])
|
|
51
|
+
expect(exitCode).toBe(0)
|
|
52
|
+
expect(existsSync(siteDir)).toBe(true)
|
|
53
|
+
})
|
|
54
|
+
})
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
|
+
import { generateClientAugmentation } from "../src/augmentation-generator.js"
|
|
3
|
+
|
|
4
|
+
describe("generateClientAugmentation", () => {
|
|
5
|
+
it("emits deterministic output independent of model order", () => {
|
|
6
|
+
const astA = {
|
|
7
|
+
models: [
|
|
8
|
+
{ tableName: "post", fields: { title: { kind: "text", required: true } } },
|
|
9
|
+
{ tableName: "comment", fields: { body: { kind: "text", required: true } } },
|
|
10
|
+
],
|
|
11
|
+
}
|
|
12
|
+
const astB = {
|
|
13
|
+
models: [
|
|
14
|
+
{ tableName: "comment", fields: { body: { kind: "text", required: true } } },
|
|
15
|
+
{ tableName: "post", fields: { title: { kind: "text", required: true } } },
|
|
16
|
+
],
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
expect(generateClientAugmentation(astA)).toEqual(generateClientAugmentation(astB))
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it("marks Insert fields optional when serverGenerated or default is set", () => {
|
|
23
|
+
const ast = {
|
|
24
|
+
models: [
|
|
25
|
+
{
|
|
26
|
+
tableName: "widget",
|
|
27
|
+
fields: {
|
|
28
|
+
id: { kind: "uuid", pgType: "UUID", required: true, primaryKey: true, default: { kind: "genRandomUuid" } },
|
|
29
|
+
name: { kind: "text", pgType: "TEXT", required: true },
|
|
30
|
+
created_at: { kind: "text", pgType: "TEXT", required: true, serverGenerated: true },
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
}
|
|
35
|
+
const out = generateClientAugmentation(ast)
|
|
36
|
+
const insertOnly = out.split("Update:")[0] ?? out
|
|
37
|
+
expect(insertOnly).toContain("id?:")
|
|
38
|
+
expect(insertOnly).toContain("created_at?:")
|
|
39
|
+
expect(insertOnly).toContain("name: string")
|
|
40
|
+
expect(insertOnly).not.toContain("name?:")
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it("types richText fields as SerializedEditorState", () => {
|
|
44
|
+
const ast = {
|
|
45
|
+
models: [
|
|
46
|
+
{
|
|
47
|
+
tableName: "note",
|
|
48
|
+
fields: {
|
|
49
|
+
id: { kind: "uuid", pgType: "UUID", required: true, primaryKey: true, default: { kind: "genRandomUuid" } },
|
|
50
|
+
body: { kind: "richText", pgType: "JSONB", required: true },
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
}
|
|
55
|
+
const out = generateClientAugmentation(ast)
|
|
56
|
+
expect(out).toContain('import("@supatype/types/lexical").SerializedEditorState')
|
|
57
|
+
expect(out).not.toMatch(/body: Record<string, unknown>/)
|
|
58
|
+
})
|
|
59
|
+
})
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest"
|
|
2
|
+
import { mkdirSync, rmSync, writeFileSync } from "node:fs"
|
|
3
|
+
import { join } from "node:path"
|
|
4
|
+
import { tmpdir } from "node:os"
|
|
5
|
+
import {
|
|
6
|
+
hasMeaningfulOverrides,
|
|
7
|
+
isLinkedToCloudProject,
|
|
8
|
+
resolveBinary,
|
|
9
|
+
VERSION_PIN_LOCAL,
|
|
10
|
+
describeActiveOverrides,
|
|
11
|
+
} from "../src/binary-cache.js"
|
|
12
|
+
import type { SupatypeProjectConfig } from "../src/project-config.js"
|
|
13
|
+
|
|
14
|
+
function baseConfig(overrides?: SupatypeProjectConfig["overrides"]): SupatypeProjectConfig {
|
|
15
|
+
return {
|
|
16
|
+
project: { name: "t" },
|
|
17
|
+
database: { provider: "native" },
|
|
18
|
+
server: { mode: "dev" },
|
|
19
|
+
app: { mode: "none" },
|
|
20
|
+
versions: { engine: "0.1.0", server: "0.1.0", postgres: "17", deno: "2.0.0" },
|
|
21
|
+
...(overrides !== undefined ? { overrides } : {}),
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let tmp: string
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
tmp = join(tmpdir(), `dt-bc-${Date.now()}`)
|
|
28
|
+
mkdirSync(join(tmp, ".supatype"), { recursive: true })
|
|
29
|
+
})
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
rmSync(tmp, { recursive: true, force: true })
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
describe("hasMeaningfulOverrides", () => {
|
|
35
|
+
it("is false without overrides", () => {
|
|
36
|
+
expect(hasMeaningfulOverrides(baseConfig())).toBe(false)
|
|
37
|
+
})
|
|
38
|
+
it("is false for empty strings only", () => {
|
|
39
|
+
expect(hasMeaningfulOverrides(baseConfig({ engine: " " }))).toBe(false)
|
|
40
|
+
})
|
|
41
|
+
it("is true when any path is non-empty", () => {
|
|
42
|
+
expect(hasMeaningfulOverrides(baseConfig({ engine: "/x/y" }))).toBe(true)
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
describe("isLinkedToCloudProject", () => {
|
|
47
|
+
it("is true when project.ref is set", () => {
|
|
48
|
+
const cfg = baseConfig()
|
|
49
|
+
cfg.project.ref = "my-slug"
|
|
50
|
+
expect(isLinkedToCloudProject(tmp, cfg)).toBe(true)
|
|
51
|
+
})
|
|
52
|
+
it("is true when .supatype/cloud.json has projectSlug", () => {
|
|
53
|
+
writeFileSync(
|
|
54
|
+
join(tmp, ".supatype", "cloud.json"),
|
|
55
|
+
JSON.stringify({ apiUrl: "x", token: "t", projectSlug: "p" }),
|
|
56
|
+
)
|
|
57
|
+
expect(isLinkedToCloudProject(tmp, baseConfig())).toBe(true)
|
|
58
|
+
})
|
|
59
|
+
it("is true when .supatype/linked.json has ref", () => {
|
|
60
|
+
writeFileSync(join(tmp, ".supatype", "linked.json"), JSON.stringify({ ref: "r1" }))
|
|
61
|
+
expect(isLinkedToCloudProject(tmp, baseConfig())).toBe(true)
|
|
62
|
+
})
|
|
63
|
+
it("is false with token-only cloud.json", () => {
|
|
64
|
+
writeFileSync(join(tmp, ".supatype", "cloud.json"), JSON.stringify({ apiUrl: "x", token: "t" }))
|
|
65
|
+
expect(isLinkedToCloudProject(tmp, baseConfig())).toBe(false)
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
describe("resolveBinary + cloud link", () => {
|
|
70
|
+
const prevCwd = process.cwd()
|
|
71
|
+
|
|
72
|
+
beforeEach(() => {
|
|
73
|
+
process.chdir(tmp)
|
|
74
|
+
})
|
|
75
|
+
afterEach(() => {
|
|
76
|
+
process.chdir(prevCwd)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it("rejects any meaningful overrides when cloud.json is linked", async () => {
|
|
80
|
+
writeFileSync(
|
|
81
|
+
join(tmp, ".supatype", "cloud.json"),
|
|
82
|
+
JSON.stringify({ apiUrl: "x", token: "t", projectSlug: "p" }),
|
|
83
|
+
)
|
|
84
|
+
const cfg = baseConfig({ studio: "/does/not/need/to/exist/for/early/throw" })
|
|
85
|
+
await expect(resolveBinary("engine", cfg)).rejects.toThrow(/linked to Supatype Cloud/)
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
describe("VERSION_PIN_LOCAL + resolveBinary", () => {
|
|
90
|
+
const prevCwd = process.cwd()
|
|
91
|
+
|
|
92
|
+
beforeEach(() => {
|
|
93
|
+
process.chdir(tmp)
|
|
94
|
+
})
|
|
95
|
+
afterEach(() => {
|
|
96
|
+
process.chdir(prevCwd)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it("throws when versions.engine is local without overrides.engine", async () => {
|
|
100
|
+
const cfg = baseConfig()
|
|
101
|
+
cfg.versions.engine = VERSION_PIN_LOCAL
|
|
102
|
+
await expect(resolveBinary("engine", cfg)).rejects.toThrow(/overrides\.engine/)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it("uses overrides.engine when versions.engine is local", async () => {
|
|
106
|
+
const fakeBin = join(tmp, "supatype-engine-local")
|
|
107
|
+
writeFileSync(fakeBin, "#")
|
|
108
|
+
const cfg = baseConfig({ engine: fakeBin })
|
|
109
|
+
cfg.versions.engine = VERSION_PIN_LOCAL
|
|
110
|
+
const p = await resolveBinary("engine", cfg)
|
|
111
|
+
expect(p.replace(/\\/g, "/")).toContain("supatype-engine-local")
|
|
112
|
+
})
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
describe("describeActiveOverrides", () => {
|
|
116
|
+
it("lists non-empty override paths", () => {
|
|
117
|
+
const lines = describeActiveOverrides(
|
|
118
|
+
baseConfig({ engine: "/a/b", server: "/c/d" }),
|
|
119
|
+
)
|
|
120
|
+
expect(lines.some((l) => l.includes("engine"))).toBe(true)
|
|
121
|
+
expect(lines.some((l) => l.includes("/a/b"))).toBe(true)
|
|
122
|
+
})
|
|
123
|
+
})
|