@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 +274 -0
- package/dist/api.js +45 -0
- package/dist/api.js.map +1 -0
- package/dist/auth.d.ts +37 -0
- package/dist/auth.js +78 -0
- package/dist/auth.js.map +1 -0
- package/dist/client.d.ts +104 -0
- package/dist/client.js +240 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +151 -0
- package/dist/config.js +89 -0
- package/dist/config.js.map +1 -0
- package/dist/env.d.ts +55 -0
- package/dist/env.js +66 -0
- package/dist/env.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/ir.d.ts +204 -0
- package/dist/ir.js +21 -0
- package/dist/ir.js.map +1 -0
- package/dist/nest-schedule.d.ts +127 -0
- package/dist/nest-schedule.js +222 -0
- package/dist/nest-schedule.js.map +1 -0
- package/dist/schedule.d.ts +62 -0
- package/dist/schedule.js +140 -0
- package/dist/schedule.js.map +1 -0
- package/package.json +23 -0
package/dist/client.js
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
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 { ENDPOINTS, } from "./api.js";
|
|
8
|
+
import { loadStoredApiKey } from "./auth.js";
|
|
9
|
+
import { CONFIG_FILENAME } from "./config.js";
|
|
10
|
+
/** Default server URL for local development. Override with `LARANJA_API_URL`. */
|
|
11
|
+
export const DEFAULT_API_URL = "https://api.laranja.io";
|
|
12
|
+
/** Where to reach the server. Env override lets us point at prod later. */
|
|
13
|
+
export function resolveApiUrl() {
|
|
14
|
+
return (process.env.LARANJA_API_URL ?? DEFAULT_API_URL).replace(/\/+$/, "");
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The caller's API key. Precedence: `LARANJA_API_KEY` env var (CI / one-off
|
|
18
|
+
* override) wins, then the key persisted by `laranja init` (~/.laranja/auth.json).
|
|
19
|
+
* The env override means a stored login never blocks a different key in CI.
|
|
20
|
+
*/
|
|
21
|
+
export function resolveApiKey() {
|
|
22
|
+
return ((process.env.LARANJA_API_KEY?.trim() || undefined) ?? loadStoredApiKey());
|
|
23
|
+
}
|
|
24
|
+
/** A failed API call — carries the server's error code so callers can branch. */
|
|
25
|
+
export class ApiRequestError extends Error {
|
|
26
|
+
code;
|
|
27
|
+
status;
|
|
28
|
+
upgradeUrl;
|
|
29
|
+
constructor(code, message, status, upgradeUrl) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.code = code;
|
|
32
|
+
this.status = status;
|
|
33
|
+
this.upgradeUrl = upgradeUrl;
|
|
34
|
+
this.name = "ApiRequestError";
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** Base URL of the laranja dashboard web app. Override with `LARANJA_DASHBOARD_URL` for local dev. */
|
|
38
|
+
export const DASHBOARD_URL = (process.env.LARANJA_DASHBOARD_URL ?? "https://laranja.io/app").replace(/\/+$/, "");
|
|
39
|
+
/** Dashboard page where users create / manage their API keys. */
|
|
40
|
+
export const DASHBOARD_KEYS_URL = `${DASHBOARD_URL}/user`;
|
|
41
|
+
/**
|
|
42
|
+
* True when an error means the API key itself is the problem — missing,
|
|
43
|
+
* expired, or deleted — as opposed to an entitlement issue.
|
|
44
|
+
*
|
|
45
|
+
* The server is inconsistent about how it reports a bad key (it leans on
|
|
46
|
+
* NestJS's generic exceptions rather than our structured `ApiErrorCode`):
|
|
47
|
+
* - a proper `401`,
|
|
48
|
+
* - a `400 { error: "Bad Request", message: "Invalid API KEY" }`,
|
|
49
|
+
* - a `403 { error: "Forbidden", message: "Forbidden resource" }` when an
|
|
50
|
+
* expired/deleted key trips the auth guard.
|
|
51
|
+
* So we treat any 401/403, or a message of "Invalid API KEY", as a bad key.
|
|
52
|
+
*
|
|
53
|
+
* The one 403 that is NOT a key problem is a *real* entitlement failure ("valid
|
|
54
|
+
* key, not entitled"), which uses our structured contract: lower-case `code:
|
|
55
|
+
* "forbidden"` + an `upgradeUrl`. NestJS's generic guard rejection instead puts
|
|
56
|
+
* capital-`F` `"Forbidden"` in the body's `error` field, so the two don't
|
|
57
|
+
* collide — we exclude only the lower-case structured code.
|
|
58
|
+
*/
|
|
59
|
+
export function isAuthKeyError(err) {
|
|
60
|
+
if (!(err instanceof ApiRequestError))
|
|
61
|
+
return false;
|
|
62
|
+
if (err.code === "forbidden")
|
|
63
|
+
return false; // structured entitlement, not a bad key
|
|
64
|
+
if (err.code === "project_access")
|
|
65
|
+
return false; // valid key, project-access issue
|
|
66
|
+
return (err.status === 401 ||
|
|
67
|
+
err.status === 403 ||
|
|
68
|
+
/invalid\s+api\s+key/i.test(err.message));
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Turn an `ApiRequestError` into a friendly, actionable one-or-many-line
|
|
72
|
+
* message, prefixed with the failing step (e.g. "Handshake failed"). Bad-key
|
|
73
|
+
* errors get create-a-new-key guidance; an unreachable server gets a "is it
|
|
74
|
+
* running?" hint; everything else falls back to the server's own message.
|
|
75
|
+
*/
|
|
76
|
+
export function apiErrorMessage(prefix, err) {
|
|
77
|
+
if (isAuthKeyError(err)) {
|
|
78
|
+
return [
|
|
79
|
+
`${prefix} — your API key is invalid, expired, or was deleted.`,
|
|
80
|
+
"",
|
|
81
|
+
` Create a new key at ${DASHBOARD_KEYS_URL}, then export it:`,
|
|
82
|
+
" export LARANJA_API_KEY=<NEW_KEY>",
|
|
83
|
+
"",
|
|
84
|
+
" (laranja also reads a saved key from ~/.laranja/auth.json —",
|
|
85
|
+
" a stale one there can cause this too.)",
|
|
86
|
+
].join("\n");
|
|
87
|
+
}
|
|
88
|
+
if (err.code === "project_access") {
|
|
89
|
+
return [
|
|
90
|
+
`${prefix} — you don't have access to this project.`,
|
|
91
|
+
"",
|
|
92
|
+
" Your API key is fine, but the project it's pointing at isn't reachable.",
|
|
93
|
+
" This usually means one of:",
|
|
94
|
+
" • the project was deleted from your dashboard,",
|
|
95
|
+
" • you were removed from it, or",
|
|
96
|
+
` • the "projectId" in ${CONFIG_FILENAME} is wrong.`,
|
|
97
|
+
"",
|
|
98
|
+
` Check your projects at ${DASHBOARD_URL} and update "projectId" in ${CONFIG_FILENAME}.`,
|
|
99
|
+
].join("\n");
|
|
100
|
+
}
|
|
101
|
+
const hint = err.status === 0
|
|
102
|
+
? `is the server running at ${resolveApiUrl()}?`
|
|
103
|
+
: err.message;
|
|
104
|
+
return `${prefix} — ${hint}`;
|
|
105
|
+
}
|
|
106
|
+
async function apiRequest(method, endpoint, opts) {
|
|
107
|
+
const url = `${opts.baseUrl ?? resolveApiUrl()}${endpoint}`;
|
|
108
|
+
let res;
|
|
109
|
+
try {
|
|
110
|
+
res = await fetch(url, {
|
|
111
|
+
method,
|
|
112
|
+
headers: {
|
|
113
|
+
"x-api-key": opts.apiKey,
|
|
114
|
+
...(opts.projectId ? { "x-project-id": opts.projectId } : {}),
|
|
115
|
+
...(opts.body !== undefined
|
|
116
|
+
? { "Content-Type": "application/json" }
|
|
117
|
+
: {}),
|
|
118
|
+
},
|
|
119
|
+
body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
catch (cause) {
|
|
123
|
+
throw new ApiRequestError("server_error", `Could not reach the laranja server at ${url}`, 0);
|
|
124
|
+
}
|
|
125
|
+
if (!res.ok) {
|
|
126
|
+
const err = (await res.json().catch(() => undefined));
|
|
127
|
+
throw new ApiRequestError(err?.error ?? "server_error", err?.message ?? `Request failed (${res.status})`, res.status, err?.upgradeUrl);
|
|
128
|
+
}
|
|
129
|
+
// Read defensively: some endpoints return a bare (unquoted) id string — e.g.
|
|
130
|
+
// destroy — or an empty body, neither of which `res.json()` can parse.
|
|
131
|
+
const text = await res.text();
|
|
132
|
+
if (!text)
|
|
133
|
+
return undefined;
|
|
134
|
+
try {
|
|
135
|
+
return JSON.parse(text);
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return text;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* `GET /v1/me` — validate the API key and fetch the caller's tier/limits.
|
|
143
|
+
* Used by `laranja init` to handshake with the server.
|
|
144
|
+
*/
|
|
145
|
+
export function getMe(apiKey, baseUrl) {
|
|
146
|
+
return apiRequest("GET", ENDPOINTS.me, { apiKey, baseUrl });
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* `POST /v1/project` — create a project (name only) from the CLI and get back
|
|
150
|
+
* its server id. User-scoped: authed by the API key, no `x-project-id`.
|
|
151
|
+
*/
|
|
152
|
+
export function createProject(name, apiKey, baseUrl) {
|
|
153
|
+
const body = { name };
|
|
154
|
+
return apiRequest("POST", ENDPOINTS.project, {
|
|
155
|
+
apiKey,
|
|
156
|
+
baseUrl,
|
|
157
|
+
body,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* `POST /v1/synth` — send the Infra IR, get back a CloudFormation template
|
|
162
|
+
* (or, for paid tiers, a CDK project). Only the IR crosses the wire; the
|
|
163
|
+
* dashboard `projectId` rides in the `x-project-id` header.
|
|
164
|
+
*/
|
|
165
|
+
export function postSynth(req, apiKey, projectId, baseUrl) {
|
|
166
|
+
return apiRequest("POST", ENDPOINTS.synth, {
|
|
167
|
+
apiKey,
|
|
168
|
+
projectId,
|
|
169
|
+
baseUrl,
|
|
170
|
+
body: req,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* `POST /v1/diff` — a read-only synth: same input as `/synth`, returns a template
|
|
175
|
+
* to diff against the deployed stack, but creates NO deployment row.
|
|
176
|
+
*/
|
|
177
|
+
export function postDiff(req, apiKey, projectId, baseUrl) {
|
|
178
|
+
return apiRequest("POST", ENDPOINTS.diff, {
|
|
179
|
+
apiKey,
|
|
180
|
+
projectId,
|
|
181
|
+
baseUrl,
|
|
182
|
+
body: req,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* `POST /v1/eject` — generate a standalone, owned CDK project from the IR. The
|
|
187
|
+
* server gates this on the caller's entitlement (403 if not allowed). Nothing is
|
|
188
|
+
* persisted; the client writes the returned files to disk.
|
|
189
|
+
*/
|
|
190
|
+
export function postEject(req, apiKey, projectId, baseUrl) {
|
|
191
|
+
return apiRequest("POST", ENDPOINTS.eject, {
|
|
192
|
+
apiKey,
|
|
193
|
+
projectId,
|
|
194
|
+
baseUrl,
|
|
195
|
+
body: req,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* `POST /v1/report` — send a free-form CLI failure report, scoped to the user
|
|
200
|
+
* (api key) + project (project id). Diagnostics only; the body shape is open.
|
|
201
|
+
*/
|
|
202
|
+
export function postReport(report, apiKey, projectId, baseUrl) {
|
|
203
|
+
return apiRequest("POST", ENDPOINTS.report, {
|
|
204
|
+
apiKey,
|
|
205
|
+
projectId,
|
|
206
|
+
baseUrl,
|
|
207
|
+
body: report,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* `PATCH /v1/deployment/:id` — advance a deployment's status. Sent with
|
|
212
|
+
* `{ status: "STARTED", region }` before touching AWS, then `{ status: "SUCCESS"
|
|
213
|
+
* | "FAILED" }` once the deploy settles. The dashboard `projectId` rides in the
|
|
214
|
+
* `x-project-id` header, consistent with the rest of the deploy/destroy calls.
|
|
215
|
+
*/
|
|
216
|
+
export function patchDeployment(deploymentId, body, apiKey, projectId, baseUrl) {
|
|
217
|
+
return apiRequest("PATCH", ENDPOINTS.deployment(deploymentId), {
|
|
218
|
+
apiKey,
|
|
219
|
+
projectId,
|
|
220
|
+
baseUrl,
|
|
221
|
+
body,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* `POST /v1/deployment/:id/resources` — report the deployed inventory after a
|
|
226
|
+
* successful deploy. Body is WRAPPED (`{ resources }`); a bare array 500s the BE.
|
|
227
|
+
*/
|
|
228
|
+
export function postDeploymentResources(deploymentId, body, apiKey, projectId, baseUrl) {
|
|
229
|
+
return apiRequest("POST", ENDPOINTS.deploymentResources(deploymentId), { apiKey, projectId, baseUrl, body });
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* `POST /v1/deployment/destory` — open a teardown deployment row and get its id,
|
|
233
|
+
* so a destroy can drive the same status lifecycle (STARTED → SUCCESS/FAILED).
|
|
234
|
+
* Tolerates either a `{ deploymentId }` body or a bare id string.
|
|
235
|
+
*/
|
|
236
|
+
export async function postDestroy(req, apiKey, projectId, baseUrl) {
|
|
237
|
+
const res = await apiRequest("POST", ENDPOINTS.deploymentDestroy, { apiKey, projectId, baseUrl, body: req });
|
|
238
|
+
return typeof res === "string" ? res : res.deploymentId;
|
|
239
|
+
}
|
|
240
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import type { CloudProvider, ComputeConfig, Framework } from "./ir.js";
|
|
2
|
+
/** Queue-only tuning knobs (SQS + event-source). */
|
|
3
|
+
export interface QueueTuning {
|
|
4
|
+
/** FIFO content-based dedup (FIFO queues only). */
|
|
5
|
+
contentBasedDedup?: boolean;
|
|
6
|
+
/** Seconds a message stays hidden while processed. Must be >= the consumer timeout. */
|
|
7
|
+
visibilityTimeout?: number;
|
|
8
|
+
/** Seconds to wait gathering a fuller batch before invoking. */
|
|
9
|
+
maxBatchingWindow?: number;
|
|
10
|
+
/** Report per-message failures so only failed items retry. */
|
|
11
|
+
reportBatchItemFailures?: boolean;
|
|
12
|
+
/** Seconds an undelivered message is retained on the source queue. */
|
|
13
|
+
messageRetention?: number;
|
|
14
|
+
}
|
|
15
|
+
/** Cron-only tuning knobs (schedule + async-invoke). */
|
|
16
|
+
export interface CronTuning {
|
|
17
|
+
/** IANA timezone for the schedule. */
|
|
18
|
+
timezone?: string;
|
|
19
|
+
/** Async-invoke retry attempts (0–2). */
|
|
20
|
+
retryAttempts?: number;
|
|
21
|
+
/** Max age (seconds) of an event before async retries are abandoned. */
|
|
22
|
+
maxEventAge?: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* DLQ for a queue — the id of another declared queue, plus the required
|
|
26
|
+
* `maxReceiveCount` (after N failed receives → dead-letter). `Q` is the union of
|
|
27
|
+
* this project's queue names, so the generated config autocompletes `queue` to a
|
|
28
|
+
* real target; it defaults to `string` for the untyped fallback.
|
|
29
|
+
*/
|
|
30
|
+
export interface QueueDlq<Q extends string = string> {
|
|
31
|
+
queue: Q;
|
|
32
|
+
maxReceiveCount: number;
|
|
33
|
+
}
|
|
34
|
+
/** DLQ for a cron — the id of another declared queue. A failed async invoke dead-letters immediately, so there's no receive count. */
|
|
35
|
+
export interface CronDlq<Q extends string = string> {
|
|
36
|
+
queue: Q;
|
|
37
|
+
}
|
|
38
|
+
/** Per-resource overrides for the HTTP proxy: compute knobs only. */
|
|
39
|
+
export type HttpResourceConfig = ComputeConfig;
|
|
40
|
+
/**
|
|
41
|
+
* Compute for one worker Lambda, keyed by its module name. Under consolidation a
|
|
42
|
+
* Nest module is a single function, so memory/timeout/etc. live here — not on the
|
|
43
|
+
* individual cron/queue, which only owns its trigger (see `*TriggerConfig`).
|
|
44
|
+
*/
|
|
45
|
+
export type WorkerResourceConfig = ComputeConfig;
|
|
46
|
+
/**
|
|
47
|
+
* A queue's TRIGGER‑level tuning (SQS + event source + DLQ) — no compute. Used for
|
|
48
|
+
* a grouped Nest queue, whose compute lives on its worker module. `Q` (the queue‑
|
|
49
|
+
* name union) types the DLQ target for autocomplete.
|
|
50
|
+
*/
|
|
51
|
+
export interface QueueTriggerConfig<Q extends string = string> extends QueueTuning {
|
|
52
|
+
dlq?: QueueDlq<Q>;
|
|
53
|
+
}
|
|
54
|
+
/** A cron's TRIGGER‑level tuning (schedule + async‑invoke + DLQ) — no compute. */
|
|
55
|
+
export interface CronTriggerConfig<Q extends string = string> extends CronTuning {
|
|
56
|
+
dlq?: CronDlq<Q>;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Per-resource overrides for a STANDALONE queue consumer (Express, or a function‑
|
|
60
|
+
* style queue in Nest): compute + trigger, since the handler is its own Lambda.
|
|
61
|
+
*/
|
|
62
|
+
export interface QueueResourceConfig<Q extends string = string> extends ComputeConfig, QueueTriggerConfig<Q> {
|
|
63
|
+
}
|
|
64
|
+
/** Per-resource overrides for a STANDALONE cron: compute + trigger (its own Lambda). */
|
|
65
|
+
export interface CronResourceConfig<Q extends string = string> extends ComputeConfig, CronTriggerConfig<Q> {
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The loose, kind-agnostic override shape — every knob optional and `dlq`'s
|
|
69
|
+
* `maxReceiveCount` optional. Used by the scanner (which validates per kind at
|
|
70
|
+
* scan time) and by the untyped `resources: Record<string, ResourceConfig>`
|
|
71
|
+
* fallback. Opt into `TypedLaranjaConfig` (from the generated `laranja.types.ts`)
|
|
72
|
+
* for per-kind compile-time typing that requires `maxReceiveCount` on a queue DLQ
|
|
73
|
+
* and rejects a foreign knob before deploy instead of only at scan time.
|
|
74
|
+
*/
|
|
75
|
+
export interface ResourceConfig extends ComputeConfig, QueueTuning, CronTuning {
|
|
76
|
+
dlq?: {
|
|
77
|
+
queue: string;
|
|
78
|
+
maxReceiveCount?: number;
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/** User-authored config, loaded from `laranja.config.ts`. */
|
|
82
|
+
export interface LaranjaConfig {
|
|
83
|
+
/** App name — used for the CloudFormation stack and resource naming. */
|
|
84
|
+
name: string;
|
|
85
|
+
/**
|
|
86
|
+
* Project id from your laranja dashboard. Identifies this project on the
|
|
87
|
+
* server (scoping, limits, deploy timeline); sent as the `x-project-id`
|
|
88
|
+
* header on `/synth`. Obtain it when you create the project in the dashboard.
|
|
89
|
+
*/
|
|
90
|
+
projectId?: string;
|
|
91
|
+
/** Target cloud. Only "aws" is implemented today. Defaults to "aws". */
|
|
92
|
+
provider?: CloudProvider;
|
|
93
|
+
region?: string;
|
|
94
|
+
/** AWS named profile to deploy with. */
|
|
95
|
+
profile?: string;
|
|
96
|
+
/** Override framework detection. */
|
|
97
|
+
framework?: Framework;
|
|
98
|
+
/** Deployment stage; part of resource names (e.g. "dev", "prod"). Defaults to "dev". */
|
|
99
|
+
stage?: string;
|
|
100
|
+
/** Plain env injected into every Lambda. */
|
|
101
|
+
env?: Record<string, string>;
|
|
102
|
+
/**
|
|
103
|
+
* Emit a per-app-stage monitoring dashboard (`<name>-<stage>`) with per-function
|
|
104
|
+
* invocations / errors / throttles / duration for the HTTP proxy and every
|
|
105
|
+
* cron/queue consumer. Provider-neutral — each back half maps it to its own
|
|
106
|
+
* primitives. HTTP status classes (2xx/4xx/5xx) come later with API Gateway.
|
|
107
|
+
* Defaults to true.
|
|
108
|
+
*/
|
|
109
|
+
monitoring?: boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Default compute config (memory, timeout, …) applied to every function — the
|
|
112
|
+
* HTTP proxy and each cron/queue consumer. Override per-resource via `resources`.
|
|
113
|
+
*/
|
|
114
|
+
compute?: ComputeConfig;
|
|
115
|
+
/**
|
|
116
|
+
* Per-resource overrides keyed by resource id ("http", or a cron/queue id).
|
|
117
|
+
* Each merges on top of `compute`, field by field. An unknown id is a hard
|
|
118
|
+
* error at scan time so a typo never silently no-ops.
|
|
119
|
+
*/
|
|
120
|
+
resources?: Record<string, ResourceConfig>;
|
|
121
|
+
}
|
|
122
|
+
export declare const CONFIG_FILENAME = "laranja.config.ts";
|
|
123
|
+
/** Overrides applied on top of the loaded config — e.g. a `--stage` CLI flag. */
|
|
124
|
+
export interface ConfigOverrides {
|
|
125
|
+
/** Wins over `config.stage`. Used by the `--stage`/`-s` flag for per-stage pipelines. */
|
|
126
|
+
stage?: string;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* The CloudFormation stack name for a project + stage: `<name>-<stage>`.
|
|
130
|
+
*
|
|
131
|
+
* Stage is part of the stack name (not just the Lambda names) so two stages can
|
|
132
|
+
* be deployed to the SAME account without colliding — one definition, two
|
|
133
|
+
* independent stacks. With separate dev/prod accounts the suffix is harmless.
|
|
134
|
+
* Single source of truth: every command must derive the stack name from here.
|
|
135
|
+
*/
|
|
136
|
+
export declare function stackName(name: string, stage: string): string;
|
|
137
|
+
/**
|
|
138
|
+
* The monitoring dashboard's physical name: `<name>-<stage>`, sanitized to the
|
|
139
|
+
* chars CloudWatch allows (A–Z a–z 0–9 - _) and capped at 255.
|
|
140
|
+
*
|
|
141
|
+
* Single source of truth: the back half names the dashboard from here, and the
|
|
142
|
+
* client builds the console deep link (`externalUrl`) from here, so the two can
|
|
143
|
+
* never drift. Provider-neutral name; each back half maps it to its own dashboard.
|
|
144
|
+
*/
|
|
145
|
+
export declare function dashboardName(name: string, stage: string): string;
|
|
146
|
+
/**
|
|
147
|
+
* Loads `laranja.config.ts` from the project dir. Runs under tsx, so importing a
|
|
148
|
+
* TypeScript config module Just Works. Returns the config with defaults applied,
|
|
149
|
+
* then any `overrides` (e.g. a `--stage` flag) layered on top.
|
|
150
|
+
*/
|
|
151
|
+
export declare function loadConfig(projectDir: string, overrides?: ConfigOverrides): Promise<Required<Pick<LaranjaConfig, "env" | "stage" | "provider" | "monitoring">> & LaranjaConfig>;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
export const CONFIG_FILENAME = "laranja.config.ts";
|
|
5
|
+
/**
|
|
6
|
+
* The CloudFormation stack name for a project + stage: `<name>-<stage>`.
|
|
7
|
+
*
|
|
8
|
+
* Stage is part of the stack name (not just the Lambda names) so two stages can
|
|
9
|
+
* be deployed to the SAME account without colliding — one definition, two
|
|
10
|
+
* independent stacks. With separate dev/prod accounts the suffix is harmless.
|
|
11
|
+
* Single source of truth: every command must derive the stack name from here.
|
|
12
|
+
*/
|
|
13
|
+
export function stackName(name, stage) {
|
|
14
|
+
return `${name}-${stage}`;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The monitoring dashboard's physical name: `<name>-<stage>`, sanitized to the
|
|
18
|
+
* chars CloudWatch allows (A–Z a–z 0–9 - _) and capped at 255.
|
|
19
|
+
*
|
|
20
|
+
* Single source of truth: the back half names the dashboard from here, and the
|
|
21
|
+
* client builds the console deep link (`externalUrl`) from here, so the two can
|
|
22
|
+
* never drift. Provider-neutral name; each back half maps it to its own dashboard.
|
|
23
|
+
*/
|
|
24
|
+
export function dashboardName(name, stage) {
|
|
25
|
+
return `${name}-${stage}`.replace(/[^A-Za-z0-9_-]/g, "-").slice(0, 255);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* `laranja.config.ts` is ESM (`export default`), but the project it lives in is
|
|
29
|
+
* almost always "typeless" — a Nest/Express app whose package.json has no
|
|
30
|
+
* `"type": "module"` because its own build is CommonJS. Importing the config then
|
|
31
|
+
* makes Node detect the format by syntax and print a MODULE_TYPELESS_PACKAGE_JSON
|
|
32
|
+
* warning the user can't act on without breaking their app's CJS build. It's
|
|
33
|
+
* laranja's config, so laranja swallows exactly that one warning code — everything
|
|
34
|
+
* else Node emits still gets through. Idempotent; installed once, lazily.
|
|
35
|
+
*/
|
|
36
|
+
let typelessWarningSilenced = false;
|
|
37
|
+
function silenceTypelessConfigWarning() {
|
|
38
|
+
if (typelessWarningSilenced)
|
|
39
|
+
return;
|
|
40
|
+
typelessWarningSilenced = true;
|
|
41
|
+
const original = process.emitWarning.bind(process);
|
|
42
|
+
process.emitWarning = ((warning, ...args) => {
|
|
43
|
+
const opt = args[0];
|
|
44
|
+
const code = opt && typeof opt === "object" ? opt.code : args[1];
|
|
45
|
+
if (code === "MODULE_TYPELESS_PACKAGE_JSON")
|
|
46
|
+
return;
|
|
47
|
+
return original(warning, ...args);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Loads `laranja.config.ts` from the project dir. Runs under tsx, so importing a
|
|
52
|
+
* TypeScript config module Just Works. Returns the config with defaults applied,
|
|
53
|
+
* then any `overrides` (e.g. a `--stage` flag) layered on top.
|
|
54
|
+
*/
|
|
55
|
+
export async function loadConfig(projectDir, overrides = {}) {
|
|
56
|
+
const file = path.join(projectDir, CONFIG_FILENAME);
|
|
57
|
+
if (!existsSync(file)) {
|
|
58
|
+
throw new Error(`No ${CONFIG_FILENAME} found in ${projectDir}. Run \`laranja init\` first.`);
|
|
59
|
+
}
|
|
60
|
+
silenceTypelessConfigWarning();
|
|
61
|
+
const mod = await import(pathToFileURL(file).href);
|
|
62
|
+
const cfg = mod.default ?? mod.config;
|
|
63
|
+
if (!cfg) {
|
|
64
|
+
throw new Error(`${CONFIG_FILENAME} must \`export default\` a config object.`);
|
|
65
|
+
}
|
|
66
|
+
if (!cfg.name) {
|
|
67
|
+
throw new Error(`${CONFIG_FILENAME}: "name" is required — run \`laranja init\` to link this directory to a project.`);
|
|
68
|
+
}
|
|
69
|
+
// The HTTP app is declared in code via an `http(app)` marker, which the scanner
|
|
70
|
+
// resolves — there's no config field for it. The scanner raises a clear error
|
|
71
|
+
// if there's ultimately nothing to deploy.
|
|
72
|
+
// Only AWS is implemented today. Reject any other provider up front so a
|
|
73
|
+
// forward-compatible config field never silently deploys to the wrong (or no)
|
|
74
|
+
// back-half. This guard goes away per-arm once a discriminated union of real
|
|
75
|
+
// providers exists.
|
|
76
|
+
if (cfg.provider && cfg.provider !== "aws") {
|
|
77
|
+
throw new Error(`${CONFIG_FILENAME}: provider "${cfg.provider}" isn't supported yet — only "aws" today.`);
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
stage: "dev",
|
|
81
|
+
provider: "aws",
|
|
82
|
+
monitoring: true,
|
|
83
|
+
env: {},
|
|
84
|
+
...cfg,
|
|
85
|
+
// Overrides win over both defaults and the config file (only when set).
|
|
86
|
+
...(overrides.stage ? { stage: overrides.stage } : {}),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/env.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side resolution of the env var NAMES discovered in user code (the IR's
|
|
3
|
+
* `envKeys`) against a source of values — by default the process environment.
|
|
4
|
+
*
|
|
5
|
+
* This runs on the developer's / CI machine at deploy time. The resolved VALUES
|
|
6
|
+
* are injected into the Lambda's environment locally and never travel to the
|
|
7
|
+
* server: only the names are in the IR (see `InfraIR.envKeys`).
|
|
8
|
+
*/
|
|
9
|
+
/** The outcome of resolving declared env names against a value source. */
|
|
10
|
+
export interface ResolvedEnv {
|
|
11
|
+
/** name -> value, for every declared key that had a value. */
|
|
12
|
+
resolved: Record<string, string>;
|
|
13
|
+
/** Declared keys that had no value in the source (unset). */
|
|
14
|
+
missing: string[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Resolve `envKeys` against `source` (defaults to `process.env`). A key is
|
|
18
|
+
* "missing" only when it is unset (`undefined`); an explicit empty string is a
|
|
19
|
+
* deliberate value and is kept.
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolveDeclaredEnv(envKeys: string[], source?: NodeJS.ProcessEnv): ResolvedEnv;
|
|
22
|
+
/**
|
|
23
|
+
* A valid environment-variable NAME: a letter or underscore, then letters, digits,
|
|
24
|
+
* or underscores. This is the conventional POSIX/shell shape and a superset of what
|
|
25
|
+
* AWS Lambda accepts, so anything failing this can never be a real env var — it's a
|
|
26
|
+
* typo (e.g. `env("MY_SECRET)")`, where the `)` slipped inside the quotes).
|
|
27
|
+
*/
|
|
28
|
+
export declare const ENV_NAME_PATTERN: RegExp;
|
|
29
|
+
/** Whether `key` is a syntactically valid environment-variable name. */
|
|
30
|
+
export declare function isValidEnvName(key: string): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* The CloudFormation Parameter logical id for an env key. Each code-discovered
|
|
33
|
+
* `env("NAME")` becomes a stack Parameter (so the value is supplied at deploy
|
|
34
|
+
* time, never baked into the template). Logical ids must be alphanumeric, so
|
|
35
|
+
* non-alphanumerics are stripped — this is the single source of truth shared by
|
|
36
|
+
* the stack (which declares the Parameter) and the deploy step (which supplies
|
|
37
|
+
* its value).
|
|
38
|
+
*
|
|
39
|
+
* NOTE: the stripping is lossy — `MY_SECRET` and `MYSECRET` both map to
|
|
40
|
+
* `EnvMYSECRET`, and two such keys collide as a duplicate-construct error at synth.
|
|
41
|
+
* Validated names (see `isValidEnvName`) make that rare, but a fully collision-free
|
|
42
|
+
* scheme (hash suffix) is a breaking change: the CLI and the server's bundled synth
|
|
43
|
+
* both compute this id, so it must only change in a coordinated release of both.
|
|
44
|
+
*/
|
|
45
|
+
export declare function envParamName(key: string): string;
|
|
46
|
+
/**
|
|
47
|
+
* The Lambda environment variable name that carries a declared queue's SQS URL.
|
|
48
|
+
* The synth injects `LARANJA_QUEUE_URL_<name>` into EVERY function's env (so any
|
|
49
|
+
* function can produce to any queue), and the runtime producer — `getQueue(name)`
|
|
50
|
+
* — reads the same key. This is the single source of truth shared by laranja-cdk
|
|
51
|
+
* (which sets the value) and laranja-runtime (which reads it); keyed by the
|
|
52
|
+
* queue's declared `name` (the user-facing identity), with non-alphanumerics
|
|
53
|
+
* collapsed to "_" so it is a valid env-var name.
|
|
54
|
+
*/
|
|
55
|
+
export declare function queueUrlEnvName(name: string): string;
|
package/dist/env.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side resolution of the env var NAMES discovered in user code (the IR's
|
|
3
|
+
* `envKeys`) against a source of values — by default the process environment.
|
|
4
|
+
*
|
|
5
|
+
* This runs on the developer's / CI machine at deploy time. The resolved VALUES
|
|
6
|
+
* are injected into the Lambda's environment locally and never travel to the
|
|
7
|
+
* server: only the names are in the IR (see `InfraIR.envKeys`).
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Resolve `envKeys` against `source` (defaults to `process.env`). A key is
|
|
11
|
+
* "missing" only when it is unset (`undefined`); an explicit empty string is a
|
|
12
|
+
* deliberate value and is kept.
|
|
13
|
+
*/
|
|
14
|
+
export function resolveDeclaredEnv(envKeys, source = process.env) {
|
|
15
|
+
const resolved = {};
|
|
16
|
+
const missing = [];
|
|
17
|
+
for (const key of envKeys) {
|
|
18
|
+
const value = source[key];
|
|
19
|
+
if (value === undefined)
|
|
20
|
+
missing.push(key);
|
|
21
|
+
else
|
|
22
|
+
resolved[key] = value;
|
|
23
|
+
}
|
|
24
|
+
return { resolved, missing };
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A valid environment-variable NAME: a letter or underscore, then letters, digits,
|
|
28
|
+
* or underscores. This is the conventional POSIX/shell shape and a superset of what
|
|
29
|
+
* AWS Lambda accepts, so anything failing this can never be a real env var — it's a
|
|
30
|
+
* typo (e.g. `env("MY_SECRET)")`, where the `)` slipped inside the quotes).
|
|
31
|
+
*/
|
|
32
|
+
export const ENV_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
33
|
+
/** Whether `key` is a syntactically valid environment-variable name. */
|
|
34
|
+
export function isValidEnvName(key) {
|
|
35
|
+
return ENV_NAME_PATTERN.test(key);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The CloudFormation Parameter logical id for an env key. Each code-discovered
|
|
39
|
+
* `env("NAME")` becomes a stack Parameter (so the value is supplied at deploy
|
|
40
|
+
* time, never baked into the template). Logical ids must be alphanumeric, so
|
|
41
|
+
* non-alphanumerics are stripped — this is the single source of truth shared by
|
|
42
|
+
* the stack (which declares the Parameter) and the deploy step (which supplies
|
|
43
|
+
* its value).
|
|
44
|
+
*
|
|
45
|
+
* NOTE: the stripping is lossy — `MY_SECRET` and `MYSECRET` both map to
|
|
46
|
+
* `EnvMYSECRET`, and two such keys collide as a duplicate-construct error at synth.
|
|
47
|
+
* Validated names (see `isValidEnvName`) make that rare, but a fully collision-free
|
|
48
|
+
* scheme (hash suffix) is a breaking change: the CLI and the server's bundled synth
|
|
49
|
+
* both compute this id, so it must only change in a coordinated release of both.
|
|
50
|
+
*/
|
|
51
|
+
export function envParamName(key) {
|
|
52
|
+
return `Env${key.replace(/[^A-Za-z0-9]/g, "")}`;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The Lambda environment variable name that carries a declared queue's SQS URL.
|
|
56
|
+
* The synth injects `LARANJA_QUEUE_URL_<name>` into EVERY function's env (so any
|
|
57
|
+
* function can produce to any queue), and the runtime producer — `getQueue(name)`
|
|
58
|
+
* — reads the same key. This is the single source of truth shared by laranja-cdk
|
|
59
|
+
* (which sets the value) and laranja-runtime (which reads it); keyed by the
|
|
60
|
+
* queue's declared `name` (the user-facing identity), with non-alphanumerics
|
|
61
|
+
* collapsed to "_" so it is a valid env-var name.
|
|
62
|
+
*/
|
|
63
|
+
export function queueUrlEnvName(name) {
|
|
64
|
+
return `LARANJA_QUEUE_URL_${name.replace(/[^A-Za-z0-9]/g, "_")}`;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=env.js.map
|
package/dist/env.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.js","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAUH;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAAiB,EACjB,SAA4B,OAAO,CAAC,GAAG;IAEvC,MAAM,QAAQ,GAA2B,EAAE,CAAC;IAC5C,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;QAC1B,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QAC1B,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;;YACtC,QAAQ,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IAC7B,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;AAC/B,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,0BAA0B,CAAC;AAE3D,wEAAwE;AACxE,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,OAAO,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACpC,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,OAAO,MAAM,GAAG,CAAC,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC,EAAE,CAAC;AAClD,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,OAAO,qBAAqB,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,GAAG,CAAC,EAAE,CAAC;AACnE,CAAC"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from "./ir.js";
|
|
2
|
+
export * from "./config.js";
|
|
3
|
+
export * from "./env.js";
|
|
4
|
+
export * from "./schedule.js";
|
|
5
|
+
export * from "./nest-schedule.js";
|
|
6
|
+
export * from "./api.js";
|
|
7
|
+
export * from "./client.js";
|
|
8
|
+
export * from "./auth.js";
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC"}
|