@terraforge/terraform 0.0.7 → 0.0.9

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.
@@ -1,61 +1,69 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
- import { parseArgs } from 'util'
4
3
  import { createLazyPlugin } from '../src/lazy-plugin'
5
4
  import { Version } from '../src/plugin/registry'
6
5
  import { generateTypes } from '../src/type-gen'
7
6
 
8
- const { values } = parseArgs({
9
- args: Bun.argv,
10
- options: {
11
- org: {
12
- type: 'string',
13
- },
14
- type: {
15
- type: 'string',
16
- },
17
- },
18
- strict: true,
19
- allowPositionals: true,
20
- })
7
+ const packageData = (await Bun.file('./package.json').json()) as {
8
+ version?: string
9
+ provider?: {
10
+ version?: Version
11
+ org?: string
12
+ type?: string
13
+ }
14
+ }
21
15
 
22
- if (!values.org) {
23
- console.error('Missing required arguments: org')
16
+ if (!packageData) {
17
+ console.error('Failed to read package.json')
24
18
  process.exit(1)
25
19
  }
26
20
 
27
- if (!values.type) {
28
- console.error('Missing required arguments: type')
21
+ const providerData = packageData.provider
22
+
23
+ if (!providerData) {
24
+ console.error('Missing required property: provider')
29
25
  process.exit(1)
30
26
  }
31
27
 
32
- const packageData = (await Bun.file('./package.json').json()) as { version?: Version }
28
+ if (!providerData.version) {
29
+ console.error('Missing required property: provider.version')
30
+ process.exit(1)
31
+ }
33
32
 
34
- if (!packageData) {
35
- console.error('Failed to read package.json')
33
+ if (!providerData.org) {
34
+ console.error('Missing required property: provider.org')
36
35
  process.exit(1)
37
36
  }
38
37
 
39
- if (!packageData.version) {
40
- console.error('Missing required arguments: version')
38
+ if (!providerData.type) {
39
+ console.error('Missing required property: provider.type')
41
40
  process.exit(1)
42
41
  }
43
42
 
44
- const org = values.org
45
- const type = values.type
46
- const version = packageData.version
43
+ const org = providerData.org
44
+ const type = providerData.type
45
+ const version = providerData.version
47
46
 
48
- console.log('org: ', org)
49
- console.log('type: ', type)
50
- console.log('version: ', version)
47
+ console.log('')
48
+ console.log('Package version: ', packageData.version)
49
+ console.log('')
50
+ console.log('Provider org: ', org)
51
+ console.log('Provider type: ', type)
52
+ console.log('Provider version: ', version)
53
+ console.log('')
51
54
 
52
55
  const ok = confirm('Continue?')
53
56
 
54
57
  if (!ok) {
55
- process.exit(0)
58
+ console.log('')
59
+ process.exit(1)
56
60
  }
57
61
 
58
62
  const load = createLazyPlugin({ org, type, version })
63
+
64
+ console.log('')
65
+ console.log('Loading provider plugin...')
66
+
59
67
  const plugin = await load()
60
68
  const schema = plugin.schema()
61
69
  const types = generateTypes(
@@ -68,24 +76,18 @@ const types = generateTypes(
68
76
 
69
77
  await plugin.stop()
70
78
 
71
- await Bun.write(`./src/types.ts`, types)
72
-
79
+ await Bun.write(`./dist/index.d.ts`, types)
73
80
  await Bun.write(
74
- `./src/index.ts`,
81
+ `./dist/index.js`,
75
82
  `
76
- import { createTerraformAPI } from '@terraforge/terraform'
77
- import { root } from './types.ts'
83
+ import { createTerraformProxy } from '@terraforge/terraform'
78
84
 
79
- // @ts-ignore
80
- export const ${type} = createTerraformAPI<typeof root.${type}>({
85
+ export const ${type} = createTerraformProxy({
81
86
  namespace: '${type}',
82
87
  provider: { org: '${org}', type: '${type}', version: '${version}' },
83
- }) as typeof root.${type}
84
-
85
- declare module '@terraforge/${type}' {
86
- import ${type} = root.${type}
87
- // @ts-ignore
88
- export { ${type} }
89
- }
88
+ })
90
89
  `
91
90
  )
91
+
92
+ console.log('Done.')
93
+ process.exit(0)
package/dist/index.d.ts CHANGED
@@ -1,23 +1,5 @@
1
1
  import { Provider, State as State$1, GetProps, CreateProps, UpdateProps, DeleteProps, GetDataProps } from '@terraforge/core';
2
2
 
3
- type Version = `${number}.${number}.${number}` | 'latest';
4
-
5
- type TerraformProviderConfig = {
6
- id?: string;
7
- location?: string;
8
- };
9
- type InstallProps = {
10
- location?: string;
11
- };
12
- declare const createTerraformAPI: <T>(props: {
13
- namespace: string;
14
- provider: {
15
- org: string;
16
- type: string;
17
- version: Version;
18
- };
19
- }) => T;
20
-
21
3
  type Property = {
22
4
  description?: string;
23
5
  required?: boolean;
@@ -89,4 +71,22 @@ declare class TerraformProvider implements Provider {
89
71
  }>;
90
72
  }
91
73
 
92
- export { type InstallProps, TerraformProvider, type TerraformProviderConfig, createTerraformAPI, generateTypes };
74
+ type Version = `${number}.${number}.${number}` | 'latest';
75
+
76
+ type TerraformProviderConfig = {
77
+ id?: string;
78
+ location?: string;
79
+ };
80
+ type InstallProps = {
81
+ location?: string;
82
+ };
83
+ declare const createTerraformProxy: (props: {
84
+ namespace: string;
85
+ provider: {
86
+ org: string;
87
+ type: string;
88
+ version: Version;
89
+ };
90
+ }) => () => void;
91
+
92
+ export { type InstallProps, TerraformProvider, type TerraformProviderConfig, createTerraformProxy, generateTypes };
package/dist/index.js CHANGED
@@ -1,3 +1,319 @@
1
+ // src/type-gen.ts
2
+ import { camelCase, pascalCase } from "change-case";
3
+ var tab = (indent) => {
4
+ return " ".repeat(indent);
5
+ };
6
+ var generateTypes = (providers, resources, dataSources) => {
7
+ return [
8
+ generateImport("c", "@terraforge/core"),
9
+ generateImport("t", "@terraforge/terraform"),
10
+ "type _Record<T> = Record<string, T>",
11
+ generateInstallHelperFunctions(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
+ })
39
+ ].join("\n\n");
40
+ };
41
+ var generateImport = (name, from) => {
42
+ return `import * as ${name} from '${from}'`;
43
+ };
44
+ var generateInstallHelperFunctions = (resources) => {
45
+ return generateNamespace(resources, (name, _prop, indent) => {
46
+ const typeName = name.toLowerCase();
47
+ return [
48
+ `${tab(indent)}export declare namespace ${typeName} {`,
49
+ `${tab(indent + 1)}export function install(props?: t.InstallProps): Promise<void>`,
50
+ `${tab(indent + 1)}export function uninstall(props?: t.InstallProps): Promise<void>`,
51
+ `${tab(indent + 1)}export function isInstalled(props?: t.InstallProps): Promise<boolean>`,
52
+ `${tab(indent)}}`
53
+ ].join("\n");
54
+ });
55
+ };
56
+ var generatePropertyInputConst = (prop, indent) => {
57
+ return generateValue(prop, {
58
+ depth: 0,
59
+ indent: indent + 1,
60
+ wrap: (v, _, ctx) => {
61
+ return `${v}${ctx.depth === 1 ? "," : ""}`;
62
+ },
63
+ filter: () => true,
64
+ optional: (p) => p.optional ?? false
65
+ });
66
+ };
67
+ var generatePropertyInputType = (prop, indent) => {
68
+ return generateValue(prop, {
69
+ depth: 0,
70
+ indent: indent + 1,
71
+ wrap: (v, p, ctx) => {
72
+ return ctx.depth > 0 ? p.optional ? `c.OptionalInput<${v}>` : `c.Input<${v}>` : v;
73
+ },
74
+ filter: (prop2) => !(prop2.computed && typeof prop2.optional === "undefined" && typeof prop2.required === "undefined"),
75
+ optional: (p) => p.optional ?? false
76
+ });
77
+ };
78
+ var generatePropertyOutputType = (prop, indent) => {
79
+ return generateValue(prop, {
80
+ depth: 0,
81
+ indent: indent + 1,
82
+ wrap: (v, p, ctx) => ctx.depth === 1 ? p.optional && !p.computed ? `c.OptionalOutput<${v}>` : `c.Output<${v}>` : v,
83
+ filter: () => true,
84
+ readonly: true,
85
+ // required: true,
86
+ optional: (p, ctx) => ctx.depth > 1 && p.optional && !p.computed || false
87
+ });
88
+ };
89
+ var generateClassProperties = (prop, indent) => {
90
+ if (prop.type !== "object") {
91
+ return "";
92
+ }
93
+ return Object.entries(prop.properties).map(([name, prop2]) => {
94
+ return [
95
+ prop2.description ? [`
96
+ `, ` `.repeat(indent), `/** `, prop2.description.trim(), " */", "\n"].join("") : "",
97
+ ` `.repeat(indent),
98
+ "readonly ",
99
+ camelCase(name),
100
+ // ctx.optional(prop, ctx) ? '?' : '',
101
+ ": ",
102
+ generateValue(prop2, {
103
+ readonly: true,
104
+ filter: () => true,
105
+ optional: (p, ctx) => ctx.depth > 1 && p.optional && !p.computed || false,
106
+ wrap: (v, p, ctx) => {
107
+ return ctx.depth === 1 ? p.optional && !p.computed ? `c.OptionalOutput<${v}>` : `c.Output<${v}>` : v;
108
+ },
109
+ // ctx.depth === 1 ? `c.Output<${p.optional && !p.computed ? `${v} | undefined` : v}>` : v,
110
+ indent: indent + 1,
111
+ depth: 1
112
+ })
113
+ ].join("");
114
+ }).join("\n");
115
+ };
116
+ var groupByNamespace = (resources, minLevel, maxLevel) => {
117
+ const grouped = {};
118
+ const types = Object.keys(resources).sort();
119
+ for (const type of types) {
120
+ const names = type.split("_");
121
+ if (names.length < minLevel) {
122
+ throw new Error(`Resource not properly namespaced: ${type}`);
123
+ }
124
+ let current = grouped;
125
+ let count = Math.min(maxLevel, names.length - 1);
126
+ while (count--) {
127
+ const ns = camelCase(names.shift());
128
+ if (!current[ns]) {
129
+ current[ns] = {};
130
+ }
131
+ current = current[ns];
132
+ }
133
+ const name = pascalCase(names.join("_"));
134
+ current[name] = type;
135
+ }
136
+ return grouped;
137
+ };
138
+ var generateNamespace = (resources, render) => {
139
+ const grouped = groupByNamespace(resources, 1, 2);
140
+ const renderNamespace = (name, group, indent) => {
141
+ if (name === "default") {
142
+ name = "$default";
143
+ }
144
+ if (typeof group === "string") {
145
+ return render(name, resources[group], indent);
146
+ }
147
+ return [
148
+ `${tab(indent)}export ${indent === 0 ? "declare " : ""}namespace ${name.toLowerCase()} {`,
149
+ Object.entries(group).map(([name2, entry]) => {
150
+ if (typeof entry !== "string") {
151
+ return renderNamespace(name2, entry, indent + 1);
152
+ } else {
153
+ return render(name2, resources[entry], indent + 1);
154
+ }
155
+ }).join("\n"),
156
+ `${tab(indent)}}`
157
+ ].join("\n");
158
+ };
159
+ return Object.entries(grouped).map(([name, entry]) => {
160
+ return renderNamespace(name, entry, 0);
161
+ });
162
+ };
163
+ var generateValue = (prop, ctx) => {
164
+ if (["string", "number", "boolean", "unknown"].includes(prop.type)) {
165
+ return ctx.wrap(prop.type, prop, ctx);
166
+ }
167
+ if (prop.type === "array") {
168
+ const type = generateValue(prop.item, { ...ctx, depth: ctx.depth + 1 });
169
+ const array = ctx.readonly ? `ReadonlyArray<${type}>` : `Array<${type}>`;
170
+ return ctx.wrap(array, prop, ctx);
171
+ }
172
+ if (prop.type === "record") {
173
+ const type = generateValue(prop.item, { ...ctx, depth: ctx.depth + 1 });
174
+ const record = ctx.readonly ? `Readonly<_Record<${type}>>` : `_Record<${type}>`;
175
+ return ctx.wrap(record, prop, ctx);
176
+ }
177
+ if (prop.type === "object" || prop.type === "array-object") {
178
+ const type = [
179
+ "{",
180
+ Object.entries(prop.properties).filter(([_, p]) => ctx.filter(p)).map(
181
+ ([name, prop2]) => [
182
+ prop2.description ? [`
183
+ `, ` `.repeat(ctx.indent), `/** `, prop2.description.trim(), " */", "\n"].join("") : "",
184
+ ` `.repeat(ctx.indent),
185
+ // ctx.readonly ? "readonly " : "",
186
+ camelCase(name),
187
+ ctx.optional(prop2, ctx) ? "?" : "",
188
+ ": ",
189
+ generateValue(prop2, { ...ctx, indent: ctx.indent + 1, depth: ctx.depth + 1 })
190
+ ].join("")
191
+ ).join("\n"),
192
+ `${` `.repeat(ctx.indent - 1)}}`
193
+ ].join("\n");
194
+ const object = ctx.readonly ? `Readonly<${type}>` : type;
195
+ return ctx.wrap(object, prop, ctx);
196
+ }
197
+ throw new Error(`Unknown property type: ${prop.type}`);
198
+ };
199
+
200
+ // src/provider.ts
201
+ import {
202
+ ResourceNotFound
203
+ } from "@terraforge/core";
204
+ var TerraformProvider = class {
205
+ constructor(type, id, createPlugin, config) {
206
+ this.type = type;
207
+ this.id = id;
208
+ this.createPlugin = createPlugin;
209
+ this.config = config;
210
+ }
211
+ configured;
212
+ plugin;
213
+ async configure() {
214
+ const plugin = await this.prepare();
215
+ if (!this.configured) {
216
+ this.configured = plugin.configure(this.config);
217
+ }
218
+ await this.configured;
219
+ return plugin;
220
+ }
221
+ prepare() {
222
+ if (!this.plugin) {
223
+ this.plugin = this.createPlugin();
224
+ }
225
+ return this.plugin;
226
+ }
227
+ async destroy() {
228
+ if (this.plugin) {
229
+ const plugin = await this.plugin;
230
+ plugin.stop();
231
+ this.plugin = void 0;
232
+ this.configured = void 0;
233
+ }
234
+ }
235
+ ownResource(id) {
236
+ return `terraform:${this.type}:${this.id}` === id;
237
+ }
238
+ async getResource({ type, state }) {
239
+ const plugin = await this.configure();
240
+ const newState = await plugin.readResource(type, state);
241
+ if (!newState) {
242
+ throw new ResourceNotFound();
243
+ }
244
+ return {
245
+ version: 0,
246
+ state: newState
247
+ };
248
+ }
249
+ async createResource({ type, state }) {
250
+ const plugin = await this.configure();
251
+ const newState = await plugin.applyResourceChange(type, null, state);
252
+ return {
253
+ version: 0,
254
+ state: newState
255
+ };
256
+ }
257
+ async updateResource({ type, priorState, proposedState }) {
258
+ const plugin = await this.configure();
259
+ const { requiresReplace } = await plugin.planResourceChange(type, priorState, proposedState);
260
+ if (requiresReplace.length > 0) {
261
+ const formattedAttrs = requiresReplace.map((p) => p.join(".")).join('", "');
262
+ throw new Error(
263
+ `Updating the "${formattedAttrs}" properties for the "${type}" resource will require the resource to be replaced.`
264
+ );
265
+ }
266
+ const newState = await plugin.applyResourceChange(type, priorState, proposedState);
267
+ return {
268
+ version: 0,
269
+ state: newState
270
+ };
271
+ }
272
+ async deleteResource({ type, state }) {
273
+ const plugin = await this.configure();
274
+ try {
275
+ await plugin.applyResourceChange(type, state, null);
276
+ } catch (error) {
277
+ try {
278
+ const newState = await plugin.readResource(type, state);
279
+ if (!newState) {
280
+ throw new ResourceNotFound();
281
+ }
282
+ } catch (_) {
283
+ }
284
+ throw error;
285
+ }
286
+ }
287
+ async getData({ type, state }) {
288
+ const plugin = await this.configure();
289
+ const data = await plugin.readDataSource(type, state);
290
+ if (!data) {
291
+ throw new Error(`Data source not found ${type}`);
292
+ }
293
+ return {
294
+ state: data
295
+ };
296
+ }
297
+ // async generateTypes(dir: string) {
298
+ // const plugin = await this.prepare()
299
+ // const schema = plugin.schema()
300
+ // const types = generateTypes(
301
+ // {
302
+ // [`${this.type}_provider`]: schema.provider,
303
+ // },
304
+ // schema.resources,
305
+ // schema.dataSources
306
+ // )
307
+ // await mkdir(dir, { recursive: true })
308
+ // await writeFile(join(dir, `${this.type}.d.ts`), types)
309
+ // await this.destroy()
310
+ // }
311
+ };
312
+
313
+ // src/proxy.ts
314
+ import { createMeta, nodeMetaSymbol } from "@terraforge/core";
315
+ import { snakeCase as snakeCase2 } from "change-case";
316
+
1
317
  // src/plugin/client.ts
2
318
  import { credentials, loadPackageDefinition } from "@grpc/grpc-js";
3
319
  import { fromJSON } from "@grpc/proto-loader";
@@ -737,9 +1053,9 @@ var createPluginClient = async (props) => {
737
1053
  // src/plugin/download.ts
738
1054
  import { createDebugger as createDebugger2 } from "@terraforge/core";
739
1055
  import jszip from "jszip";
740
- import { mkdir, stat, writeFile } from "fs/promises";
1056
+ import { mkdir, rm, stat, writeFile } from "fs/promises";
741
1057
  import { homedir } from "os";
742
- import { join } from "path";
1058
+ import { dirname, join } from "path";
743
1059
 
744
1060
  // src/plugin/registry.ts
745
1061
  import { arch, platform } from "os";
@@ -826,15 +1142,33 @@ var exists = async (file) => {
826
1142
  };
827
1143
  var debug2 = createDebugger2("Downloader");
828
1144
  var installPath = join(homedir(), ".terraforge", "plugins");
1145
+ var getInstallPath = (props) => {
1146
+ const dir = props.location ?? installPath;
1147
+ const file = join(dir, `${props.org}-${props.type}-${props.version}`);
1148
+ return file;
1149
+ };
1150
+ var isPluginInstalled = (props) => {
1151
+ return exists(getInstallPath(props));
1152
+ };
1153
+ var deletePlugin = async (props) => {
1154
+ const file = getInstallPath(props);
1155
+ const isAlreadyInstalled = await isPluginInstalled(props);
1156
+ if (isAlreadyInstalled) {
1157
+ debug2(props.type, "deleting...");
1158
+ await rm(file);
1159
+ debug2(props.type, "deleted");
1160
+ } else {
1161
+ debug2(props.type, "not installed");
1162
+ }
1163
+ };
829
1164
  var downloadPlugin = async (props) => {
830
1165
  if (props.version === "latest") {
831
1166
  const { latest } = await getProviderVersions(props.org, props.type);
832
1167
  props.version = latest;
833
1168
  }
834
- const dir = props.location ?? installPath;
835
- const file = join(dir, `${props.org}-${props.type}-${props.version}`);
836
- const exist = await exists(file);
837
- if (!exist) {
1169
+ const file = getInstallPath(props);
1170
+ const isAlreadyInstalled = await isPluginInstalled(props);
1171
+ if (!isAlreadyInstalled) {
838
1172
  debug2(props.type, "downloading...");
839
1173
  const info = await getProviderDownloadUrl(props.org, props.type, props.version);
840
1174
  const res = await fetch(info.url);
@@ -846,7 +1180,7 @@ var downloadPlugin = async (props) => {
846
1180
  }
847
1181
  const binary = await zipped.async("nodebuffer");
848
1182
  debug2(props.type, "done");
849
- await mkdir(dir, { recursive: true });
1183
+ await mkdir(dirname(file), { recursive: true });
850
1184
  await writeFile(file, binary, {
851
1185
  mode: 509
852
1186
  });
@@ -1090,7 +1424,7 @@ var parseType = (type) => {
1090
1424
  };
1091
1425
 
1092
1426
  // src/plugin/version/util.ts
1093
- import { camelCase, snakeCase } from "change-case";
1427
+ import { camelCase as camelCase2, snakeCase } from "change-case";
1094
1428
  import { pack, unpack } from "msgpackr";
1095
1429
  var encodeDynamicValue = (value) => {
1096
1430
  return {
@@ -1184,7 +1518,7 @@ var formatInputState = (schema, state, includeSchemaFields = true, path = []) =>
1184
1518
  const object = {};
1185
1519
  if (includeSchemaFields) {
1186
1520
  for (const [key, prop] of Object.entries(schema.properties)) {
1187
- const value = state[camelCase(key)];
1521
+ const value = state[camelCase2(key)];
1188
1522
  object[key] = formatInputState(prop, value, true, [...path, key]);
1189
1523
  }
1190
1524
  } else {
@@ -1229,7 +1563,7 @@ var formatOutputState = (schema, state, path = []) => {
1229
1563
  const object = {};
1230
1564
  for (const [key, prop] of Object.entries(schema.properties)) {
1231
1565
  const value = state[key];
1232
- object[camelCase(key)] = formatOutputState(prop, value, [...path, key]);
1566
+ object[camelCase2(key)] = formatOutputState(prop, value, [...path, key]);
1233
1567
  }
1234
1568
  return object;
1235
1569
  }
@@ -1241,7 +1575,7 @@ var formatOutputState = (schema, state, path = []) => {
1241
1575
  const object = {};
1242
1576
  for (const [key, prop] of Object.entries(schema.properties)) {
1243
1577
  const value = state[0][key];
1244
- object[camelCase(key)] = formatOutputState(prop, value, [...path, key]);
1578
+ object[camelCase2(key)] = formatOutputState(prop, value, [...path, key]);
1245
1579
  }
1246
1580
  return object;
1247
1581
  } else {
@@ -1474,133 +1808,37 @@ var retry = async (tries, cb) => {
1474
1808
  throw latestError;
1475
1809
  };
1476
1810
 
1477
- // src/provider.ts
1478
- import {
1479
- ResourceNotFound
1480
- } from "@terraforge/core";
1481
- var TerraformProvider = class {
1482
- constructor(type, id, createPlugin, config) {
1483
- this.type = type;
1484
- this.id = id;
1485
- this.createPlugin = createPlugin;
1486
- this.config = config;
1487
- }
1488
- configured;
1489
- plugin;
1490
- async configure() {
1491
- const plugin = await this.prepare();
1492
- if (!this.configured) {
1493
- this.configured = plugin.configure(this.config);
1494
- }
1495
- await this.configured;
1496
- return plugin;
1497
- }
1498
- prepare() {
1499
- if (!this.plugin) {
1500
- this.plugin = this.createPlugin();
1501
- }
1502
- return this.plugin;
1503
- }
1504
- async destroy() {
1505
- if (this.plugin) {
1506
- const plugin = await this.plugin;
1507
- plugin.stop();
1508
- this.plugin = void 0;
1509
- this.configured = void 0;
1510
- }
1511
- }
1512
- ownResource(id) {
1513
- return `terraform:${this.type}:${this.id}` === id;
1514
- }
1515
- async getResource({ type, state }) {
1516
- const plugin = await this.configure();
1517
- const newState = await plugin.readResource(type, state);
1518
- if (!newState) {
1519
- throw new ResourceNotFound();
1520
- }
1521
- return {
1522
- version: 0,
1523
- state: newState
1524
- };
1525
- }
1526
- async createResource({ type, state }) {
1527
- const plugin = await this.configure();
1528
- const newState = await plugin.applyResourceChange(type, null, state);
1529
- return {
1530
- version: 0,
1531
- state: newState
1532
- };
1533
- }
1534
- async updateResource({ type, priorState, proposedState }) {
1535
- const plugin = await this.configure();
1536
- const { requiresReplace } = await plugin.planResourceChange(type, priorState, proposedState);
1537
- if (requiresReplace.length > 0) {
1538
- const formattedAttrs = requiresReplace.map((p) => p.join(".")).join('", "');
1539
- throw new Error(
1540
- `Updating the "${formattedAttrs}" properties for the "${type}" resource will require the resource to be replaced.`
1541
- );
1542
- }
1543
- const newState = await plugin.applyResourceChange(type, priorState, proposedState);
1544
- return {
1545
- version: 0,
1546
- state: newState
1547
- };
1548
- }
1549
- async deleteResource({ type, state }) {
1550
- const plugin = await this.configure();
1551
- try {
1552
- await plugin.applyResourceChange(type, state, null);
1553
- } catch (error) {
1554
- try {
1555
- const newState = await plugin.readResource(type, state);
1556
- if (!newState) {
1557
- throw new ResourceNotFound();
1811
+ // src/proxy.ts
1812
+ var createResourceProxy = (cb) => {
1813
+ return new Proxy(
1814
+ {},
1815
+ {
1816
+ get(_, key) {
1817
+ return cb(key);
1818
+ },
1819
+ set(_, key) {
1820
+ if (typeof key === "string") {
1821
+ throw new Error(`Cannot set property ${key} on read-only object.`);
1558
1822
  }
1559
- } catch (_) {
1823
+ throw new Error(`This object is read-only.`);
1560
1824
  }
1561
- throw error;
1562
1825
  }
1563
- }
1564
- async getData({ type, state }) {
1565
- const plugin = await this.configure();
1566
- const data = await plugin.readDataSource(type, state);
1567
- if (!data) {
1568
- throw new Error(`Data source not found ${type}`);
1569
- }
1570
- return {
1571
- state: data
1572
- };
1573
- }
1574
- // async generateTypes(dir: string) {
1575
- // const plugin = await this.prepare()
1576
- // const schema = plugin.schema()
1577
- // const types = generateTypes(
1578
- // {
1579
- // [`${this.type}_provider`]: schema.provider,
1580
- // },
1581
- // schema.resources,
1582
- // schema.dataSources
1583
- // )
1584
- // await mkdir(dir, { recursive: true })
1585
- // await writeFile(join(dir, `${this.type}.d.ts`), types)
1586
- // await this.destroy()
1587
- // }
1826
+ );
1588
1827
  };
1589
-
1590
- // src/resource.ts
1591
- import { createMeta, nodeMetaSymbol } from "@terraforge/core";
1592
- import { snakeCase as snakeCase2 } from "change-case";
1593
- var createNamespaceProxy = (cb, scb) => {
1828
+ var createNamespaceProxy = (cb) => {
1594
1829
  const cache = /* @__PURE__ */ new Map();
1595
1830
  return new Proxy(
1596
1831
  {},
1597
1832
  {
1598
1833
  get(_, key) {
1599
- if (!cache.has(key)) {
1600
- const value = typeof key === "symbol" ? scb?.(key) : cb(key);
1601
- cache.set(key, value);
1834
+ if (typeof key === "string") {
1835
+ if (!cache.has(key)) {
1836
+ const value = cb(key);
1837
+ cache.set(key, value);
1838
+ }
1839
+ return cache.get(key);
1602
1840
  }
1603
- return cache.get(key);
1841
+ return;
1604
1842
  },
1605
1843
  set(_, key) {
1606
1844
  if (typeof key === "string") {
@@ -1611,6 +1849,25 @@ var createNamespaceProxy = (cb, scb) => {
1611
1849
  }
1612
1850
  );
1613
1851
  };
1852
+ var createRootProxy = (apply, get) => {
1853
+ const cache = /* @__PURE__ */ new Map();
1854
+ return new Proxy(() => {
1855
+ }, {
1856
+ apply(_, _this, args) {
1857
+ return apply(...args);
1858
+ },
1859
+ get(_, key) {
1860
+ if (typeof key === "string") {
1861
+ if (!cache.has(key)) {
1862
+ const value = get(key);
1863
+ cache.set(key, value);
1864
+ }
1865
+ return cache.get(key);
1866
+ }
1867
+ return;
1868
+ }
1869
+ });
1870
+ };
1614
1871
  var createClassProxy = (construct, get) => {
1615
1872
  return new Proxy(class {
1616
1873
  }, {
@@ -1628,307 +1885,102 @@ var createClassProxy = (construct, get) => {
1628
1885
  });
1629
1886
  };
1630
1887
  var createRecursiveProxy = ({
1888
+ provider,
1889
+ install,
1890
+ uninstall,
1891
+ isInstalled,
1631
1892
  resource,
1632
1893
  dataSource
1633
1894
  }) => {
1634
- const createProxy = (names) => {
1635
- return createNamespaceProxy((name) => {
1636
- const ns = [...names, name];
1637
- if (name === name.toLowerCase()) {
1638
- return createProxy(ns);
1639
- } else if (name.startsWith("get")) {
1640
- return (...args) => {
1641
- return dataSource([...names, name.substring(3)], ...args);
1642
- };
1643
- } else {
1644
- return createClassProxy(
1645
- (...args) => {
1646
- return resource(ns, ...args);
1647
- },
1648
- (...args) => {
1649
- return dataSource(ns, ...args);
1650
- }
1651
- );
1652
- }
1653
- });
1895
+ const findNextProxy = (ns, name) => {
1896
+ if (name === name.toLowerCase()) {
1897
+ return createNamespaceProxy((key) => {
1898
+ return findNextProxy([...ns, name], key);
1899
+ });
1900
+ } else if (name.startsWith("get")) {
1901
+ return (...args) => {
1902
+ return dataSource([...ns, name.substring(3)], ...args);
1903
+ };
1904
+ } else {
1905
+ return createClassProxy(
1906
+ (...args) => {
1907
+ return resource([...ns, name], ...args);
1908
+ },
1909
+ (...args) => {
1910
+ return dataSource([...ns, name], ...args);
1911
+ }
1912
+ );
1913
+ }
1654
1914
  };
1655
- return createProxy([]);
1915
+ return createRootProxy(provider, (key) => {
1916
+ if (key === "install") {
1917
+ return install;
1918
+ }
1919
+ if (key === "uninstall") {
1920
+ return uninstall;
1921
+ }
1922
+ if (key === "isInstalled") {
1923
+ return isInstalled;
1924
+ }
1925
+ return findNextProxy([], key);
1926
+ });
1656
1927
  };
1657
- var createResourceProxy = (name) => {
1928
+ var createTerraformProxy = (props) => {
1658
1929
  return createRecursiveProxy({
1930
+ provider(input, config) {
1931
+ return new TerraformProvider(
1932
+ props.namespace,
1933
+ config?.id ?? "default",
1934
+ createLazyPlugin({
1935
+ ...props.provider,
1936
+ location: config?.location
1937
+ }),
1938
+ input
1939
+ );
1940
+ },
1941
+ async install(installProps) {
1942
+ await downloadPlugin({ ...props.provider, ...installProps });
1943
+ },
1944
+ async uninstall(installProps) {
1945
+ await deletePlugin({ ...props.provider, ...installProps });
1946
+ },
1947
+ async isInstalled(installProps) {
1948
+ return await isPluginInstalled({ ...props.provider, ...installProps });
1949
+ },
1659
1950
  resource: (ns, parent, id, input, config) => {
1660
- const type = snakeCase2(name + "_" + ns.join("_"));
1661
- const provider = `terraform:${name}:${config?.provider ?? "default"}`;
1951
+ const type = snakeCase2([props.namespace, ...ns].join("_"));
1952
+ const provider = `terraform:${props.namespace}:${config?.provider ?? "default"}`;
1662
1953
  const meta = createMeta("resource", provider, parent, type, id, input, config);
1663
- const resource = createNamespaceProxy(
1664
- (key) => {
1954
+ const resource = createResourceProxy((key) => {
1955
+ if (typeof key === "string") {
1665
1956
  return meta.output((data) => data[key]);
1666
- },
1667
- (key) => {
1668
- if (key === nodeMetaSymbol) {
1669
- return meta;
1670
- }
1671
- return;
1957
+ } else if (key === nodeMetaSymbol) {
1958
+ return meta;
1672
1959
  }
1673
- );
1960
+ return;
1961
+ });
1674
1962
  parent.add(resource);
1675
1963
  return resource;
1676
1964
  },
1677
- // external: (ns: string[], id: string, input: State, config?: ResourceConfig) => {
1678
- // const type = snakeCase(ns.join('_'))
1679
- // const provider = `terraform:${ns[0]}:${config?.provider ?? 'default'}`
1680
- // const $ = createResourceMeta(provider, type, id, input, config)
1681
- // const resource = createNamespaceProxy(
1682
- // key => {
1683
- // if (key === '$') {
1684
- // return $
1685
- // }
1686
- // return $.output(data => data[key])
1687
- // },
1688
- // { $ }
1689
- // ) as Resource
1690
- // parent.add(resource)
1691
- // return resource
1692
- // },
1693
- // (ns: string[], parent: Group, id: string, input: State, config?: ResourceConfig)
1694
1965
  dataSource: (ns, parent, id, input, config) => {
1695
- const type = snakeCase2(name + "_" + ns.join("_"));
1696
- const provider = `terraform:${name}:${config?.provider ?? "default"}`;
1966
+ const type = snakeCase2([props.namespace, ...ns].join("_"));
1967
+ const provider = `terraform:${props.namespace}:${config?.provider ?? "default"}`;
1697
1968
  const meta = createMeta("data", provider, parent, type, id, input, config);
1698
- const dataSource = createNamespaceProxy(
1699
- (key) => {
1969
+ const dataSource = createResourceProxy((key) => {
1970
+ if (typeof key === "string") {
1700
1971
  return meta.output((data) => data[key]);
1701
- },
1702
- (key) => {
1703
- if (key === nodeMetaSymbol) {
1704
- return meta;
1705
- }
1706
- return;
1972
+ } else if (key === nodeMetaSymbol) {
1973
+ return meta;
1707
1974
  }
1708
- );
1975
+ return;
1976
+ });
1709
1977
  parent.add(dataSource);
1710
1978
  return dataSource;
1711
1979
  }
1712
1980
  });
1713
1981
  };
1714
-
1715
- // src/api.ts
1716
- var createTerraformAPI = (props) => {
1717
- const resource = createResourceProxy(props.namespace);
1718
- const install = async (installProps) => {
1719
- await downloadPlugin({ ...props.provider, ...installProps });
1720
- };
1721
- const createPlugin = (pluginProps) => {
1722
- return createLazyPlugin({ ...props.provider, ...pluginProps });
1723
- };
1724
- return new Proxy(() => {
1725
- }, {
1726
- apply(_, _this, [input, config]) {
1727
- return new TerraformProvider(
1728
- props.namespace,
1729
- config?.id ?? "default",
1730
- createPlugin({ location: config?.location }),
1731
- input
1732
- );
1733
- },
1734
- get(_, prop) {
1735
- if (prop === "install") {
1736
- return install;
1737
- }
1738
- return resource;
1739
- }
1740
- });
1741
- };
1742
-
1743
- // src/type-gen.ts
1744
- import { camelCase as camelCase2, pascalCase } from "change-case";
1745
- var tab = (indent) => {
1746
- return " ".repeat(indent);
1747
- };
1748
- var generateTypes = (providers, resources, dataSources) => {
1749
- return [
1750
- generateImport("c", "@terraforge/core"),
1751
- generateImport("t", "@terraforge/terraform"),
1752
- "type _Record<T> = Record<string, T>",
1753
- generateInstallFunction(providers),
1754
- generateNamespace(providers, (name, prop, indent) => {
1755
- const typeName = name.toLowerCase();
1756
- return `${tab(indent)}export function ${typeName}(props: ${generatePropertyInputConst(prop, indent)}, config?: t.TerraformProviderConfig): t.TerraformProvider`;
1757
- }),
1758
- generateNamespace(resources, (name, prop, indent) => {
1759
- const typeName = pascalCase(name);
1760
- return [
1761
- // `${tab(indent)}export type ${typeName}Input = ${generatePropertyInputType(prop, indent)}`,
1762
- // `${tab(indent)}export type ${typeName}Output = ${generatePropertyOutputType(prop, indent)}`,
1763
- // `${tab(indent)}export declare const ${typeName}: ResourceClass<${typeName}Input, ${typeName}Output>`,
1764
- `${tab(indent)}export type ${typeName}Input = ${generatePropertyInputType(prop, indent)}`,
1765
- `${tab(indent)}export type ${typeName}Output = ${generatePropertyOutputType(prop, indent)}`,
1766
- `${tab(indent)}export class ${typeName} {`,
1767
- `${tab(indent + 1)}constructor(parent: c.Group, id: string, props: ${typeName}Input, config?:c.ResourceConfig)`,
1768
- // `${tab(indent + 1)}readonly $: c.ResourceMeta<${typeName}Input, ${typeName}Output>`,
1769
- generateClassProperties(prop, indent + 1),
1770
- `${tab(indent)}}`
1771
- ].join("\n\n");
1772
- }),
1773
- generateNamespace(dataSources, (name, prop, indent) => {
1774
- const typeName = pascalCase(name);
1775
- return [
1776
- `${tab(indent)}export type Get${typeName}Input = ${generatePropertyInputType(prop, indent)}`,
1777
- `${tab(indent)}export type Get${typeName}Output = ${generatePropertyOutputType(prop, indent)}`,
1778
- `${tab(indent)}export const get${typeName}:c.DataSourceFunction<Get${typeName}Input, Get${typeName}Output>`
1779
- ].join("\n\n");
1780
- })
1781
- ].join("\n\n");
1782
- };
1783
- var generateImport = (name, from) => {
1784
- return `import * as ${name} from '${from}'`;
1785
- };
1786
- var generateInstallFunction = (resources) => {
1787
- return generateNamespace(resources, (name, _prop, indent) => {
1788
- const typeName = name.toLowerCase();
1789
- return `${tab(indent)}export namespace ${typeName} { export function install(props?: t.InstallProps): Promise<void> }`;
1790
- });
1791
- };
1792
- var generatePropertyInputConst = (prop, indent) => {
1793
- return generateValue(prop, {
1794
- depth: 0,
1795
- indent: indent + 1,
1796
- wrap: (v, _, ctx) => {
1797
- return `${v}${ctx.depth === 1 ? "," : ""}`;
1798
- },
1799
- filter: () => true,
1800
- optional: (p) => p.optional ?? false
1801
- });
1802
- };
1803
- var generatePropertyInputType = (prop, indent) => {
1804
- return generateValue(prop, {
1805
- depth: 0,
1806
- indent: indent + 1,
1807
- wrap: (v, p, ctx) => {
1808
- return ctx.depth > 0 ? p.optional ? `c.OptionalInput<${v}>` : `c.Input<${v}>` : v;
1809
- },
1810
- filter: (prop2) => !(prop2.computed && typeof prop2.optional === "undefined" && typeof prop2.required === "undefined"),
1811
- optional: (p) => p.optional ?? false
1812
- });
1813
- };
1814
- var generatePropertyOutputType = (prop, indent) => {
1815
- return generateValue(prop, {
1816
- indent: indent + 1,
1817
- depth: 0,
1818
- wrap: (v, p, ctx) => ctx.depth === 1 ? p.optional && !p.computed ? `c.OptionalOutput<${v}>` : `c.Output<${v}>` : v,
1819
- filter: () => true,
1820
- readonly: true,
1821
- // required: true,
1822
- optional: (p, ctx) => ctx.depth > 1 && p.optional && !p.computed || false
1823
- });
1824
- };
1825
- var generateClassProperties = (prop, indent) => {
1826
- if (prop.type !== "object") {
1827
- return "";
1828
- }
1829
- return Object.entries(prop.properties).map(([name, prop2]) => {
1830
- return [
1831
- prop2.description ? [`
1832
- `, ` `.repeat(indent), `/** `, prop2.description.trim(), " */", "\n"].join("") : "",
1833
- ` `.repeat(indent),
1834
- "readonly ",
1835
- camelCase2(name),
1836
- // ctx.optional(prop, ctx) ? '?' : '',
1837
- ": ",
1838
- generateValue(prop2, {
1839
- readonly: true,
1840
- filter: () => true,
1841
- optional: (p, ctx) => ctx.depth > 1 && p.optional && !p.computed || false,
1842
- wrap: (v, p, ctx) => {
1843
- return ctx.depth === 1 ? p.optional && !p.computed ? `c.OptionalOutput<${v}>` : `c.Output<${v}>` : v;
1844
- },
1845
- // ctx.depth === 1 ? `c.Output<${p.optional && !p.computed ? `${v} | undefined` : v}>` : v,
1846
- indent: indent + 1,
1847
- depth: 1
1848
- })
1849
- ].join("");
1850
- }).join("\n");
1851
- };
1852
- var groupByNamespace = (resources, minLevel, maxLevel) => {
1853
- const grouped = {};
1854
- const types = Object.keys(resources).sort();
1855
- for (const type of types) {
1856
- const names = type.split("_");
1857
- if (names.length < minLevel) {
1858
- throw new Error(`Resource not properly namespaced: ${type}`);
1859
- }
1860
- let current = grouped;
1861
- let count = Math.min(maxLevel, names.length - 1);
1862
- while (count--) {
1863
- const ns = camelCase2(names.shift());
1864
- if (!current[ns]) {
1865
- current[ns] = {};
1866
- }
1867
- current = current[ns];
1868
- }
1869
- const name = pascalCase(names.join("_"));
1870
- current[name] = type;
1871
- }
1872
- return grouped;
1873
- };
1874
- var generateNamespace = (resources, render) => {
1875
- const grouped = groupByNamespace(resources, 1, 2);
1876
- const renderNamespace = (name, group, indent) => {
1877
- if (name === "default") {
1878
- name = "$default";
1879
- }
1880
- return [
1881
- `${tab(indent)}export ${indent === 0 ? "declare " : ""}namespace ${name.toLowerCase()} {`,
1882
- Object.entries(group).map(([name2, entry]) => {
1883
- if (typeof entry !== "string") {
1884
- return renderNamespace(name2, entry, indent + 1);
1885
- } else {
1886
- return render(name2, resources[entry], indent + 1);
1887
- }
1888
- }).join("\n"),
1889
- `${tab(indent)}}`
1890
- ].join("\n");
1891
- };
1892
- return renderNamespace("root", grouped, 0);
1893
- };
1894
- var generateValue = (prop, ctx) => {
1895
- if (["string", "number", "boolean", "unknown"].includes(prop.type)) {
1896
- return ctx.wrap(prop.type, prop, ctx);
1897
- }
1898
- if (prop.type === "array") {
1899
- const type = generateValue(prop.item, { ...ctx, depth: ctx.depth + 1 });
1900
- const array = ctx.readonly ? `ReadonlyArray<${type}>` : `Array<${type}>`;
1901
- return ctx.wrap(array, prop, ctx);
1902
- }
1903
- if (prop.type === "record") {
1904
- const type = generateValue(prop.item, { ...ctx, depth: ctx.depth + 1 });
1905
- const record = ctx.readonly ? `Readonly<_Record<${type}>>` : `_Record<${type}>`;
1906
- return ctx.wrap(record, prop, ctx);
1907
- }
1908
- if (prop.type === "object" || prop.type === "array-object") {
1909
- const type = [
1910
- "{",
1911
- Object.entries(prop.properties).filter(([_, p]) => ctx.filter(p)).map(
1912
- ([name, prop2]) => [
1913
- prop2.description ? [`
1914
- `, ` `.repeat(ctx.indent), `/** `, prop2.description.trim(), " */", "\n"].join("") : "",
1915
- ` `.repeat(ctx.indent),
1916
- // ctx.readonly ? "readonly " : "",
1917
- camelCase2(name),
1918
- ctx.optional(prop2, ctx) ? "?" : "",
1919
- ": ",
1920
- generateValue(prop2, { ...ctx, indent: ctx.indent + 1, depth: ctx.depth + 1 })
1921
- ].join("")
1922
- ).join("\n"),
1923
- `${` `.repeat(ctx.indent - 1)}}`
1924
- ].join("\n");
1925
- const object = ctx.readonly ? `Readonly<${type}>` : type;
1926
- return ctx.wrap(object, prop, ctx);
1927
- }
1928
- throw new Error(`Unknown property type: ${prop.type}`);
1929
- };
1930
1982
  export {
1931
1983
  TerraformProvider,
1932
- createTerraformAPI,
1984
+ createTerraformProxy,
1933
1985
  generateTypes
1934
1986
  };
package/package.json CHANGED
@@ -1,7 +1,14 @@
1
1
  {
2
2
  "name": "@terraforge/terraform",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "type": "module",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/terraforge-js/terraforge.git"
8
+ },
9
+ "bugs": {
10
+ "url": "https://github.com/terraforge-js/terraforge/issues"
11
+ },
5
12
  "module": "./dist/index.js",
6
13
  "types": "./dist/index.d.ts",
7
14
  "bin": {
@@ -19,7 +26,7 @@
19
26
  "test": "bun test"
20
27
  },
21
28
  "peerDependencies": {
22
- "@terraforge/core": "0.0.4"
29
+ "@terraforge/core": "0.0.5"
23
30
  },
24
31
  "dependencies": {
25
32
  "@grpc/grpc-js": "1.12.6",