@ripplo/testing 0.4.7 → 0.5.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.
package/dist/index.js CHANGED
@@ -14,13 +14,15 @@ import {
14
14
  } from "./chunk-YQAEOH5W.js";
15
15
  import {
16
16
  compile
17
- } from "./chunk-DL3HLCD7.js";
17
+ } from "./chunk-YFOTJIVF.js";
18
18
  import "./chunk-MGATMMCZ.js";
19
19
  import {
20
- buildSetCookieHeader,
20
+ readAdapterWebhookSecret,
21
21
  serializeCookie,
22
+ toBatchRunResults,
23
+ toTeardownResults,
22
24
  verifyWebhookSignature
23
- } from "./chunk-V6LMXKGL.js";
25
+ } from "./chunk-XO36IU66.js";
24
26
  import "./chunk-4MGIQFAJ.js";
25
27
 
26
28
  // src/chainable.ts
@@ -149,7 +151,7 @@ function stubPreconditionDef(p) {
149
151
  name,
150
152
  returns: [],
151
153
  teardown: void 0,
152
- setup: () => Promise.resolve({})
154
+ setup: () => Promise.resolve([])
153
155
  };
154
156
  }
155
157
  function stubObserverDef(o) {
@@ -780,6 +782,30 @@ function observerParamsReferenceVariables(nodes, test2, report) {
780
782
  });
781
783
  });
782
784
  }
