Skip to content

Instantly share code, notes, and snippets.

@rmarscher
Last active October 4, 2024 20:59
Show Gist options
  • Save rmarscher/152ab4c99e1566f0cf58b3d10ca25ec7 to your computer and use it in GitHub Desktop.
Save rmarscher/152ab4c99e1566f0cf58b3d10ca25ec7 to your computer and use it in GitHub Desktop.

Revisions

  1. rmarscher revised this gist Oct 4, 2024. 1 changed file with 0 additions and 1 deletion.
    1 change: 0 additions & 1 deletion waku.ts
    Original file line number Diff line number Diff line change
    @@ -29,7 +29,6 @@ import {
    validatePlan,
    } from "../../.sst/platform/src/components/aws/ssr-site.js";

    export type { Plan };
    export interface WakuArgs extends SsrSiteArgs {
    /**
    * Configure how this component works in `sst dev`.
  2. rmarscher revised this gist Oct 4, 2024. 2 changed files with 66 additions and 83 deletions.
    87 changes: 66 additions & 21 deletions waku.ts
    Original file line number Diff line number Diff line change
    @@ -27,17 +27,10 @@ import {
    prepare,
    useCloudFrontFunctionHostHeaderInjection,
    validatePlan,
    } from "./ssr-site.js";
    } from "../../.sst/platform/src/components/aws/ssr-site.js";

    export type { Plan };
    export interface WakuArgs extends SsrSiteArgs {
    /**
    * Ability to hook into and modify the SSR site plan before it's validated.
    * @param plan
    * @returns
    */
    beforePlanValidate?: (plan: Plan) => void;

    /**
    * Configure how this component works in `sst dev`.
    *
    @@ -436,6 +429,7 @@ export class Waku extends Component implements Link.Linkable {

    const { access, bucket } = createBucket(this, name, partition, args);
    const outputPath = buildApp(this, name, args, sitePath, args.buildCommand);
    rearrangeBuildOutput(outputPath);
    const plan = buildPlan();
    // console.log(plan);
    const { distribution, ssrFunctions, edgeFunctions } =
    @@ -449,6 +443,12 @@ export class Waku extends Component implements Link.Linkable {
    plan,
    );
    const serverFunction = ssrFunctions[0] ?? Object.values(edgeFunctions)[0];
    const distDir = "dist";
    const assetsDir = "assets";
    const functionDir = "function";
    const publicDir = "public";
    const privateDir = "private";
    const functionPublicDir = path.join(functionDir, publicDir);

    this.assets = bucket;
    this.cdn = distribution;
    @@ -466,6 +466,64 @@ export class Waku extends Component implements Link.Linkable {
    },
    });

    function rearrangeBuildOutput() {
    return all([outputPath]).apply(([outputPath]) => {
    const absoluteDistDir = path.join(outputPath, distDir);
    const absoluteFunctionDir = path.join(absoluteDistDir, functionDir);
    const absoluteFunctionPublicDir = path.join(
    absoluteDistDir,
    functionPublicDir,
    );
    const absolutePublicDir = path.join(absoluteDistDir, publicDir);
    const absolutePrivateDir = path.join(outputPath, privateDir);
    const absolutePrivateFunctionDir = path.join(
    absoluteFunctionDir,
    privateDir,
    );
    const absoluteTempDistDir = path.join(outputPath, "_dist");

    // Move the build from dist to dist/function
    fs.renameSync(absoluteDistDir, absoluteTempDistDir);
    fs.mkdirSync(absoluteDistDir);
    fs.renameSync(absoluteTempDistDir, absoluteFunctionDir);

    // Then move the dist/function/public folder to dist/public
    fs.renameSync(absoluteFunctionPublicDir, absolutePublicDir);

    // Copy the private folder to the function directory
    if (fs.existsSync(absolutePrivateDir)) {
    fs.cpSync(absolutePrivateDir, absolutePrivateFunctionDir, {
    recursive: true,
    });
    }

    // Assume that any user files in public do not need to be bundled
    // with the lambda function but public/assets/*.js and css do.
    // We'll also copy any html files to the function public folder
    // for use as custom error pages.
    fs.mkdirSync(absoluteFunctionPublicDir);
    const publicAssetsDir = path.join(absolutePublicDir, assetsDir);
    const files = fs
    .readdirSync(publicAssetsDir)
    .filter((file) => file.endsWith(".css") || file.endsWith(".js"));
    for (const file of files) {
    fs.cpSync(
    path.join(publicAssetsDir, file),
    path.join(absoluteFunctionPublicDir, assetsDir, file),
    );
    }
    const htmlOrTxtFiles = fs
    .readdirSync(absolutePublicDir)
    .filter((file) => file.endsWith(".html") || file.endsWith(".txt"));
    for (const file of htmlOrTxtFiles) {
    fs.cpSync(
    path.join(absolutePublicDir, file),
    path.join(absoluteFunctionPublicDir, file),
    );
    }
    });
    }

    function buildPlan() {
    return all([outputPath]).apply(([outputPath]) => {
    const distDir = "dist";
    @@ -482,14 +540,6 @@ export class Waku extends Component implements Link.Linkable {
    // or via code injected into the Cloudfront function.
    // https://developers.cloudflare.com/pages/configuration/headers/

    console.log({
    outputPath,
    distDir,
    functionDir,
    publicDir,
    versionedSubDir,
    });

    // TODO maybe add a build arg for isStatic
    // waku also has a dynamic/static config setting for pages
    const isStatic = false;
    @@ -617,10 +667,6 @@ export class Waku extends Component implements Link.Linkable {
    }
    }

    if (args.beforePlanValidate) {
    args.beforePlanValidate(plan);
    }

    return validatePlan(plan);
    });
    }
    @@ -671,4 +717,3 @@ export class Waku extends Component implements Link.Linkable {
    const __pulumiType = "sst:aws:Waku";
    // @ts-expect-error
    Waku.__pulumiType = __pulumiType;
    d
    62 changes: 0 additions & 62 deletions [email protected]
    Original file line number Diff line number Diff line change
    @@ -1,62 +0,0 @@
    diff --git a/dist/lib/plugins/vite-plugin-deploy-aws-lambda.d.ts b/dist/lib/plugins/vite-plugin-deploy-aws-lambda.d.ts
    index b45730c18cf25b27bdb28fe94104bfd79b238bd3..0f40ca97bfdc8aa050a0b7768cda1c130937295a 100644
    --- a/dist/lib/plugins/vite-plugin-deploy-aws-lambda.d.ts
    +++ b/dist/lib/plugins/vite-plugin-deploy-aws-lambda.d.ts
    @@ -2,4 +2,5 @@ import type { Plugin } from 'vite';
    export declare function deployAwsLambdaPlugin(opts: {
    srcDir: string;
    distDir: string;
    + privateDir: string;
    }): Plugin;
    diff --git a/dist/lib/plugins/vite-plugin-deploy-aws-lambda.js b/dist/lib/plugins/vite-plugin-deploy-aws-lambda.js
    index 32d94313bbd62159f78f9775c2996ec57c5ff751..aa407724d1091e96a3ff9e1f96a33a4b8415233b 100644
    --- a/dist/lib/plugins/vite-plugin-deploy-aws-lambda.js
    +++ b/dist/lib/plugins/vite-plugin-deploy-aws-lambda.js
    @@ -1,8 +1,8 @@
    import path from 'node:path';
    -import { writeFileSync } from 'node:fs';
    +import { appendFileSync, cpSync, existsSync, mkdirSync, readdirSync, renameSync, writeFileSync } from 'node:fs';
    import { unstable_getPlatformObject } from '../../server.js';
    import { SRC_ENTRIES } from '../constants.js';
    -import { DIST_PUBLIC } from '../builder/constants.js';
    +import { DIST_ASSETS, DIST_ENTRIES_JS, DIST_PUBLIC } from '../builder/constants.js';
    const SERVE_JS = 'serve-aws-lambda.js';
    const lambdaStreaming = process.env.DEPLOY_AWS_LAMBDA_STREAMING === 'true';
    const getServeJsContent = (distDir, distPublic, srcEntriesFile)=>`
    @@ -79,6 +79,36 @@ export function deployAwsLambdaPlugin(opts) {
    writeFileSync(path.join(opts.distDir, 'package.json'), JSON.stringify({
    type: 'module'
    }, null, 2));
    + // Move the distDir so we can move files back to different locations
    + renameSync(opts.distDir, '_dist');
    + mkdirSync(opts.distDir);
    + const functionDir = path.join(opts.distDir, 'function');
    + const functionPublicDir = path.join(functionDir, DIST_PUBLIC);
    + const publicDir = path.join(opts.distDir, 'public');
    + // Move everything to the function folder
    + renameSync('_dist', functionDir);
    + // Then move the function public folder
    + renameSync(functionPublicDir, publicDir);
    + if (existsSync(opts.privateDir)) {
    + cpSync(opts.privateDir, path.join(functionDir, opts.privateDir), {
    + recursive: true
    + });
    + }
    + appendFileSync(path.join(functionDir, DIST_ENTRIES_JS), `export const buildData = ${JSON.stringify(platformObject.buildData)};`);
    + // Assume that any user files in public do not need to be bundled
    + // with the lambda function but public/assets/*.js and css do.
    + // We'll also copy any html files to the function public folder
    + // for use as custom error pages.
    + mkdirSync(functionPublicDir);
    + const publicAssetsDir = path.join(publicDir, DIST_ASSETS);
    + const files = readdirSync(publicAssetsDir).filter((file)=>file.endsWith('.css') || file.endsWith('.js'));
    + for (const file of files){
    + cpSync(path.join(publicAssetsDir, file), path.join(functionPublicDir, DIST_ASSETS, file));
    + }
    + const htmlOrTxtFiles = readdirSync(publicDir).filter((file)=>file.endsWith('.html') || file.endsWith('.txt'));
    + for (const file of htmlOrTxtFiles){
    + cpSync(path.join(publicDir, file), path.join(functionPublicDir, file));
    + }
    }
    };
    }
  3. rmarscher created this gist Oct 4, 2024.
    674 changes: 674 additions & 0 deletions waku.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,674 @@
    import {
    type ComponentResourceOptions,
    type Output,
    Unwrap,
    all,
    output,
    } from "@pulumi/pulumi";
    import * as fs from "node:fs";
    import * as path from "node:path";
    import type { Bucket } from "../../.sst/platform/src/components/aws/bucket.js";
    import type { Cdn } from "../../.sst/platform/src/components/aws/cdn.js";
    import type {
    FunctionArgs,
    Function as SstAwsFunction,
    } from "../../.sst/platform/src/components/aws/function.js";
    import { URL_UNAVAILABLE } from "../../.sst/platform/src/components/aws/linkable.js";
    import { buildApp } from "../../.sst/platform/src/components/base/base-ssr-site.js";
    import { Component } from "../../.sst/platform/src/components/component.js";
    import type { DevArgs } from "../../.sst/platform/src/components/dev.js";
    import { Link } from "../../.sst/platform/src/components/link.js";
    import {
    type Plan,
    type SsrSiteArgs,
    createBucket,
    createDevServer,
    createServersAndDistribution,
    prepare,
    useCloudFrontFunctionHostHeaderInjection,
    validatePlan,
    } from "./ssr-site.js";

    export type { Plan };
    export interface WakuArgs extends SsrSiteArgs {
    /**
    * Ability to hook into and modify the SSR site plan before it's validated.
    * @param plan
    * @returns
    */
    beforePlanValidate?: (plan: Plan) => void;

    /**
    * Configure how this component works in `sst dev`.
    *
    * :::note
    * In `sst dev` your Remix app is run in dev mode; it's not deployed.
    * :::
    *
    * Instead of deploying your Remix app, this starts it in dev mode. It's run
    * as a separate process in the `sst dev` multiplexer. Read more about
    * [`sst dev`](/docs/reference/cli/#dev).
    */
    dev?: DevArgs["dev"];
    /**
    * The number of instances of the [server function](#nodes-server) to keep warm. This is useful for cases where you are experiencing long cold starts. The default is to not keep any instances warm.
    *
    * This works by starting a serverless cron job to make _n_ concurrent requests to the server function every few minutes. Where _n_ is the number of instances to keep warm.
    *
    * @default `0`
    */
    warm?: SsrSiteArgs["warm"];
    /**
    * Permissions and the resources that the [server function](#nodes-server) in your Waku site needs to access. These permissions are used to create the function's IAM role.
    *
    * :::tip
    * If you `link` the function to a resource, the permissions to access it are
    * automatically added.
    * :::
    *
    * @example
    * Allow reading and writing to an S3 bucket called `my-bucket`.
    * ```js
    * {
    * permissions: [
    * {
    * actions: ["s3:GetObject", "s3:PutObject"],
    * resources: ["arn:aws:s3:::my-bucket/*"]
    * },
    * ]
    * }
    * ```
    *
    * Perform all actions on an S3 bucket called `my-bucket`.
    *
    * ```js
    * {
    * permissions: [
    * {
    * actions: ["s3:*"],
    * resources: ["arn:aws:s3:::my-bucket/*"]
    * },
    * ]
    * }
    * ```
    *
    * Grant permissions to access all resources.
    *
    * ```js
    * {
    * permissions: [
    * {
    * actions: ["*"],
    * resources: ["*"]
    * },
    * ]
    * }
    * ```
    */
    permissions?: SsrSiteArgs["permissions"];
    /**
    * Path to the directory where your Waku site is located. This path is relative to your `sst.config.ts`.
    *
    * By default it assumes your Waku site is in the root of your SST app.
    * @default `"."`
    *
    * @example
    *
    * If your Waku site is in a package in your monorepo.
    *
    * ```js
    * {
    * path: "packages/web"
    * }
    * ```
    */
    path?: SsrSiteArgs["path"];
    /**
    * [Link resources](/docs/linking/) to your Waku site. This will:
    *
    * 1. Grant the permissions needed to access the resources.
    * 2. Allow you to access it in your site using the [SDK](/docs/reference/sdk/).
    *
    * @example
    *
    * Takes a list of resources to link to the function.
    *
    * ```js
    * {
    * link: [bucket, stripeKey]
    * }
    * ```
    */
    link?: SsrSiteArgs["link"];
    /**
    * Set [environment variables](https://waku.gg/#environment-variables) in your Waku site. These are made available:
    *
    * 1. In `waku build`, they are loaded into `import.meta.env`.
    * 2. Locally while running `sst dev waku dev`.
    *
    * :::tip
    * You can also `link` resources to your Waku site and access them in a type-safe way with the [SDK](/docs/reference/sdk/). We recommend linking since it's more secure.
    * :::
    *
    * Recall that in Waku, you need to prefix your environment variables with `WAKU_PUBLIC_` to access them on the client-side. [Read more here](https://waku.gg/#environment-variables).
    *
    * @example
    * ```js
    * {
    * environment: {
    * API_URL: api.url,
    * // Accessible on the client-side
    * WAKU_PUBLIC_STRIPE_PUBLISHABLE_KEY: "pk_test_123"
    * }
    * }
    * ```
    */
    environment?: SsrSiteArgs["environment"];
    /**
    * Set a custom domain for your Waku site.
    *
    * Automatically manages domains hosted on AWS Route 53, Cloudflare, and Vercel. For other
    * providers, you'll need to pass in a `cert` that validates domain ownership and add the
    * DNS records.
    *
    * :::tip
    * Built-in support for AWS Route 53, Cloudflare, and Vercel. And manual setup for other
    * providers.
    * :::
    *
    * @example
    *
    * By default this assumes the domain is hosted on Route 53.
    *
    * ```js
    * {
    * domain: "example.com"
    * }
    * ```
    *
    * For domains hosted on Cloudflare.
    *
    * ```js
    * {
    * domain: {
    * name: "example.com",
    * dns: sst.cloudflare.dns()
    * }
    * }
    * ```
    *
    * Specify a `www.` version of the custom domain.
    *
    * ```js
    * {
    * domain: {
    * name: "domain.com",
    * redirects: ["www.domain.com"]
    * }
    * }
    * ```
    */
    domain?: SsrSiteArgs["domain"];
    /**
    * The command used internally to build your Waku site.
    *
    * @default `"npm run build"`
    *
    * @example
    *
    * If you want to use a different build command.
    * ```js
    * {
    * buildCommand: "yarn build"
    * }
    * ```
    */
    buildCommand?: SsrSiteArgs["buildCommand"];
    /**
    * Configure how the Waku site assets are uploaded to S3.
    *
    * By default, this is set to the following. Read more about these options below.
    * ```js
    * {
    * assets: {
    * textEncoding: "utf-8",
    * versionedFilesCacheHeader: "public,max-age=31536000,immutable",
    * nonVersionedFilesCacheHeader: "public,max-age=0,s-maxage=86400,stale-while-revalidate=8640"
    * }
    * }
    * ```
    */
    assets?: SsrSiteArgs["assets"];
    /**
    * Configure the [server function](#nodes-server) in your Waku site to connect
    * to private subnets in a virtual private cloud or VPC. This allows your site to
    * access private resources.
    *
    * @example
    * ```js
    * {
    * vpc: {
    * securityGroups: ["sg-0399348378a4c256c"],
    * subnets: ["subnet-0b6a2b73896dc8c4c", "subnet-021389ebee680c2f0"]
    * }
    * }
    * ```
    */
    vpc?: SsrSiteArgs["vpc"];
    /**
    * Configure the Waku site to use an existing CloudFront cache policy.
    *
    * :::note
    * CloudFront has a limit of 20 cache policies per account, though you can request a limit
    * increase.
    * :::
    *
    * By default, a new cache policy is created for it. This allows you to reuse an existing
    * policy instead of creating a new one.
    *
    * @default A new cache plolicy is created
    * @example
    * ```js
    * {
    * cachePolicy: "658327ea-f89d-4fab-a63d-7e88639e58f6"
    * }
    * ```
    */
    cachePolicy?: SsrSiteArgs["cachePolicy"];

    server?: {
    /**
    * The amount of memory allocated to the server function.
    * Takes values between 128 MB and 10240 MB in 1 MB increments.
    *
    * @default `"1024 MB"`
    * @example
    * ```js
    * {
    * server: {
    * memory: "2048 MB"
    * }
    * }
    * ```
    */
    memory?: FunctionArgs["memory"];
    /**
    * The [architecture](https://docs.aws.amazon.com/lambda/latest/dg/foundation-arch.html)
    * of the server function.
    *
    * @default `"x86_64"`
    * @example
    * ```js
    * {
    * server: {
    * architecture: "arm64"
    * }
    * }
    * ```
    */
    architecture?: FunctionArgs["architecture"];

    function?: Partial<Unwrap<FunctionArgs>>;
    };
    }

    /**
    * The `Waku` component lets you deploy a [Waku](https://waku.gg/) site to AWS.
    *
    * @example
    *
    * #### Minimal example
    *
    * Deploy the Waku site that's in the project root.
    *
    * ```js title="sst.config.ts"
    * new sst.aws.Waku("MyWeb");
    * ```
    *
    * #### Change the path
    *
    * Deploys the Waku site in the `my-waku-app/` directory.
    *
    * ```js {2} title="sst.config.ts"
    * new sst.aws.Waku("MyWeb", {
    * path: "my-waku-app/"
    * });
    * ```
    *
    * #### Add a custom domain
    *
    * Set a custom domain for your Waku site.
    *
    * ```js {2} title="sst.config.ts"
    * new sst.aws.Waku("MyWeb", {
    * domain: "my-app.com"
    * });
    * ```
    *
    * #### Redirect www to apex domain
    *
    * Redirect `www.my-app.com` to `my-app.com`.
    *
    * ```js {4} title="sst.config.ts"
    * new sst.aws.Waku("MyWeb", {
    * domain: {
    * name: "my-app.com",
    * redirects: ["www.my-app.com"]
    * }
    * });
    * ```
    *
    * #### Link resources
    *
    * [Link resources](/docs/linking/) to your Waku site. This will grant permissions
    * to the resources and allow you to access it in your site.
    *
    * ```ts {4} title="sst.config.ts"
    * const bucket = new sst.aws.Bucket("MyBucket");
    *
    * new sst.aws.Waku("MyWeb", {
    * link: [bucket]
    * });
    * ```
    *
    * You can use the [SDK](/docs/reference/sdk/) to access the linked resources
    * in your Waku site.
    *
    * ```ts title="src/pages/index.waku"
    * import { Resource } from "sst";
    *
    * console.log(Resource.MyBucket.name);
    * ```
    */
    export class Waku extends Component implements Link.Linkable {
    private cdn?: Output<Cdn>;
    private assets?: Bucket;
    private server?: Output<SstAwsFunction>;
    private devUrl?: Output<string>;

    constructor(
    name: string,
    args: WakuArgs = {},
    opts: ComponentResourceOptions = {},
    ) {
    super(__pulumiType, name, args, opts);

    const { sitePath, partition } = prepare(args, opts);

    if ($dev) {
    const server = createDevServer(this, name, args);
    this.devUrl = output(args.dev?.url ?? URL_UNAVAILABLE);
    this.registerOutputs({
    _metadata: {
    mode: "placeholder",
    path: sitePath,
    server: server.arn,
    },
    _receiver: {
    directory: sitePath,
    links: output(args.link || [])
    .apply(Link.build)
    .apply((links) => links.map((link) => link.name)),
    aws: {
    role: server.nodes.role.arn,
    },
    environment: args.environment,
    },
    _dev: {
    links: output(args.link || [])
    .apply(Link.build)
    .apply((links) => links.map((link) => link.name)),
    aws: {
    role: server.nodes.role.arn,
    },
    environment: args.environment,
    directory: output(args.dev?.directory).apply(
    (dir) => dir || sitePath,
    ),
    autostart: output(args.dev?.autostart).apply((val) => val ?? true),
    command: output(args.dev?.command).apply(
    (val) => val || "npm run dev",
    ),
    },
    });
    return;
    }

    const { access, bucket } = createBucket(this, name, partition, args);
    const outputPath = buildApp(this, name, args, sitePath, args.buildCommand);
    const plan = buildPlan();
    // console.log(plan);
    const { distribution, ssrFunctions, edgeFunctions } =
    createServersAndDistribution(
    this,
    name,
    args,
    outputPath,
    access,
    bucket,
    plan,
    );
    const serverFunction = ssrFunctions[0] ?? Object.values(edgeFunctions)[0];

    this.assets = bucket;
    this.cdn = distribution;
    this.server = serverFunction;
    this.registerOutputs({
    _hint: all([this.cdn.domainUrl, this.cdn.url]).apply(
    ([domainUrl, url]) => domainUrl ?? url,
    ),
    _metadata: {
    mode: "deployed",
    path: sitePath,
    url: distribution.apply((d) => d.domainUrl ?? d.url),
    edge: plan.edge,
    server: serverFunction.arn,
    },
    });

    function buildPlan() {
    return all([outputPath]).apply(([outputPath]) => {
    const distDir = "dist";
    const functionDir = "function";
    const publicDir = "public";
    const relativePublicDir = path.join(distDir, "public");
    const relativeFunctionDir = path.join(distDir, functionDir);
    const absoluteFunctionDir = path.join(outputPath, relativeFunctionDir);
    const versionedSubDir = "assets"; // waku places versioned files in public/assets
    // It would be nice for devs to be able to define cache headers for other files
    // that were manually added to the publicDir. With Cloudflare Pages, you can create
    // a _headers file which will be used to set headers for various routes. Maybe
    // that same file format could be read here and applied to the Cloudfront cache behaviors
    // or via code injected into the Cloudfront function.
    // https://developers.cloudflare.com/pages/configuration/headers/

    console.log({
    outputPath,
    distDir,
    functionDir,
    publicDir,
    versionedSubDir,
    });

    // TODO maybe add a build arg for isStatic
    // waku also has a dynamic/static config setting for pages
    const isStatic = false;
    const edge = false; // IIRC, Lambda@Edge doesn't support streaming, so we don't want it with React 19

    const serverConfig: Unwrap<FunctionArgs> = {
    streaming: true,
    ...args.server?.function,
    environment: args.environment,
    memory: args.server?.memory,
    architecture: args.server?.architecture,
    bundle: absoluteFunctionDir,
    handler: "serve-aws-lambda.handler",
    };

    const plan: Plan = {
    edge,
    // Cloudfront doesn't automatically move the Host to X-Forwarded-Host??
    // It seems a bit unnecessary to run a cloudflare function on every
    // request just to copy the header. TODO test to make sure it's needed
    cloudFrontFunctions: {
    server: {
    injections: [
    useCloudFrontFunctionHostHeaderInjection(),
    // Noting that Astro uses a custom cloudfront routing function
    // to handle requests for static page vs server endpoint vs redirects
    // useCloudFrontRoutingInjection(publicDir),
    ],
    },
    serverHostOnly: {
    injections: [useCloudFrontFunctionHostHeaderInjection()],
    },
    },
    origins: {
    staticsServer: {
    s3: {
    copy: [
    {
    from: relativePublicDir,
    to: "",
    cached: true,
    versionedSubDir,
    },
    ],
    },
    },
    },
    behaviors: [],
    errorResponses: [],
    };

    if (edge) {
    plan.edgeFunctions = {
    server: {
    function: serverConfig,
    },
    };
    plan.behaviors.push(
    {
    cacheType: "server",
    cfFunction: "server",
    edgeFunction: "edgeServer",
    origin: "staticsServer",
    },
    ...fs.readdirSync(relativePublicDir).map(
    (item) =>
    ({
    cacheType: "static",
    pattern: fs
    .statSync(path.join(relativePublicDir, item))
    .isDirectory()
    ? `${item}/*`
    : item,
    origin: "staticsServer",
    }) as const,
    ),
    );
    } else {
    if (isStatic) {
    plan.behaviors.push({
    cacheType: "static",
    cfFunction: "server",
    origin: "staticsServer",
    });
    } else {
    // biome-ignore lint/style/noNonNullAssertion: defined in plan above
    plan.cloudFrontFunctions!.imageServiceCfFunction = {
    injections: [useCloudFrontFunctionHostHeaderInjection()],
    };

    plan.origins.regionalServer = {
    server: {
    function: serverConfig,
    },
    };

    plan.origins.fallthroughServer = {
    group: {
    primaryOriginName: "staticsServer",
    fallbackOriginName: "regionalServer",
    fallbackStatusCodes: [403, 404],
    },
    };

    plan.behaviors.push(
    {
    cacheType: "server",
    cfFunction: "server",
    origin: "fallthroughServer",
    allowedMethods: ["GET", "HEAD", "OPTIONS"],
    },
    {
    cacheType: "static",
    pattern: `${versionedSubDir}/*`,
    origin: "staticsServer",
    },
    {
    cacheType: "server",
    pattern: "_image",
    cfFunction: "imageServiceCfFunction",
    origin: "regionalServer",
    allowedMethods: ["GET", "HEAD", "OPTIONS"],
    },
    );
    }
    }

    if (args.beforePlanValidate) {
    args.beforePlanValidate(plan);
    }

    return validatePlan(plan);
    });
    }
    }

    /**
    * The URL of the Waku site.
    *
    * If the `domain` is set, this is the URL with the custom domain.
    * Otherwise, it's the autogenerated CloudFront URL.
    */
    public get url() {
    return all([this.cdn?.domainUrl, this.cdn?.url, this.devUrl]).apply(
    // biome-ignore lint/style/noNonNullAssertion: devUrl should be non empty here
    ([domainUrl, url, dev]) => domainUrl ?? url ?? dev!,
    );
    }

    /**
    * The underlying [resources](/docs/components/#nodes) this component creates.
    */
    public get nodes() {
    return {
    /**
    * The AWS Lambda server function that renders the site.
    */
    server: this.server,
    /**
    * The Amazon S3 Bucket that stores the assets.
    */
    assets: this.assets,
    /**
    * The Amazon CloudFront CDN that serves the site.
    */
    cdn: this.cdn,
    };
    }

    /** @internal */
    public getSSTLink() {
    return {
    properties: {
    url: this.url,
    },
    };
    }
    }
    const __pulumiType = "sst:aws:Waku";
    // @ts-expect-error
    Waku.__pulumiType = __pulumiType;
    d
    62 changes: 62 additions & 0 deletions [email protected]
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,62 @@
    diff --git a/dist/lib/plugins/vite-plugin-deploy-aws-lambda.d.ts b/dist/lib/plugins/vite-plugin-deploy-aws-lambda.d.ts
    index b45730c18cf25b27bdb28fe94104bfd79b238bd3..0f40ca97bfdc8aa050a0b7768cda1c130937295a 100644
    --- a/dist/lib/plugins/vite-plugin-deploy-aws-lambda.d.ts
    +++ b/dist/lib/plugins/vite-plugin-deploy-aws-lambda.d.ts
    @@ -2,4 +2,5 @@ import type { Plugin } from 'vite';
    export declare function deployAwsLambdaPlugin(opts: {
    srcDir: string;
    distDir: string;
    + privateDir: string;
    }): Plugin;
    diff --git a/dist/lib/plugins/vite-plugin-deploy-aws-lambda.js b/dist/lib/plugins/vite-plugin-deploy-aws-lambda.js
    index 32d94313bbd62159f78f9775c2996ec57c5ff751..aa407724d1091e96a3ff9e1f96a33a4b8415233b 100644
    --- a/dist/lib/plugins/vite-plugin-deploy-aws-lambda.js
    +++ b/dist/lib/plugins/vite-plugin-deploy-aws-lambda.js
    @@ -1,8 +1,8 @@
    import path from 'node:path';
    -import { writeFileSync } from 'node:fs';
    +import { appendFileSync, cpSync, existsSync, mkdirSync, readdirSync, renameSync, writeFileSync } from 'node:fs';
    import { unstable_getPlatformObject } from '../../server.js';
    import { SRC_ENTRIES } from '../constants.js';
    -import { DIST_PUBLIC } from '../builder/constants.js';
    +import { DIST_ASSETS, DIST_ENTRIES_JS, DIST_PUBLIC } from '../builder/constants.js';
    const SERVE_JS = 'serve-aws-lambda.js';
    const lambdaStreaming = process.env.DEPLOY_AWS_LAMBDA_STREAMING === 'true';
    const getServeJsContent = (distDir, distPublic, srcEntriesFile)=>`
    @@ -79,6 +79,36 @@ export function deployAwsLambdaPlugin(opts) {
    writeFileSync(path.join(opts.distDir, 'package.json'), JSON.stringify({
    type: 'module'
    }, null, 2));
    + // Move the distDir so we can move files back to different locations
    + renameSync(opts.distDir, '_dist');
    + mkdirSync(opts.distDir);
    + const functionDir = path.join(opts.distDir, 'function');
    + const functionPublicDir = path.join(functionDir, DIST_PUBLIC);
    + const publicDir = path.join(opts.distDir, 'public');
    + // Move everything to the function folder
    + renameSync('_dist', functionDir);
    + // Then move the function public folder
    + renameSync(functionPublicDir, publicDir);
    + if (existsSync(opts.privateDir)) {
    + cpSync(opts.privateDir, path.join(functionDir, opts.privateDir), {
    + recursive: true
    + });
    + }
    + appendFileSync(path.join(functionDir, DIST_ENTRIES_JS), `export const buildData = ${JSON.stringify(platformObject.buildData)};`);
    + // Assume that any user files in public do not need to be bundled
    + // with the lambda function but public/assets/*.js and css do.
    + // We'll also copy any html files to the function public folder
    + // for use as custom error pages.
    + mkdirSync(functionPublicDir);
    + const publicAssetsDir = path.join(publicDir, DIST_ASSETS);
    + const files = readdirSync(publicAssetsDir).filter((file)=>file.endsWith('.css') || file.endsWith('.js'));
    + for (const file of files){
    + cpSync(path.join(publicAssetsDir, file), path.join(functionPublicDir, DIST_ASSETS, file));
    + }
    + const htmlOrTxtFiles = readdirSync(publicDir).filter((file)=>file.endsWith('.html') || file.endsWith('.txt'));
    + for (const file of htmlOrTxtFiles){
    + cpSync(path.join(publicDir, file), path.join(functionPublicDir, file));
    + }
    }
    };
    }