@xplane/devtools 0.16.0 → 1.0.0

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.
@@ -43,6 +43,14 @@ declare class Match {
43
43
  static anyValue(): Matcher;
44
44
  /** Inverts a match — asserts the value does NOT match the given pattern. */
45
45
  static not(pattern: unknown): Matcher;
46
+ /**
47
+ * Asserts the value is a pending dependency (unresolved cross-resource reference).
48
+ * Optionally match the source resource and/or path.
49
+ */
50
+ static pending(expected?: {
51
+ source?: string;
52
+ path?: string;
53
+ }): Matcher;
46
54
  }
47
55
  //#endregion
48
56
  //#region src/assertions/template.d.ts
@@ -56,34 +64,28 @@ interface SynthesizeOptions {
56
64
  /**
57
65
  * A snapshot of rendered resources from a Composition, providing
58
66
  * assertion methods for unit testing.
59
- *
60
- * @example
61
- * ```ts
62
- * const template = Template.synthesize(MyComposition, {
63
- * xr: { spec: { region: 'us-east-1' } },
64
- * });
65
- * template.hasResourceSpec('ec2.aws.crossplane.io/v1beta1', 'VPC', {
66
- * forProvider: { region: 'us-east-1' },
67
- * });
68
- * ```
69
67
  */
70
68
  declare class Template {
71
69
  private readonly _resources;
72
70
  private constructor();
73
71
  /**
74
- * Ergonomic factory: injects XR/environment data and instantiates
75
- * the Composition class, then builds a Template from the rendered resources.
72
+ * Instantiate a Composition and run the full pipeline to produce a Template.
76
73
  *
77
- * Users never need to touch `Composition._pendingXR` directly.
74
+ * Includes ALL declared resources (both emitted and blocked) — Pending
75
+ * dependency values are stripped to `undefined`. Use `Simulator` if you
76
+ * need to distinguish emitted vs blocked.
78
77
  */
79
- static synthesize(Ctor: new () => Composition, options?: SynthesizeOptions): Template;
78
+ static synthesize<TSpec, TStatus, TContext extends object>(Ctor: new () => Composition<TSpec, TStatus, TContext>, options?: SynthesizeOptions): Template;
80
79
  /**
81
80
  * Build a Template from an already-instantiated Composition.
81
+ *
82
+ * Extracts desired documents from ALL non-external resources. Unresolved
83
+ * Pending markers are serialized as `PendingValue` objects — use
84
+ * `Match.pending()` to assert them in tests.
82
85
  */
83
86
  static fromComposition(composition: Composition): Template;
84
87
  /**
85
- * Build a Template from a pre-built array of KubernetesResource objects.
86
- * Used internally by Simulator.
88
+ * Build a Template from a pre-built array of resource documents.
87
89
  */
88
90
  static fromResources(resources: KubernetesResource$1[]): Template;
89
91
  /** Get all resources matching apiVersion + kind. */
@@ -125,6 +127,18 @@ declare class Template {
125
127
  */
126
128
  toJSON(): KubernetesResource$1[];
127
129
  }
130
+ /** Symbol tag used to identify serialized PendingValue objects. */
131
+ declare const PENDING_VALUE: unique symbol;
132
+ /** Serialized form of a Pending marker in Template resource documents. */
133
+ interface PendingValue {
134
+ readonly [PENDING_VALUE]: true;
135
+ /** The resource this value is waiting on. */
136
+ readonly source: string;
137
+ /** The path within that resource's observed data. */
138
+ readonly path: string;
139
+ }
140
+ /** Type guard for PendingValue. */
141
+ declare function isPendingValue(value: unknown): value is PendingValue;
128
142
  //#endregion
129
143
  //#region src/assertions/simulator.d.ts
130
144
  /** Result of a simulation run. */
@@ -140,6 +154,14 @@ interface SimulationResult {
140
154
  reason: string;
141
155
  message: string;
142
156
  }>;
157
+ /**
158
+ * Evaluate readiness of a specific emitted resource using its registered
159
+ * readyChecks + built-in defaults against the observed state.
160
+ *
161
+ * @param resourceName The construct path of the resource (e.g., 'Cluster Provider Config')
162
+ * @returns `true` if ready, `false` if not ready or resource not found
163
+ */
164
+ isReady(resourceName: string): boolean;
143
165
  }
144
166
  /**
145
167
  * Simulates the full rendering pipeline including observed state injection,
@@ -165,7 +187,7 @@ declare class Simulator {
165
187
  * Ergonomic factory: injects XR/environment data, instantiates the
166
188
  * Composition class, and returns a Simulator ready for `.withObserved().run()`.
167
189
  */
168
- static synthesize(Ctor: new () => Composition, options?: SynthesizeOptions): Simulator;
190
+ static synthesize<TSpec, TStatus, TContext extends object>(Ctor: new () => Composition<TSpec, TStatus, TContext>, options?: SynthesizeOptions): Simulator;
169
191
  /**
170
192
  * Build a Simulator from an already-instantiated Composition.
171
193
  */
@@ -175,7 +197,7 @@ declare class Simulator {
175
197
  * Each resource is matched to a declared resource by its construct path
176
198
  * (i.e., `metadata.name` in observed state maps to `resource.path` in the composition).
177
199
  */
178
- withObserved(resources: KubernetesResource$1[]): this;
200
+ withObserved(resources: Record<string, unknown>[]): this;
179
201
  /**
180
202
  * Provide existing resource data keyed by refKey.
181
203
  * The refKey format is `apiVersion/kind/[namespace/]name`
@@ -183,12 +205,12 @@ declare class Simulator {
183
205
  *
184
206
  * This simulates what Crossplane would return via the Required Resources mechanism.
185
207
  */
186
- withExisting(resources: Record<string, KubernetesResource$1>): this;
208
+ withExisting(resources: Record<string, Record<string, unknown>>): this;
187
209
  /**
188
210
  * Run the simulation: inject observed state, resolve edges, determine sequencing.
189
211
  */
190
212
  run(): SimulationResult;
191
213
  }
192
214
  //#endregion
193
- export { type KubernetesResource, Match, type MatchResult, type Matcher, type SimulationResult, Simulator, type SynthesizeOptions, Template };
215
+ export { type KubernetesResource, Match, type MatchResult, type Matcher, PENDING_VALUE, type PendingValue, type SimulationResult, Simulator, type SynthesizeOptions, Template, isPendingValue };
194
216
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/assertions/match.ts","../../src/assertions/template.ts","../../src/assertions/simulator.ts"],"mappings":";;;;cACa,OAAA;;UAGI,WAAA;EAH2C;EAK1D,IAAA;EAL0D;EAO1D,QAAQ;AAAA;;UAIO,OAAA;EAAA,UACL,OAAA;EACV,IAAA,CAAK,MAAA,YAAkB,WAAW;AAAA;;;;;;;;;;;cAwQvB,KAAA;EAAA,QACJ,WAAA,CAAA;EAG6B;EAAA,OAA7B,UAAA,CAAW,OAAA,WAAkB,OAAA;EAKhB;EAAA,OAAb,YAAA,CAAa,OAAA,WAAkB,OAAA;EAK/B;EAAA,OAAA,SAAA,CAAU,KAAA,cAAmB,OAAA;EAAA;EAAA,OAK7B,WAAA,CAAY,KAAA,cAAmB,OAAA;EAAnB;EAAA,OAKZ,gBAAA,CAAiB,OAAA,WAAkB,MAAA,GAAS,OAAA;EAA5C;EAAA,OAKA,MAAA,CAAA,GAAU,OAAA;EALO;EAAA,OAUjB,QAAA,CAAA,GAAY,OAAA;EALZ;EAAA,OAUA,GAAA,CAAI,OAAA,YAAmB,OAAA;AAAA;;;;UCzTf,iBAAA;EDHJ;ECKX,EAAA,GAAK,MAAA;;EAEL,WAAA,GAAc,MAAM;AAAA;ADJtB;;;;AAIU;AAIV;;;;;;;;;AARA,cCqBa,QAAA;EAAA,iBACM,UAAA;EAAA,QAEV,WAAA,CAAA;;;;;;;SAUA,UAAA,CAAW,IAAA,YAAgB,WAAA,EAAa,OAAA,GAAS,iBAAA,GAAyB,QAAA;ED6QhE;;;EAAA,OChPV,eAAA,CAAgB,WAAA,EAAa,WAAA,GAAc,QAAA;ED0Pb;;;;EAAA,OC3O9B,aAAA,CAAc,SAAA,EAAW,oBAAA,KAAuB,QAAA;ED6MhD;EAAA,QCxMC,YAAA;EDwM8B;;;ECjMtC,eAAA,CAAgB,UAAA,UAAoB,IAAA,UAAc,KAAA;ED2M3C;;;;EC9LP,WAAA,CAAY,UAAA,UAAoB,IAAA,UAAc,KAAA;EDmMtB;;;;EC1KxB,eAAA,CAAgB,UAAA,UAAoB,IAAA,UAAc,SAAA;EDoL/B;;;EC9JnB,mBAAA,CAAoB,UAAA,UAAoB,IAAA,UAAc,SAAA;EDmKjB;AAAA;;EC7IrC,YAAA,CAAa,UAAA,UAAoB,IAAA,UAAc,KAAA;;AA5KjD;;;EAsME,aAAA,CAAc,UAAA,UAAoB,IAAA,UAAc,KAAA,YAAiB,oBAAA;EApMjE;;;;;AAEoB;AAiBtB;;EAmME,MAAA,CAAA,GAAU,oBAAA;AAAA;;;;UClNK,gBAAA;EFT2C;EEW1D,OAAA,EAAS,QAAA;EFXiD;EEa1D,OAAA,EAAS,QAAA;EFVM;EEYf,UAAA,EAAY,KAAA;IAAQ,IAAA;IAAc,MAAA;IAAgB,MAAA;IAAgB,OAAA;EAAA;AAAA;;;;;;;AFFhC;AAwQpC;;;;;;;;cEvNa,SAAA;EAAA,iBACM,YAAA;EAAA,QACT,SAAA;EAAA,QACA,SAAA;EAAA,QAED,WAAA,CAAA;EFyP8B;;;;EAAA,OEjP9B,UAAA,CAAW,IAAA,YAAgB,WAAA,EAAa,OAAA,GAAS,iBAAA,GAAyB,SAAA;EFmN1E;;;EAAA,OEtLA,eAAA,CAAgB,WAAA,EAAa,WAAA,GAAc,SAAA;EF2LjC;;;;;EElLjB,YAAA,CAAa,SAAA,EAAW,oBAAA;EF4LkB;;;;;;;EEhL1C,YAAA,CAAa,SAAA,EAAW,MAAA,SAAe,oBAAA;EF+L5B;;;EEvLX,GAAA,CAAA,GAAO,gBAAA;AAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/assertions/match.ts","../../src/assertions/template.ts","../../src/assertions/simulator.ts"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { resolveSequencing } from "@xplane/core";
1
+ import { DEFAULT_CHECKS, DependencyGraph, EdgeCollector, Pending, Resource, compositionStorage, evaluateReadiness, getDesiredDocument, getExternalRef, isExternal, runPipeline } from "@xplane/core";
2
2
  //#region src/assertions/match.ts
3
3
  /** Symbol used to identify Matcher instances. */
4
4
  const MATCHER = Symbol.for("xplane.devtools.matcher");
@@ -179,6 +179,21 @@ var NotMatcher = class {
179
179
  return pass();
180
180
  }
181
181
  };
