From e58e6d23688acaa76b596436f89ee61f591f6bd8 Mon Sep 17 00:00:00 2001 From: Shoujian Zheng Date: Sat, 11 May 2024 15:57:21 +0800 Subject: [PATCH] feat(sdk): add Website resource type --- .changeset/red-geese-swim.md | 6 ++ packages/pluto-infra/package.json | 6 +- packages/pluto-infra/src/aws/bucket.s3.ts | 70 +++++++++++- .../pluto-infra/src/aws/function.lambda.ts | 4 +- packages/pluto-infra/src/aws/index.ts | 1 + .../pluto-infra/src/aws/router.apigateway.ts | 2 +- packages/pluto-infra/src/aws/website.s3.ts | 102 ++++++++++++++++++ packages/pluto-infra/src/index.ts | 1 + packages/pluto-infra/src/website.ts | 50 +++++++++ packages/pluto-py/CHANGELOG.md | 5 + packages/pluto-py/pluto_client/__init__.py | 3 + .../pluto_client/clients/shared/__init__.py | 1 + .../pluto_client/clients/shared/website.py | 17 +++ packages/pluto-py/pluto_client/website.py | 63 +++++++++++ packages/pluto-py/pyproject.toml | 2 +- packages/pluto/src/clients/shared/index.ts | 1 + packages/pluto/src/clients/shared/website.ts | 17 +++ packages/pluto/src/index.ts | 1 + packages/pluto/src/website.ts | 68 ++++++++++++ pnpm-lock.yaml | 68 +++++++----- 20 files changed, 456 insertions(+), 32 deletions(-) create mode 100644 .changeset/red-geese-swim.md create mode 100644 packages/pluto-infra/src/aws/website.s3.ts create mode 100644 packages/pluto-infra/src/website.ts create mode 100644 packages/pluto-py/CHANGELOG.md create mode 100644 packages/pluto-py/pluto_client/clients/shared/website.py create mode 100644 packages/pluto-py/pluto_client/website.py create mode 100644 packages/pluto/src/clients/shared/website.ts create mode 100644 packages/pluto/src/website.ts diff --git a/.changeset/red-geese-swim.md b/.changeset/red-geese-swim.md new file mode 100644 index 00000000..ca023ae8 --- /dev/null +++ b/.changeset/red-geese-swim.md @@ -0,0 +1,6 @@ +--- +"@plutolang/pluto-infra": patch +"@plutolang/pluto": patch +--- + +feat(sdk): add Website resource type diff --git a/packages/pluto-infra/package.json b/packages/pluto-infra/package.json index 03f7d09b..21841c59 100644 --- a/packages/pluto-infra/package.json +++ b/packages/pluto-infra/package.json @@ -24,18 +24,20 @@ "@pulumi/docker": "4.4.3", "@pulumi/kubernetes": "4.3.0", "@pulumi/pulumi": "3.88.0", - "fs-extra": "^11.1.1" + "fs-extra": "^11.1.1", + "glob": "^10.3.10", + "mime-types": "^2.1.35" }, "devDependencies": { "@aws-sdk/core": "^3.431.0", "@types/aws-lambda": "^8.10.131", "@types/express": "^4.17.20", "@types/fs-extra": "^11.0.4", + "@types/mime-types": "^2.1.4", "@types/node": "^20.8.4", "@vitest/coverage-v8": "^0.34.6", "cloudevents": "^8.0.0", "express": "^4.18.2", - "glob": "^10.3.10", "typescript": "^5.2.2", "vitest": "^0.34.6" } diff --git a/packages/pluto-infra/src/aws/bucket.s3.ts b/packages/pluto-infra/src/aws/bucket.s3.ts index 9e8bc493..4b5a7770 100644 --- a/packages/pluto-infra/src/aws/bucket.s3.ts +++ b/packages/pluto-infra/src/aws/bucket.s3.ts @@ -14,13 +14,13 @@ export enum S3Ops { export class S3Bucket extends pulumi.ComponentResource implements IResourceInfra { public readonly id: string; - public readonly bucket: aws.s3.Bucket; + public readonly bucket: aws.s3.BucketV2; constructor(name: string, opts?: BucketOptions) { super("pluto:bucket:aws/S3", name, opts); this.id = genResourceId(Bucket.fqn, name); - const bucket = new aws.s3.Bucket( + const bucket = new aws.s3.BucketV2( genAwsResourceName(this.id), { bucket: genAwsResourceName(this.id), @@ -32,6 +32,72 @@ export class S3Bucket extends pulumi.ComponentResource implements IResourceInfra this.bucket = bucket; } + /** + * Enable static website hosting for the bucket. Returns the website endpoint. + */ + public configWebsite(indexDocument: string, errorDocument?: string) { + const config = new aws.s3.BucketWebsiteConfigurationV2( + genAwsResourceName(this.id), + { + bucket: this.bucket.bucket, + indexDocument: { + suffix: indexDocument, + }, + errorDocument: errorDocument + ? { + key: errorDocument, + } + : undefined, + }, + { + parent: this, + } + ); + return config.websiteEndpoint.apply((endpoint) => `http://${endpoint}`); + } + + public setPublic() { + // AWS S3 disable public access by default. + const bucketPublicAccessBlock = new aws.s3.BucketPublicAccessBlock( + genAwsResourceName(this.id), + { + bucket: this.bucket.bucket, + blockPublicPolicy: false, + blockPublicAcls: false, + restrictPublicBuckets: false, + ignorePublicAcls: false, + }, + { + parent: this, + } + ); + + // Allow public access to the objects in the bucket. + new aws.s3.BucketPolicy( + genAwsResourceName(this.id), + { + bucket: this.bucket.bucket, + policy: this.bucket.bucket.apply((bucketName) => + JSON.stringify({ + Version: "2012-10-17", + Statement: [ + { + Effect: "Allow", + Principal: "*", + Action: ["s3:GetObject"], + Resource: [`arn:aws:s3:::${bucketName}/*`], + }, + ], + }) + ), + }, + { + parent: this, + dependsOn: [bucketPublicAccessBlock], + } + ); + } + public grantPermission(op: string): Permission { const actions: string[] = []; switch (op) { diff --git a/packages/pluto-infra/src/aws/function.lambda.ts b/packages/pluto-infra/src/aws/function.lambda.ts index 5dc3f6f4..131e56bc 100644 --- a/packages/pluto-infra/src/aws/function.lambda.ts +++ b/packages/pluto-infra/src/aws/function.lambda.ts @@ -189,8 +189,8 @@ export class Lambda extends pulumi.ComponentResource implements IResourceInfra, const lambdaAssetName = genAwsResourceName(this.id, Date.now().toString()); function upload(): pulumi.Output { const lambdaZip = new pulumi.asset.FileArchive(workdir); - const object = new aws.s3.BucketObject(lambdaAssetName, { - bucket: Lambda.lambdaAssetsBucket!.bucket, + const object = new aws.s3.BucketObjectv2(lambdaAssetName, { + bucket: Lambda.lambdaAssetsBucket!.bucket.bucket, source: lambdaZip, }); return object.key; diff --git a/packages/pluto-infra/src/aws/index.ts b/packages/pluto-infra/src/aws/index.ts index e2edf22b..1ea7cead 100644 --- a/packages/pluto-infra/src/aws/index.ts +++ b/packages/pluto-infra/src/aws/index.ts @@ -6,3 +6,4 @@ export { CloudWatchSchedule } from "./schedule.cloudwatch"; export { AwsTester } from "./tester"; export { SageMaker } from "./sagemaker"; export { S3Bucket } from "./bucket.s3"; +export { Website } from "./website.s3"; diff --git a/packages/pluto-infra/src/aws/router.apigateway.ts b/packages/pluto-infra/src/aws/router.apigateway.ts index df331d9e..0ab3ca39 100644 --- a/packages/pluto-infra/src/aws/router.apigateway.ts +++ b/packages/pluto-infra/src/aws/router.apigateway.ts @@ -26,7 +26,7 @@ export class ApiGatewayRouter { public readonly id: string; - public _url: pulumi.Output = pulumi.interpolate`unkonwn`; + public _url: pulumi.Output; private apiGateway: Api; private routes: Route[]; diff --git a/packages/pluto-infra/src/aws/website.s3.ts b/packages/pluto-infra/src/aws/website.s3.ts new file mode 100644 index 00000000..b5079237 --- /dev/null +++ b/packages/pluto-infra/src/aws/website.s3.ts @@ -0,0 +1,102 @@ +import { glob } from "glob"; +import * as path from "path"; +import * as fs from "fs-extra"; +import * as aws from "@pulumi/aws"; +import * as mime from "mime-types"; +import * as pulumi from "@pulumi/pulumi"; +import { IResourceInfra } from "@plutolang/base"; +import { genResourceId } from "@plutolang/base/utils"; +import { Website as WebsiteProto, WebsiteOptions } from "@plutolang/pluto"; +import { S3Bucket } from "./bucket.s3"; + +export class Website extends pulumi.ComponentResource implements IResourceInfra { + public readonly id: string; + + private readonly envs: { [key: string]: pulumi.Output | string } = {}; + private readonly websiteDir: string; + + private readonly websiteBucket: S3Bucket; + + private readonly websiteEndpoint: pulumi.Output; + + // eslint-disable-next-line + public outputs?: pulumi.Output; + + constructor(websiteRoot: string, name?: string, options?: WebsiteOptions) { + name = name ?? "default"; + super("pluto:website:aws/Website", name, options); + this.id = genResourceId(WebsiteProto.fqn, name); + + const projectRoot = new pulumi.Config("pluto").require("projectRoot"); + this.websiteDir = path.resolve(projectRoot, websiteRoot); + if (!fs.existsSync(this.websiteDir)) { + throw new Error(`The path ${this.websiteDir} does not exist.`); + } + + this.websiteBucket = new S3Bucket(this.id); + this.websiteBucket.setPublic(); + this.websiteEndpoint = this.websiteBucket.configWebsite("index.html"); + + this.outputs = this.websiteEndpoint; + } + + public addEnv(key: string, value: pulumi.Output | string) { + this.envs[key] = value; + } + + public url(): string { + return this.websiteEndpoint as any; + } + + public grantPermission(op: string, resource?: IResourceInfra) { + op; + resource; + throw new Error("Method should be called."); + } + + public postProcess(): void { + function dumpPlutoJs(filepath: string, envs: { [key: string]: string }) { + const content = PLUTO_JS_TEMPALETE.replace("{placeholder}", JSON.stringify(envs, null, 2)); + fs.writeFileSync(filepath, content); + } + + function uploadFileToS3(bucket: S3Bucket, dirpath: string) { + glob.sync(`${dirpath}/**/*`).forEach((file) => { + const lambdaAssetName = file.replace(new RegExp(`${dirpath}/?`, "g"), ""); + const mimeType = mime.lookup(file) || undefined; + new aws.s3.BucketObjectv2(lambdaAssetName, { + bucket: bucket.bucket.bucket, + key: lambdaAssetName, + contentType: mimeType, + source: new pulumi.asset.FileAsset(file), + }); + }); + } + + pulumi.output(this.envs).apply((envs) => { + const filepath = path.join(this.websiteDir, "pluto.js"); + // Developers may have previously constructed a `pluto.js` file to facilitate debugging + // throughout the development process. Therefore, it's essential to back up the original + // content of `pluto.js` and ensure it's restored after deployment. + const originalPlutoJs = fs.existsSync(filepath) + ? fs.readFileSync(filepath, "utf8") + : undefined; + + try { + dumpPlutoJs(filepath, envs); + uploadFileToS3(this.websiteBucket, this.websiteDir); + // Remove the generated `pluto.js` file after deployment. + fs.removeSync(filepath); + } finally { + // Restore original pluto.js content. + if (originalPlutoJs) { + fs.writeFileSync(filepath, originalPlutoJs); + } + } + }); + } +} + +const PLUTO_JS_TEMPALETE = ` +window.plutoEnv = {placeholder} +`; diff --git a/packages/pluto-infra/src/index.ts b/packages/pluto-infra/src/index.ts index e91a93dc..01b7479e 100644 --- a/packages/pluto-infra/src/index.ts +++ b/packages/pluto-infra/src/index.ts @@ -6,3 +6,4 @@ export { Schedule } from "./schedule"; export { Tester } from "./tester"; export { SageMaker } from "./sagemaker.aws"; export { Bucket } from "./bucket"; +export { Website } from "./website"; diff --git a/packages/pluto-infra/src/website.ts b/packages/pluto-infra/src/website.ts new file mode 100644 index 00000000..a4683ccd --- /dev/null +++ b/packages/pluto-infra/src/website.ts @@ -0,0 +1,50 @@ +import { ProvisionType, PlatformType, utils, IResourceInfra } from "@plutolang/base"; +import { IWebsiteInfra, WebsiteOptions } from "@plutolang/pluto"; +import { ImplClassMap } from "./utils"; + +type IWebsiteInfraImpl = IWebsiteInfra & IResourceInfra; + +// Construct a type for a class constructor. The key point is that the parameters of the constructor +// must be consistent with the client class of this resource type. Use this type to ensure that +// all implementation classes have the correct and same constructor signature. +type WebsiteInfraImplClass = new ( + path: string, + name?: string, + options?: WebsiteOptions +) => IWebsiteInfraImpl; + +// Construct a map that contains all the implementation classes for this resource type. +// The final selection will be determined at runtime, and the class will be imported lazily. +const implClassMap = new ImplClassMap( + "@plutolang/pluto.Website", + { + [ProvisionType.Pulumi]: { + [PlatformType.AWS]: async () => (await import("./aws")).Website, + }, + } +); + +/** + * This is a factory class that provides an interface to create instances of this resource type + * based on the target platform and provisioning engine. + */ +export abstract class Website { + /** + * Asynchronously creates an instance of the Website infrastructure class. The parameters of this function + * must be consistent with the constructor of both the client class and infrastructure class associated + * with this resource type. + */ + public static async createInstance( + path: string, + name?: string, + options?: WebsiteOptions + ): Promise { + return implClassMap.createInstanceOrThrow( + utils.currentPlatformType(), + utils.currentEngineType(), + path, + name, + options + ); + } +} diff --git a/packages/pluto-py/CHANGELOG.md b/packages/pluto-py/CHANGELOG.md new file mode 100644 index 00000000..047b9a0b --- /dev/null +++ b/packages/pluto-py/CHANGELOG.md @@ -0,0 +1,5 @@ +# pluto-client + +## 0.0.10 + +feat(sdk): add Website resource type diff --git a/packages/pluto-py/pluto_client/__init__.py b/packages/pluto-py/pluto_client/__init__.py index a57f19bd..8d635811 100644 --- a/packages/pluto-py/pluto_client/__init__.py +++ b/packages/pluto-py/pluto_client/__init__.py @@ -4,6 +4,7 @@ from .router import Router, RouterOptions, HttpRequest, HttpResponse from .bucket import Bucket, BucketOptions from .schedule import Schedule, ScheduleOptions +from .website import Website, WebsiteOptions __all__ = [ "Queue", @@ -21,4 +22,6 @@ "BucketOptions", "Schedule", "ScheduleOptions", + "Website", + "WebsiteOptions", ] diff --git a/packages/pluto-py/pluto_client/clients/shared/__init__.py b/packages/pluto-py/pluto_client/clients/shared/__init__.py index 180cfca2..efced5a2 100644 --- a/packages/pluto-py/pluto_client/clients/shared/__init__.py +++ b/packages/pluto-py/pluto_client/clients/shared/__init__.py @@ -1 +1,2 @@ from .router import RouterClient +from .website import WebsiteClient diff --git a/packages/pluto-py/pluto_client/clients/shared/website.py b/packages/pluto-py/pluto_client/clients/shared/website.py new file mode 100644 index 00000000..9c209e71 --- /dev/null +++ b/packages/pluto-py/pluto_client/clients/shared/website.py @@ -0,0 +1,17 @@ +from typing import Optional +from pluto_base.utils import gen_resource_id, get_env_val_for_property +from ...website import IWebsiteClient, Website, WebsiteOptions + + +class WebsiteClient(IWebsiteClient): + def __init__( + self, + path: str, + name: Optional[str] = None, + opts: Optional[WebsiteOptions] = None, + ): + name = name or "default" + self.__id = gen_resource_id(Website.fqn, name) + + def url(self) -> str: + return get_env_val_for_property(self.__id, "url") diff --git a/packages/pluto-py/pluto_client/website.py b/packages/pluto-py/pluto_client/website.py new file mode 100644 index 00000000..d4532b99 --- /dev/null +++ b/packages/pluto-py/pluto_client/website.py @@ -0,0 +1,63 @@ +from typing import Optional +from dataclasses import dataclass +from pluto_base import utils +from pluto_base.platform import PlatformType +from pluto_base.resource import ( + IResource, + IResourceCapturedProps, + IResourceClientApi, + IResourceInfraApi, +) + + +@dataclass +class WebsiteOptions: + pass + + +class IWebsiteClientApi(IResourceClientApi): + pass + + +class IWebsiteInfraApi(IResourceInfraApi): + def addEnv(self, key: str, value: str) -> None: + raise NotImplementedError + + +class IWebsiteCapturedProps(IResourceCapturedProps): + def url(self) -> str: + raise NotImplementedError + + +class IWebsiteClient(IWebsiteClientApi, IWebsiteCapturedProps): + pass + + +class IWebsiteInfra(IWebsiteInfraApi, IWebsiteCapturedProps): + pass + + +class Website(IResource, IWebsiteClient, IWebsiteInfra): + fqn = "@plutolang/pluto.Website" + + def __init__( + self, + path: str, + name: Optional[str] = None, + opts: Optional[WebsiteOptions] = None, + ): + raise NotImplementedError( + "Cannot instantiate this class, instead of its subclass depending on the target runtime." + ) + + @staticmethod + def build_client( + name: str, opts: Optional[WebsiteOptions] = None + ) -> IWebsiteClient: + platform_type = utils.current_platform_type() + if platform_type in [PlatformType.AWS]: + from .clients import shared + + return shared.RouterClient(name, opts) + else: + raise ValueError(f"not support this runtime '{platform_type}'") diff --git a/packages/pluto-py/pyproject.toml b/packages/pluto-py/pyproject.toml index 479ae98c..bed99834 100644 --- a/packages/pluto-py/pyproject.toml +++ b/packages/pluto-py/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "pluto-client" -version = "0.0.9" +version = "0.0.10" description = "The Client Library for Pluto Programming Language." authors = ["Jade Zheng "] license = "Apache-2.0" diff --git a/packages/pluto/src/clients/shared/index.ts b/packages/pluto/src/clients/shared/index.ts index a23c05d2..9fd246d9 100644 --- a/packages/pluto/src/clients/shared/index.ts +++ b/packages/pluto/src/clients/shared/index.ts @@ -1 +1,2 @@ +export { WebsiteClient } from "./website"; export { RouterClient } from "./router"; diff --git a/packages/pluto/src/clients/shared/website.ts b/packages/pluto/src/clients/shared/website.ts new file mode 100644 index 00000000..941dfd52 --- /dev/null +++ b/packages/pluto/src/clients/shared/website.ts @@ -0,0 +1,17 @@ +import { utils } from "@plutolang/base"; +import { IWebsiteClient, Website, WebsiteOptions } from "../../website"; + +export class WebsiteClient implements IWebsiteClient { + private readonly id: string; + + constructor(path: string, name?: string, opts?: WebsiteOptions) { + name = name ?? "default"; + this.id = utils.genResourceId(Website.fqn, name); + path; + opts; + } + + public url(): string { + return utils.getEnvValForProperty(this.id, "url"); + } +} diff --git a/packages/pluto/src/index.ts b/packages/pluto/src/index.ts index 70c721bd..c9a9f951 100644 --- a/packages/pluto/src/index.ts +++ b/packages/pluto/src/index.ts @@ -6,3 +6,4 @@ export * from "./tester"; export * from "./function"; export * from "./sagemaker.aws"; export * from "./bucket"; +export * from "./website"; diff --git a/packages/pluto/src/website.ts b/packages/pluto/src/website.ts new file mode 100644 index 00000000..d34b6ab9 --- /dev/null +++ b/packages/pluto/src/website.ts @@ -0,0 +1,68 @@ +import { + IResource, + IResourceClientApi, + IResourceInfraApi, + PlatformType, + utils, +} from "@plutolang/base"; +import { shared } from "./clients"; + +/** + * The options for instantiating an infrastructure implementation class or a client implementation + * class. + */ +export interface WebsiteOptions {} + +/** + * Don't export these methods to developers. + * These methods are only used internally by the cli. + */ +export interface IWebsiteClientApi extends IResourceClientApi {} + +export interface IWebsiteInfraApi extends IResourceInfraApi { + addEnv(key: string, value: string): void; +} + +export interface IWebsiteCapturedProps extends IResourceInfraApi { + url(): string; +} + +/** + * Construct a type that includes all the necessary methods required to be implemented within the + * client implementation class of a resource type. + */ +export type IWebsiteClient = IWebsiteClientApi & IWebsiteCapturedProps; + +/** + * Construct a type that includes all the necessary methods required to be implemented within the + * infrastructure implementation class of a resource type. + */ +export type IWebsiteInfra = IWebsiteInfraApi & IWebsiteCapturedProps; + +export class Website implements IResource { + constructor(path: string, name?: string, opts?: WebsiteOptions) { + path; + name; + opts; + throw new Error( + "Cannot instantiate this class, instead of its subclass depending on the target runtime." + ); + } + + public static buildClient(path: string, name?: string, opts?: WebsiteOptions): IWebsiteClient { + const platformType = utils.currentPlatformType(); + switch (platformType) { + case PlatformType.AWS: + return new shared.WebsiteClient(path, name, opts); + default: + throw new Error(`not support this runtime '${platformType}'`); + } + path; + name; + opts; + } + + public static fqn = "@plutolang/pluto.Website"; +} + +export interface Website extends IResource, IWebsiteClient, IWebsiteInfra {} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 766f8f79..ded5d39e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -34,7 +34,7 @@ importers: version: 3.0.3 turbo: specifier: latest - version: 1.13.2 + version: 1.13.3 apps/cli: dependencies: @@ -689,6 +689,12 @@ importers: fs-extra: specifier: ^11.1.1 version: 11.1.1 + glob: + specifier: ^10.3.10 + version: 10.3.10 + mime-types: + specifier: ^2.1.35 + version: 2.1.35 devDependencies: '@aws-sdk/core': specifier: ^3.431.0 @@ -702,6 +708,9 @@ importers: '@types/fs-extra': specifier: ^11.0.4 version: 11.0.4 + '@types/mime-types': + specifier: ^2.1.4 + version: 2.1.4 '@types/node': specifier: ^20.8.4 version: 20.10.4 @@ -714,9 +723,6 @@ importers: express: specifier: ^4.18.2 version: 4.18.2 - glob: - specifier: ^10.3.10 - version: 10.3.10 typescript: specifier: ^5.2.2 version: 5.2.2 @@ -4581,6 +4587,7 @@ packages: strip-ansi-cjs: /strip-ansi@6.0.1 wrap-ansi: 8.1.0 wrap-ansi-cjs: /wrap-ansi@7.0.0 + dev: false /@istanbuljs/load-nyc-config@1.1.0: resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} @@ -5345,6 +5352,7 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} requiresBuild: true + dev: false optional: true /@protobufjs/aspromise@1.1.2: @@ -7013,6 +7021,10 @@ packages: resolution: {integrity: sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==} dev: true + /@types/mime-types@2.1.4: + resolution: {integrity: sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==} + dev: true + /@types/mime@1.3.5: resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} dev: true @@ -7914,6 +7926,7 @@ packages: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} dependencies: balanced-match: 1.0.2 + dev: false /braces@3.0.2: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} @@ -9227,6 +9240,7 @@ packages: dependencies: cross-spawn: 7.0.3 signal-exit: 4.1.0 + dev: false /form-data-encoder@1.7.2: resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==} @@ -9413,6 +9427,7 @@ packages: minimatch: 9.0.3 minipass: 7.0.4 path-scurry: 1.10.1 + dev: false /glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} @@ -10021,6 +10036,7 @@ packages: '@isaacs/cliui': 8.0.2 optionalDependencies: '@pkgjs/parseargs': 0.11.0 + dev: false /jest-changed-files@29.7.0: resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} @@ -10989,6 +11005,7 @@ packages: /lru-cache@10.2.0: resolution: {integrity: sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==} engines: {node: 14 || >=16.14} + dev: false /lru-cache@4.1.5: resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} @@ -11155,6 +11172,7 @@ packages: engines: {node: '>=16 || 14 >=14.17'} dependencies: brace-expansion: 2.0.1 + dev: false /minimist-options@4.1.0: resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} @@ -11171,6 +11189,7 @@ packages: /minipass@7.0.4: resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==} engines: {node: '>=16 || 14 >=14.17'} + dev: false /mixme@0.5.10: resolution: {integrity: sha512-5H76ANWinB1H3twpJ6JY8uvAtpmFvHNArpilJAjXRKXSDDLPIMoZArw5SH0q9z+lLs8IrMw7Q2VWpWimFKFT1Q==} @@ -11619,6 +11638,7 @@ packages: dependencies: lru-cache: 10.2.0 minipass: 7.0.4 + dev: false /path-to-regexp@0.1.7: resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} @@ -12777,64 +12797,64 @@ packages: yargs: 17.7.2 dev: true - /turbo-darwin-64@1.13.2: - resolution: {integrity: sha512-CCSuD8CfmtncpohCuIgq7eAzUas0IwSbHfI8/Q3vKObTdXyN8vAo01gwqXjDGpzG9bTEVedD0GmLbD23dR0MLA==} + /turbo-darwin-64@1.13.3: + resolution: {integrity: sha512-glup8Qx1qEFB5jerAnXbS8WrL92OKyMmg5Hnd4PleLljAeYmx+cmmnsmLT7tpaVZIN58EAAwu8wHC6kIIqhbWA==} cpu: [x64] os: [darwin] requiresBuild: true dev: true optional: true - /turbo-darwin-arm64@1.13.2: - resolution: {integrity: sha512-0HySm06/D2N91rJJ89FbiI/AodmY8B3WDSFTVEpu2+8spUw7hOJ8okWOT0e5iGlyayUP9gr31eOeL3VFZkpfCw==} + /turbo-darwin-arm64@1.13.3: + resolution: {integrity: sha512-/np2xD+f/+9qY8BVtuOQXRq5f9LehCFxamiQnwdqWm5iZmdjygC5T3uVSYuagVFsZKMvX3ycySwh8dylGTl6lg==} cpu: [arm64] os: [darwin] requiresBuild: true dev: true optional: true - /turbo-linux-64@1.13.2: - resolution: {integrity: sha512-7HnibgbqZrjn4lcfIouzlPu8ZHSBtURG4c7Bedu7WJUDeZo+RE1crlrQm8wuwO54S0siYqUqo7GNHxu4IXbioQ==} + /turbo-linux-64@1.13.3: + resolution: {integrity: sha512-G+HGrau54iAnbXLfl+N/PynqpDwi/uDzb6iM9hXEDG+yJnSJxaHMShhOkXYJPk9offm9prH33Khx2scXrYVW1g==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /turbo-linux-arm64@1.13.2: - resolution: {integrity: sha512-sUq4dbpk6SNKg/Hkwn256Vj2AEYSQdG96repio894h5/LEfauIK2QYiC/xxAeW3WBMc6BngmvNyURIg7ltrePg==} + /turbo-linux-arm64@1.13.3: + resolution: {integrity: sha512-qWwEl5VR02NqRyl68/3pwp3c/olZuSp+vwlwrunuoNTm6JXGLG5pTeme4zoHNnk0qn4cCX7DFrOboArlYxv0wQ==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /turbo-windows-64@1.13.2: - resolution: {integrity: sha512-DqzhcrciWq3dpzllJR2VVIyOhSlXYCo4mNEWl98DJ3FZ08PEzcI3ceudlH6F0t/nIcfSItK1bDP39cs7YoZHEA==} + /turbo-windows-64@1.13.3: + resolution: {integrity: sha512-Nudr4bRChfJzBPzEmpVV85VwUYRCGKecwkBFpbp2a4NtrJ3+UP1VZES653ckqCu2FRyRuS0n03v9euMbAvzH+Q==} cpu: [x64] os: [win32] requiresBuild: true dev: true optional: true - /turbo-windows-arm64@1.13.2: - resolution: {integrity: sha512-WnPMrwfCXxK69CdDfS1/j2DlzcKxSmycgDAqV0XCYpK/812KB0KlvsVAt5PjEbZGXkY88pCJ1BLZHAjF5FcbqA==} + /turbo-windows-arm64@1.13.3: + resolution: {integrity: sha512-ouJCgsVLd3icjRLmRvHQDDZnmGzT64GBupM1Y+TjtYn2LVaEBoV6hicFy8x5DUpnqdLy+YpCzRMkWlwhmkX7sQ==} cpu: [arm64] os: [win32] requiresBuild: true dev: true optional: true - /turbo@1.13.2: - resolution: {integrity: sha512-rX/d9f4MgRT3yK6cERPAkfavIxbpBZowDQpgvkYwGMGDQ0Nvw1nc0NVjruE76GrzXQqoxR1UpnmEP54vBARFHQ==} + /turbo@1.13.3: + resolution: {integrity: sha512-n17HJv4F4CpsYTvKzUJhLbyewbXjq1oLCi90i5tW1TiWDz16ML1eDG7wi5dHaKxzh5efIM56SITnuVbMq5dk4g==} hasBin: true optionalDependencies: - turbo-darwin-64: 1.13.2 - turbo-darwin-arm64: 1.13.2 - turbo-linux-64: 1.13.2 - turbo-linux-arm64: 1.13.2 - turbo-windows-64: 1.13.2 - turbo-windows-arm64: 1.13.2 + turbo-darwin-64: 1.13.3 + turbo-darwin-arm64: 1.13.3 + turbo-linux-64: 1.13.3 + turbo-linux-arm64: 1.13.3 + turbo-windows-64: 1.13.3 + turbo-windows-arm64: 1.13.3 dev: true /type-check@0.4.0: