sst 2.0.0-rc.2 → 2.0.0-rc.21
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/bootstrap.js +3 -3
- package/bus.d.ts +3 -3
- package/cache.js +1 -1
- package/cdk/deploy-stack.d.ts +2 -2
- package/cdk/deploy-stack.js +24 -16
- package/cli/commands/bind.js +2 -2
- package/cli/commands/build.js +4 -4
- package/cli/commands/deploy.js +11 -6
- package/cli/commands/dev.js +14 -16
- package/cli/commands/env.js +4 -3
- package/cli/commands/plugins/kysely.js +2 -2
- package/cli/commands/remove.js +4 -5
- package/cli/commands/update.js +12 -6
- package/cli/local/router.d.ts +6 -6
- package/cli/local/router.js +3 -3
- package/cli/local/server.d.ts +1 -1
- package/cli/local/server.js +7 -12
- package/cli/program.d.ts +1 -1
- package/cli/program.js +1 -1
- package/cli/telemetry/environment.d.ts +1 -1
- package/cli/telemetry/environment.js +1 -1
- package/cli/ui/deploy.js +2 -2
- package/config.js +5 -5
- package/constructs/Api.d.ts +3 -3
- package/constructs/ApiGatewayV1Api.d.ts +2 -2
- package/constructs/App.d.ts +2 -2
- package/constructs/App.js +1 -1
- package/constructs/AppSyncApi.d.ts +1 -1
- package/constructs/AppSyncApi.js +1 -1
- package/constructs/Function.d.ts +4 -5
- package/constructs/Function.js +6 -14
- package/constructs/FunctionalStack.d.ts +2 -2
- package/constructs/Job.d.ts +1 -1
- package/constructs/Job.js +2 -2
- package/constructs/Metadata.d.ts +19 -19
- package/constructs/NextjsSite.d.ts +1 -1
- package/constructs/NextjsSite.js +21 -4
- package/constructs/RDS.d.ts +1 -1
- package/constructs/RDS.js +1 -1
- package/constructs/ReactStaticSite.d.ts +2 -2
- package/constructs/ReactStaticSite.js +2 -1
- package/constructs/Script.js +1 -1
- package/constructs/SsrSite.d.ts +11 -6
- package/constructs/SsrSite.js +56 -43
- package/constructs/Stack.d.ts +1 -1
- package/constructs/Stack.js +1 -1
- package/constructs/StaticSite.d.ts +3 -3
- package/constructs/StaticSite.js +9 -9
- package/constructs/Table.d.ts +1 -1
- package/constructs/ViteStaticSite.d.ts +1 -1
- package/constructs/ViteStaticSite.js +2 -1
- package/constructs/deferred_task.d.ts +1 -1
- package/constructs/deprecated/NextjsSite.js +1 -1
- package/constructs/util/apiGatewayV1AccessLog.d.ts +1 -1
- package/constructs/util/duration.d.ts +1 -1
- package/constructs/util/permission.d.ts +3 -3
- package/constructs/util/size.d.ts +1 -1
- package/context/handler.d.ts +1 -1
- package/credentials.d.ts +1 -1
- package/credentials.js +8 -6
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/iot.js +2 -2
- package/logger.js +1 -1
- package/node/api/index.d.ts +2 -0
- package/node/api/index.js +8 -0
- package/node/auth/adapter/adapter.d.ts +1 -1
- package/node/auth/adapter/github.js +4 -4
- package/node/auth/adapter/google.d.ts +1 -1
- package/node/auth/session.d.ts +1 -1
- package/node/config/index.d.ts +2 -2
- package/node/job/index.d.ts +2 -2
- package/package.json +8 -13
- package/project.d.ts +44 -0
- package/{app.js → project.js} +56 -51
- package/runtime/handlers/dotnet.js +1 -1
- package/runtime/handlers/java.js +1 -1
- package/runtime/handlers/node.js +6 -5
- package/runtime/handlers.js +14 -11
- package/runtime/runtime.d.ts +4 -0
- package/runtime/workers.js +4 -0
- package/site-env.js +1 -1
- package/sst.mjs +807 -16608
- package/stacks/build.d.ts +1 -1
- package/stacks/build.js +13 -21
- package/stacks/metadata.js +5 -5
- package/stacks/monitor.d.ts +1 -1
- package/stacks/synth.js +4 -4
- package/support/base-site-archiver.mjs +79 -0
- package/support/bridge/bridge.mjs +83 -22
- package/support/custom-resources/index.mjs +22 -1
- package/support/edge-function/edge-lambda.mjs +2 -2
- package/support/nodejs-runtime/index.mjs +79 -72
- package/support/rds-migrator/index.mjs +27 -23
- package/support/script-function/index.mjs +30485 -0
- package/support/ssr-site-function-archiver.mjs +96 -0
- package/watcher.js +1 -1
- package/app.d.ts +0 -36
- package/constructs/Script/cfn-response.d.ts +0 -19
- package/constructs/Script/cfn-response.js +0 -77
- package/constructs/Script/index.d.ts +0 -1
- package/constructs/Script/index.js +0 -78
- package/constructs/Script/outbound.d.ts +0 -10
- package/constructs/Script/outbound.js +0 -42
- package/constructs/Script/util.d.ts +0 -2
- package/constructs/Script/util.js +0 -11
- package/support/base-site-archiver.cjs +0 -116
package/watcher.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Context } from "./context/context.js";
|
|
|
2
2
|
import chokidar from "chokidar";
|
|
3
3
|
import { useBus } from "./bus.js";
|
|
4
4
|
import path from "path";
|
|
5
|
-
import { useProject } from "./
|
|
5
|
+
import { useProject } from "./project.js";
|
|
6
6
|
export const useWatcher = Context.memo(() => {
|
|
7
7
|
const project = useProject();
|
|
8
8
|
const bus = useBus();
|
package/app.d.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
export interface Project {
|
|
2
|
-
name: string;
|
|
3
|
-
region: string;
|
|
4
|
-
stage?: string;
|
|
5
|
-
profile?: string;
|
|
6
|
-
main?: string;
|
|
7
|
-
ssmPrefix?: string;
|
|
8
|
-
}
|
|
9
|
-
declare const DEFAULTS: {
|
|
10
|
-
readonly main: "stacks/index.ts";
|
|
11
|
-
readonly stage: undefined;
|
|
12
|
-
readonly ssmPrefix: undefined;
|
|
13
|
-
};
|
|
14
|
-
declare type ProjectWithDefaults = Project & Required<{
|
|
15
|
-
[key in keyof typeof DEFAULTS]: Exclude<Project[key], undefined>;
|
|
16
|
-
}> & {
|
|
17
|
-
version: string;
|
|
18
|
-
paths: {
|
|
19
|
-
root: string;
|
|
20
|
-
out: string;
|
|
21
|
-
artifacts: string;
|
|
22
|
-
};
|
|
23
|
-
};
|
|
24
|
-
export declare const ProjectContext: {
|
|
25
|
-
use(): ProjectWithDefaults;
|
|
26
|
-
provide(value: ProjectWithDefaults): void;
|
|
27
|
-
};
|
|
28
|
-
export declare function useProject(): ProjectWithDefaults;
|
|
29
|
-
interface GlobalOptions {
|
|
30
|
-
profile?: string;
|
|
31
|
-
stage?: string;
|
|
32
|
-
root?: string;
|
|
33
|
-
region?: string;
|
|
34
|
-
}
|
|
35
|
-
export declare function initProject(globals: GlobalOptions): Promise<void>;
|
|
36
|
-
export {};
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
export declare const CREATE_FAILED_PHYSICAL_ID_MARKER = "AWSCDK::CustomResourceProviderFramework::CREATE_FAILED";
|
|
2
|
-
export declare const MISSING_PHYSICAL_ID_MARKER = "AWSCDK::CustomResourceProviderFramework::MISSING_PHYSICAL_ID";
|
|
3
|
-
export interface CloudFormationResponseOptions {
|
|
4
|
-
readonly reason?: string;
|
|
5
|
-
readonly noEcho?: boolean;
|
|
6
|
-
}
|
|
7
|
-
export interface CloudFormationEventContext {
|
|
8
|
-
StackId: string;
|
|
9
|
-
RequestId: string;
|
|
10
|
-
PhysicalResourceId?: string;
|
|
11
|
-
LogicalResourceId: string;
|
|
12
|
-
ResponseURL: string;
|
|
13
|
-
Data?: any;
|
|
14
|
-
}
|
|
15
|
-
export declare function submitResponse(status: "SUCCESS" | "FAILED", event: CloudFormationEventContext, options?: CloudFormationResponseOptions): Promise<void>;
|
|
16
|
-
export declare const includeStackTraces = true;
|
|
17
|
-
export declare function safeHandler(block: (event: any) => Promise<void>): (event: any) => Promise<void>;
|
|
18
|
-
export declare class Retry extends Error {
|
|
19
|
-
}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
/* eslint-disable max-len */
|
|
2
|
-
/* eslint-disable no-console */
|
|
3
|
-
import url from "url";
|
|
4
|
-
import { httpRequest } from "./outbound.js";
|
|
5
|
-
import { log } from "./util.js";
|
|
6
|
-
export const CREATE_FAILED_PHYSICAL_ID_MARKER = "AWSCDK::CustomResourceProviderFramework::CREATE_FAILED";
|
|
7
|
-
export const MISSING_PHYSICAL_ID_MARKER = "AWSCDK::CustomResourceProviderFramework::MISSING_PHYSICAL_ID";
|
|
8
|
-
export async function submitResponse(status, event, options = {}) {
|
|
9
|
-
const json = {
|
|
10
|
-
Status: status,
|
|
11
|
-
Reason: options.reason || status,
|
|
12
|
-
StackId: event.StackId,
|
|
13
|
-
RequestId: event.RequestId,
|
|
14
|
-
PhysicalResourceId: event.PhysicalResourceId || MISSING_PHYSICAL_ID_MARKER,
|
|
15
|
-
LogicalResourceId: event.LogicalResourceId,
|
|
16
|
-
NoEcho: options.noEcho,
|
|
17
|
-
Data: event.Data,
|
|
18
|
-
};
|
|
19
|
-
log("submit response to cloudformation", json);
|
|
20
|
-
const responseBody = JSON.stringify(json);
|
|
21
|
-
const parsedUrl = url.parse(event.ResponseURL);
|
|
22
|
-
await httpRequest({
|
|
23
|
-
hostname: parsedUrl.hostname,
|
|
24
|
-
path: parsedUrl.path,
|
|
25
|
-
method: "PUT",
|
|
26
|
-
headers: {
|
|
27
|
-
"content-type": "",
|
|
28
|
-
"content-length": responseBody.length,
|
|
29
|
-
},
|
|
30
|
-
}, responseBody);
|
|
31
|
-
}
|
|
32
|
-
export const includeStackTraces = true; // for unit tests
|
|
33
|
-
export function safeHandler(block) {
|
|
34
|
-
return async (event) => {
|
|
35
|
-
// ignore DELETE event when the physical resource ID is the marker that
|
|
36
|
-
// indicates that this DELETE is a subsequent DELETE to a failed CREATE
|
|
37
|
-
// operation.
|
|
38
|
-
if (event.RequestType === "Delete" &&
|
|
39
|
-
event.PhysicalResourceId === CREATE_FAILED_PHYSICAL_ID_MARKER) {
|
|
40
|
-
log("ignoring DELETE event caused by a failed CREATE event");
|
|
41
|
-
await submitResponse("SUCCESS", event);
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
try {
|
|
45
|
-
await block(event);
|
|
46
|
-
}
|
|
47
|
-
catch (e) {
|
|
48
|
-
// tell waiter state machine to retry
|
|
49
|
-
if (e instanceof Retry) {
|
|
50
|
-
log("retry requested by handler");
|
|
51
|
-
throw e;
|
|
52
|
-
}
|
|
53
|
-
if (!event.PhysicalResourceId) {
|
|
54
|
-
// special case: if CREATE fails, which usually implies, we usually don't
|
|
55
|
-
// have a physical resource id. in this case, the subsequent DELETE
|
|
56
|
-
// operation does not have any meaning, and will likely fail as well. to
|
|
57
|
-
// address this, we use a marker so the provider framework can simply
|
|
58
|
-
// ignore the subsequent DELETE.
|
|
59
|
-
if (event.RequestType === "Create") {
|
|
60
|
-
log("CREATE failed, responding with a marker physical resource id so that the subsequent DELETE will be ignored");
|
|
61
|
-
event.PhysicalResourceId = CREATE_FAILED_PHYSICAL_ID_MARKER;
|
|
62
|
-
}
|
|
63
|
-
else {
|
|
64
|
-
// otherwise, if PhysicalResourceId is not specified, something is
|
|
65
|
-
// terribly wrong because all other events should have an ID.
|
|
66
|
-
log(`ERROR: Malformed event. "PhysicalResourceId" is required: ${JSON.stringify(event)}`);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
// this is an actual error, fail the activity altogether and exist.
|
|
70
|
-
await submitResponse("FAILED", event, {
|
|
71
|
-
reason: includeStackTraces ? e.stack : e.message,
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
export class Retry extends Error {
|
|
77
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const handler: (event: any) => Promise<void>;
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import * as cfnResponse from "./cfn-response.js";
|
|
2
|
-
import { invokeFunction } from "./outbound.js";
|
|
3
|
-
import { log } from "./util.js";
|
|
4
|
-
export const handler = cfnResponse.safeHandler(async (cfnRequest) => {
|
|
5
|
-
log("onEventHandler", cfnRequest);
|
|
6
|
-
// Invoke user function on Create and on Update
|
|
7
|
-
const fnCreate = cfnRequest.ResourceProperties.UserCreateFunction;
|
|
8
|
-
const fnUpdate = cfnRequest.ResourceProperties.UserUpdateFunction;
|
|
9
|
-
const fnDelete = cfnRequest.ResourceProperties.UserDeleteFunction;
|
|
10
|
-
const fnParams = JSON.parse(cfnRequest.ResourceProperties.UserParams);
|
|
11
|
-
if (cfnRequest.RequestType === "Create" && fnCreate) {
|
|
12
|
-
await invokeUserFunction(fnCreate, { params: fnParams });
|
|
13
|
-
}
|
|
14
|
-
else if (cfnRequest.RequestType === "Update" && fnUpdate) {
|
|
15
|
-
await invokeUserFunction(fnUpdate, { params: fnParams });
|
|
16
|
-
}
|
|
17
|
-
else if (cfnRequest.RequestType === "Delete" && fnDelete) {
|
|
18
|
-
await invokeUserFunction(fnDelete, { params: fnParams });
|
|
19
|
-
}
|
|
20
|
-
// Build response
|
|
21
|
-
return cfnResponse.submitResponse("SUCCESS", {
|
|
22
|
-
...cfnRequest,
|
|
23
|
-
PhysicalResourceId: defaultPhysicalResourceId(cfnRequest),
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
|
-
async function invokeUserFunction(functionName, payload) {
|
|
27
|
-
log(`executing user function ${functionName} with payload`, payload);
|
|
28
|
-
const resp = await invokeFunction({
|
|
29
|
-
FunctionName: functionName,
|
|
30
|
-
Payload: JSON.stringify(payload),
|
|
31
|
-
});
|
|
32
|
-
log("user function response:", resp, typeof resp);
|
|
33
|
-
if (resp.FunctionError) {
|
|
34
|
-
log("user function threw an error:", resp.FunctionError);
|
|
35
|
-
const jsonPayload = parseJsonPayload(resp.Payload);
|
|
36
|
-
// Note: custom resources have a response limit of 4k. Limit the
|
|
37
|
-
// error message to 1000 characters.
|
|
38
|
-
const errorMessage = (jsonPayload.errorMessage || "error").substring(0, 1000);
|
|
39
|
-
// append a reference to the log group.
|
|
40
|
-
const message = [
|
|
41
|
-
errorMessage,
|
|
42
|
-
"",
|
|
43
|
-
`Logs: /aws/lambda/${functionName}`,
|
|
44
|
-
"",
|
|
45
|
-
].join("\n");
|
|
46
|
-
const e = new Error(message);
|
|
47
|
-
// the output that goes to CFN is what's in `stack`, not the error message.
|
|
48
|
-
// if we have a remote trace, construct a nice message with log group information
|
|
49
|
-
if (jsonPayload.trace) {
|
|
50
|
-
// skip first trace line because it's the message
|
|
51
|
-
e.stack = [message, ...jsonPayload.trace.slice(1)].join("\n");
|
|
52
|
-
}
|
|
53
|
-
throw e;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
function parseJsonPayload(payload) {
|
|
57
|
-
if (!payload) {
|
|
58
|
-
return {};
|
|
59
|
-
}
|
|
60
|
-
const text = payload.toString();
|
|
61
|
-
try {
|
|
62
|
-
return JSON.parse(text);
|
|
63
|
-
}
|
|
64
|
-
catch (e) {
|
|
65
|
-
throw new Error(`return values from user-handlers must be JSON objects. got: "${text}"`);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
function defaultPhysicalResourceId(req) {
|
|
69
|
-
switch (req.RequestType) {
|
|
70
|
-
case "Create":
|
|
71
|
-
return req.RequestId;
|
|
72
|
-
case "Update":
|
|
73
|
-
case "Delete":
|
|
74
|
-
return req.PhysicalResourceId;
|
|
75
|
-
default:
|
|
76
|
-
throw new Error(`Invalid "RequestType" in request "${JSON.stringify(req)}"`);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
-
import * as https from "https";
|
|
3
|
-
import type { StepFunctions, Lambda } from "aws-sdk";
|
|
4
|
-
declare function defaultHttpRequest(options: https.RequestOptions, responseBody: string): Promise<unknown>;
|
|
5
|
-
declare function defaultStartExecution(req: StepFunctions.StartExecutionInput): Promise<StepFunctions.StartExecutionOutput>;
|
|
6
|
-
declare function defaultInvokeFunction(req: Lambda.InvocationRequest): Promise<Lambda.InvocationResponse>;
|
|
7
|
-
export declare const startExecution: typeof defaultStartExecution;
|
|
8
|
-
export declare const invokeFunction: typeof defaultInvokeFunction;
|
|
9
|
-
export declare const httpRequest: typeof defaultHttpRequest;
|
|
10
|
-
export {};
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/* istanbul ignore file */
|
|
2
|
-
import * as https from "https";
|
|
3
|
-
import { createRequire } from "module";
|
|
4
|
-
const require = createRequire(import.meta.url);
|
|
5
|
-
const AWS = require("aws-sdk");
|
|
6
|
-
const FRAMEWORK_HANDLER_TIMEOUT = 900000; // 15 minutes
|
|
7
|
-
// In order to honor the overall maximum timeout set for the target process,
|
|
8
|
-
// the default 2 minutes from AWS SDK has to be overriden:
|
|
9
|
-
// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Config.html#httpOptions-property
|
|
10
|
-
const awsSdkConfig = {
|
|
11
|
-
httpOptions: { timeout: FRAMEWORK_HANDLER_TIMEOUT },
|
|
12
|
-
};
|
|
13
|
-
async function defaultHttpRequest(options, responseBody) {
|
|
14
|
-
return new Promise((resolve, reject) => {
|
|
15
|
-
try {
|
|
16
|
-
const request = https.request(options, resolve);
|
|
17
|
-
request.on("error", reject);
|
|
18
|
-
request.write(responseBody);
|
|
19
|
-
request.end();
|
|
20
|
-
}
|
|
21
|
-
catch (e) {
|
|
22
|
-
reject(e);
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
let sfn;
|
|
27
|
-
let lambda;
|
|
28
|
-
async function defaultStartExecution(req) {
|
|
29
|
-
if (!sfn) {
|
|
30
|
-
sfn = new AWS.StepFunctions(awsSdkConfig);
|
|
31
|
-
}
|
|
32
|
-
return sfn.startExecution(req).promise();
|
|
33
|
-
}
|
|
34
|
-
async function defaultInvokeFunction(req) {
|
|
35
|
-
if (!lambda) {
|
|
36
|
-
lambda = new AWS.Lambda(awsSdkConfig);
|
|
37
|
-
}
|
|
38
|
-
return lambda.invoke(req).promise();
|
|
39
|
-
}
|
|
40
|
-
export const startExecution = defaultStartExecution;
|
|
41
|
-
export const invokeFunction = defaultInvokeFunction;
|
|
42
|
-
export const httpRequest = defaultHttpRequest;
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-console */
|
|
2
|
-
export function getEnv(name) {
|
|
3
|
-
const value = process.env[name];
|
|
4
|
-
if (!value) {
|
|
5
|
-
throw new Error(`The environment variable "${name}" is not defined`);
|
|
6
|
-
}
|
|
7
|
-
return value;
|
|
8
|
-
}
|
|
9
|
-
export function log(title, ...args) {
|
|
10
|
-
console.log("[provider-framework]", title, ...args.map((x) => typeof x === "object" ? JSON.stringify(x, undefined, 2) : x));
|
|
11
|
-
}
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Based on https://github.com/aws/aws-cdk/blob/master/packages/cdk-assets/lib/private/archive.ts
|
|
3
|
-
*/
|
|
4
|
-
"use strict";
|
|
5
|
-
|
|
6
|
-
process.on("unhandledRejection", (err) => {
|
|
7
|
-
throw err;
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
const path = require("path");
|
|
11
|
-
const fs = require("fs/promises");
|
|
12
|
-
const fsSync = require("fs")
|
|
13
|
-
const archiver = require("archiver");
|
|
14
|
-
const glob = require("glob");
|
|
15
|
-
|
|
16
|
-
// Parse arguments
|
|
17
|
-
const argv = process.argv.slice(2);
|
|
18
|
-
const SITE_PATH = argv[0];
|
|
19
|
-
const ZIP_PATH = argv[1];
|
|
20
|
-
const FILE_SIZE_LIMIT_IN_MB = argv[2];
|
|
21
|
-
const FILE_SIZE_LIMIT_IN_BYTES = FILE_SIZE_LIMIT_IN_MB * 1024 * 1024;
|
|
22
|
-
|
|
23
|
-
// Get files
|
|
24
|
-
// The below options are needed to support following symlinks when building zip files:
|
|
25
|
-
// - nodir: This will prevent symlinks themselves from being copied into the zip.
|
|
26
|
-
// - follow: This will follow symlinks and copy the files within.
|
|
27
|
-
const globOptions = {
|
|
28
|
-
dot: true,
|
|
29
|
-
nodir: true,
|
|
30
|
-
follow: true,
|
|
31
|
-
cwd: SITE_PATH,
|
|
32
|
-
};
|
|
33
|
-
const files = glob.sync("**", globOptions); // The output here is already sorted
|
|
34
|
-
|
|
35
|
-
generateZips().catch(() => {
|
|
36
|
-
process.exit(1);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
function generateZips() {
|
|
40
|
-
// eslint-disable-next-line no-async-promise-executor
|
|
41
|
-
return new Promise(async (ok, fail) => {
|
|
42
|
-
let output;
|
|
43
|
-
let archive;
|
|
44
|
-
let totalSize;
|
|
45
|
-
const statuses = [];
|
|
46
|
-
|
|
47
|
-
async function openZip() {
|
|
48
|
-
const partId = statuses.length;
|
|
49
|
-
const filePath = path.join(ZIP_PATH, `part${partId}.zip`);
|
|
50
|
-
await fs.mkdir(path.dirname(filePath), {
|
|
51
|
-
recursive: true
|
|
52
|
-
})
|
|
53
|
-
output = fsSync.createWriteStream(filePath);
|
|
54
|
-
archive = archiver("zip");
|
|
55
|
-
totalSize = 0, "w";
|
|
56
|
-
statuses.push({
|
|
57
|
-
output,
|
|
58
|
-
archive,
|
|
59
|
-
isOutputClosed: false,
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
archive.on("warning", fail);
|
|
63
|
-
archive.on("error", fail);
|
|
64
|
-
// archive has been finalized and the output file descriptor has closed, resolve promise
|
|
65
|
-
// this has to be done before calling `finalize` since the events may fire immediately after.
|
|
66
|
-
// see https://www.npmjs.com/package/archiver
|
|
67
|
-
output.once("close", () => {
|
|
68
|
-
statuses[partId].isOutputClosed = true;
|
|
69
|
-
if (statuses.every(({ isOutputClosed }) => isOutputClosed)) {
|
|
70
|
-
ok();
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
archive.pipe(output);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Create the first zip file
|
|
77
|
-
await openZip();
|
|
78
|
-
|
|
79
|
-
// Append files serially to ensure file order
|
|
80
|
-
for (const file of files) {
|
|
81
|
-
const fullPath = path.join(SITE_PATH, file);
|
|
82
|
-
const [data, stat] = await Promise.all([
|
|
83
|
-
fs.readFile(fullPath),
|
|
84
|
-
fs.stat(fullPath),
|
|
85
|
-
]);
|
|
86
|
-
|
|
87
|
-
// Validate single file size cannot be greater than filesize limit
|
|
88
|
-
const filesize = stat.size;
|
|
89
|
-
if (filesize > FILE_SIZE_LIMIT_IN_BYTES) {
|
|
90
|
-
throw new Error(
|
|
91
|
-
`Cannot package file "${fullPath}". The file is larger than ${FILE_SIZE_LIMIT_IN_MB}MB.`
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Create a new zip file if current filesize exceeds
|
|
96
|
-
if (totalSize + filesize > FILE_SIZE_LIMIT_IN_BYTES) {
|
|
97
|
-
await archive.finalize();
|
|
98
|
-
openZip();
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
archive.append(data, {
|
|
102
|
-
name: file,
|
|
103
|
-
date: new Date("1980-01-01T00:00:00.000Z"), // reset dates to get the same hash for the same content
|
|
104
|
-
mode: stat.mode,
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
totalSize += filesize;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
await archive.finalize();
|
|
111
|
-
|
|
112
|
-
// Create a filenames file
|
|
113
|
-
const filenamesPath = path.join(ZIP_PATH, `filenames`);
|
|
114
|
-
await fs.writeFile(filenamesPath, files.join("\n"));
|
|
115
|
-
});
|
|
116
|
-
}
|