@graftland/cli 0.3.1 → 0.3.3

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/Manage.d.ts CHANGED
@@ -1,4 +1,6 @@
1
- import { Effect, Option } from "effect";
1
+ import * as Create from "@graftland/create";
2
+ import * as PackageManager from "@graftland/package-manager";
3
+ import { Config, Effect, Option } from "effect";
2
4
  declare namespace Manage_d_exports {
3
5
  export { add, create, remove };
4
6
  }
@@ -6,8 +8,8 @@ export declare const create: (options: {
6
8
  readonly directory: string;
7
9
  readonly name: Option.Option<string>;
8
10
  readonly install: boolean;
9
- }) => Effect.Effect<void, unknown, unknown>;
10
- export declare const add: (specs: ReadonlyArray<string>) => Effect.Effect<void, unknown, any>;
11
- export declare const remove: (names: ReadonlyArray<string>) => Effect.Effect<void, unknown, unknown>;
11
+ }) => Effect.Effect<void, Config.ConfigError | Create.CreateError | Create.InvalidName | Create.InvalidPackageManager | PackageManager.Error, import("effect/process/ChildProcessSpawner").ChildProcessSpawner | import("effect/FileSystem").FileSystem | import("effect/Path").Path>;
12
+ export declare const add: (specs: readonly string[]) => Effect.Effect<void, Config.ConfigError | PackageManager.Error, import("effect/process/ChildProcessSpawner").ChildProcessSpawner | import("effect/FileSystem").FileSystem | import("effect/Path").Path>;
13
+ export declare const remove: (names: ReadonlyArray<string>) => Effect.Effect<void, Config.ConfigError | PackageManager.Error, import("effect/process/ChildProcessSpawner").ChildProcessSpawner | import("effect/FileSystem").FileSystem | import("effect/Path").Path>;
12
14
  export { Manage_d_exports };
13
15
  //# sourceMappingURL=Manage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Manage.d.ts","names":[],"sources":["../src/Manage.ts"],"mappings":";;;;qBA6Da,SAAM;WACT;WACA,MAAM,OAAO;WACb;MACT,OAAA;qBAaY,MAAG,OAAW,0BAAqB,OAAA;qBAcnC,SAAM,OAAW,0BAAqB,OAAA"}
1
+ {"version":3,"file":"Manage.d.ts","names":[],"sources":["../src/Manage.ts"],"mappings":";;;;;;qBA4Da,SAAM;WACE;WACL,MAAA,OAAO;WACJ;MAWjB,OAAA,aAAA,OAAA,cAAA,OAAA,cAAA,OAAA,cAAA,OAAA,wBAAA,eAAA,oDAAA,kDAAA,mCAAA;qBAEW,MAAG,6BAAA,OAAA,aAAA,OAAA,cAAA,eAAA,oDAAA,kDAAA,mCAAA;qBAgBH,SAAM,OAAW,0BAAqB,OAAA,aAAA,OAAA,cAAA,eAAA,oDAAA,kDAAA,mCAAA"}
package/dist/Manage.js CHANGED
@@ -25,7 +25,7 @@ const Spec = Schema.Union([Schema.TemplateLiteralParser([
25
25
  Schema.String
26
26
  ]), PackageManager.PackageName]);
27
27
  const decodeSpecs = Schema.decodeEffect(Schema.Array(Spec));
