@ossy/deployment-tools 1.17.11 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,25 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # 1.18.0 (2026-05-25)
7
+
8
+
9
+ ### Features
10
+
11
+ * **deployment-tools:** Add OSSY_API_URL environment variable for container deployment ([d09ae18](https://github.com/ossy-se/ossy/commit/d09ae18b5255e4c49fd5387c76d1af6a24b0ba22))
12
+
13
+
14
+
15
+
16
+
17
+ ## 1.17.12 (2026-05-25)
18
+
19
+ **Note:** Version bump only for package @ossy/deployment-tools
20
+
21
+
22
+
23
+
24
+
6
25
  ## 1.17.11 (2026-05-25)
7
26
 
8
27
  **Note:** Version bump only for package @ossy/deployment-tools
package/README.md CHANGED
@@ -2,75 +2,79 @@
2
2
 
3
3
  Infrastructure and CDK tooling for the Ossy platform EC2 host.
4
4
 
5
- The host runs three services: **Caddy** (TLS + reverse proxy), **ossy-api** (the platform API), and **ossy-runtime** (the multi-tenant SSR server). Apps are deployed by running `ossy app publish`
5
+ The host runs Caddy as a reverse proxy plus several Docker containers managed by systemd. Apps are deployed by running `ossy app publish`.
6
6
 
7
7
  ## Architecture
8
8
 
9
9
  ```
10
10
  Internet → Caddy (:443)
11
- ├── api.ossy.se → ossy-api (:3001)
12
- └── *.* (on-demand TLS) → ossy-runtime (:3000)
13
- └── loads build from CMS on first request
11
+ ├── api.ossy.se → ossy-api (:3001)
12
+ ├── ossy.se → ossy-website-ossy (:3002) ← temporary
13
+ ├── www.plexus-sanitas.com ossy-website-plexus-sanitas (:3003) ← temporary
14
+ └── *.* (on-demand TLS) → ossy-platform (:3000)
15
+ └── loads build from CMS on first request
14
16
  ```
15
17
 
16
- Caddy uses **on-demand TLS**: before issuing a certificate for any domain, it calls `GET http://localhost:3001/api/v0/apps/ask?domain=<domain>`. If the domain is registered (i.e. `ossy app publish` has been run for it), Caddy issues the cert and proxies to the runtime.
18
+ Caddy uses **on-demand TLS**: before issuing a certificate for any domain, it calls `GET http://localhost:3001/api/v0/apps/ask?domain=<domain>`. If the domain is registered (i.e. `ossy app publish` has been run for it), Caddy issues the cert and proxies to the platform runtime.
17
19
 
18
20
  ## Services
19
21
 
20
22
  All services run as Docker containers managed by systemd. Service files are written to the EC2 host by CDK user data at instance creation.
21
23
 
22
- | Service | Unit file | Port | Auto-start |
23
- |---|---|---|---|
24
- | Caddy | `caddy-route53.service` | 80, 443 | Yes |
25
- | Ossy API | `ossy-api.service` | 3001 | Yes |
26
- | Ossy Runtime | `ossy-runtime.service` | 3000 | Yes |
24
+ | Service | Unit file | Port | Auto-start | Image |
25
+ |---|---|---|---|---|
26
+ | Caddy | `caddy-route53.service` | 80, 443 | Yes | — |
27
+ | Ossy API | `ossy-api.service` | 3001 | Yes | `ghcr.io/ossy-se/api:latest` |
28
+ | Ossy Platform | `ossy-runtime.service` | 3000 | Yes | `ghcr.io/ossy-se/platform:latest` |
29
+ | website-ossy | `ossy-website-ossy.service` | 3002 | Yes | `ghcr.io/ossy-se/website-ossy:latest` |
30
+ | website-plexus-sanitas | `ossy-website-plexus-sanitas.service` | 3003 | Yes | `ghcr.io/ossy-se/website-plexus-sanitas:latest` |
27
31
 
28
- All three services start automatically on first boot:
29
-
30
- - **Runtime** — image built from the published `@ossy/platform` npm package (no repo needed)
31
- - **API** — image pulled from `ghcr.io/ossy-se/api:latest` (pushed by CI on every merge to main). The `ExecStartPre` in the service unit pulls the latest image before each start, so redeploying is just `systemctl restart ossy-api`
32
+ > **TODO (website-ossy, website-plexus-sanitas):** These are temporary services. Once `ossy app publish` is validated for each site, remove their service files and Caddy blocks so traffic flows through the platform runtime instead.
32
33
 
33
34
  > **TODO:** Pin `@ossy/platform` to a specific version at CDK deploy time (thread through `PlatformConfig`) rather than always pulling `@latest`.
34
35
 
36
+ ## Configuration — `platforms.json`
37
+
38
+ `packages/infrastructure/platforms.json` is the single source of truth for a platform. It contains:
39
+
40
+ - **AWS config** — account ID, key pair, S3 bucket name, IAM role
41
+ - **`domains`** — list of domains to create Route53 A records for, all pointing to the EC2 host IP
42
+ - **`env`** — environment variables written to `/etc/environment` on the EC2 host at boot time. **These are never uploaded to S3.**
43
+
44
+ To add or change environment variables, update the `env` block in `platforms.json` and redeploy. To add a new domain, add it to `domains` and redeploy.
45
+
35
46
  ## Bootstrapping a new host
36
47
 
37
- CDK provisions the instance and starts all three services automatically. The only manual step is setting required environment variables on the host before the first start:
48
+ CDK provisions the instance, writes all service files, writes `/etc/environment` from `platforms.json`, and starts all services automatically. No manual SSH steps required.
38
49
 
39
50
  ```bash
40
- # SSH in and edit /etc/environment:
41
- sudo tee -a /etc/environment << 'EOF'
42
- DB_URL=mongodb+srv://...
43
- DB_NAME=ossy
44
- TOKEN_SECRET=...
45
- MEDIA_REPOSITORY=...
46
- MEDIA_CDN_DOMAIN_NAME=...
47
- AWS_ACCESS_KEY_ID=...
48
- AWS_SECRET_ACCESS_KEY=...
49
- OSSY_API_KEY=...
50
- EOF
51
-
52
- # Restart services to pick up the new env vars
53
- sudo systemctl restart ossy-api ossy-runtime
51
+ cd packages/deployment-tools
52
+ npx cdk deploy ossybot-deployment-target --require-approval never
54
53
  ```
55
54
 
56
- > Note: `OSSY_API_KEY` is used by the runtime to authenticate CMS reads. It should be a long-lived API token issued by the API itself.
55
+ > Note: EC2 user data only runs on **first boot**. To apply infrastructure changes to an existing instance you must terminate and recreate it, or SSH in and apply the changes manually.
57
56
 
58
57
  ## Redeploying after a code change
59
58
 
60
- Both services pull the latest image from GHCR on every restart — no manual build step needed.
59
+ All services pull the latest image from GHCR on every restart — no manual build step needed.
61
60
 
62
61
  ```bash
63
- # API — push to main triggers CI, which builds and pushes ghcr.io/ossy-se/api:latest
62
+ # API — push to main triggers CI ghcr.io/ossy-se/api:latest
64
63
  sudo systemctl restart ossy-api
65
64
 
66
- # Runtimemerging to main in the ossy monorepo publishes npm packages and then
67
- # builds and pushes ghcr.io/ossy-se/platform:latest
65
+ # Platform runtime merge to main in ossy monorepo ghcr.io/ossy-se/platform:latest
68
66
  sudo systemctl restart ossy-runtime
67
+
68
+ # website-ossy — push to main in website-ossy repo → ghcr.io/ossy-se/website-ossy:latest
69
+ sudo systemctl restart ossy-website-ossy
70
+
71
+ # website-plexus-sanitas — push to main → ghcr.io/ossy-se/website-plexus-sanitas:latest
72
+ sudo systemctl restart ossy-website-plexus-sanitas
69
73
  ```
70
74
 
71
75
  ## Environment variables
72
76
 
73
- Service env vars are read from `/etc/environment` on the host. Set these before starting the services:
77
+ All env vars are defined in the `env` block of `platforms.json` and written to `/etc/environment` at boot. Reference:
74
78
 
75
79
  | Variable | Used by | Description |
76
80
  |---|---|---|
@@ -81,17 +85,20 @@ Service env vars are read from `/etc/environment` on the host. Set these before
81
85
  | `MEDIA_CDN_DOMAIN_NAME` | API | CloudFront domain for public media |
82
86
  | `AWS_ACCESS_KEY_ID` | API | AWS credentials for S3 |
83
87
  | `AWS_SECRET_ACCESS_KEY` | API | AWS credentials for S3 |
84
- | `OSSY_API_KEY` | Runtime | API JWT for CMS reads |
88
+ | `OSSY_API_KEY` | Platform, websites | API JWT for CMS reads |
89
+ | `OSSY_COOKIE_SECRET` | Websites | Cookie signing secret |
90
+ | `OPENAI_API_KEY` | Worker | OpenAI API key |
91
+ | `OPENAI_ORGANIZATION` | Worker | OpenAI organisation ID |
85
92
 
86
93
  ## Infrastructure (CDK)
87
94
 
88
- CDK stacks are defined in `src/infrastructure/`. Run from `src/infrastructure/cli.js` via `cdk deploy`.
95
+ CDK stacks are defined in `src/infrastructure/`. Run via `cdk deploy` from the `deployment-tools` package.
89
96
 
90
97
  | Stack | Description |
91
98
  |---|---|
92
99
  | `trust-ci` | IAM role for CI to deploy infrastructure |
93
100
  | `deployment-target` | EC2 host, S3 bucket, CloudFront CDN for media |
94
- | `dns` | Route53 records |
101
+ | `dns` | Route53 A records for all domains in `platforms.json` |
95
102
  | `ses` | SES email sending |
96
103
 
97
104
  ## Useful commands
@@ -105,6 +112,7 @@ ssh -i path/to/keys ubuntu@<ip>
105
112
  ```bash
106
113
  journalctl -u ossy-runtime.service -n 200 -f
107
114
  journalctl -u ossy-api.service -n 200 -f
115
+ journalctl -u ossy-website-ossy.service -n 200 -f
108
116
  journalctl -u caddy-route53.service -n 200 -f
109
117
  ```
110
118
 
@@ -117,6 +125,7 @@ docker ps
117
125
  ```bash
118
126
  docker logs ossy-runtime
119
127
  docker logs ossy-api
128
+ docker logs ossy-website-ossy
120
129
  ```
121
130
 
122
131
  **Reload Caddy config**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ossy/deployment-tools",
3
- "version": "1.17.11",
3
+ "version": "1.18.0",
4
4
  "description": "Collection of scripts and tools to aid deployment of containers and static files to Amazon Web Services through GitHub Actions",
5
5
  "source": "./src/index.js",
6
6
  "main": "./src/index.js",
@@ -26,5 +26,5 @@
26
26
  "devDependencies": {
27
27
  "jest": "^27.5.1"
28
28
  },
29
- "gitHead": "c50aee3d73f8c3f71ce2d8a91e9be2de90c6c4d9"
29
+ "gitHead": "624e5273e14f3c3db768c9ecae2c7fe27e75f65a"
30
30
  }
@@ -3,14 +3,14 @@
3
3
  * @typedef {Object} PlatformConfig
4
4
  * @property {string} platformName - Name of platform
5
5
  * @property {string[]=} sesDomains - list of domains to configure AWS SES email identity and DKIM records for
6
+ * @property {string[]=} domains - list of domains to create Route53 A records for, pointing to the EC2 host
6
7
  * @property {object} dnsRecords - map of dns records by root domain, only supports MX records so that we can add dns records for our email service
8
+ * @property {Record<string,string>=} env - environment variables written to /etc/environment on the EC2 host at boot time; never uploaded to S3
7
9
  *
8
10
  * @property {string} awsAccountId - Aws account id
9
11
  * @property {string=} awsRegion - ?
10
12
  * @property {string=} awsKeyPairName - ?
11
13
  * @property {string} awsRoleToAssume - ?
12
- * @property {string=} awsDeploymentSqsName - ?
13
- * @property {string=} awsDeploymentSqsArn - ?
14
14
  *
15
15
  * @property {string=} ciDockerNetworkName - ?
16
16
  * @property {string} ciGithubActionsRepo - organisation/repoName
@@ -20,11 +20,6 @@ const SupportedRegions = {
20
20
  North: 'eu-north-1'
21
21
  }
22
22
 
23
- const SupportedDeploymentTypes = {
24
- Container: 'CONTAINER',
25
- Static: 'STATIC'
26
- }
27
-
28
23
  /**
29
24
  * @class
30
25
  */
@@ -40,15 +35,8 @@ class PlatformConfigService {
40
35
  ciDockerNetworkName: 'deployment-tools',
41
36
  }
42
37
 
43
- const awsDeploymentSqsName = `${withDefaults.platformName}-container-deployments-requests`
44
-
45
- const awsDeploymentSqsArn =
46
- `https://sqs.${withDefaults.awsRegion}.amazonaws.com/${withDefaults.awsAccountId}/${awsDeploymentSqsName}`
47
-
48
38
  return {
49
39
  ...withDefaults,
50
- awsDeploymentSqsName,
51
- awsDeploymentSqsArn
52
40
  }
53
41
 
54
42
  }
