@alzulejos/laranja-core 0.2.4

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/dist/api.d.ts ADDED
@@ -0,0 +1,274 @@
1
+ /**
2
+ * The wire contract between the laranja CLI and the laranja server.
3
+ *
4
+ * This module is TYPES ONLY (plus a couple of constants) — no runtime logic — so
5
+ * it can be shared verbatim by the CLI and the backend and keep them in lockstep.
6
+ *
7
+ * Auth: every request carries the user's API key in an `x-api-key: <API_KEY>`
8
+ * header. Project-scoped calls (e.g. `/synth`) also send the dashboard project
9
+ * id in an `x-project-id: <projectId>` header. Neither is part of any body type
10
+ * below — identity/context lives in headers, synth inputs in the body.
11
+ *
12
+ * Only the Infra IR crosses the wire — never the user's source code.
13
+ */
14
+ import type { InfraIR } from "./ir.js";
15
+ export declare const API_VERSION = "v1";
16
+ /** Server mounts every route under this prefix (NestJS global prefix + version). */
17
+ export declare const API_PREFIX = "/api/v1";
18
+ /**
19
+ * Shared endpoint paths, so both sides reference the same strings.
20
+ *
21
+ * Deployment reporting is a lifecycle, not a single call: `/synth` opens a
22
+ * deployment row (`INITIATED`), then the client PATCHes status as it deploys
23
+ * (`STARTED` → `SUCCESS`/`FAILED`) and POSTs the resource inventory on success.
24
+ * The `:id` here is the `deploymentId` returned by `/synth`.
25
+ */
26
+ export declare const ENDPOINTS: {
27
+ /** GET — verify the API key + return the user and their projects (used by `laranja init`). */
28
+ readonly me: "/api/v1/me";
29
+ /** POST — create a project (name only); returns the new project. Used by `laranja init`. */
30
+ readonly project: "/api/v1/project";
31
+ /** POST — IR in, CloudFormation template (or CDK files) out; opens the deployment row. */
32
+ readonly synth: "/api/v1/synth";
33
+ /** PATCH — advance a deployment's status (STARTED before AWS, then SUCCESS/FAILED). */
34
+ readonly deployment: (id: string) => string;
35
+ /** POST — report the deployed resource inventory (success only). */
36
+ readonly deploymentResources: (id: string) => string;
37
+ /** POST — open a teardown deployment row (destroy has no `/synth`). */
38
+ readonly deploymentDestroy: "/api/v1/deployment/destory";
39
+ /** POST — read-only synth (returns a template, creates NO deployment row). */
40
+ readonly diff: "/api/v1/diff";
41
+ /** POST — generate a standalone, owned CDK project (paid; server-gated). */
42
+ readonly eject: "/api/v1/eject";
43
+ /** POST — a CLI failure report (free-form), scoped to the user + project. */
44
+ readonly report: "/api/v1/report";
45
+ };
46
+ export type Tier = "free" | "pro" | "max";
47
+ /** What a tier is allowed to do. `-1` means unlimited. */
48
+ export interface Limits {
49
+ maxProjects: number;
50
+ deploysPerDay: number;
51
+ /** Whether `artifact: "cdk"` (eject) is permitted. */
52
+ canEject: boolean;
53
+ }
54
+ /** Response from `GET /v1/me` — lets `laranja init` validate the key + greet the user. */
55
+ export interface MeResponse {
56
+ userId: string;
57
+ displayName: string;
58
+ projects: ProjectGroups;
59
+ }
60
+ /** Projects grouped by ownership, mirroring the dashboard's `GET /project`. */
61
+ export interface ProjectGroups {
62
+ /** Projects the user owns. */
63
+ personal: Project[];
64
+ /** Projects shared with the user by someone else. */
65
+ collaborating: Project[];
66
+ }
67
+ export interface Project {
68
+ id: string;
69
+ name: string;
70
+ /** Detected framework (e.g. "express"); null until a deploy reveals it. */
71
+ framework: string | null;
72
+ }
73
+ /** `POST /v1/project` body — create a project from the CLI (`laranja init`). */
74
+ export interface CreateProjectRequest {
75
+ name: string;
76
+ }
77
+ /** `POST /v1/project` response — the new project's id (write it to config). */
78
+ export interface CreateProjectResponse {
79
+ id: string;
80
+ }
81
+ /** The artifact the client wants from `/synth`. `cdk` requires a paid tier. */
82
+ export type SynthArtifact = "cloudformation" | "cdk";
83
+ /**
84
+ * Per-handler content hash of the client-built zip, keyed by handler id
85
+ * ("http" | cron.id | queue.id). The hash is CDK's own `Asset.assetHash`
86
+ * (a SOURCE fingerprint), computed client-side at bundle time. The server
87
+ * embeds it into the template as the bootstrap-bucket object key (`<hash>.zip`)
88
+ * so the Lambda code reference lines up with where the client's toolkit later
89
+ * uploads the matching zip. Only the hash crosses the wire — never the code.
90
+ */
91
+ export type HandlerAssetHashes = Record<string, string>;
92
+ /**
93
+ * What `/synth` reports back per handler so the client can publish its zip to
94
+ * the exact key the template references.
95
+ */
96
+ export interface HandlerAsset {
97
+ /** Handler id — matches the client's bundled-zip id ("http" | cron.id | queue.id). */
98
+ id: string;
99
+ /** Short label (e.g. "app", or the cron/queue method name). */
100
+ label: string;
101
+ /** Content hash supplied for this handler. */
102
+ hash: string;
103
+ /** S3 object key the template references (in the bootstrap assets bucket). */
104
+ s3Key: string;
105
+ }
106
+ /** `POST /v1/synth` body. */
107
+ export interface SynthRequest {
108
+ /** Project name/slug — scopes deployments + limit accounting on the server. */
109
+ project: string;
110
+ /** Deployment stage, e.g. "dev" / "prod". */
111
+ stage: string;
112
+ /** Which artifact to generate. */
113
+ artifact: SynthArtifact;
114
+ /** The Infra IR — structure only (routes, crons, queues, env, names). */
115
+ ir: InfraIR;
116
+ /** Content hash per handler id, so the template's S3 keys match the client's uploads. */
117
+ assets: HandlerAssetHashes;
118
+ }
119
+ /** A single generated file (used by the `cdk` artifact). */
120
+ export interface GeneratedFile {
121
+ /** Path relative to the output dir, e.g. "lib/my-app-stack.ts". */
122
+ path: string;
123
+ contents: string;
124
+ }
125
+ interface SynthResponseBase {
126
+ /** Opaque id linking this synth to the dashboard timeline; echo it to `/deployments`. */
127
+ deploymentId: string;
128
+ }
129
+ /** Free + paid: the synthesized CloudFormation template the CLI then deploys. */
130
+ export interface CloudFormationSynthResponse extends SynthResponseBase {
131
+ artifact: "cloudformation";
132
+ stackName: string;
133
+ /** CloudFormation template as JSON. */
134
+ template: Record<string, unknown>;
135
+ /** Per-handler asset map (hash → S3 key) the client uploads its zips against. */
136
+ assets: HandlerAsset[];
137
+ }
138
+ /** Paid only: a standalone, editable CDK project (server-side eject). */
139
+ export interface CdkSynthResponse extends SynthResponseBase {
140
+ artifact: "cdk";
141
+ files: GeneratedFile[];
142
+ }
143
+ /**
144
+ * `200` response from `/eject` — the generated standalone CDK project. Like the
145
+ * `cdk` synth response but with NO `deploymentId` (nothing is persisted). The
146
+ * server gates this on the caller's `canEject` entitlement (403 otherwise).
147
+ */
148
+ export interface EjectResponse {
149
+ files: GeneratedFile[];
150
+ }
151
+ /** `200` response from `/synth` — discriminated on `artifact`. */
152
+ export type SynthResponse = CloudFormationSynthResponse | CdkSynthResponse;
153
+ /**
154
+ * `200` response from `/diff` — a read-only synth. Same fields as the
155
+ * CloudFormation synth response but with NO `deploymentId` (nothing is
156
+ * persisted). Fields are optional to mirror the server's `Partial<SynthResponse>`.
157
+ */
158
+ export type DiffResponse = Partial<Pick<CloudFormationSynthResponse, "stackName" | "template" | "assets">>;
159
+ /**
160
+ * A deployment moves through these states. The SERVER owns `INITIATED` (set when
161
+ * `/synth` opens the row); the CLIENT drives the rest via PATCH. Anything stuck
162
+ * in `INITIATED`/`STARTED` past a TTL is treated as abandoned by the server.
163
+ */
164
+ export type DeploymentStatus = "INITIATED" | "STARTED" | "SUCCESS" | "FAILED";
165
+ /**
166
+ * `PATCH /v1/deployment/:id` body sent right after `/synth`, BEFORE touching AWS.
167
+ * `region` travels ONLY on this transition (it's where the deploy will land).
168
+ */
169
+ export interface DeploymentStartedPatch {
170
+ status: "STARTED";
171
+ region: string;
172
+ }
173
+ /**
174
+ * `PATCH /v1/deployment/:id` body sent after the AWS deploy settles. No region.
175
+ * The success handler must be idempotent (a retried PATCH must not error).
176
+ */
177
+ export interface DeploymentOutcomePatch {
178
+ status: "SUCCESS" | "FAILED";
179
+ }
180
+ /** Every client-driven status PATCH body, discriminated on `status`. */
181
+ export type DeploymentPatch = DeploymentStartedPatch | DeploymentOutcomePatch;
182
+ /**
183
+ * Logical resource kind, for dashboard grouping/icons. ("function" = a plain
184
+ * compute fn — provider-neutral.) For an `http` proxy the logical name is "http".
185
+ */
186
+ export type DeployedResourceType = "http" | "cron" | "queue" | "function" | "dashboard";
187
+ /**
188
+ * How a resource changed in this deploy, derived from the CloudFormation change
189
+ * set (Add → CREATED, Modify → UPDATED, Remove → REMOVED). Computed against live
190
+ * AWS state so reports self-heal if one is ever missed.
191
+ */
192
+ export type DeployedResourceAction = "CREATED" | "REMOVED" | "UPDATED";
193
+ /**
194
+ * Free-form, kind-specific config bag stored on each resource (BE column is
195
+ * jsonb). Open by design — extra keys are allowed — but two are conventional:
196
+ * `warnings` for non-fatal per-resource issues (env names only, no values).
197
+ */
198
+ export interface ResourceMetadata {
199
+ /** Non-fatal issues affecting this resource — e.g. unpopulated env-var NAMES. */
200
+ warnings?: string[];
201
+ [key: string]: unknown;
202
+ }
203
+ /**
204
+ * One LOGICAL laranja resource (not one physical AWS resource) in the deployed
205
+ * inventory, reported so the dashboard can show an inventory + deep-link to the
206
+ * console.
207
+ *
208
+ * SECURITY: identifiers ONLY — never env-var values or secrets. ARNs/URLs are
209
+ * not credentials, but treat this as tenant-scoped data (per-user authz on read).
210
+ */
211
+ export interface DeployedResource {
212
+ /** Logical id, e.g. "cleanup", "process-order", or "http" for the proxy. */
213
+ name: string;
214
+ type: DeployedResourceType;
215
+ action: DeployedResourceAction;
216
+ /**
217
+ * Kind-specific config (cron schedule, queue name/fifo/batchSize, lambda cfg).
218
+ * MUST be `{}` when there's nothing to report — never `null`. No routes.
219
+ *
220
+ * May also carry `warnings: string[]` — non-fatal issues affecting THIS
221
+ * resource, e.g. the NAMES of env vars that had no value at deploy time. Names
222
+ * only, never values/secrets. (BE stores `metadata` as jsonb, so no schema
223
+ * change is needed to add this.)
224
+ */
225
+ metadata: ResourceMetadata;
226
+ /** Primary Lambda function ARN. `null` for REMOVED if unknown. */
227
+ externalId: string | null;
228
+ /** For `http`, the Lambda Function URL (no API Gateway). `null` for cron/queue. */
229
+ externalUrl: string | null;
230
+ }
231
+ /**
232
+ * `POST /v1/deployment/:id/resources` body — the deployed inventory, sent on
233
+ * SUCCESS only. WRAPPED in an object (a bare array 500s the BE controller).
234
+ */
235
+ export interface ResourcesReport {
236
+ resources: DeployedResource[];
237
+ }
238
+ /**
239
+ * `POST /v1/deployment/destory` body — opens a teardown deployment row. A destroy
240
+ * never hits `/synth` (nothing to synthesize), so this is how it gets a row + id.
241
+ * The project comes from the API key; the body identifies the stack being torn
242
+ * down. The BE owns the REMOVED resource inventory (from the last deployment).
243
+ */
244
+ export interface DestroyRequest {
245
+ /** Physical stack name, e.g. "myapp-dev". */
246
+ stackName: string;
247
+ artifact: SynthArtifact;
248
+ /** Target cloud, e.g. "AWS". */
249
+ provider: string;
250
+ region: string;
251
+ }
252
+ /** `POST /v1/deployment/destory` response — the new teardown row's id. */
253
+ export interface DestroyResponse {
254
+ deploymentId: string;
255
+ }
256
+ /**
257
+ * Error codes returned in the body alongside a non-2xx status:
258
+ * unauthorized -> 401 (missing/invalid API key)
259
+ * project_access -> 403 (valid key, but no access to the requested project)
260
+ * forbidden -> 403 (valid key, not entitled — e.g. cdk on free)
261
+ * limit_exceeded -> 402 (deploys/day or project cap hit)
262
+ * invalid_request-> 400 (malformed IR / payload)
263
+ * server_error -> 500
264
+ */
265
+ export type ApiErrorCode = "unauthorized" | "project_access" | "forbidden" | "limit_exceeded" | "invalid_request" | "server_error";
266
+ export interface ApiError {
267
+ error: ApiErrorCode;
268
+ message: string;
269
+ /** Where to upgrade, when relevant (limit_exceeded / forbidden). */
270
+ upgradeUrl?: string;
271
+ /** For rate limits: seconds until the client may retry. */
272
+ retryAfter?: number;
273
+ }
274
+ export {};
package/dist/api.js ADDED
@@ -0,0 +1,45 @@
1
+ /**
2
+ * The wire contract between the laranja CLI and the laranja server.
3
+ *
4
+ * This module is TYPES ONLY (plus a couple of constants) — no runtime logic — so
5
+ * it can be shared verbatim by the CLI and the backend and keep them in lockstep.
6
+ *
7
+ * Auth: every request carries the user's API key in an `x-api-key: <API_KEY>`
8
+ * header. Project-scoped calls (e.g. `/synth`) also send the dashboard project
9
+ * id in an `x-project-id: <projectId>` header. Neither is part of any body type
10
+ * below — identity/context lives in headers, synth inputs in the body.
11
+ *
12
+ * Only the Infra IR crosses the wire — never the user's source code.
13
+ */
14
+ export const API_VERSION = "v1";
15
+ /** Server mounts every route under this prefix (NestJS global prefix + version). */
16
+ export const API_PREFIX = `/api/${API_VERSION}`;
17
+ /**
18
+ * Shared endpoint paths, so both sides reference the same strings.
19
+ *
20
+ * Deployment reporting is a lifecycle, not a single call: `/synth` opens a
21
+ * deployment row (`INITIATED`), then the client PATCHes status as it deploys
22
+ * (`STARTED` → `SUCCESS`/`FAILED`) and POSTs the resource inventory on success.
23
+ * The `:id` here is the `deploymentId` returned by `/synth`.
24
+ */
25
+ export const ENDPOINTS = {
26
+ /** GET — verify the API key + return the user and their projects (used by `laranja init`). */
27
+ me: `${API_PREFIX}/me`,
28
+ /** POST — create a project (name only); returns the new project. Used by `laranja init`. */
29
+ project: `${API_PREFIX}/project`,
30
+ /** POST — IR in, CloudFormation template (or CDK files) out; opens the deployment row. */
31
+ synth: `${API_PREFIX}/synth`,
32
+ /** PATCH — advance a deployment's status (STARTED before AWS, then SUCCESS/FAILED). */
33
+ deployment: (id) => `${API_PREFIX}/deployment/${id}`,
34
+ /** POST — report the deployed resource inventory (success only). */
35
+ deploymentResources: (id) => `${API_PREFIX}/deployment/${id}/resources`,
36
+ /** POST — open a teardown deployment row (destroy has no `/synth`). */
37
+ deploymentDestroy: `${API_PREFIX}/deployment/destory`,
38
+ /** POST — read-only synth (returns a template, creates NO deployment row). */
39
+ diff: `${API_PREFIX}/diff`,
40
+ /** POST — generate a standalone, owned CDK project (paid; server-gated). */
41
+ eject: `${API_PREFIX}/eject`,
42
+ /** POST — a CLI failure report (free-form), scoped to the user + project. */
43
+ report: `${API_PREFIX}/report`,
44
+ };
45
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,CAAC;AAEhC,oFAAoF;AACpF,MAAM,CAAC,MAAM,UAAU,GAAG,QAAQ,WAAW,EAAE,CAAC;AAEhD;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,8FAA8F;IAC9F,EAAE,EAAE,GAAG,UAAU,KAAK;IACtB,4FAA4F;IAC5F,OAAO,EAAE,GAAG,UAAU,UAAU;IAChC,0FAA0F;IAC1F,KAAK,EAAE,GAAG,UAAU,QAAQ;IAC5B,uFAAuF;IACvF,UAAU,EAAE,CAAC,EAAU,EAAE,EAAE,CAAC,GAAG,UAAU,eAAe,EAAE,EAAE;IAC5D,oEAAoE;IACpE,mBAAmB,EAAE,CAAC,EAAU,EAAE,EAAE,CAClC,GAAG,UAAU,eAAe,EAAE,YAAY;IAC5C,uEAAuE;IACvE,iBAAiB,EAAE,GAAG,UAAU,qBAAqB;IACrD,8EAA8E;IAC9E,IAAI,EAAE,GAAG,UAAU,OAAO;IAC1B,4EAA4E;IAC5E,KAAK,EAAE,GAAG,UAAU,QAAQ;IAC5B,6EAA6E;IAC7E,MAAM,EAAE,GAAG,UAAU,SAAS;CACtB,CAAC"}
package/dist/auth.d.ts ADDED
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Global, user-level credential store for the laranja CLI.
3
+ *
4
+ * The API key is account-scoped (the same key works for every project), so it
5
+ * lives once in the user's home dir — NOT in the committed `laranja.config.ts`.
6
+ * This is the Vercel-style "log in once" model: `laranja init` validates the
7
+ * key against `/me` and then persists it here, so later commands don't need
8
+ * `LARANJA_API_KEY` re-exported in every shell.
9
+ *
10
+ * Layout (cross-platform via `os.homedir()`):
11
+ * ~/.laranja/auth.json (file mode 0600, dir mode 0700 where supported)
12
+ * { "apiKey": "...", "apiUrl": "..." }
13
+ */
14
+ /** Directory holding laranja's user-level state. Override with `LARANJA_HOME`. */
15
+ export declare function authDir(): string;
16
+ /** Path to the credential file. */
17
+ export declare function authFilePath(): string;
18
+ /** On-disk shape of the credential file. */
19
+ export interface StoredAuth {
20
+ apiKey: string;
21
+ /** The API URL the key was validated against — informational. */
22
+ apiUrl?: string;
23
+ }
24
+ /** Read the stored credentials, or `undefined` if none / unreadable. */
25
+ export declare function loadStoredAuth(): StoredAuth | undefined;
26
+ /** Convenience: just the stored key, if any. */
27
+ export declare function loadStoredApiKey(): string | undefined;
28
+ /**
29
+ * Persist credentials to `~/.laranja/auth.json` with owner-only permissions.
30
+ * Creates the directory if needed. Returns the path written.
31
+ */
32
+ export declare function storeAuth(auth: StoredAuth): string;
33
+ /**
34
+ * Delete the stored credentials (`laranja logout`). Returns `true` if a file
35
+ * was removed, `false` if there was nothing stored.
36
+ */
37
+ export declare function clearAuth(): boolean;
package/dist/auth.js ADDED
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Global, user-level credential store for the laranja CLI.
3
+ *
4
+ * The API key is account-scoped (the same key works for every project), so it
5
+ * lives once in the user's home dir — NOT in the committed `laranja.config.ts`.
6
+ * This is the Vercel-style "log in once" model: `laranja init` validates the
7
+ * key against `/me` and then persists it here, so later commands don't need
8
+ * `LARANJA_API_KEY` re-exported in every shell.
9
+ *
10
+ * Layout (cross-platform via `os.homedir()`):
11
+ * ~/.laranja/auth.json (file mode 0600, dir mode 0700 where supported)
12
+ * { "apiKey": "...", "apiUrl": "..." }
13
+ */
14
+ import os from "node:os";
15
+ import path from "node:path";
16
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, chmodSync, rmSync } from "node:fs";
17
+ /** Directory holding laranja's user-level state. Override with `LARANJA_HOME`. */
18
+ export function authDir() {
19
+ return process.env.LARANJA_HOME?.trim() || path.join(os.homedir(), ".laranja");
20
+ }
21
+ /** Path to the credential file. */
22
+ export function authFilePath() {
23
+ return path.join(authDir(), "auth.json");
24
+ }
25
+ /** Read the stored credentials, or `undefined` if none / unreadable. */
26
+ export function loadStoredAuth() {
27
+ const file = authFilePath();
28
+ if (!existsSync(file))
29
+ return undefined;
30
+ try {
31
+ const data = JSON.parse(readFileSync(file, "utf8"));
32
+ const apiKey = data.apiKey?.trim();
33
+ if (!apiKey)
34
+ return undefined;
35
+ return { apiKey, apiUrl: data.apiUrl };
36
+ }
37
+ catch {
38
+ // Corrupt file — treat as "not logged in" rather than crashing the CLI.
39
+ return undefined;
40
+ }
41
+ }
42
+ /** Convenience: just the stored key, if any. */
43
+ export function loadStoredApiKey() {
44
+ return loadStoredAuth()?.apiKey;
45
+ }
46
+ /**
47
+ * Persist credentials to `~/.laranja/auth.json` with owner-only permissions.
48
+ * Creates the directory if needed. Returns the path written.
49
+ */
50
+ export function storeAuth(auth) {
51
+ const dir = authDir();
52
+ // `recursive: true` makes mkdir a no-op if the dir already exists.
53
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
54
+ const file = authFilePath();
55
+ writeFileSync(file, JSON.stringify(auth, null, 2) + "\n", { mode: 0o600 });
56
+ // mkdir/writeFile honor `mode` only on creation; enforce it explicitly so an
57
+ // existing, looser file is tightened. (On Windows chmod is a best-effort
58
+ // no-op — POSIX perms don't apply there; the file lives in the user profile.)
59
+ try {
60
+ chmodSync(file, 0o600);
61
+ }
62
+ catch {
63
+ /* best-effort on platforms without POSIX permissions */
64
+ }
65
+ return file;
66
+ }
67
+ /**
68
+ * Delete the stored credentials (`laranja logout`). Returns `true` if a file
69
+ * was removed, `false` if there was nothing stored.
70
+ */
71
+ export function clearAuth() {
72
+ const file = authFilePath();
73
+ if (!existsSync(file))
74
+ return false;
75
+ rmSync(file, { force: true });
76
+ return true;
77
+ }
78
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEhG,kFAAkF;AAClF,MAAM,UAAU,OAAO;IACrB,OAAO,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,UAAU,CAAC,CAAC;AACjF,CAAC;AAED,mCAAmC;AACnC,MAAM,UAAU,YAAY;IAC1B,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,WAAW,CAAC,CAAC;AAC3C,CAAC;AASD,wEAAwE;AACxE,MAAM,UAAU,cAAc;IAC5B,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;IAC5B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IACxC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAwB,CAAC;QAC3E,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC;QACnC,IAAI,CAAC,MAAM;YAAE,OAAO,SAAS,CAAC;QAC9B,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,wEAAwE;QACxE,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,gDAAgD;AAChD,MAAM,UAAU,gBAAgB;IAC9B,OAAO,cAAc,EAAE,EAAE,MAAM,CAAC;AAClC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,IAAgB;IACxC,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;IACtB,mEAAmE;IACnE,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACjD,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;IAC5B,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC3E,6EAA6E;IAC7E,yEAAyE;IACzE,8EAA8E;IAC9E,IAAI,CAAC;QACH,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,wDAAwD;IAC1D,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS;IACvB,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;IAC5B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACpC,MAAM,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9B,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,104 @@
1
+ /**
2
+ * The laranja API client — the CLI's side of the wire contract in `api.ts`.
3
+ *
4
+ * Zero-dep: uses the global `fetch` (Node 18+). Only the Infra IR ever crosses
5
+ * the wire (see `SynthRequest`); the user's source code never leaves the machine.
6
+ */
7
+ import { type MeResponse, type CreateProjectResponse, type SynthRequest, type SynthResponse, type DiffResponse, type EjectResponse, type DeploymentPatch, type ResourcesReport, type DestroyRequest, type ApiErrorCode } from "./api.js";
8
+ /** Default server URL for local development. Override with `LARANJA_API_URL`. */
9
+ export declare const DEFAULT_API_URL = "https://api.laranja.io";
10
+ /** Where to reach the server. Env override lets us point at prod later. */
11
+ export declare function resolveApiUrl(): string;
12
+ /**
13
+ * The caller's API key. Precedence: `LARANJA_API_KEY` env var (CI / one-off
14
+ * override) wins, then the key persisted by `laranja init` (~/.laranja/auth.json).
15
+ * The env override means a stored login never blocks a different key in CI.
16
+ */
17
+ export declare function resolveApiKey(): string | undefined;
18
+ /** A failed API call — carries the server's error code so callers can branch. */
19
+ export declare class ApiRequestError extends Error {
20
+ readonly code: ApiErrorCode;
21
+ readonly status: number;
22
+ readonly upgradeUrl?: string | undefined;
23
+ constructor(code: ApiErrorCode, message: string, status: number, upgradeUrl?: string | undefined);
24
+ }
25
+ /** Base URL of the laranja dashboard web app. Override with `LARANJA_DASHBOARD_URL` for local dev. */
26
+ export declare const DASHBOARD_URL: string;
27
+ /** Dashboard page where users create / manage their API keys. */
28
+ export declare const DASHBOARD_KEYS_URL: string;
29
+ /**
30
+ * True when an error means the API key itself is the problem — missing,
31
+ * expired, or deleted — as opposed to an entitlement issue.
32
+ *
33
+ * The server is inconsistent about how it reports a bad key (it leans on
34
+ * NestJS's generic exceptions rather than our structured `ApiErrorCode`):
35
+ * - a proper `401`,
36
+ * - a `400 { error: "Bad Request", message: "Invalid API KEY" }`,
37
+ * - a `403 { error: "Forbidden", message: "Forbidden resource" }` when an
38
+ * expired/deleted key trips the auth guard.
39
+ * So we treat any 401/403, or a message of "Invalid API KEY", as a bad key.
40
+ *
41
+ * The one 403 that is NOT a key problem is a *real* entitlement failure ("valid
42
+ * key, not entitled"), which uses our structured contract: lower-case `code:
43
+ * "forbidden"` + an `upgradeUrl`. NestJS's generic guard rejection instead puts
44
+ * capital-`F` `"Forbidden"` in the body's `error` field, so the two don't
45
+ * collide — we exclude only the lower-case structured code.
46
+ */
47
+ export declare function isAuthKeyError(err: unknown): boolean;
48
+ /**
49
+ * Turn an `ApiRequestError` into a friendly, actionable one-or-many-line
50
+ * message, prefixed with the failing step (e.g. "Handshake failed"). Bad-key
51
+ * errors get create-a-new-key guidance; an unreachable server gets a "is it
52
+ * running?" hint; everything else falls back to the server's own message.
53
+ */
54
+ export declare function apiErrorMessage(prefix: string, err: ApiRequestError): string;
55
+ /**
56
+ * `GET /v1/me` — validate the API key and fetch the caller's tier/limits.
57
+ * Used by `laranja init` to handshake with the server.
58
+ */
59
+ export declare function getMe(apiKey: string, baseUrl?: string): Promise<MeResponse>;
60
+ /**
61
+ * `POST /v1/project` — create a project (name only) from the CLI and get back
62
+ * its server id. User-scoped: authed by the API key, no `x-project-id`.
63
+ */
64
+ export declare function createProject(name: string, apiKey: string, baseUrl?: string): Promise<CreateProjectResponse>;
65
+ /**
66
+ * `POST /v1/synth` — send the Infra IR, get back a CloudFormation template
67
+ * (or, for paid tiers, a CDK project). Only the IR crosses the wire; the
68
+ * dashboard `projectId` rides in the `x-project-id` header.
69
+ */
70
+ export declare function postSynth(req: SynthRequest, apiKey: string, projectId: string, baseUrl?: string): Promise<SynthResponse>;
71
+ /**
72
+ * `POST /v1/diff` — a read-only synth: same input as `/synth`, returns a template
73
+ * to diff against the deployed stack, but creates NO deployment row.
74
+ */
75
+ export declare function postDiff(req: SynthRequest, apiKey: string, projectId: string, baseUrl?: string): Promise<DiffResponse>;
76
+ /**
77
+ * `POST /v1/eject` — generate a standalone, owned CDK project from the IR. The
78
+ * server gates this on the caller's entitlement (403 if not allowed). Nothing is
79
+ * persisted; the client writes the returned files to disk.
80
+ */
81
+ export declare function postEject(req: SynthRequest, apiKey: string, projectId: string, baseUrl?: string): Promise<EjectResponse>;
82
+ /**
83
+ * `POST /v1/report` — send a free-form CLI failure report, scoped to the user
84
+ * (api key) + project (project id). Diagnostics only; the body shape is open.
85
+ */
86
+ export declare function postReport(report: Record<string, unknown>, apiKey: string, projectId: string, baseUrl?: string): Promise<unknown>;
87
+ /**
88
+ * `PATCH /v1/deployment/:id` — advance a deployment's status. Sent with
89
+ * `{ status: "STARTED", region }` before touching AWS, then `{ status: "SUCCESS"
90
+ * | "FAILED" }` once the deploy settles. The dashboard `projectId` rides in the
91
+ * `x-project-id` header, consistent with the rest of the deploy/destroy calls.
92
+ */
93
+ export declare function patchDeployment(deploymentId: string, body: DeploymentPatch, apiKey: string, projectId: string, baseUrl?: string): Promise<boolean>;
94
+ /**
95
+ * `POST /v1/deployment/:id/resources` — report the deployed inventory after a
96
+ * successful deploy. Body is WRAPPED (`{ resources }`); a bare array 500s the BE.
97
+ */
98
+ export declare function postDeploymentResources(deploymentId: string, body: ResourcesReport, apiKey: string, projectId: string, baseUrl?: string): Promise<boolean>;
99
+ /**
100
+ * `POST /v1/deployment/destory` — open a teardown deployment row and get its id,
101
+ * so a destroy can drive the same status lifecycle (STARTED → SUCCESS/FAILED).
102
+ * Tolerates either a `{ deploymentId }` body or a bare id string.
103
+ */
104
+ export declare function postDestroy(req: DestroyRequest, apiKey: string, projectId: string, baseUrl?: string): Promise<string>;