@hot-updater/aws 1.0.0-rc.3 → 1.0.0-rc.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/iac/index.cjs +116 -99
- package/dist/iac/index.d.cts +92 -1
- package/dist/iac/index.d.mts +93 -2
- package/dist/iac/index.mjs +111 -100
- package/package.json +3 -3
package/dist/iac/index.cjs
CHANGED
|
@@ -9437,6 +9437,41 @@ const hasExpectedSchema = (table) => {
|
|
|
9437
9437
|
return keyAttributes.every((attributeName) => table?.AttributeDefinitions?.some(({ AttributeName, AttributeType }) => AttributeName === attributeName && AttributeType === "S")) && table?.BillingModeSummary?.BillingMode === "PAY_PER_REQUEST" && table.OnDemandThroughput?.MaxReadRequestUnits === onDemandThroughput.MaxReadRequestUnits && table.OnDemandThroughput?.MaxWriteRequestUnits === onDemandThroughput.MaxWriteRequestUnits && hasKeySchema(table?.KeySchema, primaryKeySchema) && hasKeySchema(updateIndex?.KeySchema, updateIndexKeySchema) && updateIndex?.OnDemandThroughput?.MaxReadRequestUnits === onDemandThroughput.MaxReadRequestUnits && updateIndex?.OnDemandThroughput?.MaxWriteRequestUnits === onDemandThroughput.MaxWriteRequestUnits && updateIndex?.Projection?.ProjectionType === "ALL";
|
|
9438
9438
|
};
|
|
9439
9439
|
const isResourceNotFound = (error) => typeof error === "object" && error !== null && Reflect.get(error, "name") === "ResourceNotFoundException";
|
|
9440
|
+
const buildDynamoDBCreateTableInput = (tableName) => ({
|
|
9441
|
+
AttributeDefinitions: [
|
|
9442
|
+
{
|
|
9443
|
+
AttributeName: "pk",
|
|
9444
|
+
AttributeType: "S"
|
|
9445
|
+
},
|
|
9446
|
+
{
|
|
9447
|
+
AttributeName: "sk",
|
|
9448
|
+
AttributeType: "S"
|
|
9449
|
+
},
|
|
9450
|
+
{
|
|
9451
|
+
AttributeName: "gsi1pk",
|
|
9452
|
+
AttributeType: "S"
|
|
9453
|
+
},
|
|
9454
|
+
{
|
|
9455
|
+
AttributeName: "gsi1sk",
|
|
9456
|
+
AttributeType: "S"
|
|
9457
|
+
}
|
|
9458
|
+
],
|
|
9459
|
+
BillingMode: "PAY_PER_REQUEST",
|
|
9460
|
+
DeletionProtectionEnabled: true,
|
|
9461
|
+
GlobalSecondaryIndexes: [{
|
|
9462
|
+
IndexName: DYNAMODB_UPDATE_INDEX_NAME,
|
|
9463
|
+
KeySchema: [...updateIndexKeySchema],
|
|
9464
|
+
Projection: { ProjectionType: "ALL" },
|
|
9465
|
+
OnDemandThroughput: onDemandThroughput
|
|
9466
|
+
}],
|
|
9467
|
+
KeySchema: [...primaryKeySchema],
|
|
9468
|
+
OnDemandThroughput: onDemandThroughput,
|
|
9469
|
+
TableName: tableName
|
|
9470
|
+
});
|
|
9471
|
+
const buildDynamoDBBackupInput = (tableName) => ({
|
|
9472
|
+
PointInTimeRecoverySpecification: { PointInTimeRecoveryEnabled: true },
|
|
9473
|
+
TableName: tableName
|
|
9474
|
+
});
|
|
9440
9475
|
var DynamoDBManager = class {
|
|
9441
9476
|
region;
|
|
9442
9477
|
client;
|
|
@@ -9457,37 +9492,7 @@ var DynamoDBManager = class {
|
|
|
9457
9492
|
if (error instanceof Error && error.name === "AccessDeniedException" && error.message.includes(DYNAMODB_DESCRIBE_TABLE_ACTION)) throw new DynamoDBPermissionError(tableName, this.region, error);
|
|
9458
9493
|
if (!isResourceNotFound(error)) throw error;
|
|
9459
9494
|
}
|
|
9460
|
-
await this.client.createTable(
|
|
9461
|
-
AttributeDefinitions: [
|
|
9462
|
-
{
|
|
9463
|
-
AttributeName: "pk",
|
|
9464
|
-
AttributeType: "S"
|
|
9465
|
-
},
|
|
9466
|
-
{
|
|
9467
|
-
AttributeName: "sk",
|
|
9468
|
-
AttributeType: "S"
|
|
9469
|
-
},
|
|
9470
|
-
{
|
|
9471
|
-
AttributeName: "gsi1pk",
|
|
9472
|
-
AttributeType: "S"
|
|
9473
|
-
},
|
|
9474
|
-
{
|
|
9475
|
-
AttributeName: "gsi1sk",
|
|
9476
|
-
AttributeType: "S"
|
|
9477
|
-
}
|
|
9478
|
-
],
|
|
9479
|
-
BillingMode: "PAY_PER_REQUEST",
|
|
9480
|
-
DeletionProtectionEnabled: true,
|
|
9481
|
-
GlobalSecondaryIndexes: [{
|
|
9482
|
-
IndexName: DYNAMODB_UPDATE_INDEX_NAME,
|
|
9483
|
-
KeySchema: [...updateIndexKeySchema],
|
|
9484
|
-
Projection: { ProjectionType: "ALL" },
|
|
9485
|
-
OnDemandThroughput: onDemandThroughput
|
|
9486
|
-
}],
|
|
9487
|
-
KeySchema: [...primaryKeySchema],
|
|
9488
|
-
OnDemandThroughput: onDemandThroughput,
|
|
9489
|
-
TableName: tableName
|
|
9490
|
-
});
|
|
9495
|
+
await this.client.createTable(buildDynamoDBCreateTableInput(tableName));
|
|
9491
9496
|
await (0, _aws_sdk_client_dynamodb.waitUntilTableExists)({
|
|
9492
9497
|
client: this.client,
|
|
9493
9498
|
maxWaitTime: 120
|
|
@@ -9497,14 +9502,80 @@ var DynamoDBManager = class {
|
|
|
9497
9502
|
async ensureLifecycle(tableName) {
|
|
9498
9503
|
const { ContinuousBackupsDescription } = await this.client.describeContinuousBackups({ TableName: tableName });
|
|
9499
9504
|
if (ContinuousBackupsDescription?.PointInTimeRecoveryDescription?.PointInTimeRecoveryStatus === "ENABLED") return;
|
|
9500
|
-
await this.client.updateContinuousBackups(
|
|
9501
|
-
PointInTimeRecoverySpecification: { PointInTimeRecoveryEnabled: true },
|
|
9502
|
-
TableName: tableName
|
|
9503
|
-
});
|
|
9505
|
+
await this.client.updateContinuousBackups(buildDynamoDBBackupInput(tableName));
|
|
9504
9506
|
}
|
|
9505
9507
|
};
|
|
9506
9508
|
//#endregion
|
|
9507
9509
|
//#region iac/iam.ts
|
|
9510
|
+
const buildDynamoDBPolicy = (region, accountId, tableName) => {
|
|
9511
|
+
const tableArn = `arn:aws:dynamodb:${region}:${accountId}:table/${tableName}`;
|
|
9512
|
+
return {
|
|
9513
|
+
Version: "2012-10-17",
|
|
9514
|
+
Statement: [{
|
|
9515
|
+
Action: ["dynamodb:Query"],
|
|
9516
|
+
Effect: "Allow",
|
|
9517
|
+
Resource: [`${tableArn}/index/${DYNAMODB_UPDATE_INDEX_NAME}`]
|
|
9518
|
+
}, {
|
|
9519
|
+
Action: [
|
|
9520
|
+
"dynamodb:BatchGetItem",
|
|
9521
|
+
"dynamodb:DeleteItem",
|
|
9522
|
+
"dynamodb:GetItem",
|
|
9523
|
+
"dynamodb:PutItem",
|
|
9524
|
+
"dynamodb:Query",
|
|
9525
|
+
"dynamodb:TransactWriteItems",
|
|
9526
|
+
"dynamodb:UpdateItem"
|
|
9527
|
+
],
|
|
9528
|
+
Condition: { "ForAllValues:StringLike": { "dynamodb:LeadingKeys": [
|
|
9529
|
+
"_hot-updater",
|
|
9530
|
+
"bundles",
|
|
9531
|
+
"bundle_patches",
|
|
9532
|
+
DYNAMODB_CHANNEL_PARTITION,
|
|
9533
|
+
DYNAMODB_CHANNEL_NAME_PARTITION,
|
|
9534
|
+
DYNAMODB_INSIGHTS_PARTITION,
|
|
9535
|
+
DYNAMODB_INSIGHTS_INSTALLATIONS_PARTITION,
|
|
9536
|
+
DYNAMODB_INSIGHTS_EVENT_IDS_PARTITION,
|
|
9537
|
+
`${DYNAMODB_INSIGHTS_BUNDLE_PREFIX}*`,
|
|
9538
|
+
"_hot-updater#insights-user#*",
|
|
9539
|
+
DYNAMODB_API_KEY_PARTITION,
|
|
9540
|
+
DYNAMODB_API_KEY_HASH_PARTITION
|
|
9541
|
+
] } },
|
|
9542
|
+
Effect: "Allow",
|
|
9543
|
+
Resource: [tableArn]
|
|
9544
|
+
}]
|
|
9545
|
+
};
|
|
9546
|
+
};
|
|
9547
|
+
const buildS3Policy = (bucketName) => {
|
|
9548
|
+
return {
|
|
9549
|
+
Version: "2012-10-17",
|
|
9550
|
+
Statement: [{
|
|
9551
|
+
Action: ["s3:ListBucket"],
|
|
9552
|
+
Effect: "Allow",
|
|
9553
|
+
Resource: [`arn:aws:s3:::${bucketName}`]
|
|
9554
|
+
}, {
|
|
9555
|
+
Action: ["s3:GetObject"],
|
|
9556
|
+
Effect: "Allow",
|
|
9557
|
+
Resource: [`arn:aws:s3:::${bucketName}/*`]
|
|
9558
|
+
}]
|
|
9559
|
+
};
|
|
9560
|
+
};
|
|
9561
|
+
const buildSsmPolicy = (region, accountId, parameterName) => {
|
|
9562
|
+
return {
|
|
9563
|
+
Version: "2012-10-17",
|
|
9564
|
+
Statement: [{
|
|
9565
|
+
Effect: "Allow",
|
|
9566
|
+
Action: ["ssm:GetParameter"],
|
|
9567
|
+
Resource: `arn:aws:ssm:${region}:${accountId}:parameter/${parameterName.replace(/^\/+/, "")}`
|
|
9568
|
+
}]
|
|
9569
|
+
};
|
|
9570
|
+
};
|
|
9571
|
+
const LAMBDA_EDGE_TRUST_POLICY = {
|
|
9572
|
+
Version: "2012-10-17",
|
|
9573
|
+
Statement: [{
|
|
9574
|
+
Effect: "Allow",
|
|
9575
|
+
Principal: { Service: ["lambda.amazonaws.com", "edgelambda.amazonaws.com"] },
|
|
9576
|
+
Action: "sts:AssumeRole"
|
|
9577
|
+
}]
|
|
9578
|
+
};
|
|
9508
9579
|
var IAMManager = class {
|
|
9509
9580
|
region;
|
|
9510
9581
|
credentials;
|
|
@@ -9521,75 +9592,22 @@ var IAMManager = class {
|
|
|
9521
9592
|
});
|
|
9522
9593
|
}
|
|
9523
9594
|
async ensureDynamoDBPolicy(iamClient, roleName, accountId, tableName) {
|
|
9524
|
-
const tableArn = `arn:aws:dynamodb:${this.region}:${accountId}:table/${tableName}`;
|
|
9525
9595
|
await iamClient.putRolePolicy({
|
|
9526
|
-
PolicyDocument: JSON.stringify(
|
|
9527
|
-
Version: "2012-10-17",
|
|
9528
|
-
Statement: [{
|
|
9529
|
-
Action: ["dynamodb:Query"],
|
|
9530
|
-
Effect: "Allow",
|
|
9531
|
-
Resource: [`${tableArn}/index/${DYNAMODB_UPDATE_INDEX_NAME}`]
|
|
9532
|
-
}, {
|
|
9533
|
-
Action: [
|
|
9534
|
-
"dynamodb:BatchGetItem",
|
|
9535
|
-
"dynamodb:DeleteItem",
|
|
9536
|
-
"dynamodb:GetItem",
|
|
9537
|
-
"dynamodb:PutItem",
|
|
9538
|
-
"dynamodb:Query",
|
|
9539
|
-
"dynamodb:TransactWriteItems",
|
|
9540
|
-
"dynamodb:UpdateItem"
|
|
9541
|
-
],
|
|
9542
|
-
Condition: { "ForAllValues:StringLike": { "dynamodb:LeadingKeys": [
|
|
9543
|
-
"_hot-updater",
|
|
9544
|
-
"bundles",
|
|
9545
|
-
"bundle_patches",
|
|
9546
|
-
DYNAMODB_CHANNEL_PARTITION,
|
|
9547
|
-
DYNAMODB_CHANNEL_NAME_PARTITION,
|
|
9548
|
-
DYNAMODB_INSIGHTS_PARTITION,
|
|
9549
|
-
DYNAMODB_INSIGHTS_INSTALLATIONS_PARTITION,
|
|
9550
|
-
DYNAMODB_INSIGHTS_EVENT_IDS_PARTITION,
|
|
9551
|
-
`${DYNAMODB_INSIGHTS_BUNDLE_PREFIX}*`,
|
|
9552
|
-
"_hot-updater#insights-user#*",
|
|
9553
|
-
DYNAMODB_API_KEY_PARTITION,
|
|
9554
|
-
DYNAMODB_API_KEY_HASH_PARTITION
|
|
9555
|
-
] } },
|
|
9556
|
-
Effect: "Allow",
|
|
9557
|
-
Resource: [tableArn]
|
|
9558
|
-
}]
|
|
9559
|
-
}),
|
|
9596
|
+
PolicyDocument: JSON.stringify(buildDynamoDBPolicy(this.region, accountId, tableName)),
|
|
9560
9597
|
PolicyName: "HotUpdaterDynamoDBReadAccess",
|
|
9561
9598
|
RoleName: roleName
|
|
9562
9599
|
});
|
|
9563
9600
|
}
|
|
9564
9601
|
async ensureS3Policy(iamClient, roleName, bucketName) {
|
|
9565
9602
|
await iamClient.putRolePolicy({
|
|
9566
|
-
PolicyDocument: JSON.stringify(
|
|
9567
|
-
Version: "2012-10-17",
|
|
9568
|
-
Statement: [{
|
|
9569
|
-
Action: ["s3:ListBucket"],
|
|
9570
|
-
Effect: "Allow",
|
|
9571
|
-
Resource: [`arn:aws:s3:::${bucketName}`]
|
|
9572
|
-
}, {
|
|
9573
|
-
Action: ["s3:GetObject"],
|
|
9574
|
-
Effect: "Allow",
|
|
9575
|
-
Resource: [`arn:aws:s3:::${bucketName}/*`]
|
|
9576
|
-
}]
|
|
9577
|
-
}),
|
|
9603
|
+
PolicyDocument: JSON.stringify(buildS3Policy(bucketName)),
|
|
9578
9604
|
PolicyName: "HotUpdaterS3ReadAccess",
|
|
9579
9605
|
RoleName: roleName
|
|
9580
9606
|
});
|
|
9581
9607
|
}
|
|
9582
9608
|
async ensureSsmPolicy(iamClient, roleName, accountId, parameterName) {
|
|
9583
|
-
const parameterPath = parameterName.replace(/^\/+/, "");
|
|
9584
9609
|
await iamClient.putRolePolicy({
|
|
9585
|
-
PolicyDocument: JSON.stringify(
|
|
9586
|
-
Version: "2012-10-17",
|
|
9587
|
-
Statement: [{
|
|
9588
|
-
Effect: "Allow",
|
|
9589
|
-
Action: ["ssm:GetParameter"],
|
|
9590
|
-
Resource: `arn:aws:ssm:${this.region}:${accountId}:parameter/${parameterPath}`
|
|
9591
|
-
}]
|
|
9592
|
-
}),
|
|
9610
|
+
PolicyDocument: JSON.stringify(buildSsmPolicy(this.region, accountId, parameterName)),
|
|
9593
9611
|
PolicyName: "HotUpdaterSSMAccess",
|
|
9594
9612
|
RoleName: roleName
|
|
9595
9613
|
});
|
|
@@ -9604,14 +9622,7 @@ var IAMManager = class {
|
|
|
9604
9622
|
credentials: this.credentials
|
|
9605
9623
|
}).getCallerIdentity({})).Account;
|
|
9606
9624
|
if (!accountId) throw new Error("Failed to get AWS account ID");
|
|
9607
|
-
const assumeRolePolicyDocument = JSON.stringify(
|
|
9608
|
-
Version: "2012-10-17",
|
|
9609
|
-
Statement: [{
|
|
9610
|
-
Effect: "Allow",
|
|
9611
|
-
Principal: { Service: ["lambda.amazonaws.com", "edgelambda.amazonaws.com"] },
|
|
9612
|
-
Action: "sts:AssumeRole"
|
|
9613
|
-
}]
|
|
9614
|
-
});
|
|
9625
|
+
const assumeRolePolicyDocument = JSON.stringify(LAMBDA_EDGE_TRUST_POLICY);
|
|
9615
9626
|
const roleName = `hot-updater-edge-${(0, node_crypto.createHash)("sha256").update(options.lambdaName).digest("hex").slice(0, 16)}`;
|
|
9616
9627
|
try {
|
|
9617
9628
|
const { Role: existingRole } = await iamClient.getRole({ RoleName: roleName });
|
|
@@ -10282,6 +10293,12 @@ const runInit = async ({ build, envFile }) => {
|
|
|
10282
10293
|
_hot_updater_cli_tools.p.log.success("Done! 🎉");
|
|
10283
10294
|
};
|
|
10284
10295
|
//#endregion
|
|
10296
|
+
exports.LAMBDA_EDGE_TRUST_POLICY = LAMBDA_EDGE_TRUST_POLICY;
|
|
10297
|
+
exports.buildDynamoDBBackupInput = buildDynamoDBBackupInput;
|
|
10298
|
+
exports.buildDynamoDBCreateTableInput = buildDynamoDBCreateTableInput;
|
|
10299
|
+
exports.buildDynamoDBPolicy = buildDynamoDBPolicy;
|
|
10300
|
+
exports.buildS3Policy = buildS3Policy;
|
|
10301
|
+
exports.buildSsmPolicy = buildSsmPolicy;
|
|
10285
10302
|
exports.prepareDynamoDBApiKey = prepareDynamoDBApiKey;
|
|
10286
10303
|
exports.prepareDynamoDBDeployment = prepareDynamoDBDeployment;
|
|
10287
10304
|
exports.runInit = runInit;
|
package/dist/iac/index.d.cts
CHANGED
|
@@ -1,5 +1,96 @@
|
|
|
1
1
|
import { RunInitOptions } from "@hot-updater/cli-tools";
|
|
2
2
|
import { ApiKeyModel } from "@hot-updater/plugin-core";
|
|
3
|
+
//#region iac/dynamodb.d.ts
|
|
4
|
+
declare const buildDynamoDBCreateTableInput: (tableName: string) => {
|
|
5
|
+
AttributeDefinitions: {
|
|
6
|
+
AttributeName: string;
|
|
7
|
+
AttributeType: "S";
|
|
8
|
+
}[];
|
|
9
|
+
BillingMode: "PAY_PER_REQUEST";
|
|
10
|
+
DeletionProtectionEnabled: true;
|
|
11
|
+
GlobalSecondaryIndexes: {
|
|
12
|
+
IndexName: string;
|
|
13
|
+
KeySchema: ({
|
|
14
|
+
readonly AttributeName: "gsi1pk";
|
|
15
|
+
readonly KeyType: "HASH";
|
|
16
|
+
} | {
|
|
17
|
+
readonly AttributeName: "gsi1sk";
|
|
18
|
+
readonly KeyType: "RANGE";
|
|
19
|
+
})[];
|
|
20
|
+
Projection: {
|
|
21
|
+
ProjectionType: "ALL";
|
|
22
|
+
};
|
|
23
|
+
OnDemandThroughput: {
|
|
24
|
+
readonly MaxReadRequestUnits: 4000;
|
|
25
|
+
readonly MaxWriteRequestUnits: 100;
|
|
26
|
+
};
|
|
27
|
+
}[];
|
|
28
|
+
KeySchema: ({
|
|
29
|
+
readonly AttributeName: "pk";
|
|
30
|
+
readonly KeyType: "HASH";
|
|
31
|
+
} | {
|
|
32
|
+
readonly AttributeName: "sk";
|
|
33
|
+
readonly KeyType: "RANGE";
|
|
34
|
+
})[];
|
|
35
|
+
OnDemandThroughput: {
|
|
36
|
+
readonly MaxReadRequestUnits: 4000;
|
|
37
|
+
readonly MaxWriteRequestUnits: 100;
|
|
38
|
+
};
|
|
39
|
+
TableName: string;
|
|
40
|
+
};
|
|
41
|
+
declare const buildDynamoDBBackupInput: (tableName: string) => {
|
|
42
|
+
PointInTimeRecoverySpecification: {
|
|
43
|
+
PointInTimeRecoveryEnabled: true;
|
|
44
|
+
};
|
|
45
|
+
TableName: string;
|
|
46
|
+
};
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region iac/iam.d.ts
|
|
49
|
+
declare const buildDynamoDBPolicy: (region: string, accountId: string, tableName: string) => {
|
|
50
|
+
Version: string;
|
|
51
|
+
Statement: ({
|
|
52
|
+
Action: string[];
|
|
53
|
+
Effect: string;
|
|
54
|
+
Resource: string[];
|
|
55
|
+
Condition?: undefined;
|
|
56
|
+
} | {
|
|
57
|
+
Action: string[];
|
|
58
|
+
Condition: {
|
|
59
|
+
"ForAllValues:StringLike": {
|
|
60
|
+
"dynamodb:LeadingKeys": string[];
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
Effect: string;
|
|
64
|
+
Resource: string[];
|
|
65
|
+
})[];
|
|
66
|
+
};
|
|
67
|
+
declare const buildS3Policy: (bucketName: string) => {
|
|
68
|
+
Version: string;
|
|
69
|
+
Statement: {
|
|
70
|
+
Action: string[];
|
|
71
|
+
Effect: string;
|
|
72
|
+
Resource: string[];
|
|
73
|
+
}[];
|
|
74
|
+
};
|
|
75
|
+
declare const buildSsmPolicy: (region: string, accountId: string, parameterName: string) => {
|
|
76
|
+
Version: string;
|
|
77
|
+
Statement: {
|
|
78
|
+
Effect: string;
|
|
79
|
+
Action: string[];
|
|
80
|
+
Resource: string;
|
|
81
|
+
}[];
|
|
82
|
+
};
|
|
83
|
+
declare const LAMBDA_EDGE_TRUST_POLICY: {
|
|
84
|
+
Version: string;
|
|
85
|
+
Statement: {
|
|
86
|
+
Effect: string;
|
|
87
|
+
Principal: {
|
|
88
|
+
Service: string[];
|
|
89
|
+
};
|
|
90
|
+
Action: string;
|
|
91
|
+
}[];
|
|
92
|
+
};
|
|
93
|
+
//#endregion
|
|
3
94
|
//#region iac/index.d.ts
|
|
4
95
|
declare const prepareDynamoDBDeployment: (input: {
|
|
5
96
|
readonly credentials: {
|
|
@@ -16,4 +107,4 @@ declare const prepareDynamoDBApiKey: (input: {
|
|
|
16
107
|
}) => Promise<string>;
|
|
17
108
|
declare const runInit: ({ build, envFile }: RunInitOptions) => Promise<void>;
|
|
18
109
|
//#endregion
|
|
19
|
-
export { prepareDynamoDBApiKey, prepareDynamoDBDeployment, runInit };
|
|
110
|
+
export { LAMBDA_EDGE_TRUST_POLICY, buildDynamoDBBackupInput, buildDynamoDBCreateTableInput, buildDynamoDBPolicy, buildS3Policy, buildSsmPolicy, prepareDynamoDBApiKey, prepareDynamoDBDeployment, runInit };
|
package/dist/iac/index.d.mts
CHANGED
|
@@ -1,5 +1,96 @@
|
|
|
1
|
-
import { RunInitOptions } from "@hot-updater/cli-tools";
|
|
1
|
+
import { InitError, RunInitOptions } from "@hot-updater/cli-tools";
|
|
2
2
|
import { ApiKeyModel } from "@hot-updater/plugin-core";
|
|
3
|
+
//#region iac/dynamodb.d.ts
|
|
4
|
+
declare const buildDynamoDBCreateTableInput: (tableName: string) => {
|
|
5
|
+
AttributeDefinitions: {
|
|
6
|
+
AttributeName: string;
|
|
7
|
+
AttributeType: "S";
|
|
8
|
+
}[];
|
|
9
|
+
BillingMode: "PAY_PER_REQUEST";
|
|
10
|
+
DeletionProtectionEnabled: true;
|
|
11
|
+
GlobalSecondaryIndexes: {
|
|
12
|
+
IndexName: string;
|
|
13
|
+
KeySchema: ({
|
|
14
|
+
readonly AttributeName: "gsi1pk";
|
|
15
|
+
readonly KeyType: "HASH";
|
|
16
|
+
} | {
|
|
17
|
+
readonly AttributeName: "gsi1sk";
|
|
18
|
+
readonly KeyType: "RANGE";
|
|
19
|
+
})[];
|
|
20
|
+
Projection: {
|
|
21
|
+
ProjectionType: "ALL";
|
|
22
|
+
};
|
|
23
|
+
OnDemandThroughput: {
|
|
24
|
+
readonly MaxReadRequestUnits: 4000;
|
|
25
|
+
readonly MaxWriteRequestUnits: 100;
|
|
26
|
+
};
|
|
27
|
+
}[];
|
|
28
|
+
KeySchema: ({
|
|
29
|
+
readonly AttributeName: "pk";
|
|
30
|
+
readonly KeyType: "HASH";
|
|
31
|
+
} | {
|
|
32
|
+
readonly AttributeName: "sk";
|
|
33
|
+
readonly KeyType: "RANGE";
|
|
34
|
+
})[];
|
|
35
|
+
OnDemandThroughput: {
|
|
36
|
+
readonly MaxReadRequestUnits: 4000;
|
|
37
|
+
readonly MaxWriteRequestUnits: 100;
|
|
38
|
+
};
|
|
39
|
+
TableName: string;
|
|
40
|
+
};
|
|
41
|
+
declare const buildDynamoDBBackupInput: (tableName: string) => {
|
|
42
|
+
PointInTimeRecoverySpecification: {
|
|
43
|
+
PointInTimeRecoveryEnabled: true;
|
|
44
|
+
};
|
|
45
|
+
TableName: string;
|
|
46
|
+
};
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region iac/iam.d.ts
|
|
49
|
+
declare const buildDynamoDBPolicy: (region: string, accountId: string, tableName: string) => {
|
|
50
|
+
Version: string;
|
|
51
|
+
Statement: ({
|
|
52
|
+
Action: string[];
|
|
53
|
+
Effect: string;
|
|
54
|
+
Resource: string[];
|
|
55
|
+
Condition?: undefined;
|
|
56
|
+
} | {
|
|
57
|
+
Action: string[];
|
|
58
|
+
Condition: {
|
|
59
|
+
"ForAllValues:StringLike": {
|
|
60
|
+
"dynamodb:LeadingKeys": string[];
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
Effect: string;
|
|
64
|
+
Resource: string[];
|
|
65
|
+
})[];
|
|
66
|
+
};
|
|
67
|
+
declare const buildS3Policy: (bucketName: string) => {
|
|
68
|
+
Version: string;
|
|
69
|
+
Statement: {
|
|
70
|
+
Action: string[];
|
|
71
|
+
Effect: string;
|
|
72
|
+
Resource: string[];
|
|
73
|
+
}[];
|
|
74
|
+
};
|
|
75
|
+
declare const buildSsmPolicy: (region: string, accountId: string, parameterName: string) => {
|
|
76
|
+
Version: string;
|
|
77
|
+
Statement: {
|
|
78
|
+
Effect: string;
|
|
79
|
+
Action: string[];
|
|
80
|
+
Resource: string;
|
|
81
|
+
}[];
|
|
82
|
+
};
|
|
83
|
+
declare const LAMBDA_EDGE_TRUST_POLICY: {
|
|
84
|
+
Version: string;
|
|
85
|
+
Statement: {
|
|
86
|
+
Effect: string;
|
|
87
|
+
Principal: {
|
|
88
|
+
Service: string[];
|
|
89
|
+
};
|
|
90
|
+
Action: string;
|
|
91
|
+
}[];
|
|
92
|
+
};
|
|
93
|
+
//#endregion
|
|
3
94
|
//#region iac/index.d.ts
|
|
4
95
|
declare const prepareDynamoDBDeployment: (input: {
|
|
5
96
|
readonly credentials: {
|
|
@@ -16,4 +107,4 @@ declare const prepareDynamoDBApiKey: (input: {
|
|
|
16
107
|
}) => Promise<string>;
|
|
17
108
|
declare const runInit: ({ build, envFile }: RunInitOptions) => Promise<void>;
|
|
18
109
|
//#endregion
|
|
19
|
-
export { prepareDynamoDBApiKey, prepareDynamoDBDeployment, runInit };
|
|
110
|
+
export { LAMBDA_EDGE_TRUST_POLICY, buildDynamoDBBackupInput, buildDynamoDBCreateTableInput, buildDynamoDBPolicy, buildS3Policy, buildSsmPolicy, prepareDynamoDBApiKey, prepareDynamoDBDeployment, runInit };
|
package/dist/iac/index.mjs
CHANGED
|
@@ -9432,6 +9432,41 @@ const hasExpectedSchema = (table) => {
|
|
|
9432
9432
|
return keyAttributes.every((attributeName) => table?.AttributeDefinitions?.some(({ AttributeName, AttributeType }) => AttributeName === attributeName && AttributeType === "S")) && table?.BillingModeSummary?.BillingMode === "PAY_PER_REQUEST" && table.OnDemandThroughput?.MaxReadRequestUnits === onDemandThroughput.MaxReadRequestUnits && table.OnDemandThroughput?.MaxWriteRequestUnits === onDemandThroughput.MaxWriteRequestUnits && hasKeySchema(table?.KeySchema, primaryKeySchema) && hasKeySchema(updateIndex?.KeySchema, updateIndexKeySchema) && updateIndex?.OnDemandThroughput?.MaxReadRequestUnits === onDemandThroughput.MaxReadRequestUnits && updateIndex?.OnDemandThroughput?.MaxWriteRequestUnits === onDemandThroughput.MaxWriteRequestUnits && updateIndex?.Projection?.ProjectionType === "ALL";
|
|
9433
9433
|
};
|
|
9434
9434
|
const isResourceNotFound = (error) => typeof error === "object" && error !== null && Reflect.get(error, "name") === "ResourceNotFoundException";
|
|
9435
|
+
const buildDynamoDBCreateTableInput = (tableName) => ({
|
|
9436
|
+
AttributeDefinitions: [
|
|
9437
|
+
{
|
|
9438
|
+
AttributeName: "pk",
|
|
9439
|
+
AttributeType: "S"
|
|
9440
|
+
},
|
|
9441
|
+
{
|
|
9442
|
+
AttributeName: "sk",
|
|
9443
|
+
AttributeType: "S"
|
|
9444
|
+
},
|
|
9445
|
+
{
|
|
9446
|
+
AttributeName: "gsi1pk",
|
|
9447
|
+
AttributeType: "S"
|
|
9448
|
+
},
|
|
9449
|
+
{
|
|
9450
|
+
AttributeName: "gsi1sk",
|
|
9451
|
+
AttributeType: "S"
|
|
9452
|
+
}
|
|
9453
|
+
],
|
|
9454
|
+
BillingMode: "PAY_PER_REQUEST",
|
|
9455
|
+
DeletionProtectionEnabled: true,
|
|
9456
|
+
GlobalSecondaryIndexes: [{
|
|
9457
|
+
IndexName: DYNAMODB_UPDATE_INDEX_NAME,
|
|
9458
|
+
KeySchema: [...updateIndexKeySchema],
|
|
9459
|
+
Projection: { ProjectionType: "ALL" },
|
|
9460
|
+
OnDemandThroughput: onDemandThroughput
|
|
9461
|
+
}],
|
|
9462
|
+
KeySchema: [...primaryKeySchema],
|
|
9463
|
+
OnDemandThroughput: onDemandThroughput,
|
|
9464
|
+
TableName: tableName
|
|
9465
|
+
});
|
|
9466
|
+
const buildDynamoDBBackupInput = (tableName) => ({
|
|
9467
|
+
PointInTimeRecoverySpecification: { PointInTimeRecoveryEnabled: true },
|
|
9468
|
+
TableName: tableName
|
|
9469
|
+
});
|
|
9435
9470
|
var DynamoDBManager = class {
|
|
9436
9471
|
region;
|
|
9437
9472
|
client;
|
|
@@ -9452,37 +9487,7 @@ var DynamoDBManager = class {
|
|
|
9452
9487
|
if (error instanceof Error && error.name === "AccessDeniedException" && error.message.includes(DYNAMODB_DESCRIBE_TABLE_ACTION)) throw new DynamoDBPermissionError(tableName, this.region, error);
|
|
9453
9488
|
if (!isResourceNotFound(error)) throw error;
|
|
9454
9489
|
}
|
|
9455
|
-
await this.client.createTable(
|
|
9456
|
-
AttributeDefinitions: [
|
|
9457
|
-
{
|
|
9458
|
-
AttributeName: "pk",
|
|
9459
|
-
AttributeType: "S"
|
|
9460
|
-
},
|
|
9461
|
-
{
|
|
9462
|
-
AttributeName: "sk",
|
|
9463
|
-
AttributeType: "S"
|
|
9464
|
-
},
|
|
9465
|
-
{
|
|
9466
|
-
AttributeName: "gsi1pk",
|
|
9467
|
-
AttributeType: "S"
|
|
9468
|
-
},
|
|
9469
|
-
{
|
|
9470
|
-
AttributeName: "gsi1sk",
|
|
9471
|
-
AttributeType: "S"
|
|
9472
|
-
}
|
|
9473
|
-
],
|
|
9474
|
-
BillingMode: "PAY_PER_REQUEST",
|
|
9475
|
-
DeletionProtectionEnabled: true,
|
|
9476
|
-
GlobalSecondaryIndexes: [{
|
|
9477
|
-
IndexName: DYNAMODB_UPDATE_INDEX_NAME,
|
|
9478
|
-
KeySchema: [...updateIndexKeySchema],
|
|
9479
|
-
Projection: { ProjectionType: "ALL" },
|
|
9480
|
-
OnDemandThroughput: onDemandThroughput
|
|
9481
|
-
}],
|
|
9482
|
-
KeySchema: [...primaryKeySchema],
|
|
9483
|
-
OnDemandThroughput: onDemandThroughput,
|
|
9484
|
-
TableName: tableName
|
|
9485
|
-
});
|
|
9490
|
+
await this.client.createTable(buildDynamoDBCreateTableInput(tableName));
|
|
9486
9491
|
await waitUntilTableExists({
|
|
9487
9492
|
client: this.client,
|
|
9488
9493
|
maxWaitTime: 120
|
|
@@ -9492,14 +9497,80 @@ var DynamoDBManager = class {
|
|
|
9492
9497
|
async ensureLifecycle(tableName) {
|
|
9493
9498
|
const { ContinuousBackupsDescription } = await this.client.describeContinuousBackups({ TableName: tableName });
|
|
9494
9499
|
if (ContinuousBackupsDescription?.PointInTimeRecoveryDescription?.PointInTimeRecoveryStatus === "ENABLED") return;
|
|
9495
|
-
await this.client.updateContinuousBackups(
|
|
9496
|
-
PointInTimeRecoverySpecification: { PointInTimeRecoveryEnabled: true },
|
|
9497
|
-
TableName: tableName
|
|
9498
|
-
});
|
|
9500
|
+
await this.client.updateContinuousBackups(buildDynamoDBBackupInput(tableName));
|
|
9499
9501
|
}
|
|
9500
9502
|
};
|
|
9501
9503
|
//#endregion
|
|
9502
9504
|
//#region iac/iam.ts
|
|
9505
|
+
const buildDynamoDBPolicy = (region, accountId, tableName) => {
|
|
9506
|
+
const tableArn = `arn:aws:dynamodb:${region}:${accountId}:table/${tableName}`;
|
|
9507
|
+
return {
|
|
9508
|
+
Version: "2012-10-17",
|
|
9509
|
+
Statement: [{
|
|
9510
|
+
Action: ["dynamodb:Query"],
|
|
9511
|
+
Effect: "Allow",
|
|
9512
|
+
Resource: [`${tableArn}/index/${DYNAMODB_UPDATE_INDEX_NAME}`]
|
|
9513
|
+
}, {
|
|
9514
|
+
Action: [
|
|
9515
|
+
"dynamodb:BatchGetItem",
|
|
9516
|
+
"dynamodb:DeleteItem",
|
|
9517
|
+
"dynamodb:GetItem",
|
|
9518
|
+
"dynamodb:PutItem",
|
|
9519
|
+
"dynamodb:Query",
|
|
9520
|
+
"dynamodb:TransactWriteItems",
|
|
9521
|
+
"dynamodb:UpdateItem"
|
|
9522
|
+
],
|
|
9523
|
+
Condition: { "ForAllValues:StringLike": { "dynamodb:LeadingKeys": [
|
|
9524
|
+
"_hot-updater",
|
|
9525
|
+
"bundles",
|
|
9526
|
+
"bundle_patches",
|
|
9527
|
+
DYNAMODB_CHANNEL_PARTITION,
|
|
9528
|
+
DYNAMODB_CHANNEL_NAME_PARTITION,
|
|
9529
|
+
DYNAMODB_INSIGHTS_PARTITION,
|
|
9530
|
+
DYNAMODB_INSIGHTS_INSTALLATIONS_PARTITION,
|
|
9531
|
+
DYNAMODB_INSIGHTS_EVENT_IDS_PARTITION,
|
|
9532
|
+
`${DYNAMODB_INSIGHTS_BUNDLE_PREFIX}*`,
|
|
9533
|
+
"_hot-updater#insights-user#*",
|
|
9534
|
+
DYNAMODB_API_KEY_PARTITION,
|
|
9535
|
+
DYNAMODB_API_KEY_HASH_PARTITION
|
|
9536
|
+
] } },
|
|
9537
|
+
Effect: "Allow",
|
|
9538
|
+
Resource: [tableArn]
|
|
9539
|
+
}]
|
|
9540
|
+
};
|
|
9541
|
+
};
|
|
9542
|
+
const buildS3Policy = (bucketName) => {
|
|
9543
|
+
return {
|
|
9544
|
+
Version: "2012-10-17",
|
|
9545
|
+
Statement: [{
|
|
9546
|
+
Action: ["s3:ListBucket"],
|
|
9547
|
+
Effect: "Allow",
|
|
9548
|
+
Resource: [`arn:aws:s3:::${bucketName}`]
|
|
9549
|
+
}, {
|
|
9550
|
+
Action: ["s3:GetObject"],
|
|
9551
|
+
Effect: "Allow",
|
|
9552
|
+
Resource: [`arn:aws:s3:::${bucketName}/*`]
|
|
9553
|
+
}]
|
|
9554
|
+
};
|
|
9555
|
+
};
|
|
9556
|
+
const buildSsmPolicy = (region, accountId, parameterName) => {
|
|
9557
|
+
return {
|
|
9558
|
+
Version: "2012-10-17",
|
|
9559
|
+
Statement: [{
|
|
9560
|
+
Effect: "Allow",
|
|
9561
|
+
Action: ["ssm:GetParameter"],
|
|
9562
|
+
Resource: `arn:aws:ssm:${region}:${accountId}:parameter/${parameterName.replace(/^\/+/, "")}`
|
|
9563
|
+
}]
|
|
9564
|
+
};
|
|
9565
|
+
};
|
|
9566
|
+
const LAMBDA_EDGE_TRUST_POLICY = {
|
|
9567
|
+
Version: "2012-10-17",
|
|
9568
|
+
Statement: [{
|
|
9569
|
+
Effect: "Allow",
|
|
9570
|
+
Principal: { Service: ["lambda.amazonaws.com", "edgelambda.amazonaws.com"] },
|
|
9571
|
+
Action: "sts:AssumeRole"
|
|
9572
|
+
}]
|
|
9573
|
+
};
|
|
9503
9574
|
var IAMManager = class {
|
|
9504
9575
|
region;
|
|
9505
9576
|
credentials;
|
|
@@ -9516,75 +9587,22 @@ var IAMManager = class {
|
|
|
9516
9587
|
});
|
|
9517
9588
|
}
|
|
9518
9589
|
async ensureDynamoDBPolicy(iamClient, roleName, accountId, tableName) {
|
|
9519
|
-
const tableArn = `arn:aws:dynamodb:${this.region}:${accountId}:table/${tableName}`;
|
|
9520
9590
|
await iamClient.putRolePolicy({
|
|
9521
|
-
PolicyDocument: JSON.stringify(
|
|
9522
|
-
Version: "2012-10-17",
|
|
9523
|
-
Statement: [{
|
|
9524
|
-
Action: ["dynamodb:Query"],
|
|
9525
|
-
Effect: "Allow",
|
|
9526
|
-
Resource: [`${tableArn}/index/${DYNAMODB_UPDATE_INDEX_NAME}`]
|
|
9527
|
-
}, {
|
|
9528
|
-
Action: [
|
|
9529
|
-
"dynamodb:BatchGetItem",
|
|
9530
|
-
"dynamodb:DeleteItem",
|
|
9531
|
-
"dynamodb:GetItem",
|
|
9532
|
-
"dynamodb:PutItem",
|
|
9533
|
-
"dynamodb:Query",
|
|
9534
|
-
"dynamodb:TransactWriteItems",
|
|
9535
|
-
"dynamodb:UpdateItem"
|
|
9536
|
-
],
|
|
9537
|
-
Condition: { "ForAllValues:StringLike": { "dynamodb:LeadingKeys": [
|
|
9538
|
-
"_hot-updater",
|
|
9539
|
-
"bundles",
|
|
9540
|
-
"bundle_patches",
|
|
9541
|
-
DYNAMODB_CHANNEL_PARTITION,
|
|
9542
|
-
DYNAMODB_CHANNEL_NAME_PARTITION,
|
|
9543
|
-
DYNAMODB_INSIGHTS_PARTITION,
|
|
9544
|
-
DYNAMODB_INSIGHTS_INSTALLATIONS_PARTITION,
|
|
9545
|
-
DYNAMODB_INSIGHTS_EVENT_IDS_PARTITION,
|
|
9546
|
-
`${DYNAMODB_INSIGHTS_BUNDLE_PREFIX}*`,
|
|
9547
|
-
"_hot-updater#insights-user#*",
|
|
9548
|
-
DYNAMODB_API_KEY_PARTITION,
|
|
9549
|
-
DYNAMODB_API_KEY_HASH_PARTITION
|
|
9550
|
-
] } },
|
|
9551
|
-
Effect: "Allow",
|
|
9552
|
-
Resource: [tableArn]
|
|
9553
|
-
}]
|
|
9554
|
-
}),
|
|
9591
|
+
PolicyDocument: JSON.stringify(buildDynamoDBPolicy(this.region, accountId, tableName)),
|
|
9555
9592
|
PolicyName: "HotUpdaterDynamoDBReadAccess",
|
|
9556
9593
|
RoleName: roleName
|
|
9557
9594
|
});
|
|
9558
9595
|
}
|
|
9559
9596
|
async ensureS3Policy(iamClient, roleName, bucketName) {
|
|
9560
9597
|
await iamClient.putRolePolicy({
|
|
9561
|
-
PolicyDocument: JSON.stringify(
|
|
9562
|
-
Version: "2012-10-17",
|
|
9563
|
-
Statement: [{
|
|
9564
|
-
Action: ["s3:ListBucket"],
|
|
9565
|
-
Effect: "Allow",
|
|
9566
|
-
Resource: [`arn:aws:s3:::${bucketName}`]
|
|
9567
|
-
}, {
|
|
9568
|
-
Action: ["s3:GetObject"],
|
|
9569
|
-
Effect: "Allow",
|
|
9570
|
-
Resource: [`arn:aws:s3:::${bucketName}/*`]
|
|
9571
|
-
}]
|
|
9572
|
-
}),
|
|
9598
|
+
PolicyDocument: JSON.stringify(buildS3Policy(bucketName)),
|
|
9573
9599
|
PolicyName: "HotUpdaterS3ReadAccess",
|
|
9574
9600
|
RoleName: roleName
|
|
9575
9601
|
});
|
|
9576
9602
|
}
|
|
9577
9603
|
async ensureSsmPolicy(iamClient, roleName, accountId, parameterName) {
|
|
9578
|
-
const parameterPath = parameterName.replace(/^\/+/, "");
|
|
9579
9604
|
await iamClient.putRolePolicy({
|
|
9580
|
-
PolicyDocument: JSON.stringify(
|
|
9581
|
-
Version: "2012-10-17",
|
|
9582
|
-
Statement: [{
|
|
9583
|
-
Effect: "Allow",
|
|
9584
|
-
Action: ["ssm:GetParameter"],
|
|
9585
|
-
Resource: `arn:aws:ssm:${this.region}:${accountId}:parameter/${parameterPath}`
|
|
9586
|
-
}]
|
|
9587
|
-
}),
|
|
9605
|
+
PolicyDocument: JSON.stringify(buildSsmPolicy(this.region, accountId, parameterName)),
|
|
9588
9606
|
PolicyName: "HotUpdaterSSMAccess",
|
|
9589
9607
|
RoleName: roleName
|
|
9590
9608
|
});
|
|
@@ -9599,14 +9617,7 @@ var IAMManager = class {
|
|
|
9599
9617
|
credentials: this.credentials
|
|
9600
9618
|
}).getCallerIdentity({})).Account;
|
|
9601
9619
|
if (!accountId) throw new Error("Failed to get AWS account ID");
|
|
9602
|
-
const assumeRolePolicyDocument = JSON.stringify(
|
|
9603
|
-
Version: "2012-10-17",
|
|
9604
|
-
Statement: [{
|
|
9605
|
-
Effect: "Allow",
|
|
9606
|
-
Principal: { Service: ["lambda.amazonaws.com", "edgelambda.amazonaws.com"] },
|
|
9607
|
-
Action: "sts:AssumeRole"
|
|
9608
|
-
}]
|
|
9609
|
-
});
|
|
9620
|
+
const assumeRolePolicyDocument = JSON.stringify(LAMBDA_EDGE_TRUST_POLICY);
|
|
9610
9621
|
const roleName = `hot-updater-edge-${createHash("sha256").update(options.lambdaName).digest("hex").slice(0, 16)}`;
|
|
9611
9622
|
try {
|
|
9612
9623
|
const { Role: existingRole } = await iamClient.getRole({ RoleName: roleName });
|
|
@@ -10277,4 +10288,4 @@ const runInit = async ({ build, envFile }) => {
|
|
|
10277
10288
|
p.log.success("Done! 🎉");
|
|
10278
10289
|
};
|
|
10279
10290
|
//#endregion
|
|
10280
|
-
export { prepareDynamoDBApiKey, prepareDynamoDBDeployment, runInit };
|
|
10291
|
+
export { LAMBDA_EDGE_TRUST_POLICY, buildDynamoDBBackupInput, buildDynamoDBCreateTableInput, buildDynamoDBPolicy, buildS3Policy, buildSsmPolicy, prepareDynamoDBApiKey, prepareDynamoDBDeployment, runInit };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hot-updater/aws",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.0-rc.
|
|
4
|
+
"version": "1.0.0-rc.4",
|
|
5
5
|
"description": "React Native OTA solution for self-hosted",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
7
7
|
"module": "dist/index.mjs",
|
|
@@ -69,8 +69,8 @@
|
|
|
69
69
|
"hono": "4.12.34",
|
|
70
70
|
"aws-lambda": "1.0.7",
|
|
71
71
|
"@hot-updater/cli-tools": "1.0.0-rc.3",
|
|
72
|
-
"@hot-updater/
|
|
73
|
-
"@hot-updater/
|
|
72
|
+
"@hot-updater/plugin-core": "1.0.0-rc.3",
|
|
73
|
+
"@hot-updater/server": "1.0.0-rc.3"
|
|
74
74
|
},
|
|
75
75
|
"scripts": {
|
|
76
76
|
"build": "tsdown",
|