@fractal_cloud/sdk 1.0.0 → 1.2.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/README.md +191 -174
- package/dist/index.cjs +2922 -57
- package/dist/index.d.cts +1245 -2
- package/dist/index.d.mts +1245 -2
- package/dist/index.mjs +2748 -57
- package/package.json +15 -3
package/README.md
CHANGED
|
@@ -2,244 +2,261 @@
|
|
|
2
2
|
|
|
3
3
|
[![NPM Version][npm-image]][npm-url]
|
|
4
4
|
[![build status][build-image]][build-url]
|
|
5
|
+
[![codecov][codecov-image]][codecov-url]
|
|
5
6
|
[![license][license-image]](LICENSE)
|
|
6
7
|
[![Known Vulnerabilities][snyk-image]][snyk-url]
|
|
7
|
-
[![codecov][codecov-image]][codecov-url]
|
|
8
8
|
[![TypeScript Style Guide][gts-image]][gts-url]
|
|
9
9
|
|
|
10
10
|
## Overview
|
|
11
11
|
|
|
12
|
-
The Fractal TypeScript SDK
|
|
13
|
-
It is designed to bridge software architecture and infrastructure automation, enabling teams to express infrastructure intent in a general purpose language without relying on vendor specific tools or domain specific languages.
|
|
14
|
-
With Fractal, infrastructure is no longer written as low level scripts. It is modeled as reusable architectural building blocks that can be validated, governed, and evolved over time.
|
|
15
|
-
This SDK is currently at version 1.0.0. It supports Blueprint and Live System definition and deployment and is intended for early production pilots and platform experimentation.
|
|
16
|
-
|
|
17
|
-
## Why Fractal
|
|
18
|
-
|
|
19
|
-
Modern infrastructure automation forces teams to choose between flexibility and control.
|
|
20
|
-
|
|
21
|
-
Traditional Infrastructure as Code approaches are:
|
|
22
|
-
|
|
23
|
-
1. Tightly coupled to a specific cloud vendor
|
|
24
|
-
2. Based on string heavy DSLs
|
|
25
|
-
3. Difficult to govern at scale
|
|
26
|
-
4. Focused on resources rather than architecture
|
|
27
|
-
|
|
28
|
-
Fractal Cloud takes a different approach.
|
|
29
|
-
|
|
30
|
-
Fractal Cloud allows you to:
|
|
31
|
-
|
|
32
|
-
1. Define infrastructure as architecture, not scripts
|
|
33
|
-
2. Use TypeScript instead of a DSL
|
|
34
|
-
3. Encode governance and intent directly into reusable blueprints
|
|
35
|
-
4. Remain cloud-agnostic by design
|
|
36
|
-
|
|
37
|
-
This SDK is the programmatic interface to that model.
|
|
12
|
+
The Fractal Cloud TypeScript SDK lets platform engineers and application developers define cloud-agnostic infrastructure blueprints in TypeScript and deploy them to any supported cloud provider.
|
|
38
13
|
|
|
39
|
-
|
|
14
|
+
Infrastructure is modelled as reusable architectural building blocks — **Fractals** — that can be validated, governed, and evolved over time. Live Systems map those blueprints to concrete cloud resources without ever touching vendor-specific tooling or DSLs.
|
|
40
15
|
|
|
41
|
-
|
|
16
|
+
## Why Fractal
|
|
42
17
|
|
|
43
|
-
|
|
18
|
+
Traditional Infrastructure as Code forces a choice between flexibility and control:
|
|
44
19
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
20
|
+
- Tightly coupled to a specific cloud vendor
|
|
21
|
+
- String-heavy DSLs with no type safety
|
|
22
|
+
- Hard to govern at scale
|
|
23
|
+
- Focused on raw resources rather than architecture
|
|
48
24
|
|
|
49
|
-
|
|
25
|
+
Fractal Cloud takes a different approach: define infrastructure as **architecture**, stay cloud-agnostic by design, and let the Fractal Automation Engine handle provisioning and drift reconciliation.
|
|
50
26
|
|
|
51
|
-
|
|
52
|
-
It describes components, their relationships, and their architectural intent.
|
|
27
|
+
## Core concepts
|
|
53
28
|
|
|
54
|
-
|
|
29
|
+
### Fractal (Blueprint)
|
|
55
30
|
|
|
56
|
-
|
|
57
|
-
These operations may involve the addition, removal, or modification of components and their integrations within the Fractal.
|
|
31
|
+
A Fractal is a governed, reusable infrastructure pattern. It defines what an infrastructure system is allowed to be — components, their relationships, and architectural intent — without referencing any cloud provider. Fractals are registered with the Fractal Cloud API and can be composed into higher-order Fractals.
|
|
58
32
|
|
|
59
33
|
### Live System
|
|
60
34
|
|
|
61
|
-
A Live System is a running instance of a Fractal
|
|
62
|
-
Live Systems are not yet supported in the TypeScript SDK.
|
|
63
|
-
|
|
64
|
-
For a deeper explanation of Fractal Architecture, see the documentation linked below.
|
|
65
|
-
|
|
66
|
-
## What this SDK does today
|
|
67
|
-
|
|
68
|
-
Version 1.0.0 supports the following capabilities.
|
|
69
|
-
|
|
70
|
-
* Definition of Fractal Blueprints
|
|
71
|
-
* Deployment of Blueprints to Fractal Cloud
|
|
72
|
-
* Definition of Live Systems
|
|
73
|
-
* Deployment of Live Systems to a specified Fractal Cloud Environment
|
|
74
|
-
* Usage from both personal and organization owned accounts
|
|
75
|
-
|
|
76
|
-
This SDK does not yet support:
|
|
77
|
-
|
|
78
|
-
* Fractal Interface definition
|
|
79
|
-
* Bounded Context helpers
|
|
80
|
-
* Environment helpers
|
|
81
|
-
|
|
82
|
-
## Architectural positioning
|
|
83
|
-
|
|
84
|
-
The Fractal TypeScript SDK acts as a bridge between software architecture and infrastructure automation.
|
|
85
|
-
|
|
86
|
-
It allows teams to:
|
|
35
|
+
A Live System is a running instance of a Fractal. It maps each abstract blueprint component to a concrete provider-specific **Offer** and supplies the vendor parameters the Fractal Automation Engine needs to provision and reconcile cloud resources. There are no state files — the cloud is the source of truth.
|
|
87
36
|
|
|
88
|
-
|
|
89
|
-
* Keep infrastructure definitions vendor neutral
|
|
90
|
-
* Rely on the Fractal Automation Engine to interpret and enforce those definitions
|
|
37
|
+
## What this SDK supports
|
|
91
38
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
39
|
+
- Definition of Fractal Blueprints using cloud-agnostic component helpers
|
|
40
|
+
- Deployment of Blueprints to Fractal Cloud
|
|
41
|
+
- Definition of Live Systems with typed, provider-specific helpers
|
|
42
|
+
- Deployment of Live Systems to any Fractal Cloud Environment
|
|
43
|
+
- Personal and organisation-owned accounts
|
|
44
|
+
- **IaaS helpers** for AWS, Azure, GCP, OCI, and Hetzner
|
|
45
|
+
- **PaaS helpers** for AWS ECS (cluster, task definition, service)
|
|
96
46
|
|
|
97
47
|
## Installation
|
|
98
48
|
|
|
99
|
-
The SDK is published as a public npm package.
|
|
100
|
-
|
|
101
49
|
```bash
|
|
102
50
|
npm install @fractal_cloud/sdk
|
|
103
51
|
```
|
|
104
52
|
|
|
105
|
-
[Package page](https://www.npmjs.com/package/@fractal_cloud/sdk)
|
|
53
|
+
[Package page](https://www.npmjs.com/package/@fractal_cloud/sdk) · [Source repository](https://github.com/Fractal-Cloud/fractal-ts-sdk)
|
|
106
54
|
|
|
107
|
-
|
|
55
|
+
Requires Node.js 18+ and TypeScript 5+.
|
|
108
56
|
|
|
109
|
-
|
|
57
|
+
## Quick start
|
|
110
58
|
|
|
111
|
-
|
|
59
|
+
The following example defines a cloud-agnostic blueprint with a VPC, a subnet, a security group, and two VMs — then deploys it on AWS.
|
|
112
60
|
|
|
113
|
-
|
|
61
|
+
### 1. Define the blueprint (`fractal.ts`)
|
|
114
62
|
|
|
115
|
-
|
|
63
|
+
```typescript
|
|
64
|
+
import {
|
|
65
|
+
BoundedContext, Fractal, KebabCaseString, OwnerId, OwnerType, Version,
|
|
66
|
+
VirtualNetwork, Subnet, SecurityGroup, VirtualMachine,
|
|
67
|
+
} from '@fractal_cloud/sdk';
|
|
116
68
|
|
|
117
|
-
```js
|
|
118
|
-
// The Bounded Context ID is used to group Blueprints and Live System in order to granularly control access within an Organization
|
|
119
69
|
const bcId = BoundedContext.Id.getBuilder()
|
|
120
70
|
.withOwnerType(OwnerType.Personal)
|
|
121
|
-
.withOwnerId(OwnerId.getBuilder()
|
|
122
|
-
|
|
123
|
-
.build())
|
|
124
|
-
.withName(KebabCaseString.getBuilder().withValue("test").build())
|
|
71
|
+
.withOwnerId(OwnerId.getBuilder().withValue(process.env['OWNER_ID']!).build())
|
|
72
|
+
.withName(KebabCaseString.getBuilder().withValue('my-team').build())
|
|
125
73
|
.build();
|
|
126
74
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
.
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
.
|
|
151
|
-
|
|
152
|
-
|
|
75
|
+
const apiServer = VirtualMachine.create({
|
|
76
|
+
id: 'api-server',
|
|
77
|
+
version: {major: 1, minor: 0, patch: 0},
|
|
78
|
+
displayName: 'API Server',
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const webServer = VirtualMachine.create({
|
|
82
|
+
id: 'web-server',
|
|
83
|
+
version: {major: 1, minor: 0, patch: 0},
|
|
84
|
+
displayName: 'Web Server',
|
|
85
|
+
}).withLinks([{target: apiServer, fromPort: 8080}]);
|
|
86
|
+
|
|
87
|
+
const network = VirtualNetwork.create({
|
|
88
|
+
id: 'main-network',
|
|
89
|
+
version: {major: 1, minor: 0, patch: 0},
|
|
90
|
+
displayName: 'Main VPC',
|
|
91
|
+
cidrBlock: '10.0.0.0/16',
|
|
92
|
+
})
|
|
93
|
+
.withSubnets([
|
|
94
|
+
Subnet.create({
|
|
95
|
+
id: 'public-subnet',
|
|
96
|
+
version: {major: 1, minor: 0, patch: 0},
|
|
97
|
+
displayName: 'Public Subnet',
|
|
98
|
+
cidrBlock: '10.0.1.0/24',
|
|
99
|
+
}).withVirtualMachines([webServer, apiServer]),
|
|
100
|
+
])
|
|
101
|
+
.withSecurityGroups([
|
|
102
|
+
SecurityGroup.create({
|
|
103
|
+
id: 'web-sg',
|
|
104
|
+
version: {major: 1, minor: 0, patch: 0},
|
|
105
|
+
displayName: 'Web Security Group',
|
|
106
|
+
description: 'Allow SSH and HTTP',
|
|
107
|
+
ingressRules: [
|
|
108
|
+
{protocol: 'tcp', fromPort: 22, toPort: 22, sourceCidr: '0.0.0.0/0'},
|
|
109
|
+
{protocol: 'tcp', fromPort: 80, toPort: 80, sourceCidr: '0.0.0.0/0'},
|
|
110
|
+
],
|
|
111
|
+
}),
|
|
112
|
+
]);
|
|
113
|
+
|
|
114
|
+
export const fractal = Fractal.getBuilder()
|
|
115
|
+
.withId(
|
|
116
|
+
Fractal.Id.getBuilder()
|
|
117
|
+
.withBoundedContextId(bcId)
|
|
118
|
+
.withName(KebabCaseString.getBuilder().withValue('my-iaas-fractal').build())
|
|
119
|
+
.withVersion(Version.getBuilder().withMajor(1).withMinor(0).withPatch(0).build())
|
|
120
|
+
.build(),
|
|
121
|
+
)
|
|
122
|
+
.withComponents([...network.components])
|
|
153
123
|
.build();
|
|
124
|
+
```
|
|
154
125
|
|
|
155
|
-
|
|
156
|
-
|
|
126
|
+
### 2. Satisfy with AWS components (`aws_live_system.ts`)
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
import {
|
|
130
|
+
AwsVpc, AwsSubnet, AwsSecurityGroup, Ec2Instance,
|
|
131
|
+
Environment, KebabCaseString, LiveSystem, OwnerId, OwnerType,
|
|
132
|
+
} from '@fractal_cloud/sdk';
|
|
133
|
+
import {bcId, fractal} from './fractal';
|
|
134
|
+
|
|
135
|
+
function bp(id: string) {
|
|
136
|
+
const c = fractal.components.find(x => x.id.toString() === id);
|
|
137
|
+
if (!c) throw new Error(`Blueprint component '${id}' not found`);
|
|
138
|
+
return c;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function getLiveSystem(): LiveSystem {
|
|
142
|
+
const vpc = AwsVpc.satisfy(bp('main-network')).withEnableDnsSupport(true).build();
|
|
143
|
+
const subnet = AwsSubnet.satisfy(bp('public-subnet')).withAvailabilityZone('eu-central-1a').build();
|
|
144
|
+
const sg = AwsSecurityGroup.satisfy(bp('web-sg')).build();
|
|
145
|
+
const web = Ec2Instance.satisfy(bp('web-server')).withAmiId('ami-0c55b159cbfafe1f0').withInstanceType('t3.micro').build();
|
|
146
|
+
const api = Ec2Instance.satisfy(bp('api-server')).withAmiId('ami-0c55b159cbfafe1f0').withInstanceType('t3.small').build();
|
|
147
|
+
|
|
148
|
+
return LiveSystem.getBuilder()
|
|
149
|
+
.withId(LiveSystem.Id.getBuilder().withBoundedContextId(bcId)
|
|
150
|
+
.withName(KebabCaseString.getBuilder().withValue('my-iaas-aws').build()).build())
|
|
151
|
+
.withFractalId(fractal.id)
|
|
152
|
+
.withGenericProvider('AWS')
|
|
153
|
+
.withEnvironment(Environment.getBuilder()
|
|
154
|
+
.withId(Environment.Id.getBuilder()
|
|
155
|
+
.withOwnerType(OwnerType.Personal)
|
|
156
|
+
.withOwnerId(OwnerId.getBuilder().withValue(process.env['OWNER_ID']!).build())
|
|
157
|
+
.withName(KebabCaseString.getBuilder().withValue('dev').build()).build()).build())
|
|
158
|
+
.withComponent(vpc).withComponent(subnet).withComponent(sg).withComponent(web).withComponent(api)
|
|
159
|
+
.build();
|
|
160
|
+
}
|
|
157
161
|
```
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
.withId(LiveSystem.Id.getBuilder()
|
|
170
|
-
.withBoundedContextId(bcId)
|
|
171
|
-
.withName(KebabCaseString.getBuilder().withValue("live-system-from-ts").build())
|
|
172
|
-
.build())
|
|
173
|
-
.withComponent(LiveSystem.Component.getBuilder()
|
|
174
|
-
.withId(fractal.components[0].id)
|
|
175
|
-
.withDescription(fractal.components[0].description)
|
|
176
|
-
.withType(fractal.components[0].type)
|
|
177
|
-
.withDisplayName(fractal.components[0].displayName)
|
|
178
|
-
.withVersion(fractal.components[0].version)
|
|
179
|
-
.build())
|
|
180
|
-
.withDescription('Live System deployed from TypeScript')
|
|
181
|
-
.withFractalId(fractal.id)
|
|
182
|
-
.withGenericProvider('Azure')
|
|
183
|
-
.withEnvironment(Environment.getBuilder()
|
|
184
|
-
.withId(Environment.Id.getBuilder()
|
|
185
|
-
.withOwnerType(OwnerType.Personal)
|
|
186
|
-
.withOwnerId(OwnerId.getBuilder().withValue('00000000-0000-0000-0000-000000000000').build())
|
|
187
|
-
.withName(KebabCaseString.getBuilder().withValue('test').build())
|
|
188
|
-
.build())
|
|
189
|
-
.build())
|
|
162
|
+
|
|
163
|
+
### 3. Deploy (`index.ts`)
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
import {ServiceAccountCredentials, ServiceAccountId} from '@fractal_cloud/sdk';
|
|
167
|
+
import {fractal} from './fractal';
|
|
168
|
+
import {getLiveSystem} from './aws_live_system';
|
|
169
|
+
|
|
170
|
+
const credentials = ServiceAccountCredentials.getBuilder()
|
|
171
|
+
.withId(ServiceAccountId.getBuilder().withValue(process.env['SERVICE_ACCOUNT_ID']!).build())
|
|
172
|
+
.withSecret(process.env['SERVICE_ACCOUNT_SECRET']!)
|
|
190
173
|
.build();
|
|
191
174
|
|
|
192
|
-
|
|
193
|
-
await
|
|
175
|
+
await fractal.deploy(credentials);
|
|
176
|
+
await getLiveSystem().deploy(credentials);
|
|
194
177
|
```
|
|
195
178
|
|
|
196
|
-
|
|
179
|
+
The blueprint is registered with Fractal Cloud. The Automation Engine reconciles cloud resources to match the Live System definition.
|
|
197
180
|
|
|
198
|
-
|
|
181
|
+
## Deployment modes
|
|
199
182
|
|
|
200
|
-
|
|
183
|
+
`liveSystem.deploy()` supports two modes via an optional `DeployOptions` argument.
|
|
201
184
|
|
|
202
|
-
|
|
203
|
-
2. No Bounded Context or Environment abstractions
|
|
204
|
-
3. API surface still evolving
|
|
185
|
+
### Fire and forget (default)
|
|
205
186
|
|
|
206
|
-
|
|
187
|
+
Submits the live system to Fractal Cloud and returns immediately. Provisioning happens asynchronously. Errors are logged but not thrown. This is the default when no options are passed.
|
|
207
188
|
|
|
208
|
-
|
|
189
|
+
```typescript
|
|
190
|
+
// Equivalent — both are fire-and-forget
|
|
191
|
+
await liveSystem.deploy(credentials);
|
|
192
|
+
await liveSystem.deploy(credentials, {mode: 'fire-and-forget'});
|
|
193
|
+
```
|
|
209
194
|
|
|
210
|
-
|
|
195
|
+
Best for: **applications, CLIs, scripts** where infrastructure deployment is a background concern.
|
|
211
196
|
|
|
212
|
-
|
|
213
|
-
2. Bounded Context support
|
|
214
|
-
3. Environment support
|
|
197
|
+
### Wait for Active
|
|
215
198
|
|
|
216
|
-
|
|
199
|
+
Submits the live system, then polls until all components reach `Active` status. Throws if deployment fails (`FailedMutation`, `Error`) or if the timeout is exceeded.
|
|
217
200
|
|
|
218
|
-
|
|
201
|
+
```typescript
|
|
202
|
+
await liveSystem.deploy(credentials, {
|
|
203
|
+
mode: 'wait',
|
|
204
|
+
pollIntervalMs: 10_000, // check every 10 s (default: 5 s)
|
|
205
|
+
timeoutMs: 900_000, // give up after 15 min (default: 10 min)
|
|
206
|
+
});
|
|
207
|
+
// reaches here only when the live system is fully Active
|
|
208
|
+
```
|
|
219
209
|
|
|
220
|
-
|
|
210
|
+
Best for: **CI/CD pipelines** where the pipeline must not advance until infrastructure is fully provisioned.
|
|
221
211
|
|
|
222
|
-
|
|
212
|
+
---
|
|
223
213
|
|
|
224
|
-
##
|
|
214
|
+
## Multi-provider support
|
|
225
215
|
|
|
226
|
-
|
|
216
|
+
The same blueprint can be deployed on any supported provider. Live system files are short and only contain vendor-specific parameters — all structural decisions (dependencies, traffic rules, security rules) stay in the blueprint.
|
|
227
217
|
|
|
228
|
-
|
|
218
|
+
| Blueprint component | AWS | Azure | GCP | OCI | Hetzner |
|
|
219
|
+
|---------------------|-----|-------|-----|-----|---------|
|
|
220
|
+
| `VirtualNetwork` | `AwsVpc` | `AzureVnet` | `GcpVpc` | `OciVcn` | `HetznerNetwork` |
|
|
221
|
+
| `Subnet` | `AwsSubnet` | `AzureSubnet` | `GcpSubnet` | `OciSubnet` | `HetznerSubnet` |
|
|
222
|
+
| `SecurityGroup` | `AwsSecurityGroup` | `AzureNsg` | `GcpFirewall` | `OciSecurityList` | `HetznerFirewall` |
|
|
223
|
+
| `VirtualMachine` | `Ec2Instance` | `AzureVm` | `GcpVm` | `OciInstance` | `HetznerServer` |
|
|
229
224
|
|
|
230
|
-
|
|
225
|
+
PaaS and CaaS helpers are also available for AWS ECS (cluster, task definition, service) and the agnostic `ContainerPlatform` / `Workload` blueprint types.
|
|
231
226
|
|
|
232
|
-
|
|
227
|
+
## Samples
|
|
233
228
|
|
|
234
|
-
|
|
229
|
+
The [sample repository](https://github.com/Fractal-Cloud/fractal-ts-sdk-samples) contains ready-to-run examples:
|
|
235
230
|
|
|
236
|
-
|
|
231
|
+
| Sample | Description |
|
|
232
|
+
|--------|-------------|
|
|
233
|
+
| `basic_iaas` | VPC + Subnet + Security Group + two VMs — deploys on AWS, Azure, GCP, OCI, or Hetzner via `CLOUD_PROVIDER` env var |
|
|
234
|
+
| `basic_container_platform` | ECS Fargate — VPC + Subnet + Security Group + ECS Cluster + two workloads |
|
|
237
235
|
|
|
238
|
-
##
|
|
236
|
+
## Architecture
|
|
237
|
+
|
|
238
|
+
```
|
|
239
|
+
src/
|
|
240
|
+
fractal/ # Cloud-agnostic blueprint helpers
|
|
241
|
+
component/
|
|
242
|
+
network_and_compute/iaas/ # VirtualNetwork, Subnet, SecurityGroup, VirtualMachine
|
|
243
|
+
network_and_compute/paas/ # ContainerPlatform
|
|
244
|
+
custom_workloads/caas/ # Workload
|
|
245
|
+
live_system/ # Provider-specific helpers
|
|
246
|
+
component/
|
|
247
|
+
network_and_compute/iaas/ # AWS, Azure, GCP, OCI, Hetzner components
|
|
248
|
+
network_and_compute/paas/ # AwsEcsCluster, AwsEcsTaskDefinition, AwsEcsService
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Contributing and feedback
|
|
252
|
+
|
|
253
|
+
Contributions and feedback are welcome.
|
|
239
254
|
|
|
240
|
-
|
|
255
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines. Use GitHub Issues for bugs and feature requests, and GitHub Discussions for design questions.
|
|
256
|
+
|
|
257
|
+
## License
|
|
241
258
|
|
|
242
|
-
See the [LICENSE](LICENSE) file for details.
|
|
259
|
+
Licensed under GPLv3. See the [LICENSE](LICENSE) file for details.
|
|
243
260
|
|
|
244
261
|
Made with ❤️ by the Fractal Cloud team.
|
|
245
262
|
|