@reventlessdev/reventless-aws 3.0.0-alpha.334 → 3.0.0-alpha.335

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/CHANGELOG.md CHANGED
@@ -3,6 +3,13 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # 3.0.0-alpha.335 (2026-09-02)
7
+
8
+ ### Bug Fixes
9
+
10
+ * **aws:** a deployed Lambda carries its plugin's own dependencies ([8dfac24](https://github.com/ReventlessDev/reventless-core/commit/8dfac24285723beb74f84c6cc821864e56ee75a2))
11
+
12
+
6
13
  # 3.0.0-alpha.334 (2026-09-02)
7
14
 
8
15
  * feat(aws)!: the messaging sender is configuration, and a stack can choose to only log ([23b8b4b](https://github.com/ReventlessDev/reventless-core/commit/23b8b4bfe9c70555de4d74266ca686cb427485ca))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-aws",
3
- "version": "3.0.0-alpha.334",
3
+ "version": "3.0.0-alpha.335",
4
4
  "description": "AWS adapters for Reventless",
5
5
  "license": "Apache-2.0",
6
6
  "bin": {
@@ -15,15 +15,15 @@
15
15
  "@aws-sdk/s3-request-presigner": "3.970.0",
16
16
  "sury": "11.0.0-rc.2",
17
17
  "uuid": "^13.0.0",
18
- "@reventlessdev/rescript-effect": "0.1.0-alpha.32",
19
- "@reventlessdev/rescript-jest": "1.0.0-alpha.10",
20
18
  "@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.14",
19
+ "@reventlessdev/rescript-jest": "1.0.0-alpha.10",
21
20
  "@reventlessdev/rescript-node": "2.0.0-alpha.8",
22
21
  "@reventlessdev/rescript-pulumi-aws": "3.0.0-alpha.7",
22
+ "@reventlessdev/rescript-effect": "0.1.0-alpha.32",
23
+ "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.19",
23
24
  "@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
24
25
  "@reventlessdev/reventless-core": "3.0.0-alpha.254",
25
26
  "@reventlessdev/reventless-infra": "3.0.0-alpha.155",
26
- "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.19",
27
27
  "@reventlessdev/reventless-interop": "3.0.0-alpha.34",
28
28
  "@reventlessdev/reventless-postgres": "3.0.0-alpha.118",
29
29
  "@reventlessdev/reventless-spec": "3.0.0-alpha.127"
@@ -349,6 +349,126 @@ let isRuntimeProvided = (specifier: string, ~pkgName: string): bool =>
349
349
  }
350
350
  )
351
351
 
352
+ // Memo for isFrameworkPackage — one require.resolve per package name per deploy.
353
+ let frameworkPackageCache: dict<bool> = Dict.make()
354
+
355
+ /**
356
+ * Whether the framework's own module resolution reaches this package: the
357
+ * deploy-time stand-in for "the Lambda layer already carries it", since the
358
+ * layer is built from reventless-aws's dependency closure.
359
+ *
360
+ * Deliberately framework-rooted only — no Pulumi-project fallback. The question
361
+ * here is the opposite of `resolvePackageRoot`'s: a package the *project*
362
+ * resolves but the framework does not is precisely a user package, which is
363
+ * what the closure walk below must treat as a starting point rather than as
364
+ * something the runtime provides.
365
+ */
366
+ let isFrameworkPackage = (pkgName: string): bool =>
367
+ switch frameworkPackageCache->Dict.get(pkgName) {
368
+ | Some(known) => known
369
+ | None =>
370
+ let known = try {
371
+ let _ = localRequire->NodeModule.requireResolve(pkgName ++ "/package.json")
372
+ true
373
+ } catch {
374
+ | _ => false
375
+ }
376
+ frameworkPackageCache->Dict.set(pkgName, known)
377
+ known
378
+ }
379
+
380
+ // The distinct bare packages a package's own bundled files import, memoised per
381
+ // package root: the same file set the archive carries, so what is scanned is
382
+ // what is deployed.
383
+ let importedPackagesCache: dict<array<string>> = Dict.make()
384
+ let importedPackages = (packageRoot: string): array<string> =>
385
+ switch importedPackagesCache->Dict.get(packageRoot) {
386
+ | Some(names) => names
387
+ | None =>
388
+ let assets: dict<Pulumi.Archive.assetOrArchive> = Dict.make()
389
+ let paths: array<(string, string)> = []
390
+ walkDir(~dir=packageRoot, ~prefix="", ~assets, ~paths)
391
+ let names: array<string> = []
392
+ paths->Array.forEach(((relPath, absPath)) =>
393
+ if relPath->String.endsWith(".mjs") || relPath->String.endsWith(".js") {
394
+ NodeFs.readFileSync(absPath)
395
+ ->staticImportSpecifiers
396
+ ->Array.forEach(specifier =>
397
+ if isBareSpecifier(specifier) {
398
+ let name = extractPackageName(specifier)
399
+ if !(names->Array.includes(name)) {
400
+ names->Array.push(name)
401
+ }
402
+ }
403
+ )
404
+ }
405
+ )
406
+ importedPackagesCache->Dict.set(packageRoot, names)
407
+ names
408
+ }
409
+
410
+ /**
411
+ * Resolve a package root as the importing package would — from that package's
412
+ * own directory. Under pnpm a plugin's dependency is reachable only from the
413
+ * plugin, never from the framework or the Pulumi project, so this is the one
414
+ * rooting that finds it. `None` when nothing is installed there: the archive
415
+ * cannot carry what is not on disk, and a package the code truly imported at
416
+ * runtime would already be failing today.
417
+ */
418
+ let resolvePackageRootFrom = (~fromRoot: string, pkgName: string): option<string> =>
419
+ try {
420
+ Some(
421
+ NodePath.dirname(
422
+ NodeModule.createRequire(NodePath.join([fromRoot, "index.js"]))
423
+ ->NodeModule.requireResolve(pkgName ++ "/package.json"),
424
+ ),
425
+ )
426
+ } catch {
427
+ | _ => None
428
+ }
429
+
430
+ /**
431
+ * Carry every package a bundled *user* package imports into the archive, and
432
+ * every package those import in turn.
433
+ *
434
+ * A plugin package rides in `/var/task/node_modules/<plugin>`, but its own
435
+ * dependencies do not: the layer holds reventless-aws's closure, which a domain
436
+ * trait or any other plugin-level library is by definition outside of. The
437
+ * import then fails at the first command the slice handles — a green deploy and
438
+ * a `Cannot find package` at runtime.
439
+ *
440
+ * Framework packages are the starting points that are skipped, not walked:
441
+ * their closure is the layer's, and their sources reach deploy-time-only
442
+ * imports (the @pulumi bindings) that no Lambda loads — walking them would add
443
+ * tens of megabytes to every archive.
444
+ */
445
+ let addImportedPackageClosure = (packageDirs: dict<string>): unit => {
446
+ let pending =
447
+ packageDirs->Dict.toArray->Array.filter(((pkgName, _)) => !isFrameworkPackage(pkgName))
448
+ let next = ref(0)
449
+ while next.contents < pending->Array.length {
450
+ let (_, pkgRoot) = pending->Array.getUnsafe(next.contents)
451
+ next := next.contents + 1
452
+ importedPackages(pkgRoot)->Array.forEach(dep =>
453
+ if (
454
+ !(packageDirs->Dict.has(dep)) &&
455
+ !(dep->String.startsWith("node:")) &&
456
+ !(nodeBuiltins->Set.has(dep)) &&
457
+ !(dep->String.startsWith("@aws-sdk/")) &&
458
+ !(dep->String.startsWith("@smithy/")) &&
459
+ !isFrameworkPackage(dep)
460
+ ) {
461
+ switch resolvePackageRootFrom(~fromRoot=pkgRoot, dep) {
462
+ | Some(depRoot) =>
463
+ packageDirs->Dict.set(dep, depRoot)
464
+ pending->Array.push((dep, depRoot))
465
+ | None => ()
466
+ }
467
+ }
468
+ )
469
+ }
470
+ }
471
+
352
472
  /**
353
473
  * Deploy-time guard: every bare package statically imported by a bundled
354
474
  * runtime-extension (or companion) package must either be in the archive
@@ -451,7 +571,12 @@ let buildCodeArchive = (
451
571
  // deploy whose extensions are silently skipped.
452
572
  if bundleRuntimeExtensions {
453
573
  let extensionPackages = allPackageDirs->addRuntimeExtensionPackages
574
+ // The closure runs first so a companion package that is simply a static
575
+ // import of the extension is already bundled by the time the guard looks.
576
+ allPackageDirs->addImportedPackageClosure
454
577
  assertRuntimeExtensionImportsResolvable(~extensionPackages, ~bundledPackages=allPackageDirs)
578
+ } else {
579
+ allPackageDirs->addImportedPackageClosure
455
580
  }
456
581
  let packageContentHashes: ref<array<string>> = ref([])
457
582
  allPackageDirs->Dict.forEachWithKey((pkgRoot, pkgName) => {
@@ -232,6 +232,87 @@ function isRuntimeProvided(specifier, pkgName) {
232
232
  }
233
233
  }
234
234
 
235
+ let frameworkPackageCache = {};
236
+
237
+ function isFrameworkPackage(pkgName) {
238
+ let known = frameworkPackageCache[pkgName];
239
+ if (known !== undefined) {
240
+ return known;
241
+ }
242
+ let known$1;
243
+ try {
244
+ localRequire.resolve(pkgName + "/package.json");
245
+ known$1 = true;
246
+ } catch (exn) {
247
+ known$1 = false;
248
+ }
249
+ frameworkPackageCache[pkgName] = known$1;
250
+ return known$1;
251
+ }
252
+
253
+ let importedPackagesCache = {};
254
+
255
+ function importedPackages(packageRoot) {
256
+ let names = importedPackagesCache[packageRoot];
257
+ if (names !== undefined) {
258
+ return names;
259
+ }
260
+ let assets = {};
261
+ let paths = [];
262
+ walkDir(packageRoot, "", assets, paths);
263
+ let names$1 = [];
264
+ paths.forEach(param => {
265
+ let relPath = param[0];
266
+ if (relPath.endsWith(".mjs") || relPath.endsWith(".js")) {
267
+ staticImportSpecifiers(Nodefs.readFileSync(param[1], "utf8")).forEach(specifier => {
268
+ if (!isBareSpecifier(specifier)) {
269
+ return;
270
+ }
271
+ let name = extractPackageName(specifier);
272
+ if (!names$1.includes(name)) {
273
+ names$1.push(name);
274
+ return;
275
+ }
276
+ });
277
+ return;
278
+ }
279
+ });
280
+ importedPackagesCache[packageRoot] = names$1;
281
+ return names$1;
282
+ }
283
+
284
+ function resolvePackageRootFrom(fromRoot, pkgName) {
285
+ try {
286
+ return Nodepath.dirname(Nodemodule.createRequire(Nodepath.join(fromRoot, "index.js")).resolve(pkgName + "/package.json"));
287
+ } catch (exn) {
288
+ return;
289
+ }
290
+ }
291
+
292
+ function addImportedPackageClosure(packageDirs) {
293
+ let pending = Object.entries(packageDirs).filter(param => !isFrameworkPackage(param[0]));
294
+ let next = 0;
295
+ while (next < pending.length) {
296
+ let match = pending[next];
297
+ let pkgRoot = match[1];
298
+ next = next + 1 | 0;
299
+ importedPackages(pkgRoot).forEach(dep => {
300
+ if (dep in packageDirs || dep.startsWith("node:") || nodeBuiltins.has(dep) || dep.startsWith("@aws-sdk/") || dep.startsWith("@smithy/") || isFrameworkPackage(dep)) {
301
+ return;
302
+ }
303
+ let depRoot = resolvePackageRootFrom(pkgRoot, dep);
304
+ if (depRoot !== undefined) {
305
+ packageDirs[dep] = depRoot;
306
+ pending.push([
307
+ dep,
308
+ depRoot
309
+ ]);
310
+ return;
311
+ }
312
+ });
313
+ };
314
+ }
315
+
235
316
  function assertRuntimeExtensionImportsResolvable(extensionPackages, bundledPackages) {
236
317
  Stdlib_Dict.forEachWithKey(extensionPackages, (pkgRoot, pkgName) => {
237
318
  let assets = {};
@@ -275,7 +356,10 @@ function buildCodeArchive(entryPointModule, packageDirs, extraStringAssetsOpt, b
275
356
  }
276
357
  if (bundleRuntimeExtensions) {
277
358
  let extensionPackages = addRuntimeExtensionPackages(allPackageDirs);
359
+ addImportedPackageClosure(allPackageDirs);
278
360
  assertRuntimeExtensionImportsResolvable(extensionPackages, allPackageDirs);
361
+ } else {
362
+ addImportedPackageClosure(allPackageDirs);
279
363
  }
280
364
  let packageContentHashes = {
281
365
  contents: []
@@ -326,6 +410,12 @@ export {
326
410
  isBareSpecifier,
327
411
  nodeBuiltins,
328
412
  isRuntimeProvided,
413
+ frameworkPackageCache,
414
+ isFrameworkPackage,
415
+ importedPackagesCache,
416
+ importedPackages,
417
+ resolvePackageRootFrom,
418
+ addImportedPackageClosure,
329
419
  assertRuntimeExtensionImportsResolvable,
330
420
  buildCodeArchive,
331
421
  }
@@ -0,0 +1,111 @@
1
+ open JestGlobals
2
+
3
+ // A plugin package rides in the code archive, but its own dependencies used to
4
+ // stay behind: the layer carries reventless-aws's closure, and a domain trait
5
+ // (or any other plugin-level library) is outside it. The archive build now
6
+ // walks what a bundled user package actually imports.
7
+
8
+ let mkTmpRoot = (prefix: string) =>
9
+ NodeFs.realpathSync(NodeFs.mkdtempSync(NodePath.join([NodeOs.tmpdir(), prefix])))
10
+
11
+ let writePkg = (~root: string, ~name: string, files: array<(string, string)>): string => {
12
+ NodeFs.mkdirSync(root, {recursive: true})
13
+ NodeFs.writeFileSync(
14
+ NodePath.join([root, "package.json"]),
15
+ `{"name":"${name}","version":"1.0.0"}`,
16
+ )
17
+ files->Array.forEach(((relPath, content)) => {
18
+ let abs = NodePath.join([root, relPath])
19
+ NodeFs.mkdirSync(NodePath.dirname(abs), {recursive: true})
20
+ NodeFs.writeFileSync(abs, content)
21
+ })
22
+ root
23
+ }
24
+
25
+ // A user package whose slice imports a trait, the trait imports a rules
26
+ // package, and both are installed only under the user package — the pnpm shape,
27
+ // where nothing but the importer can resolve them.
28
+ let hostName = "@fixture/closure-host"
29
+ let traitName = "@fixture/closure-trait"
30
+ let rulesName = "@fixture/closure-rules"
31
+
32
+ let hostRoot = mkTmpRoot("bundle-closure-")
33
+ let traitRoot = NodePath.join([hostRoot, "node_modules", traitName])
34
+ let rulesRoot = NodePath.join([hostRoot, "node_modules", rulesName])
35
+
36
+ let _ = writePkg(
37
+ ~root=hostRoot,
38
+ ~name=hostName,
39
+ [
40
+ (
41
+ "src/Slice.res.mjs",
42
+ `import * as Trait from "${traitName}";
43
+ import * as Sury from "sury";
44
+ import * as Fs from "node:fs";
45
+ import * as Path from "path";
46
+ import * as Ddb from "@aws-sdk/client-dynamodb";
47
+ import * as Missing from "@fixture/never-installed";
48
+ export const handle = () => Trait.rule(Sury, Fs, Path, Ddb, Missing);
49
+ `,
50
+ ),
51
+ ],
52
+ )
53
+ let _ = writePkg(
54
+ ~root=traitRoot,
55
+ ~name=traitName,
56
+ [
57
+ (
58
+ "src/Trait.res.mjs",
59
+ `import * as Rules from "${rulesName}";
60
+ export const rule = () => Rules.check();
61
+ `,
62
+ ),
63
+ ],
64
+ )
65
+ let _ = writePkg(~root=rulesRoot, ~name=rulesName, [("src/Rules.res.mjs", `export const check = () => true;
66
+ `)])
67
+
68
+ let closureOf = (packageDirs: dict<string>) => {
69
+ Util_Bundle.addImportedPackageClosure(packageDirs)
70
+ packageDirs
71
+ }
72
+
73
+ describe("Util_Bundle — bundled user packages carry their imports", () => {
74
+ afterAll(() => NodeFs.rmSync(hostRoot, {recursive: true, force: true}))
75
+
76
+ testSync("a dependency only the importing package can resolve is added", () => {
77
+ let packageDirs = Dict.fromArray([(hostName, hostRoot)])
78
+ expect(closureOf(packageDirs)->Dict.get(traitName))->toEqual(Some(traitRoot))
79
+ })
80
+
81
+ testSync("the walk is transitive — the dependency's own imports come too", () => {
82
+ let packageDirs = Dict.fromArray([(hostName, hostRoot)])
83
+ expect(closureOf(packageDirs)->Dict.get(rulesName))->toEqual(Some(rulesRoot))
84
+ })
85
+
86
+ testSync("packages the deployed runtime provides are left out", () => {
87
+ let packageDirs = Dict.fromArray([(hostName, hostRoot)])
88
+ let bundled = closureOf(packageDirs)->Dict.keysToArray
89
+ // sury rides in the layer (framework-resolvable); node builtins and the
90
+ // @aws-sdk scope resolve from the runtime's own dirs.
91
+ expect(bundled->Array.includes("sury"))->toBe(false)
92
+ expect(bundled->Array.includes("fs"))->toBe(false)
93
+ expect(bundled->Array.includes("path"))->toBe(false)
94
+ expect(bundled->Array.includes("@aws-sdk/client-dynamodb"))->toBe(false)
95
+ })
96
+
97
+ testSync("an import that resolves nowhere is skipped, not thrown on", () => {
98
+ let packageDirs = Dict.fromArray([(hostName, hostRoot)])
99
+ let bundled = closureOf(packageDirs)->Dict.keysToArray
100
+ expect(bundled->Array.includes("@fixture/never-installed"))->toBe(false)
101
+ })
102
+
103
+ testSync("framework packages are starting points, not walked", () => {
104
+ // Walking reventless-aws would reach its deploy-time-only imports (the
105
+ // @pulumi bindings) and add tens of megabytes to every archive.
106
+ let packageDirs = Dict.fromArray([
107
+ ("@reventlessdev/reventless-aws", Util_Bundle.resolvePackageRoot("@reventlessdev/reventless-aws")),
108
+ ])
109
+ expect(closureOf(packageDirs)->Dict.keysToArray->Array.length)->toBe(1)
110
+ })
111
+ })
@@ -0,0 +1,129 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Nodefs from "node:fs";
4
+ import * as Nodeos from "node:os";
5
+ import * as Nodepath from "node:path";
6
+ import * as Util_Bundle$ReventlessAws from "../src/util/Util_Bundle.res.mjs";
7
+
8
+ function mkTmpRoot(prefix) {
9
+ return Nodefs.realpathSync(Nodefs.mkdtempSync(Nodepath.join(Nodeos.tmpdir(), prefix)));
10
+ }
11
+
12
+ function writePkg(root, name, files) {
13
+ Nodefs.mkdirSync(root, {
14
+ recursive: true
15
+ });
16
+ Nodefs.writeFileSync(Nodepath.join(root, "package.json"), `{"name":"` + name + `","version":"1.0.0"}`, "utf8");
17
+ files.forEach(param => {
18
+ let abs = Nodepath.join(root, param[0]);
19
+ Nodefs.mkdirSync(Nodepath.dirname(abs), {
20
+ recursive: true
21
+ });
22
+ Nodefs.writeFileSync(abs, param[1], "utf8");
23
+ });
24
+ return root;
25
+ }
26
+
27
+ let hostName = "@fixture/closure-host";
28
+
29
+ let traitName = "@fixture/closure-trait";
30
+
31
+ let rulesName = "@fixture/closure-rules";
32
+
33
+ let hostRoot = mkTmpRoot("bundle-closure-");
34
+
35
+ let traitRoot = Nodepath.join(hostRoot, "node_modules", traitName);
36
+
37
+ let rulesRoot = Nodepath.join(hostRoot, "node_modules", rulesName);
38
+
39
+ writePkg(hostRoot, hostName, [[
40
+ "src/Slice.res.mjs",
41
+ `import * as Trait from "` + traitName + `";
42
+ import * as Sury from "sury";
43
+ import * as Fs from "node:fs";
44
+ import * as Path from "path";
45
+ import * as Ddb from "@aws-sdk/client-dynamodb";
46
+ import * as Missing from "@fixture/never-installed";
47
+ export const handle = () => Trait.rule(Sury, Fs, Path, Ddb, Missing);
48
+ `
49
+ ]]);
50
+
51
+ writePkg(traitRoot, traitName, [[
52
+ "src/Trait.res.mjs",
53
+ `import * as Rules from "` + rulesName + `";
54
+ export const rule = () => Rules.check();
55
+ `
56
+ ]]);
57
+
58
+ writePkg(rulesRoot, rulesName, [[
59
+ "src/Rules.res.mjs",
60
+ `export const check = () => true;
61
+ `
62
+ ]]);
63
+
64
+ function closureOf(packageDirs) {
65
+ Util_Bundle$ReventlessAws.addImportedPackageClosure(packageDirs);
66
+ return packageDirs;
67
+ }
68
+
69
+ globalThis.describe("Util_Bundle — bundled user packages carry their imports", () => {
70
+ globalThis.afterAll(() => {
71
+ Nodefs.rmSync(hostRoot, {
72
+ recursive: true,
73
+ force: true
74
+ });
75
+ });
76
+ globalThis.test("a dependency only the importing package can resolve is added", () => {
77
+ let packageDirs = Object.fromEntries([[
78
+ hostName,
79
+ hostRoot
80
+ ]]);
81
+ globalThis.expect((Util_Bundle$ReventlessAws.addImportedPackageClosure(packageDirs), packageDirs)[traitName]).toEqual(traitRoot);
82
+ });
83
+ globalThis.test("the walk is transitive — the dependency's own imports come too", () => {
84
+ let packageDirs = Object.fromEntries([[
85
+ hostName,
86
+ hostRoot
87
+ ]]);
88
+ globalThis.expect((Util_Bundle$ReventlessAws.addImportedPackageClosure(packageDirs), packageDirs)[rulesName]).toEqual(rulesRoot);
89
+ });
90
+ globalThis.test("packages the deployed runtime provides are left out", () => {
91
+ let packageDirs = Object.fromEntries([[
92
+ hostName,
93
+ hostRoot
94
+ ]]);
95
+ let bundled = Object.keys((Util_Bundle$ReventlessAws.addImportedPackageClosure(packageDirs), packageDirs));
96
+ globalThis.expect(bundled.includes("sury")).toBe(false);
97
+ globalThis.expect(bundled.includes("fs")).toBe(false);
98
+ globalThis.expect(bundled.includes("path")).toBe(false);
99
+ globalThis.expect(bundled.includes("@aws-sdk/client-dynamodb")).toBe(false);
100
+ });
101
+ globalThis.test("an import that resolves nowhere is skipped, not thrown on", () => {
102
+ let packageDirs = Object.fromEntries([[
103
+ hostName,
104
+ hostRoot
105
+ ]]);
106
+ let bundled = Object.keys((Util_Bundle$ReventlessAws.addImportedPackageClosure(packageDirs), packageDirs));
107
+ globalThis.expect(bundled.includes("@fixture/never-installed")).toBe(false);
108
+ });
109
+ globalThis.test("framework packages are starting points, not walked", () => {
110
+ let packageDirs = Object.fromEntries([[
111
+ "@reventlessdev/reventless-aws",
112
+ Util_Bundle$ReventlessAws.resolvePackageRoot(undefined, "@reventlessdev/reventless-aws")
113
+ ]]);
114
+ globalThis.expect(Object.keys((Util_Bundle$ReventlessAws.addImportedPackageClosure(packageDirs), packageDirs)).length).toBe(1);
115
+ });
116
+ });
117
+
118
+ export {
119
+ mkTmpRoot,
120
+ writePkg,
121
+ hostName,
122
+ traitName,
123
+ rulesName,
124
+ hostRoot,
125
+ traitRoot,
126
+ rulesRoot,
127
+ closureOf,
128
+ }
129
+ /* hostRoot Not a pure module */