@terraforge/terraform 0.0.8 → 0.0.10

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 CHANGED
@@ -3,6 +3,59 @@
3
3
 
4
4
  This package is used to build Terraform bridge packages that can be used with @terraforge/core.
5
5
 
6
- ### Installation location
6
+ It works in 3 steps:
7
+
8
+ 1. It provides a build script that generates the typescript typings for your Terraform bridge package.
9
+
10
+ 2. It provides a proxy api that can we used to interact with Terraform resources.
11
+
12
+ 3. It will install the Terraform provider that you specify in your bridge package.
13
+
14
+ ### Terraform Plugin installation location
7
15
 
8
16
  The default installation location is `~/.terraforge/plugins`.
17
+
18
+ ## Usage Guide
19
+
20
+ ### Step 1. Create a package.json
21
+ Create a package.json for the bridge package you want to create.
22
+ In the `package.json` file, you will need to specify the provider details that your package is for.
23
+ Example:
24
+ ```json
25
+ {
26
+ "name": "@terraforge/aws",
27
+ "version": "1.0.0",
28
+ "provider": { // These are the terraform provider details that your package is for.
29
+ "org": "hashicorp",
30
+ "type": "aws",
31
+ "version": "1.0.0"
32
+ },
33
+ "type": "module",
34
+ "files": [
35
+ "dist"
36
+ ],
37
+ "module": "./dist/index.js",
38
+ "types": "./dist/index.d.ts",
39
+ "exports": {
40
+ ".": {
41
+ "import": "./dist/index.js",
42
+ "types": "./dist/index.d.ts"
43
+ }
44
+ },
45
+ "scripts": {
46
+ "build": "bun ../terraform/cli/build-package",
47
+ "prepublishOnly": "bun run build"
48
+ },
49
+ "peerDependencies": {
50
+ "@terraforge/terraform": "workspace:*",
51
+ "@terraforge/core": "workspace:*"
52
+ }
53
+ }
54
+ ```
55
+
56
+ ### Step 2. Publish your package
57
+ You can now publish your package to npm or any other package registry.
58
+ The prepublish script will build the package files inside the `dist` directory before publishing.
59
+
60
+ ### Step 3. There is no step 3
61
+ Sit back and relax, your package is now published and ready to be used.
@@ -13,25 +13,30 @@ const packageData = (await Bun.file('./package.json').json()) as {
13
13
  }
14
14
  }
15
15
 
16
- if (!packageData || !packageData.provider) {
16
+ if (!packageData) {
17
17
  console.error('Failed to read package.json')
18
18
  process.exit(1)
19
19
  }
20
20
 
21
21
  const providerData = packageData.provider
22
22
 
23
+ if (!providerData) {
24
+ console.error('Missing required property: provider')
25
+ process.exit(1)
26
+ }
27
+
23
28
  if (!providerData.version) {
24
- console.error('Missing required arguments: version')
29
+ console.error('Missing required property: provider.version')
25
30
  process.exit(1)
26
31
  }
27
32
 
28
33
  if (!providerData.org) {
29
- console.error('Missing required arguments: org')
34
+ console.error('Missing required property: provider.org')
30
35
  process.exit(1)
31
36
  }
32
37
 
33
38
  if (!providerData.type) {
34
- console.error('Missing required arguments: type')
39
+ console.error('Missing required property: provider.type')
35
40
  process.exit(1)
36
41
  }
37
42
 
@@ -60,29 +65,51 @@ console.log('')
60
65
  console.log('Loading provider plugin...')
61
66
 
62
67
  const plugin = await load()
68
+
69
+ console.log('Provider plugin loaded.')
70
+
63
71
  const schema = plugin.schema()
64
- const types = generateTypes(
65
- {
66
- [type]: schema.provider,
67
- },
68
- schema.resources,
69
- schema.dataSources
70
- )
71
72
 
72
73
  await plugin.stop()
73
74
 
74
- await Bun.write(`./dist/index.d.ts`, types)
75
+ // const installTypes = generateInstallHelperFunctions(type)
76
+ // await Bun.write(`./dist/install.d.ts`, installTypes)
77
+
78
+ // const providerTypes = generateProviderFactoryTypes(type, schema.provider)
79
+ // await Bun.write(`./dist/provider.d.ts`, providerTypes)
80
+
81
+ // const resourceTypes = generateResourceTypes(schema.resources)
82
+ // await Bun.write(`./dist/resources.d.ts`, resourceTypes)
83
+
84
+ // const dataSourceTypes = generateResourceTypes(schema.dataSources)
85
+ // await Bun.write(`./dist/data-sources.d.ts`, dataSourceTypes)
86
+
87
+ // await Bun.write(
88
+ // `./dist/index.d.ts`,
89
+ // `
90
+ // /// <reference path="./install.d.ts" />
91
+ // /// <reference path="./provider.d.ts" />
92
+ // /// <reference path="./resources.d.ts" />
93
+ // /// <reference path="./data-sources.d.ts" />
94
+
95
+ // export { aws }
96
+ // `
97
+ // )
98
+
99
+ await Bun.write(`./dist/index.d.ts`, generateTypes(type, schema.provider, schema.resources, schema.dataSources))
100
+
75
101
  await Bun.write(
76
102
  `./dist/index.js`,
77
103
  `
78
- import { createTerraformAPI } from '@terraforge/terraform'
104
+ import { createTerraformProxy } from '@terraforge/terraform'
79
105
 
80
- export const ${type} = createTerraformAPI({
106
+ export const ${type} = createTerraformProxy({
81
107
  namespace: '${type}',
82
108
  provider: { org: '${org}', type: '${type}', version: '${version}' },
83
109
  })
84
110
  `
85
111
  )
86
112
 
87
- console.log('Done.')
113
+ console.log('')
114
+ console.log('Package done building.')
88
115
  process.exit(0)
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Provider, State as State$1, GetProps, CreateProps, UpdateProps, DeleteProps, GetDataProps } from '@terraforge/core';
1
+ import { Provider, State as State$1, GetProps, CreateProps, UpdateProps, DeleteProps, PlanProps, GetDataProps } from '@terraforge/core';
2
2
 
