@knpkv/alchemy-console 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +185 -2
- package/dist/api.d.ts +126 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +62 -0
- package/dist/api.js.map +1 -0
- package/dist/client/bootstrap.js +2834 -0
- package/dist/discovery.d.ts +12 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +39 -0
- package/dist/discovery.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/files.d.ts +9 -0
- package/dist/internal/files.d.ts.map +1 -0
- package/dist/internal/files.js +34 -0
- package/dist/internal/files.js.map +1 -0
- package/dist/local-state.d.ts +8 -0
- package/dist/local-state.d.ts.map +1 -0
- package/dist/local-state.js +53 -0
- package/dist/local-state.js.map +1 -0
- package/dist/main.d.ts +3 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +39 -0
- package/dist/main.js.map +1 -0
- package/dist/profiles.d.ts +24 -0
- package/dist/profiles.d.ts.map +1 -0
- package/dist/profiles.js +82 -0
- package/dist/profiles.js.map +1 -0
- package/dist/s3-live.d.ts +13 -0
- package/dist/s3-live.d.ts.map +1 -0
- package/dist/s3-live.js +112 -0
- package/dist/s3-live.js.map +1 -0
- package/dist/s3-reader.d.ts +46 -0
- package/dist/s3-reader.d.ts.map +1 -0
- package/dist/s3-reader.js +10 -0
- package/dist/s3-reader.js.map +1 -0
- package/dist/s3-state.d.ts +21 -0
- package/dist/s3-state.d.ts.map +1 -0
- package/dist/s3-state.js +129 -0
- package/dist/s3-state.js.map +1 -0
- package/dist/schema.d.ts +43 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +27 -0
- package/dist/schema.js.map +1 -0
- package/dist/server/Bootstrap.d.ts +4 -0
- package/dist/server/Bootstrap.d.ts.map +1 -0
- package/dist/server/Bootstrap.js +12 -0
- package/dist/server/Bootstrap.js.map +1 -0
- package/dist/server/Catalog.d.ts +27 -0
- package/dist/server/Catalog.d.ts.map +1 -0
- package/dist/server/Catalog.js +83 -0
- package/dist/server/Catalog.js.map +1 -0
- package/dist/server/Config.d.ts +17 -0
- package/dist/server/Config.d.ts.map +1 -0
- package/dist/server/Config.js +23 -0
- package/dist/server/Config.js.map +1 -0
- package/dist/server/ControlSocket.d.ts +19 -0
- package/dist/server/ControlSocket.d.ts.map +1 -0
- package/dist/server/ControlSocket.js +147 -0
- package/dist/server/ControlSocket.js.map +1 -0
- package/dist/server/HttpApplication.d.ts +7 -0
- package/dist/server/HttpApplication.d.ts.map +1 -0
- package/dist/server/HttpApplication.js +46 -0
- package/dist/server/HttpApplication.js.map +1 -0
- package/dist/server/Launch.d.ts +7 -0
- package/dist/server/Launch.d.ts.map +1 -0
- package/dist/server/Launch.js +17 -0
- package/dist/server/Launch.js.map +1 -0
- package/dist/server/OwnerSession.d.ts +9 -0
- package/dist/server/OwnerSession.d.ts.map +1 -0
- package/dist/server/OwnerSession.js +27 -0
- package/dist/server/OwnerSession.js.map +1 -0
- package/dist/server/Server.d.ts +6 -0
- package/dist/server/Server.d.ts.map +1 -0
- package/dist/server/Server.js +22 -0
- package/dist/server/Server.js.map +1 -0
- package/dist/server/Sources.d.ts +12 -0
- package/dist/server/Sources.d.ts.map +1 -0
- package/dist/server/Sources.js +118 -0
- package/dist/server/Sources.js.map +1 -0
- package/dist/sso-credentials.d.ts +17 -0
- package/dist/sso-credentials.d.ts.map +1 -0
- package/dist/sso-credentials.js +43 -0
- package/dist/sso-credentials.js.map +1 -0
- package/dist/state.d.ts +15 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +45 -0
- package/dist/state.js.map +1 -0
- package/dist/version.d.ts +5 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +61 -0
- package/dist/version.js.map +1 -0
- package/package.json +60 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 knpkv
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,186 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @knpkv/alchemy-console
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Run `alchemy-console` to serve the read-only API on `127.0.0.1:3114` and open a
|
|
4
|
+
one-time login link. `alchemy-console login` behaves the same way: it asks an
|
|
5
|
+
existing server for a fresh link, or starts one in the foreground. `--no-open`
|
|
6
|
+
prints the link without opening a browser. CI, non-interactive stdout and
|
|
7
|
+
`BROWSER=none` also skip opening. `--port` selects a port from 1024 through 65535.
|
|
8
|
+
`alchemy-console serve` always prints only and runs until SIGINT or SIGTERM.
|
|
9
|
+
Closing a browser tab leaves the foreground server running.
|
|
10
|
+
|
|
11
|
+
The current browser page performs login, then navigates to JSON at `/api/stacks`.
|
|
12
|
+
Stack screens will follow the approved design references in a separate change.
|
|
13
|
+
|
|
14
|
+
Configuration is read at startup:
|
|
15
|
+
|
|
16
|
+
| Variable | Default | Boundary |
|
|
17
|
+
| ---------------------- | ---------------------------------------------- | ------------------------------------- |
|
|
18
|
+
| `ALCHEMY_CONSOLE_ROOT` | `Work/dev` under the user's home | Server-private project discovery path |
|
|
19
|
+
| `AWS_CONFIG_FILE` | `.aws/config` under the user's home | Server-private SSO configuration path |
|
|
20
|
+
| `ALCHEMY_CONSOLE_HOME` | `.cache/alchemy-console` under the user's home | Server-private control directory |
|
|
21
|
+
|
|
22
|
+
Login reuse is per port. A reused server keeps its startup configuration. The
|
|
23
|
+
control directory must be owned by the current user with mode `0700`; each
|
|
24
|
+
`serve-<port>.sock` is `0600`. Symlinks, foreign owners and pre-existing stale
|
|
25
|
+
sockets fail explicitly. For an abandoned socket, confirm its server has stopped
|
|
26
|
+
before removing that entry and restarting. The control channel currently requires
|
|
27
|
+
POSIX ownership and Unix sockets.
|
|
28
|
+
|
|
29
|
+
## Authenticated API
|
|
30
|
+
|
|
31
|
+
The client-safe contracts are exported from `@knpkv/alchemy-console/api` and may be
|
|
32
|
+
used with Effect's `HttpApiClient`. Both endpoints require the process's owner
|
|
33
|
+
cookie, and all responses are private and uncacheable:
|
|
34
|
+
|
|
35
|
+
The cookie name includes the bound listener port, so sessions on different ports
|
|
36
|
+
can coexist in one browser. Both API boundaries select that listener's cookie.
|
|
37
|
+
Cookies still share the loopback host and travel across ports; the name separates
|
|
38
|
+
sessions and does not provide port confidentiality. The API middleware resolves
|
|
39
|
+
the name at runtime rather than declaring a fixed OpenAPI cookie scheme.
|
|
40
|
+
|
|
41
|
+
| Route | Result |
|
|
42
|
+
| --------------------- | ------------------------------------------------------ |
|
|
43
|
+
| `GET /api/stacks` | Stack summaries and explicit local/S3 discovery issues |
|
|
44
|
+
| `GET /api/stacks/:id` | Current normalized resource state for a listed stack |
|
|
45
|
+
|
|
46
|
+
Listing runs discovery on demand, without polling or schedules. IDs are random,
|
|
47
|
+
process-scoped identifiers with no encoded locator. They remain stable across
|
|
48
|
+
scans while a stack remains present, then expire when it disappears or the server
|
|
49
|
+
restarts. They are normalized, authenticated client-visible identifiers; the
|
|
50
|
+
server never persists them. Do not emit them or stack metadata through public
|
|
51
|
+
routes or telemetry. Browsers must not persist private provider coordinates.
|
|
52
|
+
|
|
53
|
+
Discovery reports sanitized reason codes for failed projects, SSO profiles and
|
|
54
|
+
buckets while continuing other sources. A discovered entrypoint with no state
|
|
55
|
+
cannot supply an app or stage and contributes no stack row. Remote resource
|
|
56
|
+
counts and Alchemy versions remain unknown until the corresponding state or
|
|
57
|
+
lockfile establishes them. Discovery is capped at 1000 projects/stacks, 100 SSO
|
|
58
|
+
profiles, 1000 candidate buckets per profile and two minutes per list request.
|
|
59
|
+
|
|
60
|
+
The one public authentication route, `POST /auth/bootstrap`, exchanges a code
|
|
61
|
+
from the URL fragment for an HttpOnly, SameSite=Strict owner cookie. Codes are
|
|
62
|
+
single-use and expire after 60 seconds; a fresh mint replaces an unspent code.
|
|
63
|
+
The page removes the fragment before exchanging it. Codes are minted and
|
|
64
|
+
announced only after the authenticated HTTP listener is ready. Credential URLs
|
|
65
|
+
go only to stdout and the browser opener, never logs or telemetry. Session tokens
|
|
66
|
+
remain in the server and HttpOnly cookie. Every unsafe API method is refused,
|
|
67
|
+
including apply and destroy. Reads reject foreign origins and cross-site browser
|
|
68
|
+
requests. Listener and request Host are restricted to the bound loopback address.
|
|
69
|
+
|
|
70
|
+
## Data readers
|
|
71
|
+
|
|
72
|
+
The root export provides project discovery, lockfile version lookup, local
|
|
73
|
+
state metadata and SSO/S3 readers for server-side consumers.
|
|
74
|
+
|
|
75
|
+
`discoverProjects(directory)` scans an explicit root for `alchemy.run.ts` without
|
|
76
|
+
importing or executing it. It skips `.git`, `node_modules`, `.alchemy`, `.direnv`,
|
|
77
|
+
`.cache`, and `dist`, deduplicates directory aliases, and rejects symlinks outside
|
|
78
|
+
the root. Directory errors fail the scan rather than hiding inaccessible projects.
|
|
79
|
+
|
|
80
|
+
`readAlchemyVersion(projectDirectory, searchRoot)` reads the nearest pnpm or Bun
|
|
81
|
+
text lockfile within the root. pnpm resolves the project's exact workspace
|
|
82
|
+
importer; Bun reads the resolved `alchemy` package tuple. Missing lockfiles or an
|
|
83
|
+
unresolved Alchemy version return `null`. Invalid lockfiles fail explicitly.
|
|
84
|
+
|
|
85
|
+
`readLocalState(directory, alchemyVersion)` reads
|
|
86
|
+
`.alchemy/state/<app>/<stage>/<encoded-fqn>.json` for beta.74 and beta.77. Other
|
|
87
|
+
versions, including unknown versions, fail with `unsupported-version`. Resource
|
|
88
|
+
metadata includes FQN, logical ID, type, status, and namespace parent. Action rows
|
|
89
|
+
are validated and excluded. Stack output and temporary files are ignored. Missing
|
|
90
|
+
state returns `available: false`; an existing empty stage returns an empty resource
|
|
91
|
+
list with `available: true`. JSON names must match the stored FQN encoding.
|
|
92
|
+
|
|
93
|
+
## SSO and S3
|
|
94
|
+
|
|
95
|
+
`discoverSsoProfiles(configFile)` reads the explicitly supplied AWS config file.
|
|
96
|
+
It resolves modern `sso_session` references and legacy inline SSO settings,
|
|
97
|
+
including the default profile. Static keys, credential processes, assumed-role
|
|
98
|
+
chains, and service sections are excluded. Missing config returns an empty list;
|
|
99
|
+
incomplete or conflicting SSO configuration fails with a safe tagged reason.
|
|
100
|
+
The executable will supply the local config path; this layer does not choose an
|
|
101
|
+
ambient profile or execute login commands.
|
|
102
|
+
|
|
103
|
+
`s3ReaderLayer(profile)` uses `ssoCredentialsLayer()` and an Effect `HttpClient`.
|
|
104
|
+
Credentials come exclusively from the selected SSO identity and cached local
|
|
105
|
+
session. There is no environment, static-key, instance-metadata, or process
|
|
106
|
+
credential fallback. The SDK may refresh an existing SSO session in its local
|
|
107
|
+
token cache. Credentials remain redacted in server memory and are used only for
|
|
108
|
+
request signing. The profile supplies the SSO portal region; listed bucket regions
|
|
109
|
+
route S3 requests. Object requests include the selected account as
|
|
110
|
+
`ExpectedBucketOwner`. S3 transport tracing is disabled so private request URLs
|
|
111
|
+
and signing headers cannot enter telemetry.
|
|
112
|
+
|
|
113
|
+
`discoverS3Buckets()` paginates the selected account's owned buckets and selects
|
|
114
|
+
names containing `alchemy-state`. A caller may supply another naming predicate
|
|
115
|
+
or explicit server-private bucket locators. Listing owned buckets cannot discover
|
|
116
|
+
every cross-account bucket that a role might be allowed to read. Denied access or
|
|
117
|
+
expired sessions fail explicitly; they do not produce an empty account catalog.
|
|
118
|
+
An API can report these failures per profile while continuing other profiles.
|
|
119
|
+
|
|
120
|
+
`discoverS3Stacks(bucket)` recognizes `<optional-prefix><app>/<stage>/<encoded-fqn>.json`
|
|
121
|
+
keys. `listS3Versions(bucket, prefix)` returns persisted object version coordinates
|
|
122
|
+
and deletion markers. Both S3 continuation markers are retained; repeated markers
|
|
123
|
+
fail. `readS3State(stack, alchemyVersion?)` reads each latest non-deleted resource
|
|
124
|
+
using its listed version ID. Stack outputs and temporary files are excluded.
|
|
125
|
+
`readS3Resource(stack, key, versionId)` reads one historical version after checking
|
|
126
|
+
that the key belongs to that stack. JSON names must match the resource FQN.
|
|
127
|
+
Latest deletion markers never resurrect an older resource. Null versions from
|
|
128
|
+
unversioned or suspended buckets are supported. Multiple resource reads are not
|
|
129
|
+
an atomic snapshot of a deployment.
|
|
130
|
+
|
|
131
|
+
Remote state alone does not prove the installed Alchemy version or last successful
|
|
132
|
+
deploy. Both remain `null` unless a matching lockfile supplies the version.
|
|
133
|
+
Known beta.74/beta.77 resource shapes are validated even when remote version is
|
|
134
|
+
unknown; an explicitly supplied unsupported version fails. The adapter imports
|
|
135
|
+
only `ListBuckets`, `ListObjectVersions`, and `GetObject`. It never builds Alchemy's
|
|
136
|
+
state-store layer, which can create or reconfigure buckets.
|
|
137
|
+
|
|
138
|
+
Read-only IAM access needs `s3:ListAllMyBuckets`, `s3:ListBucketVersions`, and
|
|
139
|
+
`s3:GetObjectVersion` for version-pinned reads. No write policy is required. See the AWS
|
|
140
|
+
[version listing](https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectVersions.html)
|
|
141
|
+
and [object reading](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObject.html)
|
|
142
|
+
references. No live AWS or Alchemy stack is used by the tests.
|
|
143
|
+
|
|
144
|
+
The flat resource list retains namespace parents, including parents without their
|
|
145
|
+
own resource row. A consumer can group these into a tree. Provider IDs, account,
|
|
146
|
+
region, console links, and last deploy are currently `null`. Local file timestamps
|
|
147
|
+
do not prove a successful deployment. Readers do not invent missing metadata.
|
|
148
|
+
|
|
149
|
+
## Private data and redaction
|
|
150
|
+
|
|
151
|
+
Discovered directories and entrypoints are server-private filesystem locators.
|
|
152
|
+
Keep them in server memory; do not persist them to browser storage or emit them
|
|
153
|
+
through HTTP, logs, telemetry, or public error messages. The normalized stack and
|
|
154
|
+
resource summaries may cross an authenticated owner API boundary. They must never
|
|
155
|
+
appear on unauthenticated routes or public telemetry.
|
|
156
|
+
|
|
157
|
+
Persisted Alchemy state can contain plaintext secret payloads inside
|
|
158
|
+
`__redacted__` marker objects. This package never invokes Alchemy's reviver or
|
|
159
|
+
decrypts anything. It discards props, attrs, old state, action inputs, and outputs.
|
|
160
|
+
Marker objects in metadata become `<redacted>`, including sibling fields. Parsing
|
|
161
|
+
errors include only a tagged reason, never raw contents or parser diagnostics.
|
|
162
|
+
Reads are capped at 8 MiB per file and directory listings at 50,000 entries.
|
|
163
|
+
|
|
164
|
+
SSO profiles retain server-private names, config paths, account/role selectors,
|
|
165
|
+
session names, start URLs, and routing regions. S3 bucket names, state prefixes,
|
|
166
|
+
object keys, version IDs, and pagination markers are also server-private. Their
|
|
167
|
+
persisted representation is AWS config or S3 object coordinates; console code
|
|
168
|
+
holds them in server memory only. They must not cross HTTP, browser storage,
|
|
169
|
+
logs, telemetry, or public errors. Only normalized `StackState` and
|
|
170
|
+
`ResourceSummary` fields may cross the authenticated owner API. Credential-bearing
|
|
171
|
+
`SsoCredentials` results remain solely inside the signing boundary. S3 reads have
|
|
172
|
+
the same 8 MiB payload cap and a 50,000-entry / 100-page listing cap.
|
|
173
|
+
|
|
174
|
+
Cloudflare profiles will also remain server-side. The UI will never apply or
|
|
175
|
+
destroy. Plan diffs remain on demand, with no scheduled execution.
|
|
176
|
+
|
|
177
|
+
## Format references
|
|
178
|
+
|
|
179
|
+
Synthetic test states follow the pinned upstream
|
|
180
|
+
[resource format](https://github.com/alchemy-run/alchemy/blob/v2.0.0-beta.77/packages/alchemy/src/State/ResourceState.ts),
|
|
181
|
+
[local layout](https://github.com/alchemy-run/alchemy/blob/v2.0.0-beta.77/packages/alchemy/src/State/LocalState.ts),
|
|
182
|
+
and [state encoding](https://github.com/alchemy-run/alchemy/blob/v2.0.0-beta.77/packages/alchemy/src/State/StateEncoding.ts).
|
|
183
|
+
No real stack, account, bucket, credential, or project path is included in fixtures.
|
|
184
|
+
|
|
185
|
+
Run `pnpm --filter @knpkv/alchemy-console test` for the focused fixture tests.
|
|
186
|
+
Build workspace dependencies before the package's `check` script.
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/** Client-safe contracts. IDs and metadata cross only the authenticated owner API, never public telemetry. */
|
|
2
|
+
import { Schema } from "effect";
|
|
3
|
+
import { HttpApi, HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware } from "effect/http-api";
|
|
4
|
+
/** Random, process-scoped identity; contains no encoded provider locator. Not persisted by the server. */
|
|
5
|
+
export declare const StackId: Schema.String;
|
|
6
|
+
export type StackId = typeof StackId.Type;
|
|
7
|
+
export declare const SourceFailureReason: Schema.Literals<readonly ["io", "invalid-state", "invalid-lockfile", "unsupported-version", "limit", "outside-root", "invalid-config", "authentication", "denied", "not-found", "provider", "invalid-response", "invalid-source"]>;
|
|
8
|
+
export declare const DiscoveryIssue: Schema.Struct<{
|
|
9
|
+
readonly source: Schema.Literals<readonly ["local", "s3"]>;
|
|
10
|
+
readonly reason: Schema.Literals<readonly ["io", "invalid-state", "invalid-lockfile", "unsupported-version", "limit", "outside-root", "invalid-config", "authentication", "denied", "not-found", "provider", "invalid-response", "invalid-source"]>;
|
|
11
|
+
}>;
|
|
12
|
+
export type DiscoveryIssue = typeof DiscoveryIssue.Type;
|
|
13
|
+
export declare const StackSummary: Schema.Struct<{
|
|
14
|
+
readonly id: Schema.String;
|
|
15
|
+
readonly app: Schema.NonEmptyString;
|
|
16
|
+
readonly stage: Schema.NonEmptyString;
|
|
17
|
+
readonly backend: Schema.Literals<readonly ["local", "s3"]>;
|
|
18
|
+
readonly alchemyVersion: Schema.NullOr<Schema.String>;
|
|
19
|
+
readonly lastDeploy: Schema.NullOr<Schema.String>;
|
|
20
|
+
readonly resourceCount: Schema.NullOr<Schema.Int>;
|
|
21
|
+
}>;
|
|
22
|
+
export type StackSummary = typeof StackSummary.Type;
|
|
23
|
+
export declare const StackList: Schema.Struct<{
|
|
24
|
+
readonly stacks: Schema.$Array<Schema.Struct<{
|
|
25
|
+
readonly id: Schema.String;
|
|
26
|
+
readonly app: Schema.NonEmptyString;
|
|
27
|
+
readonly stage: Schema.NonEmptyString;
|
|
28
|
+
readonly backend: Schema.Literals<readonly ["local", "s3"]>;
|
|
29
|
+
readonly alchemyVersion: Schema.NullOr<Schema.String>;
|
|
30
|
+
readonly lastDeploy: Schema.NullOr<Schema.String>;
|
|
31
|
+
readonly resourceCount: Schema.NullOr<Schema.Int>;
|
|
32
|
+
}>>;
|
|
33
|
+
readonly issues: Schema.$Array<Schema.Struct<{
|
|
34
|
+
readonly source: Schema.Literals<readonly ["local", "s3"]>;
|
|
35
|
+
readonly reason: Schema.Literals<readonly ["io", "invalid-state", "invalid-lockfile", "unsupported-version", "limit", "outside-root", "invalid-config", "authentication", "denied", "not-found", "provider", "invalid-response", "invalid-source"]>;
|
|
36
|
+
}>>;
|
|
37
|
+
}>;
|
|
38
|
+
export type StackList = typeof StackList.Type;
|
|
39
|
+
export declare const StackDetail: Schema.Struct<{
|
|
40
|
+
readonly id: Schema.String;
|
|
41
|
+
readonly state: Schema.Struct<{
|
|
42
|
+
readonly app: Schema.NonEmptyString;
|
|
43
|
+
readonly stage: Schema.NonEmptyString;
|
|
44
|
+
readonly backend: Schema.Literals<readonly ["local", "s3"]>;
|
|
45
|
+
readonly alchemyVersion: Schema.NullOr<Schema.String>;
|
|
46
|
+
readonly lastDeploy: Schema.NullOr<Schema.String>;
|
|
47
|
+
readonly resources: Schema.$Array<Schema.Struct<{
|
|
48
|
+
readonly fqn: Schema.NonEmptyString;
|
|
49
|
+
readonly logicalId: Schema.NonEmptyString;
|
|
50
|
+
readonly resourceType: Schema.NonEmptyString;
|
|
51
|
+
readonly status: Schema.Literals<readonly ["creating", "created", "updating", "updated", "deleting", "replacing", "replaced"]>;
|
|
52
|
+
readonly parent: Schema.NullOr<Schema.String>;
|
|
53
|
+
readonly providerId: Schema.NullOr<Schema.String>;
|
|
54
|
+
readonly account: Schema.NullOr<Schema.String>;
|
|
55
|
+
readonly region: Schema.NullOr<Schema.String>;
|
|
56
|
+
readonly consoleUrl: Schema.NullOr<Schema.String>;
|
|
57
|
+
}>>;
|
|
58
|
+
}>;
|
|
59
|
+
}>;
|
|
60
|
+
export type StackDetail = typeof StackDetail.Type;
|
|
61
|
+
declare const CatalogReadError_base: Schema.Class<CatalogReadError, Schema.TaggedStruct<"CatalogReadError", {
|
|
62
|
+
readonly reason: Schema.Literals<readonly ["io", "invalid-state", "invalid-lockfile", "unsupported-version", "limit", "outside-root", "invalid-config", "authentication", "denied", "not-found", "provider", "invalid-response", "invalid-source"]>;
|
|
63
|
+
}>, import("effect/Cause").YieldableError>;
|
|
64
|
+
export declare class CatalogReadError extends CatalogReadError_base {
|
|
65
|
+
}
|
|
66
|
+
declare const StackNotFound_base: Schema.Class<StackNotFound, Schema.TaggedStruct<"StackNotFound", {}>, import("effect/Cause").YieldableError>;
|
|
67
|
+
export declare class StackNotFound extends StackNotFound_base {
|
|
68
|
+
}
|
|
69
|
+
declare const Unauthorized_base: Schema.Class<Unauthorized, Schema.TaggedStruct<"Unauthorized", {}>, import("effect/Cause").YieldableError>;
|
|
70
|
+
export declare class Unauthorized extends Unauthorized_base {
|
|
71
|
+
}
|
|
72
|
+
declare const Forbidden_base: Schema.Class<Forbidden, Schema.TaggedStruct<"Forbidden", {}>, import("effect/Cause").YieldableError>;
|
|
73
|
+
export declare class Forbidden extends Forbidden_base {
|
|
74
|
+
}
|
|
75
|
+
declare const OwnerAuth_base: HttpApiMiddleware.ServiceClass<OwnerAuth, "@knpkv/alchemy-console/OwnerAuth", {
|
|
76
|
+
requires: never;
|
|
77
|
+
provides: never;
|
|
78
|
+
error: readonly [typeof Unauthorized, typeof Forbidden];
|
|
79
|
+
clientError: never;
|
|
80
|
+
requiredForClient: false;
|
|
81
|
+
security: never;
|
|
82
|
+
}, HttpApiMiddleware.HttpApiMiddleware<never, readonly [typeof Unauthorized, typeof Forbidden], never>>;
|
|
83
|
+
/** The server resolves its listener-specific cookie name at request time. */
|
|
84
|
+
export declare class OwnerAuth extends OwnerAuth_base {
|
|
85
|
+
}
|
|
86
|
+
declare const AlchemyConsoleApi_base: HttpApi.HttpApi<"AlchemyConsoleApi", HttpApiGroup.HttpApiGroup<"stacks", HttpApiEndpoint.HttpApiEndpoint<"list", "GET", "/api/stacks", never, never, never, never, Schema.toCodecJson<Schema.Struct<{
|
|
87
|
+
readonly stacks: Schema.$Array<Schema.Struct<{
|
|
88
|
+
readonly id: Schema.String;
|
|
89
|
+
readonly app: Schema.NonEmptyString;
|
|
90
|
+
readonly stage: Schema.NonEmptyString;
|
|
91
|
+
readonly backend: Schema.Literals<readonly ["local", "s3"]>;
|
|
92
|
+
readonly alchemyVersion: Schema.NullOr<Schema.String>;
|
|
93
|
+
readonly lastDeploy: Schema.NullOr<Schema.String>;
|
|
94
|
+
readonly resourceCount: Schema.NullOr<Schema.Int>;
|
|
95
|
+
}>>;
|
|
96
|
+
readonly issues: Schema.$Array<Schema.Struct<{
|
|
97
|
+
readonly source: Schema.Literals<readonly ["local", "s3"]>;
|
|
98
|
+
readonly reason: Schema.Literals<readonly ["io", "invalid-state", "invalid-lockfile", "unsupported-version", "limit", "outside-root", "invalid-config", "authentication", "denied", "not-found", "provider", "invalid-response", "invalid-source"]>;
|
|
99
|
+
}>>;
|
|
100
|
+
}>>, Schema.toCodecJson<typeof CatalogReadError>, OwnerAuth, never> | HttpApiEndpoint.HttpApiEndpoint<"get", "GET", "/api/stacks/:id", Schema.toCodecStringTree<Schema.Struct<{
|
|
101
|
+
readonly id: Schema.String;
|
|
102
|
+
}>>, never, never, never, Schema.toCodecJson<Schema.Struct<{
|
|
103
|
+
readonly id: Schema.String;
|
|
104
|
+
readonly state: Schema.Struct<{
|
|
105
|
+
readonly app: Schema.NonEmptyString;
|
|
106
|
+
readonly stage: Schema.NonEmptyString;
|
|
107
|
+
readonly backend: Schema.Literals<readonly ["local", "s3"]>;
|
|
108
|
+
readonly alchemyVersion: Schema.NullOr<Schema.String>;
|
|
109
|
+
readonly lastDeploy: Schema.NullOr<Schema.String>;
|
|
110
|
+
readonly resources: Schema.$Array<Schema.Struct<{
|
|
111
|
+
readonly fqn: Schema.NonEmptyString;
|
|
112
|
+
readonly logicalId: Schema.NonEmptyString;
|
|
113
|
+
readonly resourceType: Schema.NonEmptyString;
|
|
114
|
+
readonly status: Schema.Literals<readonly ["creating", "created", "updating", "updated", "deleting", "replacing", "replaced"]>;
|
|
115
|
+
readonly parent: Schema.NullOr<Schema.String>;
|
|
116
|
+
readonly providerId: Schema.NullOr<Schema.String>;
|
|
117
|
+
readonly account: Schema.NullOr<Schema.String>;
|
|
118
|
+
readonly region: Schema.NullOr<Schema.String>;
|
|
119
|
+
readonly consoleUrl: Schema.NullOr<Schema.String>;
|
|
120
|
+
}>>;
|
|
121
|
+
}>;
|
|
122
|
+
}>>, Schema.toCodecJson<typeof CatalogReadError | typeof StackNotFound>, OwnerAuth, never>, false>>;
|
|
123
|
+
export declare class AlchemyConsoleApi extends AlchemyConsoleApi_base {
|
|
124
|
+
}
|
|
125
|
+
export {};
|
|
126
|
+
//# sourceMappingURL=api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,8GAA8G;AAC9G,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC/B,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAG3F,0GAA0G;AAC1G,eAAO,MAAM,OAAO,eAAuC,CAAA;AAC3D,MAAM,MAAM,OAAO,GAAG,OAAO,OAAO,CAAC,IAAI,CAAA;AAEzC,eAAO,MAAM,mBAAmB,oOAc9B,CAAA;AAEF,eAAO,MAAM,cAAc;;;EAGzB,CAAA;AACF,MAAM,MAAM,cAAc,GAAG,OAAO,cAAc,CAAC,IAAI,CAAA;AAEvD,eAAO,MAAM,YAAY;;;;;;;;EAQvB,CAAA;AACF,MAAM,MAAM,YAAY,GAAG,OAAO,YAAY,CAAC,IAAI,CAAA;AAEnD,eAAO,MAAM,SAAS;;;;;;;;;;;;;;EAA8F,CAAA;AACpH,MAAM,MAAM,SAAS,GAAG,OAAO,SAAS,CAAC,IAAI,CAAA;AAC7C,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;EAAoD,CAAA;AAC5E,MAAM,MAAM,WAAW,GAAG,OAAO,WAAW,CAAC,IAAI,CAAA;;;;AAEjD,qBAAa,gBAAiB,SAAQ,qBAEZ;CAAG;;AAE7B,qBAAa,aAAc,SAAQ,kBAAgF;CAAG;;AAEtH,qBAAa,YAAa,SAAQ,iBAA8E;CAAG;;AAEnH,qBAAa,SAAU,SAAQ,cAAwE;CAAG;;;;;;;;;AAE1G,6EAA6E;AAC7E,qBAAa,SAAU,SAAQ,cAE7B;CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAWL,qBAAa,iBAAkB,SAAQ,sBAAmE;CAAG"}
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/** Client-safe contracts. IDs and metadata cross only the authenticated owner API, never public telemetry. */
|
|
2
|
+
import { Schema } from "effect";
|
|
3
|
+
import { HttpApi, HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware } from "effect/http-api";
|
|
4
|
+
import { StackState } from "./schema.js";
|
|
5
|
+
/** Random, process-scoped identity; contains no encoded provider locator. Not persisted by the server. */
|
|
6
|
+
export const StackId = Schema.String.check(Schema.isUUID());
|
|
7
|
+
export const SourceFailureReason = Schema.Literals([
|
|
8
|
+
"io",
|
|
9
|
+
"invalid-state",
|
|
10
|
+
"invalid-lockfile",
|
|
11
|
+
"unsupported-version",
|
|
12
|
+
"limit",
|
|
13
|
+
"outside-root",
|
|
14
|
+
"invalid-config",
|
|
15
|
+
"authentication",
|
|
16
|
+
"denied",
|
|
17
|
+
"not-found",
|
|
18
|
+
"provider",
|
|
19
|
+
"invalid-response",
|
|
20
|
+
"invalid-source"
|
|
21
|
+
]);
|
|
22
|
+
export const DiscoveryIssue = Schema.Struct({
|
|
23
|
+
source: Schema.Literals(["local", "s3"]),
|
|
24
|
+
reason: SourceFailureReason
|
|
25
|
+
});
|
|
26
|
+
export const StackSummary = Schema.Struct({
|
|
27
|
+
id: StackId,
|
|
28
|
+
app: Schema.NonEmptyString,
|
|
29
|
+
stage: Schema.NonEmptyString,
|
|
30
|
+
backend: Schema.Literals(["local", "s3"]),
|
|
31
|
+
alchemyVersion: Schema.NullOr(Schema.String),
|
|
32
|
+
lastDeploy: Schema.NullOr(Schema.String),
|
|
33
|
+
resourceCount: Schema.NullOr(Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)))
|
|
34
|
+
});
|
|
35
|
+
export const StackList = Schema.Struct({ stacks: Schema.Array(StackSummary), issues: Schema.Array(DiscoveryIssue) });
|
|
36
|
+
export const StackDetail = Schema.Struct({ id: StackId, state: StackState });
|
|
37
|
+
export class CatalogReadError extends Schema.TaggedError()("CatalogReadError", {
|
|
38
|
+
reason: SourceFailureReason
|
|
39
|
+
}, { httpApiStatus: 503 }) {
|
|
40
|
+
}
|
|
41
|
+
export class StackNotFound extends Schema.TaggedError()("StackNotFound", {}, { httpApiStatus: 404 }) {
|
|
42
|
+
}
|
|
43
|
+
export class Unauthorized extends Schema.TaggedError()("Unauthorized", {}, { httpApiStatus: 401 }) {
|
|
44
|
+
}
|
|
45
|
+
export class Forbidden extends Schema.TaggedError()("Forbidden", {}, { httpApiStatus: 403 }) {
|
|
46
|
+
}
|
|
47
|
+
/** The server resolves its listener-specific cookie name at request time. */
|
|
48
|
+
export class OwnerAuth extends HttpApiMiddleware.Service()("@knpkv/alchemy-console/OwnerAuth", {
|
|
49
|
+
error: [Unauthorized, Forbidden]
|
|
50
|
+
}) {
|
|
51
|
+
}
|
|
52
|
+
const stacks = HttpApiGroup.make("stacks")
|
|
53
|
+
.add(HttpApiEndpoint.get("list", "/stacks", { success: StackList, error: CatalogReadError }))
|
|
54
|
+
.add(HttpApiEndpoint.get("get", "/stacks/:id", {
|
|
55
|
+
params: Schema.Struct({ id: StackId }),
|
|
56
|
+
success: StackDetail,
|
|
57
|
+
error: [StackNotFound, CatalogReadError]
|
|
58
|
+
}))
|
|
59
|
+
.prefix("/api");
|
|
60
|
+
export class AlchemyConsoleApi extends HttpApi.make("AlchemyConsoleApi").add(stacks).middleware(OwnerAuth) {
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=api.js.map
|
package/dist/api.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,8GAA8G;AAC9G,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC/B,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAC3F,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAExC,0GAA0G;AAC1G,MAAM,CAAC,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAA;AAG3D,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CAAC,QAAQ,CAAC;IACjD,IAAI;IACJ,eAAe;IACf,kBAAkB;IAClB,qBAAqB;IACrB,OAAO;IACP,cAAc;IACd,gBAAgB;IAChB,gBAAgB;IAChB,QAAQ;IACR,WAAW;IACX,UAAU;IACV,kBAAkB;IAClB,gBAAgB;CACjB,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,CAAC;IAC1C,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACxC,MAAM,EAAE,mBAAmB;CAC5B,CAAC,CAAA;AAGF,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC;IACxC,EAAE,EAAE,OAAO;IACX,GAAG,EAAE,MAAM,CAAC,cAAc;IAC1B,KAAK,EAAE,MAAM,CAAC,cAAc;IAC5B,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACzC,cAAc,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;IAC5C,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;IACxC,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,CAAC;CACjF,CAAC,CAAA;AAGF,MAAM,CAAC,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,EAAE,CAAC,CAAA;AAEpH,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAA;AAG5E,MAAM,OAAO,gBAAiB,SAAQ,MAAM,CAAC,WAAW,EAAoB,CAAC,kBAAkB,EAAE;IAC/F,MAAM,EAAE,mBAAmB;CAC5B,EAAE,EAAE,aAAa,EAAE,GAAG,EAAE,CAAC;CAAG;AAE7B,MAAM,OAAO,aAAc,SAAQ,MAAM,CAAC,WAAW,EAAiB,CAAC,eAAe,EAAE,EAAE,EAAE,EAAE,aAAa,EAAE,GAAG,EAAE,CAAC;CAAG;AAEtH,MAAM,OAAO,YAAa,SAAQ,MAAM,CAAC,WAAW,EAAgB,CAAC,cAAc,EAAE,EAAE,EAAE,EAAE,aAAa,EAAE,GAAG,EAAE,CAAC;CAAG;AAEnH,MAAM,OAAO,SAAU,SAAQ,MAAM,CAAC,WAAW,EAAa,CAAC,WAAW,EAAE,EAAE,EAAE,EAAE,aAAa,EAAE,GAAG,EAAE,CAAC;CAAG;AAE1G,6EAA6E;AAC7E,MAAM,OAAO,SAAU,SAAQ,iBAAiB,CAAC,OAAO,EAAa,CAAC,kCAAkC,EAAE;IACxG,KAAK,EAAE,CAAC,YAAY,EAAE,SAAS,CAAC;CACjC,CAAC;CAAG;AAEL,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC;KACvC,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAC;KAC5F,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,KAAK,EAAE,aAAa,EAAE;IAC7C,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC;IACtC,OAAO,EAAE,WAAW;IACpB,KAAK,EAAE,CAAC,aAAa,EAAE,gBAAgB,CAAC;CACzC,CAAC,CAAC;KACF,MAAM,CAAC,MAAM,CAAC,CAAA;AAEjB,MAAM,OAAO,iBAAkB,SAAQ,OAAO,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC;CAAG"}
|