182
+ const PENDING_VALUE$1 = Symbol.for("xplane.devtools.pending");
183
+ var PendingMatcher = class {
184
+ expected;
185
+ [MATCHER] = true;
186
+ constructor(expected) {
187
+ this.expected = expected;
188
+ }
189
+ test(actual) {
190
+ if (typeof actual !== "object" || actual === null || actual[PENDING_VALUE$1] !== true) return fail(`expected a pending value, got ${JSON.stringify(actual)}`);
191
+ const pending = actual;
192
+ if (this.expected?.source && pending.source !== this.expected.source) return fail(`expected pending from source "${this.expected.source}", got "${pending.source}"`);
193
+ if (this.expected?.path && pending.path !== this.expected.path) return fail(`expected pending path "${this.expected.path}", got "${pending.path}"`);
194
+ return pass();
195
+ }
196
+ };
182
197
  /**
183
198
  * Factory for composable matchers used in assertions.
184
199
  *
@@ -223,22 +238,19 @@ var Match = class {
223
238
  static not(pattern) {
224
239
  return new NotMatcher(pattern);
225
240
  }
241
+ /**
242
+ * Asserts the value is a pending dependency (unresolved cross-resource reference).
243
+ * Optionally match the source resource and/or path.
244
+ */
245
+ static pending(expected) {
246
+ return new PendingMatcher(expected);
247
+ }
226
248
  };
227
249
  //#endregion
228
250
  //#region src/assertions/template.ts
229
251
  /**
230
252
  * A snapshot of rendered resources from a Composition, providing
231
253
  * assertion methods for unit testing.
232
- *
233
- * @example
234
- * ```ts
235
- * const template = Template.synthesize(MyComposition, {
236
- * xr: { spec: { region: 'us-east-1' } },
237
- * });
238
- * template.hasResourceSpec('ec2.aws.crossplane.io/v1beta1', 'VPC', {
239
- * forProvider: { region: 'us-east-1' },
240
- * });
241
- * ```
242
254
  */
