@granite-js/pulumi-aws 2.5.2 → 2.5.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/CHANGELOG.md +15 -0
- package/README.md +92 -0
- package/dist/{chunk-ZBT5SDIQ.js → chunk-GEE3OQDP.js} +162 -22
- package/dist/index.cjs +5 -0
- package/dist/index.js +5 -0
- package/dist/lambda/auto-cache-removal.cjs +39921 -10542
- package/dist/lambda/auto-cache-removal.js +35 -3
- package/dist/lambda/origin-request.cjs +175 -24
- package/dist/lambda/origin-request.js +17 -3
- package/dist/lambda/origin-response.js +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @granite-js/pulumi-aws
|
|
2
2
|
|
|
3
|
+
## 2.5.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 8b04ab6: Add configurable deployment channels across Forge, deployment storage and the AWS CDN. Forge automatically
|
|
8
|
+
registers each app/channel selector in S3, so channel creation requires no per-channel infrastructure configuration.
|
|
9
|
+
Channel objects, state, history and cluster pointers are isolated under `channels/<channel>/`. Registered path
|
|
10
|
+
suffixes select channels while the legacy default and unregistered filename tags keep their existing routes.
|
|
11
|
+
Conditional selector reservations and checks against retained legacy bundles prevent tag/channel name conflicts
|
|
12
|
+
between updated publishers. Registration and rollout events invalidate service selectors, and unique invalidation
|
|
13
|
+
caller references prevent simultaneous requests from colliding. Missing channel deployments never fall back to
|
|
14
|
+
another namespace. Cluster rollouts now write the `.deploymentInfo` pointer used by readers and invalidation.
|
|
15
|
+
|
|
16
|
+
The exported `paths` helpers take one options object instead of positional arguments.
|
|
17
|
+
|
|
3
18
|
## 2.5.2
|
|
4
19
|
|
|
5
20
|
## 2.5.1
|
package/README.md
CHANGED
|
@@ -77,6 +77,98 @@ pulumi up
|
|
|
77
77
|
|
|
78
78
|
Review the changes that Pulumi proposes, then confirm deployment. Pulumi will provision your React Native CDN infrastructure on AWS.
|
|
79
79
|
|
|
80
|
+
## Deployment channels
|
|
81
|
+
|
|
82
|
+
Deploy the channel-aware infrastructure once. Subsequent channel creation is handled by Forge, without changing
|
|
83
|
+
the Pulumi configuration or redeploying Lambda for each channel:
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
granite-forge deploy --bucket sample-bucket --channel next
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Forge gets the app name from the Granite config and registers the app/channel pair in S3 before uploading the
|
|
90
|
+
platform bundles. Shared and app bundles are separate deployments and should use the same channel name.
|
|
91
|
+
|
|
92
|
+
```text
|
|
93
|
+
/ios/sample-app/1/bundle -> existing unscoped default bundle
|
|
94
|
+
/ios/sample-app/1/next -> next channel's default bundle
|
|
95
|
+
/android/shared/1/next -> next channel's shared bundle
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### S3 registration and lookup
|
|
99
|
+
|
|
100
|
+
The selector record is stored outside the channel's mutable rollout state:
|
|
101
|
+
|
|
102
|
+
```text
|
|
103
|
+
deployments/sample-app/selectors/next.json
|
|
104
|
+
{"version":1,"type":"CHANNEL"}
|
|
105
|
+
|
|
106
|
+
channels/next/deployments/sample-app/deployment_state
|
|
107
|
+
channels/next/bundles/sample-app/<deploymentId>/bundle.ios.hbc.gz
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
On a CloudFront cache miss, the origin-request Lambda reads the selector record and then resolves deployment
|
|
111
|
+
state in the selected channel. The URI is rewritten to its S3 bundle key. `bundle` bypasses registration lookup
|
|
112
|
+
and keeps the legacy path. A missing registration keeps the existing filename-tag behavior; a registered channel
|
|
113
|
+
with missing deployment state returns 404, never a legacy bundle. Invalid metadata or storage access errors fail
|
|
114
|
+
instead of being interpreted as an unregistered selector. Lambda does not keep an in-process registration cache.
|
|
115
|
+
Query parameters do not select or override channels.
|
|
116
|
+
|
|
117
|
+
Numeric groups from 1 to 1000 retain their rollout meaning within the chosen channel. Named clusters still require
|
|
118
|
+
`allowAccessCluster`; the component's default handler keeps cluster access disabled.
|
|
119
|
+
|
|
120
|
+
### Backward compatibility and concurrent publishers
|
|
121
|
+
|
|
122
|
+
`--channel` omission preserves existing S3 keys and URLs. `bundle` is reserved and cannot be a channel name.
|
|
123
|
+
Channel names follow the [Forge CLI rules](../forge-cli/README.md#deployment-channels).
|
|
124
|
+
|
|
125
|
+
Before creating a channel, the deployment manager lists all pages under `bundles/<app>/` and rejects a matching
|
|
126
|
+
legacy tag on either platform, including retained objects from older deployments. New legacy tagged uploads
|
|
127
|
+
reserve the same selector key with `{"version":1,"type":"LEGACY_TAG"}`. Conditional S3 writes (`If-None-Match: *`)
|
|
128
|
+
ensure one owner when tag and channel registrations race. Repeated registration of the same kind is idempotent;
|
|
129
|
+
it does not overwrite or invalidate an existing registration.
|
|
130
|
+
|
|
131
|
+
Upgrade legacy tag publishers to the updated deployment manager before enabling channel creation. Older writers
|
|
132
|
+
cannot honor reservations. Checks cover retained visible objects and registration records, so do not reuse known
|
|
133
|
+
legacy names after deleting their artifacts. Keep selector records permanently: failed uploads do not remove a
|
|
134
|
+
channel registration or enable legacy fallback. Retrying the deployment uses the same registration.
|
|
135
|
+
|
|
136
|
+
Deployment credentials need `s3:ListBucket` for the legacy bundle prefix in addition to object read/write access.
|
|
137
|
+
Lambda only reads registrations and deployment state; it does not scan legacy objects. The extra registration
|
|
138
|
+
read occurs on cache misses for channel-like suffixes, not every cached request.
|
|
139
|
+
|
|
140
|
+
### Cache invalidation
|
|
141
|
+
|
|
142
|
+
S3 notifications watch `deployments/` and `channels/`. Selector registrations, deployment-state updates and cluster
|
|
143
|
+
pointer changes reach the cache-removal Lambda. Registration events clear previously cached legacy/missing-selector
|
|
144
|
+
responses, and the final rollout update invalidates again after both platform uploads succeed.
|
|
145
|
+
|
|
146
|
+
For `deployments/sample-app/selectors/next.json` or `channels/next/deployments/sample-app/deployment_state`, the paths are:
|
|
147
|
+
|
|
148
|
+
```text
|
|
149
|
+
/ios/sample-app/*
|
|
150
|
+
/android/sample-app/*
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Invalidation covers the affected service across its channels; other services are unaffected. Another channel may
|
|
154
|
+
incur a cache miss but retains its own deployment state and bundle. Cluster changes narrow this to the affected
|
|
155
|
+
cluster. History and bundle uploads do not invalidate selectors. This remains asynchronous; a successful Forge
|
|
156
|
+
command does not mean CloudFront invalidation has finished. See [AWS invalidation path rules](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/invalidation-specifying-objects.html).
|
|
157
|
+
|
|
158
|
+
### Shared bundles and rollout order
|
|
159
|
+
|
|
160
|
+
The component bootstraps its prebuilt shared bundle only into the existing unscoped namespace. Publish compatible
|
|
161
|
+
shared and app bundles explicitly to each channel. Routing does not validate bytecode/runtime compatibility or
|
|
162
|
+
make separate shared/app publications atomic.
|
|
163
|
+
|
|
164
|
+
Install and verify the common Lambda code and S3 notifications before enabling channel URLs in clients. The old
|
|
165
|
+
Lambda interprets the last segment as a filename tag. Wait for selector invalidations before enabling new clients,
|
|
166
|
+
including clearing any responses cached before the infrastructure upgrade. After this one-time upgrade, deploying
|
|
167
|
+
a new channel only writes S3 data; it needs no per-channel infrastructure configuration.
|
|
168
|
+
|
|
169
|
+
Review the complete Pulumi preview before any future apply: the existing component also manages the legacy
|
|
170
|
+
shared bundle and deployment pointer.
|
|
171
|
+
|
|
80
172
|
## Cleaning up
|
|
81
173
|
|
|
82
174
|
To remove the deployed resources, use:
|
|
@@ -15867,16 +15867,66 @@ var S3Client2 = class {
|
|
|
15867
15867
|
const response = await this.s3Client.send(command);
|
|
15868
15868
|
return response;
|
|
15869
15869
|
}
|
|
15870
|
+
async *listObjectKeys(prefix) {
|
|
15871
|
+
let continuationToken;
|
|
15872
|
+
for (; ; ) {
|
|
15873
|
+
const response = await this.s3Client.send(
|
|
15874
|
+
new ListObjectsV2Command({
|
|
15875
|
+
Bucket: this.bucket,
|
|
15876
|
+
Prefix: prefix,
|
|
15877
|
+
ContinuationToken: continuationToken
|
|
15878
|
+
})
|
|
15879
|
+
);
|
|
15880
|
+
for (const object2 of response.Contents ?? []) {
|
|
15881
|
+
if (object2.Key !== void 0) {
|
|
15882
|
+
yield object2.Key;
|
|
15883
|
+
}
|
|
15884
|
+
}
|
|
15885
|
+
if (!response.IsTruncated) {
|
|
15886
|
+
return;
|
|
15887
|
+
}
|
|
15888
|
+
continuationToken = response.NextContinuationToken;
|
|
15889
|
+
if (!continuationToken) {
|
|
15890
|
+
throw new Error("Truncated S3 listing has no continuation token");
|
|
15891
|
+
}
|
|
15892
|
+
}
|
|
15893
|
+
}
|
|
15870
15894
|
destroy() {
|
|
15871
15895
|
this.s3Client.destroy();
|
|
15872
15896
|
}
|
|
15873
15897
|
};
|
|
15898
|
+
var InvalidRequest3 = class extends Error {
|
|
15899
|
+
constructor(message) {
|
|
15900
|
+
super();
|
|
15901
|
+
this.name = "InvalidRequest";
|
|
15902
|
+
this.message = message;
|
|
15903
|
+
}
|
|
15904
|
+
};
|
|
15905
|
+
function validateChannel(channel) {
|
|
15906
|
+
if (channel === "bundle") {
|
|
15907
|
+
throw new InvalidRequest3("bundle is reserved for the legacy default bundle");
|
|
15908
|
+
}
|
|
15909
|
+
if (!isChannelName(channel)) {
|
|
15910
|
+
throw new InvalidRequest3(
|
|
15911
|
+
"Channel must contain 1-64 letters, digits, underscores or hyphens, starting with a letter or digit"
|
|
15912
|
+
);
|
|
15913
|
+
}
|
|
15914
|
+
return channel;
|
|
15915
|
+
}
|
|
15916
|
+
function isChannelName(value) {
|
|
15917
|
+
return typeof value === "string" && value !== "bundle" && /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/.test(value);
|
|
15918
|
+
}
|
|
15919
|
+
function channelPrefix(channel) {
|
|
15920
|
+
return channel === void 0 ? "" : `channels/${validateChannel(channel)}/`;
|
|
15921
|
+
}
|
|
15874
15922
|
var paths = {
|
|
15875
|
-
|
|
15876
|
-
|
|
15877
|
-
|
|
15878
|
-
|
|
15879
|
-
|
|
15923
|
+
appBundlePrefix: ({ appName, channel }) => `${channelPrefix(channel)}bundles/${appName}/`,
|
|
15924
|
+
selectorRegistration: ({ appName, selector }) => `deployments/${appName}/selectors/${encodeURIComponent(selector)}.json`,
|
|
15925
|
+
bundlePathPrefix: ({ appName, deploymentId, channel }) => `${channelPrefix(channel)}bundles/${appName}/${deploymentId}`,
|
|
15926
|
+
bundleList: ({ appName, channel }) => `${channelPrefix(channel)}deployments/${appName}/DEPLOYMENTS`,
|
|
15927
|
+
deploymentState: ({ appName, channel }) => `${channelPrefix(channel)}deployments/${appName}/deployment_state`,
|
|
15928
|
+
clusterDeploymentState: ({ appName, clusterId, channel }) => `${channelPrefix(channel)}deployments/${appName}/clusters/${clusterId}`,
|
|
15929
|
+
clusterDeploymentInfoPath: ({ appName, clusterId, channel }) => `${channelPrefix(channel)}deployments/${appName}/clusters/${clusterId}.deploymentInfo`
|
|
15880
15930
|
};
|
|
15881
15931
|
function shuffle(arr) {
|
|
15882
15932
|
const result = arr.slice();
|
|
@@ -15991,7 +16041,7 @@ function isFullyRollout(config) {
|
|
|
15991
16041
|
}
|
|
15992
16042
|
async function readBundleList(appName, context) {
|
|
15993
16043
|
const { s3Client } = context;
|
|
15994
|
-
const data = await s3Client.getObject(paths.bundleList(appName));
|
|
16044
|
+
const data = await s3Client.getObject(paths.bundleList({ appName, channel: context.channel }));
|
|
15995
16045
|
return JSON.parse(data);
|
|
15996
16046
|
}
|
|
15997
16047
|
var store$4;
|
|
@@ -16357,8 +16407,8 @@ var clusterDeploymentInfo = /* @__PURE__ */ object({
|
|
|
16357
16407
|
var deploymentState = /* @__PURE__ */ union([pendingDeploymentState, stableDeploymentState, canaryDeploymentState]);
|
|
16358
16408
|
async function readCluster({ appName, clusterId }, context) {
|
|
16359
16409
|
const { s3Client } = context;
|
|
16410
|
+
const deploymentInfoPath = paths.clusterDeploymentInfoPath({ appName, clusterId, channel: context.channel });
|
|
16360
16411
|
try {
|
|
16361
|
-
const deploymentInfoPath = paths.clusterDeploymentInfoPath(appName, clusterId);
|
|
16362
16412
|
const rawData = await s3Client.getObject(deploymentInfoPath);
|
|
16363
16413
|
const { deploymentId } = parse(clusterDeploymentInfo, JSON.parse(rawData));
|
|
16364
16414
|
return deploymentId;
|
|
@@ -16375,7 +16425,7 @@ var InternalServerError = class extends Error {
|
|
|
16375
16425
|
};
|
|
16376
16426
|
async function readDeploymentState(appName, context) {
|
|
16377
16427
|
const { s3Client } = context;
|
|
16378
|
-
const deploymentStatePath = paths.deploymentState(appName);
|
|
16428
|
+
const deploymentStatePath = paths.deploymentState({ appName, channel: context.channel });
|
|
16379
16429
|
try {
|
|
16380
16430
|
const rawData = await s3Client.getObject(deploymentStatePath);
|
|
16381
16431
|
const state = parse(deploymentState, JSON.parse(rawData));
|
|
@@ -16387,19 +16437,12 @@ async function readDeploymentState(appName, context) {
|
|
|
16387
16437
|
throw error;
|
|
16388
16438
|
}
|
|
16389
16439
|
}
|
|
16390
|
-
function resolveBundle({ appName, platform, deploymentId, tag }) {
|
|
16391
|
-
const bundleUrlPrefix = paths.bundlePathPrefix(appName, deploymentId);
|
|
16440
|
+
function resolveBundle({ appName, platform, deploymentId, tag, channel }) {
|
|
16441
|
+
const bundleUrlPrefix = paths.bundlePathPrefix({ appName, deploymentId, channel });
|
|
16392
16442
|
const extension = "hbc.gz";
|
|
16393
16443
|
const name = [BUNDLE_BASE_NAME, platform, tag, extension].filter(Boolean).join(".");
|
|
16394
16444
|
return `${bundleUrlPrefix}/${name}`;
|
|
16395
16445
|
}
|
|
16396
|
-
var InvalidRequest3 = class extends Error {
|
|
16397
|
-
constructor(message) {
|
|
16398
|
-
super();
|
|
16399
|
-
this.name = "InvalidRequest";
|
|
16400
|
-
this.message = message;
|
|
16401
|
-
}
|
|
16402
|
-
};
|
|
16403
16446
|
var NotFoundError = class extends Error {
|
|
16404
16447
|
constructor(message) {
|
|
16405
16448
|
super();
|
|
@@ -16437,8 +16480,8 @@ async function resolveDeploymentIdByGroupId(appName, numericGroupId, context) {
|
|
|
16437
16480
|
}
|
|
16438
16481
|
async function resolveDeploymentIdByCluster(appName, clusterId, context) {
|
|
16439
16482
|
const { s3Client } = context;
|
|
16483
|
+
const clusterDeploymentInfoPath = paths.clusterDeploymentInfoPath({ appName, clusterId, channel: context.channel });
|
|
16440
16484
|
try {
|
|
16441
|
-
const clusterDeploymentInfoPath = paths.clusterDeploymentInfoPath(appName, clusterId);
|
|
16442
16485
|
const rawData = await s3Client.getObject(clusterDeploymentInfoPath);
|
|
16443
16486
|
const deploymentInfo = parse(clusterDeploymentInfo, JSON.parse(rawData));
|
|
16444
16487
|
return deploymentInfo.deploymentId;
|
|
@@ -16449,9 +16492,94 @@ async function resolveDeploymentIdByCluster(appName, clusterId, context) {
|
|
|
16449
16492
|
throw new InternalServerError("resolveDeploymentIdByCluster", error);
|
|
16450
16493
|
}
|
|
16451
16494
|
}
|
|
16495
|
+
var registrationSchema = /* @__PURE__ */ object({
|
|
16496
|
+
version: /* @__PURE__ */ literal(1),
|
|
16497
|
+
type: /* @__PURE__ */ union([/* @__PURE__ */ literal("CHANNEL"), /* @__PURE__ */ literal("LEGACY_TAG")])
|
|
16498
|
+
});
|
|
16499
|
+
function isAppName(appName) {
|
|
16500
|
+
return /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(appName);
|
|
16501
|
+
}
|
|
16502
|
+
async function readRegistration(options, { s3Client }) {
|
|
16503
|
+
let raw;
|
|
16504
|
+
try {
|
|
16505
|
+
raw = await s3Client.getObject(paths.selectorRegistration(options));
|
|
16506
|
+
} catch (error) {
|
|
16507
|
+
if (error instanceof NoSuchKey) {
|
|
16508
|
+
return void 0;
|
|
16509
|
+
}
|
|
16510
|
+
throw error;
|
|
16511
|
+
}
|
|
16512
|
+
try {
|
|
16513
|
+
return parse(registrationSchema, JSON.parse(raw));
|
|
16514
|
+
} catch (error) {
|
|
16515
|
+
throw new InternalServerError("Invalid selector registration", error);
|
|
16516
|
+
}
|
|
16517
|
+
}
|
|
16518
|
+
async function resolveChannel(options, context) {
|
|
16519
|
+
if (!isAppName(options.appName) || !isChannelName(options.selector)) {
|
|
16520
|
+
return void 0;
|
|
16521
|
+
}
|
|
16522
|
+
const registration = await readRegistration(options, context);
|
|
16523
|
+
return registration?.type === "CHANNEL" ? options.selector : void 0;
|
|
16524
|
+
}
|
|
16525
|
+
async function registerChannel({ appName, channel }, context) {
|
|
16526
|
+
validateChannel(channel);
|
|
16527
|
+
if (!isAppName(appName)) {
|
|
16528
|
+
throw new InvalidRequest3("Channel registration requires a URL-safe application name");
|
|
16529
|
+
}
|
|
16530
|
+
await claimSelector({ appName, selector: channel }, "CHANNEL", context);
|
|
16531
|
+
}
|
|
16532
|
+
async function reserveLegacyTag(options, context) {
|
|
16533
|
+
if (isAppName(options.appName) && isChannelName(options.selector)) {
|
|
16534
|
+
await claimSelector(options, "LEGACY_TAG", context);
|
|
16535
|
+
}
|
|
16536
|
+
}
|
|
16537
|
+
async function claimSelector(options, type, context) {
|
|
16538
|
+
const { s3Client } = context;
|
|
16539
|
+
let lastConflict;
|
|
16540
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
16541
|
+
const existing = await readRegistration(options, context);
|
|
16542
|
+
if (existing !== void 0) {
|
|
16543
|
+
if (existing.type !== type) {
|
|
16544
|
+
throw new InvalidRequest3(
|
|
16545
|
+
`Selector ${options.selector} for ${options.appName} is already reserved as ${existing.type}`
|
|
16546
|
+
);
|
|
16547
|
+
}
|
|
16548
|
+
return;
|
|
16549
|
+
}
|
|
16550
|
+
if (type === "CHANNEL") {
|
|
16551
|
+
const prefix = paths.appBundlePrefix({ appName: options.appName });
|
|
16552
|
+
for await (const key of s3Client.listObjectKeys(prefix)) {
|
|
16553
|
+
if (["ios", "android"].some((platform) => key.endsWith(`/bundle.${platform}.${options.selector}.hbc.gz`))) {
|
|
16554
|
+
throw new InvalidRequest3(
|
|
16555
|
+
`Channel ${options.selector} conflicts with an existing legacy bundle tag for ${options.appName}`
|
|
16556
|
+
);
|
|
16557
|
+
}
|
|
16558
|
+
}
|
|
16559
|
+
}
|
|
16560
|
+
try {
|
|
16561
|
+
await s3Client.putObject(paths.selectorRegistration(options), {
|
|
16562
|
+
Body: JSON.stringify({ version: 1, type }),
|
|
16563
|
+
ContentType: "application/json",
|
|
16564
|
+
CacheControl: "no-store",
|
|
16565
|
+
IfNoneMatch: "*"
|
|
16566
|
+
});
|
|
16567
|
+
return;
|
|
16568
|
+
} catch (error) {
|
|
16569
|
+
if (!(error instanceof S3ServiceException) || ![409, 412].includes(error.$metadata.httpStatusCode ?? 0)) {
|
|
16570
|
+
throw error;
|
|
16571
|
+
}
|
|
16572
|
+
lastConflict = error;
|
|
16573
|
+
}
|
|
16574
|
+
}
|
|
16575
|
+
throw new InternalServerError("Concurrent selector registration could not be completed", lastConflict);
|
|
16576
|
+
}
|
|
16452
16577
|
async function rollout({ state, appName }, context) {
|
|
16453
16578
|
const { s3Client } = context;
|
|
16454
|
-
|
|
16579
|
+
if (context.channel !== void 0) {
|
|
16580
|
+
await registerChannel({ appName, channel: context.channel }, context);
|
|
16581
|
+
}
|
|
16582
|
+
await s3Client.putObject(paths.deploymentState({ appName, channel: context.channel }), {
|
|
16455
16583
|
Body: JSON.stringify(state),
|
|
16456
16584
|
ContentType: "application/json"
|
|
16457
16585
|
});
|
|
@@ -16459,14 +16587,17 @@ async function rollout({ state, appName }, context) {
|
|
|
16459
16587
|
}
|
|
16460
16588
|
async function rolloutCluster({ appName, deploymentId, clusterId }, context) {
|
|
16461
16589
|
const { s3Client } = context;
|
|
16462
|
-
|
|
16590
|
+
if (context.channel !== void 0) {
|
|
16591
|
+
await registerChannel({ appName, channel: context.channel }, context);
|
|
16592
|
+
}
|
|
16593
|
+
await s3Client.putObject(paths.clusterDeploymentInfoPath({ appName, clusterId, channel: context.channel }), {
|
|
16463
16594
|
Body: JSON.stringify({ deploymentId }),
|
|
16464
16595
|
ContentType: "application/json"
|
|
16465
16596
|
});
|
|
16466
16597
|
}
|
|
16467
16598
|
async function writeBundleList(appName, deploymentInfo, context) {
|
|
16468
16599
|
const { s3Client } = context;
|
|
16469
|
-
await s3Client.putObject(paths.bundleList(appName), {
|
|
16600
|
+
await s3Client.putObject(paths.bundleList({ appName, channel: context.channel }), {
|
|
16470
16601
|
Body: JSON.stringify(deploymentInfo.slice(0, MAX_HISTORY_COUNT)),
|
|
16471
16602
|
ContentType: "application/json"
|
|
16472
16603
|
});
|
|
@@ -16491,7 +16622,12 @@ function toDeployedAtString(date) {
|
|
|
16491
16622
|
}
|
|
16492
16623
|
async function uploadBundle({ bundlePath, appName, deploymentId, deployedAt, platform, tag }, context) {
|
|
16493
16624
|
const { s3Client } = context;
|
|
16494
|
-
const bundlePathKey = resolveBundle({ appName, platform, deploymentId, tag });
|
|
16625
|
+
const bundlePathKey = resolveBundle({ appName, platform, deploymentId, tag, channel: context.channel });
|
|
16626
|
+
if (context.channel !== void 0) {
|
|
16627
|
+
await registerChannel({ appName, channel: context.channel }, context);
|
|
16628
|
+
} else if (tag) {
|
|
16629
|
+
await reserveLegacyTag({ appName, selector: tag }, context);
|
|
16630
|
+
}
|
|
16495
16631
|
await s3Client.putObject(bundlePathKey, {
|
|
16496
16632
|
Body: fs.createReadStream(bundlePath),
|
|
16497
16633
|
Metadata: {
|
|
@@ -16508,6 +16644,8 @@ async function uploadBundle({ bundlePath, appName, deploymentId, deployedAt, pla
|
|
|
16508
16644
|
return { bundlePathKey, deploymentInfo };
|
|
16509
16645
|
}
|
|
16510
16646
|
var DeployManager = {
|
|
16647
|
+
registerChannel,
|
|
16648
|
+
resolveChannel,
|
|
16511
16649
|
planRollout,
|
|
16512
16650
|
rollout,
|
|
16513
16651
|
rolloutCluster,
|
|
@@ -16521,8 +16659,10 @@ var DeployManager = {
|
|
|
16521
16659
|
};
|
|
16522
16660
|
|
|
16523
16661
|
export {
|
|
16662
|
+
NoSuchKey,
|
|
16524
16663
|
S3Client2,
|
|
16525
16664
|
InvalidRequest3,
|
|
16665
|
+
validateChannel,
|
|
16526
16666
|
NotFoundError,
|
|
16527
16667
|
DeployManager
|
|
16528
16668
|
};
|
package/dist/index.cjs
CHANGED
|
@@ -1982,6 +1982,11 @@ var ReactNativeBundleCDN = class extends pulumi.ComponentResource {
|
|
|
1982
1982
|
events: ["s3:ObjectCreated:*", "s3:ObjectRemoved:*"],
|
|
1983
1983
|
filterPrefix: "deployments/"
|
|
1984
1984
|
// Only trigger for objects in deployments/ folder
|
|
1985
|
+
},
|
|
1986
|
+
{
|
|
1987
|
+
lambdaFunctionArn: cacheInvalidationLambda.arn,
|
|
1988
|
+
events: ["s3:ObjectCreated:*", "s3:ObjectRemoved:*"],
|
|
1989
|
+
filterPrefix: "channels/"
|
|
1985
1990
|
}
|
|
1986
1991
|
]
|
|
1987
1992
|
},
|
package/dist/index.js
CHANGED
|
@@ -1983,6 +1983,11 @@ var ReactNativeBundleCDN = class extends pulumi.ComponentResource {
|
|
|
1983
1983
|
events: ["s3:ObjectCreated:*", "s3:ObjectRemoved:*"],
|
|
1984
1984
|
filterPrefix: "deployments/"
|
|
1985
1985
|
// Only trigger for objects in deployments/ folder
|
|
1986
|
+
},
|
|
1987
|
+
{
|
|
1988
|
+
lambdaFunctionArn: cacheInvalidationLambda.arn,
|
|
1989
|
+
events: ["s3:ObjectCreated:*", "s3:ObjectRemoved:*"],
|
|
1990
|
+
filterPrefix: "channels/"
|
|
1986
1991
|
}
|
|
1987
1992
|
]
|
|
1988
1993
|
},
|