@terraforge/core 0.0.5 → 0.0.6
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 +3 -3
- package/dist/index.d.ts +60 -2
- package/dist/index.js +277 -9
- package/package.json +9 -2
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ The most used IaC solutions are slow & don't effectively leverage diffing to spe
|
|
|
12
12
|
Install with (NPM):
|
|
13
13
|
|
|
14
14
|
```
|
|
15
|
-
npm i @terraforge/core @terraforge/aws
|
|
15
|
+
npm i @terraforge/core @terraforge/terraform @terraforge/aws
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
## Example
|
|
@@ -52,12 +52,12 @@ This example illustrates how simple it is to define multi-stack resources withou
|
|
|
52
52
|
```ts
|
|
53
53
|
const app = new App('todo-app')
|
|
54
54
|
const storage = new Stack(app, 'storage')
|
|
55
|
-
const list = new aws.s3.Bucket(storage, 'list', {
|
|
55
|
+
const list = new aws.s3.Bucket(storage, 'todo-list', {
|
|
56
56
|
bucket: 'your-bucket-name'
|
|
57
57
|
})
|
|
58
58
|
|
|
59
59
|
const items = new Stack(app, 'items')
|
|
60
|
-
const todo = new aws.s3.BucketObject(items, 'item', {
|
|
60
|
+
const todo = new aws.s3.BucketObject(items, 'todo-item', {
|
|
61
61
|
bucket: list.bucket,
|
|
62
62
|
key: 'item-1',
|
|
63
63
|
content: JSON.stringify({
|
package/dist/index.d.ts
CHANGED
|
@@ -42,7 +42,7 @@ declare const interpolate: (literals: TemplateStringsArray, ...placeholders: Inp
|
|
|
42
42
|
type URN = `urn:${string}`;
|
|
43
43
|
|
|
44
44
|
declare const nodeMetaSymbol: unique symbol;
|
|
45
|
-
type Node<T extends Tag = Tag, I extends State = State, O extends State =
|
|
45
|
+
type Node<T extends Tag = Tag, I extends State = State, O extends State = State, C extends Config = Config> = {
|
|
46
46
|
readonly [nodeMetaSymbol]: Meta<T, I, O, C>;
|
|
47
47
|
readonly urn: URN;
|
|
48
48
|
} & O;
|
|
@@ -187,6 +187,11 @@ type DeleteProps<T = State> = {
|
|
|
187
187
|
state: T;
|
|
188
188
|
idempotantToken?: string;
|
|
189
189
|
};
|
|
190
|
+
type PlanProps<T = State> = {
|
|
191
|
+
type: string;
|
|
192
|
+
priorState: T;
|
|
193
|
+
proposedState: T;
|
|
194
|
+
};
|
|
190
195
|
type GetProps<T = State> = {
|
|
191
196
|
type: string;
|
|
192
197
|
state: T;
|
|
@@ -210,12 +215,60 @@ interface Provider {
|
|
|
210
215
|
state: State;
|
|
211
216
|
}>;
|
|
212
217
|
deleteResource(props: DeleteProps): Promise<void>;
|
|
218
|
+
planResourceChange?(props: PlanProps): Promise<{
|
|
219
|
+
version: number;
|
|
220
|
+
state: State;
|
|
221
|
+
requiresReplacement: boolean;
|
|
222
|
+
}>;
|
|
213
223
|
getData?(props: GetDataProps): Promise<{
|
|
214
224
|
state: State;
|
|
215
225
|
}>;
|
|
216
226
|
destroy?(): Promise<void>;
|
|
217
227
|
}
|
|
218
228
|
|
|
229
|
+
type ResourceEvent = {
|
|
230
|
+
urn: URN;
|
|
231
|
+
type: string;
|
|
232
|
+
};
|
|
233
|
+
type BeforeResourceCreateEvent = ResourceEvent & {
|
|
234
|
+
resource: Resource;
|
|
235
|
+
newInput: State;
|
|
236
|
+
};
|
|
237
|
+
type AfterResourceCreateEvent = ResourceEvent & {
|
|
238
|
+
resource: Resource;
|
|
239
|
+
newInput: State;
|
|
240
|
+
newOutput: State;
|
|
241
|
+
};
|
|
242
|
+
type BeforeResourceUpdateEvent = ResourceEvent & {
|
|
243
|
+
resource: Resource;
|
|
244
|
+
oldInput: State;
|
|
245
|
+
newInput: State;
|
|
246
|
+
oldOutput: State;
|
|
247
|
+
};
|
|
248
|
+
type AfterResourceUpdateEvent = ResourceEvent & {
|
|
249
|
+
resource: Resource;
|
|
250
|
+
oldInput: State;
|
|
251
|
+
newInput: State;
|
|
252
|
+
oldOutput: State;
|
|
253
|
+
newOutput: State;
|
|
254
|
+
};
|
|
255
|
+
type BeforeResourceDeleteEvent = ResourceEvent & {
|
|
256
|
+
oldInput: State;
|
|
257
|
+
oldOutput: State;
|
|
258
|
+
};
|
|
259
|
+
type AfterResourceDeleteEvent = ResourceEvent & {
|
|
260
|
+
oldInput: State;
|
|
261
|
+
oldOutput: State;
|
|
262
|
+
};
|
|
263
|
+
type Hooks = {
|
|
264
|
+
beforeResourceCreate?: (event: BeforeResourceCreateEvent) => Promise<void> | void;
|
|
265
|
+
beforeResourceUpdate?: (event: BeforeResourceUpdateEvent) => Promise<void> | void;
|
|
266
|
+
beforeResourceDelete?: (event: BeforeResourceDeleteEvent) => Promise<void> | void;
|
|
267
|
+
afterResourceCreate?: (event: AfterResourceCreateEvent) => Promise<void> | void;
|
|
268
|
+
afterResourceUpdate?: (event: AfterResourceUpdateEvent) => Promise<void> | void;
|
|
269
|
+
afterResourceDelete?: (event: AfterResourceDeleteEvent) => Promise<void> | void;
|
|
270
|
+
};
|
|
271
|
+
|
|
219
272
|
type ProcedureOptions = {
|
|
220
273
|
filters?: string[];
|
|
221
274
|
idempotentToken?: UUID;
|
|
@@ -227,6 +280,7 @@ type WorkSpaceOptions = {
|
|
|
227
280
|
state: StateBackend;
|
|
228
281
|
lock: LockBackend;
|
|
229
282
|
};
|
|
283
|
+
hooks?: Hooks;
|
|
230
284
|
};
|
|
231
285
|
declare class WorkSpace {
|
|
232
286
|
protected props: WorkSpaceOptions;
|
|
@@ -356,7 +410,11 @@ type CustomResourceProvider = Partial<{
|
|
|
356
410
|
createResource?(props: Omit<CreateProps, 'type'>): Promise<State>;
|
|
357
411
|
deleteResource?(props: Omit<DeleteProps, 'type'>): Promise<void>;
|
|
358
412
|
getData?(props: Omit<GetDataProps, 'type'>): Promise<State>;
|
|
413
|
+
planResourceChange?(props: Omit<PlanProps, 'type'>): Promise<{
|
|
414
|
+
state: State;
|
|
415
|
+
requiresReplacement: boolean;
|
|
416
|
+
}>;
|
|
359
417
|
}>;
|
|
360
418
|
declare const createCustomProvider: (providerId: string, resourceProviders: Record<string, CustomResourceProvider>) => Provider;
|
|
361
419
|
|
|
362
|
-
export { App, AppError, type Config, type CreateProps, type CustomResourceProvider, type DataSource, type DataSourceFunction, type DataSourceMeta, type DeleteProps, DynamoLockBackend, FileLockBackend, FileStateBackend, Future, type GetDataProps, type GetProps, Group, type Input, type LockBackend, MemoryLockBackend, MemoryStateBackend, type Meta, type Node, type OptionalInput, type OptionalOutput, Output, type ProcedureOptions, type Provider, type Resource, ResourceAlreadyExists, type ResourceClass, type ResourceConfig, ResourceError, type ResourceMeta, ResourceNotFound, S3StateBackend, Stack, type State, type StateBackend, type Tag, type URN, type UpdateProps, WorkSpace, type WorkSpaceOptions, createCustomProvider, createCustomResourceClass, createDebugger, createMeta, deferredOutput, enableDebug, findInputDeps, getMeta, isDataSource, isNode, isResource, nodeMetaSymbol, output, resolveInputs };
|
|
420
|
+
export { App, AppError, type Config, type CreateProps, type CustomResourceProvider, type DataSource, type DataSourceFunction, type DataSourceMeta, type DeleteProps, DynamoLockBackend, FileLockBackend, FileStateBackend, Future, type GetDataProps, type GetProps, Group, type Input, type LockBackend, MemoryLockBackend, MemoryStateBackend, type Meta, type Node, type OptionalInput, type OptionalOutput, Output, type PlanProps, type ProcedureOptions, type Provider, type Resource, ResourceAlreadyExists, type ResourceClass, type ResourceConfig, ResourceError, type ResourceMeta, ResourceNotFound, S3StateBackend, Stack, type State, type StateBackend, type Tag, type URN, type UpdateProps, WorkSpace, type WorkSpaceOptions, createCustomProvider, createCustomResourceClass, createDebugger, createMeta, deferredOutput, enableDebug, findInputDeps, getMeta, isDataSource, isNode, isResource, nodeMetaSymbol, output, resolveInputs };
|
package/dist/index.js
CHANGED
|
@@ -610,11 +610,23 @@ var deleteResource = async (appToken, urn, state, opt) => {
|
|
|
610
610
|
const idempotantToken = createIdempotantToken(appToken, urn, "delete");
|
|
611
611
|
const provider = findProvider(opt.providers, state.provider);
|
|
612
612
|
try {
|
|
613
|
+
await opt.hooks?.beforeResourceDelete?.({
|
|
614
|
+
urn,
|
|
615
|
+
type: state.type,
|
|
616
|
+
oldInput: state.input,
|
|
617
|
+
oldOutput: state.output
|
|
618
|
+
});
|
|
613
619
|
await provider.deleteResource({
|
|
614
620
|
type: state.type,
|
|
615
621
|
state: state.output,
|
|
616
622
|
idempotantToken
|
|
617
623
|
});
|
|
624
|
+
await opt.hooks?.afterResourceDelete?.({
|
|
625
|
+
urn,
|
|
626
|
+
type: state.type,
|
|
627
|
+
oldInput: state.input,
|
|
628
|
+
oldOutput: state.output
|
|
629
|
+
});
|
|
618
630
|
} catch (error) {
|
|
619
631
|
if (error instanceof ResourceNotFound) {
|
|
620
632
|
debug(state.type, "already deleted");
|
|
@@ -699,11 +711,24 @@ var createResource = async (resource, appToken, input, opt) => {
|
|
|
699
711
|
debug2(input);
|
|
700
712
|
let result;
|
|
701
713
|
try {
|
|
714
|
+
await opt.hooks?.beforeResourceCreate?.({
|
|
715
|
+
urn: resource.urn,
|
|
716
|
+
type: meta.type,
|
|
717
|
+
resource,
|
|
718
|
+
newInput: input
|
|
719
|
+
});
|
|
702
720
|
result = await provider.createResource({
|
|
703
721
|
type: meta.type,
|
|
704
722
|
state: input,
|
|
705
723
|
idempotantToken
|
|
706
724
|
});
|
|
725
|
+
await opt.hooks?.afterResourceCreate?.({
|
|
726
|
+
urn: resource.urn,
|
|
727
|
+
type: meta.type,
|
|
728
|
+
resource,
|
|
729
|
+
newInput: input,
|
|
730
|
+
newOutput: result.state
|
|
731
|
+
});
|
|
707
732
|
} catch (error) {
|
|
708
733
|
throw ResourceError.wrap(meta.urn, meta.type, "create", error);
|
|
709
734
|
}
|
|
@@ -774,7 +799,7 @@ var importResource = async (resource, input, opt) => {
|
|
|
774
799
|
|
|
775
800
|
// src/workspace/procedure/replace-resource.ts
|
|
776
801
|
var debug5 = createDebugger("Replace");
|
|
777
|
-
var replaceResource = async (resource, appToken,
|
|
802
|
+
var replaceResource = async (resource, appToken, priorInputState, priorOutputState, proposedState, opt) => {
|
|
778
803
|
const meta = getMeta(resource);
|
|
779
804
|
const urn = meta.urn;
|
|
780
805
|
const type = meta.type;
|
|
@@ -786,11 +811,23 @@ var replaceResource = async (resource, appToken, priorState, proposedState, opt)
|
|
|
786
811
|
debug5("retain", type);
|
|
787
812
|
} else {
|
|
788
813
|
try {
|
|
814
|
+
await opt.hooks?.beforeResourceDelete?.({
|
|
815
|
+
urn,
|
|
816
|
+
type,
|
|
817
|
+
oldInput: priorInputState,
|
|
818
|
+
oldOutput: priorOutputState
|
|
819
|
+
});
|
|
789
820
|
await provider.deleteResource({
|
|
790
821
|
type,
|
|
791
|
-
state:
|
|
822
|
+
state: priorOutputState,
|
|
792
823
|
idempotantToken
|
|
793
824
|
});
|
|
825
|
+
await opt.hooks?.afterResourceDelete?.({
|
|
826
|
+
urn,
|
|
827
|
+
type,
|
|
828
|
+
oldInput: priorInputState,
|
|
829
|
+
oldOutput: priorOutputState
|
|
830
|
+
});
|
|
794
831
|
} catch (error) {
|
|
795
832
|
if (error instanceof ResourceNotFound) {
|
|
796
833
|
debug5(type, "already deleted");
|
|
@@ -801,11 +838,24 @@ var replaceResource = async (resource, appToken, priorState, proposedState, opt)
|
|
|
801
838
|
}
|
|
802
839
|
let result;
|
|
803
840
|
try {
|
|
841
|
+
await opt.hooks?.beforeResourceCreate?.({
|
|
842
|
+
urn,
|
|
843
|
+
type,
|
|
844
|
+
resource,
|
|
845
|
+
newInput: proposedState
|
|
846
|
+
});
|
|
804
847
|
result = await provider.createResource({
|
|
805
848
|
type,
|
|
806
849
|
state: proposedState,
|
|
807
850
|
idempotantToken
|
|
808
851
|
});
|
|
852
|
+
await opt.hooks?.afterResourceCreate?.({
|
|
853
|
+
urn,
|
|
854
|
+
type,
|
|
855
|
+
resource,
|
|
856
|
+
newInput: proposedState,
|
|
857
|
+
newOutput: result.state
|
|
858
|
+
});
|
|
809
859
|
} catch (error) {
|
|
810
860
|
throw ResourceError.wrap(urn, type, "replace", error);
|
|
811
861
|
}
|
|
@@ -817,7 +867,7 @@ var replaceResource = async (resource, appToken, priorState, proposedState, opt)
|
|
|
817
867
|
|
|
818
868
|
// src/workspace/procedure/update-resource.ts
|
|
819
869
|
var debug6 = createDebugger("Update");
|
|
820
|
-
var updateResource = async (resource, appToken,
|
|
870
|
+
var updateResource = async (resource, appToken, priorInputState, priorOutputState, proposedState, opt) => {
|
|
821
871
|
const meta = getMeta(resource);
|
|
822
872
|
const provider = findProvider(opt.providers, meta.provider);
|
|
823
873
|
const idempotantToken = createIdempotantToken(appToken, meta.urn, "update");
|
|
@@ -825,12 +875,29 @@ var updateResource = async (resource, appToken, priorState, proposedState, opt)
|
|
|
825
875
|
debug6(meta.type);
|
|
826
876
|
debug6(proposedState);
|
|
827
877
|
try {
|
|
878
|
+
await opt.hooks?.beforeResourceUpdate?.({
|
|
879
|
+
urn: resource.urn,
|
|
880
|
+
type: meta.type,
|
|
881
|
+
resource,
|
|
882
|
+
newInput: proposedState,
|
|
883
|
+
oldInput: priorInputState,
|
|
884
|
+
oldOutput: priorOutputState
|
|
885
|
+
});
|
|
828
886
|
result = await provider.updateResource({
|
|
829
887
|
type: meta.type,
|
|
830
|
-
priorState,
|
|
888
|
+
priorState: priorOutputState,
|
|
831
889
|
proposedState,
|
|
832
890
|
idempotantToken
|
|
833
891
|
});
|
|
892
|
+
await opt.hooks?.afterResourceUpdate?.({
|
|
893
|
+
urn: resource.urn,
|
|
894
|
+
type: meta.type,
|
|
895
|
+
resource,
|
|
896
|
+
newInput: proposedState,
|
|
897
|
+
oldInput: priorInputState,
|
|
898
|
+
newOutput: result.state,
|
|
899
|
+
oldOutput: priorOutputState
|
|
900
|
+
});
|
|
834
901
|
} catch (error) {
|
|
835
902
|
throw ResourceError.wrap(meta.urn, meta.type, "update", error);
|
|
836
903
|
}
|
|
@@ -842,6 +909,89 @@ var updateResource = async (resource, appToken, priorState, proposedState, opt)
|
|
|
842
909
|
|
|
843
910
|
// src/workspace/procedure/deploy-app.ts
|
|
844
911
|
var debug7 = createDebugger("Deploy App");
|
|
912
|
+
var findDependencyPaths = (value, dependencyUrn, path = []) => {
|
|
913
|
+
const paths = [];
|
|
914
|
+
const visit = (current, currentPath) => {
|
|
915
|
+
if (current instanceof Output) {
|
|
916
|
+
for (const dep of current.dependencies) {
|
|
917
|
+
if (dep.urn === dependencyUrn) {
|
|
918
|
+
paths.push(currentPath);
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
if (Array.isArray(current)) {
|
|
925
|
+
current.forEach((item, index) => {
|
|
926
|
+
visit(item, [...currentPath, index]);
|
|
927
|
+
});
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
if (current && typeof current === "object") {
|
|
931
|
+
for (const [key, item] of Object.entries(current)) {
|
|
932
|
+
visit(item, [...currentPath, key]);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
};
|
|
936
|
+
visit(value, path);
|
|
937
|
+
return paths;
|
|
938
|
+
};
|
|
939
|
+
var cloneState = (value) => JSON.parse(JSON.stringify(value));
|
|
940
|
+
var removeAtPath = (target, path) => {
|
|
941
|
+
if (path.length === 0) return;
|
|
942
|
+
let parent = target;
|
|
943
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
944
|
+
if (parent == null) return;
|
|
945
|
+
parent = parent[path[i]];
|
|
946
|
+
}
|
|
947
|
+
const last = path[path.length - 1];
|
|
948
|
+
if (Array.isArray(parent) && typeof last === "number") {
|
|
949
|
+
if (last >= 0 && last < parent.length) {
|
|
950
|
+
parent.splice(last, 1);
|
|
951
|
+
}
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
if (parent && typeof parent === "object") {
|
|
955
|
+
delete parent[last];
|
|
956
|
+
}
|
|
957
|
+
};
|
|
958
|
+
var stripDependencyInputs = (input, metaInput, dependencyUrn) => {
|
|
959
|
+
const paths = findDependencyPaths(metaInput, dependencyUrn);
|
|
960
|
+
if (paths.length === 0) {
|
|
961
|
+
return input;
|
|
962
|
+
}
|
|
963
|
+
const detached = cloneState(input);
|
|
964
|
+
const sortedPaths = [...paths].sort((a, b) => {
|
|
965
|
+
if (a.length !== b.length) return b.length - a.length;
|
|
966
|
+
const aLast = a[a.length - 1];
|
|
967
|
+
const bLast = b[b.length - 1];
|
|
968
|
+
if (typeof aLast === "number" && typeof bLast === "number") {
|
|
969
|
+
return bLast - aLast;
|
|
970
|
+
}
|
|
971
|
+
return 0;
|
|
972
|
+
});
|
|
973
|
+
for (const path of sortedPaths) {
|
|
974
|
+
removeAtPath(detached, path);
|
|
975
|
+
}
|
|
976
|
+
return detached;
|
|
977
|
+
};
|
|
978
|
+
var allowsDependentReplace = (replaceOnChanges, dependencyPaths) => {
|
|
979
|
+
if (!replaceOnChanges || replaceOnChanges.length === 0) {
|
|
980
|
+
return false;
|
|
981
|
+
}
|
|
982
|
+
for (const path of dependencyPaths) {
|
|
983
|
+
const base = typeof path[0] === "string" ? path[0] : void 0;
|
|
984
|
+
if (!base) {
|
|
985
|
+
continue;
|
|
986
|
+
}
|
|
987
|
+
for (const replacePath of replaceOnChanges) {
|
|
988
|
+
if (replacePath === base || replacePath.startsWith(`${base}.`) || replacePath.startsWith(`${base}[`) || replacePath.startsWith(`${base}.*`)) {
|
|
989
|
+
return true;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
return false;
|
|
994
|
+
};
|
|
845
995
|
var deployApp = async (app, opt) => {
|
|
846
996
|
debug7(app.name, "start");
|
|
847
997
|
const latestState = await opt.backend.state.get(app.urn);
|
|
@@ -864,6 +1014,20 @@ var deployApp = async (app, opt) => {
|
|
|
864
1014
|
stacks = app.stacks.filter((stack) => opt.filters.includes(stack.name));
|
|
865
1015
|
filteredOutStacks = app.stacks.filter((stack) => !opt.filters.includes(stack.name));
|
|
866
1016
|
}
|
|
1017
|
+
const nodeByUrn = /* @__PURE__ */ new Map();
|
|
1018
|
+
const stackStates = /* @__PURE__ */ new Map();
|
|
1019
|
+
const plannedDependents = /* @__PURE__ */ new Set();
|
|
1020
|
+
const forcedUpdateDependents = /* @__PURE__ */ new Set();
|
|
1021
|
+
for (const stack of stacks) {
|
|
1022
|
+
const stackState = appState.stacks[stack.urn] = appState.stacks[stack.urn] ?? {
|
|
1023
|
+
name: stack.name,
|
|
1024
|
+
nodes: {}
|
|
1025
|
+
};
|
|
1026
|
+
stackStates.set(stack.urn, stackState);
|
|
1027
|
+
for (const node of stack.nodes) {
|
|
1028
|
+
nodeByUrn.set(getMeta(node).urn, node);
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
867
1031
|
const queue = createConcurrencyQueue(opt.concurrency ?? 10);
|
|
868
1032
|
const graph = new DependencyGraph();
|
|
869
1033
|
const allNodes = {};
|
|
@@ -912,10 +1076,7 @@ var deployApp = async (app, opt) => {
|
|
|
912
1076
|
}
|
|
913
1077
|
}
|
|
914
1078
|
for (const stack of stacks) {
|
|
915
|
-
const stackState =
|
|
916
|
-
name: stack.name,
|
|
917
|
-
nodes: {}
|
|
918
|
-
};
|
|
1079
|
+
const stackState = stackStates.get(stack.urn);
|
|
919
1080
|
for (const [urn, nodeState] of entries(stackState.nodes)) {
|
|
920
1081
|
const resource = stack.nodes.find((r) => getMeta(r).urn === urn);
|
|
921
1082
|
if (!resource) {
|
|
@@ -986,6 +1147,7 @@ var deployApp = async (app, opt) => {
|
|
|
986
1147
|
const newResourceState = await updateResource(
|
|
987
1148
|
node,
|
|
988
1149
|
appState.idempotentToken,
|
|
1150
|
+
importedState.input,
|
|
989
1151
|
importedState.output,
|
|
990
1152
|
input,
|
|
991
1153
|
opt
|
|
@@ -1013,10 +1175,97 @@ var deployApp = async (app, opt) => {
|
|
|
1013
1175
|
!compareState(nodeState.input, input)
|
|
1014
1176
|
) {
|
|
1015
1177
|
let newResourceState;
|
|
1016
|
-
|
|
1178
|
+
const ignoreReplace = forcedUpdateDependents.has(meta2.urn);
|
|
1179
|
+
if (!ignoreReplace && requiresReplacement(nodeState.input, input, meta2.config?.replaceOnChanges ?? [])) {
|
|
1180
|
+
for (const [dependentUrn, dependentNode] of nodeByUrn.entries()) {
|
|
1181
|
+
if (!isResource(dependentNode)) {
|
|
1182
|
+
continue;
|
|
1183
|
+
}
|
|
1184
|
+
const dependentMeta = getMeta(dependentNode);
|
|
1185
|
+
if (!dependentMeta.dependencies.has(meta2.urn)) {
|
|
1186
|
+
continue;
|
|
1187
|
+
}
|
|
1188
|
+
if (plannedDependents.has(dependentUrn)) {
|
|
1189
|
+
continue;
|
|
1190
|
+
}
|
|
1191
|
+
const dependentStackState = stackStates.get(dependentMeta.stack.urn);
|
|
1192
|
+
const dependentState = dependentStackState?.nodes[dependentUrn];
|
|
1193
|
+
if (!dependentStackState || !dependentState) {
|
|
1194
|
+
continue;
|
|
1195
|
+
}
|
|
1196
|
+
const dependencyPaths = findDependencyPaths(dependentMeta.input, meta2.urn);
|
|
1197
|
+
if (dependencyPaths.length === 0) {
|
|
1198
|
+
continue;
|
|
1199
|
+
}
|
|
1200
|
+
const detachedInput = stripDependencyInputs(
|
|
1201
|
+
dependentState.input,
|
|
1202
|
+
dependentMeta.input,
|
|
1203
|
+
meta2.urn
|
|
1204
|
+
);
|
|
1205
|
+
if (compareState(dependentState.input, detachedInput)) {
|
|
1206
|
+
continue;
|
|
1207
|
+
}
|
|
1208
|
+
plannedDependents.add(dependentUrn);
|
|
1209
|
+
let dependentRequiresReplacement = false;
|
|
1210
|
+
const dependentProvider = findProvider(opt.providers, dependentMeta.provider);
|
|
1211
|
+
if (dependentProvider.planResourceChange) {
|
|
1212
|
+
try {
|
|
1213
|
+
const dependentPlan = await dependentProvider.planResourceChange({
|
|
1214
|
+
type: dependentMeta.type,
|
|
1215
|
+
priorState: dependentState.output,
|
|
1216
|
+
proposedState: detachedInput
|
|
1217
|
+
});
|
|
1218
|
+
dependentRequiresReplacement = dependentPlan.requiresReplacement;
|
|
1219
|
+
} catch (error) {
|
|
1220
|
+
throw ResourceError.wrap(
|
|
1221
|
+
dependentMeta.urn,
|
|
1222
|
+
dependentMeta.type,
|
|
1223
|
+
"update",
|
|
1224
|
+
error
|
|
1225
|
+
);
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
if (dependentRequiresReplacement) {
|
|
1229
|
+
if (!allowsDependentReplace(
|
|
1230
|
+
dependentMeta.config?.replaceOnChanges,
|
|
1231
|
+
dependencyPaths
|
|
1232
|
+
)) {
|
|
1233
|
+
throw ResourceError.wrap(
|
|
1234
|
+
dependentMeta.urn,
|
|
1235
|
+
dependentMeta.type,
|
|
1236
|
+
"update",
|
|
1237
|
+
new Error(
|
|
1238
|
+
`Replacing ${meta2.urn} requires ${dependentMeta.urn} to set replaceOnChanges for its dependency fields.`
|
|
1239
|
+
)
|
|
1240
|
+
);
|
|
1241
|
+
}
|
|
1242
|
+
await deleteResource(
|
|
1243
|
+
appState.idempotentToken,
|
|
1244
|
+
dependentUrn,
|
|
1245
|
+
dependentState,
|
|
1246
|
+
opt
|
|
1247
|
+
);
|
|
1248
|
+
delete dependentStackState.nodes[dependentUrn];
|
|
1249
|
+
} else {
|
|
1250
|
+
const updated = await updateResource(
|
|
1251
|
+
dependentNode,
|
|
1252
|
+
appState.idempotentToken,
|
|
1253
|
+
dependentState.input,
|
|
1254
|
+
dependentState.output,
|
|
1255
|
+
detachedInput,
|
|
1256
|
+
opt
|
|
1257
|
+
);
|
|
1258
|
+
Object.assign(dependentState, {
|
|
1259
|
+
input: detachedInput,
|
|
1260
|
+
...updated
|
|
1261
|
+
});
|
|
1262
|
+
forcedUpdateDependents.add(dependentUrn);
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1017
1265
|
newResourceState = await replaceResource(
|
|
1018
1266
|
node,
|
|
1019
1267
|
appState.idempotentToken,
|
|
1268
|
+
nodeState.input,
|
|
1020
1269
|
nodeState.output,
|
|
1021
1270
|
input,
|
|
1022
1271
|
opt
|
|
@@ -1025,10 +1274,14 @@ var deployApp = async (app, opt) => {
|
|
|
1025
1274
|
newResourceState = await updateResource(
|
|
1026
1275
|
node,
|
|
1027
1276
|
appState.idempotentToken,
|
|
1277
|
+
nodeState.input,
|
|
1028
1278
|
nodeState.output,
|
|
1029
1279
|
input,
|
|
1030
1280
|
opt
|
|
1031
1281
|
);
|
|
1282
|
+
if (ignoreReplace) {
|
|
1283
|
+
forcedUpdateDependents.delete(meta2.urn);
|
|
1284
|
+
}
|
|
1032
1285
|
}
|
|
1033
1286
|
Object.assign(nodeState, {
|
|
1034
1287
|
input,
|
|
@@ -1514,6 +1767,21 @@ var createCustomProvider = (providerId, resourceProviders) => {
|
|
|
1514
1767
|
async deleteResource({ type, ...props }) {
|
|
1515
1768
|
await getProvider(type).deleteResource?.(props);
|
|
1516
1769
|
},
|
|
1770
|
+
async planResourceChange({ type, ...props }) {
|
|
1771
|
+
const provider = getProvider(type);
|
|
1772
|
+
if (!provider.planResourceChange) {
|
|
1773
|
+
return {
|
|
1774
|
+
version,
|
|
1775
|
+
state: props.proposedState,
|
|
1776
|
+
requiresReplacement: false
|
|
1777
|
+
};
|
|
1778
|
+
}
|
|
1779
|
+
const result = await provider.planResourceChange(props);
|
|
1780
|
+
return {
|
|
1781
|
+
version,
|
|
1782
|
+
...result
|
|
1783
|
+
};
|
|
1784
|
+
},
|
|
1517
1785
|
async getData({ type, ...props }) {
|
|
1518
1786
|
return {
|
|
1519
1787
|
version,
|
package/package.json
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@terraforge/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
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
|
"exports": {
|
|
@@ -12,7 +19,7 @@
|
|
|
12
19
|
},
|
|
13
20
|
"scripts": {
|
|
14
21
|
"build": "tsup src/index.ts --format esm --dts --clean --out-dir ./dist",
|
|
15
|
-
"prepublishOnly": "bun run build",
|
|
22
|
+
"prepublishOnly": "if bun run test; then bun run build; else exit; fi",
|
|
16
23
|
"test": "bun test"
|
|
17
24
|
},
|
|
18
25
|
"dependencies": {
|