243
255
  var Template = class Template {
244
256
  _resources;
@@ -246,35 +258,43 @@ var Template = class Template {
246
258
  this._resources = resources;
247
259
  }
248
260
  /**
249
- * Ergonomic factory: injects XR/environment data and instantiates
250
- * the Composition class, then builds a Template from the rendered resources.
261
+ * Instantiate a Composition and run the full pipeline to produce a Template.
251
262
  *
252
- * Users never need to touch `Composition._pendingXR` directly.
263
+ * Includes ALL declared resources (both emitted and blocked) — Pending
264
+ * dependency values are stripped to `undefined`. Use `Simulator` if you
265
+ * need to distinguish emitted vs blocked.
253
266
  */
254
267
  static synthesize(Ctor, options = {}) {
255
- let base = Ctor;
256
- while (base && !Object.hasOwn(base, "_pendingXR")) base = Object.getPrototypeOf(base);
257
- if (!base) throw new Error("Could not find Composition base class with _pendingXR");
258
- const BaseComposition = base;
259
- BaseComposition._pendingXR = options.xr;
260
- BaseComposition._pendingEnvironment = options.environment;
261
- try {
262
- const instance = new Ctor();
263
- return Template.fromComposition(instance);
264
- } finally {
265
- BaseComposition._pendingXR = void 0;
266
- BaseComposition._pendingEnvironment = void 0;
267
- }
268
+ const xr = options.xr ?? {
269
+ spec: {},
270
+ status: {}
271
+ };
272
+ const pipelineContext = /* @__PURE__ */ new Map();
273
+ if (options.environment) pipelineContext.set("apiextensions.crossplane.io/environment", options.environment);
274
+ const graph = new DependencyGraph();
275
+ const collector = new EdgeCollector();
276
+ const ctx = {
277
+ xr,
278
+ pipelineContext,
279
+ requiredResources: /* @__PURE__ */ new Map(),
280
+ graph,
281
+ collector
282
+ };
283
+ const composition = compositionStorage.run(ctx, () => new Ctor());
284
+ return Template.fromComposition(composition);
268
285
  }
269
286
  /**
270
287
  * Build a Template from an already-instantiated Composition.
288
+ *
289
+ * Extracts desired documents from ALL non-external resources. Unresolved
290
+ * Pending markers are serialized as `PendingValue` objects — use
291
+ * `Match.pending()` to assert them in tests.
271
292
  */
272
293
  static fromComposition(composition) {
273
- return new Template([...composition.resources.values()].map((r) => r.toDesired()));
294
+ return new Template(composition.node.findAll().filter((c) => c instanceof Resource && !isExternal(c)).map((r) => serializePending(getDesiredDocument(r))));
274
295
  }
275
296
  /**
276
- * Build a Template from a pre-built array of KubernetesResource objects.
277
- * Used internally by Simulator.
297
+ * Build a Template from a pre-built array of resource documents.
278
298
  */
279
299
  static fromResources(resources) {
280
300
  return new Template(resources);
@@ -371,27 +391,36 @@ var Template = class Template {
371
391
  return structuredClone(this._resources);
372
392
  }
373
393
  };
374
- //#endregion
375
- //#region src/assertions/simulator.ts
376
- function getNestedValue(obj, path) {
377
- let current = obj;
378
- for (const segment of path.split(".")) {
379
- if (current === null || current === void 0 || typeof current !== "object") return;
380
- current = current[segment];
381
- }
382
- return current;
394
+ /** Symbol tag used to identify serialized PendingValue objects. */
395
+ const PENDING_VALUE = Symbol.for("xplane.devtools.pending");
396
+ /** Type guard for PendingValue. */
397
+ function isPendingValue(value) {
398
+ return typeof value === "object" && value !== null && value[PENDING_VALUE] === true;
383
399
  }
384
- function setNestedValue(obj, path, value) {
385
- const segments = path.split(".");
386
- let current = obj;
387
- for (let i = 0; i < segments.length - 1; i++) {
388
- const seg = segments[i];
389
- if (!(seg in current) || typeof current[seg] !== "object" || current[seg] === null) current[seg] = {};
390
- current = current[seg];
391
- }
392
- const lastSeg = segments[segments.length - 1];
393
- if (lastSeg !== void 0) current[lastSeg] = value;
400
+ /**
401
+ * Deep-clone a desired document, converting Pending instances into
402
+ * serialized PendingValue objects that matchers can assert against.
403
+ */
404
+ function serializePending(obj) {
405
+ const result = {};
406
+ for (const [key, value] of Object.entries(obj)) result[key] = serializeValue(value);
407
+ return result;
408
+ }
409
+ function serializeValue(value) {
410
+ if (value === null || value === void 0) return value;
411
+ if (Pending.is(value)) return {
412
+ [PENDING_VALUE]: true,
413
+ source: value.source.id,
414
+ path: value.path
415
+ };
416
+ if (typeof value !== "object") return value;
417
+ if (Array.isArray(value)) return value.map(serializeValue);
418
+ const result = {};
419
+ for (const [key, val] of Object.entries(value)) result[key] = serializeValue(val);
420
+ return result;
394
421
  }
422
+ //#endregion
423
+ //#region src/assertions/simulator.ts
395
424
  /**
396
425
  * Simulates the full rendering pipeline including observed state injection,
397
426
  * edge resolution, and sequencing — mimicking what `@xplane/function` does at runtime.
@@ -419,18 +448,22 @@ var Simulator = class Simulator {
419
448
  * Composition class, and returns a Simulator ready for `.withObserved().run()`.
420
449
  */
421
450
  static synthesize(Ctor, options = {}) {
422
- let base = Ctor;
423
- while (base && !Object.hasOwn(base, "_pendingXR")) base = Object.getPrototypeOf(base);
424
- if (!base) throw new Error("Could not find Composition base class with _pendingXR");
425
- const BaseComposition = base;
426
- BaseComposition._pendingXR = options.xr;
427
- BaseComposition._pendingEnvironment = options.environment;
428
- try {
429
- return new Simulator(new Ctor());
430
- } finally {
431
- BaseComposition._pendingXR = void 0;
432
- BaseComposition._pendingEnvironment = void 0;
433
- }
451
+ const xr = options.xr ?? {
452
+ spec: {},
453
+ status: {}
454
+ };
455
+ const pipelineContext = /* @__PURE__ */ new Map();
456
+ if (options.environment) pipelineContext.set("apiextensions.crossplane.io/environment", options.environment);
457
+ const graph = new DependencyGraph();
458
+ const collector = new EdgeCollector();
459
+ const ctx = {
460
+ xr,
461
+ pipelineContext,
462
+ requiredResources: /* @__PURE__ */ new Map(),
463
+ graph,
464
+ collector
465
+ };
466
+ return new Simulator(compositionStorage.run(ctx, () => new Ctor()));
434
467
  }
435
468
  /**
436
469
  * Build a Simulator from an already-instantiated Composition.
@@ -463,57 +496,46 @@ var Simulator = class Simulator {
463
496
  */
464
497
  run() {
465
498
  const composition = this._composition;
466
- const resources = composition.resources;
467
- const existingResources = composition.existingResources;
468
- const collector = composition.collector;
469
- const graph = composition.graph;
470
- const observedMap = /* @__PURE__ */ new Map();
499
+ const observedComposed = /* @__PURE__ */ new Map();
471
500
  for (const obs of this._observed) {
472
501
  const name = obs.metadata?.name;
473
- if (name) observedMap.set(name, obs);
474
- }
475
- graph.addEdges(collector.edges);
476
- for (const [path, resource] of resources) {
477
- const observed = observedMap.get(path);
478
- if (observed) resource.setObserved(observed);
502
+ if (name) observedComposed.set(`Composition/${name}`, obs);
479
503
  }
504
+ const observedRequired = new Map(Object.entries(this._existing));
505
+ const result = runPipeline({
506
+ composition,
507
+ observedComposed,
508
+ observedRequired
509
+ });
480
510
  const conditions = [];
481
- for (const [refKey, existingResource] of existingResources) {
482
- const data = this._existing[refKey];
483
- if (data) {
484
- existingResource.setObservedFull(data);
485
- observedMap.set(existingResource.path, data);
486
- } else {
487
- const ref = existingResource.existingRef;
488
- const name = typeof ref?.name === "string" ? ref.name : "<unresolved>";
489
- const kind = ref?.kind ?? "Unknown";
490
- conditions.push({
491
- type: "Ready",
492
- status: "False",
493
- reason: "MissingRequiredResource",
494
- message: `Required existing resource ${kind}/${name} not found in cluster`
495
- });
496
- }
511
+ for (const resource of result.resources) {
512
+ if (!isExternal(resource)) continue;
513
+ const ref = getExternalRef(resource);
514
+ if (!ref || typeof ref.name !== "string") continue;
515
+ if (!observedRequired.has(ref.refKey)) conditions.push({
516
+ type: "Ready",
517
+ status: "False",
518
+ reason: "MissingRequiredResource",
519
+ message: `Required existing resource ${ref.kind}/${ref.name} not found in cluster`
520
+ });
497
521
  }
498
- for (const edge of collector.edges) {
499
- const observed = observedMap.get(edge.from.id);
500
- if (!observed) continue;
501
- const value = getNestedValue(observed, edge.fromPath);
502
- if (value === void 0 || value === null) continue;
503
- const targetResource = resources.get(edge.to.id);
504
- if (!targetResource) continue;
505
- const toPath = edge.toPath;
506
- if (toPath.startsWith("spec.")) setNestedValue(targetResource.spec, toPath.slice(5), value);
507
- }
508
- const sequencing = resolveSequencing(resources, graph, observedMap);
522
+ const blockedResources = result.resources.filter((r) => result.classification.get(r.node.path) === "blocked").map((r) => getDesiredDocument(r));
523
+ const emittedByName = /* @__PURE__ */ new Map();
524
+ for (const e of result.emitted) emittedByName.set(e.name, e);
509
525
  return {
510
- emitted: Template.fromResources(sequencing.emit.map((r) => r.toDesired())),
511
- blocked: Template.fromResources(sequencing.blocked.map((r) => r.toDesired())),
512
- conditions
526
+ emitted: Template.fromResources(result.emitted.map((e) => e.document)),
527
+ blocked: Template.fromResources(blockedResources),
528
+ conditions,
529
+ isReady(resourceName) {
530
+ const emitted = emittedByName.get(resourceName);
531
+ if (!emitted) return false;
532
+ if (!emitted.autoReady) return false;
533
+ return evaluateReadiness([...emitted.readyChecks, ...DEFAULT_CHECKS], observedComposed.get(`Composition/${resourceName}`));
534
+ }
513
535
  };
514
536
  }
515
537
  };
516
538
  //#endregion
517
- export { Match, Simulator, Template };
539
+ export { Match, PENDING_VALUE, Simulator, Template, isPendingValue };
518
540
 
519
541
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/assertions/match.ts","../../src/assertions/template.ts","../../src/assertions/simulator.ts"],"sourcesContent":["/** Symbol used to identify Matcher instances. */\nexport const MATCHER = Symbol.for('xplane.devtools.matcher');\n\n/** Result of a match operation. */\nexport interface MatchResult {\n /** Whether the match succeeded. */\n pass: boolean;\n /** Human-readable failure messages (empty if pass is true). */\n failures: string[];\n}\n\n/** Interface for custom matchers. */\nexport interface Matcher {\n readonly [MATCHER]: true;\n test(actual: unknown): MatchResult;\n}\n\n/** Check whether a value is a Matcher instance. */\nexport function isMatcher(value: unknown): value is Matcher {\n return (\n typeof value === 'object' &&\n value !== null &&\n MATCHER in value &&\n (value as Matcher)[MATCHER] === true\n );\n}\n\nfunction pass(): MatchResult {\n return { pass: true, failures: [] };\n}\n\nfunction fail(message: string): MatchResult {\n return { pass: false, failures: [message] };\n}\n\nfunction merge(results: MatchResult[]): MatchResult {\n const failures = results.flatMap((r) => r.failures);\n return { pass: failures.length === 0, failures };\n}\n\n/**\n * Deep-match `actual` against `expected`.\n * - If `expected` is a Matcher, delegates to its `.test()`.\n * - Literal objects are matched recursively (deep-partial by default via objectLike semantics at the top level is handled by the caller).\n * - This function performs EXACT matching — partial matching is handled by ObjectLikeMatcher.\n */\nexport function deepMatch(actual: unknown, expected: unknown, path = ''): MatchResult {\n if (isMatcher(expected)) {\n const result = expected.test(actual);\n if (!result.pass) {\n return { pass: false, failures: result.failures.map((f) => (path ? `${path}: ${f}` : f)) };\n }\n return pass();\n }\n\n // Null / undefined / primitives\n if (expected === null || expected === undefined || typeof expected !== 'object') {\n if (actual === expected) return pass();\n return fail(\n path\n ? `${path}: expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`\n : `expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`,\n );\n }\n\n // Arrays\n if (Array.isArray(expected)) {\n if (!Array.isArray(actual)) {\n return fail(`${path || 'value'}: expected an array, got ${typeof actual}`);\n }\n if (actual.length !== expected.length) {\n return fail(\n `${path || 'value'}: expected array of length ${expected.length}, got length ${actual.length}`,\n );\n }\n const results: MatchResult[] = [];\n for (let i = 0; i < expected.length; i++) {\n results.push(deepMatch(actual[i], expected[i], `${path}[${i}]`));\n }\n return merge(results);\n }\n\n // Objects (exact match — all keys in expected must match, no extra keys allowed)\n if (typeof actual !== 'object' || actual === null || Array.isArray(actual)) {\n return fail(`${path || 'value'}: expected an object, got ${JSON.stringify(actual)}`);\n }\n const expectedObj = expected as Record<string, unknown>;\n const actualObj = actual as Record<string, unknown>;\n const results: MatchResult[] = [];\n\n // Check expected keys exist and match\n for (const key of Object.keys(expectedObj)) {\n results.push(deepMatch(actualObj[key], expectedObj[key], path ? `${path}.${key}` : key));\n }\n // Check no extra keys in actual\n for (const key of Object.keys(actualObj)) {\n if (!(key in expectedObj)) {\n results.push(fail(`${path ? `${path}.${key}` : key}: unexpected key`));\n }\n }\n return merge(results);\n}\n\n/**\n * Deep-partial match: all keys in `expected` must match in `actual`, but\n * `actual` may have additional keys at any level.\n */\nexport function deepPartialMatch(actual: unknown, expected: unknown, path = ''): MatchResult {\n if (isMatcher(expected)) {\n const result = expected.test(actual);\n if (!result.pass) {\n return { pass: false, failures: result.failures.map((f) => (path ? `${path}: ${f}` : f)) };\n }\n return pass();\n }\n\n if (expected === null || expected === undefined || typeof expected !== 'object') {\n if (actual === expected) return pass();\n return fail(\n path\n ? `${path}: expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`\n : `expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`,\n );\n }\n\n if (Array.isArray(expected)) {\n if (!Array.isArray(actual)) {\n return fail(`${path || 'value'}: expected an array, got ${typeof actual}`);\n }\n if (actual.length !== expected.length) {\n return fail(\n `${path || 'value'}: expected array of length ${expected.length}, got length ${actual.length}`,\n );\n }\n const results: MatchResult[] = [];\n for (let i = 0; i < expected.length; i++) {\n results.push(deepPartialMatch(actual[i], expected[i], `${path}[${i}]`));\n }\n return merge(results);\n }\n\n if (typeof actual !== 'object' || actual === null || Array.isArray(actual)) {\n return fail(`${path || 'value'}: expected an object, got ${JSON.stringify(actual)}`);\n }\n\n const expectedObj = expected as Record<string, unknown>;\n const actualObj = actual as Record<string, unknown>;\n const results: MatchResult[] = [];\n\n // Only check keys present in expected — actual may have extras\n for (const key of Object.keys(expectedObj)) {\n if (!(key in actualObj)) {\n results.push(fail(`${path ? `${path}.${key}` : key}: key not found in actual`));\n } else {\n results.push(\n deepPartialMatch(actualObj[key], expectedObj[key], path ? `${path}.${key}` : key),\n );\n }\n }\n return merge(results);\n}\n\n// ─── Matcher Implementations ────────────────────────────────────────────\n\nclass ObjectLikeMatcher implements Matcher {\n readonly [MATCHER] = true as const;\n constructor(private readonly pattern: object) {}\n test(actual: unknown): MatchResult {\n return deepPartialMatch(actual, this.pattern);\n }\n}\n\nclass ObjectEqualsMatcher implements Matcher {\n readonly [MATCHER] = true as const;\n constructor(private readonly pattern: object) {}\n test(actual: unknown): MatchResult {\n return deepMatch(actual, this.pattern);\n }\n}\n\nclass ArrayWithMatcher implements Matcher {\n readonly [MATCHER] = true as const;\n constructor(private readonly items: unknown[]) {}\n test(actual: unknown): MatchResult {\n if (!Array.isArray(actual)) {\n return fail(`expected an array, got ${typeof actual}`);\n }\n // Each item in `this.items` must appear in `actual` in order (not necessarily contiguous)\n let searchStart = 0;\n for (let i = 0; i < this.items.length; i++) {\n let found = false;\n for (let j = searchStart; j < actual.length; j++) {\n const r = deepPartialMatch(actual[j], this.items[i]);\n if (r.pass) {\n searchStart = j + 1;\n found = true;\n break;\n }\n }\n if (!found) {\n return fail(\n `arrayWith: could not find match for item at index ${i}: ${JSON.stringify(this.items[i])}`,\n );\n }\n }\n return pass();\n }\n}\n\nclass ArrayEqualsMatcher implements Matcher {\n readonly [MATCHER] = true as const;\n constructor(private readonly items: unknown[]) {}\n test(actual: unknown): MatchResult {\n return deepMatch(actual, this.items);\n }\n}\n\nclass StringLikeRegexpMatcher implements Matcher {\n readonly [MATCHER] = true as const;\n private readonly regex: RegExp;\n constructor(pattern: string | RegExp) {\n this.regex = typeof pattern === 'string' ? new RegExp(pattern) : pattern;\n }\n test(actual: unknown): MatchResult {\n if (typeof actual !== 'string') {\n return fail(`expected a string, got ${typeof actual}`);\n }\n if (!this.regex.test(actual)) {\n return fail(`expected string matching ${this.regex}, got \"${actual}\"`);\n }\n return pass();\n }\n}\n\nclass AbsentMatcher implements Matcher {\n readonly [MATCHER] = true as const;\n test(actual: unknown): MatchResult {\n if (actual !== undefined) {\n return fail(`expected absent (undefined), got ${JSON.stringify(actual)}`);\n }\n return pass();\n }\n}\n\nclass AnyValueMatcher implements Matcher {\n readonly [MATCHER] = true as const;\n test(actual: unknown): MatchResult {\n if (actual === null || actual === undefined) {\n return fail(`expected any value, got ${actual}`);\n }\n return pass();\n }\n}\n\nclass NotMatcher implements Matcher {\n readonly [MATCHER] = true as const;\n constructor(private readonly pattern: unknown) {}\n test(actual: unknown): MatchResult {\n const inner = isMatcher(this.pattern)\n ? this.pattern.test(actual)\n : deepPartialMatch(actual, this.pattern);\n if (inner.pass) {\n return fail(`expected NOT to match, but matched: ${JSON.stringify(actual)}`);\n }\n return pass();\n }\n}\n\n/**\n * Factory for composable matchers used in assertions.\n *\n * @example\n * ```ts\n * template.hasResourceSpec('v1', 'ConfigMap', {\n * data: Match.objectLike({ key: 'value' }),\n * });\n * ```\n */\nexport class Match {\n private constructor() {}\n\n /** Deep-partial object match — actual may be a superset of pattern. */\n static objectLike(pattern: object): Matcher {\n return new ObjectLikeMatcher(pattern);\n }\n\n /** Exact object match — actual must equal pattern exactly (same keys, same values). */\n static objectEquals(pattern: object): Matcher {\n return new ObjectEqualsMatcher(pattern);\n }\n\n /** Array subset match — items must appear in actual in order. */\n static arrayWith(items: unknown[]): Matcher {\n return new ArrayWithMatcher(items);\n }\n\n /** Exact array match — actual must equal items exactly. */\n static arrayEquals(items: unknown[]): Matcher {\n return new ArrayEqualsMatcher(items);\n }\n\n /** String regex match. */\n static stringLikeRegexp(pattern: string | RegExp): Matcher {\n return new StringLikeRegexpMatcher(pattern);\n }\n\n /** Asserts the value is absent (undefined). */\n static absent(): Matcher {\n return new AbsentMatcher();\n }\n\n /** Asserts any non-null/non-undefined value is present. */\n static anyValue(): Matcher {\n return new AnyValueMatcher();\n }\n\n /** Inverts a match — asserts the value does NOT match the given pattern. */\n static not(pattern: unknown): Matcher {\n return new NotMatcher(pattern);\n }\n}\n","import type { Composition, KubernetesResource } from '@xplane/core';\nimport { deepPartialMatch } from './match.js';\n\n/** Options for `Template.synthesize()`. */\nexport interface SynthesizeOptions {\n /** XR (composite resource) data to inject before instantiation. */\n xr?: Record<string, unknown>;\n /** Environment data to inject before instantiation. */\n environment?: Record<string, unknown>;\n}\n\n/**\n * A snapshot of rendered resources from a Composition, providing\n * assertion methods for unit testing.\n *\n * @example\n * ```ts\n * const template = Template.synthesize(MyComposition, {\n * xr: { spec: { region: 'us-east-1' } },\n * });\n * template.hasResourceSpec('ec2.aws.crossplane.io/v1beta1', 'VPC', {\n * forProvider: { region: 'us-east-1' },\n * });\n * ```\n */\nexport class Template {\n private readonly _resources: KubernetesResource[];\n\n private constructor(resources: KubernetesResource[]) {\n this._resources = resources;\n }\n\n /**\n * Ergonomic factory: injects XR/environment data and instantiates\n * the Composition class, then builds a Template from the rendered resources.\n *\n * Users never need to touch `Composition._pendingXR` directly.\n */\n static synthesize(Ctor: new () => Composition, options: SynthesizeOptions = {}): Template {\n // Walk up prototype chain to find the base Composition class with _pendingXR\n let base = Ctor as unknown as Record<string, unknown>;\n while (base && !Object.hasOwn(base, '_pendingXR')) {\n base = Object.getPrototypeOf(base) as Record<string, unknown>;\n }\n if (!base) {\n throw new Error('Could not find Composition base class with _pendingXR');\n }\n\n const BaseComposition = base as unknown as {\n _pendingXR: Record<string, unknown> | undefined;\n _pendingEnvironment: Record<string, unknown> | undefined;\n };\n\n BaseComposition._pendingXR = options.xr;\n BaseComposition._pendingEnvironment = options.environment;\n try {\n const instance = new Ctor();\n return Template.fromComposition(instance);\n } finally {\n BaseComposition._pendingXR = undefined;\n BaseComposition._pendingEnvironment = undefined;\n }\n }\n\n /**\n * Build a Template from an already-instantiated Composition.\n */\n static fromComposition(composition: Composition): Template {\n const resources = [\n ...(\n composition as unknown as {\n resources: ReadonlyMap<string, { toDesired(): KubernetesResource }>;\n }\n ).resources.values(),\n ].map((r) => r.toDesired());\n return new Template(resources);\n }\n\n /**\n * Build a Template from a pre-built array of KubernetesResource objects.\n * Used internally by Simulator.\n */\n static fromResources(resources: KubernetesResource[]): Template {\n return new Template(resources);\n }\n\n /** Get all resources matching apiVersion + kind. */\n private _filterByGVK(apiVersion: string, kind: string): KubernetesResource[] {\n return this._resources.filter((r) => r.apiVersion === apiVersion && r.kind === kind);\n }\n\n /**\n * Assert the number of resources with the given apiVersion and kind.\n */\n resourceCountIs(apiVersion: string, kind: string, count: number): void {\n const matched = this._filterByGVK(apiVersion, kind);\n if (matched.length !== count) {\n throw new Error(\n `Expected ${count} resource(s) of type ${apiVersion}/${kind}, found ${matched.length}`,\n );\n }\n }\n\n /**\n * Assert that at least one resource of the given type matches the expected properties.\n * Uses deep-partial matching by default (actual can be a superset of expected).\n */\n hasResource(apiVersion: string, kind: string, props?: object): void {\n const matched = this._filterByGVK(apiVersion, kind);\n if (matched.length === 0) {\n throw new Error(`No resources found with type ${apiVersion}/${kind}`);\n }\n\n if (!props) return; // Just checking existence\n\n const allFailures: string[] = [];\n for (const resource of matched) {\n const result = deepPartialMatch(resource, props);\n if (result.pass) return; // At least one matches\n allFailures.push(\n ` Resource: ${JSON.stringify(resource.metadata?.name ?? '(unnamed)')}\\n ${result.failures.join('\\n ')}`,\n );\n }\n throw new Error(\n `No resource of type ${apiVersion}/${kind} matches the expected properties:\\n${allFailures.join('\\n')}`,\n );\n }\n\n /**\n * Assert that at least one resource of the given type has a spec matching the expected properties.\n * Shorthand for matching against the `spec` field only.\n */\n hasResourceSpec(apiVersion: string, kind: string, specProps: object): void {\n const matched = this._filterByGVK(apiVersion, kind);\n if (matched.length === 0) {\n throw new Error(`No resources found with type ${apiVersion}/${kind}`);\n }\n\n const allFailures: string[] = [];\n for (const resource of matched) {\n const result = deepPartialMatch(resource.spec ?? {}, specProps);\n if (result.pass) return;\n allFailures.push(\n ` Resource: ${JSON.stringify(resource.metadata?.name ?? '(unnamed)')}\\n ${result.failures.join('\\n ')}`,\n );\n }\n throw new Error(\n `No resource of type ${apiVersion}/${kind} has spec matching the expected properties:\\n${allFailures.join('\\n')}`,\n );\n }\n\n /**\n * Assert that at least one resource of the given type has metadata matching the expected properties.\n */\n hasResourceMetadata(apiVersion: string, kind: string, metaProps: object): void {\n const matched = this._filterByGVK(apiVersion, kind);\n if (matched.length === 0) {\n throw new Error(`No resources found with type ${apiVersion}/${kind}`);\n }\n\n const allFailures: string[] = [];\n for (const resource of matched) {\n const result = deepPartialMatch(resource.metadata ?? {}, metaProps);\n if (result.pass) return;\n allFailures.push(\n ` Resource: ${JSON.stringify(resource.metadata?.name ?? '(unnamed)')}\\n ${result.failures.join('\\n ')}`,\n );\n }\n throw new Error(\n `No resource of type ${apiVersion}/${kind} has metadata matching the expected properties:\\n${allFailures.join('\\n')}`,\n );\n }\n\n /**\n * Assert that ALL resources of the given type match the expected properties.\n */\n allResources(apiVersion: string, kind: string, props: object): void {\n const matched = this._filterByGVK(apiVersion, kind);\n if (matched.length === 0) {\n throw new Error(`No resources found with type ${apiVersion}/${kind}`);\n }\n\n const failures: string[] = [];\n for (const resource of matched) {\n const result = deepPartialMatch(resource, props);\n if (!result.pass) {\n failures.push(\n ` Resource: ${JSON.stringify(resource.metadata?.name ?? '(unnamed)')}\\n ${result.failures.join('\\n ')}`,\n );\n }\n }\n if (failures.length > 0) {\n throw new Error(\n `Not all resources of type ${apiVersion}/${kind} match:\\n${failures.join('\\n')}`,\n );\n }\n }\n\n /**\n * Find all resources of the given type that match the expected properties.\n * Returns matches — never throws.\n */\n findResources(apiVersion: string, kind: string, props?: object): KubernetesResource[] {\n const matched = this._filterByGVK(apiVersion, kind);\n if (!props) return matched;\n\n return matched.filter((resource) => {\n const result = deepPartialMatch(resource, props);\n return result.pass;\n });\n }\n\n /**\n * Serialize all resources to a JSON-compatible array for snapshot testing.\n *\n * @example\n * ```ts\n * expect(template.toJSON()).toMatchSnapshot();\n * ```\n */\n toJSON(): KubernetesResource[] {\n return structuredClone(this._resources);\n }\n}\n","import {\n type Composition,\n type DependencyGraph,\n type KubernetesResource,\n type Resource,\n resolveSequencing,\n} from '@xplane/core';\nimport { type SynthesizeOptions, Template } from './template.js';\n\n/** Result of a simulation run. */\nexport interface SimulationResult {\n /** Resources that are ready to emit (all dependencies satisfied). */\n emitted: Template;\n /** Resources that are blocked on unresolved dependencies. */\n blocked: Template;\n /** Conditions that would be set on the XR status (e.g., missing existing resources). */\n conditions: Array<{ type: string; status: string; reason: string; message: string }>;\n}\n\n// ─── Path Utilities (same logic as @xplane/function handler) ─────────\n\nfunction getNestedValue(obj: unknown, path: string): unknown {\n let current: unknown = obj;\n for (const segment of path.split('.')) {\n if (current === null || current === undefined || typeof current !== 'object') {\n return undefined;\n }\n current = (current as Record<string, unknown>)[segment];\n }\n return current;\n}\n\nfunction setNestedValue(obj: Record<string, unknown>, path: string, value: unknown): void {\n const segments = path.split('.');\n let current: Record<string, unknown> = obj;\n for (let i = 0; i < segments.length - 1; i++) {\n const seg = segments[i]!;\n if (!(seg in current) || typeof current[seg] !== 'object' || current[seg] === null) {\n current[seg] = {};\n }\n current = current[seg] as Record<string, unknown>;\n }\n const lastSeg = segments[segments.length - 1];\n if (lastSeg !== undefined) {\n current[lastSeg] = value;\n }\n}\n\n/**\n * Simulates the full rendering pipeline including observed state injection,\n * edge resolution, and sequencing — mimicking what `@xplane/function` does at runtime.\n *\n * @example\n * ```ts\n * const result = Simulator.synthesize(MyComposition, { xr: { ... } })\n * .withObserved([{ apiVersion: '...', kind: 'VPC', metadata: { name: 'vpc-abc' }, status: { atProvider: { vpcId: 'vpc-123' } } }])\n * .run();\n *\n * result.emitted.hasResourceSpec('ec2.aws.crossplane.io/v1beta1', 'Subnet', {\n * forProvider: { vpcId: 'vpc-123' },\n * });\n * ```\n */\nexport class Simulator {\n private readonly _composition: Composition;\n private _observed: KubernetesResource[] = [];\n private _existing: Record<string, KubernetesResource> = {};\n\n private constructor(composition: Composition) {\n this._composition = composition;\n }\n\n /**\n * Ergonomic factory: injects XR/environment data, instantiates the\n * Composition class, and returns a Simulator ready for `.withObserved().run()`.\n */\n static synthesize(Ctor: new () => Composition, options: SynthesizeOptions = {}): Simulator {\n // Find base Composition class with _pendingXR\n let base = Ctor as unknown as Record<string, unknown>;\n while (base && !Object.hasOwn(base, '_pendingXR')) {\n base = Object.getPrototypeOf(base) as Record<string, unknown>;\n }\n if (!base) {\n throw new Error('Could not find Composition base class with _pendingXR');\n }\n\n const BaseComposition = base as unknown as {\n _pendingXR: Record<string, unknown> | undefined;\n _pendingEnvironment: Record<string, unknown> | undefined;\n };\n\n BaseComposition._pendingXR = options.xr;\n BaseComposition._pendingEnvironment = options.environment;\n try {\n const instance = new Ctor();\n return new Simulator(instance);\n } finally {\n BaseComposition._pendingXR = undefined;\n BaseComposition._pendingEnvironment = undefined;\n }\n }\n\n /**\n * Build a Simulator from an already-instantiated Composition.\n */\n static fromComposition(composition: Composition): Simulator {\n return new Simulator(composition);\n }\n\n /**\n * Provide observed (cluster) state for resources.\n * Each resource is matched to a declared resource by its construct path\n * (i.e., `metadata.name` in observed state maps to `resource.path` in the composition).\n */\n withObserved(resources: KubernetesResource[]): this {\n this._observed = resources;\n return this;\n }\n\n /**\n * Provide existing resource data keyed by refKey.\n * The refKey format is `apiVersion/kind/[namespace/]name`\n * (e.g., `\"example.io/v1/Project/my-project\"` or `\"v1/Secret/default/db-creds\"`).\n *\n * This simulates what Crossplane would return via the Required Resources mechanism.\n */\n withExisting(resources: Record<string, KubernetesResource>): this {\n this._existing = resources;\n return this;\n }\n\n /**\n * Run the simulation: inject observed state, resolve edges, determine sequencing.\n */\n run(): SimulationResult {\n const composition = this._composition;\n const resources = (composition as unknown as { resources: ReadonlyMap<string, ResourceLike> })\n .resources;\n const existingResources = (\n composition as unknown as { existingResources: ReadonlyMap<string, ExistingResourceLike> }\n ).existingResources;\n const collector = (\n composition as unknown as { collector: { edges: ReadonlyArray<DependencyEdgeLike> } }\n ).collector;\n const graph = (composition as unknown as { graph: DependencyGraph }).graph;\n\n // Build observed map keyed by resource path (same as handler)\n const observedMap = new Map<string, KubernetesResource>();\n for (const obs of this._observed) {\n const name = obs.metadata?.name;\n if (name) {\n observedMap.set(name, obs);\n }\n }\n\n // Add edges to graph\n graph.addEdges(collector.edges);\n\n // Feed observed state into resources\n for (const [path, resource] of resources) {\n const observed = observedMap.get(path);\n if (observed) {\n resource.setObserved(observed);\n }\n }\n\n // Resolve existing resources from the withExisting() map\n const conditions: SimulationResult['conditions'] = [];\n for (const [refKey, existingResource] of existingResources) {\n const data = this._existing[refKey];\n if (data) {\n existingResource.setObservedFull(data);\n observedMap.set(existingResource.path, data);\n } else {\n // Existing resource not provided — emit condition\n const ref = existingResource.existingRef;\n const name = typeof ref?.name === 'string' ? ref.name : '<unresolved>';\n const kind = ref?.kind ?? 'Unknown';\n conditions.push({\n type: 'Ready',\n status: 'False',\n reason: 'MissingRequiredResource',\n message: `Required existing resource ${kind}/${name} not found in cluster`,\n });\n }\n }\n\n // Resolve cross-resource edge values from observed state\n for (const edge of collector.edges) {\n const observed = observedMap.get(edge.from.id);\n if (!observed) continue;\n\n const value = getNestedValue(observed, edge.fromPath);\n if (value === undefined || value === null) continue;\n\n const targetResource = resources.get(edge.to.id);\n if (!targetResource) continue;\n\n const toPath = edge.toPath;\n if (toPath.startsWith('spec.')) {\n setNestedValue(\n targetResource.spec as Record<string, unknown>,\n toPath.slice('spec.'.length),\n value,\n );\n }\n }\n\n // Resolve sequencing\n const sequencing = resolveSequencing(\n resources as unknown as ReadonlyMap<string, Resource>,\n graph as DependencyGraph,\n observedMap,\n );\n\n return {\n emitted: Template.fromResources(sequencing.emit.map((r) => r.toDesired())),\n blocked: Template.fromResources(sequencing.blocked.map((r) => r.toDesired())),\n conditions,\n };\n }\n}\n\n// ─── Internal type helpers (avoid importing private types) ───────────\n\ninterface ResourceLike {\n path: string;\n spec: Record<string, unknown>;\n setObserved(observed: KubernetesResource): void;\n toDesired(): KubernetesResource;\n}\n\ninterface ExistingResourceLike {\n path: string;\n existingRef:\n | { apiVersion: string; kind: string; name: unknown; namespace?: string; refKey: string }\n | undefined;\n setObservedFull(resource: KubernetesResource): void;\n}\n\ninterface DependencyEdgeLike {\n from: { id: string };\n fromPath: string;\n to: { id: string };\n toPath: string;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["PENDING_VALUE"],"sources":["../../src/assertions/match.ts","../../src/assertions/template.ts","../../src/assertions/simulator.ts"],"sourcesContent":["/** Symbol used to identify Matcher instances. */\nexport const MATCHER = Symbol.for('xplane.devtools.matcher');\n\n/** Result of a match operation. */\nexport interface MatchResult {\n /** Whether the match succeeded. */\n pass: boolean;\n /** Human-readable failure messages (empty if pass is true). */\n failures: string[];\n}\n\n/** Interface for custom matchers. */\nexport interface Matcher {\n readonly [MATCHER]: true;\n test(actual: unknown): MatchResult;\n}\n\n/** Check whether a value is a Matcher instance. */\nexport function isMatcher(value: unknown): value is Matcher {\n return (\n typeof value === 'object' &&\n value !== null &&\n MATCHER in value &&\n (value as Matcher)[MATCHER] === true\n );\n}\n\nfunction pass(): MatchResult {\n return { pass: true, failures: [] };\n}\n\nfunction fail(message: string): MatchResult {\n return { pass: false, failures: [message] };\n}\n\nfunction merge(results: MatchResult[]): MatchResult {\n const failures = results.flatMap((r) => r.failures);\n return { pass: failures.length === 0, failures };\n}\n\n/**\n * Deep-match `actual` against `expected`.\n * - If `expected` is a Matcher, delegates to its `.test()`.\n * - Literal objects are matched recursively (deep-partial by default via objectLike semantics at the top level is handled by the caller).\n * - This function performs EXACT matching — partial matching is handled by ObjectLikeMatcher.\n */\nexport function deepMatch(actual: unknown, expected: unknown, path = ''): MatchResult {\n if (isMatcher(expected)) {\n const result = expected.test(actual);\n if (!result.pass) {\n return { pass: false, failures: result.failures.map((f) => (path ? `${path}: ${f}` : f)) };\n }\n return pass();\n }\n\n // Null / undefined / primitives\n if (expected === null || expected === undefined || typeof expected !== 'object') {\n if (actual === expected) return pass();\n return fail(\n path\n ? `${path}: expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`\n : `expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`,\n );\n }\n\n // Arrays\n if (Array.isArray(expected)) {\n if (!Array.isArray(actual)) {\n return fail(`${path || 'value'}: expected an array, got ${typeof actual}`);\n }\n if (actual.length !== expected.length) {\n return fail(\n `${path || 'value'}: expected array of length ${expected.length}, got length ${actual.length}`,\n );\n }\n const results: MatchResult[] = [];\n for (let i = 0; i < expected.length; i++) {\n results.push(deepMatch(actual[i], expected[i], `${path}[${i}]`));\n }\n return merge(results);\n }\n\n // Objects (exact match — all keys in expected must match, no extra keys allowed)\n if (typeof actual !== 'object' || actual === null || Array.isArray(actual)) {\n return fail(`${path || 'value'}: expected an object, got ${JSON.stringify(actual)}`);\n }\n const expectedObj = expected as Record<string, unknown>;\n const actualObj = actual as Record<string, unknown>;\n const results: MatchResult[] = [];\n\n // Check expected keys exist and match\n for (const key of Object.keys(expectedObj)) {\n results.push(deepMatch(actualObj[key], expectedObj[key], path ? `${path}.${key}` : key));\n }\n // Check no extra keys in actual\n for (const key of Object.keys(actualObj)) {\n if (!(key in expectedObj)) {\n results.push(fail(`${path ? `${path}.${key}` : key}: unexpected key`));\n }\n }\n return merge(results);\n}\n\n/**\n * Deep-partial match: all keys in `expected` must match in `actual`, but\n * `actual` may have additional keys at any level.\n */\nexport function deepPartialMatch(actual: unknown, expected: unknown, path = ''): MatchResult {\n if (isMatcher(expected)) {\n const result = expected.test(actual);\n if (!result.pass) {\n return { pass: false, failures: result.failures.map((f) => (path ? `${path}: ${f}` : f)) };\n }\n return pass();\n }\n\n if (expected === null || expected === undefined || typeof expected !== 'object') {\n if (actual === expected) return pass();\n return fail(\n path\n ? `${path}: expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`\n : `expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`,\n );\n }\n\n if (Array.isArray(expected)) {\n if (!Array.isArray(actual)) {\n return fail(`${path || 'value'}: expected an array, got ${typeof actual}`);\n }\n if (actual.length !== expected.length) {\n return fail(\n `${path || 'value'}: expected array of length ${expected.length}, got length ${actual.length}`,\n );\n }\n const results: MatchResult[] = [];\n for (let i = 0; i < expected.length; i++) {\n results.push(deepPartialMatch(actual[i], expected[i], `${path}[${i}]`));\n }\n return merge(results);\n }\n\n if (typeof actual !== 'object' || actual === null || Array.isArray(actual)) {\n return fail(`${path || 'value'}: expected an object, got ${JSON.stringify(actual)}`);\n }\n\n const expectedObj = expected as Record<string, unknown>;\n const actualObj = actual as Record<string, unknown>;\n const results: MatchResult[] = [];\n\n // Only check keys present in expected — actual may have extras\n for (const key of Object.keys(expectedObj)) {\n if (!(key in actualObj)) {\n results.push(fail(`${path ? `${path}.${key}` : key}: key not found in actual`));\n } else {\n results.push(\n deepPartialMatch(actualObj[key], expectedObj[key], path ? `${path}.${key}` : key),\n );\n }\n }\n return merge(results);\n}\n\n// ─── Matcher Implementations ────────────────────────────────────────────\n\nclass ObjectLikeMatcher implements Matcher {\n readonly [MATCHER] = true as const;\n constructor(private readonly pattern: object) {}\n test(actual: unknown): MatchResult {\n return deepPartialMatch(actual, this.pattern);\n }\n}\n\nclass ObjectEqualsMatcher implements Matcher {\n readonly [MATCHER] = true as const;\n constructor(private readonly pattern: object) {}\n test(actual: unknown): MatchResult {\n return deepMatch(actual, this.pattern);\n }\n}\n\nclass ArrayWithMatcher implements Matcher {\n readonly [MATCHER] = true as const;\n constructor(private readonly items: unknown[]) {}\n test(actual: unknown): MatchResult {\n if (!Array.isArray(actual)) {\n return fail(`expected an array, got ${typeof actual}`);\n }\n // Each item in `this.items` must appear in `actual` in order (not necessarily contiguous)\n let searchStart = 0;\n for (let i = 0; i < this.items.length; i++) {\n let found = false;\n for (let j = searchStart; j < actual.length; j++) {\n const r = deepPartialMatch(actual[j], this.items[i]);\n if (r.pass) {\n searchStart = j + 1;\n found = true;\n break;\n }\n }\n if (!found) {\n return fail(\n `arrayWith: could not find match for item at index ${i}: ${JSON.stringify(this.items[i])}`,\n );\n }\n }\n return pass();\n }\n}\n\nclass ArrayEqualsMatcher implements Matcher {\n readonly [MATCHER] = true as const;\n constructor(private readonly items: unknown[]) {}\n test(actual: unknown): MatchResult {\n return deepMatch(actual, this.items);\n }\n}\n\nclass StringLikeRegexpMatcher implements Matcher {\n readonly [MATCHER] = true as const;\n private readonly regex: RegExp;\n constructor(pattern: string | RegExp) {\n this.regex = typeof pattern === 'string' ? new RegExp(pattern) : pattern;\n }\n test(actual: unknown): MatchResult {\n if (typeof actual !== 'string') {\n return fail(`expected a string, got ${typeof actual}`);\n }\n if (!this.regex.test(actual)) {\n return fail(`expected string matching ${this.regex}, got \"${actual}\"`);\n }\n return pass();\n }\n}\n\nclass AbsentMatcher implements Matcher {\n readonly [MATCHER] = true as const;\n test(actual: unknown): MatchResult {\n if (actual !== undefined) {\n return fail(`expected absent (undefined), got ${JSON.stringify(actual)}`);\n }\n return pass();\n }\n}\n\nclass AnyValueMatcher implements Matcher {\n readonly [MATCHER] = true as const;\n test(actual: unknown): MatchResult {\n if (actual === null || actual === undefined) {\n return fail(`expected any value, got ${actual}`);\n }\n return pass();\n }\n}\n\nclass NotMatcher implements Matcher {\n readonly [MATCHER] = true as const;\n constructor(private readonly pattern: unknown) {}\n test(actual: unknown): MatchResult {\n const inner = isMatcher(this.pattern)\n ? this.pattern.test(actual)\n : deepPartialMatch(actual, this.pattern);\n if (inner.pass) {\n return fail(`expected NOT to match, but matched: ${JSON.stringify(actual)}`);\n }\n return pass();\n }\n}\n\nconst PENDING_VALUE = Symbol.for('xplane.devtools.pending');\n\nclass PendingMatcher implements Matcher {\n readonly [MATCHER] = true as const;\n constructor(private readonly expected?: { source?: string; path?: string }) {}\n test(actual: unknown): MatchResult {\n if (\n typeof actual !== 'object' ||\n actual === null ||\n (actual as Record<symbol, unknown>)[PENDING_VALUE] !== true\n ) {\n return fail(`expected a pending value, got ${JSON.stringify(actual)}`);\n }\n const pending = actual as { source: string; path: string };\n if (this.expected?.source && pending.source !== this.expected.source) {\n return fail(\n `expected pending from source \"${this.expected.source}\", got \"${pending.source}\"`,\n );\n }\n if (this.expected?.path && pending.path !== this.expected.path) {\n return fail(`expected pending path \"${this.expected.path}\", got \"${pending.path}\"`);\n }\n return pass();\n }\n}\n\n/**\n * Factory for composable matchers used in assertions.\n *\n * @example\n * ```ts\n * template.hasResourceSpec('v1', 'ConfigMap', {\n * data: Match.objectLike({ key: 'value' }),\n * });\n * ```\n */\nexport class Match {\n private constructor() {}\n\n /** Deep-partial object match — actual may be a superset of pattern. */\n static objectLike(pattern: object): Matcher {\n return new ObjectLikeMatcher(pattern);\n }\n\n /** Exact object match — actual must equal pattern exactly (same keys, same values). */\n static objectEquals(pattern: object): Matcher {\n return new ObjectEqualsMatcher(pattern);\n }\n\n /** Array subset match — items must appear in actual in order. */\n static arrayWith(items: unknown[]): Matcher {\n return new ArrayWithMatcher(items);\n }\n\n /** Exact array match — actual must equal items exactly. */\n static arrayEquals(items: unknown[]): Matcher {\n return new ArrayEqualsMatcher(items);\n }\n\n /** String regex match. */\n static stringLikeRegexp(pattern: string | RegExp): Matcher {\n return new StringLikeRegexpMatcher(pattern);\n }\n\n /** Asserts the value is absent (undefined). */\n static absent(): Matcher {\n return new AbsentMatcher();\n }\n\n /** Asserts any non-null/non-undefined value is present. */\n static anyValue(): Matcher {\n return new AnyValueMatcher();\n }\n\n /** Inverts a match — asserts the value does NOT match the given pattern. */\n static not(pattern: unknown): Matcher {\n return new NotMatcher(pattern);\n }\n\n /**\n * Asserts the value is a pending dependency (unresolved cross-resource reference).\n * Optionally match the source resource and/or path.\n */\n static pending(expected?: { source?: string; path?: string }): Matcher {\n return new PendingMatcher(expected);\n }\n}\n","import {\n type Composition,\n type CompositionContext,\n compositionStorage,\n DependencyGraph,\n EdgeCollector,\n getDesiredDocument,\n isExternal,\n type KubernetesResource,\n Pending,\n Resource,\n} from '@xplane/core';\nimport { deepPartialMatch } from './match.js';\n\n/** Options for `Template.synthesize()`. */\nexport interface SynthesizeOptions {\n /** XR (composite resource) data to inject before instantiation. */\n xr?: Record<string, unknown>;\n /** Environment data to inject before instantiation. */\n environment?: Record<string, unknown>;\n}\n\n/**\n * A snapshot of rendered resources from a Composition, providing\n * assertion methods for unit testing.\n */\nexport class Template {\n private readonly _resources: KubernetesResource[];\n\n private constructor(resources: KubernetesResource[]) {\n this._resources = resources;\n }\n\n /**\n * Instantiate a Composition and run the full pipeline to produce a Template.\n *\n * Includes ALL declared resources (both emitted and blocked) — Pending\n * dependency values are stripped to `undefined`. Use `Simulator` if you\n * need to distinguish emitted vs blocked.\n */\n static synthesize<TSpec, TStatus, TContext extends object>(\n Ctor: new () => Composition<TSpec, TStatus, TContext>,\n options: SynthesizeOptions = {},\n ): Template {\n const xr: Record<string, unknown> = options.xr ?? { spec: {}, status: {} };\n const pipelineContext = new Map<string, unknown>();\n if (options.environment) {\n pipelineContext.set('apiextensions.crossplane.io/environment', options.environment);\n }\n\n const graph = new DependencyGraph();\n const collector = new EdgeCollector();\n const ctx: CompositionContext = {\n xr,\n pipelineContext,\n requiredResources: new Map(),\n graph,\n collector,\n };\n\n const composition = compositionStorage.run(ctx, () => new Ctor()) as Composition;\n return Template.fromComposition(composition);\n }\n\n /**\n * Build a Template from an already-instantiated Composition.\n *\n * Extracts desired documents from ALL non-external resources. Unresolved\n * Pending markers are serialized as `PendingValue` objects — use\n * `Match.pending()` to assert them in tests.\n */\n static fromComposition(composition: Composition): Template {\n const resources = composition.node\n .findAll()\n .filter((c): c is Resource => c instanceof Resource && !isExternal(c as Resource));\n\n const docs = resources.map(\n (r) => serializePending(getDesiredDocument(r)) as KubernetesResource,\n );\n return new Template(docs);\n }\n\n /**\n * Build a Template from a pre-built array of resource documents.\n */\n static fromResources(resources: KubernetesResource[]): Template {\n return new Template(resources);\n }\n\n /** Get all resources matching apiVersion + kind. */\n private _filterByGVK(apiVersion: string, kind: string): KubernetesResource[] {\n return this._resources.filter((r) => r.apiVersion === apiVersion && r.kind === kind);\n }\n\n /**\n * Assert the number of resources with the given apiVersion and kind.\n */\n resourceCountIs(apiVersion: string, kind: string, count: number): void {\n const matched = this._filterByGVK(apiVersion, kind);\n if (matched.length !== count) {\n throw new Error(\n `Expected ${count} resource(s) of type ${apiVersion}/${kind}, found ${matched.length}`,\n );\n }\n }\n\n /**\n * Assert that at least one resource of the given type matches the expected properties.\n * Uses deep-partial matching by default (actual can be a superset of expected).\n */\n hasResource(apiVersion: string, kind: string, props?: object): void {\n const matched = this._filterByGVK(apiVersion, kind);\n if (matched.length === 0) {\n throw new Error(`No resources found with type ${apiVersion}/${kind}`);\n }\n\n if (!props) return; // Just checking existence\n\n const allFailures: string[] = [];\n for (const resource of matched) {\n const result = deepPartialMatch(resource, props);\n if (result.pass) return; // At least one matches\n allFailures.push(\n ` Resource: ${JSON.stringify(resource.metadata?.name ?? '(unnamed)')}\\n ${result.failures.join('\\n ')}`,\n );\n }\n throw new Error(\n `No resource of type ${apiVersion}/${kind} matches the expected properties:\\n${allFailures.join('\\n')}`,\n );\n }\n\n /**\n * Assert that at least one resource of the given type has a spec matching the expected properties.\n * Shorthand for matching against the `spec` field only.\n */\n hasResourceSpec(apiVersion: string, kind: string, specProps: object): void {\n const matched = this._filterByGVK(apiVersion, kind);\n if (matched.length === 0) {\n throw new Error(`No resources found with type ${apiVersion}/${kind}`);\n }\n\n const allFailures: string[] = [];\n for (const resource of matched) {\n const result = deepPartialMatch(resource.spec ?? {}, specProps);\n if (result.pass) return;\n allFailures.push(\n ` Resource: ${JSON.stringify(resource.metadata?.name ?? '(unnamed)')}\\n ${result.failures.join('\\n ')}`,\n );\n }\n throw new Error(\n `No resource of type ${apiVersion}/${kind} has spec matching the expected properties:\\n${allFailures.join('\\n')}`,\n );\n }\n\n /**\n * Assert that at least one resource of the given type has metadata matching the expected properties.\n */\n hasResourceMetadata(apiVersion: string, kind: string, metaProps: object): void {\n const matched = this._filterByGVK(apiVersion, kind);\n if (matched.length === 0) {\n throw new Error(`No resources found with type ${apiVersion}/${kind}`);\n }\n\n const allFailures: string[] = [];\n for (const resource of matched) {\n const result = deepPartialMatch(resource.metadata ?? {}, metaProps);\n if (result.pass) return;\n allFailures.push(\n ` Resource: ${JSON.stringify(resource.metadata?.name ?? '(unnamed)')}\\n ${result.failures.join('\\n ')}`,\n );\n }\n throw new Error(\n `No resource of type ${apiVersion}/${kind} has metadata matching the expected properties:\\n${allFailures.join('\\n')}`,\n );\n }\n\n /**\n * Assert that ALL resources of the given type match the expected properties.\n */\n allResources(apiVersion: string, kind: string, props: object): void {\n const matched = this._filterByGVK(apiVersion, kind);\n if (matched.length === 0) {\n throw new Error(`No resources found with type ${apiVersion}/${kind}`);\n }\n\n const failures: string[] = [];\n for (const resource of matched) {\n const result = deepPartialMatch(resource, props);\n if (!result.pass) {\n failures.push(\n ` Resource: ${JSON.stringify(resource.metadata?.name ?? '(unnamed)')}\\n ${result.failures.join('\\n ')}`,\n );\n }\n }\n if (failures.length > 0) {\n throw new Error(\n `Not all resources of type ${apiVersion}/${kind} match:\\n${failures.join('\\n')}`,\n );\n }\n }\n\n /**\n * Find all resources of the given type that match the expected properties.\n * Returns matches — never throws.\n */\n findResources(apiVersion: string, kind: string, props?: object): KubernetesResource[] {\n const matched = this._filterByGVK(apiVersion, kind);\n if (!props) return matched;\n\n return matched.filter((resource) => {\n const result = deepPartialMatch(resource, props);\n return result.pass;\n });\n }\n\n /**\n * Serialize all resources to a JSON-compatible array for snapshot testing.\n *\n * @example\n * ```ts\n * expect(template.toJSON()).toMatchSnapshot();\n * ```\n */\n toJSON(): KubernetesResource[] {\n return structuredClone(this._resources);\n }\n}\n\n// ─── Pending Serialization ────────────────────────────────────────────────────\n\n/** Symbol tag used to identify serialized PendingValue objects. */\nexport const PENDING_VALUE = Symbol.for('xplane.devtools.pending');\n\n/** Serialized form of a Pending marker in Template resource documents. */\nexport interface PendingValue {\n readonly [PENDING_VALUE]: true;\n /** The resource this value is waiting on. */\n readonly source: string;\n /** The path within that resource's observed data. */\n readonly path: string;\n}\n\n/** Type guard for PendingValue. */\nexport function isPendingValue(value: unknown): value is PendingValue {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as Record<symbol, unknown>)[PENDING_VALUE] === true\n );\n}\n\n/**\n * Deep-clone a desired document, converting Pending instances into\n * serialized PendingValue objects that matchers can assert against.\n */\nfunction serializePending(obj: Record<string, unknown>): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n result[key] = serializeValue(value);\n }\n return result;\n}\n\nfunction serializeValue(value: unknown): unknown {\n if (value === null || value === undefined) return value;\n\n if (Pending.is(value)) {\n return {\n [PENDING_VALUE]: true,\n source: value.source.id,\n path: value.path,\n } satisfies PendingValue;\n }\n\n if (typeof value !== 'object') return value;\n\n if (Array.isArray(value)) {\n return value.map(serializeValue);\n }\n\n const result: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(value as Record<string, unknown>)) {\n result[key] = serializeValue(val);\n }\n return result;\n}\n","import {\n type Composition,\n type CompositionContext,\n compositionStorage,\n DEFAULT_CHECKS,\n DependencyGraph,\n EdgeCollector,\n type EmittedResource,\n evaluateReadiness,\n getDesiredDocument,\n getExternalRef,\n isExternal,\n type KubernetesResource,\n runPipeline,\n} from '@xplane/core';\nimport { type SynthesizeOptions, Template } from './template.js';\n\n/** Result of a simulation run. */\nexport interface SimulationResult {\n /** Resources that are ready to emit (all dependencies satisfied). */\n emitted: Template;\n /** Resources that are blocked on unresolved dependencies. */\n blocked: Template;\n /** Conditions that would be set on the XR status (e.g., missing existing resources). */\n conditions: Array<{ type: string; status: string; reason: string; message: string }>;\n /**\n * Evaluate readiness of a specific emitted resource using its registered\n * readyChecks + built-in defaults against the observed state.\n *\n * @param resourceName The construct path of the resource (e.g., 'Cluster Provider Config')\n * @returns `true` if ready, `false` if not ready or resource not found\n */\n isReady(resourceName: string): boolean;\n}\n\n/**\n * Simulates the full rendering pipeline including observed state injection,\n * edge resolution, and sequencing — mimicking what `@xplane/function` does at runtime.\n *\n * @example\n * ```ts\n * const result = Simulator.synthesize(MyComposition, { xr: { ... } })\n * .withObserved([{ apiVersion: '...', kind: 'VPC', metadata: { name: 'vpc-abc' }, status: { atProvider: { vpcId: 'vpc-123' } } }])\n * .run();\n *\n * result.emitted.hasResourceSpec('ec2.aws.crossplane.io/v1beta1', 'Subnet', {\n * forProvider: { vpcId: 'vpc-123' },\n * });\n * ```\n */\nexport class Simulator {\n private readonly _composition: Composition;\n private _observed: Record<string, unknown>[] = [];\n private _existing: Record<string, Record<string, unknown>> = {};\n\n private constructor(composition: Composition) {\n this._composition = composition;\n }\n\n /**\n * Ergonomic factory: injects XR/environment data, instantiates the\n * Composition class, and returns a Simulator ready for `.withObserved().run()`.\n */\n static synthesize<TSpec, TStatus, TContext extends object>(\n Ctor: new () => Composition<TSpec, TStatus, TContext>,\n options: SynthesizeOptions = {},\n ): Simulator {\n const xr: Record<string, unknown> = options.xr ?? { spec: {}, status: {} };\n const pipelineContext = new Map<string, unknown>();\n if (options.environment) {\n pipelineContext.set('apiextensions.crossplane.io/environment', options.environment);\n }\n\n const graph = new DependencyGraph();\n const collector = new EdgeCollector();\n const ctx: CompositionContext = {\n xr,\n pipelineContext,\n requiredResources: new Map(),\n graph,\n collector,\n };\n\n const instance = compositionStorage.run(ctx, () => new Ctor()) as Composition;\n return new Simulator(instance);\n }\n\n /**\n * Build a Simulator from an already-instantiated Composition.\n */\n static fromComposition(composition: Composition): Simulator {\n return new Simulator(composition);\n }\n\n /**\n * Provide observed (cluster) state for resources.\n * Each resource is matched to a declared resource by its construct path\n * (i.e., `metadata.name` in observed state maps to `resource.path` in the composition).\n */\n withObserved(resources: Record<string, unknown>[]): this {\n this._observed = resources;\n return this;\n }\n\n /**\n * Provide existing resource data keyed by refKey.\n * The refKey format is `apiVersion/kind/[namespace/]name`\n * (e.g., `\"example.io/v1/Project/my-project\"` or `\"v1/Secret/default/db-creds\"`).\n *\n * This simulates what Crossplane would return via the Required Resources mechanism.\n */\n withExisting(resources: Record<string, Record<string, unknown>>): this {\n this._existing = resources;\n return this;\n }\n\n /**\n * Run the simulation: inject observed state, resolve edges, determine sequencing.\n */\n run(): SimulationResult {\n const composition = this._composition;\n\n // Build observed map keyed by construct path (Composition/<name>)\n const observedComposed = new Map<string, Record<string, unknown>>();\n for (const obs of this._observed) {\n const meta = obs.metadata as Record<string, unknown> | undefined;\n const name = meta?.name as string | undefined;\n if (name) {\n observedComposed.set(`Composition/${name}`, obs);\n }\n }\n\n // Build observedRequired from withExisting\n const observedRequired = new Map<string, Record<string, unknown>>(\n Object.entries(this._existing),\n );\n\n // Run the pipeline\n const result = runPipeline({\n composition,\n observedComposed,\n observedRequired,\n });\n\n // Check for missing existing resources\n const conditions: SimulationResult['conditions'] = [];\n for (const resource of result.resources) {\n if (!isExternal(resource)) continue;\n const ref = getExternalRef(resource);\n if (!ref || typeof ref.name !== 'string') continue;\n if (!observedRequired.has(ref.refKey)) {\n conditions.push({\n type: 'Ready',\n status: 'False',\n reason: 'MissingRequiredResource',\n message: `Required existing resource ${ref.kind}/${ref.name} not found in cluster`,\n });\n }\n }\n\n // Build templates from pipeline results\n const blockedResources = result.resources\n .filter((r) => result.classification.get(r.node.path) === 'blocked')\n .map((r) => getDesiredDocument(r) as KubernetesResource);\n\n // Build a lookup for readiness evaluation\n const emittedByName = new Map<string, EmittedResource>();\n for (const e of result.emitted) {\n emittedByName.set(e.name, e);\n }\n\n return {\n emitted: Template.fromResources(result.emitted.map((e) => e.document as KubernetesResource)),\n blocked: Template.fromResources(blockedResources),\n conditions,\n isReady(resourceName: string): boolean {\n const emitted = emittedByName.get(resourceName);\n if (!emitted) return false;\n if (!emitted.autoReady) return false;\n const allChecks = [...emitted.readyChecks, ...DEFAULT_CHECKS];\n const observed = observedComposed.get(`Composition/${resourceName}`);\n return evaluateReadiness(allChecks, observed);\n },\n };\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xplane/devtools",
3
- "version": "0.16.0",
3
+ "version": "1.0.0",
4
4
  "type": "module",
5
5
  "description": "Developer tools and testing utilities for @xplane/core compositions",
6
6
  "license": "Apache-2.0",
@@ -30,11 +30,11 @@
30
30
  "tsdown": "^0.22.0",
31
31
  "typescript": "6.0.3",
32
32
  "vitest": "4.1.6",
33
- "@xplane/core": "0.16.0"
33
+ "@xplane/core": "1.0.0"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "constructs": "^10.0.0",
37
- "@xplane/core": "0.16.0"
37
+ "@xplane/core": "1.0.0"
38
38
  },
39
39
  "scripts": {
40
40
  "build": "tsdown",