From 04cfc385a5bd89fc9ec46f7a8ea20083fd04ddd8 Mon Sep 17 00:00:00 2001 From: Chris McGrath Date: Thu, 28 Nov 2024 10:10:37 +0000 Subject: [PATCH 1/9] First pass at packageVersion bundling strategy needs tests --- src/bundling.ts | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ src/types.ts | 13 +++++++++++++ 2 files changed, 61 insertions(+) diff --git a/src/bundling.ts b/src/bundling.ts index 006c1f3..7eaf970 100644 --- a/src/bundling.ts +++ b/src/bundling.ts @@ -1,3 +1,5 @@ +import { execSync } from 'node:child_process'; +import { createHash } from 'node:crypto'; import * as path from 'node:path'; import { AssetHashType, @@ -13,6 +15,7 @@ import { type Runtime, } from 'aws-cdk-lib/aws-lambda'; import type { BundlingOptions, ICommandHooks } from './types'; +import { BundlingStrategy } from './types'; export const HASHABLE_DEPENDENCIES_EXCLUDE = ['*.pyc']; @@ -69,6 +72,23 @@ export interface BundlingProps extends BundlingOptions { */ export class Bundling { public static bundle(options: BundlingProps): AssetCode { + switch (options.bundlingStrategy) { + case BundlingStrategy.PACKAGE_VERSION: + return Bundling.packageVersionStrategy(options); + default: + return Bundling.sourceStrategy(options); + } + } + + /** + * Uses the AssetHashType.SOURCE strategy to calculate the asset hash. + * + * If there are multiple functions being created from a workspace they will all be rebuilt if the source changes. + * + * @param options + * @private + */ + private static sourceStrategy(options: BundlingProps): AssetCode { return Code.fromAsset(options.rootDir, { assetHashType: AssetHashType.SOURCE, exclude: HASHABLE_DEPENDENCIES_EXCLUDE, @@ -76,6 +96,34 @@ export class Bundling { }); } + /** + * Uses the AssetHashType.CUSTOM strategy and uv tree output to calculate the asset hash. + * + * This strategy uses the output of `uv tree` to calculate the asset hash. If there are multiple packages in a workspace this method will only + * rebuild the asset for a package if the package or a dependency version changes. This will not pick up local changes to the source unless they + * also change the package version in pyproject.toml. + * + * @param options + * @private + */ + private static packageVersionStrategy(options: BundlingProps): AssetCode { + const workspacePackage = options.workspacePackage; + const uvPackageArgs = workspacePackage + ? `--package ${workspacePackage}` + : ''; + const command = `cd ${options.rootDir} && uv tree ${uvPackageArgs}`; + // TODO: find something that works on Windows, maybe automatically changing directory and running the command + const tree = execSync(command).toString().trim(); + const assetHash = createHash('sha256').update(tree).digest('hex'); + + return Code.fromAsset(options.rootDir, { + assetHashType: AssetHashType.CUSTOM, + assetHash, + exclude: HASHABLE_DEPENDENCIES_EXCLUDE, + bundling: new Bundling(options), + }); + } + public readonly image: DockerImage; public readonly entrypoint?: string[] | undefined; public readonly command: string[] | undefined; diff --git a/src/types.ts b/src/types.ts index c5529b8..f6ad42a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -93,6 +93,19 @@ export interface BundlingOptions extends DockerRunOptions { * @default - BundlingFileAccess.BIND_MOUNT */ readonly bundlingFileAccess?: BundlingFileAccess; + + /** + * Strategy for bundling + * + * @default - `BundlingStrategy.SOURCE` + * + */ + readonly bundlingStrategy?: BundlingStrategy; +} + +export enum BundlingStrategy { + SOURCE = 'source', + PACKAGE_VERSION = 'package-version', } /** From 0f0a588cda3b5328e3e7a786e043bacf10cf428a Mon Sep 17 00:00:00 2001 From: Chris McGrath Date: Thu, 28 Nov 2024 12:23:06 +0000 Subject: [PATCH 2/9] Initial tests for PACKAGE_VERSION strategy A copy of the SOURCE tests to start. --- src/bundling.ts | 5 +- test/function.test.ts | 335 +++++++++++++++++++++++++++++++----------- 2 files changed, 252 insertions(+), 88 deletions(-) diff --git a/src/bundling.ts b/src/bundling.ts index 7eaf970..a24ba18 100644 --- a/src/bundling.ts +++ b/src/bundling.ts @@ -111,7 +111,10 @@ export class Bundling { const uvPackageArgs = workspacePackage ? `--package ${workspacePackage}` : ''; - const command = `cd ${options.rootDir} && uv tree ${uvPackageArgs}`; + const uvTreeOptions = [ + '--frozen', // don't try and update the lock file + ]; + const command = `cd ${options.rootDir} && uv tree ${uvTreeOptions.join(' ')} ${uvPackageArgs}`; // TODO: find something that works on Windows, maybe automatically changing directory and running the command const tree = execSync(command).toString().trim(); const assetHash = createHash('sha256').update(tree).digest('hex'); diff --git a/test/function.test.ts b/test/function.test.ts index 1f4f22a..0bbcc3f 100644 --- a/test/function.test.ts +++ b/test/function.test.ts @@ -7,7 +7,8 @@ import { App, Stack } from 'aws-cdk-lib'; import { Match, Template } from 'aws-cdk-lib/assertions'; import { Architecture, Runtime } from 'aws-cdk-lib/aws-lambda'; import * as cxapi from 'aws-cdk-lib/cx-api'; -import { PythonFunction } from '../src'; +import { BundlingStrategy, PythonFunction } from '../src'; + const execAsync = promisify(exec); const resourcesPath = path.resolve(__dirname, 'resources'); @@ -65,124 +66,284 @@ afterEach(async () => { process.env = OLD_ENV; }); -test('Create a function from basic_app', async () => { - const { app, stack } = await createStack(); +describe('When bundlingStrategy is set to BundlingStrategy.SOURCE', () => { + test('Create a function from basic_app', async () => { + const { app, stack } = await createStack(); + + new PythonFunction(stack, 'basic_app', { + rootDir: path.join(resourcesPath, 'basic_app'), + index: 'handler.py', + handler: 'lambda_handler', + runtime: Runtime.PYTHON_3_12, + architecture: await getDockerHostArch(), + bundling: { + bundlingStrategy: BundlingStrategy.SOURCE, + }, + }); + + const template = Template.fromStack(stack); - new PythonFunction(stack, 'basic_app', { - rootDir: path.join(resourcesPath, 'basic_app'), - index: 'handler.py', - handler: 'lambda_handler', - runtime: Runtime.PYTHON_3_12, - architecture: await getDockerHostArch(), + template.hasResourceProperties('AWS::Lambda::Function', { + Handler: 'handler.lambda_handler', + Runtime: 'python3.12', + Code: { + S3Bucket: Match.anyValue(), + S3Key: Match.anyValue(), + }, + }); + const functions = Object.values( + template.findResources('AWS::Lambda::Function'), + ); + expect(functions).toHaveLength(1); + const contents = await getFunctionAssetContents(functions[0], app); + expect(contents).toContain('handler.py'); }); - const template = Template.fromStack(stack); + test('Create a function from basic_app with no .py index extension', async () => { + const { stack } = await createStack(); + + new PythonFunction(stack, 'basic_app', { + rootDir: path.join(resourcesPath, 'basic_app'), + index: 'handler', + handler: 'lambda_handler', + runtime: Runtime.PYTHON_3_12, + architecture: await getDockerHostArch(), + bundling: { + bundlingStrategy: BundlingStrategy.SOURCE, + }, + }); + + const template = Template.fromStack(stack); - template.hasResourceProperties('AWS::Lambda::Function', { - Handler: 'handler.lambda_handler', - Runtime: 'python3.12', - Code: { - S3Bucket: Match.anyValue(), - S3Key: Match.anyValue(), - }, + template.hasResourceProperties('AWS::Lambda::Function', { + Handler: 'handler.lambda_handler', + Runtime: 'python3.12', + Code: { + S3Bucket: Match.anyValue(), + S3Key: Match.anyValue(), + }, + }); }); - const functions = Object.values( - template.findResources('AWS::Lambda::Function'), - ); - expect(functions).toHaveLength(1); - const contents = await getFunctionAssetContents(functions[0], app); - expect(contents).toContain('handler.py'); -}); -test('Create a function from basic_app with no .py index extension', async () => { - const { stack } = await createStack(); + test('Create a function from basic_app when skip is true', async () => { + const { stack } = await createStack(); - new PythonFunction(stack, 'basic_app', { - rootDir: path.join(resourcesPath, 'basic_app'), - index: 'handler', - handler: 'lambda_handler', - runtime: Runtime.PYTHON_3_12, - architecture: await getDockerHostArch(), + const bundlingSpy = jest + .spyOn(stack, 'bundlingRequired', 'get') + .mockReturnValue(false); + const architecture = await getDockerHostArch(); + + // To see this fail, comment out the `if (skip) { return; } code in the PythonFunction constructor + expect(() => { + new PythonFunction(stack, 'basic_app', { + rootDir: path.join(resourcesPath, 'basic_app'), + index: 'handler', + handler: 'lambda_handler', + runtime: Runtime.PYTHON_3_12, + architecture, + bundling: { + bundlingStrategy: BundlingStrategy.SOURCE, + }, + }); + }).not.toThrow(); + + bundlingSpy.mockRestore(); }); - const template = Template.fromStack(stack); + test('Create a function with workspaces_app', async () => { + const { app, stack } = await createStack('wstest'); + + new PythonFunction(stack, 'workspaces_app', { + rootDir: path.join(resourcesPath, 'workspaces_app'), + workspacePackage: 'app', + index: 'app_handler.py', + handler: 'handle_event', + runtime: Runtime.PYTHON_3_10, + architecture: await getDockerHostArch(), + bundling: { + bundlingStrategy: BundlingStrategy.SOURCE, + }, + }); + + const template = Template.fromStack(stack); + + template.hasResourceProperties('AWS::Lambda::Function', { + Handler: 'app_handler.handle_event', + Runtime: 'python3.10', + Code: { + S3Bucket: Match.anyValue(), + S3Key: Match.anyValue(), + }, + }); - template.hasResourceProperties('AWS::Lambda::Function', { - Handler: 'handler.lambda_handler', - Runtime: 'python3.12', - Code: { - S3Bucket: Match.anyValue(), - S3Key: Match.anyValue(), - }, + const functions = Object.values( + template.findResources('AWS::Lambda::Function'), + ); + expect(functions).toHaveLength(1); + const contents = await getFunctionAssetContents(functions[0], app); + for (const entry of [ + 'app', + 'common', + 'pydantic', + 'httpx', + '_common.pth', + 'app_handler.py', + ]) { + expect(contents).toContain(entry); + } }); }); -test('Create a function from basic_app when skip is true', async () => { - const { stack } = await createStack(); +describe('When bundlingStrategy is set to BundlingStrategy.PACKAGE_VERSION', () => { + test('Create a function from basic_app', async () => { + const { app, stack } = await createStack(); + + new PythonFunction(stack, 'basic_app', { + rootDir: path.join(resourcesPath, 'basic_app'), + index: 'handler.py', + handler: 'lambda_handler', + runtime: Runtime.PYTHON_3_12, + architecture: await getDockerHostArch(), + bundling: { + bundlingStrategy: BundlingStrategy.PACKAGE_VERSION, + }, + }); + + const template = Template.fromStack(stack); - const bundlingSpy = jest - .spyOn(stack, 'bundlingRequired', 'get') - .mockReturnValue(false); - const architecture = await getDockerHostArch(); + template.hasResourceProperties('AWS::Lambda::Function', { + Handler: 'handler.lambda_handler', + Runtime: 'python3.12', + Code: { + S3Bucket: Match.anyValue(), + S3Key: Match.anyValue(), + }, + }); + const functions = Object.values( + template.findResources('AWS::Lambda::Function'), + ); + expect(functions).toHaveLength(1); + const contents = await getFunctionAssetContents(functions[0], app); + expect(contents).toContain('handler.py'); + }); + + test('Create a function from basic_app with no .py index extension', async () => { + const { stack } = await createStack(); - // To see this fail, comment out the `if (skip) { return; } code in the PythonFunction constructor - expect(() => { new PythonFunction(stack, 'basic_app', { rootDir: path.join(resourcesPath, 'basic_app'), index: 'handler', handler: 'lambda_handler', runtime: Runtime.PYTHON_3_12, - architecture, + architecture: await getDockerHostArch(), + bundling: { + bundlingStrategy: BundlingStrategy.PACKAGE_VERSION, + }, }); - }).not.toThrow(); - - bundlingSpy.mockRestore(); -}); -test('Create a function with workspaces_app', async () => { - const { app, stack } = await createStack('wstest'); + const template = Template.fromStack(stack); - new PythonFunction(stack, 'workspaces_app', { - rootDir: path.join(resourcesPath, 'workspaces_app'), - workspacePackage: 'app', - index: 'app_handler.py', - handler: 'handle_event', - runtime: Runtime.PYTHON_3_10, - architecture: await getDockerHostArch(), + template.hasResourceProperties('AWS::Lambda::Function', { + Handler: 'handler.lambda_handler', + Runtime: 'python3.12', + Code: { + S3Bucket: Match.anyValue(), + S3Key: Match.anyValue(), + }, + }); }); - const template = Template.fromStack(stack); + test('Create a function from basic_app when skip is true', async () => { + const { stack } = await createStack(); + + const bundlingSpy = jest + .spyOn(stack, 'bundlingRequired', 'get') + .mockReturnValue(false); + const architecture = await getDockerHostArch(); - template.hasResourceProperties('AWS::Lambda::Function', { - Handler: 'app_handler.handle_event', - Runtime: 'python3.10', - Code: { - S3Bucket: Match.anyValue(), - S3Key: Match.anyValue(), - }, + // To see this fail, comment out the `if (skip) { return; } code in the PythonFunction constructor + expect(() => { + new PythonFunction(stack, 'basic_app', { + rootDir: path.join(resourcesPath, 'basic_app'), + index: 'handler', + handler: 'lambda_handler', + runtime: Runtime.PYTHON_3_12, + architecture, + bundling: { + bundlingStrategy: BundlingStrategy.PACKAGE_VERSION, + }, + }); + }).not.toThrow(); + + bundlingSpy.mockRestore(); }); - const functions = Object.values( - template.findResources('AWS::Lambda::Function'), - ); - expect(functions).toHaveLength(1); - const contents = await getFunctionAssetContents(functions[0], app); - for (const entry of [ - 'app', - 'common', - 'pydantic', - 'httpx', - '_common.pth', - 'app_handler.py', - ]) { - expect(contents).toContain(entry); - } + test('Create a function with workspaces_app', async () => { + const { app, stack } = await createStack('wstest'); + + new PythonFunction(stack, 'workspaces_app', { + rootDir: path.join(resourcesPath, 'workspaces_app'), + workspacePackage: 'app', + index: 'app_handler.py', + handler: 'handle_event', + runtime: Runtime.PYTHON_3_10, + architecture: await getDockerHostArch(), + bundling: { + bundlingStrategy: BundlingStrategy.PACKAGE_VERSION, + }, + }); + + const template = Template.fromStack(stack); + + template.hasResourceProperties('AWS::Lambda::Function', { + Handler: 'app_handler.handle_event', + Runtime: 'python3.10', + Code: { + S3Bucket: Match.anyValue(), + S3Key: Match.anyValue(), + }, + }); + + const functions = Object.values( + template.findResources('AWS::Lambda::Function'), + ); + expect(functions).toHaveLength(1); + const contents = await getFunctionAssetContents(functions[0], app); + console.log('contents', contents); + for (const entry of [ + 'app', + 'common', + 'pydantic', + 'httpx', + '_common.pth', + 'app_handler.py', + ]) { + expect(contents).toContain(entry); + } + }); }); // biome-ignore lint/suspicious/noExplicitAny: async function getFunctionAssetContents(functionResource: any, app: App) { const [assetHash] = functionResource.Properties.Code.S3Key.split('.'); const assetPath = path.join(app.outdir, `asset.${assetHash}`); - const contents = await fs.readdir(assetPath); - return contents; + return await fs.readdir(assetPath); } + +// async function setFunctionAssetContents( +// functionResource: any, +// app: App, +// contents: string[], +// ) { +// const [assetHash] = functionResource.Properties.Code.S3Key.split('.'); +// const assetPath = path.join(app.outdir, `asset.${assetHash}`); +// console.log({ assetPath }); +// // remove all existing contents and add the new ones as empty files +// const existingContents = await fs.readdir(assetPath); +// for (const entry of existingContents) { +// await fs.rm(entry, { recursive: true }); +// } +// for (const entry of contents) { +// await fs.writeFile(path.join(assetPath, entry), ''); +// } +// } From 21072a419e239dc59ff00aa346c1c2bf7bdba3d3 Mon Sep 17 00:00:00 2001 From: Chris McGrath Date: Fri, 29 Nov 2024 11:52:49 +0000 Subject: [PATCH 3/9] Update workspaces_app project to have two lambda functions Make the functions both depend on common and have different external dependencies. --- .../workspaces_app/app/app_handler.py | 9 +- .../workspaces_app/app/pyproject.toml | 2 +- .../workspaces_app/backend/.python-version | 1 + .../workspaces_app/backend/README.md | 0 .../workspaces_app/backend/backend_handler.py | 5 + .../workspaces_app/backend/pyproject.toml | 13 ++ .../workspaces_app/common/pyproject.toml | 1 + .../workspaces_app/common/src/common/car.py | 6 + test/resources/workspaces_app/pyproject.toml | 5 +- test/resources/workspaces_app/uv.lock | 172 +++++++++++++++++- 10 files changed, 201 insertions(+), 13 deletions(-) create mode 100644 test/resources/workspaces_app/backend/.python-version create mode 100644 test/resources/workspaces_app/backend/README.md create mode 100644 test/resources/workspaces_app/backend/backend_handler.py create mode 100644 test/resources/workspaces_app/backend/pyproject.toml create mode 100644 test/resources/workspaces_app/common/src/common/car.py diff --git a/test/resources/workspaces_app/app/app_handler.py b/test/resources/workspaces_app/app/app_handler.py index 9f5af87..d9bdaae 100644 --- a/test/resources/workspaces_app/app/app_handler.py +++ b/test/resources/workspaces_app/app/app_handler.py @@ -1,10 +1,5 @@ -from pydantic import BaseModel - -class Car(BaseModel): - brand: str - model: str - year: int +from common.car import Car def handle_event(event, context): car = Car(brand="Toyota", model="Corolla", year=2020) - return car.model_dump() \ No newline at end of file + return car.model_dump() diff --git a/test/resources/workspaces_app/app/pyproject.toml b/test/resources/workspaces_app/app/pyproject.toml index 75a3019..47d74cb 100644 --- a/test/resources/workspaces_app/app/pyproject.toml +++ b/test/resources/workspaces_app/app/pyproject.toml @@ -6,7 +6,7 @@ readme = "README.md" requires-python = ">=3.10" dependencies = [ "common", - "pydantic>=2.9.2", + "flask>=3.1.0", ] [tool.uv.sources] diff --git a/test/resources/workspaces_app/backend/.python-version b/test/resources/workspaces_app/backend/.python-version new file mode 100644 index 0000000..c8cfe39 --- /dev/null +++ b/test/resources/workspaces_app/backend/.python-version @@ -0,0 +1 @@ +3.10 diff --git a/test/resources/workspaces_app/backend/README.md b/test/resources/workspaces_app/backend/README.md new file mode 100644 index 0000000..e69de29 diff --git a/test/resources/workspaces_app/backend/backend_handler.py b/test/resources/workspaces_app/backend/backend_handler.py new file mode 100644 index 0000000..6c3e595 --- /dev/null +++ b/test/resources/workspaces_app/backend/backend_handler.py @@ -0,0 +1,5 @@ +from common.car import Car + +def handle_event(event, context): + car = Car(brand="Nissan", model="Skyline", year=2022) + return car.model_dump() diff --git a/test/resources/workspaces_app/backend/pyproject.toml b/test/resources/workspaces_app/backend/pyproject.toml new file mode 100644 index 0000000..d84c3c1 --- /dev/null +++ b/test/resources/workspaces_app/backend/pyproject.toml @@ -0,0 +1,13 @@ +[project] +name = "backend" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.10" +dependencies = [ + "common", + "pathlib>=1.0.1", +] + +[tool.uv.sources] +common = { workspace = true } diff --git a/test/resources/workspaces_app/common/pyproject.toml b/test/resources/workspaces_app/common/pyproject.toml index 25a22a3..7f1d0c1 100644 --- a/test/resources/workspaces_app/common/pyproject.toml +++ b/test/resources/workspaces_app/common/pyproject.toml @@ -6,6 +6,7 @@ readme = "README.md" requires-python = ">=3.10" dependencies = [ "httpx>=0.27.2", + "pydantic>=2.9.2", ] [project.scripts] diff --git a/test/resources/workspaces_app/common/src/common/car.py b/test/resources/workspaces_app/common/src/common/car.py new file mode 100644 index 0000000..f0f37a2 --- /dev/null +++ b/test/resources/workspaces_app/common/src/common/car.py @@ -0,0 +1,6 @@ +from pydantic import BaseModel + +class Car(BaseModel): + brand: str + model: str + year: int diff --git a/test/resources/workspaces_app/pyproject.toml b/test/resources/workspaces_app/pyproject.toml index 4af2b65..7c347fa 100644 --- a/test/resources/workspaces_app/pyproject.toml +++ b/test/resources/workspaces_app/pyproject.toml @@ -7,15 +7,16 @@ requires-python = ">=3.10" dependencies = [] [tool.uv.workspace] -members = ["common", "app"] +members = ["common", "app", "backend"] [tool.uv.sources] common = { workspace = true } app = { workspace = true } +backend = { workspace = true } [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] -packages = ["app", "common"] +packages = ["app", "backend", "common"] diff --git a/test/resources/workspaces_app/uv.lock b/test/resources/workspaces_app/uv.lock index deb98d5..c34f8ff 100644 --- a/test/resources/workspaces_app/uv.lock +++ b/test/resources/workspaces_app/uv.lock @@ -8,6 +8,7 @@ resolution-markers = [ [manifest] members = [ "app", + "backend", "common", "workspaces-app", ] @@ -42,13 +43,37 @@ version = "0.1.0" source = { editable = "app" } dependencies = [ { name = "common" }, - { name = "pydantic" }, + { name = "flask" }, ] [package.metadata] requires-dist = [ { name = "common", editable = "common" }, - { name = "pydantic", specifier = ">=2.9.2" }, + { name = "flask", specifier = ">=3.1.0" }, +] + +[[package]] +name = "backend" +version = "0.1.0" +source = { editable = "backend" } +dependencies = [ + { name = "common" }, + { name = "pathlib" }, +] + +[package.metadata] +requires-dist = [ + { name = "common", editable = "common" }, + { name = "pathlib", specifier = ">=1.0.1" }, +] + +[[package]] +name = "blinker" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458 }, ] [[package]] @@ -60,16 +85,41 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321 }, ] +[[package]] +name = "click" +version = "8.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "platform_system == 'Windows'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + [[package]] name = "common" version = "0.1.0" source = { editable = "common" } dependencies = [ { name = "httpx" }, + { name = "pydantic" }, ] [package.metadata] -requires-dist = [{ name = "httpx", specifier = ">=0.27.2" }] +requires-dist = [ + { name = "httpx", specifier = ">=0.27.2" }, + { name = "pydantic", specifier = ">=2.9.2" }, +] [[package]] name = "exceptiongroup" @@ -80,6 +130,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 }, ] +[[package]] +name = "flask" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "blinker" }, + { name = "click" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/89/50/dff6380f1c7f84135484e176e0cac8690af72fa90e932ad2a0a60e28c69b/flask-3.1.0.tar.gz", hash = "sha256:5f873c5184c897c8d9d1b05df1e3d01b14910ce69607a117bd3277098a5836ac", size = 680824 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/47/93213ee66ef8fae3b93b3e29206f6b251e65c97bd91d8e1c5596ef15af0a/flask-3.1.0-py3-none-any.whl", hash = "sha256:d667207822eb83f1c4b50949b1623c8fc8d51f2341d65f72e1a1815397551136", size = 102979 }, +] + [[package]] name = "h11" version = "0.14.0" @@ -127,6 +193,94 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, ] +[[package]] +name = "itsdangerous" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234 }, +] + +[[package]] +name = "jinja2" +version = "3.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/55/39036716d19cab0747a5020fc7e907f362fbf48c984b14e62127f7e68e5d/jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369", size = 240245 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/80/3a54838c3fb461f6fec263ebf3a3a41771bd05190238de3486aae8540c36/jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d", size = 133271 }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357 }, + { url = "https://files.pythonhosted.org/packages/04/e1/6e2194baeae0bca1fae6629dc0cbbb968d4d941469cbab11a3872edff374/MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", size = 12393 }, + { url = "https://files.pythonhosted.org/packages/1d/69/35fa85a8ece0a437493dc61ce0bb6d459dcba482c34197e3efc829aa357f/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", size = 21732 }, + { url = "https://files.pythonhosted.org/packages/22/35/137da042dfb4720b638d2937c38a9c2df83fe32d20e8c8f3185dbfef05f7/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", size = 20866 }, + { url = "https://files.pythonhosted.org/packages/29/28/6d029a903727a1b62edb51863232152fd335d602def598dade38996887f0/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", size = 20964 }, + { url = "https://files.pythonhosted.org/packages/cc/cd/07438f95f83e8bc028279909d9c9bd39e24149b0d60053a97b2bc4f8aa51/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", size = 21977 }, + { url = "https://files.pythonhosted.org/packages/29/01/84b57395b4cc062f9c4c55ce0df7d3108ca32397299d9df00fedd9117d3d/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", size = 21366 }, + { url = "https://files.pythonhosted.org/packages/bd/6e/61ebf08d8940553afff20d1fb1ba7294b6f8d279df9fd0c0db911b4bbcfd/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", size = 21091 }, + { url = "https://files.pythonhosted.org/packages/11/23/ffbf53694e8c94ebd1e7e491de185124277964344733c45481f32ede2499/MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50", size = 15065 }, + { url = "https://files.pythonhosted.org/packages/44/06/e7175d06dd6e9172d4a69a72592cb3f7a996a9c396eee29082826449bbc3/MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", size = 15514 }, + { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353 }, + { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392 }, + { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984 }, + { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120 }, + { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032 }, + { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057 }, + { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359 }, + { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306 }, + { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094 }, + { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521 }, + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, +] + +[[package]] +name = "pathlib" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/aa/9b065a76b9af472437a0059f77e8f962fe350438b927cb80184c32f075eb/pathlib-1.0.1.tar.gz", hash = "sha256:6940718dfc3eff4258203ad5021090933e5c04707d5ca8cc9e73c94a7894ea9f", size = 49298 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/f9/690a8600b93c332de3ab4a344a4ac34f00c8f104917061f779db6a918ed6/pathlib-1.0.1-py3-none-any.whl", hash = "sha256:f35f95ab8b0f59e6d354090350b44a80a80635d22efdedfa84c7ad1cf0a74147", size = 14363 }, +] + [[package]] name = "pydantic" version = "2.9.2" @@ -226,6 +380,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, ] +[[package]] +name = "werkzeug" +version = "3.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/69/83029f1f6300c5fb2471d621ab06f6ec6b3324685a2ce0f9777fd4a8b71e/werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746", size = 806925 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498 }, +] + [[package]] name = "workspaces-app" version = "0.1.0" From 3fbc70ceadd098810cc37002be004de4e298eafb Mon Sep 17 00:00:00 2001 From: Chris McGrath Date: Fri, 29 Nov 2024 11:53:52 +0000 Subject: [PATCH 4/9] Add fs-extra for copying the workspace during tests --- .projen/deps.json | 8 ++++++++ .projen/tasks.json | 4 ++-- .projenrc.ts | 6 +++++- package.json | 2 ++ yarn.lock | 15 +++++++++++++++ 5 files changed, 32 insertions(+), 3 deletions(-) diff --git a/.projen/deps.json b/.projen/deps.json index efa3840..2323239 100644 --- a/.projen/deps.json +++ b/.projen/deps.json @@ -4,6 +4,10 @@ "name": "@biomejs/biome", "type": "build" }, + { + "name": "@types/fs-extra", + "type": "build" + }, { "name": "@types/jest", "type": "build" @@ -17,6 +21,10 @@ "version": "^12", "type": "build" }, + { + "name": "fs-extra", + "type": "build" + }, { "name": "jest", "type": "build" diff --git a/.projen/tasks.json b/.projen/tasks.json index 33b38d7..f29eaf9 100644 --- a/.projen/tasks.json +++ b/.projen/tasks.json @@ -269,13 +269,13 @@ }, "steps": [ { - "exec": "npx npm-check-updates@16 --upgrade --target=minor --peer --dep=dev,peer,prod,optional --filter=@biomejs/biome,@types/jest,@types/node,jest,jsii-diff,jsii-pacmak,projen,ts-jest,ts-node,typescript" + "exec": "npx npm-check-updates@16 --upgrade --target=minor --peer --dep=dev,peer,prod,optional --filter=@biomejs/biome,@types/fs-extra,@types/jest,@types/node,fs-extra,jest,jsii-diff,jsii-pacmak,projen,ts-jest,ts-node,typescript" }, { "exec": "yarn install --check-files" }, { - "exec": "yarn upgrade @biomejs/biome @types/jest @types/node commit-and-tag-version jest jest-junit jsii-diff jsii-docgen jsii-pacmak jsii-rosetta jsii projen ts-jest ts-node typescript aws-cdk-lib constructs" + "exec": "yarn upgrade @biomejs/biome @types/fs-extra @types/jest @types/node commit-and-tag-version fs-extra jest jest-junit jsii-diff jsii-docgen jsii-pacmak jsii-rosetta jsii projen ts-jest ts-node typescript aws-cdk-lib constructs" }, { "exec": "npx projen" diff --git a/.projenrc.ts b/.projenrc.ts index ec85fe2..04dcf14 100644 --- a/.projenrc.ts +++ b/.projenrc.ts @@ -18,7 +18,11 @@ const project = new awscdk.AwsCdkConstructLibrary({ // cdkDependencies: [], /* CDK dependencies of this module. */ // deps: [], /* Runtime dependencies of this module. */ // description: undefined, /* The description is just a string that helps people understand the purpose of the package. */ - devDeps: ['@biomejs/biome'] /* Build dependencies for this module. */, + devDeps: [ + '@biomejs/biome', + 'fs-extra', + '@types/fs-extra', + ] /* Build dependencies for this module. */, // packageName: undefined, /* The "name" in package.json. */ jestOptions: { extraCliOptions: ['--testTimeout=300000'], diff --git a/package.json b/package.json index 06ebf37..02c6f0e 100644 --- a/package.json +++ b/package.json @@ -35,11 +35,13 @@ }, "devDependencies": { "@biomejs/biome": "^1.9.4", + "@types/fs-extra": "^11.0.4", "@types/jest": "^29.5.13", "@types/node": "^22.7.6", "aws-cdk-lib": "2.161.1", "commit-and-tag-version": "^12", "constructs": "10.3.0", + "fs-extra": "^11.2.0", "jest": "^29.7.0", "jest-junit": "^15", "jsii": "~5.5.0", diff --git a/yarn.lock b/yarn.lock index d4a05a0..56b2ef3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -783,6 +783,14 @@ dependencies: "@babel/types" "^7.20.7" +"@types/fs-extra@^11.0.4": + version "11.0.4" + resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-11.0.4.tgz#e16a863bb8843fba8c5004362b5a73e17becca45" + integrity sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ== + dependencies: + "@types/jsonfile" "*" + "@types/node" "*" + "@types/graceful-fs@^4.1.3": version "4.1.9" resolved "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz" @@ -817,6 +825,13 @@ expect "^29.0.0" pretty-format "^29.0.0" +"@types/jsonfile@*": + version "6.1.4" + resolved "https://registry.yarnpkg.com/@types/jsonfile/-/jsonfile-6.1.4.tgz#614afec1a1164e7d670b4a7ad64df3e7beb7b702" + integrity sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ== + dependencies: + "@types/node" "*" + "@types/minimist@^1.2.0": version "1.2.5" resolved "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz" From 13a5066ac1323dcfdf4271f7ad34b66673b15114 Mon Sep 17 00:00:00 2001 From: Chris McGrath Date: Fri, 29 Nov 2024 12:11:22 +0000 Subject: [PATCH 5/9] Add tests for PACKAGE_VERSION strategy with multiple functions These are not working correctly yet as `uv sync` is changing the lockfile in a way that's breaking things. Committing so can make a PR and get help debugging further. --- src/bundling.ts | 2 + test/function.test.ts | 437 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 400 insertions(+), 39 deletions(-) diff --git a/src/bundling.ts b/src/bundling.ts index a24ba18..b8c5dfe 100644 --- a/src/bundling.ts +++ b/src/bundling.ts @@ -112,6 +112,8 @@ export class Bundling { ? `--package ${workspacePackage}` : ''; const uvTreeOptions = [ + '--python-preference=only-system', // will be running the lambda python + '--no-dev', // don't need dev dependencies '--frozen', // don't try and update the lock file ]; const command = `cd ${options.rootDir} && uv tree ${uvTreeOptions.join(' ')} ${uvPackageArgs}`; diff --git a/test/function.test.ts b/test/function.test.ts index 0bbcc3f..bb1a7c4 100644 --- a/test/function.test.ts +++ b/test/function.test.ts @@ -7,6 +7,7 @@ import { App, Stack } from 'aws-cdk-lib'; import { Match, Template } from 'aws-cdk-lib/assertions'; import { Architecture, Runtime } from 'aws-cdk-lib/aws-lambda'; import * as cxapi from 'aws-cdk-lib/cx-api'; +import * as fsextra from 'fs-extra'; import { BundlingStrategy, PythonFunction } from '../src'; const execAsync = promisify(exec); @@ -55,7 +56,7 @@ beforeEach(async () => { jest.resetModules(); process.env = { ...OLD_ENV }; process.env.CDK_OUTDIR = await fs.mkdtemp( - path.join(os.tmpdir(), 'uv-python-lambda-test-'), + path.join(os.tmpdir(), 'uv-python-lambda-function'), ); }); @@ -67,6 +68,8 @@ afterEach(async () => { }); describe('When bundlingStrategy is set to BundlingStrategy.SOURCE', () => { + jest.setTimeout(20000); // we are doing integration tests with the file system so give tests more time + test('Create a function from basic_app', async () => { const { app, stack } = await createStack(); @@ -91,11 +94,9 @@ describe('When bundlingStrategy is set to BundlingStrategy.SOURCE', () => { S3Key: Match.anyValue(), }, }); - const functions = Object.values( - template.findResources('AWS::Lambda::Function'), - ); + const functions = getFunctions(template); expect(functions).toHaveLength(1); - const contents = await getFunctionAssetContents(functions[0], app); + const contents = await getAssetContent(functions[0], app); expect(contents).toContain('handler.py'); }); @@ -176,11 +177,9 @@ describe('When bundlingStrategy is set to BundlingStrategy.SOURCE', () => { }, }); - const functions = Object.values( - template.findResources('AWS::Lambda::Function'), - ); + const functions = getFunctions(template); expect(functions).toHaveLength(1); - const contents = await getFunctionAssetContents(functions[0], app); + const contents = await getAssetContent(functions[0], app); for (const entry of [ 'app', 'common', @@ -192,9 +191,80 @@ describe('When bundlingStrategy is set to BundlingStrategy.SOURCE', () => { expect(contents).toContain(entry); } }); + + test('Create multiple functions with workspaces_app', async () => { + const { app, stack } = await createStack('wstest'); + + new PythonFunction(stack, 'workspaces_app', { + rootDir: path.join(resourcesPath, 'workspaces_app'), + workspacePackage: 'app', + index: 'app_handler.py', + handler: 'handle_event', + runtime: Runtime.PYTHON_3_10, + architecture: await getDockerHostArch(), + bundling: { + bundlingStrategy: BundlingStrategy.SOURCE, + }, + }); + + new PythonFunction(stack, 'workspaces_backend', { + rootDir: path.join(resourcesPath, 'workspaces_app'), + workspacePackage: 'backend', + index: 'backend_handler.py', + handler: 'handle_event', + runtime: Runtime.PYTHON_3_10, + architecture: await getDockerHostArch(), + bundling: { + bundlingStrategy: BundlingStrategy.SOURCE, + }, + }); + + const template = Template.fromStack(stack); + + template.hasResourceProperties('AWS::Lambda::Function', { + Handler: 'app_handler.handle_event', + }); + + template.hasResourceProperties('AWS::Lambda::Function', { + Handler: 'backend_handler.handle_event', + }); + + const functions = getFunctions(template); + expect(functions).toHaveLength(2); + const appContents = await getAssetContent(functions[0], app); + for (const entry of [ + 'app', + 'backend', + 'common', + 'pydantic', + 'httpx', + '_common.pth', + 'app_handler.py', + ]) { + expect(appContents).toContain(entry); + } + expect(appContents).not.toContain('backend_handler.py'); + expect(appContents).not.toContain('pathlib'); + const backendContents = await getAssetContent(functions[1], app); + for (const entry of [ + 'app', + 'backend', + 'common', + 'pydantic', + 'httpx', + '_common.pth', + 'backend_handler.py', + ]) { + expect(backendContents).toContain(entry); + } + expect(backendContents).not.toContain('app_handler.py'); + expect(backendContents).not.toContain('flask'); + }, 30000); }); describe('When bundlingStrategy is set to BundlingStrategy.PACKAGE_VERSION', () => { + jest.setTimeout(30000); // we are doing integration tests with the file system so give tests more time + test('Create a function from basic_app', async () => { const { app, stack } = await createStack(); @@ -219,11 +289,9 @@ describe('When bundlingStrategy is set to BundlingStrategy.PACKAGE_VERSION', () S3Key: Match.anyValue(), }, }); - const functions = Object.values( - template.findResources('AWS::Lambda::Function'), - ); + const functions = getFunctions(template); expect(functions).toHaveLength(1); - const contents = await getFunctionAssetContents(functions[0], app); + const contents = await getAssetContent(functions[0], app); expect(contents).toContain('handler.py'); }); @@ -304,12 +372,9 @@ describe('When bundlingStrategy is set to BundlingStrategy.PACKAGE_VERSION', () }, }); - const functions = Object.values( - template.findResources('AWS::Lambda::Function'), - ); + const functions = getFunctions(template); expect(functions).toHaveLength(1); - const contents = await getFunctionAssetContents(functions[0], app); - console.log('contents', contents); + const contents = await getAssetContent(functions[0], app); for (const entry of [ 'app', 'common', @@ -321,29 +386,323 @@ describe('When bundlingStrategy is set to BundlingStrategy.PACKAGE_VERSION', () expect(contents).toContain(entry); } }); + + async function createMultipleFunctionStack( + workspacePath?: string, + ): Promise<{ app: App; stack: Stack }> { + const { app, stack } = await createStack('wstest'); + const rootPath = workspacePath ?? resourcesPath; + + new PythonFunction(stack, 'workspaces_app', { + rootDir: path.join(rootPath, 'workspaces_app'), + workspacePackage: 'app', + index: 'app_handler.py', + handler: 'handle_event', + runtime: Runtime.PYTHON_3_10, + architecture: await getDockerHostArch(), + bundling: { + bundlingStrategy: BundlingStrategy.PACKAGE_VERSION, + }, + }); + + new PythonFunction(stack, 'workspaces_backend', { + rootDir: path.join(rootPath, 'workspaces_app'), + workspacePackage: 'backend', + index: 'backend_handler.py', + handler: 'handle_event', + runtime: Runtime.PYTHON_3_10, + architecture: await getDockerHostArch(), + bundling: { + bundlingStrategy: BundlingStrategy.PACKAGE_VERSION, + }, + }); + + return { app, stack }; + } + + test('Create multiple functions with workspaces_app', async () => { + const { app, stack } = await createMultipleFunctionStack(); + const template = Template.fromStack(stack); + + template.hasResourceProperties('AWS::Lambda::Function', { + Handler: 'app_handler.handle_event', + }); + + template.hasResourceProperties('AWS::Lambda::Function', { + Handler: 'backend_handler.handle_event', + }); + + const functions = getFunctions(template); + expect(functions).toHaveLength(2); + const appContents = await getAssetContent(functions[0], app); + for (const entry of [ + 'app', + 'backend', + 'common', + 'pydantic', + 'httpx', + '_common.pth', + 'app_handler.py', + ]) { + expect(appContents).toContain(entry); + } + expect(appContents).not.toContain('backend_handler.py'); + expect(appContents).not.toContain('pathlib'); + const backendContents = await getAssetContent(functions[1], app); + for (const entry of [ + 'app', + 'backend', + 'common', + 'pydantic', + 'httpx', + '_common.pth', + 'backend_handler.py', + ]) { + expect(backendContents).toContain(entry); + } + expect(backendContents).not.toContain('app_handler.py'); + expect(backendContents).not.toContain('flask'); + }); + + test("Doesn't rebuild multiple functions if they haven't changed", async () => { + let { app, stack } = await createMultipleFunctionStack(); + let template = Template.fromStack(stack); + + const run1Functions = getFunctions(template); + const appAssetPath = getAssetPath(run1Functions[0], app); + const backendAssetPath = getAssetPath(run1Functions[1], app); + // change the contents of the folder so we can verify they are not rebuilt + await setAssetContents(run1Functions[0], app, ['app.text']); + await setAssetContents(run1Functions[1], app, ['backend.text']); + + // Need to create the stack again as synthesize is only called once otherwise + ({ app, stack } = await createMultipleFunctionStack()); + + template = Template.fromStack(stack); + const run2Functions = getFunctions(template); + // validate that the same folder is being used for the functions + expect(getAssetPath(run2Functions[0], app)).toEqual(appAssetPath); + expect(getAssetPath(run2Functions[1], app)).toEqual(backendAssetPath); + + // validate they contain the content we put in above, i.e. haven't been overwritten + expect(await getAssetContent(run2Functions[0], app)).toEqual(['app.text']); + expect(await getAssetContent(run2Functions[1], app)).toEqual([ + 'backend.text', + ]); + }); + + test('Rebuild single function when its version changes', async () => { + // copy the workspace so we can edit it without affecting the original + const workspacePath = await copyWorkspaceToTemp('workspaces_app'); + let { app, stack } = await createMultipleFunctionStack(workspacePath); + let template = Template.fromStack(stack); + + const run1Functions = getFunctions(template); + const appAssetPath = getAssetPath(run1Functions[0], app); + const backendAssetPath = getAssetPath(run1Functions[1], app); + // change the contents of the folder so we can verify what is rebuilt + await setAssetContents(run1Functions[0], app, ['app.text']); + await setAssetContents(run1Functions[1], app, ['backend.text']); + + // Update the version in the workspace + await bumpVersionAndUvSync(workspacePath, 'app'); + + // Need to create the stack again as synthesize is only called once otherwise + ({ app, stack } = await createMultipleFunctionStack(workspacePath)); + + template = Template.fromStack(stack); + const run2Functions = getFunctions(template); + // validate that different folder for updated function but same one for unchanged + expect(getAssetPath(run2Functions[0], app)).not.toEqual(appAssetPath); + expect(getAssetPath(run2Functions[1], app)).toEqual(backendAssetPath); + + // validate app has been rebuild but backend hasn't been touched + const appContents = await getAssetContent(run2Functions[0], app); + for (const entry of [ + 'app', + 'backend', + 'common', + 'pydantic', + 'httpx', + // '_common.pth', // TODO: figure out why this is not being included (something to do with editable vs virtual after uv sync) + // 'app_handler.py', // TODO: figure out why this is not being included (something to do with editable vs virtual after uv sync) + ]) { + expect(appContents).toContain(entry); + } + expect(await getAssetContent(run2Functions[1], app)).toEqual([ + 'backend.text', + ]); + }); + + test('Rebuild any function when its dependency version changes', async () => { + // copy the workspace so we can edit it without affecting the original + const workspacePath = await copyWorkspaceToTemp('workspaces_app'); + let { app, stack } = await createMultipleFunctionStack(workspacePath); + let template = Template.fromStack(stack); + + const run1Functions = getFunctions(template); + const appAssetPath = getAssetPath(run1Functions[0], app); + const backendAssetPath = getAssetPath(run1Functions[1], app); + // change the contents of the folder so we can verify what is rebuilt + await setAssetContents(run1Functions[0], app, ['app.text']); + await setAssetContents(run1Functions[1], app, ['backend.text']); + + // Update the version in the workspace + await bumpVersionAndUvSync(workspacePath, 'common'); + + // Need to create the stack again as synthesize is only called once otherwise + ({ app, stack } = await createMultipleFunctionStack(workspacePath)); + + template = Template.fromStack(stack); + const run2Functions = getFunctions(template); + // validate that different folder for both updated functions + expect(getAssetPath(run2Functions[0], app)).not.toEqual(appAssetPath); + expect(getAssetPath(run2Functions[1], app)).not.toEqual(backendAssetPath); + + // validate app and backend have both been rebuilt + const appContents = await getAssetContent(run2Functions[0], app); + for (const entry of [ + 'app', + 'backend', + 'common', + 'pydantic', + 'httpx', + // '_common.pth', // TODO: figure out why this is not being included (something to do with editable vs virtual after uv sync) + // 'app_handler.py', // TODO: figure out why this is not being included (something to do with editable vs virtual after uv sync) + ]) { + expect(appContents).toContain(entry); + } + const backendContents = await getAssetContent(run2Functions[1], app); + for (const entry of [ + 'app', + 'backend', + 'common', + 'pydantic', + 'httpx', + // '_common.pth', // TODO: figure out why this is not being included (something to do with editable vs virtual after uv sync) + // 'backend_handler.py', // TODO: figure out why this is not being included (something to do with editable vs virtual after uv sync) + ]) { + expect(backendContents).toContain(entry); + } + }); }); -// biome-ignore lint/suspicious/noExplicitAny: -async function getFunctionAssetContents(functionResource: any, app: App) { +/** + * Copy the workspace to a temporary directory and return the path to the temporary directory. + * The workspace is copied to ensure that the original workspace is not modified when we change it during tests. + * + * @param workspace + */ +async function copyWorkspaceToTemp(workspace: string): Promise { + const tempDir = await fs.mkdtemp( + path.join(os.tmpdir(), 'uv-python-lambda-workspace'), + ); + await fsextra.copy( + path.join(resourcesPath, workspace), + path.join(tempDir, workspace), + ); + return tempDir; +} + +/** + * Bump the version of a workspace package and run `uv sync` to update the workspace. + * + * @param workspacePath + * @param workspacePackage + * @param oldVersion + * @param newVersion + */ +async function bumpVersionAndUvSync( + workspacePath: string, + workspacePackage: string, + oldVersion = '0.1.0', + newVersion = '0.1.1', +): Promise { + // Update the version in the workspace + await replaceStringInFile( + path.join( + workspacePath, + 'workspaces_app', + workspacePackage, + 'pyproject.toml', + ), + `version = "${oldVersion}"`, + `version = "${newVersion}"`, + ); + const uvSyncArgs = [ + '--python-preference=only-system', + '--compile-bytecode', + '--no-dev', + '--no-editable', + '--link-mode=copy', + ]; + await execAsync( + `cd ${workspacePath}/workspaces_app && uv sync --directory ${workspacePath}/workspaces_app --package ${workspacePackage} ${uvSyncArgs.join(' ')}`, + ); +} + +/** + * Replace all occurrences of a string in a file. Used to make modifications to workspace copies for testing purposes. + * + * @param filePath + * @param searchString + * @param replaceString + */ +async function replaceStringInFile( + filePath: string, + searchString: string, + replaceString: string, +): Promise { + try { + // Read the file content + const content = await fs.readFile(filePath, 'utf-8'); + + // Perform the replacement + const updatedContent = content.replace( + new RegExp(searchString, 'g'), + replaceString, + ); + + // Write the modified content back to the file + await fs.writeFile(filePath, updatedContent, 'utf-8'); + } catch (error) { + if (error instanceof Error) { + throw new Error(`Failed to replace string in file: ${error.message}`); + } + throw error; + } +} + +// biome-ignore lint/suspicious/noExplicitAny: any is what is returned from the CDK template.findResources method +function getFunctions(template: Template): any[] { + return Object.values(template.findResources('AWS::Lambda::Function')); +} + +// biome-ignore lint/suspicious/noExplicitAny: any is what is returned from the CDK template.findResources method +function getAssetPath(functionResource: any, app: App): string { const [assetHash] = functionResource.Properties.Code.S3Key.split('.'); - const assetPath = path.join(app.outdir, `asset.${assetHash}`); - return await fs.readdir(assetPath); + return path.join(app.outdir, `asset.${assetHash}`); } -// async function setFunctionAssetContents( -// functionResource: any, -// app: App, -// contents: string[], -// ) { -// const [assetHash] = functionResource.Properties.Code.S3Key.split('.'); -// const assetPath = path.join(app.outdir, `asset.${assetHash}`); -// console.log({ assetPath }); -// // remove all existing contents and add the new ones as empty files -// const existingContents = await fs.readdir(assetPath); -// for (const entry of existingContents) { -// await fs.rm(entry, { recursive: true }); -// } -// for (const entry of contents) { -// await fs.writeFile(path.join(assetPath, entry), ''); -// } -// } +// biome-ignore lint/suspicious/noExplicitAny: any is what is returned from the CDK template.findResources method +async function getAssetContent(functionResource: any, app: App) { + return await fs.readdir(getAssetPath(functionResource, app)); +} + +async function setAssetContents( + // biome-ignore lint/suspicious/noExplicitAny: any is what is returned from the CDK template.findResources method + functionResource: any, + app: App, + contents: string[], +) { + const assetPath = getAssetPath(functionResource, app); + + // remove all existing contents and add the new ones as empty files + const existingContents = await fs.readdir(assetPath); + for (const entry of existingContents) { + await fs.rm(path.join(assetPath, entry), { force: true, recursive: true }); + } + for (const entry of contents) { + await fs.writeFile(path.join(assetPath, entry), ''); + } +} From 7a5805cdfd8deedc2a832ca67f315907d8b4c52e Mon Sep 17 00:00:00 2001 From: Chris McGrath Date: Fri, 29 Nov 2024 18:36:23 +0000 Subject: [PATCH 6/9] Add git based bundling strategy If we're not using a package in a workspace app then use the last commit hash for the entire rootDir and anything in git diff to generate the custom hash for CDK bundling. If we are using a package in a workspace app then get the list of dependencies and check and local ones for changes. This uses the hash of the last commit and any git diff in that part of the tree for each dependency and the package itself. --- src/bundling.ts | 62 ++ src/types.ts | 1 + test/function.test.ts | 582 +++++++++++++++++- .../resources/workspaces_app/common/README.md | 1 + 4 files changed, 630 insertions(+), 16 deletions(-) diff --git a/src/bundling.ts b/src/bundling.ts index b8c5dfe..caad39f 100644 --- a/src/bundling.ts +++ b/src/bundling.ts @@ -75,6 +75,8 @@ export class Bundling { switch (options.bundlingStrategy) { case BundlingStrategy.PACKAGE_VERSION: return Bundling.packageVersionStrategy(options); + case BundlingStrategy.GIT: + return Bundling.gitStrategy(options); default: return Bundling.sourceStrategy(options); } @@ -129,6 +131,45 @@ export class Bundling { }); } + private static gitStrategy(options: BundlingProps): AssetCode { + const workspacePackage = options.workspacePackage; + let assetHash: string; + + if (!workspacePackage) { + assetHash = Bundling.gitHash(options.rootDir); + } else { + const uvCommand = `cd ${options.rootDir} && uv export --package ${workspacePackage} --frozen --no-editable --no-dev --no-header`; + const dependencies = execSync(uvCommand).toString(); + // This includes the current workspacePackage + const workspaceDependencies = extractWorkspaceDependencies(dependencies); + const hash = createHash('sha256').update(dependencies); + for (const dependency of workspaceDependencies) { + hash.update(Bundling.gitHash(path.join(options.rootDir, dependency))); + } + assetHash = hash.digest('hex'); + } + + return Code.fromAsset(options.rootDir, { + assetHashType: AssetHashType.CUSTOM, + assetHash, + exclude: HASHABLE_DEPENDENCIES_EXCLUDE, + bundling: new Bundling(options), + }); + } + + private static gitHash(path: string): string { + const gitCommands = [ + `cd ${path}`, + 'git log -1 --format=%H -- .', // find the hash of the last commit that changed the files in the directory + 'git diff -- .', // get the diff of the files in the directory and below + ]; + const gitCommand = gitCommands.join(' && '); + const status = execSync(gitCommand).toString().trim(); + const assetHash = createHash('sha256').update(status).digest('hex'); + + return assetHash; + } + public readonly image: DockerImage; public readonly entrypoint?: string[] | undefined; public readonly command: string[] | undefined; @@ -232,3 +273,24 @@ export class Bundling { return commands; } } + +function extractWorkspaceDependencies(content: string): string[] { + // Split content into lines and filter out empty lines + const lines = content.split('\n').filter((line) => line.trim()); + + // Regular expression to match package lines + // Matches lines containing package==version followed by hash(es) + const packageLineRegex = + /^[\w-]+==[0-9]+\.[0-9]+(\.[0-9]+)?([a-z0-9.]+)?\s*(\\|\s|$)/; + + // Filter out package lines and hash lines + return lines.filter((line) => { + // Skip hash lines + if (line.trim().startsWith('--hash=')) { + return false; + } + + // Keep lines that don't match package pattern + return !packageLineRegex.test(line); + }); +} diff --git a/src/types.ts b/src/types.ts index f6ad42a..4eca20d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -106,6 +106,7 @@ export interface BundlingOptions extends DockerRunOptions { export enum BundlingStrategy { SOURCE = 'source', PACKAGE_VERSION = 'package-version', + GIT = 'git', } /** diff --git a/test/function.test.ts b/test/function.test.ts index bb1a7c4..c99a336 100644 --- a/test/function.test.ts +++ b/test/function.test.ts @@ -491,9 +491,538 @@ describe('When bundlingStrategy is set to BundlingStrategy.PACKAGE_VERSION', () ]); }); - test('Rebuild single function when its version changes', async () => { + test.failing( + 'FAILING: Rebuild single function when its version changes', + async () => { + // copy the workspace so we can edit it without affecting the original + const workspacePath = await copyWorkspaceToTemp('workspaces_app'); + let { app, stack } = await createMultipleFunctionStack(workspacePath); + let template = Template.fromStack(stack); + + const run1Functions = getFunctions(template); + const appAssetPath = getAssetPath(run1Functions[0], app); + const backendAssetPath = getAssetPath(run1Functions[1], app); + // change the contents of the folder so we can verify what is rebuilt + await setAssetContents(run1Functions[0], app, ['app.text']); + await setAssetContents(run1Functions[1], app, ['backend.text']); + + // Update the version in the workspace + await bumpVersionAndUvSync(workspacePath, 'app'); + + // Need to create the stack again as synthesize is only called once otherwise + ({ app, stack } = await createMultipleFunctionStack(workspacePath)); + + template = Template.fromStack(stack); + const run2Functions = getFunctions(template); + // validate that different folder for updated function but same one for unchanged + expect(getAssetPath(run2Functions[0], app)).not.toEqual(appAssetPath); + expect(getAssetPath(run2Functions[1], app)).toEqual(backendAssetPath); + + // validate app has been rebuild but backend hasn't been touched + const appContents = await getAssetContent(run2Functions[0], app); + for (const entry of [ + 'app', + 'backend', + 'common', + 'pydantic', + 'httpx', + '_common.pth', // TODO: figure out why this is not being included (something to do with editable vs virtual after uv sync) + 'app_handler.py', // TODO: figure out why this is not being included (something to do with editable vs virtual after uv sync) + ]) { + expect(appContents).toContain(entry); + } + expect(await getAssetContent(run2Functions[1], app)).toEqual([ + 'backend.text', + ]); + }, + ); + + test.failing( + 'FAILING: Rebuild any function when its dependency version changes', + async () => { + // copy the workspace so we can edit it without affecting the original + const workspacePath = await copyWorkspaceToTemp('workspaces_app'); + let { app, stack } = await createMultipleFunctionStack(workspacePath); + let template = Template.fromStack(stack); + + const run1Functions = getFunctions(template); + const appAssetPath = getAssetPath(run1Functions[0], app); + const backendAssetPath = getAssetPath(run1Functions[1], app); + // change the contents of the folder so we can verify what is rebuilt + await setAssetContents(run1Functions[0], app, ['app.text']); + await setAssetContents(run1Functions[1], app, ['backend.text']); + + // Update the version in the workspace + await bumpVersionAndUvSync(workspacePath, 'common'); + + // Need to create the stack again as synthesize is only called once otherwise + ({ app, stack } = await createMultipleFunctionStack(workspacePath)); + + template = Template.fromStack(stack); + const run2Functions = getFunctions(template); + // validate that different folder for both updated functions + expect(getAssetPath(run2Functions[0], app)).not.toEqual(appAssetPath); + expect(getAssetPath(run2Functions[1], app)).not.toEqual(backendAssetPath); + + // validate app and backend have both been rebuilt + const appContents = await getAssetContent(run2Functions[0], app); + for (const entry of [ + 'app', + 'backend', + 'common', + 'pydantic', + 'httpx', + '_common.pth', // TODO: figure out why this is not being included (something to do with editable vs virtual after uv sync) + 'app_handler.py', // TODO: figure out why this is not being included (something to do with editable vs virtual after uv sync) + ]) { + expect(appContents).toContain(entry); + } + const backendContents = await getAssetContent(run2Functions[1], app); + for (const entry of [ + 'app', + 'backend', + 'common', + 'pydantic', + 'httpx', + '_common.pth', // TODO: figure out why this is not being included (something to do with editable vs virtual after uv sync) + 'backend_handler.py', // TODO: figure out why this is not being included (something to do with editable vs virtual after uv sync) + ]) { + expect(backendContents).toContain(entry); + } + }, + ); +}); + +describe('When bundlingStrategy is set to BundlingStrategy.GIT', () => { + jest.setTimeout(30000); // we are doing integration tests with the file system so give tests more time + + test('Create a function from basic_app', async () => { + const { app, stack } = await createStack(); + + new PythonFunction(stack, 'basic_app', { + rootDir: path.join(resourcesPath, 'basic_app'), + index: 'handler.py', + handler: 'lambda_handler', + runtime: Runtime.PYTHON_3_12, + architecture: await getDockerHostArch(), + bundling: { + bundlingStrategy: BundlingStrategy.GIT, + }, + }); + + const template = Template.fromStack(stack); + + template.hasResourceProperties('AWS::Lambda::Function', { + Handler: 'handler.lambda_handler', + Runtime: 'python3.12', + Code: { + S3Bucket: Match.anyValue(), + S3Key: Match.anyValue(), + }, + }); + const functions = getFunctions(template); + expect(functions).toHaveLength(1); + const contents = await getAssetContent(functions[0], app); + expect(contents).toContain('handler.py'); + }); + + test('Create a function from basic_app when skip is true', async () => { + const { stack } = await createStack(); + + const bundlingSpy = jest + .spyOn(stack, 'bundlingRequired', 'get') + .mockReturnValue(false); + const architecture = await getDockerHostArch(); + + // To see this fail, comment out the `if (skip) { return; } code in the PythonFunction constructor + expect(() => { + new PythonFunction(stack, 'basic_app', { + rootDir: path.join(resourcesPath, 'basic_app'), + index: 'handler', + handler: 'lambda_handler', + runtime: Runtime.PYTHON_3_12, + architecture, + bundling: { + bundlingStrategy: BundlingStrategy.GIT, + }, + }); + }).not.toThrow(); + + bundlingSpy.mockRestore(); + }); + + test('Create a function from basic_app only gets rebuild when source changes', async () => { + const createBasicAppStack = async (workspacePath: string) => { + const { app, stack } = await createStack(); + + new PythonFunction(stack, 'basic_app', { + rootDir: path.join(workspacePath, 'basic_app'), + index: 'handler.py', + handler: 'lambda_handler', + runtime: Runtime.PYTHON_3_12, + architecture: await getDockerHostArch(), + bundling: { + bundlingStrategy: BundlingStrategy.GIT, + }, + }); + + return { app, stack }; + }; + + const workspacePath = await copyWorkspaceToTemp('basic_app', { git: true }); + + let { app, stack } = await createBasicAppStack(workspacePath); + let template = Template.fromStack(stack); + + const run1Functions = getFunctions(template); + const basicAppAssetPath = getAssetPath(run1Functions[0], app); + expect(run1Functions).toHaveLength(1); + const contents = await getAssetContent(run1Functions[0], app); + expect(contents).toContain('handler.py'); + // change the contents of the folder so we can verify what is rebuilt + await setAssetContents(run1Functions[0], app, ['basic_app.text']); + + // Need to create the stack again as synthesize is only called once otherwise + ({ app, stack } = await createBasicAppStack(workspacePath)); + template = Template.fromStack(stack); + const run2Functions = getFunctions(template); + // validate that same folder is used because the hash is the same + expect(getAssetPath(run2Functions[0], app)).toEqual(basicAppAssetPath); + expect(await getAssetContent(run2Functions[0], app)).toEqual([ + 'basic_app.text', + ]); + + // Now modify the source folder and check that the function is rebuilt + await replaceStringInFile( + path.join(workspacePath, 'basic_app', 'handler.py'), + 'Hello', + 'Hi', + ); + + ({ app, stack } = await createBasicAppStack(workspacePath)); + template = Template.fromStack(stack); + const run3Functions = getFunctions(template); + // validate that different folder is used because the hash is different + expect(getAssetPath(run3Functions[0], app)).not.toEqual(basicAppAssetPath); + expect(await getAssetContent(run3Functions[0], app)).toContain( + 'handler.py', + ); + }); + + test('Create a function with workspaces_app', async () => { + const { app, stack } = await createStack('wstest'); + + new PythonFunction(stack, 'workspaces_app', { + rootDir: path.join(resourcesPath, 'workspaces_app'), + workspacePackage: 'app', + index: 'app_handler.py', + handler: 'handle_event', + runtime: Runtime.PYTHON_3_10, + architecture: await getDockerHostArch(), + bundling: { + bundlingStrategy: BundlingStrategy.GIT, + }, + }); + + const template = Template.fromStack(stack); + + template.hasResourceProperties('AWS::Lambda::Function', { + Handler: 'app_handler.handle_event', + Runtime: 'python3.10', + Code: { + S3Bucket: Match.anyValue(), + S3Key: Match.anyValue(), + }, + }); + + const functions = getFunctions(template); + expect(functions).toHaveLength(1); + const contents = await getAssetContent(functions[0], app); + for (const entry of [ + 'app', + 'common', + 'pydantic', + 'httpx', + '_common.pth', + 'app_handler.py', + ]) { + expect(contents).toContain(entry); + } + }); + + const createWorkspaceAppStack = async (workspacePath: string) => { + const { app, stack } = await createStack(); + + new PythonFunction(stack, 'workspaces_app', { + rootDir: path.join(workspacePath, 'workspaces_app'), + workspacePackage: 'app', + index: 'app_handler.py', + handler: 'handle_event', + runtime: Runtime.PYTHON_3_10, + architecture: await getDockerHostArch(), + bundling: { + bundlingStrategy: BundlingStrategy.GIT, + }, + }); + + return { app, stack }; + }; + + test('Create a function with workspaces_app gets rebuilt if app changes', async () => { + const workspacePath = await copyWorkspaceToTemp('workspaces_app', { + git: true, + }); + + let { app, stack } = await createWorkspaceAppStack(workspacePath); + + let template = Template.fromStack(stack); + + const run1Functions = getFunctions(template); + expect(run1Functions).toHaveLength(1); + const appAssetPath = getAssetPath(run1Functions[0], app); + const contents = await getAssetContent(run1Functions[0], app); + for (const entry of [ + 'app', + 'common', + 'pydantic', + 'httpx', + '_common.pth', + 'app_handler.py', + ]) { + expect(contents).toContain(entry); + } + + // change the contents of the folder so we can verify what is rebuilt + await setAssetContents(run1Functions[0], app, ['app.text']); + + // Need to create the stack again as synthesize is only called once otherwise + ({ app, stack } = await createWorkspaceAppStack(workspacePath)); + template = Template.fromStack(stack); + const run2Functions = getFunctions(template); + // validate that same folder is used because the hash is the same + expect(getAssetPath(run2Functions[0], app)).toEqual(appAssetPath); + expect(await getAssetContent(run2Functions[0], app)).toEqual(['app.text']); + + // Now modify the source folder and check that the function is rebuilt + await replaceStringInFile( + path.join(workspacePath, 'workspaces_app', 'app', 'app_handler.py'), + 'Corolla', + 'Auris', + ); + + ({ app, stack } = await createWorkspaceAppStack(workspacePath)); + template = Template.fromStack(stack); + const run3Functions = getFunctions(template); + const run3AssetPath = getAssetPath(run3Functions[0], app); + // validate that different folder is used because the hash is different + expect(run3AssetPath).not.toEqual(appAssetPath); + expect(await getAssetContent(run3Functions[0], app)).toContain( + 'app_handler.py', + ); + }); + + test('Create a function with workspaces_app gets rebuilt if dependency changes', async () => { + const workspacePath = await copyWorkspaceToTemp('workspaces_app', { + git: true, + }); + + let { app, stack } = await createWorkspaceAppStack(workspacePath); + + let template = Template.fromStack(stack); + + const run1Functions = getFunctions(template); + expect(run1Functions).toHaveLength(1); + const appAssetPath = getAssetPath(run1Functions[0], app); + const contents = await getAssetContent(run1Functions[0], app); + for (const entry of [ + 'app', + 'common', + 'pydantic', + 'httpx', + '_common.pth', + 'app_handler.py', + ]) { + expect(contents).toContain(entry); + } + + // change the contents of the folder so we can verify what is rebuilt + await setAssetContents(run1Functions[0], app, ['app.text']); + + // Now modify the dependency source folder and check that the function is rebuilt + await replaceStringInFile( + path.join(workspacePath, 'workspaces_app', 'common', 'README.md'), + 'Common', + 'Shared', + ); + + ({ app, stack } = await createWorkspaceAppStack(workspacePath)); + template = Template.fromStack(stack); + const run3Functions = getFunctions(template); + const run3AssetPath = getAssetPath(run3Functions[0], app); + // validate that different folder is used because the hash is different + expect(run3AssetPath).not.toEqual(appAssetPath); + expect(await getAssetContent(run3Functions[0], app)).toContain( + 'app_handler.py', + ); + }); + + test('Create a function with workspaces_app does NOT get rebuilt if unrelated code changes', async () => { + const workspacePath = await copyWorkspaceToTemp('workspaces_app', { + git: true, + }); + + let { app, stack } = await createWorkspaceAppStack(workspacePath); + + let template = Template.fromStack(stack); + + const run1Functions = getFunctions(template); + expect(run1Functions).toHaveLength(1); + const appAssetPath = getAssetPath(run1Functions[0], app); + const contents = await getAssetContent(run1Functions[0], app); + for (const entry of [ + 'app', + 'common', + 'pydantic', + 'httpx', + '_common.pth', + 'app_handler.py', + ]) { + expect(contents).toContain(entry); + } + + // change the contents of the folder so we can verify what is rebuilt + await setAssetContents(run1Functions[0], app, ['app.text']); + + // Now modify the unrelated app source and check that the function is not rebuilt + await replaceStringInFile( + path.join( + workspacePath, + 'workspaces_app', + 'backend', + 'backend_handler.py', + ), + 'Skyline', + 'Juke', + ); + + ({ app, stack } = await createWorkspaceAppStack(workspacePath)); + template = Template.fromStack(stack); + const run3Functions = getFunctions(template); + const run3AssetPath = getAssetPath(run3Functions[0], app); + // validate that same folder is used because the hash is the same + expect(run3AssetPath).toEqual(appAssetPath); + expect(await getAssetContent(run3Functions[0], app)).toContain('app.text'); + }); + + async function createMultipleFunctionStack( + workspacePath?: string, + ): Promise<{ app: App; stack: Stack }> { + const { app, stack } = await createStack('wstest'); + const rootPath = workspacePath ?? resourcesPath; + + new PythonFunction(stack, 'workspaces_app', { + rootDir: path.join(rootPath, 'workspaces_app'), + workspacePackage: 'app', + index: 'app_handler.py', + handler: 'handle_event', + runtime: Runtime.PYTHON_3_10, + architecture: await getDockerHostArch(), + bundling: { + bundlingStrategy: BundlingStrategy.GIT, + }, + }); + + new PythonFunction(stack, 'workspaces_backend', { + rootDir: path.join(rootPath, 'workspaces_app'), + workspacePackage: 'backend', + index: 'backend_handler.py', + handler: 'handle_event', + runtime: Runtime.PYTHON_3_10, + architecture: await getDockerHostArch(), + bundling: { + bundlingStrategy: BundlingStrategy.GIT, + }, + }); + + return { app, stack }; + } + + test('Create multiple functions with workspaces_app', async () => { + const { app, stack } = await createMultipleFunctionStack(); + const template = Template.fromStack(stack); + + template.hasResourceProperties('AWS::Lambda::Function', { + Handler: 'app_handler.handle_event', + }); + + template.hasResourceProperties('AWS::Lambda::Function', { + Handler: 'backend_handler.handle_event', + }); + + const functions = getFunctions(template); + expect(functions).toHaveLength(2); + const appContents = await getAssetContent(functions[0], app); + for (const entry of [ + 'app', + 'backend', + 'common', + 'pydantic', + 'httpx', + '_common.pth', + 'app_handler.py', + ]) { + expect(appContents).toContain(entry); + } + expect(appContents).not.toContain('backend_handler.py'); + expect(appContents).not.toContain('pathlib'); + const backendContents = await getAssetContent(functions[1], app); + for (const entry of [ + 'app', + 'backend', + 'common', + 'pydantic', + 'httpx', + '_common.pth', + 'backend_handler.py', + ]) { + expect(backendContents).toContain(entry); + } + expect(backendContents).not.toContain('app_handler.py'); + expect(backendContents).not.toContain('flask'); + }); + + test("Doesn't rebuild multiple functions if they haven't changed", async () => { + let { app, stack } = await createMultipleFunctionStack(); + let template = Template.fromStack(stack); + + const run1Functions = getFunctions(template); + const appAssetPath = getAssetPath(run1Functions[0], app); + const backendAssetPath = getAssetPath(run1Functions[1], app); + // change the contents of the folder so we can verify they are not rebuilt + await setAssetContents(run1Functions[0], app, ['app.text']); + await setAssetContents(run1Functions[1], app, ['backend.text']); + + // Need to create the stack again as synthesize is only called once otherwise + ({ app, stack } = await createMultipleFunctionStack()); + + template = Template.fromStack(stack); + const run2Functions = getFunctions(template); + // validate that the same folder is being used for the functions + expect(getAssetPath(run2Functions[0], app)).toEqual(appAssetPath); + expect(getAssetPath(run2Functions[1], app)).toEqual(backendAssetPath); + + // validate they contain the content we put in above, i.e. haven't been overwritten + expect(await getAssetContent(run2Functions[0], app)).toEqual(['app.text']); + expect(await getAssetContent(run2Functions[1], app)).toEqual([ + 'backend.text', + ]); + }); + + test('Rebuild single function when its content changes', async () => { // copy the workspace so we can edit it without affecting the original - const workspacePath = await copyWorkspaceToTemp('workspaces_app'); + const workspacePath = await copyWorkspaceToTemp('workspaces_app', { + git: true, + }); let { app, stack } = await createMultipleFunctionStack(workspacePath); let template = Template.fromStack(stack); @@ -504,9 +1033,12 @@ describe('When bundlingStrategy is set to BundlingStrategy.PACKAGE_VERSION', () await setAssetContents(run1Functions[0], app, ['app.text']); await setAssetContents(run1Functions[1], app, ['backend.text']); - // Update the version in the workspace - await bumpVersionAndUvSync(workspacePath, 'app'); - + // Now modify the source folder and check that the function is rebuilt + await replaceStringInFile( + path.join(workspacePath, 'workspaces_app', 'app', 'app_handler.py'), + 'Corolla', + 'Auris', + ); // Need to create the stack again as synthesize is only called once otherwise ({ app, stack } = await createMultipleFunctionStack(workspacePath)); @@ -524,8 +1056,8 @@ describe('When bundlingStrategy is set to BundlingStrategy.PACKAGE_VERSION', () 'common', 'pydantic', 'httpx', - // '_common.pth', // TODO: figure out why this is not being included (something to do with editable vs virtual after uv sync) - // 'app_handler.py', // TODO: figure out why this is not being included (something to do with editable vs virtual after uv sync) + '_common.pth', + 'app_handler.py', ]) { expect(appContents).toContain(entry); } @@ -536,7 +1068,9 @@ describe('When bundlingStrategy is set to BundlingStrategy.PACKAGE_VERSION', () test('Rebuild any function when its dependency version changes', async () => { // copy the workspace so we can edit it without affecting the original - const workspacePath = await copyWorkspaceToTemp('workspaces_app'); + const workspacePath = await copyWorkspaceToTemp('workspaces_app', { + git: true, + }); let { app, stack } = await createMultipleFunctionStack(workspacePath); let template = Template.fromStack(stack); @@ -547,9 +1081,12 @@ describe('When bundlingStrategy is set to BundlingStrategy.PACKAGE_VERSION', () await setAssetContents(run1Functions[0], app, ['app.text']); await setAssetContents(run1Functions[1], app, ['backend.text']); - // Update the version in the workspace - await bumpVersionAndUvSync(workspacePath, 'common'); - + // Now modify the dependency source folder and check that both functions are rebuilt + await replaceStringInFile( + path.join(workspacePath, 'workspaces_app', 'common', 'README.md'), + 'Common', + 'Shared', + ); // Need to create the stack again as synthesize is only called once otherwise ({ app, stack } = await createMultipleFunctionStack(workspacePath)); @@ -567,8 +1104,8 @@ describe('When bundlingStrategy is set to BundlingStrategy.PACKAGE_VERSION', () 'common', 'pydantic', 'httpx', - // '_common.pth', // TODO: figure out why this is not being included (something to do with editable vs virtual after uv sync) - // 'app_handler.py', // TODO: figure out why this is not being included (something to do with editable vs virtual after uv sync) + '_common.pth', + 'app_handler.py', ]) { expect(appContents).toContain(entry); } @@ -579,8 +1116,8 @@ describe('When bundlingStrategy is set to BundlingStrategy.PACKAGE_VERSION', () 'common', 'pydantic', 'httpx', - // '_common.pth', // TODO: figure out why this is not being included (something to do with editable vs virtual after uv sync) - // 'backend_handler.py', // TODO: figure out why this is not being included (something to do with editable vs virtual after uv sync) + '_common.pth', + 'backend_handler.py', ]) { expect(backendContents).toContain(entry); } @@ -592,8 +1129,12 @@ describe('When bundlingStrategy is set to BundlingStrategy.PACKAGE_VERSION', () * The workspace is copied to ensure that the original workspace is not modified when we change it during tests. * * @param workspace + * @param git - if true, a git repository is initialized in the copied workspace */ -async function copyWorkspaceToTemp(workspace: string): Promise { +async function copyWorkspaceToTemp( + workspace: string, + { git = false }: { git?: boolean } = { git: false }, +): Promise { const tempDir = await fs.mkdtemp( path.join(os.tmpdir(), 'uv-python-lambda-workspace'), ); @@ -601,6 +1142,15 @@ async function copyWorkspaceToTemp(workspace: string): Promise { path.join(resourcesPath, workspace), path.join(tempDir, workspace), ); + + if (git) { + const gitPath = path.join(tempDir, workspace); + await execAsync('git init', { cwd: gitPath }); + await execAsync('git add .', { cwd: gitPath }); + await execAsync('git commit -m "commit for test purposes"', { + cwd: gitPath, + }); + } return tempDir; } diff --git a/test/resources/workspaces_app/common/README.md b/test/resources/workspaces_app/common/README.md index e69de29..418e2f2 100644 --- a/test/resources/workspaces_app/common/README.md +++ b/test/resources/workspaces_app/common/README.md @@ -0,0 +1 @@ +Common code for all workspaces apps From cad745f92fc952cba081f66fd90dad6019ba71b5 Mon Sep 17 00:00:00 2001 From: Chris McGrath Date: Fri, 29 Nov 2024 18:55:37 +0000 Subject: [PATCH 7/9] Remove PACKAGE_VERSION bundling strategy Couldn't get the tests to pass so removing for now until we can figure out why and put it back in. --- src/bundling.ts | 32 +--- src/types.ts | 1 - test/function.test.ts | 368 ------------------------------------------ 3 files changed, 3 insertions(+), 398 deletions(-) diff --git a/src/bundling.ts b/src/bundling.ts index caad39f..b52b084 100644 --- a/src/bundling.ts +++ b/src/bundling.ts @@ -73,8 +73,6 @@ export interface BundlingProps extends BundlingOptions { export class Bundling { public static bundle(options: BundlingProps): AssetCode { switch (options.bundlingStrategy) { - case BundlingStrategy.PACKAGE_VERSION: - return Bundling.packageVersionStrategy(options); case BundlingStrategy.GIT: return Bundling.gitStrategy(options); default: @@ -99,38 +97,14 @@ export class Bundling { } /** - * Uses the AssetHashType.CUSTOM strategy and uv tree output to calculate the asset hash. + * Uses the AssetHashType.CUSTOM strategy and git output to calculate the asset hash. * - * This strategy uses the output of `uv tree` to calculate the asset hash. If there are multiple packages in a workspace this method will only - * rebuild the asset for a package if the package or a dependency version changes. This will not pick up local changes to the source unless they - * also change the package version in pyproject.toml. + * This strategy uses the output of git commands to calculate the asset hash. If there are multiple packages in a workspace this method will + * rebuild the asset for a package if the package, local dependency, or a dependency version changes. * * @param options * @private */ - private static packageVersionStrategy(options: BundlingProps): AssetCode { - const workspacePackage = options.workspacePackage; - const uvPackageArgs = workspacePackage - ? `--package ${workspacePackage}` - : ''; - const uvTreeOptions = [ - '--python-preference=only-system', // will be running the lambda python - '--no-dev', // don't need dev dependencies - '--frozen', // don't try and update the lock file - ]; - const command = `cd ${options.rootDir} && uv tree ${uvTreeOptions.join(' ')} ${uvPackageArgs}`; - // TODO: find something that works on Windows, maybe automatically changing directory and running the command - const tree = execSync(command).toString().trim(); - const assetHash = createHash('sha256').update(tree).digest('hex'); - - return Code.fromAsset(options.rootDir, { - assetHashType: AssetHashType.CUSTOM, - assetHash, - exclude: HASHABLE_DEPENDENCIES_EXCLUDE, - bundling: new Bundling(options), - }); - } - private static gitStrategy(options: BundlingProps): AssetCode { const workspacePackage = options.workspacePackage; let assetHash: string; diff --git a/src/types.ts b/src/types.ts index 4eca20d..13eb2a5 100644 --- a/src/types.ts +++ b/src/types.ts @@ -105,7 +105,6 @@ export interface BundlingOptions extends DockerRunOptions { export enum BundlingStrategy { SOURCE = 'source', - PACKAGE_VERSION = 'package-version', GIT = 'git', } diff --git a/test/function.test.ts b/test/function.test.ts index c99a336..d901ab4 100644 --- a/test/function.test.ts +++ b/test/function.test.ts @@ -262,337 +262,6 @@ describe('When bundlingStrategy is set to BundlingStrategy.SOURCE', () => { }, 30000); }); -describe('When bundlingStrategy is set to BundlingStrategy.PACKAGE_VERSION', () => { - jest.setTimeout(30000); // we are doing integration tests with the file system so give tests more time - - test('Create a function from basic_app', async () => { - const { app, stack } = await createStack(); - - new PythonFunction(stack, 'basic_app', { - rootDir: path.join(resourcesPath, 'basic_app'), - index: 'handler.py', - handler: 'lambda_handler', - runtime: Runtime.PYTHON_3_12, - architecture: await getDockerHostArch(), - bundling: { - bundlingStrategy: BundlingStrategy.PACKAGE_VERSION, - }, - }); - - const template = Template.fromStack(stack); - - template.hasResourceProperties('AWS::Lambda::Function', { - Handler: 'handler.lambda_handler', - Runtime: 'python3.12', - Code: { - S3Bucket: Match.anyValue(), - S3Key: Match.anyValue(), - }, - }); - const functions = getFunctions(template); - expect(functions).toHaveLength(1); - const contents = await getAssetContent(functions[0], app); - expect(contents).toContain('handler.py'); - }); - - test('Create a function from basic_app with no .py index extension', async () => { - const { stack } = await createStack(); - - new PythonFunction(stack, 'basic_app', { - rootDir: path.join(resourcesPath, 'basic_app'), - index: 'handler', - handler: 'lambda_handler', - runtime: Runtime.PYTHON_3_12, - architecture: await getDockerHostArch(), - bundling: { - bundlingStrategy: BundlingStrategy.PACKAGE_VERSION, - }, - }); - - const template = Template.fromStack(stack); - - template.hasResourceProperties('AWS::Lambda::Function', { - Handler: 'handler.lambda_handler', - Runtime: 'python3.12', - Code: { - S3Bucket: Match.anyValue(), - S3Key: Match.anyValue(), - }, - }); - }); - - test('Create a function from basic_app when skip is true', async () => { - const { stack } = await createStack(); - - const bundlingSpy = jest - .spyOn(stack, 'bundlingRequired', 'get') - .mockReturnValue(false); - const architecture = await getDockerHostArch(); - - // To see this fail, comment out the `if (skip) { return; } code in the PythonFunction constructor - expect(() => { - new PythonFunction(stack, 'basic_app', { - rootDir: path.join(resourcesPath, 'basic_app'), - index: 'handler', - handler: 'lambda_handler', - runtime: Runtime.PYTHON_3_12, - architecture, - bundling: { - bundlingStrategy: BundlingStrategy.PACKAGE_VERSION, - }, - }); - }).not.toThrow(); - - bundlingSpy.mockRestore(); - }); - - test('Create a function with workspaces_app', async () => { - const { app, stack } = await createStack('wstest'); - - new PythonFunction(stack, 'workspaces_app', { - rootDir: path.join(resourcesPath, 'workspaces_app'), - workspacePackage: 'app', - index: 'app_handler.py', - handler: 'handle_event', - runtime: Runtime.PYTHON_3_10, - architecture: await getDockerHostArch(), - bundling: { - bundlingStrategy: BundlingStrategy.PACKAGE_VERSION, - }, - }); - - const template = Template.fromStack(stack); - - template.hasResourceProperties('AWS::Lambda::Function', { - Handler: 'app_handler.handle_event', - Runtime: 'python3.10', - Code: { - S3Bucket: Match.anyValue(), - S3Key: Match.anyValue(), - }, - }); - - const functions = getFunctions(template); - expect(functions).toHaveLength(1); - const contents = await getAssetContent(functions[0], app); - for (const entry of [ - 'app', - 'common', - 'pydantic', - 'httpx', - '_common.pth', - 'app_handler.py', - ]) { - expect(contents).toContain(entry); - } - }); - - async function createMultipleFunctionStack( - workspacePath?: string, - ): Promise<{ app: App; stack: Stack }> { - const { app, stack } = await createStack('wstest'); - const rootPath = workspacePath ?? resourcesPath; - - new PythonFunction(stack, 'workspaces_app', { - rootDir: path.join(rootPath, 'workspaces_app'), - workspacePackage: 'app', - index: 'app_handler.py', - handler: 'handle_event', - runtime: Runtime.PYTHON_3_10, - architecture: await getDockerHostArch(), - bundling: { - bundlingStrategy: BundlingStrategy.PACKAGE_VERSION, - }, - }); - - new PythonFunction(stack, 'workspaces_backend', { - rootDir: path.join(rootPath, 'workspaces_app'), - workspacePackage: 'backend', - index: 'backend_handler.py', - handler: 'handle_event', - runtime: Runtime.PYTHON_3_10, - architecture: await getDockerHostArch(), - bundling: { - bundlingStrategy: BundlingStrategy.PACKAGE_VERSION, - }, - }); - - return { app, stack }; - } - - test('Create multiple functions with workspaces_app', async () => { - const { app, stack } = await createMultipleFunctionStack(); - const template = Template.fromStack(stack); - - template.hasResourceProperties('AWS::Lambda::Function', { - Handler: 'app_handler.handle_event', - }); - - template.hasResourceProperties('AWS::Lambda::Function', { - Handler: 'backend_handler.handle_event', - }); - - const functions = getFunctions(template); - expect(functions).toHaveLength(2); - const appContents = await getAssetContent(functions[0], app); - for (const entry of [ - 'app', - 'backend', - 'common', - 'pydantic', - 'httpx', - '_common.pth', - 'app_handler.py', - ]) { - expect(appContents).toContain(entry); - } - expect(appContents).not.toContain('backend_handler.py'); - expect(appContents).not.toContain('pathlib'); - const backendContents = await getAssetContent(functions[1], app); - for (const entry of [ - 'app', - 'backend', - 'common', - 'pydantic', - 'httpx', - '_common.pth', - 'backend_handler.py', - ]) { - expect(backendContents).toContain(entry); - } - expect(backendContents).not.toContain('app_handler.py'); - expect(backendContents).not.toContain('flask'); - }); - - test("Doesn't rebuild multiple functions if they haven't changed", async () => { - let { app, stack } = await createMultipleFunctionStack(); - let template = Template.fromStack(stack); - - const run1Functions = getFunctions(template); - const appAssetPath = getAssetPath(run1Functions[0], app); - const backendAssetPath = getAssetPath(run1Functions[1], app); - // change the contents of the folder so we can verify they are not rebuilt - await setAssetContents(run1Functions[0], app, ['app.text']); - await setAssetContents(run1Functions[1], app, ['backend.text']); - - // Need to create the stack again as synthesize is only called once otherwise - ({ app, stack } = await createMultipleFunctionStack()); - - template = Template.fromStack(stack); - const run2Functions = getFunctions(template); - // validate that the same folder is being used for the functions - expect(getAssetPath(run2Functions[0], app)).toEqual(appAssetPath); - expect(getAssetPath(run2Functions[1], app)).toEqual(backendAssetPath); - - // validate they contain the content we put in above, i.e. haven't been overwritten - expect(await getAssetContent(run2Functions[0], app)).toEqual(['app.text']); - expect(await getAssetContent(run2Functions[1], app)).toEqual([ - 'backend.text', - ]); - }); - - test.failing( - 'FAILING: Rebuild single function when its version changes', - async () => { - // copy the workspace so we can edit it without affecting the original - const workspacePath = await copyWorkspaceToTemp('workspaces_app'); - let { app, stack } = await createMultipleFunctionStack(workspacePath); - let template = Template.fromStack(stack); - - const run1Functions = getFunctions(template); - const appAssetPath = getAssetPath(run1Functions[0], app); - const backendAssetPath = getAssetPath(run1Functions[1], app); - // change the contents of the folder so we can verify what is rebuilt - await setAssetContents(run1Functions[0], app, ['app.text']); - await setAssetContents(run1Functions[1], app, ['backend.text']); - - // Update the version in the workspace - await bumpVersionAndUvSync(workspacePath, 'app'); - - // Need to create the stack again as synthesize is only called once otherwise - ({ app, stack } = await createMultipleFunctionStack(workspacePath)); - - template = Template.fromStack(stack); - const run2Functions = getFunctions(template); - // validate that different folder for updated function but same one for unchanged - expect(getAssetPath(run2Functions[0], app)).not.toEqual(appAssetPath); - expect(getAssetPath(run2Functions[1], app)).toEqual(backendAssetPath); - - // validate app has been rebuild but backend hasn't been touched - const appContents = await getAssetContent(run2Functions[0], app); - for (const entry of [ - 'app', - 'backend', - 'common', - 'pydantic', - 'httpx', - '_common.pth', // TODO: figure out why this is not being included (something to do with editable vs virtual after uv sync) - 'app_handler.py', // TODO: figure out why this is not being included (something to do with editable vs virtual after uv sync) - ]) { - expect(appContents).toContain(entry); - } - expect(await getAssetContent(run2Functions[1], app)).toEqual([ - 'backend.text', - ]); - }, - ); - - test.failing( - 'FAILING: Rebuild any function when its dependency version changes', - async () => { - // copy the workspace so we can edit it without affecting the original - const workspacePath = await copyWorkspaceToTemp('workspaces_app'); - let { app, stack } = await createMultipleFunctionStack(workspacePath); - let template = Template.fromStack(stack); - - const run1Functions = getFunctions(template); - const appAssetPath = getAssetPath(run1Functions[0], app); - const backendAssetPath = getAssetPath(run1Functions[1], app); - // change the contents of the folder so we can verify what is rebuilt - await setAssetContents(run1Functions[0], app, ['app.text']); - await setAssetContents(run1Functions[1], app, ['backend.text']); - - // Update the version in the workspace - await bumpVersionAndUvSync(workspacePath, 'common'); - - // Need to create the stack again as synthesize is only called once otherwise - ({ app, stack } = await createMultipleFunctionStack(workspacePath)); - - template = Template.fromStack(stack); - const run2Functions = getFunctions(template); - // validate that different folder for both updated functions - expect(getAssetPath(run2Functions[0], app)).not.toEqual(appAssetPath); - expect(getAssetPath(run2Functions[1], app)).not.toEqual(backendAssetPath); - - // validate app and backend have both been rebuilt - const appContents = await getAssetContent(run2Functions[0], app); - for (const entry of [ - 'app', - 'backend', - 'common', - 'pydantic', - 'httpx', - '_common.pth', // TODO: figure out why this is not being included (something to do with editable vs virtual after uv sync) - 'app_handler.py', // TODO: figure out why this is not being included (something to do with editable vs virtual after uv sync) - ]) { - expect(appContents).toContain(entry); - } - const backendContents = await getAssetContent(run2Functions[1], app); - for (const entry of [ - 'app', - 'backend', - 'common', - 'pydantic', - 'httpx', - '_common.pth', // TODO: figure out why this is not being included (something to do with editable vs virtual after uv sync) - 'backend_handler.py', // TODO: figure out why this is not being included (something to do with editable vs virtual after uv sync) - ]) { - expect(backendContents).toContain(entry); - } - }, - ); -}); - describe('When bundlingStrategy is set to BundlingStrategy.GIT', () => { jest.setTimeout(30000); // we are doing integration tests with the file system so give tests more time @@ -1154,43 +823,6 @@ async function copyWorkspaceToTemp( return tempDir; } -/** - * Bump the version of a workspace package and run `uv sync` to update the workspace. - * - * @param workspacePath - * @param workspacePackage - * @param oldVersion - * @param newVersion - */ -async function bumpVersionAndUvSync( - workspacePath: string, - workspacePackage: string, - oldVersion = '0.1.0', - newVersion = '0.1.1', -): Promise { - // Update the version in the workspace - await replaceStringInFile( - path.join( - workspacePath, - 'workspaces_app', - workspacePackage, - 'pyproject.toml', - ), - `version = "${oldVersion}"`, - `version = "${newVersion}"`, - ); - const uvSyncArgs = [ - '--python-preference=only-system', - '--compile-bytecode', - '--no-dev', - '--no-editable', - '--link-mode=copy', - ]; - await execAsync( - `cd ${workspacePath}/workspaces_app && uv sync --directory ${workspacePath}/workspaces_app --package ${workspacePackage} ${uvSyncArgs.join(' ')}`, - ); -} - /** * Replace all occurrences of a string in a file. Used to make modifications to workspace copies for testing purposes. * From a844e352b2c9a2d63bced39627aff559037b7489 Mon Sep 17 00:00:00 2001 From: Chris McGrath Date: Fri, 29 Nov 2024 19:11:22 +0000 Subject: [PATCH 8/9] Fix package related build issues and test git issue --- package-lock.json | 122 +++++++++++++-- test/function.test.ts | 9 +- yarn.lock | 348 ++++++++++++++++++++++++++---------------- 3 files changed, 336 insertions(+), 143 deletions(-) diff --git a/package-lock.json b/package-lock.json index da72284..bc73e7c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,11 +10,13 @@ "license": "Apache-2.0", "devDependencies": { "@biomejs/biome": "^1.9.4", + "@types/fs-extra": "^11.0.4", "@types/jest": "^29.5.13", "@types/node": "^22.7.6", "aws-cdk-lib": "2.161.1", "commit-and-tag-version": "^12", "constructs": "10.3.0", + "fs-extra": "^11.2.0", "jest": "^29.7.0", "jest-junit": "^15", "jsii": "~5.5.0", @@ -1907,6 +1909,17 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/fs-extra": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.4.tgz", + "integrity": "sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/jsonfile": "*", + "@types/node": "*" + } + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -1955,6 +1968,16 @@ "pretty-format": "^29.0.0" } }, + "node_modules/@types/jsonfile": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.4.tgz", + "integrity": "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/minimist": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", @@ -3097,6 +3120,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/codemaker/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/collect-v8-coverage": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", @@ -4225,9 +4263,9 @@ } }, "node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", "dev": true, "license": "MIT", "dependencies": { @@ -4236,7 +4274,7 @@ "universalify": "^2.0.0" }, "engines": { - "node": ">=12" + "node": ">=14.14" } }, "node_modules/fs.realpath": { @@ -6898,6 +6936,21 @@ "dev": true, "license": "MIT" }, + "node_modules/jsii-diff/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/jsii-diff/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -6985,6 +7038,21 @@ "wrap-ansi": "^7.0.0" } }, + "node_modules/jsii-docgen/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/jsii-docgen/node_modules/glob": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", @@ -7177,6 +7245,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/jsii-pacmak/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/jsii-pacmak/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -7319,6 +7402,21 @@ "dev": true, "license": "MIT" }, + "node_modules/jsii-reflect/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/jsii-reflect/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -8953,6 +9051,8 @@ }, "node_modules/projen/node_modules/glob/node_modules/minimatch": { "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "inBundle": true, "license": "ISC", @@ -9085,17 +9185,17 @@ } }, "node_modules/projen/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=10" } }, "node_modules/projen/node_modules/minimatch/node_modules/brace-expansion": { @@ -9238,6 +9338,8 @@ }, "node_modules/projen/node_modules/shelljs/node_modules/glob": { "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, "inBundle": true, "license": "ISC", @@ -9431,6 +9533,8 @@ }, "node_modules/projen/node_modules/yargs/node_modules/yargs-parser": { "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, "inBundle": true, "license": "ISC", diff --git a/test/function.test.ts b/test/function.test.ts index d901ab4..90aff70 100644 --- a/test/function.test.ts +++ b/test/function.test.ts @@ -816,9 +816,12 @@ async function copyWorkspaceToTemp( const gitPath = path.join(tempDir, workspace); await execAsync('git init', { cwd: gitPath }); await execAsync('git add .', { cwd: gitPath }); - await execAsync('git commit -m "commit for test purposes"', { - cwd: gitPath, - }); + await execAsync( + 'git commit --author "Testy McTestface " -m "commit for test purposes"', + { + cwd: gitPath, + }, + ); } return tempDir; } diff --git a/yarn.lock b/yarn.lock index 56b2ef3..cceec03 100644 --- a/yarn.lock +++ b/yarn.lock @@ -46,7 +46,7 @@ resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.8.tgz" integrity sha512-ZsysZyXY4Tlx+Q53XdnOFmqwfB9QDTHYxaZYajWRoBLuLEAwI2UIbtxOjWh/cFaa9IKUlcB+DDuoskLuKu56JA== -"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9": +"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9", "@babel/core@^7.8.0", "@babel/core@>=7.0.0-beta.0 <8": version "7.25.8" resolved "https://registry.npmjs.org/@babel/core/-/core-7.25.8.tgz" integrity sha512-Oixnb+DzmRT30qu9d3tJSQkxuygWm32DFykT4bRoORPa9hZ/L4KhVB/XiRm6KG+roIEM7DBQlmg27kw2HZkdZg== @@ -338,41 +338,6 @@ resolved "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.4.tgz" integrity sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw== -"@biomejs/cli-darwin-x64@1.9.4": - version "1.9.4" - resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.4.tgz#eafc2ce3849d385fc02238aad1ca4a73395a64d9" - integrity sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg== - -"@biomejs/cli-linux-arm64-musl@1.9.4": - version "1.9.4" - resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.4.tgz#d780c3e01758fc90f3268357e3f19163d1f84fca" - integrity sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA== - -"@biomejs/cli-linux-arm64@1.9.4": - version "1.9.4" - resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.4.tgz#8ed1dd0e89419a4b66a47f95aefb8c46ae6041c9" - integrity sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g== - -"@biomejs/cli-linux-x64-musl@1.9.4": - version "1.9.4" - resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.4.tgz#f36982b966bd671a36671e1de4417963d7db15fb" - integrity sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg== - -"@biomejs/cli-linux-x64@1.9.4": - version "1.9.4" - resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz#a0a7f56680c76b8034ddc149dbf398bdd3a462e8" - integrity sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg== - -"@biomejs/cli-win32-arm64@1.9.4": - version "1.9.4" - resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.4.tgz#e2ef4e0084e76b7e26f0fc887c5ef1265ea56200" - integrity sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg== - -"@biomejs/cli-win32-x64@1.9.4": - version "1.9.4" - resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz#4c7afa90e3970213599b4095e62f87e5972b2340" - integrity sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA== - "@cspotcode/source-map-support@^0.8.0": version "0.8.1" resolved "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz" @@ -565,7 +530,7 @@ jest-haste-map "^29.7.0" slash "^3.0.0" -"@jest/transform@^29.7.0": +"@jest/transform@^29.0.0", "@jest/transform@^29.7.0": version "29.7.0" resolved "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz" integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== @@ -586,7 +551,7 @@ slash "^3.0.0" write-file-atomic "^4.0.2" -"@jest/types@^29.6.3": +"@jest/types@^29.0.0", "@jest/types@^29.6.3": version "29.6.3" resolved "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz" integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== @@ -622,14 +587,6 @@ resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz" integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== -"@jridgewell/trace-mapping@0.3.9": - version "0.3.9" - resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz" - integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== - dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": version "0.3.25" resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz" @@ -638,6 +595,14 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jsii/check-node@1.103.1": version "1.103.1" resolved "https://registry.npmjs.org/@jsii/check-node/-/check-node-1.103.1.tgz" @@ -669,7 +634,7 @@ "@nodelib/fs.stat" "2.0.5" run-parallel "^1.1.9" -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": +"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5": version "2.0.5" resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== @@ -785,7 +750,7 @@ "@types/fs-extra@^11.0.4": version "11.0.4" - resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-11.0.4.tgz#e16a863bb8843fba8c5004362b5a73e17becca45" + resolved "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.4.tgz" integrity sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ== dependencies: "@types/jsonfile" "*" @@ -827,7 +792,7 @@ "@types/jsonfile@*": version "6.1.4" - resolved "https://registry.yarnpkg.com/@types/jsonfile/-/jsonfile-6.1.4.tgz#614afec1a1164e7d670b4a7ad64df3e7beb7b702" + resolved "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.4.tgz" integrity sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ== dependencies: "@types/node" "*" @@ -871,14 +836,6 @@ resolved "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.4.tgz" integrity sha512-zglELfWx7g1cEpVMRBZ0srIQO5nEvKvraJ6CVUC/c5Ky1GgX8OIjtUj5qOweTYULYZo5VnXs/LpUUUNiGpX/rA== -JSONStream@^1.3.5: - version "1.3.5" - resolved "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz" - integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== - dependencies: - jsonparse "^1.2.0" - through ">=2.2.7 <3" - acorn-walk@^8.1.1: version "8.3.4" resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz" @@ -903,7 +860,17 @@ agent-base@^7.0.2, agent-base@^7.1.0: dependencies: debug "^4.3.4" -ajv@^8.0.1, ajv@^8.17.1: +ajv@^8.0.1: + version "8.17.1" + resolved "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz" + integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== + dependencies: + fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + +ajv@^8.17.1: version "8.17.1" resolved "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz" integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== @@ -1015,7 +982,7 @@ aws-cdk-lib@2.161.1: table "^6.8.2" yaml "1.10.2" -babel-jest@^29.7.0: +babel-jest@^29.0.0, babel-jest@^29.7.0: version "29.7.0" resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz" integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== @@ -1105,7 +1072,7 @@ braces@^3.0.3: dependencies: fill-range "^7.1.1" -browserslist@^4.24.0: +browserslist@^4.24.0, "browserslist@>= 4.21.0": version "4.24.0" resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz" integrity sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A== @@ -1153,7 +1120,12 @@ camelcase@^5.3.1: resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -camelcase@^6.2.0, camelcase@^6.3.0: +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +camelcase@^6.3.0: version "6.3.0" resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== @@ -1163,7 +1135,12 @@ caniuse-lite@^1.0.30001663: resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001669.tgz" integrity sha512-DlWzFDJqstqtIVx1zeSpIMLjunf5SmwOw0N2Ck/QSQdS8PLS4+9HrLaYei4w8BIAL7IB/UEDu889d8vhCTPA0w== -case@1.6.3, case@^1.6.3: +case@^1.6.3: + version "1.6.3" + resolved "https://registry.npmjs.org/case/-/case-1.6.3.tgz" + integrity sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ== + +case@1.6.3: version "1.6.3" resolved "https://registry.npmjs.org/case/-/case-1.6.3.tgz" integrity sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ== @@ -1177,7 +1154,23 @@ chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4, chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.2: +chalk@^4, chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@^4.0.0: + version "4.1.2" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@^4.0.2: version "4.1.2" resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -1256,16 +1249,16 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" - integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== - color-name@~1.1.4: version "1.1.4" resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" @@ -1338,7 +1331,7 @@ concat-stream@^2.0.0: readable-stream "^3.0.2" typedarray "^0.0.6" -constructs@10.3.0, constructs@^10.0.0: +constructs@^10.0.0, constructs@10.3.0: version "10.3.0" resolved "https://registry.npmjs.org/constructs/-/constructs-10.3.0.tgz" integrity sha512-vbK8i3rIb/xwZxSpTjz3SagHn1qq9BChLEfy5Hf6fB3/2eFbrwt2n9kHwQcS0CPTRBesreeAcsJfMq2229FnbQ== @@ -1360,12 +1353,17 @@ conventional-changelog-codemirror@^3.0.0: resolved "https://registry.npmjs.org/conventional-changelog-codemirror/-/conventional-changelog-codemirror-3.0.0.tgz" integrity sha512-wzchZt9HEaAZrenZAUUHMCFcuYzGoZ1wG/kTRMICxsnW5AXohYMRxnyecP9ob42Gvn5TilhC0q66AtTPRSNMfw== -conventional-changelog-config-spec@2.1.0, conventional-changelog-config-spec@^2.1.0: +conventional-changelog-config-spec@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/conventional-changelog-config-spec/-/conventional-changelog-config-spec-2.1.0.tgz" + integrity sha512-IpVePh16EbbB02V+UA+HQnnPIohgXvJRxHcS5+Uwk4AT5LjzCZJm5sp/yqs5C6KZJ1jMsV4paEV13BN1pvDuxQ== + +conventional-changelog-config-spec@2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/conventional-changelog-config-spec/-/conventional-changelog-config-spec-2.1.0.tgz" integrity sha512-IpVePh16EbbB02V+UA+HQnnPIohgXvJRxHcS5+Uwk4AT5LjzCZJm5sp/yqs5C6KZJ1jMsV4paEV13BN1pvDuxQ== -conventional-changelog-conventionalcommits@6.1.0, conventional-changelog-conventionalcommits@^6.0.0: +conventional-changelog-conventionalcommits@^6.0.0, conventional-changelog-conventionalcommits@6.1.0: version "6.1.0" resolved "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-6.1.0.tgz" integrity sha512-3cS3GEtR78zTfMzk0AizXKKIdN4OvSh7ibNz6/DPbhWWQu7LqE/8+/GqSodV+sywUR2gpJAdP/1JFf4XtN7Zpw== @@ -1464,8 +1462,8 @@ conventional-commits-parser@^4.0.0: resolved "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-4.0.0.tgz" integrity sha512-WRv5j1FsVM5FISJkoYMR6tPk07fkKT0UodruX4je86V4owk451yjXAKzKAPOs9l7y59E2viHUS9eQ+dfUA9NSg== dependencies: - JSONStream "^1.3.5" is-text-path "^1.0.1" + JSONStream "^1.3.5" meow "^8.1.2" split2 "^3.2.2" @@ -1487,7 +1485,12 @@ convert-source-map@^2.0.0: resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== -core-util-is@^1.0.3, core-util-is@~1.0.0: +core-util-is@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + +core-util-is@~1.0.0: version "1.0.3" resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== @@ -1549,7 +1552,7 @@ dateformat@^3.0.3: resolved "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== -debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4: +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4, debug@4: version "4.3.7" resolved "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz" integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== @@ -1764,7 +1767,7 @@ fast-json-patch@^3.1.1: resolved "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz" integrity sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ== -fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.1.0: +fast-json-stable-stringify@^2.1.0, fast-json-stable-stringify@2.x: version "2.1.0" resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== @@ -1823,7 +1826,15 @@ find-up@^3.0.0: dependencies: locate-path "^3.0.0" -find-up@^4.0.0, find-up@^4.1.0: +find-up@^4.0.0: + version "4.1.0" + resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-up@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz" integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== @@ -1887,7 +1898,7 @@ fs.realpath@^1.0.0: fsevents@^2.3.2: version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== function-bind@^1.1.2: @@ -1981,7 +1992,18 @@ glob@^7.0.0, glob@^7.1.3, glob@^7.1.4: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^8, glob@^8.1.0: +glob@^8: + version "8.1.0" + resolved "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz" + integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + +glob@^8.0.3, glob@^8.1.0: version "8.1.0" resolved "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz" integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== @@ -2124,7 +2146,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.3, inherits@~2.0.3: +inherits@^2.0.3, inherits@~2.0.3, inherits@2: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -2497,7 +2519,7 @@ jest-resolve-dependencies@^29.7.0: jest-regex-util "^29.6.3" jest-snapshot "^29.7.0" -jest-resolve@^29.7.0: +jest-resolve@*, jest-resolve@^29.7.0: version "29.7.0" resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz" integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== @@ -2641,7 +2663,7 @@ jest-worker@^29.7.0: merge-stream "^2.0.0" supports-color "^8.0.0" -jest@^29.7.0: +jest@^29.0.0, jest@^29.7.0: version "29.7.0" resolved "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz" integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== @@ -2656,7 +2678,15 @@ js-tokens@^4.0.0: resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@3.14.1, js-yaml@^3.13.1: +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@3.14.1: version "3.14.1" resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz" integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== @@ -2752,7 +2782,7 @@ jsii-reflect@^1.103.1, jsii-reflect@^1.104.0: oo-ascii-tree "^1.104.0" yargs "^16.2.0" -jsii-rosetta@~5.5.0: +"jsii-rosetta@^1.104.0 || ~5.2.0 || ~5.3.0 || ~5.4.0 || ~5.5.0", "jsii-rosetta@^1.85.0 || ~5.0.14 || ~5.1.2 || ~5.2.0 || ~5.3.0 || ~5.4.0 || ~5.5.0", jsii-rosetta@~5.5.0: version "5.5.5" resolved "https://registry.npmjs.org/jsii-rosetta/-/jsii-rosetta-5.5.5.tgz" integrity sha512-eXkY5eJck2XPd+xk6f4uRQ1S1d5/on2GO1H1Rr6WkJW7E51FXltpsmPaXzrAtvNd6doBNd6/X1CM4otEt/nnBA== @@ -2841,6 +2871,14 @@ jsonschema@^1.4.1: resolved "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.1.tgz" integrity sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ== +JSONStream@^1.3.5: + version "1.3.5" + resolved "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz" + integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== + dependencies: + jsonparse "^1.2.0" + through ">=2.2.7 <3" + kind-of@^6.0.3: version "6.0.3" resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz" @@ -3015,7 +3053,14 @@ mime-db@1.52.0: resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.12, mime-types@^2.1.35: +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime-types@^2.1.35: version "2.1.35" resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -3055,7 +3100,12 @@ minimist-options@4.1.0: is-plain-obj "^1.1.0" kind-of "^6.0.3" -minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@~1.2.8: +minimist@^1.2.0, minimist@^1.2.5, minimist@~1.2.8: + version "1.2.8" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +minimist@^1.2.3: version "1.2.8" resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== @@ -3095,7 +3145,17 @@ node-releases@^2.0.18: resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz" integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== -normalize-package-data@^2.3.2, normalize-package-data@^2.5.0: +normalize-package-data@^2.3.2: + version "2.5.0" + resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-package-data@^2.5.0: version "2.5.0" resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz" integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== @@ -3158,7 +3218,14 @@ p-limit@^1.1.0: dependencies: p-try "^1.0.0" -p-limit@^2.0.0, p-limit@^2.2.0: +p-limit@^2.0.0: + version "2.3.0" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^2.2.0: version "2.3.0" resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== @@ -3531,12 +3598,12 @@ semver-intersect@^1.5.0: dependencies: semver "^6.3.0" -"semver@2 || 3 || 4 || 5": - version "5.7.2" - resolved "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz" - integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== +semver@^6.3.0: + version "6.3.1" + resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^6.3.0, semver@^6.3.1: +semver@^6.3.1: version "6.3.1" resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== @@ -3546,6 +3613,11 @@ semver@^7.0.0, semver@^7.3.2, semver@^7.3.4, semver@^7.5.3, semver@^7.5.4, semve resolved "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz" integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== +"semver@2 || 3 || 4 || 5": + version "5.7.2" + resolved "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" @@ -3558,7 +3630,16 @@ shebang-regex@^3.0.0: resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shelljs@^0.8.3, shelljs@^0.8.5: +shelljs@^0.8.3: + version "0.8.5" + resolved "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz" + integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== + dependencies: + glob "^7.0.0" + interpret "^1.0.0" + rechoir "^0.6.2" + +shelljs@^0.8.5: version "0.8.5" resolved "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz" integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== @@ -3652,13 +3733,6 @@ spdx-license-list@^6.9.0: resolved "https://registry.npmjs.org/spdx-license-list/-/spdx-license-list-6.9.0.tgz" integrity sha512-L2jl5vc2j6jxWcNCvcVj/BW9A8yGIG02Dw+IUw0ZxDM70f7Ylf5Hq39appV1BI9yxyWQRpq2TQ1qaXvf+yjkqA== -split2@^3.2.2: - version "3.2.2" - resolved "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz" - integrity sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg== - dependencies: - readable-stream "^3.0.0" - split@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/split/-/split-1.0.1.tgz" @@ -3666,6 +3740,13 @@ split@^1.0.1: dependencies: through "2" +split2@^3.2.2: + version "3.2.2" + resolved "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz" + integrity sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg== + dependencies: + readable-stream "^3.0.0" + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" @@ -3699,6 +3780,20 @@ streamroller@^3.1.5: debug "^4.3.4" fs-extra "^8.1.0" +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + string-length@^4.0.1: version "4.0.2" resolved "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz" @@ -3716,20 +3811,6 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" @@ -3820,6 +3901,11 @@ text-extensions@^1.0.0: resolved "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz" integrity sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ== +"through@>=2.2.7 <3", through@2: + version "2.3.8" + resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz" + integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== + through2@^2.0.0: version "2.0.5" resolved "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz" @@ -3828,11 +3914,6 @@ through2@^2.0.0: readable-stream "~2.3.6" xtend "~4.0.1" -through@2, "through@>=2.2.7 <3": - version "2.3.8" - resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz" - integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== - tldts-core@^6.1.52: version "6.1.52" resolved "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.52.tgz" @@ -3896,7 +3977,7 @@ ts-jest@^29.2.5: semver "^7.6.3" yargs-parser "^21.1.1" -ts-node@^10.9.2: +ts-node@^10.9.2, ts-node@>=9.0.0: version "10.9.2" resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz" integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== @@ -3945,7 +4026,7 @@ typedarray@^0.0.6: resolved "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz" integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== -typescript@^5.6.3, typescript@next: +typescript@^5.6.3, typescript@>=2.7, "typescript@>=4.3 <6", typescript@next: version "5.6.3" resolved "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz" integrity sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw== @@ -4108,6 +4189,11 @@ xml@^1.0.1: resolved "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz" integrity sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw== +xmlbuilder@^15.1.1: + version "15.1.1" + resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz" + integrity sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg== + xmlbuilder2@^3.1.1: version "3.1.1" resolved "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-3.1.1.tgz" @@ -4118,11 +4204,6 @@ xmlbuilder2@^3.1.1: "@oozcitak/util" "8.3.8" js-yaml "3.14.1" -xmlbuilder@^15.1.1: - version "15.1.1" - resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz" - integrity sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg== - xmlchars@^2.2.0: version "2.2.0" resolved "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz" @@ -4148,16 +4229,21 @@ yallist@^4.0.0: resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yaml@1.10.2: - version "1.10.2" - resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz" - integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== +yaml@^2.2.2: + version "2.6.0" + resolved "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz" + integrity sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ== -yaml@^2.2.2, yaml@^2.4.1: +yaml@^2.4.1: version "2.6.0" resolved "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz" integrity sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ== +yaml@1.10.2: + version "1.10.2" + resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== + yargs-parser@^20.2.2, yargs-parser@^20.2.3: version "20.2.9" resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz" From b9a57a2118820febf348667c8713731ebec07943 Mon Sep 17 00:00:00 2001 From: Chris McGrath Date: Fri, 29 Nov 2024 19:17:02 +0000 Subject: [PATCH 9/9] Attempt two at fixing the git related commands --- test/function.test.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/test/function.test.ts b/test/function.test.ts index 90aff70..0b1a922 100644 --- a/test/function.test.ts +++ b/test/function.test.ts @@ -816,12 +816,16 @@ async function copyWorkspaceToTemp( const gitPath = path.join(tempDir, workspace); await execAsync('git init', { cwd: gitPath }); await execAsync('git add .', { cwd: gitPath }); - await execAsync( - 'git commit --author "Testy McTestface " -m "commit for test purposes"', - { - cwd: gitPath, - }, - ); + await execAsync('git config user.email "test@example.com"', { + cwd: gitPath, + }); + await execAsync('git config user.name "Testy McTestface"', { + cwd: gitPath, + }); + + await execAsync('git commit -m "commit for test purposes"', { + cwd: gitPath, + }); } return tempDir; }