@gapi/gcli 1.8.230 → 1.8.232
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +310 -0
- package/eslint.config.mjs +6 -1
- package/package.json +2 -3
- package/release/index.js +7723 -7985
package/README.md
CHANGED
|
@@ -205,6 +205,316 @@ options:
|
|
|
205
205
|
external: []
|
|
206
206
|
```
|
|
207
207
|
|
|
208
|
+
### MCP Server Functions
|
|
209
|
+
|
|
210
|
+
A function can be provisioned as an **MCP Server** in front of a **federation graph** by setting `mcp: true`. The MCP lambda has **no source code** — it is fully described by config: the selected `mcpGraph` (a federation lambda in the same project) is resolved to its internal router URL and **introspected** (single source of truth, no schema file), and the curated `mcpOperations` become MCP tools. Use the MCP runtime image and `newdeploy` + `minScale: 1` (MCP holds long-lived streamable-HTTP sessions).
|
|
211
|
+
|
|
212
|
+
```yaml
|
|
213
|
+
function:
|
|
214
|
+
name: my-mcp
|
|
215
|
+
route: mcp
|
|
216
|
+
method: ['GET', 'POST'] # streamable HTTP needs both
|
|
217
|
+
network: ['public']
|
|
218
|
+
env: nodejs-graphql-mcp # the MCP runtime image
|
|
219
|
+
|
|
220
|
+
mcp: true
|
|
221
|
+
mcpGraph: my-federation-gateway # an existing federation lambda in this project
|
|
222
|
+
|
|
223
|
+
# Curated operations exposed as MCP tools. The tool name is the OPERATION name (must be named);
|
|
224
|
+
# optional — the introspection/search/execute tools work without any.
|
|
225
|
+
mcpOperations:
|
|
226
|
+
- query: |
|
|
227
|
+
query GetUser($id: ID!) { user(id: $id) { id name email } }
|
|
228
|
+
|
|
229
|
+
# Static headers sent on EVERY upstream request (e.g. a fixed API key / service token).
|
|
230
|
+
mcpHeaders:
|
|
231
|
+
- name: X-Api-Key
|
|
232
|
+
value: replace-me
|
|
233
|
+
|
|
234
|
+
# Extra request headers forwarded from the caller (Authorization is ALWAYS forwarded).
|
|
235
|
+
mcpForwardHeaders:
|
|
236
|
+
- name: X-Tenant-Id
|
|
237
|
+
|
|
238
|
+
scaleOptions:
|
|
239
|
+
executorType: newdeploy
|
|
240
|
+
minScale: 1
|
|
241
|
+
maxScale: 4
|
|
242
|
+
targetCpu: 70
|
|
243
|
+
|
|
244
|
+
environment:
|
|
245
|
+
name: nodejs-graphql-mcp
|
|
246
|
+
image: rxdi/fission-nodejs-graphql-mcp:0.0.2
|
|
247
|
+
builder: rxdi/fission-node-builder:1.0.8
|
|
248
|
+
poolSize: 0
|
|
249
|
+
region: EU_CENTRAL
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
gcli lambda:create --project <projectId> --spec ./lambforge.yaml
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
`--mcp` and `--mcpGraph <name>` are also available as CLI flags; the array fields
|
|
257
|
+
(`mcpOperations`, `mcpHeaders`, `mcpForwardHeaders`) are spec-only. A full annotated example lives in
|
|
258
|
+
[`example/mcp/lambforge.yaml`](./example/mcp/lambforge.yaml). Client connects at
|
|
259
|
+
`https://<host>/<projectId>/<route>/mcp`.
|
|
260
|
+
|
|
261
|
+
### Conditional Configuration (Environment-Aware Specs)
|
|
262
|
+
|
|
263
|
+
A single spec can describe **many deploy variants** without duplicating the file. The bundler/loader understands a small set of conditional tags that are resolved at load time from external environment variables, plus native YAML references (`&anchor` / `*alias`) and merge keys (`<<`) for de-duplication.
|
|
264
|
+
|
|
265
|
+
This is how you ship the **same code to two different targets** — e.g. the legacy Docker servers (where everything must be bundled) and the new [lambforge.com](https://www.lambforge.com) platform (where the `@gapi/core` framework is preinstalled in the environment image, so it must stay _external_ to avoid double-bundling).
|
|
266
|
+
|
|
267
|
+
#### Supported tags
|
|
268
|
+
|
|
269
|
+
| Tag | Form | Resolves to |
|
|
270
|
+
| --------- | ------------------------------------------ | ----------------------------------------------------------------- |
|
|
271
|
+
| `!env` | `!env VAR` | `process.env.VAR` (or `null` when unset) |
|
|
272
|
+
| `!env` | `!env [VAR, default]` | `process.env.VAR ?? default` |
|
|
273
|
+
| `!switch` | `!switch { var, default, cases }` | `cases[process.env[var] ?? default]` |
|
|
274
|
+
| `!if` | `!if { when, then, else }` | `then` when `when` is truthy, else `else` |
|
|
275
|
+
| `!if` | `!if { var, equals, default, then, else }` | `then` when `process.env[var] ?? default === equals`, else `else` |
|
|
276
|
+
|
|
277
|
+
Notes:
|
|
278
|
+
|
|
279
|
+
- `!switch` requires a `var`. When the selected value has no matching case it falls back to `cases[default]`, otherwise `null`.
|
|
280
|
+
- `!if` truthiness treats `null`, `false`, `''`, `"false"` and `"0"` as **falsy**.
|
|
281
|
+
- Tags work **anywhere** in the file (any field — `external`, `scaleOptions`, `image`, `network`, …), and may be nested (e.g. an `!env` inside an `!if`).
|
|
282
|
+
- Specs that use **no** tags keep parsing exactly as before.
|
|
283
|
+
|
|
284
|
+
#### Recipes
|
|
285
|
+
|
|
286
|
+
##### 1. One file, two deploys (the flagship use case)
|
|
287
|
+
|
|
288
|
+
Select the bundler externals at build/deploy time. The new lambforge.com platform ships the framework preinstalled (keep it external), while the legacy Docker image ships nothing (bundle everything).
|
|
289
|
+
|
|
290
|
+
```yaml
|
|
291
|
+
# Framework packages shipped preinstalled by the lambforge.com env image.
|
|
292
|
+
# Declared once as an anchor and reused below.
|
|
293
|
+
.frameworkExternals: &frameworkExternals
|
|
294
|
+
- '@gapi/core'
|
|
295
|
+
- '@rxdi/core'
|
|
296
|
+
- 'graphql'
|
|
297
|
+
- 'rxjs'
|
|
298
|
+
- 'reflect-metadata'
|
|
299
|
+
# ...the rest of the preinstalled packages
|
|
300
|
+
|
|
301
|
+
options:
|
|
302
|
+
bundler:
|
|
303
|
+
bundle: true
|
|
304
|
+
target: node24
|
|
305
|
+
outfile: 'index.js'
|
|
306
|
+
external: !switch
|
|
307
|
+
var: LAMBFORGE_TARGET
|
|
308
|
+
default: lambforge # unset => new platform => keep framework external
|
|
309
|
+
cases:
|
|
310
|
+
lambforge: *frameworkExternals
|
|
311
|
+
docker: [] # legacy servers => nothing preinstalled => bundle everything
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
```bash
|
|
315
|
+
# New lambforge.com platform (default) — framework stays external, slim bundle
|
|
316
|
+
gcli lambda:create
|
|
317
|
+
|
|
318
|
+
# Legacy Docker servers — bundle every dependency
|
|
319
|
+
LAMBFORGE_TARGET=docker gcli lambda:create
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
##### 2. Per-stage autoscaling (anchors + merge keys for DRY)
|
|
323
|
+
|
|
324
|
+
Define a base block once, then override only what changes per stage. Branches that are YAML literals keep their type (numbers stay numbers).
|
|
325
|
+
|
|
326
|
+
```yaml
|
|
327
|
+
# Shared baseline, declared once.
|
|
328
|
+
.baseScale: &baseScale
|
|
329
|
+
minCpu: 30
|
|
330
|
+
maxCpu: 500
|
|
331
|
+
minMemory: 128
|
|
332
|
+
maxMemory: 1024
|
|
333
|
+
targetCpu: 80
|
|
334
|
+
executorType: newdeploy
|
|
335
|
+
concurrency: 1000
|
|
336
|
+
functionTimeout: 120
|
|
337
|
+
|
|
338
|
+
function:
|
|
339
|
+
name: graphql-server-lambdas
|
|
340
|
+
scaleOptions:
|
|
341
|
+
<<: *baseScale # merge in the shared baseline…
|
|
342
|
+
# …and override per environment
|
|
343
|
+
minScale:
|
|
344
|
+
!switch {
|
|
345
|
+
var: DEPLOY_ENV,
|
|
346
|
+
default: dev,
|
|
347
|
+
cases: { prod: 2, staging: 1, dev: 0 },
|
|
348
|
+
}
|
|
349
|
+
maxScale:
|
|
350
|
+
!switch {
|
|
351
|
+
var: DEPLOY_ENV,
|
|
352
|
+
default: dev,
|
|
353
|
+
cases: { prod: 10, staging: 4, dev: 2 },
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
##### 3. Region / image / builder selection (strings with sane defaults)
|
|
358
|
+
|
|
359
|
+
```yaml
|
|
360
|
+
environment:
|
|
361
|
+
name: nodejs-graphql-lambdas
|
|
362
|
+
# `!env [VAR, default]` — falls back to the default when the var is unset.
|
|
363
|
+
image: !env [ENV_IMAGE, 'rxdi/fission-nodejs-graphql-lambdas:0.0.4']
|
|
364
|
+
builder: !env [ENV_BUILDER, 'rxdi/fission-node-builder:1.0.8']
|
|
365
|
+
region: !switch
|
|
366
|
+
var: REGION
|
|
367
|
+
default: eu
|
|
368
|
+
cases:
|
|
369
|
+
eu: EU_CENTRAL
|
|
370
|
+
us: US_EAST
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
##### 4. Network visibility & secrets per stage
|
|
374
|
+
|
|
375
|
+
```yaml
|
|
376
|
+
function:
|
|
377
|
+
# Public only in dev; private everywhere else.
|
|
378
|
+
network: !switch
|
|
379
|
+
var: DEPLOY_ENV
|
|
380
|
+
default: prod
|
|
381
|
+
cases:
|
|
382
|
+
dev: ['public']
|
|
383
|
+
staging: ['private']
|
|
384
|
+
prod: ['private']
|
|
385
|
+
# Swap the mounted secret set per stage.
|
|
386
|
+
secrets: !switch
|
|
387
|
+
var: DEPLOY_ENV
|
|
388
|
+
default: prod
|
|
389
|
+
cases:
|
|
390
|
+
dev: ['environment-dev']
|
|
391
|
+
prod: ['environment']
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
##### 5. Build toggles (minify only in production)
|
|
395
|
+
|
|
396
|
+
```yaml
|
|
397
|
+
options:
|
|
398
|
+
bundler:
|
|
399
|
+
target: !env [BUILD_TARGET, node24]
|
|
400
|
+
# `!if { var, equals }` returns real booleans.
|
|
401
|
+
minify: !if { var: NODE_ENV, equals: production, then: true, else: false }
|
|
402
|
+
bundle: !if { var: NO_BUNDLE, equals: 'true', then: false, else: true }
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
##### 6. Feature flags with `!if` truthiness
|
|
406
|
+
|
|
407
|
+
`!if { when }` branches on the truthiness of a value. `!env DEBUG` resolves to `null` when unset and to the string when set; `null`, `''`, `"false"` and `"0"` are treated as falsy.
|
|
408
|
+
|
|
409
|
+
```yaml
|
|
410
|
+
options:
|
|
411
|
+
bundler:
|
|
412
|
+
# Ship sourcemaps-style verbose build only when DEBUG is set to a truthy value.
|
|
413
|
+
minify: !if { when: !env DEBUG, then: false, else: true }
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
##### 7. Nested conditionals
|
|
417
|
+
|
|
418
|
+
Tags compose — a `!switch` branch can itself be an `!if`, an `!env`, or another `!switch`.
|
|
419
|
+
|
|
420
|
+
```yaml
|
|
421
|
+
environment:
|
|
422
|
+
region: !switch
|
|
423
|
+
var: DEPLOY_ENV
|
|
424
|
+
default: dev
|
|
425
|
+
cases:
|
|
426
|
+
# In prod, pick the region from REGION; everywhere else stay in EU.
|
|
427
|
+
prod: !if { var: REGION, equals: us, then: US_EAST, else: EU_CENTRAL }
|
|
428
|
+
dev: EU_CENTRAL
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
##### 8. Putting it together — a multi-stage spec
|
|
432
|
+
|
|
433
|
+
```yaml
|
|
434
|
+
.frameworkExternals: &frameworkExternals
|
|
435
|
+
- '@gapi/core'
|
|
436
|
+
- 'rxjs'
|
|
437
|
+
- 'graphql'
|
|
438
|
+
- 'reflect-metadata'
|
|
439
|
+
|
|
440
|
+
.baseScale: &baseScale
|
|
441
|
+
minCpu: 30
|
|
442
|
+
maxCpu: 500
|
|
443
|
+
minMemory: 128
|
|
444
|
+
maxMemory: 1024
|
|
445
|
+
|
|
446
|
+
function:
|
|
447
|
+
name: graphql-server-lambdas
|
|
448
|
+
route: graphql-server-lambdas
|
|
449
|
+
file: ./src/main.ts
|
|
450
|
+
network:
|
|
451
|
+
!switch {
|
|
452
|
+
var: DEPLOY_ENV,
|
|
453
|
+
default: prod,
|
|
454
|
+
cases: { dev: ['public'], prod: ['private'] },
|
|
455
|
+
}
|
|
456
|
+
scaleOptions:
|
|
457
|
+
<<: *baseScale
|
|
458
|
+
minScale:
|
|
459
|
+
!switch { var: DEPLOY_ENV, default: dev, cases: { prod: 2, dev: 0 } }
|
|
460
|
+
maxScale:
|
|
461
|
+
!switch { var: DEPLOY_ENV, default: dev, cases: { prod: 10, dev: 2 } }
|
|
462
|
+
|
|
463
|
+
options:
|
|
464
|
+
bundler:
|
|
465
|
+
target: node24
|
|
466
|
+
minify: !if { var: DEPLOY_ENV, equals: prod, then: true, else: false }
|
|
467
|
+
external: !switch
|
|
468
|
+
var: LAMBFORGE_TARGET
|
|
469
|
+
default: lambforge
|
|
470
|
+
cases:
|
|
471
|
+
lambforge: *frameworkExternals
|
|
472
|
+
docker: []
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
```bash
|
|
476
|
+
# Production deploy to the new platform
|
|
477
|
+
DEPLOY_ENV=prod gcli lambda:update
|
|
478
|
+
|
|
479
|
+
# Dev deploy, still bundling everything for a local Docker run
|
|
480
|
+
DEPLOY_ENV=dev LAMBFORGE_TARGET=docker gcli lambda:create
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
#### Setting the variables
|
|
484
|
+
|
|
485
|
+
The tags read from `process.env` at the moment `gcli` loads the spec (i.e. at build/deploy time), so set them however that process is launched:
|
|
486
|
+
|
|
487
|
+
```bash
|
|
488
|
+
# Inline for a single command
|
|
489
|
+
LAMBFORGE_TARGET=docker gcli build
|
|
490
|
+
|
|
491
|
+
# Exported for a whole shell session / script
|
|
492
|
+
export DEPLOY_ENV=prod
|
|
493
|
+
gcli lambda:update
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
```yaml
|
|
497
|
+
# GitHub Actions — per step or job
|
|
498
|
+
- run: npx gcli lambda:update
|
|
499
|
+
env:
|
|
500
|
+
DEPLOY_ENV: prod
|
|
501
|
+
# LAMBFORGE_TARGET left unset => defaults to the new platform
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
```dockerfile
|
|
505
|
+
# Dockerfile — bake the legacy target into the image build
|
|
506
|
+
ENV LAMBFORGE_TARGET=docker
|
|
507
|
+
RUN npm run build:es # gcli build runs here and reads the value
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
#### Types & gotchas
|
|
511
|
+
|
|
512
|
+
- **`!env` is always a string.** `process.env` values are strings, so `!env [PORT, 8080]` yields the number `8080` when unset but the string `"3000"` when `PORT=3000`. For numeric/boolean fields prefer `!switch`/`!if` with YAML literal branches (e.g. `cases: { prod: 10, dev: 2 }`) — those preserve real numbers and booleans.
|
|
513
|
+
- **`!if { equals }` compares as strings**, so `equals: 'true'` matches the string `"true"`.
|
|
514
|
+
- **`!switch` requires `var`** and falls back to `cases[default]` (then `null`) when the resolved value has no case.
|
|
515
|
+
- **Define anchors before use.** `&anchor` must appear earlier in the file than the `*alias` that references it. A common convention is a leading `.`-prefixed key (e.g. `.frameworkExternals:`), which consumers ignore.
|
|
516
|
+
- **Custom tags require the `gcli` loader.** Other YAML tooling that reads the file with a vanilla parser will error on `!switch` / `!if` / `!env` (`unknown mapping tag`). The CLI itself always understands them.
|
|
517
|
+
|
|
208
518
|
### Configuration Auto-Discovery (Zero-Argument Commands)
|
|
209
519
|
|
|
210
520
|
`gcli` is designed to be context-aware. If a configuration file (`lambforge.yaml`, `spec.yaml`, or `env.yaml`) is present in your current directory, you can run commands without arguments.
|
package/eslint.config.mjs
CHANGED
|
@@ -8,7 +8,12 @@ import prettierConfig from 'eslint-config-prettier';
|
|
|
8
8
|
export default defineConfig([
|
|
9
9
|
globalIgnores(['dist/*', './release', './example', 'coverage']),
|
|
10
10
|
{
|
|
11
|
-
files: [
|
|
11
|
+
files: [
|
|
12
|
+
'./src/**/*.ts',
|
|
13
|
+
'./src/**/*.tsx',
|
|
14
|
+
'./test/**/*.ts',
|
|
15
|
+
'./test/**/*.tsx',
|
|
16
|
+
],
|
|
12
17
|
languageOptions: {
|
|
13
18
|
parser: tsParser,
|
|
14
19
|
ecmaVersion: 2018,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gapi/gcli",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.232",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/Stradivario/gapi.git"
|
|
@@ -40,7 +40,6 @@
|
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@rxdi/compressor": "^0.7.249",
|
|
42
42
|
"@types/jest": "^25.2.1",
|
|
43
|
-
"@types/js-yaml": "^4.0.3",
|
|
44
43
|
"@types/node": "^25.0.3",
|
|
45
44
|
"@types/node-fetch": "^2.5.7",
|
|
46
45
|
"@typescript-eslint/eslint-plugin": "^8.50.1",
|
|
@@ -54,7 +53,7 @@
|
|
|
54
53
|
"firebase": "7.14.6",
|
|
55
54
|
"form-data": "^4.0.5",
|
|
56
55
|
"jest": "^30.2.0",
|
|
57
|
-
"js-yaml": "^
|
|
56
|
+
"js-yaml": "^5.0.0",
|
|
58
57
|
"node-fetch": "^3.3.2",
|
|
59
58
|
"prettier": "^3.8.1",
|
|
60
59
|
"rxjs": "^7.8.2",
|