3
3
  type Property = {
4
4
  description?: string;
@@ -20,7 +20,7 @@ type Property = {
20
20
  type: 'unknown';
21
21
  });
22
22
 
23
- declare const generateTypes: (providers: Record<string, Property>, resources: Record<string, Property>, dataSources: Record<string, Property>) => string;
23
+ declare const generateTypes: (namespace: string, provider: Property, resources: Record<string, Property>, dataSources: Record<string, Property>) => string;
24
24
 
25
25
  type State = Record<string, unknown>;
26
26
  type Plugin = Readonly<{
@@ -66,6 +66,11 @@ declare class TerraformProvider implements Provider {
66
66
  state: State;
67
67
  }>;
68
68
  deleteResource({ type, state }: DeleteProps): Promise<void>;
69
+ planResourceChange({ type, priorState, proposedState }: PlanProps): Promise<{
70
+ version: number;
71
+ requiresReplacement: boolean;
72
+ state: State;
73
+ }>;
69
74
  getData({ type, state }: GetDataProps): Promise<{
70
75
  state: State;
71
76
  }>;
package/dist/index.js CHANGED
@@ -3,49 +3,56 @@ import { camelCase, pascalCase } from "change-case";
3
3
  var tab = (indent) => {
4
4
  return " ".repeat(indent);
5
5
  };
6
- var generateTypes = (providers, resources, dataSources) => {
6
+ var generateTypes = (namespace, provider, resources, dataSources) => {
7
7
  return [
8
8
  generateImport("c", "@terraforge/core"),
9
9
  generateImport("t", "@terraforge/terraform"),
10
10
  "type _Record<T> = Record<string, T>",
11
- generateInstallFunction(providers),
12
- generateNamespace(providers, (name, prop, indent) => {
13
- const typeName = name.toLowerCase();
14
- return `${tab(indent)}export declare function ${typeName}(props: ${generatePropertyInputConst(prop, indent)}, config?: t.TerraformProviderConfig): t.TerraformProvider`;
15
- }),
16
- generateNamespace(resources, (name, prop, indent) => {
17
- const typeName = pascalCase(name);
18
- return [
19
- // `${tab(indent)}export type ${typeName}Input = ${generatePropertyInputType(prop, indent)}`,
20
- // `${tab(indent)}export type ${typeName}Output = ${generatePropertyOutputType(prop, indent)}`,
21
- // `${tab(indent)}export declare const ${typeName}: ResourceClass<${typeName}Input, ${typeName}Output>`,
22
- `${tab(indent)}export type ${typeName}Input = ${generatePropertyInputType(prop, indent)}`,
23
- `${tab(indent)}export type ${typeName}Output = ${generatePropertyOutputType(prop, indent)}`,
24
- `${tab(indent)}export class ${typeName} {`,
25
- `${tab(indent + 1)}constructor(parent: c.Group, id: string, props: ${typeName}Input, config?:c.ResourceConfig)`,
26
- // `${tab(indent + 1)}readonly $: c.ResourceMeta<${typeName}Input, ${typeName}Output>`,
27
- generateClassProperties(prop, indent + 1),
28
- `${tab(indent)}}`
29
- ].join("\n\n");
30
- }),
31
- generateNamespace(dataSources, (name, prop, indent) => {
32
- const typeName = pascalCase(name);
33
- return [
34
- `${tab(indent)}export type Get${typeName}Input = ${generatePropertyInputType(prop, indent)}`,
35
- `${tab(indent)}export type Get${typeName}Output = ${generatePropertyOutputType(prop, indent)}`,
36
- `${tab(indent)}export const get${typeName}:c.DataSourceFunction<Get${typeName}Input, Get${typeName}Output>`
37
- ].join("\n\n");
38
- })
11
+ generateInstallHelperFunctions(namespace),
12
+ generateProviderFactoryTypes(namespace, provider),
13
+ generateResourceTypes(resources),
14
+ generateDataSourceTypes(dataSources)
39
15
  ].join("\n\n");
40
16
  };
17
+ var generateResourceTypes = (resources) => {
18
+ return generateNamespace(resources, (name, prop, indent) => {
19
+ const typeName = pascalCase(name);
20
+ return [
21
+ `${tab(indent)}export type ${typeName}Input = ${generatePropertyInputType(prop, indent)}`,
22
+ `${tab(indent)}export type ${typeName}Output = ${generatePropertyOutputType(prop, indent)}`,
23
+ `${tab(indent)}export class ${typeName} {`,
24
+ `${tab(indent + 1)}constructor(parent: c.Group, id: string, props: ${typeName}Input, config?:c.ResourceConfig)`,
25
+ generateClassProperties(prop, indent + 1),
26
+ `${tab(indent)}}`
27
+ ].join("\n\n");
28
+ });
29
+ };
30
+ var generateDataSourceTypes = (dataSources) => {
31
+ return generateNamespace(dataSources, (name, prop, indent) => {
32
+ const typeName = pascalCase(name);
33
+ return [
34
+ `${tab(indent)}export type Get${typeName}Input = ${generatePropertyInputType(prop, indent)}`,
35
+ `${tab(indent)}export type Get${typeName}Output = ${generatePropertyOutputType(prop, indent)}`,
36
+ `${tab(indent)}export const get${typeName}:c.DataSourceFunction<Get${typeName}Input, Get${typeName}Output>`
37
+ ].join("\n\n");
38
+ });
39
+ };
40
+ var generateProviderFactoryTypes = (namespace, provider) => {
41
+ const typeName = namespace.toLowerCase();
42
+ return `export declare function ${typeName}(props: ${generatePropertyInputConst(provider, 0)}, config?: t.TerraformProviderConfig): t.TerraformProvider`;
43
+ };
41
44
  var generateImport = (name, from) => {
42
45
  return `import * as ${name} from '${from}'`;
43
46
  };
44
- var generateInstallFunction = (resources) => {
45
- return generateNamespace(resources, (name, _prop, indent) => {
46
- const typeName = name.toLowerCase();
47
- return `${tab(indent)}export declare namespace ${typeName} { export function install(props?: t.InstallProps): Promise<void> }`;
48
- });
47
+ var generateInstallHelperFunctions = (namespace) => {
48
+ const typeName = namespace.toLowerCase();
49
+ return [
50
+ `export declare namespace ${typeName} {`,
51
+ `${tab(1)}export function install(props?: t.InstallProps): Promise<void>`,
52
+ `${tab(1)}export function uninstall(props?: t.InstallProps): Promise<void>`,
53
+ `${tab(1)}export function isInstalled(props?: t.InstallProps): Promise<boolean>`,
54
+ `}`
55
+ ].join("\n");
49
56
  };
50
57
  var generatePropertyInputConst = (prop, indent) => {
51
58
  return generateValue(prop, {
@@ -71,8 +78,8 @@ var generatePropertyInputType = (prop, indent) => {
71
78
  };
72
79
  var generatePropertyOutputType = (prop, indent) => {
73
80
  return generateValue(prop, {
74
- indent: indent + 1,
75
81
  depth: 0,
82
+ indent: indent + 1,
76
83
  wrap: (v, p, ctx) => ctx.depth === 1 ? p.optional && !p.computed ? `c.OptionalOutput<${v}>` : `c.Output<${v}>` : v,
77
84
  filter: () => true,
78
85
  readonly: true,
@@ -278,6 +285,15 @@ var TerraformProvider = class {
278
285
  throw error;
279
286
  }
280
287
  }
288
+ async planResourceChange({ type, priorState, proposedState }) {
289
+ const plugin = await this.configure();
290
+ const result = await plugin.planResourceChange(type, priorState, proposedState);
291
+ return {
292
+ version: 0,
293
+ requiresReplacement: result.requiresReplace.length > 0,
294
+ state: result.plannedState
295
+ };
296
+ }
281
297
  async getData({ type, state }) {
282
298
  const plugin = await this.configure();
283
299
  const data = await plugin.readDataSource(type, state);
@@ -1047,9 +1063,9 @@ var createPluginClient = async (props) => {
1047
1063
  // src/plugin/download.ts
1048
1064
  import { createDebugger as createDebugger2 } from "@terraforge/core";
1049
1065
  import jszip from "jszip";
1050
- import { mkdir, stat, writeFile } from "fs/promises";
1066
+ import { mkdir, rm, stat, writeFile } from "fs/promises";
1051
1067
  import { homedir } from "os";
1052
- import { join } from "path";
1068
+ import { dirname, join } from "path";
1053
1069
 
1054
1070
  // src/plugin/registry.ts
1055
1071
  import { arch, platform } from "os";
@@ -1136,15 +1152,33 @@ var exists = async (file) => {
1136
1152
  };
1137
1153
  var debug2 = createDebugger2("Downloader");
1138
1154
  var installPath = join(homedir(), ".terraforge", "plugins");
1155
+ var getInstallPath = (props) => {
1156
+ const dir = props.location ?? installPath;
1157
+ const file = join(dir, `${props.org}-${props.type}-${props.version}`);
1158
+ return file;
1159
+ };
1160
+ var isPluginInstalled = (props) => {
1161
+ return exists(getInstallPath(props));
1162
+ };
1163
+ var deletePlugin = async (props) => {
1164
+ const file = getInstallPath(props);
1165
+ const isAlreadyInstalled = await isPluginInstalled(props);
1166
+ if (isAlreadyInstalled) {
1167
+ debug2(props.type, "deleting...");
1168
+ await rm(file);
1169
+ debug2(props.type, "deleted");
1170
+ } else {
1171
+ debug2(props.type, "not installed");
1172
+ }
1173
+ };
1139
1174
  var downloadPlugin = async (props) => {
1140
1175
  if (props.version === "latest") {
1141
1176
  const { latest } = await getProviderVersions(props.org, props.type);
1142
1177
  props.version = latest;
1143
1178
  }
1144
- const dir = props.location ?? installPath;
1145
- const file = join(dir, `${props.org}-${props.type}-${props.version}`);
1146
- const exist = await exists(file);
1147
- if (!exist) {
1179
+ const file = getInstallPath(props);
1180
+ const isAlreadyInstalled = await isPluginInstalled(props);
1181
+ if (!isAlreadyInstalled) {
1148
1182
  debug2(props.type, "downloading...");
1149
1183
  const info = await getProviderDownloadUrl(props.org, props.type, props.version);
1150
1184
  const res = await fetch(info.url);
@@ -1156,7 +1190,7 @@ var downloadPlugin = async (props) => {
1156
1190
  }
1157
1191
  const binary = await zipped.async("nodebuffer");
1158
1192
  debug2(props.type, "done");
1159
- await mkdir(dir, { recursive: true });
1193
+ await mkdir(dirname(file), { recursive: true });
1160
1194
  await writeFile(file, binary, {
1161
1195
  mode: 509
1162
1196
  });
@@ -1863,6 +1897,8 @@ var createClassProxy = (construct, get) => {
1863
1897
  var createRecursiveProxy = ({
1864
1898
  provider,
1865
1899
  install,
1900
+ uninstall,
1901
+ isInstalled,
1866
1902
  resource,
1867
1903
  dataSource
1868
1904
  }) => {
@@ -1890,6 +1926,12 @@ var createRecursiveProxy = ({
1890
1926
  if (key === "install") {
1891
1927
  return install;
1892
1928
  }
1929
+ if (key === "uninstall") {
1930
+ return uninstall;
1931
+ }
1932
+ if (key === "isInstalled") {
1933
+ return isInstalled;
1934
+ }
1893
1935
  return findNextProxy([], key);
1894
1936
  });
1895
1937
  };
@@ -1909,6 +1951,12 @@ var createTerraformProxy = (props) => {
1909
1951
  async install(installProps) {
1910
1952
  await downloadPlugin({ ...props.provider, ...installProps });
1911
1953
  },
1954
+ async uninstall(installProps) {
1955
+ await deletePlugin({ ...props.provider, ...installProps });
1956
+ },
1957
+ isInstalled(installProps) {
1958
+ return isPluginInstalled({ ...props.provider, ...installProps });
1959
+ },
1912
1960
  resource: (ns, parent, id, input, config) => {
1913
1961
  const type = snakeCase2([props.namespace, ...ns].join("_"));
1914
1962
  const provider = `terraform:${props.namespace}:${config?.provider ?? "default"}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@terraforge/terraform",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -22,7 +22,7 @@
22
22
  },
23
23
  "scripts": {
24
24
  "build": "tsup src/index.ts --format esm --dts --clean --out-dir ./dist",
25
- "prepublishOnly": "bun run build",
25
+ "prepublishOnly": "if bun run test; then bun run build; else exit; fi",
26
26
  "test": "bun test"
27
27
  },
28
28
  "peerDependencies": {