@go-to-k/cdkd 0.157.0 → 0.158.1
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 +63 -160
- package/dist/cli.js +640 -159
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-UmoqjtWH.js → deploy-engine-CGmdz5WP.js} +38 -36
- package/dist/{deploy-engine-UmoqjtWH.js.map → deploy-engine-CGmdz5WP.js.map} +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# cdkd (CDK Direct)
|
|
2
2
|
|
|
3
|
-
Drop-in CDK CLI for existing CDK apps — faster deploys via AWS SDK instead of CloudFormation,
|
|
3
|
+
Drop-in CDK CLI for existing CDK apps — faster deploys via AWS SDK instead of CloudFormation, plus local emulation for Lambda, API Gateway, and ECS.
|
|
4
4
|
|
|
5
5
|
- **Drop-in CDK compatible** — your existing CDK app code runs as-is.
|
|
6
6
|
- **Up to 15x faster deploys than the AWS CDK CLI (CloudFormation)**
|
|
7
|
-
- **Local dev for CDK
|
|
7
|
+
- **Local dev for any CDK app** — invoke Lambdas, serve API Gateway routes, run ECS tasks/services directly from your CDK code. Works against both `cdkd deploy`-managed AND `cdk deploy`-managed (CloudFormation) stacks via `--from-state` / `--from-cfn-stack` — no migration, no `cdk synth → sam local` round-trip.
|
|
8
8
|
|
|
9
9
|

