@supatype/cli 0.1.7 → 0.1.9
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 +1 -1
- package/.turbo/turbo-test.log +128 -128
- package/.turbo/turbo-typecheck.log +1 -1
- package/LICENSE +202 -0
- package/deno.d.ts +1 -1
- package/dist/app/framework.js +1 -1
- package/dist/app/framework.js.map +1 -1
- package/dist/augmentation-generator.d.ts +1 -1
- package/dist/augmentation-generator.js +3 -3
- package/dist/augmentation-generator.js.map +1 -1
- package/dist/binary-cache.d.ts +30 -8
- package/dist/binary-cache.d.ts.map +1 -1
- package/dist/binary-cache.js +47 -35
- package/dist/binary-cache.js.map +1 -1
- package/dist/cache-pins.d.ts +1 -1
- package/dist/cache-pins.js +2 -2
- package/dist/cache-pins.js.map +1 -1
- package/dist/cli-artifacts.d.ts +32 -0
- package/dist/cli-artifacts.d.ts.map +1 -0
- package/dist/cli-artifacts.js +49 -0
- package/dist/cli-artifacts.js.map +1 -0
- package/dist/cli-install-method.d.ts +9 -1
- package/dist/cli-install-method.d.ts.map +1 -1
- package/dist/cli-install-method.js +22 -4
- package/dist/cli-install-method.js.map +1 -1
- package/dist/cli-package-version.d.ts +1 -1
- package/dist/cli-package-version.d.ts.map +1 -1
- package/dist/cli-package-version.js +6 -1
- package/dist/cli-package-version.js.map +1 -1
- package/dist/cli-standalone.d.ts +13 -4
- package/dist/cli-standalone.d.ts.map +1 -1
- package/dist/cli-standalone.js +86 -31
- package/dist/cli-standalone.js.map +1 -1
- package/dist/cli-version-embedded.d.ts +10 -0
- package/dist/cli-version-embedded.d.ts.map +1 -0
- package/dist/cli-version-embedded.js +13 -0
- package/dist/cli-version-embedded.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/add.js +1 -1
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/admin.js +6 -6
- package/dist/commands/admin.js.map +1 -1
- package/dist/commands/adopt.js +1 -1
- package/dist/commands/adopt.js.map +1 -1
- package/dist/commands/app.js +2 -2
- package/dist/commands/app.js.map +1 -1
- package/dist/commands/cache.d.ts +1 -1
- package/dist/commands/cache.js +3 -3
- package/dist/commands/cache.js.map +1 -1
- package/dist/commands/db.d.ts +3 -3
- package/dist/commands/db.js +9 -9
- package/dist/commands/db.js.map +1 -1
- package/dist/commands/deploy.d.ts +9 -9
- package/dist/commands/deploy.js +12 -12
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/dev.d.ts +3 -3
- package/dist/commands/dev.js +17 -17
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/doctor.d.ts +1 -1
- package/dist/commands/doctor.js +3 -3
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/engine.d.ts +1 -1
- package/dist/commands/engine.js +1 -1
- package/dist/commands/functions.js +5 -5
- package/dist/commands/functions.js.map +1 -1
- package/dist/commands/hooks.d.ts +2 -2
- package/dist/commands/hooks.js +4 -4
- package/dist/commands/hooks.js.map +1 -1
- package/dist/commands/init.js +28 -28
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/login.d.ts +1 -1
- package/dist/commands/login.js +1 -1
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/logs.d.ts +1 -1
- package/dist/commands/migrate.js +5 -5
- package/dist/commands/migrate.js.map +1 -1
- package/dist/commands/pg.d.ts +1 -1
- package/dist/commands/pg.js +1 -1
- package/dist/commands/plugins.js +1 -1
- package/dist/commands/plugins.js.map +1 -1
- package/dist/commands/pull.js +2 -2
- package/dist/commands/pull.js.map +1 -1
- package/dist/commands/push.js +3 -3
- package/dist/commands/push.js.map +1 -1
- package/dist/commands/self-host.d.ts +1 -1
- package/dist/commands/self-host.js +1 -1
- package/dist/commands/self-update.d.ts +2 -8
- package/dist/commands/self-update.d.ts.map +1 -1
- package/dist/commands/self-update.js +20 -12
- package/dist/commands/self-update.js.map +1 -1
- package/dist/commands/status.d.ts +1 -1
- package/dist/commands/update.d.ts +1 -1
- package/dist/commands/update.js +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -3
- package/dist/config.js.map +1 -1
- package/dist/db-preflight.d.ts +4 -4
- package/dist/db-preflight.js +14 -14
- package/dist/db-preflight.js.map +1 -1
- package/dist/dev-compose.d.ts +3 -3
- package/dist/dev-compose.js +27 -27
- package/dist/dev-compose.js.map +1 -1
- package/dist/dev-log-filter.d.ts +1 -1
- package/dist/dev-log-filter.d.ts.map +1 -1
- package/dist/dev-log-filter.js +1 -1
- package/dist/dev-log-filter.js.map +1 -1
- package/dist/dev-ports.d.ts +2 -2
- package/dist/dev-ports.d.ts.map +1 -1
- package/dist/dev-ports.js +2 -2
- package/dist/dev-ports.js.map +1 -1
- package/dist/dev-session.d.ts +5 -5
- package/dist/dev-session.d.ts.map +1 -1
- package/dist/dev-session.js +6 -6
- package/dist/dev-session.js.map +1 -1
- package/dist/dev-shutdown.d.ts +5 -5
- package/dist/dev-shutdown.d.ts.map +1 -1
- package/dist/dev-shutdown.js +7 -7
- package/dist/dev-shutdown.js.map +1 -1
- package/dist/dev-task-colors.d.ts +1 -1
- package/dist/dev-task-colors.d.ts.map +1 -1
- package/dist/dev-task-colors.js +1 -1
- package/dist/dev-task-colors.js.map +1 -1
- package/dist/docker-postgres.d.ts +5 -5
- package/dist/docker-postgres.d.ts.map +1 -1
- package/dist/docker-postgres.js +5 -5
- package/dist/docker-postgres.js.map +1 -1
- package/dist/docker-runtime.d.ts +2 -2
- package/dist/docker-runtime.d.ts.map +1 -1
- package/dist/docker-runtime.js +5 -5
- package/dist/docker-runtime.js.map +1 -1
- package/dist/engine-client.d.ts +1 -1
- package/dist/engine-client.js +3 -3
- package/dist/engine-client.js.map +1 -1
- package/dist/engine-push-output.js +1 -1
- package/dist/engine-push-output.js.map +1 -1
- package/dist/ensure-binary.d.ts.map +1 -1
- package/dist/ensure-binary.js +17 -0
- package/dist/ensure-binary.js.map +1 -1
- package/dist/ensure-component-binaries.js +1 -1
- package/dist/ensure-component-binaries.js.map +1 -1
- package/dist/env-file.d.ts +2 -2
- package/dist/env-file.d.ts.map +1 -1
- package/dist/env-file.js +1 -1
- package/dist/env-file.js.map +1 -1
- package/dist/field-masking-tier.d.ts +5 -5
- package/dist/field-masking-tier.d.ts.map +1 -1
- package/dist/field-masking-tier.js +6 -6
- package/dist/field-masking-tier.js.map +1 -1
- package/dist/functions-deno-types.d.ts +1 -1
- package/dist/functions-deno-types.d.ts.map +1 -1
- package/dist/functions-deno-types.js +3 -3
- package/dist/functions-deno-types.js.map +1 -1
- package/dist/functions-router-gen.js +1 -1
- package/dist/gitignore.d.ts +2 -2
- package/dist/gitignore.d.ts.map +1 -1
- package/dist/gitignore.js +3 -3
- package/dist/gitignore.js.map +1 -1
- package/dist/hook-upload.d.ts +1 -1
- package/dist/hook-upload.js +3 -3
- package/dist/hooks-generator.d.ts +1 -1
- package/dist/hooks-generator.js +9 -9
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/init-project-detect.d.ts +1 -1
- package/dist/init-project-detect.d.ts.map +1 -1
- package/dist/init-project-detect.js +1 -1
- package/dist/init-project-detect.js.map +1 -1
- package/dist/link.d.ts +1 -1
- package/dist/link.d.ts.map +1 -1
- package/dist/local-secrets.d.ts +5 -5
- package/dist/local-secrets.js +5 -5
- package/dist/main.d.ts +2 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +20 -0
- package/dist/main.js.map +1 -0
- package/dist/model-hooks.d.ts +3 -3
- package/dist/model-hooks.js +4 -4
- package/dist/pgbouncer-userlist.d.ts +1 -1
- package/dist/pgbouncer-userlist.d.ts.map +1 -1
- package/dist/pgbouncer-userlist.js +2 -2
- package/dist/pgbouncer-userlist.js.map +1 -1
- package/dist/postgres-ctl.d.ts +1 -1
- package/dist/postgres-ctl.js +4 -4
- package/dist/postgres-ctl.js.map +1 -1
- package/dist/process-manager.d.ts +2 -2
- package/dist/process-manager.d.ts.map +1 -1
- package/dist/process-manager.js +4 -4
- package/dist/process-manager.js.map +1 -1
- package/dist/project-config.d.ts +12 -12
- package/dist/project-config.d.ts.map +1 -1
- package/dist/project-config.js +9 -9
- package/dist/project-config.js.map +1 -1
- package/dist/pull-utils.d.ts +2 -2
- package/dist/pull-utils.d.ts.map +1 -1
- package/dist/pull-utils.js +4 -4
- package/dist/pull-utils.js.map +1 -1
- package/dist/realtime-launch.js +1 -1
- package/dist/realtime-launch.js.map +1 -1
- package/dist/release-pins.d.ts +1 -1
- package/dist/release-pins.js +2 -2
- package/dist/release-pins.js.map +1 -1
- package/dist/resolve-api-url.js +1 -1
- package/dist/resolve-api-url.js.map +1 -1
- package/dist/resolve-target.d.ts +1 -1
- package/dist/resolve-target.d.ts.map +1 -1
- package/dist/rest-cache-admin.js +2 -2
- package/dist/rest-cache-admin.js.map +1 -1
- package/dist/runtime-routes.d.ts +1 -1
- package/dist/runtime-routes.js +1 -1
- package/dist/schema-ast-v2.d.ts +3 -3
- package/dist/schema-ast-v2.d.ts.map +1 -1
- package/dist/schema-ast-v2.js +2 -2
- package/dist/schema-ast-v2.js.map +1 -1
- package/dist/schema-push-lock.js +3 -3
- package/dist/schema-push-lock.js.map +1 -1
- package/dist/scripts/postinstall.d.ts +1 -1
- package/dist/scripts/postinstall.js +1 -1
- package/dist/self-host-compose.d.ts +5 -5
- package/dist/self-host-compose.d.ts.map +1 -1
- package/dist/self-host-compose.js +24 -24
- package/dist/self-host-compose.js.map +1 -1
- package/dist/service-role-check.js +3 -3
- package/dist/service-role-check.js.map +1 -1
- package/dist/storage-provision.d.ts +1 -1
- package/dist/storage-provision.js +3 -3
- package/dist/storage-provision.js.map +1 -1
- package/dist/studio-dev-server.d.ts +1 -1
- package/dist/studio-dev-server.d.ts.map +1 -1
- package/dist/systemd.d.ts +1 -1
- package/dist/systemd.js +1 -1
- package/dist/target-client.js +1 -1
- package/dist/target-client.js.map +1 -1
- package/dist/tsx-runner.js +1 -1
- package/dist/tsx-runner.js.map +1 -1
- package/dist/type-extractor.js +27 -27
- package/dist/type-extractor.js.map +1 -1
- package/dist/type-resolver.js +1 -1
- package/dist/type-resolver.js.map +1 -1
- package/dist/ui/brand.d.ts +1 -1
- package/dist/ui/brand.js +1 -1
- package/dist/ui/components/LogoWordmark.d.ts +1 -1
- package/dist/ui/components/LogoWordmark.d.ts.map +1 -1
- package/dist/ui/components/LogoWordmark.js +1 -1
- package/dist/ui/components/LogoWordmark.js.map +1 -1
- package/dist/ui/components/SelectField.js +1 -1
- package/dist/ui/components/SelectField.js.map +1 -1
- package/dist/ui/confirm.d.ts +1 -1
- package/dist/ui/confirm.js +1 -1
- package/dist/ui/fatal.d.ts +1 -1
- package/dist/ui/fatal.d.ts.map +1 -1
- package/dist/ui/fatal.js +1 -1
- package/dist/ui/fatal.js.map +1 -1
- package/dist/ui/flows/prompt-fields.js +1 -1
- package/dist/ui/flows/prompt-fields.js.map +1 -1
- package/dist/ui/interactive.d.ts +1 -1
- package/dist/ui/interactive.d.ts.map +1 -1
- package/dist/ui/interactive.js +1 -1
- package/dist/ui/interactive.js.map +1 -1
- package/dist/ui/progress.d.ts +1 -1
- package/dist/ui/progress.js +1 -1
- package/dist/ui/runtime/command-chrome.js +1 -1
- package/dist/ui/runtime/command-chrome.js.map +1 -1
- package/dist/ui/runtime/run-clack-flow.js +1 -1
- package/dist/ui/runtime/run-clack-flow.js.map +1 -1
- package/dist/ui/runtime/stdin-after-ink.js +1 -1
- package/dist/ui/runtime/stdin-after-ink.js.map +1 -1
- package/dist/ui/theme.d.ts +1 -1
- package/dist/ui/theme.js +1 -1
- package/dist/valkey-sidecar.d.ts +1 -1
- package/dist/valkey-sidecar.d.ts.map +1 -1
- package/dist/valkey-sidecar.js +2 -2
- package/dist/valkey-sidecar.js.map +1 -1
- package/package.json +3 -2
- package/scripts/build-release-artifacts.sh +112 -0
- package/scripts/generate-homebrew-formula.mjs +47 -45
- package/scripts/mirror-deno-release.sh +1 -1
- package/src/TYPE-RESOLUTION.md +31 -31
- package/src/app/framework.ts +1 -1
- package/src/augmentation-generator.ts +3 -3
- package/src/binary-cache.ts +51 -36
- package/src/cache-pins.ts +2 -2
- package/src/cli-artifacts.ts +65 -0
- package/src/cli-install-method.ts +24 -4
- package/src/cli-package-version.ts +5 -1
- package/src/cli-standalone.ts +116 -34
- package/src/cli-version-embedded.ts +12 -0
- package/src/cli.ts +1 -1
- package/src/commands/add.ts +1 -1
- package/src/commands/admin.ts +6 -6
- package/src/commands/adopt.ts +1 -1
- package/src/commands/app.ts +2 -2
- package/src/commands/cache.ts +3 -3
- package/src/commands/db.ts +9 -9
- package/src/commands/deploy.ts +12 -12
- package/src/commands/dev.ts +19 -19
- package/src/commands/doctor.ts +3 -3
- package/src/commands/engine.ts +1 -1
- package/src/commands/functions.ts +5 -5
- package/src/commands/hooks.ts +6 -6
- package/src/commands/init.ts +28 -28
- package/src/commands/login.ts +2 -2
- package/src/commands/logs.ts +1 -1
- package/src/commands/migrate.ts +5 -5
- package/src/commands/pg.ts +1 -1
- package/src/commands/plugins.ts +1 -1
- package/src/commands/pull.ts +2 -2
- package/src/commands/push.ts +3 -3
- package/src/commands/self-host.ts +1 -1
- package/src/commands/self-update.ts +23 -73
- package/src/commands/status.ts +1 -1
- package/src/commands/update.ts +1 -1
- package/src/config.ts +3 -3
- package/src/db-preflight.ts +15 -15
- package/src/dev-compose.ts +27 -27
- package/src/dev-log-filter.ts +1 -1
- package/src/dev-ports.ts +3 -3
- package/src/dev-session.ts +6 -6
- package/src/dev-shutdown.ts +8 -8
- package/src/dev-task-colors.ts +1 -1
- package/src/docker-postgres.ts +6 -6
- package/src/docker-runtime.ts +5 -5
- package/src/engine-client.ts +3 -3
- package/src/engine-push-output.ts +1 -1
- package/src/ensure-binary.ts +20 -0
- package/src/ensure-component-binaries.ts +1 -1
- package/src/env-file.ts +2 -2
- package/src/field-masking-tier.ts +6 -6
- package/src/functions-deno-types.ts +3 -3
- package/src/functions-router-gen.ts +1 -1
- package/src/gitignore.ts +3 -3
- package/src/hook-upload.ts +3 -3
- package/src/hooks-generator.ts +9 -9
- package/src/index.ts +1 -1
- package/src/init-project-detect.ts +1 -1
- package/src/link.ts +1 -1
- package/src/local-secrets.ts +5 -5
- package/src/main.ts +20 -0
- package/src/model-hooks.ts +4 -4
- package/src/pgbouncer-userlist.ts +2 -2
- package/src/postgres-ctl.ts +4 -4
- package/src/process-manager.ts +4 -4
- package/src/project-config.ts +16 -16
- package/src/pull-utils.ts +4 -4
- package/src/realtime-launch.ts +1 -1
- package/src/release-pins.ts +2 -2
- package/src/resolve-api-url.ts +1 -1
- package/src/resolve-target.ts +1 -1
- package/src/rest-cache-admin.ts +2 -2
- package/src/runtime-routes.ts +2 -2
- package/src/schema-ast-v2.ts +4 -4
- package/src/schema-push-lock.ts +3 -3
- package/src/scripts/postinstall.ts +1 -1
- package/src/self-host-compose.ts +25 -25
- package/src/service-role-check.ts +3 -3
- package/src/storage-provision.ts +3 -3
- package/src/studio-dev-server.ts +1 -1
- package/src/systemd.ts +1 -1
- package/src/target-client.ts +1 -1
- package/src/tsx-runner.ts +1 -1
- package/src/type-extractor.ts +27 -27
- package/src/type-resolver.ts +1 -1
- package/src/ui/README.md +1 -1
- package/src/ui/brand.ts +1 -1
- package/src/ui/components/LogoWordmark.tsx +1 -1
- package/src/ui/components/SelectField.tsx +1 -1
- package/src/ui/confirm.ts +1 -1
- package/src/ui/fatal.ts +1 -1
- package/src/ui/flows/prompt-fields.tsx +1 -1
- package/src/ui/interactive.ts +1 -1
- package/src/ui/progress.ts +1 -1
- package/src/ui/runtime/command-chrome.ts +1 -1
- package/src/ui/runtime/run-clack-flow.tsx +1 -1
- package/src/ui/runtime/stdin-after-ink.ts +1 -1
- package/src/ui/theme.ts +1 -1
- package/src/valkey-sidecar.ts +2 -2
- package/tests/access-composition.test.ts +6 -6
- package/tests/admin-studio-membership.test.ts +1 -1
- package/tests/api-schemas.test.ts +1 -1
- package/tests/archive-name-alias.test.ts +1 -1
- package/tests/cli-help.test.ts +8 -2
- package/tests/db-preflight.test.ts +1 -1
- package/tests/doctor-render.test.ts +3 -3
- package/tests/doctor-service-role.test.ts +2 -2
- package/tests/engine-push-output.test.ts +1 -1
- package/tests/env-file.test.ts +3 -3
- package/tests/external-database-compose.test.ts +5 -5
- package/tests/external-database-config.test.ts +2 -2
- package/tests/field-masking-tier.test.ts +1 -1
- package/tests/homebrew-formula.test.ts +68 -26
- package/tests/hook-upload.test.ts +1 -1
- package/tests/hooks-generator.test.ts +1 -1
- package/tests/init.test.ts +2 -2
- package/tests/keys.test.ts +8 -2
- package/tests/local-secrets.test.ts +3 -3
- package/tests/model-hooks.test.ts +6 -6
- package/tests/runtime-contract.test.ts +2 -2
- package/tests/service-role-check.test.ts +3 -3
- package/tests/storage-provision.test.ts +1 -1
- package/tests/type-extractor.test.ts +2 -2
- package/tsconfig.tsbuildinfo +1 -1
- package/vitest.config.ts +7 -1
- package/scripts/build-release-bundle.mjs +0 -27
package/src/TYPE-RESOLUTION.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Type Resolution in the Schema Extractor
|
|
2
2
|
|
|
3
|
-
`type-extractor.ts` converts TypeScript type definitions into `ExtractedSchemaAst
|
|
3
|
+
`type-extractor.ts` converts TypeScript type definitions into `ExtractedSchemaAst`,
|
|
4
4
|
the JSON handed to the engine binary for SQL generation and client type output.
|
|
5
5
|
|
|
6
6
|
This document explains how type names are resolved, what patterns are supported,
|
|
@@ -12,24 +12,24 @@ and how the three-tier fallback chain works.
|
|
|
12
12
|
|
|
13
13
|
The extractor uses the TypeScript **parser only** (`ts.createSourceFile`), not the
|
|
14
14
|
full type checker. This is fast and requires no `tsconfig.json`, but it means the
|
|
15
|
-
extractor only sees raw source text
|
|
15
|
+
extractor only sees raw source text, it cannot evaluate what a type alias resolves to.
|
|
16
16
|
|
|
17
17
|
The consequence is that any indirection breaks resolution:
|
|
18
18
|
|
|
19
19
|
```typescript
|
|
20
|
-
// Works
|
|
20
|
+
// Works: extractor sees "Optional" literally
|
|
21
21
|
type Post = Model<{ email: Optional<Email> }>
|
|
22
22
|
|
|
23
|
-
// Previously broken
|
|
23
|
+
// Previously broken: extractor sees "Nullable", not "Optional"
|
|
24
24
|
type Nullable<T> = Optional<T>
|
|
25
25
|
type Post = Model<{ email: Nullable<Email> }>
|
|
26
26
|
|
|
27
|
-
// Previously broken
|
|
27
|
+
// Previously broken: import rename
|
|
28
28
|
import { Optional as Maybe } from "@supatype/types"
|
|
29
29
|
type Post = Model<{ email: Maybe<Email> }>
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
Failures were silent
|
|
32
|
+
Failures were silent: unknown types fell through to `{ kind: "text", pgType: "TEXT" }`
|
|
33
33
|
instead of throwing an error.
|
|
34
34
|
|
|
35
35
|
---
|
|
@@ -40,18 +40,18 @@ Every type name encountered in a field definition is resolved through three tier
|
|
|
40
40
|
in order. The first tier to succeed wins. If all three fail, an error is thrown.
|
|
41
41
|
|
|
42
42
|
```
|
|
43
|
-
Tier 1
|
|
44
|
-
Tier 2
|
|
45
|
-
Tier 3
|
|
43
|
+
Tier 1: syntactic switch instant inline primitives and modifiers by name
|
|
44
|
+
Tier 2: alias registry instant user-defined type aliases, import renames
|
|
45
|
+
Tier 3: TypeScript checker ~300ms† conditional types, mapped types
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
† Tier 3 is **lazy
|
|
48
|
+
† Tier 3 is **lazy**, the `ts.Program` and `TypeChecker` are only created the first
|
|
49
49
|
time a conditional or mapped type is encountered. Schemas that use only tiers 1 and 2
|
|
50
50
|
pay no cost.
|
|
51
51
|
|
|
52
52
|
---
|
|
53
53
|
|
|
54
|
-
## Tier 1
|
|
54
|
+
## Tier 1: Syntactic Switch
|
|
55
55
|
|
|
56
56
|
The existing behaviour. The extractor walks the type reference chain and matches
|
|
57
57
|
names exactly against a hardcoded switch:
|
|
@@ -71,12 +71,12 @@ This covers all types used inline with their canonical names.
|
|
|
71
71
|
|
|
72
72
|
---
|
|
73
73
|
|
|
74
|
-
## Tier 2
|
|
74
|
+
## Tier 2: Alias Registry
|
|
75
75
|
|
|
76
76
|
Built once at startup from all source files loaded by `loadSchemaSourceFiles`.
|
|
77
77
|
Covers two sub-cases:
|
|
78
78
|
|
|
79
|
-
### 2a
|
|
79
|
+
### 2a: Type alias declarations
|
|
80
80
|
|
|
81
81
|
Any `type X = ...` that is not a `Model<>` declaration is indexed by name:
|
|
82
82
|
|
|
@@ -103,7 +103,7 @@ type B = Optional<Email>
|
|
|
103
103
|
Cycle detection via a `resolving: Set<string>` guard prevents infinite loops and
|
|
104
104
|
throws a descriptive error instead.
|
|
105
105
|
|
|
106
|
-
### 2b
|
|
106
|
+
### 2b: Import renames
|
|
107
107
|
|
|
108
108
|
Explicit `as` renames in import statements are indexed per file:
|
|
109
109
|
|
|
@@ -123,17 +123,17 @@ the current file. `Maybe` becomes `Optional`, `MaybeNull` becomes `Nullable`
|
|
|
123
123
|
into the source file set and their aliases are available in the registry.
|
|
124
124
|
|
|
125
125
|
Only relative specifiers (`.`-prefixed) are followed. Bare specifiers and scoped
|
|
126
|
-
packages (`@supatype/types`, `node_modules/*`) are not loaded
|
|
126
|
+
packages (`@supatype/types`, `node_modules/*`) are not loaded, their exported
|
|
127
127
|
names are already covered by the tier 1 switch.
|
|
128
128
|
|
|
129
129
|
---
|
|
130
130
|
|
|
131
|
-
## Tier 3
|
|
131
|
+
## Tier 3: TypeScript Checker
|
|
132
132
|
|
|
133
133
|
Required for types that cannot be evaluated syntactically:
|
|
134
134
|
|
|
135
|
-
- **Conditional types**: `T extends U ? A : B
|
|
136
|
-
- **Mapped types**: `{ [K in keyof T]: F<T[K]> }
|
|
135
|
+
- **Conditional types**: `T extends U ? A : B`, requires evaluating the constraint
|
|
136
|
+
- **Mapped types**: `{ [K in keyof T]: F<T[K]> }`, requires enumerating `keyof T`
|
|
137
137
|
|
|
138
138
|
### How it works
|
|
139
139
|
|
|
@@ -147,7 +147,7 @@ Required for types that cannot be evaluated syntactically:
|
|
|
147
147
|
3. `checker.getTypeAtLocation(programNode)` resolves the type fully.
|
|
148
148
|
|
|
149
149
|
4. `checker.typeToString(type, ..., TypeFormatFlags.UseAliasDefinedOutsideCurrentScope)`
|
|
150
|
-
converts it back to a string with **alias names preserved
|
|
150
|
+
converts it back to a string with **alias names preserved**, so `Optional<Email>`
|
|
151
151
|
stays as `Optional<Email>` rather than expanding to the underlying branded
|
|
152
152
|
intersection type.
|
|
153
153
|
|
|
@@ -160,7 +160,7 @@ Required for types that cannot be evaluated syntactically:
|
|
|
160
160
|
|
|
161
161
|
- A field type is directly a conditional or mapped expression
|
|
162
162
|
- A tier 2 alias body contains a conditional or mapped type (detected by
|
|
163
|
-
`needsChecker()` before text substitution is attempted
|
|
163
|
+
`needsChecker()` before text substitution is attempted, the original node,
|
|
164
164
|
which has valid source positions, is passed to the checker instead)
|
|
165
165
|
- The `Model<>` fields argument is a mapped type alias:
|
|
166
166
|
```typescript
|
|
@@ -175,40 +175,40 @@ Required for types that cannot be evaluated syntactically:
|
|
|
175
175
|
## What Is Supported
|
|
176
176
|
|
|
177
177
|
```typescript
|
|
178
|
-
// Tier 1
|
|
178
|
+
// Tier 1: inline, canonical names
|
|
179
179
|
email: Optional<Email>
|
|
180
180
|
slug: Unique<Slug<"title">>
|
|
181
181
|
id: PrimaryKey<UUID>
|
|
182
182
|
|
|
183
|
-
// Tier 2a
|
|
183
|
+
// Tier 2a: simple alias
|
|
184
184
|
type Nullable<T> = Optional<T>
|
|
185
185
|
type UniqueSlug = Unique<Slug<"title">>
|
|
186
186
|
email: Nullable<Email>
|
|
187
187
|
slug: UniqueSlug
|
|
188
188
|
|
|
189
|
-
// Tier 2a
|
|
189
|
+
// Tier 2a: multi-hop
|
|
190
190
|
type A = B
|
|
191
191
|
type B = Nullable<Email>
|
|
192
192
|
email: A
|
|
193
193
|
|
|
194
|
-
// Tier 2b
|
|
194
|
+
// Tier 2b: import rename of primitive
|
|
195
195
|
import { Optional as Maybe } from "@supatype/types"
|
|
196
196
|
email: Maybe<Email>
|
|
197
197
|
|
|
198
|
-
// Tier 2b
|
|
198
|
+
// Tier 2b: import rename of local alias
|
|
199
199
|
import { Nullable as MaybeNull } from "./field-types"
|
|
200
200
|
email: MaybeNull<Email>
|
|
201
201
|
|
|
202
|
-
// Tier 2b
|
|
202
|
+
// Tier 2b: cross-file alias (no rename)
|
|
203
203
|
// helpers.ts: export type Nullable<T> = Optional<T>
|
|
204
204
|
import { Nullable } from "./helpers"
|
|
205
205
|
email: Nullable<Email>
|
|
206
206
|
|
|
207
|
-
// Tier 3
|
|
207
|
+
// Tier 3: conditional type
|
|
208
208
|
type NullableStr<T> = T extends string ? Optional<T> : T
|
|
209
209
|
email: NullableStr<Email>
|
|
210
210
|
|
|
211
|
-
// Tier 3
|
|
211
|
+
// Tier 3: mapped type as fields object
|
|
212
212
|
type AllOptional<T> = { [K in keyof T]: Optional<T[K]> }
|
|
213
213
|
type Post = Model<AllOptional<{ email: Email; name: Text }>>
|
|
214
214
|
```
|
|
@@ -225,10 +225,10 @@ import { SomeHelper } from "some-library"
|
|
|
225
225
|
// symbols only available in node_modules (other than @supatype/types)
|
|
226
226
|
|
|
227
227
|
// TypeScript utility types used as field types
|
|
228
|
-
email: NonNullable<string> // error
|
|
228
|
+
email: NonNullable<string> // error, not a @supatype/types primitive
|
|
229
229
|
|
|
230
230
|
// Namespace-qualified names
|
|
231
|
-
email: Types.Optional<Email> // error
|
|
231
|
+
email: Types.Optional<Email> // error, only identifier references are resolved
|
|
232
232
|
```
|
|
233
233
|
|
|
234
234
|
---
|
|
@@ -290,5 +290,5 @@ type CheckerContext = {
|
|
|
290
290
|
## Adding a New Tier 1 Primitive
|
|
291
291
|
|
|
292
292
|
When a new type is added to `@supatype/types`, add a `case` to the `switch` in
|
|
293
|
-
`parseScalarType`. No other changes are needed
|
|
293
|
+
`parseScalarType`. No other changes are needed, tier 2 and tier 3 handle
|
|
294
294
|
aliases and compositions of it automatically.
|
package/src/app/framework.ts
CHANGED
|
@@ -41,7 +41,7 @@ const FRAMEWORK_DETECTION: Array<{ dep: string; framework: AppFramework }> = [
|
|
|
41
41
|
{ dep: "@remix-run/react", framework: "remix-spa" },
|
|
42
42
|
{ dep: "@sveltejs/kit", framework: "sveltekit" },
|
|
43
43
|
{ dep: "nuxt", framework: "nuxt" },
|
|
44
|
-
// Vite last
|
|
44
|
+
// Vite last: many frameworks use Vite under the hood
|
|
45
45
|
{ dep: "vite", framework: "vite" },
|
|
46
46
|
]
|
|
47
47
|
|
|
@@ -15,7 +15,7 @@ interface AstShape {
|
|
|
15
15
|
* Shared with the hooks generator on purpose.
|
|
16
16
|
*
|
|
17
17
|
* A hook's `row` must be the same shape the client sees for that table, and two copies of this logic
|
|
18
|
-
* would drift
|
|
18
|
+
* would drift: the client would gain a column type the hook handler did not, which is the failure
|
|
19
19
|
* mode this codebase has already paid for once.
|
|
20
20
|
*/
|
|
21
21
|
export function resolveTableName(model: AstModel): string {
|
|
@@ -43,7 +43,7 @@ export function generateClientAugmentation(ast: unknown): string {
|
|
|
43
43
|
.sort((a, b) => a.tableName.localeCompare(b.tableName))
|
|
44
44
|
|
|
45
45
|
const lines: string[] = []
|
|
46
|
-
lines.push("// Generated by supatype CLI
|
|
46
|
+
lines.push("// Generated by supatype CLI, do not edit manually.")
|
|
47
47
|
lines.push("")
|
|
48
48
|
lines.push("declare module \"@supatype/client\" {")
|
|
49
49
|
lines.push(" interface SupatypeModels {")
|
|
@@ -104,7 +104,7 @@ function toTsType(meta: Record<string, unknown>): string {
|
|
|
104
104
|
const required = meta["required"] === true
|
|
105
105
|
const base = (() => {
|
|
106
106
|
// A field that knows its own shape says so. Without this, everything stored as JSONB collapses
|
|
107
|
-
// to `Record<string, unknown>` in the generated row
|
|
107
|
+
// to `Record<string, unknown>` in the generated row, so `Currency` would arrive as an opaque
|
|
108
108
|
// object rather than `{ amount: string; code: "USD" }`, and the schema's precision would be
|
|
109
109
|
// lost exactly where a caller reads it.
|
|
110
110
|
const declared = meta["tsType"]
|
package/src/binary-cache.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Binary cache
|
|
2
|
+
* Binary cache: manages supatype component binaries.
|
|
3
3
|
*
|
|
4
4
|
* Components: engine, server, postgres, deno, realtime.
|
|
5
5
|
* Cache root: ~/.supatype/cache/{component}/{version}/
|
|
@@ -71,7 +71,7 @@ export function hasMeaningfulOverrides(config: SupatypeProjectConfig): boolean {
|
|
|
71
71
|
return false
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
/** Lines for a startup banner
|
|
74
|
+
/** Lines for a startup banner, non-empty override paths only. */
|
|
75
75
|
export function describeActiveOverrides(config: SupatypeProjectConfig): string[] {
|
|
76
76
|
const o = config.overrides
|
|
77
77
|
if (!o) return []
|
|
@@ -200,7 +200,7 @@ function binaryName(component: Component, version: string, platform: PlatformId)
|
|
|
200
200
|
* Names a published archive may carry, most-canonical first.
|
|
201
201
|
*
|
|
202
202
|
* `supatype-postgres` published the Intel macOS archive as `darwin-x86_64` while every other platform
|
|
203
|
-
* used `amd64
|
|
203
|
+
* used `amd64`, the spelling the CLI derives from `process.arch === "x64"`. Releases already on the CDN
|
|
204
204
|
* carry the old name, so both are accepted and the *signed* checksums manifest decides which exists.
|
|
205
205
|
* Nothing is guessed: the URL and the hash always come from the same manifest entry.
|
|
206
206
|
*/
|
|
@@ -242,9 +242,9 @@ export function currentPlatform(): PlatformId {
|
|
|
242
242
|
* Resolve the binary path for a component.
|
|
243
243
|
*
|
|
244
244
|
* Resolution order:
|
|
245
|
-
* 1. config.overrides?.[component]
|
|
245
|
+
* 1. config.overrides?.[component]: local build path (must exist)
|
|
246
246
|
* 2. Cached binary at ~/.supatype/cache/{component}/{version}/
|
|
247
|
-
* 3. Throws
|
|
247
|
+
* 3. Throws, so the caller should call download() first.
|
|
248
248
|
*
|
|
249
249
|
* Hard error if any meaningful `overrides` entry is set while the project is linked to cloud
|
|
250
250
|
* (`project.ref`, `.supatype/cloud.json`, or `.supatype/linked.json`).
|
|
@@ -352,7 +352,7 @@ async function acquireDownloadSlot(
|
|
|
352
352
|
|
|
353
353
|
if (isDownloadInProgress(component, version)) {
|
|
354
354
|
console.log(
|
|
355
|
-
`[supatype] ${component} v${version} is downloading in another process
|
|
355
|
+
`[supatype] ${component} v${version} is downloading in another process, waiting...`,
|
|
356
356
|
)
|
|
357
357
|
const outcome = await waitForComponentDownload(component, version, isReady, (c) => {
|
|
358
358
|
console.log(`[supatype] Still waiting for ${c} download...`)
|
|
@@ -364,7 +364,7 @@ async function acquireDownloadSlot(
|
|
|
364
364
|
)
|
|
365
365
|
}
|
|
366
366
|
console.warn(
|
|
367
|
-
`[supatype] ${component} v${version} download did not finish in the other process
|
|
367
|
+
`[supatype] ${component} v${version} download did not finish in the other process, retrying.`,
|
|
368
368
|
)
|
|
369
369
|
continue
|
|
370
370
|
}
|
|
@@ -384,7 +384,7 @@ export async function download(
|
|
|
384
384
|
): Promise<string> {
|
|
385
385
|
if (version === VERSION_PIN_LOCAL) {
|
|
386
386
|
throw new Error(
|
|
387
|
-
`cannot download CDN binary when version is "${VERSION_PIN_LOCAL}"
|
|
387
|
+
`cannot download CDN binary when version is "${VERSION_PIN_LOCAL}": set overrides.${component === "postgres" ? "postgres_dir" : component} or pin a semver`,
|
|
388
388
|
)
|
|
389
389
|
}
|
|
390
390
|
|
|
@@ -400,7 +400,7 @@ export async function download(
|
|
|
400
400
|
return destPath
|
|
401
401
|
}
|
|
402
402
|
console.warn(
|
|
403
|
-
`[supatype] ${component} v${version} cache invalid
|
|
403
|
+
`[supatype] ${component} v${version} cache invalid, re-downloading (${destPath}).`,
|
|
404
404
|
)
|
|
405
405
|
unlinkSync(destPath)
|
|
406
406
|
}
|
|
@@ -461,7 +461,15 @@ export async function download(
|
|
|
461
461
|
* Fetch checksums.sha256, optionally verify its minisign signature, and
|
|
462
462
|
* return the expected SHA256 for `binaryFilename`.
|
|
463
463
|
*/
|
|
464
|
-
|
|
464
|
+
/**
|
|
465
|
+
* Fetch a checksums manifest and verify its minisign signature, returning the entry for one of
|
|
466
|
+
* `binaryFilenames`.
|
|
467
|
+
*
|
|
468
|
+
* Exported so the standalone CLI download path enforces the same rule as component downloads.
|
|
469
|
+
* It previously verified nothing at all, which made the CLI stricter about a postgres archive
|
|
470
|
+
* than about overwriting its own executable.
|
|
471
|
+
*/
|
|
472
|
+
export async function fetchChecksums(
|
|
465
473
|
checksumsUrl: string,
|
|
466
474
|
minisigUrl: string,
|
|
467
475
|
binaryFilenames: string | string[],
|
|
@@ -476,16 +484,16 @@ async function fetchChecksums(
|
|
|
476
484
|
if (!pubKey) {
|
|
477
485
|
// Fail closed: a missing public key means we cannot verify authenticity, only
|
|
478
486
|
// integrity (SHA256). Published builds always embed the key, so this only
|
|
479
|
-
// happens in source/contributor builds
|
|
487
|
+
// happens in source/contributor builds. Never silently downgrade.
|
|
480
488
|
if (process.env["SUPATYPE_ALLOW_UNVERIFIED_DOWNLOADS"] === "1") {
|
|
481
489
|
console.warn(
|
|
482
|
-
"[supatype] \u26a0 SUPATYPE_ALLOW_UNVERIFIED_DOWNLOADS=1
|
|
490
|
+
"[supatype] \u26a0 SUPATYPE_ALLOW_UNVERIFIED_DOWNLOADS=1: no minisign public " +
|
|
483
491
|
"key configured; verifying SHA256 only (authenticity NOT checked).",
|
|
484
492
|
)
|
|
485
493
|
return extractChecksum(checksumsText, binaryFilenames)
|
|
486
494
|
}
|
|
487
495
|
throw new Error(
|
|
488
|
-
"No minisign public key configured
|
|
496
|
+
"No minisign public key configured, so cannot verify release authenticity.\n" +
|
|
489
497
|
"Published @supatype/cli builds embed the key automatically; if you are building " +
|
|
490
498
|
"from source, set SUPATYPE_RELEASE_PUBLIC_KEY to the release public key, or set " +
|
|
491
499
|
"SUPATYPE_ALLOW_UNVERIFIED_DOWNLOADS=1 to download with SHA256-only verification (unsafe).",
|
|
@@ -511,7 +519,7 @@ async function fetchChecksums(
|
|
|
511
519
|
// ---------------------------------------------------------------------------
|
|
512
520
|
|
|
513
521
|
/**
|
|
514
|
-
* Ed25519 SPKI DER prefix
|
|
522
|
+
* Ed25519 SPKI DER prefix, wraps a raw 32-byte public key into the
|
|
515
523
|
* SubjectPublicKeyInfo structure that Node.js crypto.createPublicKey expects.
|
|
516
524
|
*
|
|
517
525
|
* Breakdown:
|
|
@@ -539,7 +547,7 @@ export function verifyMinisign(fileBytes: Buffer, sigFileContent: string, pubKey
|
|
|
539
547
|
|
|
540
548
|
// Parse signature file:
|
|
541
549
|
// line 0: untrusted comment
|
|
542
|
-
// line 1: base64 sig bytes
|
|
550
|
+
// line 1: base64 sig bytes: [2 algo][8 keyId][64 Ed25519 sig]
|
|
543
551
|
// line 2: trusted comment
|
|
544
552
|
// line 3: base64 global sig (over sig bytes + trusted comment)
|
|
545
553
|
const sigLines = sigFileContent.trim().split("\n")
|
|
@@ -552,12 +560,12 @@ export function verifyMinisign(fileBytes: Buffer, sigFileContent: string, pubKey
|
|
|
552
560
|
const signature = sigBytes.subarray(10, 74)
|
|
553
561
|
|
|
554
562
|
// Both Ed25519 modes are supported:
|
|
555
|
-
// "Ed" (0x45, 0x64)
|
|
556
|
-
// "ED" (0x45, 0x44)
|
|
563
|
+
// "Ed" (0x45, 0x64) legacy: signature is over the raw file bytes.
|
|
564
|
+
// "ED" (0x45, 0x44) prehashed: signature is over BLAKE2b-512(file).
|
|
557
565
|
// Modern minisign (and our release pipeline) default to prehashed mode.
|
|
558
566
|
if (algo[0] !== 0x45 || (algo[1] !== 0x64 && algo[1] !== 0x44)) {
|
|
559
567
|
throw new Error(
|
|
560
|
-
"Unsupported minisign algorithm
|
|
568
|
+
"Unsupported minisign algorithm, expected Ed25519 ('Ed' legacy or 'ED' prehashed).\n" +
|
|
561
569
|
`Got: 0x${algo[0]?.toString(16)}${algo[1]?.toString(16)}`,
|
|
562
570
|
)
|
|
563
571
|
}
|
|
@@ -565,7 +573,7 @@ export function verifyMinisign(fileBytes: Buffer, sigFileContent: string, pubKey
|
|
|
565
573
|
|
|
566
574
|
if (!sigKeyId.equals(pkKeyId)) {
|
|
567
575
|
throw new Error(
|
|
568
|
-
"Minisign key ID mismatch
|
|
576
|
+
"Minisign key ID mismatch: the signature was produced with a different key.\n" +
|
|
569
577
|
"This could indicate a compromised release. Do not proceed.",
|
|
570
578
|
)
|
|
571
579
|
}
|
|
@@ -582,7 +590,7 @@ export function verifyMinisign(fileBytes: Buffer, sigFileContent: string, pubKey
|
|
|
582
590
|
const valid = cryptoVerify(null, signedData, keyObject, signature)
|
|
583
591
|
if (!valid) {
|
|
584
592
|
throw new Error(
|
|
585
|
-
"Minisign signature verification FAILED
|
|
593
|
+
"Minisign signature verification FAILED: the checksum file may have been tampered with.\n" +
|
|
586
594
|
"This could indicate a supply chain attack. Aborting download.",
|
|
587
595
|
)
|
|
588
596
|
}
|
|
@@ -590,14 +598,21 @@ export function verifyMinisign(fileBytes: Buffer, sigFileContent: string, pubKey
|
|
|
590
598
|
|
|
591
599
|
/**
|
|
592
600
|
* Extract the SHA256 hash for `filename` from a checksums.sha256 file.
|
|
593
|
-
* Format: `<hash> <filename>` (sha256sum output, two spaces)
|
|
601
|
+
* Format: `<hash> <filename>` (sha256sum output, two spaces), or `<hash> *<filename>` when
|
|
602
|
+
* the publisher used binary mode.
|
|
603
|
+
*
|
|
604
|
+
* Exported so the standalone CLI download path parses the manifest the same way rather than
|
|
605
|
+
* carrying a second implementation.
|
|
594
606
|
*/
|
|
595
|
-
function extractChecksum(
|
|
607
|
+
export function extractChecksum(
|
|
608
|
+
checksumsText: string,
|
|
609
|
+
filenames: string | string[],
|
|
610
|
+
): ChecksumMatch {
|
|
596
611
|
const targets = (Array.isArray(filenames) ? filenames : [filenames]).map((f) => basename(f))
|
|
597
612
|
for (const target of targets) {
|
|
598
613
|
for (const line of checksumsText.split("\n")) {
|
|
599
614
|
const parts = line.trim().split(/\s+/)
|
|
600
|
-
if (parts.length >= 2 && parts[1] === target) {
|
|
615
|
+
if (parts.length >= 2 && parts[1]?.replace(/^\*/, "") === target) {
|
|
601
616
|
return { filename: target, checksum: parts[0]! }
|
|
602
617
|
}
|
|
603
618
|
}
|
|
@@ -608,7 +623,7 @@ function extractChecksum(checksumsText: string, filenames: string | string[]): C
|
|
|
608
623
|
)
|
|
609
624
|
}
|
|
610
625
|
|
|
611
|
-
/** Which published name matched, and its hash
|
|
626
|
+
/** Which published name matched, and its hash, both from the same signed manifest entry. */
|
|
612
627
|
export interface ChecksumMatch {
|
|
613
628
|
filename: string
|
|
614
629
|
checksum: string
|
|
@@ -690,9 +705,9 @@ export function validateArtifactFormat(
|
|
|
690
705
|
|
|
691
706
|
/**
|
|
692
707
|
* Per-component CDN artifact shapes:
|
|
693
|
-
* engine, server, deno, realtime
|
|
694
|
-
* postgres (unix)
|
|
695
|
-
* postgres (windows)
|
|
708
|
+
* engine, server, deno, realtime: native executable (ELF / Mach-O / PE)
|
|
709
|
+
* postgres (unix) .tar.gz (gzip)
|
|
710
|
+
* postgres (windows) .zip
|
|
696
711
|
*/
|
|
697
712
|
function assertArtifactFormat(
|
|
698
713
|
component: Component,
|
|
@@ -719,7 +734,7 @@ function assertGzipArchive(filePath: string): void {
|
|
|
719
734
|
if (magic[0] !== 0x1f || magic[1] !== 0x8b) {
|
|
720
735
|
throw new Error(
|
|
721
736
|
"Downloaded postgres file is not a gzip archive (bad magic bytes). " +
|
|
722
|
-
"The CDN object may be corrupt or cached HTML
|
|
737
|
+
"The CDN object may be corrupt or cached HTML, so delete ~/.supatype/cache and retry.",
|
|
723
738
|
)
|
|
724
739
|
}
|
|
725
740
|
} finally {
|
|
@@ -736,7 +751,7 @@ function assertZipArchive(filePath: string): void {
|
|
|
736
751
|
if (magic[0] !== 0x50 || magic[1] !== 0x4b) {
|
|
737
752
|
throw new Error(
|
|
738
753
|
"Downloaded postgres file is not a zip archive (bad magic bytes). " +
|
|
739
|
-
"The CDN object may be corrupt or cached HTML
|
|
754
|
+
"The CDN object may be corrupt or cached HTML, so delete ~/.supatype/cache and retry.",
|
|
740
755
|
)
|
|
741
756
|
}
|
|
742
757
|
} finally {
|
|
@@ -759,14 +774,14 @@ function assertNativeExecutable(
|
|
|
759
774
|
if (goCArchive) {
|
|
760
775
|
throw new Error(
|
|
761
776
|
`Downloaded ${component} file is a Go static archive (c-archive), not an executable. ` +
|
|
762
|
-
"The CDN object may be from a bad release build
|
|
777
|
+
"The CDN object may be from a bad release build, so delete ~/.supatype/cache and retry.",
|
|
763
778
|
)
|
|
764
779
|
}
|
|
765
780
|
if (platform.os === "windows") {
|
|
766
781
|
if (magic[0] !== 0x4d || magic[1] !== 0x5a) {
|
|
767
782
|
throw new Error(
|
|
768
783
|
`Downloaded ${component} file is not a Windows PE executable (bad magic bytes). ` +
|
|
769
|
-
"The CDN object may be corrupt or cached HTML
|
|
784
|
+
"The CDN object may be corrupt or cached HTML, so delete ~/.supatype/cache and retry.",
|
|
770
785
|
)
|
|
771
786
|
}
|
|
772
787
|
return
|
|
@@ -777,7 +792,7 @@ function assertNativeExecutable(
|
|
|
777
792
|
if (!elf) {
|
|
778
793
|
throw new Error(
|
|
779
794
|
`Downloaded ${component} file is not an ELF executable (bad magic bytes). ` +
|
|
780
|
-
"The CDN object may be corrupt or cached HTML
|
|
795
|
+
"The CDN object may be corrupt or cached HTML, so delete ~/.supatype/cache and retry.",
|
|
781
796
|
)
|
|
782
797
|
}
|
|
783
798
|
return
|
|
@@ -791,7 +806,7 @@ function assertNativeExecutable(
|
|
|
791
806
|
if (!macho) {
|
|
792
807
|
throw new Error(
|
|
793
808
|
`Downloaded ${component} file is not a Mach-O executable (bad magic bytes). ` +
|
|
794
|
-
"The CDN object may be corrupt or cached HTML
|
|
809
|
+
"The CDN object may be corrupt or cached HTML, so delete ~/.supatype/cache and retry.",
|
|
795
810
|
)
|
|
796
811
|
}
|
|
797
812
|
} finally {
|
|
@@ -837,7 +852,7 @@ async function withRetry<T>(fn: () => Promise<T>, attempts = 3): Promise<T> {
|
|
|
837
852
|
// ---------------------------------------------------------------------------
|
|
838
853
|
|
|
839
854
|
/**
|
|
840
|
-
* On Windows, Git Bash represents paths as /c/Users
|
|
855
|
+
* On Windows, Git Bash represents paths as /c/Users/..., convert to C:\Users\...
|
|
841
856
|
*/
|
|
842
857
|
export function normalisePlatformPath(p: string): string {
|
|
843
858
|
let result = p
|
|
@@ -931,7 +946,7 @@ export async function fetchAllLatestVersions(): Promise<Partial<Record<Component
|
|
|
931
946
|
*/
|
|
932
947
|
/**
|
|
933
948
|
* Verify cached binaries for the current platform (used by integration CI).
|
|
934
|
-
* Only checks components present in `versions
|
|
949
|
+
* Only checks components present in `versions`; unpublished CDN components
|
|
935
950
|
* (no latest.json yet) are skipped with a log so CI can land before first release.
|
|
936
951
|
* Throws if a pinned component is missing from cache or fails format checks.
|
|
937
952
|
*/
|
|
@@ -944,7 +959,7 @@ export function verifyCachedBinaries(versions: Partial<ComponentVersions> | unde
|
|
|
944
959
|
const version = versions[component]
|
|
945
960
|
if (typeof version !== "string" || version.trim() === "") {
|
|
946
961
|
console.log(
|
|
947
|
-
`[supatype] skipping verify for ${component} (no version
|
|
962
|
+
`[supatype] skipping verify for ${component} (no version, CDN latest.json not published yet)`,
|
|
948
963
|
)
|
|
949
964
|
continue
|
|
950
965
|
}
|
package/src/cache-pins.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Pin-aware cache eviction
|
|
2
|
+
* Pin-aware cache eviction: collect `versions.*` pins across local projects (C13).
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { existsSync, readdirSync, rmSync, statSync } from "node:fs"
|
|
@@ -219,7 +219,7 @@ export function cleanCachedBinaries(options: CleanCachedBinariesOptions): CleanC
|
|
|
219
219
|
if (!force && explicitVersion && pinned) {
|
|
220
220
|
const reason = pinReason(component, version, sources) ?? "pinned"
|
|
221
221
|
result.warned.push(
|
|
222
|
-
`${component}@${version} is ${reason}
|
|
222
|
+
`${component}@${version} is ${reason}: removing anyway (explicit version)`,
|
|
223
223
|
)
|
|
224
224
|
}
|
|
225
225
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Names and locations of the published standalone CLI artefacts.
|
|
3
|
+
*
|
|
4
|
+
* One place, because this layout had drifted into five: the release workflow, install.sh,
|
|
5
|
+
* this module's caller, the Homebrew generator and a now-deleted second installer all
|
|
6
|
+
* described it independently, and `supatype self-update` was asking for bare executables the
|
|
7
|
+
* publisher had stopped writing. `scripts/install.sh` cannot import this (it runs before any
|
|
8
|
+
* CLI exists on the machine), so the shell keeps its own copy; what holds the two together is
|
|
9
|
+
* the round-trip test in tests/integration/scripts/cli-artifact-roundtrip.sh, not this file.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { existsSync, readdirSync } from "node:fs"
|
|
13
|
+
import { currentPlatform, type PlatformId } from "./binary-cache.js"
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Which C library the running system provides. Only meaningful on Linux: Bun links
|
|
17
|
+
* libstdc++ and libgcc dynamically, so a glibc build cannot start on Alpine, and installing a
|
|
18
|
+
* glibc binary over a working musl one is how a self-update would brick an Alpine install.
|
|
19
|
+
*/
|
|
20
|
+
export type LibcId = "glibc" | "musl"
|
|
21
|
+
|
|
22
|
+
/** Detects musl by its dynamic loader, the same signal scripts/install.sh uses. */
|
|
23
|
+
export function currentLibc(platform: PlatformId = currentPlatform()): LibcId {
|
|
24
|
+
if (platform.os !== "linux") return "glibc"
|
|
25
|
+
if (!existsSync("/lib")) return "glibc"
|
|
26
|
+
try {
|
|
27
|
+
const musl = readdirSync("/lib").some((f) => f.startsWith("ld-musl-") && f.endsWith(".so.1"))
|
|
28
|
+
return musl ? "musl" : "glibc"
|
|
29
|
+
} catch {
|
|
30
|
+
return "glibc"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The archive published for a platform. Windows gets a zip because that is what Windows
|
|
36
|
+
* users can open without extra tools; everything else is a tarball. Both hold a single
|
|
37
|
+
* executable called `supatype` (`supatype.exe` on Windows), whatever the archive is named.
|
|
38
|
+
*/
|
|
39
|
+
export function cliArchiveName(
|
|
40
|
+
platform: PlatformId = currentPlatform(),
|
|
41
|
+
libc: LibcId = currentLibc(platform),
|
|
42
|
+
): string {
|
|
43
|
+
const suffix = platform.os === "linux" && libc === "musl" ? "-musl" : ""
|
|
44
|
+
const ext = platform.os === "windows" ? "zip" : "tar.gz"
|
|
45
|
+
return `supatype-cli-${platform.os}-${platform.arch}${suffix}.${ext}`
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The executable inside the archive. */
|
|
49
|
+
export function cliExecutableName(platform: PlatformId = currentPlatform()): string {
|
|
50
|
+
return platform.os === "windows" ? "supatype.exe" : "supatype"
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** CDN path of a version's archive, relative to the CDN root. */
|
|
54
|
+
export function cliArchivePath(
|
|
55
|
+
version: string,
|
|
56
|
+
platform: PlatformId = currentPlatform(),
|
|
57
|
+
libc: LibcId = currentLibc(platform),
|
|
58
|
+
): string {
|
|
59
|
+
return `/cli/v${version}/${cliArchiveName(platform, libc)}`
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** CDN path of the checksums file covering every archive in a version. */
|
|
63
|
+
export function cliChecksumsPath(version: string): string {
|
|
64
|
+
return `/cli/v${version}/checksums.sha256`
|
|
65
|
+
}
|
|
@@ -8,9 +8,29 @@ function normalizePath(path: string): string {
|
|
|
8
8
|
return path.replace(/\\/g, "/")
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Inside a Bun-compiled binary, argv[1] is a path in the embedded filesystem, not on disk:
|
|
13
|
+
* `/$bunfs/root/supatype` on Linux and macOS, `B:/~BUN/root/supatype.exe` on Windows. No
|
|
14
|
+
* filesystem call can touch either, which is why self-update failed with
|
|
15
|
+
* `ENOENT: copyfile '/$bunfs/root/supatype'` and could never have replaced itself.
|
|
16
|
+
*/
|
|
17
|
+
const EMBEDDED_FS_PATH = /(^|\/)(\$bunfs|~BUN)\//i
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The executable on disk.
|
|
21
|
+
*
|
|
22
|
+
* process.execPath is the compiled binary when running one, and the Node binary otherwise, so
|
|
23
|
+
* it is only correct to prefer it when argv[1] is an embedded path. Under Node the script path
|
|
24
|
+
* is what identifies the install.
|
|
25
|
+
*/
|
|
26
|
+
export function runningBinaryPath(): string {
|
|
27
|
+
const main = normalizePath(process.argv[1] ?? "")
|
|
28
|
+
return EMBEDDED_FS_PATH.test(main) ? process.execPath : (process.argv[1] ?? "")
|
|
29
|
+
}
|
|
30
|
+
|
|
11
31
|
/** The running binary sits in an npm or workspace layout. */
|
|
12
32
|
function hasNpmInstallPath(): boolean {
|
|
13
|
-
const main = normalizePath(
|
|
33
|
+
const main = normalizePath(runningBinaryPath())
|
|
14
34
|
return (
|
|
15
35
|
main.includes("node_modules") ||
|
|
16
36
|
main.includes("/dist/cli") ||
|
|
@@ -30,7 +50,7 @@ export function looksLikeNpmOrWorkspaceCLI(): boolean {
|
|
|
30
50
|
|
|
31
51
|
/** Homebrew / Linuxbrew Cellar layout. */
|
|
32
52
|
export function looksLikeHomebrewCLI(): boolean {
|
|
33
|
-
const main = normalizePath(
|
|
53
|
+
const main = normalizePath(runningBinaryPath())
|
|
34
54
|
if (!main) return false
|
|
35
55
|
|
|
36
56
|
if (/(?:^|\/)Cellar\/supatype\//.test(main)) return true
|
|
@@ -44,7 +64,7 @@ export function looksLikeHomebrewCLI(): boolean {
|
|
|
44
64
|
}
|
|
45
65
|
|
|
46
66
|
/**
|
|
47
|
-
* Which installer owns the running binary
|
|
67
|
+
* Which installer owns the running binary. This decides what `self-update`
|
|
48
68
|
* tells the user to run, so it has to describe the binary on disk.
|
|
49
69
|
*
|
|
50
70
|
* Path shape wins over `npm_*` in the environment: those are set whenever the
|
|
@@ -62,6 +82,6 @@ export function detectCliInstallMethod(): CliInstallMethod {
|
|
|
62
82
|
|
|
63
83
|
/** The `curl | sh` installer's default target. */
|
|
64
84
|
function isStandalonePath(): boolean {
|
|
65
|
-
const main = normalizePath(
|
|
85
|
+
const main = normalizePath(runningBinaryPath())
|
|
66
86
|
return main.includes("/.supatype/bin/")
|
|
67
87
|
}
|