@seamapi/cli 0.18.1 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -1
- package/bin/cli.js +73 -249
- package/bin/cli.js.map +1 -1
- package/lib/args/coerce.d.ts +28 -0
- package/lib/args/coerce.js +131 -0
- package/lib/args/coerce.js.map +1 -0
- package/lib/{util/cli-args.d.ts → args/parse.d.ts} +12 -15
- package/lib/{util/cli-args.js → args/parse.js} +21 -24
- package/lib/args/parse.js.map +1 -0
- package/lib/args/validate.d.ts +23 -0
- package/lib/args/validate.js +42 -0
- package/lib/args/validate.js.map +1 -0
- package/lib/auth/operations.d.ts +54 -0
- package/lib/auth/operations.js +115 -0
- package/lib/auth/operations.js.map +1 -0
- package/lib/{validate-token.js → auth/validate-token.js} +2 -2
- package/lib/auth/validate-token.js.map +1 -0
- package/lib/blueprint/cache.d.ts +16 -0
- package/lib/blueprint/cache.js +73 -0
- package/lib/blueprint/cache.js.map +1 -0
- package/lib/blueprint/endpoint.d.ts +14 -0
- package/lib/{get-response-key.js → blueprint/endpoint.js} +11 -2
- package/lib/blueprint/endpoint.js.map +1 -0
- package/lib/blueprint/index.d.ts +12 -0
- package/lib/blueprint/index.js +10 -0
- package/lib/blueprint/index.js.map +1 -0
- package/lib/blueprint/source-npm.d.ts +10 -0
- package/lib/{blueprint.js → blueprint/source-npm.js} +20 -74
- package/lib/blueprint/source-npm.js.map +1 -0
- package/lib/blueprint/source-remote.d.ts +6 -0
- package/lib/blueprint/source-remote.js +14 -0
- package/lib/blueprint/source-remote.js.map +1 -0
- package/lib/commands/api-command.d.ts +8 -0
- package/lib/commands/api-command.js +71 -0
- package/lib/commands/api-command.js.map +1 -0
- package/lib/commands/local/completion.d.ts +18 -0
- package/lib/commands/local/completion.js +35 -0
- package/lib/commands/local/completion.js.map +1 -0
- package/lib/commands/local/config-reveal-location.d.ts +2 -0
- package/lib/commands/local/config-reveal-location.js +15 -0
- package/lib/commands/local/config-reveal-location.js.map +1 -0
- package/lib/commands/local/config-set-fake-server.d.ts +3 -0
- package/lib/commands/local/config-set-fake-server.js +20 -0
- package/lib/commands/local/config-set-fake-server.js.map +1 -0
- package/lib/commands/local/config-use-remote-api-defs.d.ts +2 -0
- package/lib/commands/local/config-use-remote-api-defs.js +20 -0
- package/lib/commands/local/config-use-remote-api-defs.js.map +1 -0
- package/lib/commands/local/health.d.ts +2 -0
- package/lib/commands/local/health.js +18 -0
- package/lib/commands/local/health.js.map +1 -0
- package/lib/commands/local/login.d.ts +2 -0
- package/lib/commands/local/login.js +35 -0
- package/lib/commands/local/login.js.map +1 -0
- package/lib/commands/local/logout.d.ts +2 -0
- package/lib/commands/local/logout.js +17 -0
- package/lib/commands/local/logout.js.map +1 -0
- package/lib/commands/local/select-server.d.ts +2 -0
- package/lib/commands/local/select-server.js +27 -0
- package/lib/commands/local/select-server.js.map +1 -0
- package/lib/commands/local/select-workspace.d.ts +2 -0
- package/lib/commands/local/select-workspace.js +22 -0
- package/lib/commands/local/select-workspace.js.map +1 -0
- package/lib/commands/local/wizard.d.ts +9 -0
- package/lib/commands/local/wizard.js +28 -0
- package/lib/commands/local/wizard.js.map +1 -0
- package/lib/commands/registry.d.ts +54 -0
- package/lib/commands/registry.js +64 -0
- package/lib/commands/registry.js.map +1 -0
- package/lib/{command-spec.d.ts → commands/spec.d.ts} +8 -12
- package/lib/{command-spec.js → commands/spec.js} +27 -120
- package/lib/commands/spec.js.map +1 -0
- package/lib/config/config-store.d.ts +23 -8
- package/lib/config/config-store.js +16 -42
- package/lib/config/config-store.js.map +1 -1
- package/lib/config/index.d.ts +2 -1
- package/lib/config/index.js +2 -1
- package/lib/config/index.js.map +1 -1
- package/lib/config/memory-config-store.d.ts +21 -0
- package/lib/config/memory-config-store.js +48 -0
- package/lib/config/memory-config-store.js.map +1 -0
- package/lib/config/migrate.js +1 -1
- package/lib/config/migrate.js.map +1 -1
- package/lib/config/values.d.ts +12 -0
- package/lib/config/values.js +50 -0
- package/lib/config/values.js.map +1 -0
- package/lib/context.d.ts +37 -0
- package/lib/context.js +31 -0
- package/lib/context.js.map +1 -0
- package/lib/env.d.ts +5 -0
- package/lib/env.js +5 -0
- package/lib/env.js.map +1 -1
- package/lib/errors.d.ts +31 -0
- package/lib/errors.js +58 -0
- package/lib/errors.js.map +1 -0
- package/lib/http/api.d.ts +37 -0
- package/lib/http/api.js +20 -0
- package/lib/http/api.js.map +1 -0
- package/lib/http/client.d.ts +4 -0
- package/lib/{get-seam.js → http/client.js} +16 -17
- package/lib/http/client.js.map +1 -0
- package/lib/http/follow-ups.d.ts +6 -0
- package/lib/http/follow-ups.js +30 -0
- package/lib/http/follow-ups.js.map +1 -0
- package/lib/http/memory-seam-api.d.ts +22 -0
- package/lib/http/memory-seam-api.js +41 -0
- package/lib/http/memory-seam-api.js.map +1 -0
- package/lib/http/request.d.ts +19 -0
- package/lib/http/request.js +39 -0
- package/lib/http/request.js.map +1 -0
- package/lib/{interact-for-access-code.js → interactions/access-code.js} +4 -4
- package/lib/interactions/access-code.js.map +1 -0
- package/lib/{interact-for-acs-entrance.js → interactions/acs-entrance.js} +3 -3
- package/lib/interactions/acs-entrance.js.map +1 -0
- package/lib/{interact-for-acs-system.js → interactions/acs-system.js} +3 -3
- package/lib/interactions/acs-system.js.map +1 -0
- package/lib/{interact-for-acs-user.js → interactions/acs-user.js} +4 -4
- package/lib/interactions/acs-user.js.map +1 -0
- package/lib/{interact-for-action-attempt-poll.js → interactions/action-attempt-poll.js} +5 -5
- package/lib/interactions/action-attempt-poll.js.map +1 -0
- package/lib/{interact-for-array.js → interactions/array.js} +4 -3
- package/lib/interactions/array.js.map +1 -0
- package/lib/{interact-for-blueprint-object.d.ts → interactions/blueprint-object.d.ts} +3 -2
- package/lib/{interact-for-blueprint-object.js → interactions/blueprint-object.js} +49 -20
- package/lib/interactions/blueprint-object.js.map +1 -0
- package/lib/{interact-for-command-params.d.ts → interactions/command-params.d.ts} +2 -2
- package/lib/{interact-for-command-params.js → interactions/command-params.js} +4 -3
- package/lib/interactions/command-params.js.map +1 -0
- package/lib/interactions/command-selection.d.ts +10 -0
- package/lib/{interact-for-command-selection.js → interactions/command-selection.js} +9 -15
- package/lib/interactions/command-selection.js.map +1 -0
- package/lib/{interact-for-connected-account.js → interactions/connected-account.js} +3 -3
- package/lib/interactions/connected-account.js.map +1 -0
- package/lib/{interact-for-custom-metadata.js → interactions/custom-metadata.js} +4 -3
- package/lib/interactions/custom-metadata.js.map +1 -0
- package/lib/{interact-for-device.js → interactions/device.js} +3 -3
- package/lib/interactions/device.js.map +1 -0
- package/lib/interactions/index.d.ts +19 -0
- package/lib/interactions/index.js +20 -0
- package/lib/interactions/index.js.map +1 -0
- package/lib/{interact-for-login.js → interactions/login.js} +16 -14
- package/lib/interactions/login.js.map +1 -0
- package/lib/{interact-for-resource.js → interactions/resource.js} +3 -3
- package/lib/interactions/resource.js.map +1 -0
- package/lib/{interact-for-server-selection.js → interactions/server-selection.js} +10 -13
- package/lib/interactions/server-selection.js.map +1 -0
- package/lib/{interact-for-timestamp.js → interactions/timestamp.js} +2 -2
- package/lib/interactions/timestamp.js.map +1 -0
- package/lib/{interact-for-use-remote-api-defs.js → interactions/use-remote-api-defs.js} +5 -6
- package/lib/interactions/use-remote-api-defs.js.map +1 -0
- package/lib/{interact-for-user-identity.js → interactions/user-identity.js} +3 -3
- package/lib/interactions/user-identity.js.map +1 -0
- package/lib/{interact-for-workspace-id.js → interactions/workspace-id.js} +11 -10
- package/lib/interactions/workspace-id.js.map +1 -0
- package/lib/memory-prompt.d.ts +32 -0
- package/lib/memory-prompt.js +41 -0
- package/lib/memory-prompt.js.map +1 -0
- package/lib/output/get-output.d.ts +1 -1
- package/lib/output/get-output.js +1 -1
- package/lib/output/get-output.js.map +1 -1
- package/lib/output/{create-memory-output.d.ts → memory-output.d.ts} +1 -1
- package/lib/output/{create-memory-output.js → memory-output.js} +2 -2
- package/lib/output/memory-output.js.map +1 -0
- package/lib/output/{create-output.d.ts → output.d.ts} +18 -1
- package/lib/output/output.js +46 -0
- package/lib/output/output.js.map +1 -0
- package/lib/output/read-stdin-json.js.map +1 -0
- package/lib/output/resolve-output-format.d.ts +1 -1
- package/lib/{util → output}/with-loading.js +1 -1
- package/lib/output/with-loading.js.map +1 -0
- package/lib/prompt.d.ts +101 -0
- package/lib/{util/prompt.js → prompt.js} +83 -56
- package/lib/prompt.js.map +1 -0
- package/lib/{completion → render/completion}/describe.js +1 -2
- package/lib/render/completion/describe.js.map +1 -0
- package/lib/{completion → render/completion}/index.d.ts +2 -2
- package/lib/{completion → render/completion}/index.js +1 -2
- package/lib/render/completion/index.js.map +1 -0
- package/lib/{completion → render/completion}/render-bash.d.ts +1 -1
- package/lib/{completion → render/completion}/render-bash.js +1 -1
- package/lib/render/completion/render-bash.js.map +1 -0
- package/lib/{completion → render/completion}/render-fish.d.ts +1 -1
- package/lib/render/completion/render-fish.js.map +1 -0
- package/lib/{completion → render/completion}/render-zsh.d.ts +1 -1
- package/lib/{completion → render/completion}/render-zsh.js +1 -1
- package/lib/render/completion/render-zsh.js.map +1 -0
- package/lib/{render-help.d.ts → render/help.d.ts} +1 -1
- package/lib/{render-help.js → render/help.js} +2 -2
- package/lib/render/help.js.map +1 -0
- package/lib/render/text.d.ts +4 -0
- package/lib/render/text.js +16 -0
- package/lib/render/text.js.map +1 -0
- package/lib/version.d.ts +2 -2
- package/lib/version.js +2 -2
- package/package.json +4 -3
- package/src/bin/cli.ts +91 -332
- package/src/lib/args/coerce.ts +164 -0
- package/src/lib/{util/cli-args.ts → args/parse.ts} +30 -25
- package/src/lib/args/validate.ts +73 -0
- package/src/lib/auth/operations.ts +169 -0
- package/src/lib/{validate-token.ts → auth/validate-token.ts} +2 -2
- package/src/lib/blueprint/cache.ts +97 -0
- package/src/lib/{get-response-key.ts → blueprint/endpoint.ts} +17 -3
- package/src/lib/blueprint/index.ts +27 -0
- package/src/lib/{blueprint.ts → blueprint/source-npm.ts} +27 -99
- package/src/lib/blueprint/source-remote.ts +17 -0
- package/src/lib/commands/api-command.ts +109 -0
- package/src/lib/commands/local/completion.ts +51 -0
- package/src/lib/commands/local/config-reveal-location.ts +16 -0
- package/src/lib/commands/local/config-set-fake-server.ts +21 -0
- package/src/lib/commands/local/config-use-remote-api-defs.ts +23 -0
- package/src/lib/commands/local/health.ts +22 -0
- package/src/lib/commands/local/login.ts +42 -0
- package/src/lib/commands/local/logout.ts +18 -0
- package/src/lib/commands/local/select-server.ts +30 -0
- package/src/lib/commands/local/select-workspace.ts +25 -0
- package/src/lib/commands/local/wizard.ts +31 -0
- package/src/lib/commands/registry.ts +127 -0
- package/src/lib/{command-spec.ts → commands/spec.ts} +30 -131
- package/src/lib/config/config-store.ts +39 -64
- package/src/lib/config/index.ts +8 -1
- package/src/lib/config/memory-config-store.ts +62 -0
- package/src/lib/config/migrate.ts +1 -1
- package/src/lib/config/values.ts +72 -0
- package/src/lib/context.ts +84 -0
- package/src/lib/env.ts +9 -2
- package/src/lib/errors.ts +67 -0
- package/src/lib/http/api.ts +61 -0
- package/src/lib/{get-seam.ts → http/client.ts} +19 -18
- package/src/lib/http/follow-ups.ts +42 -0
- package/src/lib/http/memory-seam-api.ts +57 -0
- package/src/lib/http/request.ts +69 -0
- package/src/lib/{interact-for-access-code.ts → interactions/access-code.ts} +4 -3
- package/src/lib/{interact-for-acs-entrance.ts → interactions/acs-entrance.ts} +3 -2
- package/src/lib/{interact-for-acs-system.ts → interactions/acs-system.ts} +3 -2
- package/src/lib/{interact-for-acs-user.ts → interactions/acs-user.ts} +4 -3
- package/src/lib/{interact-for-action-attempt-poll.ts → interactions/action-attempt-poll.ts} +4 -4
- package/src/lib/{interact-for-array.ts → interactions/array.ts} +3 -3
- package/src/lib/{interact-for-blueprint-object.ts → interactions/blueprint-object.ts} +57 -24
- package/src/lib/{interact-for-command-params.ts → interactions/command-params.ts} +6 -4
- package/src/lib/{interact-for-command-selection.ts → interactions/command-selection.ts} +11 -23
- package/src/lib/{interact-for-connected-account.ts → interactions/connected-account.ts} +3 -2
- package/src/lib/{interact-for-custom-metadata.ts → interactions/custom-metadata.ts} +3 -7
- package/src/lib/{interact-for-device.ts → interactions/device.ts} +3 -2
- package/src/lib/interactions/index.ts +19 -0
- package/src/lib/{interact-for-login.ts → interactions/login.ts} +16 -13
- package/src/lib/{interact-for-resource.ts → interactions/resource.ts} +2 -2
- package/src/lib/{interact-for-server-selection.ts → interactions/server-selection.ts} +12 -17
- package/src/lib/{interact-for-timestamp.ts → interactions/timestamp.ts} +1 -1
- package/src/lib/{interact-for-use-remote-api-defs.ts → interactions/use-remote-api-defs.ts} +4 -5
- package/src/lib/{interact-for-user-identity.ts → interactions/user-identity.ts} +3 -2
- package/src/lib/{interact-for-workspace-id.ts → interactions/workspace-id.ts} +10 -17
- package/src/lib/memory-prompt.ts +89 -0
- package/src/lib/output/get-output.ts +1 -1
- package/src/lib/output/{create-memory-output.ts → memory-output.ts} +1 -1
- package/src/lib/output/{create-output.ts → output.ts} +47 -32
- package/src/lib/output/resolve-output-format.ts +1 -1
- package/src/lib/{util → output}/with-loading.ts +1 -1
- package/src/lib/prompt.ts +306 -0
- package/src/lib/{completion → render/completion}/describe.ts +1 -2
- package/src/lib/{completion → render/completion}/index.ts +3 -4
- package/src/lib/{completion → render/completion}/render-bash.ts +1 -1
- package/src/lib/{completion → render/completion}/render-fish.ts +2 -1
- package/src/lib/{completion → render/completion}/render-zsh.ts +2 -1
- package/src/lib/{render-help.ts → render/help.ts} +1 -1
- package/src/lib/render/text.ts +17 -0
- package/src/lib/version.ts +2 -2
- package/lib/blueprint.d.ts +0 -7
- package/lib/blueprint.js.map +0 -1
- package/lib/command-spec.js.map +0 -1
- package/lib/completion/describe.js.map +0 -1
- package/lib/completion/index.js.map +0 -1
- package/lib/completion/render-bash.js.map +0 -1
- package/lib/completion/render-fish.js.map +0 -1
- package/lib/completion/render-zsh.js.map +0 -1
- package/lib/get-api-blueprint.d.ts +0 -6
- package/lib/get-api-blueprint.js +0 -18
- package/lib/get-api-blueprint.js.map +0 -1
- package/lib/get-command-blueprint-def.d.ts +0 -2
- package/lib/get-command-blueprint-def.js +0 -11
- package/lib/get-command-blueprint-def.js.map +0 -1
- package/lib/get-credentials.d.ts +0 -15
- package/lib/get-credentials.js +0 -35
- package/lib/get-credentials.js.map +0 -1
- package/lib/get-current-workspace-id.d.ts +0 -1
- package/lib/get-current-workspace-id.js +0 -9
- package/lib/get-current-workspace-id.js.map +0 -1
- package/lib/get-response-key.d.ts +0 -9
- package/lib/get-response-key.js.map +0 -1
- package/lib/get-seam.d.ts +0 -3
- package/lib/get-seam.js.map +0 -1
- package/lib/get-server.d.ts +0 -6
- package/lib/get-server.js +0 -17
- package/lib/get-server.js.map +0 -1
- package/lib/interact-for-access-code.js.map +0 -1
- package/lib/interact-for-acs-entrance.js.map +0 -1
- package/lib/interact-for-acs-system.js.map +0 -1
- package/lib/interact-for-acs-user.js.map +0 -1
- package/lib/interact-for-action-attempt-poll.js.map +0 -1
- package/lib/interact-for-array.js.map +0 -1
- package/lib/interact-for-blueprint-object.js.map +0 -1
- package/lib/interact-for-command-params.js.map +0 -1
- package/lib/interact-for-command-selection.d.ts +0 -2
- package/lib/interact-for-command-selection.js.map +0 -1
- package/lib/interact-for-connected-account.js.map +0 -1
- package/lib/interact-for-custom-metadata.js.map +0 -1
- package/lib/interact-for-device.js.map +0 -1
- package/lib/interact-for-login.js.map +0 -1
- package/lib/interact-for-resource.js.map +0 -1
- package/lib/interact-for-server-selection.js.map +0 -1
- package/lib/interact-for-timestamp.js.map +0 -1
- package/lib/interact-for-use-remote-api-defs.js.map +0 -1
- package/lib/interact-for-user-identity.js.map +0 -1
- package/lib/interact-for-workspace-id.js.map +0 -1
- package/lib/output/create-memory-output.js.map +0 -1
- package/lib/output/create-output.js +0 -34
- package/lib/output/create-output.js.map +0 -1
- package/lib/render-help.js.map +0 -1
- package/lib/types.d.ts +0 -6
- package/lib/types.js +0 -2
- package/lib/types.js.map +0 -1
- package/lib/util/cli-args.js.map +0 -1
- package/lib/util/ellipsis.d.ts +0 -1
- package/lib/util/ellipsis.js +0 -6
- package/lib/util/ellipsis.js.map +0 -1
- package/lib/util/prompt.d.ts +0 -80
- package/lib/util/prompt.js.map +0 -1
- package/lib/util/read-stdin-json.js.map +0 -1
- package/lib/util/request-seam-api.d.ts +0 -7
- package/lib/util/request-seam-api.js +0 -26
- package/lib/util/request-seam-api.js.map +0 -1
- package/lib/util/with-loading.js.map +0 -1
- package/lib/validate-token.js.map +0 -1
- package/src/lib/get-api-blueprint.ts +0 -31
- package/src/lib/get-command-blueprint-def.ts +0 -15
- package/src/lib/get-credentials.ts +0 -38
- package/src/lib/get-current-workspace-id.ts +0 -9
- package/src/lib/get-server.ts +0 -20
- package/src/lib/types.ts +0 -7
- package/src/lib/util/ellipsis.ts +0 -4
- package/src/lib/util/prompt.ts +0 -229
- package/src/lib/util/request-seam-api.ts +0 -47
- /package/lib/{validate-token.d.ts → auth/validate-token.d.ts} +0 -0
- /package/lib/{interact-for-access-code.d.ts → interactions/access-code.d.ts} +0 -0
- /package/lib/{interact-for-acs-entrance.d.ts → interactions/acs-entrance.d.ts} +0 -0
- /package/lib/{interact-for-acs-system.d.ts → interactions/acs-system.d.ts} +0 -0
- /package/lib/{interact-for-acs-user.d.ts → interactions/acs-user.d.ts} +0 -0
- /package/lib/{interact-for-action-attempt-poll.d.ts → interactions/action-attempt-poll.d.ts} +0 -0
- /package/lib/{interact-for-array.d.ts → interactions/array.d.ts} +0 -0
- /package/lib/{interact-for-connected-account.d.ts → interactions/connected-account.d.ts} +0 -0
- /package/lib/{interact-for-custom-metadata.d.ts → interactions/custom-metadata.d.ts} +0 -0
- /package/lib/{interact-for-device.d.ts → interactions/device.d.ts} +0 -0
- /package/lib/{interact-for-login.d.ts → interactions/login.d.ts} +0 -0
- /package/lib/{interact-for-resource.d.ts → interactions/resource.d.ts} +0 -0
- /package/lib/{interact-for-server-selection.d.ts → interactions/server-selection.d.ts} +0 -0
- /package/lib/{interact-for-timestamp.d.ts → interactions/timestamp.d.ts} +0 -0
- /package/lib/{interact-for-use-remote-api-defs.d.ts → interactions/use-remote-api-defs.d.ts} +0 -0
- /package/lib/{interact-for-user-identity.d.ts → interactions/user-identity.d.ts} +0 -0
- /package/lib/{interact-for-workspace-id.d.ts → interactions/workspace-id.d.ts} +0 -0
- /package/lib/{util → output}/read-stdin-json.d.ts +0 -0
- /package/lib/{util → output}/read-stdin-json.js +0 -0
- /package/lib/{util → output}/with-loading.d.ts +0 -0
- /package/lib/{completion → render/completion}/describe.d.ts +0 -0
- /package/lib/{completion → render/completion}/render-fish.js +0 -0
- /package/src/lib/{util → output}/read-stdin-json.ts +0 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import type { Parameter } from '@seamapi/blueprint'
|
|
2
|
+
|
|
3
|
+
/** An argument whose value does not fit the parameter's documented format. */
|
|
4
|
+
export interface CoercionIssue {
|
|
5
|
+
name: string
|
|
6
|
+
given: unknown
|
|
7
|
+
/** What the parameter takes, e.g., `a number`. */
|
|
8
|
+
expected: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type Coerced = { value: unknown } | { issue: string }
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Read each argument as the JSON value its parameter documents: booleans and
|
|
15
|
+
* numbers become real booleans and numbers, lists split on commas, objects
|
|
16
|
+
* parse as JSON. The request body is then the same whether a value arrived
|
|
17
|
+
* as an argument, over stdin, or interactively.
|
|
18
|
+
*
|
|
19
|
+
* An argument naming no parameter passes through unchanged: unknown
|
|
20
|
+
* arguments are reported by `assertKnownArgs`, not silently dropped here.
|
|
21
|
+
*/
|
|
22
|
+
export const coerceArgParams = (
|
|
23
|
+
parameters: Parameter[],
|
|
24
|
+
argParams: Record<string, unknown>,
|
|
25
|
+
): { params: Record<string, unknown>; issues: CoercionIssue[] } => {
|
|
26
|
+
const byName = new Map(
|
|
27
|
+
parameters.map((parameter) => [parameter.name, parameter]),
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
const params: Record<string, unknown> = {}
|
|
31
|
+
const issues: CoercionIssue[] = []
|
|
32
|
+
|
|
33
|
+
for (const [name, given] of Object.entries(argParams)) {
|
|
34
|
+
const parameter = byName.get(name)
|
|
35
|
+
if (parameter == null) {
|
|
36
|
+
params[name] = given
|
|
37
|
+
continue
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const coerced = coerceParam(parameter, given)
|
|
41
|
+
if ('issue' in coerced) {
|
|
42
|
+
issues.push({ name, given, expected: coerced.issue })
|
|
43
|
+
continue
|
|
44
|
+
}
|
|
45
|
+
params[name] = coerced.value
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return { params, issues }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const coerceParam = (parameter: Parameter, given: unknown): Coerced => {
|
|
52
|
+
// `null` is an explicit JSON value for nullable parameters, not the string
|
|
53
|
+
// "null" (or a one-item list containing it).
|
|
54
|
+
if (parameter.isNullable && (given === null || given === 'null')) {
|
|
55
|
+
return { value: null }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (parameter.format === 'list') return coerceList(parameter, given)
|
|
59
|
+
|
|
60
|
+
// A repeated argument parses as an array, which only a list accepts.
|
|
61
|
+
if (Array.isArray(given)) return { issue: 'a single value' }
|
|
62
|
+
|
|
63
|
+
switch (parameter.format) {
|
|
64
|
+
case 'boolean':
|
|
65
|
+
return coerceBoolean(given)
|
|
66
|
+
case 'number':
|
|
67
|
+
return coerceNumber(given)
|
|
68
|
+
case 'enum':
|
|
69
|
+
return coerceEnum(parameter, given)
|
|
70
|
+
case 'object':
|
|
71
|
+
return coerceObject(given)
|
|
72
|
+
default:
|
|
73
|
+
return { value: String(given) }
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const coerceBoolean = (given: unknown): Coerced => {
|
|
78
|
+
if (given === true || given === 'true' || given === '1' || given === 1) {
|
|
79
|
+
return { value: true }
|
|
80
|
+
}
|
|
81
|
+
if (given === false || given === 'false' || given === '0' || given === 0) {
|
|
82
|
+
return { value: false }
|
|
83
|
+
}
|
|
84
|
+
return { issue: 'true or false' }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const coerceNumber = (given: unknown): Coerced => {
|
|
88
|
+
if (typeof given === 'number') return { value: given }
|
|
89
|
+
if (typeof given === 'string' && given.trim() !== '') {
|
|
90
|
+
const value = Number(given)
|
|
91
|
+
if (!Number.isNaN(value)) return { value }
|
|
92
|
+
}
|
|
93
|
+
return { issue: 'a number' }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const coerceEnum = (
|
|
97
|
+
parameter: Parameter & { format: 'enum' },
|
|
98
|
+
given: unknown,
|
|
99
|
+
): Coerced => {
|
|
100
|
+
const value = String(given)
|
|
101
|
+
const names = parameter.values.map(({ name }) => name)
|
|
102
|
+
if (!names.includes(value)) return { issue: `one of ${names.join(', ')}` }
|
|
103
|
+
return { value }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const coerceObject = (given: unknown): Coerced => {
|
|
107
|
+
if (typeof given !== 'string') return { issue: 'a JSON object' }
|
|
108
|
+
try {
|
|
109
|
+
const value: unknown = JSON.parse(given)
|
|
110
|
+
if (value == null || typeof value !== 'object' || Array.isArray(value)) {
|
|
111
|
+
return { issue: 'a JSON object' }
|
|
112
|
+
}
|
|
113
|
+
return { value }
|
|
114
|
+
} catch {
|
|
115
|
+
return { issue: 'a JSON object' }
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const coerceList = (
|
|
120
|
+
parameter: Parameter & { format: 'list' },
|
|
121
|
+
given: unknown,
|
|
122
|
+
): Coerced => {
|
|
123
|
+
const items = Array.isArray(given)
|
|
124
|
+
? given
|
|
125
|
+
: typeof given === 'string'
|
|
126
|
+
? given.split(',')
|
|
127
|
+
: [given]
|
|
128
|
+
|
|
129
|
+
const values: unknown[] = []
|
|
130
|
+
for (const item of items) {
|
|
131
|
+
const coerced = coerceListItem(parameter, item)
|
|
132
|
+
if ('issue' in coerced) return coerced
|
|
133
|
+
values.push(coerced.value)
|
|
134
|
+
}
|
|
135
|
+
return { value: values }
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const coerceListItem = (
|
|
139
|
+
parameter: Parameter & { format: 'list' },
|
|
140
|
+
item: unknown,
|
|
141
|
+
): Coerced => {
|
|
142
|
+
if (parameter.itemFormat === 'number') {
|
|
143
|
+
const coerced = coerceNumber(item)
|
|
144
|
+
if ('issue' in coerced) return { issue: 'a list of numbers' }
|
|
145
|
+
return coerced
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (parameter.itemFormat === 'boolean') {
|
|
149
|
+
const coerced = coerceBoolean(item)
|
|
150
|
+
if ('issue' in coerced) return { issue: 'a list of true or false' }
|
|
151
|
+
return coerced
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (parameter.itemFormat === 'enum') {
|
|
155
|
+
const value = String(item)
|
|
156
|
+
const names = parameter.itemEnumValues.map(({ name }) => name)
|
|
157
|
+
if (!names.includes(value)) {
|
|
158
|
+
return { issue: `a list of ${names.join(', ')}` }
|
|
159
|
+
}
|
|
160
|
+
return { value }
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return { value: String(item) }
|
|
164
|
+
}
|
|
@@ -37,40 +37,45 @@ export const cliFlags: string[] = [
|
|
|
37
37
|
'version',
|
|
38
38
|
]
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
* Thrown when the arguments do not name something the CLI can run.
|
|
49
|
-
*/
|
|
50
|
-
export class UsageError extends Error {
|
|
51
|
-
override name = 'UsageError'
|
|
52
|
-
|
|
53
|
-
/** What to run instead, reported after the message. */
|
|
54
|
-
readonly hint: string
|
|
55
|
-
|
|
56
|
-
constructor(message: string, { hint = '' }: { hint?: string } = {}) {
|
|
57
|
-
super(message)
|
|
58
|
-
this.hint = hint
|
|
59
|
-
}
|
|
40
|
+
export interface ParseCliArgsOptions {
|
|
41
|
+
/**
|
|
42
|
+
* Argument keys read exactly as given rather than by guessing at a type,
|
|
43
|
+
* e.g., every parameter of an endpoint that does not take a number or a
|
|
44
|
+
* boolean. Read as a number, an opaque value like an access code would
|
|
45
|
+
* lose leading zeroes or turn exponent notation into a digit string.
|
|
46
|
+
*/
|
|
47
|
+
stringKeys?: string[]
|
|
60
48
|
}
|
|
61
49
|
|
|
62
|
-
export const parseCliArgs = (
|
|
50
|
+
export const parseCliArgs = (
|
|
51
|
+
argv: string[],
|
|
52
|
+
{ stringKeys = [] }: ParseCliArgsOptions = {},
|
|
53
|
+
): ParsedArgs =>
|
|
63
54
|
parseArgs(argv, {
|
|
64
|
-
// A page cursor
|
|
65
|
-
//
|
|
66
|
-
|
|
67
|
-
string: ['code', 'page-cursor', 'page_cursor'],
|
|
55
|
+
// A page cursor and a code are opaque even before the endpoint's own
|
|
56
|
+
// parameter types are known, so always keep them exactly as given.
|
|
57
|
+
string: ['code', 'page-cursor', 'page_cursor', ...stringKeys],
|
|
68
58
|
boolean: ['non-interactive', 'interactive', 'json'],
|
|
69
59
|
// Deliberately not aliased to -n, which is reserved for a future
|
|
70
60
|
// --dry-run flag.
|
|
71
61
|
alias: { 'non-interactive': 'y', interactive: 'i' },
|
|
72
62
|
})
|
|
73
63
|
|
|
64
|
+
/**
|
|
65
|
+
* The request params among the parsed arguments: every key normalized to
|
|
66
|
+
* the parameter it names, minus the flags that configure the CLI itself.
|
|
67
|
+
*/
|
|
68
|
+
export const toArgParams = (args: ParsedArgs): Record<string, unknown> => {
|
|
69
|
+
const argParams: Record<string, unknown> = {}
|
|
70
|
+
for (const [key, value] of Object.entries(args)) {
|
|
71
|
+
if (key === '_') continue
|
|
72
|
+
const name = toParameterName(key)
|
|
73
|
+
if (cliFlags.includes(name)) continue
|
|
74
|
+
argParams[name] = value
|
|
75
|
+
}
|
|
76
|
+
return argParams
|
|
77
|
+
}
|
|
78
|
+
|
|
74
79
|
export interface GetInteractivityOptions {
|
|
75
80
|
/**
|
|
76
81
|
* Whether there is a terminal to prompt on.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { Parameter } from '@seamapi/blueprint'
|
|
2
|
+
|
|
3
|
+
import { NonInteractiveError, UsageError } from 'lib/errors.js'
|
|
4
|
+
|
|
5
|
+
import { toArgName, toGivenArgName } from './parse.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Report every required parameter still missing from the params, rather
|
|
9
|
+
* than prompting for it.
|
|
10
|
+
*
|
|
11
|
+
* @param target What the params are for, e.g., `/devices/list`.
|
|
12
|
+
*/
|
|
13
|
+
export const assertRequiredParams = (
|
|
14
|
+
parameters: Parameter[],
|
|
15
|
+
params: Record<string, unknown>,
|
|
16
|
+
target: string,
|
|
17
|
+
): void => {
|
|
18
|
+
// A required parameter is satisfied by being present, not by being
|
|
19
|
+
// truthy: `false`, `0` and `''` are values a caller can supply.
|
|
20
|
+
const missing = parameters
|
|
21
|
+
.filter((parameter) => parameter.isRequired)
|
|
22
|
+
.map((parameter) => parameter.name)
|
|
23
|
+
.filter((name) => params[name] === undefined)
|
|
24
|
+
|
|
25
|
+
if (missing.length === 0) return
|
|
26
|
+
|
|
27
|
+
throw new NonInteractiveError(
|
|
28
|
+
`Missing required ${
|
|
29
|
+
missing.length === 1 ? 'parameter' : 'parameters'
|
|
30
|
+
} for ${target}: ${missing.map(toArgName).join(' ')}`,
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Report any argument the command does not accept, rather than acting on it.
|
|
36
|
+
* An unrecognized argument is a mistake: forwarded to the API it would fail
|
|
37
|
+
* somewhere less obvious or be quietly ignored, and on a command the CLI
|
|
38
|
+
* handles itself it would go nowhere at all.
|
|
39
|
+
*
|
|
40
|
+
* Only arguments are checked. Params read from stdin are passed through as
|
|
41
|
+
* given, so a caller may send whatever the API itself accepts.
|
|
42
|
+
*/
|
|
43
|
+
export const assertKnownArgs = (
|
|
44
|
+
argParams: Record<string, unknown>,
|
|
45
|
+
command: string[],
|
|
46
|
+
{
|
|
47
|
+
accepted,
|
|
48
|
+
isLocal,
|
|
49
|
+
}: {
|
|
50
|
+
/** Parameter names the command accepts. */
|
|
51
|
+
accepted: Set<string>
|
|
52
|
+
/** Whether the CLI handles the command itself. */
|
|
53
|
+
isLocal: boolean
|
|
54
|
+
},
|
|
55
|
+
): void => {
|
|
56
|
+
const unknown = Object.keys(argParams).filter((key) => !accepted.has(key))
|
|
57
|
+
if (unknown.length === 0) return
|
|
58
|
+
|
|
59
|
+
// Name an endpoint command by its path, as missing params are named, and a
|
|
60
|
+
// command the CLI handles itself by the words that run it.
|
|
61
|
+
const target = isLocal
|
|
62
|
+
? command.join(' ')
|
|
63
|
+
: `/${command.join('/').replace(/-/g, '_')}`
|
|
64
|
+
|
|
65
|
+
throw new UsageError(
|
|
66
|
+
`Unknown ${
|
|
67
|
+
unknown.length === 1 ? 'parameter' : 'parameters'
|
|
68
|
+
} for ${target}: ${unknown.map(toGivenArgName).join(' ')}`,
|
|
69
|
+
{
|
|
70
|
+
hint: `Run 'seam ${command.join(' ')} --help' to see what it accepts.`,
|
|
71
|
+
},
|
|
72
|
+
)
|
|
73
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto'
|
|
2
|
+
|
|
3
|
+
import { type ConfigStore, getConfigStore } from 'lib/config/index.js'
|
|
4
|
+
import { type AuthContext, resolveAuth } from 'lib/context.js'
|
|
5
|
+
import {
|
|
6
|
+
assertEnvVarUnset,
|
|
7
|
+
endpointEnvVar,
|
|
8
|
+
tokenEnvVar,
|
|
9
|
+
workspaceIdEnvVar,
|
|
10
|
+
} from 'lib/env.js'
|
|
11
|
+
|
|
12
|
+
import { validateToken } from './validate-token.js'
|
|
13
|
+
|
|
14
|
+
/** A stored auth setting an environment variable may override. */
|
|
15
|
+
export type AuthSetting = 'server' | 'token' | 'workspaceId'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Refuse to store a setting the environment overrides.
|
|
19
|
+
*
|
|
20
|
+
* The env-override policy lives here alone: every auth mutation asserts
|
|
21
|
+
* through this before writing, so a command that appears to succeed cannot
|
|
22
|
+
* leave the CLI using something else.
|
|
23
|
+
*
|
|
24
|
+
* @param action What the command does, e.g., `log in`.
|
|
25
|
+
*/
|
|
26
|
+
export const assertMutable = (
|
|
27
|
+
auth: AuthContext,
|
|
28
|
+
setting: AuthSetting,
|
|
29
|
+
action: string,
|
|
30
|
+
): void => {
|
|
31
|
+
const { envVar, source, value } = {
|
|
32
|
+
server: {
|
|
33
|
+
envVar: endpointEnvVar,
|
|
34
|
+
source: auth.serverSource,
|
|
35
|
+
value: auth.server,
|
|
36
|
+
},
|
|
37
|
+
token: { envVar: tokenEnvVar, source: auth.tokenSource, value: auth.token },
|
|
38
|
+
workspaceId: {
|
|
39
|
+
envVar: workspaceIdEnvVar,
|
|
40
|
+
source: auth.workspaceIdSource,
|
|
41
|
+
value: auth.workspaceId,
|
|
42
|
+
},
|
|
43
|
+
}[setting]
|
|
44
|
+
|
|
45
|
+
if (source !== 'env') return
|
|
46
|
+
assertEnvVarUnset(envVar, value, action)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface LoginOptions {
|
|
50
|
+
server?: string | undefined
|
|
51
|
+
token?: string | undefined
|
|
52
|
+
workspaceId?: string | undefined
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Store the given credentials, validating the token first.
|
|
57
|
+
*
|
|
58
|
+
* The token is stored under the server it will be used with, so a given
|
|
59
|
+
* server is stored and re-resolved before the token key is derived.
|
|
60
|
+
*
|
|
61
|
+
* Validation reaches the network, so a test may inject its own `validate`.
|
|
62
|
+
*/
|
|
63
|
+
export const login = async (
|
|
64
|
+
{ server, token, workspaceId }: LoginOptions,
|
|
65
|
+
config: ConfigStore = getConfigStore(),
|
|
66
|
+
validate: typeof validateToken = validateToken,
|
|
67
|
+
): Promise<void> => {
|
|
68
|
+
let auth = resolveAuth(config)
|
|
69
|
+
|
|
70
|
+
// Nothing is stored while the environment overrides it, so refuse before
|
|
71
|
+
// storing anything rather than part way through.
|
|
72
|
+
assertMutable(auth, 'token', 'log in')
|
|
73
|
+
if (server != null) assertMutable(auth, 'server', 'select a server')
|
|
74
|
+
if (workspaceId != null) {
|
|
75
|
+
assertMutable(auth, 'workspaceId', 'select a workspace')
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (server != null) {
|
|
79
|
+
config.set('server', server)
|
|
80
|
+
config.delete('current_workspace_id')
|
|
81
|
+
auth = resolveAuth(config)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (token != null) {
|
|
85
|
+
await validate(token, workspaceId)
|
|
86
|
+
config.set(`${auth.server}.pat`, token)
|
|
87
|
+
config.delete('current_workspace_id')
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (workspaceId != null) {
|
|
91
|
+
config.set('current_workspace_id', workspaceId)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Store the token for the current server, e.g., one just prompted for. */
|
|
96
|
+
export const storeToken = (
|
|
97
|
+
token: string,
|
|
98
|
+
config: ConfigStore = getConfigStore(),
|
|
99
|
+
): void => {
|
|
100
|
+
const auth = resolveAuth(config)
|
|
101
|
+
assertMutable(auth, 'token', 'log in')
|
|
102
|
+
config.set(`${auth.server}.pat`, token)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Remove the stored token and workspace selection. */
|
|
106
|
+
export const logout = (config: ConfigStore = getConfigStore()): void => {
|
|
107
|
+
const auth = resolveAuth(config)
|
|
108
|
+
assertMutable(auth, 'token', 'log out')
|
|
109
|
+
config.delete(`${auth.server}.pat`)
|
|
110
|
+
// Configs written before tokens were stored per server may still hold an
|
|
111
|
+
// un-namespaced token, so drop that too.
|
|
112
|
+
config.delete('pat')
|
|
113
|
+
config.delete('current_workspace_id')
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Store the server to make requests against.
|
|
118
|
+
*
|
|
119
|
+
* The workspace selection belongs to the previous server, so it is cleared.
|
|
120
|
+
*/
|
|
121
|
+
export const selectServer = (
|
|
122
|
+
server: string,
|
|
123
|
+
config: ConfigStore = getConfigStore(),
|
|
124
|
+
): void => {
|
|
125
|
+
assertMutable(resolveAuth(config), 'server', 'select a server')
|
|
126
|
+
config.set('server', server)
|
|
127
|
+
config.delete('current_workspace_id')
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Store the workspace requests are made against. */
|
|
131
|
+
export const selectWorkspace = (
|
|
132
|
+
workspaceId: string,
|
|
133
|
+
config: ConfigStore = getConfigStore(),
|
|
134
|
+
): void => {
|
|
135
|
+
assertMutable(resolveAuth(config), 'workspaceId', 'select a workspace')
|
|
136
|
+
config.set('current_workspace_id', workspaceId)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Point the CLI at a fake Seam Connect server and store the well-known
|
|
141
|
+
* token it accepts. Returns the generated server URL for reporting.
|
|
142
|
+
*/
|
|
143
|
+
export const selectFakeServer = ({
|
|
144
|
+
urlSeed = randomBytes(5).toString('hex'),
|
|
145
|
+
config = getConfigStore(),
|
|
146
|
+
}: {
|
|
147
|
+
urlSeed?: string
|
|
148
|
+
config?: ConfigStore
|
|
149
|
+
} = {}): { server: string; token: string } => {
|
|
150
|
+
const auth = resolveAuth(config)
|
|
151
|
+
assertMutable(auth, 'server', 'select a server')
|
|
152
|
+
assertMutable(auth, 'token', 'log in')
|
|
153
|
+
|
|
154
|
+
const server = `https://${urlSeed}.fakeseamconnect.seam.vc`
|
|
155
|
+
const token = 'seam_apikey1_token'
|
|
156
|
+
config.set('server', server)
|
|
157
|
+
config.set(`${server}.pat`, token)
|
|
158
|
+
config.delete('current_workspace_id')
|
|
159
|
+
|
|
160
|
+
return { server, token }
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Store whether API definitions come from the server instead of npm. */
|
|
164
|
+
export const setUseRemoteApiDefs = (
|
|
165
|
+
useRemoteApiDefs: boolean,
|
|
166
|
+
config: ConfigStore = getConfigStore(),
|
|
167
|
+
): void => {
|
|
168
|
+
config.set('use_remote_api_defs', useRemoteApiDefs)
|
|
169
|
+
}
|
|
@@ -5,10 +5,10 @@ import {
|
|
|
5
5
|
SeamHttpWithoutWorkspace,
|
|
6
6
|
} from '@seamapi/http/connect'
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { resolveAuth } from 'lib/context.js'
|
|
9
9
|
|
|
10
10
|
export const validateToken = async (token: string, workspaceId?: string) => {
|
|
11
|
-
const options = { endpoint:
|
|
11
|
+
const options = { endpoint: resolveAuth().server }
|
|
12
12
|
|
|
13
13
|
if (isPersonalAccessToken(token)) {
|
|
14
14
|
const seam = workspaceId
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { mkdir, readFile, rename, writeFile } from 'node:fs/promises'
|
|
2
|
+
import { dirname, join } from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
|
|
5
|
+
import type { Blueprint } from '@seamapi/blueprint'
|
|
6
|
+
|
|
7
|
+
import { seamapiBlueprintVersion } from 'lib/version.js'
|
|
8
|
+
|
|
9
|
+
const cacheFileName = 'blueprint.json'
|
|
10
|
+
const updateCheckInterval = 24 * 60 * 60 * 1000
|
|
11
|
+
|
|
12
|
+
export interface BlueprintCache {
|
|
13
|
+
blueprintVersion: string
|
|
14
|
+
typesVersion: string
|
|
15
|
+
checkedAt: string
|
|
16
|
+
blueprint: Blueprint
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const getCacheFile = (cacheDirectory: string): string =>
|
|
20
|
+
join(cacheDirectory, cacheFileName)
|
|
21
|
+
|
|
22
|
+
export const readCache = async (
|
|
23
|
+
file: string,
|
|
24
|
+
): Promise<BlueprintCache | null> => {
|
|
25
|
+
try {
|
|
26
|
+
const contents = await readFile(file, 'utf8')
|
|
27
|
+
const cache = JSON.parse(contents) as unknown
|
|
28
|
+
if (!isBlueprintCache(cache)) return null
|
|
29
|
+
return cache
|
|
30
|
+
} catch {
|
|
31
|
+
return null
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const writeCache = async (
|
|
36
|
+
file: string,
|
|
37
|
+
cache: BlueprintCache,
|
|
38
|
+
): Promise<void> => {
|
|
39
|
+
const temporaryFile = `${file}.tmp`
|
|
40
|
+
await mkdir(dirname(file), { recursive: true })
|
|
41
|
+
await writeFile(temporaryFile, `${JSON.stringify(cache)}\n`, 'utf8')
|
|
42
|
+
await rename(temporaryFile, file)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const isUpdateCheckDue = (checkedAt: string): boolean => {
|
|
46
|
+
const checkedAtTime = Date.parse(checkedAt)
|
|
47
|
+
if (Number.isNaN(checkedAtTime)) return true
|
|
48
|
+
return Date.now() - checkedAtTime > updateCheckInterval
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The blueprint version the cache is keyed on: a cached blueprint built by a
|
|
53
|
+
* different @seamapi/blueprint version is stale even for the same types.
|
|
54
|
+
*/
|
|
55
|
+
export const getBlueprintVersion = async (): Promise<string> => {
|
|
56
|
+
if (seamapiBlueprintVersion !== '0.0.0') return seamapiBlueprintVersion
|
|
57
|
+
|
|
58
|
+
// The version is only injected when the package is packed, so a
|
|
59
|
+
// development checkout reads the pinned version from package.json
|
|
60
|
+
// to keep invalidating the cache on version changes as expected.
|
|
61
|
+
const pkg = await findOwnPackageJson()
|
|
62
|
+
return pkg?.dependencies?.['@seamapi/blueprint'] ?? seamapiBlueprintVersion
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const findOwnPackageJson = async (): Promise<{
|
|
66
|
+
dependencies?: Record<string, string>
|
|
67
|
+
} | null> => {
|
|
68
|
+
let directory = dirname(fileURLToPath(import.meta.url))
|
|
69
|
+
while (true) {
|
|
70
|
+
try {
|
|
71
|
+
const contents = await readFile(join(directory, 'package.json'), 'utf8')
|
|
72
|
+
const pkg = JSON.parse(contents) as {
|
|
73
|
+
name?: string
|
|
74
|
+
dependencies?: Record<string, string>
|
|
75
|
+
}
|
|
76
|
+
if (pkg.name === '@seamapi/cli') return pkg
|
|
77
|
+
} catch {
|
|
78
|
+
// Keep walking up until a package.json for this package is found.
|
|
79
|
+
}
|
|
80
|
+
const parent = dirname(directory)
|
|
81
|
+
if (parent === directory) return null
|
|
82
|
+
directory = parent
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const isBlueprintCache = (cache: unknown): cache is BlueprintCache => {
|
|
87
|
+
if (cache == null || typeof cache !== 'object') return false
|
|
88
|
+
const { blueprintVersion, typesVersion, checkedAt, blueprint } =
|
|
89
|
+
cache as Record<string, unknown>
|
|
90
|
+
return (
|
|
91
|
+
typeof blueprintVersion === 'string' &&
|
|
92
|
+
typeof typesVersion === 'string' &&
|
|
93
|
+
typeof checkedAt === 'string' &&
|
|
94
|
+
blueprint != null &&
|
|
95
|
+
typeof blueprint === 'object'
|
|
96
|
+
)
|
|
97
|
+
}
|
|
@@ -1,5 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import type { ApiBlueprint } from './index.js'
|
|
2
|
+
|
|
3
|
+
export const getCommandBlueprintDef = (
|
|
4
|
+
cmd: string[],
|
|
5
|
+
helpers: { blueprint: ApiBlueprint },
|
|
6
|
+
) => {
|
|
7
|
+
const path = `/${cmd.join('/').replace(/-/g, '_')}`
|
|
8
|
+
const def = helpers.blueprint.routes
|
|
9
|
+
.flatMap((route) => route.endpoints)
|
|
10
|
+
.find((endpoint) => endpoint.path === path)
|
|
11
|
+
if (!def) {
|
|
12
|
+
throw new Error(`No definition for path ${path}`)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return def
|
|
16
|
+
}
|
|
3
17
|
|
|
4
18
|
/**
|
|
5
19
|
* The top level response key documented for a command,
|
|
@@ -10,7 +24,7 @@ import type { ContextHelpers } from './types.js'
|
|
|
10
24
|
*/
|
|
11
25
|
export const getResponseKey = (
|
|
12
26
|
command: string[],
|
|
13
|
-
ctx:
|
|
27
|
+
ctx: { blueprint: ApiBlueprint },
|
|
14
28
|
): string | null => {
|
|
15
29
|
let endpoint
|
|
16
30
|
try {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Blueprint } from '@seamapi/blueprint'
|
|
2
|
+
|
|
3
|
+
import { getBlueprint } from './source-npm.js'
|
|
4
|
+
import { createRemoteBlueprint } from './source-remote.js'
|
|
5
|
+
|
|
6
|
+
export type ApiBlueprint = Blueprint
|
|
7
|
+
|
|
8
|
+
export interface GetApiBlueprintOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Build from the OpenAPI document the configured server is currently
|
|
11
|
+
* running, instead of the published npm types.
|
|
12
|
+
*/
|
|
13
|
+
useRemoteDefinitions?: boolean
|
|
14
|
+
/** Force an update of the cached Seam API definitions. */
|
|
15
|
+
update?: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const getApiBlueprint = async ({
|
|
19
|
+
useRemoteDefinitions = false,
|
|
20
|
+
update = false,
|
|
21
|
+
}: GetApiBlueprintOptions = {}): Promise<ApiBlueprint> => {
|
|
22
|
+
// Remote definitions describe whatever the server is currently running, so
|
|
23
|
+
// build them directly from the server's OpenAPI document.
|
|
24
|
+
if (useRemoteDefinitions) return await createRemoteBlueprint()
|
|
25
|
+
|
|
26
|
+
return await getBlueprint({ update })
|
|
27
|
+
}
|