@terraforge/terraform 0.0.23 → 0.0.25

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/dist/index.d.mts CHANGED
@@ -1,5 +1,11 @@
1
1
  //#region src/plugin/schema.d.ts
2
2
 
3
+ type RootProperty = {
4
+ type: 'object';
5
+ version?: number;
6
+ description?: string;
7
+ properties: Record<string, Property>;
8
+ };
3
9
  type Property = {
4
10
  description?: string;
5
11
  required?: boolean;
@@ -13,6 +19,7 @@ type Property = {
13
19
  } | {
14
20
  type: 'array' | 'record';
15
21
  item: Property;
22
+ collectionKind?: 'list' | 'set';
16
23
  } | {
17
24
  type: 'object' | 'array-object';
18
25
  properties: Record<string, Property>;
@@ -28,8 +35,8 @@ type State$1 = Record<string, unknown>;
28
35
  type Plugin = Readonly<{
29
36
  schema: () => {
30
37
  provider: Property;
31
- resources: Record<string, Property>;
32
- dataSources: Record<string, Property>;
38
+ resources: Record<string, RootProperty>;
39
+ dataSources: Record<string, RootProperty>;
33
40
  };
34
41
  stop: () => Promise<void>;
35
42
  configure: (config: State$1) => Promise<void>;
@@ -197,8 +204,12 @@ type RefreshResourceProps<T = State> = {
197
204
  priorOutputState: T;
198
205
  };
199
206
  type RefreshResourceResult<T = State> = {
200
- kind: 'unchanged' | 'updated';
207
+ kind: 'unchanged';
208
+ state: T;
209
+ } | {
210
+ kind: 'updated';
201
211
  state: T;
212
+ inputState: T;
202
213
  } | {
203
214
  kind: 'deleted';
204
215
  };
@@ -303,12 +314,15 @@ declare class TerraformProvider implements Provider {
303
314
  }: RefreshResourceProps): Promise<{
304
315
  kind: "deleted";
305
316
  state?: undefined;
317
+ inputState?: undefined;
306
318
  } | {
307
319
  kind: "unchanged";
308
320
  state: State$1;
321
+ inputState?: undefined;
309
322
  } | {
310
323
  kind: "updated";
311
324
  state: State$1;
325
+ inputState: State;
312
326
  }>;
313
327
  }
314
328
  //#endregion
package/dist/index.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  import { camelCase, pascalCase, snakeCase } from "change-case";
2
2
  import { ResourceNotFound, createDebugger, createMeta, nodeMetaSymbol } from "@terraforge/core";
3
+ import { pack, unpack } from "msgpackr";
3
4
  import { credentials, loadPackageDefinition } from "@grpc/grpc-js";
4
5
  import { fromJSON } from "@grpc/proto-loader";
5
6
  import jszip from "jszip";
@@ -8,7 +9,6 @@ import { arch, homedir, platform } from "node:os";
8
9
  import { dirname, join } from "node:path";
9
10
  import { compare } from "semver";
10
11
  import { spawn } from "node:child_process";
11
- import { pack, unpack } from "msgpackr";
12
12
 
13
13
  //#region src/type-gen.ts
14
14
  const tab = (indent) => {
@@ -215,6 +215,215 @@ const generateValue = (prop, ctx) => {
215
215
  throw new Error(`Unknown property type: ${prop.type}`);
216
216
  };
217
217
 
218
+ //#endregion
219
+ //#region src/plugin/version/util.ts
220
+ const stableStringify = (value) => {
221
+ return JSON.stringify(value, (_, item) => {
222
+ if (item !== null && item instanceof Object && !Array.isArray(item)) return Object.keys(item).sort().reduce((sorted, key) => {
223
+ sorted[key] = item[key];
224
+ return sorted;
225
+ }, {});
226
+ return item;
227
+ });
228
+ };
229
+ const sortStateValues = (values) => {
230
+ return [...values].sort((left, right) => {
231
+ const l = stableStringify(left);
232
+ const r = stableStringify(right);
233
+ if (l < r) return -1;
234
+ if (l > r) return 1;
235
+ return 0;
236
+ });
237
+ };
238
+ const encodeDynamicValue = (value) => {
239
+ return {
240
+ msgpack: pack(value),
241
+ json: value
242
+ };
243
+ };
244
+ const decodeDynamicValue = (value) => {
245
+ return unpack(value.msgpack);
246
+ };
247
+ const getResourceSchema = (resources, type) => {
248
+ const resource = resources[type];
249
+ if (!resource) throw new Error(`Unknown resource type: ${type}`);
250
+ return resource;
251
+ };
252
+ const formatAttributePath = (state) => {
253
+ if (!state) return [];
254
+ return state.map((item) => {
255
+ if (!item.steps) throw new Error("AttributePath should always have steps");
256
+ return item.steps.map((attr) => {
257
+ if ("attributeName" in attr) return attr.attributeName;
258
+ if ("elementKeyString" in attr) return attr.elementKeyString;
259
+ if ("elementKeyInt" in attr) return attr.elementKeyInt;
260
+ throw new Error("AttributePath step should always have an element");
261
+ });
262
+ });
263
+ };
264
+ const getNestedValue = (obj, path) => {
265
+ let current = obj;
266
+ for (const key of path) {
267
+ if (current === null || current === void 0) return current;
268
+ if (Array.isArray(current)) current = current[key];
269
+ else if (typeof current === "object") current = current[key];
270
+ else return;
271
+ }
272
+ return current;
273
+ };
274
+ const filterRequiresReplace = (paths, priorState, proposedState) => {
275
+ return paths.filter((path) => {
276
+ const priorValue = getNestedValue(priorState, path);
277
+ const proposedValue = getNestedValue(proposedState, path);
278
+ return JSON.stringify(priorValue) !== JSON.stringify(proposedValue);
279
+ });
280
+ };
281
+ var IncorrectType = class extends TypeError {
282
+ constructor(type, path) {
283
+ super(`${path.join(".")} should be a ${type}`);
284
+ }
285
+ };
286
+ const isEmptyOutputValue = (value) => {
287
+ if (value === null || typeof value === "undefined") return true;
288
+ if (Array.isArray(value)) return value.length === 0;
289
+ if (typeof value === "object") return Object.keys(value).length === 0;
290
+ return false;
291
+ };
292
+ const shouldOmitOutputValue = (schema, value) => {
293
+ if (!(schema.optional || schema.computed)) return false;
294
+ return isEmptyOutputValue(value);
295
+ };
296
+ const hasInputValue = (value) => typeof value !== "undefined";
297
+ const shouldIncludeFieldForComparison = (schema, inputValue) => {
298
+ return schema.required || hasInputValue(inputValue);
299
+ };
300
+ const normalizeStateForComparison = (schema, state, inputState) => {
301
+ if (!shouldIncludeFieldForComparison(schema, inputState)) return;
302
+ if (state === null || typeof state === "undefined") return state;
303
+ if (schema.type === "array") {
304
+ if (!Array.isArray(state)) return state;
305
+ const normalized = state.map((item, index) => {
306
+ const inputItem = Array.isArray(inputState) ? inputState[index] : void 0;
307
+ return normalizeStateForComparison(schema.item, item, inputItem);
308
+ });
309
+ return schema.collectionKind === "set" ? sortStateValues(normalized) : normalized;
310
+ }
311
+ if (schema.type === "record") {
312
+ if (typeof state !== "object" || state === null) return state;
313
+ return Object.fromEntries(Object.entries(state).map(([key, value]) => {
314
+ const inputValue = inputState && typeof inputState === "object" ? inputState[key] : void 0;
315
+ return [key, normalizeStateForComparison(schema.item, value, inputValue)];
316
+ }));
317
+ }
318
+ if (schema.type === "object") {
319
+ if (typeof state !== "object" || state === null) return state;
320
+ return Object.fromEntries(Object.entries(schema.properties).flatMap(([key, prop]) => {
321
+ const stateValue = state[camelCase(key)];
322
+ const normalized = normalizeStateForComparison(prop, stateValue, inputState && typeof inputState === "object" ? inputState[camelCase(key)] : void 0);
323
+ if (typeof normalized === "undefined") return [];
324
+ return [[camelCase(key), normalized]];
325
+ }));
326
+ }
327
+ if (schema.type === "array-object") {
328
+ if (typeof state !== "object" || state === null) return state;
329
+ return Object.fromEntries(Object.entries(schema.properties).flatMap(([key, prop]) => {
330
+ const stateValue = state[camelCase(key)];
331
+ const normalized = normalizeStateForComparison(prop, stateValue, inputState && typeof inputState === "object" ? inputState[camelCase(key)] : void 0);
332
+ if (typeof normalized === "undefined") return [];
333
+ return [[camelCase(key), normalized]];
334
+ }));
335
+ }
336
+ return state;
337
+ };
338
+ const formatInputState = (schema, state, includeSchemaFields = true, path = []) => {
339
+ if (state === null) return null;
340
+ if (typeof state === "undefined") return null;
341
+ if (schema.type === "unknown") return state;
342
+ if (schema.type === "string") {
343
+ if (typeof state === "string") return state;
344
+ throw new IncorrectType(schema.type, path);
345
+ }
346
+ if (schema.type === "number") {
347
+ if (typeof state === "number") return state;
348
+ throw new IncorrectType(schema.type, path);
349
+ }
350
+ if (schema.type === "boolean") {
351
+ if (typeof state === "boolean") return state;
352
+ throw new IncorrectType(schema.type, path);
353
+ }
354
+ if (schema.type === "array") {
355
+ if (Array.isArray(state)) return state.map((item, i) => formatInputState(schema.item, item, includeSchemaFields, [...path, i]));
356
+ throw new IncorrectType(schema.type, path);
357
+ }
358
+ if (schema.type === "record") {
359
+ if (typeof state === "object" && state !== null) {
360
+ const record = {};
361
+ for (const [key, value] of Object.entries(state)) record[key] = formatInputState(schema.item, value, includeSchemaFields, [...path, key]);
362
+ return record;
363
+ }
364
+ throw new IncorrectType(schema.type, path);
365
+ }
366
+ if (schema.type === "object" || schema.type === "array-object") {
367
+ if (typeof state === "object" && state !== null) {
368
+ const object = {};
369
+ if (includeSchemaFields) for (const [key, prop] of Object.entries(schema.properties)) {
370
+ const value = state[camelCase(key)];
371
+ object[key] = formatInputState(prop, value, true, [...path, key]);
372
+ }
373
+ else for (const [key, value] of Object.entries(state)) {
374
+ const prop = schema.properties[snakeCase(key)];
375
+ if (prop) object[key] = formatInputState(prop, value, false, [...path, key]);
376
+ }
377
+ if (schema.type === "array-object") return [object];
378
+ return object;
379
+ }
380
+ throw new IncorrectType(schema.type, path);
381
+ }
382
+ throw new Error(`Unknown schema type: ${schema.type}`);
383
+ };
384
+ const formatOutputState = (schema, state, path = []) => {
385
+ if (state === null || state === void 0) return null;
386
+ if (schema.type === "array") {
387
+ if (Array.isArray(state)) return state.map((item, i) => formatOutputState(schema.item, item, [...path, i]));
388
+ throw new IncorrectType(schema.type, path);
389
+ }
390
+ if (schema.type === "record") {
391
+ if (typeof state === "object" && state !== null) {
392
+ const record = {};
393
+ for (const [key, value] of Object.entries(state)) record[key] = formatOutputState(schema.item, value, [...path, key]);
394
+ return record;
395
+ }
396
+ throw new IncorrectType(schema.type, path);
397
+ }
398
+ if (schema.type === "object") {
399
+ if (typeof state === "object" && state !== null) {
400
+ const object = {};
401
+ for (const [key, prop] of Object.entries(schema.properties)) {
402
+ const value = state[key];
403
+ const formatted = formatOutputState(prop, value, [...path, key]);
404
+ if (shouldOmitOutputValue(prop, formatted)) continue;
405
+ object[camelCase(key)] = formatted;
406
+ }
407
+ return object;
408
+ }
409
+ throw new IncorrectType(schema.type, path);
410
+ }
411
+ if (schema.type === "array-object") {
412
+ if (Array.isArray(state)) if (state.length === 1) {
413
+ const object = {};
414
+ for (const [key, prop] of Object.entries(schema.properties)) {
415
+ const value = state[0][key];
416
+ const formatted = formatOutputState(prop, value, [...path, key]);
417
+ if (shouldOmitOutputValue(prop, formatted)) continue;
418
+ object[camelCase(key)] = formatted;
419
+ }
420
+ return object;
421
+ } else return null;
422
+ throw new IncorrectType(schema.type, path);
423
+ }
424
+ return state;
425
+ };
426
+
218
427
  //#endregion
219
428
  //#region src/provider.ts
220
429
  const areStatesEqual = (left, right) => {
@@ -317,6 +526,7 @@ var TerraformProvider = class {
317
526
  }
318
527
  async refreshResource({ type, priorInputState, priorOutputState }) {
319
528
  const plugin = await this.configure();
529
+ const schema = getResourceSchema(plugin.schema().resources, type);
320
530
  const refreshedState = await plugin.readResource(type, priorOutputState);
321
531
  if (!refreshedState) return { kind: "deleted" };
322
532
  const mergedState = {
@@ -324,19 +534,16 @@ var TerraformProvider = class {
324
534
  ...priorInputState
325
535
  };
326
536
  const { requiresReplace, plannedState } = await plugin.planResourceChange(type, refreshedState, mergedState, priorInputState);
327
- if (requiresReplace.length === 0 && areStatesEqual(plannedState, refreshedState)) return {
537
+ const normalizedPlannedState = normalizeStateForComparison(schema, plannedState, priorInputState);
538
+ const normalizedRefreshedState = normalizeStateForComparison(schema, refreshedState, priorInputState);
539
+ if (requiresReplace.length === 0 && areStatesEqual(normalizedPlannedState, normalizedRefreshedState)) return {
328
540
  kind: "unchanged",
329
541
  state: refreshedState
330
542
  };
331
- console.log("TYPE", type);
332
- console.log("PRIOR_INPUT_STATE", priorInputState);
333
- console.log("PRIOR_OUTPUT_STATE", priorOutputState);
334
- console.log("PLANNED_STATE", plannedState);
335
- console.log("REFRESH_STATE", refreshedState);
336
- console.log("REQUIRES_REPLACE", requiresReplace);
337
543
  return {
338
544
  kind: "updated",
339
- state: refreshedState
545
+ state: refreshedState,
546
+ inputState: normalizedRefreshedState
340
547
  };
341
548
  }
342
549
  };
@@ -1892,7 +2099,8 @@ const parseNestedBlock = (block) => {
1892
2099
  if (type === "array" || type === "record") return {
1893
2100
  ...prop,
1894
2101
  type,
1895
- item
2102
+ item,
2103
+ collectionKind: block.nesting === NestingMode.SET ? "set" : "list"
1896
2104
  };
1897
2105
  if (type === "array-object") return {
1898
2106
  ...prop,
@@ -1939,12 +2147,14 @@ const parseAttribute = (attr) => {
1939
2147
  };
1940
2148
  const parseAttributeType = (item) => {
1941
2149
  if (Array.isArray(item)) {
1942
- const type$1 = parseType(item[0]);
2150
+ const sourceType = item[0];
2151
+ const type$1 = parseType(sourceType);
1943
2152
  if (type$1 === "array" || type$1 === "record" && item) {
1944
2153
  const record = item[1];
1945
2154
  return {
1946
2155
  type: type$1,
1947
- item: parseAttributeType(record)
2156
+ item: parseAttributeType(record),
2157
+ collectionKind: sourceType === "set" ? "set" : "list"
1948
2158
  };
1949
2159
  }
1950
2160
  if (type$1 === "object") {
@@ -1981,155 +2191,6 @@ const parseType = (type) => {
1981
2191
  throw new Error(`Invalid type: ${type}`);
1982
2192
  };
1983
2193
 
1984
- //#endregion
1985
- //#region src/plugin/version/util.ts
1986
- const encodeDynamicValue = (value) => {
1987
- return {
1988
- msgpack: pack(value),
1989
- json: value
1990
- };
1991
- };
1992
- const decodeDynamicValue = (value) => {
1993
- return unpack(value.msgpack);
1994
- };
1995
- const getResourceSchema = (resources, type) => {
1996
- const resource = resources[type];
1997
- if (!resource) throw new Error(`Unknown resource type: ${type}`);
1998
- return resource;
1999
- };
2000
- const formatAttributePath = (state) => {
2001
- if (!state) return [];
2002
- return state.map((item) => {
2003
- if (!item.steps) throw new Error("AttributePath should always have steps");
2004
- return item.steps.map((attr) => {
2005
- if ("attributeName" in attr) return attr.attributeName;
2006
- if ("elementKeyString" in attr) return attr.elementKeyString;
2007
- if ("elementKeyInt" in attr) return attr.elementKeyInt;
2008
- throw new Error("AttributePath step should always have an element");
2009
- });
2010
- });
2011
- };
2012
- const getNestedValue = (obj, path) => {
2013
- let current = obj;
2014
- for (const key of path) {
2015
- if (current === null || current === void 0) return current;
2016
- if (Array.isArray(current)) current = current[key];
2017
- else if (typeof current === "object") current = current[key];
2018
- else return;
2019
- }
2020
- return current;
2021
- };
2022
- const filterRequiresReplace = (paths, priorState, proposedState) => {
2023
- return paths.filter((path) => {
2024
- const priorValue = getNestedValue(priorState, path);
2025
- const proposedValue = getNestedValue(proposedState, path);
2026
- return JSON.stringify(priorValue) !== JSON.stringify(proposedValue);
2027
- });
2028
- };
2029
- var IncorrectType = class extends TypeError {
2030
- constructor(type, path) {
2031
- super(`${path.join(".")} should be a ${type}`);
2032
- }
2033
- };
2034
- const isEmptyOutputValue = (value) => {
2035
- if (value === null || typeof value === "undefined") return true;
2036
- if (Array.isArray(value)) return value.length === 0;
2037
- if (typeof value === "object") return Object.keys(value).length === 0;
2038
- return false;
2039
- };
2040
- const shouldOmitOutputValue = (schema, value) => {
2041
- if (!(schema.optional || schema.computed)) return false;
2042
- return isEmptyOutputValue(value);
2043
- };
2044
- const formatInputState = (schema, state, includeSchemaFields = true, path = []) => {
2045
- if (state === null) return null;
2046
- if (typeof state === "undefined") return null;
2047
- if (schema.type === "unknown") return state;
2048
- if (schema.type === "string") {
2049
- if (typeof state === "string") return state;
2050
- throw new IncorrectType(schema.type, path);
2051
- }
2052
- if (schema.type === "number") {
2053
- if (typeof state === "number") return state;
2054
- throw new IncorrectType(schema.type, path);
2055
- }
2056
- if (schema.type === "boolean") {
2057
- if (typeof state === "boolean") return state;
2058
- throw new IncorrectType(schema.type, path);
2059
- }
2060
- if (schema.type === "array") {
2061
- if (Array.isArray(state)) return state.map((item, i) => formatInputState(schema.item, item, includeSchemaFields, [...path, i]));
2062
- throw new IncorrectType(schema.type, path);
2063
- }
2064
- if (schema.type === "record") {
2065
- if (typeof state === "object" && state !== null) {
2066
- const record = {};
2067
- for (const [key, value] of Object.entries(state)) record[key] = formatInputState(schema.item, value, includeSchemaFields, [...path, key]);
2068
- return record;
2069
- }
2070
- throw new IncorrectType(schema.type, path);
2071
- }
2072
- if (schema.type === "object" || schema.type === "array-object") {
2073
- if (typeof state === "object" && state !== null) {
2074
- const object = {};
2075
- if (includeSchemaFields) for (const [key, prop] of Object.entries(schema.properties)) {
2076
- const value = state[camelCase(key)];
2077
- object[key] = formatInputState(prop, value, true, [...path, key]);
2078
- }
2079
- else for (const [key, value] of Object.entries(state)) {
2080
- const prop = schema.properties[snakeCase(key)];
2081
- if (prop) object[key] = formatInputState(prop, value, false, [...path, key]);
2082
- }
2083
- if (schema.type === "array-object") return [object];
2084
- return object;
2085
- }
2086
- throw new IncorrectType(schema.type, path);
2087
- }
2088
- throw new Error(`Unknown schema type: ${schema.type}`);
2089
- };
2090
- const formatOutputState = (schema, state, path = []) => {
2091
- if (state === null || state === void 0) return null;
2092
- if (schema.type === "array") {
2093
- if (Array.isArray(state)) return state.map((item, i) => formatOutputState(schema.item, item, [...path, i]));
2094
- throw new IncorrectType(schema.type, path);
2095
- }
2096
- if (schema.type === "record") {
2097
- if (typeof state === "object" && state !== null) {
2098
- const record = {};
2099
- for (const [key, value] of Object.entries(state)) record[key] = formatOutputState(schema.item, value, [...path, key]);
2100
- return record;
2101
- }
2102
- throw new IncorrectType(schema.type, path);
2103
- }
2104
- if (schema.type === "object") {
2105
- if (typeof state === "object" && state !== null) {
2106
- const object = {};
2107
- for (const [key, prop] of Object.entries(schema.properties)) {
2108
- const value = state[key];
2109
- const formatted = formatOutputState(prop, value, [...path, key]);
2110
- if (shouldOmitOutputValue(prop, formatted)) continue;
2111
- object[camelCase(key)] = formatted;
2112
- }
2113
- return object;
2114
- }
2115
- throw new IncorrectType(schema.type, path);
2116
- }
2117
- if (schema.type === "array-object") {
2118
- if (Array.isArray(state)) if (state.length === 1) {
2119
- const object = {};
2120
- for (const [key, prop] of Object.entries(schema.properties)) {
2121
- const value = state[0][key];
2122
- const formatted = formatOutputState(prop, value, [...path, key]);
2123
- if (shouldOmitOutputValue(prop, formatted)) continue;
2124
- object[camelCase(key)] = formatted;
2125
- }
2126
- return object;
2127
- } else return null;
2128
- throw new IncorrectType(schema.type, path);
2129
- }
2130
- return state;
2131
- };
2132
-
2133
2194
  //#endregion
2134
2195
  //#region src/plugin/version/5.ts
2135
2196
  const createPlugin5 = async ({ server, client }) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@terraforge/terraform",
3
- "version": "0.0.23",
3
+ "version": "0.0.25",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",