@terraforge/terraform 0.0.22 → 0.0.24

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>;
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,10 +534,20 @@ 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
  };
543
+ console.log("TYPE", type);
544
+ console.log("PRIOR_INPUT_STATE", priorInputState);
545
+ console.log("PRIOR_OUTPUT_STATE", priorOutputState);
546
+ console.log("PLANNED_STATE", plannedState);
547
+ console.log("REFRESH_STATE", refreshedState);
548
+ console.log("NORMALIZED_PLANNED_STATE", normalizedPlannedState);
549
+ console.log("NORMALIZED_REFRESH_STATE", normalizedRefreshedState);
550
+ console.log("REQUIRES_REPLACE", requiresReplace);
331
551
  return {
332
552
  kind: "updated",
333
553
  state: refreshedState
@@ -1886,7 +2106,8 @@ const parseNestedBlock = (block) => {
1886
2106
  if (type === "array" || type === "record") return {
1887
2107
  ...prop,
1888
2108
  type,
1889
- item
2109
+ item,
2110
+ collectionKind: block.nesting === NestingMode.SET ? "set" : "list"
1890
2111
  };
1891
2112
  if (type === "array-object") return {
1892
2113
  ...prop,
@@ -1933,12 +2154,14 @@ const parseAttribute = (attr) => {
1933
2154
  };
1934
2155
  const parseAttributeType = (item) => {
1935
2156
  if (Array.isArray(item)) {
1936
- const type$1 = parseType(item[0]);
2157
+ const sourceType = item[0];
2158
+ const type$1 = parseType(sourceType);
1937
2159
  if (type$1 === "array" || type$1 === "record" && item) {
1938
2160
  const record = item[1];
1939
2161
  return {
1940
2162
  type: type$1,
1941
- item: parseAttributeType(record)
2163
+ item: parseAttributeType(record),
2164
+ collectionKind: sourceType === "set" ? "set" : "list"
1942
2165
  };
1943
2166
  }
1944
2167
  if (type$1 === "object") {
@@ -1975,155 +2198,6 @@ const parseType = (type) => {
1975
2198
  throw new Error(`Invalid type: ${type}`);
1976
2199
  };
1977
2200
 
1978
- //#endregion
1979
- //#region src/plugin/version/util.ts
1980
- const encodeDynamicValue = (value) => {
1981
- return {
1982
- msgpack: pack(value),
1983
- json: value
1984
- };
1985
- };
1986
- const decodeDynamicValue = (value) => {
1987
- return unpack(value.msgpack);
1988
- };
1989
- const getResourceSchema = (resources, type) => {
1990
- const resource = resources[type];
1991
- if (!resource) throw new Error(`Unknown resource type: ${type}`);
1992
- return resource;
1993
- };
1994
- const formatAttributePath = (state) => {
1995
- if (!state) return [];
1996
- return state.map((item) => {
1997
- if (!item.steps) throw new Error("AttributePath should always have steps");
1998
- return item.steps.map((attr) => {
1999
- if ("attributeName" in attr) return attr.attributeName;
2000
- if ("elementKeyString" in attr) return attr.elementKeyString;
2001
- if ("elementKeyInt" in attr) return attr.elementKeyInt;
2002
- throw new Error("AttributePath step should always have an element");
2003
- });
2004
- });
2005
- };
2006
- const getNestedValue = (obj, path) => {
2007
- let current = obj;
2008
- for (const key of path) {
2009
- if (current === null || current === void 0) return current;
2010
- if (Array.isArray(current)) current = current[key];
2011
- else if (typeof current === "object") current = current[key];
2012
- else return;
2013
- }
2014
- return current;
2015
- };
2016
- const filterRequiresReplace = (paths, priorState, proposedState) => {
2017
- return paths.filter((path) => {
2018
- const priorValue = getNestedValue(priorState, path);
2019
- const proposedValue = getNestedValue(proposedState, path);
2020
- return JSON.stringify(priorValue) !== JSON.stringify(proposedValue);
2021
- });
2022
- };
2023
- var IncorrectType = class extends TypeError {
2024
- constructor(type, path) {
2025
- super(`${path.join(".")} should be a ${type}`);
2026
- }
2027
- };
2028
- const isEmptyOutputValue = (value) => {
2029
- if (value === null || typeof value === "undefined") return true;
2030
- if (Array.isArray(value)) return value.length === 0;
2031
- if (typeof value === "object") return Object.keys(value).length === 0;
2032
- return false;
2033
- };
2034
- const shouldOmitOutputValue = (schema, value) => {
2035
- if (!(schema.optional || schema.computed)) return false;
2036
- return isEmptyOutputValue(value);
2037
- };
2038
- const formatInputState = (schema, state, includeSchemaFields = true, path = []) => {
2039
- if (state === null) return null;
2040
- if (typeof state === "undefined") return null;
2041
- if (schema.type === "unknown") return state;
2042
- if (schema.type === "string") {
2043
- if (typeof state === "string") return state;
2044
- throw new IncorrectType(schema.type, path);
2045
- }
2046
- if (schema.type === "number") {
2047
- if (typeof state === "number") return state;
2048
- throw new IncorrectType(schema.type, path);
2049
- }
2050
- if (schema.type === "boolean") {
2051
- if (typeof state === "boolean") return state;
2052
- throw new IncorrectType(schema.type, path);
2053
- }
2054
- if (schema.type === "array") {
2055
- if (Array.isArray(state)) return state.map((item, i) => formatInputState(schema.item, item, includeSchemaFields, [...path, i]));
2056
- throw new IncorrectType(schema.type, path);
2057
- }
2058
- if (schema.type === "record") {
2059
- if (typeof state === "object" && state !== null) {
2060
- const record = {};
2061
- for (const [key, value] of Object.entries(state)) record[key] = formatInputState(schema.item, value, includeSchemaFields, [...path, key]);
2062
- return record;
2063
- }
2064
- throw new IncorrectType(schema.type, path);
2065
- }
2066
- if (schema.type === "object" || schema.type === "array-object") {
2067
- if (typeof state === "object" && state !== null) {
2068
- const object = {};
2069
- if (includeSchemaFields) for (const [key, prop] of Object.entries(schema.properties)) {
2070
- const value = state[camelCase(key)];
2071
- object[key] = formatInputState(prop, value, true, [...path, key]);
2072
- }
2073
- else for (const [key, value] of Object.entries(state)) {
2074
- const prop = schema.properties[snakeCase(key)];
2075
- if (prop) object[key] = formatInputState(prop, value, false, [...path, key]);
2076
- }
2077
- if (schema.type === "array-object") return [object];
2078
- return object;
2079
- }
2080
- throw new IncorrectType(schema.type, path);
2081
- }
2082
- throw new Error(`Unknown schema type: ${schema.type}`);
2083
- };
2084
- const formatOutputState = (schema, state, path = []) => {
2085
- if (state === null || state === void 0) return null;
2086
- if (schema.type === "array") {
2087
- if (Array.isArray(state)) return state.map((item, i) => formatOutputState(schema.item, item, [...path, i]));
2088
- throw new IncorrectType(schema.type, path);
2089
- }
2090
- if (schema.type === "record") {
2091
- if (typeof state === "object" && state !== null) {
2092
- const record = {};
2093
- for (const [key, value] of Object.entries(state)) record[key] = formatOutputState(schema.item, value, [...path, key]);
2094
- return record;
2095
- }
2096
- throw new IncorrectType(schema.type, path);
2097
- }
2098
- if (schema.type === "object") {
2099
- if (typeof state === "object" && state !== null) {
2100
- const object = {};
2101
- for (const [key, prop] of Object.entries(schema.properties)) {
2102
- const value = state[key];
2103
- const formatted = formatOutputState(prop, value, [...path, key]);
2104
- if (shouldOmitOutputValue(prop, formatted)) continue;
2105
- object[camelCase(key)] = formatted;
2106
- }
2107
- return object;
2108
- }
2109
- throw new IncorrectType(schema.type, path);
2110
- }
2111
- if (schema.type === "array-object") {
2112
- if (Array.isArray(state)) if (state.length === 1) {
2113
- const object = {};
2114
- for (const [key, prop] of Object.entries(schema.properties)) {
2115
- const value = state[0][key];
2116
- const formatted = formatOutputState(prop, value, [...path, key]);
2117
- if (shouldOmitOutputValue(prop, formatted)) continue;
2118
- object[camelCase(key)] = formatted;
2119
- }
2120
- return object;
2121
- } else return null;
2122
- throw new IncorrectType(schema.type, path);
2123
- }
2124
- return state;
2125
- };
2126
-
2127
2201
  //#endregion
2128
2202
  //#region src/plugin/version/5.ts
2129
2203
  const createPlugin5 = async ({ server, client }) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@terraforge/terraform",
3
- "version": "0.0.22",
3
+ "version": "0.0.24",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",