@@ -58,5 +46,4 @@ class PlatformConfigService {
58
46
  module.exports = {
59
47
  PlatformConfigService,
60
48
  SupportedRegions,
61
- SupportedDeploymentTypes
62
49
  }
@@ -5,16 +5,13 @@ const { TrustCiStack } = require('./trust-ci-stack')
5
5
  const { DeploymentTargetStack } = require('./deployment-target-stack')
6
6
  const { DnsStack } = require('./dns-stack')
7
7
  const { SesStack } = require('./ses-stack')
8
- const { PlatformTemplateService, DeploymentTemplateService } = require('../template')
8
+ const { PlatformTemplateService } = require('../template')
9
9
  const { PlatformConfigService } = require('../config')
10
10
 
11
- Promise.all([
12
- DeploymentTemplateService.readFromFiles('../infrastructure/deployments.json'),
13
- PlatformTemplateService
14
- .readFromFile('../infrastructure/platforms.json')
15
- .then(templates => templates.map(PlatformConfigService.from))
16
- ])
17
- .then(([deploymentMap, configs]) => {
11
+ PlatformTemplateService
12
+ .readFromFile('../infrastructure/platforms.json')
13
+ .then(templates => templates.map(PlatformConfigService.from))
14
+ .then(configs => {
18
15
  const app = new App()
19
16
 
20
17
  configs.forEach(config => {
@@ -33,7 +30,6 @@ Promise.all([
33
30
 
34
31
  new DnsStack(app, `${config.platformName}-dns`, {
35
32
  ...stackProps,
36
- deployments: deploymentMap[config.platformName],
37
33
  containerDeploymentTargetPublicIp: deploymentTarget.containerDeploymentTargetPublicIp
38
34
  })
39
35
 
@@ -71,7 +71,7 @@ class ContainerDeploymentTarget extends Construct {
71
71
  )
72
72
 
73
73
  const platformConfigDeployment = new BucketDeployment(this, 'PlatformConfigDeployment', {
74
- sources: [Source.jsonData('platform-config.json', { ...props.config, awsRoleToAssume: undefined })],
74
+ sources: [Source.jsonData('platform-config.json', { ...props.config, awsRoleToAssume: undefined, env: undefined })],
75
75
  destinationBucket: props.bucket,
76
76
  // Default prune:true would delete every other object (e.g. media/*) on each infra deploy.
77
77
  prune: false
@@ -108,6 +108,9 @@ class ContainerDeploymentTarget extends Construct {
108
108
 
109
109
  const userData = UserData.forLinux()
110
110
 
111
+ // Write platform env vars to /etc/environment so all systemd services pick them up.
112
+ const envLines = Object.entries(props.config.env ?? {}).map(([k, v]) => `${k}=${v}`)
113
+
111
114
  userData.addCommands(
112
115
  'sudo groupadd docker',
113
116
  'sudo usermod -aG docker ubuntu',
@@ -118,6 +121,8 @@ class ContainerDeploymentTarget extends Construct {
118
121
  ...getInstallDocker(),
119
122
  'sudo apt-get install awscli --yes',
120
123
  ...AwsProfile.writeFile(role.roleArn, SupportedRegions.North),
124
+ // Write all platform env vars before starting any service
125
+ `sudo tee /etc/environment << 'ENVEOF'\n${envLines.join('\n')}\nENVEOF`,
121
126
  // Create shared Docker network for inter-container communication
122
127
  'docker network create ossy-network || true',
123
128
  ...CaddyService.install(),
@@ -12,6 +12,7 @@ ExecStart=/usr/bin/docker run --name ossy-runtime \\
12
12
  --network ossy-network \\
13
13
  -p 3000:3000 \\
14
14
  -e OSSY_API_KEY=\${OSSY_API_KEY} \\
15
+ -e OSSY_API_URL=http://ossy-api:3001/api/v0 \\
15
16
  ghcr.io/ossy-se/platform:latest
16
17
  ExecStop=/usr/bin/docker stop ossy-runtime
17
18
  Restart=on-failure
@@ -5,22 +5,15 @@ const {
5
5
  MxRecord,
6
6
  RecordTarget
7
7
  } = require('aws-cdk-lib/aws-route53')
8
- const { SupportedDeploymentTypes } = require('../config')
9
- const { DeploymentTemplateService } = require('../template')
10
8
 
11
9
  /**
12
10
  * DnsStackProps
13
11
  * @namespace DnsStack
14
12
  * @typedef {Object} DnsStackProps
15
13
  * @property {PlatformConfig} config - platform config
16
- * @property {PlatformConfig} deployments - deployment templates[]
17
- * @property {string} containerDeploymentTargetPublicIp - public ip address of ec2 instance that hosts our containerss
14
+ * @property {string} containerDeploymentTargetPublicIp - public ip address of ec2 instance
18
15
  */
19
16
 
20
- const InstanceImages = {
21
- UBUNTU: 'ami-092cce4a19b438926'
22
- }
23
-
24
17
  /**
25
18
  * @class
26
19
  */
@@ -29,57 +22,56 @@ class DnsStack extends Stack {
29
22
  /**
30
23
  * @param {object} scope - scope
31
24
  * @param {string} id - id
32
- * @param {ContainerServerProps} props - ContainerServerProps
25
+ * @param {DnsStackProps} props - DnsStackProps
33
26
  */
34
27
  constructor(scope, id, props) {
35
28
  super(scope, id, props)
36
29
 
37
- const containerDeployments = DeploymentTemplateService
38
- .getContainerDeployments(props.deployments)
39
-
40
- DeploymentTemplateService
41
- .groupDeploymentDomainsByRootDomain(containerDeployments)
42
- .forEach((domains, rootDomain) => {
43
- const zone = HostedZone.fromLookup(this, `${rootDomain}-zone`, { domainName: rootDomain })
44
-
45
- domains.forEach(domain => {
46
- new ARecord(this, `${domain}-record`, {
47
- zone,
48
- recordName: domain,
49
- target: RecordTarget.fromIpAddresses(props.containerDeploymentTargetPublicIp),
50
- ttl: Duration.seconds(60)
51
- })
30
+ // Group domains by root domain so we can look up each hosted zone once.
31
+ const domainsByRoot = (props.config.domains ?? []).reduce((map, domain) => {
32
+ const parts = domain.split('.')
33
+ const rootDomain = parts.slice(-2).join('.')
34
+ map.has(rootDomain)
35
+ ? map.set(rootDomain, [...map.get(rootDomain), domain])
36
+ : map.set(rootDomain, [domain])
37
+ return map
38
+ }, new Map())
39
+
40
+ domainsByRoot.forEach((domains, rootDomain) => {
41
+ const zone = HostedZone.fromLookup(this, `${rootDomain}-zone`, { domainName: rootDomain })
42
+
43
+ domains.forEach(domain => {
44
+ new ARecord(this, `${domain}-record`, {
45
+ zone,
46
+ recordName: domain,
47
+ target: RecordTarget.fromIpAddresses(props.containerDeploymentTargetPublicIp),
48
+ ttl: Duration.seconds(60)
52
49
  })
50
+ })
53
51
 
54
- if (props.config.dnsRecords && props.config.dnsRecords[rootDomain]) {
55
- const records = props.config.dnsRecords[rootDomain] || []
56
-
57
- records.forEach(({ type, ttl, recordName, values }) => {
58
- switch (type) {
59
-
60
- case 'MX':
61
- new MxRecord(
62
- this,
63
- `${recordName}-MX-Record`,
64
- { type, recordName, values, ttl: Duration.seconds(ttl), zone }
65
- )
66
- break
52
+ if (props.config.dnsRecords && props.config.dnsRecords[rootDomain]) {
53
+ const records = props.config.dnsRecords[rootDomain] || []
67
54
 
68
- default:
69
- throw new Error(`Unsupported record type: ${type}`)
70
- }
71
- })
72
- }
55
+ records.forEach(({ type, ttl, recordName, values }) => {
56
+ switch (type) {
73
57
 
74
- })
58
+ case 'MX':
59
+ new MxRecord(
60
+ this,
61
+ `${recordName}-MX-Record`,
62
+ { type, recordName, values, ttl: Duration.seconds(ttl), zone }
63
+ )
64
+ break
75
65
 
66
+ default:
67
+ throw new Error(`Unsupported record type: ${type}`)
68
+ }
69
+ })
70
+ }
71
+ })
76
72
  }
77
73
  }
78
74
 
79
- function createDnsRecords(zone, records) {
80
-
81
- }
82
-
83
75
  module.exports = {
84
76
  DnsStack
85
77
  }
@@ -1,4 +1,3 @@
1
1
  module.exports = {
2
- ...require('./deployment-template'),
3
2
  ...require('./platform-template')
4
3
  }
@@ -1,83 +0,0 @@
1
- const { glob } = require('glob')
2
- const { readFileSync } = require('fs')
3
- const { SupportedDeploymentTypes } = require('../config')
4
-
5
- /**
6
- * Deployment template definition
7
- * @typedef {Object} DeploymentTemplate
8
- * @property {string} type - CONTAINER | STATIC
9
- * @property {string} domain - example.com
10
- * @property {string} targetDeploymentPlatform - name of platform to deploy to
11
- *
12
- * @property {string} image - name of image to be deployed
13
- * @property {string} registry - registry where the image is hosted
14
- * @property {string=} hostPort - host port
15
- * @property {string=} containerPort - port that the container exposes
16
- */
17
-
18
- /**
19
- * Utility class that helps you read and validate platfor templates from file system
20
- * @class
21
- */
22
- class DeploymentTemplateService {
23
-
24
- /**
25
- * Read and and group deployments by targetDeploymentPlatform
26
- *
27
- * @param {string} blob - blob pattern for deployments.json files
28
- */
29
- static readFromFiles(blob) {
30
- return glob(blob, { ignore: 'node_modules/**' })
31
- .then(filePaths => filePaths
32
- .map(path => readFileSync(path, 'utf-8'))
33
- .flatMap(json => JSON.parse(json))
34
- .map(deploymentTemplate => ({
35
- containerPort: '3000',
36
- ...deploymentTemplate
37
- }))
38
- .reduce((deploymentsMap, deployment) => {
39
- if (!!deploymentsMap[deployment.targetDeploymentPlatform]) {
40
- return {
41
- ...deploymentsMap,
42
- [deployment.targetDeploymentPlatform]: [
43
- ...deploymentsMap[deployment.targetDeploymentPlatform],
44
- deployment
45
- ]
46
- }
47
- }
48
-
49
- return {
50
- ...deploymentsMap,
51
- [deployment.targetDeploymentPlatform]: [deployment]
52
- }
53
-
54
- }, {})
55
- )
56
- }
57
-
58
- static getContainerDeployments(deployments) {
59
- return deployments
60
- .filter(deployment => deployment.type === SupportedDeploymentTypes.Container)
61
- }
62
-
63
- static groupDeploymentDomainsByRootDomain(deployments) {
64
- return deployments
65
- .map(deployment => deployment.domain)
66
- .reduce((domainGroups, domain) => {
67
- const [topLevelDomain, domainName] = domain.split('.').reverse()
68
- const rootDomain = `${domainName}.${topLevelDomain}`
69
-
70
- domainGroups.has(rootDomain)
71
- ? domainGroups.set(rootDomain, [...domainGroups.get(rootDomain), domain])
72
- : domainGroups.set(rootDomain, [domain])
73
-
74
- return domainGroups
75
- }, new Map())
76
- }
77
-
78
-
79
- }
80
-
81
- module.exports = {
82
- DeploymentTemplateService
83
- }