Skip to content

Commit

Permalink
chore(eslint-plugin): rule against invalid paths (#28828)
Browse files Browse the repository at this point in the history
This is a follow up to #28658, #28772, and #28760. We had to fix multiple places where that file path extended beyond the package itself into other areas of the local repository (that would not be available after packaging). This caused myriad issues at synth time with `file not found` errors.

This PR introduces a linter rule with the following specifications:
- no inefficient paths, i.e. no going backwards multiple times. Ex. `path.join(__dirname, '..', 'folder', '..', 'another-folder')`. This should and can be easily simplified
- no paths that go backwards past a `package.json` file. This should catch the instances we faced next time.

The `yarn lint` command on `aws-cdk-lib` took 51.47s seconds without this new rule and 53.32s seconds with the rule enabled. The difference of ~2 seconds shouldn't be a hindrance in this case but I am happy to look for additional efficiencies in the rule I've written.

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
kaizencc authored Feb 2, 2024
1 parent 610fce1 commit e5b7813
Show file tree
Hide file tree
Showing 72 changed files with 221 additions and 92 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const stack = new Stack(app, 'AuthorizerInteg');
const authHandler = new lambda.Function(stack, 'auth-function', {
runtime: lambda.Runtime.NODEJS_18_X,
handler: 'index.handler',
code: lambda.Code.fromAsset(path.join(__dirname, '../auth-handler')),
code: lambda.Code.fromAsset(path.join(__dirname, '..', 'auth-handler')),
});

const authorizer = new HttpLambdaAuthorizer('LambdaAuthorizer', authHandler, {
Expand All @@ -41,7 +41,7 @@ const httpApiWithDefaultAuthorizer = new HttpApi(stack, 'MyHttpApiWithDefaultAut
const handler = new lambda.Function(stack, 'lambda', {
runtime: lambda.Runtime.NODEJS_18_X,
handler: 'index.handler',
code: lambda.AssetCode.fromAsset(path.join(__dirname, '../integ.lambda.handler')),
code: lambda.AssetCode.fromAsset(path.join(__dirname, '..', 'integ.lambda.handler')),
});

httpApi.addRoutes({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const httpApiWithDefaultAuthorizer = new HttpApi(stack, 'MyHttpApiWithDefaultAut
const handler = new lambda.Function(stack, 'lambda', {
runtime: lambda.Runtime.NODEJS_18_X,
handler: 'index.handler',
code: lambda.AssetCode.fromAsset(path.join(__dirname, '../integ.user-pool.handler')),
code: lambda.AssetCode.fromAsset(path.join(__dirname, '..', 'integ.user-pool.handler')),
});

httpApi.addRoutes({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const api = new appsync.GraphqlApi(stack, 'LambdaAPI', {
});

const func = new lambda.Function(stack, 'func', {
code: lambda.Code.fromAsset(path.join(__dirname, 'verify/lambda-tutorial')),
code: lambda.Code.fromAsset(path.join(__dirname, 'verify', 'lambda-tutorial')),
handler: 'lambda-tutorial.handler',
runtime: STANDARD_NODEJS_RUNTIME,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const integ = new IntegTest(app, 'JsResolverIntegTest', { testCases: [stack] });
* Handler that calls our api with an `addTest` Mutation
*/
const invoke = new lambda.Function(stack, 'InvokeApi', {
code: lambda.Code.fromAsset(path.join(__dirname, 'integ-assets/js-resolver-assertion')),
code: lambda.Code.fromAsset(path.join(__dirname, 'integ-assets', 'js-resolver-assertion')),
handler: 'index.handler',
runtime: lambda.Runtime.NODEJS_18_X,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class GraphQLApiLambdaAuthStack extends cdk.Stack {

const func = new lambda.Function(this, 'func', {
code: lambda.Code.fromAsset(
path.join(__dirname, 'verify/lambda-tutorial'),
path.join(__dirname, 'verify', 'lambda-tutorial'),
),
handler: 'lambda-tutorial.handler',
runtime: STANDARD_NODEJS_RUNTIME,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const bucket = new s3.Bucket(stack, 'PipelineBucket', {
});

const artifact = new deploy.BucketDeployment(stack, 'DeployApp', {
sources: [deploy.Source.asset(path.join(__dirname, 'assets/nodejs.zip'))],
sources: [deploy.Source.asset(path.join(__dirname, 'assets', 'nodejs.zip'))],
destinationBucket: bucket,
extract: false,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,13 @@ const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDefinition', {
// deploy an envfile to S3 and delete when the bucket is deleted
const envFileDeployment = new s3deployment.BucketDeployment(stack, 'EnvFileDeployment', {
destinationBucket: bucket,
sources: [s3deployment.Source.asset(path.join(__dirname, '../demo-envfiles'))],
sources: [s3deployment.Source.asset(path.join(__dirname, '..', 'demo-envfiles'))],
});

// define container with envfiles - one from local disk and another from S3
const containerDefinition = new ecs.ContainerDefinition(stack, 'Container', {
environmentFiles: [
ecs.EnvironmentFile.fromAsset(path.join(__dirname, '../demo-envfiles/test-envfile.env')),
ecs.EnvironmentFile.fromAsset(path.join(__dirname, '..', 'demo-envfiles', 'test-envfile.env')),
ecs.EnvironmentFile.fromBucket(bucket, 'test-envfile.env'),
],
image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class TestAssetProductStack1 extends servicecatalog.ProductStack {

new lambda.Function(this, 'HelloHandler', {
runtime: lambda.Runtime.PYTHON_3_9,
code: lambda.Code.fromAsset(path.join(__dirname, './assets')),
code: lambda.Code.fromAsset(path.join(__dirname, 'assets')),
handler: 'index.handler',
});
}
Expand All @@ -76,7 +76,7 @@ class TestAssetProductStack2 extends servicecatalog.ProductStack {

new lambda.Function(this, 'HelloHandler2', {
runtime: lambda.Runtime.PYTHON_3_9,
code: lambda.Code.fromAsset(path.join(__dirname, './assetsv2')),
code: lambda.Code.fromAsset(path.join(__dirname, 'assetsv2')),
handler: 'index.handler',
});
}
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/app-staging-synthesizer-alpha/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ import { Asset } from 'aws-cdk-lib/aws-s3-assets';
declare const stack: Stack;
const asset = new Asset(stack, 'deploy-time-asset', {
deployTime: true,
path: path.join(__dirname, './deploy-time-asset'),
path: path.join(__dirname, 'deploy-time-asset'),
});
```

Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-amplify-alpha/test/branch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ test('with env vars', () => {
test('with asset deployment', () => {
// WHEN
const asset = new Asset(app, 'SampleAsset', {
path: path.join(__dirname, './test-asset'),
path: path.join(__dirname, 'test-asset'),
});
app.addBranch('dev', { asset });

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class TestStack extends Stack {
super(scope, id, props);

const asset = new Asset(this, 'SampleAsset', {
path: path.join(__dirname, './test-asset'),
path: path.join(__dirname, 'test-asset'),
});

const amplifyApp = new amplify.App(this, 'App', {});
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-apprunner-alpha/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ You can specify whether to enable continuous integration from the source reposit
import * as assets from 'aws-cdk-lib/aws-ecr-assets';

const imageAsset = new assets.DockerImageAsset(this, 'ImageAssets', {
directory: path.join(__dirname, './docker.assets'),
directory: path.join(__dirname, 'docker.assets'),
});
new apprunner.Service(this, 'Service', {
source: apprunner.Source.fromAsset({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const stack = new cdk.Stack(app, 'integ-apprunner');

// Scenario 3: Create the service from local code assets
const imageAsset = new assets.DockerImageAsset(stack, 'ImageAssets', {
directory: path.join(__dirname, './docker.assets'),
directory: path.join(__dirname, 'docker.assets'),
});
const service3 = new Service(stack, 'Service3', {
source: Source.fromAsset({
Expand Down
6 changes: 3 additions & 3 deletions packages/@aws-cdk/aws-apprunner-alpha/test/service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,7 @@ test('create a service with local assets(image repository type: ECR)', () => {
const stack = new cdk.Stack(app, 'demo-stack');
// WHEN
const dockerAsset = new ecr_assets.DockerImageAsset(stack, 'Assets', {
directory: path.join(__dirname, './docker.assets'),
directory: path.join(__dirname, 'docker.assets'),
});
new apprunner.Service(stack, 'DemoService', {
source: apprunner.Source.fromAsset({
Expand Down Expand Up @@ -864,7 +864,7 @@ test('custom IAM access role and instance role are allowed', () => {
const stack = new cdk.Stack(app, 'demo-stack');
// WHEN
const dockerAsset = new ecr_assets.DockerImageAsset(stack, 'Assets', {
directory: path.join(__dirname, './docker.assets'),
directory: path.join(__dirname, 'docker.assets'),
});
new apprunner.Service(stack, 'DemoService', {
source: apprunner.Source.fromAsset({
Expand Down Expand Up @@ -1177,7 +1177,7 @@ test('autoDeploymentsEnabled flag is set true', () => {
const stack = new cdk.Stack(app, 'demo-stack');
// WHEN
const dockerAsset = new ecr_assets.DockerImageAsset(stack, 'Assets', {
directory: path.join(__dirname, './docker.assets'),
directory: path.join(__dirname, 'docker.assets'),
});
new apprunner.Service(stack, 'DemoService', {
source: apprunner.Source.fromAsset({
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-gamelift-alpha/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ match is made after 30 seconds, gradually relax the skill requirements.
```ts
new gamelift.MatchmakingRuleSet(this, 'RuleSet', {
matchmakingRuleSetName: 'my-test-ruleset',
content: gamelift.RuleSetContent.fromJsonFile(path.join(__dirname, 'my-ruleset/ruleset.json')),
content: gamelift.RuleSetContent.fromJsonFile(path.join(__dirname, 'my-ruleset', 'ruleset.json')),
});
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class TestStack extends cdk.Stack {

const ruleSet = new gamelift.MatchmakingRuleSet(this, 'MatchmakingRuleSet', {
matchmakingRuleSetName: 'my-test-ruleset',
content: gamelift.RuleSetContent.fromJsonFile(path.join(__dirname, 'my-ruleset/ruleset.json')),
content: gamelift.RuleSetContent.fromJsonFile(path.join(__dirname, 'my-ruleset', 'ruleset.json')),
});

new CfnOutput(this, 'MatchmakingRuleSetArn', { value: ruleSet.matchmakingRuleSetArn });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class TestStack extends cdk.Stack {

const ruleSet = new gamelift.MatchmakingRuleSet(this, 'QueuedMatchmakingConfiguration', {
matchmakingRuleSetName: 'my-test-ruleset',
content: gamelift.RuleSetContent.fromJsonFile(path.join(__dirname, 'my-ruleset/ruleset.json')),
content: gamelift.RuleSetContent.fromJsonFile(path.join(__dirname, 'my-ruleset', 'ruleset.json')),
});

const build = new gamelift.Build(this, 'Build', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class TestStack extends cdk.Stack {

const ruleSet = new gamelift.MatchmakingRuleSet(this, 'StandaloneMatchmakingConfiguration', {
matchmakingRuleSetName: 'my-test-ruleset',
content: gamelift.RuleSetContent.fromJsonFile(path.join(__dirname, 'my-ruleset/ruleset.json')),
content: gamelift.RuleSetContent.fromJsonFile(path.join(__dirname, 'my-ruleset', 'ruleset.json')),
});

const matchmakingConfiguration = new gamelift.StandaloneMatchmakingConfiguration(this, 'MyStandaloneMatchmakingConfiguration', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ describe('MatchmakingRuleSetBody', () => {

describe('gamelift.MatchmakingRuleSetBody.fromJsonFile', () => {
test('new RuleSetBody from Json file', () => {
const ruleSet = gamelift.RuleSetContent.fromJsonFile(path.join(__dirname, 'my-ruleset/ruleset.json'));
const ruleSet = gamelift.RuleSetContent.fromJsonFile(path.join(__dirname, 'my-ruleset', 'ruleset.json'));
const content = ruleSet.bind(stack);
const result = JSON.parse(fs.readFileSync(path.join(__dirname, 'my-ruleset/ruleset.json')).toString());
const result = JSON.parse(fs.readFileSync(path.join(__dirname, 'my-ruleset', 'ruleset.json')).toString());
expect(content.ruleSetBody).toEqual(JSON.stringify(result));
});

test('fails if file not exist', () => {
const content = path.join(__dirname, 'my-ruleset/file-not-exist.json');
const content = path.join(__dirname, 'my-ruleset', 'file-not-exist.json');
expect(() => gamelift.RuleSetContent.fromJsonFile(content))
.toThrow(`RuleSet path does not exist, please verify it, actual ${content}`);
});
Expand Down
6 changes: 3 additions & 3 deletions packages/@aws-cdk/aws-glue-alpha/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ new glue.Job(this, 'PythonSparkStreamingJob', {
executable: glue.JobExecutable.pythonStreaming({
glueVersion: glue.GlueVersion.V4_0,
pythonVersion: glue.PythonVersion.THREE,
script: glue.Code.fromAsset(path.join(__dirname, 'job-script/hello_world.py')),
script: glue.Code.fromAsset(path.join(__dirname, 'job-script', 'hello_world.py')),
}),
description: 'an example Python Streaming job',
});
Expand Down Expand Up @@ -97,7 +97,7 @@ new glue.Job(this, 'RayJob', {
glueVersion: glue.GlueVersion.V4_0,
pythonVersion: glue.PythonVersion.THREE_NINE,
runtime: glue.Runtime.RAY_TWO_FOUR,
script: glue.Code.fromAsset(path.join(__dirname, 'job-script/hello_world.py')),
script: glue.Code.fromAsset(path.join(__dirname, 'job-script', 'hello_world.py')),
}),
workerType: glue.WorkerType.Z_2X,
workerCount: 2,
Expand All @@ -118,7 +118,7 @@ new glue.Job(this, 'EnableSparkUI', {
executable: glue.JobExecutable.pythonEtl({
glueVersion: glue.GlueVersion.V3_0,
pythonVersion: glue.PythonVersion.THREE,
script: glue.Code.fromAsset(path.join(__dirname, 'job-script/hello_world.py')),
script: glue.Code.fromAsset(path.join(__dirname, 'job-script', 'hello_world.py')),
}),
});
```
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-glue-alpha/test/code.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ describe('Code', () => {
});

describe('.fromAsset()', () => {
const filePath = path.join(__dirname, 'job-script/hello_world.py');
const filePath = path.join(__dirname, 'job-script', 'hello_world.py');
const directoryPath = path.join(__dirname, 'job-script');

beforeEach(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const app = new cdk.App();

const stack = new cdk.Stack(app, 'aws-glue-job-python-shell');

const script = glue.Code.fromAsset(path.join(__dirname, 'job-script/hello_world.py'));
const script = glue.Code.fromAsset(path.join(__dirname, 'job-script', 'hello_world.py'));

new glue.Job(stack, 'ShellJob', {
jobName: 'ShellJob',
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-glue-alpha/test/integ.job.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const app = new cdk.App();

const stack = new cdk.Stack(app, 'aws-glue-job');

const script = glue.Code.fromAsset(path.join(__dirname, 'job-script/hello_world.py'));
const script = glue.Code.fromAsset(path.join(__dirname, 'job-script', 'hello_world.py'));

[glue.GlueVersion.V2_0, glue.GlueVersion.V3_0, glue.GlueVersion.V4_0].forEach((glueVersion) => {
const etlJob = new glue.Job(stack, 'EtlJob' + glueVersion.name, {
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-lambda-go-alpha/lib/bundling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ export class Bundling implements cdk.BundlingOptions {
// Docker bundling
const shouldBuildImage = props.forcedDockerBundling || !Bundling.runsLocally;
this.image = shouldBuildImage
? props.dockerImage ?? cdk.DockerImage.fromBuild(path.join(__dirname, '../lib'), {
? props.dockerImage ?? cdk.DockerImage.fromBuild(path.join(__dirname, '..', 'lib'), {
buildArgs: {
...props.buildArgs ?? {},
IMAGE: Runtime.GO_1_X.bundlingImage.image, // always use the GO_1_X build image
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-lambda-go-alpha/test/docker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as path from 'path';

const docker = process.env.CDK_DOCKER ?? 'docker';
beforeAll(() => {
spawnSync(docker, ['build', '-t', 'golang', path.join(__dirname, '../lib')]);
spawnSync(docker, ['build', '-t', 'golang', path.join(__dirname, '..', 'lib')]);
});

test('golang is available', async () => {
Expand Down
22 changes: 11 additions & 11 deletions packages/@aws-cdk/aws-lambda-go-alpha/test/function.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ beforeEach(() => {
test('GoFunction with defaults', () => {
// WHEN
new GoFunction(stack, 'handler', {
entry: path.join(__dirname, 'lambda-handler-vendor/cmd/api'),
entry: path.join(__dirname, 'lambda-handler-vendor', 'cmd', 'api'),
});

expect(Bundling.bundle).toHaveBeenCalledWith(expect.objectContaining({
Expand All @@ -43,7 +43,7 @@ test('GoFunction with defaults', () => {
test('GoFunction with using provided runtime', () => {
// WHEN
new GoFunction(stack, 'handler', {
entry: path.join(__dirname, 'lambda-handler-vendor/cmd/api'),
entry: path.join(__dirname, 'lambda-handler-vendor', 'cmd', 'api'),
runtime: Runtime.PROVIDED,
});

Expand All @@ -60,7 +60,7 @@ test('GoFunction with using provided runtime', () => {
test('GoFunction with using golang runtime', () => {
// WHEN
new GoFunction(stack, 'handler', {
entry: path.join(__dirname, 'lambda-handler-vendor/cmd/api'),
entry: path.join(__dirname, 'lambda-handler-vendor', 'cmd', 'api'),
runtime: Runtime.GO_1_X,
});

Expand All @@ -77,7 +77,7 @@ test('GoFunction with using golang runtime', () => {
test('GoFunction with container env vars', () => {
// WHEN
new GoFunction(stack, 'handler', {
entry: path.join(__dirname, 'lambda-handler-vendor/cmd/api'),
entry: path.join(__dirname, 'lambda-handler-vendor', 'cmd', 'api'),
bundling: {
environment: {
KEY: 'VALUE',
Expand All @@ -94,15 +94,15 @@ test('GoFunction with container env vars', () => {

test('throws with the wrong runtime family', () => {
expect(() => new GoFunction(stack, 'handler', {
entry: path.join(__dirname, 'lambda-handler-vendor/cmd/api'),
entry: path.join(__dirname, 'lambda-handler-vendor', 'cmd', 'api'),
runtime: Runtime.PYTHON_3_8,
})).toThrow(/Only `go` and `provided` runtimes are supported/);
});

test('resolves entry to an absolute path', () => {
// WHEN
new GoFunction(stack, 'fn', {
entry: path.join(__dirname, 'lambda-handler-vendor/cmd/api/main.go'),
entry: path.join(__dirname, 'lambda-handler-vendor', 'cmd', 'api', 'main.go'),
});

expect(Bundling.bundle).toHaveBeenCalledWith(expect.objectContaining({
Expand All @@ -112,21 +112,21 @@ test('resolves entry to an absolute path', () => {

test('throws with no existing go.mod file', () => {
expect(() => new GoFunction(stack, 'handler', {
entry: path.join(__dirname, 'lambda-handler-vendor/cmd/api'),
entry: path.join(__dirname, 'lambda-handler-vendor', 'cmd', 'api'),
moduleDir: '/does/not/exist/go.mod',
})).toThrow(/go.mod file at \/does\/not\/exist\/go.mod doesn't exist/);
});

test('throws with incorrect moduleDir file', () => {
expect(() => new GoFunction(stack, 'handler', {
entry: path.join(__dirname, 'lambda-handler-vendor/cmd/api'),
entry: path.join(__dirname, 'lambda-handler-vendor', 'cmd', 'api'),
moduleDir: '/does/not/exist.mod',
})).toThrow(/moduleDir is specifying a file that is not go.mod/);
});

test('custom moduleDir can be used', () => {
new GoFunction(stack, 'handler', {
entry: path.join(__dirname, 'lambda-handler-vendor/cmd/api'),
entry: path.join(__dirname, 'lambda-handler-vendor', 'cmd', 'api'),
moduleDir: path.join(__dirname, 'lambda-handler-vendor'),
});

Expand All @@ -137,8 +137,8 @@ test('custom moduleDir can be used', () => {

test('custom moduleDir with file path can be used', () => {
new GoFunction(stack, 'handler', {
entry: path.join(__dirname, 'lambda-handler-vendor/cmd/api'),
moduleDir: path.join(__dirname, 'lambda-handler-vendor/go.mod'),
entry: path.join(__dirname, 'lambda-handler-vendor', 'cmd', 'api'),
moduleDir: path.join(__dirname, 'lambda-handler-vendor', 'go.mod'),
});

Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class TestStack extends Stack {
super(scope, id, props);

new lambda.GoFunction(this, 'go-handler-docker', {
entry: path.join(__dirname, 'lambda-handler-vendor/cmd/api'),
entry: path.join(__dirname, 'lambda-handler-vendor', 'cmd', 'api'),
runtime: Runtime.PROVIDED_AL2023,
bundling: {
forcedDockerBundling: true,
Expand Down
Loading

0 comments on commit e5b7813

Please sign in to comment.