@projectdochelp/s3te 3.1.1 → 3.1.3
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 +152 -29
- package/package.json +1 -1
- package/packages/aws-adapter/src/deploy.mjs +11 -15
- package/packages/aws-adapter/src/features.mjs +17 -4
- package/packages/aws-adapter/src/index.mjs +1 -0
- package/packages/aws-adapter/src/package.mjs +4 -3
- package/packages/aws-adapter/src/sync.mjs +155 -0
- package/packages/cli/bin/s3te.mjs +34 -0
- package/packages/cli/src/project.mjs +337 -32
- package/packages/core/src/config.mjs +173 -23
- package/packages/core/src/index.mjs +3 -0
- package/packages/core/src/render.mjs +3 -2
package/README.md
CHANGED
|
@@ -6,14 +6,19 @@ This README is the user guide for the rewrite generation. The deeper implementat
|
|
|
6
6
|
|
|
7
7
|
## Table of Contents
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
- [S3TemplateEngine](#s3templateengine)
|
|
10
|
+
- [Table of Contents](#table-of-contents)
|
|
11
|
+
- [Motivation](#motivation)
|
|
12
|
+
- [Support](#support)
|
|
13
|
+
- [Concept](#concept)
|
|
14
|
+
- [Installation (AWS)](#installation-aws)
|
|
15
|
+
- [Installation (VSCode)](#installation-vscode)
|
|
16
|
+
- [Installation (S3TE)](#installation-s3te)
|
|
17
|
+
- [Usage](#usage)
|
|
18
|
+
- [Daily Workflow](#daily-workflow)
|
|
19
|
+
- [CLI Commands](#cli-commands)
|
|
20
|
+
- [Template Commands](#template-commands)
|
|
21
|
+
- [Optional: Webiny CMS](#optional-webiny-cms)
|
|
17
22
|
|
|
18
23
|
## Motivation
|
|
19
24
|
|
|
@@ -54,7 +59,9 @@ The website bucket contains the finished result that visitors actually receive t
|
|
|
54
59
|
<details>
|
|
55
60
|
<summary>What happens when I deploy?</summary>
|
|
56
61
|
|
|
57
|
-
`s3te deploy`
|
|
62
|
+
`s3te deploy` loads the validated project configuration, packages the AWS runtime, creates or updates one persistent CloudFormation environment stack, creates one temporary CloudFormation deploy stack for packaging artifacts, synchronizes your current source files into the code bucket, and removes the temporary stack again after the real deploy run.
|
|
63
|
+
|
|
64
|
+
That source sync is not limited to Lambda code. It includes your `.html`, `.part`, CSS, JavaScript, images and other project files so the running AWS stack can react to source changes inside the code bucket.
|
|
58
65
|
|
|
59
66
|
The persistent environment stack contains the long-lived AWS resources such as buckets, Lambda functions, DynamoDB tables, CloudFront distributions and the runtime manifest parameter. The temporary deploy stack exists only so CloudFormation can consume the packaged Lambda artifacts cleanly.
|
|
60
67
|
|
|
@@ -152,7 +159,7 @@ With the local package installed, initialize the project like this:
|
|
|
152
159
|
npx s3te init --project-name mywebsite --base-url example.com
|
|
153
160
|
```
|
|
154
161
|
|
|
155
|
-
If `npm install` already created a minimal `package.json`, `s3te init` extends it with the missing S3TE defaults and scripts instead of failing.
|
|
162
|
+
You can safely run `s3te init` more than once. If `npm install` already created a minimal `package.json`, `s3te init` extends it with the missing S3TE defaults and scripts instead of failing. An existing `s3te.config.json` is completed with missing scaffold defaults, explicit `--project-name` and `--base-url` values are refreshed on re-run, and the generated schema file is updated to the current package version. Existing content files and templates stay untouched unless you use `--force`.
|
|
156
163
|
|
|
157
164
|
If you want a one-shot scaffold without installing first, and `@projectdochelp/s3te` is already published on npm, this also works:
|
|
158
165
|
|
|
@@ -160,25 +167,15 @@ If you want a one-shot scaffold without installing first, and `@projectdochelp/s
|
|
|
160
167
|
npx --package @projectdochelp/s3te s3te init --project-name mywebsite --base-url example.com
|
|
161
168
|
```
|
|
162
169
|
|
|
163
|
-
That command only works after a real npm publish. A GitHub repository on its own is not enough.
|
|
164
|
-
|
|
165
|
-
If you are still working from this repository before the first npm publish, run the CLI directly from the repo root instead:
|
|
166
|
-
|
|
167
|
-
```bash
|
|
168
|
-
node packages/cli/bin/s3te.mjs init --dir ./mywebsite --project-name mywebsite --base-url example.com
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
</details>
|
|
172
|
-
|
|
173
|
-
<details>
|
|
174
|
-
<summary>4. What the scaffold creates</summary>
|
|
175
|
-
|
|
176
170
|
The default scaffold creates:
|
|
177
171
|
|
|
178
172
|
```text
|
|
179
173
|
mywebsite/
|
|
180
174
|
package.json
|
|
181
175
|
s3te.config.json
|
|
176
|
+
.github/
|
|
177
|
+
workflows/
|
|
178
|
+
s3te-sync.yml
|
|
182
179
|
app/
|
|
183
180
|
part/
|
|
184
181
|
head.part
|
|
@@ -195,10 +192,12 @@ mywebsite/
|
|
|
195
192
|
extensions.json
|
|
196
193
|
```
|
|
197
194
|
|
|
195
|
+
The generated `.github/workflows/s3te-sync.yml` is the default CI path for GitHub-based source publishing into the S3TE code bucket. It is scaffolded once and then left alone on later `s3te init` runs unless you use `--force`.
|
|
196
|
+
|
|
198
197
|
</details>
|
|
199
198
|
|
|
200
199
|
<details>
|
|
201
|
-
<summary>
|
|
200
|
+
<summary>4. Fill in the real AWS values in <code>s3te.config.json</code></summary>
|
|
202
201
|
|
|
203
202
|
The most important fields for a first deployment are:
|
|
204
203
|
|
|
@@ -227,10 +226,12 @@ The most important fields for a first deployment are:
|
|
|
227
226
|
|
|
228
227
|
`route53HostedZoneId` is optional. Leave it out if you want to manage DNS yourself.
|
|
229
228
|
|
|
229
|
+
Use plain hostnames in `baseUrl` and `cloudFrontAliases`, not full URLs. If your config contains a `prod` environment plus additional environments such as `test` or `stage`, S3TE keeps the `prod` hostname unchanged and derives non-production hostnames automatically by prepending `<env>.`.
|
|
230
|
+
|
|
230
231
|
</details>
|
|
231
232
|
|
|
232
233
|
<details>
|
|
233
|
-
<summary>
|
|
234
|
+
<summary>5. Run the first local check and deploy</summary>
|
|
234
235
|
|
|
235
236
|
```bash
|
|
236
237
|
npx s3te validate
|
|
@@ -244,13 +245,15 @@ npx s3te deploy --env dev
|
|
|
244
245
|
|
|
245
246
|
`deploy` creates or updates the persistent environment stack, uses a temporary deploy stack for packaged Lambda artifacts, synchronizes the source project into the code bucket, and removes the temporary stack again when the deploy finishes.
|
|
246
247
|
|
|
248
|
+
After the first successful deploy, use `s3te sync --env dev` for regular template, partial, asset and source updates when the infrastructure itself did not change.
|
|
249
|
+
|
|
247
250
|
If you left `route53HostedZoneId` out of the config, the last DNS step stays manual: point your domain at the created CloudFront distribution after deploy.
|
|
248
251
|
|
|
249
252
|
</details>
|
|
250
253
|
|
|
251
254
|
## Usage
|
|
252
255
|
|
|
253
|
-
Once the project is installed, your everyday loop
|
|
256
|
+
Once the project is installed, your everyday loop splits into two paths: deploy when infrastructure changes, sync when only project sources changed.
|
|
254
257
|
|
|
255
258
|
### Daily Workflow
|
|
256
259
|
|
|
@@ -261,15 +264,24 @@ Once the project is installed, your everyday loop is deliberately small: edit te
|
|
|
261
264
|
2. If you use content-driven tags without Webiny, edit `offline/content/en.json` or `offline/content/items.json`.
|
|
262
265
|
3. Validate and render locally.
|
|
263
266
|
4. Run your tests.
|
|
264
|
-
5.
|
|
267
|
+
5. Use `deploy` for the first installation or after infrastructure/config/runtime changes.
|
|
268
|
+
6. Use `sync` for day-to-day source publishing into the code bucket.
|
|
265
269
|
|
|
266
270
|
```bash
|
|
267
271
|
npx s3te validate
|
|
268
272
|
npx s3te render --env dev
|
|
269
273
|
npx s3te test
|
|
274
|
+
npx s3te sync --env dev
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Use a full deploy only when needed:
|
|
278
|
+
|
|
279
|
+
```bash
|
|
270
280
|
npx s3te deploy --env dev
|
|
271
281
|
```
|
|
272
282
|
|
|
283
|
+
Once Webiny is installed and the stack is deployed with Webiny enabled, CMS content changes are picked up in AWS through the DynamoDB stream integration. Those content changes do not require another `sync` or `deploy`.
|
|
284
|
+
|
|
273
285
|
</details>
|
|
274
286
|
|
|
275
287
|
### CLI Commands
|
|
@@ -284,6 +296,7 @@ npx s3te deploy --env dev
|
|
|
284
296
|
| `s3te render --env <name>` | Renders locally into `offline/S3TELocal/preview/<env>/...`. |
|
|
285
297
|
| `s3te test` | Runs the project tests from `offline/tests/`. |
|
|
286
298
|
| `s3te package --env <name>` | Builds the AWS deployment artifacts without deploying them yet. |
|
|
299
|
+
| `s3te sync --env <name>` | Uploads current project sources into the configured code buckets. |
|
|
287
300
|
| `s3te doctor --env <name>` | Checks local machine and AWS access before deploy. |
|
|
288
301
|
| `s3te deploy --env <name>` | Deploys or updates the AWS environment and syncs source files. |
|
|
289
302
|
| `s3te migrate` | Updates older project configs and can retrofit Webiny into an existing S3TE project. |
|
|
@@ -378,6 +391,13 @@ npx s3te migrate --enable-webiny --webiny-source-table webiny-1234567 --webiny-t
|
|
|
378
391
|
|
|
379
392
|
`staticContent` and `staticCodeContent` are kept automatically. Add `--webiny-model` once per custom model you want S3TE to mirror.
|
|
380
393
|
|
|
394
|
+
If different environments should read from different Webiny installations or tenants, run the migration per environment:
|
|
395
|
+
|
|
396
|
+
```bash
|
|
397
|
+
npx s3te migrate --env test --enable-webiny --webiny-source-table webiny-test-1234567 --webiny-tenant preview --write
|
|
398
|
+
npx s3te migrate --env prod --enable-webiny --webiny-source-table webiny-live-1234567 --webiny-tenant root --write
|
|
399
|
+
```
|
|
400
|
+
|
|
381
401
|
4. Turn on DynamoDB Streams for the Webiny source table with `NEW_AND_OLD_IMAGES`.
|
|
382
402
|
5. If your S3TE language keys are not identical to your Webiny locales, add `webinyLocale` per language in `s3te.config.json`, for example `"en": { "webinyLocale": "en-US" }`.
|
|
383
403
|
6. If your Webiny installation hosts multiple tenants, keep `integrations.webiny.tenant` set so S3TE only mirrors the intended tenant.
|
|
@@ -393,7 +413,100 @@ npx s3te doctor --env prod
|
|
|
393
413
|
npx s3te deploy --env prod
|
|
394
414
|
```
|
|
395
415
|
|
|
396
|
-
That deploy updates the existing environment stack and adds the Webiny mirror resources to it. You do not need a fresh S3TE installation.
|
|
416
|
+
That deploy updates the existing environment stack and adds the Webiny mirror resources to it. You do not need a fresh S3TE installation. After that, Webiny content changes flow through the deployed AWS resources automatically; only template or asset changes still need `s3te sync --env <name>`.
|
|
417
|
+
|
|
418
|
+
</details>
|
|
419
|
+
|
|
420
|
+
<details>
|
|
421
|
+
<summary>GitHub Actions source publishing</summary>
|
|
422
|
+
|
|
423
|
+
If your team works through GitHub instead of running `s3te sync` locally, the scaffold already includes `.github/workflows/s3te-sync.yml`.
|
|
424
|
+
|
|
425
|
+
That workflow is meant for source publishing only:
|
|
426
|
+
|
|
427
|
+
- it validates the project
|
|
428
|
+
- it uploads `app/...` and `part/...` into the S3TE code bucket
|
|
429
|
+
- the resulting S3 events trigger the deployed Lambda pipeline in AWS
|
|
430
|
+
|
|
431
|
+
Use a full `deploy` only when the infrastructure, environment config, or runtime package changes.
|
|
432
|
+
|
|
433
|
+
Before the workflow can run, do this once:
|
|
434
|
+
|
|
435
|
+
1. Run the first real `npx s3te deploy --env <name>` so the code bucket already exists.
|
|
436
|
+
2. In AWS IAM, create an access key for a CI user that may sync only the S3TE code bucket for that environment.
|
|
437
|
+
3. In GitHub open `Settings -> Secrets and variables -> Actions -> Secrets`.
|
|
438
|
+
4. Add these repository secrets:
|
|
439
|
+
- `AWS_ACCESS_KEY_ID`
|
|
440
|
+
- `AWS_SECRET_ACCESS_KEY`
|
|
441
|
+
5. Open `.github/workflows/s3te-sync.yml` and adjust:
|
|
442
|
+
- the branch under `on.push.branches`
|
|
443
|
+
- `aws-region`
|
|
444
|
+
- `npx s3te sync --env dev` to your target environment such as `prod` or `test`
|
|
445
|
+
|
|
446
|
+
Minimal IAM policy example for one code bucket:
|
|
447
|
+
|
|
448
|
+
```json
|
|
449
|
+
{
|
|
450
|
+
"Version": "2012-10-17",
|
|
451
|
+
"Statement": [
|
|
452
|
+
{
|
|
453
|
+
"Effect": "Allow",
|
|
454
|
+
"Action": ["s3:ListBucket"],
|
|
455
|
+
"Resource": ["arn:aws:s3:::dev-website-code-mywebsite"]
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
"Effect": "Allow",
|
|
459
|
+
"Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
|
|
460
|
+
"Resource": ["arn:aws:s3:::dev-website-code-mywebsite/*"]
|
|
461
|
+
}
|
|
462
|
+
]
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
For non-production environments or additional variants, use the derived code bucket names from your config, for example `test-website-code-mywebsite` or `app-code-mywebsite`.
|
|
467
|
+
|
|
468
|
+
The scaffolded workflow looks like this:
|
|
469
|
+
|
|
470
|
+
```yaml
|
|
471
|
+
name: S3TE Sync
|
|
472
|
+
on:
|
|
473
|
+
workflow_dispatch:
|
|
474
|
+
push:
|
|
475
|
+
branches: ["main"]
|
|
476
|
+
paths:
|
|
477
|
+
- "app/**"
|
|
478
|
+
- "package.json"
|
|
479
|
+
- "package-lock.json"
|
|
480
|
+
- ".github/workflows/s3te-sync.yml"
|
|
481
|
+
|
|
482
|
+
jobs:
|
|
483
|
+
sync:
|
|
484
|
+
runs-on: ubuntu-latest
|
|
485
|
+
permissions:
|
|
486
|
+
contents: read
|
|
487
|
+
steps:
|
|
488
|
+
- uses: actions/checkout@v4
|
|
489
|
+
- uses: actions/setup-node@v4
|
|
490
|
+
with:
|
|
491
|
+
node-version: 22
|
|
492
|
+
cache: npm
|
|
493
|
+
- name: Install dependencies
|
|
494
|
+
shell: bash
|
|
495
|
+
run: |
|
|
496
|
+
if [ -f package-lock.json ]; then
|
|
497
|
+
npm ci
|
|
498
|
+
else
|
|
499
|
+
npm install
|
|
500
|
+
fi
|
|
501
|
+
- name: Configure AWS credentials
|
|
502
|
+
uses: aws-actions/configure-aws-credentials@v4
|
|
503
|
+
with:
|
|
504
|
+
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
|
505
|
+
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
506
|
+
aws-region: eu-central-1
|
|
507
|
+
- run: npx s3te validate
|
|
508
|
+
- run: npx s3te sync --env dev
|
|
509
|
+
```
|
|
397
510
|
|
|
398
511
|
</details>
|
|
399
512
|
|
|
@@ -411,7 +524,17 @@ Example config block:
|
|
|
411
524
|
"sourceTableName": "webiny-1234567",
|
|
412
525
|
"mirrorTableName": "{stackPrefix}_s3te_content_{project}",
|
|
413
526
|
"tenant": "root",
|
|
414
|
-
"relevantModels": ["article", "staticContent", "staticCodeContent"]
|
|
527
|
+
"relevantModels": ["article", "staticContent", "staticCodeContent"],
|
|
528
|
+
"environments": {
|
|
529
|
+
"test": {
|
|
530
|
+
"sourceTableName": "webiny-test-1234567",
|
|
531
|
+
"tenant": "preview"
|
|
532
|
+
},
|
|
533
|
+
"prod": {
|
|
534
|
+
"sourceTableName": "webiny-live-1234567",
|
|
535
|
+
"tenant": "root"
|
|
536
|
+
}
|
|
537
|
+
}
|
|
415
538
|
}
|
|
416
539
|
}
|
|
417
540
|
```
|
package/package.json
CHANGED
|
@@ -10,6 +10,7 @@ import { ensureAwsCliAvailable, ensureAwsCredentials, runAwsCli } from "./aws-cl
|
|
|
10
10
|
import { resolveRequestedFeatures } from "./features.mjs";
|
|
11
11
|
import { buildAwsRuntimeManifest, extractStackOutputsMap } from "./manifest.mjs";
|
|
12
12
|
import { packageAwsProject } from "./package.mjs";
|
|
13
|
+
import { syncPreparedSources } from "./sync.mjs";
|
|
13
14
|
import { buildTemporaryDeployStackTemplate } from "./template.mjs";
|
|
14
15
|
|
|
15
16
|
function normalizeRelative(projectDir, targetPath) {
|
|
@@ -243,12 +244,12 @@ export async function deployAwsProject({
|
|
|
243
244
|
}) {
|
|
244
245
|
const runtimeConfig = buildEnvironmentRuntimeConfig(config, environment);
|
|
245
246
|
const requestedFeatureSet = new Set(features);
|
|
246
|
-
const featureSet = new Set(resolveRequestedFeatures(config, features));
|
|
247
|
+
const featureSet = new Set(resolveRequestedFeatures(config, features, environment));
|
|
247
248
|
const stackName = resolveStackName(config, environment);
|
|
248
249
|
const tempStackName = temporaryStackName(stackName);
|
|
249
250
|
const runtimeManifestPath = path.join(projectDir, packageDir ?? path.join("offline", "IAAS", "package", environment), "runtime-manifest.json");
|
|
250
251
|
|
|
251
|
-
if (requestedFeatureSet.has("webiny") && !
|
|
252
|
+
if (requestedFeatureSet.has("webiny") && !runtimeConfig.integrations.webiny.enabled) {
|
|
252
253
|
throw new S3teError("ADAPTER_ERROR", "Feature webiny was requested but is not enabled in s3te.config.json.");
|
|
253
254
|
}
|
|
254
255
|
|
|
@@ -365,20 +366,15 @@ export async function deployAwsProject({
|
|
|
365
366
|
stdio
|
|
366
367
|
});
|
|
367
368
|
|
|
368
|
-
const syncedCodeBuckets =
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
369
|
+
const syncedCodeBuckets = noSync
|
|
370
|
+
? []
|
|
371
|
+
: (await syncPreparedSources({
|
|
372
|
+
projectDir,
|
|
373
|
+
runtimeConfig,
|
|
374
|
+
syncDirectories: packaged.manifest.syncDirectories,
|
|
374
375
|
profile,
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
errorCode: "ADAPTER_ERROR"
|
|
378
|
-
});
|
|
379
|
-
syncedCodeBuckets.push(variantConfig.codeBucket);
|
|
380
|
-
}
|
|
381
|
-
}
|
|
376
|
+
stdio
|
|
377
|
+
})).syncedCodeBuckets;
|
|
382
378
|
|
|
383
379
|
const distributions = [];
|
|
384
380
|
for (const [variantName, variantConfig] of Object.entries(runtimeConfig.variants)) {
|
|
@@ -1,16 +1,29 @@
|
|
|
1
|
-
|
|
1
|
+
import { resolveEnvironmentWebinyIntegration } from "../../core/src/index.mjs";
|
|
2
|
+
|
|
3
|
+
export function getConfiguredFeatures(config, environment) {
|
|
2
4
|
const features = [];
|
|
3
5
|
|
|
4
|
-
if (
|
|
6
|
+
if (environment) {
|
|
7
|
+
if (resolveEnvironmentWebinyIntegration(config, environment).enabled) {
|
|
8
|
+
features.push("webiny");
|
|
9
|
+
}
|
|
10
|
+
return features;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const hasAnyEnvironmentWebiny = Object.keys(config.environments ?? {}).some((environmentName) => (
|
|
14
|
+
resolveEnvironmentWebinyIntegration(config, environmentName).enabled
|
|
15
|
+
));
|
|
16
|
+
|
|
17
|
+
if (hasAnyEnvironmentWebiny) {
|
|
5
18
|
features.push("webiny");
|
|
6
19
|
}
|
|
7
20
|
|
|
8
21
|
return features;
|
|
9
22
|
}
|
|
10
23
|
|
|
11
|
-
export function resolveRequestedFeatures(config, requestedFeatures = []) {
|
|
24
|
+
export function resolveRequestedFeatures(config, requestedFeatures = [], environment) {
|
|
12
25
|
return [...new Set([
|
|
13
26
|
...requestedFeatures,
|
|
14
|
-
...getConfiguredFeatures(config)
|
|
27
|
+
...getConfiguredFeatures(config, environment)
|
|
15
28
|
])].sort();
|
|
16
29
|
}
|
|
@@ -5,3 +5,4 @@ export { ensureAwsCliAvailable, ensureAwsCredentials, runAwsCli } from "./aws-cl
|
|
|
5
5
|
export { getConfiguredFeatures, resolveRequestedFeatures } from "./features.mjs";
|
|
6
6
|
export { packageAwsProject } from "./package.mjs";
|
|
7
7
|
export { deployAwsProject } from "./deploy.mjs";
|
|
8
|
+
export { stageProjectSources, syncPreparedSources, syncAwsProject } from "./sync.mjs";
|
|
@@ -221,11 +221,13 @@ export async function packageAwsProject({
|
|
|
221
221
|
clean = false,
|
|
222
222
|
features = []
|
|
223
223
|
}) {
|
|
224
|
-
|
|
224
|
+
const runtimeConfig = buildEnvironmentRuntimeConfig(config, environment);
|
|
225
|
+
|
|
226
|
+
if (features.includes("webiny") && !runtimeConfig.integrations.webiny.enabled) {
|
|
225
227
|
throw new S3teError("ADAPTER_ERROR", "Feature webiny was requested but is not enabled in s3te.config.json.");
|
|
226
228
|
}
|
|
227
229
|
|
|
228
|
-
const resolvedFeatures = resolveRequestedFeatures(config, features);
|
|
230
|
+
const resolvedFeatures = resolveRequestedFeatures(config, features, environment);
|
|
229
231
|
const packageDir = outDir
|
|
230
232
|
? path.join(projectDir, outDir)
|
|
231
233
|
: path.join(projectDir, "offline", "IAAS", "package", environment);
|
|
@@ -234,7 +236,6 @@ export async function packageAwsProject({
|
|
|
234
236
|
}
|
|
235
237
|
await ensureDirectory(packageDir);
|
|
236
238
|
|
|
237
|
-
const runtimeConfig = buildEnvironmentRuntimeConfig(config, environment);
|
|
238
239
|
const lambdaDir = path.join(packageDir, "lambda");
|
|
239
240
|
const templatePath = path.join(packageDir, "cloudformation.template.json");
|
|
240
241
|
const packagingManifestPath = path.join(packageDir, "manifest.json");
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import { buildEnvironmentRuntimeConfig } from "../../core/src/index.mjs";
|
|
5
|
+
import { ensureAwsCliAvailable, ensureAwsCredentials, runAwsCli } from "./aws-cli.mjs";
|
|
6
|
+
|
|
7
|
+
async function ensureDirectory(targetDir) {
|
|
8
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async function removeDirectory(targetDir) {
|
|
12
|
+
await fs.rm(targetDir, { recursive: true, force: true });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function listFiles(rootDir, currentDir = rootDir) {
|
|
16
|
+
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
17
|
+
const files = [];
|
|
18
|
+
|
|
19
|
+
for (const entry of entries) {
|
|
20
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
21
|
+
if (entry.isDirectory()) {
|
|
22
|
+
files.push(...await listFiles(rootDir, fullPath));
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (entry.isFile()) {
|
|
27
|
+
files.push(path.relative(rootDir, fullPath).replace(/\\/g, "/"));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return files.sort();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function copyDirectory(sourceDir, targetDir) {
|
|
35
|
+
const files = await listFiles(sourceDir);
|
|
36
|
+
for (const relativePath of files) {
|
|
37
|
+
const sourcePath = path.join(sourceDir, relativePath);
|
|
38
|
+
const targetPath = path.join(targetDir, relativePath);
|
|
39
|
+
await ensureDirectory(path.dirname(targetPath));
|
|
40
|
+
await fs.copyFile(sourcePath, targetPath);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function normalizeRelative(projectDir, targetPath) {
|
|
45
|
+
return path.relative(projectDir, targetPath).replace(/\\/g, "/");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function stageVariantSources(projectDir, runtimeConfig, variantName, syncRoot) {
|
|
49
|
+
const variantConfig = runtimeConfig.variants[variantName];
|
|
50
|
+
const variantRoot = path.join(syncRoot, variantName);
|
|
51
|
+
await removeDirectory(variantRoot);
|
|
52
|
+
await ensureDirectory(variantRoot);
|
|
53
|
+
|
|
54
|
+
await copyDirectory(path.join(projectDir, variantConfig.partDir), path.join(variantRoot, "part"));
|
|
55
|
+
await copyDirectory(path.join(projectDir, variantConfig.sourceDir), path.join(variantRoot, variantName));
|
|
56
|
+
|
|
57
|
+
return variantRoot;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function stageProjectSources({
|
|
61
|
+
projectDir,
|
|
62
|
+
config,
|
|
63
|
+
environment,
|
|
64
|
+
outDir
|
|
65
|
+
}) {
|
|
66
|
+
const runtimeConfig = buildEnvironmentRuntimeConfig(config, environment);
|
|
67
|
+
const syncRoot = outDir
|
|
68
|
+
? path.join(projectDir, outDir)
|
|
69
|
+
: path.join(projectDir, "offline", "IAAS", "sync", environment);
|
|
70
|
+
|
|
71
|
+
await ensureDirectory(syncRoot);
|
|
72
|
+
|
|
73
|
+
const syncDirectories = {};
|
|
74
|
+
for (const variantName of Object.keys(runtimeConfig.variants)) {
|
|
75
|
+
const variantRoot = await stageVariantSources(projectDir, runtimeConfig, variantName, syncRoot);
|
|
76
|
+
syncDirectories[variantName] = normalizeRelative(projectDir, variantRoot);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
runtimeConfig,
|
|
81
|
+
syncRoot: normalizeRelative(projectDir, syncRoot),
|
|
82
|
+
syncDirectories
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function syncPreparedSources({
|
|
87
|
+
projectDir,
|
|
88
|
+
runtimeConfig,
|
|
89
|
+
syncDirectories,
|
|
90
|
+
profile,
|
|
91
|
+
stdio = "pipe",
|
|
92
|
+
ensureAwsCliAvailableFn = ensureAwsCliAvailable,
|
|
93
|
+
ensureAwsCredentialsFn = ensureAwsCredentials,
|
|
94
|
+
runAwsCliFn = runAwsCli
|
|
95
|
+
}) {
|
|
96
|
+
await ensureAwsCliAvailableFn({ cwd: projectDir });
|
|
97
|
+
await ensureAwsCredentialsFn({
|
|
98
|
+
region: runtimeConfig.awsRegion,
|
|
99
|
+
profile,
|
|
100
|
+
cwd: projectDir
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const syncedCodeBuckets = [];
|
|
104
|
+
for (const [variantName, variantConfig] of Object.entries(runtimeConfig.variants)) {
|
|
105
|
+
const syncDir = path.join(projectDir, syncDirectories[variantName]);
|
|
106
|
+
await runAwsCliFn(["s3", "sync", syncDir, `s3://${variantConfig.codeBucket}`, "--delete"], {
|
|
107
|
+
region: runtimeConfig.awsRegion,
|
|
108
|
+
profile,
|
|
109
|
+
cwd: projectDir,
|
|
110
|
+
stdio,
|
|
111
|
+
errorCode: "ADAPTER_ERROR"
|
|
112
|
+
});
|
|
113
|
+
syncedCodeBuckets.push(variantConfig.codeBucket);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
syncedCodeBuckets
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export async function syncAwsProject({
|
|
122
|
+
projectDir,
|
|
123
|
+
config,
|
|
124
|
+
environment,
|
|
125
|
+
outDir,
|
|
126
|
+
profile,
|
|
127
|
+
stdio = "pipe",
|
|
128
|
+
ensureAwsCliAvailableFn = ensureAwsCliAvailable,
|
|
129
|
+
ensureAwsCredentialsFn = ensureAwsCredentials,
|
|
130
|
+
runAwsCliFn = runAwsCli
|
|
131
|
+
}) {
|
|
132
|
+
const prepared = await stageProjectSources({
|
|
133
|
+
projectDir,
|
|
134
|
+
config,
|
|
135
|
+
environment,
|
|
136
|
+
outDir
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const synced = await syncPreparedSources({
|
|
140
|
+
projectDir,
|
|
141
|
+
runtimeConfig: prepared.runtimeConfig,
|
|
142
|
+
syncDirectories: prepared.syncDirectories,
|
|
143
|
+
profile,
|
|
144
|
+
stdio,
|
|
145
|
+
ensureAwsCliAvailableFn,
|
|
146
|
+
ensureAwsCredentialsFn,
|
|
147
|
+
runAwsCliFn
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
syncRoot: prepared.syncRoot,
|
|
152
|
+
syncDirectories: prepared.syncDirectories,
|
|
153
|
+
syncedCodeBuckets: synced.syncedCodeBuckets
|
|
154
|
+
};
|
|
155
|
+
}
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
renderProject,
|
|
12
12
|
runProjectTests,
|
|
13
13
|
scaffoldProject,
|
|
14
|
+
syncProject,
|
|
14
15
|
validateProject
|
|
15
16
|
} from "../src/project.mjs";
|
|
16
17
|
|
|
@@ -82,6 +83,7 @@ function printHelp() {
|
|
|
82
83
|
" render\n" +
|
|
83
84
|
" test\n" +
|
|
84
85
|
" package\n" +
|
|
86
|
+
" sync\n" +
|
|
85
87
|
" deploy\n" +
|
|
86
88
|
" doctor\n" +
|
|
87
89
|
" migrate\n"
|
|
@@ -281,6 +283,37 @@ async function main() {
|
|
|
281
283
|
return;
|
|
282
284
|
}
|
|
283
285
|
|
|
286
|
+
if (command === "sync") {
|
|
287
|
+
const loaded = await loadConfigForCommand(cwd, options.config);
|
|
288
|
+
if (!loaded.ok) {
|
|
289
|
+
if (wantsJson) {
|
|
290
|
+
printJson("sync", false, loaded.warnings, loaded.errors, startedAt);
|
|
291
|
+
}
|
|
292
|
+
process.exitCode = 2;
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
if (!options.env) {
|
|
296
|
+
process.stderr.write("sync requires --env <name>\n");
|
|
297
|
+
process.exitCode = 1;
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const report = await syncProject(cwd, loaded.config, {
|
|
302
|
+
environment: asArray(options.env)[0],
|
|
303
|
+
outDir: options["out-dir"],
|
|
304
|
+
profile: options.profile,
|
|
305
|
+
stdio: wantsJson ? "pipe" : "inherit"
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
if (wantsJson) {
|
|
309
|
+
printJson("sync", true, [], [], startedAt, report);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
process.stdout.write(`Synced project sources to ${report.syncedCodeBuckets.length} code bucket(s)\n`);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
284
317
|
if (command === "deploy") {
|
|
285
318
|
const loaded = await loadConfigForCommand(cwd, options.config);
|
|
286
319
|
if (!loaded.ok) {
|
|
@@ -349,6 +382,7 @@ async function main() {
|
|
|
349
382
|
const rawConfig = JSON.parse(await fs.readFile(configPath, "utf8"));
|
|
350
383
|
const migration = await migrateProject(configPath, rawConfig, {
|
|
351
384
|
writeChanges: Boolean(options.write) && !Boolean(options["dry-run"]),
|
|
385
|
+
environment: asArray(options.env)[0],
|
|
352
386
|
enableWebiny: Boolean(options["enable-webiny"]),
|
|
353
387
|
disableWebiny: Boolean(options["disable-webiny"]),
|
|
354
388
|
webinySourceTable: options["webiny-source-table"],
|