|
|
10
10
|
|
|
11
|
-
**cdkd complements the AWS CDK CLI rather than replacing it.** Use cdkd in dev/test for rapid iteration and SAM-style local execution; use the AWS CDK CLI in production for full CloudFormation tooling. Bidirectional migration is supported — [import
|
|
11
|
+
**cdkd complements the AWS CDK CLI rather than replacing it.** Use cdkd in dev/test for rapid iteration and SAM-style local execution; use the AWS CDK CLI in production for full CloudFormation tooling. Install cdkd alongside an existing `cdk deploy` workflow — no migration needed, `cdkd local *` reads deployed state directly via `--from-cfn-stack`. Bidirectional migration is also supported — [import](#importing-existing-resources) into cdkd or [export](#exporting-a-stack-back-to-cloudformation) back to CloudFormation when ready.
|
|
12
12
|
|
|
13
13
|
> [!IMPORTANT]
|
|
14
14
|
> cdkd is for dev/test workflows only — early in development, not yet production-ready.
|
|
@@ -25,10 +25,10 @@ Drop-in CDK CLI for existing CDK apps — faster deploys via AWS SDK instead of
|
|
|
25
25
|
- **Rollback on failure**: When a deploy errors mid-stack, cdkd rolls back the resources it just created so the stack state stays consistent (CloudFormation parity — but cdkd does this without round-tripping through CFn). Pass `cdkd deploy --no-rollback` to skip rollback and keep the partial state for Terraform-style inspection / repair. See [Rollback behavior](#rollback-behavior).
|
|
26
26
|
- **`--no-wait` for async resources**: Skip the multi-minute wait on CloudFront / RDS / ElastiCache / NAT Gateway and return as soon as the create call returns (CloudFormation always blocks)
|
|
27
27
|
- **VPC route DependsOn relaxation (on by default)**: Drop CDK-injected defensive `DependsOn` edges from VPC Lambdas onto private-subnet routes so `CloudFront::Distribution` and `Lambda::Url` start their ~3-min propagation in parallel with NAT Gateway stabilization (~50% faster on VPC + Lambda + CloudFront stacks). Pass `--no-aggressive-vpc-parallel` to opt out.
|
|
28
|
-
- **Local execution
|
|
29
|
-
- **Bidirectional CloudFormation migration**: `cdkd import` adopts
|
|
28
|
+
- **Local execution** (`cdkd local invoke` / `start-api` / `run-task` / `start-service`): run Lambdas, API Gateway routes, ECS tasks and long-running ECS services from your CDK code via Docker. All AWS Lambda runtimes, container Lambdas, REST v1 / HTTP v2 / Function URL routes, Service Connect / Cloud Map. Works for both `cdkd deploy`-managed (`--from-state`) AND `cdk deploy`-managed (`--from-cfn-stack`) stacks. See [Local execution](#local-execution).
|
|
29
|
+
- **Bidirectional CloudFormation migration**: `cdkd import --migrate-from-cloudformation` adopts existing CFn stacks (including `cdk deploy`-managed) into cdkd state without re-creating resources; `cdkd export` hands a cdkd stack back to CloudFormation when production-ready. See [Importing](#importing-existing-resources) / [Exporting](#exporting-a-stack-back-to-cloudformation).
|
|
30
30
|
|
|
31
|
-
> **Note**: Resource types not covered by either SDK Providers or Cloud Control API cannot be deployed with cdkd.
|
|
31
|
+
> **Note**: Resource types not covered by either SDK Providers or Cloud Control API cannot be deployed with cdkd. Deployment fails with a clear error message naming the type + a 1-click issue link.
|
|
32
32
|
|
|
33
33
|
## Benchmark
|
|
34
34
|
|
|
@@ -360,34 +360,7 @@ full reference. For per-resource-type provisioning support (SDK Providers
|
|
|
360
360
|
vs Cloud Control API fallback), see
|
|
361
361
|
**[docs/supported-resources.md](docs/supported-resources.md)**.
|
|
362
362
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
`Fn::ImportValue` is a **strong reference**: `cdkd destroy <producer>`
|
|
366
|
-
refuses to delete a stack while any other stack still imports one of
|
|
367
|
-
its exports via `Fn::ImportValue`. Matches CloudFormation's behavior.
|
|
368
|
-
The error message names every offending consumer and points at the
|
|
369
|
-
two valid resolution paths (destroy the consumer first, or remove
|
|
370
|
-
the `Fn::ImportValue` from the consumer's template and redeploy).
|
|
371
|
-
|
|
372
|
-
`Fn::GetStackOutput` (cdkd-specific) is a **weak reference**: the
|
|
373
|
-
producer stays deletable independently of consumers. Use it when you
|
|
374
|
-
intentionally want decoupled lifecycles (cross-region / cross-stage /
|
|
375
|
-
staging environments).
|
|
376
|
-
|
|
377
|
-
A persistent per-region exports index at
|
|
378
|
-
`s3://{state-bucket}/cdkd/_index/{region}/exports.json` makes
|
|
379
|
-
`Fn::ImportValue` resolution O(1) at scale (200-stack environments
|
|
380
|
-
resolve in ~100ms vs minutes with the pre-#343 per-resolve scan).
|
|
381
|
-
The index is a derived view rebuilt from `state.json` on demand —
|
|
382
|
-
state.json remains the canonical source of truth, and strong-reference
|
|
383
|
-
safety checks scan it directly rather than trusting the index.
|
|
384
|
-
|
|
385
|
-
See **[docs/cross-stack-references.md](docs/cross-stack-references.md)**
|
|
386
|
-
for the full design (`Fn::ImportValue` strong-reference rules added in
|
|
387
|
-
state schema v4, lifecycle, locking, failure modes). State schema is at
|
|
388
|
-
v5 since v0.100.0; `DeletionPolicy` / `UpdateReplacePolicy` changes
|
|
389
|
-
between deploys are now detected and surfaced (see
|
|
390
|
-
[docs/state-management.md](docs/state-management.md)).
|
|
363
|
+
**Property-level coverage is incremental.** SDK Providers wire most but not every CFn property of a supported type. cdkd fails fast at pre-flight when a template uses a not-yet-implemented property, with the property name + a 1-click issue link. `--allow-unsupported-properties <Type>:<Prop>,...` is the safety valve when this is too strict (e.g. mid-life update on an existing resource); avoid it on security-meaningful properties (encryption / IAM / TLS). See [docs/cli-reference.md](docs/cli-reference.md#--allow-unsupported-properties-deploy).
|
|
391
364
|
|
|
392
365
|
## Rollback behavior
|
|
393
366
|
|
|
@@ -453,90 +426,63 @@ maintain, no `cdk synth | sam ...` round-trip.
|
|
|
453
426
|
| `cdkd local run-task <target>` | ECS RunTask — every container in a task definition started on a per-task docker network |
|
|
454
427
|
| `cdkd local start-service <target>` | Long-running ECS Service emulator — `DesiredCount` replicas with restart-on-exit (no local load balancer in v1) |
|
|
455
428
|
|
|
456
|
-
Requires Docker. Pass `--from-state`
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
same-region); without it, intrinsic values are dropped with a per-key
|
|
462
|
-
warning (matches `sam local *` semantics).
|
|
429
|
+
Requires Docker. Pass `--from-state` (cdkd-deployed) or
|
|
430
|
+
`--from-cfn-stack` (cdk-deployed / CFn-managed) to substitute deployed
|
|
431
|
+
physical IDs into intrinsic-valued env vars / secrets / image URIs;
|
|
432
|
+
without either, intrinsic values are dropped with a per-key warning
|
|
433
|
+
(matches `sam local *`). The two flags are mutually exclusive.
|
|
463
434
|
|
|
464
435
|
### `local invoke`
|
|
465
436
|
|
|
466
437
|
```bash
|
|
467
|
-
cdkd local invoke MyStack/
|
|
438
|
+
cdkd local invoke MyStack/Handler # one-shot invoke
|
|
468
439
|
cdkd local invoke MyStack/Handler --event events/get.json
|
|
469
|
-
cdkd local invoke MyStack/Handler --from-state
|
|
440
|
+
cdkd local invoke MyStack/Handler --from-state # OR --from-cfn-stack
|
|
470
441
|
```
|
|
471
442
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
and same-stack Lambda Layers bind-mounted at `/opt`.
|
|
443
|
+
All AWS Lambda runtimes (Node.js / Python / Ruby / Java / .NET /
|
|
444
|
+
`provided.al2023`), ZIP and container Lambdas, same-stack Lambda Layers
|
|
445
|
+
bind-mounted at `/opt`.
|
|
476
446
|
|
|
477
447
|
### `local start-api`
|
|
478
448
|
|
|
479
449
|
```bash
|
|
480
|
-
cdkd local start-api
|
|
481
|
-
cdkd local start-api --
|
|
482
|
-
cdkd local start-api
|
|
483
|
-
cdkd local start-api MyStack/MyHttpApi # OR: CDK Construct path
|
|
484
|
-
cdkd local start-api --warm --watch # pre-start + hot reload
|
|
485
|
-
cdkd local start-api --from-state # substitute deployed env vars in Lambda Environment
|
|
450
|
+
cdkd local start-api # one HTTP server per discovered API
|
|
451
|
+
cdkd local start-api MyStack/MyHttpApi --watch # filter + hot reload
|
|
452
|
+
cdkd local start-api --from-state # OR --from-cfn-stack
|
|
486
453
|
```
|
|
487
454
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
request template + response-template VTL), **HTTP_PROXY** (verbatim
|
|
493
|
-
upstream forward with `RequestParameters` mappings), **HTTP**
|
|
494
|
-
(HTTP_PROXY + bidirectional VTL), and **AWS** Lambda non-proxy
|
|
495
|
-
(request + response VTL via the hand-rolled engine at
|
|
496
|
-
`src/local/vtl-engine.ts`).
|
|
497
|
-
Direct AWS-service integrations (S3 / SQS / SNS / DynamoDB / etc.) are
|
|
498
|
-
NOT emulated — deploy to AWS or use HTTP_PROXY to a local mock instead.
|
|
499
|
-
Authorizers: Lambda TOKEN / REQUEST, Cognito User Pool, HTTP v2 JWT
|
|
500
|
-
(JWKS-verified), and REST v1 `AuthorizationType: 'AWS_IAM'` (SigV4
|
|
501
|
-
signature verification only — IAM policy evaluation is not emulated;
|
|
502
|
-
see `docs/local-emulation.md`). CORS preflight (HTTP API v2
|
|
503
|
-
`CorsConfiguration` + REST v1 OPTIONS MOCK preflight from
|
|
504
|
-
`defaultCorsPreflightOptions`); hot reload via `--watch`;
|
|
505
|
-
deploy-state-backed env var substitution via `--from-state`.
|
|
506
|
-
|
|
507
|
-
Function URL `InvokeMode: RESPONSE_STREAM` is supported (issue #467):
|
|
508
|
-
streaming Lambdas are invoked via the RIE streaming protocol and the
|
|
509
|
-
response is piped to the HTTP client with `Transfer-Encoding: chunked`.
|
|
510
|
-
Note that AWS's local RIE buffers the response — incremental chunk
|
|
511
|
-
delivery only manifests against the deployed Lambda runtime; locally
|
|
512
|
-
the response shape is correct but arrives in one block.
|
|
513
|
-
|
|
514
|
-
Routes whose integration cdkd cannot emulate (REST v1 AWS integration
|
|
515
|
-
to a non-Lambda service, HTTP_PROXY / HTTP with non-literal `Uri`, HTTP
|
|
516
|
-
API v2 service integrations, WebSocket APIs, Function URLs with IAM
|
|
517
|
-
auth, cross-stack Lambda Arn references) **do not block boot** — the
|
|
518
|
-
server starts with a per-route `[warn]` summary and returns HTTP 501 +
|
|
519
|
-
the reason in the JSON body if and when the route is hit. This lets
|
|
520
|
-
you run the rest of your API surface locally while the unsupported
|
|
521
|
-
routes stay on the deployed API.
|
|
455
|
+
REST v1 + HTTP API v2 + Function URL with all integration kinds
|
|
456
|
+
(AWS_PROXY / MOCK / HTTP_PROXY / HTTP / AWS Lambda non-proxy via
|
|
457
|
+
hand-rolled VTL), authorizers (Lambda / Cognito / HTTP v2 JWT /
|
|
458
|
+
REST v1 AWS_IAM SigV4), CORS, stage variables, `--watch` hot reload.
|
|
522
459
|
|
|
523
460
|
### `local run-task`
|
|
524
461
|
|
|
525
462
|
```bash
|
|
526
463
|
cdkd local run-task MyStack/MyService/TaskDef
|
|
527
|
-
cdkd local run-task MyTaskDef --from-state
|
|
464
|
+
cdkd local run-task MyTaskDef --from-state # OR --from-cfn-stack
|
|
528
465
|
```
|
|
529
466
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
(`amazon/amazon-ecs-local-container-endpoints`). `DependsOn` /
|
|
533
|
-
`Secrets` / `Volumes` (Host / Docker) are honored; `Secrets[].ValueFrom`
|
|
534
|
-
is resolved from SecretsManager / SSM at startup.
|
|
467
|
+
Every container in the task definition on a per-task docker network
|
|
468
|
+
with the AWS-published ECS metadata sidecar.
|
|
535
469
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
470
|
+
### `local start-service`
|
|
471
|
+
|
|
472
|
+
```bash
|
|
473
|
+
cdkd local start-service MyStack/Orders MyStack/Web # multiple services in one invocation
|
|
474
|
+
cdkd local start-service MyStack/Orders --from-state # OR --from-cfn-stack
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
Long-running ECS Service emulator: `DesiredCount` replicas with
|
|
478
|
+
restart-on-exit, cross-service Service Connect / Cloud Map DNS
|
|
479
|
+
discovery (peer containers reach each other by `<discoveryName>.<namespace>`).
|
|
480
|
+
No local load-balancer in v1.
|
|
481
|
+
|
|
482
|
+
See **[docs/local-emulation.md](docs/local-emulation.md)** for the
|
|
483
|
+
full reference — runtimes, target resolution, every flag, integration
|
|
484
|
+
and authorizer detail, route precedence, container pool, networking,
|
|
485
|
+
`--from-cfn-stack` semantics, v1 scope.
|
|
540
486
|
|
|
541
487
|
## Importing existing resources
|
|
542
488
|
|
|
@@ -573,63 +519,31 @@ parity matrix vs upstream `cdk import`.
|
|
|
573
519
|
## Exporting a stack back to CloudFormation
|
|
574
520
|
|
|
575
521
|
`cdkd export` is the mirror of `cdkd import`: it hands a cdkd-managed
|
|
576
|
-
stack
|
|
522
|
+
stack back to CloudFormation via a CFn `ChangeSetType=IMPORT` changeset.
|
|
577
523
|
AWS resources are unchanged across the migration; cdkd state for the
|
|
578
524
|
exported stack is deleted on success. From then on the stack is managed
|
|
579
|
-
by `cdk deploy` / `aws cloudformation`.
|
|
580
|
-
|
|
581
|
-
Lambda-backed Custom Resources (`Custom::*` and
|
|
582
|
-
`AWS::CloudFormation::CustomResource`) are NOT directly CFn-importable.
|
|
583
|
-
`cdkd export --include-non-importable` opts into a 2-phase migration
|
|
584
|
-
to handle them: phase 1 IMPORT changeset adopts every importable
|
|
585
|
-
resource, then phase 2 UPDATE changeset re-CREATEs the Custom Resources
|
|
586
|
-
through CFn — which re-invokes each backing Lambda's onCreate handler.
|
|
587
|
-
The Custom Resource Lambda must be idempotent AND must POST to
|
|
588
|
-
`event.ResponseURL` per the cfn-response protocol. Without the flag,
|
|
589
|
-
the command refuses to proceed and the user is expected to destroy
|
|
590
|
-
the offending resources (or accept abandoning them) first. Nested
|
|
591
|
-
`AWS::CloudFormation::Stack` rows are fully supported as of
|
|
592
|
-
[#464](https://github.com/go-to-k/cdkd/issues/464) PR B2: `cdkd export`
|
|
593
|
-
recursively walks the cdkd state tree, validates every parent → child
|
|
594
|
-
link, and submits **IMPORT changesets per cdkd-managed stack** in
|
|
595
|
-
leaf-first order — leaf stacks via one CREATE-via-IMPORT changeset, non-leaf
|
|
596
|
-
parents via two changesets (Phase 1A CREATE-via-IMPORT for the parent's
|
|
597
|
-
leaf resources only, then Phase 1B UPDATE-via-IMPORT for the just-imported
|
|
598
|
-
child adoption per AWS's
|
|
599
|
-
["Nest an existing stack"](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resource-import-nested-stacks.html)
|
|
600
|
-
pattern with `DeletionPolicy: Retain` plus
|
|
601
|
-
`ResourceIdentifier: { StackId: <child arn> }` plus AWS-validated
|
|
602
|
-
child-Tag forwarding). Between phases cdkd flips each
|
|
603
|
-
stack's status from `IMPORT_COMPLETE` to `UPDATE_COMPLETE` via a no-op
|
|
604
|
-
tag-only `UpdateStack` (AWS rejects `IMPORT_COMPLETE` as a non-importable
|
|
605
|
-
status for nested adoption). The original "one atomic
|
|
606
|
-
`--include-nested-stacks` IMPORT changeset" design was found infeasible
|
|
607
|
-
by the 2026-05-24 AWS spike — AWS rejects that flag combination with
|
|
608
|
-
`ValidationError: IncludeNestedStacks is not supported for changeSet type: IMPORT`;
|
|
609
|
-
see [docs/design/464-nested-stacks-export-import.md](docs/design/464-nested-stacks-export-import.md)
|
|
610
|
-
§4.0 / §4.3 for the per-stack-loop algorithm. Each child cdkd stack
|
|
611
|
-
(`<parent>~<childLogicalId>`) becomes its own CFn stack named
|
|
612
|
-
`<parent>-<childLogicalId>` by default (`~` is illegal in CFn stack
|
|
613
|
-
names); override per child with `--cfn-child-stack-name '<cdkd>=<cfn>'`
|
|
614
|
-
(repeatable). Fresh `cdkd deploy` of nested stacks works via
|
|
615
|
-
[#459](https://github.com/go-to-k/cdkd/issues/459).
|
|
525
|
+
by `cdk deploy` / `aws cloudformation`. Accepts JSON and YAML templates
|
|
526
|
+
(shorthand intrinsics round-trip).
|
|
616
527
|
|
|
617
528
|
```bash
|
|
618
529
|
cdkd export MyStack # confirmation prompt; CFn stack name = cdkd stack name
|
|
619
530
|
cdkd export MyStack --cfn-stack-name MyStack-CFn
|
|
620
531
|
cdkd export MyStack --dry-run # print the import plan, do not call CFn
|
|
621
|
-
cdkd export MyStack --template path.json # skip synth, use a pre-rendered template (JSON or YAML — format auto-detected)
|
|
622
532
|
cdkd export MyStack --include-non-importable # 2-phase: IMPORT importable + CFn-CREATE Custom Resources
|
|
623
|
-
|
|
624
|
-
# Nested-stack tree (parent + children). Default child CFn names: '<parent>-<childLogicalId>'.
|
|
625
|
-
cdkd export MyApp # leaf-first per-stack IMPORT loop
|
|
626
|
-
cdkd export MyApp --cfn-child-stack-name 'MyApp~Database=my-app-db' # per-child name override
|
|
533
|
+
cdkd export MyApp # nested-stack tree: leaf-first per-stack IMPORT loop
|
|
627
534
|
```
|
|
628
535
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
536
|
+
**Lambda-backed Custom Resources** (`Custom::*` /
|
|
537
|
+
`AWS::CloudFormation::CustomResource`) are NOT directly CFn-importable.
|
|
538
|
+
`--include-non-importable` opts into a 2-phase migration that re-CREATEs
|
|
539
|
+
them through CFn — the Custom Resource Lambda must be idempotent.
|
|
540
|
+
**Nested stacks** are supported via a leaf-first per-stack IMPORT loop
|
|
541
|
+
(AWS rejects `--include-nested-stacks` for IMPORT changesets).
|
|
542
|
+
|
|
543
|
+
See **[docs/import.md](docs/import.md)** for the full guide — Custom Resource
|
|
544
|
+
2-phase flow, nested-stack adoption mechanics (`--cfn-child-stack-name`
|
|
545
|
+
per-child overrides, AWS's "Nest an existing stack" pattern), and the
|
|
546
|
+
design rationale at [docs/design/464-nested-stacks-export-import.md](docs/design/464-nested-stacks-export-import.md).
|
|
633
547
|
|
|
634
548
|
## Drift detection
|
|
635
549
|
|
|
@@ -671,22 +585,11 @@ Both `cdkd destroy` (synth-driven) and `cdkd state destroy`
|
|
|
671
585
|
|
|
672
586
|
## `--remove-protection`: one-shot bypass for protected resources
|
|
673
587
|
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
For resource-level protection (`DeletionProtection` etc.), the standard
|
|
680
|
-
workflow is edit CDK → redeploy → destroy. `--remove-protection` is the
|
|
681
|
-
one-shot bypass:
|
|
682
|
-
|
|
683
|
-
`cdkd destroy --remove-protection` and `cdkd state destroy
|
|
684
|
-
--remove-protection` flip every protection flag off in-place
|
|
685
|
-
before each provider's delete API call so the destroy proceeds
|
|
686
|
-
without an intermediate edit / redeploy. The flag covers both
|
|
687
|
-
stack-level `terminationProtection` (the bypass logs a WARN line
|
|
688
|
-
naming the stack) and resource-level protection on the following
|
|
689
|
-
types:
|
|
588
|
+
`cdkd destroy --remove-protection` (and `cdkd state destroy --remove-protection`)
|
|
589
|
+
flips every protection flag off in-place before each provider's delete
|
|
590
|
+
API call, so a destroy proceeds without an intermediate edit / redeploy.
|
|
591
|
+
Covers stack-level `terminationProtection` (logged as a WARN) AND
|
|
592
|
+
resource-level protection on these types:
|
|
690
593
|
|
|
691
594
|
| Resource type | Protection field |
|
|
692
595
|
| --- | --- |
|