28
- const install = (directory, changes) => Effect.gen(function* () {
28
+ const install = Effect.fn("Manage.install")(function* (directory, changes) {
29
29
  const decoded = yield* Schema.decodeEffect(PackageManager.Changes)(changes);
30
30
  const userAgent = yield* Config.String("npm_config_user_agent").pipe(Config.withDefault(""));
31
31
  const result = yield* (yield* PackageManager.make({
@@ -34,7 +34,7 @@ const install = (directory, changes) => Effect.gen(function* () {
34
34
  })).install(decoded);
35
35
  yield* report(result);
36
36
  });
37
- const create = (options) => Effect.gen(function* () {
37
+ const create = Effect.fn("Manage.create")(function* (options) {
38
38
  const project = yield* Create.create({
39
39
  directory: options.directory,
40
40
  ...Option.match(options.name, {
@@ -45,7 +45,10 @@ const create = (options) => Effect.gen(function* () {
45
45
  yield* Effect.logInfo(`Created ${project.name} in ${project.directory}`);
46
46
  if (options.install) yield* install(project.directory, {});
47
47
  });
48
- const add = (specs) => Effect.flatMap(decodeSpecs(specs), (specs) => install(".", Record.fromEntries(Array.map(specs, (spec) => Predicate.isString(spec) ? [spec, "latest"] : [spec[0], spec[2]]))));
48
+ const add = Effect.fn("Manage.add")(function* (specs) {
49
+ const decoded = yield* decodeSpecs(specs);
50
+ yield* install(".", Record.fromEntries(Array.map(decoded, (spec) => Predicate.isString(spec) ? [spec, "latest"] : [spec[0], spec[2]])));
51
+ });
49
52
  const remove = (names) => install(".", Record.fromEntries(Array.map(names, (name) => [name, null])));
50
53
  export { Manage_exports, add, create, remove };
51
54
 
@@ -1 +1 @@
1
- {"version":3,"file":"Manage.js","names":[],"sources":["../src/Manage.ts"],"sourcesContent":["/**\n * Creating projects and changing their dependencies.\n */\nimport * as Create from \"@graftland/create\"\nimport * as PackageManager from \"@graftland/package-manager\"\nimport {\n\tArray,\n\tConfig,\n\tEffect,\n\tOption,\n\tPredicate,\n\tRecord,\n\tSchema,\n} from \"effect\"\n\nconst report = (result: PackageManager.InstallResult) =>\n\tEffect.logInfo(\n\t\t`${result.agent}: ${result.added.length} added, ${result.updated.length} updated, ${result.removed.length} removed`,\n\t).pipe(\n\t\tEffect.andThen(\n\t\t\tOption.match(result.snapshotCleanupFailure, {\n\t\t\t\tonNone: () => Effect.void,\n\t\t\t\tonSome: ({ cause, snapshotDirectory }) =>\n\t\t\t\t\tEffect.logWarning(\n\t\t\t\t\t\t\"Installation committed; snapshot cleanup failed. Retry removing the retained snapshot.\",\n\t\t\t\t\t\tcause,\n\t\t\t\t\t\t{ snapshotDirectory },\n\t\t\t\t\t),\n\t\t\t}),\n\t\t),\n\t)\n\n/**\n * `name@range`, split at the last `@` so a scope's `@` stays in the name. A\n * bare name means the latest version.\n */\nconst Spec = Schema.Union([\n\tSchema.TemplateLiteralParser([\n\t\tPackageManager.PackageName,\n\t\t\"@\",\n\t\tSchema.String,\n\t]),\n\tPackageManager.PackageName,\n])\nconst decodeSpecs = Schema.decodeEffect(Schema.Array(Spec))\n\nconst install = (\n\tdirectory: string,\n\tchanges: Readonly<Record<string, string | null>>,\n) =>\n\tEffect.gen(function* () {\n\t\tconst decoded = yield* Schema.decodeEffect(PackageManager.Changes)(changes)\n\t\t// Set by npm, pnpm and Yarn when they run graft, such as through `pnpm dlx`.\n\t\tconst userAgent = yield* Config.String(\"npm_config_user_agent\").pipe(\n\t\t\tConfig.withDefault(\"\"),\n\t\t)\n\t\tconst manager = yield* PackageManager.make({ directory, userAgent })\n\t\tconst result = yield* manager.install(decoded)\n\t\tyield* report(result)\n\t})\n\nexport const create = (options: {\n\treadonly directory: string\n\treadonly name: Option.Option<string>\n\treadonly install: boolean\n}) =>\n\tEffect.gen(function* () {\n\t\tconst project = yield* Create.create({\n\t\t\tdirectory: options.directory,\n\t\t\t...Option.match(options.name, {\n\t\t\t\tonNone: () => ({}),\n\t\t\t\tonSome: (name) => ({ name }),\n\t\t\t}),\n\t\t})\n\t\tyield* Effect.logInfo(`Created ${project.name} in ${project.directory}`)\n\t\tif (options.install) yield* install(project.directory, {})\n\t})\n\nexport const add = (specs: ReadonlyArray<string>) =>\n\tEffect.flatMap(decodeSpecs(specs), (specs) =>\n\t\tinstall(\n\t\t\t\".\",\n\t\t\tRecord.fromEntries(\n\t\t\t\tArray.map(specs, (spec) =>\n\t\t\t\t\tPredicate.isString(spec)\n\t\t\t\t\t\t? ([spec, \"latest\"] as const)\n\t\t\t\t\t\t: ([spec[0], spec[2]] as const),\n\t\t\t\t),\n\t\t\t),\n\t\t),\n\t)\n\nexport const remove = (names: ReadonlyArray<string>) =>\n\tinstall(\n\t\t\".\",\n\t\tRecord.fromEntries(Array.map(names, (name) => [name, null] as const)),\n\t)\n"],"mappings":";;;;;;;;;;;;;AAeA,MAAM,UAAU,WACf,OAAO,QACN,GAAG,OAAO,MAAM,IAAI,OAAO,MAAM,OAAO,UAAU,OAAO,QAAQ,OAAO,YAAY,OAAO,QAAQ,OAAO,SAC3G,CAAC,CAAC,KACD,OAAO,QACN,OAAO,MAAM,OAAO,wBAAwB;CAC3C,cAAc,OAAO;CACrB,SAAS,EAAE,OAAO,wBACjB,OAAO,WACN,0FACA,OACA,EAAE,kBAAkB,CACrB;AACF,CAAC,CACF,CACD;;;;;AAMD,MAAM,OAAO,OAAO,MAAM,CACzB,OAAO,sBAAsB;CAC5B,eAAe;CACf;CACA,OAAO;AACR,CAAC,GACD,eAAe,WAChB,CAAC;AACD,MAAM,cAAc,OAAO,aAAa,OAAO,MAAM,IAAI,CAAC;AAE1D,MAAM,WACL,WACA,YAEA,OAAO,IAAI,aAAa;CACvB,MAAM,UAAU,OAAO,OAAO,aAAa,eAAe,OAAO,CAAC,CAAC,OAAO;CAE1E,MAAM,YAAY,OAAO,OAAO,OAAO,uBAAuB,CAAC,CAAC,KAC/D,OAAO,YAAY,EAAE,CACtB;CAEA,MAAM,SAAS,QAAO,OADC,eAAe,KAAK;EAAE;EAAW;CAAU,CAAC,EAAA,CACrC,QAAQ,OAAO;CAC7C,OAAO,OAAO,MAAM;AACrB,CAAC;AAEF,MAAa,UAAU,YAKtB,OAAO,IAAI,aAAa;CACvB,MAAM,UAAU,OAAO,OAAO,OAAO;EACpC,WAAW,QAAQ;EACnB,GAAG,OAAO,MAAM,QAAQ,MAAM;GAC7B,eAAe,CAAC;GAChB,SAAS,UAAU,EAAE,KAAK;EAC3B,CAAC;CACF,CAAC;CACD,OAAO,OAAO,QAAQ,WAAW,QAAQ,KAAK,MAAM,QAAQ,WAAW;CACvE,IAAI,QAAQ,SAAS,OAAO,QAAQ,QAAQ,WAAW,CAAC,CAAC;AAC1D,CAAC;AAEF,MAAa,OAAO,UACnB,OAAO,QAAQ,YAAY,KAAK,IAAI,UACnC,QACC,KACA,OAAO,YACN,MAAM,IAAI,QAAQ,SACjB,UAAU,SAAS,IAAI,IACnB,CAAC,MAAM,QAAQ,IACf,CAAC,KAAK,IAAI,KAAK,EAAE,CACtB,CACD,CACD,CACD;AAED,MAAa,UAAU,UACtB,QACC,KACA,OAAO,YAAY,MAAM,IAAI,QAAQ,SAAS,CAAC,MAAM,IAAI,CAAU,CAAC,CACrE"}
1
+ {"version":3,"file":"Manage.js","names":[],"sources":["../src/Manage.ts"],"sourcesContent":["/**\n * Creating projects and changing their dependencies.\n */\nimport * as Create from \"@graftland/create\"\nimport * as PackageManager from \"@graftland/package-manager\"\nimport {\n\tArray,\n\tConfig,\n\tEffect,\n\tOption,\n\tPredicate,\n\tRecord,\n\tSchema,\n} from \"effect\"\n\nconst report = (result: PackageManager.InstallResult) =>\n\tEffect.logInfo(\n\t\t`${result.agent}: ${result.added.length} added, ${result.updated.length} updated, ${result.removed.length} removed`,\n\t).pipe(\n\t\tEffect.andThen(\n\t\t\tOption.match(result.snapshotCleanupFailure, {\n\t\t\t\tonNone: () => Effect.void,\n\t\t\t\tonSome: ({ cause, snapshotDirectory }) =>\n\t\t\t\t\tEffect.logWarning(\n\t\t\t\t\t\t\"Installation committed; snapshot cleanup failed. Retry removing the retained snapshot.\",\n\t\t\t\t\t\tcause,\n\t\t\t\t\t\t{ snapshotDirectory },\n\t\t\t\t\t),\n\t\t\t}),\n\t\t),\n\t)\n\n/**\n * `name@range`, split at the last `@` so a scope's `@` stays in the name. A\n * bare name means the latest version.\n */\nconst Spec = Schema.Union([\n\tSchema.TemplateLiteralParser([\n\t\tPackageManager.PackageName,\n\t\t\"@\",\n\t\tSchema.String,\n\t]),\n\tPackageManager.PackageName,\n])\nconst decodeSpecs = Schema.decodeEffect(Schema.Array(Spec))\n\nconst install = Effect.fn(\"Manage.install\")(function* (\n\tdirectory: string,\n\tchanges: Readonly<Record<string, string | null>>,\n) {\n\tconst decoded = yield* Schema.decodeEffect(PackageManager.Changes)(changes)\n\t// Set by npm, pnpm and Yarn when they run graft, such as through `pnpm dlx`.\n\tconst userAgent = yield* Config.String(\"npm_config_user_agent\").pipe(\n\t\tConfig.withDefault(\"\"),\n\t)\n\tconst manager = yield* PackageManager.make({ directory, userAgent })\n\tconst result = yield* manager.install(decoded)\n\tyield* report(result)\n})\n\nexport const create = Effect.fn(\"Manage.create\")(function* (options: {\n\treadonly directory: string\n\treadonly name: Option.Option<string>\n\treadonly install: boolean\n}) {\n\tconst project = yield* Create.create({\n\t\tdirectory: options.directory,\n\t\t...Option.match(options.name, {\n\t\t\tonNone: () => ({}),\n\t\t\tonSome: (name) => ({ name }),\n\t\t}),\n\t})\n\tyield* Effect.logInfo(`Created ${project.name} in ${project.directory}`)\n\tif (options.install) yield* install(project.directory, {})\n})\n\nexport const add = Effect.fn(\"Manage.add\")(function* (\n\tspecs: ReadonlyArray<string>,\n) {\n\tconst decoded = yield* decodeSpecs(specs)\n\tyield* install(\n\t\t\".\",\n\t\tRecord.fromEntries(\n\t\t\tArray.map(decoded, (spec) =>\n\t\t\t\tPredicate.isString(spec)\n\t\t\t\t\t? ([spec, \"latest\"] as const)\n\t\t\t\t\t: ([spec[0], spec[2]] as const),\n\t\t\t),\n\t\t),\n\t)\n})\n\nexport const remove = (names: ReadonlyArray<string>) =>\n\tinstall(\n\t\t\".\",\n\t\tRecord.fromEntries(Array.map(names, (name) => [name, null] as const)),\n\t)\n"],"mappings":";;;;;;;;;;;;;AAeA,MAAM,UAAU,WACf,OAAO,QACN,GAAG,OAAO,MAAM,IAAI,OAAO,MAAM,OAAO,UAAU,OAAO,QAAQ,OAAO,YAAY,OAAO,QAAQ,OAAO,SAC3G,CAAC,CAAC,KACD,OAAO,QACN,OAAO,MAAM,OAAO,wBAAwB;CAC3C,cAAc,OAAO;CACrB,SAAS,EAAE,OAAO,wBACjB,OAAO,WACN,0FACA,OACA,EAAE,kBAAkB,CACrB;AACF,CAAC,CACF,CACD;;;;;AAMD,MAAM,OAAO,OAAO,MAAM,CACzB,OAAO,sBAAsB;CAC5B,eAAe;CACf;CACA,OAAO;AACR,CAAC,GACD,eAAe,WAChB,CAAC;AACD,MAAM,cAAc,OAAO,aAAa,OAAO,MAAM,IAAI,CAAC;AAE1D,MAAM,UAAU,OAAO,GAAG,gBAAgB,CAAC,CAAC,WAC3C,WACA,SACC;CACD,MAAM,UAAU,OAAO,OAAO,aAAa,eAAe,OAAO,CAAC,CAAC,OAAO;CAE1E,MAAM,YAAY,OAAO,OAAO,OAAO,uBAAuB,CAAC,CAAC,KAC/D,OAAO,YAAY,EAAE,CACtB;CAEA,MAAM,SAAS,QAAO,OADC,eAAe,KAAK;EAAE;EAAW;CAAU,CAAC,EAAA,CACrC,QAAQ,OAAO;CAC7C,OAAO,OAAO,MAAM;AACrB,CAAC;AAED,MAAa,SAAS,OAAO,GAAG,eAAe,CAAC,CAAC,WAAW,SAIzD;CACF,MAAM,UAAU,OAAO,OAAO,OAAO;EACpC,WAAW,QAAQ;EACnB,GAAG,OAAO,MAAM,QAAQ,MAAM;GAC7B,eAAe,CAAC;GAChB,SAAS,UAAU,EAAE,KAAK;EAC3B,CAAC;CACF,CAAC;CACD,OAAO,OAAO,QAAQ,WAAW,QAAQ,KAAK,MAAM,QAAQ,WAAW;CACvE,IAAI,QAAQ,SAAS,OAAO,QAAQ,QAAQ,WAAW,CAAC,CAAC;AAC1D,CAAC;AAED,MAAa,MAAM,OAAO,GAAG,YAAY,CAAC,CAAC,WAC1C,OACC;CACD,MAAM,UAAU,OAAO,YAAY,KAAK;CACxC,OAAO,QACN,KACA,OAAO,YACN,MAAM,IAAI,UAAU,SACnB,UAAU,SAAS,IAAI,IACnB,CAAC,MAAM,QAAQ,IACf,CAAC,KAAK,IAAI,KAAK,EAAE,CACtB,CACD,CACD;AACD,CAAC;AAED,MAAa,UAAU,UACtB,QACC,KACA,OAAO,YAAY,MAAM,IAAI,QAAQ,SAAS,CAAC,MAAM,IAAI,CAAU,CAAC,CACrE"}
@@ -1 +1 @@
1
- {"version":3,"file":"Start.d.ts","names":[],"sources":["../src/Start.ts"],"mappings":";;;;;;iBAciB;;WAEP;;WAEA;;;;;WAKA;;;;;;;qBAQG,MAAG,SACN,YACP,OAAO,cAEP,MAAM,kBACN,cAAc,cACd,cAAc,gBACd,OAAO,cACP,OAAO,aACP,KAAK"}
1
+ {"version":3,"file":"Start.d.ts","names":[],"sources":["../src/Start.ts"],"mappings":";;;;;;iBAciB;;WAEP;;WAEA;;;;;WAKA;;;;;;;qBAQG,MACZ,SAAS,YACL,OAAO,cAET,MAAM,kBACN,cAAc,cACd,cAAc,gBACd,OAAO,cACP,OAAO,aACP,KAAK"}
package/dist/Start.js CHANGED
@@ -18,7 +18,7 @@ var Start_exports = /* @__PURE__ */ __exportAll({ run: () => run });
18
18
  * modules are imported through the hot reloader, so bare specifiers resolve
19
19
  * from the application directory whether or not `watch` is on.
20
20
  */
21
- const run = (options) => Effect.gen(function* () {
21
+ const run = Effect.fn("Start.run")(function* (options) {
22
22
  const path = yield* Path.Path;
23
23
  const file = path.resolve(options.config);
24
24
  const directory = path.dirname(file);
@@ -50,7 +50,7 @@ const run = (options) => Effect.gen(function* () {
50
50
  FileSystem.FileSystem,
51
51
  Path.Path
52
52
  ],
53
- apply: () => Effect.gen(function* () {
53
+ apply: Effect.fnUntraced(function* () {
54
54
  const loader = yield* Loader.Loader;
55
55
  yield* watchModules(hmr, directory, loader.retry).pipe(Effect.forkScoped);
56
56
  if (envFile !== void 0) yield* watchEnvFile(envFile, reload, loader.retry).pipe(Effect.forkScoped);
@@ -60,7 +60,7 @@ const run = (options) => Effect.gen(function* () {
60
60
  }
61
61
  yield* Effect.logInfo(`Running ${path.relative(process.cwd(), file) || file}`);
62
62
  return yield* Effect.never;
63
- }).pipe(Effect.scoped, Effect.provide(Layer.provideMerge(Graft.layer, Layer.mergeAll(SecureRandom.layerWebCrypto, NodeServices.layer, layer$1, layer(options.envFile).pipe(Layer.provide(NodeServices.layer)), Logger.layer([Logger.consolePretty()])))));
63
+ }, Effect.scoped, (effect, options) => Effect.provide(effect, Layer.provideMerge(Graft.layer, Layer.mergeAll(SecureRandom.layerWebCrypto, NodeServices.layer, layer$1, layer(options.envFile).pipe(Layer.provide(NodeServices.layer)), Logger.layer([Logger.consolePretty()])))));
64
64
  export { Start_exports, run };
65
65
 
66
66
  //# sourceMappingURL=Start.js.map
package/dist/Start.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"Start.js","names":["intercepts","env"],"sources":["../src/Start.ts"],"sourcesContent":["/**\n * Runs a Graft application from its config file.\n */\nimport { NodeServices } from \"@effect/platform-node\"\nimport { SecureRandom } from \"effect/crypto\"\nimport { Graft, Plugin } from \"@graftland/graft\"\nimport * as Hmr from \"@graftland/hmr\"\nimport { Include, Loader, ModuleResolver, type Tree } from \"@graftland/loader\"\nimport type { PlatformError, Schema } from \"effect\"\nimport { Effect, FileSystem, Layer, Logger, Path } from \"effect\"\nimport { EnvFile, layer as env, watchEnvFile } from \"./internal/env\"\nimport { layer as intercepts } from \"./internal/intercepts\"\nimport { watchModules } from \"./internal/watch\"\n\nexport interface Options {\n\t/** Path of the config file. */\n\treadonly config: string\n\t/** Reload plugin modules and the config file when they change. */\n\treadonly watch: boolean\n\t/**\n\t * A `.env` file that `!env` reads when the process environment does not set\n\t * a variable. With `watch`, changes to it are read again.\n\t */\n\treadonly envFile?: string | undefined\n}\n\n/**\n * Loads the entries of the config file and runs until interrupted. Plugin\n * modules are imported through the hot reloader, so bare specifiers resolve\n * from the application directory whether or not `watch` is on.\n */\nexport const run = (\n\toptions: Options,\n): Effect.Effect<\n\tnever,\n\t| Graft.ServiceConflict\n\t| PlatformError.BadArgument\n\t| PlatformError.PlatformError\n\t| Schema.SchemaError\n\t| Loader.StoreError\n\t| Tree.InvalidOperation\n> =>\n\tEffect.gen(function* () {\n\t\tconst path = yield* Path.Path\n\t\tconst file = path.resolve(options.config)\n\t\tconst directory = path.dirname(file)\n\t\tconst base = yield* path.toFileUrl(file)\n\t\tconst hmr = yield* Hmr.make({ base: base.href })\n\t\tconst resolver = yield* ModuleResolver.make({\n\t\t\tbase: directory,\n\t\t\timporter: hmr.importerScoped,\n\t\t})\n\t\tconst modules = yield* ModuleResolver.make({\n\t\t\tbase: directory,\n\t\t\timporter: hmr.importModule,\n\t\t})\n\t\tyield* Graft.provide(ModuleResolver.ModuleResolver, {\n\t\t\tresolve: resolver.resolve,\n\t\t\timportModule: modules.importModule,\n\t\t})\n\t\tconst included = yield* Graft.plugin(Include.Include, {\n\t\t\tpath: file,\n\t\t\twatch: options.watch,\n\t\t})\n\t\tyield* included.await\n\t\tif (options.watch) {\n\t\t\tconst { reload } = yield* EnvFile\n\t\t\tconst envFile = options.envFile\n\t\t\tconst reloader = Plugin.make({\n\t\t\t\tname: \"reloader\",\n\t\t\t\tinject: [Loader.Loader, FileSystem.FileSystem, Path.Path],\n\t\t\t\tapply: () =>\n\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\tconst loader = yield* Loader.Loader\n\t\t\t\t\t\tyield* watchModules(hmr, directory, loader.retry).pipe(\n\t\t\t\t\t\t\tEffect.forkScoped,\n\t\t\t\t\t\t)\n\t\t\t\t\t\tif (envFile !== undefined)\n\t\t\t\t\t\t\tyield* watchEnvFile(envFile, reload, loader.retry).pipe(\n\t\t\t\t\t\t\t\tEffect.forkScoped,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t}),\n\t\t\t})\n\t\t\tyield* Graft.plugin(reloader)\n\t\t}\n\n\t\tyield* Effect.logInfo(\n\t\t\t`Running ${path.relative(process.cwd(), file) || file}`,\n\t\t)\n\t\treturn yield* Effect.never\n\t}).pipe(\n\t\tEffect.scoped,\n\t\t// @effect-diagnostics-next-line strictEffectProvide:off -- this runs the whole application, so it is an entry point\n\t\tEffect.provide(\n\t\t\tLayer.provideMerge(\n\t\t\t\tGraft.layer,\n\t\t\t\tLayer.mergeAll(\n\t\t\t\t\tSecureRandom.layerWebCrypto,\n\t\t\t\t\tNodeServices.layer,\n\t\t\t\t\tintercepts,\n\t\t\t\t\tenv(options.envFile).pipe(Layer.provide(NodeServices.layer)),\n\t\t\t\t\tLogger.layer([Logger.consolePretty()]),\n\t\t\t\t),\n\t\t\t),\n\t\t),\n\t)\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA+BA,MAAa,OACZ,YAUA,OAAO,IAAI,aAAa;CACvB,MAAM,OAAO,OAAO,KAAK;CACzB,MAAM,OAAO,KAAK,QAAQ,QAAQ,MAAM;CACxC,MAAM,YAAY,KAAK,QAAQ,IAAI;CACnC,MAAM,OAAO,OAAO,KAAK,UAAU,IAAI;CACvC,MAAM,MAAM,OAAO,IAAI,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC;CAC/C,MAAM,WAAW,OAAO,eAAe,KAAK;EAC3C,MAAM;EACN,UAAU,IAAI;CACf,CAAC;CACD,MAAM,UAAU,OAAO,eAAe,KAAK;EAC1C,MAAM;EACN,UAAU,IAAI;CACf,CAAC;CACD,OAAO,MAAM,QAAQ,eAAe,gBAAgB;EACnD,SAAS,SAAS;EAClB,cAAc,QAAQ;CACvB,CAAC;CAKD,QAAO,OAJiB,MAAM,OAAO,QAAQ,SAAS;EACrD,MAAM;EACN,OAAO,QAAQ;CAChB,CAAC,EAAA,CACe;CAChB,IAAI,QAAQ,OAAO;EAClB,MAAM,EAAE,WAAW,OAAO;EAC1B,MAAM,UAAU,QAAQ;EACxB,MAAM,WAAW,OAAO,KAAK;GAC5B,MAAM;GACN,QAAQ;IAAC,OAAO;IAAQ,WAAW;IAAY,KAAK;GAAI;GACxD,aACC,OAAO,IAAI,aAAa;IACvB,MAAM,SAAS,OAAO,OAAO;IAC7B,OAAO,aAAa,KAAK,WAAW,OAAO,KAAK,CAAC,CAAC,KACjD,OAAO,UACR;IACA,IAAI,YAAY,KAAA,GACf,OAAO,aAAa,SAAS,QAAQ,OAAO,KAAK,CAAC,CAAC,KAClD,OAAO,UACR;GACF,CAAC;EACH,CAAC;EACD,OAAO,MAAM,OAAO,QAAQ;CAC7B;CAEA,OAAO,OAAO,QACb,WAAW,KAAK,SAAS,QAAQ,IAAI,GAAG,IAAI,KAAK,MAClD;CACA,OAAO,OAAO,OAAO;AACtB,CAAC,CAAC,CAAC,KACF,OAAO,QAEP,OAAO,QACN,MAAM,aACL,MAAM,OACN,MAAM,SACL,aAAa,gBACb,aAAa,OACbA,SACAC,MAAI,QAAQ,OAAO,CAAC,CAAC,KAAK,MAAM,QAAQ,aAAa,KAAK,CAAC,GAC3D,OAAO,MAAM,CAAC,OAAO,cAAc,CAAC,CAAC,CACtC,CACD,CACD,CACD"}
1
+ {"version":3,"file":"Start.js","names":["intercepts","env"],"sources":["../src/Start.ts"],"sourcesContent":["/**\n * Runs a Graft application from its config file.\n */\nimport { NodeServices } from \"@effect/platform-node\"\nimport { SecureRandom } from \"effect/crypto\"\nimport { Graft, Plugin } from \"@graftland/graft\"\nimport * as Hmr from \"@graftland/hmr\"\nimport { Include, Loader, ModuleResolver, type Tree } from \"@graftland/loader\"\nimport type { PlatformError, Schema } from \"effect\"\nimport { Effect, FileSystem, Layer, Logger, Path } from \"effect\"\nimport { EnvFile, layer as env, watchEnvFile } from \"./internal/env\"\nimport { layer as intercepts } from \"./internal/intercepts\"\nimport { watchModules } from \"./internal/watch\"\n\nexport interface Options {\n\t/** Path of the config file. */\n\treadonly config: string\n\t/** Reload plugin modules and the config file when they change. */\n\treadonly watch: boolean\n\t/**\n\t * A `.env` file that `!env` reads when the process environment does not set\n\t * a variable. With `watch`, changes to it are read again.\n\t */\n\treadonly envFile?: string | undefined\n}\n\n/**\n * Loads the entries of the config file and runs until interrupted. Plugin\n * modules are imported through the hot reloader, so bare specifiers resolve\n * from the application directory whether or not `watch` is on.\n */\nexport const run: (\n\toptions: Options,\n) => Effect.Effect<\n\tnever,\n\t| Graft.ServiceConflict\n\t| PlatformError.BadArgument\n\t| PlatformError.PlatformError\n\t| Schema.SchemaError\n\t| Loader.StoreError\n\t| Tree.InvalidOperation\n> = Effect.fn(\"Start.run\")(\n\tfunction* (options: Options) {\n\t\tconst path = yield* Path.Path\n\t\tconst file = path.resolve(options.config)\n\t\tconst directory = path.dirname(file)\n\t\tconst base = yield* path.toFileUrl(file)\n\t\tconst hmr = yield* Hmr.make({ base: base.href })\n\t\tconst resolver = yield* ModuleResolver.make({\n\t\t\tbase: directory,\n\t\t\timporter: hmr.importerScoped,\n\t\t})\n\t\tconst modules = yield* ModuleResolver.make({\n\t\t\tbase: directory,\n\t\t\timporter: hmr.importModule,\n\t\t})\n\t\tyield* Graft.provide(ModuleResolver.ModuleResolver, {\n\t\t\tresolve: resolver.resolve,\n\t\t\timportModule: modules.importModule,\n\t\t})\n\t\tconst included = yield* Graft.plugin(Include.Include, {\n\t\t\tpath: file,\n\t\t\twatch: options.watch,\n\t\t})\n\t\tyield* included.await\n\t\tif (options.watch) {\n\t\t\tconst { reload } = yield* EnvFile\n\t\t\tconst envFile = options.envFile\n\t\t\tconst reloader = Plugin.make({\n\t\t\t\tname: \"reloader\",\n\t\t\t\tinject: [Loader.Loader, FileSystem.FileSystem, Path.Path],\n\t\t\t\tapply: Effect.fnUntraced(function* () {\n\t\t\t\t\tconst loader = yield* Loader.Loader\n\t\t\t\t\tyield* watchModules(hmr, directory, loader.retry).pipe(\n\t\t\t\t\t\tEffect.forkScoped,\n\t\t\t\t\t)\n\t\t\t\t\tif (envFile !== undefined)\n\t\t\t\t\t\tyield* watchEnvFile(envFile, reload, loader.retry).pipe(\n\t\t\t\t\t\t\tEffect.forkScoped,\n\t\t\t\t\t\t)\n\t\t\t\t}),\n\t\t\t})\n\t\t\tyield* Graft.plugin(reloader)\n\t\t}\n\n\t\tyield* Effect.logInfo(\n\t\t\t`Running ${path.relative(process.cwd(), file) || file}`,\n\t\t)\n\t\treturn yield* Effect.never\n\t},\n\tEffect.scoped,\n\t(effect, options) =>\n\t\t// @effect-diagnostics-next-line strictEffectProvide:off -- this runs the whole application, so it is an entry point\n\t\tEffect.provide(\n\t\t\teffect,\n\t\t\tLayer.provideMerge(\n\t\t\t\tGraft.layer,\n\t\t\t\tLayer.mergeAll(\n\t\t\t\t\tSecureRandom.layerWebCrypto,\n\t\t\t\t\tNodeServices.layer,\n\t\t\t\t\tintercepts,\n\t\t\t\t\tenv(options.envFile).pipe(Layer.provide(NodeServices.layer)),\n\t\t\t\t\tLogger.layer([Logger.consolePretty()]),\n\t\t\t\t),\n\t\t\t),\n\t\t),\n)\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA+BA,MAAa,MAUT,OAAO,GAAG,WAAW,CAAC,CACzB,WAAW,SAAkB;CAC5B,MAAM,OAAO,OAAO,KAAK;CACzB,MAAM,OAAO,KAAK,QAAQ,QAAQ,MAAM;CACxC,MAAM,YAAY,KAAK,QAAQ,IAAI;CACnC,MAAM,OAAO,OAAO,KAAK,UAAU,IAAI;CACvC,MAAM,MAAM,OAAO,IAAI,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC;CAC/C,MAAM,WAAW,OAAO,eAAe,KAAK;EAC3C,MAAM;EACN,UAAU,IAAI;CACf,CAAC;CACD,MAAM,UAAU,OAAO,eAAe,KAAK;EAC1C,MAAM;EACN,UAAU,IAAI;CACf,CAAC;CACD,OAAO,MAAM,QAAQ,eAAe,gBAAgB;EACnD,SAAS,SAAS;EAClB,cAAc,QAAQ;CACvB,CAAC;CAKD,QAAO,OAJiB,MAAM,OAAO,QAAQ,SAAS;EACrD,MAAM;EACN,OAAO,QAAQ;CAChB,CAAC,EAAA,CACe;CAChB,IAAI,QAAQ,OAAO;EAClB,MAAM,EAAE,WAAW,OAAO;EAC1B,MAAM,UAAU,QAAQ;EACxB,MAAM,WAAW,OAAO,KAAK;GAC5B,MAAM;GACN,QAAQ;IAAC,OAAO;IAAQ,WAAW;IAAY,KAAK;GAAI;GACxD,OAAO,OAAO,WAAW,aAAa;IACrC,MAAM,SAAS,OAAO,OAAO;IAC7B,OAAO,aAAa,KAAK,WAAW,OAAO,KAAK,CAAC,CAAC,KACjD,OAAO,UACR;IACA,IAAI,YAAY,KAAA,GACf,OAAO,aAAa,SAAS,QAAQ,OAAO,KAAK,CAAC,CAAC,KAClD,OAAO,UACR;GACF,CAAC;EACF,CAAC;EACD,OAAO,MAAM,OAAO,QAAQ;CAC7B;CAEA,OAAO,OAAO,QACb,WAAW,KAAK,SAAS,QAAQ,IAAI,GAAG,IAAI,KAAK,MAClD;CACA,OAAO,OAAO,OAAO;AACtB,GACA,OAAO,SACN,QAAQ,YAER,OAAO,QACN,QACA,MAAM,aACL,MAAM,OACN,MAAM,SACL,aAAa,gBACb,aAAa,OACbA,SACAC,MAAI,QAAQ,OAAO,CAAC,CAAC,KAAK,MAAM,QAAQ,aAAa,KAAK,CAAC,GAC3D,OAAO,MAAM,CAAC,OAAO,cAAc,CAAC,CAAC,CACtC,CACD,CACD,CACF"}
@@ -39,7 +39,7 @@ const logStopped = (file) => (exit) => Exit.isFailure(exit) && Cause.hasInterrup
39
39
  * Reloads the file when it changes and retries the loader's entries, which
40
40
  * reads `!env` again. A file that cannot be read keeps its previous values.
41
41
  */
42
- const watchEnvFile = (file, reload, retry) => Effect.gen(function* () {
42
+ const watchEnvFile = Effect.fnUntraced(function* (file, reload, retry) {
43
43
  const fs = yield* FileSystem.FileSystem;
44
44
  const path = yield* Path.Path;
45
45
  const ready = yield* Deferred.make();
@@ -48,8 +48,8 @@ const watchEnvFile = (file, reload, retry) => Effect.gen(function* () {
48
48
  Stream.as(events, void 0),
49
49
  Stream.fromEffect(Deferred.await(ready)),
50
50
  Stream.tick("1 second").pipe(Stream.drop(1))
51
- ], { concurrency: "unbounded" }).pipe(Stream.runForEach(() => Effect.sleep("100 millis").pipe(Effect.andThen(reload), Effect.flatMap((changed) => changed ? retry : Effect.void), Effect.catchCauseIf((cause) => !Cause.hasInterrupts(cause), (cause) => Effect.logError(`Reloading ${file} failed`, cause)))), Effect.catchCauseIf((cause) => !Cause.hasInterrupts(cause), (cause) => Effect.logError(`Stopped watching ${file}`, cause)));
52
- }).pipe(Effect.onExit(logStopped(file)));
51
+ ], { concurrency: "unbounded" }).pipe(Stream.runForEach(() => Effect.sleep("100 millis").pipe(Effect.andThen(reload), Effect.flatMap((changed) => changed ? retry : Effect.void), Effect.catchCauseIf((cause) => !Cause.hasInterrupts(cause), (cause) => Effect.logError(`Reloading ${file} failed`, cause)))), Effect.catchCauseIf((cause) => !Cause.hasInterrupts(cause), (cause) => Effect.logError(`Stopped watching ${file}`, cause)), Effect.onExit(logStopped(file)));
52
+ });
53
53
  export { EnvFile, layer, watchEnvFile };
54
54
 
55
55
  //# sourceMappingURL=env.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"env.js","names":[],"sources":["../../src/internal/env.ts"],"sourcesContent":["/**\n * Where `!env` values come from: the process environment, then an optional\n * `.env` file that watch mode reads again when it changes.\n */\nimport { observeShutdown } from \"./observeShutdown\"\nimport type { PlatformError } from \"effect\"\nimport {\n\tCause,\n\tConfigProvider,\n\tContext,\n\tDeferred,\n\tEffect,\n\tExit,\n\tFileSystem,\n\tLayer,\n\tPath,\n\tQueue,\n\tRef,\n\tStream,\n} from \"effect\"\n\n// An empty variable is still set: it overrides the file, and the plugin's\n// schema decides whether it is valid.\nconst options = { preserveEmptyStrings: true } as const\n\ninterface Loaded {\n\treadonly target: string\n\treadonly text: string\n\treadonly provider: ConfigProvider.ConfigProvider\n}\n\nexport class EnvFile extends Context.Service<\n\tEnvFile,\n\t{\n\t\t/** Reads the file again; reports whether its contents changed. */\n\t\treadonly reload: Effect.Effect<boolean, PlatformError.PlatformError>\n\t}\n>()(\"@graftland/cli/internal/env/EnvFile\") {}\n\n/** Provides `ConfigProvider` and `EnvFile`; a missing file fails the layer. */\nexport const layer = (file: string | undefined) =>\n\tLayer.unwrap(\n\t\tEffect.gen(function* () {\n\t\t\tconst environment = ConfigProvider.fromEnv(options)\n\t\t\tif (file === undefined)\n\t\t\t\treturn Layer.merge(\n\t\t\t\t\tConfigProvider.layer(environment),\n\t\t\t\t\tLayer.succeed(EnvFile, { reload: Effect.succeed(false) }),\n\t\t\t\t)\n\t\t\tconst fs = yield* FileSystem.FileSystem\n\t\t\t// Resolve the request on every read, including polling. Never pin a\n\t\t\t// symlink to its initial target. The provider and its baseline are\n\t\t\t// committed together from the bytes actually read.\n\t\t\tconst read = Effect.gen(function* () {\n\t\t\t\tconst target = yield* fs.realPath(file)\n\t\t\t\tconst text = yield* fs.readFileString(target)\n\t\t\t\treturn {\n\t\t\t\t\ttarget,\n\t\t\t\t\ttext,\n\t\t\t\t\tprovider: ConfigProvider.fromDotEnvContents(text, options),\n\t\t\t\t}\n\t\t\t})\n\t\t\tconst initial: Loaded = yield* read\n\t\t\tconst loaded = yield* Ref.make(initial)\n\t\t\tconst fromFile = ConfigProvider.make((path) =>\n\t\t\t\tEffect.flatMap(Ref.get(loaded), ({ provider }) => provider.load(path)),\n\t\t\t)\n\t\t\treturn Layer.merge(\n\t\t\t\tConfigProvider.layer(ConfigProvider.orElse(environment, fromFile)),\n\t\t\t\tLayer.succeed(EnvFile, {\n\t\t\t\t\treload: Effect.gen(function* () {\n\t\t\t\t\t\tconst next = yield* read\n\t\t\t\t\t\tconst previous = yield* Ref.getAndSet(loaded, next)\n\t\t\t\t\t\treturn next.text !== previous.text\n\t\t\t\t\t}),\n\t\t\t\t}),\n\t\t\t)\n\t\t}),\n\t)\n\n/**\n * The handlers below let interruption through, and with it any failure raised\n * while stopping. Log those; failures without interruption are handled there.\n */\nconst logStopped =\n\t(file: string) =>\n\t<A, E>(exit: Exit.Exit<A, E>) =>\n\t\tExit.isFailure(exit) &&\n\t\tCause.hasInterrupts(exit.cause) &&\n\t\t(Cause.hasFails(exit.cause) || Cause.hasDies(exit.cause))\n\t\t\t? Effect.logError(`Watching ${file} stopped with a failure`, exit.cause)\n\t\t\t: Effect.void\n\n/**\n * Reloads the file when it changes and retries the loader's entries, which\n * reads `!env` again. A file that cannot be read keeps its previous values.\n */\nexport const watchEnvFile = <E, R>(\n\tfile: string,\n\treload: Effect.Effect<boolean, PlatformError.PlatformError>,\n\tretry: Effect.Effect<void, E, R>,\n) =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem\n\t\tconst path = yield* Path.Path\n\t\tconst ready = yield* Deferred.make<void>()\n\t\t// As in HMR, own the source scope inside the producer. Observe its\n\t\t// complete Exit after cleanup, before merge can discard it.\n\t\tconst events = Stream.callback<\n\t\t\tFileSystem.WatchEvent,\n\t\t\tPlatformError.PlatformError\n\t\t>(\n\t\t\t(queue) =>\n\t\t\t\tfs\n\t\t\t\t\t.watch(path.dirname(file), {\n\t\t\t\t\t\tonReady: () => Deferred.doneUnsafe(ready, Effect.void),\n\t\t\t\t\t})\n\t\t\t\t\t.pipe(\n\t\t\t\t\t\tStream.filter(\n\t\t\t\t\t\t\t(event) => path.basename(event.path) === path.basename(file),\n\t\t\t\t\t\t),\n\t\t\t\t\t\tStream.runForEachArray((events) => Queue.offerAll(queue, events)),\n\t\t\t\t\t\tEffect.scoped,\n\t\t\t\t\t\t(effect) =>\n\t\t\t\t\t\t\tobserveShutdown(\n\t\t\t\t\t\t\t\teffect,\n\t\t\t\t\t\t\t\t`Watching ${file} stopped with interrupted cleanup`,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\tEffect.onExit(logStopped(file)),\n\t\t\t\t\t\tEffect.catchCauseIf(\n\t\t\t\t\t\t\t(cause) => !Cause.hasInterrupts(cause),\n\t\t\t\t\t\t\t(cause) => Effect.logError(`Watching ${file} failed`, cause),\n\t\t\t\t\t\t),\n\t\t\t\t\t\t// A failed native backend must not stop content reconciliation.\n\t\t\t\t\t\tEffect.andThen(Effect.sleep(\"1 second\")),\n\t\t\t\t\t\tEffect.forever,\n\t\t\t\t\t),\n\t\t\t{ bufferSize: 16 },\n\t\t)\n\t\t// Native events accelerate updates. Poll the request too: its target\n\t\t// may live outside this directory, change, or lose a notification.\n\t\tyield* Stream.mergeAll(\n\t\t\t[\n\t\t\t\tStream.as(events, undefined),\n\t\t\t\tStream.fromEffect(Deferred.await(ready)),\n\t\t\t\tStream.tick(\"1 second\").pipe(Stream.drop(1)),\n\t\t\t],\n\t\t\t{ concurrency: \"unbounded\" },\n\t\t).pipe(\n\t\t\tStream.runForEach(() =>\n\t\t\t\t// Delay to let writes settle.\n\t\t\t\tEffect.sleep(\"100 millis\").pipe(\n\t\t\t\t\tEffect.andThen(reload),\n\t\t\t\t\tEffect.flatMap((changed) => (changed ? retry : Effect.void)),\n\t\t\t\t\tEffect.catchCauseIf(\n\t\t\t\t\t\t(cause) => !Cause.hasInterrupts(cause),\n\t\t\t\t\t\t(cause) => Effect.logError(`Reloading ${file} failed`, cause),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t),\n\t\t\tEffect.catchCauseIf(\n\t\t\t\t(cause) => !Cause.hasInterrupts(cause),\n\t\t\t\t(cause) => Effect.logError(`Stopped watching ${file}`, cause),\n\t\t\t),\n\t\t)\n\t}).pipe(\n\t\t// Observe the whole watcher before forkScoped owns it.\n\t\tEffect.onExit(logStopped(file)),\n\t)\n"],"mappings":"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"}
1
+ {"version":3,"file":"env.js","names":[],"sources":["../../src/internal/env.ts"],"sourcesContent":["/**\n * Where `!env` values come from: the process environment, then an optional\n * `.env` file that watch mode reads again when it changes.\n */\nimport { observeShutdown } from \"./observeShutdown\"\nimport type { PlatformError } from \"effect\"\nimport {\n\tCause,\n\tConfigProvider,\n\tContext,\n\tDeferred,\n\tEffect,\n\tExit,\n\tFileSystem,\n\tLayer,\n\tPath,\n\tQueue,\n\tRef,\n\tStream,\n} from \"effect\"\n\n// An empty variable is still set: it overrides the file, and the plugin's\n// schema decides whether it is valid.\nconst options = { preserveEmptyStrings: true } as const\n\ninterface Loaded {\n\treadonly target: string\n\treadonly text: string\n\treadonly provider: ConfigProvider.ConfigProvider\n}\n\nexport class EnvFile extends Context.Service<\n\tEnvFile,\n\t{\n\t\t/** Reads the file again; reports whether its contents changed. */\n\t\treadonly reload: Effect.Effect<boolean, PlatformError.PlatformError>\n\t}\n>()(\"@graftland/cli/internal/env/EnvFile\") {}\n\n/** Provides `ConfigProvider` and `EnvFile`; a missing file fails the layer. */\nexport const layer = (file: string | undefined) =>\n\tLayer.unwrap(\n\t\tEffect.gen(function* () {\n\t\t\tconst environment = ConfigProvider.fromEnv(options)\n\t\t\tif (file === undefined)\n\t\t\t\treturn Layer.merge(\n\t\t\t\t\tConfigProvider.layer(environment),\n\t\t\t\t\tLayer.succeed(EnvFile, { reload: Effect.succeed(false) }),\n\t\t\t\t)\n\t\t\tconst fs = yield* FileSystem.FileSystem\n\t\t\t// Resolve the request on every read, including polling. Never pin a\n\t\t\t// symlink to its initial target. The provider and its baseline are\n\t\t\t// committed together from the bytes actually read.\n\t\t\tconst read = Effect.gen(function* () {\n\t\t\t\tconst target = yield* fs.realPath(file)\n\t\t\t\tconst text = yield* fs.readFileString(target)\n\t\t\t\treturn {\n\t\t\t\t\ttarget,\n\t\t\t\t\ttext,\n\t\t\t\t\tprovider: ConfigProvider.fromDotEnvContents(text, options),\n\t\t\t\t}\n\t\t\t})\n\t\t\tconst initial: Loaded = yield* read\n\t\t\tconst loaded = yield* Ref.make(initial)\n\t\t\tconst fromFile = ConfigProvider.make((path) =>\n\t\t\t\tEffect.flatMap(Ref.get(loaded), ({ provider }) => provider.load(path)),\n\t\t\t)\n\t\t\treturn Layer.merge(\n\t\t\t\tConfigProvider.layer(ConfigProvider.orElse(environment, fromFile)),\n\t\t\t\tLayer.succeed(EnvFile, {\n\t\t\t\t\treload: Effect.gen(function* () {\n\t\t\t\t\t\tconst next = yield* read\n\t\t\t\t\t\tconst previous = yield* Ref.getAndSet(loaded, next)\n\t\t\t\t\t\treturn next.text !== previous.text\n\t\t\t\t\t}),\n\t\t\t\t}),\n\t\t\t)\n\t\t}),\n\t)\n\n/**\n * The handlers below let interruption through, and with it any failure raised\n * while stopping. Log those; failures without interruption are handled there.\n */\nconst logStopped =\n\t(file: string) =>\n\t<A, E>(exit: Exit.Exit<A, E>) =>\n\t\tExit.isFailure(exit) &&\n\t\tCause.hasInterrupts(exit.cause) &&\n\t\t(Cause.hasFails(exit.cause) || Cause.hasDies(exit.cause))\n\t\t\t? Effect.logError(`Watching ${file} stopped with a failure`, exit.cause)\n\t\t\t: Effect.void\n\n/**\n * Reloads the file when it changes and retries the loader's entries, which\n * reads `!env` again. A file that cannot be read keeps its previous values.\n */\nexport const watchEnvFile = Effect.fnUntraced(function* <E, R>(\n\tfile: string,\n\treload: Effect.Effect<boolean, PlatformError.PlatformError>,\n\tretry: Effect.Effect<void, E, R>,\n) {\n\tconst fs = yield* FileSystem.FileSystem\n\tconst path = yield* Path.Path\n\tconst ready = yield* Deferred.make<void>()\n\t// As in HMR, own the source scope inside the producer. Observe its\n\t// complete Exit after cleanup, before merge can discard it.\n\tconst events = Stream.callback<\n\t\tFileSystem.WatchEvent,\n\t\tPlatformError.PlatformError\n\t>(\n\t\t(queue) =>\n\t\t\tfs\n\t\t\t\t.watch(path.dirname(file), {\n\t\t\t\t\tonReady: () => Deferred.doneUnsafe(ready, Effect.void),\n\t\t\t\t})\n\t\t\t\t.pipe(\n\t\t\t\t\tStream.filter(\n\t\t\t\t\t\t(event) => path.basename(event.path) === path.basename(file),\n\t\t\t\t\t),\n\t\t\t\t\tStream.runForEachArray((events) => Queue.offerAll(queue, events)),\n\t\t\t\t\tEffect.scoped,\n\t\t\t\t\t(effect) =>\n\t\t\t\t\t\tobserveShutdown(\n\t\t\t\t\t\t\teffect,\n\t\t\t\t\t\t\t`Watching ${file} stopped with interrupted cleanup`,\n\t\t\t\t\t\t),\n\t\t\t\t\tEffect.onExit(logStopped(file)),\n\t\t\t\t\tEffect.catchCauseIf(\n\t\t\t\t\t\t(cause) => !Cause.hasInterrupts(cause),\n\t\t\t\t\t\t(cause) => Effect.logError(`Watching ${file} failed`, cause),\n\t\t\t\t\t),\n\t\t\t\t\t// A failed native backend must not stop content reconciliation.\n\t\t\t\t\tEffect.andThen(Effect.sleep(\"1 second\")),\n\t\t\t\t\tEffect.forever,\n\t\t\t\t),\n\t\t{ bufferSize: 16 },\n\t)\n\t// Native events accelerate updates. Poll the request too: its target\n\t// may live outside this directory, change, or lose a notification.\n\tyield* Stream.mergeAll(\n\t\t[\n\t\t\tStream.as(events, undefined),\n\t\t\tStream.fromEffect(Deferred.await(ready)),\n\t\t\tStream.tick(\"1 second\").pipe(Stream.drop(1)),\n\t\t],\n\t\t{ concurrency: \"unbounded\" },\n\t).pipe(\n\t\tStream.runForEach(() =>\n\t\t\t// Delay to let writes settle.\n\t\t\tEffect.sleep(\"100 millis\").pipe(\n\t\t\t\tEffect.andThen(reload),\n\t\t\t\tEffect.flatMap((changed) => (changed ? retry : Effect.void)),\n\t\t\t\tEffect.catchCauseIf(\n\t\t\t\t\t(cause) => !Cause.hasInterrupts(cause),\n\t\t\t\t\t(cause) => Effect.logError(`Reloading ${file} failed`, cause),\n\t\t\t\t),\n\t\t\t),\n\t\t),\n\t\tEffect.catchCauseIf(\n\t\t\t(cause) => !Cause.hasInterrupts(cause),\n\t\t\t(cause) => Effect.logError(`Stopped watching ${file}`, cause),\n\t\t),\n\t\t// Observe the whole watcher before forkScoped owns it. The steps\n\t\t// before the stream cannot fail, so this covers every failure.\n\t\tEffect.onExit(logStopped(file)),\n\t)\n})\n"],"mappings":"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"}
@@ -16,11 +16,11 @@ const describe = (result) => result._tag === "Reloaded" ? Effect.logInfo(`Reload
16
16
  const watchModules = (hmr, directory, retry) => hmr.watch([directory], {
17
17
  recursive: true,
18
18
  filter: isModule
19
- }).pipe(Stream.runForEach((result) => Effect.gen(function* () {
19
+ }).pipe(Stream.runForEach(Effect.fnUntraced(function* (result) {
20
20
  if (result._tag === "Failed") return yield* Effect.failCause(result.cause);
21
21
  yield* describe(result);
22
22
  yield* retry;
23
- }).pipe(Effect.catchCauseIf((cause) => !Cause.hasInterrupts(cause), (cause) => Effect.logError("Reloading plugin modules failed", cause)))), Effect.catchCauseIf((cause) => !Cause.hasInterrupts(cause), (cause) => Effect.logError("Watching plugin modules failed", cause)), Effect.andThen(Effect.sleep("1 second")), Effect.forever, (effect) => observeShutdown(effect, "Module watcher stopped with interrupted cleanup"), Effect.onExit((exit) => Exit.isFailure(exit) && (Cause.hasFails(exit.cause) || Cause.hasDies(exit.cause)) ? Effect.logError("Module watcher exited with a failure", exit.cause) : Effect.void));
23
+ }, Effect.catchCauseIf((cause) => !Cause.hasInterrupts(cause), (cause) => Effect.logError("Reloading plugin modules failed", cause)))), Effect.catchCauseIf((cause) => !Cause.hasInterrupts(cause), (cause) => Effect.logError("Watching plugin modules failed", cause)), Effect.andThen(Effect.sleep("1 second")), Effect.forever, (effect) => observeShutdown(effect, "Module watcher stopped with interrupted cleanup"), Effect.onExit((exit) => Exit.isFailure(exit) && (Cause.hasFails(exit.cause) || Cause.hasDies(exit.cause)) ? Effect.logError("Module watcher exited with a failure", exit.cause) : Effect.void));
24
24
  export { isModule, watchModules };
25
25
 
26
26
  //# sourceMappingURL=watch.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"watch.js","names":[],"sources":["../../src/internal/watch.ts"],"sourcesContent":["import { observeShutdown } from \"./observeShutdown\"\nimport type * as Hmr from \"@graftland/hmr\"\nimport { Array, Cause, Effect, Exit, Stream, String } from \"effect\"\n\nconst moduleExtensions = [\".js\", \".mjs\", \".cjs\", \".ts\", \".mts\", \".cts\"]\n\n/** Whether a change to this file should reload plugin modules. */\nexport const isModule = (path: string) =>\n\t!Array.contains(String.split(/[/\\\\]/)(path), \"node_modules\") &&\n\tArray.some(moduleExtensions, (extension) => String.endsWith(extension)(path))\n\nconst describe = (result: Hmr.ReloadResult) =>\n\tresult._tag === \"Reloaded\"\n\t\t? Effect.logInfo(`Reloaded ${result.entries.length} plugin module(s)`)\n\t\t: Effect.logWarning(\n\t\t\t\tresult.reason === \"ambiguous-exports\"\n\t\t\t\t\t? `Restart required: shared plugin exports diverged in ${Array.join(result.modules, \", \")}`\n\t\t\t\t\t: result.reason === \"identity-conflict\"\n\t\t\t\t\t\t? `Restart required: plugin replacement identities conflict in ${Array.join(result.modules, \", \")}`\n\t\t\t\t\t\t: `Restart required to pick up ${Array.join(result.modules, \", \")}`,\n\t\t\t)\n\n/** Owns the HMR stream and retries failed loader entries after each reload. */\nexport const watchModules = <E, R>(\n\thmr: Pick<Hmr.Hmr, \"watch\">,\n\tdirectory: string,\n\tretry: Effect.Effect<void, E, R>,\n) =>\n\thmr.watch([directory], { recursive: true, filter: isModule }).pipe(\n\t\tStream.runForEach((result) =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tif (result._tag === \"Failed\")\n\t\t\t\t\treturn yield* Effect.failCause(result.cause)\n\t\t\t\tyield* describe(result)\n\t\t\t\tyield* retry\n\t\t\t}).pipe(\n\t\t\t\tEffect.catchCauseIf(\n\t\t\t\t\t(cause) => !Cause.hasInterrupts(cause),\n\t\t\t\t\t(cause) => Effect.logError(\"Reloading plugin modules failed\", cause),\n\t\t\t\t),\n\t\t\t),\n\t\t),\n\t\tEffect.catchCauseIf(\n\t\t\t(cause) => !Cause.hasInterrupts(cause),\n\t\t\t(cause) => Effect.logError(\"Watching plugin modules failed\", cause),\n\t\t),\n\t\t// A backend failure ends the stream. Reinstall it after a delay rather than\n\t\t// leaving the application running without a watcher.\n\t\tEffect.andThen(Effect.sleep(\"1 second\")),\n\t\tEffect.forever,\n\t\t(effect) =>\n\t\t\tobserveShutdown(\n\t\t\t\teffect,\n\t\t\t\t\"Module watcher stopped with interrupted cleanup\",\n\t\t\t),\n\t\t// onExit runs uninterruptibly, after stream cleanup. Observe the entire\n\t\t// worker before forkScoped owns it, including mixed interruption causes.\n\t\tEffect.onExit((exit) =>\n\t\t\tExit.isFailure(exit) &&\n\t\t\t(Cause.hasFails(exit.cause) || Cause.hasDies(exit.cause))\n\t\t\t\t? Effect.logError(\"Module watcher exited with a failure\", exit.cause)\n\t\t\t\t: Effect.void,\n\t\t),\n\t)\n"],"mappings":";;;AAIA,MAAM,mBAAmB;CAAC;CAAO;CAAQ;CAAQ;CAAO;CAAQ;AAAM;;AAGtE,MAAa,YAAY,SACxB,CAAC,MAAM,SAAS,OAAO,MAAM,OAAO,CAAC,CAAC,IAAI,GAAG,cAAc,KAC3D,MAAM,KAAK,mBAAmB,cAAc,OAAO,SAAS,SAAS,CAAC,CAAC,IAAI,CAAC;AAE7E,MAAM,YAAY,WACjB,OAAO,SAAS,aACb,OAAO,QAAQ,YAAY,OAAO,QAAQ,OAAO,kBAAkB,IACnE,OAAO,WACP,OAAO,WAAW,sBACf,uDAAuD,MAAM,KAAK,OAAO,SAAS,IAAI,MACtF,OAAO,WAAW,sBACjB,+DAA+D,MAAM,KAAK,OAAO,SAAS,IAAI,MAC9F,+BAA+B,MAAM,KAAK,OAAO,SAAS,IAAI,GACnE;;AAGH,MAAa,gBACZ,KACA,WACA,UAEA,IAAI,MAAM,CAAC,SAAS,GAAG;CAAE,WAAW;CAAM,QAAQ;AAAS,CAAC,CAAC,CAAC,KAC7D,OAAO,YAAY,WAClB,OAAO,IAAI,aAAa;CACvB,IAAI,OAAO,SAAS,UACnB,OAAO,OAAO,OAAO,UAAU,OAAO,KAAK;CAC5C,OAAO,SAAS,MAAM;CACtB,OAAO;AACR,CAAC,CAAC,CAAC,KACF,OAAO,cACL,UAAU,CAAC,MAAM,cAAc,KAAK,IACpC,UAAU,OAAO,SAAS,mCAAmC,KAAK,CACpE,CACD,CACD,GACA,OAAO,cACL,UAAU,CAAC,MAAM,cAAc,KAAK,IACpC,UAAU,OAAO,SAAS,kCAAkC,KAAK,CACnE,GAGA,OAAO,QAAQ,OAAO,MAAM,UAAU,CAAC,GACvC,OAAO,UACN,WACA,gBACC,QACA,iDACD,GAGD,OAAO,QAAQ,SACd,KAAK,UAAU,IAAI,MAClB,MAAM,SAAS,KAAK,KAAK,KAAK,MAAM,QAAQ,KAAK,KAAK,KACpD,OAAO,SAAS,wCAAwC,KAAK,KAAK,IAClE,OAAO,IACX,CACD"}
1
+ {"version":3,"file":"watch.js","names":[],"sources":["../../src/internal/watch.ts"],"sourcesContent":["import { observeShutdown } from \"./observeShutdown\"\nimport type * as Hmr from \"@graftland/hmr\"\nimport { Array, Cause, Effect, Exit, Stream, String } from \"effect\"\n\nconst moduleExtensions = [\".js\", \".mjs\", \".cjs\", \".ts\", \".mts\", \".cts\"]\n\n/** Whether a change to this file should reload plugin modules. */\nexport const isModule = (path: string) =>\n\t!Array.contains(String.split(/[/\\\\]/)(path), \"node_modules\") &&\n\tArray.some(moduleExtensions, (extension) => String.endsWith(extension)(path))\n\nconst describe = (result: Hmr.ReloadResult) =>\n\tresult._tag === \"Reloaded\"\n\t\t? Effect.logInfo(`Reloaded ${result.entries.length} plugin module(s)`)\n\t\t: Effect.logWarning(\n\t\t\t\tresult.reason === \"ambiguous-exports\"\n\t\t\t\t\t? `Restart required: shared plugin exports diverged in ${Array.join(result.modules, \", \")}`\n\t\t\t\t\t: result.reason === \"identity-conflict\"\n\t\t\t\t\t\t? `Restart required: plugin replacement identities conflict in ${Array.join(result.modules, \", \")}`\n\t\t\t\t\t\t: `Restart required to pick up ${Array.join(result.modules, \", \")}`,\n\t\t\t)\n\n/** Owns the HMR stream and retries failed loader entries after each reload. */\nexport const watchModules = <E, R>(\n\thmr: Pick<Hmr.Hmr, \"watch\">,\n\tdirectory: string,\n\tretry: Effect.Effect<void, E, R>,\n) =>\n\thmr.watch([directory], { recursive: true, filter: isModule }).pipe(\n\t\tStream.runForEach(\n\t\t\tEffect.fnUntraced(\n\t\t\t\tfunction* (result: Hmr.WatchResult) {\n\t\t\t\t\tif (result._tag === \"Failed\")\n\t\t\t\t\t\treturn yield* Effect.failCause(result.cause)\n\t\t\t\t\tyield* describe(result)\n\t\t\t\t\tyield* retry\n\t\t\t\t},\n\t\t\t\tEffect.catchCauseIf(\n\t\t\t\t\t(cause) => !Cause.hasInterrupts(cause),\n\t\t\t\t\t(cause) => Effect.logError(\"Reloading plugin modules failed\", cause),\n\t\t\t\t),\n\t\t\t),\n\t\t),\n\t\tEffect.catchCauseIf(\n\t\t\t(cause) => !Cause.hasInterrupts(cause),\n\t\t\t(cause) => Effect.logError(\"Watching plugin modules failed\", cause),\n\t\t),\n\t\t// A backend failure ends the stream. Reinstall it after a delay rather than\n\t\t// leaving the application running without a watcher.\n\t\tEffect.andThen(Effect.sleep(\"1 second\")),\n\t\tEffect.forever,\n\t\t(effect) =>\n\t\t\tobserveShutdown(\n\t\t\t\teffect,\n\t\t\t\t\"Module watcher stopped with interrupted cleanup\",\n\t\t\t),\n\t\t// onExit runs uninterruptibly, after stream cleanup. Observe the entire\n\t\t// worker before forkScoped owns it, including mixed interruption causes.\n\t\tEffect.onExit((exit) =>\n\t\t\tExit.isFailure(exit) &&\n\t\t\t(Cause.hasFails(exit.cause) || Cause.hasDies(exit.cause))\n\t\t\t\t? Effect.logError(\"Module watcher exited with a failure\", exit.cause)\n\t\t\t\t: Effect.void,\n\t\t),\n\t)\n"],"mappings":";;;AAIA,MAAM,mBAAmB;CAAC;CAAO;CAAQ;CAAQ;CAAO;CAAQ;AAAM;;AAGtE,MAAa,YAAY,SACxB,CAAC,MAAM,SAAS,OAAO,MAAM,OAAO,CAAC,CAAC,IAAI,GAAG,cAAc,KAC3D,MAAM,KAAK,mBAAmB,cAAc,OAAO,SAAS,SAAS,CAAC,CAAC,IAAI,CAAC;AAE7E,MAAM,YAAY,WACjB,OAAO,SAAS,aACb,OAAO,QAAQ,YAAY,OAAO,QAAQ,OAAO,kBAAkB,IACnE,OAAO,WACP,OAAO,WAAW,sBACf,uDAAuD,MAAM,KAAK,OAAO,SAAS,IAAI,MACtF,OAAO,WAAW,sBACjB,+DAA+D,MAAM,KAAK,OAAO,SAAS,IAAI,MAC9F,+BAA+B,MAAM,KAAK,OAAO,SAAS,IAAI,GACnE;;AAGH,MAAa,gBACZ,KACA,WACA,UAEA,IAAI,MAAM,CAAC,SAAS,GAAG;CAAE,WAAW;CAAM,QAAQ;AAAS,CAAC,CAAC,CAAC,KAC7D,OAAO,WACN,OAAO,WACN,WAAW,QAAyB;CACnC,IAAI,OAAO,SAAS,UACnB,OAAO,OAAO,OAAO,UAAU,OAAO,KAAK;CAC5C,OAAO,SAAS,MAAM;CACtB,OAAO;AACR,GACA,OAAO,cACL,UAAU,CAAC,MAAM,cAAc,KAAK,IACpC,UAAU,OAAO,SAAS,mCAAmC,KAAK,CACpE,CACD,CACD,GACA,OAAO,cACL,UAAU,CAAC,MAAM,cAAc,KAAK,IACpC,UAAU,OAAO,SAAS,kCAAkC,KAAK,CACnE,GAGA,OAAO,QAAQ,OAAO,MAAM,UAAU,CAAC,GACvC,OAAO,UACN,WACA,gBACC,QACA,iDACD,GAGD,OAAO,QAAQ,SACd,KAAK,UAAU,IAAI,MAClB,MAAM,SAAS,KAAK,KAAK,KAAK,MAAM,QAAQ,KAAK,KAAK,KACpD,OAAO,SAAS,wCAAwC,KAAK,KAAK,IAClE,OAAO,IACX,CACD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graftland/cli",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=22.19.0"
@@ -35,18 +35,18 @@
35
35
  "vitest": "5.1.0"
36
36
  },
37
37
  "dependencies": {
38
- "@graftland/graft": "0.3.1",
39
- "@graftland/loader": "0.3.1",
40
- "@graftland/hmr": "0.3.1",
41
- "@graftland/create": "0.3.1",
42
- "@graftland/package-manager": "0.3.1",
38
+ "@graftland/graft": "0.3.3",
39
+ "@graftland/loader": "0.3.3",
40
+ "@graftland/hmr": "0.3.3",
41
+ "@graftland/create": "0.3.3",
42
+ "@graftland/package-manager": "0.3.3",
43
43
  "@effect/platform-node": "4.1.2"
44
44
  },
45
45
  "bin": {
46
46
  "graft": "./dist/bin.js"
47
47
  },
48
48
  "scripts": {
49
- "build": "tsdown && node scripts/schema.mjs",
49
+ "build": "tsc -b && tsdown && node scripts/schema.mjs",
50
50
  "test": "vitest run"
51
51
  }
52
52
  }
package/src/Manage.ts CHANGED
@@ -44,51 +44,51 @@ const Spec = Schema.Union([
44
44
  ])
45
45
  const decodeSpecs = Schema.decodeEffect(Schema.Array(Spec))
46
46
 
47
- const install = (
47
+ const install = Effect.fn("Manage.install")(function* (
48
48
  directory: string,
49
49
  changes: Readonly<Record<string, string | null>>,
50
- ) =>
51
- Effect.gen(function* () {
52
- const decoded = yield* Schema.decodeEffect(PackageManager.Changes)(changes)
53
- // Set by npm, pnpm and Yarn when they run graft, such as through `pnpm dlx`.
54
- const userAgent = yield* Config.String("npm_config_user_agent").pipe(
55
- Config.withDefault(""),
56
- )
57
- const manager = yield* PackageManager.make({ directory, userAgent })
58
- const result = yield* manager.install(decoded)
59
- yield* report(result)
60
- })
50
+ ) {
51
+ const decoded = yield* Schema.decodeEffect(PackageManager.Changes)(changes)
52
+ // Set by npm, pnpm and Yarn when they run graft, such as through `pnpm dlx`.
53
+ const userAgent = yield* Config.String("npm_config_user_agent").pipe(
54
+ Config.withDefault(""),
55
+ )
56
+ const manager = yield* PackageManager.make({ directory, userAgent })
57
+ const result = yield* manager.install(decoded)
58
+ yield* report(result)
59
+ })
61
60
 
62
- export const create = (options: {
61
+ export const create = Effect.fn("Manage.create")(function* (options: {
63
62
  readonly directory: string
64
63
  readonly name: Option.Option<string>
65
64
  readonly install: boolean
66
- }) =>
67
- Effect.gen(function* () {
68
- const project = yield* Create.create({
69
- directory: options.directory,
70
- ...Option.match(options.name, {
71
- onNone: () => ({}),
72
- onSome: (name) => ({ name }),
73
- }),
74
- })
75
- yield* Effect.logInfo(`Created ${project.name} in ${project.directory}`)
76
- if (options.install) yield* install(project.directory, {})
65
+ }) {
66
+ const project = yield* Create.create({
67
+ directory: options.directory,
68
+ ...Option.match(options.name, {
69
+ onNone: () => ({}),
70
+ onSome: (name) => ({ name }),
71
+ }),
77
72
  })
73
+ yield* Effect.logInfo(`Created ${project.name} in ${project.directory}`)
74
+ if (options.install) yield* install(project.directory, {})
75
+ })
78
76
 
79
- export const add = (specs: ReadonlyArray<string>) =>
80
- Effect.flatMap(decodeSpecs(specs), (specs) =>
81
- install(
82
- ".",
83
- Record.fromEntries(
84
- Array.map(specs, (spec) =>
85
- Predicate.isString(spec)
86
- ? ([spec, "latest"] as const)
87
- : ([spec[0], spec[2]] as const),
88
- ),
77
+ export const add = Effect.fn("Manage.add")(function* (
78
+ specs: ReadonlyArray<string>,
79
+ ) {
80
+ const decoded = yield* decodeSpecs(specs)
81
+ yield* install(
82
+ ".",
83
+ Record.fromEntries(
84
+ Array.map(decoded, (spec) =>
85
+ Predicate.isString(spec)
86
+ ? ([spec, "latest"] as const)
87
+ : ([spec[0], spec[2]] as const),
89
88
  ),
90
89
  ),
91
90
  )
91
+ })
92
92
 
93
93
  export const remove = (names: ReadonlyArray<string>) =>
94
94
  install(
package/src/Start.ts CHANGED
@@ -29,9 +29,9 @@ export interface Options {
29
29
  * modules are imported through the hot reloader, so bare specifiers resolve
30
30
  * from the application directory whether or not `watch` is on.
31
31
  */
32
- export const run = (
32
+ export const run: (
33
33
  options: Options,
34
- ): Effect.Effect<
34
+ ) => Effect.Effect<
35
35
  never,
36
36
  | Graft.ServiceConflict
37
37
  | PlatformError.BadArgument
@@ -39,8 +39,8 @@ export const run = (
39
39
  | Schema.SchemaError
40
40
  | Loader.StoreError
41
41
  | Tree.InvalidOperation
42
- > =>
43
- Effect.gen(function* () {
42
+ > = Effect.fn("Start.run")(
43
+ function* (options: Options) {
44
44
  const path = yield* Path.Path
45
45
  const file = path.resolve(options.config)
46
46
  const directory = path.dirname(file)
@@ -69,17 +69,16 @@ export const run = (
69
69
  const reloader = Plugin.make({
70
70
  name: "reloader",
71
71
  inject: [Loader.Loader, FileSystem.FileSystem, Path.Path],
72
- apply: () =>
73
- Effect.gen(function* () {
74
- const loader = yield* Loader.Loader
75
- yield* watchModules(hmr, directory, loader.retry).pipe(
72
+ apply: Effect.fnUntraced(function* () {
73
+ const loader = yield* Loader.Loader
74
+ yield* watchModules(hmr, directory, loader.retry).pipe(
75
+ Effect.forkScoped,
76
+ )
77
+ if (envFile !== undefined)
78
+ yield* watchEnvFile(envFile, reload, loader.retry).pipe(
76
79
  Effect.forkScoped,
77
80
  )
78
- if (envFile !== undefined)
79
- yield* watchEnvFile(envFile, reload, loader.retry).pipe(
80
- Effect.forkScoped,
81
- )
82
- }),
81
+ }),
83
82
  })
84
83
  yield* Graft.plugin(reloader)
85
84
  }
@@ -88,10 +87,12 @@ export const run = (
88
87
  `Running ${path.relative(process.cwd(), file) || file}`,
89
88
  )
90
89
  return yield* Effect.never
91
- }).pipe(
92
- Effect.scoped,
90
+ },
91
+ Effect.scoped,
92
+ (effect, options) =>
93
93
  // @effect-diagnostics-next-line strictEffectProvide:off -- this runs the whole application, so it is an entry point
94
94
  Effect.provide(
95
+ effect,
95
96
  Layer.provideMerge(
96
97
  Graft.layer,
97
98
  Layer.mergeAll(
@@ -103,4 +104,4 @@ export const run = (
103
104
  ),
104
105
  ),
105
106
  ),
106
- )
107
+ )
@@ -95,75 +95,74 @@ const logStopped =
95
95
  * Reloads the file when it changes and retries the loader's entries, which
96
96
  * reads `!env` again. A file that cannot be read keeps its previous values.
97
97
  */
98
- export const watchEnvFile = <E, R>(
98
+ export const watchEnvFile = Effect.fnUntraced(function* <E, R>(
99
99
  file: string,
100
100
  reload: Effect.Effect<boolean, PlatformError.PlatformError>,
101
101
  retry: Effect.Effect<void, E, R>,
102
- ) =>
103
- Effect.gen(function* () {
104
- const fs = yield* FileSystem.FileSystem
105
- const path = yield* Path.Path
106
- const ready = yield* Deferred.make<void>()
107
- // As in HMR, own the source scope inside the producer. Observe its
108
- // complete Exit after cleanup, before merge can discard it.
109
- const events = Stream.callback<
110
- FileSystem.WatchEvent,
111
- PlatformError.PlatformError
112
- >(
113
- (queue) =>
114
- fs
115
- .watch(path.dirname(file), {
116
- onReady: () => Deferred.doneUnsafe(ready, Effect.void),
117
- })
118
- .pipe(
119
- Stream.filter(
120
- (event) => path.basename(event.path) === path.basename(file),
121
- ),
122
- Stream.runForEachArray((events) => Queue.offerAll(queue, events)),
123
- Effect.scoped,
124
- (effect) =>
125
- observeShutdown(
126
- effect,
127
- `Watching ${file} stopped with interrupted cleanup`,
128
- ),
129
- Effect.onExit(logStopped(file)),
130
- Effect.catchCauseIf(
131
- (cause) => !Cause.hasInterrupts(cause),
132
- (cause) => Effect.logError(`Watching ${file} failed`, cause),
133
- ),
134
- // A failed native backend must not stop content reconciliation.
135
- Effect.andThen(Effect.sleep("1 second")),
136
- Effect.forever,
102
+ ) {
103
+ const fs = yield* FileSystem.FileSystem
104
+ const path = yield* Path.Path
105
+ const ready = yield* Deferred.make<void>()
106
+ // As in HMR, own the source scope inside the producer. Observe its
107
+ // complete Exit after cleanup, before merge can discard it.
108
+ const events = Stream.callback<
109
+ FileSystem.WatchEvent,
110
+ PlatformError.PlatformError
111
+ >(
112
+ (queue) =>
113
+ fs
114
+ .watch(path.dirname(file), {
115
+ onReady: () => Deferred.doneUnsafe(ready, Effect.void),
116
+ })
117
+ .pipe(
118
+ Stream.filter(
119
+ (event) => path.basename(event.path) === path.basename(file),
137
120
  ),
138
- { bufferSize: 16 },
139
- )
140
- // Native events accelerate updates. Poll the request too: its target
141
- // may live outside this directory, change, or lose a notification.
142
- yield* Stream.mergeAll(
143
- [
144
- Stream.as(events, undefined),
145
- Stream.fromEffect(Deferred.await(ready)),
146
- Stream.tick("1 second").pipe(Stream.drop(1)),
147
- ],
148
- { concurrency: "unbounded" },
149
- ).pipe(
150
- Stream.runForEach(() =>
151
- // Delay to let writes settle.
152
- Effect.sleep("100 millis").pipe(
153
- Effect.andThen(reload),
154
- Effect.flatMap((changed) => (changed ? retry : Effect.void)),
121
+ Stream.runForEachArray((events) => Queue.offerAll(queue, events)),
122
+ Effect.scoped,
123
+ (effect) =>
124
+ observeShutdown(
125
+ effect,
126
+ `Watching ${file} stopped with interrupted cleanup`,
127
+ ),
128
+ Effect.onExit(logStopped(file)),
155
129
  Effect.catchCauseIf(
156
130
  (cause) => !Cause.hasInterrupts(cause),
157
- (cause) => Effect.logError(`Reloading ${file} failed`, cause),
131
+ (cause) => Effect.logError(`Watching ${file} failed`, cause),
158
132
  ),
133
+ // A failed native backend must not stop content reconciliation.
134
+ Effect.andThen(Effect.sleep("1 second")),
135
+ Effect.forever,
136
+ ),
137
+ { bufferSize: 16 },
138
+ )
139
+ // Native events accelerate updates. Poll the request too: its target
140
+ // may live outside this directory, change, or lose a notification.
141
+ yield* Stream.mergeAll(
142
+ [
143
+ Stream.as(events, undefined),
144
+ Stream.fromEffect(Deferred.await(ready)),
145
+ Stream.tick("1 second").pipe(Stream.drop(1)),
146
+ ],
147
+ { concurrency: "unbounded" },
148
+ ).pipe(
149
+ Stream.runForEach(() =>
150
+ // Delay to let writes settle.
151
+ Effect.sleep("100 millis").pipe(
152
+ Effect.andThen(reload),
153
+ Effect.flatMap((changed) => (changed ? retry : Effect.void)),
154
+ Effect.catchCauseIf(
155
+ (cause) => !Cause.hasInterrupts(cause),
156
+ (cause) => Effect.logError(`Reloading ${file} failed`, cause),
159
157
  ),
160
158
  ),
161
- Effect.catchCauseIf(
162
- (cause) => !Cause.hasInterrupts(cause),
163
- (cause) => Effect.logError(`Stopped watching ${file}`, cause),
164
- ),
165
- )
166
- }).pipe(
167
- // Observe the whole watcher before forkScoped owns it.
159
+ ),
160
+ Effect.catchCauseIf(
161
+ (cause) => !Cause.hasInterrupts(cause),
162
+ (cause) => Effect.logError(`Stopped watching ${file}`, cause),
163
+ ),
164
+ // Observe the whole watcher before forkScoped owns it. The steps
165
+ // before the stream cannot fail, so this covers every failure.
168
166
  Effect.onExit(logStopped(file)),
169
167
  )
168
+ })
@@ -27,13 +27,14 @@ export const watchModules = <E, R>(
27
27
  retry: Effect.Effect<void, E, R>,
28
28
  ) =>
29
29
  hmr.watch([directory], { recursive: true, filter: isModule }).pipe(
30
- Stream.runForEach((result) =>
31
- Effect.gen(function* () {
32
- if (result._tag === "Failed")
33
- return yield* Effect.failCause(result.cause)
34
- yield* describe(result)
35
- yield* retry
36
- }).pipe(
30
+ Stream.runForEach(
31
+ Effect.fnUntraced(
32
+ function* (result: Hmr.WatchResult) {
33
+ if (result._tag === "Failed")
34
+ return yield* Effect.failCause(result.cause)
35
+ yield* describe(result)
36
+ yield* retry
37
+ },
37
38
  Effect.catchCauseIf(
38
39
  (cause) => !Cause.hasInterrupts(cause),
39
40
  (cause) => Effect.logError("Reloading plugin modules failed", cause),