From ddd5a9eba6d6df6373b1c492e143aa7428bfaab9 Mon Sep 17 00:00:00 2001 From: Mikhail Shilkov Date: Fri, 4 Dec 2020 17:26:17 +0100 Subject: [PATCH] Azure Synapse examples (#853) --- azure-nextgen-cs-synapse/.gitignore | 37 ++++++ azure-nextgen-cs-synapse/MyStack.cs | 133 ++++++++++++++++++++++ azure-nextgen-cs-synapse/Program.cs | 8 ++ azure-nextgen-cs-synapse/Pulumi.yaml | 3 + azure-nextgen-cs-synapse/README.md | 48 ++++++++ azure-nextgen-cs-synapse/Synapse.csproj | 15 +++ azure-nextgen-py-synapse/.gitignore | 2 + azure-nextgen-py-synapse/Pulumi.yaml | 3 + azure-nextgen-py-synapse/README.md | 58 ++++++++++ azure-nextgen-py-synapse/__main__.py | 103 +++++++++++++++++ azure-nextgen-py-synapse/requirements.txt | 3 + azure-nextgen-ts-synapse/Pulumi.yaml | 3 + azure-nextgen-ts-synapse/README.md | 54 +++++++++ azure-nextgen-ts-synapse/index.ts | 113 ++++++++++++++++++ azure-nextgen-ts-synapse/package.json | 12 ++ azure-nextgen-ts-synapse/tsconfig.json | 19 ++++ 16 files changed, 614 insertions(+) create mode 100644 azure-nextgen-cs-synapse/.gitignore create mode 100644 azure-nextgen-cs-synapse/MyStack.cs create mode 100644 azure-nextgen-cs-synapse/Program.cs create mode 100644 azure-nextgen-cs-synapse/Pulumi.yaml create mode 100644 azure-nextgen-cs-synapse/README.md create mode 100644 azure-nextgen-cs-synapse/Synapse.csproj create mode 100644 azure-nextgen-py-synapse/.gitignore create mode 100644 azure-nextgen-py-synapse/Pulumi.yaml create mode 100644 azure-nextgen-py-synapse/README.md create mode 100644 azure-nextgen-py-synapse/__main__.py create mode 100644 azure-nextgen-py-synapse/requirements.txt create mode 100644 azure-nextgen-ts-synapse/Pulumi.yaml create mode 100644 azure-nextgen-ts-synapse/README.md create mode 100644 azure-nextgen-ts-synapse/index.ts create mode 100644 azure-nextgen-ts-synapse/package.json create mode 100644 azure-nextgen-ts-synapse/tsconfig.json diff --git a/azure-nextgen-cs-synapse/.gitignore b/azure-nextgen-cs-synapse/.gitignore new file mode 100644 index 000000000..ee5ec04c3 --- /dev/null +++ b/azure-nextgen-cs-synapse/.gitignore @@ -0,0 +1,37 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ diff --git a/azure-nextgen-cs-synapse/MyStack.cs b/azure-nextgen-cs-synapse/MyStack.cs new file mode 100644 index 000000000..8f311a933 --- /dev/null +++ b/azure-nextgen-cs-synapse/MyStack.cs @@ -0,0 +1,133 @@ +// Copyright 2016-2020, Pulumi Corporation. All rights reserved. + +using Pulumi; +using Pulumi.AzureNextGen.Authorization.V20200401Preview; +using Pulumi.AzureNextGen.Resources.Latest; +using Pulumi.AzureNextGen.Storage.Latest; +using Pulumi.AzureNextGen.Synapse.V20190601Preview; +using Pulumi.AzureNextGen.Synapse.V20190601Preview.Inputs; +using Pulumi.Random; +using SkuArgs = Pulumi.AzureNextGen.Storage.Latest.Inputs.SkuArgs; + +class MyStack : Stack +{ + public MyStack() + { + var config = new Pulumi.Config(); + var location = config.Get("location") ?? "WestUS"; + + var resourceGroup = new ResourceGroup("resourceGroup", new ResourceGroupArgs + { + ResourceGroupName = "synapse-rg", + Location = location + }); + + var storageAccount = new StorageAccount("storageAccount", new StorageAccountArgs + { + ResourceGroupName = resourceGroup.Name, + Location = resourceGroup.Location, + AccountName = "synapsesa", + AccessTier = "Hot", + EnableHttpsTrafficOnly = true, + IsHnsEnabled = true, + Kind = "StorageV2", + Sku = new SkuArgs + { + Name = "Standard_RAGRS" + }, + }); + var dataLakeStorageAccountUrl = Output.Format($"https://{storageAccount.Name}.dfs.core.windows.net"); + + var users = new BlobContainer("users", new BlobContainerArgs + { + ResourceGroupName = resourceGroup.Name, + AccountName = storageAccount.Name, + ContainerName = "users", + PublicAccess = "None" + }); + + var workspace = new Workspace("workspace", new WorkspaceArgs + { + ResourceGroupName = resourceGroup.Name, + Location = resourceGroup.Location, + WorkspaceName = "my-workspace", + DefaultDataLakeStorage = new DataLakeStorageAccountDetailsArgs + { + AccountUrl = dataLakeStorageAccountUrl, + Filesystem = "users" + }, + Identity = new ManagedIdentityArgs + { + Type = "SystemAssigned" + }, + SqlAdministratorLogin = "sqladminuser", + SqlAdministratorLoginPassword = "YourStrongPassword" + }); + + var allowAll = new IpFirewallRule("allowAll", new IpFirewallRuleArgs + { + ResourceGroupName = resourceGroup.Name, + WorkspaceName = workspace.Name, + RuleName = "allowAll", + EndIpAddress = "255.255.255.255", + StartIpAddress = "0.0.0.0" + }); + + var subscriptionId = resourceGroup.Id.Apply(id => id.Split('/')[2]); + var roleDefinitionId = $"/subscriptions/{subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/ba92f5b4-2d11-453d-a403-e96b0029c9fe"; + + var storageAccess = new RoleAssignment("storageAccess", new RoleAssignmentArgs + { + RoleAssignmentName = new RandomUuid("roleName").Result, + Scope = storageAccount.Id, + PrincipalId = workspace.Identity.Apply(identity => identity.PrincipalId).Apply(v => v ?? ""), + PrincipalType = "ServicePrincipal", + RoleDefinitionId = roleDefinitionId + }); + var userAccess = new RoleAssignment("userAccess", new RoleAssignmentArgs + { + RoleAssignmentName = new RandomUuid("userRoleName").Result, + Scope = storageAccount.Id, + PrincipalId = config.Require("userObjectId"), + PrincipalType = "User", + RoleDefinitionId = roleDefinitionId + }); + + var sqlPool = new SqlPool("sqlPool", new SqlPoolArgs + { + ResourceGroupName = resourceGroup.Name, + Location = resourceGroup.Location, + WorkspaceName = workspace.Name, + SqlPoolName = "SQLPOOL1", + Collation = "SQL_Latin1_General_CP1_CI_AS", + CreateMode = "Default", + Sku = new Pulumi.AzureNextGen.Synapse.V20190601Preview.Inputs.SkuArgs + { + Name = "DW100c" + }, + }); + + var sparkPool = new BigDataPool("sparkPool", new BigDataPoolArgs + { + ResourceGroupName = resourceGroup.Name, + Location = resourceGroup.Location, + WorkspaceName = workspace.Name, + BigDataPoolName = "Spark1", + AutoPause = new AutoPausePropertiesArgs + { + DelayInMinutes = 15, + Enabled = true, + }, + AutoScale = new AutoScalePropertiesArgs + { + Enabled = true, + MaxNodeCount = 3, + MinNodeCount = 3, + }, + NodeCount = 3, + NodeSize = "Small", + NodeSizeFamily = "MemoryOptimized", + SparkVersion = "2.4" + }); + } +} diff --git a/azure-nextgen-cs-synapse/Program.cs b/azure-nextgen-cs-synapse/Program.cs new file mode 100644 index 000000000..1ee1c12fe --- /dev/null +++ b/azure-nextgen-cs-synapse/Program.cs @@ -0,0 +1,8 @@ +// Copyright 2016-2020, Pulumi Corporation. All rights reserved. +using System.Threading.Tasks; +using Pulumi; + +class Program +{ + static Task Main() => Deployment.RunAsync(); +} diff --git a/azure-nextgen-cs-synapse/Pulumi.yaml b/azure-nextgen-cs-synapse/Pulumi.yaml new file mode 100644 index 000000000..63e09ae84 --- /dev/null +++ b/azure-nextgen-cs-synapse/Pulumi.yaml @@ -0,0 +1,3 @@ +name: azure-nextgen-cs-synapse +runtime: dotnet +description: Creates Azure Synapse workspace with SQL and Spark pools diff --git a/azure-nextgen-cs-synapse/README.md b/azure-nextgen-cs-synapse/README.md new file mode 100644 index 000000000..0ca695f63 --- /dev/null +++ b/azure-nextgen-cs-synapse/README.md @@ -0,0 +1,48 @@ +[![Deploy](https://get.pulumi.com/new/button.svg)](https://app.pulumi.com/new) + +# Azure Synapse Workspace and Pools + +Starting point for enterprise analytics solutions based on Azure Synapse. + +## Running the App + +1. Create a new stack: + + ``` + $ pulumi stack init dev + ``` + +1. Login to Azure CLI (you will be prompted to do this during deployment if you forget this step): + + ``` + $ az login + ``` + +1. Set the Azure region location to use: + + ``` + $ pulumi config set location westus2 + ``` + +1. Set the user ID to grant access to (e.g., your current user): + + ``` + $ pulumi config set userObjectId $(az ad signed-in-user show --query=objectId | tr -d '"') + ``` + +1. Run `pulumi up` to preview and deploy changes: + + ```bash + $ pulumi up + Previewing changes: + ... + + Performing changes: + ... + Resources: + + 13 created + + Duration: 10m53s + ``` + +1. Navigate to https://web.azuresynapse.net and sign in to your new workspace. diff --git a/azure-nextgen-cs-synapse/Synapse.csproj b/azure-nextgen-cs-synapse/Synapse.csproj new file mode 100644 index 000000000..68bc3b229 --- /dev/null +++ b/azure-nextgen-cs-synapse/Synapse.csproj @@ -0,0 +1,15 @@ + + + + Exe + netcoreapp3.1 + enable + + + + + + + + + diff --git a/azure-nextgen-py-synapse/.gitignore b/azure-nextgen-py-synapse/.gitignore new file mode 100644 index 000000000..a3807e5bd --- /dev/null +++ b/azure-nextgen-py-synapse/.gitignore @@ -0,0 +1,2 @@ +*.pyc +venv/ diff --git a/azure-nextgen-py-synapse/Pulumi.yaml b/azure-nextgen-py-synapse/Pulumi.yaml new file mode 100644 index 000000000..820ce5c93 --- /dev/null +++ b/azure-nextgen-py-synapse/Pulumi.yaml @@ -0,0 +1,3 @@ +name: azure-nextgen-py-synapse +runtime: python +description: Creates Azure Synapse workspace with SQL and Spark pools diff --git a/azure-nextgen-py-synapse/README.md b/azure-nextgen-py-synapse/README.md new file mode 100644 index 000000000..123f019c3 --- /dev/null +++ b/azure-nextgen-py-synapse/README.md @@ -0,0 +1,58 @@ +[![Deploy](https://get.pulumi.com/new/button.svg)](https://app.pulumi.com/new) + +# Azure Synapse Workspace and Pools + +Starting point for enterprise analytics solutions based on Azure Synapse. + +## Running the App + +1. Create a new stack: + + ```bash + $ pulumi stack init dev + ``` + +1. Login to Azure CLI (you will be prompted to do this during deployment if you forget this step): + + ```bash + $ az login + ``` + +1. Create a Python virtualenv, activate it, and install dependencies: + + This installs the dependent packages [needed](https://www.pulumi.com/docs/intro/concepts/how-pulumi-works/) for our Pulumi program. + + ```bash + $ python3 -m venv venv + $ source venv/bin/activate + $ pip3 install -r requirements.txt + ``` + +1. Set the Azure region location to use: + + ``` + $ pulumi config set location westus2 + ``` + +1. Set the user ID to grant access to (e.g., your current user): + + ``` + $ pulumi config set userObjectId $(az ad signed-in-user show --query=objectId | tr -d '"') + ``` + +1. Run `pulumi up` to preview and deploy changes: + + ```bash + $ pulumi up + Previewing changes: + ... + + Performing changes: + ... + Resources: + + 13 created + + Duration: 10m53s + ``` + +1. Navigate to https://web.azuresynapse.net and sign in to your new workspace. diff --git a/azure-nextgen-py-synapse/__main__.py b/azure-nextgen-py-synapse/__main__.py new file mode 100644 index 000000000..d05692519 --- /dev/null +++ b/azure-nextgen-py-synapse/__main__.py @@ -0,0 +1,103 @@ +# Copyright 2016-2020, Pulumi Corporation. All rights reserved. + +import pulumi +from pulumi_azure_nextgen.authorization import v20200401preview as authorization +from pulumi_azure_nextgen.storage import latest as storage +from pulumi_azure_nextgen.synapse import v20190601preview as synapse +from pulumi_azure_nextgen.resources import latest as resources +import pulumi_random as random + +config = pulumi.Config() +location = config.get("location") or "WestUS" + +resource_group = resources.ResourceGroup("resourceGroup", + resource_group_name="synapse-rg", + location=location) + +storage_account = storage.StorageAccount("storageAccount", + resource_group_name=resource_group.name, + location=resource_group.location, + account_name="synapsesa", + access_tier="Hot", + enable_https_traffic_only=True, + is_hns_enabled=True, + kind="StorageV2", + sku=storage.SkuArgs( + name="Standard_RAGRS", + )) + +data_lake_storage_account_url = storage_account.name.apply(lambda name: f"https://{name}.dfs.core.windows.net") + +users = storage.BlobContainer("users", + resource_group_name=resource_group.name, + account_name=storage_account.name, + container_name="users", + public_access="None") + +workspace = synapse.Workspace("workspace", + resource_group_name=resource_group.name, + location=resource_group.location, + workspace_name="my-workspace", + default_data_lake_storage=synapse.DataLakeStorageAccountDetailsArgs( + account_url=data_lake_storage_account_url, + filesystem="users", + ), + identity=synapse.ManagedIdentityArgs( + type="SystemAssigned", + ), + sql_administrator_login="sqladminuser", + sql_administrator_login_password=random.RandomPassword("workspacePwd", length=12).result) + +allow_all = synapse.IpFirewallRule("allowAll", + resource_group_name=resource_group.name, + workspace_name=workspace.name, + rule_name="allowAll", + end_ip_address="255.255.255.255", + start_ip_address="0.0.0.0") + +subscription_id = resource_group.id.apply(lambda id: id.split('/')[2]) +role_definition_id = subscription_id.apply(lambda id: f"/subscriptions/{id}/providers/Microsoft.Authorization/roleDefinitions/ba92f5b4-2d11-453d-a403-e96b0029c9fe") + +storage_access = authorization.RoleAssignment("storageAccess", + role_assignment_name=random.RandomUuid("roleName").result, + scope=storage_account.id, + principal_id=workspace.identity.principal_id.apply(lambda v: v or ""), + principal_type="ServicePrincipal", + role_definition_id=role_definition_id) + +user_access = authorization.RoleAssignment("userAccess", + role_assignment_name=random.RandomUuid("userRoleName").result, + scope=storage_account.id, + principal_id=config.get("userObjectId"), + principal_type="User", + role_definition_id=role_definition_id) + +sql_pool = synapse.SqlPool("sqlPool", + resource_group_name=resource_group.name, + location=resource_group.location, + workspace_name=workspace.name, + sql_pool_name="SQLPOOL1", + collation="SQL_Latin1_General_CP1_CI_AS", + create_mode="Default", + sku=synapse.SkuArgs( + name="DW100c", + )) + +spark_pool = synapse.BigDataPool("sparkPool", + resource_group_name=resource_group.name, + location=resource_group.location, + workspace_name=workspace.name, + big_data_pool_name="Spark1", + auto_pause=synapse.AutoPausePropertiesArgs( + delay_in_minutes=15, + enabled=True, + ), + auto_scale=synapse.AutoScalePropertiesArgs( + enabled=True, + max_node_count=3, + min_node_count=3, + ), + node_count=3, + node_size="Small", + node_size_family="MemoryOptimized", + spark_version="2.4") diff --git a/azure-nextgen-py-synapse/requirements.txt b/azure-nextgen-py-synapse/requirements.txt new file mode 100644 index 000000000..7e00ae1ee --- /dev/null +++ b/azure-nextgen-py-synapse/requirements.txt @@ -0,0 +1,3 @@ +pulumi>=2.0.0,<3.0.0 +pulumi-random>=2.0.0 +pulumi-azure-nextgen>=0.2.8 diff --git a/azure-nextgen-ts-synapse/Pulumi.yaml b/azure-nextgen-ts-synapse/Pulumi.yaml new file mode 100644 index 000000000..6e9ada66e --- /dev/null +++ b/azure-nextgen-ts-synapse/Pulumi.yaml @@ -0,0 +1,3 @@ +name: azure-nextgen-ts-synapse +runtime: nodejs +description: Creates Azure Synapse workspace with SQL and Spark pools diff --git a/azure-nextgen-ts-synapse/README.md b/azure-nextgen-ts-synapse/README.md new file mode 100644 index 000000000..226010dba --- /dev/null +++ b/azure-nextgen-ts-synapse/README.md @@ -0,0 +1,54 @@ +[![Deploy](https://get.pulumi.com/new/button.svg)](https://app.pulumi.com/new) + +# Azure Synapse Workspace and Pools + +Starting point for enterprise analytics solutions based on Azure Synapse. + +## Running the App + +1. Create a new stack: + + ``` + $ pulumi stack init dev + ``` + +1. Login to Azure CLI (you will be prompted to do this during deployment if you forget this step): + + ``` + $ az login + ``` + +1. Restore NPM dependencies: + + ``` + $ npm install + ``` + +1. Set the Azure region location to use: + + ``` + $ pulumi config set location westus2 + ``` + +1. Set the user ID to grant access to (e.g., your current user): + + ``` + $ pulumi config set userObjectId $(az ad signed-in-user show --query=objectId | tr -d '"') + ``` + +1. Run `pulumi up` to preview and deploy changes: + + ```bash + $ pulumi up + Previewing changes: + ... + + Performing changes: + ... + Resources: + + 13 created + + Duration: 10m53s + ``` + +1. Navigate to https://web.azuresynapse.net and sign in to your new workspace. diff --git a/azure-nextgen-ts-synapse/index.ts b/azure-nextgen-ts-synapse/index.ts new file mode 100644 index 000000000..e6367b416 --- /dev/null +++ b/azure-nextgen-ts-synapse/index.ts @@ -0,0 +1,113 @@ +// Copyright 2016-2020, Pulumi Corporation. All rights reserved. + +import * as pulumi from "@pulumi/pulumi"; +import * as random from "@pulumi/random"; + +import * as authorization from "@pulumi/azure-nextgen/authorization/v20200401preview"; +import * as resources from "@pulumi/azure-nextgen/resources/latest"; +import * as storage from "@pulumi/azure-nextgen/storage/latest"; +import * as synapse from "@pulumi/azure-nextgen/synapse/v20190601preview"; + +const config = new pulumi.Config(); +const location = config.get("location") || "WestUS2"; + +const resourceGroup = new resources.ResourceGroup("resourceGroup", { + resourceGroupName: "synapse-rg", + location: location, +}); + +const storageAccount = new storage.StorageAccount("storageAccount", { + resourceGroupName: resourceGroup.name, + location: resourceGroup.location, + accountName: "synapsesa", + accessTier: "Hot", + enableHttpsTrafficOnly: true, + isHnsEnabled: true, + kind: "StorageV2", + sku: { + name: "Standard_RAGRS", + }, +}); + +const users = new storage.BlobContainer("users", { + resourceGroupName: resourceGroup.name, + accountName: storageAccount.name, + containerName: "users", + publicAccess: "None", +}); + +const dataLakeStorageAccountUrl = pulumi.interpolate`https://${storageAccount.name}.dfs.core.windows.net`; + +const workspace = new synapse.Workspace("workspace", { + resourceGroupName: resourceGroup.name, + location: resourceGroup.location, + workspaceName: "my-workspace", + defaultDataLakeStorage: { + accountUrl: dataLakeStorageAccountUrl, + filesystem: "users", + }, + identity: { + type: "SystemAssigned", + }, + sqlAdministratorLogin: "sqladminuser", + sqlAdministratorLoginPassword: new random.RandomPassword("workspacePwd", {length: 12 }).result, +}); + +const firewallRule = new synapse.IpFirewallRule("allowAll", { + resourceGroupName: resourceGroup.name, + workspaceName: workspace.name, + ruleName: "allowAll", + endIpAddress: "255.255.255.255", + startIpAddress: "0.0.0.0", +}); + +const subscriptionId = resourceGroup.id.apply(id => id.split("/")[2]); +const roleDefinitionId = pulumi.interpolate`/subscriptions/${subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/ba92f5b4-2d11-453d-a403-e96b0029c9fe`; + +const storageAccess = new authorization.RoleAssignment("storageAccess", { + roleAssignmentName: new random.RandomUuid("roleName").result, + scope: storageAccount.id, + principalId: workspace.identity.apply(i => i?.principalId).apply(id => id || ""), + principalType: "ServicePrincipal", + roleDefinitionId: roleDefinitionId, +}); + +const userAccess = new authorization.RoleAssignment("userAccess", { + roleAssignmentName: new random.RandomUuid("userRoleName").result, + scope: storageAccount.id, + principalId: config.Get("userObjectId"), + principalType: "User", + roleDefinitionId: roleDefinitionId, +}); + +const sqlPool = new synapse.SqlPool("sqlPool", { + resourceGroupName: resourceGroup.name, + location: resourceGroup.location, + workspaceName: workspace.name, + sqlPoolName: "SQLPOOL1", + collation: "SQL_Latin1_General_CP1_CI_AS", + createMode: "Default", + sku: { + name: "DW100c", + }, +}); + +const sparkPool = new synapse.BigDataPool("sparkPool", { + resourceGroupName: resourceGroup.name, + location: resourceGroup.location, + workspaceName: workspace.name, + bigDataPoolName: "Spark1", + autoPause: { + delayInMinutes: 15, + enabled: true, + }, + autoScale: { + enabled: true, + maxNodeCount: 3, + minNodeCount: 3, + }, + nodeCount: 3, + nodeSize: "Small", + nodeSizeFamily: "MemoryOptimized", + sparkVersion: "2.4", +}); diff --git a/azure-nextgen-ts-synapse/package.json b/azure-nextgen-ts-synapse/package.json new file mode 100644 index 000000000..1eb75147d --- /dev/null +++ b/azure-nextgen-ts-synapse/package.json @@ -0,0 +1,12 @@ +{ + "name": "azure-nextgen-ts-synapse", + "version": "1.0.0", + "devDependencies": { + "@types/node": "^8.0.0" + }, + "dependencies": { + "@pulumi/azure-nextgen": "^0.2.8", + "@pulumi/pulumi": "^2.0.0", + "@pulumi/random": "^2.0.0" + } +} diff --git a/azure-nextgen-ts-synapse/tsconfig.json b/azure-nextgen-ts-synapse/tsconfig.json new file mode 100644 index 000000000..61fe22191 --- /dev/null +++ b/azure-nextgen-ts-synapse/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "outDir": "bin", + "target": "es2016", + "module": "commonjs", + "moduleResolution": "node", + "sourceMap": true, + "experimentalDecorators": true, + "pretty": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "forceConsistentCasingInFileNames": true, + "strictNullChecks": true + }, + "files": [ + "index.ts" + ] +}