785
+ function uploadFixtureName(nodes, _test, report) {
786
+ nodes.forEach((node) => {
787
+ if (node.type !== "upload") {
788
+ return;
789
+ }
790
+ node.files.forEach((name) => {
791
+ if (name.length === 0) {
792
+ report({
793
+ message: `upload "${node.label ?? node.id}" references an empty fixture name`,
794
+ rule: "upload-fixture-name",
795
+ step: node.label ?? node.id
796
+ });
797
+ return;
798
+ }
799
+ if (name.includes("..") || name.startsWith("/")) {
800
+ report({
801
+ message: `upload "${node.label ?? node.id}" references "${name}" \u2014 fixture names must be relative paths under .ripplo/fixtures/, no ".." or absolute paths`,
802
+ rule: "upload-fixture-name",
803
+ step: node.label ?? node.id
804
+ });
805
+ }
806
+ });
807
+ });
808
+ }
783
809
  function noLiteralTemplateStrings(nodes, test2, report) {
784
810
  const declaredKeys = new Set(Object.keys(test2.spec.variables ?? {}));
785
811
  const pattern = /\{\{([^{}]+?)\}\}/g;
@@ -818,7 +844,8 @@ var RULES = [
818
844
  tautologicalPostClickAssert,
819
845
  expectedOutcomeKeywordCoverage,
820
846
  mutationWithoutObserverCoverage,
821
- observerParamsReferenceVariables
847
+ observerParamsReferenceVariables,
848
+ uploadFixtureName
822
849
  ];
823
850
 
824
851
  // src/engine.ts
@@ -835,7 +862,7 @@ function createEngine(ripplo, impls) {
835
862
  const observersByName = new Map(observerDefs.map((d) => [d.name, d]));
836
863
  return {
837
864
  executeObserver: (name, params) => executeObserver(observersByName, name, params),
838
- executePreconditions: (names, options) => executePreconditions({ defsByName: preconditionsByName, names, options }),
865
+ executePreconditions: (items, options) => executePreconditions({ defsByName: preconditionsByName, items, options }),
839
866
  getObservers: () => observerDefs,
840
867
  getPreconditions: () => preconditionDefs,
841
868
  getUnimplemented: () => ({
@@ -843,7 +870,7 @@ function createEngine(ripplo, impls) {
843
870
  preconditions: preconditionDefs.filter((p) => !p.implemented).map((p) => p.name),
844
871
  tests: ripplo.tests.filter((t) => !t.implemented).map((t) => t.id)
845
872
  }),
846
- teardown: (names, data) => teardown(preconditionsByName, names, data)
873
+ teardown: (items) => teardown({ defsByName: preconditionsByName, items })
847
874
  };
848
875
  }
849
876
  function wirePreconditions(registry, impls) {
@@ -888,17 +915,34 @@ function makeWiredPreconditionDef(handle, impl) {
888
915
  implemented: true,
889
916
  name,
890
917
  returns: [],
891
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- typedImpl.teardown has narrower TData, wider context at engine boundary
892
- teardown: typedImpl.teardown,
893
- setup: async (ctx, allDeps) => {
894
- const resolved = {};
895
- mapping.forEach(([key, depName]) => {
896
- const data = allDeps[depName];
897
- if (data != null) {
898
- resolved[key] = data;
899
- }
918
+ setup: async (items) => {
919
+ const resolvedItems = items.map((item) => {
920
+ const resolved = {};
921
+ mapping.forEach(([key, depName]) => {
922
+ const data = item.deps[depName];
923
+ if (data != null) {
924
+ resolved[key] = data;
925
+ }
926
+ });
927
+ return { ctx: item.ctx, deps: resolved };
928
+ });
929
+ const results = await typedImpl.setup(resolvedItems);
930
+ return results.map((r) => {
931
+ const out = {};
932
+ Object.entries(r).forEach(([key, value]) => {
933
+ if (!isPrimitive(value)) {
934
+ throw new TypeError(
935
+ `Precondition "${name}" returned non-primitive value at key "${key}". Setup return values must be string, number, or boolean \u2014 produced via ctx.fixed/uniqueEmail/uniqueId.`
936
+ );
937
+ }
938
+ out[key] = value;
939
+ });
940
+ return out;
900
941
  });
901
- return typedImpl.setup(ctx, resolved);
942
+ },
943
+ teardown: async (items) => {
944
+ const typed = items;
945
+ await typedImpl.teardown(typed);
902
946
  }
903
947
  };
904
948
  }
@@ -939,88 +983,170 @@ function readBudgetOf(handle) {
939
983
  }
940
984
  async function executePreconditions({
941
985
  defsByName,
942
- names,
986
+ items,
943
987
  options
944
988
  }) {
945
- const runId = crypto.randomUUID().slice(0, 12);
946
- const cookies = [];
947
989
  const defaultDomain = deriveDefaultDomain(options?.appUrl);
948
- const state = {
949
- cookies,
950
- ctx: createSetupContext({ cookies, defaultDomain, runId }),
951
- data: {},
952
- defsByName,
953
- executed: [],
954
- runId
955
- };
956
- return runBatchSequence(state, names);
990
+ const initial = items.map((item) => {
991
+ const cookiesRef = [];
992
+ return {
993
+ cookies: cookiesRef,
994
+ cookiesRef,
995
+ ctx: createSetupContext({ cookies: cookiesRef, defaultDomain, runId: item.runId }),
996
+ data: {},
997
+ executed: [],
998
+ failure: void 0,
999
+ names: item.names,
1000
+ runId: item.runId
1001
+ };
1002
+ });
1003
+ const order = topoOrder(defsByName, items);
1004
+ const orderError = order.error;
1005
+ if (orderError != null) {
1006
+ return initial.map((s) => failResult(s, orderError));
1007
+ }
1008
+ const sequence = order.names ?? [];
1009
+ let states = initial;
1010
+ let i = 0;
1011
+ while (i < sequence.length) {
1012
+ const name = sequence[i];
1013
+ if (name != null) {
1014
+ states = await executeStep({ defsByName, name, states });
1015
+ }
1016
+ i += 1;
1017
+ }
1018
+ return states.map(
1019
+ (s) => s.failure == null ? {
1020
+ cookies: s.cookiesRef,
1021
+ data: s.data,
1022
+ error: void 0,
1023
+ executed: s.executed,
1024
+ runId: s.runId,
1025
+ success: true
1026
+ } : failResult(s, s.failure)
1027
+ );
957
1028
  }
958
- async function runBatchSequence(state, names) {
959
- let index = 0;
960
- while (index < names.length) {
961
- const name = names[index];
962
- if (name == null) {
963
- break;
1029
+ function topoOrder(defsByName, items) {
1030
+ const seen = /* @__PURE__ */ new Set();
1031
+ const order = [];
1032
+ const visiting = /* @__PURE__ */ new Set();
1033
+ const visitDeps = (deps) => {
1034
+ const found = deps.find((d) => visit(d) != null);
1035
+ return found == null ? void 0 : visit(found);
1036
+ };
1037
+ const visit = (name) => {
1038
+ if (seen.has(name)) {
1039
+ return void 0;
964
1040
  }
965
- const error = validatePrecondition(state.defsByName, name);
966
- if (error != null) {
967
- return fail(state, error);
1041
+ if (visiting.has(name)) {
1042
+ return `Cycle detected at precondition "${name}"`;
968
1043
  }
969
- const stepError = await executeOnePrecondition(state, name);
970
- if (stepError != null) {
971
- return fail(state, stepError);
1044
+ const def = defsByName.get(name);
1045
+ if (def == null) {
1046
+ return `Unknown precondition: "${name}"`;
972
1047
  }
973
- index += 1;
974
- }
975
- return {
976
- cookies: state.cookies,
977
- data: state.data,
978
- error: void 0,
979
- executed: state.executed,
980
- runId: state.runId,
981
- success: true
1048
+ visiting.add(name);
1049
+ const deps = def.depMapping.map(([, depName]) => depName);
1050
+ const cycleError = visitDeps(deps);
1051
+ visiting.delete(name);
1052
+ if (cycleError != null) {
1053
+ return cycleError;
1054
+ }
1055
+ seen.add(name);
1056
+ order.push(name);
1057
+ return void 0;
982
1058
  };
1059
+ let err;
1060
+ let k = 0;
1061
+ const allNames = items.flatMap((item) => item.names);
1062
+ while (k < allNames.length && err == null) {
1063
+ const n = allNames[k];
1064
+ if (n != null) {
1065
+ err = visit(n);
1066
+ }
1067
+ k += 1;
1068
+ }
1069
+ if (err != null) {
1070
+ return { error: err, names: void 0 };
1071
+ }
1072
+ return { error: void 0, names: order };
983
1073
  }
984
- function validatePrecondition(defsByName, name) {
1074
+ async function executeStep({
1075
+ defsByName,
1076
+ name,
1077
+ states
1078
+ }) {
1079
+ const isActive = (s) => s.failure == null && s.names.includes(name);
1080
+ const active = states.filter((s) => isActive(s));
1081
+ if (active.length === 0) {
1082
+ return states;
1083
+ }
985
1084
  const def = defsByName.get(name);
986
1085
  if (def == null) {
987
- return `Unknown precondition: "${name}"`;
1086
+ return states.map(
1087
+ (s) => isActive(s) ? { ...s, failure: `Unknown precondition: "${name}"` } : s
1088
+ );
988
1089
  }
989
1090
  if (!def.implemented) {
990
- return `Precondition "${name}" is not implemented`;
1091
+ return states.map(
1092
+ (s) => isActive(s) ? { ...s, failure: `Precondition "${name}" is not implemented` } : s
1093
+ );
991
1094
  }
992
- return void 0;
1095
+ const stepResult = await runSetup({ active, def, name });
1096
+ return states.map((s) => isActive(s) ? mergeStepResult(s, name, stepResult) : s);
993
1097
  }
994
- async function executeOnePrecondition(state, name) {
995
- const def = state.defsByName.get(name);
996
- if (def == null) {
997
- return `Unknown precondition: "${name}"`;
998
- }
1098
+ async function runSetup({ active, def, name }) {
1099
+ const itemsForCall = active.map((s) => ({ ctx: s.ctx, deps: s.data }));
999
1100
  try {
1000
- const result = await def.setup(state.ctx, state.data);
1001
- const resolved = {};
1002
- Object.entries(result).forEach(([key, value]) => {
1003
- if (!isPrimitive(value)) {
1004
- throw new TypeError(
1005
- `Precondition "${name}" returned non-primitive value at key "${key}". Setup return values must be string, number, or boolean \u2014 produced via ctx.fixed/uniqueEmail/uniqueId.`
1006
- );
1007
- }
1008
- resolved[key] = value;
1101
+ const results = await def.setup(itemsForCall);
1102
+ if (results.length !== active.length) {
1103
+ return {
1104
+ batchError: `Precondition "${name}" returned ${String(results.length)} results for ${String(active.length)} items`,
1105
+ perItem: /* @__PURE__ */ new Map()
1106
+ };
1107
+ }
1108
+ const entries = active.map((s, idx) => {
1109
+ const r = results[idx];
1110
+ return r == null ? void 0 : [s.runId, r];
1009
1111
  });
1010
- state.data[name] = resolved;
1011
- state.executed.push(name);
1012
- return void 0;
1112
+ const valid = entries.filter(
1113
+ (e) => e != null
1114
+ );
1115
+ if (valid.length !== active.length) {
1116
+ return {
1117
+ batchError: `Precondition "${name}" returned a null result for at least one item`,
1118
+ perItem: /* @__PURE__ */ new Map()
1119
+ };
1120
+ }
1121
+ return { batchError: void 0, perItem: new Map(valid) };
1013
1122
  } catch (error) {
1014
- return error instanceof Error ? error.message : String(error);
1123
+ return {
1124
+ batchError: error instanceof Error ? error.message : String(error),
1125
+ perItem: /* @__PURE__ */ new Map()
1126
+ };
1015
1127
  }
1016
1128
  }
1017
- function fail(state, error) {
1129
+ function mergeStepResult(s, name, result) {
1130
+ if (result.batchError != null) {
1131
+ return { ...s, failure: result.batchError };
1132
+ }
1133
+ const data = result.perItem.get(s.runId);
1134
+ if (data == null) {
1135
+ return { ...s, failure: `Precondition "${name}" produced no result for run "${s.runId}"` };
1136
+ }
1018
1137
  return {
1019
- cookies: state.cookies,
1020
- data: state.data,
1138
+ ...s,
1139
+ data: { ...s.data, [name]: data },
1140
+ executed: [...s.executed, name]
1141
+ };
1142
+ }
1143
+ function failResult(s, error) {
1144
+ return {
1145
+ cookies: s.cookies,
1146
+ data: s.data,
1021
1147
  error,
1022
- executed: state.executed,
1023
- runId: state.runId,
1148
+ executed: s.executed,
1149
+ runId: s.runId,
1024
1150
  success: false
1025
1151
  };
1026
1152
  }
@@ -1049,25 +1175,56 @@ function createObserverContext(runId) {
1049
1175
  retry: (reason) => createRetryOutcome(reason)
1050
1176
  };
1051
1177
  }
1052
- async function teardown(defsByName, names, data) {
1053
- const reversed = [...names].toReversed();
1054
- let index = 0;
1055
- while (index < reversed.length) {
1056
- const name = reversed[index];
1178
+ async function teardown({
1179
+ defsByName,
1180
+ items
1181
+ }) {
1182
+ const errors = /* @__PURE__ */ new Map();
1183
+ const allNamesUnique = [...new Set(items.flatMap((i2) => i2.names))];
1184
+ const order = topoOrder(
1185
+ defsByName,
1186
+ allNamesUnique.map((n) => ({ names: [n], runId: "" }))
1187
+ );
1188
+ const reversed = (order.names ?? []).toReversed();
1189
+ let i = 0;
1190
+ while (i < reversed.length) {
1191
+ const name = reversed[i];
1057
1192
  if (name != null) {
1058
- await teardownOne(defsByName, name, data);
1193
+ await teardownStep({ defsByName, errors, items, name });
1059
1194
  }
1060
- index += 1;
1195
+ i += 1;
1061
1196
  }
1197
+ return items.map((it) => {
1198
+ const err = errors.get(it.runId);
1199
+ return { error: err, runId: it.runId, success: err == null };
1200
+ });
1062
1201
  }
1063
- async function teardownOne(defsByName, name, data) {
1202
+ async function teardownStep({
1203
+ defsByName,
1204
+ errors,
1205
+ items,
1206
+ name
1207
+ }) {
1064
1208
  const def = defsByName.get(name);
1065
1209
  if (def?.teardown == null) {
1066
1210
  return;
1067
1211
  }
1212
+ const active = items.filter((i) => i.names.includes(name));
1213
+ if (active.length === 0) {
1214
+ return;
1215
+ }
1216
+ const teardownItems = active.map((it) => ({
1217
+ ctx: { data: it.data[name] ?? {} }
1218
+ }));
1068
1219
  try {
1069
- await def.teardown({ data: data[name] ?? {} });
1070
- } catch {
1220
+ await def.teardown(teardownItems);
1221
+ } catch (error) {
1222
+ const message = error instanceof Error ? error.message : String(error);
1223
+ active.forEach((it) => {
1224
+ if (!errors.has(it.runId)) {
1225
+ errors.set(it.runId, message);
1226
+ }
1227
+ });
1071
1228
  }
1072
1229
  }
1073
1230
  function createSetupContext({
@@ -1099,7 +1256,6 @@ function deriveDefaultDomain(baseUrl) {
1099
1256
  export {
1100
1257
  DEFAULT_IGNORE_PATHS,
1101
1258
  DEFAULT_WATCH_PATHS,
1102
- buildSetCookieHeader,
1103
1259
  compile,
1104
1260
  createEngine,
1105
1261
  createRipplo,
@@ -1107,7 +1263,10 @@ export {
1107
1263
  notImplemented,
1108
1264
  observer,
1109
1265
  precondition,
1266
+ readAdapterWebhookSecret,
1110
1267
  serializeCookie,
1111
1268
  test,
1269
+ toBatchRunResults,
1270
+ toTeardownResults,
1112
1271
  verifyWebhookSignature
1113
1272
  };
package/dist/koa.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Middleware } from 'koa';
2
- import { R as RipploEngine } from './engine-DMOkJdjd.js';
3
- import './builder-BMjy83Iy.js';
4
- import './types-16SB7zjP.js';
2
+ import { R as RipploEngine } from './engine-DVbF4E5A.js';
3
+ import './builder-DiVz3t1D.js';
4
+ import './types-BzZrl65Z.js';
5
5
  import './step-De52hTLd.js';
6
6
  import '@ripplo/spec';
7
7
 
package/dist/koa.js CHANGED
@@ -1,18 +1,18 @@
1
1
  import {
2
2
  batchRequestSchema,
3
- buildSetCookieHeader,
4
3
  observerRequestSchema,
5
4
  readAdapterWebhookSecret,
6
- serializeCookie,
7
5
  teardownRequestSchema,
6
+ toBatchRunResults,
7
+ toTeardownResults,
8
8
  verifyWebhookSignature
9
- } from "./chunk-V6LMXKGL.js";
9
+ } from "./chunk-XO36IU66.js";
10
10
  import "./chunk-4MGIQFAJ.js";
11
11
 
12
12
  // src/adapters/koa.ts
13
13
  function createKoaHandler({ enabled, engine }) {
14
14
  if (!enabled) {
15
- return async (ctx, next) => {
15
+ return async (_ctx, next) => {
16
16
  await next();
17
17
  };
18
18
  }
@@ -57,29 +57,21 @@ async function handleExecutePreconditions({ body, ctx, engine }) {
57
57
  const parsed = json == null ? null : batchRequestSchema.safeParse(json);
58
58
  if (parsed == null || !parsed.success) {
59
59
  ctx.status = 400;
60
- ctx.body = { error: "Invalid request body", success: false };
60
+ ctx.body = { error: "Invalid request body" };
61
61
  return;
62
62
  }
63
63
  const host = ctx.get("host");
64
64
  if (host.length === 0) {
65
65
  ctx.status = 400;
66
- ctx.body = { error: "Missing host header", success: false };
66
+ ctx.body = { error: "Missing host header" };
67
67
  return;
68
68
  }
69
69
  const proto = ctx.get("x-forwarded-proto").length > 0 ? ctx.get("x-forwarded-proto") : "http";
70
70
  const appUrl = `${proto}://${host}`;
71
- const result = await engine.executePreconditions(parsed.data.preconditions, { appUrl });
72
- result.cookies.forEach((cookie) => {
73
- ctx.append("Set-Cookie", buildSetCookieHeader(serializeCookie(cookie)));
74
- });
71
+ const items = parsed.data.batch.map((b) => ({ names: b.preconditions, runId: b.runId }));
72
+ const results = await engine.executePreconditions(items, { appUrl });
75
73
  ctx.status = 200;
76
- ctx.body = {
77
- data: result.data,
78
- error: result.error,
79
- executed: result.executed,
80
- runId: result.runId,
81
- success: result.success
82
- };
74
+ ctx.body = { results: toBatchRunResults(results) };
83
75
  }
84
76
  async function handleExecuteObserver({ body, ctx, engine }) {
85
77
  const json = tryParseJson(body);
@@ -98,12 +90,17 @@ async function handleTeardown({ body, ctx, engine }) {
98
90
  const parsed = json == null ? null : teardownRequestSchema.safeParse(json);
99
91
  if (parsed == null || !parsed.success) {
100
92
  ctx.status = 400;
101
- ctx.body = { error: "Invalid request body", success: false };
93
+ ctx.body = { error: "Invalid request body" };
102
94
  return;
103
95
  }
104
- await engine.teardown(parsed.data.preconditions, parsed.data.data);
96
+ const items = parsed.data.batch.map((b) => ({
97
+ data: b.data,
98
+ names: b.preconditions,
99
+ runId: b.runId
100
+ }));
101
+ const results = await engine.teardown(items);
105
102
  ctx.status = 200;
106
- ctx.body = { success: true };
103
+ ctx.body = { results: toTeardownResults(results) };
107
104
  }
108
105
  function lastPathSegment(path) {
109
106
  const segments = path.split("/").filter((s) => s.length > 0);
@@ -1,12 +1,22 @@
1
1
  import { Codec } from '@ripplo/spec';
2
2
  import { z } from 'zod';
3
3
  import { CompileResult } from './compiler.js';
4
- import './builder-BMjy83Iy.js';
5
- import './types-16SB7zjP.js';
4
+ import './builder-DiVz3t1D.js';
5
+ import './types-BzZrl65Z.js';
6
6
  import './step-De52hTLd.js';
7
7
 
8
8
  declare const LOCKFILE_RELATIVE_PATH = ".ripplo/ripplo.lock";
9
- declare const lockfileBodySchema: z.ZodObject<{
9
+ declare const FIXTURES_RELATIVE_PATH = ".ripplo/fixtures";
10
+ declare const fixtureEntrySchema: z.ZodObject<{
11
+ sha256: z.ZodString;
12
+ size: z.ZodNumber;
13
+ }, z.core.$strip>;
14
+ type FixtureEntry = z.infer<typeof fixtureEntrySchema>;
15
+ declare const lockfileBodyV2Schema: z.ZodObject<{
16
+ fixtures: z.ZodRecord<z.ZodString, z.ZodObject<{
17
+ sha256: z.ZodString;
18
+ size: z.ZodNumber;
19
+ }, z.core.$strip>>;
10
20
  observers: z.ZodRecord<z.ZodString, z.ZodObject<{
11
21
  budget: z.ZodEnum<{
12
22
  fast: "fast";
@@ -677,7 +687,7 @@ declare const lockfileBodySchema: z.ZodObject<{
677
687
  }, z.core.$strip>;
678
688
  }, z.core.$strip>>;
679
689
  }, z.core.$strip>;
680
- type Lockfile = z.infer<typeof lockfileBodySchema>;
690
+ type Lockfile = z.infer<typeof lockfileBodyV2Schema>;
681
691
  declare const lockfileCodec: Codec<Lockfile>;
682
692
  declare function compileResultToLockfile(result: CompileResult): Lockfile;
683
693
  declare function serializeLockfile(lockfile: Lockfile): string;
@@ -690,6 +700,11 @@ interface WriteLockfileParams {
690
700
  readonly result: CompileResult;
691
701
  }
692
702
  declare function writeLockfile({ cwd, result }: WriteLockfileParams): Promise<void>;
703
+ interface HashFixturesParams {
704
+ readonly cwd: string;
705
+ readonly result: CompileResult;
706
+ }
707
+ declare function hashFixturesIntoCompileResult({ cwd, result, }: HashFixturesParams): Promise<CompileResult>;
693
708
  type LockfileComparison = "match" | "missing" | "stale";
694
709
  interface CompareLockfileParams {
695
710
  readonly compiled: CompileResult;
@@ -697,4 +712,4 @@ interface CompareLockfileParams {
697
712
  }
698
713
  declare function compareCompileToLockfile({ compiled, existing, }: CompareLockfileParams): LockfileComparison;
699
714
 
700
- export { type CompareLockfileParams, LOCKFILE_RELATIVE_PATH, type Lockfile, type LockfileComparison, type ReadLockfileParams, type WriteLockfileParams, compareCompileToLockfile, compileResultToLockfile, lockfileCodec, readLockfile, serializeLockfile, writeLockfile };
715
+ export { type CompareLockfileParams, FIXTURES_RELATIVE_PATH, type FixtureEntry, LOCKFILE_RELATIVE_PATH, type Lockfile, type LockfileComparison, type ReadLockfileParams, type WriteLockfileParams, compareCompileToLockfile, compileResultToLockfile, hashFixturesIntoCompileResult, lockfileCodec, readLockfile, serializeLockfile, writeLockfile };